@tradecanvas/chart 0.8.2 → 0.14.0
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/README.md +86 -4
- package/dist/Chart.d.ts +121 -1
- package/dist/Chart.d.ts.map +1 -1
- package/dist/{Chart-Cn_TSMO7.js → DragDropImporter-Br2ez5Gz.js} +560 -91
- package/dist/DragDropImporter-Br2ez5Gz.js.map +1 -0
- package/dist/DragDropImporter-ycI_Tcs6.cjs +2 -0
- package/dist/DragDropImporter-ycI_Tcs6.cjs.map +1 -0
- package/dist/finance/PerformanceDashboard.d.ts +43 -0
- package/dist/finance/PerformanceDashboard.d.ts.map +1 -0
- package/dist/finance/index.d.ts +4 -0
- package/dist/finance/index.d.ts.map +1 -1
- package/dist/finance/performanceData.d.ts +39 -0
- package/dist/finance/performanceData.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +287 -30
- package/dist/index.js.map +1 -1
- package/dist/io/DragDropImporter.d.ts +34 -0
- package/dist/io/DragDropImporter.d.ts.map +1 -0
- package/dist/io/index.d.ts +7 -0
- package/dist/io/index.d.ts.map +1 -0
- package/dist/io/parseOHLCV.d.ts +21 -0
- package/dist/io/parseOHLCV.d.ts.map +1 -0
- package/dist/io/resampleOHLCV.d.ts +29 -0
- package/dist/io/resampleOHLCV.d.ts.map +1 -0
- package/dist/widget/AlertNotifier.d.ts +32 -0
- package/dist/widget/AlertNotifier.d.ts.map +1 -0
- package/dist/widget/ChartWidget.d.ts +100 -2
- package/dist/widget/ChartWidget.d.ts.map +1 -1
- package/dist/widget/DrawingFavoritesStore.d.ts +22 -0
- package/dist/widget/DrawingFavoritesStore.d.ts.map +1 -0
- package/dist/widget/DrawingTemplateStore.d.ts +29 -0
- package/dist/widget/DrawingTemplateStore.d.ts.map +1 -0
- package/dist/widget/WidgetAlertsPanel.d.ts +58 -0
- package/dist/widget/WidgetAlertsPanel.d.ts.map +1 -0
- package/dist/widget/WidgetBracketBar.d.ts +21 -0
- package/dist/widget/WidgetBracketBar.d.ts.map +1 -0
- package/dist/widget/WidgetDataWindow.d.ts +41 -0
- package/dist/widget/WidgetDataWindow.d.ts.map +1 -0
- package/dist/widget/WidgetDepthLadder.d.ts +29 -0
- package/dist/widget/WidgetDepthLadder.d.ts.map +1 -0
- package/dist/widget/WidgetDrawingSidebar.d.ts +6 -0
- package/dist/widget/WidgetDrawingSidebar.d.ts.map +1 -1
- package/dist/widget/WidgetDrawingStyle.d.ts +36 -0
- package/dist/widget/WidgetDrawingStyle.d.ts.map +1 -0
- package/dist/widget/WidgetHotkeySheet.d.ts +20 -0
- package/dist/widget/WidgetHotkeySheet.d.ts.map +1 -0
- package/dist/widget/WidgetIndicatorSettings.d.ts +42 -0
- package/dist/widget/WidgetIndicatorSettings.d.ts.map +1 -0
- package/dist/widget/WidgetObjectTree.d.ts +59 -0
- package/dist/widget/WidgetObjectTree.d.ts.map +1 -0
- package/dist/widget/WidgetReplayBar.d.ts +41 -0
- package/dist/widget/WidgetReplayBar.d.ts.map +1 -0
- package/dist/widget/WidgetSettings.d.ts +1 -0
- package/dist/widget/WidgetSettings.d.ts.map +1 -1
- package/dist/widget/WidgetSymbolSearch.d.ts +34 -0
- package/dist/widget/WidgetSymbolSearch.d.ts.map +1 -0
- package/dist/widget/WidgetToolbar.d.ts.map +1 -1
- package/dist/widget/WidgetWatchlist.d.ts +39 -0
- package/dist/widget/WidgetWatchlist.d.ts.map +1 -0
- package/dist/widget/icons.d.ts.map +1 -1
- package/dist/widget/index.cjs +2372 -877
- package/dist/widget/index.cjs.map +1 -1
- package/dist/widget/index.js +2455 -185
- package/dist/widget/index.js.map +1 -1
- package/dist/widget/ladderRows.d.ts +21 -0
- package/dist/widget/ladderRows.d.ts.map +1 -0
- package/dist/widget/types.d.ts +91 -1
- package/dist/widget/types.d.ts.map +1 -1
- package/dist/widget/widgetConfig.d.ts.map +1 -1
- package/dist/widget/widgetShareState.d.ts +26 -0
- package/dist/widget/widgetShareState.d.ts.map +1 -0
- package/package.json +3 -3
- package/dist/Chart-Br_N-BCL.cjs +0 -2
- package/dist/Chart-Br_N-BCL.cjs.map +0 -1
- package/dist/Chart-Cn_TSMO7.js.map +0 -1
package/dist/widget/index.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DARK_THEME as
|
|
1
|
+
import { r as e, t } from "../DragDropImporter-Br2ez5Gz.js";
|
|
2
|
+
import { DARK_THEME as n, LIGHT_THEME as r, timeframeBucketStart as i, timeframeToMs as a } from "@tradecanvas/commons";
|
|
3
|
+
//#region src/io/resampleOHLCV.ts
|
|
4
|
+
function o(e, t, n = {}) {
|
|
5
|
+
if (e.length === 0) return [];
|
|
6
|
+
let r = n.weekStartsOn ?? 1, a = [], o = null, s = NaN;
|
|
7
|
+
for (let n of e) {
|
|
8
|
+
let e = i(n.time, t, r);
|
|
9
|
+
o === null || e !== s ? (o !== null && a.push(o), o = {
|
|
10
|
+
time: e,
|
|
11
|
+
open: n.open,
|
|
12
|
+
high: n.high,
|
|
13
|
+
low: n.low,
|
|
14
|
+
close: n.close,
|
|
15
|
+
volume: n.volume
|
|
16
|
+
}, s = e) : o = {
|
|
17
|
+
time: o.time,
|
|
18
|
+
open: o.open,
|
|
19
|
+
high: Math.max(o.high, n.high),
|
|
20
|
+
low: Math.min(o.low, n.low),
|
|
21
|
+
close: n.close,
|
|
22
|
+
volume: o.volume + n.volume
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return o !== null && a.push(o), a;
|
|
26
|
+
}
|
|
27
|
+
function s(e) {
|
|
28
|
+
if (e.length < 2) return 0;
|
|
29
|
+
let t = [];
|
|
30
|
+
for (let n = 1; n < e.length; n++) {
|
|
31
|
+
let r = e[n].time - e[n - 1].time;
|
|
32
|
+
r > 0 && t.push(r);
|
|
33
|
+
}
|
|
34
|
+
return t.length === 0 ? 0 : (t.sort((e, t) => e - t), t[Math.floor(t.length / 2)]);
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
3
37
|
//#region src/widget/widgetConfig.ts
|
|
4
|
-
var
|
|
38
|
+
var c = [
|
|
5
39
|
{
|
|
6
40
|
label: "1m",
|
|
7
41
|
value: "1m"
|
|
@@ -26,7 +60,7 @@ var r = [
|
|
|
26
60
|
label: "1D",
|
|
27
61
|
value: "1d"
|
|
28
62
|
}
|
|
29
|
-
],
|
|
63
|
+
], l = [
|
|
30
64
|
{
|
|
31
65
|
label: "Candlestick",
|
|
32
66
|
value: "candlestick"
|
|
@@ -75,7 +109,7 @@ var r = [
|
|
|
75
109
|
label: "Line + Markers",
|
|
76
110
|
value: "lineWithMarkers"
|
|
77
111
|
}
|
|
78
|
-
],
|
|
112
|
+
], u = [
|
|
79
113
|
{
|
|
80
114
|
label: "Lines",
|
|
81
115
|
tools: [
|
|
@@ -188,7 +222,7 @@ var r = [
|
|
|
188
222
|
value: "arrow"
|
|
189
223
|
}]
|
|
190
224
|
}
|
|
191
|
-
],
|
|
225
|
+
], d = [
|
|
192
226
|
{
|
|
193
227
|
id: "sma",
|
|
194
228
|
name: "SMA",
|
|
@@ -204,6 +238,11 @@ var r = [
|
|
|
204
238
|
name: "Hull MA",
|
|
205
239
|
type: "overlay"
|
|
206
240
|
},
|
|
241
|
+
{
|
|
242
|
+
id: "mtfma",
|
|
243
|
+
name: "MTF Moving Average",
|
|
244
|
+
type: "overlay"
|
|
245
|
+
},
|
|
207
246
|
{
|
|
208
247
|
id: "bb",
|
|
209
248
|
name: "Bollinger Bands",
|
|
@@ -219,6 +258,11 @@ var r = [
|
|
|
219
258
|
name: "Anchored VWAP",
|
|
220
259
|
type: "overlay"
|
|
221
260
|
},
|
|
261
|
+
{
|
|
262
|
+
id: "svwap",
|
|
263
|
+
name: "Session VWAP",
|
|
264
|
+
type: "overlay"
|
|
265
|
+
},
|
|
222
266
|
{
|
|
223
267
|
id: "ichimoku",
|
|
224
268
|
name: "Ichimoku Cloud",
|
|
@@ -259,6 +303,16 @@ var r = [
|
|
|
259
303
|
name: "Linear Regression Channel",
|
|
260
304
|
type: "overlay"
|
|
261
305
|
},
|
|
306
|
+
{
|
|
307
|
+
id: "chandelier",
|
|
308
|
+
name: "Chandelier Exit",
|
|
309
|
+
type: "overlay"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "alligator",
|
|
313
|
+
name: "Alligator",
|
|
314
|
+
type: "overlay"
|
|
315
|
+
},
|
|
262
316
|
{
|
|
263
317
|
id: "rsi",
|
|
264
318
|
name: "RSI",
|
|
@@ -349,12 +403,157 @@ var r = [
|
|
|
349
403
|
name: "Chaikin Oscillator",
|
|
350
404
|
type: "panel"
|
|
351
405
|
},
|
|
406
|
+
{
|
|
407
|
+
id: "voldelta",
|
|
408
|
+
name: "Volume Delta",
|
|
409
|
+
type: "panel"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: "vortex",
|
|
413
|
+
name: "Vortex Indicator",
|
|
414
|
+
type: "panel"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: "chop",
|
|
418
|
+
name: "Choppiness Index",
|
|
419
|
+
type: "panel"
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: "uo",
|
|
423
|
+
name: "Ultimate Oscillator",
|
|
424
|
+
type: "panel"
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
id: "fi",
|
|
428
|
+
name: "Force Index",
|
|
429
|
+
type: "panel"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "crsi",
|
|
433
|
+
name: "Connors RSI",
|
|
434
|
+
type: "panel"
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: "coppock",
|
|
438
|
+
name: "Coppock Curve",
|
|
439
|
+
type: "panel"
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "kst",
|
|
443
|
+
name: "Know Sure Thing",
|
|
444
|
+
type: "panel"
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
id: "elderray",
|
|
448
|
+
name: "Elder Ray",
|
|
449
|
+
type: "panel"
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
id: "stc",
|
|
453
|
+
name: "Schaff Trend Cycle",
|
|
454
|
+
type: "panel"
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: "kvo",
|
|
458
|
+
name: "Klinger Oscillator",
|
|
459
|
+
type: "panel"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: "fisher",
|
|
463
|
+
name: "Fisher Transform",
|
|
464
|
+
type: "panel"
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: "dpo",
|
|
468
|
+
name: "Detrended Price Osc",
|
|
469
|
+
type: "panel"
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
id: "bop",
|
|
473
|
+
name: "Balance of Power",
|
|
474
|
+
type: "panel"
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
id: "massindex",
|
|
478
|
+
name: "Mass Index",
|
|
479
|
+
type: "panel"
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
id: "cmo",
|
|
483
|
+
name: "Chande Momentum",
|
|
484
|
+
type: "panel"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
id: "trix",
|
|
488
|
+
name: "TRIX",
|
|
489
|
+
type: "panel"
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
id: "emv",
|
|
493
|
+
name: "Ease of Movement",
|
|
494
|
+
type: "panel"
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: "pvt",
|
|
498
|
+
name: "Price Volume Trend",
|
|
499
|
+
type: "panel"
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
id: "wad",
|
|
503
|
+
name: "Williams A/D",
|
|
504
|
+
type: "panel"
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
id: "chaikinvol",
|
|
508
|
+
name: "Chaikin Volatility",
|
|
509
|
+
type: "panel"
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
id: "rvi",
|
|
513
|
+
name: "Relative Vigor Index",
|
|
514
|
+
type: "panel"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
id: "stochrsi",
|
|
518
|
+
name: "Stochastic RSI",
|
|
519
|
+
type: "panel"
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
id: "ppo",
|
|
523
|
+
name: "Percentage Price Oscillator",
|
|
524
|
+
type: "panel"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
id: "ac",
|
|
528
|
+
name: "Accelerator Oscillator",
|
|
529
|
+
type: "panel"
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
id: "rmi",
|
|
533
|
+
name: "Relative Momentum Index",
|
|
534
|
+
type: "panel"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
id: "disparity",
|
|
538
|
+
name: "Disparity Index",
|
|
539
|
+
type: "panel"
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
id: "qstick",
|
|
543
|
+
name: "Qstick",
|
|
544
|
+
type: "panel"
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
id: "pgo",
|
|
548
|
+
name: "Pretty Good Oscillator",
|
|
549
|
+
type: "panel"
|
|
550
|
+
},
|
|
352
551
|
{
|
|
353
552
|
id: "volumeProfile",
|
|
354
553
|
name: "Volume Profile",
|
|
355
554
|
type: "panel"
|
|
356
555
|
}
|
|
357
|
-
],
|
|
556
|
+
], f = [
|
|
358
557
|
"sma",
|
|
359
558
|
"ema",
|
|
360
559
|
"bb",
|
|
@@ -365,12 +564,12 @@ var r = [
|
|
|
365
564
|
"atr",
|
|
366
565
|
"obv",
|
|
367
566
|
"ichimoku"
|
|
368
|
-
],
|
|
567
|
+
], ee = [
|
|
369
568
|
"BTCUSDT",
|
|
370
569
|
"ETHUSDT",
|
|
371
570
|
"SOLUSDT",
|
|
372
571
|
"BNBUSDT"
|
|
373
|
-
],
|
|
572
|
+
], p = {
|
|
374
573
|
candleUpColor: "#26A69A",
|
|
375
574
|
candleDownColor: "#EF5350",
|
|
376
575
|
candleUpWick: "#26A69A",
|
|
@@ -379,26 +578,42 @@ var r = [
|
|
|
379
578
|
gridColor: "#1E222D",
|
|
380
579
|
gridVisible: !0,
|
|
381
580
|
volumeVisible: !0,
|
|
581
|
+
volumeProfileVisible: !1,
|
|
582
|
+
marketProfileVisible: !1,
|
|
583
|
+
marketProfileSplit: !1,
|
|
584
|
+
marketProfileLetters: !1,
|
|
585
|
+
marketProfileBuckets: 48,
|
|
586
|
+
marketProfileOpacity: .32,
|
|
587
|
+
depthHeatmapVisible: !1,
|
|
588
|
+
depthHeatmapOpacity: .7,
|
|
589
|
+
sessionShadingVisible: !1,
|
|
590
|
+
pivotMarkersVisible: !1,
|
|
591
|
+
pivotStrength: 5,
|
|
592
|
+
pivotStructureLabels: !1,
|
|
593
|
+
periodLevelsVisible: !1,
|
|
594
|
+
periodLevelsPeriod: "day",
|
|
382
595
|
legendVisible: !0,
|
|
383
596
|
barCountdown: !0,
|
|
384
597
|
logScale: !1,
|
|
598
|
+
scaleMode: "regular",
|
|
385
599
|
autoScale: !0,
|
|
386
600
|
crosshairMode: "magnet",
|
|
387
|
-
numberLocale: "en-US"
|
|
388
|
-
}, u = "/* === TradeCanvas Widget Styles === */\r\n\r\n.tcw-root {\r\n display: flex;\r\n flex-direction: column;\r\n width: 100%;\r\n height: 100%;\r\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;\r\n font-size: 13px;\r\n color: #d1d4dc;\r\n background: #131722;\r\n position: relative;\r\n overflow: hidden;\r\n\r\n /* Dark theme tokens (default) */\r\n --tcw-bg: #131722;\r\n --tcw-bg-elevated: #1E222D;\r\n --tcw-border: #2A2E39;\r\n --tcw-text: #d1d4dc;\r\n --tcw-text-dim: #b2b5be;\r\n --tcw-text-muted: #787b86;\r\n --tcw-accent: #2962ff;\r\n --tcw-accent-dim: rgba(41,98,255,0.15);\r\n --tcw-accent-glow: rgba(41,98,255,0.12);\r\n --tcw-red: #ef5350;\r\n --tcw-green: #26a69a;\r\n --tcw-transition: 0.15s ease;\r\n --tcw-radius-lg: 8px;\r\n --tcw-font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;\r\n --tcw-hover-bg: rgba(255,255,255,0.04);\r\n}\r\n\r\n.tcw-root[data-tcw-theme=\"light\"] {\r\n color: #131722;\r\n background: #ffffff;\r\n\r\n --tcw-bg: #ffffff;\r\n --tcw-bg-elevated: #f0f3fa;\r\n --tcw-border: #e0e3eb;\r\n --tcw-text: #131722;\r\n --tcw-text-dim: #434651;\r\n --tcw-text-muted: #787b86;\r\n --tcw-accent: #2962ff;\r\n --tcw-accent-dim: rgba(41,98,255,0.1);\r\n --tcw-accent-glow: rgba(41,98,255,0.08);\r\n --tcw-red: #ef5350;\r\n --tcw-green: #26a69a;\r\n --tcw-hover-bg: rgba(0,0,0,0.04);\r\n}\r\n\r\n/* === Toolbar === */\r\n\r\n.tcw-toolbar {\r\n display: flex;\r\n align-items: center;\r\n gap: 0;\r\n padding: 0 12px;\r\n height: 40px;\r\n border-bottom: 1px solid var(--tcw-border);\r\n background: var(--tcw-bg-elevated);\r\n flex-shrink: 0;\r\n position: relative;\r\n z-index: 20;\r\n}\r\n\r\n.tcw-toolbar-symbol {\r\n font-size: 13px;\r\n font-weight: 700;\r\n color: var(--tcw-text);\r\n padding: 0 10px;\r\n white-space: nowrap;\r\n letter-spacing: -0.01em;\r\n cursor: pointer;\r\n border: none;\r\n background: none;\r\n font-family: inherit;\r\n}\r\n\r\n.tcw-toolbar-sep {\r\n width: 1px;\r\n height: 20px;\r\n background: var(--tcw-border);\r\n flex-shrink: 0;\r\n}\r\n\r\n.tcw-toolbar-group {\r\n display: flex;\r\n align-items: center;\r\n gap: 2px;\r\n padding: 0 8px;\r\n}\r\n\r\n.tcw-toolbar-spacer {\r\n flex: 1;\r\n}\r\n\r\n.tcw-btn {\r\n padding: 4px 8px;\r\n font-size: 12px;\r\n font-family: inherit;\r\n border: none;\r\n border-radius: 4px;\r\n background: transparent;\r\n color: var(--tcw-text-dim);\r\n cursor: pointer;\r\n transition: all var(--tcw-transition);\r\n white-space: nowrap;\r\n line-height: 1;\r\n}\r\n\r\n.tcw-btn:hover {\r\n background: var(--tcw-hover-bg);\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-btn.tcw-active {\r\n background: var(--tcw-accent);\r\n color: white;\r\n}\r\n\r\n.tcw-btn-icon {\r\n width: 32px;\r\n height: 32px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-family: inherit;\r\n border: none;\r\n border-radius: 4px;\r\n background: transparent;\r\n color: var(--tcw-text-muted);\r\n cursor: pointer;\r\n transition: all var(--tcw-transition);\r\n line-height: 1;\r\n position: relative;\r\n}\r\n\r\n.tcw-btn-icon:hover {\r\n background: var(--tcw-hover-bg);\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-badge-count {\r\n font-size: 10px;\r\n color: var(--tcw-accent);\r\n background: var(--tcw-accent-dim);\r\n padding: 0 4px;\r\n border-radius: 8px;\r\n font-weight: 600;\r\n}\r\n\r\n.tcw-indicator-chips {\r\n display: flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 0 4px;\r\n flex-wrap: nowrap;\r\n overflow: hidden;\r\n}\r\n\r\n.tcw-indicator-chip {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 2px 6px 2px 8px;\r\n font-size: 11px;\r\n font-weight: 500;\r\n background: var(--tcw-accent-dim);\r\n color: var(--tcw-accent);\r\n border-radius: 4px;\r\n white-space: nowrap;\r\n line-height: 1.4;\r\n}\r\n\r\n.tcw-chip-remove {\r\n cursor: pointer;\r\n opacity: 0.6;\r\n padding: 0 2px;\r\n transition: opacity var(--tcw-transition);\r\n border: none;\r\n background: none;\r\n color: inherit;\r\n display: inline-flex;\r\n align-items: center;\r\n}\r\n\r\n.tcw-chip-remove:hover {\r\n opacity: 1;\r\n}\r\n\r\n/* === Dropdown === */\r\n\r\n.tcw-dropdown-trigger {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 4px;\r\n padding: 4px 8px;\r\n font-size: 12px;\r\n font-family: inherit;\r\n border: none;\r\n border-radius: 4px;\r\n background: transparent;\r\n color: var(--tcw-text-dim);\r\n cursor: pointer;\r\n transition: all var(--tcw-transition);\r\n white-space: nowrap;\r\n line-height: 1;\r\n}\r\n\r\n.tcw-dropdown-trigger:hover {\r\n background: var(--tcw-hover-bg);\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-dropdown {\r\n position: absolute;\r\n top: 100%;\r\n margin-top: 4px;\r\n background: var(--tcw-bg-elevated);\r\n border: 1px solid var(--tcw-border);\r\n border-radius: var(--tcw-radius-lg);\r\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\r\n z-index: 50;\r\n padding: 4px 0;\r\n max-height: 400px;\r\n overflow-y: auto;\r\n}\r\n\r\n.tcw-dropdown-item {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 6px 12px;\r\n font-size: 12px;\r\n color: var(--tcw-text-dim);\r\n cursor: pointer;\r\n transition: background var(--tcw-transition), color var(--tcw-transition);\r\n width: 100%;\r\n border: none;\r\n background: none;\r\n font-family: inherit;\r\n text-align: left;\r\n}\r\n\r\n.tcw-dropdown-item:hover {\r\n background: var(--tcw-hover-bg);\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-dropdown-item.tcw-active {\r\n color: var(--tcw-accent);\r\n}\r\n\r\n.tcw-dropdown-label {\r\n text-transform: uppercase;\r\n font-size: 10px;\r\n color: var(--tcw-text-muted);\r\n letter-spacing: 0.05em;\r\n padding: 6px 12px;\r\n font-weight: 600;\r\n}\r\n\r\n.tcw-dropdown-divider {\r\n border-top: 1px solid var(--tcw-border);\r\n margin: 4px 0;\r\n}\r\n\r\n.tcw-tag {\r\n font-size: 10px;\r\n color: var(--tcw-text-muted);\r\n padding: 1px 5px;\r\n border-radius: 3px;\r\n background: var(--tcw-hover-bg);\r\n}\r\n\r\n/* === Body === */\r\n\r\n.tcw-body {\r\n display: flex;\r\n flex: 1;\r\n min-height: 0;\r\n}\r\n\r\n.tcw-chart-container {\r\n flex: 1;\r\n min-width: 0;\r\n min-height: 0;\r\n width: 100%;\r\n height: 100%;\r\n}\r\n\r\n/* === Sidebar === */\r\n\r\n.tcw-sidebar {\r\n width: 36px;\r\n flex-shrink: 0;\r\n background: var(--tcw-bg-elevated);\r\n border-right: 1px solid var(--tcw-border);\r\n display: flex;\r\n flex-direction: column;\r\n align-items: center;\r\n padding: 4px 0;\r\n overflow: visible;\r\n}\r\n\r\n.tcw-tool-group-wrap {\r\n position: relative;\r\n}\r\n\r\n.tcw-sidebar-btn {\r\n width: 36px;\r\n height: 32px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n background: transparent;\r\n color: var(--tcw-text-muted);\r\n cursor: pointer;\r\n font-family: inherit;\r\n transition: all var(--tcw-transition);\r\n position: relative;\r\n flex-shrink: 0;\r\n line-height: 1;\r\n}\r\n\r\n.tcw-sidebar-btn:hover {\r\n color: var(--tcw-text);\r\n background: var(--tcw-hover-bg);\r\n}\r\n\r\n.tcw-sidebar-btn.tcw-active {\r\n color: var(--tcw-accent);\r\n background: var(--tcw-accent-glow);\r\n}\r\n\r\n.tcw-sidebar-btn.tcw-danger:hover {\r\n color: var(--tcw-red);\r\n background: rgba(239,68,68,0.1);\r\n}\r\n\r\n.tcw-multi-dot {\r\n position: absolute;\r\n bottom: 2px;\r\n right: 2px;\r\n width: 4px;\r\n height: 4px;\r\n border-radius: 50%;\r\n background: var(--tcw-text-muted);\r\n}\r\n\r\n.tcw-sidebar-divider {\r\n width: 20px;\r\n height: 1px;\r\n background: var(--tcw-border);\r\n margin: 4px 0;\r\n flex-shrink: 0;\r\n}\r\n\r\n.tcw-sidebar-spacer {\r\n flex: 1;\r\n}\r\n\r\n/* === Flyout === */\r\n\r\n.tcw-flyout {\r\n position: absolute;\r\n left: 100%;\r\n top: 0;\r\n margin-left: 2px;\r\n min-width: 160px;\r\n background: var(--tcw-bg-elevated);\r\n border: 1px solid var(--tcw-border);\r\n border-radius: var(--tcw-radius-lg);\r\n box-shadow: 0 8px 32px rgba(0,0,0,0.4);\r\n z-index: 30;\r\n padding: 4px 0;\r\n}\r\n\r\n.tcw-flyout-header {\r\n text-transform: uppercase;\r\n font-size: 10px;\r\n color: var(--tcw-text-muted);\r\n letter-spacing: 0.05em;\r\n padding: 6px 12px;\r\n font-weight: 600;\r\n}\r\n\r\n.tcw-flyout-item {\r\n padding: 6px 12px;\r\n font-size: 12px;\r\n color: var(--tcw-text-dim);\r\n cursor: pointer;\r\n width: 100%;\r\n display: block;\r\n border: none;\r\n background: none;\r\n text-align: left;\r\n font-family: inherit;\r\n transition: background var(--tcw-transition), color var(--tcw-transition);\r\n}\r\n\r\n.tcw-flyout-item:hover {\r\n background: var(--tcw-hover-bg);\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-flyout-item.tcw-active {\r\n color: var(--tcw-accent);\r\n}\r\n\r\n/* === Modal === */\r\n\r\n.tcw-modal-backdrop {\r\n position: fixed;\r\n inset: 0;\r\n background: rgba(0,0,0,0.5);\r\n backdrop-filter: blur(4px);\r\n -webkit-backdrop-filter: blur(4px);\r\n z-index: 100;\r\n}\r\n\r\n.tcw-modal {\r\n position: fixed;\r\n z-index: 101;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n background: var(--tcw-bg-elevated);\r\n border: 1px solid var(--tcw-border);\r\n border-radius: var(--tcw-radius-lg);\r\n width: 480px;\r\n max-width: 90vw;\r\n max-height: 80vh;\r\n display: flex;\r\n flex-direction: column;\r\n box-shadow: 0 24px 64px rgba(0,0,0,0.5);\r\n}\r\n\r\n.tcw-modal-header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 16px 20px;\r\n border-bottom: 1px solid var(--tcw-border);\r\n}\r\n\r\n.tcw-modal-header h3 {\r\n font-size: 15px;\r\n font-weight: 600;\r\n margin: 0;\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-modal-close {\r\n width: 28px;\r\n height: 28px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n border: none;\r\n background: transparent;\r\n color: var(--tcw-text-muted);\r\n cursor: pointer;\r\n border-radius: 4px;\r\n transition: all var(--tcw-transition);\r\n}\r\n\r\n.tcw-modal-close:hover {\r\n background: var(--tcw-hover-bg);\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-modal-tabs {\r\n display: flex;\r\n gap: 0;\r\n padding: 0 20px;\r\n border-bottom: 1px solid var(--tcw-border);\r\n}\r\n\r\n.tcw-modal-tab {\r\n padding: 10px 16px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n color: var(--tcw-text-muted);\r\n border: none;\r\n background: none;\r\n cursor: pointer;\r\n border-bottom: 2px solid transparent;\r\n margin-bottom: -1px;\r\n transition: color var(--tcw-transition);\r\n font-family: inherit;\r\n text-transform: capitalize;\r\n}\r\n\r\n.tcw-modal-tab:hover {\r\n color: var(--tcw-text-dim);\r\n}\r\n\r\n.tcw-modal-tab.tcw-active {\r\n color: var(--tcw-accent);\r\n border-bottom-color: var(--tcw-accent);\r\n}\r\n\r\n.tcw-modal-body {\r\n padding: 20px;\r\n overflow-y: auto;\r\n flex: 1;\r\n}\r\n\r\n.tcw-settings-section {\r\n margin-bottom: 20px;\r\n}\r\n\r\n.tcw-settings-section:last-child {\r\n margin-bottom: 0;\r\n}\r\n\r\n.tcw-settings-section-title {\r\n font-size: 11px;\r\n font-weight: 600;\r\n text-transform: uppercase;\r\n letter-spacing: 0.05em;\r\n color: var(--tcw-text-muted);\r\n margin-bottom: 12px;\r\n}\r\n\r\n.tcw-settings-row {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 8px 0;\r\n}\r\n\r\n.tcw-settings-row + .tcw-settings-row {\r\n border-top: 1px solid rgba(255,255,255,0.03);\r\n}\r\n\r\n.tcw-root[data-tcw-theme=\"light\"] .tcw-settings-row + .tcw-settings-row {\r\n border-top: 1px solid rgba(0,0,0,0.04);\r\n}\r\n\r\n.tcw-settings-label {\r\n font-size: 13px;\r\n color: var(--tcw-text-dim);\r\n}\r\n\r\n.tcw-color-picker-wrap {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n}\r\n\r\n.tcw-color-picker-wrap input[type='color'] {\r\n width: 28px;\r\n height: 28px;\r\n border: 1px solid var(--tcw-border);\r\n border-radius: 4px;\r\n background: none;\r\n cursor: pointer;\r\n padding: 2px;\r\n}\r\n\r\n.tcw-color-picker-wrap input[type='color']::-webkit-color-swatch-wrapper {\r\n padding: 0;\r\n}\r\n\r\n.tcw-color-picker-wrap input[type='color']::-webkit-color-swatch {\r\n border: none;\r\n border-radius: 2px;\r\n}\r\n\r\n.tcw-color-hex {\r\n font-size: 11px;\r\n font-family: var(--tcw-font-mono);\r\n color: var(--tcw-text-muted);\r\n min-width: 60px;\r\n}\r\n\r\n.tcw-toggle {\r\n width: 36px;\r\n height: 20px;\r\n background: rgba(255,255,255,0.1);\r\n border-radius: 10px;\r\n position: relative;\r\n cursor: pointer;\r\n transition: background var(--tcw-transition);\r\n border: none;\r\n padding: 0;\r\n flex-shrink: 0;\r\n}\r\n\r\n.tcw-toggle::after {\r\n content: '';\r\n position: absolute;\r\n top: 2px;\r\n left: 2px;\r\n width: 16px;\r\n height: 16px;\r\n border-radius: 50%;\r\n background: var(--tcw-text-muted);\r\n transition: all var(--tcw-transition);\r\n}\r\n\r\n.tcw-toggle.tcw-on {\r\n background: var(--tcw-accent);\r\n}\r\n\r\n.tcw-toggle.tcw-on::after {\r\n left: 18px;\r\n background: white;\r\n}\r\n\r\n.tcw-root[data-tcw-theme=\"light\"] .tcw-toggle {\r\n background: rgba(0,0,0,0.1);\r\n}\r\n\r\n.tcw-root[data-tcw-theme=\"light\"] .tcw-toggle::after {\r\n background: #888;\r\n}\r\n\r\n.tcw-root[data-tcw-theme=\"light\"] .tcw-toggle.tcw-on {\r\n background: var(--tcw-accent);\r\n}\r\n\r\n.tcw-root[data-tcw-theme=\"light\"] .tcw-toggle.tcw-on::after {\r\n background: white;\r\n}\r\n\r\n.tcw-settings-select {\r\n padding: 4px 8px;\r\n font-size: 12px;\r\n font-family: inherit;\r\n background: var(--tcw-bg);\r\n border: 1px solid var(--tcw-border);\r\n border-radius: 4px;\r\n color: var(--tcw-text);\r\n cursor: pointer;\r\n}\r\n\r\n.tcw-modal-footer {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 12px 20px;\r\n border-top: 1px solid var(--tcw-border);\r\n}\r\n\r\n.tcw-reset-link {\r\n font-size: 12px;\r\n color: var(--tcw-text-muted);\r\n cursor: pointer;\r\n border: none;\r\n background: none;\r\n font-family: inherit;\r\n text-decoration: underline;\r\n text-underline-offset: 2px;\r\n transition: color var(--tcw-transition);\r\n}\r\n\r\n.tcw-reset-link:hover {\r\n color: var(--tcw-text-dim);\r\n}\r\n\r\n.tcw-done-btn {\r\n padding: 6px 16px;\r\n font-size: 12px;\r\n font-weight: 500;\r\n background: var(--tcw-accent);\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-family: inherit;\r\n transition: background var(--tcw-transition);\r\n}\r\n\r\n.tcw-done-btn:hover {\r\n background: #2563eb;\r\n}\r\n\r\n/* === Status Bar === */\r\n\r\n.tcw-statusbar {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 6px 16px;\r\n border-top: 1px solid var(--tcw-border);\r\n background: var(--tcw-bg-elevated);\r\n font-size: 11px;\r\n color: var(--tcw-text-muted);\r\n flex-shrink: 0;\r\n}\r\n\r\n.tcw-status-indicator {\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n}\r\n\r\n.tcw-status-dot {\r\n width: 6px;\r\n height: 6px;\r\n border-radius: 50%;\r\n background: var(--tcw-text-muted);\r\n transition: background 0.3s;\r\n}\r\n\r\n.tcw-status-dot.tcw-connected {\r\n background: var(--tcw-green);\r\n}\r\n\r\n.tcw-status-dot.tcw-error {\r\n background: var(--tcw-red);\r\n}\r\n\r\n/* === Command Palette === */\r\n\r\n.tcw-cmd-palette {\r\n position: fixed;\r\n z-index: 102;\r\n top: 20%;\r\n left: 50%;\r\n transform: translateX(-50%);\r\n background: var(--tcw-bg-elevated);\r\n border: 1px solid var(--tcw-border);\r\n border-radius: var(--tcw-radius-lg);\r\n width: 520px;\r\n max-width: 90vw;\r\n max-height: 60vh;\r\n display: flex;\r\n flex-direction: column;\r\n box-shadow: 0 24px 64px rgba(0,0,0,0.5);\r\n overflow: hidden;\r\n}\r\n\r\n.tcw-cmd-header {\r\n display: flex;\r\n align-items: center;\r\n padding: 12px 16px;\r\n border-bottom: 1px solid var(--tcw-border);\r\n gap: 10px;\r\n}\r\n\r\n.tcw-cmd-icon {\r\n font-size: 18px;\r\n color: var(--tcw-text-muted);\r\n flex-shrink: 0;\r\n}\r\n\r\n.tcw-cmd-input {\r\n flex: 1;\r\n background: none;\r\n border: none;\r\n outline: none;\r\n font-size: 14px;\r\n font-family: inherit;\r\n color: var(--tcw-text);\r\n caret-color: var(--tcw-accent);\r\n}\r\n\r\n.tcw-cmd-input::placeholder {\r\n color: var(--tcw-text-muted);\r\n}\r\n\r\n.tcw-cmd-hint {\r\n font-size: 10px;\r\n color: var(--tcw-text-muted);\r\n border: 1px solid var(--tcw-border);\r\n padding: 2px 6px;\r\n border-radius: 3px;\r\n flex-shrink: 0;\r\n}\r\n\r\n.tcw-cmd-list {\r\n overflow-y: auto;\r\n flex: 1;\r\n padding: 4px 0;\r\n max-height: 360px;\r\n}\r\n\r\n.tcw-cmd-category {\r\n text-transform: uppercase;\r\n font-size: 10px;\r\n color: var(--tcw-text-muted);\r\n letter-spacing: 0.05em;\r\n padding: 8px 16px 4px;\r\n font-weight: 600;\r\n}\r\n\r\n.tcw-cmd-item {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 8px 16px;\r\n font-size: 13px;\r\n color: var(--tcw-text-dim);\r\n cursor: pointer;\r\n width: 100%;\r\n border: none;\r\n background: none;\r\n font-family: inherit;\r\n text-align: left;\r\n transition: background 80ms ease;\r\n gap: 8px;\r\n}\r\n\r\n.tcw-cmd-item:hover,\r\n.tcw-cmd-item.tcw-selected {\r\n background: var(--tcw-hover-bg);\r\n color: var(--tcw-text);\r\n}\r\n\r\n.tcw-cmd-item.tcw-active .tcw-cmd-item-label {\r\n color: var(--tcw-accent);\r\n}\r\n\r\n.tcw-cmd-item-label {\r\n flex: 1;\r\n}\r\n\r\n.tcw-cmd-check {\r\n color: var(--tcw-accent);\r\n font-size: 14px;\r\n font-weight: 600;\r\n}\r\n\r\n.tcw-cmd-kbd {\r\n font-size: 10px;\r\n font-family: var(--tcw-font-mono);\r\n color: var(--tcw-text-muted);\r\n background: var(--tcw-bg);\r\n border: 1px solid var(--tcw-border);\r\n padding: 1px 5px;\r\n border-radius: 3px;\r\n}\r\n\r\n.tcw-cmd-empty {\r\n padding: 24px 16px;\r\n text-align: center;\r\n color: var(--tcw-text-muted);\r\n font-size: 13px;\r\n}\r\n\r\n.tcw-cmd-footer {\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n padding: 8px 16px;\r\n border-top: 1px solid var(--tcw-border);\r\n font-size: 11px;\r\n color: var(--tcw-text-muted);\r\n}\r\n\r\n/* === Responsive === */\r\n\r\n@media (max-width: 768px) {\r\n .tcw-toolbar {\r\n padding: 0 8px;\r\n height: 36px;\r\n }\r\n .tcw-toolbar-sep {\r\n display: none;\r\n }\r\n .tcw-indicator-chips {\r\n display: none;\r\n }\r\n .tcw-sidebar {\r\n width: 32px;\r\n }\r\n .tcw-sidebar-btn {\r\n width: 32px;\r\n height: 28px;\r\n }\r\n}\r\n", d = 0, f = "tcw-styles";
|
|
389
|
-
function p() {
|
|
390
|
-
|
|
601
|
+
numberLocale: "en-US",
|
|
602
|
+
timezone: "local"
|
|
603
|
+
}, te = "/* === TradeCanvas Widget — Premium Styles === */\n\n.tcw-root {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, system-ui, sans-serif;\n font-feature-settings: 'cv11', 'ss01', 'ss03';\n font-size: 13px;\n line-height: 1.4;\n color: #d1d4dc;\n background: #0d0f17;\n position: relative;\n overflow: hidden;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n\n /* --- Dark theme tokens (default) --- */\n --tcw-bg: #0d0f17;\n --tcw-bg-surface: #131722;\n --tcw-bg-elevated: #1a1e2d;\n --tcw-bg-overlay: #20253480;\n --tcw-border: #2a2e39;\n --tcw-border-strong: #363a45;\n --tcw-text: #e9ebf0;\n --tcw-text-dim: #b2b5be;\n --tcw-text-muted: #6b6f7a;\n\n --tcw-accent: #4f88ff;\n --tcw-accent-hover: #6b9aff;\n --tcw-accent-soft: rgba(79, 136, 255, 0.14);\n --tcw-accent-glow: rgba(79, 136, 255, 0.22);\n --tcw-accent-line: rgba(79, 136, 255, 0.55);\n\n --tcw-red: #f23645;\n --tcw-red-soft: rgba(242, 54, 69, 0.12);\n --tcw-green: #26a17b;\n --tcw-green-soft: rgba(38, 161, 123, 0.12);\n --tcw-amber: #ff9f43;\n\n --tcw-hover-bg: rgba(255, 255, 255, 0.05);\n --tcw-active-bg: rgba(255, 255, 255, 0.08);\n --tcw-divider: rgba(255, 255, 255, 0.06);\n\n /* Motion */\n --tcw-ease: cubic-bezier(0.2, 0, 0, 1);\n --tcw-ease-out: cubic-bezier(0.16, 1, 0.3, 1);\n --tcw-dur-fast: 120ms;\n --tcw-dur-normal: 180ms;\n --tcw-dur-slow: 260ms;\n\n /* Shape */\n --tcw-radius-sm: 4px;\n --tcw-radius: 6px;\n --tcw-radius-lg: 10px;\n --tcw-radius-xl: 14px;\n\n /* Elevation */\n --tcw-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);\n --tcw-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.2);\n --tcw-shadow-lg: 0 12px 36px rgba(0, 0, 0, 0.5), 0 4px 12px rgba(0, 0, 0, 0.3);\n --tcw-shadow-xl: 0 24px 64px rgba(0, 0, 0, 0.55), 0 12px 24px rgba(0, 0, 0, 0.35);\n --tcw-ring: 0 0 0 2px rgba(79, 136, 255, 0.45);\n\n /* Type */\n --tcw-font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', 'Fira Code', 'Consolas', ui-monospace, monospace;\n}\n\n.tcw-root[data-tcw-theme='light'] {\n color: #131722;\n background: #fcfcfd;\n\n --tcw-bg: #fcfcfd;\n --tcw-bg-surface: #ffffff;\n --tcw-bg-elevated: #ffffff;\n --tcw-bg-overlay: rgba(255, 255, 255, 0.75);\n --tcw-border: #e7e9ef;\n --tcw-border-strong: #d4d7e0;\n --tcw-text: #131722;\n --tcw-text-dim: #50545e;\n --tcw-text-muted: #8b8f99;\n\n --tcw-accent: #2962ff;\n --tcw-accent-hover: #1e4fd6;\n --tcw-accent-soft: rgba(41, 98, 255, 0.10);\n --tcw-accent-glow: rgba(41, 98, 255, 0.16);\n --tcw-accent-line: rgba(41, 98, 255, 0.45);\n\n --tcw-hover-bg: rgba(0, 0, 0, 0.04);\n --tcw-active-bg: rgba(41, 98, 255, 0.08);\n --tcw-divider: rgba(0, 0, 0, 0.06);\n\n --tcw-shadow-sm: 0 1px 2px rgba(20, 28, 56, 0.05);\n --tcw-shadow-md: 0 4px 12px rgba(20, 28, 56, 0.08), 0 1px 3px rgba(20, 28, 56, 0.04);\n --tcw-shadow-lg: 0 12px 36px rgba(20, 28, 56, 0.14), 0 4px 12px rgba(20, 28, 56, 0.08);\n --tcw-shadow-xl: 0 24px 64px rgba(20, 28, 56, 0.18), 0 12px 24px rgba(20, 28, 56, 0.1);\n}\n\n/* === Toolbar === */\n\n.tcw-toolbar {\n display: flex;\n align-items: center;\n gap: 0;\n padding: 0 10px;\n height: 44px;\n border-bottom: 1px solid var(--tcw-border);\n background:\n linear-gradient(180deg, var(--tcw-bg-elevated) 0%, var(--tcw-bg-surface) 100%);\n flex-shrink: 0;\n position: relative;\n z-index: 20;\n box-shadow: var(--tcw-shadow-sm);\n}\n\n.tcw-toolbar-symbol {\n font-size: 14px;\n font-weight: 650;\n color: var(--tcw-text);\n padding: 6px 12px;\n white-space: nowrap;\n letter-spacing: -0.015em;\n cursor: pointer;\n border: none;\n background: transparent;\n font-family: inherit;\n border-radius: var(--tcw-radius);\n transition: background var(--tcw-dur-fast) var(--tcw-ease);\n}\n\n.tcw-toolbar-symbol:hover {\n background: var(--tcw-hover-bg);\n}\n\n.tcw-toolbar-sep {\n width: 1px;\n height: 22px;\n background: var(--tcw-border);\n flex-shrink: 0;\n margin: 0 4px;\n}\n\n.tcw-toolbar-group {\n display: flex;\n align-items: center;\n gap: 2px;\n padding: 0 4px;\n}\n\n.tcw-toolbar-spacer {\n flex: 1;\n}\n\n.tcw-btn {\n padding: 6px 10px;\n font-size: 12px;\n font-weight: 500;\n font-family: inherit;\n border: none;\n border-radius: var(--tcw-radius);\n background: transparent;\n color: var(--tcw-text-dim);\n cursor: pointer;\n transition:\n background var(--tcw-dur-fast) var(--tcw-ease),\n color var(--tcw-dur-fast) var(--tcw-ease),\n transform var(--tcw-dur-fast) var(--tcw-ease);\n white-space: nowrap;\n line-height: 1;\n letter-spacing: -0.005em;\n}\n\n.tcw-btn:hover {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-btn:active {\n transform: translateY(0.5px);\n}\n\n.tcw-btn.tcw-active {\n background: var(--tcw-accent-soft);\n color: var(--tcw-accent);\n font-weight: 600;\n box-shadow: inset 0 0 0 1px var(--tcw-accent-line);\n}\n\n.tcw-btn:focus-visible,\n.tcw-btn-icon:focus-visible,\n.tcw-sidebar-btn:focus-visible,\n.tcw-dropdown-trigger:focus-visible {\n outline: none;\n box-shadow: var(--tcw-ring);\n}\n\n.tcw-btn-icon {\n width: 32px;\n height: 32px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n border: none;\n border-radius: var(--tcw-radius);\n background: transparent;\n color: var(--tcw-text-muted);\n cursor: pointer;\n transition:\n background var(--tcw-dur-fast) var(--tcw-ease),\n color var(--tcw-dur-fast) var(--tcw-ease);\n line-height: 1;\n position: relative;\n}\n\n.tcw-btn-icon:hover {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-btn-icon svg {\n display: block;\n}\n\n.tcw-badge-count {\n font-size: 10px;\n font-weight: 600;\n color: var(--tcw-accent);\n background: var(--tcw-accent-soft);\n padding: 1px 5px;\n border-radius: 9px;\n font-family: var(--tcw-font-mono);\n}\n\n.tcw-indicator-chips {\n display: flex;\n align-items: center;\n gap: 4px;\n padding: 0 4px;\n flex-wrap: nowrap;\n overflow: hidden;\n}\n\n.tcw-indicator-chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 6px 3px 9px;\n font-size: 11px;\n font-weight: 500;\n background: var(--tcw-accent-soft);\n color: var(--tcw-accent);\n border-radius: 9999px;\n white-space: nowrap;\n line-height: 1.4;\n letter-spacing: -0.005em;\n border: 1px solid transparent;\n transition: border-color var(--tcw-dur-fast) var(--tcw-ease);\n}\n\n.tcw-indicator-chip:hover {\n border-color: var(--tcw-accent-line);\n}\n\n.tcw-chip-remove {\n cursor: pointer;\n opacity: 0.55;\n padding: 0 2px;\n transition: opacity var(--tcw-dur-fast) var(--tcw-ease);\n border: none;\n background: transparent;\n color: inherit;\n display: inline-flex;\n align-items: center;\n}\n\n.tcw-chip-remove:hover {\n opacity: 1;\n}\n\n/* === Dropdown === */\n\n.tcw-dropdown-trigger {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 6px 10px;\n font-size: 12px;\n font-weight: 500;\n font-family: inherit;\n border: none;\n border-radius: var(--tcw-radius);\n background: transparent;\n color: var(--tcw-text-dim);\n cursor: pointer;\n transition: background var(--tcw-dur-fast) var(--tcw-ease), color var(--tcw-dur-fast) var(--tcw-ease);\n white-space: nowrap;\n line-height: 1;\n}\n\n.tcw-dropdown-trigger:hover {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-dropdown {\n position: absolute;\n top: 100%;\n margin-top: 6px;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-lg);\n box-shadow: var(--tcw-shadow-lg);\n z-index: 50;\n padding: 6px;\n max-height: 420px;\n overflow-y: auto;\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n animation: tcw-pop-in var(--tcw-dur-normal) var(--tcw-ease-out);\n}\n\n@keyframes tcw-pop-in {\n from { opacity: 0; transform: translateY(-4px) scale(0.98); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n}\n\n.tcw-dropdown-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 7px 10px;\n font-size: 12.5px;\n color: var(--tcw-text-dim);\n cursor: pointer;\n transition: background var(--tcw-dur-fast) var(--tcw-ease), color var(--tcw-dur-fast) var(--tcw-ease);\n width: 100%;\n border: none;\n background: transparent;\n font-family: inherit;\n text-align: left;\n border-radius: var(--tcw-radius-sm);\n gap: 12px;\n}\n\n.tcw-dropdown-item:hover {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-dropdown-item.tcw-active {\n color: var(--tcw-accent);\n background: var(--tcw-accent-soft);\n font-weight: 600;\n}\n\n.tcw-dropdown-label {\n text-transform: uppercase;\n font-size: 10px;\n color: var(--tcw-text-muted);\n letter-spacing: 0.08em;\n padding: 8px 10px 4px;\n font-weight: 600;\n}\n\n.tcw-dropdown-divider {\n border-top: 1px solid var(--tcw-divider);\n margin: 6px 4px;\n}\n\n.tcw-tag {\n font-size: 10px;\n font-family: var(--tcw-font-mono);\n color: var(--tcw-text-muted);\n padding: 2px 6px;\n border-radius: 4px;\n background: var(--tcw-hover-bg);\n letter-spacing: 0.02em;\n}\n\n/* === Body === */\n\n.tcw-body {\n display: flex;\n flex: 1;\n min-height: 0;\n background: var(--tcw-bg);\n}\n\n.tcw-chart-container {\n flex: 1;\n min-width: 0;\n min-height: 0;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n/* === Sidebar === */\n\n.tcw-sidebar {\n width: 40px;\n flex-shrink: 0;\n background: linear-gradient(180deg, var(--tcw-bg-elevated) 0%, var(--tcw-bg-surface) 100%);\n border-right: 1px solid var(--tcw-border);\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 6px 0;\n overflow: visible;\n box-shadow: var(--tcw-shadow-sm);\n}\n\n.tcw-tool-group-wrap {\n position: relative;\n}\n\n.tcw-sidebar-btn {\n width: 32px;\n height: 32px;\n margin: 1px 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n cursor: pointer;\n font-family: inherit;\n transition:\n background var(--tcw-dur-fast) var(--tcw-ease),\n color var(--tcw-dur-fast) var(--tcw-ease);\n position: relative;\n flex-shrink: 0;\n line-height: 1;\n border-radius: var(--tcw-radius);\n}\n\n.tcw-sidebar-btn:hover {\n color: var(--tcw-text);\n background: var(--tcw-hover-bg);\n}\n\n.tcw-sidebar-btn.tcw-active {\n color: var(--tcw-accent);\n background: var(--tcw-accent-soft);\n box-shadow: inset 2px 0 0 var(--tcw-accent);\n}\n\n.tcw-sidebar-btn.tcw-danger:hover {\n color: var(--tcw-red);\n background: var(--tcw-red-soft);\n}\n\n.tcw-multi-dot {\n position: absolute;\n bottom: 3px;\n right: 3px;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: var(--tcw-text-muted);\n opacity: 0.7;\n}\n\n.tcw-sidebar-divider {\n width: 22px;\n height: 1px;\n background: var(--tcw-divider);\n margin: 6px 0;\n flex-shrink: 0;\n}\n\n.tcw-sidebar-spacer {\n flex: 1;\n}\n\n/* === Flyout === */\n\n.tcw-flyout {\n position: absolute;\n left: 100%;\n top: 0;\n margin-left: 6px;\n min-width: 180px;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-lg);\n box-shadow: var(--tcw-shadow-lg);\n z-index: 30;\n padding: 6px;\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n animation: tcw-slide-right var(--tcw-dur-normal) var(--tcw-ease-out);\n}\n\n@keyframes tcw-slide-right {\n from { opacity: 0; transform: translateX(-4px); }\n to { opacity: 1; transform: translateX(0); }\n}\n\n.tcw-flyout-header {\n text-transform: uppercase;\n font-size: 10px;\n color: var(--tcw-text-muted);\n letter-spacing: 0.08em;\n padding: 6px 10px 4px;\n font-weight: 600;\n}\n\n.tcw-flyout-item {\n padding: 7px 10px;\n font-size: 12.5px;\n color: var(--tcw-text-dim);\n cursor: pointer;\n width: 100%;\n display: block;\n border: none;\n background: transparent;\n text-align: left;\n font-family: inherit;\n border-radius: var(--tcw-radius-sm);\n transition: background var(--tcw-dur-fast) var(--tcw-ease), color var(--tcw-dur-fast) var(--tcw-ease);\n}\n\n.tcw-flyout-item:hover {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-flyout-item.tcw-active {\n color: var(--tcw-accent);\n background: var(--tcw-accent-soft);\n font-weight: 600;\n}\n\n/* Pinned (favorited) flyout item — a star marker on the right. */\n.tcw-flyout-item.tcw-flyout-faved::after {\n content: '★';\n float: right;\n color: var(--tcw-accent);\n font-size: 11px;\n line-height: 1.4;\n}\n\n/* Favorites strip — pinned drawing tools above the tool groups. */\n.tcw-sidebar-favorites {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n.tcw-sidebar-favorites .tcw-sidebar-btn {\n position: relative;\n}\n.tcw-sidebar-favorites .tcw-sidebar-btn::before {\n content: '';\n position: absolute;\n top: 3px;\n right: 3px;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: var(--tcw-accent);\n opacity: 0.7;\n}\n\n/* === Modal === */\n\n.tcw-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(8, 10, 18, 0.55);\n backdrop-filter: blur(6px) saturate(140%);\n -webkit-backdrop-filter: blur(6px) saturate(140%);\n z-index: 100;\n animation: tcw-fade-in var(--tcw-dur-normal) var(--tcw-ease-out);\n}\n\n@keyframes tcw-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.tcw-modal {\n position: fixed;\n z-index: 101;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-xl);\n width: 520px;\n max-width: 92vw;\n max-height: 82vh;\n display: flex;\n flex-direction: column;\n box-shadow: var(--tcw-shadow-xl);\n animation: tcw-modal-in var(--tcw-dur-slow) var(--tcw-ease-out);\n}\n\n@keyframes tcw-modal-in {\n from { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }\n to { opacity: 1; transform: translate(-50%, -50%) scale(1); }\n}\n\n.tcw-modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 22px;\n border-bottom: 1px solid var(--tcw-divider);\n}\n\n.tcw-modal-header h3 {\n font-size: 15px;\n font-weight: 650;\n margin: 0;\n color: var(--tcw-text);\n letter-spacing: -0.015em;\n}\n\n.tcw-modal-close {\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n cursor: pointer;\n border-radius: var(--tcw-radius);\n transition: background var(--tcw-dur-fast) var(--tcw-ease), color var(--tcw-dur-fast) var(--tcw-ease);\n}\n\n.tcw-modal-close:hover {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-modal-tabs {\n display: flex;\n gap: 4px;\n padding: 0 18px;\n border-bottom: 1px solid var(--tcw-divider);\n}\n\n.tcw-modal-tab {\n padding: 11px 14px;\n font-size: 12px;\n font-weight: 500;\n color: var(--tcw-text-muted);\n border: none;\n background: transparent;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n margin-bottom: -1px;\n transition: color var(--tcw-dur-fast) var(--tcw-ease), border-color var(--tcw-dur-fast) var(--tcw-ease);\n font-family: inherit;\n text-transform: capitalize;\n letter-spacing: -0.005em;\n}\n\n.tcw-modal-tab:hover {\n color: var(--tcw-text-dim);\n}\n\n.tcw-modal-tab.tcw-active {\n color: var(--tcw-accent);\n border-bottom-color: var(--tcw-accent);\n font-weight: 600;\n}\n\n.tcw-modal-body {\n padding: 20px 22px;\n overflow-y: auto;\n flex: 1;\n}\n\n.tcw-settings-section {\n margin-bottom: 22px;\n}\n\n.tcw-settings-section:last-child {\n margin-bottom: 0;\n}\n\n.tcw-settings-section-title {\n font-size: 10.5px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.09em;\n color: var(--tcw-text-muted);\n margin-bottom: 12px;\n}\n\n.tcw-settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 0;\n}\n\n.tcw-settings-row + .tcw-settings-row {\n border-top: 1px solid var(--tcw-divider);\n}\n\n.tcw-settings-label {\n font-size: 13px;\n color: var(--tcw-text-dim);\n letter-spacing: -0.005em;\n}\n\n.tcw-color-picker-wrap {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.tcw-color-picker-wrap input[type='color'] {\n width: 28px;\n height: 28px;\n border: 1px solid var(--tcw-border-strong);\n border-radius: var(--tcw-radius-sm);\n background: transparent;\n cursor: pointer;\n padding: 2px;\n}\n\n.tcw-color-picker-wrap input[type='color']::-webkit-color-swatch-wrapper {\n padding: 0;\n}\n\n.tcw-color-picker-wrap input[type='color']::-webkit-color-swatch {\n border: none;\n border-radius: 2px;\n}\n\n.tcw-color-hex {\n font-size: 11px;\n font-family: var(--tcw-font-mono);\n color: var(--tcw-text-muted);\n min-width: 64px;\n letter-spacing: 0.01em;\n}\n\n.tcw-toggle {\n width: 36px;\n height: 20px;\n background: rgba(255, 255, 255, 0.12);\n border-radius: 10px;\n position: relative;\n cursor: pointer;\n transition: background var(--tcw-dur-fast) var(--tcw-ease);\n border: none;\n padding: 0;\n flex-shrink: 0;\n}\n\n.tcw-toggle::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n background: var(--tcw-text-muted);\n transition:\n left var(--tcw-dur-normal) var(--tcw-ease-out),\n background var(--tcw-dur-fast) var(--tcw-ease);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n}\n\n.tcw-toggle.tcw-on {\n background: var(--tcw-accent);\n box-shadow: 0 0 0 2px var(--tcw-accent-glow);\n}\n\n.tcw-toggle.tcw-on::after {\n left: 18px;\n background: #fff;\n}\n\n.tcw-root[data-tcw-theme='light'] .tcw-toggle {\n background: rgba(0, 0, 0, 0.12);\n}\n\n.tcw-root[data-tcw-theme='light'] .tcw-toggle::after {\n background: #999;\n}\n\n.tcw-root[data-tcw-theme='light'] .tcw-toggle.tcw-on {\n background: var(--tcw-accent);\n}\n\n.tcw-root[data-tcw-theme='light'] .tcw-toggle.tcw-on::after {\n background: #fff;\n}\n\n.tcw-settings-select {\n padding: 6px 10px;\n font-size: 12px;\n font-family: inherit;\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius);\n color: var(--tcw-text);\n cursor: pointer;\n transition: border-color var(--tcw-dur-fast) var(--tcw-ease);\n}\n\n.tcw-settings-select:hover {\n border-color: var(--tcw-border-strong);\n}\n\n.tcw-settings-select:focus-visible {\n outline: none;\n border-color: var(--tcw-accent);\n box-shadow: var(--tcw-ring);\n}\n\n.tcw-modal-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 22px;\n border-top: 1px solid var(--tcw-divider);\n background: var(--tcw-bg-surface);\n border-radius: 0 0 var(--tcw-radius-xl) var(--tcw-radius-xl);\n}\n\n.tcw-reset-link {\n font-size: 12px;\n color: var(--tcw-text-muted);\n cursor: pointer;\n border: none;\n background: transparent;\n font-family: inherit;\n text-decoration: underline;\n text-underline-offset: 2px;\n text-decoration-color: var(--tcw-border-strong);\n transition: color var(--tcw-dur-fast) var(--tcw-ease), text-decoration-color var(--tcw-dur-fast) var(--tcw-ease);\n}\n\n.tcw-reset-link:hover {\n color: var(--tcw-text-dim);\n text-decoration-color: currentColor;\n}\n\n.tcw-done-btn {\n padding: 8px 18px;\n font-size: 12.5px;\n font-weight: 600;\n background: var(--tcw-accent);\n color: #fff;\n border: none;\n border-radius: var(--tcw-radius);\n cursor: pointer;\n font-family: inherit;\n transition:\n background var(--tcw-dur-fast) var(--tcw-ease),\n transform var(--tcw-dur-fast) var(--tcw-ease),\n box-shadow var(--tcw-dur-fast) var(--tcw-ease);\n letter-spacing: -0.005em;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.15);\n}\n\n.tcw-done-btn:hover {\n background: var(--tcw-accent-hover);\n box-shadow: var(--tcw-shadow-md), 0 0 0 3px var(--tcw-accent-glow);\n}\n\n.tcw-done-btn:active {\n transform: translateY(0.5px);\n}\n\n/* === Status Bar === */\n\n.tcw-statusbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 7px 16px;\n border-top: 1px solid var(--tcw-border);\n background: var(--tcw-bg-surface);\n font-size: 11px;\n color: var(--tcw-text-muted);\n flex-shrink: 0;\n font-feature-settings: 'tnum';\n}\n\n.tcw-status-indicator {\n display: flex;\n align-items: center;\n gap: 7px;\n font-weight: 500;\n}\n\n.tcw-status-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--tcw-text-muted);\n transition: background var(--tcw-dur-normal) var(--tcw-ease), box-shadow var(--tcw-dur-normal) var(--tcw-ease);\n position: relative;\n}\n\n.tcw-status-dot.tcw-connected {\n background: var(--tcw-green);\n box-shadow: 0 0 0 3px rgba(38, 161, 123, 0.18);\n}\n\n.tcw-status-dot.tcw-connected::after {\n content: '';\n position: absolute;\n inset: -3px;\n border-radius: 50%;\n border: 2px solid var(--tcw-green);\n opacity: 0;\n animation: tcw-pulse 2.2s ease-out infinite;\n}\n\n@keyframes tcw-pulse {\n 0% { opacity: 0.6; transform: scale(0.6); }\n 100% { opacity: 0; transform: scale(2.2); }\n}\n\n.tcw-status-dot.tcw-error {\n background: var(--tcw-red);\n box-shadow: 0 0 0 3px rgba(242, 54, 69, 0.2);\n}\n\n/* === Command Palette === */\n\n.tcw-cmd-palette {\n position: fixed;\n z-index: 102;\n top: 18%;\n left: 50%;\n transform: translateX(-50%);\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-xl);\n width: 560px;\n max-width: 92vw;\n max-height: 62vh;\n display: flex;\n flex-direction: column;\n box-shadow: var(--tcw-shadow-xl);\n overflow: hidden;\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n animation: tcw-modal-in var(--tcw-dur-slow) var(--tcw-ease-out);\n}\n\n.tcw-cmd-header {\n display: flex;\n align-items: center;\n padding: 14px 18px;\n border-bottom: 1px solid var(--tcw-divider);\n gap: 12px;\n}\n\n.tcw-cmd-icon {\n font-size: 18px;\n color: var(--tcw-text-muted);\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n}\n\n.tcw-cmd-input {\n flex: 1;\n background: transparent;\n border: none;\n outline: none;\n font-size: 14.5px;\n font-family: inherit;\n color: var(--tcw-text);\n caret-color: var(--tcw-accent);\n letter-spacing: -0.005em;\n}\n\n.tcw-cmd-input::placeholder {\n color: var(--tcw-text-muted);\n}\n\n.tcw-cmd-hint {\n font-size: 10px;\n color: var(--tcw-text-muted);\n font-family: var(--tcw-font-mono);\n border: 1px solid var(--tcw-border);\n background: var(--tcw-bg-surface);\n padding: 3px 7px;\n border-radius: 4px;\n flex-shrink: 0;\n box-shadow: inset 0 -1px 0 var(--tcw-border);\n}\n\n.tcw-cmd-list {\n overflow-y: auto;\n flex: 1;\n padding: 6px;\n max-height: 380px;\n}\n\n.tcw-cmd-category {\n text-transform: uppercase;\n font-size: 10px;\n color: var(--tcw-text-muted);\n letter-spacing: 0.09em;\n padding: 10px 10px 4px;\n font-weight: 600;\n}\n\n.tcw-cmd-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 9px 12px;\n font-size: 13px;\n color: var(--tcw-text-dim);\n cursor: pointer;\n width: 100%;\n border: none;\n background: transparent;\n font-family: inherit;\n text-align: left;\n transition: background var(--tcw-dur-fast) var(--tcw-ease), color var(--tcw-dur-fast) var(--tcw-ease);\n gap: 10px;\n border-radius: var(--tcw-radius);\n letter-spacing: -0.005em;\n}\n\n.tcw-cmd-item:hover,\n.tcw-cmd-item.tcw-selected {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-cmd-item.tcw-selected {\n box-shadow: inset 2px 0 0 var(--tcw-accent);\n}\n\n.tcw-cmd-item.tcw-active .tcw-cmd-item-label {\n color: var(--tcw-accent);\n font-weight: 600;\n}\n\n.tcw-cmd-item-label {\n flex: 1;\n}\n\n.tcw-cmd-check {\n color: var(--tcw-accent);\n font-size: 14px;\n font-weight: 700;\n}\n\n.tcw-cmd-kbd {\n font-size: 10px;\n font-family: var(--tcw-font-mono);\n color: var(--tcw-text-muted);\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n padding: 2px 6px;\n border-radius: 4px;\n box-shadow: inset 0 -1px 0 var(--tcw-border);\n}\n\n.tcw-cmd-empty {\n padding: 32px 16px;\n text-align: center;\n color: var(--tcw-text-muted);\n font-size: 13px;\n}\n\n.tcw-cmd-footer {\n display: flex;\n align-items: center;\n gap: 18px;\n padding: 10px 18px;\n border-top: 1px solid var(--tcw-divider);\n font-size: 11px;\n color: var(--tcw-text-muted);\n background: var(--tcw-bg-surface);\n}\n\n/* === Watchlist === */\n\n.tcw-watchlist {\n width: 220px;\n flex-shrink: 0;\n background: var(--tcw-bg-surface);\n border-left: 1px solid var(--tcw-border);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n box-shadow: var(--tcw-shadow-sm);\n}\n\n.tcw-watchlist-header {\n padding: 10px 14px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--tcw-text-muted);\n border-bottom: 1px solid var(--tcw-divider);\n}\n\n.tcw-watchlist-list {\n flex: 1;\n overflow-y: auto;\n padding: 4px 0;\n}\n\n.tcw-watchlist-row {\n display: grid;\n grid-template-columns: 1fr auto auto;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n transition: background var(--tcw-dur-fast) var(--tcw-ease);\n border-left: 2px solid transparent;\n outline: none;\n}\n\n.tcw-watchlist-row:hover {\n background: var(--tcw-hover-bg);\n}\n\n.tcw-watchlist-row:focus-visible {\n background: var(--tcw-hover-bg);\n border-left-color: var(--tcw-accent);\n}\n\n.tcw-watchlist-row.tcw-watchlist-active {\n background: var(--tcw-accent-soft);\n border-left-color: var(--tcw-accent);\n}\n\n.tcw-watchlist-symbol {\n font-size: 12.5px;\n font-weight: 600;\n color: var(--tcw-text);\n letter-spacing: -0.005em;\n white-space: nowrap;\n}\n\n.tcw-watchlist-spark {\n width: 56px;\n height: 22px;\n align-self: center;\n}\n\n.tcw-watchlist-stats {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: 1px;\n font-variant-numeric: tabular-nums;\n font-family: var(--tcw-font-mono);\n}\n\n.tcw-watchlist-price {\n font-size: 12px;\n color: var(--tcw-text);\n font-weight: 500;\n}\n\n.tcw-watchlist-change {\n font-size: 10.5px;\n color: var(--tcw-text-muted);\n font-weight: 500;\n}\n\n.tcw-watchlist-change.tcw-up {\n color: var(--tcw-green);\n}\n\n.tcw-watchlist-change.tcw-down {\n color: var(--tcw-red);\n}\n\n@media (max-width: 768px) {\n .tcw-watchlist {\n display: none;\n }\n}\n\n/* === Toast === */\n\n.tcw-toast {\n position: absolute;\n bottom: 60px;\n left: 50%;\n transform: translate(-50%, 12px);\n z-index: 40;\n padding: 9px 14px;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border-strong);\n border-radius: 9999px;\n box-shadow: var(--tcw-shadow-lg);\n font-size: 12.5px;\n font-weight: 500;\n color: var(--tcw-text);\n opacity: 0;\n transition: opacity var(--tcw-dur-normal) var(--tcw-ease-out), transform var(--tcw-dur-normal) var(--tcw-ease-out);\n pointer-events: none;\n max-width: calc(100% - 32px);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n letter-spacing: -0.005em;\n}\n\n.tcw-toast.tcw-toast-in {\n opacity: 1;\n transform: translate(-50%, 0);\n}\n\n.tcw-toast-error {\n border-color: var(--tcw-red);\n color: var(--tcw-red);\n}\n\n/* === Replay scrubber === */\n\n.tcw-replay-bar {\n position: absolute;\n left: 50%;\n bottom: 16px;\n transform: translateX(-50%);\n z-index: 25;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border);\n border-radius: 9999px;\n box-shadow: var(--tcw-shadow-lg);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n font-size: 12px;\n color: var(--tcw-text);\n animation: tcw-pop-in var(--tcw-dur-normal) var(--tcw-ease-out);\n}\n\n.tcw-replay-btn {\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n background: transparent;\n color: var(--tcw-text-dim);\n border-radius: 9999px;\n cursor: pointer;\n font-family: inherit;\n transition: background var(--tcw-dur-fast) var(--tcw-ease), color var(--tcw-dur-fast) var(--tcw-ease);\n}\n\n.tcw-replay-btn:hover {\n background: var(--tcw-hover-bg);\n color: var(--tcw-text);\n}\n\n.tcw-replay-btn.tcw-replay-play {\n background: var(--tcw-accent);\n color: #fff;\n width: 32px;\n height: 32px;\n box-shadow: 0 0 0 3px var(--tcw-accent-glow);\n}\n\n.tcw-replay-btn.tcw-replay-play:hover {\n background: var(--tcw-accent-hover);\n}\n\n.tcw-replay-scrubber {\n appearance: none;\n -webkit-appearance: none;\n width: 220px;\n height: 4px;\n background: var(--tcw-border-strong);\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n}\n\n.tcw-replay-scrubber::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--tcw-accent);\n border: 2px solid var(--tcw-bg-elevated);\n box-shadow: 0 0 0 1px var(--tcw-accent), 0 2px 6px rgba(0, 0, 0, 0.3);\n cursor: pointer;\n}\n\n.tcw-replay-scrubber::-moz-range-thumb {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--tcw-accent);\n border: 2px solid var(--tcw-bg-elevated);\n box-shadow: 0 0 0 1px var(--tcw-accent), 0 2px 6px rgba(0, 0, 0, 0.3);\n cursor: pointer;\n}\n\n.tcw-replay-progress {\n font-size: 11px;\n font-family: var(--tcw-font-mono);\n color: var(--tcw-text-muted);\n min-width: 64px;\n text-align: center;\n font-feature-settings: 'tnum';\n}\n\n.tcw-replay-speed {\n padding: 4px 8px;\n font-size: 11px;\n font-family: var(--tcw-font-mono);\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius);\n color: var(--tcw-text-dim);\n cursor: pointer;\n}\n\n.tcw-replay-speed:hover {\n border-color: var(--tcw-border-strong);\n}\n\n@media (max-width: 640px) {\n .tcw-replay-scrubber {\n width: 140px;\n }\n .tcw-replay-progress {\n display: none;\n }\n}\n\n/* === Hotkey sheet === */\n\n.tcw-hotkey-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 24px 32px;\n}\n\n.tcw-hotkey-section .tcw-settings-section-title {\n margin-bottom: 8px;\n}\n\n.tcw-hotkey-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 6px 0;\n border-bottom: 1px solid var(--tcw-divider);\n}\n\n.tcw-hotkey-row:last-child {\n border-bottom: none;\n}\n\n.tcw-hotkey-keys {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-shrink: 0;\n}\n\n.tcw-hotkey-sep {\n font-size: 10px;\n color: var(--tcw-text-muted);\n}\n\n.tcw-hotkey-label {\n font-size: 12.5px;\n color: var(--tcw-text-dim);\n text-align: right;\n letter-spacing: -0.005em;\n}\n\n@media (max-width: 640px) {\n .tcw-hotkey-grid {\n grid-template-columns: 1fr;\n gap: 18px;\n }\n}\n\n/* === Scrollbar (subtle, premium feel) === */\n\n.tcw-dropdown::-webkit-scrollbar,\n.tcw-modal-body::-webkit-scrollbar,\n.tcw-cmd-list::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.tcw-dropdown::-webkit-scrollbar-track,\n.tcw-modal-body::-webkit-scrollbar-track,\n.tcw-cmd-list::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.tcw-dropdown::-webkit-scrollbar-thumb,\n.tcw-modal-body::-webkit-scrollbar-thumb,\n.tcw-cmd-list::-webkit-scrollbar-thumb {\n background: var(--tcw-border-strong);\n border-radius: 4px;\n border: 2px solid transparent;\n background-clip: padding-box;\n}\n\n.tcw-dropdown::-webkit-scrollbar-thumb:hover,\n.tcw-modal-body::-webkit-scrollbar-thumb:hover,\n.tcw-cmd-list::-webkit-scrollbar-thumb:hover {\n background: var(--tcw-text-muted);\n background-clip: padding-box;\n border: 2px solid transparent;\n}\n\n/* === Responsive === */\n\n@media (max-width: 768px) {\n .tcw-toolbar {\n padding: 0 4px;\n height: 44px; /* Apple HIG minimum touch target */\n }\n .tcw-toolbar-sep {\n display: none;\n }\n .tcw-indicator-chips {\n display: none;\n }\n .tcw-btn,\n .tcw-dropdown-trigger {\n padding: 10px 12px; /* fatter tap target */\n font-size: 13px;\n }\n .tcw-btn-icon {\n width: 40px;\n height: 40px;\n }\n .tcw-sidebar {\n width: 44px;\n }\n .tcw-sidebar-btn {\n width: 40px;\n height: 40px;\n }\n .tcw-modal,\n .tcw-cmd-palette {\n width: 94vw;\n border-radius: var(--tcw-radius-lg);\n }\n\n /* Hide the right-side watchlist on tablets too — already declared but\n * grouped here for clarity. */\n .tcw-watchlist {\n display: none;\n }\n\n /* Replay scrubber compact mode */\n .tcw-replay-scrubber {\n width: 120px;\n }\n .tcw-replay-progress {\n display: none;\n }\n}\n\n/* === Mobile bottom-sheet pattern ===\n * On phones, swap floating-centered modals for full-width sheets that slide\n * up from the bottom. Bigger hit targets, no awkward off-screen clipping,\n * matches how every modern iOS/Android app handles overflow UI.\n */\n@media (max-width: 640px) {\n .tcw-modal,\n .tcw-cmd-palette {\n top: auto;\n bottom: 0;\n left: 0;\n right: 0;\n transform: none;\n width: 100vw;\n max-width: 100vw;\n max-height: 92vh;\n border-radius: var(--tcw-radius-xl) var(--tcw-radius-xl) 0 0;\n border-bottom: none;\n /* Grab handle */\n padding-top: 8px;\n }\n\n /* Visual \"grab handle\" affordance — a short pill at the top of every sheet */\n .tcw-modal::before,\n .tcw-cmd-palette::before {\n content: '';\n display: block;\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background: var(--tcw-border-strong);\n margin: 0 auto 8px;\n opacity: 0.7;\n }\n\n /* Reuse the existing modal-in animation but slide from below instead of\n * popping from center, so the entrance reads as a sheet, not a dialog. */\n @keyframes tcw-modal-in {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .tcw-modal,\n .tcw-cmd-palette {\n animation: tcw-modal-in var(--tcw-dur-slow) var(--tcw-ease-out);\n }\n\n /* Safe-area-aware footer padding for iPhone notch / home indicator */\n .tcw-modal-footer,\n .tcw-cmd-footer {\n padding-bottom: max(14px, env(safe-area-inset-bottom));\n }\n\n /* Larger tap targets inside sheets */\n .tcw-modal-tab {\n padding: 14px 16px;\n }\n .tcw-cmd-item {\n padding: 12px 14px;\n font-size: 14px;\n }\n .tcw-dropdown-item {\n padding: 12px 12px;\n font-size: 14px;\n }\n\n /* Hotkey sheet — single column on phones */\n .tcw-hotkey-grid {\n grid-template-columns: 1fr;\n }\n}\n\n/* === Price alerts panel === */\n\n.tcw-alerts-panel {\n position: absolute;\n top: 50px;\n right: 12px;\n z-index: 40;\n width: 300px;\n max-width: calc(100vw - 24px);\n display: flex;\n flex-direction: column;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border-strong);\n border-radius: var(--tcw-radius-lg);\n box-shadow: var(--tcw-shadow-lg);\n overflow: hidden;\n animation: tcw-alerts-in var(--tcw-dur, 160ms) var(--tcw-ease, ease) both;\n}\n\n@keyframes tcw-alerts-in {\n from { opacity: 0; transform: translateY(-6px) scale(0.98); }\n to { opacity: 1; transform: none; }\n}\n\n.tcw-alerts-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n font-weight: 600;\n font-size: 13px;\n color: var(--tcw-text);\n border-bottom: 1px solid var(--tcw-border);\n}\n\n.tcw-alerts-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-alerts-close:hover {\n background: var(--tcw-bg-surface);\n color: var(--tcw-text);\n}\n\n.tcw-alerts-form {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 8px;\n padding: 12px;\n border-bottom: 1px solid var(--tcw-border);\n}\n\n.tcw-alerts-form input,\n.tcw-alerts-form select {\n height: 32px;\n padding: 0 8px;\n font-size: 12px;\n color: var(--tcw-text);\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-sm);\n outline: none;\n}\n.tcw-alerts-form input:focus,\n.tcw-alerts-form select:focus {\n border-color: var(--tcw-accent);\n box-shadow: 0 0 0 2px var(--tcw-accent-soft);\n}\n.tcw-alerts-message,\n.tcw-alerts-source {\n grid-column: 1 / -1;\n}\n\n.tcw-alerts-add {\n grid-column: 1 / -1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n height: 34px;\n font-size: 12px;\n font-weight: 600;\n color: #fff;\n background: var(--tcw-accent);\n border: none;\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-alerts-add:hover {\n background: var(--tcw-accent-hover);\n}\n\n.tcw-alerts-list {\n max-height: 260px;\n overflow-y: auto;\n padding: 6px;\n}\n\n.tcw-alerts-empty {\n padding: 18px 12px;\n text-align: center;\n font-size: 12px;\n color: var(--tcw-text-muted);\n line-height: 1.5;\n}\n\n.tcw-alerts-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 8px 8px;\n border-radius: var(--tcw-radius-sm);\n}\n.tcw-alerts-row:hover {\n background: var(--tcw-bg-surface);\n}\n.tcw-alerts-triggered {\n background: var(--tcw-accent-soft);\n}\n\n.tcw-alerts-info {\n min-width: 0;\n}\n.tcw-alerts-row-main {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n font-weight: 600;\n color: var(--tcw-text);\n font-variant-numeric: tabular-nums;\n}\n.tcw-alerts-row-note {\n margin-top: 2px;\n font-size: 11px;\n color: var(--tcw-text-muted);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.tcw-alerts-badge {\n font-size: 9px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n padding: 2px 5px;\n border-radius: 999px;\n color: var(--tcw-accent);\n background: var(--tcw-accent-soft);\n}\n\n.tcw-alerts-del {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-alerts-del:hover {\n background: var(--tcw-red-soft);\n color: var(--tcw-red);\n}\n\n@media (max-width: 640px) {\n .tcw-alerts-panel {\n top: auto;\n bottom: 0;\n left: 0;\n right: 0;\n width: 100vw;\n max-width: 100vw;\n border-radius: var(--tcw-radius-xl) var(--tcw-radius-xl) 0 0;\n }\n .tcw-alerts-list {\n max-height: 40vh;\n padding-bottom: max(8px, env(safe-area-inset-bottom));\n }\n}\n\n/* === Data Window === */\n\n.tcw-datawin {\n position: absolute;\n top: 50px;\n right: 12px;\n z-index: 38;\n width: 200px;\n display: flex;\n flex-direction: column;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border-strong);\n border-radius: var(--tcw-radius-lg);\n box-shadow: var(--tcw-shadow-lg);\n overflow: hidden;\n animation: tcw-alerts-in var(--tcw-dur, 160ms) var(--tcw-ease, ease) both;\n}\n.tcw-datawin-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 9px 12px;\n font-size: 12px;\n font-weight: 600;\n color: var(--tcw-text);\n border-bottom: 1px solid var(--tcw-border);\n}\n.tcw-datawin-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-datawin-close:hover { background: var(--tcw-bg-surface); color: var(--tcw-text); }\n.tcw-datawin-body { padding: 6px 4px; max-height: 360px; overflow-y: auto; }\n.tcw-datawin-empty { padding: 14px; text-align: center; font-size: 12px; color: var(--tcw-text-muted); }\n.tcw-datawin-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 3px 8px;\n font-size: 12px;\n}\n.tcw-datawin-label { color: var(--tcw-text-muted); }\n.tcw-datawin-value { color: var(--tcw-text); font-variant-numeric: tabular-nums; font-weight: 600; }\n.tcw-datawin-value.tcw-up { color: var(--tcw-green); }\n.tcw-datawin-value.tcw-down { color: var(--tcw-red); }\n.tcw-datawin-sub {\n margin: 6px 8px 2px;\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--tcw-text-muted);\n border-top: 1px solid var(--tcw-border);\n padding-top: 6px;\n}\n\n@media (max-width: 640px) {\n .tcw-datawin { top: auto; bottom: 0; left: 0; right: 0; width: 100vw; border-radius: var(--tcw-radius-xl) var(--tcw-radius-xl) 0 0; }\n}\n\n/* === Depth-of-market ladder === */\n\n.tcw-ladder {\n position: absolute;\n top: 50px;\n right: 12px;\n z-index: 41;\n width: 240px;\n display: flex;\n flex-direction: column;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border-strong);\n border-radius: var(--tcw-radius-lg);\n box-shadow: var(--tcw-shadow-lg);\n overflow: hidden;\n animation: tcw-alerts-in var(--tcw-dur, 160ms) var(--tcw-ease, ease) both;\n}\n.tcw-ladder-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n font-size: 13px;\n font-weight: 600;\n color: var(--tcw-text);\n border-bottom: 1px solid var(--tcw-border);\n}\n.tcw-ladder-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-ladder-close:hover { background: var(--tcw-bg-surface); color: var(--tcw-text); }\n.tcw-ladder-cols {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n padding: 4px 0;\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--tcw-text-muted);\n border-bottom: 1px solid var(--tcw-border);\n}\n.tcw-ladder-cols span { text-align: center; }\n.tcw-ladder-body { max-height: 360px; overflow-y: auto; }\n.tcw-ladder-empty { padding: 18px; text-align: center; font-size: 12px; color: var(--tcw-text-muted); }\n\n.tcw-ladder-row {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n height: 22px;\n font-variant-numeric: tabular-nums;\n}\n.tcw-ladder-cell {\n position: relative;\n display: flex;\n align-items: center;\n border: none;\n background: transparent;\n font-size: 11px;\n cursor: pointer;\n font-variant-numeric: tabular-nums;\n overflow: hidden;\n}\n.tcw-ladder-cell:disabled { cursor: default; }\n/* Depth bar grows from the centre column outward. */\n.tcw-ladder-cell::before {\n content: '';\n position: absolute;\n top: 1px; bottom: 1px;\n width: var(--w, 0%);\n}\n.tcw-ladder-bid { justify-content: flex-end; padding-right: 8px; color: var(--tcw-green); }\n.tcw-ladder-bid::before { right: 0; background: var(--tcw-green-soft); }\n.tcw-ladder-bid span, .tcw-ladder-bid { position: relative; }\n.tcw-ladder-ask { justify-content: flex-start; padding-left: 8px; color: var(--tcw-red); }\n.tcw-ladder-ask::before { left: 0; background: var(--tcw-red-soft); }\n.tcw-ladder-cell:not(:disabled):hover::before { opacity: 0.7; filter: brightness(1.4); }\n.tcw-ladder-cell > * { position: relative; }\n.tcw-ladder-price {\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 600;\n color: var(--tcw-text-dim);\n border-left: 1px solid var(--tcw-border);\n border-right: 1px solid var(--tcw-border);\n}\n.tcw-ladder-price.tcw-ask { color: var(--tcw-red); }\n.tcw-ladder-price.tcw-bid { color: var(--tcw-green); }\n\n@media (max-width: 640px) {\n .tcw-ladder { top: auto; bottom: 0; left: 0; right: 0; width: 100vw; border-radius: var(--tcw-radius-xl) var(--tcw-radius-xl) 0 0; }\n .tcw-ladder-body { max-height: 45vh; padding-bottom: max(0px, env(safe-area-inset-bottom)); }\n}\n\n/* === Bracket order placement === */\n\n.tcw-btn-long:hover { color: var(--tcw-green) !important; }\n.tcw-btn-short:hover { color: var(--tcw-red) !important; }\n\n.tcw-bracket-bar {\n position: absolute;\n top: 52px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 45;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 8px 7px 14px;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border-strong);\n border-radius: 999px;\n box-shadow: var(--tcw-shadow-lg);\n animation: tcw-alerts-in var(--tcw-dur, 160ms) var(--tcw-ease, ease) both;\n}\n.tcw-bracket-label {\n font-size: 12px;\n font-weight: 700;\n}\n.tcw-bracket-label[data-side='buy'] { color: var(--tcw-green); }\n.tcw-bracket-label[data-side='sell'] { color: var(--tcw-red); }\n.tcw-bracket-hint {\n font-size: 11px;\n color: var(--tcw-text-muted);\n}\n.tcw-bracket-cancel, .tcw-bracket-confirm {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n height: 30px;\n padding: 0 12px;\n font-size: 12px;\n font-weight: 600;\n border: none;\n border-radius: 999px;\n cursor: pointer;\n}\n.tcw-bracket-cancel {\n color: var(--tcw-text-dim);\n background: var(--tcw-bg-surface);\n}\n.tcw-bracket-cancel:hover { color: var(--tcw-text); }\n.tcw-bracket-confirm {\n color: #fff;\n background: var(--tcw-accent);\n}\n.tcw-bracket-confirm:hover { background: var(--tcw-accent-hover); }\n\n@media (max-width: 640px) {\n .tcw-bracket-bar { width: calc(100vw - 24px); justify-content: space-between; }\n .tcw-bracket-hint { display: none; }\n}\n\n/* === Drawing style + templates popover === */\n\n.tcw-style-panel {\n position: absolute;\n bottom: 12px;\n left: 52px;\n z-index: 40;\n width: 220px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border-strong);\n border-radius: var(--tcw-radius-lg);\n box-shadow: var(--tcw-shadow-lg);\n animation: tcw-alerts-in var(--tcw-dur, 160ms) var(--tcw-ease, ease) both;\n}\n.tcw-style-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n font-size: 12px;\n font-weight: 600;\n color: var(--tcw-text);\n}\n.tcw-style-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-style-close:hover { background: var(--tcw-bg-surface); color: var(--tcw-text); }\n\n.tcw-style-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }\n.tcw-style-swatch {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: 1px solid var(--tcw-border-strong);\n cursor: pointer;\n padding: 0;\n}\n.tcw-style-swatch.tcw-on { outline: 2px solid var(--tcw-accent); outline-offset: 1px; }\n.tcw-style-picker {\n width: 22px;\n height: 22px;\n padding: 0;\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-sm);\n background: transparent;\n cursor: pointer;\n}\n.tcw-style-width, .tcw-style-dash {\n flex: 1;\n min-width: 0;\n height: 26px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-sm);\n color: var(--tcw-text-dim);\n cursor: pointer;\n font-size: 11px;\n}\n.tcw-style-width span { width: 16px; background: currentColor; border-radius: 2px; }\n.tcw-style-width.tcw-on, .tcw-style-dash.tcw-on {\n border-color: var(--tcw-accent);\n color: var(--tcw-accent);\n}\n\n.tcw-style-subhead {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--tcw-text-muted);\n margin-top: 2px;\n}\n.tcw-style-save { display: flex; gap: 6px; }\n.tcw-style-name {\n flex: 1;\n min-width: 0;\n height: 28px;\n padding: 0 8px;\n font-size: 12px;\n color: var(--tcw-text);\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-sm);\n outline: none;\n}\n.tcw-style-name:focus { border-color: var(--tcw-accent); }\n.tcw-style-savebtn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 0 10px;\n height: 28px;\n font-size: 12px;\n font-weight: 600;\n color: #fff;\n background: var(--tcw-accent);\n border: none;\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-style-savebtn:hover { background: var(--tcw-accent-hover); }\n\n.tcw-style-templates { display: flex; flex-direction: column; gap: 2px; max-height: 140px; overflow-y: auto; }\n.tcw-style-empty { font-size: 11px; color: var(--tcw-text-muted); padding: 4px 0; }\n.tcw-style-template { display: flex; align-items: center; gap: 4px; }\n.tcw-style-apply {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 5px 7px;\n font-size: 12px;\n color: var(--tcw-text);\n background: transparent;\n border: none;\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n text-align: left;\n}\n.tcw-style-apply:hover { background: var(--tcw-bg-surface); }\n.tcw-style-tdot { flex-shrink: 0; width: 9px; height: 9px; border-radius: 50%; }\n.tcw-style-tdel {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-style-tdel:hover { background: var(--tcw-red-soft); color: var(--tcw-red); }\n\n/* === Settings range slider === */\n\n.tcw-settings-range {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n.tcw-settings-range input[type='range'] {\n width: 110px;\n accent-color: var(--tcw-accent);\n cursor: pointer;\n}\n.tcw-settings-range-val {\n min-width: 38px;\n text-align: right;\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n color: var(--tcw-text-dim);\n}\n\n/* === Indicator settings dialog === */\n\n.tcw-modal-narrow {\n max-width: 360px;\n}\n.tcw-indi-input {\n width: 110px;\n height: 30px;\n padding: 0 8px;\n font-size: 12px;\n color: var(--tcw-text);\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-sm);\n outline: none;\n font-variant-numeric: tabular-nums;\n}\n.tcw-indi-input:focus {\n border-color: var(--tcw-accent);\n box-shadow: 0 0 0 2px var(--tcw-accent-soft);\n}\n.tcw-indi-color {\n width: 36px;\n height: 28px;\n padding: 2px;\n background: var(--tcw-bg-surface);\n border: 1px solid var(--tcw-border);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n\n/* === Object tree panel === */\n\n.tcw-tree-panel {\n position: absolute;\n top: 50px;\n right: 12px;\n z-index: 39;\n width: 280px;\n max-width: calc(100vw - 24px);\n display: flex;\n flex-direction: column;\n background: var(--tcw-bg-elevated);\n border: 1px solid var(--tcw-border-strong);\n border-radius: var(--tcw-radius-lg);\n box-shadow: var(--tcw-shadow-lg);\n overflow: hidden;\n animation: tcw-alerts-in var(--tcw-dur, 160ms) var(--tcw-ease, ease) both;\n}\n\n.tcw-tree-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n font-weight: 600;\n font-size: 13px;\n color: var(--tcw-text);\n border-bottom: 1px solid var(--tcw-border);\n}\n.tcw-tree-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-tree-close:hover {\n background: var(--tcw-bg-surface);\n color: var(--tcw-text);\n}\n\n.tcw-tree-section {\n padding: 6px;\n border-bottom: 1px solid var(--tcw-border);\n}\n.tcw-tree-section:last-child {\n border-bottom: none;\n}\n.tcw-tree-section-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 6px 8px 4px;\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--tcw-text-muted);\n}\n.tcw-tree-add {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-tree-add:hover {\n background: var(--tcw-accent-soft);\n color: var(--tcw-accent);\n}\n.tcw-tree-dot {\n flex-shrink: 0;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n}\n.tcw-tree-list {\n max-height: 200px;\n overflow-y: auto;\n}\n\n.tcw-tree-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 6px 8px;\n border-radius: var(--tcw-radius-sm);\n}\n.tcw-tree-row:hover {\n background: var(--tcw-bg-surface);\n}\n.tcw-tree-hidden .tcw-tree-name {\n opacity: 0.5;\n}\n.tcw-tree-name {\n min-width: 0;\n font-size: 12px;\n color: var(--tcw-text);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.tcw-tree-actions {\n display: flex;\n align-items: center;\n gap: 2px;\n flex-shrink: 0;\n}\n.tcw-tree-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n background: transparent;\n color: var(--tcw-text-muted);\n border-radius: var(--tcw-radius-sm);\n cursor: pointer;\n}\n.tcw-tree-btn:hover {\n background: var(--tcw-bg-elevated);\n color: var(--tcw-text);\n}\n.tcw-tree-btn.tcw-tree-on {\n color: var(--tcw-accent);\n}\n.tcw-tree-btn.tcw-tree-del:hover {\n background: var(--tcw-red-soft);\n color: var(--tcw-red);\n}\n.tcw-tree-empty {\n padding: 8px;\n font-size: 12px;\n color: var(--tcw-text-muted);\n}\n\n@media (max-width: 640px) {\n .tcw-tree-panel {\n top: auto;\n bottom: 0;\n left: 0;\n right: 0;\n width: 100vw;\n max-width: 100vw;\n border-radius: var(--tcw-radius-xl) var(--tcw-radius-xl) 0 0;\n }\n .tcw-tree-list {\n max-height: 30vh;\n }\n .tcw-tree-section:last-child {\n padding-bottom: max(6px, env(safe-area-inset-bottom));\n }\n}\n\n/* === Reduced motion === */\n\n@media (prefers-reduced-motion: reduce) {\n .tcw-root *,\n .tcw-root *::before,\n .tcw-root *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n", m = 0, h = "tcw-styles";
|
|
604
|
+
function g() {
|
|
605
|
+
if (m++, m > 1 || document.getElementById(h)) return;
|
|
391
606
|
let e = document.createElement("style");
|
|
392
|
-
e.id =
|
|
607
|
+
e.id = h, e.textContent = te, document.head.appendChild(e);
|
|
393
608
|
}
|
|
394
|
-
function
|
|
395
|
-
if (
|
|
396
|
-
let e = document.getElementById(
|
|
609
|
+
function _() {
|
|
610
|
+
if (m--, m > 0) return;
|
|
611
|
+
let e = document.getElementById(h);
|
|
397
612
|
e && e.remove();
|
|
398
613
|
}
|
|
399
614
|
//#endregion
|
|
400
615
|
//#region src/widget/icons.ts
|
|
401
|
-
var
|
|
616
|
+
var v = {
|
|
402
617
|
cursor: "<path d=\"M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z\"/><path d=\"M13 13l6 6\"/>",
|
|
403
618
|
trendingUp: "<polyline points=\"22 7 13.5 15.5 8.5 10.5 2 17\"/><polyline points=\"16 7 22 7 22 13\"/>",
|
|
404
619
|
minus: "<path d=\"M5 12h14\"/>",
|
|
@@ -423,15 +638,26 @@ var h = {
|
|
|
423
638
|
play: "<polygon points=\"6 3 20 12 6 21 6 3\"/>",
|
|
424
639
|
pause: "<rect width=\"4\" height=\"16\" x=\"6\" y=\"4\"/><rect width=\"4\" height=\"16\" x=\"14\" y=\"4\"/>",
|
|
425
640
|
stop: "<rect width=\"14\" height=\"14\" x=\"5\" y=\"5\" rx=\"1\"/>",
|
|
426
|
-
receipt: "<path d=\"M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z\"/><path d=\"M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8\"/><path d=\"M12 17.5v-11\"/>"
|
|
641
|
+
receipt: "<path d=\"M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z\"/><path d=\"M16 8h-6a2 2 0 1 0 0 4h4a2 2 0 1 1 0 4H8\"/><path d=\"M12 17.5v-11\"/>",
|
|
642
|
+
bell: "<path d=\"M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9\"/><path d=\"M10.3 21a1.94 1.94 0 0 0 3.4 0\"/>",
|
|
643
|
+
plus: "<path d=\"M5 12h14\"/><path d=\"M12 5v14\"/>",
|
|
644
|
+
layers: "<path d=\"m12 2 9 5-9 5-9-5 9-5Z\"/><path d=\"m3 12 9 5 9-5\"/><path d=\"m3 17 9 5 9-5\"/>",
|
|
645
|
+
eye: "<path d=\"M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/>",
|
|
646
|
+
eyeOff: "<path d=\"M9.9 4.24A9.12 9.12 0 0 1 12 4c6.5 0 10 7 10 7a13.2 13.2 0 0 1-1.67 2.68\"/><path d=\"M6.6 6.6A13.06 13.06 0 0 0 2 11s3.5 7 10 7a9.1 9.1 0 0 0 5.4-1.6\"/><path d=\"m2 2 20 20\"/>",
|
|
647
|
+
lock: "<rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/>",
|
|
648
|
+
unlock: "<rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 9.9-1\"/>",
|
|
649
|
+
palette: "<circle cx=\"13.5\" cy=\"6.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"17.5\" cy=\"10.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"8.5\" cy=\"7.5\" r=\".5\" fill=\"currentColor\"/><circle cx=\"6.5\" cy=\"12.5\" r=\".5\" fill=\"currentColor\"/><path d=\"M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2Z\"/>",
|
|
650
|
+
trendingDown: "<polyline points=\"22 17 13.5 8.5 8.5 13.5 2 7\"/><polyline points=\"16 17 22 17 22 11\"/>",
|
|
651
|
+
check: "<polyline points=\"20 6 9 17 4 12\"/>",
|
|
652
|
+
ladder: "<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"21\"/><line x1=\"3\" y1=\"8\" x2=\"21\" y2=\"8\"/><line x1=\"3\" y1=\"13\" x2=\"21\" y2=\"13\"/><line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"/>"
|
|
427
653
|
};
|
|
428
|
-
function
|
|
429
|
-
let n =
|
|
654
|
+
function y(e, t = 14) {
|
|
655
|
+
let n = v[e];
|
|
430
656
|
return n ? `<svg xmlns="http://www.w3.org/2000/svg" width="${t}" height="${t}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${n}</svg>` : "";
|
|
431
657
|
}
|
|
432
658
|
//#endregion
|
|
433
659
|
//#region src/widget/WidgetDropdown.ts
|
|
434
|
-
var
|
|
660
|
+
var b = class {
|
|
435
661
|
trigger;
|
|
436
662
|
panel;
|
|
437
663
|
_isOpen = !1;
|
|
@@ -462,7 +688,7 @@ var _ = class {
|
|
|
462
688
|
destroy() {
|
|
463
689
|
this.trigger.removeEventListener("click", this.onTriggerClick), document.removeEventListener("mousedown", this.onOutsideClick), document.removeEventListener("keydown", this.onEscapeKey), this.panel.remove();
|
|
464
690
|
}
|
|
465
|
-
},
|
|
691
|
+
}, ne = class {
|
|
466
692
|
config;
|
|
467
693
|
callbacks;
|
|
468
694
|
el;
|
|
@@ -487,11 +713,32 @@ var _ = class {
|
|
|
487
713
|
let a = document.createElement("div");
|
|
488
714
|
a.style.position = "relative", a.style.display = "inline-flex";
|
|
489
715
|
let o = document.createElement("button");
|
|
490
|
-
o.className = "tcw-dropdown-trigger", o.dataset.role = "charttype", a.appendChild(o), n.appendChild(a), this.chartTypeDropdown = new
|
|
716
|
+
o.className = "tcw-dropdown-trigger", o.dataset.role = "charttype", a.appendChild(o), n.appendChild(a), this.chartTypeDropdown = new b(a, { width: "160px" }), this.buildChartTypeMenu(), n.appendChild(this.sep());
|
|
491
717
|
let s = document.createElement("div");
|
|
492
718
|
s.style.position = "relative", s.style.display = "inline-flex";
|
|
493
719
|
let c = document.createElement("button");
|
|
494
|
-
c.className = "tcw-dropdown-trigger", c.dataset.role = "indicators", s.appendChild(c), n.appendChild(s), this.indicatorDropdown = new
|
|
720
|
+
if (c.className = "tcw-dropdown-trigger", c.dataset.role = "indicators", s.appendChild(c), n.appendChild(s), this.indicatorDropdown = new b(s, { width: "280px" }), this.buildIndicatorMenu(), this.chipsContainer = document.createElement("div"), this.chipsContainer.className = "tcw-indicator-chips", n.appendChild(this.chipsContainer), n.appendChild(this.spacer()), t.onToggleReplay) {
|
|
721
|
+
let e = this.iconBtn("play", "Bar replay", t.onToggleReplay);
|
|
722
|
+
e.dataset.role = "replay", n.appendChild(e);
|
|
723
|
+
}
|
|
724
|
+
if (t.onBracket) {
|
|
725
|
+
let e = t.onBracket, r = this.iconBtn("trendingUp", "Long bracket", () => e("buy"));
|
|
726
|
+
r.dataset.role = "long", r.classList.add("tcw-btn-long"), n.appendChild(r);
|
|
727
|
+
let i = this.iconBtn("trendingDown", "Short bracket", () => e("sell"));
|
|
728
|
+
i.dataset.role = "short", i.classList.add("tcw-btn-short"), n.appendChild(i);
|
|
729
|
+
}
|
|
730
|
+
if (t.onToggleLadder) {
|
|
731
|
+
let e = this.iconBtn("ladder", "Depth ladder", t.onToggleLadder);
|
|
732
|
+
e.dataset.role = "ladder", n.appendChild(e);
|
|
733
|
+
}
|
|
734
|
+
if (t.onToggleObjects) {
|
|
735
|
+
let e = this.iconBtn("layers", "Objects", t.onToggleObjects);
|
|
736
|
+
e.dataset.role = "objects", n.appendChild(e);
|
|
737
|
+
}
|
|
738
|
+
if (t.onToggleAlerts) {
|
|
739
|
+
let e = this.iconBtn("bell", "Price alerts", t.onToggleAlerts);
|
|
740
|
+
e.dataset.role = "alerts", n.appendChild(e);
|
|
741
|
+
}
|
|
495
742
|
let l = this.iconBtn("camera", "Screenshot", t.onScreenshot);
|
|
496
743
|
n.appendChild(l);
|
|
497
744
|
let u = this.iconBtn("settings", "Chart Settings", t.onSettings);
|
|
@@ -527,11 +774,11 @@ var _ = class {
|
|
|
527
774
|
let n = this.el.querySelector("[data-role=\"charttype\"]");
|
|
528
775
|
if (n) {
|
|
529
776
|
let t = this.config.chartTypes.find((t) => t.value === e.chartType)?.label ?? "Candles";
|
|
530
|
-
n.innerHTML = `${
|
|
777
|
+
n.innerHTML = `${y("barChart", 14)} ${t} ${y("chevronDown", 12)}`;
|
|
531
778
|
}
|
|
532
779
|
let r = this.el.querySelector("[data-role=\"indicators\"]");
|
|
533
780
|
if (r) {
|
|
534
|
-
let t = e.activeIndicators.size, n = `${
|
|
781
|
+
let t = e.activeIndicators.size, n = `${y("trendingUp", 14)} Indicators`;
|
|
535
782
|
t > 0 && (n += ` <span class="tcw-badge-count">${t}</span>`), r.innerHTML = n;
|
|
536
783
|
}
|
|
537
784
|
if (this.chipsContainer) {
|
|
@@ -543,10 +790,10 @@ var _ = class {
|
|
|
543
790
|
let n = document.createElement("span");
|
|
544
791
|
n.textContent = e.label, t.appendChild(n);
|
|
545
792
|
let r = document.createElement("button");
|
|
546
|
-
r.className = "tcw-chip-remove", r.innerHTML =
|
|
793
|
+
r.className = "tcw-chip-remove", r.innerHTML = y("x", 10), r.addEventListener("click", () => this.callbacks.onRemoveIndicator(e.instanceId)), t.appendChild(r), this.chipsContainer.appendChild(t);
|
|
547
794
|
}
|
|
548
795
|
}
|
|
549
|
-
this.themeBtn && (this.themeBtn.innerHTML = e.isDark ?
|
|
796
|
+
this.themeBtn && (this.themeBtn.innerHTML = e.isDark ? y("moon", 14) : y("sun", 14));
|
|
550
797
|
}
|
|
551
798
|
getActiveIndicatorList(e) {
|
|
552
799
|
let t = [];
|
|
@@ -567,12 +814,12 @@ var _ = class {
|
|
|
567
814
|
}
|
|
568
815
|
iconBtn(e, t, n) {
|
|
569
816
|
let r = document.createElement("button");
|
|
570
|
-
return r.className = "tcw-btn-icon", r.title = t, r.innerHTML =
|
|
817
|
+
return r.className = "tcw-btn-icon", r.title = t, r.innerHTML = y(e, 14), r.addEventListener("click", n), r;
|
|
571
818
|
}
|
|
572
819
|
destroy() {
|
|
573
820
|
this.chartTypeDropdown?.destroy(), this.indicatorDropdown?.destroy(), this.el.remove();
|
|
574
821
|
}
|
|
575
|
-
},
|
|
822
|
+
}, re = [
|
|
576
823
|
"trendingUp",
|
|
577
824
|
"minus",
|
|
578
825
|
"penLine",
|
|
@@ -581,7 +828,43 @@ var _ = class {
|
|
|
581
828
|
"gitBranch",
|
|
582
829
|
"ruler",
|
|
583
830
|
"type"
|
|
584
|
-
],
|
|
831
|
+
], ie = {
|
|
832
|
+
trendLine: "trendingUp",
|
|
833
|
+
ray: "trendingUp",
|
|
834
|
+
extendedLine: "trendingUp",
|
|
835
|
+
horizontalLine: "minus",
|
|
836
|
+
verticalLine: "penLine",
|
|
837
|
+
rectangle: "square",
|
|
838
|
+
ellipse: "square",
|
|
839
|
+
triangle: "square",
|
|
840
|
+
arrow: "trendingUp",
|
|
841
|
+
parallelChannel: "gitBranch",
|
|
842
|
+
regressionChannel: "gitBranch",
|
|
843
|
+
pitchfork: "gitBranch",
|
|
844
|
+
fibRetracement: "hash",
|
|
845
|
+
fibExtension: "hash",
|
|
846
|
+
fibTimeZones: "hash",
|
|
847
|
+
gannBox: "hash",
|
|
848
|
+
gannFan: "gitBranch",
|
|
849
|
+
elliottWave: "trendingUp",
|
|
850
|
+
priceRange: "ruler",
|
|
851
|
+
dateRange: "ruler",
|
|
852
|
+
measure: "ruler",
|
|
853
|
+
textAnnotation: "type",
|
|
854
|
+
anchoredVWAP: "trendingUp",
|
|
855
|
+
volumeProfileRange: "barChart"
|
|
856
|
+
};
|
|
857
|
+
function ae(e) {
|
|
858
|
+
return ie[e] ?? "penLine";
|
|
859
|
+
}
|
|
860
|
+
function oe(e, t) {
|
|
861
|
+
for (let n of e) {
|
|
862
|
+
let e = n.tools.find((e) => e.value === t);
|
|
863
|
+
if (e) return e.label;
|
|
864
|
+
}
|
|
865
|
+
return t;
|
|
866
|
+
}
|
|
867
|
+
var x = class {
|
|
585
868
|
config;
|
|
586
869
|
callbacks;
|
|
587
870
|
el;
|
|
@@ -590,29 +873,51 @@ var _ = class {
|
|
|
590
873
|
cursorBtn = null;
|
|
591
874
|
magnetBtn = null;
|
|
592
875
|
flyoutEl = null;
|
|
876
|
+
favoritesEl = null;
|
|
877
|
+
favoritesDivider = null;
|
|
878
|
+
favorites = [];
|
|
593
879
|
constructor(e, t, n) {
|
|
594
880
|
this.config = t, this.callbacks = n, this.el = document.createElement("div"), this.el.className = "tcw-sidebar", this.build(), e.appendChild(this.el);
|
|
595
881
|
}
|
|
596
882
|
build() {
|
|
597
883
|
let { config: e, callbacks: t, el: n } = this;
|
|
598
|
-
this.cursorBtn = document.createElement("button"), this.cursorBtn.className = "tcw-sidebar-btn", this.cursorBtn.title = "Cursor", this.cursorBtn.innerHTML =
|
|
884
|
+
this.cursorBtn = document.createElement("button"), this.cursorBtn.className = "tcw-sidebar-btn", this.cursorBtn.title = "Cursor", this.cursorBtn.innerHTML = y("cursor", 14), this.cursorBtn.addEventListener("click", t.onCancelDrawing), n.appendChild(this.cursorBtn), n.appendChild(this.divider()), t.onToggleFavorite && (this.favorites = [...e.favorites ?? []], this.favoritesEl = document.createElement("div"), this.favoritesEl.className = "tcw-sidebar-favorites", n.appendChild(this.favoritesEl), this.favoritesDivider = this.divider(), n.appendChild(this.favoritesDivider), this.renderFavorites()), e.drawingToolGroups.forEach((e, r) => {
|
|
599
885
|
let i = document.createElement("div");
|
|
600
886
|
i.className = "tcw-tool-group-wrap";
|
|
601
887
|
let a = document.createElement("button");
|
|
602
|
-
if (a.className = "tcw-sidebar-btn", a.title = e.label, a.innerHTML =
|
|
888
|
+
if (a.className = "tcw-sidebar-btn", a.title = e.label, a.innerHTML = y(re[r] ?? "square", 14), e.tools.length > 1) {
|
|
603
889
|
let e = document.createElement("span");
|
|
604
890
|
e.className = "tcw-multi-dot", a.appendChild(e);
|
|
605
891
|
}
|
|
606
892
|
a.addEventListener("click", () => t.onDrawingTool(e.tools[0].value)), i.appendChild(a), i.addEventListener("mouseenter", () => this.showFlyout(r)), i.addEventListener("mouseleave", () => this.hideFlyout()), n.appendChild(i), this.groupWraps.push(i), this.groupButtons.push(a);
|
|
607
893
|
});
|
|
608
894
|
let r = document.createElement("div");
|
|
609
|
-
r.className = "tcw-sidebar-spacer", n.appendChild(r), n.appendChild(this.divider()),
|
|
895
|
+
if (r.className = "tcw-sidebar-spacer", n.appendChild(r), n.appendChild(this.divider()), t.onToggleStyle) {
|
|
896
|
+
let e = document.createElement("button");
|
|
897
|
+
e.className = "tcw-sidebar-btn", e.title = "Drawing style & templates", e.dataset.role = "style", e.innerHTML = y("palette", 14), e.addEventListener("click", t.onToggleStyle), n.appendChild(e);
|
|
898
|
+
}
|
|
899
|
+
this.magnetBtn = document.createElement("button"), this.magnetBtn.className = "tcw-sidebar-btn", this.magnetBtn.title = "Magnet", this.magnetBtn.innerHTML = y("magnet", 14), this.magnetBtn.addEventListener("click", t.onToggleMagnet), n.appendChild(this.magnetBtn);
|
|
610
900
|
let i = document.createElement("button");
|
|
611
|
-
i.className = "tcw-sidebar-btn", i.title = "Undo", i.innerHTML =
|
|
901
|
+
i.className = "tcw-sidebar-btn", i.title = "Undo", i.innerHTML = y("undo", 14), i.addEventListener("click", t.onUndo), n.appendChild(i);
|
|
612
902
|
let a = document.createElement("button");
|
|
613
|
-
a.className = "tcw-sidebar-btn", a.title = "Redo", a.innerHTML =
|
|
903
|
+
a.className = "tcw-sidebar-btn", a.title = "Redo", a.innerHTML = y("redo", 14), a.addEventListener("click", t.onRedo), n.appendChild(a);
|
|
614
904
|
let o = document.createElement("button");
|
|
615
|
-
o.className = "tcw-sidebar-btn tcw-danger", o.title = "Clear all", o.innerHTML =
|
|
905
|
+
o.className = "tcw-sidebar-btn tcw-danger", o.title = "Clear all", o.innerHTML = y("trash", 14), o.addEventListener("click", t.onClearDrawings), n.appendChild(o);
|
|
906
|
+
}
|
|
907
|
+
setFavorites(e) {
|
|
908
|
+
this.favorites = [...e], this.renderFavorites();
|
|
909
|
+
}
|
|
910
|
+
renderFavorites() {
|
|
911
|
+
if (!this.favoritesEl) return;
|
|
912
|
+
this.favoritesEl.replaceChildren();
|
|
913
|
+
let e = this.favorites.length > 0;
|
|
914
|
+
this.favoritesEl.style.display = e ? "" : "none", this.favoritesDivider && (this.favoritesDivider.style.display = e ? "" : "none");
|
|
915
|
+
for (let e of this.favorites) {
|
|
916
|
+
let t = document.createElement("button");
|
|
917
|
+
t.className = "tcw-sidebar-btn", t.title = `${oe(this.config.drawingToolGroups, e)} (right-click to unpin)`, t.dataset.toolValue = e, t.innerHTML = y(ae(e), 14), t.addEventListener("click", () => this.callbacks.onDrawingTool(e)), t.addEventListener("contextmenu", (t) => {
|
|
918
|
+
t.preventDefault(), this.callbacks.onToggleFavorite?.(e);
|
|
919
|
+
}), this.favoritesEl.appendChild(t);
|
|
920
|
+
}
|
|
616
921
|
}
|
|
617
922
|
showFlyout(e) {
|
|
618
923
|
let t = this.config.drawingToolGroups[e];
|
|
@@ -626,7 +931,9 @@ var _ = class {
|
|
|
626
931
|
let t = document.createElement("button");
|
|
627
932
|
t.className = "tcw-flyout-item", t.textContent = e.label, t.dataset.toolValue = e.value, t.addEventListener("click", () => {
|
|
628
933
|
this.callbacks.onDrawingTool(e.value), this.hideFlyout();
|
|
629
|
-
}),
|
|
934
|
+
}), this.callbacks.onToggleFavorite && (this.favorites.includes(e.value) && t.classList.add("tcw-flyout-faved"), t.title = "Right-click to pin/unpin", t.addEventListener("contextmenu", (t) => {
|
|
935
|
+
t.preventDefault(), this.callbacks.onToggleFavorite?.(e.value);
|
|
936
|
+
})), n.appendChild(t);
|
|
630
937
|
}
|
|
631
938
|
this.flyoutEl = n, this.groupWraps[e].appendChild(n);
|
|
632
939
|
}
|
|
@@ -652,11 +959,11 @@ var _ = class {
|
|
|
652
959
|
destroy() {
|
|
653
960
|
this.hideFlyout(), this.el.remove();
|
|
654
961
|
}
|
|
655
|
-
},
|
|
962
|
+
}, S = [
|
|
656
963
|
"style",
|
|
657
964
|
"display",
|
|
658
965
|
"scale"
|
|
659
|
-
],
|
|
966
|
+
], C = class {
|
|
660
967
|
callbacks;
|
|
661
968
|
backdrop = null;
|
|
662
969
|
modal = null;
|
|
@@ -682,12 +989,12 @@ var _ = class {
|
|
|
682
989
|
let t = document.createElement("h3");
|
|
683
990
|
t.textContent = "Chart Settings", e.appendChild(t);
|
|
684
991
|
let n = document.createElement("button");
|
|
685
|
-
n.className = "tcw-modal-close", n.innerHTML =
|
|
992
|
+
n.className = "tcw-modal-close", n.innerHTML = y("x", 16), n.addEventListener("click", () => {
|
|
686
993
|
this.callbacks.onClose(), this.close();
|
|
687
994
|
}), e.appendChild(n), this.modal.appendChild(e);
|
|
688
995
|
let r = document.createElement("div");
|
|
689
996
|
r.className = "tcw-modal-tabs", this.tabButtons = [];
|
|
690
|
-
for (let e of
|
|
997
|
+
for (let e of S) {
|
|
691
998
|
let t = document.createElement("button");
|
|
692
999
|
t.className = "tcw-modal-tab", t.textContent = e, t.dataset.tab = e, e === this.currentTab && t.classList.add("tcw-active"), t.addEventListener("click", () => {
|
|
693
1000
|
this.currentTab = e, this.tabButtons.forEach((t) => t.classList.toggle("tcw-active", t.dataset.tab === e)), this.renderTabContent();
|
|
@@ -730,7 +1037,13 @@ var _ = class {
|
|
|
730
1037
|
renderDisplayTab() {
|
|
731
1038
|
if (!this.bodyEl || !this.currentSettings) return;
|
|
732
1039
|
let e = this.currentSettings, t = this.section();
|
|
733
|
-
t.appendChild(this.toggleRow("Grid Lines", e.gridVisible, (e) => this.patch({ gridVisible: e }))), t.appendChild(this.toggleRow("Volume", e.volumeVisible, (e) => this.patch({ volumeVisible: e }))), t.appendChild(this.toggleRow("
|
|
1040
|
+
t.appendChild(this.toggleRow("Grid Lines", e.gridVisible, (e) => this.patch({ gridVisible: e }))), t.appendChild(this.toggleRow("Volume", e.volumeVisible, (e) => this.patch({ volumeVisible: e }))), t.appendChild(this.toggleRow("Volume Profile", e.volumeProfileVisible, (e) => this.patch({ volumeProfileVisible: e }))), t.appendChild(this.toggleRow("Market Profile (TPO)", e.marketProfileVisible, (e) => this.patch({ marketProfileVisible: e }))), t.appendChild(this.toggleRow("MP split by session", e.marketProfileSplit, (e) => this.patch({ marketProfileSplit: e }))), t.appendChild(this.toggleRow("MP letters (TPO)", e.marketProfileLetters, (e) => this.patch({ marketProfileLetters: e }))), t.appendChild(this.rangeRow("MP buckets", e.marketProfileBuckets, 8, 200, 1, (e) => this.patch({ marketProfileBuckets: e }))), t.appendChild(this.rangeRow("MP opacity", e.marketProfileOpacity, .05, 1, .05, (e) => this.patch({ marketProfileOpacity: e }), (e) => `${Math.round(e * 100)}%`)), t.appendChild(this.toggleRow("Liquidity heatmap", e.depthHeatmapVisible, (e) => this.patch({ depthHeatmapVisible: e }))), t.appendChild(this.rangeRow("Heatmap opacity", e.depthHeatmapOpacity, .1, 1, .05, (e) => this.patch({ depthHeatmapOpacity: e }), (e) => `${Math.round(e * 100)}%`)), t.appendChild(this.toggleRow("Swing markers", e.pivotMarkersVisible, (e) => this.patch({ pivotMarkersVisible: e }))), t.appendChild(this.rangeRow("Swing strength", e.pivotStrength, 2, 20, 1, (e) => this.patch({ pivotStrength: e }))), t.appendChild(this.toggleRow("Swing structure (HH/HL)", e.pivotStructureLabels, (e) => this.patch({ pivotStructureLabels: e }))), t.appendChild(this.toggleRow("Session shading (RTH)", e.sessionShadingVisible, (e) => this.patch({ sessionShadingVisible: e }))), t.appendChild(this.toggleRow("Prior-period levels", e.periodLevelsVisible, (e) => this.patch({ periodLevelsVisible: e }))), t.appendChild(this.selectRow("Period levels basis", e.periodLevelsPeriod, [{
|
|
1041
|
+
value: "day",
|
|
1042
|
+
label: "Prior Day (PDH/PDL)"
|
|
1043
|
+
}, {
|
|
1044
|
+
value: "week",
|
|
1045
|
+
label: "Prior Week (PWH/PWL)"
|
|
1046
|
+
}], (e) => this.patch({ periodLevelsPeriod: e }))), t.appendChild(this.toggleRow("OHLC Legend", e.legendVisible, (e) => this.patch({ legendVisible: e }))), t.appendChild(this.toggleRow("Bar Countdown", e.barCountdown, (e) => this.patch({ barCountdown: e }))), t.appendChild(this.selectRow("Crosshair Mode", e.crosshairMode, [
|
|
734
1047
|
{
|
|
735
1048
|
label: "Magnet",
|
|
736
1049
|
value: "magnet"
|
|
@@ -743,7 +1056,40 @@ var _ = class {
|
|
|
743
1056
|
label: "Hidden",
|
|
744
1057
|
value: "hidden"
|
|
745
1058
|
}
|
|
746
|
-
], (e) => this.patch({ crosshairMode: e }))), t.appendChild(this.selectRow("
|
|
1059
|
+
], (e) => this.patch({ crosshairMode: e }))), t.appendChild(this.selectRow("Timezone", e.timezone, [
|
|
1060
|
+
{
|
|
1061
|
+
value: "local",
|
|
1062
|
+
label: "Local"
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
value: "0",
|
|
1066
|
+
label: "UTC / London"
|
|
1067
|
+
},
|
|
1068
|
+
{
|
|
1069
|
+
value: "-300",
|
|
1070
|
+
label: "New York (UTC-5)"
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
value: "-360",
|
|
1074
|
+
label: "Chicago (UTC-6)"
|
|
1075
|
+
},
|
|
1076
|
+
{
|
|
1077
|
+
value: "60",
|
|
1078
|
+
label: "Frankfurt (UTC+1)"
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
value: "480",
|
|
1082
|
+
label: "Singapore/HK (UTC+8)"
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
value: "540",
|
|
1086
|
+
label: "Tokyo (UTC+9)"
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
value: "330",
|
|
1090
|
+
label: "Mumbai (UTC+5:30)"
|
|
1091
|
+
}
|
|
1092
|
+
], (e) => this.patch({ timezone: e }))), t.appendChild(this.selectRow("Number Locale", e.numberLocale, [
|
|
747
1093
|
{
|
|
748
1094
|
label: "en-US (65,234.00)",
|
|
749
1095
|
value: "en-US"
|
|
@@ -773,7 +1119,24 @@ var _ = class {
|
|
|
773
1119
|
renderScaleTab() {
|
|
774
1120
|
if (!this.bodyEl || !this.currentSettings) return;
|
|
775
1121
|
let e = this.currentSettings, t = this.section();
|
|
776
|
-
t.appendChild(this.toggleRow("Auto Scale", e.autoScale, (e) => this.patch({ autoScale: e }))), t.appendChild(this.
|
|
1122
|
+
t.appendChild(this.toggleRow("Auto Scale", e.autoScale, (e) => this.patch({ autoScale: e }))), t.appendChild(this.selectRow("Price Scale", e.scaleMode, [
|
|
1123
|
+
{
|
|
1124
|
+
value: "regular",
|
|
1125
|
+
label: "Regular"
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
value: "logarithmic",
|
|
1129
|
+
label: "Logarithmic"
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
value: "percentage",
|
|
1133
|
+
label: "Percentage"
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
value: "indexedTo100",
|
|
1137
|
+
label: "Indexed to 100"
|
|
1138
|
+
}
|
|
1139
|
+
], (e) => this.patch({ scaleMode: e }))), this.bodyEl.appendChild(t);
|
|
777
1140
|
}
|
|
778
1141
|
patch(e) {
|
|
779
1142
|
this.currentSettings && Object.assign(this.currentSettings, e), this.callbacks.onChange(e);
|
|
@@ -824,10 +1187,25 @@ var _ = class {
|
|
|
824
1187
|
}
|
|
825
1188
|
return o.addEventListener("change", () => r(o.value)), i.appendChild(o), i;
|
|
826
1189
|
}
|
|
1190
|
+
rangeRow(e, t, n, r, i, a, o = (e) => String(e)) {
|
|
1191
|
+
let s = document.createElement("div");
|
|
1192
|
+
s.className = "tcw-settings-row";
|
|
1193
|
+
let c = document.createElement("span");
|
|
1194
|
+
c.className = "tcw-settings-label", c.textContent = e, s.appendChild(c);
|
|
1195
|
+
let l = document.createElement("div");
|
|
1196
|
+
l.className = "tcw-settings-range";
|
|
1197
|
+
let u = document.createElement("input");
|
|
1198
|
+
u.type = "range", u.min = String(n), u.max = String(r), u.step = String(i), u.value = String(t);
|
|
1199
|
+
let d = document.createElement("span");
|
|
1200
|
+
return d.className = "tcw-settings-range-val", d.textContent = o(t), u.addEventListener("input", () => {
|
|
1201
|
+
let e = Number(u.value);
|
|
1202
|
+
d.textContent = o(e), a(e);
|
|
1203
|
+
}), l.appendChild(u), l.appendChild(d), s.appendChild(l), s;
|
|
1204
|
+
}
|
|
827
1205
|
destroy() {
|
|
828
1206
|
this.close();
|
|
829
1207
|
}
|
|
830
|
-
},
|
|
1208
|
+
}, w = class {
|
|
831
1209
|
el;
|
|
832
1210
|
dotEl;
|
|
833
1211
|
messageEl;
|
|
@@ -843,7 +1221,7 @@ var _ = class {
|
|
|
843
1221
|
destroy() {
|
|
844
1222
|
this.el.remove();
|
|
845
1223
|
}
|
|
846
|
-
},
|
|
1224
|
+
}, T = class {
|
|
847
1225
|
backdrop = null;
|
|
848
1226
|
modal = null;
|
|
849
1227
|
input = null;
|
|
@@ -968,145 +1346,1909 @@ var _ = class {
|
|
|
968
1346
|
destroy() {
|
|
969
1347
|
this.close();
|
|
970
1348
|
}
|
|
971
|
-
},
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
settingsState;
|
|
986
|
-
adapter = null;
|
|
987
|
-
boundGlobalKeydown = null;
|
|
988
|
-
constructor(t, n = {}) {
|
|
989
|
-
this.options = n, this.symbols = n.symbols ?? c, this.settingsState = { ...l };
|
|
990
|
-
let u = this.resolveIsDark(n.theme), d = this.resolveTheme(n.theme);
|
|
991
|
-
this.state = {
|
|
992
|
-
symbol: n.symbol ?? this.symbols[0] ?? "BTCUSDT",
|
|
993
|
-
timeframe: n.timeframe ?? "5m",
|
|
994
|
-
chartType: n.chartOptions?.chartType ?? "candlestick",
|
|
995
|
-
isDark: u,
|
|
996
|
-
activeIndicators: /* @__PURE__ */ new Map(),
|
|
997
|
-
activeTool: null,
|
|
998
|
-
magnetEnabled: !0,
|
|
999
|
-
connectionState: "connecting",
|
|
1000
|
-
connectionMessage: "Connecting..."
|
|
1001
|
-
};
|
|
1002
|
-
let f = this.symbols.indexOf(this.state.symbol);
|
|
1003
|
-
f >= 0 && (this.symbolIndex = f), p(), this.root = document.createElement("div"), this.root.className = "tcw-root", this.root.dataset.tcwTheme = u ? "dark" : "light", t.appendChild(this.root), n.toolbar !== !1 && (this.toolbar = new v(this.root, {
|
|
1004
|
-
symbols: this.symbols,
|
|
1005
|
-
timeframes: n.timeframes ? r.filter((e) => n.timeframes.includes(e.value)) : r,
|
|
1006
|
-
chartTypes: n.chartTypes ? i.filter((e) => n.chartTypes.includes(e.value)) : i,
|
|
1007
|
-
indicators: o,
|
|
1008
|
-
popularIndicatorIds: s
|
|
1009
|
-
}, {
|
|
1010
|
-
onSymbolClick: () => this.handleSymbolClick(),
|
|
1011
|
-
onTimeframe: (e) => this.handleTimeframe(e),
|
|
1012
|
-
onChartType: (e) => this.handleChartType(e),
|
|
1013
|
-
onAddIndicator: (e) => this.handleAddIndicator(e),
|
|
1014
|
-
onRemoveIndicator: (e) => this.handleRemoveIndicator(e),
|
|
1015
|
-
onScreenshot: () => this.chart.screenshot(),
|
|
1016
|
-
onSettings: () => this.openSettings(),
|
|
1017
|
-
onToggleTheme: () => this.handleToggleTheme()
|
|
1018
|
-
}));
|
|
1019
|
-
let m = document.createElement("div");
|
|
1020
|
-
m.className = "tcw-body", n.drawingTools !== !1 && (this.sidebar = new b(m, { drawingToolGroups: a }, {
|
|
1021
|
-
onDrawingTool: (e) => this.handleDrawingTool(e),
|
|
1022
|
-
onCancelDrawing: () => this.handleCancelDrawing(),
|
|
1023
|
-
onToggleMagnet: () => this.handleToggleMagnet(),
|
|
1024
|
-
onUndo: () => this.chart.undo(),
|
|
1025
|
-
onRedo: () => this.chart.redo(),
|
|
1026
|
-
onClearDrawings: () => {
|
|
1027
|
-
this.chart.clearDrawings(), this.state = {
|
|
1028
|
-
...this.state,
|
|
1029
|
-
activeTool: null
|
|
1030
|
-
}, this.updateUI();
|
|
1031
|
-
}
|
|
1032
|
-
})), this.chartContainer = document.createElement("div"), this.chartContainer.className = "tcw-chart-container", m.appendChild(this.chartContainer), this.root.appendChild(m), this.chart = new e(this.chartContainer, {
|
|
1033
|
-
chartType: this.state.chartType,
|
|
1034
|
-
theme: d,
|
|
1035
|
-
autoScale: !0,
|
|
1036
|
-
crosshair: { mode: "magnet" },
|
|
1037
|
-
features: {
|
|
1038
|
-
drawings: !0,
|
|
1039
|
-
drawingMagnet: !0,
|
|
1040
|
-
drawingUndoRedo: !0,
|
|
1041
|
-
indicators: !0,
|
|
1042
|
-
trading: n.trading !== !1,
|
|
1043
|
-
tradingContextMenu: n.trading !== !1,
|
|
1044
|
-
volume: !0,
|
|
1045
|
-
legend: !0,
|
|
1046
|
-
crosshair: !0,
|
|
1047
|
-
keyboard: !0,
|
|
1048
|
-
screenshot: !0,
|
|
1049
|
-
alerts: !0,
|
|
1050
|
-
barCountdown: !0,
|
|
1051
|
-
logScale: !0,
|
|
1052
|
-
watermark: !0
|
|
1053
|
-
},
|
|
1054
|
-
...n.chartOptions
|
|
1055
|
-
}), n.statusBar !== !1 && (this.statusBar = new C(this.root)), n.settings !== !1 && (this.settings = new S({
|
|
1056
|
-
onChange: (e) => this.applySettings(e),
|
|
1057
|
-
onReset: () => this.resetSettings(),
|
|
1058
|
-
onClose: () => {}
|
|
1059
|
-
})), this.commandPalette = new w({
|
|
1060
|
-
onIndicator: (e) => this.handleAddIndicator(e),
|
|
1061
|
-
onChartType: (e) => this.handleChartType(e),
|
|
1062
|
-
onDrawingTool: (e) => this.handleDrawingTool(e),
|
|
1063
|
-
onTimeframe: (e) => this.handleTimeframe(e),
|
|
1064
|
-
onAction: (e) => this.handleAction(e),
|
|
1065
|
-
onClose: () => {}
|
|
1066
|
-
}), this.boundGlobalKeydown = (e) => {
|
|
1067
|
-
(e.ctrlKey || e.metaKey) && e.key === "k" && (e.preventDefault(), this.toggleCommandPalette());
|
|
1068
|
-
}, document.addEventListener("keydown", this.boundGlobalKeydown), n.adapter && (this.adapter = n.adapter, this.connectStream()), this.updateUI(), n.onReady?.(this.chart);
|
|
1069
|
-
}
|
|
1070
|
-
async setSymbol(e) {
|
|
1071
|
-
this.state = {
|
|
1072
|
-
...this.state,
|
|
1073
|
-
symbol: e
|
|
1074
|
-
};
|
|
1075
|
-
let t = this.symbols.indexOf(e);
|
|
1076
|
-
t >= 0 && (this.symbolIndex = t), this.options.onSymbolChange?.(e), this.updateUI(), this.adapter && await this.connectStream();
|
|
1349
|
+
}, E = class {
|
|
1350
|
+
backdrop = null;
|
|
1351
|
+
modal = null;
|
|
1352
|
+
input = null;
|
|
1353
|
+
list = null;
|
|
1354
|
+
symbols = [];
|
|
1355
|
+
current = "";
|
|
1356
|
+
filtered = [];
|
|
1357
|
+
selectedIndex = 0;
|
|
1358
|
+
callbacks;
|
|
1359
|
+
boundKeydown;
|
|
1360
|
+
pickOverride = null;
|
|
1361
|
+
constructor(e) {
|
|
1362
|
+
this.callbacks = e, this.boundKeydown = this.handleKeydown.bind(this);
|
|
1077
1363
|
}
|
|
1078
|
-
|
|
1079
|
-
this.
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1364
|
+
open(e, t, n) {
|
|
1365
|
+
if (this.backdrop) return;
|
|
1366
|
+
this.pickOverride = n ?? null, this.symbols = e, this.current = t, this.filtered = e.map((e) => ({
|
|
1367
|
+
symbol: e,
|
|
1368
|
+
score: 0
|
|
1369
|
+
})), this.selectedIndex = Math.max(0, e.indexOf(t)), this.backdrop = document.createElement("div"), this.backdrop.className = "tcw-modal-backdrop", this.backdrop.addEventListener("click", () => this.close()), this.modal = document.createElement("div"), this.modal.className = "tcw-cmd-palette";
|
|
1370
|
+
let r = document.createElement("div");
|
|
1371
|
+
r.className = "tcw-cmd-header";
|
|
1372
|
+
let i = document.createElement("span");
|
|
1373
|
+
i.className = "tcw-cmd-icon", i.innerHTML = A(), r.appendChild(i), this.input = document.createElement("input"), this.input.className = "tcw-cmd-input", this.input.type = "text", this.input.placeholder = "Search symbol… (e.g. BTC, ETH, AAPL)", this.input.autocomplete = "off", this.input.spellcheck = !1, this.input.addEventListener("input", () => this.filter()), r.appendChild(this.input);
|
|
1374
|
+
let a = document.createElement("span");
|
|
1375
|
+
a.className = "tcw-cmd-hint", a.textContent = "ESC", r.appendChild(a), this.modal.appendChild(r), this.list = document.createElement("div"), this.list.className = "tcw-cmd-list", this.modal.appendChild(this.list);
|
|
1376
|
+
let o = document.createElement("div");
|
|
1377
|
+
o.className = "tcw-cmd-footer", o.innerHTML = "<span>↑↓ Navigate</span><span>⏎ Open</span><span>Esc Close</span>", this.modal.appendChild(o), document.body.appendChild(this.backdrop), document.body.appendChild(this.modal), this.renderList(), this.input.focus(), document.addEventListener("keydown", this.boundKeydown);
|
|
1083
1378
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
this.state = {
|
|
1087
|
-
...this.state,
|
|
1088
|
-
isDark: t
|
|
1089
|
-
}, this.root.dataset.tcwTheme = t ? "dark" : "light", this.chart.setTheme(n), this.updateUI();
|
|
1379
|
+
close() {
|
|
1380
|
+
document.removeEventListener("keydown", this.boundKeydown), this.backdrop?.remove(), this.modal?.remove(), this.backdrop = null, this.modal = null, this.input = null, this.list = null, this.pickOverride = null, this.callbacks.onClose();
|
|
1090
1381
|
}
|
|
1091
|
-
|
|
1092
|
-
return this.
|
|
1382
|
+
isOpen() {
|
|
1383
|
+
return this.backdrop !== null;
|
|
1093
1384
|
}
|
|
1094
1385
|
destroy() {
|
|
1095
|
-
this.
|
|
1386
|
+
this.close();
|
|
1387
|
+
}
|
|
1388
|
+
filter() {
|
|
1389
|
+
let e = (this.input?.value ?? "").toUpperCase().trim();
|
|
1390
|
+
e ? this.filtered = this.symbols.map((t) => ({
|
|
1391
|
+
symbol: t,
|
|
1392
|
+
score: D(t.toUpperCase(), e)
|
|
1393
|
+
})).filter((e) => e.score > 0).sort((e, t) => t.score - e.score) : this.filtered = this.symbols.map((e) => ({
|
|
1394
|
+
symbol: e,
|
|
1395
|
+
score: 0
|
|
1396
|
+
})), this.selectedIndex = 0, this.renderList();
|
|
1397
|
+
}
|
|
1398
|
+
renderList() {
|
|
1399
|
+
if (!this.list) return;
|
|
1400
|
+
if (this.list.innerHTML = "", this.filtered.length === 0) {
|
|
1401
|
+
let e = document.createElement("div");
|
|
1402
|
+
e.className = "tcw-cmd-empty", e.textContent = "No symbols match", this.list.appendChild(e);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
let e = (this.input?.value ?? "").toUpperCase().trim();
|
|
1406
|
+
for (let t = 0; t < this.filtered.length; t++) {
|
|
1407
|
+
let { symbol: n } = this.filtered[t], r = document.createElement("button");
|
|
1408
|
+
r.className = "tcw-cmd-item", t === this.selectedIndex && r.classList.add("tcw-selected"), n === this.current && r.classList.add("tcw-active");
|
|
1409
|
+
let i = document.createElement("span");
|
|
1410
|
+
if (i.className = "tcw-cmd-item-label", i.innerHTML = O(n, e), r.appendChild(i), n === this.current) {
|
|
1411
|
+
let e = document.createElement("span");
|
|
1412
|
+
e.className = "tcw-cmd-check", e.textContent = "✓", r.appendChild(e);
|
|
1413
|
+
}
|
|
1414
|
+
r.addEventListener("click", () => this.pick(t)), r.addEventListener("mouseenter", () => {
|
|
1415
|
+
this.selectedIndex = t, this.updateSelection();
|
|
1416
|
+
}), this.list.appendChild(r);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
updateSelection() {
|
|
1420
|
+
if (!this.list) return;
|
|
1421
|
+
let e = this.list.querySelectorAll(".tcw-cmd-item");
|
|
1422
|
+
e.forEach((e, t) => e.classList.toggle("tcw-selected", t === this.selectedIndex)), e[this.selectedIndex]?.scrollIntoView({ block: "nearest" });
|
|
1423
|
+
}
|
|
1424
|
+
pick(e) {
|
|
1425
|
+
let t = this.filtered[e];
|
|
1426
|
+
if (!t) return;
|
|
1427
|
+
let n = this.pickOverride;
|
|
1428
|
+
n ? n(t.symbol) : this.callbacks.onPick(t.symbol), this.close();
|
|
1429
|
+
}
|
|
1430
|
+
handleKeydown(e) {
|
|
1431
|
+
switch (e.key) {
|
|
1432
|
+
case "Escape":
|
|
1433
|
+
e.preventDefault(), this.close();
|
|
1434
|
+
break;
|
|
1435
|
+
case "ArrowDown":
|
|
1436
|
+
e.preventDefault(), this.filtered.length > 0 && (this.selectedIndex = (this.selectedIndex + 1) % this.filtered.length, this.updateSelection());
|
|
1437
|
+
break;
|
|
1438
|
+
case "ArrowUp":
|
|
1439
|
+
e.preventDefault(), this.filtered.length > 0 && (this.selectedIndex = (this.selectedIndex - 1 + this.filtered.length) % this.filtered.length, this.updateSelection());
|
|
1440
|
+
break;
|
|
1441
|
+
case "Enter":
|
|
1442
|
+
e.preventDefault(), this.pick(this.selectedIndex);
|
|
1443
|
+
break;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
function D(e, t) {
|
|
1448
|
+
if (e === t) return 1e3;
|
|
1449
|
+
if (e.startsWith(t)) return 500 + (50 - Math.min(50, e.length - t.length));
|
|
1450
|
+
if (e.includes(t)) return 200 + (50 - Math.min(50, e.length - t.length));
|
|
1451
|
+
let n = 0, r = -1, i = 0;
|
|
1452
|
+
for (let a = 0; a < e.length && n < t.length; a++) e[a] === t[n] && (r >= 0 && (i += a - r - 1), r = a, n++);
|
|
1453
|
+
return n < t.length ? 0 : 100 - Math.min(99, i);
|
|
1454
|
+
}
|
|
1455
|
+
function O(e, t) {
|
|
1456
|
+
if (!t) return k(e);
|
|
1457
|
+
let n = e.toUpperCase(), r = "", i = 0;
|
|
1458
|
+
for (let a = 0; a < e.length; a++) i < t.length && n[a] === t[i] ? (r += `<mark style="background:transparent;color:var(--tcw-accent);font-weight:700">${k(e[a])}</mark>`, i++) : r += k(e[a]);
|
|
1459
|
+
return r;
|
|
1460
|
+
}
|
|
1461
|
+
function k(e) {
|
|
1462
|
+
return e.replace(/[&<>"']/g, (e) => ({
|
|
1463
|
+
"&": "&",
|
|
1464
|
+
"<": "<",
|
|
1465
|
+
">": ">",
|
|
1466
|
+
"\"": """,
|
|
1467
|
+
"'": "'"
|
|
1468
|
+
})[e]);
|
|
1469
|
+
}
|
|
1470
|
+
function A() {
|
|
1471
|
+
return "<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m21 21-4.3-4.3\"/></svg>";
|
|
1472
|
+
}
|
|
1473
|
+
//#endregion
|
|
1474
|
+
//#region src/widget/WidgetHotkeySheet.ts
|
|
1475
|
+
var j = typeof navigator < "u" && /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent) ? "⌘" : "Ctrl", M = [
|
|
1476
|
+
{
|
|
1477
|
+
title: "Search & navigation",
|
|
1478
|
+
entries: [
|
|
1479
|
+
{
|
|
1480
|
+
keys: [j, "K"],
|
|
1481
|
+
label: "Command palette"
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
keys: [j, "P"],
|
|
1485
|
+
label: "Symbol search"
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
keys: ["?"],
|
|
1489
|
+
label: "Show this sheet"
|
|
1490
|
+
}
|
|
1491
|
+
]
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
title: "Chart manipulation",
|
|
1495
|
+
entries: [
|
|
1496
|
+
{
|
|
1497
|
+
keys: ["Shift", "Drag"],
|
|
1498
|
+
label: "Measure tool (bars × price Δ)"
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
keys: ["Drag", "Price axis"],
|
|
1502
|
+
label: "Compress / expand vertical scale"
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
keys: ["Drag", "Time axis"],
|
|
1506
|
+
label: "Zoom time axis"
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
keys: ["Double-click", "Price axis"],
|
|
1510
|
+
label: "Re-enable auto-scale"
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
keys: ["Double-click", "Time axis"],
|
|
1514
|
+
label: "Fit content"
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
keys: ["Scroll"],
|
|
1518
|
+
label: "Zoom around cursor"
|
|
1519
|
+
}
|
|
1520
|
+
]
|
|
1521
|
+
},
|
|
1522
|
+
{
|
|
1523
|
+
title: "Touch (mobile / tablet)",
|
|
1524
|
+
entries: [
|
|
1525
|
+
{
|
|
1526
|
+
keys: ["1 finger", "Drag"],
|
|
1527
|
+
label: "Pan / move crosshair"
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
keys: ["2 fingers", "Pinch"],
|
|
1531
|
+
label: "Zoom around midpoint"
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
keys: ["Long-press"],
|
|
1535
|
+
label: "Pin OHLC tooltip at bar"
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
keys: ["Drag", "Axis strip"],
|
|
1539
|
+
label: "Scale price / time axis"
|
|
1540
|
+
}
|
|
1541
|
+
]
|
|
1542
|
+
},
|
|
1543
|
+
{
|
|
1544
|
+
title: "Keyboard",
|
|
1545
|
+
entries: [
|
|
1546
|
+
{
|
|
1547
|
+
keys: ["←", "→"],
|
|
1548
|
+
label: "Pan one bar"
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
keys: ["+", "−"],
|
|
1552
|
+
label: "Zoom in / out"
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
keys: ["Home"],
|
|
1556
|
+
label: "Scroll to start"
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
keys: ["End"],
|
|
1560
|
+
label: "Scroll to end (live edge)"
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
keys: ["F"],
|
|
1564
|
+
label: "Fit content"
|
|
1565
|
+
}
|
|
1566
|
+
]
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
title: "Drawing",
|
|
1570
|
+
entries: [
|
|
1571
|
+
{
|
|
1572
|
+
keys: [j, "Z"],
|
|
1573
|
+
label: "Undo"
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
keys: [
|
|
1577
|
+
j,
|
|
1578
|
+
"Shift",
|
|
1579
|
+
"Z"
|
|
1580
|
+
],
|
|
1581
|
+
label: "Redo"
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
keys: ["Esc"],
|
|
1585
|
+
label: "Cancel active drawing"
|
|
1586
|
+
}
|
|
1587
|
+
]
|
|
1588
|
+
}
|
|
1589
|
+
], N = class {
|
|
1590
|
+
backdrop = null;
|
|
1591
|
+
modal = null;
|
|
1592
|
+
callbacks;
|
|
1593
|
+
boundKeydown;
|
|
1594
|
+
constructor(e) {
|
|
1595
|
+
this.callbacks = e, this.boundKeydown = (e) => {
|
|
1596
|
+
e.key === "Escape" && (e.preventDefault(), this.close());
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
open() {
|
|
1600
|
+
if (this.backdrop) return;
|
|
1601
|
+
this.backdrop = document.createElement("div"), this.backdrop.className = "tcw-modal-backdrop", this.backdrop.addEventListener("click", () => this.close()), this.modal = document.createElement("div"), this.modal.className = "tcw-modal tcw-hotkey-sheet", this.modal.style.width = "640px";
|
|
1602
|
+
let e = document.createElement("div");
|
|
1603
|
+
e.className = "tcw-modal-header", e.innerHTML = "<h3>Keyboard shortcuts</h3>";
|
|
1604
|
+
let t = document.createElement("button");
|
|
1605
|
+
t.className = "tcw-modal-close", t.setAttribute("aria-label", "Close"), t.innerHTML = "×", t.addEventListener("click", () => this.close()), e.appendChild(t), this.modal.appendChild(e);
|
|
1606
|
+
let n = document.createElement("div");
|
|
1607
|
+
n.className = "tcw-modal-body";
|
|
1608
|
+
let r = document.createElement("div");
|
|
1609
|
+
r.className = "tcw-hotkey-grid";
|
|
1610
|
+
for (let e of M) {
|
|
1611
|
+
let t = document.createElement("section");
|
|
1612
|
+
t.className = "tcw-hotkey-section";
|
|
1613
|
+
let n = document.createElement("div");
|
|
1614
|
+
n.className = "tcw-settings-section-title", n.textContent = e.title, t.appendChild(n);
|
|
1615
|
+
for (let n of e.entries) {
|
|
1616
|
+
let e = document.createElement("div");
|
|
1617
|
+
e.className = "tcw-hotkey-row";
|
|
1618
|
+
let r = document.createElement("div");
|
|
1619
|
+
r.className = "tcw-hotkey-keys";
|
|
1620
|
+
for (let e = 0; e < n.keys.length; e++) {
|
|
1621
|
+
if (e > 0) {
|
|
1622
|
+
let e = document.createElement("span");
|
|
1623
|
+
e.className = "tcw-hotkey-sep", e.textContent = "+", r.appendChild(e);
|
|
1624
|
+
}
|
|
1625
|
+
let t = document.createElement("kbd");
|
|
1626
|
+
t.className = "tcw-cmd-kbd", t.textContent = n.keys[e], r.appendChild(t);
|
|
1627
|
+
}
|
|
1628
|
+
e.appendChild(r);
|
|
1629
|
+
let i = document.createElement("span");
|
|
1630
|
+
i.className = "tcw-hotkey-label", i.textContent = n.label, e.appendChild(i), t.appendChild(e);
|
|
1631
|
+
}
|
|
1632
|
+
r.appendChild(t);
|
|
1633
|
+
}
|
|
1634
|
+
n.appendChild(r), this.modal.appendChild(n);
|
|
1635
|
+
let i = document.createElement("div");
|
|
1636
|
+
i.className = "tcw-modal-footer", i.innerHTML = "<span style=\"font-size:11px;color:var(--tcw-text-muted)\">Press ? anytime to show this sheet</span>";
|
|
1637
|
+
let a = document.createElement("button");
|
|
1638
|
+
a.className = "tcw-done-btn", a.textContent = "Got it", a.addEventListener("click", () => this.close()), i.appendChild(a), this.modal.appendChild(i), document.body.appendChild(this.backdrop), document.body.appendChild(this.modal), document.addEventListener("keydown", this.boundKeydown);
|
|
1639
|
+
}
|
|
1640
|
+
close() {
|
|
1641
|
+
document.removeEventListener("keydown", this.boundKeydown), this.backdrop?.remove(), this.modal?.remove(), this.backdrop = null, this.modal = null, this.callbacks.onClose();
|
|
1642
|
+
}
|
|
1643
|
+
isOpen() {
|
|
1644
|
+
return this.backdrop !== null;
|
|
1645
|
+
}
|
|
1646
|
+
destroy() {
|
|
1647
|
+
this.close();
|
|
1648
|
+
}
|
|
1649
|
+
}, P = [
|
|
1650
|
+
.5,
|
|
1651
|
+
1,
|
|
1652
|
+
2,
|
|
1653
|
+
5,
|
|
1654
|
+
10,
|
|
1655
|
+
20,
|
|
1656
|
+
50,
|
|
1657
|
+
100
|
|
1658
|
+
], F = class {
|
|
1659
|
+
root = null;
|
|
1660
|
+
scrubber = null;
|
|
1661
|
+
progressLabel = null;
|
|
1662
|
+
playBtn = null;
|
|
1663
|
+
state = "stopped";
|
|
1664
|
+
total = 0;
|
|
1665
|
+
speed = 5;
|
|
1666
|
+
callbacks;
|
|
1667
|
+
constructor(e) {
|
|
1668
|
+
this.callbacks = e;
|
|
1669
|
+
}
|
|
1670
|
+
mount(e, t) {
|
|
1671
|
+
this.root || (this.total = Math.max(0, t.total), this.speed = t.speed ?? 5, this.state = "paused", this.root = document.createElement("div"), this.root.className = "tcw-replay-bar", this.root.innerHTML = this.markup(), e.appendChild(this.root), this.wireEvents(), this.syncUi());
|
|
1672
|
+
}
|
|
1673
|
+
unmount() {
|
|
1674
|
+
this.root?.remove(), this.root = null, this.scrubber = null, this.progressLabel = null, this.playBtn = null, this.state = "stopped";
|
|
1675
|
+
}
|
|
1676
|
+
isMounted() {
|
|
1677
|
+
return this.root !== null;
|
|
1678
|
+
}
|
|
1679
|
+
setState(e) {
|
|
1680
|
+
this.state = e, this.syncUi();
|
|
1681
|
+
}
|
|
1682
|
+
setProgress(e, t) {
|
|
1683
|
+
this.total = t, this.scrubber && (this.scrubber.max = String(Math.max(0, t - 1)), this.scrubber.value = String(e)), this.progressLabel && (this.progressLabel.textContent = `${e + 1} / ${t}`);
|
|
1684
|
+
}
|
|
1685
|
+
destroy() {
|
|
1686
|
+
this.unmount();
|
|
1687
|
+
}
|
|
1688
|
+
markup() {
|
|
1689
|
+
let e = P.map((e) => `<option value="${e}"${e === this.speed ? " selected" : ""}>${e}× bars/s</option>`).join("");
|
|
1690
|
+
return `
|
|
1691
|
+
<button class="tcw-replay-btn" data-act="close" title="Exit replay" aria-label="Exit replay">${B()}</button>
|
|
1692
|
+
<button class="tcw-replay-btn" data-act="stepBack" title="Step back" aria-label="Step back">${R()}</button>
|
|
1693
|
+
<button class="tcw-replay-btn tcw-replay-play" data-act="play" title="Play / pause" aria-label="Play / pause">${I()}</button>
|
|
1694
|
+
<button class="tcw-replay-btn" data-act="stepForward" title="Step forward" aria-label="Step forward">${z()}</button>
|
|
1695
|
+
<input type="range" class="tcw-replay-scrubber" data-act="seek" min="0" max="${Math.max(0, this.total - 1)}" value="0" step="1" />
|
|
1696
|
+
<span class="tcw-replay-progress" data-tcw="progress">1 / ${this.total}</span>
|
|
1697
|
+
<select class="tcw-replay-speed" data-act="speed">${e}</select>
|
|
1698
|
+
`;
|
|
1699
|
+
}
|
|
1700
|
+
wireEvents() {
|
|
1701
|
+
this.root && (this.scrubber = this.root.querySelector("input[data-act=\"seek\"]"), this.progressLabel = this.root.querySelector("[data-tcw=\"progress\"]"), this.playBtn = this.root.querySelector("button[data-act=\"play\"]"), this.root.querySelector("[data-act=\"close\"]")?.addEventListener("click", () => this.callbacks.onClose()), this.root.querySelector("[data-act=\"stepBack\"]")?.addEventListener("click", () => this.callbacks.onStepBack()), this.root.querySelector("[data-act=\"stepForward\"]")?.addEventListener("click", () => this.callbacks.onStepForward()), this.playBtn?.addEventListener("click", () => {
|
|
1702
|
+
this.state === "playing" ? this.callbacks.onPause() : this.callbacks.onPlay();
|
|
1703
|
+
}), this.scrubber?.addEventListener("input", () => {
|
|
1704
|
+
let e = Number(this.scrubber.value);
|
|
1705
|
+
this.callbacks.onSeek(e);
|
|
1706
|
+
}), this.root.querySelector("[data-act=\"speed\"]")?.addEventListener("change", (e) => {
|
|
1707
|
+
let t = Number(e.target.value);
|
|
1708
|
+
this.speed = t, this.callbacks.onSpeedChange(t);
|
|
1709
|
+
}));
|
|
1710
|
+
}
|
|
1711
|
+
syncUi() {
|
|
1712
|
+
this.playBtn && (this.playBtn.innerHTML = this.state === "playing" ? L() : I(), this.playBtn.title = this.state === "playing" ? "Pause" : "Play");
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
function I() {
|
|
1716
|
+
return "<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M8 5v14l11-7L8 5z\"/></svg>";
|
|
1717
|
+
}
|
|
1718
|
+
function L() {
|
|
1719
|
+
return "<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/></svg>";
|
|
1720
|
+
}
|
|
1721
|
+
function R() {
|
|
1722
|
+
return "<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M6 6h2v12H6zM20 6L9 12l11 6V6z\"/></svg>";
|
|
1723
|
+
}
|
|
1724
|
+
function z() {
|
|
1725
|
+
return "<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M4 6l11 6L4 18V6zM16 6h2v12h-2z\"/></svg>";
|
|
1726
|
+
}
|
|
1727
|
+
function B() {
|
|
1728
|
+
return "<svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 6L6 18M6 6l12 12\"/></svg>";
|
|
1729
|
+
}
|
|
1730
|
+
//#endregion
|
|
1731
|
+
//#region src/widget/WidgetWatchlist.ts
|
|
1732
|
+
var V = class {
|
|
1733
|
+
el;
|
|
1734
|
+
listEl;
|
|
1735
|
+
rowMap = /* @__PURE__ */ new Map();
|
|
1736
|
+
entries = /* @__PURE__ */ new Map();
|
|
1737
|
+
active = null;
|
|
1738
|
+
callbacks;
|
|
1739
|
+
constructor(e, t, n) {
|
|
1740
|
+
this.callbacks = n, this.el = document.createElement("div"), this.el.className = "tcw-watchlist";
|
|
1741
|
+
let r = document.createElement("div");
|
|
1742
|
+
r.className = "tcw-watchlist-header", r.textContent = "Watchlist", this.el.appendChild(r), this.listEl = document.createElement("div"), this.listEl.className = "tcw-watchlist-list", this.el.appendChild(this.listEl);
|
|
1743
|
+
for (let e of t) this.entries.set(e, { symbol: e }), this.appendRow(e);
|
|
1744
|
+
e.appendChild(this.el);
|
|
1745
|
+
}
|
|
1746
|
+
setSymbols(e) {
|
|
1747
|
+
let t = new Set(e);
|
|
1748
|
+
for (let e of this.rowMap.keys()) t.has(e) || this.removeRow(e);
|
|
1749
|
+
for (let t of e) this.rowMap.has(t) || (this.entries.set(t, this.entries.get(t) ?? { symbol: t }), this.appendRow(t));
|
|
1750
|
+
}
|
|
1751
|
+
setActive(e) {
|
|
1752
|
+
if (this.active === e) return;
|
|
1753
|
+
this.active && this.rowMap.has(this.active) && this.rowMap.get(this.active).row.classList.remove("tcw-watchlist-active"), this.active = e;
|
|
1754
|
+
let t = this.rowMap.get(e);
|
|
1755
|
+
t && t.row.classList.add("tcw-watchlist-active");
|
|
1756
|
+
}
|
|
1757
|
+
setEntry(e, t) {
|
|
1758
|
+
let n = {
|
|
1759
|
+
...this.entries.get(e) ?? { symbol: e },
|
|
1760
|
+
...t
|
|
1761
|
+
};
|
|
1762
|
+
this.entries.set(e, n), this.renderRow(e);
|
|
1763
|
+
}
|
|
1764
|
+
destroy() {
|
|
1765
|
+
this.el.remove(), this.rowMap.clear(), this.entries.clear();
|
|
1766
|
+
}
|
|
1767
|
+
appendRow(e) {
|
|
1768
|
+
let t = document.createElement("div");
|
|
1769
|
+
t.className = "tcw-watchlist-row", t.tabIndex = 0;
|
|
1770
|
+
let n = document.createElement("div");
|
|
1771
|
+
n.className = "tcw-watchlist-symbol", n.textContent = H(e);
|
|
1772
|
+
let r = document.createElement("canvas");
|
|
1773
|
+
r.className = "tcw-watchlist-spark", r.width = 64, r.height = 22;
|
|
1774
|
+
let i = document.createElement("div");
|
|
1775
|
+
i.className = "tcw-watchlist-stats";
|
|
1776
|
+
let a = document.createElement("span");
|
|
1777
|
+
a.className = "tcw-watchlist-price", a.textContent = "—";
|
|
1778
|
+
let o = document.createElement("span");
|
|
1779
|
+
o.className = "tcw-watchlist-change", o.textContent = "", i.appendChild(a), i.appendChild(o), t.appendChild(n), t.appendChild(r), t.appendChild(i), t.addEventListener("click", () => this.callbacks.onSelect(e)), t.addEventListener("keydown", (t) => {
|
|
1780
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), this.callbacks.onSelect(e));
|
|
1781
|
+
}), this.listEl.appendChild(t), this.rowMap.set(e, {
|
|
1782
|
+
row: t,
|
|
1783
|
+
priceEl: a,
|
|
1784
|
+
changeEl: o,
|
|
1785
|
+
sparkCanvas: r
|
|
1786
|
+
}), e === this.active && t.classList.add("tcw-watchlist-active");
|
|
1787
|
+
}
|
|
1788
|
+
removeRow(e) {
|
|
1789
|
+
let t = this.rowMap.get(e);
|
|
1790
|
+
t && (t.row.remove(), this.rowMap.delete(e));
|
|
1791
|
+
}
|
|
1792
|
+
renderRow(e) {
|
|
1793
|
+
let t = this.rowMap.get(e), n = this.entries.get(e);
|
|
1794
|
+
if (!(!t || !n)) {
|
|
1795
|
+
if (n.lastPrice !== void 0 && (t.priceEl.textContent = U(n.lastPrice)), n.lastPrice !== void 0 && n.refPrice !== void 0 && n.refPrice !== 0) {
|
|
1796
|
+
let e = n.lastPrice - n.refPrice, r = e / n.refPrice * 100, i = e >= 0 ? "+" : "";
|
|
1797
|
+
t.changeEl.textContent = `${i}${r.toFixed(2)}%`, t.changeEl.className = `tcw-watchlist-change ${e >= 0 ? "tcw-up" : "tcw-down"}`;
|
|
1798
|
+
} else t.changeEl.textContent = "";
|
|
1799
|
+
n.sparkline && n.sparkline.length >= 2 && W(t.sparkCanvas, n.sparkline, n.refPrice ?? n.sparkline[0]);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
function H(e) {
|
|
1804
|
+
for (let t of [
|
|
1805
|
+
"USDT",
|
|
1806
|
+
"USD",
|
|
1807
|
+
"BTC",
|
|
1808
|
+
"ETH",
|
|
1809
|
+
"EUR"
|
|
1810
|
+
]) if (e.length > t.length && e.endsWith(t)) return e.slice(0, -t.length) + "/" + t;
|
|
1811
|
+
return e;
|
|
1812
|
+
}
|
|
1813
|
+
function U(e) {
|
|
1814
|
+
if (e === 0) return "0";
|
|
1815
|
+
let t = Math.abs(e);
|
|
1816
|
+
return t >= 1e3 ? e.toFixed(2) : t >= 1 ? e.toFixed(4) : e.toPrecision(4);
|
|
1817
|
+
}
|
|
1818
|
+
function W(e, t, n) {
|
|
1819
|
+
let r = e.getContext("2d");
|
|
1820
|
+
if (!r) return;
|
|
1821
|
+
let i = window.devicePixelRatio || 1;
|
|
1822
|
+
e.width !== e.clientWidth * i && (e.width = (e.clientWidth || 64) * i, e.height = (e.clientHeight || 22) * i);
|
|
1823
|
+
let a = e.width, o = e.height, s = Infinity, c = -Infinity;
|
|
1824
|
+
for (let e of t) e < s && (s = e), e > c && (c = e);
|
|
1825
|
+
let l = Math.max(c - s, 1e-9), u = t[t.length - 1] >= n ? "#26a17b" : "#f23645";
|
|
1826
|
+
r.clearRect(0, 0, a, o), r.lineWidth = 1.4 * i, r.strokeStyle = u, r.beginPath();
|
|
1827
|
+
let d = a / (t.length - 1);
|
|
1828
|
+
for (let e = 0; e < t.length; e++) {
|
|
1829
|
+
let n = e * d, i = o - (t[e] - s) / l * (o - 4) - 2;
|
|
1830
|
+
e === 0 ? r.moveTo(n, i) : r.lineTo(n, i);
|
|
1831
|
+
}
|
|
1832
|
+
r.stroke();
|
|
1833
|
+
}
|
|
1834
|
+
//#endregion
|
|
1835
|
+
//#region src/widget/WidgetAlertsPanel.ts
|
|
1836
|
+
var G = [
|
|
1837
|
+
{
|
|
1838
|
+
value: "crossing",
|
|
1839
|
+
label: "Crossing"
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
value: "crossingUp",
|
|
1843
|
+
label: "Crossing up"
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
value: "crossingDown",
|
|
1847
|
+
label: "Crossing down"
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
value: "greaterThan",
|
|
1851
|
+
label: "Greater than"
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
value: "lessThan",
|
|
1855
|
+
label: "Less than"
|
|
1856
|
+
}
|
|
1857
|
+
], K = new Map(G.map((e) => [e.value, e.label]));
|
|
1858
|
+
function q(e) {
|
|
1859
|
+
return Math.round(e * 1e6) / 1e6;
|
|
1860
|
+
}
|
|
1861
|
+
function se(e) {
|
|
1862
|
+
let t = Math.abs(e);
|
|
1863
|
+
return t >= 1e3 || t >= 1 ? e.toFixed(2) : e.toPrecision(4);
|
|
1864
|
+
}
|
|
1865
|
+
var ce = class {
|
|
1866
|
+
el;
|
|
1867
|
+
listEl;
|
|
1868
|
+
priceInput;
|
|
1869
|
+
conditionSelect;
|
|
1870
|
+
sourceSelect;
|
|
1871
|
+
messageInput;
|
|
1872
|
+
emptyEl;
|
|
1873
|
+
callbacks;
|
|
1874
|
+
open = !1;
|
|
1875
|
+
alerts = [];
|
|
1876
|
+
sources = [{
|
|
1877
|
+
channel: "price",
|
|
1878
|
+
label: "Price"
|
|
1879
|
+
}];
|
|
1880
|
+
constructor(e, t) {
|
|
1881
|
+
this.callbacks = t, this.el = document.createElement("div"), this.el.className = "tcw-alerts-panel", this.el.hidden = !0;
|
|
1882
|
+
let n = document.createElement("div");
|
|
1883
|
+
n.className = "tcw-alerts-header";
|
|
1884
|
+
let r = document.createElement("span");
|
|
1885
|
+
r.textContent = "Price Alerts";
|
|
1886
|
+
let i = document.createElement("button");
|
|
1887
|
+
i.className = "tcw-alerts-close", i.type = "button", i.setAttribute("aria-label", "Close alerts"), i.innerHTML = y("x", 14), i.addEventListener("click", () => this.close()), n.appendChild(r), n.appendChild(i), this.el.appendChild(n);
|
|
1888
|
+
let a = document.createElement("form");
|
|
1889
|
+
a.className = "tcw-alerts-form", this.sourceSelect = document.createElement("select"), this.sourceSelect.className = "tcw-alerts-source", this.sourceSelect.addEventListener("change", () => this.prefillPrice()), this.priceInput = document.createElement("input"), this.priceInput.type = "number", this.priceInput.step = "any", this.priceInput.placeholder = "Value", this.priceInput.className = "tcw-alerts-price", this.priceInput.required = !0, this.conditionSelect = document.createElement("select"), this.conditionSelect.className = "tcw-alerts-condition";
|
|
1890
|
+
for (let e of G) {
|
|
1891
|
+
let t = document.createElement("option");
|
|
1892
|
+
t.value = e.value, t.textContent = e.label, this.conditionSelect.appendChild(t);
|
|
1893
|
+
}
|
|
1894
|
+
this.messageInput = document.createElement("input"), this.messageInput.type = "text", this.messageInput.placeholder = "Note (optional)", this.messageInput.className = "tcw-alerts-message";
|
|
1895
|
+
let o = document.createElement("button");
|
|
1896
|
+
o.type = "submit", o.className = "tcw-alerts-add", o.innerHTML = `${y("plus", 14)}<span>Add alert</span>`, a.appendChild(this.sourceSelect), a.appendChild(this.priceInput), a.appendChild(this.conditionSelect), a.appendChild(this.messageInput), a.appendChild(o), a.addEventListener("submit", (e) => {
|
|
1897
|
+
e.preventDefault(), this.submitForm();
|
|
1898
|
+
}), this.el.appendChild(a), this.listEl = document.createElement("div"), this.listEl.className = "tcw-alerts-list", this.el.appendChild(this.listEl), this.emptyEl = document.createElement("div"), this.emptyEl.className = "tcw-alerts-empty", this.emptyEl.textContent = "No alerts yet. Add one above, or right-click the chart.", this.listEl.appendChild(this.emptyEl), this.renderSources(), e.appendChild(this.el);
|
|
1899
|
+
}
|
|
1900
|
+
setSources(e) {
|
|
1901
|
+
this.sources = e.length > 0 ? e : [{
|
|
1902
|
+
channel: "price",
|
|
1903
|
+
label: "Price"
|
|
1904
|
+
}], this.renderSources();
|
|
1905
|
+
}
|
|
1906
|
+
renderSources() {
|
|
1907
|
+
let e = this.sourceSelect.value || "price";
|
|
1908
|
+
this.sourceSelect.replaceChildren();
|
|
1909
|
+
for (let e of this.sources) {
|
|
1910
|
+
let t = document.createElement("option");
|
|
1911
|
+
t.value = e.channel, t.textContent = e.label, this.sourceSelect.appendChild(t);
|
|
1912
|
+
}
|
|
1913
|
+
this.sourceSelect.value = this.sources.some((t) => t.channel === e) ? e : "price";
|
|
1914
|
+
}
|
|
1915
|
+
isOpen() {
|
|
1916
|
+
return this.open;
|
|
1917
|
+
}
|
|
1918
|
+
toggle() {
|
|
1919
|
+
this.open ? this.close() : this.openPanel();
|
|
1920
|
+
}
|
|
1921
|
+
openPanel() {
|
|
1922
|
+
this.open = !0, this.el.hidden = !1, this.priceInput.value === "" && this.prefillPrice();
|
|
1923
|
+
}
|
|
1924
|
+
prefillPrice() {
|
|
1925
|
+
let e = this.callbacks.getChannelValue(this.sourceSelect.value || "price");
|
|
1926
|
+
this.priceInput.value = e === null ? "" : String(q(e));
|
|
1927
|
+
}
|
|
1928
|
+
close() {
|
|
1929
|
+
this.open = !1, this.el.hidden = !0;
|
|
1930
|
+
}
|
|
1931
|
+
setAlerts(e) {
|
|
1932
|
+
this.alerts = e, this.renderList();
|
|
1933
|
+
}
|
|
1934
|
+
destroy() {
|
|
1935
|
+
this.el.remove();
|
|
1936
|
+
}
|
|
1937
|
+
submitForm() {
|
|
1938
|
+
let e = Number(this.priceInput.value);
|
|
1939
|
+
if (!Number.isFinite(e)) return;
|
|
1940
|
+
let t = this.conditionSelect.value, n = this.messageInput.value.trim() || void 0, r = this.sourceSelect.value || "price", i = this.sources.find((e) => e.channel === r)?.label ?? "Price";
|
|
1941
|
+
this.callbacks.onAdd(e, t, n, r, i), this.messageInput.value = "", this.prefillPrice();
|
|
1942
|
+
}
|
|
1943
|
+
renderList() {
|
|
1944
|
+
if (this.listEl.replaceChildren(), this.alerts.length === 0) {
|
|
1945
|
+
this.listEl.appendChild(this.emptyEl);
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
for (let e of [...this.alerts].reverse()) {
|
|
1949
|
+
let t = document.createElement("div");
|
|
1950
|
+
t.className = "tcw-alerts-row" + (e.triggered ? " tcw-alerts-triggered" : "");
|
|
1951
|
+
let n = document.createElement("div");
|
|
1952
|
+
n.className = "tcw-alerts-info";
|
|
1953
|
+
let r = document.createElement("div");
|
|
1954
|
+
r.className = "tcw-alerts-row-main";
|
|
1955
|
+
let i = K.get(e.condition) ?? e.condition, a = e.channel && e.channel !== "price", o = a ? se(e.price) : this.callbacks.formatPrice(e.price);
|
|
1956
|
+
if (r.textContent = `${a && e.label ? `${e.label} ` : ""}${i} ${o}`, n.appendChild(r), e.message) {
|
|
1957
|
+
let t = document.createElement("div");
|
|
1958
|
+
t.className = "tcw-alerts-row-note", t.textContent = e.message, n.appendChild(t);
|
|
1959
|
+
}
|
|
1960
|
+
if (e.triggered) {
|
|
1961
|
+
let e = document.createElement("span");
|
|
1962
|
+
e.className = "tcw-alerts-badge", e.textContent = "triggered", r.appendChild(e);
|
|
1963
|
+
}
|
|
1964
|
+
let s = document.createElement("button");
|
|
1965
|
+
s.type = "button", s.className = "tcw-alerts-del", s.setAttribute("aria-label", "Delete alert"), s.innerHTML = y("trash", 14), s.addEventListener("click", () => this.callbacks.onRemove(e.id)), t.appendChild(n), t.appendChild(s), this.listEl.appendChild(t);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}, le = class {
|
|
1969
|
+
el;
|
|
1970
|
+
indicatorsEl;
|
|
1971
|
+
drawingsEl;
|
|
1972
|
+
compareEl = null;
|
|
1973
|
+
callbacks;
|
|
1974
|
+
open = !1;
|
|
1975
|
+
constructor(e, t) {
|
|
1976
|
+
this.callbacks = t, this.el = document.createElement("div"), this.el.className = "tcw-tree-panel", this.el.hidden = !0;
|
|
1977
|
+
let n = document.createElement("div");
|
|
1978
|
+
n.className = "tcw-tree-header";
|
|
1979
|
+
let r = document.createElement("span");
|
|
1980
|
+
r.textContent = "Objects";
|
|
1981
|
+
let i = document.createElement("button");
|
|
1982
|
+
i.type = "button", i.className = "tcw-tree-close", i.setAttribute("aria-label", "Close objects"), i.innerHTML = y("x", 14), i.addEventListener("click", () => this.close()), n.appendChild(r), n.appendChild(i), this.el.appendChild(n), this.indicatorsEl = this.makeSection("Indicators"), this.drawingsEl = this.makeSection("Drawings"), this.callbacks.onAddCompare && (this.compareEl = this.makeSection("Compare", () => this.callbacks.onAddCompare?.())), e.appendChild(this.el);
|
|
1983
|
+
}
|
|
1984
|
+
isOpen() {
|
|
1985
|
+
return this.open;
|
|
1986
|
+
}
|
|
1987
|
+
toggle() {
|
|
1988
|
+
this.open ? this.close() : this.openPanel();
|
|
1989
|
+
}
|
|
1990
|
+
openPanel() {
|
|
1991
|
+
this.open = !0, this.el.hidden = !1;
|
|
1992
|
+
}
|
|
1993
|
+
close() {
|
|
1994
|
+
this.open = !1, this.el.hidden = !0;
|
|
1995
|
+
}
|
|
1996
|
+
destroy() {
|
|
1997
|
+
this.el.remove();
|
|
1998
|
+
}
|
|
1999
|
+
setObjects(e, t, n = []) {
|
|
2000
|
+
this.renderIndicators(e), this.renderDrawings(t), this.renderCompares(n);
|
|
2001
|
+
}
|
|
2002
|
+
makeSection(e, t) {
|
|
2003
|
+
let n = document.createElement("div");
|
|
2004
|
+
n.className = "tcw-tree-section";
|
|
2005
|
+
let r = document.createElement("div");
|
|
2006
|
+
r.className = "tcw-tree-section-head";
|
|
2007
|
+
let i = document.createElement("span");
|
|
2008
|
+
if (i.textContent = e, r.appendChild(i), t) {
|
|
2009
|
+
let n = document.createElement("button");
|
|
2010
|
+
n.type = "button", n.className = "tcw-tree-add", n.setAttribute("aria-label", `Add ${e.toLowerCase()}`), n.title = `Add ${e.toLowerCase()}`, n.innerHTML = y("plus", 13), n.addEventListener("click", t), r.appendChild(n);
|
|
2011
|
+
}
|
|
2012
|
+
let a = document.createElement("div");
|
|
2013
|
+
return a.className = "tcw-tree-list", n.appendChild(r), n.appendChild(a), this.el.appendChild(n), a;
|
|
2014
|
+
}
|
|
2015
|
+
renderIndicators(e) {
|
|
2016
|
+
if (this.indicatorsEl.replaceChildren(), e.length === 0) {
|
|
2017
|
+
this.indicatorsEl.appendChild(this.emptyRow("No indicators"));
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
for (let t of e) {
|
|
2021
|
+
let e = this.row(t.name);
|
|
2022
|
+
t.visible || e.classList.add("tcw-tree-hidden");
|
|
2023
|
+
let n = document.createElement("div");
|
|
2024
|
+
n.className = "tcw-tree-actions", this.callbacks.onToggleIndicatorVisible && n.appendChild(this.iconButton(t.visible ? "eye" : "eyeOff", t.visible ? "Hide" : "Show", "", () => this.callbacks.onToggleIndicatorVisible?.(t.instanceId, !t.visible))), this.callbacks.onConfigureIndicator && n.appendChild(this.iconButton("settings", "Indicator settings", "", () => this.callbacks.onConfigureIndicator?.(t.instanceId))), n.appendChild(this.iconButton("trash", "Remove indicator", "tcw-tree-del", () => this.callbacks.onRemoveIndicator(t.instanceId))), e.appendChild(n), this.indicatorsEl.appendChild(e);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
renderDrawings(e) {
|
|
2028
|
+
if (this.drawingsEl.replaceChildren(), e.length === 0) {
|
|
2029
|
+
this.drawingsEl.appendChild(this.emptyRow("No drawings"));
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
for (let t of e) {
|
|
2033
|
+
let e = this.row(t.label);
|
|
2034
|
+
t.visible || e.classList.add("tcw-tree-hidden");
|
|
2035
|
+
let n = document.createElement("div");
|
|
2036
|
+
n.className = "tcw-tree-actions", n.appendChild(this.iconButton(t.visible ? "eye" : "eyeOff", t.visible ? "Hide" : "Show", "", () => this.callbacks.onToggleDrawingVisible(t.id, !t.visible))), n.appendChild(this.iconButton(t.locked ? "lock" : "unlock", t.locked ? "Unlock" : "Lock", t.locked ? "tcw-tree-on" : "", () => this.callbacks.onToggleDrawingLocked(t.id, !t.locked))), n.appendChild(this.iconButton("trash", "Remove drawing", "tcw-tree-del", () => this.callbacks.onRemoveDrawing(t.id))), e.appendChild(n), this.drawingsEl.appendChild(e);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
renderCompares(e) {
|
|
2040
|
+
if (this.compareEl) {
|
|
2041
|
+
if (this.compareEl.replaceChildren(), e.length === 0) {
|
|
2042
|
+
this.compareEl.appendChild(this.emptyRow("No comparisons"));
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
for (let t of e) {
|
|
2046
|
+
let e = this.row(t.label), n = document.createElement("span");
|
|
2047
|
+
n.className = "tcw-tree-dot", n.style.background = t.color, e.insertBefore(n, e.firstChild), this.callbacks.onRemoveCompare && e.appendChild(this.iconButton("trash", "Remove comparison", "tcw-tree-del", () => this.callbacks.onRemoveCompare?.(t.id))), this.compareEl.appendChild(e);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
row(e) {
|
|
2052
|
+
let t = document.createElement("div");
|
|
2053
|
+
t.className = "tcw-tree-row";
|
|
2054
|
+
let n = document.createElement("span");
|
|
2055
|
+
return n.className = "tcw-tree-name", n.textContent = e, n.title = e, t.appendChild(n), t;
|
|
2056
|
+
}
|
|
2057
|
+
emptyRow(e) {
|
|
2058
|
+
let t = document.createElement("div");
|
|
2059
|
+
return t.className = "tcw-tree-empty", t.textContent = e, t;
|
|
2060
|
+
}
|
|
2061
|
+
iconButton(e, t, n, r) {
|
|
2062
|
+
let i = document.createElement("button");
|
|
2063
|
+
return i.type = "button", i.className = `tcw-tree-btn ${n}`.trim(), i.setAttribute("aria-label", t), i.title = t, i.innerHTML = y(e, 14), i.addEventListener("click", r), i;
|
|
2064
|
+
}
|
|
2065
|
+
}, ue = {
|
|
2066
|
+
trendLine: "Trend Line",
|
|
2067
|
+
ray: "Ray",
|
|
2068
|
+
extendedLine: "Extended Line",
|
|
2069
|
+
horizontalLine: "Horizontal Line",
|
|
2070
|
+
verticalLine: "Vertical Line",
|
|
2071
|
+
rectangle: "Rectangle",
|
|
2072
|
+
ellipse: "Ellipse",
|
|
2073
|
+
triangle: "Triangle",
|
|
2074
|
+
arrow: "Arrow",
|
|
2075
|
+
parallelChannel: "Parallel Channel",
|
|
2076
|
+
regressionChannel: "Regression Channel",
|
|
2077
|
+
pitchfork: "Pitchfork",
|
|
2078
|
+
fibRetracement: "Fib Retracement",
|
|
2079
|
+
fibExtension: "Fib Extension",
|
|
2080
|
+
fibTimeZones: "Fib Time Zones",
|
|
2081
|
+
gannBox: "Gann Box",
|
|
2082
|
+
gannFan: "Gann Fan",
|
|
2083
|
+
elliottWave: "Elliott Wave",
|
|
2084
|
+
priceRange: "Price Range",
|
|
2085
|
+
dateRange: "Date Range",
|
|
2086
|
+
measure: "Measure",
|
|
2087
|
+
textAnnotation: "Text",
|
|
2088
|
+
anchoredVWAP: "Anchored VWAP",
|
|
2089
|
+
volumeProfileRange: "Volume Profile"
|
|
2090
|
+
};
|
|
2091
|
+
function de(e) {
|
|
2092
|
+
return ue[e] ?? e;
|
|
2093
|
+
}
|
|
2094
|
+
//#endregion
|
|
2095
|
+
//#region src/widget/WidgetIndicatorSettings.ts
|
|
2096
|
+
var fe = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
2097
|
+
function pe(e) {
|
|
2098
|
+
return e.replace(/_/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (e) => e.toUpperCase());
|
|
2099
|
+
}
|
|
2100
|
+
var me = class {
|
|
2101
|
+
backdrop;
|
|
2102
|
+
modal;
|
|
2103
|
+
bodyEl;
|
|
2104
|
+
titleEl;
|
|
2105
|
+
callbacks;
|
|
2106
|
+
target = null;
|
|
2107
|
+
draft = {};
|
|
2108
|
+
constructor(e, t) {
|
|
2109
|
+
this.callbacks = t, this.backdrop = document.createElement("div"), this.backdrop.className = "tcw-modal-backdrop", this.backdrop.hidden = !0, this.backdrop.addEventListener("click", (e) => {
|
|
2110
|
+
e.target === this.backdrop && this.close();
|
|
2111
|
+
}), this.modal = document.createElement("div"), this.modal.className = "tcw-modal tcw-modal-narrow";
|
|
2112
|
+
let n = document.createElement("div");
|
|
2113
|
+
n.className = "tcw-modal-header", this.titleEl = document.createElement("h3"), this.titleEl.textContent = "Indicator";
|
|
2114
|
+
let r = document.createElement("button");
|
|
2115
|
+
r.className = "tcw-modal-close", r.setAttribute("aria-label", "Close"), r.innerHTML = y("x", 16), r.addEventListener("click", () => this.close()), n.appendChild(this.titleEl), n.appendChild(r), this.bodyEl = document.createElement("div"), this.bodyEl.className = "tcw-modal-body";
|
|
2116
|
+
let i = document.createElement("div");
|
|
2117
|
+
i.className = "tcw-modal-footer";
|
|
2118
|
+
let a = document.createElement("button");
|
|
2119
|
+
a.className = "tcw-reset-link", a.textContent = "Reset to defaults", a.addEventListener("click", () => this.resetDefaults());
|
|
2120
|
+
let o = document.createElement("button");
|
|
2121
|
+
o.className = "tcw-done-btn", o.textContent = "Done", o.addEventListener("click", () => this.close()), i.appendChild(a), i.appendChild(o), this.modal.appendChild(n), this.modal.appendChild(this.bodyEl), this.modal.appendChild(i), this.backdrop.appendChild(this.modal), e.appendChild(this.backdrop);
|
|
2122
|
+
}
|
|
2123
|
+
open(e) {
|
|
2124
|
+
this.target = e, this.titleEl.textContent = `${e.name} settings`, this.draft = {}, this.renderFields(), this.backdrop.hidden = !1;
|
|
2125
|
+
}
|
|
2126
|
+
close() {
|
|
2127
|
+
this.backdrop.hidden = !0, this.target = null, this.callbacks.onClose();
|
|
2128
|
+
}
|
|
2129
|
+
isOpen() {
|
|
2130
|
+
return !this.backdrop.hidden;
|
|
2131
|
+
}
|
|
2132
|
+
destroy() {
|
|
2133
|
+
this.backdrop.remove();
|
|
2134
|
+
}
|
|
2135
|
+
currentValue(e) {
|
|
2136
|
+
if (e in this.draft) return this.draft[e];
|
|
2137
|
+
let t = this.target?.params[e], n = this.target?.defaults[e];
|
|
2138
|
+
return t ?? n;
|
|
2139
|
+
}
|
|
2140
|
+
setValue(e, t) {
|
|
2141
|
+
this.target && (this.draft[e] = t, this.callbacks.onApply(this.target.instanceId, { ...this.draft }));
|
|
2142
|
+
}
|
|
2143
|
+
resetDefaults() {
|
|
2144
|
+
if (!this.target) return;
|
|
2145
|
+
let e = {};
|
|
2146
|
+
for (let [t, n] of Object.entries(this.target.defaults)) e[t] = n;
|
|
2147
|
+
this.draft = e, this.callbacks.onApply(this.target.instanceId, { ...e }), this.renderFields();
|
|
2148
|
+
}
|
|
2149
|
+
renderFields() {
|
|
2150
|
+
if (this.bodyEl.replaceChildren(), !this.target) return;
|
|
2151
|
+
let e = document.createElement("div");
|
|
2152
|
+
e.className = "tcw-settings-section";
|
|
2153
|
+
let t = Object.keys(this.target.defaults);
|
|
2154
|
+
if (t.length === 0) {
|
|
2155
|
+
let t = document.createElement("div");
|
|
2156
|
+
t.className = "tcw-settings-label", t.textContent = "This indicator has no adjustable parameters.", e.appendChild(t);
|
|
2157
|
+
}
|
|
2158
|
+
for (let n of t) e.appendChild(this.fieldRow(n));
|
|
2159
|
+
this.bodyEl.appendChild(e);
|
|
2160
|
+
}
|
|
2161
|
+
fieldRow(e) {
|
|
2162
|
+
let t = document.createElement("div");
|
|
2163
|
+
t.className = "tcw-settings-row";
|
|
2164
|
+
let n = document.createElement("span");
|
|
2165
|
+
n.className = "tcw-settings-label", n.textContent = pe(e), t.appendChild(n);
|
|
2166
|
+
let r = this.currentValue(e), i = typeof (this.target?.defaults[e] ?? r);
|
|
2167
|
+
return i === "boolean" ? t.appendChild(this.toggleControl(e, !!r)) : i === "number" ? t.appendChild(this.numberControl(e, Number(r))) : typeof r == "string" && fe.test(r) ? t.appendChild(this.colorControl(e, r)) : t.appendChild(this.textControl(e, String(r ?? ""))), t;
|
|
2168
|
+
}
|
|
2169
|
+
toggleControl(e, t) {
|
|
2170
|
+
let n = document.createElement("button");
|
|
2171
|
+
return n.className = `tcw-toggle${t ? " tcw-on" : ""}`, n.type = "button", n.addEventListener("click", () => {
|
|
2172
|
+
let t = !n.classList.contains("tcw-on");
|
|
2173
|
+
n.classList.toggle("tcw-on", t), this.setValue(e, t);
|
|
2174
|
+
}), n;
|
|
2175
|
+
}
|
|
2176
|
+
numberControl(e, t) {
|
|
2177
|
+
let n = document.createElement("input");
|
|
2178
|
+
return n.type = "number", n.className = "tcw-indi-input", n.value = String(t), n.step = Number.isInteger(t) ? "1" : "0.1", n.addEventListener("change", () => {
|
|
2179
|
+
let t = Number(n.value);
|
|
2180
|
+
Number.isFinite(t) && this.setValue(e, t);
|
|
2181
|
+
}), n;
|
|
2182
|
+
}
|
|
2183
|
+
colorControl(e, t) {
|
|
2184
|
+
let n = document.createElement("input");
|
|
2185
|
+
return n.type = "color", n.className = "tcw-indi-color", n.value = t.length === 4 ? he(t) : t, n.addEventListener("input", () => this.setValue(e, n.value)), n;
|
|
2186
|
+
}
|
|
2187
|
+
textControl(e, t) {
|
|
2188
|
+
let n = document.createElement("input");
|
|
2189
|
+
return n.type = "text", n.className = "tcw-indi-input", n.value = t, n.addEventListener("change", () => this.setValue(e, n.value)), n;
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
function he(e) {
|
|
2193
|
+
let t = e[1], n = e[2], r = e[3];
|
|
2194
|
+
return `#${t}${t}${n}${n}${r}${r}`;
|
|
2195
|
+
}
|
|
2196
|
+
//#endregion
|
|
2197
|
+
//#region src/widget/DrawingTemplateStore.ts
|
|
2198
|
+
function ge() {
|
|
2199
|
+
try {
|
|
2200
|
+
return typeof localStorage < "u" ? localStorage : null;
|
|
2201
|
+
} catch {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
var _e = [
|
|
2206
|
+
"color",
|
|
2207
|
+
"lineWidth",
|
|
2208
|
+
"lineStyle",
|
|
2209
|
+
"fillColor",
|
|
2210
|
+
"fillOpacity",
|
|
2211
|
+
"fontSize"
|
|
2212
|
+
];
|
|
2213
|
+
function J(e) {
|
|
2214
|
+
if (!e || typeof e != "object") return {};
|
|
2215
|
+
let t = {}, n = e;
|
|
2216
|
+
for (let e of _e) n[e] !== void 0 && (t[e] = n[e]);
|
|
2217
|
+
return t;
|
|
2218
|
+
}
|
|
2219
|
+
var ve = class {
|
|
2220
|
+
templates = [];
|
|
2221
|
+
constructor(e = "tcw:draw-templates", t = ge()) {
|
|
2222
|
+
this.storageKey = e, this.storage = t, this.load();
|
|
2223
|
+
}
|
|
2224
|
+
list() {
|
|
2225
|
+
return this.templates.map((e) => ({
|
|
2226
|
+
name: e.name,
|
|
2227
|
+
style: { ...e.style }
|
|
2228
|
+
}));
|
|
2229
|
+
}
|
|
2230
|
+
get(e) {
|
|
2231
|
+
let t = this.templates.find((t) => t.name === e);
|
|
2232
|
+
return t ? {
|
|
2233
|
+
name: t.name,
|
|
2234
|
+
style: { ...t.style }
|
|
2235
|
+
} : null;
|
|
2236
|
+
}
|
|
2237
|
+
save(e, t) {
|
|
2238
|
+
let n = e.trim();
|
|
2239
|
+
if (!n) return;
|
|
2240
|
+
let r = J(t);
|
|
2241
|
+
this.templates = this.templates.filter((e) => e.name !== n), this.templates.push({
|
|
2242
|
+
name: n,
|
|
2243
|
+
style: r
|
|
2244
|
+
}), this.persist();
|
|
2245
|
+
}
|
|
2246
|
+
remove(e) {
|
|
2247
|
+
let t = this.templates.filter((t) => t.name !== e);
|
|
2248
|
+
t.length !== this.templates.length && (this.templates = t, this.persist());
|
|
2249
|
+
}
|
|
2250
|
+
clear() {
|
|
2251
|
+
this.templates = [], this.persist();
|
|
2252
|
+
}
|
|
2253
|
+
load() {
|
|
2254
|
+
if (this.storage) try {
|
|
2255
|
+
let e = this.storage.getItem(this.storageKey);
|
|
2256
|
+
if (!e) return;
|
|
2257
|
+
let t = JSON.parse(e);
|
|
2258
|
+
if (!Array.isArray(t)) return;
|
|
2259
|
+
this.templates = t.filter((e) => e && typeof e.name == "string").map((e) => ({
|
|
2260
|
+
name: e.name,
|
|
2261
|
+
style: J(e.style)
|
|
2262
|
+
}));
|
|
2263
|
+
} catch {}
|
|
2264
|
+
}
|
|
2265
|
+
persist() {
|
|
2266
|
+
if (this.storage) try {
|
|
2267
|
+
this.storage.setItem(this.storageKey, JSON.stringify(this.templates));
|
|
2268
|
+
} catch {}
|
|
2269
|
+
}
|
|
2270
|
+
}, ye = [
|
|
2271
|
+
"#2962ff",
|
|
2272
|
+
"#089981",
|
|
2273
|
+
"#f23645",
|
|
2274
|
+
"#ff9800",
|
|
2275
|
+
"#ab47bc",
|
|
2276
|
+
"#26c6da",
|
|
2277
|
+
"#ffffff",
|
|
2278
|
+
"#787b86"
|
|
2279
|
+
], be = [
|
|
2280
|
+
1,
|
|
2281
|
+
2,
|
|
2282
|
+
3,
|
|
2283
|
+
4
|
|
2284
|
+
], xe = [
|
|
2285
|
+
"solid",
|
|
2286
|
+
"dashed",
|
|
2287
|
+
"dotted"
|
|
2288
|
+
], Se = class {
|
|
2289
|
+
el;
|
|
2290
|
+
templatesEl;
|
|
2291
|
+
nameInput;
|
|
2292
|
+
callbacks;
|
|
2293
|
+
store;
|
|
2294
|
+
open = !1;
|
|
2295
|
+
constructor(e, t, n) {
|
|
2296
|
+
this.callbacks = t, this.store = n, this.el = document.createElement("div"), this.el.className = "tcw-style-panel", this.el.hidden = !0;
|
|
2297
|
+
let r = document.createElement("div");
|
|
2298
|
+
r.className = "tcw-style-header";
|
|
2299
|
+
let i = document.createElement("span");
|
|
2300
|
+
i.textContent = "Drawing Style";
|
|
2301
|
+
let a = document.createElement("button");
|
|
2302
|
+
a.type = "button", a.className = "tcw-style-close", a.setAttribute("aria-label", "Close"), a.innerHTML = y("x", 14), a.addEventListener("click", () => this.close()), r.appendChild(i), r.appendChild(a), this.el.appendChild(r), this.el.appendChild(this.colorRow()), this.el.appendChild(this.widthRow()), this.el.appendChild(this.lineStyleRow());
|
|
2303
|
+
let o = document.createElement("div");
|
|
2304
|
+
o.className = "tcw-style-subhead", o.textContent = "Templates", this.el.appendChild(o);
|
|
2305
|
+
let s = document.createElement("div");
|
|
2306
|
+
s.className = "tcw-style-save", this.nameInput = document.createElement("input"), this.nameInput.type = "text", this.nameInput.placeholder = "Template name", this.nameInput.className = "tcw-style-name";
|
|
2307
|
+
let c = document.createElement("button");
|
|
2308
|
+
c.type = "button", c.className = "tcw-style-savebtn", c.innerHTML = `${y("plus", 13)}<span>Save</span>`, c.addEventListener("click", () => this.saveTemplate()), s.appendChild(this.nameInput), s.appendChild(c), this.el.appendChild(s), this.templatesEl = document.createElement("div"), this.templatesEl.className = "tcw-style-templates", this.el.appendChild(this.templatesEl), e.appendChild(this.el), this.renderTemplates();
|
|
2309
|
+
}
|
|
2310
|
+
isOpen() {
|
|
2311
|
+
return this.open;
|
|
2312
|
+
}
|
|
2313
|
+
toggle() {
|
|
2314
|
+
this.open ? this.close() : this.openPanel();
|
|
2315
|
+
}
|
|
2316
|
+
openPanel() {
|
|
2317
|
+
this.open = !0, this.el.hidden = !1, this.syncActiveControls(), this.renderTemplates();
|
|
2318
|
+
}
|
|
2319
|
+
close() {
|
|
2320
|
+
this.open = !1, this.el.hidden = !0;
|
|
2321
|
+
}
|
|
2322
|
+
destroy() {
|
|
2323
|
+
this.el.remove();
|
|
2324
|
+
}
|
|
2325
|
+
colorRow() {
|
|
2326
|
+
let e = document.createElement("div");
|
|
2327
|
+
e.className = "tcw-style-row tcw-style-colors";
|
|
2328
|
+
for (let t of ye) {
|
|
2329
|
+
let n = document.createElement("button");
|
|
2330
|
+
n.type = "button", n.className = "tcw-style-swatch", n.style.background = t, n.dataset.color = t, n.setAttribute("aria-label", t), n.addEventListener("click", () => this.applyStyle({ color: t })), e.appendChild(n);
|
|
2331
|
+
}
|
|
2332
|
+
let t = document.createElement("input");
|
|
2333
|
+
return t.type = "color", t.className = "tcw-style-picker", t.addEventListener("input", () => this.applyStyle({ color: t.value })), e.appendChild(t), e;
|
|
2334
|
+
}
|
|
2335
|
+
widthRow() {
|
|
2336
|
+
let e = document.createElement("div");
|
|
2337
|
+
e.className = "tcw-style-row tcw-style-widths";
|
|
2338
|
+
for (let t of be) {
|
|
2339
|
+
let n = document.createElement("button");
|
|
2340
|
+
n.type = "button", n.className = "tcw-style-width", n.dataset.width = String(t);
|
|
2341
|
+
let r = document.createElement("span");
|
|
2342
|
+
r.style.height = `${t}px`, n.appendChild(r), n.addEventListener("click", () => this.applyStyle({ lineWidth: t })), e.appendChild(n);
|
|
2343
|
+
}
|
|
2344
|
+
return e;
|
|
2345
|
+
}
|
|
2346
|
+
lineStyleRow() {
|
|
2347
|
+
let e = document.createElement("div");
|
|
2348
|
+
e.className = "tcw-style-row tcw-style-dashes";
|
|
2349
|
+
for (let t of xe) {
|
|
2350
|
+
let n = document.createElement("button");
|
|
2351
|
+
n.type = "button", n.className = "tcw-style-dash", n.dataset.linestyle = t, n.textContent = t === "solid" ? "──" : t === "dashed" ? "╌╌" : "┄┄", n.title = t, n.addEventListener("click", () => this.applyStyle({ lineStyle: t })), e.appendChild(n);
|
|
2352
|
+
}
|
|
2353
|
+
return e;
|
|
2354
|
+
}
|
|
2355
|
+
applyStyle(e) {
|
|
2356
|
+
this.callbacks.onStyleChange(e), this.syncActiveControls();
|
|
2357
|
+
}
|
|
2358
|
+
syncActiveControls() {
|
|
2359
|
+
let e = this.callbacks.getStyle();
|
|
2360
|
+
this.el.querySelectorAll(".tcw-style-swatch").forEach((t) => {
|
|
2361
|
+
t.classList.toggle("tcw-on", t.dataset.color?.toLowerCase() === e.color?.toLowerCase());
|
|
2362
|
+
}), this.el.querySelectorAll(".tcw-style-width").forEach((t) => {
|
|
2363
|
+
t.classList.toggle("tcw-on", Number(t.dataset.width) === e.lineWidth);
|
|
2364
|
+
}), this.el.querySelectorAll(".tcw-style-dash").forEach((t) => {
|
|
2365
|
+
t.classList.toggle("tcw-on", t.dataset.linestyle === e.lineStyle);
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
saveTemplate() {
|
|
2369
|
+
let e = this.nameInput.value.trim();
|
|
2370
|
+
e && (this.store.save(e, this.callbacks.getStyle()), this.nameInput.value = "", this.renderTemplates());
|
|
2371
|
+
}
|
|
2372
|
+
renderTemplates() {
|
|
2373
|
+
this.templatesEl.replaceChildren();
|
|
2374
|
+
let e = this.store.list();
|
|
2375
|
+
if (e.length === 0) {
|
|
2376
|
+
let e = document.createElement("div");
|
|
2377
|
+
e.className = "tcw-style-empty", e.textContent = "No saved templates", this.templatesEl.appendChild(e);
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
for (let t of e) {
|
|
2381
|
+
let e = document.createElement("div");
|
|
2382
|
+
e.className = "tcw-style-template";
|
|
2383
|
+
let n = document.createElement("button");
|
|
2384
|
+
n.type = "button", n.className = "tcw-style-apply";
|
|
2385
|
+
let r = document.createElement("span");
|
|
2386
|
+
r.className = "tcw-style-tdot", r.style.background = t.style.color ?? "var(--tcw-text-muted)";
|
|
2387
|
+
let i = document.createElement("span");
|
|
2388
|
+
i.textContent = t.name, n.appendChild(r), n.appendChild(i), n.addEventListener("click", () => {
|
|
2389
|
+
this.callbacks.onStyleChange({ ...t.style }), this.syncActiveControls();
|
|
2390
|
+
});
|
|
2391
|
+
let a = document.createElement("button");
|
|
2392
|
+
a.type = "button", a.className = "tcw-style-tdel", a.setAttribute("aria-label", "Delete template"), a.innerHTML = y("trash", 13), a.addEventListener("click", () => {
|
|
2393
|
+
this.store.remove(t.name), this.renderTemplates();
|
|
2394
|
+
}), e.appendChild(n), e.appendChild(a), this.templatesEl.appendChild(e);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2398
|
+
//#endregion
|
|
2399
|
+
//#region src/widget/DrawingFavoritesStore.ts
|
|
2400
|
+
function Ce() {
|
|
2401
|
+
try {
|
|
2402
|
+
return typeof localStorage < "u" ? localStorage : null;
|
|
2403
|
+
} catch {
|
|
2404
|
+
return null;
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
var we = class {
|
|
2408
|
+
favorites = [];
|
|
2409
|
+
constructor(e = "tcw:draw-favorites", t = Ce()) {
|
|
2410
|
+
this.storageKey = e, this.storage = t, this.load();
|
|
2411
|
+
}
|
|
2412
|
+
list() {
|
|
2413
|
+
return [...this.favorites];
|
|
2414
|
+
}
|
|
2415
|
+
has(e) {
|
|
2416
|
+
return this.favorites.includes(e);
|
|
2417
|
+
}
|
|
2418
|
+
add(e) {
|
|
2419
|
+
!e || this.favorites.includes(e) || (this.favorites = [...this.favorites, e], this.persist());
|
|
2420
|
+
}
|
|
2421
|
+
remove(e) {
|
|
2422
|
+
this.favorites.includes(e) && (this.favorites = this.favorites.filter((t) => t !== e), this.persist());
|
|
2423
|
+
}
|
|
2424
|
+
toggle(e) {
|
|
2425
|
+
return this.has(e) ? (this.remove(e), !1) : (this.add(e), !0);
|
|
2426
|
+
}
|
|
2427
|
+
seedDefaults(e) {
|
|
2428
|
+
this.favorites.length > 0 || (this.favorites = [...new Set(e.filter(Boolean))], this.persist());
|
|
2429
|
+
}
|
|
2430
|
+
load() {
|
|
2431
|
+
if (this.storage) try {
|
|
2432
|
+
let e = this.storage.getItem(this.storageKey);
|
|
2433
|
+
if (!e) return;
|
|
2434
|
+
let t = JSON.parse(e);
|
|
2435
|
+
Array.isArray(t) && (this.favorites = [...new Set(t.filter((e) => typeof e == "string"))]);
|
|
2436
|
+
} catch {}
|
|
2437
|
+
}
|
|
2438
|
+
persist() {
|
|
2439
|
+
if (this.storage) try {
|
|
2440
|
+
this.storage.setItem(this.storageKey, JSON.stringify(this.favorites));
|
|
2441
|
+
} catch {}
|
|
2442
|
+
}
|
|
2443
|
+
}, Te = class {
|
|
2444
|
+
el;
|
|
2445
|
+
labelEl;
|
|
2446
|
+
callbacks;
|
|
2447
|
+
visible = !1;
|
|
2448
|
+
constructor(e, t) {
|
|
2449
|
+
this.callbacks = t, this.el = document.createElement("div"), this.el.className = "tcw-bracket-bar", this.el.hidden = !0, this.labelEl = document.createElement("span"), this.labelEl.className = "tcw-bracket-label", this.el.appendChild(this.labelEl);
|
|
2450
|
+
let n = document.createElement("span");
|
|
2451
|
+
n.className = "tcw-bracket-hint", n.textContent = "drag lines to adjust", this.el.appendChild(n);
|
|
2452
|
+
let r = document.createElement("button");
|
|
2453
|
+
r.type = "button", r.className = "tcw-bracket-cancel", r.innerHTML = `${y("x", 14)}<span>Cancel</span>`, r.addEventListener("click", () => this.callbacks.onCancel());
|
|
2454
|
+
let i = document.createElement("button");
|
|
2455
|
+
i.type = "button", i.className = "tcw-bracket-confirm", i.innerHTML = `${y("check", 14)}<span>Place</span>`, i.addEventListener("click", () => this.callbacks.onConfirm()), this.el.appendChild(r), this.el.appendChild(i), e.appendChild(this.el);
|
|
2456
|
+
}
|
|
2457
|
+
show(e) {
|
|
2458
|
+
this.visible = !0, this.labelEl.textContent = e === "buy" ? "Long bracket" : "Short bracket", this.labelEl.dataset.side = e, this.el.hidden = !1;
|
|
2459
|
+
}
|
|
2460
|
+
hide() {
|
|
2461
|
+
this.visible = !1, this.el.hidden = !0;
|
|
2462
|
+
}
|
|
2463
|
+
isVisible() {
|
|
2464
|
+
return this.visible;
|
|
2465
|
+
}
|
|
2466
|
+
destroy() {
|
|
2467
|
+
this.el.remove();
|
|
2468
|
+
}
|
|
2469
|
+
}, Ee = class {
|
|
2470
|
+
options;
|
|
2471
|
+
audioCtx = null;
|
|
2472
|
+
audioEl = null;
|
|
2473
|
+
permissionAsked = !1;
|
|
2474
|
+
constructor(e = {}) {
|
|
2475
|
+
if (this.options = e, typeof e.sound == "string") try {
|
|
2476
|
+
this.audioEl = new Audio(e.sound), this.audioEl.preload = "auto";
|
|
2477
|
+
} catch {
|
|
2478
|
+
this.audioEl = null;
|
|
2479
|
+
}
|
|
2480
|
+
e.desktop && this.ensurePermission();
|
|
2481
|
+
}
|
|
2482
|
+
setOptions(e) {
|
|
2483
|
+
this.options = {
|
|
2484
|
+
...this.options,
|
|
2485
|
+
...e
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
notify(e) {
|
|
2489
|
+
this.options.sound && this.playSound(), this.options.desktop && this.showDesktop(e);
|
|
2490
|
+
}
|
|
2491
|
+
playSound() {
|
|
2492
|
+
if (this.audioEl) {
|
|
2493
|
+
this.audioEl.currentTime = 0, this.audioEl.play().catch(() => {});
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
this.beep();
|
|
2497
|
+
}
|
|
2498
|
+
beep() {
|
|
2499
|
+
try {
|
|
2500
|
+
let e = window.AudioContext ?? window.webkitAudioContext;
|
|
2501
|
+
if (!e) return;
|
|
2502
|
+
this.audioCtx ||= new e();
|
|
2503
|
+
let t = this.audioCtx, n = t.currentTime, r = t.createGain();
|
|
2504
|
+
r.gain.setValueAtTime(1e-4, n), r.gain.exponentialRampToValueAtTime(.2, n + .01), r.gain.exponentialRampToValueAtTime(1e-4, n + .35), r.connect(t.destination);
|
|
2505
|
+
let i = t.createOscillator();
|
|
2506
|
+
i.type = "sine", i.frequency.setValueAtTime(880, n), i.frequency.setValueAtTime(1320, n + .16), i.connect(r), i.start(n), i.stop(n + .36);
|
|
2507
|
+
} catch {}
|
|
2508
|
+
}
|
|
2509
|
+
ensurePermission() {
|
|
2510
|
+
this.permissionAsked || (this.permissionAsked = !0, !(typeof Notification > "u") && Notification.permission === "default" && Notification.requestPermission().catch(() => {}));
|
|
2511
|
+
}
|
|
2512
|
+
showDesktop(e) {
|
|
2513
|
+
if (!(typeof Notification > "u")) {
|
|
2514
|
+
if (Notification.permission !== "granted") {
|
|
2515
|
+
this.ensurePermission();
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
try {
|
|
2519
|
+
new Notification(this.options.title ?? "Price alert", { body: e });
|
|
2520
|
+
} catch {}
|
|
2521
|
+
}
|
|
1096
2522
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
2523
|
+
destroy() {
|
|
2524
|
+
try {
|
|
2525
|
+
this.audioCtx?.close();
|
|
2526
|
+
} catch {}
|
|
2527
|
+
this.audioCtx = null, this.audioEl = null;
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
//#endregion
|
|
2531
|
+
//#region src/widget/ladderRows.ts
|
|
2532
|
+
function De(e, t = 12) {
|
|
2533
|
+
let n = [...e.asks].sort((e, t) => e.price - t.price).slice(0, t), r = [...e.bids].sort((e, t) => t.price - e.price).slice(0, t), i = n.length > 0 ? n[0].price : null, a = r.length > 0 ? r[0].price : null, o = i !== null && a !== null ? (i + a) / 2 : null, s = /* @__PURE__ */ new Map(), c = (e) => {
|
|
2534
|
+
let t = s.get(e);
|
|
2535
|
+
return t || (t = {
|
|
2536
|
+
price: e,
|
|
2537
|
+
bidVolume: 0,
|
|
2538
|
+
askVolume: 0
|
|
2539
|
+
}, s.set(e, t)), t;
|
|
2540
|
+
};
|
|
2541
|
+
for (let e of n) c(e.price).askVolume += e.volume;
|
|
2542
|
+
for (let e of r) c(e.price).bidVolume += e.volume;
|
|
2543
|
+
let l = [...s.values()].sort((e, t) => t.price - e.price), u = 0;
|
|
2544
|
+
for (let e of l) u = Math.max(u, e.bidVolume, e.askVolume);
|
|
2545
|
+
return {
|
|
2546
|
+
rows: l,
|
|
2547
|
+
maxVolume: u,
|
|
2548
|
+
mid: o
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
//#endregion
|
|
2552
|
+
//#region src/widget/WidgetDepthLadder.ts
|
|
2553
|
+
var Oe = class {
|
|
2554
|
+
el;
|
|
2555
|
+
bodyEl;
|
|
2556
|
+
callbacks;
|
|
2557
|
+
open = !1;
|
|
2558
|
+
levels;
|
|
2559
|
+
constructor(e, t, n = 12) {
|
|
2560
|
+
this.callbacks = t, this.levels = n, this.el = document.createElement("div"), this.el.className = "tcw-ladder", this.el.hidden = !0;
|
|
2561
|
+
let r = document.createElement("div");
|
|
2562
|
+
r.className = "tcw-ladder-header";
|
|
2563
|
+
let i = document.createElement("span");
|
|
2564
|
+
i.textContent = "Depth Ladder";
|
|
2565
|
+
let a = document.createElement("button");
|
|
2566
|
+
a.type = "button", a.className = "tcw-ladder-close", a.setAttribute("aria-label", "Close"), a.innerHTML = y("x", 14), a.addEventListener("click", () => this.close()), r.appendChild(i), r.appendChild(a), this.el.appendChild(r);
|
|
2567
|
+
let o = document.createElement("div");
|
|
2568
|
+
o.className = "tcw-ladder-cols", o.innerHTML = "<span>Bid</span><span>Price</span><span>Ask</span>", this.el.appendChild(o), this.bodyEl = document.createElement("div"), this.bodyEl.className = "tcw-ladder-body", this.el.appendChild(this.bodyEl), e.appendChild(this.el);
|
|
2569
|
+
}
|
|
2570
|
+
isOpen() {
|
|
2571
|
+
return this.open;
|
|
2572
|
+
}
|
|
2573
|
+
toggle() {
|
|
2574
|
+
this.open ? this.close() : this.openPanel();
|
|
2575
|
+
}
|
|
2576
|
+
openPanel() {
|
|
2577
|
+
this.open = !0, this.el.hidden = !1;
|
|
2578
|
+
}
|
|
2579
|
+
close() {
|
|
2580
|
+
this.open = !1, this.el.hidden = !0;
|
|
2581
|
+
}
|
|
2582
|
+
setData(e) {
|
|
2583
|
+
if (!e) {
|
|
2584
|
+
this.renderEmpty();
|
|
2585
|
+
return;
|
|
2586
|
+
}
|
|
2587
|
+
this.render(De(e, this.levels));
|
|
2588
|
+
}
|
|
2589
|
+
destroy() {
|
|
2590
|
+
this.el.remove();
|
|
2591
|
+
}
|
|
2592
|
+
renderEmpty() {
|
|
2593
|
+
this.bodyEl.replaceChildren();
|
|
2594
|
+
let e = document.createElement("div");
|
|
2595
|
+
e.className = "tcw-ladder-empty", e.textContent = "No order-book data", this.bodyEl.appendChild(e);
|
|
2596
|
+
}
|
|
2597
|
+
render(e) {
|
|
2598
|
+
if (this.bodyEl.replaceChildren(), e.rows.length === 0) {
|
|
2599
|
+
this.renderEmpty();
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
let t = e.maxVolume > 0 ? 1 / e.maxVolume : 0;
|
|
2603
|
+
for (let n of e.rows) {
|
|
2604
|
+
let e = n.askVolume > 0, r = document.createElement("div");
|
|
2605
|
+
r.className = "tcw-ladder-row";
|
|
2606
|
+
let i = document.createElement("button");
|
|
2607
|
+
i.type = "button", i.className = "tcw-ladder-cell tcw-ladder-bid", n.bidVolume > 0 ? (i.textContent = Y(n.bidVolume), i.style.setProperty("--w", `${(n.bidVolume * t * 100).toFixed(1)}%`), i.title = `Sell @ ${this.callbacks.formatPrice(n.price)}`, i.addEventListener("click", () => this.callbacks.onTrade("sell", n.price))) : i.disabled = !0;
|
|
2608
|
+
let a = document.createElement("div");
|
|
2609
|
+
a.className = `tcw-ladder-price ${e ? "tcw-ask" : "tcw-bid"}`, a.textContent = this.callbacks.formatPrice(n.price);
|
|
2610
|
+
let o = document.createElement("button");
|
|
2611
|
+
o.type = "button", o.className = "tcw-ladder-cell tcw-ladder-ask", n.askVolume > 0 ? (o.textContent = Y(n.askVolume), o.style.setProperty("--w", `${(n.askVolume * t * 100).toFixed(1)}%`), o.title = `Buy @ ${this.callbacks.formatPrice(n.price)}`, o.addEventListener("click", () => this.callbacks.onTrade("buy", n.price))) : o.disabled = !0, r.appendChild(i), r.appendChild(a), r.appendChild(o), this.bodyEl.appendChild(r);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
};
|
|
2615
|
+
function Y(e) {
|
|
2616
|
+
return e >= 1e3 ? (e / 1e3).toFixed(1) + "k" : e >= 1 ? e.toFixed(2).replace(/\.00$/, "") : e.toPrecision(2);
|
|
2617
|
+
}
|
|
2618
|
+
//#endregion
|
|
2619
|
+
//#region src/widget/WidgetDataWindow.ts
|
|
2620
|
+
var ke = class {
|
|
2621
|
+
el;
|
|
2622
|
+
bodyEl;
|
|
2623
|
+
callbacks;
|
|
2624
|
+
open = !1;
|
|
2625
|
+
constructor(e, t) {
|
|
2626
|
+
this.callbacks = t, this.el = document.createElement("div"), this.el.className = "tcw-datawin", this.el.hidden = !0;
|
|
2627
|
+
let n = document.createElement("div");
|
|
2628
|
+
n.className = "tcw-datawin-header";
|
|
2629
|
+
let r = document.createElement("span");
|
|
2630
|
+
r.textContent = "Data Window";
|
|
2631
|
+
let i = document.createElement("button");
|
|
2632
|
+
i.type = "button", i.className = "tcw-datawin-close", i.setAttribute("aria-label", "Close"), i.innerHTML = y("x", 14), i.addEventListener("click", () => this.close()), n.appendChild(r), n.appendChild(i), this.el.appendChild(n), this.bodyEl = document.createElement("div"), this.bodyEl.className = "tcw-datawin-body", this.el.appendChild(this.bodyEl), e.appendChild(this.el);
|
|
2633
|
+
}
|
|
2634
|
+
isOpen() {
|
|
2635
|
+
return this.open;
|
|
2636
|
+
}
|
|
2637
|
+
toggle() {
|
|
2638
|
+
this.open ? this.close() : this.openPanel();
|
|
2639
|
+
}
|
|
2640
|
+
openPanel() {
|
|
2641
|
+
this.open = !0, this.el.hidden = !1;
|
|
2642
|
+
}
|
|
2643
|
+
close() {
|
|
2644
|
+
this.open = !1, this.el.hidden = !0;
|
|
2645
|
+
}
|
|
2646
|
+
destroy() {
|
|
2647
|
+
this.el.remove();
|
|
2648
|
+
}
|
|
2649
|
+
render(e) {
|
|
2650
|
+
if (!this.open) return;
|
|
2651
|
+
if (this.bodyEl.replaceChildren(), !e.ohlc) {
|
|
2652
|
+
let e = document.createElement("div");
|
|
2653
|
+
e.className = "tcw-datawin-empty", e.textContent = "Hover the chart", this.bodyEl.appendChild(e);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
let t = this.callbacks.formatPrice, n = e.ohlc;
|
|
2657
|
+
this.bodyEl.appendChild(this.row("Open", t(n.open))), this.bodyEl.appendChild(this.row("High", t(n.high))), this.bodyEl.appendChild(this.row("Low", t(n.low))), this.bodyEl.appendChild(this.row("Close", t(n.close)));
|
|
2658
|
+
let r = e.change > 0 ? "up" : e.change < 0 ? "down" : "", i = e.change > 0 ? "+" : "";
|
|
2659
|
+
this.bodyEl.appendChild(this.row("Change", `${i}${t(e.change)} (${i}${e.changePct.toFixed(2)}%)`, r)), this.bodyEl.appendChild(this.row("Volume", X(n.volume)));
|
|
2660
|
+
for (let t of e.indicators) {
|
|
2661
|
+
let e = document.createElement("div");
|
|
2662
|
+
e.className = "tcw-datawin-sub", e.textContent = t.name, this.bodyEl.appendChild(e);
|
|
2663
|
+
for (let e of t.values) this.bodyEl.appendChild(this.row(e.key, Ae(e.value)));
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
row(e, t, n = "") {
|
|
2667
|
+
let r = document.createElement("div");
|
|
2668
|
+
r.className = "tcw-datawin-row";
|
|
2669
|
+
let i = document.createElement("span");
|
|
2670
|
+
i.className = "tcw-datawin-label", i.textContent = e;
|
|
2671
|
+
let a = document.createElement("span");
|
|
2672
|
+
return a.className = `tcw-datawin-value${n ? ` tcw-${n}` : ""}`, a.textContent = t, r.appendChild(i), r.appendChild(a), r;
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
function Ae(e) {
|
|
2676
|
+
if (!Number.isFinite(e)) return "—";
|
|
2677
|
+
let t = Math.abs(e);
|
|
2678
|
+
return t >= 1e3 ? e.toFixed(2) : t >= 1 ? e.toFixed(4) : e.toPrecision(4);
|
|
2679
|
+
}
|
|
2680
|
+
function X(e) {
|
|
2681
|
+
return e >= 1e9 ? (e / 1e9).toFixed(2) + "B" : e >= 1e6 ? (e / 1e6).toFixed(2) + "M" : e >= 1e3 ? (e / 1e3).toFixed(2) + "K" : e.toFixed(0);
|
|
2682
|
+
}
|
|
2683
|
+
//#endregion
|
|
2684
|
+
//#region src/widget/widgetShareState.ts
|
|
2685
|
+
var Z = globalThis.Buffer;
|
|
2686
|
+
function je(e) {
|
|
2687
|
+
if (Z) return Z.from(e, "utf8").toString("base64");
|
|
2688
|
+
let t = new TextEncoder().encode(e), n = "";
|
|
2689
|
+
for (let e of t) n += String.fromCharCode(e);
|
|
2690
|
+
return btoa(n);
|
|
2691
|
+
}
|
|
2692
|
+
function Me(e) {
|
|
2693
|
+
if (Z) return Z.from(e, "base64").toString("utf8");
|
|
2694
|
+
let t = atob(e), n = new Uint8Array(t.length);
|
|
2695
|
+
for (let e = 0; e < t.length; e++) n[e] = t.charCodeAt(e);
|
|
2696
|
+
return new TextDecoder().decode(n);
|
|
2697
|
+
}
|
|
2698
|
+
function Ne(e) {
|
|
2699
|
+
return e.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
2700
|
+
}
|
|
2701
|
+
function Pe(e) {
|
|
2702
|
+
let t = e.replace(/-/g, "+").replace(/_/g, "/");
|
|
2703
|
+
return t + (t.length % 4 == 0 ? "" : "=".repeat(4 - t.length % 4));
|
|
2704
|
+
}
|
|
2705
|
+
function Fe(e) {
|
|
2706
|
+
return Ne(je(JSON.stringify(e)));
|
|
2707
|
+
}
|
|
2708
|
+
function Ie(e) {
|
|
2709
|
+
if (!e) return null;
|
|
2710
|
+
try {
|
|
2711
|
+
let t = Me(Pe(e)), n = JSON.parse(t);
|
|
2712
|
+
return !n || n.v !== 1 || typeof n.symbol != "string" ? null : {
|
|
2713
|
+
v: 1,
|
|
2714
|
+
symbol: n.symbol,
|
|
2715
|
+
timeframe: n.timeframe,
|
|
2716
|
+
chartType: n.chartType,
|
|
2717
|
+
scaleMode: n.scaleMode ?? "regular",
|
|
2718
|
+
indicators: Array.isArray(n.indicators) ? n.indicators : [],
|
|
2719
|
+
drawings: Array.isArray(n.drawings) ? n.drawings : []
|
|
2720
|
+
};
|
|
2721
|
+
} catch {
|
|
2722
|
+
return null;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
var Q = "tcw";
|
|
2726
|
+
function Le(e) {
|
|
2727
|
+
let t = e.startsWith("#") ? e.slice(1) : e;
|
|
2728
|
+
for (let e of t.split("&")) {
|
|
2729
|
+
let t = e.indexOf("=");
|
|
2730
|
+
if (t > 0 && e.slice(0, t) === Q) return decodeURIComponent(e.slice(t + 1));
|
|
2731
|
+
}
|
|
2732
|
+
return null;
|
|
2733
|
+
}
|
|
2734
|
+
function Re(e, t) {
|
|
2735
|
+
let [n] = e.split("#");
|
|
2736
|
+
return `${n}#${Q}=${t}`;
|
|
2737
|
+
}
|
|
2738
|
+
//#endregion
|
|
2739
|
+
//#region src/widget/ChartWidget.ts
|
|
2740
|
+
var $ = [
|
|
2741
|
+
"#f7931a",
|
|
2742
|
+
"#627eea",
|
|
2743
|
+
"#26a17b",
|
|
2744
|
+
"#e84142",
|
|
2745
|
+
"#8247e5",
|
|
2746
|
+
"#f3ba2f"
|
|
2747
|
+
], ze = class {
|
|
2748
|
+
chart;
|
|
2749
|
+
state;
|
|
2750
|
+
toolbar = null;
|
|
2751
|
+
sidebar = null;
|
|
2752
|
+
settings = null;
|
|
2753
|
+
statusBar = null;
|
|
2754
|
+
commandPalette = null;
|
|
2755
|
+
symbolSearch = null;
|
|
2756
|
+
hotkeySheet = null;
|
|
2757
|
+
replayBar = null;
|
|
2758
|
+
dragDrop = null;
|
|
2759
|
+
alertsPanel = null;
|
|
2760
|
+
objectTree = null;
|
|
2761
|
+
indicatorSettings = null;
|
|
2762
|
+
drawingStyle = null;
|
|
2763
|
+
bracketBar = null;
|
|
2764
|
+
alertNotifier = null;
|
|
2765
|
+
depthLadder = null;
|
|
2766
|
+
dataWindow = null;
|
|
2767
|
+
lastHoverIndex = null;
|
|
2768
|
+
favoritesStore = new we();
|
|
2769
|
+
watchlist = null;
|
|
2770
|
+
watchlistSparkBuffer = /* @__PURE__ */ new Map();
|
|
2771
|
+
sessionRefPrice = null;
|
|
2772
|
+
watchlistInterval = null;
|
|
2773
|
+
replayOriginalData = null;
|
|
2774
|
+
replayPollInterval = null;
|
|
2775
|
+
replaySpeed = 5;
|
|
2776
|
+
layoutKeyPrefix = null;
|
|
2777
|
+
layoutDebounceMs = 1500;
|
|
2778
|
+
activeLayoutKey = null;
|
|
2779
|
+
root;
|
|
2780
|
+
chartContainer;
|
|
2781
|
+
destroyed = !1;
|
|
2782
|
+
options;
|
|
2783
|
+
symbols;
|
|
2784
|
+
settingsState;
|
|
2785
|
+
adapter = null;
|
|
2786
|
+
boundGlobalKeydown = null;
|
|
2787
|
+
baseSeries = null;
|
|
2788
|
+
baseTimeframeMs = 0;
|
|
2789
|
+
compares = [];
|
|
2790
|
+
constructor(n, r = {}) {
|
|
2791
|
+
if (this.options = r, this.symbols = r.symbols ?? ee, this.settingsState = { ...p }, r.persistLayouts) {
|
|
2792
|
+
let e = r.persistLayouts === !0 ? {} : r.persistLayouts;
|
|
2793
|
+
this.layoutKeyPrefix = e.keyPrefix ?? "tcw:layout:", this.layoutDebounceMs = e.debounceMs ?? 1500;
|
|
2794
|
+
}
|
|
2795
|
+
let i = this.resolveIsDark(r.theme), a = this.resolveTheme(r.theme);
|
|
1100
2796
|
this.state = {
|
|
2797
|
+
symbol: r.symbol ?? this.symbols[0] ?? "BTCUSDT",
|
|
2798
|
+
timeframe: r.timeframe ?? "5m",
|
|
2799
|
+
chartType: r.chartOptions?.chartType ?? "candlestick",
|
|
2800
|
+
isDark: i,
|
|
2801
|
+
activeIndicators: /* @__PURE__ */ new Map(),
|
|
2802
|
+
activeTool: null,
|
|
2803
|
+
magnetEnabled: !0,
|
|
2804
|
+
connectionState: "connecting",
|
|
2805
|
+
connectionMessage: "Connecting..."
|
|
2806
|
+
}, g(), this.root = document.createElement("div"), this.root.className = "tcw-root", this.root.dataset.tcwTheme = i ? "dark" : "light", n.appendChild(this.root), r.toolbar !== !1 && (this.toolbar = new ne(this.root, {
|
|
2807
|
+
symbols: this.symbols,
|
|
2808
|
+
timeframes: r.timeframes ? c.filter((e) => r.timeframes.includes(e.value)) : c,
|
|
2809
|
+
chartTypes: r.chartTypes ? l.filter((e) => r.chartTypes.includes(e.value)) : l,
|
|
2810
|
+
indicators: d,
|
|
2811
|
+
popularIndicatorIds: f
|
|
2812
|
+
}, {
|
|
2813
|
+
onSymbolClick: () => this.handleSymbolClick(),
|
|
2814
|
+
onTimeframe: (e) => this.handleTimeframe(e),
|
|
2815
|
+
onChartType: (e) => this.handleChartType(e),
|
|
2816
|
+
onAddIndicator: (e) => this.handleAddIndicator(e),
|
|
2817
|
+
onRemoveIndicator: (e) => this.handleRemoveIndicator(e),
|
|
2818
|
+
onScreenshot: () => this.chart.screenshot(),
|
|
2819
|
+
onSettings: () => this.openSettings(),
|
|
2820
|
+
onToggleTheme: () => this.handleToggleTheme(),
|
|
2821
|
+
onToggleReplay: () => this.toggleReplay(),
|
|
2822
|
+
onToggleAlerts: r.alerts === !1 ? void 0 : () => this.toggleAlerts(),
|
|
2823
|
+
onToggleObjects: r.objectTree === !1 ? void 0 : () => this.toggleObjects(),
|
|
2824
|
+
onBracket: r.trading === !1 ? void 0 : (e) => this.startBracket(e),
|
|
2825
|
+
onToggleLadder: r.trading !== !1 && r.depthLadder ? () => this.depthLadder?.toggle() : void 0
|
|
2826
|
+
}));
|
|
2827
|
+
let o = document.createElement("div");
|
|
2828
|
+
if (o.className = "tcw-body", r.drawingTools !== !1 && (r.drawingFavorites && this.favoritesStore.seedDefaults(r.drawingFavorites), this.sidebar = new x(o, {
|
|
2829
|
+
drawingToolGroups: u,
|
|
2830
|
+
favorites: this.favoritesStore.list()
|
|
2831
|
+
}, {
|
|
2832
|
+
onDrawingTool: (e) => this.handleDrawingTool(e),
|
|
2833
|
+
onCancelDrawing: () => this.handleCancelDrawing(),
|
|
2834
|
+
onToggleMagnet: () => this.handleToggleMagnet(),
|
|
2835
|
+
onToggleFavorite: (e) => this.handleToggleFavorite(e),
|
|
2836
|
+
onUndo: () => this.chart.undo(),
|
|
2837
|
+
onRedo: () => this.chart.redo(),
|
|
2838
|
+
onClearDrawings: () => {
|
|
2839
|
+
this.chart.clearDrawings(), this.state = {
|
|
2840
|
+
...this.state,
|
|
2841
|
+
activeTool: null
|
|
2842
|
+
}, this.updateUI();
|
|
2843
|
+
},
|
|
2844
|
+
onToggleStyle: () => this.drawingStyle?.toggle()
|
|
2845
|
+
})), this.chartContainer = document.createElement("div"), this.chartContainer.className = "tcw-chart-container", o.appendChild(this.chartContainer), r.watchlist && (this.watchlist = new V(o, this.symbols, { onSelect: (e) => {
|
|
2846
|
+
this.setSymbol(e);
|
|
2847
|
+
} }), this.watchlist.setActive(this.state.symbol)), this.root.appendChild(o), this.chart = new e(this.chartContainer, {
|
|
2848
|
+
chartType: this.state.chartType,
|
|
2849
|
+
theme: a,
|
|
2850
|
+
autoScale: !0,
|
|
2851
|
+
crosshair: { mode: "magnet" },
|
|
2852
|
+
features: {
|
|
2853
|
+
drawings: !0,
|
|
2854
|
+
drawingMagnet: !0,
|
|
2855
|
+
drawingUndoRedo: !0,
|
|
2856
|
+
indicators: !0,
|
|
2857
|
+
trading: r.trading !== !1,
|
|
2858
|
+
tradingContextMenu: r.trading !== !1,
|
|
2859
|
+
volume: !0,
|
|
2860
|
+
legend: !0,
|
|
2861
|
+
crosshair: !0,
|
|
2862
|
+
keyboard: !0,
|
|
2863
|
+
screenshot: !0,
|
|
2864
|
+
alerts: !0,
|
|
2865
|
+
barCountdown: !0,
|
|
2866
|
+
logScale: !0,
|
|
2867
|
+
watermark: !0
|
|
2868
|
+
},
|
|
2869
|
+
...r.chartOptions
|
|
2870
|
+
}), r.dragDropImport !== !1 && (this.dragDrop = new t(this.chartContainer, {
|
|
2871
|
+
onData: (e, t, n) => {
|
|
2872
|
+
this.setData(e), this.toast(`Loaded ${t.data.length} bars from ${n.name}` + (t.skipped > 0 ? ` (${t.skipped} skipped)` : ""));
|
|
2873
|
+
},
|
|
2874
|
+
onError: (e, t) => {
|
|
2875
|
+
this.toast(`${t.name}: ${e.message}`, "error");
|
|
2876
|
+
}
|
|
2877
|
+
}), this.dragDrop.attach()), r.statusBar !== !1 && (this.statusBar = new w(this.root)), r.settings !== !1 && (this.settings = new C({
|
|
2878
|
+
onChange: (e) => this.applySettings(e),
|
|
2879
|
+
onReset: () => this.resetSettings(),
|
|
2880
|
+
onClose: () => {}
|
|
2881
|
+
})), this.symbolSearch = new E({
|
|
2882
|
+
onPick: (e) => {
|
|
2883
|
+
this.setSymbol(e);
|
|
2884
|
+
},
|
|
2885
|
+
onClose: () => {}
|
|
2886
|
+
}), this.hotkeySheet = new N({ onClose: () => {} }), this.chart.on("barClick", (e) => {
|
|
2887
|
+
if (!this.replayBar?.isMounted()) return;
|
|
2888
|
+
let t = e.payload.barIndex;
|
|
2889
|
+
typeof t == "number" && (this.chart.getReplayState() === "playing" && this.chart.replayPause(), this.chart.replaySeek(t), this.replayBar.setState("paused"));
|
|
2890
|
+
}), this.dataWindow = new ke(this.root, { formatPrice: (e) => this.formatAlertPrice(e) }), this.chart.on("crosshairMove", (e) => {
|
|
2891
|
+
let t = e.payload;
|
|
2892
|
+
this.lastHoverIndex = typeof t.barIndex == "number" ? t.barIndex : null, this.dataWindow?.isOpen() && this.dataWindow.render(this.buildDataWindowModel());
|
|
2893
|
+
}), r.drawingTools !== !1 && (this.drawingStyle = new Se(this.root, {
|
|
2894
|
+
onStyleChange: (e) => {
|
|
2895
|
+
this.chart.setDrawingStyle(e), this.chart.setSelectedDrawingStyle(e);
|
|
2896
|
+
},
|
|
2897
|
+
getStyle: () => this.chart.getDrawingStyle()
|
|
2898
|
+
}, new ve())), r.trading !== !1 && (this.bracketBar = new Te(this.root, {
|
|
2899
|
+
onConfirm: () => this.chart.confirmBracket(),
|
|
2900
|
+
onCancel: () => {
|
|
2901
|
+
this.chart.cancelBracket(), this.bracketBar?.hide();
|
|
2902
|
+
}
|
|
2903
|
+
}), this.chart.on("bracketPlace", (e) => {
|
|
2904
|
+
let t = e.payload;
|
|
2905
|
+
this.bracketBar?.hide(), this.toast(`${t.side === "buy" ? "Long" : "Short"} bracket placed · ${t.riskReward.toFixed(2)}R`);
|
|
2906
|
+
}), r.depthLadder && (this.depthLadder = new Oe(this.root, {
|
|
2907
|
+
onTrade: (e, t) => {
|
|
2908
|
+
this.chart.placeOrderIntent({
|
|
2909
|
+
side: e,
|
|
2910
|
+
type: "limit",
|
|
2911
|
+
price: t
|
|
2912
|
+
}), this.toast(`${e === "buy" ? "Buy" : "Sell"} limit @ ${this.formatAlertPrice(t)}`);
|
|
2913
|
+
},
|
|
2914
|
+
formatPrice: (e) => this.formatAlertPrice(e)
|
|
2915
|
+
})), this.chart.on("dataUpdate", (e) => {
|
|
2916
|
+
let t = e.payload;
|
|
2917
|
+
t && t.bracket === "cancelled" && this.bracketBar?.hide();
|
|
2918
|
+
})), r.alerts !== !1 && (this.alertsPanel = new ce(this.root, {
|
|
2919
|
+
onAdd: (e, t, n, r, i) => {
|
|
2920
|
+
this.chart.addAlert(e, t, n, r, i);
|
|
2921
|
+
},
|
|
2922
|
+
onRemove: (e) => this.chart.removeAlert(e),
|
|
2923
|
+
onClear: () => this.chart.clearAlerts(),
|
|
2924
|
+
getChannelValue: (e) => this.getAlertChannelValue(e),
|
|
2925
|
+
formatPrice: (e) => this.formatAlertPrice(e)
|
|
2926
|
+
}), this.chart.on("alertAdd", () => this.refreshAlerts()), this.chart.on("alertRemove", () => this.refreshAlerts()), this.chart.on("alertUpdate", () => this.refreshAlerts()), r.alertNotifications && (this.alertNotifier = new Ee(r.alertNotifications)), this.chart.on("alertTriggered", (e) => {
|
|
2927
|
+
let t = e.payload, n = `Price ${this.formatAlertPrice(t.price)}${t.message ? ` — ${t.message}` : ""}`;
|
|
2928
|
+
this.toast(`🔔 Alert: ${n}`, "info"), this.alertNotifier?.notify(n), this.refreshAlerts();
|
|
2929
|
+
})), r.objectTree !== !1) {
|
|
2930
|
+
this.indicatorSettings = new me(this.root, {
|
|
2931
|
+
onApply: (e, t) => this.chart.updateIndicator(e, t),
|
|
2932
|
+
onClose: () => {}
|
|
2933
|
+
}), this.objectTree = new le(this.root, {
|
|
2934
|
+
onRemoveIndicator: (e) => this.handleRemoveIndicator(e),
|
|
2935
|
+
onConfigureIndicator: (e) => this.openIndicatorSettings(e),
|
|
2936
|
+
onToggleIndicatorVisible: (e, t) => {
|
|
2937
|
+
this.chart.setIndicatorVisible(e, t), this.refreshObjects();
|
|
2938
|
+
},
|
|
2939
|
+
onRemoveDrawing: (e) => {
|
|
2940
|
+
this.chart.removeDrawing(e), this.refreshObjects();
|
|
2941
|
+
},
|
|
2942
|
+
onToggleDrawingVisible: (e, t) => {
|
|
2943
|
+
this.chart.setDrawingVisible(e, t), this.refreshObjects();
|
|
2944
|
+
},
|
|
2945
|
+
onToggleDrawingLocked: (e, t) => {
|
|
2946
|
+
this.chart.setDrawingLocked(e, t), this.refreshObjects();
|
|
2947
|
+
},
|
|
2948
|
+
onAddCompare: () => this.handleAddCompare(),
|
|
2949
|
+
onRemoveCompare: (e) => this.handleRemoveCompare(e)
|
|
2950
|
+
});
|
|
2951
|
+
let e = () => {
|
|
2952
|
+
this.objectTree?.isOpen() && this.refreshObjects();
|
|
2953
|
+
};
|
|
2954
|
+
this.chart.on("drawingCreate", e), this.chart.on("drawingRemove", e), this.chart.on("indicatorAdd", e), this.chart.on("indicatorRemove", e);
|
|
2955
|
+
}
|
|
2956
|
+
if (this.commandPalette = new T({
|
|
2957
|
+
onIndicator: (e) => this.handleAddIndicator(e),
|
|
2958
|
+
onChartType: (e) => this.handleChartType(e),
|
|
2959
|
+
onDrawingTool: (e) => this.handleDrawingTool(e),
|
|
2960
|
+
onTimeframe: (e) => this.handleTimeframe(e),
|
|
2961
|
+
onAction: (e) => this.handleAction(e),
|
|
2962
|
+
onClose: () => {}
|
|
2963
|
+
}), this.boundGlobalKeydown = (e) => {
|
|
2964
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "k") e.preventDefault(), this.toggleCommandPalette();
|
|
2965
|
+
else if ((e.ctrlKey || e.metaKey) && (e.key === "p" || e.key === "P")) e.preventDefault(), this.symbolSearch?.open(this.symbols, this.state.symbol);
|
|
2966
|
+
else if (e.key === "?" && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
2967
|
+
let t = document.activeElement;
|
|
2968
|
+
if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
|
|
2969
|
+
e.preventDefault(), this.hotkeySheet?.open();
|
|
2970
|
+
}
|
|
2971
|
+
}, document.addEventListener("keydown", this.boundGlobalKeydown), r.adapter && (this.adapter = r.adapter, this.connectStream()), this.watchlist && (this.watchlistInterval = setInterval(() => this.tickWatchlist(), 1e3)), this.updateUI(), r.onReady?.(this.chart), r.shareUrl && typeof location < "u") {
|
|
2972
|
+
let e = Le(location.hash);
|
|
2973
|
+
e && this.importState(e);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
setSymbols(e) {
|
|
2977
|
+
this.symbols = e, this.watchlist?.setSymbols(e);
|
|
2978
|
+
}
|
|
2979
|
+
async setSymbol(e) {
|
|
2980
|
+
this.flushActiveLayout(), this.state = {
|
|
1101
2981
|
...this.state,
|
|
1102
2982
|
symbol: e
|
|
1103
|
-
}, this.options.onSymbolChange?.(e), this.updateUI(), this.adapter && this.connectStream();
|
|
2983
|
+
}, this.sessionRefPrice = null, this.options.onSymbolChange?.(e), this.updateUI(), this.watchlist?.setActive(e), this.adapter && await this.connectStream();
|
|
2984
|
+
}
|
|
2985
|
+
setWatchlistEntry(e, t) {
|
|
2986
|
+
this.watchlist?.setEntry(e, t);
|
|
2987
|
+
}
|
|
2988
|
+
tickWatchlist() {
|
|
2989
|
+
if (!this.watchlist) return;
|
|
2990
|
+
let e = this.chart.getData();
|
|
2991
|
+
if (e.length === 0) return;
|
|
2992
|
+
let t = e[e.length - 1];
|
|
2993
|
+
this.sessionRefPrice === null && (this.sessionRefPrice = e[0].open);
|
|
2994
|
+
let n = this.watchlistSparkBuffer.get(this.state.symbol) ?? [];
|
|
2995
|
+
n.push(t.close), n.length > 40 && n.shift(), this.watchlistSparkBuffer.set(this.state.symbol, n), this.watchlist.setEntry(this.state.symbol, {
|
|
2996
|
+
lastPrice: t.close,
|
|
2997
|
+
refPrice: this.sessionRefPrice,
|
|
2998
|
+
sparkline: n.slice()
|
|
2999
|
+
});
|
|
1104
3000
|
}
|
|
1105
|
-
|
|
3001
|
+
async setTimeframe(e) {
|
|
3002
|
+
if (this.state = {
|
|
3003
|
+
...this.state,
|
|
3004
|
+
timeframe: e
|
|
3005
|
+
}, this.options.onTimeframeChange?.(e), this.updateUI(), this.adapter) {
|
|
3006
|
+
await this.connectStream();
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
this.options.resampleTimeframes !== !1 && this.applyTimeframeData();
|
|
3010
|
+
}
|
|
3011
|
+
setTheme(e) {
|
|
3012
|
+
let t = this.resolveIsDark(e), n = this.resolveTheme(e);
|
|
1106
3013
|
this.state = {
|
|
3014
|
+
...this.state,
|
|
3015
|
+
isDark: t
|
|
3016
|
+
}, this.root.dataset.tcwTheme = t ? "dark" : "light", this.chart.setTheme(n), this.updateUI();
|
|
3017
|
+
}
|
|
3018
|
+
getChart() {
|
|
3019
|
+
return this.chart;
|
|
3020
|
+
}
|
|
3021
|
+
destroy() {
|
|
3022
|
+
this.destroyed || (this.destroyed = !0, this.boundGlobalKeydown && document.removeEventListener("keydown", this.boundGlobalKeydown), this.flushActiveLayout(), this.commandPalette?.destroy(), this.symbolSearch?.destroy(), this.hotkeySheet?.destroy(), this.replayPollInterval && clearInterval(this.replayPollInterval), this.replayBar?.destroy(), this.dragDrop?.detach(), this.alertsPanel?.destroy(), this.objectTree?.destroy(), this.indicatorSettings?.destroy(), this.drawingStyle?.destroy(), this.bracketBar?.destroy(), this.alertNotifier?.destroy(), this.depthLadder?.destroy(), this.dataWindow?.destroy(), this.watchlistInterval && clearInterval(this.watchlistInterval), this.watchlist?.destroy(), this.toolbar?.destroy(), this.sidebar?.destroy(), this.settings?.destroy(), this.statusBar?.destroy(), this.chart.destroy(), this.root.remove(), _());
|
|
3023
|
+
}
|
|
3024
|
+
handleSymbolClick() {
|
|
3025
|
+
this.symbolSearch?.open(this.symbols, this.state.symbol);
|
|
3026
|
+
}
|
|
3027
|
+
handleTimeframe(e) {
|
|
3028
|
+
if (this.state = {
|
|
1107
3029
|
...this.state,
|
|
1108
3030
|
timeframe: e
|
|
1109
|
-
}, this.options.onTimeframeChange?.(e), this.updateUI(), this.adapter
|
|
3031
|
+
}, this.options.onTimeframeChange?.(e), this.updateUI(), this.adapter) {
|
|
3032
|
+
this.connectStream();
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
if (this.options.resampleTimeframes !== !1) {
|
|
3036
|
+
if (!this.baseSeries) {
|
|
3037
|
+
let e = this.chart.getData();
|
|
3038
|
+
e.length > 0 && (this.baseSeries = e, this.baseTimeframeMs = s(e));
|
|
3039
|
+
}
|
|
3040
|
+
this.applyTimeframeData();
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
setData(e) {
|
|
3044
|
+
this.baseSeries = e, this.baseTimeframeMs = s(e), this.applyTimeframeData();
|
|
3045
|
+
}
|
|
3046
|
+
applyTimeframeData() {
|
|
3047
|
+
if (!this.baseSeries) return;
|
|
3048
|
+
let e = a(this.state.timeframe);
|
|
3049
|
+
if (this.baseTimeframeMs > 0 && e > this.baseTimeframeMs) {
|
|
3050
|
+
let e = this.options.weekStartsOn ?? 1;
|
|
3051
|
+
this.chart.setData(o(this.baseSeries, this.state.timeframe, { weekStartsOn: e }));
|
|
3052
|
+
} else this.chart.setData(this.baseSeries);
|
|
3053
|
+
}
|
|
3054
|
+
toggleAlerts() {
|
|
3055
|
+
this.alertsPanel && (this.alertsPanel.toggle(), this.alertsPanel.isOpen() && this.refreshAlerts());
|
|
3056
|
+
}
|
|
3057
|
+
handleToggleFavorite(e) {
|
|
3058
|
+
let t = this.favoritesStore.toggle(e);
|
|
3059
|
+
this.sidebar?.setFavorites(this.favoritesStore.list()), this.toast(t ? "Pinned to favorites" : "Unpinned");
|
|
3060
|
+
}
|
|
3061
|
+
exportState() {
|
|
3062
|
+
let e = this.chart.getActiveIndicators().map((e) => ({
|
|
3063
|
+
id: e.id,
|
|
3064
|
+
params: e.params
|
|
3065
|
+
}));
|
|
3066
|
+
return Fe({
|
|
3067
|
+
v: 1,
|
|
3068
|
+
symbol: this.state.symbol,
|
|
3069
|
+
timeframe: this.state.timeframe,
|
|
3070
|
+
chartType: this.state.chartType,
|
|
3071
|
+
scaleMode: this.chart.getScaleMode(),
|
|
3072
|
+
indicators: e,
|
|
3073
|
+
drawings: this.chart.getDrawings()
|
|
3074
|
+
});
|
|
3075
|
+
}
|
|
3076
|
+
async importState(e) {
|
|
3077
|
+
let t = Ie(e);
|
|
3078
|
+
if (!t) return !1;
|
|
3079
|
+
t.symbol && t.symbol !== this.state.symbol && await this.setSymbol(t.symbol), t.timeframe && t.timeframe !== this.state.timeframe && await this.setTimeframe(t.timeframe), t.chartType && this.handleChartType(t.chartType), this.chart.setScaleMode(t.scaleMode);
|
|
3080
|
+
for (let [, e] of this.state.activeIndicators) this.chart.removeIndicator(e);
|
|
3081
|
+
let n = /* @__PURE__ */ new Map();
|
|
3082
|
+
for (let e of t.indicators) {
|
|
3083
|
+
let t = this.chart.addIndicator(e.id, e.params);
|
|
3084
|
+
t && n.set(e.id, t);
|
|
3085
|
+
}
|
|
3086
|
+
return this.state = {
|
|
3087
|
+
...this.state,
|
|
3088
|
+
activeIndicators: n
|
|
3089
|
+
}, this.chart.setDrawings(t.drawings), this.updateUI(), !0;
|
|
3090
|
+
}
|
|
3091
|
+
async copyChartImage() {
|
|
3092
|
+
let e = await this.chart.copyScreenshot();
|
|
3093
|
+
this.toast(e ? "Chart image copied" : "Copy failed — image clipboard unavailable", e ? "info" : "error");
|
|
3094
|
+
}
|
|
3095
|
+
async copyShareLink() {
|
|
3096
|
+
let e = Re(typeof location < "u" ? location.href : "", this.exportState());
|
|
3097
|
+
try {
|
|
3098
|
+
await navigator.clipboard.writeText(e), this.toast("Share link copied");
|
|
3099
|
+
} catch {
|
|
3100
|
+
this.toast("Copy failed — clipboard unavailable", "error");
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
setDepth(e) {
|
|
3104
|
+
this.chart.setDepthData(e), this.depthLadder?.setData(e), e && this.chart.pushDepthSnapshot(e);
|
|
3105
|
+
}
|
|
3106
|
+
startBracket(e) {
|
|
3107
|
+
this.chart.startBracket(e) && this.bracketBar?.show(e);
|
|
3108
|
+
}
|
|
3109
|
+
buildDataWindowModel() {
|
|
3110
|
+
let e = this.chart.getData(), t = this.lastHoverIndex;
|
|
3111
|
+
if (t === null || t < 0 || t >= e.length) return {
|
|
3112
|
+
ohlc: null,
|
|
3113
|
+
change: 0,
|
|
3114
|
+
changePct: 0,
|
|
3115
|
+
indicators: []
|
|
3116
|
+
};
|
|
3117
|
+
let n = e[t], r = t > 0 ? e[t - 1].close : n.open, i = n.close - r, a = r === 0 ? 0 : i / r * 100, o = this.chart.getActiveIndicators().map((e) => {
|
|
3118
|
+
let n = this.chart.getIndicatorOutput(e.instanceId)?.series?.[t] ?? null, r = n ? Object.entries(n).filter(([, e]) => typeof e == "number" && Number.isFinite(e)).map(([e, t]) => ({
|
|
3119
|
+
key: e,
|
|
3120
|
+
value: t
|
|
3121
|
+
})) : [];
|
|
3122
|
+
return {
|
|
3123
|
+
name: e.descriptor.name,
|
|
3124
|
+
values: r
|
|
3125
|
+
};
|
|
3126
|
+
}).filter((e) => e.values.length > 0);
|
|
3127
|
+
return {
|
|
3128
|
+
ohlc: {
|
|
3129
|
+
open: n.open,
|
|
3130
|
+
high: n.high,
|
|
3131
|
+
low: n.low,
|
|
3132
|
+
close: n.close,
|
|
3133
|
+
volume: n.volume
|
|
3134
|
+
},
|
|
3135
|
+
change: i,
|
|
3136
|
+
changePct: a,
|
|
3137
|
+
indicators: o
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
toggleDataWindow() {
|
|
3141
|
+
this.dataWindow && (this.dataWindow.toggle(), this.dataWindow.isOpen() && this.dataWindow.render(this.buildDataWindowModel()));
|
|
3142
|
+
}
|
|
3143
|
+
toggleObjects() {
|
|
3144
|
+
this.objectTree && (this.objectTree.toggle(), this.objectTree.isOpen() && this.refreshObjects());
|
|
3145
|
+
}
|
|
3146
|
+
openIndicatorSettings(e) {
|
|
3147
|
+
if (!this.indicatorSettings) return;
|
|
3148
|
+
let t = this.chart.getActiveIndicators().find((t) => t.instanceId === e);
|
|
3149
|
+
t && this.indicatorSettings.open({
|
|
3150
|
+
instanceId: e,
|
|
3151
|
+
name: t.descriptor.name,
|
|
3152
|
+
defaults: t.descriptor.defaultConfig,
|
|
3153
|
+
params: t.params
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
refreshObjects() {
|
|
3157
|
+
if (!this.objectTree) return;
|
|
3158
|
+
let e = this.chart.getActiveIndicators().map((e) => ({
|
|
3159
|
+
instanceId: e.instanceId,
|
|
3160
|
+
name: e.descriptor.name,
|
|
3161
|
+
visible: e.visible
|
|
3162
|
+
})), t = this.chart.getDrawings().map((e) => ({
|
|
3163
|
+
id: e.id,
|
|
3164
|
+
label: de(e.type),
|
|
3165
|
+
visible: e.visible,
|
|
3166
|
+
locked: e.locked
|
|
3167
|
+
})), n = this.compares.map((e) => ({
|
|
3168
|
+
id: e.id,
|
|
3169
|
+
label: e.symbol,
|
|
3170
|
+
color: e.color
|
|
3171
|
+
}));
|
|
3172
|
+
this.objectTree.setObjects(e, t, n);
|
|
3173
|
+
}
|
|
3174
|
+
async handleAddCompare() {
|
|
3175
|
+
if (!this.adapter) {
|
|
3176
|
+
this.toast("Comparison needs a live data adapter", "error");
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3179
|
+
let e = new Set([this.state.symbol, ...this.compares.map((e) => e.symbol)]), t = this.symbols.filter((t) => !e.has(t));
|
|
3180
|
+
this.symbolSearch?.open(t.length ? t : this.symbols, this.state.symbol, (e) => {
|
|
3181
|
+
this.addCompareSymbol(e);
|
|
3182
|
+
});
|
|
3183
|
+
}
|
|
3184
|
+
async addCompareSymbol(e) {
|
|
3185
|
+
if (!this.adapter || this.compares.some((t) => t.symbol === e)) return;
|
|
3186
|
+
let t = $[this.compares.length % $.length], n = `cmp_${e}`;
|
|
3187
|
+
try {
|
|
3188
|
+
let r = await this.adapter.fetchHistory(e, this.state.timeframe, this.options.historyLimit ?? 500);
|
|
3189
|
+
this.compares.length === 0 && this.chart.setCompareMode("percent"), this.chart.addCompareSymbol(n, e, r, t), this.compares = [...this.compares, {
|
|
3190
|
+
id: n,
|
|
3191
|
+
symbol: e,
|
|
3192
|
+
color: t
|
|
3193
|
+
}], this.refreshObjects(), this.toast(`Comparing ${e}`);
|
|
3194
|
+
} catch (t) {
|
|
3195
|
+
this.toast(`${e}: ${t instanceof Error ? t.message : "failed to load"}`, "error");
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
handleRemoveCompare(e) {
|
|
3199
|
+
this.chart.removeCompareSymbol(e), this.compares = this.compares.filter((t) => t.id !== e), this.refreshObjects();
|
|
3200
|
+
}
|
|
3201
|
+
async refetchCompares() {
|
|
3202
|
+
if (!this.adapter || this.compares.length === 0) return;
|
|
3203
|
+
let e = this.compares.filter((e) => e.symbol === this.state.symbol);
|
|
3204
|
+
for (let t of e) this.handleRemoveCompare(t.id);
|
|
3205
|
+
for (let e of this.compares) try {
|
|
3206
|
+
let t = await this.adapter.fetchHistory(e.symbol, this.state.timeframe, this.options.historyLimit ?? 500);
|
|
3207
|
+
this.chart.updateCompareData(e.id, t);
|
|
3208
|
+
} catch {}
|
|
3209
|
+
}
|
|
3210
|
+
refreshAlerts() {
|
|
3211
|
+
this.alertsPanel && (this.alertsPanel.setSources(this.buildAlertSources()), this.alertsPanel.setAlerts(this.chart.getAlerts().map((e) => ({
|
|
3212
|
+
id: e.id,
|
|
3213
|
+
price: e.price,
|
|
3214
|
+
condition: e.condition,
|
|
3215
|
+
message: e.message,
|
|
3216
|
+
triggered: e.triggered,
|
|
3217
|
+
channel: e.channel,
|
|
3218
|
+
label: e.label
|
|
3219
|
+
}))));
|
|
3220
|
+
}
|
|
3221
|
+
buildAlertSources() {
|
|
3222
|
+
let e = [{
|
|
3223
|
+
channel: "price",
|
|
3224
|
+
label: "Price"
|
|
3225
|
+
}];
|
|
3226
|
+
for (let t of this.chart.getActiveIndicators()) {
|
|
3227
|
+
let n = this.chart.getIndicatorOutput(t.instanceId)?.series, r = n && n.length > 0 ? n[n.length - 1] : null, i = r ? Object.keys(r).filter((e) => typeof r[e] == "number") : [];
|
|
3228
|
+
for (let n of i) {
|
|
3229
|
+
let r = i.length > 1 ? `${t.descriptor.name} ${n}` : t.descriptor.name;
|
|
3230
|
+
e.push({
|
|
3231
|
+
channel: `${t.instanceId}:${n}`,
|
|
3232
|
+
label: r
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
return e;
|
|
3237
|
+
}
|
|
3238
|
+
getAlertChannelValue(e) {
|
|
3239
|
+
let t = this.chart.getData();
|
|
3240
|
+
if (e === "price") return t.length > 0 ? t[t.length - 1].close : null;
|
|
3241
|
+
let n = e.indexOf(":");
|
|
3242
|
+
if (n < 0 || t.length === 0) return null;
|
|
3243
|
+
let r = e.slice(0, n), i = e.slice(n + 1), a = this.chart.getIndicatorOutput(r)?.series?.[t.length - 1]?.[i];
|
|
3244
|
+
return typeof a == "number" && Number.isFinite(a) ? a : null;
|
|
3245
|
+
}
|
|
3246
|
+
formatAlertPrice(e) {
|
|
3247
|
+
let t = Math.abs(e), n = t >= 1e3 ? 2 : t >= 1 ? 4 : 6, r = this.settingsState.numberLocale || void 0;
|
|
3248
|
+
return e.toLocaleString(r, {
|
|
3249
|
+
minimumFractionDigits: 2,
|
|
3250
|
+
maximumFractionDigits: n
|
|
3251
|
+
});
|
|
1110
3252
|
}
|
|
1111
3253
|
handleChartType(e) {
|
|
1112
3254
|
this.state = {
|
|
@@ -1156,25 +3298,25 @@ var _ = class {
|
|
|
1156
3298
|
}
|
|
1157
3299
|
buildCommandItems() {
|
|
1158
3300
|
let e = [];
|
|
1159
|
-
for (let t of
|
|
3301
|
+
for (let t of d) e.push({
|
|
1160
3302
|
id: t.id,
|
|
1161
3303
|
label: t.name,
|
|
1162
3304
|
category: "indicator",
|
|
1163
3305
|
active: this.state.activeIndicators.has(t.id)
|
|
1164
3306
|
});
|
|
1165
|
-
for (let t of
|
|
3307
|
+
for (let t of l) e.push({
|
|
1166
3308
|
id: t.value,
|
|
1167
3309
|
label: t.label,
|
|
1168
3310
|
category: "chartType",
|
|
1169
3311
|
active: this.state.chartType === t.value
|
|
1170
3312
|
});
|
|
1171
|
-
for (let t of
|
|
3313
|
+
for (let t of u) for (let n of t.tools) e.push({
|
|
1172
3314
|
id: n.value,
|
|
1173
3315
|
label: n.label,
|
|
1174
3316
|
category: "drawing",
|
|
1175
3317
|
active: this.state.activeTool === n.value
|
|
1176
3318
|
});
|
|
1177
|
-
for (let t of
|
|
3319
|
+
for (let t of c) e.push({
|
|
1178
3320
|
id: t.value,
|
|
1179
3321
|
label: t.label,
|
|
1180
3322
|
category: "timeframe",
|
|
@@ -1185,6 +3327,10 @@ var _ = class {
|
|
|
1185
3327
|
label: "Screenshot",
|
|
1186
3328
|
category: "action",
|
|
1187
3329
|
shortcut: ""
|
|
3330
|
+
}, {
|
|
3331
|
+
id: "copyImage",
|
|
3332
|
+
label: "Copy Chart Image",
|
|
3333
|
+
category: "action"
|
|
1188
3334
|
}, {
|
|
1189
3335
|
id: "toggleTheme",
|
|
1190
3336
|
label: "Toggle Theme",
|
|
@@ -1193,6 +3339,18 @@ var _ = class {
|
|
|
1193
3339
|
id: "settings",
|
|
1194
3340
|
label: "Settings",
|
|
1195
3341
|
category: "action"
|
|
3342
|
+
}, {
|
|
3343
|
+
id: "shareView",
|
|
3344
|
+
label: "Share View (copy link)",
|
|
3345
|
+
category: "action"
|
|
3346
|
+
}, {
|
|
3347
|
+
id: "autoFib",
|
|
3348
|
+
label: "Auto Fibonacci (visible swing)",
|
|
3349
|
+
category: "action"
|
|
3350
|
+
}, {
|
|
3351
|
+
id: "dataWindow",
|
|
3352
|
+
label: "Toggle Data Window",
|
|
3353
|
+
category: "action"
|
|
1196
3354
|
}, {
|
|
1197
3355
|
id: "clearDrawings",
|
|
1198
3356
|
label: "Clear All Drawings",
|
|
@@ -1210,6 +3368,20 @@ var _ = class {
|
|
|
1210
3368
|
case "settings":
|
|
1211
3369
|
this.openSettings();
|
|
1212
3370
|
break;
|
|
3371
|
+
case "copyImage":
|
|
3372
|
+
this.copyChartImage();
|
|
3373
|
+
break;
|
|
3374
|
+
case "shareView":
|
|
3375
|
+
this.copyShareLink();
|
|
3376
|
+
break;
|
|
3377
|
+
case "autoFib": {
|
|
3378
|
+
let e = this.chart.autoFib();
|
|
3379
|
+
this.toast(e ? "Auto Fibonacci added" : "No clear swing in view");
|
|
3380
|
+
break;
|
|
3381
|
+
}
|
|
3382
|
+
case "dataWindow":
|
|
3383
|
+
this.toggleDataWindow();
|
|
3384
|
+
break;
|
|
1213
3385
|
case "clearDrawings":
|
|
1214
3386
|
this.chart.clearDrawings(), this.state = {
|
|
1215
3387
|
...this.state,
|
|
@@ -1242,7 +3414,7 @@ var _ = class {
|
|
|
1242
3414
|
this.state = {
|
|
1243
3415
|
...this.state,
|
|
1244
3416
|
isDark: e
|
|
1245
|
-
}, this.root.dataset.tcwTheme = e ? "dark" : "light", this.chart.setTheme(e ?
|
|
3417
|
+
}, this.root.dataset.tcwTheme = e ? "dark" : "light", this.chart.setTheme(e ? n : r), this.chart.setWatermark(this.state.symbol.replace("USDT", " / USDT"), {
|
|
1246
3418
|
fontSize: 48,
|
|
1247
3419
|
color: e ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.03)"
|
|
1248
3420
|
}), this.updateUI();
|
|
@@ -1250,16 +3422,114 @@ var _ = class {
|
|
|
1250
3422
|
openSettings() {
|
|
1251
3423
|
this.settings?.open(this.settingsState);
|
|
1252
3424
|
}
|
|
3425
|
+
toast(e, t = "info") {
|
|
3426
|
+
let n = document.createElement("div");
|
|
3427
|
+
n.className = `tcw-toast ${t === "error" ? "tcw-toast-error" : ""}`, n.textContent = e, this.root.appendChild(n), requestAnimationFrame(() => n.classList.add("tcw-toast-in")), setTimeout(() => {
|
|
3428
|
+
n.classList.remove("tcw-toast-in"), n.addEventListener("transitionend", () => n.remove(), { once: !0 }), setTimeout(() => n.remove(), 400);
|
|
3429
|
+
}, 3500);
|
|
3430
|
+
}
|
|
3431
|
+
clearSavedLayout(e) {
|
|
3432
|
+
if (!this.layoutKeyPrefix) return;
|
|
3433
|
+
let t = e ?? this.state.symbol;
|
|
3434
|
+
try {
|
|
3435
|
+
localStorage.removeItem(this.layoutKeyPrefix + t);
|
|
3436
|
+
} catch {}
|
|
3437
|
+
}
|
|
3438
|
+
applySymbolLayout(e) {
|
|
3439
|
+
if (!this.layoutKeyPrefix) return;
|
|
3440
|
+
let t = this.layoutKeyPrefix + e;
|
|
3441
|
+
this.activeLayoutKey = t;
|
|
3442
|
+
try {
|
|
3443
|
+
this.chart.loadStateFromStorage(t) && this.rebuildActiveIndicatorsMapFromChart();
|
|
3444
|
+
} catch {}
|
|
3445
|
+
this.chart.setAutoSave(t, this.layoutDebounceMs);
|
|
3446
|
+
}
|
|
3447
|
+
flushActiveLayout() {
|
|
3448
|
+
if (this.activeLayoutKey) {
|
|
3449
|
+
try {
|
|
3450
|
+
this.chart.saveState(this.activeLayoutKey);
|
|
3451
|
+
} catch {}
|
|
3452
|
+
this.chart.disableAutoSave(), this.activeLayoutKey = null;
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
rebuildActiveIndicatorsMapFromChart() {
|
|
3456
|
+
let e = /* @__PURE__ */ new Map();
|
|
3457
|
+
for (let t of this.chart.getActiveIndicators()) e.set(t.id, t.instanceId);
|
|
3458
|
+
this.state = {
|
|
3459
|
+
...this.state,
|
|
3460
|
+
activeIndicators: e
|
|
3461
|
+
}, this.updateUI();
|
|
3462
|
+
}
|
|
3463
|
+
toggleReplay() {
|
|
3464
|
+
this.replayBar?.isMounted() ? this.exitReplay() : this.enterReplay();
|
|
3465
|
+
}
|
|
3466
|
+
enterReplay() {
|
|
3467
|
+
let e = this.chart.getData();
|
|
3468
|
+
e.length < 2 || (this.replayOriginalData = e.slice(), this.replayBar = new F({
|
|
3469
|
+
onPlay: () => {
|
|
3470
|
+
this.chart.getReplayState() === "paused" ? this.chart.replayResume() : this.chart.replay({
|
|
3471
|
+
speed: this.replaySpeed,
|
|
3472
|
+
interval: 200
|
|
3473
|
+
}), this.replayBar?.setState("playing");
|
|
3474
|
+
},
|
|
3475
|
+
onPause: () => {
|
|
3476
|
+
this.chart.replayPause(), this.replayBar?.setState("paused");
|
|
3477
|
+
},
|
|
3478
|
+
onStop: () => {
|
|
3479
|
+
this.chart.replayStop(), this.replayBar?.setState("paused");
|
|
3480
|
+
},
|
|
3481
|
+
onStepBack: () => {
|
|
3482
|
+
let e = this.chart.getReplayProgress();
|
|
3483
|
+
this.chart.getReplayState() === "playing" && this.chart.replayPause(), this.chart.replaySeek(Math.max(0, e.current - 1)), this.replayBar?.setState("paused");
|
|
3484
|
+
},
|
|
3485
|
+
onStepForward: () => {
|
|
3486
|
+
let e = this.chart.getReplayProgress();
|
|
3487
|
+
this.chart.getReplayState() === "playing" && this.chart.replayPause(), this.chart.replaySeek(Math.min(e.total - 1, e.current + 1)), this.replayBar?.setState("paused");
|
|
3488
|
+
},
|
|
3489
|
+
onSeek: (e) => {
|
|
3490
|
+
this.chart.getReplayState() === "playing" && this.chart.replayPause(), this.chart.replaySeek(e), this.replayBar?.setState("paused");
|
|
3491
|
+
},
|
|
3492
|
+
onSpeedChange: (e) => {
|
|
3493
|
+
this.replaySpeed = e, this.chart.setReplaySpeed(e);
|
|
3494
|
+
},
|
|
3495
|
+
onClose: () => this.exitReplay()
|
|
3496
|
+
}), this.replayBar.mount(this.chartContainer, {
|
|
3497
|
+
total: e.length,
|
|
3498
|
+
speed: this.replaySpeed
|
|
3499
|
+
}), this.chart.replay({
|
|
3500
|
+
speed: this.replaySpeed,
|
|
3501
|
+
interval: 200,
|
|
3502
|
+
startIndex: 1
|
|
3503
|
+
}), this.chart.replayPause(), this.replayBar.setState("paused"), this.replayPollInterval = setInterval(() => {
|
|
3504
|
+
if (!this.replayBar?.isMounted()) return;
|
|
3505
|
+
let e = this.chart.getReplayProgress();
|
|
3506
|
+
this.replayBar.setProgress(e.current, e.total);
|
|
3507
|
+
let t = this.chart.getReplayState();
|
|
3508
|
+
t === "stopped" ? this.replayBar.setState("paused") : this.replayBar.setState(t);
|
|
3509
|
+
}, 150));
|
|
3510
|
+
}
|
|
3511
|
+
exitReplay() {
|
|
3512
|
+
this.replayPollInterval &&= (clearInterval(this.replayPollInterval), null), this.chart.replayStop(), this.replayBar?.unmount(), this.replayBar = null, this.replayOriginalData &&= (this.chart.setData(this.replayOriginalData), null);
|
|
3513
|
+
}
|
|
1253
3514
|
applySettings(e) {
|
|
1254
3515
|
this.settingsState = {
|
|
1255
3516
|
...this.settingsState,
|
|
1256
3517
|
...e
|
|
1257
|
-
}, e.gridVisible !== void 0 && this.chart.setGridVisible(e.gridVisible), e.volumeVisible !== void 0 && this.chart.setVolumeVisible(e.volumeVisible), e.
|
|
3518
|
+
}, e.gridVisible !== void 0 && this.chart.setGridVisible(e.gridVisible), e.volumeVisible !== void 0 && this.chart.setVolumeVisible(e.volumeVisible), e.volumeProfileVisible !== void 0 && this.chart.setVolumeProfileVisible(e.volumeProfileVisible), e.marketProfileVisible !== void 0 && this.chart.setMarketProfileVisible(e.marketProfileVisible), e.marketProfileSplit !== void 0 && this.chart.setMarketProfileConfig({ splitBySession: e.marketProfileSplit }), e.marketProfileLetters !== void 0 && this.chart.setMarketProfileConfig({ letters: e.marketProfileLetters }), e.marketProfileBuckets !== void 0 && this.chart.setMarketProfileConfig({ buckets: e.marketProfileBuckets }), e.marketProfileOpacity !== void 0 && this.chart.setMarketProfileConfig({ opacity: e.marketProfileOpacity }), e.depthHeatmapVisible !== void 0 && this.chart.setDepthHeatmapVisible(e.depthHeatmapVisible), e.depthHeatmapOpacity !== void 0 && this.chart.setDepthHeatmapConfig({ opacity: e.depthHeatmapOpacity }), e.sessionShadingVisible !== void 0 && this.chart.setSessionShadingVisible(e.sessionShadingVisible), e.pivotMarkersVisible !== void 0 && this.chart.setPivotMarkersVisible(e.pivotMarkersVisible), e.pivotStrength !== void 0 && this.chart.setPivotMarkersConfig({
|
|
3519
|
+
left: e.pivotStrength,
|
|
3520
|
+
right: e.pivotStrength
|
|
3521
|
+
}), e.pivotStructureLabels !== void 0 && this.chart.setPivotMarkersConfig({ structureLabels: e.pivotStructureLabels }), e.periodLevelsVisible !== void 0 && this.chart.setPeriodLevelsVisible(e.periodLevelsVisible), e.periodLevelsPeriod !== void 0 && this.chart.setPeriodLevelsPeriod(e.periodLevelsPeriod), e.crosshairMode !== void 0 && this.chart.setCrosshairMode(e.crosshairMode), e.autoScale !== void 0 && this.chart.setAutoScale(e.autoScale), e.scaleMode === void 0 ? e.logScale !== void 0 && (this.chart.setLogScale(e.logScale), this.settingsState = {
|
|
3522
|
+
...this.settingsState,
|
|
3523
|
+
scaleMode: e.logScale ? "logarithmic" : "regular"
|
|
3524
|
+
}) : (this.chart.setScaleMode(e.scaleMode), this.settingsState = {
|
|
3525
|
+
...this.settingsState,
|
|
3526
|
+
logScale: e.scaleMode === "logarithmic"
|
|
3527
|
+
}), e.numberLocale !== void 0 && this.chart.setNumberLocale(e.numberLocale), e.timezone !== void 0 && this.chart.setTimezoneOffset(e.timezone === "local" ? null : Number(e.timezone));
|
|
1258
3528
|
let t = { ...this.chart.getTheme() }, n = !1;
|
|
1259
3529
|
e.candleUpColor !== void 0 && (t.candleUp = e.candleUpColor, n = !0), e.candleDownColor !== void 0 && (t.candleDown = e.candleDownColor, n = !0), e.candleUpWick !== void 0 && (t.candleUpWick = e.candleUpWick, n = !0), e.candleDownWick !== void 0 && (t.candleDownWick = e.candleDownWick, n = !0), e.backgroundColor !== void 0 && (t.background = e.backgroundColor, n = !0), e.gridColor !== void 0 && (t.grid = e.gridColor, n = !0), n && this.chart.setTheme(t);
|
|
1260
3530
|
}
|
|
1261
3531
|
resetSettings() {
|
|
1262
|
-
this.settingsState = { ...
|
|
3532
|
+
this.settingsState = { ...p }, this.applySettings(this.settingsState);
|
|
1263
3533
|
}
|
|
1264
3534
|
async connectStream() {
|
|
1265
3535
|
if (this.adapter) {
|
|
@@ -1277,11 +3547,11 @@ var _ = class {
|
|
|
1277
3547
|
}), this.chart.setWatermark(this.state.symbol.replace("USDT", " / USDT"), {
|
|
1278
3548
|
fontSize: 48,
|
|
1279
3549
|
color: this.state.isDark ? "rgba(255,255,255,0.03)" : "rgba(0,0,0,0.03)"
|
|
1280
|
-
}), this.state = {
|
|
3550
|
+
}), this.applySymbolLayout(this.state.symbol), this.state = {
|
|
1281
3551
|
...this.state,
|
|
1282
3552
|
connectionState: "connected",
|
|
1283
3553
|
connectionMessage: "Live"
|
|
1284
|
-
};
|
|
3554
|
+
}, this.refetchCompares();
|
|
1285
3555
|
} catch (e) {
|
|
1286
3556
|
this.state = {
|
|
1287
3557
|
...this.state,
|
|
@@ -1304,7 +3574,7 @@ var _ = class {
|
|
|
1304
3574
|
return e === void 0 || e === "dark" ? !0 : e === "light" ? !1 : typeof e == "object" && e.background ? this.isColorDark(e.background) : !0;
|
|
1305
3575
|
}
|
|
1306
3576
|
resolveTheme(e) {
|
|
1307
|
-
return e === void 0 || e === "dark" ?
|
|
3577
|
+
return e === void 0 || e === "dark" ? n : e === "light" ? r : typeof e == "object" ? e : n;
|
|
1308
3578
|
}
|
|
1309
3579
|
isColorDark(e) {
|
|
1310
3580
|
if (e.startsWith("#") && e.length >= 7) {
|
|
@@ -1315,6 +3585,6 @@ var _ = class {
|
|
|
1315
3585
|
}
|
|
1316
3586
|
};
|
|
1317
3587
|
//#endregion
|
|
1318
|
-
export {
|
|
3588
|
+
export { ze as ChartWidget };
|
|
1319
3589
|
|
|
1320
3590
|
//# sourceMappingURL=index.js.map
|