@luxalgo/vela 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +3 -2
  2. package/dist/{DataProvider-DTOEXKTI.d.ts → DataProvider-Cqc_BaJ9.d.ts} +1 -1
  3. package/dist/{DataProvider-gnzv-dgc.d.cts → DataProvider-DgS2UyMc.d.cts} +1 -1
  4. package/dist/{chunk-L5NTMWPP.js → chunk-D6WM5IUE.js} +724 -851
  5. package/dist/{chunk-LJLZR44Y.js → chunk-EMS6PO2T.js} +5 -7
  6. package/dist/{chunk-EY63XBFZ.js → chunk-JBQUY2RI.js} +119 -12
  7. package/dist/chunk-OGRQW3M6.js +1328 -0
  8. package/dist/{chunk-P556H6EA.js → chunk-QWIEKZRE.js} +59 -34
  9. package/dist/{chunk-WJKLBYRM.js → chunk-ZNL2X6U2.js} +1 -1
  10. package/dist/{contributions-Ci_i3IN2.d.cts → contributions-4Nh1jbvb.d.cts} +2 -2
  11. package/dist/{contributions-b6AVrqqG.d.ts → contributions-DuIxJWeH.d.ts} +2 -2
  12. package/dist/{history-Cf1lVsap.d.cts → history-FsmvCIo4.d.ts} +5 -3
  13. package/dist/{history-Ci3M0xLV.d.ts → history-HofqYEh2.d.cts} +5 -3
  14. package/dist/index.cjs +2715 -1606
  15. package/dist/index.d.cts +6 -6
  16. package/dist/index.d.ts +6 -6
  17. package/dist/index.js +4 -4
  18. package/dist/{options-XTBpbx0s.d.ts → options-BlQ00Fju.d.cts} +3 -1
  19. package/dist/{options-XTBpbx0s.d.cts → options-BlQ00Fju.d.ts} +3 -1
  20. package/dist/{plugin-DlidwMj6.d.cts → plugin-DJR6z3e0.d.cts} +3 -3
  21. package/dist/{plugin-DP6A4t4o.d.ts → plugin-N_EaXN4-.d.ts} +3 -3
  22. package/dist/plugin.cjs +4 -0
  23. package/dist/plugin.d.cts +4 -4
  24. package/dist/plugin.d.ts +4 -4
  25. package/dist/plugin.js +2 -2
  26. package/dist/providers/binance.d.cts +2 -2
  27. package/dist/providers/binance.d.ts +2 -2
  28. package/dist/providers/coinbase.d.cts +2 -2
  29. package/dist/providers/coinbase.d.ts +2 -2
  30. package/dist/providers/hyperliquid.d.cts +2 -2
  31. package/dist/providers/hyperliquid.d.ts +2 -2
  32. package/dist/ui.cjs +1318 -4
  33. package/dist/ui.d.cts +383 -2
  34. package/dist/ui.d.ts +383 -2
  35. package/dist/ui.js +3 -3
  36. package/dist/vela.global.js +2709 -1600
  37. package/dist/vela.global.min.js +315 -66
  38. package/dist/widget.cjs +2275 -1032
  39. package/dist/widget.d.cts +25 -7
  40. package/dist/widget.d.ts +25 -7
  41. package/dist/widget.js +27 -9
  42. package/dist/workspace.cjs +2181 -926
  43. package/dist/workspace.d.cts +10 -5
  44. package/dist/workspace.d.ts +10 -5
  45. package/dist/workspace.js +25 -7
  46. package/package.json +1 -1
  47. 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-WJKLBYRM.js';
