@graphcommerce/framer-next-pages 3.2.2 → 3.2.5

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,29 @@
1
1
  # Change Log
2
2
 
3
+ ## 3.2.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`707dbc73d`](https://github.com/graphcommerce-org/graphcommerce/commit/707dbc73d181204d88fdbbd2e09340e25b2b5f7b), [`5c5645e6e`](https://github.com/graphcommerce-org/graphcommerce/commit/5c5645e6eaf5314c063f05547707fcd4b34a8717)]:
8
+ - @graphcommerce/framer-utils@3.1.5
9
+
10
+ ## 3.2.4
11
+
12
+ ### Patch Changes
13
+
14
+ - [#1552](https://github.com/graphcommerce-org/graphcommerce/pull/1552) [`18054c441`](https://github.com/graphcommerce-org/graphcommerce/commit/18054c441962ba750bed3acc39ab46c8d3a341ce) Thanks [@paales](https://github.com/paales)! - Updated to Next.js v12.2.2 and other packages and made compatible
15
+
16
+ * [#1552](https://github.com/graphcommerce-org/graphcommerce/pull/1552) [`21886d6fa`](https://github.com/graphcommerce-org/graphcommerce/commit/21886d6fa64a48d9e932bfaf8d138c9b13c36e43) Thanks [@paales](https://github.com/paales)! - Fix page stacking and scroll restoration when navigating
17
+
18
+ ## 3.2.3
19
+
20
+ ### Patch Changes
21
+
22
+ - [#1490](https://github.com/graphcommerce-org/graphcommerce/pull/1490) [`d311ef48b`](https://github.com/graphcommerce-org/graphcommerce/commit/d311ef48bb3e97806d992af5516d6b7f183ec9cb) Thanks [@paales](https://github.com/paales)! - upgraded packages
23
+
24
+ - Updated dependencies [[`d311ef48b`](https://github.com/graphcommerce-org/graphcommerce/commit/d311ef48bb3e97806d992af5516d6b7f183ec9cb)]:
25
+ - @graphcommerce/framer-utils@3.1.4
26
+
3
27
  ## 3.2.2
4
28
 
5
29
  ### Patch Changes
@@ -3,38 +3,39 @@ import { m, useIsPresent } from 'framer-motion'
3
3
  import React from 'react'
4
4
  import type { PageItem } from '../types'
5
5
 
6
- export type PageProps = Pick<PageItem, 'historyIdx'> & {
6
+ export type PageProps = Pick<PageItem, 'routerKey'> & {
7
7
  active: boolean
8
8
  children: React.ReactNode
9
9
  }
10
10
 
11
- export function scrollPos(idx: number): { x: number; y: number } {
12
- const scroll = global.window?.sessionStorage[`__next_scroll_${idx}`]
11
+ export function scrollPos(key: string): { x: number; y: number } {
12
+ const scroll = global.window?.sessionStorage[`__next_scroll_${key}`]
13
13
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
14
14
  return scroll ? JSON.parse(scroll) : { x: 0, y: 0 }
15
15
  }
16
16
 
17
17
  export function Page(props: PageProps) {
18
- const { active, historyIdx, children } = props
18
+ const { active, routerKey, children } = props
19
19
  const isPresent = useIsPresent()
20
20
 
21
21
  /** The active Page doesn't get any special treatment */
22
22
  let top: number | undefined
23
23
 
24
24
  /** If the Page isn't active, we offset the page */
25
- if (!active) top = scrollPos(historyIdx).y * -1
25
+ if (!active) top = scrollPos(routerKey).y * -1
26
26
 
27
27
  /**
28
28
  * If the Page isn't present as a child of <AnimatePresence/>, but it is still present in the DOM,
29
29
  * we're navigating back, so we need to offset it.
30
30
  */
31
- if (!isPresent) top = scrollPos(historyIdx).y
31
+ if (!isPresent) top = scrollPos(routerKey).y
32
32
 
33
33
  const position = active && isPresent ? 'absolute' : 'fixed'
34
34
  const zIndex = active ? 1 : undefined
35
35
 
36
36
  return (
37
37
  <m.div
38
+ layoutScroll
38
39
  style={{ position, top, zIndex, minHeight: clientSizeCssVar.y, left: 0, right: 0 }}
39
40
  // @ts-expect-error inert is not in the type definition yet
40
41
  inert={!active ? 'true' : undefined}
@@ -5,7 +5,7 @@ import React, { useMemo } from 'react'
5
5
  import { pageRouterContext } from '../context/pageRouterContext'
6
6
  import { PageItem } from '../types'
7
7
 
8
- const NoLayout: React.FC = ({ children }) => <>{children}</>
8
+ const NoLayout: React.FC<{ children?: React.ReactNode }> = ({ children }) => <>{children}</>
9
9
 
10
10
  export type PageRendererProps = Omit<AppPropsType, 'router'> & {
11
11
  Layout: React.ComponentType<AppPropsType>
@@ -1,6 +1,7 @@
1
1
  import { clientSizeCssVar, useClientSizeCssVar } from '@graphcommerce/framer-utils'
2
2
  import { AnimatePresence, m } from 'framer-motion'
3
3
  import { requestIdleCallback, cancelIdleCallback } from 'next/dist/client/request-idle-callback'
4
+ import { HistoryState, PrivateRouteInfo } from 'next/dist/shared/lib/router/router'
4
5
  import { AppPropsType } from 'next/dist/shared/lib/utils'
5
6
  import { NextRouter, Router } from 'next/router'
6
7
  import { useEffect, useRef, useState } from 'react'
@@ -44,14 +45,23 @@ function getPageInfo(router: NextRouter) {
44
45
  export function FramerNextPages(props: PagesProps) {
45
46
  const { router, Component, pageProps: incomingProps, fallback = '/', fallbackRoute = '/' } = props
46
47
 
48
+ // @ts-expect-error Key of the route is still private, should be fixed in https://github.com/vercel/next.js/pull/37192
49
+ // eslint-disable-next-line no-underscore-dangle
50
+ const key = router._key as string
51
+
47
52
  useClientSizeCssVar()
48
53
  const items = useRef<PageItem[]>([])
49
- const idx = Number(global.window?.history.state?.idx ?? 0)
54
+
55
+ const routerKeys = items.current.map((item) => item.routerKey)
56
+ if (routerKeys.indexOf(key) === -1) routerKeys.push(key)
57
+ const idx = routerKeys.indexOf(key)
58
+
50
59
  const prevHistory = useRef<number>(-1)
51
- const [fb, setFallback] = useState<PageItem>()
52
60
  const direction = idx > prevHistory.current ? 1 : -1
53
61
  prevHistory.current = idx
54
62
 
63
+ const [fb, setFallback] = useState<PageItem>()
64
+
55
65
  /** We never need to render anything beyong the current idx and we can safely omit everything */
56
66
  items.current = items.current.slice(0, idx + 1)
57
67
 
@@ -76,6 +86,7 @@ export function FramerNextPages(props: PagesProps) {
76
86
  sharedKey: Component.pageOptions?.sharedKey?.(pageInfo) ?? pageInfo.pathname,
77
87
  overlayGroup: Component.pageOptions?.overlayGroup,
78
88
  historyIdx: idx,
89
+ routerKey: key,
79
90
  routerContext: {
80
91
  pageInfo,
81
92
  prevPage: items.current[idx - 1]?.routerContext,
@@ -102,16 +113,23 @@ export function FramerNextPages(props: PagesProps) {
102
113
  // todo: implement fallback loading for up property
103
114
  // const up = items.current[0].PageComponent.pageOptions?.up?.href ?? '/'
104
115
  const up = '/'
105
- const info = await (router as Router).getRouteInfo(
106
- fallbackRoute,
107
- fallback,
108
- {},
109
- fallback,
110
- fallback,
111
- { shallow: false },
112
- router.locale,
113
- false,
114
- )
116
+ const info = await (router as Router).getRouteInfo({
117
+ route: fallbackRoute,
118
+ pathname: fallback,
119
+ query: {},
120
+ as: fallback,
121
+ resolvedAs: fallback,
122
+ routeProps: { shallow: false },
123
+ locale: router.locale,
124
+ hasMiddleware: false,
125
+ isPreview: false,
126
+ })
127
+
128
+ const isPrivateRouteInfo = (
129
+ infoResult: Awaited<ReturnType<Router['getRouteInfo']>>,
130
+ ): infoResult is PrivateRouteInfo => 'Component' in infoResult
131
+
132
+ if (!isPrivateRouteInfo(info)) return
115
133
 
116
134
  const pageInfo = { asPath: up, pathname: up, locale: router.locale, query: {} }
117
135
  const Fallback = info.Component as PageComponent
@@ -122,6 +140,7 @@ export function FramerNextPages(props: PagesProps) {
122
140
  sharedKey: Fallback.pageOptions?.sharedKey?.(pageInfo) ?? pageInfo.pathname,
123
141
  overlayGroup: Fallback.pageOptions?.overlayGroup,
124
142
  historyIdx: -1,
143
+ routerKey: 'fallback',
125
144
  routerContext: {
126
145
  pageInfo,
127
146
  up: Fallback.pageOptions?.up ?? info.props?.pageProps?.up,
@@ -186,31 +205,33 @@ and pass it as a param in <FramerNextPages fallbackRoute='/[...url]' /> in your
186
205
  .reverse()
187
206
 
188
207
  return (
189
- <AnimatePresence initial={false}>
208
+ <>
190
209
  <m.div
191
210
  style={{ position: 'absolute', top: 0, minHeight: clientSizeCssVar.y, left: 0, right: 0 }}
192
211
  />
193
- {renderItems.map((item, itemIdx) => {
194
- const { historyIdx, sharedKey, overlayGroup } = item
195
- const active = itemIdx === renderItems.length - 1
196
- const depth = itemIdx - (renderItems.length - 1)
197
- const closeIdx = renderItems[itemIdx - 1]?.historyIdx ?? -1
198
- const closeSteps = closeIdx > -1 ? historyIdx - closeIdx : 0
199
- const backSteps = historyIdx - closeIdx - 1
200
-
201
- return (
202
- <pageContext.Provider
203
- key={sharedKey}
204
- // We're actually rerendering here but since the actual page renderer is memoized we can safely do this
205
- // eslint-disable-next-line react/jsx-no-constructed-context-values
206
- value={{ depth, active, direction, closeSteps, backSteps, historyIdx, overlayGroup }}
207
- >
208
- <Page active={active} historyIdx={historyIdx}>
209
- <PageRenderer {...item} />
210
- </Page>
211
- </pageContext.Provider>
212
- )
213
- })}
214
- </AnimatePresence>
212
+ <AnimatePresence initial={false}>
213
+ {renderItems.map((item, itemIdx) => {
214
+ const { historyIdx, sharedKey, overlayGroup, routerKey } = item
215
+ const active = itemIdx === renderItems.length - 1
216
+ const depth = itemIdx - (renderItems.length - 1)
217
+ const closeIdx = renderItems[itemIdx - 1]?.historyIdx ?? -1
218
+ const closeSteps = closeIdx > -1 ? historyIdx - closeIdx : 0
219
+ const backSteps = historyIdx - closeIdx - 1
220
+
221
+ return (
222
+ <pageContext.Provider
223
+ key={sharedKey}
224
+ // We're actually rerendering here but since the actual page renderer is memoized we can safely do this
225
+ // eslint-disable-next-line react/jsx-no-constructed-context-values
226
+ value={{ depth, active, direction, closeSteps, backSteps, routerKey, overlayGroup }}
227
+ >
228
+ <Page active={active} routerKey={routerKey}>
229
+ <PageRenderer {...item} />
230
+ </Page>
231
+ </pageContext.Provider>
232
+ )
233
+ })}
234
+ </AnimatePresence>
235
+ </>
215
236
  )
216
237
  }
@@ -39,7 +39,7 @@ export type PageContext = {
39
39
  overlayGroup?: string
40
40
 
41
41
  /** @private */
42
- historyIdx: number
42
+ routerKey: string
43
43
  }
44
44
 
45
45
  export const pageContext = createContext(undefined as unknown as PageContext)
@@ -1,4 +1,5 @@
1
- import { useMemo } from 'react'
1
+ import { useMotionValue } from 'framer-motion'
2
+ import { useEffect, useMemo } from 'react'
2
3
  import { scrollPos } from '../components/Page'
3
4
  import { usePageContext } from './usePageContext'
4
5
 
@@ -7,8 +8,15 @@ import { usePageContext } from './usePageContext'
7
8
  * scroll based elements.
8
9
  */
9
10
  export function useScrollOffset() {
10
- const { active, historyIdx } = usePageContext()
11
+ const { active, routerKey } = usePageContext()
12
+
13
+ const scrolloffset = useMotionValue(0)
14
+
15
+ useEffect(() => {
16
+ if (active) scrolloffset.set(0)
17
+ else scrolloffset.set(scrollPos(routerKey).y)
18
+ }, [active, routerKey, scrolloffset])
11
19
 
12
20
  // When the page is rendered in the background we should use it's scroll offset while rendering
13
- return useMemo(() => (active ? { x: 0, y: 0 } : scrollPos(historyIdx)), [active, historyIdx])
21
+ return scrolloffset
14
22
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/framer-next-pages",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "3.2.2",
5
+ "version": "3.2.5",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,19 +12,19 @@
12
12
  }
13
13
  },
14
14
  "dependencies": {
15
- "@graphcommerce/framer-utils": "3.1.3"
15
+ "@graphcommerce/framer-utils": "3.1.5"
16
16
  },
17
17
  "devDependencies": {
18
- "@graphcommerce/eslint-config-pwa": "^4.1.5",
18
+ "@graphcommerce/eslint-config-pwa": "^4.1.10",
19
19
  "@graphcommerce/prettier-config-pwa": "^4.0.6",
20
- "@graphcommerce/typescript-config-pwa": "^4.0.2",
20
+ "@graphcommerce/typescript-config-pwa": "^4.0.4",
21
21
  "@playwright/test": "^1.21.1"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "framer-motion": "^6.2.4",
25
- "next": "12.1.2",
26
- "react": "^17.0.2",
27
- "react-dom": "^17.0.2",
25
+ "next": "^12.1.2",
26
+ "react": "^18.0.0",
27
+ "react-dom": "^18.0.0",
28
28
  "@emotion/react": "^11.8.2"
29
29
  }
30
30
  }
package/types.ts CHANGED
@@ -80,9 +80,9 @@ export type PageOptions<T extends Record<string, unknown> = Record<string, unkno
80
80
  * FramerNextPages uses Framer Motion's <AnimatePresence/> to animate pages in and out. From the
81
81
  * [docs](https://www.framer.com/api/motion/animate-presence/#usage):
82
82
  *
83
- * *In React, changing a component's key makes React treat it as an entirely new component. So the
83
+ * _In React, changing a component's key makes React treat it as an entirely new component. So the
84
84
  * old one is unmounted before the new one is mounted. So by changing the key of a single child of
85
- * AnimatePresence, we can easily make animated page transitions! 🎉*
85
+ * AnimatePresence, we can easily make animated page transitions! 🎉_
86
86
  *
87
87
  * To create transitions we need to let React know if we should create a new component. We do this
88
88
  * by specifying a 'sharedKey'. By default this key is the same as the pathname:
@@ -143,6 +143,7 @@ export type PageItem = {
143
143
  routerOverride?: Partial<NextRouter>
144
144
  PageComponent: PageComponent
145
145
  historyIdx: number
146
+ routerKey: string
146
147
  sharedKey: string
147
148
  pageProps?: Record<string, unknown>
148
149
  routerContext: PageContext