@luxalgo/vela 0.6.7 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-WVP4XV5A.js → chunk-N7LGMKCE.js} +303 -78
- package/dist/{chunk-6WFKTAT4.js → chunk-SQETIBFU.js} +46 -8
- package/dist/{contributions-Z12BrWCy.d.ts → contributions-DLLdV9jD.d.ts} +13 -0
- package/dist/{contributions-Hv4_cOJo.d.cts → contributions-Vw-Hm58R.d.cts} +13 -0
- package/dist/index.cjs +46 -8
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/{plugin-BXhDHTYn.d.cts → plugin-BnJgjLAy.d.cts} +1 -1
- package/dist/{plugin-9koBjc5H.d.ts → plugin-CC7rBrOY.d.ts} +1 -1
- package/dist/plugin.d.cts +2 -2
- package/dist/plugin.d.ts +2 -2
- package/dist/{bottombar-Br4rzx_R.d.ts → statusline-CGkB2EOo.d.ts} +87 -4
- package/dist/{bottombar-Div5zibZ.d.cts → statusline-DqzBqy42.d.cts} +87 -4
- package/dist/vela.global.js +46 -8
- package/dist/vela.global.min.js +5 -5
- package/dist/widget.cjs +348 -85
- package/dist/widget.d.cts +10 -83
- package/dist/widget.d.ts +10 -83
- package/dist/widget.js +3 -3
- package/dist/workspace.cjs +348 -85
- package/dist/workspace.d.cts +52 -8
- package/dist/workspace.d.ts +52 -8
- package/dist/workspace.js +2 -2
- package/package.json +1 -1
package/dist/workspace.cjs
CHANGED
|
@@ -5200,6 +5200,11 @@ var Topbar = class {
|
|
|
5200
5200
|
/** Overrides that LEFT their native slot (default side + a declared `order`) — they
|
|
5201
5201
|
* render through the flow cluster like ordinary actions. */
|
|
5202
5202
|
this.flowingOverrides = /* @__PURE__ */ new Set();
|
|
5203
|
+
/** Tooltips of the CURRENT icon-only action buttons — rebuilt with every
|
|
5204
|
+
* renderActions pass (contributed buttons are replaceChildren'd away). */
|
|
5205
|
+
this.actionTooltips = [];
|
|
5206
|
+
/** `iconOnly` misuse warned once per action id (renderActions re-runs freely). */
|
|
5207
|
+
this.warnedIconless = /* @__PURE__ */ new Set();
|
|
5203
5208
|
this.onHairlineSync = () => {
|
|
5204
5209
|
if (this.hairlineRaf) return;
|
|
5205
5210
|
const win = this.el.ownerDocument.defaultView;
|
|
@@ -5462,6 +5467,8 @@ var Topbar = class {
|
|
|
5462
5467
|
this.actionsHost.replaceChildren();
|
|
5463
5468
|
this.leftActionsHost.replaceChildren();
|
|
5464
5469
|
for (const pin of this.pinned.values()) pin.host.replaceChildren();
|
|
5470
|
+
for (const t of this.actionTooltips) t.destroy();
|
|
5471
|
+
this.actionTooltips = [];
|
|
5465
5472
|
const doc = this.actionsHost.ownerDocument;
|
|
5466
5473
|
const flowLeft = this.comp.left.includes("actions");
|
|
5467
5474
|
const flowRight = this.comp.right.includes("actions");
|
|
@@ -5471,10 +5478,20 @@ var Topbar = class {
|
|
|
5471
5478
|
if (!pin && builtin.has(action.id) && !this.flowingOverrides.has(action.id)) continue;
|
|
5472
5479
|
const left = pin ? pin.left : action.align === "left";
|
|
5473
5480
|
if (!pin && !(left ? flowLeft : flowRight)) continue;
|
|
5481
|
+
const iconOnly = action.iconOnly === true && !!action.icon;
|
|
5482
|
+
if (action.iconOnly === true && !action.icon && !this.warnedIconless.has(action.id)) {
|
|
5483
|
+
this.warnedIconless.add(action.id);
|
|
5484
|
+
console.warn(`[vela] widget action "${action.id}": iconOnly needs an \`icon\` \u2014 rendering the label instead.`);
|
|
5485
|
+
}
|
|
5474
5486
|
const b = doc.createElement("button");
|
|
5475
|
-
b.className = left ? "vela-widget-action-left" : "vela-widget-action";
|
|
5487
|
+
b.className = left ? "vela-widget-action-left" : iconOnly ? "vela-widget-tool" : "vela-widget-action";
|
|
5476
5488
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
5477
|
-
|
|
5489
|
+
if (iconOnly) {
|
|
5490
|
+
b.setAttribute("aria-label", action.label);
|
|
5491
|
+
this.actionTooltips.push(new Tooltip(b, { content: action.label, triggerId: `vela-action-${action.id}`, host: this.host }));
|
|
5492
|
+
} else {
|
|
5493
|
+
b.appendChild(doc.createTextNode(action.label));
|
|
5494
|
+
}
|
|
5478
5495
|
b.addEventListener("click", () => {
|
|
5479
5496
|
const c = this.opts.getContext?.();
|
|
5480
5497
|
if (c) action.run(c);
|
|
@@ -5525,7 +5542,7 @@ var Topbar = class {
|
|
|
5525
5542
|
this.tfMenu.destroy();
|
|
5526
5543
|
this.styleMenu.destroy();
|
|
5527
5544
|
this.layoutPicker?.destroy();
|
|
5528
|
-
for (const t of [...this.tooltips, ...this.panelTooltips]) t.destroy();
|
|
5545
|
+
for (const t of [...this.tooltips, ...this.panelTooltips, ...this.actionTooltips]) t.destroy();
|
|
5529
5546
|
this.el.remove();
|
|
5530
5547
|
}
|
|
5531
5548
|
/** One icon-only tool button with its kit tooltip, parked in `sink` for disposal. */
|
|
@@ -17233,19 +17250,8 @@ function registerBuiltinChartTypes() {
|
|
|
17233
17250
|
registerChartType({ id: "heikinashi", label: "Heikin Ashi", barTransform: HEIKIN_ASHI });
|
|
17234
17251
|
}
|
|
17235
17252
|
|
|
17236
|
-
// src/workspace/sync.ts
|
|
17237
|
-
function syncTargets(originId, setting, cellIds) {
|
|
17238
|
-
if (setting == null || setting === false) return [];
|
|
17239
|
-
if (setting === true) return cellIds.filter((id) => id !== originId);
|
|
17240
|
-
const group = setting[originId];
|
|
17241
|
-
if (group == null) return [];
|
|
17242
|
-
return cellIds.filter((id) => id !== originId && setting[id] === group);
|
|
17243
|
-
}
|
|
17244
|
-
function rangesWithin(a, b, epsMs) {
|
|
17245
|
-
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
17246
|
-
}
|
|
17247
|
-
|
|
17248
17253
|
// src/state/document.ts
|
|
17254
|
+
var SYNC_KINDS = ["viewport", "symbol", "timeframe", "crosshair", "drawings", "style"];
|
|
17249
17255
|
function prefixedSymbol(cell) {
|
|
17250
17256
|
if (!cell?.symbol) return void 0;
|
|
17251
17257
|
if (cell.symbol.includes(":") || !cell.provider) return cell.symbol;
|
|
@@ -17333,7 +17339,7 @@ function sanitizeSync(raw) {
|
|
|
17333
17339
|
if (raw == null || typeof raw !== "object") return null;
|
|
17334
17340
|
const s = raw;
|
|
17335
17341
|
const out = {};
|
|
17336
|
-
for (const kind of
|
|
17342
|
+
for (const kind of SYNC_KINDS) {
|
|
17337
17343
|
const v = s[kind];
|
|
17338
17344
|
if (v === true) out[kind] = true;
|
|
17339
17345
|
else if (v != null && typeof v === "object") {
|
|
@@ -17376,6 +17382,28 @@ function sanitizeTrackSizes(raw) {
|
|
|
17376
17382
|
return Object.keys(out).length > 0 ? out : null;
|
|
17377
17383
|
}
|
|
17378
17384
|
|
|
17385
|
+
// src/workspace/sync.ts
|
|
17386
|
+
function syncTargets(originId, setting, cellIds) {
|
|
17387
|
+
if (setting == null || setting === false) return [];
|
|
17388
|
+
if (setting === true) return cellIds.filter((id) => id !== originId);
|
|
17389
|
+
const group = setting[originId];
|
|
17390
|
+
if (group == null) return [];
|
|
17391
|
+
return cellIds.filter((id) => id !== originId && setting[id] === group);
|
|
17392
|
+
}
|
|
17393
|
+
function rangesWithin(a, b, epsMs) {
|
|
17394
|
+
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
17395
|
+
}
|
|
17396
|
+
var STYLE_SYNC_CONFIG_KEYS = ["layout", "panes", "grid", "priceScale", "crosshair"];
|
|
17397
|
+
function styleConfigSlice(config) {
|
|
17398
|
+
if (config == null || typeof config !== "object") return null;
|
|
17399
|
+
const doc = config;
|
|
17400
|
+
const out = {};
|
|
17401
|
+
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
17402
|
+
if (doc[key] != null && typeof doc[key] === "object") out[key] = doc[key];
|
|
17403
|
+
}
|
|
17404
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
17405
|
+
}
|
|
17406
|
+
|
|
17379
17407
|
// src/workspace/persist.ts
|
|
17380
17408
|
var memoryStore = /* @__PURE__ */ new Map();
|
|
17381
17409
|
function memoryStorageAdapter() {
|
|
@@ -19730,12 +19758,16 @@ var IndicatorInputsDialog = class {
|
|
|
19730
19758
|
closeOnEscape: false,
|
|
19731
19759
|
footer: (foot) => {
|
|
19732
19760
|
foot.append(
|
|
19761
|
+
this.resetAction(),
|
|
19733
19762
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
19734
19763
|
this.dialogButton("Ok", true, () => this.close())
|
|
19735
19764
|
);
|
|
19736
19765
|
},
|
|
19766
|
+
// Stale-guard: destroying a dialog fires its machine's onOpenChange(false)
|
|
19767
|
+
// asynchronously — after a reset rebuild that notification must not close
|
|
19768
|
+
// the replacement dialog.
|
|
19737
19769
|
onOpenChange: (open2) => {
|
|
19738
|
-
if (!open2) this.close();
|
|
19770
|
+
if (!open2 && this.uiDialog === ui) this.close();
|
|
19739
19771
|
}
|
|
19740
19772
|
});
|
|
19741
19773
|
applyChromeTokens(ui.panel, t);
|
|
@@ -19866,6 +19898,30 @@ var IndicatorInputsDialog = class {
|
|
|
19866
19898
|
}
|
|
19867
19899
|
this.close();
|
|
19868
19900
|
}
|
|
19901
|
+
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
19902
|
+
* (`margin-right:auto` against the footer's flex-end keeps Cancel/Ok right). */
|
|
19903
|
+
resetAction() {
|
|
19904
|
+
const b = this.dialogButton("Reset defaults", false, () => this.resetToDefaults());
|
|
19905
|
+
b.style.marginRight = "auto";
|
|
19906
|
+
return b;
|
|
19907
|
+
}
|
|
19908
|
+
/** Restore every input to its declared default (re-running the indicator), then
|
|
19909
|
+
* re-open the form so each control re-reads the restored values — the same
|
|
19910
|
+
* rebuild-after-reset move as the chart-settings dialog. The open-time snapshot
|
|
19911
|
+
* survives the rebuild, so Cancel after a reset still reverts the whole session. */
|
|
19912
|
+
resetToDefaults() {
|
|
19913
|
+
const row = this.row;
|
|
19914
|
+
if (!row) return;
|
|
19915
|
+
const snap = this.snapshot;
|
|
19916
|
+
for (const inp of row.inputs) {
|
|
19917
|
+
if (row.values[inp.key] !== inp.defval) {
|
|
19918
|
+
row.values[inp.key] = inp.defval;
|
|
19919
|
+
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
19920
|
+
}
|
|
19921
|
+
}
|
|
19922
|
+
this.open(row);
|
|
19923
|
+
if (snap) this.snapshot = snap;
|
|
19924
|
+
}
|
|
19869
19925
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
19870
19926
|
commit(row, key, value) {
|
|
19871
19927
|
row.values[key] = value;
|
|
@@ -31299,6 +31355,11 @@ function resizeSplit(split, dyTotal, minPx = MIN_PANE_PX) {
|
|
|
31299
31355
|
}
|
|
31300
31356
|
|
|
31301
31357
|
// src/renderers/native/backdrop/BackdropRenderer.ts
|
|
31358
|
+
function clipHighlightRect(x1, x2, left, right) {
|
|
31359
|
+
const x = Math.max(left, x1);
|
|
31360
|
+
const end = Math.min(right, x2);
|
|
31361
|
+
return end > x ? { x, width: end - x } : null;
|
|
31362
|
+
}
|
|
31302
31363
|
var BackdropRenderer = class {
|
|
31303
31364
|
constructor() {
|
|
31304
31365
|
this.canvas = null;
|
|
@@ -31331,17 +31392,22 @@ var BackdropRenderer = class {
|
|
|
31331
31392
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
31332
31393
|
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
31333
31394
|
drawHighlights(ctx, scene, coords) {
|
|
31334
|
-
const
|
|
31335
|
-
if (
|
|
31395
|
+
const sessions = scene.sessionHighlightBands();
|
|
31396
|
+
if (sessions.length > 0) {
|
|
31397
|
+
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
31398
|
+
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
31399
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
31400
|
+
}
|
|
31401
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
31402
|
+
}
|
|
31403
|
+
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
31336
31404
|
for (const band of bands) {
|
|
31337
31405
|
const x1 = coords.timeToX(band.from);
|
|
31338
31406
|
const x2 = coords.timeToX(band.to);
|
|
31339
|
-
|
|
31340
|
-
|
|
31341
|
-
const cw = Math.min(coords.width, x2) - cx;
|
|
31342
|
-
if (cw <= 0) continue;
|
|
31407
|
+
const rect = clipHighlightRect(x1, x2, left, right);
|
|
31408
|
+
if (!rect) continue;
|
|
31343
31409
|
ctx.fillStyle = band.color;
|
|
31344
|
-
ctx.fillRect(
|
|
31410
|
+
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
31345
31411
|
}
|
|
31346
31412
|
}
|
|
31347
31413
|
// ── grid ── vert/horz gate on `scene.showGrid` AND their own per-axis visibility
|
|
@@ -36083,61 +36149,144 @@ var MarketStatusTracker = class {
|
|
|
36083
36149
|
};
|
|
36084
36150
|
|
|
36085
36151
|
// src/widget/session-shading.ts
|
|
36086
|
-
|
|
36152
|
+
var DAY_MS2 = 864e5;
|
|
36153
|
+
var MINUTE_MS = 6e4;
|
|
36154
|
+
function parseWindow(text) {
|
|
36155
|
+
if (typeof text !== "string") return null;
|
|
36156
|
+
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(text);
|
|
36157
|
+
if (!m) return null;
|
|
36158
|
+
const start = Number(m[1]) * 60 + Number(m[2]);
|
|
36159
|
+
const end = Number(m[3]) * 60 + Number(m[4]);
|
|
36160
|
+
if (start >= end || end > 1440) return null;
|
|
36161
|
+
return { start, end };
|
|
36162
|
+
}
|
|
36163
|
+
function parseSessionSpec(si) {
|
|
36164
|
+
const session = si?.["session"];
|
|
36165
|
+
if (typeof session !== "string" || session === "" || session === "24x7") return null;
|
|
36166
|
+
const regular = parseWindow(session);
|
|
36167
|
+
if (!regular) return null;
|
|
36168
|
+
const tz = si?.["timezone"];
|
|
36169
|
+
const timezone = typeof tz === "string" && tz !== "" ? tz : "Etc/UTC";
|
|
36170
|
+
const ext = parseWindow(si?.["session_extended"]);
|
|
36171
|
+
const extended = ext && ext.start <= regular.start && ext.end >= regular.end ? ext : { start: 0, end: 1440 };
|
|
36172
|
+
return { regular, extended, timezone };
|
|
36173
|
+
}
|
|
36174
|
+
var dtfCache = /* @__PURE__ */ new Map();
|
|
36175
|
+
function civilFormatter(tz) {
|
|
36176
|
+
const cached = dtfCache.get(tz);
|
|
36177
|
+
if (cached !== void 0) return cached;
|
|
36178
|
+
let dtf = null;
|
|
36179
|
+
try {
|
|
36180
|
+
dtf = new Intl.DateTimeFormat("en-US", {
|
|
36181
|
+
timeZone: tz,
|
|
36182
|
+
hourCycle: "h23",
|
|
36183
|
+
weekday: "short",
|
|
36184
|
+
year: "numeric",
|
|
36185
|
+
month: "2-digit",
|
|
36186
|
+
day: "2-digit",
|
|
36187
|
+
hour: "2-digit",
|
|
36188
|
+
minute: "2-digit"
|
|
36189
|
+
});
|
|
36190
|
+
} catch {
|
|
36191
|
+
dtf = null;
|
|
36192
|
+
}
|
|
36193
|
+
dtfCache.set(tz, dtf);
|
|
36194
|
+
return dtf;
|
|
36195
|
+
}
|
|
36196
|
+
function civilParts(dtf, ms) {
|
|
36197
|
+
const parts = dtf.formatToParts(ms);
|
|
36198
|
+
const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
|
|
36199
|
+
return {
|
|
36200
|
+
weekday: get("weekday"),
|
|
36201
|
+
year: Number(get("year")),
|
|
36202
|
+
month: Number(get("month")),
|
|
36203
|
+
day: Number(get("day")),
|
|
36204
|
+
hour: Number(get("hour")) % 24,
|
|
36205
|
+
minute: Number(get("minute"))
|
|
36206
|
+
};
|
|
36207
|
+
}
|
|
36208
|
+
function expandSessionZones(spec, from, to) {
|
|
36087
36209
|
const pre = [];
|
|
36088
36210
|
const post = [];
|
|
36089
|
-
|
|
36090
|
-
|
|
36091
|
-
|
|
36092
|
-
|
|
36093
|
-
|
|
36094
|
-
|
|
36095
|
-
|
|
36096
|
-
|
|
36211
|
+
const dtf = civilFormatter(spec.timezone);
|
|
36212
|
+
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post };
|
|
36213
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36214
|
+
for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
|
|
36215
|
+
const civil = civilParts(dtf, cursor);
|
|
36216
|
+
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
36217
|
+
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
36218
|
+
seen.add(key);
|
|
36219
|
+
if (civil.weekday === "Sat" || civil.weekday === "Sun") continue;
|
|
36220
|
+
const naive = Date.UTC(civil.year, civil.month - 1, civil.day);
|
|
36221
|
+
const noonGuess = naive + 720 * MINUTE_MS;
|
|
36222
|
+
const atNoon = civilParts(dtf, noonGuess);
|
|
36223
|
+
const offset = Date.UTC(atNoon.year, atNoon.month - 1, atNoon.day, atNoon.hour, atNoon.minute) - noonGuess;
|
|
36224
|
+
const at = (minutes) => naive + minutes * MINUTE_MS - offset;
|
|
36225
|
+
if (spec.extended.start < spec.regular.start) pre.push([at(spec.extended.start), at(spec.regular.start)]);
|
|
36226
|
+
if (spec.regular.end < spec.extended.end) post.push([at(spec.regular.end), at(spec.extended.end)]);
|
|
36097
36227
|
}
|
|
36098
36228
|
return { pre, post };
|
|
36099
36229
|
}
|
|
36100
|
-
var
|
|
36101
|
-
var MIN_LOOKBACK_MS = 3 * 864e5;
|
|
36102
|
-
var MAX_LOOKBACK_MS = 120 * 864e5;
|
|
36230
|
+
var COVER_PAD_MIN_MS = 2 * DAY_MS2;
|
|
36103
36231
|
var SessionShadingTracker = class {
|
|
36104
36232
|
constructor(onZones) {
|
|
36105
36233
|
this.onZones = onZones;
|
|
36106
|
-
/** Invalidates
|
|
36234
|
+
/** Invalidates the one async step (metadata resolution) — bumped by track()/stop(). */
|
|
36107
36235
|
this.epoch = 0;
|
|
36108
|
-
|
|
36109
|
-
|
|
36236
|
+
this.spec = null;
|
|
36237
|
+
this.ready = false;
|
|
36238
|
+
this.session = "regular";
|
|
36239
|
+
this.covered = null;
|
|
36240
|
+
/** The newest range seen — viewport moves during metadata resolution (a load's fit
|
|
36241
|
+
* animation) must not be lost, so the resolution always expands the LATEST range. */
|
|
36242
|
+
this.lastRange = null;
|
|
36243
|
+
}
|
|
36244
|
+
/** (Re)bind to a chart's data surface + market and expand once metadata lands. */
|
|
36110
36245
|
track(data, symbol, opts) {
|
|
36111
36246
|
const my = ++this.epoch;
|
|
36112
|
-
|
|
36247
|
+
this.ready = false;
|
|
36248
|
+
this.spec = null;
|
|
36249
|
+
this.covered = null;
|
|
36250
|
+
this.session = opts.session;
|
|
36251
|
+
this.lastRange = opts.range;
|
|
36252
|
+
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
36253
|
+
if (my !== this.epoch) return;
|
|
36254
|
+
this.ready = true;
|
|
36255
|
+
this.spec = parseSessionSpec(si);
|
|
36256
|
+
this.emit(this.lastRange ?? opts.range, true);
|
|
36257
|
+
});
|
|
36258
|
+
}
|
|
36259
|
+
/** Follow a pan/zoom synchronously: bands are epoch-anchored, so only a range that
|
|
36260
|
+
* leaves the last expansion's coverage needs a recompute — no fetch, no debounce. */
|
|
36261
|
+
updateRange(range) {
|
|
36262
|
+
this.lastRange = range;
|
|
36263
|
+
if (!this.ready) return;
|
|
36264
|
+
this.emit(range, false);
|
|
36113
36265
|
}
|
|
36114
36266
|
stop() {
|
|
36115
36267
|
this.epoch += 1;
|
|
36116
|
-
|
|
36117
|
-
|
|
36118
|
-
|
|
36119
|
-
|
|
36120
|
-
|
|
36121
|
-
|
|
36122
|
-
|
|
36123
|
-
|
|
36124
|
-
this.onZones(null);
|
|
36268
|
+
this.ready = false;
|
|
36269
|
+
this.spec = null;
|
|
36270
|
+
this.covered = null;
|
|
36271
|
+
this.lastRange = null;
|
|
36272
|
+
}
|
|
36273
|
+
emit(range, force) {
|
|
36274
|
+
if (!this.spec) {
|
|
36275
|
+
if (force) this.onZones(null);
|
|
36125
36276
|
return;
|
|
36126
36277
|
}
|
|
36127
|
-
if (
|
|
36128
|
-
this.onZones({ pre: [], post: [] });
|
|
36278
|
+
if (this.session !== "extended") {
|
|
36279
|
+
if (force) this.onZones({ pre: [], post: [] });
|
|
36129
36280
|
return;
|
|
36130
36281
|
}
|
|
36131
|
-
const
|
|
36132
|
-
const
|
|
36133
|
-
|
|
36134
|
-
|
|
36135
|
-
|
|
36136
|
-
|
|
36137
|
-
|
|
36138
|
-
|
|
36139
|
-
if (!regular || !extended) return;
|
|
36140
|
-
this.onZones(deriveSessionZones(regular, extended));
|
|
36282
|
+
const from = Math.min(range.from, range.to);
|
|
36283
|
+
const to = Math.max(range.from, range.to);
|
|
36284
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) return;
|
|
36285
|
+
if (!force && this.covered && from >= this.covered.from && to <= this.covered.to) return;
|
|
36286
|
+
const pad = Math.max(to - from, COVER_PAD_MIN_MS);
|
|
36287
|
+
const covered = { from: from - pad, to: to + pad };
|
|
36288
|
+
this.covered = covered;
|
|
36289
|
+
this.onZones(expandSessionZones(this.spec, covered.from, covered.to));
|
|
36141
36290
|
}
|
|
36142
36291
|
};
|
|
36143
36292
|
|
|
@@ -36574,7 +36723,11 @@ var ChartCell = class {
|
|
|
36574
36723
|
this.deps.toast(`No registered provider serves "${symbol2}" (registered: ${list})`, "error", 6e3);
|
|
36575
36724
|
});
|
|
36576
36725
|
this.inner.on("load:start", () => this.watermark?.setLoading(true));
|
|
36577
|
-
this.inner.on("load:end", () =>
|
|
36726
|
+
this.inner.on("load:end", () => {
|
|
36727
|
+
this.watermark?.setLoading(false);
|
|
36728
|
+
this.refreshSessionShading();
|
|
36729
|
+
});
|
|
36730
|
+
this.inner.on("viewport:changed", (range) => this.sessionShading.updateRange(range));
|
|
36578
36731
|
const tz = deps.timezone();
|
|
36579
36732
|
if (tz !== "Etc/UTC") this.inner.renderer.set("timezone", tz);
|
|
36580
36733
|
this.indicatorTitlesOn = seed.indicatorTitles ?? true;
|
|
@@ -36701,14 +36854,18 @@ var ChartCell = class {
|
|
|
36701
36854
|
this.refreshSessionShading();
|
|
36702
36855
|
});
|
|
36703
36856
|
}
|
|
36704
|
-
/** (Re)derive the pre/post-market shading bands for this cell's market. The
|
|
36705
|
-
*
|
|
36857
|
+
/** (Re)derive the pre/post-market shading bands for this cell's market. The bands
|
|
36858
|
+
* expand locally from the symbol's session vocabulary, so they paint as soon as
|
|
36859
|
+
* metadata is known and follow any pan depth without provider round trips. */
|
|
36706
36860
|
refreshSessionShading() {
|
|
36707
36861
|
const chart = this.inner;
|
|
36708
36862
|
const symbol = this.state.symbol;
|
|
36709
36863
|
if (!chart || !symbol) return;
|
|
36710
|
-
const
|
|
36711
|
-
this.
|
|
36864
|
+
const now = Date.now();
|
|
36865
|
+
const requestedSpan = Math.max(this.state.bars ?? 1e3, this.rangeBars) * timeframeMs(this.state.timeframe ?? "60");
|
|
36866
|
+
const fallbackSpan = Number.isFinite(requestedSpan) ? Math.max(3 * 864e5, requestedSpan) : 3 * 864e5;
|
|
36867
|
+
const range = chart.getVisibleRange() ?? { from: now - fallbackSpan, to: now };
|
|
36868
|
+
this.sessionShading.track(chart.data, symbol, { session: this.session, range });
|
|
36712
36869
|
}
|
|
36713
36870
|
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36714
36871
|
* live by the dialog swatch) — the cell only proxies them into its settings rows. */
|
|
@@ -36804,10 +36961,10 @@ var ChartCell = class {
|
|
|
36804
36961
|
id: "status-line",
|
|
36805
36962
|
rows: [
|
|
36806
36963
|
{ kind: "heading", label: "Status line", id: "parts" },
|
|
36807
|
-
{ kind: "toggle", label: "Symbol name", id: "name", get: () => sl.partVisible("name"), set: (v) =>
|
|
36808
|
-
{ kind: "toggle", label: "Market status", id: "market", get: () => sl.partVisible("market"), set: (v) =>
|
|
36809
|
-
{ kind: "toggle", label: "OHLC values", id: "ohlc", get: () => sl.partVisible("ohlc"), set: (v) =>
|
|
36810
|
-
{ kind: "toggle", label: "Bar change values", id: "change", get: () => sl.partVisible("change"), set: (v) =>
|
|
36964
|
+
{ kind: "toggle", label: "Symbol name", id: "name", get: () => sl.partVisible("name"), set: (v) => this.setStatuslinePart("name", v) },
|
|
36965
|
+
{ kind: "toggle", label: "Market status", id: "market", get: () => sl.partVisible("market"), set: (v) => this.setStatuslinePart("market", v) },
|
|
36966
|
+
{ kind: "toggle", label: "OHLC values", id: "ohlc", get: () => sl.partVisible("ohlc"), set: (v) => this.setStatuslinePart("ohlc", v) },
|
|
36967
|
+
{ kind: "toggle", label: "Bar change values", id: "change", get: () => sl.partVisible("change"), set: (v) => this.setStatuslinePart("change", v) },
|
|
36811
36968
|
{ kind: "heading", label: "Indicators", id: "indicators" },
|
|
36812
36969
|
{
|
|
36813
36970
|
kind: "toggle",
|
|
@@ -36842,12 +36999,40 @@ var ChartCell = class {
|
|
|
36842
36999
|
this.indicatorTitlesOn = visible;
|
|
36843
37000
|
this.inner?.renderer.set("indicatorTitles", visible);
|
|
36844
37001
|
this.deps.onStateDirty();
|
|
37002
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
36845
37003
|
}
|
|
36846
37004
|
/** Show/hide the plot values beside this cell's legend titles (persisted per cell). */
|
|
36847
37005
|
setIndicatorValuesVisible(visible) {
|
|
36848
37006
|
this.indicatorValuesOn = visible;
|
|
36849
37007
|
this.inner?.renderer.set("indicatorValues", visible);
|
|
36850
37008
|
this.deps.onStateDirty();
|
|
37009
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
37010
|
+
}
|
|
37011
|
+
/** Show/hide one status-line segment (the settings dialog's Status line tab). */
|
|
37012
|
+
setStatuslinePart(part, visible) {
|
|
37013
|
+
this.statusline?.setPartVisible(part, visible);
|
|
37014
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
37015
|
+
}
|
|
37016
|
+
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
37017
|
+
statusPrefs() {
|
|
37018
|
+
const sl = this.statusline;
|
|
37019
|
+
return {
|
|
37020
|
+
parts: sl ? { name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
37021
|
+
indicatorTitles: this.indicatorTitlesOn,
|
|
37022
|
+
indicatorValues: this.indicatorValuesOn
|
|
37023
|
+
};
|
|
37024
|
+
}
|
|
37025
|
+
/** Converge this cell's Status line tab prefs to `prefs` — the follower half of
|
|
37026
|
+
* the workspace's style link. Idempotent: matching values change nothing, so a
|
|
37027
|
+
* propagated echo dies on its own. */
|
|
37028
|
+
applyStatusPrefs(prefs) {
|
|
37029
|
+
if (prefs.parts && this.statusline) {
|
|
37030
|
+
for (const part of Object.keys(prefs.parts)) {
|
|
37031
|
+
if (this.statusline.partVisible(part) !== prefs.parts[part]) this.statusline.setPartVisible(part, prefs.parts[part]);
|
|
37032
|
+
}
|
|
37033
|
+
}
|
|
37034
|
+
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
37035
|
+
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
36851
37036
|
}
|
|
36852
37037
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
36853
37038
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -37254,16 +37439,21 @@ var ChartCell = class {
|
|
|
37254
37439
|
|
|
37255
37440
|
// src/workspace/context.ts
|
|
37256
37441
|
function buildContext(host) {
|
|
37257
|
-
const active = host.active();
|
|
37258
37442
|
return {
|
|
37259
37443
|
get chart() {
|
|
37260
37444
|
const cell = host.active();
|
|
37261
37445
|
if (!cell) throw new Error("VelaWorkspace has no active cell yet");
|
|
37262
37446
|
return cell.chart;
|
|
37263
37447
|
},
|
|
37264
|
-
|
|
37265
|
-
|
|
37266
|
-
|
|
37448
|
+
get symbol() {
|
|
37449
|
+
return host.active()?.symbol ?? "";
|
|
37450
|
+
},
|
|
37451
|
+
get timeframe() {
|
|
37452
|
+
return host.active()?.timeframe ?? "60";
|
|
37453
|
+
},
|
|
37454
|
+
get priceStyle() {
|
|
37455
|
+
return host.active()?.priceStyle ?? "candles";
|
|
37456
|
+
},
|
|
37267
37457
|
setSymbol: (symbol) => host.active()?.setSymbol(symbol),
|
|
37268
37458
|
setTimeframe: (tf) => host.active()?.setTimeframe(tf),
|
|
37269
37459
|
setPriceStyle: (style) => host.active()?.setPriceStyle(style),
|
|
@@ -37274,8 +37464,12 @@ function buildContext(host) {
|
|
|
37274
37464
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
37275
37465
|
addNativeIndicator: (type) => host.active()?.addNative(type),
|
|
37276
37466
|
stateChanged: () => host.stateDirty(),
|
|
37277
|
-
|
|
37278
|
-
|
|
37467
|
+
get cells() {
|
|
37468
|
+
return host.cells().map((c) => ({ id: c.id, chart: c.chart, symbol: c.symbol, timeframe: c.timeframe }));
|
|
37469
|
+
},
|
|
37470
|
+
get activeCellId() {
|
|
37471
|
+
return host.active()?.id ?? "";
|
|
37472
|
+
},
|
|
37279
37473
|
setActiveCell: (id) => host.setActiveCell(id)
|
|
37280
37474
|
};
|
|
37281
37475
|
}
|
|
@@ -37614,6 +37808,10 @@ var VelaWorkspace = class {
|
|
|
37614
37808
|
/** Same guard for the drawings link: the propagated mutations' own `drawing:*`
|
|
37615
37809
|
* events fire synchronously inside the propagation loop and must not fan out again. */
|
|
37616
37810
|
this.drawingSyncBusy = false;
|
|
37811
|
+
/** Same guard for the style link: a follower's `applyConfig` re-fires its
|
|
37812
|
+
* `onConfigChanged` in the same tick, and a state restore applies per-cell
|
|
37813
|
+
* configs that legitimately differ — neither must propagate. */
|
|
37814
|
+
this.styleSyncBusy = false;
|
|
37617
37815
|
/** LINKED drawings (the drawings sync): one map per synced set (cellId → that
|
|
37618
37816
|
* cell's drawing id), reachable from every member under its `cellId\0drawingId`
|
|
37619
37817
|
* key — any member finds its peers to push edits/removals onto. Survives a
|
|
@@ -37674,9 +37872,7 @@ var VelaWorkspace = class {
|
|
|
37674
37872
|
if (boot?.favorites) this.favs = [...boot.favorites];
|
|
37675
37873
|
if (boot?.timeframeFavorites) this.tfFavs = [...boot.timeframeFavorites];
|
|
37676
37874
|
const sync = boot?.sync ?? opts.sync;
|
|
37677
|
-
for (const kind of
|
|
37678
|
-
this.applySyncSetting(kind, sync?.[kind]);
|
|
37679
|
-
}
|
|
37875
|
+
for (const kind of SYNC_KINDS) this.applySyncSetting(kind, sync?.[kind]);
|
|
37680
37876
|
this.monoLayout = opts.layout === false;
|
|
37681
37877
|
const optLayout = opts.layout === false || opts.layout === void 0 ? "4" : opts.layout;
|
|
37682
37878
|
this.def = this.resolveLayout(this.monoLayout ? "1" : boot?.layout && ensureLayout(boot.layout) ? boot.layout : optLayout);
|
|
@@ -37760,7 +37956,8 @@ var VelaWorkspace = class {
|
|
|
37760
37956
|
syncs: () => [
|
|
37761
37957
|
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
37762
37958
|
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
37763
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
37959
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
37960
|
+
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
37764
37961
|
],
|
|
37765
37962
|
onToggleSync: (id) => {
|
|
37766
37963
|
const kind = id;
|
|
@@ -38044,7 +38241,7 @@ var VelaWorkspace = class {
|
|
|
38044
38241
|
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
38045
38242
|
}
|
|
38046
38243
|
this.dock.applyState(st.panels);
|
|
38047
|
-
for (const kind of
|
|
38244
|
+
for (const kind of SYNC_KINDS) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
38048
38245
|
this.trackSizes.clear();
|
|
38049
38246
|
if (st.trackSizes) for (const [id, ts] of Object.entries(st.trackSizes)) this.trackSizes.set(id, ts);
|
|
38050
38247
|
const targetDef = this.monoLayout ? this.def : ensureLayout(st.layout) ?? this.def;
|
|
@@ -38055,9 +38252,14 @@ var VelaWorkspace = class {
|
|
|
38055
38252
|
for (const cell of this.cellsById.values()) cell.chart.drawings.setFavorites(this.favs);
|
|
38056
38253
|
}
|
|
38057
38254
|
this.drawingLinks.clear();
|
|
38058
|
-
|
|
38059
|
-
|
|
38060
|
-
this.
|
|
38255
|
+
this.styleSyncBusy = true;
|
|
38256
|
+
try {
|
|
38257
|
+
for (const [i] of this.def.cells.entries()) {
|
|
38258
|
+
const { id, ...cs } = st.charts[i];
|
|
38259
|
+
this.cellsById.get(id)?.rehydrate(cs);
|
|
38260
|
+
}
|
|
38261
|
+
} finally {
|
|
38262
|
+
this.styleSyncBusy = false;
|
|
38061
38263
|
}
|
|
38062
38264
|
if (st.timezone) this.setTimezone(st.timezone);
|
|
38063
38265
|
this.pool.clear();
|
|
@@ -38157,6 +38359,7 @@ var VelaWorkspace = class {
|
|
|
38157
38359
|
const rebuildAll = nextBackend !== this.cellBackend;
|
|
38158
38360
|
this.order = orderAfterLayout(this.order, next.cells.length, this.activeId);
|
|
38159
38361
|
const keep = new Set(this.order.slice(0, next.cells.length));
|
|
38362
|
+
const preexisting = new Set(this.cellsById.keys());
|
|
38160
38363
|
for (const [id, cell] of [...this.cellsById]) {
|
|
38161
38364
|
if (!keep.has(id) || rebuildAll) {
|
|
38162
38365
|
this.poolSet(id, cell.dehydrate());
|
|
@@ -38169,6 +38372,7 @@ var VelaWorkspace = class {
|
|
|
38169
38372
|
this.cellBackend = nextBackend;
|
|
38170
38373
|
this.applyGrid();
|
|
38171
38374
|
this.buildCells();
|
|
38375
|
+
this.alignNewCellStyles(preexisting);
|
|
38172
38376
|
this.syncCellPresentation();
|
|
38173
38377
|
this.topbar.setLayout(next.id);
|
|
38174
38378
|
const nextActive = activeAfterLayout(this.activeId, this.order.slice(0, next.cells.length));
|
|
@@ -38372,6 +38576,7 @@ var VelaWorkspace = class {
|
|
|
38372
38576
|
onMarketChanged: (id2) => this.onCellMarketChanged(id2),
|
|
38373
38577
|
onPriceStyleChanged: (id2) => this.onCellPriceStyleChanged(id2),
|
|
38374
38578
|
onIndicatorsChanged: (id2) => this.onCellIndicatorsChanged(id2),
|
|
38579
|
+
onStatusPrefsChanged: (id2) => this.propagateStylePrefs(id2),
|
|
38375
38580
|
onStateDirty: () => this.markStateDirty(),
|
|
38376
38581
|
manifestSettled: () => this.manifestSettled,
|
|
38377
38582
|
toast: (message, kind, durationMs) => this.toastHost.show(message, kind, durationMs)
|
|
@@ -38447,6 +38652,7 @@ var VelaWorkspace = class {
|
|
|
38447
38652
|
this.drawToolbar?.setEraserActive(mode === "eraser");
|
|
38448
38653
|
});
|
|
38449
38654
|
chart.on("viewport:changed", (range) => this.propagateViewport(cell.id, range));
|
|
38655
|
+
chart.renderer.onConfigChanged(() => this.propagateStylePrefs(cell.id));
|
|
38450
38656
|
chart.on("theme:changed", (t) => this.setTheme(t));
|
|
38451
38657
|
chart.renderer.onAxisLongPress((e) => {
|
|
38452
38658
|
if (this.layoutCtl.current !== "mobile") return;
|
|
@@ -38479,11 +38685,67 @@ var VelaWorkspace = class {
|
|
|
38479
38685
|
if (kind === "viewport") {
|
|
38480
38686
|
const range = this.cellsById.get(this.activeId)?.chart.getVisibleRange();
|
|
38481
38687
|
if (range) this.propagateViewport(this.activeId, range);
|
|
38688
|
+
} else if (kind === "style") {
|
|
38689
|
+
this.propagateStylePrefs(this.activeId);
|
|
38482
38690
|
} else {
|
|
38483
38691
|
this.propagateMarket(this.activeId);
|
|
38484
38692
|
}
|
|
38485
38693
|
}
|
|
38486
38694
|
}
|
|
38695
|
+
/**
|
|
38696
|
+
* Align cells minted by a layout change to their style group: with the link on, a
|
|
38697
|
+
* NEW cell (fresh slot or one returning from the pool, which missed edits while
|
|
38698
|
+
* dormant) inherits the presentation of a pre-existing group peer — the active
|
|
38699
|
+
* cell when it is one — instead of sitting on its own state beside a styled
|
|
38700
|
+
* group. Propagation runs FROM the peer, so a newborn's defaults never overwrite
|
|
38701
|
+
* the group, and the equality short-circuits keep converged peers untouched.
|
|
38702
|
+
*/
|
|
38703
|
+
alignNewCellStyles(preexisting) {
|
|
38704
|
+
const setting = this.syncOpts.style;
|
|
38705
|
+
if (!setting) return;
|
|
38706
|
+
const ids = [...this.cellsById.keys()];
|
|
38707
|
+
const propagated = /* @__PURE__ */ new Set();
|
|
38708
|
+
for (const id of ids) {
|
|
38709
|
+
if (preexisting.has(id)) continue;
|
|
38710
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
38711
|
+
if (peers.length === 0) continue;
|
|
38712
|
+
const source = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
38713
|
+
if (propagated.has(source)) continue;
|
|
38714
|
+
propagated.add(source);
|
|
38715
|
+
this.propagateStylePrefs(source);
|
|
38716
|
+
}
|
|
38717
|
+
}
|
|
38718
|
+
/**
|
|
38719
|
+
* Mirror an origin cell's presentation — the Canvas + Scales-and-lines slice of
|
|
38720
|
+
* its renderer config plus its Status line tab prefs — onto its same-group
|
|
38721
|
+
* followers (the style link). Loop-safe two ways: the busy guard eats the
|
|
38722
|
+
* followers' SYNCHRONOUS echoes (their `applyConfig` re-fires `onConfigChanged`
|
|
38723
|
+
* in the same tick), and the equality short-circuits leave already-converged
|
|
38724
|
+
* followers untouched, so nothing re-emits once the group agrees.
|
|
38725
|
+
*/
|
|
38726
|
+
propagateStylePrefs(originId) {
|
|
38727
|
+
if (this.styleSyncBusy || this.destroyed) return;
|
|
38728
|
+
const targets = syncTargets(originId, this.syncOpts.style, [...this.cellsById.keys()]);
|
|
38729
|
+
if (targets.length === 0) return;
|
|
38730
|
+
const origin = this.cellsById.get(originId);
|
|
38731
|
+
if (!origin) return;
|
|
38732
|
+
const slice = styleConfigSlice(origin.chart.renderer.getConfig());
|
|
38733
|
+
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
38734
|
+
const prefs = origin.statusPrefs();
|
|
38735
|
+
this.styleSyncBusy = true;
|
|
38736
|
+
try {
|
|
38737
|
+
for (const id of targets) {
|
|
38738
|
+
const cell = this.cellsById.get(id);
|
|
38739
|
+
if (!cell) continue;
|
|
38740
|
+
if (slice && sliceJson !== JSON.stringify(styleConfigSlice(cell.chart.renderer.getConfig()))) {
|
|
38741
|
+
cell.chart.renderer.applyConfig(slice);
|
|
38742
|
+
}
|
|
38743
|
+
cell.applyStatusPrefs(prefs);
|
|
38744
|
+
}
|
|
38745
|
+
} finally {
|
|
38746
|
+
this.styleSyncBusy = false;
|
|
38747
|
+
}
|
|
38748
|
+
}
|
|
38487
38749
|
/**
|
|
38488
38750
|
* Mirror an origin cell's pointer time onto its same-group followers as GHOST
|
|
38489
38751
|
* crosshairs (`renderer.setExternalCrosshair`). The horizontal price level rides
|
|
@@ -38791,7 +39053,8 @@ var VelaWorkspace = class {
|
|
|
38791
39053
|
syncs: () => [
|
|
38792
39054
|
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
38793
39055
|
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
38794
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
39056
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
39057
|
+
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
38795
39058
|
],
|
|
38796
39059
|
onToggleSync: (id) => {
|
|
38797
39060
|
const kind = id;
|