@graphcommerce/framer-scroller 5.1.0-canary.2 → 5.1.0-canary.4

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,24 @@
1
1
  # Change Log
2
2
 
3
+ ## 5.1.0-canary.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1755](https://github.com/graphcommerce-org/graphcommerce/pull/1755) [`f44a05a6c`](https://github.com/graphcommerce-org/graphcommerce/commit/f44a05a6cedadc17e44c87f53cad5f462bc52aba) - Use a singlular useElementScroll and provide ther scroll from the useScrollerContext ([@paales](https://github.com/paales))
8
+
9
+ - [#1755](https://github.com/graphcommerce-org/graphcommerce/pull/1755) [`3b341f694`](https://github.com/graphcommerce-org/graphcommerce/commit/3b341f69409c2c6404b619684d1a28aec32a0211) - When selecting text inside an input or textarea it shouldn't be possible to drag the scroller ([@paales](https://github.com/paales))
10
+
11
+ ## 5.1.0-canary.3
12
+
13
+ ### Patch Changes
14
+
15
+ - [#1752](https://github.com/graphcommerce-org/graphcommerce/pull/1752) [`2a6a4d9ec`](https://github.com/graphcommerce-org/graphcommerce/commit/2a6a4d9ecfa1b58a66ba9b9d00016d6feda9aa95) - Updated dependencies to latest versions, except for nextjs; Solve tons of peer dependency issues.
16
+
17
+ - Updated the @mui/material package
18
+ - Removed dependencies on react-hook-form-mui and @playwright/test
19
+ - Upgraded dependencies including type-fest and graphql-mesh
20
+ - Solved peer dependency issues ([@paales](https://github.com/paales))
21
+
3
22
  ## 5.1.0-canary.2
4
23
 
5
24
  ## 5.1.0-canary.1
@@ -0,0 +1,106 @@
1
+ import { extendableComponent } from '@graphcommerce/next-ui/Styles'
2
+ import { styled, SxProps, Theme, useTheme } from '@mui/material'
3
+ import { m, transform, useTransform } from 'framer-motion'
4
+ import { useRef } from 'react'
5
+ import { useScrollerContext } from '../hooks/useScrollerContext'
6
+ import { useScrollerControl } from '../hooks/useScrollerControl'
7
+
8
+ type OwnerProps = {
9
+ direction: 'x' | 'y'
10
+ }
11
+
12
+ type ScrollerBarProps = OwnerProps & {
13
+ sx?: SxProps<Theme>
14
+ }
15
+
16
+ const name = 'ScrollerBar'
17
+ const parts = ['track', 'thumb'] as const
18
+ const { withState } = extendableComponent<OwnerProps, typeof name, typeof parts>(name, parts)
19
+
20
+ const Track = styled(m.div)({
21
+ position: 'absolute',
22
+ bottom: 0,
23
+ right: 0,
24
+ left: 0,
25
+ padding: '2px',
26
+ })
27
+
28
+ const Scroll = styled(m.button)({
29
+ appearance: 'none',
30
+ border: 'none',
31
+ background: 'none',
32
+ padding: 0,
33
+ margin: 0,
34
+ display: 'block',
35
+ height: 7,
36
+ })
37
+
38
+ export function ScrollerBar(props: ScrollerBarProps) {
39
+ const { direction, sx } = props
40
+ const control = useScrollerControl()
41
+ const { scroll, scrollerRef } = useScrollerContext()
42
+ const track = useRef<HTMLDivElement>(null)
43
+
44
+ const classes = withState({ direction })
45
+ const theme = useTheme()
46
+
47
+ const offsetSize = direction === 'x' ? 'offsetWidth' : 'offsetHeight'
48
+ const scrollSize = direction === 'x' ? 'scrollWidth' : 'scrollHeight'
49
+ const size = direction === 'x' ? 'width' : 'height'
50
+ const offAxis = direction === 'x' ? 'height' : 'width'
51
+
52
+ const scrollProgress = scroll[`${direction}Progress`]
53
+
54
+ const thumbSize = useTransform(scrollProgress, (directionProgress) => {
55
+ if (!scrollerRef.current) return 0
56
+ const trackWidth = scrollerRef.current[offsetSize]
57
+ return trackWidth * (trackWidth / scrollerRef.current[scrollSize])
58
+ })
59
+
60
+ const thumbX = useTransform([scrollProgress, thumbSize], ([p, thumbS]: number[]) => {
61
+ if (!scrollerRef.current) return undefined
62
+ const baseX = (scrollerRef.current[offsetSize] - thumbS) * p
63
+ return control.controlling ? undefined : baseX
64
+ })
65
+
66
+ return (
67
+ <Track
68
+ ref={track}
69
+ className={classes.track}
70
+ sx={sx}
71
+ whileHover={{
72
+ backgroundColor: theme.palette.action.selected,
73
+ // borderTop: `1px solid ${theme.palette.action.active}`,
74
+ }}
75
+ >
76
+ <Scroll
77
+ className={classes.thumb}
78
+ aria-label='Scroll'
79
+ sx={{
80
+ backgroundColor: theme.palette.action.active,
81
+ borderRadius: '50px',
82
+ }}
83
+ style={{ [direction]: thumbX, [size]: thumbSize }}
84
+ whileHover={{ [offAxis]: 13 }}
85
+ // whileDrag={{ height: 20 }}
86
+ onDragStart={() => control.take()}
87
+ onDragEnd={() => control.release()}
88
+ onDrag={(event, info) => {
89
+ if (!scrollerRef.current || !track.current) return
90
+
91
+ const maxSize = scrollerRef.current[offsetSize] - thumbSize.get()
92
+
93
+ const progress = (control.x / scrollerRef.current[scrollSize]) * track.current[offsetSize]
94
+ const position = progress + info.offset[direction]
95
+ control.updateProgress({
96
+ [direction]: transform(position, [0, maxSize], [0, 1]),
97
+ })
98
+ }}
99
+ drag='x'
100
+ dragConstraints={track}
101
+ dragElastic={0}
102
+ dragMomentum={false}
103
+ />
104
+ </Track>
105
+ )
106
+ }
@@ -1,4 +1,4 @@
1
- import { useElementScroll, useMotionValueValue } from '@graphcommerce/framer-utils'
1
+ import { useMotionValueValue } from '@graphcommerce/framer-utils'
2
2
  import { Fab, FabProps, styled, SxProps, Theme } from '@mui/material'
3
3
  import { m, useTransform } from 'framer-motion'
4
4
  import React from 'react'
@@ -18,11 +18,11 @@ export const ScrollerButton = m(
18
18
  React.forwardRef<HTMLDivElement, ScrollerButtonProps>((props, ref) => {
19
19
  const { direction, sx = [], layout, className, sxContainer, ...buttonProps } = props
20
20
 
21
- const { getSnapPosition, scrollerRef } = useScrollerContext()
21
+ const { getSnapPosition, scroll } = useScrollerContext()
22
22
  const scrollTo = useScrollTo()
23
23
  const handleClick = () => scrollTo(getSnapPosition(direction))
24
24
 
25
- const { xProgress, yProgress, xMax, yMax } = useElementScroll(scrollerRef)
25
+ const { xProgress, yProgress, xMax, yMax } = scroll
26
26
 
27
27
  const visibility = useMotionValueValue(
28
28
  useTransform<number, number>([xProgress, yProgress, xMax, yMax], ([x, y, xM, yM]) => {
@@ -1,4 +1,4 @@
1
- import { useConstant } from '@graphcommerce/framer-utils'
1
+ import { useConstant, useElementScroll } from '@graphcommerce/framer-utils'
2
2
  import { MotionValue, motionValue, Point, useMotionValue } from 'framer-motion'
3
3
  import { PlaybackControls } from 'popmotion'
4
4
  import React, { useEffect, useRef, useMemo, useCallback } from 'react'
@@ -333,6 +333,8 @@ export function ScrollerProvider(props: ScrollerProviderProps) {
333
333
  return { x: 0, y: 0, [axis]: position }
334
334
  }
335
335
 
336
+ const scroll = useElementScroll(scrollerRef)
337
+
336
338
  const value = useConstant<ScrollerContext>(() => ({
337
339
  scrollerRef,
338
340
  scrollSnap,
@@ -345,6 +347,7 @@ export function ScrollerProvider(props: ScrollerProviderProps) {
345
347
  getSnapPosition,
346
348
  getScrollSnapPositions,
347
349
  registerChildren,
350
+ scroll,
348
351
  }))
349
352
 
350
353
  return <scrollerContext.Provider value={value} {...providerProps} />
@@ -1,4 +1,3 @@
1
- import { useElementScroll } from '@graphcommerce/framer-utils'
2
1
  import { MotionConfigContext, Point, Tween } from 'framer-motion'
3
2
  import { animate } from 'popmotion'
4
3
  import { useCallback, useContext } from 'react'
@@ -6,8 +5,7 @@ import { distanceAnimationDuration } from '../utils/distanceAnimationDuration'
6
5
  import { useScrollerContext } from './useScrollerContext'
7
6
 
8
7
  export function useScrollTo() {
9
- const { scrollerRef, register, disableSnap, enableSnap } = useScrollerContext()
10
- const scroll = useElementScroll(scrollerRef)
8
+ const { scrollerRef, register, disableSnap, enableSnap, scroll } = useScrollerContext()
11
9
 
12
10
  const duration = (useContext(MotionConfigContext).transition as Tween | undefined)?.duration ?? 0
13
11
 
@@ -1,4 +1,4 @@
1
- import { useConstant, useElementScroll, useMotionValueValue } from '@graphcommerce/framer-utils'
1
+ import { useConstant, useMotionValueValue } from '@graphcommerce/framer-utils'
2
2
  import { extendableComponent } from '@graphcommerce/next-ui/Styles'
3
3
  import { SxProps, Theme } from '@mui/material'
4
4
  import {
@@ -43,15 +43,13 @@ export function useScroller<
43
43
  >(props: ScrollableProps<TagName>, forwardedRef: React.ForwardedRef<E>) {
44
44
  const { hideScrollbar = false, children, grid = false, ...divProps } = props
45
45
 
46
- const { scrollSnap, scrollerRef, enableSnap, disableSnap, snap, registerChildren } =
46
+ const { scrollSnap, scrollerRef, enableSnap, disableSnap, snap, registerChildren, scroll } =
47
47
  useScrollerContext()
48
48
 
49
49
  useEffect(() => {
50
50
  registerChildren(children)
51
51
  }, [children, registerChildren])
52
52
 
53
- const scroll = useElementScroll(scrollerRef)
54
-
55
53
  const canGrab = useMotionValueValue(
56
54
  useTransform(
57
55
  [scroll.xMax, scroll.yMax] as MotionValue<string | number>[],
@@ -83,6 +81,8 @@ export function useScroller<
83
81
  const onPanStart: PanHandlers['onPanStart'] = (event) => {
84
82
  // If we're not dealing with the mouse we don't need to do anything
85
83
  if (!isHTMLMousePointerEvent(event)) return
84
+
85
+ if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') return
86
86
  if (event.target.closest('.Scroller-root') !== scrollerRef.current) return
87
87
 
88
88
  scrollStart.x.set(scroll.x.get())
@@ -0,0 +1,70 @@
1
+ import { useConstant } from '@graphcommerce/framer-utils'
2
+ import { PanInfo, Point } from 'framer-motion'
3
+ import { useScrollerContext } from './useScrollerContext'
4
+ import { useVelocitySnapTo } from './useVelocitySnapTo'
5
+
6
+ export type ScrollerControl = {
7
+ controlling: boolean
8
+ x: number
9
+ y: number
10
+ take: () => void
11
+ update(point: Partial<Point>): void
12
+ updateProgress(point: Partial<Point>): void
13
+ release: (info?: PanInfo) => Promise<void>
14
+ }
15
+
16
+ export function useScrollerControl(): ScrollerControl {
17
+ const { scrollerRef, disableSnap, scroll } = useScrollerContext()
18
+ const snapToVelocity = useVelocitySnapTo(scrollerRef)
19
+
20
+ const control = useConstant<ScrollerControl>(() => ({
21
+ controlling: false,
22
+ x: 0,
23
+ y: 0,
24
+ take: () => {
25
+ control.controlling = true
26
+ control.x = scroll.x.get()
27
+ control.y = scroll.y.get()
28
+
29
+ // @ts-expect-error private api, but we're updating the animation value here manually instead of relying on the event listener.
30
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
31
+ scroll.scroll.start(() => () => {})
32
+ disableSnap()
33
+ },
34
+ update: ({ x, y }: Point) => {
35
+ if (!control.controlling) throw Error('not controlling')
36
+ if (!scrollerRef.current) return
37
+
38
+ const current = scroll.scroll.get()
39
+ scroll.scroll.set({
40
+ ...current,
41
+ x: x ?? current.x,
42
+ y: y ?? current.y,
43
+ })
44
+ scrollerRef.current.scrollLeft = x
45
+ scrollerRef.current.scrollTop = y
46
+ },
47
+ /** X, y are values from 0, to 1 */
48
+ updateProgress: ({ x, y }: Point) => {
49
+ const scrollRef = scrollerRef.current
50
+ if (!scrollRef) return
51
+
52
+ const current = scroll.scroll.get()
53
+ const xValue = (x ?? current.x) * (scrollRef.scrollWidth - scrollRef.clientWidth)
54
+ const yValue = (y ?? current.y) * (scrollRef.scrollHeight - scrollRef.clientHeight)
55
+
56
+ control.update({ x: xValue, y: yValue })
57
+ },
58
+ release: async (info?: PanInfo) => {
59
+ if (!control.controlling) throw Error('not controlling')
60
+
61
+ scroll.scroll.stop()
62
+ await snapToVelocity(info ?? { velocity: { x: 0, y: 0 }, offset: { x: 0, y: 0 } })
63
+ control.x = 0
64
+ control.y = 0
65
+ control.controlling = false
66
+ },
67
+ }))
68
+
69
+ return control
70
+ }
@@ -1,11 +1,13 @@
1
1
  import { useMatchMedia } from '@graphcommerce/next-ui'
2
- import { useTheme } from '@mui/material'
3
2
  import { PanInfo } from 'framer-motion'
4
3
  import { inertia, InertiaOptions } from 'popmotion'
4
+ import { ScrollerContext } from '../types'
5
5
  import { scrollSnapTypeDirection } from '../utils/scrollSnapTypeDirection'
6
6
  import { useScrollerContext } from './useScrollerContext'
7
7
 
8
- const clamp = ({ velocity, offset }: PanInfo, axis: 'x' | 'y') =>
8
+ type LimitedPanInfo = Pick<PanInfo, 'velocity' | 'offset'>
9
+
10
+ const clamp = ({ velocity, offset }: LimitedPanInfo, axis: 'x' | 'y') =>
9
11
  velocity[axis] < 0
10
12
  ? Math.max(velocity[axis], -Math.abs(offset[axis] * 3))
11
13
  : Math.min(velocity[axis], Math.abs(offset[axis] * 3))
@@ -20,7 +22,7 @@ const closest = (counts: number[], target: number) =>
20
22
  export const useVelocitySnapTo = (
21
23
  ref: React.RefObject<HTMLElement> | React.MutableRefObject<HTMLElement | undefined>,
22
24
  ) => {
23
- const { disableSnap, enableSnap, register, getScrollSnapPositions, scrollSnap } =
25
+ const { disableSnap, enableSnap, register, getScrollSnapPositions, scrollSnap, scroll } =
24
26
  useScrollerContext()
25
27
 
26
28
  const matchMedia = useMatchMedia()
@@ -39,7 +41,7 @@ export const useVelocitySnapTo = (
39
41
  // restSpeed: 1,
40
42
  }
41
43
 
42
- const animatePan = async (info: PanInfo) => {
44
+ const animatePan = async (info: LimitedPanInfo) => {
43
45
  const el = ref.current
44
46
  if (!el) throw Error(`Can't find html element`)
45
47
 
@@ -59,7 +61,9 @@ export const useVelocitySnapTo = (
59
61
  min: typeof closestX !== 'undefined' ? closestX - scrollLeft : undefined,
60
62
  ...inertiaOptions,
61
63
  onUpdate: (v: number) => {
62
- el.scrollLeft = Math.round(v + scrollLeft)
64
+ const x = Math.round(v + scrollLeft)
65
+ el.scrollLeft = x
66
+ scroll.scroll.set({ ...scroll.scroll.get(), x })
63
67
  },
64
68
  onComplete,
65
69
  }),
@@ -83,7 +87,9 @@ export const useVelocitySnapTo = (
83
87
  min: typeof closestY !== 'undefined' ? closestY - scrollTop : undefined,
84
88
  ...inertiaOptions,
85
89
  onUpdate: (v: number) => {
86
- el.scrollTop = Math.round(v + scrollTop)
90
+ const y = Math.round(v + scrollTop)
91
+ el.scrollTop = y
92
+ scroll.scroll.set({ ...scroll.scroll.get(), y })
87
93
  },
88
94
  onComplete,
89
95
  }),
package/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from './types'
2
2
 
3
3
  export * from './components/MotionImageAspect'
4
4
  export * from './components/Scroller'
5
+ export * from './components/ScrollerBar'
5
6
  export * from './components/ScrollerButton'
6
7
  export * from './components/ScrollerDots'
7
8
  export * from './components/ScrollerPageCounter'
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": "5.1.0-canary.2",
5
+ "version": "5.1.0-canary.4",
6
6
  "sideEffects": false,
7
7
  "scripts": {
8
8
  "dev": "tsc -W"
@@ -15,23 +15,22 @@
15
15
  }
16
16
  },
17
17
  "dependencies": {
18
- "@graphcommerce/framer-utils": "5.1.0-canary.2",
19
- "@graphcommerce/image": "5.1.0-canary.2"
18
+ "popmotion": "11.0.5",
19
+ "@graphcommerce/framer-utils": "5.1.0-canary.4",
20
+ "@graphcommerce/image": "5.1.0-canary.4"
20
21
  },
21
22
  "devDependencies": {
22
- "@graphcommerce/eslint-config-pwa": "^5.1.0-canary.2",
23
- "@graphcommerce/prettier-config-pwa": "^5.1.0-canary.2",
24
- "@graphcommerce/typescript-config-pwa": "^5.1.0-canary.2",
25
- "@playwright/test": "^1.21.1"
23
+ "@graphcommerce/eslint-config-pwa": "5.1.0-canary.4",
24
+ "@graphcommerce/prettier-config-pwa": "5.1.0-canary.4",
25
+ "@graphcommerce/typescript-config-pwa": "5.1.0-canary.4"
26
26
  },
27
27
  "peerDependencies": {
28
- "@mui/material": "5.5.3",
28
+ "@mui/material": "^5.10.16",
29
29
  "@lingui/react": "^3.13.2",
30
30
  "@lingui/core": "^3.13.2",
31
- "framer-motion": "^6.2.4",
31
+ "framer-motion": "^7.0.0",
32
32
  "next": "^12.1.2",
33
- "popmotion": "11.0.3",
34
- "react": "^18.0.0",
35
- "react-dom": "^18.0.0"
33
+ "react": "^18.2.0",
34
+ "react-dom": "^18.2.0"
36
35
  }
37
36
  }
package/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ScrollMotionValues } from '@graphcommerce/framer-utils'
1
2
  import { MotionValue, Point } from 'framer-motion'
2
3
  import { PlaybackControls } from 'popmotion'
3
4
  import React from 'react'
@@ -26,6 +27,7 @@ export type ScrollerContext = {
26
27
  scrollerRef: ReactHtmlRefObject
27
28
  items: MotionValue<ItemState[]>
28
29
  snap: MotionValue<boolean>
30
+ scroll: ScrollMotionValues
29
31
 
30
32
  /** @private */
31
33
  enableSnap: () => void