@luxalgo/vela 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/{DataProvider-gnzv-dgc.d.cts → DataProvider-Ce6PuCzQ.d.cts} +10 -1
- package/dist/{DataProvider-DTOEXKTI.d.ts → DataProvider-DNx9ntBD.d.ts} +10 -1
- package/dist/{chunk-EY63XBFZ.js → chunk-7ROONW6H.js} +132 -23
- package/dist/{chunk-L5NTMWPP.js → chunk-CA3N3PYT.js} +785 -857
- package/dist/{chunk-LJLZR44Y.js → chunk-EMS6PO2T.js} +5 -7
- package/dist/chunk-OGRQW3M6.js +1328 -0
- package/dist/{chunk-P556H6EA.js → chunk-QWIEKZRE.js} +59 -34
- package/dist/{chunk-WJKLBYRM.js → chunk-ZNL2X6U2.js} +1 -1
- package/dist/{contributions-Ci_i3IN2.d.cts → contributions-DToMhiw3.d.ts} +9 -2
- package/dist/{contributions-b6AVrqqG.d.ts → contributions-Dlc7ZWZz.d.cts} +9 -2
- package/dist/{history-Ci3M0xLV.d.ts → history-BZADxpKx.d.cts} +5 -3
- package/dist/{history-Cf1lVsap.d.cts → history-BuHXJZJ2.d.ts} +5 -3
- package/dist/index.cjs +2761 -1597
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.js +4 -4
- package/dist/{options-XTBpbx0s.d.ts → options-BlQ00Fju.d.cts} +3 -1
- package/dist/{options-XTBpbx0s.d.cts → options-BlQ00Fju.d.ts} +3 -1
- package/dist/{plugin-DlidwMj6.d.cts → plugin-BNPLOQO1.d.cts} +3 -3
- package/dist/{plugin-DP6A4t4o.d.ts → plugin-BqjEa5r3.d.ts} +3 -3
- package/dist/plugin.cjs +4 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/ui.cjs +1318 -4
- package/dist/ui.d.cts +383 -2
- package/dist/ui.d.ts +383 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +3729 -2565
- package/dist/vela.global.min.js +315 -66
- package/dist/widget.cjs +2359 -1054
- package/dist/widget.d.cts +30 -16
- package/dist/widget.d.ts +30 -16
- package/dist/widget.js +37 -14
- package/dist/workspace.cjs +2259 -944
- package/dist/workspace.d.cts +10 -5
- package/dist/workspace.d.ts +10 -5
- package/dist/workspace.js +29 -8
- package/package.json +1 -1
- package/dist/chunk-ATPU5CI6.js +0 -81
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
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 {
|
|
@@ -685,7 +685,7 @@ var ProviderRegistry = class {
|
|
|
685
685
|
/** Register (or replace) a provider; returns a promise that settles when its index is built. */
|
|
686
686
|
register(rawName, provider) {
|
|
687
687
|
const name = normName(rawName);
|
|
688
|
-
const entry = { name, provider, index: null, descriptors: [], settled: false, settle: Promise.resolve() };
|
|
688
|
+
const entry = { name, provider, index: null, prefixIndex: null, byTicker: null, descriptors: [], settled: false, settle: Promise.resolve() };
|
|
689
689
|
this.entries.set(name, entry);
|
|
690
690
|
entry.settle = this.buildIndex(entry);
|
|
691
691
|
queueMicrotask(() => this.notify(false));
|
|
@@ -698,6 +698,9 @@ var ProviderRegistry = class {
|
|
|
698
698
|
const symbols = await entry.provider.listSymbols();
|
|
699
699
|
entry.descriptors = symbols;
|
|
700
700
|
entry.index = new Set(symbols.map((s) => normTicker(s.ticker)));
|
|
701
|
+
entry.byTicker = new Map(symbols.map((s) => [normTicker(s.ticker), s]));
|
|
702
|
+
entry.prefixIndex = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const s of symbols) if (s.prefix) entry.prefixIndex.set(`${normName(s.prefix)}:${normTicker(s.ticker)}`, s);
|
|
701
704
|
}
|
|
702
705
|
} catch {
|
|
703
706
|
entry.index = null;
|
|
@@ -733,14 +736,25 @@ var ProviderRegistry = class {
|
|
|
733
736
|
}
|
|
734
737
|
/**
|
|
735
738
|
* Resolve a symbol to `{ provider, ticker }`, or null when nothing can serve it
|
|
736
|
-
* yet. Explicit prefix → that provider (must be registered)
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
739
|
+
* yet. Explicit prefix → that provider (must be registered), else a provider whose
|
|
740
|
+
* index declares that LISTING prefix for that ticker (`NASDAQ:AAPL` routes to the
|
|
741
|
+
* provider serving Nasdaq-listed AAPL — TradingView-strict, so `NYSE:AAPL` resolves
|
|
742
|
+
* to nothing rather than auto-correcting). Bare → first indexed provider
|
|
743
|
+
* (registration order, `opts.default` first) that contains the ticker;
|
|
744
|
+
* `opts.lenient` additionally allows a sole registered provider before its index
|
|
745
|
+
* exists.
|
|
740
746
|
*/
|
|
741
747
|
resolve(raw, opts = {}) {
|
|
742
748
|
const { provider, ticker } = this.parse(raw);
|
|
743
|
-
if (provider)
|
|
749
|
+
if (provider) {
|
|
750
|
+
if (this.entries.has(provider)) return { provider, ticker };
|
|
751
|
+
const key = `${provider}:${normTicker(ticker)}`;
|
|
752
|
+
for (const name of this.candidateOrder(opts.default)) {
|
|
753
|
+
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
754
|
+
if (d) return { provider: name, ticker: d.ticker };
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
744
758
|
const norm = normTicker(ticker);
|
|
745
759
|
for (const name of this.candidateOrder(opts.default)) {
|
|
746
760
|
if (this.entries.get(name).index?.has(norm)) return { provider: name, ticker };
|
|
@@ -806,6 +820,22 @@ var ProviderRegistry = class {
|
|
|
806
820
|
}
|
|
807
821
|
return names;
|
|
808
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* The DISPLAY prefix for a resolved symbol — the descriptor's LISTING prefix when the
|
|
825
|
+
* data declares one, else the provider name. This is the single seam every label
|
|
826
|
+
* (legend chip, committed/persisted `PREFIX:TICKER` strings) derives from, which is
|
|
827
|
+
* what makes a non-canonical spelling impossible to display: the form always comes
|
|
828
|
+
* from the data, never from what was typed.
|
|
829
|
+
*/
|
|
830
|
+
displayPrefixOf(resolved) {
|
|
831
|
+
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
832
|
+
return d?.prefix ?? resolved.provider;
|
|
833
|
+
}
|
|
834
|
+
/** The canonical `PREFIX:TICKER` string for a resolved symbol (descriptor spellings). */
|
|
835
|
+
canonicalSymbol(resolved) {
|
|
836
|
+
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
837
|
+
return `${d?.prefix ?? resolved.provider}:${d?.ticker ?? resolved.ticker}`;
|
|
838
|
+
}
|
|
809
839
|
/** Indexed symbols for one provider (or all, concatenated) — for autocomplete. */
|
|
810
840
|
symbolsOf(rawName) {
|
|
811
841
|
if (rawName != null) return this.entries.get(normName(rawName))?.descriptors ?? [];
|
|
@@ -1030,6 +1060,20 @@ var MultiProviderFeed = class {
|
|
|
1030
1060
|
resolveSymbol(raw) {
|
|
1031
1061
|
return this.registry.resolve(raw, { default: this.primaryProvider });
|
|
1032
1062
|
}
|
|
1063
|
+
/**
|
|
1064
|
+
* The DISPLAY prefix for `raw` — the descriptor's LISTING prefix when the data
|
|
1065
|
+
* declares one (`NASDAQ` for AAPL), else the resolved provider name. Null while
|
|
1066
|
+
* nothing resolves the symbol.
|
|
1067
|
+
*/
|
|
1068
|
+
displayPrefix(raw) {
|
|
1069
|
+
const resolved = this.resolveSymbol(raw);
|
|
1070
|
+
return resolved ? this.registry.displayPrefixOf(resolved) : null;
|
|
1071
|
+
}
|
|
1072
|
+
/** The canonical `PREFIX:TICKER` form of `raw`, or null while unresolvable. */
|
|
1073
|
+
canonicalSymbol(raw) {
|
|
1074
|
+
const resolved = this.resolveSymbol(raw);
|
|
1075
|
+
return resolved ? this.registry.canonicalSymbol(resolved) : null;
|
|
1076
|
+
}
|
|
1033
1077
|
/** The registered provider INSTANCE under `name` (undefined if unknown). */
|
|
1034
1078
|
providerInstance(name) {
|
|
1035
1079
|
return this.registry.get(name);
|
|
@@ -1235,6 +1279,17 @@ var DataControl = class {
|
|
|
1235
1279
|
resolve(symbol) {
|
|
1236
1280
|
return this.registry?.resolveSymbol(symbol) ?? null;
|
|
1237
1281
|
}
|
|
1282
|
+
/**
|
|
1283
|
+
* The DISPLAY prefix for `symbol` — the listing venue its descriptor declares
|
|
1284
|
+
* (`NASDAQ` for AAPL) or the resolved provider name. Null while unresolvable.
|
|
1285
|
+
*/
|
|
1286
|
+
displayPrefix(symbol) {
|
|
1287
|
+
return this.registry?.displayPrefix(symbol) ?? null;
|
|
1288
|
+
}
|
|
1289
|
+
/** The canonical `PREFIX:TICKER` form of `symbol`, or null while unresolvable. */
|
|
1290
|
+
canonicalSymbol(symbol) {
|
|
1291
|
+
return this.registry?.canonicalSymbol(symbol) ?? null;
|
|
1292
|
+
}
|
|
1238
1293
|
/**
|
|
1239
1294
|
* The registered provider INSTANCE under `name` — the seam for EXTENDED provider
|
|
1240
1295
|
* surfaces: a provider may implement interfaces beyond the `DataProvider` port
|
|
@@ -2844,6 +2899,7 @@ var LEGEND_CTL_PX = 18;
|
|
|
2844
2899
|
var LEGEND_ROW_PAD_Y = 2;
|
|
2845
2900
|
var LEGEND_ROW_PAD_X = 6;
|
|
2846
2901
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
2902
|
+
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
2847
2903
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
2848
2904
|
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
2905
|
var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
|
|
@@ -2888,6 +2944,11 @@ var InputsUI = class {
|
|
|
2888
2944
|
this.dialogTips = [];
|
|
2889
2945
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
2890
2946
|
this.moveMenu = null;
|
|
2947
|
+
/** Open settings-dialog choice menu (custom dropdown — torn down with the dialog). */
|
|
2948
|
+
this.choicePop = null;
|
|
2949
|
+
/** Open settings-dialog calendar popover (torn down with the dialog). */
|
|
2950
|
+
this.calendarPop = null;
|
|
2951
|
+
this.calendarAnchor = null;
|
|
2891
2952
|
/** Collapsed panes → the master indicator id to keep visible (others hidden in the strip). */
|
|
2892
2953
|
this.paneCollapse = /* @__PURE__ */ new Map();
|
|
2893
2954
|
/** The user folded the indicator legend away behind the chevron — CHART-WIDE: every
|
|
@@ -2913,6 +2974,10 @@ var InputsUI = class {
|
|
|
2913
2974
|
this.onDialogKey = (e) => {
|
|
2914
2975
|
if (e.key === "Escape") {
|
|
2915
2976
|
e.preventDefault();
|
|
2977
|
+
if (isPopoverOpen()) {
|
|
2978
|
+
closeOpenPopovers();
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2916
2981
|
this.closeDialog();
|
|
2917
2982
|
}
|
|
2918
2983
|
};
|
|
@@ -3358,6 +3423,7 @@ var InputsUI = class {
|
|
|
3358
3423
|
titleWrap.appendChild(beta);
|
|
3359
3424
|
}
|
|
3360
3425
|
el.appendChild(titleWrap);
|
|
3426
|
+
el.appendChild(statusEl);
|
|
3361
3427
|
const valuesEl = document.createElement("span");
|
|
3362
3428
|
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
3429
|
el.appendChild(valuesEl);
|
|
@@ -3418,7 +3484,6 @@ var InputsUI = class {
|
|
|
3418
3484
|
close.addEventListener("click", () => this.onRemove?.(id));
|
|
3419
3485
|
controlsEl.appendChild(close);
|
|
3420
3486
|
el.appendChild(controlsEl);
|
|
3421
|
-
el.appendChild(statusEl);
|
|
3422
3487
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
3423
3488
|
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
3489
|
this.syncFoldToggle();
|
|
@@ -3436,7 +3501,7 @@ var InputsUI = class {
|
|
|
3436
3501
|
/**
|
|
3437
3502
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
3438
3503
|
* 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
|
|
3504
|
+
* at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
|
|
3440
3505
|
*/
|
|
3441
3506
|
setStatus(id, status) {
|
|
3442
3507
|
const row = this.rows.get(id);
|
|
@@ -3448,7 +3513,7 @@ var InputsUI = class {
|
|
|
3448
3513
|
return;
|
|
3449
3514
|
}
|
|
3450
3515
|
if (status === "loading") {
|
|
3451
|
-
el.style.cssText =
|
|
3516
|
+
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
3517
|
for (let i = 0; i < 3; i += 1) {
|
|
3453
3518
|
const dot = document.createElement("span");
|
|
3454
3519
|
dot.style.cssText = "width:4px;height:4px;border-radius:50%;background:currentColor;opacity:0.15;flex:none;";
|
|
@@ -3458,7 +3523,7 @@ var InputsUI = class {
|
|
|
3458
3523
|
return;
|
|
3459
3524
|
}
|
|
3460
3525
|
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;`;
|
|
3526
|
+
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
3527
|
}
|
|
3463
3528
|
/** Inject the status keyframes once (idempotent). */
|
|
3464
3529
|
ensureStatusKeyframes() {
|
|
@@ -3573,7 +3638,8 @@ var InputsUI = class {
|
|
|
3573
3638
|
/**
|
|
3574
3639
|
* Open the indicator's settings as a centered modal over the chart — a themed,
|
|
3575
3640
|
* dependency-free port of the app's tabbed script-settings dialog: a header with the
|
|
3576
|
-
* indicator title,
|
|
3641
|
+
* indicator title, a tab strip (the default "Inputs" tab plus any tab the inputs
|
|
3642
|
+
* declare via `tab=`), a scrollable body per tab that groups inputs by `group=`
|
|
3577
3643
|
* (section headers) and packs `inline=` inputs onto one row, and a sticky footer.
|
|
3578
3644
|
*
|
|
3579
3645
|
* Edits commit LIVE (each change re-runs the indicator), matching the current
|
|
@@ -3603,55 +3669,57 @@ var InputsUI = class {
|
|
|
3603
3669
|
});
|
|
3604
3670
|
const card = document.createElement("div");
|
|
3605
3671
|
card.className = "vela-ind-dialog";
|
|
3606
|
-
card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(
|
|
3672
|
+
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
3673
|
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
3674
|
applyChromeTokens(card, t);
|
|
3609
3675
|
card.addEventListener("keydown", (e) => {
|
|
3610
3676
|
if (e.key !== "Escape") e.stopPropagation();
|
|
3611
3677
|
});
|
|
3612
3678
|
const header = document.createElement("div");
|
|
3613
|
-
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px
|
|
3679
|
+
header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 20px;flex:0 0 auto;";
|
|
3614
3680
|
const hTitle = document.createElement("span");
|
|
3615
3681
|
hTitle.textContent = row.settingsTitle;
|
|
3616
|
-
hTitle.style.cssText = "font-weight:600;font-size:
|
|
3682
|
+
hTitle.style.cssText = "font-weight:600;font-size:20px;line-height:28px;";
|
|
3617
3683
|
const closeBtn = document.createElement("button");
|
|
3618
3684
|
closeBtn.type = "button";
|
|
3619
3685
|
closeBtn.setAttribute("aria-label", "Close");
|
|
3620
3686
|
this.dialogTips.push(attachChromeTooltip(closeBtn, { host: this.container, theme: () => this.theme, text: () => "Close" }));
|
|
3621
3687
|
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;";
|
|
3688
|
+
closeBtn.className = "vela-ind-dlg-close";
|
|
3624
3689
|
closeBtn.addEventListener("click", () => this.closeDialog());
|
|
3625
3690
|
header.append(hTitle, closeBtn);
|
|
3626
3691
|
card.appendChild(header);
|
|
3627
3692
|
if (!this.mobileLayout) makeDialogDraggable(card, header, { closeSelector: "button" });
|
|
3693
|
+
const tabDefs = tabInputs(row.inputs);
|
|
3628
3694
|
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);
|
|
3695
|
+
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
3696
|
+
const tabEls = [];
|
|
3697
|
+
const bodies = [];
|
|
3698
|
+
const activate = (idx) => {
|
|
3699
|
+
for (let i = 0; i < tabEls.length; i += 1) {
|
|
3700
|
+
const active = i === idx;
|
|
3701
|
+
const el = tabEls[i];
|
|
3702
|
+
el.style.borderBottomColor = active ? fg : "transparent";
|
|
3703
|
+
el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
|
|
3704
|
+
el.classList.toggle("vela-ind-tab-active", active);
|
|
3705
|
+
bodies[i].style.display = active ? "grid" : "none";
|
|
3645
3706
|
}
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3707
|
+
};
|
|
3708
|
+
for (const [idx, def] of tabDefs.entries()) {
|
|
3709
|
+
const tab = document.createElement("div");
|
|
3710
|
+
tab.textContent = def.name;
|
|
3711
|
+
tab.className = "vela-ind-tab";
|
|
3712
|
+
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;" : ""}`;
|
|
3713
|
+
tab.addEventListener("click", () => activate(idx));
|
|
3714
|
+
tabs.appendChild(tab);
|
|
3715
|
+
tabEls.push(tab);
|
|
3716
|
+
bodies.push(this.buildTabBody(def.inputs, row));
|
|
3651
3717
|
}
|
|
3652
|
-
card.appendChild(
|
|
3718
|
+
card.appendChild(tabs);
|
|
3719
|
+
for (const body of bodies) card.appendChild(body);
|
|
3720
|
+
activate(0);
|
|
3653
3721
|
const footer = document.createElement("div");
|
|
3654
|
-
footer.style.cssText = `display:flex;justify-content:flex-end;gap:
|
|
3722
|
+
footer.style.cssText = `display:flex;justify-content:flex-end;gap:12px;padding:16px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
|
|
3655
3723
|
footer.append(
|
|
3656
3724
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
3657
3725
|
this.dialogButton("Ok", true, () => this.closeDialog())
|
|
@@ -3663,8 +3731,32 @@ var InputsUI = class {
|
|
|
3663
3731
|
this.backdrop = backdrop;
|
|
3664
3732
|
document.addEventListener("keydown", this.onDialogKey);
|
|
3665
3733
|
}
|
|
3734
|
+
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
3735
|
+
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
3736
|
+
buildTabBody(inputs, row) {
|
|
3737
|
+
const body = document.createElement("div");
|
|
3738
|
+
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;";
|
|
3739
|
+
for (const group of groupInputs(inputs)) {
|
|
3740
|
+
const section = document.createElement("div");
|
|
3741
|
+
section.style.cssText = "display:contents;";
|
|
3742
|
+
if (group.name) {
|
|
3743
|
+
const gh = document.createElement("div");
|
|
3744
|
+
gh.textContent = group.name;
|
|
3745
|
+
const first = body.childElementCount === 0;
|
|
3746
|
+
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;`;
|
|
3747
|
+
section.appendChild(gh);
|
|
3748
|
+
}
|
|
3749
|
+
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
3750
|
+
body.appendChild(section);
|
|
3751
|
+
}
|
|
3752
|
+
return body;
|
|
3753
|
+
}
|
|
3666
3754
|
closeDialog() {
|
|
3667
3755
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
3756
|
+
closeOpenPopovers();
|
|
3757
|
+
this.choicePop = null;
|
|
3758
|
+
this.calendarPop = null;
|
|
3759
|
+
this.calendarAnchor = null;
|
|
3668
3760
|
for (const dispose of this.dialogTips.splice(0)) dispose();
|
|
3669
3761
|
this.backdrop?.remove();
|
|
3670
3762
|
this.backdrop = null;
|
|
@@ -3692,79 +3784,77 @@ var InputsUI = class {
|
|
|
3692
3784
|
buildInputRow(grid, decls, row) {
|
|
3693
3785
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
3694
3786
|
if (decls.length > 1) {
|
|
3695
|
-
const
|
|
3696
|
-
|
|
3787
|
+
const cell2 = document.createElement("div");
|
|
3788
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
|
|
3697
3789
|
for (const d of decls) {
|
|
3698
3790
|
const fit2 = d.type === "color" || d.type === "bool";
|
|
3699
3791
|
const toggleFirst = d.type === "bool";
|
|
3700
3792
|
const id2 = idOf(d);
|
|
3701
3793
|
const item = document.createElement("div");
|
|
3702
|
-
item.style.cssText = `display:flex;align-items:center;gap:
|
|
3794
|
+
item.style.cssText = `display:flex;align-items:center;gap:8px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
|
|
3703
3795
|
const name = nameOf(d);
|
|
3704
3796
|
if (name) {
|
|
3705
3797
|
const lbl = document.createElement("label");
|
|
3706
3798
|
lbl.htmlFor = id2;
|
|
3707
3799
|
lbl.textContent = name;
|
|
3708
|
-
lbl.style.cssText = `font-size:
|
|
3800
|
+
lbl.style.cssText = `font-size:14px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
|
|
3709
3801
|
item.appendChild(lbl);
|
|
3710
3802
|
}
|
|
3711
3803
|
const cw2 = document.createElement("div");
|
|
3712
|
-
cw2.style.cssText = fit2 ? "" : "width:
|
|
3804
|
+
cw2.style.cssText = fit2 ? "" : "width:100px;";
|
|
3713
3805
|
cw2.appendChild(this.buildControl(row, d, id2));
|
|
3714
3806
|
item.appendChild(cw2);
|
|
3715
|
-
|
|
3807
|
+
cell2.appendChild(item);
|
|
3716
3808
|
}
|
|
3717
3809
|
const lastTip = [...decls].reverse().find((d) => d.tooltip)?.tooltip;
|
|
3718
|
-
if (lastTip)
|
|
3719
|
-
grid.appendChild(
|
|
3810
|
+
if (lastTip) cell2.appendChild(this.infoButton(lastTip));
|
|
3811
|
+
grid.appendChild(cell2);
|
|
3720
3812
|
return;
|
|
3721
3813
|
}
|
|
3722
3814
|
const lead = decls[0];
|
|
3723
3815
|
const id = idOf(lead);
|
|
3724
3816
|
if (lead.type === "bool") {
|
|
3725
|
-
const
|
|
3726
|
-
|
|
3817
|
+
const cell2 = document.createElement("div");
|
|
3818
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;align-items:center;gap:8px;";
|
|
3727
3819
|
const lbl = document.createElement("label");
|
|
3728
|
-
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:
|
|
3820
|
+
lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;";
|
|
3729
3821
|
lbl.appendChild(this.buildControl(row, lead, id));
|
|
3730
3822
|
lbl.appendChild(document.createTextNode(nameOf(lead)));
|
|
3731
|
-
|
|
3732
|
-
if (lead.tooltip)
|
|
3733
|
-
grid.appendChild(
|
|
3734
|
-
grid.appendChild(document.createElement("div"));
|
|
3823
|
+
cell2.appendChild(lbl);
|
|
3824
|
+
if (lead.tooltip) cell2.appendChild(this.infoButton(lead.tooltip));
|
|
3825
|
+
grid.appendChild(cell2);
|
|
3735
3826
|
return;
|
|
3736
3827
|
}
|
|
3737
3828
|
if (lead.type === "text_area") {
|
|
3738
|
-
const
|
|
3739
|
-
|
|
3829
|
+
const cell2 = document.createElement("div");
|
|
3830
|
+
cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
|
|
3740
3831
|
const head = document.createElement("div");
|
|
3741
3832
|
head.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;";
|
|
3742
3833
|
const lbl = document.createElement("label");
|
|
3743
3834
|
lbl.htmlFor = id;
|
|
3744
3835
|
lbl.textContent = nameOf(lead);
|
|
3745
|
-
lbl.style.cssText = "font-size:
|
|
3836
|
+
lbl.style.cssText = "font-size:14px;";
|
|
3746
3837
|
head.appendChild(lbl);
|
|
3747
3838
|
if (lead.tooltip) head.appendChild(this.infoButton(lead.tooltip));
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
grid.appendChild(
|
|
3839
|
+
cell2.appendChild(head);
|
|
3840
|
+
cell2.appendChild(this.buildControl(row, lead, id));
|
|
3841
|
+
grid.appendChild(cell2);
|
|
3751
3842
|
return;
|
|
3752
3843
|
}
|
|
3753
3844
|
const label = document.createElement("label");
|
|
3754
3845
|
label.htmlFor = id;
|
|
3755
3846
|
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;";
|
|
3847
|
+
label.style.cssText = "font-size:14px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
|
|
3759
3848
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
3849
|
+
const cell = document.createElement("div");
|
|
3850
|
+
cell.style.cssText = "display:flex;align-items:center;gap:8px;justify-self:start;";
|
|
3760
3851
|
const cw = document.createElement("div");
|
|
3761
|
-
cw.style.cssText = fit ? "" : "width:
|
|
3852
|
+
cw.style.cssText = fit ? "" : "width:100px;";
|
|
3762
3853
|
cw.appendChild(this.buildControl(row, lead, id));
|
|
3763
|
-
|
|
3764
|
-
if (lead.tooltip)
|
|
3854
|
+
cell.appendChild(cw);
|
|
3855
|
+
if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
|
|
3765
3856
|
grid.appendChild(label);
|
|
3766
|
-
grid.appendChild(
|
|
3767
|
-
grid.appendChild(document.createElement("div"));
|
|
3857
|
+
grid.appendChild(cell);
|
|
3768
3858
|
}
|
|
3769
3859
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
3770
3860
|
buildControl(row, inp, id) {
|
|
@@ -3773,107 +3863,30 @@ var InputsUI = class {
|
|
|
3773
3863
|
row.values[inp.key] = value;
|
|
3774
3864
|
this.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
3775
3865
|
};
|
|
3776
|
-
const ctrl = this.ctrlStyle();
|
|
3777
3866
|
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),
|
|
3867
|
+
if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), emit);
|
|
3868
|
+
if (inp.type === "source") return this.select(id, SOURCES, String(current), emit);
|
|
3780
3869
|
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;
|
|
3870
|
+
const field = colorField(this.theme, () => String(row.values[inp.key] ?? inp.defval), (v) => emit(v), { shape: "circle", id });
|
|
3871
|
+
return field;
|
|
3788
3872
|
}
|
|
3789
3873
|
if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
|
|
3790
|
-
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current),
|
|
3874
|
+
if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), emit);
|
|
3791
3875
|
if (inp.type === "session") return this.buildSession(id, String(current), emit);
|
|
3792
3876
|
if (inp.type === "time") return this.buildTime(id, Number(current) || 0, emit);
|
|
3793
3877
|
if (inp.type === "text_area") return this.buildTextArea(id, String(current), emit);
|
|
3794
3878
|
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;
|
|
3879
|
+
return this.buildNumber(id, Number(current), inp, emit);
|
|
3827
3880
|
}
|
|
3828
3881
|
return this.buildTextField(id, String(current), emit);
|
|
3829
3882
|
}
|
|
3830
3883
|
/** A plain text field that commits on blur / Enter (shared by string inputs and the pickerless symbol field). */
|
|
3831
3884
|
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;
|
|
3885
|
+
return new TextField({ id, value: current, size: "md", onChange: emit }).el;
|
|
3851
3886
|
}
|
|
3852
3887
|
/** A square check-toggle (filled + check when on) — replaces the raw checkbox. */
|
|
3853
3888
|
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;
|
|
3889
|
+
return new Switch({ id, checked, size: "md", tone: "bright", onChange }).el;
|
|
3877
3890
|
}
|
|
3878
3891
|
/** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
|
|
3879
3892
|
infoButton(tooltip) {
|
|
@@ -3885,45 +3898,22 @@ var InputsUI = class {
|
|
|
3885
3898
|
return b;
|
|
3886
3899
|
}
|
|
3887
3900
|
/** 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,
|
|
3901
|
+
select(id, options, current, emit) {
|
|
3902
|
+
return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, emit);
|
|
3890
3903
|
}
|
|
3891
3904
|
/** 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;
|
|
3905
|
+
selectPairs(id, pairs, current, onChange) {
|
|
3906
|
+
return new Select({
|
|
3907
|
+
id,
|
|
3908
|
+
options: pairs,
|
|
3909
|
+
value: current,
|
|
3910
|
+
size: "md",
|
|
3911
|
+
theme: this.theme,
|
|
3912
|
+
list: this.listPopover(),
|
|
3913
|
+
onChange
|
|
3914
|
+
}).el;
|
|
3925
3915
|
}
|
|
3926
|
-
/** `input.session` → two HH:MM
|
|
3916
|
+
/** `input.session` → two typeable HH:MM comboboxes committing a `HHMM-HHMM` session string. */
|
|
3927
3917
|
buildSession(id, current, emit) {
|
|
3928
3918
|
const [start, end] = sessionToTimes(current);
|
|
3929
3919
|
const wrap = document.createElement("div");
|
|
@@ -3931,15 +3921,16 @@ var InputsUI = class {
|
|
|
3931
3921
|
let from = start;
|
|
3932
3922
|
let to = end;
|
|
3933
3923
|
const commit = () => emit(timesToSession(from, to));
|
|
3934
|
-
|
|
3935
|
-
from
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
to
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3924
|
+
wrap.append(
|
|
3925
|
+
this.timeCombobox(id, from, 86, (v) => {
|
|
3926
|
+
from = v;
|
|
3927
|
+
commit();
|
|
3928
|
+
}),
|
|
3929
|
+
this.timeCombobox(`${id}-end`, to, 86, (v) => {
|
|
3930
|
+
to = v;
|
|
3931
|
+
commit();
|
|
3932
|
+
})
|
|
3933
|
+
);
|
|
3943
3934
|
return wrap;
|
|
3944
3935
|
}
|
|
3945
3936
|
/** `input.time` → a date picker + an HH:MM dropdown, committing an epoch-ms timestamp. */
|
|
@@ -3954,20 +3945,15 @@ var InputsUI = class {
|
|
|
3954
3945
|
const ms = Date.parse(`${date}T${time}:00`);
|
|
3955
3946
|
if (Number.isFinite(ms)) emit(ms);
|
|
3956
3947
|
};
|
|
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;
|
|
3948
|
+
const dateField = this.dateField(id, date, (v) => {
|
|
3949
|
+
date = v;
|
|
3964
3950
|
commit();
|
|
3965
3951
|
});
|
|
3966
|
-
const timeSel = this.
|
|
3952
|
+
const timeSel = this.timeCombobox(`${id}-time`, time, 86, (v) => {
|
|
3967
3953
|
time = v;
|
|
3968
3954
|
commit();
|
|
3969
3955
|
});
|
|
3970
|
-
wrap.append(
|
|
3956
|
+
wrap.append(dateField, timeSel);
|
|
3971
3957
|
return wrap;
|
|
3972
3958
|
}
|
|
3973
3959
|
/** `input.text_area` → a multi-line textarea, committed on blur. */
|
|
@@ -3986,9 +3972,261 @@ var InputsUI = class {
|
|
|
3986
3972
|
});
|
|
3987
3973
|
return ta;
|
|
3988
3974
|
}
|
|
3989
|
-
/** A fixed-width HH:MM
|
|
3990
|
-
|
|
3991
|
-
|
|
3975
|
+
/** A fixed-width HH:MM combobox (typeable, plus a 30-minute-step dropdown) used by session + time. */
|
|
3976
|
+
timeCombobox(id, current, width, onChange) {
|
|
3977
|
+
const wrap = document.createElement("div");
|
|
3978
|
+
wrap.className = "vela-ind-combo";
|
|
3979
|
+
wrap.style.cssText = `position:relative;width:${width}px;`;
|
|
3980
|
+
const input = document.createElement("input");
|
|
3981
|
+
input.type = "text";
|
|
3982
|
+
input.id = id;
|
|
3983
|
+
input.value = current;
|
|
3984
|
+
input.autocomplete = "off";
|
|
3985
|
+
input.spellcheck = false;
|
|
3986
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
3987
|
+
const chevron = document.createElement("button");
|
|
3988
|
+
chevron.type = "button";
|
|
3989
|
+
chevron.tabIndex = -1;
|
|
3990
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
3991
|
+
chevron.setAttribute("aria-label", "Open time list");
|
|
3992
|
+
chevron.innerHTML = CLOCK_SVG;
|
|
3993
|
+
let value = current;
|
|
3994
|
+
const commitTyped = () => {
|
|
3995
|
+
const next = normalizeTimeInput(input.value);
|
|
3996
|
+
if (!next) {
|
|
3997
|
+
input.value = value;
|
|
3998
|
+
return;
|
|
3999
|
+
}
|
|
4000
|
+
input.value = next;
|
|
4001
|
+
if (next !== value) {
|
|
4002
|
+
value = next;
|
|
4003
|
+
onChange(next);
|
|
4004
|
+
}
|
|
4005
|
+
};
|
|
4006
|
+
input.addEventListener("blur", () => {
|
|
4007
|
+
if (isPopoverOpen()) return;
|
|
4008
|
+
commitTyped();
|
|
4009
|
+
});
|
|
4010
|
+
const pick = (v) => {
|
|
4011
|
+
value = v;
|
|
4012
|
+
input.value = v;
|
|
4013
|
+
onChange(v);
|
|
4014
|
+
};
|
|
4015
|
+
const openList = () => {
|
|
4016
|
+
this.choicePop = toggleSelectList(wrap, TIME_OPTIONS, value, pick, {
|
|
4017
|
+
...this.listPopover(),
|
|
4018
|
+
onClose: () => {
|
|
4019
|
+
this.choicePop = null;
|
|
4020
|
+
}
|
|
4021
|
+
});
|
|
4022
|
+
};
|
|
4023
|
+
input.addEventListener("keydown", (e) => {
|
|
4024
|
+
if (e.key === "Enter") {
|
|
4025
|
+
e.preventDefault();
|
|
4026
|
+
commitTyped();
|
|
4027
|
+
input.blur();
|
|
4028
|
+
}
|
|
4029
|
+
if (e.key === "ArrowDown") {
|
|
4030
|
+
e.preventDefault();
|
|
4031
|
+
openList();
|
|
4032
|
+
}
|
|
4033
|
+
});
|
|
4034
|
+
const open = (e) => {
|
|
4035
|
+
e.preventDefault();
|
|
4036
|
+
e.stopPropagation();
|
|
4037
|
+
openList();
|
|
4038
|
+
};
|
|
4039
|
+
input.addEventListener("click", open);
|
|
4040
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
4041
|
+
chevron.addEventListener("click", open);
|
|
4042
|
+
wrap.append(input, chevron);
|
|
4043
|
+
return wrap;
|
|
4044
|
+
}
|
|
4045
|
+
/** A typeable YYYY-MM-DD field that also opens a themed month calendar. */
|
|
4046
|
+
dateField(id, current, onChange) {
|
|
4047
|
+
const wrap = document.createElement("div");
|
|
4048
|
+
wrap.className = "vela-ind-combo";
|
|
4049
|
+
wrap.style.cssText = "position:relative;width:128px;";
|
|
4050
|
+
const input = document.createElement("input");
|
|
4051
|
+
input.type = "text";
|
|
4052
|
+
input.id = id;
|
|
4053
|
+
input.value = current;
|
|
4054
|
+
input.autocomplete = "off";
|
|
4055
|
+
input.spellcheck = false;
|
|
4056
|
+
input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
|
|
4057
|
+
const chevron = document.createElement("button");
|
|
4058
|
+
chevron.type = "button";
|
|
4059
|
+
chevron.tabIndex = -1;
|
|
4060
|
+
chevron.className = "vela-ind-combo-chevron";
|
|
4061
|
+
chevron.setAttribute("aria-label", "Open calendar");
|
|
4062
|
+
chevron.innerHTML = CALENDAR_SVG;
|
|
4063
|
+
let value = current;
|
|
4064
|
+
const commitTyped = () => {
|
|
4065
|
+
const next = normalizeDateInput(input.value);
|
|
4066
|
+
if (!next) {
|
|
4067
|
+
input.value = value;
|
|
4068
|
+
return;
|
|
4069
|
+
}
|
|
4070
|
+
input.value = next;
|
|
4071
|
+
if (next !== value) {
|
|
4072
|
+
value = next;
|
|
4073
|
+
onChange(next);
|
|
4074
|
+
}
|
|
4075
|
+
};
|
|
4076
|
+
input.addEventListener("blur", () => {
|
|
4077
|
+
if (isPopoverOpen()) return;
|
|
4078
|
+
commitTyped();
|
|
4079
|
+
});
|
|
4080
|
+
const pick = (v) => {
|
|
4081
|
+
value = v;
|
|
4082
|
+
input.value = v;
|
|
4083
|
+
onChange(v);
|
|
4084
|
+
};
|
|
4085
|
+
input.addEventListener("keydown", (e) => {
|
|
4086
|
+
if (e.key === "Enter") {
|
|
4087
|
+
e.preventDefault();
|
|
4088
|
+
commitTyped();
|
|
4089
|
+
input.blur();
|
|
4090
|
+
}
|
|
4091
|
+
if (e.key === "ArrowDown") {
|
|
4092
|
+
e.preventDefault();
|
|
4093
|
+
this.openCalendar(wrap, value, pick);
|
|
4094
|
+
}
|
|
4095
|
+
});
|
|
4096
|
+
const open = (e) => {
|
|
4097
|
+
e.preventDefault();
|
|
4098
|
+
e.stopPropagation();
|
|
4099
|
+
if (openPopoverTrigger() === wrap) {
|
|
4100
|
+
closeOpenPopovers();
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
this.openCalendar(wrap, value, pick);
|
|
4104
|
+
};
|
|
4105
|
+
input.addEventListener("click", open);
|
|
4106
|
+
chevron.addEventListener("mousedown", (e) => e.preventDefault());
|
|
4107
|
+
chevron.addEventListener("click", open);
|
|
4108
|
+
wrap.append(input, chevron);
|
|
4109
|
+
return wrap;
|
|
4110
|
+
}
|
|
4111
|
+
/** Numeric field with hover steppers on the right (no fill until a triangle is hovered). */
|
|
4112
|
+
buildNumber(id, current, inp, emit) {
|
|
4113
|
+
return new NumberInput({
|
|
4114
|
+
id,
|
|
4115
|
+
value: current,
|
|
4116
|
+
min: inp.min,
|
|
4117
|
+
max: inp.max,
|
|
4118
|
+
step: inp.step ?? (inp.type === "int" ? 1 : 0.1),
|
|
4119
|
+
integer: inp.type === "int",
|
|
4120
|
+
size: "md",
|
|
4121
|
+
commit: "blur",
|
|
4122
|
+
onChange: emit
|
|
4123
|
+
}).el;
|
|
4124
|
+
}
|
|
4125
|
+
/** Placement shared by the indicator dialog's dropdowns and the time combobox. */
|
|
4126
|
+
listPopover() {
|
|
4127
|
+
return {
|
|
4128
|
+
theme: this.theme,
|
|
4129
|
+
matchWidth: true,
|
|
4130
|
+
boundary: this.dialog ?? "viewport",
|
|
4131
|
+
boundaryInset: 8,
|
|
4132
|
+
gap: 4
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
/** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
|
|
4136
|
+
openCalendar(anchor, current, onPick) {
|
|
4137
|
+
const parsed = parseIsoDate(current);
|
|
4138
|
+
let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
4139
|
+
let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
|
|
4140
|
+
const selected = parsed ? isoDate(parsed) : current;
|
|
4141
|
+
const pop = new Popover({
|
|
4142
|
+
trigger: anchor,
|
|
4143
|
+
theme: this.theme,
|
|
4144
|
+
className: "vela-ind-cal",
|
|
4145
|
+
boundary: this.dialog ?? "viewport",
|
|
4146
|
+
boundaryInset: 8,
|
|
4147
|
+
gap: 4,
|
|
4148
|
+
onClose: () => {
|
|
4149
|
+
this.calendarPop = null;
|
|
4150
|
+
this.calendarAnchor = null;
|
|
4151
|
+
},
|
|
4152
|
+
content: (el) => {
|
|
4153
|
+
const title = document.createElement("div");
|
|
4154
|
+
title.className = "vela-ind-cal-title";
|
|
4155
|
+
const prev2 = document.createElement("button");
|
|
4156
|
+
prev2.type = "button";
|
|
4157
|
+
prev2.className = "vela-ind-cal-nav";
|
|
4158
|
+
prev2.setAttribute("aria-label", "Previous month");
|
|
4159
|
+
prev2.innerHTML = iconAt("chevron-left", 14);
|
|
4160
|
+
const next = document.createElement("button");
|
|
4161
|
+
next.type = "button";
|
|
4162
|
+
next.className = "vela-ind-cal-nav";
|
|
4163
|
+
next.setAttribute("aria-label", "Next month");
|
|
4164
|
+
next.innerHTML = iconAt("chevron-right", 14);
|
|
4165
|
+
const head = document.createElement("div");
|
|
4166
|
+
head.className = "vela-ind-cal-head";
|
|
4167
|
+
head.append(prev2, title, next);
|
|
4168
|
+
const week = document.createElement("div");
|
|
4169
|
+
week.className = "vela-ind-cal-week";
|
|
4170
|
+
for (const d of WEEKDAY_LABELS) {
|
|
4171
|
+
const cell = document.createElement("span");
|
|
4172
|
+
cell.textContent = d;
|
|
4173
|
+
week.appendChild(cell);
|
|
4174
|
+
}
|
|
4175
|
+
const grid = document.createElement("div");
|
|
4176
|
+
grid.className = "vela-ind-cal-grid";
|
|
4177
|
+
const paint = () => {
|
|
4178
|
+
title.textContent = `${MONTH_LABELS[month]} ${year}`;
|
|
4179
|
+
grid.replaceChildren();
|
|
4180
|
+
const first = new Date(year, month, 1);
|
|
4181
|
+
const startPad = first.getDay();
|
|
4182
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
4183
|
+
const today = isoDate(/* @__PURE__ */ new Date());
|
|
4184
|
+
for (let i = 0; i < startPad; i++) {
|
|
4185
|
+
const blank = document.createElement("span");
|
|
4186
|
+
blank.className = "vela-ind-cal-blank";
|
|
4187
|
+
grid.appendChild(blank);
|
|
4188
|
+
}
|
|
4189
|
+
for (let day = 1; day <= days; day++) {
|
|
4190
|
+
const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
4191
|
+
const b = document.createElement("button");
|
|
4192
|
+
b.type = "button";
|
|
4193
|
+
b.className = "vela-ind-cal-day";
|
|
4194
|
+
b.textContent = String(day);
|
|
4195
|
+
if (iso === selected) b.dataset.checked = "1";
|
|
4196
|
+
if (iso === today) b.dataset.today = "1";
|
|
4197
|
+
b.addEventListener("click", (e) => {
|
|
4198
|
+
e.stopPropagation();
|
|
4199
|
+
pop.hide();
|
|
4200
|
+
onPick(iso);
|
|
4201
|
+
});
|
|
4202
|
+
grid.appendChild(b);
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4205
|
+
prev2.addEventListener("click", (e) => {
|
|
4206
|
+
e.stopPropagation();
|
|
4207
|
+
month -= 1;
|
|
4208
|
+
if (month < 0) {
|
|
4209
|
+
month = 11;
|
|
4210
|
+
year -= 1;
|
|
4211
|
+
}
|
|
4212
|
+
paint();
|
|
4213
|
+
});
|
|
4214
|
+
next.addEventListener("click", (e) => {
|
|
4215
|
+
e.stopPropagation();
|
|
4216
|
+
month += 1;
|
|
4217
|
+
if (month > 11) {
|
|
4218
|
+
month = 0;
|
|
4219
|
+
year += 1;
|
|
4220
|
+
}
|
|
4221
|
+
paint();
|
|
4222
|
+
});
|
|
4223
|
+
paint();
|
|
4224
|
+
el.append(head, week, grid);
|
|
4225
|
+
}
|
|
4226
|
+
});
|
|
4227
|
+
this.calendarPop = pop;
|
|
4228
|
+
this.calendarAnchor = anchor;
|
|
4229
|
+
pop.show();
|
|
3992
4230
|
}
|
|
3993
4231
|
/**
|
|
3994
4232
|
* `input.symbol` → a field that opens the host's ticker-selection UI when a picker is wired
|
|
@@ -4016,7 +4254,7 @@ var InputsUI = class {
|
|
|
4016
4254
|
}
|
|
4017
4255
|
/** Shared field chrome for text / number / select controls (fills its wrapper's width). */
|
|
4018
4256
|
ctrlStyle() {
|
|
4019
|
-
return `width:100%;box-sizing:border-box;height:
|
|
4257
|
+
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
4258
|
}
|
|
4021
4259
|
/** Whether the active theme is dark (drives the dialog's `color-scheme`). */
|
|
4022
4260
|
isDarkTheme() {
|
|
@@ -4035,21 +4273,6 @@ var InputsUI = class {
|
|
|
4035
4273
|
strongText() {
|
|
4036
4274
|
return "var(--vela-fg-bright)";
|
|
4037
4275
|
}
|
|
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
4276
|
dialogButton(label, primary, onClick) {
|
|
4054
4277
|
const b = document.createElement("button");
|
|
4055
4278
|
b.type = "button";
|
|
@@ -4063,6 +4286,21 @@ function nameOf(inp) {
|
|
|
4063
4286
|
const t = inp.title;
|
|
4064
4287
|
return t.length > 0 ? t.charAt(0).toUpperCase() + t.slice(1) : "";
|
|
4065
4288
|
}
|
|
4289
|
+
var DEFAULT_INPUT_TAB = "Inputs";
|
|
4290
|
+
function tabInputs(inputs) {
|
|
4291
|
+
const byTab = /* @__PURE__ */ new Map();
|
|
4292
|
+
for (const inp of inputs) {
|
|
4293
|
+
const name = inp.tab && inp.tab.length > 0 ? inp.tab : DEFAULT_INPUT_TAB;
|
|
4294
|
+
if (!byTab.has(name)) byTab.set(name, []);
|
|
4295
|
+
byTab.get(name).push(inp);
|
|
4296
|
+
}
|
|
4297
|
+
const names = [...byTab.keys()];
|
|
4298
|
+
if (names.includes(DEFAULT_INPUT_TAB)) {
|
|
4299
|
+
names.splice(names.indexOf(DEFAULT_INPUT_TAB), 1);
|
|
4300
|
+
names.unshift(DEFAULT_INPUT_TAB);
|
|
4301
|
+
}
|
|
4302
|
+
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
4303
|
+
}
|
|
4066
4304
|
function groupInputs(inputs) {
|
|
4067
4305
|
const order = [];
|
|
4068
4306
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -4091,9 +4329,10 @@ function groupInputs(inputs) {
|
|
|
4091
4329
|
});
|
|
4092
4330
|
}
|
|
4093
4331
|
var CHECK_SVG = iconAt("check", 12);
|
|
4094
|
-
var
|
|
4332
|
+
var CALENDAR_SVG = iconAt("calendar", 14);
|
|
4333
|
+
var CLOCK_SVG = iconAt("clock", 14);
|
|
4095
4334
|
var DIALOG_STYLE_ID = "vela-ind-dialog-styles";
|
|
4096
|
-
var DIALOG_STYLE_REV = "
|
|
4335
|
+
var DIALOG_STYLE_REV = "25";
|
|
4097
4336
|
function ensureDialogStyles() {
|
|
4098
4337
|
if (typeof document === "undefined") return;
|
|
4099
4338
|
const existing = document.getElementById(DIALOG_STYLE_ID);
|
|
@@ -4102,30 +4341,47 @@ function ensureDialogStyles() {
|
|
|
4102
4341
|
s.id = DIALOG_STYLE_ID;
|
|
4103
4342
|
s.dataset.rev = DIALOG_STYLE_REV;
|
|
4104
4343
|
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-
|
|
4344
|
+
.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;}
|
|
4345
|
+
.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);}
|
|
4346
|
+
.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);}
|
|
4347
|
+
.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;}
|
|
4348
|
+
.vela-ind-combo-chevron:hover{opacity:0.9;}
|
|
4349
|
+
.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;}
|
|
4350
|
+
.vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
|
|
4351
|
+
.vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
|
|
4352
|
+
.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;}
|
|
4353
|
+
.vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
4354
|
+
.vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
|
|
4355
|
+
.vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
|
|
4356
|
+
.vela-ind-cal-week span{line-height:20px;}
|
|
4357
|
+
.vela-ind-cal-blank{width:28px;height:28px;}
|
|
4358
|
+
.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;}
|
|
4359
|
+
.vela-ind-cal-day:hover{background:var(--vela-hover);}
|
|
4360
|
+
.vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
|
|
4361
|
+
.vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
|
|
4362
|
+
.vela-ind-dialog ::-webkit-scrollbar{width:9px;height:9px;}
|
|
4113
4363
|
.vela-ind-dialog ::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}
|
|
4114
4364
|
.vela-ind-dialog ::-webkit-scrollbar-track{background:transparent;}
|
|
4365
|
+
.vela-ind-dialog ::-webkit-scrollbar-button{display:none;width:0;height:0;}
|
|
4366
|
+
.vela-ind-tab{transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
4367
|
+
.vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
|
|
4115
4368
|
.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
4369
|
.vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
4117
4370
|
.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
4371
|
.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
4372
|
.vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
|
|
4120
4373
|
.vela-ind-close:hover{color:var(--vela-danger) !important;background-color:var(--vela-hover-strong);}
|
|
4374
|
+
.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;}
|
|
4375
|
+
.vela-ind-dlg-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
4376
|
+
.vela-ind-dlg-close svg{width:15px;height:15px;display:block;flex:none;}
|
|
4121
4377
|
.vela-ind-fold{transition:border-color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
|
|
4122
4378
|
.vela-ind-fold:hover{border-color:var(--vela-border-strong);opacity:0.9;}
|
|
4123
4379
|
.vela-ind-menuitem{background:transparent;transition:background var(--vela-dur-fast) ease;}
|
|
4124
4380
|
.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;}`;
|
|
4381
|
+
.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;}
|
|
4382
|
+
.vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);border-color:var(--vela-fg-muted);}
|
|
4383
|
+
.vela-ind-btn-primary{border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
4384
|
+
.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
4385
|
if (!existing) document.head.appendChild(s);
|
|
4130
4386
|
}
|
|
4131
4387
|
var TIMEFRAME_OPTIONS = [
|
|
@@ -4150,6 +4406,37 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
|
|
|
4150
4406
|
const v = `${hh}:${mm}`;
|
|
4151
4407
|
return { value: v, label: v };
|
|
4152
4408
|
});
|
|
4409
|
+
var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
4410
|
+
var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
4411
|
+
function normalizeDateInput(raw) {
|
|
4412
|
+
const t = raw.trim();
|
|
4413
|
+
const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
|
|
4414
|
+
if (!m) return null;
|
|
4415
|
+
const y = Number(m[1]);
|
|
4416
|
+
const mo = Number(m[2]);
|
|
4417
|
+
const d = Number(m[3]);
|
|
4418
|
+
const dt = new Date(y, mo - 1, d);
|
|
4419
|
+
if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
|
|
4420
|
+
return isoDate(dt);
|
|
4421
|
+
}
|
|
4422
|
+
function parseIsoDate(raw) {
|
|
4423
|
+
const iso = normalizeDateInput(raw);
|
|
4424
|
+
if (!iso) return null;
|
|
4425
|
+
const [y, mo, d] = iso.split("-").map(Number);
|
|
4426
|
+
return new Date(y, mo - 1, d);
|
|
4427
|
+
}
|
|
4428
|
+
function isoDate(d) {
|
|
4429
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
4430
|
+
}
|
|
4431
|
+
function normalizeTimeInput(raw) {
|
|
4432
|
+
const t = raw.trim();
|
|
4433
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
|
|
4434
|
+
if (!m) return null;
|
|
4435
|
+
const hh = Number(m[1]);
|
|
4436
|
+
const mm = Number(m[2]);
|
|
4437
|
+
if (hh > 23 || mm > 59) return null;
|
|
4438
|
+
return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
|
|
4439
|
+
}
|
|
4153
4440
|
function sessionToTimes(session) {
|
|
4154
4441
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(session.trim());
|
|
4155
4442
|
if (!m) return ["09:00", "16:00"];
|
|
@@ -8256,6 +8543,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
|
|
|
8256
8543
|
const rx = right ? width + 2 : Math.max(x1, x2);
|
|
8257
8544
|
return [lx, yAt(lx), rx, yAt(rx)];
|
|
8258
8545
|
}
|
|
8546
|
+
function uprightLineAngle(x1, y1, x2, y2) {
|
|
8547
|
+
let a = Math.atan2(y2 - y1, x2 - x1);
|
|
8548
|
+
if (a > Math.PI / 2) a -= Math.PI;
|
|
8549
|
+
if (a < -Math.PI / 2) a += Math.PI;
|
|
8550
|
+
return a;
|
|
8551
|
+
}
|
|
8259
8552
|
function parseColor2(c) {
|
|
8260
8553
|
const s = c.trim();
|
|
8261
8554
|
let m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
@@ -9429,367 +9722,71 @@ function formatStamp(ms, timeZone) {
|
|
|
9429
9722
|
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
9430
9723
|
}
|
|
9431
9724
|
|
|
9432
|
-
// src/renderers/native/drawings/colorPicker.ts
|
|
9433
|
-
function splitColor(color) {
|
|
9434
|
-
const s = String(color ?? "").trim();
|
|
9435
|
-
let m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(s);
|
|
9436
|
-
if (m) return { hex6: `#${m[1].toLowerCase()}`, alpha: m[2] ? parseInt(m[2], 16) / 255 : 1 };
|
|
9437
|
-
m = /^#([0-9a-f]{3})$/i.exec(s);
|
|
9438
|
-
if (m) {
|
|
9439
|
-
const h = m[1].toLowerCase();
|
|
9440
|
-
return { hex6: `#${h[0]}${h[0]}${h[1]}${h[1]}${h[2]}${h[2]}`, alpha: 1 };
|
|
9441
|
-
}
|
|
9442
|
-
const rgba = /^rgba?\(([^)]+)\)/i.exec(s);
|
|
9443
|
-
if (rgba) {
|
|
9444
|
-
const p = rgba[1].split(",").map((v) => v.trim());
|
|
9445
|
-
const hx = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
9446
|
-
const a = p[3] != null ? Math.max(0, Math.min(1, parseFloat(p[3]))) : 1;
|
|
9447
|
-
return { hex6: `#${hx(parseInt(p[0] ?? "0", 10))}${hx(parseInt(p[1] ?? "0", 10))}${hx(parseInt(p[2] ?? "0", 10))}`, alpha: a };
|
|
9448
|
-
}
|
|
9449
|
-
return { hex6: DEFAULT_DRAWING_COLOR, alpha: 1 };
|
|
9450
|
-
}
|
|
9451
|
-
function combineColor(hex6, alpha) {
|
|
9452
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
9453
|
-
if (a >= 0.999) return hex6;
|
|
9454
|
-
return `${hex6}${Math.round(a * 255).toString(16).padStart(2, "0")}`;
|
|
9455
|
-
}
|
|
9456
|
-
function blendOver(fg, bg, alpha) {
|
|
9457
|
-
const rgb = (c) => {
|
|
9458
|
-
const n = parseInt(splitColor(c).hex6.slice(1), 16);
|
|
9459
|
-
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
9460
|
-
};
|
|
9461
|
-
const [fr, fgc, fb] = rgb(fg);
|
|
9462
|
-
const [br, bgc, bb] = rgb(bg);
|
|
9463
|
-
const a = Math.max(0, Math.min(1, alpha));
|
|
9464
|
-
const h = (n) => Math.round(n).toString(16).padStart(2, "0");
|
|
9465
|
-
return `#${h(fr * a + br * (1 - a))}${h(fgc * a + bgc * (1 - a))}${h(fb * a + bb * (1 - a))}`;
|
|
9466
|
-
}
|
|
9467
|
-
function hslHex(h, s, l) {
|
|
9468
|
-
const sn = s / 100;
|
|
9469
|
-
const ln = l / 100;
|
|
9470
|
-
const k = (n) => (n + h / 30) % 12;
|
|
9471
|
-
const a = sn * Math.min(ln, 1 - ln);
|
|
9472
|
-
const f = (n) => ln - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
9473
|
-
const to = (x) => Math.round(x * 255).toString(16).padStart(2, "0");
|
|
9474
|
-
return `#${to(f(0))}${to(f(8))}${to(f(4))}`;
|
|
9475
|
-
}
|
|
9476
|
-
function buildPalette() {
|
|
9477
|
-
const grays = [100, 90, 80, 68, 56, 45, 35, 25, 14, 0].map((l) => hslHex(0, 0, l));
|
|
9478
|
-
const hues = [4, 28, 50, 96, 140, 174, 200, 224, 270, 322];
|
|
9479
|
-
const levels = [
|
|
9480
|
-
[82, 56],
|
|
9481
|
-
// vivid
|
|
9482
|
-
[58, 84],
|
|
9483
|
-
// light pastel
|
|
9484
|
-
[62, 74],
|
|
9485
|
-
[66, 62],
|
|
9486
|
-
[70, 50],
|
|
9487
|
-
[64, 39],
|
|
9488
|
-
[54, 29]
|
|
9489
|
-
// dark
|
|
9490
|
-
];
|
|
9491
|
-
return [grays, ...levels.map(([s, l]) => hues.map((h) => hslHex(h, s, l)))];
|
|
9492
|
-
}
|
|
9493
|
-
var PALETTE = buildPalette();
|
|
9494
|
-
function transparencyChecker(size) {
|
|
9495
|
-
return `repeating-conic-gradient(#9aa0a6 0% 25%, #d3d6da 0% 50%) 0 0 / ${size}px ${size}px`;
|
|
9496
|
-
}
|
|
9497
|
-
var CHECKER = transparencyChecker(10);
|
|
9498
|
-
var recents = [ACCENT, BULLISH, BEARISH, WARNING, NEUTRAL];
|
|
9499
|
-
function addRecent(hex6) {
|
|
9500
|
-
const i = recents.findIndex((c) => c.toLowerCase() === hex6.toLowerCase());
|
|
9501
|
-
if (i >= 0) recents.splice(i, 1);
|
|
9502
|
-
recents.unshift(hex6);
|
|
9503
|
-
if (recents.length > 10) recents.length = 10;
|
|
9504
|
-
}
|
|
9505
|
-
function buildColorPicker(color, theme, onChange) {
|
|
9506
|
-
const parsed = splitColor(color);
|
|
9507
|
-
let curHex = parsed.hex6;
|
|
9508
|
-
let curAlpha = parsed.alpha;
|
|
9509
|
-
const root = document.createElement("div");
|
|
9510
|
-
root.style.cssText = "display:flex;flex-direction:column;gap:9px;width:236px;";
|
|
9511
|
-
const grid = document.createElement("div");
|
|
9512
|
-
grid.style.cssText = "display:grid;grid-template-columns:repeat(10,1fr);gap:4px;";
|
|
9513
|
-
const swatches = [];
|
|
9514
|
-
for (const row of PALETTE) {
|
|
9515
|
-
for (const c of row) {
|
|
9516
|
-
const sw = document.createElement("button");
|
|
9517
|
-
sw.type = "button";
|
|
9518
|
-
sw.dataset.c = c;
|
|
9519
|
-
sw.style.cssText = `width:100%;aspect-ratio:1;border-radius:4px;border:none;cursor:pointer;background:${c};padding:0;`;
|
|
9520
|
-
sw.addEventListener("click", (e) => {
|
|
9521
|
-
e.stopPropagation();
|
|
9522
|
-
pickHex(c);
|
|
9523
|
-
});
|
|
9524
|
-
grid.appendChild(sw);
|
|
9525
|
-
swatches.push(sw);
|
|
9526
|
-
}
|
|
9527
|
-
}
|
|
9528
|
-
const paintSelection = () => {
|
|
9529
|
-
for (const sw of swatches) {
|
|
9530
|
-
const on = (sw.dataset.c ?? "").toLowerCase() === curHex.toLowerCase();
|
|
9531
|
-
sw.style.outline = on ? `2px solid ${theme.textColor}` : "none";
|
|
9532
|
-
sw.style.outlineOffset = "2px";
|
|
9533
|
-
sw.style.zIndex = on ? "1" : "";
|
|
9534
|
-
sw.style.position = on ? "relative" : "";
|
|
9535
|
-
}
|
|
9536
|
-
};
|
|
9537
|
-
const recentRow = document.createElement("div");
|
|
9538
|
-
recentRow.style.cssText = "display:flex;align-items:center;gap:5px;flex-wrap:wrap;border-top:1px solid var(--vela-border);padding-top:9px;";
|
|
9539
|
-
const renderRecents = () => {
|
|
9540
|
-
recentRow.replaceChildren();
|
|
9541
|
-
for (const c of recents) {
|
|
9542
|
-
const sw = document.createElement("button");
|
|
9543
|
-
sw.type = "button";
|
|
9544
|
-
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;`;
|
|
9545
|
-
sw.addEventListener("click", (e) => {
|
|
9546
|
-
e.stopPropagation();
|
|
9547
|
-
pickHex(c);
|
|
9548
|
-
});
|
|
9549
|
-
recentRow.appendChild(sw);
|
|
9550
|
-
}
|
|
9551
|
-
const add = document.createElement("label");
|
|
9552
|
-
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;`;
|
|
9553
|
-
add.textContent = "+";
|
|
9554
|
-
const input = document.createElement("input");
|
|
9555
|
-
input.type = "color";
|
|
9556
|
-
input.value = curHex;
|
|
9557
|
-
input.style.cssText = "position:absolute;inset:0;opacity:0;cursor:pointer;";
|
|
9558
|
-
input.addEventListener("input", () => pickHex(input.value, true));
|
|
9559
|
-
add.appendChild(input);
|
|
9560
|
-
recentRow.appendChild(add);
|
|
9561
|
-
};
|
|
9562
|
-
const opLabel = document.createElement("div");
|
|
9563
|
-
opLabel.textContent = "Opacity";
|
|
9564
|
-
opLabel.style.cssText = `font:11px ${theme.fontFamily};color:var(--vela-fg-muted);`;
|
|
9565
|
-
const opRow = document.createElement("div");
|
|
9566
|
-
opRow.style.cssText = "display:flex;align-items:center;gap:10px;";
|
|
9567
|
-
const track = document.createElement("div");
|
|
9568
|
-
track.style.cssText = "flex:1;position:relative;height:13px;border-radius:7px;cursor:pointer;";
|
|
9569
|
-
const knob = document.createElement("div");
|
|
9570
|
-
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;";
|
|
9571
|
-
track.appendChild(knob);
|
|
9572
|
-
const pctBox = document.createElement("div");
|
|
9573
|
-
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;`;
|
|
9574
|
-
opRow.append(track, pctBox);
|
|
9575
|
-
const paintOpacity = () => {
|
|
9576
|
-
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
9577
|
-
knob.style.left = `${curAlpha * 100}%`;
|
|
9578
|
-
pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
|
|
9579
|
-
};
|
|
9580
|
-
let dragging = false;
|
|
9581
|
-
const onDrag = (clientX) => {
|
|
9582
|
-
const r = track.getBoundingClientRect();
|
|
9583
|
-
curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
|
|
9584
|
-
paintOpacity();
|
|
9585
|
-
emit();
|
|
9586
|
-
};
|
|
9587
|
-
track.addEventListener("pointerdown", (e) => {
|
|
9588
|
-
e.stopPropagation();
|
|
9589
|
-
dragging = true;
|
|
9590
|
-
track.setPointerCapture(e.pointerId);
|
|
9591
|
-
onDrag(e.clientX);
|
|
9592
|
-
});
|
|
9593
|
-
track.addEventListener("pointermove", (e) => {
|
|
9594
|
-
if (dragging) onDrag(e.clientX);
|
|
9595
|
-
});
|
|
9596
|
-
track.addEventListener("pointerup", () => dragging = false);
|
|
9597
|
-
function emit() {
|
|
9598
|
-
onChange(combineColor(curHex, curAlpha));
|
|
9599
|
-
}
|
|
9600
|
-
function pickHex(hex, custom = false) {
|
|
9601
|
-
curHex = splitColor(hex).hex6;
|
|
9602
|
-
addRecent(curHex);
|
|
9603
|
-
paintSelection();
|
|
9604
|
-
paintOpacity();
|
|
9605
|
-
if (custom) renderRecents();
|
|
9606
|
-
emit();
|
|
9607
|
-
}
|
|
9608
|
-
renderRecents();
|
|
9609
|
-
paintSelection();
|
|
9610
|
-
paintOpacity();
|
|
9611
|
-
root.append(grid, recentRow, opLabel, opRow);
|
|
9612
|
-
return root;
|
|
9613
|
-
}
|
|
9614
|
-
|
|
9615
|
-
// src/renderers/native/chrome/ColorField.ts
|
|
9616
|
-
var CHECKER2 = transparencyChecker(8);
|
|
9617
|
-
var STYLE_ID3 = "vela-color-field";
|
|
9618
|
-
function ensureStyles3() {
|
|
9619
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
|
|
9620
|
-
const st = document.createElement("style");
|
|
9621
|
-
st.id = STYLE_ID3;
|
|
9622
|
-
st.textContent = `
|
|
9623
|
-
.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;}
|
|
9624
|
-
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
9625
|
-
.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);}
|
|
9626
|
-
.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;}
|
|
9627
|
-
`;
|
|
9628
|
-
document.head.appendChild(st);
|
|
9629
|
-
}
|
|
9630
|
-
var openPopover = null;
|
|
9631
|
-
function closeOpen() {
|
|
9632
|
-
if (!openPopover) return;
|
|
9633
|
-
document.removeEventListener("pointerdown", openPopover.onOutside, true);
|
|
9634
|
-
window.removeEventListener("resize", openPopover.reflow, true);
|
|
9635
|
-
openPopover.el.remove();
|
|
9636
|
-
openPopover = null;
|
|
9637
|
-
}
|
|
9638
|
-
function place(pop, trigger) {
|
|
9639
|
-
const r = trigger.getBoundingClientRect();
|
|
9640
|
-
const w = pop.offsetWidth;
|
|
9641
|
-
const h = pop.offsetHeight;
|
|
9642
|
-
let left = r.right - w;
|
|
9643
|
-
let top = r.bottom + 6;
|
|
9644
|
-
if (left < 6) left = 6;
|
|
9645
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
9646
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
9647
|
-
if (top < 6) top = 6;
|
|
9648
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
9649
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
9650
|
-
}
|
|
9651
|
-
function colorField(theme, getVal, onVal) {
|
|
9652
|
-
ensureStyles3();
|
|
9653
|
-
const trigger = document.createElement("button");
|
|
9654
|
-
trigger.type = "button";
|
|
9655
|
-
trigger.className = "vela-color-field";
|
|
9656
|
-
const swatch = document.createElement("span");
|
|
9657
|
-
swatch.className = "vela-color-field-swatch";
|
|
9658
|
-
trigger.appendChild(swatch);
|
|
9659
|
-
const paint = () => {
|
|
9660
|
-
const v = getVal();
|
|
9661
|
-
swatch.style.background = `linear-gradient(${v}, ${v}), ${CHECKER2}`;
|
|
9662
|
-
};
|
|
9663
|
-
paint();
|
|
9664
|
-
trigger.addEventListener("vela-sync", paint);
|
|
9665
|
-
trigger.addEventListener("click", (e) => {
|
|
9666
|
-
e.stopPropagation();
|
|
9667
|
-
if (openPopover && openPopover.trigger === trigger) {
|
|
9668
|
-
closeOpen();
|
|
9669
|
-
return;
|
|
9670
|
-
}
|
|
9671
|
-
closeOpen();
|
|
9672
|
-
const pop = document.createElement("div");
|
|
9673
|
-
pop.className = "vela-color-field-pop";
|
|
9674
|
-
applyChromeTokens(pop, theme);
|
|
9675
|
-
pop.addEventListener("pointerdown", (ev) => ev.stopPropagation());
|
|
9676
|
-
pop.appendChild(
|
|
9677
|
-
buildColorPicker(getVal(), theme, (val) => {
|
|
9678
|
-
onVal(val);
|
|
9679
|
-
paint();
|
|
9680
|
-
})
|
|
9681
|
-
);
|
|
9682
|
-
document.body.appendChild(pop);
|
|
9683
|
-
place(pop, trigger);
|
|
9684
|
-
const onOutside = (ev) => {
|
|
9685
|
-
const t = ev.target;
|
|
9686
|
-
if (pop.contains(t) || trigger.contains(t)) return;
|
|
9687
|
-
closeOpen();
|
|
9688
|
-
};
|
|
9689
|
-
const reflow = () => place(pop, trigger);
|
|
9690
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
9691
|
-
window.addEventListener("resize", reflow, true);
|
|
9692
|
-
openPopover = { el: pop, trigger, onOutside, reflow };
|
|
9693
|
-
});
|
|
9694
|
-
return trigger;
|
|
9695
|
-
}
|
|
9696
|
-
function closeColorPopover() {
|
|
9697
|
-
closeOpen();
|
|
9698
|
-
}
|
|
9699
|
-
|
|
9700
9725
|
// src/renderers/native/chrome/WidthField.ts
|
|
9701
9726
|
var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
|
|
9702
|
-
var
|
|
9703
|
-
|
|
9704
|
-
if (typeof document === "undefined" || document.getElementById(STYLE_ID4)) return;
|
|
9705
|
-
const st = document.createElement("style");
|
|
9706
|
-
st.id = STYLE_ID4;
|
|
9707
|
-
st.textContent = `
|
|
9727
|
+
var STYLE_ID3 = "vela-width-field";
|
|
9728
|
+
var WIDTH_CSS = `
|
|
9708
9729
|
.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;}
|
|
9709
9730
|
.vela-width-field:hover{border-color:var(--vela-fg-muted);}
|
|
9710
|
-
.vela-width-field-
|
|
9731
|
+
.vela-width-field-caret{display:flex;color:var(--vela-fg-muted);}
|
|
9732
|
+
.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);}
|
|
9711
9733
|
.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;}
|
|
9712
9734
|
.vela-width-field-item:hover{background:var(--vela-hover-strong);}
|
|
9713
9735
|
.vela-width-field-item[data-active='1']{background:var(--vela-active);}
|
|
9714
9736
|
`;
|
|
9715
|
-
document.head.appendChild(st);
|
|
9716
|
-
}
|
|
9717
9737
|
function lineGlyph(width) {
|
|
9718
9738
|
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>`;
|
|
9719
9739
|
}
|
|
9720
|
-
var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="
|
|
9721
|
-
var openPopover2 = null;
|
|
9722
|
-
function closeOpen2() {
|
|
9723
|
-
if (!openPopover2) return;
|
|
9724
|
-
document.removeEventListener("pointerdown", openPopover2.onOutside, true);
|
|
9725
|
-
window.removeEventListener("resize", openPopover2.reflow, true);
|
|
9726
|
-
openPopover2.el.remove();
|
|
9727
|
-
openPopover2 = null;
|
|
9728
|
-
}
|
|
9729
|
-
function place2(pop, trigger) {
|
|
9730
|
-
const r = trigger.getBoundingClientRect();
|
|
9731
|
-
const w = pop.offsetWidth;
|
|
9732
|
-
const h = pop.offsetHeight;
|
|
9733
|
-
let left = r.right - w;
|
|
9734
|
-
let top = r.bottom + 6;
|
|
9735
|
-
if (left < 6) left = 6;
|
|
9736
|
-
if (left + w > window.innerWidth - 6) left = window.innerWidth - w - 6;
|
|
9737
|
-
if (top + h > window.innerHeight - 6) top = r.top - h - 6;
|
|
9738
|
-
if (top < 6) top = 6;
|
|
9739
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
9740
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
9741
|
-
}
|
|
9740
|
+
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>`;
|
|
9742
9741
|
function widthField(theme, getVal, onVal) {
|
|
9743
|
-
|
|
9742
|
+
injectStyles(STYLE_ID3, WIDTH_CSS, document);
|
|
9744
9743
|
const trigger = document.createElement("button");
|
|
9745
9744
|
trigger.type = "button";
|
|
9746
9745
|
trigger.className = "vela-width-field";
|
|
9747
9746
|
const paint = () => {
|
|
9748
|
-
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span
|
|
9747
|
+
trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span class="vela-width-field-caret">${CHEVRON}</span>`;
|
|
9749
9748
|
};
|
|
9750
9749
|
paint();
|
|
9750
|
+
trigger.addEventListener("pointerdown", (e) => {
|
|
9751
|
+
e.preventDefault();
|
|
9752
|
+
trigger.focus({ preventScroll: true });
|
|
9753
|
+
});
|
|
9751
9754
|
trigger.addEventListener("click", (e) => {
|
|
9752
9755
|
e.stopPropagation();
|
|
9753
|
-
if (
|
|
9754
|
-
|
|
9756
|
+
if (openPopoverTrigger() === trigger) {
|
|
9757
|
+
closeOpenPopovers();
|
|
9755
9758
|
return;
|
|
9756
9759
|
}
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
closeOpen2();
|
|
9783
|
-
};
|
|
9784
|
-
const reflow = () => place2(pop, trigger);
|
|
9785
|
-
setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
|
|
9786
|
-
window.addEventListener("resize", reflow, true);
|
|
9787
|
-
openPopover2 = { el: pop, trigger, onOutside, reflow };
|
|
9760
|
+
const pop = new Popover({
|
|
9761
|
+
trigger,
|
|
9762
|
+
theme,
|
|
9763
|
+
align: "end",
|
|
9764
|
+
gap: 6,
|
|
9765
|
+
className: "vela-width-field-pop",
|
|
9766
|
+
content: (el) => {
|
|
9767
|
+
el.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
|
|
9768
|
+
for (const w of WIDTH_FIELD_OPTIONS) {
|
|
9769
|
+
const item = document.createElement("button");
|
|
9770
|
+
item.type = "button";
|
|
9771
|
+
item.className = "vela-width-field-item";
|
|
9772
|
+
item.dataset.active = w === getVal() ? "1" : "0";
|
|
9773
|
+
item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
|
|
9774
|
+
item.addEventListener("click", (ev) => {
|
|
9775
|
+
ev.stopPropagation();
|
|
9776
|
+
pop.hide();
|
|
9777
|
+
onVal(w);
|
|
9778
|
+
paint();
|
|
9779
|
+
});
|
|
9780
|
+
el.appendChild(item);
|
|
9781
|
+
}
|
|
9782
|
+
}
|
|
9783
|
+
});
|
|
9784
|
+
pop.show();
|
|
9788
9785
|
});
|
|
9789
9786
|
return trigger;
|
|
9790
9787
|
}
|
|
9791
9788
|
function closeWidthPopover() {
|
|
9792
|
-
|
|
9789
|
+
closeOpenPopovers();
|
|
9793
9790
|
}
|
|
9794
9791
|
|
|
9795
9792
|
// src/renderers/native/chrome/SettingsDialog.ts
|
|
@@ -9804,37 +9801,24 @@ function styleLabel(id) {
|
|
|
9804
9801
|
return chartType(id)?.label ?? BUILTIN_STYLE_LABELS[id] ?? id;
|
|
9805
9802
|
}
|
|
9806
9803
|
var SD_STYLE_ID = "vela-settings-controls";
|
|
9804
|
+
var SD_STYLE_REV = "2";
|
|
9807
9805
|
var SETTINGS_SURFACE = "var(--vela-surface)";
|
|
9808
9806
|
var SETTINGS_BORDER = "var(--vela-border)";
|
|
9809
9807
|
var SETTINGS_TEXT = "var(--vela-fg)";
|
|
9810
9808
|
function ensureControlStyles() {
|
|
9811
|
-
if (typeof document === "undefined"
|
|
9812
|
-
const
|
|
9809
|
+
if (typeof document === "undefined") return;
|
|
9810
|
+
const existing = document.getElementById(SD_STYLE_ID);
|
|
9811
|
+
if (existing?.dataset.rev === SD_STYLE_REV) return;
|
|
9812
|
+
const st = existing ?? document.createElement("style");
|
|
9813
9813
|
st.id = SD_STYLE_ID;
|
|
9814
|
+
st.dataset.rev = SD_STYLE_REV;
|
|
9814
9815
|
st.textContent = `
|
|
9815
|
-
.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;}
|
|
9816
|
-
.vela-sd-check:hover{border-color:var(--vela-fg-muted);}
|
|
9817
|
-
.vela-sd-check.on{background:var(--vela-selected-bg);border-color:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
9818
|
-
.vela-sd-check svg{display:block;}
|
|
9819
|
-
.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;}
|
|
9820
|
-
.vela-sd-select:hover,.vela-sd-number:hover{border-color:var(--vela-fg-muted);}
|
|
9821
|
-
/* Custom caret: the native one hugs the right border; ours gets 8px of air. (A data URI
|
|
9822
|
-
can't read currentColor, so it uses the shared muted-gray ink, legible on both themes.) */
|
|
9823
|
-
.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;}
|
|
9824
|
-
.vela-sd-number{width:64px;}
|
|
9825
|
-
.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;}
|
|
9826
|
-
.vela-sd-color::-webkit-color-swatch-wrapper{padding:2px;}
|
|
9827
|
-
.vela-sd-color::-webkit-color-swatch{border:none;border-radius:2px;}
|
|
9828
9816
|
.vela-sd-pane::-webkit-scrollbar{width:8px;}
|
|
9829
9817
|
.vela-sd-pane::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:var(--vela-radius-sm);border:2px solid transparent;background-clip:padding-box;}
|
|
9830
9818
|
.vela-sd-pane::-webkit-scrollbar-track{background:transparent;}
|
|
9831
|
-
.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;}
|
|
9832
|
-
.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;}
|
|
9833
|
-
.vela-sd-toggle.on{background:var(--vela-active);border-color:var(--vela-selected-bg);}
|
|
9834
|
-
.vela-sd-toggle.on::after{transform:translateX(16px);background:var(--vela-selected-bg);}
|
|
9835
9819
|
/* Tab rail / footer button / header close: base styles live HERE, not inline on the
|
|
9836
9820
|
elements \u2014 inline declarations always beat stylesheet :hover rules, which is exactly
|
|
9837
|
-
what killed these hovers before. Active tab state is the .on class
|
|
9821
|
+
what killed these hovers before. Active tab state is the .on class,
|
|
9838
9822
|
hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
|
|
9839
9823
|
.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;}
|
|
9840
9824
|
.vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
@@ -9893,13 +9877,13 @@ function ensureControlStyles() {
|
|
|
9893
9877
|
.vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
|
|
9894
9878
|
.vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
|
|
9895
9879
|
.vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
|
|
9896
|
-
.vela-sd-mobile .vela-
|
|
9897
|
-
.vela-sd-mobile .vela-
|
|
9880
|
+
.vela-sd-mobile .vela-switch{width:22px;height:22px;}
|
|
9881
|
+
.vela-sd-mobile .vela-select[data-size='sm'] .vela-select-trigger,.vela-sd-mobile .vela-num[data-size='sm'] input{height:34px;}
|
|
9898
9882
|
.vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
|
|
9899
9883
|
.vela-sd-mobile .vela-sd-btn{height:38px;}
|
|
9900
9884
|
.vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
|
|
9901
9885
|
`;
|
|
9902
|
-
document.head.appendChild(st);
|
|
9886
|
+
if (!existing) document.head.appendChild(st);
|
|
9903
9887
|
}
|
|
9904
9888
|
var SettingsDialog = class {
|
|
9905
9889
|
constructor(container, theme) {
|
|
@@ -10223,7 +10207,7 @@ var SettingsDialog = class {
|
|
|
10223
10207
|
}
|
|
10224
10208
|
const paneHost = document.createElement("div");
|
|
10225
10209
|
paneHost.className = "vela-sd-pane";
|
|
10226
|
-
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;";
|
|
10210
|
+
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;";
|
|
10227
10211
|
const panes = [];
|
|
10228
10212
|
let current = null;
|
|
10229
10213
|
for (const child of [...body.children]) {
|
|
@@ -10289,7 +10273,7 @@ var SettingsDialog = class {
|
|
|
10289
10273
|
}
|
|
10290
10274
|
}
|
|
10291
10275
|
close() {
|
|
10292
|
-
|
|
10276
|
+
closeOpenPopovers();
|
|
10293
10277
|
closeWidthPopover();
|
|
10294
10278
|
this.root?.remove();
|
|
10295
10279
|
this.root = null;
|
|
@@ -10444,50 +10428,41 @@ var SettingsDialog = class {
|
|
|
10444
10428
|
return wf;
|
|
10445
10429
|
}
|
|
10446
10430
|
if (c.kind === "select") {
|
|
10447
|
-
const sel = document.createElement("select");
|
|
10448
|
-
sel.className = "vela-sd-select";
|
|
10449
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
10450
|
-
sel.title = c.label;
|
|
10451
10431
|
const current2 = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
}
|
|
10459
|
-
sel.addEventListener("change", () => put(c.key, sel.value));
|
|
10460
|
-
sel.addEventListener("vela-sync", () => {
|
|
10461
|
-
sel.value = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
|
|
10462
|
-
});
|
|
10463
|
-
return sel;
|
|
10464
|
-
}
|
|
10465
|
-
const ni = document.createElement("input");
|
|
10466
|
-
ni.type = "number";
|
|
10467
|
-
ni.className = "vela-sd-number";
|
|
10468
|
-
if (compact) ni.style.width = "56px";
|
|
10469
|
-
if (c.min !== void 0) ni.min = String(c.min);
|
|
10470
|
-
if (c.max !== void 0) ni.max = String(c.max);
|
|
10471
|
-
ni.step = String(c.step ?? 1);
|
|
10472
|
-
ni.title = c.label;
|
|
10473
|
-
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
10474
|
-
if (c.placeholder !== void 0) {
|
|
10475
|
-
ni.placeholder = c.placeholder;
|
|
10476
|
-
if (current !== c.defval) ni.value = String(current);
|
|
10477
|
-
ni.addEventListener("change", () => {
|
|
10478
|
-
const raw = ni.value.trim() === "" ? c.defval : Number(ni.value);
|
|
10479
|
-
const v = Number.isFinite(raw) ? Math.min(c.max ?? Infinity, Math.max(c.min ?? -Infinity, raw)) : c.defval;
|
|
10480
|
-
ni.value = v === c.defval ? "" : String(v);
|
|
10481
|
-
put(c.key, v);
|
|
10432
|
+
const sel = new Select({
|
|
10433
|
+
options: normalizeSelectOptions(c.options).map(([value, label]) => ({ value, label })),
|
|
10434
|
+
value: current2,
|
|
10435
|
+
size: "sm",
|
|
10436
|
+
theme: this.theme,
|
|
10437
|
+
onChange: (v) => put(c.key, v)
|
|
10482
10438
|
});
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
const v = Number(ni.value);
|
|
10487
|
-
if (Number.isFinite(v)) put(c.key, v);
|
|
10439
|
+
sel.el.title = c.label;
|
|
10440
|
+
sel.el.addEventListener("vela-sync", () => {
|
|
10441
|
+
sel.setValue(typeof bag[c.key] === "string" ? bag[c.key] : c.defval);
|
|
10488
10442
|
});
|
|
10443
|
+
return sel.el;
|
|
10489
10444
|
}
|
|
10490
|
-
|
|
10445
|
+
const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
10446
|
+
const ni = new NumberInput({
|
|
10447
|
+
value: current,
|
|
10448
|
+
min: c.min,
|
|
10449
|
+
max: c.max,
|
|
10450
|
+
step: c.step ?? 1,
|
|
10451
|
+
size: "sm",
|
|
10452
|
+
commit: "live",
|
|
10453
|
+
clamp: c.placeholder !== void 0,
|
|
10454
|
+
steppers: false,
|
|
10455
|
+
compact,
|
|
10456
|
+
title: c.label,
|
|
10457
|
+
placeholder: c.placeholder,
|
|
10458
|
+
emptyValue: c.placeholder !== void 0 ? c.defval : void 0,
|
|
10459
|
+
onChange: (v) => put(c.key, v)
|
|
10460
|
+
});
|
|
10461
|
+
ni.el.addEventListener("vela-sync", () => {
|
|
10462
|
+
const next = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
|
|
10463
|
+
ni.setValue(next);
|
|
10464
|
+
});
|
|
10465
|
+
return ni.el;
|
|
10491
10466
|
}
|
|
10492
10467
|
/**
|
|
10493
10468
|
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
@@ -10768,28 +10743,27 @@ var SettingsDialog = class {
|
|
|
10768
10743
|
* only one visible at a time; see `typeRow`). */
|
|
10769
10744
|
toggleRow(label, value, onToggle, controls, get) {
|
|
10770
10745
|
const wrap = document.createElement("div");
|
|
10771
|
-
const
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10775
|
-
let checked = value;
|
|
10776
|
-
const cbToggle = () => {
|
|
10777
|
-
checked = !checked;
|
|
10778
|
-
cb.classList.toggle("on", checked);
|
|
10779
|
-
return checked;
|
|
10780
|
-
};
|
|
10746
|
+
const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
|
|
10747
|
+
onToggle(v);
|
|
10748
|
+
if (controls.length > 0) syncDim(v);
|
|
10749
|
+
} });
|
|
10781
10750
|
const lbl = document.createElement("span");
|
|
10782
10751
|
lbl.textContent = label;
|
|
10783
10752
|
lbl.style.cssText = "opacity:0.85;white-space:nowrap;";
|
|
10753
|
+
const syncDim = (on) => {
|
|
10754
|
+
for (const c of controls) {
|
|
10755
|
+
if (c.dataset.sdSelfGated === "1") continue;
|
|
10756
|
+
c.style.opacity = on ? "1" : "0.4";
|
|
10757
|
+
c.style.pointerEvents = on ? "" : "none";
|
|
10758
|
+
}
|
|
10759
|
+
};
|
|
10784
10760
|
if (controls.length === 0) {
|
|
10785
10761
|
wrap.className = "vela-sd-bool";
|
|
10786
10762
|
wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
|
|
10787
|
-
wrap.append(
|
|
10788
|
-
cb.addEventListener("click", () => onToggle(cbToggle()));
|
|
10763
|
+
wrap.append(sw.el, lbl);
|
|
10789
10764
|
if (get) {
|
|
10790
10765
|
wrap.addEventListener("vela-sync", () => {
|
|
10791
|
-
|
|
10792
|
-
cb.classList.toggle("on", checked);
|
|
10766
|
+
sw.setChecked(get());
|
|
10793
10767
|
});
|
|
10794
10768
|
}
|
|
10795
10769
|
return wrap;
|
|
@@ -10797,28 +10771,16 @@ var SettingsDialog = class {
|
|
|
10797
10771
|
wrap.className = "vela-sd-row";
|
|
10798
10772
|
const left = document.createElement("div");
|
|
10799
10773
|
left.style.cssText = "display:flex;align-items:center;gap:8px;";
|
|
10800
|
-
left.append(
|
|
10774
|
+
left.append(sw.el, lbl);
|
|
10801
10775
|
const box = document.createElement("div");
|
|
10802
10776
|
box.style.cssText = "display:flex;align-items:center;gap:6px;";
|
|
10803
10777
|
for (const c of controls) box.appendChild(c);
|
|
10804
|
-
const syncDim = (on) => {
|
|
10805
|
-
for (const c of controls) {
|
|
10806
|
-
if (c.dataset.sdSelfGated === "1") continue;
|
|
10807
|
-
c.style.opacity = on ? "1" : "0.4";
|
|
10808
|
-
c.style.pointerEvents = on ? "" : "none";
|
|
10809
|
-
}
|
|
10810
|
-
};
|
|
10811
10778
|
syncDim(value);
|
|
10812
|
-
cb.addEventListener("click", () => {
|
|
10813
|
-
const v = cbToggle();
|
|
10814
|
-
onToggle(v);
|
|
10815
|
-
syncDim(v);
|
|
10816
|
-
});
|
|
10817
10779
|
if (get) {
|
|
10818
10780
|
wrap.addEventListener("vela-sync", () => {
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
syncDim(
|
|
10781
|
+
const v = get();
|
|
10782
|
+
sw.setChecked(v);
|
|
10783
|
+
syncDim(v);
|
|
10822
10784
|
});
|
|
10823
10785
|
}
|
|
10824
10786
|
wrap.append(left, box);
|
|
@@ -10826,52 +10788,23 @@ var SettingsDialog = class {
|
|
|
10826
10788
|
}
|
|
10827
10789
|
numberRow(label, value, min, max, step, onChange) {
|
|
10828
10790
|
const { wrap } = this.row(label);
|
|
10829
|
-
|
|
10830
|
-
ni.type = "number";
|
|
10831
|
-
ni.value = String(value);
|
|
10832
|
-
ni.min = String(min);
|
|
10833
|
-
ni.max = String(max);
|
|
10834
|
-
ni.step = String(step);
|
|
10835
|
-
ni.className = "vela-sd-number";
|
|
10836
|
-
ni.addEventListener("input", () => {
|
|
10837
|
-
const n = Number(ni.value);
|
|
10838
|
-
if (Number.isFinite(n)) onChange(n);
|
|
10839
|
-
});
|
|
10840
|
-
wrap.appendChild(ni);
|
|
10791
|
+
wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
|
|
10841
10792
|
return wrap;
|
|
10842
10793
|
}
|
|
10843
10794
|
/** A dropdown whose option values differ from their display labels. */
|
|
10844
10795
|
selectRowLabeled(label, value, options, onChange) {
|
|
10845
10796
|
const { wrap } = this.row(label);
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
if (val === value) o.selected = true;
|
|
10854
|
-
sel.appendChild(o);
|
|
10855
|
-
}
|
|
10856
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
10857
|
-
wrap.appendChild(sel);
|
|
10797
|
+
wrap.appendChild(new Select({
|
|
10798
|
+
options: options.map(([v, l]) => ({ value: v, label: l })),
|
|
10799
|
+
value,
|
|
10800
|
+
size: "sm",
|
|
10801
|
+
theme: this.theme,
|
|
10802
|
+
onChange
|
|
10803
|
+
}).el);
|
|
10858
10804
|
return wrap;
|
|
10859
10805
|
}
|
|
10860
10806
|
selectRow(label, value, options, onChange) {
|
|
10861
|
-
|
|
10862
|
-
const sel = document.createElement("select");
|
|
10863
|
-
sel.className = "vela-sd-select";
|
|
10864
|
-
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
10865
|
-
for (const opt of options) {
|
|
10866
|
-
const o = document.createElement("option");
|
|
10867
|
-
o.value = opt;
|
|
10868
|
-
o.textContent = opt;
|
|
10869
|
-
if (opt === value) o.selected = true;
|
|
10870
|
-
sel.appendChild(o);
|
|
10871
|
-
}
|
|
10872
|
-
sel.addEventListener("change", () => onChange(sel.value));
|
|
10873
|
-
wrap.appendChild(sel);
|
|
10874
|
-
return wrap;
|
|
10807
|
+
return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
|
|
10875
10808
|
}
|
|
10876
10809
|
/** Footer with the full config as JSON — the export/import (templating) surface. */
|
|
10877
10810
|
footer(_config) {
|
|
@@ -11262,13 +11195,19 @@ var DrawingPainter = class {
|
|
|
11262
11195
|
}
|
|
11263
11196
|
this.paintLabel(ctx, d, proj, theme);
|
|
11264
11197
|
}
|
|
11265
|
-
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
11198
|
+
/** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
|
|
11199
|
+
* Two-point line tools rotate the glyphs to follow the segment, kept upright. */
|
|
11266
11200
|
paintLabel(ctx, d, proj, theme) {
|
|
11267
11201
|
const text = d.text;
|
|
11268
11202
|
if (!text || !text.value || d.id === this.targets.mutedLabel) return;
|
|
11269
11203
|
const layout = labelLayout(d, proj);
|
|
11270
11204
|
if (!layout) return;
|
|
11271
11205
|
const fs = namedFontSize(text.size);
|
|
11206
|
+
ctx.save();
|
|
11207
|
+
if (layout.rotate) {
|
|
11208
|
+
ctx.translate(layout.rotate.cx, layout.rotate.cy);
|
|
11209
|
+
ctx.rotate(layout.rotate.angle);
|
|
11210
|
+
}
|
|
11272
11211
|
ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
|
|
11273
11212
|
ctx.textBaseline = "top";
|
|
11274
11213
|
ctx.textAlign = layout.align;
|
|
@@ -11276,8 +11215,8 @@ var DrawingPainter = class {
|
|
|
11276
11215
|
const lh = labelLineHeight(fs);
|
|
11277
11216
|
const lines = text.value.split("\n");
|
|
11278
11217
|
lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
|
|
11279
|
-
ctx.textAlign = "left";
|
|
11280
11218
|
if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
|
|
11219
|
+
ctx.restore();
|
|
11281
11220
|
}
|
|
11282
11221
|
/** Frame a plain text label while it is the target, so the text reads as a clickable object even
|
|
11283
11222
|
* though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
|
|
@@ -12220,7 +12159,14 @@ function labelLayout(d, proj) {
|
|
|
12220
12159
|
case "infoline":
|
|
12221
12160
|
case "trendangle": {
|
|
12222
12161
|
if (pts.length < 2) return null;
|
|
12223
|
-
|
|
12162
|
+
const [x1, y1] = pts[0];
|
|
12163
|
+
const [x2, y2] = pts[1];
|
|
12164
|
+
return {
|
|
12165
|
+
x: 0,
|
|
12166
|
+
top: -6 - lh * lines,
|
|
12167
|
+
align: "center",
|
|
12168
|
+
rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: (x1 + x2) / 2, cy: (y1 + y2) / 2 }
|
|
12169
|
+
};
|
|
12224
12170
|
}
|
|
12225
12171
|
case "box": {
|
|
12226
12172
|
if (pts.length < 2) return null;
|
|
@@ -12569,11 +12515,11 @@ function applyFree(anchor, pt, free) {
|
|
|
12569
12515
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
12570
12516
|
var BTN = 30;
|
|
12571
12517
|
var ICON = 21;
|
|
12572
|
-
var
|
|
12573
|
-
function
|
|
12574
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
12518
|
+
var STYLE_ID4 = "vela-drawing-popup-styles";
|
|
12519
|
+
function ensureStyles3() {
|
|
12520
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID4)) return;
|
|
12575
12521
|
const s = document.createElement("style");
|
|
12576
|
-
s.id =
|
|
12522
|
+
s.id = STYLE_ID4;
|
|
12577
12523
|
s.textContent = `
|
|
12578
12524
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
12579
12525
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -12607,17 +12553,14 @@ var DrawingSettingsPopup = class {
|
|
|
12607
12553
|
this.textPanel = null;
|
|
12608
12554
|
this.levelsPanel = null;
|
|
12609
12555
|
this.colorPop = null;
|
|
12610
|
-
// floating RGB + opacity picker for a color swatch
|
|
12611
12556
|
this.colorOwner = null;
|
|
12612
|
-
|
|
12613
|
-
this.menuEl = null;
|
|
12614
|
-
// floating option list for a dropdown control
|
|
12557
|
+
this.menuPop = null;
|
|
12615
12558
|
this.menuOwner = null;
|
|
12616
12559
|
this.onClose = null;
|
|
12617
12560
|
this.onOutside = (e) => {
|
|
12618
12561
|
const target = e.target;
|
|
12619
|
-
if (this.colorPop?.contains(target)) return;
|
|
12620
|
-
if (this.
|
|
12562
|
+
if (this.colorPop?.el.contains(target)) return;
|
|
12563
|
+
if (this.menuPop?.el.contains(target)) return;
|
|
12621
12564
|
if (this.el && !this.el.contains(target)) {
|
|
12622
12565
|
const cb = this.onClose;
|
|
12623
12566
|
this.close();
|
|
@@ -12636,13 +12579,13 @@ var DrawingSettingsPopup = class {
|
|
|
12636
12579
|
* picker / dropdown menu), which live outside `el`. */
|
|
12637
12580
|
contains(node) {
|
|
12638
12581
|
if (!node) return false;
|
|
12639
|
-
return this.el?.contains(node) === true || this.colorPop?.contains(node) === true || this.
|
|
12582
|
+
return this.el?.contains(node) === true || this.colorPop?.el.contains(node) === true || this.menuPop?.el.contains(node) === true;
|
|
12640
12583
|
}
|
|
12641
12584
|
/** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
|
|
12642
12585
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
12643
12586
|
open(drawing, anchor, actions, onClose) {
|
|
12644
12587
|
this.close();
|
|
12645
|
-
|
|
12588
|
+
ensureStyles3();
|
|
12646
12589
|
this.onClose = onClose ?? null;
|
|
12647
12590
|
const t = this.theme;
|
|
12648
12591
|
const schema = drawing.schema();
|
|
@@ -12653,8 +12596,7 @@ var DrawingSettingsPopup = class {
|
|
|
12653
12596
|
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;`;
|
|
12654
12597
|
applyChromeTokens(el, t);
|
|
12655
12598
|
el.addEventListener("pointerdown", () => {
|
|
12656
|
-
|
|
12657
|
-
this.closeMenu();
|
|
12599
|
+
closeOpenPopovers();
|
|
12658
12600
|
});
|
|
12659
12601
|
const bar = document.createElement("div");
|
|
12660
12602
|
bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
|
|
@@ -13391,49 +13333,76 @@ var DrawingSettingsPopup = class {
|
|
|
13391
13333
|
});
|
|
13392
13334
|
return b;
|
|
13393
13335
|
}
|
|
13336
|
+
/** Host-anchored floating shell (stays inside the chart). Content is filled after
|
|
13337
|
+
* construction so `fill` can close over the live `Popover` without hitting TDZ. */
|
|
13338
|
+
hostFloat(anchor, opts) {
|
|
13339
|
+
const t = this.theme;
|
|
13340
|
+
const pop = new Popover({
|
|
13341
|
+
trigger: anchor,
|
|
13342
|
+
host: this.host,
|
|
13343
|
+
theme: t,
|
|
13344
|
+
position: "absolute",
|
|
13345
|
+
boundary: this.host,
|
|
13346
|
+
boundaryInset: 4,
|
|
13347
|
+
viewportInset: 0,
|
|
13348
|
+
gap: 6,
|
|
13349
|
+
align: opts.align ?? "start",
|
|
13350
|
+
zIndex: opts.zIndex,
|
|
13351
|
+
onClose: () => {
|
|
13352
|
+
if (this.menuPop === pop) {
|
|
13353
|
+
this.menuPop = null;
|
|
13354
|
+
this.menuOwner = null;
|
|
13355
|
+
}
|
|
13356
|
+
if (this.colorPop === pop) {
|
|
13357
|
+
this.colorPop = null;
|
|
13358
|
+
this.colorOwner = null;
|
|
13359
|
+
}
|
|
13360
|
+
}
|
|
13361
|
+
});
|
|
13362
|
+
const el = pop.el;
|
|
13363
|
+
el.style.background = t.background;
|
|
13364
|
+
el.style.border = "1px solid var(--vela-border)";
|
|
13365
|
+
el.style.borderRadius = "var(--vela-radius-lg)";
|
|
13366
|
+
el.style.boxShadow = "var(--vela-shadow)";
|
|
13367
|
+
el.style.padding = opts.padding;
|
|
13368
|
+
el.style.display = "flex";
|
|
13369
|
+
el.style.flexDirection = "column";
|
|
13370
|
+
el.style.gap = "1px";
|
|
13371
|
+
el.style.color = t.textColor;
|
|
13372
|
+
el.style.font = `var(--vela-font-size-md) ${t.fontFamily}`;
|
|
13373
|
+
el.style.pointerEvents = "auto";
|
|
13374
|
+
opts.fill(el, pop);
|
|
13375
|
+
pop.show();
|
|
13376
|
+
return pop;
|
|
13377
|
+
}
|
|
13394
13378
|
/** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
|
|
13395
13379
|
openActionMenu(anchor, rows) {
|
|
13396
|
-
this.
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
}
|
|
13421
|
-
|
|
13422
|
-
}
|
|
13423
|
-
this.host.appendChild(menu);
|
|
13424
|
-
const ar = anchor.getBoundingClientRect();
|
|
13425
|
-
const hr = this.host.getBoundingClientRect();
|
|
13426
|
-
const w = menu.offsetWidth;
|
|
13427
|
-
const h = menu.offsetHeight;
|
|
13428
|
-
let left = ar.right - hr.left - w;
|
|
13429
|
-
let top = ar.bottom - hr.top + 6;
|
|
13430
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
13431
|
-
if (left < 4) left = 4;
|
|
13432
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
13433
|
-
if (top < 4) top = 4;
|
|
13434
|
-
menu.style.left = `${Math.round(left)}px`;
|
|
13435
|
-
menu.style.top = `${Math.round(top)}px`;
|
|
13436
|
-
this.menuEl = menu;
|
|
13380
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
13381
|
+
align: "end",
|
|
13382
|
+
zIndex: 26,
|
|
13383
|
+
padding: "4px",
|
|
13384
|
+
fill: (menu, pop) => {
|
|
13385
|
+
for (const row of rows) {
|
|
13386
|
+
const item = document.createElement("button");
|
|
13387
|
+
item.type = "button";
|
|
13388
|
+
item.className = "vela-dpop-item";
|
|
13389
|
+
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;";
|
|
13390
|
+
const ic = document.createElement("span");
|
|
13391
|
+
ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
|
|
13392
|
+
ic.innerHTML = sized(row.icon, 18);
|
|
13393
|
+
const tx = document.createElement("span");
|
|
13394
|
+
tx.textContent = row.label;
|
|
13395
|
+
tx.style.cssText = "flex:1;";
|
|
13396
|
+
item.append(ic, tx);
|
|
13397
|
+
item.addEventListener("click", (e) => {
|
|
13398
|
+
e.stopPropagation();
|
|
13399
|
+
pop.hide();
|
|
13400
|
+
row.onClick();
|
|
13401
|
+
});
|
|
13402
|
+
menu.appendChild(item);
|
|
13403
|
+
}
|
|
13404
|
+
}
|
|
13405
|
+
});
|
|
13437
13406
|
this.menuOwner = anchor;
|
|
13438
13407
|
}
|
|
13439
13408
|
/** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
|
|
@@ -13563,60 +13532,40 @@ var DrawingSettingsPopup = class {
|
|
|
13563
13532
|
/** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
|
|
13564
13533
|
* glyph (and text label when one is supplied); the current value is highlighted. */
|
|
13565
13534
|
openMenu(anchor, values, current, render, label, onPick) {
|
|
13566
|
-
this.
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13535
|
+
this.menuPop = this.hostFloat(anchor, {
|
|
13536
|
+
zIndex: 26,
|
|
13537
|
+
padding: "4px",
|
|
13538
|
+
fill: (menu, pop) => {
|
|
13539
|
+
for (const v of values) {
|
|
13540
|
+
const active = v === current;
|
|
13541
|
+
const item = document.createElement("button");
|
|
13542
|
+
item.type = "button";
|
|
13543
|
+
item.className = "vela-dpop-item";
|
|
13544
|
+
item.dataset.active = active ? "1" : "0";
|
|
13545
|
+
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;`;
|
|
13546
|
+
const ic = document.createElement("span");
|
|
13547
|
+
ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
|
|
13548
|
+
ic.innerHTML = sized(render(v), 18);
|
|
13549
|
+
item.appendChild(ic);
|
|
13550
|
+
if (label) {
|
|
13551
|
+
const tx = document.createElement("span");
|
|
13552
|
+
tx.textContent = label(v);
|
|
13553
|
+
tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
|
|
13554
|
+
item.appendChild(tx);
|
|
13555
|
+
}
|
|
13556
|
+
item.addEventListener("click", (e) => {
|
|
13557
|
+
e.stopPropagation();
|
|
13558
|
+
pop.hide();
|
|
13559
|
+
onPick(v);
|
|
13560
|
+
});
|
|
13561
|
+
menu.appendChild(item);
|
|
13562
|
+
}
|
|
13590
13563
|
}
|
|
13591
|
-
|
|
13592
|
-
e.stopPropagation();
|
|
13593
|
-
this.closeMenu();
|
|
13594
|
-
onPick(v);
|
|
13595
|
-
});
|
|
13596
|
-
menu.appendChild(item);
|
|
13597
|
-
}
|
|
13598
|
-
this.host.appendChild(menu);
|
|
13599
|
-
const ar = anchor.getBoundingClientRect();
|
|
13600
|
-
const hr = this.host.getBoundingClientRect();
|
|
13601
|
-
const w = menu.offsetWidth;
|
|
13602
|
-
const h = menu.offsetHeight;
|
|
13603
|
-
let left = ar.left - hr.left;
|
|
13604
|
-
let top = ar.bottom - hr.top + 6;
|
|
13605
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
13606
|
-
if (left < 4) left = 4;
|
|
13607
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
13608
|
-
if (top < 4) top = 4;
|
|
13609
|
-
menu.style.left = `${Math.round(left)}px`;
|
|
13610
|
-
menu.style.top = `${Math.round(top)}px`;
|
|
13611
|
-
this.menuEl = menu;
|
|
13564
|
+
});
|
|
13612
13565
|
this.menuOwner = anchor;
|
|
13613
13566
|
}
|
|
13614
13567
|
closeMenu() {
|
|
13615
|
-
|
|
13616
|
-
this.menuEl.remove();
|
|
13617
|
-
this.menuEl = null;
|
|
13618
|
-
}
|
|
13619
|
-
this.menuOwner = null;
|
|
13568
|
+
this.menuPop?.hide();
|
|
13620
13569
|
}
|
|
13621
13570
|
/** A color control: an `icon` over a thin **colored underline** showing the current
|
|
13622
13571
|
* value (the common idiom), with an invisible native picker overlaid to edit it.
|
|
@@ -13655,36 +13604,18 @@ var DrawingSettingsPopup = class {
|
|
|
13655
13604
|
}
|
|
13656
13605
|
/** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
|
|
13657
13606
|
openColorPopover(anchor, color, onChange) {
|
|
13658
|
-
this.closeColorPopover();
|
|
13659
|
-
this.closeMenu();
|
|
13660
13607
|
const t = this.theme;
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13668
|
-
const hr = this.host.getBoundingClientRect();
|
|
13669
|
-
const w = pop.offsetWidth;
|
|
13670
|
-
const h = pop.offsetHeight;
|
|
13671
|
-
let left = ar.left - hr.left;
|
|
13672
|
-
let top = ar.bottom - hr.top + 6;
|
|
13673
|
-
if (left + w > hr.width - 4) left = hr.width - w - 4;
|
|
13674
|
-
if (left < 4) left = 4;
|
|
13675
|
-
if (top + h > hr.height - 4) top = ar.top - hr.top - h - 6;
|
|
13676
|
-
if (top < 4) top = 4;
|
|
13677
|
-
pop.style.left = `${Math.round(left)}px`;
|
|
13678
|
-
pop.style.top = `${Math.round(top)}px`;
|
|
13679
|
-
this.colorPop = pop;
|
|
13608
|
+
this.colorPop = this.hostFloat(anchor, {
|
|
13609
|
+
zIndex: 25,
|
|
13610
|
+
padding: "10px",
|
|
13611
|
+
fill: (el) => {
|
|
13612
|
+
el.appendChild(buildColorPicker(color, t, onChange));
|
|
13613
|
+
}
|
|
13614
|
+
});
|
|
13680
13615
|
this.colorOwner = anchor;
|
|
13681
13616
|
}
|
|
13682
13617
|
closeColorPopover() {
|
|
13683
|
-
|
|
13684
|
-
this.colorPop.remove();
|
|
13685
|
-
this.colorPop = null;
|
|
13686
|
-
}
|
|
13687
|
-
this.colorOwner = null;
|
|
13618
|
+
this.colorPop?.hide();
|
|
13688
13619
|
}
|
|
13689
13620
|
divider() {
|
|
13690
13621
|
const d = document.createElement("div");
|
|
@@ -14067,9 +13998,9 @@ var UserDrawingController = class {
|
|
|
14067
13998
|
}
|
|
14068
13999
|
/**
|
|
14069
14000
|
* Union of the visible drawings' price ranges on `paneId` whose time extent
|
|
14070
|
-
* intersects [fromTime, toTime]
|
|
14071
|
-
*
|
|
14072
|
-
*
|
|
14001
|
+
* intersects [fromTime, toTime]. Hidden drawings and full-width references
|
|
14002
|
+
* (hline → null extent) are handled too. Kept as the seam for a future
|
|
14003
|
+
* per-drawing autoscale opt-in — the renderer does not fold this in today.
|
|
14073
14004
|
*/
|
|
14074
14005
|
priceRangeForPane(paneId, fromTime, toTime) {
|
|
14075
14006
|
let lo = Infinity;
|
|
@@ -14924,7 +14855,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
|
|
|
14924
14855
|
|
|
14925
14856
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
14926
14857
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
14927
|
-
var
|
|
14858
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
14928
14859
|
var CSS = `
|
|
14929
14860
|
.vela-attribution {
|
|
14930
14861
|
text-decoration: none;
|
|
@@ -14987,11 +14918,11 @@ var CSS = `
|
|
|
14987
14918
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
14988
14919
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
14989
14920
|
`;
|
|
14990
|
-
function
|
|
14991
|
-
let s = doc.getElementById(
|
|
14921
|
+
function ensureStyles4(doc) {
|
|
14922
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
14992
14923
|
if (!s) {
|
|
14993
14924
|
s = doc.createElement("style");
|
|
14994
|
-
s.id =
|
|
14925
|
+
s.id = STYLE_ID5;
|
|
14995
14926
|
doc.head.appendChild(s);
|
|
14996
14927
|
}
|
|
14997
14928
|
s.textContent = CSS;
|
|
@@ -15020,7 +14951,7 @@ function createCustomMark(doc, html, background) {
|
|
|
15020
14951
|
return el;
|
|
15021
14952
|
}
|
|
15022
14953
|
function createAttributionMark(doc, background) {
|
|
15023
|
-
|
|
14954
|
+
ensureStyles4(doc);
|
|
15024
14955
|
const a = doc.createElement("a");
|
|
15025
14956
|
a.className = "vela-attribution";
|
|
15026
14957
|
a.href = ATTRIBUTION_URL;
|
|
@@ -17998,7 +17929,6 @@ var NativeRenderer = class {
|
|
|
17998
17929
|
const n = this.coords.barCount;
|
|
17999
17930
|
if (n === 0) return;
|
|
18000
17931
|
const vr = this.coords.visibleLogicalRange();
|
|
18001
|
-
const vtr = this.coords.visibleTimeRange();
|
|
18002
17932
|
const i0 = Math.max(0, Math.floor(vr.from));
|
|
18003
17933
|
const i1 = Math.min(n - 1, Math.ceil(vr.to));
|
|
18004
17934
|
if (i1 < i0) return;
|
|
@@ -18016,9 +17946,7 @@ var NativeRenderer = class {
|
|
|
18016
17946
|
}
|
|
18017
17947
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
18018
17948
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
18019
|
-
|
|
18020
|
-
const udr = this.userDrawings?.priceRangeForPane(pane.id, vtr.from, vtr.to) ?? null;
|
|
18021
|
-
if (udr) dr = dr ? { min: Math.min(dr.min, udr.min), max: Math.max(dr.max, udr.max) } : udr;
|
|
17949
|
+
const dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
18022
17950
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
18023
17951
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
18024
17952
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|