@luxalgo/vela 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/widget.cjs CHANGED
@@ -14014,7 +14014,7 @@ var TOUCH_TAP_SLOP = 14;
14014
14014
  var DOUBLE_TAP_MS = 350;
14015
14015
  var DOUBLE_TAP_SLOP = 30;
14016
14016
  var TIME_SCALE_K = 4e-3;
14017
- var WHEEL_ZOOM_K = 25e-4;
14017
+ var WHEEL_ZOOM_K = 4e-3;
14018
14018
  function wheelZoomAnchor(coords, cursorX, rightEdge) {
14019
14019
  if (rightEdge) return { logical: coords.rightEdgeLogical, x: coords.width };
14020
14020
  return { logical: coords.xToLogical(cursorX), x: cursorX };
@@ -14068,6 +14068,8 @@ var InputController = class {
14068
14068
  // smoothed pointer velocity, px/ms
14069
14069
  this.middleDeleted = false;
14070
14070
  // the last middle press deleted a drawing (suppress autoscroll/paste)
14071
+ this.rightCancelled = false;
14072
+ // the last right press cancelled a placement (suppress the context menu)
14071
14073
  // Last cursor position over the element (NaN once it leaves) — lets a modifier
14072
14074
  // press/release re-shape the drawings preview with the pointer stationary.
14073
14075
  this.cursorX = NaN;
@@ -14092,6 +14094,14 @@ var InputController = class {
14092
14094
  this.onMiddleGuard = (e) => {
14093
14095
  if (e.button === 1 && this.middleDeleted) e.preventDefault();
14094
14096
  };
14097
+ /** A right press that cancelled a placement must not ALSO open the host's chart
14098
+ * context menu — swallow its companion event before it bubbles to the host. */
14099
+ this.onContextGuard = (e) => {
14100
+ if (!this.rightCancelled) return;
14101
+ this.rightCancelled = false;
14102
+ e.preventDefault();
14103
+ e.stopPropagation();
14104
+ };
14095
14105
  /** A modifier press/release with the pointer stationary: re-issue the last cursor
14096
14106
  * position so the placing ghost / drag preview reflects the new state immediately
14097
14107
  * (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
@@ -14108,6 +14118,11 @@ var InputController = class {
14108
14118
  if (this.middleDeleted) e.preventDefault();
14109
14119
  return;
14110
14120
  }
14121
+ if (e.button === 2) {
14122
+ this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
14123
+ if (this.rightCancelled) e.preventDefault();
14124
+ return;
14125
+ }
14111
14126
  if (e.button !== 0) return;
14112
14127
  const { x, y } = this.local(e);
14113
14128
  if (e.pointerType === "touch") {
@@ -14312,6 +14327,7 @@ var InputController = class {
14312
14327
  el.addEventListener("wheel", this.onWheel, { passive: false });
14313
14328
  el.addEventListener("mousedown", this.onMiddleGuard);
14314
14329
  el.addEventListener("auxclick", this.onMiddleGuard);
14330
+ el.addEventListener("contextmenu", this.onContextGuard);
14315
14331
  const win = el.ownerDocument?.defaultView;
14316
14332
  win?.addEventListener("keydown", this.onModifier);
14317
14333
  win?.addEventListener("keyup", this.onModifier);
@@ -14333,6 +14349,7 @@ var InputController = class {
14333
14349
  el.removeEventListener("wheel", this.onWheel);
14334
14350
  el.removeEventListener("mousedown", this.onMiddleGuard);
14335
14351
  el.removeEventListener("auxclick", this.onMiddleGuard);
14352
+ el.removeEventListener("contextmenu", this.onContextGuard);
14336
14353
  this.cancelLongPress();
14337
14354
  this.touches.clear();
14338
14355
  this.el = null;
@@ -16005,8 +16022,11 @@ var ChromeRenderer = class {
16005
16022
  }
16006
16023
  ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
16007
16024
  ctx.textBaseline = "middle";
16008
- if (coords.barCount === 0) return;
16009
16025
  const panes = scene.orderedPanes();
16026
+ if (coords.barCount === 0) {
16027
+ this.drawPaneSeparators(ctx, scene, theme, fullW, panes);
16028
+ return;
16029
+ }
16010
16030
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
16011
16031
  for (const pane of panes) {
16012
16032
  if (pane.collapsed) continue;
@@ -22168,6 +22188,16 @@ var UserDrawingController = class {
22168
22188
  if (this.eraserMode) return "pointer";
22169
22189
  return this.interaction.cursorAt(x, y);
22170
22190
  }
22191
+ /** Right-click while placing: cancel the in-progress drawing and revert to the
22192
+ * pointer — the gesture is an explicit escape, so it disarms even in
22193
+ * stay-in-drawing-mode (where Escape would leave the tool armed). Returns whether
22194
+ * the press was consumed; false lets the host's context menu open normally. */
22195
+ cancelPlacement() {
22196
+ if (!this.interaction.isPlacing()) return false;
22197
+ this.interaction.cancel();
22198
+ if (this.activeTool != null) this.emit({ kind: "arm", type: null });
22199
+ return true;
22200
+ }
22171
22201
  /** Double-click over a drawing → suppress the chart's view reset (single-click already
22172
22202
  * opens settings). Returns true only when a drawing is under the cursor. */
22173
22203
  dblClick(x, y) {
@@ -22652,6 +22682,20 @@ var VolumeRenderer = class {
22652
22682
  };
22653
22683
 
22654
22684
  // src/renderers/native/layers.ts
22685
+ function foldBaseModulation(acc, next) {
22686
+ if (next == null) return acc;
22687
+ if (acc == null) return { ...next };
22688
+ return {
22689
+ candleBodyScale: minOpt(acc.candleBodyScale, next.candleBodyScale),
22690
+ candleBodyAlpha: minOpt(acc.candleBodyAlpha, next.candleBodyAlpha),
22691
+ gridAlpha: minOpt(acc.gridAlpha, next.gridAlpha)
22692
+ };
22693
+ }
22694
+ function minOpt(a, b) {
22695
+ if (a == null) return b;
22696
+ if (b == null) return a;
22697
+ return Math.min(a, b);
22698
+ }
22655
22699
  var registry3 = /* @__PURE__ */ new Map();
22656
22700
  function rendererLayers() {
22657
22701
  return [...registry3.values()];
@@ -23289,7 +23333,7 @@ var NativeRenderer = class {
23289
23333
  this.scrollTargetRO = null;
23290
23334
  // eased rightOffset target while gliding back to the latest bars
23291
23335
  // Distance (px) from the plot's bottom edge to the button — tracks the bottom-most EXPANDED
23292
- // pane like the watermark: when the lower sub-panes collapse it rides up into the open pane.
23336
+ // pane: when the lower sub-panes collapse it rides up into the open pane.
23293
23337
  this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM;
23294
23338
  // Distance (px) from the plot's right edge — clears the FULL scale gutter, which widens when a
23295
23339
  // pane carries merged (own-scale) columns; the constant only clears a single-column scale.
@@ -24256,6 +24300,7 @@ var NativeRenderer = class {
24256
24300
  drawingsClaim: (x, y) => this.userDrawings?.claim(x, y) ?? false,
24257
24301
  drawingsMeasureStart: (x, y) => this.userDrawings?.beginMeasureAt(x, y) ?? false,
24258
24302
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
24303
+ drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
24259
24304
  drawingsSnapMode: () => this.snapMode,
24260
24305
  drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
24261
24306
  drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
@@ -24507,6 +24552,8 @@ var NativeRenderer = class {
24507
24552
  this.attributionEl = null;
24508
24553
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
24509
24554
  this.mountContainer?.style.removeProperty("--vela-scale-gutter");
24555
+ this.mountContainer?.style.removeProperty("--vela-price-pane-top");
24556
+ this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
24510
24557
  this.mountContainer = null;
24511
24558
  this.wrapper?.remove();
24512
24559
  }
@@ -25596,19 +25643,18 @@ var NativeRenderer = class {
25596
25643
  theme: this.theme
25597
25644
  });
25598
25645
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
25646
+ let folded = null;
25599
25647
  for (const l of this.extLayers) {
25600
25648
  const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
25601
25649
  l.instance.render(args);
25602
- if (l.def.id === this.scene.priceStyle && l.instance.modulateBase) {
25603
- const mod = l.instance.modulateBase(args);
25604
- if (mod) {
25605
- if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
25606
- if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
25607
- if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
25608
- }
25609
- }
25650
+ folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
25610
25651
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
25611
25652
  }
25653
+ if (folded) {
25654
+ if (folded.candleBodyScale != null) this.backend.candleBodyScale = clamp012(folded.candleBodyScale) || 0.01;
25655
+ if (folded.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(folded.candleBodyAlpha) * this.candleBodyAlpha;
25656
+ if (folded.gridAlpha != null) this.backend.gridAlpha = clamp012(folded.gridAlpha);
25657
+ }
25612
25658
  }
25613
25659
  const li = this.bars.length - 1;
25614
25660
  const liveActual = li >= 0 ? this.bars[li] : void 0;
@@ -26000,6 +26046,7 @@ var NativeRenderer = class {
26000
26046
  this.paneControls?.reposition();
26001
26047
  this.repositionScrollButton();
26002
26048
  this.positionAttribution();
26049
+ this.publishPricePaneBounds();
26003
26050
  return;
26004
26051
  }
26005
26052
  const collapsed = panes.filter((p) => p.collapsed);
@@ -26017,9 +26064,10 @@ var NativeRenderer = class {
26017
26064
  this.paneControls?.reposition();
26018
26065
  this.repositionScrollButton();
26019
26066
  this.positionAttribution();
26067
+ this.publishPricePaneBounds();
26020
26068
  }
26021
- /** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area (like the
26022
- * watermark): with all lower sub-panes collapsed it settles into the lowest open pane. */
26069
+ /** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area:
26070
+ * with all lower sub-panes collapsed it settles into the lowest open pane. */
26023
26071
  repositionScrollButton() {
26024
26072
  const dataHeight = this.coords.height;
26025
26073
  if (dataHeight <= 0) return;
@@ -26063,6 +26111,21 @@ var NativeRenderer = class {
26063
26111
  this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
26064
26112
  this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
26065
26113
  }
26114
+ /** Publish the price pane's vertical insets as `--vela-price-pane-top` /
26115
+ * `--vela-price-pane-bottom` on the mount container, so the symbol watermark
26116
+ * (and any other host overlay) can clip to the price pane instead of spanning
26117
+ * study panes and the time axis. A maximized study pane collapses the box to
26118
+ * zero height — the mark does not appear on a study. */
26119
+ publishPricePaneBounds() {
26120
+ if (!this.mountContainer) return;
26121
+ const plotH = this.coords.height + TIME_AXIS_H;
26122
+ const price = this.scene.panes.get(PRICE_PANE_ID);
26123
+ const top = price ? price.bounds.top : 0;
26124
+ const height = price ? price.bounds.height : this.coords.height;
26125
+ const bottom = Math.max(0, plotH - (top + height));
26126
+ this.mountContainer.style.setProperty("--vela-price-pane-top", `${top}px`);
26127
+ this.mountContainer.style.setProperty("--vela-price-pane-bottom", `${bottom}px`);
26128
+ }
26066
26129
  /** The built-in mark, or the host's own when one is set. */
26067
26130
  buildAttributionEl() {
26068
26131
  return this.attributionHtml !== null ? createCustomMark(document, this.attributionHtml, this.theme.background) : createAttributionMark(document, this.theme.background);
@@ -29038,17 +29101,17 @@ var STYLE_ID10 = "vela-widget-watermark";
29038
29101
  var CSS6 = `
29039
29102
  .vela-watermark {
29040
29103
  position: absolute;
29041
- /* Insets follow the renderer-published gutters (mount container), so the mark
29042
- * centers and fits within the PLOT \u2014 never bleeding into the drawings toolbar
29043
- * on the left or the price scale on the right (visible in small multi-chart
29044
- * cells, where the scale is a large share of the width). */
29045
- top: 0;
29046
- bottom: 0;
29104
+ /* Insets follow the renderer-published gutters AND the price-pane vertical
29105
+ * bounds (mount container), so the mark centers on the PRICE PANE \u2014 never
29106
+ * the study panes below, the drawings toolbar, or the price scale. */
29107
+ top: var(--vela-price-pane-top, 0px);
29108
+ bottom: var(--vela-price-pane-bottom, 0px);
29047
29109
  left: var(--vela-toolbar-gutter, 0px);
29048
29110
  right: var(--vela-scale-gutter, 0px);
29049
29111
  display: flex;
29050
29112
  align-items: center;
29051
29113
  justify-content: center;
29114
+ overflow: hidden;
29052
29115
  pointer-events: none;
29053
29116
  z-index: 1;
29054
29117
  color: var(--vela-fg);
@@ -29103,7 +29166,7 @@ var Watermark = class {
29103
29166
  this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
29104
29167
  this.fit();
29105
29168
  }
29106
- /** Measure the text at the cap and shrink it to the chart's own width. */
29169
+ /** Measure the text at the cap and shrink it to the price pane's width. */
29107
29170
  fit() {
29108
29171
  if (this.el.clientWidth <= 0 || !this.text.textContent) return;
29109
29172
  this.el.style.fontSize = `${MAX_FONT_PX}px`;
package/dist/widget.d.cts CHANGED
@@ -442,7 +442,7 @@ declare class Watermark {
442
442
  setLoading(loading: boolean): void;
443
443
  private sync;
444
444
  update(symbol: string, timeframe: string): void;
445
- /** Measure the text at the cap and shrink it to the chart's own width. */
445
+ /** Measure the text at the cap and shrink it to the price pane's width. */
446
446
  private fit;
447
447
  destroy(): void;
448
448
  }
package/dist/widget.d.ts CHANGED
@@ -442,7 +442,7 @@ declare class Watermark {
442
442
  setLoading(loading: boolean): void;
443
443
  private sync;
444
444
  update(symbol: string, timeframe: string): void;
445
- /** Measure the text at the cap and shrink it to the chart's own width. */
445
+ /** Measure the text at the cap and shrink it to the price pane's width. */
446
446
  private fit;
447
447
  destroy(): void;
448
448
  }
package/dist/widget.js CHANGED
@@ -1,9 +1,9 @@
1
- import { Glider, WidgetHistory, localStorageAdapter, decodeState, prefixedSymbol, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, ChartContextMenu, Toast, Watermark, Statusline, Bottombar, MobileBar, DrawingPill, LayoutModeController, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp, TimeframeDrawer, RANGE_PRESETS, DrawingsDrawer, MoreDrawer, priceStyleIcon, priceStyleLabel, TimezoneDrawer, PriceScaleDrawer, indicatorLedger, sanitizeState, legacyWidgetState, parsePersisted, statuslineInkOf, toolShortcutHints, resolveIndicators, encodeState } from './chunk-Y7TK4ZWK.js';
2
- export { Bottombar, ChartContextMenu, DataWindow, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, Watermark, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, legacyWidgetState, loadPersisted, localStorageAdapter, parsePersisted, parseTimeframe, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, timeframeLabel, timeframeMs } from './chunk-Y7TK4ZWK.js';
3
- import { registerBuiltinChartTypes, resolveTheme, parseSymbol, buildToolbar, priceStyleIds, Vela, normalizeTimezone } from './chunk-RRFGWZNJ.js';
4
- export { TIMEZONES, normalizeTimezone, tzButtonLabel, tzMenuLabel, tzOffset } from './chunk-RRFGWZNJ.js';
5
- import { widgetActions, widgetAttachments, legendActionsProviderFor, resolveEngines } from './chunk-B5TIKQJ5.js';
6
- export { DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, SidePanel, clampPanelWidth, registerSidePanel, registerWidgetAction, registerWidgetAttachment, sidePanels, unregisterSidePanel, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments } from './chunk-B5TIKQJ5.js';
1
+ import { Glider, WidgetHistory, localStorageAdapter, decodeState, prefixedSymbol, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, ChartContextMenu, Toast, Watermark, Statusline, Bottombar, MobileBar, DrawingPill, LayoutModeController, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp, TimeframeDrawer, RANGE_PRESETS, DrawingsDrawer, MoreDrawer, priceStyleIcon, priceStyleLabel, TimezoneDrawer, PriceScaleDrawer, indicatorLedger, sanitizeState, legacyWidgetState, parsePersisted, statuslineInkOf, toolShortcutHints, resolveIndicators, encodeState } from './chunk-EY63XBFZ.js';
2
+ export { Bottombar, ChartContextMenu, DataWindow, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, Watermark, dataWindowSections, decimalsFor, decodeState, encodeState, filterSymbols, fmtChange, fmtPrice, legacyWidgetState, loadPersisted, localStorageAdapter, parsePersisted, parseTimeframe, priceStyleLabel, resolveIndicators, sanitizeState, savePersisted, timeframeLabel, timeframeMs } from './chunk-EY63XBFZ.js';
3
+ import { registerBuiltinChartTypes, resolveTheme, parseSymbol, buildToolbar, priceStyleIds, Vela, normalizeTimezone } from './chunk-L5NTMWPP.js';
4
+ export { TIMEZONES, normalizeTimezone, tzButtonLabel, tzMenuLabel, tzOffset } from './chunk-L5NTMWPP.js';
5
+ import { widgetActions, widgetAttachments, legendActionsProviderFor, resolveEngines } from './chunk-WJKLBYRM.js';
6
+ export { DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, SidePanel, clampPanelWidth, registerSidePanel, registerWidgetAction, registerWidgetAttachment, sidePanels, unregisterSidePanel, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments } from './chunk-WJKLBYRM.js';
7
7
  import { ensureUIHost, KeymapManager, Menu, applyPlotOverlayTokens, isEditableTarget } from './chunk-P556H6EA.js';
8
8
  import './chunk-ATPU5CI6.js';
9
9
  import { injectStyles } from './chunk-LJLZR44Y.js';
@@ -21060,7 +21060,7 @@ var TOUCH_TAP_SLOP = 14;
21060
21060
  var DOUBLE_TAP_MS = 350;
21061
21061
  var DOUBLE_TAP_SLOP = 30;
21062
21062
  var TIME_SCALE_K = 4e-3;
21063
- var WHEEL_ZOOM_K = 25e-4;
21063
+ var WHEEL_ZOOM_K = 4e-3;
21064
21064
  function wheelZoomAnchor(coords, cursorX, rightEdge) {
21065
21065
  if (rightEdge) return { logical: coords.rightEdgeLogical, x: coords.width };
21066
21066
  return { logical: coords.xToLogical(cursorX), x: cursorX };
@@ -21114,6 +21114,8 @@ var InputController = class {
21114
21114
  // smoothed pointer velocity, px/ms
21115
21115
  this.middleDeleted = false;
21116
21116
  // the last middle press deleted a drawing (suppress autoscroll/paste)
21117
+ this.rightCancelled = false;
21118
+ // the last right press cancelled a placement (suppress the context menu)
21117
21119
  // Last cursor position over the element (NaN once it leaves) — lets a modifier
21118
21120
  // press/release re-shape the drawings preview with the pointer stationary.
21119
21121
  this.cursorX = NaN;
@@ -21138,6 +21140,14 @@ var InputController = class {
21138
21140
  this.onMiddleGuard = (e) => {
21139
21141
  if (e.button === 1 && this.middleDeleted) e.preventDefault();
21140
21142
  };
21143
+ /** A right press that cancelled a placement must not ALSO open the host's chart
21144
+ * context menu — swallow its companion event before it bubbles to the host. */
21145
+ this.onContextGuard = (e) => {
21146
+ if (!this.rightCancelled) return;
21147
+ this.rightCancelled = false;
21148
+ e.preventDefault();
21149
+ e.stopPropagation();
21150
+ };
21141
21151
  /** A modifier press/release with the pointer stationary: re-issue the last cursor
21142
21152
  * position so the placing ghost / drag preview reflects the new state immediately
21143
21153
  * (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
@@ -21154,6 +21164,11 @@ var InputController = class {
21154
21164
  if (this.middleDeleted) e.preventDefault();
21155
21165
  return;
21156
21166
  }
21167
+ if (e.button === 2) {
21168
+ this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
21169
+ if (this.rightCancelled) e.preventDefault();
21170
+ return;
21171
+ }
21157
21172
  if (e.button !== 0) return;
21158
21173
  const { x, y } = this.local(e);
21159
21174
  if (e.pointerType === "touch") {
@@ -21358,6 +21373,7 @@ var InputController = class {
21358
21373
  el.addEventListener("wheel", this.onWheel, { passive: false });
21359
21374
  el.addEventListener("mousedown", this.onMiddleGuard);
21360
21375
  el.addEventListener("auxclick", this.onMiddleGuard);
21376
+ el.addEventListener("contextmenu", this.onContextGuard);
21361
21377
  const win = el.ownerDocument?.defaultView;
21362
21378
  win?.addEventListener("keydown", this.onModifier);
21363
21379
  win?.addEventListener("keyup", this.onModifier);
@@ -21379,6 +21395,7 @@ var InputController = class {
21379
21395
  el.removeEventListener("wheel", this.onWheel);
21380
21396
  el.removeEventListener("mousedown", this.onMiddleGuard);
21381
21397
  el.removeEventListener("auxclick", this.onMiddleGuard);
21398
+ el.removeEventListener("contextmenu", this.onContextGuard);
21382
21399
  this.cancelLongPress();
21383
21400
  this.touches.clear();
21384
21401
  this.el = null;
@@ -23051,8 +23068,11 @@ var ChromeRenderer = class {
23051
23068
  }
23052
23069
  ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
23053
23070
  ctx.textBaseline = "middle";
23054
- if (coords.barCount === 0) return;
23055
23071
  const panes = scene.orderedPanes();
23072
+ if (coords.barCount === 0) {
23073
+ this.drawPaneSeparators(ctx, scene, theme, fullW, panes);
23074
+ return;
23075
+ }
23056
23076
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
23057
23077
  for (const pane of panes) {
23058
23078
  if (pane.collapsed) continue;
@@ -28496,6 +28516,16 @@ var UserDrawingController = class {
28496
28516
  if (this.eraserMode) return "pointer";
28497
28517
  return this.interaction.cursorAt(x, y);
28498
28518
  }
28519
+ /** Right-click while placing: cancel the in-progress drawing and revert to the
28520
+ * pointer — the gesture is an explicit escape, so it disarms even in
28521
+ * stay-in-drawing-mode (where Escape would leave the tool armed). Returns whether
28522
+ * the press was consumed; false lets the host's context menu open normally. */
28523
+ cancelPlacement() {
28524
+ if (!this.interaction.isPlacing()) return false;
28525
+ this.interaction.cancel();
28526
+ if (this.activeTool != null) this.emit({ kind: "arm", type: null });
28527
+ return true;
28528
+ }
28499
28529
  /** Double-click over a drawing → suppress the chart's view reset (single-click already
28500
28530
  * opens settings). Returns true only when a drawing is under the cursor. */
28501
28531
  dblClick(x, y) {
@@ -28980,6 +29010,20 @@ var VolumeRenderer = class {
28980
29010
  };
28981
29011
 
28982
29012
  // src/renderers/native/layers.ts
29013
+ function foldBaseModulation(acc, next) {
29014
+ if (next == null) return acc;
29015
+ if (acc == null) return { ...next };
29016
+ return {
29017
+ candleBodyScale: minOpt(acc.candleBodyScale, next.candleBodyScale),
29018
+ candleBodyAlpha: minOpt(acc.candleBodyAlpha, next.candleBodyAlpha),
29019
+ gridAlpha: minOpt(acc.gridAlpha, next.gridAlpha)
29020
+ };
29021
+ }
29022
+ function minOpt(a, b) {
29023
+ if (a == null) return b;
29024
+ if (b == null) return a;
29025
+ return Math.min(a, b);
29026
+ }
28983
29027
  var registry4 = /* @__PURE__ */ new Map();
28984
29028
  function rendererLayers() {
28985
29029
  return [...registry4.values()];
@@ -29489,7 +29533,7 @@ var NativeRenderer = class {
29489
29533
  this.scrollTargetRO = null;
29490
29534
  // eased rightOffset target while gliding back to the latest bars
29491
29535
  // Distance (px) from the plot's bottom edge to the button — tracks the bottom-most EXPANDED
29492
- // pane like the watermark: when the lower sub-panes collapse it rides up into the open pane.
29536
+ // pane: when the lower sub-panes collapse it rides up into the open pane.
29493
29537
  this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM;
29494
29538
  // Distance (px) from the plot's right edge — clears the FULL scale gutter, which widens when a
29495
29539
  // pane carries merged (own-scale) columns; the constant only clears a single-column scale.
@@ -30456,6 +30500,7 @@ var NativeRenderer = class {
30456
30500
  drawingsClaim: (x, y) => this.userDrawings?.claim(x, y) ?? false,
30457
30501
  drawingsMeasureStart: (x, y) => this.userDrawings?.beginMeasureAt(x, y) ?? false,
30458
30502
  drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
30503
+ drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
30459
30504
  drawingsSnapMode: () => this.snapMode,
30460
30505
  drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
30461
30506
  drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
@@ -30707,6 +30752,8 @@ var NativeRenderer = class {
30707
30752
  this.attributionEl = null;
30708
30753
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
30709
30754
  this.mountContainer?.style.removeProperty("--vela-scale-gutter");
30755
+ this.mountContainer?.style.removeProperty("--vela-price-pane-top");
30756
+ this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
30710
30757
  this.mountContainer = null;
30711
30758
  this.wrapper?.remove();
30712
30759
  }
@@ -31796,19 +31843,18 @@ var NativeRenderer = class {
31796
31843
  theme: this.theme
31797
31844
  });
31798
31845
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
31846
+ let folded = null;
31799
31847
  for (const l of this.extLayers) {
31800
31848
  const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
31801
31849
  l.instance.render(args);
31802
- if (l.def.id === this.scene.priceStyle && l.instance.modulateBase) {
31803
- const mod = l.instance.modulateBase(args);
31804
- if (mod) {
31805
- if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
31806
- if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
31807
- if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
31808
- }
31809
- }
31850
+ folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
31810
31851
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
31811
31852
  }
31853
+ if (folded) {
31854
+ if (folded.candleBodyScale != null) this.backend.candleBodyScale = clamp012(folded.candleBodyScale) || 0.01;
31855
+ if (folded.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(folded.candleBodyAlpha) * this.candleBodyAlpha;
31856
+ if (folded.gridAlpha != null) this.backend.gridAlpha = clamp012(folded.gridAlpha);
31857
+ }
31812
31858
  }
31813
31859
  const li = this.bars.length - 1;
31814
31860
  const liveActual = li >= 0 ? this.bars[li] : void 0;
@@ -32200,6 +32246,7 @@ var NativeRenderer = class {
32200
32246
  this.paneControls?.reposition();
32201
32247
  this.repositionScrollButton();
32202
32248
  this.positionAttribution();
32249
+ this.publishPricePaneBounds();
32203
32250
  return;
32204
32251
  }
32205
32252
  const collapsed = panes.filter((p) => p.collapsed);
@@ -32217,9 +32264,10 @@ var NativeRenderer = class {
32217
32264
  this.paneControls?.reposition();
32218
32265
  this.repositionScrollButton();
32219
32266
  this.positionAttribution();
32267
+ this.publishPricePaneBounds();
32220
32268
  }
32221
- /** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area (like the
32222
- * watermark): with all lower sub-panes collapsed it settles into the lowest open pane. */
32269
+ /** Pin the scroll-to-realtime button above the bottom-most EXPANDED pane's data area:
32270
+ * with all lower sub-panes collapsed it settles into the lowest open pane. */
32223
32271
  repositionScrollButton() {
32224
32272
  const dataHeight = this.coords.height;
32225
32273
  if (dataHeight <= 0) return;
@@ -32263,6 +32311,21 @@ var NativeRenderer = class {
32263
32311
  this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
32264
32312
  this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
32265
32313
  }
32314
+ /** Publish the price pane's vertical insets as `--vela-price-pane-top` /
32315
+ * `--vela-price-pane-bottom` on the mount container, so the symbol watermark
32316
+ * (and any other host overlay) can clip to the price pane instead of spanning
32317
+ * study panes and the time axis. A maximized study pane collapses the box to
32318
+ * zero height — the mark does not appear on a study. */
32319
+ publishPricePaneBounds() {
32320
+ if (!this.mountContainer) return;
32321
+ const plotH = this.coords.height + TIME_AXIS_H;
32322
+ const price = this.scene.panes.get(PRICE_PANE_ID2);
32323
+ const top = price ? price.bounds.top : 0;
32324
+ const height = price ? price.bounds.height : this.coords.height;
32325
+ const bottom = Math.max(0, plotH - (top + height));
32326
+ this.mountContainer.style.setProperty("--vela-price-pane-top", `${top}px`);
32327
+ this.mountContainer.style.setProperty("--vela-price-pane-bottom", `${bottom}px`);
32328
+ }
32266
32329
  /** The built-in mark, or the host's own when one is set. */
32267
32330
  buildAttributionEl() {
32268
32331
  return this.attributionHtml !== null ? createCustomMark(document, this.attributionHtml, this.theme.background) : createAttributionMark(document, this.theme.background);
@@ -33326,17 +33389,17 @@ var STYLE_ID26 = "vela-widget-watermark";
33326
33389
  var CSS22 = `
33327
33390
  .vela-watermark {
33328
33391
  position: absolute;
33329
- /* Insets follow the renderer-published gutters (mount container), so the mark
33330
- * centers and fits within the PLOT \u2014 never bleeding into the drawings toolbar
33331
- * on the left or the price scale on the right (visible in small multi-chart
33332
- * cells, where the scale is a large share of the width). */
33333
- top: 0;
33334
- bottom: 0;
33392
+ /* Insets follow the renderer-published gutters AND the price-pane vertical
33393
+ * bounds (mount container), so the mark centers on the PRICE PANE \u2014 never
33394
+ * the study panes below, the drawings toolbar, or the price scale. */
33395
+ top: var(--vela-price-pane-top, 0px);
33396
+ bottom: var(--vela-price-pane-bottom, 0px);
33335
33397
  left: var(--vela-toolbar-gutter, 0px);
33336
33398
  right: var(--vela-scale-gutter, 0px);
33337
33399
  display: flex;
33338
33400
  align-items: center;
33339
33401
  justify-content: center;
33402
+ overflow: hidden;
33340
33403
  pointer-events: none;
33341
33404
  z-index: 1;
33342
33405
  color: var(--vela-fg);
@@ -33391,7 +33454,7 @@ var Watermark = class {
33391
33454
  this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
33392
33455
  this.fit();
33393
33456
  }
33394
- /** Measure the text at the cap and shrink it to the chart's own width. */
33457
+ /** Measure the text at the cap and shrink it to the price pane's width. */
33395
33458
  fit() {
33396
33459
  if (this.el.clientWidth <= 0 || !this.text.textContent) return;
33397
33460
  this.el.style.fontSize = `${MAX_FONT_PX}px`;
package/dist/workspace.js CHANGED
@@ -1,7 +1,7 @@
1
- import { WidgetHistory, prefixedSymbol, Watermark, Statusline, ChartContextMenu, statuslineInkOf, indicatorLedger, Glider, localStorageAdapter, decodeState, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, Toast, Bottombar, MobileBar, DrawingPill, LayoutModeController, toolShortcutHints, resolveIndicators, sanitizeState, encodeState, TimeframeDrawer, RANGE_PRESETS, DrawingsDrawer, MoreDrawer, priceStyleIcon, priceStyleLabel, TimezoneDrawer, PriceScaleDrawer, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp } from './chunk-Y7TK4ZWK.js';
2
- export { decodeState, encodeState, sanitizeState } from './chunk-Y7TK4ZWK.js';
3
- import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs, priceStyleIds } from './chunk-RRFGWZNJ.js';
4
- import { resolveEngines, legendActionsProviderFor, rendererDefaults, widgetActions, widgetAttachments } from './chunk-B5TIKQJ5.js';
1
+ import { WidgetHistory, prefixedSymbol, Watermark, Statusline, ChartContextMenu, statuslineInkOf, indicatorLedger, Glider, localStorageAdapter, decodeState, SymbolPicker, IndicatorPicker, TimeframeQuick, Topbar, PanelDock, ObjectTree, DataWindow, Toast, Bottombar, MobileBar, DrawingPill, LayoutModeController, toolShortcutHints, resolveIndicators, sanitizeState, encodeState, TimeframeDrawer, RANGE_PRESETS, DrawingsDrawer, MoreDrawer, priceStyleIcon, priceStyleLabel, TimezoneDrawer, PriceScaleDrawer, ZOOM_IN, ZOOM_OUT, PAN_FAST, ShortcutsHelp } from './chunk-EY63XBFZ.js';
2
+ export { decodeState, encodeState, sanitizeState } from './chunk-EY63XBFZ.js';
3
+ import { parseSymbol, Vela, normalizeTimezone, TypedEventBus, MultiProviderFeed, resolveTheme, createCustomMark, createAttributionMark, DrawingToolbar, defaultToolbar, applyAttributionMarkTheme, sharedBarStore, timeframeToMs, priceStyleIds } from './chunk-L5NTMWPP.js';
4
+ import { resolveEngines, legendActionsProviderFor, rendererDefaults, widgetActions, widgetAttachments } from './chunk-WJKLBYRM.js';
5
5
  import { applyPlotOverlayTokens, ensureUIHost, KeymapManager, Menu, isEditableTarget } from './chunk-P556H6EA.js';
6
6
  import './chunk-ATPU5CI6.js';
7
7
  import { registerIcon, svg16, injectStyles } from './chunk-LJLZR44Y.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luxalgo/vela",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Open-source charting library with a native high-performance renderer, drawing tools, pluggable chart types and pluggable scripting engines.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://luxalgo.com/vela",