@graphcommerce/framer-scroller 6.2.0-canary.62 → 6.2.0-canary.64

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,13 @@
1
1
  # Change Log
2
2
 
3
+ ## 6.2.0-canary.64
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1998](https://github.com/graphcommerce-org/graphcommerce/pull/1998) [`a788baaeb`](https://github.com/graphcommerce-org/graphcommerce/commit/a788baaeb9646c5a30b55998697d38a6093b6c44) - Added option to useScrollTo to allow/disallow stopping the scroll animation on user scroll interaction ([@bramvanderholst](https://github.com/bramvanderholst))
8
+
9
+ ## 6.2.0-canary.63
10
+
3
11
  ## 6.2.0-canary.62
4
12
 
5
13
  ## 6.2.0-canary.61
@@ -1,5 +1,5 @@
1
1
  import { useConstant, useElementScroll } from '@graphcommerce/framer-utils'
2
- import { MotionValue, motionValue, Point, useMotionValue } from 'framer-motion'
2
+ import { MotionValue, motionValue, Point, useDomEvent, useMotionValue } from 'framer-motion'
3
3
  import { PlaybackControls } from 'popmotion'
4
4
  import React, { useEffect, useRef, useMemo, useCallback } from 'react'
5
5
  import { scrollerContext } from '../context/scrollerContext'
@@ -85,6 +85,7 @@ export function ScrollerProvider(props: ScrollerProviderProps) {
85
85
  )
86
86
 
87
87
  const snap = useMotionValue(_inititalSnap)
88
+ const stopOnInteraction = useMotionValue(_inititalSnap)
88
89
 
89
90
  // Monitor the visbility of all elements and store them for later use.
90
91
  const items = useMotionValue<ItemState[]>([])
@@ -104,16 +105,23 @@ export function ScrollerProvider(props: ScrollerProviderProps) {
104
105
  [stop],
105
106
  )
106
107
 
107
- const disableSnap = useCallback(() => {
108
- if (snap.get() === false) stop()
109
- if (scrollerRef.current) scrollerRef.current.style.scrollSnapType = 'none'
108
+ const disableSnap = useCallback(
109
+ (stopAnimationOnScroll = true) => {
110
+ stopOnInteraction.set(stopAnimationOnScroll)
111
+ if (snap.get() === false) stop()
112
+ if (scrollerRef.current) scrollerRef.current.style.scrollSnapType = 'none'
110
113
 
111
- scroll.animating.set(true)
112
- snap.set(false)
113
- }, [snap, stop, scroll.animating])
114
+ scroll.animating.set(true)
115
+ snap.set(false)
116
+ },
117
+ [stopOnInteraction, snap, stop, scroll.animating],
118
+ )
114
119
 
115
120
  const enableSnap = useCallback(() => {
116
121
  if (snap.get() === true) return
122
+
123
+ stop()
124
+
117
125
  const scroller = scrollerRef.current
118
126
  if (scroller) scroller.style.scrollSnapType = ''
119
127
 
@@ -132,7 +140,20 @@ export function ScrollerProvider(props: ScrollerProviderProps) {
132
140
  })
133
141
  })
134
142
  }
135
- }, [snap, scroll])
143
+ }, [snap, scroll.animating, stop])
144
+
145
+ /**
146
+ * If the scroller is animating and the user now uses their scrollwheel, enable the snapping
147
+ * behavior.
148
+ *
149
+ * If the scroller doesn't have snap enabled and isPanning===false, enable snap
150
+ */
151
+ useDomEvent(scrollerRef as React.RefObject<EventTarget>, 'wheel', () => {
152
+ if (stopOnInteraction.get()) enableSnap()
153
+ })
154
+ useDomEvent(scrollerRef as React.RefObject<EventTarget>, 'touchmove', () => {
155
+ if (stopOnInteraction.get()) stop()
156
+ })
136
157
 
137
158
  useObserveItems(scrollerRef, items)
138
159
 
@@ -4,6 +4,10 @@ import { useCallback, useContext } from 'react'
4
4
  import { distanceAnimationDuration } from '../utils/distanceAnimationDuration'
5
5
  import { useScrollerContext } from './useScrollerContext'
6
6
 
