@rioaxyz/sonrchart 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -13665,14 +13665,26 @@ var SESSION_INDICATOR = "SONR_SESSION";
13665
13665
  var PATTERN_INDICATOR = "SONR_PATTERNS";
13666
13666
  var WATERMARK_INDICATOR = "SONR_WATERMARK";
13667
13667
  var SERIES_INDICATOR = "SONR_SERIES";
13668
+ var HALT_INDICATOR = "SONR_HALT";
13668
13669
  var VWAP_INDICATOR = "VWAP";
13669
13670
  var Z_ORDER = {
13670
13671
  watermark: -3,
13671
13672
  session: -2,
13672
13673
  series: -1,
13673
13674
  compare: 0,
13674
- pattern: 10
13675
+ pattern: 10,
13676
+ halt: 11
13675
13677
  };
13678
+ var DEFAULT_HALT_BADGE = { label: "Halt", color: "#2196f3", textColor: "#ffffff" };
13679
+ function normalizeHalt(prop) {
13680
+ if (!prop) return null;
13681
+ if (prop === true) return DEFAULT_HALT_BADGE;
13682
+ return {
13683
+ label: prop.label ?? DEFAULT_HALT_BADGE.label,
13684
+ color: prop.color ?? DEFAULT_HALT_BADGE.color,
13685
+ textColor: prop.textColor ?? DEFAULT_HALT_BADGE.textColor
13686
+ };
13687
+ }
13676
13688
  var VWAP_COLOR = "#a970ff";
13677
13689
  function computeVWAP(dataList) {
13678
13690
  let day = Number.NaN;
@@ -13924,6 +13936,41 @@ function registerSonrIndicators() {
13924
13936
  return false;
13925
13937
  }
13926
13938
  });
