@luxalgo/vela 0.8.3 → 0.8.4
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-wbtJ3-Sa.d.cts → DataProvider-Bw58CjYy.d.cts} +1 -1
- package/dist/{DataProvider-oH78tYXL.d.ts → DataProvider-Byo8pEv0.d.ts} +1 -1
- package/dist/{chunk-KWI3YDBW.js → chunk-ALTGHKIM.js} +584 -25
- package/dist/{chunk-TO3GIK7Z.js → chunk-AUOB7A5Y.js} +2 -2
- package/dist/{chunk-VHGACEHO.js → chunk-I2U4NZD2.js} +7 -1
- package/dist/{chunk-2IPXYM4B.js → chunk-MOJ4T26N.js} +88 -5
- package/dist/{chunk-HMHA733L.js → chunk-UNUVPQFW.js} +1924 -734
- package/dist/{chunk-G7B7ZCBF.js → chunk-WY5H4S4I.js} +1 -1
- package/dist/{contributions-BhOBRenr.d.cts → contributions-BY3zSYiZ.d.ts} +56 -5
- package/dist/{contributions-HGqnNb_P.d.ts → contributions-DOm40g5-.d.cts} +56 -5
- package/dist/index.cjs +2583 -828
- package/dist/index.d.cts +9 -15
- package/dist/index.d.ts +9 -15
- package/dist/index.js +4 -4
- package/dist/{options-lMUwmRwh.d.cts → options-CSXbjMM5.d.cts} +64 -4
- package/dist/{options-lMUwmRwh.d.ts → options-CSXbjMM5.d.ts} +64 -4
- package/dist/{plugin-Cr2Pj8q8.d.ts → plugin-BDxUhGO3.d.ts} +3 -3
- package/dist/{plugin-Do5J5xuF.d.cts → plugin-CiQVyETg.d.cts} +3 -3
- package/dist/plugin.cjs +555 -17
- 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-B-2xGlB_.d.cts → statusline-model-C9tYXuux.d.ts} +19 -4
- package/dist/{statusline-model-CY1wzxHg.d.ts → statusline-model-DUBX6Fib.d.cts} +19 -4
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +2590 -835
- package/dist/vela.global.min.js +124 -54
- package/dist/widget.cjs +2807 -969
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +2807 -969
- package/dist/workspace.d.cts +19 -5
- package/dist/workspace.d.ts +19 -5
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -392,6 +392,39 @@ function defaultText(value = "") {
|
|
|
392
392
|
return { value, size: "normal", hAlign: "left", vAlign: "top" };
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
// src/core/drawings/visibility.ts
|
|
396
|
+
var MIN = 6e4;
|
|
397
|
+
var HOUR = 60 * MIN;
|
|
398
|
+
var DAY = 24 * HOUR;
|
|
399
|
+
var TIMEFRAME_BANDS = [
|
|
400
|
+
{ key: "s", label: "Sec", fromMs: 0 },
|
|
401
|
+
{ key: "1", label: "1m", fromMs: MIN },
|
|
402
|
+
{ key: "5", label: "5m", fromMs: 5 * MIN },
|
|
403
|
+
{ key: "15", label: "15m", fromMs: 15 * MIN },
|
|
404
|
+
{ key: "60", label: "1H", fromMs: HOUR },
|
|
405
|
+
{ key: "240", label: "4H", fromMs: 4 * HOUR },
|
|
406
|
+
{ key: "D", label: "D", fromMs: DAY },
|
|
407
|
+
{ key: "W", label: "W", fromMs: 7 * DAY },
|
|
408
|
+
{ key: "M", label: "M", fromMs: 28 * DAY }
|
|
409
|
+
];
|
|
410
|
+
var BAND_KEYS = new Set(TIMEFRAME_BANDS.map((b) => b.key));
|
|
411
|
+
function timeframeBandOf(barMs) {
|
|
412
|
+
if (!(barMs > 0)) return null;
|
|
413
|
+
let key = null;
|
|
414
|
+
for (const band of TIMEFRAME_BANDS) if (barMs >= band.fromMs) key = band.key;
|
|
415
|
+
return key;
|
|
416
|
+
}
|
|
417
|
+
function shownOnTimeframe(showOn, barMs) {
|
|
418
|
+
if (!showOn) return true;
|
|
419
|
+
const band = timeframeBandOf(barMs);
|
|
420
|
+
return band === null || showOn.includes(band);
|
|
421
|
+
}
|
|
422
|
+
function sanitizeShowOn(v) {
|
|
423
|
+
if (!Array.isArray(v)) return void 0;
|
|
424
|
+
const wanted = new Set(v.filter((k) => typeof k === "string" && BAND_KEYS.has(k)));
|
|
425
|
+
return TIMEFRAME_BANDS.map((b) => b.key).filter((k) => wanted.has(k));
|
|
426
|
+
}
|
|
427
|
+
|
|
395
428
|
// src/core/drawings/Drawing.ts
|
|
396
429
|
var _Drawing = class _Drawing {
|
|
397
430
|
// Public so the registry can construct concrete subclasses; `abstract` still
|
|
@@ -408,6 +441,7 @@ var _Drawing = class _Drawing {
|
|
|
408
441
|
this.locked = init.locked ?? false;
|
|
409
442
|
this.visible = init.visible ?? true;
|
|
410
443
|
this.zIndex = init.zIndex ?? 0;
|
|
444
|
+
this.showOn = sanitizeShowOn(init.showOn);
|
|
411
445
|
this.createdAt = init.createdAt ?? Date.now();
|
|
412
446
|
if (init.props) this.readProps(init.props);
|
|
413
447
|
}
|
|
@@ -494,7 +528,10 @@ var _Drawing = class _Drawing {
|
|
|
494
528
|
visible: this.visible,
|
|
495
529
|
zIndex: this.zIndex,
|
|
496
530
|
createdAt: this.createdAt,
|
|
497
|
-
props: this.writeProps()
|
|
531
|
+
props: this.writeProps(),
|
|
532
|
+
// Always present (undefined = every timeframe) so an edit can lift a limit; JSON
|
|
533
|
+
// drops the undefined value, so unlimited drawings save as before.
|
|
534
|
+
showOn: this.showOn ? [...this.showOn] : void 0
|
|
498
535
|
};
|
|
499
536
|
}
|
|
500
537
|
/** Per-type extras to serialize into `props` (override in subclasses). */
|
|
@@ -812,9 +849,16 @@ var TrendLine = class extends Drawing {
|
|
|
812
849
|
if (y1 == null || y2 == null) return null;
|
|
813
850
|
return [proj.xOf(a.time), y1, proj.xOf(b.time), y2];
|
|
814
851
|
}
|
|
852
|
+
/** Which ends run on past the anchors. */
|
|
853
|
+
extension() {
|
|
854
|
+
const { extendLeft: l, extendRight: r } = this.style;
|
|
855
|
+
return l && r ? "both" : l ? "left" : r ? "right" : "none";
|
|
856
|
+
}
|
|
815
857
|
hitTest(px, py, proj, tol) {
|
|
816
858
|
const p = this.pixels(proj);
|
|
817
|
-
|
|
859
|
+
if (!p) return false;
|
|
860
|
+
const [x1, y1, x2, y2] = extendRay(p[0], p[1], p[2], p[3], this.extension(), proj.width, proj.height);
|
|
861
|
+
return distToSegment(px, py, x1, y1, x2, y2) <= tol;
|
|
818
862
|
}
|
|
819
863
|
handlePoints(proj) {
|
|
820
864
|
const p = this.pixels(proj);
|
|
@@ -841,6 +885,8 @@ var TrendLine = class extends Drawing {
|
|
|
841
885
|
fields: [
|
|
842
886
|
...LINE_FIELDS,
|
|
843
887
|
{ path: "style.arrowRight", label: "Arrow", kind: "boolean", group: "line" },
|
|
888
|
+
{ path: "style.extendLeft", label: "Extend left", kind: "boolean", group: "line" },
|
|
889
|
+
{ path: "style.extendRight", label: "Extend right", kind: "boolean", group: "line" },
|
|
844
890
|
...TEXT_FIELDS
|
|
845
891
|
]
|
|
846
892
|
};
|
|
@@ -3300,6 +3346,14 @@ var FibRatios = class extends Drawing {
|
|
|
3300
3346
|
|
|
3301
3347
|
// src/core/drawings/types/FibLevels.ts
|
|
3302
3348
|
var FibLevels = class extends FibRatios {
|
|
3349
|
+
// `declare`d: the base constructor reads saved props before these could be initialized.
|
|
3350
|
+
constructor(init) {
|
|
3351
|
+
super(init);
|
|
3352
|
+
if (this.showRatios === void 0) this.showRatios = true;
|
|
3353
|
+
if (this.showPrices === void 0) this.showPrices = true;
|
|
3354
|
+
if (this.labelSide === void 0) this.labelSide = "left";
|
|
3355
|
+
if (this.background === void 0) this.background = true;
|
|
3356
|
+
}
|
|
3303
3357
|
/** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
|
|
3304
3358
|
levelPrice(ratio, p1, p2) {
|
|
3305
3359
|
return p1 + ratio * (p2 - p1);
|
|
@@ -3311,8 +3365,8 @@ var FibLevels = class extends FibRatios {
|
|
|
3311
3365
|
if (!a || !b) return null;
|
|
3312
3366
|
const xa = proj.xOf(a.time);
|
|
3313
3367
|
const xb = proj.xOf(b.time);
|
|
3314
|
-
const x1 = Math.min(xa, xb);
|
|
3315
|
-
const x2 = Math.max(xa, xb);
|
|
3368
|
+
const x1 = this.style.extendLeft ? 0 : Math.min(xa, xb);
|
|
3369
|
+
const x2 = this.style.extendRight ? proj.width : Math.max(xa, xb);
|
|
3316
3370
|
const out = [];
|
|
3317
3371
|
for (const lv of this.levels) {
|
|
3318
3372
|
if (!lv.enabled) continue;
|
|
@@ -3326,6 +3380,7 @@ var FibLevels = class extends FibRatios {
|
|
|
3326
3380
|
entryLines(proj) {
|
|
3327
3381
|
const lines = this.levelLines(proj);
|
|
3328
3382
|
if (!lines) return null;
|
|
3383
|
+
const right = this.labelSide === "right";
|
|
3329
3384
|
return lines.map((l) => ({
|
|
3330
3385
|
color: l.color,
|
|
3331
3386
|
label: l.label,
|
|
@@ -3333,16 +3388,24 @@ var FibLevels = class extends FibRatios {
|
|
|
3333
3388
|
y1: l.y,
|
|
3334
3389
|
x2: l.x2,
|
|
3335
3390
|
y2: l.y,
|
|
3336
|
-
numberText:
|
|
3337
|
-
numberX: l.x1 + 4,
|
|
3391
|
+
numberText: this.levelText(l.ratio, l.price),
|
|
3392
|
+
numberX: right ? l.x2 - 4 : l.x1 + 4,
|
|
3338
3393
|
numberY: l.y - 7,
|
|
3339
|
-
numberAlign: "left",
|
|
3394
|
+
numberAlign: right ? "right" : "left",
|
|
3340
3395
|
labelX: (l.x1 + l.x2) / 2,
|
|
3341
3396
|
labelY: l.y - 7
|
|
3342
3397
|
}));
|
|
3343
3398
|
}
|
|
3399
|
+
/** The text beside a level: its ratio, its price, both, or nothing. */
|
|
3400
|
+
levelText(ratio, price) {
|
|
3401
|
+
const p = price.toFixed(2);
|
|
3402
|
+
if (this.showRatios && this.showPrices) return `${ratio} (${p})`;
|
|
3403
|
+
if (this.showRatios) return String(ratio);
|
|
3404
|
+
return this.showPrices ? p : "";
|
|
3405
|
+
}
|
|
3344
3406
|
fillBands(proj) {
|
|
3345
|
-
|
|
3407
|
+
if (!this.background) return [];
|
|
3408
|
+
const lines = this.levelLines(proj)?.sort((p, q) => p.ratio - q.ratio);
|
|
3346
3409
|
if (!lines) return [];
|
|
3347
3410
|
const bands = [];
|
|
3348
3411
|
for (let i = 1; i < lines.length; i += 1) {
|
|
@@ -3360,6 +3423,26 @@ var FibLevels = class extends FibRatios {
|
|
|
3360
3423
|
if (prices.length === 0) return null;
|
|
3361
3424
|
return { min: Math.min(...prices), max: Math.max(...prices) };
|
|
3362
3425
|
}
|
|
3426
|
+
schema() {
|
|
3427
|
+
const base = super.schema();
|
|
3428
|
+
return {
|
|
3429
|
+
fields: [
|
|
3430
|
+
...base.fields,
|
|
3431
|
+
{ path: "style.extendLeft", label: "Extend left", kind: "boolean", group: "line" },
|
|
3432
|
+
{ path: "style.extendRight", label: "Extend right", kind: "boolean", group: "line" }
|
|
3433
|
+
]
|
|
3434
|
+
};
|
|
3435
|
+
}
|
|
3436
|
+
writeProps() {
|
|
3437
|
+
return { ...super.writeProps(), showRatios: this.showRatios, showPrices: this.showPrices, labelSide: this.labelSide, background: this.background };
|
|
3438
|
+
}
|
|
3439
|
+
readProps(props) {
|
|
3440
|
+
super.readProps(props);
|
|
3441
|
+
if (typeof props.showRatios === "boolean") this.showRatios = props.showRatios;
|
|
3442
|
+
if (typeof props.showPrices === "boolean") this.showPrices = props.showPrices;
|
|
3443
|
+
if (props.labelSide === "left" || props.labelSide === "right") this.labelSide = props.labelSide;
|
|
3444
|
+
if (typeof props.background === "boolean") this.background = props.background;
|
|
3445
|
+
}
|
|
3363
3446
|
};
|
|
3364
3447
|
|
|
3365
3448
|
// src/core/drawings/levelPalette.ts
|
|
@@ -3405,14 +3488,31 @@ var RATIO_COLORS = [
|
|
|
3405
3488
|
[0.75, LEVEL_BLUE],
|
|
3406
3489
|
[0.786, LEVEL_BLUE],
|
|
3407
3490
|
[1, LEVEL_ANCHOR],
|
|
3491
|
+
[0.705, LEVEL_AMBER],
|
|
3492
|
+
[0.886, LEVEL_PURPLE],
|
|
3408
3493
|
[1.272, LEVEL_BLUE],
|
|
3409
3494
|
[1.382, LEVEL_ORANGE],
|
|
3495
|
+
[1.414, LEVEL_AQUA],
|
|
3410
3496
|
[1.618, LEVEL_RED],
|
|
3411
3497
|
[2, LEVEL_ANCHOR],
|
|
3498
|
+
[2.272, LEVEL_VIOLET],
|
|
3412
3499
|
[2.382, LEVEL_ORANGE],
|
|
3500
|
+
[2.414, LEVEL_PINK],
|
|
3413
3501
|
[2.618, LEVEL_ORANGE],
|
|
3414
3502
|
[3, LEVEL_ANCHOR],
|
|
3503
|
+
[3.272, LEVEL_CORAL],
|
|
3504
|
+
[3.618, LEVEL_PURPLE],
|
|
3415
3505
|
[4.236, LEVEL_GREEN],
|
|
3506
|
+
[4.618, LEVEL_PINK],
|
|
3507
|
+
// Beyond the swing's end (targets): blues, deepening with distance.
|
|
3508
|
+
[-0.27, LEVEL_CYAN],
|
|
3509
|
+
[-0.272, LEVEL_CYAN],
|
|
3510
|
+
[-0.62, LEVEL_BLUE],
|
|
3511
|
+
[-0.618, LEVEL_BLUE],
|
|
3512
|
+
[-1, LEVEL_AQUA],
|
|
3513
|
+
[-1.5, LEVEL_VIOLET],
|
|
3514
|
+
[-2, LEVEL_PURPLE],
|
|
3515
|
+
[-2.5, LEVEL_PINK],
|
|
3416
3516
|
[6.854, LEVEL_TEAL],
|
|
3417
3517
|
[11.09, LEVEL_BLUE]
|
|
3418
3518
|
];
|
|
@@ -3439,8 +3539,41 @@ function cycleLevels(count, enabledCount = count) {
|
|
|
3439
3539
|
}));
|
|
3440
3540
|
}
|
|
3441
3541
|
|
|
3542
|
+
// src/core/drawings/fibPresets.ts
|
|
3543
|
+
var LADDER = [0, 0.236, 0.382, 0.5, 0.618, 0.705, 0.786, 0.886, 1, 1.272, 1.414, 1.618, 2, 2.272, 2.414, 2.618, 3, 3.272, 3.618, 4.236, 4.618, -0.272, -0.618, -1];
|
|
3544
|
+
var OTE_LADDER = [0, 0.236, 0.382, 0.5, 0.62, 0.705, 0.79, 0.886, 1, 1.272, 1.414, 1.618, 2, 2.618, 3, 3.618, 4.236, 4.618, -0.27, -0.62, -1, -1.5, -2, -2.5];
|
|
3545
|
+
function ladder(ratios, on, colors = {}) {
|
|
3546
|
+
return fibLevels(ratios.map((ratio) => ({ ratio, enabled: on.includes(ratio), color: colors[String(ratio)] })));
|
|
3547
|
+
}
|
|
3548
|
+
var FIB_PRESETS = [
|
|
3549
|
+
{
|
|
3550
|
+
key: "classic",
|
|
3551
|
+
label: "Classic",
|
|
3552
|
+
reverse: false,
|
|
3553
|
+
levels: ladder(LADDER, [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1])
|
|
3554
|
+
},
|
|
3555
|
+
{
|
|
3556
|
+
key: "extension",
|
|
3557
|
+
label: "Extension",
|
|
3558
|
+
reverse: true,
|
|
3559
|
+
levels: ladder(LADDER, [0, 1, 1.272, 1.414, 1.618, 2, 2.618, 3.618, 4.236])
|
|
3560
|
+
},
|
|
3561
|
+
{
|
|
3562
|
+
key: "ote",
|
|
3563
|
+
label: "OTE",
|
|
3564
|
+
reverse: false,
|
|
3565
|
+
// The entry zone reads as one band.
|
|
3566
|
+
levels: ladder(OTE_LADDER, [0, 0.5, 0.62, 0.705, 0.79, 1, -0.27, -0.62], { "0.62": LEVEL_AMBER, "0.79": LEVEL_AMBER })
|
|
3567
|
+
}
|
|
3568
|
+
];
|
|
3569
|
+
function matchFibPreset(levels, reverse) {
|
|
3570
|
+
const key = (ls) => JSON.stringify(ls.map((l) => [l.ratio, l.color, l.enabled, l.label ?? ""]));
|
|
3571
|
+
const mine = key(levels);
|
|
3572
|
+
return FIB_PRESETS.find((p) => p.reverse === reverse && key(p.levels) === mine)?.key ?? null;
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3442
3575
|
// src/core/drawings/types/FibRetracement.ts
|
|
3443
|
-
var LEVELS =
|
|
3576
|
+
var LEVELS = FIB_PRESETS[0].levels;
|
|
3444
3577
|
var FibRetracement = class extends FibLevels {
|
|
3445
3578
|
constructor(init) {
|
|
3446
3579
|
super(init);
|
|
@@ -3463,7 +3596,11 @@ var FibRetracement = class extends FibLevels {
|
|
|
3463
3596
|
};
|
|
3464
3597
|
|
|
3465
3598
|
// src/core/drawings/types/FibExtension.ts
|
|
3466
|
-
var LEVELS2 =
|
|
3599
|
+
var LEVELS2 = ladder(
|
|
3600
|
+
[0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.272, 1.414, 1.618, 2, 2.272, 2.414, 2.618, 3, 3.272, 3.618, 4.236, 4.618],
|
|
3601
|
+
[0, 0.382, 0.618, 1, 1.272, 1.618, 2.618],
|
|
3602
|
+
{ "2.618": LEVEL_PURPLE }
|
|
3603
|
+
);
|
|
3467
3604
|
var FibExtension = class extends FibLevels {
|
|
3468
3605
|
constructor() {
|
|
3469
3606
|
super(...arguments);
|
|
@@ -5050,20 +5187,20 @@ function signed(n) {
|
|
|
5050
5187
|
}
|
|
5051
5188
|
function formatDuration(ms) {
|
|
5052
5189
|
const m = Math.abs(ms);
|
|
5053
|
-
const
|
|
5190
|
+
const DAY3 = 864e5;
|
|
5054
5191
|
const HR = 36e5;
|
|
5055
|
-
const
|
|
5056
|
-
if (m >=
|
|
5057
|
-
const d = Math.floor(m /
|
|
5058
|
-
const h = Math.round(m %
|
|
5192
|
+
const MIN3 = 6e4;
|
|
5193
|
+
if (m >= DAY3) {
|
|
5194
|
+
const d = Math.floor(m / DAY3);
|
|
5195
|
+
const h = Math.round(m % DAY3 / HR);
|
|
5059
5196
|
return h ? `${d}d ${h}h` : `${d}d`;
|
|
5060
5197
|
}
|
|
5061
5198
|
if (m >= HR) {
|
|
5062
5199
|
const h = Math.floor(m / HR);
|
|
5063
|
-
const mn = Math.round(m % HR /
|
|
5200
|
+
const mn = Math.round(m % HR / MIN3);
|
|
5064
5201
|
return mn ? `${h}h ${mn}m` : `${h}h`;
|
|
5065
5202
|
}
|
|
5066
|
-
if (m >=
|
|
5203
|
+
if (m >= MIN3) return `${Math.round(m / MIN3)}m`;
|
|
5067
5204
|
return `${Math.round(m / 1e3)}s`;
|
|
5068
5205
|
}
|
|
5069
5206
|
var MeasureBox = class extends Drawing {
|
|
@@ -5543,6 +5680,11 @@ var MAGNIFIER_TIMEFRAME_OPTIONS = [
|
|
|
5543
5680
|
{ value: "240", label: "4h", ms: 144e5 },
|
|
5544
5681
|
{ value: "D", label: "1D", ms: 864e5 }
|
|
5545
5682
|
];
|
|
5683
|
+
function magnifierTimeframesBelow(chartMs) {
|
|
5684
|
+
if (!(chartMs > 0)) return [...MAGNIFIER_TIMEFRAME_OPTIONS];
|
|
5685
|
+
const lower = MAGNIFIER_TIMEFRAME_OPTIONS.filter((o) => o.ms > 0 && o.ms < chartMs);
|
|
5686
|
+
return lower.length > 0 ? [MAGNIFIER_TIMEFRAME_OPTIONS[0], ...lower] : [];
|
|
5687
|
+
}
|
|
5546
5688
|
function magnifierTimeframeLabel(value) {
|
|
5547
5689
|
const opt = MAGNIFIER_TIMEFRAME_OPTIONS.find((o) => o.value === value);
|
|
5548
5690
|
if (opt) return opt.label;
|
|
@@ -5637,6 +5779,393 @@ var Magnifier = class extends Drawing {
|
|
|
5637
5779
|
}
|
|
5638
5780
|
};
|
|
5639
5781
|
|
|
5782
|
+
// src/core/drawings/priceAction.ts
|
|
5783
|
+
var GAP_REACH = 3;
|
|
5784
|
+
function nearestBarIndex(bars, time) {
|
|
5785
|
+
if (bars.length === 0) return -1;
|
|
5786
|
+
let lo = 0;
|
|
5787
|
+
let hi = bars.length - 1;
|
|
5788
|
+
while (lo < hi) {
|
|
5789
|
+
const mid = lo + hi >> 1;
|
|
5790
|
+
if (bars[mid].time < time) lo = mid + 1;
|
|
5791
|
+
else hi = mid;
|
|
5792
|
+
}
|
|
5793
|
+
if (lo > 0 && Math.abs(bars[lo - 1].time - time) <= Math.abs(bars[lo].time - time)) return lo - 1;
|
|
5794
|
+
return lo;
|
|
5795
|
+
}
|
|
5796
|
+
function gapAt(bars, i) {
|
|
5797
|
+
const prev2 = bars[i - 1];
|
|
5798
|
+
const next = bars[i + 1];
|
|
5799
|
+
if (!prev2 || !next || !bars[i]) return null;
|
|
5800
|
+
if (next.low > prev2.high) return { bull: true, top: next.low, bottom: prev2.high };
|
|
5801
|
+
if (next.high < prev2.low) return { bull: false, top: prev2.low, bottom: next.high };
|
|
5802
|
+
return null;
|
|
5803
|
+
}
|
|
5804
|
+
function nearestGap(bars, i) {
|
|
5805
|
+
for (let k = 0; k <= GAP_REACH; k += 1) {
|
|
5806
|
+
for (const j of k === 0 ? [i] : [i - k, i + k]) {
|
|
5807
|
+
const g = gapAt(bars, j);
|
|
5808
|
+
if (g) return { index: j, ...g };
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
return null;
|
|
5812
|
+
}
|
|
5813
|
+
function gapState(bars, i) {
|
|
5814
|
+
const g = nearestGap(bars, i);
|
|
5815
|
+
if (!g) return null;
|
|
5816
|
+
const { index, bull, top, bottom } = g;
|
|
5817
|
+
const mid = (top + bottom) / 2;
|
|
5818
|
+
let tested = null;
|
|
5819
|
+
let half = null;
|
|
5820
|
+
let filled = null;
|
|
5821
|
+
let inverted = null;
|
|
5822
|
+
for (let j = index + 2; j < bars.length && inverted === null; j += 1) {
|
|
5823
|
+
const b = bars[j];
|
|
5824
|
+
const reach = bull ? b.low : b.high;
|
|
5825
|
+
const into = (v) => bull ? reach <= v : reach >= v;
|
|
5826
|
+
if (tested === null && into(bull ? top : bottom)) tested = j;
|
|
5827
|
+
if (half === null && into(mid)) half = j;
|
|
5828
|
+
if (filled === null && into(bull ? bottom : top)) filled = j;
|
|
5829
|
+
if (filled !== null && (bull ? b.close < bottom : b.close > top)) inverted = j;
|
|
5830
|
+
}
|
|
5831
|
+
return { index, bull, top, bottom, mid, tested, half, filled, inverted };
|
|
5832
|
+
}
|
|
5833
|
+
function blockState(bars, i, range) {
|
|
5834
|
+
const c = bars[i];
|
|
5835
|
+
if (!c) return null;
|
|
5836
|
+
const bull = c.close < c.open;
|
|
5837
|
+
const top = range === "body" ? Math.max(c.open, c.close) : c.high;
|
|
5838
|
+
const bottom = range === "body" ? Math.min(c.open, c.close) : c.low;
|
|
5839
|
+
const mid = (top + bottom) / 2;
|
|
5840
|
+
let left = false;
|
|
5841
|
+
let tested = null;
|
|
5842
|
+
let half = null;
|
|
5843
|
+
let broken = null;
|
|
5844
|
+
let leftAfterBreak = false;
|
|
5845
|
+
let retest = null;
|
|
5846
|
+
for (let j = i + 1; j < bars.length && retest === null; j += 1) {
|
|
5847
|
+
const b = bars[j];
|
|
5848
|
+
if (broken === null) {
|
|
5849
|
+
if (!left) left = bull ? b.low > top : b.high < bottom;
|
|
5850
|
+
else {
|
|
5851
|
+
if (tested === null && (bull ? b.low <= top : b.high >= bottom)) tested = j;
|
|
5852
|
+
if (half === null && (bull ? b.low <= mid : b.high >= mid)) half = j;
|
|
5853
|
+
}
|
|
5854
|
+
if (bull ? b.close < bottom : b.close > top) broken = j;
|
|
5855
|
+
} else if (!leftAfterBreak) leftAfterBreak = bull ? b.high < bottom : b.low > top;
|
|
5856
|
+
else if (bull ? b.high >= bottom : b.low <= top) retest = j;
|
|
5857
|
+
}
|
|
5858
|
+
return { index: i, bull, top, bottom, mid, tested, half, broken, retest };
|
|
5859
|
+
}
|
|
5860
|
+
function liquidityState(bars, i, side) {
|
|
5861
|
+
const c = bars[i];
|
|
5862
|
+
if (!c) return null;
|
|
5863
|
+
const high = side === "high";
|
|
5864
|
+
const price = high ? c.high : c.low;
|
|
5865
|
+
let swept = null;
|
|
5866
|
+
let broken = null;
|
|
5867
|
+
for (let j = i + 1; j < bars.length && broken === null; j += 1) {
|
|
5868
|
+
const b = bars[j];
|
|
5869
|
+
if (high ? b.close > price : b.close < price) broken = j;
|
|
5870
|
+
else if (swept === null && (high ? b.high > price : b.low < price)) swept = j;
|
|
5871
|
+
}
|
|
5872
|
+
return { index: i, high, price, swept, broken };
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
// src/core/drawings/types/PriceActionZone.ts
|
|
5876
|
+
var candlesSeen = /* @__PURE__ */ new WeakMap();
|
|
5877
|
+
var PriceActionTool = class extends Drawing {
|
|
5878
|
+
anchorSchema() {
|
|
5879
|
+
return { min: 1, max: 1, slots: [{ role: "candle", free: "x" }] };
|
|
5880
|
+
}
|
|
5881
|
+
/** The chart's candles, and the one nearest the anchor. */
|
|
5882
|
+
candles(proj) {
|
|
5883
|
+
const a = this.anchors[0];
|
|
5884
|
+
if (!a) return null;
|
|
5885
|
+
let bars = candlesSeen.get(proj);
|
|
5886
|
+
if (!bars) {
|
|
5887
|
+
bars = proj.barsInRange?.(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY) ?? [];
|
|
5888
|
+
candlesSeen.set(proj, bars);
|
|
5889
|
+
}
|
|
5890
|
+
return bars.length > 0 ? { bars, at: nearestBarIndex(bars, a.time) } : null;
|
|
5891
|
+
}
|
|
5892
|
+
/** Half a candle's width at bar `i`, in pixels. */
|
|
5893
|
+
halfBar(proj, bars, i) {
|
|
5894
|
+
const a = bars[Math.max(0, i - 1)];
|
|
5895
|
+
const b = bars[Math.min(bars.length - 1, Math.max(1, i))];
|
|
5896
|
+
if (!a || !b || a === b) return 3;
|
|
5897
|
+
return Math.max(1, Math.abs(proj.xOf(b.time) - proj.xOf(a.time)) / 2);
|
|
5898
|
+
}
|
|
5899
|
+
/** Pixel x for the right edge of a zone that stops at bar `end`, or runs on to the chart's edge. */
|
|
5900
|
+
rightEdge(proj, bars, end) {
|
|
5901
|
+
if (end === null) return Math.max(proj.width, proj.xOf(bars[bars.length - 1].time));
|
|
5902
|
+
return proj.xOf(bars[end].time) + this.halfBar(proj, bars, end);
|
|
5903
|
+
}
|
|
5904
|
+
/** The anchor itself, for a tool with nothing on the candles to hang its handle on. */
|
|
5905
|
+
anchorHandle(proj) {
|
|
5906
|
+
const a = this.anchors[0];
|
|
5907
|
+
const y = a ? proj.yOf(a.price, this.paneId) : null;
|
|
5908
|
+
return a && y != null ? [[proj.xOf(a.time), y]] : [];
|
|
5909
|
+
}
|
|
5910
|
+
hitHandle(px, py, proj, tol) {
|
|
5911
|
+
return handleAt(px, py, this.handlePoints(proj), tol + 3);
|
|
5912
|
+
}
|
|
5913
|
+
priceRange() {
|
|
5914
|
+
return null;
|
|
5915
|
+
}
|
|
5916
|
+
};
|
|
5917
|
+
var PriceActionZone = class extends PriceActionTool {
|
|
5918
|
+
/** The zone's pixel layout, or null when the candles don't form one. */
|
|
5919
|
+
zone(proj) {
|
|
5920
|
+
const c = this.candles(proj);
|
|
5921
|
+
const f = c && this.formation(c.bars, c.at);
|
|
5922
|
+
if (!c || !f) return null;
|
|
5923
|
+
const yt = proj.yOf(f.top, this.paneId);
|
|
5924
|
+
const yb = proj.yOf(f.bottom, this.paneId);
|
|
5925
|
+
if (yt == null || yb == null) return null;
|
|
5926
|
+
const { bars } = c;
|
|
5927
|
+
const s = this.settings();
|
|
5928
|
+
const left = (i) => proj.xOf(bars[i].time) - this.halfBar(proj, bars, i);
|
|
5929
|
+
const yTop = Math.min(yt, yb);
|
|
5930
|
+
const yBottom = Math.max(yt, yb);
|
|
5931
|
+
const midY = proj.yOf(f.mid, this.paneId) ?? (yTop + yBottom) / 2;
|
|
5932
|
+
const flip = f.flip && { x1: left(f.flip.from), x2: this.rightEdge(proj, bars, f.flip.to), fill: f.bull ? s.downColor : s.upColor };
|
|
5933
|
+
const edge = s.extend === "tested" ? f.bull ? f.top : f.bottom : s.extend === "half" ? f.mid : f.bull ? f.bottom : f.top;
|
|
5934
|
+
const edgeY = proj.yOf(edge, this.paneId);
|
|
5935
|
+
return {
|
|
5936
|
+
x1: left(f.start),
|
|
5937
|
+
x2: this.rightEdge(proj, bars, f.end),
|
|
5938
|
+
yTop,
|
|
5939
|
+
yBottom,
|
|
5940
|
+
fill: f.bull ? s.upColor : s.downColor,
|
|
5941
|
+
border: s.border,
|
|
5942
|
+
midY: s.midline ? midY : null,
|
|
5943
|
+
faded: f.end !== null && s.fade && !flip,
|
|
5944
|
+
marker: f.end !== null && !flip && edgeY != null ? { x: proj.xOf(bars[f.end].time), y: edgeY } : null,
|
|
5945
|
+
flip,
|
|
5946
|
+
handle: [proj.xOf(bars[f.index].time), midY]
|
|
5947
|
+
};
|
|
5948
|
+
}
|
|
5949
|
+
hitTest(px, py, proj, tol) {
|
|
5950
|
+
const z = this.zone(proj);
|
|
5951
|
+
if (!z) {
|
|
5952
|
+
const a = this.anchors[0];
|
|
5953
|
+
return a != null && Math.abs(px - proj.xOf(a.time)) <= tol;
|
|
5954
|
+
}
|
|
5955
|
+
const inY = py >= z.yTop - tol && py <= z.yBottom + tol;
|
|
5956
|
+
if (inY && px >= z.x1 - tol && px <= z.x2 + tol) return true;
|
|
5957
|
+
return z.flip != null && inY && px >= z.flip.x1 - tol && px <= z.flip.x2 + tol;
|
|
5958
|
+
}
|
|
5959
|
+
handlePoints(proj) {
|
|
5960
|
+
const z = this.zone(proj);
|
|
5961
|
+
return z ? [z.handle] : this.anchorHandle(proj);
|
|
5962
|
+
}
|
|
5963
|
+
bounds(proj) {
|
|
5964
|
+
const z = this.zone(proj);
|
|
5965
|
+
if (!z) return null;
|
|
5966
|
+
const right = Math.min(proj.width, z.flip ? z.flip.x2 : z.x2);
|
|
5967
|
+
return { x: z.x1, y: z.yTop, w: Math.max(1, right - z.x1), h: Math.max(1, z.yBottom - z.yTop) };
|
|
5968
|
+
}
|
|
5969
|
+
};
|
|
5970
|
+
|
|
5971
|
+
// src/core/drawings/types/FairValueGap.ts
|
|
5972
|
+
var EXTEND_OPTIONS = [
|
|
5973
|
+
{ value: "tested", label: "Until tested" },
|
|
5974
|
+
{ value: "half", label: "Until 50%" },
|
|
5975
|
+
{ value: "filled", label: "Until filled" },
|
|
5976
|
+
{ value: "always", label: "Always" }
|
|
5977
|
+
];
|
|
5978
|
+
function defaults() {
|
|
5979
|
+
return { extend: "filled", upColor: `${BULLISH}33`, downColor: `${BEARISH}33`, midline: true, border: false, fade: true, inverse: false };
|
|
5980
|
+
}
|
|
5981
|
+
var FairValueGap = class extends PriceActionZone {
|
|
5982
|
+
constructor(init) {
|
|
5983
|
+
super(init);
|
|
5984
|
+
this.type = "fairvaluegap";
|
|
5985
|
+
if (!this.gap) this.gap = defaults();
|
|
5986
|
+
}
|
|
5987
|
+
settings() {
|
|
5988
|
+
return this.gap;
|
|
5989
|
+
}
|
|
5990
|
+
formation(bars, at) {
|
|
5991
|
+
const s = gapState(bars, at);
|
|
5992
|
+
if (!s) return null;
|
|
5993
|
+
const { extend, inverse } = this.gap;
|
|
5994
|
+
const flip = inverse && extend === "filled" && s.inverted !== null ? { from: s.inverted, to: null } : null;
|
|
5995
|
+
return { ...s, start: s.index - 1, end: extend === "always" ? null : s[extend], flip };
|
|
5996
|
+
}
|
|
5997
|
+
schema() {
|
|
5998
|
+
return {
|
|
5999
|
+
fields: [
|
|
6000
|
+
{ path: "gap.extend", label: "Extend right", kind: "select", options: EXTEND_OPTIONS, group: "behavior" },
|
|
6001
|
+
{ path: "gap.upColor", label: "Bullish", kind: "color", group: "fill" },
|
|
6002
|
+
{ path: "gap.downColor", label: "Bearish", kind: "color", group: "fill" },
|
|
6003
|
+
{ path: "gap.midline", label: "Midline", kind: "boolean", group: "line" },
|
|
6004
|
+
{ path: "gap.border", label: "Border", kind: "boolean", group: "line" },
|
|
6005
|
+
{ path: "gap.fade", label: "Fade at end", kind: "boolean", group: "behavior", when: { path: "gap.extend", in: ["tested", "half", "filled"] } },
|
|
6006
|
+
{ path: "gap.inverse", label: "Flip on close", kind: "boolean", group: "behavior", when: { path: "gap.extend", in: ["filled"] } }
|
|
6007
|
+
]
|
|
6008
|
+
};
|
|
6009
|
+
}
|
|
6010
|
+
writeProps() {
|
|
6011
|
+
return { ...this.gap };
|
|
6012
|
+
}
|
|
6013
|
+
readProps(props) {
|
|
6014
|
+
const d = defaults();
|
|
6015
|
+
const p = props;
|
|
6016
|
+
this.gap = {
|
|
6017
|
+
extend: EXTEND_OPTIONS.some((o) => o.value === p.extend) ? p.extend : d.extend,
|
|
6018
|
+
upColor: typeof p.upColor === "string" ? p.upColor : d.upColor,
|
|
6019
|
+
downColor: typeof p.downColor === "string" ? p.downColor : d.downColor,
|
|
6020
|
+
midline: typeof p.midline === "boolean" ? p.midline : d.midline,
|
|
6021
|
+
border: typeof p.border === "boolean" ? p.border : d.border,
|
|
6022
|
+
fade: typeof p.fade === "boolean" ? p.fade : d.fade,
|
|
6023
|
+
inverse: typeof p.inverse === "boolean" ? p.inverse : d.inverse
|
|
6024
|
+
};
|
|
6025
|
+
}
|
|
6026
|
+
};
|
|
6027
|
+
|
|
6028
|
+
// src/core/drawings/types/OrderBlock.ts
|
|
6029
|
+
var RANGE_OPTIONS = [
|
|
6030
|
+
{ value: "body", label: "Body" },
|
|
6031
|
+
{ value: "wick", label: "Full range" }
|
|
6032
|
+
];
|
|
6033
|
+
var EXTEND_OPTIONS2 = [
|
|
6034
|
+
{ value: "tested", label: "Until tested" },
|
|
6035
|
+
{ value: "half", label: "Until 50%" },
|
|
6036
|
+
{ value: "broken", label: "Until broken" },
|
|
6037
|
+
{ value: "always", label: "Always" }
|
|
6038
|
+
];
|
|
6039
|
+
function defaults2() {
|
|
6040
|
+
return { range: "body", extend: "broken", upColor: `${BULLISH}29`, downColor: `${BEARISH}29`, midline: true, border: true, fade: true, breaker: true };
|
|
6041
|
+
}
|
|
6042
|
+
var OrderBlock = class extends PriceActionZone {
|
|
6043
|
+
constructor(init) {
|
|
6044
|
+
super(init);
|
|
6045
|
+
this.type = "orderblock";
|
|
6046
|
+
if (!this.block) this.block = defaults2();
|
|
6047
|
+
}
|
|
6048
|
+
settings() {
|
|
6049
|
+
return this.block;
|
|
6050
|
+
}
|
|
6051
|
+
formation(bars, at) {
|
|
6052
|
+
const s = blockState(bars, at, this.block.range);
|
|
6053
|
+
if (!s) return null;
|
|
6054
|
+
const { extend, breaker } = this.block;
|
|
6055
|
+
const flip = breaker && extend === "broken" && s.broken !== null ? { from: s.broken, to: s.retest } : null;
|
|
6056
|
+
return { ...s, start: s.index, end: extend === "always" ? null : s[extend], flip };
|
|
6057
|
+
}
|
|
6058
|
+
schema() {
|
|
6059
|
+
return {
|
|
6060
|
+
fields: [
|
|
6061
|
+
{ path: "block.range", label: "Zone", kind: "select", options: RANGE_OPTIONS, group: "behavior" },
|
|
6062
|
+
{ path: "block.extend", label: "Extend right", kind: "select", options: EXTEND_OPTIONS2, group: "behavior" },
|
|
6063
|
+
{ path: "block.upColor", label: "Bullish", kind: "color", group: "fill" },
|
|
6064
|
+
{ path: "block.downColor", label: "Bearish", kind: "color", group: "fill" },
|
|
6065
|
+
{ path: "block.midline", label: "Midline", kind: "boolean", group: "line" },
|
|
6066
|
+
{ path: "block.border", label: "Border", kind: "boolean", group: "line" },
|
|
6067
|
+
{ path: "block.fade", label: "Fade at end", kind: "boolean", group: "behavior", when: { path: "block.extend", in: ["tested", "half", "broken"] } },
|
|
6068
|
+
{ path: "block.breaker", label: "Breaker", kind: "boolean", group: "behavior", when: { path: "block.extend", in: ["broken"] } }
|
|
6069
|
+
]
|
|
6070
|
+
};
|
|
6071
|
+
}
|
|
6072
|
+
writeProps() {
|
|
6073
|
+
return { ...this.block };
|
|
6074
|
+
}
|
|
6075
|
+
readProps(props) {
|
|
6076
|
+
const d = defaults2();
|
|
6077
|
+
const p = props;
|
|
6078
|
+
this.block = {
|
|
6079
|
+
range: p.range === "body" || p.range === "wick" ? p.range : d.range,
|
|
6080
|
+
extend: EXTEND_OPTIONS2.some((o) => o.value === p.extend) ? p.extend : d.extend,
|
|
6081
|
+
upColor: typeof p.upColor === "string" ? p.upColor : d.upColor,
|
|
6082
|
+
downColor: typeof p.downColor === "string" ? p.downColor : d.downColor,
|
|
6083
|
+
midline: typeof p.midline === "boolean" ? p.midline : d.midline,
|
|
6084
|
+
border: typeof p.border === "boolean" ? p.border : d.border,
|
|
6085
|
+
fade: typeof p.fade === "boolean" ? p.fade : d.fade,
|
|
6086
|
+
breaker: typeof p.breaker === "boolean" ? p.breaker : d.breaker
|
|
6087
|
+
};
|
|
6088
|
+
}
|
|
6089
|
+
};
|
|
6090
|
+
|
|
6091
|
+
// src/core/drawings/types/Liquidity.ts
|
|
6092
|
+
var EXTEND_OPTIONS3 = [
|
|
6093
|
+
{ value: "swept", label: "Until swept" },
|
|
6094
|
+
{ value: "broken", label: "Until broken" },
|
|
6095
|
+
{ value: "always", label: "Always" }
|
|
6096
|
+
];
|
|
6097
|
+
function defaults3() {
|
|
6098
|
+
return { extend: "swept", markSweep: true };
|
|
6099
|
+
}
|
|
6100
|
+
var Liquidity = class extends PriceActionTool {
|
|
6101
|
+
constructor(init) {
|
|
6102
|
+
super(init);
|
|
6103
|
+
this.type = "liquidity";
|
|
6104
|
+
if (!this.liq) this.liq = defaults3();
|
|
6105
|
+
}
|
|
6106
|
+
anchorSchema() {
|
|
6107
|
+
return { min: 1, max: 1, slots: [{ role: "candle", free: "both" }] };
|
|
6108
|
+
}
|
|
6109
|
+
state(proj) {
|
|
6110
|
+
const c = this.candles(proj);
|
|
6111
|
+
const a = this.anchors[0];
|
|
6112
|
+
if (!c || !a) return null;
|
|
6113
|
+
const bar = c.bars[c.at];
|
|
6114
|
+
return liquidityState(c.bars, c.at, a.price >= (bar.high + bar.low) / 2 ? "high" : "low");
|
|
6115
|
+
}
|
|
6116
|
+
line(proj) {
|
|
6117
|
+
const c = this.candles(proj);
|
|
6118
|
+
const s = this.state(proj);
|
|
6119
|
+
if (!c || !s) return null;
|
|
6120
|
+
const y = proj.yOf(s.price, this.paneId);
|
|
6121
|
+
if (y == null) return null;
|
|
6122
|
+
const { bars } = c;
|
|
6123
|
+
const { extend, markSweep } = this.liq;
|
|
6124
|
+
const end = extend === "always" ? null : extend === "broken" ? s.broken : s.swept ?? s.broken;
|
|
6125
|
+
const x1 = proj.xOf(bars[s.index].time);
|
|
6126
|
+
let sweep = null;
|
|
6127
|
+
if (markSweep && s.swept !== null && (end === null || s.swept <= end)) {
|
|
6128
|
+
const b = bars[s.swept];
|
|
6129
|
+
const wick = proj.yOf(s.high ? b.high : b.low, this.paneId);
|
|
6130
|
+
if (wick != null) sweep = { x: proj.xOf(b.time), y: wick + (s.high ? -9 : 9) };
|
|
6131
|
+
}
|
|
6132
|
+
return { x1, x2: this.rightEdge(proj, bars, end), y, sweep, handle: [x1, y] };
|
|
6133
|
+
}
|
|
6134
|
+
hitTest(px, py, proj, tol) {
|
|
6135
|
+
const l = this.line(proj);
|
|
6136
|
+
return l != null && px >= l.x1 - tol && px <= l.x2 + tol && Math.abs(py - l.y) <= tol;
|
|
6137
|
+
}
|
|
6138
|
+
handlePoints(proj) {
|
|
6139
|
+
const l = this.line(proj);
|
|
6140
|
+
return l ? [l.handle] : this.anchorHandle(proj);
|
|
6141
|
+
}
|
|
6142
|
+
bounds(proj) {
|
|
6143
|
+
const l = this.line(proj);
|
|
6144
|
+
if (!l) return null;
|
|
6145
|
+
return { x: l.x1, y: l.y - 4, w: Math.max(1, Math.min(proj.width, l.x2) - l.x1), h: 8 };
|
|
6146
|
+
}
|
|
6147
|
+
schema() {
|
|
6148
|
+
return {
|
|
6149
|
+
fields: [
|
|
6150
|
+
...LINE_FIELDS,
|
|
6151
|
+
{ path: "liq.extend", label: "Extend right", kind: "select", options: EXTEND_OPTIONS3, group: "behavior" },
|
|
6152
|
+
{ path: "liq.markSweep", label: "Mark sweep", kind: "boolean", group: "behavior" }
|
|
6153
|
+
]
|
|
6154
|
+
};
|
|
6155
|
+
}
|
|
6156
|
+
writeProps() {
|
|
6157
|
+
return { ...this.liq };
|
|
6158
|
+
}
|
|
6159
|
+
readProps(props) {
|
|
6160
|
+
const d = defaults3();
|
|
6161
|
+
const p = props;
|
|
6162
|
+
this.liq = {
|
|
6163
|
+
extend: EXTEND_OPTIONS3.some((o) => o.value === p.extend) ? p.extend : d.extend,
|
|
6164
|
+
markSweep: typeof p.markSweep === "boolean" ? p.markSweep : d.markSweep
|
|
6165
|
+
};
|
|
6166
|
+
}
|
|
6167
|
+
};
|
|
6168
|
+
|
|
5640
6169
|
// src/core/drawings/registry.ts
|
|
5641
6170
|
var REGISTRY2 = /* @__PURE__ */ new Map();
|
|
5642
6171
|
function registerDrawingType(meta) {
|
|
@@ -6330,6 +6859,36 @@ registerDrawingType({
|
|
|
6330
6859
|
defaultStyle: { lineColor: BULLISH, lineWidth: 1, lineStyle: "solid" },
|
|
6331
6860
|
create: (init) => new FixedRangeVolumeProfile(init)
|
|
6332
6861
|
});
|
|
6862
|
+
var FVG_ICON = svg24('<path d="M6 15v6M12 3v18M18 2v6"/><rect x="3.5" y="9.5" width="17" height="5" rx="1" fill="currentColor" fill-opacity="0.28" stroke-width="1.4"/>');
|
|
6863
|
+
var ORDER_BLOCK_ICON = svg24('<rect x="4" y="7.5" width="5" height="8" rx="1"/><path d="M6.5 4v3.5M6.5 15.5V19"/><rect x="9" y="7.5" width="11.5" height="8" rx="1" fill="currentColor" fill-opacity="0.22" stroke-width="1.4"/>');
|
|
6864
|
+
var LIQUIDITY_ICON = svg24('<path d="M3 8h12" stroke-dasharray="2.5 2.5"/><path d="m16.5 4.5 4 4m0-4-4 4"/><path d="M3.5 20 8 13.5l4 3.5 5-8"/>');
|
|
6865
|
+
registerDrawingType({
|
|
6866
|
+
type: "fairvaluegap",
|
|
6867
|
+
group: "priceaction",
|
|
6868
|
+
label: "Fair Value Gap",
|
|
6869
|
+
icon: FVG_ICON,
|
|
6870
|
+
defaultStyle: { lineColor: BULLISH, lineWidth: 1, lineStyle: "solid" },
|
|
6871
|
+
placementHint: "Click the middle candle of a gap",
|
|
6872
|
+
create: (init) => new FairValueGap(init)
|
|
6873
|
+
});
|
|
6874
|
+
registerDrawingType({
|
|
6875
|
+
type: "orderblock",
|
|
6876
|
+
group: "priceaction",
|
|
6877
|
+
label: "Order Block",
|
|
6878
|
+
icon: ORDER_BLOCK_ICON,
|
|
6879
|
+
defaultStyle: { lineColor: BULLISH, lineWidth: 1, lineStyle: "solid" },
|
|
6880
|
+
placementHint: "Click the candle that forms the block",
|
|
6881
|
+
create: (init) => new OrderBlock(init)
|
|
6882
|
+
});
|
|
6883
|
+
registerDrawingType({
|
|
6884
|
+
type: "liquidity",
|
|
6885
|
+
group: "priceaction",
|
|
6886
|
+
label: "Liquidity",
|
|
6887
|
+
icon: LIQUIDITY_ICON,
|
|
6888
|
+
defaultStyle: { lineColor: WARNING, lineWidth: 1, lineStyle: "solid" },
|
|
6889
|
+
placementHint: "Click near a candle's high or low",
|
|
6890
|
+
create: (init) => new Liquidity(init)
|
|
6891
|
+
});
|
|
6333
6892
|
|
|
6334
6893
|
// src/core/drawings/toolbar.ts
|
|
6335
6894
|
var FIBONACCI_TYPES = [
|
|
@@ -6346,6 +6905,7 @@ var FIBONACCI_TYPES = [
|
|
|
6346
6905
|
"fibwedge",
|
|
6347
6906
|
"fibspiral"
|
|
6348
6907
|
];
|
|
6908
|
+
var PRICE_ACTION_TYPES = ["fairvaluegap", "orderblock", "liquidity"];
|
|
6349
6909
|
var GANN_TYPES = ["gannfan", "gannbox", "gannsquare"];
|
|
6350
6910
|
var GEOMETRY_TYPES = ["dedekind", "sonic", "supersonic", "goldensonic", "goldensupersonic"];
|
|
6351
6911
|
var PATTERN_TYPES = ["xabcd", "abcd", "headshoulders"];
|
|
@@ -6373,6 +6933,7 @@ var TOOLBAR_LAYOUT = [
|
|
|
6373
6933
|
label: "Fibonacci",
|
|
6374
6934
|
sections: [
|
|
6375
6935
|
{ label: "Fibonacci", types: FIBONACCI_TYPES },
|
|
6936
|
+
{ label: "Price Action", types: PRICE_ACTION_TYPES },
|
|
6376
6937
|
{ label: "Gann", types: GANN_TYPES },
|
|
6377
6938
|
{ label: "Geometry", types: GEOMETRY_TYPES }
|
|
6378
6939
|
]
|
|
@@ -6550,6 +7111,7 @@ var DrawingStore = class {
|
|
|
6550
7111
|
if (patch.visible !== void 0) d.visible = patch.visible;
|
|
6551
7112
|
if (patch.zIndex !== void 0) d.zIndex = patch.zIndex;
|
|
6552
7113
|
if (patch.props !== void 0) d.applyProps(patch.props);
|
|
7114
|
+
if ("showOn" in patch) d.showOn = sanitizeShowOn(patch.showOn);
|
|
6553
7115
|
this.emit();
|
|
6554
7116
|
return d;
|
|
6555
7117
|
}
|
|
@@ -6713,7 +7275,79 @@ var DrawingHistory = class {
|
|
|
6713
7275
|
}
|
|
6714
7276
|
};
|
|
6715
7277
|
|
|
7278
|
+
// src/core/drawings/defaults.ts
|
|
7279
|
+
function captureToolDefaults(d) {
|
|
7280
|
+
const out = { style: { ...d.style } };
|
|
7281
|
+
if (d.text) {
|
|
7282
|
+
const { value: _value, ...styling } = d.text;
|
|
7283
|
+
out.text = styling;
|
|
7284
|
+
}
|
|
7285
|
+
const props = d.serialize().props;
|
|
7286
|
+
if (props !== void 0) out.props = clonePlain(props);
|
|
7287
|
+
return out;
|
|
7288
|
+
}
|
|
7289
|
+
function applyToolDefaults(d, defaults5) {
|
|
7290
|
+
if (!defaults5) return;
|
|
7291
|
+
if (defaults5.style) d.style = { ...d.style, ...defaults5.style };
|
|
7292
|
+
if (defaults5.text) d.text = { ...d.text ?? defaultText(), ...defaults5.text, value: d.text?.value ?? "" };
|
|
7293
|
+
if (defaults5.props) d.applyProps(clonePlain(defaults5.props));
|
|
7294
|
+
}
|
|
7295
|
+
function sameToolDefaults(a, b) {
|
|
7296
|
+
return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
7297
|
+
}
|
|
7298
|
+
function sanitizeToolDefaults(raw) {
|
|
7299
|
+
if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
7300
|
+
const r = raw;
|
|
7301
|
+
const obj = (v) => v != null && typeof v === "object" && !Array.isArray(v) ? v : void 0;
|
|
7302
|
+
const out = {};
|
|
7303
|
+
const style = obj(r.style);
|
|
7304
|
+
if (style) out.style = style;
|
|
7305
|
+
const text = obj(r.text);
|
|
7306
|
+
if (text) {
|
|
7307
|
+
const { value: _value, ...styling } = text;
|
|
7308
|
+
out.text = styling;
|
|
7309
|
+
}
|
|
7310
|
+
const props = obj(r.props);
|
|
7311
|
+
if (props) out.props = props;
|
|
7312
|
+
return out.style || out.text || out.props ? out : null;
|
|
7313
|
+
}
|
|
7314
|
+
var TEMPLATE_NAME_MAX = 60;
|
|
7315
|
+
function sanitizeToolTemplates(raw) {
|
|
7316
|
+
if (!Array.isArray(raw)) return [];
|
|
7317
|
+
const byName = /* @__PURE__ */ new Map();
|
|
7318
|
+
for (const entry of raw) {
|
|
7319
|
+
if (entry == null || typeof entry !== "object") continue;
|
|
7320
|
+
const e = entry;
|
|
7321
|
+
const name = typeof e.name === "string" ? e.name.trim().slice(0, TEMPLATE_NAME_MAX) : "";
|
|
7322
|
+
const settings = sanitizeToolDefaults(e.settings);
|
|
7323
|
+
if (name && settings) byName.set(name, { name, settings });
|
|
7324
|
+
}
|
|
7325
|
+
return [...byName.values()];
|
|
7326
|
+
}
|
|
7327
|
+
var LINE_TOOLS = /* @__PURE__ */ new Set(["trendline", "ray", "extendedline", "hline", "hray", "vline", "crossline", "infoline", "trendangle"]);
|
|
7328
|
+
var AREA_TOOLS = /* @__PURE__ */ new Set(["box", "rotatedrect", "ellipse", "circle", "triangle", "parallelchannel", "disjointchannel", "flattopbottom"]);
|
|
7329
|
+
function builtinToolTemplates(type) {
|
|
7330
|
+
if (LINE_TOOLS.has(type)) {
|
|
7331
|
+
return [
|
|
7332
|
+
{ name: "Support", settings: { style: { lineColor: BULLISH, lineWidth: 2, lineStyle: "solid" } } },
|
|
7333
|
+
{ name: "Resistance", settings: { style: { lineColor: BEARISH, lineWidth: 2, lineStyle: "solid" } } },
|
|
7334
|
+
{ name: "Projection", settings: { style: { lineColor: NEUTRAL, lineWidth: 1, lineStyle: "dashed" } } }
|
|
7335
|
+
];
|
|
7336
|
+
}
|
|
7337
|
+
if (AREA_TOOLS.has(type)) {
|
|
7338
|
+
return [
|
|
7339
|
+
{ name: "Demand", settings: { style: { lineColor: BULLISH, fillColor: `${BULLISH}26` } } },
|
|
7340
|
+
{ name: "Supply", settings: { style: { lineColor: BEARISH, fillColor: `${BEARISH}26` } } }
|
|
7341
|
+
];
|
|
7342
|
+
}
|
|
7343
|
+
return [];
|
|
7344
|
+
}
|
|
7345
|
+
|
|
6716
7346
|
// src/core/drawings/DrawingController.ts
|
|
7347
|
+
function factoryDefaults(d) {
|
|
7348
|
+
const fresh = createDrawing(d.type, { paneId: d.paneId });
|
|
7349
|
+
return fresh ? captureToolDefaults(fresh) : void 0;
|
|
7350
|
+
}
|
|
6717
7351
|
var DrawingController = class {
|
|
6718
7352
|
constructor(renderer, events, option, seriesGateway) {
|
|
6719
7353
|
this.events = events;
|
|
@@ -6732,8 +7366,10 @@ var DrawingController = class {
|
|
|
6732
7366
|
// ordered; [0] is the primary (settings-popup) selection
|
|
6733
7367
|
this.clipboard = [];
|
|
6734
7368
|
// in-memory copy buffer (per chart)
|
|
6735
|
-
|
|
6736
|
-
|
|
7369
|
+
/** Per-tool remembered settings — the last drawing of each type the user styled seeds the next. */
|
|
7370
|
+
this.defaults = /* @__PURE__ */ new Map();
|
|
7371
|
+
/** Per-tool saved looks ("styles"), in the order they were saved. */
|
|
7372
|
+
this.templates = /* @__PURE__ */ new Map();
|
|
6737
7373
|
this.subs = [];
|
|
6738
7374
|
this.enabled = !!renderer.capabilities.userDrawings && !!renderer.userDrawingsPort;
|
|
6739
7375
|
this.port = this.enabled ? renderer.userDrawingsPort : null;
|
|
@@ -6755,7 +7391,8 @@ var DrawingController = class {
|
|
|
6755
7391
|
if (!this.port) return;
|
|
6756
7392
|
const changed = type !== this.activeTool;
|
|
6757
7393
|
this.activeTool = type;
|
|
6758
|
-
|
|
7394
|
+
const defaults5 = type ? this.defaults.get(type) : void 0;
|
|
7395
|
+
this.port.setActiveTool(type, defaults5?.style, defaults5);
|
|
6759
7396
|
if (changed) this.events.emit("drawing:tool", { type });
|
|
6760
7397
|
}
|
|
6761
7398
|
getTool() {
|
|
@@ -6823,6 +7460,84 @@ var DrawingController = class {
|
|
|
6823
7460
|
this.port?.setFavorites?.(list);
|
|
6824
7461
|
this.events.emit("drawing:favorites", { favorites: list });
|
|
6825
7462
|
}
|
|
7463
|
+
/** Every tool's remembered settings, by type (plain JSON — persist it as-is). */
|
|
7464
|
+
toolDefaults() {
|
|
7465
|
+
const out = {};
|
|
7466
|
+
for (const [type, d] of this.defaults) out[type] = clonePlain(d);
|
|
7467
|
+
return out;
|
|
7468
|
+
}
|
|
7469
|
+
/** Replace every tool's remembered settings (restoring persisted prefs). Unknown types and
|
|
7470
|
+
* malformed entries are dropped; an unchanged set is a no-op and emits nothing. */
|
|
7471
|
+
setToolDefaults(map2) {
|
|
7472
|
+
const next = /* @__PURE__ */ new Map();
|
|
7473
|
+
for (const [type, raw] of Object.entries(map2 ?? {})) {
|
|
7474
|
+
if (!getDrawingType(type)) continue;
|
|
7475
|
+
const d = sanitizeToolDefaults(raw);
|
|
7476
|
+
if (d) next.set(type, clonePlain(d));
|
|
7477
|
+
}
|
|
7478
|
+
const changed = /* @__PURE__ */ new Set([...this.defaults.keys(), ...next.keys()]);
|
|
7479
|
+
for (const type of [...changed]) if (sameToolDefaults(this.defaults.get(type), next.get(type))) changed.delete(type);
|
|
7480
|
+
if (changed.size === 0) return;
|
|
7481
|
+
this.defaults.clear();
|
|
7482
|
+
for (const [type, d] of next) this.defaults.set(type, d);
|
|
7483
|
+
if (this.activeTool && changed.has(this.activeTool)) this.setTool(this.activeTool);
|
|
7484
|
+
for (const type of changed) this.events.emit("drawing:defaults", { type });
|
|
7485
|
+
}
|
|
7486
|
+
/** Forget a tool's remembered settings (every tool's when `type` is omitted) — new drawings
|
|
7487
|
+
* start from the factory defaults again. */
|
|
7488
|
+
resetToolDefaults(type) {
|
|
7489
|
+
const types = type ? this.defaults.has(type) ? [type] : [] : [...this.defaults.keys()];
|
|
7490
|
+
for (const t of types) {
|
|
7491
|
+
this.defaults.delete(t);
|
|
7492
|
+
this.events.emit("drawing:defaults", { type: t });
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
/** Every tool's saved looks, by type (plain JSON — persist it as-is). */
|
|
7496
|
+
toolTemplates() {
|
|
7497
|
+
const out = {};
|
|
7498
|
+
for (const [type, list] of this.templates) out[type] = clonePlain(list);
|
|
7499
|
+
return out;
|
|
7500
|
+
}
|
|
7501
|
+
/** Replace every tool's saved looks (restoring persisted prefs). Unknown types and
|
|
7502
|
+
* malformed entries are dropped; an unchanged set is a no-op and emits nothing. */
|
|
7503
|
+
setToolTemplates(map2) {
|
|
7504
|
+
const next = /* @__PURE__ */ new Map();
|
|
7505
|
+
for (const [type, raw] of Object.entries(map2 ?? {})) {
|
|
7506
|
+
if (!getDrawingType(type)) continue;
|
|
7507
|
+
const list = sanitizeToolTemplates(raw);
|
|
7508
|
+
if (list.length > 0) next.set(type, list);
|
|
7509
|
+
}
|
|
7510
|
+
const changed = [.../* @__PURE__ */ new Set([...this.templates.keys(), ...next.keys()])].filter(
|
|
7511
|
+
(t) => JSON.stringify(this.templates.get(t) ?? []) !== JSON.stringify(next.get(t) ?? [])
|
|
7512
|
+
);
|
|
7513
|
+
if (changed.length === 0) return;
|
|
7514
|
+
this.templates = next;
|
|
7515
|
+
this.pushTemplates();
|
|
7516
|
+
for (const type of changed) this.events.emit("drawing:templates", { type });
|
|
7517
|
+
}
|
|
7518
|
+
/** Save the given settings as a named look for a tool (a same-named one is replaced). */
|
|
7519
|
+
saveToolTemplate(type, name, settings) {
|
|
7520
|
+
if (!getDrawingType(type)) return;
|
|
7521
|
+
const [entry] = sanitizeToolTemplates([{ name, settings }]);
|
|
7522
|
+
if (!entry) return;
|
|
7523
|
+
const list = (this.templates.get(type) ?? []).filter((t) => t.name !== entry.name);
|
|
7524
|
+
this.templates.set(type, [...list, clonePlain(entry)]);
|
|
7525
|
+
this.pushTemplates();
|
|
7526
|
+
this.events.emit("drawing:templates", { type });
|
|
7527
|
+
}
|
|
7528
|
+
/** Delete a tool's saved look by name. */
|
|
7529
|
+
removeToolTemplate(type, name) {
|
|
7530
|
+
const list = this.templates.get(type);
|
|
7531
|
+
if (!list?.some((t) => t.name === name)) return;
|
|
7532
|
+
const rest = list.filter((t) => t.name !== name);
|
|
7533
|
+
if (rest.length > 0) this.templates.set(type, rest);
|
|
7534
|
+
else this.templates.delete(type);
|
|
7535
|
+
this.pushTemplates();
|
|
7536
|
+
this.events.emit("drawing:templates", { type });
|
|
7537
|
+
}
|
|
7538
|
+
pushTemplates() {
|
|
7539
|
+
this.port?.setToolTemplates?.(this.toolTemplates());
|
|
7540
|
+
}
|
|
6826
7541
|
setToolbar(option) {
|
|
6827
7542
|
this.port?.setToolbar(buildToolbar(option).definition);
|
|
6828
7543
|
}
|
|
@@ -6838,21 +7553,22 @@ var DrawingController = class {
|
|
|
6838
7553
|
// ── programmatic CRUD (facade-facing) ── each is one undo step
|
|
6839
7554
|
add(type, init = {}) {
|
|
6840
7555
|
if (!this.enabled) return null;
|
|
6841
|
-
const
|
|
6842
|
-
const style = { ...last ?? {}, ...init.style ?? {} };
|
|
7556
|
+
const defaults5 = this.defaults.get(type);
|
|
6843
7557
|
const d = createDrawing(type, {
|
|
6844
7558
|
id: this.store.nextId(),
|
|
6845
7559
|
paneId: init.paneId ?? "price",
|
|
6846
7560
|
anchors: init.anchors,
|
|
6847
|
-
style,
|
|
6848
7561
|
text: init.text,
|
|
6849
|
-
props: init.props,
|
|
6850
7562
|
zIndex: init.zIndex ?? this.startZ(type, init.paneId ?? "price")
|
|
6851
7563
|
});
|
|
6852
7564
|
if (!d) return null;
|
|
7565
|
+
applyToolDefaults(d, defaults5);
|
|
7566
|
+
if (init.style) d.style = { ...d.style, ...init.style };
|
|
7567
|
+
if (init.text) d.text = { ...d.text ?? init.text, ...init.text };
|
|
7568
|
+
if (init.props) d.applyProps(init.props);
|
|
6853
7569
|
this.history.record(this.store.serialize());
|
|
6854
7570
|
this.store.add(d);
|
|
6855
|
-
this.
|
|
7571
|
+
this.captureDefaults(d.id);
|
|
6856
7572
|
this.events.emit("drawing:created", { id: d.id });
|
|
6857
7573
|
return d;
|
|
6858
7574
|
}
|
|
@@ -6864,9 +7580,8 @@ var DrawingController = class {
|
|
|
6864
7580
|
const before = this.store.serialize();
|
|
6865
7581
|
const cur = patch.style ? this.store.get(id) : void 0;
|
|
6866
7582
|
const resolved = patch.style && cur ? { ...patch, style: { ...cur.style, ...patch.style } } : patch;
|
|
6867
|
-
if (this.
|
|
7583
|
+
if (this.updateDrawing(id, resolved)) {
|
|
6868
7584
|
this.history.record(before);
|
|
6869
|
-
this.captureStyle(id);
|
|
6870
7585
|
this.events.emit("drawing:edited", { id });
|
|
6871
7586
|
}
|
|
6872
7587
|
}
|
|
@@ -6874,7 +7589,7 @@ var DrawingController = class {
|
|
|
6874
7589
|
updateMany(patches) {
|
|
6875
7590
|
this.history.begin(this.store.serialize());
|
|
6876
7591
|
for (const { id, patch } of patches) {
|
|
6877
|
-
if (this.
|
|
7592
|
+
if (this.updateDrawing(id, patch)) {
|
|
6878
7593
|
this.history.markDirty();
|
|
6879
7594
|
this.events.emit("drawing:edited", { id });
|
|
6880
7595
|
}
|
|
@@ -7054,10 +7769,27 @@ var DrawingController = class {
|
|
|
7054
7769
|
resolve(ids) {
|
|
7055
7770
|
return ids.map((id) => this.store.get(id)).filter((d) => d != null);
|
|
7056
7771
|
}
|
|
7057
|
-
/**
|
|
7058
|
-
|
|
7772
|
+
/** `store.update` that remembers the drawing's settings only when the patch changed them:
|
|
7773
|
+
* moving an older drawing must not hand its look back to the tool. */
|
|
7774
|
+
updateDrawing(id, patch) {
|
|
7775
|
+
const cur = this.store.get(id);
|
|
7776
|
+
const before = cur ? captureToolDefaults(cur) : void 0;
|
|
7777
|
+
if (!this.store.update(id, patch)) return false;
|
|
7778
|
+
const after = this.store.get(id);
|
|
7779
|
+
if (after && !sameToolDefaults(before, captureToolDefaults(after))) this.captureDefaults(id);
|
|
7780
|
+
return true;
|
|
7781
|
+
}
|
|
7782
|
+
/** Remember a drawing's settings as its tool's defaults (they seed the next one), and
|
|
7783
|
+
* announce a real change so a host can save it. */
|
|
7784
|
+
captureDefaults(id) {
|
|
7059
7785
|
const d = this.store.get(id);
|
|
7060
|
-
if (d)
|
|
7786
|
+
if (!d) return;
|
|
7787
|
+
const captured = captureToolDefaults(d);
|
|
7788
|
+
const next = sameToolDefaults(captured, factoryDefaults(d)) ? void 0 : captured;
|
|
7789
|
+
if (sameToolDefaults(this.defaults.get(d.type), next)) return;
|
|
7790
|
+
if (next) this.defaults.set(d.type, next);
|
|
7791
|
+
else this.defaults.delete(d.type);
|
|
7792
|
+
this.events.emit("drawing:defaults", { type: d.type });
|
|
7061
7793
|
}
|
|
7062
7794
|
sync() {
|
|
7063
7795
|
this.port?.syncDrawings(this.store.serialize().drawings);
|
|
@@ -7073,22 +7805,20 @@ var DrawingController = class {
|
|
|
7073
7805
|
break;
|
|
7074
7806
|
case "create": {
|
|
7075
7807
|
const before = this.store.serialize();
|
|
7076
|
-
const
|
|
7077
|
-
const style = last ? { ...i.doc.style, ...last } : i.doc.style;
|
|
7078
|
-
const d = deserializeDrawing({ ...i.doc, id: this.store.nextId(), style });
|
|
7808
|
+
const d = deserializeDrawing({ ...i.doc, id: this.store.nextId() });
|
|
7079
7809
|
if (!d) return;
|
|
7810
|
+
applyToolDefaults(d, this.defaults.get(d.type));
|
|
7080
7811
|
if (!d.zIndex) d.zIndex = this.startZ(d.type, d.paneId) ?? 0;
|
|
7081
7812
|
this.history.record(before);
|
|
7082
7813
|
this.store.add(d);
|
|
7083
|
-
this.
|
|
7814
|
+
this.captureDefaults(d.id);
|
|
7084
7815
|
this.events.emit("drawing:created", { id: d.id });
|
|
7085
7816
|
break;
|
|
7086
7817
|
}
|
|
7087
7818
|
case "edit": {
|
|
7088
7819
|
const before = this.store.serialize();
|
|
7089
|
-
if (this.
|
|
7820
|
+
if (this.updateDrawing(i.doc.id, i.doc)) {
|
|
7090
7821
|
this.history.record(before);
|
|
7091
|
-
this.captureStyle(i.doc.id);
|
|
7092
7822
|
this.events.emit("drawing:edited", { id: i.doc.id });
|
|
7093
7823
|
}
|
|
7094
7824
|
break;
|
|
@@ -7096,7 +7826,7 @@ var DrawingController = class {
|
|
|
7096
7826
|
case "edit-many": {
|
|
7097
7827
|
this.history.begin(this.store.serialize());
|
|
7098
7828
|
for (const doc of i.docs) {
|
|
7099
|
-
if (this.
|
|
7829
|
+
if (this.updateDrawing(doc.id, doc)) {
|
|
7100
7830
|
this.history.markDirty();
|
|
7101
7831
|
this.events.emit("drawing:edited", { id: doc.id });
|
|
7102
7832
|
}
|
|
@@ -7120,6 +7850,12 @@ var DrawingController = class {
|
|
|
7120
7850
|
case "favorite":
|
|
7121
7851
|
this.setFavorite(i.type, i.on);
|
|
7122
7852
|
break;
|
|
7853
|
+
case "template-save":
|
|
7854
|
+
this.saveToolTemplate(i.type, i.name, i.settings);
|
|
7855
|
+
break;
|
|
7856
|
+
case "template-remove":
|
|
7857
|
+
this.removeToolTemplate(i.type, i.name);
|
|
7858
|
+
break;
|
|
7123
7859
|
case "snap-mode":
|
|
7124
7860
|
if (i.mode !== this.snapMode) {
|
|
7125
7861
|
this.snapMode = i.mode;
|
|
@@ -7493,8 +8229,8 @@ function groupMembers(pool, groupRow) {
|
|
|
7493
8229
|
}
|
|
7494
8230
|
function defaultMemberOf(pool, groupRow) {
|
|
7495
8231
|
const members = groupMembers(pool, groupRow);
|
|
7496
|
-
const
|
|
7497
|
-
return (
|
|
8232
|
+
const defaults5 = members.filter((m) => m.default);
|
|
8233
|
+
return (defaults5.length === 1 ? defaults5[0] : members[0]) ?? null;
|
|
7498
8234
|
}
|
|
7499
8235
|
|
|
7500
8236
|
// src/data/ProviderRegistry.ts
|
|
@@ -8637,14 +9373,14 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8637
9373
|
this.replayTickSource = source;
|
|
8638
9374
|
this.prefetchTicks();
|
|
8639
9375
|
}
|
|
8640
|
-
replayPlay(
|
|
9376
|
+
replayPlay(intervalMs) {
|
|
8641
9377
|
if (!this.replayQueue) return;
|
|
8642
|
-
if (
|
|
8643
|
-
if (!(Number.isFinite(
|
|
8644
|
-
console.warn(`[vela] chart.replay.play(${
|
|
9378
|
+
if (intervalMs !== void 0) {
|
|
9379
|
+
if (!(Number.isFinite(intervalMs) && intervalMs > 0)) {
|
|
9380
|
+
console.warn(`[vela] chart.replay.play(${intervalMs}) ignored \u2014 the interval must be a positive number of ms`);
|
|
8645
9381
|
return;
|
|
8646
9382
|
}
|
|
8647
|
-
this.replayIntervalMs =
|
|
9383
|
+
this.replayIntervalMs = intervalMs;
|
|
8648
9384
|
}
|
|
8649
9385
|
this.clearReplayTimer();
|
|
8650
9386
|
this.replayPlaying = true;
|
|
@@ -9567,9 +10303,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
9567
10303
|
const prepared = await engine.prepare(source, id);
|
|
9568
10304
|
if (record.source !== source) return;
|
|
9569
10305
|
record.prepared = prepared;
|
|
9570
|
-
const
|
|
9571
|
-
for (const input of prepared.inputs)
|
|
9572
|
-
record.inputValues = { ...
|
|
10306
|
+
const defaults5 = {};
|
|
10307
|
+
for (const input of prepared.inputs) defaults5[input.key] = input.defval;
|
|
10308
|
+
record.inputValues = { ...defaults5, ...record.inputValues };
|
|
9573
10309
|
handle.setSchema(prepared.inputs);
|
|
9574
10310
|
const propDefaults = {};
|
|
9575
10311
|
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
@@ -10184,7 +10920,7 @@ function modelToValuePatch(model) {
|
|
|
10184
10920
|
let to = 0;
|
|
10185
10921
|
for (const s of model.series) {
|
|
10186
10922
|
if (s.kind === "candle" || s.kind === "bar") {
|
|
10187
|
-
series.push({ seriesId: s.id, kind: "bars", bars: s.bars });
|
|
10923
|
+
series.push({ seriesId: s.id, kind: "bars", bars: s.bars, barColors: s.barColors ?? [] });
|
|
10188
10924
|
for (const b of s.bars) {
|
|
10189
10925
|
if (b.time < from) from = b.time;
|
|
10190
10926
|
if (b.time > to) to = b.time;
|
|
@@ -10457,20 +11193,20 @@ var RendererControl = class {
|
|
|
10457
11193
|
};
|
|
10458
11194
|
|
|
10459
11195
|
// src/core/renderer-defaults.ts
|
|
10460
|
-
var
|
|
11196
|
+
var defaults4 = /* @__PURE__ */ new Map();
|
|
10461
11197
|
function registerRendererDefaults(values) {
|
|
10462
11198
|
const applied = Object.entries(values);
|
|
10463
|
-
for (const [key, value] of applied)
|
|
11199
|
+
for (const [key, value] of applied) defaults4.set(key, value);
|
|
10464
11200
|
return () => {
|
|
10465
|
-
for (const [key, value] of applied) if (
|
|
11201
|
+
for (const [key, value] of applied) if (defaults4.get(key) === value) defaults4.delete(key);
|
|
10466
11202
|
};
|
|
10467
11203
|
}
|
|
10468
11204
|
function unregisterRendererDefaults(...keys) {
|
|
10469
|
-
if (keys.length === 0)
|
|
10470
|
-
else for (const key of keys)
|
|
11205
|
+
if (keys.length === 0) defaults4.clear();
|
|
11206
|
+
else for (const key of keys) defaults4.delete(key);
|
|
10471
11207
|
}
|
|
10472
11208
|
function rendererDefaults() {
|
|
10473
|
-
return Object.fromEntries(
|
|
11209
|
+
return Object.fromEntries(defaults4);
|
|
10474
11210
|
}
|
|
10475
11211
|
|
|
10476
11212
|
// src/core/PanesControl.ts
|
|
@@ -10790,38 +11526,11 @@ function attachForming(closed, forming) {
|
|
|
10790
11526
|
}
|
|
10791
11527
|
|
|
10792
11528
|
// src/data/MultiProviderFeed.ts
|
|
10793
|
-
function intervalMs(timeframe) {
|
|
10794
|
-
const map2 = {
|
|
10795
|
-
"1m": 6e4,
|
|
10796
|
-
"3m": 18e4,
|
|
10797
|
-
"5m": 3e5,
|
|
10798
|
-
"15m": 9e5,
|
|
10799
|
-
"30m": 18e5,
|
|
10800
|
-
"1h": 36e5,
|
|
10801
|
-
"2h": 72e5,
|
|
10802
|
-
"4h": 144e5,
|
|
10803
|
-
"1d": 864e5,
|
|
10804
|
-
"1w": 6048e5,
|
|
10805
|
-
"1M": 2592e6,
|
|
10806
|
-
"1": 6e4,
|
|
10807
|
-
"5": 3e5,
|
|
10808
|
-
"15": 9e5,
|
|
10809
|
-
"30": 18e5,
|
|
10810
|
-
"60": 36e5,
|
|
10811
|
-
"240": 144e5,
|
|
10812
|
-
D: 864e5,
|
|
10813
|
-
W: 6048e5,
|
|
10814
|
-
M: 2592e6
|
|
10815
|
-
};
|
|
10816
|
-
return map2[timeframe] ?? 36e5;
|
|
10817
|
-
}
|
|
10818
11529
|
var MultiProviderFeed = class {
|
|
10819
11530
|
constructor(store = sharedBarStore) {
|
|
10820
11531
|
this.registry = new ProviderRegistry();
|
|
10821
11532
|
/** The chart symbol's resolved provider — the default for bare secondary symbols. */
|
|
10822
11533
|
this.primaryProvider = null;
|
|
10823
|
-
/** A copy of offline history, used to synthesize ticks for the `data` path. */
|
|
10824
|
-
this.liveBars = [];
|
|
10825
11534
|
/** Sync-accessible symbol metadata, warmed by load()/symbolInfoFor (the engine reads it synchronously). */
|
|
10826
11535
|
this.symInfoCache = /* @__PURE__ */ new Map();
|
|
10827
11536
|
/** Requests in flight, keyed like the cache — every concurrent asker shares one round trip. */
|
|
@@ -10931,10 +11640,7 @@ var MultiProviderFeed = class {
|
|
|
10931
11640
|
}
|
|
10932
11641
|
// ── MarketDataFeed ─────────────────────────────────────────────────────
|
|
10933
11642
|
async load(cfg) {
|
|
10934
|
-
if (cfg.data && cfg.data.length > 0)
|
|
10935
|
-
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
10936
|
-
return cfg.data;
|
|
10937
|
-
}
|
|
11643
|
+
if (cfg.data && cfg.data.length > 0) return cfg.data;
|
|
10938
11644
|
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
10939
11645
|
this.primaryProvider = resolved.provider;
|
|
10940
11646
|
this.prefetchSymbolInfo(resolved);
|
|
@@ -10942,10 +11648,7 @@ var MultiProviderFeed = class {
|
|
|
10942
11648
|
}
|
|
10943
11649
|
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
10944
11650
|
async loadProgressive(cfg, onBatch, opts) {
|
|
10945
|
-
if (cfg.data && cfg.data.length > 0)
|
|
10946
|
-
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
10947
|
-
return cfg.data;
|
|
10948
|
-
}
|
|
11651
|
+
if (cfg.data && cfg.data.length > 0) return cfg.data;
|
|
10949
11652
|
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
10950
11653
|
this.primaryProvider = resolved.provider;
|
|
10951
11654
|
this.prefetchSymbolInfo(resolved);
|
|
@@ -10971,39 +11674,16 @@ var MultiProviderFeed = class {
|
|
|
10971
11674
|
if (!resolved) return [];
|
|
10972
11675
|
return this.cache.loadRange(canonical(cfg, resolved), range);
|
|
10973
11676
|
}
|
|
11677
|
+
/** Offline `data` has no source of ticks: the caller's bars are shown exactly as given,
|
|
11678
|
+
* and live updates need a registered provider that implements `subscribe`. */
|
|
10974
11679
|
subscribe(cfg, onBar) {
|
|
10975
|
-
if (cfg.data && cfg.data.length > 0) return
|
|
11680
|
+
if (cfg.data && cfg.data.length > 0) return () => {
|
|
11681
|
+
};
|
|
10976
11682
|
const resolved = this.registry.resolve(rawSymbol(cfg), { default: this.primaryProvider });
|
|
10977
11683
|
if (!resolved) return () => {
|
|
10978
11684
|
};
|
|
10979
11685
|
return this.cache.subscribe(canonical(cfg, resolved), onBar);
|
|
10980
11686
|
}
|
|
10981
|
-
/**
|
|
10982
|
-
* Synthesize price-relative ticks for offline `data` runs (no network), so the
|
|
10983
|
-
* forming-candle path is exercised at any price scale. Mirrors the bundled
|
|
10984
|
-
* provider feed's offline behavior.
|
|
10985
|
-
*/
|
|
10986
|
-
synthesizeTicks(cfg, onBar) {
|
|
10987
|
-
if (this.liveBars.length === 0) return () => {
|
|
10988
|
-
};
|
|
10989
|
-
const step = intervalMs(cfg.timeframe ?? "60");
|
|
10990
|
-
const timer = setInterval(() => {
|
|
10991
|
-
const bars = this.liveBars;
|
|
10992
|
-
const forming = bars[bars.length - 1];
|
|
10993
|
-
if (!forming) return;
|
|
10994
|
-
const jitter = forming.close * 15e-4 * (Math.random() - 0.5) * 2;
|
|
10995
|
-
const close = Math.max(0, forming.close + jitter);
|
|
10996
|
-
const updated = { ...forming, close, high: Math.max(forming.high, close), low: Math.min(forming.low, close) };
|
|
10997
|
-
bars[bars.length - 1] = updated;
|
|
10998
|
-
onBar(updated);
|
|
10999
|
-
if (Math.random() < 0.12) {
|
|
11000
|
-
const next = { time: updated.time + step, open: updated.close, high: updated.close, low: updated.close, close: updated.close, volume: 0 };
|
|
11001
|
-
bars.push(next);
|
|
11002
|
-
onBar(next);
|
|
11003
|
-
}
|
|
11004
|
-
}, 1e3);
|
|
11005
|
-
return () => clearInterval(timer);
|
|
11006
|
-
}
|
|
11007
11687
|
};
|
|
11008
11688
|
function rawSymbol(cfg) {
|
|
11009
11689
|
return cfg.symbol ?? "";
|
|
@@ -11355,6 +12035,34 @@ var DrawingsControl = class {
|
|
|
11355
12035
|
if (this.ok("setFavorites")) this.ctrl.setFavorites([...types]);
|
|
11356
12036
|
return this;
|
|
11357
12037
|
}
|
|
12038
|
+
/** Every tool's remembered settings, by type — what the next drawing of each type starts
|
|
12039
|
+
* from (its style, text styling and per-type settings such as fib levels). Plain JSON:
|
|
12040
|
+
* persist it and hand it back to {@link setToolDefaults} on the next load. */
|
|
12041
|
+
toolDefaults() {
|
|
12042
|
+
return this.ctrl.toolDefaults();
|
|
12043
|
+
}
|
|
12044
|
+
/** Replace every tool's remembered settings (e.g. restoring persisted prefs). Malformed
|
|
12045
|
+
* entries and unknown types are dropped. Follow changes on `drawing:defaults`. */
|
|
12046
|
+
setToolDefaults(map2) {
|
|
12047
|
+
this.ctrl.setToolDefaults(map2);
|
|
12048
|
+
return this;
|
|
12049
|
+
}
|
|
12050
|
+
/** Forget one tool's remembered settings (every tool's without an argument). */
|
|
12051
|
+
resetToolDefaults(type) {
|
|
12052
|
+
this.ctrl.resetToolDefaults(type);
|
|
12053
|
+
return this;
|
|
12054
|
+
}
|
|
12055
|
+
/** Every tool's saved looks ("styles" in the settings panel), by type. Plain JSON —
|
|
12056
|
+
* persist it and hand it back to {@link setToolTemplates}. */
|
|
12057
|
+
toolTemplates() {
|
|
12058
|
+
return this.ctrl.toolTemplates();
|
|
12059
|
+
}
|
|
12060
|
+
/** Replace every tool's saved looks (e.g. restoring persisted prefs). Follow changes on
|
|
12061
|
+
* `drawing:templates`. */
|
|
12062
|
+
setToolTemplates(map2) {
|
|
12063
|
+
this.ctrl.setToolTemplates(map2);
|
|
12064
|
+
return this;
|
|
12065
|
+
}
|
|
11358
12066
|
/** Aliases mirroring `renderer.getConfig()/applyConfig()` for symmetry. */
|
|
11359
12067
|
getConfig() {
|
|
11360
12068
|
return this.ctrl.toJSON();
|
|
@@ -11451,8 +12159,8 @@ var ReplayControl = class {
|
|
|
11451
12159
|
}
|
|
11452
12160
|
/** Reveal one bar (one tick, with {@link setTicks}) every `intervalMs` (default: the last
|
|
11453
12161
|
* interval used, else 1000). Calling it again while playing changes the pace. */
|
|
11454
|
-
play(
|
|
11455
|
-
this.ctrl.replayPlay(
|
|
12162
|
+
play(intervalMs) {
|
|
12163
|
+
this.ctrl.replayPlay(intervalMs);
|
|
11456
12164
|
return this;
|
|
11457
12165
|
}
|
|
11458
12166
|
pause() {
|
|
@@ -11563,6 +12271,12 @@ function mix(base, top, alpha) {
|
|
|
11563
12271
|
const c = (x, y) => Math.round(x + (y - x) * alpha);
|
|
11564
12272
|
return `rgb(${c(b.r, t.r)},${c(b.g, t.g)},${c(b.b, t.b)})`;
|
|
11565
12273
|
}
|
|
12274
|
+
function toHex6(color, fallback = "#888888") {
|
|
12275
|
+
const c = parseRgb(color);
|
|
12276
|
+
if (!c) return fallback;
|
|
12277
|
+
const h = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
12278
|
+
return `#${h(c.r)}${h(c.g)}${h(c.b)}`;
|
|
12279
|
+
}
|
|
11566
12280
|
function isDarkColor(color) {
|
|
11567
12281
|
const c = parseRgb(color);
|
|
11568
12282
|
if (!c) return true;
|
|
@@ -12713,26 +13427,26 @@ var CalloutBubble = class {
|
|
|
12713
13427
|
title.textContent = panel.title;
|
|
12714
13428
|
root.appendChild(title);
|
|
12715
13429
|
}
|
|
12716
|
-
for (const
|
|
12717
|
-
if (
|
|
13430
|
+
for (const row2 of calloutPanelRows(panel.items)) {
|
|
13431
|
+
if (row2.type === "text") {
|
|
12718
13432
|
const text = doc.createElement("div");
|
|
12719
13433
|
text.className = "vela-callout-text";
|
|
12720
|
-
text.textContent =
|
|
13434
|
+
text.textContent = row2.text;
|
|
12721
13435
|
root.appendChild(text);
|
|
12722
13436
|
continue;
|
|
12723
13437
|
}
|
|
12724
13438
|
const actions = doc.createElement("div");
|
|
12725
13439
|
actions.className = "vela-callout-actions";
|
|
12726
|
-
for (const item of
|
|
12727
|
-
const
|
|
12728
|
-
|
|
12729
|
-
|
|
12730
|
-
|
|
12731
|
-
|
|
13440
|
+
for (const item of row2.buttons) {
|
|
13441
|
+
const btn = doc.createElement("button");
|
|
13442
|
+
btn.type = "button";
|
|
13443
|
+
btn.className = "vela-callout-btn" + (item.primary ? " vela-callout-btn-primary" : "");
|
|
13444
|
+
btn.textContent = item.label;
|
|
13445
|
+
btn.addEventListener("click", () => {
|
|
12732
13446
|
item.run();
|
|
12733
13447
|
if (closesPanel(item)) this.pop?.hide();
|
|
12734
13448
|
});
|
|
12735
|
-
actions.appendChild(
|
|
13449
|
+
actions.appendChild(btn);
|
|
12736
13450
|
}
|
|
12737
13451
|
root.appendChild(actions);
|
|
12738
13452
|
}
|
|
@@ -12980,19 +13694,19 @@ var Select = class {
|
|
|
12980
13694
|
wrap.className = "vela-select";
|
|
12981
13695
|
wrap.dataset.size = this.ctrl.size;
|
|
12982
13696
|
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
12983
|
-
const
|
|
12984
|
-
|
|
12985
|
-
if (opts.id)
|
|
12986
|
-
|
|
12987
|
-
if (this.ctrl.disabled)
|
|
13697
|
+
const btn = doc.createElement("button");
|
|
13698
|
+
btn.type = "button";
|
|
13699
|
+
if (opts.id) btn.id = opts.id;
|
|
13700
|
+
btn.className = "vela-select-trigger";
|
|
13701
|
+
if (this.ctrl.disabled) btn.disabled = true;
|
|
12988
13702
|
const label = doc.createElement("span");
|
|
12989
13703
|
label.className = "vela-select-label";
|
|
12990
13704
|
label.textContent = this.ctrl.labelOf(this.ctrl.value);
|
|
12991
13705
|
const chevron = doc.createElement("span");
|
|
12992
13706
|
chevron.className = "vela-select-chevron";
|
|
12993
13707
|
chevron.appendChild(iconEl("chevron-down", doc));
|
|
12994
|
-
|
|
12995
|
-
wrap.appendChild(
|
|
13708
|
+
btn.append(label, chevron);
|
|
13709
|
+
wrap.appendChild(btn);
|
|
12996
13710
|
if (this.ctrl.size === "sm") {
|
|
12997
13711
|
const sizer = doc.createElement("div");
|
|
12998
13712
|
sizer.className = "vela-select-sizer";
|
|
@@ -13004,16 +13718,16 @@ var Select = class {
|
|
|
13004
13718
|
}
|
|
13005
13719
|
wrap.appendChild(sizer);
|
|
13006
13720
|
}
|
|
13007
|
-
|
|
13721
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
13008
13722
|
e.preventDefault();
|
|
13009
|
-
|
|
13723
|
+
btn.focus({ preventScroll: true });
|
|
13010
13724
|
});
|
|
13011
|
-
|
|
13725
|
+
btn.addEventListener("click", (e) => {
|
|
13012
13726
|
e.stopPropagation();
|
|
13013
13727
|
this.toggle();
|
|
13014
13728
|
});
|
|
13015
13729
|
this.el = wrap;
|
|
13016
|
-
this.trigger =
|
|
13730
|
+
this.trigger = btn;
|
|
13017
13731
|
this.labelEl = label;
|
|
13018
13732
|
}
|
|
13019
13733
|
get value() {
|
|
@@ -13566,19 +14280,19 @@ var DatePicker = class {
|
|
|
13566
14280
|
const doc = document;
|
|
13567
14281
|
injectStyles(DATE_PICKER_STYLE_ID, DATE_PICKER_CSS, doc);
|
|
13568
14282
|
this.ctrl = datePickerController(opts);
|
|
13569
|
-
const
|
|
14283
|
+
const button2 = (className) => {
|
|
13570
14284
|
const b = doc.createElement("button");
|
|
13571
14285
|
b.type = "button";
|
|
13572
14286
|
b.className = className;
|
|
13573
14287
|
return b;
|
|
13574
14288
|
};
|
|
13575
|
-
this.prev =
|
|
14289
|
+
this.prev = button2("vela-date-picker-nav");
|
|
13576
14290
|
this.prev.append(iconEl("chevron-left", doc));
|
|
13577
|
-
this.next =
|
|
14291
|
+
this.next = button2("vela-date-picker-nav");
|
|
13578
14292
|
this.next.append(iconEl("chevron-right", doc));
|
|
13579
|
-
this.monthBtn =
|
|
14293
|
+
this.monthBtn = button2("vela-date-picker-switch");
|
|
13580
14294
|
this.monthBtn.setAttribute("aria-label", "Choose month");
|
|
13581
|
-
this.yearBtn =
|
|
14295
|
+
this.yearBtn = button2("vela-date-picker-switch");
|
|
13582
14296
|
this.yearBtn.setAttribute("aria-label", "Choose year");
|
|
13583
14297
|
const title = doc.createElement("div");
|
|
13584
14298
|
title.className = "vela-date-picker-title";
|
|
@@ -14314,8 +15028,8 @@ function buildColorPicker(color, theme, onChange, options = {}) {
|
|
|
14314
15028
|
const grid = document.createElement("div");
|
|
14315
15029
|
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
14316
15030
|
const swatches = [];
|
|
14317
|
-
for (const
|
|
14318
|
-
for (const c of
|
|
15031
|
+
for (const row2 of PALETTE) {
|
|
15032
|
+
for (const c of row2) {
|
|
14319
15033
|
const sw = document.createElement("button");
|
|
14320
15034
|
sw.type = "button";
|
|
14321
15035
|
sw.dataset.c = c;
|
|
@@ -15049,8 +15763,8 @@ function buildFieldControl(desc) {
|
|
|
15049
15763
|
|
|
15050
15764
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
15051
15765
|
var PROPS_TAB = "Properties";
|
|
15052
|
-
function bagOf(
|
|
15053
|
-
return decl.prop ?
|
|
15766
|
+
function bagOf(row2, decl) {
|
|
15767
|
+
return decl.prop ? row2.propValues : row2.values;
|
|
15054
15768
|
}
|
|
15055
15769
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
15056
15770
|
var IndicatorInputsDialog = class {
|
|
@@ -15083,13 +15797,13 @@ var IndicatorInputsDialog = class {
|
|
|
15083
15797
|
isOpen() {
|
|
15084
15798
|
return this.dialog !== null;
|
|
15085
15799
|
}
|
|
15086
|
-
open(
|
|
15800
|
+
open(row2) {
|
|
15087
15801
|
this.close();
|
|
15088
|
-
if (
|
|
15089
|
-
this.row =
|
|
15090
|
-
this.openId =
|
|
15091
|
-
this.snapshot = { ...
|
|
15092
|
-
this.propSnapshot = { ...
|
|
15802
|
+
if (row2.inputs.length === 0 && row2.props.length === 0) return;
|
|
15803
|
+
this.row = row2;
|
|
15804
|
+
this.openId = row2.id;
|
|
15805
|
+
this.snapshot = { ...row2.values };
|
|
15806
|
+
this.propSnapshot = { ...row2.propValues };
|
|
15093
15807
|
ensureDialogStyles();
|
|
15094
15808
|
const t = this.host.theme();
|
|
15095
15809
|
const border = "var(--vela-border)";
|
|
@@ -15097,7 +15811,7 @@ var IndicatorInputsDialog = class {
|
|
|
15097
15811
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
15098
15812
|
const ui = new Dialog({
|
|
15099
15813
|
host,
|
|
15100
|
-
title:
|
|
15814
|
+
title: row2.title,
|
|
15101
15815
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
15102
15816
|
// whole body, killing the chart and the body-portaled popovers.
|
|
15103
15817
|
modal: false,
|
|
@@ -15128,9 +15842,9 @@ var IndicatorInputsDialog = class {
|
|
|
15128
15842
|
theme: () => this.host.theme(),
|
|
15129
15843
|
text: () => "Close"
|
|
15130
15844
|
}));
|
|
15131
|
-
const tabDefs = tabInputs(
|
|
15132
|
-
if (
|
|
15133
|
-
const propDecls =
|
|
15845
|
+
const tabDefs = tabInputs(row2.inputs);
|
|
15846
|
+
if (row2.props.length > 0) {
|
|
15847
|
+
const propDecls = row2.props.map((p) => {
|
|
15134
15848
|
const d = { ...p, prop: true };
|
|
15135
15849
|
delete d.tab;
|
|
15136
15850
|
return d;
|
|
@@ -15164,7 +15878,7 @@ var IndicatorInputsDialog = class {
|
|
|
15164
15878
|
tab.addEventListener("click", () => activate(idx));
|
|
15165
15879
|
tabs.appendChild(tab);
|
|
15166
15880
|
tabEls.push(tab);
|
|
15167
|
-
bodies.push(this.buildTabBody(def.inputs,
|
|
15881
|
+
bodies.push(this.buildTabBody(def.inputs, row2, gates));
|
|
15168
15882
|
if (def.inputs.every((d) => d.when)) {
|
|
15169
15883
|
gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
|
|
15170
15884
|
}
|
|
@@ -15173,7 +15887,7 @@ var IndicatorInputsDialog = class {
|
|
|
15173
15887
|
for (const b of bodies) ui.body.appendChild(b);
|
|
15174
15888
|
activate(0);
|
|
15175
15889
|
const refresh = () => {
|
|
15176
|
-
const bag = valuesBag(
|
|
15890
|
+
const bag = valuesBag(row2);
|
|
15177
15891
|
for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
|
|
15178
15892
|
if (tabEls[active]?.style.display === "none") {
|
|
15179
15893
|
const next = tabEls.findIndex((el) => el.style.display !== "none");
|
|
@@ -15201,7 +15915,7 @@ var IndicatorInputsDialog = class {
|
|
|
15201
15915
|
}
|
|
15202
15916
|
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
15203
15917
|
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
15204
|
-
buildTabBody(inputs,
|
|
15918
|
+
buildTabBody(inputs, row2, gates) {
|
|
15205
15919
|
const body = document.createElement("div");
|
|
15206
15920
|
body.style.cssText = inputDialogBodyStyle(this.host.mobile());
|
|
15207
15921
|
for (const group of groupInputs(inputs)) {
|
|
@@ -15213,7 +15927,7 @@ var IndicatorInputsDialog = class {
|
|
|
15213
15927
|
section.appendChild(header);
|
|
15214
15928
|
}
|
|
15215
15929
|
for (const inputRow of group.rows) {
|
|
15216
|
-
const el = this.buildInputRow(section, inputRow,
|
|
15930
|
+
const el = this.buildInputRow(section, inputRow, row2);
|
|
15217
15931
|
if (inputRow.some((d) => d.when)) {
|
|
15218
15932
|
gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
|
|
15219
15933
|
}
|
|
@@ -15246,26 +15960,26 @@ var IndicatorInputsDialog = class {
|
|
|
15246
15960
|
}
|
|
15247
15961
|
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
15248
15962
|
revertAndClose() {
|
|
15249
|
-
const
|
|
15963
|
+
const row2 = this.row;
|
|
15250
15964
|
const snap = this.snapshot;
|
|
15251
|
-
if (
|
|
15252
|
-
for (const inp of
|
|
15965
|
+
if (row2 && snap) {
|
|
15966
|
+
for (const inp of row2.inputs) {
|
|
15253
15967
|
const snapped = snap[inp.key];
|
|
15254
15968
|
const before = snapped !== void 0 ? snapped : inp.defval;
|
|
15255
|
-
if (
|
|
15256
|
-
|
|
15257
|
-
this.host.onChange?.({ indicatorId:
|
|
15969
|
+
if (row2.values[inp.key] !== before) {
|
|
15970
|
+
row2.values[inp.key] = before;
|
|
15971
|
+
this.host.onChange?.({ indicatorId: row2.id, key: inp.key, value: before });
|
|
15258
15972
|
}
|
|
15259
15973
|
}
|
|
15260
15974
|
}
|
|
15261
15975
|
const propSnap = this.propSnapshot;
|
|
15262
|
-
if (
|
|
15263
|
-
for (const p of
|
|
15976
|
+
if (row2 && propSnap) {
|
|
15977
|
+
for (const p of row2.props) {
|
|
15264
15978
|
const snapped = propSnap[p.key];
|
|
15265
15979
|
const before = snapped !== void 0 ? snapped : p.defval;
|
|
15266
|
-
if (
|
|
15267
|
-
|
|
15268
|
-
this.host.onChange?.({ indicatorId:
|
|
15980
|
+
if (row2.propValues[p.key] !== before) {
|
|
15981
|
+
row2.propValues[p.key] = before;
|
|
15982
|
+
this.host.onChange?.({ indicatorId: row2.id, key: p.key, value: before, kind: "prop" });
|
|
15269
15983
|
}
|
|
15270
15984
|
}
|
|
15271
15985
|
}
|
|
@@ -15283,35 +15997,35 @@ var IndicatorInputsDialog = class {
|
|
|
15283
15997
|
* rebuild-after-reset move as the chart-settings dialog. The open-time snapshot
|
|
15284
15998
|
* survives the rebuild, so Cancel after a reset still reverts the whole session. */
|
|
15285
15999
|
resetToDefaults() {
|
|
15286
|
-
const
|
|
15287
|
-
if (!
|
|
16000
|
+
const row2 = this.row;
|
|
16001
|
+
if (!row2) return;
|
|
15288
16002
|
const snap = this.snapshot;
|
|
15289
16003
|
const propSnap = this.propSnapshot;
|
|
15290
|
-
for (const inp of
|
|
15291
|
-
if (
|
|
15292
|
-
|
|
15293
|
-
this.host.onChange?.({ indicatorId:
|
|
16004
|
+
for (const inp of row2.inputs) {
|
|
16005
|
+
if (row2.values[inp.key] !== inp.defval) {
|
|
16006
|
+
row2.values[inp.key] = inp.defval;
|
|
16007
|
+
this.host.onChange?.({ indicatorId: row2.id, key: inp.key, value: inp.defval });
|
|
15294
16008
|
}
|
|
15295
16009
|
}
|
|
15296
|
-
for (const p of
|
|
15297
|
-
if (
|
|
15298
|
-
|
|
15299
|
-
this.host.onChange?.({ indicatorId:
|
|
16010
|
+
for (const p of row2.props) {
|
|
16011
|
+
if (row2.propValues[p.key] !== p.defval) {
|
|
16012
|
+
row2.propValues[p.key] = p.defval;
|
|
16013
|
+
this.host.onChange?.({ indicatorId: row2.id, key: p.key, value: p.defval, kind: "prop" });
|
|
15300
16014
|
}
|
|
15301
16015
|
}
|
|
15302
|
-
this.open(
|
|
16016
|
+
this.open(row2);
|
|
15303
16017
|
if (snap) this.snapshot = snap;
|
|
15304
16018
|
if (propSnap) this.propSnapshot = propSnap;
|
|
15305
16019
|
}
|
|
15306
16020
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
15307
|
-
commit(
|
|
15308
|
-
bagOf(
|
|
15309
|
-
this.host.onChange?.({ indicatorId:
|
|
16021
|
+
commit(row2, decl, value) {
|
|
16022
|
+
bagOf(row2, decl)[decl.key] = value;
|
|
16023
|
+
this.host.onChange?.({ indicatorId: row2.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
15310
16024
|
this.refreshVisibility?.();
|
|
15311
16025
|
}
|
|
15312
16026
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
15313
|
-
buildInputRow(grid, decls,
|
|
15314
|
-
const idOf = (inp) => `vela-inp-${
|
|
16027
|
+
buildInputRow(grid, decls, row2) {
|
|
16028
|
+
const idOf = (inp) => `vela-inp-${row2.id}-${inp.key}`;
|
|
15315
16029
|
const append = (el) => {
|
|
15316
16030
|
grid.appendChild(el);
|
|
15317
16031
|
return el;
|
|
@@ -15322,7 +16036,7 @@ var IndicatorInputsDialog = class {
|
|
|
15322
16036
|
inline: decls.map((d) => ({
|
|
15323
16037
|
label: nameOf(d) || void 0,
|
|
15324
16038
|
id: idOf(d),
|
|
15325
|
-
control: this.buildControl(
|
|
16039
|
+
control: this.buildControl(row2, d, idOf(d)),
|
|
15326
16040
|
toggleFirst: d.type === "bool",
|
|
15327
16041
|
fit: d.type === "color" || d.type === "bool"
|
|
15328
16042
|
})),
|
|
@@ -15333,7 +16047,7 @@ var IndicatorInputsDialog = class {
|
|
|
15333
16047
|
const id = idOf(lead);
|
|
15334
16048
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
15335
16049
|
if (lead.type === "bool") {
|
|
15336
|
-
const current = Boolean(bagOf(
|
|
16050
|
+
const current = Boolean(bagOf(row2, lead)[lead.key] ?? lead.defval);
|
|
15337
16051
|
return append(fieldRow({
|
|
15338
16052
|
label: nameOf(lead),
|
|
15339
16053
|
id,
|
|
@@ -15342,7 +16056,7 @@ var IndicatorInputsDialog = class {
|
|
|
15342
16056
|
toggle: {
|
|
15343
16057
|
id,
|
|
15344
16058
|
checked: current,
|
|
15345
|
-
onChange: (v) => this.commit(
|
|
16059
|
+
onChange: (v) => this.commit(row2, lead, v)
|
|
15346
16060
|
}
|
|
15347
16061
|
}));
|
|
15348
16062
|
}
|
|
@@ -15352,7 +16066,7 @@ var IndicatorInputsDialog = class {
|
|
|
15352
16066
|
id,
|
|
15353
16067
|
stacked: true,
|
|
15354
16068
|
info,
|
|
15355
|
-
control: this.buildControl(
|
|
16069
|
+
control: this.buildControl(row2, lead, id)
|
|
15356
16070
|
}));
|
|
15357
16071
|
}
|
|
15358
16072
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
@@ -15362,13 +16076,13 @@ var IndicatorInputsDialog = class {
|
|
|
15362
16076
|
fit,
|
|
15363
16077
|
controlWidth: fit ? void 0 : 100,
|
|
15364
16078
|
info,
|
|
15365
|
-
control: this.buildControl(
|
|
16079
|
+
control: this.buildControl(row2, lead, id)
|
|
15366
16080
|
}));
|
|
15367
16081
|
}
|
|
15368
16082
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
15369
|
-
buildControl(
|
|
15370
|
-
const current = bagOf(
|
|
15371
|
-
const emit = (value) => this.commit(
|
|
16083
|
+
buildControl(row2, inp, id) {
|
|
16084
|
+
const current = bagOf(row2, inp)[inp.key] ?? inp.defval;
|
|
16085
|
+
const emit = (value) => this.commit(row2, inp, value);
|
|
15372
16086
|
if (inp.type === "bool") {
|
|
15373
16087
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
15374
16088
|
}
|
|
@@ -15379,7 +16093,7 @@ var IndicatorInputsDialog = class {
|
|
|
15379
16093
|
kind: "color",
|
|
15380
16094
|
id,
|
|
15381
16095
|
theme: this.host.theme(),
|
|
15382
|
-
get: () => String(bagOf(
|
|
16096
|
+
get: () => String(bagOf(row2, inp)[inp.key] ?? inp.defval),
|
|
15383
16097
|
onChange: (v) => emit(v),
|
|
15384
16098
|
// An input change re-executes the script over its whole history: commit the
|
|
15385
16099
|
// opacity drag once on release, not once per pointer move.
|
|
@@ -15664,21 +16378,21 @@ var IndicatorInputsDialog = class {
|
|
|
15664
16378
|
const picker = this.host.symbolPicker();
|
|
15665
16379
|
if (!picker) return this.buildTextField(id, current, emit);
|
|
15666
16380
|
let value = current;
|
|
15667
|
-
const
|
|
15668
|
-
|
|
15669
|
-
|
|
15670
|
-
|
|
16381
|
+
const btn = document.createElement("button");
|
|
16382
|
+
btn.type = "button";
|
|
16383
|
+
btn.id = id;
|
|
16384
|
+
btn.style.cssText = `${this.ctrlStyle()}display:flex;align-items:center;cursor:pointer;text-align:left;`;
|
|
15671
16385
|
const label = document.createElement("span");
|
|
15672
16386
|
label.textContent = value || "Select symbol\u2026";
|
|
15673
16387
|
label.style.cssText = "overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";
|
|
15674
|
-
|
|
15675
|
-
|
|
16388
|
+
btn.append(label);
|
|
16389
|
+
btn.addEventListener("click", () => picker(value, (picked) => {
|
|
15676
16390
|
if (!picked) return;
|
|
15677
16391
|
value = picked;
|
|
15678
16392
|
label.textContent = picked;
|
|
15679
16393
|
emit(picked);
|
|
15680
16394
|
}));
|
|
15681
|
-
return
|
|
16395
|
+
return btn;
|
|
15682
16396
|
}
|
|
15683
16397
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
15684
16398
|
ctrlStyle() {
|
|
@@ -15724,9 +16438,9 @@ function tabInputs(inputs) {
|
|
|
15724
16438
|
function rowVisible(decls, bag) {
|
|
15725
16439
|
return decls.some((d) => inputVisible(d.when, bag));
|
|
15726
16440
|
}
|
|
15727
|
-
function valuesBag(
|
|
16441
|
+
function valuesBag(row2) {
|
|
15728
16442
|
const bag = {};
|
|
15729
|
-
for (const inp of
|
|
16443
|
+
for (const inp of row2.inputs) bag[inp.key] = row2.values[inp.key] ?? inp.defval;
|
|
15730
16444
|
return bag;
|
|
15731
16445
|
}
|
|
15732
16446
|
function groupInputs(inputs) {
|
|
@@ -15922,7 +16636,7 @@ var InputsUI = class {
|
|
|
15922
16636
|
if (!this.selectedId) return;
|
|
15923
16637
|
const target = e.target;
|
|
15924
16638
|
if (target) {
|
|
15925
|
-
for (const
|
|
16639
|
+
for (const row2 of this.rows.values()) if (row2.el.contains(target)) return;
|
|
15926
16640
|
}
|
|
15927
16641
|
this.clearSelection();
|
|
15928
16642
|
};
|
|
@@ -16005,9 +16719,9 @@ var InputsUI = class {
|
|
|
16005
16719
|
setTheme(theme) {
|
|
16006
16720
|
this.theme = theme;
|
|
16007
16721
|
for (const lg of this.legends.values()) applyChromeTokens(lg, theme);
|
|
16008
|
-
for (const
|
|
16009
|
-
|
|
16010
|
-
|
|
16722
|
+
for (const row2 of this.rows.values()) {
|
|
16723
|
+
row2.el.style.background = row2.highlighted ? theme.background : this.idleRowFill();
|
|
16724
|
+
row2.el.style.color = theme.textColor;
|
|
16011
16725
|
}
|
|
16012
16726
|
if (this.foldToggle) this.syncFoldToggle();
|
|
16013
16727
|
}
|
|
@@ -16026,26 +16740,26 @@ var InputsUI = class {
|
|
|
16026
16740
|
*/
|
|
16027
16741
|
setLegendActions(provider) {
|
|
16028
16742
|
this.legendActions = provider;
|
|
16029
|
-
for (const
|
|
16743
|
+
for (const row2 of this.rows.values()) this.renderExtras(row2.id, row2.extrasEl);
|
|
16030
16744
|
}
|
|
16031
16745
|
/** (Re)build one row's contributed buttons. Same look, reveal and tooltips as the built-ins. */
|
|
16032
16746
|
renderExtras(id, extrasEl) {
|
|
16033
16747
|
this.disposeTips(this.extrasTips, id);
|
|
16034
16748
|
extrasEl.replaceChildren();
|
|
16035
16749
|
for (const action of this.legendActions?.(id) ?? []) {
|
|
16036
|
-
const
|
|
16037
|
-
|
|
16038
|
-
|
|
16039
|
-
this.tip(this.extrasTips, id,
|
|
16040
|
-
|
|
16041
|
-
|
|
16042
|
-
|
|
16043
|
-
|
|
16044
|
-
|
|
16750
|
+
const btn = document.createElement("button");
|
|
16751
|
+
btn.type = "button";
|
|
16752
|
+
btn.setAttribute("aria-label", action.tooltip);
|
|
16753
|
+
this.tip(this.extrasTips, id, btn, action.tooltip);
|
|
16754
|
+
btn.innerHTML = iconAt(action.icon, LEGEND_ICON_PX2);
|
|
16755
|
+
btn.className = "vela-ind-ctl";
|
|
16756
|
+
btn.dataset.legendAction = action.id;
|
|
16757
|
+
btn.style.cssText = LEGEND_CTL_CSS;
|
|
16758
|
+
btn.addEventListener("click", (e) => {
|
|
16045
16759
|
e.stopPropagation();
|
|
16046
16760
|
action.run();
|
|
16047
16761
|
});
|
|
16048
|
-
extrasEl.appendChild(
|
|
16762
|
+
extrasEl.appendChild(btn);
|
|
16049
16763
|
}
|
|
16050
16764
|
}
|
|
16051
16765
|
/**
|
|
@@ -16055,17 +16769,17 @@ var InputsUI = class {
|
|
|
16055
16769
|
*/
|
|
16056
16770
|
setLegendCallouts(provider) {
|
|
16057
16771
|
this.legendCallouts = provider;
|
|
16058
|
-
for (const
|
|
16772
|
+
for (const row2 of this.rows.values()) this.renderCallouts(row2);
|
|
16059
16773
|
}
|
|
16060
16774
|
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
16061
16775
|
* click (when the view carries content) deploys a panel of text and actions. */
|
|
16062
|
-
renderCallouts(
|
|
16063
|
-
this.disposeTips(this.calloutTips,
|
|
16064
|
-
for (const bubble of
|
|
16065
|
-
|
|
16066
|
-
|
|
16067
|
-
const views = this.legendCallouts?.(
|
|
16068
|
-
|
|
16776
|
+
renderCallouts(row2) {
|
|
16777
|
+
this.disposeTips(this.calloutTips, row2.id);
|
|
16778
|
+
for (const bubble of row2.callouts) bubble.destroy();
|
|
16779
|
+
row2.callouts = [];
|
|
16780
|
+
row2.calloutsEl.replaceChildren();
|
|
16781
|
+
const views = this.legendCallouts?.(row2.id) ?? [];
|
|
16782
|
+
row2.calloutsEl.style.display = legendCalloutsDisplay(row2.highlighted, views.length > 0);
|
|
16069
16783
|
for (const view of views) {
|
|
16070
16784
|
const bubble = new CalloutBubble({
|
|
16071
16785
|
icon: view.icon,
|
|
@@ -16079,9 +16793,9 @@ var InputsUI = class {
|
|
|
16079
16793
|
theme: () => this.theme
|
|
16080
16794
|
});
|
|
16081
16795
|
bubble.el.dataset.legendCallout = view.id;
|
|
16082
|
-
this.tip(this.calloutTips,
|
|
16083
|
-
|
|
16084
|
-
|
|
16796
|
+
this.tip(this.calloutTips, row2.id, bubble.el, view.tooltip);
|
|
16797
|
+
row2.callouts.push(bubble);
|
|
16798
|
+
row2.calloutsEl.appendChild(bubble.el);
|
|
16085
16799
|
}
|
|
16086
16800
|
}
|
|
16087
16801
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
@@ -16101,9 +16815,9 @@ var InputsUI = class {
|
|
|
16101
16815
|
*/
|
|
16102
16816
|
setValuesVisible(visible) {
|
|
16103
16817
|
this.valuesVisible = visible;
|
|
16104
|
-
for (const
|
|
16105
|
-
|
|
16106
|
-
this.renderRowValues(
|
|
16818
|
+
for (const row2 of this.rows.values()) {
|
|
16819
|
+
row2.showValues = null;
|
|
16820
|
+
this.renderRowValues(row2);
|
|
16107
16821
|
}
|
|
16108
16822
|
}
|
|
16109
16823
|
/**
|
|
@@ -16112,35 +16826,35 @@ var InputsUI = class {
|
|
|
16112
16826
|
* unchanged frames: each row diffs against its last rendered values before touching DOM.
|
|
16113
16827
|
*/
|
|
16114
16828
|
setPlotValues(values) {
|
|
16115
|
-
for (const
|
|
16116
|
-
const next = values.get(
|
|
16829
|
+
for (const row2 of this.rows.values()) {
|
|
16830
|
+
const next = values.get(row2.id) ?? [];
|
|
16117
16831
|
const key = next.map((v) => `${v.value}|${v.color}`).join("\0");
|
|
16118
|
-
if (key ===
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
this.renderRowValues(
|
|
16832
|
+
if (key === row2.plotValuesKey) continue;
|
|
16833
|
+
row2.plotValues = next;
|
|
16834
|
+
row2.plotValuesKey = key;
|
|
16835
|
+
this.renderRowValues(row2);
|
|
16122
16836
|
}
|
|
16123
16837
|
}
|
|
16124
16838
|
/** Whether a row currently shows its values: its own override, else the chart-wide flag. */
|
|
16125
|
-
rowValuesShown(
|
|
16126
|
-
return
|
|
16839
|
+
rowValuesShown(row2) {
|
|
16840
|
+
return row2.showValues ?? this.valuesVisible;
|
|
16127
16841
|
}
|
|
16128
16842
|
/** (Re)build one row's values readout from its stored plot values + visibility. A
|
|
16129
16843
|
* highlighted (hovered/selected) row shows only the title + controls — no values. */
|
|
16130
|
-
renderRowValues(
|
|
16131
|
-
if (!this.rowValuesShown(
|
|
16132
|
-
|
|
16133
|
-
|
|
16844
|
+
renderRowValues(row2) {
|
|
16845
|
+
if (!this.rowValuesShown(row2) || row2.plotValues.length === 0 || row2.highlighted) {
|
|
16846
|
+
row2.valuesEl.style.display = "none";
|
|
16847
|
+
row2.valuesEl.replaceChildren();
|
|
16134
16848
|
return;
|
|
16135
16849
|
}
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16850
|
+
row2.valuesEl.style.display = "block";
|
|
16851
|
+
row2.valuesEl.replaceChildren();
|
|
16852
|
+
row2.plotValues.forEach((v, i) => {
|
|
16139
16853
|
const span = document.createElement("span");
|
|
16140
16854
|
span.textContent = v.value;
|
|
16141
16855
|
span.style.color = v.color;
|
|
16142
16856
|
if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
|
|
16143
|
-
|
|
16857
|
+
row2.valuesEl.appendChild(span);
|
|
16144
16858
|
});
|
|
16145
16859
|
}
|
|
16146
16860
|
// ── legend move/merge (menu + drag) ─────────────────────────────────────
|
|
@@ -16149,8 +16863,8 @@ var InputsUI = class {
|
|
|
16149
16863
|
this.closeMoveMenu();
|
|
16150
16864
|
const api = this.moveApi;
|
|
16151
16865
|
if (!api) return;
|
|
16152
|
-
const
|
|
16153
|
-
const currentPane =
|
|
16866
|
+
const row2 = this.rows.get(id);
|
|
16867
|
+
const currentPane = row2?.paneId ?? "price";
|
|
16154
16868
|
const panes = api.panes();
|
|
16155
16869
|
const items = [];
|
|
16156
16870
|
for (const p of panes) {
|
|
@@ -16190,13 +16904,13 @@ var InputsUI = class {
|
|
|
16190
16904
|
openRowMenu(id, x, y) {
|
|
16191
16905
|
this.closeRowMenu();
|
|
16192
16906
|
this.closeMoveMenu();
|
|
16193
|
-
const
|
|
16194
|
-
if (!
|
|
16907
|
+
const row2 = this.rows.get(id);
|
|
16908
|
+
if (!row2) return;
|
|
16195
16909
|
this.rowMenuId = id;
|
|
16196
16910
|
const items = [{
|
|
16197
16911
|
id: "values",
|
|
16198
16912
|
label: "Indicator values",
|
|
16199
|
-
checked: this.rowValuesShown(
|
|
16913
|
+
checked: this.rowValuesShown(row2)
|
|
16200
16914
|
}];
|
|
16201
16915
|
if (!this.rowMenu) {
|
|
16202
16916
|
this.rowMenu = new Menu({
|
|
@@ -16223,11 +16937,11 @@ var InputsUI = class {
|
|
|
16223
16937
|
}
|
|
16224
16938
|
/** Move a legend row to another pane (indicator merged/moved), tidying an emptied container. */
|
|
16225
16939
|
setPane(id, paneId) {
|
|
16226
|
-
const
|
|
16227
|
-
if (!
|
|
16228
|
-
const prev2 =
|
|
16229
|
-
|
|
16230
|
-
this.attach(this.legendFor(paneId),
|
|
16940
|
+
const row2 = this.rows.get(id);
|
|
16941
|
+
if (!row2 || row2.paneId === paneId) return;
|
|
16942
|
+
const prev2 = row2.paneId;
|
|
16943
|
+
row2.paneId = paneId;
|
|
16944
|
+
this.attach(this.legendFor(paneId), row2.el, row2.native);
|
|
16231
16945
|
this.syncFoldToggle();
|
|
16232
16946
|
if (prev2 !== "price") {
|
|
16233
16947
|
const lg = this.legends.get(prev2);
|
|
@@ -16257,9 +16971,9 @@ var InputsUI = class {
|
|
|
16257
16971
|
lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
|
|
16258
16972
|
const collapsed = this.paneCollapse.has(paneId);
|
|
16259
16973
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
16260
|
-
for (const
|
|
16261
|
-
if (
|
|
16262
|
-
|
|
16974
|
+
for (const row2 of this.rows.values()) {
|
|
16975
|
+
if (row2.paneId !== paneId) continue;
|
|
16976
|
+
row2.el.style.display = (collapsed ? row2.id !== masterId : this.legendFolded) ? "none" : "flex";
|
|
16263
16977
|
}
|
|
16264
16978
|
let top = bounds.top + 8;
|
|
16265
16979
|
if (collapsed && Number.isFinite(bounds.height)) {
|
|
@@ -16293,13 +17007,13 @@ var InputsUI = class {
|
|
|
16293
17007
|
if (wasFolded && count > 0) this.reposition();
|
|
16294
17008
|
return;
|
|
16295
17009
|
}
|
|
16296
|
-
let
|
|
16297
|
-
if (!
|
|
16298
|
-
|
|
16299
|
-
|
|
16300
|
-
|
|
16301
|
-
this.tip(this.rowTips, "fold",
|
|
16302
|
-
|
|
17010
|
+
let btn = this.foldToggle;
|
|
17011
|
+
if (!btn) {
|
|
17012
|
+
btn = document.createElement("button");
|
|
17013
|
+
btn.type = "button";
|
|
17014
|
+
btn.className = "vela-ind-fold";
|
|
17015
|
+
this.tip(this.rowTips, "fold", btn, () => this.overviewAction !== null ? "Indicators" : this.legendFolded ? "Show indicator legend" : "Hide indicator legend");
|
|
17016
|
+
btn.addEventListener("click", () => {
|
|
16303
17017
|
if (this.overviewAction !== null) {
|
|
16304
17018
|
this.overviewAction();
|
|
16305
17019
|
return;
|
|
@@ -16307,24 +17021,24 @@ var InputsUI = class {
|
|
|
16307
17021
|
this.legendFolded = !this.legendFolded;
|
|
16308
17022
|
this.syncFoldToggle();
|
|
16309
17023
|
});
|
|
16310
|
-
this.foldToggle =
|
|
17024
|
+
this.foldToggle = btn;
|
|
16311
17025
|
}
|
|
16312
17026
|
const folded = this.legendFolded;
|
|
16313
|
-
|
|
17027
|
+
btn.setAttribute("aria-label", this.overviewAction !== null ? "Indicators" : folded ? "Show indicator legend" : "Hide indicator legend");
|
|
16314
17028
|
const ink = this.theme.textColor;
|
|
16315
|
-
|
|
16316
|
-
|
|
17029
|
+
btn.style.cssText = `pointer-events:auto;display:inline-flex;align-items:center;gap:4px;background:${this.theme.background};color:${ink};border:1px solid ${this.theme.borderColor};border-radius:4px;padding:2px 6px;margin-top:6px;cursor:pointer;font:inherit;line-height:0;`;
|
|
17030
|
+
btn.replaceChildren();
|
|
16317
17031
|
const icon2 = document.createElement("span");
|
|
16318
17032
|
icon2.style.cssText = "display:inline-flex;align-items:center;line-height:0;opacity:0.7;";
|
|
16319
17033
|
icon2.innerHTML = this.overviewAction !== null ? OVERVIEW_SVG : folded ? UNFOLD_SVG : FOLD_SVG;
|
|
16320
|
-
|
|
17034
|
+
btn.appendChild(icon2);
|
|
16321
17035
|
if (folded) {
|
|
16322
17036
|
const label = document.createElement("span");
|
|
16323
17037
|
label.textContent = String(count);
|
|
16324
17038
|
label.style.cssText = `font-weight:600;line-height:normal;font-size:12px;color:${ink};`;
|
|
16325
|
-
|
|
17039
|
+
btn.appendChild(label);
|
|
16326
17040
|
}
|
|
16327
|
-
this.legendFor("price").appendChild(
|
|
17041
|
+
this.legendFor("price").appendChild(btn);
|
|
16328
17042
|
this.reposition();
|
|
16329
17043
|
}
|
|
16330
17044
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
@@ -16406,8 +17120,8 @@ var InputsUI = class {
|
|
|
16406
17120
|
eye.className = "vela-ind-ctl";
|
|
16407
17121
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
16408
17122
|
eye.addEventListener("click", () => {
|
|
16409
|
-
const
|
|
16410
|
-
this.onToggleVisible?.(id, Boolean(
|
|
17123
|
+
const row3 = this.rows.get(id);
|
|
17124
|
+
this.onToggleVisible?.(id, Boolean(row3?.hidden));
|
|
16411
17125
|
});
|
|
16412
17126
|
controlsEl.appendChild(eye);
|
|
16413
17127
|
eyeEl = eye;
|
|
@@ -16452,9 +17166,9 @@ var InputsUI = class {
|
|
|
16452
17166
|
controlsEl.appendChild(close);
|
|
16453
17167
|
el.appendChild(controlsEl);
|
|
16454
17168
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
16455
|
-
const
|
|
16456
|
-
this.rows.set(id,
|
|
16457
|
-
this.renderCallouts(
|
|
17169
|
+
const row2 = { id, title, inputs, values: { ...values }, props: opts.props ?? [], propValues: { ...opts.propValues ?? {} }, el, titleEl, statusEl, valuesEl, plotValues: [], plotValuesKey: "", showValues: null, highlighted: false, paneId, hidden: false, eyeEl, controlsEl, extrasEl, calloutsEl, callouts: [], native: !!opts.native };
|
|
17170
|
+
this.rows.set(id, row2);
|
|
17171
|
+
this.renderCallouts(row2);
|
|
16458
17172
|
this.syncFoldToggle();
|
|
16459
17173
|
}
|
|
16460
17174
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -16464,10 +17178,10 @@ var InputsUI = class {
|
|
|
16464
17178
|
}
|
|
16465
17179
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
16466
17180
|
setValues(id, values, props) {
|
|
16467
|
-
const
|
|
16468
|
-
if (!
|
|
16469
|
-
|
|
16470
|
-
if (props)
|
|
17181
|
+
const row2 = this.rows.get(id);
|
|
17182
|
+
if (!row2) return;
|
|
17183
|
+
row2.values = { ...row2.values, ...values };
|
|
17184
|
+
if (props) row2.propValues = { ...row2.propValues, ...props };
|
|
16471
17185
|
}
|
|
16472
17186
|
/**
|
|
16473
17187
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -16475,9 +17189,9 @@ var InputsUI = class {
|
|
|
16475
17189
|
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
16476
17190
|
*/
|
|
16477
17191
|
setStatus(id, status) {
|
|
16478
|
-
const
|
|
16479
|
-
if (!
|
|
16480
|
-
const el =
|
|
17192
|
+
const row2 = this.rows.get(id);
|
|
17193
|
+
if (!row2) return;
|
|
17194
|
+
const el = row2.statusEl;
|
|
16481
17195
|
el.replaceChildren();
|
|
16482
17196
|
if (status === "idle") {
|
|
16483
17197
|
el.style.cssText = "display:none;box-sizing:border-box;flex:none;";
|
|
@@ -16506,15 +17220,15 @@ var InputsUI = class {
|
|
|
16506
17220
|
}
|
|
16507
17221
|
/** Mark a row hidden/shown — swaps the eye glyph and dims the row; does NOT remove it. */
|
|
16508
17222
|
setVisible(id, visible) {
|
|
16509
|
-
const
|
|
16510
|
-
if (!
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
if (
|
|
16514
|
-
|
|
16515
|
-
|
|
17223
|
+
const row2 = this.rows.get(id);
|
|
17224
|
+
if (!row2) return;
|
|
17225
|
+
row2.hidden = !visible;
|
|
17226
|
+
row2.el.style.opacity = visible ? "1" : "0.5";
|
|
17227
|
+
if (row2.eyeEl) {
|
|
17228
|
+
row2.eyeEl.innerHTML = visible ? EYE_SVG : EYE_OFF_SVG;
|
|
17229
|
+
row2.eyeEl.setAttribute("aria-label", visible ? "Hide" : "Show");
|
|
16516
17230
|
}
|
|
16517
|
-
this.syncRowActions(
|
|
17231
|
+
this.syncRowActions(row2);
|
|
16518
17232
|
}
|
|
16519
17233
|
/**
|
|
16520
17234
|
* Select a legend row (outlining it) and clear any previous selection, so at most one
|
|
@@ -16533,32 +17247,32 @@ var InputsUI = class {
|
|
|
16533
17247
|
* visible even when idle, so it can be un-hidden without hovering.
|
|
16534
17248
|
*/
|
|
16535
17249
|
setRowHighlighted(id, highlighted) {
|
|
16536
|
-
const
|
|
16537
|
-
if (!
|
|
16538
|
-
|
|
16539
|
-
this.renderRowValues(
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
this.syncRowActions(
|
|
17250
|
+
const row2 = this.rows.get(id);
|
|
17251
|
+
if (!row2) return;
|
|
17252
|
+
row2.highlighted = highlighted;
|
|
17253
|
+
this.renderRowValues(row2);
|
|
17254
|
+
row2.el.style.boxShadow = highlighted ? `inset 0 0 0 1px ${this.neutralBorder()}` : "none";
|
|
17255
|
+
row2.el.style.background = highlighted ? this.theme.background : this.idleRowFill();
|
|
17256
|
+
this.syncRowActions(row2);
|
|
16543
17257
|
}
|
|
16544
17258
|
/**
|
|
16545
17259
|
* Reveal the action cluster (or just the eye for a hidden indicator). Non-eye
|
|
16546
17260
|
* buttons only appear while highlighted; the eye keeps its inline-flex from
|
|
16547
17261
|
* construction and is shown/hidden with the cluster container.
|
|
16548
17262
|
*/
|
|
16549
|
-
syncRowActions(
|
|
16550
|
-
const open2 =
|
|
16551
|
-
|
|
17263
|
+
syncRowActions(row2) {
|
|
17264
|
+
const open2 = row2.highlighted;
|
|
17265
|
+
row2.controlsEl.style.display = open2 || row2.hidden ? "inline-flex" : "none";
|
|
16552
17266
|
if (open2) {
|
|
16553
|
-
|
|
16554
|
-
for (const bubble of
|
|
17267
|
+
row2.el.appendChild(row2.statusEl);
|
|
17268
|
+
for (const bubble of row2.callouts) bubble.hidePanel();
|
|
16555
17269
|
} else {
|
|
16556
|
-
|
|
17270
|
+
row2.el.insertBefore(row2.statusEl, row2.valuesEl);
|
|
16557
17271
|
}
|
|
16558
|
-
|
|
16559
|
-
for (const child of Array.from(
|
|
16560
|
-
if (!(child instanceof HTMLElement) || child ===
|
|
16561
|
-
if (child ===
|
|
17272
|
+
row2.calloutsEl.style.display = legendCalloutsDisplay(open2, row2.callouts.length > 0);
|
|
17273
|
+
for (const child of Array.from(row2.controlsEl.children)) {
|
|
17274
|
+
if (!(child instanceof HTMLElement) || child === row2.eyeEl) continue;
|
|
17275
|
+
if (child === row2.extrasEl) {
|
|
16562
17276
|
child.style.display = open2 ? "contents" : "none";
|
|
16563
17277
|
continue;
|
|
16564
17278
|
}
|
|
@@ -16572,9 +17286,9 @@ var InputsUI = class {
|
|
|
16572
17286
|
this.selectedId = null;
|
|
16573
17287
|
}
|
|
16574
17288
|
remove(id) {
|
|
16575
|
-
const
|
|
16576
|
-
for (const bubble of
|
|
16577
|
-
|
|
17289
|
+
const row2 = this.rows.get(id);
|
|
17290
|
+
for (const bubble of row2?.callouts ?? []) bubble.destroy();
|
|
17291
|
+
row2?.el.remove();
|
|
16578
17292
|
this.rows.delete(id);
|
|
16579
17293
|
this.disposeTips(this.rowTips, id);
|
|
16580
17294
|
this.disposeTips(this.extrasTips, id);
|
|
@@ -16582,11 +17296,11 @@ var InputsUI = class {
|
|
|
16582
17296
|
if (this.selectedId === id) this.selectedId = null;
|
|
16583
17297
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
16584
17298
|
this.syncFoldToggle();
|
|
16585
|
-
if (
|
|
16586
|
-
const lg = this.legends.get(
|
|
17299
|
+
if (row2 && row2.paneId !== "price") {
|
|
17300
|
+
const lg = this.legends.get(row2.paneId);
|
|
16587
17301
|
if (lg && lg.childElementCount === 0) {
|
|
16588
17302
|
lg.remove();
|
|
16589
|
-
this.legends.delete(
|
|
17303
|
+
this.legends.delete(row2.paneId);
|
|
16590
17304
|
}
|
|
16591
17305
|
}
|
|
16592
17306
|
}
|
|
@@ -16599,7 +17313,7 @@ var InputsUI = class {
|
|
|
16599
17313
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
16600
17314
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
16601
17315
|
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
16602
|
-
for (const
|
|
17316
|
+
for (const row2 of this.rows.values()) for (const bubble of row2.callouts) bubble.destroy();
|
|
16603
17317
|
for (const lg of this.legends.values()) lg.remove();
|
|
16604
17318
|
this.legends.clear();
|
|
16605
17319
|
this.rows.clear();
|
|
@@ -16620,8 +17334,8 @@ var InputsUI = class {
|
|
|
16620
17334
|
this.inputsDialog.close();
|
|
16621
17335
|
}
|
|
16622
17336
|
openDialog(id) {
|
|
16623
|
-
const
|
|
16624
|
-
if (
|
|
17337
|
+
const row2 = this.rows.get(id);
|
|
17338
|
+
if (row2) this.inputsDialog.open(row2);
|
|
16625
17339
|
}
|
|
16626
17340
|
/** Idle legend-row fill — a translucent wash of the chart background, so the title keeps
|
|
16627
17341
|
* contrast when candles reach it without laying a solid block over the plot. */
|
|
@@ -16793,9 +17507,9 @@ var PaneControls = class {
|
|
|
16793
17507
|
const soloChip = stateChipRole != null && !hovered;
|
|
16794
17508
|
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
16795
17509
|
for (const child of cluster.children) {
|
|
16796
|
-
const
|
|
16797
|
-
|
|
16798
|
-
|
|
17510
|
+
const btn = child;
|
|
17511
|
+
btn.style.display = "inline-flex";
|
|
17512
|
+
btn.style.visibility = soloChip && btn.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
16799
17513
|
}
|
|
16800
17514
|
}
|
|
16801
17515
|
}
|
|
@@ -17250,28 +17964,28 @@ function factoryResetConfig(factory, priceStyle = factory.series.style) {
|
|
|
17250
17964
|
for (const t of chartTypes()) {
|
|
17251
17965
|
const section = t.settings;
|
|
17252
17966
|
if (!section) continue;
|
|
17253
|
-
const
|
|
17967
|
+
const defaults5 = {};
|
|
17254
17968
|
const addRows = (rows) => {
|
|
17255
17969
|
for (const r of rows ?? []) {
|
|
17256
|
-
for (const k of settingsRowValueKeys(r))
|
|
17970
|
+
for (const k of settingsRowValueKeys(r)) defaults5[k.key] = k.defval;
|
|
17257
17971
|
}
|
|
17258
17972
|
};
|
|
17259
17973
|
for (const inst of section.instances ?? []) {
|
|
17260
|
-
if (inst.enableKey)
|
|
17974
|
+
if (inst.enableKey) defaults5[inst.enableKey] = false;
|
|
17261
17975
|
addRows(inst.rows);
|
|
17262
17976
|
}
|
|
17263
17977
|
for (const sub2 of section.subsections ?? []) {
|
|
17264
|
-
if (sub2.enableKey)
|
|
17978
|
+
if (sub2.enableKey) defaults5[sub2.enableKey] = false;
|
|
17265
17979
|
addRows(sub2.rows);
|
|
17266
17980
|
}
|
|
17267
17981
|
if (!section.instances) addRows(section.rows);
|
|
17268
|
-
bag[t.id] =
|
|
17982
|
+
bag[t.id] = defaults5;
|
|
17269
17983
|
}
|
|
17270
17984
|
for (const t of chartTypes()) {
|
|
17271
17985
|
if (!hasOwnCandlePaint(t.id)) continue;
|
|
17272
|
-
const
|
|
17273
|
-
for (const key of CANDLE_OVERRIDE_KEYS)
|
|
17274
|
-
bag[t.id] =
|
|
17986
|
+
const defaults5 = bag[t.id] ?? {};
|
|
17987
|
+
for (const key of CANDLE_OVERRIDE_KEYS) defaults5[key] = null;
|
|
17988
|
+
bag[t.id] = defaults5;
|
|
17275
17989
|
}
|
|
17276
17990
|
for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
|
|
17277
17991
|
bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
|
|
@@ -21548,28 +22262,28 @@ function formatPriceLabel(scale, heightPx, value, mintick) {
|
|
|
21548
22262
|
return value.toFixed(axisDecimals(scale, heightPx, mintick));
|
|
21549
22263
|
}
|
|
21550
22264
|
var SEC = 1e3;
|
|
21551
|
-
var
|
|
21552
|
-
var
|
|
21553
|
-
var
|
|
21554
|
-
var WEEK = 7 *
|
|
21555
|
-
var MONTH = 30 *
|
|
21556
|
-
var YEAR = 365 *
|
|
22265
|
+
var MIN2 = 60 * SEC;
|
|
22266
|
+
var HOUR2 = 60 * MIN2;
|
|
22267
|
+
var DAY2 = 24 * HOUR2;
|
|
22268
|
+
var WEEK = 7 * DAY2;
|
|
22269
|
+
var MONTH = 30 * DAY2;
|
|
22270
|
+
var YEAR = 365 * DAY2;
|
|
21557
22271
|
var STEP_LADDER = [
|
|
21558
22272
|
SEC,
|
|
21559
22273
|
5 * SEC,
|
|
21560
22274
|
15 * SEC,
|
|
21561
22275
|
30 * SEC,
|
|
21562
|
-
|
|
21563
|
-
5 *
|
|
21564
|
-
15 *
|
|
21565
|
-
30 *
|
|
21566
|
-
|
|
21567
|
-
2 *
|
|
21568
|
-
4 *
|
|
21569
|
-
6 *
|
|
21570
|
-
12 *
|
|
21571
|
-
|
|
21572
|
-
2 *
|
|
22276
|
+
MIN2,
|
|
22277
|
+
5 * MIN2,
|
|
22278
|
+
15 * MIN2,
|
|
22279
|
+
30 * MIN2,
|
|
22280
|
+
HOUR2,
|
|
22281
|
+
2 * HOUR2,
|
|
22282
|
+
4 * HOUR2,
|
|
22283
|
+
6 * HOUR2,
|
|
22284
|
+
12 * HOUR2,
|
|
22285
|
+
DAY2,
|
|
22286
|
+
2 * DAY2,
|
|
21573
22287
|
WEEK,
|
|
21574
22288
|
MONTH,
|
|
21575
22289
|
3 * MONTH,
|
|
@@ -21581,7 +22295,7 @@ var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
|
21581
22295
|
function formatTimeStamp(ms, timeZone, barIntervalMs) {
|
|
21582
22296
|
const d = zonedDate(ms, timeZone);
|
|
21583
22297
|
const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
|
|
21584
|
-
if (barIntervalMs >=
|
|
22298
|
+
if (barIntervalMs >= DAY2) return date;
|
|
21585
22299
|
return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
|
|
21586
22300
|
}
|
|
21587
22301
|
function pickStep(targetMs) {
|
|
@@ -21602,7 +22316,7 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
21602
22316
|
const d = new Date(zt);
|
|
21603
22317
|
let label;
|
|
21604
22318
|
let major = false;
|
|
21605
|
-
if (step <
|
|
22319
|
+
if (step < DAY2) {
|
|
21606
22320
|
const h = d.getUTCHours();
|
|
21607
22321
|
const m = d.getUTCMinutes();
|
|
21608
22322
|
if (h === 0 && m === 0) {
|
|
@@ -21670,8 +22384,8 @@ var PriceAxisTickSource = class {
|
|
|
21670
22384
|
if (m && m.kind === pane.kind && m.min === s.min && m.max === s.max && m.log === !!s.log && m.invert === !!s.invert && m.height === height && m.baseline === pct?.baseline && m.indexed === !!pct?.indexed && m.mintick === scene.priceMintick && m.format === format && m.fontSize === fontSize) {
|
|
21671
22385
|
return m.ticks;
|
|
21672
22386
|
}
|
|
21673
|
-
const
|
|
21674
|
-
const ticks = this.callHook(fn, scene, pane, coords,
|
|
22387
|
+
const defaults5 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
|
|
22388
|
+
const ticks = this.callHook(fn, scene, pane, coords, defaults5, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
|
|
21675
22389
|
this.memo.set(pane, {
|
|
21676
22390
|
kind: pane.kind,
|
|
21677
22391
|
min: s.min,
|
|
@@ -21688,7 +22402,7 @@ var PriceAxisTickSource = class {
|
|
|
21688
22402
|
});
|
|
21689
22403
|
return ticks;
|
|
21690
22404
|
}
|
|
21691
|
-
callHook(fn, scene, pane, coords,
|
|
22405
|
+
callHook(fn, scene, pane, coords, defaults5, baseline, mode) {
|
|
21692
22406
|
const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
|
|
21693
22407
|
const bounds = { top: 0, height: pane.bounds.height };
|
|
21694
22408
|
const ctx = {
|
|
@@ -21702,19 +22416,19 @@ var PriceAxisTickSource = class {
|
|
|
21702
22416
|
mintick: scene.priceMintick,
|
|
21703
22417
|
fontSize: scene.style.fontSize,
|
|
21704
22418
|
priceToY: (price) => coords.priceToY(price, scale, bounds),
|
|
21705
|
-
defaults:
|
|
22419
|
+
defaults: defaults5
|
|
21706
22420
|
};
|
|
21707
22421
|
let out;
|
|
21708
22422
|
try {
|
|
21709
22423
|
out = fn(ctx);
|
|
21710
22424
|
} catch (err) {
|
|
21711
22425
|
this.warnOnce("threw", err);
|
|
21712
|
-
return
|
|
22426
|
+
return defaults5;
|
|
21713
22427
|
}
|
|
21714
|
-
if (out == null) return
|
|
22428
|
+
if (out == null) return defaults5;
|
|
21715
22429
|
if (!Array.isArray(out)) {
|
|
21716
22430
|
this.warnOnce("returned a non-array", out);
|
|
21717
|
-
return
|
|
22431
|
+
return defaults5;
|
|
21718
22432
|
}
|
|
21719
22433
|
const ticks = [];
|
|
21720
22434
|
for (const t of out) {
|
|
@@ -21773,12 +22487,12 @@ var MARK_DECK_STEP = 3;
|
|
|
21773
22487
|
var MARK_FAN_GAP = 4;
|
|
21774
22488
|
var MARK_HIT_PAD = 3;
|
|
21775
22489
|
var MARK_FAN_HOLD = 8;
|
|
21776
|
-
function snapMarkBar(time, barTimes,
|
|
22490
|
+
function snapMarkBar(time, barTimes, intervalMs) {
|
|
21777
22491
|
const n = barTimes.length;
|
|
21778
|
-
if (n === 0 || !(
|
|
22492
|
+
if (n === 0 || !(intervalMs > 0) || !Number.isFinite(time)) return null;
|
|
21779
22493
|
if (time < barTimes[0]) return null;
|
|
21780
22494
|
const last = barTimes[n - 1];
|
|
21781
|
-
if (time >= last +
|
|
22495
|
+
if (time >= last + intervalMs) return n - 1 + Math.floor((time - last) / intervalMs);
|
|
21782
22496
|
let lo = 0;
|
|
21783
22497
|
let hi = n - 1;
|
|
21784
22498
|
while (lo < hi) {
|
|
@@ -21786,14 +22500,14 @@ function snapMarkBar(time, barTimes, intervalMs2) {
|
|
|
21786
22500
|
if (barTimes[mid] <= time) lo = mid;
|
|
21787
22501
|
else hi = mid - 1;
|
|
21788
22502
|
}
|
|
21789
|
-
if (time < barTimes[lo] +
|
|
22503
|
+
if (time < barTimes[lo] + intervalMs) return lo;
|
|
21790
22504
|
return lo + 1;
|
|
21791
22505
|
}
|
|
21792
|
-
function clusterMarks(marks, barTimes,
|
|
22506
|
+
function clusterMarks(marks, barTimes, intervalMs, hidden) {
|
|
21793
22507
|
const byKey = /* @__PURE__ */ new Map();
|
|
21794
22508
|
marks.forEach((m, seq) => {
|
|
21795
22509
|
if (m.group !== void 0 && hidden(m.group)) return;
|
|
21796
|
-
const bar = snapMarkBar(m.time, barTimes,
|
|
22510
|
+
const bar = snapMarkBar(m.time, barTimes, intervalMs);
|
|
21797
22511
|
if (bar === null) return;
|
|
21798
22512
|
const key = `${bar}|${m.group ?? ""}`;
|
|
21799
22513
|
let c = byKey.get(key);
|
|
@@ -23477,16 +24191,16 @@ var SettingsDialog = class {
|
|
|
23477
24191
|
}
|
|
23478
24192
|
};
|
|
23479
24193
|
for (const { group: g, depth } of rows) {
|
|
23480
|
-
const
|
|
24194
|
+
const row2 = this.boolRow(g.label, this.markGroupOwnVisible(g.id), (v) => {
|
|
23481
24195
|
this.emit({ marks: { groups: { [g.id]: v } } });
|
|
23482
24196
|
refreshDimming();
|
|
23483
24197
|
});
|
|
23484
24198
|
if (depth > 0) {
|
|
23485
|
-
|
|
23486
|
-
|
|
24199
|
+
row2.classList.add("vela-sd-nested");
|
|
24200
|
+
row2.style.setProperty("--vela-sd-depth", String(depth));
|
|
23487
24201
|
}
|
|
23488
|
-
rowsById.set(g.id,
|
|
23489
|
-
body.append(sid(
|
|
24202
|
+
rowsById.set(g.id, row2);
|
|
24203
|
+
body.append(sid(row2, markGroupSettingsId(g.id)));
|
|
23490
24204
|
}
|
|
23491
24205
|
refreshDimming();
|
|
23492
24206
|
}
|
|
@@ -24309,6 +25023,16 @@ var DrawingPainter = class {
|
|
|
24309
25023
|
this.paintMagnifier(ctx, d, proj, theme);
|
|
24310
25024
|
return;
|
|
24311
25025
|
}
|
|
25026
|
+
if (d instanceof PriceActionZone) {
|
|
25027
|
+
this.paintPriceZone(ctx, d, proj, theme);
|
|
25028
|
+
this.paintLabel(ctx, d, proj, theme);
|
|
25029
|
+
return;
|
|
25030
|
+
}
|
|
25031
|
+
if (d instanceof Liquidity) {
|
|
25032
|
+
this.paintLiquidity(ctx, d, proj);
|
|
25033
|
+
this.paintLabel(ctx, d, proj, theme);
|
|
25034
|
+
return;
|
|
25035
|
+
}
|
|
24312
25036
|
if (d instanceof PatternDrawing) {
|
|
24313
25037
|
this.paintPattern(ctx, d, proj, theme);
|
|
24314
25038
|
return;
|
|
@@ -24351,9 +25075,11 @@ var DrawingPainter = class {
|
|
|
24351
25075
|
case "arrow": {
|
|
24352
25076
|
const pts = d.handlePoints(proj);
|
|
24353
25077
|
if (pts.length < 2) return;
|
|
25078
|
+
const ext = d instanceof TrendLine ? d.extension() : "none";
|
|
25079
|
+
const [ex1, ey1, ex2, ey2] = extendEndpoints(pts[0][0], pts[0][1], pts[1][0], pts[1][1], ext, proj.width, proj.height);
|
|
24354
25080
|
this.stroke(ctx, d.style, () => {
|
|
24355
|
-
ctx.moveTo(
|
|
24356
|
-
ctx.lineTo(
|
|
25081
|
+
ctx.moveTo(ex1, ey1);
|
|
25082
|
+
ctx.lineTo(ex2, ey2);
|
|
24357
25083
|
});
|
|
24358
25084
|
if (d.style.arrowRight) this.paintArrowhead(ctx, pts[0], pts[1], d.style);
|
|
24359
25085
|
if (d.style.arrowLeft) this.paintArrowhead(ctx, pts[1], pts[0], d.style);
|
|
@@ -24495,9 +25221,15 @@ var DrawingPainter = class {
|
|
|
24495
25221
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
24496
25222
|
ctx.textBaseline = "top";
|
|
24497
25223
|
ctx.textAlign = layout.align;
|
|
24498
|
-
ctx.fillStyle = text.color ?? theme.textColor;
|
|
24499
25224
|
const lh = labelLineHeight(fs);
|
|
24500
25225
|
const lines = text.value.split("\n");
|
|
25226
|
+
if (d.type !== "text" && d.type !== "box") {
|
|
25227
|
+
ctx.lineJoin = "round";
|
|
25228
|
+
ctx.lineWidth = 3;
|
|
25229
|
+
ctx.strokeStyle = theme.background;
|
|
25230
|
+
lines.forEach((line, i) => ctx.strokeText(line, layout.x, layout.top + i * lh));
|
|
25231
|
+
}
|
|
25232
|
+
ctx.fillStyle = text.color ?? theme.textColor;
|
|
24501
25233
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
24502
25234
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
24503
25235
|
ctx.restore();
|
|
@@ -24552,6 +25284,78 @@ var DrawingPainter = class {
|
|
|
24552
25284
|
ctx.textAlign = "left";
|
|
24553
25285
|
ctx.textBaseline = "alphabetic";
|
|
24554
25286
|
}
|
|
25287
|
+
/** Paint a price-action zone (fair value gap, order block): the box and its halfway line,
|
|
25288
|
+
* quieter once it has run its course, a dot where it stopped, and — once price flips it —
|
|
25289
|
+
* the dashed box it carries on as. With no zone under the anchor, a faint tick marks it. */
|
|
25290
|
+
paintPriceZone(ctx, d, proj, theme) {
|
|
25291
|
+
const z = d.zone(proj);
|
|
25292
|
+
if (!z) {
|
|
25293
|
+
const a = d.anchors[0];
|
|
25294
|
+
if (!a) return;
|
|
25295
|
+
const x = Math.round(proj.xOf(a.time)) + 0.5;
|
|
25296
|
+
this.stroke(ctx, { lineColor: withAlpha(theme.textColor, 0.35), lineWidth: 1, lineStyle: "dashed" }, () => {
|
|
25297
|
+
ctx.moveTo(x, 0);
|
|
25298
|
+
ctx.lineTo(x, proj.height);
|
|
25299
|
+
});
|
|
25300
|
+
return;
|
|
25301
|
+
}
|
|
25302
|
+
const h = z.yBottom - z.yTop;
|
|
25303
|
+
const stroke = toHex6(z.fill, z.fill);
|
|
25304
|
+
ctx.save();
|
|
25305
|
+
ctx.globalAlpha *= z.faded ? 0.6 : 1;
|
|
25306
|
+
ctx.fillStyle = z.fill;
|
|
25307
|
+
ctx.fillRect(z.x1, z.yTop, z.x2 - z.x1, h);
|
|
25308
|
+
if (z.border) {
|
|
25309
|
+
ctx.lineWidth = 1;
|
|
25310
|
+
ctx.setLineDash([]);
|
|
25311
|
+
ctx.strokeStyle = stroke;
|
|
25312
|
+
ctx.strokeRect(Math.round(z.x1) + 0.5, Math.round(z.yTop) + 0.5, Math.round(z.x2 - z.x1), Math.round(h));
|
|
25313
|
+
}
|
|
25314
|
+
if (z.midY != null) {
|
|
25315
|
+
const y = Math.round(z.midY) + 0.5;
|
|
25316
|
+
this.stroke(ctx, { lineColor: stroke, lineWidth: 1, lineStyle: "dashed" }, () => {
|
|
25317
|
+
ctx.moveTo(z.x1, y);
|
|
25318
|
+
ctx.lineTo(z.x2, y);
|
|
25319
|
+
});
|
|
25320
|
+
}
|
|
25321
|
+
ctx.restore();
|
|
25322
|
+
if (z.flip) {
|
|
25323
|
+
const f = z.flip;
|
|
25324
|
+
ctx.save();
|
|
25325
|
+
ctx.fillStyle = f.fill;
|
|
25326
|
+
ctx.fillRect(f.x1, z.yTop, f.x2 - f.x1, h);
|
|
25327
|
+
ctx.lineWidth = 1;
|
|
25328
|
+
ctx.strokeStyle = toHex6(f.fill, f.fill);
|
|
25329
|
+
ctx.setLineDash([5, 4]);
|
|
25330
|
+
ctx.strokeRect(Math.round(f.x1) + 0.5, Math.round(z.yTop) + 0.5, Math.round(f.x2 - f.x1), Math.round(h));
|
|
25331
|
+
ctx.restore();
|
|
25332
|
+
}
|
|
25333
|
+
if (z.marker) {
|
|
25334
|
+
ctx.beginPath();
|
|
25335
|
+
ctx.arc(z.marker.x, z.marker.y, 3.5, 0, Math.PI * 2);
|
|
25336
|
+
ctx.fillStyle = stroke;
|
|
25337
|
+
ctx.fill();
|
|
25338
|
+
}
|
|
25339
|
+
}
|
|
25340
|
+
/** Paint a liquidity level: the line to where it stopped, and a cross where it was swept. */
|
|
25341
|
+
paintLiquidity(ctx, d, proj) {
|
|
25342
|
+
const l = d.line(proj);
|
|
25343
|
+
if (!l) return;
|
|
25344
|
+
const y = Math.round(l.y) + 0.5;
|
|
25345
|
+
this.stroke(ctx, d.style, () => {
|
|
25346
|
+
ctx.moveTo(l.x1, y);
|
|
25347
|
+
ctx.lineTo(l.x2, y);
|
|
25348
|
+
});
|
|
25349
|
+
if (l.sweep) {
|
|
25350
|
+
const { x, y: sy } = l.sweep;
|
|
25351
|
+
this.stroke(ctx, { lineColor: d.style.lineColor, lineWidth: 1.75, lineStyle: "solid" }, () => {
|
|
25352
|
+
ctx.moveTo(x - 4, sy - 4);
|
|
25353
|
+
ctx.lineTo(x + 4, sy + 4);
|
|
25354
|
+
ctx.moveTo(x + 4, sy - 4);
|
|
25355
|
+
ctx.lineTo(x - 4, sy + 4);
|
|
25356
|
+
});
|
|
25357
|
+
}
|
|
25358
|
+
}
|
|
24555
25359
|
/** Paint a concentric-ring fib tool (circles / arcs / wedge): each enabled level as an arc of
|
|
24556
25360
|
* radius R0·ratio over the tool's angular sweep, plus any bounding lines + a ratio label per ring. */
|
|
24557
25361
|
paintRadialFib(ctx, d, proj, theme) {
|
|
@@ -25021,15 +25825,15 @@ var DrawingPainter = class {
|
|
|
25021
25825
|
const { profile, maxW, anchorX, grow, yEdges } = L;
|
|
25022
25826
|
if (profile.maxTotal <= 0 || maxW <= 0) return;
|
|
25023
25827
|
for (let k = 0; k < profile.rows.length; k += 1) {
|
|
25024
|
-
const
|
|
25025
|
-
const total =
|
|
25828
|
+
const row2 = profile.rows[k];
|
|
25829
|
+
const total = row2.up + row2.down;
|
|
25026
25830
|
if (total <= 0) continue;
|
|
25027
25831
|
const yA = yEdges[k];
|
|
25028
25832
|
const yB = yEdges[k + 1];
|
|
25029
25833
|
const yTop = Math.min(yA, yB);
|
|
25030
25834
|
const h = Math.max(1, Math.abs(yB - yA) - 1);
|
|
25031
25835
|
const w2 = total / profile.maxTotal * maxW;
|
|
25032
|
-
const upW =
|
|
25836
|
+
const upW = row2.up / total * w2;
|
|
25033
25837
|
const inVa = k >= profile.vaFrom && k <= profile.vaTo;
|
|
25034
25838
|
const upColor = inVa ? s.vaUpColor : s.upColor;
|
|
25035
25839
|
const downColor = inVa ? s.vaDownColor : s.downColor;
|
|
@@ -25494,11 +26298,20 @@ var DrawingPainter = class {
|
|
|
25494
26298
|
ctx.arc(x1, y1, arcR, Math.min(0, end), Math.max(0, end));
|
|
25495
26299
|
ctx.stroke();
|
|
25496
26300
|
}
|
|
25497
|
-
|
|
26301
|
+
const label = `${angleDeg.toFixed(1)}\xB0`;
|
|
26302
|
+
const lx = x1 + arcR + 6;
|
|
26303
|
+
const ly = dy < 0 ? y1 + 14 : y1 - 6;
|
|
25498
26304
|
ctx.font = `11px ${theme.fontFamily}`;
|
|
25499
26305
|
ctx.textAlign = "left";
|
|
25500
26306
|
ctx.textBaseline = "alphabetic";
|
|
25501
|
-
ctx.
|
|
26307
|
+
ctx.save();
|
|
26308
|
+
ctx.lineJoin = "round";
|
|
26309
|
+
ctx.lineWidth = 3;
|
|
26310
|
+
ctx.strokeStyle = theme.background;
|
|
26311
|
+
ctx.strokeText(label, lx, ly);
|
|
26312
|
+
ctx.restore();
|
|
26313
|
+
ctx.fillStyle = color;
|
|
26314
|
+
ctx.fillText(label, lx, ly);
|
|
25502
26315
|
}
|
|
25503
26316
|
/** Paint a fixed-size arrow marker as a filled glyph at its anchor. */
|
|
25504
26317
|
paintArrowMark(ctx, d, proj) {
|
|
@@ -25641,6 +26454,12 @@ function labelLayout(d, proj) {
|
|
|
25641
26454
|
const pts = d.handlePoints(proj);
|
|
25642
26455
|
const lines = text.value.split("\n").length;
|
|
25643
26456
|
const lh = labelLineHeight(namedFontSize(text.size));
|
|
26457
|
+
if (d instanceof ArrowMark) {
|
|
26458
|
+
const b = d.bounds(proj);
|
|
26459
|
+
if (!b) return null;
|
|
26460
|
+
const x = b.x + b.w / 2;
|
|
26461
|
+
return d.type === "arrowmarkdown" ? { x, top: b.y - 4 - lh * lines, align: "center" } : { x, top: b.y + b.h + 4, align: "center" };
|
|
26462
|
+
}
|
|
25644
26463
|
switch (d.type) {
|
|
25645
26464
|
case "text":
|
|
25646
26465
|
return pts[0] ? { x: pts[0][0] + 2, top: pts[0][1] + 2, align: "left" } : null;
|
|
@@ -25655,11 +26474,13 @@ function labelLayout(d, proj) {
|
|
|
25655
26474
|
if (pts.length < 2) return null;
|
|
25656
26475
|
const [x1, y1] = pts[0];
|
|
25657
26476
|
const [x2, y2] = pts[1];
|
|
26477
|
+
const t = text.place === "start" ? 0.12 : text.place === "end" ? 0.88 : 0.5;
|
|
26478
|
+
const below = text.side === "below";
|
|
25658
26479
|
return {
|
|
25659
26480
|
x: 0,
|
|
25660
|
-
top: -6 - lh * lines,
|
|
26481
|
+
top: below ? 6 : -6 - lh * lines,
|
|
25661
26482
|
align: "center",
|
|
25662
|
-
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx:
|
|
26483
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: x1 + (x2 - x1) * t, cy: y1 + (y2 - y1) * t }
|
|
25663
26484
|
};
|
|
25664
26485
|
}
|
|
25665
26486
|
case "box": {
|
|
@@ -25840,8 +26661,9 @@ var DrawingInteraction = class {
|
|
|
25840
26661
|
const tool = this.deps.activeTool();
|
|
25841
26662
|
if (tool) {
|
|
25842
26663
|
const paneId = proj.paneIdAtY(y) ?? "price";
|
|
25843
|
-
const draft = createDrawing(tool, { paneId, anchors: [this.resolve(x, y, paneId, mode)]
|
|
26664
|
+
const draft = createDrawing(tool, { paneId, anchors: [this.resolve(x, y, paneId, mode)] });
|
|
25844
26665
|
if (!draft) return;
|
|
26666
|
+
applyToolDefaults(draft, this.deps.toolDefaults());
|
|
25845
26667
|
this.state = { kind: "placing", draft, need: draft.anchorSchema().max, cursor: null };
|
|
25846
26668
|
if (draft.placementMode() === "click" && draft.anchors.length >= this.state.need) this.finalize();
|
|
25847
26669
|
else this.deps.changed();
|
|
@@ -26040,7 +26862,8 @@ var DrawingInteraction = class {
|
|
|
26040
26862
|
if (this.state.kind !== "placing") return null;
|
|
26041
26863
|
const { draft, cursor } = this.state;
|
|
26042
26864
|
if (!cursor) return draft.anchors.length >= 1 && draft.type === "hline" ? draft : null;
|
|
26043
|
-
|
|
26865
|
+
const { paneId, style, text, props } = draft.serialize();
|
|
26866
|
+
return createDrawing(draft.type, { paneId, anchors: [...draft.anchors, this.placementAnchor(draft, cursor)], style, text, props });
|
|
26044
26867
|
}
|
|
26045
26868
|
/** Map a raw placement cursor to the anchor the drawing actually stores.
|
|
26046
26869
|
* Parallel channel: its 3rd anchor is the parallel-line price measured AT THE
|
|
@@ -26144,7 +26967,7 @@ function applyFree(anchor, pt, free) {
|
|
|
26144
26967
|
if (free === "both" || free === "y") anchor.price = pt.price;
|
|
26145
26968
|
}
|
|
26146
26969
|
|
|
26147
|
-
// src/renderers/native/drawings/
|
|
26970
|
+
// src/renderers/native/drawings/DrawingSettingsSections.ts
|
|
26148
26971
|
var LEVEL_UNITS = [
|
|
26149
26972
|
{ value: "price", label: "Price" },
|
|
26150
26973
|
{ value: "points", label: "Points" }
|
|
@@ -26153,15 +26976,319 @@ var FRVP_ANCHOR = [
|
|
|
26153
26976
|
{ value: "right", label: "Right" },
|
|
26154
26977
|
{ value: "left", label: "Left" }
|
|
26155
26978
|
];
|
|
26156
|
-
|
|
26157
|
-
|
|
26158
|
-
|
|
26159
|
-
|
|
26979
|
+
function buildPositionSection(grid, drawing, actions, theme) {
|
|
26980
|
+
const live = () => {
|
|
26981
|
+
const d = actions.resolve();
|
|
26982
|
+
return d instanceof PositionTool ? d : drawing;
|
|
26983
|
+
};
|
|
26984
|
+
const refreshers = [];
|
|
26985
|
+
const refreshAll = () => refreshers.forEach((f) => f());
|
|
26986
|
+
const fmt = (n) => Math.round(n * 1e8) / 1e8;
|
|
26987
|
+
const numberRow = (label, path, clamp) => {
|
|
26988
|
+
const ctrl = buildFieldControl({
|
|
26989
|
+
kind: "number",
|
|
26990
|
+
value: fmt(live()[path]),
|
|
26991
|
+
min: clamp?.min,
|
|
26992
|
+
max: clamp?.max,
|
|
26993
|
+
step: clamp?.step,
|
|
26994
|
+
integer: clamp?.integer,
|
|
26995
|
+
fill: false,
|
|
26996
|
+
commit: "blur",
|
|
26997
|
+
onChange: (n) => {
|
|
26998
|
+
actions.patch({ [path]: n });
|
|
26999
|
+
refreshAll();
|
|
27000
|
+
}
|
|
27001
|
+
});
|
|
27002
|
+
refreshers.push(() => {
|
|
27003
|
+
if (document.activeElement !== ctrl.el.querySelector("input")) ctrl.setValue?.(fmt(live()[path]));
|
|
27004
|
+
});
|
|
27005
|
+
grid.appendChild(fieldRow({ label, control: ctrl.el }));
|
|
27006
|
+
};
|
|
27007
|
+
const levelRow = (label, path, level) => {
|
|
27008
|
+
let mode = "price";
|
|
27009
|
+
const display = () => fmt(live().levelDisplayValue(level, mode));
|
|
27010
|
+
const ni = buildFieldControl({
|
|
27011
|
+
kind: "number",
|
|
27012
|
+
value: display(),
|
|
27013
|
+
fill: false,
|
|
27014
|
+
commit: "blur",
|
|
27015
|
+
onChange: (n) => {
|
|
27016
|
+
actions.patch({ [path]: live().levelPriceFromDisplay(level, mode, n) });
|
|
27017
|
+
refreshAll();
|
|
27018
|
+
}
|
|
27019
|
+
});
|
|
27020
|
+
const sel = buildFieldControl({
|
|
27021
|
+
kind: "select",
|
|
27022
|
+
options: LEVEL_UNITS,
|
|
27023
|
+
value: mode,
|
|
27024
|
+
fill: false,
|
|
27025
|
+
theme,
|
|
27026
|
+
onChange: (v) => {
|
|
27027
|
+
mode = v;
|
|
27028
|
+
ni.setValue?.(display());
|
|
27029
|
+
}
|
|
27030
|
+
});
|
|
27031
|
+
refreshers.push(() => {
|
|
27032
|
+
if (document.activeElement !== ni.el.querySelector("input")) ni.setValue?.(display());
|
|
27033
|
+
});
|
|
27034
|
+
grid.appendChild(fieldRow({ label, control: [ni.el, sel.el] }));
|
|
27035
|
+
};
|
|
27036
|
+
grid.appendChild(fieldSection("Account", { variant: "inputs", first: true }));
|
|
27037
|
+
numberRow("Risk %", "riskPercent", { min: 0, max: 100, step: 0.1 });
|
|
27038
|
+
numberRow("Account balance", "accountBalance", { min: 0, max: 1e12, step: 1, integer: true });
|
|
27039
|
+
numberRow("Position size", "quantity");
|
|
27040
|
+
grid.appendChild(fieldSection("Levels", { variant: "inputs" }));
|
|
27041
|
+
const dir = buildFieldControl({
|
|
27042
|
+
kind: "select",
|
|
27043
|
+
options: DIRECTION_OPTIONS,
|
|
27044
|
+
value: live().direction,
|
|
27045
|
+
fill: false,
|
|
27046
|
+
theme,
|
|
27047
|
+
onChange: (v) => {
|
|
27048
|
+
actions.patch({ direction: v });
|
|
27049
|
+
refreshAll();
|
|
27050
|
+
}
|
|
27051
|
+
});
|
|
27052
|
+
refreshers.push(() => dir.setValue?.(live().direction));
|
|
27053
|
+
grid.appendChild(fieldRow({ label: "Direction", control: dir.el }));
|
|
27054
|
+
numberRow("Entry price", "entryPrice");
|
|
27055
|
+
levelRow("Stop", "stopPrice", "stop");
|
|
27056
|
+
levelRow("Target", "targetPrice", "target");
|
|
27057
|
+
grid.appendChild(fieldSection("Display", { variant: "inputs" }));
|
|
27058
|
+
const toggles = [
|
|
27059
|
+
["Show text", "showText"],
|
|
27060
|
+
["Show direction & ratio", "showHeader"],
|
|
27061
|
+
["Show loss & size", "showLossSize"],
|
|
27062
|
+
["Show target label", "showTargetLabel"],
|
|
27063
|
+
["Show stop label", "showStopLabel"],
|
|
27064
|
+
["Show level prices", "showPrices"]
|
|
27065
|
+
];
|
|
27066
|
+
for (const [label, path] of toggles) {
|
|
27067
|
+
grid.appendChild(fieldRow({
|
|
27068
|
+
label,
|
|
27069
|
+
bool: true,
|
|
27070
|
+
toggle: {
|
|
27071
|
+
checked: live()[path],
|
|
27072
|
+
onChange: (v) => {
|
|
27073
|
+
actions.patch({ [path]: v });
|
|
27074
|
+
refreshAll();
|
|
27075
|
+
},
|
|
27076
|
+
get: () => live()[path]
|
|
27077
|
+
}
|
|
27078
|
+
}));
|
|
27079
|
+
}
|
|
27080
|
+
const summary = document.createElement("div");
|
|
27081
|
+
summary.className = "vela-field-span";
|
|
27082
|
+
summary.style.cssText = "opacity:0.7;font-size:11px;line-height:1.4;border-top:1px solid var(--vela-border);padding-top:6px;font-variant-numeric:tabular-nums;";
|
|
27083
|
+
refreshers.push(() => {
|
|
27084
|
+
const d = live();
|
|
27085
|
+
summary.textContent = `${d.headerLabel()} \u2014 ${d.lossSizeLabel()}`;
|
|
27086
|
+
});
|
|
27087
|
+
grid.appendChild(summary);
|
|
27088
|
+
refreshAll();
|
|
27089
|
+
}
|
|
27090
|
+
function buildProfileSection(grid, drawing, actions, theme) {
|
|
27091
|
+
const styleOf = () => {
|
|
27092
|
+
const d = actions.resolve();
|
|
27093
|
+
return d instanceof FixedRangeVolumeProfile ? d.frvp : drawing.frvp;
|
|
27094
|
+
};
|
|
27095
|
+
const s = styleOf();
|
|
27096
|
+
const numberRow = (label, path, min, max, step, integer = true) => {
|
|
27097
|
+
grid.appendChild(fieldRow({
|
|
27098
|
+
label,
|
|
27099
|
+
control: buildFieldControl({
|
|
27100
|
+
kind: "number",
|
|
27101
|
+
value: s[path],
|
|
27102
|
+
min,
|
|
27103
|
+
max,
|
|
27104
|
+
step,
|
|
27105
|
+
integer,
|
|
27106
|
+
fill: false,
|
|
27107
|
+
commit: "blur",
|
|
27108
|
+
onChange: (n) => actions.patch({ [`frvp.${path}`]: n })
|
|
27109
|
+
}).el
|
|
27110
|
+
}));
|
|
27111
|
+
};
|
|
27112
|
+
numberRow("Rows", "rows", 1, 500, 1);
|
|
27113
|
+
numberRow("Value Area", "valueAreaPct", 0, 100, 1);
|
|
27114
|
+
numberRow("Width %", "widthPct", 0, 100, 1);
|
|
27115
|
+
grid.appendChild(fieldRow({
|
|
27116
|
+
label: "Anchor",
|
|
27117
|
+
control: buildFieldControl({
|
|
27118
|
+
kind: "select",
|
|
27119
|
+
options: FRVP_ANCHOR,
|
|
27120
|
+
value: s.anchor,
|
|
27121
|
+
fill: false,
|
|
27122
|
+
theme,
|
|
27123
|
+
onChange: (v) => actions.patch({ "frvp.anchor": v })
|
|
27124
|
+
}).el
|
|
27125
|
+
}));
|
|
27126
|
+
const colorRow = (label, path) => {
|
|
27127
|
+
grid.appendChild(fieldRow({
|
|
27128
|
+
label,
|
|
27129
|
+
fit: true,
|
|
27130
|
+
control: buildFieldControl({
|
|
27131
|
+
kind: "color",
|
|
27132
|
+
theme,
|
|
27133
|
+
get: () => styleOf()[path],
|
|
27134
|
+
onChange: (v) => actions.patch({ [`frvp.${path}`]: v })
|
|
27135
|
+
}).el
|
|
27136
|
+
}));
|
|
27137
|
+
};
|
|
27138
|
+
colorRow("Up Volume", "upColor");
|
|
27139
|
+
colorRow("Down Volume", "downColor");
|
|
27140
|
+
colorRow("Value Area Up", "vaUpColor");
|
|
27141
|
+
colorRow("Value Area Down", "vaDownColor");
|
|
27142
|
+
const styles = LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, label: o.label }));
|
|
27143
|
+
const levelRow = (label, showPath, colorPath, stylePath) => {
|
|
27144
|
+
const row2 = document.createElement("div");
|
|
27145
|
+
row2.className = "vela-field-span";
|
|
27146
|
+
row2.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
27147
|
+
const sw = buildFieldControl({
|
|
27148
|
+
kind: "switch",
|
|
27149
|
+
checked: Boolean(s[showPath]),
|
|
27150
|
+
onChange: (v) => actions.patch({ [`frvp.${showPath}`]: v })
|
|
27151
|
+
});
|
|
27152
|
+
const lbl = document.createElement("span");
|
|
27153
|
+
lbl.className = "vela-field-label";
|
|
27154
|
+
lbl.style.flex = "1";
|
|
27155
|
+
lbl.textContent = label;
|
|
27156
|
+
let cur = s[colorPath] ?? contrastColor(theme.background);
|
|
27157
|
+
const col = buildFieldControl({
|
|
27158
|
+
kind: "color",
|
|
27159
|
+
theme,
|
|
27160
|
+
get: () => cur,
|
|
27161
|
+
onChange: (v) => {
|
|
27162
|
+
cur = v;
|
|
27163
|
+
actions.patch({ [`frvp.${colorPath}`]: v });
|
|
27164
|
+
}
|
|
27165
|
+
});
|
|
27166
|
+
const style = buildFieldControl({
|
|
27167
|
+
kind: "select",
|
|
27168
|
+
options: styles,
|
|
27169
|
+
value: s[stylePath],
|
|
27170
|
+
fill: false,
|
|
27171
|
+
theme,
|
|
27172
|
+
onChange: (v) => actions.patch({ [`frvp.${stylePath}`]: v })
|
|
27173
|
+
});
|
|
27174
|
+
row2.append(sw.el, lbl, col.el, style.el);
|
|
27175
|
+
grid.appendChild(row2);
|
|
27176
|
+
};
|
|
27177
|
+
levelRow("VAH", "showVah", "vahColor", "vahStyle");
|
|
27178
|
+
levelRow("VAL", "showVal", "valColor", "valStyle");
|
|
27179
|
+
levelRow("POC", "showPoc", "pocColor", "pocStyle");
|
|
27180
|
+
levelRow("Developing POC", "showDevelopingPoc", "developingPocColor", "developingPocStyle");
|
|
27181
|
+
levelRow("Developing VA", "showDevelopingVa", "developingVaColor", "developingVaStyle");
|
|
27182
|
+
}
|
|
27183
|
+
|
|
27184
|
+
// src/renderers/native/drawings/DrawingSettingsPanel.ts
|
|
27185
|
+
var LINE_LABEL_TYPES = /* @__PURE__ */ new Set(["trendline", "ray", "extendedline", "infoline", "trendangle"]);
|
|
27186
|
+
var NO_POINTS = /* @__PURE__ */ new Set(["freehand", "highlighter", "vline", "position"]);
|
|
27187
|
+
var MAX_POINTS = 8;
|
|
27188
|
+
var POSITION_PATHS = /* @__PURE__ */ new Set(["riskPercent", "accountBalance", "quantity", "direction", "entryPrice", "stopPrice", "targetPrice", "showText", "showHeader", "showLossSize", "showTargetLabel", "showStopLabel", "showPrices"]);
|
|
27189
|
+
var LEVEL_PATHS = /* @__PURE__ */ new Set(["showRatios"]);
|
|
27190
|
+
var INTRADAY = ["s", "1", "5", "15", "60", "240"];
|
|
27191
|
+
var HIGHER = ["60", "240", "D", "W", "M"];
|
|
27192
|
+
var openFolds = /* @__PURE__ */ new Map();
|
|
27193
|
+
var STYLE_ID3 = "vela-dsp-styles";
|
|
27194
|
+
var STYLE_REV = "1";
|
|
27195
|
+
var CHEVRON = '<svg viewBox="0 0 12 12" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m4.5 2.5 3.5 3.5-3.5 3.5"/></svg>';
|
|
27196
|
+
var CHEVRON_DOWN = '<svg viewBox="0 0 12 12" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m2.5 4.5 3.5 3.5 3.5-3.5"/></svg>';
|
|
27197
|
+
var CROSS = '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="m4.5 4.5 7 7M11.5 4.5l-7 7"/></svg>';
|
|
27198
|
+
var EXTEND_ICONS = {
|
|
27199
|
+
none: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 7h8"/><circle cx="5" cy="7" r="1.5" fill="currentColor"/><circle cx="13" cy="7" r="1.5" fill="currentColor"/></svg>',
|
|
27200
|
+
left: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M1 7h12"/><path d="m3.5 4.5-2.5 2.5 2.5 2.5"/><circle cx="13" cy="7" r="1.5" fill="currentColor"/></svg>',
|
|
27201
|
+
right: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 7h12"/><path d="m14.5 4.5 2.5 2.5-2.5 2.5"/><circle cx="5" cy="7" r="1.5" fill="currentColor"/></svg>',
|
|
27202
|
+
both: '<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M1 7h16"/><path d="m3.5 4.5-2.5 2.5 2.5 2.5M14.5 4.5l2.5 2.5-2.5 2.5"/></svg>'
|
|
26160
27203
|
};
|
|
26161
|
-
var
|
|
26162
|
-
|
|
27204
|
+
var widthIcon = (w) => `<svg viewBox="0 0 18 14" width="18" height="14"><rect x="2" y="${7 - w / 2}" width="14" height="${w}" rx="${w / 2}" fill="currentColor"/></svg>`;
|
|
27205
|
+
var dashIcon = (s) => `<svg viewBox="0 0 18 14" width="18" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-dasharray="${s === "dashed" ? "4 3" : s === "dotted" ? "0.1 3.5" : "0"}"><path d="M2 7h14"/></svg>`;
|
|
27206
|
+
function ensureStyles3() {
|
|
27207
|
+
if (typeof document === "undefined") return;
|
|
27208
|
+
const existing = document.getElementById(STYLE_ID3);
|
|
27209
|
+
if (existing?.dataset.rev === STYLE_REV) return;
|
|
27210
|
+
const s = existing ?? document.createElement("style");
|
|
27211
|
+
s.id = STYLE_ID3;
|
|
27212
|
+
s.dataset.rev = STYLE_REV;
|
|
27213
|
+
s.textContent = `
|
|
27214
|
+
.vela-dsp-pass { pointer-events: none !important; }
|
|
27215
|
+
.vela-dialog.vela-dsp { pointer-events: auto; width: 360px; min-width: 0; max-width: calc(100% - 24px); max-height: calc(100% - 24px); font-size: 13px; }
|
|
27216
|
+
.vela-dsp .vela-dialog-header { padding: 8px 8px 8px 16px; gap: 6px; }
|
|
27217
|
+
.vela-dsp .vela-dialog-title { font-size: 15px; line-height: 22px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27218
|
+
.vela-dsp .vela-dialog-footer { padding: 10px 12px; display: flex; justify-content: flex-end; gap: 8px; border-top: 1px solid var(--vela-border); }
|
|
27219
|
+
.vela-dsp .vela-dialog-btn { height: 30px; font-size: 13px; }
|
|
27220
|
+
.vela-dsp-scroll { overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; min-height: 0; }
|
|
27221
|
+
.vela-dsp-block { padding: 12px 16px; display: flex; flex-direction: column; gap: 8px; }
|
|
27222
|
+
.vela-dsp-block + .vela-dsp-block, .vela-dsp-fold { border-top: 1px solid var(--vela-border); }
|
|
27223
|
+
.vela-dsp-h { font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--vela-fg-muted); }
|
|
27224
|
+
.vela-dsp-row { display: flex; align-items: center; gap: 8px; min-height: 30px; }
|
|
27225
|
+
.vela-dsp-row[hidden] { display: none; }
|
|
27226
|
+
.vela-dsp-lab { flex: 0 0 96px; color: var(--vela-fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
27227
|
+
.vela-dsp-row > .vela-dsp-lab + * { min-width: 0; }
|
|
27228
|
+
.vela-dsp-spacer { flex: 1; }
|
|
27229
|
+
.vela-dsp-fold-h { all: unset; box-sizing: border-box; width: 100%; display: flex; align-items: center; gap: 8px; padding: 11px 16px; cursor: pointer; }
|
|
27230
|
+
.vela-dsp-fold-h:hover { background: var(--vela-hover); }
|
|
27231
|
+
.vela-dsp-fold-h:focus-visible { outline: 2px solid var(--vela-selected-bg); outline-offset: -2px; }
|
|
27232
|
+
.vela-dsp-fold-t { font-weight: 600; color: var(--vela-fg-bright); white-space: nowrap; }
|
|
27233
|
+
.vela-dsp-fold-s { flex: 1; min-width: 0; text-align: right; color: var(--vela-fg-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-variant-numeric: tabular-nums; }
|
|
27234
|
+
.vela-dsp-chev { display: flex; color: var(--vela-fg-muted); transition: transform var(--vela-dur-fast) ease; }
|
|
27235
|
+
.vela-dsp-fold[data-open] .vela-dsp-chev { transform: rotate(90deg); }
|
|
27236
|
+
.vela-dsp-fold-b { padding: 0 16px 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
27237
|
+
.vela-dsp-fold:not([data-open]) .vela-dsp-fold-b { display: none; }
|
|
27238
|
+
.vela-dsp-seg { display: inline-flex; flex: none; border: 1px solid var(--vela-border-strong); border-radius: 6px; overflow: hidden; }
|
|
27239
|
+
.vela-dsp-seg[data-full] { display: flex; flex: 1; }
|
|
27240
|
+
.vela-dsp-seg > button { all: unset; box-sizing: border-box; cursor: pointer; height: 28px; min-width: 32px; padding: 0 8px; display: flex; align-items: center; justify-content: center; color: var(--vela-fg-muted); white-space: nowrap; }
|
|
27241
|
+
.vela-dsp-seg[data-full] > button { flex: 1 1 0; min-width: 0; }
|
|
27242
|
+
.vela-dsp-seg > button + button { border-left: 1px solid var(--vela-border); }
|
|
27243
|
+
.vela-dsp-seg > button:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
27244
|
+
.vela-dsp-seg > button[aria-pressed='true'] { background: var(--vela-active); color: var(--vela-fg-bright); }
|
|
27245
|
+
.vela-dsp-seg > button:focus-visible { outline: 2px solid var(--vela-selected-bg); outline-offset: -2px; }
|
|
27246
|
+
.vela-dsp-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
27247
|
+
.vela-dsp-chip { all: unset; box-sizing: border-box; cursor: pointer; height: 26px; padding: 0 10px; border-radius: 13px; border: 1px solid var(--vela-border-strong); color: var(--vela-fg-muted); display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
|
|
27248
|
+
.vela-dsp-chip:hover { color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
27249
|
+
.vela-dsp-chip[aria-pressed='true'] { background: var(--vela-active); border-color: transparent; color: var(--vela-fg-bright); }
|
|
27250
|
+
.vela-dsp-chip:focus-visible { outline: 2px solid var(--vela-selected-bg); outline-offset: 1px; }
|
|
27251
|
+
.vela-dsp-note { font-size: 12px; color: var(--vela-fg-muted); line-height: 1.45; }
|
|
27252
|
+
.vela-dsp-pt { display: grid; grid-template-columns: 34px minmax(0, 120px) minmax(0, 1fr); align-items: center; gap: 8px; min-height: 30px; }
|
|
27253
|
+
.vela-dsp-pt-k { font-weight: 600; color: var(--vela-fg-muted); }
|
|
27254
|
+
.vela-dsp-pt-t { color: var(--vela-fg-muted); font-size: 12px; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
27255
|
+
.vela-dsp-tf[hidden], .vela-dsp-note[hidden] { display: none; }
|
|
27256
|
+
.vela-dsp-tf { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; }
|
|
27257
|
+
.vela-dsp-tf > button { all: unset; box-sizing: border-box; cursor: pointer; position: relative; height: 28px; border-radius: 6px; border: 1px solid var(--vela-border-strong); color: var(--vela-fg-muted); display: flex; align-items: center; justify-content: center; font-variant-numeric: tabular-nums; }
|
|
27258
|
+
.vela-dsp-tf > button:hover { color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
27259
|
+
.vela-dsp-tf > button[aria-pressed='true'] { background: var(--vela-active); border-color: transparent; color: var(--vela-fg-bright); }
|
|
27260
|
+
.vela-dsp-tf > button[data-here]::after { content: ''; position: absolute; top: 3px; right: 3px; width: 5px; height: 5px; border-radius: 50%; background: var(--vela-selected-bg); }
|
|
27261
|
+
.vela-dsp-grid { padding: 0; }
|
|
27262
|
+
.vela-dsp-lv { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 14px; }
|
|
27263
|
+
.vela-dsp-lv-c { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
27264
|
+
.vela-dsp-lv-c .vela-num:not([data-fill])[data-compact] { width: 76px; }
|
|
27265
|
+
.vela-dsp-lv-c .vela-num input { height: 28px; }
|
|
27266
|
+
.vela-dsp-lv-c[data-off] .vela-num input { color: var(--vela-fg-muted); }
|
|
27267
|
+
.vela-dsp-style { all: unset; box-sizing: border-box; cursor: pointer; flex: none; height: 28px; max-width: 150px; padding: 0 6px 0 10px; border-radius: 6px; border: 1px solid var(--vela-border-strong); display: inline-flex; align-items: center; gap: 4px; color: var(--vela-fg); font-size: 12px; }
|
|
27268
|
+
.vela-dsp-style > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27269
|
+
.vela-dsp-style:hover, .vela-dsp-style[aria-expanded='true'] { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
27270
|
+
.vela-dsp-menu { min-width: 220px; max-height: 320px; overflow-y: auto; padding: 4px; display: flex; flex-direction: column; gap: 1px; }
|
|
27271
|
+
.vela-dsp-mh { padding: 6px 8px 4px; font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--vela-fg-muted); }
|
|
27272
|
+
.vela-dsp-mi { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; gap: 8px; padding: 0 4px 0 8px; height: 30px; border-radius: 5px; color: inherit; }
|
|
27273
|
+
.vela-dsp-mi:hover, .vela-dsp-mi:focus-visible { background: var(--vela-hover-strong); }
|
|
27274
|
+
.vela-dsp-mi[data-current] { color: var(--vela-fg-bright); font-weight: 600; }
|
|
27275
|
+
.vela-dsp-mi > .vela-dsp-mi-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27276
|
+
.vela-dsp-mi-dot { flex: none; width: 14px; height: 14px; border-radius: 4px; border: 1px solid var(--vela-border-strong); }
|
|
27277
|
+
.vela-dsp-mi-x { all: unset; cursor: pointer; flex: none; width: 24px; height: 24px; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: var(--vela-fg-muted); }
|
|
27278
|
+
.vela-dsp-mi-x:hover { background: var(--vela-hover); color: var(--vela-danger); }
|
|
27279
|
+
.vela-dsp-msep { height: 1px; background: var(--vela-border); margin: 4px 0; }
|
|
27280
|
+
.vela-dsp-mname { display: flex; gap: 6px; padding: 4px; }
|
|
27281
|
+
.vela-dsp-mname > input { flex: 1; min-width: 0; height: 28px; box-sizing: border-box; padding: 0 8px; border-radius: 6px; border: 1px solid var(--vela-border-strong); background: transparent; color: inherit; font: inherit; outline: none; }
|
|
27282
|
+
.vela-dsp-mname > input:focus { border-color: var(--vela-selected-bg); }`;
|
|
27283
|
+
if (!existing) document.head.appendChild(s);
|
|
27284
|
+
}
|
|
27285
|
+
var DrawingSettingsPanel = class {
|
|
27286
|
+
constructor(host, theme, env) {
|
|
26163
27287
|
this.host = host;
|
|
27288
|
+
this.env = env;
|
|
26164
27289
|
this.ui = null;
|
|
27290
|
+
this.menu = null;
|
|
27291
|
+
this.refresh = null;
|
|
26165
27292
|
this.theme = theme;
|
|
26166
27293
|
}
|
|
26167
27294
|
setTheme(theme) {
|
|
@@ -26171,69 +27298,97 @@ var DrawingSettingsDialog = class {
|
|
|
26171
27298
|
return this.ui != null;
|
|
26172
27299
|
}
|
|
26173
27300
|
contains(node) {
|
|
26174
|
-
return this.ui?.contains(node) === true;
|
|
27301
|
+
return node != null && this.ui?.contains(node) === true;
|
|
26175
27302
|
}
|
|
26176
|
-
|
|
27303
|
+
/** Re-read the drawing after a change made outside the panel (a drag, an undo). */
|
|
27304
|
+
sync() {
|
|
27305
|
+
this.refresh?.();
|
|
27306
|
+
}
|
|
27307
|
+
open(drawing, actions, anchor) {
|
|
26177
27308
|
this.close();
|
|
26178
|
-
|
|
26179
|
-
const
|
|
26180
|
-
const
|
|
26181
|
-
|
|
26182
|
-
|
|
26183
|
-
|
|
26184
|
-
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
27309
|
+
ensureStyles3();
|
|
27310
|
+
const first = actions.resolve() ?? drawing;
|
|
27311
|
+
const snapshot = clonePlain(first.serialize());
|
|
27312
|
+
const live = () => actions.resolve() ?? first;
|
|
27313
|
+
const meta = getDrawingType(first.type);
|
|
27314
|
+
const scroll = document.createElement("div");
|
|
27315
|
+
scroll.className = "vela-dsp-scroll";
|
|
27316
|
+
let watchers = [];
|
|
27317
|
+
const runWatchers = () => {
|
|
27318
|
+
for (const fn of watchers) fn();
|
|
27319
|
+
};
|
|
27320
|
+
const build = {
|
|
27321
|
+
live,
|
|
27322
|
+
edit: (p) => {
|
|
27323
|
+
actions.patch(p);
|
|
27324
|
+
runWatchers();
|
|
27325
|
+
},
|
|
27326
|
+
watch: (fn) => {
|
|
27327
|
+
watchers.push(fn);
|
|
27328
|
+
fn();
|
|
27329
|
+
},
|
|
27330
|
+
rebuild: () => {
|
|
27331
|
+
const top = scroll.scrollTop;
|
|
27332
|
+
watchers = [];
|
|
27333
|
+
scroll.replaceChildren(...this.sections(build));
|
|
27334
|
+
scroll.scrollTop = top;
|
|
27335
|
+
styleBtn.refresh();
|
|
27336
|
+
},
|
|
27337
|
+
actions
|
|
27338
|
+
};
|
|
27339
|
+
const cancel = () => {
|
|
27340
|
+
actions.preview?.(null);
|
|
27341
|
+
actions.restore?.(snapshot);
|
|
27342
|
+
this.close();
|
|
27343
|
+
};
|
|
26189
27344
|
const ui = new Dialog({
|
|
26190
27345
|
host: this.host,
|
|
26191
|
-
title:
|
|
26192
|
-
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
26193
|
-
// whole body, killing the chart and the body-portaled popovers.
|
|
27346
|
+
title: meta?.label ?? "Settings",
|
|
26194
27347
|
modal: false,
|
|
26195
27348
|
contained: true,
|
|
26196
27349
|
align: "center",
|
|
26197
27350
|
draggable: true,
|
|
26198
27351
|
flush: true,
|
|
26199
|
-
className: "vela-
|
|
27352
|
+
className: "vela-dsp",
|
|
26200
27353
|
closeOnEscape: false,
|
|
26201
|
-
footer: (foot) =>
|
|
26202
|
-
foot.append(
|
|
26203
|
-
btn("Cancel", false, () => {
|
|
26204
|
-
actions.restore?.(snapshot);
|
|
26205
|
-
this.close();
|
|
26206
|
-
}),
|
|
26207
|
-
btn("Ok", true, () => this.close())
|
|
26208
|
-
);
|
|
26209
|
-
},
|
|
27354
|
+
footer: (foot) => foot.append(button("Cancel", false, cancel), button("Ok", true, () => this.close())),
|
|
26210
27355
|
onOpenChange: (open2) => {
|
|
26211
27356
|
if (!open2) this.close();
|
|
26212
27357
|
}
|
|
26213
27358
|
});
|
|
26214
27359
|
applyChromeTokens(ui.panel, this.theme);
|
|
26215
|
-
ui.
|
|
26216
|
-
|
|
26217
|
-
|
|
26218
|
-
|
|
26219
|
-
|
|
26220
|
-
|
|
26221
|
-
|
|
26222
|
-
|
|
26223
|
-
|
|
26224
|
-
ui.backdrop.addEventListener("pointerdown", onBackdropDown);
|
|
26225
|
-
ui.positioner.addEventListener("pointerdown", onBackdropDown);
|
|
27360
|
+
ui.backdrop.classList.add("vela-dsp-pass");
|
|
27361
|
+
ui.positioner.classList.add("vela-dsp-pass");
|
|
27362
|
+
const hostW = this.host.clientWidth;
|
|
27363
|
+
const leftHalf = anchor != null && hostW > 0 && anchor.x + anchor.w / 2 < hostW / 2;
|
|
27364
|
+
ui.positioner.style.justifyContent = leftHalf ? "flex-end" : "flex-start";
|
|
27365
|
+
ui.positioner.style.padding = "0 12px";
|
|
27366
|
+
const styleBtn = this.styleMenuButton(build);
|
|
27367
|
+
ui.titleEl.after(styleBtn.el);
|
|
27368
|
+
ui.body.appendChild(scroll);
|
|
26226
27369
|
ui.panel.addEventListener("keydown", (e) => {
|
|
26227
27370
|
if (e.key === "Escape") {
|
|
26228
27371
|
e.preventDefault();
|
|
26229
|
-
|
|
26230
|
-
|
|
27372
|
+
if (this.menu) this.menu.hide();
|
|
27373
|
+
else cancel();
|
|
26231
27374
|
}
|
|
26232
27375
|
});
|
|
26233
27376
|
this.ui = ui;
|
|
27377
|
+
this.refresh = () => {
|
|
27378
|
+
if (!actions.resolve()) {
|
|
27379
|
+
this.close();
|
|
27380
|
+
return;
|
|
27381
|
+
}
|
|
27382
|
+
runWatchers();
|
|
27383
|
+
styleBtn.refresh();
|
|
27384
|
+
};
|
|
27385
|
+
build.rebuild();
|
|
26234
27386
|
ui.show();
|
|
26235
27387
|
}
|
|
26236
27388
|
close() {
|
|
27389
|
+
this.menu?.hide();
|
|
27390
|
+
this.menu = null;
|
|
27391
|
+
this.refresh = null;
|
|
26237
27392
|
const ui = this.ui;
|
|
26238
27393
|
this.ui = null;
|
|
26239
27394
|
ui?.destroy();
|
|
@@ -26241,292 +27396,771 @@ var DrawingSettingsDialog = class {
|
|
|
26241
27396
|
destroy() {
|
|
26242
27397
|
this.close();
|
|
26243
27398
|
}
|
|
26244
|
-
|
|
26245
|
-
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
const
|
|
26250
|
-
const
|
|
26251
|
-
|
|
26252
|
-
const
|
|
26253
|
-
|
|
26254
|
-
|
|
26255
|
-
|
|
26256
|
-
|
|
26257
|
-
|
|
26258
|
-
|
|
26259
|
-
|
|
26260
|
-
|
|
26261
|
-
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
|
|
26267
|
-
|
|
26268
|
-
|
|
26269
|
-
|
|
26270
|
-
|
|
26271
|
-
|
|
26272
|
-
|
|
26273
|
-
|
|
26274
|
-
|
|
26275
|
-
|
|
26276
|
-
|
|
26277
|
-
|
|
26278
|
-
|
|
26279
|
-
|
|
26280
|
-
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
}
|
|
26284
|
-
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
26290
|
-
|
|
26291
|
-
|
|
26292
|
-
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
|
|
26296
|
-
|
|
26297
|
-
|
|
26298
|
-
|
|
26299
|
-
|
|
26300
|
-
}
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26305
|
-
|
|
26306
|
-
|
|
26307
|
-
|
|
26308
|
-
|
|
26309
|
-
|
|
26310
|
-
|
|
26311
|
-
|
|
26312
|
-
|
|
26313
|
-
|
|
26314
|
-
|
|
27399
|
+
// ── sections ──
|
|
27400
|
+
sections(b) {
|
|
27401
|
+
const d = b.live();
|
|
27402
|
+
const schema = d.schema();
|
|
27403
|
+
const fields = schema.fields;
|
|
27404
|
+
const has = (path) => fields.some((f) => f.path === path);
|
|
27405
|
+
const out = [];
|
|
27406
|
+
if (schema.textIsContent && has("text.value")) out.push(this.textContentBlock(b, has));
|
|
27407
|
+
const style = this.styleBlock(b, fields, has);
|
|
27408
|
+
if (style) out.push(style);
|
|
27409
|
+
if (d.editableLevels()) out.push(this.levelsBlock(b));
|
|
27410
|
+
if (d instanceof PositionTool) out.push(this.richBlock("Position", (grid) => buildPositionSection(grid, d, b.actions, this.theme)));
|
|
27411
|
+
if (d instanceof FixedRangeVolumeProfile) out.push(this.richBlock("Profile", (grid) => buildProfileSection(grid, d, b.actions, this.theme)));
|
|
27412
|
+
const own = this.optionsBlock(b, fields, d);
|
|
27413
|
+
if (own) out.push(own);
|
|
27414
|
+
if (!schema.textIsContent && fields.some((f) => f.path.startsWith("text."))) out.push(this.textFold(b, has));
|
|
27415
|
+
const points = this.pointsFold(b);
|
|
27416
|
+
if (points) out.push(points);
|
|
27417
|
+
out.push(this.showOnFold(b));
|
|
27418
|
+
return out;
|
|
27419
|
+
}
|
|
27420
|
+
/** Line, width, dash, ends and fill — the look every stroked tool shares. */
|
|
27421
|
+
styleBlock(b, fields, has) {
|
|
27422
|
+
const rows = [];
|
|
27423
|
+
if (has("style.lineColor") || has("style.lineWidth")) {
|
|
27424
|
+
const ctrls = [];
|
|
27425
|
+
if (has("style.lineColor")) ctrls.push(this.swatch(b, "Line color", () => b.live().style.lineColor || DEFAULT_DRAWING_COLOR, "style.lineColor"));
|
|
27426
|
+
const wf = fields.find((f) => f.path === "style.lineWidth");
|
|
27427
|
+
if (wf) {
|
|
27428
|
+
if ((wf.min ?? 1) > 1) ctrls.push(this.number(b, "Line width", () => b.live().style.lineWidth, "style.lineWidth", wf));
|
|
27429
|
+
else ctrls.push(this.seg(b, "Line width", [1, 2, 3, 4].map((w) => ({ value: w, html: widthIcon(w), title: `${w}px` })), () => b.live().style.lineWidth, (v) => b.edit({ "style.lineWidth": v })));
|
|
27430
|
+
}
|
|
27431
|
+
rows.push(row("Line", ...ctrls));
|
|
27432
|
+
}
|
|
27433
|
+
if (has("style.lineStyle")) {
|
|
27434
|
+
rows.push(row("Style", this.seg(b, "Line style", LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, html: dashIcon(o.value), title: o.label })), () => b.live().style.lineStyle, (v) => b.edit({ "style.lineStyle": v }))));
|
|
27435
|
+
}
|
|
27436
|
+
if (has("style.extendLeft") && has("style.extendRight")) {
|
|
27437
|
+
const opts = [
|
|
27438
|
+
{ value: "none", html: EXTEND_ICONS.none, title: "Don't extend" },
|
|
27439
|
+
{ value: "left", html: EXTEND_ICONS.left, title: "Extend left" },
|
|
27440
|
+
{ value: "right", html: EXTEND_ICONS.right, title: "Extend right" },
|
|
27441
|
+
{ value: "both", html: EXTEND_ICONS.both, title: "Extend both ways" }
|
|
27442
|
+
];
|
|
27443
|
+
const read = () => {
|
|
27444
|
+
const { extendLeft: l, extendRight: r } = b.live().style;
|
|
27445
|
+
return l && r ? "both" : l ? "left" : r ? "right" : "none";
|
|
27446
|
+
};
|
|
27447
|
+
rows.push(row("Extend", this.seg(b, "Extend", opts, read, (v) => b.edit({ "style.extendLeft": v === "left" || v === "both", "style.extendRight": v === "right" || v === "both" }))));
|
|
27448
|
+
}
|
|
27449
|
+
const arrows = [];
|
|
27450
|
+
if (has("style.arrowLeft")) arrows.push(this.chip(b, "Start arrow", () => !!b.live().style.arrowLeft, (on) => b.edit({ "style.arrowLeft": on })));
|
|
27451
|
+
if (has("style.arrowRight")) arrows.push(this.chip(b, has("style.arrowLeft") ? "End arrow" : "Arrow", () => !!b.live().style.arrowRight, (on) => b.edit({ "style.arrowRight": on })));
|
|
27452
|
+
if (arrows.length) rows.push(row("Ends", chips(...arrows)));
|
|
27453
|
+
if (has("style.fillColor")) {
|
|
27454
|
+
rows.push(row("Fill", this.swatch(b, "Fill", () => effectiveFillColor(b.live(), this.theme) ?? b.live().style.fillColor ?? DEFAULT_DRAWING_COLOR, "style.fillColor")));
|
|
27455
|
+
}
|
|
27456
|
+
if (!has("text.value") && has("text.color")) rows.push(row("Text", ...this.textStyling(b, has)));
|
|
27457
|
+
return rows.length ? block(null, ...rows) : null;
|
|
27458
|
+
}
|
|
27459
|
+
/** The tool's own options — every schema field the shared look and the rich sections
|
|
27460
|
+
* don't cover, each as the control its kind calls for. */
|
|
27461
|
+
optionsBlock(b, fields, d) {
|
|
27462
|
+
const skip = (f) => f.path.startsWith("style.") || f.path.startsWith("text.") || f.path.startsWith("frvp.") || d instanceof PositionTool && POSITION_PATHS.has(f.path) || d.editableLevels() != null && LEVEL_PATHS.has(f.path);
|
|
27463
|
+
const rows = [];
|
|
27464
|
+
const own = fields.filter((f) => !skip(f));
|
|
27465
|
+
for (let i = 0; i < own.length; i += 1) {
|
|
27466
|
+
const f = own[i];
|
|
27467
|
+
const next = own[i + 1];
|
|
27468
|
+
const base = f.kind === "color" && next?.kind === "lineStyle" ? pairBase(f.label, next.label) : f.kind === "color" && next?.kind === "color" ? upperLowerBase(f.label, next.label) : null;
|
|
27469
|
+
if (base) {
|
|
27470
|
+
const color = this.fieldControl(b, f);
|
|
27471
|
+
const style = this.fieldControl(b, next);
|
|
27472
|
+
if (color && style) rows.push(row(base, color, style));
|
|
27473
|
+
i += 1;
|
|
27474
|
+
continue;
|
|
26315
27475
|
}
|
|
26316
|
-
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
26320
|
-
|
|
26321
|
-
|
|
26322
|
-
|
|
26323
|
-
const
|
|
26324
|
-
|
|
26325
|
-
|
|
26326
|
-
|
|
26327
|
-
|
|
26328
|
-
|
|
26329
|
-
|
|
26330
|
-
|
|
26331
|
-
|
|
26332
|
-
|
|
26333
|
-
label,
|
|
26334
|
-
|
|
26335
|
-
|
|
26336
|
-
|
|
26337
|
-
|
|
26338
|
-
|
|
26339
|
-
|
|
26340
|
-
|
|
26341
|
-
|
|
26342
|
-
|
|
26343
|
-
|
|
27476
|
+
const ctrl = this.fieldControl(b, f);
|
|
27477
|
+
if (!ctrl) continue;
|
|
27478
|
+
const r = row(f.label, ctrl);
|
|
27479
|
+
const cond = f.when;
|
|
27480
|
+
if (cond) b.watch(() => r.hidden = !cond.in.includes(readPath(b.live(), cond.path)));
|
|
27481
|
+
rows.push(r);
|
|
27482
|
+
}
|
|
27483
|
+
const props = d.serialize().props ?? {};
|
|
27484
|
+
const sizes = TEXT_SIZE_OPTIONS.map((o) => ({ value: o.value, html: o.label }));
|
|
27485
|
+
if ("numbersSize" in props) rows.push(row("Numbers", this.seg(b, "Numbers size", sizes, () => b.live().serialize().props?.numbersSize ?? "small", (v) => b.edit({ numbersSize: v }), true)));
|
|
27486
|
+
if ("labelsSize" in props) rows.push(row("Labels", this.seg(b, "Labels size", sizes, () => b.live().serialize().props?.labelsSize ?? "normal", (v) => b.edit({ labelsSize: v }), true)));
|
|
27487
|
+
return rows.length ? block(null, ...rows) : null;
|
|
27488
|
+
}
|
|
27489
|
+
fieldControl(b, f) {
|
|
27490
|
+
const read = () => readPath(b.live(), f.path);
|
|
27491
|
+
switch (f.kind) {
|
|
27492
|
+
case "color":
|
|
27493
|
+
return this.swatch(b, f.label, () => read() || DEFAULT_DRAWING_COLOR, f.path);
|
|
27494
|
+
case "lineStyle":
|
|
27495
|
+
return this.seg(b, f.label, LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, html: dashIcon(o.value), title: o.label })), () => read(), (v) => b.edit({ [f.path]: v }));
|
|
27496
|
+
case "boolean":
|
|
27497
|
+
return this.switch(b, () => read() !== false && read() != null, (v) => b.edit({ [f.path]: v }));
|
|
27498
|
+
case "number":
|
|
27499
|
+
case "opacity":
|
|
27500
|
+
return this.number(b, f.label, () => Number(read() ?? 0), f.path, f);
|
|
27501
|
+
case "select": {
|
|
27502
|
+
const options = f.path === "magnifier.timeframe" ? magnifierTimeframesBelow(this.env.chartBarMs()) : f.options ?? [];
|
|
27503
|
+
if (options.length === 0) return null;
|
|
27504
|
+
const sel = buildFieldControl({ kind: "select", options, value: String(read() ?? options[0].value), fill: false, theme: this.theme, onChange: (v) => b.edit({ [f.path]: v }) });
|
|
27505
|
+
b.watch(() => sel.setValue?.(String(read() ?? "")));
|
|
27506
|
+
return sel.el;
|
|
27507
|
+
}
|
|
27508
|
+
case "text": {
|
|
27509
|
+
const tf = buildFieldControl({ kind: "text", value: String(read() ?? ""), fill: true, onChange: (v) => b.edit({ [f.path]: v }) });
|
|
27510
|
+
return tf.el;
|
|
27511
|
+
}
|
|
27512
|
+
default:
|
|
27513
|
+
return null;
|
|
26344
27514
|
}
|
|
26345
|
-
const summary = document.createElement("div");
|
|
26346
|
-
summary.className = "vela-field-span";
|
|
26347
|
-
summary.style.cssText = "opacity:0.7;font-size:11px;line-height:1.4;border-top:1px solid var(--vela-border);padding-top:6px;font-variant-numeric:tabular-nums;";
|
|
26348
|
-
refreshers.push(() => {
|
|
26349
|
-
const d = live();
|
|
26350
|
-
summary.textContent = `${d.headerLabel()} \u2014 ${d.lossSizeLabel()}`;
|
|
26351
|
-
});
|
|
26352
|
-
grid.appendChild(summary);
|
|
26353
|
-
refreshAll();
|
|
26354
27515
|
}
|
|
26355
|
-
|
|
26356
|
-
|
|
26357
|
-
|
|
26358
|
-
|
|
27516
|
+
/** A levelled tool's levels: every level in a two-column grid (on/off, ratio, color) — the
|
|
27517
|
+
* ones that are off stay listed, ready to switch on. A retracement leads with its ready-made
|
|
27518
|
+
* sets; the horizontal fibs follow with how their level text and fill show. */
|
|
27519
|
+
levelsBlock(b) {
|
|
27520
|
+
const d = b.live();
|
|
27521
|
+
const rows = [];
|
|
27522
|
+
const grid = document.createElement("div");
|
|
27523
|
+
grid.className = "vela-dsp-lv";
|
|
27524
|
+
const key = () => JSON.stringify(b.live().editableLevels() ?? []);
|
|
27525
|
+
let last = "";
|
|
27526
|
+
let editing = false;
|
|
27527
|
+
const mine = (p) => {
|
|
27528
|
+
editing = true;
|
|
27529
|
+
try {
|
|
27530
|
+
b.edit(p);
|
|
27531
|
+
} finally {
|
|
27532
|
+
editing = false;
|
|
27533
|
+
}
|
|
27534
|
+
last = key();
|
|
26359
27535
|
};
|
|
26360
|
-
const
|
|
26361
|
-
|
|
26362
|
-
grid.
|
|
26363
|
-
|
|
26364
|
-
|
|
27536
|
+
const fill = () => {
|
|
27537
|
+
last = key();
|
|
27538
|
+
grid.replaceChildren();
|
|
27539
|
+
const free = b.live() instanceof FibLevels;
|
|
27540
|
+
(b.live().editableLevels() ?? []).forEach((lv, i) => {
|
|
27541
|
+
const cell = document.createElement("div");
|
|
27542
|
+
cell.className = "vela-dsp-lv-c";
|
|
27543
|
+
cell.toggleAttribute("data-off", !lv.enabled);
|
|
27544
|
+
const on = buildFieldControl({
|
|
27545
|
+
kind: "switch",
|
|
27546
|
+
checked: lv.enabled,
|
|
27547
|
+
onChange: (v) => {
|
|
27548
|
+
cell.toggleAttribute("data-off", !v);
|
|
27549
|
+
mine({ [`levels.${i}.enabled`]: v });
|
|
27550
|
+
}
|
|
27551
|
+
});
|
|
27552
|
+
let ratio = lv.ratio;
|
|
27553
|
+
const num4 = buildFieldControl({
|
|
26365
27554
|
kind: "number",
|
|
26366
|
-
value:
|
|
26367
|
-
min,
|
|
26368
|
-
|
|
26369
|
-
|
|
26370
|
-
integer,
|
|
27555
|
+
value: lv.ratio,
|
|
27556
|
+
min: free ? void 0 : 0,
|
|
27557
|
+
step: 0.01,
|
|
27558
|
+
compact: true,
|
|
26371
27559
|
fill: false,
|
|
26372
27560
|
commit: "blur",
|
|
26373
|
-
|
|
26374
|
-
|
|
26375
|
-
|
|
26376
|
-
|
|
26377
|
-
|
|
26378
|
-
|
|
26379
|
-
|
|
26380
|
-
|
|
26381
|
-
|
|
26382
|
-
|
|
26383
|
-
|
|
26384
|
-
|
|
26385
|
-
value: s.anchor,
|
|
26386
|
-
fill: false,
|
|
26387
|
-
theme: this.theme,
|
|
26388
|
-
onChange: (v) => actions.patch({ "frvp.anchor": v })
|
|
26389
|
-
}).el
|
|
26390
|
-
}));
|
|
26391
|
-
const colorRow = (label, path) => {
|
|
26392
|
-
grid.appendChild(fieldRow({
|
|
26393
|
-
label,
|
|
26394
|
-
fit: true,
|
|
26395
|
-
control: buildFieldControl({
|
|
27561
|
+
title: `Level ${i + 1}`,
|
|
27562
|
+
onChange: (n) => {
|
|
27563
|
+
if (!Number.isFinite(n) || !free && n <= 0) {
|
|
27564
|
+
num4.setValue?.(ratio);
|
|
27565
|
+
return;
|
|
27566
|
+
}
|
|
27567
|
+
ratio = n;
|
|
27568
|
+
mine({ [`levels.${i}.ratio`]: n });
|
|
27569
|
+
}
|
|
27570
|
+
});
|
|
27571
|
+
let color = lv.color;
|
|
27572
|
+
const swatch = buildFieldControl({
|
|
26396
27573
|
kind: "color",
|
|
26397
27574
|
theme: this.theme,
|
|
26398
|
-
|
|
26399
|
-
|
|
26400
|
-
|
|
26401
|
-
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
|
|
26406
|
-
|
|
26407
|
-
const styles = LINE_STYLE_OPTIONS.map((o) => ({ value: o.value, label: o.label }));
|
|
26408
|
-
const levelRow = (label, showPath, colorPath, stylePath) => {
|
|
26409
|
-
const row = document.createElement("div");
|
|
26410
|
-
row.className = "vela-field-span";
|
|
26411
|
-
row.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
26412
|
-
const sw = buildFieldControl({
|
|
26413
|
-
kind: "switch",
|
|
26414
|
-
checked: Boolean(s[showPath]),
|
|
26415
|
-
onChange: (v) => actions.patch({ [`frvp.${showPath}`]: v })
|
|
26416
|
-
});
|
|
26417
|
-
const lbl = document.createElement("span");
|
|
26418
|
-
lbl.className = "vela-field-label";
|
|
26419
|
-
lbl.style.flex = "1";
|
|
26420
|
-
lbl.textContent = label;
|
|
26421
|
-
let cur = s[colorPath] ?? contrastColor(this.theme.background);
|
|
26422
|
-
const col = buildFieldControl({
|
|
26423
|
-
kind: "color",
|
|
26424
|
-
theme: this.theme,
|
|
26425
|
-
get: () => cur,
|
|
26426
|
-
onChange: (v) => {
|
|
26427
|
-
cur = v;
|
|
26428
|
-
actions.patch({ [`frvp.${colorPath}`]: v });
|
|
26429
|
-
}
|
|
27575
|
+
title: `Level ${i + 1} color`,
|
|
27576
|
+
get: () => color,
|
|
27577
|
+
onChange: (v) => {
|
|
27578
|
+
color = v;
|
|
27579
|
+
mine({ [`levels.${i}.color`]: v });
|
|
27580
|
+
}
|
|
27581
|
+
});
|
|
27582
|
+
cell.append(on.el, num4.el, swatch.el);
|
|
27583
|
+
grid.appendChild(cell);
|
|
26430
27584
|
});
|
|
26431
|
-
|
|
27585
|
+
};
|
|
27586
|
+
b.watch(() => {
|
|
27587
|
+
if (!editing && key() !== last && !grid.contains(document.activeElement)) fill();
|
|
27588
|
+
});
|
|
27589
|
+
if (d instanceof FibRetracement) {
|
|
27590
|
+
rows.push(
|
|
27591
|
+
this.seg(
|
|
27592
|
+
b,
|
|
27593
|
+
"Level set",
|
|
27594
|
+
FIB_PRESETS.map((p) => ({ value: p.key, html: p.label })),
|
|
27595
|
+
() => {
|
|
27596
|
+
const f = b.live();
|
|
27597
|
+
return matchFibPreset(f.levels, f.reverse) ?? void 0;
|
|
27598
|
+
},
|
|
27599
|
+
(k) => {
|
|
27600
|
+
const preset = FIB_PRESETS.find((p) => p.key === k);
|
|
27601
|
+
b.edit({ levels: clonePlain(preset.levels), reverse: preset.reverse });
|
|
27602
|
+
},
|
|
27603
|
+
true
|
|
27604
|
+
)
|
|
27605
|
+
);
|
|
27606
|
+
}
|
|
27607
|
+
if (d instanceof MachFigure) rows.push(row("Ratios", this.switch(b, () => b.live().showRatios !== false, (v) => b.edit({ showRatios: v }))));
|
|
27608
|
+
rows.push(grid);
|
|
27609
|
+
if (d instanceof FibLevels) {
|
|
27610
|
+
const fib = () => b.live();
|
|
27611
|
+
const flags = [
|
|
27612
|
+
this.chip(b, "Ratios", () => fib().showRatios, (on) => b.edit({ showRatios: on })),
|
|
27613
|
+
this.chip(b, "Prices", () => fib().showPrices, (on) => b.edit({ showPrices: on })),
|
|
27614
|
+
this.chip(b, "Background", () => fib().background, (on) => b.edit({ background: on }))
|
|
27615
|
+
];
|
|
27616
|
+
if (d instanceof FibRetracement) flags.push(this.chip(b, "Reverse", () => b.live().reverse, (on) => b.edit({ reverse: on })));
|
|
27617
|
+
rows.push(row("Show", chips(...flags)));
|
|
27618
|
+
rows.push(
|
|
27619
|
+
row(
|
|
27620
|
+
"Label side",
|
|
27621
|
+
this.seg(b, "Label side", [
|
|
27622
|
+
{ value: "left", html: "Left" },
|
|
27623
|
+
{ value: "right", html: "Right" }
|
|
27624
|
+
], () => fib().labelSide, (v) => b.edit({ labelSide: v }))
|
|
27625
|
+
)
|
|
27626
|
+
);
|
|
27627
|
+
}
|
|
27628
|
+
return block("Levels", ...rows);
|
|
27629
|
+
}
|
|
27630
|
+
/** A rich per-type section on the shared field grid. */
|
|
27631
|
+
richBlock(title, fill) {
|
|
27632
|
+
const grid = fieldGrid({ variant: "inputs" });
|
|
27633
|
+
grid.classList.add("vela-dsp-grid");
|
|
27634
|
+
fill(grid);
|
|
27635
|
+
return block(title, grid);
|
|
27636
|
+
}
|
|
27637
|
+
/** A note, a callout, a text label: the words lead the panel. */
|
|
27638
|
+
textContentBlock(b, has) {
|
|
27639
|
+
return block(null, this.textArea(b, "Text", 3), row("Style", ...this.textStyling(b, has)));
|
|
27640
|
+
}
|
|
27641
|
+
/** A shape's optional label: folded to one line saying what it reads. */
|
|
27642
|
+
textFold(b, has) {
|
|
27643
|
+
const type = b.live().type;
|
|
27644
|
+
const words = () => b.live().text?.value?.trim() ?? "";
|
|
27645
|
+
const summary = () => {
|
|
27646
|
+
if (!has("text.value")) return sizeLabel(b.live().text?.size);
|
|
27647
|
+
const w = words();
|
|
27648
|
+
return w ? `\u201C${w.split("\n")[0]}\u201D` : "None";
|
|
27649
|
+
};
|
|
27650
|
+
return this.fold(b, "text", has("text.value") ? "Label" : "Text", summary, () => {
|
|
27651
|
+
const rows = [];
|
|
27652
|
+
if (has("text.value")) rows.push(this.textArea(b, "Add a label", 1));
|
|
27653
|
+
rows.push(row("Style", ...this.textStyling(b, has)));
|
|
27654
|
+
if (LINE_LABEL_TYPES.has(type) && has("text.value")) {
|
|
27655
|
+
const place = row(
|
|
27656
|
+
"Place",
|
|
27657
|
+
this.seg(b, "Position along the line", [
|
|
27658
|
+
{ value: "start", html: "Start" },
|
|
27659
|
+
{ value: "middle", html: "Middle" },
|
|
27660
|
+
{ value: "end", html: "End" }
|
|
27661
|
+
], () => b.live().text?.place ?? "middle", (v) => b.edit({ "text.place": v }), true)
|
|
27662
|
+
);
|
|
27663
|
+
const side = row(
|
|
27664
|
+
"Side",
|
|
27665
|
+
this.seg(b, "Side of the line", [
|
|
27666
|
+
{ value: "above", html: "Above" },
|
|
27667
|
+
{ value: "below", html: "Below" }
|
|
27668
|
+
], () => b.live().text?.side ?? "above", (v) => b.edit({ "text.side": v }), true)
|
|
27669
|
+
);
|
|
27670
|
+
b.watch(() => place.hidden = side.hidden = words() === "");
|
|
27671
|
+
rows.push(place, side);
|
|
27672
|
+
}
|
|
27673
|
+
return rows;
|
|
27674
|
+
});
|
|
27675
|
+
}
|
|
27676
|
+
textArea(b, placeholder, rows) {
|
|
27677
|
+
const ta = new TextArea({
|
|
27678
|
+
value: b.live().text?.value ?? "",
|
|
27679
|
+
rows,
|
|
27680
|
+
size: "sm",
|
|
27681
|
+
autoGrow: true,
|
|
27682
|
+
maxLines: 6,
|
|
27683
|
+
placeholder,
|
|
27684
|
+
onChange: (v) => b.edit({ "text.value": v })
|
|
27685
|
+
});
|
|
27686
|
+
ta.input.addEventListener("keydown", (e) => {
|
|
27687
|
+
if (e.key !== "Escape") e.stopPropagation();
|
|
27688
|
+
});
|
|
27689
|
+
b.watch(() => {
|
|
27690
|
+
const v = b.live().text?.value ?? "";
|
|
27691
|
+
if (document.activeElement !== ta.input && ta.input.value !== v) ta.input.value = v;
|
|
27692
|
+
});
|
|
27693
|
+
ta.el.style.width = "100%";
|
|
27694
|
+
return ta.el;
|
|
27695
|
+
}
|
|
27696
|
+
textStyling(b, has) {
|
|
27697
|
+
const out = [];
|
|
27698
|
+
if (has("text.color")) out.push(this.swatch(b, "Text color", () => b.live().text?.color || this.theme.textColor, "text.color"));
|
|
27699
|
+
if (has("text.size")) {
|
|
27700
|
+
const sel = buildFieldControl({
|
|
26432
27701
|
kind: "select",
|
|
26433
|
-
options:
|
|
26434
|
-
value:
|
|
27702
|
+
options: TEXT_SIZE_OPTIONS,
|
|
27703
|
+
value: b.live().text?.size ?? "normal",
|
|
26435
27704
|
fill: false,
|
|
26436
27705
|
theme: this.theme,
|
|
26437
|
-
onChange: (v) =>
|
|
27706
|
+
onChange: (v) => b.edit({ "text.size": v })
|
|
26438
27707
|
});
|
|
26439
|
-
|
|
26440
|
-
|
|
27708
|
+
b.watch(() => sel.setValue?.(b.live().text?.size ?? "normal"));
|
|
27709
|
+
out.push(sel.el);
|
|
27710
|
+
}
|
|
27711
|
+
const flags = [];
|
|
27712
|
+
if (has("text.bold")) flags.push(this.chip(b, "B", () => !!b.live().text?.bold, (on) => b.edit({ "text.bold": on }), "Bold"));
|
|
27713
|
+
if (has("text.italic")) flags.push(this.chip(b, "I", () => !!b.live().text?.italic, (on) => b.edit({ "text.italic": on }), "Italic"));
|
|
27714
|
+
if (flags.length) out.push(chips(...flags));
|
|
27715
|
+
return out;
|
|
27716
|
+
}
|
|
27717
|
+
/** Each anchor's price (editable) and time. */
|
|
27718
|
+
pointsFold(b) {
|
|
27719
|
+
const d = b.live();
|
|
27720
|
+
if (NO_POINTS.has(d.type) || d.anchors.length === 0 || d.anchors.length > MAX_POINTS) return null;
|
|
27721
|
+
const slots = d.anchorSchema().slots;
|
|
27722
|
+
const free = (i) => slots[i]?.free ?? slots[slots.length - 1]?.free ?? "both";
|
|
27723
|
+
const shown = d.anchors.map((_, i) => i).filter((i) => free(i) !== "none");
|
|
27724
|
+
if (shown.length === 0) return null;
|
|
27725
|
+
const priced = shown.filter((i) => free(i) !== "x");
|
|
27726
|
+
const shownPrice = (n) => {
|
|
27727
|
+
const dec = this.env.priceDecimals() ?? Math.max(2, valueDecimals(n));
|
|
27728
|
+
return Number(n.toFixed(Math.min(dec, 10)));
|
|
26441
27729
|
};
|
|
26442
|
-
|
|
26443
|
-
|
|
26444
|
-
|
|
26445
|
-
|
|
26446
|
-
|
|
26447
|
-
|
|
26448
|
-
|
|
26449
|
-
|
|
26450
|
-
|
|
26451
|
-
|
|
26452
|
-
|
|
26453
|
-
|
|
26454
|
-
|
|
26455
|
-
|
|
26456
|
-
|
|
26457
|
-
|
|
26458
|
-
|
|
26459
|
-
|
|
27730
|
+
const fmt = (n) => String(shownPrice(n));
|
|
27731
|
+
const summary = () => {
|
|
27732
|
+
const a = b.live().anchors;
|
|
27733
|
+
if (priced.length) return priced.map((i) => fmt(a[i]?.price ?? 0)).join(" \u2192 ");
|
|
27734
|
+
const t = a[shown[0]]?.time;
|
|
27735
|
+
return shown.length === 1 && t != null ? formatTimeStamp(t, this.env.timeZone(), this.env.chartBarMs()) : `${shown.length} points`;
|
|
27736
|
+
};
|
|
27737
|
+
return this.fold(
|
|
27738
|
+
b,
|
|
27739
|
+
"points",
|
|
27740
|
+
"Points",
|
|
27741
|
+
summary,
|
|
27742
|
+
() => shown.map((i, n) => {
|
|
27743
|
+
const key = shown.length === 1 ? free(i) === "x" ? "Time" : "Price" : String.fromCharCode(65 + n);
|
|
27744
|
+
const line = document.createElement("div");
|
|
27745
|
+
line.className = "vela-dsp-pt";
|
|
27746
|
+
const k = document.createElement("span");
|
|
27747
|
+
k.className = "vela-dsp-pt-k";
|
|
27748
|
+
k.textContent = key;
|
|
27749
|
+
line.appendChild(k);
|
|
27750
|
+
if (free(i) !== "x") {
|
|
27751
|
+
const ni = buildFieldControl({
|
|
27752
|
+
kind: "number",
|
|
27753
|
+
value: shownPrice(d.anchors[i].price),
|
|
27754
|
+
fill: true,
|
|
27755
|
+
commit: "blur",
|
|
27756
|
+
title: `Point ${key} price`,
|
|
27757
|
+
onChange: (v) => b.edit({ [`anchors.${i}.price`]: v })
|
|
27758
|
+
});
|
|
27759
|
+
const input = ni.el.querySelector("input");
|
|
27760
|
+
b.watch(() => {
|
|
27761
|
+
if (document.activeElement !== input) ni.setValue?.(shownPrice(b.live().anchors[i]?.price ?? 0));
|
|
27762
|
+
});
|
|
27763
|
+
line.appendChild(ni.el);
|
|
27764
|
+
} else {
|
|
27765
|
+
line.appendChild(document.createElement("span"));
|
|
26460
27766
|
}
|
|
26461
|
-
|
|
26462
|
-
|
|
26463
|
-
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
26467
|
-
|
|
26468
|
-
|
|
26469
|
-
|
|
26470
|
-
|
|
27767
|
+
const t = document.createElement("span");
|
|
27768
|
+
t.className = "vela-dsp-pt-t";
|
|
27769
|
+
b.watch(() => {
|
|
27770
|
+
const time = b.live().anchors[i]?.time;
|
|
27771
|
+
t.textContent = time != null ? formatTimeStamp(time, this.env.timeZone(), this.env.chartBarMs()) : "";
|
|
27772
|
+
});
|
|
27773
|
+
line.appendChild(t);
|
|
27774
|
+
return line;
|
|
27775
|
+
})
|
|
27776
|
+
);
|
|
27777
|
+
}
|
|
27778
|
+
/** The timeframes the drawing shows on: one line while it's every timeframe, the band
|
|
27779
|
+
* picker only once the user goes custom. */
|
|
27780
|
+
showOnFold(b) {
|
|
27781
|
+
const bands = () => b.live().showOn;
|
|
27782
|
+
let pickedCustom = false;
|
|
27783
|
+
const mode = () => pickedCustom && bands() ? "custom" : modeOf(bands());
|
|
27784
|
+
const here = timeframeBandOf(this.env.chartBarMs());
|
|
27785
|
+
const summary = () => {
|
|
27786
|
+
const s = bands();
|
|
27787
|
+
if (!s) return "All timeframes";
|
|
27788
|
+
if (mode() === "intraday") return "Intraday";
|
|
27789
|
+
if (mode() === "higher") return "1H and up";
|
|
27790
|
+
if (s.length === 0) return "Nowhere";
|
|
27791
|
+
return s.map((k) => TIMEFRAME_BANDS.find((t) => t.key === k)?.label ?? k).join(", ");
|
|
27792
|
+
};
|
|
27793
|
+
return this.fold(b, "show", "Show on", summary, () => {
|
|
27794
|
+
const set = (next) => b.edit({ showOn: next });
|
|
27795
|
+
const pick = this.seg(
|
|
27796
|
+
b,
|
|
27797
|
+
"Show on",
|
|
27798
|
+
[
|
|
27799
|
+
{ value: "all", html: "All" },
|
|
27800
|
+
{ value: "intraday", html: "Intraday" },
|
|
27801
|
+
{ value: "higher", html: "1H and up" },
|
|
27802
|
+
{ value: "custom", html: "Custom" }
|
|
27803
|
+
],
|
|
27804
|
+
mode,
|
|
27805
|
+
(v) => {
|
|
27806
|
+
pickedCustom = v === "custom";
|
|
27807
|
+
if (v === "all") set(void 0);
|
|
27808
|
+
else if (v === "intraday") set([...INTRADAY]);
|
|
27809
|
+
else if (v === "higher") set([...HIGHER]);
|
|
27810
|
+
else set([...bands() ?? TIMEFRAME_BANDS.map((t) => t.key)]);
|
|
27811
|
+
},
|
|
27812
|
+
true
|
|
27813
|
+
);
|
|
27814
|
+
const grid = document.createElement("div");
|
|
27815
|
+
grid.className = "vela-dsp-tf";
|
|
27816
|
+
for (const band of TIMEFRAME_BANDS) {
|
|
27817
|
+
const btn = document.createElement("button");
|
|
27818
|
+
btn.type = "button";
|
|
27819
|
+
btn.textContent = band.label;
|
|
27820
|
+
if (band.key === here) {
|
|
27821
|
+
btn.dataset.here = "";
|
|
27822
|
+
btn.title = "The chart's timeframe";
|
|
26471
27823
|
}
|
|
26472
|
-
|
|
26473
|
-
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
26477
|
-
|
|
26478
|
-
|
|
26479
|
-
|
|
26480
|
-
|
|
26481
|
-
|
|
27824
|
+
btn.addEventListener("click", () => {
|
|
27825
|
+
const cur = new Set(bands() ?? []);
|
|
27826
|
+
if (cur.has(band.key)) cur.delete(band.key);
|
|
27827
|
+
else cur.add(band.key);
|
|
27828
|
+
set(TIMEFRAME_BANDS.map((t) => t.key).filter((k) => cur.has(k)));
|
|
27829
|
+
});
|
|
27830
|
+
b.watch(() => btn.setAttribute("aria-pressed", String(bands()?.includes(band.key) ?? true)));
|
|
27831
|
+
grid.appendChild(btn);
|
|
27832
|
+
}
|
|
27833
|
+
const note = document.createElement("div");
|
|
27834
|
+
note.className = "vela-dsp-note";
|
|
27835
|
+
b.watch(() => {
|
|
27836
|
+
grid.hidden = mode() !== "custom";
|
|
27837
|
+
const s = bands();
|
|
27838
|
+
const hidden = s != null && here != null && !s.includes(here);
|
|
27839
|
+
note.hidden = !hidden;
|
|
27840
|
+
note.textContent = s?.length === 0 ? "Hidden on every timeframe." : "Hidden on this chart's timeframe.";
|
|
26482
27841
|
});
|
|
26483
|
-
|
|
26484
|
-
|
|
26485
|
-
|
|
26486
|
-
|
|
26487
|
-
|
|
26488
|
-
|
|
26489
|
-
|
|
26490
|
-
|
|
27842
|
+
return [pick, grid, note];
|
|
27843
|
+
});
|
|
27844
|
+
}
|
|
27845
|
+
// ── the style menu ──
|
|
27846
|
+
/** The header's style menu: the tool's ready-made and saved looks (hover to preview, click
|
|
27847
|
+
* to apply), saving the current look, and going back to the tool's defaults. */
|
|
27848
|
+
styleMenuButton(b) {
|
|
27849
|
+
const btn = document.createElement("button");
|
|
27850
|
+
btn.type = "button";
|
|
27851
|
+
btn.className = "vela-dsp-style";
|
|
27852
|
+
btn.setAttribute("aria-haspopup", "menu");
|
|
27853
|
+
btn.setAttribute("aria-expanded", "false");
|
|
27854
|
+
const label = document.createElement("span");
|
|
27855
|
+
const chev = document.createElement("span");
|
|
27856
|
+
chev.style.display = "flex";
|
|
27857
|
+
chev.innerHTML = CHEVRON_DOWN;
|
|
27858
|
+
btn.append(label, chev);
|
|
27859
|
+
const all = () => [...builtinToolTemplates(b.live().type), ...b.actions.templates?.() ?? []];
|
|
27860
|
+
const refresh = () => {
|
|
27861
|
+
const match = all().filter((t) => matchesTemplate(b.live(), t.settings)).pop();
|
|
27862
|
+
label.textContent = match?.name ?? "Style";
|
|
27863
|
+
btn.title = match ? `Style: ${match.name}` : "Styles";
|
|
27864
|
+
};
|
|
27865
|
+
btn.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
27866
|
+
btn.addEventListener("click", (e) => {
|
|
27867
|
+
e.stopPropagation();
|
|
27868
|
+
if (this.menu) {
|
|
27869
|
+
this.menu.hide();
|
|
27870
|
+
return;
|
|
27871
|
+
}
|
|
27872
|
+
this.openStyleMenu(btn, b, all, refresh);
|
|
27873
|
+
});
|
|
27874
|
+
refresh();
|
|
27875
|
+
return { el: btn, refresh };
|
|
27876
|
+
}
|
|
27877
|
+
openStyleMenu(anchor, b, all, refresh) {
|
|
27878
|
+
closeOpenPopovers();
|
|
27879
|
+
const t = this.theme;
|
|
27880
|
+
const pop = new Popover({
|
|
27881
|
+
trigger: anchor,
|
|
27882
|
+
host: this.host,
|
|
27883
|
+
theme: t,
|
|
27884
|
+
position: "absolute",
|
|
27885
|
+
boundary: this.host,
|
|
27886
|
+
boundaryInset: 4,
|
|
27887
|
+
viewportInset: 0,
|
|
27888
|
+
gap: 6,
|
|
27889
|
+
align: "end",
|
|
27890
|
+
zIndex: 6e3,
|
|
27891
|
+
onClose: () => {
|
|
27892
|
+
b.actions.preview?.(null);
|
|
27893
|
+
anchor.setAttribute("aria-expanded", "false");
|
|
27894
|
+
if (this.menu === pop) this.menu = null;
|
|
27895
|
+
}
|
|
27896
|
+
});
|
|
27897
|
+
const el = pop.el;
|
|
27898
|
+
el.classList.add("vela-dsp-menu");
|
|
27899
|
+
el.setAttribute("role", "menu");
|
|
27900
|
+
el.style.cssText += `background:${t.background};border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);color:${t.textColor};font:13px ${t.fontFamily};pointer-events:auto;`;
|
|
27901
|
+
applyChromeTokens(el, t);
|
|
27902
|
+
const fill = () => {
|
|
27903
|
+
el.replaceChildren();
|
|
27904
|
+
const builtins = builtinToolTemplates(b.live().type);
|
|
27905
|
+
const saved = b.actions.templates?.() ?? [];
|
|
27906
|
+
const item = (tpl, removable) => {
|
|
27907
|
+
const row2 = document.createElement("button");
|
|
27908
|
+
row2.type = "button";
|
|
27909
|
+
row2.className = "vela-dsp-mi";
|
|
27910
|
+
row2.setAttribute("role", "menuitem");
|
|
27911
|
+
if (matchesTemplate(b.live(), tpl.settings)) row2.dataset.current = "";
|
|
27912
|
+
const dot = document.createElement("span");
|
|
27913
|
+
dot.className = "vela-dsp-mi-dot";
|
|
27914
|
+
dot.style.background = templateColor(tpl.settings) ?? "transparent";
|
|
27915
|
+
const name = document.createElement("span");
|
|
27916
|
+
name.className = "vela-dsp-mi-t";
|
|
27917
|
+
name.textContent = tpl.name;
|
|
27918
|
+
row2.append(dot, name);
|
|
27919
|
+
if (removable) {
|
|
27920
|
+
const x = document.createElement("span");
|
|
27921
|
+
x.className = "vela-dsp-mi-x";
|
|
27922
|
+
x.setAttribute("role", "button");
|
|
27923
|
+
x.setAttribute("aria-label", `Delete ${tpl.name}`);
|
|
27924
|
+
x.innerHTML = CROSS;
|
|
27925
|
+
x.addEventListener("click", (e) => {
|
|
27926
|
+
e.stopPropagation();
|
|
27927
|
+
b.actions.preview?.(null);
|
|
27928
|
+
b.actions.removeTemplate?.(tpl.name);
|
|
27929
|
+
fill();
|
|
27930
|
+
refresh();
|
|
27931
|
+
});
|
|
27932
|
+
row2.appendChild(x);
|
|
26491
27933
|
}
|
|
27934
|
+
row2.addEventListener("pointerenter", () => b.actions.preview?.(tpl.settings));
|
|
27935
|
+
row2.addEventListener("pointerleave", () => b.actions.preview?.(null));
|
|
27936
|
+
row2.addEventListener("click", () => {
|
|
27937
|
+
b.actions.preview?.(null);
|
|
27938
|
+
b.actions.applyToolSettings?.(tpl.settings);
|
|
27939
|
+
pop.hide();
|
|
27940
|
+
b.rebuild();
|
|
27941
|
+
});
|
|
27942
|
+
return row2;
|
|
27943
|
+
};
|
|
27944
|
+
if (builtins.length) {
|
|
27945
|
+
el.appendChild(menuHeading("Presets"));
|
|
27946
|
+
for (const tpl of builtins) el.appendChild(item(tpl, false));
|
|
27947
|
+
}
|
|
27948
|
+
if (saved.length) {
|
|
27949
|
+
el.appendChild(menuHeading("Saved"));
|
|
27950
|
+
for (const tpl of saved) el.appendChild(item(tpl, true));
|
|
27951
|
+
}
|
|
27952
|
+
if (builtins.length || saved.length) el.appendChild(separator());
|
|
27953
|
+
if (b.actions.saveTemplate) el.appendChild(this.saveRow(b, fill, refresh));
|
|
27954
|
+
const reset = document.createElement("button");
|
|
27955
|
+
reset.type = "button";
|
|
27956
|
+
reset.className = "vela-dsp-mi";
|
|
27957
|
+
reset.setAttribute("role", "menuitem");
|
|
27958
|
+
reset.innerHTML = '<span class="vela-dsp-mi-t">Reset to default</span>';
|
|
27959
|
+
reset.addEventListener("click", () => {
|
|
27960
|
+
b.actions.resetSettings();
|
|
27961
|
+
pop.hide();
|
|
27962
|
+
b.rebuild();
|
|
26492
27963
|
});
|
|
26493
|
-
|
|
26494
|
-
|
|
26495
|
-
|
|
26496
|
-
|
|
26497
|
-
|
|
26498
|
-
|
|
26499
|
-
|
|
26500
|
-
|
|
26501
|
-
|
|
26502
|
-
|
|
26503
|
-
|
|
26504
|
-
|
|
26505
|
-
|
|
26506
|
-
|
|
26507
|
-
|
|
26508
|
-
|
|
26509
|
-
|
|
27964
|
+
el.appendChild(reset);
|
|
27965
|
+
};
|
|
27966
|
+
fill();
|
|
27967
|
+
anchor.setAttribute("aria-expanded", "true");
|
|
27968
|
+
this.menu = pop;
|
|
27969
|
+
pop.show();
|
|
27970
|
+
}
|
|
27971
|
+
/** "Save as style…": a menu row that turns into a name field. */
|
|
27972
|
+
saveRow(b, refill, refresh) {
|
|
27973
|
+
const wrap = document.createElement("div");
|
|
27974
|
+
const start = document.createElement("button");
|
|
27975
|
+
start.type = "button";
|
|
27976
|
+
start.className = "vela-dsp-mi";
|
|
27977
|
+
start.setAttribute("role", "menuitem");
|
|
27978
|
+
start.innerHTML = '<span class="vela-dsp-mi-t">Save as style\u2026</span>';
|
|
27979
|
+
start.addEventListener("click", (e) => {
|
|
27980
|
+
e.stopPropagation();
|
|
27981
|
+
const form = document.createElement("div");
|
|
27982
|
+
form.className = "vela-dsp-mname";
|
|
27983
|
+
const input = document.createElement("input");
|
|
27984
|
+
input.type = "text";
|
|
27985
|
+
input.placeholder = "Style name";
|
|
27986
|
+
input.maxLength = 60;
|
|
27987
|
+
input.setAttribute("aria-label", "Style name");
|
|
27988
|
+
const save = button("Save", true, () => commit());
|
|
27989
|
+
save.style.height = "28px";
|
|
27990
|
+
const commit = () => {
|
|
27991
|
+
const name = input.value.trim();
|
|
27992
|
+
if (!name) return;
|
|
27993
|
+
b.actions.saveTemplate?.(name);
|
|
27994
|
+
refill();
|
|
27995
|
+
refresh();
|
|
27996
|
+
};
|
|
27997
|
+
input.addEventListener("keydown", (ev) => {
|
|
27998
|
+
ev.stopPropagation();
|
|
27999
|
+
if (ev.key === "Enter") commit();
|
|
28000
|
+
if (ev.key === "Escape") refill();
|
|
26510
28001
|
});
|
|
26511
|
-
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
|
|
26521
|
-
|
|
26522
|
-
|
|
26523
|
-
|
|
28002
|
+
form.append(input, save);
|
|
28003
|
+
wrap.replaceChildren(form);
|
|
28004
|
+
input.focus();
|
|
28005
|
+
});
|
|
28006
|
+
wrap.appendChild(start);
|
|
28007
|
+
return wrap;
|
|
28008
|
+
}
|
|
28009
|
+
// ── controls ──
|
|
28010
|
+
/** A fold: a one-line header saying what's set, opening to its rows. */
|
|
28011
|
+
fold(b, key, title, summary, body) {
|
|
28012
|
+
const type = b.live().type;
|
|
28013
|
+
const open2 = openFolds.get(type) ?? /* @__PURE__ */ new Set();
|
|
28014
|
+
openFolds.set(type, open2);
|
|
28015
|
+
const wrap = document.createElement("div");
|
|
28016
|
+
wrap.className = "vela-dsp-fold";
|
|
28017
|
+
const head = document.createElement("button");
|
|
28018
|
+
head.type = "button";
|
|
28019
|
+
head.className = "vela-dsp-fold-h";
|
|
28020
|
+
const t = document.createElement("span");
|
|
28021
|
+
t.className = "vela-dsp-fold-t";
|
|
28022
|
+
t.textContent = title;
|
|
28023
|
+
const s = document.createElement("span");
|
|
28024
|
+
s.className = "vela-dsp-fold-s";
|
|
28025
|
+
const chev = document.createElement("span");
|
|
28026
|
+
chev.className = "vela-dsp-chev";
|
|
28027
|
+
chev.innerHTML = CHEVRON;
|
|
28028
|
+
head.append(t, s, chev);
|
|
28029
|
+
const content = document.createElement("div");
|
|
28030
|
+
content.className = "vela-dsp-fold-b";
|
|
28031
|
+
let built = false;
|
|
28032
|
+
const setOpen = (on) => {
|
|
28033
|
+
if (on) open2.add(key);
|
|
28034
|
+
else open2.delete(key);
|
|
28035
|
+
wrap.toggleAttribute("data-open", on);
|
|
28036
|
+
head.setAttribute("aria-expanded", String(on));
|
|
28037
|
+
if (on && !built) {
|
|
28038
|
+
built = true;
|
|
28039
|
+
content.append(...body());
|
|
26524
28040
|
}
|
|
26525
|
-
|
|
28041
|
+
};
|
|
28042
|
+
head.addEventListener("click", () => setOpen(!open2.has(key)));
|
|
28043
|
+
b.watch(() => s.textContent = summary());
|
|
28044
|
+
setOpen(open2.has(key));
|
|
28045
|
+
wrap.append(head, content);
|
|
28046
|
+
return wrap;
|
|
28047
|
+
}
|
|
28048
|
+
seg(b, label, options, read, pick, full = false) {
|
|
28049
|
+
const el = document.createElement("div");
|
|
28050
|
+
el.className = "vela-dsp-seg";
|
|
28051
|
+
el.setAttribute("role", "group");
|
|
28052
|
+
el.setAttribute("aria-label", label);
|
|
28053
|
+
if (full) el.dataset.full = "";
|
|
28054
|
+
const buttons = options.map((o) => {
|
|
28055
|
+
const btn = document.createElement("button");
|
|
28056
|
+
btn.type = "button";
|
|
28057
|
+
btn.innerHTML = o.html;
|
|
28058
|
+
if (o.title) {
|
|
28059
|
+
btn.title = o.title;
|
|
28060
|
+
btn.setAttribute("aria-label", o.title);
|
|
28061
|
+
}
|
|
28062
|
+
btn.addEventListener("click", () => {
|
|
28063
|
+
pick(o.value);
|
|
28064
|
+
sync();
|
|
28065
|
+
});
|
|
28066
|
+
el.appendChild(btn);
|
|
28067
|
+
return btn;
|
|
28068
|
+
});
|
|
28069
|
+
const sync = () => {
|
|
28070
|
+
const cur = read();
|
|
28071
|
+
options.forEach((o, i) => buttons[i].setAttribute("aria-pressed", String(o.value === cur)));
|
|
28072
|
+
};
|
|
28073
|
+
b.watch(sync);
|
|
28074
|
+
return el;
|
|
28075
|
+
}
|
|
28076
|
+
chip(b, text, read, set, title) {
|
|
28077
|
+
const btn = document.createElement("button");
|
|
28078
|
+
btn.type = "button";
|
|
28079
|
+
btn.className = "vela-dsp-chip";
|
|
28080
|
+
btn.textContent = text;
|
|
28081
|
+
if (title) {
|
|
28082
|
+
btn.title = title;
|
|
28083
|
+
btn.setAttribute("aria-label", title);
|
|
28084
|
+
}
|
|
28085
|
+
if (text === "B") btn.style.fontWeight = "700";
|
|
28086
|
+
if (text === "I") btn.style.fontStyle = "italic";
|
|
28087
|
+
btn.addEventListener("click", () => set(!read()));
|
|
28088
|
+
b.watch(() => btn.setAttribute("aria-pressed", String(read())));
|
|
28089
|
+
return btn;
|
|
28090
|
+
}
|
|
28091
|
+
swatch(b, title, read, path) {
|
|
28092
|
+
const ctrl = buildFieldControl({ kind: "color", theme: this.theme, title, get: read, onChange: (v) => b.edit({ [path]: v }) });
|
|
28093
|
+
return ctrl.el;
|
|
28094
|
+
}
|
|
28095
|
+
switch(b, read, set) {
|
|
28096
|
+
const sw = buildFieldControl({ kind: "switch", checked: read(), onChange: set });
|
|
28097
|
+
b.watch(() => sw.setValue?.(read()));
|
|
28098
|
+
const wrap = document.createElement("div");
|
|
28099
|
+
wrap.style.cssText = "display:flex;flex:1;justify-content:flex-end;";
|
|
28100
|
+
wrap.appendChild(sw.el);
|
|
28101
|
+
return wrap;
|
|
28102
|
+
}
|
|
28103
|
+
number(b, title, read, path, f) {
|
|
28104
|
+
const step = f.step ?? 1;
|
|
28105
|
+
const ni = buildFieldControl({
|
|
28106
|
+
kind: "number",
|
|
28107
|
+
value: read(),
|
|
28108
|
+
min: f.min,
|
|
28109
|
+
max: f.max,
|
|
28110
|
+
step,
|
|
28111
|
+
integer: step >= 1 && Number.isInteger(step),
|
|
28112
|
+
fill: false,
|
|
28113
|
+
commit: "blur",
|
|
28114
|
+
title,
|
|
28115
|
+
onChange: (v) => b.edit({ [path]: v })
|
|
28116
|
+
});
|
|
28117
|
+
const input = ni.el.querySelector("input");
|
|
28118
|
+
b.watch(() => {
|
|
28119
|
+
if (document.activeElement !== input) ni.setValue?.(read());
|
|
26526
28120
|
});
|
|
28121
|
+
return ni.el;
|
|
26527
28122
|
}
|
|
26528
28123
|
};
|
|
26529
|
-
function
|
|
28124
|
+
function block(title, ...rows) {
|
|
28125
|
+
const el = document.createElement("div");
|
|
28126
|
+
el.className = "vela-dsp-block";
|
|
28127
|
+
if (title) {
|
|
28128
|
+
const h = document.createElement("div");
|
|
28129
|
+
h.className = "vela-dsp-h";
|
|
28130
|
+
h.textContent = title;
|
|
28131
|
+
el.appendChild(h);
|
|
28132
|
+
}
|
|
28133
|
+
el.append(...rows);
|
|
28134
|
+
return el;
|
|
28135
|
+
}
|
|
28136
|
+
function row(label, ...controls) {
|
|
28137
|
+
const el = document.createElement("div");
|
|
28138
|
+
el.className = "vela-dsp-row";
|
|
28139
|
+
const lab = document.createElement("span");
|
|
28140
|
+
lab.className = "vela-dsp-lab";
|
|
28141
|
+
lab.textContent = label;
|
|
28142
|
+
lab.title = label;
|
|
28143
|
+
el.append(lab, ...controls);
|
|
28144
|
+
return el;
|
|
28145
|
+
}
|
|
28146
|
+
function chips(...items) {
|
|
28147
|
+
const el = document.createElement("div");
|
|
28148
|
+
el.className = "vela-dsp-chips";
|
|
28149
|
+
el.append(...items);
|
|
28150
|
+
return el;
|
|
28151
|
+
}
|
|
28152
|
+
function menuHeading(text) {
|
|
28153
|
+
const el = document.createElement("div");
|
|
28154
|
+
el.className = "vela-dsp-mh";
|
|
28155
|
+
el.textContent = text;
|
|
28156
|
+
return el;
|
|
28157
|
+
}
|
|
28158
|
+
function separator() {
|
|
28159
|
+
const el = document.createElement("div");
|
|
28160
|
+
el.className = "vela-dsp-msep";
|
|
28161
|
+
return el;
|
|
28162
|
+
}
|
|
28163
|
+
function button(label, primary, onClick) {
|
|
26530
28164
|
const b = document.createElement("button");
|
|
26531
28165
|
b.type = "button";
|
|
26532
28166
|
b.textContent = label;
|
|
@@ -26534,6 +28168,50 @@ function btn(label, primary, onClick) {
|
|
|
26534
28168
|
b.addEventListener("click", onClick);
|
|
26535
28169
|
return b;
|
|
26536
28170
|
}
|
|
28171
|
+
function sizeLabel(size) {
|
|
28172
|
+
return TEXT_SIZE_OPTIONS.find((o) => o.value === (size ?? "normal"))?.label ?? "Normal";
|
|
28173
|
+
}
|
|
28174
|
+
function readPath(d, path) {
|
|
28175
|
+
let cur = d;
|
|
28176
|
+
for (const k of path.split(".")) {
|
|
28177
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
28178
|
+
cur = cur[k];
|
|
28179
|
+
}
|
|
28180
|
+
return cur;
|
|
28181
|
+
}
|
|
28182
|
+
function pairBase(colorLabel, styleLabel3) {
|
|
28183
|
+
const c = /^(.*) colou?r$/i.exec(colorLabel);
|
|
28184
|
+
const st = /^(.*) style$/i.exec(styleLabel3);
|
|
28185
|
+
return c && st && c[1] === st[1] ? c[1] : null;
|
|
28186
|
+
}
|
|
28187
|
+
function upperLowerBase(upper, lower) {
|
|
28188
|
+
const u = /^Upper (.+)$/i.exec(upper);
|
|
28189
|
+
const l = /^Lower (.+)$/i.exec(lower);
|
|
28190
|
+
if (!u || !l || u[1] !== l[1]) return null;
|
|
28191
|
+
const name = u[1].replace(/ colou?r$/i, "");
|
|
28192
|
+
const word = name.charAt(0).toUpperCase() + name.slice(1);
|
|
28193
|
+
return /fill$/i.test(word) ? word : `${word}s`;
|
|
28194
|
+
}
|
|
28195
|
+
function modeOf(showOn) {
|
|
28196
|
+
if (!showOn) return "all";
|
|
28197
|
+
const same = (a) => a.length === showOn.length && a.every((k) => showOn.includes(k));
|
|
28198
|
+
if (same(INTRADAY)) return "intraday";
|
|
28199
|
+
if (same(HIGHER)) return "higher";
|
|
28200
|
+
return "custom";
|
|
28201
|
+
}
|
|
28202
|
+
function matchesTemplate(d, s) {
|
|
28203
|
+
const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
28204
|
+
const style = d.style;
|
|
28205
|
+
for (const [k, v] of Object.entries(s.style ?? {})) if (!same(style[k], v)) return false;
|
|
28206
|
+
const text = d.text ?? {};
|
|
28207
|
+
for (const [k, v] of Object.entries(s.text ?? {})) if (!same(text[k], v)) return false;
|
|
28208
|
+
const props = d.serialize().props ?? {};
|
|
28209
|
+
for (const [k, v] of Object.entries(s.props ?? {})) if (!same(props[k], v)) return false;
|
|
28210
|
+
return Object.keys(s.style ?? {}).length + Object.keys(s.text ?? {}).length + Object.keys(s.props ?? {}).length > 0;
|
|
28211
|
+
}
|
|
28212
|
+
function templateColor(s) {
|
|
28213
|
+
return s.style?.lineColor ?? s.style?.fillColor ?? s.text?.color;
|
|
28214
|
+
}
|
|
26537
28215
|
|
|
26538
28216
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
26539
28217
|
var MIXED = /* @__PURE__ */ Symbol("mixed");
|
|
@@ -26555,15 +28233,15 @@ function distinctValues(drawings, read) {
|
|
|
26555
28233
|
}
|
|
26556
28234
|
var BTN = 30;
|
|
26557
28235
|
var ICON = 21;
|
|
26558
|
-
var
|
|
26559
|
-
var
|
|
26560
|
-
function
|
|
28236
|
+
var STYLE_ID4 = "vela-drawing-popup-styles";
|
|
28237
|
+
var STYLE_REV2 = "4";
|
|
28238
|
+
function ensureStyles4() {
|
|
26561
28239
|
if (typeof document === "undefined") return;
|
|
26562
|
-
const existing = document.getElementById(
|
|
26563
|
-
if (existing?.dataset.rev ===
|
|
28240
|
+
const existing = document.getElementById(STYLE_ID4);
|
|
28241
|
+
if (existing?.dataset.rev === STYLE_REV2) return;
|
|
26564
28242
|
const s = existing ?? document.createElement("style");
|
|
26565
|
-
s.id =
|
|
26566
|
-
s.dataset.rev =
|
|
28243
|
+
s.id = STYLE_ID4;
|
|
28244
|
+
s.dataset.rev = STYLE_REV2;
|
|
26567
28245
|
s.textContent = `
|
|
26568
28246
|
.vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
26569
28247
|
.vela-dpop-btn:hover{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
@@ -26579,13 +28257,14 @@ function ensureStyles3() {
|
|
|
26579
28257
|
if (!existing) document.head.appendChild(s);
|
|
26580
28258
|
}
|
|
26581
28259
|
var DrawingSettingsPopup = class {
|
|
26582
|
-
constructor(host, theme,
|
|
28260
|
+
constructor(host, theme, env = {}) {
|
|
26583
28261
|
this.host = host;
|
|
26584
|
-
this.chartBarMs = chartBarMs;
|
|
26585
28262
|
this.el = null;
|
|
26586
28263
|
this.tipEl = null;
|
|
26587
28264
|
// floating hover-label (above/below the toolbar)
|
|
26588
28265
|
this.textPanel = null;
|
|
28266
|
+
/** The single drawing the bar is open on (a multi-selection has no settings panel). */
|
|
28267
|
+
this.current = null;
|
|
26589
28268
|
this.colorPop = null;
|
|
26590
28269
|
this.colorOwner = null;
|
|
26591
28270
|
this.menuPop = null;
|
|
@@ -26593,9 +28272,10 @@ var DrawingSettingsPopup = class {
|
|
|
26593
28272
|
this.onClose = null;
|
|
26594
28273
|
this.onOutside = (e) => {
|
|
26595
28274
|
const node = e.target;
|
|
26596
|
-
if (this.
|
|
26597
|
-
if (this.
|
|
26598
|
-
this.
|
|
28275
|
+
if (this.panel.isOpen()) {
|
|
28276
|
+
if (this.panel.contains(node) || this.isOwnChrome(node)) return;
|
|
28277
|
+
if (this.host.contains(node)) return;
|
|
28278
|
+
this.panel.close();
|
|
26599
28279
|
return;
|
|
26600
28280
|
}
|
|
26601
28281
|
if (this.isOwnChrome(node)) return;
|
|
@@ -26604,19 +28284,16 @@ var DrawingSettingsPopup = class {
|
|
|
26604
28284
|
cb?.(e);
|
|
26605
28285
|
};
|
|
26606
28286
|
this.theme = theme;
|
|
26607
|
-
this.
|
|
26608
|
-
|
|
26609
|
-
|
|
26610
|
-
|
|
26611
|
-
|
|
26612
|
-
|
|
26613
|
-
if (!(chartMs > 0)) return [...MAGNIFIER_TIMEFRAME_OPTIONS];
|
|
26614
|
-
const lower = MAGNIFIER_TIMEFRAME_OPTIONS.filter((o) => o.ms > 0 && o.ms < chartMs);
|
|
26615
|
-
return lower.length > 0 ? [MAGNIFIER_TIMEFRAME_OPTIONS[0], ...lower] : [];
|
|
28287
|
+
this.chartBarMs = env.chartBarMs ?? (() => 0);
|
|
28288
|
+
this.panel = new DrawingSettingsPanel(host, theme, {
|
|
28289
|
+
chartBarMs: this.chartBarMs,
|
|
28290
|
+
timeZone: env.timeZone ?? (() => "UTC"),
|
|
28291
|
+
priceDecimals: env.priceDecimals ?? (() => null)
|
|
28292
|
+
});
|
|
26616
28293
|
}
|
|
26617
28294
|
setTheme(theme) {
|
|
26618
28295
|
this.theme = theme;
|
|
26619
|
-
this.
|
|
28296
|
+
this.panel.setTheme(theme);
|
|
26620
28297
|
}
|
|
26621
28298
|
isOpen() {
|
|
26622
28299
|
return this.el != null;
|
|
@@ -26624,7 +28301,22 @@ var DrawingSettingsPopup = class {
|
|
|
26624
28301
|
/** Whether `node` belongs to this popup — the bar, its host-floated color/menu shells,
|
|
26625
28302
|
* or a kit popover (select list / color chip) portaled into the chart host. */
|
|
26626
28303
|
contains(node) {
|
|
26627
|
-
return node != null && (this.isOwnChrome(node) || this.
|
|
28304
|
+
return node != null && (this.isOwnChrome(node) || this.panel.contains(node));
|
|
28305
|
+
}
|
|
28306
|
+
/** Open the full settings panel for the drawing the bar is open on. False when there is
|
|
28307
|
+
* none (no bar, or a multi-selection). */
|
|
28308
|
+
openPanel() {
|
|
28309
|
+
const cur = this.current;
|
|
28310
|
+
if (!cur) return false;
|
|
28311
|
+
this.hideTip();
|
|
28312
|
+
this.closeColorPopover();
|
|
28313
|
+
this.closeMenu();
|
|
28314
|
+
this.panel.open(cur.drawing, cur.actions, cur.anchor);
|
|
28315
|
+
return true;
|
|
28316
|
+
}
|
|
28317
|
+
/** The drawings changed under the panel (a drag, an undo) — let it re-read them. */
|
|
28318
|
+
syncPanel() {
|
|
28319
|
+
this.panel.sync();
|
|
26628
28320
|
}
|
|
26629
28321
|
/** Open the quick toolbar for `drawings` (one, or a whole multi-selection), floating clear of
|
|
26630
28322
|
* their `anchor` box. With several drawings the bar shows only the controls they ALL support,
|
|
@@ -26634,10 +28326,12 @@ var DrawingSettingsPopup = class {
|
|
|
26634
28326
|
* programmatic close), with the dismissing press when there is one, so the caller can tell a
|
|
26635
28327
|
* modifier press (multi-select) from a plain one. */
|
|
26636
28328
|
open(drawings, anchor, actions, onClose) {
|
|
26637
|
-
this.close();
|
|
26638
28329
|
const drawing = drawings[0];
|
|
28330
|
+
const keepPanel = this.panel.isOpen() && drawings.length === 1 && drawing != null && this.current?.drawing.id === drawing.id;
|
|
28331
|
+
this.close(keepPanel);
|
|
26639
28332
|
if (!drawing) return;
|
|
26640
|
-
|
|
28333
|
+
this.current = drawings.length === 1 ? { drawing, actions, anchor } : null;
|
|
28334
|
+
ensureStyles4();
|
|
26641
28335
|
this.onClose = onClose ?? null;
|
|
26642
28336
|
const t = this.theme;
|
|
26643
28337
|
const multi = drawings.length > 1;
|
|
@@ -26665,11 +28359,11 @@ var DrawingSettingsPopup = class {
|
|
|
26665
28359
|
}
|
|
26666
28360
|
if (paths.has("size")) {
|
|
26667
28361
|
const sz = common((d) => d.size ?? "normal");
|
|
26668
|
-
bar.appendChild(this.dropdown("Icon size", STAMP_SIZE_OPTIONS, sz, (s) => stampSizeIcon(s), (v) => actions.patch({ size: v }), { label:
|
|
28362
|
+
bar.appendChild(this.dropdown("Icon size", STAMP_SIZE_OPTIONS, sz, (s) => stampSizeIcon(s), (v) => actions.patch({ size: v }), { label: sizeLabel2 }));
|
|
26669
28363
|
}
|
|
26670
28364
|
if (paths.has("magnifier.timeframe") && every((d) => d instanceof Magnifier)) {
|
|
26671
28365
|
const mag = (d) => d;
|
|
26672
|
-
const options = this.
|
|
28366
|
+
const options = magnifierTimeframesBelow(this.chartBarMs());
|
|
26673
28367
|
if (options.length > 0) {
|
|
26674
28368
|
bar.appendChild(
|
|
26675
28369
|
this.dropdown("Lower timeframe", options.map((o) => o.value), common((d) => mag(d).magnifier.timeframe), () => "", (v) => actions.patch({ "magnifier.timeframe": v }), {
|
|
@@ -26698,7 +28392,6 @@ var DrawingSettingsPopup = class {
|
|
|
26698
28392
|
}
|
|
26699
28393
|
if (paths.has("style.lineStyle")) bar.appendChild(this.dropdown("Line style", LINE_STYLE_OPTIONS.map((o) => o.value), common((d) => d.style.lineStyle), (s) => lineIcon(2, s), (v) => actions.patch({ "style.lineStyle": v }), { label: styleLabel2 }));
|
|
26700
28394
|
if (paths.has("style.fillColor")) bar.appendChild(swatch("Fill", BUCKET_ICON, (d) => effectiveFillColor(d, this.theme) ?? d.style.fillColor ?? DEFAULT_DRAWING_COLOR, "style.fillColor"));
|
|
26701
|
-
const isFrvp = paths.has("frvp.rows") && drawing instanceof FixedRangeVolumeProfile;
|
|
26702
28395
|
const isPosition = paths.has("riskPercent") && every((d) => d instanceof PositionTool);
|
|
26703
28396
|
if (isPosition) {
|
|
26704
28397
|
const pos = (d) => d;
|
|
@@ -26767,7 +28460,7 @@ var DrawingSettingsPopup = class {
|
|
|
26767
28460
|
if (paths.has("showPrice")) bar.appendChild(this.toggle("Show price", PRICE_DELTA_ICON, common((d) => flags(d).showPrice !== false), (v) => actions.patch({ showPrice: v })));
|
|
26768
28461
|
if (paths.has("showDate")) bar.appendChild(this.toggle("Show date", DATE_DELTA_ICON, common((d) => flags(d).showDate !== false), (v) => actions.patch({ showDate: v })));
|
|
26769
28462
|
if (paths.has("text.color") && textOnBar) bar.appendChild(swatch("Text color", TYPE_ICON, (d) => d.text?.color || t.textColor, "text.color"));
|
|
26770
|
-
if (paths.has("text.size") && textOnBar) bar.appendChild(this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), common((d) => d.text?.size ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ "text.size": v }), { label:
|
|
28463
|
+
if (paths.has("text.size") && textOnBar) bar.appendChild(this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), common((d) => d.text?.size ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ "text.size": v }), { label: sizeLabel2 }));
|
|
26771
28464
|
const textField = !multi && paths.has("text.value");
|
|
26772
28465
|
if (paths.has("text.bold") && !textField) bar.appendChild(this.toggle("Bold", BOLD_ICON, common((d) => !!d.text?.bold), (v) => actions.patch({ "text.bold": v })));
|
|
26773
28466
|
if (paths.has("text.italic") && !textField) bar.appendChild(this.toggle("Italic", ITALIC_ICON, common((d) => !!d.text?.italic), (v) => actions.patch({ "text.italic": v })));
|
|
@@ -26776,13 +28469,11 @@ var DrawingSettingsPopup = class {
|
|
|
26776
28469
|
if (editableLevels && every((d) => d.editableLevels() != null && !(d instanceof MachFigure))) {
|
|
26777
28470
|
const fib = (d) => d;
|
|
26778
28471
|
const sizes = TEXT_SIZE_OPTIONS.map((o) => o.value);
|
|
26779
|
-
bar.appendChild(this.dropdown("Numbers size", sizes, common((d) => fib(d).numbersSize ?? "small"), (s) => numbersSizeIcon(s), (v) => actions.patch({ numbersSize: v }), { label:
|
|
26780
|
-
bar.appendChild(this.dropdown("Label size", sizes, common((d) => fib(d).labelsSize ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ labelsSize: v }), { label:
|
|
28472
|
+
bar.appendChild(this.dropdown("Numbers size", sizes, common((d) => fib(d).numbersSize ?? "small"), (s) => numbersSizeIcon(s), (v) => actions.patch({ numbersSize: v }), { label: sizeLabel2 }));
|
|
28473
|
+
bar.appendChild(this.dropdown("Label size", sizes, common((d) => fib(d).labelsSize ?? "normal"), (s) => labelSizeIcon(s), (v) => actions.patch({ labelsSize: v }), { label: sizeLabel2 }));
|
|
26781
28474
|
}
|
|
26782
28475
|
bar.appendChild(this.divider());
|
|
26783
|
-
if (!multi
|
|
26784
|
-
if (!multi && isPosition) bar.appendChild(this.iconBtn("Position size", GEAR_ICON, () => this.settingsDialog.open(drawing, actions, "position")));
|
|
26785
|
-
if (!multi && editableLevels) bar.appendChild(this.iconBtn("Levels", GEAR_ICON, () => this.settingsDialog.open(drawing, actions, "levels")));
|
|
28476
|
+
if (!multi) bar.appendChild(this.iconBtn("Settings", GEAR_ICON, () => this.openPanel()));
|
|
26786
28477
|
bar.appendChild(this.toggle("Lock", LOCK_ICON, common((d) => d.locked), (v) => actions.setLocked(v), UNLOCK_ICON));
|
|
26787
28478
|
const del = this.iconBtn("Delete", TRASH_ICON, () => actions.remove());
|
|
26788
28479
|
del.style.color = "var(--vela-danger)";
|
|
@@ -26800,16 +28491,17 @@ var DrawingSettingsPopup = class {
|
|
|
26800
28491
|
this.position(anchor);
|
|
26801
28492
|
setTimeout(() => document.addEventListener("pointerdown", this.onOutside), 0);
|
|
26802
28493
|
}
|
|
26803
|
-
close() {
|
|
28494
|
+
close(keepPanel = false) {
|
|
26804
28495
|
document.removeEventListener("pointerdown", this.onOutside);
|
|
26805
28496
|
this.closeColorPopover();
|
|
26806
28497
|
this.closeMenu();
|
|
26807
28498
|
this.hideTip();
|
|
26808
|
-
this.
|
|
28499
|
+
if (!keepPanel) this.panel.close();
|
|
26809
28500
|
this.el?.remove();
|
|
26810
28501
|
this.el = null;
|
|
26811
28502
|
this.textPanel = null;
|
|
26812
28503
|
this.onClose = null;
|
|
28504
|
+
this.current = null;
|
|
26813
28505
|
}
|
|
26814
28506
|
destroy() {
|
|
26815
28507
|
this.close();
|
|
@@ -26865,7 +28557,7 @@ var DrawingSettingsPopup = class {
|
|
|
26865
28557
|
if (withColorAndSize) {
|
|
26866
28558
|
tools.append(
|
|
26867
28559
|
this.colorButton("Text color", TYPE_ICON, text?.color || this.theme.textColor, (v) => actions.patch({ "text.color": v }), 14),
|
|
26868
|
-
this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), text?.size ?? "normal", (s) => sizeIcon(s), (v) => actions.patch({ "text.size": v }), { label:
|
|
28560
|
+
this.dropdown("Text size", TEXT_SIZE_OPTIONS.map((o) => o.value), text?.size ?? "normal", (s) => sizeIcon(s), (v) => actions.patch({ "text.size": v }), { label: sizeLabel2 })
|
|
26869
28561
|
);
|
|
26870
28562
|
}
|
|
26871
28563
|
tools.append(
|
|
@@ -27039,9 +28731,9 @@ var DrawingSettingsPopup = class {
|
|
|
27039
28731
|
* quick toolbar — the chip works without selecting the drawing first.
|
|
27040
28732
|
*/
|
|
27041
28733
|
openMagnifierTimeframeMenu(rect, current, onPick) {
|
|
27042
|
-
|
|
28734
|
+
ensureStyles4();
|
|
27043
28735
|
closeOpenPopovers();
|
|
27044
|
-
const options = this.
|
|
28736
|
+
const options = magnifierTimeframesBelow(this.chartBarMs());
|
|
27045
28737
|
const anchor = document.createElement("div");
|
|
27046
28738
|
anchor.style.cssText = `position:absolute;left:${rect.x}px;top:${rect.y}px;width:${rect.w}px;height:${rect.h}px;pointer-events:none;`;
|
|
27047
28739
|
this.host.appendChild(anchor);
|
|
@@ -27082,22 +28774,22 @@ var DrawingSettingsPopup = class {
|
|
|
27082
28774
|
zIndex: 26,
|
|
27083
28775
|
padding: "4px",
|
|
27084
28776
|
fill: (menu2, pop) => {
|
|
27085
|
-
for (const
|
|
28777
|
+
for (const row2 of rows) {
|
|
27086
28778
|
const item = document.createElement("button");
|
|
27087
28779
|
item.type = "button";
|
|
27088
28780
|
item.className = "vela-dpop-item";
|
|
27089
28781
|
item.style.cssText = "display:flex;align-items:center;gap:9px;min-width:150px;padding:6px 9px;border:none;border-radius:5px;color:inherit;cursor:pointer;text-align:left;font:inherit;";
|
|
27090
28782
|
const ic = document.createElement("span");
|
|
27091
28783
|
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
27092
|
-
ic.innerHTML = sized(
|
|
28784
|
+
ic.innerHTML = sized(row2.icon, 18);
|
|
27093
28785
|
const tx = document.createElement("span");
|
|
27094
|
-
tx.textContent =
|
|
28786
|
+
tx.textContent = row2.label;
|
|
27095
28787
|
tx.style.cssText = "flex:1;";
|
|
27096
28788
|
item.append(ic, tx);
|
|
27097
28789
|
item.addEventListener("click", (e) => {
|
|
27098
28790
|
e.stopPropagation();
|
|
27099
28791
|
pop.hide();
|
|
27100
|
-
|
|
28792
|
+
row2.onClick();
|
|
27101
28793
|
});
|
|
27102
28794
|
menu2.appendChild(item);
|
|
27103
28795
|
}
|
|
@@ -27322,12 +29014,12 @@ var DrawingSettingsPopup = class {
|
|
|
27322
29014
|
padding: "10px",
|
|
27323
29015
|
fill: (el, pop) => {
|
|
27324
29016
|
if (used.length > 1) {
|
|
27325
|
-
const
|
|
27326
|
-
|
|
29017
|
+
const row2 = document.createElement("div");
|
|
29018
|
+
row2.style.cssText = "display:flex;align-items:center;gap:6px;padding-bottom:9px;border-bottom:1px solid var(--vela-border);";
|
|
27327
29019
|
const lbl = document.createElement("span");
|
|
27328
29020
|
lbl.textContent = "In use";
|
|
27329
29021
|
lbl.style.cssText = "font:var(--vela-font-size-sm) inherit;opacity:0.7;margin-right:2px;";
|
|
27330
|
-
|
|
29022
|
+
row2.appendChild(lbl);
|
|
27331
29023
|
for (const c of used) {
|
|
27332
29024
|
const sw = document.createElement("button");
|
|
27333
29025
|
sw.type = "button";
|
|
@@ -27338,9 +29030,9 @@ var DrawingSettingsPopup = class {
|
|
|
27338
29030
|
pop.hide();
|
|
27339
29031
|
onChange(c);
|
|
27340
29032
|
});
|
|
27341
|
-
|
|
29033
|
+
row2.appendChild(sw);
|
|
27342
29034
|
}
|
|
27343
|
-
el.appendChild(
|
|
29035
|
+
el.appendChild(row2);
|
|
27344
29036
|
}
|
|
27345
29037
|
el.appendChild(buildColorPicker(color, t, onChange));
|
|
27346
29038
|
}
|
|
@@ -27414,7 +29106,7 @@ function labelSizeIcon(size) {
|
|
|
27414
29106
|
function capitalize(s) {
|
|
27415
29107
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
27416
29108
|
}
|
|
27417
|
-
function
|
|
29109
|
+
function sizeLabel2(v) {
|
|
27418
29110
|
return TEXT_SIZE_OPTIONS.find((o) => o.value === v)?.label ?? capitalize(String(v));
|
|
27419
29111
|
}
|
|
27420
29112
|
function styleLabel2(v) {
|
|
@@ -27495,7 +29187,7 @@ var DrawingToolbar = class {
|
|
|
27495
29187
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
27496
29188
|
});
|
|
27497
29189
|
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
27498
|
-
|
|
29190
|
+
ensureStyles5();
|
|
27499
29191
|
this.root = document.createElement("div");
|
|
27500
29192
|
this.root.className = "vela-dtb";
|
|
27501
29193
|
this.styleRoot();
|
|
@@ -27988,17 +29680,17 @@ var DrawingToolbar = class {
|
|
|
27988
29680
|
this.paintStay();
|
|
27989
29681
|
}
|
|
27990
29682
|
makeButton(icon2, title, onClick) {
|
|
27991
|
-
const
|
|
27992
|
-
|
|
27993
|
-
|
|
27994
|
-
|
|
27995
|
-
this.tipText.set(
|
|
27996
|
-
if (icon2)
|
|
29683
|
+
const btn = document.createElement("button");
|
|
29684
|
+
btn.type = "button";
|
|
29685
|
+
btn.className = "vela-dtb-btn";
|
|
29686
|
+
btn.setAttribute("aria-label", title);
|
|
29687
|
+
this.tipText.set(btn, title);
|
|
29688
|
+
if (icon2) btn.innerHTML = hitHtml(icon2);
|
|
27997
29689
|
this.tipDisposers.push(
|
|
27998
|
-
attachChromeTooltip(
|
|
29690
|
+
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right" })
|
|
27999
29691
|
);
|
|
28000
|
-
|
|
28001
|
-
return
|
|
29692
|
+
btn.addEventListener("click", onClick);
|
|
29693
|
+
return btn;
|
|
28002
29694
|
}
|
|
28003
29695
|
divider() {
|
|
28004
29696
|
const d = document.createElement("div");
|
|
@@ -28007,13 +29699,13 @@ var DrawingToolbar = class {
|
|
|
28007
29699
|
}
|
|
28008
29700
|
};
|
|
28009
29701
|
var MAGNET_ID = "__magnet__";
|
|
28010
|
-
var
|
|
28011
|
-
function
|
|
29702
|
+
var STYLE_ID5 = "vela-drawing-toolbar-styles";
|
|
29703
|
+
function ensureStyles5() {
|
|
28012
29704
|
if (typeof document === "undefined") return;
|
|
28013
|
-
let s = document.getElementById(
|
|
29705
|
+
let s = document.getElementById(STYLE_ID5);
|
|
28014
29706
|
if (!s) {
|
|
28015
29707
|
s = document.createElement("style");
|
|
28016
|
-
s.id =
|
|
29708
|
+
s.id = STYLE_ID5;
|
|
28017
29709
|
document.head.appendChild(s);
|
|
28018
29710
|
}
|
|
28019
29711
|
s.textContent = `
|
|
@@ -28299,7 +29991,9 @@ var UserDrawingController = class {
|
|
|
28299
29991
|
this.hoveredId = null;
|
|
28300
29992
|
// the drawing under the cursor (its handles show)
|
|
28301
29993
|
this.activeTool = null;
|
|
28302
|
-
//
|
|
29994
|
+
// the armed tool's remembered settings (seed the placement ghost)
|
|
29995
|
+
this.toolTemplates = {};
|
|
29996
|
+
// saved styles, per tool
|
|
28303
29997
|
this.intentCb = null;
|
|
28304
29998
|
/** Another chart's in-progress placement, mirrored here as a ghost (drawings sync). */
|
|
28305
29999
|
this.externalGhost = null;
|
|
@@ -28347,7 +30041,11 @@ var UserDrawingController = class {
|
|
|
28347
30041
|
this.clearSelection();
|
|
28348
30042
|
};
|
|
28349
30043
|
this.ctx = canvas.getContext("2d");
|
|
28350
|
-
this.popup = new DrawingSettingsPopup(overlayHost, deps.theme(),
|
|
30044
|
+
this.popup = new DrawingSettingsPopup(overlayHost, deps.theme(), {
|
|
30045
|
+
chartBarMs: () => deps.chartBarMs(),
|
|
30046
|
+
timeZone: () => deps.timeZone?.() ?? "UTC",
|
|
30047
|
+
priceDecimals: () => deps.priceDecimals?.() ?? null
|
|
30048
|
+
});
|
|
28351
30049
|
this.toolbar = new DrawingToolbar(
|
|
28352
30050
|
toolbarHost,
|
|
28353
30051
|
deps.theme(),
|
|
@@ -28370,7 +30068,7 @@ var UserDrawingController = class {
|
|
|
28370
30068
|
this.interaction = new DrawingInteraction({
|
|
28371
30069
|
projector: () => this.deps.projector(),
|
|
28372
30070
|
activeTool: () => this.activeTool,
|
|
28373
|
-
drawings: () => this.
|
|
30071
|
+
drawings: () => this.onScreen(),
|
|
28374
30072
|
hoveredId: () => this.hoveredId,
|
|
28375
30073
|
selectedIds: () => this.selectedIds,
|
|
28376
30074
|
emit: (i) => this.emit(i),
|
|
@@ -28380,7 +30078,7 @@ var UserDrawingController = class {
|
|
|
28380
30078
|
},
|
|
28381
30079
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
28382
30080
|
snap: (pt, paneId, mode, cursorPx) => this.deps.snap(pt, paneId, mode, cursorPx),
|
|
28383
|
-
|
|
30081
|
+
toolDefaults: () => this.activeToolDefaults
|
|
28384
30082
|
});
|
|
28385
30083
|
}
|
|
28386
30084
|
// ── IDrawingsRendererPort (commands down) ──
|
|
@@ -28432,6 +30130,7 @@ var UserDrawingController = class {
|
|
|
28432
30130
|
this.invalidateSlices();
|
|
28433
30131
|
this.render();
|
|
28434
30132
|
this.deps.requestScaleUpdate();
|
|
30133
|
+
this.popup.syncPanel();
|
|
28435
30134
|
}
|
|
28436
30135
|
/** The pane's series stack in z terms — how the core places a new drawing (just under
|
|
28437
30136
|
* `price`) and computes "front of everything" / "behind everything" for the reorders. */
|
|
@@ -28459,7 +30158,7 @@ var UserDrawingController = class {
|
|
|
28459
30158
|
}
|
|
28460
30159
|
return lo <= hi ? { min: lo, max: hi } : null;
|
|
28461
30160
|
}
|
|
28462
|
-
setActiveTool(type, lastStyle) {
|
|
30161
|
+
setActiveTool(type, lastStyle, defaults5) {
|
|
28463
30162
|
if (type != null) {
|
|
28464
30163
|
this.finishTextEditor(true);
|
|
28465
30164
|
this.withModeIntent(() => {
|
|
@@ -28468,7 +30167,7 @@ var UserDrawingController = class {
|
|
|
28468
30167
|
});
|
|
28469
30168
|
}
|
|
28470
30169
|
this.activeTool = type;
|
|
28471
|
-
this.
|
|
30170
|
+
this.activeToolDefaults = defaults5 ?? (lastStyle ? { style: lastStyle } : void 0);
|
|
28472
30171
|
this.toolbar.setActiveTool(type);
|
|
28473
30172
|
if (type == null) this.interaction.onToolCleared();
|
|
28474
30173
|
else this.clearSelection();
|
|
@@ -28478,6 +30177,10 @@ var UserDrawingController = class {
|
|
|
28478
30177
|
setFavorites(types) {
|
|
28479
30178
|
this.toolbar.setFavorites(types);
|
|
28480
30179
|
}
|
|
30180
|
+
/** Core push: every tool's saved styles — the settings panel's style menu reads them. */
|
|
30181
|
+
setToolTemplates(map2) {
|
|
30182
|
+
this.toolTemplates = map2;
|
|
30183
|
+
}
|
|
28481
30184
|
/** Core push: per-tool shortcut hints (display strings) shown in the toolbar flyouts. */
|
|
28482
30185
|
setToolShortcuts(map2) {
|
|
28483
30186
|
this.toolbar.setShortcuts(map2);
|
|
@@ -28550,8 +30253,9 @@ var UserDrawingController = class {
|
|
|
28550
30253
|
/** The topmost visible (unlocked) magnifier whose timeframe chip contains (x, y) —
|
|
28551
30254
|
* the chip's rect is what the painter measured last frame. */
|
|
28552
30255
|
magnifierChipAt(x, y) {
|
|
28553
|
-
|
|
28554
|
-
|
|
30256
|
+
const shown = this.onScreen();
|
|
30257
|
+
for (let i = shown.length - 1; i >= 0; i -= 1) {
|
|
30258
|
+
const d = shown[i];
|
|
28555
30259
|
if (!(d instanceof Magnifier) || !d.visible || d.locked) continue;
|
|
28556
30260
|
const r = d.chipRect;
|
|
28557
30261
|
if (r && x >= r.x && x <= r.x + r.w && y >= r.y && y <= r.y + r.h) return d;
|
|
@@ -28577,7 +30281,7 @@ var UserDrawingController = class {
|
|
|
28577
30281
|
* `withSelection` so a hit on a member of a multi-selection removes the selection's
|
|
28578
30282
|
* unlocked members — whichever member was hit, locked or not. */
|
|
28579
30283
|
deleteAt(x, y, withSelection = false) {
|
|
28580
|
-
const hit = topDrawingAt(this.
|
|
30284
|
+
const hit = topDrawingAt(this.onScreen(), x, y, this.deps.projector(), HIT_TOLERANCE);
|
|
28581
30285
|
if (!hit) return false;
|
|
28582
30286
|
const ids = deleteTargets(hit, this.selectedIds, this.drawings, withSelection);
|
|
28583
30287
|
if (ids.length === 0) return false;
|
|
@@ -28657,7 +30361,7 @@ var UserDrawingController = class {
|
|
|
28657
30361
|
updateHover(x, y, mod = false) {
|
|
28658
30362
|
let id = null;
|
|
28659
30363
|
if (!mod && this.activeTool == null && !this.interaction.isPlacing() && !this.interaction.isDragging()) {
|
|
28660
|
-
id = topDrawingAt(this.
|
|
30364
|
+
id = topDrawingAt(this.onScreen(), x, y, this.deps.projector(), HIT_TOLERANCE)?.id ?? null;
|
|
28661
30365
|
}
|
|
28662
30366
|
if (id !== this.hoveredId) {
|
|
28663
30367
|
this.hoveredId = id;
|
|
@@ -28892,16 +30596,20 @@ var UserDrawingController = class {
|
|
|
28892
30596
|
}
|
|
28893
30597
|
return false;
|
|
28894
30598
|
}
|
|
28895
|
-
/** Double-click over a drawing
|
|
28896
|
-
*
|
|
30599
|
+
/** Double-click over a drawing opens its full settings panel (a callout or text label edits
|
|
30600
|
+
* its words in place instead) and suppresses the chart's view reset. Returns true only
|
|
30601
|
+
* when a drawing is under the cursor. */
|
|
28897
30602
|
dblClick(x, y) {
|
|
28898
30603
|
if (this.interaction.finishPlacing(true)) return true;
|
|
28899
|
-
const hit = topDrawingAt(this.
|
|
30604
|
+
const hit = topDrawingAt(this.onScreen(), x, y, this.deps.projector(), HIT_TOLERANCE);
|
|
28900
30605
|
if (isInlineEditable(hit)) {
|
|
28901
30606
|
this.editTextInline(hit.id);
|
|
28902
30607
|
return true;
|
|
28903
30608
|
}
|
|
28904
|
-
|
|
30609
|
+
if (!hit) return false;
|
|
30610
|
+
if (this.popupMulti || !this.selectedIds.has(hit.id) || !this.popup.isOpen()) this.openSettingsById(hit.id, 0, 0);
|
|
30611
|
+
this.popup.openPanel();
|
|
30612
|
+
return true;
|
|
28905
30613
|
}
|
|
28906
30614
|
/** Keyboard pre-empt: Escape (popup/placing/selection), undo/redo, copy/paste/duplicate,
|
|
28907
30615
|
* delete (multi), and arrow-nudge. Stands down while a label text field is focused. */
|
|
@@ -28999,6 +30707,13 @@ var UserDrawingController = class {
|
|
|
28999
30707
|
this.popup.close();
|
|
29000
30708
|
this.render();
|
|
29001
30709
|
}
|
|
30710
|
+
/** The drawings shown on the chart's current timeframe. One limited to other timeframes
|
|
30711
|
+
* ("show on") is neither painted nor clickable here, but stays in the model: the object
|
|
30712
|
+
* tree still lists it, and its own visibility flag is untouched. */
|
|
30713
|
+
onScreen() {
|
|
30714
|
+
const barMs = this.deps.chartBarMs();
|
|
30715
|
+
return this.drawings.filter((d) => shownOnTimeframe(d.showOn, barMs));
|
|
30716
|
+
}
|
|
29002
30717
|
/** Whether the drawing's z puts it INSIDE the series stack (per the last-known boundaries)
|
|
29003
30718
|
* rather than over it — i.e. its body belongs to an interleave layer, not the top canvas. */
|
|
29004
30719
|
isInterleaved(d) {
|
|
@@ -29038,7 +30753,7 @@ var UserDrawingController = class {
|
|
|
29038
30753
|
const proj = this.deps.projector();
|
|
29039
30754
|
const theme = this.deps.theme();
|
|
29040
30755
|
const buckets = /* @__PURE__ */ new Map();
|
|
29041
|
-
for (const d of this.
|
|
30756
|
+
for (const d of this.onScreen()) {
|
|
29042
30757
|
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
29043
30758
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
29044
30759
|
if (beforeZ === null) continue;
|
|
@@ -29092,8 +30807,9 @@ var UserDrawingController = class {
|
|
|
29092
30807
|
};
|
|
29093
30808
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
29094
30809
|
const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
|
|
29095
|
-
|
|
29096
|
-
this.painter.
|
|
30810
|
+
const shown = this.onScreen();
|
|
30811
|
+
this.painter.paintAll(ctx, shown.filter(onTop), proj, this.deps.theme(), targets);
|
|
30812
|
+
this.painter.paintHighlights(ctx, shown.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
|
|
29097
30813
|
const clones = this.interaction.dragClones();
|
|
29098
30814
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
29099
30815
|
this.layoutTextEditor();
|
|
@@ -29203,6 +30919,7 @@ var UserDrawingController = class {
|
|
|
29203
30919
|
this.emit({ kind: "select", ids: [id] });
|
|
29204
30920
|
this.emit({ kind: "settings", id });
|
|
29205
30921
|
const anchor = drawing.bounds(this.deps.projector());
|
|
30922
|
+
let previewBase = null;
|
|
29206
30923
|
this.popup.open([drawing], anchor, {
|
|
29207
30924
|
// Sync rebuilds instances, so a panel that reads values back after a patch (e.g. the
|
|
29208
30925
|
// position tool's price fields, where one edit can flip another level) resolves fresh.
|
|
@@ -29237,11 +30954,39 @@ var UserDrawingController = class {
|
|
|
29237
30954
|
restore: (doc) => {
|
|
29238
30955
|
const d = live();
|
|
29239
30956
|
if (!d) return;
|
|
29240
|
-
|
|
29241
|
-
|
|
29242
|
-
|
|
30957
|
+
const text = doc.text ?? (d.text ? { ...d.text, value: "" } : void 0);
|
|
30958
|
+
restoreLook(d, { ...doc, text });
|
|
30959
|
+
d.anchors = doc.anchors.map((a) => ({ ...a }));
|
|
30960
|
+
d.showOn = doc.showOn ? [...doc.showOn] : void 0;
|
|
30961
|
+
this.render();
|
|
30962
|
+
this.emit({ kind: "edit", doc: { ...doc, text } });
|
|
30963
|
+
},
|
|
30964
|
+
templates: () => this.toolTemplates[drawing.type] ?? [],
|
|
30965
|
+
saveTemplate: (name) => {
|
|
30966
|
+
const d = live();
|
|
30967
|
+
if (d) this.emit({ kind: "template-save", type: d.type, name, settings: captureToolDefaults(d) });
|
|
30968
|
+
},
|
|
30969
|
+
removeTemplate: (name) => this.emit({ kind: "template-remove", type: drawing.type, name }),
|
|
30970
|
+
applyToolSettings: (settings) => {
|
|
30971
|
+
const d = live();
|
|
30972
|
+
if (!d) return;
|
|
30973
|
+
applyToolDefaults(d, settings);
|
|
30974
|
+
this.render();
|
|
30975
|
+
this.emit({ kind: "edit", doc: d.serialize() });
|
|
30976
|
+
},
|
|
30977
|
+
// A hovered style paints on the drawing without an edit; leaving puts the look back.
|
|
30978
|
+
preview: (settings) => {
|
|
30979
|
+
const d = live();
|
|
30980
|
+
if (!d) return;
|
|
30981
|
+
if (settings) {
|
|
30982
|
+
if (previewBase) restoreLook(d, previewBase);
|
|
30983
|
+
else previewBase = d.serialize();
|
|
30984
|
+
applyToolDefaults(d, settings);
|
|
30985
|
+
} else if (previewBase) {
|
|
30986
|
+
restoreLook(d, previewBase);
|
|
30987
|
+
previewBase = null;
|
|
30988
|
+
} else return;
|
|
29243
30989
|
this.render();
|
|
29244
|
-
this.emit({ kind: "edit", doc });
|
|
29245
30990
|
},
|
|
29246
30991
|
remove: () => {
|
|
29247
30992
|
this.closeTextEditor();
|
|
@@ -29290,6 +31035,11 @@ var UserDrawingController = class {
|
|
|
29290
31035
|
this.intentCb?.(i);
|
|
29291
31036
|
}
|
|
29292
31037
|
};
|
|
31038
|
+
function restoreLook(d, doc) {
|
|
31039
|
+
d.style = { ...doc.style };
|
|
31040
|
+
d.text = doc.text ? { ...doc.text } : void 0;
|
|
31041
|
+
if (doc.props !== void 0) d.applyProps(doc.props);
|
|
31042
|
+
}
|
|
29293
31043
|
|
|
29294
31044
|
// src/renderers/shared/TableOverlay.ts
|
|
29295
31045
|
var SIZE_PX4 = {
|
|
@@ -29305,7 +31055,7 @@ function fontPxOf(size) {
|
|
|
29305
31055
|
return SIZE_PX4[size] ?? SIZE_PX4.auto;
|
|
29306
31056
|
}
|
|
29307
31057
|
function tableHasContent(t) {
|
|
29308
|
-
return t.cells.some((
|
|
31058
|
+
return t.cells.some((row2) => row2?.some((c) => c != null && !c.merged));
|
|
29309
31059
|
}
|
|
29310
31060
|
function mergeRenderPlan(t) {
|
|
29311
31061
|
const span = /* @__PURE__ */ new Map();
|
|
@@ -30066,15 +31816,15 @@ var MarkPopover = class {
|
|
|
30066
31816
|
actions.className = "vela-callout-actions";
|
|
30067
31817
|
section.appendChild(actions);
|
|
30068
31818
|
}
|
|
30069
|
-
const
|
|
30070
|
-
|
|
30071
|
-
|
|
30072
|
-
|
|
30073
|
-
|
|
31819
|
+
const btn = doc.createElement("button");
|
|
31820
|
+
btn.type = "button";
|
|
31821
|
+
btn.className = "vela-callout-btn" + (item.primary ? " vela-callout-btn-primary" : "");
|
|
31822
|
+
btn.textContent = item.label;
|
|
31823
|
+
btn.addEventListener("click", () => {
|
|
30074
31824
|
item.run();
|
|
30075
31825
|
if (item.close !== false) this.close();
|
|
30076
31826
|
});
|
|
30077
|
-
actions.appendChild(
|
|
31827
|
+
actions.appendChild(btn);
|
|
30078
31828
|
continue;
|
|
30079
31829
|
}
|
|
30080
31830
|
actions = null;
|
|
@@ -30084,16 +31834,16 @@ var MarkPopover = class {
|
|
|
30084
31834
|
text.textContent = item.text;
|
|
30085
31835
|
section.appendChild(text);
|
|
30086
31836
|
} else if (item.type === "field") {
|
|
30087
|
-
const
|
|
30088
|
-
|
|
31837
|
+
const row2 = doc.createElement("div");
|
|
31838
|
+
row2.className = "vela-marks-field";
|
|
30089
31839
|
const label = doc.createElement("span");
|
|
30090
31840
|
label.className = "vela-marks-field-label";
|
|
30091
31841
|
label.textContent = item.label;
|
|
30092
31842
|
const value = doc.createElement("span");
|
|
30093
31843
|
value.className = "vela-marks-field-value";
|
|
30094
31844
|
value.textContent = item.value;
|
|
30095
|
-
|
|
30096
|
-
section.appendChild(
|
|
31845
|
+
row2.append(label, value);
|
|
31846
|
+
section.appendChild(row2);
|
|
30097
31847
|
}
|
|
30098
31848
|
}
|
|
30099
31849
|
}
|
|
@@ -30378,7 +32128,7 @@ var VELA_LETTERS_SVG = '<svg viewBox="161.66 -142 310.57 148" aria-hidden="true"
|
|
|
30378
32128
|
|
|
30379
32129
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
30380
32130
|
var ATTRIBUTION_URL = "https://velacharts.dev/?utm_source=vela&utm_medium=attribution";
|
|
30381
|
-
var
|
|
32131
|
+
var STYLE_ID6 = "vela-attribution-styles";
|
|
30382
32132
|
var CSS2 = `
|
|
30383
32133
|
.vela-attribution {
|
|
30384
32134
|
text-decoration: none;
|
|
@@ -30434,11 +32184,11 @@ var CSS2 = `
|
|
|
30434
32184
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
30435
32185
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
30436
32186
|
`;
|
|
30437
|
-
function
|
|
30438
|
-
let s = doc.getElementById(
|
|
32187
|
+
function ensureStyles6(doc) {
|
|
32188
|
+
let s = doc.getElementById(STYLE_ID6);
|
|
30439
32189
|
if (!s) {
|
|
30440
32190
|
s = doc.createElement("style");
|
|
30441
|
-
s.id =
|
|
32191
|
+
s.id = STYLE_ID6;
|
|
30442
32192
|
doc.head.appendChild(s);
|
|
30443
32193
|
}
|
|
30444
32194
|
s.textContent = CSS2;
|
|
@@ -30468,7 +32218,7 @@ function createCustomMark(doc, html, background) {
|
|
|
30468
32218
|
return el;
|
|
30469
32219
|
}
|
|
30470
32220
|
function createAttributionMark(doc, background) {
|
|
30471
|
-
|
|
32221
|
+
ensureStyles6(doc);
|
|
30472
32222
|
const a = doc.createElement("a");
|
|
30473
32223
|
a.className = "vela-attribution";
|
|
30474
32224
|
a.href = ATTRIBUTION_URL;
|
|
@@ -30741,13 +32491,13 @@ function paintVpvr(ctx, profile, geom, style) {
|
|
|
30741
32491
|
const { rightX, maxW, yOf } = geom;
|
|
30742
32492
|
if (maxTotal <= 0 || maxW <= 0) return;
|
|
30743
32493
|
for (let k = 0; k < rows.length; k += 1) {
|
|
30744
|
-
const
|
|
30745
|
-
const total =
|
|
32494
|
+
const row2 = rows[k];
|
|
32495
|
+
const total = row2.up + row2.down;
|
|
30746
32496
|
if (total <= 0) continue;
|
|
30747
|
-
const yTop = yOf(
|
|
30748
|
-
const h = Math.max(1, yOf(
|
|
32497
|
+
const yTop = yOf(row2.price + rowH);
|
|
32498
|
+
const h = Math.max(1, yOf(row2.price) - yTop - 1);
|
|
30749
32499
|
const w = total / maxTotal * maxW;
|
|
30750
|
-
const upW =
|
|
32500
|
+
const upW = row2.up / total * w;
|
|
30751
32501
|
ctx.globalAlpha = k >= vaFrom && k <= vaTo ? VPVR_VA_ALPHA : VPVR_OUTSIDE_ALPHA;
|
|
30752
32502
|
if (upW > 0) {
|
|
30753
32503
|
ctx.fillStyle = style.upColor;
|
|
@@ -30759,9 +32509,9 @@ function paintVpvr(ctx, profile, geom, style) {
|
|
|
30759
32509
|
}
|
|
30760
32510
|
}
|
|
30761
32511
|
if (style.showPoc) {
|
|
30762
|
-
const
|
|
30763
|
-
const yTop = yOf(
|
|
30764
|
-
const h = Math.max(1, yOf(
|
|
32512
|
+
const row2 = rows[poc];
|
|
32513
|
+
const yTop = yOf(row2.price + rowH);
|
|
32514
|
+
const h = Math.max(1, yOf(row2.price) - yTop - 1);
|
|
30765
32515
|
ctx.globalAlpha = VPVR_POC_ALPHA;
|
|
30766
32516
|
ctx.strokeStyle = style.pocColor;
|
|
30767
32517
|
ctx.lineWidth = 1;
|
|
@@ -31727,11 +33477,11 @@ var NativeRenderer = class {
|
|
|
31727
33477
|
}
|
|
31728
33478
|
}
|
|
31729
33479
|
makeSettingsButton() {
|
|
31730
|
-
const
|
|
31731
|
-
|
|
31732
|
-
|
|
31733
|
-
|
|
31734
|
-
Object.assign(
|
|
33480
|
+
const btn = document.createElement("button");
|
|
33481
|
+
btn.type = "button";
|
|
33482
|
+
btn.title = "Chart settings";
|
|
33483
|
+
btn.innerHTML = iconAt("gear", 14);
|
|
33484
|
+
Object.assign(btn.style, {
|
|
31735
33485
|
position: "absolute",
|
|
31736
33486
|
bottom: `${TIME_AXIS_H + 10}px`,
|
|
31737
33487
|
right: `${RIGHT_AXIS_W + 10}px`,
|
|
@@ -31752,10 +33502,10 @@ var NativeRenderer = class {
|
|
|
31752
33502
|
pointerEvents: "auto",
|
|
31753
33503
|
padding: "0"
|
|
31754
33504
|
});
|
|
31755
|
-
|
|
31756
|
-
|
|
31757
|
-
|
|
31758
|
-
return
|
|
33505
|
+
btn.addEventListener("mouseenter", () => btn.style.opacity = "1");
|
|
33506
|
+
btn.addEventListener("mouseleave", () => btn.style.opacity = "0.75");
|
|
33507
|
+
btn.addEventListener("click", () => this.toggleSettingsDialog());
|
|
33508
|
+
return btn;
|
|
31759
33509
|
}
|
|
31760
33510
|
/** Port surface: hosts (bottom-bar / chrome buttons) open the same dialog as the
|
|
31761
33511
|
* in-chart gear — created on demand, independent of the gear feature being enabled. */
|
|
@@ -31820,12 +33570,12 @@ var NativeRenderer = class {
|
|
|
31820
33570
|
}
|
|
31821
33571
|
/** Jump-back-to-latest button — same size/border chrome as the drawing-toolbar collapse toggle. */
|
|
31822
33572
|
makeScrollToRealtimeButton() {
|
|
31823
|
-
const
|
|
31824
|
-
|
|
31825
|
-
|
|
31826
|
-
|
|
31827
|
-
|
|
31828
|
-
Object.assign(
|
|
33573
|
+
const btn = document.createElement("button");
|
|
33574
|
+
btn.type = "button";
|
|
33575
|
+
btn.title = "Scroll to most recent bar (Alt+Shift+\u2192)";
|
|
33576
|
+
btn.setAttribute("aria-label", "Scroll to most recent bar");
|
|
33577
|
+
btn.innerHTML = this.scrollButtonIcon();
|
|
33578
|
+
Object.assign(btn.style, {
|
|
31829
33579
|
position: "absolute",
|
|
31830
33580
|
bottom: `${SCROLL_BTN_BOTTOM}px`,
|
|
31831
33581
|
right: `${SCROLL_BTN_RIGHT}px`,
|
|
@@ -31848,10 +33598,10 @@ var NativeRenderer = class {
|
|
|
31848
33598
|
pointerEvents: "auto",
|
|
31849
33599
|
padding: "0"
|
|
31850
33600
|
});
|
|
31851
|
-
|
|
31852
|
-
|
|
31853
|
-
|
|
31854
|
-
return
|
|
33601
|
+
btn.addEventListener("mouseenter", () => btn.style.opacity = "1");
|
|
33602
|
+
btn.addEventListener("mouseleave", () => btn.style.opacity = "0.85");
|
|
33603
|
+
btn.addEventListener("click", () => this.scrollToRealtime());
|
|
33604
|
+
return btn;
|
|
31855
33605
|
}
|
|
31856
33606
|
/** Double-chevron icon — the "jump back to the latest bar" affordance. */
|
|
31857
33607
|
scrollButtonIcon() {
|
|
@@ -31874,14 +33624,14 @@ var NativeRenderer = class {
|
|
|
31874
33624
|
* appears whenever the latest bars are off-screen and hides again once the view is back
|
|
31875
33625
|
* at the right edge. */
|
|
31876
33626
|
updateScrollToRealtimeButton() {
|
|
31877
|
-
const
|
|
31878
|
-
if (!
|
|
33627
|
+
const btn = this.scrollButton;
|
|
33628
|
+
if (!btn) return;
|
|
31879
33629
|
const latestOffScreen = this.coords.barCount > 0 && this.coords.getViewport().rightOffset < 0;
|
|
31880
33630
|
if (this.layoutMode === "mobile") {
|
|
31881
|
-
|
|
33631
|
+
btn.style.display = latestOffScreen ? "flex" : "none";
|
|
31882
33632
|
return;
|
|
31883
33633
|
}
|
|
31884
|
-
|
|
33634
|
+
btn.style.display = latestOffScreen && this.pointerNearScrollBtn ? "flex" : "none";
|
|
31885
33635
|
}
|
|
31886
33636
|
/** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
|
|
31887
33637
|
scrollToRealtime() {
|
|
@@ -32190,6 +33940,8 @@ var NativeRenderer = class {
|
|
|
32190
33940
|
};
|
|
32191
33941
|
},
|
|
32192
33942
|
chartBarMs: () => this.coords.barInterval,
|
|
33943
|
+
timeZone: () => this.scene.timezone,
|
|
33944
|
+
priceDecimals: () => this.scene.priceMintick ? tickDecimals(this.scene.priceMintick) : null,
|
|
32193
33945
|
snap: (pt, paneId, mode, cursorPx) => this.snapToCandle(pt, paneId, mode, cursorPx),
|
|
32194
33946
|
setSnapMode: (mode) => this.setSnapMode(mode),
|
|
32195
33947
|
setToolbarGutter: (px) => this.setToolbarGutter(px)
|
|
@@ -33511,7 +35263,7 @@ var NativeRenderer = class {
|
|
|
33511
35263
|
if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
|
|
33512
35264
|
if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
|
|
33513
35265
|
for (const group of this.dataWindowGroups(idx, pricePane)) {
|
|
33514
|
-
for (const
|
|
35266
|
+
for (const row2 of group.rows) parts.push(`${row2.label} ${row2.value}`);
|
|
33515
35267
|
}
|
|
33516
35268
|
this.liveRegion.textContent = parts.join(". ");
|
|
33517
35269
|
}
|
|
@@ -34467,7 +36219,10 @@ function applyPatch(model, patch) {
|
|
|
34467
36219
|
const s = model.series.find((x) => x.id === delta.seriesId);
|
|
34468
36220
|
if (!s) continue;
|
|
34469
36221
|
if (delta.kind === "points" && isLineLikeSeries(s)) s.points = delta.points;
|
|
34470
|
-
else if (delta.kind === "bars" && (s.kind === "candle" || s.kind === "bar"))
|
|
36222
|
+
else if (delta.kind === "bars" && (s.kind === "candle" || s.kind === "bar")) {
|
|
36223
|
+
s.bars = delta.bars;
|
|
36224
|
+
if (delta.barColors !== void 0) s.barColors = delta.barColors;
|
|
36225
|
+
}
|
|
34471
36226
|
}
|
|
34472
36227
|
if (patch.lines) model.lines = patch.lines;
|
|
34473
36228
|
if (patch.boxes) model.boxes = patch.boxes;
|
|
@@ -38513,8 +40268,8 @@ var Vela = class {
|
|
|
38513
40268
|
if (options.settings) this.rendererControl.setSettingsVisibility(options.settings);
|
|
38514
40269
|
this.dataControl = new DataControl(feed);
|
|
38515
40270
|
this.orchestrator = new EngineOrchestrator(element, renderer, feed, engines, config, this.dataControl);
|
|
38516
|
-
const
|
|
38517
|
-
if (Object.keys(
|
|
40271
|
+
const defaults5 = rendererDefaults();
|
|
40272
|
+
if (Object.keys(defaults5).length > 0) this.rendererControl.set(defaults5);
|
|
38518
40273
|
this.panesControl = new PanesControl(this.orchestrator);
|
|
38519
40274
|
this.drawingsControl = new DrawingsControl(this.orchestrator.drawings);
|
|
38520
40275
|
this.marksControl = new MarksControl(this.orchestrator.marks);
|