@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.
Files changed (45) hide show
  1. package/dist/{DataProvider-DGesjOJT.d.ts → DataProvider-DTOEXKTI.d.ts} +1 -1
  2. package/dist/{DataProvider-BBQ3Hr21.d.cts → DataProvider-gnzv-dgc.d.cts} +1 -1
  3. package/dist/{chunk-ULIX6AE2.js → chunk-ATPU5CI6.js} +1 -1
  4. package/dist/{chunk-S6F6SQ2L.js → chunk-B5TIKQJ5.js} +60 -7
  5. package/dist/{chunk-EP6NDJYK.js → chunk-LJLZR44Y.js} +4 -2
  6. package/dist/{chunk-AIW3EJUE.js → chunk-P556H6EA.js} +329 -10
  7. package/dist/{chunk-FNOMKHEZ.js → chunk-RRFGWZNJ.js} +1321 -363
  8. package/dist/{chunk-5SE4PL3H.js → chunk-Y7TK4ZWK.js} +1363 -48
  9. package/dist/{contributions-BlV3kb0O.d.cts → contributions-Ci_i3IN2.d.cts} +33 -2
  10. package/dist/{contributions-6N0rbrPC.d.ts → contributions-b6AVrqqG.d.ts} +33 -2
  11. package/dist/{history-DX3Zne3a.d.cts → history-Cf1lVsap.d.cts} +15 -3
  12. package/dist/{history-BTd52YMA.d.ts → history-Ci3M0xLV.d.ts} +15 -3
  13. package/dist/index.cjs +1374 -366
  14. package/dist/index.d.cts +52 -19
  15. package/dist/index.d.ts +52 -19
  16. package/dist/index.js +4 -4
  17. package/dist/{options-DbQholFV.d.cts → options-XTBpbx0s.d.cts} +47 -1
  18. package/dist/{options-DbQholFV.d.ts → options-XTBpbx0s.d.ts} +47 -1
  19. package/dist/{plugin-TN3n-9YN.d.ts → plugin-CnNd7cuF.d.ts} +172 -25
  20. package/dist/{plugin-O34MbXf-.d.cts → plugin-DfH4Y-Om.d.cts} +172 -25
  21. package/dist/plugin.cjs +53 -7
  22. package/dist/plugin.d.cts +4 -4
  23. package/dist/plugin.d.ts +4 -4
  24. package/dist/plugin.js +2 -2
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/ui.cjs +332 -9
  32. package/dist/ui.d.cts +64 -2
  33. package/dist/ui.d.ts +64 -2
  34. package/dist/ui.js +3 -3
  35. package/dist/vela.global.js +1374 -366
  36. package/dist/vela.global.min.js +84 -34
  37. package/dist/widget.cjs +3348 -550
  38. package/dist/widget.d.cts +65 -7
  39. package/dist/widget.d.ts +65 -7
  40. package/dist/widget.js +169 -18
  41. package/dist/workspace.cjs +4251 -1400
  42. package/dist/workspace.d.cts +45 -9
  43. package/dist/workspace.d.ts +45 -9
  44. package/dist/workspace.js +226 -22
  45. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
- import { parseSymbol, priceStyleIds, tzButtonLabel, TIMEZONES, normalizeTimezone, tzMenuLabel } from './chunk-FNOMKHEZ.js';
2
- import { chartType, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, getDrawingType } from './chunk-S6F6SQ2L.js';
3
- import { Tooltip, Menu, Dialog } from './chunk-AIW3EJUE.js';
4
- import { injectStyles, iconEl, iconMarkup, SESSION_PRE, SESSION_POST, SESSION_OFF, icon, registerIcon, categoricalColor } from './chunk-EP6NDJYK.js';
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.createElement("div");
239
- grid.className = "vela-lp-grid";
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
- const preview = this.hover ?? this.opts.shape();
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
- const indicatorsBtn = doc.createElement("button");
558
- indicatorsBtn.className = "vela-widget-indicators";
559
- indicatorsBtn.append(iconEl("indicators", doc), doc.createTextNode("Indicators"));
560
- if (opts.onIndicatorsClick) indicatorsBtn.addEventListener("click", opts.onIndicatorsClick);
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
- this.el.append(...leading, indicatorsBtn, sep(), this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
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.symbolEl.style.display = this.parts.name ? "" : "none";
1020
- this.marketEl.style.display = this.parts.market ? "" : "none";
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 = 72;
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.el.style.display = visible ? "" : "none";
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 STYLE_ID12 = "vela-widget-toast";
4266
- var CSS12 = `
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(STYLE_ID12, CSS12, host.ownerDocument);
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 };