7
+ type Options = {
8
+ stopAnimationOnScroll?: boolean
9
+ }
10
+
7
11
  export function useScrollTo() {
8
12
  const { scrollerRef, register, disableSnap, enableSnap, scroll, getScrollSnapPositions } =
9
13
  useScrollerContext()
@@ -11,8 +15,13 @@ export function useScrollTo() {
11
15
  const duration = (useContext(MotionConfigContext).transition as Tween | undefined)?.duration ?? 0
12
16
 
13
17
  const scrollTo = useCallback(
14
- async (incoming: Point | [number, number], retrigger = 0) => {
18
+ async (
19
+ incoming: Point | [number, number],
20
+ options: Options = { stopAnimationOnScroll: true },
21
+ __retrigger = 0,
22
+ ) => {
15
23
  const ref = scrollerRef.current
24
+ const { stopAnimationOnScroll } = options
16
25
  if (!ref) return
17
26
 
18
27
  let to: Point
@@ -24,20 +33,20 @@ export function useScrollTo() {
24
33
  to = incoming
25
34
  }
26
35
 
27
- if (process.env.NODE_ENV === 'development' && scroll.animating.get() && retrigger === 0) {
36
+ if (process.env.NODE_ENV === 'development' && scroll.animating.get() && __retrigger === 0) {
28
37
  console.warn(
29
38
  `scrollTo triggered while another animation is in progress. This cancels the current animation and creates a new one.`,
30
39
  )
31
40
  }
32
41
 
33
- if (process.env.NODE_ENV === 'development' && retrigger > 5) {
42
+ if (process.env.NODE_ENV === 'development' && __retrigger > 5) {
34
43
  console.error(
35
44
  `scrollTo triggered more than 5 times, is the element resizing constantly? Bailing out.`,
36
45
  )
37
46
  return
38
47
  }
39
48
 
40
- if (process.env.NODE_ENV === 'development' && retrigger > 0) {
49
+ if (process.env.NODE_ENV === 'development' && __retrigger > 0) {
41
50
  console.warn(
42
51
  `scrollTo re-animating to because the final location changed during animation.`,
43
52
  )
@@ -47,7 +56,8 @@ export function useScrollTo() {
47
56
 
48
57
  const xDone = new Promise<void>((onComplete) => {
49
58
  if (ref.scrollLeft !== to.x) {
50
- disableSnap()
59
+ disableSnap(stopAnimationOnScroll)
60
+ if (!stopAnimationOnScroll) ref.style.overflow = 'hidden'
51
61
 
52
62
  stop.push(
53
63
  animate({
@@ -68,7 +78,9 @@ export function useScrollTo() {
68
78
 
69
79
  const yDone = new Promise<void>((onComplete) => {
70
80
  if (ref.scrollTop !== to.y) {
71
- disableSnap()
81
+ disableSnap(stopAnimationOnScroll)
82
+ if (!stopAnimationOnScroll) ref.style.overflow = 'hidden'
83
+
72
84
  stop.push(
73
85
  animate({
74
86
  from: ref.scrollTop,
@@ -92,10 +104,12 @@ export function useScrollTo() {
92
104
  register({ stop: () => stop.forEach((s) => s.stop()) })
93
105
  await Promise.all([xDone, yDone])
94
106
 
107
+ if (!stopAnimationOnScroll) ref.style.removeProperty('overflow')
108
+
95
109
  if (Array.isArray(incoming)) {
96
110
  const checkPositions = getScrollSnapPositions()
97
111
  if (checkPositions.x[incoming[0]] !== to.x || checkPositions.y[incoming[1]] !== to.y)
98
- await scrollTo(incoming, retrigger + 1)
112
+ await scrollTo(incoming, options, __retrigger + 1)
99
113
  }
100
114
  enableSnap()
101
115
  },
@@ -7,7 +7,6 @@ import {
7
7
  MotionValue,
8
8
  PanHandlers,
9
9
  PanInfo,
10
- useDomEvent,
11
10
  useTransform,
12
11
  } from 'framer-motion'
13
12
  import React, { ReactHTML, useEffect, useState } from 'react'
@@ -47,7 +46,7 @@ export function useScroller<
47
46
  >(props: ScrollableProps<TagName>, forwardedRef: React.ForwardedRef<E>) {
48
47
  const { hideScrollbar = false, children, grid = false, ...divProps } = props
49
48
 
50
- const { scrollSnap, scrollerRef, enableSnap, disableSnap, snap, registerChildren, scroll } =
49
+ const { scrollSnap, scrollerRef, disableSnap, snap, registerChildren, scroll } =
51
50
  useScrollerContext()
52
51
 
53
52
  useEffect(() => {
@@ -68,19 +67,6 @@ export function useScroller<
68
67
 
69
68
  const [isPanning, setPanning] = useState(false)
70
69
 
71
- /** If the scroller doesn't have snap enabled and the user is not panning, enable snap */
72
- useDomEvent(scrollerRef as React.RefObject<EventTarget>, 'wheel', (e) => {
73
- /**
74
- * Todo: this is actually incorrect because when enabling the snap points, the area jumps to the
75
- * nearest point a snap.
76
- *
77
- * What we SHOULD do is wait for the scroll position to be set exactly on a snappoint and then
78
- * enable it. However, to do that then we need to know the position of all elements at all time,
79
- * we now are lazy :)
80
- */
81
- if (!snap.get() && !isPanning && e instanceof WheelEvent) enableSnap()
82
- })
83
-
84
70
  const scrollStart = useConstant(() => ({ x: motionValue(0), y: motionValue(0) }))
85
71
  const onPanStart: PanHandlers['onPanStart'] = (event) => {
86
72
  // If we're not dealing with the mouse we don't need to do anything
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/framer-scroller",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "6.2.0-canary.62",
5
+ "version": "6.2.0-canary.64",
6
6
  "sideEffects": false,
7
7
  "scripts": {
8
8
  "dev": "tsc -W"
@@ -16,13 +16,13 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "popmotion": "11.0.5",
19
- "@graphcommerce/framer-utils": "6.2.0-canary.62",
20
- "@graphcommerce/image": "6.2.0-canary.62"
19
+ "@graphcommerce/framer-utils": "6.2.0-canary.64",
20
+ "@graphcommerce/image": "6.2.0-canary.64"
21
21
  },
22
22
  "devDependencies": {
23
- "@graphcommerce/eslint-config-pwa": "6.2.0-canary.62",
24
- "@graphcommerce/prettier-config-pwa": "6.2.0-canary.62",
25
- "@graphcommerce/typescript-config-pwa": "6.2.0-canary.62"
23
+ "@graphcommerce/eslint-config-pwa": "6.2.0-canary.64",
24
+ "@graphcommerce/prettier-config-pwa": "6.2.0-canary.64",
25
+ "@graphcommerce/typescript-config-pwa": "6.2.0-canary.64"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "@mui/material": "^5.10.16",
package/types.ts CHANGED
@@ -32,7 +32,7 @@ export type ScrollerContext = {
32
32
  /** @private */
33
33
  enableSnap: () => void
34
34
  /** @private */
35
- disableSnap: () => void
35
+ disableSnap: (stopAnimationOnScroll?: boolean) => void
36
36
  /** @private */
37
37
  register: (controls: PlaybackControls) => void
38
38
  /** @private */