@luxalgo/vela 0.6.9 → 0.6.11
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-8Z95Q-RJ.d.cts → DataProvider-BBf-jc6W.d.ts} +49 -1
- package/dist/{DataProvider-DExJrfut.d.ts → DataProvider-p0TEyhlX.d.cts} +49 -1
- package/dist/{chunk-6WDDVMBJ.js → chunk-73PEA4MU.js} +707 -353
- package/dist/{chunk-62SVGONC.js → chunk-G77Y7LK2.js} +2 -2
- package/dist/{chunk-STHSKXOR.js → chunk-IO3NYSQV.js} +650 -157
- package/dist/{chunk-WFSZBX3R.js → chunk-KG4YT3TI.js} +3 -1
- package/dist/{chunk-FCVIG7JG.js → chunk-MHY7MVXH.js} +5 -1
- package/dist/{chunk-EQCHJZOT.js → chunk-MTLJKZDZ.js} +10 -2
- package/dist/{contributions-D7PVZO2i.d.ts → contributions-Bbe2R-mQ.d.ts} +20 -6
- package/dist/{contributions-C1U2Krwg.d.cts → contributions-CO01zWve.d.cts} +20 -6
- package/dist/index.cjs +719 -350
- package/dist/index.d.cts +37 -10
- package/dist/index.d.ts +37 -10
- package/dist/index.js +5 -5
- package/dist/{options-FM0peknS.d.ts → options-yp7sA96q.d.cts} +14 -7
- package/dist/{options-FM0peknS.d.cts → options-yp7sA96q.d.ts} +14 -7
- package/dist/{plugin-DfVqBz9p.d.cts → plugin-CkkH8QnX.d.cts} +3 -3
- package/dist/{plugin-7bkF32Rk.d.ts → plugin-DwxjM3Ni.d.ts} +3 -3
- package/dist/plugin.cjs +12 -1
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- 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/{statusline-DOPiT6I6.d.cts → statusline-CHPDuKNp.d.ts} +13 -5
- package/dist/{statusline-zdF4eZLr.d.ts → statusline-DTHZUFqK.d.cts} +13 -5
- package/dist/ui.cjs +7 -1
- package/dist/ui.d.cts +3 -1
- package/dist/ui.d.ts +3 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +719 -350
- package/dist/vela.global.min.js +51 -49
- package/dist/widget.cjs +1249 -388
- package/dist/widget.d.cts +17 -6
- package/dist/widget.d.ts +17 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1249 -388
- package/dist/workspace.d.cts +103 -10
- package/dist/workspace.d.ts +103 -10
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- /package/dist/{chunk-AOGZBKUE.js → chunk-H26BEHF4.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, LEGEND_AT_TOP_ATTR, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-
|
|
2
|
-
import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType } 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, SESSION_OFF, SESSION_POST, SESSION_PRE, categoricalColor } from './chunk-
|
|
1
|
+
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-73PEA4MU.js';
|
|
2
|
+
import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType, inputDeltas } from './chunk-MTLJKZDZ.js';
|
|
3
|
+
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-G77Y7LK2.js';
|
|
4
|
+
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-MHY7MVXH.js';
|
|
5
|
+
import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-KG4YT3TI.js';
|
|
6
6
|
import { baseOf } from './chunk-W4EJWLEO.js';
|
|
7
7
|
|
|
8
8
|
// src/widget/timeframe.ts
|
|
@@ -654,7 +654,7 @@ var Topbar = class {
|
|
|
654
654
|
this.leftActionsHost.className = "vela-widget-actions-left";
|
|
655
655
|
this.panelsHost = doc.createElement("span");
|
|
656
656
|
this.panelsHost.className = "vela-widget-panels";
|
|
657
|
-
const tool = (cls,
|
|
657
|
+
const tool = (cls, icon2, tip, onClick) => this.toolButton(cls, icon2, tip, onClick, this.tooltips);
|
|
658
658
|
if (vis("undo-redo")) {
|
|
659
659
|
this.undoBtn = tool("vela-widget-undo", "undo", "Undo", opts.onUndoClick);
|
|
660
660
|
this.redoBtn = tool("vela-widget-redo", "redo", "Redo", opts.onRedoClick);
|
|
@@ -852,8 +852,8 @@ var Topbar = class {
|
|
|
852
852
|
}
|
|
853
853
|
renderStyleButton(doc) {
|
|
854
854
|
this.styleButton.replaceChildren();
|
|
855
|
-
const
|
|
856
|
-
if (
|
|
855
|
+
const icon2 = priceStyleIcon(this.priceStyle);
|
|
856
|
+
if (icon2) this.styleButton.appendChild(iconEl(icon2, doc));
|
|
857
857
|
else this.styleButton.appendChild(doc.createTextNode(priceStyleLabel(this.priceStyle)));
|
|
858
858
|
this.styleButton.setAttribute("aria-label", `Chart style \u2014 ${priceStyleLabel(this.priceStyle)}`);
|
|
859
859
|
}
|
|
@@ -945,11 +945,11 @@ var Topbar = class {
|
|
|
945
945
|
this.el.remove();
|
|
946
946
|
}
|
|
947
947
|
/** One icon-only tool button with its kit tooltip, parked in `sink` for disposal. */
|
|
948
|
-
toolButton(cls,
|
|
948
|
+
toolButton(cls, icon2, tip, onClick, sink) {
|
|
949
949
|
const doc = this.el.ownerDocument;
|
|
950
950
|
const b = doc.createElement("button");
|
|
951
951
|
b.className = `vela-widget-tool ${cls}`;
|
|
952
|
-
b.appendChild(iconEl(
|
|
952
|
+
b.appendChild(iconEl(icon2, doc));
|
|
953
953
|
b.setAttribute("aria-label", tip);
|
|
954
954
|
sink.push(new Tooltip(b, { content: tip, triggerId: `vela-tool-${cls}`, host: this.host }));
|
|
955
955
|
if (onClick) b.addEventListener("click", onClick);
|
|
@@ -1629,13 +1629,13 @@ var MenuBuilder = class {
|
|
|
1629
1629
|
this.actions = /* @__PURE__ */ new Map();
|
|
1630
1630
|
this.seq = 0;
|
|
1631
1631
|
}
|
|
1632
|
-
entry(label,
|
|
1632
|
+
entry(label, icon2, run, separatorBefore = false) {
|
|
1633
1633
|
const id = this.next();
|
|
1634
1634
|
this.actions.set(id, run);
|
|
1635
|
-
return { id, label, icon, separatorBefore };
|
|
1635
|
+
return { id, label, icon: icon2, separatorBefore };
|
|
1636
1636
|
}
|
|
1637
|
-
submenu(label,
|
|
1638
|
-
return { id: this.next(), label, icon, submenu, separatorBefore };
|
|
1637
|
+
submenu(label, icon2, submenu, separatorBefore = false) {
|
|
1638
|
+
return { id: this.next(), label, icon: icon2, submenu, separatorBefore };
|
|
1639
1639
|
}
|
|
1640
1640
|
next() {
|
|
1641
1641
|
this.seq += 1;
|
|
@@ -1872,8 +1872,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
1872
1872
|
const acts = doc.createElement("span");
|
|
1873
1873
|
acts.className = "vela-ot-acts";
|
|
1874
1874
|
head.appendChild(acts);
|
|
1875
|
-
const op = (
|
|
1876
|
-
acts.appendChild(this.btn({ icon, title, run }));
|
|
1875
|
+
const op = (icon2, title, run) => {
|
|
1876
|
+
acts.appendChild(this.btn({ icon: icon2, title, run }));
|
|
1877
1877
|
};
|
|
1878
1878
|
if (pane.kind !== "price") {
|
|
1879
1879
|
if (pane.order > 1) op("arrow-up", "Move pane up", () => chart.panes.move(pane.id, "up"));
|
|
@@ -1888,8 +1888,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
1888
1888
|
const { chart } = pass;
|
|
1889
1889
|
if (row.kind === "price") {
|
|
1890
1890
|
const base = this.symbolName.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || this.symbolName;
|
|
1891
|
-
const
|
|
1892
|
-
const el2 = this.row(
|
|
1891
|
+
const icon2 = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar", this.symbolRaw ? this.iconFor?.(this.symbolRaw) : void 0);
|
|
1892
|
+
const el2 = this.row(icon2, row.label, row.visible, [
|
|
1893
1893
|
{
|
|
1894
1894
|
icon: row.visible ? "eye" : "eye-off",
|
|
1895
1895
|
title: row.visible ? "Hide" : "Show",
|
|
@@ -2008,16 +2008,16 @@ var ObjectTree = class extends SidePanel {
|
|
|
2008
2008
|
const doc = this.el.ownerDocument;
|
|
2009
2009
|
const { chart } = pass;
|
|
2010
2010
|
const markup = drawingMeta(d.type).icon;
|
|
2011
|
-
let
|
|
2011
|
+
let icon2;
|
|
2012
2012
|
if (markup) {
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2013
|
+
icon2 = doc.createElement("span");
|
|
2014
|
+
icon2.className = "vela-icon";
|
|
2015
|
+
icon2.setAttribute("aria-hidden", "true");
|
|
2016
|
+
icon2.innerHTML = markup;
|
|
2017
2017
|
} else {
|
|
2018
|
-
|
|
2018
|
+
icon2 = iconEl("pen", doc);
|
|
2019
2019
|
}
|
|
2020
|
-
const el = this.row(
|
|
2020
|
+
const el = this.row(icon2, drawingLabel(d), d.visible, [
|
|
2021
2021
|
{
|
|
2022
2022
|
icon: d.locked ? "lock" : "unlock",
|
|
2023
2023
|
title: d.locked ? "Unlock" : "Lock",
|
|
@@ -2083,7 +2083,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2083
2083
|
}
|
|
2084
2084
|
}
|
|
2085
2085
|
/** The shared row shell: icon, name, optional tag, then the action buttons. */
|
|
2086
|
-
row(
|
|
2086
|
+
row(icon2, label, visible, actions, tag) {
|
|
2087
2087
|
const doc = this.el.ownerDocument;
|
|
2088
2088
|
const el = doc.createElement("div");
|
|
2089
2089
|
el.className = "vela-ot-row";
|
|
@@ -2092,7 +2092,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
2092
2092
|
name.className = "vela-ot-name";
|
|
2093
2093
|
name.textContent = label;
|
|
2094
2094
|
name.title = label;
|
|
2095
|
-
el.append(
|
|
2095
|
+
el.append(icon2, name);
|
|
2096
2096
|
if (tag) {
|
|
2097
2097
|
const t = doc.createElement("span");
|
|
2098
2098
|
t.className = "vela-ot-tag";
|
|
@@ -2931,6 +2931,17 @@ function dedupeSymbols(list) {
|
|
|
2931
2931
|
}
|
|
2932
2932
|
return out;
|
|
2933
2933
|
}
|
|
2934
|
+
function foldGroups(list) {
|
|
2935
|
+
const groupsAbove = /* @__PURE__ */ new Set();
|
|
2936
|
+
const out = [];
|
|
2937
|
+
for (const s of list) {
|
|
2938
|
+
if (isGroupRow(s)) {
|
|
2939
|
+
groupsAbove.add(groupKeyOf(s));
|
|
2940
|
+
out.push(s);
|
|
2941
|
+
} else if (s.group == null || !groupsAbove.has(groupKeyOf(s))) out.push(s);
|
|
2942
|
+
}
|
|
2943
|
+
return out;
|
|
2944
|
+
}
|
|
2934
2945
|
var TOP_TICKERS = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "LINKUSDT"];
|
|
2935
2946
|
function parseQuery(raw, venues) {
|
|
2936
2947
|
const m = raw.match(/^\s*([^\s:]+)\s*[:\s]\s*(.*)$/);
|
|
@@ -3062,6 +3073,21 @@ var CSS6 = `
|
|
|
3062
3073
|
.vela-sp-badge[data-p='binance'] { color: #f0b90b; } /* palette-exempt: venue brand mark */
|
|
3063
3074
|
.vela-sp-badge[data-p='hyperliquid'] { color: #50d2c1; } /* palette-exempt: venue brand mark */
|
|
3064
3075
|
.vela-sp-empty { padding: var(--vela-space-3); color: var(--vela-fg-muted); text-align: center; }
|
|
3076
|
+
/* Grouped listings (futures roots): the chevron unfolds members inline, indented. */
|
|
3077
|
+
.vela-sp-expander {
|
|
3078
|
+
all: unset;
|
|
3079
|
+
flex: none;
|
|
3080
|
+
display: inline-flex;
|
|
3081
|
+
align-items: center;
|
|
3082
|
+
justify-content: center;
|
|
3083
|
+
width: 22px;
|
|
3084
|
+
height: 22px;
|
|
3085
|
+
border-radius: 5px;
|
|
3086
|
+
cursor: pointer;
|
|
3087
|
+
color: var(--vela-fg-muted);
|
|
3088
|
+
}
|
|
3089
|
+
.vela-sp-expander:hover { background: var(--vela-surface-elev); color: var(--vela-fg); }
|
|
3090
|
+
.vela-sp-row[data-member] { padding-left: 34px; }
|
|
3065
3091
|
`;
|
|
3066
3092
|
var PAGE = 100;
|
|
3067
3093
|
var SymbolPicker = class {
|
|
@@ -3073,6 +3099,11 @@ var SymbolPicker = class {
|
|
|
3073
3099
|
this.seed = "";
|
|
3074
3100
|
this.activeTab = "All";
|
|
3075
3101
|
this.visible = PAGE;
|
|
3102
|
+
/** The last filter pass returned fewer raw rows than asked — the pool is drained
|
|
3103
|
+
* (checked BEFORE folding: folding shortens pages without meaning exhaustion). */
|
|
3104
|
+
this.exhausted = false;
|
|
3105
|
+
/** Group rows currently expanded (venue-scoped keys) — members shown inline. */
|
|
3106
|
+
this.expanded = /* @__PURE__ */ new Set();
|
|
3076
3107
|
/** The ranked pool cache — `key` fingerprints the raw pool the ranking ran on. */
|
|
3077
3108
|
this.ranked = null;
|
|
3078
3109
|
this.ranking = false;
|
|
@@ -3087,7 +3118,7 @@ var SymbolPicker = class {
|
|
|
3087
3118
|
searchRow.append(iconEl("search", doc), this.input);
|
|
3088
3119
|
this.tabs = doc.createElement("div");
|
|
3089
3120
|
this.tabs.className = "vela-sp-tabs";
|
|
3090
|
-
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Forex", "Commodities"]) {
|
|
3121
|
+
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
|
|
3091
3122
|
const b = doc.createElement("button");
|
|
3092
3123
|
b.className = "vela-sp-tab";
|
|
3093
3124
|
b.textContent = t;
|
|
@@ -3103,7 +3134,7 @@ var SymbolPicker = class {
|
|
|
3103
3134
|
this.list = doc.createElement("div");
|
|
3104
3135
|
this.list.className = "vela-sp-list";
|
|
3105
3136
|
this.list.addEventListener("scroll", () => {
|
|
3106
|
-
if (this.
|
|
3137
|
+
if (this.exhausted) return;
|
|
3107
3138
|
if (this.list.scrollTop + this.list.clientHeight < this.list.scrollHeight - 200) return;
|
|
3108
3139
|
this.visible += PAGE;
|
|
3109
3140
|
this.grow();
|
|
@@ -3132,14 +3163,19 @@ var SymbolPicker = class {
|
|
|
3132
3163
|
else if (e.key === "ArrowUp") this.moveHighlight(-1);
|
|
3133
3164
|
else if (e.key === "Enter") {
|
|
3134
3165
|
const pick = this.rows[this.highlighted];
|
|
3135
|
-
if (pick) this.
|
|
3166
|
+
if (pick) this.pick(pick);
|
|
3136
3167
|
return;
|
|
3137
3168
|
} else return;
|
|
3138
3169
|
e.preventDefault();
|
|
3139
3170
|
});
|
|
3140
3171
|
this.list.addEventListener("click", (e) => {
|
|
3141
|
-
const
|
|
3142
|
-
|
|
3172
|
+
const target = e.target;
|
|
3173
|
+
const row = target.closest(".vela-sp-row");
|
|
3174
|
+
if (!row) return;
|
|
3175
|
+
const s = this.rows[Number(row.dataset.i)];
|
|
3176
|
+
if (!s) return;
|
|
3177
|
+
if (target.closest(".vela-sp-expander")) this.toggleExpand(s);
|
|
3178
|
+
else this.pick(s);
|
|
3143
3179
|
});
|
|
3144
3180
|
}
|
|
3145
3181
|
/** Wire where symbols come from (re-called on every widget rebuild). */
|
|
@@ -3156,6 +3192,27 @@ var SymbolPicker = class {
|
|
|
3156
3192
|
destroy() {
|
|
3157
3193
|
this.dialog.destroy();
|
|
3158
3194
|
}
|
|
3195
|
+
/** Route a row activation: a GROUP row loads its default member (the root itself is
|
|
3196
|
+
* listed, never loadable), any other row loads itself. */
|
|
3197
|
+
pick(s) {
|
|
3198
|
+
const target = isGroupRow(s) ? defaultMemberOf(this.pool(), s) ?? s : s;
|
|
3199
|
+
this.select(target.ticker, target.prefix ?? target.provider, this.opts.onSelect);
|
|
3200
|
+
}
|
|
3201
|
+
/** Expand/collapse a group row IN PLACE — same query, same page, same scroll; only
|
|
3202
|
+
* the member rows under the group appear or go. */
|
|
3203
|
+
toggleExpand(s) {
|
|
3204
|
+
const key = groupKeyOf(s);
|
|
3205
|
+
if (!this.expanded.delete(key)) this.expanded.add(key);
|
|
3206
|
+
const scrollTop = this.list.scrollTop;
|
|
3207
|
+
const focus = this.rows[this.highlighted];
|
|
3208
|
+
this.rows = this.computeRows();
|
|
3209
|
+
this.list.replaceChildren();
|
|
3210
|
+
this.rows.forEach((r, i) => this.list.appendChild(this.rowEl(r, i)));
|
|
3211
|
+
const at = focus ? this.rows.indexOf(focus) : -1;
|
|
3212
|
+
this.highlighted = at >= 0 ? at : Math.min(this.highlighted, Math.max(0, this.rows.length - 1));
|
|
3213
|
+
this.renderHighlight();
|
|
3214
|
+
this.list.scrollTop = scrollTop;
|
|
3215
|
+
}
|
|
3159
3216
|
select(ticker, venue, onSelect) {
|
|
3160
3217
|
this.close();
|
|
3161
3218
|
onSelect(venue ? `${venue}:${ticker}` : ticker);
|
|
@@ -3197,10 +3254,26 @@ var SymbolPicker = class {
|
|
|
3197
3254
|
return this.ranked?.result ?? raw;
|
|
3198
3255
|
}
|
|
3199
3256
|
computeRows() {
|
|
3200
|
-
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
3257
|
+
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Futures: ["futures", "root"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
3201
3258
|
const all = this.pool();
|
|
3202
3259
|
const pool = this.activeTab === "All" ? all : all.filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
|
|
3203
|
-
|
|
3260
|
+
const filtered = filterSymbols(pool, this.input.value, this.visible, symbolRanking() ? false : TOP_TICKERS);
|
|
3261
|
+
this.exhausted = filtered.length < this.visible;
|
|
3262
|
+
const folded = foldGroups(filtered);
|
|
3263
|
+
if (!this.expanded.size) return folded;
|
|
3264
|
+
const keyOf = (s) => `${(s.prefix ?? s.provider ?? "").toLowerCase()}:${s.ticker.toUpperCase()}`;
|
|
3265
|
+
const present = new Set(folded.map(keyOf));
|
|
3266
|
+
const out = [];
|
|
3267
|
+
for (const s of folded) {
|
|
3268
|
+
out.push(s);
|
|
3269
|
+
if (!isGroupRow(s) || !this.expanded.has(groupKeyOf(s))) continue;
|
|
3270
|
+
for (const m of groupMembers(all, s)) {
|
|
3271
|
+
if (present.has(keyOf(m))) continue;
|
|
3272
|
+
present.add(keyOf(m));
|
|
3273
|
+
out.push(m);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
return out;
|
|
3204
3277
|
}
|
|
3205
3278
|
refresh() {
|
|
3206
3279
|
const doc = this.list.ownerDocument;
|
|
@@ -3215,19 +3288,20 @@ var SymbolPicker = class {
|
|
|
3215
3288
|
this.list.appendChild(empty);
|
|
3216
3289
|
return;
|
|
3217
3290
|
}
|
|
3218
|
-
|
|
3291
|
+
this.rows.forEach((s, i) => this.list.appendChild(this.rowEl(s, i)));
|
|
3219
3292
|
this.renderHighlight();
|
|
3220
3293
|
}
|
|
3221
3294
|
/** Append the page the grown `visible` just uncovered — rows already on screen stay put. */
|
|
3222
3295
|
grow() {
|
|
3223
3296
|
const already = this.rows.length;
|
|
3224
3297
|
this.rows = this.computeRows();
|
|
3225
|
-
|
|
3298
|
+
this.rows.slice(already).forEach((s, j) => this.list.appendChild(this.rowEl(s, already + j)));
|
|
3226
3299
|
}
|
|
3227
|
-
rowEl(s) {
|
|
3300
|
+
rowEl(s, i) {
|
|
3228
3301
|
const doc = this.list.ownerDocument;
|
|
3229
3302
|
const row = doc.createElement("div");
|
|
3230
3303
|
row.className = "vela-sp-row";
|
|
3304
|
+
row.dataset.i = String(i);
|
|
3231
3305
|
row.dataset.ticker = s.ticker;
|
|
3232
3306
|
const venue = s.prefix ?? s.provider;
|
|
3233
3307
|
if (venue) row.dataset.venue = venue;
|
|
@@ -3242,6 +3316,14 @@ var SymbolPicker = class {
|
|
|
3242
3316
|
d.textContent = s.description ?? (s.type ?? "");
|
|
3243
3317
|
main.append(t, d);
|
|
3244
3318
|
row.append(av, main);
|
|
3319
|
+
if (isGroupRow(s)) {
|
|
3320
|
+
row.dataset.group = "1";
|
|
3321
|
+
const expander = doc.createElement("button");
|
|
3322
|
+
expander.className = "vela-sp-expander";
|
|
3323
|
+
expander.setAttribute("aria-label", "Show contracts");
|
|
3324
|
+
expander.appendChild(iconEl(this.expanded.has(groupKeyOf(s)) ? "chevron-down" : "chevron-right", doc));
|
|
3325
|
+
row.appendChild(expander);
|
|
3326
|
+
} else if (s.group != null && this.expanded.has(groupKeyOf(s))) row.dataset.member = "1";
|
|
3245
3327
|
if (venue) {
|
|
3246
3328
|
const badge = doc.createElement("span");
|
|
3247
3329
|
badge.className = "vela-sp-badge";
|
|
@@ -3675,11 +3757,12 @@ async function resolveIndicators(config, fetchImpl = fetch) {
|
|
|
3675
3757
|
}
|
|
3676
3758
|
return out;
|
|
3677
3759
|
}
|
|
3760
|
+
var ledgerEntryName = (e) => typeof e === "string" ? e : e.name;
|
|
3678
3761
|
function indicatorLedger(i) {
|
|
3679
3762
|
const natives = [...i.present];
|
|
3680
3763
|
if (i.volumePending && !natives.includes("volume")) natives.push("volume");
|
|
3681
3764
|
return {
|
|
3682
|
-
manifest: i.manifestSettled ? [...i.
|
|
3765
|
+
manifest: i.manifestSettled ? [...i.instanceEntries] : [...i.pendingManifest ?? i.instanceEntries],
|
|
3683
3766
|
natives
|
|
3684
3767
|
};
|
|
3685
3768
|
}
|
|
@@ -3761,7 +3844,17 @@ function sanitizeCell(raw) {
|
|
|
3761
3844
|
if (c.drawings != null && typeof c.drawings === "object") out.drawings = c.drawings;
|
|
3762
3845
|
const ind = c.indicators;
|
|
3763
3846
|
if (ind != null && typeof ind === "object") {
|
|
3764
|
-
const manifest = Array.isArray(ind.manifest) ? ind.manifest.
|
|
3847
|
+
const manifest = Array.isArray(ind.manifest) ? ind.manifest.flatMap((n) => {
|
|
3848
|
+
if (typeof n === "string") return [n];
|
|
3849
|
+
if (n != null && typeof n === "object" && typeof n.name === "string") {
|
|
3850
|
+
const e = n;
|
|
3851
|
+
const bag = (v) => v != null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
3852
|
+
const inputs = bag(e.inputs);
|
|
3853
|
+
const props = bag(e.props);
|
|
3854
|
+
return [inputs || props ? { name: e.name, ...inputs ? { inputs } : {}, ...props ? { props } : {} } : e.name];
|
|
3855
|
+
}
|
|
3856
|
+
return [];
|
|
3857
|
+
}) : [];
|
|
3765
3858
|
const natives = Array.isArray(ind.natives) ? ind.natives.filter((n) => typeof n === "string") : [];
|
|
3766
3859
|
out.indicators = { manifest, natives };
|
|
3767
3860
|
}
|
|
@@ -4817,6 +4910,243 @@ var SessionShadingTracker = class {
|
|
|
4817
4910
|
}
|
|
4818
4911
|
};
|
|
4819
4912
|
|
|
4913
|
+
// src/widget/glide.ts
|
|
4914
|
+
var MIN_SPAN_MS = 6e4;
|
|
4915
|
+
var FOLLOW = 0.25;
|
|
4916
|
+
var ZOOM_IN = 0.8;
|
|
4917
|
+
var ZOOM_OUT = 1.25;
|
|
4918
|
+
var PAN_FAST = 0.2;
|
|
4919
|
+
function zoomTarget(base, factor) {
|
|
4920
|
+
const newSpan = Math.max(MIN_SPAN_MS, (base.to - base.from) * factor);
|
|
4921
|
+
return { from: base.to - newSpan, to: base.to };
|
|
4922
|
+
}
|
|
4923
|
+
function followStep(cur, target, follow = FOLLOW, epsFrac = 15e-4) {
|
|
4924
|
+
const eps = Math.max(MIN_SPAN_MS, (target.to - target.from) * epsFrac);
|
|
4925
|
+
const nf = cur.from + (target.from - cur.from) * follow;
|
|
4926
|
+
const nt = cur.to + (target.to - cur.to) * follow;
|
|
4927
|
+
const done = Math.abs(target.from - nf) <= eps && Math.abs(target.to - nt) <= eps;
|
|
4928
|
+
return { cur: done ? { ...target } : { from: nf, to: nt }, done };
|
|
4929
|
+
}
|
|
4930
|
+
var Glider = class {
|
|
4931
|
+
constructor(chart) {
|
|
4932
|
+
this.chart = chart;
|
|
4933
|
+
// The glide eases ITS OWN range, never the chart's read-back: getVisibleRange()
|
|
4934
|
+
// is data-bounded (whole bars, `to` capped at the newest bar) and the renderer
|
|
4935
|
+
// clamps what gets applied — chasing that read-back can never converge on a
|
|
4936
|
+
// clamped step, leaving the rAF loop re-applying a stale target forever (and
|
|
4937
|
+
// grinding the span down when panning past the newest bar). The internal range
|
|
4938
|
+
// converges geometrically, so the glide always terminates; the renderer is free
|
|
4939
|
+
// to clamp each applied frame.
|
|
4940
|
+
this.cur = null;
|
|
4941
|
+
this.target = null;
|
|
4942
|
+
this.raf = 0;
|
|
4943
|
+
}
|
|
4944
|
+
zoom(factor) {
|
|
4945
|
+
this.to((base) => zoomTarget(base, factor));
|
|
4946
|
+
}
|
|
4947
|
+
stop() {
|
|
4948
|
+
if (this.raf) cancelAnimationFrame(this.raf);
|
|
4949
|
+
this.raf = 0;
|
|
4950
|
+
this.cur = null;
|
|
4951
|
+
this.target = null;
|
|
4952
|
+
}
|
|
4953
|
+
to(make) {
|
|
4954
|
+
const chart = this.chart();
|
|
4955
|
+
const base = this.target ?? chart?.getVisibleRange();
|
|
4956
|
+
if (!chart || !base) return;
|
|
4957
|
+
this.cur ?? (this.cur = { ...base });
|
|
4958
|
+
this.target = make(base);
|
|
4959
|
+
if (!this.raf) this.tick();
|
|
4960
|
+
}
|
|
4961
|
+
tick() {
|
|
4962
|
+
this.raf = requestAnimationFrame(() => {
|
|
4963
|
+
const chart = this.chart();
|
|
4964
|
+
if (!chart || !this.cur || !this.target) {
|
|
4965
|
+
this.stop();
|
|
4966
|
+
return;
|
|
4967
|
+
}
|
|
4968
|
+
const { cur: next, done } = followStep(this.cur, this.target);
|
|
4969
|
+
this.cur = next;
|
|
4970
|
+
chart.setVisibleRange(next);
|
|
4971
|
+
if (done) this.stop();
|
|
4972
|
+
else this.tick();
|
|
4973
|
+
});
|
|
4974
|
+
}
|
|
4975
|
+
};
|
|
4976
|
+
|
|
4977
|
+
// src/widget/cell-controls.ts
|
|
4978
|
+
var CELL_CONTROLS_PROXIMITY_PX = 120;
|
|
4979
|
+
var TIME_AXIS_H2 = 22;
|
|
4980
|
+
var CONTROLS_BOTTOM_PX = TIME_AXIS_H2 + 12;
|
|
4981
|
+
var CLUSTER_H = 24;
|
|
4982
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
4983
|
+
var CLUSTER_LEFT_CSS = "calc((100% + var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px)) / 2)";
|
|
4984
|
+
var STYLE_ID12 = "vela-cell-controls";
|
|
4985
|
+
var CSS12 = `
|
|
4986
|
+
.vela-cc-btn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;padding:0;border:none;border-radius:var(--vela-radius-sm);background:transparent;line-height:0;font-size:12px;color:var(--vela-fg-muted);cursor:pointer;}
|
|
4987
|
+
.vela-cc-btn svg{display:block;}
|
|
4988
|
+
.vela-cc-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
4989
|
+
.vela-cc-on,.vela-cc-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
4990
|
+
.vela-cc-grip{cursor:grab;touch-action:none;}
|
|
4991
|
+
.vela-cc-grip:active{cursor:grabbing;}
|
|
4992
|
+
`;
|
|
4993
|
+
function plotCenterX(width, toolbarGutter = 0, scaleGutter = 0) {
|
|
4994
|
+
return (width + toolbarGutter - scaleGutter) / 2;
|
|
4995
|
+
}
|
|
4996
|
+
function nearBottomCenter(x, y, width, height, proximityPx = CELL_CONTROLS_PROXIMITY_PX, gutters = {}) {
|
|
4997
|
+
const cx = plotCenterX(width, gutters.toolbar ?? 0, gutters.scale ?? 0);
|
|
4998
|
+
const cy = height - CONTROLS_BOTTOM_PX - CLUSTER_H / 2;
|
|
4999
|
+
return Math.hypot(x - cx, y - cy) <= proximityPx;
|
|
5000
|
+
}
|
|
5001
|
+
var CellControls = class {
|
|
5002
|
+
constructor(host, deps) {
|
|
5003
|
+
this.host = host;
|
|
5004
|
+
this.deps = deps;
|
|
5005
|
+
this.near = false;
|
|
5006
|
+
/** A grip drag is underway — the proximity reveal must not hide the cluster
|
|
5007
|
+
* while captured pointer moves sweep across the whole grid. */
|
|
5008
|
+
this.dragging = false;
|
|
5009
|
+
/** Mobile: the proximity reveal is meaningless without a cursor — the mobile
|
|
5010
|
+
* bar's maximize stop replaces the cluster. */
|
|
5011
|
+
this.suspended = false;
|
|
5012
|
+
this.onHostMove = (e) => {
|
|
5013
|
+
if (this.suspended) return;
|
|
5014
|
+
if (this.dragging) return;
|
|
5015
|
+
const rect = this.host.getBoundingClientRect();
|
|
5016
|
+
this.setNear(nearBottomCenter(e.clientX - rect.left, e.clientY - rect.top, rect.width, rect.height, CELL_CONTROLS_PROXIMITY_PX, this.hostGutters()));
|
|
5017
|
+
};
|
|
5018
|
+
this.onHostLeave = () => {
|
|
5019
|
+
if (this.dragging) return;
|
|
5020
|
+
this.setNear(false);
|
|
5021
|
+
};
|
|
5022
|
+
injectStyles(STYLE_ID12, CSS12, host.ownerDocument);
|
|
5023
|
+
this.glider = new Glider(deps.chart);
|
|
5024
|
+
this.root = host.ownerDocument.createElement("div");
|
|
5025
|
+
Object.assign(this.root.style, {
|
|
5026
|
+
position: "absolute",
|
|
5027
|
+
left: CLUSTER_LEFT_CSS,
|
|
5028
|
+
bottom: `${CONTROLS_BOTTOM_PX}px`,
|
|
5029
|
+
transform: "translateX(-50%)",
|
|
5030
|
+
zIndex: "6",
|
|
5031
|
+
display: "none",
|
|
5032
|
+
// revealed by cursor proximity (onHostMove)
|
|
5033
|
+
gap: "2px",
|
|
5034
|
+
padding: "2px",
|
|
5035
|
+
borderRadius: "var(--vela-radius-md)",
|
|
5036
|
+
background: CLUSTER_PILL,
|
|
5037
|
+
pointerEvents: "auto"
|
|
5038
|
+
});
|
|
5039
|
+
this.host.addEventListener("pointermove", this.onHostMove);
|
|
5040
|
+
this.host.addEventListener("pointerleave", this.onHostLeave);
|
|
5041
|
+
this.host.appendChild(this.root);
|
|
5042
|
+
this.refresh();
|
|
5043
|
+
}
|
|
5044
|
+
/** Rebuild the buttons (the multi-cell gate or the maximized state changed). */
|
|
5045
|
+
refresh() {
|
|
5046
|
+
this.root.textContent = "";
|
|
5047
|
+
const multi = this.deps.multiCell();
|
|
5048
|
+
const maximized = multi && this.deps.isMaximized();
|
|
5049
|
+
if (multi && !maximized) this.root.appendChild(this.makeGrip());
|
|
5050
|
+
this.root.appendChild(this.button("minus", "Zoom out", () => this.glider.zoom(ZOOM_OUT)));
|
|
5051
|
+
this.root.appendChild(this.button("plus", "Zoom in", () => this.glider.zoom(ZOOM_IN)));
|
|
5052
|
+
if (multi) {
|
|
5053
|
+
this.root.appendChild(
|
|
5054
|
+
this.button(maximized ? "restore" : "maximize", maximized ? "Restore layout" : "Maximize chart", () => this.deps.toggleMaximize(), {
|
|
5055
|
+
// The maximized state reads as an inverse chip (white-on-dark, dark-on-light),
|
|
5056
|
+
// the same active-state affordance as a collapsed pane's expand button.
|
|
5057
|
+
selected: maximized
|
|
5058
|
+
})
|
|
5059
|
+
);
|
|
5060
|
+
}
|
|
5061
|
+
this.root.appendChild(
|
|
5062
|
+
this.button("reset", "Reset chart", () => {
|
|
5063
|
+
this.glider.stop();
|
|
5064
|
+
this.deps.reset();
|
|
5065
|
+
})
|
|
5066
|
+
);
|
|
5067
|
+
}
|
|
5068
|
+
button(iconId, title, onClick, opts = {}) {
|
|
5069
|
+
const b = this.host.ownerDocument.createElement("button");
|
|
5070
|
+
b.type = "button";
|
|
5071
|
+
b.title = title;
|
|
5072
|
+
b.setAttribute("aria-label", title);
|
|
5073
|
+
b.className = opts.selected === true ? "vela-cc-btn vela-cc-on" : "vela-cc-btn";
|
|
5074
|
+
b.innerHTML = icon(iconId);
|
|
5075
|
+
b.addEventListener("click", (e) => {
|
|
5076
|
+
e.stopPropagation();
|
|
5077
|
+
onClick();
|
|
5078
|
+
});
|
|
5079
|
+
return b;
|
|
5080
|
+
}
|
|
5081
|
+
/** The drag handle (2×3 dot grip): press and drag onto another cell to trade
|
|
5082
|
+
* slots with it. The preview highlight follows the pointer; releasing outside
|
|
5083
|
+
* any other cell cancels. */
|
|
5084
|
+
makeGrip() {
|
|
5085
|
+
const b = this.host.ownerDocument.createElement("button");
|
|
5086
|
+
b.type = "button";
|
|
5087
|
+
b.title = "Drag to move chart";
|
|
5088
|
+
b.setAttribute("aria-label", "Drag to move chart");
|
|
5089
|
+
b.className = "vela-cc-btn vela-cc-grip";
|
|
5090
|
+
b.innerHTML = icon("grip");
|
|
5091
|
+
b.addEventListener("pointerdown", (e) => this.onGripDown(b, e));
|
|
5092
|
+
return b;
|
|
5093
|
+
}
|
|
5094
|
+
onGripDown(btn, e) {
|
|
5095
|
+
if (e.button !== 0 && e.pointerType === "mouse") return;
|
|
5096
|
+
e.preventDefault();
|
|
5097
|
+
e.stopPropagation();
|
|
5098
|
+
try {
|
|
5099
|
+
btn.setPointerCapture(e.pointerId);
|
|
5100
|
+
} catch {
|
|
5101
|
+
}
|
|
5102
|
+
this.dragging = true;
|
|
5103
|
+
let target = null;
|
|
5104
|
+
const move = (ev) => {
|
|
5105
|
+
target = this.deps.dragTargetAt(ev.clientX, ev.clientY);
|
|
5106
|
+
this.deps.previewDrop(target);
|
|
5107
|
+
};
|
|
5108
|
+
const finish = (commit) => () => {
|
|
5109
|
+
this.dragging = false;
|
|
5110
|
+
this.deps.previewDrop(null);
|
|
5111
|
+
btn.removeEventListener("pointermove", move);
|
|
5112
|
+
btn.removeEventListener("pointerup", onUp);
|
|
5113
|
+
btn.removeEventListener("pointercancel", onCancel);
|
|
5114
|
+
if (commit && target != null) this.deps.dropOn(target);
|
|
5115
|
+
};
|
|
5116
|
+
const onUp = finish(true);
|
|
5117
|
+
const onCancel = finish(false);
|
|
5118
|
+
btn.addEventListener("pointermove", move);
|
|
5119
|
+
btn.addEventListener("pointerup", onUp);
|
|
5120
|
+
btn.addEventListener("pointercancel", onCancel);
|
|
5121
|
+
}
|
|
5122
|
+
/** Mobile flips the cluster off entirely (and hides it if currently revealed). */
|
|
5123
|
+
setSuspended(on) {
|
|
5124
|
+
this.suspended = on;
|
|
5125
|
+
if (on) this.setNear(false);
|
|
5126
|
+
}
|
|
5127
|
+
/** Live renderer gutters on the cell host (0 when unpublished — a test stub). */
|
|
5128
|
+
hostGutters() {
|
|
5129
|
+
const view = this.host.ownerDocument.defaultView;
|
|
5130
|
+
if (!view) return { toolbar: 0, scale: 0 };
|
|
5131
|
+
const cs = view.getComputedStyle(this.host);
|
|
5132
|
+
return {
|
|
5133
|
+
toolbar: Number.parseFloat(cs.getPropertyValue("--vela-toolbar-gutter")) || 0,
|
|
5134
|
+
scale: Number.parseFloat(cs.getPropertyValue("--vela-scale-gutter")) || 0
|
|
5135
|
+
};
|
|
5136
|
+
}
|
|
5137
|
+
setNear(near) {
|
|
5138
|
+
if (near === this.near) return;
|
|
5139
|
+
this.near = near;
|
|
5140
|
+
this.root.style.display = near ? "flex" : "none";
|
|
5141
|
+
}
|
|
5142
|
+
destroy() {
|
|
5143
|
+
this.glider.stop();
|
|
5144
|
+
this.host.removeEventListener("pointermove", this.onHostMove);
|
|
5145
|
+
this.host.removeEventListener("pointerleave", this.onHostLeave);
|
|
5146
|
+
this.root.remove();
|
|
5147
|
+
}
|
|
5148
|
+
};
|
|
5149
|
+
|
|
4820
5150
|
// src/widget/history.ts
|
|
4821
5151
|
var WidgetHistory = class {
|
|
4822
5152
|
/** `getChart` late-resolves the CURRENT chart: drawing steps recorded before a chart
|
|
@@ -4918,6 +5248,12 @@ function cellDrawings(opt) {
|
|
|
4918
5248
|
if (opt === true || opt == null) return { toolbar: false };
|
|
4919
5249
|
return { ...opt, toolbar: false };
|
|
4920
5250
|
}
|
|
5251
|
+
function instanceDeltas(handle) {
|
|
5252
|
+
if (!handle) return void 0;
|
|
5253
|
+
const inputs = inputDeltas(handle.inputs, handle.inputValues());
|
|
5254
|
+
const props = inputDeltas(handle.props, handle.propValues());
|
|
5255
|
+
return inputs || props ? { ...inputs ? { inputs } : {}, ...props ? { props } : {} } : void 0;
|
|
5256
|
+
}
|
|
4921
5257
|
var ChartCell = class {
|
|
4922
5258
|
constructor(id, gridHost, seed, deps) {
|
|
4923
5259
|
this.id = id;
|
|
@@ -5066,11 +5402,18 @@ var ChartCell = class {
|
|
|
5066
5402
|
if (this.inner && this.state.symbol) this.marketStatus?.track(this.inner.data, this.state.symbol);
|
|
5067
5403
|
});
|
|
5068
5404
|
this.syncStatuslineColors();
|
|
5405
|
+
this.cellControls = new CellControls(this.host, {
|
|
5406
|
+
chart: () => this.inner,
|
|
5407
|
+
reset: () => this.resetView(),
|
|
5408
|
+
multiCell: () => deps.multiCell(),
|
|
5409
|
+
isMaximized: () => deps.isMaximized(id),
|
|
5410
|
+
toggleMaximize: () => deps.toggleMaximize(id),
|
|
5411
|
+
dragTargetAt: (x, y) => deps.cellDragTarget(id, x, y),
|
|
5412
|
+
previewDrop: (target) => deps.previewDropTarget(target),
|
|
5413
|
+
dropOn: (target) => deps.dropCell(id, target)
|
|
5414
|
+
});
|
|
5069
5415
|
this.contextMenu = new ChartContextMenu(this.host, {
|
|
5070
|
-
resetView: () =>
|
|
5071
|
-
this.inner?.renderer.set("autoScale", true);
|
|
5072
|
-
this.inner?.setVisibleRangePreset("ALL");
|
|
5073
|
-
},
|
|
5416
|
+
resetView: () => this.resetView(),
|
|
5074
5417
|
timezone: () => this.deps.timezone(),
|
|
5075
5418
|
setTimezone: (zone) => this.deps.setTimezone(zone),
|
|
5076
5419
|
// Right-clicking activates the cell first (capture-phase pointerdown), so the
|
|
@@ -5086,6 +5429,7 @@ var ChartCell = class {
|
|
|
5086
5429
|
this.syncPresentNatives();
|
|
5087
5430
|
this.refreshNativeCatalog();
|
|
5088
5431
|
});
|
|
5432
|
+
this.inner.on("indicator:inputs", () => this.deps.onStateDirty());
|
|
5089
5433
|
this.inner.on("indicator:removed", ({ id: id2 }) => {
|
|
5090
5434
|
if (this.destroyed) return;
|
|
5091
5435
|
const idx = this.instances.findIndex((it) => it.handle?.id === id2);
|
|
@@ -5094,7 +5438,7 @@ var ChartCell = class {
|
|
|
5094
5438
|
this.instances.splice(idx, 1);
|
|
5095
5439
|
this.history.push({
|
|
5096
5440
|
undo: () => {
|
|
5097
|
-
snapshot.handle = this.addToChart(snapshot.entry);
|
|
5441
|
+
snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
|
|
5098
5442
|
this.instances.push(snapshot);
|
|
5099
5443
|
this.deps.onIndicatorsChanged(this.id);
|
|
5100
5444
|
},
|
|
@@ -5444,6 +5788,20 @@ var ChartCell = class {
|
|
|
5444
5788
|
this.inner.setVisibleRangePreset(preset.preset);
|
|
5445
5789
|
}
|
|
5446
5790
|
}
|
|
5791
|
+
/** Reset this cell's view: re-enable auto scale and frame the full history —
|
|
5792
|
+
* the same action the chart context menu offers. */
|
|
5793
|
+
resetView() {
|
|
5794
|
+
this.inner?.renderer.set("autoScale", true);
|
|
5795
|
+
this.inner?.setVisibleRangePreset("ALL");
|
|
5796
|
+
}
|
|
5797
|
+
/** Rebuild the view-controls cluster (the maximize gate or state changed). */
|
|
5798
|
+
refreshControls() {
|
|
5799
|
+
this.cellControls.refresh();
|
|
5800
|
+
}
|
|
5801
|
+
/** Mobile flips the per-cell cluster off (the shell's mobile bar replaces it). */
|
|
5802
|
+
setControlsSuspended(on) {
|
|
5803
|
+
this.cellControls.setSuspended(on);
|
|
5804
|
+
}
|
|
5447
5805
|
/** Make this cell the active one and put keyboard focus on its chart surface. */
|
|
5448
5806
|
focus() {
|
|
5449
5807
|
this.deps.activate(this.id);
|
|
@@ -5469,9 +5827,9 @@ var ChartCell = class {
|
|
|
5469
5827
|
this.manifest = list;
|
|
5470
5828
|
if (this.pendingManifestNames) {
|
|
5471
5829
|
if (list.length === 0) return;
|
|
5472
|
-
for (const
|
|
5473
|
-
const entry = list.find((e) => e.name ===
|
|
5474
|
-
if (entry) this.addManifestInstance(entry, { record: false });
|
|
5830
|
+
for (const led of this.pendingManifestNames) {
|
|
5831
|
+
const entry = list.find((e) => e.name === ledgerEntryName(led));
|
|
5832
|
+
if (entry) this.addManifestInstance(entry, { record: false, ...typeof led === "object" ? { inputs: led.inputs, props: led.props } : {} });
|
|
5475
5833
|
}
|
|
5476
5834
|
this.pendingManifestNames = null;
|
|
5477
5835
|
return;
|
|
@@ -5501,9 +5859,9 @@ var ChartCell = class {
|
|
|
5501
5859
|
}
|
|
5502
5860
|
for (const it of [...this.instances]) this.dropInstance(it);
|
|
5503
5861
|
if (this.manifest.length > 0) {
|
|
5504
|
-
for (const
|
|
5505
|
-
const entry = this.manifest.find((e) => e.name ===
|
|
5506
|
-
if (entry) this.addManifestInstance(entry, { record: false });
|
|
5862
|
+
for (const item of led.manifest) {
|
|
5863
|
+
const entry = this.manifest.find((e) => e.name === ledgerEntryName(item));
|
|
5864
|
+
if (entry) this.addManifestInstance(entry, { record: false, ...typeof item === "object" ? { inputs: item.inputs, props: item.props } : {} });
|
|
5507
5865
|
}
|
|
5508
5866
|
this.pendingManifestNames = null;
|
|
5509
5867
|
} else if (!this.deps.manifestSettled()) {
|
|
@@ -5552,12 +5910,13 @@ var ChartCell = class {
|
|
|
5552
5910
|
* a persistence handler's `restore` runs silently, a user-driven call records.
|
|
5553
5911
|
*/
|
|
5554
5912
|
addExternalIndicator(entry) {
|
|
5555
|
-
this.addManifestInstance({ ...entry, enabled: true }, { external: true });
|
|
5913
|
+
this.addManifestInstance({ ...entry, enabled: true }, { external: true, ...entry.inputs ? { inputs: entry.inputs } : {}, ...entry.props ? { props: entry.props } : {} });
|
|
5556
5914
|
}
|
|
5557
5915
|
/** Add ONE instance of a manifest entry (repeatable — duplicates are legitimate). */
|
|
5558
5916
|
addManifestInstance(entry, opts = {}) {
|
|
5559
5917
|
if (this.destroyed) return;
|
|
5560
|
-
const
|
|
5918
|
+
const values = opts.inputs || opts.props ? { inputs: opts.inputs, props: opts.props } : void 0;
|
|
5919
|
+
const it = { entry, handle: this.addToChart(entry, values), ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
|
|
5561
5920
|
this.instances.push(it);
|
|
5562
5921
|
this.deps.onIndicatorsChanged(this.id);
|
|
5563
5922
|
if (opts.record === false) return;
|
|
@@ -5565,7 +5924,7 @@ var ChartCell = class {
|
|
|
5565
5924
|
this.history.push({
|
|
5566
5925
|
undo: () => this.dropInstance(snapshot),
|
|
5567
5926
|
redo: () => {
|
|
5568
|
-
snapshot.handle = this.addToChart(snapshot.entry);
|
|
5927
|
+
snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
|
|
5569
5928
|
this.instances.push(snapshot);
|
|
5570
5929
|
this.deps.onIndicatorsChanged(this.id);
|
|
5571
5930
|
}
|
|
@@ -5578,7 +5937,7 @@ var ChartCell = class {
|
|
|
5578
5937
|
const snapshot = it;
|
|
5579
5938
|
this.history.push({
|
|
5580
5939
|
undo: () => {
|
|
5581
|
-
snapshot.handle = this.addToChart(snapshot.entry);
|
|
5940
|
+
snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
|
|
5582
5941
|
this.instances.push(snapshot);
|
|
5583
5942
|
this.deps.onIndicatorsChanged(this.id);
|
|
5584
5943
|
},
|
|
@@ -5588,6 +5947,9 @@ var ChartCell = class {
|
|
|
5588
5947
|
dropInstance(it) {
|
|
5589
5948
|
const idx = this.instances.indexOf(it);
|
|
5590
5949
|
if (idx >= 0) this.instances.splice(idx, 1);
|
|
5950
|
+
const captured = instanceDeltas(it.handle);
|
|
5951
|
+
if (captured) it.values = captured;
|
|
5952
|
+
else delete it.values;
|
|
5591
5953
|
try {
|
|
5592
5954
|
it.handle?.remove();
|
|
5593
5955
|
} catch {
|
|
@@ -5630,9 +5992,13 @@ var ChartCell = class {
|
|
|
5630
5992
|
this.deps.onIndicatorsChanged(this.id);
|
|
5631
5993
|
});
|
|
5632
5994
|
}
|
|
5633
|
-
addToChart(entry) {
|
|
5995
|
+
addToChart(entry, values) {
|
|
5634
5996
|
try {
|
|
5635
|
-
return this.inner?.addIndicator(entry.script,
|
|
5997
|
+
return this.inner?.addIndicator(entry.script, {
|
|
5998
|
+
...entry.language !== void 0 ? { language: entry.language } : {},
|
|
5999
|
+
...values?.inputs ? { inputs: values.inputs } : {},
|
|
6000
|
+
...values?.props ? { props: values.props } : {}
|
|
6001
|
+
}) ?? null;
|
|
5636
6002
|
} catch (err) {
|
|
5637
6003
|
console.warn(`[vela] indicator "${entry.name}" failed to add:`, err);
|
|
5638
6004
|
return null;
|
|
@@ -5746,7 +6112,10 @@ var ChartCell = class {
|
|
|
5746
6112
|
// manifest — their plugin persists them via the `ext` seam instead.
|
|
5747
6113
|
indicators: indicatorLedger({
|
|
5748
6114
|
present: this.inner ? this.inner.presentNativeIndicators() : [],
|
|
5749
|
-
|
|
6115
|
+
instanceEntries: this.instances.filter((it) => !it.external).map((it) => {
|
|
6116
|
+
const d = it.handle ? instanceDeltas(it.handle) : it.values;
|
|
6117
|
+
return d ? { name: it.entry.name, ...d } : it.entry.name;
|
|
6118
|
+
}),
|
|
5750
6119
|
pendingManifest: this.pendingManifestNames,
|
|
5751
6120
|
manifestSettled: this.deps.manifestSettled(),
|
|
5752
6121
|
volumePending: this.volumeMayBePending && this.volumeIntent
|
|
@@ -5757,6 +6126,7 @@ var ChartCell = class {
|
|
|
5757
6126
|
destroy() {
|
|
5758
6127
|
this.destroyed = true;
|
|
5759
6128
|
this.offMarket();
|
|
6129
|
+
this.cellControls.destroy();
|
|
5760
6130
|
this.contextMenu.destroy();
|
|
5761
6131
|
this.history.destroy();
|
|
5762
6132
|
this.marketStatus?.stop();
|
|
@@ -6015,8 +6385,8 @@ var SplitterLayer = class {
|
|
|
6015
6385
|
};
|
|
6016
6386
|
|
|
6017
6387
|
// src/widget/toast.ts
|
|
6018
|
-
var
|
|
6019
|
-
var
|
|
6388
|
+
var STYLE_ID13 = "vela-widget-toast";
|
|
6389
|
+
var CSS13 = `
|
|
6020
6390
|
.vela-toast {
|
|
6021
6391
|
position: absolute;
|
|
6022
6392
|
left: 50%;
|
|
@@ -6046,7 +6416,7 @@ var CSS12 = `
|
|
|
6046
6416
|
var Toast = class {
|
|
6047
6417
|
constructor(host) {
|
|
6048
6418
|
this.timer = null;
|
|
6049
|
-
injectStyles(
|
|
6419
|
+
injectStyles(STYLE_ID13, CSS13, host.ownerDocument);
|
|
6050
6420
|
this.el = host.ownerDocument.createElement("div");
|
|
6051
6421
|
this.el.className = "vela-toast";
|
|
6052
6422
|
host.appendChild(this.el);
|
|
@@ -6070,70 +6440,6 @@ var Toast = class {
|
|
|
6070
6440
|
}
|
|
6071
6441
|
};
|
|
6072
6442
|
|
|
6073
|
-
// src/widget/glide.ts
|
|
6074
|
-
var MIN_SPAN_MS = 6e4;
|
|
6075
|
-
var FOLLOW = 0.25;
|
|
6076
|
-
var ZOOM_IN = 0.8;
|
|
6077
|
-
var ZOOM_OUT = 1.25;
|
|
6078
|
-
var PAN_FAST = 0.2;
|
|
6079
|
-
function zoomTarget(base, factor) {
|
|
6080
|
-
const newSpan = Math.max(MIN_SPAN_MS, (base.to - base.from) * factor);
|
|
6081
|
-
return { from: base.to - newSpan, to: base.to };
|
|
6082
|
-
}
|
|
6083
|
-
function followStep(cur, target, follow = FOLLOW, epsFrac = 15e-4) {
|
|
6084
|
-
const eps = Math.max(MIN_SPAN_MS, (target.to - target.from) * epsFrac);
|
|
6085
|
-
const nf = cur.from + (target.from - cur.from) * follow;
|
|
6086
|
-
const nt = cur.to + (target.to - cur.to) * follow;
|
|
6087
|
-
const done = Math.abs(target.from - nf) <= eps && Math.abs(target.to - nt) <= eps;
|
|
6088
|
-
return { cur: done ? { ...target } : { from: nf, to: nt }, done };
|
|
6089
|
-
}
|
|
6090
|
-
var Glider = class {
|
|
6091
|
-
constructor(chart) {
|
|
6092
|
-
this.chart = chart;
|
|
6093
|
-
// The glide eases ITS OWN range, never the chart's read-back: getVisibleRange()
|
|
6094
|
-
// is data-bounded (whole bars, `to` capped at the newest bar) and the renderer
|
|
6095
|
-
// clamps what gets applied — chasing that read-back can never converge on a
|
|
6096
|
-
// clamped step, leaving the rAF loop re-applying a stale target forever (and
|
|
6097
|
-
// grinding the span down when panning past the newest bar). The internal range
|
|
6098
|
-
// converges geometrically, so the glide always terminates; the renderer is free
|
|
6099
|
-
// to clamp each applied frame.
|
|
6100
|
-
this.cur = null;
|
|
6101
|
-
this.target = null;
|
|
6102
|
-
this.raf = 0;
|
|
6103
|
-
}
|
|
6104
|
-
zoom(factor) {
|
|
6105
|
-
this.to((base) => zoomTarget(base, factor));
|
|
6106
|
-
}
|
|
6107
|
-
stop() {
|
|
6108
|
-
if (this.raf) cancelAnimationFrame(this.raf);
|
|
6109
|
-
this.raf = 0;
|
|
6110
|
-
this.cur = null;
|
|
6111
|
-
this.target = null;
|
|
6112
|
-
}
|
|
6113
|
-
to(make) {
|
|
6114
|
-
const chart = this.chart();
|
|
6115
|
-
const base = this.target ?? chart?.getVisibleRange();
|
|
6116
|
-
if (!chart || !base) return;
|
|
6117
|
-
this.cur ?? (this.cur = { ...base });
|
|
6118
|
-
this.target = make(base);
|
|
6119
|
-
if (!this.raf) this.tick();
|
|
6120
|
-
}
|
|
6121
|
-
tick() {
|
|
6122
|
-
this.raf = requestAnimationFrame(() => {
|
|
6123
|
-
const chart = this.chart();
|
|
6124
|
-
if (!chart || !this.cur || !this.target) {
|
|
6125
|
-
this.stop();
|
|
6126
|
-
return;
|
|
6127
|
-
}
|
|
6128
|
-
const { cur: next, done } = followStep(this.cur, this.target);
|
|
6129
|
-
this.cur = next;
|
|
6130
|
-
chart.setVisibleRange(next);
|
|
6131
|
-
if (done) this.stop();
|
|
6132
|
-
else this.tick();
|
|
6133
|
-
});
|
|
6134
|
-
}
|
|
6135
|
-
};
|
|
6136
|
-
|
|
6137
6443
|
// src/widget/tool-shortcuts.ts
|
|
6138
6444
|
var TOOL_BINDINGS = [
|
|
6139
6445
|
["trendline", "drawings.trendline"],
|
|
@@ -6205,8 +6511,8 @@ var LayoutModeController = class {
|
|
|
6205
6511
|
};
|
|
6206
6512
|
|
|
6207
6513
|
// src/widget/mobile-bar.ts
|
|
6208
|
-
var
|
|
6209
|
-
var
|
|
6514
|
+
var STYLE_ID14 = "vela-widget-mobilebar";
|
|
6515
|
+
var CSS14 = `
|
|
6210
6516
|
.vela-mobilebar {
|
|
6211
6517
|
display: none;
|
|
6212
6518
|
align-items: stretch;
|
|
@@ -6237,6 +6543,9 @@ var CSS13 = `
|
|
|
6237
6543
|
}
|
|
6238
6544
|
.vela-mb-item:active { background: var(--vela-hover); }
|
|
6239
6545
|
.vela-mb-item .vela-icon { font-size: 18px; width: 18px; height: 18px; }
|
|
6546
|
+
/* A lit stop (the maximize toggle while something is isolated): the inverse
|
|
6547
|
+
"selected" chip \u2014 white on the dark theme, dark on the light one. */
|
|
6548
|
+
.vela-mb-item.vela-mb-on, .vela-mb-item.vela-mb-on:active { background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
6240
6549
|
/* Left-aligned contributed actions get their own stops (the built-in indicators
|
|
6241
6550
|
slot) \u2014 the wrapper is layout-transparent so each stop flexes like a sibling. */
|
|
6242
6551
|
.vela-mb-actions { display: contents; }
|
|
@@ -6256,14 +6565,14 @@ var MobileBar = class {
|
|
|
6256
6565
|
constructor(host, opts) {
|
|
6257
6566
|
this.opts = opts;
|
|
6258
6567
|
const doc = host.ownerDocument;
|
|
6259
|
-
injectStyles(
|
|
6568
|
+
injectStyles(STYLE_ID14, CSS14, doc);
|
|
6260
6569
|
this.el = doc.createElement("div");
|
|
6261
6570
|
this.el.className = "vela-mobilebar";
|
|
6262
|
-
const item = (cls, label, onClick,
|
|
6571
|
+
const item = (cls, label, onClick, icon2) => {
|
|
6263
6572
|
const b = doc.createElement("button");
|
|
6264
6573
|
b.className = `vela-mb-item ${cls}`;
|
|
6265
6574
|
b.setAttribute("aria-label", label);
|
|
6266
|
-
if (
|
|
6575
|
+
if (icon2) b.appendChild(iconEl(icon2, doc));
|
|
6267
6576
|
b.addEventListener("click", onClick);
|
|
6268
6577
|
return b;
|
|
6269
6578
|
};
|
|
@@ -6277,9 +6586,11 @@ var MobileBar = class {
|
|
|
6277
6586
|
this.actionsHost.className = "vela-mb-actions";
|
|
6278
6587
|
const onDrawings = opts.onDrawingsClick;
|
|
6279
6588
|
const drawings = onDrawings ? item("vela-mb-drawings", "Drawings", onDrawings, "pen") : null;
|
|
6589
|
+
const onMaximize = opts.onMaximizeClick;
|
|
6590
|
+
this.maxEl = onMaximize ? item("vela-mb-maximize", "Maximize chart", onMaximize, "maximize") : null;
|
|
6280
6591
|
const more = item("vela-mb-more", "More", opts.onMoreClick, "kebab");
|
|
6281
6592
|
const settings = item("vela-mb-settings", "Chart settings", opts.onSettingsClick, "gear");
|
|
6282
|
-
this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, ...drawings ? [drawings] : [], more, settings);
|
|
6593
|
+
this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, ...drawings ? [drawings] : [], ...this.maxEl ? [this.maxEl] : [], more, settings);
|
|
6283
6594
|
host.appendChild(this.el);
|
|
6284
6595
|
this.renderActions();
|
|
6285
6596
|
}
|
|
@@ -6311,14 +6622,22 @@ var MobileBar = class {
|
|
|
6311
6622
|
setTimeframe(tf) {
|
|
6312
6623
|
this.tfEl.textContent = timeframeLabel(tf);
|
|
6313
6624
|
}
|
|
6625
|
+
/** Light the maximize stop while something is isolated (a chart over the grid,
|
|
6626
|
+
* or a maximized pane inside the active chart) — inverse chip + restore glyph. */
|
|
6627
|
+
setMaximizeActive(on) {
|
|
6628
|
+
if (!this.maxEl) return;
|
|
6629
|
+
this.maxEl.classList.toggle("vela-mb-on", on);
|
|
6630
|
+
this.maxEl.setAttribute("aria-label", on ? "Restore layout" : "Maximize chart");
|
|
6631
|
+
this.maxEl.replaceChildren(iconEl(on ? "restore" : "maximize", this.el.ownerDocument));
|
|
6632
|
+
}
|
|
6314
6633
|
destroy() {
|
|
6315
6634
|
this.el.remove();
|
|
6316
6635
|
}
|
|
6317
6636
|
};
|
|
6318
6637
|
|
|
6319
6638
|
// src/widget/timeframe-drawer.ts
|
|
6320
|
-
var
|
|
6321
|
-
var
|
|
6639
|
+
var STYLE_ID15 = "vela-widget-tf-drawer";
|
|
6640
|
+
var CSS15 = `
|
|
6322
6641
|
.vela-tfd-heading {
|
|
6323
6642
|
padding: 4px 2px 8px;
|
|
6324
6643
|
font-size: 12px;
|
|
@@ -6374,7 +6693,7 @@ var TimeframeDrawer = class {
|
|
|
6374
6693
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
6375
6694
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
6376
6695
|
const doc = opts.host.ownerDocument;
|
|
6377
|
-
injectStyles(
|
|
6696
|
+
injectStyles(STYLE_ID15, CSS15, doc);
|
|
6378
6697
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
6379
6698
|
const rangeHeading = doc.createElement("div");
|
|
6380
6699
|
rangeHeading.className = "vela-tfd-heading";
|
|
@@ -6432,8 +6751,8 @@ var TimeframeDrawer = class {
|
|
|
6432
6751
|
};
|
|
6433
6752
|
|
|
6434
6753
|
// src/widget/drawings-drawer.ts
|
|
6435
|
-
var
|
|
6436
|
-
var
|
|
6754
|
+
var STYLE_ID16 = "vela-widget-drawings-drawer";
|
|
6755
|
+
var CSS16 = `
|
|
6437
6756
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
6438
6757
|
.vela-dd-sticky {
|
|
6439
6758
|
position: sticky;
|
|
@@ -6535,7 +6854,7 @@ var DrawingsDrawer = class {
|
|
|
6535
6854
|
this.definition = { groups: [] };
|
|
6536
6855
|
this.activeGroup = "";
|
|
6537
6856
|
const doc = opts.host.ownerDocument;
|
|
6538
|
-
injectStyles(
|
|
6857
|
+
injectStyles(STYLE_ID16, CSS16, doc);
|
|
6539
6858
|
this.drawer = new Drawer({
|
|
6540
6859
|
host: opts.host,
|
|
6541
6860
|
title: "Drawings",
|
|
@@ -6677,8 +6996,8 @@ var DrawingsDrawer = class {
|
|
|
6677
6996
|
};
|
|
6678
6997
|
|
|
6679
6998
|
// src/widget/more-drawer.ts
|
|
6680
|
-
var
|
|
6681
|
-
var
|
|
6999
|
+
var STYLE_ID17 = "vela-widget-more-drawer";
|
|
7000
|
+
var CSS17 = `
|
|
6682
7001
|
.vela-md-actions {
|
|
6683
7002
|
display: flex;
|
|
6684
7003
|
gap: 6px;
|
|
@@ -6753,7 +7072,7 @@ var MoreDrawer = class {
|
|
|
6753
7072
|
constructor(opts) {
|
|
6754
7073
|
this.opts = opts;
|
|
6755
7074
|
this.view = "main";
|
|
6756
|
-
injectStyles(
|
|
7075
|
+
injectStyles(STYLE_ID17, CSS17, opts.host.ownerDocument);
|
|
6757
7076
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
6758
7077
|
}
|
|
6759
7078
|
open() {
|
|
@@ -6809,11 +7128,11 @@ var MoreDrawer = class {
|
|
|
6809
7128
|
renderMain(doc) {
|
|
6810
7129
|
const actions = doc.createElement("div");
|
|
6811
7130
|
actions.className = "vela-md-actions";
|
|
6812
|
-
const action = (
|
|
7131
|
+
const action = (icon2, label, enabled, run) => {
|
|
6813
7132
|
const b = doc.createElement("button");
|
|
6814
7133
|
b.className = "vela-md-action";
|
|
6815
7134
|
b.disabled = !enabled;
|
|
6816
|
-
b.appendChild(iconEl(
|
|
7135
|
+
b.appendChild(iconEl(icon2, doc));
|
|
6817
7136
|
b.appendChild(doc.createTextNode(label));
|
|
6818
7137
|
b.addEventListener("click", () => {
|
|
6819
7138
|
run();
|
|
@@ -6947,8 +7266,8 @@ var MoreDrawer = class {
|
|
|
6947
7266
|
};
|
|
6948
7267
|
|
|
6949
7268
|
// src/widget/timezone-drawer.ts
|
|
6950
|
-
var
|
|
6951
|
-
var
|
|
7269
|
+
var STYLE_ID18 = "vela-widget-timezone-drawer";
|
|
7270
|
+
var CSS18 = `
|
|
6952
7271
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
6953
7272
|
.vela-tzd-row {
|
|
6954
7273
|
display: flex;
|
|
@@ -6967,7 +7286,7 @@ var CSS17 = `
|
|
|
6967
7286
|
var TimezoneDrawer = class {
|
|
6968
7287
|
constructor(opts) {
|
|
6969
7288
|
this.opts = opts;
|
|
6970
|
-
injectStyles(
|
|
7289
|
+
injectStyles(STYLE_ID18, CSS18, opts.host.ownerDocument);
|
|
6971
7290
|
this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
|
|
6972
7291
|
}
|
|
6973
7292
|
open() {
|
|
@@ -7005,8 +7324,8 @@ var TimezoneDrawer = class {
|
|
|
7005
7324
|
};
|
|
7006
7325
|
|
|
7007
7326
|
// src/widget/price-scale-drawer.ts
|
|
7008
|
-
var
|
|
7009
|
-
var
|
|
7327
|
+
var STYLE_ID19 = "vela-widget-price-scale-drawer";
|
|
7328
|
+
var CSS19 = `
|
|
7010
7329
|
.vela-psd-list { padding: 2px 0 4px; }
|
|
7011
7330
|
.vela-psd-row {
|
|
7012
7331
|
display: flex;
|
|
@@ -7035,7 +7354,7 @@ var PriceScaleDrawer = class {
|
|
|
7035
7354
|
constructor(opts) {
|
|
7036
7355
|
this.opts = opts;
|
|
7037
7356
|
this.pane = null;
|
|
7038
|
-
injectStyles(
|
|
7357
|
+
injectStyles(STYLE_ID19, CSS19, opts.host.ownerDocument);
|
|
7039
7358
|
this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
|
|
7040
7359
|
}
|
|
7041
7360
|
open() {
|
|
@@ -7171,8 +7490,8 @@ var PriceScaleDrawer = class {
|
|
|
7171
7490
|
};
|
|
7172
7491
|
|
|
7173
7492
|
// src/widget/drawing-pill.ts
|
|
7174
|
-
var
|
|
7175
|
-
var
|
|
7493
|
+
var STYLE_ID20 = "vela-widget-drawing-pill";
|
|
7494
|
+
var CSS20 = `
|
|
7176
7495
|
.vela-drawpill { display: none; }
|
|
7177
7496
|
[data-layout='mobile'] .vela-drawpill {
|
|
7178
7497
|
position: absolute;
|
|
@@ -7232,17 +7551,17 @@ var DrawingPill = class {
|
|
|
7232
7551
|
this.chart = null;
|
|
7233
7552
|
this.chartSubs = [];
|
|
7234
7553
|
const doc = host.ownerDocument;
|
|
7235
|
-
injectStyles(
|
|
7554
|
+
injectStyles(STYLE_ID20, CSS20, doc);
|
|
7236
7555
|
this.el = doc.createElement("div");
|
|
7237
7556
|
this.el.className = "vela-drawpill";
|
|
7238
7557
|
this.el.hidden = true;
|
|
7239
7558
|
this.toolGlyph = doc.createElement("span");
|
|
7240
7559
|
this.toolGlyph.className = "vela-drawpill-tool";
|
|
7241
|
-
const btn = (
|
|
7560
|
+
const btn = (icon2, label, onClick) => {
|
|
7242
7561
|
const b = doc.createElement("button");
|
|
7243
7562
|
b.className = "vela-drawpill-btn";
|
|
7244
7563
|
b.setAttribute("aria-label", label);
|
|
7245
|
-
b.appendChild(iconEl(
|
|
7564
|
+
b.appendChild(iconEl(icon2, doc));
|
|
7246
7565
|
b.addEventListener("click", onClick);
|
|
7247
7566
|
return b;
|
|
7248
7567
|
};
|
|
@@ -7345,10 +7664,10 @@ function buildContext(host) {
|
|
|
7345
7664
|
var DEFAULT_TIMEFRAMES = ["1", "5", "15", "60", "240", "D", "W"];
|
|
7346
7665
|
var GAP_PX = 2;
|
|
7347
7666
|
var POOL_CAP = 16;
|
|
7348
|
-
var
|
|
7667
|
+
var TIME_AXIS_H3 = 22;
|
|
7349
7668
|
var ALERT_CAP = 50;
|
|
7350
|
-
var
|
|
7351
|
-
var
|
|
7669
|
+
var STYLE_ID21 = "vela-workspace";
|
|
7670
|
+
var CSS21 = `
|
|
7352
7671
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
7353
7672
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
7354
7673
|
.vela-ws-toolbar { position: relative; flex: none; }
|
|
@@ -7375,6 +7694,21 @@ var CSS20 = `
|
|
|
7375
7694
|
/* Mobile: the docked drawing-toolbar column would eat a phone-width grid \u2014 the shell's
|
|
7376
7695
|
drawings drawer + on-chart pill replace it (same policy as the widget's in-chart bar). */
|
|
7377
7696
|
[data-layout='mobile'] .vela-ws-toolbar { display: none; }
|
|
7697
|
+
/* A maximized cell owns the whole grid: the splitter strips have no seams to grab and
|
|
7698
|
+
the active ring would just outline the only visible chart \u2014 both are noise here. */
|
|
7699
|
+
.vela-ws-grid[data-maximized='1'] .vela-ws-splitter { display: none; }
|
|
7700
|
+
.vela-ws-grid[data-maximized='1'] .vela-cell[data-active='1']::after { display: none; }
|
|
7701
|
+
/* Drop-target preview while a cell's drag handle is held: a dashed ring + the same
|
|
7702
|
+
soft wash the splitter hover uses, over the chart, inert to the pointer. */
|
|
7703
|
+
.vela-cell[data-drop-target='1']::before {
|
|
7704
|
+
content: '';
|
|
7705
|
+
position: absolute;
|
|
7706
|
+
inset: 0;
|
|
7707
|
+
border: 2px dashed var(--vela-fg-bright);
|
|
7708
|
+
background: var(--vela-separator-hover-band);
|
|
7709
|
+
pointer-events: none;
|
|
7710
|
+
z-index: 11;
|
|
7711
|
+
}
|
|
7378
7712
|
`;
|
|
7379
7713
|
registerIcon("layout", svg16('<rect x="1.5" y="1.5" width="13" height="13" rx="1.5"/><path d="M8 1.5v13M1.5 8h13"/>'));
|
|
7380
7714
|
function declaredOrder(cells) {
|
|
@@ -7404,6 +7738,9 @@ var VelaWorkspace = class {
|
|
|
7404
7738
|
* slots beyond the list get auto identities. Grows, never reorders. */
|
|
7405
7739
|
this.order = [];
|
|
7406
7740
|
this.activeId = null;
|
|
7741
|
+
/** The cell maximized over the whole grid (null = normal grid). TRANSIENT view
|
|
7742
|
+
* state — never persisted; any structural change (layout, applyState) restores. */
|
|
7743
|
+
this.maximizedId = null;
|
|
7407
7744
|
this.cellBackend = "auto";
|
|
7408
7745
|
this.destroyed = false;
|
|
7409
7746
|
this.shortcutsHelp = null;
|
|
@@ -7508,7 +7845,7 @@ var VelaWorkspace = class {
|
|
|
7508
7845
|
this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
|
|
7509
7846
|
const bootActive = boot?.activeCellId ?? null;
|
|
7510
7847
|
const doc = hostEl.ownerDocument;
|
|
7511
|
-
injectStyles(
|
|
7848
|
+
injectStyles(STYLE_ID21, CSS21, doc);
|
|
7512
7849
|
this.root = doc.createElement("div");
|
|
7513
7850
|
this.root.className = "vela-workspace";
|
|
7514
7851
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
@@ -7618,7 +7955,11 @@ var VelaWorkspace = class {
|
|
|
7618
7955
|
if (attribution !== false) {
|
|
7619
7956
|
const background = resolveTheme(opts.theme).background;
|
|
7620
7957
|
const mark = typeof attribution === "string" && attribution.trim() ? createCustomMark(doc, attribution, background) : createAttributionMark(doc, background);
|
|
7621
|
-
Object.assign(mark.style, {
|
|
7958
|
+
Object.assign(mark.style, {
|
|
7959
|
+
left: "calc(var(--vela-toolbar-gutter, 0px) + 12px)",
|
|
7960
|
+
bottom: `calc(var(--vela-bottom-gutter, ${TIME_AXIS_H3}px) + 10px)`,
|
|
7961
|
+
zIndex: "11"
|
|
7962
|
+
});
|
|
7622
7963
|
this.gridEl.appendChild(mark);
|
|
7623
7964
|
this.attributionMark = mark;
|
|
7624
7965
|
}
|
|
@@ -7684,6 +8025,9 @@ var VelaWorkspace = class {
|
|
|
7684
8025
|
...topbarHas(this.topbarComp, "indicators") && (picker || this.indicatorsOverride) ? { onIndicatorsClick: this.indicatorsOverride ? () => this.runOverride(this.indicatorsOverride) : () => picker.open() } : {},
|
|
7685
8026
|
getContext: () => this.context(),
|
|
7686
8027
|
...this.drawingsEnabled ? { onDrawingsClick: () => this.openDrawingsDrawer() } : {},
|
|
8028
|
+
// Multi-chart only: the stop that isolates the ACTIVE chart (the
|
|
8029
|
+
// per-cell hover cluster has no cursor to reveal it on mobile).
|
|
8030
|
+
...this.monoLayout ? {} : { onMaximizeClick: () => this.toggleMobileMaximize() },
|
|
7687
8031
|
onMoreClick: () => this.openMoreDrawer(),
|
|
7688
8032
|
onSettingsClick: () => this.active.chart.renderer.openSettings()
|
|
7689
8033
|
}) : null;
|
|
@@ -7890,7 +8234,9 @@ var VelaWorkspace = class {
|
|
|
7890
8234
|
this.pool.clear();
|
|
7891
8235
|
for (const { id, ...cs } of st.charts.slice(liveCount)) this.pool.set(id, cs);
|
|
7892
8236
|
this.order = st.charts.map((c) => c.id);
|
|
8237
|
+
this.clearMaximized();
|
|
7893
8238
|
this.applyGrid();
|
|
8239
|
+
this.refreshCellControls();
|
|
7894
8240
|
const nextActive2 = st.activeCellId && this.cellsById.has(st.activeCellId) ? st.activeCellId : this.order[0] ?? null;
|
|
7895
8241
|
if (nextActive2 === this.activeId) this.projectActiveCell();
|
|
7896
8242
|
else this.setActiveCell(nextActive2);
|
|
@@ -7916,6 +8262,7 @@ var VelaWorkspace = class {
|
|
|
7916
8262
|
const def = this.monoLayout ? null : ensureLayout(st.layout);
|
|
7917
8263
|
if (def) this.def = def;
|
|
7918
8264
|
this.cellBackend = this.backendFor(this.def);
|
|
8265
|
+
this.clearMaximized();
|
|
7919
8266
|
this.applyGrid();
|
|
7920
8267
|
this.buildCells();
|
|
7921
8268
|
this.syncCellPresentation();
|
|
@@ -7979,6 +8326,7 @@ var VelaWorkspace = class {
|
|
|
7979
8326
|
setLayout(layout) {
|
|
7980
8327
|
if (this.destroyed) return;
|
|
7981
8328
|
if (this.monoLayout) return;
|
|
8329
|
+
this.clearMaximized();
|
|
7982
8330
|
const next = this.resolveLayout(layout);
|
|
7983
8331
|
const nextBackend = this.backendFor(next);
|
|
7984
8332
|
const rebuildAll = nextBackend !== this.cellBackend;
|
|
@@ -7999,6 +8347,7 @@ var VelaWorkspace = class {
|
|
|
7999
8347
|
this.buildCells();
|
|
8000
8348
|
this.alignNewCellStyles(preexisting);
|
|
8001
8349
|
this.syncCellPresentation();
|
|
8350
|
+
this.refreshCellControls();
|
|
8002
8351
|
this.topbar.setLayout(next.id);
|
|
8003
8352
|
const nextActive = activeAfterLayout(this.activeId, this.order.slice(0, next.cells.length));
|
|
8004
8353
|
if (nextActive === this.activeId) this.projectActiveCell();
|
|
@@ -8007,6 +8356,67 @@ var VelaWorkspace = class {
|
|
|
8007
8356
|
this.events.emit("layout:changed", { layout: next.id });
|
|
8008
8357
|
this.markStateDirty();
|
|
8009
8358
|
}
|
|
8359
|
+
/** The identity of the cell maximized over the whole grid, or null. */
|
|
8360
|
+
get maximizedCell() {
|
|
8361
|
+
return this.maximizedId;
|
|
8362
|
+
}
|
|
8363
|
+
/**
|
|
8364
|
+
* Maximize one cell over the whole grid, or restore the layout with `null`. Pure
|
|
8365
|
+
* presentation: the other cells stay alive underneath — charts, subscriptions and
|
|
8366
|
+
* state untouched — so restoring is instant. The maximized cell becomes the active
|
|
8367
|
+
* one. Transient view state (also reachable from each cell's bottom-center view
|
|
8368
|
+
* cluster): switching layouts or applying a state document restores the grid.
|
|
8369
|
+
*/
|
|
8370
|
+
maximizeCell(id) {
|
|
8371
|
+
if (this.destroyed) return;
|
|
8372
|
+
if (id != null && (!this.cellsById.has(id) || this.def.cells.length <= 1)) return;
|
|
8373
|
+
if (id === this.maximizedId) return;
|
|
8374
|
+
this.maximizedId = id;
|
|
8375
|
+
if (id) this.setActiveCell(id);
|
|
8376
|
+
this.applyGrid();
|
|
8377
|
+
this.refreshCellControls();
|
|
8378
|
+
this.syncMobileMaximize();
|
|
8379
|
+
this.events.emit("cell:maximized", { id });
|
|
8380
|
+
}
|
|
8381
|
+
/** The mobile bar's maximize stop: one press isolates the ACTIVE chart over the
|
|
8382
|
+
* grid; while something is already isolated — the chart, or a pane inside it
|
|
8383
|
+
* (mobile's double-tap) — the press restores that instead. Every branch re-syncs
|
|
8384
|
+
* the stop on its own (`maximizeCell` directly, `panes.maximize` via its
|
|
8385
|
+
* synchronous `pane:changed`). */
|
|
8386
|
+
toggleMobileMaximize() {
|
|
8387
|
+
const cell = this.activeId ? this.cellsById.get(this.activeId) : void 0;
|
|
8388
|
+
if (!cell) return;
|
|
8389
|
+
if (this.maximizedId) this.maximizeCell(null);
|
|
8390
|
+
else if (cell.chart.panes.list().some((p) => p.maximized)) cell.chart.panes.maximize(null);
|
|
8391
|
+
else this.maximizeCell(cell.id);
|
|
8392
|
+
}
|
|
8393
|
+
/** Keep the mobile bar's maximize stop truthful: lit (inverse chip, restore
|
|
8394
|
+
* glyph) while the active chart covers the grid OR one of its panes is
|
|
8395
|
+
* maximized — the state a double-tap toggles is otherwise invisible on mobile. */
|
|
8396
|
+
syncMobileMaximize() {
|
|
8397
|
+
if (!this.mobileBar) return;
|
|
8398
|
+
const cell = this.activeId ? this.cellsById.get(this.activeId) : void 0;
|
|
8399
|
+
const paneMax = cell ? cell.chart.panes.list().some((p) => p.maximized) : false;
|
|
8400
|
+
this.mobileBar.setMaximizeActive(this.maximizedId != null || paneMax);
|
|
8401
|
+
}
|
|
8402
|
+
/**
|
|
8403
|
+
* Trade the SLOTS of two live cells — the grid arrangement changes, the cells
|
|
8404
|
+
* themselves (charts, indicators, drawings, the active flag) stay untouched.
|
|
8405
|
+
* What each cell's drag handle commits; also callable directly by hosts.
|
|
8406
|
+
*/
|
|
8407
|
+
swapCells(a, b) {
|
|
8408
|
+
if (this.destroyed || a === b) return;
|
|
8409
|
+
const i = this.order.indexOf(a);
|
|
8410
|
+
const j = this.order.indexOf(b);
|
|
8411
|
+
if (i < 0 || j < 0 || !this.cellsById.has(a) || !this.cellsById.has(b)) return;
|
|
8412
|
+
[this.order[i], this.order[j]] = [this.order[j], this.order[i]];
|
|
8413
|
+
for (const [k] of this.def.cells.entries()) {
|
|
8414
|
+
const host = this.cellsById.get(this.order[k] ?? "")?.host;
|
|
8415
|
+
if (host) this.gridEl.appendChild(host);
|
|
8416
|
+
}
|
|
8417
|
+
this.applyGrid();
|
|
8418
|
+
this.markStateDirty();
|
|
8419
|
+
}
|
|
8010
8420
|
resize() {
|
|
8011
8421
|
this.splitters.layout();
|
|
8012
8422
|
}
|
|
@@ -8076,6 +8486,7 @@ var VelaWorkspace = class {
|
|
|
8076
8486
|
this.mobileBar?.renderActions();
|
|
8077
8487
|
this.mobileBar?.setSymbol(cell.symbol);
|
|
8078
8488
|
this.mobileBar?.setTimeframe(cell.timeframe);
|
|
8489
|
+
this.syncMobileMaximize();
|
|
8079
8490
|
this.drawingPill?.onChart(cell.chart);
|
|
8080
8491
|
const pushHistory = () => this.topbar.setHistoryState(cell.history.canUndo, cell.history.canRedo);
|
|
8081
8492
|
this.historyUnsub?.();
|
|
@@ -8165,8 +8576,78 @@ var VelaWorkspace = class {
|
|
|
8165
8576
|
const host = this.cellsById.get(this.order[i] ?? "")?.host;
|
|
8166
8577
|
if (host) host.style.gridArea = perCell[slot.id]?.gridArea ?? "";
|
|
8167
8578
|
}
|
|
8579
|
+
this.applyMaximizePresentation();
|
|
8580
|
+
this.mountAttributionMark();
|
|
8168
8581
|
this.splitters.layout();
|
|
8169
8582
|
}
|
|
8583
|
+
/** Overlay the maximize presentation on the freshly applied grid: EVERY cell spans
|
|
8584
|
+
* the full track grid — the maximized one on top, the siblings invisible beneath
|
|
8585
|
+
* it (their charts stay alive — restoring is instant). The siblings must span too:
|
|
8586
|
+
* left in their slots they would auto-flow into implicit zero-height rows, whose
|
|
8587
|
+
* gaps steal height from the maximized cell and collapse their renderers to 0.
|
|
8588
|
+
* The splitter strips and the active ring hide via the `data-maximized` rules. */
|
|
8589
|
+
applyMaximizePresentation() {
|
|
8590
|
+
const maxId = this.maximizedId;
|
|
8591
|
+
if (maxId) this.gridEl.dataset.maximized = "1";
|
|
8592
|
+
else delete this.gridEl.dataset.maximized;
|
|
8593
|
+
for (const [id, cell] of this.cellsById) {
|
|
8594
|
+
const style = cell.host.style;
|
|
8595
|
+
if (maxId) style.gridArea = "1 / 1 / -1 / -1";
|
|
8596
|
+
style.zIndex = maxId && id === maxId ? "5" : "";
|
|
8597
|
+
style.visibility = maxId && id !== maxId ? "hidden" : "";
|
|
8598
|
+
}
|
|
8599
|
+
}
|
|
8600
|
+
/** Rebuild every cell's view cluster (the maximize gate or state changed). */
|
|
8601
|
+
refreshCellControls() {
|
|
8602
|
+
for (const cell of this.cellsById.values()) cell.refreshControls();
|
|
8603
|
+
}
|
|
8604
|
+
/** Drop the transient maximize on a structural change (layout switch, state
|
|
8605
|
+
* document) — WITH the event, so hosts tracking `cell:maximized` never drift
|
|
8606
|
+
* from `maximizedCell`. The caller's own grid re-apply paints the restore. */
|
|
8607
|
+
clearMaximized() {
|
|
8608
|
+
if (this.maximizedId == null) return;
|
|
8609
|
+
this.maximizedId = null;
|
|
8610
|
+
this.events.emit("cell:maximized", { id: null });
|
|
8611
|
+
}
|
|
8612
|
+
/** The live cell under a viewport point, excluding `excludeId` and any host a
|
|
8613
|
+
* maximize has hidden — the drag handle's hit-test. */
|
|
8614
|
+
cellAtPoint(x, y, excludeId) {
|
|
8615
|
+
for (const [id, cell] of this.cellsById) {
|
|
8616
|
+
if (id === excludeId || cell.host.style.visibility === "hidden") continue;
|
|
8617
|
+
const r = cell.host.getBoundingClientRect();
|
|
8618
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return id;
|
|
8619
|
+
}
|
|
8620
|
+
return null;
|
|
8621
|
+
}
|
|
8622
|
+
/** Mark one cell as the live drop target of a grip drag (null clears all) —
|
|
8623
|
+
* the `data-drop-target` stylesheet rule paints the dashed preview ring. */
|
|
8624
|
+
setDropTarget(id) {
|
|
8625
|
+
for (const [cid, cell] of this.cellsById) {
|
|
8626
|
+
if (cid === id) cell.host.dataset.dropTarget = "1";
|
|
8627
|
+
else delete cell.host.dataset.dropTarget;
|
|
8628
|
+
}
|
|
8629
|
+
}
|
|
8630
|
+
/** The cell whose bottom-left corner the grid's attribution mark floats in — the
|
|
8631
|
+
* maximized cell while one covers the grid, else the bottom-left slot's cell. */
|
|
8632
|
+
bottomLeftCell() {
|
|
8633
|
+
if (this.maximizedId) return this.cellsById.get(this.maximizedId);
|
|
8634
|
+
const grid = occupancyGrid(this.def);
|
|
8635
|
+
const slot = grid[grid.length - 1]?.[0];
|
|
8636
|
+
const idx = this.def.cells.findIndex((c) => (c.area ?? c.id) === slot);
|
|
8637
|
+
return this.cellsById.get(this.order[idx >= 0 ? idx : 0] ?? "");
|
|
8638
|
+
}
|
|
8639
|
+
/** Keep the shared attribution mark inside the BOTTOM-LEFT visible cell: its
|
|
8640
|
+
* offsets ride that cell's renderer-published `--vela-bottom-gutter` /
|
|
8641
|
+
* `--vela-toolbar-gutter`, so collapsed pane strips push the mark up without any
|
|
8642
|
+
* bookkeeping here. Re-run after anything that changes which host that is
|
|
8643
|
+
* (layout switch, maximize, cell rebuild); a destroyed host drops the mark from
|
|
8644
|
+
* the DOM, and this re-mount brings it back. */
|
|
8645
|
+
mountAttributionMark() {
|
|
8646
|
+
const mark = this.attributionMark;
|
|
8647
|
+
if (!mark) return;
|
|
8648
|
+
const host = this.bottomLeftCell()?.host ?? this.gridEl;
|
|
8649
|
+
if (mark.parentElement !== host) host.appendChild(mark);
|
|
8650
|
+
}
|
|
8170
8651
|
/** Create the cells the current layout wants but don't exist yet (pool-first).
|
|
8171
8652
|
* A slot's CELL IDENTITY is `order[i]` (declaration order — never the slot's own
|
|
8172
8653
|
* positional id); slots past the declared list mint an auto identity once. */
|
|
@@ -8198,6 +8679,12 @@ var VelaWorkspace = class {
|
|
|
8198
8679
|
setTimezone: (zone) => this.setTimezone(zone),
|
|
8199
8680
|
context: () => this.context(),
|
|
8200
8681
|
activate: (id2) => this.setActiveCell(id2),
|
|
8682
|
+
multiCell: () => !this.monoLayout && this.def.cells.length > 1,
|
|
8683
|
+
isMaximized: (id2) => this.maximizedId === id2,
|
|
8684
|
+
toggleMaximize: (id2) => this.maximizeCell(this.maximizedId === id2 ? null : id2),
|
|
8685
|
+
cellDragTarget: (id2, x, y) => this.cellAtPoint(x, y, id2),
|
|
8686
|
+
previewDropTarget: (target) => this.setDropTarget(target),
|
|
8687
|
+
dropCell: (id2, target) => this.swapCells(id2, target),
|
|
8201
8688
|
onMarketChanged: (id2) => this.onCellMarketChanged(id2),
|
|
8202
8689
|
onPriceStyleChanged: (id2) => this.onCellPriceStyleChanged(id2),
|
|
8203
8690
|
onIndicatorsChanged: (id2) => this.onCellIndicatorsChanged(id2),
|
|
@@ -8211,6 +8698,7 @@ var VelaWorkspace = class {
|
|
|
8211
8698
|
if (id === this.activeId) cell.host.dataset.active = "1";
|
|
8212
8699
|
this.wireCell(cell);
|
|
8213
8700
|
cell.chart.renderer.setLayoutMode(this.layoutCtl.current);
|
|
8701
|
+
cell.setControlsSuspended(this.layoutCtl.current === "mobile");
|
|
8214
8702
|
if (this.favs.length > 0) cell.chart.drawings.setFavorites(this.favs);
|
|
8215
8703
|
cell.setManifest(this.manifest, pooled?.indicators == null);
|
|
8216
8704
|
cell.restorePersistedExt();
|
|
@@ -8220,6 +8708,7 @@ var VelaWorkspace = class {
|
|
|
8220
8708
|
const host = this.cellsById.get(this.order[i] ?? "")?.host;
|
|
8221
8709
|
if (host) this.gridEl.appendChild(host);
|
|
8222
8710
|
}
|
|
8711
|
+
this.mountAttributionMark();
|
|
8223
8712
|
}
|
|
8224
8713
|
/** Per-cell chart subscriptions (trigger ② — the chart instance is stable for the
|
|
8225
8714
|
* cell's whole life, so these live and die with the cell). */
|
|
@@ -8279,6 +8768,9 @@ var VelaWorkspace = class {
|
|
|
8279
8768
|
chart.on("viewport:changed", (range) => this.propagateViewport(cell.id, range));
|
|
8280
8769
|
chart.renderer.onConfigChanged(() => this.propagateStylePrefs(cell.id));
|
|
8281
8770
|
chart.on("theme:changed", (t) => this.setTheme(t));
|
|
8771
|
+
chart.on("pane:changed", () => {
|
|
8772
|
+
if (cell.id === this.activeId) this.syncMobileMaximize();
|
|
8773
|
+
});
|
|
8282
8774
|
chart.renderer.onAxisLongPress((e) => {
|
|
8283
8775
|
if (this.layoutCtl.current !== "mobile") return;
|
|
8284
8776
|
if (e.axis === "time") this.openTimezoneDrawer();
|
|
@@ -8599,6 +9091,7 @@ var VelaWorkspace = class {
|
|
|
8599
9091
|
for (const cell of this.cellsById.values()) {
|
|
8600
9092
|
cell.chart.renderer.closeDialogs();
|
|
8601
9093
|
cell.chart.renderer.setLayoutMode(mode);
|
|
9094
|
+
cell.setControlsSuspended(mode === "mobile");
|
|
8602
9095
|
}
|
|
8603
9096
|
this.syncCellPresentation();
|
|
8604
9097
|
}
|