@luxalgo/vela 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataProvider-DGesjOJT.d.ts → DataProvider-DTOEXKTI.d.ts} +1 -1
- package/dist/{DataProvider-BBQ3Hr21.d.cts → DataProvider-gnzv-dgc.d.cts} +1 -1
- package/dist/{chunk-ULIX6AE2.js → chunk-ATPU5CI6.js} +1 -1
- package/dist/{chunk-S6F6SQ2L.js → chunk-B5TIKQJ5.js} +60 -7
- package/dist/{chunk-EP6NDJYK.js → chunk-LJLZR44Y.js} +4 -2
- package/dist/{chunk-AIW3EJUE.js → chunk-P556H6EA.js} +329 -10
- package/dist/{chunk-FNOMKHEZ.js → chunk-RRFGWZNJ.js} +1321 -363
- package/dist/{chunk-5SE4PL3H.js → chunk-Y7TK4ZWK.js} +1363 -48
- package/dist/{contributions-BlV3kb0O.d.cts → contributions-Ci_i3IN2.d.cts} +33 -2
- package/dist/{contributions-6N0rbrPC.d.ts → contributions-b6AVrqqG.d.ts} +33 -2
- package/dist/{history-DX3Zne3a.d.cts → history-Cf1lVsap.d.cts} +15 -3
- package/dist/{history-BTd52YMA.d.ts → history-Ci3M0xLV.d.ts} +15 -3
- package/dist/index.cjs +1374 -366
- package/dist/index.d.cts +52 -19
- package/dist/index.d.ts +52 -19
- package/dist/index.js +4 -4
- package/dist/{options-DbQholFV.d.cts → options-XTBpbx0s.d.cts} +47 -1
- package/dist/{options-DbQholFV.d.ts → options-XTBpbx0s.d.ts} +47 -1
- package/dist/{plugin-TN3n-9YN.d.ts → plugin-CnNd7cuF.d.ts} +172 -25
- package/dist/{plugin-O34MbXf-.d.cts → plugin-DfH4Y-Om.d.cts} +172 -25
- package/dist/plugin.cjs +53 -7
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/ui.cjs +332 -9
- package/dist/ui.d.cts +64 -2
- package/dist/ui.d.ts +64 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +1374 -366
- package/dist/vela.global.min.js +84 -34
- package/dist/widget.cjs +3348 -550
- package/dist/widget.d.cts +65 -7
- package/dist/widget.d.ts +65 -7
- package/dist/widget.js +169 -18
- package/dist/workspace.cjs +4251 -1400
- package/dist/workspace.d.cts +45 -9
- package/dist/workspace.d.ts +45 -9
- package/dist/workspace.js +226 -22
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { parseSymbol, priceStyleIds, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel } from './chunk-
|
|
2
|
-
import { chartType, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, getDrawingType } from './chunk-
|
|
3
|
-
import { Tooltip, Menu, Dialog } from './chunk-
|
|
4
|
-
import { injectStyles, iconEl,
|
|
1
|
+
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel } from './chunk-RRFGWZNJ.js';
|
|
2
|
+
import { chartType, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, getDrawingType } from './chunk-B5TIKQJ5.js';
|
|
3
|
+
import { Tooltip, Menu, Dialog, Drawer } from './chunk-P556H6EA.js';
|
|
4
|
+
import { iconMarkup, registerIcon, injectStyles, iconEl, SESSION_PRE, SESSION_POST, SESSION_OFF, icon, categoricalColor } from './chunk-LJLZR44Y.js';
|
|
5
5
|
|
|
6
6
|
// src/widget/timeframe.ts
|
|
7
7
|
var UNIT_MS = {
|
|
@@ -192,6 +192,31 @@ var CSS = `
|
|
|
192
192
|
.vela-lp-switch.on::after { transform: translateX(16px); background: var(--vela-selected-fg); }
|
|
193
193
|
`;
|
|
194
194
|
var GRID = 4;
|
|
195
|
+
function layoutGridCanvas(doc) {
|
|
196
|
+
injectStyles(STYLE_ID, CSS, doc);
|
|
197
|
+
const el = doc.createElement("div");
|
|
198
|
+
el.className = "vela-lp-grid";
|
|
199
|
+
const squares = [];
|
|
200
|
+
for (let r = 0; r < GRID; r += 1) {
|
|
201
|
+
for (let c = 0; c < GRID; c += 1) {
|
|
202
|
+
const sq = doc.createElement("button");
|
|
203
|
+
sq.className = "vela-lp-sq";
|
|
204
|
+
sq.dataset.r = String(r);
|
|
205
|
+
sq.dataset.c = String(c);
|
|
206
|
+
sq.setAttribute("aria-label", `${c + 1} \xD7 ${r + 1}`);
|
|
207
|
+
squares.push(sq);
|
|
208
|
+
el.appendChild(sq);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { el, squares };
|
|
212
|
+
}
|
|
213
|
+
function paintLayoutGrid(squares, shape) {
|
|
214
|
+
for (const sq of squares) {
|
|
215
|
+
const on = shape !== null && Number(sq.dataset.r) < shape.rows && Number(sq.dataset.c) < shape.cols;
|
|
216
|
+
if (on) sq.dataset.on = "1";
|
|
217
|
+
else delete sq.dataset.on;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
195
220
|
var LayoutPicker = class {
|
|
196
221
|
constructor(opts) {
|
|
197
222
|
this.squares = [];
|
|
@@ -235,19 +260,8 @@ var LayoutPicker = class {
|
|
|
235
260
|
placement: "bottom",
|
|
236
261
|
content: () => this.tipNode()
|
|
237
262
|
});
|
|
238
|
-
const grid = doc
|
|
239
|
-
|
|
240
|
-
for (let r = 0; r < GRID; r += 1) {
|
|
241
|
-
for (let c = 0; c < GRID; c += 1) {
|
|
242
|
-
const sq = doc.createElement("button");
|
|
243
|
-
sq.className = "vela-lp-sq";
|
|
244
|
-
sq.dataset.r = String(r);
|
|
245
|
-
sq.dataset.c = String(c);
|
|
246
|
-
sq.setAttribute("aria-label", `${c + 1} \xD7 ${r + 1}`);
|
|
247
|
-
this.squares.push(sq);
|
|
248
|
-
grid.appendChild(sq);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
263
|
+
const { el: grid, squares } = layoutGridCanvas(doc);
|
|
264
|
+
this.squares.push(...squares);
|
|
251
265
|
layoutCol.appendChild(grid);
|
|
252
266
|
this.presetsEl = doc.createElement("div");
|
|
253
267
|
this.presetsEl.className = "vela-lp-presets";
|
|
@@ -347,13 +361,7 @@ var LayoutPicker = class {
|
|
|
347
361
|
}
|
|
348
362
|
/** Project the interaction state onto the DOM (the hover/current rectangle). */
|
|
349
363
|
render() {
|
|
350
|
-
|
|
351
|
-
for (const sq of this.squares) {
|
|
352
|
-
const { r, c } = this.squarePos(sq);
|
|
353
|
-
const on = preview !== null && r < preview.rows && c < preview.cols;
|
|
354
|
-
if (on) sq.dataset.on = "1";
|
|
355
|
-
else delete sq.dataset.on;
|
|
356
|
-
}
|
|
364
|
+
paintLayoutGrid(this.squares, this.hover ?? this.opts.shape());
|
|
357
365
|
}
|
|
358
366
|
renderPresets() {
|
|
359
367
|
const doc = this.doc;
|
|
@@ -420,7 +428,7 @@ var CSS2 = `
|
|
|
420
428
|
font-size: var(--vela-font-size-md);
|
|
421
429
|
flex: none;
|
|
422
430
|
}
|
|
423
|
-
.vela-widget-symbol, .vela-widget-tf, .vela-widget-style, .vela-widget-indicators {
|
|
431
|
+
.vela-widget-symbol, .vela-widget-tf, .vela-widget-style, .vela-widget-indicators, .vela-widget-action-left {
|
|
424
432
|
all: unset;
|
|
425
433
|
display: inline-flex;
|
|
426
434
|
align-items: center;
|
|
@@ -442,10 +450,10 @@ var CSS2 = `
|
|
|
442
450
|
padding: 0 10px;
|
|
443
451
|
gap: 7px;
|
|
444
452
|
}
|
|
445
|
-
.vela-widget-tf, .vela-widget-style, .vela-widget-indicators {
|
|
453
|
+
.vela-widget-tf, .vela-widget-style, .vela-widget-indicators, .vela-widget-action-left {
|
|
446
454
|
color: var(--vela-fg-bright);
|
|
447
455
|
}
|
|
448
|
-
.vela-widget-symbol:hover, .vela-widget-tf:hover, .vela-widget-style:hover, .vela-widget-indicators:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
456
|
+
.vela-widget-symbol:hover, .vela-widget-tf:hover, .vela-widget-style:hover, .vela-widget-indicators:hover, .vela-widget-action-left:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
449
457
|
.vela-widget-topbar .vela-icon { color: inherit; font-size: 16px; width: 16px; height: 16px; }
|
|
450
458
|
/* Width is set in syncHairlines() to exactly one device pixel \u2014 a CSS 1px at
|
|
451
459
|
fractional DPR (1.25, 1.5\u2026) straddles two physical pixels and siblings end
|
|
@@ -468,6 +476,8 @@ var CSS2 = `
|
|
|
468
476
|
justify-content: center;
|
|
469
477
|
}
|
|
470
478
|
.vela-widget-actions { margin-left: auto; display: inline-flex; gap: var(--vela-space-1); }
|
|
479
|
+
/* Left-aligned contributed actions \u2014 the primary-chrome cluster after the dropdowns. */
|
|
480
|
+
.vela-widget-actions-left { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
471
481
|
/* The side-panel toggles, one per docked panel \u2014 a group so the dock can rebuild them
|
|
472
482
|
without disturbing the tools around it. */
|
|
473
483
|
.vela-widget-panels { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
@@ -554,12 +564,17 @@ var Topbar = class {
|
|
|
554
564
|
this.styleButton = doc.createElement("button");
|
|
555
565
|
this.styleButton.className = "vela-widget-style";
|
|
556
566
|
this.renderStyleButton(doc);
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
567
|
+
let indicatorsBtn = null;
|
|
568
|
+
if (opts.onIndicatorsClick) {
|
|
569
|
+
indicatorsBtn = doc.createElement("button");
|
|
570
|
+
indicatorsBtn.className = "vela-widget-indicators";
|
|
571
|
+
indicatorsBtn.append(iconEl("indicators", doc), doc.createTextNode("Indicators"));
|
|
572
|
+
indicatorsBtn.addEventListener("click", opts.onIndicatorsClick);
|
|
573
|
+
}
|
|
561
574
|
this.actionsHost = doc.createElement("span");
|
|
562
575
|
this.actionsHost.className = "vela-widget-actions";
|
|
576
|
+
this.leftActionsHost = doc.createElement("span");
|
|
577
|
+
this.leftActionsHost.className = "vela-widget-actions-left";
|
|
563
578
|
this.panelsHost = doc.createElement("span");
|
|
564
579
|
this.panelsHost.className = "vela-widget-panels";
|
|
565
580
|
const tool = (cls, icon2, tip, onClick) => this.toolButton(cls, icon2, tip, onClick, this.tooltips);
|
|
@@ -587,7 +602,10 @@ var Topbar = class {
|
|
|
587
602
|
};
|
|
588
603
|
const leading = [this.symbolEl, sep(), this.tfButton, sep(), this.styleButton, sep()];
|
|
589
604
|
if (this.layoutButton) leading.push(this.layoutButton, sep());
|
|
590
|
-
|
|
605
|
+
if (indicatorsBtn) leading.push(indicatorsBtn, sep());
|
|
606
|
+
this.leftActionsSep = sep();
|
|
607
|
+
this.leftActionsSep.hidden = true;
|
|
608
|
+
this.el.append(...leading, this.leftActionsHost, this.leftActionsSep, this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
|
|
591
609
|
host.appendChild(this.el);
|
|
592
610
|
this.onHairlineSync();
|
|
593
611
|
this.hairlineRo = new ResizeObserver(this.onHairlineSync);
|
|
@@ -666,18 +684,22 @@ var Topbar = class {
|
|
|
666
684
|
renderActions() {
|
|
667
685
|
const ctx = this.opts.getContext?.();
|
|
668
686
|
this.actionsHost.replaceChildren();
|
|
687
|
+
this.leftActionsHost.replaceChildren();
|
|
669
688
|
const doc = this.actionsHost.ownerDocument;
|
|
670
689
|
for (const action of widgetActions("topbar", ctx)) {
|
|
690
|
+
const left = action.align === "left";
|
|
671
691
|
const b = doc.createElement("button");
|
|
672
|
-
b.className = "vela-widget-action";
|
|
692
|
+
b.className = left ? "vela-widget-action-left" : "vela-widget-action";
|
|
673
693
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
674
694
|
b.appendChild(doc.createTextNode(action.label));
|
|
675
695
|
b.addEventListener("click", () => {
|
|
676
696
|
const c = this.opts.getContext?.();
|
|
677
697
|
if (c) action.run(c);
|
|
678
698
|
});
|
|
679
|
-
this.actionsHost.appendChild(b);
|
|
699
|
+
(left ? this.leftActionsHost : this.actionsHost).appendChild(b);
|
|
680
700
|
}
|
|
701
|
+
this.leftActionsSep.hidden = this.leftActionsHost.childElementCount === 0;
|
|
702
|
+
this.onHairlineSync();
|
|
681
703
|
}
|
|
682
704
|
setIndicatorCount(_n) {
|
|
683
705
|
}
|
|
@@ -751,11 +773,12 @@ var Topbar = class {
|
|
|
751
773
|
return this.opts.timeframes.map((tf) => ({ id: tf, label: timeframeLabel(tf), checked: tf === this.timeframe }));
|
|
752
774
|
}
|
|
753
775
|
styleItems() {
|
|
754
|
-
return priceStyleIds().map((id) => ({
|
|
776
|
+
return priceStyleIds().map((id, i) => ({
|
|
755
777
|
id,
|
|
756
778
|
label: priceStyleLabel(id),
|
|
757
779
|
icon: priceStyleIcon(id),
|
|
758
|
-
checked: id === this.priceStyle
|
|
780
|
+
checked: id === this.priceStyle,
|
|
781
|
+
separatorBefore: i === BUILTIN_PRICE_STYLES.length
|
|
759
782
|
}));
|
|
760
783
|
}
|
|
761
784
|
};
|
|
@@ -806,6 +829,7 @@ function tickerIconEl(doc, base, name, className) {
|
|
|
806
829
|
}
|
|
807
830
|
const img = doc.createElement("img");
|
|
808
831
|
img.alt = "";
|
|
832
|
+
img.crossOrigin = "anonymous";
|
|
809
833
|
img.src = cryptoIconUrl(key);
|
|
810
834
|
img.style.cssText = "width:100%;height:100%;border-radius:50%;display:block;object-fit:cover;";
|
|
811
835
|
img.addEventListener(
|
|
@@ -904,6 +928,59 @@ var CSS3 = `
|
|
|
904
928
|
* Statusline constructor) \u2014 the stylesheet is document-global, so a bare container
|
|
905
929
|
* class here would shift every chart on the page, including statusline-less ones. */
|
|
906
930
|
.vela-has-statusline [data-vela-pane='price'] { transform: translateY(26px); }
|
|
931
|
+
/* Mobile: two-line chip \u2014 logo / symbol / meta / market status on one aligned row, the
|
|
932
|
+
* bar change on the next. Full O/H/L/C stays hidden (too dense on a phone-width plot).
|
|
933
|
+
* GRID, not a wrapping flexbox: an absolutely positioned wrapping flex container sizes
|
|
934
|
+
* to the one-line sum of ALL segments (max-content), so its background used to stretch
|
|
935
|
+
* far past the market badge; a grid hugs the widest actual row. The meta column may
|
|
936
|
+
* shrink (it carries the ellipsis), the others wrap their content. */
|
|
937
|
+
[data-layout='mobile'] .vela-statusline {
|
|
938
|
+
display: grid;
|
|
939
|
+
grid-template-columns: auto auto minmax(0, auto) auto;
|
|
940
|
+
justify-content: start;
|
|
941
|
+
align-items: center;
|
|
942
|
+
row-gap: 1px;
|
|
943
|
+
max-width: calc(100% - var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px) - 24px);
|
|
944
|
+
font-size: var(--vela-font-size-sm);
|
|
945
|
+
}
|
|
946
|
+
[data-layout='mobile'] .vela-statusline .vela-sl-symbol {
|
|
947
|
+
font-size: var(--vela-font-size-md);
|
|
948
|
+
line-height: 1;
|
|
949
|
+
}
|
|
950
|
+
[data-layout='mobile'] .vela-statusline .vela-sl-meta {
|
|
951
|
+
min-width: 0;
|
|
952
|
+
overflow: hidden;
|
|
953
|
+
text-overflow: ellipsis;
|
|
954
|
+
white-space: nowrap;
|
|
955
|
+
line-height: 1;
|
|
956
|
+
}
|
|
957
|
+
[data-layout='mobile'] .vela-statusline .vela-sl-avatar,
|
|
958
|
+
[data-layout='mobile'] .vela-statusline .vela-sl-market { align-self: center; }
|
|
959
|
+
[data-layout='mobile'] .vela-statusline .vela-sl-ohlc { display: none !important; }
|
|
960
|
+
[data-layout='mobile'] .vela-statusline .vela-sl-change {
|
|
961
|
+
/* Second row, under the text column \u2014 the avatar keeps the first column. */
|
|
962
|
+
grid-column: 2 / -1;
|
|
963
|
+
font-size: var(--vela-font-size-sm);
|
|
964
|
+
line-height: 1.2;
|
|
965
|
+
}
|
|
966
|
+
[data-layout='mobile'] .vela-has-statusline [data-vela-pane='price'] { transform: translateY(40px); }
|
|
967
|
+
/* FIT mode (multi-chart cells \u2014 see setFitMode): the line never wraps; segments that
|
|
968
|
+
* don't fit are HIDDEN by fit() (change first, then meta, then the market badge), so
|
|
969
|
+
* overflow:hidden only guards the transient between a resize and the next measure.
|
|
970
|
+
* Placed after the mobile block on purpose: same specificity, later wins. */
|
|
971
|
+
.vela-statusline.vela-sl-fit {
|
|
972
|
+
display: flex; /* undo the mobile grid \u2014 fit mode is one flex row again */
|
|
973
|
+
flex-wrap: nowrap;
|
|
974
|
+
align-items: center;
|
|
975
|
+
max-width: calc(100% - var(--vela-toolbar-gutter, 0px) - var(--vela-scale-gutter, 0px) - 24px);
|
|
976
|
+
overflow: hidden;
|
|
977
|
+
}
|
|
978
|
+
.vela-statusline.vela-sl-fit .vela-sl-change {
|
|
979
|
+
flex-basis: auto;
|
|
980
|
+
padding-left: 0;
|
|
981
|
+
}
|
|
982
|
+
/* One row again \u2014 the mobile two-line shift doesn't apply in fit mode. */
|
|
983
|
+
[data-layout='mobile'] .vela-sl-fit-host.vela-has-statusline [data-vela-pane='price'] { transform: translateY(26px); }
|
|
907
984
|
`;
|
|
908
985
|
function baseOfTicker(ticker) {
|
|
909
986
|
return ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || ticker;
|
|
@@ -956,11 +1033,15 @@ var Statusline = class {
|
|
|
956
1033
|
this.isUp = null;
|
|
957
1034
|
/** 'ohlc' for bar-shaped styles; 'value' (the single plotted close) for line styles. */
|
|
958
1035
|
this.readout = "ohlc";
|
|
1036
|
+
/** Fit mode (multi-chart cells): one row, overflowing segments hidden — see {@link setFitMode}. */
|
|
1037
|
+
this.fitMode = false;
|
|
1038
|
+
this.fitRO = null;
|
|
959
1039
|
const doc = host.ownerDocument;
|
|
960
1040
|
injectStyles(STYLE_ID3, CSS3, doc);
|
|
961
1041
|
host.classList.add("vela-has-statusline");
|
|
962
1042
|
this.el = doc.createElement("div");
|
|
963
1043
|
this.el.className = "vela-statusline";
|
|
1044
|
+
this.el.dataset.velaScreenshot = "1";
|
|
964
1045
|
const ticker = parseSymbol(symbol).ticker;
|
|
965
1046
|
this.avatarEl = tickerIconEl(doc, baseOfTicker(ticker), ticker, "vela-sl-avatar");
|
|
966
1047
|
this.symbolEl = doc.createElement("span");
|
|
@@ -986,6 +1067,52 @@ var Statusline = class {
|
|
|
986
1067
|
const fresh = tickerIconEl(this.el.ownerDocument, baseOfTicker(ticker), ticker, "vela-sl-avatar");
|
|
987
1068
|
this.avatarEl.replaceWith(fresh);
|
|
988
1069
|
this.avatarEl = fresh;
|
|
1070
|
+
this.fit();
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Multi-chart cells: keep the line on ONE row whatever the cell width — never wrap.
|
|
1074
|
+
* Segments that don't fit are hidden outright rather than clipped mid-glyph, least
|
|
1075
|
+
* important first: OHLC, then the bar change, the venue/timeframe meta, and the
|
|
1076
|
+
* market badge; the logo + ticker always stay. Re-fits live on host resizes.
|
|
1077
|
+
*/
|
|
1078
|
+
setFitMode(on) {
|
|
1079
|
+
if (on === this.fitMode) return;
|
|
1080
|
+
this.fitMode = on;
|
|
1081
|
+
this.el.classList.toggle("vela-sl-fit", on);
|
|
1082
|
+
this.host.classList.toggle("vela-sl-fit-host", on);
|
|
1083
|
+
if (on) {
|
|
1084
|
+
if (typeof ResizeObserver !== "undefined" && !this.fitRO) {
|
|
1085
|
+
this.fitRO = new ResizeObserver(() => this.fit());
|
|
1086
|
+
this.fitRO.observe(this.host);
|
|
1087
|
+
}
|
|
1088
|
+
this.fit();
|
|
1089
|
+
} else {
|
|
1090
|
+
this.fitRO?.disconnect();
|
|
1091
|
+
this.fitRO = null;
|
|
1092
|
+
this.syncParts();
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
/** Project the parts config onto the segments (the baseline fit() prunes from). */
|
|
1096
|
+
syncParts() {
|
|
1097
|
+
this.symbolEl.style.display = this.parts.name ? "" : "none";
|
|
1098
|
+
this.marketEl.style.display = this.parts.market ? "" : "none";
|
|
1099
|
+
this.ohlcEl.style.display = this.parts.ohlc ? "" : "none";
|
|
1100
|
+
this.changeEl.style.display = this.parts.change ? "" : "none";
|
|
1101
|
+
}
|
|
1102
|
+
/** Hide overflowing segments until the row fits its max-width (fit mode only). */
|
|
1103
|
+
fit() {
|
|
1104
|
+
if (!this.fitMode) return;
|
|
1105
|
+
this.syncParts();
|
|
1106
|
+
const order = [
|
|
1107
|
+
[this.ohlcEl, this.parts.ohlc],
|
|
1108
|
+
[this.changeEl, this.parts.change],
|
|
1109
|
+
[this.metaEl, true],
|
|
1110
|
+
[this.marketEl, this.parts.market]
|
|
1111
|
+
];
|
|
1112
|
+
for (const [el, shown] of order) {
|
|
1113
|
+
if (this.el.scrollWidth <= this.el.clientWidth) break;
|
|
1114
|
+
if (shown) el.style.display = "none";
|
|
1115
|
+
}
|
|
989
1116
|
}
|
|
990
1117
|
/** Shape + color the value readout after the active price style: its own up/down
|
|
991
1118
|
* colors (candle bodies, bar ticks, the line color, …), the direction rule that
|
|
@@ -1005,6 +1132,7 @@ var Statusline = class {
|
|
|
1005
1132
|
/** The "· BINANCE · 1h" segment after the symbol — venue first, then resolution. */
|
|
1006
1133
|
setMeta(timeframe, provider) {
|
|
1007
1134
|
this.metaEl.textContent = `${provider ? `\xB7 ${provider.toUpperCase()} ` : ""}\xB7 ${timeframeLabel(timeframe)}`;
|
|
1135
|
+
this.fit();
|
|
1008
1136
|
}
|
|
1009
1137
|
/** Dress the market badge for a session state: its icon, tinted circle, and the
|
|
1010
1138
|
* hover label. Callers with no session model leave the constructor's 'open'. */
|
|
@@ -1016,10 +1144,8 @@ var Statusline = class {
|
|
|
1016
1144
|
}
|
|
1017
1145
|
setPartVisible(part, visible) {
|
|
1018
1146
|
this.parts[part] = visible;
|
|
1019
|
-
this.
|
|
1020
|
-
this.
|
|
1021
|
-
this.ohlcEl.style.display = this.parts.ohlc ? "" : "none";
|
|
1022
|
-
this.changeEl.style.display = this.parts.change ? "" : "none";
|
|
1147
|
+
this.syncParts();
|
|
1148
|
+
this.fit();
|
|
1023
1149
|
}
|
|
1024
1150
|
partVisible(part) {
|
|
1025
1151
|
return this.parts[part];
|
|
@@ -1043,8 +1169,10 @@ var Statusline = class {
|
|
|
1043
1169
|
}
|
|
1044
1170
|
destroy() {
|
|
1045
1171
|
this.detach();
|
|
1172
|
+
this.fitRO?.disconnect();
|
|
1173
|
+
this.fitRO = null;
|
|
1046
1174
|
this.marketTip.destroy();
|
|
1047
|
-
this.host.classList.remove("vela-has-statusline");
|
|
1175
|
+
this.host.classList.remove("vela-has-statusline", "vela-sl-fit-host");
|
|
1048
1176
|
this.el.remove();
|
|
1049
1177
|
}
|
|
1050
1178
|
detach() {
|
|
@@ -1076,11 +1204,12 @@ var Statusline = class {
|
|
|
1076
1204
|
this.changeEl.textContent = fmtChange(bar.open, bar.close);
|
|
1077
1205
|
this.changeEl.dataset.dir = up ? "up" : "down";
|
|
1078
1206
|
this.changeEl.style.color = ink;
|
|
1207
|
+
this.fit();
|
|
1079
1208
|
}
|
|
1080
1209
|
};
|
|
1081
1210
|
|
|
1082
1211
|
// src/widget/watermark.ts
|
|
1083
|
-
var MAX_FONT_PX =
|
|
1212
|
+
var MAX_FONT_PX = 36;
|
|
1084
1213
|
var MIN_FONT_PX = 12;
|
|
1085
1214
|
var FILL = 0.9;
|
|
1086
1215
|
var STYLE_ID4 = "vela-widget-watermark";
|
|
@@ -1116,9 +1245,16 @@ function watermarkFontPx(availPx, textPxAtMax) {
|
|
|
1116
1245
|
var Watermark = class {
|
|
1117
1246
|
constructor(host, symbol, timeframe) {
|
|
1118
1247
|
this.resizeObserver = null;
|
|
1248
|
+
/** The host's visibility preference (the persisted watermark toggle). */
|
|
1249
|
+
this.shown = true;
|
|
1250
|
+
/** A bar load is in flight with nothing painted — the loading affordance owns the
|
|
1251
|
+
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
1252
|
+
* fires during chart construction, before any subscriber can see it. */
|
|
1253
|
+
this.loading = true;
|
|
1119
1254
|
injectStyles(STYLE_ID4, CSS4, host.ownerDocument);
|
|
1120
1255
|
this.el = host.ownerDocument.createElement("div");
|
|
1121
1256
|
this.el.className = "vela-watermark";
|
|
1257
|
+
this.el.dataset.velaScreenshot = "under";
|
|
1122
1258
|
this.text = host.ownerDocument.createElement("span");
|
|
1123
1259
|
this.el.appendChild(this.text);
|
|
1124
1260
|
host.appendChild(this.el);
|
|
@@ -1127,9 +1263,19 @@ var Watermark = class {
|
|
|
1127
1263
|
this.resizeObserver.observe(this.el);
|
|
1128
1264
|
}
|
|
1129
1265
|
this.update(symbol, timeframe);
|
|
1266
|
+
this.sync();
|
|
1130
1267
|
}
|
|
1131
1268
|
setVisible(visible) {
|
|
1132
|
-
this.
|
|
1269
|
+
this.shown = visible;
|
|
1270
|
+
this.sync();
|
|
1271
|
+
}
|
|
1272
|
+
/** Loading and the watermark never share the canvas — hidden while a load is up. */
|
|
1273
|
+
setLoading(loading) {
|
|
1274
|
+
this.loading = loading;
|
|
1275
|
+
this.sync();
|
|
1276
|
+
}
|
|
1277
|
+
sync() {
|
|
1278
|
+
this.el.style.display = this.shown && !this.loading ? "" : "none";
|
|
1133
1279
|
}
|
|
1134
1280
|
update(symbol, timeframe) {
|
|
1135
1281
|
this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
|
|
@@ -1406,6 +1552,13 @@ var CSS6 = `
|
|
|
1406
1552
|
outline: none;
|
|
1407
1553
|
}
|
|
1408
1554
|
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 6px; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
1555
|
+
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
1556
|
+
overflowing the body, and the result list stops capping itself \u2014 the body owns
|
|
1557
|
+
the scrolling in the fullscreen presentation. */
|
|
1558
|
+
[data-layout='mobile'] .vela-sp-tabs { overflow-x: auto; scrollbar-width: none; gap: 6px; }
|
|
1559
|
+
[data-layout='mobile'] .vela-sp-tabs::-webkit-scrollbar { display: none; }
|
|
1560
|
+
[data-layout='mobile'] .vela-sp-tab { flex: none; padding: 7px 10px; }
|
|
1561
|
+
[data-layout='mobile'] .vela-sp-list { max-height: none; }
|
|
1409
1562
|
.vela-sp-tab {
|
|
1410
1563
|
all: unset;
|
|
1411
1564
|
padding: 3px 10px;
|
|
@@ -2881,6 +3034,9 @@ var ObjectTree = class extends SidePanel {
|
|
|
2881
3034
|
this.refresh();
|
|
2882
3035
|
})
|
|
2883
3036
|
];
|
|
3037
|
+
if (chart.renderer.supportsIndicatorSettings) {
|
|
3038
|
+
items.push(b.entry("Indicator settings", "gear", () => chart.renderer.openIndicatorSettings(row.id)));
|
|
3039
|
+
}
|
|
2884
3040
|
if (chart.panes.supported) {
|
|
2885
3041
|
const moves = this.moveItems(b, pass, row.id, paneId);
|
|
2886
3042
|
if (moves.length > 0) items.push(b.submenu("Move to", "move-vertical", moves));
|
|
@@ -3199,6 +3355,11 @@ var PanelDock = class {
|
|
|
3199
3355
|
get openId() {
|
|
3200
3356
|
return this.entries.find((e) => e.panel.open)?.id ?? null;
|
|
3201
3357
|
}
|
|
3358
|
+
/** The docked panels in dock order — what a non-topbar chrome (the mobile
|
|
3359
|
+
* three-dots drawer) lists so every panel stays reachable there too. */
|
|
3360
|
+
list() {
|
|
3361
|
+
return this.entries.map((e) => ({ id: e.id, title: e.title, icon: e.icon }));
|
|
3362
|
+
}
|
|
3202
3363
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
3203
3364
|
getState() {
|
|
3204
3365
|
const out = {};
|
|
@@ -3602,6 +3763,9 @@ var CSS10 = `
|
|
|
3602
3763
|
.vela-ip-searchrow .vela-icon { color: var(--vela-fg-muted); }
|
|
3603
3764
|
.vela-ip-search { flex: 1; background: transparent; color: var(--vela-fg); border: none; font-size: 14px; outline: none; }
|
|
3604
3765
|
.vela-ip-list { max-height: 50vh; overflow: auto; min-width: 380px; }
|
|
3766
|
+
/* Mobile (fullscreen dialog): no width floor \u2014 380px would overflow a phone \u2014
|
|
3767
|
+
and no height cap; the fullscreen body owns the scrolling. */
|
|
3768
|
+
[data-layout='mobile'] .vela-ip-list { min-width: 0; max-height: none; }
|
|
3605
3769
|
.vela-ip-list::-webkit-scrollbar { width: 8px; }
|
|
3606
3770
|
.vela-ip-list::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
3607
3771
|
.vela-ip-group {
|
|
@@ -3683,7 +3847,7 @@ var IndicatorPicker = class {
|
|
|
3683
3847
|
if (open) {
|
|
3684
3848
|
this.search.value = "";
|
|
3685
3849
|
this.refresh();
|
|
3686
|
-
setTimeout(() => this.search.focus(), 0);
|
|
3850
|
+
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
3687
3851
|
}
|
|
3688
3852
|
opts.onOpenChange?.(open);
|
|
3689
3853
|
}
|
|
@@ -3995,6 +4159,7 @@ function sanitizeCell(raw) {
|
|
|
3995
4159
|
if (typeof c.bars === "number" && Number.isFinite(c.bars) && c.bars > 0) out.bars = c.bars;
|
|
3996
4160
|
if (typeof c.watermark === "boolean") out.watermark = c.watermark;
|
|
3997
4161
|
if (typeof c.indicatorTitles === "boolean") out.indicatorTitles = c.indicatorTitles;
|
|
4162
|
+
if (typeof c.indicatorValues === "boolean") out.indicatorValues = c.indicatorValues;
|
|
3998
4163
|
if (c.rendererConfig != null && typeof c.rendererConfig === "object") out.rendererConfig = c.rendererConfig;
|
|
3999
4164
|
if (c.drawings != null && typeof c.drawings === "object") out.drawings = c.drawings;
|
|
4000
4165
|
const ind = c.indicators;
|
|
@@ -4101,6 +4266,1156 @@ function indicatorLedger(i) {
|
|
|
4101
4266
|
};
|
|
4102
4267
|
}
|
|
4103
4268
|
|
|
4269
|
+
// src/widget/layout-mode.ts
|
|
4270
|
+
var MOBILE_BREAKPOINT_PX = 640;
|
|
4271
|
+
var COARSE_BREAKPOINT_PX = 920;
|
|
4272
|
+
function resolveLayoutMode(option, width, coarsePointer) {
|
|
4273
|
+
if (option !== "auto") return option;
|
|
4274
|
+
if (width <= 0) return "desktop";
|
|
4275
|
+
if (width < MOBILE_BREAKPOINT_PX) return "mobile";
|
|
4276
|
+
if (coarsePointer && width < COARSE_BREAKPOINT_PX) return "mobile";
|
|
4277
|
+
return "desktop";
|
|
4278
|
+
}
|
|
4279
|
+
var LayoutModeController = class {
|
|
4280
|
+
constructor(el, option = "auto") {
|
|
4281
|
+
this.el = el;
|
|
4282
|
+
this.option = option;
|
|
4283
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
4284
|
+
this.ro = null;
|
|
4285
|
+
this.mql = null;
|
|
4286
|
+
this.onMediaChange = () => this.evaluate();
|
|
4287
|
+
const win = el.ownerDocument.defaultView;
|
|
4288
|
+
if (win && typeof win.matchMedia === "function") {
|
|
4289
|
+
this.mql = win.matchMedia("(pointer: coarse)");
|
|
4290
|
+
if (typeof this.mql.addEventListener === "function") this.mql.addEventListener("change", this.onMediaChange);
|
|
4291
|
+
}
|
|
4292
|
+
this.mode = resolveLayoutMode(option, el.getBoundingClientRect().width, this.mql?.matches ?? false);
|
|
4293
|
+
el.dataset.layout = this.mode;
|
|
4294
|
+
if (option === "auto" && win && typeof win.ResizeObserver === "function") {
|
|
4295
|
+
this.ro = new win.ResizeObserver(() => this.evaluate());
|
|
4296
|
+
this.ro.observe(el);
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
get current() {
|
|
4300
|
+
return this.mode;
|
|
4301
|
+
}
|
|
4302
|
+
/** Subscribe to mode transitions (fires with the NEW mode). */
|
|
4303
|
+
onChange(cb) {
|
|
4304
|
+
this.listeners.add(cb);
|
|
4305
|
+
return () => this.listeners.delete(cb);
|
|
4306
|
+
}
|
|
4307
|
+
destroy() {
|
|
4308
|
+
this.ro?.disconnect();
|
|
4309
|
+
this.ro = null;
|
|
4310
|
+
if (this.mql && typeof this.mql.removeEventListener === "function") this.mql.removeEventListener("change", this.onMediaChange);
|
|
4311
|
+
this.mql = null;
|
|
4312
|
+
this.listeners.clear();
|
|
4313
|
+
}
|
|
4314
|
+
evaluate() {
|
|
4315
|
+
const next = resolveLayoutMode(this.option, this.el.getBoundingClientRect().width, this.mql?.matches ?? false);
|
|
4316
|
+
if (next === this.mode) return;
|
|
4317
|
+
this.mode = next;
|
|
4318
|
+
this.el.dataset.layout = next;
|
|
4319
|
+
for (const cb of [...this.listeners]) cb(next);
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4322
|
+
|
|
4323
|
+
// src/widget/mobile-bar.ts
|
|
4324
|
+
var STYLE_ID12 = "vela-widget-mobilebar";
|
|
4325
|
+
var CSS12 = `
|
|
4326
|
+
.vela-mobilebar {
|
|
4327
|
+
display: none;
|
|
4328
|
+
align-items: stretch;
|
|
4329
|
+
gap: 2px;
|
|
4330
|
+
padding: 4px 6px calc(4px + env(safe-area-inset-bottom, 0px));
|
|
4331
|
+
border-top: 1px solid var(--vela-border);
|
|
4332
|
+
color: var(--vela-fg-muted);
|
|
4333
|
+
flex: none;
|
|
4334
|
+
}
|
|
4335
|
+
[data-layout='mobile'] .vela-mobilebar { display: flex; }
|
|
4336
|
+
[data-layout='mobile'] .vela-widget-topbar { display: none; }
|
|
4337
|
+
[data-layout='mobile'] .vela-widget-bottombar { display: none; }
|
|
4338
|
+
.vela-mb-item {
|
|
4339
|
+
all: unset;
|
|
4340
|
+
flex: 1 1 0;
|
|
4341
|
+
min-width: 0;
|
|
4342
|
+
min-height: 44px;
|
|
4343
|
+
display: inline-flex;
|
|
4344
|
+
align-items: center;
|
|
4345
|
+
justify-content: center;
|
|
4346
|
+
gap: 6px;
|
|
4347
|
+
border-radius: 6px;
|
|
4348
|
+
cursor: pointer;
|
|
4349
|
+
color: var(--vela-fg-bright);
|
|
4350
|
+
font-size: 13px;
|
|
4351
|
+
font-weight: 600;
|
|
4352
|
+
-webkit-tap-highlight-color: transparent;
|
|
4353
|
+
}
|
|
4354
|
+
.vela-mb-item:active { background: var(--vela-hover); }
|
|
4355
|
+
.vela-mb-item .vela-icon { font-size: 18px; width: 18px; height: 18px; }
|
|
4356
|
+
/* Left-aligned contributed actions get their own stops (the built-in indicators
|
|
4357
|
+
slot) \u2014 the wrapper is layout-transparent so each stop flexes like a sibling. */
|
|
4358
|
+
.vela-mb-actions { display: contents; }
|
|
4359
|
+
.vela-mb-symbol {
|
|
4360
|
+
flex: 1.6 1 0;
|
|
4361
|
+
font-size: 14px;
|
|
4362
|
+
letter-spacing: 0.3px;
|
|
4363
|
+
white-space: nowrap;
|
|
4364
|
+
overflow: hidden;
|
|
4365
|
+
text-overflow: ellipsis;
|
|
4366
|
+
display: block;
|
|
4367
|
+
line-height: 44px;
|
|
4368
|
+
text-align: center;
|
|
4369
|
+
}
|
|
4370
|
+
`;
|
|
4371
|
+
var MobileBar = class {
|
|
4372
|
+
constructor(host, opts) {
|
|
4373
|
+
this.opts = opts;
|
|
4374
|
+
const doc = host.ownerDocument;
|
|
4375
|
+
injectStyles(STYLE_ID12, CSS12, doc);
|
|
4376
|
+
this.el = doc.createElement("div");
|
|
4377
|
+
this.el.className = "vela-mobilebar";
|
|
4378
|
+
const item = (cls, label, onClick, icon2) => {
|
|
4379
|
+
const b = doc.createElement("button");
|
|
4380
|
+
b.className = `vela-mb-item ${cls}`;
|
|
4381
|
+
b.setAttribute("aria-label", label);
|
|
4382
|
+
if (icon2) b.appendChild(iconEl(icon2, doc));
|
|
4383
|
+
b.addEventListener("click", onClick);
|
|
4384
|
+
return b;
|
|
4385
|
+
};
|
|
4386
|
+
this.symbolEl = item("vela-mb-symbol", "Symbol search", opts.onSymbolClick);
|
|
4387
|
+
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
4388
|
+
this.tfEl = item("vela-mb-tf", "Timeframe", opts.onTimeframeClick);
|
|
4389
|
+
this.tfEl.textContent = timeframeLabel(opts.timeframe);
|
|
4390
|
+
const onIndicators = opts.onIndicatorsClick;
|
|
4391
|
+
const indicators = onIndicators ? item("vela-mb-indicators", "Indicators", onIndicators, "indicators") : null;
|
|
4392
|
+
this.actionsHost = doc.createElement("span");
|
|
4393
|
+
this.actionsHost.className = "vela-mb-actions";
|
|
4394
|
+
const drawings = item("vela-mb-drawings", "Drawings", opts.onDrawingsClick, "pen");
|
|
4395
|
+
const more = item("vela-mb-more", "More", opts.onMoreClick, "kebab");
|
|
4396
|
+
const settings = item("vela-mb-settings", "Chart settings", opts.onSettingsClick, "gear");
|
|
4397
|
+
this.el.append(this.symbolEl, this.tfEl, ...indicators ? [indicators] : [], this.actionsHost, drawings, more, settings);
|
|
4398
|
+
host.appendChild(this.el);
|
|
4399
|
+
this.renderActions();
|
|
4400
|
+
}
|
|
4401
|
+
/** Re-project the left-aligned contributed actions as icon-only stops in the
|
|
4402
|
+
* indicators slot (call after registrations change). Right-aligned actions stay
|
|
4403
|
+
* in the three-dots drawer — a primary stop is what `align: 'left'` opts into. */
|
|
4404
|
+
renderActions() {
|
|
4405
|
+
const ctx = this.opts.getContext?.();
|
|
4406
|
+
if (!ctx) return;
|
|
4407
|
+
const doc = this.el.ownerDocument;
|
|
4408
|
+
this.actionsHost.replaceChildren();
|
|
4409
|
+
for (const action of widgetActions("topbar", ctx).filter((a) => a.align === "left")) {
|
|
4410
|
+
const b = doc.createElement("button");
|
|
4411
|
+
b.className = "vela-mb-item";
|
|
4412
|
+
b.setAttribute("aria-label", action.label);
|
|
4413
|
+
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
4414
|
+
else b.appendChild(doc.createTextNode(action.label));
|
|
4415
|
+
b.addEventListener("click", () => {
|
|
4416
|
+
const c = this.opts.getContext?.();
|
|
4417
|
+
if (c) action.run(c);
|
|
4418
|
+
});
|
|
4419
|
+
this.actionsHost.appendChild(b);
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
setSymbol(symbol) {
|
|
4423
|
+
this.symbolEl.textContent = parseSymbol(symbol).ticker;
|
|
4424
|
+
}
|
|
4425
|
+
setTimeframe(tf) {
|
|
4426
|
+
this.tfEl.textContent = timeframeLabel(tf);
|
|
4427
|
+
}
|
|
4428
|
+
destroy() {
|
|
4429
|
+
this.el.remove();
|
|
4430
|
+
}
|
|
4431
|
+
};
|
|
4432
|
+
|
|
4433
|
+
// src/widget/timeframe-drawer.ts
|
|
4434
|
+
var STYLE_ID13 = "vela-widget-tf-drawer";
|
|
4435
|
+
var CSS13 = `
|
|
4436
|
+
.vela-tfd-heading {
|
|
4437
|
+
padding: 4px 2px 8px;
|
|
4438
|
+
font-size: 12px;
|
|
4439
|
+
font-weight: 600;
|
|
4440
|
+
letter-spacing: 0.4px;
|
|
4441
|
+
text-transform: uppercase;
|
|
4442
|
+
color: var(--vela-fg-bright);
|
|
4443
|
+
}
|
|
4444
|
+
.vela-tfd-heading + .vela-tfd-ranges,
|
|
4445
|
+
.vela-tfd-heading + .vela-tfd-grid { padding-top: 0; }
|
|
4446
|
+
.vela-tfd-ranges {
|
|
4447
|
+
display: flex;
|
|
4448
|
+
gap: 6px;
|
|
4449
|
+
overflow-x: auto;
|
|
4450
|
+
scrollbar-width: none;
|
|
4451
|
+
/* Sideways-scrolling strip: keep its touches native scroll (the drawer body is pan-y). */
|
|
4452
|
+
touch-action: pan-x;
|
|
4453
|
+
padding: 0 2px 14px;
|
|
4454
|
+
}
|
|
4455
|
+
.vela-tfd-ranges::-webkit-scrollbar { display: none; }
|
|
4456
|
+
.vela-tfd-grid {
|
|
4457
|
+
display: grid;
|
|
4458
|
+
grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
|
|
4459
|
+
gap: 6px;
|
|
4460
|
+
padding-bottom: 4px;
|
|
4461
|
+
}
|
|
4462
|
+
.vela-tfd-chip {
|
|
4463
|
+
all: unset;
|
|
4464
|
+
min-height: 40px;
|
|
4465
|
+
padding: 0 14px;
|
|
4466
|
+
display: inline-flex;
|
|
4467
|
+
align-items: center;
|
|
4468
|
+
justify-content: center;
|
|
4469
|
+
flex: none;
|
|
4470
|
+
border: 1px solid var(--vela-border);
|
|
4471
|
+
border-radius: 8px;
|
|
4472
|
+
font-size: 13px;
|
|
4473
|
+
font-weight: 600;
|
|
4474
|
+
color: var(--vela-fg);
|
|
4475
|
+
cursor: pointer;
|
|
4476
|
+
-webkit-tap-highlight-color: transparent;
|
|
4477
|
+
}
|
|
4478
|
+
.vela-tfd-chip:active { background: var(--vela-hover); }
|
|
4479
|
+
.vela-tfd-chip[data-active='1'] {
|
|
4480
|
+
color: var(--vela-fg-bright);
|
|
4481
|
+
border-color: var(--vela-fg-bright);
|
|
4482
|
+
background: var(--vela-hover);
|
|
4483
|
+
}
|
|
4484
|
+
`;
|
|
4485
|
+
var TimeframeDrawer = class {
|
|
4486
|
+
constructor(opts) {
|
|
4487
|
+
this.opts = opts;
|
|
4488
|
+
this.rangeChips = /* @__PURE__ */ new Map();
|
|
4489
|
+
this.tfChips = /* @__PURE__ */ new Map();
|
|
4490
|
+
const doc = opts.host.ownerDocument;
|
|
4491
|
+
injectStyles(STYLE_ID13, CSS13, doc);
|
|
4492
|
+
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
4493
|
+
const rangeHeading = doc.createElement("div");
|
|
4494
|
+
rangeHeading.className = "vela-tfd-heading";
|
|
4495
|
+
rangeHeading.textContent = "Date Range";
|
|
4496
|
+
const ranges = doc.createElement("div");
|
|
4497
|
+
ranges.className = "vela-tfd-ranges";
|
|
4498
|
+
for (const preset of opts.ranges) {
|
|
4499
|
+
const chip = doc.createElement("button");
|
|
4500
|
+
chip.className = "vela-tfd-chip";
|
|
4501
|
+
chip.textContent = preset.id;
|
|
4502
|
+
chip.addEventListener("click", () => {
|
|
4503
|
+
opts.onRange(preset);
|
|
4504
|
+
this.drawer.hide();
|
|
4505
|
+
});
|
|
4506
|
+
this.rangeChips.set(preset.id, chip);
|
|
4507
|
+
ranges.appendChild(chip);
|
|
4508
|
+
}
|
|
4509
|
+
const tfHeading = doc.createElement("div");
|
|
4510
|
+
tfHeading.className = "vela-tfd-heading";
|
|
4511
|
+
tfHeading.textContent = "Timeframes";
|
|
4512
|
+
const grid = doc.createElement("div");
|
|
4513
|
+
grid.className = "vela-tfd-grid";
|
|
4514
|
+
for (const tf of opts.timeframes) {
|
|
4515
|
+
const chip = doc.createElement("button");
|
|
4516
|
+
chip.className = "vela-tfd-chip";
|
|
4517
|
+
chip.textContent = timeframeLabel(tf);
|
|
4518
|
+
chip.addEventListener("click", () => {
|
|
4519
|
+
opts.onTimeframe(tf);
|
|
4520
|
+
this.drawer.hide();
|
|
4521
|
+
});
|
|
4522
|
+
this.tfChips.set(tf, chip);
|
|
4523
|
+
grid.appendChild(chip);
|
|
4524
|
+
}
|
|
4525
|
+
this.drawer.body.append(rangeHeading, ranges, tfHeading, grid);
|
|
4526
|
+
}
|
|
4527
|
+
open() {
|
|
4528
|
+
const tf = this.opts.currentTimeframe();
|
|
4529
|
+
const range = this.opts.activeRange();
|
|
4530
|
+
for (const [id, chip] of this.rangeChips) {
|
|
4531
|
+
if (range !== null && id === range) chip.dataset.active = "1";
|
|
4532
|
+
else delete chip.dataset.active;
|
|
4533
|
+
}
|
|
4534
|
+
for (const [id, chip] of this.tfChips) {
|
|
4535
|
+
if (range === null && id === tf) chip.dataset.active = "1";
|
|
4536
|
+
else delete chip.dataset.active;
|
|
4537
|
+
}
|
|
4538
|
+
this.drawer.show();
|
|
4539
|
+
}
|
|
4540
|
+
close() {
|
|
4541
|
+
this.drawer.hide();
|
|
4542
|
+
}
|
|
4543
|
+
destroy() {
|
|
4544
|
+
this.drawer.destroy();
|
|
4545
|
+
}
|
|
4546
|
+
};
|
|
4547
|
+
|
|
4548
|
+
// src/widget/drawings-drawer.ts
|
|
4549
|
+
var STYLE_ID14 = "vela-widget-drawings-drawer";
|
|
4550
|
+
var CSS14 = `
|
|
4551
|
+
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
4552
|
+
.vela-dd-sticky {
|
|
4553
|
+
position: sticky;
|
|
4554
|
+
top: 0;
|
|
4555
|
+
z-index: 1;
|
|
4556
|
+
background: var(--vela-surface);
|
|
4557
|
+
padding-top: 2px;
|
|
4558
|
+
}
|
|
4559
|
+
.vela-dd-search {
|
|
4560
|
+
display: flex;
|
|
4561
|
+
align-items: center;
|
|
4562
|
+
gap: 8px;
|
|
4563
|
+
padding: 8px 12px;
|
|
4564
|
+
margin: 0 2px 8px;
|
|
4565
|
+
border: 1px solid var(--vela-border);
|
|
4566
|
+
border-radius: 8px;
|
|
4567
|
+
color: var(--vela-fg-muted);
|
|
4568
|
+
background: var(--vela-surface);
|
|
4569
|
+
}
|
|
4570
|
+
.vela-dd-search input {
|
|
4571
|
+
all: unset;
|
|
4572
|
+
flex: 1 1 auto;
|
|
4573
|
+
min-width: 0;
|
|
4574
|
+
font-size: 14px;
|
|
4575
|
+
color: var(--vela-fg-bright);
|
|
4576
|
+
}
|
|
4577
|
+
.vela-dd-tabs {
|
|
4578
|
+
display: flex;
|
|
4579
|
+
gap: 4px;
|
|
4580
|
+
overflow-x: auto;
|
|
4581
|
+
scrollbar-width: none;
|
|
4582
|
+
/* The strip scrolls sideways itself \u2014 its touches are native scroll, not tab swipes
|
|
4583
|
+
(the drawer body is pan-y so everywhere ELSE a sideways move swipes the tabs). */
|
|
4584
|
+
touch-action: pan-x;
|
|
4585
|
+
padding: 0 2px 8px;
|
|
4586
|
+
border-bottom: 1px solid var(--vela-border);
|
|
4587
|
+
background: var(--vela-surface);
|
|
4588
|
+
}
|
|
4589
|
+
.vela-dd-tabs::-webkit-scrollbar { display: none; }
|
|
4590
|
+
.vela-dd-tab {
|
|
4591
|
+
all: unset;
|
|
4592
|
+
flex: none;
|
|
4593
|
+
min-height: 36px;
|
|
4594
|
+
padding: 0 12px;
|
|
4595
|
+
display: inline-flex;
|
|
4596
|
+
align-items: center;
|
|
4597
|
+
border-radius: 8px;
|
|
4598
|
+
font-size: 13px;
|
|
4599
|
+
font-weight: 600;
|
|
4600
|
+
color: var(--vela-fg-muted);
|
|
4601
|
+
cursor: pointer;
|
|
4602
|
+
-webkit-tap-highlight-color: transparent;
|
|
4603
|
+
}
|
|
4604
|
+
.vela-dd-tab[data-active='1'] { color: var(--vela-fg-bright); background: var(--vela-hover); }
|
|
4605
|
+
.vela-dd-list { padding: 6px 0 4px; }
|
|
4606
|
+
.vela-dd-section {
|
|
4607
|
+
padding: 10px 2px 6px;
|
|
4608
|
+
font-size: 12px;
|
|
4609
|
+
font-weight: 600;
|
|
4610
|
+
letter-spacing: 0.4px;
|
|
4611
|
+
text-transform: uppercase;
|
|
4612
|
+
color: var(--vela-fg-muted);
|
|
4613
|
+
}
|
|
4614
|
+
.vela-dd-row {
|
|
4615
|
+
display: flex;
|
|
4616
|
+
align-items: center;
|
|
4617
|
+
gap: 12px;
|
|
4618
|
+
min-height: 44px;
|
|
4619
|
+
padding: 0 2px;
|
|
4620
|
+
border-radius: 8px;
|
|
4621
|
+
cursor: pointer;
|
|
4622
|
+
-webkit-tap-highlight-color: transparent;
|
|
4623
|
+
}
|
|
4624
|
+
.vela-dd-row:active { background: var(--vela-hover); }
|
|
4625
|
+
.vela-dd-row[data-active='1'] { background: var(--vela-hover); }
|
|
4626
|
+
.vela-dd-row[data-active='1'] .vela-dd-label { color: var(--vela-accent); }
|
|
4627
|
+
.vela-dd-glyph { flex: none; width: 24px; height: 24px; color: var(--vela-fg); }
|
|
4628
|
+
.vela-dd-glyph svg { width: 24px; height: 24px; }
|
|
4629
|
+
.vela-dd-label { flex: 1 1 auto; min-width: 0; font-size: 14px; color: var(--vela-fg-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4630
|
+
.vela-dd-star {
|
|
4631
|
+
all: unset;
|
|
4632
|
+
flex: none;
|
|
4633
|
+
width: 40px;
|
|
4634
|
+
height: 40px;
|
|
4635
|
+
display: inline-flex;
|
|
4636
|
+
align-items: center;
|
|
4637
|
+
justify-content: center;
|
|
4638
|
+
border-radius: 8px;
|
|
4639
|
+
color: var(--vela-fg-muted);
|
|
4640
|
+
cursor: pointer;
|
|
4641
|
+
-webkit-tap-highlight-color: transparent;
|
|
4642
|
+
}
|
|
4643
|
+
.vela-dd-star[data-on='1'] { color: var(--vela-highlight); }
|
|
4644
|
+
.vela-dd-empty { padding: 18px 2px; color: var(--vela-fg-muted); font-size: 13px; }
|
|
4645
|
+
`;
|
|
4646
|
+
var DrawingsDrawer = class {
|
|
4647
|
+
constructor(opts) {
|
|
4648
|
+
this.opts = opts;
|
|
4649
|
+
this.definition = { groups: [] };
|
|
4650
|
+
this.activeGroup = "";
|
|
4651
|
+
const doc = opts.host.ownerDocument;
|
|
4652
|
+
injectStyles(STYLE_ID14, CSS14, doc);
|
|
4653
|
+
this.drawer = new Drawer({
|
|
4654
|
+
host: opts.host,
|
|
4655
|
+
title: "Drawings",
|
|
4656
|
+
onOpenChange: opts.onOpenChange,
|
|
4657
|
+
// Swiping across the tool list pages through the group tabs.
|
|
4658
|
+
onSwipe: (dir) => this.stepGroup(dir === "left" ? 1 : -1)
|
|
4659
|
+
});
|
|
4660
|
+
const sticky = doc.createElement("div");
|
|
4661
|
+
sticky.className = "vela-dd-sticky";
|
|
4662
|
+
const search = doc.createElement("div");
|
|
4663
|
+
search.className = "vela-dd-search";
|
|
4664
|
+
search.appendChild(iconEl("search", doc));
|
|
4665
|
+
this.input = doc.createElement("input");
|
|
4666
|
+
this.input.type = "text";
|
|
4667
|
+
this.input.placeholder = "Search tools\u2026";
|
|
4668
|
+
this.input.addEventListener("input", () => this.renderList());
|
|
4669
|
+
search.appendChild(this.input);
|
|
4670
|
+
this.tabs = doc.createElement("div");
|
|
4671
|
+
this.tabs.className = "vela-dd-tabs";
|
|
4672
|
+
sticky.append(search, this.tabs);
|
|
4673
|
+
this.list = doc.createElement("div");
|
|
4674
|
+
this.list.className = "vela-dd-list";
|
|
4675
|
+
this.drawer.body.append(sticky, this.list);
|
|
4676
|
+
}
|
|
4677
|
+
open() {
|
|
4678
|
+
this.definition = this.opts.toolbar();
|
|
4679
|
+
if (!this.definition.groups.some((g) => g.id === this.activeGroup)) this.activeGroup = this.definition.groups[0]?.id ?? "";
|
|
4680
|
+
this.input.value = "";
|
|
4681
|
+
this.renderTabs();
|
|
4682
|
+
this.renderList();
|
|
4683
|
+
this.drawer.show();
|
|
4684
|
+
}
|
|
4685
|
+
close() {
|
|
4686
|
+
this.drawer.hide();
|
|
4687
|
+
}
|
|
4688
|
+
destroy() {
|
|
4689
|
+
this.drawer.destroy();
|
|
4690
|
+
}
|
|
4691
|
+
/** Move the active group tab by `step` (a horizontal swipe on the list). Search mode
|
|
4692
|
+
* shows the flattened results — no tabs to page through, so the swipe is inert. */
|
|
4693
|
+
stepGroup(step) {
|
|
4694
|
+
if (this.input.value.trim()) return;
|
|
4695
|
+
const groups = this.definition.groups;
|
|
4696
|
+
const next = groups[groups.findIndex((g) => g.id === this.activeGroup) + step];
|
|
4697
|
+
if (!next) return;
|
|
4698
|
+
this.activeGroup = next.id;
|
|
4699
|
+
this.renderTabs();
|
|
4700
|
+
this.renderList();
|
|
4701
|
+
this.tabs.querySelector('[data-active="1"]')?.scrollIntoView?.({ block: "nearest", inline: "nearest" });
|
|
4702
|
+
}
|
|
4703
|
+
renderTabs() {
|
|
4704
|
+
const doc = this.tabs.ownerDocument;
|
|
4705
|
+
this.tabs.replaceChildren();
|
|
4706
|
+
for (const group of this.definition.groups) {
|
|
4707
|
+
const tab = doc.createElement("button");
|
|
4708
|
+
tab.className = "vela-dd-tab";
|
|
4709
|
+
tab.textContent = group.label;
|
|
4710
|
+
if (group.id === this.activeGroup) tab.dataset.active = "1";
|
|
4711
|
+
tab.addEventListener("click", () => {
|
|
4712
|
+
this.activeGroup = group.id;
|
|
4713
|
+
this.input.value = "";
|
|
4714
|
+
this.renderTabs();
|
|
4715
|
+
this.renderList();
|
|
4716
|
+
});
|
|
4717
|
+
this.tabs.appendChild(tab);
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
renderList() {
|
|
4721
|
+
const doc = this.list.ownerDocument;
|
|
4722
|
+
this.list.replaceChildren();
|
|
4723
|
+
const query = this.input.value.trim().toLowerCase();
|
|
4724
|
+
const active = this.opts.currentTool();
|
|
4725
|
+
const section = (label) => {
|
|
4726
|
+
const el = doc.createElement("div");
|
|
4727
|
+
el.className = "vela-dd-section";
|
|
4728
|
+
el.textContent = label;
|
|
4729
|
+
this.list.appendChild(el);
|
|
4730
|
+
};
|
|
4731
|
+
const row = (tool) => {
|
|
4732
|
+
const el = doc.createElement("div");
|
|
4733
|
+
el.className = "vela-dd-row";
|
|
4734
|
+
if (tool.type === active) el.dataset.active = "1";
|
|
4735
|
+
const glyph = doc.createElement("span");
|
|
4736
|
+
glyph.className = "vela-dd-glyph";
|
|
4737
|
+
glyph.innerHTML = tool.icon;
|
|
4738
|
+
const label = doc.createElement("span");
|
|
4739
|
+
label.className = "vela-dd-label";
|
|
4740
|
+
label.textContent = tool.label;
|
|
4741
|
+
const star = doc.createElement("button");
|
|
4742
|
+
star.className = "vela-dd-star";
|
|
4743
|
+
const paintStar = (on) => {
|
|
4744
|
+
star.replaceChildren(iconEl(on ? "star-filled" : "star", doc));
|
|
4745
|
+
if (on) star.dataset.on = "1";
|
|
4746
|
+
else delete star.dataset.on;
|
|
4747
|
+
};
|
|
4748
|
+
paintStar(this.opts.isFavorite(tool.type));
|
|
4749
|
+
star.setAttribute("aria-label", `Favorite ${tool.label}`);
|
|
4750
|
+
star.addEventListener("click", (e) => {
|
|
4751
|
+
e.stopPropagation();
|
|
4752
|
+
const on = !this.opts.isFavorite(tool.type);
|
|
4753
|
+
this.opts.onFavorite(tool.type, on);
|
|
4754
|
+
paintStar(on);
|
|
4755
|
+
});
|
|
4756
|
+
el.append(glyph, label, star);
|
|
4757
|
+
el.addEventListener("click", () => {
|
|
4758
|
+
this.opts.onSelect(tool.type);
|
|
4759
|
+
this.drawer.hide();
|
|
4760
|
+
});
|
|
4761
|
+
this.list.appendChild(el);
|
|
4762
|
+
};
|
|
4763
|
+
if (query) {
|
|
4764
|
+
let any = false;
|
|
4765
|
+
for (const group2 of this.definition.groups) {
|
|
4766
|
+
const hits = group2.tools.filter((t) => t.label.toLowerCase().includes(query));
|
|
4767
|
+
if (hits.length === 0) continue;
|
|
4768
|
+
any = true;
|
|
4769
|
+
section(group2.label);
|
|
4770
|
+
for (const tool of hits) row(tool);
|
|
4771
|
+
}
|
|
4772
|
+
if (!any) {
|
|
4773
|
+
const empty = doc.createElement("div");
|
|
4774
|
+
empty.className = "vela-dd-empty";
|
|
4775
|
+
empty.textContent = "No tools match.";
|
|
4776
|
+
this.list.appendChild(empty);
|
|
4777
|
+
}
|
|
4778
|
+
return;
|
|
4779
|
+
}
|
|
4780
|
+
const group = this.definition.groups.find((g) => g.id === this.activeGroup);
|
|
4781
|
+
if (!group) return;
|
|
4782
|
+
if (group.sections && group.sections.length > 0) {
|
|
4783
|
+
for (const s of group.sections) {
|
|
4784
|
+
section(s.label);
|
|
4785
|
+
for (const tool of s.tools) row(tool);
|
|
4786
|
+
}
|
|
4787
|
+
} else {
|
|
4788
|
+
for (const tool of group.tools) row(tool);
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
4791
|
+
};
|
|
4792
|
+
|
|
4793
|
+
// src/widget/more-drawer.ts
|
|
4794
|
+
var STYLE_ID15 = "vela-widget-more-drawer";
|
|
4795
|
+
var CSS15 = `
|
|
4796
|
+
.vela-md-actions {
|
|
4797
|
+
display: flex;
|
|
4798
|
+
gap: 6px;
|
|
4799
|
+
padding: 2px 2px 10px;
|
|
4800
|
+
border-bottom: 1px solid var(--vela-border);
|
|
4801
|
+
}
|
|
4802
|
+
.vela-md-action {
|
|
4803
|
+
all: unset;
|
|
4804
|
+
flex: 1 1 0;
|
|
4805
|
+
min-height: 48px;
|
|
4806
|
+
display: inline-flex;
|
|
4807
|
+
flex-direction: column;
|
|
4808
|
+
align-items: center;
|
|
4809
|
+
justify-content: center;
|
|
4810
|
+
gap: 4px;
|
|
4811
|
+
border-radius: 8px;
|
|
4812
|
+
font-size: 11px;
|
|
4813
|
+
color: var(--vela-fg);
|
|
4814
|
+
cursor: pointer;
|
|
4815
|
+
-webkit-tap-highlight-color: transparent;
|
|
4816
|
+
}
|
|
4817
|
+
.vela-md-action:active { background: var(--vela-hover); }
|
|
4818
|
+
.vela-md-action:disabled { opacity: 0.35; cursor: default; }
|
|
4819
|
+
.vela-md-action .vela-icon { font-size: 17px; width: 17px; height: 17px; }
|
|
4820
|
+
.vela-md-list { padding: 6px 0 4px; }
|
|
4821
|
+
.vela-md-row {
|
|
4822
|
+
display: flex;
|
|
4823
|
+
align-items: center;
|
|
4824
|
+
gap: 12px;
|
|
4825
|
+
min-height: 46px;
|
|
4826
|
+
padding: 0 2px;
|
|
4827
|
+
border-radius: 8px;
|
|
4828
|
+
cursor: pointer;
|
|
4829
|
+
-webkit-tap-highlight-color: transparent;
|
|
4830
|
+
}
|
|
4831
|
+
.vela-md-row:active { background: var(--vela-hover); }
|
|
4832
|
+
.vela-md-row[data-checked='1'] { background: var(--vela-hover-strong); }
|
|
4833
|
+
.vela-md-row .vela-icon { flex: none; font-size: 17px; width: 17px; height: 17px; color: var(--vela-fg-muted); }
|
|
4834
|
+
.vela-md-row-label { flex: 1 1 auto; min-width: 0; font-size: 14px; color: var(--vela-fg-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4835
|
+
.vela-md-row-value { flex: none; font-size: 13px; color: var(--vela-fg-muted); }
|
|
4836
|
+
.vela-md-back {
|
|
4837
|
+
all: unset;
|
|
4838
|
+
display: flex;
|
|
4839
|
+
align-items: center;
|
|
4840
|
+
gap: 8px;
|
|
4841
|
+
min-height: 42px;
|
|
4842
|
+
padding: 0 2px;
|
|
4843
|
+
font-size: 14px;
|
|
4844
|
+
font-weight: 600;
|
|
4845
|
+
color: var(--vela-fg-bright);
|
|
4846
|
+
cursor: pointer;
|
|
4847
|
+
border-bottom: 1px solid var(--vela-border);
|
|
4848
|
+
width: 100%;
|
|
4849
|
+
box-sizing: border-box;
|
|
4850
|
+
-webkit-tap-highlight-color: transparent;
|
|
4851
|
+
}
|
|
4852
|
+
/* The layout sub-view's grid canvas, centered and touch-sized (the base .vela-lp-grid
|
|
4853
|
+
squares are popover-sized for a mouse). */
|
|
4854
|
+
.vela-md-gridwrap { display: flex; justify-content: center; padding: 14px 0 10px; }
|
|
4855
|
+
.vela-md-gridwrap .vela-lp-grid { grid-template-columns: repeat(4, 44px); grid-auto-rows: 44px; gap: 8px; }
|
|
4856
|
+
.vela-md-section {
|
|
4857
|
+
padding: 12px 2px 4px;
|
|
4858
|
+
font-size: 12px;
|
|
4859
|
+
font-weight: 600;
|
|
4860
|
+
letter-spacing: 0.4px;
|
|
4861
|
+
text-transform: uppercase;
|
|
4862
|
+
color: var(--vela-fg-muted);
|
|
4863
|
+
}
|
|
4864
|
+
.vela-md-empty { padding: 18px 2px; color: var(--vela-fg-muted); font-size: 13px; }
|
|
4865
|
+
`;
|
|
4866
|
+
var MoreDrawer = class {
|
|
4867
|
+
constructor(opts) {
|
|
4868
|
+
this.opts = opts;
|
|
4869
|
+
this.view = "main";
|
|
4870
|
+
injectStyles(STYLE_ID15, CSS15, opts.host.ownerDocument);
|
|
4871
|
+
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
4872
|
+
}
|
|
4873
|
+
open() {
|
|
4874
|
+
this.view = "main";
|
|
4875
|
+
this.render();
|
|
4876
|
+
this.drawer.show();
|
|
4877
|
+
}
|
|
4878
|
+
close() {
|
|
4879
|
+
this.drawer.hide();
|
|
4880
|
+
}
|
|
4881
|
+
destroy() {
|
|
4882
|
+
this.drawer.destroy();
|
|
4883
|
+
}
|
|
4884
|
+
show(view) {
|
|
4885
|
+
this.view = view;
|
|
4886
|
+
this.render();
|
|
4887
|
+
}
|
|
4888
|
+
render() {
|
|
4889
|
+
const doc = this.drawer.body.ownerDocument;
|
|
4890
|
+
this.drawer.body.replaceChildren();
|
|
4891
|
+
if (this.view !== "main") {
|
|
4892
|
+
const back = doc.createElement("button");
|
|
4893
|
+
back.className = "vela-md-back";
|
|
4894
|
+
back.appendChild(iconEl("chevron-left", doc));
|
|
4895
|
+
back.appendChild(doc.createTextNode(this.view === "style" ? "Chart type" : this.view === "layout" ? "Layout" : "Alerts"));
|
|
4896
|
+
back.addEventListener("click", () => this.show("main"));
|
|
4897
|
+
this.drawer.body.appendChild(back);
|
|
4898
|
+
}
|
|
4899
|
+
if (this.view === "main") this.renderMain(doc);
|
|
4900
|
+
else if (this.view === "style") this.renderStyle(doc);
|
|
4901
|
+
else if (this.view === "layout") this.renderLayout(doc);
|
|
4902
|
+
else this.renderAlerts(doc);
|
|
4903
|
+
}
|
|
4904
|
+
row(doc, label, opts) {
|
|
4905
|
+
const el = doc.createElement("div");
|
|
4906
|
+
el.className = "vela-md-row";
|
|
4907
|
+
if (opts.icon) el.appendChild(iconEl(opts.icon, doc));
|
|
4908
|
+
const text = doc.createElement("span");
|
|
4909
|
+
text.className = "vela-md-row-label";
|
|
4910
|
+
text.textContent = label;
|
|
4911
|
+
el.appendChild(text);
|
|
4912
|
+
if (opts.value) {
|
|
4913
|
+
const value = doc.createElement("span");
|
|
4914
|
+
value.className = "vela-md-row-value";
|
|
4915
|
+
value.textContent = opts.value;
|
|
4916
|
+
el.appendChild(value);
|
|
4917
|
+
}
|
|
4918
|
+
if (opts.checked) el.dataset.checked = "1";
|
|
4919
|
+
if (opts.chevron) el.appendChild(iconEl("chevron-right", doc));
|
|
4920
|
+
el.addEventListener("click", opts.onClick);
|
|
4921
|
+
return el;
|
|
4922
|
+
}
|
|
4923
|
+
renderMain(doc) {
|
|
4924
|
+
const actions = doc.createElement("div");
|
|
4925
|
+
actions.className = "vela-md-actions";
|
|
4926
|
+
const action = (icon2, label, enabled, run) => {
|
|
4927
|
+
const b = doc.createElement("button");
|
|
4928
|
+
b.className = "vela-md-action";
|
|
4929
|
+
b.disabled = !enabled;
|
|
4930
|
+
b.appendChild(iconEl(icon2, doc));
|
|
4931
|
+
b.appendChild(doc.createTextNode(label));
|
|
4932
|
+
b.addEventListener("click", () => {
|
|
4933
|
+
run();
|
|
4934
|
+
this.drawer.hide();
|
|
4935
|
+
});
|
|
4936
|
+
actions.appendChild(b);
|
|
4937
|
+
};
|
|
4938
|
+
action("undo", "Undo", this.opts.canUndo(), this.opts.onUndo);
|
|
4939
|
+
action("redo", "Redo", this.opts.canRedo(), this.opts.onRedo);
|
|
4940
|
+
action("camera", "Screenshot", true, this.opts.onScreenshot);
|
|
4941
|
+
this.drawer.body.appendChild(actions);
|
|
4942
|
+
const list = doc.createElement("div");
|
|
4943
|
+
list.className = "vela-md-list";
|
|
4944
|
+
const current = this.opts.priceStyles().find((s) => s.id === this.opts.priceStyle());
|
|
4945
|
+
list.appendChild(this.row(doc, "Chart type", { icon: "style-line", value: current?.label, chevron: true, onClick: () => this.show("style") }));
|
|
4946
|
+
if (this.opts.layout) {
|
|
4947
|
+
const shape = this.opts.layout.shape();
|
|
4948
|
+
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
4949
|
+
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
4950
|
+
}
|
|
4951
|
+
for (const panel of this.opts.panels()) {
|
|
4952
|
+
list.appendChild(
|
|
4953
|
+
this.row(doc, panel.title, {
|
|
4954
|
+
icon: panel.icon,
|
|
4955
|
+
onClick: () => {
|
|
4956
|
+
this.opts.onTogglePanel(panel.id);
|
|
4957
|
+
this.drawer.hide();
|
|
4958
|
+
}
|
|
4959
|
+
})
|
|
4960
|
+
);
|
|
4961
|
+
}
|
|
4962
|
+
const alertCount = this.opts.alerts().length;
|
|
4963
|
+
list.appendChild(this.row(doc, "Alerts", { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
|
|
4964
|
+
for (const act of this.opts.actions()) {
|
|
4965
|
+
list.appendChild(
|
|
4966
|
+
this.row(doc, act.label, {
|
|
4967
|
+
icon: act.icon,
|
|
4968
|
+
onClick: () => {
|
|
4969
|
+
act.run();
|
|
4970
|
+
this.drawer.hide();
|
|
4971
|
+
}
|
|
4972
|
+
})
|
|
4973
|
+
);
|
|
4974
|
+
}
|
|
4975
|
+
this.drawer.body.appendChild(list);
|
|
4976
|
+
}
|
|
4977
|
+
renderStyle(doc) {
|
|
4978
|
+
const list = doc.createElement("div");
|
|
4979
|
+
list.className = "vela-md-list";
|
|
4980
|
+
const current = this.opts.priceStyle();
|
|
4981
|
+
for (const style of this.opts.priceStyles()) {
|
|
4982
|
+
list.appendChild(
|
|
4983
|
+
this.row(doc, style.label, {
|
|
4984
|
+
icon: style.icon,
|
|
4985
|
+
checked: style.id === current,
|
|
4986
|
+
onClick: () => {
|
|
4987
|
+
this.opts.onPriceStyle(style.id);
|
|
4988
|
+
this.drawer.hide();
|
|
4989
|
+
}
|
|
4990
|
+
})
|
|
4991
|
+
);
|
|
4992
|
+
}
|
|
4993
|
+
this.drawer.body.appendChild(list);
|
|
4994
|
+
}
|
|
4995
|
+
renderLayout(doc) {
|
|
4996
|
+
const layout = this.opts.layout;
|
|
4997
|
+
if (!layout) return;
|
|
4998
|
+
const list = doc.createElement("div");
|
|
4999
|
+
list.className = "vela-md-list";
|
|
5000
|
+
const wrap = doc.createElement("div");
|
|
5001
|
+
wrap.className = "vela-md-gridwrap";
|
|
5002
|
+
const { el: grid, squares } = layoutGridCanvas(doc);
|
|
5003
|
+
paintLayoutGrid(squares, layout.shape());
|
|
5004
|
+
grid.addEventListener("click", (e) => {
|
|
5005
|
+
const sq = e.target?.closest?.(".vela-lp-sq");
|
|
5006
|
+
if (!(sq instanceof HTMLButtonElement)) return;
|
|
5007
|
+
layout.onSelectGrid(Number(sq.dataset.r) + 1, Number(sq.dataset.c) + 1);
|
|
5008
|
+
this.drawer.hide();
|
|
5009
|
+
});
|
|
5010
|
+
wrap.appendChild(grid);
|
|
5011
|
+
list.appendChild(wrap);
|
|
5012
|
+
for (const preset of layout.presets()) {
|
|
5013
|
+
list.appendChild(
|
|
5014
|
+
this.row(doc, preset.label, {
|
|
5015
|
+
checked: preset.checked,
|
|
5016
|
+
onClick: () => {
|
|
5017
|
+
layout.onSelectPreset(preset.id);
|
|
5018
|
+
this.drawer.hide();
|
|
5019
|
+
}
|
|
5020
|
+
})
|
|
5021
|
+
);
|
|
5022
|
+
}
|
|
5023
|
+
const syncs = layout.syncs?.() ?? [];
|
|
5024
|
+
if (syncs.length > 0) {
|
|
5025
|
+
const heading = doc.createElement("div");
|
|
5026
|
+
heading.className = "vela-md-section";
|
|
5027
|
+
heading.textContent = "Sync";
|
|
5028
|
+
list.appendChild(heading);
|
|
5029
|
+
for (const s of syncs) {
|
|
5030
|
+
list.appendChild(
|
|
5031
|
+
this.row(doc, s.label, {
|
|
5032
|
+
checked: s.checked,
|
|
5033
|
+
onClick: () => {
|
|
5034
|
+
layout.onToggleSync?.(s.id);
|
|
5035
|
+
this.render();
|
|
5036
|
+
}
|
|
5037
|
+
})
|
|
5038
|
+
);
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
this.drawer.body.appendChild(list);
|
|
5042
|
+
}
|
|
5043
|
+
renderAlerts(doc) {
|
|
5044
|
+
const alerts = this.opts.alerts();
|
|
5045
|
+
if (alerts.length === 0) {
|
|
5046
|
+
const empty = doc.createElement("div");
|
|
5047
|
+
empty.className = "vela-md-empty";
|
|
5048
|
+
empty.textContent = "No alerts yet.";
|
|
5049
|
+
this.drawer.body.appendChild(empty);
|
|
5050
|
+
return;
|
|
5051
|
+
}
|
|
5052
|
+
const list = doc.createElement("div");
|
|
5053
|
+
list.className = "vela-md-list";
|
|
5054
|
+
for (const a of alerts) {
|
|
5055
|
+
list.appendChild(this.row(doc, `${a.title}: ${a.message}`, { value: new Date(a.time).toLocaleTimeString(), onClick: () => void 0 }));
|
|
5056
|
+
}
|
|
5057
|
+
this.drawer.body.appendChild(list);
|
|
5058
|
+
}
|
|
5059
|
+
};
|
|
5060
|
+
|
|
5061
|
+
// src/widget/timezone-drawer.ts
|
|
5062
|
+
var STYLE_ID16 = "vela-widget-timezone-drawer";
|
|
5063
|
+
var CSS16 = `
|
|
5064
|
+
.vela-tzd-list { padding: 2px 0 4px; }
|
|
5065
|
+
.vela-tzd-row {
|
|
5066
|
+
display: flex;
|
|
5067
|
+
align-items: center;
|
|
5068
|
+
gap: 12px;
|
|
5069
|
+
min-height: 46px;
|
|
5070
|
+
padding: 0 2px;
|
|
5071
|
+
border-radius: 8px;
|
|
5072
|
+
cursor: pointer;
|
|
5073
|
+
-webkit-tap-highlight-color: transparent;
|
|
5074
|
+
}
|
|
5075
|
+
.vela-tzd-row:active { background: var(--vela-hover); }
|
|
5076
|
+
.vela-tzd-row-label { flex: 1 1 auto; min-width: 0; font-size: 14px; color: var(--vela-fg-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
5077
|
+
.vela-tzd-row .vela-icon { flex: none; color: var(--vela-fg-bright); }
|
|
5078
|
+
`;
|
|
5079
|
+
var TimezoneDrawer = class {
|
|
5080
|
+
constructor(opts) {
|
|
5081
|
+
this.opts = opts;
|
|
5082
|
+
injectStyles(STYLE_ID16, CSS16, opts.host.ownerDocument);
|
|
5083
|
+
this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
|
|
5084
|
+
}
|
|
5085
|
+
open() {
|
|
5086
|
+
this.render();
|
|
5087
|
+
this.drawer.show();
|
|
5088
|
+
}
|
|
5089
|
+
close() {
|
|
5090
|
+
this.drawer.hide();
|
|
5091
|
+
}
|
|
5092
|
+
destroy() {
|
|
5093
|
+
this.drawer.destroy();
|
|
5094
|
+
}
|
|
5095
|
+
render() {
|
|
5096
|
+
const doc = this.drawer.body.ownerDocument;
|
|
5097
|
+
this.drawer.body.replaceChildren();
|
|
5098
|
+
const list = doc.createElement("div");
|
|
5099
|
+
list.className = "vela-tzd-list";
|
|
5100
|
+
const current = normalizeTimezone(this.opts.timezone());
|
|
5101
|
+
for (const tz of TIMEZONES) {
|
|
5102
|
+
const row = doc.createElement("div");
|
|
5103
|
+
row.className = "vela-tzd-row";
|
|
5104
|
+
const label = doc.createElement("span");
|
|
5105
|
+
label.className = "vela-tzd-row-label";
|
|
5106
|
+
label.textContent = tzMenuLabel(tz.value, tz.label);
|
|
5107
|
+
row.appendChild(label);
|
|
5108
|
+
if (tz.value === current) row.appendChild(iconEl("check", doc));
|
|
5109
|
+
row.addEventListener("click", () => {
|
|
5110
|
+
this.opts.onTimezone(tz.value);
|
|
5111
|
+
this.drawer.hide();
|
|
5112
|
+
});
|
|
5113
|
+
list.appendChild(row);
|
|
5114
|
+
}
|
|
5115
|
+
this.drawer.body.appendChild(list);
|
|
5116
|
+
}
|
|
5117
|
+
};
|
|
5118
|
+
|
|
5119
|
+
// src/widget/price-scale-drawer.ts
|
|
5120
|
+
var STYLE_ID17 = "vela-widget-price-scale-drawer";
|
|
5121
|
+
var CSS17 = `
|
|
5122
|
+
.vela-psd-list { padding: 2px 0 4px; }
|
|
5123
|
+
.vela-psd-row {
|
|
5124
|
+
display: flex;
|
|
5125
|
+
align-items: center;
|
|
5126
|
+
gap: 12px;
|
|
5127
|
+
min-height: 46px;
|
|
5128
|
+
padding: 0 2px;
|
|
5129
|
+
border-radius: 8px;
|
|
5130
|
+
cursor: pointer;
|
|
5131
|
+
-webkit-tap-highlight-color: transparent;
|
|
5132
|
+
}
|
|
5133
|
+
.vela-psd-row:active { background: var(--vela-hover); }
|
|
5134
|
+
.vela-psd-row[data-sep='1'] { margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--vela-border); border-radius: 0 0 8px 8px; }
|
|
5135
|
+
.vela-psd-row-label { flex: 1 1 auto; min-width: 0; font-size: 14px; color: var(--vela-fg-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
5136
|
+
.vela-psd-row .vela-icon { flex: none; color: var(--vela-fg-bright); }
|
|
5137
|
+
.vela-psd-section {
|
|
5138
|
+
padding: 12px 2px 4px;
|
|
5139
|
+
font-size: 12px;
|
|
5140
|
+
font-weight: 600;
|
|
5141
|
+
letter-spacing: 0.4px;
|
|
5142
|
+
text-transform: uppercase;
|
|
5143
|
+
color: var(--vela-fg-muted);
|
|
5144
|
+
}
|
|
5145
|
+
`;
|
|
5146
|
+
var PriceScaleDrawer = class {
|
|
5147
|
+
constructor(opts) {
|
|
5148
|
+
this.opts = opts;
|
|
5149
|
+
this.pane = null;
|
|
5150
|
+
injectStyles(STYLE_ID17, CSS17, opts.host.ownerDocument);
|
|
5151
|
+
this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
|
|
5152
|
+
}
|
|
5153
|
+
open() {
|
|
5154
|
+
const chart = this.opts.chart();
|
|
5155
|
+
const panes = chart?.renderer.get("paneScales");
|
|
5156
|
+
this.pane = Array.isArray(panes) ? paneScaleAt(panes, this.opts.pressY()) : null;
|
|
5157
|
+
this.render();
|
|
5158
|
+
this.drawer.show();
|
|
5159
|
+
}
|
|
5160
|
+
close() {
|
|
5161
|
+
this.drawer.hide();
|
|
5162
|
+
}
|
|
5163
|
+
destroy() {
|
|
5164
|
+
this.drawer.destroy();
|
|
5165
|
+
}
|
|
5166
|
+
flag(feature) {
|
|
5167
|
+
return Boolean(this.opts.chart()?.renderer.get(feature));
|
|
5168
|
+
}
|
|
5169
|
+
render() {
|
|
5170
|
+
const chart = this.opts.chart();
|
|
5171
|
+
const doc = this.drawer.body.ownerDocument;
|
|
5172
|
+
this.drawer.body.replaceChildren();
|
|
5173
|
+
if (!chart) return;
|
|
5174
|
+
const pane = this.pane;
|
|
5175
|
+
const items = priceAxisItems({
|
|
5176
|
+
auto: chart.renderer.get("autoScale") !== false,
|
|
5177
|
+
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
5178
|
+
choice: scaleChoiceOf(pane ?? { mode: String(chart.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
5179
|
+
axisLabels: this.flag("axisLabels"),
|
|
5180
|
+
priceLabel: this.flag("priceLabel"),
|
|
5181
|
+
countdown: this.flag("countdown"),
|
|
5182
|
+
priceLine: this.flag("currentPriceLine")
|
|
5183
|
+
});
|
|
5184
|
+
const list = doc.createElement("div");
|
|
5185
|
+
list.className = "vela-psd-list";
|
|
5186
|
+
const row = (label, opts) => {
|
|
5187
|
+
const el = doc.createElement("div");
|
|
5188
|
+
el.className = "vela-psd-row";
|
|
5189
|
+
if (opts.sep) el.dataset.sep = "1";
|
|
5190
|
+
const text = doc.createElement("span");
|
|
5191
|
+
text.className = "vela-psd-row-label";
|
|
5192
|
+
text.textContent = label;
|
|
5193
|
+
el.appendChild(text);
|
|
5194
|
+
if (opts.checked) el.appendChild(iconEl("check", doc));
|
|
5195
|
+
el.addEventListener("click", opts.onClick);
|
|
5196
|
+
list.appendChild(el);
|
|
5197
|
+
};
|
|
5198
|
+
const section = (label) => {
|
|
5199
|
+
const el = doc.createElement("div");
|
|
5200
|
+
el.className = "vela-psd-section";
|
|
5201
|
+
el.textContent = label;
|
|
5202
|
+
list.appendChild(el);
|
|
5203
|
+
};
|
|
5204
|
+
for (const item of items) {
|
|
5205
|
+
if (item.id === "labels" && item.submenu) {
|
|
5206
|
+
section("Labels");
|
|
5207
|
+
for (const sub of item.submenu) {
|
|
5208
|
+
row(sub.label, {
|
|
5209
|
+
checked: sub.checked,
|
|
5210
|
+
onClick: () => {
|
|
5211
|
+
const feature = sub.id.slice("toggle:".length);
|
|
5212
|
+
chart.renderer.set(feature, !this.flag(feature));
|
|
5213
|
+
this.render();
|
|
5214
|
+
}
|
|
5215
|
+
});
|
|
5216
|
+
}
|
|
5217
|
+
continue;
|
|
5218
|
+
}
|
|
5219
|
+
if (item.id === "levels" && item.submenu) {
|
|
5220
|
+
section("Levels");
|
|
5221
|
+
for (const sub of item.submenu) {
|
|
5222
|
+
row(sub.label, {
|
|
5223
|
+
checked: sub.checked,
|
|
5224
|
+
onClick: () => {
|
|
5225
|
+
const feature = sub.id.slice("toggle:".length);
|
|
5226
|
+
chart.renderer.set(feature, !this.flag(feature));
|
|
5227
|
+
this.render();
|
|
5228
|
+
}
|
|
5229
|
+
});
|
|
5230
|
+
}
|
|
5231
|
+
continue;
|
|
5232
|
+
}
|
|
5233
|
+
if (item.id.startsWith("settings")) {
|
|
5234
|
+
row(item.label, {
|
|
5235
|
+
sep: true,
|
|
5236
|
+
onClick: () => {
|
|
5237
|
+
chart.renderer.openSettings(settingsSectionOf(item.id));
|
|
5238
|
+
this.drawer.hide();
|
|
5239
|
+
}
|
|
5240
|
+
});
|
|
5241
|
+
continue;
|
|
5242
|
+
}
|
|
5243
|
+
if (item.id === "auto") {
|
|
5244
|
+
row(item.label, {
|
|
5245
|
+
checked: item.checked,
|
|
5246
|
+
onClick: () => {
|
|
5247
|
+
chart.renderer.set("autoScale", chart.renderer.get("autoScale") === false);
|
|
5248
|
+
this.render();
|
|
5249
|
+
}
|
|
5250
|
+
});
|
|
5251
|
+
continue;
|
|
5252
|
+
}
|
|
5253
|
+
if (item.id === "invert") {
|
|
5254
|
+
row(item.label, {
|
|
5255
|
+
checked: item.checked,
|
|
5256
|
+
onClick: () => {
|
|
5257
|
+
const [feature, value] = invertWrite(!(pane ? pane.invert : this.flag("invertScale")), pane);
|
|
5258
|
+
chart.renderer.set(feature, value);
|
|
5259
|
+
const panes = chart.renderer.get("paneScales");
|
|
5260
|
+
this.pane = Array.isArray(panes) ? paneScaleAt(panes, this.opts.pressY()) : pane;
|
|
5261
|
+
this.render();
|
|
5262
|
+
}
|
|
5263
|
+
});
|
|
5264
|
+
continue;
|
|
5265
|
+
}
|
|
5266
|
+
if (item.id.startsWith("scale:")) {
|
|
5267
|
+
row(item.label, {
|
|
5268
|
+
checked: item.checked,
|
|
5269
|
+
sep: item.separatorBefore,
|
|
5270
|
+
onClick: () => {
|
|
5271
|
+
for (const [feature, value] of scaleWrites(item.id.slice("scale:".length), pane)) {
|
|
5272
|
+
chart.renderer.set(feature, value);
|
|
5273
|
+
}
|
|
5274
|
+
const panes = chart.renderer.get("paneScales");
|
|
5275
|
+
this.pane = Array.isArray(panes) ? paneScaleAt(panes, this.opts.pressY()) : pane;
|
|
5276
|
+
this.render();
|
|
5277
|
+
}
|
|
5278
|
+
});
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
this.drawer.body.appendChild(list);
|
|
5282
|
+
}
|
|
5283
|
+
};
|
|
5284
|
+
|
|
5285
|
+
// src/widget/drawing-pill.ts
|
|
5286
|
+
var STYLE_ID18 = "vela-widget-drawing-pill";
|
|
5287
|
+
var CSS18 = `
|
|
5288
|
+
.vela-drawpill { display: none; }
|
|
5289
|
+
[data-layout='mobile'] .vela-drawpill {
|
|
5290
|
+
position: absolute;
|
|
5291
|
+
left: 50%;
|
|
5292
|
+
bottom: 10px;
|
|
5293
|
+
transform: translateX(-50%);
|
|
5294
|
+
z-index: 7;
|
|
5295
|
+
display: flex;
|
|
5296
|
+
align-items: center;
|
|
5297
|
+
gap: 2px;
|
|
5298
|
+
padding: 4px;
|
|
5299
|
+
border: 1px solid var(--vela-border-strong);
|
|
5300
|
+
border-radius: 999px;
|
|
5301
|
+
background: var(--vela-surface);
|
|
5302
|
+
box-shadow: var(--vela-shadow-dialog);
|
|
5303
|
+
color: var(--vela-fg);
|
|
5304
|
+
}
|
|
5305
|
+
[data-layout='mobile'] .vela-drawpill[hidden] { display: none !important; }
|
|
5306
|
+
.vela-drawpill-tool {
|
|
5307
|
+
display: inline-flex;
|
|
5308
|
+
align-items: center;
|
|
5309
|
+
justify-content: center;
|
|
5310
|
+
width: 36px;
|
|
5311
|
+
height: 36px;
|
|
5312
|
+
border-radius: 999px;
|
|
5313
|
+
background: var(--vela-hover);
|
|
5314
|
+
color: var(--vela-accent);
|
|
5315
|
+
}
|
|
5316
|
+
.vela-drawpill-tool svg { width: 22px; height: 22px; }
|
|
5317
|
+
.vela-drawpill-btn {
|
|
5318
|
+
all: unset;
|
|
5319
|
+
width: 36px;
|
|
5320
|
+
height: 36px;
|
|
5321
|
+
display: inline-flex;
|
|
5322
|
+
align-items: center;
|
|
5323
|
+
justify-content: center;
|
|
5324
|
+
border-radius: 999px;
|
|
5325
|
+
color: var(--vela-fg-muted);
|
|
5326
|
+
cursor: pointer;
|
|
5327
|
+
-webkit-tap-highlight-color: transparent;
|
|
5328
|
+
position: relative;
|
|
5329
|
+
}
|
|
5330
|
+
.vela-drawpill-btn:active { background: var(--vela-hover); }
|
|
5331
|
+
.vela-drawpill-btn[data-on='1'] { color: var(--vela-accent); background: var(--vela-hover); }
|
|
5332
|
+
.vela-drawpill-btn .vela-icon { font-size: 17px; width: 17px; height: 17px; }
|
|
5333
|
+
.vela-drawpill-badge {
|
|
5334
|
+
position: absolute;
|
|
5335
|
+
top: 2px;
|
|
5336
|
+
right: 2px;
|
|
5337
|
+
font-size: 8px;
|
|
5338
|
+
font-weight: 700;
|
|
5339
|
+
color: var(--vela-accent);
|
|
5340
|
+
}
|
|
5341
|
+
`;
|
|
5342
|
+
var DrawingPill = class {
|
|
5343
|
+
constructor(host) {
|
|
5344
|
+
this.chart = null;
|
|
5345
|
+
this.chartSubs = [];
|
|
5346
|
+
const doc = host.ownerDocument;
|
|
5347
|
+
injectStyles(STYLE_ID18, CSS18, doc);
|
|
5348
|
+
this.el = doc.createElement("div");
|
|
5349
|
+
this.el.className = "vela-drawpill";
|
|
5350
|
+
this.el.hidden = true;
|
|
5351
|
+
this.toolGlyph = doc.createElement("span");
|
|
5352
|
+
this.toolGlyph.className = "vela-drawpill-tool";
|
|
5353
|
+
const btn = (icon2, label, onClick) => {
|
|
5354
|
+
const b = doc.createElement("button");
|
|
5355
|
+
b.className = "vela-drawpill-btn";
|
|
5356
|
+
b.setAttribute("aria-label", label);
|
|
5357
|
+
b.appendChild(iconEl(icon2, doc));
|
|
5358
|
+
b.addEventListener("click", onClick);
|
|
5359
|
+
return b;
|
|
5360
|
+
};
|
|
5361
|
+
this.magnetBtn = btn("magnet", "Magnet snap", () => {
|
|
5362
|
+
const order = ["off", "weak", "strong"];
|
|
5363
|
+
const cur = this.chart?.drawings.getSnapMode() ?? "off";
|
|
5364
|
+
this.chart?.drawings.setSnapMode(order[(order.indexOf(cur) + 1) % order.length]);
|
|
5365
|
+
});
|
|
5366
|
+
this.magnetBadge = doc.createElement("span");
|
|
5367
|
+
this.magnetBadge.className = "vela-drawpill-badge";
|
|
5368
|
+
this.magnetBtn.appendChild(this.magnetBadge);
|
|
5369
|
+
this.stayBtn = btn(iconMarkup("pen-lock") ? "pen-lock" : "pen", "Stay in drawing mode", () => {
|
|
5370
|
+
this.chart?.drawings.setStayMode(!this.chart.drawings.getStayMode());
|
|
5371
|
+
});
|
|
5372
|
+
this.eraserBtn = btn("eraser", "Eraser", () => {
|
|
5373
|
+
this.chart?.drawings.setMode(this.chart.drawings.getMode() === "eraser" ? null : "eraser");
|
|
5374
|
+
});
|
|
5375
|
+
const close = btn("close", "Exit drawing mode", () => {
|
|
5376
|
+
this.chart?.drawings.setTool(null);
|
|
5377
|
+
this.chart?.drawings.setMode(null);
|
|
5378
|
+
});
|
|
5379
|
+
this.el.append(this.toolGlyph, this.magnetBtn, this.stayBtn, this.eraserBtn, close);
|
|
5380
|
+
host.appendChild(this.el);
|
|
5381
|
+
}
|
|
5382
|
+
/** Rebind to another chart (a rebuild, or the workspace's active cell changing) —
|
|
5383
|
+
* the previous chart's subscriptions are dropped so only one chart drives the pill. */
|
|
5384
|
+
onChart(chart) {
|
|
5385
|
+
for (const off of this.chartSubs) off();
|
|
5386
|
+
this.chart = chart;
|
|
5387
|
+
this.chartSubs = [
|
|
5388
|
+
chart.on("drawing:tool", () => this.sync()),
|
|
5389
|
+
chart.on("drawing:snap", () => this.sync()),
|
|
5390
|
+
chart.on("drawing:stay", () => this.sync()),
|
|
5391
|
+
chart.on("drawing:mode", () => this.sync())
|
|
5392
|
+
];
|
|
5393
|
+
this.sync();
|
|
5394
|
+
}
|
|
5395
|
+
sync() {
|
|
5396
|
+
const drawings = this.chart?.drawings;
|
|
5397
|
+
if (!drawings || !drawings.supported) {
|
|
5398
|
+
this.el.hidden = true;
|
|
5399
|
+
return;
|
|
5400
|
+
}
|
|
5401
|
+
const tool = drawings.getTool();
|
|
5402
|
+
const eraser = drawings.getMode() === "eraser";
|
|
5403
|
+
this.el.hidden = tool === null && !eraser;
|
|
5404
|
+
if (this.el.hidden) return;
|
|
5405
|
+
this.toolGlyph.innerHTML = tool !== null ? getDrawingType(tool)?.icon ?? "" : iconMarkup("eraser") ?? "";
|
|
5406
|
+
const snap = drawings.getSnapMode();
|
|
5407
|
+
this.magnetBtn.dataset.on = snap !== "off" ? "1" : "";
|
|
5408
|
+
this.magnetBadge.textContent = snap === "weak" ? "W" : snap === "strong" ? "S" : "";
|
|
5409
|
+
this.stayBtn.dataset.on = drawings.getStayMode() ? "1" : "";
|
|
5410
|
+
this.eraserBtn.dataset.on = eraser ? "1" : "";
|
|
5411
|
+
}
|
|
5412
|
+
destroy() {
|
|
5413
|
+
for (const off of this.chartSubs) off();
|
|
5414
|
+
this.chartSubs = [];
|
|
5415
|
+
this.el.remove();
|
|
5416
|
+
}
|
|
5417
|
+
};
|
|
5418
|
+
|
|
4104
5419
|
// src/widget/glide.ts
|
|
4105
5420
|
var MIN_SPAN_MS = 6e4;
|
|
4106
5421
|
var FOLLOW = 0.25;
|
|
@@ -4262,8 +5577,8 @@ var WidgetHistory = class {
|
|
|
4262
5577
|
};
|
|
4263
5578
|
|
|
4264
5579
|
// src/widget/toast.ts
|
|
4265
|
-
var
|
|
4266
|
-
var
|
|
5580
|
+
var STYLE_ID19 = "vela-widget-toast";
|
|
5581
|
+
var CSS19 = `
|
|
4267
5582
|
.vela-toast {
|
|
4268
5583
|
position: absolute;
|
|
4269
5584
|
left: 50%;
|
|
@@ -4293,7 +5608,7 @@ var CSS12 = `
|
|
|
4293
5608
|
var Toast = class {
|
|
4294
5609
|
constructor(host) {
|
|
4295
5610
|
this.timer = null;
|
|
4296
|
-
injectStyles(
|
|
5611
|
+
injectStyles(STYLE_ID19, CSS19, host.ownerDocument);
|
|
4297
5612
|
this.el = host.ownerDocument.createElement("div");
|
|
4298
5613
|
this.el.className = "vela-toast";
|
|
4299
5614
|
host.appendChild(this.el);
|
|
@@ -4317,4 +5632,4 @@ var Toast = class {
|
|
|
4317
5632
|
}
|
|
4318
5633
|
};
|
|
4319
5634
|
|
|
4320
|
-
export { Bottombar, ChartContextMenu, DataWindow, Glider, IndicatorPicker, ObjectTree, PAN_FAST, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Toast, Topbar, Watermark, WidgetHistory, ZOOM_IN, ZOOM_OUT, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, indicatorLedger, legacyWidgetState, loadPersisted, localStorageAdapter, parsePersisted, parseTimeframe, prefixedSymbol, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, statuslineInkOf, timeframeLabel, timeframeMs, toolShortcutHints };
|
|
5635
|
+
export { Bottombar, ChartContextMenu, DataWindow, DrawingPill, DrawingsDrawer, Glider, IndicatorPicker, LayoutModeController, MobileBar, MoreDrawer, ObjectTree, PAN_FAST, PanelDock, PriceScaleDrawer, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeDrawer, TimeframeQuick, TimezoneDrawer, Toast, Topbar, Watermark, WidgetHistory, ZOOM_IN, ZOOM_OUT, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, indicatorLedger, legacyWidgetState, loadPersisted, localStorageAdapter, parsePersisted, parseTimeframe, prefixedSymbol, priceStyleIcon, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, statuslineInkOf, timeframeLabel, timeframeMs, toolShortcutHints };
|