@rioaxyz/sonrchart 0.2.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/README.md CHANGED
@@ -43,8 +43,10 @@ bars come from.
43
43
  drawing tools + snapshot; add your own buttons via its children slot.
44
44
  - **Volume trend readout.** Optional header row classifying the last N bars'
45
45
  volume as Increasing / Decreasing / Mixed (lookback editable live in the
46
- settings dialog, tracks the crosshair) — `volumeTrend` on `ChartHeader` /
47
- `SonrChartShell`, or subscribe via `subscribeVolumeTrend` on the chart handle.
46
+ settings dialog, tracks the crosshair). Toggle it from the indicators menu
47
+ ("VOLTREND"), seed it on with `volumeTrend` on `ChartHeader` /
48
+ `SonrChartShell`, drive it via `setVolumeTrendEnabled` on the chart handle,
49
+ or subscribe to the raw trend via `subscribeVolumeTrend`.
48
50
  - **Fast & dependency-light.** The only runtime dependency is KLineChart;
49
51
  React is a peer. The per-frame hot paths are tuned (see
50
52
  [PERFORMANCE.md](PERFORMANCE.md)).
package/dist/index.cjs CHANGED
@@ -73,6 +73,7 @@ __export(index_exports, {
73
73
  ToolbarButton: () => ToolbarButton,
74
74
  UNIT_MS: () => UNIT_MS,
75
75
  USER_DRAWING_GROUP: () => USER_DRAWING_GROUP,
76
+ VOLUME_TREND_OPTION: () => VOLUME_TREND_OPTION,
76
77
  VWAP_INDICATOR: () => VWAP_INDICATOR,
77
78
  WATERMARK_INDICATOR: () => WATERMARK_INDICATOR,
78
79
  arrowHeadPoints: () => arrowHeadPoints,
@@ -15688,6 +15689,8 @@ var SonrChart = (0, import_react2.forwardRef)(function SonrChart2({
15688
15689
  const trendSubs = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
15689
15690
  const trendBarsRef = (0, import_react2.useRef)(DEFAULT_VOLUME_TREND_BARS);
15690
15691
  const trendBarsSubs = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
15692
+ const trendOnRef = (0, import_react2.useRef)(false);
15693
+ const trendOnSubs = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
15691
15694
  const crosshairActiveRef = (0, import_react2.useRef)(false);
15692
15695
  const headerIdxRef = (0, import_react2.useRef)(-1);
15693
15696
  const emitHeaderAt = (list, idx) => {
@@ -15992,6 +15995,16 @@ var SonrChart = (0, import_react2.forwardRef)(function SonrChart2({
15992
15995
  subscribeVolumeTrendBars: (cb) => {
15993
15996
  trendBarsSubs.current.add(cb);
15994
15997
  return () => trendBarsSubs.current.delete(cb);
15998
+ },
15999
+ getVolumeTrendEnabled: () => trendOnRef.current,
16000
+ setVolumeTrendEnabled: (on) => {
16001
+ if (on === trendOnRef.current) return;
16002
+ trendOnRef.current = on;
16003
+ trendOnSubs.current.forEach((cb) => cb(on));
16004
+ },
16005
+ subscribeVolumeTrendEnabled: (cb) => {
16006
+ trendOnSubs.current.add(cb);
16007
+ return () => trendOnSubs.current.delete(cb);
15995
16008
  }
15996
16009
  },
15997
16010
  // paneHeights/priceScale captured by closure; recreated per render is fine.
@@ -16744,6 +16757,7 @@ function mergeLabels(over) {
16744
16757
 
16745
16758
  // src/IndicatorMenu.tsx
16746
16759
  var import_jsx_runtime2 = require("react/jsx-runtime");
16760
+ var VOLUME_TREND_OPTION = "VOLTREND";
16747
16761
  var DEFAULT_INDICATOR_OPTIONS = [
16748
16762
  // Price-pane overlays.
16749
16763
  { name: "MA", label: "Moving Average", pane: "main" },
@@ -16756,6 +16770,7 @@ var DEFAULT_INDICATOR_OPTIONS = [
16756
16770
  { name: "VWAP", label: "Volume Weighted Avg Price", pane: "main" },
16757
16771
  // Sub-pane oscillators & volume studies.
16758
16772
  { name: "VOL", label: "Volume", pane: "new" },
16773
+ { name: VOLUME_TREND_OPTION, label: "Volume Trend (header row)" },
16759
16774
  { name: "MACD", label: "MACD", pane: "new" },
16760
16775
  { name: "RSI", label: "Relative Strength Index", pane: "new" },
16761
16776
  { name: "KDJ", label: "KDJ Stochastic", pane: "new" },
@@ -16786,12 +16801,27 @@ function IndicatorMenu({
16786
16801
  const [active, setActive] = (0, import_react4.useState)([]);
16787
16802
  const { open, toggle: toggleOpen, pos, triggerRef, panelRef } = useDropdown();
16788
16803
  (0, import_react4.useEffect)(() => {
16789
- const t = setTimeout(() => setActive(chartRef.current?.listIndicators() ?? []), 0);
16790
- return () => clearTimeout(t);
16804
+ let unsubOn;
16805
+ const t = setTimeout(() => {
16806
+ const h = chartRef.current;
16807
+ const base = h?.listIndicators() ?? [];
16808
+ setActive(h?.getVolumeTrendEnabled?.() ? [...base, VOLUME_TREND_OPTION] : base);
16809
+ unsubOn = h?.subscribeVolumeTrendEnabled?.(
16810
+ (on) => setActive((a) => on ? a.includes(VOLUME_TREND_OPTION) ? a : [...a, VOLUME_TREND_OPTION] : a.filter((n) => n !== VOLUME_TREND_OPTION))
16811
+ );
16812
+ }, 0);
16813
+ return () => {
16814
+ clearTimeout(t);
16815
+ unsubOn?.();
16816
+ };
16791
16817
  }, [chartRef]);
16792
16818
  const toggle = (opt) => {
16793
16819
  const handle = chartRef.current;
16794
16820
  if (!handle) return;
16821
+ if (opt.name === VOLUME_TREND_OPTION) {
16822
+ handle.setVolumeTrendEnabled?.(!active.includes(opt.name));
16823
+ return;
16824
+ }
16795
16825
  if (active.includes(opt.name)) {
16796
16826
  handle.removeIndicator(opt.name);
16797
16827
  setActive((a) => a.filter((n) => n !== opt.name));
@@ -19073,20 +19103,23 @@ function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, v
19073
19103
  const L = (0, import_react17.useMemo)(() => mergeLabels(labels), [labels]);
19074
19104
  const [bar, setBar] = (0, import_react17.useState)({ current: null, prev: null });
19075
19105
  const [trend, setTrend] = (0, import_react17.useState)(null);
19076
- const trendOn = volumeTrend2 === true || typeof volumeTrend2 === "number" && volumeTrend2 > 0;
19106
+ const [trendOn, setTrendOn] = (0, import_react17.useState)(false);
19107
+ const seedOn = volumeTrend2 === true || typeof volumeTrend2 === "number" && volumeTrend2 > 0;
19077
19108
  (0, import_react17.useEffect)(() => {
19078
19109
  let cancelled = false;
19079
19110
  let unsub;
19080
- let unsubTrend;
19111
+ let unsubOn;
19081
19112
  const attach = () => {
19082
19113
  if (cancelled) return;
19083
19114
  const h = chartRef.current;
19084
19115
  if (h?.subscribeHeaderBar) {
19085
19116
  unsub = h.subscribeHeaderBar((current, prev) => setBar({ current, prev }));
19086
- if (trendOn) {
19117
+ if (seedOn) {
19087
19118
  if (typeof volumeTrend2 === "number") h.setVolumeTrendBars?.(volumeTrend2);
19088
- unsubTrend = h.subscribeVolumeTrend?.((t) => setTrend(t));
19119
+ h.setVolumeTrendEnabled?.(true);
19089
19120
  }
19121
+ setTrendOn(h.getVolumeTrendEnabled?.() ?? seedOn);
19122
+ unsubOn = h.subscribeVolumeTrendEnabled?.(setTrendOn);
19090
19123
  } else {
19091
19124
  setTimeout(attach, 50);
19092
19125
  }
@@ -19095,9 +19128,17 @@ function ChartHeader({ chartRef, symbol, interval, source, pricePrecision = 2, v
19095
19128
  return () => {
19096
19129
  cancelled = true;
19097
19130
  unsub?.();
19098
- unsubTrend?.();
19131
+ unsubOn?.();
19099
19132
  };
19100
- }, [chartRef, volumeTrend2, trendOn]);
19133
+ }, [chartRef, volumeTrend2, seedOn]);
19134
+ (0, import_react17.useEffect)(() => {
19135
+ if (!trendOn) {
19136
+ setTrend(null);
19137
+ return;
19138
+ }
19139
+ const unsubTrend = chartRef.current?.subscribeVolumeTrend?.((t) => setTrend(t));
19140
+ return () => unsubTrend?.();
19141
+ }, [chartRef, trendOn]);
19101
19142
  const k = bar.current;
19102
19143
  const up = (k?.close ?? 0) >= (k?.open ?? 0);
19103
19144
  const dirClass = up ? "sonr-hd-up" : "sonr-hd-down";
@@ -19786,6 +19827,7 @@ function setCSSCustomProperty(name, value, el) {
19786
19827
  ToolbarButton,
19787
19828
  UNIT_MS,
19788
19829
  USER_DRAWING_GROUP,
19830
+ VOLUME_TREND_OPTION,
19789
19831
  VWAP_INDICATOR,
19790
19832
  WATERMARK_INDICATOR,
19791
19833
  arrowHeadPoints,