@luxalgo/vela 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +28 -0
  3. package/README.md +135 -0
  4. package/dist/DataProvider-DKNDHpNv.d.cts +134 -0
  5. package/dist/DataProvider-Dzd-Erlk.d.ts +134 -0
  6. package/dist/chunk-7UFX5ZIG.js +5976 -0
  7. package/dist/chunk-GYD2THPV.js +252 -0
  8. package/dist/chunk-KCCZNKH7.js +19137 -0
  9. package/dist/chunk-KM6LHB3Y.js +76 -0
  10. package/dist/chunk-OVQKKXLZ.js +3978 -0
  11. package/dist/chunk-Q3XQHLIH.js +7 -0
  12. package/dist/chunk-RHIDOUFL.js +698 -0
  13. package/dist/contributions-hX3EUyjG.d.ts +1528 -0
  14. package/dist/contributions-o1GRKPI_.d.cts +1528 -0
  15. package/dist/history-Dzxz-MQj.d.ts +298 -0
  16. package/dist/history-LJkz4-sS.d.cts +298 -0
  17. package/dist/icons-BZYbJXSV.d.cts +10 -0
  18. package/dist/icons-BZYbJXSV.d.ts +10 -0
  19. package/dist/index.cjs +25294 -0
  20. package/dist/index.d.cts +1189 -0
  21. package/dist/index.d.ts +1189 -0
  22. package/dist/index.js +5 -0
  23. package/dist/keymap-CGOz5F5f.d.cts +64 -0
  24. package/dist/keymap-CGOz5F5f.d.ts +64 -0
  25. package/dist/options-Q-576hIi.d.cts +1545 -0
  26. package/dist/options-Q-576hIi.d.ts +1545 -0
  27. package/dist/plugin-D94muTV-.d.cts +405 -0
  28. package/dist/plugin-aGUD1epn.d.ts +405 -0
  29. package/dist/plugin.cjs +5967 -0
  30. package/dist/plugin.d.cts +7 -0
  31. package/dist/plugin.d.ts +7 -0
  32. package/dist/plugin.js +3 -0
  33. package/dist/providers/binance.cjs +390 -0
  34. package/dist/providers/binance.d.cts +62 -0
  35. package/dist/providers/binance.d.ts +62 -0
  36. package/dist/providers/binance.js +388 -0
  37. package/dist/providers/coinbase.cjs +462 -0
  38. package/dist/providers/coinbase.d.cts +59 -0
  39. package/dist/providers/coinbase.d.ts +59 -0
  40. package/dist/providers/coinbase.js +460 -0
  41. package/dist/providers/hyperliquid.cjs +361 -0
  42. package/dist/providers/hyperliquid.d.cts +54 -0
  43. package/dist/providers/hyperliquid.d.ts +54 -0
  44. package/dist/providers/hyperliquid.js +359 -0
  45. package/dist/side-panel-CT9ZwIGz.d.cts +63 -0
  46. package/dist/side-panel-CT9ZwIGz.d.ts +63 -0
  47. package/dist/ui.cjs +1030 -0
  48. package/dist/ui.d.cts +188 -0
  49. package/dist/ui.d.ts +188 -0
  50. package/dist/ui.js +3 -0
  51. package/dist/vela.global.js +26509 -0
  52. package/dist/vela.global.min.js +230 -0
  53. package/dist/widget.cjs +31178 -0
  54. package/dist/widget.d.cts +799 -0
  55. package/dist/widget.d.ts +799 -0
  56. package/dist/widget.js +1060 -0
  57. package/dist/workspace.cjs +31692 -0
  58. package/dist/workspace.d.cts +579 -0
  59. package/dist/workspace.d.ts +579 -0
  60. package/dist/workspace.js +1694 -0
  61. package/package.json +95 -0
