@luxalgo/vela 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/{DataProvider-DTOEXKTI.d.ts → DataProvider-Cqc_BaJ9.d.ts} +1 -1
- package/dist/{DataProvider-gnzv-dgc.d.cts → DataProvider-DgS2UyMc.d.cts} +1 -1
- package/dist/{chunk-RRFGWZNJ.js → chunk-D6WM5IUE.js} +786 -864
- package/dist/{chunk-LJLZR44Y.js → chunk-EMS6PO2T.js} +5 -7
- package/dist/{chunk-Y7TK4ZWK.js → chunk-JBQUY2RI.js} +126 -19
- package/dist/chunk-OGRQW3M6.js +1328 -0
- package/dist/{chunk-P556H6EA.js → chunk-QWIEKZRE.js} +59 -34
- package/dist/{chunk-B5TIKQJ5.js → chunk-ZNL2X6U2.js} +16 -2
- package/dist/{contributions-Ci_i3IN2.d.cts → contributions-4Nh1jbvb.d.cts} +2 -2
- package/dist/{contributions-b6AVrqqG.d.ts → contributions-DuIxJWeH.d.ts} +2 -2
- package/dist/{history-Cf1lVsap.d.cts → history-FsmvCIo4.d.ts} +5 -3
- package/dist/{history-Ci3M0xLV.d.ts → history-HofqYEh2.d.cts} +5 -3
- package/dist/index.cjs +2783 -1611
- package/dist/index.d.cts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +4 -4
- package/dist/{options-XTBpbx0s.d.ts → options-BlQ00Fju.d.cts} +3 -1
- package/dist/{options-XTBpbx0s.d.cts → options-BlQ00Fju.d.ts} +3 -1
- package/dist/{plugin-DfH4Y-Om.d.cts → plugin-DJR6z3e0.d.cts} +8 -7
- package/dist/{plugin-CnNd7cuF.d.ts → plugin-N_EaXN4-.d.ts} +8 -7
- package/dist/plugin.cjs +4 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/ui.cjs +1318 -4
- package/dist/ui.d.cts +383 -2
- package/dist/ui.d.ts +383 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +2777 -1605
- package/dist/vela.global.min.js +315 -66
- package/dist/widget.cjs +2358 -1052
- package/dist/widget.d.cts +26 -8
- package/dist/widget.d.ts +26 -8
- package/dist/widget.js +27 -9
- package/dist/workspace.cjs +2264 -946
- package/dist/workspace.d.cts +10 -5
- package/dist/workspace.d.ts +10 -5
- package/dist/workspace.js +25 -7
- package/package.json +1 -1
- package/dist/chunk-ATPU5CI6.js +0 -81
package/dist/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"];
|
|
@@ -14014,7 +15487,7 @@ var TOUCH_TAP_SLOP = 14;
|
|
|
14014
15487
|
var DOUBLE_TAP_MS = 350;
|
|
14015
15488
|
var DOUBLE_TAP_SLOP = 30;
|
|
14016
15489
|
var TIME_SCALE_K = 4e-3;
|
|
14017
|
-
var WHEEL_ZOOM_K =
|
|
15490
|
+
var WHEEL_ZOOM_K = 4e-3;
|
|
14018
15491
|
function wheelZoomAnchor(coords, cursorX, rightEdge) {
|
|
14019
15492
|
if (rightEdge) return { logical: coords.rightEdgeLogical, x: coords.width };
|
|
14020
15493
|
return { logical: coords.xToLogical(cursorX), x: cursorX };
|
|
@@ -14068,6 +15541,8 @@ var InputController = class {
|
|
|
14068
15541
|
// smoothed pointer velocity, px/ms
|
|
14069
15542
|
this.middleDeleted = false;
|
|
14070
15543
|
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
15544
|
+
this.rightCancelled = false;
|
|
15545
|
+
// the last right press cancelled a placement (suppress the context menu)
|
|
14071
15546
|
// Last cursor position over the element (NaN once it leaves) — lets a modifier
|
|
14072
15547
|
// press/release re-shape the drawings preview with the pointer stationary.
|
|
14073
15548
|
this.cursorX = NaN;
|
|
@@ -14092,6 +15567,14 @@ var InputController = class {
|
|
|
14092
15567
|
this.onMiddleGuard = (e) => {
|
|
14093
15568
|
if (e.button === 1 && this.middleDeleted) e.preventDefault();
|
|
14094
15569
|
};
|
|
15570
|
+
/** A right press that cancelled a placement must not ALSO open the host's chart
|
|
15571
|
+
* context menu — swallow its companion event before it bubbles to the host. */
|
|
15572
|
+
this.onContextGuard = (e) => {
|
|
15573
|
+
if (!this.rightCancelled) return;
|
|
15574
|
+
this.rightCancelled = false;
|
|
15575
|
+
e.preventDefault();
|
|
15576
|
+
e.stopPropagation();
|
|
15577
|
+
};
|
|
14095
15578
|
/** A modifier press/release with the pointer stationary: re-issue the last cursor
|
|
14096
15579
|
* position so the placing ghost / drag preview reflects the new state immediately
|
|
14097
15580
|
* (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
|
|
@@ -14108,6 +15591,11 @@ var InputController = class {
|
|
|
14108
15591
|
if (this.middleDeleted) e.preventDefault();
|
|
14109
15592
|
return;
|
|
14110
15593
|
}
|
|
15594
|
+
if (e.button === 2) {
|
|
15595
|
+
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
15596
|
+
if (this.rightCancelled) e.preventDefault();
|
|
15597
|
+
return;
|
|
15598
|
+
}
|
|
14111
15599
|
if (e.button !== 0) return;
|
|
14112
15600
|
const { x, y } = this.local(e);
|
|
14113
15601
|
if (e.pointerType === "touch") {
|
|
@@ -14312,6 +15800,7 @@ var InputController = class {
|
|
|
14312
15800
|
el.addEventListener("wheel", this.onWheel, { passive: false });
|
|
14313
15801
|
el.addEventListener("mousedown", this.onMiddleGuard);
|
|
14314
15802
|
el.addEventListener("auxclick", this.onMiddleGuard);
|
|
15803
|
+
el.addEventListener("contextmenu", this.onContextGuard);
|
|
14315
15804
|
const win = el.ownerDocument?.defaultView;
|
|
14316
15805
|
win?.addEventListener("keydown", this.onModifier);
|
|
14317
15806
|
win?.addEventListener("keyup", this.onModifier);
|
|
@@ -14333,6 +15822,7 @@ var InputController = class {
|
|
|
14333
15822
|
el.removeEventListener("wheel", this.onWheel);
|
|
14334
15823
|
el.removeEventListener("mousedown", this.onMiddleGuard);
|
|
14335
15824
|
el.removeEventListener("auxclick", this.onMiddleGuard);
|
|
15825
|
+
el.removeEventListener("contextmenu", this.onContextGuard);
|
|
14336
15826
|
this.cancelLongPress();
|
|
14337
15827
|
this.touches.clear();
|
|
14338
15828
|
this.el = null;
|
|
@@ -15284,6 +16774,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
|
|
|
15284
16774
|
const rx = right ? width + 2 : Math.max(x1, x2);
|
|
15285
16775
|
return [lx, yAt(lx), rx, yAt(rx)];
|
|
15286
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
|
+
}
|
|
15287
16783
|
function parseColor2(c) {
|
|
15288
16784
|
const s = c.trim();
|
|
15289
16785
|
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
@@ -16005,8 +17501,11 @@ var ChromeRenderer = class {
|
|
|
16005
17501
|
}
|
|
16006
17502
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
16007
17503
|
ctx.textBaseline = "middle";
|
|
16008
|
-
if (coords.barCount === 0) return;
|
|
16009
17504
|
const panes = scene.orderedPanes();
|
|
17505
|
+
if (coords.barCount === 0) {
|
|
17506
|
+
this.drawPaneSeparators(ctx, scene, theme, fullW, panes);
|
|
17507
|
+
return;
|
|
17508
|
+
}
|
|
16010
17509
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
16011
17510
|
for (const pane of panes) {
|
|
16012
17511
|
if (pane.collapsed) continue;
|
|
@@ -16535,380 +18034,71 @@ function tzButtonLabel(zone) {
|
|
|
16535
18034
|
return tzOffset(zone);
|
|
16536
18035
|
}
|
|
16537
18036
|
|
|
16538
|
-
// src/core/drawings/effectiveColor.ts
|
|
16539
|
-
var VALID_FILL = VALID;
|
|
16540
|
-
var INVALID_FILL = INVALID;
|
|
16541
|
-
function effectiveFillColor(d, theme) {
|
|
16542
|
-
if (d instanceof PatternDrawing) {
|
|
16543
|
-
if (!d.fillTriangles().length) return null;
|
|
16544
|
-
const valid = d.valid();
|
|
16545
|
-
return d.style.fillColor ?? (valid == null ? d.style.lineColor : valid ? VALID_FILL : INVALID_FILL);
|
|
16546
|
-
}
|
|
16547
|
-
if (d instanceof CalloutBase) return d.style.fillColor ?? theme.background;
|
|
16548
|
-
return d.style.fillColor ?? null;
|
|
16549
|
-
}
|
|
16550
|
-
|
|
16551
|
-
// src/renderers/native/drawings/colorPicker.ts
|
|
16552
|
-
function splitColor(color) {
|
|
16553
|
-
const s = String(color ?? "").trim();
|
|
16554
|
-
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
16555
|
-
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
16556
|
-
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
16557
|
-
if (m) {
|
|
16558
|
-
const h = m[1].toLowerCase();
|
|
16559
|
-
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
16560
|
-
}
|
|
16561
|
-
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
16562
|
-
if (rgba) {
|
|
16563
|
-
const p = rgba[1].split(",").map((v) => v.trim());
|
|
16564
|
-
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
16565
|
-
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
16566
|
-
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
16567
|
-
}
|
|
16568
|
-
return { hex6: DEFAULT_DRAWING_COLOR, alpha: 1 };
|
|
16569
|
-
}
|
|
16570
|
-
function combineColor(hex6, alpha) {
|
|
16571
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
16572
|
-
if (a >= 0.999) return hex6;
|
|
16573
|
-
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
16574
|
-
}
|
|
16575
|
-
function blendOver(fg, bg, alpha) {
|
|
16576
|
-
const rgb = (c) => {
|
|
16577
|
-
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
16578
|
-
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
16579
|
-
};
|
|
16580
|
-
const [fr, fgc, fb] = rgb(fg);
|
|
16581
|
-
const [br, bgc, bb] = rgb(bg);
|
|
16582
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
16583
|
-
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
16584
|
-
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
16585
|
-
}
|
|
16586
|
-
function hslHex(h, s, l) {
|
|
16587
|
-
const sn = s / 100;
|
|
16588
|
-
const ln = l / 100;
|
|
16589
|
-
const k = (n) => (n + h / 30) % 12;
|
|
16590
|
-
const a = sn * Math.min(ln, 1 - ln);
|
|
16591
|
-
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
16592
|
-
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
16593
|
-
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
16594
|
-
}
|
|
16595
|
-
function buildPalette() {
|
|
16596
|
-
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
16597
|
-
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
16598
|
-
const levels = [
|
|
16599
|
-
[82, 56],
|
|
16600
|
-
// vivid
|
|
16601
|
-
[58, 84],
|
|
16602
|
-
// light pastel
|
|
16603
|
-
[62, 74],
|
|
16604
|
-
[66, 62],
|
|
16605
|
-
[70, 50],
|
|
16606
|
-
[64, 39],
|
|
16607
|
-
[54, 29]
|
|
16608
|
-
// dark
|
|
16609
|
-
];
|
|
16610
|
-
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
16611
|
-
}
|
|
16612
|
-
var PALETTE = buildPalette();
|
|
16613
|
-
function transparencyChecker(size) {
|
|
16614
|
-
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
16615
|
-
}
|
|
16616
|
-
var CHECKER = transparencyChecker(10);
|
|
16617
|
-
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
16618
|
-
function addRecent(hex6) {
|
|
16619
|
-
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
16620
|
-
if (i >= 0) recents.splice(i, 1);
|
|
16621
|
-
recents.unshift(hex6);
|
|
16622
|
-
if (recents.length > 10) recents.length = 10;
|
|
16623
|
-
}
|
|
16624
|
-
function buildColorPicker(color, theme, onChange) {
|
|
16625
|
-
const parsed = splitColor(color);
|
|
16626
|
-
let curHex = parsed.hex6;
|
|
16627
|
-
let curAlpha = parsed.alpha;
|
|
16628
|
-
const root = document.createElement("div");
|
|
16629
|
-
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
16630
|
-
const grid = document.createElement("div");
|
|
16631
|
-
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
16632
|
-
const swatches = [];
|
|
16633
|
-
for (const row of PALETTE) {
|
|
16634
|
-
for (const c of row) {
|
|
16635
|
-
const sw = document.createElement("button");
|
|
16636
|
-
sw.type = "button";
|
|
16637
|
-
sw.dataset.c = c;
|
|
16638
|
-
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
16639
|
-
sw.addEventListener("click", (e) => {
|
|
16640
|
-
e.stopPropagation();
|
|
16641
|
-
pickHex(c);
|
|
16642
|
-
});
|
|
16643
|
-
grid.appendChild(sw);
|
|
16644
|
-
swatches.push(sw);
|
|
16645
|
-
}
|
|
16646
|
-
}
|
|
16647
|
-
const paintSelection = () => {
|
|
16648
|
-
for (const sw of swatches) {
|
|
16649
|
-
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
16650
|
-
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
16651
|
-
sw.style.outlineOffset = "2px";
|
|
16652
|
-
sw.style.zIndex = on ? "1" : "";
|
|
16653
|
-
sw.style.position = on ? "relative" : "";
|
|
16654
|
-
}
|
|
16655
|
-
};
|
|
16656
|
-
const recentRow = document.createElement("div");
|
|
16657
|
-
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
16658
|
-
const renderRecents = () => {
|
|
16659
|
-
recentRow.replaceChildren();
|
|
16660
|
-
for (const c of recents) {
|
|
16661
|
-
const sw = document.createElement("button");
|
|
16662
|
-
sw.type = "button";
|
|
16663
|
-
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;`;
|
|
16664
|
-
sw.addEventListener("click", (e) => {
|
|
16665
|
-
e.stopPropagation();
|
|
16666
|
-
pickHex(c);
|
|
16667
|
-
});
|
|
16668
|
-
recentRow.appendChild(sw);
|
|
16669
|
-
}
|
|
16670
|
-
const add = document.createElement("label");
|
|
16671
|
-
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;`;
|
|
16672
|
-
add.textContent = "+";
|
|
16673
|
-
const input = document.createElement("input");
|
|
16674
|
-
input.type = "color";
|
|
16675
|
-
input.value = curHex;
|
|
16676
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
16677
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
16678
|
-
add.appendChild(input);
|
|
16679
|
-
recentRow.appendChild(add);
|
|
16680
|
-
};
|
|
16681
|
-
const opLabel = document.createElement("div");
|
|
16682
|
-
opLabel.textContent = "Opacity";
|
|
16683
|
-
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
16684
|
-
const opRow = document.createElement("div");
|
|
16685
|
-
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
16686
|
-
const track = document.createElement("div");
|
|
16687
|
-
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
16688
|
-
const knob = document.createElement("div");
|
|
16689
|
-
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;";
|
|
16690
|
-
track.appendChild(knob);
|
|
16691
|
-
const pctBox = document.createElement("div");
|
|
16692
|
-
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;`;
|
|
16693
|
-
opRow.append(track, pctBox);
|
|
16694
|
-
const paintOpacity = () => {
|
|
16695
|
-
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
16696
|
-
knob.style.left = `${curAlpha * 100}%`;
|
|
16697
|
-
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
16698
|
-
};
|
|
16699
|
-
let dragging = false;
|
|
16700
|
-
const onDrag = (clientX) => {
|
|
16701
|
-
const r = track.getBoundingClientRect();
|
|
16702
|
-
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
16703
|
-
paintOpacity();
|
|
16704
|
-
emit();
|
|
16705
|
-
};
|
|
16706
|
-
track.addEventListener("pointerdown", (e) => {
|
|
16707
|
-
e.stopPropagation();
|
|
16708
|
-
dragging = true;
|
|
16709
|
-
track.setPointerCapture(e.pointerId);
|
|
16710
|
-
onDrag(e.clientX);
|
|
16711
|
-
});
|
|
16712
|
-
track.addEventListener("pointermove", (e) => {
|
|
16713
|
-
if (dragging) onDrag(e.clientX);
|
|
16714
|
-
});
|
|
16715
|
-
track.addEventListener("pointerup", () => dragging = false);
|
|
16716
|
-
function emit() {
|
|
16717
|
-
onChange(combineColor(curHex, curAlpha));
|
|
16718
|
-
}
|
|
16719
|
-
function pickHex(hex, custom = false) {
|
|
16720
|
-
curHex = splitColor(hex).hex6;
|
|
16721
|
-
addRecent(curHex);
|
|
16722
|
-
paintSelection();
|
|
16723
|
-
paintOpacity();
|
|
16724
|
-
if (custom) renderRecents();
|
|
16725
|
-
emit();
|
|
16726
|
-
}
|
|
16727
|
-
renderRecents();
|
|
16728
|
-
paintSelection();
|
|
16729
|
-
paintOpacity();
|
|
16730
|
-
root.append(grid, recentRow, opLabel, opRow);
|
|
16731
|
-
return root;
|
|
16732
|
-
}
|
|
16733
|
-
|
|
16734
|
-
// src/renderers/native/chrome/ColorField.ts
|
|
16735
|
-
var CHECKER2 = transparencyChecker(8);
|
|
16736
|
-
var STYLE_ID2 = "vela-color-field";
|
|
16737
|
-
function ensureStyles2() {
|
|
16738
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
|
|
16739
|
-
const st = document.createElement("style");
|
|
16740
|
-
st.id = STYLE_ID2;
|
|
16741
|
-
st.textContent = `
|
|
16742
|
-
.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;}
|
|
16743
|
-
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
16744
|
-
.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);}
|
|
16745
|
-
.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;}
|
|
16746
|
-
`;
|
|
16747
|
-
document.head.appendChild(st);
|
|
16748
|
-
}
|
|
16749
|
-
var openPopover = null;
|
|
16750
|
-
function closeOpen() {
|
|
16751
|
-
if (!openPopover) return;
|
|
16752
|
-
document.removeEventListener("pointerdown", openPopover.onOutside, true);
|
|
16753
|
-
window.removeEventListener("resize", openPopover.reflow, true);
|
|
16754
|
-
openPopover.el.remove();
|
|
16755
|
-
openPopover = null;
|
|
16756
|
-
}
|
|
16757
|
-
function place(pop, trigger) {
|
|
16758
|
-
const r = trigger.getBoundingClientRect();
|
|
16759
|
-
const w = pop.offsetWidth;
|
|
16760
|
-
const h = pop.offsetHeight;
|
|
16761
|
-
let left = r.right - w;
|
|
16762
|
-
let top = r.bottom + 6;
|
|
16763
|
-
if (left < 6) left = 6;
|
|
16764
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
16765
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
16766
|
-
if (top < 6) top = 6;
|
|
16767
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
16768
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
16769
|
-
}
|
|
16770
|
-
function colorField(theme, getVal, onVal) {
|
|
16771
|
-
ensureStyles2();
|
|
16772
|
-
const trigger = document.createElement("button");
|
|
16773
|
-
trigger.type = "button";
|
|
16774
|
-
trigger.className = "vela-color-field";
|
|
16775
|
-
const swatch = document.createElement("span");
|
|
16776
|
-
swatch.className = "vela-color-field-swatch";
|
|
16777
|
-
trigger.appendChild(swatch);
|
|
16778
|
-
const paint = () => {
|
|
16779
|
-
const v = getVal();
|
|
16780
|
-
swatch.style.background = `linear-gradient(${v}, ${v}), ${CHECKER2}`;
|
|
16781
|
-
};
|
|
16782
|
-
paint();
|
|
16783
|
-
trigger.addEventListener("vela-sync", paint);
|
|
16784
|
-
trigger.addEventListener("click", (e) => {
|
|
16785
|
-
e.stopPropagation();
|
|
16786
|
-
if (openPopover && openPopover.trigger === trigger) {
|
|
16787
|
-
closeOpen();
|
|
16788
|
-
return;
|
|
16789
|
-
}
|
|
16790
|
-
closeOpen();
|
|
16791
|
-
const pop = document.createElement("div");
|
|
16792
|
-
pop.className = "vela-color-field-pop";
|
|
16793
|
-
applyChromeTokens(pop, theme);
|
|
16794
|
-
pop.addEventListener("pointerdown", (ev) => ev.stopPropagation());
|
|
16795
|
-
pop.appendChild(
|
|
16796
|
-
buildColorPicker(getVal(), theme, (val) => {
|
|
16797
|
-
onVal(val);
|
|
16798
|
-
paint();
|
|
16799
|
-
})
|
|
16800
|
-
);
|
|
16801
|
-
document.body.appendChild(pop);
|
|
16802
|
-
place(pop, trigger);
|
|
16803
|
-
const onOutside = (ev) => {
|
|
16804
|
-
const t = ev.target;
|
|
16805
|
-
if (pop.contains(t) || trigger.contains(t)) return;
|
|
16806
|
-
closeOpen();
|
|
16807
|
-
};
|
|
16808
|
-
const reflow = () => place(pop, trigger);
|
|
16809
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
16810
|
-
window.addEventListener("resize", reflow, true);
|
|
16811
|
-
openPopover = { el: pop, trigger, onOutside, reflow };
|
|
16812
|
-
});
|
|
16813
|
-
return trigger;
|
|
16814
|
-
}
|
|
16815
|
-
function closeColorPopover() {
|
|
16816
|
-
closeOpen();
|
|
16817
|
-
}
|
|
16818
|
-
|
|
16819
18037
|
// src/renderers/native/chrome/WidthField.ts
|
|
16820
18038
|
var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
|
|
16821
|
-
var
|
|
16822
|
-
|
|
16823
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
16824
|
-
const st = document.createElement("style");
|
|
16825
|
-
st.id = STYLE_ID3;
|
|
16826
|
-
st.textContent = `
|
|
18039
|
+
var STYLE_ID2 = "vela-width-field";
|
|
18040
|
+
var WIDTH_CSS = `
|
|
16827
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;}
|
|
16828
18042
|
.vela-width-field:hover{border-color:var(--vela-fg-muted);}
|
|
16829
|
-
.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);}
|
|
16830
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;}
|
|
16831
18046
|
.vela-width-field-item:hover{background:var(--vela-hover-strong);}
|
|
16832
18047
|
.vela-width-field-item[data-active='1']{background:var(--vela-active);}
|
|
16833
18048
|
`;
|
|
16834
|
-
document.head.appendChild(st);
|
|
16835
|
-
}
|
|
16836
18049
|
function lineGlyph(width) {
|
|
16837
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>`;
|
|
16838
18051
|
}
|
|
16839
|
-
var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="
|
|
16840
|
-
var openPopover2 = null;
|
|
16841
|
-
function closeOpen2() {
|
|
16842
|
-
if (!openPopover2) return;
|
|
16843
|
-
document.removeEventListener("pointerdown", openPopover2.onOutside, true);
|
|
16844
|
-
window.removeEventListener("resize", openPopover2.reflow, true);
|
|
16845
|
-
openPopover2.el.remove();
|
|
16846
|
-
openPopover2 = null;
|
|
16847
|
-
}
|
|
16848
|
-
function place2(pop, trigger) {
|
|
16849
|
-
const r = trigger.getBoundingClientRect();
|
|
16850
|
-
const w = pop.offsetWidth;
|
|
16851
|
-
const h = pop.offsetHeight;
|
|
16852
|
-
let left = r.right - w;
|
|
16853
|
-
let top = r.bottom + 6;
|
|
16854
|
-
if (left < 6) left = 6;
|
|
16855
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
16856
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
16857
|
-
if (top < 6) top = 6;
|
|
16858
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
16859
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
16860
|
-
}
|
|
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>`;
|
|
16861
18053
|
function widthField(theme, getVal, onVal) {
|
|
16862
|
-
|
|
18054
|
+
injectStyles(STYLE_ID2, WIDTH_CSS, document);
|
|
16863
18055
|
const trigger = document.createElement("button");
|
|
16864
18056
|
trigger.type = "button";
|
|
16865
18057
|
trigger.className = "vela-width-field";
|
|
16866
18058
|
const paint = () => {
|
|
16867
|
-
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>`;
|
|
16868
18060
|
};
|
|
16869
18061
|
paint();
|
|
18062
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
18063
|
+
e.preventDefault();
|
|
18064
|
+
trigger.focus({ preventScroll: true });
|
|
18065
|
+
});
|
|
16870
18066
|
trigger.addEventListener("click", (e) => {
|
|
16871
18067
|
e.stopPropagation();
|
|
16872
|
-
if (
|
|
16873
|
-
|
|
18068
|
+
if (openPopoverTrigger() === trigger) {
|
|
18069
|
+
closeOpenPopovers();
|
|
16874
18070
|
return;
|
|
16875
18071
|
}
|
|
16876
|
-
|
|
16877
|
-
|
|
16878
|
-
|
|
16879
|
-
|
|
16880
|
-
|
|
16881
|
-
|
|
16882
|
-
|
|
16883
|
-
|
|
16884
|
-
|
|
16885
|
-
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
16890
|
-
|
|
16891
|
-
|
|
16892
|
-
|
|
16893
|
-
|
|
16894
|
-
|
|
16895
|
-
|
|
16896
|
-
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
|
|
16900
|
-
|
|
16901
|
-
closeOpen2();
|
|
16902
|
-
};
|
|
16903
|
-
const reflow = () => place2(pop, trigger);
|
|
16904
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
16905
|
-
window.addEventListener("resize", reflow, true);
|
|
16906
|
-
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();
|
|
16907
18097
|
});
|
|
16908
18098
|
return trigger;
|
|
16909
18099
|
}
|
|
16910
18100
|
function closeWidthPopover() {
|
|
16911
|
-
|
|
18101
|
+
closeOpenPopovers();
|
|
16912
18102
|
}
|
|
16913
18103
|
|
|
16914
18104
|
// src/renderers/native/chrome/SettingsDialog.ts
|
|
@@ -16923,37 +18113,24 @@ function styleLabel(id) {
|
|
|
16923
18113
|
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS[id] ?? id;
|
|
16924
18114
|
}
|
|
16925
18115
|
var SD_STYLE_ID = "vela-settings-controls";
|
|
18116
|
+
var SD_STYLE_REV = "2";
|
|
16926
18117
|
var SETTINGS_SURFACE = "var(--vela-surface)";
|
|
16927
18118
|
var SETTINGS_BORDER = "var(--vela-border)";
|
|
16928
18119
|
var SETTINGS_TEXT = "var(--vela-fg)";
|
|
16929
18120
|
function ensureControlStyles() {
|
|
16930
|
-
if (typeof document === "undefined"
|
|
16931
|
-
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");
|
|
16932
18125
|
st.id = SD_STYLE_ID;
|
|
18126
|
+
st.dataset.rev = SD_STYLE_REV;
|
|
16933
18127
|
st.textContent = `
|
|
16934
|
-
.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;}
|
|
16935
|
-
.vela-sd-check:hover{border-color:var(--vela-fg-muted);}
|
|
16936
|
-
.vela-sd-check.on{background:var(--vela-selected-bg);border-color:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
16937
|
-
.vela-sd-check svg{display:block;}
|
|
16938
|
-
.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;}
|
|
16939
|
-
.vela-sd-select:hover,.vela-sd-number:hover{border-color:var(--vela-fg-muted);}
|
|
16940
|
-
/* Custom caret: the native one hugs the right border; ours gets 8px of air. (A data URI
|
|
16941
|
-
can't read currentColor, so it uses the shared muted-gray ink, legible on both themes.) */
|
|
16942
|
-
.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;}
|
|
16943
|
-
.vela-sd-number{width:64px;}
|
|
16944
|
-
.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;}
|
|
16945
|
-
.vela-sd-color::-webkit-color-swatch-wrapper{padding:2px;}
|
|
16946
|
-
.vela-sd-color::-webkit-color-swatch{border:none;border-radius:2px;}
|
|
16947
18128
|
.vela-sd-pane::-webkit-scrollbar{width:8px;}
|
|
16948
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;}
|
|
16949
18130
|
.vela-sd-pane::-webkit-scrollbar-track{background:transparent;}
|
|
16950
|
-
.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;}
|
|
16951
|
-
.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;}
|
|
16952
|
-
.vela-sd-toggle.on{background:var(--vela-active);border-color:var(--vela-selected-bg);}
|
|
16953
|
-
.vela-sd-toggle.on::after{transform:translateX(16px);background:var(--vela-selected-bg);}
|
|
16954
18131
|
/* Tab rail / footer button / header close: base styles live HERE, not inline on the
|
|
16955
18132
|
elements \u2014 inline declarations always beat stylesheet :hover rules, which is exactly
|
|
16956
|
-
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,
|
|
16957
18134
|
hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
|
|
16958
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;}
|
|
16959
18136
|
.vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
@@ -17012,13 +18189,13 @@ function ensureControlStyles() {
|
|
|
17012
18189
|
.vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
|
|
17013
18190
|
.vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
|
|
17014
18191
|
.vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
|
|
17015
|
-
.vela-sd-mobile .vela-
|
|
17016
|
-
.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;}
|
|
17017
18194
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
17018
18195
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
17019
18196
|
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
|
|
17020
18197
|
`;
|
|
17021
|
-
document.head.appendChild(st);
|
|
18198
|
+
if (!existing) document.head.appendChild(st);
|
|
17022
18199
|
}
|
|
17023
18200
|
var SettingsDialog = class {
|
|
17024
18201
|
constructor(container, theme) {
|
|
@@ -17334,15 +18511,15 @@ var SettingsDialog = class {
|
|
|
17334
18511
|
railScrim = document.createElement("div");
|
|
17335
18512
|
railScrim.className = "vela-sd-railscrim";
|
|
17336
18513
|
railScrim.addEventListener("click", () => toggleRail?.());
|
|
17337
|
-
toggleRail = (
|
|
17338
|
-
const on =
|
|
18514
|
+
toggleRail = (open2) => {
|
|
18515
|
+
const on = open2 ?? !rail.classList.contains("open");
|
|
17339
18516
|
rail.classList.toggle("open", on);
|
|
17340
18517
|
railScrim?.classList.toggle("open", on);
|
|
17341
18518
|
};
|
|
17342
18519
|
}
|
|
17343
18520
|
const paneHost = document.createElement("div");
|
|
17344
18521
|
paneHost.className = "vela-sd-pane";
|
|
17345
|
-
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;";
|
|
17346
18523
|
const panes = [];
|
|
17347
18524
|
let current = null;
|
|
17348
18525
|
for (const child of [...body.children]) {
|
|
@@ -17408,7 +18585,7 @@ var SettingsDialog = class {
|
|
|
17408
18585
|
}
|
|
17409
18586
|
}
|
|
17410
18587
|
close() {
|
|
17411
|
-
|
|
18588
|
+
closeOpenPopovers();
|
|
17412
18589
|
closeWidthPopover();
|
|
17413
18590
|
this.root?.remove();
|
|
17414
18591
|
this.root = null;
|
|
@@ -17563,50 +18740,41 @@ var SettingsDialog = class {
|
|
|
17563
18740
|
return wf;
|
|
17564
18741
|
}
|
|
17565
18742
|
if (c.kind === "select") {
|
|
17566
|
-
const sel = document.createElement("select");
|
|
17567
|
-
sel.className = "vela-sd-select";
|
|
17568
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
17569
|
-
sel.title = c.label;
|
|
17570
18743
|
const current2 = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
17571
|
-
|
|
17572
|
-
|
|
17573
|
-
|
|
17574
|
-
|
|
17575
|
-
|
|
17576
|
-
|
|
17577
|
-
}
|
|
17578
|
-
sel.addEventListener("change", () => put(c.key, sel.value));
|
|
17579
|
-
sel.addEventListener("vela-sync", () => {
|
|
17580
|
-
sel.value = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
17581
|
-
});
|
|
17582
|
-
return sel;
|
|
17583
|
-
}
|
|
17584
|
-
const ni = document.createElement("input");
|
|
17585
|
-
ni.type = "number";
|
|
17586
|
-
ni.className = "vela-sd-number";
|
|
17587
|
-
if (compact) ni.style.width = "56px";
|
|
17588
|
-
if (c.min !== void 0) ni.min = String(c.min);
|
|
17589
|
-
if (c.max !== void 0) ni.max = String(c.max);
|
|
17590
|
-
ni.step = String(c.step ?? 1);
|
|
17591
|
-
ni.title = c.label;
|
|
17592
|
-
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
17593
|
-
if (c.placeholder !== void 0) {
|
|
17594
|
-
ni.placeholder = c.placeholder;
|
|
17595
|
-
if (current !== c.defval) ni.value = String(current);
|
|
17596
|
-
ni.addEventListener("change", () => {
|
|
17597
|
-
const raw = ni.value.trim() === "" ? c.defval : Number(ni.value);
|
|
17598
|
-
const v = Number.isFinite(raw) ? Math.min(c.max ?? Infinity, Math.max(c.min ?? -Infinity, raw)) : c.defval;
|
|
17599
|
-
ni.value = v === c.defval ? "" : String(v);
|
|
17600
|
-
put(c.key, v);
|
|
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
|
-
const v = Number(ni.value);
|
|
17606
|
-
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);
|
|
17607
18754
|
});
|
|
18755
|
+
return sel.el;
|
|
17608
18756
|
}
|
|
17609
|
-
|
|
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;
|
|
17610
18778
|
}
|
|
17611
18779
|
/**
|
|
17612
18780
|
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
@@ -17887,28 +19055,27 @@ var SettingsDialog = class {
|
|
|
17887
19055
|
* only one visible at a time; see `typeRow`). */
|
|
17888
19056
|
toggleRow(label, value, onToggle, controls, get) {
|
|
17889
19057
|
const wrap = document.createElement("div");
|
|
17890
|
-
const
|
|
17891
|
-
|
|
17892
|
-
|
|
17893
|
-
|
|
17894
|
-
let checked = value;
|
|
17895
|
-
const cbToggle = () => {
|
|
17896
|
-
checked = !checked;
|
|
17897
|
-
cb.classList.toggle("on", checked);
|
|
17898
|
-
return checked;
|
|
17899
|
-
};
|
|
19058
|
+
const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
|
|
19059
|
+
onToggle(v);
|
|
19060
|
+
if (controls.length > 0) syncDim(v);
|
|
19061
|
+
} });
|
|
17900
19062
|
const lbl = document.createElement("span");
|
|
17901
19063
|
lbl.textContent = label;
|
|
17902
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
|
+
};
|
|
17903
19072
|
if (controls.length === 0) {
|
|
17904
19073
|
wrap.className = "vela-sd-bool";
|
|
17905
19074
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
|
|
17906
|
-
wrap.append(
|
|
17907
|
-
cb.addEventListener("click", () => onToggle(cbToggle()));
|
|
19075
|
+
wrap.append(sw.el, lbl);
|
|
17908
19076
|
if (get) {
|
|
17909
19077
|
wrap.addEventListener("vela-sync", () => {
|
|
17910
|
-
|
|
17911
|
-
cb.classList.toggle("on", checked);
|
|
19078
|
+
sw.setChecked(get());
|
|
17912
19079
|
});
|
|
17913
19080
|
}
|
|
17914
19081
|
return wrap;
|
|
@@ -17916,28 +19083,16 @@ var SettingsDialog = class {
|
|
|
17916
19083
|
wrap.className = "vela-sd-row";
|
|
17917
19084
|
const left = document.createElement("div");
|
|
17918
19085
|
left.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
17919
|
-
left.append(
|
|
19086
|
+
left.append(sw.el, lbl);
|
|
17920
19087
|
const box = document.createElement("div");
|
|
17921
19088
|
box.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
17922
19089
|
for (const c of controls) box.appendChild(c);
|
|
17923
|
-
const syncDim = (on) => {
|
|
17924
|
-
for (const c of controls) {
|
|
17925
|
-
if (c.dataset.sdSelfGated === "1") continue;
|
|
17926
|
-
c.style.opacity = on ? "1" : "0.4";
|
|
17927
|
-
c.style.pointerEvents = on ? "" : "none";
|
|
17928
|
-
}
|
|
17929
|
-
};
|
|
17930
19090
|
syncDim(value);
|
|
17931
|
-
cb.addEventListener("click", () => {
|
|
17932
|
-
const v = cbToggle();
|
|
17933
|
-
onToggle(v);
|
|
17934
|
-
syncDim(v);
|
|
17935
|
-
});
|
|
17936
19091
|
if (get) {
|
|
17937
19092
|
wrap.addEventListener("vela-sync", () => {
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
syncDim(
|
|
19093
|
+
const v = get();
|
|
19094
|
+
sw.setChecked(v);
|
|
19095
|
+
syncDim(v);
|
|
17941
19096
|
});
|
|
17942
19097
|
}
|
|
17943
19098
|
wrap.append(left, box);
|
|
@@ -17945,52 +19100,23 @@ var SettingsDialog = class {
|
|
|
17945
19100
|
}
|
|
17946
19101
|
numberRow(label, value, min, max, step, onChange) {
|
|
17947
19102
|
const { wrap } = this.row(label);
|
|
17948
|
-
|
|
17949
|
-
ni.type = "number";
|
|
17950
|
-
ni.value = String(value);
|
|
17951
|
-
ni.min = String(min);
|
|
17952
|
-
ni.max = String(max);
|
|
17953
|
-
ni.step = String(step);
|
|
17954
|
-
ni.className = "vela-sd-number";
|
|
17955
|
-
ni.addEventListener("input", () => {
|
|
17956
|
-
const n = Number(ni.value);
|
|
17957
|
-
if (Number.isFinite(n)) onChange(n);
|
|
17958
|
-
});
|
|
17959
|
-
wrap.appendChild(ni);
|
|
19103
|
+
wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
|
|
17960
19104
|
return wrap;
|
|
17961
19105
|
}
|
|
17962
19106
|
/** A dropdown whose option values differ from their display labels. */
|
|
17963
19107
|
selectRowLabeled(label, value, options, onChange) {
|
|
17964
19108
|
const { wrap } = this.row(label);
|
|
17965
|
-
|
|
17966
|
-
|
|
17967
|
-
|
|
17968
|
-
|
|
17969
|
-
|
|
17970
|
-
|
|
17971
|
-
|
|
17972
|
-
if (val === value) o.selected = true;
|
|
17973
|
-
sel.appendChild(o);
|
|
17974
|
-
}
|
|
17975
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
17976
|
-
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);
|
|
17977
19116
|
return wrap;
|
|
17978
19117
|
}
|
|
17979
19118
|
selectRow(label, value, options, onChange) {
|
|
17980
|
-
|
|
17981
|
-
const sel = document.createElement("select");
|
|
17982
|
-
sel.className = "vela-sd-select";
|
|
17983
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
17984
|
-
for (const opt of options) {
|
|
17985
|
-
const o = document.createElement("option");
|
|
17986
|
-
o.value = opt;
|
|
17987
|
-
o.textContent = opt;
|
|
17988
|
-
if (opt === value) o.selected = true;
|
|
17989
|
-
sel.appendChild(o);
|
|
17990
|
-
}
|
|
17991
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
17992
|
-
wrap.appendChild(sel);
|
|
17993
|
-
return wrap;
|
|
19119
|
+
return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
|
|
17994
19120
|
}
|
|
17995
19121
|
/** Footer with the full config as JSON — the export/import (templating) surface. */
|
|
17996
19122
|
footer(_config) {
|
|
@@ -18015,6 +19141,19 @@ function timezoneOptions(current) {
|
|
|
18015
19141
|
return [[normalized, tzMenuLabel(normalized, normalized)], ...options];
|
|
18016
19142
|
}
|
|
18017
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
|
+
|
|
18018
19157
|
// src/renderers/native/drawings/DrawingPainter.ts
|
|
18019
19158
|
var HANDLE_RADIUS = 4.5;
|
|
18020
19159
|
var HANDLE_BORDER = DEFAULT_DRAWING_COLOR;
|
|
@@ -18381,13 +19520,19 @@ var DrawingPainter = class {
|
|
|
18381
19520
|
}
|
|
18382
19521
|
this.paintLabel(ctx, d, proj, theme);
|
|
18383
19522
|
}
|
|
18384
|
-
/** 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. */
|
|
18385
19525
|
paintLabel(ctx, d, proj, theme) {
|
|
18386
19526
|
const text = d.text;
|
|
18387
19527
|
if (!text || !text.value || d.id === this.targets.mutedLabel) return;
|
|
18388
19528
|
const layout = labelLayout(d, proj);
|
|
18389
19529
|
if (!layout) return;
|
|
18390
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
|
+
}
|
|
18391
19536
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
18392
19537
|
ctx.textBaseline = "top";
|
|
18393
19538
|
ctx.textAlign = layout.align;
|
|
@@ -18395,8 +19540,8 @@ var DrawingPainter = class {
|
|
|
18395
19540
|
const lh = labelLineHeight(fs);
|
|
18396
19541
|
const lines = text.value.split("\n");
|
|
18397
19542
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
18398
|
-
ctx.textAlign = "left";
|
|
18399
19543
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
19544
|
+
ctx.restore();
|
|
18400
19545
|
}
|
|
18401
19546
|
/** Frame a plain text label while it is the target, so the text reads as a clickable object even
|
|
18402
19547
|
* though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
|
|
@@ -19339,7 +20484,14 @@ function labelLayout(d, proj) {
|
|
|
19339
20484
|
case "infoline":
|
|
19340
20485
|
case "trendangle": {
|
|
19341
20486
|
if (pts.length < 2) return null;
|
|
19342
|
-
|
|
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
|
+
};
|
|
19343
20495
|
}
|
|
19344
20496
|
case "box": {
|
|
19345
20497
|
if (pts.length < 2) return null;
|
|
@@ -19688,11 +20840,11 @@ function applyFree(anchor, pt, free) {
|
|
|
19688
20840
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
19689
20841
|
var BTN = 30;
|
|
19690
20842
|
var ICON = 21;
|
|
19691
|
-
var
|
|
19692
|
-
function
|
|
19693
|
-
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;
|
|
19694
20846
|
const s = document.createElement("style");
|
|
19695
|
-
s.id =
|
|
20847
|
+
s.id = STYLE_ID3;
|
|
19696
20848
|
s.textContent = `
|
|
19697
20849
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
19698
20850
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -19726,17 +20878,14 @@ var DrawingSettingsPopup = class {
|
|
|
19726
20878
|
this.textPanel = null;
|
|
19727
20879
|
this.levelsPanel = null;
|
|
19728
20880
|
this.colorPop = null;
|
|
19729
|
-
// floating RGB + opacity picker for a color swatch
|
|
19730
20881
|
this.colorOwner = null;
|
|
19731
|
-
|
|
19732
|
-
this.menuEl = null;
|
|
19733
|
-
// floating option list for a dropdown control
|
|
20882
|
+
this.menuPop = null;
|
|
19734
20883
|
this.menuOwner = null;
|
|
19735
20884
|
this.onClose = null;
|
|
19736
20885
|
this.onOutside = (e) => {
|
|
19737
20886
|
const target = e.target;
|
|
19738
|
-
if (this.colorPop?.contains(target)) return;
|
|
19739
|
-
if (this.
|
|
20887
|
+
if (this.colorPop?.el.contains(target)) return;
|
|
20888
|
+
if (this.menuPop?.el.contains(target)) return;
|
|
19740
20889
|
if (this.el && !this.el.contains(target)) {
|
|
19741
20890
|
const cb = this.onClose;
|
|
19742
20891
|
this.close();
|
|
@@ -19755,13 +20904,13 @@ var DrawingSettingsPopup = class {
|
|
|
19755
20904
|
* picker / dropdown menu), which live outside `el`. */
|
|
19756
20905
|
contains(node) {
|
|
19757
20906
|
if (!node) return false;
|
|
19758
|
-
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;
|
|
19759
20908
|
}
|
|
19760
20909
|
/** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
|
|
19761
20910
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
19762
20911
|
open(drawing, anchor, actions, onClose) {
|
|
19763
20912
|
this.close();
|
|
19764
|
-
|
|
20913
|
+
ensureStyles2();
|
|
19765
20914
|
this.onClose = onClose ?? null;
|
|
19766
20915
|
const t = this.theme;
|
|
19767
20916
|
const schema = drawing.schema();
|
|
@@ -19772,8 +20921,7 @@ var DrawingSettingsPopup = class {
|
|
|
19772
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;`;
|
|
19773
20922
|
applyChromeTokens(el, t);
|
|
19774
20923
|
el.addEventListener("pointerdown", () => {
|
|
19775
|
-
|
|
19776
|
-
this.closeMenu();
|
|
20924
|
+
closeOpenPopovers();
|
|
19777
20925
|
});
|
|
19778
20926
|
const bar = document.createElement("div");
|
|
19779
20927
|
bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
|
|
@@ -20510,49 +21658,76 @@ var DrawingSettingsPopup = class {
|
|
|
20510
21658
|
});
|
|
20511
21659
|
return b;
|
|
20512
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
|
+
}
|
|
20513
21703
|
/** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
|
|
20514
21704
|
openActionMenu(anchor, rows) {
|
|
20515
|
-
this.
|
|
20516
|
-
|
|
20517
|
-
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
|
|
20527
|
-
|
|
20528
|
-
|
|
20529
|
-
|
|
20530
|
-
|
|
20531
|
-
|
|
20532
|
-
|
|
20533
|
-
|
|
20534
|
-
|
|
20535
|
-
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
}
|
|
20540
|
-
|
|
20541
|
-
}
|
|
20542
|
-
this.host.appendChild(menu2);
|
|
20543
|
-
const ar = anchor.getBoundingClientRect();
|
|
20544
|
-
const hr = this.host.getBoundingClientRect();
|
|
20545
|
-
const w = menu2.offsetWidth;
|
|
20546
|
-
const h = menu2.offsetHeight;
|
|
20547
|
-
let left = ar.right - hr.left - w;
|
|
20548
|
-
let top = ar.bottom - hr.top + 6;
|
|
20549
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
20550
|
-
if (left < 4) left = 4;
|
|
20551
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
20552
|
-
if (top < 4) top = 4;
|
|
20553
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
20554
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
20555
|
-
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
|
+
});
|
|
20556
21731
|
this.menuOwner = anchor;
|
|
20557
21732
|
}
|
|
20558
21733
|
/** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
|
|
@@ -20682,60 +21857,40 @@ var DrawingSettingsPopup = class {
|
|
|
20682
21857
|
/** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
|
|
20683
21858
|
* glyph (and text label when one is supplied); the current value is highlighted. */
|
|
20684
21859
|
openMenu(anchor, values, current, render, label, onPick) {
|
|
20685
|
-
this.
|
|
20686
|
-
|
|
20687
|
-
|
|
20688
|
-
|
|
20689
|
-
|
|
20690
|
-
|
|
20691
|
-
|
|
20692
|
-
|
|
20693
|
-
|
|
20694
|
-
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
|
|
20698
|
-
|
|
20699
|
-
|
|
20700
|
-
|
|
20701
|
-
|
|
20702
|
-
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
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
|
+
}
|
|
20709
21888
|
}
|
|
20710
|
-
|
|
20711
|
-
e.stopPropagation();
|
|
20712
|
-
this.closeMenu();
|
|
20713
|
-
onPick(v);
|
|
20714
|
-
});
|
|
20715
|
-
menu2.appendChild(item);
|
|
20716
|
-
}
|
|
20717
|
-
this.host.appendChild(menu2);
|
|
20718
|
-
const ar = anchor.getBoundingClientRect();
|
|
20719
|
-
const hr = this.host.getBoundingClientRect();
|
|
20720
|
-
const w = menu2.offsetWidth;
|
|
20721
|
-
const h = menu2.offsetHeight;
|
|
20722
|
-
let left = ar.left - hr.left;
|
|
20723
|
-
let top = ar.bottom - hr.top + 6;
|
|
20724
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
20725
|
-
if (left < 4) left = 4;
|
|
20726
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
20727
|
-
if (top < 4) top = 4;
|
|
20728
|
-
menu2.style.left = `${Math.round(left)}px`;
|
|
20729
|
-
menu2.style.top = `${Math.round(top)}px`;
|
|
20730
|
-
this.menuEl = menu2;
|
|
21889
|
+
});
|
|
20731
21890
|
this.menuOwner = anchor;
|
|
20732
21891
|
}
|
|
20733
21892
|
closeMenu() {
|
|
20734
|
-
|
|
20735
|
-
this.menuEl.remove();
|
|
20736
|
-
this.menuEl = null;
|
|
20737
|
-
}
|
|
20738
|
-
this.menuOwner = null;
|
|
21893
|
+
this.menuPop?.hide();
|
|
20739
21894
|
}
|
|
20740
21895
|
/** A color control: an `icon` over a thin **colored underline** showing the current
|
|
20741
21896
|
* value (the common idiom), with an invisible native picker overlaid to edit it.
|
|
@@ -20774,36 +21929,18 @@ var DrawingSettingsPopup = class {
|
|
|
20774
21929
|
}
|
|
20775
21930
|
/** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
|
|
20776
21931
|
openColorPopover(anchor, color, onChange) {
|
|
20777
|
-
this.closeColorPopover();
|
|
20778
|
-
this.closeMenu();
|
|
20779
21932
|
const t = this.theme;
|
|
20780
|
-
|
|
20781
|
-
|
|
20782
|
-
|
|
20783
|
-
|
|
20784
|
-
|
|
20785
|
-
|
|
20786
|
-
|
|
20787
|
-
const hr = this.host.getBoundingClientRect();
|
|
20788
|
-
const w = pop.offsetWidth;
|
|
20789
|
-
const h = pop.offsetHeight;
|
|
20790
|
-
let left = ar.left - hr.left;
|
|
20791
|
-
let top = ar.bottom - hr.top + 6;
|
|
20792
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
20793
|
-
if (left < 4) left = 4;
|
|
20794
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
20795
|
-
if (top < 4) top = 4;
|
|
20796
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
20797
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
20798
|
-
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
|
+
});
|
|
20799
21940
|
this.colorOwner = anchor;
|
|
20800
21941
|
}
|
|
20801
21942
|
closeColorPopover() {
|
|
20802
|
-
|
|
20803
|
-
this.colorPop.remove();
|
|
20804
|
-
this.colorPop = null;
|
|
20805
|
-
}
|
|
20806
|
-
this.colorOwner = null;
|
|
21943
|
+
this.colorPop?.hide();
|
|
20807
21944
|
}
|
|
20808
21945
|
divider() {
|
|
20809
21946
|
const d = document.createElement("div");
|
|
@@ -20942,7 +22079,7 @@ var DrawingToolbar = class {
|
|
|
20942
22079
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
20943
22080
|
});
|
|
20944
22081
|
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
20945
|
-
|
|
22082
|
+
ensureStyles3();
|
|
20946
22083
|
this.root = document.createElement("div");
|
|
20947
22084
|
this.root.className = "vela-dtb";
|
|
20948
22085
|
this.styleRoot();
|
|
@@ -21425,13 +22562,13 @@ var DrawingToolbar = class {
|
|
|
21425
22562
|
}
|
|
21426
22563
|
};
|
|
21427
22564
|
var MAGNET_ID = "__magnet__";
|
|
21428
|
-
var
|
|
21429
|
-
function
|
|
22565
|
+
var STYLE_ID4 = "vela-drawing-toolbar-styles";
|
|
22566
|
+
function ensureStyles3() {
|
|
21430
22567
|
if (typeof document === "undefined") return;
|
|
21431
|
-
let s = document.getElementById(
|
|
22568
|
+
let s = document.getElementById(STYLE_ID4);
|
|
21432
22569
|
if (!s) {
|
|
21433
22570
|
s = document.createElement("style");
|
|
21434
|
-
s.id =
|
|
22571
|
+
s.id = STYLE_ID4;
|
|
21435
22572
|
document.head.appendChild(s);
|
|
21436
22573
|
}
|
|
21437
22574
|
s.textContent = `
|
|
@@ -21810,9 +22947,9 @@ var UserDrawingController = class {
|
|
|
21810
22947
|
}
|
|
21811
22948
|
/**
|
|
21812
22949
|
* Union of the visible drawings' price ranges on `paneId` whose time extent
|
|
21813
|
-
* intersects [fromTime, toTime]
|
|
21814
|
-
*
|
|
21815
|
-
*
|
|
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.
|
|
21816
22953
|
*/
|
|
21817
22954
|
priceRangeForPane(paneId, fromTime, toTime) {
|
|
21818
22955
|
let lo = Infinity;
|
|
@@ -22168,6 +23305,16 @@ var UserDrawingController = class {
|
|
|
22168
23305
|
if (this.eraserMode) return "pointer";
|
|
22169
23306
|
return this.interaction.cursorAt(x, y);
|
|
22170
23307
|
}
|
|
23308
|
+
/** Right-click while placing: cancel the in-progress drawing and revert to the
|
|
23309
|
+
* pointer — the gesture is an explicit escape, so it disarms even in
|
|
23310
|
+
* stay-in-drawing-mode (where Escape would leave the tool armed). Returns whether
|
|
23311
|
+
* the press was consumed; false lets the host's context menu open normally. */
|
|
23312
|
+
cancelPlacement() {
|
|
23313
|
+
if (!this.interaction.isPlacing()) return false;
|
|
23314
|
+
this.interaction.cancel();
|
|
23315
|
+
if (this.activeTool != null) this.emit({ kind: "arm", type: null });
|
|
23316
|
+
return true;
|
|
23317
|
+
}
|
|
22171
23318
|
/** Double-click over a drawing → suppress the chart's view reset (single-click already
|
|
22172
23319
|
* opens settings). Returns true only when a drawing is under the cursor. */
|
|
22173
23320
|
dblClick(x, y) {
|
|
@@ -22652,6 +23799,20 @@ var VolumeRenderer = class {
|
|
|
22652
23799
|
};
|
|
22653
23800
|
|
|
22654
23801
|
// src/renderers/native/layers.ts
|
|
23802
|
+
function foldBaseModulation(acc, next) {
|
|
23803
|
+
if (next == null) return acc;
|
|
23804
|
+
if (acc == null) return { ...next };
|
|
23805
|
+
return {
|
|
23806
|
+
candleBodyScale: minOpt(acc.candleBodyScale, next.candleBodyScale),
|
|
23807
|
+
candleBodyAlpha: minOpt(acc.candleBodyAlpha, next.candleBodyAlpha),
|
|
23808
|
+
gridAlpha: minOpt(acc.gridAlpha, next.gridAlpha)
|
|
23809
|
+
};
|
|
23810
|
+
}
|
|
23811
|
+
function minOpt(a, b) {
|
|
23812
|
+
if (a == null) return b;
|
|
23813
|
+
if (b == null) return a;
|
|
23814
|
+
return Math.min(a, b);
|
|
23815
|
+
}
|
|
22655
23816
|
var registry3 = /* @__PURE__ */ new Map();
|
|
22656
23817
|
function rendererLayers() {
|
|
22657
23818
|
return [...registry3.values()];
|
|
@@ -22663,7 +23824,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
|
|
|
22663
23824
|
|
|
22664
23825
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
22665
23826
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
22666
|
-
var
|
|
23827
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
22667
23828
|
var CSS2 = `
|
|
22668
23829
|
.vela-attribution {
|
|
22669
23830
|
text-decoration: none;
|
|
@@ -22726,11 +23887,11 @@ var CSS2 = `
|
|
|
22726
23887
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
22727
23888
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
22728
23889
|
`;
|
|
22729
|
-
function
|
|
22730
|
-
let s = doc.getElementById(
|
|
23890
|
+
function ensureStyles4(doc) {
|
|
23891
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
22731
23892
|
if (!s) {
|
|
22732
23893
|
s = doc.createElement("style");
|
|
22733
|
-
s.id =
|
|
23894
|
+
s.id = STYLE_ID5;
|
|
22734
23895
|
doc.head.appendChild(s);
|
|
22735
23896
|
}
|
|
22736
23897
|
s.textContent = CSS2;
|
|
@@ -22759,7 +23920,7 @@ function createCustomMark(doc, html, background) {
|
|
|
22759
23920
|
return el;
|
|
22760
23921
|
}
|
|
22761
23922
|
function createAttributionMark(doc, background) {
|
|
22762
|
-
|
|
23923
|
+
ensureStyles4(doc);
|
|
22763
23924
|
const a = doc.createElement("a");
|
|
22764
23925
|
a.className = "vela-attribution";
|
|
22765
23926
|
a.href = ATTRIBUTION_URL;
|
|
@@ -23289,7 +24450,7 @@ var NativeRenderer = class {
|
|
|
23289
24450
|
this.scrollTargetRO = null;
|
|
23290
24451
|
// eased rightOffset target while gliding back to the latest bars
|
|
23291
24452
|
// Distance (px) from the plot's bottom edge to the button — tracks the bottom-most EXPANDED
|
|
23292
|
-
// pane
|
|
24453
|
+
// pane: when the lower sub-panes collapse it rides up into the open pane.
|
|
23293
24454
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM;
|
|
23294
24455
|
// Distance (px) from the plot's right edge — clears the FULL scale gutter, which widens when a
|
|
23295
24456
|
// pane carries merged (own-scale) columns; the constant only clears a single-column scale.
|
|
@@ -24256,6 +25417,7 @@ var NativeRenderer = class {
|
|
|
24256
25417
|
drawingsClaim: (x, y) => this.userDrawings?.claim(x, y) ?? false,
|
|
24257
25418
|
drawingsMeasureStart: (x, y) => this.userDrawings?.beginMeasureAt(x, y) ?? false,
|
|
24258
25419
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
25420
|
+
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
24259
25421
|
drawingsSnapMode: () => this.snapMode,
|
|
24260
25422
|
drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
|
|
24261
25423
|
drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
|
|
@@ -24507,6 +25669,8 @@ var NativeRenderer = class {
|
|
|
24507
25669
|
this.attributionEl = null;
|
|
24508
25670
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
24509
25671
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
25672
|
+
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
25673
|
+
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
24510
25674
|
this.mountContainer = null;
|
|
24511
25675
|
this.wrapper?.remove();
|
|
24512
25676
|
}
|
|
@@ -25596,19 +26760,18 @@ var NativeRenderer = class {
|
|
|
25596
26760
|
theme: this.theme
|
|
25597
26761
|
});
|
|
25598
26762
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
26763
|
+
let folded = null;
|
|
25599
26764
|
for (const l of this.extLayers) {
|
|
25600
26765
|
const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
|
|
25601
26766
|
l.instance.render(args);
|
|
25602
|
-
|
|
25603
|
-
const mod = l.instance.modulateBase(args);
|
|
25604
|
-
if (mod) {
|
|
25605
|
-
if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
|
|
25606
|
-
if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
|
|
25607
|
-
if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
|
|
25608
|
-
}
|
|
25609
|
-
}
|
|
26767
|
+
folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
25610
26768
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
25611
26769
|
}
|
|
26770
|
+
if (folded) {
|
|
26771
|
+
if (folded.candleBodyScale != null) this.backend.candleBodyScale = clamp012(folded.candleBodyScale) || 0.01;
|
|
26772
|
+
if (folded.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(folded.candleBodyAlpha) * this.candleBodyAlpha;
|
|
26773
|
+
if (folded.gridAlpha != null) this.backend.gridAlpha = clamp012(folded.gridAlpha);
|
|
26774
|
+
}
|
|
25612
26775
|
}
|
|
25613
26776
|
const li = this.bars.length - 1;
|
|
25614
26777
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
@@ -25735,7 +26898,6 @@ var NativeRenderer = class {
|
|
|
25735
26898
|
const n = this.coords.barCount;
|
|
25736
26899
|
if (n === 0) return;
|
|
25737
26900
|
const vr = this.coords.visibleLogicalRange();
|
|
25738
|
-
const vtr = this.coords.visibleTimeRange();
|
|
25739
26901
|
const i0 = Math.max(0, Math.floor(vr.from));
|
|
25740
26902
|
const i1 = Math.min(n - 1, Math.ceil(vr.to));
|
|
25741
26903
|
if (i1 < i0) return;
|
|
@@ -25753,9 +26915,7 @@ var NativeRenderer = class {
|
|
|
25753
26915
|
}
|
|
25754
26916
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
25755
26917
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
25756
|
-
|
|
25757
|
-
const udr = this.userDrawings?.priceRangeForPane(pane.id, vtr.from, vtr.to) ?? null;
|
|
25758
|
-
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);
|
|
25759
26919
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
25760
26920
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
25761
26921
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -26000,6 +27160,7 @@ var NativeRenderer = class {
|
|
|
26000
27160
|
this.paneControls?.reposition();
|
|
26001
27161
|
this.repositionScrollButton();
|
|
26002
27162
|
this.positionAttribution();
|
|
27163
|
+
this.publishPricePaneBounds();
|
|
26003
27164
|
return;
|
|
26004
27165
|
}
|
|
26005
27166
|
const collapsed = panes.filter((p) => p.collapsed);
|
|
@@ -26017,9 +27178,10 @@ var NativeRenderer = class {
|
|
|
26017
27178
|
this.paneControls?.reposition();
|
|
26018
27179
|
this.repositionScrollButton();
|
|
26019
27180
|
this.positionAttribution();
|
|
27181
|
+
this.publishPricePaneBounds();
|
|
26020
27182
|
}
|
|
26021
|
-
/** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area
|
|
26022
|
-
*
|
|
27183
|
+
/** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area:
|
|
27184
|
+
* with all lower sub-panes collapsed it settles into the lowest open pane. */
|
|
26023
27185
|
repositionScrollButton() {
|
|
26024
27186
|
const dataHeight = this.coords.height;
|
|
26025
27187
|
if (dataHeight <= 0) return;
|
|
@@ -26063,6 +27225,21 @@ var NativeRenderer = class {
|
|
|
26063
27225
|
this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
|
|
26064
27226
|
this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
|
|
26065
27227
|
}
|
|
27228
|
+
/** Publish the price pane's vertical insets as `--vela-price-pane-top` /
|
|
27229
|
+
* `--vela-price-pane-bottom` on the mount container, so the symbol watermark
|
|
27230
|
+
* (and any other host overlay) can clip to the price pane instead of spanning
|
|
27231
|
+
* study panes and the time axis. A maximized study pane collapses the box to
|
|
27232
|
+
* zero height — the mark does not appear on a study. */
|
|
27233
|
+
publishPricePaneBounds() {
|
|
27234
|
+
if (!this.mountContainer) return;
|
|
27235
|
+
const plotH = this.coords.height + TIME_AXIS_H;
|
|
27236
|
+
const price = this.scene.panes.get(PRICE_PANE_ID);
|
|
27237
|
+
const top = price ? price.bounds.top : 0;
|
|
27238
|
+
const height = price ? price.bounds.height : this.coords.height;
|
|
27239
|
+
const bottom = Math.max(0, plotH - (top + height));
|
|
27240
|
+
this.mountContainer.style.setProperty("--vela-price-pane-top", `${top}px`);
|
|
27241
|
+
this.mountContainer.style.setProperty("--vela-price-pane-bottom", `${bottom}px`);
|
|
27242
|
+
}
|
|
26066
27243
|
/** The built-in mark, or the host's own when one is set. */
|
|
26067
27244
|
buildAttributionEl() {
|
|
26068
27245
|
return this.attributionHtml !== null ? createCustomMark(document, this.attributionHtml, this.theme.background) : createAttributionMark(document, this.theme.background);
|
|
@@ -26735,55 +27912,6 @@ function resolveElement(container) {
|
|
|
26735
27912
|
if (!element) throw new Error(`[vela] container not found for selector "${container}"`);
|
|
26736
27913
|
return element;
|
|
26737
27914
|
}
|
|
26738
|
-
|
|
26739
|
-
// src/ui/styles.ts
|
|
26740
|
-
function injectStyles(id, css, root = document) {
|
|
26741
|
-
if (typeof document === "undefined") return;
|
|
26742
|
-
const host = root instanceof Document ? root.head : root;
|
|
26743
|
-
if (root.getElementById?.(id) ?? host.querySelector(`#${CSS.escape(id)}`)) return;
|
|
26744
|
-
const s = document.createElement("style");
|
|
26745
|
-
s.id = id;
|
|
26746
|
-
s.textContent = css;
|
|
26747
|
-
host.appendChild(s);
|
|
26748
|
-
}
|
|
26749
|
-
|
|
26750
|
-
// src/ui/tokens.ts
|
|
26751
|
-
var STATIC_ID = "vela-ui-tokens";
|
|
26752
|
-
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
26753
|
-
var STATIC_CSS = `
|
|
26754
|
-
.vela-ui, .vela-ui-layer {
|
|
26755
|
-
${STATIC_DECLS}
|
|
26756
|
-
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
26757
|
-
box-sizing: border-box;
|
|
26758
|
-
}
|
|
26759
|
-
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
26760
|
-
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
26761
|
-
.vela-icon svg { display: block; }
|
|
26762
|
-
`;
|
|
26763
|
-
function applyThemeTokens(el, t) {
|
|
26764
|
-
const tokens = themeTokens(t);
|
|
26765
|
-
for (const key in tokens) el.style.setProperty(key, tokens[key]);
|
|
26766
|
-
}
|
|
26767
|
-
function applyPlotOverlayTokens(host, base, config) {
|
|
26768
|
-
const layout = config?.layout;
|
|
26769
|
-
const t = layout?.background && layout.textColor ? { ...base, background: layout.background, textColor: layout.textColor } : base;
|
|
26770
|
-
applyThemeTokens(host, t);
|
|
26771
|
-
}
|
|
26772
|
-
function ensureUIHost(el, theme) {
|
|
26773
|
-
injectStyles(STATIC_ID, STATIC_CSS, el.getRootNode());
|
|
26774
|
-
el.classList.add("vela-ui");
|
|
26775
|
-
if (theme) applyThemeTokens(el, theme);
|
|
26776
|
-
}
|
|
26777
|
-
|
|
26778
|
-
// src/ui/icons.ts
|
|
26779
|
-
function iconEl(id, doc = document) {
|
|
26780
|
-
const span = doc.createElement("span");
|
|
26781
|
-
span.className = "vela-icon";
|
|
26782
|
-
span.setAttribute("aria-hidden", "true");
|
|
26783
|
-
const svg = iconMarkup(id);
|
|
26784
|
-
if (svg) span.innerHTML = svg;
|
|
26785
|
-
return span;
|
|
26786
|
-
}
|
|
26787
27915
|
function runMachine(machine5, props, render) {
|
|
26788
27916
|
const m = new vanilla.VanillaMachine(machine5, props);
|
|
26789
27917
|
const unsub = m.subscribe(render);
|
|
@@ -27025,7 +28153,10 @@ function menuController(opts) {
|
|
|
27025
28153
|
props: {
|
|
27026
28154
|
id: opts.id ?? nextUid("vela-menu"),
|
|
27027
28155
|
ids: opts.triggerId ? { trigger: opts.triggerId } : void 0,
|
|
27028
|
-
positioning: {
|
|
28156
|
+
positioning: {
|
|
28157
|
+
placement: opts.placement ?? "bottom-start",
|
|
28158
|
+
...opts.getAnchorRect ? { getAnchorRect: () => opts.getAnchorRect() } : {}
|
|
28159
|
+
},
|
|
27029
28160
|
onSelect: (d) => opts.onSelect?.(d.value),
|
|
27030
28161
|
onOpenChange: (d) => opts.onOpenChange?.(d.open)
|
|
27031
28162
|
},
|
|
@@ -27107,6 +28238,22 @@ var MENU_CSS = `
|
|
|
27107
28238
|
}
|
|
27108
28239
|
.vela-menu-switch.on { background: var(--vela-accent-bright, var(--vela-accent)); border-color: var(--vela-accent-bright, var(--vela-accent)); }
|
|
27109
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); }
|
|
27110
28257
|
.vela-menu-sep { height: 1px; margin: 4px 6px; background: var(--vela-border); }
|
|
27111
28258
|
/* Submenu trigger row: a right-aligned chevron, and it stays highlighted while its own
|
|
27112
28259
|
list is open so the path you came down remains readable. */
|
|
@@ -27125,9 +28272,13 @@ var Surface = class _Surface {
|
|
|
27125
28272
|
this.items = [];
|
|
27126
28273
|
/** Branch item id → the surface it opens. */
|
|
27127
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;
|
|
27128
28278
|
this.doc = doc;
|
|
27129
28279
|
this.host = opts.host;
|
|
27130
28280
|
this.onSelect = opts.onSelect;
|
|
28281
|
+
this.onFavorite = opts.onFavorite;
|
|
27131
28282
|
this.positioner = doc.createElement("div");
|
|
27132
28283
|
this.positioner.className = "vela-ui-layer";
|
|
27133
28284
|
this.list = doc.createElement("ul");
|
|
@@ -27135,16 +28286,25 @@ var Surface = class _Surface {
|
|
|
27135
28286
|
if (opts.minWidth) this.list.style.minWidth = opts.minWidth;
|
|
27136
28287
|
this.positioner.appendChild(this.list);
|
|
27137
28288
|
this.host.appendChild(this.positioner);
|
|
28289
|
+
const trigger = opts.trigger;
|
|
27138
28290
|
this.ctrl = menuController({
|
|
27139
28291
|
items: [],
|
|
27140
28292
|
id: opts.id,
|
|
27141
28293
|
placement: opts.placement,
|
|
27142
28294
|
onSelect: (id) => this.onSelect(id),
|
|
27143
|
-
|
|
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
|
+
},
|
|
27144
28305
|
triggerId: opts.triggerId
|
|
27145
28306
|
});
|
|
27146
28307
|
this.mid = String(this.ctrl.props.id);
|
|
27147
|
-
const trigger = opts.trigger;
|
|
27148
28308
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
27149
28309
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
27150
28310
|
const api = this.ctrl.connect(service);
|
|
@@ -27224,6 +28384,25 @@ var Surface = class _Surface {
|
|
|
27224
28384
|
hint.textContent = item.hint;
|
|
27225
28385
|
li.appendChild(hint);
|
|
27226
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
|
+
}
|
|
27227
28406
|
if (item.toggle) {
|
|
27228
28407
|
const sw = doc.createElement("span");
|
|
27229
28408
|
sw.className = "vela-menu-switch" + (item.checked ? " on" : "");
|
|
@@ -27239,6 +28418,7 @@ var Surface = class _Surface {
|
|
|
27239
28418
|
host: this.host,
|
|
27240
28419
|
placement: "right-start",
|
|
27241
28420
|
onSelect: this.onSelect,
|
|
28421
|
+
onFavorite: this.onFavorite,
|
|
27242
28422
|
id: `${this.mid}--${item.id}`
|
|
27243
28423
|
});
|
|
27244
28424
|
sub.setItems(item.submenu ?? []);
|
|
@@ -27262,7 +28442,8 @@ var Menu = class {
|
|
|
27262
28442
|
trigger: opts.trigger,
|
|
27263
28443
|
triggerId: opts.triggerId,
|
|
27264
28444
|
id: opts.id,
|
|
27265
|
-
minWidth: opts.minWidth
|
|
28445
|
+
minWidth: opts.minWidth,
|
|
28446
|
+
onFavorite: opts.onFavorite
|
|
27266
28447
|
});
|
|
27267
28448
|
this.root.setItems(opts.items);
|
|
27268
28449
|
}
|
|
@@ -27766,7 +28947,7 @@ var Drawer = class {
|
|
|
27766
28947
|
};
|
|
27767
28948
|
|
|
27768
28949
|
// src/widget/layout-picker.ts
|
|
27769
|
-
var
|
|
28950
|
+
var STYLE_ID6 = "vela-widget-layout-picker-v14";
|
|
27770
28951
|
var CSS3 = `
|
|
27771
28952
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
27772
28953
|
.vela-lp {
|
|
@@ -27871,7 +29052,7 @@ var CSS3 = `
|
|
|
27871
29052
|
`;
|
|
27872
29053
|
var GRID = 4;
|
|
27873
29054
|
function layoutGridCanvas(doc) {
|
|
27874
|
-
injectStyles(
|
|
29055
|
+
injectStyles(STYLE_ID6, CSS3, doc);
|
|
27875
29056
|
const el = doc.createElement("div");
|
|
27876
29057
|
el.className = "vela-lp-grid";
|
|
27877
29058
|
const squares = [];
|
|
@@ -27911,7 +29092,7 @@ var LayoutPicker = class {
|
|
|
27911
29092
|
};
|
|
27912
29093
|
this.opts = opts;
|
|
27913
29094
|
this.doc = opts.host.ownerDocument;
|
|
27914
|
-
injectStyles(
|
|
29095
|
+
injectStyles(STYLE_ID6, CSS3, this.doc);
|
|
27915
29096
|
const doc = this.doc;
|
|
27916
29097
|
this.layer = doc.createElement("div");
|
|
27917
29098
|
this.layer.className = "vela-ui-layer vela-lp-layer";
|
|
@@ -28224,6 +29405,24 @@ function timeframeMs(value) {
|
|
|
28224
29405
|
if (!Number.isFinite(count) || count < 1) return NaN;
|
|
28225
29406
|
return count * UNIT_MS[key];
|
|
28226
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
|
+
}
|
|
28227
29426
|
function timeframeLabel(value) {
|
|
28228
29427
|
const parsed = parseTimeframe(value);
|
|
28229
29428
|
if (!parsed.valid) return value;
|
|
@@ -28238,7 +29437,7 @@ function timeframeLabel(value) {
|
|
|
28238
29437
|
}
|
|
28239
29438
|
|
|
28240
29439
|
// src/widget/topbar.ts
|
|
28241
|
-
var
|
|
29440
|
+
var STYLE_ID7 = "vela-topbar";
|
|
28242
29441
|
var CSS4 = `
|
|
28243
29442
|
.vela-widget-topbar {
|
|
28244
29443
|
display: flex;
|
|
@@ -28276,6 +29475,36 @@ var CSS4 = `
|
|
|
28276
29475
|
color: var(--vela-fg-bright);
|
|
28277
29476
|
}
|
|
28278
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; }
|
|
28279
29508
|
.vela-widget-topbar .vela-icon { color: inherit; font-size: 16px; width: 16px; height: 16px; }
|
|
28280
29509
|
/* Width is set in syncHairlines() to exactly one device pixel \u2014 a CSS 1px at
|
|
28281
29510
|
fractional DPR (1.25, 1.5\u2026) straddles two physical pixels and siblings end
|
|
@@ -28373,16 +29602,23 @@ var Topbar = class {
|
|
|
28373
29602
|
this.timeframe = opts.timeframe;
|
|
28374
29603
|
this.priceStyle = opts.priceStyle;
|
|
28375
29604
|
const doc = host.ownerDocument;
|
|
28376
|
-
injectStyles(
|
|
29605
|
+
injectStyles(STYLE_ID7, CSS4, doc);
|
|
28377
29606
|
this.el = doc.createElement("div");
|
|
28378
29607
|
this.el.className = "vela-widget-topbar";
|
|
28379
29608
|
this.symbolEl = doc.createElement("button");
|
|
28380
29609
|
this.symbolEl.className = "vela-widget-symbol";
|
|
28381
29610
|
this.symbolEl.textContent = parseSymbol(opts.symbol).ticker;
|
|
28382
29611
|
if (opts.onSymbolClick) this.symbolEl.addEventListener("click", opts.onSymbolClick);
|
|
28383
|
-
this.
|
|
28384
|
-
this.
|
|
28385
|
-
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);
|
|
28386
29622
|
this.styleButton = doc.createElement("button");
|
|
28387
29623
|
this.styleButton.className = "vela-widget-style";
|
|
28388
29624
|
this.renderStyleButton(doc);
|
|
@@ -28422,19 +29658,20 @@ var Topbar = class {
|
|
|
28422
29658
|
d.className = "vela-sep";
|
|
28423
29659
|
return d;
|
|
28424
29660
|
};
|
|
28425
|
-
const leading = [this.symbolEl, sep(),
|
|
29661
|
+
const leading = [this.symbolEl, sep(), tfGroup, sep(), this.styleButton, sep()];
|
|
28426
29662
|
if (this.layoutButton) leading.push(this.layoutButton, sep());
|
|
28427
29663
|
if (indicatorsBtn) leading.push(indicatorsBtn, sep());
|
|
28428
29664
|
this.leftActionsSep = sep();
|
|
28429
29665
|
this.leftActionsSep.hidden = true;
|
|
28430
29666
|
this.el.append(...leading, this.leftActionsHost, this.leftActionsSep, this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
|
|
28431
29667
|
host.appendChild(this.el);
|
|
29668
|
+
this.renderTfChips();
|
|
28432
29669
|
this.onHairlineSync();
|
|
28433
29670
|
this.hairlineRo = new ResizeObserver(this.onHairlineSync);
|
|
28434
29671
|
this.hairlineRo.observe(this.el);
|
|
28435
29672
|
doc.defaultView?.addEventListener("resize", this.onHairlineSync);
|
|
28436
29673
|
this.renderActions();
|
|
28437
|
-
this.tooltips.push(new Tooltip(this.
|
|
29674
|
+
this.tooltips.push(new Tooltip(this.tfCaret, { content: "Timeframe", triggerId: "vela-topbar-tf", host }));
|
|
28438
29675
|
this.tooltips.push(new Tooltip(this.styleButton, { content: "Chart style", triggerId: "vela-topbar-style", host }));
|
|
28439
29676
|
if (this.layoutButton && opts.layout) {
|
|
28440
29677
|
this.tooltips.push(new Tooltip(this.layoutButton, { content: "Layout", triggerId: "vela-topbar-layout", host }));
|
|
@@ -28451,11 +29688,12 @@ var Topbar = class {
|
|
|
28451
29688
|
});
|
|
28452
29689
|
}
|
|
28453
29690
|
this.tfMenu = new Menu({
|
|
28454
|
-
trigger: this.
|
|
29691
|
+
trigger: this.tfCaret,
|
|
28455
29692
|
triggerId: "vela-topbar-tf",
|
|
28456
29693
|
host,
|
|
28457
29694
|
items: this.tfItems(),
|
|
28458
29695
|
onSelect: (id) => opts.onTimeframe(id),
|
|
29696
|
+
onFavorite: (id, on) => opts.onTimeframeFavorite?.(id, on),
|
|
28459
29697
|
// Timeframe labels are two-or-three characters ("1m", "4h", "1D") — the
|
|
28460
29698
|
// stylesheet's default min-width would leave the list mostly empty.
|
|
28461
29699
|
minWidth: "84px"
|
|
@@ -28473,9 +29711,49 @@ var Topbar = class {
|
|
|
28473
29711
|
}
|
|
28474
29712
|
setTimeframe(tf) {
|
|
28475
29713
|
this.timeframe = tf;
|
|
28476
|
-
this.
|
|
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();
|
|
28477
29721
|
this.tfMenu.setItems(this.tfItems());
|
|
28478
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();
|
|
29756
|
+
}
|
|
28479
29757
|
setPriceStyle(style) {
|
|
28480
29758
|
this.priceStyle = style;
|
|
28481
29759
|
this.renderStyleButton(this.styleButton.ownerDocument);
|
|
@@ -28552,9 +29830,9 @@ var Topbar = class {
|
|
|
28552
29830
|
}
|
|
28553
29831
|
/** Reflect a docked side panel's open state on its button — the panels toggle each other,
|
|
28554
29832
|
* so the dock pushes the state rather than the button assuming it. */
|
|
28555
|
-
setPanelActive(id,
|
|
29833
|
+
setPanelActive(id, open2) {
|
|
28556
29834
|
const btn = this.panelBtns.get(id);
|
|
28557
|
-
if (btn) btn.dataset.active =
|
|
29835
|
+
if (btn) btn.dataset.active = open2 ? "1" : "";
|
|
28558
29836
|
}
|
|
28559
29837
|
destroy() {
|
|
28560
29838
|
this.hairlineRo?.disconnect();
|
|
@@ -28592,7 +29870,13 @@ var Topbar = class {
|
|
|
28592
29870
|
}
|
|
28593
29871
|
}
|
|
28594
29872
|
tfItems() {
|
|
28595
|
-
|
|
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
|
+
}));
|
|
28596
29880
|
}
|
|
28597
29881
|
styleItems() {
|
|
28598
29882
|
return priceStyleIds().map((id, i) => ({
|
|
@@ -28615,10 +29899,10 @@ function fmtPrice(n, dp) {
|
|
|
28615
29899
|
const d = dp ?? decimalsFor(n);
|
|
28616
29900
|
return n.toLocaleString("en-US", { minimumFractionDigits: d, maximumFractionDigits: d });
|
|
28617
29901
|
}
|
|
28618
|
-
function fmtChange(
|
|
28619
|
-
if (
|
|
28620
|
-
const diff = close -
|
|
28621
|
-
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;
|
|
28622
29906
|
const sign = diff >= 0 ? "+" : "";
|
|
28623
29907
|
return `${sign}${fmtPrice(diff, decimalsFor(close))} (${sign}${pct.toFixed(2)}%)`;
|
|
28624
29908
|
}
|
|
@@ -28667,7 +29951,7 @@ function tickerIconEl(doc, base, name, className) {
|
|
|
28667
29951
|
}
|
|
28668
29952
|
|
|
28669
29953
|
// src/widget/statusline.ts
|
|
28670
|
-
var
|
|
29954
|
+
var STYLE_ID8 = "vela-widget-statusline";
|
|
28671
29955
|
var CSS5 = `
|
|
28672
29956
|
.vela-statusline {
|
|
28673
29957
|
position: absolute;
|
|
@@ -28859,7 +30143,7 @@ var Statusline = class {
|
|
|
28859
30143
|
this.fitMode = false;
|
|
28860
30144
|
this.fitRO = null;
|
|
28861
30145
|
const doc = host.ownerDocument;
|
|
28862
|
-
injectStyles(
|
|
30146
|
+
injectStyles(STYLE_ID8, CSS5, doc);
|
|
28863
30147
|
host.classList.add("vela-has-statusline");
|
|
28864
30148
|
this.el = doc.createElement("div");
|
|
28865
30149
|
this.el.className = "vela-statusline";
|
|
@@ -29034,21 +30318,21 @@ var Statusline = class {
|
|
|
29034
30318
|
var MAX_FONT_PX = 36;
|
|
29035
30319
|
var MIN_FONT_PX = 12;
|
|
29036
30320
|
var FILL = 0.9;
|
|
29037
|
-
var
|
|
30321
|
+
var STYLE_ID9 = "vela-widget-watermark";
|
|
29038
30322
|
var CSS6 = `
|
|
29039
30323
|
.vela-watermark {
|
|
29040
30324
|
position: absolute;
|
|
29041
|
-
/* Insets follow the renderer-published gutters
|
|
29042
|
-
*
|
|
29043
|
-
*
|
|
29044
|
-
|
|
29045
|
-
|
|
29046
|
-
bottom: 0;
|
|
30325
|
+
/* Insets follow the renderer-published gutters AND the price-pane vertical
|
|
30326
|
+
* bounds (mount container), so the mark centers on the PRICE PANE \u2014 never
|
|
30327
|
+
* the study panes below, the drawings toolbar, or the price scale. */
|
|
30328
|
+
top: var(--vela-price-pane-top, 0px);
|
|
30329
|
+
bottom: var(--vela-price-pane-bottom, 0px);
|
|
29047
30330
|
left: var(--vela-toolbar-gutter, 0px);
|
|
29048
30331
|
right: var(--vela-scale-gutter, 0px);
|
|
29049
30332
|
display: flex;
|
|
29050
30333
|
align-items: center;
|
|
29051
30334
|
justify-content: center;
|
|
30335
|
+
overflow: hidden;
|
|
29052
30336
|
pointer-events: none;
|
|
29053
30337
|
z-index: 1;
|
|
29054
30338
|
color: var(--vela-fg);
|
|
@@ -29073,7 +30357,7 @@ var Watermark = class {
|
|
|
29073
30357
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
29074
30358
|
* fires during chart construction, before any subscriber can see it. */
|
|
29075
30359
|
this.loading = true;
|
|
29076
|
-
injectStyles(
|
|
30360
|
+
injectStyles(STYLE_ID9, CSS6, host.ownerDocument);
|
|
29077
30361
|
this.el = host.ownerDocument.createElement("div");
|
|
29078
30362
|
this.el.className = "vela-watermark";
|
|
29079
30363
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -29103,7 +30387,7 @@ var Watermark = class {
|
|
|
29103
30387
|
this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
|
|
29104
30388
|
this.fit();
|
|
29105
30389
|
}
|
|
29106
|
-
/** Measure the text at the cap and shrink it to the
|
|
30390
|
+
/** Measure the text at the cap and shrink it to the price pane's width. */
|
|
29107
30391
|
fit() {
|
|
29108
30392
|
if (this.el.clientWidth <= 0 || !this.text.textContent) return;
|
|
29109
30393
|
this.el.style.fontSize = `${MAX_FONT_PX}px`;
|
|
@@ -29137,7 +30421,7 @@ var RANGE_PRESETS = [
|
|
|
29137
30421
|
{ id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
|
|
29138
30422
|
// everything the provider serves
|
|
29139
30423
|
];
|
|
29140
|
-
var
|
|
30424
|
+
var STYLE_ID10 = "vela-widget-bottombar";
|
|
29141
30425
|
var CSS7 = `
|
|
29142
30426
|
.vela-widget-bottombar {
|
|
29143
30427
|
display: flex;
|
|
@@ -29213,7 +30497,7 @@ var Bottombar = class {
|
|
|
29213
30497
|
this.timer = null;
|
|
29214
30498
|
this.timezone = opts.timezone;
|
|
29215
30499
|
const doc = host.ownerDocument;
|
|
29216
|
-
injectStyles(
|
|
30500
|
+
injectStyles(STYLE_ID10, CSS7, doc);
|
|
29217
30501
|
this.el = doc.createElement("div");
|
|
29218
30502
|
this.el.className = "vela-widget-bottombar";
|
|
29219
30503
|
for (const preset of RANGE_PRESETS) {
|
|
@@ -29351,7 +30635,7 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
29351
30635
|
}
|
|
29352
30636
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
29353
30637
|
}
|
|
29354
|
-
var
|
|
30638
|
+
var STYLE_ID11 = "vela-widget-symbolpicker";
|
|
29355
30639
|
var CSS8 = `
|
|
29356
30640
|
.vela-sp-searchrow {
|
|
29357
30641
|
display: flex;
|
|
@@ -29449,7 +30733,7 @@ var SymbolPicker = class {
|
|
|
29449
30733
|
this.seed = "";
|
|
29450
30734
|
this.activeTab = "All";
|
|
29451
30735
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
29452
|
-
injectStyles(
|
|
30736
|
+
injectStyles(STYLE_ID11, CSS8, doc);
|
|
29453
30737
|
this.input = doc.createElement("input");
|
|
29454
30738
|
this.input.className = "vela-sp-input";
|
|
29455
30739
|
this.input.placeholder = "Search symbol\u2026";
|
|
@@ -29479,8 +30763,8 @@ var SymbolPicker = class {
|
|
|
29479
30763
|
host: opts.host,
|
|
29480
30764
|
closeOnInteractOutside: true,
|
|
29481
30765
|
content: (body) => body.append(searchRow, this.tabs, this.list),
|
|
29482
|
-
onOpenChange: (
|
|
29483
|
-
if (
|
|
30766
|
+
onOpenChange: (open2) => {
|
|
30767
|
+
if (open2) {
|
|
29484
30768
|
this.input.value = this.seed;
|
|
29485
30769
|
this.seed = "";
|
|
29486
30770
|
this.refresh();
|
|
@@ -29489,7 +30773,7 @@ var SymbolPicker = class {
|
|
|
29489
30773
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length);
|
|
29490
30774
|
}, 0);
|
|
29491
30775
|
}
|
|
29492
|
-
opts.onOpenChange?.(
|
|
30776
|
+
opts.onOpenChange?.(open2);
|
|
29493
30777
|
}
|
|
29494
30778
|
});
|
|
29495
30779
|
this.input.addEventListener("input", () => this.refresh());
|
|
@@ -29771,7 +31055,7 @@ function treeIsEmpty(panes) {
|
|
|
29771
31055
|
}
|
|
29772
31056
|
|
|
29773
31057
|
// src/widget/side-panel.ts
|
|
29774
|
-
var
|
|
31058
|
+
var STYLE_ID12 = "vela-widget-sidepanel";
|
|
29775
31059
|
var DEFAULT_PANEL_WIDTH = 280;
|
|
29776
31060
|
var DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
29777
31061
|
var DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
@@ -29875,7 +31159,7 @@ var SidePanel = class {
|
|
|
29875
31159
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
29876
31160
|
this.onWidthChange = null;
|
|
29877
31161
|
const doc = host.ownerDocument;
|
|
29878
|
-
injectStyles(
|
|
31162
|
+
injectStyles(STYLE_ID12, CSS9, doc);
|
|
29879
31163
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
29880
31164
|
this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
|
|
29881
31165
|
this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
|
|
@@ -29907,10 +31191,10 @@ var SidePanel = class {
|
|
|
29907
31191
|
return !this.el.hidden;
|
|
29908
31192
|
}
|
|
29909
31193
|
/** Open/close the panel — a bare call flips it. */
|
|
29910
|
-
toggle(
|
|
29911
|
-
if (
|
|
29912
|
-
this.el.hidden = !
|
|
29913
|
-
this.onOpenChange?.(
|
|
31194
|
+
toggle(open2 = this.el.hidden) {
|
|
31195
|
+
if (open2 === !this.el.hidden) return;
|
|
31196
|
+
this.el.hidden = !open2;
|
|
31197
|
+
this.onOpenChange?.(open2);
|
|
29914
31198
|
}
|
|
29915
31199
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
29916
31200
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -29984,7 +31268,7 @@ var SidePanel = class {
|
|
|
29984
31268
|
};
|
|
29985
31269
|
|
|
29986
31270
|
// src/widget/object-tree.ts
|
|
29987
|
-
var
|
|
31271
|
+
var STYLE_ID13 = "vela-widget-objtree";
|
|
29988
31272
|
var CSS10 = `
|
|
29989
31273
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
29990
31274
|
.vela-ot-pane {
|
|
@@ -30223,7 +31507,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
30223
31507
|
this.menu = null;
|
|
30224
31508
|
this.menuActions = /* @__PURE__ */ new Map();
|
|
30225
31509
|
this.drag = null;
|
|
30226
|
-
injectStyles(
|
|
31510
|
+
injectStyles(STYLE_ID13, CSS10, host.ownerDocument);
|
|
30227
31511
|
this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
30228
31512
|
this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
30229
31513
|
}
|
|
@@ -30233,9 +31517,9 @@ var ObjectTree = class extends SidePanel {
|
|
|
30233
31517
|
set groups(next) {
|
|
30234
31518
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
30235
31519
|
}
|
|
30236
|
-
toggle(
|
|
30237
|
-
super.toggle(
|
|
30238
|
-
if (
|
|
31520
|
+
toggle(open2 = this.el.hidden) {
|
|
31521
|
+
super.toggle(open2);
|
|
31522
|
+
if (open2) this.refresh();
|
|
30239
31523
|
}
|
|
30240
31524
|
setSymbol(symbol) {
|
|
30241
31525
|
this.symbolName = parseSymbol(symbol).ticker;
|
|
@@ -31205,7 +32489,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
31205
32489
|
};
|
|
31206
32490
|
|
|
31207
32491
|
// src/widget/data-window.ts
|
|
31208
|
-
var
|
|
32492
|
+
var STYLE_ID14 = "vela-widget-datawindow";
|
|
31209
32493
|
var CSS11 = `
|
|
31210
32494
|
.vela-dw-group {
|
|
31211
32495
|
padding: 10px 8px 4px;
|
|
@@ -31265,11 +32549,11 @@ var DataWindow = class extends SidePanel {
|
|
|
31265
32549
|
super(host, "Data window", "vela-dw");
|
|
31266
32550
|
this.chart = null;
|
|
31267
32551
|
this.unsubs = [];
|
|
31268
|
-
injectStyles(
|
|
32552
|
+
injectStyles(STYLE_ID14, CSS11, host.ownerDocument);
|
|
31269
32553
|
}
|
|
31270
|
-
toggle(
|
|
31271
|
-
super.toggle(
|
|
31272
|
-
if (
|
|
32554
|
+
toggle(open2 = this.el.hidden) {
|
|
32555
|
+
super.toggle(open2);
|
|
32556
|
+
if (open2) this.refresh();
|
|
31273
32557
|
}
|
|
31274
32558
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
31275
32559
|
onChart(chart) {
|
|
@@ -31383,8 +32667,8 @@ var PanelDock = class {
|
|
|
31383
32667
|
}
|
|
31384
32668
|
}
|
|
31385
32669
|
/** Open/close one panel by id — a bare call flips it. Unknown ids are ignored. */
|
|
31386
|
-
toggle(id,
|
|
31387
|
-
this.entries.find((e) => e.id === id)?.panel.toggle(
|
|
32670
|
+
toggle(id, open2) {
|
|
32671
|
+
this.entries.find((e) => e.id === id)?.panel.toggle(open2);
|
|
31388
32672
|
}
|
|
31389
32673
|
/** The open panel's id, or null when the column is closed. */
|
|
31390
32674
|
get openId() {
|
|
@@ -31398,8 +32682,8 @@ var PanelDock = class {
|
|
|
31398
32682
|
/** The dock's persistable state, or null when there is nothing worth saving. */
|
|
31399
32683
|
getState() {
|
|
31400
32684
|
const out = {};
|
|
31401
|
-
const
|
|
31402
|
-
if (
|
|
32685
|
+
const open2 = this.openId;
|
|
32686
|
+
if (open2) out.open = open2;
|
|
31403
32687
|
if (this.widths.size > 0) out.widths = Object.fromEntries(this.widths);
|
|
31404
32688
|
return out.open || out.widths ? out : null;
|
|
31405
32689
|
}
|
|
@@ -31432,13 +32716,13 @@ var PanelDock = class {
|
|
|
31432
32716
|
this.entries.sort((a, b) => a.order - b.order);
|
|
31433
32717
|
const stored = this.widths.get(entry.id);
|
|
31434
32718
|
if (stored !== void 0) entry.panel.setWidth(stored);
|
|
31435
|
-
entry.panel.onOpenChange = (
|
|
31436
|
-
if (
|
|
32719
|
+
entry.panel.onOpenChange = (open2) => {
|
|
32720
|
+
if (open2) {
|
|
31437
32721
|
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
31438
32722
|
this.pendingOpen = null;
|
|
31439
32723
|
}
|
|
31440
|
-
this.deps.chrome.setPanelActive(entry.id,
|
|
31441
|
-
if (
|
|
32724
|
+
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
32725
|
+
if (open2) entry.handle?.onOpen?.();
|
|
31442
32726
|
this.deps.changed?.();
|
|
31443
32727
|
};
|
|
31444
32728
|
entry.panel.onWidthChange = (px) => {
|
|
@@ -31467,7 +32751,7 @@ var PanelDock = class {
|
|
|
31467
32751
|
};
|
|
31468
32752
|
|
|
31469
32753
|
// src/widget/shortcuts-help.ts
|
|
31470
|
-
var
|
|
32754
|
+
var STYLE_ID15 = "vela-widget-shortcuts";
|
|
31471
32755
|
var CSS12 = `
|
|
31472
32756
|
.vela-sh-cat {
|
|
31473
32757
|
color: var(--vela-fg-muted);
|
|
@@ -31493,16 +32777,16 @@ var ShortcutsHelp = class {
|
|
|
31493
32777
|
constructor(keymap, host, onOpenChange) {
|
|
31494
32778
|
this.keymap = keymap;
|
|
31495
32779
|
const doc = (host ?? document.body).ownerDocument;
|
|
31496
|
-
injectStyles(
|
|
32780
|
+
injectStyles(STYLE_ID15, CSS12, doc);
|
|
31497
32781
|
this.list = doc.createElement("div");
|
|
31498
32782
|
this.dialog = new Dialog({
|
|
31499
32783
|
title: "Keyboard shortcuts",
|
|
31500
32784
|
host,
|
|
31501
32785
|
closeOnInteractOutside: true,
|
|
31502
32786
|
content: (body) => body.appendChild(this.list),
|
|
31503
|
-
onOpenChange: (
|
|
31504
|
-
if (
|
|
31505
|
-
onOpenChange?.(
|
|
32787
|
+
onOpenChange: (open2) => {
|
|
32788
|
+
if (open2) this.refresh();
|
|
32789
|
+
onOpenChange?.(open2);
|
|
31506
32790
|
}
|
|
31507
32791
|
});
|
|
31508
32792
|
}
|
|
@@ -31781,7 +33065,7 @@ var ChartContextMenu = class {
|
|
|
31781
33065
|
};
|
|
31782
33066
|
|
|
31783
33067
|
// src/widget/indicator-picker.ts
|
|
31784
|
-
var
|
|
33068
|
+
var STYLE_ID16 = "vela-widget-indpicker";
|
|
31785
33069
|
var CSS13 = `
|
|
31786
33070
|
.vela-ip-searchrow {
|
|
31787
33071
|
display: flex;
|
|
@@ -31859,7 +33143,7 @@ var IndicatorPicker = class {
|
|
|
31859
33143
|
this.isOpen = false;
|
|
31860
33144
|
this.opts = opts;
|
|
31861
33145
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
31862
|
-
injectStyles(
|
|
33146
|
+
injectStyles(STYLE_ID16, CSS13, doc);
|
|
31863
33147
|
this.search = doc.createElement("input");
|
|
31864
33148
|
this.search.className = "vela-ip-search";
|
|
31865
33149
|
this.search.placeholder = "Search\u2026";
|
|
@@ -31877,14 +33161,14 @@ var IndicatorPicker = class {
|
|
|
31877
33161
|
closeOnInteractOutside: true,
|
|
31878
33162
|
// The picker adds/removes indicators live — keep the chart undimmed behind it.
|
|
31879
33163
|
content: (body) => body.append(searchRow, this.list),
|
|
31880
|
-
onOpenChange: (
|
|
31881
|
-
this.isOpen =
|
|
31882
|
-
if (
|
|
33164
|
+
onOpenChange: (open2) => {
|
|
33165
|
+
this.isOpen = open2;
|
|
33166
|
+
if (open2) {
|
|
31883
33167
|
this.search.value = "";
|
|
31884
33168
|
this.refresh();
|
|
31885
33169
|
if (!this.search.closest('[data-layout="mobile"]')) setTimeout(() => this.search.focus(), 0);
|
|
31886
33170
|
}
|
|
31887
|
-
opts.onOpenChange?.(
|
|
33171
|
+
opts.onOpenChange?.(open2);
|
|
31888
33172
|
}
|
|
31889
33173
|
});
|
|
31890
33174
|
this.list.addEventListener("click", (e) => {
|
|
@@ -31987,7 +33271,7 @@ var IndicatorPicker = class {
|
|
|
31987
33271
|
};
|
|
31988
33272
|
|
|
31989
33273
|
// src/widget/timeframe-quick.ts
|
|
31990
|
-
var
|
|
33274
|
+
var STYLE_ID17 = "vela-widget-tfquick";
|
|
31991
33275
|
var CSS14 = `
|
|
31992
33276
|
.vela-tq-input {
|
|
31993
33277
|
display: block;
|
|
@@ -32011,7 +33295,7 @@ var CSS14 = `
|
|
|
32011
33295
|
var TimeframeQuick = class {
|
|
32012
33296
|
constructor(opts) {
|
|
32013
33297
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
32014
|
-
injectStyles(
|
|
33298
|
+
injectStyles(STYLE_ID17, CSS14, doc);
|
|
32015
33299
|
this.input = doc.createElement("input");
|
|
32016
33300
|
this.input.className = "vela-tq-input";
|
|
32017
33301
|
this.input.setAttribute("spellcheck", "false");
|
|
@@ -32023,7 +33307,7 @@ var TimeframeQuick = class {
|
|
|
32023
33307
|
draggable: true,
|
|
32024
33308
|
closeOnInteractOutside: true,
|
|
32025
33309
|
content: (body) => body.append(this.input, this.hint),
|
|
32026
|
-
onOpenChange: (
|
|
33310
|
+
onOpenChange: (open2) => opts.onOpenChange?.(open2)
|
|
32027
33311
|
});
|
|
32028
33312
|
this.input.addEventListener("input", () => this.renderHint());
|
|
32029
33313
|
this.input.addEventListener("keydown", (e) => {
|
|
@@ -32193,7 +33477,7 @@ var LayoutModeController = class {
|
|
|
32193
33477
|
};
|
|
32194
33478
|
|
|
32195
33479
|
// src/widget/mobile-bar.ts
|
|
32196
|
-
var
|
|
33480
|
+
var STYLE_ID18 = "vela-widget-mobilebar";
|
|
32197
33481
|
var CSS15 = `
|
|
32198
33482
|
.vela-mobilebar {
|
|
32199
33483
|
display: none;
|
|
@@ -32244,7 +33528,7 @@ var MobileBar = class {
|
|
|
32244
33528
|
constructor(host, opts) {
|
|
32245
33529
|
this.opts = opts;
|
|
32246
33530
|
const doc = host.ownerDocument;
|
|
32247
|
-
injectStyles(
|
|
33531
|
+
injectStyles(STYLE_ID18, CSS15, doc);
|
|
32248
33532
|
this.el = doc.createElement("div");
|
|
32249
33533
|
this.el.className = "vela-mobilebar";
|
|
32250
33534
|
const item = (cls, label, onClick, icon2) => {
|
|
@@ -32303,7 +33587,7 @@ var MobileBar = class {
|
|
|
32303
33587
|
};
|
|
32304
33588
|
|
|
32305
33589
|
// src/widget/timeframe-drawer.ts
|
|
32306
|
-
var
|
|
33590
|
+
var STYLE_ID19 = "vela-widget-tf-drawer";
|
|
32307
33591
|
var CSS16 = `
|
|
32308
33592
|
.vela-tfd-heading {
|
|
32309
33593
|
padding: 4px 2px 8px;
|
|
@@ -32360,7 +33644,7 @@ var TimeframeDrawer = class {
|
|
|
32360
33644
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
32361
33645
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
32362
33646
|
const doc = opts.host.ownerDocument;
|
|
32363
|
-
injectStyles(
|
|
33647
|
+
injectStyles(STYLE_ID19, CSS16, doc);
|
|
32364
33648
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
32365
33649
|
const rangeHeading = doc.createElement("div");
|
|
32366
33650
|
rangeHeading.className = "vela-tfd-heading";
|
|
@@ -32418,7 +33702,7 @@ var TimeframeDrawer = class {
|
|
|
32418
33702
|
};
|
|
32419
33703
|
|
|
32420
33704
|
// src/widget/drawings-drawer.ts
|
|
32421
|
-
var
|
|
33705
|
+
var STYLE_ID20 = "vela-widget-drawings-drawer";
|
|
32422
33706
|
var CSS17 = `
|
|
32423
33707
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
32424
33708
|
.vela-dd-sticky {
|
|
@@ -32521,7 +33805,7 @@ var DrawingsDrawer = class {
|
|
|
32521
33805
|
this.definition = { groups: [] };
|
|
32522
33806
|
this.activeGroup = "";
|
|
32523
33807
|
const doc = opts.host.ownerDocument;
|
|
32524
|
-
injectStyles(
|
|
33808
|
+
injectStyles(STYLE_ID20, CSS17, doc);
|
|
32525
33809
|
this.drawer = new Drawer({
|
|
32526
33810
|
host: opts.host,
|
|
32527
33811
|
title: "Drawings",
|
|
@@ -32663,7 +33947,7 @@ var DrawingsDrawer = class {
|
|
|
32663
33947
|
};
|
|
32664
33948
|
|
|
32665
33949
|
// src/widget/more-drawer.ts
|
|
32666
|
-
var
|
|
33950
|
+
var STYLE_ID21 = "vela-widget-more-drawer";
|
|
32667
33951
|
var CSS18 = `
|
|
32668
33952
|
.vela-md-actions {
|
|
32669
33953
|
display: flex;
|
|
@@ -32739,7 +34023,7 @@ var MoreDrawer = class {
|
|
|
32739
34023
|
constructor(opts) {
|
|
32740
34024
|
this.opts = opts;
|
|
32741
34025
|
this.view = "main";
|
|
32742
|
-
injectStyles(
|
|
34026
|
+
injectStyles(STYLE_ID21, CSS18, opts.host.ownerDocument);
|
|
32743
34027
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
32744
34028
|
}
|
|
32745
34029
|
open() {
|
|
@@ -32931,7 +34215,7 @@ var MoreDrawer = class {
|
|
|
32931
34215
|
};
|
|
32932
34216
|
|
|
32933
34217
|
// src/widget/timezone-drawer.ts
|
|
32934
|
-
var
|
|
34218
|
+
var STYLE_ID22 = "vela-widget-timezone-drawer";
|
|
32935
34219
|
var CSS19 = `
|
|
32936
34220
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
32937
34221
|
.vela-tzd-row {
|
|
@@ -32951,7 +34235,7 @@ var CSS19 = `
|
|
|
32951
34235
|
var TimezoneDrawer = class {
|
|
32952
34236
|
constructor(opts) {
|
|
32953
34237
|
this.opts = opts;
|
|
32954
|
-
injectStyles(
|
|
34238
|
+
injectStyles(STYLE_ID22, CSS19, opts.host.ownerDocument);
|
|
32955
34239
|
this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
|
|
32956
34240
|
}
|
|
32957
34241
|
open() {
|
|
@@ -32989,7 +34273,7 @@ var TimezoneDrawer = class {
|
|
|
32989
34273
|
};
|
|
32990
34274
|
|
|
32991
34275
|
// src/widget/price-scale-drawer.ts
|
|
32992
|
-
var
|
|
34276
|
+
var STYLE_ID23 = "vela-widget-price-scale-drawer";
|
|
32993
34277
|
var CSS20 = `
|
|
32994
34278
|
.vela-psd-list { padding: 2px 0 4px; }
|
|
32995
34279
|
.vela-psd-row {
|
|
@@ -33019,7 +34303,7 @@ var PriceScaleDrawer = class {
|
|
|
33019
34303
|
constructor(opts) {
|
|
33020
34304
|
this.opts = opts;
|
|
33021
34305
|
this.pane = null;
|
|
33022
|
-
injectStyles(
|
|
34306
|
+
injectStyles(STYLE_ID23, CSS20, opts.host.ownerDocument);
|
|
33023
34307
|
this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
|
|
33024
34308
|
}
|
|
33025
34309
|
open() {
|
|
@@ -33155,7 +34439,7 @@ var PriceScaleDrawer = class {
|
|
|
33155
34439
|
};
|
|
33156
34440
|
|
|
33157
34441
|
// src/widget/drawing-pill.ts
|
|
33158
|
-
var
|
|
34442
|
+
var STYLE_ID24 = "vela-widget-drawing-pill";
|
|
33159
34443
|
var CSS21 = `
|
|
33160
34444
|
.vela-drawpill { display: none; }
|
|
33161
34445
|
[data-layout='mobile'] .vela-drawpill {
|
|
@@ -33216,7 +34500,7 @@ var DrawingPill = class {
|
|
|
33216
34500
|
this.chart = null;
|
|
33217
34501
|
this.chartSubs = [];
|
|
33218
34502
|
const doc = host.ownerDocument;
|
|
33219
|
-
injectStyles(
|
|
34503
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
33220
34504
|
this.el = doc.createElement("div");
|
|
33221
34505
|
this.el.className = "vela-drawpill";
|
|
33222
34506
|
this.el.hidden = true;
|
|
@@ -33325,6 +34609,10 @@ function sanitizeState(doc) {
|
|
|
33325
34609
|
const favs = d.favorites.filter((f) => typeof f === "string");
|
|
33326
34610
|
if (favs.length > 0) out.favorites = favs;
|
|
33327
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
|
+
}
|
|
33328
34616
|
const sync = sanitizeSync(d.sync);
|
|
33329
34617
|
if (sync) out.sync = sync;
|
|
33330
34618
|
const tracks = sanitizeTrackSizes(d.trackSizes);
|
|
@@ -33598,7 +34886,7 @@ var WidgetHistory = class {
|
|
|
33598
34886
|
};
|
|
33599
34887
|
|
|
33600
34888
|
// src/widget/toast.ts
|
|
33601
|
-
var
|
|
34889
|
+
var STYLE_ID25 = "vela-widget-toast";
|
|
33602
34890
|
var CSS22 = `
|
|
33603
34891
|
.vela-toast {
|
|
33604
34892
|
position: absolute;
|
|
@@ -33629,7 +34917,7 @@ var CSS22 = `
|
|
|
33629
34917
|
var Toast = class {
|
|
33630
34918
|
constructor(host) {
|
|
33631
34919
|
this.timer = null;
|
|
33632
|
-
injectStyles(
|
|
34920
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
33633
34921
|
this.el = host.ownerDocument.createElement("div");
|
|
33634
34922
|
this.el.className = "vela-toast";
|
|
33635
34923
|
host.appendChild(this.el);
|
|
@@ -33765,6 +35053,8 @@ var VelaWidget = class {
|
|
|
33765
35053
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
33766
35054
|
/** Favorite drawing-tool types — mirrored from chart events, reapplied on rebuilds. */
|
|
33767
35055
|
this.favs = [];
|
|
35056
|
+
/** Favorite timeframes — the topbar's quick-switch chips, persisted with the shell. */
|
|
35057
|
+
this.tfFavs = [];
|
|
33768
35058
|
/** Mounted attachment disposers (by id), torn down at destroy. */
|
|
33769
35059
|
this.attachmentDisposers = /* @__PURE__ */ new Map();
|
|
33770
35060
|
this.onUnload = () => this.persistNow();
|
|
@@ -33807,6 +35097,7 @@ var VelaWidget = class {
|
|
|
33807
35097
|
this.indicatorTitlesOn = bootCell?.indicatorTitles ?? true;
|
|
33808
35098
|
this.indicatorValuesOn = bootCell?.indicatorValues ?? true;
|
|
33809
35099
|
this.favs = boot?.favorites ? [...boot.favorites] : [];
|
|
35100
|
+
this.tfFavs = boot?.timeframeFavorites ? [...boot.timeframeFavorites] : [];
|
|
33810
35101
|
this.savedConfig = bootCell?.rendererConfig ?? null;
|
|
33811
35102
|
this.savedDrawings = bootCell?.drawings ?? null;
|
|
33812
35103
|
this.pendingIndicators = bootCell?.indicators ?? null;
|
|
@@ -33820,9 +35111,9 @@ var VelaWidget = class {
|
|
|
33820
35111
|
this.symbolPicker = new SymbolPicker({
|
|
33821
35112
|
host: this.root,
|
|
33822
35113
|
onSelect: (symbol) => this.setSymbol(symbol),
|
|
33823
|
-
onOpenChange: (
|
|
33824
|
-
if (
|
|
33825
|
-
this.trackDialog(
|
|
35114
|
+
onOpenChange: (open2) => {
|
|
35115
|
+
if (open2) this.inner?.renderer.closeDialogs();
|
|
35116
|
+
this.trackDialog(open2);
|
|
33826
35117
|
}
|
|
33827
35118
|
});
|
|
33828
35119
|
this.indicatorPicker = opts.indicatorPicker !== false ? new IndicatorPicker({
|
|
@@ -33845,15 +35136,15 @@ var VelaWidget = class {
|
|
|
33845
35136
|
if (i < present.length) this.removeNative(present[i].type);
|
|
33846
35137
|
else this.removeInstance(i - present.length);
|
|
33847
35138
|
},
|
|
33848
|
-
onOpenChange: (
|
|
33849
|
-
if (
|
|
33850
|
-
this.trackDialog(
|
|
35139
|
+
onOpenChange: (open2) => {
|
|
35140
|
+
if (open2) this.inner?.renderer.closeDialogs();
|
|
35141
|
+
this.trackDialog(open2);
|
|
33851
35142
|
}
|
|
33852
35143
|
}) : null;
|
|
33853
35144
|
this.tfQuick = new TimeframeQuick({
|
|
33854
35145
|
host: this.root,
|
|
33855
35146
|
onApply: (tf) => this.setTimeframe(tf),
|
|
33856
|
-
onOpenChange: (
|
|
35147
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
33857
35148
|
});
|
|
33858
35149
|
const picker = this.indicatorPicker;
|
|
33859
35150
|
this.topbar = new Topbar(this.root, {
|
|
@@ -33866,8 +35157,10 @@ var VelaWidget = class {
|
|
|
33866
35157
|
onAlertsClick: (anchor) => this.openAlertsMenu(anchor),
|
|
33867
35158
|
timeframe: this.timeframe,
|
|
33868
35159
|
timeframes: opts.timeframes ?? DEFAULT_TIMEFRAMES,
|
|
35160
|
+
timeframeFavorites: this.tfFavs,
|
|
33869
35161
|
priceStyle: this.priceStyle,
|
|
33870
35162
|
onTimeframe: (tf) => this.setTimeframe(tf),
|
|
35163
|
+
onTimeframeFavorite: (tf, on) => this.setTimeframeFavorite(tf, on),
|
|
33871
35164
|
onPriceStyle: (style) => this.setPriceStyle(style),
|
|
33872
35165
|
getContext: () => this.context()
|
|
33873
35166
|
});
|
|
@@ -33978,7 +35271,7 @@ var VelaWidget = class {
|
|
|
33978
35271
|
label: "Show this shortcuts panel",
|
|
33979
35272
|
category: "Help",
|
|
33980
35273
|
run: () => {
|
|
33981
|
-
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)));
|
|
33982
35275
|
this.shortcutsHelp.open();
|
|
33983
35276
|
}
|
|
33984
35277
|
});
|
|
@@ -34012,7 +35305,7 @@ var VelaWidget = class {
|
|
|
34012
35305
|
activeRange: () => this.activeRangeId,
|
|
34013
35306
|
onTimeframe: (tf) => this.setTimeframe(tf),
|
|
34014
35307
|
onRange: (preset) => this.applyRange(preset),
|
|
34015
|
-
onOpenChange: (
|
|
35308
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34016
35309
|
}));
|
|
34017
35310
|
this.tfDrawer.open();
|
|
34018
35311
|
}
|
|
@@ -34024,7 +35317,7 @@ var VelaWidget = class {
|
|
|
34024
35317
|
isFavorite: (type) => this.inner?.drawings.isFavorite(type) ?? false,
|
|
34025
35318
|
onFavorite: (type, on) => this.inner?.drawings.setFavorite(type, on),
|
|
34026
35319
|
onSelect: (type) => this.inner?.drawings.setTool(type),
|
|
34027
|
-
onOpenChange: (
|
|
35320
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34028
35321
|
}));
|
|
34029
35322
|
this.drawingsDrawer.open();
|
|
34030
35323
|
}
|
|
@@ -34045,7 +35338,7 @@ var VelaWidget = class {
|
|
|
34045
35338
|
// Left-aligned actions have their own bottom-bar stop — only the rest
|
|
34046
35339
|
// lands in the drawer, or every left action would appear twice.
|
|
34047
35340
|
actions: () => widgetActions("topbar", this.context()).filter((a) => a.align !== "left").map((a) => ({ label: a.label, icon: a.icon, run: () => a.run(this.context()) })),
|
|
34048
|
-
onOpenChange: (
|
|
35341
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34049
35342
|
}));
|
|
34050
35343
|
this.moreDrawer.open();
|
|
34051
35344
|
}
|
|
@@ -34054,7 +35347,7 @@ var VelaWidget = class {
|
|
|
34054
35347
|
host: this.root,
|
|
34055
35348
|
timezone: () => this.timezone,
|
|
34056
35349
|
onTimezone: (zone) => this.setTimezone(zone),
|
|
34057
|
-
onOpenChange: (
|
|
35350
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34058
35351
|
}));
|
|
34059
35352
|
this.timezoneDrawer.open();
|
|
34060
35353
|
}
|
|
@@ -34064,7 +35357,7 @@ var VelaWidget = class {
|
|
|
34064
35357
|
host: this.root,
|
|
34065
35358
|
chart: () => this.inner,
|
|
34066
35359
|
pressY: () => this.priceScalePressY,
|
|
34067
|
-
onOpenChange: (
|
|
35360
|
+
onOpenChange: (open2) => this.trackDialog(open2)
|
|
34068
35361
|
}));
|
|
34069
35362
|
this.priceScaleDrawer.open();
|
|
34070
35363
|
}
|
|
@@ -34093,7 +35386,7 @@ var VelaWidget = class {
|
|
|
34093
35386
|
setTimeframe: (tf) => this.setTimeframe(tf),
|
|
34094
35387
|
setPriceStyle: (style) => this.setPriceStyle(style),
|
|
34095
35388
|
openSymbolSearch: (query) => this.symbolPicker.open(query ?? ""),
|
|
34096
|
-
togglePanel: (id,
|
|
35389
|
+
togglePanel: (id, open2) => this.dock.toggle(id, open2),
|
|
34097
35390
|
host: this.root,
|
|
34098
35391
|
toast: (message, kind) => this.toast?.show(message, kind)
|
|
34099
35392
|
};
|
|
@@ -34152,6 +35445,14 @@ var VelaWidget = class {
|
|
|
34152
35445
|
this.markStateDirty();
|
|
34153
35446
|
void this.inner?.setMarket({ timeframe: tf, bars: this.bars });
|
|
34154
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
|
+
}
|
|
34155
35456
|
/**
|
|
34156
35457
|
* Switch the chart symbol in place. An `EXCHANGE:` prefix pins the venue (the
|
|
34157
35458
|
* symbol picker composes one from the row the user pointed at) — a bare ticker
|
|
@@ -34209,6 +35510,7 @@ var VelaWidget = class {
|
|
|
34209
35510
|
});
|
|
34210
35511
|
const state = { version: 1, layout: "1", activeCellId: "c1", timezone: this.timezone, charts: [{ id: "c1", ...cell }] };
|
|
34211
35512
|
if (this.favs.length > 0) state.favorites = [...this.favs];
|
|
35513
|
+
if (this.tfFavs.length > 0) state.timeframeFavorites = [...this.tfFavs];
|
|
34212
35514
|
const panels2 = this.dock.getState();
|
|
34213
35515
|
if (panels2) state.panels = panels2;
|
|
34214
35516
|
return state;
|
|
@@ -34232,6 +35534,10 @@ var VelaWidget = class {
|
|
|
34232
35534
|
this.favs = [...st.favorites];
|
|
34233
35535
|
this.inner?.drawings.setFavorites(this.favs);
|
|
34234
35536
|
}
|
|
35537
|
+
if (st.timeframeFavorites) {
|
|
35538
|
+
this.tfFavs = [...st.timeframeFavorites];
|
|
35539
|
+
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
35540
|
+
}
|
|
34235
35541
|
this.dock.applyState(st.panels);
|
|
34236
35542
|
if (cell) {
|
|
34237
35543
|
const style = fromUrl.priceStyle ?? cell.priceStyle;
|
|
@@ -34806,9 +36112,9 @@ var VelaWidget = class {
|
|
|
34806
36112
|
this.tfQuick.open(key);
|
|
34807
36113
|
}
|
|
34808
36114
|
}
|
|
34809
|
-
trackDialog(
|
|
34810
|
-
this.openDialogs = Math.max(0, this.openDialogs + (
|
|
34811
|
-
if (
|
|
36115
|
+
trackDialog(open2) {
|
|
36116
|
+
this.openDialogs = Math.max(0, this.openDialogs + (open2 ? 1 : -1));
|
|
36117
|
+
if (open2) this.keymap.pushScope("dialog");
|
|
34812
36118
|
else this.keymap.popScope("dialog");
|
|
34813
36119
|
}
|
|
34814
36120
|
downloadScreenshot() {
|