@luxalgo/vela 0.8.1 → 0.8.3
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/README.md +1 -1
- package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-oH78tYXL.d.ts} +1 -1
- package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-wbtJ3-Sa.d.cts} +1 -1
- package/dist/{chunk-DBDXKZUF.js → chunk-2IPXYM4B.js} +306 -177
- package/dist/{chunk-NELQJCGK.js → chunk-G7B7ZCBF.js} +167 -10
- package/dist/{chunk-ATGTCWHJ.js → chunk-HMHA733L.js} +473 -196
- package/dist/{chunk-EZ5FWVLA.js → chunk-KWI3YDBW.js} +343 -17
- package/dist/{chunk-LMBYEGUR.js → chunk-TO3GIK7Z.js} +71 -6
- package/dist/{chunk-BZQM2XO7.js → chunk-VHGACEHO.js} +62 -1
- package/dist/{contributions-tjNExo1o.d.ts → contributions-BhOBRenr.d.cts} +79 -12
- package/dist/{contributions-CtlptxR8.d.cts → contributions-HGqnNb_P.d.ts} +79 -12
- package/dist/index.cjs +733 -113
- package/dist/index.d.cts +34 -14
- package/dist/index.d.ts +34 -14
- package/dist/index.js +5 -5
- package/dist/{options-BnEPUuVV.d.ts → options-lMUwmRwh.d.cts} +69 -4
- package/dist/{options-BnEPUuVV.d.cts → options-lMUwmRwh.d.ts} +69 -4
- package/dist/{plugin-e2WUEtiI.d.ts → plugin-Cr2Pj8q8.d.ts} +4 -4
- package/dist/{plugin-BWxJRKzS.d.cts → plugin-Do5J5xuF.d.cts} +4 -4
- package/dist/plugin.cjs +38 -6
- 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-HF0IAzwf.d.cts → side-panel-_2UK1gSm.d.cts} +58 -3
- package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-_2UK1gSm.d.ts} +58 -3
- package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-B-2xGlB_.d.cts} +3 -3
- package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-CY1wzxHg.d.ts} +3 -3
- package/dist/ui.cjs +298 -13
- package/dist/ui.d.cts +132 -74
- package/dist/ui.d.ts +132 -74
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +734 -114
- package/dist/vela.global.min.js +86 -52
- package/dist/widget.cjs +1227 -211
- package/dist/widget.d.cts +36 -12
- package/dist/widget.d.ts +36 -12
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1226 -211
- package/dist/workspace.d.cts +47 -20
- package/dist/workspace.d.ts +47 -20
- package/dist/workspace.js +6 -6
- package/package.json +2 -2
- /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, 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-HMHA733L.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-KWI3YDBW.js';
|
|
3
|
+
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-TO3GIK7Z.js';
|
|
4
|
+
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-G7B7ZCBF.js';
|
|
5
|
+
import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, announceSurface, holdForExit, icon, categoricalColor } from './chunk-VHGACEHO.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) => {
|
|
@@ -261,6 +263,7 @@ var LayoutPicker = class {
|
|
|
261
263
|
const panel = doc.createElement("div");
|
|
262
264
|
panel.className = "vela-lp";
|
|
263
265
|
this.layer.appendChild(panel);
|
|
266
|
+
this.panel = panel;
|
|
264
267
|
const cols = doc.createElement("div");
|
|
265
268
|
cols.className = "vela-lp-cols";
|
|
266
269
|
panel.appendChild(cols);
|
|
@@ -334,6 +337,8 @@ var LayoutPicker = class {
|
|
|
334
337
|
open() {
|
|
335
338
|
if (this.isOpen) return;
|
|
336
339
|
this.isOpen = true;
|
|
340
|
+
this.exit?.cancel();
|
|
341
|
+
this.exit = null;
|
|
337
342
|
this.hover = null;
|
|
338
343
|
this.refresh();
|
|
339
344
|
this.layer.style.display = "";
|
|
@@ -342,11 +347,16 @@ var LayoutPicker = class {
|
|
|
342
347
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
343
348
|
this.doc.addEventListener("keydown", this.onDocKeydown, true);
|
|
344
349
|
this.opts.onOpenChange?.(true);
|
|
350
|
+
if (this.isOpen) announceSurface(this.panel, true, "popover", this.opts.trigger);
|
|
345
351
|
}
|
|
346
352
|
close() {
|
|
347
353
|
if (!this.isOpen) return;
|
|
348
354
|
this.isOpen = false;
|
|
349
|
-
this.
|
|
355
|
+
announceSurface(this.panel, false, "popover", this.opts.trigger);
|
|
356
|
+
this.exit = holdForExit(this.panel, () => {
|
|
357
|
+
this.exit = null;
|
|
358
|
+
if (!this.isOpen) this.layer.style.display = "none";
|
|
359
|
+
});
|
|
350
360
|
this.opts.trigger.setAttribute("aria-expanded", "false");
|
|
351
361
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
352
362
|
this.doc.removeEventListener("keydown", this.onDocKeydown, true);
|
|
@@ -361,6 +371,7 @@ var LayoutPicker = class {
|
|
|
361
371
|
}
|
|
362
372
|
destroy() {
|
|
363
373
|
this.close();
|
|
374
|
+
this.exit?.finish();
|
|
364
375
|
this.infoTip.destroy();
|
|
365
376
|
this.layer.remove();
|
|
366
377
|
}
|
|
@@ -427,7 +438,7 @@ var LayoutPicker = class {
|
|
|
427
438
|
const hostRect = this.opts.host.getBoundingClientRect();
|
|
428
439
|
const trigRect = this.opts.trigger.getBoundingClientRect();
|
|
429
440
|
let left = trigRect.left - hostRect.left;
|
|
430
|
-
const top = trigRect.bottom - hostRect.top +
|
|
441
|
+
const top = trigRect.bottom - hostRect.top + 8;
|
|
431
442
|
const width = this.layer.offsetWidth;
|
|
432
443
|
if (left + width > hostRect.width - 8) left = Math.max(8, hostRect.width - 8 - width);
|
|
433
444
|
this.layer.style.left = `${left}px`;
|
|
@@ -780,7 +791,8 @@ var Topbar = class {
|
|
|
780
791
|
triggerId: "vela-topbar-style",
|
|
781
792
|
host,
|
|
782
793
|
items: this.styleItems(),
|
|
783
|
-
onSelect: (id) => opts.onPriceStyle(id)
|
|
794
|
+
onSelect: (id) => opts.onPriceStyle(id),
|
|
795
|
+
iconBadges: true
|
|
784
796
|
});
|
|
785
797
|
}
|
|
786
798
|
setSymbol(symbol) {
|
|
@@ -846,8 +858,14 @@ var Topbar = class {
|
|
|
846
858
|
renderLayoutButton(doc) {
|
|
847
859
|
if (!this.layoutButton) return;
|
|
848
860
|
this.layoutButton.replaceChildren();
|
|
849
|
-
|
|
850
|
-
|
|
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
|
+
}
|
|
851
869
|
this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
|
|
852
870
|
}
|
|
853
871
|
renderStyleButton(doc) {
|
|
@@ -1701,8 +1719,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
1701
1719
|
set groups(next) {
|
|
1702
1720
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
1703
1721
|
}
|
|
1704
|
-
toggle(open = this.
|
|
1705
|
-
super.toggle(open);
|
|
1722
|
+
toggle(open = !this.open, instant = false) {
|
|
1723
|
+
super.toggle(open, instant);
|
|
1706
1724
|
if (open) this.refresh();
|
|
1707
1725
|
}
|
|
1708
1726
|
setSymbol(symbol) {
|
|
@@ -2736,8 +2754,8 @@ var DataWindow = class extends SidePanel {
|
|
|
2736
2754
|
this.unsubs = [];
|
|
2737
2755
|
injectStyles(STYLE_ID5, CSS5, host.ownerDocument);
|
|
2738
2756
|
}
|
|
2739
|
-
toggle(open = this.
|
|
2740
|
-
super.toggle(open);
|
|
2757
|
+
toggle(open = !this.open, instant = false) {
|
|
2758
|
+
super.toggle(open, instant);
|
|
2741
2759
|
if (open) this.refresh();
|
|
2742
2760
|
}
|
|
2743
2761
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
@@ -2813,19 +2831,22 @@ var PanelDock = class {
|
|
|
2813
2831
|
/**
|
|
2814
2832
|
* (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
|
|
2815
2833
|
* again on `refreshActions()` so a late registration appears. Contributed panels that are
|
|
2816
|
-
* gone from the registry are dropped
|
|
2817
|
-
* descriptor
|
|
2834
|
+
* gone from the registry are dropped and a REPLACED descriptor is rebuilt; a panel whose
|
|
2835
|
+
* descriptor is unchanged stays mounted as it is, so its content and state survive.
|
|
2818
2836
|
*/
|
|
2819
2837
|
refresh() {
|
|
2820
2838
|
const openBefore = this.openId;
|
|
2821
|
-
|
|
2822
|
-
for (const
|
|
2839
|
+
const registered = sidePanels();
|
|
2840
|
+
for (const entry of [...this.entries]) if (entry.contributed && !registered.includes(entry.desc)) this.drop(entry);
|
|
2841
|
+
for (const desc of registered) {
|
|
2842
|
+
if (this.entries.some((e) => e.desc === desc)) continue;
|
|
2823
2843
|
const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
|
|
2824
2844
|
width: desc.width,
|
|
2825
2845
|
resizable: desc.resizable,
|
|
2826
2846
|
minWidth: desc.minWidth,
|
|
2827
2847
|
maxWidth: desc.maxWidth,
|
|
2828
|
-
overlay: desc.overlay
|
|
2848
|
+
overlay: desc.overlay,
|
|
2849
|
+
maximizable: desc.maximizable
|
|
2829
2850
|
});
|
|
2830
2851
|
const entry = {
|
|
2831
2852
|
id: desc.id,
|
|
@@ -2833,10 +2854,19 @@ var PanelDock = class {
|
|
|
2833
2854
|
icon: desc.icon,
|
|
2834
2855
|
order: desc.order ?? DEFAULT_PANEL_ORDER,
|
|
2835
2856
|
panel,
|
|
2836
|
-
contributed: true
|
|
2857
|
+
contributed: true,
|
|
2858
|
+
desc
|
|
2837
2859
|
};
|
|
2838
2860
|
try {
|
|
2839
|
-
|
|
2861
|
+
const header = {
|
|
2862
|
+
slot: panel.headerSlot,
|
|
2863
|
+
setTitle: (t) => panel.setTitle(t),
|
|
2864
|
+
get maximized() {
|
|
2865
|
+
return panel.maximized;
|
|
2866
|
+
},
|
|
2867
|
+
setMaximized: (on) => panel.setMaximized(on)
|
|
2868
|
+
};
|
|
2869
|
+
entry.handle = desc.mount(this.deps.context(), panel.content, header) ?? void 0;
|
|
2840
2870
|
if (this.chart) entry.handle?.onChart?.(this.chart);
|
|
2841
2871
|
} catch (err) {
|
|
2842
2872
|
console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
|
|
@@ -2895,7 +2925,7 @@ var PanelDock = class {
|
|
|
2895
2925
|
}
|
|
2896
2926
|
this.pinned = new Set(state.pinned ?? []);
|
|
2897
2927
|
for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
|
|
2898
|
-
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);
|
|
2899
2929
|
this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
|
|
2900
2930
|
}
|
|
2901
2931
|
/** Drop the contributed panels (the shell destroys its own). */
|
|
@@ -2911,7 +2941,7 @@ var PanelDock = class {
|
|
|
2911
2941
|
if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
|
|
2912
2942
|
entry.panel.onOpenChange = (open) => {
|
|
2913
2943
|
if (open) {
|
|
2914
|
-
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);
|
|
2915
2945
|
this.pendingOpen = null;
|
|
2916
2946
|
}
|
|
2917
2947
|
this.deps.chrome.setPanelActive(entry.id, open);
|
|
@@ -4027,15 +4057,48 @@ function syncTargets(originId, setting, cellIds) {
|
|
|
4027
4057
|
function rangesWithin(a, b, epsMs) {
|
|
4028
4058
|
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
4029
4059
|
}
|
|
4030
|
-
var STYLE_SYNC_CONFIG_KEYS = [
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4060
|
+
var STYLE_SYNC_CONFIG_KEYS = [
|
|
4061
|
+
"layout",
|
|
4062
|
+
"panes",
|
|
4063
|
+
"grid",
|
|
4064
|
+
"margins",
|
|
4065
|
+
"priceScale",
|
|
4066
|
+
"crosshair",
|
|
4067
|
+
"animations",
|
|
4068
|
+
"candles",
|
|
4069
|
+
"bars",
|
|
4070
|
+
"line",
|
|
4071
|
+
"area",
|
|
4072
|
+
"baseline",
|
|
4073
|
+
"sessions"
|
|
4074
|
+
];
|
|
4075
|
+
function styleConfigPatch(origin, follower) {
|
|
4076
|
+
const from = asRecord(origin);
|
|
4077
|
+
const to = asRecord(follower);
|
|
4078
|
+
if (!from || !to) return null;
|
|
4079
|
+
const patch = {};
|
|
4035
4080
|
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
4036
|
-
|
|
4081
|
+
const block = asRecord(from[key]);
|
|
4082
|
+
if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
|
|
4037
4083
|
}
|
|
4038
|
-
|
|
4084
|
+
const spacing = asRecord(from.series)?.spacing;
|
|
4085
|
+
if (typeof spacing === "number" && spacing !== asRecord(to.series)?.spacing) patch.series = { spacing };
|
|
4086
|
+
const fromTypes = asRecord(from.chartTypes) ?? {};
|
|
4087
|
+
const toTypes = asRecord(to.chartTypes) ?? {};
|
|
4088
|
+
const types = {};
|
|
4089
|
+
for (const typeId of /* @__PURE__ */ new Set([...Object.keys(fromTypes), ...Object.keys(toTypes)])) {
|
|
4090
|
+
const src = asRecord(fromTypes[typeId]) ?? {};
|
|
4091
|
+
const dst = asRecord(toTypes[typeId]) ?? {};
|
|
4092
|
+
for (const key of CANDLE_OVERRIDE_KEYS) {
|
|
4093
|
+
const value = src[key] ?? null;
|
|
4094
|
+
if (value !== (dst[key] ?? null)) (types[typeId] ?? (types[typeId] = {}))[key] = value;
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
if (Object.keys(types).length > 0) patch.chartTypes = types;
|
|
4098
|
+
return Object.keys(patch).length > 0 ? patch : null;
|
|
4099
|
+
}
|
|
4100
|
+
function asRecord(v) {
|
|
4101
|
+
return v != null && typeof v === "object" ? v : null;
|
|
4039
4102
|
}
|
|
4040
4103
|
|
|
4041
4104
|
// src/workspace/persist.ts
|
|
@@ -4223,6 +4286,9 @@ var CSS10 = `
|
|
|
4223
4286
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
4224
4287
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
4225
4288
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
4289
|
+
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
4290
|
+
* its digits keeps its width \u2014 render() skips the ladder for those (see widthKey). */
|
|
4291
|
+
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
4226
4292
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
4227
4293
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
4228
4294
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -4340,6 +4406,10 @@ var Statusline = class {
|
|
|
4340
4406
|
this.fitRO = null;
|
|
4341
4407
|
/** The ladder rung currently applied — see {@link fit}. */
|
|
4342
4408
|
this.layout = { stacked: false, level: "full" };
|
|
4409
|
+
/** The readout {@link widthKey} the applied rung was fitted to. */
|
|
4410
|
+
this.fitKey = null;
|
|
4411
|
+
/** Web fonts landing after the last fit change every width without a resize. */
|
|
4412
|
+
this.onFontsLoaded = () => this.fit();
|
|
4343
4413
|
this.onContextMenu = (e) => {
|
|
4344
4414
|
if (!this.menu || !this.menuHooks) return;
|
|
4345
4415
|
e.preventDefault();
|
|
@@ -4403,6 +4473,7 @@ var Statusline = class {
|
|
|
4403
4473
|
this.fitRO = new ResizeObserver(() => this.fit());
|
|
4404
4474
|
this.fitRO.observe(host);
|
|
4405
4475
|
}
|
|
4476
|
+
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
4406
4477
|
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
4407
4478
|
this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
|
|
4408
4479
|
this.setMarketStatus("open");
|
|
@@ -4455,6 +4526,7 @@ var Statusline = class {
|
|
|
4455
4526
|
* (detached host, node tests) nothing overflows, so the widest rung stays.
|
|
4456
4527
|
*/
|
|
4457
4528
|
fit() {
|
|
4529
|
+
this.fitKey = this.widthKey();
|
|
4458
4530
|
this.syncParts();
|
|
4459
4531
|
const seg = segmentVisibility(this.parts, this.chartHidden);
|
|
4460
4532
|
const overflows = () => this.el.scrollWidth > this.el.clientWidth;
|
|
@@ -4611,6 +4683,7 @@ var Statusline = class {
|
|
|
4611
4683
|
this.detach();
|
|
4612
4684
|
this.fitRO?.disconnect();
|
|
4613
4685
|
this.fitRO = null;
|
|
4686
|
+
this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
4614
4687
|
this.el.removeEventListener("contextmenu", this.onContextMenu);
|
|
4615
4688
|
this.menu?.destroy();
|
|
4616
4689
|
this.menu = null;
|
|
@@ -4626,7 +4699,22 @@ var Statusline = class {
|
|
|
4626
4699
|
}
|
|
4627
4700
|
render() {
|
|
4628
4701
|
if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
|
|
4629
|
-
this.
|
|
4702
|
+
if (this.widthKey() === this.fitKey) this.renderValues();
|
|
4703
|
+
else this.fit();
|
|
4704
|
+
}
|
|
4705
|
+
/** Everything the readout's width depends on, at every rung: its shape, and each
|
|
4706
|
+
* value's sign, digit count and decimals (the digits themselves are tabular — see
|
|
4707
|
+
* the CSS; the grouping commas follow from the digit count). `toFixed` carries all
|
|
4708
|
+
* three at a fraction of the locale formatter's cost — this runs per pointer move.
|
|
4709
|
+
* The parts, the host's size and the fonts re-fit through their own paths. */
|
|
4710
|
+
widthKey() {
|
|
4711
|
+
const bar = this.hoverBar ?? this.lastBar;
|
|
4712
|
+
if (!bar) return this.readout;
|
|
4713
|
+
const dp = decimalsFor(bar.close);
|
|
4714
|
+
const diff = bar.close - bar.open;
|
|
4715
|
+
const text = [bar.open, bar.high, bar.low, bar.close, diff].map((v) => v.toFixed(dp));
|
|
4716
|
+
text.push((diff / bar.open * 100).toFixed(2));
|
|
4717
|
+
return `${this.readout} ${text.join(" ")}`.replace(/\d/g, "0");
|
|
4630
4718
|
}
|
|
4631
4719
|
/** Write the value readout for the current bar at the current ladder level. */
|
|
4632
4720
|
renderValues() {
|
|
@@ -4772,113 +4860,28 @@ var Watermark = class {
|
|
|
4772
4860
|
}
|
|
4773
4861
|
};
|
|
4774
4862
|
|
|
4775
|
-
// src/widget/context-menu-model.ts
|
|
4776
|
-
var SETTINGS_SECTION = {
|
|
4777
|
-
body: "Canvas",
|
|
4778
|
-
"price-axis": "Scales and lines",
|
|
4779
|
-
"time-axis": "Scales and lines"
|
|
4780
|
-
};
|
|
4781
|
-
function settingsItem(zone, label) {
|
|
4782
|
-
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, separatorBefore: true };
|
|
4783
|
-
}
|
|
4784
|
-
function settingsSectionOf(id) {
|
|
4785
|
-
return id.slice("settings:".length) || void 0;
|
|
4786
|
-
}
|
|
4787
|
-
var SCALE_CHOICES = [
|
|
4788
|
-
["regular", "Regular"],
|
|
4789
|
-
["percent", "Percent"],
|
|
4790
|
-
["indexed", "Indexed to 100"],
|
|
4791
|
-
["log", "Logarithmic"]
|
|
4792
|
-
];
|
|
4793
|
-
function paneScaleAt(panes, y) {
|
|
4794
|
-
return panes.find((p) => y >= p.top && y < p.top + p.height) ?? panes[0] ?? null;
|
|
4795
|
-
}
|
|
4796
|
-
function scaleChoiceOf(scale) {
|
|
4797
|
-
if (scale.log) return "log";
|
|
4798
|
-
if (scale.mode === "percent") return "percent";
|
|
4799
|
-
if (scale.mode === "indexed") return "indexed";
|
|
4800
|
-
return "regular";
|
|
4801
|
-
}
|
|
4802
|
-
function mainScale(pane) {
|
|
4803
|
-
return pane === null || pane.kind === "price";
|
|
4804
|
-
}
|
|
4805
|
-
function scaleWrites(choice, pane) {
|
|
4806
|
-
const mode = choice === "percent" ? "percent" : choice === "indexed" ? "indexed" : "price";
|
|
4807
|
-
const log = choice === "log";
|
|
4808
|
-
if (mainScale(pane)) {
|
|
4809
|
-
return [
|
|
4810
|
-
["scaleMode", mode],
|
|
4811
|
-
["logScale", log]
|
|
4812
|
-
];
|
|
4813
|
-
}
|
|
4814
|
-
return [
|
|
4815
|
-
["scaleMode", { pane: pane.id, mode }],
|
|
4816
|
-
["logScale", { pane: pane.id, value: log }]
|
|
4817
|
-
];
|
|
4818
|
-
}
|
|
4819
|
-
function invertWrite(next, pane) {
|
|
4820
|
-
return mainScale(pane) ? ["invertScale", next] : ["invertScale", { pane: pane.id, value: next }];
|
|
4821
|
-
}
|
|
4822
|
-
function priceAxisItems(s) {
|
|
4823
|
-
return [
|
|
4824
|
-
{ id: "auto", label: "Auto (fits data to screen)", checked: s.auto },
|
|
4825
|
-
{ id: "invert", label: "Invert scale", checked: s.invert },
|
|
4826
|
-
...SCALE_CHOICES.map(([choice, label], i) => ({
|
|
4827
|
-
id: `scale:${choice}`,
|
|
4828
|
-
label,
|
|
4829
|
-
checked: s.choice === choice,
|
|
4830
|
-
separatorBefore: i === 0
|
|
4831
|
-
})),
|
|
4832
|
-
{
|
|
4833
|
-
id: "labels",
|
|
4834
|
-
label: "Labels",
|
|
4835
|
-
separatorBefore: true,
|
|
4836
|
-
submenu: [
|
|
4837
|
-
{ id: "toggle:axisLabels", label: "Price axis labels", checked: s.axisLabels },
|
|
4838
|
-
{ id: "toggle:priceLabel", label: "Last price label", checked: s.priceLabel },
|
|
4839
|
-
{ id: "toggle:countdown", label: "Countdown to bar close", checked: s.countdown }
|
|
4840
|
-
]
|
|
4841
|
-
},
|
|
4842
|
-
{
|
|
4843
|
-
id: "levels",
|
|
4844
|
-
label: "Levels",
|
|
4845
|
-
submenu: [{ id: "toggle:currentPriceLine", label: "Last Price Line", checked: s.priceLine }]
|
|
4846
|
-
},
|
|
4847
|
-
settingsItem("price-axis", "More settings\u2026")
|
|
4848
|
-
];
|
|
4849
|
-
}
|
|
4850
|
-
function timeAxisItems(timezone) {
|
|
4851
|
-
return [
|
|
4852
|
-
{
|
|
4853
|
-
id: "timezone",
|
|
4854
|
-
label: "Time zone",
|
|
4855
|
-
submenu: timezoneMenuRows(timezone).map((r) => ({ id: `tz:${r.value}`, label: r.label, checked: r.checked }))
|
|
4856
|
-
},
|
|
4857
|
-
settingsItem("time-axis", "More settings\u2026")
|
|
4858
|
-
];
|
|
4859
|
-
}
|
|
4860
|
-
function bodyItems(counts) {
|
|
4861
|
-
return [
|
|
4862
|
-
{ id: "reset-view", label: "Reset chart view" },
|
|
4863
|
-
{ id: "remove-drawings", label: "Remove drawings", disabled: counts.drawings === 0, separatorBefore: true },
|
|
4864
|
-
{ id: "remove-indicators", label: "Remove indicators", disabled: counts.indicators === 0 },
|
|
4865
|
-
settingsItem("body", "Settings\u2026")
|
|
4866
|
-
];
|
|
4867
|
-
}
|
|
4868
|
-
|
|
4869
4863
|
// src/widget/context-menu.ts
|
|
4870
4864
|
var PRICE_AXIS_W = 60;
|
|
4871
4865
|
var TIME_AXIS_H = 26;
|
|
4866
|
+
var OFF_PLOT = { price: null, time: null, paneKind: null };
|
|
4872
4867
|
var ChartContextMenu = class {
|
|
4873
4868
|
constructor(host, cbs) {
|
|
4874
4869
|
this.cbs = cbs;
|
|
4875
4870
|
this.chart = null;
|
|
4871
|
+
this.offCrosshair = null;
|
|
4872
|
+
/** The bound chart's latest crosshair report — a right-click always follows a pointer
|
|
4873
|
+
* move to its spot, so this is where it landed. */
|
|
4874
|
+
this.crosshair = OFF_PLOT;
|
|
4875
|
+
/** {@link crosshair} as it stood when the menu opened: the pointer moves on through the
|
|
4876
|
+
* menu before a row is picked, and `run` must see the right-clicked spot. */
|
|
4877
|
+
this.pointer = OFF_PLOT;
|
|
4876
4878
|
this.lastZone = "body";
|
|
4877
4879
|
/** The pane whose scale the open price-axis menu targets (null ⇒ the main scale). */
|
|
4878
4880
|
this.lastPane = null;
|
|
4879
4881
|
this.onContextMenu = (e) => {
|
|
4880
4882
|
e.preventDefault();
|
|
4881
4883
|
if (!this.chart) return;
|
|
4884
|
+
this.pointer = this.crosshair;
|
|
4882
4885
|
this.lastZone = this.zoneOf(e);
|
|
4883
4886
|
this.lastPane = this.lastZone === "price-axis" ? this.paneAt(e) : null;
|
|
4884
4887
|
this.menu.setItems(this.itemsFor(this.lastZone));
|
|
@@ -4892,15 +4895,23 @@ var ChartContextMenu = class {
|
|
|
4892
4895
|
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
4893
4896
|
// washed row (which would read as hover in a menu with no trigger button).
|
|
4894
4897
|
checkmarks: true,
|
|
4898
|
+
iconBadges: true,
|
|
4895
4899
|
onSelect: (id) => this.run(id)
|
|
4896
4900
|
});
|
|
4897
4901
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
4898
4902
|
}
|
|
4899
4903
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
4900
4904
|
onChart(chart) {
|
|
4905
|
+
this.offCrosshair?.();
|
|
4901
4906
|
this.chart = chart;
|
|
4907
|
+
this.crosshair = OFF_PLOT;
|
|
4908
|
+
this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
|
|
4909
|
+
this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
|
|
4910
|
+
});
|
|
4902
4911
|
}
|
|
4903
4912
|
destroy() {
|
|
4913
|
+
this.offCrosshair?.();
|
|
4914
|
+
this.offCrosshair = null;
|
|
4904
4915
|
this.host.removeEventListener("contextmenu", this.onContextMenu);
|
|
4905
4916
|
this.menu.destroy();
|
|
4906
4917
|
}
|
|
@@ -4919,49 +4930,52 @@ var ChartContextMenu = class {
|
|
|
4919
4930
|
flag(feature) {
|
|
4920
4931
|
return Boolean(this.chart?.renderer.get(feature));
|
|
4921
4932
|
}
|
|
4922
|
-
|
|
4933
|
+
/** A fresh widget context carrying the pointer captured at open. Copied by property
|
|
4934
|
+
* descriptor: the context's getters must stay LIVE, and a spread would freeze them. */
|
|
4935
|
+
context() {
|
|
4923
4936
|
const ctx = this.cbs.getContext?.();
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4937
|
+
if (!ctx) return void 0;
|
|
4938
|
+
return Object.create(Object.getPrototypeOf(ctx), {
|
|
4939
|
+
...Object.getOwnPropertyDescriptors(ctx),
|
|
4940
|
+
pointer: { value: this.pointer, enumerable: true }
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4943
|
+
contributed(zone) {
|
|
4944
|
+
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
4945
|
+
item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
|
|
4946
|
+
order: a.order
|
|
4929
4947
|
}));
|
|
4930
4948
|
}
|
|
4931
4949
|
itemsFor(zone) {
|
|
4950
|
+
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
4951
|
+
}
|
|
4952
|
+
builtinItems(zone) {
|
|
4932
4953
|
if (zone === "price-axis") {
|
|
4933
4954
|
const pane = this.lastPane;
|
|
4934
|
-
return
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
}),
|
|
4944
|
-
...this.contributed(zone)
|
|
4945
|
-
];
|
|
4955
|
+
return priceAxisItems({
|
|
4956
|
+
auto: this.chart?.renderer.get("autoScale") !== false,
|
|
4957
|
+
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
4958
|
+
choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
4959
|
+
axisLabels: this.flag("axisLabels"),
|
|
4960
|
+
priceLabel: this.flag("priceLabel"),
|
|
4961
|
+
countdown: this.flag("countdown"),
|
|
4962
|
+
priceLine: this.flag("currentPriceLine")
|
|
4963
|
+
});
|
|
4946
4964
|
}
|
|
4947
4965
|
if (zone === "time-axis") {
|
|
4948
|
-
|
|
4949
|
-
return [...timeAxisItems(tz), ...this.contributed("time-axis")];
|
|
4966
|
+
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
|
|
4950
4967
|
}
|
|
4951
4968
|
const chart = this.chart;
|
|
4952
|
-
return
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
}),
|
|
4957
|
-
...this.contributed("body")
|
|
4958
|
-
];
|
|
4969
|
+
return bodyItems({
|
|
4970
|
+
drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
|
|
4971
|
+
indicators: chart?.indicators().length ?? 0
|
|
4972
|
+
});
|
|
4959
4973
|
}
|
|
4960
4974
|
run(id) {
|
|
4961
4975
|
const chart = this.chart;
|
|
4962
4976
|
if (!chart) return;
|
|
4963
4977
|
if (id.startsWith("action:")) {
|
|
4964
|
-
const ctx = this.
|
|
4978
|
+
const ctx = this.context();
|
|
4965
4979
|
if (ctx) widgetActions(`context:${this.lastZone}`, ctx).find((a) => a.id === id.slice("action:".length))?.run(ctx);
|
|
4966
4980
|
return;
|
|
4967
4981
|
}
|
|
@@ -5611,8 +5625,8 @@ function seedDefaults(opts) {
|
|
|
5611
5625
|
};
|
|
5612
5626
|
}
|
|
5613
5627
|
function cellChartDefaults(opts) {
|
|
5614
|
-
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings } = opts;
|
|
5615
|
-
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings };
|
|
5628
|
+
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis } = opts;
|
|
5629
|
+
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis };
|
|
5616
5630
|
}
|
|
5617
5631
|
function cellDrawings(opt) {
|
|
5618
5632
|
if (opt === false) return false;
|
|
@@ -6182,12 +6196,14 @@ var ChartCell = class {
|
|
|
6182
6196
|
this.watermarkOn = visible;
|
|
6183
6197
|
this.watermark?.setVisible(visible);
|
|
6184
6198
|
this.deps.onStateDirty();
|
|
6199
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
6185
6200
|
}
|
|
6186
6201
|
/** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
|
|
6187
6202
|
setReplayWatermarkVisible(visible) {
|
|
6188
6203
|
this.replayWatermarkOn = visible;
|
|
6189
6204
|
this.watermark?.setReplayVisible(visible);
|
|
6190
6205
|
this.deps.onStateDirty();
|
|
6206
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
6191
6207
|
}
|
|
6192
6208
|
/** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
|
|
6193
6209
|
setIndicatorTitlesVisible(visible) {
|
|
@@ -6208,18 +6224,20 @@ var ChartCell = class {
|
|
|
6208
6224
|
this.statusline?.setPartVisible(part, visible);
|
|
6209
6225
|
this.deps.onStatusPrefsChanged(this.id);
|
|
6210
6226
|
}
|
|
6211
|
-
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
6227
|
+
/** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
6212
6228
|
statusPrefs() {
|
|
6213
6229
|
const sl = this.statusline;
|
|
6214
6230
|
return {
|
|
6215
6231
|
parts: sl ? { logo: sl.partVisible("logo"), name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
6216
6232
|
indicatorTitles: this.indicatorTitlesOn,
|
|
6217
|
-
indicatorValues: this.indicatorValuesOn
|
|
6233
|
+
indicatorValues: this.indicatorValuesOn,
|
|
6234
|
+
watermark: this.watermarkOn,
|
|
6235
|
+
replayWatermark: this.replayWatermarkOn
|
|
6218
6236
|
};
|
|
6219
6237
|
}
|
|
6220
|
-
/** Converge this cell's Status line tab prefs to `prefs` — the
|
|
6221
|
-
* the workspace's style link. Idempotent: matching values change
|
|
6222
|
-
* propagated echo dies on its own. */
|
|
6238
|
+
/** Converge this cell's Status line tab and watermark prefs to `prefs` — the
|
|
6239
|
+
* follower half of the workspace's style link. Idempotent: matching values change
|
|
6240
|
+
* nothing, so a propagated echo dies on its own. */
|
|
6223
6241
|
applyStatusPrefs(prefs) {
|
|
6224
6242
|
if (prefs.parts && this.statusline) {
|
|
6225
6243
|
for (const part of Object.keys(prefs.parts)) {
|
|
@@ -6228,6 +6246,8 @@ var ChartCell = class {
|
|
|
6228
6246
|
}
|
|
6229
6247
|
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
6230
6248
|
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
6249
|
+
if (prefs.watermark !== this.watermarkOn) this.setWatermarkVisible(prefs.watermark);
|
|
6250
|
+
if (prefs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(prefs.replayWatermark);
|
|
6231
6251
|
}
|
|
6232
6252
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
6233
6253
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -7099,6 +7119,55 @@ function occupancyGrid(def) {
|
|
|
7099
7119
|
const cols = def.cols.length;
|
|
7100
7120
|
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
|
|
7101
7121
|
}
|
|
7122
|
+
var GLYPH_MIN = 1.5;
|
|
7123
|
+
var GLYPH_SPAN = 13;
|
|
7124
|
+
function glyphStops(weights, n) {
|
|
7125
|
+
const w = Array.from({ length: n }, (_, i) => {
|
|
7126
|
+
const v = weights[i];
|
|
7127
|
+
return v !== void 0 && Number.isFinite(v) && v > 0 ? v : 1;
|
|
7128
|
+
});
|
|
7129
|
+
const total = w.reduce((a, b) => a + b, 0);
|
|
7130
|
+
const stops = [GLYPH_MIN];
|
|
7131
|
+
let acc = 0;
|
|
7132
|
+
for (const v of w) {
|
|
7133
|
+
acc += v;
|
|
7134
|
+
stops.push(Math.round((GLYPH_MIN + GLYPH_SPAN * acc / total) * 100) / 100);
|
|
7135
|
+
}
|
|
7136
|
+
return stops;
|
|
7137
|
+
}
|
|
7138
|
+
function layoutGlyph(def) {
|
|
7139
|
+
const grid = occupancyGrid(def);
|
|
7140
|
+
const rows = grid.length;
|
|
7141
|
+
const cols = grid.reduce((n, row) => Math.max(n, row.length), 0);
|
|
7142
|
+
const xs = glyphStops(def.cols, cols);
|
|
7143
|
+
const ys = glyphStops(def.rows, rows);
|
|
7144
|
+
const at = (r, c) => grid[r]?.[c];
|
|
7145
|
+
const seams = [];
|
|
7146
|
+
for (let c = 1; c < cols; c += 1) {
|
|
7147
|
+
let start = -1;
|
|
7148
|
+
for (let r = 0; r <= rows; r += 1) {
|
|
7149
|
+
const split = r < rows && at(r, c - 1) !== at(r, c);
|
|
7150
|
+
if (split && start < 0) start = r;
|
|
7151
|
+
if (!split && start >= 0) {
|
|
7152
|
+
seams.push(`M${xs[c]} ${ys[start]}V${ys[r]}`);
|
|
7153
|
+
start = -1;
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
7157
|
+
for (let r = 1; r < rows; r += 1) {
|
|
7158
|
+
let start = -1;
|
|
7159
|
+
for (let c = 0; c <= cols; c += 1) {
|
|
7160
|
+
const split = c < cols && at(r - 1, c) !== at(r, c);
|
|
7161
|
+
if (split && start < 0) start = c;
|
|
7162
|
+
if (!split && start >= 0) {
|
|
7163
|
+
seams.push(`M${xs[start]} ${ys[r]}H${xs[c]}`);
|
|
7164
|
+
start = -1;
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7168
|
+
const frame = `<rect x="${GLYPH_MIN}" y="${GLYPH_MIN}" width="${GLYPH_SPAN}" height="${GLYPH_SPAN}" rx="1.5"/>`;
|
|
7169
|
+
return svg16(seams.length > 0 ? `${frame}<path d="${seams.join("")}"/>` : frame);
|
|
7170
|
+
}
|
|
7102
7171
|
function gridStyles(def, trackSizes) {
|
|
7103
7172
|
const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
|
|
7104
7173
|
const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
|
|
@@ -8009,7 +8078,11 @@ var MoreDrawer = class {
|
|
|
8009
8078
|
row(doc, label, opts) {
|
|
8010
8079
|
const el = doc.createElement("div");
|
|
8011
8080
|
el.className = "vela-md-row";
|
|
8012
|
-
if (opts.icon)
|
|
8081
|
+
if (opts.icon) {
|
|
8082
|
+
const icon2 = iconEl(opts.icon, doc);
|
|
8083
|
+
if (opts.glyph) icon2.innerHTML = opts.glyph;
|
|
8084
|
+
el.appendChild(icon2);
|
|
8085
|
+
}
|
|
8013
8086
|
const text = doc.createElement("span");
|
|
8014
8087
|
text.className = "vela-md-row-label";
|
|
8015
8088
|
text.textContent = label;
|
|
@@ -8051,7 +8124,7 @@ var MoreDrawer = class {
|
|
|
8051
8124
|
if (this.opts.layout) {
|
|
8052
8125
|
const shape = this.opts.layout.shape();
|
|
8053
8126
|
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
8054
|
-
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
8127
|
+
list.appendChild(this.row(doc, "Layout", { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
|
|
8055
8128
|
}
|
|
8056
8129
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
8057
8130
|
for (const panel of this.opts.panels()) {
|
|
@@ -8915,7 +8988,8 @@ var VelaWorkspace = class {
|
|
|
8915
8988
|
onToggleSync: (id) => {
|
|
8916
8989
|
const kind = id;
|
|
8917
8990
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
8918
|
-
}
|
|
8991
|
+
},
|
|
8992
|
+
glyph: () => layoutGlyph(this.def)
|
|
8919
8993
|
},
|
|
8920
8994
|
getContext: () => this.context()
|
|
8921
8995
|
});
|
|
@@ -9414,6 +9488,7 @@ var VelaWorkspace = class {
|
|
|
9414
9488
|
this.applyGrid();
|
|
9415
9489
|
this.buildCells();
|
|
9416
9490
|
this.alignNewCellStyles(preexisting);
|
|
9491
|
+
this.alignNewCellDrawings(preexisting);
|
|
9417
9492
|
this.syncCellPresentation();
|
|
9418
9493
|
this.refreshCellControls();
|
|
9419
9494
|
this.topbar.setLayout(next.id);
|
|
@@ -9830,6 +9905,7 @@ var VelaWorkspace = class {
|
|
|
9830
9905
|
const chart = cell.chart;
|
|
9831
9906
|
chart.on("indicator:error", ({ error }) => this.toastHost.show(`[${cell.id}] ${error.message}`, "error", 5e3));
|
|
9832
9907
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
9908
|
+
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
9833
9909
|
chart.on("alert", (alert) => {
|
|
9834
9910
|
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
|
|
9835
9911
|
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? "Alert", message: alert.message, time: alert.time });
|
|
@@ -9947,12 +10023,66 @@ var VelaWorkspace = class {
|
|
|
9947
10023
|
}
|
|
9948
10024
|
}
|
|
9949
10025
|
/**
|
|
9950
|
-
*
|
|
9951
|
-
*
|
|
9952
|
-
*
|
|
9953
|
-
*
|
|
9954
|
-
* in
|
|
9955
|
-
*
|
|
10026
|
+
* Bring cells minted by a layout change into their drawings group: with the link
|
|
10027
|
+
* on, a NEW cell (fresh slot, or one returning from the pool that missed edits
|
|
10028
|
+
* while dormant) receives the drawings of a pre-existing group peer — the active
|
|
10029
|
+
* cell when it is one. A drawing the arriving cell already holds a linked copy of
|
|
10030
|
+
* is refreshed in place; any other is copied and linked like a freshly synced
|
|
10031
|
+
* drawing, so later edits and removals follow both ways. The arrival is the cell's
|
|
10032
|
+
* starting state, not an edit: it stays out of the cell's undo timeline, and the
|
|
10033
|
+
* busy guard keeps the copies' own events from fanning back out.
|
|
10034
|
+
*/
|
|
10035
|
+
alignNewCellDrawings(preexisting) {
|
|
10036
|
+
const setting = this.syncOpts.drawings;
|
|
10037
|
+
if (!setting) return;
|
|
10038
|
+
const ids = [...this.cellsById.keys()];
|
|
10039
|
+
for (const id of ids) {
|
|
10040
|
+
if (preexisting.has(id)) continue;
|
|
10041
|
+
const cell = this.cellsById.get(id);
|
|
10042
|
+
if (!cell?.chart.drawings.supported) continue;
|
|
10043
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
10044
|
+
if (peers.length === 0) continue;
|
|
10045
|
+
const sourceId = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
10046
|
+
const source = this.cellsById.get(sourceId);
|
|
10047
|
+
if (!source) continue;
|
|
10048
|
+
const drawings = cell.chart.drawings;
|
|
10049
|
+
this.drawingSyncBusy = true;
|
|
10050
|
+
try {
|
|
10051
|
+
cell.history.silently(() => {
|
|
10052
|
+
const held = new Set(drawings.all().map((d) => d.id));
|
|
10053
|
+
for (const doc of source.chart.drawings.all()) {
|
|
10054
|
+
const group = this.drawingLinks.get(`${sourceId}\0${doc.id}`) ?? /* @__PURE__ */ new Map([[sourceId, doc.id]]);
|
|
10055
|
+
const peerId = group.get(id);
|
|
10056
|
+
if (peerId != null && held.has(peerId)) {
|
|
10057
|
+
drawings.update(peerId, { anchors: doc.anchors, style: doc.style, text: doc.text, props: doc.props });
|
|
10058
|
+
continue;
|
|
10059
|
+
}
|
|
10060
|
+
const copy = drawings.add(doc.type, {
|
|
10061
|
+
paneId: doc.paneId,
|
|
10062
|
+
anchors: doc.anchors,
|
|
10063
|
+
style: doc.style,
|
|
10064
|
+
text: doc.text,
|
|
10065
|
+
props: doc.props,
|
|
10066
|
+
zIndex: doc.zIndex
|
|
10067
|
+
});
|
|
10068
|
+
if (!copy) continue;
|
|
10069
|
+
if (peerId != null) this.drawingLinks.delete(`${id}\0${peerId}`);
|
|
10070
|
+
group.set(id, copy.id);
|
|
10071
|
+
for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
|
|
10072
|
+
}
|
|
10073
|
+
});
|
|
10074
|
+
} finally {
|
|
10075
|
+
this.drawingSyncBusy = false;
|
|
10076
|
+
}
|
|
10077
|
+
}
|
|
10078
|
+
}
|
|
10079
|
+
/**
|
|
10080
|
+
* Mirror an origin cell's presentation — the style-link slice of its renderer
|
|
10081
|
+
* config ({@link styleConfigPatch}) plus its Status line and watermark prefs —
|
|
10082
|
+
* onto its same-group followers (the style link). Loop-safe two ways: the busy
|
|
10083
|
+
* guard eats the followers' SYNCHRONOUS echoes (their `applyConfig` re-fires
|
|
10084
|
+
* `onConfigChanged` in the same tick), and the equality short-circuits leave
|
|
10085
|
+
* already-converged followers untouched, so nothing re-emits once the group agrees.
|
|
9956
10086
|
*/
|
|
9957
10087
|
propagateStylePrefs(originId) {
|
|
9958
10088
|
if (this.styleSyncBusy || this.destroyed) return;
|
|
@@ -9960,17 +10090,15 @@ var VelaWorkspace = class {
|
|
|
9960
10090
|
if (targets.length === 0) return;
|
|
9961
10091
|
const origin = this.cellsById.get(originId);
|
|
9962
10092
|
if (!origin) return;
|
|
9963
|
-
const
|
|
9964
|
-
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
10093
|
+
const config = origin.chart.renderer.getConfig();
|
|
9965
10094
|
const prefs = origin.statusPrefs();
|
|
9966
10095
|
this.styleSyncBusy = true;
|
|
9967
10096
|
try {
|
|
9968
10097
|
for (const id of targets) {
|
|
9969
10098
|
const cell = this.cellsById.get(id);
|
|
9970
10099
|
if (!cell) continue;
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
}
|
|
10100
|
+
const patch = styleConfigPatch(config, cell.chart.renderer.getConfig());
|
|
10101
|
+
if (patch) cell.chart.renderer.applyConfig(patch);
|
|
9974
10102
|
cell.applyStatusPrefs(prefs);
|
|
9975
10103
|
}
|
|
9976
10104
|
} finally {
|
|
@@ -10291,7 +10419,8 @@ var VelaWorkspace = class {
|
|
|
10291
10419
|
onToggleSync: (id) => {
|
|
10292
10420
|
const kind = id;
|
|
10293
10421
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
10294
|
-
}
|
|
10422
|
+
},
|
|
10423
|
+
glyph: () => layoutGlyph(this.def)
|
|
10295
10424
|
},
|
|
10296
10425
|
onOpenChange: (open) => this.trackDialog(open)
|
|
10297
10426
|
}));
|