@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
@@ -1,6 +1,6 @@
1
- import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, 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, magnifierTimeframeLabel, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-F4M24ANM.js';
2
- import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MHY7MVXH.js';
3
- import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-KG4YT3TI.js';
1
+ import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, 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, magnifierTimeframeLabel, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-32A7KHFM.js';
2
+ import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MWDNPGU3.js';
3
+ import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-6DPAYEGO.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
6
6
  var TypedEventBus = class {
@@ -468,6 +468,15 @@ var RendererControl = class {
468
468
  console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
469
469
  return null;
470
470
  }
471
+ /**
472
+ * Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
473
+ * or null if the renderer has no canvas export. Silent — a host compositing
474
+ * several charts skips a renderer that cannot contribute.
475
+ */
476
+ screenshotCanvas() {
477
+ if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
478
+ return null;
479
+ }
471
480
  /**
472
481
  * The active renderer's full cosmetic config as a serializable, versioned JSON
473
482
  * document — persist it (templates, saved settings) and feed it back to
@@ -5080,6 +5089,66 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
5080
5089
  center: (wickLeftDev + wickDev / 2) / dpr
5081
5090
  };
5082
5091
  }
5092
+ function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
5093
+ const out = [];
5094
+ let col = NaN;
5095
+ let intervals = [];
5096
+ const flush = () => {
5097
+ if (intervals.length === 0) return;
5098
+ const merged = [intervals[0]];
5099
+ for (let k = 1; k < intervals.length; k += 1) {
5100
+ const prev2 = merged[merged.length - 1];
5101
+ const next = intervals[k];
5102
+ if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
5103
+ prev2.hi = next.hi;
5104
+ if (next.headIdx < prev2.headIdx) {
5105
+ prev2.headIdx = next.headIdx;
5106
+ prev2.headTime = next.headTime;
5107
+ prev2.open = next.open;
5108
+ }
5109
+ if (next.lastIdx > prev2.lastIdx) {
5110
+ prev2.lastIdx = next.lastIdx;
5111
+ prev2.close = next.close;
5112
+ }
5113
+ } else {
5114
+ merged.push(next);
5115
+ }
5116
+ }
5117
+ for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
5118
+ intervals = [];
5119
+ };
5120
+ for (let i = i0; i <= i1; i += 1) {
5121
+ const b = bars[i];
5122
+ if (!b || b.high <= b.low) continue;
5123
+ const x = Math.round(xOf(i));
5124
+ if (x !== col) {
5125
+ flush();
5126
+ col = x;
5127
+ }
5128
+ let lo = b.low;
5129
+ let hi = b.high;
5130
+ let headIdx = i;
5131
+ let headTime = b.time;
5132
+ let open = b.open;
5133
+ for (let k = intervals.length - 1; k >= 0; k -= 1) {
5134
+ const iv = intervals[k];
5135
+ if (iv.lo > hi || iv.hi < lo) continue;
5136
+ if (iv.lo < lo) lo = iv.lo;
5137
+ if (iv.hi > hi) hi = iv.hi;
5138
+ if (iv.headIdx < headIdx) {
5139
+ headIdx = iv.headIdx;
5140
+ headTime = iv.headTime;
5141
+ open = iv.open;
5142
+ }
5143
+ intervals.splice(k, 1);
5144
+ }
5145
+ let insertAt = 0;
5146
+ while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
5147
+ intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open, lastIdx: i, close: b.close });
5148
+ }
5149
+ flush();
5150
+ return out;
5151
+ }
5083
5152
 
5084
5153
  // src/renderers/native/core/chartConfig.ts
5085
5154
  var CHART_CONFIG_VERSION = 1;
@@ -5090,6 +5159,7 @@ var BASELINE_FILL_ALPHA_FAR = 0.05;
5090
5159
  var BASELINE_LEVEL_DEFAULT = 50;
5091
5160
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
5092
5161
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
5162
+ var EXTENDED_SHADE = POSTMARKET_SHADE;
5093
5163
  function defaultChartStyle() {
5094
5164
  return {
5095
5165
  chartTypes: {},
@@ -5098,7 +5168,7 @@ function defaultChartStyle() {
5098
5168
  gridHorz: { visible: true, color: null },
5099
5169
  borderColor: null,
5100
5170
  separatorColor: null,
5101
- crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: SLATE },
5171
+ crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
5102
5172
  candle: {
5103
5173
  bodyVisible: true,
5104
5174
  borderVisible: false,
@@ -5121,7 +5191,7 @@ function defaultChartStyle() {
5121
5191
  width: 2,
5122
5192
  baselineLevel: BASELINE_LEVEL_DEFAULT
5123
5193
  },
5124
- sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
5194
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
5125
5195
  };
5126
5196
  }
5127
5197
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -5350,7 +5420,8 @@ function mergeConfig(base, patch) {
5350
5420
  },
5351
5421
  sessions: {
5352
5422
  premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
5353
- postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
5423
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
5424
+ extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
5354
5425
  },
5355
5426
  stacking: {
5356
5427
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
@@ -6335,44 +6406,19 @@ var WebGL2Backend = class {
6335
6406
  }
6336
6407
  }
6337
6408
  /**
6338
- * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bucket bars
6339
- * sharing a rounded pixel column into one high-low stick draw cost bounded by
6340
- * screen width, not bar count. Bucket color follows first-open→last-close.
6409
+ * Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
6410
+ * rounded pixel column collapse into high-low sticks one per contiguous
6411
+ * coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
6412
+ * column stays a void. Draw cost stays bounded by screen width, not bar count;
6413
+ * stick color follows its own first-open→last-close.
6341
6414
  */
6342
6415
  emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
6343
- let px = NaN;
6344
- let lo = Infinity;
6345
- let hi = -Infinity;
6346
- let openV = 0;
6347
- let closeV = 0;
6348
- let headTime = 0;
6349
- let active = false;
6350
- const flush = () => {
6351
- if (!active) return;
6352
- const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
6353
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
6354
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
6355
- b.rect(px - 0.5, hY, 1, lY - hY, c);
6356
- };
6357
- for (let i = i0; i <= i1; i += 1) {
6358
- const bar = bars[i];
6359
- if (!bar || bar.high <= bar.low) continue;
6360
- const x = Math.round(coords.logicalToX(i));
6361
- if (!active || x !== px) {
6362
- flush();
6363
- px = x;
6364
- lo = bar.low;
6365
- hi = bar.high;
6366
- openV = bar.open;
6367
- headTime = bar.time;
6368
- active = true;
6369
- } else {
6370
- if (bar.low < lo) lo = bar.low;
6371
- if (bar.high > hi) hi = bar.high;
6372
- }
6373
- closeV = bar.close;
6416
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
6417
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
6418
+ const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
6419
+ const hY = yOf(s.hi);
6420
+ b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
6374
6421
  }
6375
- flush();
6376
6422
  }
