@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
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { deserializeDrawing, chartTypes, rendererLayers, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-
|
|
2
|
-
import { themeTokens, STATIC_TOKENS } from './chunk-
|
|
3
|
-
import { icon, iconAt, withAlpha, NEUTRAL, BEARISH, BULLISH, isDarkColor,
|
|
1
|
+
import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-ZNL2X6U2.js';
|
|
2
|
+
import { themeTokens, closeOpenPopovers, Select, NumberInput, colorField, Switch, blendOver, splitColor, isPopoverOpen, TextField, Popover, openPopoverTrigger, buildColorPicker, toggleSelectList, STATIC_TOKENS } from './chunk-OGRQW3M6.js';
|
|
3
|
+
import { icon, iconAt, withAlpha, NEUTRAL, BEARISH, BULLISH, isDarkColor, ACCENT, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, injectStyles, SLATE_DEEP, VALID, INVALID, svg24, svg24Solid } from './chunk-EMS6PO2T.js';
|
|
4
4
|
|
|
5
5
|
// src/core/events/EventBus.ts
|
|
6
6
|
var TypedEventBus = class {
|
|
@@ -2844,6 +2844,7 @@ var LEGEND_CTL_PX = 18;
|
|
|
2844
2844
|
var LEGEND_ROW_PAD_Y = 2;
|
|
2845
2845
|
var LEGEND_ROW_PAD_X = 6;
|
|
2846
2846
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
2847
|
+
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
2847
2848
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
2848
2849
|
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;`;
|
|
2849
2850
|
var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
|
|
@@ -2888,6 +2889,11 @@ var InputsUI = class {
|
|
|
2888
2889
|
this.dialogTips = [];
|
|
2889
2890
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
2890
2891
|
this.moveMenu = null;
|
|
2892
|
+
/** Open settings-dialog choice menu (custom dropdown — torn down with the dialog). */
|
|
2893
|
+
this.choicePop = null;
|
|
2894
|
+
/** Open settings-dialog calendar popover (torn down with the dialog). */
|
|
2895
|
+
this.calendarPop = null;
|
|
2896
|
+
this.calendarAnchor = null;
|
|
2891
2897
|
/** Collapsed panes → the master indicator id to keep visible (others hidden in the strip). */
|
|
2892
2898
|
this.paneCollapse = /* @__PURE__ */ new Map();
|
|
2893
2899
|
/** The user folded the indicator legend away behind the chevron — CHART-WIDE: every
|
|
@@ -2913,6 +2919,10 @@ var InputsUI = class {
|
|
|
2913
2919
|
this.onDialogKey = (e) => {
|
|
2914
2920
|
if (e.key === "Escape") {
|
|
2915
2921
|
e.preventDefault();
|
|
2922
|
+
if (isPopoverOpen()) {
|
|
2923
|
+
closeOpenPopovers();
|
|
2924
|
+
return;
|
|
2925
|
+
}
|
|
2916
2926
|
this.closeDialog();
|
|
2917
2927
|
}
|
|
2918
2928
|
};
|
|
@@ -3358,6 +3368,7 @@ var InputsUI = class {
|
|
|
3358
3368
|
titleWrap.appendChild(beta);
|
|
3359
3369
|
}
|
|
3360
3370
|
el.appendChild(titleWrap);
|
|
3371
|
+
el.appendChild(statusEl);
|
|
3361
3372
|
const valuesEl = document.createElement("span");
|
|
3362
3373
|
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;`;
|
|
3363
3374
|
el.appendChild(valuesEl);
|
|
@@ -3418,7 +3429,6 @@ var InputsUI = class {
|
|
|
3418
3429
|
close.addEventListener("click", () => this.onRemove?.(id));
|
|
3419
3430
|
controlsEl.appendChild(close);
|
|
3420
3431
|
el.appendChild(controlsEl);
|
|
3421
|
-
el.appendChild(statusEl);
|
|
3422
3432
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
3423
3433
|
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 });
|
|
3424
3434
|
this.syncFoldToggle();
|
|
@@ -3436,7 +3446,7 @@ var InputsUI = class {
|
|
|
3436
3446
|
/**
|
|
3437
3447
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
3438
3448
|
* dots (a fetch is in flight — the same load affordance as the chart's own bar-load dots,
|
|
3439
|
-
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered
|
|
3449
|
+
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
3440
3450
|
*/
|
|
3441
3451
|
setStatus(id, status) {
|
|
3442
3452
|
const row = this.rows.get(id);
|
|
@@ -3448,7 +3458,7 @@ var InputsUI = class {
|
|
|
3448
3458
|
return;
|
|
3449
3459
|
}
|
|
3450
3460
|
if (status === "loading") {
|
|
3451
|
-
el.style.cssText =
|
|
3461
|
+
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);`;
|
|
3452
3462
|
for (let i = 0; i < 3; i += 1) {
|
|
3453
3463
|
const dot = document.createElement("span");
|
|
3454
3464
|
dot.style.cssText = "width:4px;height:4px;border-radius:50%;background:currentColor;opacity:0.15;flex:none;";
|
|
@@ -3458,7 +3468,7 @@ var InputsUI = class {
|
|
|
3458
3468
|
return;
|
|
3459
3469
|
}
|
|
3460
3470
|
this.ensureStatusKeyframes();
|
|
3461
|
-
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;`;
|
|
3471
|
+
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;`;
|
|
3462
3472
|
}
|
|
3463
3473
|
/** Inject the status keyframes once (idempotent). */
|
|
3464
3474
|
ensureStatusKeyframes() {
|
|
@@ -3573,7 +3583,8 @@ var InputsUI = class {
|
|
|
3573
3583
|
/**
|
|
3574
3584
|
* Open the indicator's settings as a centered modal over the chart — a themed,
|
|
3575
3585
|
* dependency-free port of the app's tabbed script-settings dialog: a header with the
|
|
3576
|
-
* indicator title,
|
|
3586
|
+
* indicator title, a tab strip (the default "Inputs" tab plus any tab the inputs
|
|
3587
|
+
* declare via `tab=`), a scrollable body per tab that groups inputs by `group=`
|
|
3577
3588
|
* (section headers) and packs `inline=` inputs onto one row, and a sticky footer.
|
|
3578
3589
|
*
|
|
3579
3590
|
* Edits commit LIVE (each change re-runs the indicator), matching the current
|
|
@@ -3603,55 +3614,57 @@ var InputsUI = class {
|
|
|
3603
3614
|
});
|
|
3604
3615
|
const card = document.createElement("div");
|
|
3605
3616
|
card.className = "vela-ind-dialog";
|
|
3606
|
-
card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(
|
|
3617
|
+
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;`;
|
|
3607
3618
|
if (this.mobileLayout) card.style.cssText += "width:100%;min-width:0;max-width:none;height:100%;max-height:none;border:none;border-radius:0;";
|
|
3608
3619
|
applyChromeTokens(card, t);
|
|
3609
3620
|
card.addEventListener("keydown", (e) => {
|
|
3610
3621
|
if (e.key !== "Escape") e.stopPropagation();
|
|
3611
3622
|
});
|
|
3612
3623
|
const header = document.createElement("div");
|
|
3613
|
-
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px
|
|
3624
|
+
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 20px;flex:0 0 auto;";
|
|
3614
3625
|
const hTitle = document.createElement("span");
|
|
3615
3626
|
hTitle.textContent = row.settingsTitle;
|
|
3616
|
-
hTitle.style.cssText = "font-weight:600;font-size:
|
|
3627
|
+
hTitle.style.cssText = "font-weight:600;font-size:20px;line-height:28px;";
|
|
3617
3628
|
const closeBtn = document.createElement("button");
|
|
3618
3629
|
closeBtn.type = "button";
|
|
3619
3630
|
closeBtn.setAttribute("aria-label", "Close");
|
|
3620
3631
|
this.dialogTips.push(attachChromeTooltip(closeBtn, { host: this.container, theme: () => this.theme, text: () => "Close" }));
|
|
3621
3632
|
closeBtn.innerHTML = iconAt("close", 15);
|
|
3622
|
-
closeBtn.className = "vela-ind-
|
|
3623
|
-
closeBtn.style.cssText = "cursor:pointer;display:inline-flex;align-items:center;background:transparent;border:none;line-height:0;padding:2px;flex:0 0 auto;";
|
|
3633
|
+
closeBtn.className = "vela-ind-dlg-close";
|
|
3624
3634
|
closeBtn.addEventListener("click", () => this.closeDialog());
|
|
3625
3635
|
header.append(hTitle, closeBtn);
|
|
3626
3636
|
card.appendChild(header);
|
|
3627
3637
|
if (!this.mobileLayout) makeDialogDraggable(card, header, { closeSelector: "button" });
|
|
3638
|
+
const tabDefs = tabInputs(row.inputs);
|
|
3628
3639
|
const tabs = document.createElement("div");
|
|
3629
|
-
tabs.style.cssText = `display:flex;gap:
|
|
3630
|
-
const
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
if (group.name) {
|
|
3641
|
-
const gh = document.createElement("div");
|
|
3642
|
-
gh.textContent = group.name;
|
|
3643
|
-
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);";
|
|
3644
|
-
section.appendChild(gh);
|
|
3640
|
+
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
3641
|
+
const tabEls = [];
|
|
3642
|
+
const bodies = [];
|
|
3643
|
+
const activate = (idx) => {
|
|
3644
|
+
for (let i = 0; i < tabEls.length; i += 1) {
|
|
3645
|
+
const active = i === idx;
|
|
3646
|
+
const el = tabEls[i];
|
|
3647
|
+
el.style.borderBottomColor = active ? fg : "transparent";
|
|
3648
|
+
el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
|
|
3649
|
+
el.classList.toggle("vela-ind-tab-active", active);
|
|
3650
|
+
bodies[i].style.display = active ? "grid" : "none";
|
|
3645
3651
|
}
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3652
|
+
};
|
|
3653
|
+
for (const [idx, def] of tabDefs.entries()) {
|
|
3654
|
+
const tab = document.createElement("div");
|
|
3655
|
+
tab.textContent = def.name;
|
|
3656
|
+
tab.className = "vela-ind-tab";
|
|
3657
|
+
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;" : ""}`;
|
|
3658
|
+
tab.addEventListener("click", () => activate(idx));
|
|
3659
|
+
tabs.appendChild(tab);
|
|
3660
|
+
tabEls.push(tab);
|
|
3661
|
+
bodies.push(this.buildTabBody(def.inputs, row));
|
|
3651
3662
|
}
|
|
3652
|
-
card.appendChild(
|
|
3663
|
+
card.appendChild(tabs);
|
|
3664
|
+
for (const body of bodies) card.appendChild(body);
|
|
3665
|
+
activate(0);
|
|
3653
3666
|
const footer = document.createElement("div");
|
|
3654
|
-
footer.style.cssText = `display:flex;justify-content:flex-end;gap:
|
|
3667
|
+
footer.style.cssText = `display:flex;justify-content:flex-end;gap:12px;padding:16px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
|
|
3655
3668
|
footer.append(
|
|
3656
3669
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
3657
3670
|
this.dialogButton("Ok", true, () => this.closeDialog())
|
|
@@ -3663,8 +3676,32 @@ var InputsUI = class {
|
|
|
3663
3676
|
this.backdrop = backdrop;
|
|
3664
3677
|
document.addEventListener("keydown", this.onDialogKey);
|
|
3665
3678
|
}
|
|
3679
|
+
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
3680
|
+
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
3681
|
+
buildTabBody(inputs, row) {
|
|
3682
|
+
const body = document.createElement("div");
|
|
3683
|
+
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;";
|
|
3684
|
+
for (const group of groupInputs(inputs)) {
|
|
3685
|
+
const section = document.createElement("div");
|
|
3686
|
+
section.style.cssText = "display:contents;";
|
|
3687
|
+
if (group.name) {
|
|
3688
|
+
const gh = document.createElement("div");
|
|
3689
|
+
gh.textContent = group.name;
|
|
3690
|
+
const first = body.childElementCount === 0;
|
|
3691
|
+
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;`;
|
|
3692
|
+
section.appendChild(gh);
|
|
3693
|
+
}
|
|
3694
|
+
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
3695
|
+
body.appendChild(section);
|
|
3696
|
+
}
|
|
3697
|
+
return body;
|
|
3698
|
+
}
|
|
3666
3699
|
closeDialog() {
|
|
3667
3700
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
3701
|
+
closeOpenPopovers();
|
|
3702
|
+
this.choicePop = null;
|
|
3703
|
+
this.calendarPop = null;
|
|
3704
|
+
this.calendarAnchor = null;
|
|
3668
3705
|
for (const dispose of this.dialogTips.splice(0)) dispose();
|
|
3669
3706
|
this.backdrop?.remove();
|
|
3670
3707
|
this.backdrop = null;
|
|
@@ -3692,79 +3729,77 @@ var InputsUI = class {
|
|
|
3692
3729
|
buildInputRow(grid, decls, row) {
|
|
3693
3730
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
3694
3731
|
if (decls.length > 1) {
|
|
3695
|
-
const
|
|
3696
|
-
|
|
3732
|
+
const cell2 = document.createElement("div");
|
|
3733
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
|
|
3697
3734
|
for (const d of decls) {
|
|
3698
3735
|
const fit2 = d.type === "color" || d.type === "bool";
|
|
3699
3736
|
const toggleFirst = d.type === "bool";
|
|
3700
3737
|
const id2 = idOf(d);
|
|
3701
3738
|
const item = document.createElement("div");
|
|
3702
|
-
item.style.cssText = `display:flex;align-items:center;gap:
|
|
3739
|
+
item.style.cssText = `display:flex;align-items:center;gap:8px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
|
|
3703
3740
|
const name = nameOf(d);
|
|
3704
3741
|
if (name) {
|
|
3705
3742
|
const lbl = document.createElement("label");
|
|
3706
3743
|
lbl.htmlFor = id2;
|
|
3707
3744
|
lbl.textContent = name;
|
|
3708
|
-
lbl.style.cssText = `font-size:
|
|
3745
|
+
lbl.style.cssText = `font-size:14px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
|
|
3709
3746
|
item.appendChild(lbl);
|
|
3710
3747
|
}
|
|
3711
3748
|
const cw2 = document.createElement("div");
|
|
3712
|
-
cw2.style.cssText = fit2 ? "" : "width:
|
|
3749
|
+
cw2.style.cssText = fit2 ? "" : "width:100px;";
|
|
3713
3750
|
cw2.appendChild(this.buildControl(row, d, id2));
|
|
3714
3751
|
item.appendChild(cw2);
|
|
3715
|
-
|
|
3752
|
+
cell2.appendChild(item);
|
|
3716
3753
|
}
|
|
3717
3754
|
const lastTip = [...decls].reverse().find((d) => d.tooltip)?.tooltip;
|
|
3718
|
-
if (lastTip)
|
|
3719
|
-
grid.appendChild(
|
|
3755
|
+
if (lastTip) cell2.appendChild(this.infoButton(lastTip));
|
|
3756
|
+
grid.appendChild(cell2);
|
|
3720
3757
|
return;
|
|
3721
3758
|
}
|
|
3722
3759
|
const lead = decls[0];
|
|
3723
3760
|
const id = idOf(lead);
|
|
3724
3761
|
if (lead.type === "bool") {
|
|
3725
|
-
const
|
|
3726
|
-
|
|
3762
|
+
const cell2 = document.createElement("div");
|
|
3763
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;align-items:center;gap:8px;";
|
|
3727
3764
|
const lbl = document.createElement("label");
|
|
3728
|
-
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:
|
|
3765
|
+
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;";
|
|
3729
3766
|
lbl.appendChild(this.buildControl(row, lead, id));
|
|
3730
3767
|
lbl.appendChild(document.createTextNode(nameOf(lead)));
|
|
3731
|
-
|
|
3732
|
-
if (lead.tooltip)
|
|
3733
|
-
grid.appendChild(
|
|
3734
|
-
grid.appendChild(document.createElement("div"));
|
|
3768
|
+
cell2.appendChild(lbl);
|
|
3769
|
+
if (lead.tooltip) cell2.appendChild(this.infoButton(lead.tooltip));
|
|
3770
|
+
grid.appendChild(cell2);
|
|
3735
3771
|
return;
|
|
3736
3772
|
}
|
|
3737
3773
|
if (lead.type === "text_area") {
|
|
3738
|
-
const
|
|
3739
|
-
|
|
3774
|
+
const cell2 = document.createElement("div");
|
|
3775
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
|
|
3740
3776
|
const head = document.createElement("div");
|
|
3741
3777
|
head.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;";
|
|
3742
3778
|
const lbl = document.createElement("label");
|
|
3743
3779
|
lbl.htmlFor = id;
|
|
3744
3780
|
lbl.textContent = nameOf(lead);
|
|
3745
|
-
lbl.style.cssText = "font-size:
|
|
3781
|
+
lbl.style.cssText = "font-size:14px;";
|
|
3746
3782
|
head.appendChild(lbl);
|
|
3747
3783
|
if (lead.tooltip) head.appendChild(this.infoButton(lead.tooltip));
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
grid.appendChild(
|
|
3784
|
+
cell2.appendChild(head);
|
|
3785
|
+
cell2.appendChild(this.buildControl(row, lead, id));
|
|
3786
|
+
grid.appendChild(cell2);
|
|
3751
3787
|
return;
|
|
3752
3788
|
}
|
|
3753
3789
|
const label = document.createElement("label");
|
|
3754
3790
|
label.htmlFor = id;
|
|
3755
3791
|
label.textContent = nameOf(lead);
|
|
3756
|
-
label.style.cssText = "font-size:
|
|
3757
|
-
const controlCell = document.createElement("div");
|
|
3758
|
-
controlCell.style.cssText = "grid-column:span 2;display:flex;align-items:center;gap:8px;";
|
|
3792
|
+
label.style.cssText = "font-size:14px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
3759
3793
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
3794
|
+
const cell = document.createElement("div");
|
|
3795
|
+
cell.style.cssText = "display:flex;align-items:center;gap:8px;justify-self:start;";
|
|
3760
3796
|
const cw = document.createElement("div");
|
|
3761
|
-
cw.style.cssText = fit ? "" : "width:
|
|
3797
|
+
cw.style.cssText = fit ? "" : "width:100px;";
|
|
3762
3798
|
cw.appendChild(this.buildControl(row, lead, id));
|
|
3763
|
-
|
|
3764
|
-
if (lead.tooltip)
|
|
3799
|
+
cell.appendChild(cw);
|
|
3800
|
+
if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
|
|
3765
3801
|
grid.appendChild(label);
|
|
3766
|
-
grid.appendChild(
|
|
3767
|
-
grid.appendChild(document.createElement("div"));
|
|
3802
|
+
grid.appendChild(cell);
|
|
3768
3803
|
}
|
|
3769
3804
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
3770
3805
|
buildControl(row, inp, id) {
|
|
@@ -3773,107 +3808,30 @@ var InputsUI = class {
|
|
|
3773
3808
|
row.values[inp.key] = value;
|
|
3774
3809
|
this.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
3775
3810
|
};
|
|
3776
|
-
const ctrl = this.ctrlStyle();
|
|
3777
3811
|
if (inp.type === "bool") return this.buildToggle(id, Boolean(current), emit);
|
|
3778
|
-
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current),
|
|
3779
|
-
if (inp.type === "source") return this.select(id, SOURCES, String(current),
|
|
3812
|
+
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), emit);
|
|
3813
|
+
if (inp.type === "source") return this.select(id, SOURCES, String(current), emit);
|
|
3780
3814
|
if (inp.type === "color") {
|
|
3781
|
-
const
|
|
3782
|
-
|
|
3783
|
-
ci.id = id;
|
|
3784
|
-
ci.value = toHex6(String(current));
|
|
3785
|
-
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;`;
|
|
3786
|
-
ci.addEventListener("input", () => emit(ci.value));
|
|
3787
|
-
return ci;
|
|
3815
|
+
const field = colorField(this.theme, () => String(row.values[inp.key] ?? inp.defval), (v) => emit(v), { shape: "circle", id });
|
|
3816
|
+
return field;
|
|
3788
3817
|
}
|
|
3789
3818
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
3790
|
-
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current),
|
|
3819
|
+
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), emit);
|
|
3791
3820
|
if (inp.type === "session") return this.buildSession(id, String(current), emit);
|
|
3792
3821
|
if (inp.type === "time") return this.buildTime(id, Number(current) || 0, emit);
|
|
3793
3822
|
if (inp.type === "text_area") return this.buildTextArea(id, String(current), emit);
|
|
3794
3823
|
if (inp.type === "int" || inp.type === "float" || inp.type === "price") {
|
|
3795
|
-
|
|
3796
|
-
ni.type = "number";
|
|
3797
|
-
ni.id = id;
|
|
3798
|
-
ni.value = String(current);
|
|
3799
|
-
if (inp.min !== void 0) ni.min = String(inp.min);
|
|
3800
|
-
if (inp.max !== void 0) ni.max = String(inp.max);
|
|
3801
|
-
ni.step = String(inp.step ?? (inp.type === "int" ? 1 : 0.1));
|
|
3802
|
-
ni.style.cssText = ctrl;
|
|
3803
|
-
let last = String(current);
|
|
3804
|
-
const commit = () => {
|
|
3805
|
-
let n = Number(ni.value);
|
|
3806
|
-
if (!Number.isFinite(n)) {
|
|
3807
|
-
ni.value = last;
|
|
3808
|
-
return;
|
|
3809
|
-
}
|
|
3810
|
-
if (inp.min !== void 0) n = Math.max(inp.min, n);
|
|
3811
|
-
if (inp.max !== void 0) n = Math.min(inp.max, n);
|
|
3812
|
-
if (inp.type === "int") n = Math.round(n);
|
|
3813
|
-
ni.value = String(n);
|
|
3814
|
-
if (String(n) !== last) {
|
|
3815
|
-
last = String(n);
|
|
3816
|
-
emit(n);
|
|
3817
|
-
}
|
|
3818
|
-
};
|
|
3819
|
-
ni.addEventListener("blur", commit);
|
|
3820
|
-
ni.addEventListener("keydown", (e) => {
|
|
3821
|
-
if (e.key === "Enter") {
|
|
3822
|
-
e.preventDefault();
|
|
3823
|
-
ni.blur();
|
|
3824
|
-
}
|
|
3825
|
-
});
|
|
3826
|
-
return ni;
|
|
3824
|
+
return this.buildNumber(id, Number(current), inp, emit);
|
|
3827
3825
|
}
|
|
3828
3826
|
return this.buildTextField(id, String(current), emit);
|
|
3829
3827
|
}
|
|
3830
3828
|
/** A plain text field that commits on blur / Enter (shared by string inputs and the pickerless symbol field). */
|
|
3831
3829
|
buildTextField(id, current, emit) {
|
|
3832
|
-
|
|
3833
|
-
ti.type = "text";
|
|
3834
|
-
ti.id = id;
|
|
3835
|
-
ti.value = current;
|
|
3836
|
-
ti.style.cssText = this.ctrlStyle();
|
|
3837
|
-
let last = current;
|
|
3838
|
-
ti.addEventListener("blur", () => {
|
|
3839
|
-
if (ti.value !== last) {
|
|
3840
|
-
last = ti.value;
|
|
3841
|
-
emit(ti.value);
|
|
3842
|
-
}
|
|
3843
|
-
});
|
|
3844
|
-
ti.addEventListener("keydown", (e) => {
|
|
3845
|
-
if (e.key === "Enter") {
|
|
3846
|
-
e.preventDefault();
|
|
3847
|
-
ti.blur();
|
|
3848
|
-
}
|
|
3849
|
-
});
|
|
3850
|
-
return ti;
|
|
3830
|
+
return new TextField({ id, value: current, size: "md", onChange: emit }).el;
|
|
3851
3831
|
}
|
|
3852
3832
|
/** A square check-toggle (filled + check when on) — replaces the raw checkbox. */
|
|
3853
3833
|
buildToggle(id, checked, onChange) {
|
|
3854
|
-
|
|
3855
|
-
const border = this.neutralBorder();
|
|
3856
|
-
const fill = this.strongText();
|
|
3857
|
-
const b = document.createElement("button");
|
|
3858
|
-
b.type = "button";
|
|
3859
|
-
b.id = id;
|
|
3860
|
-
b.setAttribute("role", "switch");
|
|
3861
|
-
b.innerHTML = CHECK_SVG;
|
|
3862
|
-
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;`;
|
|
3863
|
-
let on = checked;
|
|
3864
|
-
const paint = (v) => {
|
|
3865
|
-
b.setAttribute("aria-checked", v ? "true" : "false");
|
|
3866
|
-
b.style.background = v ? fill : "transparent";
|
|
3867
|
-
b.style.borderColor = v ? fill : border;
|
|
3868
|
-
b.style.color = v ? t.background : "transparent";
|
|
3869
|
-
};
|
|
3870
|
-
paint(checked);
|
|
3871
|
-
b.addEventListener("click", () => {
|
|
3872
|
-
on = !on;
|
|
3873
|
-
paint(on);
|
|
3874
|
-
onChange(on);
|
|
3875
|
-
});
|
|
3876
|
-
return b;
|
|
3834
|
+
return new Switch({ id, checked, size: "md", tone: "bright", onChange }).el;
|
|
3877
3835
|
}
|
|
3878
3836
|
/** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
|
|
3879
3837
|
infoButton(tooltip) {
|
|
@@ -3885,45 +3843,22 @@ var InputsUI = class {
|
|
|
3885
3843
|
return b;
|
|
3886
3844
|
}
|
|
3887
3845
|
/** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
|
|
3888
|
-
select(id, options, current,
|
|
3889
|
-
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current,
|
|
3846
|
+
select(id, options, current, emit) {
|
|
3847
|
+
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, emit);
|
|
3890
3848
|
}
|
|
3891
3849
|
/** A dropdown whose visible labels differ from the committed values (label ≠ value pairs). */
|
|
3892
|
-
selectPairs(id, pairs, current,
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
sel.appendChild(o);
|
|
3903
|
-
}
|
|
3904
|
-
for (const p of pairs) {
|
|
3905
|
-
const o = document.createElement("option");
|
|
3906
|
-
o.value = p.value;
|
|
3907
|
-
o.textContent = p.label;
|
|
3908
|
-
if (p.value === current) o.selected = true;
|
|
3909
|
-
sel.appendChild(o);
|
|
3910
|
-
}
|
|
3911
|
-
this.styleSelect(sel);
|
|
3912
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
3913
|
-
return this.wrapSelectChevron(sel);
|
|
3914
|
-
}
|
|
3915
|
-
/** Wrap a `<select>` with an inset chevron — native arrows can't be repositioned reliably. */
|
|
3916
|
-
wrapSelectChevron(sel) {
|
|
3917
|
-
const wrap = document.createElement("div");
|
|
3918
|
-
wrap.style.cssText = "position:relative;width:100%;";
|
|
3919
|
-
sel.style.paddingRight = "28px";
|
|
3920
|
-
const chevron = document.createElement("span");
|
|
3921
|
-
chevron.innerHTML = SELECT_CHEVRON_SVG;
|
|
3922
|
-
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;`;
|
|
3923
|
-
wrap.append(sel, chevron);
|
|
3924
|
-
return wrap;
|
|
3850
|
+
selectPairs(id, pairs, current, onChange) {
|
|
3851
|
+
return new Select({
|
|
3852
|
+
id,
|
|
3853
|
+
options: pairs,
|
|
3854
|
+
value: current,
|
|
3855
|
+
size: "md",
|
|
3856
|
+
theme: this.theme,
|
|
3857
|
+
list: this.listPopover(),
|
|
3858
|
+
onChange
|
|
3859
|
+
}).el;
|
|
3925
3860
|
}
|
|
3926
|
-
/** `input.session` → two HH:MM
|
|
3861
|
+
/** `input.session` → two typeable HH:MM comboboxes committing a `HHMM-HHMM` session string. */
|
|
3927
3862
|
buildSession(id, current, emit) {
|
|
3928
3863
|
const [start, end] = sessionToTimes(current);
|
|
3929
3864
|
const wrap = document.createElement("div");
|
|
@@ -3931,15 +3866,16 @@ var InputsUI = class {
|
|
|
3931
3866
|
let from = start;
|
|
3932
3867
|
let to = end;
|
|
3933
3868
|
const commit = () => emit(timesToSession(from, to));
|
|
3934
|
-
|
|
3935
|
-
from
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
to
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3869
|
+
wrap.append(
|
|
3870
|
+
this.timeCombobox(id, from, 86, (v) => {
|
|
3871
|
+
from = v;
|
|
3872
|
+
commit();
|
|
3873
|
+
}),
|
|
3874
|
+
this.timeCombobox(`${id}-end`, to, 86, (v) => {
|
|
3875
|
+
to = v;
|
|
3876
|
+
commit();
|
|
3877
|
+
})
|
|
3878
|
+
);
|
|
3943
3879
|
return wrap;
|
|
3944
3880
|
}
|
|
3945
3881
|
/** `input.time` → a date picker + an HH:MM dropdown, committing an epoch-ms timestamp. */
|
|
@@ -3954,20 +3890,15 @@ var InputsUI = class {
|
|
|
3954
3890
|
const ms = Date.parse(`${date}T${time}:00`);
|
|
3955
3891
|
if (Number.isFinite(ms)) emit(ms);
|
|
3956
3892
|
};
|
|
3957
|
-
const
|
|
3958
|
-
|
|
3959
|
-
dateInput.id = id;
|
|
3960
|
-
dateInput.value = date;
|
|
3961
|
-
dateInput.style.cssText = `${this.ctrlStyle()}width:auto;cursor:pointer;`;
|
|
3962
|
-
dateInput.addEventListener("change", () => {
|
|
3963
|
-
date = dateInput.value;
|
|
3893
|
+
const dateField = this.dateField(id, date, (v) => {
|
|
3894
|
+
date = v;
|
|
3964
3895
|
commit();
|
|
3965
3896
|
});
|
|
3966
|
-
const timeSel = this.
|
|
3897
|
+
const timeSel = this.timeCombobox(`${id}-time`, time, 86, (v) => {
|
|
3967
3898
|
time = v;
|
|
3968
3899
|
commit();
|
|
3969
3900
|
});
|
|
3970
|
-
wrap.append(
|
|
3901
|
+
wrap.append(dateField, timeSel);
|
|
3971
3902
|
return wrap;
|
|
3972
3903
|
}
|
|
3973
3904
|
/** `input.text_area` → a multi-line textarea, committed on blur. */
|
|
@@ -3986,9 +3917,261 @@ var InputsUI = class {
|
|
|
3986
3917
|
});
|
|
3987
3918
|
return ta;
|
|
3988
3919
|
}
|
|
3989
|
-
/** A fixed-width HH:MM
|
|
3990
|
-
|
|
3991
|
-
|
|
3920
|
+
/** A fixed-width HH:MM combobox (typeable, plus a 30-minute-step dropdown) used by session + time. */
|
|
3921
|
+
timeCombobox(id, current, width, onChange) {
|
|
3922
|
+
const wrap = document.createElement("div");
|
|
3923
|
+
wrap.className = "vela-ind-combo";
|
|
3924
|
+
wrap.style.cssText = `position:relative;width:${width}px;`;
|
|
3925
|
+
const input = document.createElement("input");
|
|
3926
|
+
input.type = "text";
|
|
3927
|
+
input.id = id;
|
|
3928
|
+
input.value = current;
|
|
3929
|
+
input.autocomplete = "off";
|
|
3930
|
+
input.spellcheck = false;
|
|
3931
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
3932
|
+
const chevron = document.createElement("button");
|
|
3933
|
+
chevron.type = "button";
|
|
3934
|
+
chevron.tabIndex = -1;
|
|
3935
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
3936
|
+
chevron.setAttribute("aria-label", "Open time list");
|
|
3937
|
+
chevron.innerHTML = CLOCK_SVG;
|
|
3938
|
+
let value = current;
|
|
3939
|
+
const commitTyped = () => {
|
|
3940
|
+
const next = normalizeTimeInput(input.value);
|
|
3941
|
+
if (!next) {
|
|
3942
|
+
input.value = value;
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
input.value = next;
|
|
3946
|
+
if (next !== value) {
|
|
3947
|
+
value = next;
|
|
3948
|
+
onChange(next);
|
|
3949
|
+
}
|
|
3950
|
+
};
|
|
3951
|
+
input.addEventListener("blur", () => {
|
|
3952
|
+
if (isPopoverOpen()) return;
|
|
3953
|
+
commitTyped();
|
|
3954
|
+
});
|
|
3955
|
+
const pick = (v) => {
|
|
3956
|
+
value = v;
|
|
3957
|
+
input.value = v;
|
|
3958
|
+
onChange(v);
|
|
3959
|
+
};
|
|
3960
|
+
const openList = () => {
|
|
3961
|
+
this.choicePop = toggleSelectList(wrap, TIME_OPTIONS, value, pick, {
|
|
3962
|
+
...this.listPopover(),
|
|
3963
|
+
onClose: () => {
|
|
3964
|
+
this.choicePop = null;
|
|
3965
|
+
}
|
|
3966
|
+
});
|
|
3967
|
+
};
|
|
3968
|
+
input.addEventListener("keydown", (e) => {
|
|
3969
|
+
if (e.key === "Enter") {
|
|
3970
|
+
e.preventDefault();
|
|
3971
|
+
commitTyped();
|
|
3972
|
+
input.blur();
|
|
3973
|
+
}
|
|
3974
|
+
if (e.key === "ArrowDown") {
|
|
3975
|
+
e.preventDefault();
|
|
3976
|
+
openList();
|
|
3977
|
+
}
|
|
3978
|
+
});
|
|
3979
|
+
const open = (e) => {
|
|
3980
|
+
e.preventDefault();
|
|
3981
|
+
e.stopPropagation();
|
|
3982
|
+
openList();
|
|
3983
|
+
};
|
|
3984
|
+
input.addEventListener("click", open);
|
|
3985
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
3986
|
+
chevron.addEventListener("click", open);
|
|
3987
|
+
wrap.append(input, chevron);
|
|
3988
|
+
return wrap;
|
|
3989
|
+
}
|
|
3990
|
+
/** A typeable YYYY-MM-DD field that also opens a themed month calendar. */
|
|
3991
|
+
dateField(id, current, onChange) {
|
|
3992
|
+
const wrap = document.createElement("div");
|
|
3993
|
+
wrap.className = "vela-ind-combo";
|
|
3994
|
+
wrap.style.cssText = "position:relative;width:128px;";
|
|
3995
|
+
const input = document.createElement("input");
|
|
3996
|
+
input.type = "text";
|
|
3997
|
+
input.id = id;
|
|
3998
|
+
input.value = current;
|
|
3999
|
+
input.autocomplete = "off";
|
|
4000
|
+
input.spellcheck = false;
|
|
4001
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
4002
|
+
const chevron = document.createElement("button");
|
|
4003
|
+
chevron.type = "button";
|
|
4004
|
+
chevron.tabIndex = -1;
|
|
4005
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
4006
|
+
chevron.setAttribute("aria-label", "Open calendar");
|
|
4007
|
+
chevron.innerHTML = CALENDAR_SVG;
|
|
4008
|
+
let value = current;
|
|
4009
|
+
const commitTyped = () => {
|
|
4010
|
+
const next = normalizeDateInput(input.value);
|
|
4011
|
+
if (!next) {
|
|
4012
|
+
input.value = value;
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
input.value = next;
|
|
4016
|
+
if (next !== value) {
|
|
4017
|
+
value = next;
|
|
4018
|
+
onChange(next);
|
|
4019
|
+
}
|
|
4020
|
+
};
|
|
4021
|
+
input.addEventListener("blur", () => {
|
|
4022
|
+
if (isPopoverOpen()) return;
|
|
4023
|
+
commitTyped();
|
|
4024
|
+
});
|
|
4025
|
+
const pick = (v) => {
|
|
4026
|
+
value = v;
|
|
4027
|
+
input.value = v;
|
|
4028
|
+
onChange(v);
|
|
4029
|
+
};
|
|
4030
|
+
input.addEventListener("keydown", (e) => {
|
|
4031
|
+
if (e.key === "Enter") {
|
|
4032
|
+
e.preventDefault();
|
|
4033
|
+
commitTyped();
|
|
4034
|
+
input.blur();
|
|
4035
|
+
}
|
|
4036
|
+
if (e.key === "ArrowDown") {
|
|
4037
|
+
e.preventDefault();
|
|
4038
|
+
this.openCalendar(wrap, value, pick);
|
|
4039
|
+
}
|
|
4040
|
+
});
|
|
4041
|
+
const open = (e) => {
|
|
4042
|
+
e.preventDefault();
|
|
4043
|
+
e.stopPropagation();
|
|
4044
|
+
if (openPopoverTrigger() === wrap) {
|
|
4045
|
+
closeOpenPopovers();
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
this.openCalendar(wrap, value, pick);
|
|
4049
|
+
};
|
|
4050
|
+
input.addEventListener("click", open);
|
|
4051
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
4052
|
+
chevron.addEventListener("click", open);
|
|
4053
|
+
wrap.append(input, chevron);
|
|
4054
|
+
return wrap;
|
|
4055
|
+
}
|
|
4056
|
+
/** Numeric field with hover steppers on the right (no fill until a triangle is hovered). */
|
|
4057
|
+
buildNumber(id, current, inp, emit) {
|
|
4058
|
+
return new NumberInput({
|
|
4059
|
+
id,
|
|
4060
|
+
value: current,
|
|
4061
|
+
min: inp.min,
|
|
4062
|
+
max: inp.max,
|
|
4063
|
+
step: inp.step ?? (inp.type === "int" ? 1 : 0.1),
|
|
4064
|
+
integer: inp.type === "int",
|
|
4065
|
+
size: "md",
|
|
4066
|
+
commit: "blur",
|
|
4067
|
+
onChange: emit
|
|
4068
|
+
}).el;
|
|
4069
|
+
}
|
|
4070
|
+
/** Placement shared by the indicator dialog's dropdowns and the time combobox. */
|
|
4071
|
+
listPopover() {
|
|
4072
|
+
return {
|
|
4073
|
+
theme: this.theme,
|
|
4074
|
+
matchWidth: true,
|
|
4075
|
+
boundary: this.dialog ?? "viewport",
|
|
4076
|
+
boundaryInset: 8,
|
|
4077
|
+
gap: 4
|
|
4078
|
+
};
|
|
4079
|
+
}
|
|
4080
|
+
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
4081
|
+
openCalendar(anchor, current, onPick) {
|
|
4082
|
+
const parsed = parseIsoDate(current);
|
|
4083
|
+
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
4084
|
+
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
4085
|
+
const selected = parsed ? isoDate(parsed) : current;
|
|
4086
|
+
const pop = new Popover({
|
|
4087
|
+
trigger: anchor,
|
|
4088
|
+
theme: this.theme,
|
|
4089
|
+
className: "vela-ind-cal",
|
|
4090
|
+
boundary: this.dialog ?? "viewport",
|
|
4091
|
+
boundaryInset: 8,
|
|
4092
|
+
gap: 4,
|
|
4093
|
+
onClose: () => {
|
|
4094
|
+
this.calendarPop = null;
|
|
4095
|
+
this.calendarAnchor = null;
|
|
4096
|
+
},
|
|
4097
|
+
content: (el) => {
|
|
4098
|
+
const title = document.createElement("div");
|
|
4099
|
+
title.className = "vela-ind-cal-title";
|
|
4100
|
+
const prev2 = document.createElement("button");
|
|
4101
|
+
prev2.type = "button";
|
|
4102
|
+
prev2.className = "vela-ind-cal-nav";
|
|
4103
|
+
prev2.setAttribute("aria-label", "Previous month");
|
|
4104
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
4105
|
+
const next = document.createElement("button");
|
|
4106
|
+
next.type = "button";
|
|
4107
|
+
next.className = "vela-ind-cal-nav";
|
|
4108
|
+
next.setAttribute("aria-label", "Next month");
|
|
4109
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
4110
|
+
const head = document.createElement("div");
|
|
4111
|
+
head.className = "vela-ind-cal-head";
|
|
4112
|
+
head.append(prev2, title, next);
|
|
4113
|
+
const week = document.createElement("div");
|
|
4114
|
+
week.className = "vela-ind-cal-week";
|
|
4115
|
+
for (const d of WEEKDAY_LABELS) {
|
|
4116
|
+
const cell = document.createElement("span");
|
|
4117
|
+
cell.textContent = d;
|
|
4118
|
+
week.appendChild(cell);
|
|
4119
|
+
}
|
|
4120
|
+
const grid = document.createElement("div");
|
|
4121
|
+
grid.className = "vela-ind-cal-grid";
|
|
4122
|
+
const paint = () => {
|
|
4123
|
+
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
4124
|
+
grid.replaceChildren();
|
|
4125
|
+
const first = new Date(year, month, 1);
|
|
4126
|
+
const startPad = first.getDay();
|
|
4127
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
4128
|
+
const today = isoDate(/* @__PURE__ */ new Date());
|
|
4129
|
+
for (let i = 0; i < startPad; i++) {
|
|
4130
|
+
const blank = document.createElement("span");
|
|
4131
|
+
blank.className = "vela-ind-cal-blank";
|
|
4132
|
+
grid.appendChild(blank);
|
|
4133
|
+
}
|
|
4134
|
+
for (let day = 1; day <= days; day++) {
|
|
4135
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
4136
|
+
const b = document.createElement("button");
|
|
4137
|
+
b.type = "button";
|
|
4138
|
+
b.className = "vela-ind-cal-day";
|
|
4139
|
+
b.textContent = String(day);
|
|
4140
|
+
if (iso === selected) b.dataset.checked = "1";
|
|
4141
|
+
if (iso === today) b.dataset.today = "1";
|
|
4142
|
+
b.addEventListener("click", (e) => {
|
|
4143
|
+
e.stopPropagation();
|
|
4144
|
+
pop.hide();
|
|
4145
|
+
onPick(iso);
|
|
4146
|
+
});
|
|
4147
|
+
grid.appendChild(b);
|
|
4148
|
+
}
|
|
4149
|
+
};
|
|
4150
|
+
prev2.addEventListener("click", (e) => {
|
|
4151
|
+
e.stopPropagation();
|
|
4152
|
+
month -= 1;
|
|
4153
|
+
if (month < 0) {
|
|
4154
|
+
month = 11;
|
|
4155
|
+
year -= 1;
|
|
4156
|
+
}
|
|
4157
|
+
paint();
|
|
4158
|
+
});
|
|
4159
|
+
next.addEventListener("click", (e) => {
|
|
4160
|
+
e.stopPropagation();
|
|
4161
|
+
month += 1;
|
|
4162
|
+
if (month > 11) {
|
|
4163
|
+
month = 0;
|
|
4164
|
+
year += 1;
|
|
4165
|
+
}
|
|
4166
|
+
paint();
|
|
4167
|
+
});
|
|
4168
|
+
paint();
|
|
4169
|
+
el.append(head, week, grid);
|
|
4170
|
+
}
|
|
4171
|
+
});
|
|
4172
|
+
this.calendarPop = pop;
|
|
4173
|
+
this.calendarAnchor = anchor;
|
|
4174
|
+
pop.show();
|
|
3992
4175
|
}
|
|
3993
4176
|
/**
|
|
3994
4177
|
* `input.symbol` → a field that opens the host's ticker-selection UI when a picker is wired
|
|
@@ -4016,7 +4199,7 @@ var InputsUI = class {
|
|
|
4016
4199
|
}
|
|
4017
4200
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
4018
4201
|
ctrlStyle() {
|
|
4019
|
-
return `width:100%;box-sizing:border-box;height:
|
|
4202
|
+
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;`;
|
|
4020
4203
|
}
|
|
4021
4204
|
/** Whether the active theme is dark (drives the dialog's `color-scheme`). */
|
|
4022
4205
|
isDarkTheme() {
|
|
@@ -4035,21 +4218,6 @@ var InputsUI = class {
|
|
|
4035
4218
|
strongText() {
|
|
4036
4219
|
return "var(--vela-fg-bright)";
|
|
4037
4220
|
}
|
|
4038
|
-
/**
|
|
4039
|
-
* Paint a `<select>` + its `<option>`s so the native dropdown popup matches the dialog: the
|
|
4040
|
-
* option list's background is the dialog color and its text is the strong foreground. `color-scheme`
|
|
4041
|
-
* alone doesn't theme the popup in every embedded browser (some render it white with white text —
|
|
4042
|
-
* invisible), so the colors are set explicitly here.
|
|
4043
|
-
*/
|
|
4044
|
-
styleSelect(sel) {
|
|
4045
|
-
const bg = this.theme.background;
|
|
4046
|
-
const fg = this.strongText();
|
|
4047
|
-
sel.style.backgroundColor = bg;
|
|
4048
|
-
for (const o of Array.from(sel.options)) {
|
|
4049
|
-
o.style.backgroundColor = bg;
|
|
4050
|
-
o.style.color = fg;
|
|
4051
|
-
}
|
|
4052
|
-
}
|
|
4053
4221
|
dialogButton(label, primary, onClick) {
|
|
4054
4222
|
const b = document.createElement("button");
|
|
4055
4223
|
b.type = "button";
|
|
@@ -4063,6 +4231,21 @@ function nameOf(inp) {
|
|
|
4063
4231
|
const t = inp.title;
|
|
4064
4232
|
return t.length > 0 ? t.charAt(0).toUpperCase() + t.slice(1) : "";
|
|
4065
4233
|
}
|
|
4234
|
+
var DEFAULT_INPUT_TAB = "Inputs";
|
|
4235
|
+
function tabInputs(inputs) {
|
|
4236
|
+
const byTab = /* @__PURE__ */ new Map();
|
|
4237
|
+
for (const inp of inputs) {
|
|
4238
|
+
const name = inp.tab && inp.tab.length > 0 ? inp.tab : DEFAULT_INPUT_TAB;
|
|
4239
|
+
if (!byTab.has(name)) byTab.set(name, []);
|
|
4240
|
+
byTab.get(name).push(inp);
|
|
4241
|
+
}
|
|
4242
|
+
const names = [...byTab.keys()];
|
|
4243
|
+
if (names.includes(DEFAULT_INPUT_TAB)) {
|
|
4244
|
+
names.splice(names.indexOf(DEFAULT_INPUT_TAB), 1);
|
|
4245
|
+
names.unshift(DEFAULT_INPUT_TAB);
|
|
4246
|
+
}
|
|
4247
|
+
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
4248
|
+
}
|
|
4066
4249
|
function groupInputs(inputs) {
|
|
4067
4250
|
const order = [];
|
|
4068
4251
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -4091,9 +4274,10 @@ function groupInputs(inputs) {
|
|
|
4091
4274
|
});
|
|
4092
4275
|
}
|
|
4093
4276
|
var CHECK_SVG = iconAt("check", 12);
|
|
4094
|
-
var
|
|
4277
|
+
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
4278
|
+
var CLOCK_SVG = iconAt("clock", 14);
|
|
4095
4279
|
var DIALOG_STYLE_ID = "vela-ind-dialog-styles";
|
|
4096
|
-
var DIALOG_STYLE_REV = "
|
|
4280
|
+
var DIALOG_STYLE_REV = "25";
|
|
4097
4281
|
function ensureDialogStyles() {
|
|
4098
4282
|
if (typeof document === "undefined") return;
|
|
4099
4283
|
const existing = document.getElementById(DIALOG_STYLE_ID);
|
|
@@ -4102,30 +4286,47 @@ function ensureDialogStyles() {
|
|
|
4102
4286
|
s.id = DIALOG_STYLE_ID;
|
|
4103
4287
|
s.dataset.rev = DIALOG_STYLE_REV;
|
|
4104
4288
|
s.textContent = `
|
|
4105
|
-
.vela-ind-dialog input[type=
|
|
4106
|
-
.vela-ind-dialog input[type=
|
|
4107
|
-
.vela-ind-dialog input[type=
|
|
4108
|
-
.vela-ind-
|
|
4109
|
-
.vela-ind-
|
|
4110
|
-
.vela-ind-
|
|
4111
|
-
.vela-ind-
|
|
4112
|
-
.vela-ind-
|
|
4289
|
+
.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;}
|
|
4290
|
+
.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);}
|
|
4291
|
+
.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);}
|
|
4292
|
+
.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;}
|
|
4293
|
+
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
4294
|
+
.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;}
|
|
4295
|
+
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
4296
|
+
.vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
|
|
4297
|
+
.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;}
|
|
4298
|
+
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
4299
|
+
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
4300
|
+
.vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
|
|
4301
|
+
.vela-ind-cal-week span{line-height:20px;}
|
|
4302
|
+
.vela-ind-cal-blank{width:28px;height:28px;}
|
|
4303
|
+
.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;}
|
|
4304
|
+
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
4305
|
+
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
4306
|
+
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
4307
|
+
.vela-ind-dialog ::-webkit-scrollbar{width:9px;height:9px;}
|
|
4113
4308
|
.vela-ind-dialog ::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}
|
|
4114
4309
|
.vela-ind-dialog ::-webkit-scrollbar-track{background:transparent;}
|
|
4310
|
+
.vela-ind-dialog ::-webkit-scrollbar-button{display:none;width:0;height:0;}
|
|
4311
|
+
.vela-ind-tab{transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
4312
|
+
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
4115
4313
|
.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;}
|
|
4116
4314
|
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
4117
4315
|
.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;}
|
|
4118
4316
|
.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;}
|
|
4119
4317
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
4120
4318
|
.vela-ind-close:hover{color:var(--vela-danger) !important;background-color:var(--vela-hover-strong);}
|
|
4319
|
+
.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;}
|
|
4320
|
+
.vela-ind-dlg-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
4321
|
+
.vela-ind-dlg-close svg{width:15px;height:15px;display:block;flex:none;}
|
|
4121
4322
|
.vela-ind-fold{transition:border-color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
|
|
4122
4323
|
.vela-ind-fold:hover{border-color:var(--vela-border-strong);opacity:0.9;}
|
|
4123
4324
|
.vela-ind-menuitem{background:transparent;transition:background var(--vela-dur-fast) ease;}
|
|
4124
4325
|
.vela-ind-menuitem:hover{background:var(--vela-hover-strong);}
|
|
4125
|
-
.vela-ind-btn{cursor:pointer;padding:
|
|
4126
|
-
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
4127
|
-
.vela-ind-btn-primary{
|
|
4128
|
-
.vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;}`;
|
|
4326
|
+
.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;}
|
|
4327
|
+
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);border-color:var(--vela-fg-muted);}
|
|
4328
|
+
.vela-ind-btn-primary{border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
4329
|
+
.vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;border-color:var(--vela-selected-bg);}`;
|
|
4129
4330
|
if (!existing) document.head.appendChild(s);
|
|
4130
4331
|
}
|
|
4131
4332
|
var TIMEFRAME_OPTIONS = [
|
|
@@ -4150,6 +4351,37 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
4150
4351
|
const v = `${hh}:${mm}`;
|
|
4151
4352
|
return { value: v, label: v };
|
|
4152
4353
|
});
|
|
4354
|
+
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
4355
|
+
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
4356
|
+
function normalizeDateInput(raw) {
|
|
4357
|
+
const t = raw.trim();
|
|
4358
|
+
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
|
|
4359
|
+
if (!m) return null;
|
|
4360
|
+
const y = Number(m[1]);
|
|
4361
|
+
const mo = Number(m[2]);
|
|
4362
|
+
const d = Number(m[3]);
|
|
4363
|
+
const dt = new Date(y, mo - 1, d);
|
|
4364
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
|
|
4365
|
+
return isoDate(dt);
|
|
4366
|
+
}
|
|
4367
|
+
function parseIsoDate(raw) {
|
|
4368
|
+
const iso = normalizeDateInput(raw);
|
|
4369
|
+
if (!iso) return null;
|
|
4370
|
+
const [y, mo, d] = iso.split("-").map(Number);
|
|
4371
|
+
return new Date(y, mo - 1, d);
|
|
4372
|
+
}
|
|
4373
|
+
function isoDate(d) {
|
|
4374
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
4375
|
+
}
|
|
4376
|
+
function normalizeTimeInput(raw) {
|
|
4377
|
+
const t = raw.trim();
|
|
4378
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
4379
|
+
if (!m) return null;
|
|
4380
|
+
const hh = Number(m[1]);
|
|
4381
|
+
const mm = Number(m[2]);
|
|
4382
|
+
if (hh > 23 || mm > 59) return null;
|
|
4383
|
+
return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
|
|
4384
|
+
}
|
|
4153
4385
|
function sessionToTimes(session) {
|
|
4154
4386
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(session.trim());
|
|
4155
4387
|
if (!m) return ["09:00", "16:00"];
|
|
@@ -6969,7 +7201,7 @@ var TOUCH_TAP_SLOP = 14;
|
|
|
6969
7201
|
var DOUBLE_TAP_MS = 350;
|
|
6970
7202
|
var DOUBLE_TAP_SLOP = 30;
|
|
6971
7203
|
var TIME_SCALE_K = 4e-3;
|
|
6972
|
-
var WHEEL_ZOOM_K =
|
|
7204
|
+
var WHEEL_ZOOM_K = 4e-3;
|
|
6973
7205
|
function wheelZoomAnchor(coords, cursorX, rightEdge) {
|
|
6974
7206
|
if (rightEdge) return { logical: coords.rightEdgeLogical, x: coords.width };
|
|
6975
7207
|
return { logical: coords.xToLogical(cursorX), x: cursorX };
|
|
@@ -7023,6 +7255,8 @@ var InputController = class {
|
|
|
7023
7255
|
// smoothed pointer velocity, px/ms
|
|
7024
7256
|
this.middleDeleted = false;
|
|
7025
7257
|
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
7258
|
+
this.rightCancelled = false;
|
|
7259
|
+
// the last right press cancelled a placement (suppress the context menu)
|
|
7026
7260
|
// Last cursor position over the element (NaN once it leaves) — lets a modifier
|
|
7027
7261
|
// press/release re-shape the drawings preview with the pointer stationary.
|
|
7028
7262
|
this.cursorX = NaN;
|
|
@@ -7047,6 +7281,14 @@ var InputController = class {
|
|
|
7047
7281
|
this.onMiddleGuard = (e) => {
|
|
7048
7282
|
if (e.button === 1 && this.middleDeleted) e.preventDefault();
|
|
7049
7283
|
};
|
|
7284
|
+
/** A right press that cancelled a placement must not ALSO open the host's chart
|
|
7285
|
+
* context menu — swallow its companion event before it bubbles to the host. */
|
|
7286
|
+
this.onContextGuard = (e) => {
|
|
7287
|
+
if (!this.rightCancelled) return;
|
|
7288
|
+
this.rightCancelled = false;
|
|
7289
|
+
e.preventDefault();
|
|
7290
|
+
e.stopPropagation();
|
|
7291
|
+
};
|
|
7050
7292
|
/** A modifier press/release with the pointer stationary: re-issue the last cursor
|
|
7051
7293
|
* position so the placing ghost / drag preview reflects the new state immediately
|
|
7052
7294
|
* (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
|
|
@@ -7063,6 +7305,11 @@ var InputController = class {
|
|
|
7063
7305
|
if (this.middleDeleted) e.preventDefault();
|
|
7064
7306
|
return;
|
|
7065
7307
|
}
|
|
7308
|
+
if (e.button === 2) {
|
|
7309
|
+
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
7310
|
+
if (this.rightCancelled) e.preventDefault();
|
|
7311
|
+
return;
|
|
7312
|
+
}
|
|
7066
7313
|
if (e.button !== 0) return;
|
|
7067
7314
|
const { x, y } = this.local(e);
|
|
7068
7315
|
if (e.pointerType === "touch") {
|
|
@@ -7267,6 +7514,7 @@ var InputController = class {
|
|
|
7267
7514
|
el.addEventListener("wheel", this.onWheel, { passive: false });
|
|
7268
7515
|
el.addEventListener("mousedown", this.onMiddleGuard);
|
|
7269
7516
|
el.addEventListener("auxclick", this.onMiddleGuard);
|
|
7517
|
+
el.addEventListener("contextmenu", this.onContextGuard);
|
|
7270
7518
|
const win = el.ownerDocument?.defaultView;
|
|
7271
7519
|
win?.addEventListener("keydown", this.onModifier);
|
|
7272
7520
|
win?.addEventListener("keyup", this.onModifier);
|
|
@@ -7288,6 +7536,7 @@ var InputController = class {
|
|
|
7288
7536
|
el.removeEventListener("wheel", this.onWheel);
|
|
7289
7537
|
el.removeEventListener("mousedown", this.onMiddleGuard);
|
|
7290
7538
|
el.removeEventListener("auxclick", this.onMiddleGuard);
|
|
7539
|
+
el.removeEventListener("contextmenu", this.onContextGuard);
|
|
7291
7540
|
this.cancelLongPress();
|
|
7292
7541
|
this.touches.clear();
|
|
7293
7542
|
this.el = null;
|
|
@@ -8239,6 +8488,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
|
|
|
8239
8488
|
const rx = right ? width + 2 : Math.max(x1, x2);
|
|
8240
8489
|
return [lx, yAt(lx), rx, yAt(rx)];
|
|
8241
8490
|
}
|
|
8491
|
+
function uprightLineAngle(x1, y1, x2, y2) {
|
|
8492
|
+
let a = Math.atan2(y2 - y1, x2 - x1);
|
|
8493
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
8494
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
8495
|
+
return a;
|
|
8496
|
+
}
|
|
8242
8497
|
function parseColor2(c) {
|
|
8243
8498
|
const s = c.trim();
|
|
8244
8499
|
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
@@ -8960,8 +9215,11 @@ var ChromeRenderer = class {
|
|
|
8960
9215
|
}
|
|
8961
9216
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
8962
9217
|
ctx.textBaseline = "middle";
|
|
8963
|
-
if (coords.barCount === 0) return;
|
|
8964
9218
|
const panes = scene.orderedPanes();
|
|
9219
|
+
if (coords.barCount === 0) {
|
|
9220
|
+
this.drawPaneSeparators(ctx, scene, theme, fullW, panes);
|
|
9221
|
+
return;
|
|
9222
|
+
}
|
|
8965
9223
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
8966
9224
|
for (const pane of panes) {
|
|
8967
9225
|
if (pane.collapsed) continue;
|
|
@@ -9409,367 +9667,71 @@ function formatStamp(ms, timeZone) {
|
|
|
9409
9667
|
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
9410
9668
|
}
|
|
9411
9669
|
|
|
9412
|
-
// src/renderers/native/drawings/colorPicker.ts
|
|
9413
|
-
function splitColor(color) {
|
|
9414
|
-
const s = String(color ?? "").trim();
|
|
9415
|
-
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
9416
|
-
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
9417
|
-
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
9418
|
-
if (m) {
|
|
9419
|
-
const h = m[1].toLowerCase();
|
|
9420
|
-
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
9421
|
-
}
|
|
9422
|
-
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
9423
|
-
if (rgba) {
|
|
9424
|
-
const p = rgba[1].split(",").map((v) => v.trim());
|
|
9425
|
-
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
9426
|
-
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
9427
|
-
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
9428
|
-
}
|
|
9429
|
-
return { hex6: DEFAULT_DRAWING_COLOR, alpha: 1 };
|
|
9430
|
-
}
|
|
9431
|
-
function combineColor(hex6, alpha) {
|
|
9432
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
9433
|
-
if (a >= 0.999) return hex6;
|
|
9434
|
-
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
9435
|
-
}
|
|
9436
|
-
function blendOver(fg, bg, alpha) {
|
|
9437
|
-
const rgb = (c) => {
|
|
9438
|
-
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
9439
|
-
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
9440
|
-
};
|
|
9441
|
-
const [fr, fgc, fb] = rgb(fg);
|
|
9442
|
-
const [br, bgc, bb] = rgb(bg);
|
|
9443
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
9444
|
-
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
9445
|
-
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
9446
|
-
}
|
|
9447
|
-
function hslHex(h, s, l) {
|
|
9448
|
-
const sn = s / 100;
|
|
9449
|
-
const ln = l / 100;
|
|
9450
|
-
const k = (n) => (n + h / 30) % 12;
|
|
9451
|
-
const a = sn * Math.min(ln, 1 - ln);
|
|
9452
|
-
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
9453
|
-
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
9454
|
-
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
9455
|
-
}
|
|
9456
|
-
function buildPalette() {
|
|
9457
|
-
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
9458
|
-
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
9459
|
-
const levels = [
|
|
9460
|
-
[82, 56],
|
|
9461
|
-
// vivid
|
|
9462
|
-
[58, 84],
|
|
9463
|
-
// light pastel
|
|
9464
|
-
[62, 74],
|
|
9465
|
-
[66, 62],
|
|
9466
|
-
[70, 50],
|
|
9467
|
-
[64, 39],
|
|
9468
|
-
[54, 29]
|
|
9469
|
-
// dark
|
|
9470
|
-
];
|
|
9471
|
-
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
9472
|
-
}
|
|
9473
|
-
var PALETTE = buildPalette();
|
|
9474
|
-
function transparencyChecker(size) {
|
|
9475
|
-
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
9476
|
-
}
|
|
9477
|
-
var CHECKER = transparencyChecker(10);
|
|
9478
|
-
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
9479
|
-
function addRecent(hex6) {
|
|
9480
|
-
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
9481
|
-
if (i >= 0) recents.splice(i, 1);
|
|
9482
|
-
recents.unshift(hex6);
|
|
9483
|
-
if (recents.length > 10) recents.length = 10;
|
|
9484
|
-
}
|
|
9485
|
-
function buildColorPicker(color, theme, onChange) {
|
|
9486
|
-
const parsed = splitColor(color);
|
|
9487
|
-
let curHex = parsed.hex6;
|
|
9488
|
-
let curAlpha = parsed.alpha;
|
|
9489
|
-
const root = document.createElement("div");
|
|
9490
|
-
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
9491
|
-
const grid = document.createElement("div");
|
|
9492
|
-
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
9493
|
-
const swatches = [];
|
|
9494
|
-
for (const row of PALETTE) {
|
|
9495
|
-
for (const c of row) {
|
|
9496
|
-
const sw = document.createElement("button");
|
|
9497
|
-
sw.type = "button";
|
|
9498
|
-
sw.dataset.c = c;
|
|
9499
|
-
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
9500
|
-
sw.addEventListener("click", (e) => {
|
|
9501
|
-
e.stopPropagation();
|
|
9502
|
-
pickHex(c);
|
|
9503
|
-
});
|
|
9504
|
-
grid.appendChild(sw);
|
|
9505
|
-
swatches.push(sw);
|
|
9506
|
-
}
|
|
9507
|
-
}
|
|
9508
|
-
const paintSelection = () => {
|
|
9509
|
-
for (const sw of swatches) {
|
|
9510
|
-
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
9511
|
-
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
9512
|
-
sw.style.outlineOffset = "2px";
|
|
9513
|
-
sw.style.zIndex = on ? "1" : "";
|
|
9514
|
-
sw.style.position = on ? "relative" : "";
|
|
9515
|
-
}
|
|
9516
|
-
};
|
|
9517
|
-
const recentRow = document.createElement("div");
|
|
9518
|
-
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
9519
|
-
const renderRecents = () => {
|
|
9520
|
-
recentRow.replaceChildren();
|
|
9521
|
-
for (const c of recents) {
|
|
9522
|
-
const sw = document.createElement("button");
|
|
9523
|
-
sw.type = "button";
|
|
9524
|
-
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;`;
|
|
9525
|
-
sw.addEventListener("click", (e) => {
|
|
9526
|
-
e.stopPropagation();
|
|
9527
|
-
pickHex(c);
|
|
9528
|
-
});
|
|
9529
|
-
recentRow.appendChild(sw);
|
|
9530
|
-
}
|
|
9531
|
-
const add = document.createElement("label");
|
|
9532
|
-
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;`;
|
|
9533
|
-
add.textContent = "+";
|
|
9534
|
-
const input = document.createElement("input");
|
|
9535
|
-
input.type = "color";
|
|
9536
|
-
input.value = curHex;
|
|
9537
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
9538
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
9539
|
-
add.appendChild(input);
|
|
9540
|
-
recentRow.appendChild(add);
|
|
9541
|
-
};
|
|
9542
|
-
const opLabel = document.createElement("div");
|
|
9543
|
-
opLabel.textContent = "Opacity";
|
|
9544
|
-
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
9545
|
-
const opRow = document.createElement("div");
|
|
9546
|
-
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
9547
|
-
const track = document.createElement("div");
|
|
9548
|
-
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
9549
|
-
const knob = document.createElement("div");
|
|
9550
|
-
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;";
|
|
9551
|
-
track.appendChild(knob);
|
|
9552
|
-
const pctBox = document.createElement("div");
|
|
9553
|
-
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;`;
|
|
9554
|
-
opRow.append(track, pctBox);
|
|
9555
|
-
const paintOpacity = () => {
|
|
9556
|
-
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
9557
|
-
knob.style.left = `${curAlpha * 100}%`;
|
|
9558
|
-
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
9559
|
-
};
|
|
9560
|
-
let dragging = false;
|
|
9561
|
-
const onDrag = (clientX) => {
|
|
9562
|
-
const r = track.getBoundingClientRect();
|
|
9563
|
-
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
9564
|
-
paintOpacity();
|
|
9565
|
-
emit();
|
|
9566
|
-
};
|
|
9567
|
-
track.addEventListener("pointerdown", (e) => {
|
|
9568
|
-
e.stopPropagation();
|
|
9569
|
-
dragging = true;
|
|
9570
|
-
track.setPointerCapture(e.pointerId);
|
|
9571
|
-
onDrag(e.clientX);
|
|
9572
|
-
});
|
|
9573
|
-
track.addEventListener("pointermove", (e) => {
|
|
9574
|
-
if (dragging) onDrag(e.clientX);
|
|
9575
|
-
});
|
|
9576
|
-
track.addEventListener("pointerup", () => dragging = false);
|
|
9577
|
-
function emit() {
|
|
9578
|
-
onChange(combineColor(curHex, curAlpha));
|
|
9579
|
-
}
|
|
9580
|
-
function pickHex(hex, custom = false) {
|
|
9581
|
-
curHex = splitColor(hex).hex6;
|
|
9582
|
-
addRecent(curHex);
|
|
9583
|
-
paintSelection();
|
|
9584
|
-
paintOpacity();
|
|
9585
|
-
if (custom) renderRecents();
|
|
9586
|
-
emit();
|
|
9587
|
-
}
|
|
9588
|
-
renderRecents();
|
|
9589
|
-
paintSelection();
|
|
9590
|
-
paintOpacity();
|
|
9591
|
-
root.append(grid, recentRow, opLabel, opRow);
|
|
9592
|
-
return root;
|
|
9593
|
-
}
|
|
9594
|
-
|
|
9595
|
-
// src/renderers/native/chrome/ColorField.ts
|
|
9596
|
-
var CHECKER2 = transparencyChecker(8);
|
|
9597
|
-
var STYLE_ID3 = "vela-color-field";
|
|
9598
|
-
function ensureStyles3() {
|
|
9599
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
9600
|
-
const st = document.createElement("style");
|
|
9601
|
-
st.id = STYLE_ID3;
|
|
9602
|
-
st.textContent = `
|
|
9603
|
-
.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;}
|
|
9604
|
-
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
9605
|
-
.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);}
|
|
9606
|
-
.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;}
|
|
9607
|
-
`;
|
|
9608
|
-
document.head.appendChild(st);
|
|
9609
|
-
}
|
|
9610
|
-
var openPopover = null;
|
|
9611
|
-
function closeOpen() {
|
|
9612
|
-
if (!openPopover) return;
|
|
9613
|
-
document.removeEventListener("pointerdown", openPopover.onOutside, true);
|
|
9614
|
-
window.removeEventListener("resize", openPopover.reflow, true);
|
|
9615
|
-
openPopover.el.remove();
|
|
9616
|
-
openPopover = null;
|
|
9617
|
-
}
|
|
9618
|
-
function place(pop, trigger) {
|
|
9619
|
-
const r = trigger.getBoundingClientRect();
|
|
9620
|
-
const w = pop.offsetWidth;
|
|
9621
|
-
const h = pop.offsetHeight;
|
|
9622
|
-
let left = r.right - w;
|
|
9623
|
-
let top = r.bottom + 6;
|
|
9624
|
-
if (left < 6) left = 6;
|
|
9625
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
9626
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
9627
|
-
if (top < 6) top = 6;
|
|
9628
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
9629
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
9630
|
-
}
|
|
9631
|
-
function colorField(theme, getVal, onVal) {
|
|
9632
|
-
ensureStyles3();
|
|
9633
|
-
const trigger = document.createElement("button");
|
|
9634
|
-
trigger.type = "button";
|
|
9635
|
-
trigger.className = "vela-color-field";
|
|
9636
|
-
const swatch = document.createElement("span");
|
|
9637
|
-
swatch.className = "vela-color-field-swatch";
|
|
9638
|
-
trigger.appendChild(swatch);
|
|
9639
|
-
const paint = () => {
|
|
9640
|
-
const v = getVal();
|
|
9641
|
-
swatch.style.background = `linear-gradient(${v}, ${v}), ${CHECKER2}`;
|
|
9642
|
-
};
|
|
9643
|
-
paint();
|
|
9644
|
-
trigger.addEventListener("vela-sync", paint);
|
|
9645
|
-
trigger.addEventListener("click", (e) => {
|
|
9646
|
-
e.stopPropagation();
|
|
9647
|
-
if (openPopover && openPopover.trigger === trigger) {
|
|
9648
|
-
closeOpen();
|
|
9649
|
-
return;
|
|
9650
|
-
}
|
|
9651
|
-
closeOpen();
|
|
9652
|
-
const pop = document.createElement("div");
|
|
9653
|
-
pop.className = "vela-color-field-pop";
|
|
9654
|
-
applyChromeTokens(pop, theme);
|
|
9655
|
-
pop.addEventListener("pointerdown", (ev) => ev.stopPropagation());
|
|
9656
|
-
pop.appendChild(
|
|
9657
|
-
buildColorPicker(getVal(), theme, (val) => {
|
|
9658
|
-
onVal(val);
|
|
9659
|
-
paint();
|
|
9660
|
-
})
|
|
9661
|
-
);
|
|
9662
|
-
document.body.appendChild(pop);
|
|
9663
|
-
place(pop, trigger);
|
|
9664
|
-
const onOutside = (ev) => {
|
|
9665
|
-
const t = ev.target;
|
|
9666
|
-
if (pop.contains(t) || trigger.contains(t)) return;
|
|
9667
|
-
closeOpen();
|
|
9668
|
-
};
|
|
9669
|
-
const reflow = () => place(pop, trigger);
|
|
9670
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
9671
|
-
window.addEventListener("resize", reflow, true);
|
|
9672
|
-
openPopover = { el: pop, trigger, onOutside, reflow };
|
|
9673
|
-
});
|
|
9674
|
-
return trigger;
|
|
9675
|
-
}
|
|
9676
|
-
function closeColorPopover() {
|
|
9677
|
-
closeOpen();
|
|
9678
|
-
}
|
|
9679
|
-
|
|
9680
9670
|
// src/renderers/native/chrome/WidthField.ts
|
|
9681
9671
|
var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
|
|
9682
|
-
var
|
|
9683
|
-
|
|
9684
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID4)) return;
|
|
9685
|
-
const st = document.createElement("style");
|
|
9686
|
-
st.id = STYLE_ID4;
|
|
9687
|
-
st.textContent = `
|
|
9672
|
+
var STYLE_ID3 = "vela-width-field";
|
|
9673
|
+
var WIDTH_CSS = `
|
|
9688
9674
|
.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;}
|
|
9689
9675
|
.vela-width-field:hover{border-color:var(--vela-fg-muted);}
|
|
9690
|
-
.vela-width-field-
|
|
9676
|
+
.vela-width-field-caret{display:flex;color:var(--vela-fg-muted);}
|
|
9677
|
+
.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);}
|
|
9691
9678
|
.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;}
|
|
9692
9679
|
.vela-width-field-item:hover{background:var(--vela-hover-strong);}
|
|
9693
9680
|
.vela-width-field-item[data-active='1']{background:var(--vela-active);}
|
|
9694
9681
|
`;
|
|
9695
|
-
document.head.appendChild(st);
|
|
9696
|
-
}
|
|
9697
9682
|
function lineGlyph(width) {
|
|
9698
9683
|
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>`;
|
|
9699
9684
|
}
|
|
9700
|
-
var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="
|
|
9701
|
-
var openPopover2 = null;
|
|
9702
|
-
function closeOpen2() {
|
|
9703
|
-
if (!openPopover2) return;
|
|
9704
|
-
document.removeEventListener("pointerdown", openPopover2.onOutside, true);
|
|
9705
|
-
window.removeEventListener("resize", openPopover2.reflow, true);
|
|
9706
|
-
openPopover2.el.remove();
|
|
9707
|
-
openPopover2 = null;
|
|
9708
|
-
}
|
|
9709
|
-
function place2(pop, trigger) {
|
|
9710
|
-
const r = trigger.getBoundingClientRect();
|
|
9711
|
-
const w = pop.offsetWidth;
|
|
9712
|
-
const h = pop.offsetHeight;
|
|
9713
|
-
let left = r.right - w;
|
|
9714
|
-
let top = r.bottom + 6;
|
|
9715
|
-
if (left < 6) left = 6;
|
|
9716
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
9717
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
9718
|
-
if (top < 6) top = 6;
|
|
9719
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
9720
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
9721
|
-
}
|
|
9685
|
+
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>`;
|
|
9722
9686
|
function widthField(theme, getVal, onVal) {
|
|
9723
|
-
|
|
9687
|
+
injectStyles(STYLE_ID3, WIDTH_CSS, document);
|
|
9724
9688
|
const trigger = document.createElement("button");
|
|
9725
9689
|
trigger.type = "button";
|
|
9726
9690
|
trigger.className = "vela-width-field";
|
|
9727
9691
|
const paint = () => {
|
|
9728
|
-
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span
|
|
9692
|
+
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span class="vela-width-field-caret">${CHEVRON}</span>`;
|
|
9729
9693
|
};
|
|
9730
9694
|
paint();
|
|
9695
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
9696
|
+
e.preventDefault();
|
|
9697
|
+
trigger.focus({ preventScroll: true });
|
|
9698
|
+
});
|
|
9731
9699
|
trigger.addEventListener("click", (e) => {
|
|
9732
9700
|
e.stopPropagation();
|
|
9733
|
-
if (
|
|
9734
|
-
|
|
9701
|
+
if (openPopoverTrigger() === trigger) {
|
|
9702
|
+
closeOpenPopovers();
|
|
9735
9703
|
return;
|
|
9736
9704
|
}
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
closeOpen2();
|
|
9763
|
-
};
|
|
9764
|
-
const reflow = () => place2(pop, trigger);
|
|
9765
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
9766
|
-
window.addEventListener("resize", reflow, true);
|
|
9767
|
-
openPopover2 = { el: pop, trigger, onOutside, reflow };
|
|
9705
|
+
const pop = new Popover({
|
|
9706
|
+
trigger,
|
|
9707
|
+
theme,
|
|
9708
|
+
align: "end",
|
|
9709
|
+
gap: 6,
|
|
9710
|
+
className: "vela-width-field-pop",
|
|
9711
|
+
content: (el) => {
|
|
9712
|
+
el.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
|
|
9713
|
+
for (const w of WIDTH_FIELD_OPTIONS) {
|
|
9714
|
+
const item = document.createElement("button");
|
|
9715
|
+
item.type = "button";
|
|
9716
|
+
item.className = "vela-width-field-item";
|
|
9717
|
+
item.dataset.active = w === getVal() ? "1" : "0";
|
|
9718
|
+
item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
|
|
9719
|
+
item.addEventListener("click", (ev) => {
|
|
9720
|
+
ev.stopPropagation();
|
|
9721
|
+
pop.hide();
|
|
9722
|
+
onVal(w);
|
|
9723
|
+
paint();
|
|
9724
|
+
});
|
|
9725
|
+
el.appendChild(item);
|
|
9726
|
+
}
|
|
9727
|
+
}
|
|
9728
|
+
});
|
|
9729
|
+
pop.show();
|
|
9768
9730
|
});
|
|
9769
9731
|
return trigger;
|
|
9770
9732
|
}
|
|
9771
9733
|
function closeWidthPopover() {
|
|
9772
|
-
|
|
9734
|
+
closeOpenPopovers();
|
|
9773
9735
|
}
|
|
9774
9736
|
|
|
9775
9737
|
// src/renderers/native/chrome/SettingsDialog.ts
|
|
@@ -9784,37 +9746,24 @@ function styleLabel(id) {
|
|
|
9784
9746
|
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS[id] ?? id;
|
|
9785
9747
|
}
|
|
9786
9748
|
var SD_STYLE_ID = "vela-settings-controls";
|
|
9749
|
+
var SD_STYLE_REV = "2";
|
|
9787
9750
|
var SETTINGS_SURFACE = "var(--vela-surface)";
|
|
9788
9751
|
var SETTINGS_BORDER = "var(--vela-border)";
|
|
9789
9752
|
var SETTINGS_TEXT = "var(--vela-fg)";
|
|
9790
9753
|
function ensureControlStyles() {
|
|
9791
|
-
if (typeof document === "undefined"
|
|
9792
|
-
const
|
|
9754
|
+
if (typeof document === "undefined") return;
|
|
9755
|
+
const existing = document.getElementById(SD_STYLE_ID);
|
|
9756
|
+
if (existing?.dataset.rev === SD_STYLE_REV) return;
|
|
9757
|
+
const st = existing ?? document.createElement("style");
|
|
9793
9758
|
st.id = SD_STYLE_ID;
|
|
9759
|
+
st.dataset.rev = SD_STYLE_REV;
|
|
9794
9760
|
st.textContent = `
|
|
9795
|
-
.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;}
|
|
9796
|
-
.vela-sd-check:hover{border-color:var(--vela-fg-muted);}
|
|
9797
|
-
.vela-sd-check.on{background:var(--vela-selected-bg);border-color:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
9798
|
-
.vela-sd-check svg{display:block;}
|
|
9799
|
-
.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;}
|
|
9800
|
-
.vela-sd-select:hover,.vela-sd-number:hover{border-color:var(--vela-fg-muted);}
|
|
9801
|
-
/* Custom caret: the native one hugs the right border; ours gets 8px of air. (A data URI
|
|
9802
|
-
can't read currentColor, so it uses the shared muted-gray ink, legible on both themes.) */
|
|
9803
|
-
.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;}
|
|
9804
|
-
.vela-sd-number{width:64px;}
|
|
9805
|
-
.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;}
|
|
9806
|
-
.vela-sd-color::-webkit-color-swatch-wrapper{padding:2px;}
|
|
9807
|
-
.vela-sd-color::-webkit-color-swatch{border:none;border-radius:2px;}
|
|
9808
9761
|
.vela-sd-pane::-webkit-scrollbar{width:8px;}
|
|
9809
9762
|
.vela-sd-pane::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:var(--vela-radius-sm);border:2px solid transparent;background-clip:padding-box;}
|
|
9810
9763
|
.vela-sd-pane::-webkit-scrollbar-track{background:transparent;}
|
|
9811
|
-
.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;}
|
|
9812
|
-
.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;}
|
|
9813
|
-
.vela-sd-toggle.on{background:var(--vela-active);border-color:var(--vela-selected-bg);}
|
|
9814
|
-
.vela-sd-toggle.on::after{transform:translateX(16px);background:var(--vela-selected-bg);}
|
|
9815
9764
|
/* Tab rail / footer button / header close: base styles live HERE, not inline on the
|
|
9816
9765
|
elements \u2014 inline declarations always beat stylesheet :hover rules, which is exactly
|
|
9817
|
-
what killed these hovers before. Active tab state is the .on class
|
|
9766
|
+
what killed these hovers before. Active tab state is the .on class,
|
|
9818
9767
|
hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
|
|
9819
9768
|
.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;}
|
|
9820
9769
|
.vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
@@ -9873,13 +9822,13 @@ function ensureControlStyles() {
|
|
|
9873
9822
|
.vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
|
|
9874
9823
|
.vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
|
|
9875
9824
|
.vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
|
|
9876
|
-
.vela-sd-mobile .vela-
|
|
9877
|
-
.vela-sd-mobile .vela-
|
|
9825
|
+
.vela-sd-mobile .vela-switch{width:22px;height:22px;}
|
|
9826
|
+
.vela-sd-mobile .vela-select[data-size='sm'] .vela-select-trigger,.vela-sd-mobile .vela-num[data-size='sm'] input{height:34px;}
|
|
9878
9827
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
9879
9828
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
9880
9829
|
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
|
|
9881
9830
|
`;
|
|
9882
|
-
document.head.appendChild(st);
|
|
9831
|
+
if (!existing) document.head.appendChild(st);
|
|
9883
9832
|
}
|
|
9884
9833
|
var SettingsDialog = class {
|
|
9885
9834
|
constructor(container, theme) {
|
|
@@ -10203,7 +10152,7 @@ var SettingsDialog = class {
|
|
|
10203
10152
|
}
|
|
10204
10153
|
const paneHost = document.createElement("div");
|
|
10205
10154
|
paneHost.className = "vela-sd-pane";
|
|
10206
|
-
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;";
|
|
10155
|
+
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;";
|
|
10207
10156
|
const panes = [];
|
|
10208
10157
|
let current = null;
|
|
10209
10158
|
for (const child of [...body.children]) {
|
|
@@ -10269,7 +10218,7 @@ var SettingsDialog = class {
|
|
|
10269
10218
|
}
|
|
10270
10219
|
}
|
|
10271
10220
|
close() {
|
|
10272
|
-
|
|
10221
|
+
closeOpenPopovers();
|
|
10273
10222
|
closeWidthPopover();
|
|
10274
10223
|
this.root?.remove();
|
|
10275
10224
|
this.root = null;
|
|
@@ -10424,50 +10373,41 @@ var SettingsDialog = class {
|
|
|
10424
10373
|
return wf;
|
|
10425
10374
|
}
|
|
10426
10375
|
if (c.kind === "select") {
|
|
10427
|
-
const sel = document.createElement("select");
|
|
10428
|
-
sel.className = "vela-sd-select";
|
|
10429
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
10430
|
-
sel.title = c.label;
|
|
10431
10376
|
const current2 = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
}
|
|
10439
|
-
sel.addEventListener("change", () => put(c.key, sel.value));
|
|
10440
|
-
sel.addEventListener("vela-sync", () => {
|
|
10441
|
-
sel.value = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
10442
|
-
});
|
|
10443
|
-
return sel;
|
|
10444
|
-
}
|
|
10445
|
-
const ni = document.createElement("input");
|
|
10446
|
-
ni.type = "number";
|
|
10447
|
-
ni.className = "vela-sd-number";
|
|
10448
|
-
if (compact) ni.style.width = "56px";
|
|
10449
|
-
if (c.min !== void 0) ni.min = String(c.min);
|
|
10450
|
-
if (c.max !== void 0) ni.max = String(c.max);
|
|
10451
|
-
ni.step = String(c.step ?? 1);
|
|
10452
|
-
ni.title = c.label;
|
|
10453
|
-
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
10454
|
-
if (c.placeholder !== void 0) {
|
|
10455
|
-
ni.placeholder = c.placeholder;
|
|
10456
|
-
if (current !== c.defval) ni.value = String(current);
|
|
10457
|
-
ni.addEventListener("change", () => {
|
|
10458
|
-
const raw = ni.value.trim() === "" ? c.defval : Number(ni.value);
|
|
10459
|
-
const v = Number.isFinite(raw) ? Math.min(c.max ?? Infinity, Math.max(c.min ?? -Infinity, raw)) : c.defval;
|
|
10460
|
-
ni.value = v === c.defval ? "" : String(v);
|
|
10461
|
-
put(c.key, v);
|
|
10377
|
+
const sel = new Select({
|
|
10378
|
+
options: normalizeSelectOptions(c.options).map(([value, label]) => ({ value, label })),
|
|
10379
|
+
value: current2,
|
|
10380
|
+
size: "sm",
|
|
10381
|
+
theme: this.theme,
|
|
10382
|
+
onChange: (v) => put(c.key, v)
|
|
10462
10383
|
});
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
const v = Number(ni.value);
|
|
10467
|
-
if (Number.isFinite(v)) put(c.key, v);
|
|
10384
|
+
sel.el.title = c.label;
|
|
10385
|
+
sel.el.addEventListener("vela-sync", () => {
|
|
10386
|
+
sel.setValue(typeof bag[c.key] === "string" ? bag[c.key] : c.defval);
|
|
10468
10387
|
});
|
|
10388
|
+
return sel.el;
|
|
10469
10389
|
}
|
|
10470
|
-
|
|
10390
|
+
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
10391
|
+
const ni = new NumberInput({
|
|
10392
|
+
value: current,
|
|
10393
|
+
min: c.min,
|
|
10394
|
+
max: c.max,
|
|
10395
|
+
step: c.step ?? 1,
|
|
10396
|
+
size: "sm",
|
|
10397
|
+
commit: "live",
|
|
10398
|
+
clamp: c.placeholder !== void 0,
|
|
10399
|
+
steppers: false,
|
|
10400
|
+
compact,
|
|
10401
|
+
title: c.label,
|
|
10402
|
+
placeholder: c.placeholder,
|
|
10403
|
+
emptyValue: c.placeholder !== void 0 ? c.defval : void 0,
|
|
10404
|
+
onChange: (v) => put(c.key, v)
|
|
10405
|
+
});
|
|
10406
|
+
ni.el.addEventListener("vela-sync", () => {
|
|
10407
|
+
const next = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
10408
|
+
ni.setValue(next);
|
|
10409
|
+
});
|
|
10410
|
+
return ni.el;
|
|
10471
10411
|
}
|
|
10472
10412
|
/**
|
|
10473
10413
|
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
@@ -10748,28 +10688,27 @@ var SettingsDialog = class {
|
|
|
10748
10688
|
* only one visible at a time; see `typeRow`). */
|
|
10749
10689
|
toggleRow(label, value, onToggle, controls, get) {
|
|
10750
10690
|
const wrap = document.createElement("div");
|
|
10751
|
-
const
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
let checked = value;
|
|
10756
|
-
const cbToggle = () => {
|
|
10757
|
-
checked = !checked;
|
|
10758
|
-
cb.classList.toggle("on", checked);
|
|
10759
|
-
return checked;
|
|
10760
|
-
};
|
|
10691
|
+
const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
|
|
10692
|
+
onToggle(v);
|
|
10693
|
+
if (controls.length > 0) syncDim(v);
|
|
10694
|
+
} });
|
|
10761
10695
|
const lbl = document.createElement("span");
|
|
10762
10696
|
lbl.textContent = label;
|
|
10763
10697
|
lbl.style.cssText = "opacity:0.85;white-space:nowrap;";
|
|
10698
|
+
const syncDim = (on) => {
|
|
10699
|
+
for (const c of controls) {
|
|
10700
|
+
if (c.dataset.sdSelfGated === "1") continue;
|
|
10701
|
+
c.style.opacity = on ? "1" : "0.4";
|
|
10702
|
+
c.style.pointerEvents = on ? "" : "none";
|
|
10703
|
+
}
|
|
10704
|
+
};
|
|
10764
10705
|
if (controls.length === 0) {
|
|
10765
10706
|
wrap.className = "vela-sd-bool";
|
|
10766
10707
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
|
|
10767
|
-
wrap.append(
|
|
10768
|
-
cb.addEventListener("click", () => onToggle(cbToggle()));
|
|
10708
|
+
wrap.append(sw.el, lbl);
|
|
10769
10709
|
if (get) {
|
|
10770
10710
|
wrap.addEventListener("vela-sync", () => {
|
|
10771
|
-
|
|
10772
|
-
cb.classList.toggle("on", checked);
|
|
10711
|
+
sw.setChecked(get());
|
|
10773
10712
|
});
|
|
10774
10713
|
}
|
|
10775
10714
|
return wrap;
|
|
@@ -10777,28 +10716,16 @@ var SettingsDialog = class {
|
|
|
10777
10716
|
wrap.className = "vela-sd-row";
|
|
10778
10717
|
const left = document.createElement("div");
|
|
10779
10718
|
left.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
10780
|
-
left.append(
|
|
10719
|
+
left.append(sw.el, lbl);
|
|
10781
10720
|
const box = document.createElement("div");
|
|
10782
10721
|
box.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
10783
10722
|
for (const c of controls) box.appendChild(c);
|
|
10784
|
-
const syncDim = (on) => {
|
|
10785
|
-
for (const c of controls) {
|
|
10786
|
-
if (c.dataset.sdSelfGated === "1") continue;
|
|
10787
|
-
c.style.opacity = on ? "1" : "0.4";
|
|
10788
|
-
c.style.pointerEvents = on ? "" : "none";
|
|
10789
|
-
}
|
|
10790
|
-
};
|
|
10791
10723
|
syncDim(value);
|
|
10792
|
-
cb.addEventListener("click", () => {
|
|
10793
|
-
const v = cbToggle();
|
|
10794
|
-
onToggle(v);
|
|
10795
|
-
syncDim(v);
|
|
10796
|
-
});
|
|
10797
10724
|
if (get) {
|
|
10798
10725
|
wrap.addEventListener("vela-sync", () => {
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
syncDim(
|
|
10726
|
+
const v = get();
|
|
10727
|
+
sw.setChecked(v);
|
|
10728
|
+
syncDim(v);
|
|
10802
10729
|
});
|
|
10803
10730
|
}
|
|
10804
10731
|
wrap.append(left, box);
|
|
@@ -10806,52 +10733,23 @@ var SettingsDialog = class {
|
|
|
10806
10733
|
}
|
|
10807
10734
|
numberRow(label, value, min, max, step, onChange) {
|
|
10808
10735
|
const { wrap } = this.row(label);
|
|
10809
|
-
|
|
10810
|
-
ni.type = "number";
|
|
10811
|
-
ni.value = String(value);
|
|
10812
|
-
ni.min = String(min);
|
|
10813
|
-
ni.max = String(max);
|
|
10814
|
-
ni.step = String(step);
|
|
10815
|
-
ni.className = "vela-sd-number";
|
|
10816
|
-
ni.addEventListener("input", () => {
|
|
10817
|
-
const n = Number(ni.value);
|
|
10818
|
-
if (Number.isFinite(n)) onChange(n);
|
|
10819
|
-
});
|
|
10820
|
-
wrap.appendChild(ni);
|
|
10736
|
+
wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
|
|
10821
10737
|
return wrap;
|
|
10822
10738
|
}
|
|
10823
10739
|
/** A dropdown whose option values differ from their display labels. */
|
|
10824
10740
|
selectRowLabeled(label, value, options, onChange) {
|
|
10825
10741
|
const { wrap } = this.row(label);
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
if (val === value) o.selected = true;
|
|
10834
|
-
sel.appendChild(o);
|
|
10835
|
-
}
|
|
10836
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
10837
|
-
wrap.appendChild(sel);
|
|
10742
|
+
wrap.appendChild(new Select({
|
|
10743
|
+
options: options.map(([v, l]) => ({ value: v, label: l })),
|
|
10744
|
+
value,
|
|
10745
|
+
size: "sm",
|
|
10746
|
+
theme: this.theme,
|
|
10747
|
+
onChange
|
|
10748
|
+
}).el);
|
|
10838
10749
|
return wrap;
|
|
10839
10750
|
}
|
|
10840
10751
|
selectRow(label, value, options, onChange) {
|
|
10841
|
-
|
|
10842
|
-
const sel = document.createElement("select");
|
|
10843
|
-
sel.className = "vela-sd-select";
|
|
10844
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
10845
|
-
for (const opt of options) {
|
|
10846
|
-
const o = document.createElement("option");
|
|
10847
|
-
o.value = opt;
|
|
10848
|
-
o.textContent = opt;
|
|
10849
|
-
if (opt === value) o.selected = true;
|
|
10850
|
-
sel.appendChild(o);
|
|
10851
|
-
}
|
|
10852
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
10853
|
-
wrap.appendChild(sel);
|
|
10854
|
-
return wrap;
|
|
10752
|
+
return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
|
|
10855
10753
|
}
|
|
10856
10754
|
/** Footer with the full config as JSON — the export/import (templating) surface. */
|
|
10857
10755
|
footer(_config) {
|
|
@@ -11242,13 +11140,19 @@ var DrawingPainter = class {
|
|
|
11242
11140
|
}
|
|
11243
11141
|
this.paintLabel(ctx, d, proj, theme);
|
|
11244
11142
|
}
|
|
11245
|
-
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
11143
|
+
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
11144
|
+
* Two-point line tools rotate the glyphs to follow the segment, kept upright. */
|
|
11246
11145
|
paintLabel(ctx, d, proj, theme) {
|
|
11247
11146
|
const text = d.text;
|
|
11248
11147
|
if (!text || !text.value || d.id === this.targets.mutedLabel) return;
|
|
11249
11148
|
const layout = labelLayout(d, proj);
|
|
11250
11149
|
if (!layout) return;
|
|
11251
11150
|
const fs = namedFontSize(text.size);
|
|
11151
|
+
ctx.save();
|
|
11152
|
+
if (layout.rotate) {
|
|
11153
|
+
ctx.translate(layout.rotate.cx, layout.rotate.cy);
|
|
11154
|
+
ctx.rotate(layout.rotate.angle);
|
|
11155
|
+
}
|
|
11252
11156
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
11253
11157
|
ctx.textBaseline = "top";
|
|
11254
11158
|
ctx.textAlign = layout.align;
|
|
@@ -11256,8 +11160,8 @@ var DrawingPainter = class {
|
|
|
11256
11160
|
const lh = labelLineHeight(fs);
|
|
11257
11161
|
const lines = text.value.split("\n");
|
|
11258
11162
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
11259
|
-
ctx.textAlign = "left";
|
|
11260
11163
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
11164
|
+
ctx.restore();
|
|
11261
11165
|
}
|
|
11262
11166
|
/** Frame a plain text label while it is the target, so the text reads as a clickable object even
|
|
11263
11167
|
* though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
|
|
@@ -12200,7 +12104,14 @@ function labelLayout(d, proj) {
|
|
|
12200
12104
|
case "infoline":
|
|
12201
12105
|
case "trendangle": {
|
|
12202
12106
|
if (pts.length < 2) return null;
|
|
12203
|
-
|
|
12107
|
+
const [x1, y1] = pts[0];
|
|
12108
|
+
const [x2, y2] = pts[1];
|
|
12109
|
+
return {
|
|
12110
|
+
x: 0,
|
|
12111
|
+
top: -6 - lh * lines,
|
|
12112
|
+
align: "center",
|
|
12113
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: (x1 + x2) / 2, cy: (y1 + y2) / 2 }
|
|
12114
|
+
};
|
|
12204
12115
|
}
|
|
12205
12116
|
case "box": {
|
|
12206
12117
|
if (pts.length < 2) return null;
|
|
@@ -12549,11 +12460,11 @@ function applyFree(anchor, pt, free) {
|
|
|
12549
12460
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
12550
12461
|
var BTN = 30;
|
|
12551
12462
|
var ICON = 21;
|
|
12552
|
-
var
|
|
12553
|
-
function
|
|
12554
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
12463
|
+
var STYLE_ID4 = "vela-drawing-popup-styles";
|
|
12464
|
+
function ensureStyles3() {
|
|
12465
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID4)) return;
|
|
12555
12466
|
const s = document.createElement("style");
|
|
12556
|
-
s.id =
|
|
12467
|
+
s.id = STYLE_ID4;
|
|
12557
12468
|
s.textContent = `
|
|
12558
12469
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
12559
12470
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -12587,17 +12498,14 @@ var DrawingSettingsPopup = class {
|
|
|
12587
12498
|
this.textPanel = null;
|
|
12588
12499
|
this.levelsPanel = null;
|
|
12589
12500
|
this.colorPop = null;
|
|
12590
|
-
// floating RGB + opacity picker for a color swatch
|
|
12591
12501
|
this.colorOwner = null;
|
|
12592
|
-
|
|
12593
|
-
this.menuEl = null;
|
|
12594
|
-
// floating option list for a dropdown control
|
|
12502
|
+
this.menuPop = null;
|
|
12595
12503
|
this.menuOwner = null;
|
|
12596
12504
|
this.onClose = null;
|
|
12597
12505
|
this.onOutside = (e) => {
|
|
12598
12506
|
const target = e.target;
|
|
12599
|
-
if (this.colorPop?.contains(target)) return;
|
|
12600
|
-
if (this.
|
|
12507
|
+
if (this.colorPop?.el.contains(target)) return;
|
|
12508
|
+
if (this.menuPop?.el.contains(target)) return;
|
|
12601
12509
|
if (this.el && !this.el.contains(target)) {
|
|
12602
12510
|
const cb = this.onClose;
|
|
12603
12511
|
this.close();
|
|
@@ -12616,13 +12524,13 @@ var DrawingSettingsPopup = class {
|
|
|
12616
12524
|
* picker / dropdown menu), which live outside `el`. */
|
|
12617
12525
|
contains(node) {
|
|
12618
12526
|
if (!node) return false;
|
|
12619
|
-
return this.el?.contains(node) === true || this.colorPop?.contains(node) === true || this.
|
|
12527
|
+
return this.el?.contains(node) === true || this.colorPop?.el.contains(node) === true || this.menuPop?.el.contains(node) === true;
|
|
12620
12528
|
}
|
|
12621
12529
|
/** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
|
|
12622
12530
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
12623
12531
|
open(drawing, anchor, actions, onClose) {
|
|
12624
12532
|
this.close();
|
|
12625
|
-
|
|
12533
|
+
ensureStyles3();
|
|
12626
12534
|
this.onClose = onClose ?? null;
|
|
12627
12535
|
const t = this.theme;
|
|
12628
12536
|
const schema = drawing.schema();
|
|
@@ -12633,8 +12541,7 @@ var DrawingSettingsPopup = class {
|
|
|
12633
12541
|
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;`;
|
|
12634
12542
|
applyChromeTokens(el, t);
|
|
12635
12543
|
el.addEventListener("pointerdown", () => {
|
|
12636
|
-
|
|
12637
|
-
this.closeMenu();
|
|
12544
|
+
closeOpenPopovers();
|
|
12638
12545
|
});
|
|
12639
12546
|
const bar = document.createElement("div");
|
|
12640
12547
|
bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
|
|
@@ -13371,49 +13278,76 @@ var DrawingSettingsPopup = class {
|
|
|
13371
13278
|
});
|
|
13372
13279
|
return b;
|
|
13373
13280
|
}
|
|
13281
|
+
/** Host-anchored floating shell (stays inside the chart). Content is filled after
|
|
13282
|
+
* construction so `fill` can close over the live `Popover` without hitting TDZ. */
|
|
13283
|
+
hostFloat(anchor, opts) {
|
|
13284
|
+
const t = this.theme;
|
|
13285
|
+
const pop = new Popover({
|
|
13286
|
+
trigger: anchor,
|
|
13287
|
+
host: this.host,
|
|
13288
|
+
theme: t,
|
|
13289
|
+
position: "absolute",
|
|
13290
|
+
boundary: this.host,
|
|
13291
|
+
boundaryInset: 4,
|
|
13292
|
+
viewportInset: 0,
|
|
13293
|
+
gap: 6,
|
|
13294
|
+
align: opts.align ?? "start",
|
|
13295
|
+
zIndex: opts.zIndex,
|
|
13296
|
+
onClose: () => {
|
|
13297
|
+
if (this.menuPop === pop) {
|
|
13298
|
+
this.menuPop = null;
|
|
13299
|
+
this.menuOwner = null;
|
|
13300
|
+
}
|
|
13301
|
+
if (this.colorPop === pop) {
|
|
13302
|
+
this.colorPop = null;
|
|
13303
|
+
this.colorOwner = null;
|
|
13304
|
+
}
|
|
13305
|
+
}
|
|
13306
|
+
});
|
|
13307
|
+
const el = pop.el;
|
|
13308
|
+
el.style.background = t.background;
|
|
13309
|
+
el.style.border = "1px solid var(--vela-border)";
|
|
13310
|
+
el.style.borderRadius = "var(--vela-radius-lg)";
|
|
13311
|
+
el.style.boxShadow = "var(--vela-shadow)";
|
|
13312
|
+
el.style.padding = opts.padding;
|
|
13313
|
+
el.style.display = "flex";
|
|
13314
|
+
el.style.flexDirection = "column";
|
|
13315
|
+
el.style.gap = "1px";
|
|
13316
|
+
el.style.color = t.textColor;
|
|
13317
|
+
el.style.font = `var(--vela-font-size-md) ${t.fontFamily}`;
|
|
13318
|
+
el.style.pointerEvents = "auto";
|
|
13319
|
+
opts.fill(el, pop);
|
|
13320
|
+
pop.show();
|
|
13321
|
+
return pop;
|
|
13322
|
+
}
|
|
13374
13323
|
/** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
|
|
13375
13324
|
openActionMenu(anchor, rows) {
|
|
13376
|
-
this.
|
|
13377
|
-
|
|
13378
|
-
|
|
13379
|
-
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13383
|
-
|
|
13384
|
-
|
|
13385
|
-
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
13389
|
-
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
}
|
|
13401
|
-
|
|
13402
|
-
}
|
|
13403
|
-
this.host.appendChild(menu);
|
|
13404
|
-
const ar = anchor.getBoundingClientRect();
|
|
13405
|
-
const hr = this.host.getBoundingClientRect();
|
|
13406
|
-
const w = menu.offsetWidth;
|
|
13407
|
-
const h = menu.offsetHeight;
|
|
13408
|
-
let left = ar.right - hr.left - w;
|
|
13409
|
-
let top = ar.bottom - hr.top + 6;
|
|
13410
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
13411
|
-
if (left < 4) left = 4;
|
|
13412
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
13413
|
-
if (top < 4) top = 4;
|
|
13414
|
-
menu.style.left = `${Math.round(left)}px`;
|
|
13415
|
-
menu.style.top = `${Math.round(top)}px`;
|
|
13416
|
-
this.menuEl = menu;
|
|
13325
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
13326
|
+
align: "end",
|
|
13327
|
+
zIndex: 26,
|
|
13328
|
+
padding: "4px",
|
|
13329
|
+
fill: (menu, pop) => {
|
|
13330
|
+
for (const row of rows) {
|
|
13331
|
+
const item = document.createElement("button");
|
|
13332
|
+
item.type = "button";
|
|
13333
|
+
item.className = "vela-dpop-item";
|
|
13334
|
+
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;";
|
|
13335
|
+
const ic = document.createElement("span");
|
|
13336
|
+
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
13337
|
+
ic.innerHTML = sized(row.icon, 18);
|
|
13338
|
+
const tx = document.createElement("span");
|
|
13339
|
+
tx.textContent = row.label;
|
|
13340
|
+
tx.style.cssText = "flex:1;";
|
|
13341
|
+
item.append(ic, tx);
|
|
13342
|
+
item.addEventListener("click", (e) => {
|
|
13343
|
+
e.stopPropagation();
|
|
13344
|
+
pop.hide();
|
|
13345
|
+
row.onClick();
|
|
13346
|
+
});
|
|
13347
|
+
menu.appendChild(item);
|
|
13348
|
+
}
|
|
13349
|
+
}
|
|
13350
|
+
});
|
|
13417
13351
|
this.menuOwner = anchor;
|
|
13418
13352
|
}
|
|
13419
13353
|
/** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
|
|
@@ -13543,60 +13477,40 @@ var DrawingSettingsPopup = class {
|
|
|
13543
13477
|
/** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
|
|
13544
13478
|
* glyph (and text label when one is supplied); the current value is highlighted. */
|
|
13545
13479
|
openMenu(anchor, values, current, render, label, onPick) {
|
|
13546
|
-
this.
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13480
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
13481
|
+
zIndex: 26,
|
|
13482
|
+
padding: "4px",
|
|
13483
|
+
fill: (menu, pop) => {
|
|
13484
|
+
for (const v of values) {
|
|
13485
|
+
const active = v === current;
|
|
13486
|
+
const item = document.createElement("button");
|
|
13487
|
+
item.type = "button";
|
|
13488
|
+
item.className = "vela-dpop-item";
|
|
13489
|
+
item.dataset.active = active ? "1" : "0";
|
|
13490
|
+
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;`;
|
|
13491
|
+
const ic = document.createElement("span");
|
|
13492
|
+
ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
|
|
13493
|
+
ic.innerHTML = sized(render(v), 18);
|
|
13494
|
+
item.appendChild(ic);
|
|
13495
|
+
if (label) {
|
|
13496
|
+
const tx = document.createElement("span");
|
|
13497
|
+
tx.textContent = label(v);
|
|
13498
|
+
tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
|
|
13499
|
+
item.appendChild(tx);
|
|
13500
|
+
}
|
|
13501
|
+
item.addEventListener("click", (e) => {
|
|
13502
|
+
e.stopPropagation();
|
|
13503
|
+
pop.hide();
|
|
13504
|
+
onPick(v);
|
|
13505
|
+
});
|
|
13506
|
+
menu.appendChild(item);
|
|
13507
|
+
}
|
|
13570
13508
|
}
|
|
13571
|
-
|
|
13572
|
-
e.stopPropagation();
|
|
13573
|
-
this.closeMenu();
|
|
13574
|
-
onPick(v);
|
|
13575
|
-
});
|
|
13576
|
-
menu.appendChild(item);
|
|
13577
|
-
}
|
|
13578
|
-
this.host.appendChild(menu);
|
|
13579
|
-
const ar = anchor.getBoundingClientRect();
|
|
13580
|
-
const hr = this.host.getBoundingClientRect();
|
|
13581
|
-
const w = menu.offsetWidth;
|
|
13582
|
-
const h = menu.offsetHeight;
|
|
13583
|
-
let left = ar.left - hr.left;
|
|
13584
|
-
let top = ar.bottom - hr.top + 6;
|
|
13585
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
13586
|
-
if (left < 4) left = 4;
|
|
13587
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
13588
|
-
if (top < 4) top = 4;
|
|
13589
|
-
menu.style.left = `${Math.round(left)}px`;
|
|
13590
|
-
menu.style.top = `${Math.round(top)}px`;
|
|
13591
|
-
this.menuEl = menu;
|
|
13509
|
+
});
|
|
13592
13510
|
this.menuOwner = anchor;
|
|
13593
13511
|
}
|
|
13594
13512
|
closeMenu() {
|
|
13595
|
-
|
|
13596
|
-
this.menuEl.remove();
|
|
13597
|
-
this.menuEl = null;
|
|
13598
|
-
}
|
|
13599
|
-
this.menuOwner = null;
|
|
13513
|
+
this.menuPop?.hide();
|
|
13600
13514
|
}
|
|
13601
13515
|
/** A color control: an `icon` over a thin **colored underline** showing the current
|
|
13602
13516
|
* value (the common idiom), with an invisible native picker overlaid to edit it.
|
|
@@ -13635,36 +13549,18 @@ var DrawingSettingsPopup = class {
|
|
|
13635
13549
|
}
|
|
13636
13550
|
/** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
|
|
13637
13551
|
openColorPopover(anchor, color, onChange) {
|
|
13638
|
-
this.closeColorPopover();
|
|
13639
|
-
this.closeMenu();
|
|
13640
13552
|
const t = this.theme;
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
const hr = this.host.getBoundingClientRect();
|
|
13649
|
-
const w = pop.offsetWidth;
|
|
13650
|
-
const h = pop.offsetHeight;
|
|
13651
|
-
let left = ar.left - hr.left;
|
|
13652
|
-
let top = ar.bottom - hr.top + 6;
|
|
13653
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
13654
|
-
if (left < 4) left = 4;
|
|
13655
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
13656
|
-
if (top < 4) top = 4;
|
|
13657
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
13658
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
13659
|
-
this.colorPop = pop;
|
|
13553
|
+
this.colorPop = this.hostFloat(anchor, {
|
|
13554
|
+
zIndex: 25,
|
|
13555
|
+
padding: "10px",
|
|
13556
|
+
fill: (el) => {
|
|
13557
|
+
el.appendChild(buildColorPicker(color, t, onChange));
|
|
13558
|
+
}
|
|
13559
|
+
});
|
|
13660
13560
|
this.colorOwner = anchor;
|
|
13661
13561
|
}
|
|
13662
13562
|
closeColorPopover() {
|
|
13663
|
-
|
|
13664
|
-
this.colorPop.remove();
|
|
13665
|
-
this.colorPop = null;
|
|
13666
|
-
}
|
|
13667
|
-
this.colorOwner = null;
|
|
13563
|
+
this.colorPop?.hide();
|
|
13668
13564
|
}
|
|
13669
13565
|
divider() {
|
|
13670
13566
|
const d = document.createElement("div");
|
|
@@ -14047,9 +13943,9 @@ var UserDrawingController = class {
|
|
|
14047
13943
|
}
|
|
14048
13944
|
/**
|
|
14049
13945
|
* Union of the visible drawings' price ranges on `paneId` whose time extent
|
|
14050
|
-
* intersects [fromTime, toTime]
|
|
14051
|
-
*
|
|
14052
|
-
*
|
|
13946
|
+
* intersects [fromTime, toTime]. Hidden drawings and full-width references
|
|
13947
|
+
* (hline → null extent) are handled too. Kept as the seam for a future
|
|
13948
|
+
* per-drawing autoscale opt-in — the renderer does not fold this in today.
|
|
14053
13949
|
*/
|
|
14054
13950
|
priceRangeForPane(paneId, fromTime, toTime) {
|
|
14055
13951
|
let lo = Infinity;
|
|
@@ -14405,6 +14301,16 @@ var UserDrawingController = class {
|
|
|
14405
14301
|
if (this.eraserMode) return "pointer";
|
|
14406
14302
|
return this.interaction.cursorAt(x, y);
|
|
14407
14303
|
}
|
|
14304
|
+
/** Right-click while placing: cancel the in-progress drawing and revert to the
|
|
14305
|
+
* pointer — the gesture is an explicit escape, so it disarms even in
|
|
14306
|
+
* stay-in-drawing-mode (where Escape would leave the tool armed). Returns whether
|
|
14307
|
+
* the press was consumed; false lets the host's context menu open normally. */
|
|
14308
|
+
cancelPlacement() {
|
|
14309
|
+
if (!this.interaction.isPlacing()) return false;
|
|
14310
|
+
this.interaction.cancel();
|
|
14311
|
+
if (this.activeTool != null) this.emit({ kind: "arm", type: null });
|
|
14312
|
+
return true;
|
|
14313
|
+
}
|
|
14408
14314
|
/** Double-click over a drawing → suppress the chart's view reset (single-click already
|
|
14409
14315
|
* opens settings). Returns true only when a drawing is under the cursor. */
|
|
14410
14316
|
dblClick(x, y) {
|
|
@@ -14894,7 +14800,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
|
|
|
14894
14800
|
|
|
14895
14801
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
14896
14802
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
14897
|
-
var
|
|
14803
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
14898
14804
|
var CSS = `
|
|
14899
14805
|
.vela-attribution {
|
|
14900
14806
|
text-decoration: none;
|
|
@@ -14957,11 +14863,11 @@ var CSS = `
|
|
|
14957
14863
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
14958
14864
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
14959
14865
|
`;
|
|
14960
|
-
function
|
|
14961
|
-
let s = doc.getElementById(
|
|
14866
|
+
function ensureStyles4(doc) {
|
|
14867
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
14962
14868
|
if (!s) {
|
|
14963
14869
|
s = doc.createElement("style");
|
|
14964
|
-
s.id =
|
|
14870
|
+
s.id = STYLE_ID5;
|
|
14965
14871
|
doc.head.appendChild(s);
|
|
14966
14872
|
}
|
|
14967
14873
|
s.textContent = CSS;
|
|
@@ -14990,7 +14896,7 @@ function createCustomMark(doc, html, background) {
|
|
|
14990
14896
|
return el;
|
|
14991
14897
|
}
|
|
14992
14898
|
function createAttributionMark(doc, background) {
|
|
14993
|
-
|
|
14899
|
+
ensureStyles4(doc);
|
|
14994
14900
|
const a = doc.createElement("a");
|
|
14995
14901
|
a.className = "vela-attribution";
|
|
14996
14902
|
a.href = ATTRIBUTION_URL;
|
|
@@ -15520,7 +15426,7 @@ var NativeRenderer = class {
|
|
|
15520
15426
|
this.scrollTargetRO = null;
|
|
15521
15427
|
// eased rightOffset target while gliding back to the latest bars
|
|
15522
15428
|
// Distance (px) from the plot's bottom edge to the button — tracks the bottom-most EXPANDED
|
|
15523
|
-
// pane
|
|
15429
|
+
// pane: when the lower sub-panes collapse it rides up into the open pane.
|
|
15524
15430
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM;
|
|
15525
15431
|
// Distance (px) from the plot's right edge — clears the FULL scale gutter, which widens when a
|
|
15526
15432
|
// pane carries merged (own-scale) columns; the constant only clears a single-column scale.
|
|
@@ -16487,6 +16393,7 @@ var NativeRenderer = class {
|
|
|
16487
16393
|
drawingsClaim: (x, y) => this.userDrawings?.claim(x, y) ?? false,
|
|
16488
16394
|
drawingsMeasureStart: (x, y) => this.userDrawings?.beginMeasureAt(x, y) ?? false,
|
|
16489
16395
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
16396
|
+
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
16490
16397
|
drawingsSnapMode: () => this.snapMode,
|
|
16491
16398
|
drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
|
|
16492
16399
|
drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
|
|
@@ -16738,6 +16645,8 @@ var NativeRenderer = class {
|
|
|
16738
16645
|
this.attributionEl = null;
|
|
16739
16646
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
16740
16647
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
16648
|
+
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
16649
|
+
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
16741
16650
|
this.mountContainer = null;
|
|
16742
16651
|
this.wrapper?.remove();
|
|
16743
16652
|
}
|
|
@@ -17827,19 +17736,18 @@ var NativeRenderer = class {
|
|
|
17827
17736
|
theme: this.theme
|
|
17828
17737
|
});
|
|
17829
17738
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
17739
|
+
let folded = null;
|
|
17830
17740
|
for (const l of this.extLayers) {
|
|
17831
17741
|
const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
|
|
17832
17742
|
l.instance.render(args);
|
|
17833
|
-
|
|
17834
|
-
const mod = l.instance.modulateBase(args);
|
|
17835
|
-
if (mod) {
|
|
17836
|
-
if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
|
|
17837
|
-
if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
|
|
17838
|
-
if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
|
|
17839
|
-
}
|
|
17840
|
-
}
|
|
17743
|
+
folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
17841
17744
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
17842
17745
|
}
|
|
17746
|
+
if (folded) {
|
|
17747
|
+
if (folded.candleBodyScale != null) this.backend.candleBodyScale = clamp012(folded.candleBodyScale) || 0.01;
|
|
17748
|
+
if (folded.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(folded.candleBodyAlpha) * this.candleBodyAlpha;
|
|
17749
|
+
if (folded.gridAlpha != null) this.backend.gridAlpha = clamp012(folded.gridAlpha);
|
|
17750
|
+
}
|
|
17843
17751
|
}
|
|
17844
17752
|
const li = this.bars.length - 1;
|
|
17845
17753
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
@@ -17966,7 +17874,6 @@ var NativeRenderer = class {
|
|
|
17966
17874
|
const n = this.coords.barCount;
|
|
17967
17875
|
if (n === 0) return;
|
|
17968
17876
|
const vr = this.coords.visibleLogicalRange();
|
|
17969
|
-
const vtr = this.coords.visibleTimeRange();
|
|
17970
17877
|
const i0 = Math.max(0, Math.floor(vr.from));
|
|
17971
17878
|
const i1 = Math.min(n - 1, Math.ceil(vr.to));
|
|
17972
17879
|
if (i1 < i0) return;
|
|
@@ -17984,9 +17891,7 @@ var NativeRenderer = class {
|
|
|
17984
17891
|
}
|
|
17985
17892
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
17986
17893
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
17987
|
-
|
|
17988
|
-
const udr = this.userDrawings?.priceRangeForPane(pane.id, vtr.from, vtr.to) ?? null;
|
|
17989
|
-
if (udr) dr = dr ? { min: Math.min(dr.min, udr.min), max: Math.max(dr.max, udr.max) } : udr;
|
|
17894
|
+
const dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
17990
17895
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
17991
17896
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
17992
17897
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -18231,6 +18136,7 @@ var NativeRenderer = class {
|
|
|
18231
18136
|
this.paneControls?.reposition();
|
|
18232
18137
|
this.repositionScrollButton();
|
|
18233
18138
|
this.positionAttribution();
|
|
18139
|
+
this.publishPricePaneBounds();
|
|
18234
18140
|
return;
|
|
18235
18141
|
}
|
|
18236
18142
|
const collapsed = panes.filter((p) => p.collapsed);
|
|
@@ -18248,9 +18154,10 @@ var NativeRenderer = class {
|
|
|
18248
18154
|
this.paneControls?.reposition();
|
|
18249
18155
|
this.repositionScrollButton();
|
|
18250
18156
|
this.positionAttribution();
|
|
18157
|
+
this.publishPricePaneBounds();
|
|
18251
18158
|
}
|
|
18252
|
-
/** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area
|
|
18253
|
-
*
|
|
18159
|
+
/** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area:
|
|
18160
|
+
* with all lower sub-panes collapsed it settles into the lowest open pane. */
|
|
18254
18161
|
repositionScrollButton() {
|
|
18255
18162
|
const dataHeight = this.coords.height;
|
|
18256
18163
|
if (dataHeight <= 0) return;
|
|
@@ -18294,6 +18201,21 @@ var NativeRenderer = class {
|
|
|
18294
18201
|
this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
|
|
18295
18202
|
this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
|
|
18296
18203
|
}
|
|
18204
|
+
/** Publish the price pane's vertical insets as `--vela-price-pane-top` /
|
|
18205
|
+
* `--vela-price-pane-bottom` on the mount container, so the symbol watermark
|
|
18206
|
+
* (and any other host overlay) can clip to the price pane instead of spanning
|
|
18207
|
+
* study panes and the time axis. A maximized study pane collapses the box to
|
|
18208
|
+
* zero height — the mark does not appear on a study. */
|
|
18209
|
+
publishPricePaneBounds() {
|
|
18210
|
+
if (!this.mountContainer) return;
|
|
18211
|
+
const plotH = this.coords.height + TIME_AXIS_H;
|
|
18212
|
+
const price = this.scene.panes.get(PRICE_PANE_ID);
|
|
18213
|
+
const top = price ? price.bounds.top : 0;
|
|
18214
|
+
const height = price ? price.bounds.height : this.coords.height;
|
|
18215
|
+
const bottom = Math.max(0, plotH - (top + height));
|
|
18216
|
+
this.mountContainer.style.setProperty("--vela-price-pane-top", `${top}px`);
|
|
18217
|
+
this.mountContainer.style.setProperty("--vela-price-pane-bottom", `${bottom}px`);
|
|
18218
|
+
}
|
|
18297
18219
|
/** The built-in mark, or the host's own when one is set. */
|
|
18298
18220
|
buildAttributionEl() {
|
|
18299
18221
|
return this.attributionHtml !== null ? createCustomMark(document, this.attributionHtml, this.theme.background) : createAttributionMark(document, this.theme.background);
|