@luxalgo/vela 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/{DataProvider-gnzv-dgc.d.cts → DataProvider-Ce6PuCzQ.d.cts} +10 -1
- package/dist/{DataProvider-DTOEXKTI.d.ts → DataProvider-DNx9ntBD.d.ts} +10 -1
- package/dist/{chunk-EY63XBFZ.js → chunk-7ROONW6H.js} +132 -23
- package/dist/{chunk-L5NTMWPP.js → chunk-CA3N3PYT.js} +785 -857
- package/dist/{chunk-LJLZR44Y.js → chunk-EMS6PO2T.js} +5 -7
- package/dist/chunk-OGRQW3M6.js +1328 -0
- package/dist/{chunk-P556H6EA.js → chunk-QWIEKZRE.js} +59 -34
- package/dist/{chunk-WJKLBYRM.js → chunk-ZNL2X6U2.js} +1 -1
- package/dist/{contributions-Ci_i3IN2.d.cts → contributions-DToMhiw3.d.ts} +9 -2
- package/dist/{contributions-b6AVrqqG.d.ts → contributions-Dlc7ZWZz.d.cts} +9 -2
- package/dist/{history-Ci3M0xLV.d.ts → history-BZADxpKx.d.cts} +5 -3
- package/dist/{history-Cf1lVsap.d.cts → history-BuHXJZJ2.d.ts} +5 -3
- package/dist/index.cjs +2761 -1597
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- 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-DlidwMj6.d.cts → plugin-BNPLOQO1.d.cts} +3 -3
- package/dist/{plugin-DP6A4t4o.d.ts → plugin-BqjEa5r3.d.ts} +3 -3
- 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 +3729 -2565
- package/dist/vela.global.min.js +315 -66
- package/dist/widget.cjs +2359 -1054
- package/dist/widget.d.cts +30 -16
- package/dist/widget.d.ts +30 -16
- package/dist/widget.js +37 -14
- package/dist/workspace.cjs +2259 -944
- package/dist/workspace.d.cts +10 -5
- package/dist/workspace.d.ts +10 -5
- package/dist/workspace.js +29 -8
- package/package.json +1 -1
- package/dist/chunk-ATPU5CI6.js +0 -81
package/dist/workspace.cjs
CHANGED
|
@@ -152,7 +152,7 @@ var ProviderRegistry = class {
|
|
|
152
152
|
/** Register (or replace) a provider; returns a promise that settles when its index is built. */
|
|
153
153
|
register(rawName, provider) {
|
|
154
154
|
const name = normName(rawName);
|
|
155
|
-
const entry = { name, provider, index: null, descriptors: [], settled: false, settle: Promise.resolve() };
|
|
155
|
+
const entry = { name, provider, index: null, prefixIndex: null, byTicker: null, descriptors: [], settled: false, settle: Promise.resolve() };
|
|
156
156
|
this.entries.set(name, entry);
|
|
157
157
|
entry.settle = this.buildIndex(entry);
|
|
158
158
|
queueMicrotask(() => this.notify(false));
|
|
@@ -165,6 +165,9 @@ var ProviderRegistry = class {
|
|
|
165
165
|
const symbols = await entry.provider.listSymbols();
|
|
166
166
|
entry.descriptors = symbols;
|
|
167
167
|
entry.index = new Set(symbols.map((s) => normTicker(s.ticker)));
|
|
168
|
+
entry.byTicker = new Map(symbols.map((s) => [normTicker(s.ticker), s]));
|
|
169
|
+
entry.prefixIndex = /* @__PURE__ */ new Map();
|
|
170
|
+
for (const s of symbols) if (s.prefix) entry.prefixIndex.set(`${normName(s.prefix)}:${normTicker(s.ticker)}`, s);
|
|
168
171
|
}
|
|
169
172
|
} catch {
|
|
170
173
|
entry.index = null;
|
|
@@ -200,14 +203,25 @@ var ProviderRegistry = class {
|
|
|
200
203
|
}
|
|
201
204
|
/**
|
|
202
205
|
* Resolve a symbol to `{ provider, ticker }`, or null when nothing can serve it
|
|
203
|
-
* yet. Explicit prefix → that provider (must be registered)
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
206
|
+
* yet. Explicit prefix → that provider (must be registered), else a provider whose
|
|
207
|
+
* index declares that LISTING prefix for that ticker (`NASDAQ:AAPL` routes to the
|
|
208
|
+
* provider serving Nasdaq-listed AAPL — TradingView-strict, so `NYSE:AAPL` resolves
|
|
209
|
+
* to nothing rather than auto-correcting). Bare → first indexed provider
|
|
210
|
+
* (registration order, `opts.default` first) that contains the ticker;
|
|
211
|
+
* `opts.lenient` additionally allows a sole registered provider before its index
|
|
212
|
+
* exists.
|
|
207
213
|
*/
|
|
208
214
|
resolve(raw, opts = {}) {
|
|
209
215
|
const { provider, ticker } = this.parse(raw);
|
|
210
|
-
if (provider)
|
|
216
|
+
if (provider) {
|
|
217
|
+
if (this.entries.has(provider)) return { provider, ticker };
|
|
218
|
+
const key = `${provider}:${normTicker(ticker)}`;
|
|
219
|
+
for (const name of this.candidateOrder(opts.default)) {
|
|
220
|
+
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
221
|
+
if (d) return { provider: name, ticker: d.ticker };
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
211
225
|
const norm = normTicker(ticker);
|
|
212
226
|
for (const name of this.candidateOrder(opts.default)) {
|
|
213
227
|
if (this.entries.get(name).index?.has(norm)) return { provider: name, ticker };
|
|
@@ -273,6 +287,22 @@ var ProviderRegistry = class {
|
|
|
273
287
|
}
|
|
274
288
|
return names;
|
|
275
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* The DISPLAY prefix for a resolved symbol — the descriptor's LISTING prefix when the
|
|
292
|
+
* data declares one, else the provider name. This is the single seam every label
|
|
293
|
+
* (legend chip, committed/persisted `PREFIX:TICKER` strings) derives from, which is
|
|
294
|
+
* what makes a non-canonical spelling impossible to display: the form always comes
|
|
295
|
+
* from the data, never from what was typed.
|
|
296
|
+
*/
|
|
297
|
+
displayPrefixOf(resolved) {
|
|
298
|
+
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
299
|
+
return d?.prefix ?? resolved.provider;
|
|
300
|
+
}
|
|
301
|
+
/** The canonical `PREFIX:TICKER` string for a resolved symbol (descriptor spellings). */
|
|
302
|
+
canonicalSymbol(resolved) {
|
|
303
|
+
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
304
|
+
return `${d?.prefix ?? resolved.provider}:${d?.ticker ?? resolved.ticker}`;
|
|
305
|
+
}
|
|
276
306
|
/** Indexed symbols for one provider (or all, concatenated) — for autocomplete. */
|
|
277
307
|
symbolsOf(rawName) {
|
|
278
308
|
if (rawName != null) return this.entries.get(normName(rawName))?.descriptors ?? [];
|
|
@@ -596,6 +626,20 @@ var MultiProviderFeed = class {
|
|
|
596
626
|
resolveSymbol(raw) {
|
|
597
627
|
return this.registry.resolve(raw, { default: this.primaryProvider });
|
|
598
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* The DISPLAY prefix for `raw` — the descriptor's LISTING prefix when the data
|
|
631
|
+
* declares one (`NASDAQ` for AAPL), else the resolved provider name. Null while
|
|
632
|
+
* nothing resolves the symbol.
|
|
633
|
+
*/
|
|
634
|
+
displayPrefix(raw) {
|
|
635
|
+
const resolved = this.resolveSymbol(raw);
|
|
636
|
+
return resolved ? this.registry.displayPrefixOf(resolved) : null;
|
|
637
|
+
}
|
|
638
|
+
/** The canonical `PREFIX:TICKER` form of `raw`, or null while unresolvable. */
|
|
639
|
+
canonicalSymbol(raw) {
|
|
640
|
+
const resolved = this.resolveSymbol(raw);
|
|
641
|
+
return resolved ? this.registry.canonicalSymbol(resolved) : null;
|
|
642
|
+
}
|
|
599
643
|
/** The registered provider INSTANCE under `name` (undefined if unknown). */
|
|
600
644
|
providerInstance(name) {
|
|
601
645
|
return this.registry.get(name);
|
|
@@ -805,12 +849,6 @@ function mix(base, top, alpha) {
|
|
|
805
849
|
const c = (x, y) => Math.round(x + (y - x) * alpha);
|
|
806
850
|
return `rgb(${c(b.r, t.r)},${c(b.g, t.g)},${c(b.b, t.b)})`;
|
|
807
851
|
}
|
|
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
852
|
function isDarkColor(color) {
|
|
815
853
|
const c = parseRgb(color);
|
|
816
854
|
if (!c) return true;
|
|
@@ -840,6 +878,9 @@ var STATIC_TOKENS = {
|
|
|
840
878
|
"--vela-z-tooltip": "60",
|
|
841
879
|
"--vela-z-menu": "50",
|
|
842
880
|
"--vela-z-dialog": "40",
|
|
881
|
+
// Form popovers portal to <body> (or a chart host) and must sit above a dialog
|
|
882
|
+
// whose own stacking context may be nested inside the chart container.
|
|
883
|
+
"--vela-z-popover": "6000",
|
|
843
884
|
"--vela-ease": "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
844
885
|
"--vela-dur-fast": "90ms",
|
|
845
886
|
"--vela-dur-med": "160ms",
|
|
@@ -987,6 +1028,10 @@ registerIcon(
|
|
|
987
1028
|
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
1029
|
);
|
|
989
1030
|
registerIcon("clock", S('<circle cx="8" cy="8" r="6.2"/><path d="M8 4.8V8l2.4 1.6"/>'));
|
|
1031
|
+
registerIcon(
|
|
1032
|
+
"calendar",
|
|
1033
|
+
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"/>')
|
|
1034
|
+
);
|
|
990
1035
|
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
1036
|
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
1037
|
registerIcon(
|
|
@@ -1341,7 +1386,10 @@ function menuController(opts) {
|
|
|
1341
1386
|
props: {
|
|
1342
1387
|
id: opts.id ?? nextUid("vela-menu"),
|
|
1343
1388
|
ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
|
|
1344
|
-
positioning: {
|
|
1389
|
+
positioning: {
|
|
1390
|
+
placement: opts.placement ?? "bottom-start",
|
|
1391
|
+
...opts.getAnchorRect ? { getAnchorRect: () => opts.getAnchorRect() } : {}
|
|
1392
|
+
},
|
|
1345
1393
|
onSelect: (d) => opts.onSelect?.(d.value),
|
|
1346
1394
|
onOpenChange: (d) => opts.onOpenChange?.(d.open)
|
|
1347
1395
|
},
|
|
@@ -1423,6 +1471,22 @@ var MENU_CSS = `
|
|
|
1423
1471
|
}
|
|
1424
1472
|
.vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
|
|
1425
1473
|
.vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
|
|
1474
|
+
/* Favorite star (rows carrying \`favorite\`): reserved space always \u2014 it fades in on row
|
|
1475
|
+
hover instead of shifting the layout \u2014 and a starred row keeps its filled star visible.
|
|
1476
|
+
Same control language as the drawing toolbar's flyout stars. */
|
|
1477
|
+
.vela-menu-item .vela-menu-star {
|
|
1478
|
+
order: 98;
|
|
1479
|
+
/* Pad the 16px icon slot into a bigger hit target without moving the row's layout. */
|
|
1480
|
+
margin: -3px -5px -3px 0;
|
|
1481
|
+
padding: 3px 5px;
|
|
1482
|
+
box-sizing: content-box;
|
|
1483
|
+
border-radius: var(--vela-radius-sm);
|
|
1484
|
+
opacity: 0;
|
|
1485
|
+
transition: opacity 0.1s ease, background 0.1s ease;
|
|
1486
|
+
}
|
|
1487
|
+
.vela-menu-item[data-highlighted] .vela-menu-star { opacity: 0.55; }
|
|
1488
|
+
.vela-menu-item .vela-menu-star:hover { opacity: 1; background: var(--vela-hover-strong); }
|
|
1489
|
+
.vela-menu-item .vela-menu-star.vela-fav { opacity: 0.95; color: var(--vela-highlight); }
|
|
1426
1490
|
.vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
|
|
1427
1491
|
/* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
|
|
1428
1492
|
list is open so the path you came down remains readable. */
|
|
@@ -1441,9 +1505,13 @@ var Surface = class _Surface {
|
|
|
1441
1505
|
this.items = [];
|
|
1442
1506
|
/** Branch item id → the surface it opens. */
|
|
1443
1507
|
this.subs = /* @__PURE__ */ new Map();
|
|
1508
|
+
/** Trigger rect captured when this level opened — the list stays put if the
|
|
1509
|
+
* trigger then moves (favorite chips shifting the caret). */
|
|
1510
|
+
this.pinnedAnchor = null;
|
|
1444
1511
|
this.doc = doc;
|
|
1445
1512
|
this.host = opts.host;
|
|
1446
1513
|
this.onSelect = opts.onSelect;
|
|
1514
|
+
this.onFavorite = opts.onFavorite;
|
|
1447
1515
|
this.positioner = doc.createElement("div");
|
|
1448
1516
|
this.positioner.className = "vela-ui-layer";
|
|
1449
1517
|
this.list = doc.createElement("ul");
|
|
@@ -1451,16 +1519,25 @@ var Surface = class _Surface {
|
|
|
1451
1519
|
if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
|
|
1452
1520
|
this.positioner.appendChild(this.list);
|
|
1453
1521
|
this.host.appendChild(this.positioner);
|
|
1522
|
+
const trigger = opts.trigger;
|
|
1454
1523
|
this.ctrl = menuController({
|
|
1455
1524
|
items: [],
|
|
1456
1525
|
id: opts.id,
|
|
1457
1526
|
placement: opts.placement,
|
|
1458
1527
|
onSelect: (id) => this.onSelect(id),
|
|
1459
|
-
|
|
1528
|
+
getAnchorRect: () => this.pinnedAnchor,
|
|
1529
|
+
onOpenChange: (open2) => {
|
|
1530
|
+
if (open2 && trigger) {
|
|
1531
|
+
const r = trigger.getBoundingClientRect();
|
|
1532
|
+
this.pinnedAnchor = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
1533
|
+
} else {
|
|
1534
|
+
this.pinnedAnchor = null;
|
|
1535
|
+
}
|
|
1536
|
+
opts.onOpenChange?.(open2);
|
|
1537
|
+
},
|
|
1460
1538
|
triggerId: opts.triggerId
|
|
1461
1539
|
});
|
|
1462
1540
|
this.mid = String(this.ctrl.props.id);
|
|
1463
|
-
const trigger = opts.trigger;
|
|
1464
1541
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
1465
1542
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
1466
1543
|
const api = this.ctrl.connect(service);
|
|
@@ -1540,6 +1617,25 @@ var Surface = class _Surface {
|
|
|
1540
1617
|
hint.textContent = item.hint;
|
|
1541
1618
|
li.appendChild(hint);
|
|
1542
1619
|
}
|
|
1620
|
+
if (item.favorite !== void 0 && this.onFavorite) {
|
|
1621
|
+
const on = item.favorite;
|
|
1622
|
+
const star = iconEl(on ? "star-filled" : "star", doc);
|
|
1623
|
+
star.classList.add("vela-menu-star");
|
|
1624
|
+
if (on) star.classList.add("vela-fav");
|
|
1625
|
+
star.removeAttribute("aria-hidden");
|
|
1626
|
+
star.setAttribute("role", "button");
|
|
1627
|
+
star.setAttribute("aria-label", on ? "Remove from favorites" : "Add to favorites");
|
|
1628
|
+
star.setAttribute("aria-pressed", on ? "true" : "false");
|
|
1629
|
+
for (const ev of ["pointerdown", "pointerup", "mousedown", "mouseup"]) {
|
|
1630
|
+
star.addEventListener(ev, (e) => e.stopPropagation());
|
|
1631
|
+
}
|
|
1632
|
+
star.addEventListener("click", (e) => {
|
|
1633
|
+
e.stopPropagation();
|
|
1634
|
+
e.preventDefault();
|
|
1635
|
+
this.onFavorite?.(item.id, !on);
|
|
1636
|
+
});
|
|
1637
|
+
li.appendChild(star);
|
|
1638
|
+
}
|
|
1543
1639
|
if (item.toggle) {
|
|
1544
1640
|
const sw = doc.createElement("span");
|
|
1545
1641
|
sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
|
|
@@ -1555,6 +1651,7 @@ var Surface = class _Surface {
|
|
|
1555
1651
|
host: this.host,
|
|
1556
1652
|
placement: "right-start",
|
|
1557
1653
|
onSelect: this.onSelect,
|
|
1654
|
+
onFavorite: this.onFavorite,
|
|
1558
1655
|
id: `${this.mid}--${item.id}`
|
|
1559
1656
|
});
|
|
1560
1657
|
sub.setItems(item.submenu ?? []);
|
|
@@ -1578,7 +1675,8 @@ var Menu = class {
|
|
|
1578
1675
|
trigger: opts.trigger,
|
|
1579
1676
|
triggerId: opts.triggerId,
|
|
1580
1677
|
id: opts.id,
|
|
1581
|
-
minWidth: opts.minWidth
|
|
1678
|
+
minWidth: opts.minWidth,
|
|
1679
|
+
onFavorite: opts.onFavorite
|
|
1582
1680
|
});
|
|
1583
1681
|
this.root.setItems(opts.items);
|
|
1584
1682
|
}
|
|
@@ -2081,6 +2179,1197 @@ var Drawer = class {
|
|
|
2081
2179
|
}
|
|
2082
2180
|
};
|
|
2083
2181
|
|
|
2182
|
+
// src/ui/components/popover/controller.ts
|
|
2183
|
+
function insetRect(r, inset) {
|
|
2184
|
+
return {
|
|
2185
|
+
left: r.left + inset,
|
|
2186
|
+
top: r.top + inset,
|
|
2187
|
+
right: r.right - inset,
|
|
2188
|
+
bottom: r.bottom - inset,
|
|
2189
|
+
width: r.width - inset * 2,
|
|
2190
|
+
height: r.height - inset * 2
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function viewportRect(width, height, inset) {
|
|
2194
|
+
return {
|
|
2195
|
+
left: inset,
|
|
2196
|
+
top: inset,
|
|
2197
|
+
right: width - inset,
|
|
2198
|
+
bottom: height - inset,
|
|
2199
|
+
width: width - inset * 2,
|
|
2200
|
+
height: height - inset * 2
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
function intersectRects(a, b) {
|
|
2204
|
+
const left = Math.max(a.left, b.left);
|
|
2205
|
+
const top = Math.max(a.top, b.top);
|
|
2206
|
+
const right = Math.min(a.right, b.right);
|
|
2207
|
+
const bottom = Math.min(a.bottom, b.bottom);
|
|
2208
|
+
return { left, top, right, bottom, width: right - left, height: bottom - top };
|
|
2209
|
+
}
|
|
2210
|
+
function placePopover(a) {
|
|
2211
|
+
let left = a.align === "end" ? a.trigger.right - a.pop.width : a.trigger.left;
|
|
2212
|
+
const below = a.trigger.bottom + a.gap;
|
|
2213
|
+
const above = a.trigger.top - a.pop.height - a.gap;
|
|
2214
|
+
const fitsBelow = below + a.pop.height <= a.clamp.bottom;
|
|
2215
|
+
const fitsAbove = above >= a.clamp.top;
|
|
2216
|
+
let top = below;
|
|
2217
|
+
if (!fitsBelow && (fitsAbove || a.trigger.top - a.clamp.top > a.clamp.bottom - a.trigger.bottom)) {
|
|
2218
|
+
top = Math.max(a.clamp.top, above);
|
|
2219
|
+
}
|
|
2220
|
+
if (left + a.pop.width > a.clamp.right) left = a.clamp.right - a.pop.width;
|
|
2221
|
+
if (left < a.clamp.left) left = a.clamp.left;
|
|
2222
|
+
if (top + a.pop.height > a.clamp.bottom) top = a.clamp.bottom - a.pop.height;
|
|
2223
|
+
if (top < a.clamp.top) top = a.clamp.top;
|
|
2224
|
+
return {
|
|
2225
|
+
left: Math.round(left - a.originX),
|
|
2226
|
+
top: Math.round(top - a.originY)
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2229
|
+
function popoverController(opts = {}) {
|
|
2230
|
+
return {
|
|
2231
|
+
gap: opts.gap ?? 4,
|
|
2232
|
+
align: opts.align ?? "start",
|
|
2233
|
+
matchWidth: opts.matchWidth ?? false,
|
|
2234
|
+
position: opts.position ?? "fixed",
|
|
2235
|
+
boundaryInset: opts.boundaryInset ?? 0,
|
|
2236
|
+
viewportInset: opts.viewportInset ?? 6,
|
|
2237
|
+
onClose: opts.onClose
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
// src/ui/components/popover/styles.ts
|
|
2242
|
+
var POPOVER_STYLE_ID = "vela-ui-popover";
|
|
2243
|
+
var POPOVER_CSS = `
|
|
2244
|
+
.vela-popover {
|
|
2245
|
+
position: fixed;
|
|
2246
|
+
z-index: var(--vela-z-popover);
|
|
2247
|
+
box-sizing: border-box;
|
|
2248
|
+
/* Never serve as a scroll anchor: the shell is portaled and re-placed between
|
|
2249
|
+
gestures, and anchoring against it jumps the scroller underneath. */
|
|
2250
|
+
overflow-anchor: none;
|
|
2251
|
+
}
|
|
2252
|
+
.vela-popover[data-position='absolute'] { position: absolute; }
|
|
2253
|
+
.vela-popover[hidden] { display: none !important; }
|
|
2254
|
+
`;
|
|
2255
|
+
|
|
2256
|
+
// src/ui/components/popover/view.ts
|
|
2257
|
+
var open = null;
|
|
2258
|
+
function closeOpenPopovers() {
|
|
2259
|
+
open?.hide();
|
|
2260
|
+
}
|
|
2261
|
+
function isPopoverOpen() {
|
|
2262
|
+
return open !== null;
|
|
2263
|
+
}
|
|
2264
|
+
function openPopoverTrigger() {
|
|
2265
|
+
return open?.trigger ?? null;
|
|
2266
|
+
}
|
|
2267
|
+
function toRect(r) {
|
|
2268
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
|
|
2269
|
+
}
|
|
2270
|
+
var Popover = class {
|
|
2271
|
+
constructor(opts) {
|
|
2272
|
+
this.onOutside = null;
|
|
2273
|
+
this.onKey = null;
|
|
2274
|
+
this.onReflow = null;
|
|
2275
|
+
this.shown = false;
|
|
2276
|
+
const doc = opts.trigger.ownerDocument;
|
|
2277
|
+
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
2278
|
+
this.trigger = opts.trigger;
|
|
2279
|
+
this.host = opts.host ?? doc.body;
|
|
2280
|
+
this.ctrl = popoverController(opts);
|
|
2281
|
+
this.boundary = opts.boundary ?? "viewport";
|
|
2282
|
+
this.theme = opts.theme;
|
|
2283
|
+
this.el = doc.createElement("div");
|
|
2284
|
+
this.el.className = "vela-popover vela-ui-layer" + (opts.className ? ` ${opts.className}` : "");
|
|
2285
|
+
this.el.dataset.position = this.ctrl.position;
|
|
2286
|
+
if (opts.zIndex !== void 0) this.el.style.zIndex = String(opts.zIndex);
|
|
2287
|
+
this.el.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
2288
|
+
if (opts.content instanceof Node) this.el.appendChild(opts.content);
|
|
2289
|
+
else if (typeof opts.content === "function") opts.content(this.el);
|
|
2290
|
+
}
|
|
2291
|
+
get open() {
|
|
2292
|
+
return this.shown;
|
|
2293
|
+
}
|
|
2294
|
+
get position() {
|
|
2295
|
+
return this.ctrl.position;
|
|
2296
|
+
}
|
|
2297
|
+
get align() {
|
|
2298
|
+
return this.ctrl.align;
|
|
2299
|
+
}
|
|
2300
|
+
show() {
|
|
2301
|
+
if (this.shown) {
|
|
2302
|
+
this.place();
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
if (open && open !== this) open.hide();
|
|
2306
|
+
ensureUIHost(this.el, this.theme);
|
|
2307
|
+
this.host.appendChild(this.el);
|
|
2308
|
+
this.shown = true;
|
|
2309
|
+
open = this;
|
|
2310
|
+
this.place();
|
|
2311
|
+
const onOutside = (ev) => {
|
|
2312
|
+
const t = ev.target;
|
|
2313
|
+
if (this.el.contains(t) || this.trigger.contains(t)) return;
|
|
2314
|
+
this.hide();
|
|
2315
|
+
};
|
|
2316
|
+
const onKey = (ev) => {
|
|
2317
|
+
if (ev.key !== "Escape") return;
|
|
2318
|
+
ev.preventDefault();
|
|
2319
|
+
ev.stopPropagation();
|
|
2320
|
+
this.hide();
|
|
2321
|
+
};
|
|
2322
|
+
const onReflow = () => this.place();
|
|
2323
|
+
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
2324
|
+
document.addEventListener("keydown", onKey, true);
|
|
2325
|
+
window.addEventListener("resize", onReflow, true);
|
|
2326
|
+
document.addEventListener("scroll", onReflow, true);
|
|
2327
|
+
this.onOutside = onOutside;
|
|
2328
|
+
this.onKey = onKey;
|
|
2329
|
+
this.onReflow = onReflow;
|
|
2330
|
+
}
|
|
2331
|
+
hide() {
|
|
2332
|
+
if (!this.shown) return;
|
|
2333
|
+
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
2334
|
+
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
2335
|
+
if (this.onReflow) {
|
|
2336
|
+
window.removeEventListener("resize", this.onReflow, true);
|
|
2337
|
+
document.removeEventListener("scroll", this.onReflow, true);
|
|
2338
|
+
}
|
|
2339
|
+
this.onOutside = null;
|
|
2340
|
+
this.onKey = null;
|
|
2341
|
+
this.onReflow = null;
|
|
2342
|
+
this.el.remove();
|
|
2343
|
+
this.shown = false;
|
|
2344
|
+
if (open === this) open = null;
|
|
2345
|
+
this.ctrl.onClose?.();
|
|
2346
|
+
}
|
|
2347
|
+
/** Show if closed, hide if this instance is the open popover. */
|
|
2348
|
+
toggle() {
|
|
2349
|
+
if (this.shown) this.hide();
|
|
2350
|
+
else this.show();
|
|
2351
|
+
}
|
|
2352
|
+
destroy() {
|
|
2353
|
+
this.hide();
|
|
2354
|
+
}
|
|
2355
|
+
reposition() {
|
|
2356
|
+
if (this.shown) this.place();
|
|
2357
|
+
}
|
|
2358
|
+
clampRect() {
|
|
2359
|
+
const view = viewportRect(window.innerWidth, window.innerHeight, this.ctrl.viewportInset);
|
|
2360
|
+
const bound = this.readBoundary();
|
|
2361
|
+
if (!bound) return view;
|
|
2362
|
+
const inset = insetRect(bound, this.ctrl.boundaryInset);
|
|
2363
|
+
return intersectRects(view, inset);
|
|
2364
|
+
}
|
|
2365
|
+
readBoundary() {
|
|
2366
|
+
const b = this.boundary;
|
|
2367
|
+
if (b === "viewport") return null;
|
|
2368
|
+
const raw = typeof b === "function" ? b() : b.getBoundingClientRect();
|
|
2369
|
+
return raw ? toRect(raw) : null;
|
|
2370
|
+
}
|
|
2371
|
+
place() {
|
|
2372
|
+
const ar = this.trigger.getBoundingClientRect();
|
|
2373
|
+
if (this.ctrl.matchWidth) {
|
|
2374
|
+
this.el.style.minWidth = `${Math.round(Math.max(this.el.offsetWidth, ar.width))}px`;
|
|
2375
|
+
}
|
|
2376
|
+
const origin = this.ctrl.position === "absolute" ? this.host.getBoundingClientRect() : { left: 0, top: 0 };
|
|
2377
|
+
const pos = placePopover({
|
|
2378
|
+
trigger: toRect(ar),
|
|
2379
|
+
pop: { width: this.el.offsetWidth, height: this.el.offsetHeight },
|
|
2380
|
+
gap: this.ctrl.gap,
|
|
2381
|
+
align: this.ctrl.align,
|
|
2382
|
+
clamp: this.clampRect(),
|
|
2383
|
+
originX: origin.left,
|
|
2384
|
+
originY: origin.top
|
|
2385
|
+
});
|
|
2386
|
+
this.el.style.left = `${pos.left}px`;
|
|
2387
|
+
this.el.style.top = `${pos.top}px`;
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
|
|
2391
|
+
// src/ui/components/switch/controller.ts
|
|
2392
|
+
function switchController(opts = {}) {
|
|
2393
|
+
let checked = opts.checked ?? false;
|
|
2394
|
+
const disabled = opts.disabled ?? false;
|
|
2395
|
+
return {
|
|
2396
|
+
get checked() {
|
|
2397
|
+
return checked;
|
|
2398
|
+
},
|
|
2399
|
+
disabled,
|
|
2400
|
+
size: opts.size ?? "md",
|
|
2401
|
+
tone: opts.tone ?? "bright",
|
|
2402
|
+
setChecked(v) {
|
|
2403
|
+
checked = v;
|
|
2404
|
+
},
|
|
2405
|
+
toggle() {
|
|
2406
|
+
if (disabled) return checked;
|
|
2407
|
+
checked = !checked;
|
|
2408
|
+
opts.onChange?.(checked);
|
|
2409
|
+
return checked;
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// src/ui/components/switch/styles.ts
|
|
2415
|
+
var SWITCH_STYLE_ID = "vela-ui-switch";
|
|
2416
|
+
var SWITCH_CSS = `
|
|
2417
|
+
.vela-switch {
|
|
2418
|
+
width: 20px;
|
|
2419
|
+
height: 20px;
|
|
2420
|
+
flex: 0 0 auto;
|
|
2421
|
+
display: inline-flex;
|
|
2422
|
+
align-items: center;
|
|
2423
|
+
justify-content: center;
|
|
2424
|
+
padding: 0;
|
|
2425
|
+
border: 1px solid var(--vela-border);
|
|
2426
|
+
border-radius: 5px;
|
|
2427
|
+
background: transparent;
|
|
2428
|
+
color: transparent;
|
|
2429
|
+
cursor: pointer;
|
|
2430
|
+
line-height: 0;
|
|
2431
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
2432
|
+
}
|
|
2433
|
+
.vela-switch .vela-icon, .vela-switch svg { display: block; width: 12px; height: 12px; }
|
|
2434
|
+
.vela-switch[data-size='sm'] .vela-icon, .vela-switch[data-size='sm'] svg { width: 11px; height: 11px; }
|
|
2435
|
+
.vela-switch:hover { border-color: var(--vela-fg-muted); }
|
|
2436
|
+
.vela-switch[data-checked] {
|
|
2437
|
+
background: var(--vela-fg-bright);
|
|
2438
|
+
border-color: var(--vela-fg-bright);
|
|
2439
|
+
color: var(--vela-bg);
|
|
2440
|
+
}
|
|
2441
|
+
.vela-switch[data-size='sm'] { width: 18px; height: 18px; border-color: var(--vela-border-strong); }
|
|
2442
|
+
.vela-switch[data-size='sm']:hover { border-color: var(--vela-fg-muted); }
|
|
2443
|
+
.vela-switch[data-size='sm'][data-checked] {
|
|
2444
|
+
background: var(--vela-selected-bg);
|
|
2445
|
+
border-color: var(--vela-selected-bg);
|
|
2446
|
+
color: var(--vela-selected-fg);
|
|
2447
|
+
}
|
|
2448
|
+
.vela-switch[data-tone='selected'][data-checked] {
|
|
2449
|
+
background: var(--vela-selected-bg);
|
|
2450
|
+
border-color: var(--vela-selected-bg);
|
|
2451
|
+
color: var(--vela-selected-fg);
|
|
2452
|
+
}
|
|
2453
|
+
.vela-switch[disabled] { opacity: 0.4; cursor: default; }
|
|
2454
|
+
`;
|
|
2455
|
+
|
|
2456
|
+
// src/ui/components/switch/view.ts
|
|
2457
|
+
var Switch = class {
|
|
2458
|
+
constructor(opts = {}) {
|
|
2459
|
+
injectStyles(SWITCH_STYLE_ID, SWITCH_CSS, document);
|
|
2460
|
+
this.ctrl = switchController(opts);
|
|
2461
|
+
const b = document.createElement("button");
|
|
2462
|
+
b.type = "button";
|
|
2463
|
+
if (opts.id) b.id = opts.id;
|
|
2464
|
+
b.className = "vela-switch";
|
|
2465
|
+
b.dataset.size = this.ctrl.size;
|
|
2466
|
+
b.dataset.tone = this.ctrl.tone;
|
|
2467
|
+
b.setAttribute("role", "switch");
|
|
2468
|
+
if (this.ctrl.disabled) b.disabled = true;
|
|
2469
|
+
b.appendChild(iconEl("check", b.ownerDocument));
|
|
2470
|
+
this.el = b;
|
|
2471
|
+
this.paint();
|
|
2472
|
+
b.addEventListener("pointerdown", (e) => {
|
|
2473
|
+
e.preventDefault();
|
|
2474
|
+
b.focus({ preventScroll: true });
|
|
2475
|
+
});
|
|
2476
|
+
b.addEventListener("click", () => {
|
|
2477
|
+
this.ctrl.toggle();
|
|
2478
|
+
this.paint();
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
get checked() {
|
|
2482
|
+
return this.ctrl.checked;
|
|
2483
|
+
}
|
|
2484
|
+
setChecked(v) {
|
|
2485
|
+
this.ctrl.setChecked(v);
|
|
2486
|
+
this.paint();
|
|
2487
|
+
}
|
|
2488
|
+
paint() {
|
|
2489
|
+
const on = this.ctrl.checked;
|
|
2490
|
+
this.el.setAttribute("aria-checked", on ? "true" : "false");
|
|
2491
|
+
if (on) this.el.dataset.checked = "";
|
|
2492
|
+
else delete this.el.dataset.checked;
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
|
|
2496
|
+
// src/ui/components/select/controller.ts
|
|
2497
|
+
function selectController(opts) {
|
|
2498
|
+
const options = opts.options;
|
|
2499
|
+
let value = opts.value ?? options[0]?.value ?? "";
|
|
2500
|
+
const size = opts.size ?? "md";
|
|
2501
|
+
return {
|
|
2502
|
+
options,
|
|
2503
|
+
get value() {
|
|
2504
|
+
return value;
|
|
2505
|
+
},
|
|
2506
|
+
size,
|
|
2507
|
+
fill: opts.fill ?? size !== "sm",
|
|
2508
|
+
disabled: opts.disabled ?? false,
|
|
2509
|
+
setValue(v) {
|
|
2510
|
+
value = v;
|
|
2511
|
+
},
|
|
2512
|
+
labelOf(v) {
|
|
2513
|
+
return options.find((o) => o.value === v)?.label ?? v;
|
|
2514
|
+
},
|
|
2515
|
+
pick(v) {
|
|
2516
|
+
value = v;
|
|
2517
|
+
const label = options.find((o) => o.value === v)?.label ?? v;
|
|
2518
|
+
opts.onChange?.(v, label);
|
|
2519
|
+
return { value: v, label };
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// src/ui/components/select/styles.ts
|
|
2525
|
+
var SELECT_STYLE_ID = "vela-ui-select";
|
|
2526
|
+
var SELECT_CSS = `
|
|
2527
|
+
.vela-select { position: relative; display: inline-block; min-width: 0; }
|
|
2528
|
+
.vela-select[data-fill] { width: 100%; }
|
|
2529
|
+
.vela-select-trigger {
|
|
2530
|
+
display: flex;
|
|
2531
|
+
align-items: center;
|
|
2532
|
+
width: 100%;
|
|
2533
|
+
box-sizing: border-box;
|
|
2534
|
+
height: 34px;
|
|
2535
|
+
padding: 0 26px 0 8px;
|
|
2536
|
+
background: transparent;
|
|
2537
|
+
border: 1px solid var(--vela-border-strong);
|
|
2538
|
+
border-radius: 6px;
|
|
2539
|
+
color: var(--vela-fg-bright);
|
|
2540
|
+
font-size: 14px;
|
|
2541
|
+
font-family: inherit;
|
|
2542
|
+
cursor: pointer;
|
|
2543
|
+
text-align: left;
|
|
2544
|
+
outline: none;
|
|
2545
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
2546
|
+
}
|
|
2547
|
+
.vela-select-trigger:hover { border-color: var(--vela-fg-muted); }
|
|
2548
|
+
.vela-select-trigger:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
2549
|
+
/* As a grid item (chart-settings rows dissolve via display:contents into a shared
|
|
2550
|
+
max-content column) the wrap must NOT stretch to the column: it sizes to its own
|
|
2551
|
+
widest option, like the native select it replaces. */
|
|
2552
|
+
.vela-select[data-size='sm'] { max-width: 200px; flex: 0 0 auto; justify-self: start; }
|
|
2553
|
+
.vela-select[data-size='sm'] .vela-select-trigger {
|
|
2554
|
+
height: 28px;
|
|
2555
|
+
background: var(--vela-surface-elev);
|
|
2556
|
+
border-radius: var(--vela-radius-sm);
|
|
2557
|
+
color: var(--vela-fg);
|
|
2558
|
+
font-size: 13px;
|
|
2559
|
+
box-shadow: none;
|
|
2560
|
+
}
|
|
2561
|
+
.vela-select[data-size='sm'] .vela-select-trigger:focus { box-shadow: none; border-color: var(--vela-fg-muted); }
|
|
2562
|
+
.vela-select-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2563
|
+
/* Invisible option-label stack: gives the shrink-to-fit wrap the WIDEST option's
|
|
2564
|
+
intrinsic width (like a native select), so picking a shorter/longer label never
|
|
2565
|
+
resizes the trigger and shifts the row around it. */
|
|
2566
|
+
.vela-select-sizer { visibility: hidden; height: 0; overflow: hidden; font-size: 14px; }
|
|
2567
|
+
.vela-select[data-size='sm'] .vela-select-sizer { font-size: 13px; }
|
|
2568
|
+
.vela-select-sizer span { display: block; height: 0; white-space: nowrap; padding: 0 26px 0 8px; border-inline: 1px solid transparent; }
|
|
2569
|
+
.vela-select-chevron {
|
|
2570
|
+
position: absolute;
|
|
2571
|
+
right: 8px;
|
|
2572
|
+
top: 50%;
|
|
2573
|
+
transform: translateY(-50%);
|
|
2574
|
+
pointer-events: none;
|
|
2575
|
+
display: flex;
|
|
2576
|
+
opacity: 0.55;
|
|
2577
|
+
line-height: 0;
|
|
2578
|
+
color: inherit;
|
|
2579
|
+
}
|
|
2580
|
+
.vela-select-chevron .vela-icon, .vela-select-chevron svg { width: 12px; height: 12px; display: block; }
|
|
2581
|
+
.vela-select-list {
|
|
2582
|
+
background: var(--vela-bg);
|
|
2583
|
+
color: var(--vela-fg);
|
|
2584
|
+
border: none;
|
|
2585
|
+
border-radius: 6px;
|
|
2586
|
+
box-shadow: var(--vela-shadow);
|
|
2587
|
+
font: 14px var(--vela-font);
|
|
2588
|
+
padding: 4px;
|
|
2589
|
+
overflow: hidden;
|
|
2590
|
+
}
|
|
2591
|
+
.vela-select-list[data-size='sm'] { font-size: 13px; background: var(--vela-surface-overlay); }
|
|
2592
|
+
.vela-select-items { max-height: none; overflow: hidden; }
|
|
2593
|
+
.vela-select-list.is-scroll { display: flex; align-items: stretch; gap: 2px; }
|
|
2594
|
+
.vela-select-list.is-scroll .vela-select-items {
|
|
2595
|
+
flex: 1 1 auto;
|
|
2596
|
+
min-width: 0;
|
|
2597
|
+
max-height: 240px;
|
|
2598
|
+
overflow-y: auto;
|
|
2599
|
+
scrollbar-width: none;
|
|
2600
|
+
}
|
|
2601
|
+
.vela-select-list.is-scroll .vela-select-items::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
|
2602
|
+
.vela-select-rail { position: relative; flex: none; width: 3px; margin: 2px 1px 2px 0; pointer-events: none; }
|
|
2603
|
+
.vela-select-thumb { width: 3px; border-radius: 2px; background: var(--vela-scroll); }
|
|
2604
|
+
.vela-select-item {
|
|
2605
|
+
display: block;
|
|
2606
|
+
width: 100%;
|
|
2607
|
+
text-align: left;
|
|
2608
|
+
padding: 7px 10px;
|
|
2609
|
+
border: none;
|
|
2610
|
+
border-radius: 4px;
|
|
2611
|
+
background: transparent;
|
|
2612
|
+
color: inherit;
|
|
2613
|
+
cursor: pointer;
|
|
2614
|
+
font: inherit;
|
|
2615
|
+
font-weight: 400;
|
|
2616
|
+
}
|
|
2617
|
+
.vela-select-item:hover { background: var(--vela-hover); }
|
|
2618
|
+
.vela-select-item[data-checked] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
2619
|
+
.vela-select-item[data-checked]:hover { background: var(--vela-hover-strong); }
|
|
2620
|
+
`;
|
|
2621
|
+
|
|
2622
|
+
// src/ui/components/select/view.ts
|
|
2623
|
+
function ensureSelectStyles(doc) {
|
|
2624
|
+
injectStyles(SELECT_STYLE_ID, SELECT_CSS, doc);
|
|
2625
|
+
}
|
|
2626
|
+
function syncThumb(list, thumb) {
|
|
2627
|
+
const view = list.clientHeight;
|
|
2628
|
+
const total = list.scrollHeight;
|
|
2629
|
+
if (total <= view) {
|
|
2630
|
+
thumb.style.height = "0";
|
|
2631
|
+
return;
|
|
2632
|
+
}
|
|
2633
|
+
const thumbH = Math.max(20, view / total * view);
|
|
2634
|
+
const top = list.scrollTop / (total - view) * (view - thumbH);
|
|
2635
|
+
thumb.style.height = `${Math.round(thumbH)}px`;
|
|
2636
|
+
thumb.style.transform = `translateY(${Math.round(top)}px`;
|
|
2637
|
+
}
|
|
2638
|
+
function fillSelectList(menu2, options, current, onPick) {
|
|
2639
|
+
menu2.replaceChildren();
|
|
2640
|
+
const list = menu2.ownerDocument.createElement("div");
|
|
2641
|
+
list.className = "vela-select-items";
|
|
2642
|
+
for (const p of options) {
|
|
2643
|
+
const item = menu2.ownerDocument.createElement("button");
|
|
2644
|
+
item.type = "button";
|
|
2645
|
+
item.className = "vela-select-item";
|
|
2646
|
+
item.textContent = p.label;
|
|
2647
|
+
if (p.value === current) item.dataset.checked = "1";
|
|
2648
|
+
item.addEventListener("click", (e) => {
|
|
2649
|
+
e.stopPropagation();
|
|
2650
|
+
onPick(p.value, p.label);
|
|
2651
|
+
});
|
|
2652
|
+
list.appendChild(item);
|
|
2653
|
+
}
|
|
2654
|
+
menu2.appendChild(list);
|
|
2655
|
+
}
|
|
2656
|
+
function decorateSelectScroll(menu2) {
|
|
2657
|
+
const list = menu2.querySelector(".vela-select-items");
|
|
2658
|
+
if (!list) return;
|
|
2659
|
+
if (list.scrollHeight > 240) {
|
|
2660
|
+
menu2.classList.add("is-scroll");
|
|
2661
|
+
const rail = menu2.ownerDocument.createElement("div");
|
|
2662
|
+
rail.className = "vela-select-rail";
|
|
2663
|
+
const thumb = menu2.ownerDocument.createElement("div");
|
|
2664
|
+
thumb.className = "vela-select-thumb";
|
|
2665
|
+
rail.appendChild(thumb);
|
|
2666
|
+
menu2.appendChild(rail);
|
|
2667
|
+
list.addEventListener("scroll", () => syncThumb(list, thumb));
|
|
2668
|
+
syncThumb(list, thumb);
|
|
2669
|
+
}
|
|
2670
|
+
menu2.querySelector(".vela-select-item[data-checked]")?.scrollIntoView({ block: "nearest" });
|
|
2671
|
+
list.dispatchEvent(new Event("scroll"));
|
|
2672
|
+
}
|
|
2673
|
+
function toggleSelectList(trigger, options, current, onPick, opts = {}) {
|
|
2674
|
+
if (openPopoverTrigger() === trigger) {
|
|
2675
|
+
closeOpenPopovers();
|
|
2676
|
+
return null;
|
|
2677
|
+
}
|
|
2678
|
+
return openSelectList(trigger, options, current, onPick, opts);
|
|
2679
|
+
}
|
|
2680
|
+
function openSelectList(trigger, options, current, onPick, opts = {}) {
|
|
2681
|
+
ensureSelectStyles(trigger.ownerDocument);
|
|
2682
|
+
closeOpenPopovers();
|
|
2683
|
+
const pop = new Popover({
|
|
2684
|
+
trigger,
|
|
2685
|
+
theme: opts.theme,
|
|
2686
|
+
matchWidth: opts.matchWidth ?? true,
|
|
2687
|
+
boundary: opts.boundary,
|
|
2688
|
+
boundaryInset: opts.boundaryInset,
|
|
2689
|
+
gap: opts.gap ?? 4,
|
|
2690
|
+
align: opts.align ?? "start",
|
|
2691
|
+
host: opts.host,
|
|
2692
|
+
position: opts.position,
|
|
2693
|
+
zIndex: opts.zIndex,
|
|
2694
|
+
className: "vela-select-list",
|
|
2695
|
+
onClose: opts.onClose,
|
|
2696
|
+
content: (el) => {
|
|
2697
|
+
if (opts.size) el.dataset.size = opts.size;
|
|
2698
|
+
fillSelectList(el, options, current, (value, label) => {
|
|
2699
|
+
pop.hide();
|
|
2700
|
+
onPick(value, label);
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
pop.show();
|
|
2705
|
+
decorateSelectScroll(pop.el);
|
|
2706
|
+
pop.reposition();
|
|
2707
|
+
return pop;
|
|
2708
|
+
}
|
|
2709
|
+
var Select = class {
|
|
2710
|
+
constructor(opts) {
|
|
2711
|
+
this.list = null;
|
|
2712
|
+
const doc = opts.list?.host?.ownerDocument ?? document;
|
|
2713
|
+
ensureSelectStyles(doc);
|
|
2714
|
+
this.ctrl = selectController(opts);
|
|
2715
|
+
this.listOpts = { ...opts.list, theme: opts.theme ?? opts.list?.theme, size: this.ctrl.size };
|
|
2716
|
+
const wrap = doc.createElement("div");
|
|
2717
|
+
wrap.className = "vela-select";
|
|
2718
|
+
wrap.dataset.size = this.ctrl.size;
|
|
2719
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
2720
|
+
const btn = doc.createElement("button");
|
|
2721
|
+
btn.type = "button";
|
|
2722
|
+
if (opts.id) btn.id = opts.id;
|
|
2723
|
+
btn.className = "vela-select-trigger";
|
|
2724
|
+
if (this.ctrl.disabled) btn.disabled = true;
|
|
2725
|
+
const label = doc.createElement("span");
|
|
2726
|
+
label.className = "vela-select-label";
|
|
2727
|
+
label.textContent = this.ctrl.labelOf(this.ctrl.value);
|
|
2728
|
+
const chevron = doc.createElement("span");
|
|
2729
|
+
chevron.className = "vela-select-chevron";
|
|
2730
|
+
chevron.appendChild(iconEl("chevron-down", doc));
|
|
2731
|
+
btn.append(label, chevron);
|
|
2732
|
+
wrap.appendChild(btn);
|
|
2733
|
+
const sizer = doc.createElement("div");
|
|
2734
|
+
sizer.className = "vela-select-sizer";
|
|
2735
|
+
sizer.setAttribute("aria-hidden", "true");
|
|
2736
|
+
for (const o of this.ctrl.options) {
|
|
2737
|
+
const s = doc.createElement("span");
|
|
2738
|
+
s.textContent = o.label;
|
|
2739
|
+
sizer.appendChild(s);
|
|
2740
|
+
}
|
|
2741
|
+
wrap.appendChild(sizer);
|
|
2742
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
2743
|
+
e.preventDefault();
|
|
2744
|
+
btn.focus({ preventScroll: true });
|
|
2745
|
+
});
|
|
2746
|
+
btn.addEventListener("click", (e) => {
|
|
2747
|
+
e.stopPropagation();
|
|
2748
|
+
this.toggle();
|
|
2749
|
+
});
|
|
2750
|
+
this.el = wrap;
|
|
2751
|
+
this.trigger = btn;
|
|
2752
|
+
this.labelEl = label;
|
|
2753
|
+
}
|
|
2754
|
+
get value() {
|
|
2755
|
+
return this.ctrl.value;
|
|
2756
|
+
}
|
|
2757
|
+
setValue(v) {
|
|
2758
|
+
this.ctrl.setValue(v);
|
|
2759
|
+
this.labelEl.textContent = this.ctrl.labelOf(v);
|
|
2760
|
+
}
|
|
2761
|
+
toggle() {
|
|
2762
|
+
this.list = toggleSelectList(
|
|
2763
|
+
this.trigger,
|
|
2764
|
+
this.ctrl.options,
|
|
2765
|
+
this.ctrl.value,
|
|
2766
|
+
(value, label) => {
|
|
2767
|
+
this.ctrl.pick(value);
|
|
2768
|
+
this.labelEl.textContent = label;
|
|
2769
|
+
this.list = null;
|
|
2770
|
+
},
|
|
2771
|
+
{ ...this.listOpts, onClose: () => {
|
|
2772
|
+
this.list = null;
|
|
2773
|
+
this.listOpts.onClose?.();
|
|
2774
|
+
} }
|
|
2775
|
+
);
|
|
2776
|
+
}
|
|
2777
|
+
destroy() {
|
|
2778
|
+
this.list?.hide();
|
|
2779
|
+
this.list = null;
|
|
2780
|
+
}
|
|
2781
|
+
};
|
|
2782
|
+
|
|
2783
|
+
// src/ui/components/number-input/controller.ts
|
|
2784
|
+
function clampNumber(n, opts) {
|
|
2785
|
+
let v = n;
|
|
2786
|
+
if (opts.min !== void 0) v = Math.max(opts.min, v);
|
|
2787
|
+
if (opts.max !== void 0) v = Math.min(opts.max, v);
|
|
2788
|
+
if (opts.integer) v = Math.round(v);
|
|
2789
|
+
return v;
|
|
2790
|
+
}
|
|
2791
|
+
function decimalsOf(n) {
|
|
2792
|
+
const s = String(n);
|
|
2793
|
+
const e = s.search(/[eE]/);
|
|
2794
|
+
if (e >= 0) {
|
|
2795
|
+
const frac = s.slice(0, e).split(".")[1]?.length ?? 0;
|
|
2796
|
+
return Math.max(0, Math.min(20, frac - Number(s.slice(e + 1))));
|
|
2797
|
+
}
|
|
2798
|
+
return s.split(".")[1]?.length ?? 0;
|
|
2799
|
+
}
|
|
2800
|
+
function snapToStep(n, base, step) {
|
|
2801
|
+
const d = Math.min(20, Math.max(decimalsOf(base), decimalsOf(step)));
|
|
2802
|
+
return Number(n.toFixed(d));
|
|
2803
|
+
}
|
|
2804
|
+
function numberInputController(opts) {
|
|
2805
|
+
let value = opts.value;
|
|
2806
|
+
const integer = opts.integer ?? false;
|
|
2807
|
+
const commit = opts.commit ?? "blur";
|
|
2808
|
+
const clamp = opts.clamp ?? commit === "blur";
|
|
2809
|
+
const step = opts.step ?? (integer ? 1 : 0.1);
|
|
2810
|
+
const onChange = opts.onChange;
|
|
2811
|
+
const normalize = (raw) => clamp ? clampNumber(raw, { min: opts.min, max: opts.max, integer }) : raw;
|
|
2812
|
+
const apply = (raw) => {
|
|
2813
|
+
if (!Number.isFinite(raw)) return null;
|
|
2814
|
+
const n = normalize(raw);
|
|
2815
|
+
if (n !== value) {
|
|
2816
|
+
value = n;
|
|
2817
|
+
onChange?.(n);
|
|
2818
|
+
}
|
|
2819
|
+
return n;
|
|
2820
|
+
};
|
|
2821
|
+
return {
|
|
2822
|
+
get value() {
|
|
2823
|
+
return value;
|
|
2824
|
+
},
|
|
2825
|
+
min: opts.min,
|
|
2826
|
+
max: opts.max,
|
|
2827
|
+
step,
|
|
2828
|
+
integer,
|
|
2829
|
+
size: opts.size ?? "md",
|
|
2830
|
+
commit,
|
|
2831
|
+
steppers: opts.steppers ?? commit === "blur",
|
|
2832
|
+
clamp,
|
|
2833
|
+
disabled: opts.disabled ?? false,
|
|
2834
|
+
apply,
|
|
2835
|
+
sync(raw) {
|
|
2836
|
+
const n = Number.isFinite(raw) ? normalize(raw) : value;
|
|
2837
|
+
value = n;
|
|
2838
|
+
return n;
|
|
2839
|
+
},
|
|
2840
|
+
nudge(dir) {
|
|
2841
|
+
return apply(snapToStep(value + dir * step, value, step));
|
|
2842
|
+
}
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/ui/components/number-input/styles.ts
|
|
2847
|
+
var NUMBER_STYLE_ID = "vela-ui-number";
|
|
2848
|
+
var NUMBER_CSS = `
|
|
2849
|
+
.vela-num { position: relative; display: inline-block; min-width: 0; }
|
|
2850
|
+
.vela-num[data-fill] { width: 100%; }
|
|
2851
|
+
.vela-num input {
|
|
2852
|
+
width: 100%;
|
|
2853
|
+
box-sizing: border-box;
|
|
2854
|
+
height: 34px;
|
|
2855
|
+
background: transparent;
|
|
2856
|
+
border: 1px solid var(--vela-border-strong);
|
|
2857
|
+
border-radius: 6px;
|
|
2858
|
+
color: var(--vela-fg-bright);
|
|
2859
|
+
padding: 0 8px;
|
|
2860
|
+
font-size: 14px;
|
|
2861
|
+
font-family: inherit;
|
|
2862
|
+
outline: none;
|
|
2863
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
2864
|
+
-moz-appearance: textfield;
|
|
2865
|
+
appearance: textfield;
|
|
2866
|
+
}
|
|
2867
|
+
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
2868
|
+
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
2869
|
+
.vela-num input:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
2870
|
+
.vela-num[data-steppers] input { padding-right: 26px; }
|
|
2871
|
+
.vela-num[data-size='sm'] { width: 64px; flex: none; }
|
|
2872
|
+
.vela-num[data-size='sm'][data-compact] { width: 56px; }
|
|
2873
|
+
.vela-num[data-size='sm'] input {
|
|
2874
|
+
height: 28px;
|
|
2875
|
+
background: var(--vela-surface-elev);
|
|
2876
|
+
border-radius: var(--vela-radius-sm);
|
|
2877
|
+
color: var(--vela-fg);
|
|
2878
|
+
font-size: 13px;
|
|
2879
|
+
box-shadow: none;
|
|
2880
|
+
}
|
|
2881
|
+
.vela-num[data-size='sm'] input:focus { box-shadow: none; }
|
|
2882
|
+
.vela-num-step {
|
|
2883
|
+
position: absolute;
|
|
2884
|
+
right: 0;
|
|
2885
|
+
top: 0;
|
|
2886
|
+
bottom: 0;
|
|
2887
|
+
width: 22px;
|
|
2888
|
+
display: none;
|
|
2889
|
+
flex-direction: column;
|
|
2890
|
+
justify-content: center;
|
|
2891
|
+
box-sizing: border-box;
|
|
2892
|
+
padding: 2px 6px 2px 0;
|
|
2893
|
+
line-height: 0;
|
|
2894
|
+
}
|
|
2895
|
+
.vela-num[data-steppers]:hover .vela-num-step { display: flex; }
|
|
2896
|
+
.vela-num-step button {
|
|
2897
|
+
flex: 1;
|
|
2898
|
+
width: 100%;
|
|
2899
|
+
min-height: 0;
|
|
2900
|
+
border: none;
|
|
2901
|
+
background: transparent;
|
|
2902
|
+
color: var(--vela-fg-muted);
|
|
2903
|
+
border-radius: 3px;
|
|
2904
|
+
padding: 0;
|
|
2905
|
+
cursor: pointer;
|
|
2906
|
+
display: flex;
|
|
2907
|
+
align-items: center;
|
|
2908
|
+
justify-content: center;
|
|
2909
|
+
}
|
|
2910
|
+
.vela-num-step button:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
2911
|
+
.vela-num-step .vela-icon, .vela-num-step svg { width: 12px; height: 12px; display: block; }
|
|
2912
|
+
`;
|
|
2913
|
+
|
|
2914
|
+
// src/ui/components/number-input/view.ts
|
|
2915
|
+
var NumberInput = class {
|
|
2916
|
+
constructor(opts) {
|
|
2917
|
+
const doc = document;
|
|
2918
|
+
injectStyles(NUMBER_STYLE_ID, NUMBER_CSS, doc);
|
|
2919
|
+
this.ctrl = numberInputController(opts);
|
|
2920
|
+
this.placeholderMode = opts.placeholder !== void 0;
|
|
2921
|
+
this.emptyValue = opts.emptyValue ?? opts.value;
|
|
2922
|
+
this.last = String(opts.value);
|
|
2923
|
+
const wrap = doc.createElement("div");
|
|
2924
|
+
wrap.className = "vela-num";
|
|
2925
|
+
wrap.dataset.size = this.ctrl.size;
|
|
2926
|
+
if (this.ctrl.size !== "sm") wrap.dataset.fill = "";
|
|
2927
|
+
if (opts.compact) wrap.dataset.compact = "";
|
|
2928
|
+
if (this.ctrl.steppers) wrap.dataset.steppers = "";
|
|
2929
|
+
const ni = doc.createElement("input");
|
|
2930
|
+
ni.type = "number";
|
|
2931
|
+
if (opts.id) ni.id = opts.id;
|
|
2932
|
+
if (opts.title) ni.title = opts.title;
|
|
2933
|
+
if (opts.min !== void 0) ni.min = String(opts.min);
|
|
2934
|
+
if (opts.max !== void 0) ni.max = String(opts.max);
|
|
2935
|
+
ni.step = String(this.ctrl.step);
|
|
2936
|
+
if (this.ctrl.disabled) ni.disabled = true;
|
|
2937
|
+
if (this.placeholderMode) {
|
|
2938
|
+
ni.placeholder = opts.placeholder ?? "";
|
|
2939
|
+
ni.value = opts.value !== this.emptyValue ? String(opts.value) : "";
|
|
2940
|
+
} else {
|
|
2941
|
+
ni.value = String(opts.value);
|
|
2942
|
+
}
|
|
2943
|
+
const commitRaw = (raw) => {
|
|
2944
|
+
const n = this.ctrl.apply(raw);
|
|
2945
|
+
if (n === null) {
|
|
2946
|
+
ni.value = this.placeholderMode && this.ctrl.value === this.emptyValue ? "" : this.last;
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
this.last = String(n);
|
|
2950
|
+
if (this.placeholderMode && n === this.emptyValue) ni.value = "";
|
|
2951
|
+
else ni.value = String(n);
|
|
2952
|
+
};
|
|
2953
|
+
if (this.placeholderMode) {
|
|
2954
|
+
ni.addEventListener("change", () => {
|
|
2955
|
+
const raw = ni.value.trim() === "" ? this.emptyValue : Number(ni.value);
|
|
2956
|
+
commitRaw(Number.isFinite(raw) ? raw : this.emptyValue);
|
|
2957
|
+
});
|
|
2958
|
+
} else if (this.ctrl.commit === "live") {
|
|
2959
|
+
ni.addEventListener("input", () => {
|
|
2960
|
+
const v = Number(ni.value);
|
|
2961
|
+
if (Number.isFinite(v)) this.ctrl.apply(v);
|
|
2962
|
+
});
|
|
2963
|
+
} else {
|
|
2964
|
+
ni.addEventListener("blur", () => commitRaw(Number(ni.value)));
|
|
2965
|
+
ni.addEventListener("keydown", (e) => {
|
|
2966
|
+
if (e.key === "Enter") {
|
|
2967
|
+
e.preventDefault();
|
|
2968
|
+
ni.blur();
|
|
2969
|
+
}
|
|
2970
|
+
});
|
|
2971
|
+
}
|
|
2972
|
+
wrap.appendChild(ni);
|
|
2973
|
+
if (this.ctrl.steppers) {
|
|
2974
|
+
wrap.appendChild(this.buildSteppers(doc, (dir) => {
|
|
2975
|
+
const cur = Number(ni.value);
|
|
2976
|
+
const base = Number.isFinite(cur) ? cur : this.ctrl.value;
|
|
2977
|
+
commitRaw(snapToStep(base + dir * this.ctrl.step, base, this.ctrl.step));
|
|
2978
|
+
}));
|
|
2979
|
+
}
|
|
2980
|
+
this.el = wrap;
|
|
2981
|
+
this.input = ni;
|
|
2982
|
+
}
|
|
2983
|
+
get value() {
|
|
2984
|
+
return this.ctrl.value;
|
|
2985
|
+
}
|
|
2986
|
+
setValue(v) {
|
|
2987
|
+
const n = this.ctrl.sync(v);
|
|
2988
|
+
this.last = String(n);
|
|
2989
|
+
if (this.placeholderMode && n === this.emptyValue) this.input.value = "";
|
|
2990
|
+
else this.input.value = String(n);
|
|
2991
|
+
}
|
|
2992
|
+
buildSteppers(doc, applyDir) {
|
|
2993
|
+
const steps = doc.createElement("div");
|
|
2994
|
+
steps.className = "vela-num-step";
|
|
2995
|
+
const mk = (dir, icon2, label) => {
|
|
2996
|
+
const b = doc.createElement("button");
|
|
2997
|
+
b.type = "button";
|
|
2998
|
+
b.tabIndex = -1;
|
|
2999
|
+
b.setAttribute("aria-label", label);
|
|
3000
|
+
b.appendChild(iconEl(icon2, doc));
|
|
3001
|
+
let timer = 0;
|
|
3002
|
+
const apply = () => applyDir(dir);
|
|
3003
|
+
const stop = () => {
|
|
3004
|
+
if (timer) {
|
|
3005
|
+
window.clearTimeout(timer);
|
|
3006
|
+
timer = 0;
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
b.addEventListener("pointerdown", (e) => {
|
|
3010
|
+
e.preventDefault();
|
|
3011
|
+
e.stopPropagation();
|
|
3012
|
+
apply();
|
|
3013
|
+
timer = window.setTimeout(function tick() {
|
|
3014
|
+
apply();
|
|
3015
|
+
timer = window.setTimeout(tick, 60);
|
|
3016
|
+
}, 400);
|
|
3017
|
+
});
|
|
3018
|
+
b.addEventListener("pointerup", stop);
|
|
3019
|
+
b.addEventListener("pointerleave", stop);
|
|
3020
|
+
b.addEventListener("pointercancel", stop);
|
|
3021
|
+
return b;
|
|
3022
|
+
};
|
|
3023
|
+
steps.append(mk(1, "chevron-up", "Increase"), mk(-1, "chevron-down", "Decrease"));
|
|
3024
|
+
return steps;
|
|
3025
|
+
}
|
|
3026
|
+
};
|
|
3027
|
+
|
|
3028
|
+
// src/ui/components/text-field/controller.ts
|
|
3029
|
+
function textFieldController(opts = {}) {
|
|
3030
|
+
let value = opts.value ?? "";
|
|
3031
|
+
const size = opts.size ?? "md";
|
|
3032
|
+
return {
|
|
3033
|
+
get value() {
|
|
3034
|
+
return value;
|
|
3035
|
+
},
|
|
3036
|
+
size,
|
|
3037
|
+
fill: opts.fill ?? size !== "sm",
|
|
3038
|
+
disabled: opts.disabled ?? false,
|
|
3039
|
+
commit(next) {
|
|
3040
|
+
if (next === value) return null;
|
|
3041
|
+
value = next;
|
|
3042
|
+
opts.onChange?.(next);
|
|
3043
|
+
return next;
|
|
3044
|
+
},
|
|
3045
|
+
sync(next) {
|
|
3046
|
+
value = next;
|
|
3047
|
+
}
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
// src/ui/components/text-field/styles.ts
|
|
3052
|
+
var TEXT_STYLE_ID = "vela-ui-text";
|
|
3053
|
+
var TEXT_CSS = `
|
|
3054
|
+
.vela-text {
|
|
3055
|
+
display: inline-block;
|
|
3056
|
+
min-width: 0;
|
|
3057
|
+
}
|
|
3058
|
+
.vela-text[data-fill] { width: 100%; }
|
|
3059
|
+
.vela-text-field {
|
|
3060
|
+
width: 100%;
|
|
3061
|
+
box-sizing: border-box;
|
|
3062
|
+
height: 34px;
|
|
3063
|
+
background: transparent;
|
|
3064
|
+
border: 1px solid var(--vela-border-strong);
|
|
3065
|
+
border-radius: 6px;
|
|
3066
|
+
color: var(--vela-fg-bright);
|
|
3067
|
+
padding: 0 8px;
|
|
3068
|
+
font-size: 14px;
|
|
3069
|
+
font-family: inherit;
|
|
3070
|
+
outline: none;
|
|
3071
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
3072
|
+
}
|
|
3073
|
+
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
3074
|
+
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
3075
|
+
.vela-text[data-size='sm'] .vela-text-field {
|
|
3076
|
+
height: 28px;
|
|
3077
|
+
background: var(--vela-surface-elev);
|
|
3078
|
+
border-radius: var(--vela-radius-sm);
|
|
3079
|
+
color: var(--vela-fg);
|
|
3080
|
+
font-size: 13px;
|
|
3081
|
+
}
|
|
3082
|
+
`;
|
|
3083
|
+
|
|
3084
|
+
// src/ui/components/text-field/view.ts
|
|
3085
|
+
var TextField = class {
|
|
3086
|
+
constructor(opts = {}) {
|
|
3087
|
+
const doc = document;
|
|
3088
|
+
injectStyles(TEXT_STYLE_ID, TEXT_CSS, doc);
|
|
3089
|
+
this.ctrl = textFieldController(opts);
|
|
3090
|
+
const wrap = doc.createElement("div");
|
|
3091
|
+
wrap.className = "vela-text";
|
|
3092
|
+
wrap.dataset.size = this.ctrl.size;
|
|
3093
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
3094
|
+
const ti = doc.createElement("input");
|
|
3095
|
+
ti.type = "text";
|
|
3096
|
+
if (opts.id) ti.id = opts.id;
|
|
3097
|
+
ti.value = this.ctrl.value;
|
|
3098
|
+
ti.className = "vela-text-field";
|
|
3099
|
+
if (this.ctrl.disabled) ti.disabled = true;
|
|
3100
|
+
ti.addEventListener("blur", () => {
|
|
3101
|
+
this.ctrl.commit(ti.value);
|
|
3102
|
+
});
|
|
3103
|
+
ti.addEventListener("keydown", (e) => {
|
|
3104
|
+
if (e.key === "Enter") {
|
|
3105
|
+
e.preventDefault();
|
|
3106
|
+
ti.blur();
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
wrap.appendChild(ti);
|
|
3110
|
+
this.el = wrap;
|
|
3111
|
+
this.input = ti;
|
|
3112
|
+
}
|
|
3113
|
+
get value() {
|
|
3114
|
+
return this.ctrl.value;
|
|
3115
|
+
}
|
|
3116
|
+
setValue(v) {
|
|
3117
|
+
this.ctrl.sync(v);
|
|
3118
|
+
this.input.value = v;
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
|
|
3122
|
+
// src/ui/components/color-picker/controller.ts
|
|
3123
|
+
function splitColor(color) {
|
|
3124
|
+
const s = String(color ?? "").trim();
|
|
3125
|
+
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
3126
|
+
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
3127
|
+
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
3128
|
+
if (m) {
|
|
3129
|
+
const h = m[1].toLowerCase();
|
|
3130
|
+
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
3131
|
+
}
|
|
3132
|
+
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
3133
|
+
if (rgba) {
|
|
3134
|
+
const p = rgba[1].split(",").map((v) => v.trim());
|
|
3135
|
+
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
3136
|
+
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
3137
|
+
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
3138
|
+
}
|
|
3139
|
+
return { hex6: ACCENT_BRIGHT, alpha: 1 };
|
|
3140
|
+
}
|
|
3141
|
+
function combineColor(hex6, alpha) {
|
|
3142
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
3143
|
+
if (a >= 0.999) return hex6;
|
|
3144
|
+
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
3145
|
+
}
|
|
3146
|
+
function blendOver(fg, bg, alpha) {
|
|
3147
|
+
const rgb = (c) => {
|
|
3148
|
+
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
3149
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
3150
|
+
};
|
|
3151
|
+
const [fr, fgc, fb] = rgb(fg);
|
|
3152
|
+
const [br, bgc, bb] = rgb(bg);
|
|
3153
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
3154
|
+
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
3155
|
+
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
3156
|
+
}
|
|
3157
|
+
function hslHex(h, s, l) {
|
|
3158
|
+
const sn = s / 100;
|
|
3159
|
+
const ln = l / 100;
|
|
3160
|
+
const k = (n) => (n + h / 30) % 12;
|
|
3161
|
+
const a = sn * Math.min(ln, 1 - ln);
|
|
3162
|
+
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
3163
|
+
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
3164
|
+
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
3165
|
+
}
|
|
3166
|
+
function buildPalette() {
|
|
3167
|
+
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
3168
|
+
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
3169
|
+
const levels = [
|
|
3170
|
+
[82, 56],
|
|
3171
|
+
[58, 84],
|
|
3172
|
+
[62, 74],
|
|
3173
|
+
[66, 62],
|
|
3174
|
+
[70, 50],
|
|
3175
|
+
[64, 39],
|
|
3176
|
+
[54, 29]
|
|
3177
|
+
];
|
|
3178
|
+
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
3179
|
+
}
|
|
3180
|
+
function transparencyChecker(size) {
|
|
3181
|
+
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
// src/ui/components/color-picker/styles.ts
|
|
3185
|
+
var COLOR_STYLE_ID = "vela-ui-color";
|
|
3186
|
+
var COLOR_CSS = `
|
|
3187
|
+
.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;}
|
|
3188
|
+
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
3189
|
+
.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);}
|
|
3190
|
+
.vela-color-field-circle{width:26px;height:26px;padding:3px;border:1px solid var(--vela-border-strong);border-radius:4px;background:transparent;overflow:hidden;}
|
|
3191
|
+
.vela-color-field-circle .vela-color-field-swatch{border-radius:2px;box-shadow:none;}
|
|
3192
|
+
.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;}
|
|
3193
|
+
`;
|
|
3194
|
+
|
|
3195
|
+
// src/ui/components/color-picker/view.ts
|
|
3196
|
+
var PALETTE = buildPalette();
|
|
3197
|
+
var CHECKER = transparencyChecker(10);
|
|
3198
|
+
var FIELD_CHECKER = transparencyChecker(8);
|
|
3199
|
+
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
3200
|
+
function addRecent(hex6) {
|
|
3201
|
+
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
3202
|
+
if (i >= 0) recents.splice(i, 1);
|
|
3203
|
+
recents.unshift(hex6);
|
|
3204
|
+
if (recents.length > 10) recents.length = 10;
|
|
3205
|
+
}
|
|
3206
|
+
function buildColorPicker(color, theme, onChange) {
|
|
3207
|
+
const parsed = splitColor(color);
|
|
3208
|
+
let curHex = parsed.hex6;
|
|
3209
|
+
let curAlpha = parsed.alpha;
|
|
3210
|
+
const root = document.createElement("div");
|
|
3211
|
+
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
3212
|
+
const grid = document.createElement("div");
|
|
3213
|
+
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
3214
|
+
const swatches = [];
|
|
3215
|
+
for (const row of PALETTE) {
|
|
3216
|
+
for (const c of row) {
|
|
3217
|
+
const sw = document.createElement("button");
|
|
3218
|
+
sw.type = "button";
|
|
3219
|
+
sw.dataset.c = c;
|
|
3220
|
+
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
3221
|
+
sw.addEventListener("click", (e) => {
|
|
3222
|
+
e.stopPropagation();
|
|
3223
|
+
pickHex(c);
|
|
3224
|
+
});
|
|
3225
|
+
grid.appendChild(sw);
|
|
3226
|
+
swatches.push(sw);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
const paintSelection = () => {
|
|
3230
|
+
for (const sw of swatches) {
|
|
3231
|
+
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
3232
|
+
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
3233
|
+
sw.style.outlineOffset = "2px";
|
|
3234
|
+
sw.style.zIndex = on ? "1" : "";
|
|
3235
|
+
sw.style.position = on ? "relative" : "";
|
|
3236
|
+
}
|
|
3237
|
+
};
|
|
3238
|
+
const recentRow = document.createElement("div");
|
|
3239
|
+
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
3240
|
+
const renderRecents = () => {
|
|
3241
|
+
recentRow.replaceChildren();
|
|
3242
|
+
for (const c of recents) {
|
|
3243
|
+
const sw = document.createElement("button");
|
|
3244
|
+
sw.type = "button";
|
|
3245
|
+
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;`;
|
|
3246
|
+
sw.addEventListener("click", (e) => {
|
|
3247
|
+
e.stopPropagation();
|
|
3248
|
+
pickHex(c);
|
|
3249
|
+
});
|
|
3250
|
+
recentRow.appendChild(sw);
|
|
3251
|
+
}
|
|
3252
|
+
const add = document.createElement("label");
|
|
3253
|
+
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;`;
|
|
3254
|
+
add.textContent = "+";
|
|
3255
|
+
const input = document.createElement("input");
|
|
3256
|
+
input.type = "color";
|
|
3257
|
+
input.value = curHex;
|
|
3258
|
+
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
3259
|
+
input.addEventListener("input", () => pickHex(input.value, true));
|
|
3260
|
+
add.appendChild(input);
|
|
3261
|
+
recentRow.appendChild(add);
|
|
3262
|
+
};
|
|
3263
|
+
const opLabel = document.createElement("div");
|
|
3264
|
+
opLabel.textContent = "Opacity";
|
|
3265
|
+
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
3266
|
+
const opRow = document.createElement("div");
|
|
3267
|
+
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
3268
|
+
const track = document.createElement("div");
|
|
3269
|
+
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
3270
|
+
const knob = document.createElement("div");
|
|
3271
|
+
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;";
|
|
3272
|
+
track.appendChild(knob);
|
|
3273
|
+
const pctBox = document.createElement("div");
|
|
3274
|
+
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;`;
|
|
3275
|
+
opRow.append(track, pctBox);
|
|
3276
|
+
const paintOpacity = () => {
|
|
3277
|
+
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
3278
|
+
knob.style.left = `${curAlpha * 100}%`;
|
|
3279
|
+
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
3280
|
+
};
|
|
3281
|
+
let dragging = false;
|
|
3282
|
+
const onDrag = (clientX) => {
|
|
3283
|
+
const r = track.getBoundingClientRect();
|
|
3284
|
+
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
3285
|
+
paintOpacity();
|
|
3286
|
+
emit();
|
|
3287
|
+
};
|
|
3288
|
+
track.addEventListener("pointerdown", (e) => {
|
|
3289
|
+
e.stopPropagation();
|
|
3290
|
+
dragging = true;
|
|
3291
|
+
track.setPointerCapture(e.pointerId);
|
|
3292
|
+
onDrag(e.clientX);
|
|
3293
|
+
});
|
|
3294
|
+
track.addEventListener("pointermove", (e) => {
|
|
3295
|
+
if (dragging) onDrag(e.clientX);
|
|
3296
|
+
});
|
|
3297
|
+
track.addEventListener("pointerup", () => dragging = false);
|
|
3298
|
+
function emit() {
|
|
3299
|
+
onChange(combineColor(curHex, curAlpha));
|
|
3300
|
+
}
|
|
3301
|
+
function pickHex(hex, custom = false) {
|
|
3302
|
+
curHex = splitColor(hex).hex6;
|
|
3303
|
+
addRecent(curHex);
|
|
3304
|
+
paintSelection();
|
|
3305
|
+
paintOpacity();
|
|
3306
|
+
if (custom) renderRecents();
|
|
3307
|
+
emit();
|
|
3308
|
+
}
|
|
3309
|
+
renderRecents();
|
|
3310
|
+
paintSelection();
|
|
3311
|
+
paintOpacity();
|
|
3312
|
+
root.append(grid, recentRow, opLabel, opRow);
|
|
3313
|
+
return root;
|
|
3314
|
+
}
|
|
3315
|
+
function colorField(theme, getVal, onVal, opts) {
|
|
3316
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
3317
|
+
}
|
|
3318
|
+
var ColorField = class {
|
|
3319
|
+
constructor(opts) {
|
|
3320
|
+
injectStyles(COLOR_STYLE_ID, COLOR_CSS, document);
|
|
3321
|
+
this.theme = opts.theme;
|
|
3322
|
+
this.getVal = opts.getVal;
|
|
3323
|
+
this.onVal = opts.onVal;
|
|
3324
|
+
this.popoverOpts = opts.popover;
|
|
3325
|
+
const trigger = document.createElement("button");
|
|
3326
|
+
trigger.type = "button";
|
|
3327
|
+
if (opts.id) trigger.id = opts.id;
|
|
3328
|
+
trigger.className = opts.shape === "circle" ? "vela-color-field vela-color-field-circle" : "vela-color-field";
|
|
3329
|
+
const swatch = document.createElement("span");
|
|
3330
|
+
swatch.className = "vela-color-field-swatch";
|
|
3331
|
+
trigger.appendChild(swatch);
|
|
3332
|
+
this.el = trigger;
|
|
3333
|
+
this.swatch = swatch;
|
|
3334
|
+
this.paint();
|
|
3335
|
+
trigger.addEventListener("vela-sync", () => this.paint());
|
|
3336
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
3337
|
+
e.preventDefault();
|
|
3338
|
+
trigger.focus({ preventScroll: true });
|
|
3339
|
+
});
|
|
3340
|
+
trigger.addEventListener("click", (e) => {
|
|
3341
|
+
e.stopPropagation();
|
|
3342
|
+
this.toggle();
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
paint() {
|
|
3346
|
+
const v = this.getVal();
|
|
3347
|
+
this.swatch.style.background = `linear-gradient(${v}, ${v}), ${FIELD_CHECKER}`;
|
|
3348
|
+
}
|
|
3349
|
+
toggle() {
|
|
3350
|
+
if (openPopoverTrigger() === this.el) {
|
|
3351
|
+
closeOpenPopovers();
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3354
|
+
const pop = new Popover({
|
|
3355
|
+
trigger: this.el,
|
|
3356
|
+
theme: this.theme,
|
|
3357
|
+
align: this.popoverOpts?.align ?? "end",
|
|
3358
|
+
gap: this.popoverOpts?.gap ?? 6,
|
|
3359
|
+
host: this.popoverOpts?.host,
|
|
3360
|
+
position: this.popoverOpts?.position,
|
|
3361
|
+
boundary: this.popoverOpts?.boundary,
|
|
3362
|
+
zIndex: this.popoverOpts?.zIndex,
|
|
3363
|
+
className: "vela-color-field-pop",
|
|
3364
|
+
content: buildColorPicker(this.getVal(), this.theme, (val) => {
|
|
3365
|
+
this.onVal(val);
|
|
3366
|
+
this.paint();
|
|
3367
|
+
})
|
|
3368
|
+
});
|
|
3369
|
+
pop.show();
|
|
3370
|
+
}
|
|
3371
|
+
};
|
|
3372
|
+
|
|
2084
3373
|
// src/widget/layout-picker.ts
|
|
2085
3374
|
var STYLE_ID = "vela-widget-layout-picker-v14";
|
|
2086
3375
|
var CSS2 = `
|
|
@@ -2837,6 +4126,36 @@ function parseTimeframe(text) {
|
|
|
2837
4126
|
short: `${count}${UNIT_SHORT[key]}`
|
|
2838
4127
|
};
|
|
2839
4128
|
}
|
|
4129
|
+
function timeframeMs(value) {
|
|
4130
|
+
const v = String(value ?? "").trim();
|
|
4131
|
+
if (!v) return NaN;
|
|
4132
|
+
const m = /^(\d*)\s*([a-zA-Z]?)$/.exec(v);
|
|
4133
|
+
if (!m) return NaN;
|
|
4134
|
+
const key = unitKey((m[2] ?? "").toUpperCase());
|
|
4135
|
+
if (key === null) return NaN;
|
|
4136
|
+
if ((m[1] ?? "") === "" && key === "MIN") return NaN;
|
|
4137
|
+
const count = (m[1] ?? "") === "" ? 1 : parseInt(m[1], 10);
|
|
4138
|
+
if (!Number.isFinite(count) || count < 1) return NaN;
|
|
4139
|
+
return count * UNIT_MS[key];
|
|
4140
|
+
}
|
|
4141
|
+
function favoriteTimeframeChips(favorites) {
|
|
4142
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4143
|
+
const out = [];
|
|
4144
|
+
for (const tf of favorites) {
|
|
4145
|
+
if (seen.has(tf)) continue;
|
|
4146
|
+
seen.add(tf);
|
|
4147
|
+
out.push(tf);
|
|
4148
|
+
}
|
|
4149
|
+
return out.sort((a, b) => {
|
|
4150
|
+
const ma = timeframeMs(a);
|
|
4151
|
+
const mb = timeframeMs(b);
|
|
4152
|
+
const aOk = Number.isFinite(ma);
|
|
4153
|
+
const bOk = Number.isFinite(mb);
|
|
4154
|
+
if (aOk && bOk && ma !== mb) return ma - mb;
|
|
4155
|
+
if (aOk !== bOk) return aOk ? -1 : 1;
|
|
4156
|
+
return 0;
|
|
4157
|
+
});
|
|
4158
|
+
}
|
|
2840
4159
|
function timeframeLabel(value) {
|
|
2841
4160
|
const parsed = parseTimeframe(value);
|
|
2842
4161
|
if (!parsed.valid) return value;
|
|
@@ -2889,6 +4208,36 @@ var CSS3 = `
|
|
|
2889
4208
|
color: var(--vela-fg-bright);
|
|
2890
4209
|
}
|
|
2891
4210
|
.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); }
|
|
4211
|
+
/* Timeframe cluster: duration-sorted favorite chips, highlight in place, caret
|
|
4212
|
+
opening the full dropdown. With no favorites the caret is the merged trigger
|
|
4213
|
+
(label + chevron). An unstarred current value sits as an extra chip by the caret. */
|
|
4214
|
+
.vela-widget-tf-group { display: inline-flex; align-items: center; gap: 2px; }
|
|
4215
|
+
.vela-widget-tf-chips { display: inline-flex; align-items: center; gap: 2px; }
|
|
4216
|
+
.vela-widget-tf-chips:empty { display: none; }
|
|
4217
|
+
.vela-widget-tf[data-current='1'] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
4218
|
+
.vela-widget-tf-caret {
|
|
4219
|
+
all: unset;
|
|
4220
|
+
display: inline-flex;
|
|
4221
|
+
align-items: center;
|
|
4222
|
+
justify-content: center;
|
|
4223
|
+
width: 18px;
|
|
4224
|
+
height: 30px;
|
|
4225
|
+
border-radius: 4px;
|
|
4226
|
+
cursor: pointer;
|
|
4227
|
+
color: var(--vela-fg-muted);
|
|
4228
|
+
}
|
|
4229
|
+
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
4230
|
+
.vela-widget-tf-caret[data-solo='1'] {
|
|
4231
|
+
width: auto;
|
|
4232
|
+
padding: 0 6px 0 9px;
|
|
4233
|
+
gap: 4px;
|
|
4234
|
+
color: var(--vela-fg-bright);
|
|
4235
|
+
background: var(--vela-hover-strong);
|
|
4236
|
+
font-size: 13px;
|
|
4237
|
+
font-weight: 550;
|
|
4238
|
+
white-space: nowrap;
|
|
4239
|
+
}
|
|
4240
|
+
.vela-widget-topbar .vela-widget-tf-caret .vela-icon { font-size: 14px; width: 14px; height: 14px; }
|
|
2892
4241
|
.vela-widget-topbar .vela-icon { color: inherit; font-size: 16px; width: 16px; height: 16px; }
|
|
2893
4242
|
/* Width is set in syncHairlines() to exactly one device pixel \u2014 a CSS 1px at
|
|
2894
4243
|
fractional DPR (1.25, 1.5\u2026) straddles two physical pixels and siblings end
|
|
@@ -2993,9 +4342,16 @@ var Topbar = class {
|
|
|
2993
4342
|
this.symbolEl.className = "vela-widget-symbol";
|
|
2994
4343
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
2995
4344
|
if (opts.onSymbolClick) this.symbolEl.addEventListener("click", opts.onSymbolClick);
|
|
2996
|
-
this.
|
|
2997
|
-
this.
|
|
2998
|
-
this.
|
|
4345
|
+
this.tfFavs = [...opts.timeframeFavorites ?? []];
|
|
4346
|
+
this.tfChipsHost = doc.createElement("span");
|
|
4347
|
+
this.tfChipsHost.className = "vela-widget-tf-chips";
|
|
4348
|
+
this.tfCaret = doc.createElement("button");
|
|
4349
|
+
this.tfCaret.className = "vela-widget-tf-caret";
|
|
4350
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
4351
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
4352
|
+
const tfGroup = doc.createElement("span");
|
|
4353
|
+
tfGroup.className = "vela-widget-tf-group";
|
|
4354
|
+
tfGroup.append(this.tfChipsHost, this.tfCaret);
|
|
2999
4355
|
this.styleButton = doc.createElement("button");
|
|
3000
4356
|
this.styleButton.className = "vela-widget-style";
|
|
3001
4357
|
this.renderStyleButton(doc);
|
|
@@ -3035,19 +4391,20 @@ var Topbar = class {
|
|
|
3035
4391
|
d.className = "vela-sep";
|
|
3036
4392
|
return d;
|
|
3037
4393
|
};
|
|
3038
|
-
const leading = [this.symbolEl, sep(),
|
|
4394
|
+
const leading = [this.symbolEl, sep(), tfGroup, sep(), this.styleButton, sep()];
|
|
3039
4395
|
if (this.layoutButton) leading.push(this.layoutButton, sep());
|
|
3040
4396
|
if (indicatorsBtn) leading.push(indicatorsBtn, sep());
|
|
3041
4397
|
this.leftActionsSep = sep();
|
|
3042
4398
|
this.leftActionsSep.hidden = true;
|
|
3043
4399
|
this.el.append(...leading, this.leftActionsHost, this.leftActionsSep, this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
|
|
3044
4400
|
host.appendChild(this.el);
|
|
4401
|
+
this.renderTfChips();
|
|
3045
4402
|
this.onHairlineSync();
|
|
3046
4403
|
this.hairlineRo = new ResizeObserver(this.onHairlineSync);
|
|
3047
4404
|
this.hairlineRo.observe(this.el);
|
|
3048
4405
|
doc.defaultView?.addEventListener("resize", this.onHairlineSync);
|
|
3049
4406
|
this.renderActions();
|
|
3050
|
-
this.tooltips.push(new Tooltip(this.
|
|
4407
|
+
this.tooltips.push(new Tooltip(this.tfCaret, { content: "Timeframe", triggerId: "vela-topbar-tf", host }));
|
|
3051
4408
|
this.tooltips.push(new Tooltip(this.styleButton, { content: "Chart style", triggerId: "vela-topbar-style", host }));
|
|
3052
4409
|
if (this.layoutButton && opts.layout) {
|
|
3053
4410
|
this.tooltips.push(new Tooltip(this.layoutButton, { content: "Layout", triggerId: "vela-topbar-layout", host }));
|
|
@@ -3064,11 +4421,12 @@ var Topbar = class {
|
|
|
3064
4421
|
});
|
|
3065
4422
|
}
|
|
3066
4423
|
this.tfMenu = new Menu({
|
|
3067
|
-
trigger: this.
|
|
4424
|
+
trigger: this.tfCaret,
|
|
3068
4425
|
triggerId: "vela-topbar-tf",
|
|
3069
4426
|
host,
|
|
3070
4427
|
items: this.tfItems(),
|
|
3071
4428
|
onSelect: (id) => opts.onTimeframe(id),
|
|
4429
|
+
onFavorite: (id, on) => opts.onTimeframeFavorite?.(id, on),
|
|
3072
4430
|
// Timeframe labels are two-or-three characters ("1m", "4h", "1D") — the
|
|
3073
4431
|
// stylesheet's default min-width would leave the list mostly empty.
|
|
3074
4432
|
minWidth: "84px"
|
|
@@ -3086,8 +4444,48 @@ var Topbar = class {
|
|
|
3086
4444
|
}
|
|
3087
4445
|
setTimeframe(tf) {
|
|
3088
4446
|
this.timeframe = tf;
|
|
3089
|
-
this.tfButton.textContent = timeframeLabel(tf);
|
|
3090
4447
|
this.tfMenu.setItems(this.tfItems());
|
|
4448
|
+
this.renderTfChips();
|
|
4449
|
+
}
|
|
4450
|
+
/** Reflect the favorite-timeframe set — the quick-switch chips and the dropdown stars. */
|
|
4451
|
+
setTimeframeFavorites(favs) {
|
|
4452
|
+
this.tfFavs = [...favs];
|
|
4453
|
+
this.renderTfChips();
|
|
4454
|
+
this.tfMenu.setItems(this.tfItems());
|
|
4455
|
+
}
|
|
4456
|
+
/** Rebuild the quick-switch chips (current value changed, or the favorite set did). */
|
|
4457
|
+
renderTfChips() {
|
|
4458
|
+
const doc = this.el.ownerDocument;
|
|
4459
|
+
this.tfChipsHost.replaceChildren();
|
|
4460
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
4461
|
+
const chips = stars ? favoriteTimeframeChips(this.tfFavs) : [];
|
|
4462
|
+
const currentIsFav = chips.includes(this.timeframe);
|
|
4463
|
+
const shown = currentIsFav || chips.length === 0 ? chips : [...chips, this.timeframe];
|
|
4464
|
+
for (const tf of shown) {
|
|
4465
|
+
const b = doc.createElement("button");
|
|
4466
|
+
b.className = "vela-widget-tf";
|
|
4467
|
+
const label = timeframeLabel(tf);
|
|
4468
|
+
b.textContent = label;
|
|
4469
|
+
if (tf === this.timeframe) {
|
|
4470
|
+
b.dataset.current = "1";
|
|
4471
|
+
b.setAttribute("aria-current", "true");
|
|
4472
|
+
} else {
|
|
4473
|
+
b.setAttribute("aria-label", `Switch timeframe to ${label}`);
|
|
4474
|
+
b.addEventListener("click", () => this.opts.onTimeframe(tf));
|
|
4475
|
+
}
|
|
4476
|
+
this.tfChipsHost.appendChild(b);
|
|
4477
|
+
}
|
|
4478
|
+
this.tfCaret.replaceChildren();
|
|
4479
|
+
if (chips.length === 0) {
|
|
4480
|
+
this.tfCaret.dataset.solo = "1";
|
|
4481
|
+
this.tfCaret.append(doc.createTextNode(timeframeLabel(this.timeframe)), iconEl("chevron-down", doc));
|
|
4482
|
+
this.tfCaret.setAttribute("aria-label", `Timeframe \u2014 ${timeframeLabel(this.timeframe)}`);
|
|
4483
|
+
} else {
|
|
4484
|
+
delete this.tfCaret.dataset.solo;
|
|
4485
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
4486
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
4487
|
+
}
|
|
4488
|
+
this.onHairlineSync();
|
|
3091
4489
|
}
|
|
3092
4490
|
setPriceStyle(style) {
|
|
3093
4491
|
this.priceStyle = style;
|
|
@@ -3165,9 +4563,9 @@ var Topbar = class {
|
|
|
3165
4563
|
}
|
|
3166
4564
|
/** Reflect a docked side panel's open state on its button — the panels toggle each other,
|
|
3167
4565
|
* so the dock pushes the state rather than the button assuming it. */
|
|
3168
|
-
setPanelActive(id,
|
|
4566
|
+
setPanelActive(id, open2) {
|
|
3169
4567
|
const btn = this.panelBtns.get(id);
|
|
3170
|
-
if (btn) btn.dataset.active =
|
|
4568
|
+
if (btn) btn.dataset.active = open2 ? "1" : "";
|
|
3171
4569
|
}
|
|
3172
4570
|
destroy() {
|
|
3173
4571
|
this.hairlineRo?.disconnect();
|
|
@@ -3205,7 +4603,13 @@ var Topbar = class {
|
|
|
3205
4603
|
}
|
|
3206
4604
|
}
|
|
3207
4605
|
tfItems() {
|
|
3208
|
-
|
|
4606
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
4607
|
+
return this.opts.timeframes.map((tf) => ({
|
|
4608
|
+
id: tf,
|
|
4609
|
+
label: timeframeLabel(tf),
|
|
4610
|
+
checked: tf === this.timeframe,
|
|
4611
|
+
...stars ? { favorite: this.tfFavs.includes(tf) } : {}
|
|
4612
|
+
}));
|
|
3209
4613
|
}
|
|
3210
4614
|
styleItems() {
|
|
3211
4615
|
return priceStyleIds().map((id, i) => ({
|
|
@@ -9490,10 +10894,10 @@ var SidePanel = class {
|
|
|
9490
10894
|
return !this.el.hidden;
|
|
9491
10895
|
}
|
|
9492
10896
|
/** Open/close the panel — a bare call flips it. */
|
|
9493
|
-
toggle(
|
|
9494
|
-
if (
|
|
9495
|
-
this.el.hidden = !
|
|
9496
|
-
this.onOpenChange?.(
|
|
10897
|
+
toggle(open2 = this.el.hidden) {
|
|
10898
|
+
if (open2 === !this.el.hidden) return;
|
|
10899
|
+
this.el.hidden = !open2;
|
|
10900
|
+
this.onOpenChange?.(open2);
|
|
9497
10901
|
}
|
|
9498
10902
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
9499
10903
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -9816,9 +11220,9 @@ var ObjectTree = class extends SidePanel {
|
|
|
9816
11220
|
set groups(next) {
|
|
9817
11221
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
9818
11222
|
}
|
|
9819
|
-
toggle(
|
|
9820
|
-
super.toggle(
|
|
9821
|
-
if (
|
|
11223
|
+
toggle(open2 = this.el.hidden) {
|
|
11224
|
+
super.toggle(open2);
|
|
11225
|
+
if (open2) this.refresh();
|
|
9822
11226
|
}
|
|
9823
11227
|
setSymbol(symbol) {
|
|
9824
11228
|
this.symbolName = parseSymbol(symbol).ticker;
|
|
@@ -10850,9 +12254,9 @@ var DataWindow = class extends SidePanel {
|
|
|
10850
12254
|
this.unsubs = [];
|
|
10851
12255
|
injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
|
|
10852
12256
|
}
|
|
10853
|
-
toggle(
|
|
10854
|
-
super.toggle(
|
|
10855
|
-
if (
|
|
12257
|
+
toggle(open2 = this.el.hidden) {
|
|
12258
|
+
super.toggle(open2);
|
|
12259
|
+
if (open2) this.refresh();
|
|
10856
12260
|
}
|
|
10857
12261
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
10858
12262
|
onChart(chart) {
|
|
@@ -10966,8 +12370,8 @@ var PanelDock = class {
|
|
|
10966
12370
|
}
|
|
10967
12371
|
}
|
|
10968
12372
|
/** 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(
|
|
12373
|
+
toggle(id, open2) {
|
|
12374
|
+
this.entries.find((e) => e.id === id)?.panel.toggle(open2);
|
|
10971
12375
|
}
|
|
10972
12376
|
/** The open panel's id, or null when the column is closed. */
|
|
10973
12377
|
get openId() {
|
|
@@ -10981,8 +12385,8 @@ var PanelDock = class {
|
|
|
10981
12385
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
10982
12386
|
getState() {
|
|
10983
12387
|
const out = {};
|
|
10984
|
-
const
|
|
10985
|
-
if (
|
|
12388
|
+
const open2 = this.openId;
|
|
12389
|
+
if (open2) out.open = open2;
|
|
10986
12390
|
if (this.widths.size > 0) out.widths = Object.fromEntries(this.widths);
|
|
10987
12391
|
return out.open || out.widths ? out : null;
|
|
10988
12392
|
}
|
|
@@ -11015,13 +12419,13 @@ var PanelDock = class {
|
|
|
11015
12419
|
this.entries.sort((a, b) => a.order - b.order);
|
|
11016
12420
|
const stored = this.widths.get(entry.id);
|
|
11017
12421
|
if (stored !== void 0) entry.panel.setWidth(stored);
|
|
11018
|
-
entry.panel.onOpenChange = (
|
|
11019
|
-
if (
|
|
12422
|
+
entry.panel.onOpenChange = (open2) => {
|
|
12423
|
+
if (open2) {
|
|
11020
12424
|
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
11021
12425
|
this.pendingOpen = null;
|
|
11022
12426
|
}
|
|
11023
|
-
this.deps.chrome.setPanelActive(entry.id,
|
|
11024
|
-
if (
|
|
12427
|
+
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
12428
|
+
if (open2) entry.handle?.onOpen?.();
|
|
11025
12429
|
this.deps.changed?.();
|
|
11026
12430
|
};
|
|
11027
12431
|
entry.panel.onWidthChange = (px) => {
|
|
@@ -11063,10 +12467,11 @@ function parseQuery(raw, venues) {
|
|
|
11063
12467
|
function onlyOne(matches) {
|
|
11064
12468
|
return matches.length === 1 ? matches[0] : null;
|
|
11065
12469
|
}
|
|
12470
|
+
var venueOf = (s) => s.prefix ?? s.provider;
|
|
11066
12471
|
function filterSymbols(list, query, limit = 100) {
|
|
11067
|
-
const venues = [...new Set(list.
|
|
12472
|
+
const venues = [...new Set(list.flatMap((s) => [venueOf(s)?.toLowerCase(), s.provider?.toLowerCase()]).filter((p) => !!p))];
|
|
11068
12473
|
const { scope, term } = parseQuery(query, venues);
|
|
11069
|
-
const pool = scope ? list.filter((s) => s.provider?.toLowerCase() === scope) : list;
|
|
12474
|
+
const pool = scope ? list.filter((s) => venueOf(s)?.toLowerCase() === scope || s.provider?.toLowerCase() === scope) : list;
|
|
11070
12475
|
const q = term.toUpperCase();
|
|
11071
12476
|
if (!q) {
|
|
11072
12477
|
if (scope) return [...pool].sort((a, b) => a.ticker.localeCompare(b.ticker)).slice(0, limit);
|
|
@@ -11085,7 +12490,7 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
11085
12490
|
if (t.startsWith(q)) prefix.push(s);
|
|
11086
12491
|
else if (t.includes(q)) substr.push(s);
|
|
11087
12492
|
else if ((s.description ?? "").toUpperCase().includes(q)) desc.push(s);
|
|
11088
|
-
else if (!scope && s.provider?.toLowerCase().includes(qLower)) venue.push(s);
|
|
12493
|
+
else if (!scope && (venueOf(s)?.toLowerCase().includes(qLower) || s.provider?.toLowerCase().includes(qLower))) venue.push(s);
|
|
11089
12494
|
if (prefix.length >= limit) break;
|
|
11090
12495
|
}
|
|
11091
12496
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
@@ -11218,8 +12623,8 @@ var SymbolPicker = class {
|
|
|
11218
12623
|
host: opts.host,
|
|
11219
12624
|
closeOnInteractOutside: true,
|
|
11220
12625
|
content: (body) => body.append(searchRow, this.tabs, this.list),
|
|
11221
|
-
onOpenChange: (
|
|
11222
|
-
if (
|
|
12626
|
+
onOpenChange: (open2) => {
|
|
12627
|
+
if (open2) {
|
|
11223
12628
|
this.input.value = this.seed;
|
|
11224
12629
|
this.seed = "";
|
|
11225
12630
|
this.refresh();
|
|
@@ -11228,7 +12633,7 @@ var SymbolPicker = class {
|
|
|
11228
12633
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length);
|
|
11229
12634
|
}, 0);
|
|
11230
12635
|
}
|
|
11231
|
-
opts.onOpenChange?.(
|
|
12636
|
+
opts.onOpenChange?.(open2);
|
|
11232
12637
|
}
|
|
11233
12638
|
});
|
|
11234
12639
|
this.input.addEventListener("input", () => this.refresh());
|
|
@@ -11237,14 +12642,14 @@ var SymbolPicker = class {
|
|
|
11237
12642
|
else if (e.key === "ArrowUp") this.moveHighlight(-1);
|
|
11238
12643
|
else if (e.key === "Enter") {
|
|
11239
12644
|
const pick = this.rows[this.highlighted];
|
|
11240
|
-
if (pick) this.select(pick.ticker, pick.provider, opts.onSelect);
|
|
12645
|
+
if (pick) this.select(pick.ticker, pick.prefix ?? pick.provider, opts.onSelect);
|
|
11241
12646
|
return;
|
|
11242
12647
|
} else return;
|
|
11243
12648
|
e.preventDefault();
|
|
11244
12649
|
});
|
|
11245
12650
|
this.list.addEventListener("click", (e) => {
|
|
11246
12651
|
const row = e.target.closest(".vela-sp-row");
|
|
11247
|
-
if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.
|
|
12652
|
+
if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.venue, opts.onSelect);
|
|
11248
12653
|
});
|
|
11249
12654
|
}
|
|
11250
12655
|
/** Wire where symbols come from (re-called on every widget rebuild). */
|
|
@@ -11261,9 +12666,9 @@ var SymbolPicker = class {
|
|
|
11261
12666
|
destroy() {
|
|
11262
12667
|
this.dialog.destroy();
|
|
11263
12668
|
}
|
|
11264
|
-
select(ticker,
|
|
12669
|
+
select(ticker, venue, onSelect) {
|
|
11265
12670
|
this.close();
|
|
11266
|
-
onSelect(
|
|
12671
|
+
onSelect(venue ? `${venue}:${ticker}` : ticker);
|
|
11267
12672
|
}
|
|
11268
12673
|
moveHighlight(delta) {
|
|
11269
12674
|
if (!this.rows.length) return;
|
|
@@ -11296,7 +12701,8 @@ var SymbolPicker = class {
|
|
|
11296
12701
|
const row = doc.createElement("div");
|
|
11297
12702
|
row.className = "vela-sp-row";
|
|
11298
12703
|
row.dataset.ticker = s.ticker;
|
|
11299
|
-
|
|
12704
|
+
const venue = s.prefix ?? s.provider;
|
|
12705
|
+
if (venue) row.dataset.venue = venue;
|
|
11300
12706
|
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
11301
12707
|
const main = doc.createElement("span");
|
|
11302
12708
|
main.className = "vela-sp-main";
|
|
@@ -11308,11 +12714,11 @@ var SymbolPicker = class {
|
|
|
11308
12714
|
d.textContent = s.description ?? (s.type ?? "");
|
|
11309
12715
|
main.append(t, d);
|
|
11310
12716
|
row.append(av, main);
|
|
11311
|
-
if (
|
|
12717
|
+
if (venue) {
|
|
11312
12718
|
const badge = doc.createElement("span");
|
|
11313
12719
|
badge.className = "vela-sp-badge";
|
|
11314
|
-
badge.dataset.p = s.provider;
|
|
11315
|
-
badge.textContent =
|
|
12720
|
+
badge.dataset.p = s.provider ?? venue;
|
|
12721
|
+
badge.textContent = venue;
|
|
11316
12722
|
row.appendChild(badge);
|
|
11317
12723
|
}
|
|
11318
12724
|
this.list.appendChild(row);
|
|
@@ -11418,14 +12824,14 @@ var IndicatorPicker = class {
|
|
|
11418
12824
|
closeOnInteractOutside: true,
|
|
11419
12825
|
// The picker adds/removes indicators live — keep the chart undimmed behind it.
|
|
11420
12826
|
content: (body) => body.append(searchRow, this.list),
|
|
11421
|
-
onOpenChange: (
|
|
11422
|
-
this.isOpen =
|
|
11423
|
-
if (
|
|
12827
|
+
onOpenChange: (open2) => {
|
|
12828
|
+
this.isOpen = open2;
|
|
12829
|
+
if (open2) {
|
|
11424
12830
|
this.search.value = "";
|
|
11425
12831
|
this.refresh();
|
|
11426
12832
|
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
11427
12833
|
}
|
|
11428
|
-
opts.onOpenChange?.(
|
|
12834
|
+
opts.onOpenChange?.(open2);
|
|
11429
12835
|
}
|
|
11430
12836
|
});
|
|
11431
12837
|
this.list.addEventListener("click", (e) => {
|
|
@@ -11564,7 +12970,7 @@ var TimeframeQuick = class {
|
|
|
11564
12970
|
draggable: true,
|
|
11565
12971
|
closeOnInteractOutside: true,
|
|
11566
12972
|
content: (body) => body.append(this.input, this.hint),
|
|
11567
|
-
onOpenChange: (
|
|
12973
|
+
onOpenChange: (open2) => opts.onOpenChange?.(open2)
|
|
11568
12974
|
});
|
|
11569
12975
|
this.input.addEventListener("input", () => this.renderHint());
|
|
11570
12976
|
this.input.addEventListener("keydown", (e) => {
|
|
@@ -11641,9 +13047,9 @@ var ShortcutsHelp = class {
|
|
|
11641
13047
|
host,
|
|
11642
13048
|
closeOnInteractOutside: true,
|
|
11643
13049
|
content: (body) => body.appendChild(this.list),
|
|
11644
|
-
onOpenChange: (
|
|
11645
|
-
if (
|
|
11646
|
-
onOpenChange?.(
|
|
13050
|
+
onOpenChange: (open2) => {
|
|
13051
|
+
if (open2) this.refresh();
|
|
13052
|
+
onOpenChange?.(open2);
|
|
11647
13053
|
}
|
|
11648
13054
|
});
|
|
11649
13055
|
}
|
|
@@ -14831,6 +16237,10 @@ function sanitizeState(doc) {
|
|
|
14831
16237
|
const favs = d.favorites.filter((f) => typeof f === "string");
|
|
14832
16238
|
if (favs.length > 0) out.favorites = favs;
|
|
14833
16239
|
}
|
|
16240
|
+
if (Array.isArray(d.timeframeFavorites)) {
|
|
16241
|
+
const favs = d.timeframeFavorites.filter((f) => typeof f === "string");
|
|
16242
|
+
if (favs.length > 0) out.timeframeFavorites = favs;
|
|
16243
|
+
}
|
|
14834
16244
|
const sync = sanitizeSync(d.sync);
|
|
14835
16245
|
if (sync) out.sync = sync;
|
|
14836
16246
|
const tracks = sanitizeTrackSizes(d.trackSizes);
|
|
@@ -16903,6 +18313,17 @@ var DataControl = class {
|
|
|
16903
18313
|
resolve(symbol) {
|
|
16904
18314
|
return this.registry?.resolveSymbol(symbol) ?? null;
|
|
16905
18315
|
}
|
|
18316
|
+
/**
|
|
18317
|
+
* The DISPLAY prefix for `symbol` — the listing venue its descriptor declares
|
|
18318
|
+
* (`NASDAQ` for AAPL) or the resolved provider name. Null while unresolvable.
|
|
18319
|
+
*/
|
|
18320
|
+
displayPrefix(symbol) {
|
|
18321
|
+
return this.registry?.displayPrefix(symbol) ?? null;
|
|
18322
|
+
}
|
|
18323
|
+
/** The canonical `PREFIX:TICKER` form of `symbol`, or null while unresolvable. */
|
|
18324
|
+
canonicalSymbol(symbol) {
|
|
18325
|
+
return this.registry?.canonicalSymbol(symbol) ?? null;
|
|
18326
|
+
}
|
|
16906
18327
|
/**
|
|
16907
18328
|
* The registered provider INSTANCE under `name` — the seam for EXTENDED provider
|
|
16908
18329
|
* surfaces: a provider may implement interfaces beyond the `DataProvider` port
|
|
@@ -17210,6 +18631,7 @@ var LEGEND_CTL_PX = 18;
|
|
|
17210
18631
|
var LEGEND_ROW_PAD_Y = 2;
|
|
17211
18632
|
var LEGEND_ROW_PAD_X = 6;
|
|
17212
18633
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
18634
|
+
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
17213
18635
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
17214
18636
|
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
18637
|
var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
|
|
@@ -17254,6 +18676,11 @@ var InputsUI = class {
|
|
|
17254
18676
|
this.dialogTips = [];
|
|
17255
18677
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
17256
18678
|
this.moveMenu = null;
|
|
18679
|
+
/** Open settings-dialog choice menu (custom dropdown — torn down with the dialog). */
|
|
18680
|
+
this.choicePop = null;
|
|
18681
|
+
/** Open settings-dialog calendar popover (torn down with the dialog). */
|
|
18682
|
+
this.calendarPop = null;
|
|
18683
|
+
this.calendarAnchor = null;
|
|
17257
18684
|
/** Collapsed panes → the master indicator id to keep visible (others hidden in the strip). */
|
|
17258
18685
|
this.paneCollapse = /* @__PURE__ */ new Map();
|
|
17259
18686
|
/** The user folded the indicator legend away behind the chevron — CHART-WIDE: every
|
|
@@ -17279,6 +18706,10 @@ var InputsUI = class {
|
|
|
17279
18706
|
this.onDialogKey = (e) => {
|
|
17280
18707
|
if (e.key === "Escape") {
|
|
17281
18708
|
e.preventDefault();
|
|
18709
|
+
if (isPopoverOpen()) {
|
|
18710
|
+
closeOpenPopovers();
|
|
18711
|
+
return;
|
|
18712
|
+
}
|
|
17282
18713
|
this.closeDialog();
|
|
17283
18714
|
}
|
|
17284
18715
|
};
|
|
@@ -17724,6 +19155,7 @@ var InputsUI = class {
|
|
|
17724
19155
|
titleWrap.appendChild(beta);
|
|
17725
19156
|
}
|
|
17726
19157
|
el.appendChild(titleWrap);
|
|
19158
|
+
el.appendChild(statusEl);
|
|
17727
19159
|
const valuesEl = document.createElement("span");
|
|
17728
19160
|
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
19161
|
el.appendChild(valuesEl);
|
|
@@ -17784,7 +19216,6 @@ var InputsUI = class {
|
|
|
17784
19216
|
close.addEventListener("click", () => this.onRemove?.(id));
|
|
17785
19217
|
controlsEl.appendChild(close);
|
|
17786
19218
|
el.appendChild(controlsEl);
|
|
17787
|
-
el.appendChild(statusEl);
|
|
17788
19219
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
17789
19220
|
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
19221
|
this.syncFoldToggle();
|
|
@@ -17802,7 +19233,7 @@ var InputsUI = class {
|
|
|
17802
19233
|
/**
|
|
17803
19234
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
17804
19235
|
* 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
|
|
19236
|
+
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
17806
19237
|
*/
|
|
17807
19238
|
setStatus(id, status) {
|
|
17808
19239
|
const row = this.rows.get(id);
|
|
@@ -17814,7 +19245,7 @@ var InputsUI = class {
|
|
|
17814
19245
|
return;
|
|
17815
19246
|
}
|
|
17816
19247
|
if (status === "loading") {
|
|
17817
|
-
el.style.cssText =
|
|
19248
|
+
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
19249
|
for (let i = 0; i < 3; i += 1) {
|
|
17819
19250
|
const dot = document.createElement("span");
|
|
17820
19251
|
dot.style.cssText = "width:4px;height:4px;border-radius:50%;background:currentColor;opacity:0.15;flex:none;";
|
|
@@ -17824,7 +19255,7 @@ var InputsUI = class {
|
|
|
17824
19255
|
return;
|
|
17825
19256
|
}
|
|
17826
19257
|
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;`;
|
|
19258
|
+
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
19259
|
}
|
|
17829
19260
|
/** Inject the status keyframes once (idempotent). */
|
|
17830
19261
|
ensureStatusKeyframes() {
|
|
@@ -17877,15 +19308,15 @@ var InputsUI = class {
|
|
|
17877
19308
|
* construction and is shown/hidden with the cluster container.
|
|
17878
19309
|
*/
|
|
17879
19310
|
syncRowActions(row) {
|
|
17880
|
-
const
|
|
17881
|
-
row.controlsEl.style.display =
|
|
19311
|
+
const open2 = row.highlighted;
|
|
19312
|
+
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
17882
19313
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
17883
19314
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
17884
19315
|
if (child === row.extrasEl) {
|
|
17885
|
-
child.style.display =
|
|
19316
|
+
child.style.display = open2 ? "contents" : "none";
|
|
17886
19317
|
continue;
|
|
17887
19318
|
}
|
|
17888
|
-
child.style.display =
|
|
19319
|
+
child.style.display = open2 ? "inline-flex" : "none";
|
|
17889
19320
|
}
|
|
17890
19321
|
}
|
|
17891
19322
|
/** Drop the current selection outline, if any. */
|
|
@@ -17939,7 +19370,8 @@ var InputsUI = class {
|
|
|
17939
19370
|
/**
|
|
17940
19371
|
* Open the indicator's settings as a centered modal over the chart — a themed,
|
|
17941
19372
|
* dependency-free port of the app's tabbed script-settings dialog: a header with the
|
|
17942
|
-
* indicator title,
|
|
19373
|
+
* indicator title, a tab strip (the default "Inputs" tab plus any tab the inputs
|
|
19374
|
+
* declare via `tab=`), a scrollable body per tab that groups inputs by `group=`
|
|
17943
19375
|
* (section headers) and packs `inline=` inputs onto one row, and a sticky footer.
|
|
17944
19376
|
*
|
|
17945
19377
|
* Edits commit LIVE (each change re-runs the indicator), matching the current
|
|
@@ -17969,55 +19401,57 @@ var InputsUI = class {
|
|
|
17969
19401
|
});
|
|
17970
19402
|
const card = document.createElement("div");
|
|
17971
19403
|
card.className = "vela-ind-dialog";
|
|
17972
|
-
card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(
|
|
19404
|
+
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
19405
|
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
19406
|
applyChromeTokens(card, t);
|
|
17975
19407
|
card.addEventListener("keydown", (e) => {
|
|
17976
19408
|
if (e.key !== "Escape") e.stopPropagation();
|
|
17977
19409
|
});
|
|
17978
19410
|
const header = document.createElement("div");
|
|
17979
|
-
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px
|
|
19411
|
+
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 20px;flex:0 0 auto;";
|
|
17980
19412
|
const hTitle = document.createElement("span");
|
|
17981
19413
|
hTitle.textContent = row.settingsTitle;
|
|
17982
|
-
hTitle.style.cssText = "font-weight:600;font-size:
|
|
19414
|
+
hTitle.style.cssText = "font-weight:600;font-size:20px;line-height:28px;";
|
|
17983
19415
|
const closeBtn = document.createElement("button");
|
|
17984
19416
|
closeBtn.type = "button";
|
|
17985
19417
|
closeBtn.setAttribute("aria-label", "Close");
|
|
17986
19418
|
this.dialogTips.push(attachChromeTooltip(closeBtn, { host: this.container, theme: () => this.theme, text: () => "Close" }));
|
|
17987
19419
|
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;";
|
|
19420
|
+
closeBtn.className = "vela-ind-dlg-close";
|
|
17990
19421
|
closeBtn.addEventListener("click", () => this.closeDialog());
|
|
17991
19422
|
header.append(hTitle, closeBtn);
|
|
17992
19423
|
card.appendChild(header);
|
|
17993
19424
|
if (!this.mobileLayout) makeDialogDraggable(card, header, { closeSelector: "button" });
|
|
19425
|
+
const tabDefs = tabInputs(row.inputs);
|
|
17994
19426
|
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);
|
|
19427
|
+
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
19428
|
+
const tabEls = [];
|
|
19429
|
+
const bodies = [];
|
|
19430
|
+
const activate = (idx) => {
|
|
19431
|
+
for (let i = 0; i < tabEls.length; i += 1) {
|
|
19432
|
+
const active = i === idx;
|
|
19433
|
+
const el = tabEls[i];
|
|
19434
|
+
el.style.borderBottomColor = active ? fg : "transparent";
|
|
19435
|
+
el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
|
|
19436
|
+
el.classList.toggle("vela-ind-tab-active", active);
|
|
19437
|
+
bodies[i].style.display = active ? "grid" : "none";
|
|
18011
19438
|
}
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
19439
|
+
};
|
|
19440
|
+
for (const [idx, def] of tabDefs.entries()) {
|
|
19441
|
+
const tab = document.createElement("div");
|
|
19442
|
+
tab.textContent = def.name;
|
|
19443
|
+
tab.className = "vela-ind-tab";
|
|
19444
|
+
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;" : ""}`;
|
|
19445
|
+
tab.addEventListener("click", () => activate(idx));
|
|
19446
|
+
tabs.appendChild(tab);
|
|
19447
|
+
tabEls.push(tab);
|
|
19448
|
+
bodies.push(this.buildTabBody(def.inputs, row));
|
|
18017
19449
|
}
|
|
18018
|
-
card.appendChild(
|
|
19450
|
+
card.appendChild(tabs);
|
|
19451
|
+
for (const body of bodies) card.appendChild(body);
|
|
19452
|
+
activate(0);
|
|
18019
19453
|
const footer = document.createElement("div");
|
|
18020
|
-
footer.style.cssText = `display:flex;justify-content:flex-end;gap:
|
|
19454
|
+
footer.style.cssText = `display:flex;justify-content:flex-end;gap:12px;padding:16px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
|
|
18021
19455
|
footer.append(
|
|
18022
19456
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
18023
19457
|
this.dialogButton("Ok", true, () => this.closeDialog())
|
|
@@ -18029,8 +19463,32 @@ var InputsUI = class {
|
|
|
18029
19463
|
this.backdrop = backdrop;
|
|
18030
19464
|
document.addEventListener("keydown", this.onDialogKey);
|
|
18031
19465
|
}
|
|
19466
|
+
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
19467
|
+
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
19468
|
+
buildTabBody(inputs, row) {
|
|
19469
|
+
const body = document.createElement("div");
|
|
19470
|
+
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;";
|
|
19471
|
+
for (const group of groupInputs(inputs)) {
|
|
19472
|
+
const section = document.createElement("div");
|
|
19473
|
+
section.style.cssText = "display:contents;";
|
|
19474
|
+
if (group.name) {
|
|
19475
|
+
const gh = document.createElement("div");
|
|
19476
|
+
gh.textContent = group.name;
|
|
19477
|
+
const first = body.childElementCount === 0;
|
|
19478
|
+
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;`;
|
|
19479
|
+
section.appendChild(gh);
|
|
19480
|
+
}
|
|
19481
|
+
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
19482
|
+
body.appendChild(section);
|
|
19483
|
+
}
|
|
19484
|
+
return body;
|
|
19485
|
+
}
|
|
18032
19486
|
closeDialog() {
|
|
18033
19487
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
19488
|
+
closeOpenPopovers();
|
|
19489
|
+
this.choicePop = null;
|
|
19490
|
+
this.calendarPop = null;
|
|
19491
|
+
this.calendarAnchor = null;
|
|
18034
19492
|
for (const dispose of this.dialogTips.splice(0)) dispose();
|
|
18035
19493
|
this.backdrop?.remove();
|
|
18036
19494
|
this.backdrop = null;
|
|
@@ -18058,79 +19516,77 @@ var InputsUI = class {
|
|
|
18058
19516
|
buildInputRow(grid, decls, row) {
|
|
18059
19517
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
18060
19518
|
if (decls.length > 1) {
|
|
18061
|
-
const
|
|
18062
|
-
|
|
19519
|
+
const cell2 = document.createElement("div");
|
|
19520
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
|
|
18063
19521
|
for (const d of decls) {
|
|
18064
19522
|
const fit2 = d.type === "color" || d.type === "bool";
|
|
18065
19523
|
const toggleFirst = d.type === "bool";
|
|
18066
19524
|
const id2 = idOf(d);
|
|
18067
19525
|
const item = document.createElement("div");
|
|
18068
|
-
item.style.cssText = `display:flex;align-items:center;gap:
|
|
19526
|
+
item.style.cssText = `display:flex;align-items:center;gap:8px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
|
|
18069
19527
|
const name = nameOf(d);
|
|
18070
19528
|
if (name) {
|
|
18071
19529
|
const lbl = document.createElement("label");
|
|
18072
19530
|
lbl.htmlFor = id2;
|
|
18073
19531
|
lbl.textContent = name;
|
|
18074
|
-
lbl.style.cssText = `font-size:
|
|
19532
|
+
lbl.style.cssText = `font-size:14px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
|
|
18075
19533
|
item.appendChild(lbl);
|
|
18076
19534
|
}
|
|
18077
19535
|
const cw2 = document.createElement("div");
|
|
18078
|
-
cw2.style.cssText = fit2 ? "" : "width:
|
|
19536
|
+
cw2.style.cssText = fit2 ? "" : "width:100px;";
|
|
18079
19537
|
cw2.appendChild(this.buildControl(row, d, id2));
|
|
18080
19538
|
item.appendChild(cw2);
|
|
18081
|
-
|
|
19539
|
+
cell2.appendChild(item);
|
|
18082
19540
|
}
|
|
18083
19541
|
const lastTip = [...decls].reverse().find((d) => d.tooltip)?.tooltip;
|
|
18084
|
-
if (lastTip)
|
|
18085
|
-
grid.appendChild(
|
|
19542
|
+
if (lastTip) cell2.appendChild(this.infoButton(lastTip));
|
|
19543
|
+
grid.appendChild(cell2);
|
|
18086
19544
|
return;
|
|
18087
19545
|
}
|
|
18088
19546
|
const lead = decls[0];
|
|
18089
19547
|
const id = idOf(lead);
|
|
18090
19548
|
if (lead.type === "bool") {
|
|
18091
|
-
const
|
|
18092
|
-
|
|
19549
|
+
const cell2 = document.createElement("div");
|
|
19550
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;align-items:center;gap:8px;";
|
|
18093
19551
|
const lbl = document.createElement("label");
|
|
18094
|
-
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:
|
|
19552
|
+
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;";
|
|
18095
19553
|
lbl.appendChild(this.buildControl(row, lead, id));
|
|
18096
19554
|
lbl.appendChild(document.createTextNode(nameOf(lead)));
|
|
18097
|
-
|
|
18098
|
-
if (lead.tooltip)
|
|
18099
|
-
grid.appendChild(
|
|
18100
|
-
grid.appendChild(document.createElement("div"));
|
|
19555
|
+
cell2.appendChild(lbl);
|
|
19556
|
+
if (lead.tooltip) cell2.appendChild(this.infoButton(lead.tooltip));
|
|
19557
|
+
grid.appendChild(cell2);
|
|
18101
19558
|
return;
|
|
18102
19559
|
}
|
|
18103
19560
|
if (lead.type === "text_area") {
|
|
18104
|
-
const
|
|
18105
|
-
|
|
19561
|
+
const cell2 = document.createElement("div");
|
|
19562
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
|
|
18106
19563
|
const head = document.createElement("div");
|
|
18107
19564
|
head.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;";
|
|
18108
19565
|
const lbl = document.createElement("label");
|
|
18109
19566
|
lbl.htmlFor = id;
|
|
18110
19567
|
lbl.textContent = nameOf(lead);
|
|
18111
|
-
lbl.style.cssText = "font-size:
|
|
19568
|
+
lbl.style.cssText = "font-size:14px;";
|
|
18112
19569
|
head.appendChild(lbl);
|
|
18113
19570
|
if (lead.tooltip) head.appendChild(this.infoButton(lead.tooltip));
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
grid.appendChild(
|
|
19571
|
+
cell2.appendChild(head);
|
|
19572
|
+
cell2.appendChild(this.buildControl(row, lead, id));
|
|
19573
|
+
grid.appendChild(cell2);
|
|
18117
19574
|
return;
|
|
18118
19575
|
}
|
|
18119
19576
|
const label = document.createElement("label");
|
|
18120
19577
|
label.htmlFor = id;
|
|
18121
19578
|
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;";
|
|
19579
|
+
label.style.cssText = "font-size:14px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
18125
19580
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
19581
|
+
const cell = document.createElement("div");
|
|
19582
|
+
cell.style.cssText = "display:flex;align-items:center;gap:8px;justify-self:start;";
|
|
18126
19583
|
const cw = document.createElement("div");
|
|
18127
|
-
cw.style.cssText = fit ? "" : "width:
|
|
19584
|
+
cw.style.cssText = fit ? "" : "width:100px;";
|
|
18128
19585
|
cw.appendChild(this.buildControl(row, lead, id));
|
|
18129
|
-
|
|
18130
|
-
if (lead.tooltip)
|
|
19586
|
+
cell.appendChild(cw);
|
|
19587
|
+
if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
|
|
18131
19588
|
grid.appendChild(label);
|
|
18132
|
-
grid.appendChild(
|
|
18133
|
-
grid.appendChild(document.createElement("div"));
|
|
19589
|
+
grid.appendChild(cell);
|
|
18134
19590
|
}
|
|
18135
19591
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
18136
19592
|
buildControl(row, inp, id) {
|
|
@@ -18139,107 +19595,30 @@ var InputsUI = class {
|
|
|
18139
19595
|
row.values[inp.key] = value;
|
|
18140
19596
|
this.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
18141
19597
|
};
|
|
18142
|
-
const ctrl = this.ctrlStyle();
|
|
18143
19598
|
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),
|
|
19599
|
+
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), emit);
|
|
19600
|
+
if (inp.type === "source") return this.select(id, SOURCES, String(current), emit);
|
|
18146
19601
|
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;
|
|
19602
|
+
const field = colorField(this.theme, () => String(row.values[inp.key] ?? inp.defval), (v) => emit(v), { shape: "circle", id });
|
|
19603
|
+
return field;
|
|
18154
19604
|
}
|
|
18155
19605
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
18156
|
-
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current),
|
|
19606
|
+
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), emit);
|
|
18157
19607
|
if (inp.type === "session") return this.buildSession(id, String(current), emit);
|
|
18158
19608
|
if (inp.type === "time") return this.buildTime(id, Number(current) || 0, emit);
|
|
18159
19609
|
if (inp.type === "text_area") return this.buildTextArea(id, String(current), emit);
|
|
18160
19610
|
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;
|
|
19611
|
+
return this.buildNumber(id, Number(current), inp, emit);
|
|
18193
19612
|
}
|
|
18194
19613
|
return this.buildTextField(id, String(current), emit);
|
|
18195
19614
|
}
|
|
18196
19615
|
/** A plain text field that commits on blur / Enter (shared by string inputs and the pickerless symbol field). */
|
|
18197
19616
|
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;
|
|
19617
|
+
return new TextField({ id, value: current, size: "md", onChange: emit }).el;
|
|
18217
19618
|
}
|
|
18218
19619
|
/** A square check-toggle (filled + check when on) — replaces the raw checkbox. */
|
|
18219
19620
|
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;
|
|
19621
|
+
return new Switch({ id, checked, size: "md", tone: "bright", onChange }).el;
|
|
18243
19622
|
}
|
|
18244
19623
|
/** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
|
|
18245
19624
|
infoButton(tooltip2) {
|
|
@@ -18251,45 +19630,22 @@ var InputsUI = class {
|
|
|
18251
19630
|
return b;
|
|
18252
19631
|
}
|
|
18253
19632
|
/** 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,
|
|
19633
|
+
select(id, options, current, emit) {
|
|
19634
|
+
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, emit);
|
|
18256
19635
|
}
|
|
18257
19636
|
/** 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;
|
|
19637
|
+
selectPairs(id, pairs, current, onChange) {
|
|
19638
|
+
return new Select({
|
|
19639
|
+
id,
|
|
19640
|
+
options: pairs,
|
|
19641
|
+
value: current,
|
|
19642
|
+
size: "md",
|
|
19643
|
+
theme: this.theme,
|
|
19644
|
+
list: this.listPopover(),
|
|
19645
|
+
onChange
|
|
19646
|
+
}).el;
|
|
18291
19647
|
}
|
|
18292
|
-
/** `input.session` → two HH:MM
|
|
19648
|
+
/** `input.session` → two typeable HH:MM comboboxes committing a `HHMM-HHMM` session string. */
|
|
18293
19649
|
buildSession(id, current, emit) {
|
|
18294
19650
|
const [start, end] = sessionToTimes(current);
|
|
18295
19651
|
const wrap = document.createElement("div");
|
|
@@ -18297,15 +19653,16 @@ var InputsUI = class {
|
|
|
18297
19653
|
let from = start;
|
|
18298
19654
|
let to = end;
|
|
18299
19655
|
const commit = () => emit(timesToSession(from, to));
|
|
18300
|
-
|
|
18301
|
-
from
|
|
18302
|
-
|
|
18303
|
-
|
|
18304
|
-
|
|
18305
|
-
to
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
|
|
19656
|
+
wrap.append(
|
|
19657
|
+
this.timeCombobox(id, from, 86, (v) => {
|
|
19658
|
+
from = v;
|
|
19659
|
+
commit();
|
|
19660
|
+
}),
|
|
19661
|
+
this.timeCombobox(`${id}-end`, to, 86, (v) => {
|
|
19662
|
+
to = v;
|
|
19663
|
+
commit();
|
|
19664
|
+
})
|
|
19665
|
+
);
|
|
18309
19666
|
return wrap;
|
|
18310
19667
|
}
|
|
18311
19668
|
/** `input.time` → a date picker + an HH:MM dropdown, committing an epoch-ms timestamp. */
|
|
@@ -18320,20 +19677,15 @@ var InputsUI = class {
|
|
|
18320
19677
|
const ms = Date.parse(`${date}T${time}:00`);
|
|
18321
19678
|
if (Number.isFinite(ms)) emit(ms);
|
|
18322
19679
|
};
|
|
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;
|
|
19680
|
+
const dateField = this.dateField(id, date, (v) => {
|
|
19681
|
+
date = v;
|
|
18330
19682
|
commit();
|
|
18331
19683
|
});
|
|
18332
|
-
const timeSel = this.
|
|
19684
|
+
const timeSel = this.timeCombobox(`${id}-time`, time, 86, (v) => {
|
|
18333
19685
|
time = v;
|
|
18334
19686
|
commit();
|
|
18335
19687
|
});
|
|
18336
|
-
wrap.append(
|
|
19688
|
+
wrap.append(dateField, timeSel);
|
|
18337
19689
|
return wrap;
|
|
18338
19690
|
}
|
|
18339
19691
|
/** `input.text_area` → a multi-line textarea, committed on blur. */
|
|
@@ -18352,9 +19704,261 @@ var InputsUI = class {
|
|
|
18352
19704
|
});
|
|
18353
19705
|
return ta;
|
|
18354
19706
|
}
|
|
18355
|
-
/** A fixed-width HH:MM
|
|
18356
|
-
|
|
18357
|
-
|
|
19707
|
+
/** A fixed-width HH:MM combobox (typeable, plus a 30-minute-step dropdown) used by session + time. */
|
|
19708
|
+
timeCombobox(id, current, width, onChange) {
|
|
19709
|
+
const wrap = document.createElement("div");
|
|
19710
|
+
wrap.className = "vela-ind-combo";
|
|
19711
|
+
wrap.style.cssText = `position:relative;width:${width}px;`;
|
|
19712
|
+
const input = document.createElement("input");
|
|
19713
|
+
input.type = "text";
|
|
19714
|
+
input.id = id;
|
|
19715
|
+
input.value = current;
|
|
19716
|
+
input.autocomplete = "off";
|
|
19717
|
+
input.spellcheck = false;
|
|
19718
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
19719
|
+
const chevron = document.createElement("button");
|
|
19720
|
+
chevron.type = "button";
|
|
19721
|
+
chevron.tabIndex = -1;
|
|
19722
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
19723
|
+
chevron.setAttribute("aria-label", "Open time list");
|
|
19724
|
+
chevron.innerHTML = CLOCK_SVG;
|
|
19725
|
+
let value = current;
|
|
19726
|
+
const commitTyped = () => {
|
|
19727
|
+
const next = normalizeTimeInput(input.value);
|
|
19728
|
+
if (!next) {
|
|
19729
|
+
input.value = value;
|
|
19730
|
+
return;
|
|
19731
|
+
}
|
|
19732
|
+
input.value = next;
|
|
19733
|
+
if (next !== value) {
|
|
19734
|
+
value = next;
|
|
19735
|
+
onChange(next);
|
|
19736
|
+
}
|
|
19737
|
+
};
|
|
19738
|
+
input.addEventListener("blur", () => {
|
|
19739
|
+
if (isPopoverOpen()) return;
|
|
19740
|
+
commitTyped();
|
|
19741
|
+
});
|
|
19742
|
+
const pick = (v) => {
|
|
19743
|
+
value = v;
|
|
19744
|
+
input.value = v;
|
|
19745
|
+
onChange(v);
|
|
19746
|
+
};
|
|
19747
|
+
const openList = () => {
|
|
19748
|
+
this.choicePop = toggleSelectList(wrap, TIME_OPTIONS, value, pick, {
|
|
19749
|
+
...this.listPopover(),
|
|
19750
|
+
onClose: () => {
|
|
19751
|
+
this.choicePop = null;
|
|
19752
|
+
}
|
|
19753
|
+
});
|
|
19754
|
+
};
|
|
19755
|
+
input.addEventListener("keydown", (e) => {
|
|
19756
|
+
if (e.key === "Enter") {
|
|
19757
|
+
e.preventDefault();
|
|
19758
|
+
commitTyped();
|
|
19759
|
+
input.blur();
|
|
19760
|
+
}
|
|
19761
|
+
if (e.key === "ArrowDown") {
|
|
19762
|
+
e.preventDefault();
|
|
19763
|
+
openList();
|
|
19764
|
+
}
|
|
19765
|
+
});
|
|
19766
|
+
const open2 = (e) => {
|
|
19767
|
+
e.preventDefault();
|
|
19768
|
+
e.stopPropagation();
|
|
19769
|
+
openList();
|
|
19770
|
+
};
|
|
19771
|
+
input.addEventListener("click", open2);
|
|
19772
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
19773
|
+
chevron.addEventListener("click", open2);
|
|
19774
|
+
wrap.append(input, chevron);
|
|
19775
|
+
return wrap;
|
|
19776
|
+
}
|
|
19777
|
+
/** A typeable YYYY-MM-DD field that also opens a themed month calendar. */
|
|
19778
|
+
dateField(id, current, onChange) {
|
|
19779
|
+
const wrap = document.createElement("div");
|
|
19780
|
+
wrap.className = "vela-ind-combo";
|
|
19781
|
+
wrap.style.cssText = "position:relative;width:128px;";
|
|
19782
|
+
const input = document.createElement("input");
|
|
19783
|
+
input.type = "text";
|
|
19784
|
+
input.id = id;
|
|
19785
|
+
input.value = current;
|
|
19786
|
+
input.autocomplete = "off";
|
|
19787
|
+
input.spellcheck = false;
|
|
19788
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
19789
|
+
const chevron = document.createElement("button");
|
|
19790
|
+
chevron.type = "button";
|
|
19791
|
+
chevron.tabIndex = -1;
|
|
19792
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
19793
|
+
chevron.setAttribute("aria-label", "Open calendar");
|
|
19794
|
+
chevron.innerHTML = CALENDAR_SVG;
|
|
19795
|
+
let value = current;
|
|
19796
|
+
const commitTyped = () => {
|
|
19797
|
+
const next = normalizeDateInput(input.value);
|
|
19798
|
+
if (!next) {
|
|
19799
|
+
input.value = value;
|
|
19800
|
+
return;
|
|
19801
|
+
}
|
|
19802
|
+
input.value = next;
|
|
19803
|
+
if (next !== value) {
|
|
19804
|
+
value = next;
|
|
19805
|
+
onChange(next);
|
|
19806
|
+
}
|
|
19807
|
+
};
|
|
19808
|
+
input.addEventListener("blur", () => {
|
|
19809
|
+
if (isPopoverOpen()) return;
|
|
19810
|
+
commitTyped();
|
|
19811
|
+
});
|
|
19812
|
+
const pick = (v) => {
|
|
19813
|
+
value = v;
|
|
19814
|
+
input.value = v;
|
|
19815
|
+
onChange(v);
|
|
19816
|
+
};
|
|
19817
|
+
input.addEventListener("keydown", (e) => {
|
|
19818
|
+
if (e.key === "Enter") {
|
|
19819
|
+
e.preventDefault();
|
|
19820
|
+
commitTyped();
|
|
19821
|
+
input.blur();
|
|
19822
|
+
}
|
|
19823
|
+
if (e.key === "ArrowDown") {
|
|
19824
|
+
e.preventDefault();
|
|
19825
|
+
this.openCalendar(wrap, value, pick);
|
|
19826
|
+
}
|
|
19827
|
+
});
|
|
19828
|
+
const open2 = (e) => {
|
|
19829
|
+
e.preventDefault();
|
|
19830
|
+
e.stopPropagation();
|
|
19831
|
+
if (openPopoverTrigger() === wrap) {
|
|
19832
|
+
closeOpenPopovers();
|
|
19833
|
+
return;
|
|
19834
|
+
}
|
|
19835
|
+
this.openCalendar(wrap, value, pick);
|
|
19836
|
+
};
|
|
19837
|
+
input.addEventListener("click", open2);
|
|
19838
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
19839
|
+
chevron.addEventListener("click", open2);
|
|
19840
|
+
wrap.append(input, chevron);
|
|
19841
|
+
return wrap;
|
|
19842
|
+
}
|
|
19843
|
+
/** Numeric field with hover steppers on the right (no fill until a triangle is hovered). */
|
|
19844
|
+
buildNumber(id, current, inp, emit) {
|
|
19845
|
+
return new NumberInput({
|
|
19846
|
+
id,
|
|
19847
|
+
value: current,
|
|
19848
|
+
min: inp.min,
|
|
19849
|
+
max: inp.max,
|
|
19850
|
+
step: inp.step ?? (inp.type === "int" ? 1 : 0.1),
|
|
19851
|
+
integer: inp.type === "int",
|
|
19852
|
+
size: "md",
|
|
19853
|
+
commit: "blur",
|
|
19854
|
+
onChange: emit
|
|
19855
|
+
}).el;
|
|
19856
|
+
}
|
|
19857
|
+
/** Placement shared by the indicator dialog's dropdowns and the time combobox. */
|
|
19858
|
+
listPopover() {
|
|
19859
|
+
return {
|
|
19860
|
+
theme: this.theme,
|
|
19861
|
+
matchWidth: true,
|
|
19862
|
+
boundary: this.dialog ?? "viewport",
|
|
19863
|
+
boundaryInset: 8,
|
|
19864
|
+
gap: 4
|
|
19865
|
+
};
|
|
19866
|
+
}
|
|
19867
|
+
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
19868
|
+
openCalendar(anchor, current, onPick) {
|
|
19869
|
+
const parsed = parseIsoDate(current);
|
|
19870
|
+
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
19871
|
+
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
19872
|
+
const selected = parsed ? isoDate(parsed) : current;
|
|
19873
|
+
const pop = new Popover({
|
|
19874
|
+
trigger: anchor,
|
|
19875
|
+
theme: this.theme,
|
|
19876
|
+
className: "vela-ind-cal",
|
|
19877
|
+
boundary: this.dialog ?? "viewport",
|
|
19878
|
+
boundaryInset: 8,
|
|
19879
|
+
gap: 4,
|
|
19880
|
+
onClose: () => {
|
|
19881
|
+
this.calendarPop = null;
|
|
19882
|
+
this.calendarAnchor = null;
|
|
19883
|
+
},
|
|
19884
|
+
content: (el) => {
|
|
19885
|
+
const title = document.createElement("div");
|
|
19886
|
+
title.className = "vela-ind-cal-title";
|
|
19887
|
+
const prev2 = document.createElement("button");
|
|
19888
|
+
prev2.type = "button";
|
|
19889
|
+
prev2.className = "vela-ind-cal-nav";
|
|
19890
|
+
prev2.setAttribute("aria-label", "Previous month");
|
|
19891
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
19892
|
+
const next = document.createElement("button");
|
|
19893
|
+
next.type = "button";
|
|
19894
|
+
next.className = "vela-ind-cal-nav";
|
|
19895
|
+
next.setAttribute("aria-label", "Next month");
|
|
19896
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
19897
|
+
const head = document.createElement("div");
|
|
19898
|
+
head.className = "vela-ind-cal-head";
|
|
19899
|
+
head.append(prev2, title, next);
|
|
19900
|
+
const week = document.createElement("div");
|
|
19901
|
+
week.className = "vela-ind-cal-week";
|
|
19902
|
+
for (const d of WEEKDAY_LABELS) {
|
|
19903
|
+
const cell = document.createElement("span");
|
|
19904
|
+
cell.textContent = d;
|
|
19905
|
+
week.appendChild(cell);
|
|
19906
|
+
}
|
|
19907
|
+
const grid = document.createElement("div");
|
|
19908
|
+
grid.className = "vela-ind-cal-grid";
|
|
19909
|
+
const paint = () => {
|
|
19910
|
+
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
19911
|
+
grid.replaceChildren();
|
|
19912
|
+
const first = new Date(year, month, 1);
|
|
19913
|
+
const startPad = first.getDay();
|
|
19914
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
19915
|
+
const today = isoDate(/* @__PURE__ */ new Date());
|
|
19916
|
+
for (let i = 0; i < startPad; i++) {
|
|
19917
|
+
const blank = document.createElement("span");
|
|
19918
|
+
blank.className = "vela-ind-cal-blank";
|
|
19919
|
+
grid.appendChild(blank);
|
|
19920
|
+
}
|
|
19921
|
+
for (let day = 1; day <= days; day++) {
|
|
19922
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
19923
|
+
const b = document.createElement("button");
|
|
19924
|
+
b.type = "button";
|
|
19925
|
+
b.className = "vela-ind-cal-day";
|
|
19926
|
+
b.textContent = String(day);
|
|
19927
|
+
if (iso === selected) b.dataset.checked = "1";
|
|
19928
|
+
if (iso === today) b.dataset.today = "1";
|
|
19929
|
+
b.addEventListener("click", (e) => {
|
|
19930
|
+
e.stopPropagation();
|
|
19931
|
+
pop.hide();
|
|
19932
|
+
onPick(iso);
|
|
19933
|
+
});
|
|
19934
|
+
grid.appendChild(b);
|
|
19935
|
+
}
|
|
19936
|
+
};
|
|
19937
|
+
prev2.addEventListener("click", (e) => {
|
|
19938
|
+
e.stopPropagation();
|
|
19939
|
+
month -= 1;
|
|
19940
|
+
if (month < 0) {
|
|
19941
|
+
month = 11;
|
|
19942
|
+
year -= 1;
|
|
19943
|
+
}
|
|
19944
|
+
paint();
|
|
19945
|
+
});
|
|
19946
|
+
next.addEventListener("click", (e) => {
|
|
19947
|
+
e.stopPropagation();
|
|
19948
|
+
month += 1;
|
|
19949
|
+
if (month > 11) {
|
|
19950
|
+
month = 0;
|
|
19951
|
+
year += 1;
|
|
19952
|
+
}
|
|
19953
|
+
paint();
|
|
19954
|
+
});
|
|
19955
|
+
paint();
|
|
19956
|
+
el.append(head, week, grid);
|
|
19957
|
+
}
|
|
19958
|
+
});
|
|
19959
|
+
this.calendarPop = pop;
|
|
19960
|
+
this.calendarAnchor = anchor;
|
|
19961
|
+
pop.show();
|
|
18358
19962
|
}
|
|
18359
19963
|
/**
|
|
18360
19964
|
* `input.symbol` → a field that opens the host's ticker-selection UI when a picker is wired
|
|
@@ -18382,7 +19986,7 @@ var InputsUI = class {
|
|
|
18382
19986
|
}
|
|
18383
19987
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
18384
19988
|
ctrlStyle() {
|
|
18385
|
-
return `width:100%;box-sizing:border-box;height:
|
|
19989
|
+
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
19990
|
}
|
|
18387
19991
|
/** Whether the active theme is dark (drives the dialog's `color-scheme`). */
|
|
18388
19992
|
isDarkTheme() {
|
|
@@ -18401,21 +20005,6 @@ var InputsUI = class {
|
|
|
18401
20005
|
strongText() {
|
|
18402
20006
|
return "var(--vela-fg-bright)";
|
|
18403
20007
|
}
|
|
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
20008
|
dialogButton(label, primary, onClick) {
|
|
18420
20009
|
const b = document.createElement("button");
|
|
18421
20010
|
b.type = "button";
|
|
@@ -18429,6 +20018,21 @@ function nameOf(inp) {
|
|
|
18429
20018
|
const t = inp.title;
|
|
18430
20019
|
return t.length > 0 ? t.charAt(0).toUpperCase() + t.slice(1) : "";
|
|
18431
20020
|
}
|
|
20021
|
+
var DEFAULT_INPUT_TAB = "Inputs";
|
|
20022
|
+
function tabInputs(inputs) {
|
|
20023
|
+
const byTab = /* @__PURE__ */ new Map();
|
|
20024
|
+
for (const inp of inputs) {
|
|
20025
|
+
const name = inp.tab && inp.tab.length > 0 ? inp.tab : DEFAULT_INPUT_TAB;
|
|
20026
|
+
if (!byTab.has(name)) byTab.set(name, []);
|
|
20027
|
+
byTab.get(name).push(inp);
|
|
20028
|
+
}
|
|
20029
|
+
const names = [...byTab.keys()];
|
|
20030
|
+
if (names.includes(DEFAULT_INPUT_TAB)) {
|
|
20031
|
+
names.splice(names.indexOf(DEFAULT_INPUT_TAB), 1);
|
|
20032
|
+
names.unshift(DEFAULT_INPUT_TAB);
|
|
20033
|
+
}
|
|
20034
|
+
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
20035
|
+
}
|
|
18432
20036
|
function groupInputs(inputs) {
|
|
18433
20037
|
const order = [];
|
|
18434
20038
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -18457,9 +20061,10 @@ function groupInputs(inputs) {
|
|
|
18457
20061
|
});
|
|
18458
20062
|
}
|
|
18459
20063
|
var CHECK_SVG = iconAt("check", 12);
|
|
18460
|
-
var
|
|
20064
|
+
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
20065
|
+
var CLOCK_SVG = iconAt("clock", 14);
|
|
18461
20066
|
var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
|
|
18462
|
-
var DIALOG_STYLE_REV = "
|
|
20067
|
+
var DIALOG_STYLE_REV = "25";
|
|
18463
20068
|
function ensureDialogStyles() {
|
|
18464
20069
|
if (typeof document === "undefined") return;
|
|
18465
20070
|
const existing = document.getElementById(DIALOG_STYLE_ID2);
|
|
@@ -18468,30 +20073,47 @@ function ensureDialogStyles() {
|
|
|
18468
20073
|
s.id = DIALOG_STYLE_ID2;
|
|
18469
20074
|
s.dataset.rev = DIALOG_STYLE_REV;
|
|
18470
20075
|
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-
|
|
20076
|
+
.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;}
|
|
20077
|
+
.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);}
|
|
20078
|
+
.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);}
|
|
20079
|
+
.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;}
|
|
20080
|
+
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
20081
|
+
.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;}
|
|
20082
|
+
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
20083
|
+
.vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
|
|
20084
|
+
.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;}
|
|
20085
|
+
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
20086
|
+
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
20087
|
+
.vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
|
|
20088
|
+
.vela-ind-cal-week span{line-height:20px;}
|
|
20089
|
+
.vela-ind-cal-blank{width:28px;height:28px;}
|
|
20090
|
+
.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;}
|
|
20091
|
+
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
20092
|
+
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
20093
|
+
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
20094
|
+
.vela-ind-dialog ::-webkit-scrollbar{width:9px;height:9px;}
|
|
18479
20095
|
.vela-ind-dialog ::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}
|
|
18480
20096
|
.vela-ind-dialog ::-webkit-scrollbar-track{background:transparent;}
|
|
20097
|
+
.vela-ind-dialog ::-webkit-scrollbar-button{display:none;width:0;height:0;}
|
|
20098
|
+
.vela-ind-tab{transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
20099
|
+
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
18481
20100
|
.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
20101
|
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
18483
20102
|
.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
20103
|
.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
20104
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
18486
20105
|
.vela-ind-close:hover{color:var(--vela-danger) !important;background-color:var(--vela-hover-strong);}
|
|
20106
|
+
.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;}
|
|
20107
|
+
.vela-ind-dlg-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
20108
|
+
.vela-ind-dlg-close svg{width:15px;height:15px;display:block;flex:none;}
|
|
18487
20109
|
.vela-ind-fold{transition:border-color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
|
|
18488
20110
|
.vela-ind-fold:hover{border-color:var(--vela-border-strong);opacity:0.9;}
|
|
18489
20111
|
.vela-ind-menuitem{background:transparent;transition:background var(--vela-dur-fast) ease;}
|
|
18490
20112
|
.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;}`;
|
|
20113
|
+
.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;}
|
|
20114
|
+
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);border-color:var(--vela-fg-muted);}
|
|
20115
|
+
.vela-ind-btn-primary{border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
20116
|
+
.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
20117
|
if (!existing) document.head.appendChild(s);
|
|
18496
20118
|
}
|
|
18497
20119
|
var TIMEFRAME_OPTIONS = [
|
|
@@ -18516,6 +20138,37 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
18516
20138
|
const v = `${hh}:${mm}`;
|
|
18517
20139
|
return { value: v, label: v };
|
|
18518
20140
|
});
|
|
20141
|
+
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
20142
|
+
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
20143
|
+
function normalizeDateInput(raw) {
|
|
20144
|
+
const t = raw.trim();
|
|
20145
|
+
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
|
|
20146
|
+
if (!m) return null;
|
|
20147
|
+
const y = Number(m[1]);
|
|
20148
|
+
const mo = Number(m[2]);
|
|
20149
|
+
const d = Number(m[3]);
|
|
20150
|
+
const dt = new Date(y, mo - 1, d);
|
|
20151
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
|
|
20152
|
+
return isoDate(dt);
|
|
20153
|
+
}
|
|
20154
|
+
function parseIsoDate(raw) {
|
|
20155
|
+
const iso = normalizeDateInput(raw);
|
|
20156
|
+
if (!iso) return null;
|
|
20157
|
+
const [y, mo, d] = iso.split("-").map(Number);
|
|
20158
|
+
return new Date(y, mo - 1, d);
|
|
20159
|
+
}
|
|
20160
|
+
function isoDate(d) {
|
|
20161
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
20162
|
+
}
|
|
20163
|
+
function normalizeTimeInput(raw) {
|
|
20164
|
+
const t = raw.trim();
|
|
20165
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
20166
|
+
if (!m) return null;
|
|
20167
|
+
const hh = Number(m[1]);
|
|
20168
|
+
const mm = Number(m[2]);
|
|
20169
|
+
if (hh > 23 || mm > 59) return null;
|
|
20170
|
+
return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
|
|
20171
|
+
}
|
|
18519
20172
|
function sessionToTimes(session) {
|
|
18520
20173
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(session.trim());
|
|
18521
20174
|
if (!m) return ["09:00", "16:00"];
|
|
@@ -22347,6 +24000,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
|
|
|
22347
24000
|
const rx = right ? width + 2 : Math.max(x1, x2);
|
|
22348
24001
|
return [lx, yAt(lx), rx, yAt(rx)];
|
|
22349
24002
|
}
|
|
24003
|
+
function uprightLineAngle(x1, y1, x2, y2) {
|
|
24004
|
+
let a = Math.atan2(y2 - y1, x2 - x1);
|
|
24005
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
24006
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
24007
|
+
return a;
|
|
24008
|
+
}
|
|
22350
24009
|
function parseColor2(c) {
|
|
22351
24010
|
const s = c.trim();
|
|
22352
24011
|
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
@@ -23520,367 +25179,71 @@ function formatStamp(ms, timeZone) {
|
|
|
23520
25179
|
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
23521
25180
|
}
|
|
23522
25181
|
|
|
23523
|
-
// src/renderers/native/drawings/colorPicker.ts
|
|
23524
|
-
function splitColor(color) {
|
|
23525
|
-
const s = String(color ?? "").trim();
|
|
23526
|
-
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
23527
|
-
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
23528
|
-
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
23529
|
-
if (m) {
|
|
23530
|
-
const h = m[1].toLowerCase();
|
|
23531
|
-
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
23532
|
-
}
|
|
23533
|
-
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
23534
|
-
if (rgba) {
|
|
23535
|
-
const p = rgba[1].split(",").map((v) => v.trim());
|
|
23536
|
-
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
23537
|
-
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
23538
|
-
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
23539
|
-
}
|
|
23540
|
-
return { hex6: DEFAULT_DRAWING_COLOR, alpha: 1 };
|
|
23541
|
-
}
|
|
23542
|
-
function combineColor(hex6, alpha) {
|
|
23543
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
23544
|
-
if (a >= 0.999) return hex6;
|
|
23545
|
-
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
23546
|
-
}
|
|
23547
|
-
function blendOver(fg, bg, alpha) {
|
|
23548
|
-
const rgb = (c) => {
|
|
23549
|
-
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
23550
|
-
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
23551
|
-
};
|
|
23552
|
-
const [fr, fgc, fb] = rgb(fg);
|
|
23553
|
-
const [br, bgc, bb] = rgb(bg);
|
|
23554
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
23555
|
-
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
23556
|
-
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
23557
|
-
}
|
|
23558
|
-
function hslHex(h, s, l) {
|
|
23559
|
-
const sn = s / 100;
|
|
23560
|
-
const ln = l / 100;
|
|
23561
|
-
const k = (n) => (n + h / 30) % 12;
|
|
23562
|
-
const a = sn * Math.min(ln, 1 - ln);
|
|
23563
|
-
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
23564
|
-
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
23565
|
-
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
23566
|
-
}
|
|
23567
|
-
function buildPalette() {
|
|
23568
|
-
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
23569
|
-
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
23570
|
-
const levels = [
|
|
23571
|
-
[82, 56],
|
|
23572
|
-
// vivid
|
|
23573
|
-
[58, 84],
|
|
23574
|
-
// light pastel
|
|
23575
|
-
[62, 74],
|
|
23576
|
-
[66, 62],
|
|
23577
|
-
[70, 50],
|
|
23578
|
-
[64, 39],
|
|
23579
|
-
[54, 29]
|
|
23580
|
-
// dark
|
|
23581
|
-
];
|
|
23582
|
-
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
23583
|
-
}
|
|
23584
|
-
var PALETTE = buildPalette();
|
|
23585
|
-
function transparencyChecker(size) {
|
|
23586
|
-
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
23587
|
-
}
|
|
23588
|
-
var CHECKER = transparencyChecker(10);
|
|
23589
|
-
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
23590
|
-
function addRecent(hex6) {
|
|
23591
|
-
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
23592
|
-
if (i >= 0) recents.splice(i, 1);
|
|
23593
|
-
recents.unshift(hex6);
|
|
23594
|
-
if (recents.length > 10) recents.length = 10;
|
|
23595
|
-
}
|
|
23596
|
-
function buildColorPicker(color, theme, onChange) {
|
|
23597
|
-
const parsed = splitColor(color);
|
|
23598
|
-
let curHex = parsed.hex6;
|
|
23599
|
-
let curAlpha = parsed.alpha;
|
|
23600
|
-
const root = document.createElement("div");
|
|
23601
|
-
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
23602
|
-
const grid = document.createElement("div");
|
|
23603
|
-
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
23604
|
-
const swatches = [];
|
|
23605
|
-
for (const row of PALETTE) {
|
|
23606
|
-
for (const c of row) {
|
|
23607
|
-
const sw = document.createElement("button");
|
|
23608
|
-
sw.type = "button";
|
|
23609
|
-
sw.dataset.c = c;
|
|
23610
|
-
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
23611
|
-
sw.addEventListener("click", (e) => {
|
|
23612
|
-
e.stopPropagation();
|
|
23613
|
-
pickHex(c);
|
|
23614
|
-
});
|
|
23615
|
-
grid.appendChild(sw);
|
|
23616
|
-
swatches.push(sw);
|
|
23617
|
-
}
|
|
23618
|
-
}
|
|
23619
|
-
const paintSelection = () => {
|
|
23620
|
-
for (const sw of swatches) {
|
|
23621
|
-
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
23622
|
-
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
23623
|
-
sw.style.outlineOffset = "2px";
|
|
23624
|
-
sw.style.zIndex = on ? "1" : "";
|
|
23625
|
-
sw.style.position = on ? "relative" : "";
|
|
23626
|
-
}
|
|
23627
|
-
};
|
|
23628
|
-
const recentRow = document.createElement("div");
|
|
23629
|
-
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
23630
|
-
const renderRecents = () => {
|
|
23631
|
-
recentRow.replaceChildren();
|
|
23632
|
-
for (const c of recents) {
|
|
23633
|
-
const sw = document.createElement("button");
|
|
23634
|
-
sw.type = "button";
|
|
23635
|
-
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;`;
|
|
23636
|
-
sw.addEventListener("click", (e) => {
|
|
23637
|
-
e.stopPropagation();
|
|
23638
|
-
pickHex(c);
|
|
23639
|
-
});
|
|
23640
|
-
recentRow.appendChild(sw);
|
|
23641
|
-
}
|
|
23642
|
-
const add = document.createElement("label");
|
|
23643
|
-
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;`;
|
|
23644
|
-
add.textContent = "+";
|
|
23645
|
-
const input = document.createElement("input");
|
|
23646
|
-
input.type = "color";
|
|
23647
|
-
input.value = curHex;
|
|
23648
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
23649
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
23650
|
-
add.appendChild(input);
|
|
23651
|
-
recentRow.appendChild(add);
|
|
23652
|
-
};
|
|
23653
|
-
const opLabel = document.createElement("div");
|
|
23654
|
-
opLabel.textContent = "Opacity";
|
|
23655
|
-
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
23656
|
-
const opRow = document.createElement("div");
|
|
23657
|
-
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
23658
|
-
const track = document.createElement("div");
|
|
23659
|
-
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
23660
|
-
const knob = document.createElement("div");
|
|
23661
|
-
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;";
|
|
23662
|
-
track.appendChild(knob);
|
|
23663
|
-
const pctBox = document.createElement("div");
|
|
23664
|
-
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;`;
|
|
23665
|
-
opRow.append(track, pctBox);
|
|
23666
|
-
const paintOpacity = () => {
|
|
23667
|
-
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
23668
|
-
knob.style.left = `${curAlpha * 100}%`;
|
|
23669
|
-
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
23670
|
-
};
|
|
23671
|
-
let dragging = false;
|
|
23672
|
-
const onDrag = (clientX) => {
|
|
23673
|
-
const r = track.getBoundingClientRect();
|
|
23674
|
-
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
23675
|
-
paintOpacity();
|
|
23676
|
-
emit();
|
|
23677
|
-
};
|
|
23678
|
-
track.addEventListener("pointerdown", (e) => {
|
|
23679
|
-
e.stopPropagation();
|
|
23680
|
-
dragging = true;
|
|
23681
|
-
track.setPointerCapture(e.pointerId);
|
|
23682
|
-
onDrag(e.clientX);
|
|
23683
|
-
});
|
|
23684
|
-
track.addEventListener("pointermove", (e) => {
|
|
23685
|
-
if (dragging) onDrag(e.clientX);
|
|
23686
|
-
});
|
|
23687
|
-
track.addEventListener("pointerup", () => dragging = false);
|
|
23688
|
-
function emit() {
|
|
23689
|
-
onChange(combineColor(curHex, curAlpha));
|
|
23690
|
-
}
|
|
23691
|
-
function pickHex(hex, custom = false) {
|
|
23692
|
-
curHex = splitColor(hex).hex6;
|
|
23693
|
-
addRecent(curHex);
|
|
23694
|
-
paintSelection();
|
|
23695
|
-
paintOpacity();
|
|
23696
|
-
if (custom) renderRecents();
|
|
23697
|
-
emit();
|
|
23698
|
-
}
|
|
23699
|
-
renderRecents();
|
|
23700
|
-
paintSelection();
|
|
23701
|
-
paintOpacity();
|
|
23702
|
-
root.append(grid, recentRow, opLabel, opRow);
|
|
23703
|
-
return root;
|
|
23704
|
-
}
|
|
23705
|
-
|
|
23706
|
-
// src/renderers/native/chrome/ColorField.ts
|
|
23707
|
-
var CHECKER2 = transparencyChecker(8);
|
|
23708
|
-
var STYLE_ID22 = "vela-color-field";
|
|
23709
|
-
function ensureStyles4() {
|
|
23710
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID22)) return;
|
|
23711
|
-
const st = document.createElement("style");
|
|
23712
|
-
st.id = STYLE_ID22;
|
|
23713
|
-
st.textContent = `
|
|
23714
|
-
.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;}
|
|
23715
|
-
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
23716
|
-
.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);}
|
|
23717
|
-
.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;}
|
|
23718
|
-
`;
|
|
23719
|
-
document.head.appendChild(st);
|
|
23720
|
-
}
|
|
23721
|
-
var openPopover = null;
|
|
23722
|
-
function closeOpen() {
|
|
23723
|
-
if (!openPopover) return;
|
|
23724
|
-
document.removeEventListener("pointerdown", openPopover.onOutside, true);
|
|
23725
|
-
window.removeEventListener("resize", openPopover.reflow, true);
|
|
23726
|
-
openPopover.el.remove();
|
|
23727
|
-
openPopover = null;
|
|
23728
|
-
}
|
|
23729
|
-
function place(pop, trigger) {
|
|
23730
|
-
const r = trigger.getBoundingClientRect();
|
|
23731
|
-
const w = pop.offsetWidth;
|
|
23732
|
-
const h = pop.offsetHeight;
|
|
23733
|
-
let left = r.right - w;
|
|
23734
|
-
let top = r.bottom + 6;
|
|
23735
|
-
if (left < 6) left = 6;
|
|
23736
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
23737
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
23738
|
-
if (top < 6) top = 6;
|
|
23739
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
23740
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
23741
|
-
}
|
|
23742
|
-
function colorField(theme, getVal, onVal) {
|
|
23743
|
-
ensureStyles4();
|
|
23744
|
-
const trigger = document.createElement("button");
|
|
23745
|
-
trigger.type = "button";
|
|
23746
|
-
trigger.className = "vela-color-field";
|
|
23747
|
-
const swatch = document.createElement("span");
|
|
23748
|
-
swatch.className = "vela-color-field-swatch";
|
|
23749
|
-
trigger.appendChild(swatch);
|
|
23750
|
-
const paint = () => {
|
|
23751
|
-
const v = getVal();
|
|
23752
|
-
swatch.style.background = `linear-gradient(${v}, ${v}), ${CHECKER2}`;
|
|
23753
|
-
};
|
|
23754
|
-
paint();
|
|
23755
|
-
trigger.addEventListener("vela-sync", paint);
|
|
23756
|
-
trigger.addEventListener("click", (e) => {
|
|
23757
|
-
e.stopPropagation();
|
|
23758
|
-
if (openPopover && openPopover.trigger === trigger) {
|
|
23759
|
-
closeOpen();
|
|
23760
|
-
return;
|
|
23761
|
-
}
|
|
23762
|
-
closeOpen();
|
|
23763
|
-
const pop = document.createElement("div");
|
|
23764
|
-
pop.className = "vela-color-field-pop";
|
|
23765
|
-
applyChromeTokens(pop, theme);
|
|
23766
|
-
pop.addEventListener("pointerdown", (ev) => ev.stopPropagation());
|
|
23767
|
-
pop.appendChild(
|
|
23768
|
-
buildColorPicker(getVal(), theme, (val) => {
|
|
23769
|
-
onVal(val);
|
|
23770
|
-
paint();
|
|
23771
|
-
})
|
|
23772
|
-
);
|
|
23773
|
-
document.body.appendChild(pop);
|
|
23774
|
-
place(pop, trigger);
|
|
23775
|
-
const onOutside = (ev) => {
|
|
23776
|
-
const t = ev.target;
|
|
23777
|
-
if (pop.contains(t) || trigger.contains(t)) return;
|
|
23778
|
-
closeOpen();
|
|
23779
|
-
};
|
|
23780
|
-
const reflow = () => place(pop, trigger);
|
|
23781
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
23782
|
-
window.addEventListener("resize", reflow, true);
|
|
23783
|
-
openPopover = { el: pop, trigger, onOutside, reflow };
|
|
23784
|
-
});
|
|
23785
|
-
return trigger;
|
|
23786
|
-
}
|
|
23787
|
-
function closeColorPopover() {
|
|
23788
|
-
closeOpen();
|
|
23789
|
-
}
|
|
23790
|
-
|
|
23791
25182
|
// src/renderers/native/chrome/WidthField.ts
|
|
23792
25183
|
var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
|
|
23793
|
-
var
|
|
23794
|
-
|
|
23795
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID23)) return;
|
|
23796
|
-
const st = document.createElement("style");
|
|
23797
|
-
st.id = STYLE_ID23;
|
|
23798
|
-
st.textContent = `
|
|
25184
|
+
var STYLE_ID22 = "vela-width-field";
|
|
25185
|
+
var WIDTH_CSS = `
|
|
23799
25186
|
.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;}
|
|
23800
25187
|
.vela-width-field:hover{border-color:var(--vela-fg-muted);}
|
|
23801
|
-
.vela-width-field-
|
|
25188
|
+
.vela-width-field-caret{display:flex;color:var(--vela-fg-muted);}
|
|
25189
|
+
.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);}
|
|
23802
25190
|
.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;}
|
|
23803
25191
|
.vela-width-field-item:hover{background:var(--vela-hover-strong);}
|
|
23804
25192
|
.vela-width-field-item[data-active='1']{background:var(--vela-active);}
|
|
23805
25193
|
`;
|
|
23806
|
-
document.head.appendChild(st);
|
|
23807
|
-
}
|
|
23808
25194
|
function lineGlyph(width) {
|
|
23809
25195
|
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>`;
|
|
23810
25196
|
}
|
|
23811
|
-
var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="
|
|
23812
|
-
var openPopover2 = null;
|
|
23813
|
-
function closeOpen2() {
|
|
23814
|
-
if (!openPopover2) return;
|
|
23815
|
-
document.removeEventListener("pointerdown", openPopover2.onOutside, true);
|
|
23816
|
-
window.removeEventListener("resize", openPopover2.reflow, true);
|
|
23817
|
-
openPopover2.el.remove();
|
|
23818
|
-
openPopover2 = null;
|
|
23819
|
-
}
|
|
23820
|
-
function place2(pop, trigger) {
|
|
23821
|
-
const r = trigger.getBoundingClientRect();
|
|
23822
|
-
const w = pop.offsetWidth;
|
|
23823
|
-
const h = pop.offsetHeight;
|
|
23824
|
-
let left = r.right - w;
|
|
23825
|
-
let top = r.bottom + 6;
|
|
23826
|
-
if (left < 6) left = 6;
|
|
23827
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
23828
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
23829
|
-
if (top < 6) top = 6;
|
|
23830
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
23831
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
23832
|
-
}
|
|
25197
|
+
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>`;
|
|
23833
25198
|
function widthField(theme, getVal, onVal) {
|
|
23834
|
-
|
|
25199
|
+
injectStyles(STYLE_ID22, WIDTH_CSS, document);
|
|
23835
25200
|
const trigger = document.createElement("button");
|
|
23836
25201
|
trigger.type = "button";
|
|
23837
25202
|
trigger.className = "vela-width-field";
|
|
23838
25203
|
const paint = () => {
|
|
23839
|
-
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span
|
|
25204
|
+
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span class="vela-width-field-caret">${CHEVRON}</span>`;
|
|
23840
25205
|
};
|
|
23841
25206
|
paint();
|
|
25207
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
25208
|
+
e.preventDefault();
|
|
25209
|
+
trigger.focus({ preventScroll: true });
|
|
25210
|
+
});
|
|
23842
25211
|
trigger.addEventListener("click", (e) => {
|
|
23843
25212
|
e.stopPropagation();
|
|
23844
|
-
if (
|
|
23845
|
-
|
|
25213
|
+
if (openPopoverTrigger() === trigger) {
|
|
25214
|
+
closeOpenPopovers();
|
|
23846
25215
|
return;
|
|
23847
25216
|
}
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23853
|
-
|
|
23854
|
-
|
|
23855
|
-
|
|
23856
|
-
|
|
23857
|
-
|
|
23858
|
-
|
|
23859
|
-
|
|
23860
|
-
|
|
23861
|
-
|
|
23862
|
-
|
|
23863
|
-
|
|
23864
|
-
|
|
23865
|
-
|
|
23866
|
-
|
|
23867
|
-
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
|
|
23873
|
-
closeOpen2();
|
|
23874
|
-
};
|
|
23875
|
-
const reflow = () => place2(pop, trigger);
|
|
23876
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
23877
|
-
window.addEventListener("resize", reflow, true);
|
|
23878
|
-
openPopover2 = { el: pop, trigger, onOutside, reflow };
|
|
25217
|
+
const pop = new Popover({
|
|
25218
|
+
trigger,
|
|
25219
|
+
theme,
|
|
25220
|
+
align: "end",
|
|
25221
|
+
gap: 6,
|
|
25222
|
+
className: "vela-width-field-pop",
|
|
25223
|
+
content: (el) => {
|
|
25224
|
+
el.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
|
|
25225
|
+
for (const w of WIDTH_FIELD_OPTIONS) {
|
|
25226
|
+
const item = document.createElement("button");
|
|
25227
|
+
item.type = "button";
|
|
25228
|
+
item.className = "vela-width-field-item";
|
|
25229
|
+
item.dataset.active = w === getVal() ? "1" : "0";
|
|
25230
|
+
item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
|
|
25231
|
+
item.addEventListener("click", (ev) => {
|
|
25232
|
+
ev.stopPropagation();
|
|
25233
|
+
pop.hide();
|
|
25234
|
+
onVal(w);
|
|
25235
|
+
paint();
|
|
25236
|
+
});
|
|
25237
|
+
el.appendChild(item);
|
|
25238
|
+
}
|
|
25239
|
+
}
|
|
25240
|
+
});
|
|
25241
|
+
pop.show();
|
|
23879
25242
|
});
|
|
23880
25243
|
return trigger;
|
|
23881
25244
|
}
|
|
23882
25245
|
function closeWidthPopover() {
|
|
23883
|
-
|
|
25246
|
+
closeOpenPopovers();
|
|
23884
25247
|
}
|
|
23885
25248
|
|
|
23886
25249
|
// src/renderers/native/chrome/SettingsDialog.ts
|
|
@@ -23895,37 +25258,24 @@ function styleLabel(id) {
|
|
|
23895
25258
|
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS2[id] ?? id;
|
|
23896
25259
|
}
|
|
23897
25260
|
var SD_STYLE_ID = "vela-settings-controls";
|
|
25261
|
+
var SD_STYLE_REV = "2";
|
|
23898
25262
|
var SETTINGS_SURFACE = "var(--vela-surface)";
|
|
23899
25263
|
var SETTINGS_BORDER = "var(--vela-border)";
|
|
23900
25264
|
var SETTINGS_TEXT = "var(--vela-fg)";
|
|
23901
25265
|
function ensureControlStyles() {
|
|
23902
|
-
if (typeof document === "undefined"
|
|
23903
|
-
const
|
|
25266
|
+
if (typeof document === "undefined") return;
|
|
25267
|
+
const existing = document.getElementById(SD_STYLE_ID);
|
|
25268
|
+
if (existing?.dataset.rev === SD_STYLE_REV) return;
|
|
25269
|
+
const st = existing ?? document.createElement("style");
|
|
23904
25270
|
st.id = SD_STYLE_ID;
|
|
25271
|
+
st.dataset.rev = SD_STYLE_REV;
|
|
23905
25272
|
st.textContent = `
|
|
23906
|
-
.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;}
|
|
23907
|
-
.vela-sd-check:hover{border-color:var(--vela-fg-muted);}
|
|
23908
|
-
.vela-sd-check.on{background:var(--vela-selected-bg);border-color:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
23909
|
-
.vela-sd-check svg{display:block;}
|
|
23910
|
-
.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;}
|
|
23911
|
-
.vela-sd-select:hover,.vela-sd-number:hover{border-color:var(--vela-fg-muted);}
|
|
23912
|
-
/* Custom caret: the native one hugs the right border; ours gets 8px of air. (A data URI
|
|
23913
|
-
can't read currentColor, so it uses the shared muted-gray ink, legible on both themes.) */
|
|
23914
|
-
.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;}
|
|
23915
|
-
.vela-sd-number{width:64px;}
|
|
23916
|
-
.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;}
|
|
23917
|
-
.vela-sd-color::-webkit-color-swatch-wrapper{padding:2px;}
|
|
23918
|
-
.vela-sd-color::-webkit-color-swatch{border:none;border-radius:2px;}
|
|
23919
25273
|
.vela-sd-pane::-webkit-scrollbar{width:8px;}
|
|
23920
25274
|
.vela-sd-pane::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:var(--vela-radius-sm);border:2px solid transparent;background-clip:padding-box;}
|
|
23921
25275
|
.vela-sd-pane::-webkit-scrollbar-track{background:transparent;}
|
|
23922
|
-
.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;}
|
|
23923
|
-
.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;}
|
|
23924
|
-
.vela-sd-toggle.on{background:var(--vela-active);border-color:var(--vela-selected-bg);}
|
|
23925
|
-
.vela-sd-toggle.on::after{transform:translateX(16px);background:var(--vela-selected-bg);}
|
|
23926
25276
|
/* Tab rail / footer button / header close: base styles live HERE, not inline on the
|
|
23927
25277
|
elements \u2014 inline declarations always beat stylesheet :hover rules, which is exactly
|
|
23928
|
-
what killed these hovers before. Active tab state is the .on class
|
|
25278
|
+
what killed these hovers before. Active tab state is the .on class,
|
|
23929
25279
|
hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
|
|
23930
25280
|
.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;}
|
|
23931
25281
|
.vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
@@ -23984,13 +25334,13 @@ function ensureControlStyles() {
|
|
|
23984
25334
|
.vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
|
|
23985
25335
|
.vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
|
|
23986
25336
|
.vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
|
|
23987
|
-
.vela-sd-mobile .vela-
|
|
23988
|
-
.vela-sd-mobile .vela-
|
|
25337
|
+
.vela-sd-mobile .vela-switch{width:22px;height:22px;}
|
|
25338
|
+
.vela-sd-mobile .vela-select[data-size='sm'] .vela-select-trigger,.vela-sd-mobile .vela-num[data-size='sm'] input{height:34px;}
|
|
23989
25339
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
23990
25340
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
23991
25341
|
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
|
|
23992
25342
|
`;
|
|
23993
|
-
document.head.appendChild(st);
|
|
25343
|
+
if (!existing) document.head.appendChild(st);
|
|
23994
25344
|
}
|
|
23995
25345
|
var SettingsDialog = class {
|
|
23996
25346
|
constructor(container, theme) {
|
|
@@ -24306,15 +25656,15 @@ var SettingsDialog = class {
|
|
|
24306
25656
|
railScrim = document.createElement("div");
|
|
24307
25657
|
railScrim.className = "vela-sd-railscrim";
|
|
24308
25658
|
railScrim.addEventListener("click", () => toggleRail?.());
|
|
24309
|
-
toggleRail = (
|
|
24310
|
-
const on =
|
|
25659
|
+
toggleRail = (open2) => {
|
|
25660
|
+
const on = open2 ?? !rail.classList.contains("open");
|
|
24311
25661
|
rail.classList.toggle("open", on);
|
|
24312
25662
|
railScrim?.classList.toggle("open", on);
|
|
24313
25663
|
};
|
|
24314
25664
|
}
|
|
24315
25665
|
const paneHost = document.createElement("div");
|
|
24316
25666
|
paneHost.className = "vela-sd-pane";
|
|
24317
|
-
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;";
|
|
25667
|
+
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;";
|
|
24318
25668
|
const panes = [];
|
|
24319
25669
|
let current = null;
|
|
24320
25670
|
for (const child of [...body.children]) {
|
|
@@ -24380,7 +25730,7 @@ var SettingsDialog = class {
|
|
|
24380
25730
|
}
|
|
24381
25731
|
}
|
|
24382
25732
|
close() {
|
|
24383
|
-
|
|
25733
|
+
closeOpenPopovers();
|
|
24384
25734
|
closeWidthPopover();
|
|
24385
25735
|
this.root?.remove();
|
|
24386
25736
|
this.root = null;
|
|
@@ -24535,50 +25885,41 @@ var SettingsDialog = class {
|
|
|
24535
25885
|
return wf;
|
|
24536
25886
|
}
|
|
24537
25887
|
if (c.kind === "select") {
|
|
24538
|
-
const sel = document.createElement("select");
|
|
24539
|
-
sel.className = "vela-sd-select";
|
|
24540
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
24541
|
-
sel.title = c.label;
|
|
24542
25888
|
const current2 = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
24543
|
-
|
|
24544
|
-
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24548
|
-
|
|
24549
|
-
}
|
|
24550
|
-
sel.addEventListener("change", () => put(c.key, sel.value));
|
|
24551
|
-
sel.addEventListener("vela-sync", () => {
|
|
24552
|
-
sel.value = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
25889
|
+
const sel = new Select({
|
|
25890
|
+
options: normalizeSelectOptions(c.options).map(([value, label]) => ({ value, label })),
|
|
25891
|
+
value: current2,
|
|
25892
|
+
size: "sm",
|
|
25893
|
+
theme: this.theme,
|
|
25894
|
+
onChange: (v) => put(c.key, v)
|
|
24553
25895
|
});
|
|
24554
|
-
|
|
24555
|
-
|
|
24556
|
-
|
|
24557
|
-
ni.type = "number";
|
|
24558
|
-
ni.className = "vela-sd-number";
|
|
24559
|
-
if (compact) ni.style.width = "56px";
|
|
24560
|
-
if (c.min !== void 0) ni.min = String(c.min);
|
|
24561
|
-
if (c.max !== void 0) ni.max = String(c.max);
|
|
24562
|
-
ni.step = String(c.step ?? 1);
|
|
24563
|
-
ni.title = c.label;
|
|
24564
|
-
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
24565
|
-
if (c.placeholder !== void 0) {
|
|
24566
|
-
ni.placeholder = c.placeholder;
|
|
24567
|
-
if (current !== c.defval) ni.value = String(current);
|
|
24568
|
-
ni.addEventListener("change", () => {
|
|
24569
|
-
const raw = ni.value.trim() === "" ? c.defval : Number(ni.value);
|
|
24570
|
-
const v = Number.isFinite(raw) ? Math.min(c.max ?? Infinity, Math.max(c.min ?? -Infinity, raw)) : c.defval;
|
|
24571
|
-
ni.value = v === c.defval ? "" : String(v);
|
|
24572
|
-
put(c.key, v);
|
|
24573
|
-
});
|
|
24574
|
-
} else {
|
|
24575
|
-
ni.value = String(current);
|
|
24576
|
-
ni.addEventListener("input", () => {
|
|
24577
|
-
const v = Number(ni.value);
|
|
24578
|
-
if (Number.isFinite(v)) put(c.key, v);
|
|
25896
|
+
sel.el.title = c.label;
|
|
25897
|
+
sel.el.addEventListener("vela-sync", () => {
|
|
25898
|
+
sel.setValue(typeof bag[c.key] === "string" ? bag[c.key] : c.defval);
|
|
24579
25899
|
});
|
|
25900
|
+
return sel.el;
|
|
24580
25901
|
}
|
|
24581
|
-
|
|
25902
|
+
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
25903
|
+
const ni = new NumberInput({
|
|
25904
|
+
value: current,
|
|
25905
|
+
min: c.min,
|
|
25906
|
+
max: c.max,
|
|
25907
|
+
step: c.step ?? 1,
|
|
25908
|
+
size: "sm",
|
|
25909
|
+
commit: "live",
|
|
25910
|
+
clamp: c.placeholder !== void 0,
|
|
25911
|
+
steppers: false,
|
|
25912
|
+
compact,
|
|
25913
|
+
title: c.label,
|
|
25914
|
+
placeholder: c.placeholder,
|
|
25915
|
+
emptyValue: c.placeholder !== void 0 ? c.defval : void 0,
|
|
25916
|
+
onChange: (v) => put(c.key, v)
|
|
25917
|
+
});
|
|
25918
|
+
ni.el.addEventListener("vela-sync", () => {
|
|
25919
|
+
const next = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
25920
|
+
ni.setValue(next);
|
|
25921
|
+
});
|
|
25922
|
+
return ni.el;
|
|
24582
25923
|
}
|
|
24583
25924
|
/**
|
|
24584
25925
|
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
@@ -24859,28 +26200,27 @@ var SettingsDialog = class {
|
|
|
24859
26200
|
* only one visible at a time; see `typeRow`). */
|
|
24860
26201
|
toggleRow(label, value, onToggle, controls, get) {
|
|
24861
26202
|
const wrap = document.createElement("div");
|
|
24862
|
-
const
|
|
24863
|
-
|
|
24864
|
-
|
|
24865
|
-
|
|
24866
|
-
let checked = value;
|
|
24867
|
-
const cbToggle = () => {
|
|
24868
|
-
checked = !checked;
|
|
24869
|
-
cb.classList.toggle("on", checked);
|
|
24870
|
-
return checked;
|
|
24871
|
-
};
|
|
26203
|
+
const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
|
|
26204
|
+
onToggle(v);
|
|
26205
|
+
if (controls.length > 0) syncDim(v);
|
|
26206
|
+
} });
|
|
24872
26207
|
const lbl = document.createElement("span");
|
|
24873
26208
|
lbl.textContent = label;
|
|
24874
26209
|
lbl.style.cssText = "opacity:0.85;white-space:nowrap;";
|
|
26210
|
+
const syncDim = (on) => {
|
|
26211
|
+
for (const c of controls) {
|
|
26212
|
+
if (c.dataset.sdSelfGated === "1") continue;
|
|
26213
|
+
c.style.opacity = on ? "1" : "0.4";
|
|
26214
|
+
c.style.pointerEvents = on ? "" : "none";
|
|
26215
|
+
}
|
|
26216
|
+
};
|
|
24875
26217
|
if (controls.length === 0) {
|
|
24876
26218
|
wrap.className = "vela-sd-bool";
|
|
24877
26219
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
|
|
24878
|
-
wrap.append(
|
|
24879
|
-
cb.addEventListener("click", () => onToggle(cbToggle()));
|
|
26220
|
+
wrap.append(sw.el, lbl);
|
|
24880
26221
|
if (get) {
|
|
24881
26222
|
wrap.addEventListener("vela-sync", () => {
|
|
24882
|
-
|
|
24883
|
-
cb.classList.toggle("on", checked);
|
|
26223
|
+
sw.setChecked(get());
|
|
24884
26224
|
});
|
|
24885
26225
|
}
|
|
24886
26226
|
return wrap;
|
|
@@ -24888,28 +26228,16 @@ var SettingsDialog = class {
|
|
|
24888
26228
|
wrap.className = "vela-sd-row";
|
|
24889
26229
|
const left = document.createElement("div");
|
|
24890
26230
|
left.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
24891
|
-
left.append(
|
|
26231
|
+
left.append(sw.el, lbl);
|
|
24892
26232
|
const box = document.createElement("div");
|
|
24893
26233
|
box.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
24894
26234
|
for (const c of controls) box.appendChild(c);
|
|
24895
|
-
const syncDim = (on) => {
|
|
24896
|
-
for (const c of controls) {
|
|
24897
|
-
if (c.dataset.sdSelfGated === "1") continue;
|
|
24898
|
-
c.style.opacity = on ? "1" : "0.4";
|
|
24899
|
-
c.style.pointerEvents = on ? "" : "none";
|
|
24900
|
-
}
|
|
24901
|
-
};
|
|
24902
26235
|
syncDim(value);
|
|
24903
|
-
cb.addEventListener("click", () => {
|
|
24904
|
-
const v = cbToggle();
|
|
24905
|
-
onToggle(v);
|
|
24906
|
-
syncDim(v);
|
|
24907
|
-
});
|
|
24908
26236
|
if (get) {
|
|
24909
26237
|
wrap.addEventListener("vela-sync", () => {
|
|
24910
|
-
|
|
24911
|
-
|
|
24912
|
-
syncDim(
|
|
26238
|
+
const v = get();
|
|
26239
|
+
sw.setChecked(v);
|
|
26240
|
+
syncDim(v);
|
|
24913
26241
|
});
|
|
24914
26242
|
}
|
|
24915
26243
|
wrap.append(left, box);
|
|
@@ -24917,52 +26245,23 @@ var SettingsDialog = class {
|
|
|
24917
26245
|
}
|
|
24918
26246
|
numberRow(label, value, min, max, step, onChange) {
|
|
24919
26247
|
const { wrap } = this.row(label);
|
|
24920
|
-
|
|
24921
|
-
ni.type = "number";
|
|
24922
|
-
ni.value = String(value);
|
|
24923
|
-
ni.min = String(min);
|
|
24924
|
-
ni.max = String(max);
|
|
24925
|
-
ni.step = String(step);
|
|
24926
|
-
ni.className = "vela-sd-number";
|
|
24927
|
-
ni.addEventListener("input", () => {
|
|
24928
|
-
const n = Number(ni.value);
|
|
24929
|
-
if (Number.isFinite(n)) onChange(n);
|
|
24930
|
-
});
|
|
24931
|
-
wrap.appendChild(ni);
|
|
26248
|
+
wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
|
|
24932
26249
|
return wrap;
|
|
24933
26250
|
}
|
|
24934
26251
|
/** A dropdown whose option values differ from their display labels. */
|
|
24935
26252
|
selectRowLabeled(label, value, options, onChange) {
|
|
24936
26253
|
const { wrap } = this.row(label);
|
|
24937
|
-
|
|
24938
|
-
|
|
24939
|
-
|
|
24940
|
-
|
|
24941
|
-
|
|
24942
|
-
|
|
24943
|
-
|
|
24944
|
-
if (val === value) o.selected = true;
|
|
24945
|
-
sel.appendChild(o);
|
|
24946
|
-
}
|
|
24947
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
24948
|
-
wrap.appendChild(sel);
|
|
26254
|
+
wrap.appendChild(new Select({
|
|
26255
|
+
options: options.map(([v, l]) => ({ value: v, label: l })),
|
|
26256
|
+
value,
|
|
26257
|
+
size: "sm",
|
|
26258
|
+
theme: this.theme,
|
|
26259
|
+
onChange
|
|
26260
|
+
}).el);
|
|
24949
26261
|
return wrap;
|
|
24950
26262
|
}
|
|
24951
26263
|
selectRow(label, value, options, onChange) {
|
|
24952
|
-
|
|
24953
|
-
const sel = document.createElement("select");
|
|
24954
|
-
sel.className = "vela-sd-select";
|
|
24955
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
24956
|
-
for (const opt of options) {
|
|
24957
|
-
const o = document.createElement("option");
|
|
24958
|
-
o.value = opt;
|
|
24959
|
-
o.textContent = opt;
|
|
24960
|
-
if (opt === value) o.selected = true;
|
|
24961
|
-
sel.appendChild(o);
|
|
24962
|
-
}
|
|
24963
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
24964
|
-
wrap.appendChild(sel);
|
|
24965
|
-
return wrap;
|
|
26264
|
+
return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
|
|
24966
26265
|
}
|
|
24967
26266
|
/** Footer with the full config as JSON — the export/import (templating) surface. */
|
|
24968
26267
|
footer(_config) {
|
|
@@ -25353,13 +26652,19 @@ var DrawingPainter = class {
|
|
|
25353
26652
|
}
|
|
25354
26653
|
this.paintLabel(ctx, d, proj, theme);
|
|
25355
26654
|
}
|
|
25356
|
-
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
26655
|
+
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
26656
|
+
* Two-point line tools rotate the glyphs to follow the segment, kept upright. */
|
|
25357
26657
|
paintLabel(ctx, d, proj, theme) {
|
|
25358
26658
|
const text = d.text;
|
|
25359
26659
|
if (!text || !text.value || d.id === this.targets.mutedLabel) return;
|
|
25360
26660
|
const layout = labelLayout(d, proj);
|
|
25361
26661
|
if (!layout) return;
|
|
25362
26662
|
const fs = namedFontSize(text.size);
|
|
26663
|
+
ctx.save();
|
|
26664
|
+
if (layout.rotate) {
|
|
26665
|
+
ctx.translate(layout.rotate.cx, layout.rotate.cy);
|
|
26666
|
+
ctx.rotate(layout.rotate.angle);
|
|
26667
|
+
}
|
|
25363
26668
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
25364
26669
|
ctx.textBaseline = "top";
|
|
25365
26670
|
ctx.textAlign = layout.align;
|
|
@@ -25367,8 +26672,8 @@ var DrawingPainter = class {
|
|
|
25367
26672
|
const lh = labelLineHeight(fs);
|
|
25368
26673
|
const lines = text.value.split("\n");
|
|
25369
26674
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
25370
|
-
ctx.textAlign = "left";
|
|
25371
26675
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
26676
|
+
ctx.restore();
|
|
25372
26677
|
}
|
|
25373
26678
|
/** Frame a plain text label while it is the target, so the text reads as a clickable object even
|
|
25374
26679
|
* though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
|
|
@@ -26311,7 +27616,14 @@ function labelLayout(d, proj) {
|
|
|
26311
27616
|
case "infoline":
|
|
26312
27617
|
case "trendangle": {
|
|
26313
27618
|
if (pts.length < 2) return null;
|
|
26314
|
-
|
|
27619
|
+
const [x1, y1] = pts[0];
|
|
27620
|
+
const [x2, y2] = pts[1];
|
|
27621
|
+
return {
|
|
27622
|
+
x: 0,
|
|
27623
|
+
top: -6 - lh * lines,
|
|
27624
|
+
align: "center",
|
|
27625
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: (x1 + x2) / 2, cy: (y1 + y2) / 2 }
|
|
27626
|
+
};
|
|
26315
27627
|
}
|
|
26316
27628
|
case "box": {
|
|
26317
27629
|
if (pts.length < 2) return null;
|
|
@@ -26660,11 +27972,11 @@ function applyFree(anchor, pt, free) {
|
|
|
26660
27972
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
26661
27973
|
var BTN = 30;
|
|
26662
27974
|
var ICON = 21;
|
|
26663
|
-
var
|
|
26664
|
-
function
|
|
26665
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
27975
|
+
var STYLE_ID23 = "vela-drawing-popup-styles";
|
|
27976
|
+
function ensureStyles4() {
|
|
27977
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID23)) return;
|
|
26666
27978
|
const s = document.createElement("style");
|
|
26667
|
-
s.id =
|
|
27979
|
+
s.id = STYLE_ID23;
|
|
26668
27980
|
s.textContent = `
|
|
26669
27981
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
26670
27982
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -26698,17 +28010,14 @@ var DrawingSettingsPopup = class {
|
|
|
26698
28010
|
this.textPanel = null;
|
|
26699
28011
|
this.levelsPanel = null;
|
|
26700
28012
|
this.colorPop = null;
|
|
26701
|
-
// floating RGB + opacity picker for a color swatch
|
|
26702
28013
|
this.colorOwner = null;
|
|
26703
|
-
|
|
26704
|
-
this.menuEl = null;
|
|
26705
|
-
// floating option list for a dropdown control
|
|
28014
|
+
this.menuPop = null;
|
|
26706
28015
|
this.menuOwner = null;
|
|
26707
28016
|
this.onClose = null;
|
|
26708
28017
|
this.onOutside = (e) => {
|
|
26709
28018
|
const target = e.target;
|
|
26710
|
-
if (this.colorPop?.contains(target)) return;
|
|
26711
|
-
if (this.
|
|
28019
|
+
if (this.colorPop?.el.contains(target)) return;
|
|
28020
|
+
if (this.menuPop?.el.contains(target)) return;
|
|
26712
28021
|
if (this.el && !this.el.contains(target)) {
|
|
26713
28022
|
const cb = this.onClose;
|
|
26714
28023
|
this.close();
|
|
@@ -26727,13 +28036,13 @@ var DrawingSettingsPopup = class {
|
|
|
26727
28036
|
* picker / dropdown menu), which live outside `el`. */
|
|
26728
28037
|
contains(node) {
|
|
26729
28038
|
if (!node) return false;
|
|
26730
|
-
return this.el?.contains(node) === true || this.colorPop?.contains(node) === true || this.
|
|
28039
|
+
return this.el?.contains(node) === true || this.colorPop?.el.contains(node) === true || this.menuPop?.el.contains(node) === true;
|
|
26731
28040
|
}
|
|
26732
28041
|
/** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
|
|
26733
28042
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
26734
28043
|
open(drawing, anchor, actions, onClose) {
|
|
26735
28044
|
this.close();
|
|
26736
|
-
|
|
28045
|
+
ensureStyles4();
|
|
26737
28046
|
this.onClose = onClose ?? null;
|
|
26738
28047
|
const t = this.theme;
|
|
26739
28048
|
const schema = drawing.schema();
|
|
@@ -26744,8 +28053,7 @@ var DrawingSettingsPopup = class {
|
|
|
26744
28053
|
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;`;
|
|
26745
28054
|
applyChromeTokens(el, t);
|
|
26746
28055
|
el.addEventListener("pointerdown", () => {
|
|
26747
|
-
|
|
26748
|
-
this.closeMenu();
|
|
28056
|
+
closeOpenPopovers();
|
|
26749
28057
|
});
|
|
26750
28058
|
const bar = document.createElement("div");
|
|
26751
28059
|
bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
|
|
@@ -27482,49 +28790,76 @@ var DrawingSettingsPopup = class {
|
|
|
27482
28790
|
});
|
|
27483
28791
|
return b;
|
|
27484
28792
|
}
|
|
28793
|
+
/** Host-anchored floating shell (stays inside the chart). Content is filled after
|
|
28794
|
+
* construction so `fill` can close over the live `Popover` without hitting TDZ. */
|
|
28795
|
+
hostFloat(anchor, opts) {
|
|
28796
|
+
const t = this.theme;
|
|
28797
|
+
const pop = new Popover({
|
|
28798
|
+
trigger: anchor,
|
|
28799
|
+
host: this.host,
|
|
28800
|
+
theme: t,
|
|
28801
|
+
position: "absolute",
|
|
28802
|
+
boundary: this.host,
|
|
28803
|
+
boundaryInset: 4,
|
|
28804
|
+
viewportInset: 0,
|
|
28805
|
+
gap: 6,
|
|
28806
|
+
align: opts.align ?? "start",
|
|
28807
|
+
zIndex: opts.zIndex,
|
|
28808
|
+
onClose: () => {
|
|
28809
|
+
if (this.menuPop === pop) {
|
|
28810
|
+
this.menuPop = null;
|
|
28811
|
+
this.menuOwner = null;
|
|
28812
|
+
}
|
|
28813
|
+
if (this.colorPop === pop) {
|
|
28814
|
+
this.colorPop = null;
|
|
28815
|
+
this.colorOwner = null;
|
|
28816
|
+
}
|
|
28817
|
+
}
|
|
28818
|
+
});
|
|
28819
|
+
const el = pop.el;
|
|
28820
|
+
el.style.background = t.background;
|
|
28821
|
+
el.style.border = "1px solid var(--vela-border)";
|
|
28822
|
+
el.style.borderRadius = "var(--vela-radius-lg)";
|
|
28823
|
+
el.style.boxShadow = "var(--vela-shadow)";
|
|
28824
|
+
el.style.padding = opts.padding;
|
|
28825
|
+
el.style.display = "flex";
|
|
28826
|
+
el.style.flexDirection = "column";
|
|
28827
|
+
el.style.gap = "1px";
|
|
28828
|
+
el.style.color = t.textColor;
|
|
28829
|
+
el.style.font = `var(--vela-font-size-md) ${t.fontFamily}`;
|
|
28830
|
+
el.style.pointerEvents = "auto";
|
|
28831
|
+
opts.fill(el, pop);
|
|
28832
|
+
pop.show();
|
|
28833
|
+
return pop;
|
|
28834
|
+
}
|
|
27485
28835
|
/** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
|
|
27486
28836
|
openActionMenu(anchor, rows) {
|
|
27487
|
-
this.
|
|
27488
|
-
|
|
27489
|
-
|
|
27490
|
-
|
|
27491
|
-
|
|
27492
|
-
|
|
27493
|
-
|
|
27494
|
-
|
|
27495
|
-
|
|
27496
|
-
|
|
27497
|
-
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
27505
|
-
|
|
27506
|
-
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27510
|
-
|
|
27511
|
-
}
|
|
27512
|
-
|
|
27513
|
-
}
|
|
27514
|
-
this.host.appendChild(menu2);
|
|
27515
|
-
const ar = anchor.getBoundingClientRect();
|
|
27516
|
-
const hr = this.host.getBoundingClientRect();
|
|
27517
|
-
const w = menu2.offsetWidth;
|
|
27518
|
-
const h = menu2.offsetHeight;
|
|
27519
|
-
let left = ar.right - hr.left - w;
|
|
27520
|
-
let top = ar.bottom - hr.top + 6;
|
|
27521
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
27522
|
-
if (left < 4) left = 4;
|
|
27523
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
27524
|
-
if (top < 4) top = 4;
|
|
27525
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
27526
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
27527
|
-
this.menuEl = menu2;
|
|
28837
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
28838
|
+
align: "end",
|
|
28839
|
+
zIndex: 26,
|
|
28840
|
+
padding: "4px",
|
|
28841
|
+
fill: (menu2, pop) => {
|
|
28842
|
+
for (const row of rows) {
|
|
28843
|
+
const item = document.createElement("button");
|
|
28844
|
+
item.type = "button";
|
|
28845
|
+
item.className = "vela-dpop-item";
|
|
28846
|
+
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;";
|
|
28847
|
+
const ic = document.createElement("span");
|
|
28848
|
+
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
28849
|
+
ic.innerHTML = sized(row.icon, 18);
|
|
28850
|
+
const tx = document.createElement("span");
|
|
28851
|
+
tx.textContent = row.label;
|
|
28852
|
+
tx.style.cssText = "flex:1;";
|
|
28853
|
+
item.append(ic, tx);
|
|
28854
|
+
item.addEventListener("click", (e) => {
|
|
28855
|
+
e.stopPropagation();
|
|
28856
|
+
pop.hide();
|
|
28857
|
+
row.onClick();
|
|
28858
|
+
});
|
|
28859
|
+
menu2.appendChild(item);
|
|
28860
|
+
}
|
|
28861
|
+
}
|
|
28862
|
+
});
|
|
27528
28863
|
this.menuOwner = anchor;
|
|
27529
28864
|
}
|
|
27530
28865
|
/** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
|
|
@@ -27654,60 +28989,40 @@ var DrawingSettingsPopup = class {
|
|
|
27654
28989
|
/** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
|
|
27655
28990
|
* glyph (and text label when one is supplied); the current value is highlighted. */
|
|
27656
28991
|
openMenu(anchor, values, current, render, label, onPick) {
|
|
27657
|
-
this.
|
|
27658
|
-
|
|
27659
|
-
|
|
27660
|
-
|
|
27661
|
-
|
|
27662
|
-
|
|
27663
|
-
|
|
27664
|
-
|
|
27665
|
-
|
|
27666
|
-
|
|
27667
|
-
|
|
27668
|
-
|
|
27669
|
-
|
|
27670
|
-
|
|
27671
|
-
|
|
27672
|
-
|
|
27673
|
-
|
|
27674
|
-
|
|
27675
|
-
|
|
27676
|
-
|
|
27677
|
-
|
|
27678
|
-
|
|
27679
|
-
|
|
27680
|
-
|
|
28992
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
28993
|
+
zIndex: 26,
|
|
28994
|
+
padding: "4px",
|
|
28995
|
+
fill: (menu2, pop) => {
|
|
28996
|
+
for (const v of values) {
|
|
28997
|
+
const active = v === current;
|
|
28998
|
+
const item = document.createElement("button");
|
|
28999
|
+
item.type = "button";
|
|
29000
|
+
item.className = "vela-dpop-item";
|
|
29001
|
+
item.dataset.active = active ? "1" : "0";
|
|
29002
|
+
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;`;
|
|
29003
|
+
const ic = document.createElement("span");
|
|
29004
|
+
ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
|
|
29005
|
+
ic.innerHTML = sized(render(v), 18);
|
|
29006
|
+
item.appendChild(ic);
|
|
29007
|
+
if (label) {
|
|
29008
|
+
const tx = document.createElement("span");
|
|
29009
|
+
tx.textContent = label(v);
|
|
29010
|
+
tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
|
|
29011
|
+
item.appendChild(tx);
|
|
29012
|
+
}
|
|
29013
|
+
item.addEventListener("click", (e) => {
|
|
29014
|
+
e.stopPropagation();
|
|
29015
|
+
pop.hide();
|
|
29016
|
+
onPick(v);
|
|
29017
|
+
});
|
|
29018
|
+
menu2.appendChild(item);
|
|
29019
|
+
}
|
|
27681
29020
|
}
|
|
27682
|
-
|
|
27683
|
-
e.stopPropagation();
|
|
27684
|
-
this.closeMenu();
|
|
27685
|
-
onPick(v);
|
|
27686
|
-
});
|
|
27687
|
-
menu2.appendChild(item);
|
|
27688
|
-
}
|
|
27689
|
-
this.host.appendChild(menu2);
|
|
27690
|
-
const ar = anchor.getBoundingClientRect();
|
|
27691
|
-
const hr = this.host.getBoundingClientRect();
|
|
27692
|
-
const w = menu2.offsetWidth;
|
|
27693
|
-
const h = menu2.offsetHeight;
|
|
27694
|
-
let left = ar.left - hr.left;
|
|
27695
|
-
let top = ar.bottom - hr.top + 6;
|
|
27696
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
27697
|
-
if (left < 4) left = 4;
|
|
27698
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
27699
|
-
if (top < 4) top = 4;
|
|
27700
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
27701
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
27702
|
-
this.menuEl = menu2;
|
|
29021
|
+
});
|
|
27703
29022
|
this.menuOwner = anchor;
|
|
27704
29023
|
}
|
|
27705
29024
|
closeMenu() {
|
|
27706
|
-
|
|
27707
|
-
this.menuEl.remove();
|
|
27708
|
-
this.menuEl = null;
|
|
27709
|
-
}
|
|
27710
|
-
this.menuOwner = null;
|
|
29025
|
+
this.menuPop?.hide();
|
|
27711
29026
|
}
|
|
27712
29027
|
/** A color control: an `icon` over a thin **colored underline** showing the current
|
|
27713
29028
|
* value (the common idiom), with an invisible native picker overlaid to edit it.
|
|
@@ -27746,36 +29061,18 @@ var DrawingSettingsPopup = class {
|
|
|
27746
29061
|
}
|
|
27747
29062
|
/** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
|
|
27748
29063
|
openColorPopover(anchor, color, onChange) {
|
|
27749
|
-
this.closeColorPopover();
|
|
27750
|
-
this.closeMenu();
|
|
27751
29064
|
const t = this.theme;
|
|
27752
|
-
|
|
27753
|
-
|
|
27754
|
-
|
|
27755
|
-
|
|
27756
|
-
|
|
27757
|
-
|
|
27758
|
-
|
|
27759
|
-
const hr = this.host.getBoundingClientRect();
|
|
27760
|
-
const w = pop.offsetWidth;
|
|
27761
|
-
const h = pop.offsetHeight;
|
|
27762
|
-
let left = ar.left - hr.left;
|
|
27763
|
-
let top = ar.bottom - hr.top + 6;
|
|
27764
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
27765
|
-
if (left < 4) left = 4;
|
|
27766
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
27767
|
-
if (top < 4) top = 4;
|
|
27768
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
27769
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
27770
|
-
this.colorPop = pop;
|
|
29065
|
+
this.colorPop = this.hostFloat(anchor, {
|
|
29066
|
+
zIndex: 25,
|
|
29067
|
+
padding: "10px",
|
|
29068
|
+
fill: (el) => {
|
|
29069
|
+
el.appendChild(buildColorPicker(color, t, onChange));
|
|
29070
|
+
}
|
|
29071
|
+
});
|
|
27771
29072
|
this.colorOwner = anchor;
|
|
27772
29073
|
}
|
|
27773
29074
|
closeColorPopover() {
|
|
27774
|
-
|
|
27775
|
-
this.colorPop.remove();
|
|
27776
|
-
this.colorPop = null;
|
|
27777
|
-
}
|
|
27778
|
-
this.colorOwner = null;
|
|
29075
|
+
this.colorPop?.hide();
|
|
27779
29076
|
}
|
|
27780
29077
|
divider() {
|
|
27781
29078
|
const d = document.createElement("div");
|
|
@@ -28158,9 +29455,9 @@ var UserDrawingController = class {
|
|
|
28158
29455
|
}
|
|
28159
29456
|
/**
|
|
28160
29457
|
* Union of the visible drawings' price ranges on `paneId` whose time extent
|
|
28161
|
-
* intersects [fromTime, toTime]
|
|
28162
|
-
*
|
|
28163
|
-
*
|
|
29458
|
+
* intersects [fromTime, toTime]. Hidden drawings and full-width references
|
|
29459
|
+
* (hline → null extent) are handled too. Kept as the seam for a future
|
|
29460
|
+
* per-drawing autoscale opt-in — the renderer does not fold this in today.
|
|
28164
29461
|
*/
|
|
28165
29462
|
priceRangeForPane(paneId, fromTime, toTime) {
|
|
28166
29463
|
let lo = Infinity;
|
|
@@ -31981,7 +33278,6 @@ var NativeRenderer = class {
|
|
|
31981
33278
|
const n = this.coords.barCount;
|
|
31982
33279
|
if (n === 0) return;
|
|
31983
33280
|
const vr = this.coords.visibleLogicalRange();
|
|
31984
|
-
const vtr = this.coords.visibleTimeRange();
|
|
31985
33281
|
const i0 = Math.max(0, Math.floor(vr.from));
|
|
31986
33282
|
const i1 = Math.min(n - 1, Math.ceil(vr.to));
|
|
31987
33283
|
if (i1 < i0) return;
|
|
@@ -31999,9 +33295,7 @@ var NativeRenderer = class {
|
|
|
31999
33295
|
}
|
|
32000
33296
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
32001
33297
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
32002
|
-
|
|
32003
|
-
const udr = this.userDrawings?.priceRangeForPane(pane.id, vtr.from, vtr.to) ?? null;
|
|
32004
|
-
if (udr) dr = dr ? { min: Math.min(dr.min, udr.min), max: Math.max(dr.max, udr.max) } : udr;
|
|
33298
|
+
const dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
32005
33299
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
32006
33300
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
32007
33301
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -33009,16 +34303,16 @@ function fmtPrice(n, dp) {
|
|
|
33009
34303
|
const d = dp ?? decimalsFor(n);
|
|
33010
34304
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
33011
34305
|
}
|
|
33012
|
-
function fmtChange(
|
|
33013
|
-
if (
|
|
33014
|
-
const diff = close -
|
|
33015
|
-
const pct = diff /
|
|
34306
|
+
function fmtChange(open2, close) {
|
|
34307
|
+
if (open2 == null || close == null || !Number.isFinite(open2) || !Number.isFinite(close) || open2 === 0) return "";
|
|
34308
|
+
const diff = close - open2;
|
|
34309
|
+
const pct = diff / open2 * 100;
|
|
33016
34310
|
const sign = diff >= 0 ? "+" : "";
|
|
33017
34311
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
33018
34312
|
}
|
|
33019
34313
|
|
|
33020
34314
|
// src/widget/statusline.ts
|
|
33021
|
-
var
|
|
34315
|
+
var STYLE_ID24 = "vela-widget-statusline";
|
|
33022
34316
|
var CSS21 = `
|
|
33023
34317
|
.vela-statusline {
|
|
33024
34318
|
position: absolute;
|
|
@@ -33210,7 +34504,7 @@ var Statusline = class {
|
|
|
33210
34504
|
this.fitMode = false;
|
|
33211
34505
|
this.fitRO = null;
|
|
33212
34506
|
const doc = host.ownerDocument;
|
|
33213
|
-
injectStyles(
|
|
34507
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
33214
34508
|
host.classList.add("vela-has-statusline");
|
|
33215
34509
|
this.el = doc.createElement("div");
|
|
33216
34510
|
this.el.className = "vela-statusline";
|
|
@@ -33385,7 +34679,7 @@ var Statusline = class {
|
|
|
33385
34679
|
var MAX_FONT_PX = 36;
|
|
33386
34680
|
var MIN_FONT_PX = 12;
|
|
33387
34681
|
var FILL = 0.9;
|
|
33388
|
-
var
|
|
34682
|
+
var STYLE_ID25 = "vela-widget-watermark";
|
|
33389
34683
|
var CSS22 = `
|
|
33390
34684
|
.vela-watermark {
|
|
33391
34685
|
position: absolute;
|
|
@@ -33424,7 +34718,7 @@ var Watermark = class {
|
|
|
33424
34718
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
33425
34719
|
* fires during chart construction, before any subscriber can see it. */
|
|
33426
34720
|
this.loading = true;
|
|
33427
|
-
injectStyles(
|
|
34721
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
33428
34722
|
this.el = host.ownerDocument.createElement("div");
|
|
33429
34723
|
this.el.className = "vela-watermark";
|
|
33430
34724
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -33799,6 +35093,9 @@ var ChartCell = class {
|
|
|
33799
35093
|
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "") : null;
|
|
33800
35094
|
this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
|
|
33801
35095
|
this.statusline?.onChart(this.inner);
|
|
35096
|
+
void this.inner.data.ready().then(() => {
|
|
35097
|
+
if (this.inner && this.state.symbol) this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
|
|
35098
|
+
});
|
|
33802
35099
|
this.syncStatuslineColors();
|
|
33803
35100
|
this.contextMenu = new ChartContextMenu(this.host, {
|
|
33804
35101
|
resetView: () => {
|
|
@@ -33921,7 +35218,7 @@ var ChartCell = class {
|
|
|
33921
35218
|
this.state.timeframe = timeframe;
|
|
33922
35219
|
this.watermark?.update(symbol2, timeframe);
|
|
33923
35220
|
this.statusline?.setSymbol(symbol2);
|
|
33924
|
-
this.statusline?.setMeta(timeframe, this.inner?.data.
|
|
35221
|
+
this.statusline?.setMeta(timeframe, this.inner?.data.displayPrefix(symbol2) ?? this.state.provider ?? "");
|
|
33925
35222
|
if (this.inner) this.statusline?.onChart(this.inner);
|
|
33926
35223
|
this.refreshNativeCatalog();
|
|
33927
35224
|
this.deps.onMarketChanged(this.id);
|
|
@@ -34227,7 +35524,7 @@ function buildContext(host) {
|
|
|
34227
35524
|
setTimeframe: (tf) => host.active()?.setTimeframe(tf),
|
|
34228
35525
|
setPriceStyle: (style) => host.active()?.setPriceStyle(style),
|
|
34229
35526
|
openSymbolSearch: (query) => host.openSymbolSearch(query),
|
|
34230
|
-
togglePanel: (id,
|
|
35527
|
+
togglePanel: (id, open2) => host.togglePanel(id, open2),
|
|
34231
35528
|
host: host.root,
|
|
34232
35529
|
toast: (message, kind) => host.toast(message, kind),
|
|
34233
35530
|
cells: host.cells().map((c) => ({ id: c.id, chart: c.chart, symbol: c.symbol, timeframe: c.timeframe })),
|
|
@@ -34487,7 +35784,7 @@ var GAP_PX = 2;
|
|
|
34487
35784
|
var POOL_CAP = 16;
|
|
34488
35785
|
var TIME_AXIS_H3 = 22;
|
|
34489
35786
|
var ALERT_CAP = 50;
|
|
34490
|
-
var
|
|
35787
|
+
var STYLE_ID26 = "vela-workspace";
|
|
34491
35788
|
var CSS23 = `
|
|
34492
35789
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
34493
35790
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
@@ -34558,6 +35855,8 @@ var VelaWorkspace = class {
|
|
|
34558
35855
|
this.historyUnsub = null;
|
|
34559
35856
|
/** Favorite drawing tools — a WORKSPACE preference (one star set, every cell). */
|
|
34560
35857
|
this.favs = [];
|
|
35858
|
+
/** Favorite timeframes — the shared topbar's quick-switch chips, one set for the grid. */
|
|
35859
|
+
this.tfFavs = [];
|
|
34561
35860
|
/** Live sync configuration (mutable copy of the option). */
|
|
34562
35861
|
this.syncOpts = {};
|
|
34563
35862
|
this.stateTimer = null;
|
|
@@ -34621,6 +35920,7 @@ var VelaWorkspace = class {
|
|
|
34621
35920
|
}
|
|
34622
35921
|
this.timezone = boot?.timezone ?? opts.timezone ?? "Etc/UTC";
|
|
34623
35922
|
if (boot?.favorites) this.favs = [...boot.favorites];
|
|
35923
|
+
if (boot?.timeframeFavorites) this.tfFavs = [...boot.timeframeFavorites];
|
|
34624
35924
|
const sync = boot?.sync ?? opts.sync;
|
|
34625
35925
|
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) {
|
|
34626
35926
|
this.applySyncSetting(kind, sync?.[kind]);
|
|
@@ -34631,7 +35931,7 @@ var VelaWorkspace = class {
|
|
|
34631
35931
|
this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
|
|
34632
35932
|
const bootActive = boot?.activeCellId ?? null;
|
|
34633
35933
|
const doc = hostEl.ownerDocument;
|
|
34634
|
-
injectStyles(
|
|
35934
|
+
injectStyles(STYLE_ID26, CSS23, doc);
|
|
34635
35935
|
this.root = doc.createElement("div");
|
|
34636
35936
|
this.root.className = "vela-workspace";
|
|
34637
35937
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
@@ -34642,9 +35942,9 @@ var VelaWorkspace = class {
|
|
|
34642
35942
|
this.symbolPicker = new SymbolPicker({
|
|
34643
35943
|
host: this.root,
|
|
34644
35944
|
onSelect: (ticker) => this.active.setSymbol(ticker),
|
|
34645
|
-
onOpenChange: (
|
|
34646
|
-
if (
|
|
34647
|
-
this.trackDialog(
|
|
35945
|
+
onOpenChange: (open2) => {
|
|
35946
|
+
if (open2) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
|
|
35947
|
+
this.trackDialog(open2);
|
|
34648
35948
|
}
|
|
34649
35949
|
});
|
|
34650
35950
|
this.symbolPicker.setSource(() => this.feed.symbols());
|
|
@@ -34654,16 +35954,16 @@ var VelaWorkspace = class {
|
|
|
34654
35954
|
onChart: () => this.active.onChartRows(),
|
|
34655
35955
|
onAdd: (i) => this.active.addFromLibrary(i),
|
|
34656
35956
|
onRemove: (i) => this.active.removeFromChart(i),
|
|
34657
|
-
onOpenChange: (
|
|
34658
|
-
if (
|
|
34659
|
-
this.trackDialog(
|
|
35957
|
+
onOpenChange: (open2) => {
|
|
35958
|
+
if (open2) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
|
|
35959
|
+
this.trackDialog(open2);
|
|
34660
35960
|
}
|
|
34661
35961
|
}) : null;
|
|
34662
35962
|
const picker = this.indicatorPicker;
|
|
34663
35963
|
this.tfQuick = new TimeframeQuick({
|
|
34664
35964
|
host: this.root,
|
|
34665
35965
|
onApply: (tf) => this.setActiveTimeframe(tf),
|
|
34666
|
-
onOpenChange: (
|
|
35966
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34667
35967
|
});
|
|
34668
35968
|
this.topbar = new Topbar(this.root, {
|
|
34669
35969
|
symbol: "",
|
|
@@ -34675,8 +35975,10 @@ var VelaWorkspace = class {
|
|
|
34675
35975
|
onAlertsClick: (anchor) => this.openAlertsMenu(anchor),
|
|
34676
35976
|
timeframe: "60",
|
|
34677
35977
|
timeframes: opts.timeframes ?? DEFAULT_TIMEFRAMES,
|
|
35978
|
+
timeframeFavorites: this.tfFavs,
|
|
34678
35979
|
priceStyle: "candles",
|
|
34679
35980
|
onTimeframe: (tf) => this.setActiveTimeframe(tf),
|
|
35981
|
+
onTimeframeFavorite: (tf, on) => this.setTimeframeFavorite(tf, on),
|
|
34680
35982
|
onPriceStyle: (style) => this.active.setPriceStyle(style),
|
|
34681
35983
|
layout: {
|
|
34682
35984
|
current: this.def.id,
|
|
@@ -34890,7 +36192,7 @@ var VelaWorkspace = class {
|
|
|
34890
36192
|
cells: () => this.cells(),
|
|
34891
36193
|
setActiveCell: (id) => this.setActiveCell(id),
|
|
34892
36194
|
openSymbolSearch: (query) => this.symbolPicker.open(query ?? ""),
|
|
34893
|
-
togglePanel: (id,
|
|
36195
|
+
togglePanel: (id, open2) => this.dock.toggle(id, open2),
|
|
34894
36196
|
root: this.root,
|
|
34895
36197
|
toast: (message, kind) => this.toast.show(message, kind)
|
|
34896
36198
|
});
|
|
@@ -34927,6 +36229,7 @@ var VelaWorkspace = class {
|
|
|
34927
36229
|
const state = { version: 1, layout: this.def.id, timezone: this.timezone, sync: { ...this.syncOpts }, charts };
|
|
34928
36230
|
if (this.activeId) state.activeCellId = this.activeId;
|
|
34929
36231
|
if (this.favs.length > 0) state.favorites = [...this.favs];
|
|
36232
|
+
if (this.tfFavs.length > 0) state.timeframeFavorites = [...this.tfFavs];
|
|
34930
36233
|
if (this.trackSizes.size > 0) state.trackSizes = Object.fromEntries([...this.trackSizes].map(([k, v]) => [k, { ...v }]));
|
|
34931
36234
|
const panels2 = this.dock.getState();
|
|
34932
36235
|
if (panels2) state.panels = panels2;
|
|
@@ -34947,6 +36250,10 @@ var VelaWorkspace = class {
|
|
|
34947
36250
|
this.bottombar?.setTimezone(st.timezone);
|
|
34948
36251
|
}
|
|
34949
36252
|
if (st.favorites) this.favs = [...st.favorites];
|
|
36253
|
+
if (st.timeframeFavorites) {
|
|
36254
|
+
this.tfFavs = [...st.timeframeFavorites];
|
|
36255
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
36256
|
+
}
|
|
34950
36257
|
this.dock.applyState(st.panels);
|
|
34951
36258
|
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
34952
36259
|
this.trackSizes.clear();
|
|
@@ -35522,6 +36829,14 @@ var VelaWorkspace = class {
|
|
|
35522
36829
|
this.bottombar?.setActiveRange(null);
|
|
35523
36830
|
this.active.setTimeframe(tf);
|
|
35524
36831
|
}
|
|
36832
|
+
/** Star/unstar a timeframe — a WORKSPACE preference (one chip row, whatever the
|
|
36833
|
+
* active cell); the topbar follows and the set persists with the document. */
|
|
36834
|
+
setTimeframeFavorite(tf, on) {
|
|
36835
|
+
if (on === this.tfFavs.includes(tf)) return;
|
|
36836
|
+
this.tfFavs = on ? [tf, ...this.tfFavs] : this.tfFavs.filter((f) => f !== tf);
|
|
36837
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
36838
|
+
this.markStateDirty();
|
|
36839
|
+
}
|
|
35525
36840
|
/** The mode flipped (container resized across the breakpoint, or a coarse-pointer
|
|
35526
36841
|
* change). Close the open surfaces — a desktop card must not linger over the
|
|
35527
36842
|
* mobile chrome (and vice versa) — and re-present every cell's own chrome. */
|
|
@@ -35566,7 +36881,7 @@ var VelaWorkspace = class {
|
|
|
35566
36881
|
this.active.applyRange(preset);
|
|
35567
36882
|
this.bottombar?.setActiveRange(preset.id);
|
|
35568
36883
|
},
|
|
35569
|
-
onOpenChange: (
|
|
36884
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35570
36885
|
}));
|
|
35571
36886
|
this.tfDrawer.open();
|
|
35572
36887
|
}
|
|
@@ -35579,7 +36894,7 @@ var VelaWorkspace = class {
|
|
|
35579
36894
|
isFavorite: (type) => this.active.chart.drawings.isFavorite(type),
|
|
35580
36895
|
onFavorite: (type, on) => this.active.chart.drawings.setFavorite(type, on),
|
|
35581
36896
|
onSelect: (type) => this.active.chart.drawings.setTool(type),
|
|
35582
|
-
onOpenChange: (
|
|
36897
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35583
36898
|
}));
|
|
35584
36899
|
this.drawingsDrawer.open();
|
|
35585
36900
|
}
|
|
@@ -35618,7 +36933,7 @@ var VelaWorkspace = class {
|
|
|
35618
36933
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
35619
36934
|
}
|
|
35620
36935
|
},
|
|
35621
|
-
onOpenChange: (
|
|
36936
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35622
36937
|
}));
|
|
35623
36938
|
this.moreDrawer.open();
|
|
35624
36939
|
}
|
|
@@ -35627,7 +36942,7 @@ var VelaWorkspace = class {
|
|
|
35627
36942
|
host: this.root,
|
|
35628
36943
|
timezone: () => this.timezone,
|
|
35629
36944
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
35630
|
-
onOpenChange: (
|
|
36945
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35631
36946
|
}));
|
|
35632
36947
|
this.timezoneDrawer.open();
|
|
35633
36948
|
}
|
|
@@ -35637,7 +36952,7 @@ var VelaWorkspace = class {
|
|
|
35637
36952
|
host: this.root,
|
|
35638
36953
|
chart: () => this.activeId ? this.cellsById.get(this.activeId)?.chart ?? null : null,
|
|
35639
36954
|
pressY: () => this.priceScalePressY,
|
|
35640
|
-
onOpenChange: (
|
|
36955
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
35641
36956
|
}));
|
|
35642
36957
|
this.priceScaleDrawer.open();
|
|
35643
36958
|
}
|
|
@@ -35721,7 +37036,7 @@ var VelaWorkspace = class {
|
|
|
35721
37036
|
label: "Show this shortcuts panel",
|
|
35722
37037
|
category: "Help",
|
|
35723
37038
|
run: () => {
|
|
35724
|
-
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (
|
|
37039
|
+
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open2) => this.trackDialog(open2)));
|
|
35725
37040
|
this.shortcutsHelp.open();
|
|
35726
37041
|
}
|
|
35727
37042
|
});
|
|
@@ -35740,9 +37055,9 @@ var VelaWorkspace = class {
|
|
|
35740
37055
|
this.tfQuick.open(key);
|
|
35741
37056
|
}
|
|
35742
37057
|
}
|
|
35743
|
-
trackDialog(
|
|
35744
|
-
this.openDialogs = Math.max(0, this.openDialogs + (
|
|
35745
|
-
if (
|
|
37058
|
+
trackDialog(open2) {
|
|
37059
|
+
this.openDialogs = Math.max(0, this.openDialogs + (open2 ? 1 : -1));
|
|
37060
|
+
if (open2) this.keymap.pushScope("dialog");
|
|
35746
37061
|
else this.keymap.popScope("dialog");
|
|
35747
37062
|
}
|
|
35748
37063
|
/** Pool a dehydrated slot state (bounded — oldest entries drop past the cap). */
|