@luxalgo/vela 0.7.4 → 0.7.6
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-RF1axuy1.d.cts → DataProvider-CGa4KHRa.d.cts} +1 -1
- package/dist/{DataProvider-CVpN5sCq.d.ts → DataProvider-CmWUtDVL.d.ts} +1 -1
- package/dist/{chunk-CSFNKXUW.js → chunk-EMB53WNQ.js} +16 -7
- package/dist/{chunk-DUURH3RN.js → chunk-JSVCWYBF.js} +271 -74
- package/dist/{contributions-CspPrMAJ.d.ts → contributions-C7Wsm_S9.d.ts} +13 -3
- package/dist/{contributions-DpZeu7KP.d.cts → contributions-D6p1RB38.d.cts} +13 -3
- package/dist/index.cjs +271 -74
- package/dist/index.d.cts +19 -6
- package/dist/index.d.ts +19 -6
- package/dist/index.js +1 -1
- package/dist/{options-XVpfr9HV.d.cts → options-D1AJKsn_.d.cts} +18 -0
- package/dist/{options-XVpfr9HV.d.ts → options-D1AJKsn_.d.ts} +18 -0
- package/dist/{plugin-D07HD2GL.d.cts → plugin-CxWfg6vK.d.cts} +3 -3
- package/dist/{plugin-BoLOkblF.d.ts → plugin-SodaQhjQ.d.ts} +3 -3
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- 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-model-Bd4qKuD6.d.cts → statusline-model-gWHrsOy3.d.cts} +3 -3
- package/dist/{statusline-model-CPa_hmhd.d.ts → statusline-model-xBiVsKoE.d.ts} +3 -3
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/vela.global.js +271 -74
- package/dist/vela.global.min.js +53 -49
- package/dist/widget.cjs +286 -80
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +3 -3
- package/dist/workspace.cjs +286 -80
- package/dist/workspace.d.cts +10 -5
- package/dist/workspace.d.ts +10 -5
- package/dist/workspace.js +2 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-
|
|
1
|
+
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, TIMEZONES, normalizeTimezone, tzMenuLabel, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs } from './chunk-JSVCWYBF.js';
|
|
2
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-BFA32GOU.js';
|
|
3
3
|
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-ZADTVRUO.js';
|
|
4
4
|
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-BKHSQ4YM.js';
|
|
@@ -5644,6 +5644,11 @@ var ChartCell = class {
|
|
|
5644
5644
|
if (typeof zone === "string" && normalizeTimezone(zone) !== normalizeTimezone(this.deps.timezone())) {
|
|
5645
5645
|
this.deps.setTimezone(normalizeTimezone(zone));
|
|
5646
5646
|
}
|
|
5647
|
+
const style = this.priceStyle;
|
|
5648
|
+
if (style !== (this.state.priceStyle ?? "candles")) {
|
|
5649
|
+
this.state.priceStyle = style;
|
|
5650
|
+
this.deps.onPriceStyleChanged(this.id);
|
|
5651
|
+
}
|
|
5647
5652
|
this.syncStatuslineColors();
|
|
5648
5653
|
this.syncPlotOverlayTokens();
|
|
5649
5654
|
});
|
|
@@ -6283,16 +6288,19 @@ var ChartCell = class {
|
|
|
6283
6288
|
* a persistence handler's `restore` runs silently, a user-driven call records.
|
|
6284
6289
|
*/
|
|
6285
6290
|
addExternalIndicator(entry) {
|
|
6291
|
+
const { id, inputs, props, hidden, ...script } = entry;
|
|
6286
6292
|
this.addManifestInstance(
|
|
6287
|
-
{ ...
|
|
6288
|
-
{ external: true, ...
|
|
6293
|
+
{ ...script, enabled: true },
|
|
6294
|
+
{ external: true, ...id !== void 0 ? { id } : {}, ...inputs ? { inputs } : {}, ...props ? { props } : {}, ...hidden ? { hidden: true } : {} }
|
|
6289
6295
|
);
|
|
6290
6296
|
}
|
|
6291
6297
|
/** Add ONE instance of a manifest entry (repeatable — duplicates are legitimate). */
|
|
6292
6298
|
addManifestInstance(entry, opts = {}) {
|
|
6293
6299
|
if (this.destroyed) return;
|
|
6294
6300
|
const values = opts.inputs || opts.props ? { inputs: opts.inputs, props: opts.props } : void 0;
|
|
6295
|
-
const
|
|
6301
|
+
const handle = this.addToChart(entry, values, opts.id);
|
|
6302
|
+
if (!handle) return;
|
|
6303
|
+
const it = { entry, handle, id: handle.id, ...opts.external ? { external: true } : {}, ...values ? { values } : {} };
|
|
6296
6304
|
if (opts.hidden) it.handle?.setVisible(false);
|
|
6297
6305
|
this.instances.push(it);
|
|
6298
6306
|
this.deps.onIndicatorsChanged(this.id);
|
|
@@ -6301,7 +6309,7 @@ var ChartCell = class {
|
|
|
6301
6309
|
this.history.push({
|
|
6302
6310
|
undo: () => this.dropInstance(snapshot),
|
|
6303
6311
|
redo: () => {
|
|
6304
|
-
snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
|
|
6312
|
+
snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
|
|
6305
6313
|
this.instances.push(snapshot);
|
|
6306
6314
|
this.deps.onIndicatorsChanged(this.id);
|
|
6307
6315
|
}
|
|
@@ -6314,7 +6322,7 @@ var ChartCell = class {
|
|
|
6314
6322
|
const snapshot = it;
|
|
6315
6323
|
this.history.push({
|
|
6316
6324
|
undo: () => {
|
|
6317
|
-
snapshot.handle = this.addToChart(snapshot.entry, snapshot.values);
|
|
6325
|
+
snapshot.handle = this.addToChart(snapshot.entry, snapshot.values, snapshot.id);
|
|
6318
6326
|
this.instances.push(snapshot);
|
|
6319
6327
|
this.deps.onIndicatorsChanged(this.id);
|
|
6320
6328
|
},
|
|
@@ -6404,9 +6412,10 @@ var ChartCell = class {
|
|
|
6404
6412
|
this.deps.onIndicatorsChanged(this.id);
|
|
6405
6413
|
});
|
|
6406
6414
|
}
|
|
6407
|
-
addToChart(entry, values) {
|
|
6415
|
+
addToChart(entry, values, id) {
|
|
6408
6416
|
try {
|
|
6409
6417
|
return this.inner?.addIndicator(entry.script, {
|
|
6418
|
+
...id !== void 0 ? { id } : {},
|
|
6410
6419
|
...entry.language !== void 0 ? { language: entry.language } : {},
|
|
6411
6420
|
...values?.inputs ? { inputs: values.inputs } : {},
|
|
6412
6421
|
...values?.props ? { props: values.props } : {}
|
|
@@ -5356,6 +5356,17 @@ function hasOwnCandlePaint(style) {
|
|
|
5356
5356
|
for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
|
|
5357
5357
|
return false;
|
|
5358
5358
|
}
|
|
5359
|
+
var CANDLE_OVERRIDE_KEYS = [
|
|
5360
|
+
"candleUpColor",
|
|
5361
|
+
"candleDownColor",
|
|
5362
|
+
"candleBodyVisible",
|
|
5363
|
+
"candleBorderVisible",
|
|
5364
|
+
"candleBorderUpColor",
|
|
5365
|
+
"candleBorderDownColor",
|
|
5366
|
+
"candleWickVisible",
|
|
5367
|
+
"candleWickUpColor",
|
|
5368
|
+
"candleWickDownColor"
|
|
5369
|
+
];
|
|
5359
5370
|
function candleOverrideFor(style, bags) {
|
|
5360
5371
|
if (!hasOwnCandlePaint(style)) return null;
|
|
5361
5372
|
const bag = bags[style] ?? {};
|
|
@@ -5427,7 +5438,7 @@ function clampLevel(v) {
|
|
|
5427
5438
|
function clampSpacing(v) {
|
|
5428
5439
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
5429
5440
|
}
|
|
5430
|
-
function factoryResetConfig(factory) {
|
|
5441
|
+
function factoryResetConfig(factory, priceStyle = factory.series.style) {
|
|
5431
5442
|
const bag = {};
|
|
5432
5443
|
for (const t of chartTypes()) {
|
|
5433
5444
|
const section = t.settings;
|
|
@@ -5449,10 +5460,16 @@ function factoryResetConfig(factory) {
|
|
|
5449
5460
|
if (!section.instances) addRows(section.rows);
|
|
5450
5461
|
bag[t.id] = defaults;
|
|
5451
5462
|
}
|
|
5463
|
+
for (const t of chartTypes()) {
|
|
5464
|
+
if (!hasOwnCandlePaint(t.id)) continue;
|
|
5465
|
+
const defaults = bag[t.id] ?? {};
|
|
5466
|
+
for (const key of CANDLE_OVERRIDE_KEYS) defaults[key] = null;
|
|
5467
|
+
bag[t.id] = defaults;
|
|
5468
|
+
}
|
|
5452
5469
|
for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
|
|
5453
5470
|
bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
|
|
5454
5471
|
}
|
|
5455
|
-
return { ...factory, chartTypes: bag };
|
|
5472
|
+
return { ...factory, chartTypes: bag, series: { ...factory.series, style: priceStyle } };
|
|
5456
5473
|
}
|
|
5457
5474
|
function mergeConfig(base, patch) {
|
|
5458
5475
|
const p = asObject(patch);
|
|
@@ -7819,6 +7836,42 @@ function drawTextLines(ctx, lines, x, firstY, step, color) {
|
|
|
7819
7836
|
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], x, firstY + i * step);
|
|
7820
7837
|
}
|
|
7821
7838
|
|
|
7839
|
+
// src/core/marks/visibility.ts
|
|
7840
|
+
function markGroupOwnVisible(choices, groupId, groups) {
|
|
7841
|
+
const chosen = choices[groupId];
|
|
7842
|
+
if (typeof chosen === "boolean") return chosen;
|
|
7843
|
+
return groups.find((g) => g.id === groupId)?.visible !== false;
|
|
7844
|
+
}
|
|
7845
|
+
function markGroupVisible(choices, groupId, groups) {
|
|
7846
|
+
if (groupId === void 0) return true;
|
|
7847
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7848
|
+
let id = groupId;
|
|
7849
|
+
while (id !== void 0 && !seen.has(id)) {
|
|
7850
|
+
seen.add(id);
|
|
7851
|
+
if (!markGroupOwnVisible(choices, id, groups)) return false;
|
|
7852
|
+
const parent = groups.find((g) => g.id === id)?.parent;
|
|
7853
|
+
id = parent !== void 0 && groups.some((g) => g.id === parent) ? parent : void 0;
|
|
7854
|
+
}
|
|
7855
|
+
return true;
|
|
7856
|
+
}
|
|
7857
|
+
function markGroupRows(groups) {
|
|
7858
|
+
const ids = new Set(groups.map((g) => g.id));
|
|
7859
|
+
const out = [];
|
|
7860
|
+
const placed = /* @__PURE__ */ new Set();
|
|
7861
|
+
const place = (group, depth) => {
|
|
7862
|
+
if (placed.has(group.id)) return;
|
|
7863
|
+
placed.add(group.id);
|
|
7864
|
+
out.push({ group, depth });
|
|
7865
|
+
for (const child of groups) if (child.parent === group.id && child.id !== group.id) place(child, depth + 1);
|
|
7866
|
+
};
|
|
7867
|
+
for (const g of groups) {
|
|
7868
|
+
const parentKnown = g.parent !== void 0 && ids.has(g.parent) && g.parent !== g.id;
|
|
7869
|
+
if (!parentKnown) place(g, 0);
|
|
7870
|
+
}
|
|
7871
|
+
for (const g of groups) place(g, 0);
|
|
7872
|
+
return out;
|
|
7873
|
+
}
|
|
7874
|
+
|
|
7822
7875
|
// src/renderers/shared/marks-state.ts
|
|
7823
7876
|
function defaultMarksState() {
|
|
7824
7877
|
return { visible: true, groups: {} };
|
|
@@ -7831,11 +7884,8 @@ function mergeMarksState(base, patch) {
|
|
|
7831
7884
|
for (const [id, v] of Object.entries(g)) if (typeof v === "boolean") groups[id] = v;
|
|
7832
7885
|
return { visible: typeof p.visible === "boolean" ? p.visible : base.visible, groups };
|
|
7833
7886
|
}
|
|
7834
|
-
function
|
|
7835
|
-
|
|
7836
|
-
const chosen = state.groups[groupId];
|
|
7837
|
-
if (typeof chosen === "boolean") return chosen;
|
|
7838
|
-
return groups.find((g) => g.id === groupId)?.visible !== false;
|
|
7887
|
+
function markGroupVisible2(state, groupId, groups) {
|
|
7888
|
+
return markGroupVisible(state.groups, groupId, groups);
|
|
7839
7889
|
}
|
|
7840
7890
|
|
|
7841
7891
|
// src/renderers/native/core/SceneGraph.ts
|
|
@@ -10011,13 +10061,29 @@ function paintMarkLane(ctx, layout, deps) {
|
|
|
10011
10061
|
const img = deps.icons.get(mark.glyph.icon, ink, symbolPx, deps.dpr);
|
|
10012
10062
|
if (img) ctx.drawImage(img, center.x - symbolPx / 2, center.y - symbolPx / 2, symbolPx, symbolPx);
|
|
10013
10063
|
} else if (mark.glyph.letter) {
|
|
10064
|
+
const letter = mark.glyph.letter.slice(0, 2);
|
|
10014
10065
|
ctx.fillStyle = ink;
|
|
10015
|
-
|
|
10016
|
-
ctx.
|
|
10066
|
+
let px = letterFontPx(size, letter);
|
|
10067
|
+
ctx.font = `600 ${px}px ${deps.fontFamily}`;
|
|
10068
|
+
if (letter.length > 1) {
|
|
10069
|
+
px = fitLetterPx(px, ctx.measureText(letter).width, symbolInnerWidth(shape, size));
|
|
10070
|
+
ctx.font = `600 ${px}px ${deps.fontFamily}`;
|
|
10071
|
+
}
|
|
10072
|
+
ctx.fillText(letter, center.x, center.y + 0.5);
|
|
10017
10073
|
}
|
|
10018
10074
|
}
|
|
10019
10075
|
ctx.restore();
|
|
10020
10076
|
}
|
|
10077
|
+
function letterFontPx(size, letter) {
|
|
10078
|
+
return Math.round(size * (letter.length > 1 ? 0.38 : 0.58));
|
|
10079
|
+
}
|
|
10080
|
+
function symbolInnerWidth(shape, size) {
|
|
10081
|
+
return (shape === "pin" ? size * 0.82 : size) - 3;
|
|
10082
|
+
}
|
|
10083
|
+
function fitLetterPx(px, width, inner) {
|
|
10084
|
+
if (!(width > inner) || !(width > 0)) return px;
|
|
10085
|
+
return Math.max(4, Math.floor(px * inner / width));
|
|
10086
|
+
}
|
|
10021
10087
|
function traceShape(ctx, shape, x, y, size) {
|
|
10022
10088
|
const r = size / 2;
|
|
10023
10089
|
ctx.beginPath();
|
|
@@ -10219,7 +10285,7 @@ var ChromeRenderer = class {
|
|
|
10219
10285
|
this.markLayout = layoutMarkLane({
|
|
10220
10286
|
marks: scene.timelineMarks,
|
|
10221
10287
|
groups: scene.markGroups,
|
|
10222
|
-
hidden: (groupId) => !
|
|
10288
|
+
hidden: (groupId) => !markGroupVisible2(scene.marks, groupId, scene.markGroups),
|
|
10223
10289
|
barTimes: this.barTimes(scene),
|
|
10224
10290
|
intervalMs: coords.barInterval,
|
|
10225
10291
|
xOf: (bar) => coords.logicalToX(bar),
|
|
@@ -10909,6 +10975,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
10909
10975
|
muted and non-interactive. Applied to each row's children so it survives display:contents;
|
|
10910
10976
|
!important beats the inline opacity on labels. */
|
|
10911
10977
|
.vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
|
|
10978
|
+
/* A mark group nested under a parent group on the Events tab: indented one step per level
|
|
10979
|
+
(--vela-sd-depth). The indent rides the first child (the switch) as a MARGIN \u2014 padding
|
|
10980
|
+
would push the switch's own tick out of its box. */
|
|
10981
|
+
.vela-sd-nested>*:first-child{margin-left:calc(var(--vela-sd-depth,1)*24px);}
|
|
10912
10982
|
/* \u2500\u2500 mobile presentation (.vela-sd-mobile on the scrim; structural sizes are inline in open()) \u2500\u2500
|
|
10913
10983
|
The tab rail becomes a burger-opened overlay sidebar; the group TOC becomes a sticky
|
|
10914
10984
|
row of horizontally scrollable tabs; the instance strip scrolls instead of wrapping;
|
|
@@ -10957,6 +11027,8 @@ var SettingsDialog = class {
|
|
|
10957
11027
|
this.hostSections = [];
|
|
10958
11028
|
/** The timeline-mark groups (defined + named by marks) — one checkbox each on the Events tab. */
|
|
10959
11029
|
this.markGroups = [];
|
|
11030
|
+
/** A group's OWN switch (what its checkbox shows) — a child under an off parent keeps its own state. */
|
|
11031
|
+
this.markGroupOwnVisible = () => true;
|
|
10960
11032
|
this.markGroupVisible = () => true;
|
|
10961
11033
|
/** The Canvas → Theme row: current app theme + where a pick is raised. The row is a
|
|
10962
11034
|
* host callback, NOT a config patch — the app theme stays out of the persisted
|
|
@@ -10974,6 +11046,9 @@ var SettingsDialog = class {
|
|
|
10974
11046
|
this.activeSection = null;
|
|
10975
11047
|
/** Mobile chrome: fullscreen card, burger-opened section sidebar, TOC as top tabs. */
|
|
10976
11048
|
this.mobileLayout = false;
|
|
11049
|
+
/** Opens/closes the mobile section sidebar of the CURRENT build (the burger in the
|
|
11050
|
+
* shell header outlives pane rebuilds, so it reaches the rail through here). */
|
|
11051
|
+
this.toggleRail = null;
|
|
10977
11052
|
/** The visibility policy: setting ids hidden by the host (subtree semantics). */
|
|
10978
11053
|
this.hiddenSettings = /* @__PURE__ */ new Set();
|
|
10979
11054
|
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
@@ -10983,7 +11058,8 @@ var SettingsDialog = class {
|
|
|
10983
11058
|
this.hostSections = sections;
|
|
10984
11059
|
}
|
|
10985
11060
|
/** The timeline-mark groups and their current visibility — the Events tab's rows on next open. */
|
|
10986
|
-
setMarkGroups(groups, visible) {
|
|
11061
|
+
setMarkGroups(groups, visible, ownVisible = visible) {
|
|
11062
|
+
this.markGroupOwnVisible = ownVisible;
|
|
10987
11063
|
this.markGroups = groups;
|
|
10988
11064
|
this.markGroupVisible = visible;
|
|
10989
11065
|
}
|
|
@@ -11049,7 +11125,6 @@ var SettingsDialog = class {
|
|
|
11049
11125
|
this.onReset = onReset ?? null;
|
|
11050
11126
|
ensureControlStyles();
|
|
11051
11127
|
const mobile = this.mobileLayout;
|
|
11052
|
-
let toggleRail = null;
|
|
11053
11128
|
let burger;
|
|
11054
11129
|
if (mobile) {
|
|
11055
11130
|
burger = document.createElement("button");
|
|
@@ -11057,8 +11132,83 @@ var SettingsDialog = class {
|
|
|
11057
11132
|
burger.className = "vela-sd-burger";
|
|
11058
11133
|
burger.innerHTML = iconAt("burger", 16);
|
|
11059
11134
|
burger.title = "Sections";
|
|
11060
|
-
burger.addEventListener("click", () => toggleRail?.());
|
|
11135
|
+
burger.addEventListener("click", () => this.toggleRail?.());
|
|
11136
|
+
}
|
|
11137
|
+
const ui = new Dialog({
|
|
11138
|
+
host: this.container,
|
|
11139
|
+
title: "Chart settings",
|
|
11140
|
+
// Non-modal: a live-edit dialog must leave the page interactive — a modal
|
|
11141
|
+
// machine locks pointer events on the whole body, killing the chart, the
|
|
11142
|
+
// legend, and the body-portaled popovers (color picker, select lists).
|
|
11143
|
+
modal: false,
|
|
11144
|
+
contained: true,
|
|
11145
|
+
align: "top",
|
|
11146
|
+
draggable: !mobile,
|
|
11147
|
+
flush: true,
|
|
11148
|
+
className: "vela-dialog--settings",
|
|
11149
|
+
headerStart: burger,
|
|
11150
|
+
closeOnBackdrop: true,
|
|
11151
|
+
footer: (foot) => {
|
|
11152
|
+
foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
|
|
11153
|
+
const resetBtn = document.createElement("button");
|
|
11154
|
+
resetBtn.type = "button";
|
|
11155
|
+
resetBtn.textContent = "Reset defaults";
|
|
11156
|
+
resetBtn.className = "vela-sd-btn";
|
|
11157
|
+
resetBtn.addEventListener("click", () => this.onReset?.());
|
|
11158
|
+
foot.appendChild(resetBtn);
|
|
11159
|
+
},
|
|
11160
|
+
// A close signal may only close ITS OWN dialog: the machine reports the exit
|
|
11161
|
+
// asynchronously, so a close-then-reopen in one tick would otherwise see the
|
|
11162
|
+
// old instance's signal tear down the freshly opened one.
|
|
11163
|
+
onOpenChange: (open) => {
|
|
11164
|
+
if (!open && this.ui === ui) this.close();
|
|
11165
|
+
}
|
|
11166
|
+
});
|
|
11167
|
+
if (mobile) ui.positioner.classList.add("vela-sd-mobile");
|
|
11168
|
+
ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
|
|
11169
|
+
this.root = ui.positioner;
|
|
11170
|
+
this.ui = ui;
|
|
11171
|
+
const panes = this.buildContent(ui, config, section, mobile);
|
|
11172
|
+
ui.show();
|
|
11173
|
+
this.layoutPanes(panes);
|
|
11174
|
+
}
|
|
11175
|
+
/**
|
|
11176
|
+
* Re-seed every control of an OPEN dialog from `config`, in place: the shell stays
|
|
11177
|
+
* (no close/open transition), only the tab rail and panes rebuild, landing back on
|
|
11178
|
+
* the current tab. The reset path — the restored values must show without the
|
|
11179
|
+
* dialog re-entering. No-op while closed.
|
|
11180
|
+
*/
|
|
11181
|
+
refresh(config) {
|
|
11182
|
+
const ui = this.ui;
|
|
11183
|
+
if (!ui) return;
|
|
11184
|
+
closeOpenPopovers();
|
|
11185
|
+
closeWidthPopover();
|
|
11186
|
+
for (const dispose of this.hintTips) dispose();
|
|
11187
|
+
this.hintTips = [];
|
|
11188
|
+
this.config = config;
|
|
11189
|
+
ui.body.replaceChildren();
|
|
11190
|
+
this.layoutPanes(this.buildContent(ui, config, this.activeSection ?? void 0, this.mobileLayout));
|
|
11191
|
+
}
|
|
11192
|
+
/** Structured chart-type panes (instance strip / group TOC) own their layout and
|
|
11193
|
+
* tag their rows hosts instead; each host gets its own field grid. Runs on a SHOWN
|
|
11194
|
+
* dialog — the grids measure their labels. */
|
|
11195
|
+
layoutPanes(panes) {
|
|
11196
|
+
for (const el of panes) {
|
|
11197
|
+
const hosts = [...el.querySelectorAll("[data-sd-rows-host]")];
|
|
11198
|
+
if (hosts.length === 0) {
|
|
11199
|
+
this.layoutSettingsGrids(el);
|
|
11200
|
+
continue;
|
|
11201
|
+
}
|
|
11202
|
+
for (const h of hosts) this.layoutSettingsGrids(h);
|
|
11061
11203
|
}
|
|
11204
|
+
}
|
|
11205
|
+
/**
|
|
11206
|
+
* Build the tab rail + one pane per section into `ui.body` (the linear `body` of
|
|
11207
|
+
* section markers and rows is split afterwards), select `section` (the active
|
|
11208
|
+
* style's own tab when none is asked for), and return the pane elements for
|
|
11209
|
+
* {@link layoutPanes}.
|
|
11210
|
+
*/
|
|
11211
|
+
buildContent(ui, config, section, mobile) {
|
|
11062
11212
|
const body = document.createElement("div");
|
|
11063
11213
|
body.style.cssText = "display:flex;flex-direction:column;gap:0;";
|
|
11064
11214
|
const sid = (el, id) => {
|
|
@@ -11262,9 +11412,28 @@ var SettingsDialog = class {
|
|
|
11262
11412
|
if (this.markGroups.length > 0) {
|
|
11263
11413
|
body.append(sid(this.section("Events"), MARKS_SETTINGS_ID));
|
|
11264
11414
|
body.append(sid(this.sectionTitle("Visible events"), MARKS_GROUPS_SETTINGS_ID));
|
|
11265
|
-
|
|
11266
|
-
|
|
11415
|
+
const rowsById = /* @__PURE__ */ new Map();
|
|
11416
|
+
const rows = markGroupRows(this.markGroups);
|
|
11417
|
+
const refreshDimming = () => {
|
|
11418
|
+
for (const { group } of rows) {
|
|
11419
|
+
const el = rowsById.get(group.id);
|
|
11420
|
+
if (!el || group.parent === void 0) continue;
|
|
11421
|
+
el.classList.toggle("vela-sd-soft", !this.markGroupVisible(group.parent));
|
|
11422
|
+
}
|
|
11423
|
+
};
|
|
11424
|
+
for (const { group: g, depth } of rows) {
|
|
11425
|
+
const row = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
|
|
11426
|
+
this.emit({ marks: { groups: { [g.id]: v } } });
|
|
11427
|
+
refreshDimming();
|
|
11428
|
+
});
|
|
11429
|
+
if (depth > 0) {
|
|
11430
|
+
row.classList.add("vela-sd-nested");
|
|
11431
|
+
row.style.setProperty("--vela-sd-depth", String(depth));
|
|
11432
|
+
}
|
|
11433
|
+
rowsById.set(g.id, row);
|
|
11434
|
+
body.append(sid(row, markGroupSettingsId(g.id)));
|
|
11267
11435
|
}
|
|
11436
|
+
refreshDimming();
|
|
11268
11437
|
}
|
|
11269
11438
|
renderChartTypeSections("end");
|
|
11270
11439
|
renderHostSections("end");
|
|
@@ -11292,8 +11461,8 @@ var SettingsDialog = class {
|
|
|
11292
11461
|
if (mobile) {
|
|
11293
11462
|
railScrim = document.createElement("div");
|
|
11294
11463
|
railScrim.className = "vela-sd-railscrim";
|
|
11295
|
-
railScrim.addEventListener("click", () => toggleRail?.());
|
|
11296
|
-
toggleRail = (open) => {
|
|
11464
|
+
railScrim.addEventListener("click", () => this.toggleRail?.());
|
|
11465
|
+
this.toggleRail = (open) => {
|
|
11297
11466
|
const on = open ?? !rail.classList.contains("open");
|
|
11298
11467
|
rail.classList.toggle("open", on);
|
|
11299
11468
|
railScrim?.classList.toggle("open", on);
|
|
@@ -11333,35 +11502,6 @@ var SettingsDialog = class {
|
|
|
11333
11502
|
});
|
|
11334
11503
|
if (hidActive) activate(0);
|
|
11335
11504
|
};
|
|
11336
|
-
const ui = new Dialog({
|
|
11337
|
-
host: this.container,
|
|
11338
|
-
title: "Chart settings",
|
|
11339
|
-
// Non-modal: a live-edit dialog must leave the page interactive — a modal
|
|
11340
|
-
// machine locks pointer events on the whole body, killing the chart, the
|
|
11341
|
-
// legend, and the body-portaled popovers (color picker, select lists).
|
|
11342
|
-
modal: false,
|
|
11343
|
-
contained: true,
|
|
11344
|
-
align: "top",
|
|
11345
|
-
draggable: !mobile,
|
|
11346
|
-
flush: true,
|
|
11347
|
-
className: "vela-dialog--settings",
|
|
11348
|
-
headerStart: burger,
|
|
11349
|
-
closeOnBackdrop: true,
|
|
11350
|
-
footer: (foot) => {
|
|
11351
|
-
foot.style.cssText = `padding:10px 14px;display:flex;align-items:center;justify-content:flex-start;gap:8px;`;
|
|
11352
|
-
const resetBtn = document.createElement("button");
|
|
11353
|
-
resetBtn.type = "button";
|
|
11354
|
-
resetBtn.textContent = "Reset defaults";
|
|
11355
|
-
resetBtn.className = "vela-sd-btn";
|
|
11356
|
-
resetBtn.addEventListener("click", () => this.onReset?.());
|
|
11357
|
-
foot.appendChild(resetBtn);
|
|
11358
|
-
},
|
|
11359
|
-
onOpenChange: (open) => {
|
|
11360
|
-
if (!open) this.close();
|
|
11361
|
-
}
|
|
11362
|
-
});
|
|
11363
|
-
if (mobile) ui.positioner.classList.add("vela-sd-mobile");
|
|
11364
|
-
ui.positioner.style.paddingTop = mobile ? "0" : "8vh";
|
|
11365
11505
|
const activate = (idx) => {
|
|
11366
11506
|
panes.forEach((p, i) => {
|
|
11367
11507
|
p.el.style.display = i === idx ? "block" : "none";
|
|
@@ -11370,7 +11510,7 @@ var SettingsDialog = class {
|
|
|
11370
11510
|
this.activeSection = panes[idx]?.title ?? null;
|
|
11371
11511
|
if (mobile) {
|
|
11372
11512
|
ui.titleEl.textContent = panes[idx]?.title ?? "Chart settings";
|
|
11373
|
-
toggleRail?.(false);
|
|
11513
|
+
this.toggleRail?.(false);
|
|
11374
11514
|
}
|
|
11375
11515
|
};
|
|
11376
11516
|
panes.forEach((p, i) => {
|
|
@@ -11385,17 +11525,7 @@ var SettingsDialog = class {
|
|
|
11385
11525
|
shell.append(rail, paneHost);
|
|
11386
11526
|
if (railScrim) shell.append(railScrim);
|
|
11387
11527
|
ui.body.appendChild(shell);
|
|
11388
|
-
|
|
11389
|
-
this.ui = ui;
|
|
11390
|
-
ui.show();
|
|
11391
|
-
for (const p of panes) {
|
|
11392
|
-
const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
|
|
11393
|
-
if (hosts.length === 0) {
|
|
11394
|
-
this.layoutSettingsGrids(p.el);
|
|
11395
|
-
continue;
|
|
11396
|
-
}
|
|
11397
|
-
for (const h of hosts) this.layoutSettingsGrids(h);
|
|
11398
|
-
}
|
|
11528
|
+
return panes.map((p) => p.el);
|
|
11399
11529
|
}
|
|
11400
11530
|
close() {
|
|
11401
11531
|
closeOpenPopovers();
|
|
@@ -11403,6 +11533,7 @@ var SettingsDialog = class {
|
|
|
11403
11533
|
const ui = this.ui;
|
|
11404
11534
|
this.ui = null;
|
|
11405
11535
|
this.root = null;
|
|
11536
|
+
this.toggleRail = null;
|
|
11406
11537
|
this.tabs = [];
|
|
11407
11538
|
for (const dispose of this.hintTips) dispose();
|
|
11408
11539
|
this.hintTips = [];
|
|
@@ -18843,7 +18974,11 @@ var NativeRenderer = class {
|
|
|
18843
18974
|
}
|
|
18844
18975
|
this.settingsDialog.setTheme(this.theme);
|
|
18845
18976
|
this.settingsDialog.setHostSections(this.hostSettingsSections);
|
|
18846
|
-
this.settingsDialog.setMarkGroups(
|
|
18977
|
+
this.settingsDialog.setMarkGroups(
|
|
18978
|
+
this.markGroupsForEventsTab(),
|
|
18979
|
+
(id) => markGroupVisible2(this.scene.marks, id, this.scene.markGroups),
|
|
18980
|
+
(id) => markGroupOwnVisible(this.scene.marks.groups, id, this.scene.markGroups)
|
|
18981
|
+
);
|
|
18847
18982
|
this.settingsDialog.setHiddenSettings(this.hiddenSettings);
|
|
18848
18983
|
this.syncThemeControl();
|
|
18849
18984
|
this.settingsDialog.toggle(
|
|
@@ -18851,13 +18986,24 @@ var NativeRenderer = class {
|
|
|
18851
18986
|
(patch) => this.applyConfig(patch),
|
|
18852
18987
|
(json) => this.applyConfig(json),
|
|
18853
18988
|
() => {
|
|
18854
|
-
if (this.factoryConfig) this.applyConfig(
|
|
18855
|
-
this.settingsDialog?.
|
|
18856
|
-
this.openSettingsDialog();
|
|
18989
|
+
if (this.factoryConfig) this.applyConfig(this.factoryResetDocument(this.factoryConfig));
|
|
18990
|
+
this.settingsDialog?.refresh(this.getConfig());
|
|
18857
18991
|
},
|
|
18858
18992
|
section
|
|
18859
18993
|
);
|
|
18860
18994
|
}
|
|
18995
|
+
/**
|
|
18996
|
+
* The document "Reset defaults" applies: every setting back to its first-run value,
|
|
18997
|
+
* with two things that are NOT settings held or resolved here — the price style
|
|
18998
|
+
* stays the one the user is looking at, and the timeline-mark groups (an additive
|
|
18999
|
+
* merge, like the type bags) are named back to their host-declared visibility.
|
|
19000
|
+
*/
|
|
19001
|
+
factoryResetDocument(factory) {
|
|
19002
|
+
const doc = factoryResetConfig(factory, this.scene.priceStyle);
|
|
19003
|
+
const groups = { ...doc.marks.groups };
|
|
19004
|
+
for (const g of this.markGroupsInUse()) groups[g.id] = g.visible !== false;
|
|
19005
|
+
return { ...doc, marks: { ...doc.marks, groups } };
|
|
19006
|
+
}
|
|
18861
19007
|
/** Close the in-chart dialogs (indicator settings + chart-settings gear). No-op when none are open. */
|
|
18862
19008
|
closeDialogs() {
|
|
18863
19009
|
this.inputsUI?.closeOpenDialog();
|
|
@@ -19920,6 +20066,15 @@ var NativeRenderer = class {
|
|
|
19920
20066
|
markGroupsInUse() {
|
|
19921
20067
|
return effectiveMarkGroups(this.scene.timelineMarks, this.scene.markGroups);
|
|
19922
20068
|
}
|
|
20069
|
+
/**
|
|
20070
|
+
* The groups as the Events tab lists them: nested only under a DEFINED parent. The
|
|
20071
|
+
* painter's visibility chain resolves parents against the defined groups alone, so a
|
|
20072
|
+
* parent that marks merely name must not nest (and dim) a child the painter still shows.
|
|
20073
|
+
*/
|
|
20074
|
+
markGroupsForEventsTab() {
|
|
20075
|
+
const defined = new Set(this.scene.markGroups.map((g) => g.id));
|
|
20076
|
+
return this.markGroupsInUse().map((g) => g.parent !== void 0 && !defined.has(g.parent) ? { ...g, parent: void 0 } : g);
|
|
20077
|
+
}
|
|
19923
20078
|
onViewportChange(cb) {
|
|
19924
20079
|
this.viewportCbs.add(cb);
|
|
19925
20080
|
return () => this.viewportCbs.delete(cb);
|
|
@@ -21552,10 +21707,18 @@ var IndicatorRegistry = class {
|
|
|
21552
21707
|
this.records = /* @__PURE__ */ new Map();
|
|
21553
21708
|
this.counter = 0;
|
|
21554
21709
|
}
|
|
21555
|
-
/**
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
|
|
21710
|
+
/**
|
|
21711
|
+
* Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
|
|
21712
|
+
* id skips anything already recorded — and anything `taken` reports live elsewhere
|
|
21713
|
+
* (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
|
|
21714
|
+
*/
|
|
21715
|
+
nextId(prefix = "ind", taken = () => false) {
|
|
21716
|
+
let id;
|
|
21717
|
+
do {
|
|
21718
|
+
this.counter += 1;
|
|
21719
|
+
id = `${prefix}-${this.counter}`;
|
|
21720
|
+
} while (this.records.has(id) || taken(id));
|
|
21721
|
+
return id;
|
|
21559
21722
|
}
|
|
21560
21723
|
add(record) {
|
|
21561
21724
|
this.records.set(record.id, record);
|
|
@@ -21791,6 +21954,7 @@ var MarksController = class {
|
|
|
21791
21954
|
defineGroup(group) {
|
|
21792
21955
|
if (!group || typeof group.id !== "string" || group.id.length === 0) throw new Error("[vela] marks.defineGroup: `id` must be a non-empty string");
|
|
21793
21956
|
if (typeof group.label !== "string") throw new Error(`[vela] marks.defineGroup: group "${group.id}" needs a string \`label\``);
|
|
21957
|
+
if (group.parent !== void 0 && (typeof group.parent !== "string" || group.parent.length === 0)) throw new Error(`[vela] marks.defineGroup: group "${group.id}" has a \`parent\` that is not a group id`);
|
|
21794
21958
|
this.groups.set(group.id, { ...group });
|
|
21795
21959
|
this.sync();
|
|
21796
21960
|
}
|
|
@@ -21810,12 +21974,16 @@ var MarksController = class {
|
|
|
21810
21974
|
}
|
|
21811
21975
|
this.renderer.applyFeature("marks", { groups: { [id]: visible } });
|
|
21812
21976
|
}
|
|
21813
|
-
/**
|
|
21977
|
+
/**
|
|
21978
|
+
* A group's effective visibility: its own switch — the user's (persisted) choice, else
|
|
21979
|
+
* the declared default, else visible — AND every ancestor's, so a child under a
|
|
21980
|
+
* switched-off parent reads hidden whatever its own choice says.
|
|
21981
|
+
*/
|
|
21814
21982
|
isGroupVisible(id) {
|
|
21815
21983
|
const state = this.renderer.readFeature("marks");
|
|
21816
|
-
const
|
|
21817
|
-
if (typeof
|
|
21818
|
-
return this.groups.
|
|
21984
|
+
const groups = {};
|
|
21985
|
+
for (const [gid, v] of Object.entries(state?.groups ?? {})) if (typeof v === "boolean") groups[gid] = v;
|
|
21986
|
+
return markGroupVisible(groups, id, [...this.groups.values()]);
|
|
21819
21987
|
}
|
|
21820
21988
|
destroy() {
|
|
21821
21989
|
for (const unsub of this.subs) unsub();
|
|
@@ -22870,7 +23038,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22870
23038
|
this.engines.set(language, engine);
|
|
22871
23039
|
}
|
|
22872
23040
|
addIndicator(source, options = {}) {
|
|
22873
|
-
const id = this.
|
|
23041
|
+
const id = this.claimIndicatorId(options.id);
|
|
22874
23042
|
const title = options.title ?? "Indicator";
|
|
22875
23043
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
22876
23044
|
this.handles.set(id, handle);
|
|
@@ -22892,7 +23060,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
22892
23060
|
const existing = this.registry.all().find((r) => r.native?.type === type);
|
|
22893
23061
|
if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
|
|
22894
23062
|
}
|
|
22895
|
-
const id = this.registry.nextId("native");
|
|
23063
|
+
const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
|
|
22896
23064
|
const title = descriptor?.title ?? type;
|
|
22897
23065
|
const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
|
|
22898
23066
|
this.handles.set(id, handle);
|
|
@@ -23170,6 +23338,24 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
23170
23338
|
this.events.clear();
|
|
23171
23339
|
}
|
|
23172
23340
|
// ── internals ───────────────────────────────────────────────
|
|
23341
|
+
/**
|
|
23342
|
+
* The id a new script indicator runs under: the host's when supplied, else a minted
|
|
23343
|
+
* one. A host id is opaque but must be a non-empty string, and it must not be live
|
|
23344
|
+
* on this chart — a duplicate is a programming error surfaced synchronously, never
|
|
23345
|
+
* renamed behind the caller's back (the whole point of supplying one is that
|
|
23346
|
+
* `handle.id` equals what was passed). "Live" reads both the records and the handle
|
|
23347
|
+
* map: a fail-soft handle never enters the registry but still owns its id.
|
|
23348
|
+
*/
|
|
23349
|
+
claimIndicatorId(requested) {
|
|
23350
|
+
if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
|
|
23351
|
+
if (typeof requested !== "string" || requested.length === 0) {
|
|
23352
|
+
throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
|
|
23353
|
+
}
|
|
23354
|
+
if (this.handles.has(requested) || this.registry.get(requested)) {
|
|
23355
|
+
throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
|
|
23356
|
+
}
|
|
23357
|
+
return requested;
|
|
23358
|
+
}
|
|
23173
23359
|
async startIndicator(id, source, options, handle) {
|
|
23174
23360
|
try {
|
|
23175
23361
|
await this.readyPromise;
|
|
@@ -26375,6 +26561,7 @@ function registerClassicIndicators() {
|
|
|
26375
26561
|
}
|
|
26376
26562
|
|
|
26377
26563
|
// src/Vela.ts
|
|
26564
|
+
var asError = (err) => err instanceof Error ? err : new Error(String(err));
|
|
26378
26565
|
var Vela = class {
|
|
26379
26566
|
constructor(container, options = {}, deps = {}) {
|
|
26380
26567
|
registerBuiltinChartTypes();
|
|
@@ -26498,7 +26685,12 @@ var Vela = class {
|
|
|
26498
26685
|
* fetch from — the same relationship `script:run` has to `context:changed`.
|
|
26499
26686
|
*/
|
|
26500
26687
|
runScript(source, options) {
|
|
26501
|
-
|
|
26688
|
+
let handle;
|
|
26689
|
+
try {
|
|
26690
|
+
handle = this.addIndicator(source, options);
|
|
26691
|
+
} catch (err) {
|
|
26692
|
+
return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
|
|
26693
|
+
}
|
|
26502
26694
|
const updates = /* @__PURE__ */ new Set();
|
|
26503
26695
|
const drop = () => {
|
|
26504
26696
|
try {
|
|
@@ -26542,7 +26734,12 @@ var Vela = class {
|
|
|
26542
26734
|
});
|
|
26543
26735
|
}
|
|
26544
26736
|
runIndicator(source, options) {
|
|
26545
|
-
|
|
26737
|
+
let handle;
|
|
26738
|
+
try {
|
|
26739
|
+
handle = this.addIndicator(source, options);
|
|
26740
|
+
} catch (err) {
|
|
26741
|
+
return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
|
|
26742
|
+
}
|
|
26546
26743
|
return new Promise((resolve) => {
|
|
26547
26744
|
const offReady = handle.on("ready", () => {
|
|
26548
26745
|
offReady();
|