@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
@@ -0,0 +1,7 @@
1
+ // src/core/model/identity.ts
2
+ function stableSeriesId(parts) {
3
+ const normTitle = parts.title.trim().toLowerCase().replace(/\s+/g, "-");
4
+ return `${parts.instanceId}:${parts.kind}:${normTitle}#${parts.ordinal}`;
5
+ }
6
+
7
+ export { stableSeriesId };
@@ -0,0 +1,698 @@
1
+ import { STATIC_TOKENS, themeTokens } from './chunk-KM6LHB3Y.js';
2
+ import { injectStyles, iconEl } from './chunk-GYD2THPV.js';
3
+ import { VanillaMachine, normalizeProps, spreadProps } from '@zag-js/vanilla';
4
+ export { normalizeProps, spreadProps } from '@zag-js/vanilla';
5
+ import * as tooltip from '@zag-js/tooltip';
6
+ import * as menu from '@zag-js/menu';
7
+ import * as dialog from '@zag-js/dialog';
8
+
9
+ // src/ui/tokens.ts
10
+ var STATIC_ID = "vela-ui-tokens";
11
+ var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
12
+ var STATIC_CSS = `
13
+ .vela-ui, .vela-ui-layer {
14
+ ${STATIC_DECLS}
15
+ font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
16
+ box-sizing: border-box;
17
+ }
18
+ .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
19
+ .vela-icon { display: inline-flex; align-items: center; flex: none; }
20
+ .vela-icon svg { display: block; }
21
+ `;
22
+ function applyThemeTokens(el, t) {
23
+ const tokens = themeTokens(t);
24
+ for (const key in tokens) el.style.setProperty(key, tokens[key]);
25
+ }
26
+ function applyPlotOverlayTokens(host, base, config) {
27
+ const layout = config?.layout;
28
+ const t = layout?.background && layout.textColor ? { ...base, background: layout.background, textColor: layout.textColor } : base;
29
+ applyThemeTokens(host, t);
30
+ }
31
+ function ensureUIHost(el, theme) {
32
+ injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
33
+ el.classList.add("vela-ui");
34
+ if (theme) applyThemeTokens(el, theme);
35
+ }
36
+ function runMachine(machine4, props, render) {
37
+ const m = new VanillaMachine(machine4, props);
38
+ const unsub = m.subscribe(render);
39
+ m.start();
40
+ render(m.service);
41
+ return {
42
+ service: m.service,
43
+ flush: () => render(m.service),
44
+ stop: () => {
45
+ unsub();
46
+ m.stop();
47
+ }
48
+ };
49
+ }
50
+ var uid = 0;
51
+ function nextUid(prefix) {
52
+ return `${prefix}-${++uid}`;
53
+ }
54
+
55
+ // src/ui/keymap.ts
56
+ var KEY_ALIASES = {
57
+ esc: "escape",
58
+ space: " ",
59
+ plus: "+",
60
+ minus: "-",
61
+ del: "delete",
62
+ return: "enter",
63
+ left: "arrowleft",
64
+ right: "arrowright",
65
+ up: "arrowup",
66
+ down: "arrowdown"
67
+ };
68
+ var MAC_GLYPHS = { meta: "\u2318", ctrl: "\u2303", alt: "\u2325", shift: "\u21E7" };
69
+ function parseChord(spec, mac) {
70
+ const parts = spec.toLowerCase().split("+").map((p) => p.trim()).filter((p, i, a) => p !== "" || a[i - 1] === "");
71
+ const chord = { ctrl: false, meta: false, alt: false, shift: false, key: "" };
72
+ for (const raw of parts) {
73
+ const p = raw === "" ? "+" : raw;
74
+ if (p === "mod") mac ? chord.meta = true : chord.ctrl = true;
75
+ else if (p === "ctrl" || p === "control") chord.ctrl = true;
76
+ else if (p === "meta" || p === "cmd" || p === "command") chord.meta = true;
77
+ else if (p === "alt" || p === "option") chord.alt = true;
78
+ else if (p === "shift") chord.shift = true;
79
+ else chord.key = KEY_ALIASES[p] ?? p;
80
+ }
81
+ return chord;
82
+ }
83
+ function eventMatches(ev, c) {
84
+ return ev.ctrlKey === c.ctrl && ev.metaKey === c.meta && ev.altKey === c.alt && // Shift is part of producing many printable keys ('?', '+') — only enforce it
85
+ // when the chord names a non-printable/letter key where shift is a real modifier.
86
+ (c.key.length > 1 || /^[a-z0-9 ]$/.test(c.key) ? ev.shiftKey === c.shift : true) && ev.key.toLowerCase() === c.key;
87
+ }
88
+ function isEditableTarget(ev) {
89
+ const t = ev.target;
90
+ if (!t || typeof t !== "object") return false;
91
+ const tag = (t.tagName ?? "").toLowerCase();
92
+ if (tag === "input" || tag === "textarea" || tag === "select") return true;
93
+ if (t.isContentEditable === true) return true;
94
+ return (typeof t.getAttribute === "function" ? t.getAttribute("role") : null) === "textbox";
95
+ }
96
+ function displayChord(spec, mac) {
97
+ const c = parseChord(spec, mac);
98
+ const keyLabel = c.key === " " ? "Space" : c.key.length === 1 ? c.key.toUpperCase() : c.key.charAt(0).toUpperCase() + c.key.slice(1);
99
+ if (mac) {
100
+ return (c.ctrl ? MAC_GLYPHS.ctrl : "") + (c.alt ? MAC_GLYPHS.alt : "") + (c.shift ? MAC_GLYPHS.shift : "") + (c.meta ? MAC_GLYPHS.meta : "") + keyLabel;
101
+ }
102
+ const mods = [c.ctrl && "Ctrl", c.alt && "Alt", c.shift && "Shift", c.meta && "Win"].filter(Boolean);
103
+ return [...mods, keyLabel].join("+");
104
+ }
105
+ var KeymapManager = class {
106
+ constructor(opts = {}) {
107
+ this.descriptors = /* @__PURE__ */ new Map();
108
+ this.rebinds = /* @__PURE__ */ new Map();
109
+ this.scopeStack = [];
110
+ this.target = null;
111
+ this.onKeydown = (ev) => {
112
+ this.handleKeydown(ev);
113
+ };
114
+ this.mac = opts.platform !== void 0 ? opts.platform === "mac" : typeof navigator !== "undefined" && /mac|iphone|ipad/i.test(navigator.platform ?? "");
115
+ this.baseScope = opts.baseScope ?? "chart";
116
+ }
117
+ /** Register (or replace, by id) a binding. Returns a disposer. */
118
+ register(desc) {
119
+ this.descriptors.set(desc.id, desc);
120
+ return () => {
121
+ if (this.descriptors.get(desc.id) === desc) this.descriptors.delete(desc.id);
122
+ };
123
+ }
124
+ unregister(id) {
125
+ this.descriptors.delete(id);
126
+ this.rebinds.delete(id);
127
+ }
128
+ /** User-level rebinding: overrides the descriptor's default chords (null resets). */
129
+ rebind(id, keys) {
130
+ if (keys === null) this.rebinds.delete(id);
131
+ else this.rebinds.set(id, Array.isArray(keys) ? [...keys] : [keys]);
132
+ }
133
+ /** Snapshot for a shortcuts help panel / rebinding UI. */
134
+ bindings() {
135
+ return [...this.descriptors.values()].map((d) => {
136
+ const keys = this.activeKeys(d);
137
+ return {
138
+ id: d.id,
139
+ label: d.label,
140
+ category: d.category ?? "General",
141
+ scope: d.scope ?? this.baseScope,
142
+ keys,
143
+ display: keys.map((k) => displayChord(k, this.mac))
144
+ };
145
+ });
146
+ }
147
+ pushScope(scope) {
148
+ this.scopeStack.push(scope);
149
+ return () => this.popScope(scope);
150
+ }
151
+ /** Pops the TOPMOST occurrence of `scope` (tolerates out-of-order teardown). */
152
+ popScope(scope) {
153
+ const i = this.scopeStack.lastIndexOf(scope);
154
+ if (i >= 0) this.scopeStack.splice(i, 1);
155
+ }
156
+ get activeScope() {
157
+ return this.scopeStack[this.scopeStack.length - 1] ?? this.baseScope;
158
+ }
159
+ attach(target) {
160
+ this.detach();
161
+ this.target = target;
162
+ target.addEventListener("keydown", this.onKeydown);
163
+ }
164
+ detach() {
165
+ this.target?.removeEventListener("keydown", this.onKeydown);
166
+ this.target = null;
167
+ }
168
+ /** The matcher — public so hosts/tests can feed events from their own listeners. */
169
+ handleKeydown(ev) {
170
+ const editable = isEditableTarget(ev);
171
+ for (const d of this.descriptors.values()) {
172
+ const scope = d.scope ?? this.baseScope;
173
+ if (scope !== "global" && scope !== this.activeScope) continue;
174
+ if (editable && !d.allowInInput) continue;
175
+ if (d.when && !d.when()) continue;
176
+ for (const spec of this.activeKeys(d)) {
177
+ if (eventMatches(ev, parseChord(spec, this.mac))) {
178
+ if (d.preventDefault !== false) {
179
+ ev.preventDefault?.();
180
+ ev.stopPropagation?.();
181
+ }
182
+ d.run(ev);
183
+ return true;
184
+ }
185
+ }
186
+ }
187
+ return false;
188
+ }
189
+ destroy() {
190
+ this.detach();
191
+ this.descriptors.clear();
192
+ this.rebinds.clear();
193
+ this.scopeStack.length = 0;
194
+ }
195
+ activeKeys(d) {
196
+ return this.rebinds.get(d.id) ?? (Array.isArray(d.keys) ? d.keys : [d.keys]);
197
+ }
198
+ };
199
+ function tooltipController(opts = {}) {
200
+ return {
201
+ machine: tooltip.machine,
202
+ props: {
203
+ id: nextUid("vela-tooltip"),
204
+ openDelay: opts.openDelay ?? 0,
205
+ closeDelay: opts.closeDelay ?? 0,
206
+ interactive: opts.interactive ?? false,
207
+ ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
208
+ positioning: { placement: opts.placement ?? "top" }
209
+ },
210
+ connect: (service) => tooltip.connect(service, normalizeProps)
211
+ };
212
+ }
213
+
214
+ // src/ui/components/tooltip/styles.ts
215
+ var TOOLTIP_STYLE_ID = "vela-ui-tooltip";
216
+ var TOOLTIP_CSS = `
217
+ .vela-tooltip {
218
+ background: var(--vela-bg);
219
+ color: var(--vela-fg);
220
+ border: 1px solid var(--vela-border-soft);
221
+ border-radius: var(--vela-radius-md);
222
+ box-shadow: var(--vela-shadow);
223
+ font-size: var(--vela-font-size-md);
224
+ line-height: 1.4;
225
+ padding: var(--vela-space-1) var(--vela-space-2);
226
+ max-width: 280px;
227
+ pointer-events: none;
228
+ z-index: var(--vela-z-tooltip);
229
+ }
230
+ .vela-tooltip[data-interactive] { pointer-events: auto; }
231
+ .vela-tooltip[data-state='open'] { animation: vela-tooltip-in 0.12s ease; }
232
+ @keyframes vela-tooltip-in {
233
+ from { opacity: 0; transform: scale(0.97); }
234
+ to { opacity: 1; transform: scale(1); }
235
+ }
236
+ `;
237
+ function resolveHost(trigger, host) {
238
+ return host ?? trigger.closest(".vela-ui") ?? trigger.ownerDocument.body;
239
+ }
240
+ var Tooltip = class {
241
+ constructor(trigger, opts) {
242
+ this.trigger = trigger;
243
+ const doc = trigger.ownerDocument;
244
+ injectStyles(TOOLTIP_STYLE_ID, TOOLTIP_CSS, doc);
245
+ this.positioner = doc.createElement("div");
246
+ this.positioner.className = "vela-ui-layer";
247
+ this.content = doc.createElement("div");
248
+ this.content.className = "vela-tooltip";
249
+ this.positioner.appendChild(this.content);
250
+ resolveHost(trigger, opts.host).appendChild(this.positioner);
251
+ this.setContent(opts.content);
252
+ const ctrl = tooltipController(opts);
253
+ const mid = String(ctrl.props.id);
254
+ if (opts.triggerId) trigger.id = opts.triggerId;
255
+ this.handle = runMachine(ctrl.machine, ctrl.props, (service) => {
256
+ const api = ctrl.connect(service);
257
+ spreadProps(trigger, api.getTriggerProps(), mid);
258
+ spreadProps(this.positioner, api.getPositionerProps(), mid);
259
+ spreadProps(this.content, api.getContentProps(), mid);
260
+ });
261
+ }
262
+ setContent(content) {
263
+ this.content.replaceChildren(typeof content === "function" ? content() : content);
264
+ }
265
+ destroy() {
266
+ this.handle.stop();
267
+ this.positioner.remove();
268
+ this.trigger.removeAttribute("data-scope");
269
+ }
270
+ };
271
+ function menuController(opts) {
272
+ return {
273
+ machine: menu.machine,
274
+ props: {
275
+ id: opts.id ?? nextUid("vela-menu"),
276
+ ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
277
+ positioning: { placement: opts.placement ?? "bottom-start" },
278
+ onSelect: (d) => opts.onSelect?.(d.value),
279
+ onOpenChange: (d) => opts.onOpenChange?.(d.open)
280
+ },
281
+ connect: (service) => menu.connect(service, normalizeProps)
282
+ };
283
+ }
284
+
285
+ // src/ui/components/menu/styles.ts
286
+ var MENU_STYLE_ID = "vela-ui-menu";
287
+ var MENU_CSS = `
288
+ .vela-menu {
289
+ /* The list is a <ul>: without this, block rows (the separator) paint a ::marker dot. */
290
+ list-style: none;
291
+ margin: 0;
292
+ background: var(--vela-surface-elev);
293
+ color: var(--vela-fg);
294
+ border: 1px solid var(--vela-border-strong);
295
+ border-radius: 6px;
296
+ box-shadow: var(--vela-shadow);
297
+ padding: 6px;
298
+ min-width: 180px;
299
+ max-height: 60vh;
300
+ overflow-y: auto;
301
+ font-size: 13px;
302
+ z-index: var(--vela-z-menu);
303
+ outline: none;
304
+ }
305
+ .vela-menu[data-state='open'] { animation: vela-menu-in var(--vela-dur-fast) var(--vela-ease); }
306
+ @keyframes vela-menu-in {
307
+ from { opacity: 0; transform: translateY(-3px); }
308
+ to { opacity: 1; transform: translateY(0); }
309
+ }
310
+ .vela-menu-item {
311
+ display: flex;
312
+ align-items: center;
313
+ gap: 10px;
314
+ padding: 7px 10px;
315
+ border-radius: 4px;
316
+ cursor: pointer;
317
+ user-select: none;
318
+ white-space: nowrap;
319
+ }
320
+ .vela-menu-item[data-highlighted] { background: var(--vela-hover); }
321
+ .vela-menu-item[data-disabled] { opacity: 0.4; cursor: default; }
322
+ .vela-menu-item .vela-icon { width: 16px; height: 16px; font-size: 16px; justify-content: center; color: var(--vela-fg-muted); }
323
+ .vela-menu-item .vela-menu-label { flex: 1 1 auto; }
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); }
329
+ /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
330
+ same control language as the settings dialog's toggles. */
331
+ .vela-menu-switch {
332
+ order: 99;
333
+ margin-left: auto;
334
+ position: relative;
335
+ flex: none;
336
+ width: 34px;
337
+ height: 18px;
338
+ border-radius: 9px;
339
+ background: var(--vela-surface-overlay);
340
+ border: 1px solid var(--vela-border-soft);
341
+ transition: background 0.16s ease, border-color 0.16s ease;
342
+ }
343
+ .vela-menu-switch::after {
344
+ content: '';
345
+ position: absolute;
346
+ top: 2px;
347
+ left: 2px;
348
+ width: 12px;
349
+ height: 12px;
350
+ border-radius: 50%;
351
+ background: var(--vela-fg-muted);
352
+ transition: transform 0.16s ease, background 0.16s ease;
353
+ }
354
+ .vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
355
+ .vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
356
+ .vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
357
+ /* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
358
+ list is open so the path you came down remains readable. */
359
+ .vela-menu-item .vela-menu-arrow {
360
+ order: 99;
361
+ margin-left: auto;
362
+ width: 12px;
363
+ flex: none;
364
+ font-size: 11px;
365
+ color: var(--vela-fg-faint);
366
+ }
367
+ .vela-menu-item[data-state='open'] { background: var(--vela-hover); }
368
+ `;
369
+ var Surface = class _Surface {
370
+ constructor(doc, opts) {
371
+ this.items = [];
372
+ /** Branch item id → the surface it opens. */
373
+ this.subs = /* @__PURE__ */ new Map();
374
+ this.doc = doc;
375
+ this.host = opts.host;
376
+ this.onSelect = opts.onSelect;
377
+ this.positioner = doc.createElement("div");
378
+ this.positioner.className = "vela-ui-layer";
379
+ this.list = doc.createElement("ul");
380
+ this.list.className = "vela-menu";
381
+ if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
382
+ this.positioner.appendChild(this.list);
383
+ this.host.appendChild(this.positioner);
384
+ this.ctrl = menuController({
385
+ items: [],
386
+ id: opts.id,
387
+ placement: opts.placement,
388
+ onSelect: (id) => this.onSelect(id),
389
+ onOpenChange: opts.onOpenChange,
390
+ triggerId: opts.triggerId
391
+ });
392
+ this.mid = String(this.ctrl.props.id);
393
+ const trigger = opts.trigger;
394
+ if (opts.triggerId && trigger) trigger.id = opts.triggerId;
395
+ this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
396
+ const api = this.ctrl.connect(service);
397
+ if (trigger) spreadProps(trigger, api.getTriggerProps(), this.mid);
398
+ spreadProps(this.positioner, api.getPositionerProps(), this.mid);
399
+ spreadProps(this.list, api.getContentProps(), this.mid);
400
+ this.project(api);
401
+ });
402
+ }
403
+ get api() {
404
+ return this.ctrl.connect(this.handle.service);
405
+ }
406
+ setItems(items) {
407
+ this.items = items;
408
+ for (const sub of this.subs.values()) sub.destroy();
409
+ this.subs.clear();
410
+ this.build();
411
+ const api = this.api;
412
+ for (const sub of this.subs.values()) {
413
+ api.setChild(sub.handle.service);
414
+ sub.api.setParent(this.handle.service);
415
+ }
416
+ this.handle.flush();
417
+ if (this.subs.size > 0) queueMicrotask(() => this.handle.flush());
418
+ }
419
+ destroy() {
420
+ for (const sub of this.subs.values()) sub.destroy();
421
+ this.subs.clear();
422
+ this.handle.stop();
423
+ this.positioner.remove();
424
+ }
425
+ byId(id) {
426
+ return this.items.find((i) => i.id === id);
427
+ }
428
+ /** Push the machine's item props onto the rendered rows. A branch takes the submenu
429
+ * trigger props, which carry the child's ids and hover handlers; a leaf takes plain ones. */
430
+ project(api) {
431
+ for (const li of this.list.children) {
432
+ const id = li.dataset.veiId;
433
+ if (!id) continue;
434
+ const sub = this.subs.get(id);
435
+ if (sub) {
436
+ spreadProps(li, api.getTriggerItemProps(sub.api), this.mid);
437
+ continue;
438
+ }
439
+ const item = this.byId(id);
440
+ spreadProps(li, api.getItemProps({ value: id, disabled: item?.disabled, closeOnSelect: item?.toggle ? false : void 0 }), this.mid);
441
+ }
442
+ }
443
+ build() {
444
+ const doc = this.doc;
445
+ this.list.replaceChildren();
446
+ for (const item of this.items) {
447
+ if (item.separatorBefore) {
448
+ const sep = doc.createElement("li");
449
+ sep.className = "vela-menu-sep";
450
+ sep.setAttribute("role", "separator");
451
+ this.list.appendChild(sep);
452
+ }
453
+ const branch = item.submenu !== void 0 && item.submenu.length > 0;
454
+ const li = doc.createElement("li");
455
+ li.className = "vela-menu-item";
456
+ li.dataset.veiId = item.id;
457
+ if (item.toggle) {
458
+ li.dataset.toggle = "1";
459
+ } else if (!branch && item.checked) {
460
+ li.dataset.checked = "1";
461
+ }
462
+ if (item.icon) li.appendChild(iconEl(item.icon, doc));
463
+ const label = doc.createElement("span");
464
+ label.className = "vela-menu-label";
465
+ label.textContent = item.label;
466
+ li.appendChild(label);
467
+ if (item.hint) {
468
+ const hint = doc.createElement("span");
469
+ hint.className = "vela-menu-hint";
470
+ hint.textContent = item.hint;
471
+ li.appendChild(hint);
472
+ }
473
+ if (item.toggle) {
474
+ const sw = doc.createElement("span");
475
+ sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
476
+ sw.setAttribute("aria-hidden", "true");
477
+ li.appendChild(sw);
478
+ }
479
+ if (branch) {
480
+ li.dataset.branch = "1";
481
+ const arrow = iconEl("chevron-right", doc);
482
+ arrow.classList.add("vela-menu-arrow");
483
+ li.appendChild(arrow);
484
+ const sub = new _Surface(doc, {
485
+ host: this.host,
486
+ placement: "right-start",
487
+ onSelect: this.onSelect,
488
+ id: `${this.mid}--${item.id}`
489
+ });
490
+ sub.setItems(item.submenu ?? []);
491
+ this.subs.set(item.id, sub);
492
+ }
493
+ this.list.appendChild(li);
494
+ }
495
+ }
496
+ };
497
+ var Menu = class {
498
+ constructor(opts) {
499
+ const anchor = opts.trigger ?? opts.host ?? document.body;
500
+ const doc = anchor.ownerDocument;
501
+ injectStyles(MENU_STYLE_ID, MENU_CSS, doc);
502
+ const host = opts.host ?? anchor.closest?.(".vela-ui") ?? doc.body;
503
+ this.root = new Surface(doc, {
504
+ host,
505
+ placement: opts.placement,
506
+ onSelect: (id) => opts.onSelect?.(id),
507
+ onOpenChange: opts.onOpenChange,
508
+ trigger: opts.trigger,
509
+ triggerId: opts.triggerId,
510
+ id: opts.id,
511
+ minWidth: opts.minWidth
512
+ });
513
+ this.root.setItems(opts.items);
514
+ }
515
+ get api() {
516
+ return this.root.api;
517
+ }
518
+ open() {
519
+ this.api.setOpen(true);
520
+ }
521
+ /** Open anchored to a viewport point (context menus). */
522
+ openAt(clientX, clientY) {
523
+ const api = this.api;
524
+ api.setOpen(true);
525
+ api.reposition({ getAnchorRect: () => ({ x: clientX, y: clientY, width: 0, height: 0 }) });
526
+ }
527
+ close() {
528
+ this.api.setOpen(false);
529
+ }
530
+ /** Swap the item descriptors (e.g. checked states) and re-project. */
531
+ setItems(items) {
532
+ this.root.setItems(items);
533
+ }
534
+ destroy() {
535
+ this.root.destroy();
536
+ }
537
+ };
538
+ function dialogController(opts = {}) {
539
+ return {
540
+ machine: dialog.machine,
541
+ props: {
542
+ id: nextUid("vela-dialog"),
543
+ modal: opts.modal ?? true,
544
+ closeOnEscape: opts.closeOnEscape ?? true,
545
+ closeOnInteractOutside: opts.closeOnInteractOutside ?? false,
546
+ onOpenChange: (d) => opts.onOpenChange?.(d.open)
547
+ },
548
+ connect: (service) => dialog.connect(service, normalizeProps)
549
+ };
550
+ }
551
+
552
+ // src/ui/components/dialog/styles.ts
553
+ var DIALOG_STYLE_ID = "vela-ui-dialog";
554
+ var DIALOG_CSS = `
555
+ .vela-dialog-backdrop {
556
+ position: fixed;
557
+ inset: 0;
558
+ background: var(--vela-backdrop);
559
+ z-index: var(--vela-z-dialog);
560
+ }
561
+ /* Non-dimming variant: still catches outside clicks, but the page stays readable. */
562
+ .vela-dialog-backdrop--clear { background: transparent; }
563
+ .vela-dialog-positioner {
564
+ position: fixed;
565
+ inset: 0;
566
+ display: flex;
567
+ align-items: flex-start;
568
+ justify-content: center;
569
+ padding-top: 10vh;
570
+ z-index: var(--vela-z-dialog);
571
+ }
572
+ .vela-dialog {
573
+ background: var(--vela-surface);
574
+ color: var(--vela-fg);
575
+ border: 1px solid var(--vela-border-strong);
576
+ border-radius: 10px;
577
+ box-shadow: var(--vela-shadow-dialog);
578
+ font-size: 13px;
579
+ min-width: 300px;
580
+ max-width: min(92vw, 560px);
581
+ max-height: 70vh;
582
+ display: flex;
583
+ flex-direction: column;
584
+ overflow: hidden;
585
+ outline: none;
586
+ }
587
+ .vela-dialog[data-state='open'] { animation: vela-dialog-in var(--vela-dur-med) var(--vela-ease); }
588
+ @keyframes vela-dialog-in {
589
+ from { opacity: 0; transform: translateY(6px) scale(0.98); }
590
+ to { opacity: 1; transform: translateY(0) scale(1); }
591
+ }
592
+ .vela-dialog-header {
593
+ display: flex;
594
+ align-items: center;
595
+ padding: 9px 9px 9px 16px;
596
+ border-bottom: 1px solid var(--vela-border);
597
+ user-select: none;
598
+ }
599
+ .vela-dialog-title { flex: 1; font-size: 17px; font-weight: 600; letter-spacing: 0.2px; color: var(--vela-fg-bright); }
600
+ .vela-dialog-close {
601
+ all: unset;
602
+ cursor: pointer;
603
+ width: 30px;
604
+ height: 30px;
605
+ display: flex;
606
+ align-items: center;
607
+ justify-content: center;
608
+ border-radius: 4px;
609
+ color: var(--vela-fg-muted);
610
+ line-height: 1;
611
+ font-size: 15px;
612
+ }
613
+ .vela-dialog-close:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
614
+ .vela-dialog-body { padding: var(--vela-space-4); overflow: auto; }
615
+ .vela-dialog-body::-webkit-scrollbar { width: 8px; }
616
+ .vela-dialog-body::-webkit-scrollbar-thumb {
617
+ background: var(--vela-scroll);
618
+ border-radius: 4px;
619
+ border: 2px solid transparent;
620
+ background-clip: padding-box;
621
+ }
622
+ `;
623
+ var Dialog = class {
624
+ constructor(opts = {}) {
625
+ const doc = (opts.host ?? document.body).ownerDocument;
626
+ injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
627
+ const host = opts.host ?? doc.body;
628
+ this.backdrop = doc.createElement("div");
629
+ this.backdrop.className = "vela-dialog-backdrop vela-ui-layer";
630
+ if (opts.dimBackdrop !== true) this.backdrop.classList.add("vela-dialog-backdrop--clear");
631
+ this.positioner = doc.createElement("div");
632
+ this.positioner.className = "vela-dialog-positioner vela-ui-layer";
633
+ this.panel = doc.createElement("div");
634
+ this.panel.className = "vela-dialog";
635
+ const header = doc.createElement("div");
636
+ header.className = "vela-dialog-header";
637
+ if (opts.draggable) {
638
+ header.style.cursor = "move";
639
+ let sx = 0, sy = 0, ox = 0, oy = 0, dragging = false;
640
+ header.addEventListener("pointerdown", (e) => {
641
+ if (e.target.closest(".vela-dialog-close")) return;
642
+ dragging = true;
643
+ sx = e.clientX - ox;
644
+ sy = e.clientY - oy;
645
+ header.setPointerCapture(e.pointerId);
646
+ });
647
+ header.addEventListener("pointermove", (e) => {
648
+ if (!dragging) return;
649
+ ox = e.clientX - sx;
650
+ oy = e.clientY - sy;
651
+ this.panel.style.transform = `translate(${ox}px, ${oy}px)`;
652
+ });
653
+ header.addEventListener("pointerup", () => dragging = false);
654
+ }
655
+ const title = doc.createElement("div");
656
+ title.className = "vela-dialog-title";
657
+ title.textContent = opts.title ?? "";
658
+ const close = doc.createElement("button");
659
+ close.className = "vela-dialog-close";
660
+ close.appendChild(iconEl("close", doc));
661
+ header.append(title, close);
662
+ this.body = doc.createElement("div");
663
+ this.body.className = "vela-dialog-body";
664
+ if (opts.content instanceof Node) this.body.appendChild(opts.content);
665
+ else if (typeof opts.content === "function") opts.content(this.body);
666
+ this.panel.append(header, this.body);
667
+ this.positioner.appendChild(this.panel);
668
+ host.append(this.backdrop, this.positioner);
669
+ this.ctrl = dialogController(opts);
670
+ const mid = String(this.ctrl.props.id);
671
+ this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
672
+ const api = this.ctrl.connect(service);
673
+ spreadProps(this.backdrop, api.getBackdropProps(), mid);
674
+ spreadProps(this.positioner, api.getPositionerProps(), mid);
675
+ spreadProps(this.panel, api.getContentProps(), mid);
676
+ spreadProps(title, api.getTitleProps(), mid);
677
+ spreadProps(close, api.getCloseTriggerProps(), mid);
678
+ this.backdrop.style.display = api.open ? "" : "none";
679
+ this.positioner.style.display = api.open ? "" : "none";
680
+ });
681
+ }
682
+ get open() {
683
+ return this.ctrl.connect(this.handle.service).open;
684
+ }
685
+ show() {
686
+ this.ctrl.connect(this.handle.service).setOpen(true);
687
+ }
688
+ hide() {
689
+ this.ctrl.connect(this.handle.service).setOpen(false);
690
+ }
691
+ destroy() {
692
+ this.handle.stop();
693
+ this.backdrop.remove();
694
+ this.positioner.remove();
695
+ }
696
+ };
697
+
698
+ export { Dialog, KeymapManager, Menu, Tooltip, applyPlotOverlayTokens, applyThemeTokens, dialogController, ensureUIHost, isEditableTarget, menuController, nextUid, runMachine, tooltipController };