@luxalgo/vela 0.6.22 → 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/workspace.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";
|
|
@@ -8154,16 +8158,18 @@ var Pitchfork = class extends SegmentDrawing {
|
|
|
8154
8158
|
geometry(proj) {
|
|
8155
8159
|
const a = this.anchors[0];
|
|
8156
8160
|
const b = this.anchors[1];
|
|
8157
|
-
|
|
8158
|
-
if (!a || !b || !c) return null;
|
|
8161
|
+
if (!a || !b) return null;
|
|
8159
8162
|
const px = (p) => {
|
|
8160
8163
|
const y = proj.yOf(p.price, this.paneId);
|
|
8161
8164
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8162
8165
|
};
|
|
8163
8166
|
const P0 = px(a);
|
|
8164
8167
|
const P1 = px(b);
|
|
8168
|
+
if (!P0 || !P1) return null;
|
|
8169
|
+
const c = this.anchors[2];
|
|
8170
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
8165
8171
|
const P2 = px(c);
|
|
8166
|
-
if (!
|
|
8172
|
+
if (!P2) return null;
|
|
8167
8173
|
const mx = (P1[0] + P2[0]) / 2;
|
|
8168
8174
|
const my = (P1[1] + P2[1]) / 2;
|
|
8169
8175
|
const dx = mx - P0[0];
|
|
@@ -8210,16 +8216,19 @@ var PitchforkVariant = class extends SegmentDrawing {
|
|
|
8210
8216
|
geometry(proj) {
|
|
8211
8217
|
const a = this.anchors[0];
|
|
8212
8218
|
const b = this.anchors[1];
|
|
8213
|
-
|
|
8214
|
-
if (!a || !b || !c) return null;
|
|
8219
|
+
if (!a || !b) return null;
|
|
8215
8220
|
const px = (p) => {
|
|
8216
8221
|
const y = proj.yOf(p.price, this.paneId);
|
|
8217
8222
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8218
8223
|
};
|
|
8219
|
-
const
|
|
8224
|
+
const P0 = px(a);
|
|
8220
8225
|
const P1 = px(b);
|
|
8226
|
+
if (!P0 || !P1) return null;
|
|
8227
|
+
const c = this.anchors[2];
|
|
8228
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
8229
|
+
const S2 = px(this.medianStart(a, b, c));
|
|
8221
8230
|
const P2 = px(c);
|
|
8222
|
-
if (!S2 || !
|
|
8231
|
+
if (!S2 || !P2) return null;
|
|
8223
8232
|
const mx = (P1[0] + P2[0]) / 2;
|
|
8224
8233
|
const my = (P1[1] + P2[1]) / 2;
|
|
8225
8234
|
const dx = mx - S2[0];
|
|
@@ -8234,8 +8243,12 @@ var PitchforkVariant = class extends SegmentDrawing {
|
|
|
8234
8243
|
// upper tine
|
|
8235
8244
|
extendRay(P2[0], P2[1], P2[0] + dx, P2[1] + dy, "right", w, h),
|
|
8236
8245
|
// lower tine
|
|
8237
|
-
[P1[0], P1[1], P2[0], P2[1]]
|
|
8246
|
+
[P1[0], P1[1], P2[0], P2[1]],
|
|
8238
8247
|
// base line
|
|
8248
|
+
// The median leaves from the shifted origin, not the pivot — without this construction
|
|
8249
|
+
// line the pivot handle would float unattached to the shape it defines.
|
|
8250
|
+
[P0[0], P0[1], P1[0], P1[1]]
|
|
8251
|
+
// pivot → first tine anchor
|
|
8239
8252
|
],
|
|
8240
8253
|
fill: null
|
|
8241
8254
|
};
|
|
@@ -8292,17 +8305,19 @@ var InsidePitchfork = class extends SegmentDrawing {
|
|
|
8292
8305
|
geometry(proj) {
|
|
8293
8306
|
const a = this.anchors[0];
|
|
8294
8307
|
const b = this.anchors[1];
|
|
8295
|
-
|
|
8296
|
-
if (!a || !b || !c) return null;
|
|
8308
|
+
if (!a || !b) return null;
|
|
8297
8309
|
const px = (p) => {
|
|
8298
8310
|
const y = proj.yOf(p.price, this.paneId);
|
|
8299
8311
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8300
8312
|
};
|
|
8301
|
-
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
8302
8313
|
const P0 = px(a);
|
|
8303
8314
|
const P1 = px(b);
|
|
8315
|
+
if (!P0 || !P1) return null;
|
|
8316
|
+
const c = this.anchors[2];
|
|
8317
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
8318
|
+
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
8304
8319
|
const P2 = px(c);
|
|
8305
|
-
if (!B || !
|
|
8320
|
+
if (!B || !P2) return null;
|
|
8306
8321
|
const mx = (P1[0] + P2[0]) / 2;
|
|
8307
8322
|
const my = (P1[1] + P2[1]) / 2;
|
|
8308
8323
|
const dx = P2[0] - B[0];
|
|
@@ -8473,16 +8488,18 @@ var Triangle = class extends SegmentDrawing {
|
|
|
8473
8488
|
geometry(proj) {
|
|
8474
8489
|
const a = this.anchors[0];
|
|
8475
8490
|
const b = this.anchors[1];
|
|
8476
|
-
|
|
8477
|
-
if (!a || !b || !c) return null;
|
|
8491
|
+
if (!a || !b) return null;
|
|
8478
8492
|
const px = (p) => {
|
|
8479
8493
|
const y = proj.yOf(p.price, this.paneId);
|
|
8480
8494
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
8481
8495
|
};
|
|
8482
8496
|
const A = px(a);
|
|
8483
8497
|
const B = px(b);
|
|
8498
|
+
if (!A || !B) return null;
|
|
8499
|
+
const c = this.anchors[2];
|
|
8500
|
+
if (!c) return { segments: [[A[0], A[1], B[0], B[1]]], fill: null };
|
|
8484
8501
|
const C = px(c);
|
|
8485
|
-
if (!
|
|
8502
|
+
if (!C) return null;
|
|
8486
8503
|
return {
|
|
8487
8504
|
segments: [
|
|
8488
8505
|
[A[0], A[1], B[0], B[1]],
|
|
@@ -18344,6 +18361,13 @@ function resolveAnimations(animations) {
|
|
|
18344
18361
|
function isLineLikeSeries(spec) {
|
|
18345
18362
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
18346
18363
|
}
|
|
18364
|
+
function seriesShownOn(spec, surface) {
|
|
18365
|
+
if (spec.display) return spec.display[surface] !== false;
|
|
18366
|
+
return spec.visible !== false;
|
|
18367
|
+
}
|
|
18368
|
+
function seriesInScale(spec) {
|
|
18369
|
+
return seriesShownOn(spec, "pane") || seriesShownOn(spec, "priceScale");
|
|
18370
|
+
}
|
|
18347
18371
|
|
|
18348
18372
|
// src/core/engine/IndicatorRegistry.ts
|
|
18349
18373
|
var IndicatorRegistry = class {
|
|
@@ -18373,6 +18397,9 @@ var IndicatorRegistry = class {
|
|
|
18373
18397
|
};
|
|
18374
18398
|
|
|
18375
18399
|
// src/core/native-indicators/NativeIndicator.ts
|
|
18400
|
+
function nativeInstanceChannel(type, instanceId) {
|
|
18401
|
+
return `${type}#${instanceId}`;
|
|
18402
|
+
}
|
|
18376
18403
|
var REGISTRY2 = /* @__PURE__ */ new Map();
|
|
18377
18404
|
function registerNativeIndicator(descriptor) {
|
|
18378
18405
|
REGISTRY2.set(descriptor.type, descriptor);
|
|
@@ -18442,6 +18469,9 @@ var IndicatorHandleImpl = class {
|
|
|
18442
18469
|
setProps(values) {
|
|
18443
18470
|
this.controller.applyProps(this.id, values);
|
|
18444
18471
|
}
|
|
18472
|
+
updateCode(source) {
|
|
18473
|
+
this.controller.updateCode(this.id, source);
|
|
18474
|
+
}
|
|
18445
18475
|
setVisible(visible) {
|
|
18446
18476
|
this.controller.setVisible(this.id, visible);
|
|
18447
18477
|
}
|
|
@@ -18464,6 +18494,10 @@ var IndicatorHandleImpl = class {
|
|
|
18464
18494
|
setPropsSchema(schema) {
|
|
18465
18495
|
this.propsSchema = schema;
|
|
18466
18496
|
}
|
|
18497
|
+
/** Sync the public `source` once a code update has been prepared and taken over. */
|
|
18498
|
+
setSource(source) {
|
|
18499
|
+
this.source = source;
|
|
18500
|
+
}
|
|
18467
18501
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
18468
18502
|
setVisibleState(visible) {
|
|
18469
18503
|
this.visibleState = visible;
|
|
@@ -19284,6 +19318,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19284
19318
|
if (identityChanged) {
|
|
19285
19319
|
this.beginLoad(false);
|
|
19286
19320
|
this.setBarSeries([], { clearing: true });
|
|
19321
|
+
this.blankIndicatorVisuals();
|
|
19287
19322
|
if (this.activeEngineStyle) {
|
|
19288
19323
|
this.typeEngines.get(this.activeEngineStyle)?.suspend();
|
|
19289
19324
|
this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
|
|
@@ -19326,7 +19361,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19326
19361
|
restartNativeIndicators() {
|
|
19327
19362
|
for (const record of this.registry.all()) {
|
|
19328
19363
|
if (!record.native) continue;
|
|
19329
|
-
record.native.instance.stop();
|
|
19364
|
+
if (record.native.started) record.native.instance.stop();
|
|
19330
19365
|
record.native.instance = record.native.descriptor.create();
|
|
19331
19366
|
record.native.started = false;
|
|
19332
19367
|
record.pendingStructural = true;
|
|
@@ -19534,6 +19569,23 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19534
19569
|
this.renderer.setBars(this.bars, { preserveView: true });
|
|
19535
19570
|
this.reexecuteIndicators();
|
|
19536
19571
|
}
|
|
19572
|
+
/**
|
|
19573
|
+
* Blank every mounted indicator's painted output — series data, fills, backgrounds,
|
|
19574
|
+
* price lines, drawings, bar colors, trades — while keeping the mount (legend row,
|
|
19575
|
+
* pane, settings) intact, via the same idempotent-by-id remount an input edit uses.
|
|
19576
|
+
* Called on a market identity switch: the fresh models arrive only after the new
|
|
19577
|
+
* bars load, so nothing blanked here is ever restored — each consumer's re-run
|
|
19578
|
+
* remounts a full model over it.
|
|
19579
|
+
*/
|
|
19580
|
+
blankIndicatorVisuals() {
|
|
19581
|
+
for (const record of this.registry.all()) {
|
|
19582
|
+
if (record.hidden || !record.renderHandle || !record.model) continue;
|
|
19583
|
+
record.model = blankedModel(record.model);
|
|
19584
|
+
record.renderHandle = this.renderer.mountIndicator(record.model);
|
|
19585
|
+
record.pendingStructural = true;
|
|
19586
|
+
this.setLoading(record, true);
|
|
19587
|
+
}
|
|
19588
|
+
}
|
|
19537
19589
|
/** Stop + re-run every visible Pine indicator (its input bars changed wholesale). */
|
|
19538
19590
|
reexecuteIndicators() {
|
|
19539
19591
|
for (const record of this.registry.all()) {
|
|
@@ -19778,6 +19830,61 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19778
19830
|
record.session.update(record.inputValues, record.propValues);
|
|
19779
19831
|
this.events.emit("indicator:inputs", { id });
|
|
19780
19832
|
}
|
|
19833
|
+
/**
|
|
19834
|
+
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
19835
|
+
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
19836
|
+
* computing until the new source has compiled, so a broken edit costs the user
|
|
19837
|
+
* nothing but an `error` event. Natives have no script — warn and leave them alone.
|
|
19838
|
+
*/
|
|
19839
|
+
updateCode(id, source) {
|
|
19840
|
+
const record = this.registry.get(id);
|
|
19841
|
+
const handle = this.handles.get(id);
|
|
19842
|
+
if (!record || !handle) return;
|
|
19843
|
+
if (record.native) {
|
|
19844
|
+
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
19845
|
+
return;
|
|
19846
|
+
}
|
|
19847
|
+
if (source === record.source && record.pendingSource === void 0) return;
|
|
19848
|
+
record.pendingSource = source;
|
|
19849
|
+
void this.swapSource(id, source, handle);
|
|
19850
|
+
}
|
|
19851
|
+
async swapSource(id, source, handle) {
|
|
19852
|
+
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
19853
|
+
let prepared;
|
|
19854
|
+
try {
|
|
19855
|
+
prepared = await engine.prepare(source, id);
|
|
19856
|
+
} catch (err) {
|
|
19857
|
+
const record2 = this.registry.get(id);
|
|
19858
|
+
if (record2?.pendingSource === source) {
|
|
19859
|
+
record2.pendingSource = void 0;
|
|
19860
|
+
this.fail(id, handle, err);
|
|
19861
|
+
}
|
|
19862
|
+
return;
|
|
19863
|
+
}
|
|
19864
|
+
const record = this.registry.get(id);
|
|
19865
|
+
if (!record || record.pendingSource !== source) return;
|
|
19866
|
+
record.pendingSource = void 0;
|
|
19867
|
+
record.source = source;
|
|
19868
|
+
record.engine = engine;
|
|
19869
|
+
record.prepared = prepared;
|
|
19870
|
+
handle.setSource(source);
|
|
19871
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
19872
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
19873
|
+
handle.setSchema(prepared.inputs);
|
|
19874
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
19875
|
+
record.session?.stop();
|
|
19876
|
+
record.session = void 0;
|
|
19877
|
+
record.pendingStructural = true;
|
|
19878
|
+
record.pendingCause = "code";
|
|
19879
|
+
if (record.hidden) return;
|
|
19880
|
+
if (record.renderHandle) {
|
|
19881
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
19882
|
+
this.setLoading(record, true);
|
|
19883
|
+
} else {
|
|
19884
|
+
this.mountLoadingPlaceholder(id, record);
|
|
19885
|
+
}
|
|
19886
|
+
this.executeIndicator(id, handle);
|
|
19887
|
+
}
|
|
19781
19888
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
19782
19889
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
19783
19890
|
listIndicators() {
|
|
@@ -19792,7 +19899,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19792
19899
|
const record = this.registry.remove(id);
|
|
19793
19900
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
19794
19901
|
record?.session?.stop();
|
|
19795
|
-
record?.native?.instance.stop();
|
|
19902
|
+
if (record?.native?.started) record.native.instance.stop();
|
|
19796
19903
|
this.handles.delete(id);
|
|
19797
19904
|
if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
|
|
19798
19905
|
const paneId = record?.model?.paneId;
|
|
@@ -19818,7 +19925,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19818
19925
|
if (!visible) {
|
|
19819
19926
|
record.session?.stop();
|
|
19820
19927
|
record.session = void 0;
|
|
19821
|
-
record.native?.instance.suspend();
|
|
19928
|
+
if (record.native?.started) record.native.instance.suspend();
|
|
19822
19929
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
19823
19930
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
19824
19931
|
} else {
|
|
@@ -19899,7 +20006,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19899
20006
|
this.paneActionUnsub?.();
|
|
19900
20007
|
for (const record of this.registry.all()) {
|
|
19901
20008
|
record.session?.stop();
|
|
19902
|
-
record.native?.instance.stop();
|
|
20009
|
+
if (record.native?.started) record.native.instance.stop();
|
|
19903
20010
|
}
|
|
19904
20011
|
for (const engine of this.typeEngines.values()) engine.stop();
|
|
19905
20012
|
this.typeEngines.clear();
|
|
@@ -19923,6 +20030,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19923
20030
|
const engine = this.engineFor(options.language);
|
|
19924
20031
|
record.engine = engine;
|
|
19925
20032
|
const prepared = await engine.prepare(source, id);
|
|
20033
|
+
if (record.source !== source) return;
|
|
19926
20034
|
record.prepared = prepared;
|
|
19927
20035
|
const defaults2 = {};
|
|
19928
20036
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
@@ -19993,7 +20101,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19993
20101
|
await this.readyPromise;
|
|
19994
20102
|
const record = this.registry.get(id);
|
|
19995
20103
|
if (!record?.native || record.hidden) return;
|
|
20104
|
+
const channel = this.nativeChannel(record);
|
|
19996
20105
|
const ctx = {
|
|
20106
|
+
id,
|
|
19997
20107
|
symbol: this.config.market.symbol ?? "TEST",
|
|
19998
20108
|
timeframe: this.config.market.timeframe ?? "60",
|
|
19999
20109
|
live: this.config.live,
|
|
@@ -20001,7 +20111,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20001
20111
|
bars: () => this.bars,
|
|
20002
20112
|
data: this.dataControl,
|
|
20003
20113
|
emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
|
|
20004
|
-
pushData: (data) => this.renderer.setNativeData?.(
|
|
20114
|
+
pushData: (data) => this.renderer.setNativeData?.(channel, data),
|
|
20005
20115
|
setStatus: (status) => {
|
|
20006
20116
|
if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
|
|
20007
20117
|
}
|
|
@@ -20012,16 +20122,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20012
20122
|
this.fail(id, handle, err);
|
|
20013
20123
|
}
|
|
20014
20124
|
}
|
|
20125
|
+
/** The renderer-layer channel a native instance's `pushData` lands on: the type itself
|
|
20126
|
+
* for a single-instance type (the layer id doubles as the channel), a per-instance
|
|
20127
|
+
* channel for a `multiInstance` type — otherwise every instance would overwrite the
|
|
20128
|
+
* one layer. Stamped on the model so the renderer mounts a dedicated layer for it. */
|
|
20129
|
+
nativeChannel(record) {
|
|
20130
|
+
const { type, descriptor } = record.native;
|
|
20131
|
+
return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
|
|
20132
|
+
}
|
|
20015
20133
|
/** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
|
|
20016
20134
|
buildNativeModel(record, out) {
|
|
20017
20135
|
const d = record.native.descriptor;
|
|
20136
|
+
const type = record.native.type;
|
|
20137
|
+
const channel = this.nativeChannel(record);
|
|
20018
20138
|
return {
|
|
20019
20139
|
id: record.id,
|
|
20020
20140
|
title: record.title,
|
|
20021
20141
|
...d.shortTitle ? { shorttitle: d.shortTitle } : {},
|
|
20022
20142
|
overlay: d.overlay,
|
|
20023
20143
|
paneHint: d.paneHint,
|
|
20024
|
-
native: { type
|
|
20144
|
+
native: { type, ...channel !== type ? { channel } : {} },
|
|
20025
20145
|
...d.legend === false ? { legend: false } : {},
|
|
20026
20146
|
...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
|
|
20027
20147
|
series: out.series ?? [],
|
|
@@ -20311,6 +20431,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20311
20431
|
applyModel(id, model) {
|
|
20312
20432
|
const record = this.registry.get(id);
|
|
20313
20433
|
if (!record || record.hidden) return true;
|
|
20434
|
+
if (this.switchingMarket) return false;
|
|
20314
20435
|
if (record.loading && this.bars.length === 0 && !_EngineOrchestrator.modelHasOutput(model)) return false;
|
|
20315
20436
|
const handle = this.handles.get(id);
|
|
20316
20437
|
if (!record.renderHandle) {
|
|
@@ -20328,7 +20449,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20328
20449
|
if (record.loading && !record.paneLocked) {
|
|
20329
20450
|
const routed = this.routePane(id, model, record.options ?? {});
|
|
20330
20451
|
if (routed !== paneId) {
|
|
20331
|
-
if (paneId !== "price") {
|
|
20452
|
+
if (paneId !== "price" && !this.registry.all().some((r) => r.id !== id && r.model?.paneId === paneId)) {
|
|
20332
20453
|
this.renderer.removePane(paneId);
|
|
20333
20454
|
this.forgetPane(paneId);
|
|
20334
20455
|
}
|
|
@@ -20457,6 +20578,34 @@ function yieldToPaint() {
|
|
|
20457
20578
|
}
|
|
20458
20579
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
20459
20580
|
}
|
|
20581
|
+
function valuesOnSchema(schema, previous) {
|
|
20582
|
+
const out = {};
|
|
20583
|
+
const declared = /* @__PURE__ */ new Set();
|
|
20584
|
+
for (const s of schema) {
|
|
20585
|
+
out[s.key] = s.defval;
|
|
20586
|
+
declared.add(s.key);
|
|
20587
|
+
declared.add(s.title);
|
|
20588
|
+
}
|
|
20589
|
+
for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
|
|
20590
|
+
return out;
|
|
20591
|
+
}
|
|
20592
|
+
function blankedModel(model) {
|
|
20593
|
+
return {
|
|
20594
|
+
...model,
|
|
20595
|
+
series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
|
|
20596
|
+
fills: [],
|
|
20597
|
+
backgrounds: [],
|
|
20598
|
+
priceLines: [],
|
|
20599
|
+
lines: [],
|
|
20600
|
+
boxes: [],
|
|
20601
|
+
labels: [],
|
|
20602
|
+
polylines: [],
|
|
20603
|
+
linefills: [],
|
|
20604
|
+
tables: [],
|
|
20605
|
+
barColors: [],
|
|
20606
|
+
trades: []
|
|
20607
|
+
};
|
|
20608
|
+
}
|
|
20460
20609
|
function modelToValuePatch(model) {
|
|
20461
20610
|
const series = [];
|
|
20462
20611
|
let from = Number.POSITIVE_INFINITY;
|
|
@@ -23216,33 +23365,32 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
23216
23365
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
23217
23366
|
};
|
|
23218
23367
|
}
|
|
23219
|
-
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
23368
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf, colorOf2) {
|
|
23220
23369
|
const out = [];
|
|
23221
23370
|
let col = NaN;
|
|
23222
|
-
let
|
|
23371
|
+
let runs = [];
|
|
23223
23372
|
const flush = () => {
|
|
23224
|
-
if (
|
|
23225
|
-
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
prev2.headTime = next.headTime;
|
|
23234
|
-
prev2.open = next.open;
|
|
23235
|
-
}
|
|
23236
|
-
if (next.lastIdx > prev2.lastIdx) {
|
|
23237
|
-
prev2.lastIdx = next.lastIdx;
|
|
23238
|
-
prev2.close = next.close;
|
|
23373
|
+
if (runs.length === 0) return;
|
|
23374
|
+
runs.sort((a, b) => a.lo - b.lo);
|
|
23375
|
+
const merged = [];
|
|
23376
|
+
for (const next of runs) {
|
|
23377
|
+
let prev2;
|
|
23378
|
+
for (let k = merged.length - 1; k >= 0; k -= 1) {
|
|
23379
|
+
if (merged[k].color === next.color) {
|
|
23380
|
+
prev2 = merged[k];
|
|
23381
|
+
break;
|
|
23239
23382
|
}
|
|
23383
|
+
}
|
|
23384
|
+
if (prev2 && Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
23385
|
+
prev2.hi = next.hi;
|
|
23386
|
+
if (next.lastIdx > prev2.lastIdx) prev2.lastIdx = next.lastIdx;
|
|
23240
23387
|
} else {
|
|
23241
23388
|
merged.push(next);
|
|
23242
23389
|
}
|
|
23243
23390
|
}
|
|
23244
|
-
|
|
23245
|
-
|
|
23391
|
+
merged.sort((a, b) => a.lastIdx - b.lastIdx);
|
|
23392
|
+
for (const r of merged) out.push({ x: col, hi: r.hi, lo: r.lo, color: r.color });
|
|
23393
|
+
runs = [];
|
|
23246
23394
|
};
|
|
23247
23395
|
for (let i = i0; i <= i1; i += 1) {
|
|
23248
23396
|
const b = bars[i];
|
|
@@ -23252,26 +23400,17 @@ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
|
23252
23400
|
flush();
|
|
23253
23401
|
col = x;
|
|
23254
23402
|
}
|
|
23403
|
+
const color = colorOf2(b);
|
|
23255
23404
|
let lo = b.low;
|
|
23256
23405
|
let hi = b.high;
|
|
23257
|
-
let
|
|
23258
|
-
|
|
23259
|
-
|
|
23260
|
-
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
|
|
23264
|
-
|
|
23265
|
-
if (iv.headIdx < headIdx) {
|
|
23266
|
-
headIdx = iv.headIdx;
|
|
23267
|
-
headTime = iv.headTime;
|
|
23268
|
-
open2 = iv.open;
|
|
23269
|
-
}
|
|
23270
|
-
intervals.splice(k, 1);
|
|
23271
|
-
}
|
|
23272
|
-
let insertAt = 0;
|
|
23273
|
-
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
23274
|
-
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
23406
|
+
for (let k = runs.length - 1; k >= 0; k -= 1) {
|
|
23407
|
+
const r = runs[k];
|
|
23408
|
+
if (r.color !== color || r.lo > hi || r.hi < lo) continue;
|
|
23409
|
+
if (r.lo < lo) lo = r.lo;
|
|
23410
|
+
if (r.hi > hi) hi = r.hi;
|
|
23411
|
+
runs.splice(k, 1);
|
|
23412
|
+
}
|
|
23413
|
+
runs.push({ lo, hi, color, lastIdx: i });
|
|
23275
23414
|
}
|
|
23276
23415
|
flush();
|
|
23277
23416
|
return out;
|
|
@@ -23942,7 +24081,7 @@ var WebGL2Backend = class {
|
|
|
23942
24081
|
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
23943
24082
|
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
23944
24083
|
const emitOne = (s, off) => {
|
|
23945
|
-
if (!isLineLikeSeries(s) || s
|
|
24084
|
+
if (!isLineLikeSeries(s) || !seriesShownOn(s, "pane")) return;
|
|
23946
24085
|
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
23947
24086
|
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
23948
24087
|
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
@@ -24053,7 +24192,7 @@ var WebGL2Backend = class {
|
|
|
24053
24192
|
const spacing = coords.bodySpacing();
|
|
24054
24193
|
const tier = candleTier(spacing);
|
|
24055
24194
|
if (tier === "aggregate") {
|
|
24056
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24195
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => barColors.get(bar.time) ?? (bar.close >= bar.open ? up : down));
|
|
24057
24196
|
return;
|
|
24058
24197
|
}
|
|
24059
24198
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -24194,14 +24333,17 @@ var WebGL2Backend = class {
|
|
|
24194
24333
|
const spacing = coords.bodySpacing();
|
|
24195
24334
|
const tier = candleTier(spacing);
|
|
24196
24335
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
24336
|
+
const up = paint.up;
|
|
24337
|
+
const down = paint.down;
|
|
24338
|
+
const cs = paint.candle;
|
|
24197
24339
|
if (tier === "aggregate") {
|
|
24198
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24340
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => {
|
|
24341
|
+
const isUp = bar.close >= bar.open;
|
|
24342
|
+
return (isUp ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(bar.time) ?? (isUp ? up : down);
|
|
24343
|
+
});
|
|
24199
24344
|
return;
|
|
24200
24345
|
}
|
|
24201
24346
|
const drawBody = tier === "full";
|
|
24202
|
-
const up = paint.up;
|
|
24203
|
-
const down = paint.down;
|
|
24204
|
-
const cs = paint.candle;
|
|
24205
24347
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
24206
24348
|
for (let i = i0; i <= i1; i += 1) {
|
|
24207
24349
|
const bar = bars[i];
|
|
@@ -24250,24 +24392,26 @@ var WebGL2Backend = class {
|
|
|
24250
24392
|
/**
|
|
24251
24393
|
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
24252
24394
|
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
24253
|
-
* coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
24254
|
-
* column stays a void
|
|
24255
|
-
*
|
|
24395
|
+
* SAME-COLOR coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
24396
|
+
* inside the column stays a void and every bar's extent keeps its own color. Draw
|
|
24397
|
+
* cost stays bounded by screen width, not bar count. `colorOf` is the calling
|
|
24398
|
+
* style's per-bar paint rule (candles and OHLC bars resolve theirs differently).
|
|
24256
24399
|
*/
|
|
24257
|
-
emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24400
|
+
emitCandlesAggregated(b, bars, i0, i1, coords, pane, colorOf2) {
|
|
24258
24401
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
24259
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
24260
|
-
const c = parseColor(
|
|
24402
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
24403
|
+
const c = parseColor(s.color);
|
|
24261
24404
|
const hY = yOf(s.hi);
|
|
24262
24405
|
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
24263
24406
|
}
|
|
24264
24407
|
}
|
|
24265
24408
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
24409
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
24266
24410
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
24267
24411
|
this.emitPlotCandles(b, spec, pane, coords, i0, i1, theme, off);
|
|
24268
24412
|
return;
|
|
24269
24413
|
}
|
|
24270
|
-
if (!isLineLikeSeries(spec)
|
|
24414
|
+
if (!isLineLikeSeries(spec)) return;
|
|
24271
24415
|
switch (spec.kind) {
|
|
24272
24416
|
case "histogram":
|
|
24273
24417
|
case "columns":
|
|
@@ -25931,7 +26075,7 @@ var Canvas2dBackend = class {
|
|
|
25931
26075
|
const spacing = coords.bodySpacing();
|
|
25932
26076
|
const tier = candleTier(spacing);
|
|
25933
26077
|
if (tier === "aggregate") {
|
|
25934
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26078
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => barColors.get(b.time) ?? (b.close >= b.open ? up : down));
|
|
25935
26079
|
return;
|
|
25936
26080
|
}
|
|
25937
26081
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -26107,12 +26251,15 @@ var Canvas2dBackend = class {
|
|
|
26107
26251
|
const spacing = coords.bodySpacing();
|
|
26108
26252
|
const tier = candleTier(spacing);
|
|
26109
26253
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
26254
|
+
const cs = paint.candle;
|
|
26110
26255
|
if (tier === "aggregate") {
|
|
26111
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26256
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => {
|
|
26257
|
+
const up = b.close >= b.open;
|
|
26258
|
+
return (up ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(b.time) ?? (up ? paint.up : paint.down);
|
|
26259
|
+
});
|
|
26112
26260
|
return;
|
|
26113
26261
|
}
|
|
26114
26262
|
const drawBody = tier === "full";
|
|
26115
|
-
const cs = paint.candle;
|
|
26116
26263
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
26117
26264
|
for (let i = i0; i <= i1; i += 1) {
|
|
26118
26265
|
const b = bars[i];
|
|
@@ -26169,17 +26316,18 @@ var Canvas2dBackend = class {
|
|
|
26169
26316
|
}
|
|
26170
26317
|
/**
|
|
26171
26318
|
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
26172
|
-
* sticks (one per contiguous coverage run — see
|
|
26173
|
-
* so draw cost is bounded by screen width (not
|
|
26174
|
-
*
|
|
26175
|
-
* its
|
|
26319
|
+
* sticks (one per contiguous SAME-COLOR coverage run — see
|
|
26320
|
+
* {@link aggregateCandleColumns}), so draw cost is bounded by screen width (not
|
|
26321
|
+
* bar count) when zoomed far out, a price gap inside the column stays a void, and
|
|
26322
|
+
* every bar's extent keeps its own color. `colorOf` is the calling style's per-bar
|
|
26323
|
+
* paint rule (candles and OHLC bars resolve theirs differently).
|
|
26176
26324
|
*/
|
|
26177
|
-
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26325
|
+
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, colorOf2) {
|
|
26178
26326
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
26179
26327
|
ctx.lineWidth = 1;
|
|
26180
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
26328
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
26181
26329
|
const x = s.x + 0.5;
|
|
26182
|
-
ctx.strokeStyle =
|
|
26330
|
+
ctx.strokeStyle = s.color;
|
|
26183
26331
|
ctx.beginPath();
|
|
26184
26332
|
ctx.moveTo(x, yOf(s.hi));
|
|
26185
26333
|
ctx.lineTo(x, yOf(s.lo));
|
|
@@ -26187,11 +26335,12 @@ var Canvas2dBackend = class {
|
|
|
26187
26335
|
}
|
|
26188
26336
|
}
|
|
26189
26337
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
26338
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
26190
26339
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
26191
26340
|
this.drawPlotCandles(ctx, spec, pane, coords, i0, i1, theme, off);
|
|
26192
26341
|
return;
|
|
26193
26342
|
}
|
|
26194
|
-
if (!isLineLikeSeries(spec)
|
|
26343
|
+
if (!isLineLikeSeries(spec)) return;
|
|
26195
26344
|
switch (spec.kind) {
|
|
26196
26345
|
case "histogram":
|
|
26197
26346
|
case "columns":
|
|
@@ -34417,6 +34566,7 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
34417
34566
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
34418
34567
|
}
|
|
34419
34568
|
function considerSeries(s, i0, i1, off, consider) {
|
|
34569
|
+
if (!seriesInScale(s)) return;
|
|
34420
34570
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
34421
34571
|
for (let i = i0; i <= i1; i += 1) {
|
|
34422
34572
|
const b = s.bars[i - off];
|
|
@@ -34636,6 +34786,22 @@ var MarkPopover = class {
|
|
|
34636
34786
|
this.openKey = null;
|
|
34637
34787
|
/** Bumped per open/close — a lazy content resolving after its popup went away is dropped. */
|
|
34638
34788
|
this.generation = 0;
|
|
34789
|
+
/**
|
|
34790
|
+
* The cluster whose popup was open when the current pointer press began. The anchor is
|
|
34791
|
+
* hit-transparent (the glyph is canvas pixels), so the shell's outside-dismiss closes the
|
|
34792
|
+
* popup on pointerdown; the click the renderer derives from the same pointerup must then
|
|
34793
|
+
* read as "close", not reopen that cluster. Captured on the document ahead of the shell's
|
|
34794
|
+
* (deferred) listener, cleared once the release has dispatched.
|
|
34795
|
+
*/
|
|
34796
|
+
this.pressKey = null;
|
|
34797
|
+
this.onPress = () => {
|
|
34798
|
+
if (this.openKey === null) return;
|
|
34799
|
+
this.pressKey = this.openKey;
|
|
34800
|
+
const doc = this.deps.plot.ownerDocument;
|
|
34801
|
+
const clear = () => void setTimeout(() => this.pressKey = null, 0);
|
|
34802
|
+
doc.addEventListener("pointerup", clear, { capture: true, once: true });
|
|
34803
|
+
doc.addEventListener("pointercancel", clear, { capture: true, once: true });
|
|
34804
|
+
};
|
|
34639
34805
|
const doc = deps.plot.ownerDocument;
|
|
34640
34806
|
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
34641
34807
|
injectStyles(MARKS_STYLE_ID, MARKS_CSS, doc);
|
|
@@ -34643,12 +34809,14 @@ var MarkPopover = class {
|
|
|
34643
34809
|
this.anchor.className = "vela-marks-anchor";
|
|
34644
34810
|
Object.assign(this.anchor.style, { position: "absolute", pointerEvents: "none", left: "0", top: "0", width: "0", height: "0" });
|
|
34645
34811
|
deps.plot.appendChild(this.anchor);
|
|
34812
|
+
doc.addEventListener("pointerdown", this.onPress, true);
|
|
34646
34813
|
}
|
|
34647
34814
|
/** The cluster key the open popup belongs to, or null. */
|
|
34648
34815
|
get key() {
|
|
34649
34816
|
return this.openKey;
|
|
34650
34817
|
}
|
|
34651
34818
|
open(cluster, rect) {
|
|
34819
|
+
if (this.pressKey === cluster.key) return;
|
|
34652
34820
|
this.close();
|
|
34653
34821
|
this.place(rect);
|
|
34654
34822
|
const gen = ++this.generation;
|
|
@@ -34696,6 +34864,7 @@ var MarkPopover = class {
|
|
|
34696
34864
|
}
|
|
34697
34865
|
destroy() {
|
|
34698
34866
|
this.close();
|
|
34867
|
+
this.deps.plot.ownerDocument.removeEventListener("pointerdown", this.onPress, true);
|
|
34699
34868
|
this.anchor.remove();
|
|
34700
34869
|
}
|
|
34701
34870
|
place(rect) {
|
|
@@ -35456,6 +35625,13 @@ var NativeRenderer = class {
|
|
|
35456
35625
|
this.backdropRenderer = new BackdropRenderer();
|
|
35457
35626
|
this.volumeRenderer = new VolumeRenderer();
|
|
35458
35627
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
35628
|
+
/**
|
|
35629
|
+
* The mounted SDK layers. One BASE entry per registered definition (channel = the layer
|
|
35630
|
+
* id, no owner of its own), plus one DEDICATED entry per mounted native indicator whose
|
|
35631
|
+
* model names its own channel (`native.channel` — an instance of a multi-instance type):
|
|
35632
|
+
* same definition, its own canvas, reading that instance's channel and following that
|
|
35633
|
+
* instance's pane and z key. `channel` is unique across the array.
|
|
35634
|
+
*/
|
|
35459
35635
|
this.extLayers = [];
|
|
35460
35636
|
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
35461
35637
|
this.layerOrderSig = "";
|
|
@@ -36601,11 +36777,7 @@ var NativeRenderer = class {
|
|
|
36601
36777
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
36602
36778
|
this.plot = document.createElement("div");
|
|
36603
36779
|
Object.assign(this.plot.style, { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" });
|
|
36604
|
-
this.extLayers = rendererLayers().map((def) => {
|
|
36605
|
-
const canvas = document.createElement("canvas");
|
|
36606
|
-
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
36607
|
-
return { def, instance: def.create(), canvas };
|
|
36608
|
-
});
|
|
36780
|
+
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
36609
36781
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
36610
36782
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
36611
36783
|
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
@@ -36622,6 +36794,7 @@ var NativeRenderer = class {
|
|
|
36622
36794
|
this.volumeRenderer.mount(this.volumeCanvas);
|
|
36623
36795
|
this.vpvrRenderer.mount(this.vpvrCanvas);
|
|
36624
36796
|
for (const l of this.extLayers) l.instance.mount(l.canvas);
|
|
36797
|
+
for (const m of this.scene.indicators.values()) this.ensureInstanceLayer(m);
|
|
36625
36798
|
this.chrome.mount(this.chromeCanvas);
|
|
36626
36799
|
this.crosshairLayer.mount(this.cursorCanvas);
|
|
36627
36800
|
this.coords.setViewport(defaultViewport());
|
|
@@ -37214,6 +37387,7 @@ var NativeRenderer = class {
|
|
|
37214
37387
|
this.refreshAnchorOffset(model);
|
|
37215
37388
|
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
37216
37389
|
else this.scene.assignIndicatorZ(model.id);
|
|
37390
|
+
this.ensureInstanceLayer(model);
|
|
37217
37391
|
if (model.legend !== false) {
|
|
37218
37392
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
37219
37393
|
native: !!model.native,
|
|
@@ -37257,6 +37431,7 @@ var NativeRenderer = class {
|
|
|
37257
37431
|
this.scene.vpvrLayer = null;
|
|
37258
37432
|
}
|
|
37259
37433
|
this.scene.indicators.delete(handle.id);
|
|
37434
|
+
this.dropInstanceLayer(handle.id);
|
|
37260
37435
|
this.scene.forgetIndicatorZ(handle.id);
|
|
37261
37436
|
this.scene.forgetAnchorOffset(handle.id);
|
|
37262
37437
|
this.scene.dropIndicatorScale(handle.id);
|
|
@@ -38050,29 +38225,31 @@ var NativeRenderer = class {
|
|
|
38050
38225
|
up: bar.close >= bar.open
|
|
38051
38226
|
};
|
|
38052
38227
|
}
|
|
38053
|
-
/** One group per indicator (name = indicator title), each with a row per
|
|
38228
|
+
/** One group per indicator (name = indicator title), each with a row per plot shown in the data window. */
|
|
38054
38229
|
dataWindowGroups(idx, pricePane) {
|
|
38055
38230
|
const groups = [];
|
|
38056
38231
|
for (const model of this.scene.indicators.values()) {
|
|
38057
|
-
const rows = this.
|
|
38232
|
+
const rows = this.readoutRowsFor(model, idx, pricePane, "dataWindow");
|
|
38058
38233
|
if (rows.length) groups.push({ name: model.title, rows });
|
|
38059
38234
|
}
|
|
38060
38235
|
return groups;
|
|
38061
38236
|
}
|
|
38062
|
-
/** One indicator's readout at bar `idx
|
|
38063
|
-
|
|
38237
|
+
/** One indicator's readout at bar `idx` for one value surface (the legend or the data window):
|
|
38238
|
+
* a row per plot shown on that surface, formatted on its pane's scale. A plot's `display`
|
|
38239
|
+
* decides per surface — a data-window-only plot has a row here and none in the legend. */
|
|
38240
|
+
readoutRowsFor(model, idx, pricePane, surface) {
|
|
38064
38241
|
if (model.native?.type === "volume") return this.volumeReadoutRows(model, idx);
|
|
38065
38242
|
const pane = (model.paneId ? this.scene.panes.get(model.paneId) : null) ?? pricePane;
|
|
38066
38243
|
const off = this.scene.offsetOf(model.id);
|
|
38067
38244
|
const rows = [];
|
|
38068
38245
|
for (const s of model.series) {
|
|
38246
|
+
if (!seriesShownOn(s, surface)) continue;
|
|
38069
38247
|
let value;
|
|
38070
38248
|
let color;
|
|
38071
38249
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
38072
38250
|
value = s.bars[idx - off]?.close;
|
|
38073
38251
|
color = s.style?.up ?? this.theme.upColor;
|
|
38074
38252
|
} else if (isLineLikeSeries(s)) {
|
|
38075
|
-
if (s.visible === false) continue;
|
|
38076
38253
|
value = s.points[idx - off]?.value;
|
|
38077
38254
|
color = s.points[idx - off]?.color ?? s.style.color;
|
|
38078
38255
|
} else {
|
|
@@ -38095,9 +38272,10 @@ var NativeRenderer = class {
|
|
|
38095
38272
|
const color = bar.close >= bar.open ? cfg?.upColor ?? this.theme.upColor : cfg?.downColor ?? this.theme.downColor;
|
|
38096
38273
|
return [{ label: model.title, value: formatVolume(vol), color }];
|
|
38097
38274
|
}
|
|
38098
|
-
/** Refresh the plot values beside every legend title — the same
|
|
38099
|
-
*
|
|
38100
|
-
*
|
|
38275
|
+
/** Refresh the plot values beside every legend title — the same bar the data window reads
|
|
38276
|
+
* (crosshair bar, else the latest bar), pushed per paint, filtered to the plots whose
|
|
38277
|
+
* `display` includes the legend. A hidden indicator has no scene model, so its row is
|
|
38278
|
+
* absent from the map and its readout clears. */
|
|
38101
38279
|
updateLegendValues() {
|
|
38102
38280
|
if (!this.inputsUI) return;
|
|
38103
38281
|
const n = this.bars.length;
|
|
@@ -38106,7 +38284,7 @@ var NativeRenderer = class {
|
|
|
38106
38284
|
const idx = this.hoverLogical != null ? this.hoverLogical : n - 1;
|
|
38107
38285
|
const pricePane = this.dataWindowPricePane();
|
|
38108
38286
|
for (const model of this.scene.indicators.values()) {
|
|
38109
|
-
values.set(model.id, this.
|
|
38287
|
+
values.set(model.id, this.readoutRowsFor(model, idx, pricePane, "legend").map((r) => ({ value: r.value, color: r.color })));
|
|
38110
38288
|
}
|
|
38111
38289
|
}
|
|
38112
38290
|
this.inputsUI.setPlotValues(values);
|
|
@@ -38132,9 +38310,9 @@ var NativeRenderer = class {
|
|
|
38132
38310
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
38133
38311
|
for (const l of this.extLayers) {
|
|
38134
38312
|
if (!l.def.repaintOnCursor) continue;
|
|
38135
|
-
const lp = this.layerPane(l
|
|
38313
|
+
const lp = this.layerPane(l) ?? pane;
|
|
38136
38314
|
if (lp.collapsed) continue;
|
|
38137
|
-
l.instance.render(this.extLayerArgs(l
|
|
38315
|
+
l.instance.render(this.extLayerArgs(l, lp.scale, lp.bounds, nowMs));
|
|
38138
38316
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
38139
38317
|
}
|
|
38140
38318
|
}
|
|
@@ -38143,13 +38321,15 @@ var NativeRenderer = class {
|
|
|
38143
38321
|
if (canvas.width === 0 || canvas.height === 0) return;
|
|
38144
38322
|
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
38145
38323
|
}
|
|
38146
|
-
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
38147
|
-
|
|
38324
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
38325
|
+
* Data + pending come from the entry's channel (per instance for a dedicated entry);
|
|
38326
|
+
* the settings bag is the TYPE's — a chart type's dialog values, one bag per type. */
|
|
38327
|
+
extLayerArgs(l, scale, bounds, nowMs) {
|
|
38148
38328
|
return {
|
|
38149
38329
|
bars: this.scene.bars,
|
|
38150
|
-
data: this.scene.nativeData.get(
|
|
38151
|
-
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
38152
|
-
pending: this.scene.nativePending.get(
|
|
38330
|
+
data: this.scene.nativeData.get(l.channel),
|
|
38331
|
+
settings: this.scene.nativeData.get(`${l.def.id}-settings`) ?? {},
|
|
38332
|
+
pending: this.scene.nativePending.get(l.channel) ?? [],
|
|
38153
38333
|
coords: this.coords,
|
|
38154
38334
|
scale,
|
|
38155
38335
|
bounds,
|
|
@@ -38192,12 +38372,12 @@ var NativeRenderer = class {
|
|
|
38192
38372
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
38193
38373
|
let folded = null;
|
|
38194
38374
|
for (const l of this.extLayers) {
|
|
38195
|
-
const lp = this.layerPane(l
|
|
38375
|
+
const lp = this.layerPane(l) ?? pane;
|
|
38196
38376
|
if (lp.collapsed) {
|
|
38197
38377
|
this.clearLayerCanvas(l.canvas);
|
|
38198
38378
|
continue;
|
|
38199
38379
|
}
|
|
38200
|
-
const args = this.extLayerArgs(l
|
|
38380
|
+
const args = this.extLayerArgs(l, lp.scale, lp.bounds, nowMs);
|
|
38201
38381
|
l.instance.render(args);
|
|
38202
38382
|
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
38203
38383
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
@@ -38480,19 +38660,60 @@ var NativeRenderer = class {
|
|
|
38480
38660
|
}
|
|
38481
38661
|
return null;
|
|
38482
38662
|
}
|
|
38483
|
-
/**
|
|
38484
|
-
|
|
38485
|
-
|
|
38486
|
-
|
|
38487
|
-
|
|
38663
|
+
/** One transparent, pointer-transparent SDK layer canvas covering the plot. */
|
|
38664
|
+
createLayerCanvas() {
|
|
38665
|
+
const canvas = document.createElement("canvas");
|
|
38666
|
+
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38667
|
+
return canvas;
|
|
38668
|
+
}
|
|
38669
|
+
/**
|
|
38670
|
+
* Mount the DEDICATED layer of a native indicator that names its own channel (an
|
|
38671
|
+
* instance of a multi-instance type painting through a registered layer): a fresh
|
|
38672
|
+
* instance of the layer's definition on its own canvas, reading that channel. Idempotent
|
|
38673
|
+
* — a show after hide re-mounts the model, and the layer (kept across the hide, like the
|
|
38674
|
+
* z key) is simply reused. Skipped while the renderer itself is unmounted (`mount()`
|
|
38675
|
+
* catches up from the scene).
|
|
38676
|
+
*/
|
|
38677
|
+
ensureInstanceLayer(model) {
|
|
38678
|
+
const channel = model.native?.channel;
|
|
38679
|
+
if (!channel || !this.plot) return;
|
|
38680
|
+
if (this.extLayers.some((l) => l.owner === model.id)) return;
|
|
38681
|
+
const base = this.extLayers.find((l) => l.owner === null && l.def.id === model.native.type);
|
|
38682
|
+
if (!base) return;
|
|
38683
|
+
const canvas = this.createLayerCanvas();
|
|
38684
|
+
canvas.width = this.dataCanvas.width;
|
|
38685
|
+
canvas.height = this.dataCanvas.height;
|
|
38686
|
+
canvas.style.width = this.dataCanvas.style.width || "100%";
|
|
38687
|
+
canvas.style.height = this.dataCanvas.style.height || "100%";
|
|
38688
|
+
const entry = { def: base.def, instance: base.def.create(), canvas, channel, owner: model.id };
|
|
38689
|
+
this.extLayers.push(entry);
|
|
38690
|
+
this.plot.insertBefore(canvas, this.chromeCanvas);
|
|
38691
|
+
entry.instance.mount(canvas);
|
|
38692
|
+
}
|
|
38693
|
+
/** Tear down the dedicated layer of a removed indicator (a hide keeps it). */
|
|
38694
|
+
dropInstanceLayer(indicatorId) {
|
|
38695
|
+
const i = this.extLayers.findIndex((l2) => l2.owner === indicatorId);
|
|
38696
|
+
if (i < 0) return;
|
|
38697
|
+
const [l] = this.extLayers.splice(i, 1);
|
|
38698
|
+
l.instance.destroy?.();
|
|
38699
|
+
l.canvas.remove();
|
|
38700
|
+
}
|
|
38701
|
+
/** The mounted native indicator that OWNS an SDK layer entry. A dedicated entry is
|
|
38702
|
+
* owned by the instance it was mounted for; the base entry by the mounted indicator
|
|
38703
|
+
* whose type equals the layer id and that has no channel of its own (the id doubles
|
|
38704
|
+
* as the data channel, so the pairing is the SDK's own contract). Null for chart-type
|
|
38705
|
+
* channels and while the owner is hidden (a hidden indicator leaves the scene; its
|
|
38706
|
+
* cleared data channel paints nothing anyway). */
|
|
38707
|
+
layerOwner(l) {
|
|
38708
|
+
if (l.owner !== null) return this.scene.indicators.get(l.owner) ?? null;
|
|
38488
38709
|
for (const m of this.scene.indicators.values()) {
|
|
38489
|
-
if (m.native?.type ===
|
|
38710
|
+
if (m.native?.type === l.def.id && !m.native.channel) return m;
|
|
38490
38711
|
}
|
|
38491
38712
|
return null;
|
|
38492
38713
|
}
|
|
38493
38714
|
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
38494
|
-
layerPane(
|
|
38495
|
-
const owner = this.layerOwner(
|
|
38715
|
+
layerPane(l) {
|
|
38716
|
+
const owner = this.layerOwner(l);
|
|
38496
38717
|
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID2 : PRICE_PANE_ID2;
|
|
38497
38718
|
return this.scene.panes.get(paneId) ?? null;
|
|
38498
38719
|
}
|
|
@@ -38500,12 +38721,12 @@ var NativeRenderer = class {
|
|
|
38500
38721
|
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
38501
38722
|
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
38502
38723
|
orderedLayerCanvases() {
|
|
38503
|
-
const byId = new Map(this.extLayers.map((l) => [l.
|
|
38724
|
+
const byId = new Map(this.extLayers.map((l) => [l.channel, l.canvas]));
|
|
38504
38725
|
const { below, above } = stackLayers(
|
|
38505
38726
|
this.extLayers.map((l) => {
|
|
38506
|
-
const owner = this.layerOwner(l
|
|
38727
|
+
const owner = this.layerOwner(l);
|
|
38507
38728
|
return {
|
|
38508
|
-
id: l.
|
|
38729
|
+
id: l.channel,
|
|
38509
38730
|
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
38510
38731
|
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
38511
38732
|
};
|
|
@@ -38527,7 +38748,7 @@ var NativeRenderer = class {
|
|
|
38527
38748
|
syncLayerCanvasOrder() {
|
|
38528
38749
|
if (this.extLayers.length === 0 || !this.plot) return;
|
|
38529
38750
|
const { below, above } = this.orderedLayerCanvases();
|
|
38530
|
-
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).
|
|
38751
|
+
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(",");
|
|
38531
38752
|
if (sig === this.layerOrderSig) return;
|
|
38532
38753
|
this.layerOrderSig = sig;
|
|
38533
38754
|
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
@@ -43691,7 +43912,8 @@ var ChartCell = class {
|
|
|
43691
43912
|
this.statusline?.setFitMode(on);
|
|
43692
43913
|
}
|
|
43693
43914
|
/** The workspace shell keeps the app theme; the cell host's tokens re-derive from
|
|
43694
|
-
* the LIVE plot surface (
|
|
43915
|
+
* the LIVE plot surface for overlay ink (statusline, watermark). Menus portal to
|
|
43916
|
+
* `.vela-ui` and keep the app-theme surface (see {@link applyPlotOverlayTokens}). */
|
|
43695
43917
|
syncPlotOverlayTokens() {
|
|
43696
43918
|
applyPlotOverlayTokens(this.host, this.appTheme, this.inner?.renderer.getConfig() ?? null);
|
|
43697
43919
|
}
|
|
@@ -44581,7 +44803,21 @@ var VelaWorkspace = class {
|
|
|
44581
44803
|
/** Live sync configuration (mutable copy of the option). */
|
|
44582
44804
|
this.syncOpts = {};
|
|
44583
44805
|
this.stateTimer = null;
|
|
44584
|
-
|
|
44806
|
+
/** Re-entrance guard for {@link flushPendingState}: a `state:changed` subscriber that
|
|
44807
|
+
* calls back into the workspace (`destroy()` on save is the obvious one) must not
|
|
44808
|
+
* re-enter the flush and recurse. */
|
|
44809
|
+
this.emitting = false;
|
|
44810
|
+
/** False until the constructor finishes. Boot is not an edit: seeding the sync
|
|
44811
|
+
* settings marks state dirty, so without this a workspace mounted and torn down
|
|
44812
|
+
* inside the debounce window would flush and tell the host the user had changed
|
|
44813
|
+
* something — a save of the default document over whatever was stored. */
|
|
44814
|
+
this.booted = false;
|
|
44815
|
+
/** A page unload gets the same flush as a teardown — the pending edit reaches the
|
|
44816
|
+
* host, the timer is cleared, and a `destroy()` that follows adds no second write. */
|
|
44817
|
+
this.onUnload = () => {
|
|
44818
|
+
this.flushPendingState();
|
|
44819
|
+
this.persistNow();
|
|
44820
|
+
};
|
|
44585
44821
|
/** Re-entrance guard around one propagation tick: followers' synchronous echoes
|
|
44586
44822
|
* (their setVisibleRange re-emits viewport:changed) must not re-propagate. */
|
|
44587
44823
|
this.syncBusy = false;
|
|
@@ -44895,6 +45131,7 @@ var VelaWorkspace = class {
|
|
|
44895
45131
|
}
|
|
44896
45132
|
this.mountAttachments();
|
|
44897
45133
|
this.restoreGlobalExt();
|
|
45134
|
+
this.booted = true;
|
|
44898
45135
|
}
|
|
44899
45136
|
// ── access ──────────────────────────────────────────────────
|
|
44900
45137
|
/** The cell with identity `id` (its declared name, or `c<N>` when undeclared), or
|
|
@@ -45303,13 +45540,17 @@ var VelaWorkspace = class {
|
|
|
45303
45540
|
}
|
|
45304
45541
|
destroy() {
|
|
45305
45542
|
if (this.destroyed) return;
|
|
45306
|
-
this.
|
|
45543
|
+
this.flushPendingState();
|
|
45307
45544
|
this.destroyed = true;
|
|
45308
|
-
if (this.stateTimer != null) clearTimeout(this.stateTimer);
|
|
45309
45545
|
if (this.persistKey !== null && typeof window !== "undefined") window.removeEventListener("beforeunload", this.onUnload);
|
|
45310
45546
|
this.resizeObserver?.disconnect();
|
|
45311
45547
|
this.splitters.destroy();
|
|
45312
45548
|
for (const [id, cell] of [...this.cellsById]) {
|
|
45549
|
+
try {
|
|
45550
|
+
this.poolSet(id, cell.dehydrate());
|
|
45551
|
+
} catch (err) {
|
|
45552
|
+
console.warn(`[vela] could not snapshot cell "${id}" while destroying:`, err);
|
|
45553
|
+
}
|
|
45313
45554
|
cell.destroy();
|
|
45314
45555
|
this.cellsById.delete(id);
|
|
45315
45556
|
}
|
|
@@ -45395,7 +45636,7 @@ var VelaWorkspace = class {
|
|
|
45395
45636
|
/** Debounced dirty mark: one `state:changed` (+ one storage write in persist mode)
|
|
45396
45637
|
* per burst of edits, flushed hard on unload/destroy. */
|
|
45397
45638
|
markStateDirty() {
|
|
45398
|
-
if (this.destroyed) return;
|
|
45639
|
+
if (this.destroyed || !this.booted) return;
|
|
45399
45640
|
if (this.stateTimer != null) clearTimeout(this.stateTimer);
|
|
45400
45641
|
this.stateTimer = setTimeout(() => {
|
|
45401
45642
|
this.stateTimer = null;
|
|
@@ -45403,6 +45644,44 @@ var VelaWorkspace = class {
|
|
|
45403
45644
|
this.persistNow();
|
|
45404
45645
|
}, 500);
|
|
45405
45646
|
}
|
|
45647
|
+
/**
|
|
45648
|
+
* Push a pending debounced change out NOW, for a teardown or a page unload — the
|
|
45649
|
+
* two moments the 500ms timer would otherwise never reach.
|
|
45650
|
+
*
|
|
45651
|
+
* Dropping it was a silent data loss: the timer carries BOTH halves of the dirty
|
|
45652
|
+
* signal — the `state:changed` event and the storage write — so a host that saves
|
|
45653
|
+
* from that event (a server-backed host, where `persistNow` is a no-op for want of
|
|
45654
|
+
* a `persistKey`) simply never heard about the user's last edit.
|
|
45655
|
+
*
|
|
45656
|
+
* Idempotent and re-entrancy safe, which the ordering here buys:
|
|
45657
|
+
* - a no-op unless a timer is actually pending, so a teardown with nothing
|
|
45658
|
+
* outstanding stays silent and never invents an edit;
|
|
45659
|
+
* - the timer is cleared FIRST, so nothing survives this call and a second call
|
|
45660
|
+
* does nothing — `destroy()` after an unload flush adds no second write;
|
|
45661
|
+
* - the state is snapshotted BEFORE the handlers run, and that snapshot is what
|
|
45662
|
+
* gets persisted, so storage and the host are told the same thing even if a
|
|
45663
|
+
* handler mutates state while it runs;
|
|
45664
|
+
* - `emitting` is held across the emit, so a handler calling back in (a host that
|
|
45665
|
+
* tears down on save is the obvious one) cannot re-enter this and recurse.
|
|
45666
|
+
*/
|
|
45667
|
+
flushPendingState() {
|
|
45668
|
+
if (this.stateTimer == null || this.emitting) return;
|
|
45669
|
+
clearTimeout(this.stateTimer);
|
|
45670
|
+
this.stateTimer = null;
|
|
45671
|
+
const snapshot = this.persistKey !== null ? encodeState(this.getState()) : null;
|
|
45672
|
+
this.emitting = true;
|
|
45673
|
+
try {
|
|
45674
|
+
this.events.emit("state:changed", void 0);
|
|
45675
|
+
} finally {
|
|
45676
|
+
this.emitting = false;
|
|
45677
|
+
}
|
|
45678
|
+
if (snapshot !== null && this.persistKey !== null) {
|
|
45679
|
+
try {
|
|
45680
|
+
void this.storage.set(this.persistKey, snapshot);
|
|
45681
|
+
} catch {
|
|
45682
|
+
}
|
|
45683
|
+
}
|
|
45684
|
+
}
|
|
45406
45685
|
/** Write the current state through the storage adapter now (fire-and-forget). */
|
|
45407
45686
|
persistNow() {
|
|
45408
45687
|
if (this.persistKey === null || this.destroyed) return;
|