@luxalgo/vela 0.5.1 → 0.5.3

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-5SE4PL3H.js → chunk-EY63XBFZ.js} +1370 -55
  5. package/dist/{chunk-FNOMKHEZ.js → chunk-L5NTMWPP.js} +1383 -376
  6. package/dist/{chunk-EP6NDJYK.js → chunk-LJLZR44Y.js} +4 -2
  7. package/dist/{chunk-AIW3EJUE.js → chunk-P556H6EA.js} +329 -10
  8. package/dist/{chunk-S6F6SQ2L.js → chunk-WJKLBYRM.js} +74 -7
  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 +1450 -379
  14. package/dist/index.d.cts +60 -21
  15. package/dist/index.d.ts +60 -21
  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-DP6A4t4o.d.ts} +177 -29
  20. package/dist/{plugin-O34MbXf-.d.cts → plugin-DlidwMj6.d.cts} +177 -29
  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 +1450 -379
  36. package/dist/vela.global.min.js +84 -34
  37. package/dist/widget.cjs +3430 -569
  38. package/dist/widget.d.cts +66 -8
  39. package/dist/widget.d.ts +66 -8
  40. package/dist/widget.js +169 -18
  41. package/dist/workspace.cjs +4332 -1418
  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
@@ -106,14 +106,15 @@ registerIcon(
106
106
  svg24('<path d="m10 20-1.25-2.5L6 16"/><path d="M10 4 8.75 6.5 6 8"/><path d="m14 20 1.25-2.5L18 16"/><path d="m14 4 1.25 2.5L18 8"/><path d="m17 10-5 5-5-5"/><path d="M4 12h16"/>')
107
107
  );
108
108
  registerIcon("chevron-right", S('<path d="m6 3.5 4.5 4.5L6 12.5"/>'));
109
+ registerIcon("chevron-left", S('<path d="M10 3.5 5.5 8l4.5 4.5"/>'));
109
110
  registerIcon("chevron-down", S('<path d="M3.5 6 8 10.5 12.5 6"/>'));
110
111
  registerIcon("chevron-up", S('<path d="M3.5 10 8 5.5 12.5 10"/>'));
111
112
  registerIcon("chevrons-right", S('<path d="m4 3.5 4.5 4.5L4 12.5"/><path d="m8.5 3.5 4.5 4.5-4.5 4.5"/>'));
112
113
  registerIcon("chevrons-left", S('<path d="M12 3.5 7.5 8l4.5 4.5"/><path d="M7.5 3.5 3 8l4.5 4.5"/>'));
113
114
  registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
114
115
  registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
