traderead-charts 1.3.1 → 1.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.3.3 — 2026-09-29
4
+
5
+ - **The view can be dragged past the last bar by half the plot**, not by ten
6
+ bars. On a phone at 2 px a bar ten bars were 20 px: the latest candles
7
+ stayed pinned to the price axis and could not be brought to the middle to
8
+ zoom on them. The limit is now in pixels; wide bars keep the old ten.
9
+ - **Pinch zooms around the fingers**, and the bar under them stays under
10
+ them. It used to zoom from the right edge only.
11
+ - **Pinch measures the real distance between the fingers.** A pinch made
12
+ with one finger above the other has almost no horizontal spread; measuring
13
+ only that made the zoom jump.
14
+ - **A pinch on the chart never zooms the page instead.** iOS ignores
15
+ `user-scalable=no`: the touch handler now claims the gesture, and Safari's
16
+ own `gesturestart` / `gesturechange` are cancelled on the chart.
17
+
18
+ ## 1.3.2 — 2026-09-20
19
+
20
+ - **README install example unpinned from 1.2.0.** The copy-paste snippet on
21
+ the npm page still pointed at an old build, so anyone following it landed
22
+ three releases back, without the measure tool. It now pins 1.3.1. No code
23
+ change; the engine is byte-identical to 1.3.0.
24
+
3
25
  ## 1.3.1 — 2026-09-20
4
26
 
5
27
  - **`homepage` now points at traderead.ai**, not at the GitHub Pages docs
package/README.md CHANGED
@@ -58,7 +58,7 @@ npm install traderead-charts
58
58
  or straight from a CDN, no build step:
59
59
 
60
60
  ```html
61
- <script src="https://cdn.jsdelivr.net/npm/traderead-charts@1.2.0/src/traderead-charts.js"></script>
61
+ <script src="https://cdn.jsdelivr.net/npm/traderead-charts@1.3.1/src/traderead-charts.js"></script>
62
62
  ```
63
63
 
64
64
  ## Quick start
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "traderead-charts",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
4
4
  "description": "Complete financial charting widget — candlesticks, indicators with a pro-style panel, drawing tools, symbol search, themes. Batteries included, zero dependencies, in production on traderead.ai.",
5
5
  "main": "src/traderead-charts.js",
6
6
  "files": [
@@ -758,6 +758,14 @@
758
758
  Chart.prototype.xToIndex = function (x) {
759
759
  return this.rightIndex + this.opt.rightPadBars - (this._plotW() - x) / this.barSpacing;
760
760
  };
761
+ // How far past the last bar the view may be dragged: the pad, or half the
762
+ // plot, whichever is more — in pixels, not bars. Ten bars of slack was 20 px
763
+ // on a phone at 2 px a bar: the latest candles stayed pinned to the price
764
+ // axis and could not be brought to the middle to zoom on them (owner's
765
+ // find, 29 Sep 2026).
766
+ Chart.prototype._maxRight = function () {
767
+ return this.bars.length - 1 + Math.max(this.opt.rightPadBars * 2, (this._plotW() * 0.5) / (this.barSpacing || 1));
768
+ };
761
769
  Chart.prototype._visibleRange = function () {
762
770
  var lo = Math.floor(this.xToIndex(0)), hi = Math.ceil(this.xToIndex(this._plotW()));
763
771
  return [clamp(lo, 0, this.bars.length - 1), clamp(hi, 0, this.bars.length - 1)];
@@ -1938,7 +1946,7 @@
1938
1946
  function step(now) {
1939
1947
  var dt = now - last; last = now;
1940
1948
  self.rightIndex = clamp(self.rightIndex - glide.v * dt / self.barSpacing,
1941
- 0, self.bars.length - 1 + self.opt.rightPadBars * 2);
1949
+ 0, self._maxRight());
1942
1950
  self._paint();
1943
1951
  glide.v *= Math.pow(0.94, dt / 16.7);
1944
1952
  if (Math.abs(glide.v) >= 0.05) glide.raf = requestAnimationFrame(step);
@@ -2003,7 +2011,7 @@
2003
2011
  }
2004
2012
  glide.lastX = nowX; glide.lastT = nowT;
2005
2013
  self.rightIndex = drag.right0 + (drag.x0 - nowX) / self.barSpacing;
2006
- self.rightIndex = clamp(self.rightIndex, 0, self.bars.length - 1 + self.opt.rightPadBars * 2);
2014
+ self.rightIndex = clamp(self.rightIndex, 0, self._maxRight());
2007
2015
  if (drag.vpan) {
2008
2016
  var us = self.panes[0].h * (1 - self.opt.volumeHeightPct * 0.35) || 1;
2009
2017
  self._vOff = drag.off0 + (e.clientY - r.top - drag.y0) / us;
@@ -2152,7 +2160,7 @@
2152
2160
  // way every charting tool treats it. Vertical wheel stays zoom.
2153
2161
  if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) {
2154
2162
  self.rightIndex = clamp(self.rightIndex + e.deltaX / self.barSpacing,
2155
- 0, self.bars.length - 1 + self.opt.rightPadBars * 2);
2163
+ 0, self._maxRight());
2156
2164
  self._paint();
2157
2165
  return;
2158
2166
  }
