From fc09da705317bfc21d633fe63db7539de4ac130a Mon Sep 17 00:00:00 2001 From: Chris Amow Date: Sun, 16 Aug 2026 23:25:36 -0500 Subject: [PATCH] final pan fix --- docs/implementation.md | 6 ++++ static/app.js | 6 ++-- static/chart.js | 62 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/docs/implementation.md b/docs/implementation.md index 1aaa3be..855ba0b 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -1106,6 +1106,12 @@ that object replaced the whole option, so `pressedMouseMove` and `axisPressedMouseMove` never returned. Desktop vertical click-drag died after any tool use. Restore the full mouse-on, touch-off set. +LWC reads `axisPressedMouseMove.price`, not a boolean. Passing `true` +made `.price` undefined and axis/price drag stayed dead. Use +`{ time: true, price: true }`. Pane vertical drag is also a no-op while +the scale is still auto; a mostly-vertical mouse drag now sets +`manualPriceRange` the same way two-finger pan does. + ### 2026-08-16 — the rest of the same session, none of it subtle Written because they were skipped the first time and then asked for. diff --git a/static/app.js b/static/app.js index 621f8c3..8fb30ee 100644 --- a/static/app.js +++ b/static/app.js @@ -234,7 +234,7 @@ createApp({ const dataUpdatedAt = computed(() => { if (dataReceivedAt.value == null) return '—'; return new Intl.DateTimeFormat(undefined, { - hour: 'numeric', minute: '2-digit', second: '2-digit', + hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', }).format(new Date(dataReceivedAt.value)); }); const build = ref({ commit: '', startedAt: '' }); @@ -244,7 +244,7 @@ createApp({ const at = new Date(build.value.startedAt); if (Number.isNaN(at.getTime())) return commit; return `${commit} ${new Intl.DateTimeFormat(undefined, { - day: 'numeric', month: 'short', hour: 'numeric', minute: '2-digit', + day: 'numeric', month: 'short', hour12: false, hour: '2-digit', minute: '2-digit', }).format(at)}`; }); const quoteChange = computed(() => { @@ -302,7 +302,7 @@ createApp({ function addEvent(message, kind = 'info', url = null, number = null) { events.value = [ - { key: ++eventSequence, at: new Date().toLocaleTimeString(), message, kind, url, number }, + { key: ++eventSequence, at: new Date().toLocaleTimeString(undefined, { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }), message, kind, url, number }, ...events.value, ].slice(0, 20); } diff --git a/static/chart.js b/static/chart.js index c1a5fe3..c885f35 100644 --- a/static/chart.js +++ b/static/chart.js @@ -192,8 +192,8 @@ class ConfluenceChart { handleScale: { mouseWheel: true, pinch: false, - axisPressedMouseMove: true, - axisDoubleClickReset: true, + axisPressedMouseMove: { time: true, price: true }, + axisDoubleClickReset: { time: true, price: true }, }, handleScroll: { mouseWheel: true, @@ -750,7 +750,8 @@ class ConfluenceChart { if (event.touches.length === 1 && this.touchTap) { const touch = event.touches[0]; if (Math.hypot(touch.clientX - this.touchTap.x, touch.clientY - this.touchTap.y) > 10) { - this.touchTap = null; + this.touchTap = null; + this.mousePan = null; } } if (!this.touchTwo || event.touches.length !== 2) return; @@ -1245,8 +1246,8 @@ class ConfluenceChart { handleScale: tool ? false : { mouseWheel: true, pinch: false, - axisPressedMouseMove: true, - axisDoubleClickReset: true, + axisPressedMouseMove: { time: true, price: true }, + axisDoubleClickReset: { time: true, price: true }, }, }); this.hideSnapDot(); @@ -1372,9 +1373,13 @@ class ConfluenceChart { } startToolGesture(event) { - if (!this.armedTool || event.button !== 0) return; + if (event.button !== 0) return; const point = this.eventPoint(event); if (!point) return; + if (!this.armedTool) { + if (this.withinPlot(point)) this.mousePan = { start: point }; + return; + } event.preventDefault(); this.gesture = { start: point, end: point }; this.chartEl.setPointerCapture?.(event.pointerId); @@ -1382,7 +1387,10 @@ class ConfluenceChart { } moveToolGesture(event) { - if (!this.armedTool) return; + if (!this.armedTool) { + this.moveMousePan(event); + return; + } const point = this.eventPoint(event); if (!point) return; // The listener is on window, so it fires over the sidebar and the price @@ -1497,7 +1505,47 @@ class ConfluenceChart { if (a.t != null && b.t != null) this.setLinePreview(a, b, '#65b7cf', 2); } + moveMousePan(event) { + if (!this.mousePan) return; + const point = this.eventPoint(event); + if (!point) return; + const gesture = this.mousePan; + if (gesture.minValue == null) { + const dx = point.x - gesture.start.x; + const dy = point.y - gesture.start.y; + if (Math.hypot(dx, dy) < 8) return; + if (Math.abs(dy) <= Math.abs(dx)) { + this.mousePan = null; + return; + } + const plot = this.plotCanvas()?.getBoundingClientRect(); + if (!plot) { + this.mousePan = null; + return; + } + const margins = this.chart.priceScale('right').options().scaleMargins; + const top = this.candles.coordinateToPrice(plot.height * margins.top); + const bottom = this.candles.coordinateToPrice(plot.height * (1 - margins.bottom)); + if (top == null || bottom == null) { + this.mousePan = null; + return; + } + gesture.minValue = Math.min(top, bottom); + gesture.maxValue = Math.max(top, bottom); + gesture.pricePerPx = (gesture.maxValue - gesture.minValue) + / Math.max(plot.height * (1 - margins.top - margins.bottom), 1); + } + const dPrice = (point.y - gesture.start.y) * gesture.pricePerPx; + this.manualPriceRange = { + minValue: gesture.minValue + dPrice, + maxValue: gesture.maxValue + dPrice, + }; + this.candles.applyOptions({ autoscaleInfoProvider: this.candleAutoscaleProvider }); + this.updateCurrentPrice(this.lastCurrentPrice); + } + finishToolGesture(event) { + this.mousePan = null; if (!this.armedTool || !this.gesture) return; const tool = this.armedTool; const { start, end } = this.gesture;