6377
6423
  emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
6378
6424
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -7670,13 +7716,15 @@ var SceneGraph = class {
7670
7716
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
7671
7717
  return this.orderedCache;
7672
7718
  }
7673
- /** The session zones resolved into colored bands (pre/post-market washes from the
7674
- * config's session colors) — consumed by the same painting path as {@link highlights}. */
7719
+ /** The session zones resolved into colored bands (pre/post-market or extended-hours
7720
+ * washes from the config's session colors) — consumed by the same painting path as
7721
+ * {@link highlights}. */
7675
7722
  sessionHighlightBands() {
7676
7723
  if (!this.sessionZones) return [];
7677
7724
  const out = [];
7678
7725
  for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
7679
7726
  for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
7727
+ for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
7680
7728
  return out;
7681
7729
  }
7682
7730
  indicatorsForPane(paneId) {
@@ -8215,50 +8263,23 @@ var Canvas2dBackend = class {
8215
8263
  }
8216
8264
  }
8217
8265
  /**
8218
- * Sub-pixel LOD: bucket every bar sharing a rounded pixel column into ONE
8219
- * high-low stick, so draw cost is bounded by screen width (not bar count) when
8220
- * zoomed far out. The bucket's color follows its first-open→last-close
8221
- * direction (a barcolor() on the head bar still wins).
8266
+ * Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
8267
+ * sticks (one per contiguous coverage run see {@link aggregateCandleColumns}),
8268
+ * so draw cost is bounded by screen width (not bar count) when zoomed far out
8269
+ * and a price gap inside the column stays a void. Each stick's color follows
8270
+ * its first-open→last-close direction (a barcolor() on the head bar still wins).
8222
8271
  */
8223
8272
  drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