115
- registerIcon("eye", S('<path d="M1.5 8s2.5-4.2 6.5-4.2S14.5 8 14.5 8 12 12.2 8 12.2 1.5 8 1.5 8z"/><circle cx="8" cy="8" r="1.8"/>'));
116
- registerIcon("eye-off", S('<path d="M1.5 8s2.5-4.2 6.5-4.2S14.5 8 14.5 8 12 12.2 8 12.2 1.5 8 1.5 8z" opacity="0.45"/><path d="m3 13 10-10"/>'));
116
+ registerIcon("eye", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z"/><circle cx="8" cy="8" r="1.8"/>'));
117
+ registerIcon("eye-off", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z" opacity="0.45"/><path d="m3 13 10-10"/>'));
117
118
  registerIcon("lock", S('<rect x="3.2" y="7" width="9.6" height="6.6" rx="1.2"/><path d="M5.6 7V5.2a2.4 2.4 0 0 1 4.8 0V7"/>'));
118
119
  registerIcon("unlock", S('<rect x="3.2" y="7" width="9.6" height="6.6" rx="1.2"/><path d="M5.6 7V5.2a2.4 2.4 0 0 1 4.7-.7"/>'));
119
120
  registerIcon("trash", S('<path d="M2.5 4.3h11M6.2 4.3V3.1a1 1 0 0 1 1-1h1.6a1 1 0 0 1 1 1v1.2M4.2 4.3l.5 9.1a1.15 1.15 0 0 0 1.15 1.1h4.3a1.15 1.15 0 0 0 1.15-1.1l.5-9.1M6.5 7v4.5M9.5 7v4.5"/>'));
@@ -136,6 +137,7 @@ registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l
136
137
  registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
137
138
  registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
138
139
  registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
140
+ registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
139
141
  registerIcon("reset", S('<rect x="6" y="6" width="4" height="4" rx="0.8"/><path d="M2.2 8a5.8 5.8 0 1 0 1.9-4.3L2.2 5.3"/><path d="M2.2 2.2v3.1h3.1"/>'));
140
142
  registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
141
143
  registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
@@ -1,5 +1,5 @@
1
- import { STATIC_TOKENS, themeTokens } from './chunk-ULIX6AE2.js';
2
- import { injectStyles, iconEl } from './chunk-EP6NDJYK.js';
1
+ import { STATIC_TOKENS, themeTokens } from './chunk-ATPU5CI6.js';
2
+ import { injectStyles, iconEl } from './chunk-LJLZR44Y.js';
3
3
  import { VanillaMachine, normalizeProps, spreadProps } from '@zag-js/vanilla';
4
4
  export { normalizeProps, spreadProps } from '@zag-js/vanilla';
5
5
  import * as tooltip from '@zag-js/tooltip';
@@ -33,8 +33,8 @@ function ensureUIHost(el, theme) {
33
33
  el.classList.add("vela-ui");
34
34
  if (theme) applyThemeTokens(el, theme);
35
35
  }
36
- function runMachine(machine4, props, render) {
37
- const m = new VanillaMachine(machine4, props);
36
+ function runMachine(machine5, props, render) {
37
+ const m = new VanillaMachine(machine5, props);
38
38
  const unsub = m.subscribe(render);
39
39
  m.start();
40
40
  render(m.service);
@@ -322,10 +322,13 @@ var MENU_CSS = `
322
322
  .vela-menu-item .vela-icon { width: 16px; height: 16px; font-size: 16px; justify-content: center; color: var(--vela-fg-muted); }
323
323
  .vela-menu-item .vela-menu-label { flex: 1 1 auto; }
324
324
  .vela-menu-item .vela-menu-hint { color: var(--vela-fg-faint); font-size: var(--vela-font-size-sm); }
325
- /* Active entry: a brighter row surface + bright ink \u2014 no accent recolor, no glyph.
326
- Declared after [data-highlighted] so the selected surface wins while hovered. */
327
- .vela-menu-item[data-checked] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
328
- .vela-menu-item[data-checked] .vela-icon { color: var(--vela-fg-bright); }
325
+ /* Active entry: the row surface carries the selection \u2014 a stronger background wash
326
+ than the hover one, plus bright ink. Declared after the hover rule so a selected
327
+ row stays visibly selected while highlighted. */
328
+ .vela-menu-item[data-checked] {
329
+ background: var(--vela-hover-strong);
330
+ color: var(--vela-fg-bright);
331
+ }
329
332
  /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
330
333
  same control language as the settings dialog's toggles. */
331
334
  .vela-menu-switch {
@@ -543,6 +546,7 @@ function dialogController(opts = {}) {
543
546
  modal: opts.modal ?? true,
544
547
  closeOnEscape: opts.closeOnEscape ?? true,
545
548
  closeOnInteractOutside: opts.closeOnInteractOutside ?? false,
549
+ initialFocusEl: opts.initialFocusEl,
546
550
  onOpenChange: (d) => opts.onOpenChange?.(d.open)
547
551
  },
548
552
  connect: (service) => dialog.connect(service, normalizeProps)
@@ -619,6 +623,29 @@ var DIALOG_CSS = `
619
623
  border: 2px solid transparent;
620
624
  background-clip: padding-box;
621
625
  }
626
+ /* \u2500\u2500 mobile chrome: dialogs fill the shell \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
627
+ Inside a shell in the mobile size class ([data-layout='mobile'] on the widget root,
628
+ which is position:relative) every kit dialog presents fullscreen: desktop cards are
629
+ unusable at phone widths, and the shell's bounds \u2014 not the viewport \u2014 are the honest
630
+ "screen" for an embedded chart. */
631
+ [data-layout='mobile'] .vela-dialog-backdrop { position: absolute; }
632
+ [data-layout='mobile'] .vela-dialog-positioner {
633
+ position: absolute;
634
+ padding: 0;
635
+ align-items: stretch;
636
+ }
637
+ [data-layout='mobile'] .vela-dialog {
638
+ width: 100%;
639
+ min-width: 0;
640
+ max-width: none;
641
+ max-height: none;
642
+ flex: 1 1 auto;
643
+ border: none;
644
+ border-radius: 0;
645
+ transform: none !important; /* a desktop drag offset must not survive the flip */
646
+ }
647
+ [data-layout='mobile'] .vela-dialog-close { width: 40px; height: 40px; }
648
+ [data-layout='mobile'] .vela-dialog-body { padding-bottom: calc(var(--vela-space-4) + env(safe-area-inset-bottom, 0px)); }
622
649
  `;
623
650
  var Dialog = class {
624
651
  constructor(opts = {}) {
@@ -632,6 +659,7 @@ var Dialog = class {
632
659
  this.positioner.className = "vela-dialog-positioner vela-ui-layer";
633
660
  this.panel = doc.createElement("div");
634
661
  this.panel.className = "vela-dialog";
662
+ this.panel.tabIndex = -1;
635
663
  const header = doc.createElement("div");
636
664
  header.className = "vela-dialog-header";
637
665
  if (opts.draggable) {
@@ -666,7 +694,18 @@ var Dialog = class {
666
694
  this.panel.append(header, this.body);
667
695
  this.positioner.appendChild(this.panel);
668
696
  host.append(this.backdrop, this.positioner);
669
- this.ctrl = dialogController(opts);
697
+ this.ctrl = dialogController({
698
+ ...opts,
699
+ // Mobile chrome (fullscreen presentation): opening must never land focus on
700
+ // an input — that pops the on-screen keyboard over the just-opened dialog.
701
+ // Focus goes to the panel; a caller that WANTS the keyboard focuses its
702
+ // input explicitly. Desktop keeps the caller's pick, else the machine's
703
+ // first-tabbable default.
704
+ initialFocusEl: () => {
705
+ if (this.positioner.closest('[data-layout="mobile"]')) return this.panel;
706
+ return opts.initialFocusEl?.() ?? null;
707
+ }
708
+ });
670
709
  const mid = String(this.ctrl.props.id);
671
710
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
672
711
  const api = this.ctrl.connect(service);
@@ -694,5 +733,285 @@ var Dialog = class {
694
733
  this.positioner.remove();
695
734
  }
696
735
  };
736
+ function drawerController(opts = {}) {
737
+ return {
738
+ machine: dialog.machine,
739
+ props: {
740
+ id: nextUid("vela-drawer"),
741
+ modal: true,
742
+ closeOnEscape: opts.closeOnEscape ?? true,
743
+ closeOnInteractOutside: opts.closeOnInteractOutside ?? true,
744
+ initialFocusEl: opts.initialFocusEl,
745
+ onOpenChange: (d) => opts.onOpenChange?.(d.open)
746
+ },
747
+ connect: (service) => dialog.connect(service, normalizeProps)
748
+ };
749
+ }
750
+
751
+ // src/ui/components/drawer/styles.ts
752
+ var DRAWER_STYLE_ID = "vela-ui-drawer";
753
+ var DRAWER_CSS = `
754
+ .vela-drawer-backdrop {
755
+ position: fixed;
756
+ inset: 0;
757
+ background: var(--vela-backdrop);
758
+ z-index: var(--vela-z-dialog);
759
+ }
760
+ .vela-drawer-positioner {
761
+ position: fixed;
762
+ inset: 0;
763
+ display: flex;
764
+ align-items: flex-end;
765
+ justify-content: center;
766
+ z-index: var(--vela-z-dialog);
767
+ }
768
+ /* Inside a shell that declares a size class, the sheet scopes to the SHELL's bounds
769
+ (the widget root is position:relative) instead of the whole viewport \u2014 an embedded
770
+ chart must not curtain the host page. */
771
+ [data-layout] .vela-drawer-backdrop, [data-layout] .vela-drawer-positioner { position: absolute; }
772
+ .vela-drawer {
773
+ background: var(--vela-surface);
774
+ color: var(--vela-fg);
775
+ border: 1px solid var(--vela-border-strong);
776
+ border-bottom: none;
777
+ border-radius: 14px 14px 0 0;
778
+ box-shadow: var(--vela-shadow-dialog);
779
+ font-size: 13px;
780
+ width: 100%;
781
+ max-height: 85%;
782
+ display: flex;
783
+ flex-direction: column;
784
+ overflow: hidden;
785
+ outline: none;
786
+ }
787
+ .vela-drawer[data-state='open'] { animation: vela-drawer-in var(--vela-dur-med) var(--vela-ease); }
788
+ @keyframes vela-drawer-in {
789
+ from { transform: translateY(100%); }
790
+ to { transform: translateY(0); }
791
+ }
792
+ /* The grab zone owns its touches (drag-to-dismiss), so the browser must not scroll it. */
793
+ .vela-drawer-grab {
794
+ flex: none;
795
+ display: flex;
796
+ align-items: center;
797
+ justify-content: center;
798
+ padding: 10px 0 6px;
799
+ cursor: grab;
800
+ touch-action: none;
801
+ user-select: none;
802
+ }
803
+ .vela-drawer-grab::before {
804
+ content: '';
805
+ width: 36px;
806
+ height: 4px;
807
+ border-radius: 2px;
808
+ background: var(--vela-border-strong);
809
+ }
810
+ .vela-drawer-title {
811
+ flex: none;
812
+ padding: 0 16px 10px;
813
+ font-size: 15px;
814
+ font-weight: 600;
815
+ letter-spacing: 0.2px;
816
+ color: var(--vela-fg-bright);
817
+ user-select: none;
818
+ }
819
+ .vela-drawer-title:empty { display: none; }
820
+ .vela-drawer-body {
821
+ flex: 1 1 auto;
822
+ min-height: 0;
823
+ overflow-y: auto;
824
+ overscroll-behavior: contain;
825
+ -webkit-overflow-scrolling: touch;
826
+ /* Vertical pans stay native scrolling; horizontal moves reach the sheet's gesture
827
+ recognizer as pointer events (tab swipes). Without this the browser claims a
828
+ sideways touch as a scroll attempt and CANCELS the pointer stream, so swipes
829
+ never registered on real touch devices. Sideways-scrolling strips inside the
830
+ body opt back in with their own touch-action: pan-x. */
831
+ touch-action: pan-y;
832
+ padding: 0 var(--vela-space-3) calc(var(--vela-space-3) + env(safe-area-inset-bottom, 0px));
833
+ }
834
+ .vela-drawer-body::-webkit-scrollbar { width: 8px; }
835
+ .vela-drawer-body::-webkit-scrollbar-thumb {
836
+ background: var(--vela-scroll);
837
+ border-radius: 4px;
838
+ border: 2px solid transparent;
839
+ background-clip: padding-box;
840
+ }
841
+ `;
842
+ var DISMISS_FRACTION = 0.33;
843
+ var DISMISS_PX = 96;
844
+ var SLOP_PX = 8;
845
+ var HSWIPE_MIN_PX = 48;
846
+ function classifyGesture(dx, dy, ctx) {
847
+ if (Math.max(Math.abs(dx), Math.abs(dy)) < SLOP_PX) return "pending";
848
+ if (Math.abs(dy) > Math.abs(dx)) return dy > 0 && !ctx.scrolled ? "drag" : "scroll";
849
+ return ctx.canSwipe && !ctx.hScrollable ? "hswipe" : "scroll";
850
+ }
851
+ function dragDismisses(dy, panelHeightPx) {
852
+ return dy >= Math.min(DISMISS_PX, panelHeightPx * DISMISS_FRACTION);
853
+ }
854
+ function swipeDirection(dx, dy) {
855
+ if (Math.abs(dx) < HSWIPE_MIN_PX || Math.abs(dx) <= Math.abs(dy)) return null;
856
+ return dx < 0 ? "left" : "right";
857
+ }
858
+ var Drawer = class {
859
+ constructor(opts = {}) {
860
+ const doc = (opts.host ?? document.body).ownerDocument;
861
+ injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
862
+ const host = opts.host ?? doc.body;
863
+ this.backdrop = doc.createElement("div");
864
+ this.backdrop.className = "vela-drawer-backdrop vela-ui-layer";
865
+ this.positioner = doc.createElement("div");
866
+ this.positioner.className = "vela-drawer-positioner vela-ui-layer";
867
+ this.panel = doc.createElement("div");
868
+ this.panel.className = "vela-drawer";
869
+ this.panel.tabIndex = -1;
870
+ const grab = doc.createElement("div");
871
+ grab.className = "vela-drawer-grab";
872
+ this.titleEl = doc.createElement("div");
873
+ this.titleEl.className = "vela-drawer-title";
874
+ this.titleEl.textContent = opts.title ?? "";
875
+ this.body = doc.createElement("div");
876
+ this.body.className = "vela-drawer-body";
877
+ if (opts.content instanceof Node) this.body.appendChild(opts.content);
878
+ else if (typeof opts.content === "function") opts.content(this.body);
879
+ this.panel.append(grab, this.titleEl, this.body);
880
+ this.positioner.appendChild(this.panel);
881
+ host.append(this.backdrop, this.positioner);
882
+ this.wireGestures(grab, opts.onSwipe);
883
+ this.ctrl = drawerController({ ...opts, initialFocusEl: () => this.panel });
884
+ const mid = String(this.ctrl.props.id);
885
+ this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
886
+ const api = this.ctrl.connect(service);
887
+ spreadProps(this.backdrop, api.getBackdropProps(), mid);
888
+ spreadProps(this.positioner, api.getPositionerProps(), mid);
889
+ spreadProps(this.panel, api.getContentProps(), mid);
890
+ spreadProps(this.titleEl, api.getTitleProps(), mid);
891
+ this.backdrop.style.display = api.open ? "" : "none";
892
+ this.positioner.style.display = api.open ? "" : "none";
893
+ });
894
+ }
895
+ /** Any element between `from` and the panel that has already been scrolled down —
896
+ * a downward pull there must scroll it back up, never drag the sheet. */
897
+ scrolledAncestor(from) {
898
+ let el = from instanceof Element ? from : null;
899
+ while (el && el !== this.panel) {
900
+ if (el.scrollTop > 0) return true;
901
+ el = el.parentElement;
902
+ }
903
+ return false;
904
+ }
905
+ /** Any element between `from` and the panel that scrolls horizontally on its own
906
+ * (the tab strip, chip rows) — a sideways move there is ITS scroll, not a swipe. */
907
+ hScrollableAncestor(from) {
908
+ let el = from instanceof Element ? from : null;
909
+ while (el && el !== this.panel) {
910
+ if (el.scrollWidth > el.clientWidth + 1) return true;
911
+ el = el.parentElement;
912
+ }
913
+ return false;
914
+ }
915
+ /**
916
+ * One gesture recognizer for the whole sheet. A downward pull dismisses from
917
+ * anywhere — the grab handle immediately, the content once it is decidedly vertical
918
+ * and its scroller is at rest (a scrolled list keeps native scrolling). A decidedly
919
+ * horizontal move becomes an `onSwipe` (tabbed drawers flip pages with it). The
920
+ * non-passive touchmove hook is what keeps the browser from claiming the pull as a
921
+ * scroll once the sheet is (or may become) the drag target.
922
+ */
923
+ wireGestures(grab, onSwipe) {
924
+ let startX = 0;
925
+ let startY = 0;
926
+ let dx = 0;
927
+ let dy = 0;
928
+ let mode = "idle";
929
+ const beginDrag = (e) => {
930
+ mode = "drag";
931
+ startY = e.clientY;
932
+ this.panel.style.transition = "none";
933
+ try {
934
+ this.panel.setPointerCapture(e.pointerId);
935
+ } catch {
936
+ }
937
+ };
938
+ this.panel.addEventListener("pointerdown", (e) => {
939
+ if (e.isPrimary === false) return;
940
+ startX = e.clientX;
941
+ startY = e.clientY;
942
+ dx = 0;
943
+ dy = 0;
944
+ if (grab.contains(e.target)) beginDrag(e);
945
+ else mode = "pending";
946
+ });
947
+ this.panel.addEventListener("pointermove", (e) => {
948
+ if (mode === "idle" || mode === "scroll") return;
949
+ dx = e.clientX - startX;
950
+ dy = e.clientY - startY;
951
+ if (mode === "pending") {
952
+ const intent = classifyGesture(dx, dy, {
953
+ canSwipe: !!onSwipe,
954
+ scrolled: this.scrolledAncestor(e.target),
955
+ hScrollable: this.hScrollableAncestor(e.target)
956
+ });
957
+ if (intent === "pending") return;
958
+ if (intent === "drag") beginDrag(e);
959
+ else mode = intent;
960
+ }
961
+ if (mode === "drag") {
962
+ dy = Math.max(0, e.clientY - startY);
963
+ this.panel.style.transform = dy > 0 ? `translateY(${dy}px)` : "";
964
+ }
965
+ });
966
+ this.panel.addEventListener(
967
+ "touchmove",
968
+ (e) => {
969
+ if (mode === "drag" || mode === "hswipe") {
970
+ e.preventDefault();
971
+ return;
972
+ }
973
+ if (mode !== "pending") return;
974
+ const t = e.touches[0];
975
+ if (!t) return;
976
+ const mdx = t.clientX - startX;
977
+ const mdy = t.clientY - startY;
978
+ if (mdy > Math.abs(mdx) && !this.scrolledAncestor(e.target)) e.preventDefault();
979
+ },
980
+ { passive: false }
981
+ );
982
+ const settle = () => {
983
+ if (mode === "idle") return;
984
+ const finished = mode;
985
+ mode = "idle";
986
+ if (finished === "drag") {
987
+ this.panel.style.transition = "";
988
+ this.panel.style.transform = "";
989
+ if (dragDismisses(dy, this.panel.getBoundingClientRect().height)) this.hide();
990
+ } else if (finished === "hswipe") {
991
+ const dir = swipeDirection(dx, dy);
992
+ if (dir) onSwipe?.(dir);
993
+ }
994
+ };
995
+ this.panel.addEventListener("pointerup", settle);
996
+ this.panel.addEventListener("pointercancel", settle);
997
+ }
998
+ setTitle(title) {
999
+ this.titleEl.textContent = title;
1000
+ }
1001
+ get open() {
1002
+ return this.ctrl.connect(this.handle.service).open;
1003
+ }
1004
+ show() {
1005
+ this.ctrl.connect(this.handle.service).setOpen(true);
1006
+ }
1007
+ hide() {
1008
+ this.ctrl.connect(this.handle.service).setOpen(false);
1009
+ }
1010
+ destroy() {
1011
+ this.handle.stop();
1012
+ this.backdrop.remove();
1013
+ this.positioner.remove();
1014
+ }
1015
+ };
697
1016
 
698
- export { Dialog, KeymapManager, Menu, Tooltip, applyPlotOverlayTokens, applyThemeTokens, dialogController, ensureUIHost, isEditableTarget, menuController, nextUid, runMachine, tooltipController };
1017
+ export { Dialog, Drawer, KeymapManager, Menu, Tooltip, applyPlotOverlayTokens, applyThemeTokens, dialogController, drawerController, ensureUIHost, isEditableTarget, menuController, nextUid, runMachine, tooltipController };
@@ -1,4 +1,4 @@
1
- import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl } from './chunk-EP6NDJYK.js';
1
+ import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl } from './chunk-LJLZR44Y.js';
2
2
 
3
3
  // src/core/native-indicators/NativeIndicator.ts
4
4
  var REGISTRY = /* @__PURE__ */ new Map();
@@ -1490,10 +1490,10 @@ function polylineHit(px, py, pts, tol) {
1490
1490
  }
1491
1491
 
1492
1492
  // src/core/drawings/types/FixedRangeVolumeProfile.ts
1493
- var OUTSIDE_UP = `${BULLISH}BF`;
1494
- var OUTSIDE_DOWN = `${BEARISH}BF`;
1495
- var VA_UP = `${BULLISH}66`;
1496
- var VA_DOWN = `${BEARISH}66`;
1493
+ var OUTSIDE_UP = `${BULLISH}66`;
1494
+ var OUTSIDE_DOWN = `${BEARISH}66`;
1495
+ var VA_UP = `${BULLISH}BF`;
1496
+ var VA_DOWN = `${BEARISH}BF`;
1497
1497
  function defaultFrvpStyle() {
1498
1498
  return {
1499
1499
  rows: 24,
@@ -1511,7 +1511,8 @@ function defaultFrvpStyle() {
1511
1511
  valColor: NEUTRAL,
1512
1512
  valStyle: "solid",
1513
1513
  showPoc: true,
1514
- pocColor: ACCENT,
1514
+ pocColor: void 0,
1515
+ // theme contrast ink until the user picks a color
1515
1516
  pocStyle: "solid",
1516
1517
  showDevelopingPoc: false,
1517
1518
  developingPocColor: ACCENT,
@@ -5647,6 +5648,47 @@ registerDrawingType({
5647
5648
  });
5648
5649
 
5649
5650
  // src/chart-types/registry.ts
5651
+ function normalizeSettingsRow(r) {
5652
+ switch (r.kind) {
5653
+ case "row":
5654
+ return { label: r.label, toggle: r.toggle, controls: r.controls, when: r.when };
5655
+ case "toggle": {
5656
+ const controls = [];
5657
+ if (r.number) controls.push({ kind: "number", ...r.number });
5658
+ for (const c of r.colors ?? []) controls.push({ kind: "color", ...c });
5659
+ if (r.width) controls.push({ kind: "width", ...r.width });
5660
+ return { label: r.label, toggle: { key: r.key, defval: r.defval }, controls, when: r.when };
5661
+ }
5662
+ case "number":
5663
+ return { label: r.label, controls: [{ kind: "number", key: r.key, label: r.label, defval: r.defval, min: r.min, max: r.max, step: r.step }], when: r.when };
5664
+ case "color":
5665
+ return { label: r.label, controls: [{ kind: "color", key: r.key, label: r.label, defval: r.defval }], when: r.when };
5666
+ case "select":
5667
+ return { label: r.label, controls: [{ kind: "select", key: r.key, label: r.label, options: r.options, defval: r.defval }], when: r.when };
5668
+ case "range":
5669
+ return {
5670
+ label: r.label,
5671
+ controls: [
5672
+ { kind: "number", key: r.minKey, label: `${r.label} \u2014 min`, defval: r.defval, min: r.min, max: r.max, step: r.step, placeholder: r.placeholder },
5673
+ { kind: "hint", text: "\u2013" },
5674
+ { kind: "number", key: r.maxKey, label: `${r.label} \u2014 max`, defval: r.defval, min: r.min, max: r.max, step: r.step, placeholder: r.placeholder }
5675
+ ],
5676
+ when: r.when
5677
+ };
5678
+ }
5679
+ }
5680
+ function settingsRowValueKeys(r) {
5681
+ if (r.kind === "heading" || r.kind === "header") return [];
5682
+ const n = normalizeSettingsRow(r);
5683
+ const keys = [];
5684
+ if (n.toggle) keys.push({ key: n.toggle.key, type: "boolean", defval: n.toggle.defval });
5685
+ for (const c of n.controls) {
5686
+ if (c.kind === "hint") continue;
5687
+ if (c.kind === "color" || c.kind === "select") keys.push({ key: c.key, type: "string", defval: c.defval });
5688
+ else keys.push({ key: c.key, type: "number", defval: c.defval });
5689
+ }
5690
+ return keys;
5691
+ }
5650
5692
  function settingsRowVisible(when, bag) {
5651
5693
  if (!when) return true;
5652
5694
  const conds = Array.isArray(when) ? when : [when];
@@ -5687,6 +5729,20 @@ function rendererDefaults() {
5687
5729
  }
5688
5730
 
5689
5731
  // src/renderers/native/layers.ts
5732
+ function foldBaseModulation(acc, next) {
5733
+ if (next == null) return acc;
5734
+ if (acc == null) return { ...next };
5735
+ return {
5736
+ candleBodyScale: minOpt(acc.candleBodyScale, next.candleBodyScale),
5737
+ candleBodyAlpha: minOpt(acc.candleBodyAlpha, next.candleBodyAlpha),
5738
+ gridAlpha: minOpt(acc.gridAlpha, next.gridAlpha)
5739
+ };
5740
+ }
5741
+ function minOpt(a, b) {
5742
+ if (a == null) return b;
5743
+ if (b == null) return a;
5744
+ return Math.min(a, b);
5745
+ }
5690
5746
  var registry2 = /* @__PURE__ */ new Map();
5691
5747
  function registerRendererLayer(def) {
5692
5748
  registry2.set(def.id, def);
@@ -5850,6 +5906,17 @@ var CSS = `
5850
5906
  }
5851
5907
  .vela-panel-resizer:hover::after,
5852
5908
  .vela-panel-resizer[data-dragging]::after { background: var(--vela-accent); }
5909
+ /* Mobile: a 280px column would crush a phone-width chart \u2014 the panel overlays the
5910
+ chart area instead (its flex parent is position:relative), full-bleed, closed by
5911
+ the same header \u2715. Width dragging is a pointer affordance; off on mobile. */
5912
+ [data-layout='mobile'] .vela-panel {
5913
+ position: absolute;
5914
+ inset: 0;
5915
+ width: auto;
5916
+ z-index: 25;
5917
+ border-left: none;
5918
+ }
5919
+ [data-layout='mobile'] .vela-panel-resizer { display: none; }
5853
5920
  `;
5854
5921
  function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
5855
5922
  const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
@@ -5978,4 +6045,4 @@ var SidePanel = class {
5978
6045
  }
5979
6046
  };
5980
6047
 
5981
- export { AnchoredVwap, ArrowMark, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MachFigure, MeasureBox, Note, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TextLabel, chartType, chartTypes, clampPanelWidth, createDrawing, deserializeDrawing, drawingTypes, formatDuration, getDrawingType, getNativeIndicator, legendActions, legendActionsProviderFor, lineSegmentIntersection, nativeIndicatorDescriptors, nativeIndicatorTypes, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, settingsRowVisible, sidePanels, tickerModifierIds, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
6048
+ export { AnchoredVwap, ArrowMark, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MachFigure, MeasureBox, Note, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TextLabel, chartType, chartTypes, clampPanelWidth, createDrawing, deserializeDrawing, drawingTypes, foldBaseModulation, formatDuration, getDrawingType, getNativeIndicator, legendActions, legendActionsProviderFor, lineSegmentIntersection, nativeIndicatorDescriptors, nativeIndicatorTypes, normalizeSettingsRow, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, settingsRowValueKeys, settingsRowVisible, sidePanels, tickerModifierIds, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
@@ -1,5 +1,5 @@
1
- import { s as Millis, ab as InputSchema, aa as IndicatorMeta, P as PriceStyle, O as OHLCV, l as InputValue, j as IndicatorModel, i as MoveTarget, ax as SeriesSpec, a8 as Fill, B as Background, ap as PriceLine, _ as DrawingLine, W as DrawingBox, Z as DrawingLabel, a2 as DrawingPolyline, $ as DrawingLinefill, a4 as DrawingTable, f as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, u as DrawingTypeKey, t as SnapMode, a0 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, n as LegendActionView, U as Unsubscribe, C as CrosshairEvent, q as DataWindowReadout, m as SymbolPickerFn, aJ as PaneInfo, a7 as DrawingsOption, D as Drawing, b as VelaOptions, A as AddIndicatorOptions, al as MarketSwitch, ak as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-DbQholFV.cjs';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-BBQ3Hr21.cjs';
1
+ import { s as Millis, ac as InputSchema, ab as IndicatorMeta, P as PriceStyle, O as OHLCV, l as InputValue, j as IndicatorModel, i as MoveTarget, ay as SeriesSpec, a9 as Fill, B as Background, aq as PriceLine, $ as DrawingLine, X as DrawingBox, _ as DrawingLabel, a3 as DrawingPolyline, a0 as DrawingLinefill, a5 as DrawingTable, f as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, u as DrawingTypeKey, t as SnapMode, a1 as DrawingMode, I as IChartRenderer, R as RendererCapabilities, n as LegendActionView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, q as DataWindowReadout, m as SymbolPickerFn, aK as PaneInfo, a8 as DrawingsOption, D as Drawing, b as VelaOptions, w as AddIndicatorOptions, am as MarketSwitch, al as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-XTBpbx0s.cjs';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-gnzv-dgc.cjs';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -434,7 +434,13 @@ interface NativeIndicator {
434
434
  */
435
435
  interface NativeIndicatorDescriptor {
436
436
  readonly type: string;
437
+ /** Full display name (picker, settings header, handle title). */
437
438
  readonly title: string;
439
+ /**
440
+ * Compact name for the on-chart legend chip. Absent ⇒ {@link title}. The picker and
441
+ * settings dialog keep the full title either way.
442
+ */
443
+ readonly shortTitle?: string;
438
444
  readonly paneHint: 'price' | 'new';
439
445
  readonly overlay: boolean;
440
446
  /** Whether instances react to viewport changes (drives the orchestrator's viewport poke). */
@@ -821,6 +827,20 @@ declare class RendererControl {
821
827
  * degradation as the sync ghost crosshair.
822
828
  */
823
829
  setLegendActions(provider: ((indicatorId: string) => LegendActionView[]) | null): void;
830
+ /**
831
+ * Replace the indicator legend's fold toggle with a host action (or restore it with
832
+ * `null`) — multi-chart shells point the chip at their indicator overview instead of
833
+ * unfolding rows in place. Silent no-op on a renderer without a foldable legend.
834
+ */
835
+ setLegendOverviewAction(action: (() => void) | null): this;
836
+ /** Whether the active renderer can open a per-indicator settings dialog. */
837
+ get supportsIndicatorSettings(): boolean;
838
+ /**
839
+ * Open one indicator's settings dialog — the programmatic twin of the legend gear
840
+ * (see {@link supportsIndicatorSettings}). Silent no-op without the seam or for an
841
+ * unknown id.
842
+ */
843
+ openIndicatorSettings(indicatorId: string): this;
824
844
  /**
825
845
  * Export the current chart as a PNG data URL, or null if the active renderer
826
846
  * doesn't support it (warns). DOM overlays (tables, legend) are not included.
@@ -851,6 +871,8 @@ declare class RendererControl {
851
871
  * public seam host chrome (status lines, data windows) builds on.
852
872
  */
853
873
  onCrosshairMove(cb: (e: CrosshairEvent) => void): Unsubscribe;
874
+ /** Touch long-press on a price or time axis strip — silent no-op without the seam. */
875
+ onAxisLongPress(cb: (e: AxisLongPressEvent) => void): Unsubscribe;
854
876
  /**
855
877
  * The current data-window readout — the hovered bar's date/time and OHLCV plus every
856
878
  * indicator's value there, each already formatted on its pane's scale (the latest bar when
@@ -896,6 +918,10 @@ declare class RendererControl {
896
918
  title: string;
897
919
  rows: readonly unknown[];
898
920
  }>): this;
921
+ /** Tell the renderer's own chrome which size class the host shell is in —
922
+ * `'mobile'` switches its dialogs/toolbars to the touch-first presentation.
923
+ * Silent no-op on a renderer without adaptive chrome. */
924
+ setLayoutMode(mode: 'mobile' | 'desktop'): this;
899
925
  }
900
926
 
901
927
  /** Callback surface `PanesControl` uses to drive the orchestrator. */
@@ -1400,6 +1426,11 @@ interface WidgetActionDescriptor {
1400
1426
  icon?: string;
1401
1427
  /** Sort key within the contributed group (ascending; default 0). */
1402
1428
  order?: number;
1429
+ /** Topbar only: which cluster the button joins. `'right'` (default) is the
1430
+ * right-hand tools cluster; `'left'` puts it with the PRIMARY chrome buttons —
1431
+ * right after the style/layout dropdowns, styled like them (the spot and look of
1432
+ * the built-in Indicators button, for actions that replace it). */
1433
+ align?: 'left' | 'right';
1403
1434
  /** Runtime gate — omitted ⇒ always shown. */
1404
1435
  when?: (ctx: WidgetContext) => boolean;
1405
1436
  run: (ctx: WidgetContext) => void;