@luxalgo/vela 0.6.13 → 0.6.15

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 (49) hide show
  1. package/NOTICE +28 -28
  2. package/README.md +1 -1
  3. package/dist/{DataProvider-DhzstpQb.d.ts → DataProvider-XLdJB86x.d.ts} +1 -1
  4. package/dist/{DataProvider-DlMtrwqM.d.cts → DataProvider-xH2VBcFT.d.cts} +1 -1
  5. package/dist/{chunk-F4M24ANM.js → chunk-32A7KHFM.js} +73 -3
  6. package/dist/{chunk-KG4YT3TI.js → chunk-6DPAYEGO.js} +8 -1
  7. package/dist/{chunk-G52XAKZY.js → chunk-7C7NWEXH.js} +345 -148
  8. package/dist/{chunk-JKGA36ZM.js → chunk-J5QIR2Z3.js} +165 -130
  9. package/dist/{chunk-MHY7MVXH.js → chunk-MWDNPGU3.js} +7 -1
  10. package/dist/{chunk-G77Y7LK2.js → chunk-MWS4CUJL.js} +2 -2
  11. package/dist/{contributions-37nni40G.d.ts → contributions-h5BzEiV2.d.ts} +13 -2
  12. package/dist/{contributions-lPojhTxI.d.cts → contributions-x7iamlnd.d.cts} +13 -2
  13. package/dist/index.cjs +176 -127
  14. package/dist/index.d.cts +20 -12
  15. package/dist/index.d.ts +20 -12
  16. package/dist/index.js +4 -4
  17. package/dist/{options-CYS5Wmlx.d.ts → options-bdSAtp7y.d.cts} +5 -0
  18. package/dist/{options-CYS5Wmlx.d.cts → options-bdSAtp7y.d.ts} +5 -0
  19. package/dist/{plugin-Cwikpz1m.d.cts → plugin-2sJcf5mk.d.cts} +11 -8
  20. package/dist/{plugin-DHyaoMjW.d.ts → plugin-BLaNPh55.d.ts} +11 -8
  21. package/dist/plugin.cjs +8 -0
  22. package/dist/plugin.d.cts +5 -5
  23. package/dist/plugin.d.ts +5 -5
  24. package/dist/plugin.js +2 -2
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/{side-panel-CT9ZwIGz.d.cts → side-panel-HF0IAzwf.d.cts} +17 -0
  32. package/dist/{side-panel-CT9ZwIGz.d.ts → side-panel-HF0IAzwf.d.ts} +17 -0
  33. package/dist/{statusline-model-D-q_OOx9.d.ts → statusline-model--tH0VWCy.d.cts} +6 -4
  34. package/dist/{statusline-model-CiJm-riV.d.cts → statusline-model-CLpaO3Dl.d.ts} +6 -4
  35. package/dist/ui.cjs +12 -0
  36. package/dist/ui.d.cts +1 -1
  37. package/dist/ui.d.ts +1 -1
  38. package/dist/ui.js +3 -3
  39. package/dist/vela.global.js +176 -127
  40. package/dist/vela.global.min.js +52 -46
  41. package/dist/widget.cjs +587 -272
  42. package/dist/widget.d.cts +15 -11
  43. package/dist/widget.d.ts +15 -11
  44. package/dist/widget.js +7 -7
  45. package/dist/workspace.cjs +587 -272
  46. package/dist/workspace.d.cts +29 -10
  47. package/dist/workspace.d.ts +29 -10
  48. package/dist/workspace.js +6 -6
  49. package/package.json +1 -1