8224
- let px = NaN;
8225
- let lo = Infinity;
8226
- let hi = -Infinity;
8227
- let openV = 0;
8228
- let closeV = 0;
8229
- let headTime = 0;
8230
- let active = false;
8231
- const flush = () => {
8232
- if (!active) return;
8233
- const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
8234
- const hY = coords.priceToY(hi, pane.scale, pane.bounds);
8235
- const lY = coords.priceToY(lo, pane.scale, pane.bounds);
8236
- ctx.strokeStyle = color;
8237
- ctx.lineWidth = 1;
8273
+ const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
8274
+ ctx.lineWidth = 1;
8275
+ for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
8276
+ const x = s.x + 0.5;
8277
+ ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
8238
8278
  ctx.beginPath();
8239
- ctx.moveTo(px, hY);
8240
- ctx.lineTo(px, lY);
8279
+ ctx.moveTo(x, yOf(s.hi));
8280
+ ctx.lineTo(x, yOf(s.lo));
8241
8281
  ctx.stroke();
8242
- };
8243
- for (let i = i0; i <= i1; i += 1) {
8244
- const b = bars[i];
8245
- if (!b || b.high <= b.low) continue;
8246
- const x = Math.round(coords.logicalToX(i)) + 0.5;
8247
- if (!active || x !== px) {
8248
- flush();
8249
- px = x;
8250
- lo = b.low;
8251
- hi = b.high;
8252
- openV = b.open;
8253
- headTime = b.time;
8254
- active = true;
8255
- } else {
8256
- if (b.low < lo) lo = b.low;
8257
- if (b.high > hi) hi = b.high;
8258
- }
8259
- closeV = b.close;
8260
8282
  }
8261
- flush();
8262
8283
  }
8263
8284
  drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
8264
8285
  if (spec.kind === "candle" || spec.kind === "bar") {
@@ -9282,6 +9303,32 @@ var DrawingSceneRenderer = class {
9282
9303
  }
9283
9304
  };
9284
9305
 
9306
+ // src/renderers/native/chrome/tz.ts
9307
+ function tzOffsetMs(ms, timeZone) {
9308
+ if (!timeZone || timeZone === "UTC") return 0;
9309
+ try {
9310
+ const dtf = new Intl.DateTimeFormat("en-US", {
9311
+ timeZone,
9312
+ hourCycle: "h23",
9313
+ year: "numeric",
9314
+ month: "2-digit",
9315
+ day: "2-digit",
9316
+ hour: "2-digit",
9317
+ minute: "2-digit",
9318
+ second: "2-digit"
9319
+ });
9320
+ const parts = dtf.formatToParts(new Date(ms));
9321
+ const get = (t) => Number(parts.find((p) => p.type === t)?.value);
9322
+ const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
9323
+ return asUTC - ms;
9324
+ } catch {
9325
+ return 0;
9326
+ }
9327
+ }
9328
+ function zonedDate(ms, timeZone) {
9329
+ return new Date(ms + tzOffsetMs(ms, timeZone));
9330
+ }
9331
+
9285
9332
  // src/renderers/native/chrome/ticks.ts
9286
9333
  function priceTicks(min, max, target = 6) {
9287
9334
  if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return [];
@@ -9429,7 +9476,14 @@ var STEP_LADDER = [
9429
9476
  YEAR
9430
9477
  ];
9431
9478
  var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
9479
+ var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
9432
9480
  var pad2 = (n) => n < 10 ? `0${n}` : String(n);
9481
+ function formatTimeStamp(ms, timeZone, barIntervalMs) {
9482
+ const d = zonedDate(ms, timeZone);
9483
+ const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
9484
+ if (barIntervalMs >= DAY) return date;
9485
+ return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
9486
+ }
9433
9487
  function pickStep(targetMs) {
9434
9488
  for (const step of STEP_LADDER) if (step >= targetMs) return step;
9435
9489
  return STEP_LADDER[STEP_LADDER.length - 1];
@@ -9471,32 +9525,6 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
9471
9525
  return out;
9472
9526
  }
9473
9527
 
9474
- // src/renderers/native/chrome/tz.ts
9475
- function tzOffsetMs(ms, timeZone) {
9476
- if (!timeZone || timeZone === "UTC") return 0;
9477
- try {
9478
- const dtf = new Intl.DateTimeFormat("en-US", {
9479
- timeZone,
9480
- hourCycle: "h23",
9481
- year: "numeric",
9482
- month: "2-digit",
9483
- day: "2-digit",
9484
- hour: "2-digit",
9485
- minute: "2-digit",
9486
- second: "2-digit"
9487
- });
9488
- const parts = dtf.formatToParts(new Date(ms));
9489
- const get = (t) => Number(parts.find((p) => p.type === t)?.value);
9490
- const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
9491
- return asUTC - ms;
9492
- } catch {
9493
- return 0;
9494
- }
9495
- }
9496
- function zonedDate(ms, timeZone) {
9497
- return new Date(ms + tzOffsetMs(ms, timeZone));
9498
- }
9499
-
9500
9528
  // src/renderers/native/chrome/ChromeRenderer.ts
9501
9529
  var ChromeRenderer = class {
9502
9530
  constructor() {
@@ -9943,7 +9971,7 @@ var CrosshairRenderer = class {
9943
9971
  const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
9944
9972
  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);
9945
9973
  }
9946
- this.chip(ctx, x, dataH + 1, formatStamp(coords.logicalToTime(logical), scene.timezone), chipBg, "center", true, theme.background);
9974
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
9947
9975
  }
9948
9976
  destroy() {
9949
9977
  this.canvas = null;
@@ -9991,7 +10019,7 @@ var CrosshairRenderer = class {
9991
10019
  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);
9992
10020
  }
9993
10021
  }