2
- import { themeTokens, STATIC_TOKENS } from './chunk-ATPU5CI6.js';
3
- import { icon, iconAt, withAlpha, NEUTRAL, BEARISH, BULLISH, isDarkColor, toHex6, ACCENT, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, SLATE_DEEP, VALID, INVALID, svg24, WARNING, svg24Solid } from './chunk-LJLZR44Y.js';
1
+ import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-ZNL2X6U2.js';
2
+ import { themeTokens, closeOpenPopovers, Select, NumberInput, colorField, Switch, blendOver, splitColor, isPopoverOpen, TextField, Popover, openPopoverTrigger, buildColorPicker, toggleSelectList, STATIC_TOKENS } from './chunk-OGRQW3M6.js';
3
+ import { icon, iconAt, withAlpha, NEUTRAL, BEARISH, BULLISH, isDarkColor, ACCENT, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, injectStyles, SLATE_DEEP, VALID, INVALID, svg24, svg24Solid } from './chunk-EMS6PO2T.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
6
6
  var TypedEventBus = class {
@@ -2844,6 +2844,7 @@ var LEGEND_CTL_PX = 18;
2844
2844
  var LEGEND_ROW_PAD_Y = 2;
2845
2845
  var LEGEND_ROW_PAD_X = 6;
2846
2846
  var LEGEND_TITLE_VALUES_GAP_PX = 6;
2847
+ var LEGEND_TITLE_STATUS_GAP_PX = 8;
2847
2848
  var LEGEND_ACTION_GAP_PX = 6;
2848
2849
  var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
2849
2850
  var LEGEND_ROW_MIN_H = LEGEND_CTL_PX + LEGEND_ROW_PAD_Y * 2;
@@ -2888,6 +2889,11 @@ var InputsUI = class {
2888
2889
  this.dialogTips = [];
2889
2890
  /** Open "Move to" menu (kept so it can be torn down). */
2890
2891
  this.moveMenu = null;
2892
+ /** Open settings-dialog choice menu (custom dropdown — torn down with the dialog). */
2893
+ this.choicePop = null;
2894
+ /** Open settings-dialog calendar popover (torn down with the dialog). */
2895
+ this.calendarPop = null;
2896
+ this.calendarAnchor = null;
2891
2897
  /** Collapsed panes → the master indicator id to keep visible (others hidden in the strip). */
2892
2898
  this.paneCollapse = /* @__PURE__ */ new Map();
2893
2899
  /** The user folded the indicator legend away behind the chevron — CHART-WIDE: every
@@ -2913,6 +2919,10 @@ var InputsUI = class {
2913
2919
  this.onDialogKey = (e) => {
2914
2920
  if (e.key === "Escape") {
2915
2921
  e.preventDefault();
2922
+ if (isPopoverOpen()) {
2923
+ closeOpenPopovers();
2924
+ return;
2925
+ }
2916
2926
  this.closeDialog();
2917
2927
  }
2918
2928
  };
@@ -3358,6 +3368,7 @@ var InputsUI = class {
3358
3368
  titleWrap.appendChild(beta);
3359
3369
  }
3360
3370
  el.appendChild(titleWrap);
3371
+ el.appendChild(statusEl);
3361
3372
  const valuesEl = document.createElement("span");
3362
3373
  valuesEl.style.cssText = `display:none;align-items:center;gap:5px;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
3363
3374
  el.appendChild(valuesEl);
@@ -3418,7 +3429,6 @@ var InputsUI = class {
3418
3429
  close.addEventListener("click", () => this.onRemove?.(id));
3419
3430
  controlsEl.appendChild(close);
3420
3431
  el.appendChild(controlsEl);
3421
- el.appendChild(statusEl);
3422
3432
  this.attach(this.legendFor(paneId), el, !!opts.native);
3423
3433
  this.rows.set(id, { id, title, settingsTitle, inputs, values: { ...values }, el, titleEl, statusEl, valuesEl, plotValues: [], plotValuesKey: "", showValues: null, highlighted: false, paneId, hidden: false, eyeEl, controlsEl, extrasEl, native: !!opts.native });
3424
3434
  this.syncFoldToggle();
@@ -3436,7 +3446,7 @@ var InputsUI = class {
3436
3446
  /**
3437
3447
  * Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
3438
3448
  * dots (a fetch is in flight — the same load affordance as the chart's own bar-load dots,
3439
- * at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered at the row's right end.
3449
+ * at legend scale), `'live'` a pulsing dot, `'idle'` nothing. Rendered just right of the title.
3440
3450
  */
3441
3451
  setStatus(id, status) {
3442
3452
  const row = this.rows.get(id);
@@ -3448,7 +3458,7 @@ var InputsUI = class {
3448
3458
  return;
3449
3459
  }
3450
3460
  if (status === "loading") {
3451
- el.style.cssText = "display:inline-flex;align-items:center;gap:3px;box-sizing:border-box;flex:none;";
3461
+ el.style.cssText = `display:inline-flex;align-items:center;gap:3px;box-sizing:border-box;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;line-height:0;transform:translateY(1px);`;
3452
3462
  for (let i = 0; i < 3; i += 1) {
3453
3463
  const dot = document.createElement("span");
3454
3464
  dot.style.cssText = "width:4px;height:4px;border-radius:50%;background:currentColor;opacity:0.15;flex:none;";
@@ -3458,7 +3468,7 @@ var InputsUI = class {
3458
3468
  return;
3459
3469
  }
3460
3470
  this.ensureStatusKeyframes();
3461
- el.style.cssText = `display:inline-block;box-sizing:border-box;flex:none;width:8px;height:8px;border-radius:50%;background:${this.theme.upColor};animation:vela-ind-pulse 1.2s ease-in-out infinite;`;
3471
+ el.style.cssText = `display:inline-block;box-sizing:border-box;flex:none;width:8px;height:8px;border-radius:50%;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;transform:translateY(1px);background:${this.theme.upColor};animation:vela-ind-pulse 1.2s ease-in-out infinite;`;
3462
3472
  }
3463
3473
  /** Inject the status keyframes once (idempotent). */
3464
3474
  ensureStatusKeyframes() {
@@ -3573,7 +3583,8 @@ var InputsUI = class {
3573
3583
  /**
3574
3584
  * Open the indicator's settings as a centered modal over the chart — a themed,
3575
3585
  * dependency-free port of the app's tabbed script-settings dialog: a header with the
3576
- * indicator title, an "Inputs" tab, a scrollable body that groups inputs by `group=`
3586
+ * indicator title, a tab strip (the default "Inputs" tab plus any tab the inputs
3587
+ * declare via `tab=`), a scrollable body per tab that groups inputs by `group=`
3577
3588
  * (section headers) and packs `inline=` inputs onto one row, and a sticky footer.
3578
3589
  *
3579
3590
  * Edits commit LIVE (each change re-runs the indicator), matching the current
@@ -3603,55 +3614,57 @@ var InputsUI = class {
3603
3614
  });
3604
3615
  const card = document.createElement("div");
3605
3616
  card.className = "vela-ind-dialog";
3606
- card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(360px,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:13px ${font};overflow:hidden;`;
3617
+ card.style.cssText = `display:flex;flex-direction:column;width:fit-content;min-width:min(380px,90%);max-width:min(640px,94%);max-height:82%;background:${t.background};border:1px solid ${border};border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow-dialog);color:${fg};color-scheme:${scheme};font:14px ${font};overflow:hidden;`;
3607
3618
  if (this.mobileLayout) card.style.cssText += "width:100%;min-width:0;max-width:none;height:100%;max-height:none;border:none;border-radius:0;";
3608
3619
  applyChromeTokens(card, t);
3609
3620
  card.addEventListener("keydown", (e) => {
3610
3621
  if (e.key !== "Escape") e.stopPropagation();
3611
3622
  });
3612
3623
  const header = document.createElement("div");
3613
- header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 12px;flex:0 0 auto;";
3624
+ header.style.cssText = "display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:16px 20px 20px;flex:0 0 auto;";
3614
3625
  const hTitle = document.createElement("span");
3615
3626
  hTitle.textContent = row.settingsTitle;
3616
- hTitle.style.cssText = "font-weight:600;font-size:16px;line-height:1.3;";
3627
+ hTitle.style.cssText = "font-weight:600;font-size:20px;line-height:28px;";
3617
3628
  const closeBtn = document.createElement("button");
3618
3629
  closeBtn.type = "button";
3619
3630
  closeBtn.setAttribute("aria-label", "Close");
3620
3631
  this.dialogTips.push(attachChromeTooltip(closeBtn, { host: this.container, theme: () => this.theme, text: () => "Close" }));
3621
3632
  closeBtn.innerHTML = iconAt("close", 15);
3622
- closeBtn.className = "vela-ind-ctl";
3623
- closeBtn.style.cssText = "cursor:pointer;display:inline-flex;align-items:center;background:transparent;border:none;line-height:0;padding:2px;flex:0 0 auto;";
3633
+ closeBtn.className = "vela-ind-dlg-close";
3624
3634
  closeBtn.addEventListener("click", () => this.closeDialog());
3625
3635
  header.append(hTitle, closeBtn);
3626
3636
  card.appendChild(header);
3627
3637
  if (!this.mobileLayout) makeDialogDraggable(card, header, { closeSelector: "button" });
3638
+ const tabDefs = tabInputs(row.inputs);
3628
3639
  const tabs = document.createElement("div");
3629
- tabs.style.cssText = `display:flex;gap:16px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
3630
- const tab = document.createElement("div");
3631
- tab.textContent = "Inputs";
3632
- tab.style.cssText = `padding:8px 2px;margin-bottom:-1px;border-bottom:2px solid ${fg};font-weight:600;font-size:13px;`;
3633
- tabs.appendChild(tab);
3634
- card.appendChild(tabs);
3635
- const body = document.createElement("div");
3636
- body.style.cssText = "padding:14px 20px 6px;overflow-y:auto;flex:1 1 auto;display:flex;flex-direction:column;gap:24px;";
3637
- for (const group of groupInputs(row.inputs)) {
3638
- const section = document.createElement("div");
3639
- section.style.cssText = "display:flex;flex-direction:column;gap:14px;";
3640
- if (group.name) {
3641
- const gh = document.createElement("div");
3642
- gh.textContent = group.name;
3643
- gh.style.cssText = "font-size:var(--vela-font-size-sm);font-weight:600;letter-spacing:0.05em;text-transform:uppercase;color:var(--vela-fg-muted);";
3644
- section.appendChild(gh);
3640
+ tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
3641
+ const tabEls = [];
3642
+ const bodies = [];
3643
+ const activate = (idx) => {
3644
+ for (let i = 0; i < tabEls.length; i += 1) {
3645
+ const active = i === idx;
3646
+ const el = tabEls[i];
3647
+ el.style.borderBottomColor = active ? fg : "transparent";
3648
+ el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
3649
+ el.classList.toggle("vela-ind-tab-active", active);
3650
+ bodies[i].style.display = active ? "grid" : "none";
3645
3651
  }
3646
- const grid = document.createElement("div");
3647
- grid.style.cssText = this.mobileLayout ? "display:grid;grid-template-columns:minmax(0,1fr) auto auto max-content;align-items:center;column-gap:12px;row-gap:22px;" : "display:grid;grid-template-columns:minmax(140px,auto) auto auto 1fr;align-items:center;column-gap:12px;row-gap:22px;";
3648
- for (const inputRow of group.rows) this.buildInputRow(grid, inputRow, row);
3649
- section.appendChild(grid);
3650
- body.appendChild(section);
3652
+ };
3653
+ for (const [idx, def] of tabDefs.entries()) {
3654
+ const tab = document.createElement("div");
3655
+ tab.textContent = def.name;
3656
+ tab.className = "vela-ind-tab";
3657
+ tab.style.cssText = `padding:0 0 8px;margin-bottom:-1px;border-bottom:2px solid transparent;font-weight:600;font-size:16px;line-height:24px;${tabDefs.length > 1 ? "cursor:pointer;" : ""}`;
3658
+ tab.addEventListener("click", () => activate(idx));
3659
+ tabs.appendChild(tab);
3660
+ tabEls.push(tab);
3661
+ bodies.push(this.buildTabBody(def.inputs, row));
3651
3662
  }
3652
- card.appendChild(body);
3663
+ card.appendChild(tabs);
3664
+ for (const body of bodies) card.appendChild(body);
3665
+ activate(0);
3653
3666
  const footer = document.createElement("div");
3654
- footer.style.cssText = `display:flex;justify-content:flex-end;gap:8px;padding:14px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
3667
+ footer.style.cssText = `display:flex;justify-content:flex-end;gap:12px;padding:16px 20px;border-top:1px solid ${border};flex:0 0 auto;`;
3655
3668
  footer.append(
3656
3669
  this.dialogButton("Cancel", false, () => this.revertAndClose()),
3657
3670
  this.dialogButton("Ok", true, () => this.closeDialog())
@@ -3663,8 +3676,32 @@ var InputsUI = class {
3663
3676
  this.backdrop = backdrop;
3664
3677
  document.addEventListener("keydown", this.onDialogKey);
3665
3678
  }
3679
+ /** One tab's scrollable body — one shared grid so every group's controls share a
3680
+ * left edge (a 100px number field lines up with a wider session/time pair). */
3681
+ buildTabBody(inputs, row) {
3682
+ const body = document.createElement("div");
3683
+ body.style.cssText = this.mobileLayout ? "padding:16px 20px;overflow-y:auto;overflow-x:hidden;overflow-anchor:none;flex:1 1 auto;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;column-gap:16px;row-gap:16px;" : "padding:16px 20px;overflow-y:auto;overflow-x:hidden;overflow-anchor:none;flex:1 1 auto;display:grid;grid-template-columns:max-content 1fr;align-items:center;column-gap:16px;row-gap:16px;";
3684
+ for (const group of groupInputs(inputs)) {
3685
+ const section = document.createElement("div");
3686
+ section.style.cssText = "display:contents;";
3687
+ if (group.name) {
3688
+ const gh = document.createElement("div");
3689
+ gh.textContent = group.name;
3690
+ const first = body.childElementCount === 0;
3691
+ gh.style.cssText = `grid-column:1 / -1;font-size:11px;font-weight:600;letter-spacing:0.05em;text-transform:uppercase;color:var(--vela-fg-muted);padding:${first ? "4px" : "20px"} 0 8px;`;
3692
+ section.appendChild(gh);
3693
+ }
3694
+ for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
3695
+ body.appendChild(section);
3696
+ }
3697
+ return body;
3698
+ }
3666
3699
  closeDialog() {
3667
3700
  document.removeEventListener("keydown", this.onDialogKey);
3701
+ closeOpenPopovers();
3702
+ this.choicePop = null;
3703
+ this.calendarPop = null;
3704
+ this.calendarAnchor = null;
3668
3705
  for (const dispose of this.dialogTips.splice(0)) dispose();
3669
3706
  this.backdrop?.remove();
3670
3707
  this.backdrop = null;
@@ -3692,79 +3729,77 @@ var InputsUI = class {
3692
3729
  buildInputRow(grid, decls, row) {
3693
3730
  const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
3694
3731
  if (decls.length > 1) {
3695
- const cell = document.createElement("div");
3696
- cell.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
3732
+ const cell2 = document.createElement("div");
3733
+ cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:12px;";
3697
3734
  for (const d of decls) {
3698
3735
  const fit2 = d.type === "color" || d.type === "bool";
3699
3736
  const toggleFirst = d.type === "bool";
3700
3737
  const id2 = idOf(d);
3701
3738
  const item = document.createElement("div");
3702
- item.style.cssText = `display:flex;align-items:center;gap:6px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
3739
+ item.style.cssText = `display:flex;align-items:center;gap:8px;${toggleFirst ? "flex-direction:row-reverse;" : ""}`;
3703
3740
  const name = nameOf(d);
3704
3741
  if (name) {
3705
3742
  const lbl = document.createElement("label");
3706
3743
  lbl.htmlFor = id2;
3707
3744
  lbl.textContent = name;
3708
- lbl.style.cssText = `font-size:13px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
3745
+ lbl.style.cssText = `font-size:14px;opacity:0.9;${toggleFirst ? "cursor:pointer;" : ""}`;
3709
3746
  item.appendChild(lbl);
3710
3747
  }
3711
3748
  const cw2 = document.createElement("div");
3712
- cw2.style.cssText = fit2 ? "" : "width:110px;";
3749
+ cw2.style.cssText = fit2 ? "" : "width:100px;";
3713
3750
  cw2.appendChild(this.buildControl(row, d, id2));
3714
3751
  item.appendChild(cw2);
3715
- cell.appendChild(item);
3752
+ cell2.appendChild(item);
3716
3753
  }
3717
3754
  const lastTip = [...decls].reverse().find((d) => d.tooltip)?.tooltip;
3718
- if (lastTip) cell.appendChild(this.infoButton(lastTip));
3719
- grid.appendChild(cell);
3755
+ if (lastTip) cell2.appendChild(this.infoButton(lastTip));
3756
+ grid.appendChild(cell2);
3720
3757
  return;
3721
3758
  }
3722
3759
  const lead = decls[0];
3723
3760
  const id = idOf(lead);
3724
3761
  if (lead.type === "bool") {
3725
- const cell = document.createElement("div");
3726
- cell.style.cssText = "grid-column:1 / 4;display:flex;align-items:center;gap:8px;";
3762
+ const cell2 = document.createElement("div");
3763
+ cell2.style.cssText = "grid-column:1 / -1;display:flex;align-items:center;gap:8px;";
3727
3764
  const lbl = document.createElement("label");
3728
- lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px;";
3765
+ lbl.style.cssText = "display:flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;";
3729
3766
  lbl.appendChild(this.buildControl(row, lead, id));
3730
3767
  lbl.appendChild(document.createTextNode(nameOf(lead)));
3731
- cell.appendChild(lbl);
3732
- if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
3733
- grid.appendChild(cell);
3734
- grid.appendChild(document.createElement("div"));
3768
+ cell2.appendChild(lbl);
3769
+ if (lead.tooltip) cell2.appendChild(this.infoButton(lead.tooltip));
3770
+ grid.appendChild(cell2);
3735
3771
  return;
3736
3772
  }
3737
3773
  if (lead.type === "text_area") {
3738
- const cell = document.createElement("div");
3739
- cell.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
3774
+ const cell2 = document.createElement("div");
3775
+ cell2.style.cssText = "grid-column:1 / -1;display:flex;flex-direction:column;gap:8px;";
3740
3776
  const head = document.createElement("div");
3741
3777
  head.style.cssText = "display:flex;align-items:center;justify-content:center;gap:8px;";
3742
3778
  const lbl = document.createElement("label");
3743
3779
  lbl.htmlFor = id;
3744
3780
  lbl.textContent = nameOf(lead);
3745
- lbl.style.cssText = "font-size:13px;";
3781
+ lbl.style.cssText = "font-size:14px;";
3746
3782
  head.appendChild(lbl);
3747
3783
  if (lead.tooltip) head.appendChild(this.infoButton(lead.tooltip));
3748
- cell.appendChild(head);
3749
- cell.appendChild(this.buildControl(row, lead, id));
3750
- grid.appendChild(cell);
3784
+ cell2.appendChild(head);
3785
+ cell2.appendChild(this.buildControl(row, lead, id));
3786
+ grid.appendChild(cell2);
3751
3787
  return;
3752
3788
  }
3753
3789
  const label = document.createElement("label");
3754
3790
  label.htmlFor = id;
3755
3791
  label.textContent = nameOf(lead);
3756
- label.style.cssText = "font-size:13px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
3757
- const controlCell = document.createElement("div");
3758
- controlCell.style.cssText = "grid-column:span 2;display:flex;align-items:center;gap:8px;";
3792
+ label.style.cssText = "font-size:14px;opacity:0.9;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
3759
3793
  const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
3794
+ const cell = document.createElement("div");
3795
+ cell.style.cssText = "display:flex;align-items:center;gap:8px;justify-self:start;";
3760
3796
  const cw = document.createElement("div");
3761
- cw.style.cssText = fit ? "" : "width:140px;";
3797
+ cw.style.cssText = fit ? "" : "width:100px;";
3762
3798
  cw.appendChild(this.buildControl(row, lead, id));
3763
- controlCell.appendChild(cw);
3764
- if (lead.tooltip) controlCell.appendChild(this.infoButton(lead.tooltip));
3799
+ cell.appendChild(cw);
3800
+ if (lead.tooltip) cell.appendChild(this.infoButton(lead.tooltip));
3765
3801
  grid.appendChild(label);
3766
- grid.appendChild(controlCell);
3767
- grid.appendChild(document.createElement("div"));
3802
+ grid.appendChild(cell);
3768
3803
  }
3769
3804
  /** Build the typed control for one input, committing edits live via `onChange`. */
3770
3805
  buildControl(row, inp, id) {
@@ -3773,107 +3808,30 @@ var InputsUI = class {
3773
3808
  row.values[inp.key] = value;
3774
3809
  this.onChange?.({ indicatorId: row.id, key: inp.key, value });
3775
3810
  };
3776
- const ctrl = this.ctrlStyle();
3777
3811
  if (inp.type === "bool") return this.buildToggle(id, Boolean(current), emit);
3778
- if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), `${ctrl}cursor:pointer;`, emit);
3779
- if (inp.type === "source") return this.select(id, SOURCES, String(current), `${ctrl}cursor:pointer;`, emit);
3812
+ if (inp.options && inp.options.length > 0) return this.select(id, inp.options.map(String), String(current), emit);
3813
+ if (inp.type === "source") return this.select(id, SOURCES, String(current), emit);
3780
3814
  if (inp.type === "color") {
3781
- const ci = document.createElement("input");
3782
- ci.type = "color";
3783
- ci.id = id;
3784
- ci.value = toHex6(String(current));
3785
- ci.style.cssText = `flex:0 0 auto;box-sizing:border-box;width:32px;height:32px;padding:2px;border:1px solid ${this.neutralBorder()};border-radius:0;background:transparent;cursor:pointer;`;
3786
- ci.addEventListener("input", () => emit(ci.value));
3787
- return ci;
3815
+ const field = colorField(this.theme, () => String(row.values[inp.key] ?? inp.defval), (v) => emit(v), { shape: "circle", id });
3816
+ return field;
3788
3817
  }
3789
3818
  if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
3790
- if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), `${ctrl}cursor:pointer;`, emit);
3819
+ if (inp.type === "timeframe") return this.selectPairs(id, TIMEFRAME_OPTIONS, String(current), emit);
3791
3820
  if (inp.type === "session") return this.buildSession(id, String(current), emit);
3792
3821
  if (inp.type === "time") return this.buildTime(id, Number(current) || 0, emit);
3793
3822
  if (inp.type === "text_area") return this.buildTextArea(id, String(current), emit);
3794
3823
  if (inp.type === "int" || inp.type === "float" || inp.type === "price") {
3795
- const ni = document.createElement("input");
3796
- ni.type = "number";
3797
- ni.id = id;
3798
- ni.value = String(current);
3799
- if (inp.min !== void 0) ni.min = String(inp.min);
3800
- if (inp.max !== void 0) ni.max = String(inp.max);
3801
- ni.step = String(inp.step ?? (inp.type === "int" ? 1 : 0.1));
3802
- ni.style.cssText = ctrl;
3803
- let last = String(current);
3804
- const commit = () => {
3805
- let n = Number(ni.value);
3806
- if (!Number.isFinite(n)) {
3807
- ni.value = last;
3808
- return;
3809
- }
3810
- if (inp.min !== void 0) n = Math.max(inp.min, n);
3811
- if (inp.max !== void 0) n = Math.min(inp.max, n);
3812
- if (inp.type === "int") n = Math.round(n);
3813
- ni.value = String(n);
3814
- if (String(n) !== last) {
3815
- last = String(n);
3816
- emit(n);
3817
- }
3818
- };
3819
- ni.addEventListener("blur", commit);
3820
- ni.addEventListener("keydown", (e) => {
3821
- if (e.key === "Enter") {
3822
- e.preventDefault();
3823
- ni.blur();
3824
- }
3825
- });
3826
- return ni;
3824
+ return this.buildNumber(id, Number(current), inp, emit);
3827
3825
  }
3828
3826
  return this.buildTextField(id, String(current), emit);
3829
3827
  }
3830
3828
  /** A plain text field that commits on blur / Enter (shared by string inputs and the pickerless symbol field). */
3831
3829
  buildTextField(id, current, emit) {
3832
- const ti = document.createElement("input");
3833
- ti.type = "text";
3834
- ti.id = id;
3835
- ti.value = current;
3836
- ti.style.cssText = this.ctrlStyle();
3837
- let last = current;
3838
- ti.addEventListener("blur", () => {
3839
- if (ti.value !== last) {
3840
- last = ti.value;
3841
- emit(ti.value);
3842
- }
3843
- });
3844
- ti.addEventListener("keydown", (e) => {
3845
- if (e.key === "Enter") {
3846
- e.preventDefault();
3847
- ti.blur();
3848
- }
3849
- });
3850
- return ti;
3830
+ return new TextField({ id, value: current, size: "md", onChange: emit }).el;
3851
3831
  }
3852
3832
  /** A square check-toggle (filled + check when on) — replaces the raw checkbox. */
3853
3833
  buildToggle(id, checked, onChange) {
3854
- const t = this.theme;
3855
- const border = this.neutralBorder();
3856
- const fill = this.strongText();
3857
- const b = document.createElement("button");
3858
- b.type = "button";
3859
- b.id = id;
3860
- b.setAttribute("role", "switch");
3861
- b.innerHTML = CHECK_SVG;
3862
- b.style.cssText = `width:20px;height:20px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;border:1px solid ${border};border-radius:5px;cursor:pointer;padding:0;transition:background .12s ease,border-color .12s ease;`;
3863
- let on = checked;
3864
- const paint = (v) => {
3865
- b.setAttribute("aria-checked", v ? "true" : "false");
3866
- b.style.background = v ? fill : "transparent";
3867
- b.style.borderColor = v ? fill : border;
3868
- b.style.color = v ? t.background : "transparent";
3869
- };
3870
- paint(checked);
3871
- b.addEventListener("click", () => {
3872
- on = !on;
3873
- paint(on);
3874
- onChange(on);
3875
- });
3876
- return b;
3834
+ return new Switch({ id, checked, size: "md", tone: "bright", onChange }).el;
3877
3835
  }
3878
3836
  /** A hoverable ⓘ affordance carrying an input's `tooltip` (themed chrome tip; wraps). */
3879
3837
  infoButton(tooltip) {
@@ -3885,45 +3843,22 @@ var InputsUI = class {
3885
3843
  return b;
3886
3844
  }
3887
3845
  /** A dropdown over plain string options (value === label) — a thin case of {@link selectPairs}. */
3888
- select(id, options, current, style, emit) {
3889
- return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, style, emit);
3846
+ select(id, options, current, emit) {
3847
+ return this.selectPairs(id, options.map((o) => ({ value: o, label: o })), current, emit);
3890
3848
  }
3891
3849
  /** A dropdown whose visible labels differ from the committed values (label ≠ value pairs). */
3892
- selectPairs(id, pairs, current, style, onChange) {
3893
- const sel = document.createElement("select");
3894
- sel.id = id;
3895
- sel.style.cssText = style;
3896
- if (current !== "" && !pairs.some((p) => p.value === current)) {
3897
- const o = document.createElement("option");
3898
- o.value = current;
3899
- o.textContent = current;
3900
- o.selected = true;
3901
- o.disabled = true;
3902
- sel.appendChild(o);
3903
- }
3904
- for (const p of pairs) {
3905
- const o = document.createElement("option");
3906
- o.value = p.value;
3907
- o.textContent = p.label;
3908
- if (p.value === current) o.selected = true;
3909
- sel.appendChild(o);
3910
- }
3911
- this.styleSelect(sel);
3912
- sel.addEventListener("change", () => onChange(sel.value));
3913
- return this.wrapSelectChevron(sel);
3914
- }
3915
- /** Wrap a `<select>` with an inset chevron — native arrows can't be repositioned reliably. */
3916
- wrapSelectChevron(sel) {
3917
- const wrap = document.createElement("div");
3918
- wrap.style.cssText = "position:relative;width:100%;";
3919
- sel.style.paddingRight = "28px";
3920
- const chevron = document.createElement("span");
3921
- chevron.innerHTML = SELECT_CHEVRON_SVG;
3922
- chevron.style.cssText = `position:absolute;right:10px;top:50%;transform:translateY(-50%);pointer-events:none;display:flex;align-items:center;color:${this.strongText()};opacity:0.85;line-height:0;`;
3923
- wrap.append(sel, chevron);
3924
- return wrap;
3850
+ selectPairs(id, pairs, current, onChange) {
3851
+ return new Select({
3852
+ id,
3853
+ options: pairs,
3854
+ value: current,
3855
+ size: "md",
3856
+ theme: this.theme,
3857
+ list: this.listPopover(),
3858
+ onChange
3859
+ }).el;
3925
3860
  }
3926
- /** `input.session` → two HH:MM dropdowns committing a `HHMM-HHMM` session string. */
3861
+ /** `input.session` → two typeable HH:MM comboboxes committing a `HHMM-HHMM` session string. */
3927
3862
  buildSession(id, current, emit) {
3928
3863
  const [start, end] = sessionToTimes(current);
3929
3864
  const wrap = document.createElement("div");
@@ -3931,15 +3866,16 @@ var InputsUI = class {
3931
3866
  let from = start;
3932
3867
  let to = end;
3933
3868
  const commit = () => emit(timesToSession(from, to));
3934
- const startSel = this.timeSelect(id, from, 86, (v) => {
3935
- from = v;
3936
- commit();
3937
- });
3938
- const endSel = this.timeSelect(`${id}-end`, to, 86, (v) => {
3939
- to = v;
3940
- commit();
3941
- });
3942
- wrap.append(startSel, endSel);
3869
+ wrap.append(
3870
+ this.timeCombobox(id, from, 86, (v) => {
3871
+ from = v;
3872
+ commit();
3873
+ }),
3874
+ this.timeCombobox(`${id}-end`, to, 86, (v) => {
3875
+ to = v;
3876
+ commit();
3877
+ })
3878
+ );
3943
3879
  return wrap;
3944
3880
  }
3945
3881
  /** `input.time` → a date picker + an HH:MM dropdown, committing an epoch-ms timestamp. */
@@ -3954,20 +3890,15 @@ var InputsUI = class {
3954
3890
  const ms = Date.parse(`${date}T${time}:00`);
3955
3891
  if (Number.isFinite(ms)) emit(ms);
3956
3892
  };
3957
- const dateInput = document.createElement("input");
3958
- dateInput.type = "date";
3959
- dateInput.id = id;
3960
- dateInput.value = date;
3961
- dateInput.style.cssText = `${this.ctrlStyle()}width:auto;cursor:pointer;`;
3962
- dateInput.addEventListener("change", () => {
3963
- date = dateInput.value;
3893
+ const dateField = this.dateField(id, date, (v) => {
3894
+ date = v;
3964
3895
  commit();
3965
3896
  });
3966
- const timeSel = this.timeSelect(`${id}-time`, time, 86, (v) => {
3897
+ const timeSel = this.timeCombobox(`${id}-time`, time, 86, (v) => {
3967
3898
  time = v;
3968
3899
  commit();
3969
3900
  });
3970
- wrap.append(dateInput, timeSel);
3901
+ wrap.append(dateField, timeSel);
3971
3902
  return wrap;
3972
3903
  }
3973
3904
  /** `input.text_area` → a multi-line textarea, committed on blur. */
@@ -3986,9 +3917,261 @@ var InputsUI = class {
3986
3917
  });
3987
3918
  return ta;
3988
3919
  }
3989
- /** A fixed-width HH:MM dropdown (30-minute steps) used by the session + time controls. */
3990
- timeSelect(id, current, width, onChange) {
3991
- return this.selectPairs(id, TIME_OPTIONS, current, `${this.ctrlStyle()}width:${width}px;cursor:pointer;`, onChange);
3920
+ /** A fixed-width HH:MM combobox (typeable, plus a 30-minute-step dropdown) used by session + time. */
3921
+ timeCombobox(id, current, width, onChange) {
3922
+ const wrap = document.createElement("div");
3923
+ wrap.className = "vela-ind-combo";
3924
+ wrap.style.cssText = `position:relative;width:${width}px;`;
3925
+ const input = document.createElement("input");
3926
+ input.type = "text";
3927
+ input.id = id;
3928
+ input.value = current;
3929
+ input.autocomplete = "off";
3930
+ input.spellcheck = false;
3931
+ input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
3932
+ const chevron = document.createElement("button");
3933
+ chevron.type = "button";
3934
+ chevron.tabIndex = -1;
3935
+ chevron.className = "vela-ind-combo-chevron";
3936
+ chevron.setAttribute("aria-label", "Open time list");
3937
+ chevron.innerHTML = CLOCK_SVG;
3938
+ let value = current;
3939
+ const commitTyped = () => {
3940
+ const next = normalizeTimeInput(input.value);
3941
+ if (!next) {
3942
+ input.value = value;
3943
+ return;
3944
+ }
3945
+ input.value = next;
3946
+ if (next !== value) {
3947
+ value = next;
3948
+ onChange(next);
3949
+ }
3950
+ };
3951
+ input.addEventListener("blur", () => {
3952
+ if (isPopoverOpen()) return;
3953
+ commitTyped();
3954
+ });
3955
+ const pick = (v) => {
3956
+ value = v;
3957
+ input.value = v;
3958
+ onChange(v);
3959
+ };
3960
+ const openList = () => {
3961
+ this.choicePop = toggleSelectList(wrap, TIME_OPTIONS, value, pick, {
3962
+ ...this.listPopover(),
3963
+ onClose: () => {
3964
+ this.choicePop = null;
3965
+ }
3966
+ });
3967
+ };
3968
+ input.addEventListener("keydown", (e) => {
3969
+ if (e.key === "Enter") {
3970
+ e.preventDefault();
3971
+ commitTyped();
3972
+ input.blur();
3973
+ }
3974
+ if (e.key === "ArrowDown") {
3975
+ e.preventDefault();
3976
+ openList();
3977
+ }
3978
+ });
3979
+ const open = (e) => {
3980
+ e.preventDefault();
3981
+ e.stopPropagation();
3982
+ openList();
3983
+ };
3984
+ input.addEventListener("click", open);
3985
+ chevron.addEventListener("mousedown", (e) => e.preventDefault());
3986
+ chevron.addEventListener("click", open);
3987
+ wrap.append(input, chevron);
3988
+ return wrap;
3989
+ }
3990
+ /** A typeable YYYY-MM-DD field that also opens a themed month calendar. */
3991
+ dateField(id, current, onChange) {
3992
+ const wrap = document.createElement("div");
3993
+ wrap.className = "vela-ind-combo";
3994
+ wrap.style.cssText = "position:relative;width:128px;";
3995
+ const input = document.createElement("input");
3996
+ input.type = "text";
3997
+ input.id = id;
3998
+ input.value = current;
3999
+ input.autocomplete = "off";
4000
+ input.spellcheck = false;
4001
+ input.style.cssText = `${this.ctrlStyle()}padding-right:26px;`;
4002
+ const chevron = document.createElement("button");
4003
+ chevron.type = "button";
4004
+ chevron.tabIndex = -1;
4005
+ chevron.className = "vela-ind-combo-chevron";
4006
+ chevron.setAttribute("aria-label", "Open calendar");
4007
+ chevron.innerHTML = CALENDAR_SVG;
4008
+ let value = current;
4009
+ const commitTyped = () => {
4010
+ const next = normalizeDateInput(input.value);
4011
+ if (!next) {
4012
+ input.value = value;
4013
+ return;
4014
+ }
4015
+ input.value = next;
4016
+ if (next !== value) {
4017
+ value = next;
4018
+ onChange(next);
4019
+ }
4020
+ };
4021
+ input.addEventListener("blur", () => {
4022
+ if (isPopoverOpen()) return;
4023
+ commitTyped();
4024
+ });
4025
+ const pick = (v) => {
4026
+ value = v;
4027
+ input.value = v;
4028
+ onChange(v);
4029
+ };
4030
+ input.addEventListener("keydown", (e) => {
4031
+ if (e.key === "Enter") {
4032
+ e.preventDefault();
4033
+ commitTyped();
4034
+ input.blur();
4035
+ }
4036
+ if (e.key === "ArrowDown") {
4037
+ e.preventDefault();
4038
+ this.openCalendar(wrap, value, pick);
4039
+ }
4040
+ });
4041
+ const open = (e) => {
4042
+ e.preventDefault();
4043
+ e.stopPropagation();
4044
+ if (openPopoverTrigger() === wrap) {
4045
+ closeOpenPopovers();
4046
+ return;
4047
+ }
4048
+ this.openCalendar(wrap, value, pick);
4049
+ };
4050
+ input.addEventListener("click", open);
4051
+ chevron.addEventListener("mousedown", (e) => e.preventDefault());
4052
+ chevron.addEventListener("click", open);
4053
+ wrap.append(input, chevron);
4054
+ return wrap;
4055
+ }
4056
+ /** Numeric field with hover steppers on the right (no fill until a triangle is hovered). */
4057
+ buildNumber(id, current, inp, emit) {
4058
+ return new NumberInput({
4059
+ id,
4060
+ value: current,
4061
+ min: inp.min,
4062
+ max: inp.max,
4063
+ step: inp.step ?? (inp.type === "int" ? 1 : 0.1),
4064
+ integer: inp.type === "int",
4065
+ size: "md",
4066
+ commit: "blur",
4067
+ onChange: emit
4068
+ }).el;
4069
+ }
4070
+ /** Placement shared by the indicator dialog's dropdowns and the time combobox. */
4071
+ listPopover() {
4072
+ return {
4073
+ theme: this.theme,
4074
+ matchWidth: true,
4075
+ boundary: this.dialog ?? "viewport",
4076
+ boundaryInset: 8,
4077
+ gap: 4
4078
+ };
4079
+ }
4080
+ /** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
4081
+ openCalendar(anchor, current, onPick) {
4082
+ const parsed = parseIsoDate(current);
4083
+ let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
4084
+ let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
4085
+ const selected = parsed ? isoDate(parsed) : current;
4086
+ const pop = new Popover({
4087
+ trigger: anchor,
4088
+ theme: this.theme,
4089
+ className: "vela-ind-cal",
4090
+ boundary: this.dialog ?? "viewport",
4091
+ boundaryInset: 8,
4092
+ gap: 4,
4093
+ onClose: () => {
4094
+ this.calendarPop = null;
4095
+ this.calendarAnchor = null;
4096
+ },
4097
+ content: (el) => {
4098
+ const title = document.createElement("div");
4099
+ title.className = "vela-ind-cal-title";
4100
+ const prev2 = document.createElement("button");
4101
+ prev2.type = "button";
4102
+ prev2.className = "vela-ind-cal-nav";
4103
+ prev2.setAttribute("aria-label", "Previous month");
4104
+ prev2.innerHTML = iconAt("chevron-left", 14);
4105
+ const next = document.createElement("button");
4106
+ next.type = "button";
4107
+ next.className = "vela-ind-cal-nav";
4108
+ next.setAttribute("aria-label", "Next month");
4109
+ next.innerHTML = iconAt("chevron-right", 14);
4110
+ const head = document.createElement("div");
4111
+ head.className = "vela-ind-cal-head";
4112
+ head.append(prev2, title, next);
4113
+ const week = document.createElement("div");
4114
+ week.className = "vela-ind-cal-week";
4115
+ for (const d of WEEKDAY_LABELS) {
4116
+ const cell = document.createElement("span");
4117
+ cell.textContent = d;
4118
+ week.appendChild(cell);
4119
+ }
4120
+ const grid = document.createElement("div");
4121
+ grid.className = "vela-ind-cal-grid";
4122
+ const paint = () => {
4123
+ title.textContent = `${MONTH_LABELS[month]} ${year}`;
4124
+ grid.replaceChildren();
4125
+ const first = new Date(year, month, 1);
4126
+ const startPad = first.getDay();
4127
+ const days = new Date(year, month + 1, 0).getDate();
4128
+ const today = isoDate(/* @__PURE__ */ new Date());
4129
+ for (let i = 0; i < startPad; i++) {
4130
+ const blank = document.createElement("span");
4131
+ blank.className = "vela-ind-cal-blank";
4132
+ grid.appendChild(blank);
4133
+ }
4134
+ for (let day = 1; day <= days; day++) {
4135
+ const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
4136
+ const b = document.createElement("button");
4137
+ b.type = "button";
4138
+ b.className = "vela-ind-cal-day";
4139
+ b.textContent = String(day);
4140
+ if (iso === selected) b.dataset.checked = "1";
4141
+ if (iso === today) b.dataset.today = "1";
4142
+ b.addEventListener("click", (e) => {
4143
+ e.stopPropagation();
4144
+ pop.hide();
4145
+ onPick(iso);
4146
+ });
4147
+ grid.appendChild(b);
4148
+ }
4149
+ };
4150
+ prev2.addEventListener("click", (e) => {
4151
+ e.stopPropagation();
4152
+ month -= 1;
4153
+ if (month < 0) {
4154
+ month = 11;
4155
+ year -= 1;
4156
+ }
4157
+ paint();
4158
+ });
4159
+ next.addEventListener("click", (e) => {
4160
+ e.stopPropagation();
4161
+ month += 1;
4162
+ if (month > 11) {
4163
+ month = 0;
4164
+ year += 1;
4165
+ }
4166
+ paint();
4167
+ });
4168
+ paint();
4169
+ el.append(head, week, grid);
4170
+ }
4171
+ });
4172
+ this.calendarPop = pop;
4173
+ this.calendarAnchor = anchor;
4174
+ pop.show();
3992
4175
  }
3993
4176
  /**
3994
4177
  * `input.symbol` → a field that opens the host's ticker-selection UI when a picker is wired
@@ -4016,7 +4199,7 @@ var InputsUI = class {
4016
4199
  }
4017
4200
  /** Shared field chrome for text / number / select controls (fills its wrapper's width). */
4018
4201
  ctrlStyle() {
4019
- return `width:100%;box-sizing:border-box;height:32px;background:transparent;border:1px solid ${this.neutralBorder()};color:${this.strongText()};border-radius:6px;padding:0 8px;font-size:13px;font-family:inherit;outline:none;`;
4202
+ return `width:100%;box-sizing:border-box;height:34px;background:transparent;border:1px solid var(--vela-border-strong);color:${this.strongText()};border-radius:6px;padding:0 8px;font-size:14px;font-family:inherit;outline:none;`;
4020
4203
  }
4021
4204
  /** Whether the active theme is dark (drives the dialog's `color-scheme`). */
4022
4205
  isDarkTheme() {
@@ -4035,21 +4218,6 @@ var InputsUI = class {
4035
4218
  strongText() {
4036
4219
  return "var(--vela-fg-bright)";
4037
4220
  }
4038
- /**
4039
- * Paint a `<select>` + its `<option>`s so the native dropdown popup matches the dialog: the
4040
- * option list's background is the dialog color and its text is the strong foreground. `color-scheme`
4041
- * alone doesn't theme the popup in every embedded browser (some render it white with white text —
4042
- * invisible), so the colors are set explicitly here.
4043
- */
4044
- styleSelect(sel) {
4045
- const bg = this.theme.background;
4046
- const fg = this.strongText();
4047
- sel.style.backgroundColor = bg;
4048
- for (const o of Array.from(sel.options)) {
4049
- o.style.backgroundColor = bg;
4050
- o.style.color = fg;
4051
- }
4052
- }
4053
4221
  dialogButton(label, primary, onClick) {
4054
4222
  const b = document.createElement("button");
4055
4223
  b.type = "button";
@@ -4063,6 +4231,21 @@ function nameOf(inp) {
4063
4231
  const t = inp.title;
4064
4232
  return t.length > 0 ? t.charAt(0).toUpperCase() + t.slice(1) : "";
4065
4233
  }
4234
+ var DEFAULT_INPUT_TAB = "Inputs";
4235
+ function tabInputs(inputs) {
4236
+ const byTab = /* @__PURE__ */ new Map();
4237
+ for (const inp of inputs) {
4238
+ const name = inp.tab && inp.tab.length > 0 ? inp.tab : DEFAULT_INPUT_TAB;
4239
+ if (!byTab.has(name)) byTab.set(name, []);
4240
+ byTab.get(name).push(inp);
4241
+ }
4242
+ const names = [...byTab.keys()];
4243
+ if (names.includes(DEFAULT_INPUT_TAB)) {
4244
+ names.splice(names.indexOf(DEFAULT_INPUT_TAB), 1);
4245
+ names.unshift(DEFAULT_INPUT_TAB);
4246
+ }
4247
+ return names.map((name) => ({ name, inputs: byTab.get(name) }));
4248
+ }
4066
4249
  function groupInputs(inputs) {
4067
4250
  const order = [];
4068
4251
  const byGroup = /* @__PURE__ */ new Map();
@@ -4091,9 +4274,10 @@ function groupInputs(inputs) {
4091
4274
  });
4092
4275
  }
4093
4276
  var CHECK_SVG = iconAt("check", 12);
4094
- var SELECT_CHEVRON_SVG = iconAt("chevron-down", 16);
4277
+ var CALENDAR_SVG = iconAt("calendar", 14);
4278
+ var CLOCK_SVG = iconAt("clock", 14);
4095
4279
  var DIALOG_STYLE_ID = "vela-ind-dialog-styles";
4096
- var DIALOG_STYLE_REV = "10";
4280
+ var DIALOG_STYLE_REV = "25";
4097
4281
  function ensureDialogStyles() {
4098
4282
  if (typeof document === "undefined") return;
4099
4283
  const existing = document.getElementById(DIALOG_STYLE_ID);
@@ -4102,30 +4286,47 @@ function ensureDialogStyles() {
4102
4286
  s.id = DIALOG_STYLE_ID;
4103
4287
  s.dataset.rev = DIALOG_STYLE_REV;
4104
4288
  s.textContent = `
4105
- .vela-ind-dialog input[type=color]{-webkit-appearance:none;appearance:none;}
4106
- .vela-ind-dialog input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
4107
- .vela-ind-dialog input[type=color]::-webkit-color-swatch{border:none;border-radius:4px;}
4108
- .vela-ind-dialog input[type=color]::-moz-color-swatch{border:none;border-radius:4px;}
4109
- .vela-ind-dialog input[type=text],.vela-ind-dialog input[type=number],.vela-ind-dialog select{transition:border-color .12s ease,box-shadow .12s ease;}
4110
- .vela-ind-dialog select{-webkit-appearance:none;appearance:none;}
4111
- .vela-ind-dialog input[type=text]:focus,.vela-ind-dialog input[type=number]:focus,.vela-ind-dialog select:focus{border-color:var(--vela-focus);box-shadow:0 0 0 3px var(--vela-focus-soft);}
4112
- .vela-ind-dialog ::-webkit-scrollbar{width:9px;}
4289
+ .vela-ind-dialog input[type=text],.vela-ind-dialog input[type=number],.vela-ind-dialog input[type=date]{transition:border-color .12s ease,box-shadow .12s ease;}
4290
+ .vela-ind-dialog input[type=text]:hover,.vela-ind-dialog input[type=number]:hover,.vela-ind-dialog input[type=date]:hover{border-color:var(--vela-fg-muted);}
4291
+ .vela-ind-dialog input[type=text]:focus,.vela-ind-dialog input[type=number]:focus,.vela-ind-dialog input[type=date]:focus{border-color:var(--vela-focus);box-shadow:0 0 0 3px var(--vela-focus-soft);}
4292
+ .vela-ind-combo-chevron{position:absolute;right:0;top:0;bottom:0;width:26px;border:none;background:transparent;color:inherit;opacity:0.55;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;}
4293
+ .vela-ind-combo-chevron:hover{opacity:0.9;}
4294
+ .vela-ind-cal{background:var(--vela-bg);color:var(--vela-fg);border:none;border-radius:6px;box-shadow:var(--vela-shadow);font:14px var(--vela-font);padding:10px 12px;user-select:none;}
4295
+ .vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
4296
+ .vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
4297
+ .vela-ind-cal-nav{width:24px;height:24px;border:none;background:transparent;color:var(--vela-fg-muted);border-radius:4px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
4298
+ .vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
4299
+ .vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
4300
+ .vela-ind-cal-week{margin-bottom:4px;color:var(--vela-fg-muted);font-size:11px;text-align:center;}
4301
+ .vela-ind-cal-week span{line-height:20px;}
4302
+ .vela-ind-cal-blank{width:28px;height:28px;}
4303
+ .vela-ind-cal-day{width:28px;height:28px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0;cursor:pointer;font:inherit;font-size:14px;}
4304
+ .vela-ind-cal-day:hover{background:var(--vela-hover);}
4305
+ .vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
4306
+ .vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
4307
+ .vela-ind-dialog ::-webkit-scrollbar{width:9px;height:9px;}
4113
4308
  .vela-ind-dialog ::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:4px;border:2px solid transparent;background-clip:padding-box;}
4114
4309
  .vela-ind-dialog ::-webkit-scrollbar-track{background:transparent;}
4310
+ .vela-ind-dialog ::-webkit-scrollbar-button{display:none;width:0;height:0;}
4311
+ .vela-ind-tab{transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
4312
+ .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
4115
4313
  .vela-ind-hint{background:var(--vela-hover);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
4116
4314
  .vela-ind-hint:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
4117
4315
  .vela-ind-ctl,.vela-ind-close{background-color:transparent;color:inherit;transition:color var(--vela-dur-fast) ease,background-color var(--vela-dur-fast) ease;}
4118
4316
  .vela-ind-ctl svg,.vela-ind-close svg{width:${LEGEND_ICON_PX}px;height:${LEGEND_ICON_PX}px;display:block;flex:none;stroke-width:1;}
4119
4317
  .vela-ind-ctl:hover{background-color:var(--vela-hover-strong);}
4120
4318
  .vela-ind-close:hover{color:var(--vela-danger) !important;background-color:var(--vela-hover-strong);}
4319
+ .vela-ind-dlg-close{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--vela-fg-muted);line-height:0;width:30px;height:30px;flex:0 0 auto;border-radius:var(--vela-radius-sm);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
4320
+ .vela-ind-dlg-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
4321
+ .vela-ind-dlg-close svg{width:15px;height:15px;display:block;flex:none;}
4121
4322
  .vela-ind-fold{transition:border-color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
4122
4323
  .vela-ind-fold:hover{border-color:var(--vela-border-strong);opacity:0.9;}
4123
4324
  .vela-ind-menuitem{background:transparent;transition:background var(--vela-dur-fast) ease;}
4124
4325
  .vela-ind-menuitem:hover{background:var(--vela-hover-strong);}
4125
- .vela-ind-btn{cursor:pointer;padding:7px 14px;border-radius:var(--vela-radius-md);border:1px solid transparent;background:transparent;color:var(--vela-fg-muted);font-weight:600;font-size:13px;font-family:inherit;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease;}
4126
- .vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
4127
- .vela-ind-btn-primary{padding:7px 16px;border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
4128
- .vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;}`;
4326
+ .vela-ind-btn{cursor:pointer;height:34px;padding:0 11px;border-radius:6px;border:1px solid var(--vela-border-strong);background:transparent;color:var(--vela-fg);font-weight:400;font-size:14px;font-family:inherit;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease,opacity var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
4327
+ .vela-ind-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);border-color:var(--vela-fg-muted);}
4328
+ .vela-ind-btn-primary{border-color:var(--vela-selected-bg);background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
4329
+ .vela-ind-btn-primary:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);opacity:0.85;border-color:var(--vela-selected-bg);}`;
4129
4330
  if (!existing) document.head.appendChild(s);
4130
4331
  }
4131
4332
  var TIMEFRAME_OPTIONS = [
@@ -4150,6 +4351,37 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
4150
4351
  const v = `${hh}:${mm}`;
4151
4352
  return { value: v, label: v };
4152
4353
  });
4354
+ var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
4355
+ var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
4356
+ function normalizeDateInput(raw) {
4357
+ const t = raw.trim();
4358
+ const m = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(t);
4359
+ if (!m) return null;
4360
+ const y = Number(m[1]);
4361
+ const mo = Number(m[2]);
4362
+ const d = Number(m[3]);
4363
+ const dt = new Date(y, mo - 1, d);
4364
+ if (dt.getFullYear() !== y || dt.getMonth() !== mo - 1 || dt.getDate() !== d) return null;
4365
+ return isoDate(dt);
4366
+ }
4367
+ function parseIsoDate(raw) {
4368
+ const iso = normalizeDateInput(raw);
4369
+ if (!iso) return null;
4370
+ const [y, mo, d] = iso.split("-").map(Number);
4371
+ return new Date(y, mo - 1, d);
4372
+ }
4373
+ function isoDate(d) {
4374
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
4375
+ }
4376
+ function normalizeTimeInput(raw) {
4377
+ const t = raw.trim();
4378
+ const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
4379
+ if (!m) return null;
4380
+ const hh = Number(m[1]);
4381
+ const mm = Number(m[2]);
4382
+ if (hh > 23 || mm > 59) return null;
4383
+ return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
4384
+ }
4153
4385
  function sessionToTimes(session) {
4154
4386
  const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(session.trim());
4155
4387
  if (!m) return ["09:00", "16:00"];
@@ -8256,6 +8488,12 @@ function extendEndpoints(x1, y1, x2, y2, extend, width, height) {
8256
8488
  const rx = right ? width + 2 : Math.max(x1, x2);
8257
8489
  return [lx, yAt(lx), rx, yAt(rx)];
8258
8490
  }
8491
+ function uprightLineAngle(x1, y1, x2, y2) {
8492
+ let a = Math.atan2(y2 - y1, x2 - x1);
8493
+ if (a > Math.PI / 2) a -= Math.PI;
8494
+ if (a < -Math.PI / 2) a += Math.PI;
8495
+ return a;
8496
+ }
8259
8497
  function parseColor2(c) {
8260
8498
  const s = c.trim();
8261
8499
  let m = /^#([0-9a-f]{3})$/i.exec(s);
@@ -9429,367 +9667,71 @@ function formatStamp(ms, timeZone) {
9429
9667
  return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
9430
9668
  }
9431
9669
 
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
9670
  // src/renderers/native/chrome/WidthField.ts
9701
9671
  var WIDTH_FIELD_OPTIONS = [1, 2, 3, 4, 5];
9702
- var STYLE_ID4 = "vela-width-field";
9703
- function ensureStyles4() {
9704
- if (typeof document === "undefined" || document.getElementById(STYLE_ID4)) return;
9705
- const st = document.createElement("style");
9706
- st.id = STYLE_ID4;
9707
- st.textContent = `
9672
+ var STYLE_ID3 = "vela-width-field";
9673
+ var WIDTH_CSS = `
9708
9674
  .vela-width-field{height:28px;padding:0 8px;border:1px solid var(--vela-border-strong);border-radius:var(--vela-radius-sm);background:var(--vela-surface-elev);cursor:pointer;display:inline-flex;align-items:center;gap:8px;flex:none;color:var(--vela-fg);font-family:inherit;outline:none;}
9709
9675
  .vela-width-field:hover{border-color:var(--vela-fg-muted);}
9710
- .vela-width-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:4px;display:flex;flex-direction:column;gap:1px;color:var(--vela-fg);}
9676
+ .vela-width-field-caret{display:flex;color:var(--vela-fg-muted);}
9677
+ .vela-width-field-pop{background:var(--vela-surface-overlay);border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);padding:4px;display:flex;flex-direction:column;gap:1px;color:var(--vela-fg);}
9711
9678
  .vela-width-field-item{display:flex;align-items:center;gap:8px;min-width:96px;padding:5px 8px;border:none;border-radius:5px;background:transparent;color:inherit;cursor:pointer;text-align:left;font:inherit;}
9712
9679
  .vela-width-field-item:hover{background:var(--vela-hover-strong);}
9713
9680
  .vela-width-field-item[data-active='1']{background:var(--vela-active);}
9714
9681
  `;
9715
- document.head.appendChild(st);
9716
- }
9717
9682
  function lineGlyph(width) {
9718
9683
  return `<svg width="22" height="14" viewBox="0 0 22 14" fill="none"><line x1="2" y1="7" x2="20" y2="7" stroke="currentColor" stroke-width="${width}" stroke-linecap="round"/></svg>`;
9719
9684
  }
9720
- var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="#868a96" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
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
- }
9685
+ var CHEVRON = `<svg width="8" height="5" viewBox="0 0 8 5" fill="none"><path d="M1 1l3 3 3-3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
9742
9686
  function widthField(theme, getVal, onVal) {
9743
- ensureStyles4();
9687
+ injectStyles(STYLE_ID3, WIDTH_CSS, document);
9744
9688
  const trigger = document.createElement("button");
9745
9689
  trigger.type = "button";
9746
9690
  trigger.className = "vela-width-field";
9747
9691
  const paint = () => {
9748
- trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span style="display:flex;">${CHEVRON}</span>`;
9692
+ trigger.innerHTML = `<span style="display:flex;">${lineGlyph(getVal())}</span><span class="vela-width-field-caret">${CHEVRON}</span>`;
9749
9693
  };
9750
9694
  paint();
9695
+ trigger.addEventListener("pointerdown", (e) => {
9696
+ e.preventDefault();
9697
+ trigger.focus({ preventScroll: true });
9698
+ });
9751
9699
  trigger.addEventListener("click", (e) => {
9752
9700
  e.stopPropagation();
9753
- if (openPopover2 && openPopover2.trigger === trigger) {
9754
- closeOpen2();
9701
+ if (openPopoverTrigger() === trigger) {
9702
+ closeOpenPopovers();
9755
9703
  return;
9756
9704
  }
9757
- closeOpen2();
9758
- const pop = document.createElement("div");
9759
- pop.className = "vela-width-field-pop";
9760
- applyChromeTokens(pop, theme);
9761
- pop.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
9762
- pop.addEventListener("pointerdown", (ev) => ev.stopPropagation());
9763
- for (const w of WIDTH_FIELD_OPTIONS) {
9764
- const item = document.createElement("button");
9765
- item.type = "button";
9766
- item.className = "vela-width-field-item";
9767
- item.dataset.active = w === getVal() ? "1" : "0";
9768
- item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
9769
- item.addEventListener("click", (ev) => {
9770
- ev.stopPropagation();
9771
- closeOpen2();
9772
- onVal(w);
9773
- paint();
9774
- });
9775
- pop.appendChild(item);
9776
- }
9777
- document.body.appendChild(pop);
9778
- place2(pop, trigger);
9779
- const onOutside = (ev) => {
9780
- const t = ev.target;
9781
- if (pop.contains(t) || trigger.contains(t)) return;
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 };
9705
+ const pop = new Popover({
9706
+ trigger,
9707
+ theme,
9708
+ align: "end",
9709
+ gap: 6,
9710
+ className: "vela-width-field-pop",
9711
+ content: (el) => {
9712
+ el.style.font = `var(--vela-font-size-md) ${theme.fontFamily}`;
9713
+ for (const w of WIDTH_FIELD_OPTIONS) {
9714
+ const item = document.createElement("button");
9715
+ item.type = "button";
9716
+ item.className = "vela-width-field-item";
9717
+ item.dataset.active = w === getVal() ? "1" : "0";
9718
+ item.innerHTML = `<span style="display:flex;flex:none;">${lineGlyph(w)}</span><span style="flex:1;font-variant-numeric:tabular-nums;">${w}px</span>`;
9719
+ item.addEventListener("click", (ev) => {
9720
+ ev.stopPropagation();
9721
+ pop.hide();
9722
+ onVal(w);
9723
+ paint();
9724
+ });
9725
+ el.appendChild(item);
9726
+ }
9727
+ }
9728
+ });
9729
+ pop.show();
9788
9730
  });
9789
9731
  return trigger;
9790
9732
  }
9791
9733
  function closeWidthPopover() {
9792
- closeOpen2();
9734
+ closeOpenPopovers();
9793
9735
  }
9794
9736
 
9795
9737
  // src/renderers/native/chrome/SettingsDialog.ts
@@ -9804,37 +9746,24 @@ function styleLabel(id) {
9804
9746
  return chartType(id)?.label ?? BUILTIN_STYLE_LABELS[id] ?? id;
9805
9747
  }
9806
9748
  var SD_STYLE_ID = "vela-settings-controls";
9749
+ var SD_STYLE_REV = "2";
9807
9750
  var SETTINGS_SURFACE = "var(--vela-surface)";
9808
9751
  var SETTINGS_BORDER = "var(--vela-border)";
9809
9752
  var SETTINGS_TEXT = "var(--vela-fg)";
9810
9753
  function ensureControlStyles() {
9811
- if (typeof document === "undefined" || document.getElementById(SD_STYLE_ID)) return;
9812
- const st = document.createElement("style");
9754
+ if (typeof document === "undefined") return;
9755
+ const existing = document.getElementById(SD_STYLE_ID);
9756
+ if (existing?.dataset.rev === SD_STYLE_REV) return;
9757
+ const st = existing ?? document.createElement("style");
9813
9758
  st.id = SD_STYLE_ID;
9759
+ st.dataset.rev = SD_STYLE_REV;
9814
9760
  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
9761
  .vela-sd-pane::-webkit-scrollbar{width:8px;}
9829
9762
  .vela-sd-pane::-webkit-scrollbar-thumb{background:var(--vela-scroll);border-radius:var(--vela-radius-sm);border:2px solid transparent;background-clip:padding-box;}
9830
9763
  .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
9764
  /* Tab rail / footer button / header close: base styles live HERE, not inline on the
9836
9765
  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 (like .vela-sd-check),
9766
+ what killed these hovers before. Active tab state is the .on class,
9838
9767
  hover fills follow the app convention (--vela-hover + --vela-fg-bright, fast transition). */
9839
9768
  .vela-sd-tab{text-align:left;padding:9px 12px;background:transparent;border:none;border-radius:var(--vela-radius-md);color:var(--vela-fg-muted);font-weight:600;font-size:13px;font-family:inherit;cursor:pointer;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
9840
9769
  .vela-sd-tab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
@@ -9893,13 +9822,13 @@ function ensureControlStyles() {
9893
9822
  .vela-sd-mobile .vela-sd-itabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;}
9894
9823
  .vela-sd-mobile .vela-sd-itabs::-webkit-scrollbar{display:none;}
9895
9824
  .vela-sd-mobile .vela-sd-itab{height:36px;flex:none;}
9896
- .vela-sd-mobile .vela-sd-check{width:22px;height:22px;}
9897
- .vela-sd-mobile .vela-sd-select,.vela-sd-mobile .vela-sd-number{height:34px;}
9825
+ .vela-sd-mobile .vela-switch{width:22px;height:22px;}
9826
+ .vela-sd-mobile .vela-select[data-size='sm'] .vela-select-trigger,.vela-sd-mobile .vela-num[data-size='sm'] input{height:34px;}
9898
9827
  .vela-sd-mobile .vela-sd-close{width:40px;height:40px;}
9899
9828
  .vela-sd-mobile .vela-sd-btn{height:38px;}
9900
9829
  .vela-sd-mobile .vela-sd-row span,.vela-sd-mobile .vela-sd-bool span{white-space:normal !important;}
9901
9830
  `;
9902
- document.head.appendChild(st);
9831
+ if (!existing) document.head.appendChild(st);
9903
9832
  }
9904
9833
  var SettingsDialog = class {
9905
9834
  constructor(container, theme) {
@@ -10223,7 +10152,7 @@ var SettingsDialog = class {
10223
10152
  }
10224
10153
  const paneHost = document.createElement("div");
10225
10154
  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;";
10155
+ paneHost.style.cssText = mobile ? "flex:1;min-width:0;overflow-y:auto;overflow-anchor:none;padding:6px 14px calc(14px + env(safe-area-inset-bottom, 0px));" : "flex:1;overflow-y:auto;overflow-anchor:none;padding:6px 18px 14px;";
10227
10156
  const panes = [];
10228
10157
  let current = null;
10229
10158
  for (const child of [...body.children]) {
@@ -10289,7 +10218,7 @@ var SettingsDialog = class {
10289
10218
  }
10290
10219
  }
10291
10220
  close() {
10292
- closeColorPopover();
10221
+ closeOpenPopovers();
10293
10222
  closeWidthPopover();
10294
10223
  this.root?.remove();
10295
10224
  this.root = null;
@@ -10444,50 +10373,41 @@ var SettingsDialog = class {
10444
10373
  return wf;
10445
10374
  }
10446
10375
  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
10376
  const current2 = typeof bag[c.key] === "string" ? bag[c.key] : c.defval;
10452
- for (const [val, lbl] of normalizeSelectOptions(c.options)) {
10453
- const o = document.createElement("option");
10454
- o.value = val;
10455
- o.textContent = lbl;
10456
- if (val === current2) o.selected = true;
10457
- sel.appendChild(o);
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);
10377
+ const sel = new Select({
10378
+ options: normalizeSelectOptions(c.options).map(([value, label]) => ({ value, label })),
10379
+ value: current2,
10380
+ size: "sm",
10381
+ theme: this.theme,
10382
+ onChange: (v) => put(c.key, v)
10482
10383
  });
10483
- } else {
10484
- ni.value = String(current);
10485
- ni.addEventListener("input", () => {
10486
- const v = Number(ni.value);
10487
- if (Number.isFinite(v)) put(c.key, v);
10384
+ sel.el.title = c.label;
10385
+ sel.el.addEventListener("vela-sync", () => {
10386
+ sel.setValue(typeof bag[c.key] === "string" ? bag[c.key] : c.defval);
10488
10387
  });
10388
+ return sel.el;
10489
10389
  }
10490
- return ni;
10390
+ const current = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
10391
+ const ni = new NumberInput({
10392
+ value: current,
10393
+ min: c.min,
10394
+ max: c.max,
10395
+ step: c.step ?? 1,
10396
+ size: "sm",
10397
+ commit: "live",
10398
+ clamp: c.placeholder !== void 0,
10399
+ steppers: false,
10400
+ compact,
10401
+ title: c.label,
10402
+ placeholder: c.placeholder,
10403
+ emptyValue: c.placeholder !== void 0 ? c.defval : void 0,
10404
+ onChange: (v) => put(c.key, v)
10405
+ });
10406
+ ni.el.addEventListener("vela-sync", () => {
10407
+ const next = typeof bag[c.key] === "number" ? bag[c.key] : c.defval;
10408
+ ni.setValue(next);
10409
+ });
10410
+ return ni.el;
10491
10411
  }
10492
10412
  /**
10493
10413
  * The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
@@ -10768,28 +10688,27 @@ var SettingsDialog = class {
10768
10688
  * only one visible at a time; see `typeRow`). */
10769
10689
  toggleRow(label, value, onToggle, controls, get) {
10770
10690
  const wrap = document.createElement("div");
10771
- const cb = document.createElement("button");
10772
- cb.type = "button";
10773
- cb.className = "vela-sd-check" + (value ? " on" : "");
10774
- cb.innerHTML = iconAt("check", 11);
10775
- let checked = value;
10776
- const cbToggle = () => {
10777
- checked = !checked;
10778
- cb.classList.toggle("on", checked);
10779
- return checked;
10780
- };
10691
+ const sw = new Switch({ checked: value, size: "sm", onChange: (v) => {
10692
+ onToggle(v);
10693
+ if (controls.length > 0) syncDim(v);
10694
+ } });
10781
10695
  const lbl = document.createElement("span");
10782
10696
  lbl.textContent = label;
10783
10697
  lbl.style.cssText = "opacity:0.85;white-space:nowrap;";
10698
+ const syncDim = (on) => {
10699
+ for (const c of controls) {
10700
+ if (c.dataset.sdSelfGated === "1") continue;
10701
+ c.style.opacity = on ? "1" : "0.4";
10702
+ c.style.pointerEvents = on ? "" : "none";
10703
+ }
10704
+ };
10784
10705
  if (controls.length === 0) {
10785
10706
  wrap.className = "vela-sd-bool";
10786
10707
  wrap.style.cssText = "display:flex;align-items:center;gap:8px;min-height:22px;";
10787
- wrap.append(cb, lbl);
10788
- cb.addEventListener("click", () => onToggle(cbToggle()));
10708
+ wrap.append(sw.el, lbl);
10789
10709
  if (get) {
10790
10710
  wrap.addEventListener("vela-sync", () => {
10791
- checked = get();
10792
- cb.classList.toggle("on", checked);
10711
+ sw.setChecked(get());
10793
10712
  });
10794
10713
  }
10795
10714
  return wrap;
@@ -10797,28 +10716,16 @@ var SettingsDialog = class {
10797
10716
  wrap.className = "vela-sd-row";
10798
10717
  const left = document.createElement("div");
10799
10718
  left.style.cssText = "display:flex;align-items:center;gap:8px;";
10800
- left.append(cb, lbl);
10719
+ left.append(sw.el, lbl);
10801
10720
  const box = document.createElement("div");
10802
10721
  box.style.cssText = "display:flex;align-items:center;gap:6px;";
10803
10722
  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
10723
  syncDim(value);
10812
- cb.addEventListener("click", () => {
10813
- const v = cbToggle();
10814
- onToggle(v);
10815
- syncDim(v);
10816
- });
10817
10724
  if (get) {
10818
10725
  wrap.addEventListener("vela-sync", () => {
10819
- checked = get();
10820
- cb.classList.toggle("on", checked);
10821
- syncDim(checked);
10726
+ const v = get();
10727
+ sw.setChecked(v);
10728
+ syncDim(v);
10822
10729
  });
10823
10730
  }
10824
10731
  wrap.append(left, box);
@@ -10826,52 +10733,23 @@ var SettingsDialog = class {
10826
10733
  }
10827
10734
  numberRow(label, value, min, max, step, onChange) {
10828
10735
  const { wrap } = this.row(label);
10829
- const ni = document.createElement("input");
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);
10736
+ wrap.appendChild(new NumberInput({ value, min, max, step, size: "sm", commit: "live", onChange }).el);
10841
10737
  return wrap;
10842
10738
  }
10843
10739
  /** A dropdown whose option values differ from their display labels. */
10844
10740
  selectRowLabeled(label, value, options, onChange) {
10845
10741
  const { wrap } = this.row(label);
10846
- const sel = document.createElement("select");
10847
- sel.className = "vela-sd-select";
10848
- sel.style.cssText = "max-width:200px;flex:0 0 auto;";
10849
- for (const [val, lbl] of options) {
10850
- const o = document.createElement("option");
10851
- o.value = val;
10852
- o.textContent = lbl;
10853
- if (val === value) o.selected = true;
10854
- sel.appendChild(o);
10855
- }
10856
- sel.addEventListener("change", () => onChange(sel.value));
10857
- wrap.appendChild(sel);
10742
+ wrap.appendChild(new Select({
10743
+ options: options.map(([v, l]) => ({ value: v, label: l })),
10744
+ value,
10745
+ size: "sm",
10746
+ theme: this.theme,
10747
+ onChange
10748
+ }).el);
10858
10749
  return wrap;
10859
10750
  }
10860
10751
  selectRow(label, value, options, onChange) {
10861
- const { wrap } = this.row(label);
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;
10752
+ return this.selectRowLabeled(label, value, options.map((o) => [o, o]), onChange);
10875
10753
  }
10876
10754
  /** Footer with the full config as JSON — the export/import (templating) surface. */
10877
10755
  footer(_config) {
@@ -11262,13 +11140,19 @@ var DrawingPainter = class {
11262
11140
  }
11263
11141
  this.paintLabel(ctx, d, proj, theme);
11264
11142
  }
11265
- /** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware). */
11143
+ /** Draw a drawing's optional text label at a type-appropriate anchor (multi-line aware).
11144
+ * Two-point line tools rotate the glyphs to follow the segment, kept upright. */
11266
11145
  paintLabel(ctx, d, proj, theme) {
11267
11146
  const text = d.text;
11268
11147
  if (!text || !text.value || d.id === this.targets.mutedLabel) return;
11269
11148
  const layout = labelLayout(d, proj);
11270
11149
  if (!layout) return;
11271
11150
  const fs = namedFontSize(text.size);
11151
+ ctx.save();
11152
+ if (layout.rotate) {
11153
+ ctx.translate(layout.rotate.cx, layout.rotate.cy);
11154
+ ctx.rotate(layout.rotate.angle);
11155
+ }
11272
11156
  ctx.font = `${text.bold ? "bold " : ""}${text.italic ? "italic " : ""}${fs}px ${theme.fontFamily}`;
11273
11157
  ctx.textBaseline = "top";
11274
11158
  ctx.textAlign = layout.align;
@@ -11276,8 +11160,8 @@ var DrawingPainter = class {
11276
11160
  const lh = labelLineHeight(fs);
11277
11161
  const lines = text.value.split("\n");
11278
11162
  lines.forEach((line, i) => ctx.fillText(line, layout.x, layout.top + i * lh));
11279
- ctx.textAlign = "left";
11280
11163
  if (d.type === "text") this.paintTextFrame(ctx, d.id, layout.x, layout.top, lines, fs, theme);
11164
+ ctx.restore();
11281
11165
  }
11282
11166
  /** Frame a plain text label while it is the target, so the text reads as a clickable object even
11283
11167
  * though it has no shape of its own: a firm frame once selected, a fainter one under the cursor.
@@ -12220,7 +12104,14 @@ function labelLayout(d, proj) {
12220
12104
  case "infoline":
12221
12105
  case "trendangle": {
12222
12106
  if (pts.length < 2) return null;
12223
- return { x: (pts[0][0] + pts[1][0]) / 2, top: (pts[0][1] + pts[1][1]) / 2 - 6 - lh * lines, align: "center" };
12107
+ const [x1, y1] = pts[0];
12108
+ const [x2, y2] = pts[1];
12109
+ return {
12110
+ x: 0,
12111
+ top: -6 - lh * lines,
12112
+ align: "center",
12113
+ rotate: { angle: uprightLineAngle(x1, y1, x2, y2), cx: (x1 + x2) / 2, cy: (y1 + y2) / 2 }
12114
+ };
12224
12115
  }
12225
12116
  case "box": {
12226
12117
  if (pts.length < 2) return null;
@@ -12569,11 +12460,11 @@ function applyFree(anchor, pt, free) {
12569
12460
  // src/renderers/native/drawings/DrawingSettingsPopup.ts
12570
12461
  var BTN = 30;
12571
12462
  var ICON = 21;
12572
- var STYLE_ID5 = "vela-drawing-popup-styles";
12573
- function ensureStyles5() {
12574
- if (typeof document === "undefined" || document.getElementById(STYLE_ID5)) return;
12463
+ var STYLE_ID4 = "vela-drawing-popup-styles";
12464
+ function ensureStyles3() {
12465
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID4)) return;
12575
12466
  const s = document.createElement("style");
12576
- s.id = STYLE_ID5;
12467
+ s.id = STYLE_ID4;
12577
12468
  s.textContent = `
12578
12469
  .vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
12579
12470
  .vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
@@ -12607,17 +12498,14 @@ var DrawingSettingsPopup = class {
12607
12498
  this.textPanel = null;
12608
12499
  this.levelsPanel = null;
12609
12500
  this.colorPop = null;
12610
- // floating RGB + opacity picker for a color swatch
12611
12501
  this.colorOwner = null;
12612
- // the swatch the open color popover belongs to (for toggle)
12613
- this.menuEl = null;
12614
- // floating option list for a dropdown control
12502
+ this.menuPop = null;
12615
12503
  this.menuOwner = null;
12616
12504
  this.onClose = null;
12617
12505
  this.onOutside = (e) => {
12618
12506
  const target = e.target;
12619
- if (this.colorPop?.contains(target)) return;
12620
- if (this.menuEl?.contains(target)) return;
12507
+ if (this.colorPop?.el.contains(target)) return;
12508
+ if (this.menuPop?.el.contains(target)) return;
12621
12509
  if (this.el && !this.el.contains(target)) {
12622
12510
  const cb = this.onClose;
12623
12511
  this.close();
@@ -12636,13 +12524,13 @@ var DrawingSettingsPopup = class {
12636
12524
  * picker / dropdown menu), which live outside `el`. */
12637
12525
  contains(node) {
12638
12526
  if (!node) return false;
12639
- return this.el?.contains(node) === true || this.colorPop?.contains(node) === true || this.menuEl?.contains(node) === true;
12527
+ return this.el?.contains(node) === true || this.colorPop?.el.contains(node) === true || this.menuPop?.el.contains(node) === true;
12640
12528
  }
12641
12529
  /** Open the quick toolbar for `drawing`, floating clear of its `anchor` box.
12642
12530
  * `onClose` fires on an outside-click dismissal (not a programmatic close). */
12643
12531
  open(drawing, anchor, actions, onClose) {
12644
12532
  this.close();
12645
- ensureStyles5();
12533
+ ensureStyles3();
12646
12534
  this.onClose = onClose ?? null;
12647
12535
  const t = this.theme;
12648
12536
  const schema = drawing.schema();
@@ -12653,8 +12541,7 @@ var DrawingSettingsPopup = class {
12653
12541
  el.style.cssText = `position:absolute;z-index:22;background:${t.background};border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);color:${t.textColor};font:var(--vela-font-size-md) ${t.fontFamily};display:flex;flex-direction:column;pointer-events:auto;overflow:hidden;`;
12654
12542
  applyChromeTokens(el, t);
12655
12543
  el.addEventListener("pointerdown", () => {
12656
- this.closeColorPopover();
12657
- this.closeMenu();
12544
+ closeOpenPopovers();
12658
12545
  });
12659
12546
  const bar = document.createElement("div");
12660
12547
  bar.style.cssText = "display:flex;align-items:center;gap:2px;padding:4px;";
@@ -13391,49 +13278,76 @@ var DrawingSettingsPopup = class {
13391
13278
  });
13392
13279
  return b;
13393
13280
  }
13281
+ /** Host-anchored floating shell (stays inside the chart). Content is filled after
13282
+ * construction so `fill` can close over the live `Popover` without hitting TDZ. */
13283
+ hostFloat(anchor, opts) {
13284
+ const t = this.theme;
13285
+ const pop = new Popover({
13286
+ trigger: anchor,
13287
+ host: this.host,
13288
+ theme: t,
13289
+ position: "absolute",
13290
+ boundary: this.host,
13291
+ boundaryInset: 4,
13292
+ viewportInset: 0,
13293
+ gap: 6,
13294
+ align: opts.align ?? "start",
13295
+ zIndex: opts.zIndex,
13296
+ onClose: () => {
13297
+ if (this.menuPop === pop) {
13298
+ this.menuPop = null;
13299
+ this.menuOwner = null;
13300
+ }
13301
+ if (this.colorPop === pop) {
13302
+ this.colorPop = null;
13303
+ this.colorOwner = null;
13304
+ }
13305
+ }
13306
+ });
13307
+ const el = pop.el;
13308
+ el.style.background = t.background;
13309
+ el.style.border = "1px solid var(--vela-border)";
13310
+ el.style.borderRadius = "var(--vela-radius-lg)";
13311
+ el.style.boxShadow = "var(--vela-shadow)";
13312
+ el.style.padding = opts.padding;
13313
+ el.style.display = "flex";
13314
+ el.style.flexDirection = "column";
13315
+ el.style.gap = "1px";
13316
+ el.style.color = t.textColor;
13317
+ el.style.font = `var(--vela-font-size-md) ${t.fontFamily}`;
13318
+ el.style.pointerEvents = "auto";
13319
+ opts.fill(el, pop);
13320
+ pop.show();
13321
+ return pop;
13322
+ }
13394
13323
  /** A floating list of one-shot actions (icon + label rows) opened by the kebab. */
13395
13324
  openActionMenu(anchor, rows) {
13396
- this.closeMenu();
13397
- this.closeColorPopover();
13398
- const t = this.theme;
13399
- const menu = document.createElement("div");
13400
- menu.className = "vela-dpop";
13401
- menu.style.cssText = `position:absolute;z-index:26;background:${t.background};border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);padding:4px;pointer-events:auto;display:flex;flex-direction:column;gap:1px;color:${t.textColor};font:var(--vela-font-size-md) ${t.fontFamily};`;
13402
- applyChromeTokens(menu, t);
13403
- menu.addEventListener("pointerdown", (e) => e.stopPropagation());
13404
- for (const row of rows) {
13405
- const item = document.createElement("button");
13406
- item.type = "button";
13407
- item.className = "vela-dpop-item";
13408
- 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;";
13409
- const ic = document.createElement("span");
13410
- ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
13411
- ic.innerHTML = sized(row.icon, 18);
13412
- const tx = document.createElement("span");
13413
- tx.textContent = row.label;
13414
- tx.style.cssText = "flex:1;";
13415
- item.append(ic, tx);
13416
- item.addEventListener("click", (e) => {
13417
- e.stopPropagation();
13418
- this.closeMenu();
13419
- row.onClick();
13420
- });
13421
- menu.appendChild(item);
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;
13325
+ this.menuPop = this.hostFloat(anchor, {
13326
+ align: "end",
13327
+ zIndex: 26,
13328
+ padding: "4px",
13329
+ fill: (menu, pop) => {
13330
+ for (const row of rows) {
13331
+ const item = document.createElement("button");
13332
+ item.type = "button";
13333
+ item.className = "vela-dpop-item";
13334
+ item.style.cssText = "display:flex;align-items:center;gap:9px;min-width:150px;padding:6px 9px;border:none;border-radius:5px;color:inherit;cursor:pointer;text-align:left;font:inherit;";
13335
+ const ic = document.createElement("span");
13336
+ ic.style.cssText = "display:flex;flex:none;width:20px;justify-content:center;";
13337
+ ic.innerHTML = sized(row.icon, 18);
13338
+ const tx = document.createElement("span");
13339
+ tx.textContent = row.label;
13340
+ tx.style.cssText = "flex:1;";
13341
+ item.append(ic, tx);
13342
+ item.addEventListener("click", (e) => {
13343
+ e.stopPropagation();
13344
+ pop.hide();
13345
+ row.onClick();
13346
+ });
13347
+ menu.appendChild(item);
13348
+ }
13349
+ }
13350
+ });
13437
13351
  this.menuOwner = anchor;
13438
13352
  }
13439
13353
  /** The floating hover-label. Sits just above or below the toolbar depending on where it floats,
@@ -13563,60 +13477,40 @@ var DrawingSettingsPopup = class {
13563
13477
  /** The floating option list opened by a {@link dropdown} trigger. Each row shows the option's
13564
13478
  * glyph (and text label when one is supplied); the current value is highlighted. */
13565
13479
  openMenu(anchor, values, current, render, label, onPick) {
13566
- this.closeMenu();
13567
- this.closeColorPopover();
13568
- const t = this.theme;
13569
- const menu = document.createElement("div");
13570
- menu.className = "vela-dpop";
13571
- menu.style.cssText = `position:absolute;z-index:26;background:${t.background};border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);padding:4px;pointer-events:auto;display:flex;flex-direction:column;gap:1px;color:${t.textColor};font:var(--vela-font-size-md) ${t.fontFamily};`;
13572
- applyChromeTokens(menu, t);
13573
- menu.addEventListener("pointerdown", (e) => e.stopPropagation());
13574
- for (const v of values) {
13575
- const active = v === current;
13576
- const item = document.createElement("button");
13577
- item.type = "button";
13578
- item.className = "vela-dpop-item";
13579
- item.dataset.active = active ? "1" : "0";
13580
- 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;`;
13581
- const ic = document.createElement("span");
13582
- ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
13583
- ic.innerHTML = sized(render(v), 18);
13584
- item.appendChild(ic);
13585
- if (label) {
13586
- const tx = document.createElement("span");
13587
- tx.textContent = label(v);
13588
- tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
13589
- item.appendChild(tx);
13480
+ this.menuPop = this.hostFloat(anchor, {
13481
+ zIndex: 26,
13482
+ padding: "4px",
13483
+ fill: (menu, pop) => {
13484
+ for (const v of values) {
13485
+ const active = v === current;
13486
+ const item = document.createElement("button");
13487
+ item.type = "button";
13488
+ item.className = "vela-dpop-item";
13489
+ item.dataset.active = active ? "1" : "0";
13490
+ item.style.cssText = `display:flex;align-items:center;gap:8px;${label ? "min-width:118px;" : ""}padding:5px 8px;border:none;border-radius:5px;color:inherit;cursor:pointer;text-align:left;font:inherit;`;
13491
+ const ic = document.createElement("span");
13492
+ ic.style.cssText = "display:flex;flex:none;width:22px;justify-content:center;";
13493
+ ic.innerHTML = sized(render(v), 18);
13494
+ item.appendChild(ic);
13495
+ if (label) {
13496
+ const tx = document.createElement("span");
13497
+ tx.textContent = label(v);
13498
+ tx.style.cssText = "flex:1;font-variant-numeric:tabular-nums;";
13499
+ item.appendChild(tx);
13500
+ }
13501
+ item.addEventListener("click", (e) => {
13502
+ e.stopPropagation();
13503
+ pop.hide();
13504
+ onPick(v);
13505
+ });
13506
+ menu.appendChild(item);
13507
+ }
13590
13508
  }
13591
- item.addEventListener("click", (e) => {
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;
13509
+ });
13612
13510
  this.menuOwner = anchor;
13613
13511
  }
13614
13512
  closeMenu() {
13615
- if (this.menuEl) {
13616
- this.menuEl.remove();
13617
- this.menuEl = null;
13618
- }
13619
- this.menuOwner = null;
13513
+ this.menuPop?.hide();
13620
13514
  }
13621
13515
  /** A color control: an `icon` over a thin **colored underline** showing the current
13622
13516
  * value (the common idiom), with an invisible native picker overlaid to edit it.
@@ -13655,36 +13549,18 @@ var DrawingSettingsPopup = class {
13655
13549
  }
13656
13550
  /** A floating RGB picker + opacity slider anchored to a color swatch — emits `#RRGGBB(AA)`. */
13657
13551
  openColorPopover(anchor, color, onChange) {
13658
- this.closeColorPopover();
13659
- this.closeMenu();
13660
13552
  const t = this.theme;
13661
- const pop = document.createElement("div");
13662
- pop.style.cssText = `position:absolute;z-index:25;background:${t.background};border:1px solid var(--vela-border);border-radius:var(--vela-radius-lg);box-shadow:var(--vela-shadow);padding:10px;pointer-events:auto;`;
13663
- applyChromeTokens(pop, t);
13664
- pop.addEventListener("pointerdown", (e) => e.stopPropagation());
13665
- pop.appendChild(buildColorPicker(color, t, onChange));
13666
- this.host.appendChild(pop);
13667
- const ar = anchor.getBoundingClientRect();
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;
13553
+ this.colorPop = this.hostFloat(anchor, {
13554
+ zIndex: 25,
13555
+ padding: "10px",
13556
+ fill: (el) => {
13557
+ el.appendChild(buildColorPicker(color, t, onChange));
13558
+ }
13559
+ });
13680
13560
  this.colorOwner = anchor;
13681
13561
  }
13682
13562
  closeColorPopover() {
13683
- if (this.colorPop) {
13684
- this.colorPop.remove();
13685
- this.colorPop = null;
13686
- }
13687
- this.colorOwner = null;
13563
+ this.colorPop?.hide();
13688
13564
  }
13689
13565
  divider() {
13690
13566
  const d = document.createElement("div");
@@ -14067,9 +13943,9 @@ var UserDrawingController = class {
14067
13943
  }
14068
13944
  /**
14069
13945
  * Union of the visible drawings' price ranges on `paneId` whose time extent
14070
- * intersects [fromTime, toTime] folded into the pane's autoscale so an
14071
- * off-series drawing still expands the scale (mirrors Pine drawings). Hidden
14072
- * drawings and full-width references (hline null extent) are handled too.
13946
+ * intersects [fromTime, toTime]. Hidden drawings and full-width references
13947
+ * (hline null extent) are handled too. Kept as the seam for a future
13948
+ * per-drawing autoscale opt-in the renderer does not fold this in today.
14073
13949
  */
14074
13950
  priceRangeForPane(paneId, fromTime, toTime) {
14075
13951
  let lo = Infinity;
@@ -14924,7 +14800,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
14924
14800
 
14925
14801
  // src/renderers/native/chrome/AttributionMark.ts
14926
14802
  var ATTRIBUTION_URL = "https://luxalgo.com/vela";
14927
- var STYLE_ID6 = "vela-attribution-styles";
14803
+ var STYLE_ID5 = "vela-attribution-styles";
14928
14804
  var CSS = `
14929
14805
  .vela-attribution {
14930
14806
  text-decoration: none;
@@ -14987,11 +14863,11 @@ var CSS = `
14987
14863
  [data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
14988
14864
  [data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
14989
14865
  `;
14990
- function ensureStyles6(doc) {
14991
- let s = doc.getElementById(STYLE_ID6);
14866
+ function ensureStyles4(doc) {
14867
+ let s = doc.getElementById(STYLE_ID5);
14992
14868
  if (!s) {
14993
14869
  s = doc.createElement("style");
14994
- s.id = STYLE_ID6;
14870
+ s.id = STYLE_ID5;
14995
14871
  doc.head.appendChild(s);
14996
14872
  }
14997
14873
  s.textContent = CSS;
@@ -15020,7 +14896,7 @@ function createCustomMark(doc, html, background) {
15020
14896
  return el;
15021
14897
  }
15022
14898
  function createAttributionMark(doc, background) {
15023
- ensureStyles6(doc);
14899
+ ensureStyles4(doc);
15024
14900
  const a = doc.createElement("a");
15025
14901
  a.className = "vela-attribution";
15026
14902
  a.href = ATTRIBUTION_URL;
@@ -17998,7 +17874,6 @@ var NativeRenderer = class {
17998
17874
  const n = this.coords.barCount;
17999
17875
  if (n === 0) return;
18000
17876
  const vr = this.coords.visibleLogicalRange();
18001
- const vtr = this.coords.visibleTimeRange();
18002
17877
  const i0 = Math.max(0, Math.floor(vr.from));
18003
17878
  const i1 = Math.min(n - 1, Math.ceil(vr.to));
18004
17879
  if (i1 < i0) return;
@@ -18016,9 +17891,7 @@ var NativeRenderer = class {
18016
17891
  }
18017
17892
  const models = this.scene.indicatorsForPane(pane.id);
18018
17893
  const masterModels = models.filter((m) => m.ownScale !== true);
18019
- let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
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;
17894
+ const dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
18022
17895
  const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
18023
17896
  pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
18024
17897
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);