@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/workspace.cjs
CHANGED
|
@@ -805,12 +805,6 @@ function mix(base, top, alpha) {
|
|
|
805
805
|
const c = (x, y) => Math.round(x + (y - x) * alpha);
|
|
806
806
|
return `rgb(${c(b.r, t.r)},${c(b.g, t.g)},${c(b.b, t.b)})`;
|
|
807
807
|
}
|
|
808
|
-
function toHex6(color, fallback = "#888888") {
|
|
809
|
-
const c = parseRgb(color);
|
|
810
|
-
if (!c) return fallback;
|
|
811
|
-
const h = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
812
|
-
return `#${h(c.r)}${h(c.g)}${h(c.b)}`;
|
|
813
|
-
}
|
|
814
808
|
function isDarkColor(color) {
|
|
815
809
|
const c = parseRgb(color);
|
|
816
810
|
if (!c) return true;
|
|
@@ -840,6 +834,9 @@ var STATIC_TOKENS = {
|
|
|
840
834
|
"--vela-z-tooltip": "60",
|
|
841
835
|
"--vela-z-menu": "50",
|
|
842
836
|
"--vela-z-dialog": "40",
|
|
837
|
+
// Form popovers portal to <body> (or a chart host) and must sit above a dialog
|
|
838
|
+
// whose own stacking context may be nested inside the chart container.
|
|
839
|
+
"--vela-z-popover": "6000",
|
|
843
840
|
"--vela-ease": "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
844
841
|
"--vela-dur-fast": "90ms",
|
|
845
842
|
"--vela-dur-med": "160ms",
|
|
@@ -987,6 +984,10 @@ registerIcon(
|
|
|
987
984
|
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"/>')
|
|
988
985
|
);
|
|
989
986
|
registerIcon("clock", S('<circle cx="8" cy="8" r="6.2"/><path d="M8 4.8V8l2.4 1.6"/>'));
|
|
987
|
+
registerIcon(
|
|
988
|
+
"calendar",
|
|
989
|
+
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"/>')
|
|
990
|
+
);
|
|
990
991
|
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"/>'));
|
|
991
992
|
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"/>'));
|
|
992
993
|
registerIcon(
|
|
@@ -1341,7 +1342,10 @@ function menuController(opts) {
|
|
|
1341
1342
|
props: {
|
|
1342
1343
|
id: opts.id ?? nextUid("vela-menu"),
|
|
1343
1344
|
ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
|
|
1344
|
-
positioning: {
|
|
1345
|
+
positioning: {
|
|
1346
|
+
placement: opts.placement ?? "bottom-start",
|
|
1347
|
+
...opts.getAnchorRect ? { getAnchorRect: () => opts.getAnchorRect() } : {}
|
|
1348
|
+
},
|
|
1345
1349
|
onSelect: (d) => opts.onSelect?.(d.value),
|
|
1346
1350
|
onOpenChange: (d) => opts.onOpenChange?.(d.open)
|
|
1347
1351
|
},
|
|
@@ -1423,6 +1427,22 @@ var MENU_CSS = `
|
|
|
1423
1427
|
}
|
|
1424
1428
|
.vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
|
|
1425
1429
|
.vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
|
|
1430
|
+
/* Favorite star (rows carrying \`favorite\`): reserved space always \u2014 it fades in on row
|
|
1431
|
+
hover instead of shifting the layout \u2014 and a starred row keeps its filled star visible.
|
|
1432
|
+
Same control language as the drawing toolbar's flyout stars. */
|
|
1433
|
+
.vela-menu-item .vela-menu-star {
|
|
1434
|
+
order: 98;
|
|
1435
|
+
/* Pad the 16px icon slot into a bigger hit target without moving the row's layout. */
|
|
1436
|
+
margin: -3px -5px -3px 0;
|
|
1437
|
+
padding: 3px 5px;
|
|
1438
|
+
box-sizing: content-box;
|
|
1439
|
+
border-radius: var(--vela-radius-sm);
|
|
1440
|
+
opacity: 0;
|
|
1441
|
+
transition: opacity 0.1s ease, background 0.1s ease;
|
|
1442
|
+
}
|
|
1443
|
+
.vela-menu-item[data-highlighted] .vela-menu-star { opacity: 0.55; }
|
|
1444
|
+
.vela-menu-item .vela-menu-star:hover { opacity: 1; background: var(--vela-hover-strong); }
|
|
1445
|
+
.vela-menu-item .vela-menu-star.vela-fav { opacity: 0.95; color: var(--vela-highlight); }
|
|
1426
1446
|
.vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
|
|
1427
1447
|
/* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
|
|
1428
1448
|
list is open so the path you came down remains readable. */
|
|
@@ -1441,9 +1461,13 @@ var Surface = class _Surface {
|
|
|
1441
1461
|
this.items = [];
|
|
1442
1462
|
/** Branch item id → the surface it opens. */
|
|
1443
1463
|
this.subs = /* @__PURE__ */ new Map();
|
|
1464
|
+
/** Trigger rect captured when this level opened — the list stays put if the
|
|
1465
|
+
* trigger then moves (favorite chips shifting the caret). */
|
|
1466
|
+
this.pinnedAnchor = null;
|
|
1444
1467
|
this.doc = doc;
|
|
1445
1468
|
this.host = opts.host;
|
|
1446
1469
|
this.onSelect = opts.onSelect;
|
|
1470
|
+
this.onFavorite = opts.onFavorite;
|
|
1447
1471
|
this.positioner = doc.createElement("div");
|
|
1448
1472
|
this.positioner.className = "vela-ui-layer";
|
|
1449
1473
|
this.list = doc.createElement("ul");
|
|
@@ -1451,16 +1475,25 @@ var Surface = class _Surface {
|
|
|
1451
1475
|
if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
|
|
1452
1476
|
this.positioner.appendChild(this.list);
|
|
1453
1477
|
this.host.appendChild(this.positioner);
|
|
1478
|
+
const trigger = opts.trigger;
|
|
1454
1479
|
this.ctrl = menuController({
|
|
1455
1480
|
items: [],
|
|
1456
1481
|
id: opts.id,
|
|
1457
1482
|
placement: opts.placement,
|
|
1458
1483
|
onSelect: (id) => this.onSelect(id),
|
|
1459
|
-
|
|
1484
|
+
getAnchorRect: () => this.pinnedAnchor,
|
|
1485
|
+
onOpenChange: (open2) => {
|
|
1486
|
+
if (open2 && trigger) {
|
|
1487
|
+
const r = trigger.getBoundingClientRect();
|
|
1488
|
+
this.pinnedAnchor = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
1489
|
+
} else {
|
|
1490
|
+
this.pinnedAnchor = null;
|
|
1491
|
+
}
|
|
1492
|
+
opts.onOpenChange?.(open2);
|
|
1493
|
+
},
|
|
1460
1494
|
triggerId: opts.triggerId
|
|
1461
1495
|
});
|
|
1462
1496
|
this.mid = String(this.ctrl.props.id);
|
|
1463
|
-
const trigger = opts.trigger;
|
|
1464
1497
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
1465
1498
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
1466
1499
|
const api = this.ctrl.connect(service);
|
|
@@ -1540,6 +1573,25 @@ var Surface = class _Surface {
|
|
|
1540
1573
|
hint.textContent = item.hint;
|
|
1541
1574
|
li.appendChild(hint);
|
|
1542
1575
|
}
|
|
1576
|
+
if (item.favorite !== void 0 && this.onFavorite) {
|
|
1577
|
+
const on = item.favorite;
|
|
1578
|
+
const star = iconEl(on ? "star-filled" : "star", doc);
|
|
1579
|
+
star.classList.add("vela-menu-star");
|
|
1580
|
+
if (on) star.classList.add("vela-fav");
|
|
1581
|
+
star.removeAttribute("aria-hidden");
|
|
1582
|
+
star.setAttribute("role", "button");
|
|
1583
|
+
star.setAttribute("aria-label", on ? "Remove from favorites" : "Add to favorites");
|
|
1584
|
+
star.setAttribute("aria-pressed", on ? "true" : "false");
|
|
1585
|
+
for (const ev of ["pointerdown", "pointerup", "mousedown", "mouseup"]) {
|
|
1586
|
+
star.addEventListener(ev, (e) => e.stopPropagation());
|
|
1587
|
+
}
|
|
1588
|
+
star.addEventListener("click", (e) => {
|
|
1589
|
+
e.stopPropagation();
|
|
1590
|
+
e.preventDefault();
|
|
1591
|
+
this.onFavorite?.(item.id, !on);
|
|
1592
|
+
});
|
|
1593
|
+
li.appendChild(star);
|
|
1594
|
+
}
|
|
1543
1595
|
if (item.toggle) {
|
|
1544
1596
|
const sw = doc.createElement("span");
|
|
1545
1597
|
sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
|
|
@@ -1555,6 +1607,7 @@ var Surface = class _Surface {
|
|
|
1555
1607
|
host: this.host,
|
|
1556
1608
|
placement: "right-start",
|
|
1557
1609
|
onSelect: this.onSelect,
|
|
1610
|
+
onFavorite: this.onFavorite,
|
|
1558
1611
|
id: `${this.mid}--${item.id}`
|
|
1559
1612
|
});
|
|
1560
1613
|
sub.setItems(item.submenu ?? []);
|
|
@@ -1578,7 +1631,8 @@ var Menu = class {
|
|
|
1578
1631
|
trigger: opts.trigger,
|
|
1579
1632
|
triggerId: opts.triggerId,
|
|
1580
1633
|
id: opts.id,
|
|
1581
|
-
minWidth: opts.minWidth
|
|
1634
|
+
minWidth: opts.minWidth,
|
|
1635
|
+
onFavorite: opts.onFavorite
|
|
1582
1636
|
});
|
|
1583
1637
|
this.root.setItems(opts.items);
|
|
1584
1638
|
}
|
|
@@ -2081,6 +2135,1197 @@ var Drawer = class {
|
|
|
2081
2135
|
}
|
|
2082
2136
|
};
|
|
2083
2137
|
|
|
2138
|
+
// src/ui/components/popover/controller.ts
|
|
2139
|
+
function insetRect(r, inset) {
|
|
2140
|
+
return {
|
|
2141
|
+
left: r.left + inset,
|
|
2142
|
+
top: r.top + inset,
|
|
2143
|
+
right: r.right - inset,
|
|
2144
|
+
bottom: r.bottom - inset,
|
|
2145
|
+
width: r.width - inset * 2,
|
|
2146
|
+
height: r.height - inset * 2
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
function viewportRect(width, height, inset) {
|
|
2150
|
+
return {
|
|
2151
|
+
left: inset,
|
|
2152
|
+
top: inset,
|
|
2153
|
+
right: width - inset,
|
|
2154
|
+
bottom: height - inset,
|
|
2155
|
+
width: width - inset * 2,
|
|
2156
|
+
height: height - inset * 2
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
function intersectRects(a, b) {
|
|
2160
|
+
const left = Math.max(a.left, b.left);
|
|
2161
|
+
const top = Math.max(a.top, b.top);
|
|
2162
|
+
const right = Math.min(a.right, b.right);
|
|
2163
|
+
const bottom = Math.min(a.bottom, b.bottom);
|
|
2164
|
+
return { left, top, right, bottom, width: right - left, height: bottom - top };
|
|
2165
|
+
}
|
|
2166
|
+
function placePopover(a) {
|
|
2167
|
+
let left = a.align === "end" ? a.trigger.right - a.pop.width : a.trigger.left;
|
|
2168
|
+
const below = a.trigger.bottom + a.gap;
|
|
2169
|
+
const above = a.trigger.top - a.pop.height - a.gap;
|
|
2170
|
+
const fitsBelow = below + a.pop.height <= a.clamp.bottom;
|
|
2171
|
+
const fitsAbove = above >= a.clamp.top;
|
|
2172
|
+
let top = below;
|
|
2173
|
+
if (!fitsBelow && (fitsAbove || a.trigger.top - a.clamp.top > a.clamp.bottom - a.trigger.bottom)) {
|
|
2174
|
+
top = Math.max(a.clamp.top, above);
|
|
2175
|
+
}
|
|
2176
|
+
if (left + a.pop.width > a.clamp.right) left = a.clamp.right - a.pop.width;
|
|
2177
|
+
if (left < a.clamp.left) left = a.clamp.left;
|
|
2178
|
+
if (top + a.pop.height > a.clamp.bottom) top = a.clamp.bottom - a.pop.height;
|
|
2179
|
+
if (top < a.clamp.top) top = a.clamp.top;
|
|
2180
|
+
return {
|
|
2181
|
+
left: Math.round(left - a.originX),
|
|
2182
|
+
top: Math.round(top - a.originY)
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
function popoverController(opts = {}) {
|
|
2186
|
+
return {
|
|
2187
|
+
gap: opts.gap ?? 4,
|
|
2188
|
+
align: opts.align ?? "start",
|
|
2189
|
+
matchWidth: opts.matchWidth ?? false,
|
|
2190
|
+
position: opts.position ?? "fixed",
|
|
2191
|
+
boundaryInset: opts.boundaryInset ?? 0,
|
|
2192
|
+
viewportInset: opts.viewportInset ?? 6,
|
|
2193
|
+
onClose: opts.onClose
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// src/ui/components/popover/styles.ts
|
|
2198
|
+
var POPOVER_STYLE_ID = "vela-ui-popover";
|
|
2199
|
+
var POPOVER_CSS = `
|
|
2200
|
+
.vela-popover {
|
|
2201
|
+
position: fixed;
|
|
2202
|
+
z-index: var(--vela-z-popover);
|
|
2203
|
+
box-sizing: border-box;
|
|
2204
|
+
/* Never serve as a scroll anchor: the shell is portaled and re-placed between
|
|
2205
|
+
gestures, and anchoring against it jumps the scroller underneath. */
|
|
2206
|
+
overflow-anchor: none;
|
|
2207
|
+
}
|
|
2208
|
+
.vela-popover[data-position='absolute'] { position: absolute; }
|
|
2209
|
+
.vela-popover[hidden] { display: none !important; }
|
|
2210
|
+
`;
|
|
2211
|
+
|
|
2212
|
+
// src/ui/components/popover/view.ts
|
|
2213
|
+
var open = null;
|
|
2214
|
+
function closeOpenPopovers() {
|
|
2215
|
+
open?.hide();
|
|
2216
|
+
}
|
|
2217
|
+
function isPopoverOpen() {
|
|
2218
|
+
return open !== null;
|
|
2219
|
+
}
|
|
2220
|
+
function openPopoverTrigger() {
|
|
2221
|
+
return open?.trigger ?? null;
|
|
2222
|
+
}
|
|
2223
|
+
function toRect(r) {
|
|
2224
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
|
|
2225
|
+
}
|
|
2226
|
+
var Popover = class {
|
|
2227
|
+
constructor(opts) {
|
|
2228
|
+
this.onOutside = null;
|
|
2229
|
+
this.onKey = null;
|
|
2230
|
+
this.onReflow = null;
|
|
2231
|
+
this.shown = false;
|
|
2232
|
+
const doc = opts.trigger.ownerDocument;
|
|
2233
|
+
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
2234
|
+
this.trigger = opts.trigger;
|
|
2235
|
+
this.host = opts.host ?? doc.body;
|
|
2236
|
+
this.ctrl = popoverController(opts);
|
|
2237
|
+
this.boundary = opts.boundary ?? "viewport";
|
|
2238
|
+
this.theme = opts.theme;
|
|
2239
|
+
this.el = doc.createElement("div");
|
|
2240
|
+
this.el.className = "vela-popover vela-ui-layer" + (opts.className ? ` ${opts.className}` : "");
|
|
2241
|
+
this.el.dataset.position = this.ctrl.position;
|
|
2242
|
+
if (opts.zIndex !== void 0) this.el.style.zIndex = String(opts.zIndex);
|
|
2243
|
+
this.el.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
2244
|
+
if (opts.content instanceof Node) this.el.appendChild(opts.content);
|
|
2245
|
+
else if (typeof opts.content === "function") opts.content(this.el);
|
|
2246
|
+
}
|
|
2247
|
+
get open() {
|
|
2248
|
+
return this.shown;
|
|
2249
|
+
}
|
|
2250
|
+
get position() {
|
|
2251
|
+
return this.ctrl.position;
|
|
2252
|
+
}
|
|
2253
|
+
get align() {
|
|
2254
|
+
return this.ctrl.align;
|
|
2255
|
+
}
|
|
2256
|
+
show() {
|
|
2257
|
+
if (this.shown) {
|
|
2258
|
+
this.place();
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
if (open && open !== this) open.hide();
|
|
2262
|
+
ensureUIHost(this.el, this.theme);
|
|
2263
|
+
this.host.appendChild(this.el);
|
|
2264
|
+
this.shown = true;
|
|
2265
|
+
open = this;
|
|
2266
|
+
this.place();
|
|
2267
|
+
const onOutside = (ev) => {
|
|
2268
|
+
const t = ev.target;
|
|
2269
|
+
if (this.el.contains(t) || this.trigger.contains(t)) return;
|
|
2270
|
+
this.hide();
|
|
2271
|
+
};
|
|
2272
|
+
const onKey = (ev) => {
|
|
2273
|
+
if (ev.key !== "Escape") return;
|
|
2274
|
+
ev.preventDefault();
|
|
2275
|
+
ev.stopPropagation();
|
|
2276
|
+
this.hide();
|
|
2277
|
+
};
|
|
2278
|
+
const onReflow = () => this.place();
|
|
2279
|
+
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
2280
|
+
document.addEventListener("keydown", onKey, true);
|
|
2281
|
+
window.addEventListener("resize", onReflow, true);
|
|
2282
|
+
document.addEventListener("scroll", onReflow, true);
|
|
2283
|
+
this.onOutside = onOutside;
|
|
2284
|
+
this.onKey = onKey;
|
|
2285
|
+
this.onReflow = onReflow;
|
|
2286
|
+
}
|
|
2287
|
+
hide() {
|
|
2288
|
+
if (!this.shown) return;
|
|
2289
|
+
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
2290
|
+
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
2291
|
+
if (this.onReflow) {
|
|
2292
|
+
window.removeEventListener("resize", this.onReflow, true);
|
|
2293
|
+
document.removeEventListener("scroll", this.onReflow, true);
|
|
2294
|
+
}
|
|
2295
|
+
this.onOutside = null;
|
|
2296
|
+
this.onKey = null;
|
|
2297
|
+
this.onReflow = null;
|
|
2298
|
+
this.el.remove();
|
|
2299
|
+
this.shown = false;
|
|
2300
|
+
if (open === this) open = null;
|
|
2301
|
+
this.ctrl.onClose?.();
|
|
2302
|
+
}
|
|
2303
|
+
/** Show if closed, hide if this instance is the open popover. */
|
|
2304
|
+
toggle() {
|
|
2305
|
+
if (this.shown) this.hide();
|
|
2306
|
+
else this.show();
|
|
2307
|
+
}
|
|
2308
|
+
destroy() {
|
|
2309
|
+
this.hide();
|
|
2310
|
+
}
|
|
2311
|
+
reposition() {
|
|
2312
|
+
if (this.shown) this.place();
|
|
2313
|
+
}
|
|
2314
|
+
clampRect() {
|
|
2315
|
+
const view = viewportRect(window.innerWidth, window.innerHeight, this.ctrl.viewportInset);
|
|
2316
|
+
const bound = this.readBoundary();
|
|
2317
|
+
if (!bound) return view;
|
|
2318
|
+
const inset = insetRect(bound, this.ctrl.boundaryInset);
|
|
2319
|
+
return intersectRects(view, inset);
|
|
2320
|
+
}
|
|
2321
|
+
readBoundary() {
|
|
2322
|
+
const b = this.boundary;
|
|
2323
|
+
if (b === "viewport") return null;
|
|
2324
|
+
const raw = typeof b === "function" ? b() : b.getBoundingClientRect();
|
|
2325
|
+
return raw ? toRect(raw) : null;
|
|
2326
|
+
}
|
|
2327
|
+
place() {
|
|
2328
|
+
const ar = this.trigger.getBoundingClientRect();
|
|
2329
|
+
if (this.ctrl.matchWidth) {
|
|
2330
|
+
this.el.style.minWidth = `${Math.round(Math.max(this.el.offsetWidth, ar.width))}px`;
|
|
2331
|
+
}
|
|
2332
|
+
const origin = this.ctrl.position === "absolute" ? this.host.getBoundingClientRect() : { left: 0, top: 0 };
|
|
2333
|
+
const pos = placePopover({
|
|
2334
|
+
trigger: toRect(ar),
|
|
2335
|
+
pop: { width: this.el.offsetWidth, height: this.el.offsetHeight },
|
|
2336
|
+
gap: this.ctrl.gap,
|
|
2337
|
+
align: this.ctrl.align,
|
|
2338
|
+
clamp: this.clampRect(),
|
|
2339
|
+
originX: origin.left,
|
|
2340
|
+
originY: origin.top
|
|
2341
|
+
});
|
|
2342
|
+
this.el.style.left = `${pos.left}px`;
|
|
2343
|
+
this.el.style.top = `${pos.top}px`;
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
|
|
2347
|
+
// src/ui/components/switch/controller.ts
|
|
2348
|
+
function switchController(opts = {}) {
|
|
2349
|
+
let checked = opts.checked ?? false;
|
|
2350
|
+
const disabled = opts.disabled ?? false;
|
|
2351
|
+
return {
|
|
2352
|
+
get checked() {
|
|
2353
|
+
return checked;
|
|
2354
|
+
},
|
|
2355
|
+
disabled,
|
|
2356
|
+
size: opts.size ?? "md",
|
|
2357
|
+
tone: opts.tone ?? "bright",
|
|
2358
|
+
setChecked(v) {
|
|
2359
|
+
checked = v;
|
|
2360
|
+
},
|
|
2361
|
+
toggle() {
|
|
2362
|
+
if (disabled) return checked;
|
|
2363
|
+
checked = !checked;
|
|
2364
|
+
opts.onChange?.(checked);
|
|
2365
|
+
return checked;
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// src/ui/components/switch/styles.ts
|
|
2371
|
+
var SWITCH_STYLE_ID = "vela-ui-switch";
|
|
2372
|
+
var SWITCH_CSS = `
|
|
2373
|
+
.vela-switch {
|
|
2374
|
+
width: 20px;
|
|
2375
|
+
height: 20px;
|
|
2376
|
+
flex: 0 0 auto;
|
|
2377
|
+
display: inline-flex;
|
|
2378
|
+
align-items: center;
|
|
2379
|
+
justify-content: center;
|
|
2380
|
+
padding: 0;
|
|
2381
|
+
border: 1px solid var(--vela-border);
|
|
2382
|
+
border-radius: 5px;
|
|
2383
|
+
background: transparent;
|
|
2384
|
+
color: transparent;
|
|
2385
|
+
cursor: pointer;
|
|
2386
|
+
line-height: 0;
|
|
2387
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
2388
|
+
}
|
|
2389
|
+
.vela-switch .vela-icon, .vela-switch svg { display: block; width: 12px; height: 12px; }
|
|
2390
|
+
.vela-switch[data-size='sm'] .vela-icon, .vela-switch[data-size='sm'] svg { width: 11px; height: 11px; }
|
|
2391
|
+
.vela-switch:hover { border-color: var(--vela-fg-muted); }
|
|
2392
|
+
.vela-switch[data-checked] {
|
|
2393
|
+
background: var(--vela-fg-bright);
|
|
2394
|
+
border-color: var(--vela-fg-bright);
|
|
2395
|
+
color: var(--vela-bg);
|
|
2396
|
+
}
|
|
2397
|
+
.vela-switch[data-size='sm'] { width: 18px; height: 18px; border-color: var(--vela-border-strong); }
|
|
2398
|
+
.vela-switch[data-size='sm']:hover { border-color: var(--vela-fg-muted); }
|
|
2399
|
+
.vela-switch[data-size='sm'][data-checked] {
|
|
2400
|
+
background: var(--vela-selected-bg);
|
|
2401
|
+
border-color: var(--vela-selected-bg);
|
|
2402
|
+
color: var(--vela-selected-fg);
|
|
2403
|
+
}
|
|
2404
|
+
.vela-switch[data-tone='selected'][data-checked] {
|
|
2405
|
+
background: var(--vela-selected-bg);
|
|
2406
|
+
border-color: var(--vela-selected-bg);
|
|
2407
|
+
color: var(--vela-selected-fg);
|
|
2408
|
+
}
|
|
2409
|
+
.vela-switch[disabled] { opacity: 0.4; cursor: default; }
|
|
2410
|
+
`;
|
|
2411
|
+
|
|
2412
|
+
// src/ui/components/switch/view.ts
|
|
2413
|
+
var Switch = class {
|
|
2414
|
+
constructor(opts = {}) {
|
|
2415
|
+
injectStyles(SWITCH_STYLE_ID, SWITCH_CSS, document);
|
|
2416
|
+
this.ctrl = switchController(opts);
|
|
2417
|
+
const b = document.createElement("button");
|
|
2418
|
+
b.type = "button";
|
|
2419
|
+
if (opts.id) b.id = opts.id;
|
|
2420
|
+
b.className = "vela-switch";
|
|
2421
|
+
b.dataset.size = this.ctrl.size;
|
|
2422
|
+
b.dataset.tone = this.ctrl.tone;
|
|
2423
|
+
b.setAttribute("role", "switch");
|
|
2424
|
+
if (this.ctrl.disabled) b.disabled = true;
|
|
2425
|
+
b.appendChild(iconEl("check", b.ownerDocument));
|
|
2426
|
+
this.el = b;
|
|
2427
|
+
this.paint();
|
|
2428
|
+
b.addEventListener("pointerdown", (e) => {
|
|
2429
|
+
e.preventDefault();
|
|
2430
|
+
b.focus({ preventScroll: true });
|
|
2431
|
+
});
|
|
2432
|
+
b.addEventListener("click", () => {
|
|
2433
|
+
this.ctrl.toggle();
|
|
2434
|
+
this.paint();
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
get checked() {
|
|
2438
|
+
return this.ctrl.checked;
|
|
2439
|
+
}
|
|
2440
|
+
setChecked(v) {
|
|
2441
|
+
this.ctrl.setChecked(v);
|
|
2442
|
+
this.paint();
|
|
2443
|
+
}
|
|
2444
|
+
paint() {
|
|
2445
|
+
const on = this.ctrl.checked;
|
|
2446
|
+
this.el.setAttribute("aria-checked", on ? "true" : "false");
|
|
2447
|
+
if (on) this.el.dataset.checked = "";
|
|
2448
|
+
else delete this.el.dataset.checked;
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
|
|
2452
|
+
// src/ui/components/select/controller.ts
|
|
2453
|
+
function selectController(opts) {
|
|
2454
|
+
const options = opts.options;
|
|
2455
|
+
let value = opts.value ?? options[0]?.value ?? "";
|
|
2456
|
+
const size = opts.size ?? "md";
|
|
2457
|
+
return {
|
|
2458
|
+
options,
|
|
2459
|
+
get value() {
|
|
2460
|
+
return value;
|
|
2461
|
+
},
|
|
2462
|
+
size,
|
|
2463
|
+
fill: opts.fill ?? size !== "sm",
|
|
2464
|
+
disabled: opts.disabled ?? false,
|
|
2465
|
+
setValue(v) {
|
|
2466
|
+
value = v;
|
|
2467
|
+
},
|
|
2468
|
+
labelOf(v) {
|
|
2469
|
+
return options.find((o) => o.value === v)?.label ?? v;
|
|
2470
|
+
},
|
|
2471
|
+
pick(v) {
|
|
2472
|
+
value = v;
|
|
2473
|
+
const label = options.find((o) => o.value === v)?.label ?? v;
|
|
2474
|
+
opts.onChange?.(v, label);
|
|
2475
|
+
return { value: v, label };
|
|
2476
|
+
}
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// src/ui/components/select/styles.ts
|
|
2481
|
+
var SELECT_STYLE_ID = "vela-ui-select";
|
|
2482
|
+
var SELECT_CSS = `
|
|
2483
|
+
.vela-select { position: relative; display: inline-block; min-width: 0; }
|
|
2484
|
+
.vela-select[data-fill] { width: 100%; }
|
|
2485
|
+
.vela-select-trigger {
|
|
2486
|
+
display: flex;
|
|
2487
|
+
align-items: center;
|
|
2488
|
+
width: 100%;
|
|
2489
|
+
box-sizing: border-box;
|
|
2490
|
+
height: 34px;
|
|
2491
|
+
padding: 0 26px 0 8px;
|
|
2492
|
+
background: transparent;
|
|
2493
|
+
border: 1px solid var(--vela-border-strong);
|
|
2494
|
+
border-radius: 6px;
|
|
2495
|
+
color: var(--vela-fg-bright);
|
|
2496
|
+
font-size: 14px;
|
|
2497
|
+
font-family: inherit;
|
|
2498
|
+
cursor: pointer;
|
|
2499
|
+
text-align: left;
|
|
2500
|
+
outline: none;
|
|
2501
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
2502
|
+
}
|
|
2503
|
+
.vela-select-trigger:hover { border-color: var(--vela-fg-muted); }
|
|
2504
|
+
.vela-select-trigger:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
2505
|
+
/* As a grid item (chart-settings rows dissolve via display:contents into a shared
|
|
2506
|
+
max-content column) the wrap must NOT stretch to the column: it sizes to its own
|
|
2507
|
+
widest option, like the native select it replaces. */
|
|
2508
|
+
.vela-select[data-size='sm'] { max-width: 200px; flex: 0 0 auto; justify-self: start; }
|
|
2509
|
+
.vela-select[data-size='sm'] .vela-select-trigger {
|
|
2510
|
+
height: 28px;
|
|
2511
|
+
background: var(--vela-surface-elev);
|
|
2512
|
+
border-radius: var(--vela-radius-sm);
|
|
2513
|
+
color: var(--vela-fg);
|
|
2514
|
+
font-size: 13px;
|
|
2515
|
+
box-shadow: none;
|
|
2516
|
+
}
|
|
2517
|
+
.vela-select[data-size='sm'] .vela-select-trigger:focus { box-shadow: none; border-color: var(--vela-fg-muted); }
|
|
2518
|
+
.vela-select-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2519
|
+
/* Invisible option-label stack: gives the shrink-to-fit wrap the WIDEST option's
|
|
2520
|
+
intrinsic width (like a native select), so picking a shorter/longer label never
|
|
2521
|
+
resizes the trigger and shifts the row around it. */
|
|
2522
|
+
.vela-select-sizer { visibility: hidden; height: 0; overflow: hidden; font-size: 14px; }
|
|
2523
|
+
.vela-select[data-size='sm'] .vela-select-sizer { font-size: 13px; }
|
|
2524
|
+
.vela-select-sizer span { display: block; height: 0; white-space: nowrap; padding: 0 26px 0 8px; border-inline: 1px solid transparent; }
|
|
2525
|
+
.vela-select-chevron {
|
|
2526
|
+
position: absolute;
|
|
2527
|
+
right: 8px;
|
|
2528
|
+
top: 50%;
|
|
2529
|
+
transform: translateY(-50%);
|
|
2530
|
+
pointer-events: none;
|
|
2531
|
+
display: flex;
|
|
2532
|
+
opacity: 0.55;
|
|
2533
|
+
line-height: 0;
|
|
2534
|
+
color: inherit;
|
|
2535
|
+
}
|
|
2536
|
+
.vela-select-chevron .vela-icon, .vela-select-chevron svg { width: 12px; height: 12px; display: block; }
|
|
2537
|
+
.vela-select-list {
|
|
2538
|
+
background: var(--vela-bg);
|
|
2539
|
+
color: var(--vela-fg);
|
|
2540
|
+
border: none;
|
|
2541
|
+
border-radius: 6px;
|
|
2542
|
+
box-shadow: var(--vela-shadow);
|
|
2543
|
+
font: 14px var(--vela-font);
|
|
2544
|
+
padding: 4px;
|
|
2545
|
+
overflow: hidden;
|
|
2546
|
+
}
|
|
2547
|
+
.vela-select-list[data-size='sm'] { font-size: 13px; background: var(--vela-surface-overlay); }
|
|
2548
|
+
.vela-select-items { max-height: none; overflow: hidden; }
|
|
2549
|
+
.vela-select-list.is-scroll { display: flex; align-items: stretch; gap: 2px; }
|
|
2550
|
+
.vela-select-list.is-scroll .vela-select-items {
|
|
2551
|
+
flex: 1 1 auto;
|
|
2552
|
+
min-width: 0;
|
|
2553
|
+
max-height: 240px;
|
|
2554
|
+
overflow-y: auto;
|
|
2555
|
+
scrollbar-width: none;
|
|
2556
|
+
}
|
|
2557
|
+
.vela-select-list.is-scroll .vela-select-items::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
|
2558
|
+
.vela-select-rail { position: relative; flex: none; width: 3px; margin: 2px 1px 2px 0; pointer-events: none; }
|
|
2559
|
+
.vela-select-thumb { width: 3px; border-radius: 2px; background: var(--vela-scroll); }
|
|
2560
|
+
.vela-select-item {
|
|
2561
|
+
display: block;
|
|
2562
|
+
width: 100%;
|
|
2563
|
+
text-align: left;
|
|
2564
|
+
padding: 7px 10px;
|
|
2565
|
+
border: none;
|
|
2566
|
+
border-radius: 4px;
|
|
2567
|
+
background: transparent;
|
|
2568
|
+
color: inherit;
|
|
2569
|
+
cursor: pointer;
|
|
2570
|
+
font: inherit;
|
|
2571
|
+
font-weight: 400;
|
|
2572
|
+
}
|
|
2573
|
+
.vela-select-item:hover { background: var(--vela-hover); }
|
|
2574
|
+
.vela-select-item[data-checked] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
2575
|
+
.vela-select-item[data-checked]:hover { background: var(--vela-hover-strong); }
|
|
2576
|
+
`;
|
|
2577
|
+
|
|
2578
|
+
// src/ui/components/select/view.ts
|
|
2579
|
+
function ensureSelectStyles(doc) {
|
|
2580
|
+
injectStyles(SELECT_STYLE_ID, SELECT_CSS, doc);
|
|
2581
|
+
}
|
|
2582
|
+
function syncThumb(list, thumb) {
|
|
2583
|
+
const view = list.clientHeight;
|
|
2584
|
+
const total = list.scrollHeight;
|
|
2585
|
+
if (total <= view) {
|
|
2586
|
+
thumb.style.height = "0";
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
const thumbH = Math.max(20, view / total * view);
|
|
2590
|
+
const top = list.scrollTop / (total - view) * (view - thumbH);
|
|
2591
|
+
thumb.style.height = `${Math.round(thumbH)}px`;
|
|
2592
|
+
thumb.style.transform = `translateY(${Math.round(top)}px`;
|
|
2593
|
+
}
|
|
2594
|
+
function fillSelectList(menu2, options, current, onPick) {
|
|
2595
|
+
menu2.replaceChildren();
|
|
2596
|
+
const list = menu2.ownerDocument.createElement("div");
|
|
2597
|
+
list.className = "vela-select-items";
|
|
2598
|
+
for (const p of options) {
|
|
2599
|
+
const item = menu2.ownerDocument.createElement("button");
|
|
2600
|
+
item.type = "button";
|
|
2601
|
+
item.className = "vela-select-item";
|
|
2602
|
+
item.textContent = p.label;
|
|
2603
|
+
if (p.value === current) item.dataset.checked = "1";
|
|
2604
|
+
item.addEventListener("click", (e) => {
|
|
2605
|
+
e.stopPropagation();
|
|
2606
|
+
onPick(p.value, p.label);
|
|
2607
|
+
});
|
|
2608
|
+
list.appendChild(item);
|
|
2609
|
+
}
|
|
2610
|
+
menu2.appendChild(list);
|
|
2611
|
+
}
|
|
2612
|
+
function decorateSelectScroll(menu2) {
|
|
2613
|
+
const list = menu2.querySelector(".vela-select-items");
|
|
2614
|
+
if (!list) return;
|
|
2615
|
+
if (list.scrollHeight > 240) {
|
|
2616
|
+
menu2.classList.add("is-scroll");
|
|
2617
|
+
const rail = menu2.ownerDocument.createElement("div");
|
|
2618
|
+
rail.className = "vela-select-rail";
|
|
2619
|
+
const thumb = menu2.ownerDocument.createElement("div");
|
|
2620
|
+
thumb.className = "vela-select-thumb";
|
|
2621
|
+
rail.appendChild(thumb);
|
|
2622
|
+
menu2.appendChild(rail);
|
|
2623
|
+
list.addEventListener("scroll", () => syncThumb(list, thumb));
|
|
2624
|
+
syncThumb(list, thumb);
|
|
2625
|
+
}
|
|
2626
|
+
menu2.querySelector(".vela-select-item[data-checked]")?.scrollIntoView({ block: "nearest" });
|
|
2627
|
+
list.dispatchEvent(new Event("scroll"));
|
|
2628
|
+
}
|
|
2629
|
+
function toggleSelectList(trigger, options, current, onPick, opts = {}) {
|
|
2630
|
+
if (openPopoverTrigger() === trigger) {
|
|
2631
|
+
closeOpenPopovers();
|
|
2632
|
+
return null;
|
|
2633
|
+
}
|
|
2634
|
+
return openSelectList(trigger, options, current, onPick, opts);
|
|
2635
|
+
}
|
|
2636
|
+
function openSelectList(trigger, options, current, onPick, opts = {}) {
|
|
2637
|
+
ensureSelectStyles(trigger.ownerDocument);
|
|
2638
|
+
closeOpenPopovers();
|
|
2639
|
+
const pop = new Popover({
|
|
2640
|
+
trigger,
|
|
2641
|
+
theme: opts.theme,
|
|
2642
|
+
matchWidth: opts.matchWidth ?? true,
|
|
2643
|
+
boundary: opts.boundary,
|
|
2644
|
+
boundaryInset: opts.boundaryInset,
|
|
2645
|
+
gap: opts.gap ?? 4,
|
|
2646
|
+
align: opts.align ?? "start",
|
|
2647
|
+
host: opts.host,
|
|
2648
|
+
position: opts.position,
|
|
2649
|
+
zIndex: opts.zIndex,
|
|
2650
|
+
className: "vela-select-list",
|
|
2651
|
+
onClose: opts.onClose,
|
|
2652
|
+
content: (el) => {
|
|
2653
|
+
if (opts.size) el.dataset.size = opts.size;
|
|
2654
|
+
fillSelectList(el, options, current, (value, label) => {
|
|
2655
|
+
pop.hide();
|
|
2656
|
+
onPick(value, label);
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
pop.show();
|
|
2661
|
+
decorateSelectScroll(pop.el);
|
|
2662
|
+
pop.reposition();
|
|
2663
|
+
return pop;
|
|
2664
|
+
}
|
|
2665
|
+
var Select = class {
|
|
2666
|
+
constructor(opts) {
|
|
2667
|
+
this.list = null;
|
|
2668
|
+
const doc = opts.list?.host?.ownerDocument ?? document;
|
|
2669
|
+
ensureSelectStyles(doc);
|
|
2670
|
+
this.ctrl = selectController(opts);
|
|
2671
|
+
this.listOpts = { ...opts.list, theme: opts.theme ?? opts.list?.theme, size: this.ctrl.size };
|
|
2672
|
+
const wrap = doc.createElement("div");
|
|
2673
|
+
wrap.className = "vela-select";
|
|
2674
|
+
wrap.dataset.size = this.ctrl.size;
|
|
2675
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
2676
|
+
const btn = doc.createElement("button");
|
|
2677
|
+
btn.type = "button";
|
|
2678
|
+
if (opts.id) btn.id = opts.id;
|
|
2679
|
+
btn.className = "vela-select-trigger";
|
|
2680
|
+
if (this.ctrl.disabled) btn.disabled = true;
|
|
2681
|
+
const label = doc.createElement("span");
|
|
2682
|
+
label.className = "vela-select-label";
|
|
2683
|
+
label.textContent = this.ctrl.labelOf(this.ctrl.value);
|
|
2684
|
+
const chevron = doc.createElement("span");
|
|
2685
|
+
chevron.className = "vela-select-chevron";
|
|
2686
|
+
chevron.appendChild(iconEl("chevron-down", doc));
|
|
2687
|
+
btn.append(label, chevron);
|
|
2688
|
+
wrap.appendChild(btn);
|
|
2689
|
+
const sizer = doc.createElement("div");
|
|
2690
|
+
sizer.className = "vela-select-sizer";
|
|
2691
|
+
sizer.setAttribute("aria-hidden", "true");
|
|
2692
|
+
for (const o of this.ctrl.options) {
|
|
2693
|
+
const s = doc.createElement("span");
|
|
2694
|
+
s.textContent = o.label;
|
|
2695
|
+
sizer.appendChild(s);
|
|
2696
|
+
}
|
|
2697
|
+
wrap.appendChild(sizer);
|
|
2698
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
2699
|
+
e.preventDefault();
|
|
2700
|
+
btn.focus({ preventScroll: true });
|
|
2701
|
+
});
|
|
2702
|
+
btn.addEventListener("click", (e) => {
|
|
2703
|
+
e.stopPropagation();
|
|
2704
|
+
this.toggle();
|
|
2705
|
+
});
|
|
2706
|
+
this.el = wrap;
|
|
2707
|
+
this.trigger = btn;
|
|
2708
|
+
this.labelEl = label;
|
|
2709
|
+
}
|
|
2710
|
+
get value() {
|
|
2711
|
+
return this.ctrl.value;
|
|
2712
|
+
}
|
|
2713
|
+
setValue(v) {
|
|
2714
|
+
this.ctrl.setValue(v);
|
|
2715
|
+
this.labelEl.textContent = this.ctrl.labelOf(v);
|
|
2716
|
+
}
|
|
2717
|
+
toggle() {
|
|
2718
|
+
this.list = toggleSelectList(
|
|
2719
|
+
this.trigger,
|
|
2720
|
+
this.ctrl.options,
|
|
2721
|
+
this.ctrl.value,
|
|
2722
|
+
(value, label) => {
|
|
2723
|
+
this.ctrl.pick(value);
|
|
2724
|
+
this.labelEl.textContent = label;
|
|
2725
|
+
this.list = null;
|
|
2726
|
+
},
|
|
2727
|
+
{ ...this.listOpts, onClose: () => {
|
|
2728
|
+
this.list = null;
|
|
2729
|
+
this.listOpts.onClose?.();
|
|
2730
|
+
} }
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
destroy() {
|
|
2734
|
+
this.list?.hide();
|
|
2735
|
+
this.list = null;
|
|
2736
|
+
}
|
|
2737
|
+
};
|
|
2738
|
+
|
|
2739
|
+
// src/ui/components/number-input/controller.ts
|
|
2740
|
+
function clampNumber(n, opts) {
|
|
2741
|
+
let v = n;
|
|
2742
|
+
if (opts.min !== void 0) v = Math.max(opts.min, v);
|
|
2743
|
+
if (opts.max !== void 0) v = Math.min(opts.max, v);
|
|
2744
|
+
if (opts.integer) v = Math.round(v);
|
|
2745
|
+
return v;
|
|
2746
|
+
}
|
|
2747
|
+
function decimalsOf(n) {
|
|
2748
|
+
const s = String(n);
|
|
2749
|
+
const e = s.search(/[eE]/);
|
|
2750
|
+
if (e >= 0) {
|
|
2751
|
+
const frac = s.slice(0, e).split(".")[1]?.length ?? 0;
|
|
2752
|
+
return Math.max(0, Math.min(20, frac - Number(s.slice(e + 1))));
|
|
2753
|
+
}
|
|
2754
|
+
return s.split(".")[1]?.length ?? 0;
|
|
2755
|
+
}
|
|
2756
|
+
function snapToStep(n, base, step) {
|
|
2757
|
+
const d = Math.min(20, Math.max(decimalsOf(base), decimalsOf(step)));
|
|
2758
|
+
return Number(n.toFixed(d));
|
|
2759
|
+
}
|
|
2760
|
+
function numberInputController(opts) {
|
|
2761
|
+
let value = opts.value;
|
|
2762
|
+
const integer = opts.integer ?? false;
|
|
2763
|
+
const commit = opts.commit ?? "blur";
|
|
2764
|
+
const clamp = opts.clamp ?? commit === "blur";
|
|
2765
|
+
const step = opts.step ?? (integer ? 1 : 0.1);
|
|
2766
|
+
const onChange = opts.onChange;
|
|
2767
|
+
const normalize = (raw) => clamp ? clampNumber(raw, { min: opts.min, max: opts.max, integer }) : raw;
|
|
2768
|
+
const apply = (raw) => {
|
|
2769
|
+
if (!Number.isFinite(raw)) return null;
|
|
2770
|
+
const n = normalize(raw);
|
|
2771
|
+
if (n !== value) {
|
|
2772
|
+
value = n;
|
|
2773
|
+
onChange?.(n);
|
|
2774
|
+
}
|
|
2775
|
+
return n;
|
|
2776
|
+
};
|
|
2777
|
+
return {
|
|
2778
|
+
get value() {
|
|
2779
|
+
return value;
|
|
2780
|
+
},
|
|
2781
|
+
min: opts.min,
|
|
2782
|
+
max: opts.max,
|
|
2783
|
+
step,
|
|
2784
|
+
integer,
|
|
2785
|
+
size: opts.size ?? "md",
|
|
2786
|
+
commit,
|
|
2787
|
+
steppers: opts.steppers ?? commit === "blur",
|
|
2788
|
+
clamp,
|
|
2789
|
+
disabled: opts.disabled ?? false,
|
|
2790
|
+
apply,
|
|
2791
|
+
sync(raw) {
|
|
2792
|
+
const n = Number.isFinite(raw) ? normalize(raw) : value;
|
|
2793
|
+
value = n;
|
|
2794
|
+
return n;
|
|
2795
|
+
},
|
|
2796
|
+
nudge(dir) {
|
|
2797
|
+
return apply(snapToStep(value + dir * step, value, step));
|
|
2798
|
+
}
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// src/ui/components/number-input/styles.ts
|
|
2803
|
+
var NUMBER_STYLE_ID = "vela-ui-number";
|
|
2804
|
+
var NUMBER_CSS = `
|
|
2805
|
+
.vela-num { position: relative; display: inline-block; min-width: 0; }
|
|
2806
|
+
.vela-num[data-fill] { width: 100%; }
|
|
2807
|
+
.vela-num input {
|
|
2808
|
+
width: 100%;
|
|
2809
|
+
box-sizing: border-box;
|
|
2810
|
+
height: 34px;
|
|
2811
|
+
background: transparent;
|
|
2812
|
+
border: 1px solid var(--vela-border-strong);
|
|
2813
|
+
border-radius: 6px;
|
|
2814
|
+
color: var(--vela-fg-bright);
|
|
2815
|
+
padding: 0 8px;
|
|
2816
|
+
font-size: 14px;
|
|
2817
|
+
font-family: inherit;
|
|
2818
|
+
outline: none;
|
|
2819
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
2820
|
+
-moz-appearance: textfield;
|
|
2821
|
+
appearance: textfield;
|
|
2822
|
+
}
|
|
2823
|
+
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
2824
|
+
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
2825
|
+
.vela-num input:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
2826
|
+
.vela-num[data-steppers] input { padding-right: 26px; }
|
|
2827
|
+
.vela-num[data-size='sm'] { width: 64px; flex: none; }
|
|
2828
|
+
.vela-num[data-size='sm'][data-compact] { width: 56px; }
|
|
2829
|
+
.vela-num[data-size='sm'] input {
|
|
2830
|
+
height: 28px;
|
|
2831
|
+
background: var(--vela-surface-elev);
|
|
2832
|
+
border-radius: var(--vela-radius-sm);
|
|
2833
|
+
color: var(--vela-fg);
|
|
2834
|
+
font-size: 13px;
|
|
2835
|
+
box-shadow: none;
|
|
2836
|
+
}
|
|
2837
|
+
.vela-num[data-size='sm'] input:focus { box-shadow: none; }
|
|
2838
|
+
.vela-num-step {
|
|
2839
|
+
position: absolute;
|
|
2840
|
+
right: 0;
|
|
2841
|
+
top: 0;
|
|
2842
|
+
bottom: 0;
|
|
2843
|
+
width: 22px;
|
|
2844
|
+
display: none;
|
|
2845
|
+
flex-direction: column;
|
|
2846
|
+
justify-content: center;
|
|
2847
|
+
box-sizing: border-box;
|
|
2848
|
+
padding: 2px 6px 2px 0;
|
|
2849
|
+
line-height: 0;
|
|
2850
|
+
}
|
|
2851
|
+
.vela-num[data-steppers]:hover .vela-num-step { display: flex; }
|
|
2852
|
+
.vela-num-step button {
|
|
2853
|
+
flex: 1;
|
|
2854
|
+
width: 100%;
|
|
2855
|
+
min-height: 0;
|
|
2856
|
+
border: none;
|
|
2857
|
+
background: transparent;
|
|
2858
|
+
color: var(--vela-fg-muted);
|
|
2859
|
+
border-radius: 3px;
|
|
2860
|
+
padding: 0;
|
|
2861
|
+
cursor: pointer;
|
|
2862
|
+
display: flex;
|
|
2863
|
+
align-items: center;
|
|
2864
|
+
justify-content: center;
|
|
2865
|
+
}
|
|
2866
|
+
.vela-num-step button:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
2867
|
+
.vela-num-step .vela-icon, .vela-num-step svg { width: 12px; height: 12px; display: block; }
|
|
2868
|
+
`;
|
|
2869
|
+
|
|
2870
|
+
// src/ui/components/number-input/view.ts
|
|
2871
|
+
var NumberInput = class {
|
|
2872
|
+
constructor(opts) {
|
|
2873
|
+
const doc = document;
|
|
2874
|
+
injectStyles(NUMBER_STYLE_ID, NUMBER_CSS, doc);
|
|
2875
|
+
this.ctrl = numberInputController(opts);
|
|
2876
|
+
this.placeholderMode = opts.placeholder !== void 0;
|
|
2877
|
+
this.emptyValue = opts.emptyValue ?? opts.value;
|
|
2878
|
+
this.last = String(opts.value);
|
|
2879
|
+
const wrap = doc.createElement("div");
|
|
2880
|
+
wrap.className = "vela-num";
|
|
2881
|
+
wrap.dataset.size = this.ctrl.size;
|
|
2882
|
+
if (this.ctrl.size !== "sm") wrap.dataset.fill = "";
|
|
2883
|
+
if (opts.compact) wrap.dataset.compact = "";
|
|
2884
|
+
if (this.ctrl.steppers) wrap.dataset.steppers = "";
|
|
2885
|
+
const ni = doc.createElement("input");
|
|
2886
|
+
ni.type = "number";
|
|
2887
|
+
if (opts.id) ni.id = opts.id;
|
|
2888
|
+
if (opts.title) ni.title = opts.title;
|
|
2889
|
+
if (opts.min !== void 0) ni.min = String(opts.min);
|
|
2890
|
+
if (opts.max !== void 0) ni.max = String(opts.max);
|
|
2891
|
+
ni.step = String(this.ctrl.step);
|
|
2892
|
+
if (this.ctrl.disabled) ni.disabled = true;
|
|
2893
|
+
if (this.placeholderMode) {
|
|
2894
|
+
ni.placeholder = opts.placeholder ?? "";
|
|
2895
|
+
ni.value = opts.value !== this.emptyValue ? String(opts.value) : "";
|
|
2896
|
+
} else {
|
|
2897
|
+
ni.value = String(opts.value);
|
|
2898
|
+
}
|
|
2899
|
+
const commitRaw = (raw) => {
|
|
2900
|
+
const n = this.ctrl.apply(raw);
|
|
2901
|
+
if (n === null) {
|
|
2902
|
+
ni.value = this.placeholderMode && this.ctrl.value === this.emptyValue ? "" : this.last;
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
this.last = String(n);
|
|
2906
|
+
if (this.placeholderMode && n === this.emptyValue) ni.value = "";
|
|
2907
|
+
else ni.value = String(n);
|
|
2908
|
+
};
|
|
2909
|
+
if (this.placeholderMode) {
|
|
2910
|
+
ni.addEventListener("change", () => {
|
|
2911
|
+
const raw = ni.value.trim() === "" ? this.emptyValue : Number(ni.value);
|
|
2912
|
+
commitRaw(Number.isFinite(raw) ? raw : this.emptyValue);
|
|
2913
|
+
});
|
|
2914
|
+
} else if (this.ctrl.commit === "live") {
|
|
2915
|
+
ni.addEventListener("input", () => {
|
|
2916
|
+
const v = Number(ni.value);
|
|
2917
|
+
if (Number.isFinite(v)) this.ctrl.apply(v);
|
|
2918
|
+
});
|
|
2919
|
+
} else {
|
|
2920
|
+
ni.addEventListener("blur", () => commitRaw(Number(ni.value)));
|
|
2921
|
+
ni.addEventListener("keydown", (e) => {
|
|
2922
|
+
if (e.key === "Enter") {
|
|
2923
|
+
e.preventDefault();
|
|
2924
|
+
ni.blur();
|
|
2925
|
+
}
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
wrap.appendChild(ni);
|
|
2929
|
+
if (this.ctrl.steppers) {
|
|
2930
|
+
wrap.appendChild(this.buildSteppers(doc, (dir) => {
|
|
2931
|
+
const cur = Number(ni.value);
|
|
2932
|
+
const base = Number.isFinite(cur) ? cur : this.ctrl.value;
|
|
2933
|
+
commitRaw(snapToStep(base + dir * this.ctrl.step, base, this.ctrl.step));
|
|
2934
|
+
}));
|
|
2935
|
+
}
|
|
2936
|
+
this.el = wrap;
|
|
2937
|
+
this.input = ni;
|
|
2938
|
+
}
|
|
2939
|
+
get value() {
|
|
2940
|
+
return this.ctrl.value;
|
|
2941
|
+
}
|
|
2942
|
+
setValue(v) {
|
|
2943
|
+
const n = this.ctrl.sync(v);
|
|
2944
|
+
this.last = String(n);
|
|
2945
|
+
if (this.placeholderMode && n === this.emptyValue) this.input.value = "";
|
|
2946
|
+
else this.input.value = String(n);
|
|
2947
|
+
}
|
|
2948
|
+
buildSteppers(doc, applyDir) {
|
|
2949
|
+
const steps = doc.createElement("div");
|
|
2950
|
+
steps.className = "vela-num-step";
|
|
2951
|
+
const mk = (dir, icon2, label) => {
|
|
2952
|
+
const b = doc.createElement("button");
|
|
2953
|
+
b.type = "button";
|
|
2954
|
+
b.tabIndex = -1;
|
|
2955
|
+
b.setAttribute("aria-label", label);
|
|
2956
|
+
b.appendChild(iconEl(icon2, doc));
|
|
2957
|
+
let timer = 0;
|
|
2958
|
+
const apply = () => applyDir(dir);
|
|
2959
|
+
const stop = () => {
|
|
2960
|
+
if (timer) {
|
|
2961
|
+
window.clearTimeout(timer);
|
|
2962
|
+
timer = 0;
|
|
2963
|
+
}
|
|
2964
|
+
};
|
|
2965
|
+
b.addEventListener("pointerdown", (e) => {
|
|
2966
|
+
e.preventDefault();
|
|
2967
|
+
e.stopPropagation();
|
|
2968
|
+
apply();
|
|
2969
|
+
timer = window.setTimeout(function tick() {
|
|
2970
|
+
apply();
|
|
2971
|
+
timer = window.setTimeout(tick, 60);
|
|
2972
|
+
}, 400);
|
|
2973
|
+
});
|
|
2974
|
+
b.addEventListener("pointerup", stop);
|
|
2975
|
+
b.addEventListener("pointerleave", stop);
|
|
2976
|
+
b.addEventListener("pointercancel", stop);
|
|
2977
|
+
return b;
|
|
2978
|
+
};
|
|
2979
|
+
steps.append(mk(1, "chevron-up", "Increase"), mk(-1, "chevron-down", "Decrease"));
|
|
2980
|
+
return steps;
|
|
2981
|
+
}
|
|
2982
|
+
};
|
|
2983
|
+
|
|
2984
|
+
// src/ui/components/text-field/controller.ts
|
|
2985
|
+
function textFieldController(opts = {}) {
|
|
2986
|
+
let value = opts.value ?? "";
|
|
2987
|
+
const size = opts.size ?? "md";
|
|
2988
|
+
return {
|
|
2989
|
+
get value() {
|
|
2990
|
+
return value;
|
|
2991
|
+
},
|
|
2992
|
+
size,
|
|
2993
|
+
fill: opts.fill ?? size !== "sm",
|
|
2994
|
+
disabled: opts.disabled ?? false,
|
|
2995
|
+
commit(next) {
|
|
2996
|
+
if (next === value) return null;
|
|
2997
|
+
value = next;
|
|
2998
|
+
opts.onChange?.(next);
|
|
2999
|
+
return next;
|
|
3000
|
+
},
|
|
3001
|
+
sync(next) {
|
|
3002
|
+
value = next;
|
|
3003
|
+
}
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// src/ui/components/text-field/styles.ts
|
|
3008
|
+
var TEXT_STYLE_ID = "vela-ui-text";
|
|
3009
|
+
var TEXT_CSS = `
|
|
3010
|
+
.vela-text {
|
|
3011
|
+
display: inline-block;
|
|
3012
|
+
min-width: 0;
|
|
3013
|
+
}
|
|
3014
|
+
.vela-text[data-fill] { width: 100%; }
|
|
3015
|
+
.vela-text-field {
|
|
3016
|
+
width: 100%;
|
|
3017
|
+
box-sizing: border-box;
|
|
3018
|
+
height: 34px;
|
|
3019
|
+
background: transparent;
|
|
3020
|
+
border: 1px solid var(--vela-border-strong);
|
|
3021
|
+
border-radius: 6px;
|
|
3022
|
+
color: var(--vela-fg-bright);
|
|
3023
|
+
padding: 0 8px;
|
|
3024
|
+
font-size: 14px;
|
|
3025
|
+
font-family: inherit;
|
|
3026
|
+
outline: none;
|
|
3027
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
3028
|
+
}
|
|
3029
|
+
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
3030
|
+
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
3031
|
+
.vela-text[data-size='sm'] .vela-text-field {
|
|
3032
|
+
height: 28px;
|
|
3033
|
+
background: var(--vela-surface-elev);
|
|
3034
|
+
border-radius: var(--vela-radius-sm);
|
|
3035
|
+
color: var(--vela-fg);
|
|
3036
|
+
font-size: 13px;
|
|
3037
|
+
}
|
|
3038
|
+
`;
|
|
3039
|
+
|
|
3040
|
+
// src/ui/components/text-field/view.ts
|
|
3041
|
+
var TextField = class {
|
|
3042
|
+
constructor(opts = {}) {
|
|
3043
|
+
const doc = document;
|
|
3044
|
+
injectStyles(TEXT_STYLE_ID, TEXT_CSS, doc);
|
|
3045
|
+
this.ctrl = textFieldController(opts);
|
|
3046
|
+
const wrap = doc.createElement("div");
|
|
3047
|
+
wrap.className = "vela-text";
|
|
3048
|
+
wrap.dataset.size = this.ctrl.size;
|
|
3049
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
3050
|
+
const ti = doc.createElement("input");
|
|
3051
|
+
ti.type = "text";
|
|
3052
|
+
if (opts.id) ti.id = opts.id;
|
|
3053
|
+
ti.value = this.ctrl.value;
|
|
3054
|
+
ti.className = "vela-text-field";
|
|
3055
|
+
if (this.ctrl.disabled) ti.disabled = true;
|
|
3056
|
+
ti.addEventListener("blur", () => {
|
|
3057
|
+
this.ctrl.commit(ti.value);
|
|
3058
|
+
});
|
|
3059
|
+
ti.addEventListener("keydown", (e) => {
|
|
3060
|
+
if (e.key === "Enter") {
|
|
3061
|
+
e.preventDefault();
|
|
3062
|
+
ti.blur();
|
|
3063
|
+
}
|
|
3064
|
+
});
|
|
3065
|
+
wrap.appendChild(ti);
|
|
3066
|
+
this.el = wrap;
|
|
3067
|
+
this.input = ti;
|
|
3068
|
+
}
|
|
3069
|
+
get value() {
|
|
3070
|
+
return this.ctrl.value;
|
|
3071
|
+
}
|
|
3072
|
+
setValue(v) {
|
|
3073
|
+
this.ctrl.sync(v);
|
|
3074
|
+
this.input.value = v;
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
|
|
3078
|
+
// src/ui/components/color-picker/controller.ts
|
|
3079
|
+
function splitColor(color) {
|
|
3080
|
+
const s = String(color ?? "").trim();
|
|
3081
|
+
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
3082
|
+
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
3083
|
+
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
3084
|
+
if (m) {
|
|
3085
|
+
const h = m[1].toLowerCase();
|
|
3086
|
+
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
3087
|
+
}
|
|
3088
|
+
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
3089
|
+
if (rgba) {
|
|
3090
|
+
const p = rgba[1].split(",").map((v) => v.trim());
|
|
3091
|
+
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
3092
|
+
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
3093
|
+
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
3094
|
+
}
|
|
3095
|
+
return { hex6: ACCENT_BRIGHT, alpha: 1 };
|
|
3096
|
+
}
|
|
3097
|
+
function combineColor(hex6, alpha) {
|
|
3098
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
3099
|
+
if (a >= 0.999) return hex6;
|
|
3100
|
+
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
3101
|
+
}
|
|
3102
|
+
function blendOver(fg, bg, alpha) {
|
|
3103
|
+
const rgb = (c) => {
|
|
3104
|
+
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
3105
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
3106
|
+
};
|
|
3107
|
+
const [fr, fgc, fb] = rgb(fg);
|
|
3108
|
+
const [br, bgc, bb] = rgb(bg);
|
|
3109
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
3110
|
+
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
3111
|
+
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
3112
|
+
}
|
|
3113
|
+
function hslHex(h, s, l) {
|
|
3114
|
+
const sn = s / 100;
|
|
3115
|
+
const ln = l / 100;
|
|
3116
|
+
const k = (n) => (n + h / 30) % 12;
|
|
3117
|
+
const a = sn * Math.min(ln, 1 - ln);
|
|
3118
|
+
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
3119
|
+
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
3120
|
+
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
3121
|
+
}
|
|
3122
|
+
function buildPalette() {
|
|
3123
|
+
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
3124
|
+
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
3125
|
+
const levels = [
|
|
3126
|
+
[82, 56],
|
|
3127
|
+
[58, 84],
|
|
3128
|
+
[62, 74],
|
|
3129
|
+
[66, 62],
|
|
3130
|
+
[70, 50],
|
|
3131
|
+
[64, 39],
|
|
3132
|
+
[54, 29]
|
|
3133
|
+
];
|
|
3134
|
+
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
3135
|
+
}
|
|
3136
|
+
function transparencyChecker(size) {
|
|
3137
|
+
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
// src/ui/components/color-picker/styles.ts
|
|
3141
|
+
var COLOR_STYLE_ID = "vela-ui-color";
|
|
3142
|
+
var COLOR_CSS = `
|
|
3143
|
+
.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;}
|
|
3144
|
+
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
3145
|
+
.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);}
|
|
3146
|
+
.vela-color-field-circle{width:26px;height:26px;padding:3px;border:1px solid var(--vela-border-strong);border-radius:4px;background:transparent;overflow:hidden;}
|
|
3147
|
+
.vela-color-field-circle .vela-color-field-swatch{border-radius:2px;box-shadow:none;}
|
|
3148
|
+
.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;}
|
|
3149
|
+
`;
|
|
3150
|
+
|
|
3151
|
+
// src/ui/components/color-picker/view.ts
|
|
3152
|
+
var PALETTE = buildPalette();
|
|
3153
|
+
var CHECKER = transparencyChecker(10);
|
|
3154
|
+
var FIELD_CHECKER = transparencyChecker(8);
|
|
3155
|
+
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
3156
|
+
function addRecent(hex6) {
|
|
3157
|
+
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
3158
|
+
if (i >= 0) recents.splice(i, 1);
|
|
3159
|
+
recents.unshift(hex6);
|
|
3160
|
+
if (recents.length > 10) recents.length = 10;
|
|
3161
|
+
}
|
|
3162
|
+
function buildColorPicker(color, theme, onChange) {
|
|
3163
|
+
const parsed = splitColor(color);
|
|
3164
|
+
let curHex = parsed.hex6;
|
|
3165
|
+
let curAlpha = parsed.alpha;
|
|
3166
|
+
const root = document.createElement("div");
|
|
3167
|
+
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
3168
|
+
const grid = document.createElement("div");
|
|
3169
|
+
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
3170
|
+
const swatches = [];
|
|
3171
|
+
for (const row of PALETTE) {
|
|
3172
|
+
for (const c of row) {
|
|
3173
|
+
const sw = document.createElement("button");
|
|
3174
|
+
sw.type = "button";
|
|
3175
|
+
sw.dataset.c = c;
|
|
3176
|
+
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
3177
|
+
sw.addEventListener("click", (e) => {
|
|
3178
|
+
e.stopPropagation();
|
|
3179
|
+
pickHex(c);
|
|
3180
|
+
});
|
|
3181
|
+
grid.appendChild(sw);
|
|
3182
|
+
swatches.push(sw);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
const paintSelection = () => {
|
|
3186
|
+
for (const sw of swatches) {
|
|
3187
|
+
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
3188
|
+
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
3189
|
+
sw.style.outlineOffset = "2px";
|
|
3190
|
+
sw.style.zIndex = on ? "1" : "";
|
|
3191
|
+
sw.style.position = on ? "relative" : "";
|
|
3192
|
+
}
|
|
3193
|
+
};
|
|
3194
|
+
const recentRow = document.createElement("div");
|
|
3195
|
+
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
3196
|
+
const renderRecents = () => {
|
|
3197
|
+
recentRow.replaceChildren();
|
|
3198
|
+
for (const c of recents) {
|
|
3199
|
+
const sw = document.createElement("button");
|
|
3200
|
+
sw.type = "button";
|
|
3201
|
+
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;`;
|
|
3202
|
+
sw.addEventListener("click", (e) => {
|
|
3203
|
+
e.stopPropagation();
|
|
3204
|
+
pickHex(c);
|
|
3205
|
+
});
|
|
3206
|
+
recentRow.appendChild(sw);
|
|
3207
|
+
}
|
|
3208
|
+
const add = document.createElement("label");
|
|
3209
|
+
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;`;
|
|
3210
|
+
add.textContent = "+";
|
|
3211
|
+
const input = document.createElement("input");
|
|
3212
|
+
input.type = "color";
|
|
3213
|
+
input.value = curHex;
|
|
3214
|
+
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
3215
|
+
input.addEventListener("input", () => pickHex(input.value, true));
|
|
3216
|
+
add.appendChild(input);
|
|
3217
|
+
recentRow.appendChild(add);
|
|
3218
|
+
};
|
|
3219
|
+
const opLabel = document.createElement("div");
|
|
3220
|
+
opLabel.textContent = "Opacity";
|
|
3221
|
+
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
3222
|
+
const opRow = document.createElement("div");
|
|
3223
|
+
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
3224
|
+
const track = document.createElement("div");
|
|
3225
|
+
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
3226
|
+
const knob = document.createElement("div");
|
|
3227
|
+
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;";
|
|
3228
|
+
track.appendChild(knob);
|
|
3229
|
+
const pctBox = document.createElement("div");
|
|
3230
|
+
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;`;
|
|
3231
|
+
opRow.append(track, pctBox);
|
|
3232
|
+
const paintOpacity = () => {
|
|
3233
|
+
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
3234
|
+
knob.style.left = `${curAlpha * 100}%`;
|
|
3235
|
+
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
3236
|
+
};
|
|
3237
|
+
let dragging = false;
|
|
3238
|
+
const onDrag = (clientX) => {
|
|
3239
|
+
const r = track.getBoundingClientRect();
|
|
3240
|
+
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
3241
|
+
paintOpacity();
|
|
3242
|
+
emit();
|
|
3243
|
+
};
|
|
3244
|
+
track.addEventListener("pointerdown", (e) => {
|
|
3245
|
+
e.stopPropagation();
|
|
3246
|
+
dragging = true;
|
|
3247
|
+
track.setPointerCapture(e.pointerId);
|
|
3248
|
+
onDrag(e.clientX);
|
|
3249
|
+
});
|
|
3250
|
+
track.addEventListener("pointermove", (e) => {
|
|
3251
|
+
if (dragging) onDrag(e.clientX);
|
|
3252
|
+
});
|
|
3253
|
+
track.addEventListener("pointerup", () => dragging = false);
|
|
3254
|
+
function emit() {
|
|
3255
|
+
onChange(combineColor(curHex, curAlpha));
|
|
3256
|
+
}
|
|
3257
|
+
function pickHex(hex, custom = false) {
|
|
3258
|
+
curHex = splitColor(hex).hex6;
|
|
3259
|
+
addRecent(curHex);
|
|
3260
|
+
paintSelection();
|
|
3261
|
+
paintOpacity();
|
|
3262
|
+
if (custom) renderRecents();
|
|
3263
|
+
emit();
|
|
3264
|
+
}
|
|
3265
|
+
renderRecents();
|
|
3266
|
+
paintSelection();
|
|
3267
|
+
paintOpacity();
|
|
3268
|
+
root.append(grid, recentRow, opLabel, opRow);
|
|
3269
|
+
return root;
|
|
3270
|
+
}
|
|
3271
|
+
function colorField(theme, getVal, onVal, opts) {
|
|
3272
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
3273
|
+
}
|
|
3274
|
+
var ColorField = class {
|
|
3275
|
+
constructor(opts) {
|
|
3276
|
+
injectStyles(COLOR_STYLE_ID, COLOR_CSS, document);
|
|
3277
|
+
this.theme = opts.theme;
|
|
3278
|
+
this.getVal = opts.getVal;
|
|
3279
|
+
this.onVal = opts.onVal;
|
|
3280
|
+
this.popoverOpts = opts.popover;
|
|
3281
|
+
const trigger = document.createElement("button");
|
|
3282
|
+
trigger.type = "button";
|
|
3283
|
+
if (opts.id) trigger.id = opts.id;
|
|
3284
|
+
trigger.className = opts.shape === "circle" ? "vela-color-field vela-color-field-circle" : "vela-color-field";
|
|
3285
|
+
const swatch = document.createElement("span");
|
|
3286
|
+
swatch.className = "vela-color-field-swatch";
|
|
3287
|
+
trigger.appendChild(swatch);
|
|
3288
|
+
this.el = trigger;
|
|
3289
|
+
this.swatch = swatch;
|
|
3290
|
+
this.paint();
|
|
3291
|
+
trigger.addEventListener("vela-sync", () => this.paint());
|
|
3292
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
3293
|
+
e.preventDefault();
|
|
3294
|
+
trigger.focus({ preventScroll: true });
|
|
3295
|
+
});
|
|
3296
|
+
trigger.addEventListener("click", (e) => {
|
|
3297
|
+
e.stopPropagation();
|
|
3298
|
+
this.toggle();
|
|
3299
|
+
});
|
|
3300
|
+
}
|
|
3301
|
+
paint() {
|
|
3302
|
+
const v = this.getVal();
|
|
3303
|
+
this.swatch.style.background = `linear-gradient(${v}, ${v}), ${FIELD_CHECKER}`;
|
|
3304
|
+
}
|
|
3305
|
+
toggle() {
|
|
3306
|
+
if (openPopoverTrigger() === this.el) {
|
|
3307
|
+
closeOpenPopovers();
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
const pop = new Popover({
|
|
3311
|
+
trigger: this.el,
|
|
3312
|
+
theme: this.theme,
|
|
3313
|
+
align: this.popoverOpts?.align ?? "end",
|
|
3314
|
+
gap: this.popoverOpts?.gap ?? 6,
|
|
3315
|
+
host: this.popoverOpts?.host,
|
|
3316
|
+
position: this.popoverOpts?.position,
|
|
3317
|
+
boundary: this.popoverOpts?.boundary,
|
|
3318
|
+
zIndex: this.popoverOpts?.zIndex,
|
|
3319
|
+
className: "vela-color-field-pop",
|
|
3320
|
+
content: buildColorPicker(this.getVal(), this.theme, (val) => {
|
|
3321
|
+
this.onVal(val);
|
|
3322
|
+
this.paint();
|
|
3323
|
+
})
|
|
3324
|
+
});
|
|
3325
|
+
pop.show();
|
|
3326
|
+
}
|
|
3327
|
+
};
|
|
3328
|
+
|
|
2084
3329
|
// src/widget/layout-picker.ts
|
|
2085
3330
|
var STYLE_ID = "vela-widget-layout-picker-v14";
|
|
2086
3331
|
var CSS2 = `
|
|
@@ -2837,6 +4082,36 @@ function parseTimeframe(text) {
|
|
|
2837
4082
|
short: `${count}${UNIT_SHORT[key]}`
|
|
2838
4083
|
};
|
|
2839
4084
|
}
|
|
4085
|
+
function timeframeMs(value) {
|
|
4086
|
+
const v = String(value ?? "").trim();
|
|
4087
|
+
if (!v) return NaN;
|
|
4088
|
+
const m = /^(\d*)\s*([a-zA-Z]?)$/.exec(v);
|
|
4089
|
+
if (!m) return NaN;
|
|
4090
|
+
const key = unitKey((m[2] ?? "").toUpperCase());
|
|
4091
|
+
if (key === null) return NaN;
|
|
4092
|
+
if ((m[1] ?? "") === "" && key === "MIN") return NaN;
|
|
4093
|
+
const count = (m[1] ?? "") === "" ? 1 : parseInt(m[1], 10);
|
|
4094
|
+
if (!Number.isFinite(count) || count < 1) return NaN;
|
|
4095
|
+
return count * UNIT_MS[key];
|
|
4096
|
+
}
|
|
4097
|
+
function favoriteTimeframeChips(favorites) {
|
|
4098
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4099
|
+
const out = [];
|
|
4100
|
+
for (const tf of favorites) {
|
|
4101
|
+
if (seen.has(tf)) continue;
|
|
4102
|
+
seen.add(tf);
|
|
4103
|
+
out.push(tf);
|
|
4104
|
+
}
|
|
4105
|
+
return out.sort((a, b) => {
|
|
4106
|
+
const ma = timeframeMs(a);
|
|
4107
|
+
const mb = timeframeMs(b);
|
|
4108
|
+
const aOk = Number.isFinite(ma);
|
|
4109
|
+
const bOk = Number.isFinite(mb);
|
|
4110
|
+
if (aOk && bOk && ma !== mb) return ma - mb;
|
|
4111
|
+
if (aOk !== bOk) return aOk ? -1 : 1;
|
|
4112
|
+
return 0;
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
2840
4115
|
function timeframeLabel(value) {
|
|
2841
4116
|
const parsed = parseTimeframe(value);
|
|
2842
4117
|
if (!parsed.valid) return value;
|
|
@@ -2889,6 +4164,36 @@ var CSS3 = `
|
|
|
2889
4164
|
color: var(--vela-fg-bright);
|
|
2890
4165
|
}
|
|
2891
4166
|
.vela-widget-symbol:hover, .vela-widget-tf:hover, .vela-widget-style:hover, .vela-widget-indicators:hover, .vela-widget-action-left:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
4167
|
+
/* Timeframe cluster: duration-sorted favorite chips, highlight in place, caret
|
|
4168
|
+
opening the full dropdown. With no favorites the caret is the merged trigger
|
|
4169
|
+
(label + chevron). An unstarred current value sits as an extra chip by the caret. */
|
|
4170
|
+
.vela-widget-tf-group { display: inline-flex; align-items: center; gap: 2px; }
|
|
4171
|
+
.vela-widget-tf-chips { display: inline-flex; align-items: center; gap: 2px; }
|
|
4172
|
+
.vela-widget-tf-chips:empty { display: none; }
|
|
4173
|
+
.vela-widget-tf[data-current='1'] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
4174
|
+
.vela-widget-tf-caret {
|
|
4175
|
+
all: unset;
|
|
4176
|
+
display: inline-flex;
|
|
4177
|
+
align-items: center;
|
|
4178
|
+
justify-content: center;
|
|
4179
|
+
width: 18px;
|
|
4180
|
+
height: 30px;
|
|
4181
|
+
border-radius: 4px;
|
|
4182
|
+
cursor: pointer;
|
|
4183
|
+
color: var(--vela-fg-muted);
|
|
4184
|
+
}
|
|
4185
|
+
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
4186
|
+
.vela-widget-tf-caret[data-solo='1'] {
|
|
4187
|
+
width: auto;
|
|
4188
|
+
padding: 0 6px 0 9px;
|
|
4189
|
+
gap: 4px;
|
|
4190
|
+
color: var(--vela-fg-bright);
|
|
4191
|
+
background: var(--vela-hover-strong);
|
|
4192
|
+
font-size: 13px;
|
|
4193
|
+
font-weight: 550;
|
|
4194
|
+
white-space: nowrap;
|
|
4195
|
+
}
|
|
4196
|
+
.vela-widget-topbar .vela-widget-tf-caret .vela-icon { font-size: 14px; width: 14px; height: 14px; }
|
|
2892
4197
|
.vela-widget-topbar .vela-icon { color: inherit; font-size: 16px; width: 16px; height: 16px; }
|
|
2893
4198
|
/* Width is set in syncHairlines() to exactly one device pixel \u2014 a CSS 1px at
|
|
2894
4199
|
fractional DPR (1.25, 1.5\u2026) straddles two physical pixels and siblings end
|
|
@@ -2993,9 +4298,16 @@ var Topbar = class {
|
|
|
2993
4298
|
this.symbolEl.className = "vela-widget-symbol";
|
|
2994
4299
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
2995
4300
|
if (opts.onSymbolClick) this.symbolEl.addEventListener("click", opts.onSymbolClick);
|
|
2996
|
-
this.
|
|
2997
|
-
this.
|
|
2998
|
-
this.
|
|
4301
|
+
this.tfFavs = [...opts.timeframeFavorites ?? []];
|
|
4302
|
+
this.tfChipsHost = doc.createElement("span");
|
|
4303
|
+
this.tfChipsHost.className = "vela-widget-tf-chips";
|
|
4304
|
+
this.tfCaret = doc.createElement("button");
|
|
4305
|
+
this.tfCaret.className = "vela-widget-tf-caret";
|
|
4306
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
4307
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
4308
|
+
const tfGroup = doc.createElement("span");
|
|
4309
|
+
tfGroup.className = "vela-widget-tf-group";
|
|
4310
|
+
tfGroup.append(this.tfChipsHost, this.tfCaret);
|
|
2999
4311
|
this.styleButton = doc.createElement("button");
|
|
3000
4312
|
this.styleButton.className = "vela-widget-style";
|
|
3001
4313
|
this.renderStyleButton(doc);
|
|
@@ -3035,19 +4347,20 @@ var Topbar = class {
|
|
|
3035
4347
|
d.className = "vela-sep";
|
|
3036
4348
|
return d;
|
|
3037
4349
|
};
|
|
3038
|
-
const leading = [this.symbolEl, sep(),
|
|
4350
|
+
const leading = [this.symbolEl, sep(), tfGroup, sep(), this.styleButton, sep()];
|
|
3039
4351
|
if (this.layoutButton) leading.push(this.layoutButton, sep());
|
|
3040
4352
|
if (indicatorsBtn) leading.push(indicatorsBtn, sep());
|
|
3041
4353
|
this.leftActionsSep = sep();
|
|
3042
4354
|
this.leftActionsSep.hidden = true;
|
|
3043
4355
|
this.el.append(...leading, this.leftActionsHost, this.leftActionsSep, this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
|
|
3044
4356
|
host.appendChild(this.el);
|
|
4357
|
+
this.renderTfChips();
|
|
3045
4358
|
this.onHairlineSync();
|
|
3046
4359
|
this.hairlineRo = new ResizeObserver(this.onHairlineSync);
|
|
3047
4360
|
this.hairlineRo.observe(this.el);
|
|
3048
4361
|
doc.defaultView?.addEventListener("resize", this.onHairlineSync);
|
|
3049
4362
|
this.renderActions();
|
|
3050
|
-
this.tooltips.push(new Tooltip(this.
|
|
4363
|
+
this.tooltips.push(new Tooltip(this.tfCaret, { content: "Timeframe", triggerId: "vela-topbar-tf", host }));
|
|
3051
4364
|
this.tooltips.push(new Tooltip(this.styleButton, { content: "Chart style", triggerId: "vela-topbar-style", host }));
|
|
3052
4365
|
if (this.layoutButton && opts.layout) {
|
|
3053
4366
|
this.tooltips.push(new Tooltip(this.layoutButton, { content: "Layout", triggerId: "vela-topbar-layout", host }));
|
|
@@ -3064,11 +4377,12 @@ var Topbar = class {
|
|
|
3064
4377
|
});
|
|
3065
4378
|
}
|
|
3066
4379
|
this.tfMenu = new Menu({
|
|
3067
|
-
trigger: this.
|
|
4380
|
+
trigger: this.tfCaret,
|
|
3068
4381
|
triggerId: "vela-topbar-tf",
|
|
3069
4382
|
host,
|
|
3070
4383
|
items: this.tfItems(),
|
|
3071
4384
|
onSelect: (id) => opts.onTimeframe(id),
|
|
4385
|
+
onFavorite: (id, on) => opts.onTimeframeFavorite?.(id, on),
|
|
3072
4386
|
// Timeframe labels are two-or-three characters ("1m", "4h", "1D") — the
|
|
3073
4387
|
// stylesheet's default min-width would leave the list mostly empty.
|
|
3074
4388
|
minWidth: "84px"
|
|
@@ -3086,8 +4400,48 @@ var Topbar = class {
|
|
|
3086
4400
|
}
|
|
3087
4401
|
setTimeframe(tf) {
|
|
3088
4402
|
this.timeframe = tf;
|
|
3089
|
-
this.tfButton.textContent = timeframeLabel(tf);
|
|
3090
4403
|
this.tfMenu.setItems(this.tfItems());
|
|
4404
|
+
this.renderTfChips();
|
|
4405
|
+
}
|
|
4406
|
+
/** Reflect the favorite-timeframe set — the quick-switch chips and the dropdown stars. */
|
|
4407
|
+
setTimeframeFavorites(favs) {
|
|
4408
|
+
this.tfFavs = [...favs];
|
|
4409
|
+
this.renderTfChips();
|
|
4410
|
+
this.tfMenu.setItems(this.tfItems());
|
|
4411
|
+
}
|
|
4412
|
+
/** Rebuild the quick-switch chips (current value changed, or the favorite set did). */
|
|
4413
|
+
renderTfChips() {
|
|
4414
|
+
const doc = this.el.ownerDocument;
|
|
4415
|
+
this.tfChipsHost.replaceChildren();
|
|
4416
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
4417
|
+
const chips = stars ? favoriteTimeframeChips(this.tfFavs) : [];
|
|
4418
|
+
const currentIsFav = chips.includes(this.timeframe);
|
|
4419
|
+
const shown = currentIsFav || chips.length === 0 ? chips : [...chips, this.timeframe];
|
|
4420
|
+
for (const tf of shown) {
|
|
4421
|
+
const b = doc.createElement("button");
|
|
4422
|
+
b.className = "vela-widget-tf";
|
|
4423
|
+
const label = timeframeLabel(tf);
|
|
4424
|
+
b.textContent = label;
|
|
4425
|
+
if (tf === this.timeframe) {
|
|
4426
|
+
b.dataset.current = "1";
|
|
4427
|
+
b.setAttribute("aria-current", "true");
|
|
4428
|
+
} else {
|
|
4429
|
+
b.setAttribute("aria-label", `Switch timeframe to ${label}`);
|
|
4430
|
+
b.addEventListener("click", () => this.opts.onTimeframe(tf));
|
|
4431
|
+
}
|
|
4432
|
+
this.tfChipsHost.appendChild(b);
|
|
4433
|
+
}
|
|
4434
|
+
this.tfCaret.replaceChildren();
|
|
4435
|
+
if (chips.length === 0) {
|
|
4436
|
+
this.tfCaret.dataset.solo = "1";
|
|
4437
|
+
this.tfCaret.append(doc.createTextNode(timeframeLabel(this.timeframe)), iconEl("chevron-down", doc));
|
|
4438
|
+
this.tfCaret.setAttribute("aria-label", `Timeframe \u2014 ${timeframeLabel(this.timeframe)}`);
|
|
4439
|
+
} else {
|
|
4440
|
+
delete this.tfCaret.dataset.solo;
|
|
4441
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
4442
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
4443
|
+
}
|
|
4444
|
+
this.onHairlineSync();
|
|
3091
4445
|
}
|
|
3092
4446
|
setPriceStyle(style) {
|
|
3093
4447
|
this.priceStyle = style;
|
|
@@ -3165,9 +4519,9 @@ var Topbar = class {
|
|
|
3165
4519
|
}
|
|
3166
4520
|
/** Reflect a docked side panel's open state on its button — the panels toggle each other,
|
|
3167
4521
|
* so the dock pushes the state rather than the button assuming it. */
|
|
3168
|
-
setPanelActive(id,
|
|
4522
|
+
setPanelActive(id, open2) {
|
|
3169
4523
|
const btn = this.panelBtns.get(id);
|
|
3170
|
-
if (btn) btn.dataset.active =
|
|
4524
|
+
if (btn) btn.dataset.active = open2 ? "1" : "";
|
|
3171
4525
|
}
|
|
3172
4526
|
destroy() {
|
|
3173
4527
|
this.hairlineRo?.disconnect();
|
|
@@ -3205,7 +4559,13 @@ var Topbar = class {
|
|
|
3205
4559
|
}
|
|
3206
4560
|
}
|
|
3207
4561
|
tfItems() {
|
|
3208
|
-
|
|
4562
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
4563
|
+
return this.opts.timeframes.map((tf) => ({
|
|
4564
|
+
id: tf,
|
|
4565
|
+
label: timeframeLabel(tf),
|
|
4566
|
+
checked: tf === this.timeframe,
|
|
4567
|
+
...stars ? { favorite: this.tfFavs.includes(tf) } : {}
|
|
4568
|
+
}));
|
|
3209
4569
|
}
|
|
3210
4570
|
styleItems() {
|
|
3211
4571
|
return priceStyleIds().map((id, i) => ({
|
|
@@ -9490,10 +10850,10 @@ var SidePanel = class {
|
|
|
9490
10850
|
return !this.el.hidden;
|
|
9491
10851
|
}
|
|
9492
10852
|
/** Open/close the panel — a bare call flips it. */
|
|
9493
|
-
toggle(
|
|
9494
|
-
if (
|
|
9495
|
-
this.el.hidden = !
|
|
9496
|
-
this.onOpenChange?.(
|
|
10853
|
+
toggle(open2 = this.el.hidden) {
|
|
10854
|
+
if (open2 === !this.el.hidden) return;
|
|
10855
|
+
this.el.hidden = !open2;
|
|
10856
|
+
this.onOpenChange?.(open2);
|
|
9497
10857
|
}
|
|
9498
10858
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
9499
10859
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -9816,9 +11176,9 @@ var ObjectTree = class extends SidePanel {
|
|
|
9816
11176
|
set groups(next) {
|
|
9817
11177
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
9818
11178
|
}
|
|
9819
|
-
toggle(
|
|
9820
|
-
super.toggle(
|
|
9821
|
-
if (
|
|
11179
|
+
toggle(open2 = this.el.hidden) {
|
|
11180
|
+
super.toggle(open2);
|
|
11181
|
+
if (open2) this.refresh();
|
|
9822
11182
|
}
|
|
9823
11183
|
setSymbol(symbol) {
|
|
9824
11184
|
this.symbolName = parseSymbol(symbol).ticker;
|
|
@@ -10850,9 +12210,9 @@ var DataWindow = class extends SidePanel {
|
|
|
10850
12210
|
this.unsubs = [];
|
|
10851
12211
|
injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
|
|
10852
12212
|
}
|
|
10853
|
-
toggle(
|
|
10854
|
-
super.toggle(
|
|
10855
|
-
if (
|
|
12213
|
+
toggle(open2 = this.el.hidden) {
|
|
12214
|
+
super.toggle(open2);
|
|
12215
|
+
if (open2) this.refresh();
|
|
10856
12216
|
}
|
|
10857
12217
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
10858
12218
|
onChart(chart) {
|
|
@@ -10966,8 +12326,8 @@ var PanelDock = class {
|
|
|
10966
12326
|
}
|
|
10967
12327
|
}
|
|
10968
12328
|
/** Open/close one panel by id — a bare call flips it. Unknown ids are ignored. */
|
|
10969
|
-
toggle(id,
|
|
10970
|
-
this.entries.find((e) => e.id === id)?.panel.toggle(
|
|
12329
|
+
toggle(id, open2) {
|
|
12330
|
+
this.entries.find((e) => e.id === id)?.panel.toggle(open2);
|
|
10971
12331
|
}
|
|
10972
12332
|
/** The open panel's id, or null when the column is closed. */
|
|
10973
12333
|
get openId() {
|
|
@@ -10981,8 +12341,8 @@ var PanelDock = class {
|
|
|
10981
12341
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
10982
12342
|
getState() {
|
|
10983
12343
|
const out = {};
|
|
10984
|
-
const
|
|
10985
|
-
if (
|
|
12344
|
+
const open2 = this.openId;
|
|
12345
|
+
if (open2) out.open = open2;
|
|
10986
12346
|
if (this.widths.size > 0) out.widths = Object.fromEntries(this.widths);
|
|
10987
12347
|
return out.open || out.widths ? out : null;
|
|
10988
12348
|
}
|
|
@@ -11015,13 +12375,13 @@ var PanelDock = class {
|
|
|
11015
12375
|
this.entries.sort((a, b) => a.order - b.order);
|
|
11016
12376
|
const stored = this.widths.get(entry.id);
|
|
11017
12377
|
if (stored !== void 0) entry.panel.setWidth(stored);
|
|
11018
|
-
entry.panel.onOpenChange = (
|
|
11019
|
-
if (
|
|
12378
|
+
entry.panel.onOpenChange = (open2) => {
|
|
12379
|
+
if (open2) {
|
|
11020
12380
|
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
11021
12381
|
this.pendingOpen = null;
|
|
11022
12382
|
}
|
|
11023
|
-
this.deps.chrome.setPanelActive(entry.id,
|
|
11024
|
-
if (
|
|
12383
|
+
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
12384
|
+
if (open2) entry.handle?.onOpen?.();
|
|
11025
12385
|
this.deps.changed?.();
|
|
11026
12386
|
};
|
|
11027
12387
|
entry.panel.onWidthChange = (px) => {
|
|
@@ -11218,8 +12578,8 @@ var SymbolPicker = class {
|
|
|
11218
12578
|
host: opts.host,
|
|
11219
12579
|
closeOnInteractOutside: true,
|
|
11220
12580
|
content: (body) => body.append(searchRow, this.tabs, this.list),
|
|
11221
|
-
onOpenChange: (
|
|
11222
|
-
if (
|
|
12581
|
+
onOpenChange: (open2) => {
|
|
12582
|
+
if (open2) {
|
|
11223
12583
|
this.input.value = this.seed;
|
|
11224
12584
|
this.seed = "";
|
|
11225
12585
|
this.refresh();
|
|
@@ -11228,7 +12588,7 @@ var SymbolPicker = class {
|
|
|
11228
12588
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length);
|
|
11229
12589
|
}, 0);
|
|
11230
12590
|
}
|
|
11231
|
-
opts.onOpenChange?.(
|
|
12591
|
+
opts.onOpenChange?.(open2);
|
|
11232
12592
|
}
|
|
11233
12593
|
});
|
|
11234
12594
|
this.input.addEventListener("input", () => this.refresh());
|
|
@@ -11418,14 +12778,14 @@ var IndicatorPicker = class {
|
|
|
11418
12778
|
closeOnInteractOutside: true,
|
|
11419
12779
|
// The picker adds/removes indicators live — keep the chart undimmed behind it.
|
|
11420
12780
|
content: (body) => body.append(searchRow, this.list),
|
|
11421
|
-
onOpenChange: (
|
|
11422
|
-
this.isOpen =
|
|
11423
|
-
if (
|
|
12781
|
+
onOpenChange: (open2) => {
|
|
12782
|
+
this.isOpen = open2;
|
|
12783
|
+
if (open2) {
|
|
11424
12784
|
this.search.value = "";
|
|
11425
12785
|
this.refresh();
|
|
11426
12786
|
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
11427
12787
|
}
|
|
11428
|
-
opts.onOpenChange?.(
|
|
12788
|
+
opts.onOpenChange?.(open2);
|
|
11429
12789
|
}
|
|
11430
12790
|
});
|
|
11431
12791
|
this.list.addEventListener("click", (e) => {
|
|
@@ -11564,7 +12924,7 @@ var TimeframeQuick = class {
|
|
|
11564
12924
|
draggable: true,
|
|
11565
12925
|
closeOnInteractOutside: true,
|
|
11566
12926
|
content: (body) => body.append(this.input, this.hint),
|
|
11567
|
-
onOpenChange: (
|
|
12927
|
+
onOpenChange: (open2) => opts.onOpenChange?.(open2)
|
|
11568
12928
|
});
|
|
11569
12929
|
this.input.addEventListener("input", () => this.renderHint());
|
|
11570
12930
|
this.input.addEventListener("keydown", (e) => {
|
|
@@ -11641,9 +13001,9 @@ var ShortcutsHelp = class {
|
|
|
11641
13001
|
host,
|
|
11642
13002
|
closeOnInteractOutside: true,
|
|
11643
13003
|
content: (body) => body.appendChild(this.list),
|
|
11644
|
-
onOpenChange: (
|
|
11645
|
-
if (
|
|
11646
|
-
onOpenChange?.(
|
|
13004
|
+
onOpenChange: (open2) => {
|
|
13005
|
+
if (open2) this.refresh();
|
|
13006
|
+
onOpenChange?.(open2);
|
|
11647
13007
|
}
|
|
11648
13008
|
});
|
|
11649
13009
|
}
|
|
@@ -14831,6 +16191,10 @@ function sanitizeState(doc) {
|
|
|
14831
16191
|
const favs = d.favorites.filter((f) => typeof f === "string");
|
|
14832
16192
|
if (favs.length > 0) out.favorites = favs;
|
|
14833
16193
|
}
|
|
16194
|
+
if (Array.isArray(d.timeframeFavorites)) {
|
|
16195
|
+
const favs = d.timeframeFavorites.filter((f) => typeof f === "string");
|
|
16196
|
+
if (favs.length > 0) out.timeframeFavorites = favs;
|
|
16197
|
+
}
|
|
14834
16198
|
const sync = sanitizeSync(d.sync);
|
|
14835
16199
|
if (sync) out.sync = sync;
|
|
14836
16200
|
const tracks = sanitizeTrackSizes(d.trackSizes);
|
|
@@ -17210,6 +18574,7 @@ var LEGEND_CTL_PX = 18;
|
|
|
17210
18574
|
var LEGEND_ROW_PAD_Y = 2;
|
|
17211
18575
|
var LEGEND_ROW_PAD_X = 6;
|
|
17212
18576
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
18577
|
+
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
17213
18578
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
17214
18579
|
var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
|
|
17215
18580
|
var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
|
|
@@ -17254,6 +18619,11 @@ var InputsUI = class {
|
|
|
17254
18619
|
this.dialogTips = [];
|
|
17255
18620
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
17256
18621
|
this.moveMenu = null;
|
|
18622
|
+
/** Open settings-dialog choice menu (custom dropdown — torn down with the dialog). */
|
|
18623
|
+
this.choicePop = null;
|
|
18624
|
+
/** Open settings-dialog calendar popover (torn down with the dialog). */
|
|
18625
|
+
this.calendarPop = null;
|
|
18626
|
+
this.calendarAnchor = null;
|
|
17257
18627
|
/** Collapsed panes → the master indicator id to keep visible (others hidden in the strip). */
|
|
17258
18628
|
this.paneCollapse = /* @__PURE__ */ new Map();
|
|
17259
18629
|
/** The user folded the indicator legend away behind the chevron — CHART-WIDE: every
|
|
@@ -17279,6 +18649,10 @@ var InputsUI = class {
|
|
|
17279
18649
|
this.onDialogKey = (e) => {
|
|
17280
18650
|
if (e.key === "Escape") {
|
|
17281
18651
|
e.preventDefault();
|
|
18652
|
+
if (isPopoverOpen()) {
|
|
18653
|
+
closeOpenPopovers();
|
|
18654
|
+
return;
|
|
18655
|
+
}
|
|
17282
18656
|
this.closeDialog();
|
|
17283
18657
|
}
|
|
17284
18658
|
};
|
|
@@ -17724,6 +19098,7 @@ var InputsUI = class {
|
|
|
17724
19098
|
titleWrap.appendChild(beta);
|
|
17725
19099
|
}
|
|
17726
19100
|
el.appendChild(titleWrap);
|
|
19101
|
+
el.appendChild(statusEl);
|
|
17727
19102
|
const valuesEl = document.createElement("span");
|
|
17728
19103
|
valuesEl.style.cssText = `display:none;align-items:center;gap:5px;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
|
|
17729
19104
|
el.appendChild(valuesEl);
|
|
@@ -17784,7 +19159,6 @@ var InputsUI = class {
|
|
|
17784
19159
|
close.addEventListener("click", () => this.onRemove?.(id));
|
|
17785
19160
|
controlsEl.appendChild(close);
|
|
17786
19161
|
el.appendChild(controlsEl);
|
|
17787
|
-
el.appendChild(statusEl);
|
|
17788
19162
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
17789
19163
|
this.rows.set(id, { id, title, settingsTitle, inputs, values: { ...values }, el, titleEl, statusEl, valuesEl, plotValues: [], plotValuesKey: "", showValues: null, highlighted: false, paneId, hidden: false, eyeEl, controlsEl, extrasEl, native: !!opts.native });
|
|
17790
19164
|
this.syncFoldToggle();
|
|
@@ -17802,7 +19176,7 @@ var InputsUI = class {
|
|
|
17802
19176
|
/**
|
|
17803
19177
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
17804
19178
|
* dots (a fetch is in flight — the same load affordance as the chart's own bar-load dots,
|
|
17805
|
-
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered
|
|
19179
|
+
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
17806
19180
|
*/
|
|
17807
19181
|
setStatus(id, status) {
|
|
17808
19182
|
const row = this.rows.get(id);
|
|
@@ -17814,7 +19188,7 @@ var InputsUI = class {
|
|
|
17814
19188
|
return;
|
|
17815
19189
|
}
|
|
17816
19190
|
if (status === "loading") {
|
|
17817
|
-
el.style.cssText =
|
|
19191
|
+
el.style.cssText = `display:inline-flex;align-items:center;gap:3px;box-sizing:border-box;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;line-height:0;transform:translateY(1px);`;
|
|
17818
19192
|
for (let i = 0; i < 3; i += 1) {
|
|
17819
19193
|
const dot = document.createElement("span");
|
|
17820
19194
|
dot.style.cssText = "width:4px;height:4px;border-radius:50%;background:currentColor;opacity:0.15;flex:none;";
|
|
@@ -17824,7 +19198,7 @@ var InputsUI = class {
|
|
|
17824
19198
|
return;
|
|
17825
19199
|
}
|
|
17826
19200
|
this.ensureStatusKeyframes();
|
|
17827
|
-
el.style.cssText = `display:inline-block;box-sizing:border-box;flex:none;width:8px;height:8px;border-radius:50%;background:${this.theme.upColor};animation:vela-ind-pulse 1.2s ease-in-out infinite;`;
|
|
19201
|
+
el.style.cssText = `display:inline-block;box-sizing:border-box;flex:none;width:8px;height:8px;border-radius:50%;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;transform:translateY(1px);background:${this.theme.upColor};animation:vela-ind-pulse 1.2s ease-in-out infinite;`;
|
|
17828
19202
|
}
|
|
17829
19203
|
/** Inject the status keyframes once (idempotent). */
|
|
17830
19204
|
ensureStatusKeyframes() {
|
|
@@ -17877,15 +19251,15 @@ var InputsUI = class {
|
|
|
17877
19251
|
* construction and is shown/hidden with the cluster container.
|
|
17878
19252
|
*/
|
|
17879
19253
|
syncRowActions(row) {
|
|
17880
|
-
const
|
|
17881
|
-
row.controlsEl.style.display =
|
|
19254
|
+
const open2 = row.highlighted;
|
|
19255
|
+
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
17882
19256
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
17883
19257
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
17884
19258
|
if (child === row.extrasEl) {
|
|
17885
|
-
child.style.display =
|
|
19259
|
+
child.style.display = open2 ? "contents" : "none";
|
|
17886
19260
|
continue;
|
|
17887
19261
|
}
|
|
17888
|
-
child.style.display =
|
|
19262
|
+
child.style.display = open2 ? "inline-flex" : "none";
|
|
17889
19263
|
}
|
|
17890
19264
|
}
|
|
17891
19265
|
/** Drop the current selection outline, if any. */
|
|
@@ -17939,7 +19313,8 @@ var InputsUI = class {
|
|
|
17939
19313
|
/**
|
|
17940
19314
|
* Open the indicator's settings as a centered modal over the chart — a themed,
|
|
17941
19315
|
* dependency-free port of the app's tabbed script-settings dialog: a header with the
|
|
17942
|
-
* indicator title,
|
|
19316
|
+
* indicator title, a tab strip (the default "Inputs" tab plus any tab the inputs
|
|
19317
|
+
* declare via `tab=`), a scrollable body per tab that groups inputs by `group=`
|
|
17943
19318
|
* (section headers) and packs `inline=` inputs onto one row, and a sticky footer.
|
|
17944
19319
|
*
|
|
17945
19320
|
* Edits commit LIVE (each change re-runs the indicator), matching the current
|
|
@@ -17969,55 +19344,57 @@ var InputsUI = class {
|
|
|
17969
19344
|
});
|
|
17970
19345
|
const card = document.createElement("div");
|
|
17971
19346
|
card.className = "vela-ind-dialog";
|
|
17972
|
-
card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(
|
|
19347
|
+
card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(380px,90%);max-width:min(640px,94%);max-height:82%;background:${t.background};border:1px solid ${border};border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow-dialog);color:${fg};color-scheme:${scheme};font:14px ${font};overflow:hidden;`;
|
|
17973
19348
|
if (this.mobileLayout) card.style.cssText += "width:100%;min-width:0;max-width:none;height:100%;max-height:none;border:none;border-radius:0;";
|
|
17974
19349
|
applyChromeTokens(card, t);
|
|
17975
19350
|
card.addEventListener("keydown", (e) => {
|
|
17976
19351
|
if (e.key !== "Escape") e.stopPropagation();
|
|
17977
19352
|
});
|
|
17978
19353
|
const header = document.createElement("div");
|
|
17979
|
-
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px
|
|
19354
|
+
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 20px;flex:0 0 auto;";
|
|
17980
19355
|
const hTitle = document.createElement("span");
|
|
17981
19356
|
hTitle.textContent = row.settingsTitle;
|
|
17982
|
-
hTitle.style.cssText = "font-weight:600;font-size:
|
|
19357
|
+
hTitle.style.cssText = "font-weight:600;font-size:20px;line-height:28px;";
|
|
17983
19358
|
const closeBtn = document.createElement("button");
|
|
17984
19359
|
closeBtn.type = "button";
|
|
17985
19360
|
closeBtn.setAttribute("aria-label", "Close");
|
|
17986
19361
|
this.dialogTips.push(attachChromeTooltip(closeBtn, { host: this.container, theme: () => this.theme, text: () => "Close" }));
|
|
17987
19362
|
closeBtn.innerHTML = iconAt("close", 15);
|
|
17988
|
-
closeBtn.className = "vela-ind-
|
|
17989
|
-
closeBtn.style.cssText = "cursor:pointer;display:inline-flex;align-items:center;background:transparent;border:none;line-height:0;padding:2px;flex:0 0 auto;";
|
|
19363
|
+
closeBtn.className = "vela-ind-dlg-close";
|
|
17990
19364
|
closeBtn.addEventListener("click", () => this.closeDialog());
|
|
17991
19365
|
header.append(hTitle, closeBtn);
|
|
17992
19366
|
card.appendChild(header);
|
|
17993
19367
|
if (!this.mobileLayout) makeDialogDraggable(card, header, { closeSelector: "button" });
|
|
19368
|
+
const tabDefs = tabInputs(row.inputs);
|
|
17994
19369
|
const tabs = document.createElement("div");
|
|
17995
|
-
tabs.style.cssText = `display:flex;gap:
|
|
17996
|
-
const
|
|
17997
|
-
|
|
17998
|
-
|
|
17999
|
-
|
|
18000
|
-
|
|
18001
|
-
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
|
|
18006
|
-
if (group.name) {
|
|
18007
|
-
const gh = document.createElement("div");
|
|
18008
|
-
gh.textContent = group.name;
|
|
18009
|
-
gh.style.cssText = "font-size:var(--vela-font-size-sm);font-weight:600;letter-spacing:0.05em;text-transform:uppercase;color:var(--vela-fg-muted);";
|
|
18010
|
-
section.appendChild(gh);
|
|
19370
|
+
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
19371
|
+
const tabEls = [];
|
|
19372
|
+
const bodies = [];
|
|
19373
|
+
const activate = (idx) => {
|
|
19374
|
+
for (let i = 0; i < tabEls.length; i += 1) {
|
|
19375
|
+
const active = i === idx;
|
|
19376
|
+
const el = tabEls[i];
|
|
19377
|
+
el.style.borderBottomColor = active ? fg : "transparent";
|
|
19378
|
+
el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
|
|
19379
|
+
el.classList.toggle("vela-ind-tab-active", active);
|
|
19380
|
+
bodies[i].style.display = active ? "grid" : "none";
|
|
18011
19381
|
}
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
19382
|
+
};
|
|
19383
|
+
for (const [idx, def] of tabDefs.entries()) {
|
|
19384
|
+
const tab = document.createElement("div");
|
|
19385
|
+
tab.textContent = def.name;
|
|
19386
|
+
tab.className = "vela-ind-tab";
|
|
19387
|
+
tab.style.cssText = `padding:0 0 8px;margin-bottom:-1px;border-bottom:2px solid transparent;font-weight:600;font-size:16px;line-height:24px;${tabDefs.length > 1 ? "cursor:pointer;" : ""}`;
|
|
19388
|
+
tab.addEventListener("click", () => activate(idx));
|
|
19389
|
+
tabs.appendChild(tab);
|
|
19390
|
+
tabEls.push(tab);
|
|
19391
|
+
bodies.push(this.buildTabBody(def.inputs, row));
|
|
18017
19392
|
}
|
|
18018
|
-
card.appendChild(
|
|
19393
|
+
card.appendChild(tabs);
|
|
19394
|
+
for (const body of bodies) card.appendChild(body);
|
|
19395
|
+
activate(0);
|
|
18019
19396
|
const footer = document.createElement("div");
|
|
18020
|
-
footer.style.cssText = `display:flex;justify-content:flex-end;gap:
|
|
19397
|
+
footer.style.cssText = `display:flex;justify-content:flex-end;gap:12px;padding:16px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
|
|
18021
19398
|
footer.append(
|
|
18022
19399
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
18023
19400
|
this.dialogButton("Ok", true, () => this.closeDialog())
|
|
@@ -18029,8 +19406,32 @@ var InputsUI = class {
|
|
|
18029
19406
|
this.backdrop = backdrop;
|
|
18030
19407
|
document.addEventListener("keydown", this.onDialogKey);
|
|
18031
19408
|
}
|
|
19409
|
+
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
19410
|
+
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
19411
|
+
buildTabBody(inputs, row) {
|
|
19412
|
+
const body = document.createElement("div");
|
|
19413
|
+
body.style.cssText = this.mobileLayout ? "padding:16px 20px;overflow-y:auto;overflow-x:hidden;overflow-anchor:none;flex:1 1 auto;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:16px;row-gap:16px;" : "padding:16px 20px;overflow-y:auto;overflow-x:hidden;overflow-anchor:none;flex:1 1 auto;display:grid;grid-template-columns:max-content 1fr;align-items:center;column-gap:16px;row-gap:16px;";
|
|
19414
|
+
for (const group of groupInputs(inputs)) {
|
|
19415
|
+
const section = document.createElement("div");
|
|
19416
|
+
section.style.cssText = "display:contents;";
|
|
19417
|
+
if (group.name) {
|
|
19418
|
+
const gh = document.createElement("div");
|
|
19419
|
+
gh.textContent = group.name;
|
|
19420
|
+
const first = body.childElementCount === 0;
|
|
19421
|
+
gh.style.cssText = `grid-column:1 / -1;font-size:11px;font-weight:600;letter-spacing:0.05em;text-transform:uppercase;color:var(--vela-fg-muted);padding:${first ? "4px" : "20px"} 0 8px;`;
|
|
19422
|
+
section.appendChild(gh);
|
|
19423
|
+
}
|
|
19424
|
+
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
19425
|
+
body.appendChild(section);
|
|
19426
|
+
}
|
|
19427
|
+
return body;
|
|
19428
|
+
}
|
|
18032
19429
|
closeDialog() {
|
|
18033
19430
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
19431
|
+
closeOpenPopovers();
|
|
19432
|
+
this.choicePop = null;
|
|
19433
|
+
this.calendarPop = null;
|
|
19434
|
+
this.calendarAnchor = null;
|
|
18034
19435
|
for (const dispose of this.dialogTips.splice(0)) dispose();
|
|
18035
19436
|
this.backdrop?.remove();
|
|
18036
19437
|
this.backdrop = null;
|
|
@@ -18058,79 +19459,77 @@ var InputsUI = class {
|
|
|
18058
19459
|
buildInputRow(grid, decls, row) {
|
|
18059
19460
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
18060
19461
|
if (decls.length > 1) {
|
|
18061
|
-
const
|
|
18062
|
-
|
|
19462
|
+
const cell2 = document.createElement("div");
|
|
19463
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
|
|
18063
19464
|
for (const d of decls) {
|
|
18064
19465
|
const fit2 = d.type === "color" || d.type === "bool";
|
|
18065
19466
|
const toggleFirst = d.type === "bool";
|
|
18066
19467
|
const id2 = idOf(d);
|
|
18067
19468
|
const item = document.createElement("div");
|
|
18068
|
-
item.style.cssText = `display:flex;align-items:center;gap:
|
|
19469
|
+
item.style.cssText = `display:flex;align-items:center;gap:8px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
|
|
18069
19470
|
const name = nameOf(d);
|
|
18070
19471
|
if (name) {
|
|
18071
19472
|
const lbl = document.createElement("label");
|
|
18072
19473
|
lbl.htmlFor = id2;
|
|
18073
19474
|
lbl.textContent = name;
|
|
18074
|
-
lbl.style.cssText = `font-size:
|
|
19475
|
+
lbl.style.cssText = `font-size:14px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
|
|
18075
19476
|
item.appendChild(lbl);
|
|
18076
19477
|
}
|
|
18077
19478
|
const cw2 = document.createElement("div");
|
|
18078
|
-
cw2.style.cssText = fit2 ? "" : "width:
|
|
19479
|
+
cw2.style.cssText = fit2 ? "" : "width:100px;";
|
|
18079
19480
|
cw2.appendChild(this.buildControl(row, d, id2));
|
|
18080
19481
|
item.appendChild(cw2);
|
|
18081
|
-
|
|
19482
|
+
cell2.appendChild(item);
|
|
18082
19483
|
}
|
|
18083
19484
|
const lastTip = [...decls].reverse().find((d) => d.tooltip)?.tooltip;
|
|
18084
|
-
if (lastTip)
|
|
18085
|
-
grid.appendChild(
|
|
19485
|
+
if (lastTip) cell2.appendChild(this.infoButton(lastTip));
|
|
19486
|
+
grid.appendChild(cell2);
|
|
18086
19487
|
return;
|
|
18087
19488
|
}
|
|
18088
19489
|
const lead = decls[0];
|
|
18089
19490
|
const id = idOf(lead);
|
|
18090
19491
|
if (lead.type === "bool") {
|
|
18091
|
-
const
|
|
18092
|
-
|
|
19492
|
+
const cell2 = document.createElement("div");
|
|
19493
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;align-items:center;gap:8px;";
|
|
18093
19494
|
const lbl = document.createElement("label");
|
|
18094
|
-
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:
|
|
19495
|
+
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;";
|
|
18095
19496
|
lbl.appendChild(this.buildControl(row, lead, id));
|
|
18096
19497
|
lbl.appendChild(document.createTextNode(nameOf(lead)));
|
|
18097
|
-
|
|
18098
|
-
if (lead.tooltip)
|
|
18099
|
-
grid.appendChild(
|
|
18100
|
-
grid.appendChild(document.createElement("div"));
|
|
19498
|
+
cell2.appendChild(lbl);
|
|
19499
|
+
if (lead.tooltip) cell2.appendChild(this.infoButton(lead.tooltip));
|
|
19500
|
+
grid.appendChild(cell2);
|
|
18101
19501
|
return;
|
|
18102
19502
|
}
|
|
18103
19503
|
if (lead.type === "text_area") {
|
|
18104
|
-
const
|
|
18105
|
-
|
|
19504
|
+
const cell2 = document.createElement("div");
|
|
19505
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
|
|
18106
19506
|
const head = document.createElement("div");
|
|
18107
19507
|
head.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;";
|
|
18108
19508
|
const lbl = document.createElement("label");
|
|
18109
19509
|
lbl.htmlFor = id;
|
|
18110
19510
|
lbl.textContent = nameOf(lead);
|
|
18111
|
-
lbl.style.cssText = "font-size:
|
|
19511
|
+
lbl.style.cssText = "font-size:14px;";
|
|
18112
19512
|
head.appendChild(lbl);
|
|
18113
19513
|
if (lead.tooltip) head.appendChild(this.infoButton(lead.tooltip));
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
grid.appendChild(
|
|
19514
|
+
cell2.appendChild(head);
|
|
19515
|
+
cell2.appendChild(this.buildControl(row, lead, id));
|
|
19516
|
+
grid.appendChild(cell2);
|
|
18117
19517
|
return;
|
|
18118
19518
|
}
|
|
18119
19519
|
const label = document.createElement("label");
|
|
18120
19520
|
label.htmlFor = id;
|
|
18121
19521
|
label.textContent = nameOf(lead);
|
|
18122
|
-
label.style.cssText = "font-size:
|
|
18123
|
-
const controlCell = document.createElement("div");
|
|
18124
|
-
controlCell.style.cssText = "grid-column:span 2;display:flex;align-items:center;gap:8px;";
|
|
19522
|
+
label.style.cssText = "font-size:14px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
18125
19523
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
19524
|
+
const cell = document.createElement("div");
|
|
19525
|
+
cell.style.cssText = "display:flex;align-items:center;gap:8px;justify-self:start;";
|
|
18126
19526
|
const cw = document.createElement("div");
|
|
18127
|
-
cw.style.cssText = fit ? "" : "width:
|
|
19527
|
+
cw.style.cssText = fit ? "" : "width:100px;";
|
|
18128
19528
|
cw.appendChild(this.buildControl(row, lead, id));
|
|
18129
|
-
|
|
18130
|
-
if (lead.tooltip)
|
|
19529
|
+
cell.appendChild(cw);
|
|
19530
|
+
if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
|
|
18131
19531
|
grid.appendChild(label);
|
|
18132
|
-
grid.appendChild(
|
|
18133
|
-
grid.appendChild(document.createElement("div"));
|
|
19532
|
+
grid.appendChild(cell);
|
|
18134
19533
|
}
|
|
18135
19534
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
18136
19535
|
buildControl(row, inp, id) {
|
|
@@ -18139,107 +19538,30 @@ var InputsUI = class {
|
|
|
18139
19538
|
row.values[inp.key] = value;
|
|
18140
19539
|
this.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
18141
19540
|
};
|
|
18142
|
-
const ctrl = this.ctrlStyle();
|
|
18143
19541
|
if (inp.type === "bool") return this.buildToggle(id, Boolean(current), emit);
|
|
18144
|
-
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current),
|
|
18145
|
-
if (inp.type === "source") return this.select(id, SOURCES, String(current),
|
|
19542
|
+
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), emit);
|
|
19543
|
+
if (inp.type === "source") return this.select(id, SOURCES, String(current), emit);
|
|
18146
19544
|
if (inp.type === "color") {
|
|
18147
|
-
const
|
|
18148
|
-
|
|
18149
|
-
ci.id = id;
|
|
18150
|
-
ci.value = toHex6(String(current));
|
|
18151
|
-
ci.style.cssText = `flex:0 0 auto;box-sizing:border-box;width:32px;height:32px;padding:2px;border:1px solid ${this.neutralBorder()};border-radius:0;background:transparent;cursor:pointer;`;
|
|
18152
|
-
ci.addEventListener("input", () => emit(ci.value));
|
|
18153
|
-
return ci;
|
|
19545
|
+
const field = colorField(this.theme, () => String(row.values[inp.key] ?? inp.defval), (v) => emit(v), { shape: "circle", id });
|
|
19546
|
+
return field;
|
|
18154
19547
|
}
|
|
18155
19548
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
18156
|
-
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current),
|
|
19549
|
+
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), emit);
|
|
18157
19550
|
if (inp.type === "session") return this.buildSession(id, String(current), emit);
|
|
18158
19551
|
if (inp.type === "time") return this.buildTime(id, Number(current) || 0, emit);
|
|
18159
19552
|
if (inp.type === "text_area") return this.buildTextArea(id, String(current), emit);
|
|
18160
19553
|
if (inp.type === "int" || inp.type === "float" || inp.type === "price") {
|
|
18161
|
-
|
|
18162
|
-
ni.type = "number";
|
|
18163
|
-
ni.id = id;
|
|
18164
|
-
ni.value = String(current);
|
|
18165
|
-
if (inp.min !== void 0) ni.min = String(inp.min);
|
|
18166
|
-
if (inp.max !== void 0) ni.max = String(inp.max);
|
|
18167
|
-
ni.step = String(inp.step ?? (inp.type === "int" ? 1 : 0.1));
|
|
18168
|
-
ni.style.cssText = ctrl;
|
|
18169
|
-
let last = String(current);
|
|
18170
|
-
const commit = () => {
|
|
18171
|
-
let n = Number(ni.value);
|
|
18172
|
-
if (!Number.isFinite(n)) {
|
|
18173
|
-
ni.value = last;
|
|
18174
|
-
return;
|
|
18175
|
-
}
|
|
18176
|
-
if (inp.min !== void 0) n = Math.max(inp.min, n);
|
|
18177
|
-
if (inp.max !== void 0) n = Math.min(inp.max, n);
|
|
18178
|
-
if (inp.type === "int") n = Math.round(n);
|
|
18179
|
-
ni.value = String(n);
|
|
18180
|
-
if (String(n) !== last) {
|
|
18181
|
-
last = String(n);
|
|
18182
|
-
emit(n);
|
|
18183
|
-
}
|
|
18184
|
-
};
|
|
18185
|
-
ni.addEventListener("blur", commit);
|
|
18186
|
-
ni.addEventListener("keydown", (e) => {
|
|
18187
|
-
if (e.key === "Enter") {
|
|
18188
|
-
e.preventDefault();
|
|
18189
|
-
ni.blur();
|
|
18190
|
-
}
|
|
18191
|
-
});
|
|
18192
|
-
return ni;
|
|
19554
|
+
return this.buildNumber(id, Number(current), inp, emit);
|
|
18193
19555
|
}
|
|
18194
19556
|
return this.buildTextField(id, String(current), emit);
|
|
18195
19557
|
}
|
|
18196
19558
|
/** A plain text field that commits on blur / Enter (shared by string inputs and the pickerless symbol field). */
|
|
18197
19559
|
buildTextField(id, current, emit) {
|
|
18198
|
-
|
|
18199
|
-
ti.type = "text";
|
|
18200
|
-
ti.id = id;
|
|
18201
|
-
ti.value = current;
|
|
18202
|
-
ti.style.cssText = this.ctrlStyle();
|
|
18203
|
-
let last = current;
|
|
18204
|
-
ti.addEventListener("blur", () => {
|
|
18205
|
-
if (ti.value !== last) {
|
|
18206
|
-
last = ti.value;
|
|
18207
|
-
emit(ti.value);
|
|
18208
|
-
}
|
|
18209
|
-
});
|
|
18210
|
-
ti.addEventListener("keydown", (e) => {
|
|
18211
|
-
if (e.key === "Enter") {
|
|
18212
|
-
e.preventDefault();
|
|
18213
|
-
ti.blur();
|
|
18214
|
-
}
|
|
18215
|
-
});
|
|
18216
|
-
return ti;
|
|
19560
|
+
return new TextField({ id, value: current, size: "md", onChange: emit }).el;
|
|
18217
19561
|
}
|
|
18218
19562
|
/** A square check-toggle (filled + check when on) — replaces the raw checkbox. */
|
|
18219
19563
|
buildToggle(id, checked, onChange) {
|
|
18220
|
-
|
|
18221
|
-
const border = this.neutralBorder();
|
|
18222
|
-
const fill = this.strongText();
|
|
18223
|
-
const b = document.createElement("button");
|
|
18224
|
-
b.type = "button";
|
|
18225
|
-
b.id = id;
|
|
18226
|
-
b.setAttribute("role", "switch");
|
|
18227
|
-
b.innerHTML = CHECK_SVG;
|
|
18228
|
-
b.style.cssText = `width:20px;height:20px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;border:1px solid ${border};border-radius:5px;cursor:pointer;padding:0;transition:background .12s ease,border-color .12s ease;`;
|
|
18229
|
-
let on = checked;
|
|
18230
|
-
const paint = (v) => {
|
|
18231
|
-
b.setAttribute("aria-checked", v ? "true" : "false");
|
|
18232
|
-
b.style.background = v ? fill : "transparent";
|
|
18233
|
-
b.style.borderColor = v ? fill : border;
|
|
18234
|
-
b.style.color = v ? t.background : "transparent";
|
|
18235
|
-
};
|
|
18236
|
-
paint(checked);
|
|
18237
|
-
b.addEventListener("click", () => {
|
|
18238
|
-
on = !on;
|
|
18239
|
-
paint(on);
|
|
18240
|
-
onChange(on);
|
|
18241
|
-
});
|
|
18242
|
-
return b;
|
|
19564
|
+
return new Switch({ id, checked, size: "md", tone: "bright", onChange }).el;
|
|
18243
19565
|
}
|
|
18244
19566
|
/** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
|
|
18245
19567
|
infoButton(tooltip2) {
|
|
@@ -18251,45 +19573,22 @@ var InputsUI = class {
|
|
|
18251
19573
|
return b;
|
|
18252
19574
|
}
|
|
18253
19575
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
18254
|
-
select(id, options, current,
|
|
18255
|
-
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current,
|
|
19576
|
+
select(id, options, current, emit) {
|
|
19577
|
+
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, emit);
|
|
18256
19578
|
}
|
|
18257
19579
|
/** A dropdown whose visible labels differ from the committed values (label ≠ value pairs). */
|
|
18258
|
-
selectPairs(id, pairs, current,
|
|
18259
|
-
|
|
18260
|
-
|
|
18261
|
-
|
|
18262
|
-
|
|
18263
|
-
|
|
18264
|
-
|
|
18265
|
-
|
|
18266
|
-
|
|
18267
|
-
|
|
18268
|
-
sel.appendChild(o);
|
|
18269
|
-
}
|
|
18270
|
-
for (const p of pairs) {
|
|
18271
|
-
const o = document.createElement("option");
|
|
18272
|
-
o.value = p.value;
|
|
18273
|
-
o.textContent = p.label;
|
|
18274
|
-
if (p.value === current) o.selected = true;
|
|
18275
|
-
sel.appendChild(o);
|
|
18276
|
-
}
|
|
18277
|
-
this.styleSelect(sel);
|
|
18278
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
18279
|
-
return this.wrapSelectChevron(sel);
|
|
18280
|
-
}
|
|
18281
|
-
/** Wrap a `<select>` with an inset chevron — native arrows can't be repositioned reliably. */
|
|
18282
|
-
wrapSelectChevron(sel) {
|
|
18283
|
-
const wrap = document.createElement("div");
|
|
18284
|
-
wrap.style.cssText = "position:relative;width:100%;";
|
|
18285
|
-
sel.style.paddingRight = "28px";
|
|
18286
|
-
const chevron = document.createElement("span");
|
|
18287
|
-
chevron.innerHTML = SELECT_CHEVRON_SVG;
|
|
18288
|
-
chevron.style.cssText = `position:absolute;right:10px;top:50%;transform:translateY(-50%);pointer-events:none;display:flex;align-items:center;color:${this.strongText()};opacity:0.85;line-height:0;`;
|
|
18289
|
-
wrap.append(sel, chevron);
|
|
18290
|
-
return wrap;
|
|
19580
|
+
selectPairs(id, pairs, current, onChange) {
|
|
19581
|
+
return new Select({
|
|
19582
|
+
id,
|
|
19583
|
+
options: pairs,
|
|
19584
|
+
value: current,
|
|
19585
|
+
size: "md",
|
|
19586
|
+
theme: this.theme,
|
|
19587
|
+
list: this.listPopover(),
|
|
19588
|
+
onChange
|
|
19589
|
+
}).el;
|
|
18291
19590
|
}
|
|
18292
|
-
/** `input.session` → two HH:MM
|
|
19591
|
+
/** `input.session` → two typeable HH:MM comboboxes committing a `HHMM-HHMM` session string. */
|
|
18293
19592
|
buildSession(id, current, emit) {
|
|
18294
19593
|
const [start, end] = sessionToTimes(current);
|
|
18295
19594
|
const wrap = document.createElement("div");
|
|
@@ -18297,15 +19596,16 @@ var InputsUI = class {
|
|
|
18297
19596
|
let from = start;
|
|
18298
19597
|
let to = end;
|
|
18299
19598
|
const commit = () => emit(timesToSession(from, to));
|
|
18300
|
-
|
|
18301
|
-
from
|
|
18302
|
-
|
|
18303
|
-
|
|
18304
|
-
|
|
18305
|
-
to
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
19599
|
+
wrap.append(
|
|
19600
|
+
this.timeCombobox(id, from, 86, (v) => {
|
|
19601
|
+
from = v;
|
|
19602
|
+
commit();
|
|
19603
|
+
}),
|
|
19604
|
+
this.timeCombobox(`${id}-end`, to, 86, (v) => {
|
|
19605
|
+
to = v;
|
|
19606
|
+
commit();
|
|
19607
|
+
})
|
|
19608
|
+
);
|
|
18309
19609
|
return wrap;
|
|
18310
19610
|
}
|
|
18311
19611
|
/** `input.time` → a date picker + an HH:MM dropdown, committing an epoch-ms timestamp. */
|
|
@@ -18320,20 +19620,15 @@ var InputsUI = class {
|
|
|
18320
19620
|
const ms = Date.parse(`${date}T${time}:00`);
|
|
18321
19621
|
if (Number.isFinite(ms)) emit(ms);
|
|
18322
19622
|
};
|
|
18323
|
-
const
|
|
18324
|
-
|
|
18325
|
-
dateInput.id = id;
|
|
18326
|
-
dateInput.value = date;
|
|
18327
|
-
dateInput.style.cssText = `${this.ctrlStyle()}width:auto;cursor:pointer;`;
|
|
18328
|
-
dateInput.addEventListener("change", () => {
|
|
18329
|
-
date = dateInput.value;
|
|
19623
|
+
const dateField = this.dateField(id, date, (v) => {
|
|
19624
|
+
date = v;
|
|
18330
19625
|
commit();
|
|
18331
19626
|
});
|
|
18332
|
-
const timeSel = this.
|
|
19627
|
+
const timeSel = this.timeCombobox(`${id}-time`, time, 86, (v) => {
|
|
18333
19628
|
time = v;
|
|
18334
19629
|
commit();
|
|
18335
19630
|
});
|
|
18336
|
-
wrap.append(
|
|
19631
|
+
wrap.append(dateField, timeSel);
|
|
18337
19632
|
return wrap;
|
|
18338
19633
|
}
|
|
18339
19634
|
/** `input.text_area` → a multi-line textarea, committed on blur. */
|
|
@@ -18352,9 +19647,261 @@ var InputsUI = class {
|
|
|
18352
19647
|
});
|
|
18353
19648
|
return ta;
|
|
18354
19649
|
}
|
|
18355
|
-
/** A fixed-width HH:MM
|
|
18356
|
-
|
|
18357
|
-
|
|
19650
|
+
/** A fixed-width HH:MM combobox (typeable, plus a 30-minute-step dropdown) used by session + time. */
|
|
19651
|
+
timeCombobox(id, current, width, onChange) {
|
|
19652
|
+
const wrap = document.createElement("div");
|
|
19653
|
+
wrap.className = "vela-ind-combo";
|
|
19654
|
+
wrap.style.cssText = `position:relative;width:${width}px;`;
|
|
19655
|
+
const input = document.createElement("input");
|
|
19656
|
+
input.type = "text";
|
|
19657
|
+
input.id = id;
|
|
19658
|
+
input.value = current;
|
|
19659
|
+
input.autocomplete = "off";
|
|
19660
|
+
input.spellcheck = false;
|
|
19661
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
19662
|
+
const chevron = document.createElement("button");
|
|
19663
|
+
chevron.type = "button";
|
|
19664
|
+
chevron.tabIndex = -1;
|
|
19665
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
19666
|
+
chevron.setAttribute("aria-label", "Open time list");
|
|
19667
|
+
chevron.innerHTML = CLOCK_SVG;
|
|
19668
|
+
let value = current;
|
|
19669
|
+
const commitTyped = () => {
|
|
19670
|
+
const next = normalizeTimeInput(input.value);
|
|
19671
|
+
if (!next) {
|
|
19672
|
+
input.value = value;
|
|
19673
|
+
return;
|
|
19674
|
+
}
|
|
19675
|
+
input.value = next;
|
|
19676
|
+
if (next !== value) {
|
|
19677
|
+
value = next;
|
|
19678
|
+
onChange(next);
|
|
19679
|
+
}
|
|
19680
|
+
};
|
|
19681
|
+
input.addEventListener("blur", () => {
|
|
19682
|
+
if (isPopoverOpen()) return;
|
|
19683
|
+
commitTyped();
|
|
19684
|
+
});
|
|
19685
|
+
const pick = (v) => {
|
|
19686
|
+
value = v;
|
|
19687
|
+
input.value = v;
|
|
19688
|
+
onChange(v);
|
|
19689
|
+
};
|
|
19690
|
+
const openList = () => {
|
|
19691
|
+
this.choicePop = toggleSelectList(wrap, TIME_OPTIONS, value, pick, {
|
|
19692
|
+
...this.listPopover(),
|
|
19693
|
+
onClose: () => {
|
|
19694
|
+
this.choicePop = null;
|
|
19695
|
+
}
|
|
19696
|
+
});
|
|
19697
|
+
};
|
|
19698
|
+
input.addEventListener("keydown", (e) => {
|
|
19699
|
+
if (e.key === "Enter") {
|
|
19700
|
+
e.preventDefault();
|
|
19701
|
+
commitTyped();
|
|
19702
|
+
input.blur();
|
|
19703
|
+
}
|
|
19704
|
+
if (e.key === "ArrowDown") {
|
|
19705
|
+
e.preventDefault();
|
|
19706
|
+
openList();
|
|
19707
|
+
}
|
|
19708
|
+
});
|
|
19709
|
+
const open2 = (e) => {
|
|
19710
|
+
e.preventDefault();
|
|
19711
|
+
e.stopPropagation();
|
|
19712
|
+
openList();
|
|
19713
|
+
};
|
|
19714
|
+
input.addEventListener("click", open2);
|
|
19715
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
19716
|
+
chevron.addEventListener("click", open2);
|
|
19717
|
+
wrap.append(input, chevron);
|
|
19718
|
+
return wrap;
|
|
19719
|
+
}
|
|
19720
|
+
/** A typeable YYYY-MM-DD field that also opens a themed month calendar. */
|
|
19721
|
+
dateField(id, current, onChange) {
|
|
19722
|
+
const wrap = document.createElement("div");
|
|
19723
|
+
wrap.className = "vela-ind-combo";
|
|
19724
|
+
wrap.style.cssText = "position:relative;width:128px;";
|
|
19725
|
+
const input = document.createElement("input");
|
|
19726
|
+
input.type = "text";
|
|
19727
|
+
input.id = id;
|
|
19728
|
+
input.value = current;
|
|
19729
|
+
input.autocomplete = "off";
|
|
19730
|
+
input.spellcheck = false;
|
|
19731
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
19732
|
+
const chevron = document.createElement("button");
|
|
19733
|
+
chevron.type = "button";
|
|
19734
|
+
chevron.tabIndex = -1;
|
|
19735
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
19736
|
+
chevron.setAttribute("aria-label", "Open calendar");
|
|
19737
|
+
chevron.innerHTML = CALENDAR_SVG;
|
|
19738
|
+
let value = current;
|
|
19739
|
+
const commitTyped = () => {
|
|
19740
|
+
const next = normalizeDateInput(input.value);
|
|
19741
|
+
if (!next) {
|
|
19742
|
+
input.value = value;
|
|
19743
|
+
return;
|
|
19744
|
+
}
|
|
19745
|
+
input.value = next;
|
|
19746
|
+
if (next !== value) {
|
|
19747
|
+
value = next;
|
|
19748
|
+
onChange(next);
|
|
19749
|
+
}
|
|
19750
|
+
};
|
|
19751
|
+
input.addEventListener("blur", () => {
|
|
19752
|
+
if (isPopoverOpen()) return;
|
|
19753
|
+
commitTyped();
|
|
19754
|
+
});
|
|
19755
|
+
const pick = (v) => {
|
|
19756
|
+
value = v;
|
|
19757
|
+
input.value = v;
|
|
19758
|
+
onChange(v);
|
|
19759
|
+
};
|
|
19760
|
+
input.addEventListener("keydown", (e) => {
|
|
19761
|
+
if (e.key === "Enter") {
|
|
19762
|
+
e.preventDefault();
|
|
19763
|
+
commitTyped();
|
|
19764
|
+
input.blur();
|
|
19765
|
+
}
|
|
19766
|
+
if (e.key === "ArrowDown") {
|
|
19767
|
+
e.preventDefault();
|
|
19768
|
+
this.openCalendar(wrap, value, pick);
|
|
19769
|
+
}
|
|
19770
|
+
});
|
|
19771
|
+
const open2 = (e) => {
|
|
19772
|
+
e.preventDefault();
|
|
19773
|
+
e.stopPropagation();
|
|
19774
|
+
if (openPopoverTrigger() === wrap) {
|
|
19775
|
+
closeOpenPopovers();
|
|
19776
|
+
return;
|
|
19777
|
+
}
|
|
19778
|
+
this.openCalendar(wrap, value, pick);
|
|
19779
|
+
};
|
|
19780
|
+
input.addEventListener("click", open2);
|
|
19781
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
19782
|
+
chevron.addEventListener("click", open2);
|
|
19783
|
+
wrap.append(input, chevron);
|
|
19784
|
+
return wrap;
|
|
19785
|
+
}
|
|
19786
|
+
/** Numeric field with hover steppers on the right (no fill until a triangle is hovered). */
|
|
19787
|
+
buildNumber(id, current, inp, emit) {
|
|
19788
|
+
return new NumberInput({
|
|
19789
|
+
id,
|
|
19790
|
+
value: current,
|
|
19791
|
+
min: inp.min,
|
|
19792
|
+
max: inp.max,
|
|
19793
|
+
step: inp.step ?? (inp.type === "int" ? 1 : 0.1),
|
|
19794
|
+
integer: inp.type === "int",
|
|
19795
|
+
size: "md",
|
|
19796
|
+
commit: "blur",
|
|
19797
|
+
onChange: emit
|
|
19798
|
+
}).el;
|
|
19799
|
+
}
|
|
19800
|
+
/** Placement shared by the indicator dialog's dropdowns and the time combobox. */
|
|
19801
|
+
listPopover() {
|
|
19802
|
+
return {
|
|
19803
|
+
theme: this.theme,
|
|
19804
|
+
matchWidth: true,
|
|
19805
|
+
boundary: this.dialog ?? "viewport",
|
|
19806
|
+
boundaryInset: 8,
|
|
19807
|
+
gap: 4
|
|
19808
|
+
};
|
|
19809
|
+
}
|
|
19810
|
+
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
19811
|
+
openCalendar(anchor, current, onPick) {
|
|
19812
|
+
const parsed = parseIsoDate(current);
|
|
19813
|
+
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
19814
|
+
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
19815
|
+
const selected = parsed ? isoDate(parsed) : current;
|
|
19816
|
+
const pop = new Popover({
|
|
19817
|
+
trigger: anchor,
|
|
19818
|
+
theme: this.theme,
|
|
19819
|
+
className: "vela-ind-cal",
|
|
19820
|
+
boundary: this.dialog ?? "viewport",
|
|
19821
|
+
boundaryInset: 8,
|
|
19822
|
+
gap: 4,
|
|
19823
|
+
onClose: () => {
|
|
19824
|
+
this.calendarPop = null;
|
|
19825
|
+
this.calendarAnchor = null;
|
|
19826
|
+
},
|
|
19827
|
+
content: (el) => {
|
|
19828
|
+
const title = document.createElement("div");
|
|
19829
|
+
title.className = "vela-ind-cal-title";
|
|
19830
|
+
const prev2 = document.createElement("button");
|
|
19831
|
+
prev2.type = "button";
|
|
19832
|
+
prev2.className = "vela-ind-cal-nav";
|
|
19833
|
+
prev2.setAttribute("aria-label", "Previous month");
|
|
19834
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
19835
|
+
const next = document.createElement("button");
|
|
19836
|
+
next.type = "button";
|
|
19837
|
+
next.className = "vela-ind-cal-nav";
|
|
19838
|
+
next.setAttribute("aria-label", "Next month");
|
|
19839
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
19840
|
+
const head = document.createElement("div");
|
|
19841
|
+
head.className = "vela-ind-cal-head";
|
|
19842
|
+
head.append(prev2, title, next);
|
|
19843
|
+
const week = document.createElement("div");
|
|
19844
|
+
week.className = "vela-ind-cal-week";
|
|
19845
|
+
for (const d of WEEKDAY_LABELS) {
|
|
19846
|
+
const cell = document.createElement("span");
|
|
19847
|
+
cell.textContent = d;
|
|
19848
|
+
week.appendChild(cell);
|
|
19849
|
+
}
|
|
19850
|
+
const grid = document.createElement("div");
|
|
19851
|
+
grid.className = "vela-ind-cal-grid";
|
|
19852
|
+
const paint = () => {
|
|
19853
|
+
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
19854
|
+
grid.replaceChildren();
|
|
19855
|
+
const first = new Date(year, month, 1);
|
|
19856
|
+
const startPad = first.getDay();
|
|
19857
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
19858
|
+
const today = isoDate(/* @__PURE__ */ new Date());
|
|
19859
|
+
for (let i = 0; i < startPad; i++) {
|
|
19860
|
+
const blank = document.createElement("span");
|
|
19861
|
+
blank.className = "vela-ind-cal-blank";
|
|
19862
|
+
grid.appendChild(blank);
|
|
19863
|
+
}
|
|
19864
|
+
for (let day = 1; day <= days; day++) {
|
|
19865
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
19866
|
+
const b = document.createElement("button");
|
|
19867
|
+
b.type = "button";
|
|
19868
|
+
b.className = "vela-ind-cal-day";
|
|
19869
|
+
b.textContent = String(day);
|
|
19870
|
+
if (iso === selected) b.dataset.checked = "1";
|
|
19871
|
+
if (iso === today) b.dataset.today = "1";
|
|
19872
|
+
b.addEventListener("click", (e) => {
|
|
19873
|
+
e.stopPropagation();
|
|
19874
|
+
pop.hide();
|
|
19875
|
+
onPick(iso);
|
|
19876
|
+
});
|
|
19877
|
+
grid.appendChild(b);
|
|
19878
|
+
}
|
|
19879
|
+
};
|
|
19880
|
+
prev2.addEventListener("click", (e) => {
|
|
19881
|
+
e.stopPropagation();
|
|
19882
|
+
month -= 1;
|
|
19883
|
+
if (month < 0) {
|
|
19884
|
+
month = 11;
|
|
19885
|
+
year -= 1;
|
|
19886
|
+
}
|
|
19887
|
+
paint();
|
|
19888
|
+
});
|
|
19889
|
+
next.addEventListener("click", (e) => {
|
|
19890
|
+
e.stopPropagation();
|
|
19891
|
+
month += 1;
|
|
19892
|
+
if (month > 11) {
|
|
19893
|
+
month = 0;
|
|
19894
|
+
year += 1;
|
|
19895
|
+
}
|
|
19896
|
+
paint();
|
|
19897
|
+
});
|
|
19898
|
+
paint();
|
|
19899
|
+
el.append(head, week, grid);
|
|
19900
|
+
}
|
|
19901
|
+
});
|
|
19902
|
+
this.calendarPop = pop;
|
|
19903
|
+
this.calendarAnchor = anchor;
|
|
19904
|
+
pop.show();
|
|
18358
19905
|
}
|
|
18359
19906
|
/**
|
|
18360
19907
|
* `input.symbol` → a field that opens the host's ticker-selection UI when a picker is wired
|
|
@@ -18382,7 +19929,7 @@ var InputsUI = class {
|
|
|
18382
19929
|
}
|
|
18383
19930
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
18384
19931
|
ctrlStyle() {
|
|
18385
|
-
return `width:100%;box-sizing:border-box;height:
|
|
19932
|
+
return `width:100%;box-sizing:border-box;height:34px;background:transparent;border:1px solid var(--vela-border-strong);color:${this.strongText()};border-radius:6px;padding:0 8px;font-size:14px;font-family:inherit;outline:none;`;
|
|
18386
19933
|
}
|
|
18387
19934
|
/** Whether the active theme is dark (drives the dialog's `color-scheme`). */
|
|
18388
19935
|
isDarkTheme() {
|
|
@@ -18401,21 +19948,6 @@ var InputsUI = class {
|
|
|
18401
19948
|
strongText() {
|
|
18402
19949
|
return "var(--vela-fg-bright)";
|
|
18403
19950
|
}
|
|
18404
|
-
/**
|
|
18405
|
-
* Paint a `<select>` + its `<option>`s so the native dropdown popup matches the dialog: the
|
|
18406
|
-
* option list's background is the dialog color and its text is the strong foreground. `color-scheme`
|
|
18407
|
-
* alone doesn't theme the popup in every embedded browser (some render it white with white text —
|
|
18408
|
-
* invisible), so the colors are set explicitly here.
|
|
18409
|
-
*/
|
|
18410
|
-
styleSelect(sel) {
|
|
18411
|
-
const bg = this.theme.background;
|
|
18412
|
-
const fg = this.strongText();
|
|
18413
|
-
sel.style.backgroundColor = bg;
|
|
18414
|
-
for (const o of Array.from(sel.options)) {
|
|
18415
|
-
o.style.backgroundColor = bg;
|
|
18416
|
-
o.style.color = fg;
|
|
18417
|
-
}
|
|
18418
|
-
}
|
|
18419
19951
|
dialogButton(label, primary, onClick) {
|
|
18420
19952
|
const b = document.createElement("button");
|
|
18421
19953
|
b.type = "button";
|
|
@@ -18429,6 +19961,21 @@ function nameOf(inp) {
|
|
|
18429
19961
|
const t = inp.title;
|
|
18430
19962
|
return t.length > 0 ? t.charAt(0).toUpperCase() + t.slice(1) : "";
|
|
18431
19963
|
}
|
|
19964
|
+
var DEFAULT_INPUT_TAB = "Inputs";
|
|
19965
|
+
function tabInputs(inputs) {
|
|
19966
|
+
const byTab = /* @__PURE__ */ new Map();
|
|
19967
|
+
for (const inp of inputs) {
|
|
19968
|
+
const name = inp.tab && inp.tab.length > 0 ? inp.tab : DEFAULT_INPUT_TAB;
|
|
19969
|
+
if (!byTab.has(name)) byTab.set(name, []);
|
|
19970
|
+
byTab.get(name).push(inp);
|
|
19971
|
+
}
|
|
19972
|
+
const names = [...byTab.keys()];
|
|
19973
|
+
if (names.includes(DEFAULT_INPUT_TAB)) {
|
|
19974
|
+
names.splice(names.indexOf(DEFAULT_INPUT_TAB), 1);
|
|
19975
|
+
names.unshift(DEFAULT_INPUT_TAB);
|
|
19976
|
+
}
|
|
19977
|
+
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
19978
|
+
}
|
|
18432
19979
|
function groupInputs(inputs) {
|
|
18433
19980
|
const order = [];
|
|
18434
19981
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -18457,9 +20004,10 @@ function groupInputs(inputs) {
|
|
|
18457
20004
|
});
|
|
18458
20005
|
}
|
|
18459
20006
|
var CHECK_SVG = iconAt("check", 12);
|
|
18460
|
-
var
|
|
20007
|
+
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
20008
|
+
var CLOCK_SVG = iconAt("clock", 14);
|
|
18461
20009
|
var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
|
|
18462
|
-
var DIALOG_STYLE_REV = "
|
|
20010
|
+
var DIALOG_STYLE_REV = "25";
|
|
18463
20011
|
function ensureDialogStyles() {
|
|
18464
20012
|
if (typeof document === "undefined") return;
|
|
18465
20013
|
const existing = document.getElementById(DIALOG_STYLE_ID2);
|
|
@@ -18468,30 +20016,47 @@ function ensureDialogStyles() {
|
|
|
18468
20016
|
s.id = DIALOG_STYLE_ID2;
|
|
18469
20017
|
s.dataset.rev = DIALOG_STYLE_REV;
|
|
18470
20018
|
s.textContent = `
|
|
18471
|
-
.vela-ind-dialog input[type=
|
|
18472
|
-
.vela-ind-dialog input[type=
|
|
18473
|
-
.vela-ind-dialog input[type=
|
|
18474
|
-
.vela-ind-
|
|
18475
|
-
.vela-ind-
|
|
18476
|
-
.vela-ind-
|
|
18477
|
-
.vela-ind-
|
|
18478
|
-
.vela-ind-
|
|
20019
|
+
.vela-ind-dialog input[type=text],.vela-ind-dialog input[type=number],.vela-ind-dialog input[type=date]{transition:border-color .12s ease,box-shadow .12s ease;}
|
|
20020
|
+
.vela-ind-dialog input[type=text]:hover,.vela-ind-dialog input[type=number]:hover,.vela-ind-dialog input[type=date]:hover{border-color:var(--vela-fg-muted);}
|
|
20021
|
+
.vela-ind-dialog input[type=text]:focus,.vela-ind-dialog input[type=number]:focus,.vela-ind-dialog input[type=date]:focus{border-color:var(--vela-focus);box-shadow:0 0 0 3px var(--vela-focus-soft);}
|
|
20022
|
+
.vela-ind-combo-chevron{position:absolute;right:0;top:0;bottom:0;width:26px;border:none;background:transparent;color:inherit;opacity:0.55;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;}
|
|
20023
|
+
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
20024
|
+
.vela-ind-cal{background:var(--vela-bg);color:var(--vela-fg);border:none;border-radius:6px;box-shadow:var(--vela-shadow);font:14px var(--vela-font);padding:10px 12px;user-select:none;}
|
|
20025
|
+
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
20026
|
+
.vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
|
|
20027
|
+
.vela-ind-cal-nav{width:24px;height:24px;border:none;background:transparent;color:var(--vela-fg-muted);border-radius:4px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
|
|
20028
|
+
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
20029
|
+
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
20030
|
+
.vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
|
|
20031
|
+
.vela-ind-cal-week span{line-height:20px;}
|
|
20032
|
+
.vela-ind-cal-blank{width:28px;height:28px;}
|
|
20033
|
+
.vela-ind-cal-day{width:28px;height:28px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0;cursor:pointer;font:inherit;font-size:14px;}
|
|
20034
|
+
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
20035
|
+
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
20036
|
+
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
20037
|
+
.vela-ind-dialog ::-webkit-scrollbar{width:9px;height:9px;}
|
|
18479
20038
|
.vela-ind-dialog ::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}
|
|
18480
20039
|
.vela-ind-dialog ::-webkit-scrollbar-track{background:transparent;}
|
|
20040
|
+
.vela-ind-dialog ::-webkit-scrollbar-button{display:none;width:0;height:0;}
|
|
20041
|
+
.vela-ind-tab{transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
20042
|
+
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
18481
20043
|
.vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
18482
20044
|
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
18483
20045
|
.vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
|
|
18484
20046
|
.vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
|
|
18485
20047
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
18486
20048
|
.vela-ind-close:hover{color:var(--vela-danger) !important;background-color:var(--vela-hover-strong);}
|
|
20049
|
+
.vela-ind-dlg-close{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--vela-fg-muted);line-height:0;width:30px;height:30px;flex:0 0 auto;border-radius:var(--vela-radius-sm);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
20050
|
+
.vela-ind-dlg-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
20051
|
+
.vela-ind-dlg-close svg{width:15px;height:15px;display:block;flex:none;}
|
|
18487
20052
|
.vela-ind-fold{transition:border-color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
|
|
18488
20053
|
.vela-ind-fold:hover{border-color:var(--vela-border-strong);opacity:0.9;}
|
|
18489
20054
|
.vela-ind-menuitem{background:transparent;transition:background var(--vela-dur-fast) ease;}
|
|
18490
20055
|
.vela-ind-menuitem:hover{background:var(--vela-hover-strong);}
|
|
18491
|
-
.vela-ind-btn{cursor:pointer;padding:
|
|
18492
|
-
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
18493
|
-
.vela-ind-btn-primary{
|
|
18494
|
-
.vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;}`;
|
|
20056
|
+
.vela-ind-btn{cursor:pointer;height:34px;padding:0 11px;border-radius:6px;border:1px solid var(--vela-border-strong);background:transparent;color:var(--vela-fg);font-weight:400;font-size:14px;font-family:inherit;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
20057
|
+
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);border-color:var(--vela-fg-muted);}
|
|
20058
|
+
.vela-ind-btn-primary{border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
20059
|
+
.vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;border-color:var(--vela-selected-bg);}`;
|
|
18495
20060
|
if (!existing) document.head.appendChild(s);
|
|
18496
20061
|
}
|
|
18497
20062
|
var TIMEFRAME_OPTIONS = [
|
|
@@ -18516,6 +20081,37 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
18516
20081
|
const v = `${hh}:${mm}`;
|
|
18517
20082
|
return { value: v, label: v };
|
|
18518
20083
|
});
|
|
20084
|
+
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
20085
|
+
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
20086
|
+
function normalizeDateInput(raw) {
|
|
20087
|
+
const t = raw.trim();
|
|
20088
|
+
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
|
|
20089
|
+
if (!m) return null;
|
|
20090
|
+
const y = Number(m[1]);
|
|
20091
|
+
const mo = Number(m[2]);
|
|
20092
|
+
const d = Number(m[3]);
|
|
20093
|
+
const dt = new Date(y, mo - 1, d);
|
|
20094
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
|
|
20095
|
+
return isoDate(dt);
|
|
20096
|
+
}
|
|
20097
|
+
function parseIsoDate(raw) {
|
|
20098
|
+
const iso = normalizeDateInput(raw);
|
|
20099
|
+
if (!iso) return null;
|
|
20100
|
+
const [y, mo, d] = iso.split("-").map(Number);
|
|
20101
|
+
return new Date(y, mo - 1, d);
|
|
20102
|
+
}
|
|
20103
|
+
function isoDate(d) {
|
|
20104
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
20105
|
+
}
|
|
20106
|
+
function normalizeTimeInput(raw) {
|
|
20107
|
+
const t = raw.trim();
|
|
20108
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
20109
|
+
if (!m) return null;
|
|
20110
|
+
const hh = Number(m[1]);
|
|
20111
|
+
const mm = Number(m[2]);
|
|
20112
|
+
if (hh > 23 || mm > 59) return null;
|
|
20113
|
+
return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
|
|
20114
|
+
}
|
|
18519
20115
|
function sessionToTimes(session) {
|
|
18520
20116
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(session.trim());
|
|
18521
20117
|
if (!m) return ["09:00", "16:00"];
|
|
@@ -21060,7 +22656,7 @@ var TOUCH_TAP_SLOP = 14;
|
|
|
21060
22656
|
var DOUBLE_TAP_MS = 350;
|
|
21061
22657
|
var DOUBLE_TAP_SLOP = 30;
|
|
21062
22658
|
var TIME_SCALE_K = 4e-3;
|
|
21063
|
-
var WHEEL_ZOOM_K =
|
|
22659
|
+
var WHEEL_ZOOM_K = 4e-3;
|
|
21064
22660
|
function wheelZoomAnchor(coords, cursorX, rightEdge) {
|
|
21065
22661
|
if (rightEdge) return { logical: coords.rightEdgeLogical, x: coords.width };
|
|
21066
22662
|
return { logical: coords.xToLogical(cursorX), x: cursorX };
|
|
@@ -21114,6 +22710,8 @@ var InputController = class {
|
|
|
21114
22710
|
// smoothed pointer velocity, px/ms
|
|
21115
22711
|
this.middleDeleted = false;
|
|
21116
22712
|
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
22713
|
+
this.rightCancelled = false;
|
|
22714
|
+
// the last right press cancelled a placement (suppress the context menu)
|
|
21117
22715
|
// Last cursor position over the element (NaN once it leaves) — lets a modifier
|
|
21118
22716
|
// press/release re-shape the drawings preview with the pointer stationary.
|
|
21119
22717
|
this.cursorX = NaN;
|
|
@@ -21138,6 +22736,14 @@ var InputController = class {
|
|
|
21138
22736
|
this.onMiddleGuard = (e) => {
|
|
21139
22737
|
if (e.button === 1 && this.middleDeleted) e.preventDefault();
|
|
21140
22738
|
};
|
|
22739
|
+
/** A right press that cancelled a placement must not ALSO open the host's chart
|
|
22740
|
+
* context menu — swallow its companion event before it bubbles to the host. */
|
|
22741
|
+
this.onContextGuard = (e) => {
|
|
22742
|
+
if (!this.rightCancelled) return;
|
|
22743
|
+
this.rightCancelled = false;
|
|
22744
|
+
e.preventDefault();
|
|
22745
|
+
e.stopPropagation();
|
|
22746
|
+
};
|
|
21141
22747
|
/** A modifier press/release with the pointer stationary: re-issue the last cursor
|
|
21142
22748
|
* position so the placing ghost / drag preview reflects the new state immediately
|
|
21143
22749
|
* (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
|
|
@@ -21154,6 +22760,11 @@ var InputController = class {
|
|
|
21154
22760
|
if (this.middleDeleted) e.preventDefault();
|
|
21155
22761
|
return;
|
|
21156
22762
|
}
|
|
22763
|
+
if (e.button === 2) {
|
|
22764
|
+
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
22765
|
+
if (this.rightCancelled) e.preventDefault();
|
|
22766
|
+
return;
|
|
22767
|
+
}
|
|
21157
22768
|
if (e.button !== 0) return;
|
|
21158
22769
|
const { x, y } = this.local(e);
|
|
21159
22770
|
if (e.pointerType === "touch") {
|
|
@@ -21358,6 +22969,7 @@ var InputController = class {
|
|
|
21358
22969
|
el.addEventListener("wheel", this.onWheel, { passive: false });
|
|
21359
22970
|
el.addEventListener("mousedown", this.onMiddleGuard);
|
|
21360
22971
|
el.addEventListener("auxclick", this.onMiddleGuard);
|
|
22972
|
+
el.addEventListener("contextmenu", this.onContextGuard);
|
|
21361
22973
|
const win = el.ownerDocument?.defaultView;
|
|
21362
22974
|
win?.addEventListener("keydown", this.onModifier);
|
|
21363
22975
|
win?.addEventListener("keyup", this.onModifier);
|
|
@@ -21379,6 +22991,7 @@ var InputController = class {
|
|
|
21379
22991
|
el.removeEventListener("wheel", this.onWheel);
|
|
21380
22992
|
el.removeEventListener("mousedown", this.onMiddleGuard);
|
|
21381
22993
|
el.removeEventListener("auxclick", this.onMiddleGuard);
|
|
22994
|
+
el.removeEventListener("contextmenu", this.onContextGuard);
|
|
21382
22995
|
this.cancelLongPress();
|
|
21383
22996
|
this.touches.clear();
|
|
21384
22997
|
this.el = null;
|
|
@@ -22330,6 +23943,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
|
|
|
22330
23943
|
const rx = right ? width + 2 : Math.max(x1, x2);
|
|
22331
23944
|
return [lx, yAt(lx), rx, yAt(rx)];
|
|
22332
23945
|
}
|
|
23946
|
+
function uprightLineAngle(x1, y1, x2, y2) {
|
|
23947
|
+
let a = Math.atan2(y2 - y1, x2 - x1);
|
|
23948
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
23949
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
23950
|
+
return a;
|
|
23951
|
+
}
|
|
22333
23952
|
function parseColor2(c) {
|
|
22334
23953
|
const s = c.trim();
|
|
22335
23954
|
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
@@ -23051,8 +24670,11 @@ var ChromeRenderer = class {
|
|
|
23051
24670
|
}
|
|
23052
24671
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
23053
24672
|
ctx.textBaseline = "middle";
|
|
23054
|
-
if (coords.barCount === 0) return;
|
|
23055
24673
|
const panes = scene.orderedPanes();
|
|
24674
|
+
if (coords.barCount === 0) {
|
|
24675
|
+
this.drawPaneSeparators(ctx, scene, theme, fullW, panes);
|
|
24676
|
+
return;
|
|
24677
|
+
}
|
|
23056
24678
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
23057
24679
|
for (const pane of panes) {
|
|
23058
24680
|
if (pane.collapsed) continue;
|
|
@@ -23500,367 +25122,71 @@ function formatStamp(ms, timeZone) {
|
|
|
23500
25122
|
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
23501
25123
|
}
|
|
23502
25124
|
|
|
23503
|
-
// src/renderers/native/drawings/colorPicker.ts
|
|
23504
|
-
function splitColor(color) {
|
|
23505
|
-
const s = String(color ?? "").trim();
|
|
23506
|
-
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
23507
|
-
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
23508
|
-
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
23509
|
-
if (m) {
|
|
23510
|
-
const h = m[1].toLowerCase();
|
|
23511
|
-
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
23512
|
-
}
|
|
23513
|
-
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
23514
|
-
if (rgba) {
|
|
23515
|
-
const p = rgba[1].split(",").map((v) => v.trim());
|
|
23516
|
-
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
23517
|
-
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
23518
|
-
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
23519
|
-
}
|
|
23520
|
-
return { hex6: DEFAULT_DRAWING_COLOR, alpha: 1 };
|
|
23521
|
-
}
|
|
23522
|
-
function combineColor(hex6, alpha) {
|
|
23523
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
23524
|
-
if (a >= 0.999) return hex6;
|
|
23525
|
-
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
23526
|
-
}
|
|
23527
|
-
function blendOver(fg, bg, alpha) {
|
|
23528
|
-
const rgb = (c) => {
|
|
23529
|
-
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
23530
|
-
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
23531
|
-
};
|
|
23532
|
-
const [fr, fgc, fb] = rgb(fg);
|
|
23533
|
-
const [br, bgc, bb] = rgb(bg);
|
|
23534
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
23535
|
-
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
23536
|
-
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
23537
|
-
}
|
|
23538
|
-
function hslHex(h, s, l) {
|
|
23539
|
-
const sn = s / 100;
|
|
23540
|
-
const ln = l / 100;
|
|
23541
|
-
const k = (n) => (n + h / 30) % 12;
|
|
23542
|
-
const a = sn * Math.min(ln, 1 - ln);
|
|
23543
|
-
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
23544
|
-
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
23545
|
-
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
23546
|
-
}
|
|
23547
|
-
function buildPalette() {
|
|
23548
|
-
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
23549
|
-
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
23550
|
-
const levels = [
|
|
23551
|
-
[82, 56],
|
|
23552
|
-
// vivid
|
|
23553
|
-
[58, 84],
|
|
23554
|
-
// light pastel
|
|
23555
|
-
[62, 74],
|
|
23556
|
-
[66, 62],
|
|
23557
|
-
[70, 50],
|
|
23558
|
-
[64, 39],
|
|
23559
|
-
[54, 29]
|
|
23560
|
-
// dark
|
|
23561
|
-
];
|
|
23562
|
-
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
23563
|
-
}
|
|
23564
|
-
var PALETTE = buildPalette();
|
|
23565
|
-
function transparencyChecker(size) {
|
|
23566
|
-
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
23567
|
-
}
|
|
23568
|
-
var CHECKER = transparencyChecker(10);
|
|
23569
|
-
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
23570
|
-
function addRecent(hex6) {
|
|
23571
|
-
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
23572
|
-
if (i >= 0) recents.splice(i, 1);
|
|
23573
|
-
recents.unshift(hex6);
|
|
23574
|
-
if (recents.length > 10) recents.length = 10;
|
|
23575
|
-
}
|
|
23576
|
-
function buildColorPicker(color, theme, onChange) {
|
|
23577
|
-
const parsed = splitColor(color);
|
|
23578
|
-
let curHex = parsed.hex6;
|
|
23579
|
-
let curAlpha = parsed.alpha;
|
|
23580
|
-
const root = document.createElement("div");
|
|
23581
|
-
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
23582
|
-
const grid = document.createElement("div");
|
|
23583
|
-
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
23584
|
-
const swatches = [];
|
|
23585
|
-
for (const row of PALETTE) {
|
|
23586
|
-
for (const c of row) {
|
|
23587
|
-
const sw = document.createElement("button");
|
|
23588
|
-
sw.type = "button";
|
|
23589
|
-
sw.dataset.c = c;
|
|
23590
|
-
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
23591
|
-
sw.addEventListener("click", (e) => {
|
|
23592
|
-
e.stopPropagation();
|
|
23593
|
-
pickHex(c);
|
|
23594
|
-
});
|
|
23595
|
-
grid.appendChild(sw);
|
|
23596
|
-
swatches.push(sw);
|
|
23597
|
-
}
|
|
23598
|
-
}
|
|
23599
|
-
const paintSelection = () => {
|
|
23600
|
-
for (const sw of swatches) {
|
|
23601
|
-
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
23602
|
-
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
23603
|
-
sw.style.outlineOffset = "2px";
|
|
23604
|
-
sw.style.zIndex = on ? "1" : "";
|
|
23605
|
-
sw.style.position = on ? "relative" : "";
|
|
23606
|
-
}
|
|
23607
|
-
};
|
|
23608
|
-
const recentRow = document.createElement("div");
|
|
23609
|
-
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
23610
|
-
const renderRecents = () => {
|
|
23611
|
-
recentRow.replaceChildren();
|
|
23612
|
-
for (const c of recents) {
|
|
23613
|
-
const sw = document.createElement("button");
|
|
23614
|
-
sw.type = "button";
|
|
23615
|
-
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;`;
|
|
23616
|
-
sw.addEventListener("click", (e) => {
|
|
23617
|
-
e.stopPropagation();
|
|
23618
|
-
pickHex(c);
|
|
23619
|
-
});
|
|
23620
|
-
recentRow.appendChild(sw);
|
|
23621
|
-
}
|
|
23622
|
-
const add = document.createElement("label");
|
|
23623
|
-
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;`;
|
|
23624
|
-
add.textContent = "+";
|
|
23625
|
-
const input = document.createElement("input");
|
|
23626
|
-
input.type = "color";
|
|
23627
|
-
input.value = curHex;
|
|
23628
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
23629
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
23630
|
-
add.appendChild(input);
|
|
23631
|
-
recentRow.appendChild(add);
|
|
23632
|
-
};
|
|
23633
|
-
const opLabel = document.createElement("div");
|
|
23634
|
-
opLabel.textContent = "Opacity";
|
|
23635
|
-
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
23636
|
-
const opRow = document.createElement("div");
|
|
23637
|
-
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
23638
|
-
const track = document.createElement("div");
|
|
23639
|
-
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
23640
|
-
const knob = document.createElement("div");
|
|
23641
|
-
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;";
|
|
23642
|
-
track.appendChild(knob);
|
|
23643
|
-
const pctBox = document.createElement("div");
|
|
23644
|
-
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;`;
|
|
23645
|
-
opRow.append(track, pctBox);
|
|
23646
|
-
const paintOpacity = () => {
|
|
23647
|
-
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
23648
|
-
knob.style.left = `${curAlpha * 100}%`;
|
|
23649
|
-
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
23650
|
-
};
|
|
23651
|
-
let dragging = false;
|
|
23652
|
-
const onDrag = (clientX) => {
|
|
23653
|
-
const r = track.getBoundingClientRect();
|
|
23654
|
-
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
23655
|
-
paintOpacity();
|
|
23656
|
-
emit();
|
|
23657
|
-
};
|
|
23658
|
-
track.addEventListener("pointerdown", (e) => {
|
|
23659
|
-
e.stopPropagation();
|
|
23660
|
-
dragging = true;
|
|
23661
|
-
track.setPointerCapture(e.pointerId);
|
|
23662
|
-
onDrag(e.clientX);
|
|
23663
|
-
});
|
|
23664
|
-
track.addEventListener("pointermove", (e) => {
|
|
23665
|
-
if (dragging) onDrag(e.clientX);
|
|
23666
|
-
});
|
|
23667
|
-
track.addEventListener("pointerup", () => dragging = false);
|
|
23668
|
-
function emit() {
|
|
23669
|
-
onChange(combineColor(curHex, curAlpha));
|
|
23670
|
-
}
|
|
23671
|
-
function pickHex(hex, custom = false) {
|
|
23672
|
-
curHex = splitColor(hex).hex6;
|
|
23673
|
-
addRecent(curHex);
|
|
23674
|
-
paintSelection();
|
|
23675
|
-
paintOpacity();
|
|
23676
|
-
if (custom) renderRecents();
|
|
23677
|
-
emit();
|
|
23678
|
-
}
|
|
23679
|
-
renderRecents();
|
|
23680
|
-
paintSelection();
|
|
23681
|
-
paintOpacity();
|
|
23682
|
-
root.append(grid, recentRow, opLabel, opRow);
|
|
23683
|
-
return root;
|
|
23684
|
-
}
|
|
23685
|
-
|
|
23686
|
-
// src/renderers/native/chrome/ColorField.ts
|
|
23687
|
-
var CHECKER2 = transparencyChecker(8);
|
|
23688
|
-
var STYLE_ID22 = "vela-color-field";
|
|
23689
|
-
function ensureStyles4() {
|
|
23690
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID22)) return;
|
|
23691
|
-
const st = document.createElement("style");
|
|
23692
|
-
st.id = STYLE_ID22;
|
|
23693
|
-
st.textContent = `
|
|
23694
|
-
.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;}
|
|
23695
|
-
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
23696
|
-
.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);}
|
|
23697
|
-
.vela-color-field-pop{position:fixed;z-index:6000;background:var(--vela-surface-overlay);border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);padding:10px;}
|
|
23698
|
-
`;
|
|
23699
|
-
document.head.appendChild(st);
|
|
23700
|
-
}
|
|
23701
|
-
var openPopover = null;
|
|
23702
|
-
function closeOpen() {
|
|
23703
|
-
if (!openPopover) return;
|
|
23704
|
-
document.removeEventListener("pointerdown", openPopover.onOutside, true);
|
|
23705
|
-
window.removeEventListener("resize", openPopover.reflow, true);
|
|
23706
|
-
openPopover.el.remove();
|
|
23707
|
-
openPopover = null;
|
|
23708
|
-
}
|
|
23709
|
-
function place(pop, trigger) {
|
|
23710
|
-
const r = trigger.getBoundingClientRect();
|
|
23711
|
-
const w = pop.offsetWidth;
|
|
23712
|
-
const h = pop.offsetHeight;
|
|
23713
|
-
let left = r.right - w;
|
|
23714
|
-
let top = r.bottom + 6;
|
|
23715
|
-
if (left < 6) left = 6;
|
|
23716
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
23717
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
23718
|
-
if (top < 6) top = 6;
|
|
23719
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
23720
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
23721
|
-
}
|
|
23722
|
-
function colorField(theme, getVal, onVal) {
|
|
23723
|
-
ensureStyles4();
|
|
23724
|
-
const trigger = document.createElement("button");
|
|
23725
|
-
trigger.type = "button";
|
|
23726
|
-
trigger.className = "vela-color-field";
|
|
23727
|
-
const swatch = document.createElement("span");
|
|
23728
|
-
swatch.className = "vela-color-field-swatch";
|
|
23729
|
-
trigger.appendChild(swatch);
|
|
23730
|
-
const paint = () => {
|
|
23731
|
-
const v = getVal();
|
|
23732
|
-
swatch.style.background = `linear-gradient(${v}, ${v}), ${CHECKER2}`;
|
|
23733
|
-
};
|
|
23734
|
-
paint();
|
|
23735
|
-
trigger.addEventListener("vela-sync", paint);
|
|
23736
|
-
trigger.addEventListener("click", (e) => {
|
|
23737
|
-
e.stopPropagation();
|
|
23738
|
-
if (openPopover && openPopover.trigger === trigger) {
|
|
23739
|
-
closeOpen();
|
|
23740
|
-
return;
|
|
23741
|
-
}
|
|
23742
|
-
closeOpen();
|
|
23743
|
-
const pop = document.createElement("div");
|
|
23744
|
-
pop.className = "vela-color-field-pop";
|
|
23745
|
-
applyChromeTokens(pop, theme);
|
|
23746
|
-
pop.addEventListener("pointerdown", (ev) => ev.stopPropagation());
|
|
23747
|
-
pop.appendChild(
|
|
23748
|
-
buildColorPicker(getVal(), theme, (val) => {
|
|
23749
|
-
onVal(val);
|
|
23750
|
-
paint();
|
|
23751
|
-
})
|
|
23752
|
-
);
|
|
23753
|
-
document.body.appendChild(pop);
|
|
23754
|
-
place(pop, trigger);
|
|
23755
|
-
const onOutside = (ev) => {
|
|
23756
|
-
const t = ev.target;
|
|
23757
|
-
if (pop.contains(t) || trigger.contains(t)) return;
|
|
23758
|
-
closeOpen();
|
|
23759
|
-
};
|
|
23760
|
-
const reflow = () => place(pop, trigger);
|
|
23761
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
23762
|
-
window.addEventListener("resize", reflow, true);
|
|
23763
|
-
openPopover = { el: pop, trigger, onOutside, reflow };
|
|
23764
|
-
});
|
|
23765
|
-
return trigger;
|
|
23766
|
-
}
|
|
23767
|
-
function closeColorPopover() {
|
|
23768
|
-
closeOpen();
|
|
23769
|
-
}
|
|
23770
|
-
|
|
23771
25125
|
// src/renderers/native/chrome/WidthField.ts
|
|
23772
25126
|
var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
|
|
23773
|
-
var
|
|
23774
|
-
|
|
23775
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID23)) return;
|
|
23776
|
-
const st = document.createElement("style");
|
|
23777
|
-
st.id = STYLE_ID23;
|
|
23778
|
-
st.textContent = `
|
|
25127
|
+
var STYLE_ID22 = "vela-width-field";
|
|
25128
|
+
var WIDTH_CSS = `
|
|
23779
25129
|
.vela-width-field{height:28px;padding:0 8px;border:1px solid var(--vela-border-strong);border-radius:var(--vela-radius-sm);background:var(--vela-surface-elev);cursor:pointer;display:inline-flex;align-items:center;gap:8px;flex:none;color:var(--vela-fg);font-family:inherit;outline:none;}
|
|
23780
25130
|
.vela-width-field:hover{border-color:var(--vela-fg-muted);}
|
|
23781
|
-
.vela-width-field-
|
|
25131
|
+
.vela-width-field-caret{display:flex;color:var(--vela-fg-muted);}
|
|
25132
|
+
.vela-width-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:4px;display:flex;flex-direction:column;gap:1px;color:var(--vela-fg);}
|
|
23782
25133
|
.vela-width-field-item{display:flex;align-items:center;gap:8px;min-width:96px;padding:5px 8px;border:none;border-radius:5px;background:transparent;color:inherit;cursor:pointer;text-align:left;font:inherit;}
|
|
23783
25134
|
.vela-width-field-item:hover{background:var(--vela-hover-strong);}
|
|
23784
25135
|
.vela-width-field-item[data-active='1']{background:var(--vela-active);}
|
|
23785
25136
|
`;
|
|
23786
|
-
document.head.appendChild(st);
|
|
23787
|
-
}
|
|
23788
25137
|
function lineGlyph(width) {
|
|
23789
25138
|
return `<svg width="22" height="14" viewBox="0 0 22 14" fill="none"><line x1="2" y1="7" x2="20" y2="7" stroke="currentColor" stroke-width="${width}" stroke-linecap="round"/></svg>`;
|
|
23790
25139
|
}
|
|
23791
|
-
var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="
|
|
23792
|
-
var openPopover2 = null;
|
|
23793
|
-
function closeOpen2() {
|
|
23794
|
-
if (!openPopover2) return;
|
|
23795
|
-
document.removeEventListener("pointerdown", openPopover2.onOutside, true);
|
|
23796
|
-
window.removeEventListener("resize", openPopover2.reflow, true);
|
|
23797
|
-
openPopover2.el.remove();
|
|
23798
|
-
openPopover2 = null;
|
|
23799
|
-
}
|
|
23800
|
-
function place2(pop, trigger) {
|
|
23801
|
-
const r = trigger.getBoundingClientRect();
|
|
23802
|
-
const w = pop.offsetWidth;
|
|
23803
|
-
const h = pop.offsetHeight;
|
|
23804
|
-
let left = r.right - w;
|
|
23805
|
-
let top = r.bottom + 6;
|
|
23806
|
-
if (left < 6) left = 6;
|
|
23807
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
23808
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
23809
|
-
if (top < 6) top = 6;
|
|
23810
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
23811
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
23812
|
-
}
|
|
25140
|
+
var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
23813
25141
|
function widthField(theme, getVal, onVal) {
|
|
23814
|
-
|
|
25142
|
+
injectStyles(STYLE_ID22, WIDTH_CSS, document);
|
|
23815
25143
|
const trigger = document.createElement("button");
|
|
23816
25144
|
trigger.type = "button";
|
|
23817
25145
|
trigger.className = "vela-width-field";
|
|
23818
25146
|
const paint = () => {
|
|
23819
|
-
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span
|
|
25147
|
+
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span class="vela-width-field-caret">${CHEVRON}</span>`;
|
|
23820
25148
|
};
|
|
23821
25149
|
paint();
|
|
25150
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
25151
|
+
e.preventDefault();
|
|
25152
|
+
trigger.focus({ preventScroll: true });
|
|
25153
|
+
});
|
|
23822
25154
|
trigger.addEventListener("click", (e) => {
|
|
23823
25155
|
e.stopPropagation();
|
|
23824
|
-
if (
|
|
23825
|
-
|
|
25156
|
+
if (openPopoverTrigger() === trigger) {
|
|
25157
|
+
closeOpenPopovers();
|
|
23826
25158
|
return;
|
|
23827
25159
|
}
|
|
23828
|
-
|
|
23829
|
-
|
|
23830
|
-
|
|
23831
|
-
|
|
23832
|
-
|
|
23833
|
-
|
|
23834
|
-
|
|
23835
|
-
|
|
23836
|
-
|
|
23837
|
-
|
|
23838
|
-
|
|
23839
|
-
|
|
23840
|
-
|
|
23841
|
-
|
|
23842
|
-
|
|
23843
|
-
|
|
23844
|
-
|
|
23845
|
-
|
|
23846
|
-
|
|
23847
|
-
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23853
|
-
closeOpen2();
|
|
23854
|
-
};
|
|
23855
|
-
const reflow = () => place2(pop, trigger);
|
|
23856
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
23857
|
-
window.addEventListener("resize", reflow, true);
|
|
23858
|
-
openPopover2 = { el: pop, trigger, onOutside, reflow };
|
|
25160
|
+
const pop = new Popover({
|
|
25161
|
+
trigger,
|
|
25162
|
+
theme,
|
|
25163
|
+
align: "end",
|
|
25164
|
+
gap: 6,
|
|
25165
|
+
className: "vela-width-field-pop",
|
|
25166
|
+
content: (el) => {
|
|
25167
|
+
el.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
|
|
25168
|
+
for (const w of WIDTH_FIELD_OPTIONS) {
|
|
25169
|
+
const item = document.createElement("button");
|
|
25170
|
+
item.type = "button";
|
|
25171
|
+
item.className = "vela-width-field-item";
|
|
25172
|
+
item.dataset.active = w === getVal() ? "1" : "0";
|
|
25173
|
+
item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
|
|
25174
|
+
item.addEventListener("click", (ev) => {
|
|
25175
|
+
ev.stopPropagation();
|
|
25176
|
+
pop.hide();
|
|
25177
|
+
onVal(w);
|
|
25178
|
+
paint();
|
|
25179
|
+
});
|
|
25180
|
+
el.appendChild(item);
|
|
25181
|
+
}
|
|
25182
|
+
}
|
|
25183
|
+
});
|
|
25184
|
+
pop.show();
|
|
23859
25185
|
});
|
|
23860
25186
|
return trigger;
|
|
23861
25187
|
}
|
|
23862
25188
|
function closeWidthPopover() {
|
|
23863
|
-
|
|
25189
|
+
closeOpenPopovers();
|
|
23864
25190
|
}
|
|
23865
25191
|
|
|
23866
25192
|
// src/renderers/native/chrome/SettingsDialog.ts
|
|
@@ -23875,37 +25201,24 @@ function styleLabel(id) {
|
|
|
23875
25201
|
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS2[id] ?? id;
|
|
23876
25202
|
}
|
|
23877
25203
|
var SD_STYLE_ID = "vela-settings-controls";
|
|
25204
|
+
var SD_STYLE_REV = "2";
|
|
23878
25205
|
var SETTINGS_SURFACE = "var(--vela-surface)";
|
|
23879
25206
|
var SETTINGS_BORDER = "var(--vela-border)";
|
|
23880
25207
|
var SETTINGS_TEXT = "var(--vela-fg)";
|
|
23881
25208
|
function ensureControlStyles() {
|
|
23882
|
-
if (typeof document === "undefined"
|
|
23883
|
-
const
|
|
25209
|
+
if (typeof document === "undefined") return;
|
|
25210
|
+
const existing = document.getElementById(SD_STYLE_ID);
|
|
25211
|
+
if (existing?.dataset.rev === SD_STYLE_REV) return;
|
|
25212
|
+
const st = existing ?? document.createElement("style");
|
|
23884
25213
|
st.id = SD_STYLE_ID;
|
|
25214
|
+
st.dataset.rev = SD_STYLE_REV;
|
|
23885
25215
|
st.textContent = `
|
|
23886
|
-
.vela-sd-check{width:18px;height:18px;flex:none;display:inline-flex;align-items:center;justify-content:center;padding:0;border:1px solid var(--vela-border-strong);border-radius:5px;background:transparent;color:transparent;cursor:pointer;}
|
|
23887
|
-
.vela-sd-check:hover{border-color:var(--vela-fg-muted);}
|
|
23888
|
-
.vela-sd-check.on{background:var(--vela-selected-bg);border-color:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
23889
|
-
.vela-sd-check svg{display:block;}
|
|
23890
|
-
.vela-sd-select,.vela-sd-number{height:28px;background:var(--vela-surface-elev);border:1px solid var(--vela-border-strong);border-radius:var(--vela-radius-sm);color:var(--vela-fg);padding:0 8px;font-size:13px;outline:none;font-family:inherit;}
|
|
23891
|
-
.vela-sd-select:hover,.vela-sd-number:hover{border-color:var(--vela-fg-muted);}
|
|
23892
|
-
/* Custom caret: the native one hugs the right border; ours gets 8px of air. (A data URI
|
|
23893
|
-
can't read currentColor, so it uses the shared muted-gray ink, legible on both themes.) */
|
|
23894
|
-
.vela-sd-select{-webkit-appearance:none;appearance:none;padding-right:26px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'%3E%3Cpath d='M1 1l3 3 3-3' fill='none' stroke='%23868a96' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;}
|
|
23895
|
-
.vela-sd-number{width:64px;}
|
|
23896
|
-
.vela-sd-color{width:32px;height:26px;padding:0;border:1px solid var(--vela-border);border-radius:var(--vela-radius-sm);background:transparent;cursor:pointer;-webkit-appearance:none;appearance:none;}
|
|
23897
|
-
.vela-sd-color::-webkit-color-swatch-wrapper{padding:2px;}
|
|
23898
|
-
.vela-sd-color::-webkit-color-swatch{border:none;border-radius:2px;}
|
|
23899
25216
|
.vela-sd-pane::-webkit-scrollbar{width:8px;}
|
|
23900
25217
|
.vela-sd-pane::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:var(--vela-radius-sm);border:2px solid transparent;background-clip:padding-box;}
|
|
23901
25218
|
.vela-sd-pane::-webkit-scrollbar-track{background:transparent;}
|
|
23902
|
-
.vela-sd-toggle{position:relative;width:38px;height:22px;border-radius:11px;background:var(--vela-surface-sunken);border:1px solid var(--vela-border);cursor:pointer;flex:none;padding:0;}
|
|
23903
|
-
.vela-sd-toggle::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--vela-fg-muted);transition:transform var(--vela-dur-med) ease,background var(--vela-dur-med) ease;}
|
|
23904
|
-
.vela-sd-toggle.on{background:var(--vela-active);border-color:var(--vela-selected-bg);}
|
|
23905
|
-
.vela-sd-toggle.on::after{transform:translateX(16px);background:var(--vela-selected-bg);}
|
|
23906
25219
|
/* Tab rail / footer button / header close: base styles live HERE, not inline on the
|
|
23907
25220
|
elements \u2014 inline declarations always beat stylesheet :hover rules, which is exactly
|
|
23908
|
-
what killed these hovers before. Active tab state is the .on class
|
|
25221
|
+
what killed these hovers before. Active tab state is the .on class,
|
|
23909
25222
|
hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
|
|
23910
25223
|
.vela-sd-tab{text-align:left;padding:9px 12px;background:transparent;border:none;border-radius:var(--vela-radius-md);color:var(--vela-fg-muted);font-weight:600;font-size:13px;font-family:inherit;cursor:pointer;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
23911
25224
|
.vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
@@ -23964,13 +25277,13 @@ function ensureControlStyles() {
|
|
|
23964
25277
|
.vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
|
|
23965
25278
|
.vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
|
|
23966
25279
|
.vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
|
|
23967
|
-
.vela-sd-mobile .vela-
|
|
23968
|
-
.vela-sd-mobile .vela-
|
|
25280
|
+
.vela-sd-mobile .vela-switch{width:22px;height:22px;}
|
|
25281
|
+
.vela-sd-mobile .vela-select[data-size='sm'] .vela-select-trigger,.vela-sd-mobile .vela-num[data-size='sm'] input{height:34px;}
|
|
23969
25282
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
23970
25283
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
23971
25284
|
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
|
|
23972
25285
|
`;
|
|
23973
|
-
document.head.appendChild(st);
|
|
25286
|
+
if (!existing) document.head.appendChild(st);
|
|
23974
25287
|
}
|
|
23975
25288
|
var SettingsDialog = class {
|
|
23976
25289
|
constructor(container, theme) {
|
|
@@ -24286,15 +25599,15 @@ var SettingsDialog = class {
|
|
|
24286
25599
|
railScrim = document.createElement("div");
|
|
24287
25600
|
railScrim.className = "vela-sd-railscrim";
|
|
24288
25601
|
railScrim.addEventListener("click", () => toggleRail?.());
|
|
24289
|
-
toggleRail = (
|
|
24290
|
-
const on =
|
|
25602
|
+
toggleRail = (open2) => {
|
|
25603
|
+
const on = open2 ?? !rail.classList.contains("open");
|
|
24291
25604
|
rail.classList.toggle("open", on);
|
|
24292
25605
|
railScrim?.classList.toggle("open", on);
|
|
24293
25606
|
};
|
|
24294
25607
|
}
|
|
24295
25608
|
const paneHost = document.createElement("div");
|
|
24296
25609
|
paneHost.className = "vela-sd-pane";
|
|
24297
|
-
paneHost.style.cssText = mobile ? "flex:1;min-width:0;overflow-y:auto;padding:6px 14px calc(14px + env(safe-area-inset-bottom, 0px));" : "flex:1;overflow-y:auto;padding:6px 18px 14px;";
|
|
25610
|
+
paneHost.style.cssText = mobile ? "flex:1;min-width:0;overflow-y:auto;overflow-anchor:none;padding:6px 14px calc(14px + env(safe-area-inset-bottom, 0px));" : "flex:1;overflow-y:auto;overflow-anchor:none;padding:6px 18px 14px;";
|
|
24298
25611
|
const panes = [];
|
|
24299
25612
|
let current = null;
|
|
24300
25613
|
for (const child of [...body.children]) {
|
|
@@ -24360,7 +25673,7 @@ var SettingsDialog = class {
|
|
|
24360
25673
|
}
|
|
24361
25674
|
}
|
|
24362
25675
|
close() {
|
|
24363
|
-
|
|
25676
|
+
closeOpenPopovers();
|
|
24364
25677
|
closeWidthPopover();
|
|
24365
25678
|
this.root?.remove();
|
|
24366
25679
|
this.root = null;
|
|
@@ -24515,50 +25828,41 @@ var SettingsDialog = class {
|
|
|
24515
25828
|
return wf;
|
|
24516
25829
|
}
|
|
24517
25830
|
if (c.kind === "select") {
|
|
24518
|
-
const sel = document.createElement("select");
|
|
24519
|
-
sel.className = "vela-sd-select";
|
|
24520
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
24521
|
-
sel.title = c.label;
|
|
24522
25831
|
const current2 = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
24523
|
-
|
|
24524
|
-
|
|
24525
|
-
|
|
24526
|
-
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
}
|
|
24530
|
-
sel.addEventListener("change", () => put(c.key, sel.value));
|
|
24531
|
-
sel.addEventListener("vela-sync", () => {
|
|
24532
|
-
sel.value = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
25832
|
+
const sel = new Select({
|
|
25833
|
+
options: normalizeSelectOptions(c.options).map(([value, label]) => ({ value, label })),
|
|
25834
|
+
value: current2,
|
|
25835
|
+
size: "sm",
|
|
25836
|
+
theme: this.theme,
|
|
25837
|
+
onChange: (v) => put(c.key, v)
|
|
24533
25838
|
});
|
|
24534
|
-
|
|
24535
|
-
|
|
24536
|
-
|
|
24537
|
-
ni.type = "number";
|
|
24538
|
-
ni.className = "vela-sd-number";
|
|
24539
|
-
if (compact) ni.style.width = "56px";
|
|
24540
|
-
if (c.min !== void 0) ni.min = String(c.min);
|
|
24541
|
-
if (c.max !== void 0) ni.max = String(c.max);
|
|
24542
|
-
ni.step = String(c.step ?? 1);
|
|
24543
|
-
ni.title = c.label;
|
|
24544
|
-
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
24545
|
-
if (c.placeholder !== void 0) {
|
|
24546
|
-
ni.placeholder = c.placeholder;
|
|
24547
|
-
if (current !== c.defval) ni.value = String(current);
|
|
24548
|
-
ni.addEventListener("change", () => {
|
|
24549
|
-
const raw = ni.value.trim() === "" ? c.defval : Number(ni.value);
|
|
24550
|
-
const v = Number.isFinite(raw) ? Math.min(c.max ?? Infinity, Math.max(c.min ?? -Infinity, raw)) : c.defval;
|
|
24551
|
-
ni.value = v === c.defval ? "" : String(v);
|
|
24552
|
-
put(c.key, v);
|
|
24553
|
-
});
|
|
24554
|
-
} else {
|
|
24555
|
-
ni.value = String(current);
|
|
24556
|
-
ni.addEventListener("input", () => {
|
|
24557
|
-
const v = Number(ni.value);
|
|
24558
|
-
if (Number.isFinite(v)) put(c.key, v);
|
|
25839
|
+
sel.el.title = c.label;
|
|
25840
|
+
sel.el.addEventListener("vela-sync", () => {
|
|
25841
|
+
sel.setValue(typeof bag[c.key] === "string" ? bag[c.key] : c.defval);
|
|
24559
25842
|
});
|
|
25843
|
+
return sel.el;
|
|
24560
25844
|
}
|
|
24561
|
-
|
|
25845
|
+
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
25846
|
+
const ni = new NumberInput({
|
|
25847
|
+
value: current,
|
|
25848
|
+
min: c.min,
|
|
25849
|
+
max: c.max,
|
|
25850
|
+
step: c.step ?? 1,
|
|
25851
|
+
size: "sm",
|
|
25852
|
+
commit: "live",
|
|
25853
|
+
clamp: c.placeholder !== void 0,
|
|
25854
|
+
steppers: false,
|
|
25855
|
+
compact,
|
|
25856
|
+
title: c.label,
|
|
25857
|
+
placeholder: c.placeholder,
|
|
25858
|
+
emptyValue: c.placeholder !== void 0 ? c.defval : void 0,
|
|
25859
|
+
onChange: (v) => put(c.key, v)
|
|
25860
|
+
});
|
|
25861
|
+
ni.el.addEventListener("vela-sync", () => {
|
|
25862
|
+
const next = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
25863
|
+
ni.setValue(next);
|
|
25864
|
+
});
|
|
25865
|
+
return ni.el;
|
|
24562
25866
|
}
|
|
24563
25867
|
/**
|
|
24564
25868
|
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
@@ -24839,28 +26143,27 @@ var SettingsDialog = class {
|
|
|
24839
26143
|
* only one visible at a time; see `typeRow`). */
|
|
24840
26144
|
toggleRow(label, value, onToggle, controls, get) {
|
|
24841
26145
|
const wrap = document.createElement("div");
|
|
24842
|
-
const
|
|
24843
|
-
|
|
24844
|
-
|
|
24845
|
-
|
|
24846
|
-
let checked = value;
|
|
24847
|
-
const cbToggle = () => {
|
|
24848
|
-
checked = !checked;
|
|
24849
|
-
cb.classList.toggle("on", checked);
|
|
24850
|
-
return checked;
|
|
24851
|
-
};
|
|
26146
|
+
const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
|
|
26147
|
+
onToggle(v);
|
|
26148
|
+
if (controls.length > 0) syncDim(v);
|
|
26149
|
+
} });
|
|
24852
26150
|
const lbl = document.createElement("span");
|
|
24853
26151
|
lbl.textContent = label;
|
|
24854
26152
|
lbl.style.cssText = "opacity:0.85;white-space:nowrap;";
|
|
26153
|
+
const syncDim = (on) => {
|
|
26154
|
+
for (const c of controls) {
|
|
26155
|
+
if (c.dataset.sdSelfGated === "1") continue;
|
|
26156
|
+
c.style.opacity = on ? "1" : "0.4";
|
|
26157
|
+
c.style.pointerEvents = on ? "" : "none";
|
|
26158
|
+
}
|
|
26159
|
+
};
|
|
24855
26160
|
if (controls.length === 0) {
|
|
24856
26161
|
wrap.className = "vela-sd-bool";
|
|
24857
26162
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
|
|
24858
|
-
wrap.append(
|
|
24859
|
-
cb.addEventListener("click", () => onToggle(cbToggle()));
|
|
26163
|
+
wrap.append(sw.el, lbl);
|
|
24860
26164
|
if (get) {
|
|
24861
26165
|
wrap.addEventListener("vela-sync", () => {
|
|
24862
|
-
|
|
24863
|
-
cb.classList.toggle("on", checked);
|
|
26166
|
+
sw.setChecked(get());
|
|
24864
26167
|
});
|
|
24865
26168
|
}
|
|
24866
26169
|
return wrap;
|
|
@@ -24868,28 +26171,16 @@ var SettingsDialog = class {
|
|
|
24868
26171
|
wrap.className = "vela-sd-row";
|
|
24869
26172
|
const left = document.createElement("div");
|
|
24870
26173
|
left.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
24871
|
-
left.append(
|
|
26174
|
+
left.append(sw.el, lbl);
|
|
24872
26175
|
const box = document.createElement("div");
|
|
24873
26176
|
box.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
24874
26177
|
for (const c of controls) box.appendChild(c);
|
|
24875
|
-
const syncDim = (on) => {
|
|
24876
|
-
for (const c of controls) {
|
|
24877
|
-
if (c.dataset.sdSelfGated === "1") continue;
|
|
24878
|
-
c.style.opacity = on ? "1" : "0.4";
|
|
24879
|
-
c.style.pointerEvents = on ? "" : "none";
|
|
24880
|
-
}
|
|
24881
|
-
};
|
|
24882
26178
|
syncDim(value);
|
|
24883
|
-
cb.addEventListener("click", () => {
|
|
24884
|
-
const v = cbToggle();
|
|
24885
|
-
onToggle(v);
|
|
24886
|
-
syncDim(v);
|
|
24887
|
-
});
|
|
24888
26179
|
if (get) {
|
|
24889
26180
|
wrap.addEventListener("vela-sync", () => {
|
|
24890
|
-
|
|
24891
|
-
|
|
24892
|
-
syncDim(
|
|
26181
|
+
const v = get();
|
|
26182
|
+
sw.setChecked(v);
|
|
26183
|
+
syncDim(v);
|
|
24893
26184
|
});
|
|
24894
26185
|
}
|
|
24895
26186
|
wrap.append(left, box);
|
|
@@ -24897,52 +26188,23 @@ var SettingsDialog = class {
|
|
|
24897
26188
|
}
|
|
24898
26189
|
numberRow(label, value, min, max, step, onChange) {
|
|
24899
26190
|
const { wrap } = this.row(label);
|
|
24900
|
-
|
|
24901
|
-
ni.type = "number";
|
|
24902
|
-
ni.value = String(value);
|
|
24903
|
-
ni.min = String(min);
|
|
24904
|
-
ni.max = String(max);
|
|
24905
|
-
ni.step = String(step);
|
|
24906
|
-
ni.className = "vela-sd-number";
|
|
24907
|
-
ni.addEventListener("input", () => {
|
|
24908
|
-
const n = Number(ni.value);
|
|
24909
|
-
if (Number.isFinite(n)) onChange(n);
|
|
24910
|
-
});
|
|
24911
|
-
wrap.appendChild(ni);
|
|
26191
|
+
wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
|
|
24912
26192
|
return wrap;
|
|
24913
26193
|
}
|
|
24914
26194
|
/** A dropdown whose option values differ from their display labels. */
|
|
24915
26195
|
selectRowLabeled(label, value, options, onChange) {
|
|
24916
26196
|
const { wrap } = this.row(label);
|
|
24917
|
-
|
|
24918
|
-
|
|
24919
|
-
|
|
24920
|
-
|
|
24921
|
-
|
|
24922
|
-
|
|
24923
|
-
|
|
24924
|
-
if (val === value) o.selected = true;
|
|
24925
|
-
sel.appendChild(o);
|
|
24926
|
-
}
|
|
24927
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
24928
|
-
wrap.appendChild(sel);
|
|
26197
|
+
wrap.appendChild(new Select({
|
|
26198
|
+
options: options.map(([v, l]) => ({ value: v, label: l })),
|
|
26199
|
+
value,
|
|
26200
|
+
size: "sm",
|
|
26201
|
+
theme: this.theme,
|
|
26202
|
+
onChange
|
|
26203
|
+
}).el);
|
|
24929
26204
|
return wrap;
|
|
24930
26205
|
}
|
|
24931
26206
|
selectRow(label, value, options, onChange) {
|
|
24932
|
-
|
|
24933
|
-
const sel = document.createElement("select");
|
|
24934
|
-
sel.className = "vela-sd-select";
|
|
24935
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
24936
|
-
for (const opt of options) {
|
|
24937
|
-
const o = document.createElement("option");
|
|
24938
|
-
o.value = opt;
|
|
24939
|
-
o.textContent = opt;
|
|
24940
|
-
if (opt === value) o.selected = true;
|
|
24941
|
-
sel.appendChild(o);
|
|
24942
|
-
}
|
|
24943
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
24944
|
-
wrap.appendChild(sel);
|
|
24945
|
-
return wrap;
|
|
26207
|
+
return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
|
|
24946
26208
|
}
|
|
24947
26209
|
/** Footer with the full config as JSON — the export/import (templating) surface. */
|
|
24948
26210
|
footer(_config) {
|
|
@@ -25333,13 +26595,19 @@ var DrawingPainter = class {
|
|
|
25333
26595
|
}
|
|
25334
26596
|
this.paintLabel(ctx, d, proj, theme);
|
|
25335
26597
|
}
|
|
25336
|
-
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
26598
|
+
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
26599
|
+
* Two-point line tools rotate the glyphs to follow the segment, kept upright. */
|
|
25337
26600
|
paintLabel(ctx, d, proj, theme) {
|
|
25338
26601
|
const text = d.text;
|
|
25339
26602
|
if (!text || !text.value || d.id === this.targets.mutedLabel) return;
|
|
25340
26603
|
const layout = labelLayout(d, proj);
|
|
25341
26604
|
if (!layout) return;
|
|
25342
26605
|
const fs = namedFontSize(text.size);
|
|
26606
|
+
ctx.save();
|
|
26607
|
+
if (layout.rotate) {
|
|
26608
|
+
ctx.translate(layout.rotate.cx, layout.rotate.cy);
|
|
26609
|
+
ctx.rotate(layout.rotate.angle);
|
|
26610
|
+
}
|
|
25343
26611
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
25344
26612
|
ctx.textBaseline = "top";
|
|
25345
26613
|
ctx.textAlign = layout.align;
|
|
@@ -25347,8 +26615,8 @@ var DrawingPainter = class {
|
|
|
25347
26615
|
const lh = labelLineHeight(fs);
|
|
25348
26616
|
const lines = text.value.split("\n");
|
|
25349
26617
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
25350
|
-
ctx.textAlign = "left";
|
|
25351
26618
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
26619
|
+
ctx.restore();
|
|
25352
26620
|
}
|
|
25353
26621
|
/** Frame a plain text label while it is the target, so the text reads as a clickable object even
|
|
25354
26622
|
* though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
|
|
@@ -26291,7 +27559,14 @@ function labelLayout(d, proj) {
|
|
|
26291
27559
|
case "infoline":
|
|
26292
27560
|
case "trendangle": {
|
|
26293
27561
|
if (pts.length < 2) return null;
|
|
26294
|
-
|
|
27562
|
+
const [x1, y1] = pts[0];
|
|
27563
|
+
const [x2, y2] = pts[1];
|
|
27564
|
+
return {
|
|
27565
|
+
x: 0,
|
|
27566
|
+
top: -6 - lh * lines,
|
|
27567
|
+
align: "center",
|
|
27568
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: (x1 + x2) / 2, cy: (y1 + y2) / 2 }
|
|
27569
|
+
};
|
|
26295
27570
|
}
|
|
26296
27571
|
case "box": {
|
|
26297
27572
|
if (pts.length < 2) return null;
|
|
@@ -26640,11 +27915,11 @@ function applyFree(anchor, pt, free) {
|
|
|
26640
27915
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
26641
27916
|
var BTN = 30;
|
|
26642
27917
|
var ICON = 21;
|
|
26643
|
-
var
|
|
26644
|
-
function
|
|
26645
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
27918
|
+
var STYLE_ID23 = "vela-drawing-popup-styles";
|
|
27919
|
+
function ensureStyles4() {
|
|
27920
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID23)) return;
|
|
26646
27921
|
const s = document.createElement("style");
|
|
26647
|
-
s.id =
|
|
27922
|
+
s.id = STYLE_ID23;
|
|
26648
27923
|
s.textContent = `
|
|
26649
27924
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
26650
27925
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -26678,17 +27953,14 @@ var DrawingSettingsPopup = class {
|
|
|
26678
27953
|
this.textPanel = null;
|
|
26679
27954
|
this.levelsPanel = null;
|
|
26680
27955
|
this.colorPop = null;
|
|
26681
|
-
// floating RGB + opacity picker for a color swatch
|
|
26682
27956
|
this.colorOwner = null;
|
|
26683
|
-
|
|
26684
|
-
this.menuEl = null;
|
|
26685
|
-
// floating option list for a dropdown control
|
|
27957
|
+
this.menuPop = null;
|
|
26686
27958
|
this.menuOwner = null;
|
|
26687
27959
|
this.onClose = null;
|
|
26688
27960
|
this.onOutside = (e) => {
|
|
26689
27961
|
const target = e.target;
|
|
26690
|
-
if (this.colorPop?.contains(target)) return;
|
|
26691
|
-
if (this.
|
|
27962
|
+
if (this.colorPop?.el.contains(target)) return;
|
|
27963
|
+
if (this.menuPop?.el.contains(target)) return;
|
|
26692
27964
|
if (this.el && !this.el.contains(target)) {
|
|
26693
27965
|
const cb = this.onClose;
|
|
26694
27966
|
this.close();
|
|
@@ -26707,13 +27979,13 @@ var DrawingSettingsPopup = class {
|
|
|
26707
27979
|
* picker / dropdown menu), which live outside `el`. */
|
|
26708
27980
|
contains(node) {
|
|
26709
27981
|
if (!node) return false;
|
|
26710
|
-
return this.el?.contains(node) === true || this.colorPop?.contains(node) === true || this.
|
|
27982
|
+
return this.el?.contains(node) === true || this.colorPop?.el.contains(node) === true || this.menuPop?.el.contains(node) === true;
|
|
26711
27983
|
}
|
|
26712
27984
|
/** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
|
|
26713
27985
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
26714
27986
|
open(drawing, anchor, actions, onClose) {
|
|
26715
27987
|
this.close();
|
|
26716
|
-
|
|
27988
|
+
ensureStyles4();
|
|
26717
27989
|
this.onClose = onClose ?? null;
|
|
26718
27990
|
const t = this.theme;
|
|
26719
27991
|
const schema = drawing.schema();
|
|
@@ -26724,8 +27996,7 @@ var DrawingSettingsPopup = class {
|
|
|
26724
27996
|
el.style.cssText = `position:absolute;z-index:22;background:${t.background};border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);color:${t.textColor};font:var(--vela-font-size-md) ${t.fontFamily};display:flex;flex-direction:column;pointer-events:auto;overflow:hidden;`;
|
|
26725
27997
|
applyChromeTokens(el, t);
|
|
26726
27998
|
el.addEventListener("pointerdown", () => {
|
|
26727
|
-
|
|
26728
|
-
this.closeMenu();
|
|
27999
|
+
closeOpenPopovers();
|
|
26729
28000
|
});
|
|
26730
28001
|
const bar = document.createElement("div");
|
|
26731
28002
|
bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
|
|
@@ -27462,49 +28733,76 @@ var DrawingSettingsPopup = class {
|
|
|
27462
28733
|
});
|
|
27463
28734
|
return b;
|
|
27464
28735
|
}
|
|
28736
|
+
/** Host-anchored floating shell (stays inside the chart). Content is filled after
|
|
28737
|
+
* construction so `fill` can close over the live `Popover` without hitting TDZ. */
|
|
28738
|
+
hostFloat(anchor, opts) {
|
|
28739
|
+
const t = this.theme;
|
|
28740
|
+
const pop = new Popover({
|
|
28741
|
+
trigger: anchor,
|
|
28742
|
+
host: this.host,
|
|
28743
|
+
theme: t,
|
|
28744
|
+
position: "absolute",
|
|
28745
|
+
boundary: this.host,
|
|
28746
|
+
boundaryInset: 4,
|
|
28747
|
+
viewportInset: 0,
|
|
28748
|
+
gap: 6,
|
|
28749
|
+
align: opts.align ?? "start",
|
|
28750
|
+
zIndex: opts.zIndex,
|
|
28751
|
+
onClose: () => {
|
|
28752
|
+
if (this.menuPop === pop) {
|
|
28753
|
+
this.menuPop = null;
|
|
28754
|
+
this.menuOwner = null;
|
|
28755
|
+
}
|
|
28756
|
+
if (this.colorPop === pop) {
|
|
28757
|
+
this.colorPop = null;
|
|
28758
|
+
this.colorOwner = null;
|
|
28759
|
+
}
|
|
28760
|
+
}
|
|
28761
|
+
});
|
|
28762
|
+
const el = pop.el;
|
|
28763
|
+
el.style.background = t.background;
|
|
28764
|
+
el.style.border = "1px solid var(--vela-border)";
|
|
28765
|
+
el.style.borderRadius = "var(--vela-radius-lg)";
|
|
28766
|
+
el.style.boxShadow = "var(--vela-shadow)";
|
|
28767
|
+
el.style.padding = opts.padding;
|
|
28768
|
+
el.style.display = "flex";
|
|
28769
|
+
el.style.flexDirection = "column";
|
|
28770
|
+
el.style.gap = "1px";
|
|
28771
|
+
el.style.color = t.textColor;
|
|
28772
|
+
el.style.font = `var(--vela-font-size-md) ${t.fontFamily}`;
|
|
28773
|
+
el.style.pointerEvents = "auto";
|
|
28774
|
+
opts.fill(el, pop);
|
|
28775
|
+
pop.show();
|
|
28776
|
+
return pop;
|
|
28777
|
+
}
|
|
27465
28778
|
/** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
|
|
27466
28779
|
openActionMenu(anchor, rows) {
|
|
27467
|
-
this.
|
|
27468
|
-
|
|
27469
|
-
|
|
27470
|
-
|
|
27471
|
-
|
|
27472
|
-
|
|
27473
|
-
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
|
|
27487
|
-
|
|
27488
|
-
|
|
27489
|
-
|
|
27490
|
-
|
|
27491
|
-
}
|
|
27492
|
-
|
|
27493
|
-
}
|
|
27494
|
-
this.host.appendChild(menu2);
|
|
27495
|
-
const ar = anchor.getBoundingClientRect();
|
|
27496
|
-
const hr = this.host.getBoundingClientRect();
|
|
27497
|
-
const w = menu2.offsetWidth;
|
|
27498
|
-
const h = menu2.offsetHeight;
|
|
27499
|
-
let left = ar.right - hr.left - w;
|
|
27500
|
-
let top = ar.bottom - hr.top + 6;
|
|
27501
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
27502
|
-
if (left < 4) left = 4;
|
|
27503
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
27504
|
-
if (top < 4) top = 4;
|
|
27505
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
27506
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
27507
|
-
this.menuEl = menu2;
|
|
28780
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
28781
|
+
align: "end",
|
|
28782
|
+
zIndex: 26,
|
|
28783
|
+
padding: "4px",
|
|
28784
|
+
fill: (menu2, pop) => {
|
|
28785
|
+
for (const row of rows) {
|
|
28786
|
+
const item = document.createElement("button");
|
|
28787
|
+
item.type = "button";
|
|
28788
|
+
item.className = "vela-dpop-item";
|
|
28789
|
+
item.style.cssText = "display:flex;align-items:center;gap:9px;min-width:150px;padding:6px 9px;border:none;border-radius:5px;color:inherit;cursor:pointer;text-align:left;font:inherit;";
|
|
28790
|
+
const ic = document.createElement("span");
|
|
28791
|
+
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
28792
|
+
ic.innerHTML = sized(row.icon, 18);
|
|
28793
|
+
const tx = document.createElement("span");
|
|
28794
|
+
tx.textContent = row.label;
|
|
28795
|
+
tx.style.cssText = "flex:1;";
|
|
28796
|
+
item.append(ic, tx);
|
|
28797
|
+
item.addEventListener("click", (e) => {
|
|
28798
|
+
e.stopPropagation();
|
|
28799
|
+
pop.hide();
|
|
28800
|
+
row.onClick();
|
|
28801
|
+
});
|
|
28802
|
+
menu2.appendChild(item);
|
|
28803
|
+
}
|
|
28804
|
+
}
|
|
28805
|
+
});
|
|
27508
28806
|
this.menuOwner = anchor;
|
|
27509
28807
|
}
|
|
27510
28808
|
/** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
|
|
@@ -27634,60 +28932,40 @@ var DrawingSettingsPopup = class {
|
|
|
27634
28932
|
/** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
|
|
27635
28933
|
* glyph (and text label when one is supplied); the current value is highlighted. */
|
|
27636
28934
|
openMenu(anchor, values, current, render, label, onPick) {
|
|
27637
|
-
this.
|
|
27638
|
-
|
|
27639
|
-
|
|
27640
|
-
|
|
27641
|
-
|
|
27642
|
-
|
|
27643
|
-
|
|
27644
|
-
|
|
27645
|
-
|
|
27646
|
-
|
|
27647
|
-
|
|
27648
|
-
|
|
27649
|
-
|
|
27650
|
-
|
|
27651
|
-
|
|
27652
|
-
|
|
27653
|
-
|
|
27654
|
-
|
|
27655
|
-
|
|
27656
|
-
|
|
27657
|
-
|
|
27658
|
-
|
|
27659
|
-
|
|
27660
|
-
|
|
28935
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
28936
|
+
zIndex: 26,
|
|
28937
|
+
padding: "4px",
|
|
28938
|
+
fill: (menu2, pop) => {
|
|
28939
|
+
for (const v of values) {
|
|
28940
|
+
const active = v === current;
|
|
28941
|
+
const item = document.createElement("button");
|
|
28942
|
+
item.type = "button";
|
|
28943
|
+
item.className = "vela-dpop-item";
|
|
28944
|
+
item.dataset.active = active ? "1" : "0";
|
|
28945
|
+
item.style.cssText = `display:flex;align-items:center;gap:8px;${label ? "min-width:118px;" : ""}padding:5px 8px;border:none;border-radius:5px;color:inherit;cursor:pointer;text-align:left;font:inherit;`;
|
|
28946
|
+
const ic = document.createElement("span");
|
|
28947
|
+
ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
|
|
28948
|
+
ic.innerHTML = sized(render(v), 18);
|
|
28949
|
+
item.appendChild(ic);
|
|
28950
|
+
if (label) {
|
|
28951
|
+
const tx = document.createElement("span");
|
|
28952
|
+
tx.textContent = label(v);
|
|
28953
|
+
tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
|
|
28954
|
+
item.appendChild(tx);
|
|
28955
|
+
}
|
|
28956
|
+
item.addEventListener("click", (e) => {
|
|
28957
|
+
e.stopPropagation();
|
|
28958
|
+
pop.hide();
|
|
28959
|
+
onPick(v);
|
|
28960
|
+
});
|
|
28961
|
+
menu2.appendChild(item);
|
|
28962
|
+
}
|
|
27661
28963
|
}
|
|
27662
|
-
|
|
27663
|
-
e.stopPropagation();
|
|
27664
|
-
this.closeMenu();
|
|
27665
|
-
onPick(v);
|
|
27666
|
-
});
|
|
27667
|
-
menu2.appendChild(item);
|
|
27668
|
-
}
|
|
27669
|
-
this.host.appendChild(menu2);
|
|
27670
|
-
const ar = anchor.getBoundingClientRect();
|
|
27671
|
-
const hr = this.host.getBoundingClientRect();
|
|
27672
|
-
const w = menu2.offsetWidth;
|
|
27673
|
-
const h = menu2.offsetHeight;
|
|
27674
|
-
let left = ar.left - hr.left;
|
|
27675
|
-
let top = ar.bottom - hr.top + 6;
|
|
27676
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
27677
|
-
if (left < 4) left = 4;
|
|
27678
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
27679
|
-
if (top < 4) top = 4;
|
|
27680
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
27681
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
27682
|
-
this.menuEl = menu2;
|
|
28964
|
+
});
|
|
27683
28965
|
this.menuOwner = anchor;
|
|
27684
28966
|
}
|
|
27685
28967
|
closeMenu() {
|
|
27686
|
-
|
|
27687
|
-
this.menuEl.remove();
|
|
27688
|
-
this.menuEl = null;
|
|
27689
|
-
}
|
|
27690
|
-
this.menuOwner = null;
|
|
28968
|
+
this.menuPop?.hide();
|
|
27691
28969
|
}
|
|
27692
28970
|
/** A color control: an `icon` over a thin **colored underline** showing the current
|
|
27693
28971
|
* value (the common idiom), with an invisible native picker overlaid to edit it.
|
|
@@ -27726,36 +29004,18 @@ var DrawingSettingsPopup = class {
|
|
|
27726
29004
|
}
|
|
27727
29005
|
/** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
|
|
27728
29006
|
openColorPopover(anchor, color, onChange) {
|
|
27729
|
-
this.closeColorPopover();
|
|
27730
|
-
this.closeMenu();
|
|
27731
29007
|
const t = this.theme;
|
|
27732
|
-
|
|
27733
|
-
|
|
27734
|
-
|
|
27735
|
-
|
|
27736
|
-
|
|
27737
|
-
|
|
27738
|
-
|
|
27739
|
-
const hr = this.host.getBoundingClientRect();
|
|
27740
|
-
const w = pop.offsetWidth;
|
|
27741
|
-
const h = pop.offsetHeight;
|
|
27742
|
-
let left = ar.left - hr.left;
|
|
27743
|
-
let top = ar.bottom - hr.top + 6;
|
|
27744
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
27745
|
-
if (left < 4) left = 4;
|
|
27746
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
27747
|
-
if (top < 4) top = 4;
|
|
27748
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
27749
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
27750
|
-
this.colorPop = pop;
|
|
29008
|
+
this.colorPop = this.hostFloat(anchor, {
|
|
29009
|
+
zIndex: 25,
|
|
29010
|
+
padding: "10px",
|
|
29011
|
+
fill: (el) => {
|
|
29012
|
+
el.appendChild(buildColorPicker(color, t, onChange));
|
|
29013
|
+
}
|
|
29014
|
+
});
|
|
27751
29015
|
this.colorOwner = anchor;
|
|
27752
29016
|
}
|
|
27753
29017
|
closeColorPopover() {
|
|
27754
|
-
|
|
27755
|
-
this.colorPop.remove();
|
|
27756
|
-
this.colorPop = null;
|
|
27757
|
-
}
|
|
27758
|
-
this.colorOwner = null;
|
|
29018
|
+
this.colorPop?.hide();
|
|
27759
29019
|
}
|
|
27760
29020
|
divider() {
|
|
27761
29021
|
const d = document.createElement("div");
|
|
@@ -28138,9 +29398,9 @@ var UserDrawingController = class {
|
|
|
28138
29398
|
}
|
|
28139
29399
|
/**
|
|
28140
29400
|
* Union of the visible drawings' price ranges on `paneId` whose time extent
|
|
28141
|
-
* intersects [fromTime, toTime]
|
|
28142
|
-
*
|
|
28143
|
-
*
|
|
29401
|
+
* intersects [fromTime, toTime]. Hidden drawings and full-width references
|
|
29402
|
+
* (hline → null extent) are handled too. Kept as the seam for a future
|
|
29403
|
+
* per-drawing autoscale opt-in — the renderer does not fold this in today.
|
|
28144
29404
|
*/
|
|
28145
29405
|
priceRangeForPane(paneId, fromTime, toTime) {
|
|
28146
29406
|
let lo = Infinity;
|
|
@@ -28496,6 +29756,16 @@ var UserDrawingController = class {
|
|
|
28496
29756
|
if (this.eraserMode) return "pointer";
|
|
28497
29757
|
return this.interaction.cursorAt(x, y);
|
|
28498
29758
|
}
|
|
29759
|
+
/** Right-click while placing: cancel the in-progress drawing and revert to the
|
|
29760
|
+
* pointer — the gesture is an explicit escape, so it disarms even in
|
|
29761
|
+
* stay-in-drawing-mode (where Escape would leave the tool armed). Returns whether
|
|
29762
|
+
* the press was consumed; false lets the host's context menu open normally. */
|
|
29763
|
+
cancelPlacement() {
|
|
29764
|
+
if (!this.interaction.isPlacing()) return false;
|
|
29765
|
+
this.interaction.cancel();
|
|
29766
|
+
if (this.activeTool != null) this.emit({ kind: "arm", type: null });
|
|
29767
|
+
return true;
|
|
29768
|
+
}
|
|
28499
29769
|
/** Double-click over a drawing → suppress the chart's view reset (single-click already
|
|
28500
29770
|
* opens settings). Returns true only when a drawing is under the cursor. */
|
|
28501
29771
|
dblClick(x, y) {
|
|
@@ -28980,6 +30250,20 @@ var VolumeRenderer = class {
|
|
|
28980
30250
|
};
|
|
28981
30251
|
|
|
28982
30252
|
// src/renderers/native/layers.ts
|
|
30253
|
+
function foldBaseModulation(acc, next) {
|
|
30254
|
+
if (next == null) return acc;
|
|
30255
|
+
if (acc == null) return { ...next };
|
|
30256
|
+
return {
|
|
30257
|
+
candleBodyScale: minOpt(acc.candleBodyScale, next.candleBodyScale),
|
|
30258
|
+
candleBodyAlpha: minOpt(acc.candleBodyAlpha, next.candleBodyAlpha),
|
|
30259
|
+
gridAlpha: minOpt(acc.gridAlpha, next.gridAlpha)
|
|
30260
|
+
};
|
|
30261
|
+
}
|
|
30262
|
+
function minOpt(a, b) {
|
|
30263
|
+
if (a == null) return b;
|
|
30264
|
+
if (b == null) return a;
|
|
30265
|
+
return Math.min(a, b);
|
|
30266
|
+
}
|
|
28983
30267
|
var registry4 = /* @__PURE__ */ new Map();
|
|
28984
30268
|
function rendererLayers() {
|
|
28985
30269
|
return [...registry4.values()];
|
|
@@ -29489,7 +30773,7 @@ var NativeRenderer = class {
|
|
|
29489
30773
|
this.scrollTargetRO = null;
|
|
29490
30774
|
// eased rightOffset target while gliding back to the latest bars
|
|
29491
30775
|
// Distance (px) from the plot's bottom edge to the button — tracks the bottom-most EXPANDED
|
|
29492
|
-
// pane
|
|
30776
|
+
// pane: when the lower sub-panes collapse it rides up into the open pane.
|
|
29493
30777
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM;
|
|
29494
30778
|
// Distance (px) from the plot's right edge — clears the FULL scale gutter, which widens when a
|
|
29495
30779
|
// pane carries merged (own-scale) columns; the constant only clears a single-column scale.
|
|
@@ -30456,6 +31740,7 @@ var NativeRenderer = class {
|
|
|
30456
31740
|
drawingsClaim: (x, y) => this.userDrawings?.claim(x, y) ?? false,
|
|
30457
31741
|
drawingsMeasureStart: (x, y) => this.userDrawings?.beginMeasureAt(x, y) ?? false,
|
|
30458
31742
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
31743
|
+
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
30459
31744
|
drawingsSnapMode: () => this.snapMode,
|
|
30460
31745
|
drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
|
|
30461
31746
|
drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
|
|
@@ -30707,6 +31992,8 @@ var NativeRenderer = class {
|
|
|
30707
31992
|
this.attributionEl = null;
|
|
30708
31993
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
30709
31994
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
31995
|
+
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
31996
|
+
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
30710
31997
|
this.mountContainer = null;
|
|
30711
31998
|
this.wrapper?.remove();
|
|
30712
31999
|
}
|
|
@@ -31796,19 +33083,18 @@ var NativeRenderer = class {
|
|
|
31796
33083
|
theme: this.theme
|
|
31797
33084
|
});
|
|
31798
33085
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
33086
|
+
let folded = null;
|
|
31799
33087
|
for (const l of this.extLayers) {
|
|
31800
33088
|
const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
|
|
31801
33089
|
l.instance.render(args);
|
|
31802
|
-
|
|
31803
|
-
const mod = l.instance.modulateBase(args);
|
|
31804
|
-
if (mod) {
|
|
31805
|
-
if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
|
|
31806
|
-
if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
|
|
31807
|
-
if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
|
|
31808
|
-
}
|
|
31809
|
-
}
|
|
33090
|
+
folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
31810
33091
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
31811
33092
|
}
|
|
33093
|
+
if (folded) {
|
|
33094
|
+
if (folded.candleBodyScale != null) this.backend.candleBodyScale = clamp012(folded.candleBodyScale) || 0.01;
|
|
33095
|
+
if (folded.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(folded.candleBodyAlpha) * this.candleBodyAlpha;
|
|
33096
|
+
if (folded.gridAlpha != null) this.backend.gridAlpha = clamp012(folded.gridAlpha);
|
|
33097
|
+
}
|
|
31812
33098
|
}
|
|
31813
33099
|
const li = this.bars.length - 1;
|
|
31814
33100
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
@@ -31935,7 +33221,6 @@ var NativeRenderer = class {
|
|
|
31935
33221
|
const n = this.coords.barCount;
|
|
31936
33222
|
if (n === 0) return;
|
|
31937
33223
|
const vr = this.coords.visibleLogicalRange();
|
|
31938
|
-
const vtr = this.coords.visibleTimeRange();
|
|
31939
33224
|
const i0 = Math.max(0, Math.floor(vr.from));
|
|
31940
33225
|
const i1 = Math.min(n - 1, Math.ceil(vr.to));
|
|
31941
33226
|
if (i1 < i0) return;
|
|
@@ -31953,9 +33238,7 @@ var NativeRenderer = class {
|
|
|
31953
33238
|
}
|
|
31954
33239
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
31955
33240
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
31956
|
-
|
|
31957
|
-
const udr = this.userDrawings?.priceRangeForPane(pane.id, vtr.from, vtr.to) ?? null;
|
|
31958
|
-
if (udr) dr = dr ? { min: Math.min(dr.min, udr.min), max: Math.max(dr.max, udr.max) } : udr;
|
|
33241
|
+
const dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
31959
33242
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
31960
33243
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
31961
33244
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -32200,6 +33483,7 @@ var NativeRenderer = class {
|
|
|
32200
33483
|
this.paneControls?.reposition();
|
|
32201
33484
|
this.repositionScrollButton();
|
|
32202
33485
|
this.positionAttribution();
|
|
33486
|
+
this.publishPricePaneBounds();
|
|
32203
33487
|
return;
|
|
32204
33488
|
}
|
|
32205
33489
|
const collapsed = panes.filter((p) => p.collapsed);
|
|
@@ -32217,9 +33501,10 @@ var NativeRenderer = class {
|
|
|
32217
33501
|
this.paneControls?.reposition();
|
|
32218
33502
|
this.repositionScrollButton();
|
|
32219
33503
|
this.positionAttribution();
|
|
33504
|
+
this.publishPricePaneBounds();
|
|
32220
33505
|
}
|
|
32221
|
-
/** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area
|
|
32222
|
-
*
|
|
33506
|
+
/** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area:
|
|
33507
|
+
* with all lower sub-panes collapsed it settles into the lowest open pane. */
|
|
32223
33508
|
repositionScrollButton() {
|
|
32224
33509
|
const dataHeight = this.coords.height;
|
|
32225
33510
|
if (dataHeight <= 0) return;
|
|
@@ -32263,6 +33548,21 @@ var NativeRenderer = class {
|
|
|
32263
33548
|
this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
|
|
32264
33549
|
this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
|
|
32265
33550
|
}
|
|
33551
|
+
/** Publish the price pane's vertical insets as `--vela-price-pane-top` /
|
|
33552
|
+
* `--vela-price-pane-bottom` on the mount container, so the symbol watermark
|
|
33553
|
+
* (and any other host overlay) can clip to the price pane instead of spanning
|
|
33554
|
+
* study panes and the time axis. A maximized study pane collapses the box to
|
|
33555
|
+
* zero height — the mark does not appear on a study. */
|
|
33556
|
+
publishPricePaneBounds() {
|
|
33557
|
+
if (!this.mountContainer) return;
|
|
33558
|
+
const plotH = this.coords.height + TIME_AXIS_H;
|
|
33559
|
+
const price = this.scene.panes.get(PRICE_PANE_ID2);
|
|
33560
|
+
const top = price ? price.bounds.top : 0;
|
|
33561
|
+
const height = price ? price.bounds.height : this.coords.height;
|
|
33562
|
+
const bottom = Math.max(0, plotH - (top + height));
|
|
33563
|
+
this.mountContainer.style.setProperty("--vela-price-pane-top", `${top}px`);
|
|
33564
|
+
this.mountContainer.style.setProperty("--vela-price-pane-bottom", `${bottom}px`);
|
|
33565
|
+
}
|
|
32266
33566
|
/** The built-in mark, or the host's own when one is set. */
|
|
32267
33567
|
buildAttributionEl() {
|
|
32268
33568
|
return this.attributionHtml !== null ? createCustomMark(document, this.attributionHtml, this.theme.background) : createAttributionMark(document, this.theme.background);
|
|
@@ -32946,16 +34246,16 @@ function fmtPrice(n, dp) {
|
|
|
32946
34246
|
const d = dp ?? decimalsFor(n);
|
|
32947
34247
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
32948
34248
|
}
|
|
32949
|
-
function fmtChange(
|
|
32950
|
-
if (
|
|
32951
|
-
const diff = close -
|
|
32952
|
-
const pct = diff /
|
|
34249
|
+
function fmtChange(open2, close) {
|
|
34250
|
+
if (open2 == null || close == null || !Number.isFinite(open2) || !Number.isFinite(close) || open2 === 0) return "";
|
|
34251
|
+
const diff = close - open2;
|
|
34252
|
+
const pct = diff / open2 * 100;
|
|
32953
34253
|
const sign = diff >= 0 ? "+" : "";
|
|
32954
34254
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
32955
34255
|
}
|
|
32956
34256
|
|
|
32957
34257
|
// src/widget/statusline.ts
|
|
32958
|
-
var
|
|
34258
|
+
var STYLE_ID24 = "vela-widget-statusline";
|
|
32959
34259
|
var CSS21 = `
|
|
32960
34260
|
.vela-statusline {
|
|
32961
34261
|
position: absolute;
|
|
@@ -33147,7 +34447,7 @@ var Statusline = class {
|
|
|
33147
34447
|
this.fitMode = false;
|
|
33148
34448
|
this.fitRO = null;
|
|
33149
34449
|
const doc = host.ownerDocument;
|
|
33150
|
-
injectStyles(
|
|
34450
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
33151
34451
|
host.classList.add("vela-has-statusline");
|
|
33152
34452
|
this.el = doc.createElement("div");
|
|
33153
34453
|
this.el.className = "vela-statusline";
|
|
@@ -33322,21 +34622,21 @@ var Statusline = class {
|
|
|
33322
34622
|
var MAX_FONT_PX = 36;
|
|
33323
34623
|
var MIN_FONT_PX = 12;
|
|
33324
34624
|
var FILL = 0.9;
|
|
33325
|
-
var
|
|
34625
|
+
var STYLE_ID25 = "vela-widget-watermark";
|
|
33326
34626
|
var CSS22 = `
|
|
33327
34627
|
.vela-watermark {
|
|
33328
34628
|
position: absolute;
|
|
33329
|
-
/* Insets follow the renderer-published gutters
|
|
33330
|
-
*
|
|
33331
|
-
*
|
|
33332
|
-
|
|
33333
|
-
|
|
33334
|
-
bottom: 0;
|
|
34629
|
+
/* Insets follow the renderer-published gutters AND the price-pane vertical
|
|
34630
|
+
* bounds (mount container), so the mark centers on the PRICE PANE \u2014 never
|
|
34631
|
+
* the study panes below, the drawings toolbar, or the price scale. */
|
|
34632
|
+
top: var(--vela-price-pane-top, 0px);
|
|
34633
|
+
bottom: var(--vela-price-pane-bottom, 0px);
|
|
33335
34634
|
left: var(--vela-toolbar-gutter, 0px);
|
|
33336
34635
|
right: var(--vela-scale-gutter, 0px);
|
|
33337
34636
|
display: flex;
|
|
33338
34637
|
align-items: center;
|
|
33339
34638
|
justify-content: center;
|
|
34639
|
+
overflow: hidden;
|
|
33340
34640
|
pointer-events: none;
|
|
33341
34641
|
z-index: 1;
|
|
33342
34642
|
color: var(--vela-fg);
|
|
@@ -33361,7 +34661,7 @@ var Watermark = class {
|
|
|
33361
34661
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
33362
34662
|
* fires during chart construction, before any subscriber can see it. */
|
|
33363
34663
|
this.loading = true;
|
|
33364
|
-
injectStyles(
|
|
34664
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
33365
34665
|
this.el = host.ownerDocument.createElement("div");
|
|
33366
34666
|
this.el.className = "vela-watermark";
|
|
33367
34667
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -33391,7 +34691,7 @@ var Watermark = class {
|
|
|
33391
34691
|
this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
|
|
33392
34692
|
this.fit();
|
|
33393
34693
|
}
|
|
33394
|
-
/** Measure the text at the cap and shrink it to the
|
|
34694
|
+
/** Measure the text at the cap and shrink it to the price pane's width. */
|
|
33395
34695
|
fit() {
|
|
33396
34696
|
if (this.el.clientWidth <= 0 || !this.text.textContent) return;
|
|
33397
34697
|
this.el.style.fontSize = `${MAX_FONT_PX}px`;
|
|
@@ -34164,7 +35464,7 @@ function buildContext(host) {
|
|
|
34164
35464
|
setTimeframe: (tf) => host.active()?.setTimeframe(tf),
|
|
34165
35465
|
setPriceStyle: (style) => host.active()?.setPriceStyle(style),
|
|
34166
35466
|
openSymbolSearch: (query) => host.openSymbolSearch(query),
|
|
34167
|
-
togglePanel: (id,
|
|
35467
|
+
togglePanel: (id, open2) => host.togglePanel(id, open2),
|
|
34168
35468
|
host: host.root,
|
|
34169
35469
|
toast: (message, kind) => host.toast(message, kind),
|
|
34170
35470
|
cells: host.cells().map((c) => ({ id: c.id, chart: c.chart, symbol: c.symbol, timeframe: c.timeframe })),
|
|
@@ -34424,7 +35724,7 @@ var GAP_PX = 2;
|
|
|
34424
35724
|
var POOL_CAP = 16;
|
|
34425
35725
|
var TIME_AXIS_H3 = 22;
|
|
34426
35726
|
var ALERT_CAP = 50;
|
|
34427
|
-
var
|
|
35727
|
+
var STYLE_ID26 = "vela-workspace";
|
|
34428
35728
|
var CSS23 = `
|
|
34429
35729
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
34430
35730
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
@@ -34495,6 +35795,8 @@ var VelaWorkspace = class {
|
|
|
34495
35795
|
this.historyUnsub = null;
|
|
34496
35796
|
/** Favorite drawing tools — a WORKSPACE preference (one star set, every cell). */
|
|
34497
35797
|
this.favs = [];
|
|
35798
|
+
/** Favorite timeframes — the shared topbar's quick-switch chips, one set for the grid. */
|
|
35799
|
+
this.tfFavs = [];
|
|
34498
35800
|
/** Live sync configuration (mutable copy of the option). */
|
|
34499
35801
|
this.syncOpts = {};
|
|
34500
35802
|
this.stateTimer = null;
|
|
@@ -34558,6 +35860,7 @@ var VelaWorkspace = class {
|
|
|
34558
35860
|
}
|
|
34559
35861
|
this.timezone = boot?.timezone ?? opts.timezone ?? "Etc/UTC";
|
|
34560
35862
|
if (boot?.favorites) this.favs = [...boot.favorites];
|
|
35863
|
+
if (boot?.timeframeFavorites) this.tfFavs = [...boot.timeframeFavorites];
|
|
34561
35864
|
const sync = boot?.sync ?? opts.sync;
|
|
34562
35865
|
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) {
|
|
34563
35866
|
this.applySyncSetting(kind, sync?.[kind]);
|
|
@@ -34568,7 +35871,7 @@ var VelaWorkspace = class {
|
|
|
34568
35871
|
this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
|
|
34569
35872
|
const bootActive = boot?.activeCellId ?? null;
|
|
34570
35873
|
const doc = hostEl.ownerDocument;
|
|
34571
|
-
injectStyles(
|
|
35874
|
+
injectStyles(STYLE_ID26, CSS23, doc);
|
|
34572
35875
|
this.root = doc.createElement("div");
|
|
34573
35876
|
this.root.className = "vela-workspace";
|
|
34574
35877
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
@@ -34579,9 +35882,9 @@ var VelaWorkspace = class {
|
|
|
34579
35882
|
this.symbolPicker = new SymbolPicker({
|
|
34580
35883
|
host: this.root,
|
|
34581
35884
|
onSelect: (ticker) => this.active.setSymbol(ticker),
|
|
34582
|
-
onOpenChange: (
|
|
34583
|
-
if (
|
|
34584
|
-
this.trackDialog(
|
|
35885
|
+
onOpenChange: (open2) => {
|
|
35886
|
+
if (open2) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
|
|
35887
|
+
this.trackDialog(open2);
|
|
34585
35888
|
}
|
|
34586
35889
|
});
|
|
34587
35890
|
this.symbolPicker.setSource(() => this.feed.symbols());
|
|
@@ -34591,16 +35894,16 @@ var VelaWorkspace = class {
|
|
|
34591
35894
|
onChart: () => this.active.onChartRows(),
|
|
34592
35895
|
onAdd: (i) => this.active.addFromLibrary(i),
|
|
34593
35896
|
onRemove: (i) => this.active.removeFromChart(i),
|
|
34594
|
-
onOpenChange: (
|
|
34595
|
-
if (
|
|
34596
|
-
this.trackDialog(
|
|
35897
|
+
onOpenChange: (open2) => {
|
|
35898
|
+
if (open2) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
|
|
35899
|
+
this.trackDialog(open2);
|
|
34597
35900
|
}
|
|
34598
35901
|
}) : null;
|
|
34599
35902
|
const picker = this.indicatorPicker;
|
|
34600
35903
|
this.tfQuick = new TimeframeQuick({
|
|
34601
35904
|
host: this.root,
|
|
34602
35905
|
onApply: (tf) => this.setActiveTimeframe(tf),
|
|
34603
|
-
onOpenChange: (
|
|
35906
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34604
35907
|
});
|
|
34605
35908
|
this.topbar = new Topbar(this.root, {
|
|
34606
35909
|
symbol: "",
|
|
@@ -34612,8 +35915,10 @@ var VelaWorkspace = class {
|
|
|
34612
35915
|
onAlertsClick: (anchor) => this.openAlertsMenu(anchor),
|
|
34613
35916
|
timeframe: "60",
|
|
34614
35917
|
timeframes: opts.timeframes ?? DEFAULT_TIMEFRAMES,
|
|
35918
|
+
timeframeFavorites: this.tfFavs,
|
|
34615
35919
|
priceStyle: "candles",
|
|
34616
35920
|
onTimeframe: (tf) => this.setActiveTimeframe(tf),
|
|
35921
|
+
onTimeframeFavorite: (tf, on) => this.setTimeframeFavorite(tf, on),
|
|
34617
35922
|
onPriceStyle: (style) => this.active.setPriceStyle(style),
|
|
34618
35923
|
layout: {
|
|
34619
35924
|
current: this.def.id,
|
|
@@ -34827,7 +36132,7 @@ var VelaWorkspace = class {
|
|
|
34827
36132
|
cells: () => this.cells(),
|
|
34828
36133
|
setActiveCell: (id) => this.setActiveCell(id),
|
|
34829
36134
|
openSymbolSearch: (query) => this.symbolPicker.open(query ?? ""),
|
|
34830
|
-
togglePanel: (id,
|
|
36135
|
+
togglePanel: (id, open2) => this.dock.toggle(id, open2),
|
|
34831
36136
|
root: this.root,
|
|
34832
36137
|
toast: (message, kind) => this.toast.show(message, kind)
|
|
34833
36138
|
});
|
|
@@ -34864,6 +36169,7 @@ var VelaWorkspace = class {
|
|
|
34864
36169
|
const state = { version: 1, layout: this.def.id, timezone: this.timezone, sync: { ...this.syncOpts }, charts };
|
|
34865
36170
|
if (this.activeId) state.activeCellId = this.activeId;
|
|
34866
36171
|
if (this.favs.length > 0) state.favorites = [...this.favs];
|
|
36172
|
+
if (this.tfFavs.length > 0) state.timeframeFavorites = [...this.tfFavs];
|
|
34867
36173
|
if (this.trackSizes.size > 0) state.trackSizes = Object.fromEntries([...this.trackSizes].map(([k, v]) => [k, { ...v }]));
|
|
34868
36174
|
const panels2 = this.dock.getState();
|
|
34869
36175
|
if (panels2) state.panels = panels2;
|
|
@@ -34884,6 +36190,10 @@ var VelaWorkspace = class {
|
|
|
34884
36190
|
this.bottombar?.setTimezone(st.timezone);
|
|
34885
36191
|
}
|
|
34886
36192
|
if (st.favorites) this.favs = [...st.favorites];
|
|
36193
|
+
if (st.timeframeFavorites) {
|
|
36194
|
+
this.tfFavs = [...st.timeframeFavorites];
|
|
36195
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
36196
|
+
}
|
|
34887
36197
|
this.dock.applyState(st.panels);
|
|
34888
36198
|
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
34889
36199
|
this.trackSizes.clear();
|
|
@@ -35459,6 +36769,14 @@ var VelaWorkspace = class {
|
|
|
35459
36769
|
this.bottombar?.setActiveRange(null);
|
|
35460
36770
|
this.active.setTimeframe(tf);
|
|
35461
36771
|
}
|
|
36772
|
+
/** Star/unstar a timeframe — a WORKSPACE preference (one chip row, whatever the
|
|
36773
|
+
* active cell); the topbar follows and the set persists with the document. */
|
|
36774
|
+
setTimeframeFavorite(tf, on) {
|
|
36775
|
+
if (on === this.tfFavs.includes(tf)) return;
|
|
36776
|
+
this.tfFavs = on ? [tf, ...this.tfFavs] : this.tfFavs.filter((f) => f !== tf);
|
|
36777
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
36778
|
+
this.markStateDirty();
|
|
36779
|
+
}
|
|
35462
36780
|
/** The mode flipped (container resized across the breakpoint, or a coarse-pointer
|
|
35463
36781
|
* change). Close the open surfaces — a desktop card must not linger over the
|
|
35464
36782
|
* mobile chrome (and vice versa) — and re-present every cell's own chrome. */
|
|
@@ -35503,7 +36821,7 @@ var VelaWorkspace = class {
|
|
|
35503
36821
|
this.active.applyRange(preset);
|
|
35504
36822
|
this.bottombar?.setActiveRange(preset.id);
|
|
35505
36823
|
},
|
|
35506
|
-
onOpenChange: (
|
|
36824
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35507
36825
|
}));
|
|
35508
36826
|
this.tfDrawer.open();
|
|
35509
36827
|
}
|
|
@@ -35516,7 +36834,7 @@ var VelaWorkspace = class {
|
|
|
35516
36834
|
isFavorite: (type) => this.active.chart.drawings.isFavorite(type),
|
|
35517
36835
|
onFavorite: (type, on) => this.active.chart.drawings.setFavorite(type, on),
|
|
35518
36836
|
onSelect: (type) => this.active.chart.drawings.setTool(type),
|
|
35519
|
-
onOpenChange: (
|
|
36837
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35520
36838
|
}));
|
|
35521
36839
|
this.drawingsDrawer.open();
|
|
35522
36840
|
}
|
|
@@ -35555,7 +36873,7 @@ var VelaWorkspace = class {
|
|
|
35555
36873
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
35556
36874
|
}
|
|
35557
36875
|
},
|
|
35558
|
-
onOpenChange: (
|
|
36876
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35559
36877
|
}));
|
|
35560
36878
|
this.moreDrawer.open();
|
|
35561
36879
|
}
|
|
@@ -35564,7 +36882,7 @@ var VelaWorkspace = class {
|
|
|
35564
36882
|
host: this.root,
|
|
35565
36883
|
timezone: () => this.timezone,
|
|
35566
36884
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
35567
|
-
onOpenChange: (
|
|
36885
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35568
36886
|
}));
|
|
35569
36887
|
this.timezoneDrawer.open();
|
|
35570
36888
|
}
|
|
@@ -35574,7 +36892,7 @@ var VelaWorkspace = class {
|
|
|
35574
36892
|
host: this.root,
|
|
35575
36893
|
chart: () => this.activeId ? this.cellsById.get(this.activeId)?.chart ?? null : null,
|
|
35576
36894
|
pressY: () => this.priceScalePressY,
|
|
35577
|
-
onOpenChange: (
|
|
36895
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35578
36896
|
}));
|
|
35579
36897
|
this.priceScaleDrawer.open();
|
|
35580
36898
|
}
|
|
@@ -35658,7 +36976,7 @@ var VelaWorkspace = class {
|
|
|
35658
36976
|
label: "Show this shortcuts panel",
|
|
35659
36977
|
category: "Help",
|
|
35660
36978
|
run: () => {
|
|
35661
|
-
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (
|
|
36979
|
+
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open2) => this.trackDialog(open2)));
|
|
35662
36980
|
this.shortcutsHelp.open();
|
|
35663
36981
|
}
|
|
35664
36982
|
});
|
|
@@ -35677,9 +36995,9 @@ var VelaWorkspace = class {
|
|
|
35677
36995
|
this.tfQuick.open(key);
|
|
35678
36996
|
}
|
|
35679
36997
|
}
|
|
35680
|
-
trackDialog(
|
|
35681
|
-
this.openDialogs = Math.max(0, this.openDialogs + (
|
|
35682
|
-
if (
|
|
36998
|
+
trackDialog(open2) {
|
|
36999
|
+
this.openDialogs = Math.max(0, this.openDialogs + (open2 ? 1 : -1));
|
|
37000
|
+
if (open2) this.keymap.pushScope("dialog");
|
|
35683
37001
|
else this.keymap.popScope("dialog");
|
|
35684
37002
|
}
|
|
35685
37003
|
/** Pool a dehydrated slot state (bounded — oldest entries drop past the cap). */
|