9994
- this.chip(ctx, x, dataH + 1, formatStamp(ext.time, scene.timezone), chipBg, "center", true, theme.background);
10022
+ this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
9995
10023
  ctx.globalAlpha = 1;
9996
10024
  }
9997
10025
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
@@ -10024,11 +10052,6 @@ function setDash3(ctx, style) {
10024
10052
  else if (style === "dotted") ctx.setLineDash([2, 3]);
10025
10053
  else ctx.setLineDash([]);
10026
10054
  }
10027
- function formatStamp(ms, timeZone) {
10028
- const d = zonedDate(ms, timeZone);
10029
- const pad = (n) => n < 10 ? `0${n}` : String(n);
10030
- return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
10031
- }
10032
10055
 
10033
10056
  // src/renderers/native/chrome/settings-visibility.ts
10034
10057
  function settingsIdSlug(label) {
@@ -15807,6 +15830,9 @@ function clipHighlightRect(x1, x2, left, right) {
15807
15830
  const end = Math.min(right, x2);
15808
15831
  return end > x ? { x, width: end - x } : null;
15809
15832
  }
15833
+ function bandEdgeSlot(logical) {
15834
+ return Math.ceil(logical) - 0.5;
15835
+ }
15810
15836
  var BackdropRenderer = class {
15811
15837
  constructor() {
15812
15838
  this.canvas = null;
@@ -15837,21 +15863,22 @@ var BackdropRenderer = class {
15837
15863
  this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
15838
15864
  }
15839
15865
  /** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
15840
- * Session-zone washes (pre/post-market) paint first, host highlights on top. */
15866
+ * Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
15867
+ * never cuts a candle in two), host highlights on top (continuous time mapping —
15868
+ * hosts may paint sub-bar spans). */
15841
15869
  drawHighlights(ctx, scene, coords) {
15842
15870
  const sessions = scene.sessionHighlightBands();
15843
15871
  if (sessions.length > 0) {
15844
15872
  const left = Math.max(0, coords.logicalToX(-0.5));
15845
15873
  const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
15846
- this.drawHighlightSet(ctx, sessions, coords, left, right);
15874
+ this.drawHighlightSet(ctx, sessions, coords, left, right, true);
15847
15875
  }
15848
- this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
15876
+ this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
15849
15877
  }
15850
- drawHighlightSet(ctx, bands, coords, left, right) {
15878
+ drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
15879
+ const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
15851
15880
  for (const band of bands) {
15852
- const x1 = coords.timeToX(band.from);
15853
- const x2 = coords.timeToX(band.to);
15854
- const rect = clipHighlightRect(x1, x2, left, right);
15881
+ const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
15855
15882
  if (!rect) continue;
15856
15883
  ctx.fillStyle = band.color;
15857
15884
  ctx.fillRect(rect.x, 0, rect.width, coords.height);
@@ -16084,6 +16111,7 @@ function createCustomMark(doc, html, background) {
16084
16111
  });
16085
16112
  el.style.color = attributionMarkColor(background);
16086
16113
  el.innerHTML = html;
16114
+ el.dataset.velaScreenshot = "1";
16087
16115
  return el;
16088
16116
  }
16089
16117
  function createAttributionMark(doc, background) {
@@ -16102,6 +16130,7 @@ function createAttributionMark(doc, background) {
16102
16130
  cursor: "pointer"
16103
16131
  });
16104
16132
  applyAttributionMarkTheme(a, background);
16133
+ a.dataset.velaScreenshot = "1";
16105
16134
  const symbol = doc.createElement("span");
16106
16135
  symbol.className = "vela-attr-symbol";
16107
16136
  symbol.setAttribute("aria-hidden", "true");
@@ -17106,7 +17135,7 @@ var NativeRenderer = class {
17106
17135
  };
17107
17136
  })(),
