From 1c94cd98d331b093b5adcd815c0b7d3ec11f6dc3 Mon Sep 17 00:00:00 2001 From: maryia-deriv Date: Wed, 31 Jul 2024 19:15:40 +0300 Subject: [PATCH 1/6] feat: disable onWheel event outside y-axis --- src/store/ChartAdapterStore.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/store/ChartAdapterStore.ts b/src/store/ChartAdapterStore.ts index 18b3ebf28..84182d568 100644 --- a/src/store/ChartAdapterStore.ts +++ b/src/store/ChartAdapterStore.ts @@ -218,13 +218,13 @@ export default class ChartAdapterStore { onWheel = (e: WheelEvent) => { e.preventDefault(); + if (e.offsetX < Number(this.mainStore.chart.chartNode?.offsetWidth) - this.mainStore.chart.yAxisWidth) return; if (e.deltaX === 0 && e.deltaZ === 0) { const value = (100 - Math.min(10, Math.max(-10, e.deltaY))) / 100; this.scale(value); } else { window.flutterChart?.app.scroll(e.deltaX); } - return false; }; @@ -447,16 +447,14 @@ export default class ChartAdapterStore { delta_x = this.getXFromEpoch(barNext.DT!.getTime()) - x; ratio = - (((date as unknown) as number) - bar.DT!.getTime()) / - (barNext.DT!.getTime() - bar.DT!.getTime()); + ((date as unknown as number) - bar.DT!.getTime()) / (barNext.DT!.getTime() - bar.DT!.getTime()); if (price) delta_y = barNext.Close - price; } else if (barPrev && barPrev.Close) { delta_x = x - this.getXFromEpoch(barPrev.DT!.getTime()); ratio = - (((date as unknown) as number) - bar.DT!.getTime()) / - (bar.DT!.getTime() - barPrev.DT!.getTime()); + ((date as unknown as number) - bar.DT!.getTime()) / (bar.DT!.getTime() - barPrev.DT!.getTime()); if (price) delta_y = price - barPrev.Close; } From f7173bcba11d552d18b09e488e99bc607da065c9 Mon Sep 17 00:00:00 2001 From: maryia-deriv Date: Thu, 1 Aug 2024 17:59:36 +0300 Subject: [PATCH 2/6] feat: disable pointer events outside y-axis when a user scroll vertically --- README.md | 1 + src/store/ChartAdapterStore.ts | 18 +++++++++++++++++- src/store/ChartState.ts | 4 ++++ src/types/props.types.ts | 1 + 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1519676e2..5d94d91c7 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ Props marked with `*` are **mandatory**: | isConnectionOpened | Sets the connection status. If set, upon reconnection smartcharts will either patch missing tick data or refresh the chart, depending on granularity; if not set, it is assumed that connection is always opened. Defaults to `undefined`. | | onMessage | SmartCharts will send notifications via this callback, should it be provided. Each notification will have the following structure: `{ text, type, category }`. | | isAnimationEnabled | Determine whether chart animation is enabled or disabled. It may needs to be disabled for better performance. Defaults to `true`. | +| isVerticalScrollEnabled | Determine whether verticall scroll on the chart outside Y-axis is disabled. It may need to be disabled for mobile app version to scroll the page up or down instead of the chart. Defaults to `true`. | | showLastDigitStats | Shows last digits stats. Defaults to `false`. | | scrollToEpoch | Scrolls the chart to the leftmost side and sets the last spot/bar as the first visible spot/bar in the chart. Also, it disables scrolling until the chart reaches the 3/4 of the width of the main pane of the chart. Defaults to `null`. | | diff --git a/src/store/ChartAdapterStore.ts b/src/store/ChartAdapterStore.ts index 84182d568..d98c0f9e0 100644 --- a/src/store/ChartAdapterStore.ts +++ b/src/store/ChartAdapterStore.ts @@ -42,6 +42,7 @@ export default class ChartAdapterStore { }; isOverFlutterCharts = false; + enableVerticalScrollTimer?: ReturnType; constructor(mainStore: MainStore) { makeObservable(this, { @@ -54,6 +55,7 @@ export default class ChartAdapterStore { onQuoteAreaChanged: action.bound, setMsPerPx: action.bound, newChart: action.bound, + enableVerticalScrollTimer: observable, scale: action.bound, toggleDataFitMode: action.bound, onCrosshairMove: action.bound, @@ -200,6 +202,7 @@ export default class ChartAdapterStore { window.flutterChartElement?.removeEventListener('wheel', this.onWheel, { capture: true }); window.flutterChartElement?.removeEventListener('dblclick', this.onDoubleClick, { capture: true }); window.removeEventListener('mousemove', this.onMouseMove, { capture: true }); + clearTimeout(this.enableVerticalScrollTimer); } onChartLoad() { @@ -218,7 +221,20 @@ export default class ChartAdapterStore { onWheel = (e: WheelEvent) => { e.preventDefault(); - if (e.offsetX < Number(this.mainStore.chart.chartNode?.offsetWidth) - this.mainStore.chart.yAxisWidth) return; + const chartNode = this.mainStore.chart.chartNode; + if (chartNode && !this.mainStore.state.isVerticalScrollEnabled) { + const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth; + const isVerticalScroll = e.deltaY && e.deltaX === 0; + if (e.offsetX < nonScrollableAreaWidth && isVerticalScroll) { + if (chartNode.style.pointerEvents !== 'none') { + chartNode.style.pointerEvents = 'none'; + this.enableVerticalScrollTimer = setTimeout(() => { + chartNode.style.pointerEvents = 'auto'; + }, 400); + } + return; + } + } if (e.deltaX === 0 && e.deltaZ === 0) { const value = (100 - Math.min(10, Math.max(-10, e.deltaY))) / 100; this.scale(value); diff --git a/src/store/ChartState.ts b/src/store/ChartState.ts index fccccfbe7..1e8bad72a 100644 --- a/src/store/ChartState.ts +++ b/src/store/ChartState.ts @@ -42,6 +42,7 @@ class ChartState { shouldDrawTicksFromContractInfo? = false; has_updated_settings = false; isAnimationEnabled?: boolean; + isVerticalScrollEnabled? = true; mainStore: MainStore; margin?: number; granularity: TGranularity; @@ -108,6 +109,7 @@ class ChartState { heightFactor: observable, isConnectionOpened: observable, isChartReady: observable, + isVerticalScrollEnabled: observable, chartStatusListener: observable, debouncedStateChange: action.bound, stateChangeListener: observable, @@ -171,6 +173,7 @@ class ChartState { isAnimationEnabled = true, isConnectionOpened, isStaticChart, + isVerticalScrollEnabled = true, granularity, margin = 0, refreshActiveSymbols, @@ -223,6 +226,7 @@ class ChartState { this.isAnimationEnabled = isAnimationEnabled; this.isConnectionOpened = isConnectionOpened; this.isStaticChart = isStaticChart; + this.isVerticalScrollEnabled = isVerticalScrollEnabled; this.margin = margin; this.has_updated_settings = !isDeepEqual(this.settings?.whitespace, settings?.whitespace); this.settings = settings; diff --git a/src/types/props.types.ts b/src/types/props.types.ts index 985139181..1d10756a6 100644 --- a/src/types/props.types.ts +++ b/src/types/props.types.ts @@ -211,6 +211,7 @@ export type TChartProps = { isConnectionOpened?: boolean; onMessage?: (message: TNotification) => void; isAnimationEnabled?: boolean; + isVerticalScrollEnabled?: boolean; showLastDigitStats?: boolean; scrollToEpoch?: number | null; clearChart?: () => void; From 0a90160405910594c90a839e500c944ede093b44 Mon Sep 17 00:00:00 2001 From: maryia-deriv Date: Fri, 2 Aug 2024 18:23:25 +0300 Subject: [PATCH 3/6] test: touch events for disabling pointer events --- src/store/ChartAdapterStore.ts | 84 ++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/src/store/ChartAdapterStore.ts b/src/store/ChartAdapterStore.ts index d98c0f9e0..974fbcde4 100644 --- a/src/store/ChartAdapterStore.ts +++ b/src/store/ChartAdapterStore.ts @@ -40,14 +40,21 @@ export default class ChartAdapterStore { yLocal: 0, bottomIndex: 0, }; + touchMoveValues: { + x?: number; + y?: number; + } = {}; isOverFlutterCharts = false; enableVerticalScrollTimer?: ReturnType; + enableVerticalScrollTouchTimer?: ReturnType; constructor(mainStore: MainStore) { makeObservable(this, { onMount: action.bound, onTickHistory: action.bound, + // onTouchStart: action.bound, + // onTouchEnd: action.bound, onChartLoad: action.bound, onTick: action.bound, loadHistory: action.bound, @@ -56,8 +63,10 @@ export default class ChartAdapterStore { setMsPerPx: action.bound, newChart: action.bound, enableVerticalScrollTimer: observable, + enableVerticalScrollTouchTimer: observable, scale: action.bound, toggleDataFitMode: action.bound, + touchMoveValues: observable, onCrosshairMove: action.bound, isDataFitModeEnabled: observable, isChartLoaded: observable, @@ -191,7 +200,10 @@ export default class ChartAdapterStore { runChartApp(); } - window.flutterChartElement?.addEventListener('wheel', this.onWheel, { capture: true }); + window.flutterChartElement?.addEventListener('wheel', this.onWheel as any, { capture: true }); + // window.flutterChartElement?.addEventListener('touchstart', this.onTouchStart, { capture: true }); + window.flutterChartElement?.addEventListener('touchmove', this.onWheel as any, { capture: true }); + // window.flutterChartElement?.addEventListener('touchend', this.onTouchEnd, { capture: true }); window.flutterChartElement?.addEventListener('dblclick', this.onDoubleClick, { capture: true }); window.addEventListener('mousemove', this.onMouseMove, { capture: true }); } @@ -199,10 +211,14 @@ export default class ChartAdapterStore { onUnmount() { window._flutter.initState.isMounted = false; - window.flutterChartElement?.removeEventListener('wheel', this.onWheel, { capture: true }); + window.flutterChartElement?.removeEventListener('wheel', this.onWheel as any, { capture: true }); + // window.flutterChartElement?.removeEventListener('touchstart', this.onTouchStart, { capture: true }); + window.flutterChartElement?.removeEventListener('touchmove', this.onWheel as any, { capture: true }); + // window.flutterChartElement?.removeEventListener('touchend', this.onTouchEnd, { capture: true }); window.flutterChartElement?.removeEventListener('dblclick', this.onDoubleClick, { capture: true }); window.removeEventListener('mousemove', this.onMouseMove, { capture: true }); clearTimeout(this.enableVerticalScrollTimer); + clearTimeout(this.enableVerticalScrollTouchTimer); } onChartLoad() { @@ -219,17 +235,77 @@ export default class ChartAdapterStore { } } - onWheel = (e: WheelEvent) => { + // onTouchStart(e: TouchEvent) { + // console.log('touchstart'); + // const chartNode = this.mainStore.chart.chartNode; + // if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { + // const { screenX, screenY } = e.touches[0]; + // this.touchMoveValues = { + // x: screenX, + // y: screenY, + // }; + // chartNode.style.pointerEvents = 'none'; + // } + // } + // onTouchEnd(e: TouchEvent) { + // const chartNode = this.mainStore.chart.chartNode; + // if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { + // this.touchMoveValues = {}; + // console.log('touchend'); + // chartNode.style.pointerEvents = 'auto'; + // } + // } + + onWheel = (e: WheelEvent & TouchEvent) => { e.preventDefault(); const chartNode = this.mainStore.chart.chartNode; if (chartNode && !this.mainStore.state.isVerticalScrollEnabled) { const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth; + const { left } = chartNode.getBoundingClientRect(); + + if (e.type === 'touchmove' && e.touches.length === 1) { + // console.log('touchmove 1'); + const { pageX, screenX, screenY } = e.touches[0]; + if (this.touchMoveValues.x && this.touchMoveValues.y) { + const deltaX = Math.abs(screenX - this.touchMoveValues.x); + const deltaY = Math.abs(screenY - this.touchMoveValues.y); + const isVerticalScroll = deltaY > deltaX; + const x = pageX - left; + if (x < nonScrollableAreaWidth && isVerticalScroll) { + chartNode.style.pointerEvents = 'none'; + chartNode.style.touchAction = 'none'; + // console.log('touchmove set to none'); + if (!this.enableVerticalScrollTouchTimer) { + this.enableVerticalScrollTouchTimer = setTimeout(() => { + // console.log('touchmove set to auto'); + chartNode.style.pointerEvents = 'auto'; + chartNode.style.touchAction = 'auto'; + this.enableVerticalScrollTouchTimer = undefined; + }, 700); + } + } else { + // console.log('touchmove set to auto 2'); + chartNode.style.pointerEvents = 'auto'; + chartNode.style.touchAction = 'auto'; + } + } + this.touchMoveValues = { + x: screenX, + y: screenY, + }; + return; + } + const isVerticalScroll = e.deltaY && e.deltaX === 0; - if (e.offsetX < nonScrollableAreaWidth && isVerticalScroll) { + const x = e.pageX - left; + if (x < nonScrollableAreaWidth && isVerticalScroll) { if (chartNode.style.pointerEvents !== 'none') { chartNode.style.pointerEvents = 'none'; + chartNode.style.touchAction = 'none'; + // console.log('wheel', chartNode.style.pointerEvents); this.enableVerticalScrollTimer = setTimeout(() => { chartNode.style.pointerEvents = 'auto'; + chartNode.style.touchAction = 'auto'; }, 400); } return; From bd766652427aa9869bd4c61ec858b4185ffad593 Mon Sep 17 00:00:00 2001 From: maryia-deriv Date: Mon, 5 Aug 2024 01:20:50 +0300 Subject: [PATCH 4/6] feat: touch devices approach to prevent vertical scroll on the chart --- src/store/ChartAdapterStore.ts | 154 ++++++++++++++++++--------------- 1 file changed, 83 insertions(+), 71 deletions(-) diff --git a/src/store/ChartAdapterStore.ts b/src/store/ChartAdapterStore.ts index 974fbcde4..1a967f58d 100644 --- a/src/store/ChartAdapterStore.ts +++ b/src/store/ChartAdapterStore.ts @@ -1,4 +1,4 @@ -import { action, makeObservable, observable, when, runInAction } from 'mobx'; +import { action, makeObservable, observable, when, runInAction, computed } from 'mobx'; import moment from 'moment'; import debounce from 'lodash.debounce'; import { TFlutterChart, TLoadHistoryParams, TQuote } from 'src/types'; @@ -8,6 +8,9 @@ import { safeParse } from 'src/utils'; import { capitalize } from 'src/components/ui/utils'; import MainStore from '.'; +type CustomWheelEventHandler = (e: WheelEvent) => void; +type CustomTouchEventHandler = (e: TouchEvent) => void; + export default class ChartAdapterStore { private mainStore: MainStore; isChartLoaded = false; @@ -40,21 +43,22 @@ export default class ChartAdapterStore { yLocal: 0, bottomIndex: 0, }; - touchMoveValues: { + touchValues: { x?: number; y?: number; + yOnTouchEnd?: number; } = {}; isOverFlutterCharts = false; enableVerticalScrollTimer?: ReturnType; - enableVerticalScrollTouchTimer?: ReturnType; + scrollChartParentOnTouchTimer?: ReturnType; constructor(mainStore: MainStore) { makeObservable(this, { onMount: action.bound, onTickHistory: action.bound, - // onTouchStart: action.bound, - // onTouchEnd: action.bound, + onTouchStart: action.bound, + onTouchEnd: action.bound, onChartLoad: action.bound, onTick: action.bound, loadHistory: action.bound, @@ -63,10 +67,11 @@ export default class ChartAdapterStore { setMsPerPx: action.bound, newChart: action.bound, enableVerticalScrollTimer: observable, - enableVerticalScrollTouchTimer: observable, scale: action.bound, + scrollableChartParent: computed, + scrollChartParentOnTouchTimer: observable, toggleDataFitMode: action.bound, - touchMoveValues: observable, + touchValues: observable, onCrosshairMove: action.bound, isDataFitModeEnabled: observable, isChartLoaded: observable, @@ -200,10 +205,14 @@ export default class ChartAdapterStore { runChartApp(); } - window.flutterChartElement?.addEventListener('wheel', this.onWheel as any, { capture: true }); - // window.flutterChartElement?.addEventListener('touchstart', this.onTouchStart, { capture: true }); - window.flutterChartElement?.addEventListener('touchmove', this.onWheel as any, { capture: true }); - // window.flutterChartElement?.addEventListener('touchend', this.onTouchEnd, { capture: true }); + window.flutterChartElement?.addEventListener('wheel', this.onWheel as CustomWheelEventHandler, { + capture: true, + }); + window.flutterChartElement?.addEventListener('touchstart', this.onTouchStart, { capture: true }); + window.flutterChartElement?.addEventListener('touchmove', this.onWheel as CustomTouchEventHandler, { + capture: true, + }); + window.flutterChartElement?.addEventListener('touchend', this.onTouchEnd, { capture: true }); window.flutterChartElement?.addEventListener('dblclick', this.onDoubleClick, { capture: true }); window.addEventListener('mousemove', this.onMouseMove, { capture: true }); } @@ -211,14 +220,18 @@ export default class ChartAdapterStore { onUnmount() { window._flutter.initState.isMounted = false; - window.flutterChartElement?.removeEventListener('wheel', this.onWheel as any, { capture: true }); - // window.flutterChartElement?.removeEventListener('touchstart', this.onTouchStart, { capture: true }); - window.flutterChartElement?.removeEventListener('touchmove', this.onWheel as any, { capture: true }); - // window.flutterChartElement?.removeEventListener('touchend', this.onTouchEnd, { capture: true }); + window.flutterChartElement?.removeEventListener('wheel', this.onWheel as CustomWheelEventHandler, { + capture: true, + }); + window.flutterChartElement?.removeEventListener('touchstart', this.onTouchStart, { capture: true }); + window.flutterChartElement?.removeEventListener('touchmove', this.onWheel as CustomTouchEventHandler, { + capture: true, + }); + window.flutterChartElement?.removeEventListener('touchend', this.onTouchEnd, { capture: true }); window.flutterChartElement?.removeEventListener('dblclick', this.onDoubleClick, { capture: true }); window.removeEventListener('mousemove', this.onMouseMove, { capture: true }); clearTimeout(this.enableVerticalScrollTimer); - clearTimeout(this.enableVerticalScrollTouchTimer); + clearTimeout(this.scrollChartParentOnTouchTimer); } onChartLoad() { @@ -235,82 +248,81 @@ export default class ChartAdapterStore { } } - // onTouchStart(e: TouchEvent) { - // console.log('touchstart'); - // const chartNode = this.mainStore.chart.chartNode; - // if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { - // const { screenX, screenY } = e.touches[0]; - // this.touchMoveValues = { - // x: screenX, - // y: screenY, - // }; - // chartNode.style.pointerEvents = 'none'; - // } - // } - // onTouchEnd(e: TouchEvent) { - // const chartNode = this.mainStore.chart.chartNode; - // if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { - // this.touchMoveValues = {}; - // console.log('touchend'); - // chartNode.style.pointerEvents = 'auto'; - // } - // } + onTouchStart(e: TouchEvent) { + const chartNode = this.mainStore.chart.chartNode; + if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { + const { screenX, screenY } = e.touches[0]; + this.touchValues = { x: screenX, y: screenY }; + } + } + onTouchEnd(e: TouchEvent) { + const chartNode = this.mainStore.chart.chartNode; + if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { + this.touchValues = { yOnTouchEnd: e.touches[0].screenY }; + } + } + + get scrollableChartParent() { + const chartNode = this.mainStore.chart.chartNode; + if (!chartNode) return undefined; + let parent = chartNode.parentElement; + while (parent) { + const { overflow } = window.getComputedStyle(parent); + if (overflow.split(' ').every(o => o === 'auto' || o === 'scroll')) { + return parent; + } + parent = parent.parentElement; + } + return document.documentElement; + } onWheel = (e: WheelEvent & TouchEvent) => { - e.preventDefault(); + if (e.type === 'wheel') e.preventDefault(); + + // Prevent vertical scroll on the chart if isVerticalScrollEnabled is false: const chartNode = this.mainStore.chart.chartNode; if (chartNode && !this.mainStore.state.isVerticalScrollEnabled) { const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth; const { left } = chartNode.getBoundingClientRect(); - if (e.type === 'touchmove' && e.touches.length === 1) { - // console.log('touchmove 1'); + if (e.type === 'touchmove') { + // Touch devices approach - forcing scroll on a scrollable parent of the chart: + if (e.touches.length > 1 && !this.scrollableChartParent) return; const { pageX, screenX, screenY } = e.touches[0]; - if (this.touchMoveValues.x && this.touchMoveValues.y) { - const deltaX = Math.abs(screenX - this.touchMoveValues.x); - const deltaY = Math.abs(screenY - this.touchMoveValues.y); + if (this.touchValues.x && this.touchValues.y) { + const deltaX = Math.abs(screenX - this.touchValues.x); + const deltaY = Math.abs(screenY - this.touchValues.y); const isVerticalScroll = deltaY > deltaX; const x = pageX - left; - if (x < nonScrollableAreaWidth && isVerticalScroll) { - chartNode.style.pointerEvents = 'none'; - chartNode.style.touchAction = 'none'; - // console.log('touchmove set to none'); - if (!this.enableVerticalScrollTouchTimer) { - this.enableVerticalScrollTouchTimer = setTimeout(() => { - // console.log('touchmove set to auto'); - chartNode.style.pointerEvents = 'auto'; - chartNode.style.touchAction = 'auto'; - this.enableVerticalScrollTouchTimer = undefined; - }, 700); - } - } else { - // console.log('touchmove set to auto 2'); - chartNode.style.pointerEvents = 'auto'; - chartNode.style.touchAction = 'auto'; + if (x < nonScrollableAreaWidth && isVerticalScroll && !this.scrollChartParentOnTouchTimer) { + this.touchValues.yOnTouchEnd = undefined; + this.scrollChartParentOnTouchTimer = setTimeout(() => { + this.scrollableChartParent?.scrollBy({ + top: screenY - Number(this.touchValues.yOnTouchEnd ?? this.touchValues.y), + behavior: 'smooth', + }); + this.scrollChartParentOnTouchTimer = undefined; + }, 300); } } - this.touchMoveValues = { - x: screenX, - y: screenY, - }; + this.touchValues = { x: screenX, y: screenY }; return; } + // Wheel devices approach - disabling pointer events on the chart to make its parent scrollable: const isVerticalScroll = e.deltaY && e.deltaX === 0; const x = e.pageX - left; if (x < nonScrollableAreaWidth && isVerticalScroll) { - if (chartNode.style.pointerEvents !== 'none') { - chartNode.style.pointerEvents = 'none'; - chartNode.style.touchAction = 'none'; - // console.log('wheel', chartNode.style.pointerEvents); - this.enableVerticalScrollTimer = setTimeout(() => { - chartNode.style.pointerEvents = 'auto'; - chartNode.style.touchAction = 'auto'; - }, 400); - } + if (this.enableVerticalScrollTimer) return; + chartNode.style.pointerEvents = 'none'; + this.enableVerticalScrollTimer = setTimeout(() => { + chartNode.style.pointerEvents = 'auto'; + this.enableVerticalScrollTimer = undefined; + }, 300); return; } } + if (e.deltaX === 0 && e.deltaZ === 0) { const value = (100 - Math.min(10, Math.max(-10, e.deltaY))) / 100; this.scale(value); From 86aa3b8a203442c0b3d852fa42375f448324ac65 Mon Sep 17 00:00:00 2001 From: maryia-deriv Date: Mon, 5 Aug 2024 10:43:04 +0300 Subject: [PATCH 5/6] refactor: add onTouch callback for all touch events --- src/store/ChartAdapterStore.ts | 123 +++++++++++++++------------------ 1 file changed, 55 insertions(+), 68 deletions(-) diff --git a/src/store/ChartAdapterStore.ts b/src/store/ChartAdapterStore.ts index 1a967f58d..f21a2731e 100644 --- a/src/store/ChartAdapterStore.ts +++ b/src/store/ChartAdapterStore.ts @@ -8,9 +8,6 @@ import { safeParse } from 'src/utils'; import { capitalize } from 'src/components/ui/utils'; import MainStore from '.'; -type CustomWheelEventHandler = (e: WheelEvent) => void; -type CustomTouchEventHandler = (e: TouchEvent) => void; - export default class ChartAdapterStore { private mainStore: MainStore; isChartLoaded = false; @@ -57,8 +54,7 @@ export default class ChartAdapterStore { makeObservable(this, { onMount: action.bound, onTickHistory: action.bound, - onTouchStart: action.bound, - onTouchEnd: action.bound, + onTouch: action.bound, onChartLoad: action.bound, onTick: action.bound, loadHistory: action.bound, @@ -205,14 +201,10 @@ export default class ChartAdapterStore { runChartApp(); } - window.flutterChartElement?.addEventListener('wheel', this.onWheel as CustomWheelEventHandler, { - capture: true, - }); - window.flutterChartElement?.addEventListener('touchstart', this.onTouchStart, { capture: true }); - window.flutterChartElement?.addEventListener('touchmove', this.onWheel as CustomTouchEventHandler, { - capture: true, - }); - window.flutterChartElement?.addEventListener('touchend', this.onTouchEnd, { capture: true }); + window.flutterChartElement?.addEventListener('wheel', this.onWheel, { capture: true }); + window.flutterChartElement?.addEventListener('touchstart', this.onTouch, { capture: true }); + window.flutterChartElement?.addEventListener('touchmove', this.onTouch, { capture: true }); + window.flutterChartElement?.addEventListener('touchend', this.onTouch, { capture: true }); window.flutterChartElement?.addEventListener('dblclick', this.onDoubleClick, { capture: true }); window.addEventListener('mousemove', this.onMouseMove, { capture: true }); } @@ -220,14 +212,10 @@ export default class ChartAdapterStore { onUnmount() { window._flutter.initState.isMounted = false; - window.flutterChartElement?.removeEventListener('wheel', this.onWheel as CustomWheelEventHandler, { - capture: true, - }); - window.flutterChartElement?.removeEventListener('touchstart', this.onTouchStart, { capture: true }); - window.flutterChartElement?.removeEventListener('touchmove', this.onWheel as CustomTouchEventHandler, { - capture: true, - }); - window.flutterChartElement?.removeEventListener('touchend', this.onTouchEnd, { capture: true }); + window.flutterChartElement?.removeEventListener('wheel', this.onWheel, { capture: true }); + window.flutterChartElement?.removeEventListener('touchstart', this.onTouch, { capture: true }); + window.flutterChartElement?.removeEventListener('touchmove', this.onTouch, { capture: true }); + window.flutterChartElement?.removeEventListener('touchend', this.onTouch, { capture: true }); window.flutterChartElement?.removeEventListener('dblclick', this.onDoubleClick, { capture: true }); window.removeEventListener('mousemove', this.onMouseMove, { capture: true }); clearTimeout(this.enableVerticalScrollTimer); @@ -248,47 +236,22 @@ export default class ChartAdapterStore { } } - onTouchStart(e: TouchEvent) { - const chartNode = this.mainStore.chart.chartNode; - if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { - const { screenX, screenY } = e.touches[0]; - this.touchValues = { x: screenX, y: screenY }; - } - } - onTouchEnd(e: TouchEvent) { - const chartNode = this.mainStore.chart.chartNode; - if (chartNode && !this.mainStore.state.isVerticalScrollEnabled && e.touches.length === 1) { - this.touchValues = { yOnTouchEnd: e.touches[0].screenY }; - } - } - - get scrollableChartParent() { - const chartNode = this.mainStore.chart.chartNode; - if (!chartNode) return undefined; - let parent = chartNode.parentElement; - while (parent) { - const { overflow } = window.getComputedStyle(parent); - if (overflow.split(' ').every(o => o === 'auto' || o === 'scroll')) { - return parent; - } - parent = parent.parentElement; - } - return document.documentElement; - } - - onWheel = (e: WheelEvent & TouchEvent) => { - if (e.type === 'wheel') e.preventDefault(); - - // Prevent vertical scroll on the chart if isVerticalScrollEnabled is false: + onTouch(e: TouchEvent) { const chartNode = this.mainStore.chart.chartNode; - if (chartNode && !this.mainStore.state.isVerticalScrollEnabled) { - const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth; - const { left } = chartNode.getBoundingClientRect(); + // Prevent vertical scroll on the chart for touch devices by forcing scroll on a scrollable parent of the chart: + if ( + chartNode && + this.scrollableChartParent && + !this.mainStore.state.isVerticalScrollEnabled && + e.touches.length === 1 + ) { + const { pageX, screenX, screenY } = e.touches[0]; + if (['touchstart', 'touchend'].includes(e.type)) { + this.touchValues = e.type === 'touchstart' ? { x: screenX, y: screenY } : { yOnTouchEnd: screenY }; + } else if (e.type === 'touchmove') { + const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth; + const { left } = chartNode.getBoundingClientRect(); - if (e.type === 'touchmove') { - // Touch devices approach - forcing scroll on a scrollable parent of the chart: - if (e.touches.length > 1 && !this.scrollableChartParent) return; - const { pageX, screenX, screenY } = e.touches[0]; if (this.touchValues.x && this.touchValues.y) { const deltaX = Math.abs(screenX - this.touchValues.x); const deltaY = Math.abs(screenY - this.touchValues.y); @@ -306,10 +269,18 @@ export default class ChartAdapterStore { } } this.touchValues = { x: screenX, y: screenY }; - return; } + } + } - // Wheel devices approach - disabling pointer events on the chart to make its parent scrollable: + onWheel = (e: WheelEvent) => { + e.preventDefault(); + + // Prevent vertical scroll on the chart on wheel devices by disabling pointer events to make chart parent scrollable: + const chartNode = this.mainStore.chart.chartNode; + if (chartNode && !this.mainStore.state.isVerticalScrollEnabled) { + const nonScrollableAreaWidth = chartNode.offsetWidth - this.mainStore.chart.yAxisWidth; + const { left } = chartNode.getBoundingClientRect(); const isVerticalScroll = e.deltaY && e.deltaX === 0; const x = e.pageX - left; if (x < nonScrollableAreaWidth && isVerticalScroll) { @@ -543,22 +514,24 @@ export default class ChartAdapterStore { let delta_x, delta_y, ratio; // Here we interpolate the pixel distance between two adjacent ticks. - if (bar && bar.DT! < date) { + if (bar && (bar.DT as Date) < date) { const barNext = this.mainStore.chart.feed?.quotes[tickIdx + 1]; const barPrev = tickIdx > 0 ? this.mainStore.chart.feed?.quotes[tickIdx - 1] : null; - if (barNext && barNext.Close && barNext.DT! > date) { - delta_x = this.getXFromEpoch(barNext.DT!.getTime()) - x; + if (barNext && barNext.Close && (barNext.DT as Date) > date) { + delta_x = this.getXFromEpoch((barNext.DT as Date).getTime()) - x; ratio = - ((date as unknown as number) - bar.DT!.getTime()) / (barNext.DT!.getTime() - bar.DT!.getTime()); + ((date as unknown as number) - (bar.DT as Date).getTime()) / + ((barNext.DT as Date).getTime() - (bar.DT as Date).getTime()); if (price) delta_y = barNext.Close - price; } else if (barPrev && barPrev.Close) { - delta_x = x - this.getXFromEpoch(barPrev.DT!.getTime()); + delta_x = x - this.getXFromEpoch((barPrev.DT as Date).getTime()); ratio = - ((date as unknown as number) - bar.DT!.getTime()) / (bar.DT!.getTime() - barPrev.DT!.getTime()); + ((date as unknown as number) - (bar.DT as Date).getTime()) / + ((bar.DT as Date).getTime() - (barPrev.DT as Date).getTime()); if (price) delta_y = price - barPrev.Close; } @@ -601,4 +574,18 @@ export default class ChartAdapterStore { getQuoteFromY(y: number) { return this.flutterChart?.app.getQuoteFromY(y) ?? 0; } + + get scrollableChartParent() { + const chartNode = this.mainStore.chart.chartNode; + if (!chartNode) return undefined; + let parent = chartNode.parentElement; + while (parent) { + const { overflow } = window.getComputedStyle(parent); + if (overflow.split(' ').every(o => o === 'auto' || o === 'scroll')) { + return parent; + } + parent = parent.parentElement; + } + return document.documentElement; + } } From ffa0e62d8eb259f98bb01adbd71b405d7edd87f8 Mon Sep 17 00:00:00 2001 From: Maryia <103177211+maryia-deriv@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:53:00 +0300 Subject: [PATCH 6/6] refactor: remove extra spaces from readme Co-authored-by: Akmal Djumakhodjaev --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d94d91c7..a6e94566e 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Props marked with `*` are **mandatory**: | isConnectionOpened | Sets the connection status. If set, upon reconnection smartcharts will either patch missing tick data or refresh the chart, depending on granularity; if not set, it is assumed that connection is always opened. Defaults to `undefined`. | | onMessage | SmartCharts will send notifications via this callback, should it be provided. Each notification will have the following structure: `{ text, type, category }`. | | isAnimationEnabled | Determine whether chart animation is enabled or disabled. It may needs to be disabled for better performance. Defaults to `true`. | -| isVerticalScrollEnabled | Determine whether verticall scroll on the chart outside Y-axis is disabled. It may need to be disabled for mobile app version to scroll the page up or down instead of the chart. Defaults to `true`. | +| isVerticalScrollEnabled | Determine whether verticall scroll on the chart outside Y-axis is disabled. It may need to be disabled for mobile app version to scroll the page up or down instead of the chart. Defaults to `true`. | | showLastDigitStats | Shows last digits stats. Defaults to `false`. | | scrollToEpoch | Scrolls the chart to the leftmost side and sets the last spot/bar as the first visible spot/bar in the chart. Also, it disables scrolling until the chart reaches the 3/4 of the width of the main pane of the chart. Defaults to `null`. | |