@luxalgo/vela 0.7.0 → 0.7.1
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-Bu9E5Zh5.d.ts → DataProvider-BALR0qPq.d.ts} +1 -1
- package/dist/{DataProvider-Ck2qyS_9.d.cts → DataProvider-D5uMiJYz.d.cts} +1 -1
- package/dist/{chunk-A4G64KVF.js → chunk-5RQGCZTX.js} +299 -110
- package/dist/{chunk-IFJJPXSV.js → chunk-BFA32GOU.js} +44 -16
- package/dist/{chunk-CZRNTHD6.js → chunk-BKHSQ4YM.js} +129 -125
- package/dist/{chunk-A347YL2P.js → chunk-QGVSNAX7.js} +67 -9
- package/dist/{chunk-UHXG5J7C.js → chunk-ZADTVRUO.js} +1 -1
- package/dist/{contributions-BjQ7hyIx.d.ts → contributions-BbhbZd3c.d.ts} +31 -9
- package/dist/{contributions-Cv2Mutvn.d.cts → contributions-CcBFe-91.d.cts} +31 -9
- package/dist/index.cjs +379 -156
- package/dist/index.d.cts +44 -17
- package/dist/index.d.ts +44 -17
- package/dist/index.js +4 -4
- package/dist/{options-ex2_gtKp.d.ts → options-C7b_Qssu.d.cts} +42 -1
- package/dist/{options-ex2_gtKp.d.cts → options-C7b_Qssu.d.ts} +42 -1
- package/dist/{plugin-CiyhU7pi.d.ts → plugin-Cnix5ZBh.d.ts} +6 -4
- package/dist/{plugin-absNH7Yn.d.cts → plugin-D6MlX4--.d.cts} +6 -4
- package/dist/plugin.cjs +39 -15
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- 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-BXoU4Kua.d.ts → statusline-model-D8NL7o4L.d.ts} +3 -3
- package/dist/{statusline-model-cRhPBLPO.d.cts → statusline-model-DUKt7_TQ.d.cts} +3 -3
- package/dist/ui.cjs +8 -4
- package/dist/ui.d.cts +10 -7
- package/dist/ui.d.ts +10 -7
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +379 -156
- package/dist/vela.global.min.js +70 -70
- package/dist/widget.cjs +406 -127
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +6 -6
- package/dist/workspace.cjs +406 -127
- package/dist/workspace.d.cts +39 -6
- package/dist/workspace.d.ts +39 -6
- package/dist/workspace.js +5 -5
- package/package.json +1 -1
- /package/dist/{chunk-FE6V2YAQ.js → chunk-JU4CWEBF.js} +0 -0
package/dist/widget.cjs
CHANGED
|
@@ -1105,6 +1105,9 @@ function applyPlotOverlayTokens(host, base, config) {
|
|
|
1105
1105
|
const t = layout?.background && layout.textColor ? { ...base, background: layout.background, textColor: layout.textColor } : base;
|
|
1106
1106
|
applyThemeTokens(host, t);
|
|
1107
1107
|
}
|
|
1108
|
+
function floatingLayerHost(from, fallback) {
|
|
1109
|
+
return from.closest(".vela-ui") ?? fallback ?? from;
|
|
1110
|
+
}
|
|
1108
1111
|
function ensureUIHost(el, theme) {
|
|
1109
1112
|
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
1110
1113
|
el.classList.add("vela-ui");
|
|
@@ -1549,7 +1552,7 @@ var MENU_CSS = `
|
|
|
1549
1552
|
/* The list is a <ul>: without this, block rows (the separator) paint a ::marker dot. */
|
|
1550
1553
|
list-style: none;
|
|
1551
1554
|
margin: 0;
|
|
1552
|
-
background: var(--vela-surface
|
|
1555
|
+
background: var(--vela-surface);
|
|
1553
1556
|
color: var(--vela-fg);
|
|
1554
1557
|
border: 1px solid var(--vela-border-strong);
|
|
1555
1558
|
border-radius: 6px;
|
|
@@ -1840,7 +1843,7 @@ var Menu = class {
|
|
|
1840
1843
|
const anchor = opts.trigger ?? opts.host ?? document.body;
|
|
1841
1844
|
const doc = anchor.ownerDocument;
|
|
1842
1845
|
injectStyles(MENU_STYLE_ID, MENU_CSS, doc);
|
|
1843
|
-
const host = opts.host ?? anchor
|
|
1846
|
+
const host = floatingLayerHost(opts.host ?? anchor, doc.body);
|
|
1844
1847
|
this.root = new Surface(doc, {
|
|
1845
1848
|
host,
|
|
1846
1849
|
placement: opts.placement,
|
|
@@ -1985,7 +1988,8 @@ var Popover = class {
|
|
|
1985
1988
|
const doc = opts.trigger.ownerDocument;
|
|
1986
1989
|
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
1987
1990
|
this.trigger = opts.trigger;
|
|
1988
|
-
this.host = opts.host ?? doc.body;
|
|
1991
|
+
this.host = opts.host ?? floatingLayerHost(opts.trigger, doc.body);
|
|
1992
|
+
this.inheritsTokens = !opts.host && this.host !== doc.body;
|
|
1989
1993
|
this.ctrl = popoverController(opts);
|
|
1990
1994
|
this.boundary = opts.boundary ?? "viewport";
|
|
1991
1995
|
this.theme = opts.theme;
|
|
@@ -2017,7 +2021,7 @@ var Popover = class {
|
|
|
2017
2021
|
clearTimeout(this.leaveTimer);
|
|
2018
2022
|
this.leaveTimer = null;
|
|
2019
2023
|
}
|
|
2020
|
-
ensureUIHost(this.el, this.theme);
|
|
2024
|
+
ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
|
|
2021
2025
|
if (this.fadeMs > 0) {
|
|
2022
2026
|
this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
|
|
2023
2027
|
this.el.style.opacity = "0";
|
|
@@ -8208,16 +8212,18 @@ var Pitchfork = class extends SegmentDrawing {
|
|
|
8208
8212
|
geometry(proj) {
|
|
8209
8213
|
const a = this.anchors[0];
|
|
8210
8214
|
const b = this.anchors[1];
|
|
8211
|
-
|
|
8212
|
-
if (!a || !b || !c) return null;
|
|
8215
|
+
if (!a || !b) return null;
|
|
8213
8216
|
const px = (p) => {
|
|
8214
8217
|
const y = proj.yOf(p.price, this.paneId);
|
|
8215
8218
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8216
8219
|
};
|
|
8217
8220
|
const P0 = px(a);
|
|
8218
8221
|
const P1 = px(b);
|
|
8222
|
+
if (!P0 || !P1) return null;
|
|
8223
|
+
const c = this.anchors[2];
|
|
8224
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
8219
8225
|
const P2 = px(c);
|
|
8220
|
-
if (!
|
|
8226
|
+
if (!P2) return null;
|
|
8221
8227
|
const mx = (P1[0] + P2[0]) / 2;
|
|
8222
8228
|
const my = (P1[1] + P2[1]) / 2;
|
|
8223
8229
|
const dx = mx - P0[0];
|
|
@@ -8264,16 +8270,19 @@ var PitchforkVariant = class extends SegmentDrawing {
|
|
|
8264
8270
|
geometry(proj) {
|
|
8265
8271
|
const a = this.anchors[0];
|
|
8266
8272
|
const b = this.anchors[1];
|
|
8267
|
-
|
|
8268
|
-
if (!a || !b || !c) return null;
|
|
8273
|
+
if (!a || !b) return null;
|
|
8269
8274
|
const px = (p) => {
|
|
8270
8275
|
const y = proj.yOf(p.price, this.paneId);
|
|
8271
8276
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8272
8277
|
};
|
|
8273
|
-
const
|
|
8278
|
+
const P0 = px(a);
|
|
8274
8279
|
const P1 = px(b);
|
|
8280
|
+
if (!P0 || !P1) return null;
|
|
8281
|
+
const c = this.anchors[2];
|
|
8282
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
8283
|
+
const S2 = px(this.medianStart(a, b, c));
|
|
8275
8284
|
const P2 = px(c);
|
|
8276
|
-
if (!S2 || !
|
|
8285
|
+
if (!S2 || !P2) return null;
|
|
8277
8286
|
const mx = (P1[0] + P2[0]) / 2;
|
|
8278
8287
|
const my = (P1[1] + P2[1]) / 2;
|
|
8279
8288
|
const dx = mx - S2[0];
|
|
@@ -8288,8 +8297,12 @@ var PitchforkVariant = class extends SegmentDrawing {
|
|
|
8288
8297
|
// upper tine
|
|
8289
8298
|
extendRay(P2[0], P2[1], P2[0] + dx, P2[1] + dy, "right", w, h),
|
|
8290
8299
|
// lower tine
|
|
8291
|
-
[P1[0], P1[1], P2[0], P2[1]]
|
|
8300
|
+
[P1[0], P1[1], P2[0], P2[1]],
|
|
8292
8301
|
// base line
|
|
8302
|
+
// The median leaves from the shifted origin, not the pivot — without this construction
|
|
8303
|
+
// line the pivot handle would float unattached to the shape it defines.
|
|
8304
|
+
[P0[0], P0[1], P1[0], P1[1]]
|
|
8305
|
+
// pivot → first tine anchor
|
|
8293
8306
|
],
|
|
8294
8307
|
fill: null
|
|
8295
8308
|
};
|
|
@@ -8346,17 +8359,19 @@ var InsidePitchfork = class extends SegmentDrawing {
|
|
|
8346
8359
|
geometry(proj) {
|
|
8347
8360
|
const a = this.anchors[0];
|
|
8348
8361
|
const b = this.anchors[1];
|
|
8349
|
-
|
|
8350
|
-
if (!a || !b || !c) return null;
|
|
8362
|
+
if (!a || !b) return null;
|
|
8351
8363
|
const px = (p) => {
|
|
8352
8364
|
const y = proj.yOf(p.price, this.paneId);
|
|
8353
8365
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8354
8366
|
};
|
|
8355
|
-
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
8356
8367
|
const P0 = px(a);
|
|
8357
8368
|
const P1 = px(b);
|
|
8369
|
+
if (!P0 || !P1) return null;
|
|
8370
|
+
const c = this.anchors[2];
|
|
8371
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
8372
|
+
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
8358
8373
|
const P2 = px(c);
|
|
8359
|
-
if (!B || !
|
|
8374
|
+
if (!B || !P2) return null;
|
|
8360
8375
|
const mx = (P1[0] + P2[0]) / 2;
|
|
8361
8376
|
const my = (P1[1] + P2[1]) / 2;
|
|
8362
8377
|
const dx = P2[0] - B[0];
|
|
@@ -8527,16 +8542,18 @@ var Triangle = class extends SegmentDrawing {
|
|
|
8527
8542
|
geometry(proj) {
|
|
8528
8543
|
const a = this.anchors[0];
|
|
8529
8544
|
const b = this.anchors[1];
|
|
8530
|
-
|
|
8531
|
-
if (!a || !b || !c) return null;
|
|
8545
|
+
if (!a || !b) return null;
|
|
8532
8546
|
const px = (p) => {
|
|
8533
8547
|
const y = proj.yOf(p.price, this.paneId);
|
|
8534
8548
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8535
8549
|
};
|
|
8536
8550
|
const A = px(a);
|
|
8537
8551
|
const B = px(b);
|
|
8552
|
+
if (!A || !B) return null;
|
|
8553
|
+
const c = this.anchors[2];
|
|
8554
|
+
if (!c) return { segments: [[A[0], A[1], B[0], B[1]]], fill: null };
|
|
8538
8555
|
const C = px(c);
|
|
8539
|
-
if (!
|
|
8556
|
+
if (!C) return null;
|
|
8540
8557
|
return {
|
|
8541
8558
|
segments: [
|
|
8542
8559
|
[A[0], A[1], B[0], B[1]],
|
|
@@ -18412,6 +18429,13 @@ function resolveAnimations(animations) {
|
|
|
18412
18429
|
function isLineLikeSeries(spec) {
|
|
18413
18430
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
18414
18431
|
}
|
|
18432
|
+
function seriesShownOn(spec, surface) {
|
|
18433
|
+
if (spec.display) return spec.display[surface] !== false;
|
|
18434
|
+
return spec.visible !== false;
|
|
18435
|
+
}
|
|
18436
|
+
function seriesInScale(spec) {
|
|
18437
|
+
return seriesShownOn(spec, "pane") || seriesShownOn(spec, "priceScale");
|
|
18438
|
+
}
|
|
18415
18439
|
|
|
18416
18440
|
// src/core/engine/IndicatorRegistry.ts
|
|
18417
18441
|
var IndicatorRegistry = class {
|
|
@@ -18441,6 +18465,9 @@ var IndicatorRegistry = class {
|
|
|
18441
18465
|
};
|
|
18442
18466
|
|
|
18443
18467
|
// src/core/native-indicators/NativeIndicator.ts
|
|
18468
|
+
function nativeInstanceChannel(type, instanceId) {
|
|
18469
|
+
return `${type}#${instanceId}`;
|
|
18470
|
+
}
|
|
18444
18471
|
var REGISTRY2 = /* @__PURE__ */ new Map();
|
|
18445
18472
|
function registerNativeIndicator(descriptor) {
|
|
18446
18473
|
REGISTRY2.set(descriptor.type, descriptor);
|
|
@@ -18510,6 +18537,9 @@ var IndicatorHandleImpl = class {
|
|
|
18510
18537
|
setProps(values) {
|
|
18511
18538
|
this.controller.applyProps(this.id, values);
|
|
18512
18539
|
}
|
|
18540
|
+
updateCode(source) {
|
|
18541
|
+
this.controller.updateCode(this.id, source);
|
|
18542
|
+
}
|
|
18513
18543
|
setVisible(visible) {
|
|
18514
18544
|
this.controller.setVisible(this.id, visible);
|
|
18515
18545
|
}
|
|
@@ -18532,6 +18562,10 @@ var IndicatorHandleImpl = class {
|
|
|
18532
18562
|
setPropsSchema(schema) {
|
|
18533
18563
|
this.propsSchema = schema;
|
|
18534
18564
|
}
|
|
18565
|
+
/** Sync the public `source` once a code update has been prepared and taken over. */
|
|
18566
|
+
setSource(source) {
|
|
18567
|
+
this.source = source;
|
|
18568
|
+
}
|
|
18535
18569
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
18536
18570
|
setVisibleState(visible) {
|
|
18537
18571
|
this.visibleState = visible;
|
|
@@ -19352,6 +19386,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19352
19386
|
if (identityChanged) {
|
|
19353
19387
|
this.beginLoad(false);
|
|
19354
19388
|
this.setBarSeries([], { clearing: true });
|
|
19389
|
+
this.blankIndicatorVisuals();
|
|
19355
19390
|
if (this.activeEngineStyle) {
|
|
19356
19391
|
this.typeEngines.get(this.activeEngineStyle)?.suspend();
|
|
19357
19392
|
this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
|
|
@@ -19394,7 +19429,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19394
19429
|
restartNativeIndicators() {
|
|
19395
19430
|
for (const record of this.registry.all()) {
|
|
19396
19431
|
if (!record.native) continue;
|
|
19397
|
-
record.native.instance.stop();
|
|
19432
|
+
if (record.native.started) record.native.instance.stop();
|
|
19398
19433
|
record.native.instance = record.native.descriptor.create();
|
|
19399
19434
|
record.native.started = false;
|
|
19400
19435
|
record.pendingStructural = true;
|
|
@@ -19602,6 +19637,23 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19602
19637
|
this.renderer.setBars(this.bars, { preserveView: true });
|
|
19603
19638
|
this.reexecuteIndicators();
|
|
19604
19639
|
}
|
|
19640
|
+
/**
|
|
19641
|
+
* Blank every mounted indicator's painted output — series data, fills, backgrounds,
|
|
19642
|
+
* price lines, drawings, bar colors, trades — while keeping the mount (legend row,
|
|
19643
|
+
* pane, settings) intact, via the same idempotent-by-id remount an input edit uses.
|
|
19644
|
+
* Called on a market identity switch: the fresh models arrive only after the new
|
|
19645
|
+
* bars load, so nothing blanked here is ever restored — each consumer's re-run
|
|
19646
|
+
* remounts a full model over it.
|
|
19647
|
+
*/
|
|
19648
|
+
blankIndicatorVisuals() {
|
|
19649
|
+
for (const record of this.registry.all()) {
|
|
19650
|
+
if (record.hidden || !record.renderHandle || !record.model) continue;
|
|
19651
|
+
record.model = blankedModel(record.model);
|
|
19652
|
+
record.renderHandle = this.renderer.mountIndicator(record.model);
|
|
19653
|
+
record.pendingStructural = true;
|
|
19654
|
+
this.setLoading(record, true);
|
|
19655
|
+
}
|
|
19656
|
+
}
|
|
19605
19657
|
/** Stop + re-run every visible Pine indicator (its input bars changed wholesale). */
|
|
19606
19658
|
reexecuteIndicators() {
|
|
19607
19659
|
for (const record of this.registry.all()) {
|
|
@@ -19846,6 +19898,61 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19846
19898
|
record.session.update(record.inputValues, record.propValues);
|
|
19847
19899
|
this.events.emit("indicator:inputs", { id });
|
|
19848
19900
|
}
|
|
19901
|
+
/**
|
|
19902
|
+
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
19903
|
+
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
19904
|
+
* computing until the new source has compiled, so a broken edit costs the user
|
|
19905
|
+
* nothing but an `error` event. Natives have no script — warn and leave them alone.
|
|
19906
|
+
*/
|
|
19907
|
+
updateCode(id, source) {
|
|
19908
|
+
const record = this.registry.get(id);
|
|
19909
|
+
const handle = this.handles.get(id);
|
|
19910
|
+
if (!record || !handle) return;
|
|
19911
|
+
if (record.native) {
|
|
19912
|
+
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
19913
|
+
return;
|
|
19914
|
+
}
|
|
19915
|
+
if (source === record.source && record.pendingSource === void 0) return;
|
|
19916
|
+
record.pendingSource = source;
|
|
19917
|
+
void this.swapSource(id, source, handle);
|
|
19918
|
+
}
|
|
19919
|
+
async swapSource(id, source, handle) {
|
|
19920
|
+
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
19921
|
+
let prepared;
|
|
19922
|
+
try {
|
|
19923
|
+
prepared = await engine.prepare(source, id);
|
|
19924
|
+
} catch (err) {
|
|
19925
|
+
const record2 = this.registry.get(id);
|
|
19926
|
+
if (record2?.pendingSource === source) {
|
|
19927
|
+
record2.pendingSource = void 0;
|
|
19928
|
+
this.fail(id, handle, err);
|
|
19929
|
+
}
|
|
19930
|
+
return;
|
|
19931
|
+
}
|
|
19932
|
+
const record = this.registry.get(id);
|
|
19933
|
+
if (!record || record.pendingSource !== source) return;
|
|
19934
|
+
record.pendingSource = void 0;
|
|
19935
|
+
record.source = source;
|
|
19936
|
+
record.engine = engine;
|
|
19937
|
+
record.prepared = prepared;
|
|
19938
|
+
handle.setSource(source);
|
|
19939
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
19940
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
19941
|
+
handle.setSchema(prepared.inputs);
|
|
19942
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
19943
|
+
record.session?.stop();
|
|
19944
|
+
record.session = void 0;
|
|
19945
|
+
record.pendingStructural = true;
|
|
19946
|
+
record.pendingCause = "code";
|
|
19947
|
+
if (record.hidden) return;
|
|
19948
|
+
if (record.renderHandle) {
|
|
19949
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
19950
|
+
this.setLoading(record, true);
|
|
19951
|
+
} else {
|
|
19952
|
+
this.mountLoadingPlaceholder(id, record);
|
|
19953
|
+
}
|
|
19954
|
+
this.executeIndicator(id, handle);
|
|
19955
|
+
}
|
|
19849
19956
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
19850
19957
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
19851
19958
|
listIndicators() {
|
|
@@ -19860,7 +19967,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19860
19967
|
const record = this.registry.remove(id);
|
|
19861
19968
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
19862
19969
|
record?.session?.stop();
|
|
19863
|
-
record?.native?.instance.stop();
|
|
19970
|
+
if (record?.native?.started) record.native.instance.stop();
|
|
19864
19971
|
this.handles.delete(id);
|
|
19865
19972
|
if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
|
|
19866
19973
|
const paneId = record?.model?.paneId;
|
|
@@ -19886,7 +19993,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19886
19993
|
if (!visible) {
|
|
19887
19994
|
record.session?.stop();
|
|
19888
19995
|
record.session = void 0;
|
|
19889
|
-
record.native?.instance.suspend();
|
|
19996
|
+
if (record.native?.started) record.native.instance.suspend();
|
|
19890
19997
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
19891
19998
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
19892
19999
|
} else {
|
|
@@ -19967,7 +20074,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19967
20074
|
this.paneActionUnsub?.();
|
|
19968
20075
|
for (const record of this.registry.all()) {
|
|
19969
20076
|
record.session?.stop();
|
|
19970
|
-
record.native?.instance.stop();
|
|
20077
|
+
if (record.native?.started) record.native.instance.stop();
|
|
19971
20078
|
}
|
|
19972
20079
|
for (const engine of this.typeEngines.values()) engine.stop();
|
|
19973
20080
|
this.typeEngines.clear();
|
|
@@ -19991,6 +20098,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19991
20098
|
const engine = this.engineFor(options.language);
|
|
19992
20099
|
record.engine = engine;
|
|
19993
20100
|
const prepared = await engine.prepare(source, id);
|
|
20101
|
+
if (record.source !== source) return;
|
|
19994
20102
|
record.prepared = prepared;
|
|
19995
20103
|
const defaults2 = {};
|
|
19996
20104
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
@@ -20061,7 +20169,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20061
20169
|
await this.readyPromise;
|
|
20062
20170
|
const record = this.registry.get(id);
|
|
20063
20171
|
if (!record?.native || record.hidden) return;
|
|
20172
|
+
const channel = this.nativeChannel(record);
|
|
20064
20173
|
const ctx = {
|
|
20174
|
+
id,
|
|
20065
20175
|
symbol: this.config.market.symbol ?? "TEST",
|
|
20066
20176
|
timeframe: this.config.market.timeframe ?? "60",
|
|
20067
20177
|
live: this.config.live,
|
|
@@ -20069,7 +20179,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20069
20179
|
bars: () => this.bars,
|
|
20070
20180
|
data: this.dataControl,
|
|
20071
20181
|
emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
|
|
20072
|
-
pushData: (data) => this.renderer.setNativeData?.(
|
|
20182
|
+
pushData: (data) => this.renderer.setNativeData?.(channel, data),
|
|
20073
20183
|
setStatus: (status) => {
|
|
20074
20184
|
if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
|
|
20075
20185
|
}
|
|
@@ -20080,16 +20190,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20080
20190
|
this.fail(id, handle, err);
|
|
20081
20191
|
}
|
|
20082
20192
|
}
|
|
20193
|
+
/** The renderer-layer channel a native instance's `pushData` lands on: the type itself
|
|
20194
|
+
* for a single-instance type (the layer id doubles as the channel), a per-instance
|
|
20195
|
+
* channel for a `multiInstance` type — otherwise every instance would overwrite the
|
|
20196
|
+
* one layer. Stamped on the model so the renderer mounts a dedicated layer for it. */
|
|
20197
|
+
nativeChannel(record) {
|
|
20198
|
+
const { type, descriptor } = record.native;
|
|
20199
|
+
return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
|
|
20200
|
+
}
|
|
20083
20201
|
/** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
|
|
20084
20202
|
buildNativeModel(record, out) {
|
|
20085
20203
|
const d = record.native.descriptor;
|
|
20204
|
+
const type = record.native.type;
|
|
20205
|
+
const channel = this.nativeChannel(record);
|
|
20086
20206
|
return {
|
|
20087
20207
|
id: record.id,
|
|
20088
20208
|
title: record.title,
|
|
20089
20209
|
...d.shortTitle ? { shorttitle: d.shortTitle } : {},
|
|
20090
20210
|
overlay: d.overlay,
|
|
20091
20211
|
paneHint: d.paneHint,
|
|
20092
|
-
native: { type
|
|
20212
|
+
native: { type, ...channel !== type ? { channel } : {} },
|
|
20093
20213
|
...d.legend === false ? { legend: false } : {},
|
|
20094
20214
|
...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
|
|
20095
20215
|
series: out.series ?? [],
|
|
@@ -20379,6 +20499,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20379
20499
|
applyModel(id, model) {
|
|
20380
20500
|
const record = this.registry.get(id);
|
|
20381
20501
|
if (!record || record.hidden) return true;
|
|
20502
|
+
if (this.switchingMarket) return false;
|
|
20382
20503
|
if (record.loading && this.bars.length === 0 && !_EngineOrchestrator.modelHasOutput(model)) return false;
|
|
20383
20504
|
const handle = this.handles.get(id);
|
|
20384
20505
|
if (!record.renderHandle) {
|
|
@@ -20396,7 +20517,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20396
20517
|
if (record.loading && !record.paneLocked) {
|
|
20397
20518
|
const routed = this.routePane(id, model, record.options ?? {});
|
|
20398
20519
|
if (routed !== paneId) {
|
|
20399
|
-
if (paneId !== "price") {
|
|
20520
|
+
if (paneId !== "price" && !this.registry.all().some((r) => r.id !== id && r.model?.paneId === paneId)) {
|
|
20400
20521
|
this.renderer.removePane(paneId);
|
|
20401
20522
|
this.forgetPane(paneId);
|
|
20402
20523
|
}
|
|
@@ -20525,6 +20646,34 @@ function yieldToPaint() {
|
|
|
20525
20646
|
}
|
|
20526
20647
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
20527
20648
|
}
|
|
20649
|
+
function valuesOnSchema(schema, previous) {
|
|
20650
|
+
const out = {};
|
|
20651
|
+
const declared = /* @__PURE__ */ new Set();
|
|
20652
|
+
for (const s of schema) {
|
|
20653
|
+
out[s.key] = s.defval;
|
|
20654
|
+
declared.add(s.key);
|
|
20655
|
+
declared.add(s.title);
|
|
20656
|
+
}
|
|
20657
|
+
for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
|
|
20658
|
+
return out;
|
|
20659
|
+
}
|
|
20660
|
+
function blankedModel(model) {
|
|
20661
|
+
return {
|
|
20662
|
+
...model,
|
|
20663
|
+
series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
|
|
20664
|
+
fills: [],
|
|
20665
|
+
backgrounds: [],
|
|
20666
|
+
priceLines: [],
|
|
20667
|
+
lines: [],
|
|
20668
|
+
boxes: [],
|
|
20669
|
+
labels: [],
|
|
20670
|
+
polylines: [],
|
|
20671
|
+
linefills: [],
|
|
20672
|
+
tables: [],
|
|
20673
|
+
barColors: [],
|
|
20674
|
+
trades: []
|
|
20675
|
+
};
|
|
20676
|
+
}
|
|
20528
20677
|
function modelToValuePatch(model) {
|
|
20529
20678
|
const series = [];
|
|
20530
20679
|
let from = Number.POSITIVE_INFINITY;
|
|
@@ -23284,33 +23433,32 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
23284
23433
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
23285
23434
|
};
|
|
23286
23435
|
}
|
|
23287
|
-
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
23436
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf, colorOf2) {
|
|
23288
23437
|
const out = [];
|
|
23289
23438
|
let col = NaN;
|
|
23290
|
-
let
|
|
23439
|
+
let runs = [];
|
|
23291
23440
|
const flush = () => {
|
|
23292
|
-
if (
|
|
23293
|
-
|
|
23294
|
-
|
|
23295
|
-
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
prev2.headTime = next.headTime;
|
|
23302
|
-
prev2.open = next.open;
|
|
23303
|
-
}
|
|
23304
|
-
if (next.lastIdx > prev2.lastIdx) {
|
|
23305
|
-
prev2.lastIdx = next.lastIdx;
|
|
23306
|
-
prev2.close = next.close;
|
|
23441
|
+
if (runs.length === 0) return;
|
|
23442
|
+
runs.sort((a, b) => a.lo - b.lo);
|
|
23443
|
+
const merged = [];
|
|
23444
|
+
for (const next of runs) {
|
|
23445
|
+
let prev2;
|
|
23446
|
+
for (let k = merged.length - 1; k >= 0; k -= 1) {
|
|
23447
|
+
if (merged[k].color === next.color) {
|
|
23448
|
+
prev2 = merged[k];
|
|
23449
|
+
break;
|
|
23307
23450
|
}
|
|
23451
|
+
}
|
|
23452
|
+
if (prev2 && Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
23453
|
+
prev2.hi = next.hi;
|
|
23454
|
+
if (next.lastIdx > prev2.lastIdx) prev2.lastIdx = next.lastIdx;
|
|
23308
23455
|
} else {
|
|
23309
23456
|
merged.push(next);
|
|
23310
23457
|
}
|
|
23311
23458
|
}
|
|
23312
|
-
|
|
23313
|
-
|
|
23459
|
+
merged.sort((a, b) => a.lastIdx - b.lastIdx);
|
|
23460
|
+
for (const r of merged) out.push({ x: col, hi: r.hi, lo: r.lo, color: r.color });
|
|
23461
|
+
runs = [];
|
|
23314
23462
|
};
|
|
23315
23463
|
for (let i = i0; i <= i1; i += 1) {
|
|
23316
23464
|
const b = bars[i];
|
|
@@ -23320,26 +23468,17 @@ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
|
23320
23468
|
flush();
|
|
23321
23469
|
col = x;
|
|
23322
23470
|
}
|
|
23471
|
+
const color = colorOf2(b);
|
|
23323
23472
|
let lo = b.low;
|
|
23324
23473
|
let hi = b.high;
|
|
23325
|
-
let
|
|
23326
|
-
|
|
23327
|
-
|
|
23328
|
-
|
|
23329
|
-
|
|
23330
|
-
|
|
23331
|
-
|
|
23332
|
-
|
|
23333
|
-
if (iv.headIdx < headIdx) {
|
|
23334
|
-
headIdx = iv.headIdx;
|
|
23335
|
-
headTime = iv.headTime;
|
|
23336
|
-
open2 = iv.open;
|
|
23337
|
-
}
|
|
23338
|
-
intervals.splice(k, 1);
|
|
23339
|
-
}
|
|
23340
|
-
let insertAt = 0;
|
|
23341
|
-
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
23342
|
-
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
23474
|
+
for (let k = runs.length - 1; k >= 0; k -= 1) {
|
|
23475
|
+
const r = runs[k];
|
|
23476
|
+
if (r.color !== color || r.lo > hi || r.hi < lo) continue;
|
|
23477
|
+
if (r.lo < lo) lo = r.lo;
|
|
23478
|
+
if (r.hi > hi) hi = r.hi;
|
|
23479
|
+
runs.splice(k, 1);
|
|
23480
|
+
}
|
|
23481
|
+
runs.push({ lo, hi, color, lastIdx: i });
|
|
23343
23482
|
}
|
|
23344
23483
|
flush();
|
|
23345
23484
|
return out;
|
|
@@ -24010,7 +24149,7 @@ var WebGL2Backend = class {
|
|
|
24010
24149
|
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
24011
24150
|
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
24012
24151
|
const emitOne = (s, off) => {
|
|
24013
|
-
if (!isLineLikeSeries(s) || s
|
|
24152
|
+
if (!isLineLikeSeries(s) || !seriesShownOn(s, "pane")) return;
|
|
24014
24153
|
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
24015
24154
|
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
24016
24155
|
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
@@ -24121,7 +24260,7 @@ var WebGL2Backend = class {
|
|
|
24121
24260
|
const spacing = coords.bodySpacing();
|
|
24122
24261
|
const tier = candleTier(spacing);
|
|
24123
24262
|
if (tier === "aggregate") {
|
|
24124
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24263
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => barColors.get(bar.time) ?? (bar.close >= bar.open ? up : down));
|
|
24125
24264
|
return;
|
|
24126
24265
|
}
|
|
24127
24266
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -24262,14 +24401,17 @@ var WebGL2Backend = class {
|
|
|
24262
24401
|
const spacing = coords.bodySpacing();
|
|
24263
24402
|
const tier = candleTier(spacing);
|
|
24264
24403
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
24404
|
+
const up = paint.up;
|
|
24405
|
+
const down = paint.down;
|
|
24406
|
+
const cs = paint.candle;
|
|
24265
24407
|
if (tier === "aggregate") {
|
|
24266
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24408
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => {
|
|
24409
|
+
const isUp = bar.close >= bar.open;
|
|
24410
|
+
return (isUp ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(bar.time) ?? (isUp ? up : down);
|
|
24411
|
+
});
|
|
24267
24412
|
return;
|
|
24268
24413
|
}
|
|
24269
24414
|
const drawBody = tier === "full";
|
|
24270
|
-
const up = paint.up;
|
|
24271
|
-
const down = paint.down;
|
|
24272
|
-
const cs = paint.candle;
|
|
24273
24415
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
24274
24416
|
for (let i = i0; i <= i1; i += 1) {
|
|
24275
24417
|
const bar = bars[i];
|
|
@@ -24318,24 +24460,26 @@ var WebGL2Backend = class {
|
|
|
24318
24460
|
/**
|
|
24319
24461
|
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
24320
24462
|
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
24321
|
-
* coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
24322
|
-
* column stays a void
|
|
24323
|
-
*
|
|
24463
|
+
* SAME-COLOR coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
24464
|
+
* inside the column stays a void and every bar's extent keeps its own color. Draw
|
|
24465
|
+
* cost stays bounded by screen width, not bar count. `colorOf` is the calling
|
|
24466
|
+
* style's per-bar paint rule (candles and OHLC bars resolve theirs differently).
|
|
24324
24467
|
*/
|
|
24325
|
-
emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24468
|
+
emitCandlesAggregated(b, bars, i0, i1, coords, pane, colorOf2) {
|
|
24326
24469
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
24327
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
24328
|
-
const c = parseColor(
|
|
24470
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
24471
|
+
const c = parseColor(s.color);
|
|
24329
24472
|
const hY = yOf(s.hi);
|
|
24330
24473
|
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
24331
24474
|
}
|
|
24332
24475
|
}
|
|
24333
24476
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
24477
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
24334
24478
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
24335
24479
|
this.emitPlotCandles(b, spec, pane, coords, i0, i1, theme, off);
|
|
24336
24480
|
return;
|
|
24337
24481
|
}
|
|
24338
|
-
if (!isLineLikeSeries(spec)
|
|
24482
|
+
if (!isLineLikeSeries(spec)) return;
|
|
24339
24483
|
switch (spec.kind) {
|
|
24340
24484
|
case "histogram":
|
|
24341
24485
|
case "columns":
|
|
@@ -25999,7 +26143,7 @@ var Canvas2dBackend = class {
|
|
|
25999
26143
|
const spacing = coords.bodySpacing();
|
|
26000
26144
|
const tier = candleTier(spacing);
|
|
26001
26145
|
if (tier === "aggregate") {
|
|
26002
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26146
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => barColors.get(b.time) ?? (b.close >= b.open ? up : down));
|
|
26003
26147
|
return;
|
|
26004
26148
|
}
|
|
26005
26149
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -26175,12 +26319,15 @@ var Canvas2dBackend = class {
|
|
|
26175
26319
|
const spacing = coords.bodySpacing();
|
|
26176
26320
|
const tier = candleTier(spacing);
|
|
26177
26321
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
26322
|
+
const cs = paint.candle;
|
|
26178
26323
|
if (tier === "aggregate") {
|
|
26179
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26324
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => {
|
|
26325
|
+
const up = b.close >= b.open;
|
|
26326
|
+
return (up ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(b.time) ?? (up ? paint.up : paint.down);
|
|
26327
|
+
});
|
|
26180
26328
|
return;
|
|
26181
26329
|
}
|
|
26182
26330
|
const drawBody = tier === "full";
|
|
26183
|
-
const cs = paint.candle;
|
|
26184
26331
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
26185
26332
|
for (let i = i0; i <= i1; i += 1) {
|
|
26186
26333
|
const b = bars[i];
|
|
@@ -26237,17 +26384,18 @@ var Canvas2dBackend = class {
|
|
|
26237
26384
|
}
|
|
26238
26385
|
/**
|
|
26239
26386
|
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
26240
|
-
* sticks (one per contiguous coverage run — see
|
|
26241
|
-
* so draw cost is bounded by screen width (not
|
|
26242
|
-
*
|
|
26243
|
-
* its
|
|
26387
|
+
* sticks (one per contiguous SAME-COLOR coverage run — see
|
|
26388
|
+
* {@link aggregateCandleColumns}), so draw cost is bounded by screen width (not
|
|
26389
|
+
* bar count) when zoomed far out, a price gap inside the column stays a void, and
|
|
26390
|
+
* every bar's extent keeps its own color. `colorOf` is the calling style's per-bar
|
|
26391
|
+
* paint rule (candles and OHLC bars resolve theirs differently).
|
|
26244
26392
|
*/
|
|
26245
|
-
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26393
|
+
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, colorOf2) {
|
|
26246
26394
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
26247
26395
|
ctx.lineWidth = 1;
|
|
26248
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
26396
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
26249
26397
|
const x = s.x + 0.5;
|
|
26250
|
-
ctx.strokeStyle =
|
|
26398
|
+
ctx.strokeStyle = s.color;
|
|
26251
26399
|
ctx.beginPath();
|
|
26252
26400
|
ctx.moveTo(x, yOf(s.hi));
|
|
26253
26401
|
ctx.lineTo(x, yOf(s.lo));
|
|
@@ -26255,11 +26403,12 @@ var Canvas2dBackend = class {
|
|
|
26255
26403
|
}
|
|
26256
26404
|
}
|
|
26257
26405
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
26406
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
26258
26407
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
26259
26408
|
this.drawPlotCandles(ctx, spec, pane, coords, i0, i1, theme, off);
|
|
26260
26409
|
return;
|
|
26261
26410
|
}
|
|
26262
|
-
if (!isLineLikeSeries(spec)
|
|
26411
|
+
if (!isLineLikeSeries(spec)) return;
|
|
26263
26412
|
switch (spec.kind) {
|
|
26264
26413
|
case "histogram":
|
|
26265
26414
|
case "columns":
|
|
@@ -34485,6 +34634,7 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
34485
34634
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
34486
34635
|
}
|
|
34487
34636
|
function considerSeries(s, i0, i1, off, consider) {
|
|
34637
|
+
if (!seriesInScale(s)) return;
|
|
34488
34638
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
34489
34639
|
for (let i = i0; i <= i1; i += 1) {
|
|
34490
34640
|
const b = s.bars[i - off];
|
|
@@ -34704,6 +34854,22 @@ var MarkPopover = class {
|
|
|
34704
34854
|
this.openKey = null;
|
|
34705
34855
|
/** Bumped per open/close — a lazy content resolving after its popup went away is dropped. */
|
|
34706
34856
|
this.generation = 0;
|
|
34857
|
+
/**
|
|
34858
|
+
* The cluster whose popup was open when the current pointer press began. The anchor is
|
|
34859
|
+
* hit-transparent (the glyph is canvas pixels), so the shell's outside-dismiss closes the
|
|
34860
|
+
* popup on pointerdown; the click the renderer derives from the same pointerup must then
|
|
34861
|
+
* read as "close", not reopen that cluster. Captured on the document ahead of the shell's
|
|
34862
|
+
* (deferred) listener, cleared once the release has dispatched.
|
|
34863
|
+
*/
|
|
34864
|
+
this.pressKey = null;
|
|
34865
|
+
this.onPress = () => {
|
|
34866
|
+
if (this.openKey === null) return;
|
|
34867
|
+
this.pressKey = this.openKey;
|
|
34868
|
+
const doc = this.deps.plot.ownerDocument;
|
|
34869
|
+
const clear = () => void setTimeout(() => this.pressKey = null, 0);
|
|
34870
|
+
doc.addEventListener("pointerup", clear, { capture: true, once: true });
|
|
34871
|
+
doc.addEventListener("pointercancel", clear, { capture: true, once: true });
|
|
34872
|
+
};
|
|
34707
34873
|
const doc = deps.plot.ownerDocument;
|
|
34708
34874
|
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
34709
34875
|
injectStyles(MARKS_STYLE_ID, MARKS_CSS, doc);
|
|
@@ -34711,12 +34877,14 @@ var MarkPopover = class {
|
|
|
34711
34877
|
this.anchor.className = "vela-marks-anchor";
|
|
34712
34878
|
Object.assign(this.anchor.style, { position: "absolute", pointerEvents: "none", left: "0", top: "0", width: "0", height: "0" });
|
|
34713
34879
|
deps.plot.appendChild(this.anchor);
|
|
34880
|
+
doc.addEventListener("pointerdown", this.onPress, true);
|
|
34714
34881
|
}
|
|
34715
34882
|
/** The cluster key the open popup belongs to, or null. */
|
|
34716
34883
|
get key() {
|
|
34717
34884
|
return this.openKey;
|
|
34718
34885
|
}
|
|
34719
34886
|
open(cluster, rect) {
|
|
34887
|
+
if (this.pressKey === cluster.key) return;
|
|
34720
34888
|
this.close();
|
|
34721
34889
|
this.place(rect);
|
|
34722
34890
|
const gen = ++this.generation;
|
|
@@ -34764,6 +34932,7 @@ var MarkPopover = class {
|
|
|
34764
34932
|
}
|
|
34765
34933
|
destroy() {
|
|
34766
34934
|
this.close();
|
|
34935
|
+
this.deps.plot.ownerDocument.removeEventListener("pointerdown", this.onPress, true);
|
|
34767
34936
|
this.anchor.remove();
|
|
34768
34937
|
}
|
|
34769
34938
|
place(rect) {
|
|
@@ -35524,6 +35693,13 @@ var NativeRenderer = class {
|
|
|
35524
35693
|
this.backdropRenderer = new BackdropRenderer();
|
|
35525
35694
|
this.volumeRenderer = new VolumeRenderer();
|
|
35526
35695
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
35696
|
+
/**
|
|
35697
|
+
* The mounted SDK layers. One BASE entry per registered definition (channel = the layer
|
|
35698
|
+
* id, no owner of its own), plus one DEDICATED entry per mounted native indicator whose
|
|
35699
|
+
* model names its own channel (`native.channel` — an instance of a multi-instance type):
|
|
35700
|
+
* same definition, its own canvas, reading that instance's channel and following that
|
|
35701
|
+
* instance's pane and z key. `channel` is unique across the array.
|
|
35702
|
+
*/
|
|
35527
35703
|
this.extLayers = [];
|
|
35528
35704
|
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
35529
35705
|
this.layerOrderSig = "";
|
|
@@ -36669,11 +36845,7 @@ var NativeRenderer = class {
|
|
|
36669
36845
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
36670
36846
|
this.plot = document.createElement("div");
|
|
36671
36847
|
Object.assign(this.plot.style, { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" });
|
|
36672
|
-
this.extLayers = rendererLayers().map((def) => {
|
|
36673
|
-
const canvas = document.createElement("canvas");
|
|
36674
|
-
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
36675
|
-
return { def, instance: def.create(), canvas };
|
|
36676
|
-
});
|
|
36848
|
+
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
36677
36849
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
36678
36850
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
36679
36851
|
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
@@ -36690,6 +36862,7 @@ var NativeRenderer = class {
|
|
|
36690
36862
|
this.volumeRenderer.mount(this.volumeCanvas);
|
|
36691
36863
|
this.vpvrRenderer.mount(this.vpvrCanvas);
|
|
36692
36864
|
for (const l of this.extLayers) l.instance.mount(l.canvas);
|
|
36865
|
+
for (const m of this.scene.indicators.values()) this.ensureInstanceLayer(m);
|
|
36693
36866
|
this.chrome.mount(this.chromeCanvas);
|
|
36694
36867
|
this.crosshairLayer.mount(this.cursorCanvas);
|
|
36695
36868
|
this.coords.setViewport(defaultViewport());
|
|
@@ -37282,6 +37455,7 @@ var NativeRenderer = class {
|
|
|
37282
37455
|
this.refreshAnchorOffset(model);
|
|
37283
37456
|
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
37284
37457
|
else this.scene.assignIndicatorZ(model.id);
|
|
37458
|
+
this.ensureInstanceLayer(model);
|
|
37285
37459
|
if (model.legend !== false) {
|
|
37286
37460
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
37287
37461
|
native: !!model.native,
|
|
@@ -37325,6 +37499,7 @@ var NativeRenderer = class {
|
|
|
37325
37499
|
this.scene.vpvrLayer = null;
|
|
37326
37500
|
}
|
|
37327
37501
|
this.scene.indicators.delete(handle.id);
|
|
37502
|
+
this.dropInstanceLayer(handle.id);
|
|
37328
37503
|
this.scene.forgetIndicatorZ(handle.id);
|
|
37329
37504
|
this.scene.forgetAnchorOffset(handle.id);
|
|
37330
37505
|
this.scene.dropIndicatorScale(handle.id);
|
|
@@ -38118,29 +38293,31 @@ var NativeRenderer = class {
|
|
|
38118
38293
|
up: bar.close >= bar.open
|
|
38119
38294
|
};
|
|
38120
38295
|
}
|
|
38121
|
-
/** One group per indicator (name = indicator title), each with a row per
|
|
38296
|
+
/** One group per indicator (name = indicator title), each with a row per plot shown in the data window. */
|
|
38122
38297
|
dataWindowGroups(idx, pricePane) {
|
|
38123
38298
|
const groups = [];
|
|
38124
38299
|
for (const model of this.scene.indicators.values()) {
|
|
38125
|
-
const rows = this.
|
|
38300
|
+
const rows = this.readoutRowsFor(model, idx, pricePane, "dataWindow");
|
|
38126
38301
|
if (rows.length) groups.push({ name: model.title, rows });
|
|
38127
38302
|
}
|
|
38128
38303
|
return groups;
|
|
38129
38304
|
}
|
|
38130
|
-
/** One indicator's readout at bar `idx
|
|
38131
|
-
|
|
38305
|
+
/** One indicator's readout at bar `idx` for one value surface (the legend or the data window):
|
|
38306
|
+
* a row per plot shown on that surface, formatted on its pane's scale. A plot's `display`
|
|
38307
|
+
* decides per surface — a data-window-only plot has a row here and none in the legend. */
|
|
38308
|
+
readoutRowsFor(model, idx, pricePane, surface) {
|
|
38132
38309
|
if (model.native?.type === "volume") return this.volumeReadoutRows(model, idx);
|
|
38133
38310
|
const pane = (model.paneId ? this.scene.panes.get(model.paneId) : null) ?? pricePane;
|
|
38134
38311
|
const off = this.scene.offsetOf(model.id);
|
|
38135
38312
|
const rows = [];
|
|
38136
38313
|
for (const s of model.series) {
|
|
38314
|
+
if (!seriesShownOn(s, surface)) continue;
|
|
38137
38315
|
let value;
|
|
38138
38316
|
let color;
|
|
38139
38317
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
38140
38318
|
value = s.bars[idx - off]?.close;
|
|
38141
38319
|
color = s.style?.up ?? this.theme.upColor;
|
|
38142
38320
|
} else if (isLineLikeSeries(s)) {
|
|
38143
|
-
if (s.visible === false) continue;
|
|
38144
38321
|
value = s.points[idx - off]?.value;
|
|
38145
38322
|
color = s.points[idx - off]?.color ?? s.style.color;
|
|
38146
38323
|
} else {
|
|
@@ -38163,9 +38340,10 @@ var NativeRenderer = class {
|
|
|
38163
38340
|
const color = bar.close >= bar.open ? cfg?.upColor ?? this.theme.upColor : cfg?.downColor ?? this.theme.downColor;
|
|
38164
38341
|
return [{ label: model.title, value: formatVolume(vol), color }];
|
|
38165
38342
|
}
|
|
38166
|
-
/** Refresh the plot values beside every legend title — the same
|
|
38167
|
-
*
|
|
38168
|
-
*
|
|
38343
|
+
/** Refresh the plot values beside every legend title — the same bar the data window reads
|
|
38344
|
+
* (crosshair bar, else the latest bar), pushed per paint, filtered to the plots whose
|
|
38345
|
+
* `display` includes the legend. A hidden indicator has no scene model, so its row is
|
|
38346
|
+
* absent from the map and its readout clears. */
|
|
38169
38347
|
updateLegendValues() {
|
|
38170
38348
|
if (!this.inputsUI) return;
|
|
38171
38349
|
const n = this.bars.length;
|
|
@@ -38174,7 +38352,7 @@ var NativeRenderer = class {
|
|
|
38174
38352
|
const idx = this.hoverLogical != null ? this.hoverLogical : n - 1;
|
|
38175
38353
|
const pricePane = this.dataWindowPricePane();
|
|
38176
38354
|
for (const model of this.scene.indicators.values()) {
|
|
38177
|
-
values.set(model.id, this.
|
|
38355
|
+
values.set(model.id, this.readoutRowsFor(model, idx, pricePane, "legend").map((r) => ({ value: r.value, color: r.color })));
|
|
38178
38356
|
}
|
|
38179
38357
|
}
|
|
38180
38358
|
this.inputsUI.setPlotValues(values);
|
|
@@ -38200,9 +38378,9 @@ var NativeRenderer = class {
|
|
|
38200
38378
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
38201
38379
|
for (const l of this.extLayers) {
|
|
38202
38380
|
if (!l.def.repaintOnCursor) continue;
|
|
38203
|
-
const lp = this.layerPane(l
|
|
38381
|
+
const lp = this.layerPane(l) ?? pane;
|
|
38204
38382
|
if (lp.collapsed) continue;
|
|
38205
|
-
l.instance.render(this.extLayerArgs(l
|
|
38383
|
+
l.instance.render(this.extLayerArgs(l, lp.scale, lp.bounds, nowMs));
|
|
38206
38384
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
38207
38385
|
}
|
|
38208
38386
|
}
|
|
@@ -38211,13 +38389,15 @@ var NativeRenderer = class {
|
|
|
38211
38389
|
if (canvas.width === 0 || canvas.height === 0) return;
|
|
38212
38390
|
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
38213
38391
|
}
|
|
38214
|
-
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
38215
|
-
|
|
38392
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
38393
|
+
* Data + pending come from the entry's channel (per instance for a dedicated entry);
|
|
38394
|
+
* the settings bag is the TYPE's — a chart type's dialog values, one bag per type. */
|
|
38395
|
+
extLayerArgs(l, scale, bounds, nowMs) {
|
|
38216
38396
|
return {
|
|
38217
38397
|
bars: this.scene.bars,
|
|
38218
|
-
data: this.scene.nativeData.get(
|
|
38219
|
-
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
38220
|
-
pending: this.scene.nativePending.get(
|
|
38398
|
+
data: this.scene.nativeData.get(l.channel),
|
|
38399
|
+
settings: this.scene.nativeData.get(`${l.def.id}-settings`) ?? {},
|
|
38400
|
+
pending: this.scene.nativePending.get(l.channel) ?? [],
|
|
38221
38401
|
coords: this.coords,
|
|
38222
38402
|
scale,
|
|
38223
38403
|
bounds,
|
|
@@ -38260,12 +38440,12 @@ var NativeRenderer = class {
|
|
|
38260
38440
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
38261
38441
|
let folded = null;
|
|
38262
38442
|
for (const l of this.extLayers) {
|
|
38263
|
-
const lp = this.layerPane(l
|
|
38443
|
+
const lp = this.layerPane(l) ?? pane;
|
|
38264
38444
|
if (lp.collapsed) {
|
|
38265
38445
|
this.clearLayerCanvas(l.canvas);
|
|
38266
38446
|
continue;
|
|
38267
38447
|
}
|
|
38268
|
-
const args = this.extLayerArgs(l
|
|
38448
|
+
const args = this.extLayerArgs(l, lp.scale, lp.bounds, nowMs);
|
|
38269
38449
|
l.instance.render(args);
|
|
38270
38450
|
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
38271
38451
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
@@ -38548,19 +38728,60 @@ var NativeRenderer = class {
|
|
|
38548
38728
|
}
|
|
38549
38729
|
return null;
|
|
38550
38730
|
}
|
|
38551
|
-
/**
|
|
38552
|
-
|
|
38553
|
-
|
|
38554
|
-
|
|
38555
|
-
|
|
38731
|
+
/** One transparent, pointer-transparent SDK layer canvas covering the plot. */
|
|
38732
|
+
createLayerCanvas() {
|
|
38733
|
+
const canvas = document.createElement("canvas");
|
|
38734
|
+
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38735
|
+
return canvas;
|
|
38736
|
+
}
|
|
38737
|
+
/**
|
|
38738
|
+
* Mount the DEDICATED layer of a native indicator that names its own channel (an
|
|
38739
|
+
* instance of a multi-instance type painting through a registered layer): a fresh
|
|
38740
|
+
* instance of the layer's definition on its own canvas, reading that channel. Idempotent
|
|
38741
|
+
* — a show after hide re-mounts the model, and the layer (kept across the hide, like the
|
|
38742
|
+
* z key) is simply reused. Skipped while the renderer itself is unmounted (`mount()`
|
|
38743
|
+
* catches up from the scene).
|
|
38744
|
+
*/
|
|
38745
|
+
ensureInstanceLayer(model) {
|
|
38746
|
+
const channel = model.native?.channel;
|
|
38747
|
+
if (!channel || !this.plot) return;
|
|
38748
|
+
if (this.extLayers.some((l) => l.owner === model.id)) return;
|
|
38749
|
+
const base = this.extLayers.find((l) => l.owner === null && l.def.id === model.native.type);
|
|
38750
|
+
if (!base) return;
|
|
38751
|
+
const canvas = this.createLayerCanvas();
|
|
38752
|
+
canvas.width = this.dataCanvas.width;
|
|
38753
|
+
canvas.height = this.dataCanvas.height;
|
|
38754
|
+
canvas.style.width = this.dataCanvas.style.width || "100%";
|
|
38755
|
+
canvas.style.height = this.dataCanvas.style.height || "100%";
|
|
38756
|
+
const entry = { def: base.def, instance: base.def.create(), canvas, channel, owner: model.id };
|
|
38757
|
+
this.extLayers.push(entry);
|
|
38758
|
+
this.plot.insertBefore(canvas, this.chromeCanvas);
|
|
38759
|
+
entry.instance.mount(canvas);
|
|
38760
|
+
}
|
|
38761
|
+
/** Tear down the dedicated layer of a removed indicator (a hide keeps it). */
|
|
38762
|
+
dropInstanceLayer(indicatorId) {
|
|
38763
|
+
const i = this.extLayers.findIndex((l2) => l2.owner === indicatorId);
|
|
38764
|
+
if (i < 0) return;
|
|
38765
|
+
const [l] = this.extLayers.splice(i, 1);
|
|
38766
|
+
l.instance.destroy?.();
|
|
38767
|
+
l.canvas.remove();
|
|
38768
|
+
}
|
|
38769
|
+
/** The mounted native indicator that OWNS an SDK layer entry. A dedicated entry is
|
|
38770
|
+
* owned by the instance it was mounted for; the base entry by the mounted indicator
|
|
38771
|
+
* whose type equals the layer id and that has no channel of its own (the id doubles
|
|
38772
|
+
* as the data channel, so the pairing is the SDK's own contract). Null for chart-type
|
|
38773
|
+
* channels and while the owner is hidden (a hidden indicator leaves the scene; its
|
|
38774
|
+
* cleared data channel paints nothing anyway). */
|
|
38775
|
+
layerOwner(l) {
|
|
38776
|
+
if (l.owner !== null) return this.scene.indicators.get(l.owner) ?? null;
|
|
38556
38777
|
for (const m of this.scene.indicators.values()) {
|
|
38557
|
-
if (m.native?.type ===
|
|
38778
|
+
if (m.native?.type === l.def.id && !m.native.channel) return m;
|
|
38558
38779
|
}
|
|
38559
38780
|
return null;
|
|
38560
38781
|
}
|
|
38561
38782
|
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
38562
|
-
layerPane(
|
|
38563
|
-
const owner = this.layerOwner(
|
|
38783
|
+
layerPane(l) {
|
|
38784
|
+
const owner = this.layerOwner(l);
|
|
38564
38785
|
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID2 : PRICE_PANE_ID2;
|
|
38565
38786
|
return this.scene.panes.get(paneId) ?? null;
|
|
38566
38787
|
}
|
|
@@ -38568,12 +38789,12 @@ var NativeRenderer = class {
|
|
|
38568
38789
|
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
38569
38790
|
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
38570
38791
|
orderedLayerCanvases() {
|
|
38571
|
-
const byId = new Map(this.extLayers.map((l) => [l.
|
|
38792
|
+
const byId = new Map(this.extLayers.map((l) => [l.channel, l.canvas]));
|
|
38572
38793
|
const { below, above } = stackLayers(
|
|
38573
38794
|
this.extLayers.map((l) => {
|
|
38574
|
-
const owner = this.layerOwner(l
|
|
38795
|
+
const owner = this.layerOwner(l);
|
|
38575
38796
|
return {
|
|
38576
|
-
id: l.
|
|
38797
|
+
id: l.channel,
|
|
38577
38798
|
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
38578
38799
|
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
38579
38800
|
};
|
|
@@ -38595,7 +38816,7 @@ var NativeRenderer = class {
|
|
|
38595
38816
|
syncLayerCanvasOrder() {
|
|
38596
38817
|
if (this.extLayers.length === 0 || !this.plot) return;
|
|
38597
38818
|
const { below, above } = this.orderedLayerCanvases();
|
|
38598
|
-
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).
|
|
38819
|
+
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).channel), "|", ...above.map((c) => this.extLayers.find((l) => l.canvas === c).channel)].join(",");
|
|
38599
38820
|
if (sig === this.layerOrderSig) return;
|
|
38600
38821
|
this.layerOrderSig = sig;
|
|
38601
38822
|
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
@@ -43759,7 +43980,8 @@ var ChartCell = class {
|
|
|
43759
43980
|
this.statusline?.setFitMode(on);
|
|
43760
43981
|
}
|
|
43761
43982
|
/** The workspace shell keeps the app theme; the cell host's tokens re-derive from
|
|
43762
|
-
* the LIVE plot surface (
|
|
43983
|
+
* the LIVE plot surface for overlay ink (statusline, watermark). Menus portal to
|
|
43984
|
+
* `.vela-ui` and keep the app-theme surface (see {@link applyPlotOverlayTokens}). */
|
|
43763
43985
|
syncPlotOverlayTokens() {
|
|
43764
43986
|
applyPlotOverlayTokens(this.host, this.appTheme, this.inner?.renderer.getConfig() ?? null);
|
|
43765
43987
|
}
|
|
@@ -44643,7 +44865,21 @@ var VelaWorkspace = class {
|
|
|
44643
44865
|
/** Live sync configuration (mutable copy of the option). */
|
|
44644
44866
|
this.syncOpts = {};
|
|
44645
44867
|
this.stateTimer = null;
|
|
44646
|
-
|
|
44868
|
+
/** Re-entrance guard for {@link flushPendingState}: a `state:changed` subscriber that
|
|
44869
|
+
* calls back into the workspace (`destroy()` on save is the obvious one) must not
|
|
44870
|
+
* re-enter the flush and recurse. */
|
|
44871
|
+
this.emitting = false;
|
|
44872
|
+
/** False until the constructor finishes. Boot is not an edit: seeding the sync
|
|
44873
|
+
* settings marks state dirty, so without this a workspace mounted and torn down
|
|
44874
|
+
* inside the debounce window would flush and tell the host the user had changed
|
|
44875
|
+
* something — a save of the default document over whatever was stored. */
|
|
44876
|
+
this.booted = false;
|
|
44877
|
+
/** A page unload gets the same flush as a teardown — the pending edit reaches the
|
|
44878
|
+
* host, the timer is cleared, and a `destroy()` that follows adds no second write. */
|
|
44879
|
+
this.onUnload = () => {
|
|
44880
|
+
this.flushPendingState();
|
|
44881
|
+
this.persistNow();
|
|
44882
|
+
};
|
|
44647
44883
|
/** Re-entrance guard around one propagation tick: followers' synchronous echoes
|
|
44648
44884
|
* (their setVisibleRange re-emits viewport:changed) must not re-propagate. */
|
|
44649
44885
|
this.syncBusy = false;
|
|
@@ -44957,6 +45193,7 @@ var VelaWorkspace = class {
|
|
|
44957
45193
|
}
|
|
44958
45194
|
this.mountAttachments();
|
|
44959
45195
|
this.restoreGlobalExt();
|
|
45196
|
+
this.booted = true;
|
|
44960
45197
|
}
|
|
44961
45198
|
// ── access ──────────────────────────────────────────────────
|
|
44962
45199
|
/** The cell with identity `id` (its declared name, or `c<N>` when undeclared), or
|
|
@@ -45365,13 +45602,17 @@ var VelaWorkspace = class {
|
|
|
45365
45602
|
}
|
|
45366
45603
|
destroy() {
|
|
45367
45604
|
if (this.destroyed) return;
|
|
45368
|
-
this.
|
|
45605
|
+
this.flushPendingState();
|
|
45369
45606
|
this.destroyed = true;
|
|
45370
|
-
if (this.stateTimer != null) clearTimeout(this.stateTimer);
|
|
45371
45607
|
if (this.persistKey !== null && typeof window !== "undefined") window.removeEventListener("beforeunload", this.onUnload);
|
|
45372
45608
|
this.resizeObserver?.disconnect();
|
|
45373
45609
|
this.splitters.destroy();
|
|
45374
45610
|
for (const [id, cell] of [...this.cellsById]) {
|
|
45611
|
+
try {
|
|
45612
|
+
this.poolSet(id, cell.dehydrate());
|
|
45613
|
+
} catch (err) {
|
|
45614
|
+
console.warn(`[vela] could not snapshot cell "${id}" while destroying:`, err);
|
|
45615
|
+
}
|
|
45375
45616
|
cell.destroy();
|
|
45376
45617
|
this.cellsById.delete(id);
|
|
45377
45618
|
}
|
|
@@ -45457,7 +45698,7 @@ var VelaWorkspace = class {
|
|
|
45457
45698
|
/** Debounced dirty mark: one `state:changed` (+ one storage write in persist mode)
|
|
45458
45699
|
* per burst of edits, flushed hard on unload/destroy. */
|
|
45459
45700
|
markStateDirty() {
|
|
45460
|
-
if (this.destroyed) return;
|
|
45701
|
+
if (this.destroyed || !this.booted) return;
|
|
45461
45702
|
if (this.stateTimer != null) clearTimeout(this.stateTimer);
|
|
45462
45703
|
this.stateTimer = setTimeout(() => {
|
|
45463
45704
|
this.stateTimer = null;
|
|
@@ -45465,6 +45706,44 @@ var VelaWorkspace = class {
|
|
|
45465
45706
|
this.persistNow();
|
|
45466
45707
|
}, 500);
|
|
45467
45708
|
}
|
|
45709
|
+
/**
|
|
45710
|
+
* Push a pending debounced change out NOW, for a teardown or a page unload — the
|
|
45711
|
+
* two moments the 500ms timer would otherwise never reach.
|
|
45712
|
+
*
|
|
45713
|
+
* Dropping it was a silent data loss: the timer carries BOTH halves of the dirty
|
|
45714
|
+
* signal — the `state:changed` event and the storage write — so a host that saves
|
|
45715
|
+
* from that event (a server-backed host, where `persistNow` is a no-op for want of
|
|
45716
|
+
* a `persistKey`) simply never heard about the user's last edit.
|
|
45717
|
+
*
|
|
45718
|
+
* Idempotent and re-entrancy safe, which the ordering here buys:
|
|
45719
|
+
* - a no-op unless a timer is actually pending, so a teardown with nothing
|
|
45720
|
+
* outstanding stays silent and never invents an edit;
|
|
45721
|
+
* - the timer is cleared FIRST, so nothing survives this call and a second call
|
|
45722
|
+
* does nothing — `destroy()` after an unload flush adds no second write;
|
|
45723
|
+
* - the state is snapshotted BEFORE the handlers run, and that snapshot is what
|
|
45724
|
+
* gets persisted, so storage and the host are told the same thing even if a
|
|
45725
|
+
* handler mutates state while it runs;
|
|
45726
|
+
* - `emitting` is held across the emit, so a handler calling back in (a host that
|
|
45727
|
+
* tears down on save is the obvious one) cannot re-enter this and recurse.
|
|
45728
|
+
*/
|
|
45729
|
+
flushPendingState() {
|
|
45730
|
+
if (this.stateTimer == null || this.emitting) return;
|
|
45731
|
+
clearTimeout(this.stateTimer);
|
|
45732
|
+
this.stateTimer = null;
|
|
45733
|
+
const snapshot = this.persistKey !== null ? encodeState(this.getState()) : null;
|
|
45734
|
+
this.emitting = true;
|
|
45735
|
+
try {
|
|
45736
|
+
this.events.emit("state:changed", void 0);
|
|
45737
|
+
} finally {
|
|
45738
|
+
this.emitting = false;
|
|
45739
|
+
}
|
|
45740
|
+
if (snapshot !== null && this.persistKey !== null) {
|
|
45741
|
+
try {
|
|
45742
|
+
void this.storage.set(this.persistKey, snapshot);
|
|
45743
|
+
} catch {
|
|
45744
|
+
}
|
|
45745
|
+
}
|
|
45746
|
+
}
|
|
45468
45747
|
/** Write the current state through the storage adapter now (fire-and-forget). */
|
|
45469
45748
|
persistNow() {
|
|
45470
45749
|
if (this.persistKey === null || this.destroyed) return;
|