@@ -2209,8 +2217,12 @@
2209
2217
  if (self._draft) { self.drawings.push(self._draft); self._selected = self._draft.id; self._draft = null; self._persist(); self._paintDrawings(); }
2210
2218
  self._dragDraw = null; self._sepDrag = null; drag = null;
2211
2219
  self._touchLock();
2212
- var dx = e.touches[0].clientX - e.touches[1].clientX;
2213
- pinch = { d0: Math.abs(dx) || 1, spacing0: self.barSpacing };
2220
+ // Zoom around the fingers, on their real distance: a pinch made with
2221
+ // one finger above the other has almost no horizontal spread, and
2222
+ // measuring only that made the zoom jump.
2223
+ var f0 = e.touches[0], f1 = e.touches[1];
2224
+ pinch = { d0: Math.hypot(f0.clientX - f1.clientX, f0.clientY - f1.clientY) || 1, spacing0: self.barSpacing,
2225
+ anchor: self.xToIndex((f0.clientX + f1.clientX) / 2 - r.left) };
2214
2226
  }
2215
2227
  }, { passive: true });
2216
2228
  el.addEventListener("touchmove", function (e) {
@@ -2254,9 +2266,16 @@
2254
2266
  return;
2255
2267
  }
2256
2268
  if (pinch && e.touches.length === 2) {
2257
- var d = Math.abs(e.touches[0].clientX - e.touches[1].clientX) || 1;
2258
- self.barSpacing = clamp(pinch.spacing0 * d / pinch.d0, self.opt.minBarSpacing, self.opt.maxBarSpacing);
2269
+ var g0 = e.touches[0], g1 = e.touches[1];
2270
+ var pd = Math.hypot(g0.clientX - g1.clientX, g0.clientY - g1.clientY) || 1;
2271
+ var pmx = (g0.clientX + g1.clientX) / 2 - r.left;
2272
+ self.barSpacing = clamp(pinch.spacing0 * pd / pinch.d0, self.opt.minBarSpacing, self.opt.maxBarSpacing);
2273
+ // the bar that was under the fingers stays under them (and follows
2274
+ // them: two fingers also pan)
2275
+ self.rightIndex = clamp(pinch.anchor - (self.xToIndex(pmx) - self.rightIndex), 0, self._maxRight());
2259
2276
  self._paint();
2277
+ // the page must not zoom instead: iOS ignores user-scalable=no
2278
+ e.preventDefault();
2260
2279
  } else if (drag && e.touches.length === 1) {
2261
2280
  var tNowT = performance.now(), tNowX = e.touches[0].clientX - r.left;
2262
2281
  if (glide.lastT) {
@@ -2265,7 +2284,7 @@
2265
2284
  }
2266
2285
  glide.lastX = tNowX; glide.lastT = tNowT;
2267
2286
  self.rightIndex = drag.right0 + (drag.x0 - tNowX) / self.barSpacing;
2268
- self.rightIndex = clamp(self.rightIndex, 0, self.bars.length - 1 + self.opt.rightPadBars * 2);
2287
+ self.rightIndex = clamp(self.rightIndex, 0, self._maxRight());
2269
2288
  if (drag.vpan) {
2270
2289
  var us2 = self.panes[0].h * (1 - self.opt.volumeHeightPct * 0.35) || 1;
2271
2290
  self._vOff = drag.off0 + (e.touches[0].clientY - r.top - drag.y0) / us2;
@@ -2291,6 +2310,9 @@
2291
2310
  self._touchLock();
2292
2311
  }
2293
2312
  el.addEventListener("touchend", endTouch, { passive: true });
2313
+ // Safari's own pinch events: without this it zooms the whole page.
2314
+ el.addEventListener("gesturestart", function (e) { e.preventDefault(); });
2315
+ el.addEventListener("gesturechange", function (e) { e.preventDefault(); });
2294
2316
  // a browser-fired cancel must COMMIT the in-flight shape, not destroy it
2295
2317
  el.addEventListener("touchcancel", endTouch, { passive: true });
2296
2318
  };