@luxalgo/vela 0.5.2 → 0.5.4
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.
- package/README.md +3 -2
- package/dist/{DataProvider-DTOEXKTI.d.ts → DataProvider-Cqc_BaJ9.d.ts} +1 -1
- package/dist/{DataProvider-gnzv-dgc.d.cts → DataProvider-DgS2UyMc.d.cts} +1 -1
- package/dist/{chunk-RRFGWZNJ.js → chunk-D6WM5IUE.js} +786 -864
- package/dist/{chunk-LJLZR44Y.js → chunk-EMS6PO2T.js} +5 -7
- package/dist/{chunk-Y7TK4ZWK.js → chunk-JBQUY2RI.js} +126 -19
- package/dist/chunk-OGRQW3M6.js +1328 -0
- package/dist/{chunk-P556H6EA.js → chunk-QWIEKZRE.js} +59 -34
- package/dist/{chunk-B5TIKQJ5.js → chunk-ZNL2X6U2.js} +16 -2
- package/dist/{contributions-Ci_i3IN2.d.cts → contributions-4Nh1jbvb.d.cts} +2 -2
- package/dist/{contributions-b6AVrqqG.d.ts → contributions-DuIxJWeH.d.ts} +2 -2
- package/dist/{history-Cf1lVsap.d.cts → history-FsmvCIo4.d.ts} +5 -3
- package/dist/{history-Ci3M0xLV.d.ts → history-HofqYEh2.d.cts} +5 -3
- package/dist/index.cjs +2783 -1611
- package/dist/index.d.cts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +4 -4
- package/dist/{options-XTBpbx0s.d.ts → options-BlQ00Fju.d.cts} +3 -1
- package/dist/{options-XTBpbx0s.d.cts → options-BlQ00Fju.d.ts} +3 -1
- package/dist/{plugin-DfH4Y-Om.d.cts → plugin-DJR6z3e0.d.cts} +8 -7
- package/dist/{plugin-CnNd7cuF.d.ts → plugin-N_EaXN4-.d.ts} +8 -7
- package/dist/plugin.cjs +4 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/ui.cjs +1318 -4
- package/dist/ui.d.cts +383 -2
- package/dist/ui.d.ts +383 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +2777 -1605
- package/dist/vela.global.min.js +315 -66
- package/dist/widget.cjs +2358 -1052
- package/dist/widget.d.cts +26 -8
- package/dist/widget.d.ts +26 -8
- package/dist/widget.js +27 -9
- package/dist/workspace.cjs +2264 -946
- package/dist/workspace.d.cts +10 -5
- package/dist/workspace.d.ts +10 -5
- package/dist/workspace.js +25 -7
- package/package.json +1 -1
- package/dist/chunk-ATPU5CI6.js +0 -81
package/dist/ui.cjs
CHANGED
|
@@ -86,7 +86,11 @@ function injectStyles(id, css, root = document) {
|
|
|
86
86
|
// src/core/palette.ts
|
|
87
87
|
var ACCENT = "#2962ff";
|
|
88
88
|
var ACCENT_BRIGHT = "#38c0fd";
|
|
89
|
+
var BULLISH = "#089981";
|
|
90
|
+
var BEARISH = "#f23645";
|
|
91
|
+
var NEUTRAL = "#787b86";
|
|
89
92
|
var HIGHLIGHT = "#e0b400";
|
|
93
|
+
var WARNING = "#ff9800";
|
|
90
94
|
|
|
91
95
|
// src/core/tokens.ts
|
|
92
96
|
var STATIC_TOKENS = {
|
|
@@ -100,6 +104,9 @@ var STATIC_TOKENS = {
|
|
|
100
104
|
"--vela-z-tooltip": "60",
|
|
101
105
|
"--vela-z-menu": "50",
|
|
102
106
|
"--vela-z-dialog": "40",
|
|
107
|
+
// Form popovers portal to <body> (or a chart host) and must sit above a dialog
|
|
108
|
+
// whose own stacking context may be nested inside the chart container.
|
|
109
|
+
"--vela-z-popover": "6000",
|
|
103
110
|
"--vela-ease": "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
104
111
|
"--vela-dur-fast": "90ms",
|
|
105
112
|
"--vela-dur-med": "160ms",
|
|
@@ -238,6 +245,10 @@ registerIcon(
|
|
|
238
245
|
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"/>')
|
|
239
246
|
);
|
|
240
247
|
registerIcon("clock", S('<circle cx="8" cy="8" r="6.2"/><path d="M8 4.8V8l2.4 1.6"/>'));
|
|
248
|
+
registerIcon(
|
|
249
|
+
"calendar",
|
|
250
|
+
S('<rect x="2.2" y="3.2" width="11.6" height="10.6" rx="1.4"/><path d="M5.2 1.8v2.8M10.8 1.8v2.8M2.2 6.8h11.6"/>')
|
|
251
|
+
);
|
|
241
252
|
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"/>'));
|
|
242
253
|
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"/>'));
|
|
243
254
|
registerIcon(
|
|
@@ -592,7 +603,10 @@ function menuController(opts) {
|
|
|
592
603
|
props: {
|
|
593
604
|
id: opts.id ?? nextUid("vela-menu"),
|
|
594
605
|
ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
|
|
595
|
-
positioning: {
|
|
606
|
+
positioning: {
|
|
607
|
+
placement: opts.placement ?? "bottom-start",
|
|
608
|
+
...opts.getAnchorRect ? { getAnchorRect: () => opts.getAnchorRect() } : {}
|
|
609
|
+
},
|
|
596
610
|
onSelect: (d) => opts.onSelect?.(d.value),
|
|
597
611
|
onOpenChange: (d) => opts.onOpenChange?.(d.open)
|
|
598
612
|
},
|
|
@@ -674,6 +688,22 @@ var MENU_CSS = `
|
|
|
674
688
|
}
|
|
675
689
|
.vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
|
|
676
690
|
.vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
|
|
691
|
+
/* Favorite star (rows carrying \`favorite\`): reserved space always \u2014 it fades in on row
|
|
692
|
+
hover instead of shifting the layout \u2014 and a starred row keeps its filled star visible.
|
|
693
|
+
Same control language as the drawing toolbar's flyout stars. */
|
|
694
|
+
.vela-menu-item .vela-menu-star {
|
|
695
|
+
order: 98;
|
|
696
|
+
/* Pad the 16px icon slot into a bigger hit target without moving the row's layout. */
|
|
697
|
+
margin: -3px -5px -3px 0;
|
|
698
|
+
padding: 3px 5px;
|
|
699
|
+
box-sizing: content-box;
|
|
700
|
+
border-radius: var(--vela-radius-sm);
|
|
701
|
+
opacity: 0;
|
|
702
|
+
transition: opacity 0.1s ease, background 0.1s ease;
|
|
703
|
+
}
|
|
704
|
+
.vela-menu-item[data-highlighted] .vela-menu-star { opacity: 0.55; }
|
|
705
|
+
.vela-menu-item .vela-menu-star:hover { opacity: 1; background: var(--vela-hover-strong); }
|
|
706
|
+
.vela-menu-item .vela-menu-star.vela-fav { opacity: 0.95; color: var(--vela-highlight); }
|
|
677
707
|
.vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
|
|
678
708
|
/* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
|
|
679
709
|
list is open so the path you came down remains readable. */
|
|
@@ -692,9 +722,13 @@ var Surface = class _Surface {
|
|
|
692
722
|
this.items = [];
|
|
693
723
|
/** Branch item id → the surface it opens. */
|
|
694
724
|
this.subs = /* @__PURE__ */ new Map();
|
|
725
|
+
/** Trigger rect captured when this level opened — the list stays put if the
|
|
726
|
+
* trigger then moves (favorite chips shifting the caret). */
|
|
727
|
+
this.pinnedAnchor = null;
|
|
695
728
|
this.doc = doc;
|
|
696
729
|
this.host = opts.host;
|
|
697
730
|
this.onSelect = opts.onSelect;
|
|
731
|
+
this.onFavorite = opts.onFavorite;
|
|
698
732
|
this.positioner = doc.createElement("div");
|
|
699
733
|
this.positioner.className = "vela-ui-layer";
|
|
700
734
|
this.list = doc.createElement("ul");
|
|
@@ -702,16 +736,25 @@ var Surface = class _Surface {
|
|
|
702
736
|
if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
|
|
703
737
|
this.positioner.appendChild(this.list);
|
|
704
738
|
this.host.appendChild(this.positioner);
|
|
739
|
+
const trigger = opts.trigger;
|
|
705
740
|
this.ctrl = menuController({
|
|
706
741
|
items: [],
|
|
707
742
|
id: opts.id,
|
|
708
743
|
placement: opts.placement,
|
|
709
744
|
onSelect: (id) => this.onSelect(id),
|
|
710
|
-
|
|
745
|
+
getAnchorRect: () => this.pinnedAnchor,
|
|
746
|
+
onOpenChange: (open2) => {
|
|
747
|
+
if (open2 && trigger) {
|
|
748
|
+
const r = trigger.getBoundingClientRect();
|
|
749
|
+
this.pinnedAnchor = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
750
|
+
} else {
|
|
751
|
+
this.pinnedAnchor = null;
|
|
752
|
+
}
|
|
753
|
+
opts.onOpenChange?.(open2);
|
|
754
|
+
},
|
|
711
755
|
triggerId: opts.triggerId
|
|
712
756
|
});
|
|
713
757
|
this.mid = String(this.ctrl.props.id);
|
|
714
|
-
const trigger = opts.trigger;
|
|
715
758
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
716
759
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
717
760
|
const api = this.ctrl.connect(service);
|
|
@@ -791,6 +834,25 @@ var Surface = class _Surface {
|
|
|
791
834
|
hint.textContent = item.hint;
|
|
792
835
|
li.appendChild(hint);
|
|
793
836
|
}
|
|
837
|
+
if (item.favorite !== void 0 && this.onFavorite) {
|
|
838
|
+
const on = item.favorite;
|
|
839
|
+
const star = iconEl(on ? "star-filled" : "star", doc);
|
|
840
|
+
star.classList.add("vela-menu-star");
|
|
841
|
+
if (on) star.classList.add("vela-fav");
|
|
842
|
+
star.removeAttribute("aria-hidden");
|
|
843
|
+
star.setAttribute("role", "button");
|
|
844
|
+
star.setAttribute("aria-label", on ? "Remove from favorites" : "Add to favorites");
|
|
845
|
+
star.setAttribute("aria-pressed", on ? "true" : "false");
|
|
846
|
+
for (const ev of ["pointerdown", "pointerup", "mousedown", "mouseup"]) {
|
|
847
|
+
star.addEventListener(ev, (e) => e.stopPropagation());
|
|
848
|
+
}
|
|
849
|
+
star.addEventListener("click", (e) => {
|
|
850
|
+
e.stopPropagation();
|
|
851
|
+
e.preventDefault();
|
|
852
|
+
this.onFavorite?.(item.id, !on);
|
|
853
|
+
});
|
|
854
|
+
li.appendChild(star);
|
|
855
|
+
}
|
|
794
856
|
if (item.toggle) {
|
|
795
857
|
const sw = doc.createElement("span");
|
|
796
858
|
sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
|
|
@@ -806,6 +868,7 @@ var Surface = class _Surface {
|
|
|
806
868
|
host: this.host,
|
|
807
869
|
placement: "right-start",
|
|
808
870
|
onSelect: this.onSelect,
|
|
871
|
+
onFavorite: this.onFavorite,
|
|
809
872
|
id: `${this.mid}--${item.id}`
|
|
810
873
|
});
|
|
811
874
|
sub.setItems(item.submenu ?? []);
|
|
@@ -829,7 +892,8 @@ var Menu = class {
|
|
|
829
892
|
trigger: opts.trigger,
|
|
830
893
|
triggerId: opts.triggerId,
|
|
831
894
|
id: opts.id,
|
|
832
|
-
minWidth: opts.minWidth
|
|
895
|
+
minWidth: opts.minWidth,
|
|
896
|
+
onFavorite: opts.onFavorite
|
|
833
897
|
});
|
|
834
898
|
this.root.setItems(opts.items);
|
|
835
899
|
}
|
|
@@ -1332,6 +1396,1222 @@ var Drawer = class {
|
|
|
1332
1396
|
}
|
|
1333
1397
|
};
|
|
1334
1398
|
|
|
1399
|
+
// src/ui/components/popover/controller.ts
|
|
1400
|
+
function insetRect(r, inset) {
|
|
1401
|
+
return {
|
|
1402
|
+
left: r.left + inset,
|
|
1403
|
+
top: r.top + inset,
|
|
1404
|
+
right: r.right - inset,
|
|
1405
|
+
bottom: r.bottom - inset,
|
|
1406
|
+
width: r.width - inset * 2,
|
|
1407
|
+
height: r.height - inset * 2
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
function viewportRect(width, height, inset) {
|
|
1411
|
+
return {
|
|
1412
|
+
left: inset,
|
|
1413
|
+
top: inset,
|
|
1414
|
+
right: width - inset,
|
|
1415
|
+
bottom: height - inset,
|
|
1416
|
+
width: width - inset * 2,
|
|
1417
|
+
height: height - inset * 2
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function intersectRects(a, b) {
|
|
1421
|
+
const left = Math.max(a.left, b.left);
|
|
1422
|
+
const top = Math.max(a.top, b.top);
|
|
1423
|
+
const right = Math.min(a.right, b.right);
|
|
1424
|
+
const bottom = Math.min(a.bottom, b.bottom);
|
|
1425
|
+
return { left, top, right, bottom, width: right - left, height: bottom - top };
|
|
1426
|
+
}
|
|
1427
|
+
function placePopover(a) {
|
|
1428
|
+
let left = a.align === "end" ? a.trigger.right - a.pop.width : a.trigger.left;
|
|
1429
|
+
const below = a.trigger.bottom + a.gap;
|
|
1430
|
+
const above = a.trigger.top - a.pop.height - a.gap;
|
|
1431
|
+
const fitsBelow = below + a.pop.height <= a.clamp.bottom;
|
|
1432
|
+
const fitsAbove = above >= a.clamp.top;
|
|
1433
|
+
let top = below;
|
|
1434
|
+
if (!fitsBelow && (fitsAbove || a.trigger.top - a.clamp.top > a.clamp.bottom - a.trigger.bottom)) {
|
|
1435
|
+
top = Math.max(a.clamp.top, above);
|
|
1436
|
+
}
|
|
1437
|
+
if (left + a.pop.width > a.clamp.right) left = a.clamp.right - a.pop.width;
|
|
1438
|
+
if (left < a.clamp.left) left = a.clamp.left;
|
|
1439
|
+
if (top + a.pop.height > a.clamp.bottom) top = a.clamp.bottom - a.pop.height;
|
|
1440
|
+
if (top < a.clamp.top) top = a.clamp.top;
|
|
1441
|
+
return {
|
|
1442
|
+
left: Math.round(left - a.originX),
|
|
1443
|
+
top: Math.round(top - a.originY)
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
function popoverController(opts = {}) {
|
|
1447
|
+
return {
|
|
1448
|
+
gap: opts.gap ?? 4,
|
|
1449
|
+
align: opts.align ?? "start",
|
|
1450
|
+
matchWidth: opts.matchWidth ?? false,
|
|
1451
|
+
position: opts.position ?? "fixed",
|
|
1452
|
+
boundaryInset: opts.boundaryInset ?? 0,
|
|
1453
|
+
viewportInset: opts.viewportInset ?? 6,
|
|
1454
|
+
onClose: opts.onClose
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/ui/components/popover/styles.ts
|
|
1459
|
+
var POPOVER_STYLE_ID = "vela-ui-popover";
|
|
1460
|
+
var POPOVER_CSS = `
|
|
1461
|
+
.vela-popover {
|
|
1462
|
+
position: fixed;
|
|
1463
|
+
z-index: var(--vela-z-popover);
|
|
1464
|
+
box-sizing: border-box;
|
|
1465
|
+
/* Never serve as a scroll anchor: the shell is portaled and re-placed between
|
|
1466
|
+
gestures, and anchoring against it jumps the scroller underneath. */
|
|
1467
|
+
overflow-anchor: none;
|
|
1468
|
+
}
|
|
1469
|
+
.vela-popover[data-position='absolute'] { position: absolute; }
|
|
1470
|
+
.vela-popover[hidden] { display: none !important; }
|
|
1471
|
+
`;
|
|
1472
|
+
|
|
1473
|
+
// src/ui/components/popover/view.ts
|
|
1474
|
+
var open = null;
|
|
1475
|
+
function closeOpenPopovers() {
|
|
1476
|
+
open?.hide();
|
|
1477
|
+
}
|
|
1478
|
+
function isPopoverOpen() {
|
|
1479
|
+
return open !== null;
|
|
1480
|
+
}
|
|
1481
|
+
function openPopoverTrigger() {
|
|
1482
|
+
return open?.trigger ?? null;
|
|
1483
|
+
}
|
|
1484
|
+
function toRect(r) {
|
|
1485
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
|
|
1486
|
+
}
|
|
1487
|
+
var Popover = class {
|
|
1488
|
+
constructor(opts) {
|
|
1489
|
+
this.onOutside = null;
|
|
1490
|
+
this.onKey = null;
|
|
1491
|
+
this.onReflow = null;
|
|
1492
|
+
this.shown = false;
|
|
1493
|
+
const doc = opts.trigger.ownerDocument;
|
|
1494
|
+
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
1495
|
+
this.trigger = opts.trigger;
|
|
1496
|
+
this.host = opts.host ?? doc.body;
|
|
1497
|
+
this.ctrl = popoverController(opts);
|
|
1498
|
+
this.boundary = opts.boundary ?? "viewport";
|
|
1499
|
+
this.theme = opts.theme;
|
|
1500
|
+
this.el = doc.createElement("div");
|
|
1501
|
+
this.el.className = "vela-popover vela-ui-layer" + (opts.className ? ` ${opts.className}` : "");
|
|
1502
|
+
this.el.dataset.position = this.ctrl.position;
|
|
1503
|
+
if (opts.zIndex !== void 0) this.el.style.zIndex = String(opts.zIndex);
|
|
1504
|
+
this.el.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
1505
|
+
if (opts.content instanceof Node) this.el.appendChild(opts.content);
|
|
1506
|
+
else if (typeof opts.content === "function") opts.content(this.el);
|
|
1507
|
+
}
|
|
1508
|
+
get open() {
|
|
1509
|
+
return this.shown;
|
|
1510
|
+
}
|
|
1511
|
+
get position() {
|
|
1512
|
+
return this.ctrl.position;
|
|
1513
|
+
}
|
|
1514
|
+
get align() {
|
|
1515
|
+
return this.ctrl.align;
|
|
1516
|
+
}
|
|
1517
|
+
show() {
|
|
1518
|
+
if (this.shown) {
|
|
1519
|
+
this.place();
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (open && open !== this) open.hide();
|
|
1523
|
+
ensureUIHost(this.el, this.theme);
|
|
1524
|
+
this.host.appendChild(this.el);
|
|
1525
|
+
this.shown = true;
|
|
1526
|
+
open = this;
|
|
1527
|
+
this.place();
|
|
1528
|
+
const onOutside = (ev) => {
|
|
1529
|
+
const t = ev.target;
|
|
1530
|
+
if (this.el.contains(t) || this.trigger.contains(t)) return;
|
|
1531
|
+
this.hide();
|
|
1532
|
+
};
|
|
1533
|
+
const onKey = (ev) => {
|
|
1534
|
+
if (ev.key !== "Escape") return;
|
|
1535
|
+
ev.preventDefault();
|
|
1536
|
+
ev.stopPropagation();
|
|
1537
|
+
this.hide();
|
|
1538
|
+
};
|
|
1539
|
+
const onReflow = () => this.place();
|
|
1540
|
+
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
1541
|
+
document.addEventListener("keydown", onKey, true);
|
|
1542
|
+
window.addEventListener("resize", onReflow, true);
|
|
1543
|
+
document.addEventListener("scroll", onReflow, true);
|
|
1544
|
+
this.onOutside = onOutside;
|
|
1545
|
+
this.onKey = onKey;
|
|
1546
|
+
this.onReflow = onReflow;
|
|
1547
|
+
}
|
|
1548
|
+
hide() {
|
|
1549
|
+
if (!this.shown) return;
|
|
1550
|
+
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
1551
|
+
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
1552
|
+
if (this.onReflow) {
|
|
1553
|
+
window.removeEventListener("resize", this.onReflow, true);
|
|
1554
|
+
document.removeEventListener("scroll", this.onReflow, true);
|
|
1555
|
+
}
|
|
1556
|
+
this.onOutside = null;
|
|
1557
|
+
this.onKey = null;
|
|
1558
|
+
this.onReflow = null;
|
|
1559
|
+
this.el.remove();
|
|
1560
|
+
this.shown = false;
|
|
1561
|
+
if (open === this) open = null;
|
|
1562
|
+
this.ctrl.onClose?.();
|
|
1563
|
+
}
|
|
1564
|
+
/** Show if closed, hide if this instance is the open popover. */
|
|
1565
|
+
toggle() {
|
|
1566
|
+
if (this.shown) this.hide();
|
|
1567
|
+
else this.show();
|
|
1568
|
+
}
|
|
1569
|
+
destroy() {
|
|
1570
|
+
this.hide();
|
|
1571
|
+
}
|
|
1572
|
+
reposition() {
|
|
1573
|
+
if (this.shown) this.place();
|
|
1574
|
+
}
|
|
1575
|
+
clampRect() {
|
|
1576
|
+
const view = viewportRect(window.innerWidth, window.innerHeight, this.ctrl.viewportInset);
|
|
1577
|
+
const bound = this.readBoundary();
|
|
1578
|
+
if (!bound) return view;
|
|
1579
|
+
const inset = insetRect(bound, this.ctrl.boundaryInset);
|
|
1580
|
+
return intersectRects(view, inset);
|
|
1581
|
+
}
|
|
1582
|
+
readBoundary() {
|
|
1583
|
+
const b = this.boundary;
|
|
1584
|
+
if (b === "viewport") return null;
|
|
1585
|
+
const raw = typeof b === "function" ? b() : b.getBoundingClientRect();
|
|
1586
|
+
return raw ? toRect(raw) : null;
|
|
1587
|
+
}
|
|
1588
|
+
place() {
|
|
1589
|
+
const ar = this.trigger.getBoundingClientRect();
|
|
1590
|
+
if (this.ctrl.matchWidth) {
|
|
1591
|
+
this.el.style.minWidth = `${Math.round(Math.max(this.el.offsetWidth, ar.width))}px`;
|
|
1592
|
+
}
|
|
1593
|
+
const origin = this.ctrl.position === "absolute" ? this.host.getBoundingClientRect() : { left: 0, top: 0 };
|
|
1594
|
+
const pos = placePopover({
|
|
1595
|
+
trigger: toRect(ar),
|
|
1596
|
+
pop: { width: this.el.offsetWidth, height: this.el.offsetHeight },
|
|
1597
|
+
gap: this.ctrl.gap,
|
|
1598
|
+
align: this.ctrl.align,
|
|
1599
|
+
clamp: this.clampRect(),
|
|
1600
|
+
originX: origin.left,
|
|
1601
|
+
originY: origin.top
|
|
1602
|
+
});
|
|
1603
|
+
this.el.style.left = `${pos.left}px`;
|
|
1604
|
+
this.el.style.top = `${pos.top}px`;
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
// src/ui/components/switch/controller.ts
|
|
1609
|
+
function switchController(opts = {}) {
|
|
1610
|
+
let checked = opts.checked ?? false;
|
|
1611
|
+
const disabled = opts.disabled ?? false;
|
|
1612
|
+
return {
|
|
1613
|
+
get checked() {
|
|
1614
|
+
return checked;
|
|
1615
|
+
},
|
|
1616
|
+
disabled,
|
|
1617
|
+
size: opts.size ?? "md",
|
|
1618
|
+
tone: opts.tone ?? "bright",
|
|
1619
|
+
setChecked(v) {
|
|
1620
|
+
checked = v;
|
|
1621
|
+
},
|
|
1622
|
+
toggle() {
|
|
1623
|
+
if (disabled) return checked;
|
|
1624
|
+
checked = !checked;
|
|
1625
|
+
opts.onChange?.(checked);
|
|
1626
|
+
return checked;
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// src/ui/components/switch/styles.ts
|
|
1632
|
+
var SWITCH_STYLE_ID = "vela-ui-switch";
|
|
1633
|
+
var SWITCH_CSS = `
|
|
1634
|
+
.vela-switch {
|
|
1635
|
+
width: 20px;
|
|
1636
|
+
height: 20px;
|
|
1637
|
+
flex: 0 0 auto;
|
|
1638
|
+
display: inline-flex;
|
|
1639
|
+
align-items: center;
|
|
1640
|
+
justify-content: center;
|
|
1641
|
+
padding: 0;
|
|
1642
|
+
border: 1px solid var(--vela-border);
|
|
1643
|
+
border-radius: 5px;
|
|
1644
|
+
background: transparent;
|
|
1645
|
+
color: transparent;
|
|
1646
|
+
cursor: pointer;
|
|
1647
|
+
line-height: 0;
|
|
1648
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
1649
|
+
}
|
|
1650
|
+
.vela-switch .vela-icon, .vela-switch svg { display: block; width: 12px; height: 12px; }
|
|
1651
|
+
.vela-switch[data-size='sm'] .vela-icon, .vela-switch[data-size='sm'] svg { width: 11px; height: 11px; }
|
|
1652
|
+
.vela-switch:hover { border-color: var(--vela-fg-muted); }
|
|
1653
|
+
.vela-switch[data-checked] {
|
|
1654
|
+
background: var(--vela-fg-bright);
|
|
1655
|
+
border-color: var(--vela-fg-bright);
|
|
1656
|
+
color: var(--vela-bg);
|
|
1657
|
+
}
|
|
1658
|
+
.vela-switch[data-size='sm'] { width: 18px; height: 18px; border-color: var(--vela-border-strong); }
|
|
1659
|
+
.vela-switch[data-size='sm']:hover { border-color: var(--vela-fg-muted); }
|
|
1660
|
+
.vela-switch[data-size='sm'][data-checked] {
|
|
1661
|
+
background: var(--vela-selected-bg);
|
|
1662
|
+
border-color: var(--vela-selected-bg);
|
|
1663
|
+
color: var(--vela-selected-fg);
|
|
1664
|
+
}
|
|
1665
|
+
.vela-switch[data-tone='selected'][data-checked] {
|
|
1666
|
+
background: var(--vela-selected-bg);
|
|
1667
|
+
border-color: var(--vela-selected-bg);
|
|
1668
|
+
color: var(--vela-selected-fg);
|
|
1669
|
+
}
|
|
1670
|
+
.vela-switch[disabled] { opacity: 0.4; cursor: default; }
|
|
1671
|
+
`;
|
|
1672
|
+
|
|
1673
|
+
// src/ui/components/switch/view.ts
|
|
1674
|
+
var Switch = class {
|
|
1675
|
+
constructor(opts = {}) {
|
|
1676
|
+
injectStyles(SWITCH_STYLE_ID, SWITCH_CSS, document);
|
|
1677
|
+
this.ctrl = switchController(opts);
|
|
1678
|
+
const b = document.createElement("button");
|
|
1679
|
+
b.type = "button";
|
|
1680
|
+
if (opts.id) b.id = opts.id;
|
|
1681
|
+
b.className = "vela-switch";
|
|
1682
|
+
b.dataset.size = this.ctrl.size;
|
|
1683
|
+
b.dataset.tone = this.ctrl.tone;
|
|
1684
|
+
b.setAttribute("role", "switch");
|
|
1685
|
+
if (this.ctrl.disabled) b.disabled = true;
|
|
1686
|
+
b.appendChild(iconEl("check", b.ownerDocument));
|
|
1687
|
+
this.el = b;
|
|
1688
|
+
this.paint();
|
|
1689
|
+
b.addEventListener("pointerdown", (e) => {
|
|
1690
|
+
e.preventDefault();
|
|
1691
|
+
b.focus({ preventScroll: true });
|
|
1692
|
+
});
|
|
1693
|
+
b.addEventListener("click", () => {
|
|
1694
|
+
this.ctrl.toggle();
|
|
1695
|
+
this.paint();
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
get checked() {
|
|
1699
|
+
return this.ctrl.checked;
|
|
1700
|
+
}
|
|
1701
|
+
setChecked(v) {
|
|
1702
|
+
this.ctrl.setChecked(v);
|
|
1703
|
+
this.paint();
|
|
1704
|
+
}
|
|
1705
|
+
paint() {
|
|
1706
|
+
const on = this.ctrl.checked;
|
|
1707
|
+
this.el.setAttribute("aria-checked", on ? "true" : "false");
|
|
1708
|
+
if (on) this.el.dataset.checked = "";
|
|
1709
|
+
else delete this.el.dataset.checked;
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
|
|
1713
|
+
// src/ui/components/select/controller.ts
|
|
1714
|
+
function selectController(opts) {
|
|
1715
|
+
const options = opts.options;
|
|
1716
|
+
let value = opts.value ?? options[0]?.value ?? "";
|
|
1717
|
+
const size = opts.size ?? "md";
|
|
1718
|
+
return {
|
|
1719
|
+
options,
|
|
1720
|
+
get value() {
|
|
1721
|
+
return value;
|
|
1722
|
+
},
|
|
1723
|
+
size,
|
|
1724
|
+
fill: opts.fill ?? size !== "sm",
|
|
1725
|
+
disabled: opts.disabled ?? false,
|
|
1726
|
+
setValue(v) {
|
|
1727
|
+
value = v;
|
|
1728
|
+
},
|
|
1729
|
+
labelOf(v) {
|
|
1730
|
+
return options.find((o) => o.value === v)?.label ?? v;
|
|
1731
|
+
},
|
|
1732
|
+
pick(v) {
|
|
1733
|
+
value = v;
|
|
1734
|
+
const label = options.find((o) => o.value === v)?.label ?? v;
|
|
1735
|
+
opts.onChange?.(v, label);
|
|
1736
|
+
return { value: v, label };
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/ui/components/select/styles.ts
|
|
1742
|
+
var SELECT_STYLE_ID = "vela-ui-select";
|
|
1743
|
+
var SELECT_CSS = `
|
|
1744
|
+
.vela-select { position: relative; display: inline-block; min-width: 0; }
|
|
1745
|
+
.vela-select[data-fill] { width: 100%; }
|
|
1746
|
+
.vela-select-trigger {
|
|
1747
|
+
display: flex;
|
|
1748
|
+
align-items: center;
|
|
1749
|
+
width: 100%;
|
|
1750
|
+
box-sizing: border-box;
|
|
1751
|
+
height: 34px;
|
|
1752
|
+
padding: 0 26px 0 8px;
|
|
1753
|
+
background: transparent;
|
|
1754
|
+
border: 1px solid var(--vela-border-strong);
|
|
1755
|
+
border-radius: 6px;
|
|
1756
|
+
color: var(--vela-fg-bright);
|
|
1757
|
+
font-size: 14px;
|
|
1758
|
+
font-family: inherit;
|
|
1759
|
+
cursor: pointer;
|
|
1760
|
+
text-align: left;
|
|
1761
|
+
outline: none;
|
|
1762
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
1763
|
+
}
|
|
1764
|
+
.vela-select-trigger:hover { border-color: var(--vela-fg-muted); }
|
|
1765
|
+
.vela-select-trigger:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
1766
|
+
/* As a grid item (chart-settings rows dissolve via display:contents into a shared
|
|
1767
|
+
max-content column) the wrap must NOT stretch to the column: it sizes to its own
|
|
1768
|
+
widest option, like the native select it replaces. */
|
|
1769
|
+
.vela-select[data-size='sm'] { max-width: 200px; flex: 0 0 auto; justify-self: start; }
|
|
1770
|
+
.vela-select[data-size='sm'] .vela-select-trigger {
|
|
1771
|
+
height: 28px;
|
|
1772
|
+
background: var(--vela-surface-elev);
|
|
1773
|
+
border-radius: var(--vela-radius-sm);
|
|
1774
|
+
color: var(--vela-fg);
|
|
1775
|
+
font-size: 13px;
|
|
1776
|
+
box-shadow: none;
|
|
1777
|
+
}
|
|
1778
|
+
.vela-select[data-size='sm'] .vela-select-trigger:focus { box-shadow: none; border-color: var(--vela-fg-muted); }
|
|
1779
|
+
.vela-select-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1780
|
+
/* Invisible option-label stack: gives the shrink-to-fit wrap the WIDEST option's
|
|
1781
|
+
intrinsic width (like a native select), so picking a shorter/longer label never
|
|
1782
|
+
resizes the trigger and shifts the row around it. */
|
|
1783
|
+
.vela-select-sizer { visibility: hidden; height: 0; overflow: hidden; font-size: 14px; }
|
|
1784
|
+
.vela-select[data-size='sm'] .vela-select-sizer { font-size: 13px; }
|
|
1785
|
+
.vela-select-sizer span { display: block; height: 0; white-space: nowrap; padding: 0 26px 0 8px; border-inline: 1px solid transparent; }
|
|
1786
|
+
.vela-select-chevron {
|
|
1787
|
+
position: absolute;
|
|
1788
|
+
right: 8px;
|
|
1789
|
+
top: 50%;
|
|
1790
|
+
transform: translateY(-50%);
|
|
1791
|
+
pointer-events: none;
|
|
1792
|
+
display: flex;
|
|
1793
|
+
opacity: 0.55;
|
|
1794
|
+
line-height: 0;
|
|
1795
|
+
color: inherit;
|
|
1796
|
+
}
|
|
1797
|
+
.vela-select-chevron .vela-icon, .vela-select-chevron svg { width: 12px; height: 12px; display: block; }
|
|
1798
|
+
.vela-select-list {
|
|
1799
|
+
background: var(--vela-bg);
|
|
1800
|
+
color: var(--vela-fg);
|
|
1801
|
+
border: none;
|
|
1802
|
+
border-radius: 6px;
|
|
1803
|
+
box-shadow: var(--vela-shadow);
|
|
1804
|
+
font: 14px var(--vela-font);
|
|
1805
|
+
padding: 4px;
|
|
1806
|
+
overflow: hidden;
|
|
1807
|
+
}
|
|
1808
|
+
.vela-select-list[data-size='sm'] { font-size: 13px; background: var(--vela-surface-overlay); }
|
|
1809
|
+
.vela-select-items { max-height: none; overflow: hidden; }
|
|
1810
|
+
.vela-select-list.is-scroll { display: flex; align-items: stretch; gap: 2px; }
|
|
1811
|
+
.vela-select-list.is-scroll .vela-select-items {
|
|
1812
|
+
flex: 1 1 auto;
|
|
1813
|
+
min-width: 0;
|
|
1814
|
+
max-height: 240px;
|
|
1815
|
+
overflow-y: auto;
|
|
1816
|
+
scrollbar-width: none;
|
|
1817
|
+
}
|
|
1818
|
+
.vela-select-list.is-scroll .vela-select-items::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
|
1819
|
+
.vela-select-rail { position: relative; flex: none; width: 3px; margin: 2px 1px 2px 0; pointer-events: none; }
|
|
1820
|
+
.vela-select-thumb { width: 3px; border-radius: 2px; background: var(--vela-scroll); }
|
|
1821
|
+
.vela-select-item {
|
|
1822
|
+
display: block;
|
|
1823
|
+
width: 100%;
|
|
1824
|
+
text-align: left;
|
|
1825
|
+
padding: 7px 10px;
|
|
1826
|
+
border: none;
|
|
1827
|
+
border-radius: 4px;
|
|
1828
|
+
background: transparent;
|
|
1829
|
+
color: inherit;
|
|
1830
|
+
cursor: pointer;
|
|
1831
|
+
font: inherit;
|
|
1832
|
+
font-weight: 400;
|
|
1833
|
+
}
|
|
1834
|
+
.vela-select-item:hover { background: var(--vela-hover); }
|
|
1835
|
+
.vela-select-item[data-checked] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
1836
|
+
.vela-select-item[data-checked]:hover { background: var(--vela-hover-strong); }
|
|
1837
|
+
`;
|
|
1838
|
+
|
|
1839
|
+
// src/ui/components/select/view.ts
|
|
1840
|
+
function ensureSelectStyles(doc) {
|
|
1841
|
+
injectStyles(SELECT_STYLE_ID, SELECT_CSS, doc);
|
|
1842
|
+
}
|
|
1843
|
+
function syncThumb(list, thumb) {
|
|
1844
|
+
const view = list.clientHeight;
|
|
1845
|
+
const total = list.scrollHeight;
|
|
1846
|
+
if (total <= view) {
|
|
1847
|
+
thumb.style.height = "0";
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
const thumbH = Math.max(20, view / total * view);
|
|
1851
|
+
const top = list.scrollTop / (total - view) * (view - thumbH);
|
|
1852
|
+
thumb.style.height = `${Math.round(thumbH)}px`;
|
|
1853
|
+
thumb.style.transform = `translateY(${Math.round(top)}px`;
|
|
1854
|
+
}
|
|
1855
|
+
function fillSelectList(menu2, options, current, onPick) {
|
|
1856
|
+
menu2.replaceChildren();
|
|
1857
|
+
const list = menu2.ownerDocument.createElement("div");
|
|
1858
|
+
list.className = "vela-select-items";
|
|
1859
|
+
for (const p of options) {
|
|
1860
|
+
const item = menu2.ownerDocument.createElement("button");
|
|
1861
|
+
item.type = "button";
|
|
1862
|
+
item.className = "vela-select-item";
|
|
1863
|
+
item.textContent = p.label;
|
|
1864
|
+
if (p.value === current) item.dataset.checked = "1";
|
|
1865
|
+
item.addEventListener("click", (e) => {
|
|
1866
|
+
e.stopPropagation();
|
|
1867
|
+
onPick(p.value, p.label);
|
|
1868
|
+
});
|
|
1869
|
+
list.appendChild(item);
|
|
1870
|
+
}
|
|
1871
|
+
menu2.appendChild(list);
|
|
1872
|
+
}
|
|
1873
|
+
function decorateSelectScroll(menu2) {
|
|
1874
|
+
const list = menu2.querySelector(".vela-select-items");
|
|
1875
|
+
if (!list) return;
|
|
1876
|
+
if (list.scrollHeight > 240) {
|
|
1877
|
+
menu2.classList.add("is-scroll");
|
|
1878
|
+
const rail = menu2.ownerDocument.createElement("div");
|
|
1879
|
+
rail.className = "vela-select-rail";
|
|
1880
|
+
const thumb = menu2.ownerDocument.createElement("div");
|
|
1881
|
+
thumb.className = "vela-select-thumb";
|
|
1882
|
+
rail.appendChild(thumb);
|
|
1883
|
+
menu2.appendChild(rail);
|
|
1884
|
+
list.addEventListener("scroll", () => syncThumb(list, thumb));
|
|
1885
|
+
syncThumb(list, thumb);
|
|
1886
|
+
}
|
|
1887
|
+
menu2.querySelector(".vela-select-item[data-checked]")?.scrollIntoView({ block: "nearest" });
|
|
1888
|
+
list.dispatchEvent(new Event("scroll"));
|
|
1889
|
+
}
|
|
1890
|
+
function toggleSelectList(trigger, options, current, onPick, opts = {}) {
|
|
1891
|
+
if (openPopoverTrigger() === trigger) {
|
|
1892
|
+
closeOpenPopovers();
|
|
1893
|
+
return null;
|
|
1894
|
+
}
|
|
1895
|
+
return openSelectList(trigger, options, current, onPick, opts);
|
|
1896
|
+
}
|
|
1897
|
+
function openSelectList(trigger, options, current, onPick, opts = {}) {
|
|
1898
|
+
ensureSelectStyles(trigger.ownerDocument);
|
|
1899
|
+
closeOpenPopovers();
|
|
1900
|
+
const pop = new Popover({
|
|
1901
|
+
trigger,
|
|
1902
|
+
theme: opts.theme,
|
|
1903
|
+
matchWidth: opts.matchWidth ?? true,
|
|
1904
|
+
boundary: opts.boundary,
|
|
1905
|
+
boundaryInset: opts.boundaryInset,
|
|
1906
|
+
gap: opts.gap ?? 4,
|
|
1907
|
+
align: opts.align ?? "start",
|
|
1908
|
+
host: opts.host,
|
|
1909
|
+
position: opts.position,
|
|
1910
|
+
zIndex: opts.zIndex,
|
|
1911
|
+
className: "vela-select-list",
|
|
1912
|
+
onClose: opts.onClose,
|
|
1913
|
+
content: (el) => {
|
|
1914
|
+
if (opts.size) el.dataset.size = opts.size;
|
|
1915
|
+
fillSelectList(el, options, current, (value, label) => {
|
|
1916
|
+
pop.hide();
|
|
1917
|
+
onPick(value, label);
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
});
|
|
1921
|
+
pop.show();
|
|
1922
|
+
decorateSelectScroll(pop.el);
|
|
1923
|
+
pop.reposition();
|
|
1924
|
+
return pop;
|
|
1925
|
+
}
|
|
1926
|
+
var Select = class {
|
|
1927
|
+
constructor(opts) {
|
|
1928
|
+
this.list = null;
|
|
1929
|
+
const doc = opts.list?.host?.ownerDocument ?? document;
|
|
1930
|
+
ensureSelectStyles(doc);
|
|
1931
|
+
this.ctrl = selectController(opts);
|
|
1932
|
+
this.listOpts = { ...opts.list, theme: opts.theme ?? opts.list?.theme, size: this.ctrl.size };
|
|
1933
|
+
const wrap = doc.createElement("div");
|
|
1934
|
+
wrap.className = "vela-select";
|
|
1935
|
+
wrap.dataset.size = this.ctrl.size;
|
|
1936
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
1937
|
+
const btn = doc.createElement("button");
|
|
1938
|
+
btn.type = "button";
|
|
1939
|
+
if (opts.id) btn.id = opts.id;
|
|
1940
|
+
btn.className = "vela-select-trigger";
|
|
1941
|
+
if (this.ctrl.disabled) btn.disabled = true;
|
|
1942
|
+
const label = doc.createElement("span");
|
|
1943
|
+
label.className = "vela-select-label";
|
|
1944
|
+
label.textContent = this.ctrl.labelOf(this.ctrl.value);
|
|
1945
|
+
const chevron = doc.createElement("span");
|
|
1946
|
+
chevron.className = "vela-select-chevron";
|
|
1947
|
+
chevron.appendChild(iconEl("chevron-down", doc));
|
|
1948
|
+
btn.append(label, chevron);
|
|
1949
|
+
wrap.appendChild(btn);
|
|
1950
|
+
const sizer = doc.createElement("div");
|
|
1951
|
+
sizer.className = "vela-select-sizer";
|
|
1952
|
+
sizer.setAttribute("aria-hidden", "true");
|
|
1953
|
+
for (const o of this.ctrl.options) {
|
|
1954
|
+
const s = doc.createElement("span");
|
|
1955
|
+
s.textContent = o.label;
|
|
1956
|
+
sizer.appendChild(s);
|
|
1957
|
+
}
|
|
1958
|
+
wrap.appendChild(sizer);
|
|
1959
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
1960
|
+
e.preventDefault();
|
|
1961
|
+
btn.focus({ preventScroll: true });
|
|
1962
|
+
});
|
|
1963
|
+
btn.addEventListener("click", (e) => {
|
|
1964
|
+
e.stopPropagation();
|
|
1965
|
+
this.toggle();
|
|
1966
|
+
});
|
|
1967
|
+
this.el = wrap;
|
|
1968
|
+
this.trigger = btn;
|
|
1969
|
+
this.labelEl = label;
|
|
1970
|
+
}
|
|
1971
|
+
get value() {
|
|
1972
|
+
return this.ctrl.value;
|
|
1973
|
+
}
|
|
1974
|
+
setValue(v) {
|
|
1975
|
+
this.ctrl.setValue(v);
|
|
1976
|
+
this.labelEl.textContent = this.ctrl.labelOf(v);
|
|
1977
|
+
}
|
|
1978
|
+
toggle() {
|
|
1979
|
+
this.list = toggleSelectList(
|
|
1980
|
+
this.trigger,
|
|
1981
|
+
this.ctrl.options,
|
|
1982
|
+
this.ctrl.value,
|
|
1983
|
+
(value, label) => {
|
|
1984
|
+
this.ctrl.pick(value);
|
|
1985
|
+
this.labelEl.textContent = label;
|
|
1986
|
+
this.list = null;
|
|
1987
|
+
},
|
|
1988
|
+
{ ...this.listOpts, onClose: () => {
|
|
1989
|
+
this.list = null;
|
|
1990
|
+
this.listOpts.onClose?.();
|
|
1991
|
+
} }
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
destroy() {
|
|
1995
|
+
this.list?.hide();
|
|
1996
|
+
this.list = null;
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
// src/ui/components/number-input/controller.ts
|
|
2001
|
+
function clampNumber(n, opts) {
|
|
2002
|
+
let v = n;
|
|
2003
|
+
if (opts.min !== void 0) v = Math.max(opts.min, v);
|
|
2004
|
+
if (opts.max !== void 0) v = Math.min(opts.max, v);
|
|
2005
|
+
if (opts.integer) v = Math.round(v);
|
|
2006
|
+
return v;
|
|
2007
|
+
}
|
|
2008
|
+
function decimalsOf(n) {
|
|
2009
|
+
const s = String(n);
|
|
2010
|
+
const e = s.search(/[eE]/);
|
|
2011
|
+
if (e >= 0) {
|
|
2012
|
+
const frac = s.slice(0, e).split(".")[1]?.length ?? 0;
|
|
2013
|
+
return Math.max(0, Math.min(20, frac - Number(s.slice(e + 1))));
|
|
2014
|
+
}
|
|
2015
|
+
return s.split(".")[1]?.length ?? 0;
|
|
2016
|
+
}
|
|
2017
|
+
function snapToStep(n, base, step) {
|
|
2018
|
+
const d = Math.min(20, Math.max(decimalsOf(base), decimalsOf(step)));
|
|
2019
|
+
return Number(n.toFixed(d));
|
|
2020
|
+
}
|
|
2021
|
+
function numberInputController(opts) {
|
|
2022
|
+
let value = opts.value;
|
|
2023
|
+
const integer = opts.integer ?? false;
|
|
2024
|
+
const commit = opts.commit ?? "blur";
|
|
2025
|
+
const clamp = opts.clamp ?? commit === "blur";
|
|
2026
|
+
const step = opts.step ?? (integer ? 1 : 0.1);
|
|
2027
|
+
const onChange = opts.onChange;
|
|
2028
|
+
const normalize = (raw) => clamp ? clampNumber(raw, { min: opts.min, max: opts.max, integer }) : raw;
|
|
2029
|
+
const apply = (raw) => {
|
|
2030
|
+
if (!Number.isFinite(raw)) return null;
|
|
2031
|
+
const n = normalize(raw);
|
|
2032
|
+
if (n !== value) {
|
|
2033
|
+
value = n;
|
|
2034
|
+
onChange?.(n);
|
|
2035
|
+
}
|
|
2036
|
+
return n;
|
|
2037
|
+
};
|
|
2038
|
+
return {
|
|
2039
|
+
get value() {
|
|
2040
|
+
return value;
|
|
2041
|
+
},
|
|
2042
|
+
min: opts.min,
|
|
2043
|
+
max: opts.max,
|
|
2044
|
+
step,
|
|
2045
|
+
integer,
|
|
2046
|
+
size: opts.size ?? "md",
|
|
2047
|
+
commit,
|
|
2048
|
+
steppers: opts.steppers ?? commit === "blur",
|
|
2049
|
+
clamp,
|
|
2050
|
+
disabled: opts.disabled ?? false,
|
|
2051
|
+
apply,
|
|
2052
|
+
sync(raw) {
|
|
2053
|
+
const n = Number.isFinite(raw) ? normalize(raw) : value;
|
|
2054
|
+
value = n;
|
|
2055
|
+
return n;
|
|
2056
|
+
},
|
|
2057
|
+
nudge(dir) {
|
|
2058
|
+
return apply(snapToStep(value + dir * step, value, step));
|
|
2059
|
+
}
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/ui/components/number-input/styles.ts
|
|
2064
|
+
var NUMBER_STYLE_ID = "vela-ui-number";
|
|
2065
|
+
var NUMBER_CSS = `
|
|
2066
|
+
.vela-num { position: relative; display: inline-block; min-width: 0; }
|
|
2067
|
+
.vela-num[data-fill] { width: 100%; }
|
|
2068
|
+
.vela-num input {
|
|
2069
|
+
width: 100%;
|
|
2070
|
+
box-sizing: border-box;
|
|
2071
|
+
height: 34px;
|
|
2072
|
+
background: transparent;
|
|
2073
|
+
border: 1px solid var(--vela-border-strong);
|
|
2074
|
+
border-radius: 6px;
|
|
2075
|
+
color: var(--vela-fg-bright);
|
|
2076
|
+
padding: 0 8px;
|
|
2077
|
+
font-size: 14px;
|
|
2078
|
+
font-family: inherit;
|
|
2079
|
+
outline: none;
|
|
2080
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
2081
|
+
-moz-appearance: textfield;
|
|
2082
|
+
appearance: textfield;
|
|
2083
|
+
}
|
|
2084
|
+
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
2085
|
+
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
2086
|
+
.vela-num input:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
2087
|
+
.vela-num[data-steppers] input { padding-right: 26px; }
|
|
2088
|
+
.vela-num[data-size='sm'] { width: 64px; flex: none; }
|
|
2089
|
+
.vela-num[data-size='sm'][data-compact] { width: 56px; }
|
|
2090
|
+
.vela-num[data-size='sm'] input {
|
|
2091
|
+
height: 28px;
|
|
2092
|
+
background: var(--vela-surface-elev);
|
|
2093
|
+
border-radius: var(--vela-radius-sm);
|
|
2094
|
+
color: var(--vela-fg);
|
|
2095
|
+
font-size: 13px;
|
|
2096
|
+
box-shadow: none;
|
|
2097
|
+
}
|
|
2098
|
+
.vela-num[data-size='sm'] input:focus { box-shadow: none; }
|
|
2099
|
+
.vela-num-step {
|
|
2100
|
+
position: absolute;
|
|
2101
|
+
right: 0;
|
|
2102
|
+
top: 0;
|
|
2103
|
+
bottom: 0;
|
|
2104
|
+
width: 22px;
|
|
2105
|
+
display: none;
|
|
2106
|
+
flex-direction: column;
|
|
2107
|
+
justify-content: center;
|
|
2108
|
+
box-sizing: border-box;
|
|
2109
|
+
padding: 2px 6px 2px 0;
|
|
2110
|
+
line-height: 0;
|
|
2111
|
+
}
|
|
2112
|
+
.vela-num[data-steppers]:hover .vela-num-step { display: flex; }
|
|
2113
|
+
.vela-num-step button {
|
|
2114
|
+
flex: 1;
|
|
2115
|
+
width: 100%;
|
|
2116
|
+
min-height: 0;
|
|
2117
|
+
border: none;
|
|
2118
|
+
background: transparent;
|
|
2119
|
+
color: var(--vela-fg-muted);
|
|
2120
|
+
border-radius: 3px;
|
|
2121
|
+
padding: 0;
|
|
2122
|
+
cursor: pointer;
|
|
2123
|
+
display: flex;
|
|
2124
|
+
align-items: center;
|
|
2125
|
+
justify-content: center;
|
|
2126
|
+
}
|
|
2127
|
+
.vela-num-step button:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
2128
|
+
.vela-num-step .vela-icon, .vela-num-step svg { width: 12px; height: 12px; display: block; }
|
|
2129
|
+
`;
|
|
2130
|
+
|
|
2131
|
+
// src/ui/components/number-input/view.ts
|
|
2132
|
+
var NumberInput = class {
|
|
2133
|
+
constructor(opts) {
|
|
2134
|
+
const doc = document;
|
|
2135
|
+
injectStyles(NUMBER_STYLE_ID, NUMBER_CSS, doc);
|
|
2136
|
+
this.ctrl = numberInputController(opts);
|
|
2137
|
+
this.placeholderMode = opts.placeholder !== void 0;
|
|
2138
|
+
this.emptyValue = opts.emptyValue ?? opts.value;
|
|
2139
|
+
this.last = String(opts.value);
|
|
2140
|
+
const wrap = doc.createElement("div");
|
|
2141
|
+
wrap.className = "vela-num";
|
|
2142
|
+
wrap.dataset.size = this.ctrl.size;
|
|
2143
|
+
if (this.ctrl.size !== "sm") wrap.dataset.fill = "";
|
|
2144
|
+
if (opts.compact) wrap.dataset.compact = "";
|
|
2145
|
+
if (this.ctrl.steppers) wrap.dataset.steppers = "";
|
|
2146
|
+
const ni = doc.createElement("input");
|
|
2147
|
+
ni.type = "number";
|
|
2148
|
+
if (opts.id) ni.id = opts.id;
|
|
2149
|
+
if (opts.title) ni.title = opts.title;
|
|
2150
|
+
if (opts.min !== void 0) ni.min = String(opts.min);
|
|
2151
|
+
if (opts.max !== void 0) ni.max = String(opts.max);
|
|
2152
|
+
ni.step = String(this.ctrl.step);
|
|
2153
|
+
if (this.ctrl.disabled) ni.disabled = true;
|
|
2154
|
+
if (this.placeholderMode) {
|
|
2155
|
+
ni.placeholder = opts.placeholder ?? "";
|
|
2156
|
+
ni.value = opts.value !== this.emptyValue ? String(opts.value) : "";
|
|
2157
|
+
} else {
|
|
2158
|
+
ni.value = String(opts.value);
|
|
2159
|
+
}
|
|
2160
|
+
const commitRaw = (raw) => {
|
|
2161
|
+
const n = this.ctrl.apply(raw);
|
|
2162
|
+
if (n === null) {
|
|
2163
|
+
ni.value = this.placeholderMode && this.ctrl.value === this.emptyValue ? "" : this.last;
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
this.last = String(n);
|
|
2167
|
+
if (this.placeholderMode && n === this.emptyValue) ni.value = "";
|
|
2168
|
+
else ni.value = String(n);
|
|
2169
|
+
};
|
|
2170
|
+
if (this.placeholderMode) {
|
|
2171
|
+
ni.addEventListener("change", () => {
|
|
2172
|
+
const raw = ni.value.trim() === "" ? this.emptyValue : Number(ni.value);
|
|
2173
|
+
commitRaw(Number.isFinite(raw) ? raw : this.emptyValue);
|
|
2174
|
+
});
|
|
2175
|
+
} else if (this.ctrl.commit === "live") {
|
|
2176
|
+
ni.addEventListener("input", () => {
|
|
2177
|
+
const v = Number(ni.value);
|
|
2178
|
+
if (Number.isFinite(v)) this.ctrl.apply(v);
|
|
2179
|
+
});
|
|
2180
|
+
} else {
|
|
2181
|
+
ni.addEventListener("blur", () => commitRaw(Number(ni.value)));
|
|
2182
|
+
ni.addEventListener("keydown", (e) => {
|
|
2183
|
+
if (e.key === "Enter") {
|
|
2184
|
+
e.preventDefault();
|
|
2185
|
+
ni.blur();
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
wrap.appendChild(ni);
|
|
2190
|
+
if (this.ctrl.steppers) {
|
|
2191
|
+
wrap.appendChild(this.buildSteppers(doc, (dir) => {
|
|
2192
|
+
const cur = Number(ni.value);
|
|
2193
|
+
const base = Number.isFinite(cur) ? cur : this.ctrl.value;
|
|
2194
|
+
commitRaw(snapToStep(base + dir * this.ctrl.step, base, this.ctrl.step));
|
|
2195
|
+
}));
|
|
2196
|
+
}
|
|
2197
|
+
this.el = wrap;
|
|
2198
|
+
this.input = ni;
|
|
2199
|
+
}
|
|
2200
|
+
get value() {
|
|
2201
|
+
return this.ctrl.value;
|
|
2202
|
+
}
|
|
2203
|
+
setValue(v) {
|
|
2204
|
+
const n = this.ctrl.sync(v);
|
|
2205
|
+
this.last = String(n);
|
|
2206
|
+
if (this.placeholderMode && n === this.emptyValue) this.input.value = "";
|
|
2207
|
+
else this.input.value = String(n);
|
|
2208
|
+
}
|
|
2209
|
+
buildSteppers(doc, applyDir) {
|
|
2210
|
+
const steps = doc.createElement("div");
|
|
2211
|
+
steps.className = "vela-num-step";
|
|
2212
|
+
const mk = (dir, icon, label) => {
|
|
2213
|
+
const b = doc.createElement("button");
|
|
2214
|
+
b.type = "button";
|
|
2215
|
+
b.tabIndex = -1;
|
|
2216
|
+
b.setAttribute("aria-label", label);
|
|
2217
|
+
b.appendChild(iconEl(icon, doc));
|
|
2218
|
+
let timer = 0;
|
|
2219
|
+
const apply = () => applyDir(dir);
|
|
2220
|
+
const stop = () => {
|
|
2221
|
+
if (timer) {
|
|
2222
|
+
window.clearTimeout(timer);
|
|
2223
|
+
timer = 0;
|
|
2224
|
+
}
|
|
2225
|
+
};
|
|
2226
|
+
b.addEventListener("pointerdown", (e) => {
|
|
2227
|
+
e.preventDefault();
|
|
2228
|
+
e.stopPropagation();
|
|
2229
|
+
apply();
|
|
2230
|
+
timer = window.setTimeout(function tick() {
|
|
2231
|
+
apply();
|
|
2232
|
+
timer = window.setTimeout(tick, 60);
|
|
2233
|
+
}, 400);
|
|
2234
|
+
});
|
|
2235
|
+
b.addEventListener("pointerup", stop);
|
|
2236
|
+
b.addEventListener("pointerleave", stop);
|
|
2237
|
+
b.addEventListener("pointercancel", stop);
|
|
2238
|
+
return b;
|
|
2239
|
+
};
|
|
2240
|
+
steps.append(mk(1, "chevron-up", "Increase"), mk(-1, "chevron-down", "Decrease"));
|
|
2241
|
+
return steps;
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
|
|
2245
|
+
// src/ui/components/text-field/controller.ts
|
|
2246
|
+
function textFieldController(opts = {}) {
|
|
2247
|
+
let value = opts.value ?? "";
|
|
2248
|
+
const size = opts.size ?? "md";
|
|
2249
|
+
return {
|
|
2250
|
+
get value() {
|
|
2251
|
+
return value;
|
|
2252
|
+
},
|
|
2253
|
+
size,
|
|
2254
|
+
fill: opts.fill ?? size !== "sm",
|
|
2255
|
+
disabled: opts.disabled ?? false,
|
|
2256
|
+
commit(next) {
|
|
2257
|
+
if (next === value) return null;
|
|
2258
|
+
value = next;
|
|
2259
|
+
opts.onChange?.(next);
|
|
2260
|
+
return next;
|
|
2261
|
+
},
|
|
2262
|
+
sync(next) {
|
|
2263
|
+
value = next;
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// src/ui/components/text-field/styles.ts
|
|
2269
|
+
var TEXT_STYLE_ID = "vela-ui-text";
|
|
2270
|
+
var TEXT_CSS = `
|
|
2271
|
+
.vela-text {
|
|
2272
|
+
display: inline-block;
|
|
2273
|
+
min-width: 0;
|
|
2274
|
+
}
|
|
2275
|
+
.vela-text[data-fill] { width: 100%; }
|
|
2276
|
+
.vela-text-field {
|
|
2277
|
+
width: 100%;
|
|
2278
|
+
box-sizing: border-box;
|
|
2279
|
+
height: 34px;
|
|
2280
|
+
background: transparent;
|
|
2281
|
+
border: 1px solid var(--vela-border-strong);
|
|
2282
|
+
border-radius: 6px;
|
|
2283
|
+
color: var(--vela-fg-bright);
|
|
2284
|
+
padding: 0 8px;
|
|
2285
|
+
font-size: 14px;
|
|
2286
|
+
font-family: inherit;
|
|
2287
|
+
outline: none;
|
|
2288
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
2289
|
+
}
|
|
2290
|
+
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
2291
|
+
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
2292
|
+
.vela-text[data-size='sm'] .vela-text-field {
|
|
2293
|
+
height: 28px;
|
|
2294
|
+
background: var(--vela-surface-elev);
|
|
2295
|
+
border-radius: var(--vela-radius-sm);
|
|
2296
|
+
color: var(--vela-fg);
|
|
2297
|
+
font-size: 13px;
|
|
2298
|
+
}
|
|
2299
|
+
`;
|
|
2300
|
+
|
|
2301
|
+
// src/ui/components/text-field/view.ts
|
|
2302
|
+
var TextField = class {
|
|
2303
|
+
constructor(opts = {}) {
|
|
2304
|
+
const doc = document;
|
|
2305
|
+
injectStyles(TEXT_STYLE_ID, TEXT_CSS, doc);
|
|
2306
|
+
this.ctrl = textFieldController(opts);
|
|
2307
|
+
const wrap = doc.createElement("div");
|
|
2308
|
+
wrap.className = "vela-text";
|
|
2309
|
+
wrap.dataset.size = this.ctrl.size;
|
|
2310
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
2311
|
+
const ti = doc.createElement("input");
|
|
2312
|
+
ti.type = "text";
|
|
2313
|
+
if (opts.id) ti.id = opts.id;
|
|
2314
|
+
ti.value = this.ctrl.value;
|
|
2315
|
+
ti.className = "vela-text-field";
|
|
2316
|
+
if (this.ctrl.disabled) ti.disabled = true;
|
|
2317
|
+
ti.addEventListener("blur", () => {
|
|
2318
|
+
this.ctrl.commit(ti.value);
|
|
2319
|
+
});
|
|
2320
|
+
ti.addEventListener("keydown", (e) => {
|
|
2321
|
+
if (e.key === "Enter") {
|
|
2322
|
+
e.preventDefault();
|
|
2323
|
+
ti.blur();
|
|
2324
|
+
}
|
|
2325
|
+
});
|
|
2326
|
+
wrap.appendChild(ti);
|
|
2327
|
+
this.el = wrap;
|
|
2328
|
+
this.input = ti;
|
|
2329
|
+
}
|
|
2330
|
+
get value() {
|
|
2331
|
+
return this.ctrl.value;
|
|
2332
|
+
}
|
|
2333
|
+
setValue(v) {
|
|
2334
|
+
this.ctrl.sync(v);
|
|
2335
|
+
this.input.value = v;
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2339
|
+
// src/ui/components/color-picker/controller.ts
|
|
2340
|
+
function splitColor(color) {
|
|
2341
|
+
const s = String(color ?? "").trim();
|
|
2342
|
+
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
2343
|
+
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
2344
|
+
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
2345
|
+
if (m) {
|
|
2346
|
+
const h = m[1].toLowerCase();
|
|
2347
|
+
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
2348
|
+
}
|
|
2349
|
+
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
2350
|
+
if (rgba) {
|
|
2351
|
+
const p = rgba[1].split(",").map((v) => v.trim());
|
|
2352
|
+
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
2353
|
+
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
2354
|
+
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
2355
|
+
}
|
|
2356
|
+
return { hex6: ACCENT_BRIGHT, alpha: 1 };
|
|
2357
|
+
}
|
|
2358
|
+
function combineColor(hex6, alpha) {
|
|
2359
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
2360
|
+
if (a >= 0.999) return hex6;
|
|
2361
|
+
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
2362
|
+
}
|
|
2363
|
+
function blendOver(fg, bg, alpha) {
|
|
2364
|
+
const rgb = (c) => {
|
|
2365
|
+
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
2366
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
2367
|
+
};
|
|
2368
|
+
const [fr, fgc, fb] = rgb(fg);
|
|
2369
|
+
const [br, bgc, bb] = rgb(bg);
|
|
2370
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
2371
|
+
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
2372
|
+
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
2373
|
+
}
|
|
2374
|
+
function hslHex(h, s, l) {
|
|
2375
|
+
const sn = s / 100;
|
|
2376
|
+
const ln = l / 100;
|
|
2377
|
+
const k = (n) => (n + h / 30) % 12;
|
|
2378
|
+
const a = sn * Math.min(ln, 1 - ln);
|
|
2379
|
+
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
2380
|
+
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
2381
|
+
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
2382
|
+
}
|
|
2383
|
+
function buildPalette() {
|
|
2384
|
+
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
2385
|
+
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
2386
|
+
const levels = [
|
|
2387
|
+
[82, 56],
|
|
2388
|
+
[58, 84],
|
|
2389
|
+
[62, 74],
|
|
2390
|
+
[66, 62],
|
|
2391
|
+
[70, 50],
|
|
2392
|
+
[64, 39],
|
|
2393
|
+
[54, 29]
|
|
2394
|
+
];
|
|
2395
|
+
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
2396
|
+
}
|
|
2397
|
+
function transparencyChecker(size) {
|
|
2398
|
+
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
2399
|
+
}
|
|
2400
|
+
function colorPickerController(opts = {}) {
|
|
2401
|
+
const parsed = splitColor(opts.color ?? ACCENT_BRIGHT);
|
|
2402
|
+
let hex6 = parsed.hex6;
|
|
2403
|
+
let alpha = parsed.alpha;
|
|
2404
|
+
return {
|
|
2405
|
+
get hex6() {
|
|
2406
|
+
return hex6;
|
|
2407
|
+
},
|
|
2408
|
+
get alpha() {
|
|
2409
|
+
return alpha;
|
|
2410
|
+
},
|
|
2411
|
+
combined() {
|
|
2412
|
+
return combineColor(hex6, alpha);
|
|
2413
|
+
},
|
|
2414
|
+
setHex(hex) {
|
|
2415
|
+
hex6 = splitColor(hex).hex6;
|
|
2416
|
+
},
|
|
2417
|
+
setAlpha(a) {
|
|
2418
|
+
alpha = Math.max(0, Math.min(1, a));
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
// src/ui/components/color-picker/styles.ts
|
|
2424
|
+
var COLOR_STYLE_ID = "vela-ui-color";
|
|
2425
|
+
var COLOR_CSS = `
|
|
2426
|
+
.vela-color-field{width:24px;height:24px;padding:2px;border:1px solid var(--vela-border);border-radius:0;background:var(--vela-surface-sunken);cursor:pointer;display:inline-flex;flex:none;}
|
|
2427
|
+
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
2428
|
+
.vela-color-field-swatch{display:block;width:100%;height:100%;border-radius:0;box-shadow:inset 0 0 0 1px rgba(0,0,0,0.25);}
|
|
2429
|
+
.vela-color-field-circle{width:26px;height:26px;padding:3px;border:1px solid var(--vela-border-strong);border-radius:4px;background:transparent;overflow:hidden;}
|
|
2430
|
+
.vela-color-field-circle .vela-color-field-swatch{border-radius:2px;box-shadow:none;}
|
|
2431
|
+
.vela-color-field-pop{background:var(--vela-surface-overlay);border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);padding:10px;}
|
|
2432
|
+
`;
|
|
2433
|
+
|
|
2434
|
+
// src/ui/components/color-picker/view.ts
|
|
2435
|
+
var PALETTE = buildPalette();
|
|
2436
|
+
var CHECKER = transparencyChecker(10);
|
|
2437
|
+
var FIELD_CHECKER = transparencyChecker(8);
|
|
2438
|
+
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
2439
|
+
function addRecent(hex6) {
|
|
2440
|
+
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
2441
|
+
if (i >= 0) recents.splice(i, 1);
|
|
2442
|
+
recents.unshift(hex6);
|
|
2443
|
+
if (recents.length > 10) recents.length = 10;
|
|
2444
|
+
}
|
|
2445
|
+
function buildColorPicker(color, theme, onChange) {
|
|
2446
|
+
const parsed = splitColor(color);
|
|
2447
|
+
let curHex = parsed.hex6;
|
|
2448
|
+
let curAlpha = parsed.alpha;
|
|
2449
|
+
const root = document.createElement("div");
|
|
2450
|
+
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
2451
|
+
const grid = document.createElement("div");
|
|
2452
|
+
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
2453
|
+
const swatches = [];
|
|
2454
|
+
for (const row of PALETTE) {
|
|
2455
|
+
for (const c of row) {
|
|
2456
|
+
const sw = document.createElement("button");
|
|
2457
|
+
sw.type = "button";
|
|
2458
|
+
sw.dataset.c = c;
|
|
2459
|
+
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
2460
|
+
sw.addEventListener("click", (e) => {
|
|
2461
|
+
e.stopPropagation();
|
|
2462
|
+
pickHex(c);
|
|
2463
|
+
});
|
|
2464
|
+
grid.appendChild(sw);
|
|
2465
|
+
swatches.push(sw);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
const paintSelection = () => {
|
|
2469
|
+
for (const sw of swatches) {
|
|
2470
|
+
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
2471
|
+
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
2472
|
+
sw.style.outlineOffset = "2px";
|
|
2473
|
+
sw.style.zIndex = on ? "1" : "";
|
|
2474
|
+
sw.style.position = on ? "relative" : "";
|
|
2475
|
+
}
|
|
2476
|
+
};
|
|
2477
|
+
const recentRow = document.createElement("div");
|
|
2478
|
+
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
2479
|
+
const renderRecents = () => {
|
|
2480
|
+
recentRow.replaceChildren();
|
|
2481
|
+
for (const c of recents) {
|
|
2482
|
+
const sw = document.createElement("button");
|
|
2483
|
+
sw.type = "button";
|
|
2484
|
+
sw.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px solid var(--vela-border);cursor:pointer;background:${c};padding:0;`;
|
|
2485
|
+
sw.addEventListener("click", (e) => {
|
|
2486
|
+
e.stopPropagation();
|
|
2487
|
+
pickHex(c);
|
|
2488
|
+
});
|
|
2489
|
+
recentRow.appendChild(sw);
|
|
2490
|
+
}
|
|
2491
|
+
const add = document.createElement("label");
|
|
2492
|
+
add.style.cssText = `width:17px;height:17px;border-radius:var(--vela-radius-sm);border:1px dashed var(--vela-border-strong);cursor:pointer;display:flex;align-items:center;justify-content:center;color:${theme.textColor};font:14px ${theme.fontFamily};position:relative;`;
|
|
2493
|
+
add.textContent = "+";
|
|
2494
|
+
const input = document.createElement("input");
|
|
2495
|
+
input.type = "color";
|
|
2496
|
+
input.value = curHex;
|
|
2497
|
+
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
2498
|
+
input.addEventListener("input", () => pickHex(input.value, true));
|
|
2499
|
+
add.appendChild(input);
|
|
2500
|
+
recentRow.appendChild(add);
|
|
2501
|
+
};
|
|
2502
|
+
const opLabel = document.createElement("div");
|
|
2503
|
+
opLabel.textContent = "Opacity";
|
|
2504
|
+
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
2505
|
+
const opRow = document.createElement("div");
|
|
2506
|
+
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
2507
|
+
const track = document.createElement("div");
|
|
2508
|
+
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
2509
|
+
const knob = document.createElement("div");
|
|
2510
|
+
knob.style.cssText = "position:absolute;top:50%;width:15px;height:15px;border-radius:50%;background:var(--vela-selected-bg);box-shadow:0 1px 3px rgba(0,0,0,0.55);transform:translate(-50%,-50%);pointer-events:none;";
|
|
2511
|
+
track.appendChild(knob);
|
|
2512
|
+
const pctBox = document.createElement("div");
|
|
2513
|
+
pctBox.style.cssText = `min-width:42px;text-align:center;font:var(--vela-font-size-md) ${theme.fontFamily};color:var(--vela-fg);border:1px solid var(--vela-border);border-radius:5px;padding:3px 4px;`;
|
|
2514
|
+
opRow.append(track, pctBox);
|
|
2515
|
+
const paintOpacity = () => {
|
|
2516
|
+
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
2517
|
+
knob.style.left = `${curAlpha * 100}%`;
|
|
2518
|
+
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
2519
|
+
};
|
|
2520
|
+
let dragging = false;
|
|
2521
|
+
const onDrag = (clientX) => {
|
|
2522
|
+
const r = track.getBoundingClientRect();
|
|
2523
|
+
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
2524
|
+
paintOpacity();
|
|
2525
|
+
emit();
|
|
2526
|
+
};
|
|
2527
|
+
track.addEventListener("pointerdown", (e) => {
|
|
2528
|
+
e.stopPropagation();
|
|
2529
|
+
dragging = true;
|
|
2530
|
+
track.setPointerCapture(e.pointerId);
|
|
2531
|
+
onDrag(e.clientX);
|
|
2532
|
+
});
|
|
2533
|
+
track.addEventListener("pointermove", (e) => {
|
|
2534
|
+
if (dragging) onDrag(e.clientX);
|
|
2535
|
+
});
|
|
2536
|
+
track.addEventListener("pointerup", () => dragging = false);
|
|
2537
|
+
function emit() {
|
|
2538
|
+
onChange(combineColor(curHex, curAlpha));
|
|
2539
|
+
}
|
|
2540
|
+
function pickHex(hex, custom = false) {
|
|
2541
|
+
curHex = splitColor(hex).hex6;
|
|
2542
|
+
addRecent(curHex);
|
|
2543
|
+
paintSelection();
|
|
2544
|
+
paintOpacity();
|
|
2545
|
+
if (custom) renderRecents();
|
|
2546
|
+
emit();
|
|
2547
|
+
}
|
|
2548
|
+
renderRecents();
|
|
2549
|
+
paintSelection();
|
|
2550
|
+
paintOpacity();
|
|
2551
|
+
root.append(grid, recentRow, opLabel, opRow);
|
|
2552
|
+
return root;
|
|
2553
|
+
}
|
|
2554
|
+
function colorField(theme, getVal, onVal, opts) {
|
|
2555
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
2556
|
+
}
|
|
2557
|
+
var ColorField = class {
|
|
2558
|
+
constructor(opts) {
|
|
2559
|
+
injectStyles(COLOR_STYLE_ID, COLOR_CSS, document);
|
|
2560
|
+
this.theme = opts.theme;
|
|
2561
|
+
this.getVal = opts.getVal;
|
|
2562
|
+
this.onVal = opts.onVal;
|
|
2563
|
+
this.popoverOpts = opts.popover;
|
|
2564
|
+
const trigger = document.createElement("button");
|
|
2565
|
+
trigger.type = "button";
|
|
2566
|
+
if (opts.id) trigger.id = opts.id;
|
|
2567
|
+
trigger.className = opts.shape === "circle" ? "vela-color-field vela-color-field-circle" : "vela-color-field";
|
|
2568
|
+
const swatch = document.createElement("span");
|
|
2569
|
+
swatch.className = "vela-color-field-swatch";
|
|
2570
|
+
trigger.appendChild(swatch);
|
|
2571
|
+
this.el = trigger;
|
|
2572
|
+
this.swatch = swatch;
|
|
2573
|
+
this.paint();
|
|
2574
|
+
trigger.addEventListener("vela-sync", () => this.paint());
|
|
2575
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
2576
|
+
e.preventDefault();
|
|
2577
|
+
trigger.focus({ preventScroll: true });
|
|
2578
|
+
});
|
|
2579
|
+
trigger.addEventListener("click", (e) => {
|
|
2580
|
+
e.stopPropagation();
|
|
2581
|
+
this.toggle();
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
paint() {
|
|
2585
|
+
const v = this.getVal();
|
|
2586
|
+
this.swatch.style.background = `linear-gradient(${v}, ${v}), ${FIELD_CHECKER}`;
|
|
2587
|
+
}
|
|
2588
|
+
toggle() {
|
|
2589
|
+
if (openPopoverTrigger() === this.el) {
|
|
2590
|
+
closeOpenPopovers();
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
const pop = new Popover({
|
|
2594
|
+
trigger: this.el,
|
|
2595
|
+
theme: this.theme,
|
|
2596
|
+
align: this.popoverOpts?.align ?? "end",
|
|
2597
|
+
gap: this.popoverOpts?.gap ?? 6,
|
|
2598
|
+
host: this.popoverOpts?.host,
|
|
2599
|
+
position: this.popoverOpts?.position,
|
|
2600
|
+
boundary: this.popoverOpts?.boundary,
|
|
2601
|
+
zIndex: this.popoverOpts?.zIndex,
|
|
2602
|
+
className: "vela-color-field-pop",
|
|
2603
|
+
content: buildColorPicker(this.getVal(), this.theme, (val) => {
|
|
2604
|
+
this.onVal(val);
|
|
2605
|
+
this.paint();
|
|
2606
|
+
})
|
|
2607
|
+
});
|
|
2608
|
+
pop.show();
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
function closeColorPopover() {
|
|
2612
|
+
closeOpenPopovers();
|
|
2613
|
+
}
|
|
2614
|
+
|
|
1335
2615
|
Object.defineProperty(exports, "normalizeProps", {
|
|
1336
2616
|
enumerable: true,
|
|
1337
2617
|
get: function () { return vanilla.normalizeProps; }
|
|
@@ -1340,24 +2620,58 @@ Object.defineProperty(exports, "spreadProps", {
|
|
|
1340
2620
|
enumerable: true,
|
|
1341
2621
|
get: function () { return vanilla.spreadProps; }
|
|
1342
2622
|
});
|
|
2623
|
+
exports.ColorField = ColorField;
|
|
1343
2624
|
exports.Dialog = Dialog;
|
|
1344
2625
|
exports.Drawer = Drawer;
|
|
1345
2626
|
exports.KeymapManager = KeymapManager;
|
|
1346
2627
|
exports.Menu = Menu;
|
|
2628
|
+
exports.NumberInput = NumberInput;
|
|
2629
|
+
exports.Popover = Popover;
|
|
2630
|
+
exports.Select = Select;
|
|
2631
|
+
exports.Switch = Switch;
|
|
2632
|
+
exports.TextField = TextField;
|
|
1347
2633
|
exports.Tooltip = Tooltip;
|
|
1348
2634
|
exports.applyPlotOverlayTokens = applyPlotOverlayTokens;
|
|
1349
2635
|
exports.applyThemeTokens = applyThemeTokens;
|
|
2636
|
+
exports.blendOver = blendOver;
|
|
2637
|
+
exports.buildColorPicker = buildColorPicker;
|
|
2638
|
+
exports.buildPalette = buildPalette;
|
|
2639
|
+
exports.clampNumber = clampNumber;
|
|
2640
|
+
exports.closeColorPopover = closeColorPopover;
|
|
2641
|
+
exports.closeOpenPopovers = closeOpenPopovers;
|
|
2642
|
+
exports.colorField = colorField;
|
|
2643
|
+
exports.colorPickerController = colorPickerController;
|
|
2644
|
+
exports.combineColor = combineColor;
|
|
2645
|
+
exports.decorateSelectScroll = decorateSelectScroll;
|
|
1350
2646
|
exports.dialogController = dialogController;
|
|
1351
2647
|
exports.drawerController = drawerController;
|
|
1352
2648
|
exports.ensureUIHost = ensureUIHost;
|
|
2649
|
+
exports.fillSelectList = fillSelectList;
|
|
2650
|
+
exports.hslHex = hslHex;
|
|
1353
2651
|
exports.iconEl = iconEl;
|
|
1354
2652
|
exports.iconMarkup = iconMarkup;
|
|
1355
2653
|
exports.injectStyles = injectStyles;
|
|
2654
|
+
exports.insetRect = insetRect;
|
|
2655
|
+
exports.intersectRects = intersectRects;
|
|
2656
|
+
exports.isPopoverOpen = isPopoverOpen;
|
|
1356
2657
|
exports.menuController = menuController;
|
|
1357
2658
|
exports.nextUid = nextUid;
|
|
2659
|
+
exports.numberInputController = numberInputController;
|
|
2660
|
+
exports.openPopoverTrigger = openPopoverTrigger;
|
|
2661
|
+
exports.openSelectList = openSelectList;
|
|
2662
|
+
exports.placePopover = placePopover;
|
|
2663
|
+
exports.popoverController = popoverController;
|
|
1358
2664
|
exports.registerIcon = registerIcon;
|
|
1359
2665
|
exports.runMachine = runMachine;
|
|
2666
|
+
exports.selectController = selectController;
|
|
2667
|
+
exports.snapToStep = snapToStep;
|
|
2668
|
+
exports.splitColor = splitColor;
|
|
1360
2669
|
exports.svg16 = svg16;
|
|
1361
2670
|
exports.svg24 = svg24;
|
|
2671
|
+
exports.switchController = switchController;
|
|
2672
|
+
exports.textFieldController = textFieldController;
|
|
2673
|
+
exports.toggleSelectList = toggleSelectList;
|
|
1362
2674
|
exports.tooltipController = tooltipController;
|
|
2675
|
+
exports.transparencyChecker = transparencyChecker;
|
|
2676
|
+
exports.viewportRect = viewportRect;
|
|
1363
2677
|
exports.withAlpha = withAlpha;
|