package/dist/ui.js CHANGED
@@ -1,3 +1,3 @@
1
- export { Drawer, KeymapManager, Tooltip, drawerController, tooltipController } from './chunk-G77Y7LK2.js';
2
- export { CALLOUT_CSS, CALLOUT_STYLE_ID, CalloutBubble, ColorField, Dialog, FIELD_GAP_PX, GlyphSelect, Menu, NumberInput, Popover, Select, Switch, TextArea, TextField, WIDTH_FIELD_OPTIONS, applyPlotOverlayTokens, applyThemeTokens, blendOver, buildColorPicker, buildFieldControl, buildPalette, calloutPanelRows, clampNumber, closeColorPopover, closeOpenPopovers, closeWidthPopover, closesPanel, colorField, colorPickerController, combineColor, decorateSelectScroll, dialogController, ensureUIHost, eventDismissedPopover, fieldGrid, fieldGridColumns, fieldRow, fieldSection, fieldSeparator, fillSelectList, glyphSelectController, hslHex, insetRect, intersectRects, isPopoverOpen, lineWidthGlyph, menuController, nextUid, normalizeProps, numberInputController, openPopoverTrigger, openSelectList, placePopover, popoverController, runMachine, selectController, snapToStep, splitColor, spreadProps, switchController, textAreaController, textFieldController, toggleSelectList, transparencyChecker, viewportRect, widthField, widthFieldOptions } from './chunk-MHY7MVXH.js';
3
- export { FIELD_FOCUS_CSS, FIELD_FOCUS_RING, iconEl, iconMarkup, injectStyles, overlayScrollbarCss, registerIcon, svg16, svg24, withAlpha } from './chunk-KG4YT3TI.js';
1
+ export { Drawer, KeymapManager, Tooltip, drawerController, tooltipController } from './chunk-MWS4CUJL.js';
2
+ export { CALLOUT_CSS, CALLOUT_STYLE_ID, CalloutBubble, ColorField, Dialog, FIELD_GAP_PX, GlyphSelect, Menu, NumberInput, Popover, Select, Switch, TextArea, TextField, WIDTH_FIELD_OPTIONS, applyPlotOverlayTokens, applyThemeTokens, blendOver, buildColorPicker, buildFieldControl, buildPalette, calloutPanelRows, clampNumber, closeColorPopover, closeOpenPopovers, closeWidthPopover, closesPanel, colorField, colorPickerController, combineColor, decorateSelectScroll, dialogController, ensureUIHost, eventDismissedPopover, fieldGrid, fieldGridColumns, fieldRow, fieldSection, fieldSeparator, fillSelectList, glyphSelectController, hslHex, insetRect, intersectRects, isPopoverOpen, lineWidthGlyph, menuController, nextUid, normalizeProps, numberInputController, openPopoverTrigger, openSelectList, placePopover, popoverController, runMachine, selectController, snapToStep, splitColor, spreadProps, switchController, textAreaController, textFieldController, toggleSelectList, transparencyChecker, viewportRect, widthField, widthFieldOptions } from './chunk-MWDNPGU3.js';
3
+ export { FIELD_FOCUS_CSS, FIELD_FOCUS_RING, iconEl, iconMarkup, injectStyles, overlayScrollbarCss, registerIcon, svg16, svg24, withAlpha } from './chunk-6DPAYEGO.js';
@@ -276,6 +276,7 @@ var Vela = (function (exports) {
276
276
  var CROSSHAIR = "#9aa0ad";
277
277
  var SLATE_DEEP = "#1e293b";
278
278
  var SLATE = "#475569";
279
+ var CHIP_PLATE = "#595959";
279
280
  var TRADE_LONG = ACCENT;
280
281
  var TRADE_SHORT = BEARISH;
281
282
  var TRADE_EXIT = "#d500f9";
@@ -503,6 +504,10 @@ var Vela = (function (exports) {
503
504
  "market-post",
504
505
  svg24('<path d="M12 10V2"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 14-4 4-4-4"/><path d="M16 6a4 4 0 0 0-8 0"/>')
505
506
  );
507
+ registerIcon(
508
+ "market-extended",
509
+ svg24('<path d="M22 22H2"/><path d="M12 3a5 5 0 0 0 7.5 7.5A7.5 7.5 0 1 1 12 3Z"/>')
510
+ );
506
511
  registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
507
512
  registerIcon(
508
513
  "market-holiday",
@@ -516,6 +521,8 @@ var Vela = (function (exports) {
516
521
  registerIcon("chevrons-left", S('<path d="M12 3.5 7.5 8l4.5 4.5"/><path d="M7.5 3.5 3 8l4.5 4.5"/>'));
517
522
  registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
518
523
  registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
524
+ registerIcon("pin", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/>'));
525
+ registerIcon("pin-filled", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/><path d="M6.4 2.5h3.2v3.6l2 2.3v.9H4.4v-.9l2-2.3z" fill="currentColor" stroke="none"/>'));
519
526
  registerIcon("eye", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z"/><circle cx="8" cy="8" r="1.8"/>'));
520
527
  registerIcon("eye-off", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z" opacity="0.45"/><path d="m3 13 10-10"/>'));
521
528
  registerIcon("lock", S('<rect x="3.2" y="7" width="9.6" height="6.6" rx="1.2"/><path d="M5.6 7V5.2a2.4 2.4 0 0 1 4.8 0V7"/>'));
@@ -7578,6 +7585,7 @@ var Vela = (function (exports) {
7578
7585
  var PREVIEW_BARS = 300;
7579
7586
  var SINGLE_LOAD_BARS = 5e3;
7580
7587
  var CHUNK_BARS = 1e4;
7588
+ var FIRST_PAINT_BARS = 20;
7581
7589
  var GAP_FACTOR = 1.5;
7582
7590
  var HEAL_COOLDOWN_MS = 5e3;
7583
7591
  var EngineOrchestrator = class _EngineOrchestrator {
@@ -7853,6 +7861,7 @@ var Vela = (function (exports) {
7853
7861
  let painted = false;
7854
7862
  const paint = (bars, final) => {
7855
7863
  if (this.generation !== gen || !final && bars.length === 0) return;
7864
+ if (!painted && !final && bars.length < Math.min(requested, FIRST_PAINT_BARS)) return;
7856
7865
  this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
7857
7866
  if (!painted && bars.length > 0) {
7858
7867
  painted = true;
@@ -9270,6 +9279,15 @@ var Vela = (function (exports) {
9270
9279
  console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
9271
9280
  return null;
9272
9281
  }
9282
+ /**
9283
+ * Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
9284
+ * or null if the renderer has no canvas export. Silent — a host compositing
9285
+ * several charts skips a renderer that cannot contribute.
9286
+ */
9287
+ screenshotCanvas() {
9288
+ if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
9289
+ return null;
9290
+ }
9273
9291
  /**
9274
9292
  * The active renderer's full cosmetic config as a serializable, versioned JSON
9275
9293
  * document — persist it (templates, saved settings) and feed it back to
@@ -17139,6 +17157,12 @@ ${STATIC_DECLS}
17139
17157
  .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
17140
17158
  /* Text ENTRY is the one exception: selection is part of editing. */
17141
17159
  .vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
17160
+ /* iOS Safari zooms the page when a focused field is under 16px, and often
17161
+ keeps that zoom after the field blurs or a dialog closes. The shell's
17162
+ mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
17163
+ chart on a wide desktop still gets the rule when it is in the phone layout.
17164
+ Scoped to the kit root so a host page's own [data-layout] is never touched. */
17165
+ .vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
17142
17166
  .vela-icon { display: inline-flex; align-items: center; flex: none; }
17143
17167
  .vela-icon svg { display: block; }
17144
17168
  `;
@@ -22759,6 +22783,66 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22759
22783
  center: (wickLeftDev + wickDev / 2) / dpr
22760
22784
  };
22761
22785
  }
22786
+ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
22787
+ const out = [];
22788
+ let col = NaN;
22789
+ let intervals = [];
22790
+ const flush = () => {
22791
+ if (intervals.length === 0) return;
22792
+ const merged = [intervals[0]];
22793
+ for (let k = 1; k < intervals.length; k += 1) {
22794
+ const prev3 = merged[merged.length - 1];
22795
+ const next2 = intervals[k];
22796
+ if (Math.abs(yOf(prev3.hi) - yOf(next2.lo)) < 1) {
22797
+ prev3.hi = next2.hi;
22798
+ if (next2.headIdx < prev3.headIdx) {
22799
+ prev3.headIdx = next2.headIdx;
22800
+ prev3.headTime = next2.headTime;
22801
+ prev3.open = next2.open;
22802
+ }
22803
+ if (next2.lastIdx > prev3.lastIdx) {
22804
+ prev3.lastIdx = next2.lastIdx;
22805
+ prev3.close = next2.close;
22806
+ }
22807
+ } else {
22808
+ merged.push(next2);
22809
+ }
22810
+ }
22811
+ for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
22812
+ intervals = [];
22813
+ };
22814
+ for (let i = i0; i <= i1; i += 1) {
22815
+ const b = bars[i];
22816
+ if (!b || b.high <= b.low) continue;
22817
+ const x = Math.round(xOf(i));
22818
+ if (x !== col) {
22819
+ flush();
22820
+ col = x;
22821
+ }
22822
+ let lo = b.low;
22823
+ let hi = b.high;
22824
+ let headIdx = i;
22825
+ let headTime = b.time;
22826
+ let open2 = b.open;
22827
+ for (let k = intervals.length - 1; k >= 0; k -= 1) {
22828
+ const iv = intervals[k];
22829
+ if (iv.lo > hi || iv.hi < lo) continue;
22830
+ if (iv.lo < lo) lo = iv.lo;
22831
+ if (iv.hi > hi) hi = iv.hi;
22832
+ if (iv.headIdx < headIdx) {
22833
+ headIdx = iv.headIdx;
22834
+ headTime = iv.headTime;
22835
+ open2 = iv.open;
22836
+ }
22837
+ intervals.splice(k, 1);
22838
+ }
22839
+ let insertAt = 0;
22840
+ while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
22841
+ intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
22842
+ }
22843
+ flush();
22844
+ return out;
22845
+ }
22762
22846
 
22763
22847
  // src/renderers/native/core/chartConfig.ts
22764
22848
  var CHART_CONFIG_VERSION = 1;
@@ -22769,6 +22853,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22769
22853
  var BASELINE_LEVEL_DEFAULT = 50;
22770
22854
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
22771
22855
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
22856
+ var EXTENDED_SHADE = POSTMARKET_SHADE;
22772
22857
  function defaultChartStyle() {
22773
22858
  return {
22774
22859
  chartTypes: {},
@@ -22777,7 +22862,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22777
22862
  gridHorz: { visible: true, color: null },
22778
22863
  borderColor: null,
22779
22864
  separatorColor: null,
22780
- crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: SLATE },
22865
+ crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
22781
22866
  candle: {
22782
22867
  bodyVisible: true,
22783
22868
  borderVisible: false,
@@ -22800,7 +22885,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22800
22885
  width: 2,
22801
22886
  baselineLevel: BASELINE_LEVEL_DEFAULT
22802
22887
  },
22803
- sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
22888
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
22804
22889
  };
22805
22890
  }
22806
22891
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -23029,7 +23114,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23029
23114
  },
23030
23115
  sessions: {
23031
23116
  premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
23032
- postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
23117
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
23118
+ extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
23033
23119
  },
23034
23120
  stacking: {
23035
23121
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
@@ -24014,44 +24100,19 @@ void main() {
24014
24100
  }
24015
24101
  }
24016
24102
  /**
24017
- * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bucket bars
24018
- * sharing a rounded pixel column into one high-low stick draw cost bounded by
24019
- * screen width, not bar count. Bucket color follows first-open→last-close.
24103
+ * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
24104
+ * rounded pixel column collapse into high-low sticks one per contiguous
24105
+ * coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
24106
+ * column stays a void. Draw cost stays bounded by screen width, not bar count;
24107
+ * stick color follows its own first-open→last-close.
24020
24108
  */
24021
24109
  emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
24022
- let px = NaN;
24023
- let lo = Infinity;
24024
- let hi = -Infinity;
24025
- let openV = 0;
24026
- let closeV = 0;
24027
- let headTime = 0;
24028
- let active = false;
24029
- const flush = () => {
24030
- if (!active) return;
24031
- const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
24032
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
24033
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
24034
- b.rect(px - 0.5, hY, 1, lY - hY, c);
24035
- };
24036
- for (let i = i0; i <= i1; i += 1) {
24037
- const bar = bars[i];
24038
- if (!bar || bar.high <= bar.low) continue;
24039
- const x = Math.round(coords.logicalToX(i));
24040
- if (!active || x !== px) {
24041
- flush();
24042
- px = x;
24043
- lo = bar.low;
24044
- hi = bar.high;
24045
- openV = bar.open;
24046
- headTime = bar.time;
24047
- active = true;
24048
- } else {
24049
- if (bar.low < lo) lo = bar.low;
24050
- if (bar.high > hi) hi = bar.high;
24051
- }
24052
- closeV = bar.close;
24110
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
24111
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
24112
+ const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
24113
+ const hY = yOf(s.hi);
24114
+ b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
24053
24115
  }
24054
- flush();
24055
24116
  }
24056
24117
  emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
24057
24118
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -25349,13 +25410,15 @@ void main() {
25349
25410
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
25350
25411
  return this.orderedCache;
25351
25412
  }
25352
- /** The session zones resolved into colored bands (pre/post-market washes from the
25353
- * config's session colors) — consumed by the same painting path as {@link highlights}. */
25413
+ /** The session zones resolved into colored bands (pre/post-market or extended-hours
25414
+ * washes from the config's session colors) — consumed by the same painting path as
25415
+ * {@link highlights}. */
25354
25416
  sessionHighlightBands() {
25355
25417
  if (!this.sessionZones) return [];
25356
25418
  const out = [];
25357
25419
  for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
25358
25420
  for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
25421
+ for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
25359
25422
  return out;
25360
25423
  }
25361
25424
  indicatorsForPane(paneId) {
@@ -25894,50 +25957,23 @@ void main() {
25894
25957
  }
25895
25958
  }
25896
25959
  /**
25897
- * Sub-pixel LOD: bucket every bar sharing a rounded pixel column into ONE
25898
- * high-low stick, so draw cost is bounded by screen width (not bar count) when
25899
- * zoomed far out. The bucket's color follows its first-open→last-close
25900
- * direction (a barcolor() on the head bar still wins).
25960
+ * Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
25961
+ * sticks (one per contiguous coverage run see {@link aggregateCandleColumns}),
25962
+ * so draw cost is bounded by screen width (not bar count) when zoomed far out
25963
+ * and a price gap inside the column stays a void. Each stick's color follows
25964
+ * its first-open→last-close direction (a barcolor() on the head bar still wins).
25901
25965
  */
25902
25966
  drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
25903
- let px = NaN;
25904
- let lo = Infinity;
25905
- let hi = -Infinity;
25906
- let openV = 0;
25907
- let closeV = 0;
25908
- let headTime = 0;
25909
- let active = false;
25910
- const flush = () => {
25911
- if (!active) return;
25912
- const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
25913
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
25914
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
25915
- ctx.strokeStyle = color;
25916
- ctx.lineWidth = 1;
25967
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
25968
+ ctx.lineWidth = 1;
25969
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
25970
+ const x = s.x + 0.5;
25971
+ ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
25917
25972
  ctx.beginPath();
25918
- ctx.moveTo(px, hY);
25919
- ctx.lineTo(px, lY);
25973
+ ctx.moveTo(x, yOf(s.hi));
25974
+ ctx.lineTo(x, yOf(s.lo));
25920
25975
  ctx.stroke();
25921
- };
25922
- for (let i = i0; i <= i1; i += 1) {
25923
- const b = bars[i];
25924
- if (!b || b.high <= b.low) continue;
25925
- const x = Math.round(coords.logicalToX(i)) + 0.5;
25926
- if (!active || x !== px) {
25927
- flush();
25928
- px = x;
25929
- lo = b.low;
25930
- hi = b.high;
25931
- openV = b.open;
25932
- headTime = b.time;
25933
- active = true;
25934
- } else {
25935
- if (b.low < lo) lo = b.low;
25936
- if (b.high > hi) hi = b.high;
25937
- }
25938
- closeV = b.close;
25939
25976
  }
25940
- flush();
25941
25977
  }
25942
25978
  drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
25943
25979
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -26961,6 +26997,32 @@ void main() {
26961
26997
  }
26962
26998
  };
26963
26999
 
27000
+ // src/renderers/native/chrome/tz.ts
27001
+ function tzOffsetMs(ms, timeZone) {
27002
+ if (!timeZone || timeZone === "UTC") return 0;
27003
+ try {
27004
+ const dtf = new Intl.DateTimeFormat("en-US", {
27005
+ timeZone,
27006
+ hourCycle: "h23",
27007
+ year: "numeric",
27008
+ month: "2-digit",
27009
+ day: "2-digit",
27010
+ hour: "2-digit",
27011
+ minute: "2-digit",
27012
+ second: "2-digit"
27013
+ });
27014
+ const parts3 = dtf.formatToParts(new Date(ms));
27015
+ const get = (t) => Number(parts3.find((p) => p.type === t)?.value);
27016
+ const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
27017
+ return asUTC - ms;
27018
+ } catch {
27019
+ return 0;
27020
+ }
27021
+ }
27022
+ function zonedDate(ms, timeZone) {
27023
+ return new Date(ms + tzOffsetMs(ms, timeZone));
27024
+ }
27025
+
26964
27026
  // src/renderers/native/chrome/ticks.ts
26965
27027
  function priceTicks(min2, max2, target = 6) {
26966
27028
  if (!(max2 > min2) || !Number.isFinite(min2) || !Number.isFinite(max2)) return [];
@@ -27108,7 +27170,14 @@ void main() {
27108
27170
  YEAR
27109
27171
  ];
27110
27172
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
27173
+ var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
27111
27174
  var pad2 = (n) => n < 10 ? `0${n}` : String(n);
27175
+ function formatTimeStamp(ms, timeZone, barIntervalMs) {
27176
+ const d = zonedDate(ms, timeZone);
27177
+ const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
27178
+ if (barIntervalMs >= DAY) return date;
27179
+ return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
27180
+ }
27112
27181
  function pickStep(targetMs) {
27113
27182
  for (const step of STEP_LADDER) if (step >= targetMs) return step;
27114
27183
  return STEP_LADDER[STEP_LADDER.length - 1];
@@ -27150,32 +27219,6 @@ void main() {
27150
27219
  return out;
27151
27220
  }
27152
27221
 
27153
- // src/renderers/native/chrome/tz.ts
27154
- function tzOffsetMs(ms, timeZone) {
27155
- if (!timeZone || timeZone === "UTC") return 0;
27156
- try {
27157
- const dtf = new Intl.DateTimeFormat("en-US", {
27158
- timeZone,
27159
- hourCycle: "h23",
27160
- year: "numeric",
27161
- month: "2-digit",
27162
- day: "2-digit",
27163
- hour: "2-digit",
27164
- minute: "2-digit",
27165
- second: "2-digit"
27166
- });
27167
- const parts3 = dtf.formatToParts(new Date(ms));
27168
- const get = (t) => Number(parts3.find((p) => p.type === t)?.value);
27169
- const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
27170
- return asUTC - ms;
27171
- } catch {
27172
- return 0;
27173
- }
27174
- }
27175
- function zonedDate(ms, timeZone) {
27176
- return new Date(ms + tzOffsetMs(ms, timeZone));
27177
- }
27178
-
27179
27222
  // src/renderers/native/chrome/ChromeRenderer.ts
27180
27223
  var ChromeRenderer = class {
27181
27224
  constructor() {
@@ -27622,7 +27665,7 @@ void main() {
27622
27665
  const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
27623
27666
  this.chip(ctx, dataW + 1, ch.y, formatAxisValue(pane.scale, pane.bounds.height, price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
27624
27667
  }
27625
- this.chip(ctx, x, dataH + 1, formatStamp(coords.logicalToTime(logical), scene.timezone), chipBg, "center", true, theme.background);
27668
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
27626
27669
  }
27627
27670
  destroy() {
27628
27671
  this.canvas = null;
@@ -27670,7 +27713,7 @@ void main() {
27670
27713
  this.chip(ctx, dataW + 1, ext.y, formatAxisValue(pane.scale, pane.bounds.height, ext.price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
27671
27714
  }
27672
27715
  }
27673
- this.chip(ctx, x, dataH + 1, formatStamp(ext.time, scene.timezone), chipBg, "center", true, theme.background);
27716
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
27674
27717
  ctx.globalAlpha = 1;
27675
27718
  }
27676
27719
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
@@ -27703,11 +27746,6 @@ void main() {
27703
27746
  else if (style === "dotted") ctx.setLineDash([2, 3]);
27704
27747
  else ctx.setLineDash([]);
27705
27748
  }
27706
- function formatStamp(ms, timeZone) {
27707
- const d = zonedDate(ms, timeZone);
27708
- const pad = (n) => n < 10 ? `0${n}` : String(n);
27709
- return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
27710
- }
27711
27749
 
27712
27750
  // src/core/timezones.ts
27713
27751
  var TIMEZONES = [
@@ -34196,6 +34234,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34196
34234
  const end = Math.min(right, x2);
34197
34235
  return end > x ? { x, width: end - x } : null;
34198
34236
  }
34237
+ function bandEdgeSlot(logical) {
34238
+ return Math.ceil(logical) - 0.5;
34239
+ }
34199
34240
  var BackdropRenderer = class {
34200
34241
  constructor() {
34201
34242
  this.canvas = null;
@@ -34226,21 +34267,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34226
34267
  this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
34227
34268
  }
34228
34269
  /** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
34229
- * Session-zone washes (pre/post-market) paint first, host highlights on top. */
34270
+ * Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
34271
+ * never cuts a candle in two), host highlights on top (continuous time mapping —
34272
+ * hosts may paint sub-bar spans). */
34230
34273
  drawHighlights(ctx, scene, coords) {
34231
34274
  const sessions = scene.sessionHighlightBands();
34232
34275
  if (sessions.length > 0) {
34233
34276
  const left = Math.max(0, coords.logicalToX(-0.5));
34234
34277
  const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
34235
- this.drawHighlightSet(ctx, sessions, coords, left, right);
34278
+ this.drawHighlightSet(ctx, sessions, coords, left, right, true);
34236
34279
  }
34237
- this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
34280
+ this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
34238
34281
  }
34239
- drawHighlightSet(ctx, bands, coords, left, right) {
34282
+ drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
34283
+ const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
34240
34284
  for (const band of bands) {
34241
- const x1 = coords.timeToX(band.from);
34242
- const x2 = coords.timeToX(band.to);
34243
- const rect = clipHighlightRect(x1, x2, left, right);
34285
+ const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
34244
34286
  if (!rect) continue;
34245
34287
  ctx.fillStyle = band.color;
34246
34288
  ctx.fillRect(rect.x, 0, rect.width, coords.height);
@@ -34499,6 +34541,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34499
34541
  });
34500
34542
  el.style.color = attributionMarkColor(background);
34501
34543
  el.innerHTML = html;
34544
+ el.dataset.velaScreenshot = "1";
34502
34545
  return el;
34503
34546
  }
34504
34547
  function createAttributionMark(doc, background) {
@@ -34517,6 +34560,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34517
34560
  cursor: "pointer"
34518
34561
  });
34519
34562
  applyAttributionMarkTheme(a, background);
34563
+ a.dataset.velaScreenshot = "1";
34520
34564
  const symbol = doc.createElement("span");
34521
34565
  symbol.className = "vela-attr-symbol";
34522
34566
  symbol.setAttribute("aria-hidden", "true");
@@ -35521,7 +35565,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35521
35565
  };
35522
35566
  })(),
35523
35567
  series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
35524
- sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
35568
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
35525
35569
  };
35526
35570
  }
35527
35571
  /**
@@ -35603,7 +35647,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35603
35647
  width: next2.baseline.width,
35604
35648
  baselineLevel: next2.baseline.baselineLevel
35605
35649
  };
35606
- s.sessions = { premarketColor: next2.sessions.premarketColor, postmarketColor: next2.sessions.postmarketColor };
35650
+ s.sessions = { premarketColor: next2.sessions.premarketColor, postmarketColor: next2.sessions.postmarketColor, extendedColor: next2.sessions.extendedColor };
35607
35651
  this.setPriceStyle(next2.series.style);
35608
35652
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
35609
35653
  this.scene.baselineValue = next2.series.baseline;
@@ -35825,9 +35869,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35825
35869
  * opts in with a `data-vela-screenshot` attribute on the mount container's
35826
35870
  * subtree (the widget marks its status line, and its symbol watermark with
35827
35871
  * `"under"` — drawn beneath the canvases, where it sits on screen). Only the
35828
- * crosshair (L2) is intentionally excluded.
35872
+ * crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
35873
+ * as a PNG data URL.
35829
35874
  */
35830
- screenshot() {
35875
+ screenshotCanvas() {
35831
35876
  if (!this.dataCanvas) return null;
35832
35877
  this.computeScales();
35833
35878
  this.paintData();
@@ -35855,7 +35900,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35855
35900
  if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
35856
35901
  }
35857
35902
  }
35858
- return out.toDataURL("image/png");
35903
+ return out;
35904
+ }
35905
+ screenshot() {
35906
+ return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
35859
35907
  }
35860
35908
  /**
35861
35909
  * The candles-first-appear reveal — animates the **candles themselves** (no extra
@@ -37250,7 +37298,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37250
37298
  const pricePane = this.dataWindowPricePane();
37251
37299
  const ohlc = this.dataWindowOHLC(bar, pricePane);
37252
37300
  const parts3 = [];
37253
- if (bar) parts3.push(formatStamp2(bar.time, this.scene.timezone));
37301
+ if (bar) parts3.push(formatStamp(bar.time, this.scene.timezone));
37254
37302
  if (ohlc) parts3.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
37255
37303
  for (const group of this.dataWindowGroups(idx, pricePane)) {
37256
37304
  for (const row of group.rows) parts3.push(`${row.label} ${row.value}`);
@@ -38137,7 +38185,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38137
38185
  time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
38138
38186
  };
38139
38187
  }
38140
- function formatStamp2(ms, timeZone) {
38188
+ function formatStamp(ms, timeZone) {
38141
38189
  const { date, time } = formatStampParts(ms, timeZone);
38142
38190
  return `${date} ${time}`;
38143
38191
  }
@@ -38168,7 +38216,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38168
38216
  }
38169
38217
  return out.sort((a, b) => a[0] - b[0]);
38170
38218
  };
38171
- return { pre: windows(v.pre), post: windows(v.post) };
38219
+ return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
38172
38220
  }
38173
38221
  function clamp012(v) {
38174
38222
  return Math.max(0, Math.min(1, v));
@@ -40076,6 +40124,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
40076
40124
  exports.BarStore = BarStore;
40077
40125
  exports.BinanceProvider = BinanceProvider;
40078
40126
  exports.CATEGORICAL = CATEGORICAL;
40127
+ exports.CHIP_PLATE = CHIP_PLATE;
40079
40128
  exports.CROSSHAIR = CROSSHAIR;
40080
40129
  exports.CachingDataFeed = CachingDataFeed;
40081
40130
  exports.CoinbaseProvider = CoinbaseProvider;