@luxalgo/vela 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/{DataProvider-DTOEXKTI.d.ts → DataProvider-Cqc_BaJ9.d.ts} +1 -1
- package/dist/{DataProvider-gnzv-dgc.d.cts → DataProvider-DgS2UyMc.d.cts} +1 -1
- package/dist/{chunk-L5NTMWPP.js → chunk-D6WM5IUE.js} +724 -851
- package/dist/{chunk-LJLZR44Y.js → chunk-EMS6PO2T.js} +5 -7
- package/dist/{chunk-EY63XBFZ.js → chunk-JBQUY2RI.js} +119 -12
- 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-4Nh1jbvb.d.cts} +2 -2
- package/dist/{contributions-b6AVrqqG.d.ts → contributions-DuIxJWeH.d.ts} +2 -2
- package/dist/{history-Cf1lVsap.d.cts → history-FsmvCIo4.d.ts} +5 -3
- package/dist/{history-Ci3M0xLV.d.ts → history-HofqYEh2.d.cts} +5 -3
- package/dist/index.cjs +2715 -1606
- package/dist/index.d.cts +6 -6
- package/dist/index.d.ts +6 -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-DJR6z3e0.d.cts} +3 -3
- package/dist/{plugin-DP6A4t4o.d.ts → plugin-N_EaXN4-.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 +2709 -1600
- package/dist/vela.global.min.js +315 -66
- package/dist/widget.cjs +2275 -1032
- package/dist/widget.d.cts +25 -7
- package/dist/widget.d.ts +25 -7
- package/dist/widget.js +27 -9
- package/dist/workspace.cjs +2181 -926
- package/dist/workspace.d.cts +10 -5
- package/dist/workspace.d.ts +10 -5
- package/dist/workspace.js +25 -7
- package/package.json +1 -1
- package/dist/chunk-ATPU5CI6.js +0 -81
package/dist/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(
|
|
@@ -9675,12 +9679,6 @@ function mix(base, top, alpha) {
|
|
|
9675
9679
|
const c = (x, y) => Math.round(x + (y - x) * alpha);
|
|
9676
9680
|
return `rgb(${c(b.r, t.r)},${c(b.g, t.g)},${c(b.b, t.b)})`;
|
|
9677
9681
|
}
|
|
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
9682
|
function isDarkColor(color) {
|
|
9685
9683
|
const c = parseRgb(color);
|
|
9686
9684
|
if (!c) return true;
|
|
@@ -9699,6 +9697,9 @@ var STATIC_TOKENS = {
|
|
|
9699
9697
|
"--vela-z-tooltip": "60",
|
|
9700
9698
|
"--vela-z-menu": "50",
|
|
9701
9699
|
"--vela-z-dialog": "40",
|
|
9700
|
+
// Form popovers portal to <body> (or a chart host) and must sit above a dialog
|
|
9701
|
+
// whose own stacking context may be nested inside the chart container.
|
|
9702
|
+
"--vela-z-popover": "6000",
|
|
9702
9703
|
"--vela-ease": "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
9703
9704
|
"--vela-dur-fast": "90ms",
|
|
9704
9705
|
"--vela-dur-med": "160ms",
|
|
@@ -9881,6 +9882,1246 @@ function makeDialogDraggable(box, header, options) {
|
|
|
9881
9882
|
return { reset };
|
|
9882
9883
|
}
|
|
9883
9884
|
|
|
9885
|
+
// src/ui/styles.ts
|
|
9886
|
+
function injectStyles(id, css, root = document) {
|
|
9887
|
+
if (typeof document === "undefined") return;
|
|
9888
|
+
const host = root instanceof Document ? root.head : root;
|
|
9889
|
+
if (root.getElementById?.(id) ?? host.querySelector(`#${CSS.escape(id)}`)) return;
|
|
9890
|
+
const s = document.createElement("style");
|
|
9891
|
+
s.id = id;
|
|
9892
|
+
s.textContent = css;
|
|
9893
|
+
host.appendChild(s);
|
|
9894
|
+
}
|
|
9895
|
+
|
|
9896
|
+
// src/ui/icons.ts
|
|
9897
|
+
function iconEl(id, doc = document) {
|
|
9898
|
+
const span = doc.createElement("span");
|
|
9899
|
+
span.className = "vela-icon";
|
|
9900
|
+
span.setAttribute("aria-hidden", "true");
|
|
9901
|
+
const svg = iconMarkup(id);
|
|
9902
|
+
if (svg) span.innerHTML = svg;
|
|
9903
|
+
return span;
|
|
9904
|
+
}
|
|
9905
|
+
|
|
9906
|
+
// src/ui/components/switch/controller.ts
|
|
9907
|
+
function switchController(opts = {}) {
|
|
9908
|
+
let checked = opts.checked ?? false;
|
|
9909
|
+
const disabled = opts.disabled ?? false;
|
|
9910
|
+
return {
|
|
9911
|
+
get checked() {
|
|
9912
|
+
return checked;
|
|
9913
|
+
},
|
|
9914
|
+
disabled,
|
|
9915
|
+
size: opts.size ?? "md",
|
|
9916
|
+
tone: opts.tone ?? "bright",
|
|
9917
|
+
setChecked(v) {
|
|
9918
|
+
checked = v;
|
|
9919
|
+
},
|
|
9920
|
+
toggle() {
|
|
9921
|
+
if (disabled) return checked;
|
|
9922
|
+
checked = !checked;
|
|
9923
|
+
opts.onChange?.(checked);
|
|
9924
|
+
return checked;
|
|
9925
|
+
}
|
|
9926
|
+
};
|
|
9927
|
+
}
|
|
9928
|
+
|
|
9929
|
+
// src/ui/components/switch/styles.ts
|
|
9930
|
+
var SWITCH_STYLE_ID = "vela-ui-switch";
|
|
9931
|
+
var SWITCH_CSS = `
|
|
9932
|
+
.vela-switch {
|
|
9933
|
+
width: 20px;
|
|
9934
|
+
height: 20px;
|
|
9935
|
+
flex: 0 0 auto;
|
|
9936
|
+
display: inline-flex;
|
|
9937
|
+
align-items: center;
|
|
9938
|
+
justify-content: center;
|
|
9939
|
+
padding: 0;
|
|
9940
|
+
border: 1px solid var(--vela-border);
|
|
9941
|
+
border-radius: 5px;
|
|
9942
|
+
background: transparent;
|
|
9943
|
+
color: transparent;
|
|
9944
|
+
cursor: pointer;
|
|
9945
|
+
line-height: 0;
|
|
9946
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
9947
|
+
}
|
|
9948
|
+
.vela-switch .vela-icon, .vela-switch svg { display: block; width: 12px; height: 12px; }
|
|
9949
|
+
.vela-switch[data-size='sm'] .vela-icon, .vela-switch[data-size='sm'] svg { width: 11px; height: 11px; }
|
|
9950
|
+
.vela-switch:hover { border-color: var(--vela-fg-muted); }
|
|
9951
|
+
.vela-switch[data-checked] {
|
|
9952
|
+
background: var(--vela-fg-bright);
|
|
9953
|
+
border-color: var(--vela-fg-bright);
|
|
9954
|
+
color: var(--vela-bg);
|
|
9955
|
+
}
|
|
9956
|
+
.vela-switch[data-size='sm'] { width: 18px; height: 18px; border-color: var(--vela-border-strong); }
|
|
9957
|
+
.vela-switch[data-size='sm']:hover { border-color: var(--vela-fg-muted); }
|
|
9958
|
+
.vela-switch[data-size='sm'][data-checked] {
|
|
9959
|
+
background: var(--vela-selected-bg);
|
|
9960
|
+
border-color: var(--vela-selected-bg);
|
|
9961
|
+
color: var(--vela-selected-fg);
|
|
9962
|
+
}
|
|
9963
|
+
.vela-switch[data-tone='selected'][data-checked] {
|
|
9964
|
+
background: var(--vela-selected-bg);
|
|
9965
|
+
border-color: var(--vela-selected-bg);
|
|
9966
|
+
color: var(--vela-selected-fg);
|
|
9967
|
+
}
|
|
9968
|
+
.vela-switch[disabled] { opacity: 0.4; cursor: default; }
|
|
9969
|
+
`;
|
|
9970
|
+
|
|
9971
|
+
// src/ui/components/switch/view.ts
|
|
9972
|
+
var Switch = class {
|
|
9973
|
+
constructor(opts = {}) {
|
|
9974
|
+
injectStyles(SWITCH_STYLE_ID, SWITCH_CSS, document);
|
|
9975
|
+
this.ctrl = switchController(opts);
|
|
9976
|
+
const b = document.createElement("button");
|
|
9977
|
+
b.type = "button";
|
|
9978
|
+
if (opts.id) b.id = opts.id;
|
|
9979
|
+
b.className = "vela-switch";
|
|
9980
|
+
b.dataset.size = this.ctrl.size;
|
|
9981
|
+
b.dataset.tone = this.ctrl.tone;
|
|
9982
|
+
b.setAttribute("role", "switch");
|
|
9983
|
+
if (this.ctrl.disabled) b.disabled = true;
|
|
9984
|
+
b.appendChild(iconEl("check", b.ownerDocument));
|
|
9985
|
+
this.el = b;
|
|
9986
|
+
this.paint();
|
|
9987
|
+
b.addEventListener("pointerdown", (e) => {
|
|
9988
|
+
e.preventDefault();
|
|
9989
|
+
b.focus({ preventScroll: true });
|
|
9990
|
+
});
|
|
9991
|
+
b.addEventListener("click", () => {
|
|
9992
|
+
this.ctrl.toggle();
|
|
9993
|
+
this.paint();
|
|
9994
|
+
});
|
|
9995
|
+
}
|
|
9996
|
+
get checked() {
|
|
9997
|
+
return this.ctrl.checked;
|
|
9998
|
+
}
|
|
9999
|
+
setChecked(v) {
|
|
10000
|
+
this.ctrl.setChecked(v);
|
|
10001
|
+
this.paint();
|
|
10002
|
+
}
|
|
10003
|
+
paint() {
|
|
10004
|
+
const on = this.ctrl.checked;
|
|
10005
|
+
this.el.setAttribute("aria-checked", on ? "true" : "false");
|
|
10006
|
+
if (on) this.el.dataset.checked = "";
|
|
10007
|
+
else delete this.el.dataset.checked;
|
|
10008
|
+
}
|
|
10009
|
+
};
|
|
10010
|
+
|
|
10011
|
+
// src/ui/tokens.ts
|
|
10012
|
+
var STATIC_ID = "vela-ui-tokens";
|
|
10013
|
+
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
10014
|
+
var STATIC_CSS = `
|
|
10015
|
+
.vela-ui, .vela-ui-layer {
|
|
10016
|
+
${STATIC_DECLS}
|
|
10017
|
+
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
10018
|
+
box-sizing: border-box;
|
|
10019
|
+
}
|
|
10020
|
+
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
10021
|
+
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
10022
|
+
.vela-icon svg { display: block; }
|
|
10023
|
+
`;
|
|
10024
|
+
function applyThemeTokens(el, t) {
|
|
10025
|
+
const tokens = themeTokens(t);
|
|
10026
|
+
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
10027
|
+
}
|
|
10028
|
+
function applyPlotOverlayTokens(host, base, config) {
|
|
10029
|
+
const layout = config?.layout;
|
|
10030
|
+
const t = layout?.background && layout.textColor ? { ...base, background: layout.background, textColor: layout.textColor } : base;
|
|
10031
|
+
applyThemeTokens(host, t);
|
|
10032
|
+
}
|
|
10033
|
+
function ensureUIHost(el, theme) {
|
|
10034
|
+
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
10035
|
+
el.classList.add("vela-ui");
|
|
10036
|
+
if (theme) applyThemeTokens(el, theme);
|
|
10037
|
+
}
|
|
10038
|
+
|
|
10039
|
+
// src/ui/components/popover/controller.ts
|
|
10040
|
+
function insetRect(r, inset) {
|
|
10041
|
+
return {
|
|
10042
|
+
left: r.left + inset,
|
|
10043
|
+
top: r.top + inset,
|
|
10044
|
+
right: r.right - inset,
|
|
10045
|
+
bottom: r.bottom - inset,
|
|
10046
|
+
width: r.width - inset * 2,
|
|
10047
|
+
height: r.height - inset * 2
|
|
10048
|
+
};
|
|
10049
|
+
}
|
|
10050
|
+
function viewportRect(width, height, inset) {
|
|
10051
|
+
return {
|
|
10052
|
+
left: inset,
|
|
10053
|
+
top: inset,
|
|
10054
|
+
right: width - inset,
|
|
10055
|
+
bottom: height - inset,
|
|
10056
|
+
width: width - inset * 2,
|
|
10057
|
+
height: height - inset * 2
|
|
10058
|
+
};
|
|
10059
|
+
}
|
|
10060
|
+
function intersectRects(a, b) {
|
|
10061
|
+
const left = Math.max(a.left, b.left);
|
|
10062
|
+
const top = Math.max(a.top, b.top);
|
|
10063
|
+
const right = Math.min(a.right, b.right);
|
|
10064
|
+
const bottom = Math.min(a.bottom, b.bottom);
|
|
10065
|
+
return { left, top, right, bottom, width: right - left, height: bottom - top };
|
|
10066
|
+
}
|
|
10067
|
+
function placePopover(a) {
|
|
10068
|
+
let left = a.align === "end" ? a.trigger.right - a.pop.width : a.trigger.left;
|
|
10069
|
+
const below = a.trigger.bottom + a.gap;
|
|
10070
|
+
const above = a.trigger.top - a.pop.height - a.gap;
|
|
10071
|
+
const fitsBelow = below + a.pop.height <= a.clamp.bottom;
|
|
10072
|
+
const fitsAbove = above >= a.clamp.top;
|
|
10073
|
+
let top = below;
|
|
10074
|
+
if (!fitsBelow && (fitsAbove || a.trigger.top - a.clamp.top > a.clamp.bottom - a.trigger.bottom)) {
|
|
10075
|
+
top = Math.max(a.clamp.top, above);
|
|
10076
|
+
}
|
|
10077
|
+
if (left + a.pop.width > a.clamp.right) left = a.clamp.right - a.pop.width;
|
|
10078
|
+
if (left < a.clamp.left) left = a.clamp.left;
|
|
10079
|
+
if (top + a.pop.height > a.clamp.bottom) top = a.clamp.bottom - a.pop.height;
|
|
10080
|
+
if (top < a.clamp.top) top = a.clamp.top;
|
|
10081
|
+
return {
|
|
10082
|
+
left: Math.round(left - a.originX),
|
|
10083
|
+
top: Math.round(top - a.originY)
|
|
10084
|
+
};
|
|
10085
|
+
}
|
|
10086
|
+
function popoverController(opts = {}) {
|
|
10087
|
+
return {
|
|
10088
|
+
gap: opts.gap ?? 4,
|
|
10089
|
+
align: opts.align ?? "start",
|
|
10090
|
+
matchWidth: opts.matchWidth ?? false,
|
|
10091
|
+
position: opts.position ?? "fixed",
|
|
10092
|
+
boundaryInset: opts.boundaryInset ?? 0,
|
|
10093
|
+
viewportInset: opts.viewportInset ?? 6,
|
|
10094
|
+
onClose: opts.onClose
|
|
10095
|
+
};
|
|
10096
|
+
}
|
|
10097
|
+
|
|
10098
|
+
// src/ui/components/popover/styles.ts
|
|
10099
|
+
var POPOVER_STYLE_ID = "vela-ui-popover";
|
|
10100
|
+
var POPOVER_CSS = `
|
|
10101
|
+
.vela-popover {
|
|
10102
|
+
position: fixed;
|
|
10103
|
+
z-index: var(--vela-z-popover);
|
|
10104
|
+
box-sizing: border-box;
|
|
10105
|
+
/* Never serve as a scroll anchor: the shell is portaled and re-placed between
|
|
10106
|
+
gestures, and anchoring against it jumps the scroller underneath. */
|
|
10107
|
+
overflow-anchor: none;
|
|
10108
|
+
}
|
|
10109
|
+
.vela-popover[data-position='absolute'] { position: absolute; }
|
|
10110
|
+
.vela-popover[hidden] { display: none !important; }
|
|
10111
|
+
`;
|
|
10112
|
+
|
|
10113
|
+
// src/ui/components/popover/view.ts
|
|
10114
|
+
var open = null;
|
|
10115
|
+
function closeOpenPopovers() {
|
|
10116
|
+
open?.hide();
|
|
10117
|
+
}
|
|
10118
|
+
function isPopoverOpen() {
|
|
10119
|
+
return open !== null;
|
|
10120
|
+
}
|
|
10121
|
+
function openPopoverTrigger() {
|
|
10122
|
+
return open?.trigger ?? null;
|
|
10123
|
+
}
|
|
10124
|
+
function toRect(r) {
|
|
10125
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
|
|
10126
|
+
}
|
|
10127
|
+
var Popover = class {
|
|
10128
|
+
constructor(opts) {
|
|
10129
|
+
this.onOutside = null;
|
|
10130
|
+
this.onKey = null;
|
|
10131
|
+
this.onReflow = null;
|
|
10132
|
+
this.shown = false;
|
|
10133
|
+
const doc = opts.trigger.ownerDocument;
|
|
10134
|
+
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
10135
|
+
this.trigger = opts.trigger;
|
|
10136
|
+
this.host = opts.host ?? doc.body;
|
|
10137
|
+
this.ctrl = popoverController(opts);
|
|
10138
|
+
this.boundary = opts.boundary ?? "viewport";
|
|
10139
|
+
this.theme = opts.theme;
|
|
10140
|
+
this.el = doc.createElement("div");
|
|
10141
|
+
this.el.className = "vela-popover vela-ui-layer" + (opts.className ? ` ${opts.className}` : "");
|
|
10142
|
+
this.el.dataset.position = this.ctrl.position;
|
|
10143
|
+
if (opts.zIndex !== void 0) this.el.style.zIndex = String(opts.zIndex);
|
|
10144
|
+
this.el.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
10145
|
+
if (opts.content instanceof Node) this.el.appendChild(opts.content);
|
|
10146
|
+
else if (typeof opts.content === "function") opts.content(this.el);
|
|
10147
|
+
}
|
|
10148
|
+
get open() {
|
|
10149
|
+
return this.shown;
|
|
10150
|
+
}
|
|
10151
|
+
get position() {
|
|
10152
|
+
return this.ctrl.position;
|
|
10153
|
+
}
|
|
10154
|
+
get align() {
|
|
10155
|
+
return this.ctrl.align;
|
|
10156
|
+
}
|
|
10157
|
+
show() {
|
|
10158
|
+
if (this.shown) {
|
|
10159
|
+
this.place();
|
|
10160
|
+
return;
|
|
10161
|
+
}
|
|
10162
|
+
if (open && open !== this) open.hide();
|
|
10163
|
+
ensureUIHost(this.el, this.theme);
|
|
10164
|
+
this.host.appendChild(this.el);
|
|
10165
|
+
this.shown = true;
|
|
10166
|
+
open = this;
|
|
10167
|
+
this.place();
|
|
10168
|
+
const onOutside = (ev) => {
|
|
10169
|
+
const t = ev.target;
|
|
10170
|
+
if (this.el.contains(t) || this.trigger.contains(t)) return;
|
|
10171
|
+
this.hide();
|
|
10172
|
+
};
|
|
10173
|
+
const onKey = (ev) => {
|
|
10174
|
+
if (ev.key !== "Escape") return;
|
|
10175
|
+
ev.preventDefault();
|
|
10176
|
+
ev.stopPropagation();
|
|
10177
|
+
this.hide();
|
|
10178
|
+
};
|
|
10179
|
+
const onReflow = () => this.place();
|
|
10180
|
+
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
10181
|
+
document.addEventListener("keydown", onKey, true);
|
|
10182
|
+
window.addEventListener("resize", onReflow, true);
|
|
10183
|
+
document.addEventListener("scroll", onReflow, true);
|
|
10184
|
+
this.onOutside = onOutside;
|
|
10185
|
+
this.onKey = onKey;
|
|
10186
|
+
this.onReflow = onReflow;
|
|
10187
|
+
}
|
|
10188
|
+
hide() {
|
|
10189
|
+
if (!this.shown) return;
|
|
10190
|
+
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
10191
|
+
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
10192
|
+
if (this.onReflow) {
|
|
10193
|
+
window.removeEventListener("resize", this.onReflow, true);
|
|
10194
|
+
document.removeEventListener("scroll", this.onReflow, true);
|
|
10195
|
+
}
|
|
10196
|
+
this.onOutside = null;
|
|
10197
|
+
this.onKey = null;
|
|
10198
|
+
this.onReflow = null;
|
|
10199
|
+
this.el.remove();
|
|
10200
|
+
this.shown = false;
|
|
10201
|
+
if (open === this) open = null;
|
|
10202
|
+
this.ctrl.onClose?.();
|
|
10203
|
+
}
|
|
10204
|
+
/** Show if closed, hide if this instance is the open popover. */
|
|
10205
|
+
toggle() {
|
|
10206
|
+
if (this.shown) this.hide();
|
|
10207
|
+
else this.show();
|
|
10208
|
+
}
|
|
10209
|
+
destroy() {
|
|
10210
|
+
this.hide();
|
|
10211
|
+
}
|
|
10212
|
+
reposition() {
|
|
10213
|
+
if (this.shown) this.place();
|
|
10214
|
+
}
|
|
10215
|
+
clampRect() {
|
|
10216
|
+
const view = viewportRect(window.innerWidth, window.innerHeight, this.ctrl.viewportInset);
|
|
10217
|
+
const bound = this.readBoundary();
|
|
10218
|
+
if (!bound) return view;
|
|
10219
|
+
const inset = insetRect(bound, this.ctrl.boundaryInset);
|
|
10220
|
+
return intersectRects(view, inset);
|
|
10221
|
+
}
|
|
10222
|
+
readBoundary() {
|
|
10223
|
+
const b = this.boundary;
|
|
10224
|
+
if (b === "viewport") return null;
|
|
10225
|
+
const raw = typeof b === "function" ? b() : b.getBoundingClientRect();
|
|
10226
|
+
return raw ? toRect(raw) : null;
|
|
10227
|
+
}
|
|
10228
|
+
place() {
|
|
10229
|
+
const ar = this.trigger.getBoundingClientRect();
|
|
10230
|
+
if (this.ctrl.matchWidth) {
|
|
10231
|
+
this.el.style.minWidth = `${Math.round(Math.max(this.el.offsetWidth, ar.width))}px`;
|
|
10232
|
+
}
|
|
10233
|
+
const origin = this.ctrl.position === "absolute" ? this.host.getBoundingClientRect() : { left: 0, top: 0 };
|
|
10234
|
+
const pos = placePopover({
|
|
10235
|
+
trigger: toRect(ar),
|
|
10236
|
+
pop: { width: this.el.offsetWidth, height: this.el.offsetHeight },
|
|
10237
|
+
gap: this.ctrl.gap,
|
|
10238
|
+
align: this.ctrl.align,
|
|
10239
|
+
clamp: this.clampRect(),
|
|
10240
|
+
originX: origin.left,
|
|
10241
|
+
originY: origin.top
|
|
10242
|
+
});
|
|
10243
|
+
this.el.style.left = `${pos.left}px`;
|
|
10244
|
+
this.el.style.top = `${pos.top}px`;
|
|
10245
|
+
}
|
|
10246
|
+
};
|
|
10247
|
+
|
|
10248
|
+
// src/ui/components/select/controller.ts
|
|
10249
|
+
function selectController(opts) {
|
|
10250
|
+
const options = opts.options;
|
|
10251
|
+
let value = opts.value ?? options[0]?.value ?? "";
|
|
10252
|
+
const size = opts.size ?? "md";
|
|
10253
|
+
return {
|
|
10254
|
+
options,
|
|
10255
|
+
get value() {
|
|
10256
|
+
return value;
|
|
10257
|
+
},
|
|
10258
|
+
size,
|
|
10259
|
+
fill: opts.fill ?? size !== "sm",
|
|
10260
|
+
disabled: opts.disabled ?? false,
|
|
10261
|
+
setValue(v) {
|
|
10262
|
+
value = v;
|
|
10263
|
+
},
|
|
10264
|
+
labelOf(v) {
|
|
10265
|
+
return options.find((o) => o.value === v)?.label ?? v;
|
|
10266
|
+
},
|
|
10267
|
+
pick(v) {
|
|
10268
|
+
value = v;
|
|
10269
|
+
const label = options.find((o) => o.value === v)?.label ?? v;
|
|
10270
|
+
opts.onChange?.(v, label);
|
|
10271
|
+
return { value: v, label };
|
|
10272
|
+
}
|
|
10273
|
+
};
|
|
10274
|
+
}
|
|
10275
|
+
|
|
10276
|
+
// src/ui/components/select/styles.ts
|
|
10277
|
+
var SELECT_STYLE_ID = "vela-ui-select";
|
|
10278
|
+
var SELECT_CSS = `
|
|
10279
|
+
.vela-select { position: relative; display: inline-block; min-width: 0; }
|
|
10280
|
+
.vela-select[data-fill] { width: 100%; }
|
|
10281
|
+
.vela-select-trigger {
|
|
10282
|
+
display: flex;
|
|
10283
|
+
align-items: center;
|
|
10284
|
+
width: 100%;
|
|
10285
|
+
box-sizing: border-box;
|
|
10286
|
+
height: 34px;
|
|
10287
|
+
padding: 0 26px 0 8px;
|
|
10288
|
+
background: transparent;
|
|
10289
|
+
border: 1px solid var(--vela-border-strong);
|
|
10290
|
+
border-radius: 6px;
|
|
10291
|
+
color: var(--vela-fg-bright);
|
|
10292
|
+
font-size: 14px;
|
|
10293
|
+
font-family: inherit;
|
|
10294
|
+
cursor: pointer;
|
|
10295
|
+
text-align: left;
|
|
10296
|
+
outline: none;
|
|
10297
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
10298
|
+
}
|
|
10299
|
+
.vela-select-trigger:hover { border-color: var(--vela-fg-muted); }
|
|
10300
|
+
.vela-select-trigger:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
10301
|
+
/* As a grid item (chart-settings rows dissolve via display:contents into a shared
|
|
10302
|
+
max-content column) the wrap must NOT stretch to the column: it sizes to its own
|
|
10303
|
+
widest option, like the native select it replaces. */
|
|
10304
|
+
.vela-select[data-size='sm'] { max-width: 200px; flex: 0 0 auto; justify-self: start; }
|
|
10305
|
+
.vela-select[data-size='sm'] .vela-select-trigger {
|
|
10306
|
+
height: 28px;
|
|
10307
|
+
background: var(--vela-surface-elev);
|
|
10308
|
+
border-radius: var(--vela-radius-sm);
|
|
10309
|
+
color: var(--vela-fg);
|
|
10310
|
+
font-size: 13px;
|
|
10311
|
+
box-shadow: none;
|
|
10312
|
+
}
|
|
10313
|
+
.vela-select[data-size='sm'] .vela-select-trigger:focus { box-shadow: none; border-color: var(--vela-fg-muted); }
|
|
10314
|
+
.vela-select-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
10315
|
+
/* Invisible option-label stack: gives the shrink-to-fit wrap the WIDEST option's
|
|
10316
|
+
intrinsic width (like a native select), so picking a shorter/longer label never
|
|
10317
|
+
resizes the trigger and shifts the row around it. */
|
|
10318
|
+
.vela-select-sizer { visibility: hidden; height: 0; overflow: hidden; font-size: 14px; }
|
|
10319
|
+
.vela-select[data-size='sm'] .vela-select-sizer { font-size: 13px; }
|
|
10320
|
+
.vela-select-sizer span { display: block; height: 0; white-space: nowrap; padding: 0 26px 0 8px; border-inline: 1px solid transparent; }
|
|
10321
|
+
.vela-select-chevron {
|
|
10322
|
+
position: absolute;
|
|
10323
|
+
right: 8px;
|
|
10324
|
+
top: 50%;
|
|
10325
|
+
transform: translateY(-50%);
|
|
10326
|
+
pointer-events: none;
|
|
10327
|
+
display: flex;
|
|
10328
|
+
opacity: 0.55;
|
|
10329
|
+
line-height: 0;
|
|
10330
|
+
color: inherit;
|
|
10331
|
+
}
|
|
10332
|
+
.vela-select-chevron .vela-icon, .vela-select-chevron svg { width: 12px; height: 12px; display: block; }
|
|
10333
|
+
.vela-select-list {
|
|
10334
|
+
background: var(--vela-bg);
|
|
10335
|
+
color: var(--vela-fg);
|
|
10336
|
+
border: none;
|
|
10337
|
+
border-radius: 6px;
|
|
10338
|
+
box-shadow: var(--vela-shadow);
|
|
10339
|
+
font: 14px var(--vela-font);
|
|
10340
|
+
padding: 4px;
|
|
10341
|
+
overflow: hidden;
|
|
10342
|
+
}
|
|
10343
|
+
.vela-select-list[data-size='sm'] { font-size: 13px; background: var(--vela-surface-overlay); }
|
|
10344
|
+
.vela-select-items { max-height: none; overflow: hidden; }
|
|
10345
|
+
.vela-select-list.is-scroll { display: flex; align-items: stretch; gap: 2px; }
|
|
10346
|
+
.vela-select-list.is-scroll .vela-select-items {
|
|
10347
|
+
flex: 1 1 auto;
|
|
10348
|
+
min-width: 0;
|
|
10349
|
+
max-height: 240px;
|
|
10350
|
+
overflow-y: auto;
|
|
10351
|
+
scrollbar-width: none;
|
|
10352
|
+
}
|
|
10353
|
+
.vela-select-list.is-scroll .vela-select-items::-webkit-scrollbar { display: none; width: 0; height: 0; }
|
|
10354
|
+
.vela-select-rail { position: relative; flex: none; width: 3px; margin: 2px 1px 2px 0; pointer-events: none; }
|
|
10355
|
+
.vela-select-thumb { width: 3px; border-radius: 2px; background: var(--vela-scroll); }
|
|
10356
|
+
.vela-select-item {
|
|
10357
|
+
display: block;
|
|
10358
|
+
width: 100%;
|
|
10359
|
+
text-align: left;
|
|
10360
|
+
padding: 7px 10px;
|
|
10361
|
+
border: none;
|
|
10362
|
+
border-radius: 4px;
|
|
10363
|
+
background: transparent;
|
|
10364
|
+
color: inherit;
|
|
10365
|
+
cursor: pointer;
|
|
10366
|
+
font: inherit;
|
|
10367
|
+
font-weight: 400;
|
|
10368
|
+
}
|
|
10369
|
+
.vela-select-item:hover { background: var(--vela-hover); }
|
|
10370
|
+
.vela-select-item[data-checked] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
10371
|
+
.vela-select-item[data-checked]:hover { background: var(--vela-hover-strong); }
|
|
10372
|
+
`;
|
|
10373
|
+
|
|
10374
|
+
// src/ui/components/select/view.ts
|
|
10375
|
+
function ensureSelectStyles(doc) {
|
|
10376
|
+
injectStyles(SELECT_STYLE_ID, SELECT_CSS, doc);
|
|
10377
|
+
}
|
|
10378
|
+
function syncThumb(list, thumb) {
|
|
10379
|
+
const view = list.clientHeight;
|
|
10380
|
+
const total = list.scrollHeight;
|
|
10381
|
+
if (total <= view) {
|
|
10382
|
+
thumb.style.height = "0";
|
|
10383
|
+
return;
|
|
10384
|
+
}
|
|
10385
|
+
const thumbH = Math.max(20, view / total * view);
|
|
10386
|
+
const top = list.scrollTop / (total - view) * (view - thumbH);
|
|
10387
|
+
thumb.style.height = `${Math.round(thumbH)}px`;
|
|
10388
|
+
thumb.style.transform = `translateY(${Math.round(top)}px`;
|
|
10389
|
+
}
|
|
10390
|
+
function fillSelectList(menu2, options, current, onPick) {
|
|
10391
|
+
menu2.replaceChildren();
|
|
10392
|
+
const list = menu2.ownerDocument.createElement("div");
|
|
10393
|
+
list.className = "vela-select-items";
|
|
10394
|
+
for (const p of options) {
|
|
10395
|
+
const item = menu2.ownerDocument.createElement("button");
|
|
10396
|
+
item.type = "button";
|
|
10397
|
+
item.className = "vela-select-item";
|
|
10398
|
+
item.textContent = p.label;
|
|
10399
|
+
if (p.value === current) item.dataset.checked = "1";
|
|
10400
|
+
item.addEventListener("click", (e) => {
|
|
10401
|
+
e.stopPropagation();
|
|
10402
|
+
onPick(p.value, p.label);
|
|
10403
|
+
});
|
|
10404
|
+
list.appendChild(item);
|
|
10405
|
+
}
|
|
10406
|
+
menu2.appendChild(list);
|
|
10407
|
+
}
|
|
10408
|
+
function decorateSelectScroll(menu2) {
|
|
10409
|
+
const list = menu2.querySelector(".vela-select-items");
|
|
10410
|
+
if (!list) return;
|
|
10411
|
+
if (list.scrollHeight > 240) {
|
|
10412
|
+
menu2.classList.add("is-scroll");
|
|
10413
|
+
const rail = menu2.ownerDocument.createElement("div");
|
|
10414
|
+
rail.className = "vela-select-rail";
|
|
10415
|
+
const thumb = menu2.ownerDocument.createElement("div");
|
|
10416
|
+
thumb.className = "vela-select-thumb";
|
|
10417
|
+
rail.appendChild(thumb);
|
|
10418
|
+
menu2.appendChild(rail);
|
|
10419
|
+
list.addEventListener("scroll", () => syncThumb(list, thumb));
|
|
10420
|
+
syncThumb(list, thumb);
|
|
10421
|
+
}
|
|
10422
|
+
menu2.querySelector(".vela-select-item[data-checked]")?.scrollIntoView({ block: "nearest" });
|
|
10423
|
+
list.dispatchEvent(new Event("scroll"));
|
|
10424
|
+
}
|
|
10425
|
+
function toggleSelectList(trigger, options, current, onPick, opts = {}) {
|
|
10426
|
+
if (openPopoverTrigger() === trigger) {
|
|
10427
|
+
closeOpenPopovers();
|
|
10428
|
+
return null;
|
|
10429
|
+
}
|
|
10430
|
+
return openSelectList(trigger, options, current, onPick, opts);
|
|
10431
|
+
}
|
|
10432
|
+
function openSelectList(trigger, options, current, onPick, opts = {}) {
|
|
10433
|
+
ensureSelectStyles(trigger.ownerDocument);
|
|
10434
|
+
closeOpenPopovers();
|
|
10435
|
+
const pop = new Popover({
|
|
10436
|
+
trigger,
|
|
10437
|
+
theme: opts.theme,
|
|
10438
|
+
matchWidth: opts.matchWidth ?? true,
|
|
10439
|
+
boundary: opts.boundary,
|
|
10440
|
+
boundaryInset: opts.boundaryInset,
|
|
10441
|
+
gap: opts.gap ?? 4,
|
|
10442
|
+
align: opts.align ?? "start",
|
|
10443
|
+
host: opts.host,
|
|
10444
|
+
position: opts.position,
|
|
10445
|
+
zIndex: opts.zIndex,
|
|
10446
|
+
className: "vela-select-list",
|
|
10447
|
+
onClose: opts.onClose,
|
|
10448
|
+
content: (el) => {
|
|
10449
|
+
if (opts.size) el.dataset.size = opts.size;
|
|
10450
|
+
fillSelectList(el, options, current, (value, label) => {
|
|
10451
|
+
pop.hide();
|
|
10452
|
+
onPick(value, label);
|
|
10453
|
+
});
|
|
10454
|
+
}
|
|
10455
|
+
});
|
|
10456
|
+
pop.show();
|
|
10457
|
+
decorateSelectScroll(pop.el);
|
|
10458
|
+
pop.reposition();
|
|
10459
|
+
return pop;
|
|
10460
|
+
}
|
|
10461
|
+
var Select = class {
|
|
10462
|
+
constructor(opts) {
|
|
10463
|
+
this.list = null;
|
|
10464
|
+
const doc = opts.list?.host?.ownerDocument ?? document;
|
|
10465
|
+
ensureSelectStyles(doc);
|
|
10466
|
+
this.ctrl = selectController(opts);
|
|
10467
|
+
this.listOpts = { ...opts.list, theme: opts.theme ?? opts.list?.theme, size: this.ctrl.size };
|
|
10468
|
+
const wrap = doc.createElement("div");
|
|
10469
|
+
wrap.className = "vela-select";
|
|
10470
|
+
wrap.dataset.size = this.ctrl.size;
|
|
10471
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
10472
|
+
const btn = doc.createElement("button");
|
|
10473
|
+
btn.type = "button";
|
|
10474
|
+
if (opts.id) btn.id = opts.id;
|
|
10475
|
+
btn.className = "vela-select-trigger";
|
|
10476
|
+
if (this.ctrl.disabled) btn.disabled = true;
|
|
10477
|
+
const label = doc.createElement("span");
|
|
10478
|
+
label.className = "vela-select-label";
|
|
10479
|
+
label.textContent = this.ctrl.labelOf(this.ctrl.value);
|
|
10480
|
+
const chevron = doc.createElement("span");
|
|
10481
|
+
chevron.className = "vela-select-chevron";
|
|
10482
|
+
chevron.appendChild(iconEl("chevron-down", doc));
|
|
10483
|
+
btn.append(label, chevron);
|
|
10484
|
+
wrap.appendChild(btn);
|
|
10485
|
+
const sizer = doc.createElement("div");
|
|
10486
|
+
sizer.className = "vela-select-sizer";
|
|
10487
|
+
sizer.setAttribute("aria-hidden", "true");
|
|
10488
|
+
for (const o of this.ctrl.options) {
|
|
10489
|
+
const s = doc.createElement("span");
|
|
10490
|
+
s.textContent = o.label;
|
|
10491
|
+
sizer.appendChild(s);
|
|
10492
|
+
}
|
|
10493
|
+
wrap.appendChild(sizer);
|
|
10494
|
+
btn.addEventListener("pointerdown", (e) => {
|
|
10495
|
+
e.preventDefault();
|
|
10496
|
+
btn.focus({ preventScroll: true });
|
|
10497
|
+
});
|
|
10498
|
+
btn.addEventListener("click", (e) => {
|
|
10499
|
+
e.stopPropagation();
|
|
10500
|
+
this.toggle();
|
|
10501
|
+
});
|
|
10502
|
+
this.el = wrap;
|
|
10503
|
+
this.trigger = btn;
|
|
10504
|
+
this.labelEl = label;
|
|
10505
|
+
}
|
|
10506
|
+
get value() {
|
|
10507
|
+
return this.ctrl.value;
|
|
10508
|
+
}
|
|
10509
|
+
setValue(v) {
|
|
10510
|
+
this.ctrl.setValue(v);
|
|
10511
|
+
this.labelEl.textContent = this.ctrl.labelOf(v);
|
|
10512
|
+
}
|
|
10513
|
+
toggle() {
|
|
10514
|
+
this.list = toggleSelectList(
|
|
10515
|
+
this.trigger,
|
|
10516
|
+
this.ctrl.options,
|
|
10517
|
+
this.ctrl.value,
|
|
10518
|
+
(value, label) => {
|
|
10519
|
+
this.ctrl.pick(value);
|
|
10520
|
+
this.labelEl.textContent = label;
|
|
10521
|
+
this.list = null;
|
|
10522
|
+
},
|
|
10523
|
+
{ ...this.listOpts, onClose: () => {
|
|
10524
|
+
this.list = null;
|
|
10525
|
+
this.listOpts.onClose?.();
|
|
10526
|
+
} }
|
|
10527
|
+
);
|
|
10528
|
+
}
|
|
10529
|
+
destroy() {
|
|
10530
|
+
this.list?.hide();
|
|
10531
|
+
this.list = null;
|
|
10532
|
+
}
|
|
10533
|
+
};
|
|
10534
|
+
|
|
10535
|
+
// src/ui/components/number-input/controller.ts
|
|
10536
|
+
function clampNumber(n, opts) {
|
|
10537
|
+
let v = n;
|
|
10538
|
+
if (opts.min !== void 0) v = Math.max(opts.min, v);
|
|
10539
|
+
if (opts.max !== void 0) v = Math.min(opts.max, v);
|
|
10540
|
+
if (opts.integer) v = Math.round(v);
|
|
10541
|
+
return v;
|
|
10542
|
+
}
|
|
10543
|
+
function decimalsOf(n) {
|
|
10544
|
+
const s = String(n);
|
|
10545
|
+
const e = s.search(/[eE]/);
|
|
10546
|
+
if (e >= 0) {
|
|
10547
|
+
const frac = s.slice(0, e).split(".")[1]?.length ?? 0;
|
|
10548
|
+
return Math.max(0, Math.min(20, frac - Number(s.slice(e + 1))));
|
|
10549
|
+
}
|
|
10550
|
+
return s.split(".")[1]?.length ?? 0;
|
|
10551
|
+
}
|
|
10552
|
+
function snapToStep(n, base, step) {
|
|
10553
|
+
const d = Math.min(20, Math.max(decimalsOf(base), decimalsOf(step)));
|
|
10554
|
+
return Number(n.toFixed(d));
|
|
10555
|
+
}
|
|
10556
|
+
function numberInputController(opts) {
|
|
10557
|
+
let value = opts.value;
|
|
10558
|
+
const integer = opts.integer ?? false;
|
|
10559
|
+
const commit = opts.commit ?? "blur";
|
|
10560
|
+
const clamp = opts.clamp ?? commit === "blur";
|
|
10561
|
+
const step = opts.step ?? (integer ? 1 : 0.1);
|
|
10562
|
+
const onChange = opts.onChange;
|
|
10563
|
+
const normalize = (raw) => clamp ? clampNumber(raw, { min: opts.min, max: opts.max, integer }) : raw;
|
|
10564
|
+
const apply = (raw) => {
|
|
10565
|
+
if (!Number.isFinite(raw)) return null;
|
|
10566
|
+
const n = normalize(raw);
|
|
10567
|
+
if (n !== value) {
|
|
10568
|
+
value = n;
|
|
10569
|
+
onChange?.(n);
|
|
10570
|
+
}
|
|
10571
|
+
return n;
|
|
10572
|
+
};
|
|
10573
|
+
return {
|
|
10574
|
+
get value() {
|
|
10575
|
+
return value;
|
|
10576
|
+
},
|
|
10577
|
+
min: opts.min,
|
|
10578
|
+
max: opts.max,
|
|
10579
|
+
step,
|
|
10580
|
+
integer,
|
|
10581
|
+
size: opts.size ?? "md",
|
|
10582
|
+
commit,
|
|
10583
|
+
steppers: opts.steppers ?? commit === "blur",
|
|
10584
|
+
clamp,
|
|
10585
|
+
disabled: opts.disabled ?? false,
|
|
10586
|
+
apply,
|
|
10587
|
+
sync(raw) {
|
|
10588
|
+
const n = Number.isFinite(raw) ? normalize(raw) : value;
|
|
10589
|
+
value = n;
|
|
10590
|
+
return n;
|
|
10591
|
+
},
|
|
10592
|
+
nudge(dir) {
|
|
10593
|
+
return apply(snapToStep(value + dir * step, value, step));
|
|
10594
|
+
}
|
|
10595
|
+
};
|
|
10596
|
+
}
|
|
10597
|
+
|
|
10598
|
+
// src/ui/components/number-input/styles.ts
|
|
10599
|
+
var NUMBER_STYLE_ID = "vela-ui-number";
|
|
10600
|
+
var NUMBER_CSS = `
|
|
10601
|
+
.vela-num { position: relative; display: inline-block; min-width: 0; }
|
|
10602
|
+
.vela-num[data-fill] { width: 100%; }
|
|
10603
|
+
.vela-num input {
|
|
10604
|
+
width: 100%;
|
|
10605
|
+
box-sizing: border-box;
|
|
10606
|
+
height: 34px;
|
|
10607
|
+
background: transparent;
|
|
10608
|
+
border: 1px solid var(--vela-border-strong);
|
|
10609
|
+
border-radius: 6px;
|
|
10610
|
+
color: var(--vela-fg-bright);
|
|
10611
|
+
padding: 0 8px;
|
|
10612
|
+
font-size: 14px;
|
|
10613
|
+
font-family: inherit;
|
|
10614
|
+
outline: none;
|
|
10615
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
10616
|
+
-moz-appearance: textfield;
|
|
10617
|
+
appearance: textfield;
|
|
10618
|
+
}
|
|
10619
|
+
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
10620
|
+
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
10621
|
+
.vela-num input:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
10622
|
+
.vela-num[data-steppers] input { padding-right: 26px; }
|
|
10623
|
+
.vela-num[data-size='sm'] { width: 64px; flex: none; }
|
|
10624
|
+
.vela-num[data-size='sm'][data-compact] { width: 56px; }
|
|
10625
|
+
.vela-num[data-size='sm'] input {
|
|
10626
|
+
height: 28px;
|
|
10627
|
+
background: var(--vela-surface-elev);
|
|
10628
|
+
border-radius: var(--vela-radius-sm);
|
|
10629
|
+
color: var(--vela-fg);
|
|
10630
|
+
font-size: 13px;
|
|
10631
|
+
box-shadow: none;
|
|
10632
|
+
}
|
|
10633
|
+
.vela-num[data-size='sm'] input:focus { box-shadow: none; }
|
|
10634
|
+
.vela-num-step {
|
|
10635
|
+
position: absolute;
|
|
10636
|
+
right: 0;
|
|
10637
|
+
top: 0;
|
|
10638
|
+
bottom: 0;
|
|
10639
|
+
width: 22px;
|
|
10640
|
+
display: none;
|
|
10641
|
+
flex-direction: column;
|
|
10642
|
+
justify-content: center;
|
|
10643
|
+
box-sizing: border-box;
|
|
10644
|
+
padding: 2px 6px 2px 0;
|
|
10645
|
+
line-height: 0;
|
|
10646
|
+
}
|
|
10647
|
+
.vela-num[data-steppers]:hover .vela-num-step { display: flex; }
|
|
10648
|
+
.vela-num-step button {
|
|
10649
|
+
flex: 1;
|
|
10650
|
+
width: 100%;
|
|
10651
|
+
min-height: 0;
|
|
10652
|
+
border: none;
|
|
10653
|
+
background: transparent;
|
|
10654
|
+
color: var(--vela-fg-muted);
|
|
10655
|
+
border-radius: 3px;
|
|
10656
|
+
padding: 0;
|
|
10657
|
+
cursor: pointer;
|
|
10658
|
+
display: flex;
|
|
10659
|
+
align-items: center;
|
|
10660
|
+
justify-content: center;
|
|
10661
|
+
}
|
|
10662
|
+
.vela-num-step button:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
10663
|
+
.vela-num-step .vela-icon, .vela-num-step svg { width: 12px; height: 12px; display: block; }
|
|
10664
|
+
`;
|
|
10665
|
+
|
|
10666
|
+
// src/ui/components/number-input/view.ts
|
|
10667
|
+
var NumberInput = class {
|
|
10668
|
+
constructor(opts) {
|
|
10669
|
+
const doc = document;
|
|
10670
|
+
injectStyles(NUMBER_STYLE_ID, NUMBER_CSS, doc);
|
|
10671
|
+
this.ctrl = numberInputController(opts);
|
|
10672
|
+
this.placeholderMode = opts.placeholder !== void 0;
|
|
10673
|
+
this.emptyValue = opts.emptyValue ?? opts.value;
|
|
10674
|
+
this.last = String(opts.value);
|
|
10675
|
+
const wrap = doc.createElement("div");
|
|
10676
|
+
wrap.className = "vela-num";
|
|
10677
|
+
wrap.dataset.size = this.ctrl.size;
|
|
10678
|
+
if (this.ctrl.size !== "sm") wrap.dataset.fill = "";
|
|
10679
|
+
if (opts.compact) wrap.dataset.compact = "";
|
|
10680
|
+
if (this.ctrl.steppers) wrap.dataset.steppers = "";
|
|
10681
|
+
const ni = doc.createElement("input");
|
|
10682
|
+
ni.type = "number";
|
|
10683
|
+
if (opts.id) ni.id = opts.id;
|
|
10684
|
+
if (opts.title) ni.title = opts.title;
|
|
10685
|
+
if (opts.min !== void 0) ni.min = String(opts.min);
|
|
10686
|
+
if (opts.max !== void 0) ni.max = String(opts.max);
|
|
10687
|
+
ni.step = String(this.ctrl.step);
|
|
10688
|
+
if (this.ctrl.disabled) ni.disabled = true;
|
|
10689
|
+
if (this.placeholderMode) {
|
|
10690
|
+
ni.placeholder = opts.placeholder ?? "";
|
|
10691
|
+
ni.value = opts.value !== this.emptyValue ? String(opts.value) : "";
|
|
10692
|
+
} else {
|
|
10693
|
+
ni.value = String(opts.value);
|
|
10694
|
+
}
|
|
10695
|
+
const commitRaw = (raw) => {
|
|
10696
|
+
const n = this.ctrl.apply(raw);
|
|
10697
|
+
if (n === null) {
|
|
10698
|
+
ni.value = this.placeholderMode && this.ctrl.value === this.emptyValue ? "" : this.last;
|
|
10699
|
+
return;
|
|
10700
|
+
}
|
|
10701
|
+
this.last = String(n);
|
|
10702
|
+
if (this.placeholderMode && n === this.emptyValue) ni.value = "";
|
|
10703
|
+
else ni.value = String(n);
|
|
10704
|
+
};
|
|
10705
|
+
if (this.placeholderMode) {
|
|
10706
|
+
ni.addEventListener("change", () => {
|
|
10707
|
+
const raw = ni.value.trim() === "" ? this.emptyValue : Number(ni.value);
|
|
10708
|
+
commitRaw(Number.isFinite(raw) ? raw : this.emptyValue);
|
|
10709
|
+
});
|
|
10710
|
+
} else if (this.ctrl.commit === "live") {
|
|
10711
|
+
ni.addEventListener("input", () => {
|
|
10712
|
+
const v = Number(ni.value);
|
|
10713
|
+
if (Number.isFinite(v)) this.ctrl.apply(v);
|
|
10714
|
+
});
|
|
10715
|
+
} else {
|
|
10716
|
+
ni.addEventListener("blur", () => commitRaw(Number(ni.value)));
|
|
10717
|
+
ni.addEventListener("keydown", (e) => {
|
|
10718
|
+
if (e.key === "Enter") {
|
|
10719
|
+
e.preventDefault();
|
|
10720
|
+
ni.blur();
|
|
10721
|
+
}
|
|
10722
|
+
});
|
|
10723
|
+
}
|
|
10724
|
+
wrap.appendChild(ni);
|
|
10725
|
+
if (this.ctrl.steppers) {
|
|
10726
|
+
wrap.appendChild(this.buildSteppers(doc, (dir) => {
|
|
10727
|
+
const cur = Number(ni.value);
|
|
10728
|
+
const base = Number.isFinite(cur) ? cur : this.ctrl.value;
|
|
10729
|
+
commitRaw(snapToStep(base + dir * this.ctrl.step, base, this.ctrl.step));
|
|
10730
|
+
}));
|
|
10731
|
+
}
|
|
10732
|
+
this.el = wrap;
|
|
10733
|
+
this.input = ni;
|
|
10734
|
+
}
|
|
10735
|
+
get value() {
|
|
10736
|
+
return this.ctrl.value;
|
|
10737
|
+
}
|
|
10738
|
+
setValue(v) {
|
|
10739
|
+
const n = this.ctrl.sync(v);
|
|
10740
|
+
this.last = String(n);
|
|
10741
|
+
if (this.placeholderMode && n === this.emptyValue) this.input.value = "";
|
|
10742
|
+
else this.input.value = String(n);
|
|
10743
|
+
}
|
|
10744
|
+
buildSteppers(doc, applyDir) {
|
|
10745
|
+
const steps = doc.createElement("div");
|
|
10746
|
+
steps.className = "vela-num-step";
|
|
10747
|
+
const mk = (dir, icon2, label) => {
|
|
10748
|
+
const b = doc.createElement("button");
|
|
10749
|
+
b.type = "button";
|
|
10750
|
+
b.tabIndex = -1;
|
|
10751
|
+
b.setAttribute("aria-label", label);
|
|
10752
|
+
b.appendChild(iconEl(icon2, doc));
|
|
10753
|
+
let timer = 0;
|
|
10754
|
+
const apply = () => applyDir(dir);
|
|
10755
|
+
const stop = () => {
|
|
10756
|
+
if (timer) {
|
|
10757
|
+
window.clearTimeout(timer);
|
|
10758
|
+
timer = 0;
|
|
10759
|
+
}
|
|
10760
|
+
};
|
|
10761
|
+
b.addEventListener("pointerdown", (e) => {
|
|
10762
|
+
e.preventDefault();
|
|
10763
|
+
e.stopPropagation();
|
|
10764
|
+
apply();
|
|
10765
|
+
timer = window.setTimeout(function tick() {
|
|
10766
|
+
apply();
|
|
10767
|
+
timer = window.setTimeout(tick, 60);
|
|
10768
|
+
}, 400);
|
|
10769
|
+
});
|
|
10770
|
+
b.addEventListener("pointerup", stop);
|
|
10771
|
+
b.addEventListener("pointerleave", stop);
|
|
10772
|
+
b.addEventListener("pointercancel", stop);
|
|
10773
|
+
return b;
|
|
10774
|
+
};
|
|
10775
|
+
steps.append(mk(1, "chevron-up", "Increase"), mk(-1, "chevron-down", "Decrease"));
|
|
10776
|
+
return steps;
|
|
10777
|
+
}
|
|
10778
|
+
};
|
|
10779
|
+
|
|
10780
|
+
// src/ui/components/text-field/controller.ts
|
|
10781
|
+
function textFieldController(opts = {}) {
|
|
10782
|
+
let value = opts.value ?? "";
|
|
10783
|
+
const size = opts.size ?? "md";
|
|
10784
|
+
return {
|
|
10785
|
+
get value() {
|
|
10786
|
+
return value;
|
|
10787
|
+
},
|
|
10788
|
+
size,
|
|
10789
|
+
fill: opts.fill ?? size !== "sm",
|
|
10790
|
+
disabled: opts.disabled ?? false,
|
|
10791
|
+
commit(next) {
|
|
10792
|
+
if (next === value) return null;
|
|
10793
|
+
value = next;
|
|
10794
|
+
opts.onChange?.(next);
|
|
10795
|
+
return next;
|
|
10796
|
+
},
|
|
10797
|
+
sync(next) {
|
|
10798
|
+
value = next;
|
|
10799
|
+
}
|
|
10800
|
+
};
|
|
10801
|
+
}
|
|
10802
|
+
|
|
10803
|
+
// src/ui/components/text-field/styles.ts
|
|
10804
|
+
var TEXT_STYLE_ID = "vela-ui-text";
|
|
10805
|
+
var TEXT_CSS = `
|
|
10806
|
+
.vela-text {
|
|
10807
|
+
display: inline-block;
|
|
10808
|
+
min-width: 0;
|
|
10809
|
+
}
|
|
10810
|
+
.vela-text[data-fill] { width: 100%; }
|
|
10811
|
+
.vela-text-field {
|
|
10812
|
+
width: 100%;
|
|
10813
|
+
box-sizing: border-box;
|
|
10814
|
+
height: 34px;
|
|
10815
|
+
background: transparent;
|
|
10816
|
+
border: 1px solid var(--vela-border-strong);
|
|
10817
|
+
border-radius: 6px;
|
|
10818
|
+
color: var(--vela-fg-bright);
|
|
10819
|
+
padding: 0 8px;
|
|
10820
|
+
font-size: 14px;
|
|
10821
|
+
font-family: inherit;
|
|
10822
|
+
outline: none;
|
|
10823
|
+
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
10824
|
+
}
|
|
10825
|
+
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
10826
|
+
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
10827
|
+
.vela-text[data-size='sm'] .vela-text-field {
|
|
10828
|
+
height: 28px;
|
|
10829
|
+
background: var(--vela-surface-elev);
|
|
10830
|
+
border-radius: var(--vela-radius-sm);
|
|
10831
|
+
color: var(--vela-fg);
|
|
10832
|
+
font-size: 13px;
|
|
10833
|
+
}
|
|
10834
|
+
`;
|
|
10835
|
+
|
|
10836
|
+
// src/ui/components/text-field/view.ts
|
|
10837
|
+
var TextField = class {
|
|
10838
|
+
constructor(opts = {}) {
|
|
10839
|
+
const doc = document;
|
|
10840
|
+
injectStyles(TEXT_STYLE_ID, TEXT_CSS, doc);
|
|
10841
|
+
this.ctrl = textFieldController(opts);
|
|
10842
|
+
const wrap = doc.createElement("div");
|
|
10843
|
+
wrap.className = "vela-text";
|
|
10844
|
+
wrap.dataset.size = this.ctrl.size;
|
|
10845
|
+
if (this.ctrl.fill) wrap.dataset.fill = "";
|
|
10846
|
+
const ti = doc.createElement("input");
|
|
10847
|
+
ti.type = "text";
|
|
10848
|
+
if (opts.id) ti.id = opts.id;
|
|
10849
|
+
ti.value = this.ctrl.value;
|
|
10850
|
+
ti.className = "vela-text-field";
|
|
10851
|
+
if (this.ctrl.disabled) ti.disabled = true;
|
|
10852
|
+
ti.addEventListener("blur", () => {
|
|
10853
|
+
this.ctrl.commit(ti.value);
|
|
10854
|
+
});
|
|
10855
|
+
ti.addEventListener("keydown", (e) => {
|
|
10856
|
+
if (e.key === "Enter") {
|
|
10857
|
+
e.preventDefault();
|
|
10858
|
+
ti.blur();
|
|
10859
|
+
}
|
|
10860
|
+
});
|
|
10861
|
+
wrap.appendChild(ti);
|
|
10862
|
+
this.el = wrap;
|
|
10863
|
+
this.input = ti;
|
|
10864
|
+
}
|
|
10865
|
+
get value() {
|
|
10866
|
+
return this.ctrl.value;
|
|
10867
|
+
}
|
|
10868
|
+
setValue(v) {
|
|
10869
|
+
this.ctrl.sync(v);
|
|
10870
|
+
this.input.value = v;
|
|
10871
|
+
}
|
|
10872
|
+
};
|
|
10873
|
+
|
|
10874
|
+
// src/ui/components/color-picker/controller.ts
|
|
10875
|
+
function splitColor(color) {
|
|
10876
|
+
const s = String(color ?? "").trim();
|
|
10877
|
+
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
10878
|
+
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
10879
|
+
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
10880
|
+
if (m) {
|
|
10881
|
+
const h = m[1].toLowerCase();
|
|
10882
|
+
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
10883
|
+
}
|
|
10884
|
+
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
10885
|
+
if (rgba) {
|
|
10886
|
+
const p = rgba[1].split(",").map((v) => v.trim());
|
|
10887
|
+
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
10888
|
+
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
10889
|
+
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
10890
|
+
}
|
|
10891
|
+
return { hex6: ACCENT_BRIGHT, alpha: 1 };
|
|
10892
|
+
}
|
|
10893
|
+
function combineColor(hex6, alpha) {
|
|
10894
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
10895
|
+
if (a >= 0.999) return hex6;
|
|
10896
|
+
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
10897
|
+
}
|
|
10898
|
+
function blendOver(fg, bg, alpha) {
|
|
10899
|
+
const rgb = (c) => {
|
|
10900
|
+
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
10901
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
10902
|
+
};
|
|
10903
|
+
const [fr, fgc, fb] = rgb(fg);
|
|
10904
|
+
const [br, bgc, bb] = rgb(bg);
|
|
10905
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
10906
|
+
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
10907
|
+
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
10908
|
+
}
|
|
10909
|
+
function hslHex(h, s, l) {
|
|
10910
|
+
const sn = s / 100;
|
|
10911
|
+
const ln = l / 100;
|
|
10912
|
+
const k = (n) => (n + h / 30) % 12;
|
|
10913
|
+
const a = sn * Math.min(ln, 1 - ln);
|
|
10914
|
+
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
10915
|
+
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
10916
|
+
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
10917
|
+
}
|
|
10918
|
+
function buildPalette() {
|
|
10919
|
+
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
10920
|
+
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
10921
|
+
const levels = [
|
|
10922
|
+
[82, 56],
|
|
10923
|
+
[58, 84],
|
|
10924
|
+
[62, 74],
|
|
10925
|
+
[66, 62],
|
|
10926
|
+
[70, 50],
|
|
10927
|
+
[64, 39],
|
|
10928
|
+
[54, 29]
|
|
10929
|
+
];
|
|
10930
|
+
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
10931
|
+
}
|
|
10932
|
+
function transparencyChecker(size) {
|
|
10933
|
+
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
10934
|
+
}
|
|
10935
|
+
|
|
10936
|
+
// src/ui/components/color-picker/styles.ts
|
|
10937
|
+
var COLOR_STYLE_ID = "vela-ui-color";
|
|
10938
|
+
var COLOR_CSS = `
|
|
10939
|
+
.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;}
|
|
10940
|
+
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
10941
|
+
.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);}
|
|
10942
|
+
.vela-color-field-circle{width:26px;height:26px;padding:3px;border:1px solid var(--vela-border-strong);border-radius:4px;background:transparent;overflow:hidden;}
|
|
10943
|
+
.vela-color-field-circle .vela-color-field-swatch{border-radius:2px;box-shadow:none;}
|
|
10944
|
+
.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;}
|
|
10945
|
+
`;
|
|
10946
|
+
|
|
10947
|
+
// src/ui/components/color-picker/view.ts
|
|
10948
|
+
var PALETTE = buildPalette();
|
|
10949
|
+
var CHECKER = transparencyChecker(10);
|
|
10950
|
+
var FIELD_CHECKER = transparencyChecker(8);
|
|
10951
|
+
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
10952
|
+
function addRecent(hex6) {
|
|
10953
|
+
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
10954
|
+
if (i >= 0) recents.splice(i, 1);
|
|
10955
|
+
recents.unshift(hex6);
|
|
10956
|
+
if (recents.length > 10) recents.length = 10;
|
|
10957
|
+
}
|
|
10958
|
+
function buildColorPicker(color, theme, onChange) {
|
|
10959
|
+
const parsed = splitColor(color);
|
|
10960
|
+
let curHex = parsed.hex6;
|
|
10961
|
+
let curAlpha = parsed.alpha;
|
|
10962
|
+
const root = document.createElement("div");
|
|
10963
|
+
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
10964
|
+
const grid = document.createElement("div");
|
|
10965
|
+
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
10966
|
+
const swatches = [];
|
|
10967
|
+
for (const row of PALETTE) {
|
|
10968
|
+
for (const c of row) {
|
|
10969
|
+
const sw = document.createElement("button");
|
|
10970
|
+
sw.type = "button";
|
|
10971
|
+
sw.dataset.c = c;
|
|
10972
|
+
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
10973
|
+
sw.addEventListener("click", (e) => {
|
|
10974
|
+
e.stopPropagation();
|
|
10975
|
+
pickHex(c);
|
|
10976
|
+
});
|
|
10977
|
+
grid.appendChild(sw);
|
|
10978
|
+
swatches.push(sw);
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10981
|
+
const paintSelection = () => {
|
|
10982
|
+
for (const sw of swatches) {
|
|
10983
|
+
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
10984
|
+
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
10985
|
+
sw.style.outlineOffset = "2px";
|
|
10986
|
+
sw.style.zIndex = on ? "1" : "";
|
|
10987
|
+
sw.style.position = on ? "relative" : "";
|
|
10988
|
+
}
|
|
10989
|
+
};
|
|
10990
|
+
const recentRow = document.createElement("div");
|
|
10991
|
+
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
10992
|
+
const renderRecents = () => {
|
|
10993
|
+
recentRow.replaceChildren();
|
|
10994
|
+
for (const c of recents) {
|
|
10995
|
+
const sw = document.createElement("button");
|
|
10996
|
+
sw.type = "button";
|
|
10997
|
+
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;`;
|
|
10998
|
+
sw.addEventListener("click", (e) => {
|
|
10999
|
+
e.stopPropagation();
|
|
11000
|
+
pickHex(c);
|
|
11001
|
+
});
|
|
11002
|
+
recentRow.appendChild(sw);
|
|
11003
|
+
}
|
|
11004
|
+
const add = document.createElement("label");
|
|
11005
|
+
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;`;
|
|
11006
|
+
add.textContent = "+";
|
|
11007
|
+
const input = document.createElement("input");
|
|
11008
|
+
input.type = "color";
|
|
11009
|
+
input.value = curHex;
|
|
11010
|
+
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
11011
|
+
input.addEventListener("input", () => pickHex(input.value, true));
|
|
11012
|
+
add.appendChild(input);
|
|
11013
|
+
recentRow.appendChild(add);
|
|
11014
|
+
};
|
|
11015
|
+
const opLabel = document.createElement("div");
|
|
11016
|
+
opLabel.textContent = "Opacity";
|
|
11017
|
+
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
11018
|
+
const opRow = document.createElement("div");
|
|
11019
|
+
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
11020
|
+
const track = document.createElement("div");
|
|
11021
|
+
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
11022
|
+
const knob = document.createElement("div");
|
|
11023
|
+
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;";
|
|
11024
|
+
track.appendChild(knob);
|
|
11025
|
+
const pctBox = document.createElement("div");
|
|
11026
|
+
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;`;
|
|
11027
|
+
opRow.append(track, pctBox);
|
|
11028
|
+
const paintOpacity = () => {
|
|
11029
|
+
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
11030
|
+
knob.style.left = `${curAlpha * 100}%`;
|
|
11031
|
+
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
11032
|
+
};
|
|
11033
|
+
let dragging = false;
|
|
11034
|
+
const onDrag = (clientX) => {
|
|
11035
|
+
const r = track.getBoundingClientRect();
|
|
11036
|
+
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
11037
|
+
paintOpacity();
|
|
11038
|
+
emit();
|
|
11039
|
+
};
|
|
11040
|
+
track.addEventListener("pointerdown", (e) => {
|
|
11041
|
+
e.stopPropagation();
|
|
11042
|
+
dragging = true;
|
|
11043
|
+
track.setPointerCapture(e.pointerId);
|
|
11044
|
+
onDrag(e.clientX);
|
|
11045
|
+
});
|
|
11046
|
+
track.addEventListener("pointermove", (e) => {
|
|
11047
|
+
if (dragging) onDrag(e.clientX);
|
|
11048
|
+
});
|
|
11049
|
+
track.addEventListener("pointerup", () => dragging = false);
|
|
11050
|
+
function emit() {
|
|
11051
|
+
onChange(combineColor(curHex, curAlpha));
|
|
11052
|
+
}
|
|
11053
|
+
function pickHex(hex, custom = false) {
|
|
11054
|
+
curHex = splitColor(hex).hex6;
|
|
11055
|
+
addRecent(curHex);
|
|
11056
|
+
paintSelection();
|
|
11057
|
+
paintOpacity();
|
|
11058
|
+
if (custom) renderRecents();
|
|
11059
|
+
emit();
|
|
11060
|
+
}
|
|
11061
|
+
renderRecents();
|
|
11062
|
+
paintSelection();
|
|
11063
|
+
paintOpacity();
|
|
11064
|
+
root.append(grid, recentRow, opLabel, opRow);
|
|
11065
|
+
return root;
|
|
11066
|
+
}
|
|
11067
|
+
function colorField(theme, getVal, onVal, opts) {
|
|
11068
|
+
return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
|
|
11069
|
+
}
|
|
11070
|
+
var ColorField = class {
|
|
11071
|
+
constructor(opts) {
|
|
11072
|
+
injectStyles(COLOR_STYLE_ID, COLOR_CSS, document);
|
|
11073
|
+
this.theme = opts.theme;
|
|
11074
|
+
this.getVal = opts.getVal;
|
|
11075
|
+
this.onVal = opts.onVal;
|
|
11076
|
+
this.popoverOpts = opts.popover;
|
|
11077
|
+
const trigger = document.createElement("button");
|
|
11078
|
+
trigger.type = "button";
|
|
11079
|
+
if (opts.id) trigger.id = opts.id;
|
|
11080
|
+
trigger.className = opts.shape === "circle" ? "vela-color-field vela-color-field-circle" : "vela-color-field";
|
|
11081
|
+
const swatch = document.createElement("span");
|
|
11082
|
+
swatch.className = "vela-color-field-swatch";
|
|
11083
|
+
trigger.appendChild(swatch);
|
|
11084
|
+
this.el = trigger;
|
|
11085
|
+
this.swatch = swatch;
|
|
11086
|
+
this.paint();
|
|
11087
|
+
trigger.addEventListener("vela-sync", () => this.paint());
|
|
11088
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
11089
|
+
e.preventDefault();
|
|
11090
|
+
trigger.focus({ preventScroll: true });
|
|
11091
|
+
});
|
|
11092
|
+
trigger.addEventListener("click", (e) => {
|
|
11093
|
+
e.stopPropagation();
|
|
11094
|
+
this.toggle();
|
|
11095
|
+
});
|
|
11096
|
+
}
|
|
11097
|
+
paint() {
|
|
11098
|
+
const v = this.getVal();
|
|
11099
|
+
this.swatch.style.background = `linear-gradient(${v}, ${v}), ${FIELD_CHECKER}`;
|
|
11100
|
+
}
|
|
11101
|
+
toggle() {
|
|
11102
|
+
if (openPopoverTrigger() === this.el) {
|
|
11103
|
+
closeOpenPopovers();
|
|
11104
|
+
return;
|
|
11105
|
+
}
|
|
11106
|
+
const pop = new Popover({
|
|
11107
|
+
trigger: this.el,
|
|
11108
|
+
theme: this.theme,
|
|
11109
|
+
align: this.popoverOpts?.align ?? "end",
|
|
11110
|
+
gap: this.popoverOpts?.gap ?? 6,
|
|
11111
|
+
host: this.popoverOpts?.host,
|
|
11112
|
+
position: this.popoverOpts?.position,
|
|
11113
|
+
boundary: this.popoverOpts?.boundary,
|
|
11114
|
+
zIndex: this.popoverOpts?.zIndex,
|
|
11115
|
+
className: "vela-color-field-pop",
|
|
11116
|
+
content: buildColorPicker(this.getVal(), this.theme, (val) => {
|
|
11117
|
+
this.onVal(val);
|
|
11118
|
+
this.paint();
|
|
11119
|
+
})
|
|
11120
|
+
});
|
|
11121
|
+
pop.show();
|
|
11122
|
+
}
|
|
11123
|
+
};
|
|
11124
|
+
|
|
9884
11125
|
// src/renderers/shared/InputsUI.ts
|
|
9885
11126
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
9886
11127
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
@@ -9889,6 +11130,7 @@ var LEGEND_CTL_PX = 18;
|
|
|
9889
11130
|
var LEGEND_ROW_PAD_Y = 2;
|
|
9890
11131
|
var LEGEND_ROW_PAD_X = 6;
|
|
9891
11132
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
11133
|
+
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
9892
11134
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
9893
11135
|
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
11136
|
var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
|
|
@@ -9933,6 +11175,11 @@ var InputsUI = class {
|
|
|
9933
11175
|
this.dialogTips = [];
|
|
9934
11176
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
9935
11177
|
this.moveMenu = null;
|
|
11178
|
+
/** Open settings-dialog choice menu (custom dropdown — torn down with the dialog). */
|
|
11179
|
+
this.choicePop = null;
|
|
11180
|
+
/** Open settings-dialog calendar popover (torn down with the dialog). */
|
|
11181
|
+
this.calendarPop = null;
|
|
11182
|
+
this.calendarAnchor = null;
|
|
9936
11183
|
/** Collapsed panes → the master indicator id to keep visible (others hidden in the strip). */
|
|
9937
11184
|
this.paneCollapse = /* @__PURE__ */ new Map();
|
|
9938
11185
|
/** The user folded the indicator legend away behind the chevron — CHART-WIDE: every
|
|
@@ -9958,6 +11205,10 @@ var InputsUI = class {
|
|
|
9958
11205
|
this.onDialogKey = (e) => {
|
|
9959
11206
|
if (e.key === "Escape") {
|
|
9960
11207
|
e.preventDefault();
|
|
11208
|
+
if (isPopoverOpen()) {
|
|
11209
|
+
closeOpenPopovers();
|
|
11210
|
+
return;
|
|
11211
|
+
}
|
|
9961
11212
|
this.closeDialog();
|
|
9962
11213
|
}
|
|
9963
11214
|
};
|
|
@@ -10403,6 +11654,7 @@ var InputsUI = class {
|
|
|
10403
11654
|
titleWrap.appendChild(beta);
|
|
10404
11655
|
}
|
|
10405
11656
|
el.appendChild(titleWrap);
|
|
11657
|
+
el.appendChild(statusEl);
|
|
10406
11658
|
const valuesEl = document.createElement("span");
|
|
10407
11659
|
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
11660
|
el.appendChild(valuesEl);
|
|
@@ -10463,7 +11715,6 @@ var InputsUI = class {
|
|
|
10463
11715
|
close.addEventListener("click", () => this.onRemove?.(id));
|
|
10464
11716
|
controlsEl.appendChild(close);
|
|
10465
11717
|
el.appendChild(controlsEl);
|
|
10466
|
-
el.appendChild(statusEl);
|
|
10467
11718
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
10468
11719
|
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
11720
|
this.syncFoldToggle();
|
|
@@ -10481,7 +11732,7 @@ var InputsUI = class {
|
|
|
10481
11732
|
/**
|
|
10482
11733
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
10483
11734
|
* 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
|
|
11735
|
+
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
10485
11736
|
*/
|
|
10486
11737
|
setStatus(id, status) {
|
|
10487
11738
|
const row = this.rows.get(id);
|
|
@@ -10493,7 +11744,7 @@ var InputsUI = class {
|
|
|
10493
11744
|
return;
|
|
10494
11745
|
}
|
|
10495
11746
|
if (status === "loading") {
|
|
10496
|
-
el.style.cssText =
|
|
11747
|
+
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
11748
|
for (let i = 0; i < 3; i += 1) {
|
|
10498
11749
|
const dot = document.createElement("span");
|
|
10499
11750
|
dot.style.cssText = "width:4px;height:4px;border-radius:50%;background:currentColor;opacity:0.15;flex:none;";
|
|
@@ -10503,7 +11754,7 @@ var InputsUI = class {
|
|
|
10503
11754
|
return;
|
|
10504
11755
|
}
|
|
10505
11756
|
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;`;
|
|
11757
|
+
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
11758
|
}
|
|
10508
11759
|
/** Inject the status keyframes once (idempotent). */
|
|
10509
11760
|
ensureStatusKeyframes() {
|
|
@@ -10556,15 +11807,15 @@ var InputsUI = class {
|
|
|
10556
11807
|
* construction and is shown/hidden with the cluster container.
|
|
10557
11808
|
*/
|
|
10558
11809
|
syncRowActions(row) {
|
|
10559
|
-
const
|
|
10560
|
-
row.controlsEl.style.display =
|
|
11810
|
+
const open2 = row.highlighted;
|
|
11811
|
+
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
10561
11812
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
10562
11813
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
10563
11814
|
if (child === row.extrasEl) {
|
|
10564
|
-
child.style.display =
|
|
11815
|
+
child.style.display = open2 ? "contents" : "none";
|
|
10565
11816
|
continue;
|
|
10566
11817
|
}
|
|
10567
|
-
child.style.display =
|
|
11818
|
+
child.style.display = open2 ? "inline-flex" : "none";
|
|
10568
11819
|
}
|
|
10569
11820
|
}
|
|
10570
11821
|
/** Drop the current selection outline, if any. */
|
|
@@ -10618,7 +11869,8 @@ var InputsUI = class {
|
|
|
10618
11869
|
/**
|
|
10619
11870
|
* Open the indicator's settings as a centered modal over the chart — a themed,
|
|
10620
11871
|
* dependency-free port of the app's tabbed script-settings dialog: a header with the
|
|
10621
|
-
* indicator title,
|
|
11872
|
+
* indicator title, a tab strip (the default "Inputs" tab plus any tab the inputs
|
|
11873
|
+
* declare via `tab=`), a scrollable body per tab that groups inputs by `group=`
|
|
10622
11874
|
* (section headers) and packs `inline=` inputs onto one row, and a sticky footer.
|
|
10623
11875
|
*
|
|
10624
11876
|
* Edits commit LIVE (each change re-runs the indicator), matching the current
|
|
@@ -10648,55 +11900,57 @@ var InputsUI = class {
|
|
|
10648
11900
|
});
|
|
10649
11901
|
const card = document.createElement("div");
|
|
10650
11902
|
card.className = "vela-ind-dialog";
|
|
10651
|
-
card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(
|
|
11903
|
+
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
11904
|
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
11905
|
applyChromeTokens(card, t);
|
|
10654
11906
|
card.addEventListener("keydown", (e) => {
|
|
10655
11907
|
if (e.key !== "Escape") e.stopPropagation();
|
|
10656
11908
|
});
|
|
10657
11909
|
const header = document.createElement("div");
|
|
10658
|
-
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px
|
|
11910
|
+
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 20px;flex:0 0 auto;";
|
|
10659
11911
|
const hTitle = document.createElement("span");
|
|
10660
11912
|
hTitle.textContent = row.settingsTitle;
|
|
10661
|
-
hTitle.style.cssText = "font-weight:600;font-size:
|
|
11913
|
+
hTitle.style.cssText = "font-weight:600;font-size:20px;line-height:28px;";
|
|
10662
11914
|
const closeBtn = document.createElement("button");
|
|
10663
11915
|
closeBtn.type = "button";
|
|
10664
11916
|
closeBtn.setAttribute("aria-label", "Close");
|
|
10665
11917
|
this.dialogTips.push(attachChromeTooltip(closeBtn, { host: this.container, theme: () => this.theme, text: () => "Close" }));
|
|
10666
11918
|
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;";
|
|
11919
|
+
closeBtn.className = "vela-ind-dlg-close";
|
|
10669
11920
|
closeBtn.addEventListener("click", () => this.closeDialog());
|
|
10670
11921
|
header.append(hTitle, closeBtn);
|
|
10671
11922
|
card.appendChild(header);
|
|
10672
11923
|
if (!this.mobileLayout) makeDialogDraggable(card, header, { closeSelector: "button" });
|
|
11924
|
+
const tabDefs = tabInputs(row.inputs);
|
|
10673
11925
|
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);
|
|
11926
|
+
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
11927
|
+
const tabEls = [];
|
|
11928
|
+
const bodies = [];
|
|
11929
|
+
const activate = (idx) => {
|
|
11930
|
+
for (let i = 0; i < tabEls.length; i += 1) {
|
|
11931
|
+
const active = i === idx;
|
|
11932
|
+
const el = tabEls[i];
|
|
11933
|
+
el.style.borderBottomColor = active ? fg : "transparent";
|
|
11934
|
+
el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
|
|
11935
|
+
el.classList.toggle("vela-ind-tab-active", active);
|
|
11936
|
+
bodies[i].style.display = active ? "grid" : "none";
|
|
10690
11937
|
}
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
|
|
11938
|
+
};
|
|
11939
|
+
for (const [idx, def] of tabDefs.entries()) {
|
|
11940
|
+
const tab = document.createElement("div");
|
|
11941
|
+
tab.textContent = def.name;
|
|
11942
|
+
tab.className = "vela-ind-tab";
|
|
11943
|
+
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;" : ""}`;
|
|
11944
|
+
tab.addEventListener("click", () => activate(idx));
|
|
11945
|
+
tabs.appendChild(tab);
|
|
11946
|
+
tabEls.push(tab);
|
|
11947
|
+
bodies.push(this.buildTabBody(def.inputs, row));
|
|
10696
11948
|
}
|
|
10697
|
-
card.appendChild(
|
|
11949
|
+
card.appendChild(tabs);
|
|
11950
|
+
for (const body of bodies) card.appendChild(body);
|
|
11951
|
+
activate(0);
|
|
10698
11952
|
const footer = document.createElement("div");
|
|
10699
|
-
footer.style.cssText = `display:flex;justify-content:flex-end;gap:
|
|
11953
|
+
footer.style.cssText = `display:flex;justify-content:flex-end;gap:12px;padding:16px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
|
|
10700
11954
|
footer.append(
|
|
10701
11955
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
10702
11956
|
this.dialogButton("Ok", true, () => this.closeDialog())
|
|
@@ -10708,8 +11962,32 @@ var InputsUI = class {
|
|
|
10708
11962
|
this.backdrop = backdrop;
|
|
10709
11963
|
document.addEventListener("keydown", this.onDialogKey);
|
|
10710
11964
|
}
|
|
11965
|
+
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
11966
|
+
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
11967
|
+
buildTabBody(inputs, row) {
|
|
11968
|
+
const body = document.createElement("div");
|
|
11969
|
+
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;";
|
|
11970
|
+
for (const group of groupInputs(inputs)) {
|
|
11971
|
+
const section = document.createElement("div");
|
|
11972
|
+
section.style.cssText = "display:contents;";
|
|
11973
|
+
if (group.name) {
|
|
11974
|
+
const gh = document.createElement("div");
|
|
11975
|
+
gh.textContent = group.name;
|
|
11976
|
+
const first = body.childElementCount === 0;
|
|
11977
|
+
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;`;
|
|
11978
|
+
section.appendChild(gh);
|
|
11979
|
+
}
|
|
11980
|
+
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
11981
|
+
body.appendChild(section);
|
|
11982
|
+
}
|
|
11983
|
+
return body;
|
|
11984
|
+
}
|
|
10711
11985
|
closeDialog() {
|
|
10712
11986
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
11987
|
+
closeOpenPopovers();
|
|
11988
|
+
this.choicePop = null;
|
|
11989
|
+
this.calendarPop = null;
|
|
11990
|
+
this.calendarAnchor = null;
|
|
10713
11991
|
for (const dispose of this.dialogTips.splice(0)) dispose();
|
|
10714
11992
|
this.backdrop?.remove();
|
|
10715
11993
|
this.backdrop = null;
|
|
@@ -10737,79 +12015,77 @@ var InputsUI = class {
|
|
|
10737
12015
|
buildInputRow(grid, decls, row) {
|
|
10738
12016
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
10739
12017
|
if (decls.length > 1) {
|
|
10740
|
-
const
|
|
10741
|
-
|
|
12018
|
+
const cell2 = document.createElement("div");
|
|
12019
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
|
|
10742
12020
|
for (const d of decls) {
|
|
10743
12021
|
const fit2 = d.type === "color" || d.type === "bool";
|
|
10744
12022
|
const toggleFirst = d.type === "bool";
|
|
10745
12023
|
const id2 = idOf(d);
|
|
10746
12024
|
const item = document.createElement("div");
|
|
10747
|
-
item.style.cssText = `display:flex;align-items:center;gap:
|
|
12025
|
+
item.style.cssText = `display:flex;align-items:center;gap:8px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
|
|
10748
12026
|
const name = nameOf(d);
|
|
10749
12027
|
if (name) {
|
|
10750
12028
|
const lbl = document.createElement("label");
|
|
10751
12029
|
lbl.htmlFor = id2;
|
|
10752
12030
|
lbl.textContent = name;
|
|
10753
|
-
lbl.style.cssText = `font-size:
|
|
12031
|
+
lbl.style.cssText = `font-size:14px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
|
|
10754
12032
|
item.appendChild(lbl);
|
|
10755
12033
|
}
|
|
10756
12034
|
const cw2 = document.createElement("div");
|
|
10757
|
-
cw2.style.cssText = fit2 ? "" : "width:
|
|
12035
|
+
cw2.style.cssText = fit2 ? "" : "width:100px;";
|
|
10758
12036
|
cw2.appendChild(this.buildControl(row, d, id2));
|
|
10759
12037
|
item.appendChild(cw2);
|
|
10760
|
-
|
|
12038
|
+
cell2.appendChild(item);
|
|
10761
12039
|
}
|
|
10762
12040
|
const lastTip = [...decls].reverse().find((d) => d.tooltip)?.tooltip;
|
|
10763
|
-
if (lastTip)
|
|
10764
|
-
grid.appendChild(
|
|
12041
|
+
if (lastTip) cell2.appendChild(this.infoButton(lastTip));
|
|
12042
|
+
grid.appendChild(cell2);
|
|
10765
12043
|
return;
|
|
10766
12044
|
}
|
|
10767
12045
|
const lead = decls[0];
|
|
10768
12046
|
const id = idOf(lead);
|
|
10769
12047
|
if (lead.type === "bool") {
|
|
10770
|
-
const
|
|
10771
|
-
|
|
12048
|
+
const cell2 = document.createElement("div");
|
|
12049
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;align-items:center;gap:8px;";
|
|
10772
12050
|
const lbl = document.createElement("label");
|
|
10773
|
-
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:
|
|
12051
|
+
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;";
|
|
10774
12052
|
lbl.appendChild(this.buildControl(row, lead, id));
|
|
10775
12053
|
lbl.appendChild(document.createTextNode(nameOf(lead)));
|
|
10776
|
-
|
|
10777
|
-
if (lead.tooltip)
|
|
10778
|
-
grid.appendChild(
|
|
10779
|
-
grid.appendChild(document.createElement("div"));
|
|
12054
|
+
cell2.appendChild(lbl);
|
|
12055
|
+
if (lead.tooltip) cell2.appendChild(this.infoButton(lead.tooltip));
|
|
12056
|
+
grid.appendChild(cell2);
|
|
10780
12057
|
return;
|
|
10781
12058
|
}
|
|
10782
12059
|
if (lead.type === "text_area") {
|
|
10783
|
-
const
|
|
10784
|
-
|
|
12060
|
+
const cell2 = document.createElement("div");
|
|
12061
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
|
|
10785
12062
|
const head = document.createElement("div");
|
|
10786
12063
|
head.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;";
|
|
10787
12064
|
const lbl = document.createElement("label");
|
|
10788
12065
|
lbl.htmlFor = id;
|
|
10789
12066
|
lbl.textContent = nameOf(lead);
|
|
10790
|
-
lbl.style.cssText = "font-size:
|
|
12067
|
+
lbl.style.cssText = "font-size:14px;";
|
|
10791
12068
|
head.appendChild(lbl);
|
|
10792
12069
|
if (lead.tooltip) head.appendChild(this.infoButton(lead.tooltip));
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
grid.appendChild(
|
|
12070
|
+
cell2.appendChild(head);
|
|
12071
|
+
cell2.appendChild(this.buildControl(row, lead, id));
|
|
12072
|
+
grid.appendChild(cell2);
|
|
10796
12073
|
return;
|
|
10797
12074
|
}
|
|
10798
12075
|
const label = document.createElement("label");
|
|
10799
12076
|
label.htmlFor = id;
|
|
10800
12077
|
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;";
|
|
12078
|
+
label.style.cssText = "font-size:14px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
10804
12079
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
12080
|
+
const cell = document.createElement("div");
|
|
12081
|
+
cell.style.cssText = "display:flex;align-items:center;gap:8px;justify-self:start;";
|
|
10805
12082
|
const cw = document.createElement("div");
|
|
10806
|
-
cw.style.cssText = fit ? "" : "width:
|
|
12083
|
+
cw.style.cssText = fit ? "" : "width:100px;";
|
|
10807
12084
|
cw.appendChild(this.buildControl(row, lead, id));
|
|
10808
|
-
|
|
10809
|
-
if (lead.tooltip)
|
|
12085
|
+
cell.appendChild(cw);
|
|
12086
|
+
if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
|
|
10810
12087
|
grid.appendChild(label);
|
|
10811
|
-
grid.appendChild(
|
|
10812
|
-
grid.appendChild(document.createElement("div"));
|
|
12088
|
+
grid.appendChild(cell);
|
|
10813
12089
|
}
|
|
10814
12090
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
10815
12091
|
buildControl(row, inp, id) {
|
|
@@ -10818,107 +12094,30 @@ var InputsUI = class {
|
|
|
10818
12094
|
row.values[inp.key] = value;
|
|
10819
12095
|
this.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
10820
12096
|
};
|
|
10821
|
-
const ctrl = this.ctrlStyle();
|
|
10822
12097
|
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),
|
|
12098
|
+
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), emit);
|
|
12099
|
+
if (inp.type === "source") return this.select(id, SOURCES, String(current), emit);
|
|
10825
12100
|
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;
|
|
12101
|
+
const field = colorField(this.theme, () => String(row.values[inp.key] ?? inp.defval), (v) => emit(v), { shape: "circle", id });
|
|
12102
|
+
return field;
|
|
10833
12103
|
}
|
|
10834
12104
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
10835
|
-
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current),
|
|
12105
|
+
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), emit);
|
|
10836
12106
|
if (inp.type === "session") return this.buildSession(id, String(current), emit);
|
|
10837
12107
|
if (inp.type === "time") return this.buildTime(id, Number(current) || 0, emit);
|
|
10838
12108
|
if (inp.type === "text_area") return this.buildTextArea(id, String(current), emit);
|
|
10839
12109
|
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;
|
|
12110
|
+
return this.buildNumber(id, Number(current), inp, emit);
|
|
10872
12111
|
}
|
|
10873
12112
|
return this.buildTextField(id, String(current), emit);
|
|
10874
12113
|
}
|
|
10875
12114
|
/** A plain text field that commits on blur / Enter (shared by string inputs and the pickerless symbol field). */
|
|
10876
12115
|
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;
|
|
12116
|
+
return new TextField({ id, value: current, size: "md", onChange: emit }).el;
|
|
10896
12117
|
}
|
|
10897
12118
|
/** A square check-toggle (filled + check when on) — replaces the raw checkbox. */
|
|
10898
12119
|
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;
|
|
12120
|
+
return new Switch({ id, checked, size: "md", tone: "bright", onChange }).el;
|
|
10922
12121
|
}
|
|
10923
12122
|
/** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
|
|
10924
12123
|
infoButton(tooltip2) {
|
|
@@ -10930,45 +12129,22 @@ var InputsUI = class {
|
|
|
10930
12129
|
return b;
|
|
10931
12130
|
}
|
|
10932
12131
|
/** 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,
|
|
12132
|
+
select(id, options, current, emit) {
|
|
12133
|
+
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, emit);
|
|
10935
12134
|
}
|
|
10936
12135
|
/** 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;
|
|
12136
|
+
selectPairs(id, pairs, current, onChange) {
|
|
12137
|
+
return new Select({
|
|
12138
|
+
id,
|
|
12139
|
+
options: pairs,
|
|
12140
|
+
value: current,
|
|
12141
|
+
size: "md",
|
|
12142
|
+
theme: this.theme,
|
|
12143
|
+
list: this.listPopover(),
|
|
12144
|
+
onChange
|
|
12145
|
+
}).el;
|
|
10970
12146
|
}
|
|
10971
|
-
/** `input.session` → two HH:MM
|
|
12147
|
+
/** `input.session` → two typeable HH:MM comboboxes committing a `HHMM-HHMM` session string. */
|
|
10972
12148
|
buildSession(id, current, emit) {
|
|
10973
12149
|
const [start, end] = sessionToTimes(current);
|
|
10974
12150
|
const wrap = document.createElement("div");
|
|
@@ -10976,15 +12152,16 @@ var InputsUI = class {
|
|
|
10976
12152
|
let from = start;
|
|
10977
12153
|
let to = end;
|
|
10978
12154
|
const commit = () => emit(timesToSession(from, to));
|
|
10979
|
-
|
|
10980
|
-
from
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
to
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
12155
|
+
wrap.append(
|
|
12156
|
+
this.timeCombobox(id, from, 86, (v) => {
|
|
12157
|
+
from = v;
|
|
12158
|
+
commit();
|
|
12159
|
+
}),
|
|
12160
|
+
this.timeCombobox(`${id}-end`, to, 86, (v) => {
|
|
12161
|
+
to = v;
|
|
12162
|
+
commit();
|
|
12163
|
+
})
|
|
12164
|
+
);
|
|
10988
12165
|
return wrap;
|
|
10989
12166
|
}
|
|
10990
12167
|
/** `input.time` → a date picker + an HH:MM dropdown, committing an epoch-ms timestamp. */
|
|
@@ -10999,20 +12176,15 @@ var InputsUI = class {
|
|
|
10999
12176
|
const ms = Date.parse(`${date}T${time}:00`);
|
|
11000
12177
|
if (Number.isFinite(ms)) emit(ms);
|
|
11001
12178
|
};
|
|
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;
|
|
12179
|
+
const dateField = this.dateField(id, date, (v) => {
|
|
12180
|
+
date = v;
|
|
11009
12181
|
commit();
|
|
11010
12182
|
});
|
|
11011
|
-
const timeSel = this.
|
|
12183
|
+
const timeSel = this.timeCombobox(`${id}-time`, time, 86, (v) => {
|
|
11012
12184
|
time = v;
|
|
11013
12185
|
commit();
|
|
11014
12186
|
});
|
|
11015
|
-
wrap.append(
|
|
12187
|
+
wrap.append(dateField, timeSel);
|
|
11016
12188
|
return wrap;
|
|
11017
12189
|
}
|
|
11018
12190
|
/** `input.text_area` → a multi-line textarea, committed on blur. */
|
|
@@ -11031,9 +12203,261 @@ var InputsUI = class {
|
|
|
11031
12203
|
});
|
|
11032
12204
|
return ta;
|
|
11033
12205
|
}
|
|
11034
|
-
/** A fixed-width HH:MM
|
|
11035
|
-
|
|
11036
|
-
|
|
12206
|
+
/** A fixed-width HH:MM combobox (typeable, plus a 30-minute-step dropdown) used by session + time. */
|
|
12207
|
+
timeCombobox(id, current, width, onChange) {
|
|
12208
|
+
const wrap = document.createElement("div");
|
|
12209
|
+
wrap.className = "vela-ind-combo";
|
|
12210
|
+
wrap.style.cssText = `position:relative;width:${width}px;`;
|
|
12211
|
+
const input = document.createElement("input");
|
|
12212
|
+
input.type = "text";
|
|
12213
|
+
input.id = id;
|
|
12214
|
+
input.value = current;
|
|
12215
|
+
input.autocomplete = "off";
|
|
12216
|
+
input.spellcheck = false;
|
|
12217
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
12218
|
+
const chevron = document.createElement("button");
|
|
12219
|
+
chevron.type = "button";
|
|
12220
|
+
chevron.tabIndex = -1;
|
|
12221
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
12222
|
+
chevron.setAttribute("aria-label", "Open time list");
|
|
12223
|
+
chevron.innerHTML = CLOCK_SVG;
|
|
12224
|
+
let value = current;
|
|
12225
|
+
const commitTyped = () => {
|
|
12226
|
+
const next = normalizeTimeInput(input.value);
|
|
12227
|
+
if (!next) {
|
|
12228
|
+
input.value = value;
|
|
12229
|
+
return;
|
|
12230
|
+
}
|
|
12231
|
+
input.value = next;
|
|
12232
|
+
if (next !== value) {
|
|
12233
|
+
value = next;
|
|
12234
|
+
onChange(next);
|
|
12235
|
+
}
|
|
12236
|
+
};
|
|
12237
|
+
input.addEventListener("blur", () => {
|
|
12238
|
+
if (isPopoverOpen()) return;
|
|
12239
|
+
commitTyped();
|
|
12240
|
+
});
|
|
12241
|
+
const pick = (v) => {
|
|
12242
|
+
value = v;
|
|
12243
|
+
input.value = v;
|
|
12244
|
+
onChange(v);
|
|
12245
|
+
};
|
|
12246
|
+
const openList = () => {
|
|
12247
|
+
this.choicePop = toggleSelectList(wrap, TIME_OPTIONS, value, pick, {
|
|
12248
|
+
...this.listPopover(),
|
|
12249
|
+
onClose: () => {
|
|
12250
|
+
this.choicePop = null;
|
|
12251
|
+
}
|
|
12252
|
+
});
|
|
12253
|
+
};
|
|
12254
|
+
input.addEventListener("keydown", (e) => {
|
|
12255
|
+
if (e.key === "Enter") {
|
|
12256
|
+
e.preventDefault();
|
|
12257
|
+
commitTyped();
|
|
12258
|
+
input.blur();
|
|
12259
|
+
}
|
|
12260
|
+
if (e.key === "ArrowDown") {
|
|
12261
|
+
e.preventDefault();
|
|
12262
|
+
openList();
|
|
12263
|
+
}
|
|
12264
|
+
});
|
|
12265
|
+
const open2 = (e) => {
|
|
12266
|
+
e.preventDefault();
|
|
12267
|
+
e.stopPropagation();
|
|
12268
|
+
openList();
|
|
12269
|
+
};
|
|
12270
|
+
input.addEventListener("click", open2);
|
|
12271
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
12272
|
+
chevron.addEventListener("click", open2);
|
|
12273
|
+
wrap.append(input, chevron);
|
|
12274
|
+
return wrap;
|
|
12275
|
+
}
|
|
12276
|
+
/** A typeable YYYY-MM-DD field that also opens a themed month calendar. */
|
|
12277
|
+
dateField(id, current, onChange) {
|
|
12278
|
+
const wrap = document.createElement("div");
|
|
12279
|
+
wrap.className = "vela-ind-combo";
|
|
12280
|
+
wrap.style.cssText = "position:relative;width:128px;";
|
|
12281
|
+
const input = document.createElement("input");
|
|
12282
|
+
input.type = "text";
|
|
12283
|
+
input.id = id;
|
|
12284
|
+
input.value = current;
|
|
12285
|
+
input.autocomplete = "off";
|
|
12286
|
+
input.spellcheck = false;
|
|
12287
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
12288
|
+
const chevron = document.createElement("button");
|
|
12289
|
+
chevron.type = "button";
|
|
12290
|
+
chevron.tabIndex = -1;
|
|
12291
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
12292
|
+
chevron.setAttribute("aria-label", "Open calendar");
|
|
12293
|
+
chevron.innerHTML = CALENDAR_SVG;
|
|
12294
|
+
let value = current;
|
|
12295
|
+
const commitTyped = () => {
|
|
12296
|
+
const next = normalizeDateInput(input.value);
|
|
12297
|
+
if (!next) {
|
|
12298
|
+
input.value = value;
|
|
12299
|
+
return;
|
|
12300
|
+
}
|
|
12301
|
+
input.value = next;
|
|
12302
|
+
if (next !== value) {
|
|
12303
|
+
value = next;
|
|
12304
|
+
onChange(next);
|
|
12305
|
+
}
|
|
12306
|
+
};
|
|
12307
|
+
input.addEventListener("blur", () => {
|
|
12308
|
+
if (isPopoverOpen()) return;
|
|
12309
|
+
commitTyped();
|
|
12310
|
+
});
|
|
12311
|
+
const pick = (v) => {
|
|
12312
|
+
value = v;
|
|
12313
|
+
input.value = v;
|
|
12314
|
+
onChange(v);
|
|
12315
|
+
};
|
|
12316
|
+
input.addEventListener("keydown", (e) => {
|
|
12317
|
+
if (e.key === "Enter") {
|
|
12318
|
+
e.preventDefault();
|
|
12319
|
+
commitTyped();
|
|
12320
|
+
input.blur();
|
|
12321
|
+
}
|
|
12322
|
+
if (e.key === "ArrowDown") {
|
|
12323
|
+
e.preventDefault();
|
|
12324
|
+
this.openCalendar(wrap, value, pick);
|
|
12325
|
+
}
|
|
12326
|
+
});
|
|
12327
|
+
const open2 = (e) => {
|
|
12328
|
+
e.preventDefault();
|
|
12329
|
+
e.stopPropagation();
|
|
12330
|
+
if (openPopoverTrigger() === wrap) {
|
|
12331
|
+
closeOpenPopovers();
|
|
12332
|
+
return;
|
|
12333
|
+
}
|
|
12334
|
+
this.openCalendar(wrap, value, pick);
|
|
12335
|
+
};
|
|
12336
|
+
input.addEventListener("click", open2);
|
|
12337
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
12338
|
+
chevron.addEventListener("click", open2);
|
|
12339
|
+
wrap.append(input, chevron);
|
|
12340
|
+
return wrap;
|
|
12341
|
+
}
|
|
12342
|
+
/** Numeric field with hover steppers on the right (no fill until a triangle is hovered). */
|
|
12343
|
+
buildNumber(id, current, inp, emit) {
|
|
12344
|
+
return new NumberInput({
|
|
12345
|
+
id,
|
|
12346
|
+
value: current,
|
|
12347
|
+
min: inp.min,
|
|
12348
|
+
max: inp.max,
|
|
12349
|
+
step: inp.step ?? (inp.type === "int" ? 1 : 0.1),
|
|
12350
|
+
integer: inp.type === "int",
|
|
12351
|
+
size: "md",
|
|
12352
|
+
commit: "blur",
|
|
12353
|
+
onChange: emit
|
|
12354
|
+
}).el;
|
|
12355
|
+
}
|
|
12356
|
+
/** Placement shared by the indicator dialog's dropdowns and the time combobox. */
|
|
12357
|
+
listPopover() {
|
|
12358
|
+
return {
|
|
12359
|
+
theme: this.theme,
|
|
12360
|
+
matchWidth: true,
|
|
12361
|
+
boundary: this.dialog ?? "viewport",
|
|
12362
|
+
boundaryInset: 8,
|
|
12363
|
+
gap: 4
|
|
12364
|
+
};
|
|
12365
|
+
}
|
|
12366
|
+
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
12367
|
+
openCalendar(anchor, current, onPick) {
|
|
12368
|
+
const parsed = parseIsoDate(current);
|
|
12369
|
+
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
12370
|
+
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
12371
|
+
const selected = parsed ? isoDate(parsed) : current;
|
|
12372
|
+
const pop = new Popover({
|
|
12373
|
+
trigger: anchor,
|
|
12374
|
+
theme: this.theme,
|
|
12375
|
+
className: "vela-ind-cal",
|
|
12376
|
+
boundary: this.dialog ?? "viewport",
|
|
12377
|
+
boundaryInset: 8,
|
|
12378
|
+
gap: 4,
|
|
12379
|
+
onClose: () => {
|
|
12380
|
+
this.calendarPop = null;
|
|
12381
|
+
this.calendarAnchor = null;
|
|
12382
|
+
},
|
|
12383
|
+
content: (el) => {
|
|
12384
|
+
const title = document.createElement("div");
|
|
12385
|
+
title.className = "vela-ind-cal-title";
|
|
12386
|
+
const prev2 = document.createElement("button");
|
|
12387
|
+
prev2.type = "button";
|
|
12388
|
+
prev2.className = "vela-ind-cal-nav";
|
|
12389
|
+
prev2.setAttribute("aria-label", "Previous month");
|
|
12390
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
12391
|
+
const next = document.createElement("button");
|
|
12392
|
+
next.type = "button";
|
|
12393
|
+
next.className = "vela-ind-cal-nav";
|
|
12394
|
+
next.setAttribute("aria-label", "Next month");
|
|
12395
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
12396
|
+
const head = document.createElement("div");
|
|
12397
|
+
head.className = "vela-ind-cal-head";
|
|
12398
|
+
head.append(prev2, title, next);
|
|
12399
|
+
const week = document.createElement("div");
|
|
12400
|
+
week.className = "vela-ind-cal-week";
|
|
12401
|
+
for (const d of WEEKDAY_LABELS) {
|
|
12402
|
+
const cell = document.createElement("span");
|
|
12403
|
+
cell.textContent = d;
|
|
12404
|
+
week.appendChild(cell);
|
|
12405
|
+
}
|
|
12406
|
+
const grid = document.createElement("div");
|
|
12407
|
+
grid.className = "vela-ind-cal-grid";
|
|
12408
|
+
const paint = () => {
|
|
12409
|
+
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
12410
|
+
grid.replaceChildren();
|
|
12411
|
+
const first = new Date(year, month, 1);
|
|
12412
|
+
const startPad = first.getDay();
|
|
12413
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
12414
|
+
const today = isoDate(/* @__PURE__ */ new Date());
|
|
12415
|
+
for (let i = 0; i < startPad; i++) {
|
|
12416
|
+
const blank = document.createElement("span");
|
|
12417
|
+
blank.className = "vela-ind-cal-blank";
|
|
12418
|
+
grid.appendChild(blank);
|
|
12419
|
+
}
|
|
12420
|
+
for (let day = 1; day <= days; day++) {
|
|
12421
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
12422
|
+
const b = document.createElement("button");
|
|
12423
|
+
b.type = "button";
|
|
12424
|
+
b.className = "vela-ind-cal-day";
|
|
12425
|
+
b.textContent = String(day);
|
|
12426
|
+
if (iso === selected) b.dataset.checked = "1";
|
|
12427
|
+
if (iso === today) b.dataset.today = "1";
|
|
12428
|
+
b.addEventListener("click", (e) => {
|
|
12429
|
+
e.stopPropagation();
|
|
12430
|
+
pop.hide();
|
|
12431
|
+
onPick(iso);
|
|
12432
|
+
});
|
|
12433
|
+
grid.appendChild(b);
|
|
12434
|
+
}
|
|
12435
|
+
};
|
|
12436
|
+
prev2.addEventListener("click", (e) => {
|
|
12437
|
+
e.stopPropagation();
|
|
12438
|
+
month -= 1;
|
|
12439
|
+
if (month < 0) {
|
|
12440
|
+
month = 11;
|
|
12441
|
+
year -= 1;
|
|
12442
|
+
}
|
|
12443
|
+
paint();
|
|
12444
|
+
});
|
|
12445
|
+
next.addEventListener("click", (e) => {
|
|
12446
|
+
e.stopPropagation();
|
|
12447
|
+
month += 1;
|
|
12448
|
+
if (month > 11) {
|
|
12449
|
+
month = 0;
|
|
12450
|
+
year += 1;
|
|
12451
|
+
}
|
|
12452
|
+
paint();
|
|
12453
|
+
});
|
|
12454
|
+
paint();
|
|
12455
|
+
el.append(head, week, grid);
|
|
12456
|
+
}
|
|
12457
|
+
});
|
|
12458
|
+
this.calendarPop = pop;
|
|
12459
|
+
this.calendarAnchor = anchor;
|
|
12460
|
+
pop.show();
|
|
11037
12461
|
}
|
|
11038
12462
|
/**
|
|
11039
12463
|
* `input.symbol` → a field that opens the host's ticker-selection UI when a picker is wired
|
|
@@ -11061,7 +12485,7 @@ var InputsUI = class {
|
|
|
11061
12485
|
}
|
|
11062
12486
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
11063
12487
|
ctrlStyle() {
|
|
11064
|
-
return `width:100%;box-sizing:border-box;height:
|
|
12488
|
+
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
12489
|
}
|
|
11066
12490
|
/** Whether the active theme is dark (drives the dialog's `color-scheme`). */
|
|
11067
12491
|
isDarkTheme() {
|
|
@@ -11080,21 +12504,6 @@ var InputsUI = class {
|
|
|
11080
12504
|
strongText() {
|
|
11081
12505
|
return "var(--vela-fg-bright)";
|
|
11082
12506
|
}
|
|
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
12507
|
dialogButton(label, primary, onClick) {
|
|
11099
12508
|
const b = document.createElement("button");
|
|
11100
12509
|
b.type = "button";
|
|
@@ -11108,6 +12517,21 @@ function nameOf(inp) {
|
|
|
11108
12517
|
const t = inp.title;
|
|
11109
12518
|
return t.length > 0 ? t.charAt(0).toUpperCase() + t.slice(1) : "";
|
|
11110
12519
|
}
|
|
12520
|
+
var DEFAULT_INPUT_TAB = "Inputs";
|
|
12521
|
+
function tabInputs(inputs) {
|
|
12522
|
+
const byTab = /* @__PURE__ */ new Map();
|
|
12523
|
+
for (const inp of inputs) {
|
|
12524
|
+
const name = inp.tab && inp.tab.length > 0 ? inp.tab : DEFAULT_INPUT_TAB;
|
|
12525
|
+
if (!byTab.has(name)) byTab.set(name, []);
|
|
12526
|
+
byTab.get(name).push(inp);
|
|
12527
|
+
}
|
|
12528
|
+
const names = [...byTab.keys()];
|
|
12529
|
+
if (names.includes(DEFAULT_INPUT_TAB)) {
|
|
12530
|
+
names.splice(names.indexOf(DEFAULT_INPUT_TAB), 1);
|
|
12531
|
+
names.unshift(DEFAULT_INPUT_TAB);
|
|
12532
|
+
}
|
|
12533
|
+
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
12534
|
+
}
|
|
11111
12535
|
function groupInputs(inputs) {
|
|
11112
12536
|
const order = [];
|
|
11113
12537
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -11136,9 +12560,10 @@ function groupInputs(inputs) {
|
|
|
11136
12560
|
});
|
|
11137
12561
|
}
|
|
11138
12562
|
var CHECK_SVG = iconAt("check", 12);
|
|
11139
|
-
var
|
|
12563
|
+
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
12564
|
+
var CLOCK_SVG = iconAt("clock", 14);
|
|
11140
12565
|
var DIALOG_STYLE_ID = "vela-ind-dialog-styles";
|
|
11141
|
-
var DIALOG_STYLE_REV = "
|
|
12566
|
+
var DIALOG_STYLE_REV = "25";
|
|
11142
12567
|
function ensureDialogStyles() {
|
|
11143
12568
|
if (typeof document === "undefined") return;
|
|
11144
12569
|
const existing = document.getElementById(DIALOG_STYLE_ID);
|
|
@@ -11147,30 +12572,47 @@ function ensureDialogStyles() {
|
|
|
11147
12572
|
s.id = DIALOG_STYLE_ID;
|
|
11148
12573
|
s.dataset.rev = DIALOG_STYLE_REV;
|
|
11149
12574
|
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-
|
|
12575
|
+
.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;}
|
|
12576
|
+
.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);}
|
|
12577
|
+
.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);}
|
|
12578
|
+
.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;}
|
|
12579
|
+
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
12580
|
+
.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;}
|
|
12581
|
+
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
12582
|
+
.vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
|
|
12583
|
+
.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;}
|
|
12584
|
+
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
12585
|
+
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
12586
|
+
.vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
|
|
12587
|
+
.vela-ind-cal-week span{line-height:20px;}
|
|
12588
|
+
.vela-ind-cal-blank{width:28px;height:28px;}
|
|
12589
|
+
.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;}
|
|
12590
|
+
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
12591
|
+
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
12592
|
+
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
12593
|
+
.vela-ind-dialog ::-webkit-scrollbar{width:9px;height:9px;}
|
|
11158
12594
|
.vela-ind-dialog ::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}
|
|
11159
12595
|
.vela-ind-dialog ::-webkit-scrollbar-track{background:transparent;}
|
|
12596
|
+
.vela-ind-dialog ::-webkit-scrollbar-button{display:none;width:0;height:0;}
|
|
12597
|
+
.vela-ind-tab{transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
12598
|
+
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
11160
12599
|
.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
12600
|
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
11162
12601
|
.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
12602
|
.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
12603
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
11165
12604
|
.vela-ind-close:hover{color:var(--vela-danger) !important;background-color:var(--vela-hover-strong);}
|
|
12605
|
+
.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;}
|
|
12606
|
+
.vela-ind-dlg-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
12607
|
+
.vela-ind-dlg-close svg{width:15px;height:15px;display:block;flex:none;}
|
|
11166
12608
|
.vela-ind-fold{transition:border-color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
|
|
11167
12609
|
.vela-ind-fold:hover{border-color:var(--vela-border-strong);opacity:0.9;}
|
|
11168
12610
|
.vela-ind-menuitem{background:transparent;transition:background var(--vela-dur-fast) ease;}
|
|
11169
12611
|
.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;}`;
|
|
12612
|
+
.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;}
|
|
12613
|
+
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);border-color:var(--vela-fg-muted);}
|
|
12614
|
+
.vela-ind-btn-primary{border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
12615
|
+
.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
12616
|
if (!existing) document.head.appendChild(s);
|
|
11175
12617
|
}
|
|
11176
12618
|
var TIMEFRAME_OPTIONS = [
|
|
@@ -11195,6 +12637,37 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
11195
12637
|
const v = `${hh}:${mm}`;
|
|
11196
12638
|
return { value: v, label: v };
|
|
11197
12639
|
});
|
|
12640
|
+
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
12641
|
+
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
12642
|
+
function normalizeDateInput(raw) {
|
|
12643
|
+
const t = raw.trim();
|
|
12644
|
+
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
|
|
12645
|
+
if (!m) return null;
|
|
12646
|
+
const y = Number(m[1]);
|
|
12647
|
+
const mo = Number(m[2]);
|
|
12648
|
+
const d = Number(m[3]);
|
|
12649
|
+
const dt = new Date(y, mo - 1, d);
|
|
12650
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
|
|
12651
|
+
return isoDate(dt);
|
|
12652
|
+
}
|
|
12653
|
+
function parseIsoDate(raw) {
|
|
12654
|
+
const iso = normalizeDateInput(raw);
|
|
12655
|
+
if (!iso) return null;
|
|
12656
|
+
const [y, mo, d] = iso.split("-").map(Number);
|
|
12657
|
+
return new Date(y, mo - 1, d);
|
|
12658
|
+
}
|
|
12659
|
+
function isoDate(d) {
|
|
12660
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
12661
|
+
}
|
|
12662
|
+
function normalizeTimeInput(raw) {
|
|
12663
|
+
const t = raw.trim();
|
|
12664
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
12665
|
+
if (!m) return null;
|
|
12666
|
+
const hh = Number(m[1]);
|
|
12667
|
+
const mm = Number(m[2]);
|
|
12668
|
+
if (hh > 23 || mm > 59) return null;
|
|
12669
|
+
return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
|
|
12670
|
+
}
|
|
11198
12671
|
function sessionToTimes(session) {
|
|
11199
12672
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(session.trim());
|
|
11200
12673
|
if (!m) return ["09:00", "16:00"];
|
|
@@ -15301,6 +16774,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
|
|
|
15301
16774
|
const rx = right ? width + 2 : Math.max(x1, x2);
|
|
15302
16775
|
return [lx, yAt(lx), rx, yAt(rx)];
|
|
15303
16776
|
}
|
|
16777
|
+
function uprightLineAngle(x1, y1, x2, y2) {
|
|
16778
|
+
let a = Math.atan2(y2 - y1, x2 - x1);
|
|
16779
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
16780
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
16781
|
+
return a;
|
|
16782
|
+
}
|
|
15304
16783
|
function parseColor2(c) {
|
|
15305
16784
|
const s = c.trim();
|
|
15306
16785
|
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
@@ -16555,380 +18034,71 @@ function tzButtonLabel(zone) {
|
|
|
16555
18034
|
return tzOffset(zone);
|
|
16556
18035
|
}
|
|
16557
18036
|
|
|
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
18037
|
// src/renderers/native/chrome/WidthField.ts
|
|
16840
18038
|
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 = `
|
|
18039
|
+
var STYLE_ID2 = "vela-width-field";
|
|
18040
|
+
var WIDTH_CSS = `
|
|
16847
18041
|
.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
18042
|
.vela-width-field:hover{border-color:var(--vela-fg-muted);}
|
|
16849
|
-
.vela-width-field-
|
|
18043
|
+
.vela-width-field-caret{display:flex;color:var(--vela-fg-muted);}
|
|
18044
|
+
.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
18045
|
.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
18046
|
.vela-width-field-item:hover{background:var(--vela-hover-strong);}
|
|
16852
18047
|
.vela-width-field-item[data-active='1']{background:var(--vela-active);}
|
|
16853
18048
|
`;
|
|
16854
|
-
document.head.appendChild(st);
|
|
16855
|
-
}
|
|
16856
18049
|
function lineGlyph(width) {
|
|
16857
18050
|
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
18051
|
}
|
|
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
|
-
}
|
|
18052
|
+
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
18053
|
function widthField(theme, getVal, onVal) {
|
|
16882
|
-
|
|
18054
|
+
injectStyles(STYLE_ID2, WIDTH_CSS, document);
|
|
16883
18055
|
const trigger = document.createElement("button");
|
|
16884
18056
|
trigger.type = "button";
|
|
16885
18057
|
trigger.className = "vela-width-field";
|
|
16886
18058
|
const paint = () => {
|
|
16887
|
-
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span
|
|
18059
|
+
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span class="vela-width-field-caret">${CHEVRON}</span>`;
|
|
16888
18060
|
};
|
|
16889
18061
|
paint();
|
|
18062
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
18063
|
+
e.preventDefault();
|
|
18064
|
+
trigger.focus({ preventScroll: true });
|
|
18065
|
+
});
|
|
16890
18066
|
trigger.addEventListener("click", (e) => {
|
|
16891
18067
|
e.stopPropagation();
|
|
16892
|
-
if (
|
|
16893
|
-
|
|
18068
|
+
if (openPopoverTrigger() === trigger) {
|
|
18069
|
+
closeOpenPopovers();
|
|
16894
18070
|
return;
|
|
16895
18071
|
}
|
|
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 };
|
|
18072
|
+
const pop = new Popover({
|
|
18073
|
+
trigger,
|
|
18074
|
+
theme,
|
|
18075
|
+
align: "end",
|
|
18076
|
+
gap: 6,
|
|
18077
|
+
className: "vela-width-field-pop",
|
|
18078
|
+
content: (el) => {
|
|
18079
|
+
el.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
|
|
18080
|
+
for (const w of WIDTH_FIELD_OPTIONS) {
|
|
18081
|
+
const item = document.createElement("button");
|
|
18082
|
+
item.type = "button";
|
|
18083
|
+
item.className = "vela-width-field-item";
|
|
18084
|
+
item.dataset.active = w === getVal() ? "1" : "0";
|
|
18085
|
+
item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
|
|
18086
|
+
item.addEventListener("click", (ev) => {
|
|
18087
|
+
ev.stopPropagation();
|
|
18088
|
+
pop.hide();
|
|
18089
|
+
onVal(w);
|
|
18090
|
+
paint();
|
|
18091
|
+
});
|
|
18092
|
+
el.appendChild(item);
|
|
18093
|
+
}
|
|
18094
|
+
}
|
|
18095
|
+
});
|
|
18096
|
+
pop.show();
|
|
16927
18097
|
});
|
|
16928
18098
|
return trigger;
|
|
16929
18099
|
}
|
|
16930
18100
|
function closeWidthPopover() {
|
|
16931
|
-
|
|
18101
|
+
closeOpenPopovers();
|
|
16932
18102
|
}
|
|
16933
18103
|
|
|
16934
18104
|
// src/renderers/native/chrome/SettingsDialog.ts
|
|
@@ -16943,37 +18113,24 @@ function styleLabel(id) {
|
|
|
16943
18113
|
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS[id] ?? id;
|
|
16944
18114
|
}
|
|
16945
18115
|
var SD_STYLE_ID = "vela-settings-controls";
|
|
18116
|
+
var SD_STYLE_REV = "2";
|
|
16946
18117
|
var SETTINGS_SURFACE = "var(--vela-surface)";
|
|
16947
18118
|
var SETTINGS_BORDER = "var(--vela-border)";
|
|
16948
18119
|
var SETTINGS_TEXT = "var(--vela-fg)";
|
|
16949
18120
|
function ensureControlStyles() {
|
|
16950
|
-
if (typeof document === "undefined"
|
|
16951
|
-
const
|
|
18121
|
+
if (typeof document === "undefined") return;
|
|
18122
|
+
const existing = document.getElementById(SD_STYLE_ID);
|
|
18123
|
+
if (existing?.dataset.rev === SD_STYLE_REV) return;
|
|
18124
|
+
const st = existing ?? document.createElement("style");
|
|
16952
18125
|
st.id = SD_STYLE_ID;
|
|
18126
|
+
st.dataset.rev = SD_STYLE_REV;
|
|
16953
18127
|
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
18128
|
.vela-sd-pane::-webkit-scrollbar{width:8px;}
|
|
16968
18129
|
.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
18130
|
.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
18131
|
/* Tab rail / footer button / header close: base styles live HERE, not inline on the
|
|
16975
18132
|
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
|
|
18133
|
+
what killed these hovers before. Active tab state is the .on class,
|
|
16977
18134
|
hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
|
|
16978
18135
|
.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
18136
|
.vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
@@ -17032,13 +18189,13 @@ function ensureControlStyles() {
|
|
|
17032
18189
|
.vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
|
|
17033
18190
|
.vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
|
|
17034
18191
|
.vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
|
|
17035
|
-
.vela-sd-mobile .vela-
|
|
17036
|
-
.vela-sd-mobile .vela-
|
|
18192
|
+
.vela-sd-mobile .vela-switch{width:22px;height:22px;}
|
|
18193
|
+
.vela-sd-mobile .vela-select[data-size='sm'] .vela-select-trigger,.vela-sd-mobile .vela-num[data-size='sm'] input{height:34px;}
|
|
17037
18194
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
17038
18195
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
17039
18196
|
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
|
|
17040
18197
|
`;
|
|
17041
|
-
document.head.appendChild(st);
|
|
18198
|
+
if (!existing) document.head.appendChild(st);
|
|
17042
18199
|
}
|
|
17043
18200
|
var SettingsDialog = class {
|
|
17044
18201
|
constructor(container, theme) {
|
|
@@ -17354,15 +18511,15 @@ var SettingsDialog = class {
|
|
|
17354
18511
|
railScrim = document.createElement("div");
|
|
17355
18512
|
railScrim.className = "vela-sd-railscrim";
|
|
17356
18513
|
railScrim.addEventListener("click", () => toggleRail?.());
|
|
17357
|
-
toggleRail = (
|
|
17358
|
-
const on =
|
|
18514
|
+
toggleRail = (open2) => {
|
|
18515
|
+
const on = open2 ?? !rail.classList.contains("open");
|
|
17359
18516
|
rail.classList.toggle("open", on);
|
|
17360
18517
|
railScrim?.classList.toggle("open", on);
|
|
17361
18518
|
};
|
|
17362
18519
|
}
|
|
17363
18520
|
const paneHost = document.createElement("div");
|
|
17364
18521
|
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;";
|
|
18522
|
+
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
18523
|
const panes = [];
|
|
17367
18524
|
let current = null;
|
|
17368
18525
|
for (const child of [...body.children]) {
|
|
@@ -17428,7 +18585,7 @@ var SettingsDialog = class {
|
|
|
17428
18585
|
}
|
|
17429
18586
|
}
|
|
17430
18587
|
close() {
|
|
17431
|
-
|
|
18588
|
+
closeOpenPopovers();
|
|
17432
18589
|
closeWidthPopover();
|
|
17433
18590
|
this.root?.remove();
|
|
17434
18591
|
this.root = null;
|
|
@@ -17583,50 +18740,41 @@ var SettingsDialog = class {
|
|
|
17583
18740
|
return wf;
|
|
17584
18741
|
}
|
|
17585
18742
|
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
18743
|
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;
|
|
18744
|
+
const sel = new Select({
|
|
18745
|
+
options: normalizeSelectOptions(c.options).map(([value, label]) => ({ value, label })),
|
|
18746
|
+
value: current2,
|
|
18747
|
+
size: "sm",
|
|
18748
|
+
theme: this.theme,
|
|
18749
|
+
onChange: (v) => put(c.key, v)
|
|
17601
18750
|
});
|
|
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);
|
|
18751
|
+
sel.el.title = c.label;
|
|
18752
|
+
sel.el.addEventListener("vela-sync", () => {
|
|
18753
|
+
sel.setValue(typeof bag[c.key] === "string" ? bag[c.key] : c.defval);
|
|
17627
18754
|
});
|
|
18755
|
+
return sel.el;
|
|
17628
18756
|
}
|
|
17629
|
-
|
|
18757
|
+
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
18758
|
+
const ni = new NumberInput({
|
|
18759
|
+
value: current,
|
|
18760
|
+
min: c.min,
|
|
18761
|
+
max: c.max,
|
|
18762
|
+
step: c.step ?? 1,
|
|
18763
|
+
size: "sm",
|
|
18764
|
+
commit: "live",
|
|
18765
|
+
clamp: c.placeholder !== void 0,
|
|
18766
|
+
steppers: false,
|
|
18767
|
+
compact,
|
|
18768
|
+
title: c.label,
|
|
18769
|
+
placeholder: c.placeholder,
|
|
18770
|
+
emptyValue: c.placeholder !== void 0 ? c.defval : void 0,
|
|
18771
|
+
onChange: (v) => put(c.key, v)
|
|
18772
|
+
});
|
|
18773
|
+
ni.el.addEventListener("vela-sync", () => {
|
|
18774
|
+
const next = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
18775
|
+
ni.setValue(next);
|
|
18776
|
+
});
|
|
18777
|
+
return ni.el;
|
|
17630
18778
|
}
|
|
17631
18779
|
/**
|
|
17632
18780
|
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
@@ -17907,28 +19055,27 @@ var SettingsDialog = class {
|
|
|
17907
19055
|
* only one visible at a time; see `typeRow`). */
|
|
17908
19056
|
toggleRow(label, value, onToggle, controls, get) {
|
|
17909
19057
|
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
|
-
};
|
|
19058
|
+
const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
|
|
19059
|
+
onToggle(v);
|
|
19060
|
+
if (controls.length > 0) syncDim(v);
|
|
19061
|
+
} });
|
|
17920
19062
|
const lbl = document.createElement("span");
|
|
17921
19063
|
lbl.textContent = label;
|
|
17922
19064
|
lbl.style.cssText = "opacity:0.85;white-space:nowrap;";
|
|
19065
|
+
const syncDim = (on) => {
|
|
19066
|
+
for (const c of controls) {
|
|
19067
|
+
if (c.dataset.sdSelfGated === "1") continue;
|
|
19068
|
+
c.style.opacity = on ? "1" : "0.4";
|
|
19069
|
+
c.style.pointerEvents = on ? "" : "none";
|
|
19070
|
+
}
|
|
19071
|
+
};
|
|
17923
19072
|
if (controls.length === 0) {
|
|
17924
19073
|
wrap.className = "vela-sd-bool";
|
|
17925
19074
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
|
|
17926
|
-
wrap.append(
|
|
17927
|
-
cb.addEventListener("click", () => onToggle(cbToggle()));
|
|
19075
|
+
wrap.append(sw.el, lbl);
|
|
17928
19076
|
if (get) {
|
|
17929
19077
|
wrap.addEventListener("vela-sync", () => {
|
|
17930
|
-
|
|
17931
|
-
cb.classList.toggle("on", checked);
|
|
19078
|
+
sw.setChecked(get());
|
|
17932
19079
|
});
|
|
17933
19080
|
}
|
|
17934
19081
|
return wrap;
|
|
@@ -17936,28 +19083,16 @@ var SettingsDialog = class {
|
|
|
17936
19083
|
wrap.className = "vela-sd-row";
|
|
17937
19084
|
const left = document.createElement("div");
|
|
17938
19085
|
left.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
17939
|
-
left.append(
|
|
19086
|
+
left.append(sw.el, lbl);
|
|
17940
19087
|
const box = document.createElement("div");
|
|
17941
19088
|
box.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
17942
19089
|
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
19090
|
syncDim(value);
|
|
17951
|
-
cb.addEventListener("click", () => {
|
|
17952
|
-
const v = cbToggle();
|
|
17953
|
-
onToggle(v);
|
|
17954
|
-
syncDim(v);
|
|
17955
|
-
});
|
|
17956
19091
|
if (get) {
|
|
17957
19092
|
wrap.addEventListener("vela-sync", () => {
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
syncDim(
|
|
19093
|
+
const v = get();
|
|
19094
|
+
sw.setChecked(v);
|
|
19095
|
+
syncDim(v);
|
|
17961
19096
|
});
|
|
17962
19097
|
}
|
|
17963
19098
|
wrap.append(left, box);
|
|
@@ -17965,52 +19100,23 @@ var SettingsDialog = class {
|
|
|
17965
19100
|
}
|
|
17966
19101
|
numberRow(label, value, min, max, step, onChange) {
|
|
17967
19102
|
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);
|
|
19103
|
+
wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
|
|
17980
19104
|
return wrap;
|
|
17981
19105
|
}
|
|
17982
19106
|
/** A dropdown whose option values differ from their display labels. */
|
|
17983
19107
|
selectRowLabeled(label, value, options, onChange) {
|
|
17984
19108
|
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);
|
|
19109
|
+
wrap.appendChild(new Select({
|
|
19110
|
+
options: options.map(([v, l]) => ({ value: v, label: l })),
|
|
19111
|
+
value,
|
|
19112
|
+
size: "sm",
|
|
19113
|
+
theme: this.theme,
|
|
19114
|
+
onChange
|
|
19115
|
+
}).el);
|
|
17997
19116
|
return wrap;
|
|
17998
19117
|
}
|
|
17999
19118
|
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;
|
|
19119
|
+
return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
|
|
18014
19120
|
}
|
|
18015
19121
|
/** Footer with the full config as JSON — the export/import (templating) surface. */
|
|
18016
19122
|
footer(_config) {
|
|
@@ -18035,6 +19141,19 @@ function timezoneOptions(current) {
|
|
|
18035
19141
|
return [[normalized, tzMenuLabel(normalized, normalized)], ...options];
|
|
18036
19142
|
}
|
|
18037
19143
|
|
|
19144
|
+
// src/core/drawings/effectiveColor.ts
|
|
19145
|
+
var VALID_FILL = VALID;
|
|
19146
|
+
var INVALID_FILL = INVALID;
|
|
19147
|
+
function effectiveFillColor(d, theme) {
|
|
19148
|
+
if (d instanceof PatternDrawing) {
|
|
19149
|
+
if (!d.fillTriangles().length) return null;
|
|
19150
|
+
const valid = d.valid();
|
|
19151
|
+
return d.style.fillColor ?? (valid == null ? d.style.lineColor : valid ? VALID_FILL : INVALID_FILL);
|
|
19152
|
+
}
|
|
19153
|
+
if (d instanceof CalloutBase) return d.style.fillColor ?? theme.background;
|
|
19154
|
+
return d.style.fillColor ?? null;
|
|
19155
|
+
}
|
|
19156
|
+
|
|
18038
19157
|
// src/renderers/native/drawings/DrawingPainter.ts
|
|
18039
19158
|
var HANDLE_RADIUS = 4.5;
|
|
18040
19159
|
var HANDLE_BORDER = DEFAULT_DRAWING_COLOR;
|
|
@@ -18401,13 +19520,19 @@ var DrawingPainter = class {
|
|
|
18401
19520
|
}
|
|
18402
19521
|
this.paintLabel(ctx, d, proj, theme);
|
|
18403
19522
|
}
|
|
18404
|
-
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
19523
|
+
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
19524
|
+
* Two-point line tools rotate the glyphs to follow the segment, kept upright. */
|
|
18405
19525
|
paintLabel(ctx, d, proj, theme) {
|
|
18406
19526
|
const text = d.text;
|
|
18407
19527
|
if (!text || !text.value || d.id === this.targets.mutedLabel) return;
|
|
18408
19528
|
const layout = labelLayout(d, proj);
|
|
18409
19529
|
if (!layout) return;
|
|
18410
19530
|
const fs = namedFontSize(text.size);
|
|
19531
|
+
ctx.save();
|
|
19532
|
+
if (layout.rotate) {
|
|
19533
|
+
ctx.translate(layout.rotate.cx, layout.rotate.cy);
|
|
19534
|
+
ctx.rotate(layout.rotate.angle);
|
|
19535
|
+
}
|
|
18411
19536
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
18412
19537
|
ctx.textBaseline = "top";
|
|
18413
19538
|
ctx.textAlign = layout.align;
|
|
@@ -18415,8 +19540,8 @@ var DrawingPainter = class {
|
|
|
18415
19540
|
const lh = labelLineHeight(fs);
|
|
18416
19541
|
const lines = text.value.split("\n");
|
|
18417
19542
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
18418
|
-
ctx.textAlign = "left";
|
|
18419
19543
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
19544
|
+
ctx.restore();
|
|
18420
19545
|
}
|
|
18421
19546
|
/** Frame a plain text label while it is the target, so the text reads as a clickable object even
|
|
18422
19547
|
* though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
|
|
@@ -19359,7 +20484,14 @@ function labelLayout(d, proj) {
|
|
|
19359
20484
|
case "infoline":
|
|
19360
20485
|
case "trendangle": {
|
|
19361
20486
|
if (pts.length < 2) return null;
|
|
19362
|
-
|
|
20487
|
+
const [x1, y1] = pts[0];
|
|
20488
|
+
const [x2, y2] = pts[1];
|
|
20489
|
+
return {
|
|
20490
|
+
x: 0,
|
|
20491
|
+
top: -6 - lh * lines,
|
|
20492
|
+
align: "center",
|
|
20493
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: (x1 + x2) / 2, cy: (y1 + y2) / 2 }
|
|
20494
|
+
};
|
|
19363
20495
|
}
|
|
19364
20496
|
case "box": {
|
|
19365
20497
|
if (pts.length < 2) return null;
|
|
@@ -19708,11 +20840,11 @@ function applyFree(anchor, pt, free) {
|
|
|
19708
20840
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
19709
20841
|
var BTN = 30;
|
|
19710
20842
|
var ICON = 21;
|
|
19711
|
-
var
|
|
19712
|
-
function
|
|
19713
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
20843
|
+
var STYLE_ID3 = "vela-drawing-popup-styles";
|
|
20844
|
+
function ensureStyles2() {
|
|
20845
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
19714
20846
|
const s = document.createElement("style");
|
|
19715
|
-
s.id =
|
|
20847
|
+
s.id = STYLE_ID3;
|
|
19716
20848
|
s.textContent = `
|
|
19717
20849
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
19718
20850
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -19746,17 +20878,14 @@ var DrawingSettingsPopup = class {
|
|
|
19746
20878
|
this.textPanel = null;
|
|
19747
20879
|
this.levelsPanel = null;
|
|
19748
20880
|
this.colorPop = null;
|
|
19749
|
-
// floating RGB + opacity picker for a color swatch
|
|
19750
20881
|
this.colorOwner = null;
|
|
19751
|
-
|
|
19752
|
-
this.menuEl = null;
|
|
19753
|
-
// floating option list for a dropdown control
|
|
20882
|
+
this.menuPop = null;
|
|
19754
20883
|
this.menuOwner = null;
|
|
19755
20884
|
this.onClose = null;
|
|
19756
20885
|
this.onOutside = (e) => {
|
|
19757
20886
|
const target = e.target;
|
|
19758
|
-
if (this.colorPop?.contains(target)) return;
|
|
19759
|
-
if (this.
|
|
20887
|
+
if (this.colorPop?.el.contains(target)) return;
|
|
20888
|
+
if (this.menuPop?.el.contains(target)) return;
|
|
19760
20889
|
if (this.el && !this.el.contains(target)) {
|
|
19761
20890
|
const cb = this.onClose;
|
|
19762
20891
|
this.close();
|
|
@@ -19775,13 +20904,13 @@ var DrawingSettingsPopup = class {
|
|
|
19775
20904
|
* picker / dropdown menu), which live outside `el`. */
|
|
19776
20905
|
contains(node) {
|
|
19777
20906
|
if (!node) return false;
|
|
19778
|
-
return this.el?.contains(node) === true || this.colorPop?.contains(node) === true || this.
|
|
20907
|
+
return this.el?.contains(node) === true || this.colorPop?.el.contains(node) === true || this.menuPop?.el.contains(node) === true;
|
|
19779
20908
|
}
|
|
19780
20909
|
/** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
|
|
19781
20910
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
19782
20911
|
open(drawing, anchor, actions, onClose) {
|
|
19783
20912
|
this.close();
|
|
19784
|
-
|
|
20913
|
+
ensureStyles2();
|
|
19785
20914
|
this.onClose = onClose ?? null;
|
|
19786
20915
|
const t = this.theme;
|
|
19787
20916
|
const schema = drawing.schema();
|
|
@@ -19792,8 +20921,7 @@ var DrawingSettingsPopup = class {
|
|
|
19792
20921
|
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
20922
|
applyChromeTokens(el, t);
|
|
19794
20923
|
el.addEventListener("pointerdown", () => {
|
|
19795
|
-
|
|
19796
|
-
this.closeMenu();
|
|
20924
|
+
closeOpenPopovers();
|
|
19797
20925
|
});
|
|
19798
20926
|
const bar = document.createElement("div");
|
|
19799
20927
|
bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
|
|
@@ -20530,49 +21658,76 @@ var DrawingSettingsPopup = class {
|
|
|
20530
21658
|
});
|
|
20531
21659
|
return b;
|
|
20532
21660
|
}
|
|
21661
|
+
/** Host-anchored floating shell (stays inside the chart). Content is filled after
|
|
21662
|
+
* construction so `fill` can close over the live `Popover` without hitting TDZ. */
|
|
21663
|
+
hostFloat(anchor, opts) {
|
|
21664
|
+
const t = this.theme;
|
|
21665
|
+
const pop = new Popover({
|
|
21666
|
+
trigger: anchor,
|
|
21667
|
+
host: this.host,
|
|
21668
|
+
theme: t,
|
|
21669
|
+
position: "absolute",
|
|
21670
|
+
boundary: this.host,
|
|
21671
|
+
boundaryInset: 4,
|
|
21672
|
+
viewportInset: 0,
|
|
21673
|
+
gap: 6,
|
|
21674
|
+
align: opts.align ?? "start",
|
|
21675
|
+
zIndex: opts.zIndex,
|
|
21676
|
+
onClose: () => {
|
|
21677
|
+
if (this.menuPop === pop) {
|
|
21678
|
+
this.menuPop = null;
|
|
21679
|
+
this.menuOwner = null;
|
|
21680
|
+
}
|
|
21681
|
+
if (this.colorPop === pop) {
|
|
21682
|
+
this.colorPop = null;
|
|
21683
|
+
this.colorOwner = null;
|
|
21684
|
+
}
|
|
21685
|
+
}
|
|
21686
|
+
});
|
|
21687
|
+
const el = pop.el;
|
|
21688
|
+
el.style.background = t.background;
|
|
21689
|
+
el.style.border = "1px solid var(--vela-border)";
|
|
21690
|
+
el.style.borderRadius = "var(--vela-radius-lg)";
|
|
21691
|
+
el.style.boxShadow = "var(--vela-shadow)";
|
|
21692
|
+
el.style.padding = opts.padding;
|
|
21693
|
+
el.style.display = "flex";
|
|
21694
|
+
el.style.flexDirection = "column";
|
|
21695
|
+
el.style.gap = "1px";
|
|
21696
|
+
el.style.color = t.textColor;
|
|
21697
|
+
el.style.font = `var(--vela-font-size-md) ${t.fontFamily}`;
|
|
21698
|
+
el.style.pointerEvents = "auto";
|
|
21699
|
+
opts.fill(el, pop);
|
|
21700
|
+
pop.show();
|
|
21701
|
+
return pop;
|
|
21702
|
+
}
|
|
20533
21703
|
/** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
|
|
20534
21704
|
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;
|
|
21705
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
21706
|
+
align: "end",
|
|
21707
|
+
zIndex: 26,
|
|
21708
|
+
padding: "4px",
|
|
21709
|
+
fill: (menu2, pop) => {
|
|
21710
|
+
for (const row of rows) {
|
|
21711
|
+
const item = document.createElement("button");
|
|
21712
|
+
item.type = "button";
|
|
21713
|
+
item.className = "vela-dpop-item";
|
|
21714
|
+
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;";
|
|
21715
|
+
const ic = document.createElement("span");
|
|
21716
|
+
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
21717
|
+
ic.innerHTML = sized(row.icon, 18);
|
|
21718
|
+
const tx = document.createElement("span");
|
|
21719
|
+
tx.textContent = row.label;
|
|
21720
|
+
tx.style.cssText = "flex:1;";
|
|
21721
|
+
item.append(ic, tx);
|
|
21722
|
+
item.addEventListener("click", (e) => {
|
|
21723
|
+
e.stopPropagation();
|
|
21724
|
+
pop.hide();
|
|
21725
|
+
row.onClick();
|
|
21726
|
+
});
|
|
21727
|
+
menu2.appendChild(item);
|
|
21728
|
+
}
|
|
21729
|
+
}
|
|
21730
|
+
});
|
|
20576
21731
|
this.menuOwner = anchor;
|
|
20577
21732
|
}
|
|
20578
21733
|
/** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
|
|
@@ -20702,60 +21857,40 @@ var DrawingSettingsPopup = class {
|
|
|
20702
21857
|
/** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
|
|
20703
21858
|
* glyph (and text label when one is supplied); the current value is highlighted. */
|
|
20704
21859
|
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
|
-
|
|
21860
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
21861
|
+
zIndex: 26,
|
|
21862
|
+
padding: "4px",
|
|
21863
|
+
fill: (menu2, pop) => {
|
|
21864
|
+
for (const v of values) {
|
|
21865
|
+
const active = v === current;
|
|
21866
|
+
const item = document.createElement("button");
|
|
21867
|
+
item.type = "button";
|
|
21868
|
+
item.className = "vela-dpop-item";
|
|
21869
|
+
item.dataset.active = active ? "1" : "0";
|
|
21870
|
+
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;`;
|
|
21871
|
+
const ic = document.createElement("span");
|
|
21872
|
+
ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
|
|
21873
|
+
ic.innerHTML = sized(render(v), 18);
|
|
21874
|
+
item.appendChild(ic);
|
|
21875
|
+
if (label) {
|
|
21876
|
+
const tx = document.createElement("span");
|
|
21877
|
+
tx.textContent = label(v);
|
|
21878
|
+
tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
|
|
21879
|
+
item.appendChild(tx);
|
|
21880
|
+
}
|
|
21881
|
+
item.addEventListener("click", (e) => {
|
|
21882
|
+
e.stopPropagation();
|
|
21883
|
+
pop.hide();
|
|
21884
|
+
onPick(v);
|
|
21885
|
+
});
|
|
21886
|
+
menu2.appendChild(item);
|
|
21887
|
+
}
|
|
20729
21888
|
}
|
|
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;
|
|
21889
|
+
});
|
|
20751
21890
|
this.menuOwner = anchor;
|
|
20752
21891
|
}
|
|
20753
21892
|
closeMenu() {
|
|
20754
|
-
|
|
20755
|
-
this.menuEl.remove();
|
|
20756
|
-
this.menuEl = null;
|
|
20757
|
-
}
|
|
20758
|
-
this.menuOwner = null;
|
|
21893
|
+
this.menuPop?.hide();
|
|
20759
21894
|
}
|
|
20760
21895
|
/** A color control: an `icon` over a thin **colored underline** showing the current
|
|
20761
21896
|
* value (the common idiom), with an invisible native picker overlaid to edit it.
|
|
@@ -20794,36 +21929,18 @@ var DrawingSettingsPopup = class {
|
|
|
20794
21929
|
}
|
|
20795
21930
|
/** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
|
|
20796
21931
|
openColorPopover(anchor, color, onChange) {
|
|
20797
|
-
this.closeColorPopover();
|
|
20798
|
-
this.closeMenu();
|
|
20799
21932
|
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;
|
|
21933
|
+
this.colorPop = this.hostFloat(anchor, {
|
|
21934
|
+
zIndex: 25,
|
|
21935
|
+
padding: "10px",
|
|
21936
|
+
fill: (el) => {
|
|
21937
|
+
el.appendChild(buildColorPicker(color, t, onChange));
|
|
21938
|
+
}
|
|
21939
|
+
});
|
|
20819
21940
|
this.colorOwner = anchor;
|
|
20820
21941
|
}
|
|
20821
21942
|
closeColorPopover() {
|
|
20822
|
-
|
|
20823
|
-
this.colorPop.remove();
|
|
20824
|
-
this.colorPop = null;
|
|
20825
|
-
}
|
|
20826
|
-
this.colorOwner = null;
|
|
21943
|
+
this.colorPop?.hide();
|
|
20827
21944
|
}
|
|
20828
21945
|
divider() {
|
|
20829
21946
|
const d = document.createElement("div");
|
|
@@ -20962,7 +22079,7 @@ var DrawingToolbar = class {
|
|
|
20962
22079
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
20963
22080
|
});
|
|
20964
22081
|
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
20965
|
-
|
|
22082
|
+
ensureStyles3();
|
|
20966
22083
|
this.root = document.createElement("div");
|
|
20967
22084
|
this.root.className = "vela-dtb";
|
|
20968
22085
|
this.styleRoot();
|
|
@@ -21445,13 +22562,13 @@ var DrawingToolbar = class {
|
|
|
21445
22562
|
}
|
|
21446
22563
|
};
|
|
21447
22564
|
var MAGNET_ID = "__magnet__";
|
|
21448
|
-
var
|
|
21449
|
-
function
|
|
22565
|
+
var STYLE_ID4 = "vela-drawing-toolbar-styles";
|
|
22566
|
+
function ensureStyles3() {
|
|
21450
22567
|
if (typeof document === "undefined") return;
|
|
21451
|
-
let s = document.getElementById(
|
|
22568
|
+
let s = document.getElementById(STYLE_ID4);
|
|
21452
22569
|
if (!s) {
|
|
21453
22570
|
s = document.createElement("style");
|
|
21454
|
-
s.id =
|
|
22571
|
+
s.id = STYLE_ID4;
|
|
21455
22572
|
document.head.appendChild(s);
|
|
21456
22573
|
}
|
|
21457
22574
|
s.textContent = `
|
|
@@ -21830,9 +22947,9 @@ var UserDrawingController = class {
|
|
|
21830
22947
|
}
|
|
21831
22948
|
/**
|
|
21832
22949
|
* Union of the visible drawings' price ranges on `paneId` whose time extent
|
|
21833
|
-
* intersects [fromTime, toTime]
|
|
21834
|
-
*
|
|
21835
|
-
*
|
|
22950
|
+
* intersects [fromTime, toTime]. Hidden drawings and full-width references
|
|
22951
|
+
* (hline → null extent) are handled too. Kept as the seam for a future
|
|
22952
|
+
* per-drawing autoscale opt-in — the renderer does not fold this in today.
|
|
21836
22953
|
*/
|
|
21837
22954
|
priceRangeForPane(paneId, fromTime, toTime) {
|
|
21838
22955
|
let lo = Infinity;
|
|
@@ -22707,7 +23824,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
|
|
|
22707
23824
|
|
|
22708
23825
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
22709
23826
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
22710
|
-
var
|
|
23827
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
22711
23828
|
var CSS2 = `
|
|
22712
23829
|
.vela-attribution {
|
|
22713
23830
|
text-decoration: none;
|
|
@@ -22770,11 +23887,11 @@ var CSS2 = `
|
|
|
22770
23887
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
22771
23888
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
22772
23889
|
`;
|
|
22773
|
-
function
|
|
22774
|
-
let s = doc.getElementById(
|
|
23890
|
+
function ensureStyles4(doc) {
|
|
23891
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
22775
23892
|
if (!s) {
|
|
22776
23893
|
s = doc.createElement("style");
|
|
22777
|
-
s.id =
|
|
23894
|
+
s.id = STYLE_ID5;
|
|
22778
23895
|
doc.head.appendChild(s);
|
|
22779
23896
|
}
|
|
22780
23897
|
s.textContent = CSS2;
|
|
@@ -22803,7 +23920,7 @@ function createCustomMark(doc, html, background) {
|
|
|
22803
23920
|
return el;
|
|
22804
23921
|
}
|
|
22805
23922
|
function createAttributionMark(doc, background) {
|
|
22806
|
-
|
|
23923
|
+
ensureStyles4(doc);
|
|
22807
23924
|
const a = doc.createElement("a");
|
|
22808
23925
|
a.className = "vela-attribution";
|
|
22809
23926
|
a.href = ATTRIBUTION_URL;
|
|
@@ -25781,7 +26898,6 @@ var NativeRenderer = class {
|
|
|
25781
26898
|
const n = this.coords.barCount;
|
|
25782
26899
|
if (n === 0) return;
|
|
25783
26900
|
const vr = this.coords.visibleLogicalRange();
|
|
25784
|
-
const vtr = this.coords.visibleTimeRange();
|
|
25785
26901
|
const i0 = Math.max(0, Math.floor(vr.from));
|
|
25786
26902
|
const i1 = Math.min(n - 1, Math.ceil(vr.to));
|
|
25787
26903
|
if (i1 < i0) return;
|
|
@@ -25799,9 +26915,7 @@ var NativeRenderer = class {
|
|
|
25799
26915
|
}
|
|
25800
26916
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
25801
26917
|
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;
|
|
26918
|
+
const dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
25805
26919
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
25806
26920
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
25807
26921
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -26798,55 +27912,6 @@ function resolveElement(container) {
|
|
|
26798
27912
|
if (!element) throw new Error(`[vela] container not found for selector "${container}"`);
|
|
26799
27913
|
return element;
|
|
26800
27914
|
}
|
|
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
27915
|
function runMachine(machine5, props, render) {
|
|
26851
27916
|
const m = new vanilla.VanillaMachine(machine5, props);
|
|
26852
27917
|
const unsub = m.subscribe(render);
|
|
@@ -27088,7 +28153,10 @@ function menuController(opts) {
|
|
|
27088
28153
|
props: {
|
|
27089
28154
|
id: opts.id ?? nextUid("vela-menu"),
|
|
27090
28155
|
ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
|
|
27091
|
-
positioning: {
|
|
28156
|
+
positioning: {
|
|
28157
|
+
placement: opts.placement ?? "bottom-start",
|
|
28158
|
+
...opts.getAnchorRect ? { getAnchorRect: () => opts.getAnchorRect() } : {}
|
|
28159
|
+
},
|
|
27092
28160
|
onSelect: (d) => opts.onSelect?.(d.value),
|
|
27093
28161
|
onOpenChange: (d) => opts.onOpenChange?.(d.open)
|
|
27094
28162
|
},
|
|
@@ -27170,6 +28238,22 @@ var MENU_CSS = `
|
|
|
27170
28238
|
}
|
|
27171
28239
|
.vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
|
|
27172
28240
|
.vela-menu-switch.on::after { transform: translateX(16px); background: var(--vela-bg); }
|
|
28241
|
+
/* Favorite star (rows carrying \`favorite\`): reserved space always \u2014 it fades in on row
|
|
28242
|
+
hover instead of shifting the layout \u2014 and a starred row keeps its filled star visible.
|
|
28243
|
+
Same control language as the drawing toolbar's flyout stars. */
|
|
28244
|
+
.vela-menu-item .vela-menu-star {
|
|
28245
|
+
order: 98;
|
|
28246
|
+
/* Pad the 16px icon slot into a bigger hit target without moving the row's layout. */
|
|
28247
|
+
margin: -3px -5px -3px 0;
|
|
28248
|
+
padding: 3px 5px;
|
|
28249
|
+
box-sizing: content-box;
|
|
28250
|
+
border-radius: var(--vela-radius-sm);
|
|
28251
|
+
opacity: 0;
|
|
28252
|
+
transition: opacity 0.1s ease, background 0.1s ease;
|
|
28253
|
+
}
|
|
28254
|
+
.vela-menu-item[data-highlighted] .vela-menu-star { opacity: 0.55; }
|
|
28255
|
+
.vela-menu-item .vela-menu-star:hover { opacity: 1; background: var(--vela-hover-strong); }
|
|
28256
|
+
.vela-menu-item .vela-menu-star.vela-fav { opacity: 0.95; color: var(--vela-highlight); }
|
|
27173
28257
|
.vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
|
|
27174
28258
|
/* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
|
|
27175
28259
|
list is open so the path you came down remains readable. */
|
|
@@ -27188,9 +28272,13 @@ var Surface = class _Surface {
|
|
|
27188
28272
|
this.items = [];
|
|
27189
28273
|
/** Branch item id → the surface it opens. */
|
|
27190
28274
|
this.subs = /* @__PURE__ */ new Map();
|
|
28275
|
+
/** Trigger rect captured when this level opened — the list stays put if the
|
|
28276
|
+
* trigger then moves (favorite chips shifting the caret). */
|
|
28277
|
+
this.pinnedAnchor = null;
|
|
27191
28278
|
this.doc = doc;
|
|
27192
28279
|
this.host = opts.host;
|
|
27193
28280
|
this.onSelect = opts.onSelect;
|
|
28281
|
+
this.onFavorite = opts.onFavorite;
|
|
27194
28282
|
this.positioner = doc.createElement("div");
|
|
27195
28283
|
this.positioner.className = "vela-ui-layer";
|
|
27196
28284
|
this.list = doc.createElement("ul");
|
|
@@ -27198,16 +28286,25 @@ var Surface = class _Surface {
|
|
|
27198
28286
|
if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
|
|
27199
28287
|
this.positioner.appendChild(this.list);
|
|
27200
28288
|
this.host.appendChild(this.positioner);
|
|
28289
|
+
const trigger = opts.trigger;
|
|
27201
28290
|
this.ctrl = menuController({
|
|
27202
28291
|
items: [],
|
|
27203
28292
|
id: opts.id,
|
|
27204
28293
|
placement: opts.placement,
|
|
27205
28294
|
onSelect: (id) => this.onSelect(id),
|
|
27206
|
-
|
|
28295
|
+
getAnchorRect: () => this.pinnedAnchor,
|
|
28296
|
+
onOpenChange: (open2) => {
|
|
28297
|
+
if (open2 && trigger) {
|
|
28298
|
+
const r = trigger.getBoundingClientRect();
|
|
28299
|
+
this.pinnedAnchor = { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
28300
|
+
} else {
|
|
28301
|
+
this.pinnedAnchor = null;
|
|
28302
|
+
}
|
|
28303
|
+
opts.onOpenChange?.(open2);
|
|
28304
|
+
},
|
|
27207
28305
|
triggerId: opts.triggerId
|
|
27208
28306
|
});
|
|
27209
28307
|
this.mid = String(this.ctrl.props.id);
|
|
27210
|
-
const trigger = opts.trigger;
|
|
27211
28308
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
27212
28309
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
27213
28310
|
const api = this.ctrl.connect(service);
|
|
@@ -27287,6 +28384,25 @@ var Surface = class _Surface {
|
|
|
27287
28384
|
hint.textContent = item.hint;
|
|
27288
28385
|
li.appendChild(hint);
|
|
27289
28386
|
}
|
|
28387
|
+
if (item.favorite !== void 0 && this.onFavorite) {
|
|
28388
|
+
const on = item.favorite;
|
|
28389
|
+
const star = iconEl(on ? "star-filled" : "star", doc);
|
|
28390
|
+
star.classList.add("vela-menu-star");
|
|
28391
|
+
if (on) star.classList.add("vela-fav");
|
|
28392
|
+
star.removeAttribute("aria-hidden");
|
|
28393
|
+
star.setAttribute("role", "button");
|
|
28394
|
+
star.setAttribute("aria-label", on ? "Remove from favorites" : "Add to favorites");
|
|
28395
|
+
star.setAttribute("aria-pressed", on ? "true" : "false");
|
|
28396
|
+
for (const ev of ["pointerdown", "pointerup", "mousedown", "mouseup"]) {
|
|
28397
|
+
star.addEventListener(ev, (e) => e.stopPropagation());
|
|
28398
|
+
}
|
|
28399
|
+
star.addEventListener("click", (e) => {
|
|
28400
|
+
e.stopPropagation();
|
|
28401
|
+
e.preventDefault();
|
|
28402
|
+
this.onFavorite?.(item.id, !on);
|
|
28403
|
+
});
|
|
28404
|
+
li.appendChild(star);
|
|
28405
|
+
}
|
|
27290
28406
|
if (item.toggle) {
|
|
27291
28407
|
const sw = doc.createElement("span");
|
|
27292
28408
|
sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
|
|
@@ -27302,6 +28418,7 @@ var Surface = class _Surface {
|
|
|
27302
28418
|
host: this.host,
|
|
27303
28419
|
placement: "right-start",
|
|
27304
28420
|
onSelect: this.onSelect,
|
|
28421
|
+
onFavorite: this.onFavorite,
|
|
27305
28422
|
id: `${this.mid}--${item.id}`
|
|
27306
28423
|
});
|
|
27307
28424
|
sub.setItems(item.submenu ?? []);
|
|
@@ -27325,7 +28442,8 @@ var Menu = class {
|
|
|
27325
28442
|
trigger: opts.trigger,
|
|
27326
28443
|
triggerId: opts.triggerId,
|
|
27327
28444
|
id: opts.id,
|
|
27328
|
-
minWidth: opts.minWidth
|
|
28445
|
+
minWidth: opts.minWidth,
|
|
28446
|
+
onFavorite: opts.onFavorite
|
|
27329
28447
|
});
|
|
27330
28448
|
this.root.setItems(opts.items);
|
|
27331
28449
|
}
|
|
@@ -27829,7 +28947,7 @@ var Drawer = class {
|
|
|
27829
28947
|
};
|
|
27830
28948
|
|
|
27831
28949
|
// src/widget/layout-picker.ts
|
|
27832
|
-
var
|
|
28950
|
+
var STYLE_ID6 = "vela-widget-layout-picker-v14";
|
|
27833
28951
|
var CSS3 = `
|
|
27834
28952
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
27835
28953
|
.vela-lp {
|
|
@@ -27934,7 +29052,7 @@ var CSS3 = `
|
|
|
27934
29052
|
`;
|
|
27935
29053
|
var GRID = 4;
|
|
27936
29054
|
function layoutGridCanvas(doc) {
|
|
27937
|
-
injectStyles(
|
|
29055
|
+
injectStyles(STYLE_ID6, CSS3, doc);
|
|
27938
29056
|
const el = doc.createElement("div");
|
|
27939
29057
|
el.className = "vela-lp-grid";
|
|
27940
29058
|
const squares = [];
|
|
@@ -27974,7 +29092,7 @@ var LayoutPicker = class {
|
|
|
27974
29092
|
};
|
|
27975
29093
|
this.opts = opts;
|
|
27976
29094
|
this.doc = opts.host.ownerDocument;
|
|
27977
|
-
injectStyles(
|
|
29095
|
+
injectStyles(STYLE_ID6, CSS3, this.doc);
|
|
27978
29096
|
const doc = this.doc;
|
|
27979
29097
|
this.layer = doc.createElement("div");
|
|
27980
29098
|
this.layer.className = "vela-ui-layer vela-lp-layer";
|
|
@@ -28287,6 +29405,24 @@ function timeframeMs(value) {
|
|
|
28287
29405
|
if (!Number.isFinite(count) || count < 1) return NaN;
|
|
28288
29406
|
return count * UNIT_MS[key];
|
|
28289
29407
|
}
|
|
29408
|
+
function favoriteTimeframeChips(favorites) {
|
|
29409
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29410
|
+
const out = [];
|
|
29411
|
+
for (const tf of favorites) {
|
|
29412
|
+
if (seen.has(tf)) continue;
|
|
29413
|
+
seen.add(tf);
|
|
29414
|
+
out.push(tf);
|
|
29415
|
+
}
|
|
29416
|
+
return out.sort((a, b) => {
|
|
29417
|
+
const ma = timeframeMs(a);
|
|
29418
|
+
const mb = timeframeMs(b);
|
|
29419
|
+
const aOk = Number.isFinite(ma);
|
|
29420
|
+
const bOk = Number.isFinite(mb);
|
|
29421
|
+
if (aOk && bOk && ma !== mb) return ma - mb;
|
|
29422
|
+
if (aOk !== bOk) return aOk ? -1 : 1;
|
|
29423
|
+
return 0;
|
|
29424
|
+
});
|
|
29425
|
+
}
|
|
28290
29426
|
function timeframeLabel(value) {
|
|
28291
29427
|
const parsed = parseTimeframe(value);
|
|
28292
29428
|
if (!parsed.valid) return value;
|
|
@@ -28301,7 +29437,7 @@ function timeframeLabel(value) {
|
|
|
28301
29437
|
}
|
|
28302
29438
|
|
|
28303
29439
|
// src/widget/topbar.ts
|
|
28304
|
-
var
|
|
29440
|
+
var STYLE_ID7 = "vela-topbar";
|
|
28305
29441
|
var CSS4 = `
|
|
28306
29442
|
.vela-widget-topbar {
|
|
28307
29443
|
display: flex;
|
|
@@ -28339,6 +29475,36 @@ var CSS4 = `
|
|
|
28339
29475
|
color: var(--vela-fg-bright);
|
|
28340
29476
|
}
|
|
28341
29477
|
.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); }
|
|
29478
|
+
/* Timeframe cluster: duration-sorted favorite chips, highlight in place, caret
|
|
29479
|
+
opening the full dropdown. With no favorites the caret is the merged trigger
|
|
29480
|
+
(label + chevron). An unstarred current value sits as an extra chip by the caret. */
|
|
29481
|
+
.vela-widget-tf-group { display: inline-flex; align-items: center; gap: 2px; }
|
|
29482
|
+
.vela-widget-tf-chips { display: inline-flex; align-items: center; gap: 2px; }
|
|
29483
|
+
.vela-widget-tf-chips:empty { display: none; }
|
|
29484
|
+
.vela-widget-tf[data-current='1'] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
29485
|
+
.vela-widget-tf-caret {
|
|
29486
|
+
all: unset;
|
|
29487
|
+
display: inline-flex;
|
|
29488
|
+
align-items: center;
|
|
29489
|
+
justify-content: center;
|
|
29490
|
+
width: 18px;
|
|
29491
|
+
height: 30px;
|
|
29492
|
+
border-radius: 4px;
|
|
29493
|
+
cursor: pointer;
|
|
29494
|
+
color: var(--vela-fg-muted);
|
|
29495
|
+
}
|
|
29496
|
+
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
29497
|
+
.vela-widget-tf-caret[data-solo='1'] {
|
|
29498
|
+
width: auto;
|
|
29499
|
+
padding: 0 6px 0 9px;
|
|
29500
|
+
gap: 4px;
|
|
29501
|
+
color: var(--vela-fg-bright);
|
|
29502
|
+
background: var(--vela-hover-strong);
|
|
29503
|
+
font-size: 13px;
|
|
29504
|
+
font-weight: 550;
|
|
29505
|
+
white-space: nowrap;
|
|
29506
|
+
}
|
|
29507
|
+
.vela-widget-topbar .vela-widget-tf-caret .vela-icon { font-size: 14px; width: 14px; height: 14px; }
|
|
28342
29508
|
.vela-widget-topbar .vela-icon { color: inherit; font-size: 16px; width: 16px; height: 16px; }
|
|
28343
29509
|
/* Width is set in syncHairlines() to exactly one device pixel \u2014 a CSS 1px at
|
|
28344
29510
|
fractional DPR (1.25, 1.5\u2026) straddles two physical pixels and siblings end
|
|
@@ -28436,16 +29602,23 @@ var Topbar = class {
|
|
|
28436
29602
|
this.timeframe = opts.timeframe;
|
|
28437
29603
|
this.priceStyle = opts.priceStyle;
|
|
28438
29604
|
const doc = host.ownerDocument;
|
|
28439
|
-
injectStyles(
|
|
29605
|
+
injectStyles(STYLE_ID7, CSS4, doc);
|
|
28440
29606
|
this.el = doc.createElement("div");
|
|
28441
29607
|
this.el.className = "vela-widget-topbar";
|
|
28442
29608
|
this.symbolEl = doc.createElement("button");
|
|
28443
29609
|
this.symbolEl.className = "vela-widget-symbol";
|
|
28444
29610
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
28445
29611
|
if (opts.onSymbolClick) this.symbolEl.addEventListener("click", opts.onSymbolClick);
|
|
28446
|
-
this.
|
|
28447
|
-
this.
|
|
28448
|
-
this.
|
|
29612
|
+
this.tfFavs = [...opts.timeframeFavorites ?? []];
|
|
29613
|
+
this.tfChipsHost = doc.createElement("span");
|
|
29614
|
+
this.tfChipsHost.className = "vela-widget-tf-chips";
|
|
29615
|
+
this.tfCaret = doc.createElement("button");
|
|
29616
|
+
this.tfCaret.className = "vela-widget-tf-caret";
|
|
29617
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
29618
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
29619
|
+
const tfGroup = doc.createElement("span");
|
|
29620
|
+
tfGroup.className = "vela-widget-tf-group";
|
|
29621
|
+
tfGroup.append(this.tfChipsHost, this.tfCaret);
|
|
28449
29622
|
this.styleButton = doc.createElement("button");
|
|
28450
29623
|
this.styleButton.className = "vela-widget-style";
|
|
28451
29624
|
this.renderStyleButton(doc);
|
|
@@ -28485,19 +29658,20 @@ var Topbar = class {
|
|
|
28485
29658
|
d.className = "vela-sep";
|
|
28486
29659
|
return d;
|
|
28487
29660
|
};
|
|
28488
|
-
const leading = [this.symbolEl, sep(),
|
|
29661
|
+
const leading = [this.symbolEl, sep(), tfGroup, sep(), this.styleButton, sep()];
|
|
28489
29662
|
if (this.layoutButton) leading.push(this.layoutButton, sep());
|
|
28490
29663
|
if (indicatorsBtn) leading.push(indicatorsBtn, sep());
|
|
28491
29664
|
this.leftActionsSep = sep();
|
|
28492
29665
|
this.leftActionsSep.hidden = true;
|
|
28493
29666
|
this.el.append(...leading, this.leftActionsHost, this.leftActionsSep, this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
|
|
28494
29667
|
host.appendChild(this.el);
|
|
29668
|
+
this.renderTfChips();
|
|
28495
29669
|
this.onHairlineSync();
|
|
28496
29670
|
this.hairlineRo = new ResizeObserver(this.onHairlineSync);
|
|
28497
29671
|
this.hairlineRo.observe(this.el);
|
|
28498
29672
|
doc.defaultView?.addEventListener("resize", this.onHairlineSync);
|
|
28499
29673
|
this.renderActions();
|
|
28500
|
-
this.tooltips.push(new Tooltip(this.
|
|
29674
|
+
this.tooltips.push(new Tooltip(this.tfCaret, { content: "Timeframe", triggerId: "vela-topbar-tf", host }));
|
|
28501
29675
|
this.tooltips.push(new Tooltip(this.styleButton, { content: "Chart style", triggerId: "vela-topbar-style", host }));
|
|
28502
29676
|
if (this.layoutButton && opts.layout) {
|
|
28503
29677
|
this.tooltips.push(new Tooltip(this.layoutButton, { content: "Layout", triggerId: "vela-topbar-layout", host }));
|
|
@@ -28514,11 +29688,12 @@ var Topbar = class {
|
|
|
28514
29688
|
});
|
|
28515
29689
|
}
|
|
28516
29690
|
this.tfMenu = new Menu({
|
|
28517
|
-
trigger: this.
|
|
29691
|
+
trigger: this.tfCaret,
|
|
28518
29692
|
triggerId: "vela-topbar-tf",
|
|
28519
29693
|
host,
|
|
28520
29694
|
items: this.tfItems(),
|
|
28521
29695
|
onSelect: (id) => opts.onTimeframe(id),
|
|
29696
|
+
onFavorite: (id, on) => opts.onTimeframeFavorite?.(id, on),
|
|
28522
29697
|
// Timeframe labels are two-or-three characters ("1m", "4h", "1D") — the
|
|
28523
29698
|
// stylesheet's default min-width would leave the list mostly empty.
|
|
28524
29699
|
minWidth: "84px"
|
|
@@ -28536,8 +29711,48 @@ var Topbar = class {
|
|
|
28536
29711
|
}
|
|
28537
29712
|
setTimeframe(tf) {
|
|
28538
29713
|
this.timeframe = tf;
|
|
28539
|
-
this.tfButton.textContent = timeframeLabel(tf);
|
|
28540
29714
|
this.tfMenu.setItems(this.tfItems());
|
|
29715
|
+
this.renderTfChips();
|
|
29716
|
+
}
|
|
29717
|
+
/** Reflect the favorite-timeframe set — the quick-switch chips and the dropdown stars. */
|
|
29718
|
+
setTimeframeFavorites(favs) {
|
|
29719
|
+
this.tfFavs = [...favs];
|
|
29720
|
+
this.renderTfChips();
|
|
29721
|
+
this.tfMenu.setItems(this.tfItems());
|
|
29722
|
+
}
|
|
29723
|
+
/** Rebuild the quick-switch chips (current value changed, or the favorite set did). */
|
|
29724
|
+
renderTfChips() {
|
|
29725
|
+
const doc = this.el.ownerDocument;
|
|
29726
|
+
this.tfChipsHost.replaceChildren();
|
|
29727
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
29728
|
+
const chips = stars ? favoriteTimeframeChips(this.tfFavs) : [];
|
|
29729
|
+
const currentIsFav = chips.includes(this.timeframe);
|
|
29730
|
+
const shown = currentIsFav || chips.length === 0 ? chips : [...chips, this.timeframe];
|
|
29731
|
+
for (const tf of shown) {
|
|
29732
|
+
const b = doc.createElement("button");
|
|
29733
|
+
b.className = "vela-widget-tf";
|
|
29734
|
+
const label = timeframeLabel(tf);
|
|
29735
|
+
b.textContent = label;
|
|
29736
|
+
if (tf === this.timeframe) {
|
|
29737
|
+
b.dataset.current = "1";
|
|
29738
|
+
b.setAttribute("aria-current", "true");
|
|
29739
|
+
} else {
|
|
29740
|
+
b.setAttribute("aria-label", `Switch timeframe to ${label}`);
|
|
29741
|
+
b.addEventListener("click", () => this.opts.onTimeframe(tf));
|
|
29742
|
+
}
|
|
29743
|
+
this.tfChipsHost.appendChild(b);
|
|
29744
|
+
}
|
|
29745
|
+
this.tfCaret.replaceChildren();
|
|
29746
|
+
if (chips.length === 0) {
|
|
29747
|
+
this.tfCaret.dataset.solo = "1";
|
|
29748
|
+
this.tfCaret.append(doc.createTextNode(timeframeLabel(this.timeframe)), iconEl("chevron-down", doc));
|
|
29749
|
+
this.tfCaret.setAttribute("aria-label", `Timeframe \u2014 ${timeframeLabel(this.timeframe)}`);
|
|
29750
|
+
} else {
|
|
29751
|
+
delete this.tfCaret.dataset.solo;
|
|
29752
|
+
this.tfCaret.appendChild(iconEl("chevron-down", doc));
|
|
29753
|
+
this.tfCaret.setAttribute("aria-label", "Timeframes");
|
|
29754
|
+
}
|
|
29755
|
+
this.onHairlineSync();
|
|
28541
29756
|
}
|
|
28542
29757
|
setPriceStyle(style) {
|
|
28543
29758
|
this.priceStyle = style;
|
|
@@ -28615,9 +29830,9 @@ var Topbar = class {
|
|
|
28615
29830
|
}
|
|
28616
29831
|
/** Reflect a docked side panel's open state on its button — the panels toggle each other,
|
|
28617
29832
|
* so the dock pushes the state rather than the button assuming it. */
|
|
28618
|
-
setPanelActive(id,
|
|
29833
|
+
setPanelActive(id, open2) {
|
|
28619
29834
|
const btn = this.panelBtns.get(id);
|
|
28620
|
-
if (btn) btn.dataset.active =
|
|
29835
|
+
if (btn) btn.dataset.active = open2 ? "1" : "";
|
|
28621
29836
|
}
|
|
28622
29837
|
destroy() {
|
|
28623
29838
|
this.hairlineRo?.disconnect();
|
|
@@ -28655,7 +29870,13 @@ var Topbar = class {
|
|
|
28655
29870
|
}
|
|
28656
29871
|
}
|
|
28657
29872
|
tfItems() {
|
|
28658
|
-
|
|
29873
|
+
const stars = this.opts.onTimeframeFavorite !== void 0;
|
|
29874
|
+
return this.opts.timeframes.map((tf) => ({
|
|
29875
|
+
id: tf,
|
|
29876
|
+
label: timeframeLabel(tf),
|
|
29877
|
+
checked: tf === this.timeframe,
|
|
29878
|
+
...stars ? { favorite: this.tfFavs.includes(tf) } : {}
|
|
29879
|
+
}));
|
|
28659
29880
|
}
|
|
28660
29881
|
styleItems() {
|
|
28661
29882
|
return priceStyleIds().map((id, i) => ({
|
|
@@ -28678,10 +29899,10 @@ function fmtPrice(n, dp) {
|
|
|
28678
29899
|
const d = dp ?? decimalsFor(n);
|
|
28679
29900
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
28680
29901
|
}
|
|
28681
|
-
function fmtChange(
|
|
28682
|
-
if (
|
|
28683
|
-
const diff = close -
|
|
28684
|
-
const pct = diff /
|
|
29902
|
+
function fmtChange(open2, close) {
|
|
29903
|
+
if (open2 == null || close == null || !Number.isFinite(open2) || !Number.isFinite(close) || open2 === 0) return "";
|
|
29904
|
+
const diff = close - open2;
|
|
29905
|
+
const pct = diff / open2 * 100;
|
|
28685
29906
|
const sign = diff >= 0 ? "+" : "";
|
|
28686
29907
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
28687
29908
|
}
|
|
@@ -28730,7 +29951,7 @@ function tickerIconEl(doc, base, name, className) {
|
|
|
28730
29951
|
}
|
|
28731
29952
|
|
|
28732
29953
|
// src/widget/statusline.ts
|
|
28733
|
-
var
|
|
29954
|
+
var STYLE_ID8 = "vela-widget-statusline";
|
|
28734
29955
|
var CSS5 = `
|
|
28735
29956
|
.vela-statusline {
|
|
28736
29957
|
position: absolute;
|
|
@@ -28922,7 +30143,7 @@ var Statusline = class {
|
|
|
28922
30143
|
this.fitMode = false;
|
|
28923
30144
|
this.fitRO = null;
|
|
28924
30145
|
const doc = host.ownerDocument;
|
|
28925
|
-
injectStyles(
|
|
30146
|
+
injectStyles(STYLE_ID8, CSS5, doc);
|
|
28926
30147
|
host.classList.add("vela-has-statusline");
|
|
28927
30148
|
this.el = doc.createElement("div");
|
|
28928
30149
|
this.el.className = "vela-statusline";
|
|
@@ -29097,7 +30318,7 @@ var Statusline = class {
|
|
|
29097
30318
|
var MAX_FONT_PX = 36;
|
|
29098
30319
|
var MIN_FONT_PX = 12;
|
|
29099
30320
|
var FILL = 0.9;
|
|
29100
|
-
var
|
|
30321
|
+
var STYLE_ID9 = "vela-widget-watermark";
|
|
29101
30322
|
var CSS6 = `
|
|
29102
30323
|
.vela-watermark {
|
|
29103
30324
|
position: absolute;
|
|
@@ -29136,7 +30357,7 @@ var Watermark = class {
|
|
|
29136
30357
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
29137
30358
|
* fires during chart construction, before any subscriber can see it. */
|
|
29138
30359
|
this.loading = true;
|
|
29139
|
-
injectStyles(
|
|
30360
|
+
injectStyles(STYLE_ID9, CSS6, host.ownerDocument);
|
|
29140
30361
|
this.el = host.ownerDocument.createElement("div");
|
|
29141
30362
|
this.el.className = "vela-watermark";
|
|
29142
30363
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -29200,7 +30421,7 @@ var RANGE_PRESETS = [
|
|
|
29200
30421
|
{ id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
|
|
29201
30422
|
// everything the provider serves
|
|
29202
30423
|
];
|
|
29203
|
-
var
|
|
30424
|
+
var STYLE_ID10 = "vela-widget-bottombar";
|
|
29204
30425
|
var CSS7 = `
|
|
29205
30426
|
.vela-widget-bottombar {
|
|
29206
30427
|
display: flex;
|
|
@@ -29276,7 +30497,7 @@ var Bottombar = class {
|
|
|
29276
30497
|
this.timer = null;
|
|
29277
30498
|
this.timezone = opts.timezone;
|
|
29278
30499
|
const doc = host.ownerDocument;
|
|
29279
|
-
injectStyles(
|
|
30500
|
+
injectStyles(STYLE_ID10, CSS7, doc);
|
|
29280
30501
|
this.el = doc.createElement("div");
|
|
29281
30502
|
this.el.className = "vela-widget-bottombar";
|
|
29282
30503
|
for (const preset of RANGE_PRESETS) {
|
|
@@ -29414,7 +30635,7 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
29414
30635
|
}
|
|
29415
30636
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
29416
30637
|
}
|
|
29417
|
-
var
|
|
30638
|
+
var STYLE_ID11 = "vela-widget-symbolpicker";
|
|
29418
30639
|
var CSS8 = `
|
|
29419
30640
|
.vela-sp-searchrow {
|
|
29420
30641
|
display: flex;
|
|
@@ -29512,7 +30733,7 @@ var SymbolPicker = class {
|
|
|
29512
30733
|
this.seed = "";
|
|
29513
30734
|
this.activeTab = "All";
|
|
29514
30735
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
29515
|
-
injectStyles(
|
|
30736
|
+
injectStyles(STYLE_ID11, CSS8, doc);
|
|
29516
30737
|
this.input = doc.createElement("input");
|
|
29517
30738
|
this.input.className = "vela-sp-input";
|
|
29518
30739
|
this.input.placeholder = "Search symbol\u2026";
|
|
@@ -29542,8 +30763,8 @@ var SymbolPicker = class {
|
|
|
29542
30763
|
host: opts.host,
|
|
29543
30764
|
closeOnInteractOutside: true,
|
|
29544
30765
|
content: (body) => body.append(searchRow, this.tabs, this.list),
|
|
29545
|
-
onOpenChange: (
|
|
29546
|
-
if (
|
|
30766
|
+
onOpenChange: (open2) => {
|
|
30767
|
+
if (open2) {
|
|
29547
30768
|
this.input.value = this.seed;
|
|
29548
30769
|
this.seed = "";
|
|
29549
30770
|
this.refresh();
|
|
@@ -29552,7 +30773,7 @@ var SymbolPicker = class {
|
|
|
29552
30773
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length);
|
|
29553
30774
|
}, 0);
|
|
29554
30775
|
}
|
|
29555
|
-
opts.onOpenChange?.(
|
|
30776
|
+
opts.onOpenChange?.(open2);
|
|
29556
30777
|
}
|
|
29557
30778
|
});
|
|
29558
30779
|
this.input.addEventListener("input", () => this.refresh());
|
|
@@ -29834,7 +31055,7 @@ function treeIsEmpty(panes) {
|
|
|
29834
31055
|
}
|
|
29835
31056
|
|
|
29836
31057
|
// src/widget/side-panel.ts
|
|
29837
|
-
var
|
|
31058
|
+
var STYLE_ID12 = "vela-widget-sidepanel";
|
|
29838
31059
|
var DEFAULT_PANEL_WIDTH = 280;
|
|
29839
31060
|
var DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
29840
31061
|
var DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
@@ -29938,7 +31159,7 @@ var SidePanel = class {
|
|
|
29938
31159
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
29939
31160
|
this.onWidthChange = null;
|
|
29940
31161
|
const doc = host.ownerDocument;
|
|
29941
|
-
injectStyles(
|
|
31162
|
+
injectStyles(STYLE_ID12, CSS9, doc);
|
|
29942
31163
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
29943
31164
|
this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
|
|
29944
31165
|
this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
|
|
@@ -29970,10 +31191,10 @@ var SidePanel = class {
|
|
|
29970
31191
|
return !this.el.hidden;
|
|
29971
31192
|
}
|
|
29972
31193
|
/** Open/close the panel — a bare call flips it. */
|
|
29973
|
-
toggle(
|
|
29974
|
-
if (
|
|
29975
|
-
this.el.hidden = !
|
|
29976
|
-
this.onOpenChange?.(
|
|
31194
|
+
toggle(open2 = this.el.hidden) {
|
|
31195
|
+
if (open2 === !this.el.hidden) return;
|
|
31196
|
+
this.el.hidden = !open2;
|
|
31197
|
+
this.onOpenChange?.(open2);
|
|
29977
31198
|
}
|
|
29978
31199
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
29979
31200
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -30047,7 +31268,7 @@ var SidePanel = class {
|
|
|
30047
31268
|
};
|
|
30048
31269
|
|
|
30049
31270
|
// src/widget/object-tree.ts
|
|
30050
|
-
var
|
|
31271
|
+
var STYLE_ID13 = "vela-widget-objtree";
|
|
30051
31272
|
var CSS10 = `
|
|
30052
31273
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
30053
31274
|
.vela-ot-pane {
|
|
@@ -30286,7 +31507,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
30286
31507
|
this.menu = null;
|
|
30287
31508
|
this.menuActions = /* @__PURE__ */ new Map();
|
|
30288
31509
|
this.drag = null;
|
|
30289
|
-
injectStyles(
|
|
31510
|
+
injectStyles(STYLE_ID13, CSS10, host.ownerDocument);
|
|
30290
31511
|
this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
30291
31512
|
this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
30292
31513
|
}
|
|
@@ -30296,9 +31517,9 @@ var ObjectTree = class extends SidePanel {
|
|
|
30296
31517
|
set groups(next) {
|
|
30297
31518
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
30298
31519
|
}
|
|
30299
|
-
toggle(
|
|
30300
|
-
super.toggle(
|
|
30301
|
-
if (
|
|
31520
|
+
toggle(open2 = this.el.hidden) {
|
|
31521
|
+
super.toggle(open2);
|
|
31522
|
+
if (open2) this.refresh();
|
|
30302
31523
|
}
|
|
30303
31524
|
setSymbol(symbol) {
|
|
30304
31525
|
this.symbolName = parseSymbol(symbol).ticker;
|
|
@@ -31268,7 +32489,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
31268
32489
|
};
|
|
31269
32490
|
|
|
31270
32491
|
// src/widget/data-window.ts
|
|
31271
|
-
var
|
|
32492
|
+
var STYLE_ID14 = "vela-widget-datawindow";
|
|
31272
32493
|
var CSS11 = `
|
|
31273
32494
|
.vela-dw-group {
|
|
31274
32495
|
padding: 10px 8px 4px;
|
|
@@ -31328,11 +32549,11 @@ var DataWindow = class extends SidePanel {
|
|
|
31328
32549
|
super(host, "Data window", "vela-dw");
|
|
31329
32550
|
this.chart = null;
|
|
31330
32551
|
this.unsubs = [];
|
|
31331
|
-
injectStyles(
|
|
32552
|
+
injectStyles(STYLE_ID14, CSS11, host.ownerDocument);
|
|
31332
32553
|
}
|
|
31333
|
-
toggle(
|
|
31334
|
-
super.toggle(
|
|
31335
|
-
if (
|
|
32554
|
+
toggle(open2 = this.el.hidden) {
|
|
32555
|
+
super.toggle(open2);
|
|
32556
|
+
if (open2) this.refresh();
|
|
31336
32557
|
}
|
|
31337
32558
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
31338
32559
|
onChart(chart) {
|
|
@@ -31446,8 +32667,8 @@ var PanelDock = class {
|
|
|
31446
32667
|
}
|
|
31447
32668
|
}
|
|
31448
32669
|
/** 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(
|
|
32670
|
+
toggle(id, open2) {
|
|
32671
|
+
this.entries.find((e) => e.id === id)?.panel.toggle(open2);
|
|
31451
32672
|
}
|
|
31452
32673
|
/** The open panel's id, or null when the column is closed. */
|
|
31453
32674
|
get openId() {
|
|
@@ -31461,8 +32682,8 @@ var PanelDock = class {
|
|
|
31461
32682
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
31462
32683
|
getState() {
|
|
31463
32684
|
const out = {};
|
|
31464
|
-
const
|
|
31465
|
-
if (
|
|
32685
|
+
const open2 = this.openId;
|
|
32686
|
+
if (open2) out.open = open2;
|
|
31466
32687
|
if (this.widths.size > 0) out.widths = Object.fromEntries(this.widths);
|
|
31467
32688
|
return out.open || out.widths ? out : null;
|
|
31468
32689
|
}
|
|
@@ -31495,13 +32716,13 @@ var PanelDock = class {
|
|
|
31495
32716
|
this.entries.sort((a, b) => a.order - b.order);
|
|
31496
32717
|
const stored = this.widths.get(entry.id);
|
|
31497
32718
|
if (stored !== void 0) entry.panel.setWidth(stored);
|
|
31498
|
-
entry.panel.onOpenChange = (
|
|
31499
|
-
if (
|
|
32719
|
+
entry.panel.onOpenChange = (open2) => {
|
|
32720
|
+
if (open2) {
|
|
31500
32721
|
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
31501
32722
|
this.pendingOpen = null;
|
|
31502
32723
|
}
|
|
31503
|
-
this.deps.chrome.setPanelActive(entry.id,
|
|
31504
|
-
if (
|
|
32724
|
+
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
32725
|
+
if (open2) entry.handle?.onOpen?.();
|
|
31505
32726
|
this.deps.changed?.();
|
|
31506
32727
|
};
|
|
31507
32728
|
entry.panel.onWidthChange = (px) => {
|
|
@@ -31530,7 +32751,7 @@ var PanelDock = class {
|
|
|
31530
32751
|
};
|
|
31531
32752
|
|
|
31532
32753
|
// src/widget/shortcuts-help.ts
|
|
31533
|
-
var
|
|
32754
|
+
var STYLE_ID15 = "vela-widget-shortcuts";
|
|
31534
32755
|
var CSS12 = `
|
|
31535
32756
|
.vela-sh-cat {
|
|
31536
32757
|
color: var(--vela-fg-muted);
|
|
@@ -31556,16 +32777,16 @@ var ShortcutsHelp = class {
|
|
|
31556
32777
|
constructor(keymap, host, onOpenChange) {
|
|
31557
32778
|
this.keymap = keymap;
|
|
31558
32779
|
const doc = (host ?? document.body).ownerDocument;
|
|
31559
|
-
injectStyles(
|
|
32780
|
+
injectStyles(STYLE_ID15, CSS12, doc);
|
|
31560
32781
|
this.list = doc.createElement("div");
|
|
31561
32782
|
this.dialog = new Dialog({
|
|
31562
32783
|
title: "Keyboard shortcuts",
|
|
31563
32784
|
host,
|
|
31564
32785
|
closeOnInteractOutside: true,
|
|
31565
32786
|
content: (body) => body.appendChild(this.list),
|
|
31566
|
-
onOpenChange: (
|
|
31567
|
-
if (
|
|
31568
|
-
onOpenChange?.(
|
|
32787
|
+
onOpenChange: (open2) => {
|
|
32788
|
+
if (open2) this.refresh();
|
|
32789
|
+
onOpenChange?.(open2);
|
|
31569
32790
|
}
|
|
31570
32791
|
});
|
|
31571
32792
|
}
|
|
@@ -31844,7 +33065,7 @@ var ChartContextMenu = class {
|
|
|
31844
33065
|
};
|
|
31845
33066
|
|
|
31846
33067
|
// src/widget/indicator-picker.ts
|
|
31847
|
-
var
|
|
33068
|
+
var STYLE_ID16 = "vela-widget-indpicker";
|
|
31848
33069
|
var CSS13 = `
|
|
31849
33070
|
.vela-ip-searchrow {
|
|
31850
33071
|
display: flex;
|
|
@@ -31922,7 +33143,7 @@ var IndicatorPicker = class {
|
|
|
31922
33143
|
this.isOpen = false;
|
|
31923
33144
|
this.opts = opts;
|
|
31924
33145
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
31925
|
-
injectStyles(
|
|
33146
|
+
injectStyles(STYLE_ID16, CSS13, doc);
|
|
31926
33147
|
this.search = doc.createElement("input");
|
|
31927
33148
|
this.search.className = "vela-ip-search";
|
|
31928
33149
|
this.search.placeholder = "Search\u2026";
|
|
@@ -31940,14 +33161,14 @@ var IndicatorPicker = class {
|
|
|
31940
33161
|
closeOnInteractOutside: true,
|
|
31941
33162
|
// The picker adds/removes indicators live — keep the chart undimmed behind it.
|
|
31942
33163
|
content: (body) => body.append(searchRow, this.list),
|
|
31943
|
-
onOpenChange: (
|
|
31944
|
-
this.isOpen =
|
|
31945
|
-
if (
|
|
33164
|
+
onOpenChange: (open2) => {
|
|
33165
|
+
this.isOpen = open2;
|
|
33166
|
+
if (open2) {
|
|
31946
33167
|
this.search.value = "";
|
|
31947
33168
|
this.refresh();
|
|
31948
33169
|
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
31949
33170
|
}
|
|
31950
|
-
opts.onOpenChange?.(
|
|
33171
|
+
opts.onOpenChange?.(open2);
|
|
31951
33172
|
}
|
|
31952
33173
|
});
|
|
31953
33174
|
this.list.addEventListener("click", (e) => {
|
|
@@ -32050,7 +33271,7 @@ var IndicatorPicker = class {
|
|
|
32050
33271
|
};
|
|
32051
33272
|
|
|
32052
33273
|
// src/widget/timeframe-quick.ts
|
|
32053
|
-
var
|
|
33274
|
+
var STYLE_ID17 = "vela-widget-tfquick";
|
|
32054
33275
|
var CSS14 = `
|
|
32055
33276
|
.vela-tq-input {
|
|
32056
33277
|
display: block;
|
|
@@ -32074,7 +33295,7 @@ var CSS14 = `
|
|
|
32074
33295
|
var TimeframeQuick = class {
|
|
32075
33296
|
constructor(opts) {
|
|
32076
33297
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
32077
|
-
injectStyles(
|
|
33298
|
+
injectStyles(STYLE_ID17, CSS14, doc);
|
|
32078
33299
|
this.input = doc.createElement("input");
|
|
32079
33300
|
this.input.className = "vela-tq-input";
|
|
32080
33301
|
this.input.setAttribute("spellcheck", "false");
|
|
@@ -32086,7 +33307,7 @@ var TimeframeQuick = class {
|
|
|
32086
33307
|
draggable: true,
|
|
32087
33308
|
closeOnInteractOutside: true,
|
|
32088
33309
|
content: (body) => body.append(this.input, this.hint),
|
|
32089
|
-
onOpenChange: (
|
|
33310
|
+
onOpenChange: (open2) => opts.onOpenChange?.(open2)
|
|
32090
33311
|
});
|
|
32091
33312
|
this.input.addEventListener("input", () => this.renderHint());
|
|
32092
33313
|
this.input.addEventListener("keydown", (e) => {
|
|
@@ -32256,7 +33477,7 @@ var LayoutModeController = class {
|
|
|
32256
33477
|
};
|
|
32257
33478
|
|
|
32258
33479
|
// src/widget/mobile-bar.ts
|
|
32259
|
-
var
|
|
33480
|
+
var STYLE_ID18 = "vela-widget-mobilebar";
|
|
32260
33481
|
var CSS15 = `
|
|
32261
33482
|
.vela-mobilebar {
|
|
32262
33483
|
display: none;
|
|
@@ -32307,7 +33528,7 @@ var MobileBar = class {
|
|
|
32307
33528
|
constructor(host, opts) {
|
|
32308
33529
|
this.opts = opts;
|
|
32309
33530
|
const doc = host.ownerDocument;
|
|
32310
|
-
injectStyles(
|
|
33531
|
+
injectStyles(STYLE_ID18, CSS15, doc);
|
|
32311
33532
|
this.el = doc.createElement("div");
|
|
32312
33533
|
this.el.className = "vela-mobilebar";
|
|
32313
33534
|
const item = (cls, label, onClick, icon2) => {
|
|
@@ -32366,7 +33587,7 @@ var MobileBar = class {
|
|
|
32366
33587
|
};
|
|
32367
33588
|
|
|
32368
33589
|
// src/widget/timeframe-drawer.ts
|
|
32369
|
-
var
|
|
33590
|
+
var STYLE_ID19 = "vela-widget-tf-drawer";
|
|
32370
33591
|
var CSS16 = `
|
|
32371
33592
|
.vela-tfd-heading {
|
|
32372
33593
|
padding: 4px 2px 8px;
|
|
@@ -32423,7 +33644,7 @@ var TimeframeDrawer = class {
|
|
|
32423
33644
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
32424
33645
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
32425
33646
|
const doc = opts.host.ownerDocument;
|
|
32426
|
-
injectStyles(
|
|
33647
|
+
injectStyles(STYLE_ID19, CSS16, doc);
|
|
32427
33648
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
32428
33649
|
const rangeHeading = doc.createElement("div");
|
|
32429
33650
|
rangeHeading.className = "vela-tfd-heading";
|
|
@@ -32481,7 +33702,7 @@ var TimeframeDrawer = class {
|
|
|
32481
33702
|
};
|
|
32482
33703
|
|
|
32483
33704
|
// src/widget/drawings-drawer.ts
|
|
32484
|
-
var
|
|
33705
|
+
var STYLE_ID20 = "vela-widget-drawings-drawer";
|
|
32485
33706
|
var CSS17 = `
|
|
32486
33707
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
32487
33708
|
.vela-dd-sticky {
|
|
@@ -32584,7 +33805,7 @@ var DrawingsDrawer = class {
|
|
|
32584
33805
|
this.definition = { groups: [] };
|
|
32585
33806
|
this.activeGroup = "";
|
|
32586
33807
|
const doc = opts.host.ownerDocument;
|
|
32587
|
-
injectStyles(
|
|
33808
|
+
injectStyles(STYLE_ID20, CSS17, doc);
|
|
32588
33809
|
this.drawer = new Drawer({
|
|
32589
33810
|
host: opts.host,
|
|
32590
33811
|
title: "Drawings",
|
|
@@ -32726,7 +33947,7 @@ var DrawingsDrawer = class {
|
|
|
32726
33947
|
};
|
|
32727
33948
|
|
|
32728
33949
|
// src/widget/more-drawer.ts
|
|
32729
|
-
var
|
|
33950
|
+
var STYLE_ID21 = "vela-widget-more-drawer";
|
|
32730
33951
|
var CSS18 = `
|
|
32731
33952
|
.vela-md-actions {
|
|
32732
33953
|
display: flex;
|
|
@@ -32802,7 +34023,7 @@ var MoreDrawer = class {
|
|
|
32802
34023
|
constructor(opts) {
|
|
32803
34024
|
this.opts = opts;
|
|
32804
34025
|
this.view = "main";
|
|
32805
|
-
injectStyles(
|
|
34026
|
+
injectStyles(STYLE_ID21, CSS18, opts.host.ownerDocument);
|
|
32806
34027
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
32807
34028
|
}
|
|
32808
34029
|
open() {
|
|
@@ -32994,7 +34215,7 @@ var MoreDrawer = class {
|
|
|
32994
34215
|
};
|
|
32995
34216
|
|
|
32996
34217
|
// src/widget/timezone-drawer.ts
|
|
32997
|
-
var
|
|
34218
|
+
var STYLE_ID22 = "vela-widget-timezone-drawer";
|
|
32998
34219
|
var CSS19 = `
|
|
32999
34220
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
33000
34221
|
.vela-tzd-row {
|
|
@@ -33014,7 +34235,7 @@ var CSS19 = `
|
|
|
33014
34235
|
var TimezoneDrawer = class {
|
|
33015
34236
|
constructor(opts) {
|
|
33016
34237
|
this.opts = opts;
|
|
33017
|
-
injectStyles(
|
|
34238
|
+
injectStyles(STYLE_ID22, CSS19, opts.host.ownerDocument);
|
|
33018
34239
|
this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
|
|
33019
34240
|
}
|
|
33020
34241
|
open() {
|
|
@@ -33052,7 +34273,7 @@ var TimezoneDrawer = class {
|
|
|
33052
34273
|
};
|
|
33053
34274
|
|
|
33054
34275
|
// src/widget/price-scale-drawer.ts
|
|
33055
|
-
var
|
|
34276
|
+
var STYLE_ID23 = "vela-widget-price-scale-drawer";
|
|
33056
34277
|
var CSS20 = `
|
|
33057
34278
|
.vela-psd-list { padding: 2px 0 4px; }
|
|
33058
34279
|
.vela-psd-row {
|
|
@@ -33082,7 +34303,7 @@ var PriceScaleDrawer = class {
|
|
|
33082
34303
|
constructor(opts) {
|
|
33083
34304
|
this.opts = opts;
|
|
33084
34305
|
this.pane = null;
|
|
33085
|
-
injectStyles(
|
|
34306
|
+
injectStyles(STYLE_ID23, CSS20, opts.host.ownerDocument);
|
|
33086
34307
|
this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
|
|
33087
34308
|
}
|
|
33088
34309
|
open() {
|
|
@@ -33218,7 +34439,7 @@ var PriceScaleDrawer = class {
|
|
|
33218
34439
|
};
|
|
33219
34440
|
|
|
33220
34441
|
// src/widget/drawing-pill.ts
|
|
33221
|
-
var
|
|
34442
|
+
var STYLE_ID24 = "vela-widget-drawing-pill";
|
|
33222
34443
|
var CSS21 = `
|
|
33223
34444
|
.vela-drawpill { display: none; }
|
|
33224
34445
|
[data-layout='mobile'] .vela-drawpill {
|
|
@@ -33279,7 +34500,7 @@ var DrawingPill = class {
|
|
|
33279
34500
|
this.chart = null;
|
|
33280
34501
|
this.chartSubs = [];
|
|
33281
34502
|
const doc = host.ownerDocument;
|
|
33282
|
-
injectStyles(
|
|
34503
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
33283
34504
|
this.el = doc.createElement("div");
|
|
33284
34505
|
this.el.className = "vela-drawpill";
|
|
33285
34506
|
this.el.hidden = true;
|
|
@@ -33388,6 +34609,10 @@ function sanitizeState(doc) {
|
|
|
33388
34609
|
const favs = d.favorites.filter((f) => typeof f === "string");
|
|
33389
34610
|
if (favs.length > 0) out.favorites = favs;
|
|
33390
34611
|
}
|
|
34612
|
+
if (Array.isArray(d.timeframeFavorites)) {
|
|
34613
|
+
const favs = d.timeframeFavorites.filter((f) => typeof f === "string");
|
|
34614
|
+
if (favs.length > 0) out.timeframeFavorites = favs;
|
|
34615
|
+
}
|
|
33391
34616
|
const sync = sanitizeSync(d.sync);
|
|
33392
34617
|
if (sync) out.sync = sync;
|
|
33393
34618
|
const tracks = sanitizeTrackSizes(d.trackSizes);
|
|
@@ -33661,7 +34886,7 @@ var WidgetHistory = class {
|
|
|
33661
34886
|
};
|
|
33662
34887
|
|
|
33663
34888
|
// src/widget/toast.ts
|
|
33664
|
-
var
|
|
34889
|
+
var STYLE_ID25 = "vela-widget-toast";
|
|
33665
34890
|
var CSS22 = `
|
|
33666
34891
|
.vela-toast {
|
|
33667
34892
|
position: absolute;
|
|
@@ -33692,7 +34917,7 @@ var CSS22 = `
|
|
|
33692
34917
|
var Toast = class {
|
|
33693
34918
|
constructor(host) {
|
|
33694
34919
|
this.timer = null;
|
|
33695
|
-
injectStyles(
|
|
34920
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
33696
34921
|
this.el = host.ownerDocument.createElement("div");
|
|
33697
34922
|
this.el.className = "vela-toast";
|
|
33698
34923
|
host.appendChild(this.el);
|
|
@@ -33828,6 +35053,8 @@ var VelaWidget = class {
|
|
|
33828
35053
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
33829
35054
|
/** Favorite drawing-tool types — mirrored from chart events, reapplied on rebuilds. */
|
|
33830
35055
|
this.favs = [];
|
|
35056
|
+
/** Favorite timeframes — the topbar's quick-switch chips, persisted with the shell. */
|
|
35057
|
+
this.tfFavs = [];
|
|
33831
35058
|
/** Mounted attachment disposers (by id), torn down at destroy. */
|
|
33832
35059
|
this.attachmentDisposers = /* @__PURE__ */ new Map();
|
|
33833
35060
|
this.onUnload = () => this.persistNow();
|
|
@@ -33870,6 +35097,7 @@ var VelaWidget = class {
|
|
|
33870
35097
|
this.indicatorTitlesOn = bootCell?.indicatorTitles ?? true;
|
|
33871
35098
|
this.indicatorValuesOn = bootCell?.indicatorValues ?? true;
|
|
33872
35099
|
this.favs = boot?.favorites ? [...boot.favorites] : [];
|
|
35100
|
+
this.tfFavs = boot?.timeframeFavorites ? [...boot.timeframeFavorites] : [];
|
|
33873
35101
|
this.savedConfig = bootCell?.rendererConfig ?? null;
|
|
33874
35102
|
this.savedDrawings = bootCell?.drawings ?? null;
|
|
33875
35103
|
this.pendingIndicators = bootCell?.indicators ?? null;
|
|
@@ -33883,9 +35111,9 @@ var VelaWidget = class {
|
|
|
33883
35111
|
this.symbolPicker = new SymbolPicker({
|
|
33884
35112
|
host: this.root,
|
|
33885
35113
|
onSelect: (symbol) => this.setSymbol(symbol),
|
|
33886
|
-
onOpenChange: (
|
|
33887
|
-
if (
|
|
33888
|
-
this.trackDialog(
|
|
35114
|
+
onOpenChange: (open2) => {
|
|
35115
|
+
if (open2) this.inner?.renderer.closeDialogs();
|
|
35116
|
+
this.trackDialog(open2);
|
|
33889
35117
|
}
|
|
33890
35118
|
});
|
|
33891
35119
|
this.indicatorPicker = opts.indicatorPicker !== false ? new IndicatorPicker({
|
|
@@ -33908,15 +35136,15 @@ var VelaWidget = class {
|
|
|
33908
35136
|
if (i < present.length) this.removeNative(present[i].type);
|
|
33909
35137
|
else this.removeInstance(i - present.length);
|
|
33910
35138
|
},
|
|
33911
|
-
onOpenChange: (
|
|
33912
|
-
if (
|
|
33913
|
-
this.trackDialog(
|
|
35139
|
+
onOpenChange: (open2) => {
|
|
35140
|
+
if (open2) this.inner?.renderer.closeDialogs();
|
|
35141
|
+
this.trackDialog(open2);
|
|
33914
35142
|
}
|
|
33915
35143
|
}) : null;
|
|
33916
35144
|
this.tfQuick = new TimeframeQuick({
|
|
33917
35145
|
host: this.root,
|
|
33918
35146
|
onApply: (tf) => this.setTimeframe(tf),
|
|
33919
|
-
onOpenChange: (
|
|
35147
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
33920
35148
|
});
|
|
33921
35149
|
const picker = this.indicatorPicker;
|
|
33922
35150
|
this.topbar = new Topbar(this.root, {
|
|
@@ -33929,8 +35157,10 @@ var VelaWidget = class {
|
|
|
33929
35157
|
onAlertsClick: (anchor) => this.openAlertsMenu(anchor),
|
|
33930
35158
|
timeframe: this.timeframe,
|
|
33931
35159
|
timeframes: opts.timeframes ?? DEFAULT_TIMEFRAMES,
|
|
35160
|
+
timeframeFavorites: this.tfFavs,
|
|
33932
35161
|
priceStyle: this.priceStyle,
|
|
33933
35162
|
onTimeframe: (tf) => this.setTimeframe(tf),
|
|
35163
|
+
onTimeframeFavorite: (tf, on) => this.setTimeframeFavorite(tf, on),
|
|
33934
35164
|
onPriceStyle: (style) => this.setPriceStyle(style),
|
|
33935
35165
|
getContext: () => this.context()
|
|
33936
35166
|
});
|
|
@@ -34041,7 +35271,7 @@ var VelaWidget = class {
|
|
|
34041
35271
|
label: "Show this shortcuts panel",
|
|
34042
35272
|
category: "Help",
|
|
34043
35273
|
run: () => {
|
|
34044
|
-
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (
|
|
35274
|
+
this.shortcutsHelp ?? (this.shortcutsHelp = new ShortcutsHelp(this.keymap, this.root, (open2) => this.trackDialog(open2)));
|
|
34045
35275
|
this.shortcutsHelp.open();
|
|
34046
35276
|
}
|
|
34047
35277
|
});
|
|
@@ -34075,7 +35305,7 @@ var VelaWidget = class {
|
|
|
34075
35305
|
activeRange: () => this.activeRangeId,
|
|
34076
35306
|
onTimeframe: (tf) => this.setTimeframe(tf),
|
|
34077
35307
|
onRange: (preset) => this.applyRange(preset),
|
|
34078
|
-
onOpenChange: (
|
|
35308
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34079
35309
|
}));
|
|
34080
35310
|
this.tfDrawer.open();
|
|
34081
35311
|
}
|
|
@@ -34087,7 +35317,7 @@ var VelaWidget = class {
|
|
|
34087
35317
|
isFavorite: (type) => this.inner?.drawings.isFavorite(type) ?? false,
|
|
34088
35318
|
onFavorite: (type, on) => this.inner?.drawings.setFavorite(type, on),
|
|
34089
35319
|
onSelect: (type) => this.inner?.drawings.setTool(type),
|
|
34090
|
-
onOpenChange: (
|
|
35320
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34091
35321
|
}));
|
|
34092
35322
|
this.drawingsDrawer.open();
|
|
34093
35323
|
}
|
|
@@ -34108,7 +35338,7 @@ var VelaWidget = class {
|
|
|
34108
35338
|
// Left-aligned actions have their own bottom-bar stop — only the rest
|
|
34109
35339
|
// lands in the drawer, or every left action would appear twice.
|
|
34110
35340
|
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: (
|
|
35341
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34112
35342
|
}));
|
|
34113
35343
|
this.moreDrawer.open();
|
|
34114
35344
|
}
|
|
@@ -34117,7 +35347,7 @@ var VelaWidget = class {
|
|
|
34117
35347
|
host: this.root,
|
|
34118
35348
|
timezone: () => this.timezone,
|
|
34119
35349
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
34120
|
-
onOpenChange: (
|
|
35350
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34121
35351
|
}));
|
|
34122
35352
|
this.timezoneDrawer.open();
|
|
34123
35353
|
}
|
|
@@ -34127,7 +35357,7 @@ var VelaWidget = class {
|
|
|
34127
35357
|
host: this.root,
|
|
34128
35358
|
chart: () => this.inner,
|
|
34129
35359
|
pressY: () => this.priceScalePressY,
|
|
34130
|
-
onOpenChange: (
|
|
35360
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34131
35361
|
}));
|
|
34132
35362
|
this.priceScaleDrawer.open();
|
|
34133
35363
|
}
|
|
@@ -34156,7 +35386,7 @@ var VelaWidget = class {
|
|
|
34156
35386
|
setTimeframe: (tf) => this.setTimeframe(tf),
|
|
34157
35387
|
setPriceStyle: (style) => this.setPriceStyle(style),
|
|
34158
35388
|
openSymbolSearch: (query) => this.symbolPicker.open(query ?? ""),
|
|
34159
|
-
togglePanel: (id,
|
|
35389
|
+
togglePanel: (id, open2) => this.dock.toggle(id, open2),
|
|
34160
35390
|
host: this.root,
|
|
34161
35391
|
toast: (message, kind) => this.toast?.show(message, kind)
|
|
34162
35392
|
};
|
|
@@ -34215,6 +35445,14 @@ var VelaWidget = class {
|
|
|
34215
35445
|
this.markStateDirty();
|
|
34216
35446
|
void this.inner?.setMarket({ timeframe: tf, bars: this.bars });
|
|
34217
35447
|
}
|
|
35448
|
+
/** Star/unstar a timeframe — the topbar chips and dropdown stars follow, and the
|
|
35449
|
+
* set persists with the rest of the shell state. */
|
|
35450
|
+
setTimeframeFavorite(tf, on) {
|
|
35451
|
+
if (on === this.tfFavs.includes(tf)) return;
|
|
35452
|
+
this.tfFavs = on ? [tf, ...this.tfFavs] : this.tfFavs.filter((f) => f !== tf);
|
|
35453
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
35454
|
+
this.markStateDirty();
|
|
35455
|
+
}
|
|
34218
35456
|
/**
|
|
34219
35457
|
* Switch the chart symbol in place. An `EXCHANGE:` prefix pins the venue (the
|
|
34220
35458
|
* symbol picker composes one from the row the user pointed at) — a bare ticker
|
|
@@ -34272,6 +35510,7 @@ var VelaWidget = class {
|
|
|
34272
35510
|
});
|
|
34273
35511
|
const state = { version: 1, layout: "1", activeCellId: "c1", timezone: this.timezone, charts: [{ id: "c1", ...cell }] };
|
|
34274
35512
|
if (this.favs.length > 0) state.favorites = [...this.favs];
|
|
35513
|
+
if (this.tfFavs.length > 0) state.timeframeFavorites = [...this.tfFavs];
|
|
34275
35514
|
const panels2 = this.dock.getState();
|
|
34276
35515
|
if (panels2) state.panels = panels2;
|
|
34277
35516
|
return state;
|
|
@@ -34295,6 +35534,10 @@ var VelaWidget = class {
|
|
|
34295
35534
|
this.favs = [...st.favorites];
|
|
34296
35535
|
this.inner?.drawings.setFavorites(this.favs);
|
|
34297
35536
|
}
|
|
35537
|
+
if (st.timeframeFavorites) {
|
|
35538
|
+
this.tfFavs = [...st.timeframeFavorites];
|
|
35539
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
35540
|
+
}
|
|
34298
35541
|
this.dock.applyState(st.panels);
|
|
34299
35542
|
if (cell) {
|
|
34300
35543
|
const style = fromUrl.priceStyle ?? cell.priceStyle;
|
|
@@ -34869,9 +36112,9 @@ var VelaWidget = class {
|
|
|
34869
36112
|
this.tfQuick.open(key);
|
|
34870
36113
|
}
|
|
34871
36114
|
}
|
|
34872
|
-
trackDialog(
|
|
34873
|
-
this.openDialogs = Math.max(0, this.openDialogs + (
|
|
34874
|
-
if (
|
|
36115
|
+
trackDialog(open2) {
|
|
36116
|
+
this.openDialogs = Math.max(0, this.openDialogs + (open2 ? 1 : -1));
|
|
36117
|
+
if (open2) this.keymap.pushScope("dialog");
|
|
34875
36118
|
else this.keymap.popScope("dialog");
|
|
34876
36119
|
}
|
|
34877
36120
|
downloadScreenshot() {
|