17108
17137
  series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
17109
- sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
17138
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
17110
17139
  };
17111
17140
  }
17112
17141
  /**
@@ -17188,7 +17217,7 @@ var NativeRenderer = class {
17188
17217
  width: next.baseline.width,
17189
17218
  baselineLevel: next.baseline.baselineLevel
17190
17219
  };
17191
- s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
17220
+ s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor, extendedColor: next.sessions.extendedColor };
17192
17221
  this.setPriceStyle(next.series.style);
17193
17222
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
17194
17223
  this.scene.baselineValue = next.series.baseline;
@@ -17410,9 +17439,10 @@ var NativeRenderer = class {
17410
17439
  * opts in with a `data-vela-screenshot` attribute on the mount container's
17411
17440
  * subtree (the widget marks its status line, and its symbol watermark with
17412
17441
  * `"under"` — drawn beneath the canvases, where it sits on screen). Only the
17413
- * crosshair (L2) is intentionally excluded.
17442
+ * crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
17443
+ * as a PNG data URL.
17414
17444
  */
17415
- screenshot() {
17445
+ screenshotCanvas() {
17416
17446
  if (!this.dataCanvas) return null;
17417
17447
  this.computeScales();
17418
17448
  this.paintData();
@@ -17440,7 +17470,10 @@ var NativeRenderer = class {
17440
17470
  if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
17441
17471
  }
17442
17472
  }
17443
- return out.toDataURL("image/png");
17473
+ return out;
17474
+ }
17475
+ screenshot() {
17476
+ return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
17444
17477
  }
17445
17478
  /**
17446
17479
  * The candles-first-appear reveal — animates the **candles themselves** (no extra
@@ -18835,7 +18868,7 @@ var NativeRenderer = class {
18835
18868
  const pricePane = this.dataWindowPricePane();
18836
18869
  const ohlc = this.dataWindowOHLC(bar, pricePane);
18837
18870
  const parts = [];
18838
- if (bar) parts.push(formatStamp2(bar.time, this.scene.timezone));
18871
+ if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
18839
18872
  if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
18840
18873
  for (const group of this.dataWindowGroups(idx, pricePane)) {
18841
18874
  for (const row of group.rows) parts.push(`${row.label} ${row.value}`);
@@ -19722,7 +19755,7 @@ function formatStampParts(ms, timeZone) {
19722
19755
  time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
19723
19756
  };
19724
19757
  }
19725
- function formatStamp2(ms, timeZone) {
19758
+ function formatStamp(ms, timeZone) {
19726
19759
  const { date, time } = formatStampParts(ms, timeZone);
19727
19760
  return `${date} ${time}`;
19728
19761
  }
@@ -19753,7 +19786,7 @@ function sanitizeSessionZones(value) {
19753
19786
  }
19754
19787
  return out.sort((a, b) => a[0] - b[0]);
19755
19788
  };
19756
- return { pre: windows(v.pre), post: windows(v.post) };
19789
+ return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
19757
19790
  }
19758
19791
  function clamp012(v) {
19759
19792
  return Math.max(0, Math.min(1, v));
@@ -20155,6 +20188,7 @@ var RUN_EMIT_THROTTLE_MS = 1e3;
20155
20188
  var PREVIEW_BARS = 300;
20156
20189
  var SINGLE_LOAD_BARS = 5e3;
20157
20190
  var CHUNK_BARS = 1e4;
20191
+ var FIRST_PAINT_BARS = 20;
20158
20192
  var GAP_FACTOR = 1.5;
20159
20193
  var HEAL_COOLDOWN_MS = 5e3;
20160
20194
  var EngineOrchestrator = class _EngineOrchestrator {
@@ -20430,6 +20464,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
20430
20464
  let painted = false;
20431
20465
  const paint = (bars, final) => {
20432
20466
  if (this.generation !== gen || !final && bars.length === 0) return;
20467
+ if (!painted && !final && bars.length < Math.min(requested, FIRST_PAINT_BARS)) return;
20433
20468
  this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
20434
20469
  if (!painted && bars.length > 0) {
20435
20470
  painted = true;
@@ -1,4 +1,4 @@
1
- import { overlayScrollbarCss, injectStyles, isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha, iconAt, iconEl, BULLISH, BEARISH, WARNING, NEUTRAL } from './chunk-KG4YT3TI.js';
1
+ import { overlayScrollbarCss, injectStyles, isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha, iconAt, iconEl, BULLISH, BEARISH, WARNING, NEUTRAL } from './chunk-6DPAYEGO.js';
2
2
  import { VanillaMachine, normalizeProps, spreadProps } from '@zag-js/vanilla';
3
3
  export { normalizeProps, spreadProps } from '@zag-js/vanilla';
4
4
  import * as menu from '@zag-js/menu';
@@ -472,6 +472,12 @@ ${STATIC_DECLS}
472
472
  .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
473
473
  /* Text ENTRY is the one exception: selection is part of editing. */
