@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/widget.cjs
CHANGED
|
@@ -476,6 +476,10 @@ registerIcon(
|
|
|
476
476
|
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"/>')
|
|
477
477
|
);
|
|
478
478
|
registerIcon("clock", S('<circle cx="8" cy="8" r="6.2"/><path d="M8 4.8V8l2.4 1.6"/>'));
|
|
479
|
+
registerIcon(
|
|
480
|
+
"calendar",
|
|
481
|
+
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"/>')
|
|
482
|
+
);
|
|
479
483
|
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"/>'));
|
|
480
484
|
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"/>'));
|
|
481
485
|
registerIcon(
|
|
@@ -6932,7 +6936,7 @@ var ProviderRegistry = class {
|
|
|
6932
6936
|
/** Register (or replace) a provider; returns a promise that settles when its index is built. */
|
|
6933
6937
|
register(rawName, provider) {
|
|
6934
6938
|
const name = normName(rawName);
|
|
6935
|
-
const entry = { name, provider, index: null, descriptors: [], settled: false, settle: Promise.resolve() };
|
|
6939
|
+
const entry = { name, provider, index: null, prefixIndex: null, byTicker: null, descriptors: [], settled: false, settle: Promise.resolve() };
|
|
6936
6940
|
this.entries.set(name, entry);
|
|
6937
6941
|
entry.settle = this.buildIndex(entry);
|
|
6938
6942
|
queueMicrotask(() => this.notify(false));
|
|
@@ -6945,6 +6949,9 @@ var ProviderRegistry = class {
|
|
|
6945
6949
|
const symbols = await entry.provider.listSymbols();
|
|
6946
6950
|
entry.descriptors = symbols;
|
|
6947
6951
|
entry.index = new Set(symbols.map((s) => normTicker(s.ticker)));
|
|
6952
|
+
entry.byTicker = new Map(symbols.map((s) => [normTicker(s.ticker), s]));
|
|
6953
|
+
entry.prefixIndex = /* @__PURE__ */ new Map();
|
|
6954
|
+
for (const s of symbols) if (s.prefix) entry.prefixIndex.set(`${normName(s.prefix)}:${normTicker(s.ticker)}`, s);
|
|
6948
6955
|
}
|
|
6949
6956
|
} catch {
|
|
6950
6957
|
entry.index = null;
|
|
@@ -6980,14 +6987,25 @@ var ProviderRegistry = class {
|
|
|
6980
6987
|
}
|
|
6981
6988
|
/**
|
|
6982
6989
|
* Resolve a symbol to `{ provider, ticker }`, or null when nothing can serve it
|
|
6983
|
-
* yet. Explicit prefix → that provider (must be registered)
|
|
6984
|
-
*
|
|
6985
|
-
*
|
|
6986
|
-
*
|
|
6990
|
+
* yet. Explicit prefix → that provider (must be registered), else a provider whose
|
|
6991
|
+
* index declares that LISTING prefix for that ticker (`NASDAQ:AAPL` routes to the
|
|
6992
|
+
* provider serving Nasdaq-listed AAPL — TradingView-strict, so `NYSE:AAPL` resolves
|
|
6993
|
+
* to nothing rather than auto-correcting). Bare → first indexed provider
|
|
6994
|
+
* (registration order, `opts.default` first) that contains the ticker;
|
|
6995
|
+
* `opts.lenient` additionally allows a sole registered provider before its index
|
|
6996
|
+
* exists.
|
|
6987
6997
|
*/
|
|
6988
6998
|
resolve(raw, opts = {}) {
|
|
6989
6999
|
const { provider, ticker } = this.parse(raw);
|
|
6990
|
-
if (provider)
|
|
7000
|
+
if (provider) {
|
|
7001
|
+
if (this.entries.has(provider)) return { provider, ticker };
|
|
7002
|
+
const key = `${provider}:${normTicker(ticker)}`;
|
|
7003
|
+
for (const name of this.candidateOrder(opts.default)) {
|
|
7004
|
+
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
7005
|
+
if (d) return { provider: name, ticker: d.ticker };
|
|
7006
|
+
}
|
|
7007
|
+
return null;
|
|
7008
|
+
}
|
|
6991
7009
|
const norm = normTicker(ticker);
|
|
6992
7010
|
for (const name of this.candidateOrder(opts.default)) {
|
|
6993
7011
|
if (this.entries.get(name).index?.has(norm)) return { provider: name, ticker };
|
|
@@ -7053,6 +7071,22 @@ var ProviderRegistry = class {
|
|
|
7053
7071
|
}
|
|
7054
7072
|
return names;
|
|
7055
7073
|
}
|
|
7074
|
+
/**
|
|
7075
|
+
* The DISPLAY prefix for a resolved symbol — the descriptor's LISTING prefix when the
|
|
7076
|
+
* data declares one, else the provider name. This is the single seam every label
|
|
7077
|
+
* (legend chip, committed/persisted `PREFIX:TICKER` strings) derives from, which is
|
|
7078
|
+
* what makes a non-canonical spelling impossible to display: the form always comes
|
|
7079
|
+
* from the data, never from what was typed.
|
|
7080
|
+
*/
|
|
7081
|
+
displayPrefixOf(resolved) {
|
|
7082
|
+
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
7083
|
+
return d?.prefix ?? resolved.provider;
|
|
7084
|
+
}
|
|
7085
|
+
/** The canonical `PREFIX:TICKER` string for a resolved symbol (descriptor spellings). */
|
|
7086
|
+
canonicalSymbol(resolved) {
|
|
7087
|
+
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
7088
|
+
return `${d?.prefix ?? resolved.provider}:${d?.ticker ?? resolved.ticker}`;
|
|
7089
|
+
}
|
|
7056
7090
|
/** Indexed symbols for one provider (or all, concatenated) — for autocomplete. */
|
|
7057
7091
|
symbolsOf(rawName) {
|
|
7058
7092
|
if (rawName != null) return this.entries.get(normName(rawName))?.descriptors ?? [];
|
|
@@ -9191,6 +9225,20 @@ var MultiProviderFeed = class {
|
|
|
9191
9225
|
resolveSymbol(raw) {
|
|
9192
9226
|
return this.registry.resolve(raw, { default: this.primaryProvider });
|
|
9193
9227
|
}
|
|
9228
|
+
/**
|
|
9229
|
+
* The DISPLAY prefix for `raw` — the descriptor's LISTING prefix when the data
|
|
9230
|
+
* declares one (`NASDAQ` for AAPL), else the resolved provider name. Null while
|
|
9231
|
+
* nothing resolves the symbol.
|
|
9232
|
+
*/
|
|
9233
|
+
displayPrefix(raw) {
|
|
9234
|
+
const resolved = this.resolveSymbol(raw);
|
|
9235
|
+
return resolved ? this.registry.displayPrefixOf(resolved) : null;
|
|
9236
|
+
}
|
|
9237
|
+
/** The canonical `PREFIX:TICKER` form of `raw`, or null while unresolvable. */
|
|
9238
|
+
canonicalSymbol(raw) {
|
|
9239
|
+
const resolved = this.resolveSymbol(raw);
|
|
9240
|
+
return resolved ? this.registry.canonicalSymbol(resolved) : null;
|
|
9241
|
+
}
|
|
9194
9242
|
/** The registered provider INSTANCE under `name` (undefined if unknown). */
|
|
9195
9243
|
providerInstance(name) {
|
|
9196
9244
|
return this.registry.get(name);
|
|
@@ -9396,6 +9444,17 @@ var DataControl = class {
|
|
|
9396
9444
|
resolve(symbol) {
|
|
9397
9445
|
return this.registry?.resolveSymbol(symbol) ?? null;
|
|
9398
9446
|
}
|
|
9447
|
+
/**
|
|
9448
|
+
* The DISPLAY prefix for `symbol` — the listing venue its descriptor declares
|
|
9449
|
+
* (`NASDAQ` for AAPL) or the resolved provider name. Null while unresolvable.
|
|
9450
|
+
*/
|
|
9451
|
+
displayPrefix(symbol) {
|
|
9452
|
+
return this.registry?.displayPrefix(symbol) ?? null;
|
|
9453
|
+
}
|
|
9454
|
+
/** The canonical `PREFIX:TICKER` form of `symbol`, or null while unresolvable. */
|
|
9455
|
+
canonicalSymbol(symbol) {
|
|
9456
|
+
return this.registry?.canonicalSymbol(symbol) ?? null;
|
|
9457
|
+
}
|
|
9399
9458
|
/**
|
|
9400
9459
|
* The registered provider INSTANCE under `name` — the seam for EXTENDED provider
|
|
9401
9460
|
* surfaces: a provider may implement interfaces beyond the `DataProvider` port
|
|
@@ -9675,12 +9734,6 @@ function mix(base, top, alpha) {
|
|
|
9675
9734
|
const c = (x, y) => Math.round(x + (y - x) * alpha);
|
|
9676
9735
|
return `rgb(${c(b.r, t.r)},${c(b.g, t.g)},${c(b.b, t.b)})`;
|
|
9677
9736
|
}
|
|
9678
|
-
function toHex6(color, fallback = "#888888") {
|
|
9679
|
-
const c = parseRgb(color);
|
|
9680
|
-
if (!c) return fallback;
|
|
9681
|
-
const h = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
9682
|
-
return `#${h(c.r)}${h(c.g)}${h(c.b)}`;
|
|
9683
|
-
}
|
|
9684
9737
|
function isDarkColor(color) {
|
|
9685
9738
|
const c = parseRgb(color);
|
|
9686
9739
|
if (!c) return true;
|
|
@@ -9699,6 +9752,9 @@ var STATIC_TOKENS = {
|
|
|
9699
9752
|
"--vela-z-tooltip": "60",
|
|
9700
9753
|
"--vela-z-menu": "50",
|
|
9701
9754
|
"--vela-z-dialog": "40",
|
|
9755
|
+
// Form popovers portal to <body> (or a chart host) and must sit above a dialog
|
|
9756
|
+
// whose own stacking context may be nested inside the chart container.
|
|
9757
|
+
"--vela-z-popover": "6000",
|
|
9702
9758
|
"--vela-ease": "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
9703
9759
|
"--vela-dur-fast": "90ms",
|
|
9704
9760
|
"--vela-dur-med": "160ms",
|
|
@@ -9881,6 +9937,1246 @@ function makeDialogDraggable(box, header, options) {
|
|
|
9881
9937
|
return { reset };
|
|
9882
9938
|
}
|
|
9883
9939
|
|
|
9940
|
+
// src/ui/styles.ts
|
|
9941
|
+
function injectStyles(id, css, root = document) {
|
|
9942
|
+
if (typeof document === "undefined") return;
|
|
9943
|
+
const host = root instanceof Document ? root.head : root;
|
|
9944
|
+
if (root.getElementById?.(id) ?? host.querySelector(`#${CSS.escape(id)}`)) return;
|
|
9945
|
+
const s = document.createElement("style");
|
|
9946
|
+
s.id = id;
|
|
9947
|
+
s.textContent = css;
|
|
9948
|
+
host.appendChild(s);
|
|
9949
|
+
}
|
|
9950
|
+
|
|
9951
|
+
// src/ui/icons.ts
|
|
9952
|
+
function iconEl(id, doc = document) {
|
|
9953
|
+
const span = doc.createElement("span");
|
|
9954
|
+
span.className = "vela-icon";
|
|
9955
|
+
span.setAttribute("aria-hidden", "true");
|
|
9956
|
+
const svg = iconMarkup(id);
|
|
9957
|
+
if (svg) span.innerHTML = svg;
|
|
9958
|
+
return span;
|
|
9959
|
+
}
|
|
9960
|
+
|
|
9961
|
+
// src/ui/components/switch/controller.ts
|
|
9962
|
+
function switchController(opts = {}) {
|
|
9963
|
+
let checked = opts.checked ?? false;
|
|
9964
|
+
const disabled = opts.disabled ?? false;
|
|
9965
|
+
return {
|
|
9966
|
+
get checked() {
|
|
9967
|
+
return checked;
|
|
9968
|
+
},
|
|
9969
|
+
disabled,
|
|
9970
|
+
size: opts.size ?? "md",
|
|
9971
|
+
tone: opts.tone ?? "bright",
|
|
9972
|
+
setChecked(v) {
|
|
9973
|
+
checked = v;
|
|
9974
|
+
},
|
|
9975
|
+
toggle() {
|
|
9976
|
+
if (disabled) return checked;
|
|
9977
|
+
checked = !checked;
|
|
9978
|
+
opts.onChange?.(checked);
|
|
9979
|
+
return checked;
|
|
9980
|
+
}
|
|
9981
|
+
};
|
|
9982
|
+
}
|
|
9983
|
+
|
|
9984
|
+
// src/ui/components/switch/styles.ts
|
|
9985
|
+
var SWITCH_STYLE_ID = "vela-ui-switch";
|
|
9986
|
+
var SWITCH_CSS = `
|
|
9987
|
+
.vela-switch {
|
|
9988
|
+
width: 20px;
|
|
9989
|
+
height: 20px;
|
|
9990
|
+
flex: 0 0 auto;
|
|
9991
|
+
display: inline-flex;
|
|
9992
|
+
align-items: center;
|
|
9993
|
+
justify-content: center;
|
|
9994
|
+
padding: 0;
|
|
9995
|
+
border: 1px solid var(--vela-border);
|
|
9996
|
+
border-radius: 5px;
|
|
9997
|
+
background: transparent;
|
|
9998
|
+
color: transparent;
|
|
9999
|
+
cursor: pointer;
|
|
10000
|
+
line-height: 0;
|
|
10001
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
10002
|
+
}
|
|
10003
|
+
.vela-switch .vela-icon, .vela-switch svg { display: block; width: 12px; height: 12px; }
|
|
10004
|
+
.vela-switch[data-size='sm'] .vela-icon, .vela-switch[data-size='sm'] svg { width: 11px; height: 11px; }
|
|
10005
|
+
.vela-switch:hover { border-color: var(--vela-fg-muted); }
|
|
10006
|
+
.vela-switch[data-checked] {
|
|
10007
|
+
background: var(--vela-fg-bright);
|
|
10008
|
+
border-color: var(--vela-fg-bright);
|
|
10009
|
+
color: var(--vela-bg);
|
|
10010
|
+
}
|
|
10011
|
+
.vela-switch[data-size='sm'] { width: 18px; height: 18px; border-color: var(--vela-border-strong); }
|
|
10012
|
+
.vela-switch[data-size='sm']:hover { border-color: var(--vela-fg-muted); }
|
|
10013
|
+
.vela-switch[data-size='sm'][data-checked] {
|
|
10014
|
+
background: var(--vela-selected-bg);
|
|
10015
|
+
border-color: var(--vela-selected-bg);
|
|
10016
|
+
color: var(--vela-selected-fg);
|
|
10017
|
+
}
|
|
10018
|
+
.vela-switch[data-tone='selected'][data-checked] {
|
|
10019
|
+
background: var(--vela-selected-bg);
|
|
10020
|
+
border-color: var(--vela-selected-bg);
|
|
10021
|
+
color: var(--vela-selected-fg);
|
|
10022
|
+
}
|
|
10023
|
+
.vela-switch[disabled] { opacity: 0.4; cursor: default; }
|
|
10024
|
+
`;
|
|
10025
|
+
|
|
10026
|
+
// src/ui/components/switch/view.ts
|
|
10027
|
+
var Switch = class {
|
|
10028
|
+
constructor(opts = {}) {
|
|
10029
|
+
injectStyles(SWITCH_STYLE_ID, SWITCH_CSS, document);
|
|
10030
|
+
this.ctrl = switchController(opts);
|
|
10031
|
+
const b = document.createElement("button");
|
|
10032
|
+
b.type = "button";
|
|
10033
|
+
if (opts.id) b.id = opts.id;
|
|
10034
|
+
b.className = "vela-switch";
|
|
10035
|
+
b.dataset.size = this.ctrl.size;
|
|
10036
|
+
b.dataset.tone = this.ctrl.tone;
|
|
10037
|
+
b.setAttribute("role", "switch");
|
|
10038
|
+
if (this.ctrl.disabled) b.disabled = true;
|
|
10039
|
+
b.appendChild(iconEl("check", b.ownerDocument));
|
|
10040
|
+
this.el = b;
|
|
10041
|
+
this.paint();
|
|
10042
|
+
b.addEventListener("pointerdown", (e) => {
|
|
10043
|
+
e.preventDefault();
|
|
10044
|
+
b.focus({ preventScroll: true });
|
|
10045
|
+
});
|
|
10046
|
+
b.addEventListener("click", () => {
|
|
10047
|
+
this.ctrl.toggle();
|
|
10048
|
+
this.paint();
|
|
10049
|
+
});
|
|
10050
|
+
}
|
|
10051
|
+
get checked() {
|
|
10052
|
+
return this.ctrl.checked;
|
|
10053
|
+
}
|
|
10054
|
+
setChecked(v) {
|
|
10055
|
+
this.ctrl.setChecked(v);
|
|
10056
|
+
this.paint();
|
|
10057
|
+
}
|
|
10058
|
+
paint() {
|
|
10059
|
+
const on = this.ctrl.checked;
|
|
10060
|
+
this.el.setAttribute("aria-checked", on ? "true" : "false");
|
|
10061
|
+
if (on) this.el.dataset.checked = "";
|
|
10062
|
+
else delete this.el.dataset.checked;
|
|
10063
|
+
}
|
|
10064
|
+
};
|
|
10065
|
+
|
|
10066
|
+
// src/ui/tokens.ts
|
|
10067
|
+
var STATIC_ID = "vela-ui-tokens";
|
|
10068
|
+
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
10069
|
+
var STATIC_CSS = `
|
|
10070
|
+
.vela-ui, .vela-ui-layer {
|
|
10071
|
+
${STATIC_DECLS}
|
|
10072
|
+
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
10073
|
+
box-sizing: border-box;
|
|
10074
|
+
}
|
|
10075
|
+
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
10076
|
+
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
10077
|
+
.vela-icon svg { display: block; }
|
|
10078
|
+
`;
|
|
10079
|
+
function applyThemeTokens(el, t) {
|
|
10080
|
+
const tokens = themeTokens(t);
|
|
10081
|
+
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
10082
|
+
}
|
|
10083
|
+
function applyPlotOverlayTokens(host, base, config) {
|
|
10084
|
+
const layout = config?.layout;
|
|
10085
|
+
const t = layout?.background && layout.textColor ? { ...base, background: layout.background, textColor: layout.textColor } : base;
|
|
10086
|
+
applyThemeTokens(host, t);
|
|
10087
|
+
}
|
|
10088
|
+
function ensureUIHost(el, theme) {
|
|
10089
|
+
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
10090
|
+
el.classList.add("vela-ui");
|
|
10091
|
+
if (theme) applyThemeTokens(el, theme);
|
|
10092
|
+
}
|
|
10093
|
+
|
|
10094
|
+
// src/ui/components/popover/controller.ts
|
|
10095
|
+
function insetRect(r, inset) {
|
|
10096
|
+
return {
|
|
10097
|
+
left: r.left + inset,
|
|
10098
|
+
top: r.top + inset,
|
|
10099
|
+
right: r.right - inset,
|
|
10100
|
+
bottom: r.bottom - inset,
|
|
10101
|
+
width: r.width - inset * 2,
|
|
10102
|
+
height: r.height - inset * 2
|
|
10103
|
+
};
|
|
10104
|
+
}
|
|
10105
|
+
function viewportRect(width, height, inset) {
|
|
10106
|
+
return {
|
|
10107
|
+
left: inset,
|
|
10108
|
+
top: inset,
|
|
10109
|
+
right: width - inset,
|
|
10110
|
+
bottom: height - inset,
|
|
10111
|
+
width: width - inset * 2,
|
|
10112
|
+
height: height - inset * 2
|
|
10113
|
+
};
|
|
10114
|
+
}
|
|
10115
|
+
function intersectRects(a, b) {
|
|
10116
|
+
const left = Math.max(a.left, b.left);
|
|
10117
|
+
const top = Math.max(a.top, b.top);
|
|
10118
|
+
const right = Math.min(a.right, b.right);
|
|
10119
|
+
const bottom = Math.min(a.bottom, b.bottom);
|
|
10120
|
+
return { left, top, right, bottom, width: right - left, height: bottom - top };
|
|
10121
|
+
}
|
|
10122
|
+
function placePopover(a) {
|
|
10123
|
+
let left = a.align === "end" ? a.trigger.right - a.pop.width : a.trigger.left;
|
|
10124
|
+
const below = a.trigger.bottom + a.gap;
|
|
10125
|
+
const above = a.trigger.top - a.pop.height - a.gap;
|
|
10126
|
+
const fitsBelow = below + a.pop.height <= a.clamp.bottom;
|
|
10127
|
+
const fitsAbove = above >= a.clamp.top;
|
|
10128
|
+
let top = below;
|
|
10129
|
+
if (!fitsBelow && (fitsAbove || a.trigger.top - a.clamp.top > a.clamp.bottom - a.trigger.bottom)) {
|
|
10130
|
+
top = Math.max(a.clamp.top, above);
|
|
10131
|
+
}
|
|
10132
|
+
if (left + a.pop.width > a.clamp.right) left = a.clamp.right - a.pop.width;
|
|
10133
|
+
if (left < a.clamp.left) left = a.clamp.left;
|
|
10134
|
+
if (top + a.pop.height > a.clamp.bottom) top = a.clamp.bottom - a.pop.height;
|
|
10135
|
+
if (top < a.clamp.top) top = a.clamp.top;
|
|
10136
|
+
return {
|
|
10137
|
+
left: Math.round(left - a.originX),
|
|
10138
|
+
top: Math.round(top - a.originY)
|
|
10139
|
+
};
|
|
10140
|
+
}
|
|
10141
|
+
function popoverController(opts = {}) {
|
|
10142
|
+
return {
|
|
10143
|
+
gap: opts.gap ?? 4,
|
|
10144
|
+
align: opts.align ?? "start",
|
|
10145
|
+
matchWidth: opts.matchWidth ?? false,
|
|
10146
|
+
position: opts.position ?? "fixed",
|
|
10147
|
+
boundaryInset: opts.boundaryInset ?? 0,
|
|
10148
|
+
viewportInset: opts.viewportInset ?? 6,
|
|
10149
|
+
onClose: opts.onClose
|
|
10150
|
+
};
|
|
10151
|
+
}
|
|
10152
|
+
|
|
10153
|
+
// src/ui/components/popover/styles.ts
|
|
10154
|
+
var POPOVER_STYLE_ID = "vela-ui-popover";
|
|
10155
|
+
var POPOVER_CSS = `
|
|
10156
|
+
.vela-popover {
|
|
10157
|
+
position: fixed;
|
|
10158
|
+
z-index: var(--vela-z-popover);
|
|
10159
|
+
box-sizing: border-box;
|
|
10160
|
+
/* Never serve as a scroll anchor: the shell is portaled and re-placed between
|
|
10161
|
+
gestures, and anchoring against it jumps the scroller underneath. */
|
|
10162
|
+
overflow-anchor: none;
|
|
10163
|
+
}
|
|
10164
|
+
.vela-popover[data-position='absolute'] { position: absolute; }
|
|
10165
|
+
.vela-popover[hidden] { display: none !important; }
|
|
10166
|
+
`;
|
|
10167
|
+
|
|
10168
|
+
// src/ui/components/popover/view.ts
|
|
10169
|
+
var open = null;
|
|
10170
|
+
function closeOpenPopovers() {
|
|
10171
|
+
open?.hide();
|
|
10172
|
+
}
|
|
10173
|
+
function isPopoverOpen() {
|
|
10174
|
+
return open !== null;
|
|
10175
|
+
}
|
|
10176
|
+
function openPopoverTrigger() {
|
|
10177
|
+
return open?.trigger ?? null;
|
|
10178
|
+
}
|
|
10179
|
+
function toRect(r) {
|
|
10180
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
|
|
10181
|
+
}
|
|
10182
|
+
var Popover = class {
|
|
10183
|
+
constructor(opts) {
|
|
10184
|
+
this.onOutside = null;
|
|
10185
|
+
this.onKey = null;
|
|
10186
|
+
this.onReflow = null;
|
|
10187
|
+
this.shown = false;
|
|
10188
|
+
const doc = opts.trigger.ownerDocument;
|
|
10189
|
+
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
10190
|
+
this.trigger = opts.trigger;
|
|
10191
|
+
this.host = opts.host ?? doc.body;
|
|
10192
|
+
this.ctrl = popoverController(opts);
|
|
10193
|
+
this.boundary = opts.boundary ?? "viewport";
|
|
10194
|
+
this.theme = opts.theme;
|
|
10195
|
+
this.el = doc.createElement("div");
|
|
10196
|
+
this.el.className = "vela-popover vela-ui-layer" + (opts.className ? ` ${opts.className}` : "");
|
|
10197
|
+
this.el.dataset.position = this.ctrl.position;
|
|
10198
|
+
if (opts.zIndex !== void 0) this.el.style.zIndex = String(opts.zIndex);
|
|
10199
|
+
this.el.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
10200
|
+
if (opts.content instanceof Node) this.el.appendChild(opts.content);
|
|
10201
|
+
else if (typeof opts.content === "function") opts.content(this.el);
|
|
10202
|
+
}
|
|
10203
|
+
get open() {
|
|
10204
|
+
return this.shown;
|
|
10205
|
+
}
|
|
10206
|
+
get position() {
|
|
10207
|
+
return this.ctrl.position;
|
|
10208
|
+
}
|
|
10209
|
+
get align() {
|
|
10210
|
+
return this.ctrl.align;
|
|
10211
|
+
}
|
|
10212
|
+
show() {
|
|
10213
|
+
if (this.shown) {
|
|
10214
|
+
this.place();
|
|
10215
|
+
return;
|
|
10216
|
+
}
|
|
10217
|
+
if (open && open !== this) open.hide();
|
|
10218
|
+
ensureUIHost(this.el, this.theme);
|
|
10219
|
+
this.host.appendChild(this.el);
|
|
10220
|
+
this.shown = true;
|
|
10221
|
+
open = this;
|
|
10222
|
+
this.place();
|
|
10223
|
+
const onOutside = (ev) => {
|
|
10224
|
+
const t = ev.target;
|
|
10225
|
+
if (this.el.contains(t) || this.trigger.contains(t)) return;
|
|
10226
|
+
this.hide();
|
|
10227
|
+
};
|
|
10228
|
+
const onKey = (ev) => {
|
|
10229
|
+
if (ev.key !== "Escape") return;
|
|
10230
|
+
ev.preventDefault();
|
|
10231
|
+
ev.stopPropagation();
|
|
10232
|
+
this.hide();
|
|
10233
|
+
};
|
|
10234
|
+
const onReflow = () => this.place();
|
|
10235
|
+
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
10236
|
+
document.addEventListener("keydown", onKey, true);
|
|
10237
|
+
window.addEventListener("resize", onReflow, true);
|
|
10238
|
+
document.addEventListener("scroll", onReflow, true);
|
|
10239
|
+
this.onOutside = onOutside;
|
|
10240
|
+
this.onKey = onKey;
|
|
10241
|
+
this.onReflow = onReflow;
|
|
10242
|
+
}
|
|
10243
|
+
hide() {
|
|
10244
|
+
if (!this.shown) return;
|
|
10245
|
+
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
10246
|
+
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
10247
|
+
if (this.onReflow) {
|
|
10248
|
+
window.removeEventListener("resize", this.onReflow, true);
|
|
10249
|
+
document.removeEventListener("scroll", this.onReflow, true);
|
|
10250
|
+
}
|
|
10251
|
+
this.onOutside = null;
|
|
10252
|
+
this.onKey = null;
|
|
10253
|
+
this.onReflow = null;
|
|
10254
|
+
this.el.remove();
|
|
10255
|
+
this.shown = false;
|
|
10256
|
+
if (open === this) open = null;
|
|
10257
|
+
this.ctrl.onClose?.();
|
|
10258
|
+
}
|
|
10259
|
+
/** Show if closed, hide if this instance is the open popover. */
|
|
10260
|
+
toggle() {
|
|
10261
|
+
if (this.shown) this.hide();
|
|
10262
|
+
else this.show();
|
|
10263
|
+
}
|
|
10264
|
+
destroy() {
|
|
10265
|
+
this.hide();
|
|
10266
|
+
}
|
|
10267
|
+
reposition() {
|
|
10268
|
+
if (this.shown) this.place();
|
|
10269
|
+
}
|
|
10270
|
+
clampRect() {
|
|
10271
|
+
const view = viewportRect(window.innerWidth, window.innerHeight, this.ctrl.viewportInset);
|
|
10272
|
+
const bound = this.readBoundary();
|
|
10273
|
+
if (!bound) return view;
|
|
10274
|
+
const inset = insetRect(bound, this.ctrl.boundaryInset);
|
|
10275
|
+
return intersectRects(view, inset);
|
|
10276
|
+
}
|
|
10277
|
+
readBoundary() {
|
|
10278
|
+
const b = this.boundary;
|
|
10279
|
+
if (b === "viewport") return null;
|
|
10280
|
+
const raw = typeof b === "function" ? b() : b.getBoundingClientRect();
|
|
10281
|
+
return raw ? toRect(raw) : null;
|
|
10282
|
+
}
|
|
10283
|
+
place() {
|
|
10284
|
+
const ar = this.trigger.getBoundingClientRect();
|
|
10285
|
+
if (this.ctrl.matchWidth) {
|
|
10286
|
+
this.el.style.minWidth = `${Math.round(Math.max(this.el.offsetWidth, ar.width))}px`;
|
|
10287
|
+
}
|
|
10288
|
+
const origin = this.ctrl.position === "absolute" ? this.host.getBoundingClientRect() : { left: 0, top: 0 };
|
|
10289
|
+
const pos = placePopover({
|
|
10290
|
+
trigger: toRect(ar),
|
|
10291
|
+
pop: { width: this.el.offsetWidth, height: this.el.offsetHeight },
|
|
10292
|
+
gap: this.ctrl.gap,
|
|
10293
|
+
align: this.ctrl.align,
|
|
10294
|
+
clamp: this.clampRect(),
|
|
10295
|
+
originX: origin.left,
|
|
10296
|
+
originY: origin.top
|
|
10297
|
+
});
|
|
10298
|
+
this.el.style.left = `${pos.left}px`;
|
|
10299
|
+
this.el.style.top = `${pos.top}px`;
|
|
10300
|
+
}
|
|
10301
|
+
};
|
|
10302
|
+
|
|
10303
|
+
// src/ui/components/select/controller.ts
|
|
10304
|
+
function selectController(opts) {
|
|
10305
|
+
const options = opts.options;
|
|
10306
|
+
let value = opts.value ?? options[0]?.value ?? "";
|
|
10307
|
+
const size = opts.size ?? "md";
|
|
10308
|
+
return {
|
|
10309
|
+
options,
|
|
10310
|
+
get value() {
|
|
10311
|
+
return value;
|
|
10312
|
+
},
|
|
10313
|
+
size,
|
|
10314
|
+
fill: opts.fill ?? size !== "sm",
|
|
10315
|
+
disabled: opts.disabled ?? false,
|
|
10316
|
+
setValue(v) {
|
|
10317
|
+
value = v;
|
|
10318
|
+
},
|
|
10319
|
+
labelOf(v) {
|
|
10320
|
+
return options.find((o) => o.value === v)?.label ?? v;
|
|
10321
|
+
},
|
|
10322
|
+
pick(v) {
|
|
10323
|
+
value = v;
|
|
10324
|
+
const label = options.find((o) => o.value === v)?.label ?? v;
|
|
10325
|
+
opts.onChange?.(v, label);
|
|
10326
|
+
return { value: v, label };
|
|
10327
|
+
}
|
|
10328
|
+
};
|
|
10329
|
+
}
|
|
10330
|
+
|
|
10331
|
+
// src/ui/components/select/styles.ts
|
|
10332
|
+
var SELECT_STYLE_ID = "vela-ui-select";
|
|
10333
|
+
var SELECT_CSS = `
|
|
10334
|
+
.vela-select { position: relative; display: inline-block; min-width: 0; }
|
|
10335
|
+
.vela-select[data-fill] { width: 100%; }
|
|
10336
|
+
.vela-select-trigger {
|
|
10337
|
+
display: flex;
|
|
10338
|
+
align-items: center;
|
|
10339
|
+
width: 100%;
|
|
10340
|
+
box-sizing: border-box;
|
|
10341
|
+
height: 34px;
|
|
10342
|
+
padding: 0 26px 0 8px;
|
|
10343
|
+
background: transparent;
|
|
10344
|
+
border: 1px solid var(--vela-border-strong);
|
|
10345
|
+
border-radius: 6px;
|
|
10346
|
+
color: var(--vela-fg-bright);
|
|
10347
|
+
font-size: 14px;
|
|
10348
|
+
font-family: inherit;
|
|
10349
|
+
cursor: pointer;
|
|
10350
|
+
text-align: left;
|
|
10351
|
+
outline: none;
|
|
10352
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
10353
|
+
}
|
|
10354
|
+
.vela-select-trigger:hover { border-color: var(--vela-fg-muted); }
|
|
10355
|
+
.vela-select-trigger:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
10356
|
+
/* As a grid item (chart-settings rows dissolve via display:contents into a shared
|
|
10357
|
+
max-content column) the wrap must NOT stretch to the column: it sizes to its own
|
|
10358
|
+
widest option, like the native select it replaces. */
|
|
10359
|
+
.vela-select[data-size='sm'] { max-width: 200px; flex: 0 0 auto; justify-self: start; }
|
|
10360
|
+
.vela-select[data-size='sm'] .vela-select-trigger {
|
|
10361
|
+
height: 28px;
|
|
10362
|
+
background: var(--vela-surface-elev);
|
|
10363
|
+
border-radius: var(--vela-radius-sm);
|
|
10364
|
+
color: var(--vela-fg);
|
|
10365
|
+
font-size: 13px;
|
|
10366
|
+
box-shadow: none;
|
|
10367
|
+
}
|
|
10368
|
+
.vela-select[data-size='sm'] .vela-select-trigger:focus { box-shadow: none; border-color: var(--vela-fg-muted); }
|
|
10369
|
+
.vela-select-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
10370
|
+
/* Invisible option-label stack: gives the shrink-to-fit wrap the WIDEST option's
|
|
10371
|
+
intrinsic width (like a native select), so picking a shorter/longer label never
|
|
10372
|
+
resizes the trigger and shifts the row around it. */
|
|
10373
|
+
.vela-select-sizer { visibility: hidden; height: 0; overflow: hidden; font-size: 14px; }
|
|
10374
|
+
.vela-select[data-size='sm'] .vela-select-sizer { font-size: 13px; }
|
|
10375
|
+
.vela-select-sizer span { display: block; height: 0; white-space: nowrap; padding: 0 26px 0 8px; border-inline: 1px solid transparent; }
|
|
10376
|
+
.vela-select-chevron {
|
|
10377
|
+
position: absolute;
|
|
10378
|
+
right: 8px;
|
|
10379
|
+
top: 50%;
|
|
10380
|
+
transform: translateY(-50%);
|
|
10381
|
+
pointer-events: none;
|
|
10382
|
+
display: flex;
|
|
10383
|
+
opacity: 0.55;
|
|
10384
|
+
line-height: 0;
|
|
10385
|
+
color: inherit;
|
|
10386
|
+
}
|
|
10387
|
+
.vela-select-chevron .vela-icon, .vela-select-chevron svg { width: 12px; height: 12px; display: block; }
|
|
10388
|
+
.vela-select-list {
|
|
10389
|
+
background: var(--vela-bg);
|
|
10390
|
+
color: var(--vela-fg);
|
|
10391
|
+
border: none;
|
|
10392
|
+
border-radius: 6px;
|
|
10393
|
+
box-shadow: var(--vela-shadow);
|
|
10394
|
+
font: 14px var(--vela-font);
|
|
10395
|
+
padding: 4px;
|
|
10396
|
+
overflow: hidden;
|
|
10397
|
+
}
|
|
10398
|
+
.vela-select-list[data-size='sm'] { font-size: 13px; background: var(--vela-surface-overlay); }
|
|
10399
|
+
.vela-select-items { max-height: none; overflow: hidden; }
|
|
10400
|
+
.vela-select-list.is-scroll { display: flex; align-items: stretch; gap: 2px; }
|
|
10401
|
+
.vela-select-list.is-scroll .vela-select-items {
|
|
10402
|
+
flex: 1 1 auto;
|
|
10403
|
+
min-width: 0;
|
|
10404
|
+
max-height: 240px;
|
|
10405
|
+
overflow-y: auto;
|
|
10406
|
+
scrollbar-width: none;
|
|
10407
|
+
}
|
|
10408
|
+
.vela-select-list.is-scroll .vela-select-items::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
|
10409
|
+
.vela-select-rail { position: relative; flex: none; width: 3px; margin: 2px 1px 2px 0; pointer-events: none; }
|
|
10410
|
+
.vela-select-thumb { width: 3px; border-radius: 2px; background: var(--vela-scroll); }
|
|
10411
|
+
.vela-select-item {
|
|
10412
|
+
display: block;
|
|
10413
|
+
width: 100%;
|
|
10414
|
+
text-align: left;
|
|
10415
|
+
padding: 7px 10px;
|
|
10416
|
+
border: none;
|
|
10417
|
+
border-radius: 4px;
|
|
10418
|
+
background: transparent;
|
|
10419
|
+
color: inherit;
|
|
10420
|
+
cursor: pointer;
|
|
10421
|
+
font: inherit;
|
|
10422
|
+
font-weight: 400;
|
|
10423
|
+
}
|
|
10424
|
+
.vela-select-item:hover { background: var(--vela-hover); }
|
|
10425
|
+
.vela-select-item[data-checked] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
10426
|
+
.vela-select-item[data-checked]:hover { background: var(--vela-hover-strong); }
|
|
10427
|
+
`;
|
|
10428
|
+
|
|
10429
|
+
// src/ui/components/select/view.ts
|
|
10430
|
+
function ensureSelectStyles(doc) {
|
|
10431
|
+
injectStyles(SELECT_STYLE_ID, SELECT_CSS, doc);
|
|
10432
|
+
}
|
|
10433
|
+
function syncThumb(list, thumb) {
|
|
10434
|
+
const view = list.clientHeight;
|
|
10435
|
+
const total = list.scrollHeight;
|
|
10436
|
+
if (total <= view) {
|
|
10437
|
+
thumb.style.height = "0";
|
|
10438
|
+
return;
|
|
10439
|
+
}
|
|
10440
|
+
const thumbH = Math.max(20, view / total * view);
|
|
10441
|
+
const top = list.scrollTop / (total - view) * (view - thumbH);
|
|
10442
|
+
thumb.style.height = `${Math.round(thumbH)}px`;
|
|
10443
|
+
thumb.style.transform = `translateY(${Math.round(top)}px`;
|
|
10444
|
+
}
|
|
10445
|
+
function fillSelectList(menu2, options, current, onPick) {
|
|
10446
|
+
menu2.replaceChildren();
|
|
10447
|
+
const list = menu2.ownerDocument.createElement("div");
|
|
10448
|
+
list.className = "vela-select-items";
|
|
10449
|
+
for (const p of options) {
|
|
10450
|
+
const item = menu2.ownerDocument.createElement("button");
|
|
10451
|
+
item.type = "button";
|
|
10452
|
+
item.className = "vela-select-item";
|
|
10453
|
+
item.textContent = p.label;
|
|
10454
|
+
if (p.value === current) item.dataset.checked = "1";
|
|
10455
|
+
item.addEventListener("click", (e) => {
|
|
10456
|
+
e.stopPropagation();
|
|
10457
|
+
onPick(p.value, p.label);
|
|
10458
|
+
});
|
|
10459
|
+
list.appendChild(item);
|
|
10460
|
+
}
|
|
10461
|
+
menu2.appendChild(list);
|
|
10462
|
+
}
|
|
10463
|
+
function decorateSelectScroll(menu2) {
|
|
10464
|
+
const list = menu2.querySelector(".vela-select-items");
|
|
10465
|
+
if (!list) return;
|
|
10466
|
+
if (list.scrollHeight > 240) {
|
|
10467
|
+
menu2.classList.add("is-scroll");
|
|
10468
|
+
const rail = menu2.ownerDocument.createElement("div");
|
|
10469
|
+
rail.className = "vela-select-rail";
|
|
10470
|
+
const thumb = menu2.ownerDocument.createElement("div");
|
|
10471
|
+
thumb.className = "vela-select-thumb";
|
|
10472
|
+
rail.appendChild(thumb);
|
|
10473
|
+
menu2.appendChild(rail);
|
|
10474
|
+
list.addEventListener("scroll", () => syncThumb(list, thumb));
|
|
10475
|
+
syncThumb(list, thumb);
|
|
10476
|
+
}
|
|
10477
|
+
menu2.querySelector(".vela-select-item[data-checked]")?.scrollIntoView({ block: "nearest" });
|
|
10478
|
+
list.dispatchEvent(new Event("scroll"));
|
|
10479
|
+
}
|
|
10480
|
+
function toggleSelectList(trigger, options, current, onPick, opts = {}) {
|
|
10481
|
+
if (openPopoverTrigger() === trigger) {
|
|
10482
|
+
closeOpenPopovers();
|
|
10483
|
+
return null;
|
|
10484
|
+
}
|
|
10485
|
+
return openSelectList(trigger, options, current, onPick, opts);
|
|
10486
|
+
}
|
|
10487
|
+
function openSelectList(trigger, options, current, onPick, opts = {}) {
|
|
10488
|
+
ensureSelectStyles(trigger.ownerDocument);
|
|
10489
|
+
closeOpenPopovers();
|
|
10490
|
+
const pop = new Popover({
|
|
10491
|
+
trigger,
|
|
10492
|
+
theme: opts.theme,
|
|
10493
|
+
matchWidth: opts.matchWidth ?? true,
|
|
10494
|
+
boundary: opts.boundary,
|
|
10495
|
+
boundaryInset: opts.boundaryInset,
|
|
10496
|
+
gap: opts.gap ?? 4,
|
|
10497
|
+
align: opts.align ?? "start",
|
|
10498
|
+
host: opts.host,
|
|
10499
|
+
position: opts.position,
|
|
10500
|
+
zIndex: opts.zIndex,
|
|
10501
|
+
className: "vela-select-list",
|
|
10502
|
+
onClose: opts.onClose,
|
|
10503
|
+
content: (el) => {
|
|
10504
|
+
if (opts.size) el.dataset.size = opts.size;
|
|
10505
|
+
fillSelectList(el, options, current, (value, label) => {
|
|
10506
|
+
pop.hide();
|
|
10507
|
+
onPick(value, label);
|
|
10508
|
+
});
|
|
10509
|
+
}
|
|
10510
|
+
});
|
|
10511
|
+
pop.show();
|
|
10512
|
+
decorateSelectScroll(pop.el);
|
|
10513
|
+
pop.reposition();
|
|
10514
|
+
return pop;
|
|
10515
|
+
}
|
|
10516
|
+
var Select = class {
|
|
10517
|
+
constructor(opts) {
|
|
10518
|
+
this.list = null;
|
|
10519
|
+
const doc = opts.list?.host?.ownerDocument ?? document;
|
|
10520
|
+
ensureSelectStyles(doc);
|
|
10521
|
+
this.ctrl = selectController(opts);
|
|
10522
|
+
this.listOpts = { ...opts.list, theme: opts.theme ?? opts.list?.theme, size: this.ctrl.size };
|
|
10523
|
+
const wrap = doc.createElement("div");
|
|
10524
|
+
wrap.className = "vela-select";
|
|
10525
|
+
wrap.dataset.size = this.ctrl.size;
|
|
10526
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
10527
|
+
const btn = doc.createElement("button");
|
|
10528
|
+
btn.type = "button";
|
|
10529
|
+
if (opts.id) btn.id = opts.id;
|
|
10530
|
+
btn.className = "vela-select-trigger";
|
|
10531
|
+
if (this.ctrl.disabled) btn.disabled = true;
|
|
10532
|
+
const label = doc.createElement("span");
|
|
10533
|
+
label.className = "vela-select-label";
|
|
10534
|
+
label.textContent = this.ctrl.labelOf(this.ctrl.value);
|
|
10535
|
+
const chevron = doc.createElement("span");
|
|
10536
|
+
chevron.className = "vela-select-chevron";
|
|
10537
|
+
chevron.appendChild(iconEl("chevron-down", doc));
|
|
10538
|
+
btn.append(label, chevron);
|
|
10539
|
+
wrap.appendChild(btn);
|
|
10540
|
+
const sizer = doc.createElement("div");
|
|
10541
|
+
sizer.className = "vela-select-sizer";
|
|
10542
|
+
sizer.setAttribute("aria-hidden", "true");
|
|
10543
|
+
for (const o of this.ctrl.options) {
|
|
10544
|
+
const s = doc.createElement("span");
|
|
10545
|
+
s.textContent = o.label;
|
|
10546
|
+
sizer.appendChild(s);
|
|
10547
|
+
}
|
|
10548
|
+
wrap.appendChild(sizer);
|
|
10549
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
10550
|
+
e.preventDefault();
|
|
10551
|
+
btn.focus({ preventScroll: true });
|
|
10552
|
+
});
|
|
10553
|
+
btn.addEventListener("click", (e) => {
|
|
10554
|
+
e.stopPropagation();
|
|
10555
|
+
this.toggle();
|
|
10556
|
+
});
|
|
10557
|
+
this.el = wrap;
|
|
10558
|
+
this.trigger = btn;
|
|
10559
|
+
this.labelEl = label;
|
|
10560
|
+
}
|
|
10561
|
+
get value() {
|
|
10562
|
+
return this.ctrl.value;
|
|
10563
|
+
}
|
|
10564
|
+
setValue(v) {
|
|
10565
|
+
this.ctrl.setValue(v);
|
|
10566
|
+
this.labelEl.textContent = this.ctrl.labelOf(v);
|
|
10567
|
+
}
|
|
10568
|
+
toggle() {
|
|
10569
|
+
this.list = toggleSelectList(
|
|
10570
|
+
this.trigger,
|
|
10571
|
+
this.ctrl.options,
|
|
10572
|
+
this.ctrl.value,
|
|
10573
|
+
(value, label) => {
|
|
10574
|
+
this.ctrl.pick(value);
|
|
10575
|
+
this.labelEl.textContent = label;
|
|
10576
|
+
this.list = null;
|
|
10577
|
+
},
|
|
10578
|
+
{ ...this.listOpts, onClose: () => {
|
|
10579
|
+
this.list = null;
|
|
10580
|
+
this.listOpts.onClose?.();
|
|
10581
|
+
} }
|
|
10582
|
+
);
|
|
10583
|
+
}
|
|
10584
|
+
destroy() {
|
|
10585
|
+
this.list?.hide();
|
|
10586
|
+
this.list = null;
|
|
10587
|
+
}
|
|
10588
|
+
};
|
|
10589
|
+
|
|
10590
|
+
// src/ui/components/number-input/controller.ts
|
|
10591
|
+
function clampNumber(n, opts) {
|
|
10592
|
+
let v = n;
|
|
10593
|
+
if (opts.min !== void 0) v = Math.max(opts.min, v);
|
|
10594
|
+
if (opts.max !== void 0) v = Math.min(opts.max, v);
|
|
10595
|
+
if (opts.integer) v = Math.round(v);
|
|
10596
|
+
return v;
|
|
10597
|
+
}
|
|
10598
|
+
function decimalsOf(n) {
|
|
10599
|
+
const s = String(n);
|
|
10600
|
+
const e = s.search(/[eE]/);
|
|
10601
|
+
if (e >= 0) {
|
|
10602
|
+
const frac = s.slice(0, e).split(".")[1]?.length ?? 0;
|
|
10603
|
+
return Math.max(0, Math.min(20, frac - Number(s.slice(e + 1))));
|
|
10604
|
+
}
|
|
10605
|
+
return s.split(".")[1]?.length ?? 0;
|
|
10606
|
+
}
|
|
10607
|
+
function snapToStep(n, base, step) {
|
|
10608
|
+
const d = Math.min(20, Math.max(decimalsOf(base), decimalsOf(step)));
|
|
10609
|
+
return Number(n.toFixed(d));
|
|
10610
|
+
}
|
|
10611
|
+
function numberInputController(opts) {
|
|
10612
|
+
let value = opts.value;
|
|
10613
|
+
const integer = opts.integer ?? false;
|
|
10614
|
+
const commit = opts.commit ?? "blur";
|
|
10615
|
+
const clamp = opts.clamp ?? commit === "blur";
|
|
10616
|
+
const step = opts.step ?? (integer ? 1 : 0.1);
|
|
10617
|
+
const onChange = opts.onChange;
|
|
10618
|
+
const normalize = (raw) => clamp ? clampNumber(raw, { min: opts.min, max: opts.max, integer }) : raw;
|
|
10619
|
+
const apply = (raw) => {
|
|
10620
|
+
if (!Number.isFinite(raw)) return null;
|
|
10621
|
+
const n = normalize(raw);
|
|
10622
|
+
if (n !== value) {
|
|
10623
|
+
value = n;
|
|
10624
|
+
onChange?.(n);
|
|
10625
|
+
}
|
|
10626
|
+
return n;
|
|
10627
|
+
};
|
|
10628
|
+
return {
|
|
10629
|
+
get value() {
|
|
10630
|
+
return value;
|
|
10631
|
+
},
|
|
10632
|
+
min: opts.min,
|
|
10633
|
+
max: opts.max,
|
|
10634
|
+
step,
|
|
10635
|
+
integer,
|
|
10636
|
+
size: opts.size ?? "md",
|
|
10637
|
+
commit,
|
|
10638
|
+
steppers: opts.steppers ?? commit === "blur",
|
|
10639
|
+
clamp,
|
|
10640
|
+
disabled: opts.disabled ?? false,
|
|
10641
|
+
apply,
|
|
10642
|
+
sync(raw) {
|
|
10643
|
+
const n = Number.isFinite(raw) ? normalize(raw) : value;
|
|
10644
|
+
value = n;
|
|
10645
|
+
return n;
|
|
10646
|
+
},
|
|
10647
|
+
nudge(dir) {
|
|
10648
|
+
return apply(snapToStep(value + dir * step, value, step));
|
|
10649
|
+
}
|
|
10650
|
+
};
|
|
10651
|
+
}
|
|
10652
|
+
|
|
10653
|
+
// src/ui/components/number-input/styles.ts
|
|
10654
|
+
var NUMBER_STYLE_ID = "vela-ui-number";
|
|
10655
|
+
var NUMBER_CSS = `
|
|
10656
|
+
.vela-num { position: relative; display: inline-block; min-width: 0; }
|
|
10657
|
+
.vela-num[data-fill] { width: 100%; }
|
|
10658
|
+
.vela-num input {
|
|
10659
|
+
width: 100%;
|
|
10660
|
+
box-sizing: border-box;
|
|
10661
|
+
height: 34px;
|
|
10662
|
+
background: transparent;
|
|
10663
|
+
border: 1px solid var(--vela-border-strong);
|
|
10664
|
+
border-radius: 6px;
|
|
10665
|
+
color: var(--vela-fg-bright);
|
|
10666
|
+
padding: 0 8px;
|
|
10667
|
+
font-size: 14px;
|
|
10668
|
+
font-family: inherit;
|
|
10669
|
+
outline: none;
|
|
10670
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
10671
|
+
-moz-appearance: textfield;
|
|
10672
|
+
appearance: textfield;
|
|
10673
|
+
}
|
|
10674
|
+
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
10675
|
+
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
10676
|
+
.vela-num input:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
10677
|
+
.vela-num[data-steppers] input { padding-right: 26px; }
|
|
10678
|
+
.vela-num[data-size='sm'] { width: 64px; flex: none; }
|
|
10679
|
+
.vela-num[data-size='sm'][data-compact] { width: 56px; }
|
|
10680
|
+
.vela-num[data-size='sm'] input {
|
|
10681
|
+
height: 28px;
|
|
10682
|
+
background: var(--vela-surface-elev);
|
|
10683
|
+
border-radius: var(--vela-radius-sm);
|
|
10684
|
+
color: var(--vela-fg);
|
|
10685
|
+
font-size: 13px;
|
|
10686
|
+
box-shadow: none;
|
|
10687
|
+
}
|
|
10688
|
+
.vela-num[data-size='sm'] input:focus { box-shadow: none; }
|
|
10689
|
+
.vela-num-step {
|
|
10690
|
+
position: absolute;
|
|
10691
|
+
right: 0;
|
|
10692
|
+
top: 0;
|
|
10693
|
+
bottom: 0;
|
|
10694
|
+
width: 22px;
|
|
10695
|
+
display: none;
|
|
10696
|
+
flex-direction: column;
|
|
10697
|
+
justify-content: center;
|
|
10698
|
+
box-sizing: border-box;
|
|
10699
|
+
padding: 2px 6px 2px 0;
|
|
10700
|
+
line-height: 0;
|
|
10701
|
+
}
|
|
10702
|
+
.vela-num[data-steppers]:hover .vela-num-step { display: flex; }
|
|
10703
|
+
.vela-num-step button {
|
|
10704
|
+
flex: 1;
|
|
10705
|
+
width: 100%;
|
|
10706
|
+
min-height: 0;
|
|
10707
|
+
border: none;
|
|
10708
|
+
background: transparent;
|
|
10709
|
+
color: var(--vela-fg-muted);
|
|
10710
|
+
border-radius: 3px;
|
|
10711
|
+
padding: 0;
|
|
10712
|
+
cursor: pointer;
|
|
10713
|
+
display: flex;
|
|
10714
|
+
align-items: center;
|
|
10715
|
+
justify-content: center;
|
|
10716
|
+
}
|
|
10717
|
+
.vela-num-step button:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
10718
|
+
.vela-num-step .vela-icon, .vela-num-step svg { width: 12px; height: 12px; display: block; }
|
|
10719
|
+
`;
|
|
10720
|
+
|
|
10721
|
+
// src/ui/components/number-input/view.ts
|
|
10722
|
+
var NumberInput = class {
|
|
10723
|
+
constructor(opts) {
|
|
10724
|
+
const doc = document;
|
|
10725
|
+
injectStyles(NUMBER_STYLE_ID, NUMBER_CSS, doc);
|
|
10726
|
+
this.ctrl = numberInputController(opts);
|
|
10727
|
+
this.placeholderMode = opts.placeholder !== void 0;
|
|
10728
|
+
this.emptyValue = opts.emptyValue ?? opts.value;
|
|
10729
|
+
this.last = String(opts.value);
|
|
10730
|
+
const wrap = doc.createElement("div");
|
|
10731
|
+
wrap.className = "vela-num";
|
|
10732
|
+
wrap.dataset.size = this.ctrl.size;
|
|
10733
|
+
if (this.ctrl.size !== "sm") wrap.dataset.fill = "";
|
|
10734
|
+
if (opts.compact) wrap.dataset.compact = "";
|
|
10735
|
+
if (this.ctrl.steppers) wrap.dataset.steppers = "";
|
|
10736
|
+
const ni = doc.createElement("input");
|
|
10737
|
+
ni.type = "number";
|
|
10738
|
+
if (opts.id) ni.id = opts.id;
|
|
10739
|
+
if (opts.title) ni.title = opts.title;
|
|
10740
|
+
if (opts.min !== void 0) ni.min = String(opts.min);
|
|
10741
|
+
if (opts.max !== void 0) ni.max = String(opts.max);
|
|
10742
|
+
ni.step = String(this.ctrl.step);
|
|
10743
|
+
if (this.ctrl.disabled) ni.disabled = true;
|
|
10744
|
+
if (this.placeholderMode) {
|
|
10745
|
+
ni.placeholder = opts.placeholder ?? "";
|
|
10746
|
+
ni.value = opts.value !== this.emptyValue ? String(opts.value) : "";
|
|
10747
|
+
} else {
|
|
10748
|
+
ni.value = String(opts.value);
|
|
10749
|
+
}
|
|
10750
|
+
const commitRaw = (raw) => {
|
|
10751
|
+
const n = this.ctrl.apply(raw);
|
|
10752
|
+
if (n === null) {
|
|
10753
|
+
ni.value = this.placeholderMode && this.ctrl.value === this.emptyValue ? "" : this.last;
|
|
10754
|
+
return;
|
|
10755
|
+
}
|
|
10756
|
+
this.last = String(n);
|
|
10757
|
+
if (this.placeholderMode && n === this.emptyValue) ni.value = "";
|
|
10758
|
+
else ni.value = String(n);
|
|
10759
|
+
};
|
|
10760
|
+
if (this.placeholderMode) {
|
|
10761
|
+
ni.addEventListener("change", () => {
|
|
10762
|
+
const raw = ni.value.trim() === "" ? this.emptyValue : Number(ni.value);
|
|
10763
|
+
commitRaw(Number.isFinite(raw) ? raw : this.emptyValue);
|
|
10764
|
+
});
|
|
10765
|
+
} else if (this.ctrl.commit === "live") {
|
|
10766
|
+
ni.addEventListener("input", () => {
|
|
10767
|
+
const v = Number(ni.value);
|
|
10768
|
+
if (Number.isFinite(v)) this.ctrl.apply(v);
|
|
10769
|
+
});
|
|
10770
|
+
} else {
|
|
10771
|
+
ni.addEventListener("blur", () => commitRaw(Number(ni.value)));
|
|
10772
|
+
ni.addEventListener("keydown", (e) => {
|
|
10773
|
+
if (e.key === "Enter") {
|
|
10774
|
+
e.preventDefault();
|
|
10775
|
+
ni.blur();
|
|
10776
|
+
}
|
|
10777
|
+
});
|
|
10778
|
+
}
|
|
10779
|
+
wrap.appendChild(ni);
|
|
10780
|
+
if (this.ctrl.steppers) {
|
|
10781
|
+
wrap.appendChild(this.buildSteppers(doc, (dir) => {
|
|
10782
|
+
const cur = Number(ni.value);
|
|
10783
|
+
const base = Number.isFinite(cur) ? cur : this.ctrl.value;
|
|
10784
|
+
commitRaw(snapToStep(base + dir * this.ctrl.step, base, this.ctrl.step));
|
|
10785
|
+
}));
|
|
10786
|
+
}
|
|
10787
|
+
this.el = wrap;
|
|
10788
|
+
this.input = ni;
|
|
10789
|
+
}
|
|
10790
|
+
get value() {
|
|
10791
|
+
return this.ctrl.value;
|
|
10792
|
+
}
|
|
10793
|
+
setValue(v) {
|
|
10794
|
+
const n = this.ctrl.sync(v);
|
|
10795
|
+
this.last = String(n);
|
|
10796
|
+
if (this.placeholderMode && n === this.emptyValue) this.input.value = "";
|
|
10797
|
+
else this.input.value = String(n);
|
|
10798
|
+
}
|
|
10799
|
+
buildSteppers(doc, applyDir) {
|
|
10800
|
+
const steps = doc.createElement("div");
|
|
10801
|
+
steps.className = "vela-num-step";
|
|
10802
|
+
const mk = (dir, icon2, label) => {
|
|
10803
|
+
const b = doc.createElement("button");
|
|
10804
|
+
b.type = "button";
|
|
10805
|
+
b.tabIndex = -1;
|
|
10806
|
+
b.setAttribute("aria-label", label);
|
|
10807
|
+
b.appendChild(iconEl(icon2, doc));
|
|
10808
|
+
let timer = 0;
|
|
10809
|
+
const apply = () => applyDir(dir);
|
|
10810
|
+
const stop = () => {
|
|
10811
|
+
if (timer) {
|
|
10812
|
+
window.clearTimeout(timer);
|
|
10813
|
+
timer = 0;
|
|
10814
|
+
}
|
|
10815
|
+
};
|
|
10816
|
+
b.addEventListener("pointerdown", (e) => {
|
|
10817
|
+
e.preventDefault();
|
|
10818
|
+
e.stopPropagation();
|
|
10819
|
+
apply();
|
|
10820
|
+
timer = window.setTimeout(function tick() {
|
|
10821
|
+
apply();
|
|
10822
|
+
timer = window.setTimeout(tick, 60);
|
|
10823
|
+
}, 400);
|
|
10824
|
+
});
|
|
10825
|
+
b.addEventListener("pointerup", stop);
|
|
10826
|
+
b.addEventListener("pointerleave", stop);
|
|
10827
|
+
b.addEventListener("pointercancel", stop);
|
|
10828
|
+
return b;
|
|
10829
|
+
};
|
|
10830
|
+
steps.append(mk(1, "chevron-up", "Increase"), mk(-1, "chevron-down", "Decrease"));
|
|
10831
|
+
return steps;
|
|
10832
|
+
}
|
|
10833
|
+
};
|
|
10834
|
+
|
|
10835
|
+
// src/ui/components/text-field/controller.ts
|
|
10836
|
+
function textFieldController(opts = {}) {
|
|
10837
|
+
let value = opts.value ?? "";
|
|
10838
|
+
const size = opts.size ?? "md";
|
|
10839
|
+
return {
|
|
10840
|
+
get value() {
|
|
10841
|
+
return value;
|
|
10842
|
+
},
|
|
10843
|
+
size,
|
|
10844
|
+
fill: opts.fill ?? size !== "sm",
|
|
10845
|
+
disabled: opts.disabled ?? false,
|
|
10846
|
+
commit(next) {
|
|
10847
|
+
if (next === value) return null;
|
|
10848
|
+
value = next;
|
|
10849
|
+
opts.onChange?.(next);
|
|
10850
|
+
return next;
|
|
10851
|
+
},
|
|
10852
|
+
sync(next) {
|
|
10853
|
+
value = next;
|
|
10854
|
+
}
|
|
10855
|
+
};
|
|
10856
|
+
}
|
|
10857
|
+
|
|
10858
|
+
// src/ui/components/text-field/styles.ts
|
|
10859
|
+
var TEXT_STYLE_ID = "vela-ui-text";
|
|
10860
|
+
var TEXT_CSS = `
|
|
10861
|
+
.vela-text {
|
|
10862
|
+
display: inline-block;
|
|
10863
|
+
min-width: 0;
|
|
10864
|
+
}
|
|
10865
|
+
.vela-text[data-fill] { width: 100%; }
|
|
10866
|
+
.vela-text-field {
|
|
10867
|
+
width: 100%;
|
|
10868
|
+
box-sizing: border-box;
|
|
10869
|
+
height: 34px;
|
|
10870
|
+
background: transparent;
|
|
10871
|
+
border: 1px solid var(--vela-border-strong);
|
|
10872
|
+
border-radius: 6px;
|
|
10873
|
+
color: var(--vela-fg-bright);
|
|
10874
|
+
padding: 0 8px;
|
|
10875
|
+
font-size: 14px;
|
|
10876
|
+
font-family: inherit;
|
|
10877
|
+
outline: none;
|
|
10878
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
10879
|
+
}
|
|
10880
|
+
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
10881
|
+
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
10882
|
+
.vela-text[data-size='sm'] .vela-text-field {
|
|
10883
|
+
height: 28px;
|
|
10884
|
+
background: var(--vela-surface-elev);
|
|
10885
|
+
border-radius: var(--vela-radius-sm);
|
|
10886
|
+
color: var(--vela-fg);
|
|
10887
|
+
font-size: 13px;
|
|
10888
|
+
}
|
|
10889
|
+
`;
|
|
10890
|
+
|
|
10891
|
+
// src/ui/components/text-field/view.ts
|
|
10892
|
+
var TextField = class {
|
|
10893
|
+
constructor(opts = {}) {
|
|
10894
|
+
const doc = document;
|
|
10895
|
+
injectStyles(TEXT_STYLE_ID, TEXT_CSS, doc);
|
|
10896
|
+
this.ctrl = textFieldController(opts);
|
|
10897
|
+
const wrap = doc.createElement("div");
|
|
10898
|
+
wrap.className = "vela-text";
|
|
10899
|
+
wrap.dataset.size = this.ctrl.size;
|
|
10900
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
10901
|
+
const ti = doc.createElement("input");
|
|
10902
|
+
ti.type = "text";
|
|
10903
|
+
if (opts.id) ti.id = opts.id;
|
|
10904
|
+
ti.value = this.ctrl.value;
|
|
10905
|
+
ti.className = "vela-text-field";
|
|
10906
|
+
if (this.ctrl.disabled) ti.disabled = true;
|
|
10907
|
+
ti.addEventListener("blur", () => {
|
|
10908
|
+
this.ctrl.commit(ti.value);
|
|
10909
|
+
});
|
|
10910
|
+
ti.addEventListener("keydown", (e) => {
|
|
10911
|
+
if (e.key === "Enter") {
|
|
10912
|
+
e.preventDefault();
|
|
10913
|
+
ti.blur();
|
|
10914
|
+
}
|
|
10915
|
+
});
|
|
10916
|
+
wrap.appendChild(ti);
|
|
10917
|
+
this.el = wrap;
|
|
10918
|
+
this.input = ti;
|
|
10919
|
+
}
|
|
10920
|
+
get value() {
|
|
10921
|
+
return this.ctrl.value;
|
|
10922
|
+
}
|
|
10923
|
+
setValue(v) {
|
|
10924
|
+
this.ctrl.sync(v);
|
|
10925
|
+
this.input.value = v;
|
|
10926
|
+
}
|
|
10927
|
+
};
|
|
10928
|
+
|
|
10929
|
+
// src/ui/components/color-picker/controller.ts
|
|
10930
|
+
function splitColor(color) {
|
|
10931
|
+
const s = String(color ?? "").trim();
|
|
10932
|
+
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
10933
|
+
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
10934
|
+
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
10935
|
+
if (m) {
|
|
10936
|
+
const h = m[1].toLowerCase();
|
|
10937
|
+
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
10938
|
+
}
|
|
10939
|
+
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
10940
|
+
if (rgba) {
|
|
10941
|
+
const p = rgba[1].split(",").map((v) => v.trim());
|
|
10942
|
+
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
10943
|
+
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
10944
|
+
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
10945
|
+
}
|
|
10946
|
+
return { hex6: ACCENT_BRIGHT, alpha: 1 };
|
|
10947
|
+
}
|
|
10948
|
+
function combineColor(hex6, alpha) {
|
|
10949
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
10950
|
+
if (a >= 0.999) return hex6;
|
|
10951
|
+
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
10952
|
+
}
|
|
10953
|
+
function blendOver(fg, bg, alpha) {
|
|
10954
|
+
const rgb = (c) => {
|
|
10955
|
+
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
10956
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
10957
|
+
};
|
|
10958
|
+
const [fr, fgc, fb] = rgb(fg);
|
|
10959
|
+
const [br, bgc, bb] = rgb(bg);
|
|
10960
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
10961
|
+
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
10962
|
+
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
10963
|
+
}
|
|
10964
|
+
function hslHex(h, s, l) {
|
|
10965
|
+
const sn = s / 100;
|
|
10966
|
+
const ln = l / 100;
|
|
10967
|
+
const k = (n) => (n + h / 30) % 12;
|
|
10968
|
+
const a = sn * Math.min(ln, 1 - ln);
|
|
10969
|
+
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
10970
|
+
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
10971
|
+
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
10972
|
+
}
|
|
10973
|
+
function buildPalette() {
|
|
10974
|
+
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
10975
|
+
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
10976
|
+
const levels = [
|
|
10977
|
+
[82, 56],
|
|
10978
|
+
[58, 84],
|
|
10979
|
+
[62, 74],
|
|
10980
|
+
[66, 62],
|
|
10981
|
+
[70, 50],
|
|
10982
|
+
[64, 39],
|
|
10983
|
+
[54, 29]
|
|
10984
|
+
];
|
|
10985
|
+
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
10986
|
+
}
|
|
10987
|
+
function transparencyChecker(size) {
|
|
10988
|
+
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
10989
|
+
}
|
|
10990
|
+
|
|
10991
|
+
// src/ui/components/color-picker/styles.ts
|
|
10992
|
+
var COLOR_STYLE_ID = "vela-ui-color";
|
|
10993
|
+
var COLOR_CSS = `
|
|
10994
|
+
.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;}
|
|
10995
|
+
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
10996
|
+
.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);}
|
|
10997
|
+
.vela-color-field-circle{width:26px;height:26px;padding:3px;border:1px solid var(--vela-border-strong);border-radius:4px;background:transparent;overflow:hidden;}
|
|
10998
|
+
.vela-color-field-circle .vela-color-field-swatch{border-radius:2px;box-shadow:none;}
|
|
10999
|
+
.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;}
|
|
11000
|
+
`;
|
|
11001
|
+
|
|
11002
|
+
// src/ui/components/color-picker/view.ts
|
|
11003
|
+
var PALETTE = buildPalette();
|
|
11004
|
+
var CHECKER = transparencyChecker(10);
|
|
11005
|
+
var FIELD_CHECKER = transparencyChecker(8);
|
|
11006
|
+
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
11007
|
+
function addRecent(hex6) {
|
|
11008
|
+
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
11009
|
+
if (i >= 0) recents.splice(i, 1);
|
|
11010
|
+
recents.unshift(hex6);
|
|
11011
|
+
if (recents.length > 10) recents.length = 10;
|
|
11012
|
+
}
|
|
11013
|
+
function buildColorPicker(color, theme, onChange) {
|
|
11014
|
+
const parsed = splitColor(color);
|
|
11015
|
+
let curHex = parsed.hex6;
|
|
11016
|
+
let curAlpha = parsed.alpha;
|
|
11017
|
+
const root = document.createElement("div");
|
|
11018
|
+
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
11019
|
+
const grid = document.createElement("div");
|
|
11020
|
+
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
11021
|
+
const swatches = [];
|
|
11022
|
+
for (const row of PALETTE) {
|
|
11023
|
+
for (const c of row) {
|
|
11024
|
+
const sw = document.createElement("button");
|
|
11025
|
+
sw.type = "button";
|
|
11026
|
+
sw.dataset.c = c;
|
|
11027
|
+
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
11028
|
+
sw.addEventListener("click", (e) => {
|
|
11029
|
+
e.stopPropagation();
|
|
11030
|
+
pickHex(c);
|
|
11031
|
+
});
|
|
11032
|
+
grid.appendChild(sw);
|
|
11033
|
+
swatches.push(sw);
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
11036
|
+
const paintSelection = () => {
|
|
11037
|
+
for (const sw of swatches) {
|
|
11038
|
+
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
11039
|
+
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
11040
|
+
sw.style.outlineOffset = "2px";
|
|
11041
|
+
sw.style.zIndex = on ? "1" : "";
|
|
11042
|
+
sw.style.position = on ? "relative" : "";
|
|
11043
|
+
}
|
|
11044
|
+
};
|
|
11045
|
+
const recentRow = document.createElement("div");
|
|
11046
|
+
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
11047
|
+
const renderRecents = () => {
|
|
11048
|
+
recentRow.replaceChildren();
|
|
11049
|
+
for (const c of recents) {
|
|
11050
|
+
const sw = document.createElement("button");
|
|
11051
|
+
sw.type = "button";
|
|
11052
|
+
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;`;
|
|
11053
|
+
sw.addEventListener("click", (e) => {
|
|
11054
|
+
e.stopPropagation();
|
|
11055
|
+
pickHex(c);
|
|
11056
|
+
});
|
|
11057
|
+
recentRow.appendChild(sw);
|
|
11058
|
+
}
|
|
11059
|
+
const add = document.createElement("label");
|
|
11060
|
+
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;`;
|
|
11061
|
+
add.textContent = "+";
|
|
11062
|
+
const input = document.createElement("input");
|
|
11063
|
+
input.type = "color";
|
|
11064
|
+
input.value = curHex;
|
|
11065
|
+
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
11066
|
+
input.addEventListener("input", () => pickHex(input.value, true));
|
|
11067
|
+
add.appendChild(input);
|
|
11068
|
+
recentRow.appendChild(add);
|
|
11069
|
+
};
|
|
11070
|
+
const opLabel = document.createElement("div");
|
|
11071
|
+
opLabel.textContent = "Opacity";
|
|
11072
|
+
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
11073
|
+
const opRow = document.createElement("div");
|
|
11074
|
+
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
11075
|
+
const track = document.createElement("div");
|
|
11076
|
+
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
11077
|
+
const knob = document.createElement("div");
|
|
11078
|
+
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;";
|
|
11079
|
+
track.appendChild(knob);
|
|
11080
|
+
const pctBox = document.createElement("div");
|
|
11081
|
+
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;`;
|
|
11082
|
+
opRow.append(track, pctBox);
|
|
11083
|
+
const paintOpacity = () => {
|
|
11084
|
+
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
11085
|
+
knob.style.left = `${curAlpha * 100}%`;
|
|
11086
|
+
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
11087
|
+
};
|
|
11088
|
+
let dragging = false;
|
|
11089
|
+
const onDrag = (clientX) => {
|
|
11090
|
+
const r = track.getBoundingClientRect();
|
|
11091
|
+
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
11092
|
+
paintOpacity();
|
|
11093
|
+
emit();
|
|
11094
|
+
};
|
|
11095
|
+
track.addEventListener("pointerdown", (e) => {
|
|
11096
|
+
e.stopPropagation();
|
|
11097
|
+
dragging = true;
|
|
11098
|
+
track.setPointerCapture(e.pointerId);
|
|
11099
|
+
onDrag(e.clientX);
|
|
11100
|
+
});
|
|
11101
|
+
track.addEventListener("pointermove", (e) => {
|
|
11102
|
+
if (dragging) onDrag(e.clientX);
|
|
11103
|
+
});
|
|
11104
|
+
track.addEventListener("pointerup", () => dragging = false);
|
|
11105
|
+
function emit() {
|
|
11106
|
+
onChange(combineColor(curHex, curAlpha));
|
|
11107
|
+
}
|
|
11108
|
+
function pickHex(hex, custom = false) {
|
|
11109
|
+
curHex = splitColor(hex).hex6;
|
|
11110
|
+
addRecent(curHex);
|
|
11111
|
+
paintSelection();
|
|
11112
|
+
paintOpacity();
|
|
11113
|
+
if (custom) renderRecents();
|
|
11114
|
+
emit();
|
|
11115
|
+
}
|
|
11116
|
+
renderRecents();
|
|
11117
|
+
paintSelection();
|
|
11118
|
+
paintOpacity();
|
|
11119
|
+
root.append(grid, recentRow, opLabel, opRow);
|
|
11120
|
+
return root;
|
|
11121
|
+
}
|
|
11122
|
+
function colorField(theme, getVal, onVal, opts) {
|
|
11123
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
11124
|
+
}
|
|
11125
|
+
var ColorField = class {
|
|
11126
|
+
constructor(opts) {
|
|
11127
|
+
injectStyles(COLOR_STYLE_ID, COLOR_CSS, document);
|
|
11128
|
+
this.theme = opts.theme;
|
|
11129
|
+
this.getVal = opts.getVal;
|
|
11130
|
+
this.onVal = opts.onVal;
|
|
11131
|
+
this.popoverOpts = opts.popover;
|
|
11132
|
+
const trigger = document.createElement("button");
|
|
11133
|
+
trigger.type = "button";
|
|
11134
|
+
if (opts.id) trigger.id = opts.id;
|
|
11135
|
+
trigger.className = opts.shape === "circle" ? "vela-color-field vela-color-field-circle" : "vela-color-field";
|
|
11136
|
+
const swatch = document.createElement("span");
|
|
11137
|
+
swatch.className = "vela-color-field-swatch";
|
|
11138
|
+
trigger.appendChild(swatch);
|
|
11139
|
+
this.el = trigger;
|
|
11140
|
+
this.swatch = swatch;
|
|
11141
|
+
this.paint();
|
|
11142
|
+
trigger.addEventListener("vela-sync", () => this.paint());
|
|
11143
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
11144
|
+
e.preventDefault();
|
|
11145
|
+
trigger.focus({ preventScroll: true });
|
|
11146
|
+
});
|
|
11147
|
+
trigger.addEventListener("click", (e) => {
|
|
11148
|
+
e.stopPropagation();
|
|
11149
|
+
this.toggle();
|
|
11150
|
+
});
|
|
11151
|
+
}
|
|
11152
|
+
paint() {
|
|
11153
|
+
const v = this.getVal();
|
|
11154
|
+
this.swatch.style.background = `linear-gradient(${v}, ${v}), ${FIELD_CHECKER}`;
|
|
11155
|
+
}
|
|
11156
|
+
toggle() {
|
|
11157
|
+
if (openPopoverTrigger() === this.el) {
|
|
11158
|
+
closeOpenPopovers();
|
|
11159
|
+
return;
|
|
11160
|
+
}
|
|
11161
|
+
const pop = new Popover({
|
|
11162
|
+
trigger: this.el,
|
|
11163
|
+
theme: this.theme,
|
|
11164
|
+
align: this.popoverOpts?.align ?? "end",
|
|
11165
|
+
gap: this.popoverOpts?.gap ?? 6,
|
|
11166
|
+
host: this.popoverOpts?.host,
|
|
11167
|
+
position: this.popoverOpts?.position,
|
|
11168
|
+
boundary: this.popoverOpts?.boundary,
|
|
11169
|
+
zIndex: this.popoverOpts?.zIndex,
|
|
11170
|
+
className: "vela-color-field-pop",
|
|
11171
|
+
content: buildColorPicker(this.getVal(), this.theme, (val) => {
|
|
11172
|
+
this.onVal(val);
|
|
11173
|
+
this.paint();
|
|
11174
|
+
})
|
|
11175
|
+
});
|
|
11176
|
+
pop.show();
|
|
11177
|
+
}
|
|
11178
|
+
};
|
|
11179
|
+
|
|
9884
11180
|
// src/renderers/shared/InputsUI.ts
|
|
9885
11181
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
9886
11182
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
@@ -9889,6 +11185,7 @@ var LEGEND_CTL_PX = 18;
|
|
|
9889
11185
|
var LEGEND_ROW_PAD_Y = 2;
|
|
9890
11186
|
var LEGEND_ROW_PAD_X = 6;
|
|
9891
11187
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
11188
|
+
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
9892
11189
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
9893
11190
|
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;`;
|
|
9894
11191
|
var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
|
|
@@ -9933,6 +11230,11 @@ var InputsUI = class {
|
|
|
9933
11230
|
this.dialogTips = [];
|
|
9934
11231
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
9935
11232
|
this.moveMenu = null;
|
|
11233
|
+
/** Open settings-dialog choice menu (custom dropdown — torn down with the dialog). */
|
|
11234
|
+
this.choicePop = null;
|
|
11235
|
+
/** Open settings-dialog calendar popover (torn down with the dialog). */
|
|
11236
|
+
this.calendarPop = null;
|
|
11237
|
+
this.calendarAnchor = null;
|
|
9936
11238
|
/** Collapsed panes → the master indicator id to keep visible (others hidden in the strip). */
|
|
9937
11239
|
this.paneCollapse = /* @__PURE__ */ new Map();
|
|
9938
11240
|
/** The user folded the indicator legend away behind the chevron — CHART-WIDE: every
|
|
@@ -9958,6 +11260,10 @@ var InputsUI = class {
|
|
|
9958
11260
|
this.onDialogKey = (e) => {
|
|
9959
11261
|
if (e.key === "Escape") {
|
|
9960
11262
|
e.preventDefault();
|
|
11263
|
+
if (isPopoverOpen()) {
|
|
11264
|
+
closeOpenPopovers();
|
|
11265
|
+
return;
|
|
11266
|
+
}
|
|
9961
11267
|
this.closeDialog();
|
|
9962
11268
|
}
|
|
9963
11269
|
};
|
|
@@ -10403,6 +11709,7 @@ var InputsUI = class {
|
|
|
10403
11709
|
titleWrap.appendChild(beta);
|
|
10404
11710
|
}
|
|
10405
11711
|
el.appendChild(titleWrap);
|
|
11712
|
+
el.appendChild(statusEl);
|
|
10406
11713
|
const valuesEl = document.createElement("span");
|
|
10407
11714
|
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;`;
|
|
10408
11715
|
el.appendChild(valuesEl);
|
|
@@ -10463,7 +11770,6 @@ var InputsUI = class {
|
|
|
10463
11770
|
close.addEventListener("click", () => this.onRemove?.(id));
|
|
10464
11771
|
controlsEl.appendChild(close);
|
|
10465
11772
|
el.appendChild(controlsEl);
|
|
10466
|
-
el.appendChild(statusEl);
|
|
10467
11773
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
10468
11774
|
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 });
|
|
10469
11775
|
this.syncFoldToggle();
|
|
@@ -10481,7 +11787,7 @@ var InputsUI = class {
|
|
|
10481
11787
|
/**
|
|
10482
11788
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
10483
11789
|
* dots (a fetch is in flight — the same load affordance as the chart's own bar-load dots,
|
|
10484
|
-
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered
|
|
11790
|
+
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
10485
11791
|
*/
|
|
10486
11792
|
setStatus(id, status) {
|
|
10487
11793
|
const row = this.rows.get(id);
|
|
@@ -10493,7 +11799,7 @@ var InputsUI = class {
|
|
|
10493
11799
|
return;
|
|
10494
11800
|
}
|
|
10495
11801
|
if (status === "loading") {
|
|
10496
|
-
el.style.cssText =
|
|
11802
|
+
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);`;
|
|
10497
11803
|
for (let i = 0; i < 3; i += 1) {
|
|
10498
11804
|
const dot = document.createElement("span");
|
|
10499
11805
|
dot.style.cssText = "width:4px;height:4px;border-radius:50%;background:currentColor;opacity:0.15;flex:none;";
|
|
@@ -10503,7 +11809,7 @@ var InputsUI = class {
|
|
|
10503
11809
|
return;
|
|
10504
11810
|
}
|
|
10505
11811
|
this.ensureStatusKeyframes();
|
|
10506
|
-
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;`;
|
|
11812
|
+
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;`;
|
|
10507
11813
|
}
|
|
10508
11814
|
/** Inject the status keyframes once (idempotent). */
|
|
10509
11815
|
ensureStatusKeyframes() {
|
|
@@ -10556,15 +11862,15 @@ var InputsUI = class {
|
|
|
10556
11862
|
* construction and is shown/hidden with the cluster container.
|
|
10557
11863
|
*/
|
|
10558
11864
|
syncRowActions(row) {
|
|
10559
|
-
const
|
|
10560
|
-
row.controlsEl.style.display =
|
|
11865
|
+
const open2 = row.highlighted;
|
|
11866
|
+
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
10561
11867
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
10562
11868
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
10563
11869
|
if (child === row.extrasEl) {
|
|
10564
|
-
child.style.display =
|
|
11870
|
+
child.style.display = open2 ? "contents" : "none";
|
|
10565
11871
|
continue;
|
|
10566
11872
|
}
|
|
10567
|
-
child.style.display =
|
|
11873
|
+
child.style.display = open2 ? "inline-flex" : "none";
|
|
10568
11874
|
}
|
|
10569
11875
|
}
|
|
10570
11876
|
/** Drop the current selection outline, if any. */
|
|
@@ -10618,7 +11924,8 @@ var InputsUI = class {
|
|
|
10618
11924
|
/**
|
|
10619
11925
|
* Open the indicator's settings as a centered modal over the chart — a themed,
|
|
10620
11926
|
* dependency-free port of the app's tabbed script-settings dialog: a header with the
|
|
10621
|
-
* indicator title,
|
|
11927
|
+
* indicator title, a tab strip (the default "Inputs" tab plus any tab the inputs
|
|
11928
|
+
* declare via `tab=`), a scrollable body per tab that groups inputs by `group=`
|
|
10622
11929
|
* (section headers) and packs `inline=` inputs onto one row, and a sticky footer.
|
|
10623
11930
|
*
|
|
10624
11931
|
* Edits commit LIVE (each change re-runs the indicator), matching the current
|
|
@@ -10648,55 +11955,57 @@ var InputsUI = class {
|
|
|
10648
11955
|
});
|
|
10649
11956
|
const card = document.createElement("div");
|
|
10650
11957
|
card.className = "vela-ind-dialog";
|
|
10651
|
-
card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(
|
|
11958
|
+
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;`;
|
|
10652
11959
|
if (this.mobileLayout) card.style.cssText += "width:100%;min-width:0;max-width:none;height:100%;max-height:none;border:none;border-radius:0;";
|
|
10653
11960
|
applyChromeTokens(card, t);
|
|
10654
11961
|
card.addEventListener("keydown", (e) => {
|
|
10655
11962
|
if (e.key !== "Escape") e.stopPropagation();
|
|
10656
11963
|
});
|
|
10657
11964
|
const header = document.createElement("div");
|
|
10658
|
-
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px
|
|
11965
|
+
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 20px;flex:0 0 auto;";
|
|
10659
11966
|
const hTitle = document.createElement("span");
|
|
10660
11967
|
hTitle.textContent = row.settingsTitle;
|
|
10661
|
-
hTitle.style.cssText = "font-weight:600;font-size:
|
|
11968
|
+
hTitle.style.cssText = "font-weight:600;font-size:20px;line-height:28px;";
|
|
10662
11969
|
const closeBtn = document.createElement("button");
|
|
10663
11970
|
closeBtn.type = "button";
|
|
10664
11971
|
closeBtn.setAttribute("aria-label", "Close");
|
|
10665
11972
|
this.dialogTips.push(attachChromeTooltip(closeBtn, { host: this.container, theme: () => this.theme, text: () => "Close" }));
|
|
10666
11973
|
closeBtn.innerHTML = iconAt("close", 15);
|
|
10667
|
-
closeBtn.className = "vela-ind-
|
|
10668
|
-
closeBtn.style.cssText = "cursor:pointer;display:inline-flex;align-items:center;background:transparent;border:none;line-height:0;padding:2px;flex:0 0 auto;";
|
|
11974
|
+
closeBtn.className = "vela-ind-dlg-close";
|
|
10669
11975
|
closeBtn.addEventListener("click", () => this.closeDialog());
|
|
10670
11976
|
header.append(hTitle, closeBtn);
|
|
10671
11977
|
card.appendChild(header);
|
|
10672
11978
|
if (!this.mobileLayout) makeDialogDraggable(card, header, { closeSelector: "button" });
|
|
11979
|
+
const tabDefs = tabInputs(row.inputs);
|
|
10673
11980
|
const tabs = document.createElement("div");
|
|
10674
|
-
tabs.style.cssText = `display:flex;gap:
|
|
10675
|
-
const
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
if (group.name) {
|
|
10686
|
-
const gh = document.createElement("div");
|
|
10687
|
-
gh.textContent = group.name;
|
|
10688
|
-
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);";
|
|
10689
|
-
section.appendChild(gh);
|
|
11981
|
+
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
11982
|
+
const tabEls = [];
|
|
11983
|
+
const bodies = [];
|
|
11984
|
+
const activate = (idx) => {
|
|
11985
|
+
for (let i = 0; i < tabEls.length; i += 1) {
|
|
11986
|
+
const active = i === idx;
|
|
11987
|
+
const el = tabEls[i];
|
|
11988
|
+
el.style.borderBottomColor = active ? fg : "transparent";
|
|
11989
|
+
el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
|
|
11990
|
+
el.classList.toggle("vela-ind-tab-active", active);
|
|
11991
|
+
bodies[i].style.display = active ? "grid" : "none";
|
|
10690
11992
|
}
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
11993
|
+
};
|
|
11994
|
+
for (const [idx, def] of tabDefs.entries()) {
|
|
11995
|
+
const tab = document.createElement("div");
|
|
11996
|
+
tab.textContent = def.name;
|
|
11997
|
+
tab.className = "vela-ind-tab";
|
|
11998
|
+
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;" : ""}`;
|
|
11999
|
+
tab.addEventListener("click", () => activate(idx));
|
|
12000
|
+
tabs.appendChild(tab);
|
|
12001
|
+
tabEls.push(tab);
|
|
12002
|
+
bodies.push(this.buildTabBody(def.inputs, row));
|
|
10696
12003
|
}
|
|
10697
|
-
card.appendChild(
|
|
12004
|
+
card.appendChild(tabs);
|
|
12005
|
+
for (const body of bodies) card.appendChild(body);
|
|
12006
|
+
activate(0);
|
|
10698
12007
|
const footer = document.createElement("div");
|
|
10699
|
-
footer.style.cssText = `display:flex;justify-content:flex-end;gap:
|
|
12008
|
+
footer.style.cssText = `display:flex;justify-content:flex-end;gap:12px;padding:16px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
|
|
10700
12009
|
footer.append(
|
|
10701
12010
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
10702
12011
|
this.dialogButton("Ok", true, () => this.closeDialog())
|
|
@@ -10708,8 +12017,32 @@ var InputsUI = class {
|
|
|
10708
12017
|
this.backdrop = backdrop;
|
|
10709
12018
|
document.addEventListener("keydown", this.onDialogKey);
|
|
10710
12019
|
}
|
|
12020
|
+
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
12021
|
+
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
12022
|
+
buildTabBody(inputs, row) {
|
|
12023
|
+
const body = document.createElement("div");
|
|
12024
|
+
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;";
|
|
12025
|
+
for (const group of groupInputs(inputs)) {
|
|
12026
|
+
const section = document.createElement("div");
|
|
12027
|
+
section.style.cssText = "display:contents;";
|
|
12028
|
+
if (group.name) {
|
|
12029
|
+
const gh = document.createElement("div");
|
|
12030
|
+
gh.textContent = group.name;
|
|
12031
|
+
const first = body.childElementCount === 0;
|
|
12032
|
+
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;`;
|
|
12033
|
+
section.appendChild(gh);
|
|
12034
|
+
}
|
|
12035
|
+
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
12036
|
+
body.appendChild(section);
|
|
12037
|
+
}
|
|
12038
|
+
return body;
|
|
12039
|
+
}
|
|
10711
12040
|
closeDialog() {
|
|
10712
12041
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
12042
|
+
closeOpenPopovers();
|
|
12043
|
+
this.choicePop = null;
|
|
12044
|
+
this.calendarPop = null;
|
|
12045
|
+
this.calendarAnchor = null;
|
|
10713
12046
|
for (const dispose of this.dialogTips.splice(0)) dispose();
|
|
10714
12047
|
this.backdrop?.remove();
|
|
10715
12048
|
this.backdrop = null;
|
|
@@ -10737,79 +12070,77 @@ var InputsUI = class {
|
|
|
10737
12070
|
buildInputRow(grid, decls, row) {
|
|
10738
12071
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
10739
12072
|
if (decls.length > 1) {
|
|
10740
|
-
const
|
|
10741
|
-
|
|
12073
|
+
const cell2 = document.createElement("div");
|
|
12074
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
|
|
10742
12075
|
for (const d of decls) {
|
|
10743
12076
|
const fit2 = d.type === "color" || d.type === "bool";
|
|
10744
12077
|
const toggleFirst = d.type === "bool";
|
|
10745
12078
|
const id2 = idOf(d);
|
|
10746
12079
|
const item = document.createElement("div");
|
|
10747
|
-
item.style.cssText = `display:flex;align-items:center;gap:
|
|
12080
|
+
item.style.cssText = `display:flex;align-items:center;gap:8px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
|
|
10748
12081
|
const name = nameOf(d);
|
|
10749
12082
|
if (name) {
|
|
10750
12083
|
const lbl = document.createElement("label");
|
|
10751
12084
|
lbl.htmlFor = id2;
|
|
10752
12085
|
lbl.textContent = name;
|
|
10753
|
-
lbl.style.cssText = `font-size:
|
|
12086
|
+
lbl.style.cssText = `font-size:14px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
|
|
10754
12087
|
item.appendChild(lbl);
|
|
10755
12088
|
}
|
|
10756
12089
|
const cw2 = document.createElement("div");
|
|
10757
|
-
cw2.style.cssText = fit2 ? "" : "width:
|
|
12090
|
+
cw2.style.cssText = fit2 ? "" : "width:100px;";
|
|
10758
12091
|
cw2.appendChild(this.buildControl(row, d, id2));
|
|
10759
12092
|
item.appendChild(cw2);
|
|
10760
|
-
|
|
12093
|
+
cell2.appendChild(item);
|
|
10761
12094
|
}
|
|
10762
12095
|
const lastTip = [...decls].reverse().find((d) => d.tooltip)?.tooltip;
|
|
10763
|
-
if (lastTip)
|
|
10764
|
-
grid.appendChild(
|
|
12096
|
+
if (lastTip) cell2.appendChild(this.infoButton(lastTip));
|
|
12097
|
+
grid.appendChild(cell2);
|
|
10765
12098
|
return;
|
|
10766
12099
|
}
|
|
10767
12100
|
const lead = decls[0];
|
|
10768
12101
|
const id = idOf(lead);
|
|
10769
12102
|
if (lead.type === "bool") {
|
|
10770
|
-
const
|
|
10771
|
-
|
|
12103
|
+
const cell2 = document.createElement("div");
|
|
12104
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;align-items:center;gap:8px;";
|
|
10772
12105
|
const lbl = document.createElement("label");
|
|
10773
|
-
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:
|
|
12106
|
+
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;";
|
|
10774
12107
|
lbl.appendChild(this.buildControl(row, lead, id));
|
|
10775
12108
|
lbl.appendChild(document.createTextNode(nameOf(lead)));
|
|
10776
|
-
|
|
10777
|
-
if (lead.tooltip)
|
|
10778
|
-
grid.appendChild(
|
|
10779
|
-
grid.appendChild(document.createElement("div"));
|
|
12109
|
+
cell2.appendChild(lbl);
|
|
12110
|
+
if (lead.tooltip) cell2.appendChild(this.infoButton(lead.tooltip));
|
|
12111
|
+
grid.appendChild(cell2);
|
|
10780
12112
|
return;
|
|
10781
12113
|
}
|
|
10782
12114
|
if (lead.type === "text_area") {
|
|
10783
|
-
const
|
|
10784
|
-
|
|
12115
|
+
const cell2 = document.createElement("div");
|
|
12116
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
|
|
10785
12117
|
const head = document.createElement("div");
|
|
10786
12118
|
head.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;";
|
|
10787
12119
|
const lbl = document.createElement("label");
|
|
10788
12120
|
lbl.htmlFor = id;
|
|
10789
12121
|
lbl.textContent = nameOf(lead);
|
|
10790
|
-
lbl.style.cssText = "font-size:
|
|
12122
|
+
lbl.style.cssText = "font-size:14px;";
|
|
10791
12123
|
head.appendChild(lbl);
|
|
10792
12124
|
if (lead.tooltip) head.appendChild(this.infoButton(lead.tooltip));
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
grid.appendChild(
|
|
12125
|
+
cell2.appendChild(head);
|
|
12126
|
+
cell2.appendChild(this.buildControl(row, lead, id));
|
|
12127
|
+
grid.appendChild(cell2);
|
|
10796
12128
|
return;
|
|
10797
12129
|
}
|
|
10798
12130
|
const label = document.createElement("label");
|
|
10799
12131
|
label.htmlFor = id;
|
|
10800
12132
|
label.textContent = nameOf(lead);
|
|
10801
|
-
label.style.cssText = "font-size:
|
|
10802
|
-
const controlCell = document.createElement("div");
|
|
10803
|
-
controlCell.style.cssText = "grid-column:span 2;display:flex;align-items:center;gap:8px;";
|
|
12133
|
+
label.style.cssText = "font-size:14px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
10804
12134
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
12135
|
+
const cell = document.createElement("div");
|
|
12136
|
+
cell.style.cssText = "display:flex;align-items:center;gap:8px;justify-self:start;";
|
|
10805
12137
|
const cw = document.createElement("div");
|
|
10806
|
-
cw.style.cssText = fit ? "" : "width:
|
|
12138
|
+
cw.style.cssText = fit ? "" : "width:100px;";
|
|
10807
12139
|
cw.appendChild(this.buildControl(row, lead, id));
|
|
10808
|
-
|
|
10809
|
-
if (lead.tooltip)
|
|
12140
|
+
cell.appendChild(cw);
|
|
12141
|
+
if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
|
|
10810
12142
|
grid.appendChild(label);
|
|
10811
|
-
grid.appendChild(
|
|
10812
|
-
grid.appendChild(document.createElement("div"));
|
|
12143
|
+
grid.appendChild(cell);
|
|
10813
12144
|
}
|
|
10814
12145
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
10815
12146
|
buildControl(row, inp, id) {
|
|
@@ -10818,107 +12149,30 @@ var InputsUI = class {
|
|
|
10818
12149
|
row.values[inp.key] = value;
|
|
10819
12150
|
this.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
10820
12151
|
};
|
|
10821
|
-
const ctrl = this.ctrlStyle();
|
|
10822
12152
|
if (inp.type === "bool") return this.buildToggle(id, Boolean(current), emit);
|
|
10823
|
-
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current),
|
|
10824
|
-
if (inp.type === "source") return this.select(id, SOURCES, String(current),
|
|
12153
|
+
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), emit);
|
|
12154
|
+
if (inp.type === "source") return this.select(id, SOURCES, String(current), emit);
|
|
10825
12155
|
if (inp.type === "color") {
|
|
10826
|
-
const
|
|
10827
|
-
|
|
10828
|
-
ci.id = id;
|
|
10829
|
-
ci.value = toHex6(String(current));
|
|
10830
|
-
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;`;
|
|
10831
|
-
ci.addEventListener("input", () => emit(ci.value));
|
|
10832
|
-
return ci;
|
|
12156
|
+
const field = colorField(this.theme, () => String(row.values[inp.key] ?? inp.defval), (v) => emit(v), { shape: "circle", id });
|
|
12157
|
+
return field;
|
|
10833
12158
|
}
|
|
10834
12159
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
10835
|
-
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current),
|
|
12160
|
+
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), emit);
|
|
10836
12161
|
if (inp.type === "session") return this.buildSession(id, String(current), emit);
|
|
10837
12162
|
if (inp.type === "time") return this.buildTime(id, Number(current) || 0, emit);
|
|
10838
12163
|
if (inp.type === "text_area") return this.buildTextArea(id, String(current), emit);
|
|
10839
12164
|
if (inp.type === "int" || inp.type === "float" || inp.type === "price") {
|
|
10840
|
-
|
|
10841
|
-
ni.type = "number";
|
|
10842
|
-
ni.id = id;
|
|
10843
|
-
ni.value = String(current);
|
|
10844
|
-
if (inp.min !== void 0) ni.min = String(inp.min);
|
|
10845
|
-
if (inp.max !== void 0) ni.max = String(inp.max);
|
|
10846
|
-
ni.step = String(inp.step ?? (inp.type === "int" ? 1 : 0.1));
|
|
10847
|
-
ni.style.cssText = ctrl;
|
|
10848
|
-
let last = String(current);
|
|
10849
|
-
const commit = () => {
|
|
10850
|
-
let n = Number(ni.value);
|
|
10851
|
-
if (!Number.isFinite(n)) {
|
|
10852
|
-
ni.value = last;
|
|
10853
|
-
return;
|
|
10854
|
-
}
|
|
10855
|
-
if (inp.min !== void 0) n = Math.max(inp.min, n);
|
|
10856
|
-
if (inp.max !== void 0) n = Math.min(inp.max, n);
|
|
10857
|
-
if (inp.type === "int") n = Math.round(n);
|
|
10858
|
-
ni.value = String(n);
|
|
10859
|
-
if (String(n) !== last) {
|
|
10860
|
-
last = String(n);
|
|
10861
|
-
emit(n);
|
|
10862
|
-
}
|
|
10863
|
-
};
|
|
10864
|
-
ni.addEventListener("blur", commit);
|
|
10865
|
-
ni.addEventListener("keydown", (e) => {
|
|
10866
|
-
if (e.key === "Enter") {
|
|
10867
|
-
e.preventDefault();
|
|
10868
|
-
ni.blur();
|
|
10869
|
-
}
|
|
10870
|
-
});
|
|
10871
|
-
return ni;
|
|
12165
|
+
return this.buildNumber(id, Number(current), inp, emit);
|
|
10872
12166
|
}
|
|
10873
12167
|
return this.buildTextField(id, String(current), emit);
|
|
10874
12168
|
}
|
|
10875
12169
|
/** A plain text field that commits on blur / Enter (shared by string inputs and the pickerless symbol field). */
|
|
10876
12170
|
buildTextField(id, current, emit) {
|
|
10877
|
-
|
|
10878
|
-
ti.type = "text";
|
|
10879
|
-
ti.id = id;
|
|
10880
|
-
ti.value = current;
|
|
10881
|
-
ti.style.cssText = this.ctrlStyle();
|
|
10882
|
-
let last = current;
|
|
10883
|
-
ti.addEventListener("blur", () => {
|
|
10884
|
-
if (ti.value !== last) {
|
|
10885
|
-
last = ti.value;
|
|
10886
|
-
emit(ti.value);
|
|
10887
|
-
}
|
|
10888
|
-
});
|
|
10889
|
-
ti.addEventListener("keydown", (e) => {
|
|
10890
|
-
if (e.key === "Enter") {
|
|
10891
|
-
e.preventDefault();
|
|
10892
|
-
ti.blur();
|
|
10893
|
-
}
|
|
10894
|
-
});
|
|
10895
|
-
return ti;
|
|
12171
|
+
return new TextField({ id, value: current, size: "md", onChange: emit }).el;
|
|
10896
12172
|
}
|
|
10897
12173
|
/** A square check-toggle (filled + check when on) — replaces the raw checkbox. */
|
|
10898
12174
|
buildToggle(id, checked, onChange) {
|
|
10899
|
-
|
|
10900
|
-
const border = this.neutralBorder();
|
|
10901
|
-
const fill = this.strongText();
|
|
10902
|
-
const b = document.createElement("button");
|
|
10903
|
-
b.type = "button";
|
|
10904
|
-
b.id = id;
|
|
10905
|
-
b.setAttribute("role", "switch");
|
|
10906
|
-
b.innerHTML = CHECK_SVG;
|
|
10907
|
-
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;`;
|
|
10908
|
-
let on = checked;
|
|
10909
|
-
const paint = (v) => {
|
|
10910
|
-
b.setAttribute("aria-checked", v ? "true" : "false");
|
|
10911
|
-
b.style.background = v ? fill : "transparent";
|
|
10912
|
-
b.style.borderColor = v ? fill : border;
|
|
10913
|
-
b.style.color = v ? t.background : "transparent";
|
|
10914
|
-
};
|
|
10915
|
-
paint(checked);
|
|
10916
|
-
b.addEventListener("click", () => {
|
|
10917
|
-
on = !on;
|
|
10918
|
-
paint(on);
|
|
10919
|
-
onChange(on);
|
|
10920
|
-
});
|
|
10921
|
-
return b;
|
|
12175
|
+
return new Switch({ id, checked, size: "md", tone: "bright", onChange }).el;
|
|
10922
12176
|
}
|
|
10923
12177
|
/** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
|
|
10924
12178
|
infoButton(tooltip2) {
|
|
@@ -10930,45 +12184,22 @@ var InputsUI = class {
|
|
|
10930
12184
|
return b;
|
|
10931
12185
|
}
|
|
10932
12186
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
10933
|
-
select(id, options, current,
|
|
10934
|
-
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current,
|
|
12187
|
+
select(id, options, current, emit) {
|
|
12188
|
+
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, emit);
|
|
10935
12189
|
}
|
|
10936
12190
|
/** A dropdown whose visible labels differ from the committed values (label ≠ value pairs). */
|
|
10937
|
-
selectPairs(id, pairs, current,
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
sel.appendChild(o);
|
|
10948
|
-
}
|
|
10949
|
-
for (const p of pairs) {
|
|
10950
|
-
const o = document.createElement("option");
|
|
10951
|
-
o.value = p.value;
|
|
10952
|
-
o.textContent = p.label;
|
|
10953
|
-
if (p.value === current) o.selected = true;
|
|
10954
|
-
sel.appendChild(o);
|
|
10955
|
-
}
|
|
10956
|
-
this.styleSelect(sel);
|
|
10957
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
10958
|
-
return this.wrapSelectChevron(sel);
|
|
10959
|
-
}
|
|
10960
|
-
/** Wrap a `<select>` with an inset chevron — native arrows can't be repositioned reliably. */
|
|
10961
|
-
wrapSelectChevron(sel) {
|
|
10962
|
-
const wrap = document.createElement("div");
|
|
10963
|
-
wrap.style.cssText = "position:relative;width:100%;";
|
|
10964
|
-
sel.style.paddingRight = "28px";
|
|
10965
|
-
const chevron = document.createElement("span");
|
|
10966
|
-
chevron.innerHTML = SELECT_CHEVRON_SVG;
|
|
10967
|
-
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;`;
|
|
10968
|
-
wrap.append(sel, chevron);
|
|
10969
|
-
return wrap;
|
|
12191
|
+
selectPairs(id, pairs, current, onChange) {
|
|
12192
|
+
return new Select({
|
|
12193
|
+
id,
|
|
12194
|
+
options: pairs,
|
|
12195
|
+
value: current,
|
|
12196
|
+
size: "md",
|
|
12197
|
+
theme: this.theme,
|
|
12198
|
+
list: this.listPopover(),
|
|
12199
|
+
onChange
|
|
12200
|
+
}).el;
|
|
10970
12201
|
}
|
|
10971
|
-
/** `input.session` → two HH:MM
|
|
12202
|
+
/** `input.session` → two typeable HH:MM comboboxes committing a `HHMM-HHMM` session string. */
|
|
10972
12203
|
buildSession(id, current, emit) {
|
|
10973
12204
|
const [start, end] = sessionToTimes(current);
|
|
10974
12205
|
const wrap = document.createElement("div");
|
|
@@ -10976,15 +12207,16 @@ var InputsUI = class {
|
|
|
10976
12207
|
let from = start;
|
|
10977
12208
|
let to = end;
|
|
10978
12209
|
const commit = () => emit(timesToSession(from, to));
|
|
10979
|
-
|
|
10980
|
-
from
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
to
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
12210
|
+
wrap.append(
|
|
12211
|
+
this.timeCombobox(id, from, 86, (v) => {
|
|
12212
|
+
from = v;
|
|
12213
|
+
commit();
|
|
12214
|
+
}),
|
|
12215
|
+
this.timeCombobox(`${id}-end`, to, 86, (v) => {
|
|
12216
|
+
to = v;
|
|
12217
|
+
commit();
|
|
12218
|
+
})
|
|
12219
|
+
);
|
|
10988
12220
|
return wrap;
|
|
10989
12221
|
}
|
|
10990
12222
|
/** `input.time` → a date picker + an HH:MM dropdown, committing an epoch-ms timestamp. */
|
|
@@ -10999,20 +12231,15 @@ var InputsUI = class {
|
|
|
10999
12231
|
const ms = Date.parse(`${date}T${time}:00`);
|
|
11000
12232
|
if (Number.isFinite(ms)) emit(ms);
|
|
11001
12233
|
};
|
|
11002
|
-
const
|
|
11003
|
-
|
|
11004
|
-
dateInput.id = id;
|
|
11005
|
-
dateInput.value = date;
|
|
11006
|
-
dateInput.style.cssText = `${this.ctrlStyle()}width:auto;cursor:pointer;`;
|
|
11007
|
-
dateInput.addEventListener("change", () => {
|
|
11008
|
-
date = dateInput.value;
|
|
12234
|
+
const dateField = this.dateField(id, date, (v) => {
|
|
12235
|
+
date = v;
|
|
11009
12236
|
commit();
|
|
11010
12237
|
});
|
|
11011
|
-
const timeSel = this.
|
|
12238
|
+
const timeSel = this.timeCombobox(`${id}-time`, time, 86, (v) => {
|
|
11012
12239
|
time = v;
|
|
11013
12240
|
commit();
|
|
11014
12241
|
});
|
|
11015
|
-
wrap.append(
|
|
12242
|
+
wrap.append(dateField, timeSel);
|
|
11016
12243
|
return wrap;
|
|
11017
12244
|
}
|
|
11018
12245
|
/** `input.text_area` → a multi-line textarea, committed on blur. */
|
|
@@ -11031,9 +12258,261 @@ var InputsUI = class {
|
|
|
11031
12258
|
});
|
|
11032
12259
|
return ta;
|
|
11033
12260
|
}
|
|
11034
|
-
/** A fixed-width HH:MM
|
|
11035
|
-
|
|
11036
|
-
|
|
12261
|
+
/** A fixed-width HH:MM combobox (typeable, plus a 30-minute-step dropdown) used by session + time. */
|
|
12262
|
+
timeCombobox(id, current, width, onChange) {
|
|
12263
|
+
const wrap = document.createElement("div");
|
|
12264
|
+
wrap.className = "vela-ind-combo";
|
|
12265
|
+
wrap.style.cssText = `position:relative;width:${width}px;`;
|
|
12266
|
+
const input = document.createElement("input");
|
|
12267
|
+
input.type = "text";
|
|
12268
|
+
input.id = id;
|
|
12269
|
+
input.value = current;
|
|
12270
|
+
input.autocomplete = "off";
|
|
12271
|
+
input.spellcheck = false;
|
|
12272
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
12273
|
+
const chevron = document.createElement("button");
|
|
12274
|
+
chevron.type = "button";
|
|
12275
|
+
chevron.tabIndex = -1;
|
|
12276
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
12277
|
+
chevron.setAttribute("aria-label", "Open time list");
|
|
12278
|
+
chevron.innerHTML = CLOCK_SVG;
|
|
12279
|
+
let value = current;
|
|
12280
|
+
const commitTyped = () => {
|
|
12281
|
+
const next = normalizeTimeInput(input.value);
|
|
12282
|
+
if (!next) {
|
|
12283
|
+
input.value = value;
|
|
12284
|
+
return;
|
|
12285
|
+
}
|
|
12286
|
+
input.value = next;
|
|
12287
|
+
if (next !== value) {
|
|
12288
|
+
value = next;
|
|
12289
|
+
onChange(next);
|
|
12290
|
+
}
|
|
12291
|
+
};
|
|
12292
|
+
input.addEventListener("blur", () => {
|
|
12293
|
+
if (isPopoverOpen()) return;
|
|
12294
|
+
commitTyped();
|
|
12295
|
+
});
|
|
12296
|
+
const pick = (v) => {
|
|
12297
|
+
value = v;
|
|
12298
|
+
input.value = v;
|
|
12299
|
+
onChange(v);
|
|
12300
|
+
};
|
|
12301
|
+
const openList = () => {
|
|
12302
|
+
this.choicePop = toggleSelectList(wrap, TIME_OPTIONS, value, pick, {
|
|
12303
|
+
...this.listPopover(),
|
|
12304
|
+
onClose: () => {
|
|
12305
|
+
this.choicePop = null;
|
|
12306
|
+
}
|
|
12307
|
+
});
|
|
12308
|
+
};
|
|
12309
|
+
input.addEventListener("keydown", (e) => {
|
|
12310
|
+
if (e.key === "Enter") {
|
|
12311
|
+
e.preventDefault();
|
|
12312
|
+
commitTyped();
|
|
12313
|
+
input.blur();
|
|
12314
|
+
}
|
|
12315
|
+
if (e.key === "ArrowDown") {
|
|
12316
|
+
e.preventDefault();
|
|
12317
|
+
openList();
|
|
12318
|
+
}
|
|
12319
|
+
});
|
|
12320
|
+
const open2 = (e) => {
|
|
12321
|
+
e.preventDefault();
|
|
12322
|
+
e.stopPropagation();
|
|
12323
|
+
openList();
|
|
12324
|
+
};
|
|
12325
|
+
input.addEventListener("click", open2);
|
|
12326
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
12327
|
+
chevron.addEventListener("click", open2);
|
|
12328
|
+
wrap.append(input, chevron);
|
|
12329
|
+
return wrap;
|
|
12330
|
+
}
|
|
12331
|
+
/** A typeable YYYY-MM-DD field that also opens a themed month calendar. */
|
|
12332
|
+
dateField(id, current, onChange) {
|
|
12333
|
+
const wrap = document.createElement("div");
|
|
12334
|
+
wrap.className = "vela-ind-combo";
|
|
12335
|
+
wrap.style.cssText = "position:relative;width:128px;";
|
|
12336
|
+
const input = document.createElement("input");
|
|
12337
|
+
input.type = "text";
|
|
12338
|
+
input.id = id;
|
|
12339
|
+
input.value = current;
|
|
12340
|
+
input.autocomplete = "off";
|
|
12341
|
+
input.spellcheck = false;
|
|
12342
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
12343
|
+
const chevron = document.createElement("button");
|
|
12344
|
+
chevron.type = "button";
|
|
12345
|
+
chevron.tabIndex = -1;
|
|
12346
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
12347
|
+
chevron.setAttribute("aria-label", "Open calendar");
|
|
12348
|
+
chevron.innerHTML = CALENDAR_SVG;
|
|
12349
|
+
let value = current;
|
|
12350
|
+
const commitTyped = () => {
|
|
12351
|
+
const next = normalizeDateInput(input.value);
|
|
12352
|
+
if (!next) {
|
|
12353
|
+
input.value = value;
|
|
12354
|
+
return;
|
|
12355
|
+
}
|
|
12356
|
+
input.value = next;
|
|
12357
|
+
if (next !== value) {
|
|
12358
|
+
value = next;
|
|
12359
|
+
onChange(next);
|
|
12360
|
+
}
|
|
12361
|
+
};
|
|
12362
|
+
input.addEventListener("blur", () => {
|
|
12363
|
+
if (isPopoverOpen()) return;
|
|
12364
|
+
commitTyped();
|
|
12365
|
+
});
|
|
12366
|
+
const pick = (v) => {
|
|
12367
|
+
value = v;
|
|
12368
|
+
input.value = v;
|
|
12369
|
+
onChange(v);
|
|
12370
|
+
};
|
|
12371
|
+
input.addEventListener("keydown", (e) => {
|
|
12372
|
+
if (e.key === "Enter") {
|
|
12373
|
+
e.preventDefault();
|
|
12374
|
+
commitTyped();
|
|
12375
|
+
input.blur();
|
|
12376
|
+
}
|
|
12377
|
+
if (e.key === "ArrowDown") {
|
|
12378
|
+
e.preventDefault();
|
|
12379
|
+
this.openCalendar(wrap, value, pick);
|
|
12380
|
+
}
|
|
12381
|
+
});
|
|
12382
|
+
const open2 = (e) => {
|
|
12383
|
+
e.preventDefault();
|
|
12384
|
+
e.stopPropagation();
|
|
12385
|
+
if (openPopoverTrigger() === wrap) {
|
|
12386
|
+
closeOpenPopovers();
|
|
12387
|
+
return;
|
|
12388
|
+
}
|
|
12389
|
+
this.openCalendar(wrap, value, pick);
|
|
12390
|
+
};
|
|
12391
|
+
input.addEventListener("click", open2);
|
|
12392
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
12393
|
+
chevron.addEventListener("click", open2);
|
|
12394
|
+
wrap.append(input, chevron);
|
|
12395
|
+
return wrap;
|
|
12396
|
+
}
|
|
12397
|
+
/** Numeric field with hover steppers on the right (no fill until a triangle is hovered). */
|
|
12398
|
+
buildNumber(id, current, inp, emit) {
|
|
12399
|
+
return new NumberInput({
|
|
12400
|
+
id,
|
|
12401
|
+
value: current,
|
|
12402
|
+
min: inp.min,
|
|
12403
|
+
max: inp.max,
|
|
12404
|
+
step: inp.step ?? (inp.type === "int" ? 1 : 0.1),
|
|
12405
|
+
integer: inp.type === "int",
|
|
12406
|
+
size: "md",
|
|
12407
|
+
commit: "blur",
|
|
12408
|
+
onChange: emit
|
|
12409
|
+
}).el;
|
|
12410
|
+
}
|
|
12411
|
+
/** Placement shared by the indicator dialog's dropdowns and the time combobox. */
|
|
12412
|
+
listPopover() {
|
|
12413
|
+
return {
|
|
12414
|
+
theme: this.theme,
|
|
12415
|
+
matchWidth: true,
|
|
12416
|
+
boundary: this.dialog ?? "viewport",
|
|
12417
|
+
boundaryInset: 8,
|
|
12418
|
+
gap: 4
|
|
12419
|
+
};
|
|
12420
|
+
}
|
|
12421
|
+
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
12422
|
+
openCalendar(anchor, current, onPick) {
|
|
12423
|
+
const parsed = parseIsoDate(current);
|
|
12424
|
+
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
12425
|
+
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
12426
|
+
const selected = parsed ? isoDate(parsed) : current;
|
|
12427
|
+
const pop = new Popover({
|
|
12428
|
+
trigger: anchor,
|
|
12429
|
+
theme: this.theme,
|
|
12430
|
+
className: "vela-ind-cal",
|
|
12431
|
+
boundary: this.dialog ?? "viewport",
|
|
12432
|
+
boundaryInset: 8,
|
|
12433
|
+
gap: 4,
|
|
12434
|
+
onClose: () => {
|
|
12435
|
+
this.calendarPop = null;
|
|
12436
|
+
this.calendarAnchor = null;
|
|
12437
|
+
},
|
|
12438
|
+
content: (el) => {
|
|
12439
|
+
const title = document.createElement("div");
|
|
12440
|
+
title.className = "vela-ind-cal-title";
|
|
12441
|
+
const prev2 = document.createElement("button");
|
|
12442
|
+
prev2.type = "button";
|
|
12443
|
+
prev2.className = "vela-ind-cal-nav";
|
|
12444
|
+
prev2.setAttribute("aria-label", "Previous month");
|
|
12445
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
12446
|
+
const next = document.createElement("button");
|
|
12447
|
+
next.type = "button";
|
|
12448
|
+
next.className = "vela-ind-cal-nav";
|
|
12449
|
+
next.setAttribute("aria-label", "Next month");
|
|
12450
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
12451
|
+
const head = document.createElement("div");
|
|
12452
|
+
head.className = "vela-ind-cal-head";
|
|
12453
|
+
head.append(prev2, title, next);
|
|
12454
|
+
const week = document.createElement("div");
|
|
12455
|
+
week.className = "vela-ind-cal-week";
|
|
12456
|
+
for (const d of WEEKDAY_LABELS) {
|
|
12457
|
+
const cell = document.createElement("span");
|
|
12458
|
+
cell.textContent = d;
|
|
12459
|
+
week.appendChild(cell);
|
|
12460
|
+
}
|
|
12461
|
+
const grid = document.createElement("div");
|
|
12462
|
+
grid.className = "vela-ind-cal-grid";
|
|
12463
|
+
const paint = () => {
|
|
12464
|
+
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
12465
|
+
grid.replaceChildren();
|
|
12466
|
+
const first = new Date(year, month, 1);
|
|
12467
|
+
const startPad = first.getDay();
|
|
12468
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
12469
|
+
const today = isoDate(/* @__PURE__ */ new Date());
|
|
12470
|
+
for (let i = 0; i < startPad; i++) {
|
|
12471
|
+
const blank = document.createElement("span");
|
|
12472
|
+
blank.className = "vela-ind-cal-blank";
|
|
12473
|
+
grid.appendChild(blank);
|
|
12474
|
+
}
|
|
12475
|
+
for (let day = 1; day <= days; day++) {
|
|
12476
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
12477
|
+
const b = document.createElement("button");
|
|
12478
|
+
b.type = "button";
|
|
12479
|
+
b.className = "vela-ind-cal-day";
|
|
12480
|
+
b.textContent = String(day);
|
|
12481
|
+
if (iso === selected) b.dataset.checked = "1";
|
|
12482
|
+
if (iso === today) b.dataset.today = "1";
|
|
12483
|
+
b.addEventListener("click", (e) => {
|
|
12484
|
+
e.stopPropagation();
|
|
12485
|
+
pop.hide();
|
|
12486
|
+
onPick(iso);
|
|
12487
|
+
});
|
|
12488
|
+
grid.appendChild(b);
|
|
12489
|
+
}
|
|
12490
|
+
};
|
|
12491
|
+
prev2.addEventListener("click", (e) => {
|
|
12492
|
+
e.stopPropagation();
|
|
12493
|
+
month -= 1;
|
|
12494
|
+
if (month < 0) {
|
|
12495
|
+
month = 11;
|
|
12496
|
+
year -= 1;
|
|
12497
|
+
}
|
|
12498
|
+
paint();
|
|
12499
|
+
});
|
|
12500
|
+
next.addEventListener("click", (e) => {
|
|
12501
|
+
e.stopPropagation();
|
|
12502
|
+
month += 1;
|
|
12503
|
+
if (month > 11) {
|
|
12504
|
+
month = 0;
|
|
12505
|
+
year += 1;
|
|
12506
|
+
}
|
|
12507
|
+
paint();
|
|
12508
|
+
});
|
|
12509
|
+
paint();
|
|
12510
|
+
el.append(head, week, grid);
|
|
12511
|
+
}
|
|
12512
|
+
});
|
|
12513
|
+
this.calendarPop = pop;
|
|
12514
|
+
this.calendarAnchor = anchor;
|
|
12515
|
+
pop.show();
|
|
11037
12516
|
}
|
|
11038
12517
|
/**
|
|
11039
12518
|
* `input.symbol` → a field that opens the host's ticker-selection UI when a picker is wired
|
|
@@ -11061,7 +12540,7 @@ var InputsUI = class {
|
|
|
11061
12540
|
}
|
|
11062
12541
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
11063
12542
|
ctrlStyle() {
|
|
11064
|
-
return `width:100%;box-sizing:border-box;height:
|
|
12543
|
+
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;`;
|
|
11065
12544
|
}
|
|
11066
12545
|
/** Whether the active theme is dark (drives the dialog's `color-scheme`). */
|
|
11067
12546
|
isDarkTheme() {
|
|
@@ -11080,21 +12559,6 @@ var InputsUI = class {
|
|
|
11080
12559
|
strongText() {
|
|
11081
12560
|
return "var(--vela-fg-bright)";
|
|
11082
12561
|
}
|
|
11083
|
-
/**
|
|
11084
|
-
* Paint a `<select>` + its `<option>`s so the native dropdown popup matches the dialog: the
|
|
11085
|
-
* option list's background is the dialog color and its text is the strong foreground. `color-scheme`
|
|
11086
|
-
* alone doesn't theme the popup in every embedded browser (some render it white with white text —
|
|
11087
|
-
* invisible), so the colors are set explicitly here.
|
|
11088
|
-
*/
|
|
11089
|
-
styleSelect(sel) {
|
|
11090
|
-
const bg = this.theme.background;
|
|
11091
|
-
const fg = this.strongText();
|
|
11092
|
-
sel.style.backgroundColor = bg;
|
|
11093
|
-
for (const o of Array.from(sel.options)) {
|
|
11094
|
-
o.style.backgroundColor = bg;
|
|
11095
|
-
o.style.color = fg;
|
|
11096
|
-
}
|
|
11097
|
-
}
|
|
11098
12562
|
dialogButton(label, primary, onClick) {
|
|
11099
12563
|
const b = document.createElement("button");
|
|
11100
12564
|
b.type = "button";
|
|
@@ -11108,6 +12572,21 @@ function nameOf(inp) {
|
|
|
11108
12572
|
const t = inp.title;
|
|
11109
12573
|
return t.length > 0 ? t.charAt(0).toUpperCase() + t.slice(1) : "";
|
|
11110
12574
|
}
|
|
12575
|
+
var DEFAULT_INPUT_TAB = "Inputs";
|
|
12576
|
+
function tabInputs(inputs) {
|
|
12577
|
+
const byTab = /* @__PURE__ */ new Map();
|
|
12578
|
+
for (const inp of inputs) {
|
|
12579
|
+
const name = inp.tab && inp.tab.length > 0 ? inp.tab : DEFAULT_INPUT_TAB;
|
|
12580
|
+
if (!byTab.has(name)) byTab.set(name, []);
|
|
12581
|
+
byTab.get(name).push(inp);
|
|
12582
|
+
}
|
|
12583
|
+
const names = [...byTab.keys()];
|
|
12584
|
+
if (names.includes(DEFAULT_INPUT_TAB)) {
|
|
12585
|
+
names.splice(names.indexOf(DEFAULT_INPUT_TAB), 1);
|
|
12586
|
+
names.unshift(DEFAULT_INPUT_TAB);
|
|
12587
|
+
}
|
|
12588
|
+
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
12589
|
+
}
|
|
11111
12590
|
function groupInputs(inputs) {
|
|
11112
12591
|
const order = [];
|
|
11113
12592
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -11136,9 +12615,10 @@ function groupInputs(inputs) {
|
|
|
11136
12615
|
});
|
|
11137
12616
|
}
|
|
11138
12617
|
var CHECK_SVG = iconAt("check", 12);
|
|
11139
|
-
var
|
|
12618
|
+
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
12619
|
+
var CLOCK_SVG = iconAt("clock", 14);
|
|
11140
12620
|
var DIALOG_STYLE_ID = "vela-ind-dialog-styles";
|
|
11141
|
-
var DIALOG_STYLE_REV = "
|
|
12621
|
+
var DIALOG_STYLE_REV = "25";
|
|
11142
12622
|
function ensureDialogStyles() {
|
|
11143
12623
|
if (typeof document === "undefined") return;
|
|
11144
12624
|
const existing = document.getElementById(DIALOG_STYLE_ID);
|
|
@@ -11147,30 +12627,47 @@ function ensureDialogStyles() {
|
|
|
11147
12627
|
s.id = DIALOG_STYLE_ID;
|
|
11148
12628
|
s.dataset.rev = DIALOG_STYLE_REV;
|
|
11149
12629
|
s.textContent = `
|
|
11150
|
-
.vela-ind-dialog input[type=
|
|
11151
|
-
.vela-ind-dialog input[type=
|
|
11152
|
-
.vela-ind-dialog input[type=
|
|
11153
|
-
.vela-ind-
|
|
11154
|
-
.vela-ind-
|
|
11155
|
-
.vela-ind-
|
|
11156
|
-
.vela-ind-
|
|
11157
|
-
.vela-ind-
|
|
12630
|
+
.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;}
|
|
12631
|
+
.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);}
|
|
12632
|
+
.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);}
|
|
12633
|
+
.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;}
|
|
12634
|
+
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
12635
|
+
.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;}
|
|
12636
|
+
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
12637
|
+
.vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
|
|
12638
|
+
.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;}
|
|
12639
|
+
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
12640
|
+
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
12641
|
+
.vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
|
|
12642
|
+
.vela-ind-cal-week span{line-height:20px;}
|
|
12643
|
+
.vela-ind-cal-blank{width:28px;height:28px;}
|
|
12644
|
+
.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;}
|
|
12645
|
+
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
12646
|
+
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
12647
|
+
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
12648
|
+
.vela-ind-dialog ::-webkit-scrollbar{width:9px;height:9px;}
|
|
11158
12649
|
.vela-ind-dialog ::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}
|
|
11159
12650
|
.vela-ind-dialog ::-webkit-scrollbar-track{background:transparent;}
|
|
12651
|
+
.vela-ind-dialog ::-webkit-scrollbar-button{display:none;width:0;height:0;}
|
|
12652
|
+
.vela-ind-tab{transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
12653
|
+
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
11160
12654
|
.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;}
|
|
11161
12655
|
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
11162
12656
|
.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;}
|
|
11163
12657
|
.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;}
|
|
11164
12658
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
11165
12659
|
.vela-ind-close:hover{color:var(--vela-danger) !important;background-color:var(--vela-hover-strong);}
|
|
12660
|
+
.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;}
|
|
12661
|
+
.vela-ind-dlg-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
12662
|
+
.vela-ind-dlg-close svg{width:15px;height:15px;display:block;flex:none;}
|
|
11166
12663
|
.vela-ind-fold{transition:border-color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
|
|
11167
12664
|
.vela-ind-fold:hover{border-color:var(--vela-border-strong);opacity:0.9;}
|
|
11168
12665
|
.vela-ind-menuitem{background:transparent;transition:background var(--vela-dur-fast) ease;}
|
|
11169
12666
|
.vela-ind-menuitem:hover{background:var(--vela-hover-strong);}
|
|
11170
|
-
.vela-ind-btn{cursor:pointer;padding:
|
|
11171
|
-
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
11172
|
-
.vela-ind-btn-primary{
|
|
11173
|
-
.vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;}`;
|
|
12667
|
+
.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;}
|
|
12668
|
+
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);border-color:var(--vela-fg-muted);}
|
|
12669
|
+
.vela-ind-btn-primary{border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
12670
|
+
.vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;border-color:var(--vela-selected-bg);}`;
|
|
11174
12671
|
if (!existing) document.head.appendChild(s);
|
|
11175
12672
|
}
|
|
11176
12673
|
var TIMEFRAME_OPTIONS = [
|
|
@@ -11195,6 +12692,37 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
11195
12692
|
const v = `${hh}:${mm}`;
|
|
11196
12693
|
return { value: v, label: v };
|
|
11197
12694
|
});
|
|
12695
|
+
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
12696
|
+
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
12697
|
+
function normalizeDateInput(raw) {
|
|
12698
|
+
const t = raw.trim();
|
|
12699
|
+
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
|
|
12700
|
+
if (!m) return null;
|
|
12701
|
+
const y = Number(m[1]);
|
|
12702
|
+
const mo = Number(m[2]);
|
|
12703
|
+
const d = Number(m[3]);
|
|
12704
|
+
const dt = new Date(y, mo - 1, d);
|
|
12705
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
|
|
12706
|
+
return isoDate(dt);
|
|
12707
|
+
}
|
|
12708
|
+
function parseIsoDate(raw) {
|
|
12709
|
+
const iso = normalizeDateInput(raw);
|
|
12710
|
+
if (!iso) return null;
|
|
12711
|
+
const [y, mo, d] = iso.split("-").map(Number);
|
|
12712
|
+
return new Date(y, mo - 1, d);
|
|
12713
|
+
}
|
|
12714
|
+
function isoDate(d) {
|
|
12715
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
12716
|
+
}
|
|
12717
|
+
function normalizeTimeInput(raw) {
|
|
12718
|
+
const t = raw.trim();
|
|
12719
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
12720
|
+
if (!m) return null;
|
|
12721
|
+
const hh = Number(m[1]);
|
|
12722
|
+
const mm = Number(m[2]);
|
|
12723
|
+
if (hh > 23 || mm > 59) return null;
|
|
12724
|
+
return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
|
|
12725
|
+
}
|
|
11198
12726
|
function sessionToTimes(session) {
|
|
11199
12727
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(session.trim());
|
|
11200
12728
|
if (!m) return ["09:00", "16:00"];
|
|
@@ -15301,6 +16829,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
|
|
|
15301
16829
|
const rx = right ? width + 2 : Math.max(x1, x2);
|
|
15302
16830
|
return [lx, yAt(lx), rx, yAt(rx)];
|
|
15303
16831
|
}
|
|
16832
|
+
function uprightLineAngle(x1, y1, x2, y2) {
|
|
16833
|
+
let a = Math.atan2(y2 - y1, x2 - x1);
|
|
16834
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
16835
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
16836
|
+
return a;
|
|
16837
|
+
}
|
|
15304
16838
|
function parseColor2(c) {
|
|
15305
16839
|
const s = c.trim();
|
|
15306
16840
|
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
@@ -16555,380 +18089,71 @@ function tzButtonLabel(zone) {
|
|
|
16555
18089
|
return tzOffset(zone);
|
|
16556
18090
|
}
|
|
16557
18091
|
|
|
16558
|
-
// src/core/drawings/effectiveColor.ts
|
|
16559
|
-
var VALID_FILL = VALID;
|
|
16560
|
-
var INVALID_FILL = INVALID;
|
|
16561
|
-
function effectiveFillColor(d, theme) {
|
|
16562
|
-
if (d instanceof PatternDrawing) {
|
|
16563
|
-
if (!d.fillTriangles().length) return null;
|
|
16564
|
-
const valid = d.valid();
|
|
16565
|
-
return d.style.fillColor ?? (valid == null ? d.style.lineColor : valid ? VALID_FILL : INVALID_FILL);
|
|
16566
|
-
}
|
|
16567
|
-
if (d instanceof CalloutBase) return d.style.fillColor ?? theme.background;
|
|
16568
|
-
return d.style.fillColor ?? null;
|
|
16569
|
-
}
|
|
16570
|
-
|
|
16571
|
-
// src/renderers/native/drawings/colorPicker.ts
|
|
16572
|
-
function splitColor(color) {
|
|
16573
|
-
const s = String(color ?? "").trim();
|
|
16574
|
-
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
16575
|
-
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
16576
|
-
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
16577
|
-
if (m) {
|
|
16578
|
-
const h = m[1].toLowerCase();
|
|
16579
|
-
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
16580
|
-
}
|
|
16581
|
-
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
16582
|
-
if (rgba) {
|
|
16583
|
-
const p = rgba[1].split(",").map((v) => v.trim());
|
|
16584
|
-
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
16585
|
-
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
16586
|
-
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
16587
|
-
}
|
|
16588
|
-
return { hex6: DEFAULT_DRAWING_COLOR, alpha: 1 };
|
|
16589
|
-
}
|
|
16590
|
-
function combineColor(hex6, alpha) {
|
|
16591
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
16592
|
-
if (a >= 0.999) return hex6;
|
|
16593
|
-
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
16594
|
-
}
|
|
16595
|
-
function blendOver(fg, bg, alpha) {
|
|
16596
|
-
const rgb = (c) => {
|
|
16597
|
-
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
16598
|
-
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
16599
|
-
};
|
|
16600
|
-
const [fr, fgc, fb] = rgb(fg);
|
|
16601
|
-
const [br, bgc, bb] = rgb(bg);
|
|
16602
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
16603
|
-
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
16604
|
-
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
16605
|
-
}
|
|
16606
|
-
function hslHex(h, s, l) {
|
|
16607
|
-
const sn = s / 100;
|
|
16608
|
-
const ln = l / 100;
|
|
16609
|
-
const k = (n) => (n + h / 30) % 12;
|
|
16610
|
-
const a = sn * Math.min(ln, 1 - ln);
|
|
16611
|
-
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
16612
|
-
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
16613
|
-
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
16614
|
-
}
|
|
16615
|
-
function buildPalette() {
|
|
16616
|
-
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
16617
|
-
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
16618
|
-
const levels = [
|
|
16619
|
-
[82, 56],
|
|
16620
|
-
// vivid
|
|
16621
|
-
[58, 84],
|
|
16622
|
-
// light pastel
|
|
16623
|
-
[62, 74],
|
|
16624
|
-
[66, 62],
|
|
16625
|
-
[70, 50],
|
|
16626
|
-
[64, 39],
|
|
16627
|
-
[54, 29]
|
|
16628
|
-
// dark
|
|
16629
|
-
];
|
|
16630
|
-
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
16631
|
-
}
|
|
16632
|
-
var PALETTE = buildPalette();
|
|
16633
|
-
function transparencyChecker(size) {
|
|
16634
|
-
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
16635
|
-
}
|
|
16636
|
-
var CHECKER = transparencyChecker(10);
|
|
16637
|
-
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
16638
|
-
function addRecent(hex6) {
|
|
16639
|
-
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
16640
|
-
if (i >= 0) recents.splice(i, 1);
|
|
16641
|
-
recents.unshift(hex6);
|
|
16642
|
-
if (recents.length > 10) recents.length = 10;
|
|
16643
|
-
}
|
|
16644
|
-
function buildColorPicker(color, theme, onChange) {
|
|
16645
|
-
const parsed = splitColor(color);
|
|
16646
|
-
let curHex = parsed.hex6;
|
|
16647
|
-
let curAlpha = parsed.alpha;
|
|
16648
|
-
const root = document.createElement("div");
|
|
16649
|
-
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
16650
|
-
const grid = document.createElement("div");
|
|
16651
|
-
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
16652
|
-
const swatches = [];
|
|
16653
|
-
for (const row of PALETTE) {
|
|
16654
|
-
for (const c of row) {
|
|
16655
|
-
const sw = document.createElement("button");
|
|
16656
|
-
sw.type = "button";
|
|
16657
|
-
sw.dataset.c = c;
|
|
16658
|
-
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
16659
|
-
sw.addEventListener("click", (e) => {
|
|
16660
|
-
e.stopPropagation();
|
|
16661
|
-
pickHex(c);
|
|
16662
|
-
});
|
|
16663
|
-
grid.appendChild(sw);
|
|
16664
|
-
swatches.push(sw);
|
|
16665
|
-
}
|
|
16666
|
-
}
|
|
16667
|
-
const paintSelection = () => {
|
|
16668
|
-
for (const sw of swatches) {
|
|
16669
|
-
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
16670
|
-
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
16671
|
-
sw.style.outlineOffset = "2px";
|
|
16672
|
-
sw.style.zIndex = on ? "1" : "";
|
|
16673
|
-
sw.style.position = on ? "relative" : "";
|
|
16674
|
-
}
|
|
16675
|
-
};
|
|
16676
|
-
const recentRow = document.createElement("div");
|
|
16677
|
-
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
16678
|
-
const renderRecents = () => {
|
|
16679
|
-
recentRow.replaceChildren();
|
|
16680
|
-
for (const c of recents) {
|
|
16681
|
-
const sw = document.createElement("button");
|
|
16682
|
-
sw.type = "button";
|
|
16683
|
-
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;`;
|
|
16684
|
-
sw.addEventListener("click", (e) => {
|
|
16685
|
-
e.stopPropagation();
|
|
16686
|
-
pickHex(c);
|
|
16687
|
-
});
|
|
16688
|
-
recentRow.appendChild(sw);
|
|
16689
|
-
}
|
|
16690
|
-
const add = document.createElement("label");
|
|
16691
|
-
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;`;
|
|
16692
|
-
add.textContent = "+";
|
|
16693
|
-
const input = document.createElement("input");
|
|
16694
|
-
input.type = "color";
|
|
16695
|
-
input.value = curHex;
|
|
16696
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
16697
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
16698
|
-
add.appendChild(input);
|
|
16699
|
-
recentRow.appendChild(add);
|
|
16700
|
-
};
|
|
16701
|
-
const opLabel = document.createElement("div");
|
|
16702
|
-
opLabel.textContent = "Opacity";
|
|
16703
|
-
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
16704
|
-
const opRow = document.createElement("div");
|
|
16705
|
-
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
16706
|
-
const track = document.createElement("div");
|
|
16707
|
-
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
16708
|
-
const knob = document.createElement("div");
|
|
16709
|
-
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;";
|
|
16710
|
-
track.appendChild(knob);
|
|
16711
|
-
const pctBox = document.createElement("div");
|
|
16712
|
-
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;`;
|
|
16713
|
-
opRow.append(track, pctBox);
|
|
16714
|
-
const paintOpacity = () => {
|
|
16715
|
-
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
16716
|
-
knob.style.left = `${curAlpha * 100}%`;
|
|
16717
|
-
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
16718
|
-
};
|
|
16719
|
-
let dragging = false;
|
|
16720
|
-
const onDrag = (clientX) => {
|
|
16721
|
-
const r = track.getBoundingClientRect();
|
|
16722
|
-
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
16723
|
-
paintOpacity();
|
|
16724
|
-
emit();
|
|
16725
|
-
};
|
|
16726
|
-
track.addEventListener("pointerdown", (e) => {
|
|
16727
|
-
e.stopPropagation();
|
|
16728
|
-
dragging = true;
|
|
16729
|
-
track.setPointerCapture(e.pointerId);
|
|
16730
|
-
onDrag(e.clientX);
|
|
16731
|
-
});
|
|
16732
|
-
track.addEventListener("pointermove", (e) => {
|
|
16733
|
-
if (dragging) onDrag(e.clientX);
|
|
16734
|
-
});
|
|
16735
|
-
track.addEventListener("pointerup", () => dragging = false);
|
|
16736
|
-
function emit() {
|
|
16737
|
-
onChange(combineColor(curHex, curAlpha));
|
|
16738
|
-
}
|
|
16739
|
-
function pickHex(hex, custom = false) {
|
|
16740
|
-
curHex = splitColor(hex).hex6;
|
|
16741
|
-
addRecent(curHex);
|
|
16742
|
-
paintSelection();
|
|
16743
|
-
paintOpacity();
|
|
16744
|
-
if (custom) renderRecents();
|
|
16745
|
-
emit();
|
|
16746
|
-
}
|
|
16747
|
-
renderRecents();
|
|
16748
|
-
paintSelection();
|
|
16749
|
-
paintOpacity();
|
|
16750
|
-
root.append(grid, recentRow, opLabel, opRow);
|
|
16751
|
-
return root;
|
|
16752
|
-
}
|
|
16753
|
-
|
|
16754
|
-
// src/renderers/native/chrome/ColorField.ts
|
|
16755
|
-
var CHECKER2 = transparencyChecker(8);
|
|
16756
|
-
var STYLE_ID2 = "vela-color-field";
|
|
16757
|
-
function ensureStyles2() {
|
|
16758
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
|
|
16759
|
-
const st = document.createElement("style");
|
|
16760
|
-
st.id = STYLE_ID2;
|
|
16761
|
-
st.textContent = `
|
|
16762
|
-
.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;}
|
|
16763
|
-
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
16764
|
-
.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);}
|
|
16765
|
-
.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;}
|
|
16766
|
-
`;
|
|
16767
|
-
document.head.appendChild(st);
|
|
16768
|
-
}
|
|
16769
|
-
var openPopover = null;
|
|
16770
|
-
function closeOpen() {
|
|
16771
|
-
if (!openPopover) return;
|
|
16772
|
-
document.removeEventListener("pointerdown", openPopover.onOutside, true);
|
|
16773
|
-
window.removeEventListener("resize", openPopover.reflow, true);
|
|
16774
|
-
openPopover.el.remove();
|
|
16775
|
-
openPopover = null;
|
|
16776
|
-
}
|
|
16777
|
-
function place(pop, trigger) {
|
|
16778
|
-
const r = trigger.getBoundingClientRect();
|
|
16779
|
-
const w = pop.offsetWidth;
|
|
16780
|
-
const h = pop.offsetHeight;
|
|
16781
|
-
let left = r.right - w;
|
|
16782
|
-
let top = r.bottom + 6;
|
|
16783
|
-
if (left < 6) left = 6;
|
|
16784
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
16785
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
16786
|
-
if (top < 6) top = 6;
|
|
16787
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
16788
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
16789
|
-
}
|
|
16790
|
-
function colorField(theme, getVal, onVal) {
|
|
16791
|
-
ensureStyles2();
|
|
16792
|
-
const trigger = document.createElement("button");
|
|
16793
|
-
trigger.type = "button";
|
|
16794
|
-
trigger.className = "vela-color-field";
|
|
16795
|
-
const swatch = document.createElement("span");
|
|
16796
|
-
swatch.className = "vela-color-field-swatch";
|
|
16797
|
-
trigger.appendChild(swatch);
|
|
16798
|
-
const paint = () => {
|
|
16799
|
-
const v = getVal();
|
|
16800
|
-
swatch.style.background = `linear-gradient(${v}, ${v}), ${CHECKER2}`;
|
|
16801
|
-
};
|
|
16802
|
-
paint();
|
|
16803
|
-
trigger.addEventListener("vela-sync", paint);
|
|
16804
|
-
trigger.addEventListener("click", (e) => {
|
|
16805
|
-
e.stopPropagation();
|
|
16806
|
-
if (openPopover && openPopover.trigger === trigger) {
|
|
16807
|
-
closeOpen();
|
|
16808
|
-
return;
|
|
16809
|
-
}
|
|
16810
|
-
closeOpen();
|
|
16811
|
-
const pop = document.createElement("div");
|
|
16812
|
-
pop.className = "vela-color-field-pop";
|
|
16813
|
-
applyChromeTokens(pop, theme);
|
|
16814
|
-
pop.addEventListener("pointerdown", (ev) => ev.stopPropagation());
|
|
16815
|
-
pop.appendChild(
|
|
16816
|
-
buildColorPicker(getVal(), theme, (val) => {
|
|
16817
|
-
onVal(val);
|
|
16818
|
-
paint();
|
|
16819
|
-
})
|
|
16820
|
-
);
|
|
16821
|
-
document.body.appendChild(pop);
|
|
16822
|
-
place(pop, trigger);
|
|
16823
|
-
const onOutside = (ev) => {
|
|
16824
|
-
const t = ev.target;
|
|
16825
|
-
if (pop.contains(t) || trigger.contains(t)) return;
|
|
16826
|
-
closeOpen();
|
|
16827
|
-
};
|
|
16828
|
-
const reflow = () => place(pop, trigger);
|
|
16829
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
16830
|
-
window.addEventListener("resize", reflow, true);
|
|
16831
|
-
openPopover = { el: pop, trigger, onOutside, reflow };
|
|
16832
|
-
});
|
|
16833
|
-
return trigger;
|
|
16834
|
-
}
|
|
16835
|
-
function closeColorPopover() {
|
|
16836
|
-
closeOpen();
|
|
16837
|
-
}
|
|
16838
|
-
|
|
16839
18092
|
// src/renderers/native/chrome/WidthField.ts
|
|
16840
18093
|
var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
|
|
16841
|
-
var
|
|
16842
|
-
|
|
16843
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
16844
|
-
const st = document.createElement("style");
|
|
16845
|
-
st.id = STYLE_ID3;
|
|
16846
|
-
st.textContent = `
|
|
18094
|
+
var STYLE_ID2 = "vela-width-field";
|
|
18095
|
+
var WIDTH_CSS = `
|
|
16847
18096
|
.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;}
|
|
16848
18097
|
.vela-width-field:hover{border-color:var(--vela-fg-muted);}
|
|
16849
|
-
.vela-width-field-
|
|
18098
|
+
.vela-width-field-caret{display:flex;color:var(--vela-fg-muted);}
|
|
18099
|
+
.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);}
|
|
16850
18100
|
.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;}
|
|
16851
18101
|
.vela-width-field-item:hover{background:var(--vela-hover-strong);}
|
|
16852
18102
|
.vela-width-field-item[data-active='1']{background:var(--vela-active);}
|
|
16853
18103
|
`;
|
|
16854
|
-
document.head.appendChild(st);
|
|
16855
|
-
}
|
|
16856
18104
|
function lineGlyph(width) {
|
|
16857
18105
|
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>`;
|
|
16858
18106
|
}
|
|
16859
|
-
var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="
|
|
16860
|
-
var openPopover2 = null;
|
|
16861
|
-
function closeOpen2() {
|
|
16862
|
-
if (!openPopover2) return;
|
|
16863
|
-
document.removeEventListener("pointerdown", openPopover2.onOutside, true);
|
|
16864
|
-
window.removeEventListener("resize", openPopover2.reflow, true);
|
|
16865
|
-
openPopover2.el.remove();
|
|
16866
|
-
openPopover2 = null;
|
|
16867
|
-
}
|
|
16868
|
-
function place2(pop, trigger) {
|
|
16869
|
-
const r = trigger.getBoundingClientRect();
|
|
16870
|
-
const w = pop.offsetWidth;
|
|
16871
|
-
const h = pop.offsetHeight;
|
|
16872
|
-
let left = r.right - w;
|
|
16873
|
-
let top = r.bottom + 6;
|
|
16874
|
-
if (left < 6) left = 6;
|
|
16875
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
16876
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
16877
|
-
if (top < 6) top = 6;
|
|
16878
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
16879
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
16880
|
-
}
|
|
18107
|
+
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>`;
|
|
16881
18108
|
function widthField(theme, getVal, onVal) {
|
|
16882
|
-
|
|
18109
|
+
injectStyles(STYLE_ID2, WIDTH_CSS, document);
|
|
16883
18110
|
const trigger = document.createElement("button");
|
|
16884
18111
|
trigger.type = "button";
|
|
16885
18112
|
trigger.className = "vela-width-field";
|
|
16886
18113
|
const paint = () => {
|
|
16887
|
-
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span
|
|
18114
|
+
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span class="vela-width-field-caret">${CHEVRON}</span>`;
|
|
16888
18115
|
};
|
|
16889
18116
|
paint();
|
|
18117
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
18118
|
+
e.preventDefault();
|
|
18119
|
+
trigger.focus({ preventScroll: true });
|
|
18120
|
+
});
|
|
16890
18121
|
trigger.addEventListener("click", (e) => {
|
|
16891
18122
|
e.stopPropagation();
|
|
16892
|
-
if (
|
|
16893
|
-
|
|
18123
|
+
if (openPopoverTrigger() === trigger) {
|
|
18124
|
+
closeOpenPopovers();
|
|
16894
18125
|
return;
|
|
16895
18126
|
}
|
|
16896
|
-
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
|
|
16904
|
-
|
|
16905
|
-
|
|
16906
|
-
|
|
16907
|
-
|
|
16908
|
-
|
|
16909
|
-
|
|
16910
|
-
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
16915
|
-
|
|
16916
|
-
|
|
16917
|
-
|
|
16918
|
-
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
closeOpen2();
|
|
16922
|
-
};
|
|
16923
|
-
const reflow = () => place2(pop, trigger);
|
|
16924
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
16925
|
-
window.addEventListener("resize", reflow, true);
|
|
16926
|
-
openPopover2 = { el: pop, trigger, onOutside, reflow };
|
|
18127
|
+
const pop = new Popover({
|
|
18128
|
+
trigger,
|
|
18129
|
+
theme,
|
|
18130
|
+
align: "end",
|
|
18131
|
+
gap: 6,
|
|
18132
|
+
className: "vela-width-field-pop",
|
|
18133
|
+
content: (el) => {
|
|
18134
|
+
el.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
|
|
18135
|
+
for (const w of WIDTH_FIELD_OPTIONS) {
|
|
18136
|
+
const item = document.createElement("button");
|
|
18137
|
+
item.type = "button";
|
|
18138
|
+
item.className = "vela-width-field-item";
|
|
18139
|
+
item.dataset.active = w === getVal() ? "1" : "0";
|
|
18140
|
+
item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
|
|
18141
|
+
item.addEventListener("click", (ev) => {
|
|
18142
|
+
ev.stopPropagation();
|
|
18143
|
+
pop.hide();
|
|
18144
|
+
onVal(w);
|
|
18145
|
+
paint();
|
|
18146
|
+
});
|
|
18147
|
+
el.appendChild(item);
|
|
18148
|
+
}
|
|
18149
|
+
}
|
|
18150
|
+
});
|
|
18151
|
+
pop.show();
|
|
16927
18152
|
});
|
|
16928
18153
|
return trigger;
|
|
16929
18154
|
}
|
|
16930
18155
|
function closeWidthPopover() {
|
|
16931
|
-
|
|
18156
|
+
closeOpenPopovers();
|
|
16932
18157
|
}
|
|
16933
18158
|
|
|
16934
18159
|
// src/renderers/native/chrome/SettingsDialog.ts
|
|
@@ -16943,37 +18168,24 @@ function styleLabel(id) {
|
|
|
16943
18168
|
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS[id] ?? id;
|
|
16944
18169
|
}
|
|
16945
18170
|
var SD_STYLE_ID = "vela-settings-controls";
|
|
18171
|
+
var SD_STYLE_REV = "2";
|
|
16946
18172
|
var SETTINGS_SURFACE = "var(--vela-surface)";
|
|
16947
18173
|
var SETTINGS_BORDER = "var(--vela-border)";
|
|
16948
18174
|
var SETTINGS_TEXT = "var(--vela-fg)";
|
|
16949
18175
|
function ensureControlStyles() {
|
|
16950
|
-
if (typeof document === "undefined"
|
|
16951
|
-
const
|
|
18176
|
+
if (typeof document === "undefined") return;
|
|
18177
|
+
const existing = document.getElementById(SD_STYLE_ID);
|
|
18178
|
+
if (existing?.dataset.rev === SD_STYLE_REV) return;
|
|
18179
|
+
const st = existing ?? document.createElement("style");
|
|
16952
18180
|
st.id = SD_STYLE_ID;
|
|
18181
|
+
st.dataset.rev = SD_STYLE_REV;
|
|
16953
18182
|
st.textContent = `
|
|
16954
|
-
.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;}
|
|
16955
|
-
.vela-sd-check:hover{border-color:var(--vela-fg-muted);}
|
|
16956
|
-
.vela-sd-check.on{background:var(--vela-selected-bg);border-color:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
16957
|
-
.vela-sd-check svg{display:block;}
|
|
16958
|
-
.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;}
|
|
16959
|
-
.vela-sd-select:hover,.vela-sd-number:hover{border-color:var(--vela-fg-muted);}
|
|
16960
|
-
/* Custom caret: the native one hugs the right border; ours gets 8px of air. (A data URI
|
|
16961
|
-
can't read currentColor, so it uses the shared muted-gray ink, legible on both themes.) */
|
|
16962
|
-
.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;}
|
|
16963
|
-
.vela-sd-number{width:64px;}
|
|
16964
|
-
.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;}
|
|
16965
|
-
.vela-sd-color::-webkit-color-swatch-wrapper{padding:2px;}
|
|
16966
|
-
.vela-sd-color::-webkit-color-swatch{border:none;border-radius:2px;}
|
|
16967
18183
|
.vela-sd-pane::-webkit-scrollbar{width:8px;}
|
|
16968
18184
|
.vela-sd-pane::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:var(--vela-radius-sm);border:2px solid transparent;background-clip:padding-box;}
|
|
16969
18185
|
.vela-sd-pane::-webkit-scrollbar-track{background:transparent;}
|
|
16970
|
-
.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;}
|
|
16971
|
-
.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;}
|
|
16972
|
-
.vela-sd-toggle.on{background:var(--vela-active);border-color:var(--vela-selected-bg);}
|
|
16973
|
-
.vela-sd-toggle.on::after{transform:translateX(16px);background:var(--vela-selected-bg);}
|
|
16974
18186
|
/* Tab rail / footer button / header close: base styles live HERE, not inline on the
|
|
16975
18187
|
elements \u2014 inline declarations always beat stylesheet :hover rules, which is exactly
|
|
16976
|
-
what killed these hovers before. Active tab state is the .on class
|
|
18188
|
+
what killed these hovers before. Active tab state is the .on class,
|
|
16977
18189
|
hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
|
|
16978
18190
|
.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;}
|
|
16979
18191
|
.vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
@@ -17032,13 +18244,13 @@ function ensureControlStyles() {
|
|
|
17032
18244
|
.vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
|
|
17033
18245
|
.vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
|
|
17034
18246
|
.vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
|
|
17035
|
-
.vela-sd-mobile .vela-
|
|
17036
|
-
.vela-sd-mobile .vela-
|
|
18247
|
+
.vela-sd-mobile .vela-switch{width:22px;height:22px;}
|
|
18248
|
+
.vela-sd-mobile .vela-select[data-size='sm'] .vela-select-trigger,.vela-sd-mobile .vela-num[data-size='sm'] input{height:34px;}
|
|
17037
18249
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
17038
18250
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
17039
18251
|
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
|
|
17040
18252
|
`;
|
|
17041
|
-
document.head.appendChild(st);
|
|
18253
|
+
if (!existing) document.head.appendChild(st);
|
|
17042
18254
|
}
|
|
17043
18255
|
var SettingsDialog = class {
|
|
17044
18256
|
constructor(container, theme) {
|
|
@@ -17354,15 +18566,15 @@ var SettingsDialog = class {
|
|
|
17354
18566
|
railScrim = document.createElement("div");
|
|
17355
18567
|
railScrim.className = "vela-sd-railscrim";
|
|
17356
18568
|
railScrim.addEventListener("click", () => toggleRail?.());
|
|
17357
|
-
toggleRail = (
|
|
17358
|
-
const on =
|
|
18569
|
+
toggleRail = (open2) => {
|
|
18570
|
+
const on = open2 ?? !rail.classList.contains("open");
|
|
17359
18571
|
rail.classList.toggle("open", on);
|
|
17360
18572
|
railScrim?.classList.toggle("open", on);
|
|
17361
18573
|
};
|
|
17362
18574
|
}
|
|
17363
18575
|
const paneHost = document.createElement("div");
|
|
17364
18576
|
paneHost.className = "vela-sd-pane";
|
|
17365
|
-
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;";
|
|
18577
|
+
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;";
|
|
17366
18578
|
const panes = [];
|
|
17367
18579
|
let current = null;
|
|
17368
18580
|
for (const child of [...body.children]) {
|
|
@@ -17428,7 +18640,7 @@ var SettingsDialog = class {
|
|
|
17428
18640
|
}
|
|
17429
18641
|
}
|
|
17430
18642
|
close() {
|
|
17431
|
-
|
|
18643
|
+
closeOpenPopovers();
|
|
17432
18644
|
closeWidthPopover();
|
|
17433
18645
|
this.root?.remove();
|
|
17434
18646
|
this.root = null;
|
|
@@ -17583,50 +18795,41 @@ var SettingsDialog = class {
|
|
|
17583
18795
|
return wf;
|
|
17584
18796
|
}
|
|
17585
18797
|
if (c.kind === "select") {
|
|
17586
|
-
const sel = document.createElement("select");
|
|
17587
|
-
sel.className = "vela-sd-select";
|
|
17588
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
17589
|
-
sel.title = c.label;
|
|
17590
18798
|
const current2 = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
}
|
|
17598
|
-
sel.addEventListener("change", () => put(c.key, sel.value));
|
|
17599
|
-
sel.addEventListener("vela-sync", () => {
|
|
17600
|
-
sel.value = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
18799
|
+
const sel = new Select({
|
|
18800
|
+
options: normalizeSelectOptions(c.options).map(([value, label]) => ({ value, label })),
|
|
18801
|
+
value: current2,
|
|
18802
|
+
size: "sm",
|
|
18803
|
+
theme: this.theme,
|
|
18804
|
+
onChange: (v) => put(c.key, v)
|
|
17601
18805
|
});
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
ni.type = "number";
|
|
17606
|
-
ni.className = "vela-sd-number";
|
|
17607
|
-
if (compact) ni.style.width = "56px";
|
|
17608
|
-
if (c.min !== void 0) ni.min = String(c.min);
|
|
17609
|
-
if (c.max !== void 0) ni.max = String(c.max);
|
|
17610
|
-
ni.step = String(c.step ?? 1);
|
|
17611
|
-
ni.title = c.label;
|
|
17612
|
-
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
17613
|
-
if (c.placeholder !== void 0) {
|
|
17614
|
-
ni.placeholder = c.placeholder;
|
|
17615
|
-
if (current !== c.defval) ni.value = String(current);
|
|
17616
|
-
ni.addEventListener("change", () => {
|
|
17617
|
-
const raw = ni.value.trim() === "" ? c.defval : Number(ni.value);
|
|
17618
|
-
const v = Number.isFinite(raw) ? Math.min(c.max ?? Infinity, Math.max(c.min ?? -Infinity, raw)) : c.defval;
|
|
17619
|
-
ni.value = v === c.defval ? "" : String(v);
|
|
17620
|
-
put(c.key, v);
|
|
17621
|
-
});
|
|
17622
|
-
} else {
|
|
17623
|
-
ni.value = String(current);
|
|
17624
|
-
ni.addEventListener("input", () => {
|
|
17625
|
-
const v = Number(ni.value);
|
|
17626
|
-
if (Number.isFinite(v)) put(c.key, v);
|
|
18806
|
+
sel.el.title = c.label;
|
|
18807
|
+
sel.el.addEventListener("vela-sync", () => {
|
|
18808
|
+
sel.setValue(typeof bag[c.key] === "string" ? bag[c.key] : c.defval);
|
|
17627
18809
|
});
|
|
18810
|
+
return sel.el;
|
|
17628
18811
|
}
|
|
17629
|
-
|
|
18812
|
+
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
18813
|
+
const ni = new NumberInput({
|
|
18814
|
+
value: current,
|
|
18815
|
+
min: c.min,
|
|
18816
|
+
max: c.max,
|
|
18817
|
+
step: c.step ?? 1,
|
|
18818
|
+
size: "sm",
|
|
18819
|
+
commit: "live",
|
|
18820
|
+
clamp: c.placeholder !== void 0,
|
|
18821
|
+
steppers: false,
|
|
18822
|
+
compact,
|
|
18823
|
+
title: c.label,
|
|
18824
|
+
placeholder: c.placeholder,
|
|
18825
|
+
emptyValue: c.placeholder !== void 0 ? c.defval : void 0,
|
|
18826
|
+
onChange: (v) => put(c.key, v)
|
|
18827
|
+
});
|
|
18828
|
+
ni.el.addEventListener("vela-sync", () => {
|
|
18829
|
+
const next = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
18830
|
+
ni.setValue(next);
|
|
18831
|
+
});
|
|
18832
|
+
return ni.el;
|
|
17630
18833
|
}
|
|
17631
18834
|
/**
|
|
17632
18835
|
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
@@ -17907,28 +19110,27 @@ var SettingsDialog = class {
|
|
|
17907
19110
|
* only one visible at a time; see `typeRow`). */
|
|
17908
19111
|
toggleRow(label, value, onToggle, controls, get) {
|
|
17909
19112
|
const wrap = document.createElement("div");
|
|
17910
|
-
const
|
|
17911
|
-
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
let checked = value;
|
|
17915
|
-
const cbToggle = () => {
|
|
17916
|
-
checked = !checked;
|
|
17917
|
-
cb.classList.toggle("on", checked);
|
|
17918
|
-
return checked;
|
|
17919
|
-
};
|
|
19113
|
+
const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
|
|
19114
|
+
onToggle(v);
|
|
19115
|
+
if (controls.length > 0) syncDim(v);
|
|
19116
|
+
} });
|
|
17920
19117
|
const lbl = document.createElement("span");
|
|
17921
19118
|
lbl.textContent = label;
|
|
17922
19119
|
lbl.style.cssText = "opacity:0.85;white-space:nowrap;";
|
|
19120
|
+
const syncDim = (on) => {
|
|
19121
|
+
for (const c of controls) {
|
|
19122
|
+
if (c.dataset.sdSelfGated === "1") continue;
|
|
19123
|
+
c.style.opacity = on ? "1" : "0.4";
|
|
19124
|
+
c.style.pointerEvents = on ? "" : "none";
|
|
19125
|
+
}
|
|
19126
|
+
};
|
|
17923
19127
|
if (controls.length === 0) {
|
|
17924
19128
|
wrap.className = "vela-sd-bool";
|
|
17925
19129
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
|
|
17926
|
-
wrap.append(
|
|
17927
|
-
cb.addEventListener("click", () => onToggle(cbToggle()));
|
|
19130
|
+
wrap.append(sw.el, lbl);
|
|
17928
19131
|
if (get) {
|
|
17929
19132
|
wrap.addEventListener("vela-sync", () => {
|
|
17930
|
-
|
|
17931
|
-
cb.classList.toggle("on", checked);
|
|
19133
|
+
sw.setChecked(get());
|
|
17932
19134
|
});
|
|
17933
19135
|
}
|
|
17934
19136
|
return wrap;
|
|
@@ -17936,28 +19138,16 @@ var SettingsDialog = class {
|
|
|
17936
19138
|
wrap.className = "vela-sd-row";
|
|
17937
19139
|
const left = document.createElement("div");
|
|
17938
19140
|
left.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
17939
|
-
left.append(
|
|
19141
|
+
left.append(sw.el, lbl);
|
|
17940
19142
|
const box = document.createElement("div");
|
|
17941
19143
|
box.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
17942
19144
|
for (const c of controls) box.appendChild(c);
|
|
17943
|
-
const syncDim = (on) => {
|
|
17944
|
-
for (const c of controls) {
|
|
17945
|
-
if (c.dataset.sdSelfGated === "1") continue;
|
|
17946
|
-
c.style.opacity = on ? "1" : "0.4";
|
|
17947
|
-
c.style.pointerEvents = on ? "" : "none";
|
|
17948
|
-
}
|
|
17949
|
-
};
|
|
17950
19145
|
syncDim(value);
|
|
17951
|
-
cb.addEventListener("click", () => {
|
|
17952
|
-
const v = cbToggle();
|
|
17953
|
-
onToggle(v);
|
|
17954
|
-
syncDim(v);
|
|
17955
|
-
});
|
|
17956
19146
|
if (get) {
|
|
17957
19147
|
wrap.addEventListener("vela-sync", () => {
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
syncDim(
|
|
19148
|
+
const v = get();
|
|
19149
|
+
sw.setChecked(v);
|
|
19150
|
+
syncDim(v);
|
|
17961
19151
|
});
|
|
17962
19152
|
}
|
|
17963
19153
|
wrap.append(left, box);
|
|
@@ -17965,52 +19155,23 @@ var SettingsDialog = class {
|
|
|
17965
19155
|
}
|
|
17966
19156
|
numberRow(label, value, min, max, step, onChange) {
|
|
17967
19157
|
const { wrap } = this.row(label);
|
|
17968
|
-
|
|
17969
|
-
ni.type = "number";
|
|
17970
|
-
ni.value = String(value);
|
|
17971
|
-
ni.min = String(min);
|
|
17972
|
-
ni.max = String(max);
|
|
17973
|
-
ni.step = String(step);
|
|
17974
|
-
ni.className = "vela-sd-number";
|
|
17975
|
-
ni.addEventListener("input", () => {
|
|
17976
|
-
const n = Number(ni.value);
|
|
17977
|
-
if (Number.isFinite(n)) onChange(n);
|
|
17978
|
-
});
|
|
17979
|
-
wrap.appendChild(ni);
|
|
19158
|
+
wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
|
|
17980
19159
|
return wrap;
|
|
17981
19160
|
}
|
|
17982
19161
|
/** A dropdown whose option values differ from their display labels. */
|
|
17983
19162
|
selectRowLabeled(label, value, options, onChange) {
|
|
17984
19163
|
const { wrap } = this.row(label);
|
|
17985
|
-
|
|
17986
|
-
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
if (val === value) o.selected = true;
|
|
17993
|
-
sel.appendChild(o);
|
|
17994
|
-
}
|
|
17995
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
17996
|
-
wrap.appendChild(sel);
|
|
19164
|
+
wrap.appendChild(new Select({
|
|
19165
|
+
options: options.map(([v, l]) => ({ value: v, label: l })),
|
|
19166
|
+
value,
|
|
19167
|
+
size: "sm",
|
|
19168
|
+
theme: this.theme,
|
|
19169
|
+
onChange
|
|
19170
|
+
}).el);
|
|
17997
19171
|
return wrap;
|
|
17998
19172
|
}
|
|
17999
19173
|
selectRow(label, value, options, onChange) {
|
|
18000
|
-
|
|
18001
|
-
const sel = document.createElement("select");
|
|
18002
|
-
sel.className = "vela-sd-select";
|
|
18003
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
18004
|
-
for (const opt of options) {
|
|
18005
|
-
const o = document.createElement("option");
|
|
18006
|
-
o.value = opt;
|
|
18007
|
-
o.textContent = opt;
|
|
18008
|
-
if (opt === value) o.selected = true;
|
|
18009
|
-
sel.appendChild(o);
|
|
18010
|
-
}
|
|
18011
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
18012
|
-
wrap.appendChild(sel);
|
|
18013
|
-
return wrap;
|
|
19174
|
+
return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
|
|
18014
19175
|
}
|
|
18015
19176
|
/** Footer with the full config as JSON — the export/import (templating) surface. */
|
|
18016
19177
|
footer(_config) {
|
|
@@ -18035,6 +19196,19 @@ function timezoneOptions(current) {
|
|
|
18035
19196
|
return [[normalized, tzMenuLabel(normalized, normalized)], ...options];
|
|
18036
19197
|
}
|
|
18037
19198
|
|
|
19199
|
+
// src/core/drawings/effectiveColor.ts
|
|
19200
|
+
var VALID_FILL = VALID;
|
|
19201
|
+
var INVALID_FILL = INVALID;
|
|
19202
|
+
function effectiveFillColor(d, theme) {
|
|
19203
|
+
if (d instanceof PatternDrawing) {
|
|
19204
|
+
if (!d.fillTriangles().length) return null;
|
|
19205
|
+
const valid = d.valid();
|
|
19206
|
+
return d.style.fillColor ?? (valid == null ? d.style.lineColor : valid ? VALID_FILL : INVALID_FILL);
|
|
19207
|
+
}
|
|
19208
|
+
if (d instanceof CalloutBase) return d.style.fillColor ?? theme.background;
|
|
19209
|
+
return d.style.fillColor ?? null;
|
|
19210
|
+
}
|
|
19211
|
+
|
|
18038
19212
|
// src/renderers/native/drawings/DrawingPainter.ts
|
|
18039
19213
|
var HANDLE_RADIUS = 4.5;
|
|
18040
19214
|
var HANDLE_BORDER = DEFAULT_DRAWING_COLOR;
|
|
@@ -18401,13 +19575,19 @@ var DrawingPainter = class {
|
|
|
18401
19575
|
}
|
|
18402
19576
|
this.paintLabel(ctx, d, proj, theme);
|
|
18403
19577
|
}
|
|
18404
|
-
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
19578
|
+
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
19579
|
+
* Two-point line tools rotate the glyphs to follow the segment, kept upright. */
|
|
18405
19580
|
paintLabel(ctx, d, proj, theme) {
|
|
18406
19581
|
const text = d.text;
|
|
18407
19582
|
if (!text || !text.value || d.id === this.targets.mutedLabel) return;
|
|
18408
19583
|
const layout = labelLayout(d, proj);
|
|
18409
19584
|
if (!layout) return;
|
|
18410
19585
|
const fs = namedFontSize(text.size);
|
|
19586
|
+
ctx.save();
|
|
19587
|
+
if (layout.rotate) {
|
|
19588
|
+
ctx.translate(layout.rotate.cx, layout.rotate.cy);
|
|
19589
|
+
ctx.rotate(layout.rotate.angle);
|
|
19590
|
+
}
|
|
18411
19591
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
18412
19592
|
ctx.textBaseline = "top";
|
|
18413
19593
|
ctx.textAlign = layout.align;
|
|
@@ -18415,8 +19595,8 @@ var DrawingPainter = class {
|
|
|
18415
19595
|
const lh = labelLineHeight(fs);
|
|
18416
19596
|
const lines = text.value.split("\n");
|
|
18417
19597
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
18418
|
-
ctx.textAlign = "left";
|
|
18419
19598
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
19599
|
+
ctx.restore();
|
|
18420
19600
|
}
|
|
18421
19601
|
/** Frame a plain text label while it is the target, so the text reads as a clickable object even
|
|
18422
19602
|
* though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
|
|
@@ -19359,7 +20539,14 @@ function labelLayout(d, proj) {
|
|
|
19359
20539
|
case "infoline":
|
|
19360
20540
|
case "trendangle": {
|
|
19361
20541
|
if (pts.length < 2) return null;
|
|
19362
|
-
|
|
20542
|
+
const [x1, y1] = pts[0];
|
|
20543
|
+
const [x2, y2] = pts[1];
|
|
20544
|
+
return {
|
|
20545
|
+
x: 0,
|
|
20546
|
+
top: -6 - lh * lines,
|
|
20547
|
+
align: "center",
|
|
20548
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: (x1 + x2) / 2, cy: (y1 + y2) / 2 }
|
|
20549
|
+
};
|
|
19363
20550
|
}
|
|
19364
20551
|
case "box": {
|
|
19365
20552
|
if (pts.length < 2) return null;
|
|
@@ -19708,11 +20895,11 @@ function applyFree(anchor, pt, free) {
|
|
|
19708
20895
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
19709
20896
|
var BTN = 30;
|
|
19710
20897
|
var ICON = 21;
|
|
19711
|
-
var
|
|
19712
|
-
function
|
|
19713
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
20898
|
+
var STYLE_ID3 = "vela-drawing-popup-styles";
|
|
20899
|
+
function ensureStyles2() {
|
|
20900
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
19714
20901
|
const s = document.createElement("style");
|
|
19715
|
-
s.id =
|
|
20902
|
+
s.id = STYLE_ID3;
|
|
19716
20903
|
s.textContent = `
|
|
19717
20904
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
19718
20905
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -19746,17 +20933,14 @@ var DrawingSettingsPopup = class {
|
|
|
19746
20933
|
this.textPanel = null;
|
|
19747
20934
|
this.levelsPanel = null;
|
|
19748
20935
|
this.colorPop = null;
|
|
19749
|
-
// floating RGB + opacity picker for a color swatch
|
|
19750
20936
|
this.colorOwner = null;
|
|
19751
|
-
|
|
19752
|
-
this.menuEl = null;
|
|
19753
|
-
// floating option list for a dropdown control
|
|
20937
|
+
this.menuPop = null;
|
|
19754
20938
|
this.menuOwner = null;
|
|
19755
20939
|
this.onClose = null;
|
|
19756
20940
|
this.onOutside = (e) => {
|
|
19757
20941
|
const target = e.target;
|
|
19758
|
-
if (this.colorPop?.contains(target)) return;
|
|
19759
|
-
if (this.
|
|
20942
|
+
if (this.colorPop?.el.contains(target)) return;
|
|
20943
|
+
if (this.menuPop?.el.contains(target)) return;
|
|
19760
20944
|
if (this.el && !this.el.contains(target)) {
|
|
19761
20945
|
const cb = this.onClose;
|
|
19762
20946
|
this.close();
|
|
@@ -19775,13 +20959,13 @@ var DrawingSettingsPopup = class {
|
|
|
19775
20959
|
* picker / dropdown menu), which live outside `el`. */
|
|
19776
20960
|
contains(node) {
|
|
19777
20961
|
if (!node) return false;
|
|
19778
|
-
return this.el?.contains(node) === true || this.colorPop?.contains(node) === true || this.
|
|
20962
|
+
return this.el?.contains(node) === true || this.colorPop?.el.contains(node) === true || this.menuPop?.el.contains(node) === true;
|
|
19779
20963
|
}
|
|
19780
20964
|
/** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
|
|
19781
20965
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
19782
20966
|
open(drawing, anchor, actions, onClose) {
|
|
19783
20967
|
this.close();
|
|
19784
|
-
|
|
20968
|
+
ensureStyles2();
|
|
19785
20969
|
this.onClose = onClose ?? null;
|
|
19786
20970
|
const t = this.theme;
|
|
19787
20971
|
const schema = drawing.schema();
|
|
@@ -19792,8 +20976,7 @@ var DrawingSettingsPopup = class {
|
|
|
19792
20976
|
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;`;
|
|
19793
20977
|
applyChromeTokens(el, t);
|
|
19794
20978
|
el.addEventListener("pointerdown", () => {
|
|
19795
|
-
|
|
19796
|
-
this.closeMenu();
|
|
20979
|
+
closeOpenPopovers();
|
|
19797
20980
|
});
|
|
19798
20981
|
const bar = document.createElement("div");
|
|
19799
20982
|
bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
|
|
@@ -20530,49 +21713,76 @@ var DrawingSettingsPopup = class {
|
|
|
20530
21713
|
});
|
|
20531
21714
|
return b;
|
|
20532
21715
|
}
|
|
21716
|
+
/** Host-anchored floating shell (stays inside the chart). Content is filled after
|
|
21717
|
+
* construction so `fill` can close over the live `Popover` without hitting TDZ. */
|
|
21718
|
+
hostFloat(anchor, opts) {
|
|
21719
|
+
const t = this.theme;
|
|
21720
|
+
const pop = new Popover({
|
|
21721
|
+
trigger: anchor,
|
|
21722
|
+
host: this.host,
|
|
21723
|
+
theme: t,
|
|
21724
|
+
position: "absolute",
|
|
21725
|
+
boundary: this.host,
|
|
21726
|
+
boundaryInset: 4,
|
|
21727
|
+
viewportInset: 0,
|
|
21728
|
+
gap: 6,
|
|
21729
|
+
align: opts.align ?? "start",
|
|
21730
|
+
zIndex: opts.zIndex,
|
|
21731
|
+
onClose: () => {
|
|
21732
|
+
if (this.menuPop === pop) {
|
|
21733
|
+
this.menuPop = null;
|
|
21734
|
+
this.menuOwner = null;
|
|
21735
|
+
}
|
|
21736
|
+
if (this.colorPop === pop) {
|
|
21737
|
+
this.colorPop = null;
|
|
21738
|
+
this.colorOwner = null;
|
|
21739
|
+
}
|
|
21740
|
+
}
|
|
21741
|
+
});
|
|
21742
|
+
const el = pop.el;
|
|
21743
|
+
el.style.background = t.background;
|
|
21744
|
+
el.style.border = "1px solid var(--vela-border)";
|
|
21745
|
+
el.style.borderRadius = "var(--vela-radius-lg)";
|
|
21746
|
+
el.style.boxShadow = "var(--vela-shadow)";
|
|
21747
|
+
el.style.padding = opts.padding;
|
|
21748
|
+
el.style.display = "flex";
|
|
21749
|
+
el.style.flexDirection = "column";
|
|
21750
|
+
el.style.gap = "1px";
|
|
21751
|
+
el.style.color = t.textColor;
|
|
21752
|
+
el.style.font = `var(--vela-font-size-md) ${t.fontFamily}`;
|
|
21753
|
+
el.style.pointerEvents = "auto";
|
|
21754
|
+
opts.fill(el, pop);
|
|
21755
|
+
pop.show();
|
|
21756
|
+
return pop;
|
|
21757
|
+
}
|
|
20533
21758
|
/** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
|
|
20534
21759
|
openActionMenu(anchor, rows) {
|
|
20535
|
-
this.
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
|
|
20540
|
-
|
|
20541
|
-
|
|
20542
|
-
|
|
20543
|
-
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
|
|
20552
|
-
|
|
20553
|
-
|
|
20554
|
-
|
|
20555
|
-
|
|
20556
|
-
|
|
20557
|
-
|
|
20558
|
-
|
|
20559
|
-
}
|
|
20560
|
-
|
|
20561
|
-
}
|
|
20562
|
-
this.host.appendChild(menu2);
|
|
20563
|
-
const ar = anchor.getBoundingClientRect();
|
|
20564
|
-
const hr = this.host.getBoundingClientRect();
|
|
20565
|
-
const w = menu2.offsetWidth;
|
|
20566
|
-
const h = menu2.offsetHeight;
|
|
20567
|
-
let left = ar.right - hr.left - w;
|
|
20568
|
-
let top = ar.bottom - hr.top + 6;
|
|
20569
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
20570
|
-
if (left < 4) left = 4;
|
|
20571
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
20572
|
-
if (top < 4) top = 4;
|
|
20573
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
20574
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
20575
|
-
this.menuEl = menu2;
|
|
21760
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
21761
|
+
align: "end",
|
|
21762
|
+
zIndex: 26,
|
|
21763
|
+
padding: "4px",
|
|
21764
|
+
fill: (menu2, pop) => {
|
|
21765
|
+
for (const row of rows) {
|
|
21766
|
+
const item = document.createElement("button");
|
|
21767
|
+
item.type = "button";
|
|
21768
|
+
item.className = "vela-dpop-item";
|
|
21769
|
+
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;";
|
|
21770
|
+
const ic = document.createElement("span");
|
|
21771
|
+
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
21772
|
+
ic.innerHTML = sized(row.icon, 18);
|
|
21773
|
+
const tx = document.createElement("span");
|
|
21774
|
+
tx.textContent = row.label;
|
|
21775
|
+
tx.style.cssText = "flex:1;";
|
|
21776
|
+
item.append(ic, tx);
|
|
21777
|
+
item.addEventListener("click", (e) => {
|
|
21778
|
+
e.stopPropagation();
|
|
21779
|
+
pop.hide();
|
|
21780
|
+
row.onClick();
|
|
21781
|
+
});
|
|
21782
|
+
menu2.appendChild(item);
|
|
21783
|
+
}
|
|
21784
|
+
}
|
|
21785
|
+
});
|
|
20576
21786
|
this.menuOwner = anchor;
|
|
20577
21787
|
}
|
|
20578
21788
|
/** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
|
|
@@ -20702,60 +21912,40 @@ var DrawingSettingsPopup = class {
|
|
|
20702
21912
|
/** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
|
|
20703
21913
|
* glyph (and text label when one is supplied); the current value is highlighted. */
|
|
20704
21914
|
openMenu(anchor, values, current, render, label, onPick) {
|
|
20705
|
-
this.
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
|
|
20711
|
-
|
|
20712
|
-
|
|
20713
|
-
|
|
20714
|
-
|
|
20715
|
-
|
|
20716
|
-
|
|
20717
|
-
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
|
|
20722
|
-
|
|
20723
|
-
|
|
20724
|
-
|
|
20725
|
-
|
|
20726
|
-
|
|
20727
|
-
|
|
20728
|
-
|
|
21915
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
21916
|
+
zIndex: 26,
|
|
21917
|
+
padding: "4px",
|
|
21918
|
+
fill: (menu2, pop) => {
|
|
21919
|
+
for (const v of values) {
|
|
21920
|
+
const active = v === current;
|
|
21921
|
+
const item = document.createElement("button");
|
|
21922
|
+
item.type = "button";
|
|
21923
|
+
item.className = "vela-dpop-item";
|
|
21924
|
+
item.dataset.active = active ? "1" : "0";
|
|
21925
|
+
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;`;
|
|
21926
|
+
const ic = document.createElement("span");
|
|
21927
|
+
ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
|
|
21928
|
+
ic.innerHTML = sized(render(v), 18);
|
|
21929
|
+
item.appendChild(ic);
|
|
21930
|
+
if (label) {
|
|
21931
|
+
const tx = document.createElement("span");
|
|
21932
|
+
tx.textContent = label(v);
|
|
21933
|
+
tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
|
|
21934
|
+
item.appendChild(tx);
|
|
21935
|
+
}
|
|
21936
|
+
item.addEventListener("click", (e) => {
|
|
21937
|
+
e.stopPropagation();
|
|
21938
|
+
pop.hide();
|
|
21939
|
+
onPick(v);
|
|
21940
|
+
});
|
|
21941
|
+
menu2.appendChild(item);
|
|
21942
|
+
}
|
|
20729
21943
|
}
|
|
20730
|
-
|
|
20731
|
-
e.stopPropagation();
|
|
20732
|
-
this.closeMenu();
|
|
20733
|
-
onPick(v);
|
|
20734
|
-
});
|
|
20735
|
-
menu2.appendChild(item);
|
|
20736
|
-
}
|
|
20737
|
-
this.host.appendChild(menu2);
|
|
20738
|
-
const ar = anchor.getBoundingClientRect();
|
|
20739
|
-
const hr = this.host.getBoundingClientRect();
|
|
20740
|
-
const w = menu2.offsetWidth;
|
|
20741
|
-
const h = menu2.offsetHeight;
|
|
20742
|
-
let left = ar.left - hr.left;
|
|
20743
|
-
let top = ar.bottom - hr.top + 6;
|
|
20744
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
20745
|
-
if (left < 4) left = 4;
|
|
20746
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
20747
|
-
if (top < 4) top = 4;
|
|
20748
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
20749
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
20750
|
-
this.menuEl = menu2;
|
|
21944
|
+
});
|
|
20751
21945
|
this.menuOwner = anchor;
|
|
20752
21946
|
}
|
|
20753
21947
|
closeMenu() {
|
|
20754
|
-
|
|
20755
|
-
this.menuEl.remove();
|
|
20756
|
-
this.menuEl = null;
|
|
20757
|
-
}
|
|
20758
|
-
this.menuOwner = null;
|
|
21948
|
+
this.menuPop?.hide();
|
|
20759
21949
|
}
|
|
20760
21950
|
/** A color control: an `icon` over a thin **colored underline** showing the current
|
|
20761
21951
|
* value (the common idiom), with an invisible native picker overlaid to edit it.
|
|
@@ -20794,36 +21984,18 @@ var DrawingSettingsPopup = class {
|
|
|
20794
21984
|
}
|
|
20795
21985
|
/** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
|
|
20796
21986
|
openColorPopover(anchor, color, onChange) {
|
|
20797
|
-
this.closeColorPopover();
|
|
20798
|
-
this.closeMenu();
|
|
20799
21987
|
const t = this.theme;
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
|
|
20807
|
-
const hr = this.host.getBoundingClientRect();
|
|
20808
|
-
const w = pop.offsetWidth;
|
|
20809
|
-
const h = pop.offsetHeight;
|
|
20810
|
-
let left = ar.left - hr.left;
|
|
20811
|
-
let top = ar.bottom - hr.top + 6;
|
|
20812
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
20813
|
-
if (left < 4) left = 4;
|
|
20814
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
20815
|
-
if (top < 4) top = 4;
|
|
20816
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
20817
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
20818
|
-
this.colorPop = pop;
|
|
21988
|
+
this.colorPop = this.hostFloat(anchor, {
|
|
21989
|
+
zIndex: 25,
|
|
21990
|
+
padding: "10px",
|
|
21991
|
+
fill: (el) => {
|
|
21992
|
+
el.appendChild(buildColorPicker(color, t, onChange));
|
|
21993
|
+
}
|
|
21994
|
+
});
|
|
20819
21995
|
this.colorOwner = anchor;
|
|
20820
21996
|
}
|
|
20821
21997
|
closeColorPopover() {
|
|
20822
|
-
|
|
20823
|
-
this.colorPop.remove();
|
|
20824
|
-
this.colorPop = null;
|
|
20825
|
-
}
|
|
20826
|
-
this.colorOwner = null;
|
|
21998
|
+
this.colorPop?.hide();
|
|
20827
21999
|
}
|
|
20828
22000
|
divider() {
|
|
20829
22001
|
const d = document.createElement("div");
|
|
@@ -20962,7 +22134,7 @@ var DrawingToolbar = class {
|
|
|
20962
22134
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
20963
22135
|
});
|
|
20964
22136
|
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
20965
|
-
|
|
22137
|
+
ensureStyles3();
|
|
20966
22138
|
this.root = document.createElement("div");
|
|
20967
22139
|
this.root.className = "vela-dtb";
|
|
20968
22140
|
this.styleRoot();
|
|
@@ -21445,13 +22617,13 @@ var DrawingToolbar = class {
|
|
|
21445
22617
|
}
|
|
21446
22618
|
};
|
|
21447
22619
|
var MAGNET_ID = "__magnet__";
|
|
21448
|
-
var
|
|
21449
|
-
function
|
|
22620
|
+
var STYLE_ID4 = "vela-drawing-toolbar-styles";
|
|
22621
|
+
function ensureStyles3() {
|
|
21450
22622
|
if (typeof document === "undefined") return;
|
|
21451
|
-
let s = document.getElementById(
|
|
22623
|
+
let s = document.getElementById(STYLE_ID4);
|
|
21452
22624
|
if (!s) {
|
|
21453
22625
|
s = document.createElement("style");
|
|
21454
|
-
s.id =
|
|
22626
|
+
s.id = STYLE_ID4;
|
|
21455
22627
|
document.head.appendChild(s);
|
|
21456
22628
|
}
|
|
21457
22629
|
s.textContent = `
|
|
@@ -21830,9 +23002,9 @@ var UserDrawingController = class {
|
|
|
21830
23002
|
}
|
|
21831
23003
|
/**
|
|
21832
23004
|
* Union of the visible drawings' price ranges on `paneId` whose time extent
|
|
21833
|
-
* intersects [fromTime, toTime]
|
|
21834
|
-
*
|
|
21835
|
-
*
|
|
23005
|
+
* intersects [fromTime, toTime]. Hidden drawings and full-width references
|
|
23006
|
+
* (hline → null extent) are handled too. Kept as the seam for a future
|
|
23007
|
+
* per-drawing autoscale opt-in — the renderer does not fold this in today.
|
|
21836
23008
|
*/
|
|
21837
23009
|
priceRangeForPane(paneId, fromTime, toTime) {
|
|
21838
23010
|
let lo = Infinity;
|
|
@@ -22707,7 +23879,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
|
|
|
22707
23879
|
|
|
22708
23880
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
22709
23881
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
22710
|
-
var
|
|
23882
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
22711
23883
|
var CSS2 = `
|
|
22712
23884
|
.vela-attribution {
|
|
22713
23885
|
text-decoration: none;
|
|
@@ -22770,11 +23942,11 @@ var CSS2 = `
|
|
|
22770
23942
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
22771
23943
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
22772
23944
|
`;
|
|
22773
|
-
function
|
|
22774
|
-
let s = doc.getElementById(
|
|
23945
|
+
function ensureStyles4(doc) {
|
|
23946
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
22775
23947
|
if (!s) {
|
|
22776
23948
|
s = doc.createElement("style");
|
|
22777
|
-
s.id =
|
|
23949
|
+
s.id = STYLE_ID5;
|
|
22778
23950
|
doc.head.appendChild(s);
|
|
22779
23951
|
}
|
|
22780
23952
|
s.textContent = CSS2;
|
|
@@ -22803,7 +23975,7 @@ function createCustomMark(doc, html, background) {
|
|
|
22803
23975
|
return el;
|
|
22804
23976
|
}
|
|
22805
23977
|
function createAttributionMark(doc, background) {
|
|
22806
|
-
|
|
23978
|
+
ensureStyles4(doc);
|
|
22807
23979
|
const a = doc.createElement("a");
|
|
22808
23980
|
a.className = "vela-attribution";
|
|
22809
23981
|
a.href = ATTRIBUTION_URL;
|
|
@@ -25781,7 +26953,6 @@ var NativeRenderer = class {
|
|
|
25781
26953
|
const n = this.coords.barCount;
|
|
25782
26954
|
if (n === 0) return;
|
|
25783
26955
|
const vr = this.coords.visibleLogicalRange();
|
|
25784
|
-
const vtr = this.coords.visibleTimeRange();
|
|
25785
26956
|
const i0 = Math.max(0, Math.floor(vr.from));
|
|
25786
26957
|
const i1 = Math.min(n - 1, Math.ceil(vr.to));
|
|
25787
26958
|
if (i1 < i0) return;
|
|
@@ -25799,9 +26970,7 @@ var NativeRenderer = class {
|
|
|
25799
26970
|
}
|
|
25800
26971
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
25801
26972
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
25802
|
-
|
|
25803
|
-
const udr = this.userDrawings?.priceRangeForPane(pane.id, vtr.from, vtr.to) ?? null;
|
|
25804
|
-
if (udr) dr = dr ? { min: Math.min(dr.min, udr.min), max: Math.max(dr.max, udr.max) } : udr;
|
|
26973
|
+
const dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
25805
26974
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
25806
26975
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
25807
26976
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -26798,55 +27967,6 @@ function resolveElement(container) {
|
|
|
26798
27967
|
if (!element) throw new Error(`[vela] container not found for selector "${container}"`);
|
|
26799
27968
|
return element;
|
|
26800
27969
|
}
|
|
26801
|
-
|
|
26802
|
-
// src/ui/styles.ts
|
|
26803
|
-
function injectStyles(id, css, root = document) {
|
|
26804
|
-
if (typeof document === "undefined") return;
|
|
26805
|
-
const host = root instanceof Document ? root.head : root;
|
|
26806
|
-
if (root.getElementById?.(id) ?? host.querySelector(`#${CSS.escape(id)}`)) return;
|
|
26807
|
-
const s = document.createElement("style");
|
|
26808
|
-
s.id = id;
|
|
26809
|
-
s.textContent = css;
|
|
26810
|
-
host.appendChild(s);
|
|
26811
|
-
}
|
|
26812
|
-
|
|
26813
|
-
// src/ui/tokens.ts
|
|
26814
|
-
var STATIC_ID = "vela-ui-tokens";
|
|
26815
|
-
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
26816
|
-
var STATIC_CSS = `
|
|
26817
|
-
.vela-ui, .vela-ui-layer {
|
|
26818
|
-
${STATIC_DECLS}
|
|
26819
|
-
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
26820
|
-
box-sizing: border-box;
|
|
26821
|
-
}
|
|
26822
|
-
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
26823
|
-
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
26824
|
-
.vela-icon svg { display: block; }
|
|
26825
|
-
`;
|
|
26826
|
-
function applyThemeTokens(el, t) {
|
|
26827
|
-
const tokens = themeTokens(t);
|
|
26828
|
-
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
26829
|
-
}
|
|
26830
|
-
function applyPlotOverlayTokens(host, base, config) {
|
|
26831
|
-
const layout = config?.layout;
|
|
26832
|
-
const t = layout?.background && layout.textColor ? { ...base, background: layout.background, textColor: layout.textColor } : base;
|
|
26833
|
-
applyThemeTokens(host, t);
|
|
26834
|
-
}
|
|
26835
|
-
function ensureUIHost(el, theme) {
|
|
26836
|
-
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
26837
|
-
el.classList.add("vela-ui");
|
|
26838
|
-
if (theme) applyThemeTokens(el, theme);
|
|
26839
|
-
}
|
|
26840
|
-
|
|
26841
|
-
// src/ui/icons.ts
|
|
26842
|
-
function iconEl(id, doc = document) {
|
|
26843
|
-
const span = doc.createElement("span");
|
|
26844
|
-
span.className = "vela-icon";
|
|
26845
|
-
span.setAttribute("aria-hidden", "true");
|
|
26846
|
-
const svg = iconMarkup(id);
|
|
26847
|
-
if (svg) span.innerHTML = svg;
|
|
26848
|
-
return span;
|
|
26849
|
-
}
|
|
26850
27970
|
function runMachine(machine5, props, render) {
|
|
26851
27971
|
const m = new vanilla.VanillaMachine(machine5, props);
|
|
26852
27972
|
const unsub = m.subscribe(render);
|
|
@@ -27088,7 +28208,10 @@ function menuController(opts) {
|
|
|
27088
28208
|
props: {
|
|
27089
28209
|
id: opts.id ?? nextUid("vela-menu"),
|
|
27090
28210
|
ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
|
|
27091
|
-
positioning: {
|
|
28211
|
+
positioning: {
|
|
28212
|
+
placement: opts.placement ?? "bottom-start",
|
|
28213
|
+
...opts.getAnchorRect ? { getAnchorRect: () => opts.getAnchorRect() } : {}
|
|
28214
|
+
},
|
|
27092
28215
|
onSelect: (d) => opts.onSelect?.(d.value),
|
|
27093
28216
|
onOpenChange: (d) => opts.onOpenChange?.(d.open)
|
|
27094
28217
|
},
|
|
@@ -27170,6 +28293,22 @@ var MENU_CSS = `
|
|
|
27170
28293
|
}
|
|
27171
28294
|
.vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
|
|
27172
28295
|
.vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
|
|
28296
|
+
/* Favorite star (rows carrying \`favorite\`): reserved space always \u2014 it fades in on row
|
|
28297
|
+
hover instead of shifting the layout \u2014 and a starred row keeps its filled star visible.
|
|
28298
|
+
Same control language as the drawing toolbar's flyout stars. */
|
|
28299
|
+
.vela-menu-item .vela-menu-star {
|
|
28300
|
+
order: 98;
|
|
28301
|
+
/* Pad the 16px icon slot into a bigger hit target without moving the row's layout. */
|
|
28302
|
+
margin: -3px -5px -3px 0;
|
|
28303
|
+
padding: 3px 5px;
|
|
28304
|
+
box-sizing: content-box;
|
|
28305
|
+
border-radius: var(--vela-radius-sm);
|
|
28306
|
+
opacity: 0;
|
|
28307
|
+
transition: opacity 0.1s ease, background 0.1s ease;
|
|
28308
|
+
}
|
|
28309
|
+
.vela-menu-item[data-highlighted] .vela-menu-star { opacity: 0.55; }
|
|
28310
|
+
.vela-menu-item .vela-menu-star:hover { opacity: 1; background: var(--vela-hover-strong); }
|
|
28311
|
+
.vela-menu-item .vela-menu-star.vela-fav { opacity: 0.95; color: var(--vela-highlight); }
|
|
27173
28312
|
.vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
|
|
27174
28313
|
/* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
|
|
27175
28314
|
list is open so the path you came down remains readable. */
|
|
@@ -27188,9 +28327,13 @@ var Surface = class _Surface {
|
|
|
27188
28327
|
this.items = [];
|
|
27189
28328
|
/** Branch item id → the surface it opens. */
|
|
27190
28329
|
this.subs = /* @__PURE__ */ new Map();
|
|
28330
|
+
/** Trigger rect captured when this level opened — the list stays put if the
|
|
28331
|
+
* trigger then moves (favorite chips shifting the caret). */
|
|
28332
|
+
this.pinnedAnchor = null;
|
|
27191
28333
|
this.doc = doc;
|
|
27192
28334
|
this.host = opts.host;
|
|
27193
28335
|
this.onSelect = opts.onSelect;
|
|
28336
|
+
this.onFavorite = opts.onFavorite;
|
|
27194
28337
|
this.positioner = doc.createElement("div");
|
|
27195
28338
|
this.positioner.className = "vela-ui-layer";
|
|
27196
28339
|
this.list = doc.createElement("ul");
|
|
@@ -27198,16 +28341,25 @@ var Surface = class _Surface {
|
|
|
27198
28341
|
if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
|
|
27199
28342
|
this.positioner.appendChild(this.list);
|
|
27200
28343
|
this.host.appendChild(this.positioner);
|
|
28344
|
+
const trigger = opts.trigger;
|
|
27201
28345
|
this.ctrl = menuController({
|
|
27202
28346
|
items: [],
|
|
27203
28347
|
id: opts.id,
|
|
27204
28348
|
placement: opts.placement,
|
|
27205
28349
|
onSelect: (id) => this.onSelect(id),
|
|
27206
|
-
|
|
28350
|
+
getAnchorRect: () => this.pinnedAnchor,
|
|
28351
|
+
onOpenChange: (open2) => {
|
|
28352
|
+
if (open2 && trigger) {
|
|
28353
|
+
const r = trigger.getBoundingClientRect();
|
|
28354
|
+
this.pinnedAnchor = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
28355
|
+
} else {
|
|
28356
|
+
this.pinnedAnchor = null;
|
|
28357
|
+
}
|
|
28358
|
+
opts.onOpenChange?.(open2);
|
|
28359
|
+
},
|
|
27207
28360
|
triggerId: opts.triggerId
|
|
27208
28361
|
});
|
|
27209
28362
|
this.mid = String(this.ctrl.props.id);
|
|
27210
|
-
const trigger = opts.trigger;
|
|
27211
28363
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
27212
28364
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
27213
28365
|
const api = this.ctrl.connect(service);
|
|
@@ -27287,6 +28439,25 @@ var Surface = class _Surface {
|
|
|
27287
28439
|
hint.textContent = item.hint;
|
|
27288
28440
|
li.appendChild(hint);
|
|
27289
28441
|
}
|
|
28442
|
+
if (item.favorite !== void 0 && this.onFavorite) {
|
|
28443
|
+
const on = item.favorite;
|
|
28444
|
+
const star = iconEl(on ? "star-filled" : "star", doc);
|
|
28445
|
+
star.classList.add("vela-menu-star");
|
|
28446
|
+
if (on) star.classList.add("vela-fav");
|
|
28447
|
+
star.removeAttribute("aria-hidden");
|
|
28448
|
+
star.setAttribute("role", "button");
|
|
28449
|
+
star.setAttribute("aria-label", on ? "Remove from favorites" : "Add to favorites");
|
|
28450
|
+
star.setAttribute("aria-pressed", on ? "true" : "false");
|
|
28451
|
+
for (const ev of ["pointerdown", "pointerup", "mousedown", "mouseup"]) {
|
|
28452
|
+
star.addEventListener(ev, (e) => e.stopPropagation());
|
|
28453
|
+
}
|
|
28454
|
+
star.addEventListener("click", (e) => {
|
|
28455
|
+
e.stopPropagation();
|
|
28456
|
+
e.preventDefault();
|
|
28457
|
+
this.onFavorite?.(item.id, !on);
|
|
28458
|
+
});
|
|
28459
|
+
li.appendChild(star);
|
|
28460
|
+
}
|
|
27290
28461
|
if (item.toggle) {
|
|
27291
28462
|
const sw = doc.createElement("span");
|
|
27292
28463
|
sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
|
|
@@ -27302,6 +28473,7 @@ var Surface = class _Surface {
|
|
|
27302
28473
|
host: this.host,
|
|
27303
28474
|
placement: "right-start",
|
|
27304
28475
|
onSelect: this.onSelect,
|
|
28476
|
+
onFavorite: this.onFavorite,
|
|
27305
28477
|
id: `${this.mid}--${item.id}`
|
|
27306
28478
|
});
|
|
27307
28479
|
sub.setItems(item.submenu ?? []);
|
|
@@ -27325,7 +28497,8 @@ var Menu = class {
|
|
|
27325
28497
|
trigger: opts.trigger,
|
|
27326
28498
|
triggerId: opts.triggerId,
|
|
27327
28499
|
id: opts.id,
|
|
27328
|
-
minWidth: opts.minWidth
|
|
28500
|
+
minWidth: opts.minWidth,
|
|
28501
|
+
onFavorite: opts.onFavorite
|
|
27329
28502
|
});
|
|
27330
28503
|
this.root.setItems(opts.items);
|
|
27331
28504
|
}
|
|
@@ -27829,7 +29002,7 @@ var Drawer = class {
|
|
|
27829
29002
|
};
|
|
27830
29003
|
|
|
27831
29004
|
// src/widget/layout-picker.ts
|
|
27832
|
-
var
|
|
29005
|
+
var STYLE_ID6 = "vela-widget-layout-picker-v14";
|
|
27833
29006
|
var CSS3 = `
|
|
27834
29007
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
27835
29008
|
.vela-lp {
|
|
@@ -27934,7 +29107,7 @@ var CSS3 = `
|
|
|
27934
29107
|
`;
|
|
27935
29108
|
var GRID = 4;
|
|
27936
29109
|
function layoutGridCanvas(doc) {
|
|
27937
|
-
injectStyles(
|
|
29110
|
+
injectStyles(STYLE_ID6, CSS3, doc);
|
|
27938
29111
|
const el = doc.createElement("div");
|
|
27939
29112
|
el.className = "vela-lp-grid";
|
|
27940
29113
|
const squares = [];
|
|
@@ -27974,7 +29147,7 @@ var LayoutPicker = class {
|
|
|
27974
29147
|
};
|
|
27975
29148
|
this.opts = opts;
|
|
27976
29149
|
this.doc = opts.host.ownerDocument;
|
|
27977
|
-
injectStyles(
|
|
29150
|
+
injectStyles(STYLE_ID6, CSS3, this.doc);
|
|
27978
29151
|
const doc = this.doc;
|
|
27979
29152
|
this.layer = doc.createElement("div");
|
|
27980
29153
|
this.layer.className = "vela-ui-layer vela-lp-layer";
|
|
@@ -28287,6 +29460,24 @@ function timeframeMs(value) {
|
|
|
28287
29460
|
if (!Number.isFinite(count) || count < 1) return NaN;
|
|
28288
29461
|
return count * UNIT_MS[key];
|
|
28289
29462
|
}
|
|
29463
|
+
function favoriteTimeframeChips(favorites) {
|
|
29464
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29465
|
+
const out = [];
|
|
29466
|
+
for (const tf of favorites) {
|
|
29467
|
+
if (seen.has(tf)) continue;
|
|
29468
|
+
seen.add(tf);
|
|
29469
|
+
out.push(tf);
|
|
29470
|
+
}
|
|
29471
|
+
return out.sort((a, b) => {
|
|
29472
|
+
const ma = timeframeMs(a);
|
|
29473
|
+
const mb = timeframeMs(b);
|
|
29474
|
+
const aOk = Number.isFinite(ma);
|
|
29475
|
+
const bOk = Number.isFinite(mb);
|
|
29476
|
+
if (aOk && bOk && ma !== mb) return ma - mb;
|
|
29477
|
+
if (aOk !== bOk) return aOk ? -1 : 1;
|
|
29478
|
+
return 0;
|
|
29479
|
+
});
|
|
29480
|
+
}
|
|
28290
29481
|
function timeframeLabel(value) {
|
|
28291
29482
|
const parsed = parseTimeframe(value);
|
|
28292
29483
|
if (!parsed.valid) return value;
|
|
@@ -28301,7 +29492,7 @@ function timeframeLabel(value) {
|
|
|
28301
29492
|
}
|
|
28302
29493
|
|
|
28303
29494
|
// src/widget/topbar.ts
|
|
28304
|
-
var
|
|
29495
|
+
var STYLE_ID7 = "vela-topbar";
|
|
28305
29496
|
var CSS4 = `
|
|
28306
29497
|
.vela-widget-topbar {
|
|
28307
29498
|
display: flex;
|
|
@@ -28339,6 +29530,36 @@ var CSS4 = `
|
|
|
28339
29530
|
color: var(--vela-fg-bright);
|
|
28340
29531
|
}
|
|
28341
29532
|
.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); }
|
|
29533
|
+
/* Timeframe cluster: duration-sorted favorite chips, highlight in place, caret
|
|
29534
|
+
opening the full dropdown. With no favorites the caret is the merged trigger
|
|
29535
|
+
(label + chevron). An unstarred current value sits as an extra chip by the caret. */
|
|
29536
|
+
.vela-widget-tf-group { display: inline-flex; align-items: center; gap: 2px; }
|
|
29537
|
+
.vela-widget-tf-chips { display: inline-flex; align-items: center; gap: 2px; }
|
|
29538
|
+
.vela-widget-tf-chips:empty { display: none; }
|
|
29539
|
+
.vela-widget-tf[data-current='1'] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
29540
|
+
.vela-widget-tf-caret {
|
|
29541
|
+
all: unset;
|
|
29542
|
+
display: inline-flex;
|
|
29543
|
+
align-items: center;
|
|
29544
|
+
justify-content: center;
|
|
29545
|
+
width: 18px;
|
|
29546
|
+
height: 30px;
|
|
29547
|
+
border-radius: 4px;
|
|
29548
|
+
cursor: pointer;
|
|
29549
|
+
color: var(--vela-fg-muted);
|
|
29550
|
+
}
|
|
29551
|
+
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
29552
|
+
.vela-widget-tf-caret[data-solo='1'] {
|
|
29553
|
+
width: auto;
|
|
29554
|
+
padding: 0 6px 0 9px;
|
|
29555
|
+
gap: 4px;
|
|
29556
|
+
color: var(--vela-fg-bright);
|
|
29557
|
+
background: var(--vela-hover-strong);
|
|
29558
|
+
font-size: 13px;
|
|
29559
|
+
font-weight: 550;
|
|
29560
|
+
white-space: nowrap;
|
|
29561
|
+
}
|
|
29562
|
+
.vela-widget-topbar .vela-widget-tf-caret .vela-icon { font-size: 14px; width: 14px; height: 14px; }
|
|
28342
29563
|
.vela-widget-topbar .vela-icon { color: inherit; font-size: 16px; width: 16px; height: 16px; }
|
|
28343
29564
|
/* Width is set in syncHairlines() to exactly one device pixel \u2014 a CSS 1px at
|
|
28344
29565
|
fractional DPR (1.25, 1.5\u2026) straddles two physical pixels and siblings end
|
|
@@ -28436,16 +29657,23 @@ var Topbar = class {
|
|
|
28436
29657
|
this.timeframe = opts.timeframe;
|
|
28437
29658
|
this.priceStyle = opts.priceStyle;
|
|
28438
29659
|
const doc = host.ownerDocument;
|
|
28439
|
-
injectStyles(
|
|
29660
|
+
injectStyles(STYLE_ID7, CSS4, doc);
|
|
28440
29661
|
this.el = doc.createElement("div");
|
|
28441
29662
|
this.el.className = "vela-widget-topbar";
|
|
28442
29663
|
this.symbolEl = doc.createElement("button");
|
|
28443
29664
|
this.symbolEl.className = "vela-widget-symbol";
|
|
28444
29665
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
28445
29666
|
if (opts.onSymbolClick) this.symbolEl.addEventListener("click", opts.onSymbolClick);
|
|
28446
|
-
this.
|
|
28447
|
-
this.
|
|
28448
|
-
this.
|
|
29667
|
+
this.tfFavs = [...opts.timeframeFavorites ?? []];
|
|
29668
|
+
this.tfChipsHost = doc.createElement("span");
|
|
29669
|
+
this.tfChipsHost.className = "vela-widget-tf-chips";
|
|
29670
|
+
this.tfCaret = doc.createElement("button");
|
|
29671
|
+
this.tfCaret.className = "vela-widget-tf-caret";
|
|
29672
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
29673
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
29674
|
+
const tfGroup = doc.createElement("span");
|
|
29675
|
+
tfGroup.className = "vela-widget-tf-group";
|
|
29676
|
+
tfGroup.append(this.tfChipsHost, this.tfCaret);
|
|
28449
29677
|
this.styleButton = doc.createElement("button");
|
|
28450
29678
|
this.styleButton.className = "vela-widget-style";
|
|
28451
29679
|
this.renderStyleButton(doc);
|
|
@@ -28485,19 +29713,20 @@ var Topbar = class {
|
|
|
28485
29713
|
d.className = "vela-sep";
|
|
28486
29714
|
return d;
|
|
28487
29715
|
};
|
|
28488
|
-
const leading = [this.symbolEl, sep(),
|
|
29716
|
+
const leading = [this.symbolEl, sep(), tfGroup, sep(), this.styleButton, sep()];
|
|
28489
29717
|
if (this.layoutButton) leading.push(this.layoutButton, sep());
|
|
28490
29718
|
if (indicatorsBtn) leading.push(indicatorsBtn, sep());
|
|
28491
29719
|
this.leftActionsSep = sep();
|
|
28492
29720
|
this.leftActionsSep.hidden = true;
|
|
28493
29721
|
this.el.append(...leading, this.leftActionsHost, this.leftActionsSep, this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
|
|
28494
29722
|
host.appendChild(this.el);
|
|
29723
|
+
this.renderTfChips();
|
|
28495
29724
|
this.onHairlineSync();
|
|
28496
29725
|
this.hairlineRo = new ResizeObserver(this.onHairlineSync);
|
|
28497
29726
|
this.hairlineRo.observe(this.el);
|
|
28498
29727
|
doc.defaultView?.addEventListener("resize", this.onHairlineSync);
|
|
28499
29728
|
this.renderActions();
|
|
28500
|
-
this.tooltips.push(new Tooltip(this.
|
|
29729
|
+
this.tooltips.push(new Tooltip(this.tfCaret, { content: "Timeframe", triggerId: "vela-topbar-tf", host }));
|
|
28501
29730
|
this.tooltips.push(new Tooltip(this.styleButton, { content: "Chart style", triggerId: "vela-topbar-style", host }));
|
|
28502
29731
|
if (this.layoutButton && opts.layout) {
|
|
28503
29732
|
this.tooltips.push(new Tooltip(this.layoutButton, { content: "Layout", triggerId: "vela-topbar-layout", host }));
|
|
@@ -28514,11 +29743,12 @@ var Topbar = class {
|
|
|
28514
29743
|
});
|
|
28515
29744
|
}
|
|
28516
29745
|
this.tfMenu = new Menu({
|
|
28517
|
-
trigger: this.
|
|
29746
|
+
trigger: this.tfCaret,
|
|
28518
29747
|
triggerId: "vela-topbar-tf",
|
|
28519
29748
|
host,
|
|
28520
29749
|
items: this.tfItems(),
|
|
28521
29750
|
onSelect: (id) => opts.onTimeframe(id),
|
|
29751
|
+
onFavorite: (id, on) => opts.onTimeframeFavorite?.(id, on),
|
|
28522
29752
|
// Timeframe labels are two-or-three characters ("1m", "4h", "1D") — the
|
|
28523
29753
|
// stylesheet's default min-width would leave the list mostly empty.
|
|
28524
29754
|
minWidth: "84px"
|
|
@@ -28536,8 +29766,48 @@ var Topbar = class {
|
|
|
28536
29766
|
}
|
|
28537
29767
|
setTimeframe(tf) {
|
|
28538
29768
|
this.timeframe = tf;
|
|
28539
|
-
this.tfButton.textContent = timeframeLabel(tf);
|
|
28540
29769
|
this.tfMenu.setItems(this.tfItems());
|
|
29770
|
+
this.renderTfChips();
|
|
29771
|
+
}
|
|
29772
|
+
/** Reflect the favorite-timeframe set — the quick-switch chips and the dropdown stars. */
|
|
29773
|
+
setTimeframeFavorites(favs) {
|
|
29774
|
+
this.tfFavs = [...favs];
|
|
29775
|
+
this.renderTfChips();
|
|
29776
|
+
this.tfMenu.setItems(this.tfItems());
|
|
29777
|
+
}
|
|
29778
|
+
/** Rebuild the quick-switch chips (current value changed, or the favorite set did). */
|
|
29779
|
+
renderTfChips() {
|
|
29780
|
+
const doc = this.el.ownerDocument;
|
|
29781
|
+
this.tfChipsHost.replaceChildren();
|
|
29782
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
29783
|
+
const chips = stars ? favoriteTimeframeChips(this.tfFavs) : [];
|
|
29784
|
+
const currentIsFav = chips.includes(this.timeframe);
|
|
29785
|
+
const shown = currentIsFav || chips.length === 0 ? chips : [...chips, this.timeframe];
|
|
29786
|
+
for (const tf of shown) {
|
|
29787
|
+
const b = doc.createElement("button");
|
|
29788
|
+
b.className = "vela-widget-tf";
|
|
29789
|
+
const label = timeframeLabel(tf);
|
|
29790
|
+
b.textContent = label;
|
|
29791
|
+
if (tf === this.timeframe) {
|
|
29792
|
+
b.dataset.current = "1";
|
|
29793
|
+
b.setAttribute("aria-current", "true");
|
|
29794
|
+
} else {
|
|
29795
|
+
b.setAttribute("aria-label", `Switch timeframe to ${label}`);
|
|
29796
|
+
b.addEventListener("click", () => this.opts.onTimeframe(tf));
|
|
29797
|
+
}
|
|
29798
|
+
this.tfChipsHost.appendChild(b);
|
|
29799
|
+
}
|
|
29800
|
+
this.tfCaret.replaceChildren();
|
|
29801
|
+
if (chips.length === 0) {
|
|
29802
|
+
this.tfCaret.dataset.solo = "1";
|
|
29803
|
+
this.tfCaret.append(doc.createTextNode(timeframeLabel(this.timeframe)), iconEl("chevron-down", doc));
|
|
29804
|
+
this.tfCaret.setAttribute("aria-label", `Timeframe \u2014 ${timeframeLabel(this.timeframe)}`);
|
|
29805
|
+
} else {
|
|
29806
|
+
delete this.tfCaret.dataset.solo;
|
|
29807
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
29808
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
29809
|
+
}
|
|
29810
|
+
this.onHairlineSync();
|
|
28541
29811
|
}
|
|
28542
29812
|
setPriceStyle(style) {
|
|
28543
29813
|
this.priceStyle = style;
|
|
@@ -28615,9 +29885,9 @@ var Topbar = class {
|
|
|
28615
29885
|
}
|
|
28616
29886
|
/** Reflect a docked side panel's open state on its button — the panels toggle each other,
|
|
28617
29887
|
* so the dock pushes the state rather than the button assuming it. */
|
|
28618
|
-
setPanelActive(id,
|
|
29888
|
+
setPanelActive(id, open2) {
|
|
28619
29889
|
const btn = this.panelBtns.get(id);
|
|
28620
|
-
if (btn) btn.dataset.active =
|
|
29890
|
+
if (btn) btn.dataset.active = open2 ? "1" : "";
|
|
28621
29891
|
}
|
|
28622
29892
|
destroy() {
|
|
28623
29893
|
this.hairlineRo?.disconnect();
|
|
@@ -28655,7 +29925,13 @@ var Topbar = class {
|
|
|
28655
29925
|
}
|
|
28656
29926
|
}
|
|
28657
29927
|
tfItems() {
|
|
28658
|
-
|
|
29928
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
29929
|
+
return this.opts.timeframes.map((tf) => ({
|
|
29930
|
+
id: tf,
|
|
29931
|
+
label: timeframeLabel(tf),
|
|
29932
|
+
checked: tf === this.timeframe,
|
|
29933
|
+
...stars ? { favorite: this.tfFavs.includes(tf) } : {}
|
|
29934
|
+
}));
|
|
28659
29935
|
}
|
|
28660
29936
|
styleItems() {
|
|
28661
29937
|
return priceStyleIds().map((id, i) => ({
|
|
@@ -28678,10 +29954,10 @@ function fmtPrice(n, dp) {
|
|
|
28678
29954
|
const d = dp ?? decimalsFor(n);
|
|
28679
29955
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
28680
29956
|
}
|
|
28681
|
-
function fmtChange(
|
|
28682
|
-
if (
|
|
28683
|
-
const diff = close -
|
|
28684
|
-
const pct = diff /
|
|
29957
|
+
function fmtChange(open2, close) {
|
|
29958
|
+
if (open2 == null || close == null || !Number.isFinite(open2) || !Number.isFinite(close) || open2 === 0) return "";
|
|
29959
|
+
const diff = close - open2;
|
|
29960
|
+
const pct = diff / open2 * 100;
|
|
28685
29961
|
const sign = diff >= 0 ? "+" : "";
|
|
28686
29962
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
28687
29963
|
}
|
|
@@ -28730,7 +30006,7 @@ function tickerIconEl(doc, base, name, className) {
|
|
|
28730
30006
|
}
|
|
28731
30007
|
|
|
28732
30008
|
// src/widget/statusline.ts
|
|
28733
|
-
var
|
|
30009
|
+
var STYLE_ID8 = "vela-widget-statusline";
|
|
28734
30010
|
var CSS5 = `
|
|
28735
30011
|
.vela-statusline {
|
|
28736
30012
|
position: absolute;
|
|
@@ -28922,7 +30198,7 @@ var Statusline = class {
|
|
|
28922
30198
|
this.fitMode = false;
|
|
28923
30199
|
this.fitRO = null;
|
|
28924
30200
|
const doc = host.ownerDocument;
|
|
28925
|
-
injectStyles(
|
|
30201
|
+
injectStyles(STYLE_ID8, CSS5, doc);
|
|
28926
30202
|
host.classList.add("vela-has-statusline");
|
|
28927
30203
|
this.el = doc.createElement("div");
|
|
28928
30204
|
this.el.className = "vela-statusline";
|
|
@@ -29097,7 +30373,7 @@ var Statusline = class {
|
|
|
29097
30373
|
var MAX_FONT_PX = 36;
|
|
29098
30374
|
var MIN_FONT_PX = 12;
|
|
29099
30375
|
var FILL = 0.9;
|
|
29100
|
-
var
|
|
30376
|
+
var STYLE_ID9 = "vela-widget-watermark";
|
|
29101
30377
|
var CSS6 = `
|
|
29102
30378
|
.vela-watermark {
|
|
29103
30379
|
position: absolute;
|
|
@@ -29136,7 +30412,7 @@ var Watermark = class {
|
|
|
29136
30412
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
29137
30413
|
* fires during chart construction, before any subscriber can see it. */
|
|
29138
30414
|
this.loading = true;
|
|
29139
|
-
injectStyles(
|
|
30415
|
+
injectStyles(STYLE_ID9, CSS6, host.ownerDocument);
|
|
29140
30416
|
this.el = host.ownerDocument.createElement("div");
|
|
29141
30417
|
this.el.className = "vela-watermark";
|
|
29142
30418
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -29200,7 +30476,7 @@ var RANGE_PRESETS = [
|
|
|
29200
30476
|
{ id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
|
|
29201
30477
|
// everything the provider serves
|
|
29202
30478
|
];
|
|
29203
|
-
var
|
|
30479
|
+
var STYLE_ID10 = "vela-widget-bottombar";
|
|
29204
30480
|
var CSS7 = `
|
|
29205
30481
|
.vela-widget-bottombar {
|
|
29206
30482
|
display: flex;
|
|
@@ -29276,7 +30552,7 @@ var Bottombar = class {
|
|
|
29276
30552
|
this.timer = null;
|
|
29277
30553
|
this.timezone = opts.timezone;
|
|
29278
30554
|
const doc = host.ownerDocument;
|
|
29279
|
-
injectStyles(
|
|
30555
|
+
injectStyles(STYLE_ID10, CSS7, doc);
|
|
29280
30556
|
this.el = doc.createElement("div");
|
|
29281
30557
|
this.el.className = "vela-widget-bottombar";
|
|
29282
30558
|
for (const preset of RANGE_PRESETS) {
|
|
@@ -29387,10 +30663,11 @@ function parseQuery(raw, venues) {
|
|
|
29387
30663
|
function onlyOne(matches) {
|
|
29388
30664
|
return matches.length === 1 ? matches[0] : null;
|
|
29389
30665
|
}
|
|
30666
|
+
var venueOf = (s) => s.prefix ?? s.provider;
|
|
29390
30667
|
function filterSymbols(list, query, limit = 100) {
|
|
29391
|
-
const venues = [...new Set(list.
|
|
30668
|
+
const venues = [...new Set(list.flatMap((s) => [venueOf(s)?.toLowerCase(), s.provider?.toLowerCase()]).filter((p) => !!p))];
|
|
29392
30669
|
const { scope, term } = parseQuery(query, venues);
|
|
29393
|
-
const pool = scope ? list.filter((s) => s.provider?.toLowerCase() === scope) : list;
|
|
30670
|
+
const pool = scope ? list.filter((s) => venueOf(s)?.toLowerCase() === scope || s.provider?.toLowerCase() === scope) : list;
|
|
29394
30671
|
const q = term.toUpperCase();
|
|
29395
30672
|
if (!q) {
|
|
29396
30673
|
if (scope) return [...pool].sort((a, b) => a.ticker.localeCompare(b.ticker)).slice(0, limit);
|
|
@@ -29409,12 +30686,12 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
29409
30686
|
if (t.startsWith(q)) prefix.push(s);
|
|
29410
30687
|
else if (t.includes(q)) substr.push(s);
|
|
29411
30688
|
else if ((s.description ?? "").toUpperCase().includes(q)) desc.push(s);
|
|
29412
|
-
else if (!scope && s.provider?.toLowerCase().includes(qLower)) venue.push(s);
|
|
30689
|
+
else if (!scope && (venueOf(s)?.toLowerCase().includes(qLower) || s.provider?.toLowerCase().includes(qLower))) venue.push(s);
|
|
29413
30690
|
if (prefix.length >= limit) break;
|
|
29414
30691
|
}
|
|
29415
30692
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
29416
30693
|
}
|
|
29417
|
-
var
|
|
30694
|
+
var STYLE_ID11 = "vela-widget-symbolpicker";
|
|
29418
30695
|
var CSS8 = `
|
|
29419
30696
|
.vela-sp-searchrow {
|
|
29420
30697
|
display: flex;
|
|
@@ -29512,7 +30789,7 @@ var SymbolPicker = class {
|
|
|
29512
30789
|
this.seed = "";
|
|
29513
30790
|
this.activeTab = "All";
|
|
29514
30791
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
29515
|
-
injectStyles(
|
|
30792
|
+
injectStyles(STYLE_ID11, CSS8, doc);
|
|
29516
30793
|
this.input = doc.createElement("input");
|
|
29517
30794
|
this.input.className = "vela-sp-input";
|
|
29518
30795
|
this.input.placeholder = "Search symbol\u2026";
|
|
@@ -29542,8 +30819,8 @@ var SymbolPicker = class {
|
|
|
29542
30819
|
host: opts.host,
|
|
29543
30820
|
closeOnInteractOutside: true,
|
|
29544
30821
|
content: (body) => body.append(searchRow, this.tabs, this.list),
|
|
29545
|
-
onOpenChange: (
|
|
29546
|
-
if (
|
|
30822
|
+
onOpenChange: (open2) => {
|
|
30823
|
+
if (open2) {
|
|
29547
30824
|
this.input.value = this.seed;
|
|
29548
30825
|
this.seed = "";
|
|
29549
30826
|
this.refresh();
|
|
@@ -29552,7 +30829,7 @@ var SymbolPicker = class {
|
|
|
29552
30829
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length);
|
|
29553
30830
|
}, 0);
|
|
29554
30831
|
}
|
|
29555
|
-
opts.onOpenChange?.(
|
|
30832
|
+
opts.onOpenChange?.(open2);
|
|
29556
30833
|
}
|
|
29557
30834
|
});
|
|
29558
30835
|
this.input.addEventListener("input", () => this.refresh());
|
|
@@ -29561,14 +30838,14 @@ var SymbolPicker = class {
|
|
|
29561
30838
|
else if (e.key === "ArrowUp") this.moveHighlight(-1);
|
|
29562
30839
|
else if (e.key === "Enter") {
|
|
29563
30840
|
const pick = this.rows[this.highlighted];
|
|
29564
|
-
if (pick) this.select(pick.ticker, pick.provider, opts.onSelect);
|
|
30841
|
+
if (pick) this.select(pick.ticker, pick.prefix ?? pick.provider, opts.onSelect);
|
|
29565
30842
|
return;
|
|
29566
30843
|
} else return;
|
|
29567
30844
|
e.preventDefault();
|
|
29568
30845
|
});
|
|
29569
30846
|
this.list.addEventListener("click", (e) => {
|
|
29570
30847
|
const row = e.target.closest(".vela-sp-row");
|
|
29571
|
-
if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.
|
|
30848
|
+
if (row?.dataset.ticker) this.select(row.dataset.ticker, row.dataset.venue, opts.onSelect);
|
|
29572
30849
|
});
|
|
29573
30850
|
}
|
|
29574
30851
|
/** Wire where symbols come from (re-called on every widget rebuild). */
|
|
@@ -29585,9 +30862,9 @@ var SymbolPicker = class {
|
|
|
29585
30862
|
destroy() {
|
|
29586
30863
|
this.dialog.destroy();
|
|
29587
30864
|
}
|
|
29588
|
-
select(ticker,
|
|
30865
|
+
select(ticker, venue, onSelect) {
|
|
29589
30866
|
this.close();
|
|
29590
|
-
onSelect(
|
|
30867
|
+
onSelect(venue ? `${venue}:${ticker}` : ticker);
|
|
29591
30868
|
}
|
|
29592
30869
|
moveHighlight(delta) {
|
|
29593
30870
|
if (!this.rows.length) return;
|
|
@@ -29620,7 +30897,8 @@ var SymbolPicker = class {
|
|
|
29620
30897
|
const row = doc.createElement("div");
|
|
29621
30898
|
row.className = "vela-sp-row";
|
|
29622
30899
|
row.dataset.ticker = s.ticker;
|
|
29623
|
-
|
|
30900
|
+
const venue = s.prefix ?? s.provider;
|
|
30901
|
+
if (venue) row.dataset.venue = venue;
|
|
29624
30902
|
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
29625
30903
|
const main = doc.createElement("span");
|
|
29626
30904
|
main.className = "vela-sp-main";
|
|
@@ -29632,11 +30910,11 @@ var SymbolPicker = class {
|
|
|
29632
30910
|
d.textContent = s.description ?? (s.type ?? "");
|
|
29633
30911
|
main.append(t, d);
|
|
29634
30912
|
row.append(av, main);
|
|
29635
|
-
if (
|
|
30913
|
+
if (venue) {
|
|
29636
30914
|
const badge = doc.createElement("span");
|
|
29637
30915
|
badge.className = "vela-sp-badge";
|
|
29638
|
-
badge.dataset.p = s.provider;
|
|
29639
|
-
badge.textContent =
|
|
30916
|
+
badge.dataset.p = s.provider ?? venue;
|
|
30917
|
+
badge.textContent = venue;
|
|
29640
30918
|
row.appendChild(badge);
|
|
29641
30919
|
}
|
|
29642
30920
|
this.list.appendChild(row);
|
|
@@ -29834,7 +31112,7 @@ function treeIsEmpty(panes) {
|
|
|
29834
31112
|
}
|
|
29835
31113
|
|
|
29836
31114
|
// src/widget/side-panel.ts
|
|
29837
|
-
var
|
|
31115
|
+
var STYLE_ID12 = "vela-widget-sidepanel";
|
|
29838
31116
|
var DEFAULT_PANEL_WIDTH = 280;
|
|
29839
31117
|
var DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
29840
31118
|
var DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
@@ -29938,7 +31216,7 @@ var SidePanel = class {
|
|
|
29938
31216
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
29939
31217
|
this.onWidthChange = null;
|
|
29940
31218
|
const doc = host.ownerDocument;
|
|
29941
|
-
injectStyles(
|
|
31219
|
+
injectStyles(STYLE_ID12, CSS9, doc);
|
|
29942
31220
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
29943
31221
|
this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
|
|
29944
31222
|
this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
|
|
@@ -29970,10 +31248,10 @@ var SidePanel = class {
|
|
|
29970
31248
|
return !this.el.hidden;
|
|
29971
31249
|
}
|
|
29972
31250
|
/** Open/close the panel — a bare call flips it. */
|
|
29973
|
-
toggle(
|
|
29974
|
-
if (
|
|
29975
|
-
this.el.hidden = !
|
|
29976
|
-
this.onOpenChange?.(
|
|
31251
|
+
toggle(open2 = this.el.hidden) {
|
|
31252
|
+
if (open2 === !this.el.hidden) return;
|
|
31253
|
+
this.el.hidden = !open2;
|
|
31254
|
+
this.onOpenChange?.(open2);
|
|
29977
31255
|
}
|
|
29978
31256
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
29979
31257
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -30047,7 +31325,7 @@ var SidePanel = class {
|
|
|
30047
31325
|
};
|
|
30048
31326
|
|
|
30049
31327
|
// src/widget/object-tree.ts
|
|
30050
|
-
var
|
|
31328
|
+
var STYLE_ID13 = "vela-widget-objtree";
|
|
30051
31329
|
var CSS10 = `
|
|
30052
31330
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
30053
31331
|
.vela-ot-pane {
|
|
@@ -30286,7 +31564,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
30286
31564
|
this.menu = null;
|
|
30287
31565
|
this.menuActions = /* @__PURE__ */ new Map();
|
|
30288
31566
|
this.drag = null;
|
|
30289
|
-
injectStyles(
|
|
31567
|
+
injectStyles(STYLE_ID13, CSS10, host.ownerDocument);
|
|
30290
31568
|
this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
30291
31569
|
this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
30292
31570
|
}
|
|
@@ -30296,9 +31574,9 @@ var ObjectTree = class extends SidePanel {
|
|
|
30296
31574
|
set groups(next) {
|
|
30297
31575
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
30298
31576
|
}
|
|
30299
|
-
toggle(
|
|
30300
|
-
super.toggle(
|
|
30301
|
-
if (
|
|
31577
|
+
toggle(open2 = this.el.hidden) {
|
|
31578
|
+
super.toggle(open2);
|
|
31579
|
+
if (open2) this.refresh();
|
|
30302
31580
|
}
|
|
30303
31581
|
setSymbol(symbol) {
|
|
30304
31582
|
this.symbolName = parseSymbol(symbol).ticker;
|
|
@@ -31268,7 +32546,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
31268
32546
|
};
|
|
31269
32547
|
|
|
31270
32548
|
// src/widget/data-window.ts
|
|
31271
|
-
var
|
|
32549
|
+
var STYLE_ID14 = "vela-widget-datawindow";
|
|
31272
32550
|
var CSS11 = `
|
|
31273
32551
|
.vela-dw-group {
|
|
31274
32552
|
padding: 10px 8px 4px;
|
|
@@ -31328,11 +32606,11 @@ var DataWindow = class extends SidePanel {
|
|
|
31328
32606
|
super(host, "Data window", "vela-dw");
|
|
31329
32607
|
this.chart = null;
|
|
31330
32608
|
this.unsubs = [];
|
|
31331
|
-
injectStyles(
|
|
32609
|
+
injectStyles(STYLE_ID14, CSS11, host.ownerDocument);
|
|
31332
32610
|
}
|
|
31333
|
-
toggle(
|
|
31334
|
-
super.toggle(
|
|
31335
|
-
if (
|
|
32611
|
+
toggle(open2 = this.el.hidden) {
|
|
32612
|
+
super.toggle(open2);
|
|
32613
|
+
if (open2) this.refresh();
|
|
31336
32614
|
}
|
|
31337
32615
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
31338
32616
|
onChart(chart) {
|
|
@@ -31446,8 +32724,8 @@ var PanelDock = class {
|
|
|
31446
32724
|
}
|
|
31447
32725
|
}
|
|
31448
32726
|
/** Open/close one panel by id — a bare call flips it. Unknown ids are ignored. */
|
|
31449
|
-
toggle(id,
|
|
31450
|
-
this.entries.find((e) => e.id === id)?.panel.toggle(
|
|
32727
|
+
toggle(id, open2) {
|
|
32728
|
+
this.entries.find((e) => e.id === id)?.panel.toggle(open2);
|
|
31451
32729
|
}
|
|
31452
32730
|
/** The open panel's id, or null when the column is closed. */
|
|
31453
32731
|
get openId() {
|
|
@@ -31461,8 +32739,8 @@ var PanelDock = class {
|
|
|
31461
32739
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
31462
32740
|
getState() {
|
|
31463
32741
|
const out = {};
|
|
31464
|
-
const
|
|
31465
|
-
if (
|
|
32742
|
+
const open2 = this.openId;
|
|
32743
|
+
if (open2) out.open = open2;
|
|
31466
32744
|
if (this.widths.size > 0) out.widths = Object.fromEntries(this.widths);
|
|
31467
32745
|
return out.open || out.widths ? out : null;
|
|
31468
32746
|
}
|
|
@@ -31495,13 +32773,13 @@ var PanelDock = class {
|
|
|
31495
32773
|
this.entries.sort((a, b) => a.order - b.order);
|
|
31496
32774
|
const stored = this.widths.get(entry.id);
|
|
31497
32775
|
if (stored !== void 0) entry.panel.setWidth(stored);
|
|
31498
|
-
entry.panel.onOpenChange = (
|
|
31499
|
-
if (
|
|
32776
|
+
entry.panel.onOpenChange = (open2) => {
|
|
32777
|
+
if (open2) {
|
|
31500
32778
|
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
31501
32779
|
this.pendingOpen = null;
|
|
31502
32780
|
}
|
|
31503
|
-
this.deps.chrome.setPanelActive(entry.id,
|
|
31504
|
-
if (
|
|
32781
|
+
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
32782
|
+
if (open2) entry.handle?.onOpen?.();
|
|
31505
32783
|
this.deps.changed?.();
|
|
31506
32784
|
};
|
|
31507
32785
|
entry.panel.onWidthChange = (px) => {
|
|
@@ -31530,7 +32808,7 @@ var PanelDock = class {
|
|
|
31530
32808
|
};
|
|
31531
32809
|
|
|
31532
32810
|
// src/widget/shortcuts-help.ts
|
|
31533
|
-
var
|
|
32811
|
+
var STYLE_ID15 = "vela-widget-shortcuts";
|
|
31534
32812
|
var CSS12 = `
|
|
31535
32813
|
.vela-sh-cat {
|
|
31536
32814
|
color: var(--vela-fg-muted);
|
|
@@ -31556,16 +32834,16 @@ var ShortcutsHelp = class {
|
|
|
31556
32834
|
constructor(keymap, host, onOpenChange) {
|
|
31557
32835
|
this.keymap = keymap;
|
|
31558
32836
|
const doc = (host ?? document.body).ownerDocument;
|
|
31559
|
-
injectStyles(
|
|
32837
|
+
injectStyles(STYLE_ID15, CSS12, doc);
|
|
31560
32838
|
this.list = doc.createElement("div");
|
|
31561
32839
|
this.dialog = new Dialog({
|
|
31562
32840
|
title: "Keyboard shortcuts",
|
|
31563
32841
|
host,
|
|
31564
32842
|
closeOnInteractOutside: true,
|
|
31565
32843
|
content: (body) => body.appendChild(this.list),
|
|
31566
|
-
onOpenChange: (
|
|
31567
|
-
if (
|
|
31568
|
-
onOpenChange?.(
|
|
32844
|
+
onOpenChange: (open2) => {
|
|
32845
|
+
if (open2) this.refresh();
|
|
32846
|
+
onOpenChange?.(open2);
|
|
31569
32847
|
}
|
|
31570
32848
|
});
|
|
31571
32849
|
}
|
|
@@ -31844,7 +33122,7 @@ var ChartContextMenu = class {
|
|
|
31844
33122
|
};
|
|
31845
33123
|
|
|
31846
33124
|
// src/widget/indicator-picker.ts
|
|
31847
|
-
var
|
|
33125
|
+
var STYLE_ID16 = "vela-widget-indpicker";
|
|
31848
33126
|
var CSS13 = `
|
|
31849
33127
|
.vela-ip-searchrow {
|
|
31850
33128
|
display: flex;
|
|
@@ -31922,7 +33200,7 @@ var IndicatorPicker = class {
|
|
|
31922
33200
|
this.isOpen = false;
|
|
31923
33201
|
this.opts = opts;
|
|
31924
33202
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
31925
|
-
injectStyles(
|
|
33203
|
+
injectStyles(STYLE_ID16, CSS13, doc);
|
|
31926
33204
|
this.search = doc.createElement("input");
|
|
31927
33205
|
this.search.className = "vela-ip-search";
|
|
31928
33206
|
this.search.placeholder = "Search\u2026";
|
|
@@ -31940,14 +33218,14 @@ var IndicatorPicker = class {
|
|
|
31940
33218
|
closeOnInteractOutside: true,
|
|
31941
33219
|
// The picker adds/removes indicators live — keep the chart undimmed behind it.
|
|
31942
33220
|
content: (body) => body.append(searchRow, this.list),
|
|
31943
|
-
onOpenChange: (
|
|
31944
|
-
this.isOpen =
|
|
31945
|
-
if (
|
|
33221
|
+
onOpenChange: (open2) => {
|
|
33222
|
+
this.isOpen = open2;
|
|
33223
|
+
if (open2) {
|
|
31946
33224
|
this.search.value = "";
|
|
31947
33225
|
this.refresh();
|
|
31948
33226
|
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
31949
33227
|
}
|
|
31950
|
-
opts.onOpenChange?.(
|
|
33228
|
+
opts.onOpenChange?.(open2);
|
|
31951
33229
|
}
|
|
31952
33230
|
});
|
|
31953
33231
|
this.list.addEventListener("click", (e) => {
|
|
@@ -32050,7 +33328,7 @@ var IndicatorPicker = class {
|
|
|
32050
33328
|
};
|
|
32051
33329
|
|
|
32052
33330
|
// src/widget/timeframe-quick.ts
|
|
32053
|
-
var
|
|
33331
|
+
var STYLE_ID17 = "vela-widget-tfquick";
|
|
32054
33332
|
var CSS14 = `
|
|
32055
33333
|
.vela-tq-input {
|
|
32056
33334
|
display: block;
|
|
@@ -32074,7 +33352,7 @@ var CSS14 = `
|
|
|
32074
33352
|
var TimeframeQuick = class {
|
|
32075
33353
|
constructor(opts) {
|
|
32076
33354
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
32077
|
-
injectStyles(
|
|
33355
|
+
injectStyles(STYLE_ID17, CSS14, doc);
|
|
32078
33356
|
this.input = doc.createElement("input");
|
|
32079
33357
|
this.input.className = "vela-tq-input";
|
|
32080
33358
|
this.input.setAttribute("spellcheck", "false");
|
|
@@ -32086,7 +33364,7 @@ var TimeframeQuick = class {
|
|
|
32086
33364
|
draggable: true,
|
|
32087
33365
|
closeOnInteractOutside: true,
|
|
32088
33366
|
content: (body) => body.append(this.input, this.hint),
|
|
32089
|
-
onOpenChange: (
|
|
33367
|
+
onOpenChange: (open2) => opts.onOpenChange?.(open2)
|
|
32090
33368
|
});
|
|
32091
33369
|
this.input.addEventListener("input", () => this.renderHint());
|
|
32092
33370
|
this.input.addEventListener("keydown", (e) => {
|
|
@@ -32256,7 +33534,7 @@ var LayoutModeController = class {
|
|
|
32256
33534
|
};
|
|
32257
33535
|
|
|
32258
33536
|
// src/widget/mobile-bar.ts
|
|
32259
|
-
var
|
|
33537
|
+
var STYLE_ID18 = "vela-widget-mobilebar";
|
|
32260
33538
|
var CSS15 = `
|
|
32261
33539
|
.vela-mobilebar {
|
|
32262
33540
|
display: none;
|
|
@@ -32307,7 +33585,7 @@ var MobileBar = class {
|
|
|
32307
33585
|
constructor(host, opts) {
|
|
32308
33586
|
this.opts = opts;
|
|
32309
33587
|
const doc = host.ownerDocument;
|
|
32310
|
-
injectStyles(
|
|
33588
|
+
injectStyles(STYLE_ID18, CSS15, doc);
|
|
32311
33589
|
this.el = doc.createElement("div");
|
|
32312
33590
|
this.el.className = "vela-mobilebar";
|
|
32313
33591
|
const item = (cls, label, onClick, icon2) => {
|
|
@@ -32366,7 +33644,7 @@ var MobileBar = class {
|
|
|
32366
33644
|
};
|
|
32367
33645
|
|
|
32368
33646
|
// src/widget/timeframe-drawer.ts
|
|
32369
|
-
var
|
|
33647
|
+
var STYLE_ID19 = "vela-widget-tf-drawer";
|
|
32370
33648
|
var CSS16 = `
|
|
32371
33649
|
.vela-tfd-heading {
|
|
32372
33650
|
padding: 4px 2px 8px;
|
|
@@ -32423,7 +33701,7 @@ var TimeframeDrawer = class {
|
|
|
32423
33701
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
32424
33702
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
32425
33703
|
const doc = opts.host.ownerDocument;
|
|
32426
|
-
injectStyles(
|
|
33704
|
+
injectStyles(STYLE_ID19, CSS16, doc);
|
|
32427
33705
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
32428
33706
|
const rangeHeading = doc.createElement("div");
|
|
32429
33707
|
rangeHeading.className = "vela-tfd-heading";
|
|
@@ -32481,7 +33759,7 @@ var TimeframeDrawer = class {
|
|
|
32481
33759
|
};
|
|
32482
33760
|
|
|
32483
33761
|
// src/widget/drawings-drawer.ts
|
|
32484
|
-
var
|
|
33762
|
+
var STYLE_ID20 = "vela-widget-drawings-drawer";
|
|
32485
33763
|
var CSS17 = `
|
|
32486
33764
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
32487
33765
|
.vela-dd-sticky {
|
|
@@ -32584,7 +33862,7 @@ var DrawingsDrawer = class {
|
|
|
32584
33862
|
this.definition = { groups: [] };
|
|
32585
33863
|
this.activeGroup = "";
|
|
32586
33864
|
const doc = opts.host.ownerDocument;
|
|
32587
|
-
injectStyles(
|
|
33865
|
+
injectStyles(STYLE_ID20, CSS17, doc);
|
|
32588
33866
|
this.drawer = new Drawer({
|
|
32589
33867
|
host: opts.host,
|
|
32590
33868
|
title: "Drawings",
|
|
@@ -32726,7 +34004,7 @@ var DrawingsDrawer = class {
|
|
|
32726
34004
|
};
|
|
32727
34005
|
|
|
32728
34006
|
// src/widget/more-drawer.ts
|
|
32729
|
-
var
|
|
34007
|
+
var STYLE_ID21 = "vela-widget-more-drawer";
|
|
32730
34008
|
var CSS18 = `
|
|
32731
34009
|
.vela-md-actions {
|
|
32732
34010
|
display: flex;
|
|
@@ -32802,7 +34080,7 @@ var MoreDrawer = class {
|
|
|
32802
34080
|
constructor(opts) {
|
|
32803
34081
|
this.opts = opts;
|
|
32804
34082
|
this.view = "main";
|
|
32805
|
-
injectStyles(
|
|
34083
|
+
injectStyles(STYLE_ID21, CSS18, opts.host.ownerDocument);
|
|
32806
34084
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
32807
34085
|
}
|
|
32808
34086
|
open() {
|
|
@@ -32994,7 +34272,7 @@ var MoreDrawer = class {
|
|
|
32994
34272
|
};
|
|
32995
34273
|
|
|
32996
34274
|
// src/widget/timezone-drawer.ts
|
|
32997
|
-
var
|
|
34275
|
+
var STYLE_ID22 = "vela-widget-timezone-drawer";
|
|
32998
34276
|
var CSS19 = `
|
|
32999
34277
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
33000
34278
|
.vela-tzd-row {
|
|
@@ -33014,7 +34292,7 @@ var CSS19 = `
|
|
|
33014
34292
|
var TimezoneDrawer = class {
|
|
33015
34293
|
constructor(opts) {
|
|
33016
34294
|
this.opts = opts;
|
|
33017
|
-
injectStyles(
|
|
34295
|
+
injectStyles(STYLE_ID22, CSS19, opts.host.ownerDocument);
|
|
33018
34296
|
this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
|
|
33019
34297
|
}
|
|
33020
34298
|
open() {
|
|
@@ -33052,7 +34330,7 @@ var TimezoneDrawer = class {
|
|
|
33052
34330
|
};
|
|
33053
34331
|
|
|
33054
34332
|
// src/widget/price-scale-drawer.ts
|
|
33055
|
-
var
|
|
34333
|
+
var STYLE_ID23 = "vela-widget-price-scale-drawer";
|
|
33056
34334
|
var CSS20 = `
|
|
33057
34335
|
.vela-psd-list { padding: 2px 0 4px; }
|
|
33058
34336
|
.vela-psd-row {
|
|
@@ -33082,7 +34360,7 @@ var PriceScaleDrawer = class {
|
|
|
33082
34360
|
constructor(opts) {
|
|
33083
34361
|
this.opts = opts;
|
|
33084
34362
|
this.pane = null;
|
|
33085
|
-
injectStyles(
|
|
34363
|
+
injectStyles(STYLE_ID23, CSS20, opts.host.ownerDocument);
|
|
33086
34364
|
this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
|
|
33087
34365
|
}
|
|
33088
34366
|
open() {
|
|
@@ -33218,7 +34496,7 @@ var PriceScaleDrawer = class {
|
|
|
33218
34496
|
};
|
|
33219
34497
|
|
|
33220
34498
|
// src/widget/drawing-pill.ts
|
|
33221
|
-
var
|
|
34499
|
+
var STYLE_ID24 = "vela-widget-drawing-pill";
|
|
33222
34500
|
var CSS21 = `
|
|
33223
34501
|
.vela-drawpill { display: none; }
|
|
33224
34502
|
[data-layout='mobile'] .vela-drawpill {
|
|
@@ -33279,7 +34557,7 @@ var DrawingPill = class {
|
|
|
33279
34557
|
this.chart = null;
|
|
33280
34558
|
this.chartSubs = [];
|
|
33281
34559
|
const doc = host.ownerDocument;
|
|
33282
|
-
injectStyles(
|
|
34560
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
33283
34561
|
this.el = doc.createElement("div");
|
|
33284
34562
|
this.el.className = "vela-drawpill";
|
|
33285
34563
|
this.el.hidden = true;
|
|
@@ -33388,6 +34666,10 @@ function sanitizeState(doc) {
|
|
|
33388
34666
|
const favs = d.favorites.filter((f) => typeof f === "string");
|
|
33389
34667
|
if (favs.length > 0) out.favorites = favs;
|
|
33390
34668
|
}
|
|
34669
|
+
if (Array.isArray(d.timeframeFavorites)) {
|
|
34670
|
+
const favs = d.timeframeFavorites.filter((f) => typeof f === "string");
|
|
34671
|
+
if (favs.length > 0) out.timeframeFavorites = favs;
|
|
34672
|
+
}
|
|
33391
34673
|
const sync = sanitizeSync(d.sync);
|
|
33392
34674
|
if (sync) out.sync = sync;
|
|
33393
34675
|
const tracks = sanitizeTrackSizes(d.trackSizes);
|
|
@@ -33661,7 +34943,7 @@ var WidgetHistory = class {
|
|
|
33661
34943
|
};
|
|
33662
34944
|
|
|
33663
34945
|
// src/widget/toast.ts
|
|
33664
|
-
var
|
|
34946
|
+
var STYLE_ID25 = "vela-widget-toast";
|
|
33665
34947
|
var CSS22 = `
|
|
33666
34948
|
.vela-toast {
|
|
33667
34949
|
position: absolute;
|
|
@@ -33692,7 +34974,7 @@ var CSS22 = `
|
|
|
33692
34974
|
var Toast = class {
|
|
33693
34975
|
constructor(host) {
|
|
33694
34976
|
this.timer = null;
|
|
33695
|
-
injectStyles(
|
|
34977
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
33696
34978
|
this.el = host.ownerDocument.createElement("div");
|
|
33697
34979
|
this.el.className = "vela-toast";
|
|
33698
34980
|
host.appendChild(this.el);
|
|
@@ -33828,6 +35110,8 @@ var VelaWidget = class {
|
|
|
33828
35110
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
33829
35111
|
/** Favorite drawing-tool types — mirrored from chart events, reapplied on rebuilds. */
|
|
33830
35112
|
this.favs = [];
|
|
35113
|
+
/** Favorite timeframes — the topbar's quick-switch chips, persisted with the shell. */
|
|
35114
|
+
this.tfFavs = [];
|
|
33831
35115
|
/** Mounted attachment disposers (by id), torn down at destroy. */
|
|
33832
35116
|
this.attachmentDisposers = /* @__PURE__ */ new Map();
|
|
33833
35117
|
this.onUnload = () => this.persistNow();
|
|
@@ -33870,6 +35154,7 @@ var VelaWidget = class {
|
|
|
33870
35154
|
this.indicatorTitlesOn = bootCell?.indicatorTitles ?? true;
|
|
33871
35155
|
this.indicatorValuesOn = bootCell?.indicatorValues ?? true;
|
|
33872
35156
|
this.favs = boot?.favorites ? [...boot.favorites] : [];
|
|
35157
|
+
this.tfFavs = boot?.timeframeFavorites ? [...boot.timeframeFavorites] : [];
|
|
33873
35158
|
this.savedConfig = bootCell?.rendererConfig ?? null;
|
|
33874
35159
|
this.savedDrawings = bootCell?.drawings ?? null;
|
|
33875
35160
|
this.pendingIndicators = bootCell?.indicators ?? null;
|
|
@@ -33883,9 +35168,9 @@ var VelaWidget = class {
|
|
|
33883
35168
|
this.symbolPicker = new SymbolPicker({
|
|
33884
35169
|
host: this.root,
|
|
33885
35170
|
onSelect: (symbol) => this.setSymbol(symbol),
|
|
33886
|
-
onOpenChange: (
|
|
33887
|
-
if (
|
|
33888
|
-
this.trackDialog(
|
|
35171
|
+
onOpenChange: (open2) => {
|
|
35172
|
+
if (open2) this.inner?.renderer.closeDialogs();
|
|
35173
|
+
this.trackDialog(open2);
|
|
33889
35174
|
}
|
|
33890
35175
|
});
|
|
33891
35176
|
this.indicatorPicker = opts.indicatorPicker !== false ? new IndicatorPicker({
|
|
@@ -33908,15 +35193,15 @@ var VelaWidget = class {
|
|
|
33908
35193
|
if (i < present.length) this.removeNative(present[i].type);
|
|
33909
35194
|
else this.removeInstance(i - present.length);
|
|
33910
35195
|
},
|
|
33911
|
-
onOpenChange: (
|
|
33912
|
-
if (
|
|
33913
|
-
this.trackDialog(
|
|
35196
|
+
onOpenChange: (open2) => {
|
|
35197
|
+
if (open2) this.inner?.renderer.closeDialogs();
|
|
35198
|
+
this.trackDialog(open2);
|
|
33914
35199
|
}
|
|
33915
35200
|
}) : null;
|
|
33916
35201
|
this.tfQuick = new TimeframeQuick({
|
|
33917
35202
|
host: this.root,
|
|
33918
35203
|
onApply: (tf) => this.setTimeframe(tf),
|
|
33919
|
-
onOpenChange: (
|
|
35204
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
33920
35205
|
});
|
|
33921
35206
|
const picker = this.indicatorPicker;
|
|
33922
35207
|
this.topbar = new Topbar(this.root, {
|
|
@@ -33929,8 +35214,10 @@ var VelaWidget = class {
|
|
|
33929
35214
|
onAlertsClick: (anchor) => this.openAlertsMenu(anchor),
|
|
33930
35215
|
timeframe: this.timeframe,
|
|
33931
35216
|
timeframes: opts.timeframes ?? DEFAULT_TIMEFRAMES,
|
|
35217
|
+
timeframeFavorites: this.tfFavs,
|
|
33932
35218
|
priceStyle: this.priceStyle,
|
|
33933
35219
|
onTimeframe: (tf) => this.setTimeframe(tf),
|
|
35220
|
+
onTimeframeFavorite: (tf, on) => this.setTimeframeFavorite(tf, on),
|
|
33934
35221
|
onPriceStyle: (style) => this.setPriceStyle(style),
|
|
33935
35222
|
getContext: () => this.context()
|
|
33936
35223
|
});
|
|
@@ -34041,7 +35328,7 @@ var VelaWidget = class {
|
|
|
34041
35328
|
label: "Show this shortcuts panel",
|
|
34042
35329
|
category: "Help",
|
|
34043
35330
|
run: () => {
|
|
34044
|
-
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (
|
|
35331
|
+
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open2) => this.trackDialog(open2)));
|
|
34045
35332
|
this.shortcutsHelp.open();
|
|
34046
35333
|
}
|
|
34047
35334
|
});
|
|
@@ -34075,7 +35362,7 @@ var VelaWidget = class {
|
|
|
34075
35362
|
activeRange: () => this.activeRangeId,
|
|
34076
35363
|
onTimeframe: (tf) => this.setTimeframe(tf),
|
|
34077
35364
|
onRange: (preset) => this.applyRange(preset),
|
|
34078
|
-
onOpenChange: (
|
|
35365
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34079
35366
|
}));
|
|
34080
35367
|
this.tfDrawer.open();
|
|
34081
35368
|
}
|
|
@@ -34087,7 +35374,7 @@ var VelaWidget = class {
|
|
|
34087
35374
|
isFavorite: (type) => this.inner?.drawings.isFavorite(type) ?? false,
|
|
34088
35375
|
onFavorite: (type, on) => this.inner?.drawings.setFavorite(type, on),
|
|
34089
35376
|
onSelect: (type) => this.inner?.drawings.setTool(type),
|
|
34090
|
-
onOpenChange: (
|
|
35377
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34091
35378
|
}));
|
|
34092
35379
|
this.drawingsDrawer.open();
|
|
34093
35380
|
}
|
|
@@ -34108,7 +35395,7 @@ var VelaWidget = class {
|
|
|
34108
35395
|
// Left-aligned actions have their own bottom-bar stop — only the rest
|
|
34109
35396
|
// lands in the drawer, or every left action would appear twice.
|
|
34110
35397
|
actions: () => widgetActions("topbar", this.context()).filter((a) => a.align !== "left").map((a) => ({ label: a.label, icon: a.icon, run: () => a.run(this.context()) })),
|
|
34111
|
-
onOpenChange: (
|
|
35398
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34112
35399
|
}));
|
|
34113
35400
|
this.moreDrawer.open();
|
|
34114
35401
|
}
|
|
@@ -34117,7 +35404,7 @@ var VelaWidget = class {
|
|
|
34117
35404
|
host: this.root,
|
|
34118
35405
|
timezone: () => this.timezone,
|
|
34119
35406
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
34120
|
-
onOpenChange: (
|
|
35407
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34121
35408
|
}));
|
|
34122
35409
|
this.timezoneDrawer.open();
|
|
34123
35410
|
}
|
|
@@ -34127,7 +35414,7 @@ var VelaWidget = class {
|
|
|
34127
35414
|
host: this.root,
|
|
34128
35415
|
chart: () => this.inner,
|
|
34129
35416
|
pressY: () => this.priceScalePressY,
|
|
34130
|
-
onOpenChange: (
|
|
35417
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34131
35418
|
}));
|
|
34132
35419
|
this.priceScaleDrawer.open();
|
|
34133
35420
|
}
|
|
@@ -34156,7 +35443,7 @@ var VelaWidget = class {
|
|
|
34156
35443
|
setTimeframe: (tf) => this.setTimeframe(tf),
|
|
34157
35444
|
setPriceStyle: (style) => this.setPriceStyle(style),
|
|
34158
35445
|
openSymbolSearch: (query) => this.symbolPicker.open(query ?? ""),
|
|
34159
|
-
togglePanel: (id,
|
|
35446
|
+
togglePanel: (id, open2) => this.dock.toggle(id, open2),
|
|
34160
35447
|
host: this.root,
|
|
34161
35448
|
toast: (message, kind) => this.toast?.show(message, kind)
|
|
34162
35449
|
};
|
|
@@ -34198,13 +35485,15 @@ var VelaWidget = class {
|
|
|
34198
35485
|
this.statusline?.setMeta(tf, this.providerLabel());
|
|
34199
35486
|
}
|
|
34200
35487
|
/**
|
|
34201
|
-
* The
|
|
34202
|
-
*
|
|
34203
|
-
*
|
|
35488
|
+
* The venue label to show: the LISTING prefix the resolved symbol's data declares
|
|
35489
|
+
* (`NASDAQ` for AAPL), else the provider that RESOLVED the symbol — never the one
|
|
35490
|
+
* configured or typed. They differ whenever the `provider` option is just a default,
|
|
35491
|
+
* the prefix was a listing venue, or the spelling isn't registered — the status line
|
|
35492
|
+
* must not claim a venue that served nothing.
|
|
34204
35493
|
*/
|
|
34205
35494
|
providerLabel() {
|
|
34206
|
-
const
|
|
34207
|
-
return
|
|
35495
|
+
const display = this.inner?.data.displayPrefix(this.symbol);
|
|
35496
|
+
return display ?? parseSymbol(this.symbol).provider ?? "";
|
|
34208
35497
|
}
|
|
34209
35498
|
setTimeframe(tf) {
|
|
34210
35499
|
if (tf === this.timeframe || this.destroyed) return;
|
|
@@ -34215,6 +35504,14 @@ var VelaWidget = class {
|
|
|
34215
35504
|
this.markStateDirty();
|
|
34216
35505
|
void this.inner?.setMarket({ timeframe: tf, bars: this.bars });
|
|
34217
35506
|
}
|
|
35507
|
+
/** Star/unstar a timeframe — the topbar chips and dropdown stars follow, and the
|
|
35508
|
+
* set persists with the rest of the shell state. */
|
|
35509
|
+
setTimeframeFavorite(tf, on) {
|
|
35510
|
+
if (on === this.tfFavs.includes(tf)) return;
|
|
35511
|
+
this.tfFavs = on ? [tf, ...this.tfFavs] : this.tfFavs.filter((f) => f !== tf);
|
|
35512
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
35513
|
+
this.markStateDirty();
|
|
35514
|
+
}
|
|
34218
35515
|
/**
|
|
34219
35516
|
* Switch the chart symbol in place. An `EXCHANGE:` prefix pins the venue (the
|
|
34220
35517
|
* symbol picker composes one from the row the user pointed at) — a bare ticker
|
|
@@ -34272,6 +35569,7 @@ var VelaWidget = class {
|
|
|
34272
35569
|
});
|
|
34273
35570
|
const state = { version: 1, layout: "1", activeCellId: "c1", timezone: this.timezone, charts: [{ id: "c1", ...cell }] };
|
|
34274
35571
|
if (this.favs.length > 0) state.favorites = [...this.favs];
|
|
35572
|
+
if (this.tfFavs.length > 0) state.timeframeFavorites = [...this.tfFavs];
|
|
34275
35573
|
const panels2 = this.dock.getState();
|
|
34276
35574
|
if (panels2) state.panels = panels2;
|
|
34277
35575
|
return state;
|
|
@@ -34295,6 +35593,10 @@ var VelaWidget = class {
|
|
|
34295
35593
|
this.favs = [...st.favorites];
|
|
34296
35594
|
this.inner?.drawings.setFavorites(this.favs);
|
|
34297
35595
|
}
|
|
35596
|
+
if (st.timeframeFavorites) {
|
|
35597
|
+
this.tfFavs = [...st.timeframeFavorites];
|
|
35598
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
35599
|
+
}
|
|
34298
35600
|
this.dock.applyState(st.panels);
|
|
34299
35601
|
if (cell) {
|
|
34300
35602
|
const style = fromUrl.priceStyle ?? cell.priceStyle;
|
|
@@ -34551,6 +35853,9 @@ var VelaWidget = class {
|
|
|
34551
35853
|
for (const [language, make] of Object.entries(resolveEngines(engines))) chart.registerEngine(language, make());
|
|
34552
35854
|
this.inner = chart;
|
|
34553
35855
|
this.symbolPicker.setSource(() => chart.data.symbols());
|
|
35856
|
+
void chart.data.ready().then(() => {
|
|
35857
|
+
if (this.inner === chart) this.statusline?.setMeta(this.timeframe, this.providerLabel());
|
|
35858
|
+
});
|
|
34554
35859
|
if (this.layoutCtl !== void 0) chart.renderer.setLayoutMode(this.layoutCtl.current);
|
|
34555
35860
|
this.objectTree.setSymbol(this.symbol);
|
|
34556
35861
|
chart.renderer.setLegendActions(legendActionsProviderFor(chart, () => this.context()));
|
|
@@ -34869,9 +36174,9 @@ var VelaWidget = class {
|
|
|
34869
36174
|
this.tfQuick.open(key);
|
|
34870
36175
|
}
|
|
34871
36176
|
}
|
|
34872
|
-
trackDialog(
|
|
34873
|
-
this.openDialogs = Math.max(0, this.openDialogs + (
|
|
34874
|
-
if (
|
|
36177
|
+
trackDialog(open2) {
|
|
36178
|
+
this.openDialogs = Math.max(0, this.openDialogs + (open2 ? 1 : -1));
|
|
36179
|
+
if (open2) this.keymap.pushScope("dialog");
|
|
34875
36180
|
else this.keymap.popScope("dialog");
|
|
34876
36181
|
}
|
|
34877
36182
|
downloadScreenshot() {
|