13939
+ registerIndicator({
13940
+ name: HALT_INDICATOR,
13941
+ shortName: "",
13942
+ series: "price",
13943
+ figures: [],
13944
+ calc: (dataList) => dataList.map(() => ({})),
13945
+ draw: ({ ctx, bounding, indicator }) => {
13946
+ const cfg = indicator.extendData;
13947
+ if (!cfg || !cfg.label) return false;
13948
+ const padX = 8;
13949
+ const h = 20;
13950
+ const inset = 8;
13951
+ ctx.save();
13952
+ ctx.font = '600 11px ui-sans-serif, -apple-system, "Segoe UI", sans-serif';
13953
+ const w = Math.ceil(ctx.measureText(cfg.label).width) + padX * 2;
13954
+ const x = bounding.width - w - inset;
13955
+ const y = (bounding.top ?? 0) + inset;
13956
+ const r = h / 2;
13957
+ ctx.beginPath();
13958
+ ctx.moveTo(x + r, y);
13959
+ ctx.arcTo(x + w, y, x + w, y + h, r);
13960
+ ctx.arcTo(x + w, y + h, x, y + h, r);
13961
+ ctx.arcTo(x, y + h, x, y, r);
13962
+ ctx.arcTo(x, y, x + w, y, r);
13963
+ ctx.closePath();
13964
+ ctx.fillStyle = cfg.color;
13965
+ ctx.fill();
13966
+ ctx.fillStyle = cfg.textColor;
13967
+ ctx.textAlign = "center";
13968
+ ctx.textBaseline = "middle";
13969
+ ctx.fillText(cfg.label, x + w / 2, y + h / 2 + 0.5);
13970
+ ctx.restore();
13971
+ return false;
13972
+ }
13973
+ });
13927
13974
  registerIndicator({
13928
13975
  name: PATTERN_INDICATOR,
13929
13976
  shortName: "",
@@ -15276,6 +15323,25 @@ function formatWithUnit(text2, quote) {
15276
15323
  return out;
15277
15324
  }
15278
15325
 
15326
+ // src/volumeTrend.ts
15327
+ var DEFAULT_VOLUME_TREND_BARS = 3;
15328
+ function volumeTrend(list, endIndex, bars = DEFAULT_VOLUME_TREND_BARS) {
15329
+ const n = Math.max(1, Math.floor(bars));
15330
+ if (endIndex < n || endIndex >= list.length) return null;
15331
+ let up = 0;
15332
+ let down = 0;
15333
+ for (let i = endIndex - n + 1; i <= endIndex; i++) {
15334
+ const v = list[i]?.volume;
15335
+ const pv = list[i - 1]?.volume;
15336
+ if (typeof v !== "number" || typeof pv !== "number") return null;
15337
+ if (v > pv) up++;
15338
+ else if (v < pv) down++;
15339
+ }
15340
+ if (up === n) return "increasing";
15341
+ if (down === n) return "decreasing";
15342
+ return "mixed";
15343
+ }
15344
+
15279
15345
  // src/SonrChart.tsx
15280
15346
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15281
15347
  registerSonrIndicators();
@@ -15285,7 +15351,7 @@ registerShapeOverlays();
15285
15351
  registerExtendedDrawings();
15286
15352
  registerTradeReview();
15287
15353
  registerTextOverlay();
15288
- var INTERNAL_INDICATORS = /* @__PURE__ */ new Set([SESSION_INDICATOR, PATTERN_INDICATOR, COMPARE_INDICATOR, WATERMARK_INDICATOR, SERIES_INDICATOR]);
15354
+ var INTERNAL_INDICATORS = /* @__PURE__ */ new Set([SESSION_INDICATOR, PATTERN_INDICATOR, COMPARE_INDICATOR, WATERMARK_INDICATOR, SERIES_INDICATOR, HALT_INDICATOR]);
15289
15355
  var isUserIndicator = (name) => !INTERNAL_INDICATORS.has(name);
15290
15356
  function magnetMode(m) {
15291
15357
  if (m === true || m === "weak") return "weak_magnet";
@@ -15385,6 +15451,7 @@ var SonrChart = forwardRef(function SonrChart2({
15385
15451
  sessions: sessionsProp,
15386
15452
  watermark: watermarkProp,
15387
15453
  patterns,
15454
+ halted,
15388
15455
  priceLines,
15389
15456
  orderLines,
15390
15457
  priceScale: priceScaleProp,
@@ -15502,14 +15569,24 @@ var SonrChart = forwardRef(function SonrChart2({
15502
15569
  const legendRef = useRef2(legend);
15503
15570
  legendRef.current = legend;
15504
15571
  const headerSubs = useRef2(/* @__PURE__ */ new Set());
15572
+ const trendSubs = useRef2(/* @__PURE__ */ new Set());
15573
+ const trendBarsRef = useRef2(DEFAULT_VOLUME_TREND_BARS);
15574
+ const trendBarsSubs = useRef2(/* @__PURE__ */ new Set());
15575
+ const trendOnRef = useRef2(false);
15576
+ const trendOnSubs = useRef2(/* @__PURE__ */ new Set());
15505
15577
  const crosshairActiveRef = useRef2(false);
15578
+ const headerIdxRef = useRef2(-1);
15579
+ const emitHeaderAt = (list, idx) => {
15580
+ headerIdxRef.current = idx;
15581
+ const current = idx >= 0 ? list[idx] ?? null : null;
15582
+ const prev = idx > 0 ? list[idx - 1] : null;
15583
+ headerSubs.current.forEach((cb) => cb(current, prev));
15584
+ trendSubs.current.forEach((s) => s.cb(volumeTrend(list, idx, s.bars ?? trendBarsRef.current)));
15585
+ };
15506
15586
  const emitLatestHeader = () => {
15507
15587
  if (crosshairActiveRef.current) return;
15508
15588
  const list = chartRef.current?.getDataList() ?? [];
15509
- const n = list.length;
15510
- const current = n ? list[n - 1] : null;
15511
- const prev = n > 1 ? list[n - 2] : null;
15512
- headerSubs.current.forEach((cb) => cb(current, prev));
15589
+ emitHeaderAt(list, list.length - 1);
15513
15590
  };
15514
15591
  const haRef = useRef2(new HaTransformer());
15515
15592
  const [textEdit, setTextEdit] = useState(null);
@@ -15777,6 +15854,40 @@ var SonrChart = forwardRef(function SonrChart2({
15777
15854
  const n = list.length;
15778
15855
  cb(n ? list[n - 1] : null, n > 1 ? list[n - 2] : null);
15779
15856
  return () => headerSubs.current.delete(cb);
15857
+ },
15858
+ // Volume trend (#79) ---------------------------------------------------
15859
+ subscribeVolumeTrend: (cb, bars) => {
15860
+ const sub = { bars: bars === void 0 ? void 0 : Math.max(1, Math.floor(bars)), cb };
15861
+ trendSubs.current.add(sub);
15862
+ const list = chartRef.current?.getDataList() ?? [];
15863
+ cb(volumeTrend(list, list.length - 1, sub.bars ?? trendBarsRef.current));
15864
+ return () => trendSubs.current.delete(sub);
15865
+ },
15866
+ getVolumeTrendBars: () => trendBarsRef.current,
15867
+ setVolumeTrendBars: (bars) => {
15868
+ const n = Math.max(1, Math.floor(bars));
15869
+ if (!Number.isFinite(n) || n === trendBarsRef.current) return;
15870
+ trendBarsRef.current = n;
15871
+ const list = chartRef.current?.getDataList() ?? [];
15872
+ const idx = crosshairActiveRef.current ? headerIdxRef.current : list.length - 1;
15873
+ trendSubs.current.forEach((s) => {
15874
+ if (s.bars === void 0) s.cb(volumeTrend(list, idx, n));
15875
+ });
15876
+ trendBarsSubs.current.forEach((cb) => cb(n));
15877
+ },
15878
+ subscribeVolumeTrendBars: (cb) => {
15879
+ trendBarsSubs.current.add(cb);
15880
+ return () => trendBarsSubs.current.delete(cb);
15881
+ },
15882
+ getVolumeTrendEnabled: () => trendOnRef.current,
15883
+ setVolumeTrendEnabled: (on) => {
15884
+ if (on === trendOnRef.current) return;
15885
+ trendOnRef.current = on;
15886
+ trendOnSubs.current.forEach((cb) => cb(on));
15887
+ },
15888
+ subscribeVolumeTrendEnabled: (cb) => {
15889
+ trendOnSubs.current.add(cb);
15890
+ return () => trendOnSubs.current.delete(cb);
15780
15891
  }
15781
15892
  },
15782
15893
  // paneHeights/priceScale captured by closure; recreated per render is fine.
@@ -15840,16 +15951,29 @@ var SonrChart = forwardRef(function SonrChart2({
15840
15951
  chart.setStyles(mergeStyles(themeStyleObj, styles2));
15841
15952
  chart.subscribeAction("onCrosshairChange", (d) => {
15842
15953
  const cross = d;
15843
- const k = cross?.kLineData;
15844
- if (k && typeof cross?.dataIndex === "number") {
15954
+ const list = chart.getDataList();
15955
+ let k = cross?.kLineData ?? null;
15956
+ let idx = typeof cross?.dataIndex === "number" ? cross.dataIndex : -1;
15957
+ if (!k && typeof cross?.x === "number" && list.length > 0) {
15958
+ const [p] = chart.convertFromPixel([{ x: cross.x }], { paneId: CANDLE_PANE });
15959
+ if (typeof p?.dataIndex === "number") {
15960
+ idx = Math.max(0, Math.min(p.dataIndex, list.length - 1));
15961
+ k = list[idx] ?? null;
15962
+ }
15963
+ }
15964
+ if (k && idx >= 0) {
15845
15965
  crosshairActiveRef.current = true;
15846
- const list = chart.getDataList();
15847
- headerSubs.current.forEach((cb) => cb(k, cross.dataIndex > 0 ? list[cross.dataIndex - 1] : null));
15966
+ emitHeaderAt(list, idx);
15848
15967
  } else {
15849
15968
  crosshairActiveRef.current = false;
15850
15969
  emitLatestHeader();
15851
15970
  }
15852
15971
  });
15972
+ const onPointerLeave = () => {
15973
+ crosshairActiveRef.current = false;
15974
+ emitLatestHeader();
15975
+ };
15976
+ el.addEventListener("mouseleave", onPointerLeave);
15853
15977
  applyCandleStyle(chart, candleStyleRef.current, themeStyleObj, baseTheme);
15854
15978
  applyPriceLabels(chart, priceLabelsRef.current);
15855
15979
  applyFormatter(chart);
@@ -15959,6 +16083,7 @@ var SonrChart = forwardRef(function SonrChart2({
15959
16083
  return () => {
15960
16084
  disposed = true;
15961
16085
  unsub();
16086
+ el.removeEventListener("mouseleave", onPointerLeave);
15962
16087
  dispose(idRef.current);
15963
16088
  chartRef.current = null;
15964
16089
  overlayIds.current = [];
@@ -16030,6 +16155,19 @@ var SonrChart = forwardRef(function SonrChart2({
16030
16155
  chart.removeIndicator({ name: PATTERN_INDICATOR });
16031
16156
  }
16032
16157
  }, [patterns, chartEpoch]);
16158
+ useEffect(() => {
16159
+ const chart = chartRef.current;
16160
+ if (!chart) return;
16161
+ const present = chart.getIndicators({ name: HALT_INDICATOR }).length > 0;
16162
+ const badge = normalizeHalt(halted);
16163
+ if (badge && !present) {
16164
+ chart.createIndicator({ name: HALT_INDICATOR, paneId: CANDLE_PANE, zLevel: Z_ORDER.halt, extendData: badge }, true);
16165
+ } else if (badge && present) {
16166
+ chart.overrideIndicator({ name: HALT_INDICATOR, extendData: badge });
16167
+ } else if (!badge && present) {
16168
+ chart.removeIndicator({ name: HALT_INDICATOR });
16169
+ }
16170
+ }, [halted, chartEpoch]);
16033
16171
  useEffect(() => {
16034
16172
  const chart = chartRef.current;
16035
16173
  if (!chart) return;
@@ -16283,7 +16421,7 @@ var CSS = `
16283
16421
  :where(.sonr-mkt-handle){position:absolute;top:50%;width:12px;height:12px;border-radius:50%;background:#fff;border:2px solid #22a06b;transform:translate(-50%,-50%);}
16284
16422
  :where(.sonr-mkt-ticks){grid-column:2;display:flex;justify-content:space-between;font-size:10px;color:#888;}
16285
16423
  :where(.sonr-mkt-tz){margin-top:12px;font-size:11px;color:#888;text-align:center;}
16286
- :where(.sonr-hd){display:inline-flex;align-items:center;gap:10px;font-family:inherit;font-size:12px;color:#000;white-space:nowrap;}
16424
+ :where(.sonr-hd){display:inline-flex;flex-wrap:wrap;align-items:center;gap:10px;font-family:inherit;font-size:12px;color:#000;white-space:nowrap;}
16287
16425
  :where(.sonr-hd-sym){font-weight:700;color:#000;}
16288
16426
  :where(.sonr-hd-ohlc){display:inline-flex;align-items:center;gap:8px;font-variant-numeric:tabular-nums;}
16289
16427
  :where(.sonr-hd-cell){display:inline-flex;align-items:baseline;gap:3px;}
@@ -16291,6 +16429,8 @@ var CSS = `
16291
16429
  :where(.sonr-hd-v){color:#555;}
16292
16430
  :where(.sonr-hd-up){color:#22a06b;}
16293
16431
  :where(.sonr-hd-down){color:#d23c3c;}
16432
+ :where(.sonr-hd-trend){flex-basis:100%;display:inline-flex;align-items:baseline;gap:4px;margin-top:2px;}
16433
+ :where(.sonr-hd-mixed){color:#c98a1b;}
16294
16434
  :where(.sonr-brand){position:absolute;z-index:3;pointer-events:none;font-family:inherit;font-weight:700;font-size:13px;letter-spacing:0.02em;color:#000;user-select:none;line-height:1;}
16295
16435
  :where(.sonr-brand-img){display:block;}
16296
16436
  :where(.sonr-brand-link){pointer-events:auto;color:inherit;text-decoration:none;}
@@ -16466,6 +16606,10 @@ var DEFAULT_LABELS = {
16466
16606
  zoomIn: "Zoom in",
16467
16607
  fitContent: "Fit all data",
16468
16608
  goToRealtime: "Go to realtime",
16609
+ volumeTrend: "Vol trend",
16610
+ volumeTrendIncreasing: "Increasing",
16611
+ volumeTrendDecreasing: "Decreasing",
16612
+ volumeTrendMixed: "Mixed",
16469
16613
  marketPre: "Pre-market",
16470
16614
  marketOpen: "Open",
16471
16615
  marketAfter: "After-hours",
@@ -16496,6 +16640,7 @@ function mergeLabels(over) {
16496
16640
 
16497
16641
  // src/IndicatorMenu.tsx
16498
16642
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
16643
+ var VOLUME_TREND_OPTION = "VOLTREND";
16499
16644
  var DEFAULT_INDICATOR_OPTIONS = [
16500
16645
  // Price-pane overlays.
16501
16646
  { name: "MA", label: "Moving Average", pane: "main" },
@@ -16508,6 +16653,7 @@ var DEFAULT_INDICATOR_OPTIONS = [
16508
16653
  { name: "VWAP", label: "Volume Weighted Avg Price", pane: "main" },
16509
16654
  // Sub-pane oscillators & volume studies.
16510
16655
  { name: "VOL", label: "Volume", pane: "new" },
16656
+ { name: VOLUME_TREND_OPTION, label: "Volume Trend (header row)" },
16511
16657
  { name: "MACD", label: "MACD", pane: "new" },
16512
16658
  { name: "RSI", label: "Relative Strength Index", pane: "new" },
16513
16659
  { name: "KDJ", label: "KDJ Stochastic", pane: "new" },
@@ -16538,12 +16684,27 @@ function IndicatorMenu({
16538
16684
  const [active, setActive] = useState3([]);
16539
16685
  const { open, toggle: toggleOpen, pos, triggerRef, panelRef } = useDropdown();
16540
16686
  useEffect3(() => {
16541
- const t = setTimeout(() => setActive(chartRef.current?.listIndicators() ?? []), 0);
16542
- return () => clearTimeout(t);
16687
+ let unsubOn;
16688
+ const t = setTimeout(() => {
16689
+ const h = chartRef.current;
16690
+ const base = h?.listIndicators() ?? [];
16691
+ setActive(h?.getVolumeTrendEnabled?.() ? [...base, VOLUME_TREND_OPTION] : base);
16692
+ unsubOn = h?.subscribeVolumeTrendEnabled?.(
16693
+ (on) => setActive((a) => on ? a.includes(VOLUME_TREND_OPTION) ? a : [...a, VOLUME_TREND_OPTION] : a.filter((n) => n !== VOLUME_TREND_OPTION))
16694
+ );
16695
+ }, 0);
16696
+ return () => {
16697
+ clearTimeout(t);
16698
+ unsubOn?.();
16699
+ };
16543
16700
  }, [chartRef]);
16544
16701
  const toggle = (opt) => {
16545
16702
  const handle = chartRef.current;
16546
16703
  if (!handle) return;
16704
+ if (opt.name === VOLUME_TREND_OPTION) {
16705
+ handle.setVolumeTrendEnabled?.(!active.includes(opt.name));
16706
+ return;
16707
+ }
16547
16708
  if (active.includes(opt.name)) {
16548
16709
  handle.removeIndicator(opt.name);
16549
16710
  setActive((a) => a.filter((n) => n !== opt.name));
@@ -17984,6 +18145,7 @@ function SettingsModal({
17984
18145
  const [indicators2, setIndicators] = useState11([]);
17985
18146
  const [indColors, setIndColors] = useState11({});
17986
18147
  const [indParams, setIndParams] = useState11({});
18148
+ const [trendBars, setTrendBars] = useState11(DEFAULT_VOLUME_TREND_BARS);
17987
18149
  const [watermark, setWatermark] = useState11({ symbol: true, interval: true, color: "#e6ebe9", opacity: 0.06 });
17988
18150
  const [brushStyles, setBrushStyles] = useState11({
17989
18151
  brush: { color: "#e6ebe9", size: 2 },
@@ -18018,6 +18180,8 @@ function SettingsModal({
18018
18180
  }
18019
18181
  }
18020
18182
  setIndParams(params);
18183
+ setTrendBars(chartRef.current?.getVolumeTrendBars?.() ?? DEFAULT_VOLUME_TREND_BARS);
18184
+ const unsubTrendBars = chartRef.current?.subscribeVolumeTrendBars?.(setTrendBars);
18021
18185
  const readWm = () => {
18022
18186
  const w = chartRef.current?.getWatermark?.();
18023
18187
  if (w) setWatermark(w);
@@ -18060,6 +18224,7 @@ function SettingsModal({
18060
18224
  document.addEventListener("keydown", onKey);
18061
18225
  return () => {
18062
18226
  document.removeEventListener("keydown", onKey);
18227
+ unsubTrendBars?.();
18063
18228
  unsubWm?.();
18064
18229
  unsubBrush?.();
18065
18230
  unsubText?.();
@@ -18168,6 +18333,25 @@ function SettingsModal({
18168
18333
  ) })
18169
18334
  ] }),
18170
18335
  section === "indicators" && /* @__PURE__ */ jsxs14(Fragment4, { children: [
18336
+ /* @__PURE__ */ jsx15("div", { className: "sonr-set-subhead", children: L.volumeTrend }),
18337
+ /* @__PURE__ */ jsx15(Row, { label: "Bars (lookback)", children: /* @__PURE__ */ jsx15(
18338
+ "input",
18339
+ {
18340
+ type: "number",
18341
+ className: "sonr-set-num",
18342
+ min: "1",
18343
+ max: "100",
18344
+ value: trendBars,
18345
+ onChange: (e) => {
18346
+ const v = e.target.valueAsNumber;
18347
+ if (Number.isNaN(v)) return;
18348
+ const n = Math.max(1, Math.min(100, Math.floor(v)));
18349
+ setTrendBars(n);
18350
+ chartRef.current?.setVolumeTrendBars?.(n);
18351
+ }
18352
+ }
18353
+ ) }),
18354
+ /* @__PURE__ */ jsx15("div", { className: "sonr-set-subhead", children: "Active indicators" }),
18171
18355
  /* @__PURE__ */ jsx15("p", { className: "sonr-set-note", children: "An indicator must be active on the chart to change its settings \u2014 add it from the Indicators menu first." }),
18172
18356
  indicators2.length === 0 && /* @__PURE__ */ jsx15("p", { className: "sonr-set-empty", children: "No indicators active." }),
18173
18357
  indicators2.map((name) => {
@@ -18797,18 +18981,28 @@ var fmtVolume = (v) => {
18797
18981
  if (n >= 1e3) return `${(n / 1e3).toFixed(2)}K`;
18798
18982
  return `${n}`;
18799
18983
  };
18800
- function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, labels, className }) {
18984
+ function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, volumeTrend: volumeTrend2, labels, className }) {
18801
18985
  useEffect16(ensureDefaultStyles, []);
18802
18986
  const L = useMemo12(() => mergeLabels(labels), [labels]);
18803
18987
  const [bar, setBar] = useState14({ current: null, prev: null });
18988
+ const [trend, setTrend] = useState14(null);
18989
+ const [trendOn, setTrendOn] = useState14(false);
18990
+ const seedOn = volumeTrend2 === true || typeof volumeTrend2 === "number" && volumeTrend2 > 0;
18804
18991
  useEffect16(() => {
18805
18992
  let cancelled = false;
18806
18993
  let unsub;
18994
+ let unsubOn;
18807
18995
  const attach = () => {
18808
18996
  if (cancelled) return;
18809
18997
  const h = chartRef.current;
18810
18998
  if (h?.subscribeHeaderBar) {
18811
18999
  unsub = h.subscribeHeaderBar((current, prev) => setBar({ current, prev }));
19000
+ if (seedOn) {
19001
+ if (typeof volumeTrend2 === "number") h.setVolumeTrendBars?.(volumeTrend2);
19002
+ h.setVolumeTrendEnabled?.(true);
19003
+ }
19004
+ setTrendOn(h.getVolumeTrendEnabled?.() ?? seedOn);
19005
+ unsubOn = h.subscribeVolumeTrendEnabled?.(setTrendOn);
18812
19006
  } else {
18813
19007
  setTimeout(attach, 50);
18814
19008
  }
@@ -18817,8 +19011,17 @@ function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, l
18817
19011
  return () => {
18818
19012
  cancelled = true;
18819
19013
  unsub?.();
19014
+ unsubOn?.();
18820
19015
  };
18821
- }, [chartRef]);
19016
+ }, [chartRef, volumeTrend2, seedOn]);
19017
+ useEffect16(() => {
19018
+ if (!trendOn) {
19019
+ setTrend(null);
19020
+ return;
19021
+ }
19022
+ const unsubTrend = chartRef.current?.subscribeVolumeTrend?.((t) => setTrend(t));
19023
+ return () => unsubTrend?.();
19024
+ }, [chartRef, trendOn]);
18822
19025
  const k = bar.current;
18823
19026
  const up = (k?.close ?? 0) >= (k?.open ?? 0);
18824
19027
  const dirClass = up ? "sonr-hd-up" : "sonr-hd-down";
@@ -18859,6 +19062,10 @@ function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, l
18859
19062
  /* @__PURE__ */ jsx18("span", { className: "sonr-hd-k", children: "V" }),
18860
19063
  /* @__PURE__ */ jsx18("span", { className: "sonr-hd-v", children: fmtVolume(k.volume) })
18861
19064
  ] })
19065
+ ] }),
19066
+ trendOn && trend && /* @__PURE__ */ jsxs17("span", { className: "sonr-hd-trend", children: [
19067
+ /* @__PURE__ */ jsx18("span", { className: "sonr-hd-k", children: L.volumeTrend }),
19068
+ /* @__PURE__ */ jsx18("span", { className: trend === "increasing" ? "sonr-hd-up" : trend === "decreasing" ? "sonr-hd-down" : "sonr-hd-mixed", children: trend === "increasing" ? L.volumeTrendIncreasing : trend === "decreasing" ? L.volumeTrendDecreasing : L.volumeTrendMixed })
18862
19069
  ] })
18863
19070
  ] });
18864
19071
  }
@@ -19321,6 +19528,7 @@ var SonrChartShell = forwardRef2(function SonrChartShell2({
19321
19528
  ranges,
19322
19529
  source,
19323
19530
  exchange = source,
19531
+ volumeTrend: volumeTrend2 = true,
19324
19532
  onIntervalChange,
19325
19533
  onSymbolChange,
19326
19534
  brand,
@@ -19394,7 +19602,7 @@ var SonrChartShell = forwardRef2(function SonrChartShell2({
19394
19602
  style: { position: "absolute", inset: 0 }
19395
19603
  }
19396
19604
  ),
19397
- /* @__PURE__ */ jsx25("div", { className: "sonr-tile-header", children: /* @__PURE__ */ jsx25(ChartHeader, { chartRef, symbol, interval, source }) }),
19605
+ /* @__PURE__ */ jsx25("div", { className: "sonr-tile-header", children: /* @__PURE__ */ jsx25(ChartHeader, { chartRef, symbol, interval, source, volumeTrend: volumeTrend2, labels }) }),
19398
19606
  /* @__PURE__ */ jsx25("div", { className: "sonr-tile-nav", children: /* @__PURE__ */ jsx25(NavControlBar, { chartRef, labels }) }),
19399
19607
  brand != null && /* @__PURE__ */ jsx25(BrandMark, { position: brandCorner, opacity: brandOpacity, children: brand })
19400
19608
  ] }),
@@ -19456,6 +19664,7 @@ export {
19456
19664
  ConfirmDialog,
19457
19665
  DEFAULT_CANDLE_STYLES,
19458
19666
  DEFAULT_DRAWING_TOOLS,
19667
+ DEFAULT_HALT_BADGE,
19459
19668
  DEFAULT_INDICATOR_OPTIONS,
19460
19669
  DEFAULT_LABELS,
19461
19670
  DEFAULT_RANGES,
@@ -19463,6 +19672,7 @@ export {
19463
19672
  DEFAULT_SESSION_COLORS,
19464
19673
  DEFAULT_TIMEZONE,
19465
19674
  DEFAULT_TIMEZONES,
19675
+ DEFAULT_VOLUME_TREND_BARS,
19466
19676
  DataWindow,
19467
19677
  DrawingTemplateStore,
19468
19678
  DrawingToolbar,
@@ -19470,6 +19680,7 @@ export {
19470
19680
  EXTENDED_OVERLAYS,
19471
19681
  FIB_EXTENSION_LEVELS,
19472
19682
  GoToDateDialog,
19683
+ HALT_INDICATOR,
19473
19684
  IndicatorMenu,
19474
19685
  MarketStatus,
19475
19686
  NavControlBar,
@@ -19498,6 +19709,7 @@ export {
19498
19709
  ToolbarButton,
19499
19710
  UNIT_MS,
19500
19711
  USER_DRAWING_GROUP,
19712
+ VOLUME_TREND_OPTION,
19501
19713
  VWAP_INDICATOR,
19502
19714
  WATERMARK_INDICATOR,
19503
19715
  arrowHeadPoints,
@@ -19515,6 +19727,7 @@ export {
19515
19727
  isEditableTarget,
19516
19728
  marketStatus,
19517
19729
  mergeLabels,
19730
+ normalizeHalt,
19518
19731
  parseLocalDateTime,
19519
19732
  parseResolution,
19520
19733
  priceRangeLabel,
@@ -19533,7 +19746,8 @@ export {
19533
19746
  themeStyles,
19534
19747
  timezoneLabel,
19535
19748
  toHeikinAshi,
19536
- tradeBoxFigures
19749
+ tradeBoxFigures,
19750
+ volumeTrend
19537
19751
  };
19538
19752
  /*! Bundled license information:
19539
19753