@luxalgo/vela 0.7.0 → 0.7.2
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-IFJJPXSV.js → chunk-BFA32GOU.js} +44 -16
- package/dist/{chunk-CZRNTHD6.js → chunk-BKHSQ4YM.js} +129 -125
- package/dist/{chunk-A4G64KVF.js → chunk-PTNO3YK2.js} +311 -111
- package/dist/{chunk-A347YL2P.js → chunk-QBWLINWL.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 +391 -157
- package/dist/index.d.cts +52 -17
- package/dist/index.d.ts +52 -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 +391 -157
- package/dist/vela.global.min.js +70 -70
- package/dist/widget.cjs +418 -128
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +6 -6
- package/dist/workspace.cjs +418 -128
- 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/vela.global.js
CHANGED
|
@@ -51,6 +51,13 @@ var Vela = (function (exports) {
|
|
|
51
51
|
function isLineLikeSeries(spec) {
|
|
52
52
|
return spec.kind !== "candle" && spec.kind !== "bar" && spec.kind !== "markers";
|
|
53
53
|
}
|
|
54
|
+
function seriesShownOn(spec, surface) {
|
|
55
|
+
if (spec.display) return spec.display[surface] !== false;
|
|
56
|
+
return spec.visible !== false;
|
|
57
|
+
}
|
|
58
|
+
function seriesInScale(spec) {
|
|
59
|
+
return seriesShownOn(spec, "pane") || seriesShownOn(spec, "priceScale");
|
|
60
|
+
}
|
|
54
61
|
|
|
55
62
|
// src/core/events/EventBus.ts
|
|
56
63
|
var TypedEventBus = class {
|
|
@@ -120,6 +127,9 @@ var Vela = (function (exports) {
|
|
|
120
127
|
};
|
|
121
128
|
|
|
122
129
|
// src/core/native-indicators/NativeIndicator.ts
|
|
130
|
+
function nativeInstanceChannel(type, instanceId) {
|
|
131
|
+
return `${type}#${instanceId}`;
|
|
132
|
+
}
|
|
123
133
|
var REGISTRY = /* @__PURE__ */ new Map();
|
|
124
134
|
function registerNativeIndicator(descriptor) {
|
|
125
135
|
REGISTRY.set(descriptor.type, descriptor);
|
|
@@ -195,6 +205,9 @@ var Vela = (function (exports) {
|
|
|
195
205
|
setProps(values) {
|
|
196
206
|
this.controller.applyProps(this.id, values);
|
|
197
207
|
}
|
|
208
|
+
updateCode(source) {
|
|
209
|
+
this.controller.updateCode(this.id, source);
|
|
210
|
+
}
|
|
198
211
|
setVisible(visible) {
|
|
199
212
|
this.controller.setVisible(this.id, visible);
|
|
200
213
|
}
|
|
@@ -217,6 +230,10 @@ var Vela = (function (exports) {
|
|
|
217
230
|
setPropsSchema(schema) {
|
|
218
231
|
this.propsSchema = schema;
|
|
219
232
|
}
|
|
233
|
+
/** Sync the public `source` once a code update has been prepared and taken over. */
|
|
234
|
+
setSource(source) {
|
|
235
|
+
this.source = source;
|
|
236
|
+
}
|
|
220
237
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
221
238
|
setVisibleState(visible) {
|
|
222
239
|
this.visibleState = visible;
|
|
@@ -2323,16 +2340,18 @@ var Vela = (function (exports) {
|
|
|
2323
2340
|
geometry(proj) {
|
|
2324
2341
|
const a = this.anchors[0];
|
|
2325
2342
|
const b = this.anchors[1];
|
|
2326
|
-
|
|
2327
|
-
if (!a || !b || !c) return null;
|
|
2343
|
+
if (!a || !b) return null;
|
|
2328
2344
|
const px = (p) => {
|
|
2329
2345
|
const y = proj.yOf(p.price, this.paneId);
|
|
2330
2346
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2331
2347
|
};
|
|
2332
2348
|
const P0 = px(a);
|
|
2333
2349
|
const P1 = px(b);
|
|
2350
|
+
if (!P0 || !P1) return null;
|
|
2351
|
+
const c = this.anchors[2];
|
|
2352
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
2334
2353
|
const P2 = px(c);
|
|
2335
|
-
if (!
|
|
2354
|
+
if (!P2) return null;
|
|
2336
2355
|
const mx = (P1[0] + P2[0]) / 2;
|
|
2337
2356
|
const my = (P1[1] + P2[1]) / 2;
|
|
2338
2357
|
const dx = mx - P0[0];
|
|
@@ -2379,16 +2398,19 @@ var Vela = (function (exports) {
|
|
|
2379
2398
|
geometry(proj) {
|
|
2380
2399
|
const a = this.anchors[0];
|
|
2381
2400
|
const b = this.anchors[1];
|
|
2382
|
-
|
|
2383
|
-
if (!a || !b || !c) return null;
|
|
2401
|
+
if (!a || !b) return null;
|
|
2384
2402
|
const px = (p) => {
|
|
2385
2403
|
const y = proj.yOf(p.price, this.paneId);
|
|
2386
2404
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2387
2405
|
};
|
|
2388
|
-
const
|
|
2406
|
+
const P0 = px(a);
|
|
2389
2407
|
const P1 = px(b);
|
|
2408
|
+
if (!P0 || !P1) return null;
|
|
2409
|
+
const c = this.anchors[2];
|
|
2410
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
2411
|
+
const S2 = px(this.medianStart(a, b, c));
|
|
2390
2412
|
const P2 = px(c);
|
|
2391
|
-
if (!S2 || !
|
|
2413
|
+
if (!S2 || !P2) return null;
|
|
2392
2414
|
const mx = (P1[0] + P2[0]) / 2;
|
|
2393
2415
|
const my = (P1[1] + P2[1]) / 2;
|
|
2394
2416
|
const dx = mx - S2[0];
|
|
@@ -2403,8 +2425,12 @@ var Vela = (function (exports) {
|
|
|
2403
2425
|
// upper tine
|
|
2404
2426
|
extendRay(P2[0], P2[1], P2[0] + dx, P2[1] + dy, "right", w, h),
|
|
2405
2427
|
// lower tine
|
|
2406
|
-
[P1[0], P1[1], P2[0], P2[1]]
|
|
2428
|
+
[P1[0], P1[1], P2[0], P2[1]],
|
|
2407
2429
|
// base line
|
|
2430
|
+
// The median leaves from the shifted origin, not the pivot — without this construction
|
|
2431
|
+
// line the pivot handle would float unattached to the shape it defines.
|
|
2432
|
+
[P0[0], P0[1], P1[0], P1[1]]
|
|
2433
|
+
// pivot → first tine anchor
|
|
2408
2434
|
],
|
|
2409
2435
|
fill: null
|
|
2410
2436
|
};
|
|
@@ -2461,17 +2487,19 @@ var Vela = (function (exports) {
|
|
|
2461
2487
|
geometry(proj) {
|
|
2462
2488
|
const a = this.anchors[0];
|
|
2463
2489
|
const b = this.anchors[1];
|
|
2464
|
-
|
|
2465
|
-
if (!a || !b || !c) return null;
|
|
2490
|
+
if (!a || !b) return null;
|
|
2466
2491
|
const px = (p) => {
|
|
2467
2492
|
const y = proj.yOf(p.price, this.paneId);
|
|
2468
2493
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2469
2494
|
};
|
|
2470
|
-
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
2471
2495
|
const P0 = px(a);
|
|
2472
2496
|
const P1 = px(b);
|
|
2497
|
+
if (!P0 || !P1) return null;
|
|
2498
|
+
const c = this.anchors[2];
|
|
2499
|
+
if (!c) return { segments: [[P0[0], P0[1], P1[0], P1[1]]], fill: null };
|
|
2500
|
+
const B = px({ time: (a.time + b.time) / 2, price: (a.price + b.price) / 2 });
|
|
2473
2501
|
const P2 = px(c);
|
|
2474
|
-
if (!B || !
|
|
2502
|
+
if (!B || !P2) return null;
|
|
2475
2503
|
const mx = (P1[0] + P2[0]) / 2;
|
|
2476
2504
|
const my = (P1[1] + P2[1]) / 2;
|
|
2477
2505
|
const dx = P2[0] - B[0];
|
|
@@ -2642,16 +2670,18 @@ var Vela = (function (exports) {
|
|
|
2642
2670
|
geometry(proj) {
|
|
2643
2671
|
const a = this.anchors[0];
|
|
2644
2672
|
const b = this.anchors[1];
|
|
2645
|
-
|
|
2646
|
-
if (!a || !b || !c) return null;
|
|
2673
|
+
if (!a || !b) return null;
|
|
2647
2674
|
const px = (p) => {
|
|
2648
2675
|
const y = proj.yOf(p.price, this.paneId);
|
|
2649
2676
|
return y == null ? null : [proj.xOf(p.time), y];
|
|
2650
2677
|
};
|
|
2651
2678
|
const A = px(a);
|
|
2652
2679
|
const B = px(b);
|
|
2680
|
+
if (!A || !B) return null;
|
|
2681
|
+
const c = this.anchors[2];
|
|
2682
|
+
if (!c) return { segments: [[A[0], A[1], B[0], B[1]]], fill: null };
|
|
2653
2683
|
const C = px(c);
|
|
2654
|
-
if (!
|
|
2684
|
+
if (!C) return null;
|
|
2655
2685
|
return {
|
|
2656
2686
|
segments: [
|
|
2657
2687
|
[A[0], A[1], B[0], B[1]],
|
|
@@ -8180,6 +8210,7 @@ var Vela = (function (exports) {
|
|
|
8180
8210
|
if (identityChanged) {
|
|
8181
8211
|
this.beginLoad(false);
|
|
8182
8212
|
this.setBarSeries([], { clearing: true });
|
|
8213
|
+
this.blankIndicatorVisuals();
|
|
8183
8214
|
if (this.activeEngineStyle) {
|
|
8184
8215
|
this.typeEngines.get(this.activeEngineStyle)?.suspend();
|
|
8185
8216
|
this.renderer.setNativeData?.(this.activeEngineStyle, void 0);
|
|
@@ -8222,7 +8253,7 @@ var Vela = (function (exports) {
|
|
|
8222
8253
|
restartNativeIndicators() {
|
|
8223
8254
|
for (const record of this.registry.all()) {
|
|
8224
8255
|
if (!record.native) continue;
|
|
8225
|
-
record.native.instance.stop();
|
|
8256
|
+
if (record.native.started) record.native.instance.stop();
|
|
8226
8257
|
record.native.instance = record.native.descriptor.create();
|
|
8227
8258
|
record.native.started = false;
|
|
8228
8259
|
record.pendingStructural = true;
|
|
@@ -8430,6 +8461,23 @@ var Vela = (function (exports) {
|
|
|
8430
8461
|
this.renderer.setBars(this.bars, { preserveView: true });
|
|
8431
8462
|
this.reexecuteIndicators();
|
|
8432
8463
|
}
|
|
8464
|
+
/**
|
|
8465
|
+
* Blank every mounted indicator's painted output — series data, fills, backgrounds,
|
|
8466
|
+
* price lines, drawings, bar colors, trades — while keeping the mount (legend row,
|
|
8467
|
+
* pane, settings) intact, via the same idempotent-by-id remount an input edit uses.
|
|
8468
|
+
* Called on a market identity switch: the fresh models arrive only after the new
|
|
8469
|
+
* bars load, so nothing blanked here is ever restored — each consumer's re-run
|
|
8470
|
+
* remounts a full model over it.
|
|
8471
|
+
*/
|
|
8472
|
+
blankIndicatorVisuals() {
|
|
8473
|
+
for (const record of this.registry.all()) {
|
|
8474
|
+
if (record.hidden || !record.renderHandle || !record.model) continue;
|
|
8475
|
+
record.model = blankedModel(record.model);
|
|
8476
|
+
record.renderHandle = this.renderer.mountIndicator(record.model);
|
|
8477
|
+
record.pendingStructural = true;
|
|
8478
|
+
this.setLoading(record, true);
|
|
8479
|
+
}
|
|
8480
|
+
}
|
|
8433
8481
|
/** Stop + re-run every visible Pine indicator (its input bars changed wholesale). */
|
|
8434
8482
|
reexecuteIndicators() {
|
|
8435
8483
|
for (const record of this.registry.all()) {
|
|
@@ -8674,6 +8722,61 @@ var Vela = (function (exports) {
|
|
|
8674
8722
|
record.session.update(record.inputValues, record.propValues);
|
|
8675
8723
|
this.events.emit("indicator:inputs", { id });
|
|
8676
8724
|
}
|
|
8725
|
+
/**
|
|
8726
|
+
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
8727
|
+
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
8728
|
+
* computing until the new source has compiled, so a broken edit costs the user
|
|
8729
|
+
* nothing but an `error` event. Natives have no script — warn and leave them alone.
|
|
8730
|
+
*/
|
|
8731
|
+
updateCode(id, source) {
|
|
8732
|
+
const record = this.registry.get(id);
|
|
8733
|
+
const handle = this.handles.get(id);
|
|
8734
|
+
if (!record || !handle) return;
|
|
8735
|
+
if (record.native) {
|
|
8736
|
+
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
8737
|
+
return;
|
|
8738
|
+
}
|
|
8739
|
+
if (source === record.source && record.pendingSource === void 0) return;
|
|
8740
|
+
record.pendingSource = source;
|
|
8741
|
+
void this.swapSource(id, source, handle);
|
|
8742
|
+
}
|
|
8743
|
+
async swapSource(id, source, handle) {
|
|
8744
|
+
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
8745
|
+
let prepared;
|
|
8746
|
+
try {
|
|
8747
|
+
prepared = await engine.prepare(source, id);
|
|
8748
|
+
} catch (err) {
|
|
8749
|
+
const record2 = this.registry.get(id);
|
|
8750
|
+
if (record2?.pendingSource === source) {
|
|
8751
|
+
record2.pendingSource = void 0;
|
|
8752
|
+
this.fail(id, handle, err);
|
|
8753
|
+
}
|
|
8754
|
+
return;
|
|
8755
|
+
}
|
|
8756
|
+
const record = this.registry.get(id);
|
|
8757
|
+
if (!record || record.pendingSource !== source) return;
|
|
8758
|
+
record.pendingSource = void 0;
|
|
8759
|
+
record.source = source;
|
|
8760
|
+
record.engine = engine;
|
|
8761
|
+
record.prepared = prepared;
|
|
8762
|
+
handle.setSource(source);
|
|
8763
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
8764
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
8765
|
+
handle.setSchema(prepared.inputs);
|
|
8766
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
8767
|
+
record.session?.stop();
|
|
8768
|
+
record.session = void 0;
|
|
8769
|
+
record.pendingStructural = true;
|
|
8770
|
+
record.pendingCause = "code";
|
|
8771
|
+
if (record.hidden) return;
|
|
8772
|
+
if (record.renderHandle) {
|
|
8773
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8774
|
+
this.setLoading(record, true);
|
|
8775
|
+
} else {
|
|
8776
|
+
this.mountLoadingPlaceholder(id, record);
|
|
8777
|
+
}
|
|
8778
|
+
this.executeIndicator(id, handle);
|
|
8779
|
+
}
|
|
8677
8780
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
8678
8781
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
8679
8782
|
listIndicators() {
|
|
@@ -8688,7 +8791,7 @@ var Vela = (function (exports) {
|
|
|
8688
8791
|
const record = this.registry.remove(id);
|
|
8689
8792
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
8690
8793
|
record?.session?.stop();
|
|
8691
|
-
record?.native?.instance.stop();
|
|
8794
|
+
if (record?.native?.started) record.native.instance.stop();
|
|
8692
8795
|
this.handles.delete(id);
|
|
8693
8796
|
if (record?.renderHandle) this.renderer.removeIndicator(record.renderHandle);
|
|
8694
8797
|
const paneId = record?.model?.paneId;
|
|
@@ -8714,7 +8817,7 @@ var Vela = (function (exports) {
|
|
|
8714
8817
|
if (!visible) {
|
|
8715
8818
|
record.session?.stop();
|
|
8716
8819
|
record.session = void 0;
|
|
8717
|
-
record.native?.instance.suspend();
|
|
8820
|
+
if (record.native?.started) record.native.instance.suspend();
|
|
8718
8821
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
8719
8822
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
8720
8823
|
} else {
|
|
@@ -8795,7 +8898,7 @@ var Vela = (function (exports) {
|
|
|
8795
8898
|
this.paneActionUnsub?.();
|
|
8796
8899
|
for (const record of this.registry.all()) {
|
|
8797
8900
|
record.session?.stop();
|
|
8798
|
-
record.native?.instance.stop();
|
|
8901
|
+
if (record.native?.started) record.native.instance.stop();
|
|
8799
8902
|
}
|
|
8800
8903
|
for (const engine of this.typeEngines.values()) engine.stop();
|
|
8801
8904
|
this.typeEngines.clear();
|
|
@@ -8819,6 +8922,7 @@ var Vela = (function (exports) {
|
|
|
8819
8922
|
const engine = this.engineFor(options.language);
|
|
8820
8923
|
record.engine = engine;
|
|
8821
8924
|
const prepared = await engine.prepare(source, id);
|
|
8925
|
+
if (record.source !== source) return;
|
|
8822
8926
|
record.prepared = prepared;
|
|
8823
8927
|
const defaults2 = {};
|
|
8824
8928
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
@@ -8889,7 +8993,9 @@ var Vela = (function (exports) {
|
|
|
8889
8993
|
await this.readyPromise;
|
|
8890
8994
|
const record = this.registry.get(id);
|
|
8891
8995
|
if (!record?.native || record.hidden) return;
|
|
8996
|
+
const channel = this.nativeChannel(record);
|
|
8892
8997
|
const ctx = {
|
|
8998
|
+
id,
|
|
8893
8999
|
symbol: this.config.market.symbol ?? "TEST",
|
|
8894
9000
|
timeframe: this.config.market.timeframe ?? "60",
|
|
8895
9001
|
live: this.config.live,
|
|
@@ -8897,7 +9003,7 @@ var Vela = (function (exports) {
|
|
|
8897
9003
|
bars: () => this.bars,
|
|
8898
9004
|
data: this.dataControl,
|
|
8899
9005
|
emit: (out) => this.applyModel(id, this.buildNativeModel(record, out)),
|
|
8900
|
-
pushData: (data) => this.renderer.setNativeData?.(
|
|
9006
|
+
pushData: (data) => this.renderer.setNativeData?.(channel, data),
|
|
8901
9007
|
setStatus: (status) => {
|
|
8902
9008
|
if (record.renderHandle && !record.hidden) this.renderer.setIndicatorStatus?.(record.renderHandle, status);
|
|
8903
9009
|
}
|
|
@@ -8908,16 +9014,26 @@ var Vela = (function (exports) {
|
|
|
8908
9014
|
this.fail(id, handle, err);
|
|
8909
9015
|
}
|
|
8910
9016
|
}
|
|
9017
|
+
/** The renderer-layer channel a native instance's `pushData` lands on: the type itself
|
|
9018
|
+
* for a single-instance type (the layer id doubles as the channel), a per-instance
|
|
9019
|
+
* channel for a `multiInstance` type — otherwise every instance would overwrite the
|
|
9020
|
+
* one layer. Stamped on the model so the renderer mounts a dedicated layer for it. */
|
|
9021
|
+
nativeChannel(record) {
|
|
9022
|
+
const { type, descriptor } = record.native;
|
|
9023
|
+
return descriptor.multiInstance ? nativeInstanceChannel(type, record.id) : type;
|
|
9024
|
+
}
|
|
8911
9025
|
/** Wrap a native indicator's emitted visuals into a full IndicatorModel, tagged `native`. */
|
|
8912
9026
|
buildNativeModel(record, out) {
|
|
8913
9027
|
const d = record.native.descriptor;
|
|
9028
|
+
const type = record.native.type;
|
|
9029
|
+
const channel = this.nativeChannel(record);
|
|
8914
9030
|
return {
|
|
8915
9031
|
id: record.id,
|
|
8916
9032
|
title: record.title,
|
|
8917
9033
|
...d.shortTitle ? { shorttitle: d.shortTitle } : {},
|
|
8918
9034
|
overlay: d.overlay,
|
|
8919
9035
|
paneHint: d.paneHint,
|
|
8920
|
-
native: { type
|
|
9036
|
+
native: { type, ...channel !== type ? { channel } : {} },
|
|
8921
9037
|
...d.legend === false ? { legend: false } : {},
|
|
8922
9038
|
...out.paneAxis != null ? { paneAxis: out.paneAxis } : {},
|
|
8923
9039
|
series: out.series ?? [],
|
|
@@ -9207,6 +9323,7 @@ var Vela = (function (exports) {
|
|
|
9207
9323
|
applyModel(id, model) {
|
|
9208
9324
|
const record = this.registry.get(id);
|
|
9209
9325
|
if (!record || record.hidden) return true;
|
|
9326
|
+
if (this.switchingMarket) return false;
|
|
9210
9327
|
if (record.loading && this.bars.length === 0 && !_EngineOrchestrator.modelHasOutput(model)) return false;
|
|
9211
9328
|
const handle = this.handles.get(id);
|
|
9212
9329
|
if (!record.renderHandle) {
|
|
@@ -9224,7 +9341,7 @@ var Vela = (function (exports) {
|
|
|
9224
9341
|
if (record.loading && !record.paneLocked) {
|
|
9225
9342
|
const routed = this.routePane(id, model, record.options ?? {});
|
|
9226
9343
|
if (routed !== paneId) {
|
|
9227
|
-
if (paneId !== "price") {
|
|
9344
|
+
if (paneId !== "price" && !this.registry.all().some((r) => r.id !== id && r.model?.paneId === paneId)) {
|
|
9228
9345
|
this.renderer.removePane(paneId);
|
|
9229
9346
|
this.forgetPane(paneId);
|
|
9230
9347
|
}
|
|
@@ -9353,6 +9470,34 @@ var Vela = (function (exports) {
|
|
|
9353
9470
|
}
|
|
9354
9471
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
9355
9472
|
}
|
|
9473
|
+
function valuesOnSchema(schema, previous) {
|
|
9474
|
+
const out = {};
|
|
9475
|
+
const declared = /* @__PURE__ */ new Set();
|
|
9476
|
+
for (const s of schema) {
|
|
9477
|
+
out[s.key] = s.defval;
|
|
9478
|
+
declared.add(s.key);
|
|
9479
|
+
declared.add(s.title);
|
|
9480
|
+
}
|
|
9481
|
+
for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
|
|
9482
|
+
return out;
|
|
9483
|
+
}
|
|
9484
|
+
function blankedModel(model) {
|
|
9485
|
+
return {
|
|
9486
|
+
...model,
|
|
9487
|
+
series: model.series.map((s) => s.kind === "candle" || s.kind === "bar" ? { ...s, bars: [] } : s.kind === "markers" ? { ...s, markers: [] } : { ...s, points: [] }),
|
|
9488
|
+
fills: [],
|
|
9489
|
+
backgrounds: [],
|
|
9490
|
+
priceLines: [],
|
|
9491
|
+
lines: [],
|
|
9492
|
+
boxes: [],
|
|
9493
|
+
labels: [],
|
|
9494
|
+
polylines: [],
|
|
9495
|
+
linefills: [],
|
|
9496
|
+
tables: [],
|
|
9497
|
+
barColors: [],
|
|
9498
|
+
trades: []
|
|
9499
|
+
};
|
|
9500
|
+
}
|
|
9356
9501
|
function modelToValuePatch(model) {
|
|
9357
9502
|
const series = [];
|
|
9358
9503
|
let from = Number.POSITIVE_INFINITY;
|
|
@@ -12755,6 +12900,43 @@ var Vela = (function (exports) {
|
|
|
12755
12900
|
host.appendChild(s);
|
|
12756
12901
|
}
|
|
12757
12902
|
|
|
12903
|
+
// src/ui/tokens.ts
|
|
12904
|
+
var STATIC_ID = "vela-ui-tokens";
|
|
12905
|
+
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
12906
|
+
var STATIC_CSS = `
|
|
12907
|
+
.vela-ui, .vela-ui-layer {
|
|
12908
|
+
${STATIC_DECLS}
|
|
12909
|
+
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
12910
|
+
box-sizing: border-box;
|
|
12911
|
+
/* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
|
|
12912
|
+
user-select: none;
|
|
12913
|
+
-webkit-user-select: none;
|
|
12914
|
+
}
|
|
12915
|
+
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
12916
|
+
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
12917
|
+
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
12918
|
+
/* iOS Safari zooms the page when a focused field is under 16px, and often
|
|
12919
|
+
keeps that zoom after the field blurs or a dialog closes. The shell's
|
|
12920
|
+
mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
|
|
12921
|
+
chart on a wide desktop still gets the rule when it is in the phone layout.
|
|
12922
|
+
Scoped to the kit root so a host page's own [data-layout] is never touched. */
|
|
12923
|
+
.vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
|
|
12924
|
+
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
12925
|
+
.vela-icon svg { display: block; }
|
|
12926
|
+
`;
|
|
12927
|
+
function applyThemeTokens(el, t) {
|
|
12928
|
+
const tokens = themeTokens(t);
|
|
12929
|
+
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
12930
|
+
}
|
|
12931
|
+
function floatingLayerHost(from, fallback) {
|
|
12932
|
+
return from.closest(".vela-ui") ?? fallback ?? from;
|
|
12933
|
+
}
|
|
12934
|
+
function ensureUIHost(el, theme) {
|
|
12935
|
+
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
12936
|
+
el.classList.add("vela-ui");
|
|
12937
|
+
if (theme) applyThemeTokens(el, theme);
|
|
12938
|
+
}
|
|
12939
|
+
|
|
12758
12940
|
// src/ui/icons.ts
|
|
12759
12941
|
function iconEl(id, doc = document) {
|
|
12760
12942
|
const span = doc.createElement("span");
|
|
@@ -17120,7 +17302,7 @@ var Vela = (function (exports) {
|
|
|
17120
17302
|
/* The list is a <ul>: without this, block rows (the separator) paint a ::marker dot. */
|
|
17121
17303
|
list-style: none;
|
|
17122
17304
|
margin: 0;
|
|
17123
|
-
background: var(--vela-surface
|
|
17305
|
+
background: var(--vela-surface);
|
|
17124
17306
|
color: var(--vela-fg);
|
|
17125
17307
|
border: 1px solid var(--vela-border-strong);
|
|
17126
17308
|
border-radius: 6px;
|
|
@@ -17413,7 +17595,7 @@ var Vela = (function (exports) {
|
|
|
17413
17595
|
const anchor = opts.trigger ?? opts.host ?? document.body;
|
|
17414
17596
|
const doc = anchor.ownerDocument;
|
|
17415
17597
|
injectStyles(MENU_STYLE_ID, MENU_CSS, doc);
|
|
17416
|
-
const host = opts.host ?? anchor
|
|
17598
|
+
const host = floatingLayerHost(opts.host ?? anchor, doc.body);
|
|
17417
17599
|
this.root = new Surface(doc, {
|
|
17418
17600
|
host,
|
|
17419
17601
|
placement: opts.placement,
|
|
@@ -17452,40 +17634,6 @@ var Vela = (function (exports) {
|
|
|
17452
17634
|
}
|
|
17453
17635
|
};
|
|
17454
17636
|
|
|
17455
|
-
// src/ui/tokens.ts
|
|
17456
|
-
var STATIC_ID = "vela-ui-tokens";
|
|
17457
|
-
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
17458
|
-
var STATIC_CSS = `
|
|
17459
|
-
.vela-ui, .vela-ui-layer {
|
|
17460
|
-
${STATIC_DECLS}
|
|
17461
|
-
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
17462
|
-
box-sizing: border-box;
|
|
17463
|
-
/* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
|
|
17464
|
-
user-select: none;
|
|
17465
|
-
-webkit-user-select: none;
|
|
17466
|
-
}
|
|
17467
|
-
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
17468
|
-
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
17469
|
-
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
17470
|
-
/* iOS Safari zooms the page when a focused field is under 16px, and often
|
|
17471
|
-
keeps that zoom after the field blurs or a dialog closes. The shell's
|
|
17472
|
-
mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
|
|
17473
|
-
chart on a wide desktop still gets the rule when it is in the phone layout.
|
|
17474
|
-
Scoped to the kit root so a host page's own [data-layout] is never touched. */
|
|
17475
|
-
.vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
|
|
17476
|
-
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
17477
|
-
.vela-icon svg { display: block; }
|
|
17478
|
-
`;
|
|
17479
|
-
function applyThemeTokens(el, t) {
|
|
17480
|
-
const tokens = themeTokens(t);
|
|
17481
|
-
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
17482
|
-
}
|
|
17483
|
-
function ensureUIHost(el, theme) {
|
|
17484
|
-
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
17485
|
-
el.classList.add("vela-ui");
|
|
17486
|
-
if (theme) applyThemeTokens(el, theme);
|
|
17487
|
-
}
|
|
17488
|
-
|
|
17489
17637
|
// src/ui/components/popover/controller.ts
|
|
17490
17638
|
function insetRect(r, inset) {
|
|
17491
17639
|
return {
|
|
@@ -17592,7 +17740,8 @@ ${STATIC_DECLS}
|
|
|
17592
17740
|
const doc = opts.trigger.ownerDocument;
|
|
17593
17741
|
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
17594
17742
|
this.trigger = opts.trigger;
|
|
17595
|
-
this.host = opts.host ?? doc.body;
|
|
17743
|
+
this.host = opts.host ?? floatingLayerHost(opts.trigger, doc.body);
|
|
17744
|
+
this.inheritsTokens = !opts.host && this.host !== doc.body;
|
|
17596
17745
|
this.ctrl = popoverController(opts);
|
|
17597
17746
|
this.boundary = opts.boundary ?? "viewport";
|
|
17598
17747
|
this.theme = opts.theme;
|
|
@@ -17624,7 +17773,7 @@ ${STATIC_DECLS}
|
|
|
17624
17773
|
clearTimeout(this.leaveTimer);
|
|
17625
17774
|
this.leaveTimer = null;
|
|
17626
17775
|
}
|
|
17627
|
-
ensureUIHost(this.el, this.theme);
|
|
17776
|
+
ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
|
|
17628
17777
|
if (this.fadeMs > 0) {
|
|
17629
17778
|
this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
|
|
17630
17779
|
this.el.style.opacity = "0";
|
|
@@ -23119,33 +23268,32 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23119
23268
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
23120
23269
|
};
|
|
23121
23270
|
}
|
|
23122
|
-
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
23271
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf, colorOf2) {
|
|
23123
23272
|
const out = [];
|
|
23124
23273
|
let col = NaN;
|
|
23125
|
-
let
|
|
23274
|
+
let runs = [];
|
|
23126
23275
|
const flush = () => {
|
|
23127
|
-
if (
|
|
23128
|
-
|
|
23129
|
-
|
|
23130
|
-
|
|
23131
|
-
|
|
23132
|
-
|
|
23133
|
-
|
|
23134
|
-
|
|
23135
|
-
|
|
23136
|
-
prev3.headTime = next2.headTime;
|
|
23137
|
-
prev3.open = next2.open;
|
|
23138
|
-
}
|
|
23139
|
-
if (next2.lastIdx > prev3.lastIdx) {
|
|
23140
|
-
prev3.lastIdx = next2.lastIdx;
|
|
23141
|
-
prev3.close = next2.close;
|
|
23276
|
+
if (runs.length === 0) return;
|
|
23277
|
+
runs.sort((a, b) => a.lo - b.lo);
|
|
23278
|
+
const merged = [];
|
|
23279
|
+
for (const next2 of runs) {
|
|
23280
|
+
let prev3;
|
|
23281
|
+
for (let k = merged.length - 1; k >= 0; k -= 1) {
|
|
23282
|
+
if (merged[k].color === next2.color) {
|
|
23283
|
+
prev3 = merged[k];
|
|
23284
|
+
break;
|
|
23142
23285
|
}
|
|
23286
|
+
}
|
|
23287
|
+
if (prev3 && Math.abs(yOf(prev3.hi) - yOf(next2.lo)) < 1) {
|
|
23288
|
+
prev3.hi = next2.hi;
|
|
23289
|
+
if (next2.lastIdx > prev3.lastIdx) prev3.lastIdx = next2.lastIdx;
|
|
23143
23290
|
} else {
|
|
23144
23291
|
merged.push(next2);
|
|
23145
23292
|
}
|
|
23146
23293
|
}
|
|
23147
|
-
|
|
23148
|
-
|
|
23294
|
+
merged.sort((a, b) => a.lastIdx - b.lastIdx);
|
|
23295
|
+
for (const r of merged) out.push({ x: col, hi: r.hi, lo: r.lo, color: r.color });
|
|
23296
|
+
runs = [];
|
|
23149
23297
|
};
|
|
23150
23298
|
for (let i = i0; i <= i1; i += 1) {
|
|
23151
23299
|
const b = bars[i];
|
|
@@ -23155,26 +23303,17 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
23155
23303
|
flush();
|
|
23156
23304
|
col = x;
|
|
23157
23305
|
}
|
|
23306
|
+
const color = colorOf2(b);
|
|
23158
23307
|
let lo = b.low;
|
|
23159
23308
|
let hi = b.high;
|
|
23160
|
-
let
|
|
23161
|
-
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
if (iv.headIdx < headIdx) {
|
|
23169
|
-
headIdx = iv.headIdx;
|
|
23170
|
-
headTime = iv.headTime;
|
|
23171
|
-
open2 = iv.open;
|
|
23172
|
-
}
|
|
23173
|
-
intervals.splice(k, 1);
|
|
23174
|
-
}
|
|
23175
|
-
let insertAt = 0;
|
|
23176
|
-
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
23177
|
-
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
23309
|
+
for (let k = runs.length - 1; k >= 0; k -= 1) {
|
|
23310
|
+
const r = runs[k];
|
|
23311
|
+
if (r.color !== color || r.lo > hi || r.hi < lo) continue;
|
|
23312
|
+
if (r.lo < lo) lo = r.lo;
|
|
23313
|
+
if (r.hi > hi) hi = r.hi;
|
|
23314
|
+
runs.splice(k, 1);
|
|
23315
|
+
}
|
|
23316
|
+
runs.push({ lo, hi, color, lastIdx: i });
|
|
23178
23317
|
}
|
|
23179
23318
|
flush();
|
|
23180
23319
|
return out;
|
|
@@ -24149,7 +24288,7 @@ void main() {
|
|
|
24149
24288
|
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
24150
24289
|
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
24151
24290
|
const emitOne = (s, off) => {
|
|
24152
|
-
if (!isLineLikeSeries(s) || s
|
|
24291
|
+
if (!isLineLikeSeries(s) || !seriesShownOn(s, "pane")) return;
|
|
24153
24292
|
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
24154
24293
|
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
24155
24294
|
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
@@ -24260,7 +24399,7 @@ void main() {
|
|
|
24260
24399
|
const spacing = coords.bodySpacing();
|
|
24261
24400
|
const tier = candleTier(spacing);
|
|
24262
24401
|
if (tier === "aggregate") {
|
|
24263
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24402
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => barColors.get(bar.time) ?? (bar.close >= bar.open ? up : down));
|
|
24264
24403
|
return;
|
|
24265
24404
|
}
|
|
24266
24405
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -24401,14 +24540,17 @@ void main() {
|
|
|
24401
24540
|
const spacing = coords.bodySpacing();
|
|
24402
24541
|
const tier = candleTier(spacing);
|
|
24403
24542
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
24543
|
+
const up = paint.up;
|
|
24544
|
+
const down = paint.down;
|
|
24545
|
+
const cs = paint.candle;
|
|
24404
24546
|
if (tier === "aggregate") {
|
|
24405
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24547
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, (bar) => {
|
|
24548
|
+
const isUp = bar.close >= bar.open;
|
|
24549
|
+
return (isUp ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(bar.time) ?? (isUp ? up : down);
|
|
24550
|
+
});
|
|
24406
24551
|
return;
|
|
24407
24552
|
}
|
|
24408
24553
|
const drawBody = tier === "full";
|
|
24409
|
-
const up = paint.up;
|
|
24410
|
-
const down = paint.down;
|
|
24411
|
-
const cs = paint.candle;
|
|
24412
24554
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
24413
24555
|
for (let i = i0; i <= i1; i += 1) {
|
|
24414
24556
|
const bar = bars[i];
|
|
@@ -24457,24 +24599,26 @@ void main() {
|
|
|
24457
24599
|
/**
|
|
24458
24600
|
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
24459
24601
|
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
24460
|
-
* coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
24461
|
-
* column stays a void
|
|
24462
|
-
*
|
|
24602
|
+
* SAME-COLOR coverage run (see {@link aggregateCandleColumns}), so a price gap
|
|
24603
|
+
* inside the column stays a void and every bar's extent keeps its own color. Draw
|
|
24604
|
+
* cost stays bounded by screen width, not bar count. `colorOf` is the calling
|
|
24605
|
+
* style's per-bar paint rule (candles and OHLC bars resolve theirs differently).
|
|
24463
24606
|
*/
|
|
24464
|
-
emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
24607
|
+
emitCandlesAggregated(b, bars, i0, i1, coords, pane, colorOf2) {
|
|
24465
24608
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
24466
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
24467
|
-
const c = parseColor(
|
|
24609
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
24610
|
+
const c = parseColor(s.color);
|
|
24468
24611
|
const hY = yOf(s.hi);
|
|
24469
24612
|
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
24470
24613
|
}
|
|
24471
24614
|
}
|
|
24472
24615
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
24616
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
24473
24617
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
24474
24618
|
this.emitPlotCandles(b, spec, pane, coords, i0, i1, theme, off);
|
|
24475
24619
|
return;
|
|
24476
24620
|
}
|
|
24477
|
-
if (!isLineLikeSeries(spec)
|
|
24621
|
+
if (!isLineLikeSeries(spec)) return;
|
|
24478
24622
|
switch (spec.kind) {
|
|
24479
24623
|
case "histogram":
|
|
24480
24624
|
case "columns":
|
|
@@ -26138,7 +26282,7 @@ void main() {
|
|
|
26138
26282
|
const spacing = coords.bodySpacing();
|
|
26139
26283
|
const tier = candleTier(spacing);
|
|
26140
26284
|
if (tier === "aggregate") {
|
|
26141
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26285
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => barColors.get(b.time) ?? (b.close >= b.open ? up : down));
|
|
26142
26286
|
return;
|
|
26143
26287
|
}
|
|
26144
26288
|
const tickW = Math.max(1, Math.round(spacing * 0.35));
|
|
@@ -26314,12 +26458,15 @@ void main() {
|
|
|
26314
26458
|
const spacing = coords.bodySpacing();
|
|
26315
26459
|
const tier = candleTier(spacing);
|
|
26316
26460
|
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
26461
|
+
const cs = paint.candle;
|
|
26317
26462
|
if (tier === "aggregate") {
|
|
26318
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26463
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, (b) => {
|
|
26464
|
+
const up = b.close >= b.open;
|
|
26465
|
+
return (up ? cs.wickUpColor : cs.wickDownColor) ?? barColors.get(b.time) ?? (up ? paint.up : paint.down);
|
|
26466
|
+
});
|
|
26319
26467
|
return;
|
|
26320
26468
|
}
|
|
26321
26469
|
const drawBody = tier === "full";
|
|
26322
|
-
const cs = paint.candle;
|
|
26323
26470
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
26324
26471
|
for (let i = i0; i <= i1; i += 1) {
|
|
26325
26472
|
const b = bars[i];
|
|
@@ -26376,17 +26523,18 @@ void main() {
|
|
|
26376
26523
|
}
|
|
26377
26524
|
/**
|
|
26378
26525
|
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
26379
|
-
* sticks (one per contiguous coverage run — see
|
|
26380
|
-
* so draw cost is bounded by screen width (not
|
|
26381
|
-
*
|
|
26382
|
-
* its
|
|
26526
|
+
* sticks (one per contiguous SAME-COLOR coverage run — see
|
|
26527
|
+
* {@link aggregateCandleColumns}), so draw cost is bounded by screen width (not
|
|
26528
|
+
* bar count) when zoomed far out, a price gap inside the column stays a void, and
|
|
26529
|
+
* every bar's extent keeps its own color. `colorOf` is the calling style's per-bar
|
|
26530
|
+
* paint rule (candles and OHLC bars resolve theirs differently).
|
|
26383
26531
|
*/
|
|
26384
|
-
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
26532
|
+
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, colorOf2) {
|
|
26385
26533
|
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
26386
26534
|
ctx.lineWidth = 1;
|
|
26387
|
-
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
26535
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf, colorOf2)) {
|
|
26388
26536
|
const x = s.x + 0.5;
|
|
26389
|
-
ctx.strokeStyle =
|
|
26537
|
+
ctx.strokeStyle = s.color;
|
|
26390
26538
|
ctx.beginPath();
|
|
26391
26539
|
ctx.moveTo(x, yOf(s.hi));
|
|
26392
26540
|
ctx.lineTo(x, yOf(s.lo));
|
|
@@ -26394,11 +26542,12 @@ void main() {
|
|
|
26394
26542
|
}
|
|
26395
26543
|
}
|
|
26396
26544
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
26545
|
+
if (!seriesShownOn(spec, "pane")) return;
|
|
26397
26546
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
26398
26547
|
this.drawPlotCandles(ctx, spec, pane, coords, i0, i1, theme, off);
|
|
26399
26548
|
return;
|
|
26400
26549
|
}
|
|
26401
|
-
if (!isLineLikeSeries(spec)
|
|
26550
|
+
if (!isLineLikeSeries(spec)) return;
|
|
26402
26551
|
switch (spec.kind) {
|
|
26403
26552
|
case "histogram":
|
|
26404
26553
|
case "columns":
|
|
@@ -35346,6 +35495,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35346
35495
|
return { min: min2 - span * MARGIN_BOTTOM, max: max2 + span * MARGIN_TOP };
|
|
35347
35496
|
}
|
|
35348
35497
|
function considerSeries(s, i0, i1, off, consider) {
|
|
35498
|
+
if (!seriesInScale(s)) return;
|
|
35349
35499
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
35350
35500
|
for (let i = i0; i <= i1; i += 1) {
|
|
35351
35501
|
const b = s.bars[i - off];
|
|
@@ -35565,6 +35715,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35565
35715
|
this.openKey = null;
|
|
35566
35716
|
/** Bumped per open/close — a lazy content resolving after its popup went away is dropped. */
|
|
35567
35717
|
this.generation = 0;
|
|
35718
|
+
/**
|
|
35719
|
+
* The cluster whose popup was open when the current pointer press began. The anchor is
|
|
35720
|
+
* hit-transparent (the glyph is canvas pixels), so the shell's outside-dismiss closes the
|
|
35721
|
+
* popup on pointerdown; the click the renderer derives from the same pointerup must then
|
|
35722
|
+
* read as "close", not reopen that cluster. Captured on the document ahead of the shell's
|
|
35723
|
+
* (deferred) listener, cleared once the release has dispatched.
|
|
35724
|
+
*/
|
|
35725
|
+
this.pressKey = null;
|
|
35726
|
+
this.onPress = () => {
|
|
35727
|
+
if (this.openKey === null) return;
|
|
35728
|
+
this.pressKey = this.openKey;
|
|
35729
|
+
const doc = this.deps.plot.ownerDocument;
|
|
35730
|
+
const clear = () => void setTimeout(() => this.pressKey = null, 0);
|
|
35731
|
+
doc.addEventListener("pointerup", clear, { capture: true, once: true });
|
|
35732
|
+
doc.addEventListener("pointercancel", clear, { capture: true, once: true });
|
|
35733
|
+
};
|
|
35568
35734
|
const doc = deps.plot.ownerDocument;
|
|
35569
35735
|
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
35570
35736
|
injectStyles(MARKS_STYLE_ID, MARKS_CSS, doc);
|
|
@@ -35572,12 +35738,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35572
35738
|
this.anchor.className = "vela-marks-anchor";
|
|
35573
35739
|
Object.assign(this.anchor.style, { position: "absolute", pointerEvents: "none", left: "0", top: "0", width: "0", height: "0" });
|
|
35574
35740
|
deps.plot.appendChild(this.anchor);
|
|
35741
|
+
doc.addEventListener("pointerdown", this.onPress, true);
|
|
35575
35742
|
}
|
|
35576
35743
|
/** The cluster key the open popup belongs to, or null. */
|
|
35577
35744
|
get key() {
|
|
35578
35745
|
return this.openKey;
|
|
35579
35746
|
}
|
|
35580
35747
|
open(cluster, rect) {
|
|
35748
|
+
if (this.pressKey === cluster.key) return;
|
|
35581
35749
|
this.close();
|
|
35582
35750
|
this.place(rect);
|
|
35583
35751
|
const gen = ++this.generation;
|
|
@@ -35625,6 +35793,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35625
35793
|
}
|
|
35626
35794
|
destroy() {
|
|
35627
35795
|
this.close();
|
|
35796
|
+
this.deps.plot.ownerDocument.removeEventListener("pointerdown", this.onPress, true);
|
|
35628
35797
|
this.anchor.remove();
|
|
35629
35798
|
}
|
|
35630
35799
|
place(rect) {
|
|
@@ -36521,6 +36690,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36521
36690
|
this.backdropRenderer = new BackdropRenderer();
|
|
36522
36691
|
this.volumeRenderer = new VolumeRenderer();
|
|
36523
36692
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
36693
|
+
/**
|
|
36694
|
+
* The mounted SDK layers. One BASE entry per registered definition (channel = the layer
|
|
36695
|
+
* id, no owner of its own), plus one DEDICATED entry per mounted native indicator whose
|
|
36696
|
+
* model names its own channel (`native.channel` — an instance of a multi-instance type):
|
|
36697
|
+
* same definition, its own canvas, reading that instance's channel and following that
|
|
36698
|
+
* instance's pane and z key. `channel` is unique across the array.
|
|
36699
|
+
*/
|
|
36524
36700
|
this.extLayers = [];
|
|
36525
36701
|
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
36526
36702
|
this.layerOrderSig = "";
|
|
@@ -37666,11 +37842,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37666
37842
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
37667
37843
|
this.plot = document.createElement("div");
|
|
37668
37844
|
Object.assign(this.plot.style, { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" });
|
|
37669
|
-
this.extLayers = rendererLayers().map((def) => {
|
|
37670
|
-
const canvas = document.createElement("canvas");
|
|
37671
|
-
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
37672
|
-
return { def, instance: def.create(), canvas };
|
|
37673
|
-
});
|
|
37845
|
+
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
37674
37846
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
37675
37847
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
37676
37848
|
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
@@ -37687,6 +37859,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37687
37859
|
this.volumeRenderer.mount(this.volumeCanvas);
|
|
37688
37860
|
this.vpvrRenderer.mount(this.vpvrCanvas);
|
|
37689
37861
|
for (const l of this.extLayers) l.instance.mount(l.canvas);
|
|
37862
|
+
for (const m of this.scene.indicators.values()) this.ensureInstanceLayer(m);
|
|
37690
37863
|
this.chrome.mount(this.chromeCanvas);
|
|
37691
37864
|
this.crosshairLayer.mount(this.cursorCanvas);
|
|
37692
37865
|
this.coords.setViewport(defaultViewport());
|
|
@@ -38279,6 +38452,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38279
38452
|
this.refreshAnchorOffset(model);
|
|
38280
38453
|
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
38281
38454
|
else this.scene.assignIndicatorZ(model.id);
|
|
38455
|
+
this.ensureInstanceLayer(model);
|
|
38282
38456
|
if (model.legend !== false) {
|
|
38283
38457
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
38284
38458
|
native: !!model.native,
|
|
@@ -38322,6 +38496,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38322
38496
|
this.scene.vpvrLayer = null;
|
|
38323
38497
|
}
|
|
38324
38498
|
this.scene.indicators.delete(handle.id);
|
|
38499
|
+
this.dropInstanceLayer(handle.id);
|
|
38325
38500
|
this.scene.forgetIndicatorZ(handle.id);
|
|
38326
38501
|
this.scene.forgetAnchorOffset(handle.id);
|
|
38327
38502
|
this.scene.dropIndicatorScale(handle.id);
|
|
@@ -39115,29 +39290,31 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39115
39290
|
up: bar.close >= bar.open
|
|
39116
39291
|
};
|
|
39117
39292
|
}
|
|
39118
|
-
/** One group per indicator (name = indicator title), each with a row per
|
|
39293
|
+
/** One group per indicator (name = indicator title), each with a row per plot shown in the data window. */
|
|
39119
39294
|
dataWindowGroups(idx, pricePane) {
|
|
39120
39295
|
const groups = [];
|
|
39121
39296
|
for (const model of this.scene.indicators.values()) {
|
|
39122
|
-
const rows = this.
|
|
39297
|
+
const rows = this.readoutRowsFor(model, idx, pricePane, "dataWindow");
|
|
39123
39298
|
if (rows.length) groups.push({ name: model.title, rows });
|
|
39124
39299
|
}
|
|
39125
39300
|
return groups;
|
|
39126
39301
|
}
|
|
39127
|
-
/** One indicator's readout at bar `idx
|
|
39128
|
-
|
|
39302
|
+
/** One indicator's readout at bar `idx` for one value surface (the legend or the data window):
|
|
39303
|
+
* a row per plot shown on that surface, formatted on its pane's scale. A plot's `display`
|
|
39304
|
+
* decides per surface — a data-window-only plot has a row here and none in the legend. */
|
|
39305
|
+
readoutRowsFor(model, idx, pricePane, surface) {
|
|
39129
39306
|
if (model.native?.type === "volume") return this.volumeReadoutRows(model, idx);
|
|
39130
39307
|
const pane = (model.paneId ? this.scene.panes.get(model.paneId) : null) ?? pricePane;
|
|
39131
39308
|
const off = this.scene.offsetOf(model.id);
|
|
39132
39309
|
const rows = [];
|
|
39133
39310
|
for (const s of model.series) {
|
|
39311
|
+
if (!seriesShownOn(s, surface)) continue;
|
|
39134
39312
|
let value;
|
|
39135
39313
|
let color;
|
|
39136
39314
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
39137
39315
|
value = s.bars[idx - off]?.close;
|
|
39138
39316
|
color = s.style?.up ?? this.theme.upColor;
|
|
39139
39317
|
} else if (isLineLikeSeries(s)) {
|
|
39140
|
-
if (s.visible === false) continue;
|
|
39141
39318
|
value = s.points[idx - off]?.value;
|
|
39142
39319
|
color = s.points[idx - off]?.color ?? s.style.color;
|
|
39143
39320
|
} else {
|
|
@@ -39160,9 +39337,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39160
39337
|
const color = bar.close >= bar.open ? cfg?.upColor ?? this.theme.upColor : cfg?.downColor ?? this.theme.downColor;
|
|
39161
39338
|
return [{ label: model.title, value: formatVolume(vol), color }];
|
|
39162
39339
|
}
|
|
39163
|
-
/** Refresh the plot values beside every legend title — the same
|
|
39164
|
-
*
|
|
39165
|
-
*
|
|
39340
|
+
/** Refresh the plot values beside every legend title — the same bar the data window reads
|
|
39341
|
+
* (crosshair bar, else the latest bar), pushed per paint, filtered to the plots whose
|
|
39342
|
+
* `display` includes the legend. A hidden indicator has no scene model, so its row is
|
|
39343
|
+
* absent from the map and its readout clears. */
|
|
39166
39344
|
updateLegendValues() {
|
|
39167
39345
|
if (!this.inputsUI) return;
|
|
39168
39346
|
const n = this.bars.length;
|
|
@@ -39171,7 +39349,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39171
39349
|
const idx = this.hoverLogical != null ? this.hoverLogical : n - 1;
|
|
39172
39350
|
const pricePane = this.dataWindowPricePane();
|
|
39173
39351
|
for (const model of this.scene.indicators.values()) {
|
|
39174
|
-
values.set(model.id, this.
|
|
39352
|
+
values.set(model.id, this.readoutRowsFor(model, idx, pricePane, "legend").map((r) => ({ value: r.value, color: r.color })));
|
|
39175
39353
|
}
|
|
39176
39354
|
}
|
|
39177
39355
|
this.inputsUI.setPlotValues(values);
|
|
@@ -39197,9 +39375,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39197
39375
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
39198
39376
|
for (const l of this.extLayers) {
|
|
39199
39377
|
if (!l.def.repaintOnCursor) continue;
|
|
39200
|
-
const lp = this.layerPane(l
|
|
39378
|
+
const lp = this.layerPane(l) ?? pane;
|
|
39201
39379
|
if (lp.collapsed) continue;
|
|
39202
|
-
l.instance.render(this.extLayerArgs(l
|
|
39380
|
+
l.instance.render(this.extLayerArgs(l, lp.scale, lp.bounds, nowMs));
|
|
39203
39381
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
39204
39382
|
}
|
|
39205
39383
|
}
|
|
@@ -39208,13 +39386,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39208
39386
|
if (canvas.width === 0 || canvas.height === 0) return;
|
|
39209
39387
|
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
39210
39388
|
}
|
|
39211
|
-
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
39212
|
-
|
|
39389
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths).
|
|
39390
|
+
* Data + pending come from the entry's channel (per instance for a dedicated entry);
|
|
39391
|
+
* the settings bag is the TYPE's — a chart type's dialog values, one bag per type. */
|
|
39392
|
+
extLayerArgs(l, scale, bounds, nowMs) {
|
|
39213
39393
|
return {
|
|
39214
39394
|
bars: this.scene.bars,
|
|
39215
|
-
data: this.scene.nativeData.get(
|
|
39216
|
-
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
39217
|
-
pending: this.scene.nativePending.get(
|
|
39395
|
+
data: this.scene.nativeData.get(l.channel),
|
|
39396
|
+
settings: this.scene.nativeData.get(`${l.def.id}-settings`) ?? {},
|
|
39397
|
+
pending: this.scene.nativePending.get(l.channel) ?? [],
|
|
39218
39398
|
coords: this.coords,
|
|
39219
39399
|
scale,
|
|
39220
39400
|
bounds,
|
|
@@ -39257,12 +39437,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39257
39437
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
39258
39438
|
let folded = null;
|
|
39259
39439
|
for (const l of this.extLayers) {
|
|
39260
|
-
const lp = this.layerPane(l
|
|
39440
|
+
const lp = this.layerPane(l) ?? pane;
|
|
39261
39441
|
if (lp.collapsed) {
|
|
39262
39442
|
this.clearLayerCanvas(l.canvas);
|
|
39263
39443
|
continue;
|
|
39264
39444
|
}
|
|
39265
|
-
const args = this.extLayerArgs(l
|
|
39445
|
+
const args = this.extLayerArgs(l, lp.scale, lp.bounds, nowMs);
|
|
39266
39446
|
l.instance.render(args);
|
|
39267
39447
|
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
39268
39448
|
if (this.animZoom.on && l.instance.animating?.()) this.animator.start();
|
|
@@ -39426,7 +39606,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39426
39606
|
const or2 = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
39427
39607
|
if (or2) dr = dr ? { min: Math.min(dr.min, or2.min), max: Math.max(dr.max, or2.max) } : or2;
|
|
39428
39608
|
}
|
|
39429
|
-
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
39609
|
+
const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels));
|
|
39430
39610
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
39431
39611
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
39432
39612
|
pane.axisFormat = void 0;
|
|
@@ -39545,19 +39725,60 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39545
39725
|
}
|
|
39546
39726
|
return null;
|
|
39547
39727
|
}
|
|
39548
|
-
/**
|
|
39549
|
-
|
|
39550
|
-
|
|
39551
|
-
|
|
39552
|
-
|
|
39728
|
+
/** One transparent, pointer-transparent SDK layer canvas covering the plot. */
|
|
39729
|
+
createLayerCanvas() {
|
|
39730
|
+
const canvas = document.createElement("canvas");
|
|
39731
|
+
Object.assign(canvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
39732
|
+
return canvas;
|
|
39733
|
+
}
|
|
39734
|
+
/**
|
|
39735
|
+
* Mount the DEDICATED layer of a native indicator that names its own channel (an
|
|
39736
|
+
* instance of a multi-instance type painting through a registered layer): a fresh
|
|
39737
|
+
* instance of the layer's definition on its own canvas, reading that channel. Idempotent
|
|
39738
|
+
* — a show after hide re-mounts the model, and the layer (kept across the hide, like the
|
|
39739
|
+
* z key) is simply reused. Skipped while the renderer itself is unmounted (`mount()`
|
|
39740
|
+
* catches up from the scene).
|
|
39741
|
+
*/
|
|
39742
|
+
ensureInstanceLayer(model) {
|
|
39743
|
+
const channel = model.native?.channel;
|
|
39744
|
+
if (!channel || !this.plot) return;
|
|
39745
|
+
if (this.extLayers.some((l) => l.owner === model.id)) return;
|
|
39746
|
+
const base = this.extLayers.find((l) => l.owner === null && l.def.id === model.native.type);
|
|
39747
|
+
if (!base) return;
|
|
39748
|
+
const canvas = this.createLayerCanvas();
|
|
39749
|
+
canvas.width = this.dataCanvas.width;
|
|
39750
|
+
canvas.height = this.dataCanvas.height;
|
|
39751
|
+
canvas.style.width = this.dataCanvas.style.width || "100%";
|
|
39752
|
+
canvas.style.height = this.dataCanvas.style.height || "100%";
|
|
39753
|
+
const entry = { def: base.def, instance: base.def.create(), canvas, channel, owner: model.id };
|
|
39754
|
+
this.extLayers.push(entry);
|
|
39755
|
+
this.plot.insertBefore(canvas, this.chromeCanvas);
|
|
39756
|
+
entry.instance.mount(canvas);
|
|
39757
|
+
}
|
|
39758
|
+
/** Tear down the dedicated layer of a removed indicator (a hide keeps it). */
|
|
39759
|
+
dropInstanceLayer(indicatorId) {
|
|
39760
|
+
const i = this.extLayers.findIndex((l2) => l2.owner === indicatorId);
|
|
39761
|
+
if (i < 0) return;
|
|
39762
|
+
const [l] = this.extLayers.splice(i, 1);
|
|
39763
|
+
l.instance.destroy?.();
|
|
39764
|
+
l.canvas.remove();
|
|
39765
|
+
}
|
|
39766
|
+
/** The mounted native indicator that OWNS an SDK layer entry. A dedicated entry is
|
|
39767
|
+
* owned by the instance it was mounted for; the base entry by the mounted indicator
|
|
39768
|
+
* whose type equals the layer id and that has no channel of its own (the id doubles
|
|
39769
|
+
* as the data channel, so the pairing is the SDK's own contract). Null for chart-type
|
|
39770
|
+
* channels and while the owner is hidden (a hidden indicator leaves the scene; its
|
|
39771
|
+
* cleared data channel paints nothing anyway). */
|
|
39772
|
+
layerOwner(l) {
|
|
39773
|
+
if (l.owner !== null) return this.scene.indicators.get(l.owner) ?? null;
|
|
39553
39774
|
for (const m of this.scene.indicators.values()) {
|
|
39554
|
-
if (m.native?.type ===
|
|
39775
|
+
if (m.native?.type === l.def.id && !m.native.channel) return m;
|
|
39555
39776
|
}
|
|
39556
39777
|
return null;
|
|
39557
39778
|
}
|
|
39558
39779
|
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
39559
|
-
layerPane(
|
|
39560
|
-
const owner = this.layerOwner(
|
|
39780
|
+
layerPane(l) {
|
|
39781
|
+
const owner = this.layerOwner(l);
|
|
39561
39782
|
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
|
|
39562
39783
|
return this.scene.panes.get(paneId) ?? null;
|
|
39563
39784
|
}
|
|
@@ -39565,12 +39786,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39565
39786
|
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
39566
39787
|
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
39567
39788
|
orderedLayerCanvases() {
|
|
39568
|
-
const byId = new Map(this.extLayers.map((l) => [l.
|
|
39789
|
+
const byId = new Map(this.extLayers.map((l) => [l.channel, l.canvas]));
|
|
39569
39790
|
const { below, above } = stackLayers(
|
|
39570
39791
|
this.extLayers.map((l) => {
|
|
39571
|
-
const owner = this.layerOwner(l
|
|
39792
|
+
const owner = this.layerOwner(l);
|
|
39572
39793
|
return {
|
|
39573
|
-
id: l.
|
|
39794
|
+
id: l.channel,
|
|
39574
39795
|
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
39575
39796
|
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
39576
39797
|
};
|
|
@@ -39592,7 +39813,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39592
39813
|
syncLayerCanvasOrder() {
|
|
39593
39814
|
if (this.extLayers.length === 0 || !this.plot) return;
|
|
39594
39815
|
const { below, above } = this.orderedLayerCanvases();
|
|
39595
|
-
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).
|
|
39816
|
+
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(",");
|
|
39596
39817
|
if (sig === this.layerOrderSig) return;
|
|
39597
39818
|
this.layerOrderSig = sig;
|
|
39598
39819
|
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
@@ -39616,6 +39837,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39616
39837
|
(m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
|
|
39617
39838
|
);
|
|
39618
39839
|
}
|
|
39840
|
+
/**
|
|
39841
|
+
* True when SDK-layer content on the price pane paints at BAR PRICES without
|
|
39842
|
+
* contributing a series: the active chart type's own layer, or an overlay layer native
|
|
39843
|
+
* (a series-less model whose type names a mounted layer). Hiding the candles must then
|
|
39844
|
+
* keep the bars in the price scale — with nothing else on the pane the scale would fall
|
|
39845
|
+
* to the {0,1} placeholder and every such layer would paint off-screen and vanish.
|
|
39846
|
+
*/
|
|
39847
|
+
priceLayersAnchoredToBars(masterModels) {
|
|
39848
|
+
if (this.extLayers.some((l) => l.owner === null && l.def.id === this.scene.priceStyle)) return true;
|
|
39849
|
+
return masterModels.some((m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type));
|
|
39850
|
+
}
|
|
39619
39851
|
/** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
|
|
39620
39852
|
* band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
|
|
39621
39853
|
* Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
|
|
@@ -44355,6 +44587,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
44355
44587
|
exports.rendererLayers = rendererLayers;
|
|
44356
44588
|
exports.resolveEngines = resolveEngines;
|
|
44357
44589
|
exports.resolveTheme = resolveTheme;
|
|
44590
|
+
exports.seriesInScale = seriesInScale;
|
|
44591
|
+
exports.seriesShownOn = seriesShownOn;
|
|
44358
44592
|
exports.settingsRowValueKeys = settingsRowValueKeys;
|
|
44359
44593
|
exports.sharedBarStore = sharedBarStore;
|
|
44360
44594
|
exports.sidePanels = sidePanels;
|