@luxalgo/vela 0.5.0 → 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-Dzd-Erlk.d.ts → DataProvider-DTOEXKTI.d.ts} +1 -1
  2. package/dist/{DataProvider-DKNDHpNv.d.cts → DataProvider-gnzv-dgc.d.cts} +1 -1
  3. package/dist/{chunk-KM6LHB3Y.js → chunk-ATPU5CI6.js} +6 -1
  4. package/dist/{chunk-7UFX5ZIG.js → chunk-B5TIKQJ5.js} +65 -7
  5. package/dist/{chunk-GYD2THPV.js → chunk-LJLZR44Y.js} +9 -2
  6. package/dist/{chunk-RHIDOUFL.js → chunk-P556H6EA.js} +329 -10
  7. package/dist/{chunk-KCCZNKH7.js → chunk-RRFGWZNJ.js} +1915 -367
  8. package/dist/{chunk-OVQKKXLZ.js → chunk-Y7TK4ZWK.js} +1810 -153
  9. package/dist/{contributions-o1GRKPI_.d.cts → contributions-Ci_i3IN2.d.cts} +77 -31
  10. package/dist/{contributions-hX3EUyjG.d.ts → contributions-b6AVrqqG.d.ts} +77 -31
  11. package/dist/{history-LJkz4-sS.d.cts → history-Cf1lVsap.d.cts} +22 -5
  12. package/dist/{history-Dzxz-MQj.d.ts → history-Ci3M0xLV.d.ts} +22 -5
  13. package/dist/index.cjs +2010 -397
  14. package/dist/index.d.cts +83 -25
  15. package/dist/index.d.ts +83 -25
  16. package/dist/index.js +4 -4
  17. package/dist/{options-Q-576hIi.d.cts → options-XTBpbx0s.d.cts} +61 -1
  18. package/dist/{options-Q-576hIi.d.ts → options-XTBpbx0s.d.ts} +61 -1
  19. package/dist/{plugin-aGUD1epn.d.ts → plugin-CnNd7cuF.d.ts} +305 -8
  20. package/dist/{plugin-D94muTV-.d.cts → plugin-DfH4Y-Om.d.cts} +305 -8
  21. package/dist/plugin.cjs +58 -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 +342 -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 +2010 -397
  36. package/dist/vela.global.min.js +115 -34
  37. package/dist/widget.cjs +5987 -2242
  38. package/dist/widget.d.cts +89 -17
  39. package/dist/widget.d.ts +89 -17
  40. package/dist/widget.js +169 -18
  41. package/dist/workspace.cjs +5627 -1587
  42. package/dist/workspace.d.cts +106 -12
  43. package/dist/workspace.d.ts +106 -12
  44. package/dist/workspace.js +494 -52
  45. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-Q-576hIi.js';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-XTBpbx0s.js';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-Q-576hIi.cjs';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-XTBpbx0s.cjs';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -1,4 +1,4 @@
1
- import { isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha } from './chunk-GYD2THPV.js';
1
+ import { isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha } from './chunk-LJLZR44Y.js';
2
2
 
3
3
  // src/core/tokens.ts
4
4
  var STATIC_TOKENS = {
@@ -53,6 +53,11 @@ function themeTokens(t) {
53
53
  "--vela-hover-strong": wash(0.16),
54
54
  "--vela-focus": withAlpha(t.textColor, 0.5),
55
55
  "--vela-focus-soft": withAlpha(t.textColor, 0.12),
56
+ // Separator hover — the SAME wash the chart's pane separators paint on hover
57
+ // (soft full-thickness band + solid 2px center line), so DOM-drawn dividers
58
+ // (the workspace grid) and canvas-drawn ones read as one family.
59
+ "--vela-separator-hover-band": withAlpha(t.textColor, 0.1),
60
+ "--vela-separator-hover-line": withAlpha(t.textColor, 0.55),
56
61
  "--vela-scroll": withAlpha(t.textColor, 0.3),
57
62
  "--vela-accent": ACCENT,
58
63
  "--vela-accent-bright": ACCENT_BRIGHT,
@@ -1,4 +1,4 @@
1
- import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl } from './chunk-GYD2THPV.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,52 @@ 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
+ }
5692
+ function settingsRowVisible(when, bag) {
5693
+ if (!when) return true;
5694
+ const conds = Array.isArray(when) ? when : [when];
5695
+ return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === bag[c.key]) : bag[c.key] === c.equals);
5696
+ }
5650
5697
  var registry = /* @__PURE__ */ new Map();
5651
5698
  function registerChartType(def) {
5652
5699
  registry.set(def.id, def);
@@ -5845,6 +5892,17 @@ var CSS = `
5845
5892
  }
5846
5893
  .vela-panel-resizer:hover::after,
5847
5894
  .vela-panel-resizer[data-dragging]::after { background: var(--vela-accent); }
5895
+ /* Mobile: a 280px column would crush a phone-width chart \u2014 the panel overlays the
5896
+ chart area instead (its flex parent is position:relative), full-bleed, closed by
5897
+ the same header \u2715. Width dragging is a pointer affordance; off on mobile. */
5898
+ [data-layout='mobile'] .vela-panel {
5899
+ position: absolute;
5900
+ inset: 0;
5901
+ width: auto;
5902
+ z-index: 25;
5903
+ border-left: none;
5904
+ }
5905
+ [data-layout='mobile'] .vela-panel-resizer { display: none; }
5848
5906
  `;
5849
5907
  function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
5850
5908
  const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
@@ -5973,4 +6031,4 @@ var SidePanel = class {
5973
6031
  }
5974
6032
  };
5975
6033
 
5976
- 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, sidePanels, tickerModifierIds, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
6034
+ 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, 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 };
@@ -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"/>'));
@@ -156,6 +158,11 @@ registerIcon(
156
158
  "pen-lock",
157
159
  svg24('<path d="M13.8 4.2a2.1 2.1 0 0 1 3 3L8.5 15.5l-3.5 1 1-3.5Z"/><rect x="13" y="14.5" width="8" height="6" rx="1.2"/><path d="M15 14.5v-1.6a2 2 0 0 1 4 0v1.6"/>')
158
160
  );
161
+ registerIcon(
162
+ "pen-sync",
163
+ // Pen + two stacked panes — drawing onto every linked chart at once.
164
+ svg24('<path d="M13.8 4.2a2.1 2.1 0 0 1 3 3L8.5 15.5l-3.5 1 1-3.5Z"/><rect x="12.5" y="13.5" width="6" height="4.8" rx="1"/><path d="M15.5 18.3v1a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-2.8a1 1 0 0 0-1-1h-1"/>')
165
+ );
159
166
  registerIcon("brush", svg24('<path d="M9.5 12 17 4.5a2.12 2.12 0 0 1 3 3L12.5 15"/><path d="M7 14a3 3 0 0 0-3 3c0 1.3-1.2 1.5-1.5 2 .8.9 2 1.5 3.5 1.5a3.5 3.5 0 0 0 3.5-3.5 3 3 0 0 0-2.5-3Z"/>'));
160
167
  registerIcon(
161
168
  "bucket",
@@ -1,5 +1,5 @@
1
- import { STATIC_TOKENS, themeTokens } from './chunk-KM6LHB3Y.js';
2
- import { injectStyles, iconEl } from './chunk-GYD2THPV.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 };