package/dist/ui.cjs ADDED
@@ -0,0 +1,1030 @@
1
+ 'use strict';
2
+
3
+ var vanilla = require('@zag-js/vanilla');
4
+ var tooltip = require('@zag-js/tooltip');
5
+ var menu = require('@zag-js/menu');
6
+ var dialog = require('@zag-js/dialog');
7
+
8
+ function _interopNamespace(e) {
9
+ if (e && e.__esModule) return e;
10
+ var n = Object.create(null);
11
+ if (e) {
12
+ Object.keys(e).forEach(function (k) {
13
+ if (k !== 'default') {
14
+ var d = Object.getOwnPropertyDescriptor(e, k);
15
+ Object.defineProperty(n, k, d.get ? d : {
16
+ enumerable: true,
17
+ get: function () { return e[k]; }
18
+ });
19
+ }
20
+ });
21
+ }
22
+ n.default = e;
23
+ return Object.freeze(n);
24
+ }
25
+
26
+ var tooltip__namespace = /*#__PURE__*/_interopNamespace(tooltip);
27
+ var menu__namespace = /*#__PURE__*/_interopNamespace(menu);
28
+ var dialog__namespace = /*#__PURE__*/_interopNamespace(dialog);
29
+
30
+ // src/core/color.ts
31
+ function parseRgb(color) {
32
+ const s = color.trim();
33
+ let m = /^#([0-9a-f]{3})$/i.exec(s);
34
+ if (m) {
35
+ const h = m[1];
36
+ return {
37
+ r: parseInt(h[0] + h[0], 16),
38
+ g: parseInt(h[1] + h[1], 16),
39
+ b: parseInt(h[2] + h[2], 16)
40
+ };
41
+ }
42
+ m = /^#([0-9a-f]{6})[0-9a-f]{0,2}$/i.exec(s);
43
+ if (m) {
44
+ const h = m[1];
45
+ return {
46
+ r: parseInt(h.slice(0, 2), 16),
47
+ g: parseInt(h.slice(2, 4), 16),
48
+ b: parseInt(h.slice(4, 6), 16)
49
+ };
50
+ }
51
+ const rgb = /^rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)/i.exec(s);
52
+ if (rgb) {
53
+ return { r: parseFloat(rgb[1]), g: parseFloat(rgb[2]), b: parseFloat(rgb[3]) };
54
+ }
55
+ return null;
56
+ }
57
+ function withAlpha(color, alpha) {
58
+ const c = parseRgb(color);
59
+ if (!c) return color;
60
+ return `rgba(${c.r},${c.g},${c.b},${alpha})`;
61
+ }
62
+ function mix(base, top, alpha) {
63
+ const b = parseRgb(base);
64
+ const t = parseRgb(top);
65
+ if (!b || !t) return base;
66
+ const c = (x, y) => Math.round(x + (y - x) * alpha);
67
+ return `rgb(${c(b.r, t.r)},${c(b.g, t.g)},${c(b.b, t.b)})`;
68
+ }
69
+ function isDarkColor(color) {
70
+ const c = parseRgb(color);
71
+ if (!c) return true;
72
+ return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b < 128;
73
+ }
74
+
75
+ // src/ui/styles.ts
76
+ function injectStyles(id, css, root = document) {
77
+ if (typeof document === "undefined") return;
78
+ const host = root instanceof Document ? root.head : root;
79
+ if (root.getElementById?.(id) ?? host.querySelector(`#${CSS.escape(id)}`)) return;
80
+ const s = document.createElement("style");
81
+ s.id = id;
82
+ s.textContent = css;
83
+ host.appendChild(s);
84
+ }
85
+
86
+ // src/core/palette.ts
87
+ var ACCENT = "#2962ff";
88
+ var ACCENT_BRIGHT = "#38c0fd";
89
+ var HIGHLIGHT = "#e0b400";
90
+
91
+ // src/core/tokens.ts
92
+ var STATIC_TOKENS = {
93
+ "--vela-space-1": "4px",
94
+ "--vela-space-2": "8px",
95
+ "--vela-space-3": "12px",
96
+ "--vela-space-4": "16px",
97
+ "--vela-radius-sm": "4px",
98
+ "--vela-radius-md": "6px",
99
+ "--vela-radius-lg": "10px",
100
+ "--vela-z-tooltip": "60",
101
+ "--vela-z-menu": "50",
102
+ "--vela-z-dialog": "40",
103
+ "--vela-ease": "cubic-bezier(0.22, 1, 0.36, 1)",
104
+ "--vela-dur-fast": "90ms",
105
+ "--vela-dur-med": "160ms",
106
+ "--vela-font-size-sm": "11px",
107
+ "--vela-font-size-md": "12px",
108
+ "--vela-font-size-lg": "14px"
109
+ };
110
+ function themeTokens(t) {
111
+ const dark = isDarkColor(t.background);
112
+ const wash = (a) => dark ? `rgba(255,255,255,${a})` : withAlpha(t.textColor, a + 0.02);
113
+ const elevated = mix(t.background, dark ? "#ffffff" : t.textColor, dark ? 0.03 : 0.05);
114
+ return {
115
+ "--vela-font": t.fontFamily,
116
+ "--vela-bg": t.background,
117
+ // Chrome text is slightly brighter than the chart's own axis text, which is
118
+ // deliberately recessive; the chart keeps using `t.textColor` directly.
119
+ "--vela-fg": dark ? "#d1d4dc" : t.textColor,
120
+ "--vela-fg-muted": dark ? "#868a96" : withAlpha(t.textColor, 0.62),
121
+ "--vela-fg-faint": withAlpha(t.textColor, 0.35),
122
+ "--vela-fg-bright": dark ? "#f0f3fa" : "#000000",
123
+ "--vela-surface": t.background,
124
+ // Panels and menus float ABOVE the chart, so their surface is the wash flattened onto
125
+ // the chart background — opaque, or candles read through the panel.
126
+ "--vela-surface-elev": elevated,
127
+ "--vela-surface-overlay": elevated,
128
+ // Recessed fields (inputs, selects) read as cut INTO their panel, so they fall back
129
+ // to the chart surface and are separated from the panel by their border alone.
130
+ "--vela-surface-sunken": t.background,
131
+ "--vela-border": t.borderColor,
132
+ "--vela-border-strong": dark ? "#34353b" : withAlpha(t.textColor, 0.28),
133
+ "--vela-border-soft": t.borderColor,
134
+ // Barely-there rules INSIDE a panel (row separators), where a full border would
135
+ // chop the list into boxes.
136
+ "--vela-border-faint": withAlpha(t.textColor, 0.08),
137
+ "--vela-hover": wash(0.06),
138
+ "--vela-active": wash(0.1),
139
+ // A deliberately stronger hover for rows inside an already-tinted surface (menu
140
+ // items in an active flyout), where the normal wash would not separate from it.
141
+ "--vela-hover-strong": wash(0.16),
142
+ "--vela-focus": withAlpha(t.textColor, 0.5),
143
+ "--vela-focus-soft": withAlpha(t.textColor, 0.12),
144
+ "--vela-scroll": withAlpha(t.textColor, 0.3),
145
+ "--vela-accent": ACCENT,
146
+ "--vela-accent-bright": ACCENT_BRIGHT,
147
+ "--vela-highlight": HIGHLIGHT,
148
+ // The inverse chip: a filled selected state (active tab, ticked checkbox). Its ink
149
+ // must contrast the fill, so the pair flips together with the theme.
150
+ "--vela-selected-bg": dark ? "#f0f3fa" : t.textColor,
151
+ "--vela-selected-fg": dark ? t.background : "#ffffff",
152
+ // Fixed ink for saturated fills (accent buttons, categorical avatars) — those fills
153
+ // are theme-independent, so their ink is too.
154
+ "--vela-fg-on-fill": "#ffffff",
155
+ "--vela-up": t.upColor,
156
+ "--vela-down": t.downColor,
157
+ "--vela-danger": t.downColor,
158
+ "--vela-shadow": "0 8px 30px rgba(0,0,0,0.5)",
159
+ "--vela-shadow-dialog": "0 20px 60px rgba(0,0,0,0.5)",
160
+ "--vela-backdrop": "rgba(0,0,0,0.45)"
161
+ };
162
+ }
163
+
164
+ // src/ui/tokens.ts
165
+ var STATIC_ID = "vela-ui-tokens";
166
+ var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
167
+ var STATIC_CSS = `
168
+ .vela-ui, .vela-ui-layer {
169
+ ${STATIC_DECLS}
170
+ font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
171
+ box-sizing: border-box;
172
+ }
173
+ .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
174
+ .vela-icon { display: inline-flex; align-items: center; flex: none; }
175
+ .vela-icon svg { display: block; }
176
+ `;
177
+ function applyThemeTokens(el, t) {
178
+ const tokens = themeTokens(t);
179
+ for (const key in tokens) el.style.setProperty(key, tokens[key]);
180
+ }
181
+ function applyPlotOverlayTokens(host, base, config) {
182
+ const layout = config?.layout;
183
+ const t = layout?.background && layout.textColor ? { ...base, background: layout.background, textColor: layout.textColor } : base;
184
+ applyThemeTokens(host, t);
185
+ }
186
+ function ensureUIHost(el, theme) {
187
+ injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
188
+ el.classList.add("vela-ui");
189
+ if (theme) applyThemeTokens(el, theme);
190
+ }
191
+
192
+ // src/core/icons.ts
193
+ var registry = /* @__PURE__ */ new Map();
194
+ function registerIcon(id, svg) {
195
+ registry.set(id, svg);
196
+ }
197
+ function iconMarkup(id) {
198
+ return registry.get(id) ?? null;
199
+ }
200
+ function svg16(body, extra = "") {
201
+ return `<svg viewBox="0 0 16 16" width="1em" height="1em" ${extra ? extra + " " : ""}fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
202
+ }
203
+ function svg24(body, extra = "") {
204
+ return `<svg viewBox="0 0 24 24" width="1em" height="1em" ${extra ? extra + " " : ""}fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`;
205
+ }
206
+ var S = svg16;
207
+ registerIcon(
208
+ "style-candles",
209
+ S('<path d="M4.5 2v2M4.5 12v2M11.5 2v1.5M11.5 11v3"/><rect x="2.8" y="4" width="3.4" height="8" rx="0.6" fill="currentColor" fill-opacity="0.25"/><rect x="9.8" y="3.5" width="3.4" height="7.5" rx="0.6" fill="currentColor"/>')
210
+ );
211
+ registerIcon(
212
+ "style-bars",
213
+ S('<path d="M4.5 2.5v11M2.5 5h2M4.5 11h2"/><path d="M11.5 2.5v11M9.5 4.5h2M11.5 10.5h2"/>')
214
+ );
215
+ registerIcon("style-line", S('<path d="M1.5 11.5 5.5 7l3 2.5 5-6"/>'));
216
+ registerIcon(
217
+ "style-area",
218
+ S('<path d="M1.5 11.5 5.5 7l3 2.5 5-6"/><path d="M1.5 11.5 5.5 7l3 2.5 5-6V13.5h-12z" fill="currentColor" fill-opacity="0.25" stroke="none"/>')
219
+ );
220
+ registerIcon(
221
+ "style-baseline",
222
+ S('<path d="M1.5 8h13" stroke-dasharray="2 2"/><path d="M2.5 8 5.25 4.5 8 8h-5.5z" fill="currentColor" fill-opacity="0.25" stroke="none"/><path d="M2.5 8 5.25 4.5 8 8"/><path d="M8 8l2.75 3.5L13.5 8h-5.5z" fill="currentColor" fill-opacity="0.25" stroke="none"/><path d="M8 8l2.75 3.5L13.5 8"/>')
223
+ );
224
+ registerIcon(
225
+ "style-heikinashi",
226
+ S('<path d="M4.5 12v2M11.5 2v2"/><rect x="2.8" y="5" width="3.4" height="7" rx="0.6" fill="currentColor" fill-opacity="0.25"/><rect x="9.8" y="4" width="3.4" height="7" rx="0.6" fill="currentColor"/>')
227
+ );
228
+ registerIcon("indicators", S('<path d="M1.5 12.5 5 7l2.5 3.5L11 4l3.5 5"/><circle cx="11" cy="4" r="1.4" fill="currentColor" stroke="none"/>'));
229
+ registerIcon("undo", S('<path d="M6.2 9.5 2.7 6l3.5-3.5"/><path d="M2.7 6h6.8a3.65 3.65 0 0 1 0 7.3H7.5"/>'));
230
+ registerIcon("redo", S('<path d="M9.8 9.5 13.3 6 9.8 2.5"/><path d="M13.3 6H6.5a3.65 3.65 0 0 0 0 7.3h2"/>'));
231
+ registerIcon(
232
+ "objects",
233
+ S('<path d="M8 1.8 14 5 8 8.2 2 5z"/><path d="M2 8l6 3.2L14 8" opacity="0.7"/><path d="M2 11l6 3.2 6-3.2" opacity="0.4"/>')
234
+ );
235
+ registerIcon("clock", S('<circle cx="8" cy="8" r="6.2"/><path d="M8 4.8V8l2.4 1.6"/>'));
236
+ registerIcon("datawindow", S('<rect x="1.8" y="2.5" width="12.4" height="11" rx="1.5"/><path d="M4.5 5.5h4M4.5 8h7M4.5 10.5h5.5"/>'));
237
+ registerIcon("camera", S('<path d="M5.5 4 6.5 2.5h3L10.5 4h3A1 1 0 0 1 14.5 5v7.5a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1z"/><circle cx="8" cy="8.5" r="2.6"/>'));
238
+ registerIcon(
239
+ "gear",
240
+ S('<path d="M6.55 1.4h2.9l.32 1.72c.48.14.92.38 1.3.7l1.66-.55 1.45 2.5-1.35 1.1c.08.34.12.7.12 1.07s-.04.73-.12 1.07l1.35 1.1-1.45 2.5-1.66-.55a4.3 4.3 0 0 1-1.3.7L9.45 14.6h-2.9l-.32-1.72a4.3 4.3 0 0 1-1.3-.7l-1.66.55-1.45-2.5 1.35-1.1A4.4 4.4 0 0 1 3.05 7.94c0-.37.04-.73.12-1.07l-1.35-1.1 1.45-2.5 1.66.55c.38-.32.82-.56 1.3-.7L6.55 1.4z"/><circle cx="8" cy="8" r="2.2"/>')
241
+ );
242
+ registerIcon("search", S('<circle cx="7" cy="7" r="4.5"/><path d="m10.5 10.5 3.5 3.5"/>'));
243
+ registerIcon("bell", S('<path d="M8 2a4 4 0 0 0-4 4v2.5L2.5 11v1h11v-1L12 8.5V6a4 4 0 0 0-4-4z"/><path d="M6.5 13.5a1.5 1.5 0 0 0 3 0"/>'));
244
+ registerIcon(
245
+ "market-open",
246
+ svg24('<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>')
247
+ );
248
+ registerIcon(
249
+ "market-pre",
250
+ svg24('<path d="M12 2v8"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 6-4 4-4-4"/><path d="M16 18a4 4 0 0 0-8 0"/>')
251
+ );
252
+ registerIcon(
253
+ "market-post",
254
+ svg24('<path d="M12 10V2"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 18-4-4-4 4"/><path d="M16 6a4 4 0 0 0-8 0"/>')
255
+ );
256
+ registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
257
+ registerIcon(
258
+ "market-holiday",
259
+ 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"/>')
260
+ );
261
+ registerIcon("chevron-right", S('<path d="m6 3.5 4.5 4.5L6 12.5"/>'));
262
+ registerIcon("chevron-down", S('<path d="M3.5 6 8 10.5 12.5 6"/>'));
263
+ registerIcon("chevron-up", S('<path d="M3.5 10 8 5.5 12.5 10"/>'));
264
+ 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"/>'));
265
+ 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"/>'));
266
+ registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
267
+ registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
268
+ 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"/>'));
269
+ 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"/>'));
270
+ 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"/>'));
271
+ 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"/>'));
272
+ 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"/>'));
273
+ registerIcon("group", S('<path d="M1.8 4.5V2.4a.6.6 0 0 1 .6-.6h2.1M11.5 1.8h2.1a.6.6 0 0 1 .6.6v2.1M14.2 11.5v2.1a.6.6 0 0 1-.6.6h-2.1M4.5 14.2H2.4a.6.6 0 0 1-.6-.6v-2.1"/><rect x="4" y="4" width="4.2" height="4.2" rx="0.7"/><rect x="7.8" y="7.8" width="4.2" height="4.2" rx="0.7"/>'));
274
+ registerIcon("ungroup", S('<rect x="1.6" y="1.6" width="6.2" height="6.2" rx="0.9"/><rect x="8.2" y="8.2" width="6.2" height="6.2" rx="0.9" stroke-dasharray="2 1.5"/>'));
275
+ registerIcon("clone", S('<rect x="5.5" y="5.5" width="9" height="9" rx="1.2"/><path d="M11 5.5v-3a1 1 0 0 0-1-1H2.5a1 1 0 0 0-1 1V10a1 1 0 0 0 1 1h3"/>'));
276
+ registerIcon("arrow-up", S('<path d="M8 13.2V3M4.2 6.8 8 3l3.8 3.8"/>'));
277
+ registerIcon("arrow-down", S('<path d="M8 2.8V13M4.2 9.2 8 13l3.8-3.8"/>'));
278
+ registerIcon("move-vertical", S('<path d="M8 2.5v11M5 5.5 8 2.5l3 3M5 10.5l3 3 3-3"/>'));
279
+ registerIcon("move", S('<path d="M8 1.8v12.4M1.8 8h12.4M5.6 4.2 8 1.8l2.4 2.4M5.6 11.8 8 14.2l2.4-2.4M4.2 5.6 1.8 8l2.4 2.4M11.8 5.6 14.2 8l-2.4 2.4"/>'));
280
+ registerIcon("pen", S('<path d="m10.8 2.2 3 3-8 8-3.6.6.6-3.6z"/><path d="m9.2 3.8 3 3"/>'));
281
+ registerIcon("wave", S('<path d="M1.5 10.4c1.6 0 1.9-4.8 3.4-4.8s1.8 4.8 3.4 4.8 1.8-4.8 3.4-4.8 1.6 4.8 2.8 4.8"/>'));
282
+ registerIcon("folder-plus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1h-10.8a1 1 0 0 1-1-1z"/><path d="M8 7.6v3.6M6.2 9.4h3.6"/>'));
283
+ registerIcon("folder-minus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1h-10.8a1 1 0 0 1-1-1z"/><path d="M6.2 9.4h3.6"/>'));
284
+ registerIcon("collapse", S('<path d="M3 8h10"/>'));
285
+ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1.4"/><path d="M8 5.4v5.2M5.4 8h5.2"/>'));
286
+ registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
287
+ registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
288
+ registerIcon("star", 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"/>'));
289
+ 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"'));
290
+ 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"'));
291
+ 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"'));
292
+ 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"/>'));
293
+ 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"/>'));
294
+ 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"/>'));
295
+ registerIcon("cursor", svg24('<path d="M5 3l6 16 2-6 6-2z"/>', 'fill="currentColor" stroke="none"'));
296
+ registerIcon(
297
+ "ruler",
298
+ svg24('<path d="M21.3 15.3 8.7 2.7a1 1 0 0 0-1.4 0L2.7 7.3a1 1 0 0 0 0 1.4l12.6 12.6a1 1 0 0 0 1.4 0l4.6-4.6a1 1 0 0 0 0-1.4Z"/><path d="m7.5 10.5 2 2"/><path d="m10.5 7.5 2 2"/><path d="m13.5 4.5 2 2"/><path d="m4.5 13.5 2 2"/>')
299
+ );
300
+ registerIcon(
301
+ "magnet",
302
+ svg24('<path d="m6 15-4-4 6.75-6.77a7.79 7.79 0 0 1 11 11L13 22l-4-4 6.39-6.36a2.14 2.14 0 0 0-3-3L6 15"/><path d="m5 8 4 4"/><path d="m12 15 4 4"/>')
303
+ );
304
+ registerIcon(
305
+ "eraser",
306
+ svg24('<path d="m7 21-4.3-4.3a1 1 0 0 1 0-1.4L13 5a2 2 0 0 1 2.8 0l4.2 4.2a2 2 0 0 1 0 2.8L12 20"/><path d="M22 21H7"/><path d="m5 11 9 9"/>')
307
+ );
308
+ registerIcon(
309
+ "pen-lock",
310
+ 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"/>')
311
+ );
312
+ 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"/>'));
313
+ registerIcon(
314
+ "bucket",
315
+ svg24('<path d="m18.5 11.5-7-7L4 12a1.8 1.8 0 0 0 0 2.5l5 5a1.8 1.8 0 0 0 2.5 0Z"/><path d="m5 5 4 4"/><path d="M3.5 13.5h13"/><path d="M21 17.5c0 1.1-.9 2-2 2s-2-.9-2-2c0-1 1.2-1.7 2-3 .8 1.3 2 2 2 3Z"/>')
316
+ );
317
+ registerIcon("type", svg24('<path d="M5 6V4.5h14V6"/><path d="M12 4.5v15"/><path d="M9.5 19.5h5"/>'));
318
+ registerIcon("bold", svg24('<path d="M7 5h6a3.5 3.5 0 0 1 0 7H7Z"/><path d="M7 12h7a3.5 3.5 0 0 1 0 7H7Z"/>', 'stroke-width="2.4"'));
319
+ registerIcon("italic", svg24('<path d="M15 5h-5M14 19H9M14.5 5 10 19"/>', 'stroke-width="2.2"'));
320
+ registerIcon("price-delta", svg24('<path d="M12 4v16"/><path d="M8 8l4-4 4 4"/><path d="M8 16l4 4 4-4"/>'));
321
+ registerIcon("date-delta", svg24('<path d="M4 12h16"/><path d="M8 8l-4 4 4 4"/><path d="M16 8l4 4-4 4"/>'));
322
+ registerIcon("bring-front", svg24('<rect x="8.5" y="8.5" width="7" height="7" rx="1.5"/><path d="M4.5 10.5V6a1.5 1.5 0 0 1 1.5-1.5h4.5"/><path d="M19.5 13.5V18a1.5 1.5 0 0 1-1.5 1.5h-4.5"/>'));
323
+ registerIcon(
324
+ "send-back",
325
+ svg24('<rect x="8.5" y="8.5" width="7" height="7" rx="1.5" fill="currentColor" stroke="none" opacity="0.35"/><path d="M4.5 10.5V6a1.5 1.5 0 0 1 1.5-1.5h4.5"/><path d="M19.5 13.5V18a1.5 1.5 0 0 1-1.5 1.5h-4.5"/><rect x="8.5" y="8.5" width="7" height="7" rx="1.5"/>')
326
+ );
327
+ registerIcon("r-squared", '<svg viewBox="0 0 24 24" width="1em" height="1em" fill="currentColor" stroke="none"><text x="2.5" y="17.5" font-size="14" font-family="serif">R\xB2</text></svg>');
328
+ registerIcon("bands", svg24('<path d="M3 6h18"/><path d="M3 18h18"/><path d="M3 12h18" stroke-dasharray="3 3"/>'));
329
+ registerIcon("dedekind", svg24('<path d="M2 20h20"/><path d="M4 20a8 8 0 0 1 16 0"/><path d="M8 20a4 4 0 0 1 8 0"/><path d="M12 4v16"/>'));
330
+ registerIcon("sonic", svg24('<circle cx="15" cy="12" r="4"/><circle cx="12" cy="12" r="2.5"/><path d="M8 5v14"/>'));
331
+ registerIcon("supersonic", svg24('<circle cx="16" cy="12" r="3.5"/><path d="M6 12 15 6M6 12 15 18"/>'));
332
+
333
+ // src/ui/icons.ts
334
+ function iconEl(id, doc = document) {
335
+ const span = doc.createElement("span");
336
+ span.className = "vela-icon";
337
+ span.setAttribute("aria-hidden", "true");
338
+ const svg = iconMarkup(id);
339
+ if (svg) span.innerHTML = svg;
340
+ return span;
341
+ }
342
+ function runMachine(machine4, props, render) {
343
+ const m = new vanilla.VanillaMachine(machine4, props);
344
+ const unsub = m.subscribe(render);
345
+ m.start();
346
+ render(m.service);
347
+ return {
348
+ service: m.service,
349
+ flush: () => render(m.service),
350
+ stop: () => {
351
+ unsub();
352
+ m.stop();
353
+ }
354
+ };
355
+ }
356
+ var uid = 0;
357
+ function nextUid(prefix) {
358
+ return `${prefix}-${++uid}`;
359
+ }
360
+
361
+ // src/ui/keymap.ts
362
+ var KEY_ALIASES = {
363
+ esc: "escape",
364
+ space: " ",
365
+ plus: "+",
366
+ minus: "-",
367
+ del: "delete",
368
+ return: "enter",
369
+ left: "arrowleft",
370
+ right: "arrowright",
371
+ up: "arrowup",
372
+ down: "arrowdown"
373
+ };
374
+ var MAC_GLYPHS = { meta: "\u2318", ctrl: "\u2303", alt: "\u2325", shift: "\u21E7" };
375
+ function parseChord(spec, mac) {
376
+ const parts = spec.toLowerCase().split("+").map((p) => p.trim()).filter((p, i, a) => p !== "" || a[i - 1] === "");
377
+ const chord = { ctrl: false, meta: false, alt: false, shift: false, key: "" };
378
+ for (const raw of parts) {
379
+ const p = raw === "" ? "+" : raw;
380
+ if (p === "mod") mac ? chord.meta = true : chord.ctrl = true;
381
+ else if (p === "ctrl" || p === "control") chord.ctrl = true;
382
+ else if (p === "meta" || p === "cmd" || p === "command") chord.meta = true;
383
+ else if (p === "alt" || p === "option") chord.alt = true;
384
+ else if (p === "shift") chord.shift = true;
385
+ else chord.key = KEY_ALIASES[p] ?? p;
386
+ }
387
+ return chord;
388
+ }
389
+ function eventMatches(ev, c) {
390
+ return ev.ctrlKey === c.ctrl && ev.metaKey === c.meta && ev.altKey === c.alt && // Shift is part of producing many printable keys ('?', '+') — only enforce it
391
+ // when the chord names a non-printable/letter key where shift is a real modifier.
392
+ (c.key.length > 1 || /^[a-z0-9 ]$/.test(c.key) ? ev.shiftKey === c.shift : true) && ev.key.toLowerCase() === c.key;
393
+ }
394
+ function isEditableTarget(ev) {
395
+ const t = ev.target;
396
+ if (!t || typeof t !== "object") return false;
397
+ const tag = (t.tagName ?? "").toLowerCase();
398
+ if (tag === "input" || tag === "textarea" || tag === "select") return true;
399
+ if (t.isContentEditable === true) return true;
400
+ return (typeof t.getAttribute === "function" ? t.getAttribute("role") : null) === "textbox";
401
+ }
402
+ function displayChord(spec, mac) {
403
+ const c = parseChord(spec, mac);
404
+ const keyLabel = c.key === " " ? "Space" : c.key.length === 1 ? c.key.toUpperCase() : c.key.charAt(0).toUpperCase() + c.key.slice(1);
405
+ if (mac) {
406
+ return (c.ctrl ? MAC_GLYPHS.ctrl : "") + (c.alt ? MAC_GLYPHS.alt : "") + (c.shift ? MAC_GLYPHS.shift : "") + (c.meta ? MAC_GLYPHS.meta : "") + keyLabel;
407
+ }
408
+ const mods = [c.ctrl && "Ctrl", c.alt && "Alt", c.shift && "Shift", c.meta && "Win"].filter(Boolean);
409
+ return [...mods, keyLabel].join("+");
410
+ }
411
+ var KeymapManager = class {
412
+ constructor(opts = {}) {
413
+ this.descriptors = /* @__PURE__ */ new Map();
414
+ this.rebinds = /* @__PURE__ */ new Map();
415
+ this.scopeStack = [];
416
+ this.target = null;
417
+ this.onKeydown = (ev) => {
418
+ this.handleKeydown(ev);
419
+ };
420
+ this.mac = opts.platform !== void 0 ? opts.platform === "mac" : typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
421
+ this.baseScope = opts.baseScope ?? "chart";
422
+ }
423
+ /** Register (or replace, by id) a binding. Returns a disposer. */
424
+ register(desc) {
425
+ this.descriptors.set(desc.id, desc);
426
+ return () => {
427
+ if (this.descriptors.get(desc.id) === desc) this.descriptors.delete(desc.id);
428
+ };
429
+ }
430
+ unregister(id) {
431
+ this.descriptors.delete(id);
432
+ this.rebinds.delete(id);
433
+ }
434
+ /** User-level rebinding: overrides the descriptor's default chords (null resets). */
435
+ rebind(id, keys) {
436
+ if (keys === null) this.rebinds.delete(id);
437
+ else this.rebinds.set(id, Array.isArray(keys) ? [...keys] : [keys]);
438
+ }
439
+ /** Snapshot for a shortcuts help panel / rebinding UI. */
440
+ bindings() {
441
+ return [...this.descriptors.values()].map((d) => {
442
+ const keys = this.activeKeys(d);
443
+ return {
444
+ id: d.id,
445
+ label: d.label,
446
+ category: d.category ?? "General",
447
+ scope: d.scope ?? this.baseScope,
448
+ keys,
449
+ display: keys.map((k) => displayChord(k, this.mac))
450
+ };
451
+ });
452
+ }
453
+ pushScope(scope) {
454
+ this.scopeStack.push(scope);
455
+ return () => this.popScope(scope);
456
+ }
457
+ /** Pops the TOPMOST occurrence of `scope` (tolerates out-of-order teardown). */
458
+ popScope(scope) {
459
+ const i = this.scopeStack.lastIndexOf(scope);
460
+ if (i >= 0) this.scopeStack.splice(i, 1);
461
+ }
462
+ get activeScope() {
463
+ return this.scopeStack[this.scopeStack.length - 1] ?? this.baseScope;
464
+ }
465
+ attach(target) {
466
+ this.detach();
467
+ this.target = target;
468
+ target.addEventListener("keydown", this.onKeydown);
469
+ }
470
+ detach() {
471
+ this.target?.removeEventListener("keydown", this.onKeydown);
472
+ this.target = null;
473
+ }
474
+ /** The matcher — public so hosts/tests can feed events from their own listeners. */
475
+ handleKeydown(ev) {
476
+ const editable = isEditableTarget(ev);
477
+ for (const d of this.descriptors.values()) {
478
+ const scope = d.scope ?? this.baseScope;
479
+ if (scope !== "global" && scope !== this.activeScope) continue;
480
+ if (editable && !d.allowInInput) continue;
481
+ if (d.when && !d.when()) continue;
482
+ for (const spec of this.activeKeys(d)) {
483
+ if (eventMatches(ev, parseChord(spec, this.mac))) {
484
+ if (d.preventDefault !== false) {
485
+ ev.preventDefault?.();
486
+ ev.stopPropagation?.();
487
+ }
488
+ d.run(ev);
489
+ return true;
490
+ }
491
+ }
492
+ }
493
+ return false;
494
+ }
495
+ destroy() {
496
+ this.detach();
497
+ this.descriptors.clear();
498
+ this.rebinds.clear();
499
+ this.scopeStack.length = 0;
500
+ }
501
+ activeKeys(d) {
502
+ return this.rebinds.get(d.id) ?? (Array.isArray(d.keys) ? d.keys : [d.keys]);
503
+ }
504
+ };
505
+ function tooltipController(opts = {}) {
506
+ return {
507
+ machine: tooltip__namespace.machine,
508
+ props: {
509
+ id: nextUid("vela-tooltip"),
510
+ openDelay: opts.openDelay ?? 0,
511
+ closeDelay: opts.closeDelay ?? 0,
512
+ interactive: opts.interactive ?? false,
513
+ ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
514
+ positioning: { placement: opts.placement ?? "top" }
515
+ },
516
+ connect: (service) => tooltip__namespace.connect(service, vanilla.normalizeProps)
517
+ };
518
+ }
519
+
520
+ // src/ui/components/tooltip/styles.ts
521
+ var TOOLTIP_STYLE_ID = "vela-ui-tooltip";
522
+ var TOOLTIP_CSS = `
523
+ .vela-tooltip {
524
+ background: var(--vela-bg);
525
+ color: var(--vela-fg);
526
+ border: 1px solid var(--vela-border-soft);
527
+ border-radius: var(--vela-radius-md);
528
+ box-shadow: var(--vela-shadow);
529
+ font-size: var(--vela-font-size-md);
530
+ line-height: 1.4;
531
+ padding: var(--vela-space-1) var(--vela-space-2);
532
+ max-width: 280px;
533
+ pointer-events: none;
534
+ z-index: var(--vela-z-tooltip);
535
+ }
536
+ .vela-tooltip[data-interactive] { pointer-events: auto; }
537
+ .vela-tooltip[data-state='open'] { animation: vela-tooltip-in 0.12s ease; }
538
+ @keyframes vela-tooltip-in {
539
+ from { opacity: 0; transform: scale(0.97); }
540
+ to { opacity: 1; transform: scale(1); }
541
+ }
542
+ `;
543
+ function resolveHost(trigger, host) {
544
+ return host ?? trigger.closest(".vela-ui") ?? trigger.ownerDocument.body;
545
+ }
546
+ var Tooltip = class {
547
+ constructor(trigger, opts) {
548
+ this.trigger = trigger;
549
+ const doc = trigger.ownerDocument;
550
+ injectStyles(TOOLTIP_STYLE_ID, TOOLTIP_CSS, doc);
551
+ this.positioner = doc.createElement("div");
552
+ this.positioner.className = "vela-ui-layer";
553
+ this.content = doc.createElement("div");
554
+ this.content.className = "vela-tooltip";
555
+ this.positioner.appendChild(this.content);
556
+ resolveHost(trigger, opts.host).appendChild(this.positioner);
557
+ this.setContent(opts.content);
558
+ const ctrl = tooltipController(opts);
559
+ const mid = String(ctrl.props.id);
560
+ if (opts.triggerId) trigger.id = opts.triggerId;
561
+ this.handle = runMachine(ctrl.machine, ctrl.props, (service) => {
562
+ const api = ctrl.connect(service);
563
+ vanilla.spreadProps(trigger, api.getTriggerProps(), mid);
564
+ vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
565
+ vanilla.spreadProps(this.content, api.getContentProps(), mid);
566
+ });
567
+ }
568
+ setContent(content) {
569
+ this.content.replaceChildren(typeof content === "function" ? content() : content);
570
+ }
571
+ destroy() {
572
+ this.handle.stop();
573
+ this.positioner.remove();
574
+ this.trigger.removeAttribute("data-scope");
575
+ }
576
+ };
577
+ function menuController(opts) {
578
+ return {
579
+ machine: menu__namespace.machine,
580
+ props: {
581
+ id: opts.id ?? nextUid("vela-menu"),
582
+ ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
583
+ positioning: { placement: opts.placement ?? "bottom-start" },
584
+ onSelect: (d) => opts.onSelect?.(d.value),
585
+ onOpenChange: (d) => opts.onOpenChange?.(d.open)
586
+ },
587
+ connect: (service) => menu__namespace.connect(service, vanilla.normalizeProps)
588
+ };
589
+ }
590
+
591
+ // src/ui/components/menu/styles.ts
592
+ var MENU_STYLE_ID = "vela-ui-menu";
593
+ var MENU_CSS = `
594
+ .vela-menu {
595
+ /* The list is a <ul>: without this, block rows (the separator) paint a ::marker dot. */
596
+ list-style: none;
597
+ margin: 0;
598
+ background: var(--vela-surface-elev);
599
+ color: var(--vela-fg);
600
+ border: 1px solid var(--vela-border-strong);
601
+ border-radius: 6px;
602
+ box-shadow: var(--vela-shadow);
603
+ padding: 6px;
604
+ min-width: 180px;
605
+ max-height: 60vh;
606
+ overflow-y: auto;
607
+ font-size: 13px;
608
+ z-index: var(--vela-z-menu);
609
+ outline: none;
610
+ }
611
+ .vela-menu[data-state='open'] { animation: vela-menu-in var(--vela-dur-fast) var(--vela-ease); }
612
+ @keyframes vela-menu-in {
613
+ from { opacity: 0; transform: translateY(-3px); }
614
+ to { opacity: 1; transform: translateY(0); }
615
+ }
616
+ .vela-menu-item {
617
+ display: flex;
618
+ align-items: center;
619
+ gap: 10px;
620
+ padding: 7px 10px;
621
+ border-radius: 4px;
622
+ cursor: pointer;
623
+ user-select: none;
624
+ white-space: nowrap;
625
+ }
626
+ .vela-menu-item[data-highlighted] { background: var(--vela-hover); }
627
+ .vela-menu-item[data-disabled] { opacity: 0.4; cursor: default; }
628
+ .vela-menu-item .vela-icon { width: 16px; height: 16px; font-size: 16px; justify-content: center; color: var(--vela-fg-muted); }
629
+ .vela-menu-item .vela-menu-label { flex: 1 1 auto; }
630
+ .vela-menu-item .vela-menu-hint { color: var(--vela-fg-faint); font-size: var(--vela-font-size-sm); }
631
+ /* Active entry: a brighter row surface + bright ink \u2014 no accent recolor, no glyph.
632
+ Declared after [data-highlighted] so the selected surface wins while hovered. */
633
+ .vela-menu-item[data-checked] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
634
+ .vela-menu-item[data-checked] .vela-icon { color: var(--vela-fg-bright); }
635
+ /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
636
+ same control language as the settings dialog's toggles. */
637
+ .vela-menu-switch {
638
+ order: 99;
639
+ margin-left: auto;
640
+ position: relative;
641
+ flex: none;
642
+ width: 34px;
643
+ height: 18px;
644
+ border-radius: 9px;
645
+ background: var(--vela-surface-overlay);
646
+ border: 1px solid var(--vela-border-soft);
647
+ transition: background 0.16s ease, border-color 0.16s ease;
648
+ }
649
+ .vela-menu-switch::after {
650
+ content: '';
651
+ position: absolute;
652
+ top: 2px;
653
+ left: 2px;
654
+ width: 12px;
655
+ height: 12px;
656
+ border-radius: 50%;
657
+ background: var(--vela-fg-muted);
658
+ transition: transform 0.16s ease, background 0.16s ease;
659
+ }
660
+ .vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
661
+ .vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
662
+ .vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
663
+ /* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
664
+ list is open so the path you came down remains readable. */
665
+ .vela-menu-item .vela-menu-arrow {
666
+ order: 99;
667
+ margin-left: auto;
668
+ width: 12px;
669
+ flex: none;
670
+ font-size: 11px;
671
+ color: var(--vela-fg-faint);
672
+ }
673
+ .vela-menu-item[data-state='open'] { background: var(--vela-hover); }
674
+ `;
675
+ var Surface = class _Surface {
676
+ constructor(doc, opts) {
677
+ this.items = [];
678
+ /** Branch item id → the surface it opens. */
679
+ this.subs = /* @__PURE__ */ new Map();
680
+ this.doc = doc;
681
+ this.host = opts.host;
682
+ this.onSelect = opts.onSelect;
683
+ this.positioner = doc.createElement("div");
684
+ this.positioner.className = "vela-ui-layer";
685
+ this.list = doc.createElement("ul");
686
+ this.list.className = "vela-menu";
687
+ if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
688
+ this.positioner.appendChild(this.list);
689
+ this.host.appendChild(this.positioner);
690
+ this.ctrl = menuController({
691
+ items: [],
692
+ id: opts.id,
693
+ placement: opts.placement,
694
+ onSelect: (id) => this.onSelect(id),
695
+ onOpenChange: opts.onOpenChange,
696
+ triggerId: opts.triggerId
697
+ });
698
+ this.mid = String(this.ctrl.props.id);
699
+ const trigger = opts.trigger;
700
+ if (opts.triggerId && trigger) trigger.id = opts.triggerId;
701
+ this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
702
+ const api = this.ctrl.connect(service);
703
+ if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
704
+ vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
705
+ vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
706
+ this.project(api);
707
+ });
708
+ }
709
+ get api() {
710
+ return this.ctrl.connect(this.handle.service);
711
+ }
712
+ setItems(items) {
713
+ this.items = items;
714
+ for (const sub of this.subs.values()) sub.destroy();
715
+ this.subs.clear();
716
+ this.build();
717
+ const api = this.api;
718
+ for (const sub of this.subs.values()) {
719
+ api.setChild(sub.handle.service);
720
+ sub.api.setParent(this.handle.service);
721
+ }
722
+ this.handle.flush();
723
+ if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
724
+ }
725
+ destroy() {
726
+ for (const sub of this.subs.values()) sub.destroy();
727
+ this.subs.clear();
728
+ this.handle.stop();
729
+ this.positioner.remove();
730
+ }
731
+ byId(id) {
732
+ return this.items.find((i) => i.id === id);
733
+ }
734
+ /** Push the machine's item props onto the rendered rows. A branch takes the submenu
735
+ * trigger props, which carry the child's ids and hover handlers; a leaf takes plain ones. */
736
+ project(api) {
737
+ for (const li of this.list.children) {
738
+ const id = li.dataset.veiId;
739
+ if (!id) continue;
740
+ const sub = this.subs.get(id);
741
+ if (sub) {
742
+ vanilla.spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
743
+ continue;
744
+ }
745
+ const item = this.byId(id);
746
+ vanilla.spreadProps(li, api.getItemProps({ value: id, disabled: item?.disabled, closeOnSelect: item?.toggle ? false : void 0 }), this.mid);
747
+ }
748
+ }
749
+ build() {
750
+ const doc = this.doc;
751
+ this.list.replaceChildren();
752
+ for (const item of this.items) {
753
+ if (item.separatorBefore) {
754
+ const sep = doc.createElement("li");
755
+ sep.className = "vela-menu-sep";
756
+ sep.setAttribute("role", "separator");
757
+ this.list.appendChild(sep);
758
+ }
759
+ const branch = item.submenu !== void 0 && item.submenu.length > 0;
760
+ const li = doc.createElement("li");
761
+ li.className = "vela-menu-item";
762
+ li.dataset.veiId = item.id;
763
+ if (item.toggle) {
764
+ li.dataset.toggle = "1";
765
+ } else if (!branch && item.checked) {
766
+ li.dataset.checked = "1";
767
+ }
768
+ if (item.icon) li.appendChild(iconEl(item.icon, doc));
769
+ const label = doc.createElement("span");
770
+ label.className = "vela-menu-label";
771
+ label.textContent = item.label;
772
+ li.appendChild(label);
773
+ if (item.hint) {
774
+ const hint = doc.createElement("span");
775
+ hint.className = "vela-menu-hint";
776
+ hint.textContent = item.hint;
777
+ li.appendChild(hint);
778
+ }
779
+ if (item.toggle) {
780
+ const sw = doc.createElement("span");
781
+ sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
782
+ sw.setAttribute("aria-hidden", "true");
783
+ li.appendChild(sw);
784
+ }
785
+ if (branch) {
786
+ li.dataset.branch = "1";
787
+ const arrow = iconEl("chevron-right", doc);
788
+ arrow.classList.add("vela-menu-arrow");
789
+ li.appendChild(arrow);
790
+ const sub = new _Surface(doc, {
791
+ host: this.host,
792
+ placement: "right-start",
793
+ onSelect: this.onSelect,
794
+ id: `${this.mid}--${item.id}`
795
+ });
796
+ sub.setItems(item.submenu ?? []);
797
+ this.subs.set(item.id, sub);
798
+ }
799
+ this.list.appendChild(li);
800
+ }
801
+ }
802
+ };
803
+ var Menu = class {
804
+ constructor(opts) {
805
+ const anchor = opts.trigger ?? opts.host ?? document.body;
806
+ const doc = anchor.ownerDocument;
807
+ injectStyles(MENU_STYLE_ID, MENU_CSS, doc);
808
+ const host = opts.host ?? anchor.closest?.(".vela-ui") ?? doc.body;
809
+ this.root = new Surface(doc, {
810
+ host,
811
+ placement: opts.placement,
812
+ onSelect: (id) => opts.onSelect?.(id),
813
+ onOpenChange: opts.onOpenChange,
814
+ trigger: opts.trigger,
815
+ triggerId: opts.triggerId,
816
+ id: opts.id,
817
+ minWidth: opts.minWidth
818
+ });
819
+ this.root.setItems(opts.items);
820
+ }
821
+ get api() {
822
+ return this.root.api;
823
+ }
824
+ open() {
825
+ this.api.setOpen(true);
826
+ }
827
+ /** Open anchored to a viewport point (context menus). */
828
+ openAt(clientX, clientY) {
829
+ const api = this.api;
830
+ api.setOpen(true);
831
+ api.reposition({ getAnchorRect: () => ({ x: clientX, y: clientY, width: 0, height: 0 }) });
832
+ }
833
+ close() {
834
+ this.api.setOpen(false);
835
+ }
836
+ /** Swap the item descriptors (e.g. checked states) and re-project. */
837
+ setItems(items) {
838
+ this.root.setItems(items);
839
+ }
840
+ destroy() {
841
+ this.root.destroy();
842
+ }
843
+ };
844
+ function dialogController(opts = {}) {
845
+ return {
846
+ machine: dialog__namespace.machine,
847
+ props: {
848
+ id: nextUid("vela-dialog"),
849
+ modal: opts.modal ?? true,
850
+ closeOnEscape: opts.closeOnEscape ?? true,
851
+ closeOnInteractOutside: opts.closeOnInteractOutside ?? false,
852
+ onOpenChange: (d) => opts.onOpenChange?.(d.open)
853
+ },
854
+ connect: (service) => dialog__namespace.connect(service, vanilla.normalizeProps)
855
+ };
856
+ }
857
+
858
+ // src/ui/components/dialog/styles.ts
859
+ var DIALOG_STYLE_ID = "vela-ui-dialog";
860
+ var DIALOG_CSS = `
861
+ .vela-dialog-backdrop {
862
+ position: fixed;
863
+ inset: 0;
864
+ background: var(--vela-backdrop);
865
+ z-index: var(--vela-z-dialog);
866
+ }
867
+ /* Non-dimming variant: still catches outside clicks, but the page stays readable. */
868
+ .vela-dialog-backdrop--clear { background: transparent; }
869
+ .vela-dialog-positioner {
870
+ position: fixed;
871
+ inset: 0;
872
+ display: flex;
873
+ align-items: flex-start;
874
+ justify-content: center;
875
+ padding-top: 10vh;
876
+ z-index: var(--vela-z-dialog);
877
+ }
878
+ .vela-dialog {
879
+ background: var(--vela-surface);
880
+ color: var(--vela-fg);
881
+ border: 1px solid var(--vela-border-strong);
882
+ border-radius: 10px;
883
+ box-shadow: var(--vela-shadow-dialog);
884
+ font-size: 13px;
885
+ min-width: 300px;
886
+ max-width: min(92vw, 560px);
887
+ max-height: 70vh;
888
+ display: flex;
889
+ flex-direction: column;
890
+ overflow: hidden;
891
+ outline: none;
892
+ }
893
+ .vela-dialog[data-state='open'] { animation: vela-dialog-in var(--vela-dur-med) var(--vela-ease); }
894
+ @keyframes vela-dialog-in {
895
+ from { opacity: 0; transform: translateY(6px) scale(0.98); }
896
+ to { opacity: 1; transform: translateY(0) scale(1); }
897
+ }
898
+ .vela-dialog-header {
899
+ display: flex;
900
+ align-items: center;
901
+ padding: 9px 9px 9px 16px;
902
+ border-bottom: 1px solid var(--vela-border);
903
+ user-select: none;
904
+ }
905
+ .vela-dialog-title { flex: 1; font-size: 17px; font-weight: 600; letter-spacing: 0.2px; color: var(--vela-fg-bright); }
906
+ .vela-dialog-close {
907
+ all: unset;
908
+ cursor: pointer;
909
+ width: 30px;
910
+ height: 30px;
911
+ display: flex;
912
+ align-items: center;
913
+ justify-content: center;
914
+ border-radius: 4px;
915
+ color: var(--vela-fg-muted);
916
+ line-height: 1;
917
+ font-size: 15px;
918
+ }
919
+ .vela-dialog-close:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
920
+ .vela-dialog-body { padding: var(--vela-space-4); overflow: auto; }
921
+ .vela-dialog-body::-webkit-scrollbar { width: 8px; }
922
+ .vela-dialog-body::-webkit-scrollbar-thumb {
923
+ background: var(--vela-scroll);
924
+ border-radius: 4px;
925
+ border: 2px solid transparent;
926
+ background-clip: padding-box;
927
+ }
928
+ `;
929
+ var Dialog = class {
930
+ constructor(opts = {}) {
931
+ const doc = (opts.host ?? document.body).ownerDocument;
932
+ injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
933
+ const host = opts.host ?? doc.body;
934
+ this.backdrop = doc.createElement("div");
935
+ this.backdrop.className = "vela-dialog-backdrop vela-ui-layer";
936
+ if (opts.dimBackdrop !== true) this.backdrop.classList.add("vela-dialog-backdrop--clear");
937
+ this.positioner = doc.createElement("div");
938
+ this.positioner.className = "vela-dialog-positioner vela-ui-layer";
939
+ this.panel = doc.createElement("div");
940
+ this.panel.className = "vela-dialog";
941
+ const header = doc.createElement("div");
942
+ header.className = "vela-dialog-header";
943
+ if (opts.draggable) {
944
+ header.style.cursor = "move";
945
+ let sx = 0, sy = 0, ox = 0, oy = 0, dragging = false;
946
+ header.addEventListener("pointerdown", (e) => {
947
+ if (e.target.closest(".vela-dialog-close")) return;
948
+ dragging = true;
949
+ sx = e.clientX - ox;
950
+ sy = e.clientY - oy;
951
+ header.setPointerCapture(e.pointerId);
952
+ });
953
+ header.addEventListener("pointermove", (e) => {
954
+ if (!dragging) return;
955
+ ox = e.clientX - sx;
956
+ oy = e.clientY - sy;
957
+ this.panel.style.transform = `translate(${ox}px, ${oy}px)`;
958
+ });
959
+ header.addEventListener("pointerup", () => dragging = false);
960
+ }
961
+ const title = doc.createElement("div");
962
+ title.className = "vela-dialog-title";
963
+ title.textContent = opts.title ?? "";
964
+ const close = doc.createElement("button");
965
+ close.className = "vela-dialog-close";
966
+ close.appendChild(iconEl("close", doc));
967
+ header.append(title, close);
968
+ this.body = doc.createElement("div");
969
+ this.body.className = "vela-dialog-body";
970
+ if (opts.content instanceof Node) this.body.appendChild(opts.content);
971
+ else if (typeof opts.content === "function") opts.content(this.body);
972
+ this.panel.append(header, this.body);
973
+ this.positioner.appendChild(this.panel);
974
+ host.append(this.backdrop, this.positioner);
975
+ this.ctrl = dialogController(opts);
976
+ const mid = String(this.ctrl.props.id);
977
+ this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
978
+ const api = this.ctrl.connect(service);
979
+ vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
980
+ vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
981
+ vanilla.spreadProps(this.panel, api.getContentProps(), mid);
982
+ vanilla.spreadProps(title, api.getTitleProps(), mid);
983
+ vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
984
+ this.backdrop.style.display = api.open ? "" : "none";
985
+ this.positioner.style.display = api.open ? "" : "none";
986
+ });
987
+ }
988
+ get open() {
989
+ return this.ctrl.connect(this.handle.service).open;
990
+ }
991
+ show() {
992
+ this.ctrl.connect(this.handle.service).setOpen(true);
993
+ }
994
+ hide() {
995
+ this.ctrl.connect(this.handle.service).setOpen(false);
996
+ }
997
+ destroy() {
998
+ this.handle.stop();
999
+ this.backdrop.remove();
1000
+ this.positioner.remove();
1001
+ }
1002
+ };
1003
+
1004
+ Object.defineProperty(exports, "normalizeProps", {
1005
+ enumerable: true,
1006
+ get: function () { return vanilla.normalizeProps; }
1007
+ });
1008
+ Object.defineProperty(exports, "spreadProps", {
1009
+ enumerable: true,
1010
+ get: function () { return vanilla.spreadProps; }
1011
+ });
1012
+ exports.Dialog = Dialog;
1013
+ exports.KeymapManager = KeymapManager;
1014
+ exports.Menu = Menu;
1015
+ exports.Tooltip = Tooltip;
1016
+ exports.applyPlotOverlayTokens = applyPlotOverlayTokens;
1017
+ exports.applyThemeTokens = applyThemeTokens;
1018
+ exports.dialogController = dialogController;
1019
+ exports.ensureUIHost = ensureUIHost;
1020
+ exports.iconEl = iconEl;
1021
+ exports.iconMarkup = iconMarkup;
1022
+ exports.injectStyles = injectStyles;
1023
+ exports.menuController = menuController;
1024
+ exports.nextUid = nextUid;
1025
+ exports.registerIcon = registerIcon;
1026
+ exports.runMachine = runMachine;
1027
+ exports.svg16 = svg16;
1028
+ exports.svg24 = svg24;
1029
+ exports.tooltipController = tooltipController;
1030
+ exports.withAlpha = withAlpha;