474
474
  .vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
475
+ /* iOS Safari zooms the page when a focused field is under 16px, and often
476
+ keeps that zoom after the field blurs or a dialog closes. The shell's
477
+ mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
478
+ chart on a wide desktop still gets the rule when it is in the phone layout.
479
+ Scoped to the kit root so a host page's own [data-layout] is never touched. */
480
+ .vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
475
481
  .vela-icon { display: inline-flex; align-items: center; flex: none; }
476
482
  .vela-icon svg { display: block; }
477
483
  `;
@@ -1,5 +1,5 @@
1
- import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-MHY7MVXH.js';
2
- import { injectStyles } from './chunk-KG4YT3TI.js';
1
+ import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-MWDNPGU3.js';
2
+ import { injectStyles } from './chunk-6DPAYEGO.js';
3
3
  import * as tooltip from '@zag-js/tooltip';
4
4
  import * as dialog from '@zag-js/dialog';
5
5
 
@@ -1,5 +1,5 @@
1
- import { u as Millis, ai as InputSchema, ag as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, au as PaneAxis, aH as SeriesSpec, ae as Fill, B as Background, az as PriceLine, a1 as DrawingLine, Z as DrawingBox, a0 as DrawingLabel, a5 as DrawingPolyline, a2 as DrawingLinefill, aa as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, w as DrawingTypeKey, v as SnapMode, a3 as DrawingMode, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, s as DataWindowReadout, n as SymbolPickerFn, aW as PaneInfo, ad as DrawingsOption, a7 as DrawingSeriesGateway, D as Drawing, b as VelaOptions, y as AddIndicatorOptions, at as MarketSwitch, as as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-CYS5Wmlx.js';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-DhzstpQb.js';
1
+ import { u as Millis, ai as InputSchema, ag as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, au as PaneAxis, aH as SeriesSpec, ae as Fill, B as Background, az as PriceLine, a1 as DrawingLine, Z as DrawingBox, a0 as DrawingLabel, a5 as DrawingPolyline, a2 as DrawingLinefill, aa as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, w as DrawingTypeKey, v as SnapMode, a3 as DrawingMode, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, s as DataWindowReadout, n as SymbolPickerFn, aW as PaneInfo, ad as DrawingsOption, a7 as DrawingSeriesGateway, D as Drawing, b as VelaOptions, y as AddIndicatorOptions, at as MarketSwitch, as as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-bdSAtp7y.js';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-XLdJB86x.js';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -916,6 +916,12 @@ declare class RendererControl {
916
916
  * doesn't support it (warns). DOM overlays (tables, legend) are not included.
917
917
  */
918
918
  screenshot(): string | null;
919
+ /**
920
+ * Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
921
+ * or null if the renderer has no canvas export. Silent — a host compositing
922
+ * several charts skips a renderer that cannot contribute.
923
+ */
924
+ screenshotCanvas(): HTMLCanvasElement | null;
919
925
  /**
920
926
  * The active renderer's full cosmetic config as a serializable, versioned JSON
921
927
  * document — persist it (templates, saved settings) and feed it back to
@@ -1624,6 +1630,11 @@ interface SidePanelDescriptor {
1624
1630
  resizable?: boolean;
1625
1631
  minWidth?: number;
1626
1632
  maxWidth?: number;
1633
+ /** Float over the chart's right edge instead of docking beside it: the chart keeps its
1634
+ * width and layout, and the panel covers its right-hand part (default false — a docked
1635
+ * column that shrinks the chart). For panels wide enough that a column would crush
1636
+ * the plot. The dock stays exclusive either way. */
1637
+ overlay?: boolean;
1627
1638
  mount(ctx: WidgetContext, body: HTMLElement, header: SidePanelHeader): SidePanelHandle | void;
1628
1639
  }
1629
1640
  /** One panel toggle, as the shell's chrome consumes it (data, never DOM). */
@@ -1,5 +1,5 @@
1
- import { u as Millis, ai as InputSchema, ag as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, au as PaneAxis, aH as SeriesSpec, ae as Fill, B as Background, az as PriceLine, a1 as DrawingLine, Z as DrawingBox, a0 as DrawingLabel, a5 as DrawingPolyline, a2 as DrawingLinefill, aa as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, w as DrawingTypeKey, v as SnapMode, a3 as DrawingMode, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, s as DataWindowReadout, n as SymbolPickerFn, aW as PaneInfo, ad as DrawingsOption, a7 as DrawingSeriesGateway, D as Drawing, b as VelaOptions, y as AddIndicatorOptions, at as MarketSwitch, as as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-CYS5Wmlx.cjs';
2
- import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-DlMtrwqM.cjs';
1
+ import { u as Millis, ai as InputSchema, ag as IndicatorMeta, P as PriceStyle, O as OHLCV, I as InputValue, l as IndicatorModel, k as MoveTarget, au as PaneAxis, aH as SeriesSpec, ae as Fill, B as Background, az as PriceLine, a1 as DrawingLine, Z as DrawingBox, a0 as DrawingLabel, a5 as DrawingPolyline, a2 as DrawingLinefill, aa as DrawingTable, h as IndicatorStatus, a as VisibleRange, c as VelaTheme, S as SerializedDrawing, w as DrawingTypeKey, v as SnapMode, a3 as DrawingMode, e as IChartRenderer, R as RendererCapabilities, o as LegendActionView, p as LegendCalloutView, U as Unsubscribe, C as CrosshairEvent, A as AxisLongPressEvent, s as DataWindowReadout, n as SymbolPickerFn, aW as PaneInfo, ad as DrawingsOption, a7 as DrawingSeriesGateway, D as Drawing, b as VelaOptions, y as AddIndicatorOptions, at as MarketSwitch, as as MarketSnapshot, V as VisibleRangePreset, T as ThemeName } from './options-bdSAtp7y.cjs';
2
+ import { S as SymbolInfo, B as BarRange, M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, b as ProviderCapabilities } from './DataProvider-xH2VBcFT.cjs';
3
3
 
4
4
  /**
5
5
  * A strategy's broker state at ONE bar — the flat summary a host reads while a script
@@ -916,6 +916,12 @@ declare class RendererControl {
916
916
  * doesn't support it (warns). DOM overlays (tables, legend) are not included.
917
917
  */
918
918
  screenshot(): string | null;
919
+ /**
920
+ * Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
921
+ * or null if the renderer has no canvas export. Silent — a host compositing
922
+ * several charts skips a renderer that cannot contribute.
923
+ */
924
+ screenshotCanvas(): HTMLCanvasElement | null;
919
925
  /**
920
926
  * The active renderer's full cosmetic config as a serializable, versioned JSON
921
927
  * document — persist it (templates, saved settings) and feed it back to
@@ -1624,6 +1630,11 @@ interface SidePanelDescriptor {
1624
1630
  resizable?: boolean;
1625
1631
  minWidth?: number;
1626
1632
  maxWidth?: number;
1633
+ /** Float over the chart's right edge instead of docking beside it: the chart keeps its
1634
+ * width and layout, and the panel covers its right-hand part (default false — a docked
1635
+ * column that shrinks the chart). For panels wide enough that a column would crush
1636
+ * the plot. The dock stays exclusive either way. */
1637
+ overlay?: boolean;
1627
1638
  mount(ctx: WidgetContext, body: HTMLElement, header: SidePanelHeader): SidePanelHandle | void;
1628
1639
  }
1629
1640
  /** One panel toggle, as the shell's chrome consumes it (data, never DOM). */