@react-navigation/native 6.0.16 → 6.1.0

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.
@@ -95,7 +95,10 @@ function createMemoryHistory() {
95
95
  state
96
96
  } = _ref3;
97
97
  interrupt();
98
- const id = (_window$history$state2 = (_window$history$state3 = window.history.state) === null || _window$history$state3 === void 0 ? void 0 : _window$history$state3.id) !== null && _window$history$state2 !== void 0 ? _window$history$state2 : (0, _nonSecure.nanoid)();
98
+ const id = (_window$history$state2 = (_window$history$state3 = window.history.state) === null || _window$history$state3 === void 0 ? void 0 : _window$history$state3.id) !== null && _window$history$state2 !== void 0 ? _window$history$state2 : (0, _nonSecure.nanoid)(); // Need to keep the hash part of the path if there was no previous history entry
99
+ // or the previous history entry had the same path
100
+
101
+ let pathWithHash = path;
99
102
 
100
103
  if (!items.length || items.findIndex(item => item.id === id) < 0) {
101
104
  // There are two scenarios for creating an array with only one history record:
@@ -104,13 +107,18 @@ function createMemoryHistory() {
104
107
  // the page when navigating forward in history.
105
108
  // - This is the first time any state modifications are done
106
109
  // So we need to push the entry as there's nothing to replace
110
+ pathWithHash = pathWithHash + location.hash;
107
111
  items = [{
108
- path,
112
+ path: pathWithHash,
109
113
  state,
110
114
  id
111
115
  }];
112
116
  index = 0;
113
117
  } else {
118
+ if (items[index].path === path) {
119
+ pathWithHash = pathWithHash + location.hash;
120
+ }
121
+
114
122
  items[index] = {
115
123
  path,
116
124
  state,
@@ -120,7 +128,7 @@ function createMemoryHistory() {
120
128
 
121
129
  window.history.replaceState({
122
130
  id
123
- }, '', path);
131
+ }, '', pathWithHash);
124
132
  },
125
133
 
126
134
  // `history.go(n)` is asynchronous, there are couple of things to keep in mind:
@@ -1 +1 @@
1
- {"version":3,"names":["createMemoryHistory","index","items","pending","interrupt","forEach","it","cb","history","id","window","state","findIndex","item","get","backIndex","path","i","push","nanoid","slice","length","pushState","replace","replaceState","go","n","nextIndex","lastItemIndex","Promise","resolve","reject","done","interrupted","clearTimeout","timer","Error","title","document","ref","setTimeout","splice","onPopState","currentIndex","Math","max","last","pop","removeEventListener","addEventListener","listen","listener"],"sources":["createMemoryHistory.tsx"],"sourcesContent":["import type { NavigationState } from '@react-navigation/core';\nimport { nanoid } from 'nanoid/non-secure';\n\ntype HistoryRecord = {\n // Unique identifier for this record to match it with window.history.state\n id: string;\n // Navigation state object for the history entry\n state: NavigationState;\n // Path of the history entry\n path: string;\n};\n\nexport default function createMemoryHistory() {\n let index = 0;\n let items: HistoryRecord[] = [];\n\n // Pending callbacks for `history.go(n)`\n // We might modify the callback stored if it was interrupted, so we have a ref to identify it\n const pending: { ref: unknown; cb: (interrupted?: boolean) => void }[] = [];\n\n const interrupt = () => {\n // If another history operation was performed we need to interrupt existing ones\n // This makes sure that calls such as `history.replace` after `history.go` don't happen\n // Since otherwise it won't be correct if something else has changed\n pending.forEach((it) => {\n const cb = it.cb;\n it.cb = () => cb(true);\n });\n };\n\n const history = {\n get index(): number {\n // We store an id in the state instead of an index\n // Index could get out of sync with in-memory values if page reloads\n const id = window.history.state?.id;\n\n if (id) {\n const index = items.findIndex((item) => item.id === id);\n\n return index > -1 ? index : 0;\n }\n\n return 0;\n },\n\n get(index: number) {\n return items[index];\n },\n\n backIndex({ path }: { path: string }) {\n // We need to find the index from the element before current to get closest path to go back to\n for (let i = index - 1; i >= 0; i--) {\n const item = items[i];\n\n if (item.path === path) {\n return i;\n }\n }\n\n return -1;\n },\n\n push({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = nanoid();\n\n // When a new entry is pushed, all the existing entries after index will be inaccessible\n // So we remove any existing entries after the current index to clean them up\n items = items.slice(0, index + 1);\n\n items.push({ path, state, id });\n index = items.length - 1;\n\n // We pass empty string for title because it's ignored in all browsers except safari\n // We don't store state object in history.state because:\n // - browsers have limits on how big it can be, and we don't control the size\n // - while not recommended, there could be non-serializable data in state\n window.history.pushState({ id }, '', path);\n },\n\n replace({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = window.history.state?.id ?? nanoid();\n\n if (!items.length || items.findIndex((item) => item.id === id) < 0) {\n // There are two scenarios for creating an array with only one history record:\n // - When loaded id not found in the items array, this function by default will replace\n // the first item. We need to keep only the new updated object, otherwise it will break\n // the page when navigating forward in history.\n // - This is the first time any state modifications are done\n // So we need to push the entry as there's nothing to replace\n items = [{ path, state, id }];\n index = 0;\n } else {\n items[index] = { path, state, id };\n }\n\n window.history.replaceState({ id }, '', path);\n },\n\n // `history.go(n)` is asynchronous, there are couple of things to keep in mind:\n // - it won't do anything if we can't go `n` steps, the `popstate` event won't fire.\n // - each `history.go(n)` call will trigger a separate `popstate` event with correct location.\n // - the `popstate` event fires before the next frame after calling `history.go(n)`.\n // This method differs from `history.go(n)` in the sense that it'll go back as many steps it can.\n go(n: number) {\n interrupt();\n\n // To guard against unexpected navigation out of the app we will assume that browser history is only as deep as the length of our memory\n // history. If we don't have an item to navigate to then update our index and navigate as far as we can without taking the user out of the app.\n const nextIndex = index + n;\n const lastItemIndex = items.length - 1;\n if (n < 0 && !items[nextIndex]) {\n // Attempted to navigate beyond the first index. Negating the current index will align the browser history with the first item.\n n = -index;\n index = 0;\n } else if (n > 0 && nextIndex > lastItemIndex) {\n // Attempted to navigate past the last index. Calculate how many indices away from the last index and go there.\n n = lastItemIndex - index;\n index = lastItemIndex;\n } else {\n index = nextIndex;\n }\n\n if (n === 0) {\n return;\n }\n\n // When we call `history.go`, `popstate` will fire when there's history to go back to\n // So we need to somehow handle following cases:\n // - There's history to go back, `history.go` is called, and `popstate` fires\n // - `history.go` is called multiple times, we need to resolve on respective `popstate`\n // - No history to go back, but `history.go` was called, browser has no API to detect it\n return new Promise<void>((resolve, reject) => {\n const done = (interrupted?: boolean) => {\n clearTimeout(timer);\n\n if (interrupted) {\n reject(new Error('History was changed during navigation.'));\n return;\n }\n\n // There seems to be a bug in Chrome regarding updating the title\n // If we set a title just before calling `history.go`, the title gets lost\n // However the value of `document.title` is still what we set it to\n // It's just not displayed in the tab bar\n // To update the tab bar, we need to reset the title to something else first (e.g. '')\n // And set the title to what it was before so it gets applied\n // It won't work without setting it to empty string coz otherwise title isn't changing\n // Which means that the browser won't do anything after setting the title\n const { title } = window.document;\n\n window.document.title = '';\n window.document.title = title;\n\n resolve();\n };\n\n pending.push({ ref: done, cb: done });\n\n // If navigation didn't happen within 100ms, assume that it won't happen\n // This may not be accurate, but hopefully it won't take so much time\n // In Chrome, navigation seems to happen instantly in next microtask\n // But on Firefox, it seems to take much longer, around 50ms from our testing\n // We're using a hacky timeout since there doesn't seem to be way to know for sure\n const timer = setTimeout(() => {\n const index = pending.findIndex((it) => it.ref === done);\n\n if (index > -1) {\n pending[index].cb();\n pending.splice(index, 1);\n }\n }, 100);\n\n const onPopState = () => {\n const id = window.history.state?.id;\n const currentIndex = items.findIndex((item) => item.id === id);\n\n // Fix createMemoryHistory.index variable's value\n // as it may go out of sync when navigating in the browser.\n index = Math.max(currentIndex, 0);\n\n const last = pending.pop();\n\n window.removeEventListener('popstate', onPopState);\n last?.cb();\n };\n\n window.addEventListener('popstate', onPopState);\n window.history.go(n);\n });\n },\n\n // The `popstate` event is triggered when history changes, except `pushState` and `replaceState`\n // If we call `history.go(n)` ourselves, we don't want it to trigger the listener\n // Here we normalize it so that only external changes (e.g. user pressing back/forward) trigger the listener\n listen(listener: () => void) {\n const onPopState = () => {\n if (pending.length) {\n // This was triggered by `history.go(n)`, we shouldn't call the listener\n return;\n }\n\n listener();\n };\n\n window.addEventListener('popstate', onPopState);\n\n return () => window.removeEventListener('popstate', onPopState);\n },\n };\n\n return history;\n}\n"],"mappings":"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"}
1
+ {"version":3,"names":["createMemoryHistory","index","items","pending","interrupt","forEach","it","cb","history","id","window","state","findIndex","item","get","backIndex","path","i","push","nanoid","slice","length","pushState","replace","pathWithHash","location","hash","replaceState","go","n","nextIndex","lastItemIndex","Promise","resolve","reject","done","interrupted","clearTimeout","timer","Error","title","document","ref","setTimeout","splice","onPopState","currentIndex","Math","max","last","pop","removeEventListener","addEventListener","listen","listener"],"sources":["createMemoryHistory.tsx"],"sourcesContent":["import type { NavigationState } from '@react-navigation/core';\nimport { nanoid } from 'nanoid/non-secure';\n\ntype HistoryRecord = {\n // Unique identifier for this record to match it with window.history.state\n id: string;\n // Navigation state object for the history entry\n state: NavigationState;\n // Path of the history entry\n path: string;\n};\n\nexport default function createMemoryHistory() {\n let index = 0;\n let items: HistoryRecord[] = [];\n\n // Pending callbacks for `history.go(n)`\n // We might modify the callback stored if it was interrupted, so we have a ref to identify it\n const pending: { ref: unknown; cb: (interrupted?: boolean) => void }[] = [];\n\n const interrupt = () => {\n // If another history operation was performed we need to interrupt existing ones\n // This makes sure that calls such as `history.replace` after `history.go` don't happen\n // Since otherwise it won't be correct if something else has changed\n pending.forEach((it) => {\n const cb = it.cb;\n it.cb = () => cb(true);\n });\n };\n\n const history = {\n get index(): number {\n // We store an id in the state instead of an index\n // Index could get out of sync with in-memory values if page reloads\n const id = window.history.state?.id;\n\n if (id) {\n const index = items.findIndex((item) => item.id === id);\n\n return index > -1 ? index : 0;\n }\n\n return 0;\n },\n\n get(index: number) {\n return items[index];\n },\n\n backIndex({ path }: { path: string }) {\n // We need to find the index from the element before current to get closest path to go back to\n for (let i = index - 1; i >= 0; i--) {\n const item = items[i];\n\n if (item.path === path) {\n return i;\n }\n }\n\n return -1;\n },\n\n push({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = nanoid();\n\n // When a new entry is pushed, all the existing entries after index will be inaccessible\n // So we remove any existing entries after the current index to clean them up\n items = items.slice(0, index + 1);\n\n items.push({ path, state, id });\n index = items.length - 1;\n\n // We pass empty string for title because it's ignored in all browsers except safari\n // We don't store state object in history.state because:\n // - browsers have limits on how big it can be, and we don't control the size\n // - while not recommended, there could be non-serializable data in state\n window.history.pushState({ id }, '', path);\n },\n\n replace({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = window.history.state?.id ?? nanoid();\n\n // Need to keep the hash part of the path if there was no previous history entry\n // or the previous history entry had the same path\n let pathWithHash = path;\n\n if (!items.length || items.findIndex((item) => item.id === id) < 0) {\n // There are two scenarios for creating an array with only one history record:\n // - When loaded id not found in the items array, this function by default will replace\n // the first item. We need to keep only the new updated object, otherwise it will break\n // the page when navigating forward in history.\n // - This is the first time any state modifications are done\n // So we need to push the entry as there's nothing to replace\n pathWithHash = pathWithHash + location.hash;\n items = [{ path: pathWithHash, state, id }];\n index = 0;\n } else {\n if (items[index].path === path) {\n pathWithHash = pathWithHash + location.hash;\n }\n items[index] = { path, state, id };\n }\n\n window.history.replaceState({ id }, '', pathWithHash);\n },\n\n // `history.go(n)` is asynchronous, there are couple of things to keep in mind:\n // - it won't do anything if we can't go `n` steps, the `popstate` event won't fire.\n // - each `history.go(n)` call will trigger a separate `popstate` event with correct location.\n // - the `popstate` event fires before the next frame after calling `history.go(n)`.\n // This method differs from `history.go(n)` in the sense that it'll go back as many steps it can.\n go(n: number) {\n interrupt();\n\n // To guard against unexpected navigation out of the app we will assume that browser history is only as deep as the length of our memory\n // history. If we don't have an item to navigate to then update our index and navigate as far as we can without taking the user out of the app.\n const nextIndex = index + n;\n const lastItemIndex = items.length - 1;\n if (n < 0 && !items[nextIndex]) {\n // Attempted to navigate beyond the first index. Negating the current index will align the browser history with the first item.\n n = -index;\n index = 0;\n } else if (n > 0 && nextIndex > lastItemIndex) {\n // Attempted to navigate past the last index. Calculate how many indices away from the last index and go there.\n n = lastItemIndex - index;\n index = lastItemIndex;\n } else {\n index = nextIndex;\n }\n\n if (n === 0) {\n return;\n }\n\n // When we call `history.go`, `popstate` will fire when there's history to go back to\n // So we need to somehow handle following cases:\n // - There's history to go back, `history.go` is called, and `popstate` fires\n // - `history.go` is called multiple times, we need to resolve on respective `popstate`\n // - No history to go back, but `history.go` was called, browser has no API to detect it\n return new Promise<void>((resolve, reject) => {\n const done = (interrupted?: boolean) => {\n clearTimeout(timer);\n\n if (interrupted) {\n reject(new Error('History was changed during navigation.'));\n return;\n }\n\n // There seems to be a bug in Chrome regarding updating the title\n // If we set a title just before calling `history.go`, the title gets lost\n // However the value of `document.title` is still what we set it to\n // It's just not displayed in the tab bar\n // To update the tab bar, we need to reset the title to something else first (e.g. '')\n // And set the title to what it was before so it gets applied\n // It won't work without setting it to empty string coz otherwise title isn't changing\n // Which means that the browser won't do anything after setting the title\n const { title } = window.document;\n\n window.document.title = '';\n window.document.title = title;\n\n resolve();\n };\n\n pending.push({ ref: done, cb: done });\n\n // If navigation didn't happen within 100ms, assume that it won't happen\n // This may not be accurate, but hopefully it won't take so much time\n // In Chrome, navigation seems to happen instantly in next microtask\n // But on Firefox, it seems to take much longer, around 50ms from our testing\n // We're using a hacky timeout since there doesn't seem to be way to know for sure\n const timer = setTimeout(() => {\n const index = pending.findIndex((it) => it.ref === done);\n\n if (index > -1) {\n pending[index].cb();\n pending.splice(index, 1);\n }\n }, 100);\n\n const onPopState = () => {\n const id = window.history.state?.id;\n const currentIndex = items.findIndex((item) => item.id === id);\n\n // Fix createMemoryHistory.index variable's value\n // as it may go out of sync when navigating in the browser.\n index = Math.max(currentIndex, 0);\n\n const last = pending.pop();\n\n window.removeEventListener('popstate', onPopState);\n last?.cb();\n };\n\n window.addEventListener('popstate', onPopState);\n window.history.go(n);\n });\n },\n\n // The `popstate` event is triggered when history changes, except `pushState` and `replaceState`\n // If we call `history.go(n)` ourselves, we don't want it to trigger the listener\n // Here we normalize it so that only external changes (e.g. user pressing back/forward) trigger the listener\n listen(listener: () => void) {\n const onPopState = () => {\n if (pending.length) {\n // This was triggered by `history.go(n)`, we shouldn't call the listener\n return;\n }\n\n listener();\n };\n\n window.addEventListener('popstate', onPopState);\n\n return () => window.removeEventListener('popstate', onPopState);\n },\n };\n\n return history;\n}\n"],"mappings":"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"}
@@ -40,55 +40,63 @@ function useScrollToTop(ref) {
40
40
  const navigation = (0, _core.useNavigation)();
41
41
  const route = (0, _core.useRoute)();
42
42
  React.useEffect(() => {
43
- let current = navigation; // The screen might be inside another navigator such as stack nested in tabs
44
- // We need to find the closest tab navigator and add the listener there
43
+ let tabNavigations = [];
44
+ let currentNavigation = navigation; // If the screen is nested inside multiple tab navigators, we should scroll to top for any of them
45
+ // So we need to find all the parent tab navigators and add the listeners there
45
46
 
46
- while (current && current.getState().type !== 'tab') {
47
- current = current.getParent();
47
+ while (currentNavigation) {
48
+ if (currentNavigation.getState().type === 'tab') {
49
+ tabNavigations.push(currentNavigation);
50
+ }
51
+
52
+ currentNavigation = currentNavigation.getParent();
48
53
  }
49
54
 
50
- if (!current) {
55
+ if (tabNavigations.length === 0) {
51
56
  return;
52
57
  }
53
58
 
54
- const unsubscribe = current.addListener( // We don't wanna import tab types here to avoid extra deps
55
- // in addition, there are multiple tab implementations
56
- // @ts-expect-error
57
- 'tabPress', e => {
58
- // We should scroll to top only when the screen is focused
59
- const isFocused = navigation.isFocused(); // In a nested stack navigator, tab press resets the stack to first screen
60
- // So we should scroll to top only when we are on first screen
59
+ const unsubscribers = tabNavigations.map(tab => {
60
+ return tab.addListener( // We don't wanna import tab types here to avoid extra deps
61
+ // in addition, there are multiple tab implementations
62
+ // @ts-expect-error
63
+ 'tabPress', e => {
64
+ // We should scroll to top only when the screen is focused
65
+ const isFocused = navigation.isFocused(); // In a nested stack navigator, tab press resets the stack to first screen
66
+ // So we should scroll to top only when we are on first screen
61
67
 
62
- const isFirst = navigation === current || navigation.getState().routes[0].key === route.key; // Run the operation in the next frame so we're sure all listeners have been run
63
- // This is necessary to know if preventDefault() has been called
68
+ const isFirst = tabNavigations.includes(navigation) || navigation.getState().routes[0].key === route.key; // Run the operation in the next frame so we're sure all listeners have been run
69
+ // This is necessary to know if preventDefault() has been called
64
70
 
65
- requestAnimationFrame(() => {
66
- const scrollable = getScrollableNode(ref);
71
+ requestAnimationFrame(() => {
72
+ const scrollable = getScrollableNode(ref);
67
73
 
68
- if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
69
- if ('scrollToTop' in scrollable) {
70
- scrollable.scrollToTop();
71
- } else if ('scrollTo' in scrollable) {
72
- scrollable.scrollTo({
73
- x: 0,
74
- y: 0,
75
- animated: true
76
- });
77
- } else if ('scrollToOffset' in scrollable) {
78
- scrollable.scrollToOffset({
79
- offset: 0,
80
- animated: true
81
- });
82
- } else if ('scrollResponderScrollTo' in scrollable) {
83
- scrollable.scrollResponderScrollTo({
84
- y: 0,
85
- animated: true
86
- });
74
+ if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
75
+ if ('scrollToTop' in scrollable) {
76
+ scrollable.scrollToTop();
77
+ } else if ('scrollTo' in scrollable) {
78
+ scrollable.scrollTo({
79
+ y: 0,
80
+ animated: true
81
+ });
82
+ } else if ('scrollToOffset' in scrollable) {
83
+ scrollable.scrollToOffset({
84
+ offset: 0,
85
+ animated: true
86
+ });
87
+ } else if ('scrollResponderScrollTo' in scrollable) {
88
+ scrollable.scrollResponderScrollTo({
89
+ y: 0,
90
+ animated: true
91
+ });
92
+ }
87
93
  }
88
- }
94
+ });
89
95
  });
90
96
  });
91
- return unsubscribe;
97
+ return () => {
98
+ unsubscribers.forEach(unsubscribe => unsubscribe());
99
+ };
92
100
  }, [navigation, ref, route.key]);
93
101
  }
94
102
  //# sourceMappingURL=useScrollToTop.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["getScrollableNode","ref","current","getScrollResponder","getNode","useScrollToTop","navigation","useNavigation","route","useRoute","React","useEffect","getState","type","getParent","unsubscribe","addListener","e","isFocused","isFirst","routes","key","requestAnimationFrame","scrollable","defaultPrevented","scrollToTop","scrollTo","x","y","animated","scrollToOffset","offset","scrollResponderScrollTo"],"sources":["useScrollToTop.tsx"],"sourcesContent":["import { EventArg, useNavigation, useRoute } from '@react-navigation/core';\nimport * as React from 'react';\n\ntype ScrollOptions = { x?: number; y?: number; animated?: boolean };\n\ntype ScrollableView =\n | { scrollToTop(): void }\n | { scrollTo(options: ScrollOptions): void }\n | { scrollToOffset(options: { offset?: number; animated?: boolean }): void }\n | { scrollResponderScrollTo(options: ScrollOptions): void };\n\ntype ScrollableWrapper =\n | { getScrollResponder(): React.ReactNode }\n | { getNode(): ScrollableView }\n | ScrollableView;\n\nfunction getScrollableNode(ref: React.RefObject<ScrollableWrapper>) {\n if (ref.current == null) {\n return null;\n }\n\n if (\n 'scrollToTop' in ref.current ||\n 'scrollTo' in ref.current ||\n 'scrollToOffset' in ref.current ||\n 'scrollResponderScrollTo' in ref.current\n ) {\n // This is already a scrollable node.\n return ref.current;\n } else if ('getScrollResponder' in ref.current) {\n // If the view is a wrapper like FlatList, SectionList etc.\n // We need to use `getScrollResponder` to get access to the scroll responder\n return ref.current.getScrollResponder();\n } else if ('getNode' in ref.current) {\n // When a `ScrollView` is wraped in `Animated.createAnimatedComponent`\n // we need to use `getNode` to get the ref to the actual scrollview.\n // Note that `getNode` is deprecated in newer versions of react-native\n // this is why we check if we already have a scrollable node above.\n return ref.current.getNode();\n } else {\n return ref.current;\n }\n}\n\nexport default function useScrollToTop(\n ref: React.RefObject<ScrollableWrapper>\n) {\n const navigation = useNavigation();\n const route = useRoute();\n\n React.useEffect(() => {\n let current = navigation;\n\n // The screen might be inside another navigator such as stack nested in tabs\n // We need to find the closest tab navigator and add the listener there\n while (current && current.getState().type !== 'tab') {\n current = current.getParent();\n }\n\n if (!current) {\n return;\n }\n\n const unsubscribe = current.addListener(\n // We don't wanna import tab types here to avoid extra deps\n // in addition, there are multiple tab implementations\n // @ts-expect-error\n 'tabPress',\n (e: EventArg<'tabPress', true>) => {\n // We should scroll to top only when the screen is focused\n const isFocused = navigation.isFocused();\n\n // In a nested stack navigator, tab press resets the stack to first screen\n // So we should scroll to top only when we are on first screen\n const isFirst =\n navigation === current ||\n navigation.getState().routes[0].key === route.key;\n\n // Run the operation in the next frame so we're sure all listeners have been run\n // This is necessary to know if preventDefault() has been called\n requestAnimationFrame(() => {\n const scrollable = getScrollableNode(ref) as ScrollableWrapper;\n\n if (isFocused && isFirst && scrollable && !e.defaultPrevented) {\n if ('scrollToTop' in scrollable) {\n scrollable.scrollToTop();\n } else if ('scrollTo' in scrollable) {\n scrollable.scrollTo({ x: 0, y: 0, animated: true });\n } else if ('scrollToOffset' in scrollable) {\n scrollable.scrollToOffset({ offset: 0, animated: true });\n } else if ('scrollResponderScrollTo' in scrollable) {\n scrollable.scrollResponderScrollTo({ y: 0, animated: true });\n }\n }\n });\n }\n );\n\n return unsubscribe;\n }, [navigation, ref, route.key]);\n}\n"],"mappings":";;;;;;;AAAA;;AACA;;;;;;AAeA,SAASA,iBAAT,CAA2BC,GAA3B,EAAoE;EAClE,IAAIA,GAAG,CAACC,OAAJ,IAAe,IAAnB,EAAyB;IACvB,OAAO,IAAP;EACD;;EAED,IACE,iBAAiBD,GAAG,CAACC,OAArB,IACA,cAAcD,GAAG,CAACC,OADlB,IAEA,oBAAoBD,GAAG,CAACC,OAFxB,IAGA,6BAA6BD,GAAG,CAACC,OAJnC,EAKE;IACA;IACA,OAAOD,GAAG,CAACC,OAAX;EACD,CARD,MAQO,IAAI,wBAAwBD,GAAG,CAACC,OAAhC,EAAyC;IAC9C;IACA;IACA,OAAOD,GAAG,CAACC,OAAJ,CAAYC,kBAAZ,EAAP;EACD,CAJM,MAIA,IAAI,aAAaF,GAAG,CAACC,OAArB,EAA8B;IACnC;IACA;IACA;IACA;IACA,OAAOD,GAAG,CAACC,OAAJ,CAAYE,OAAZ,EAAP;EACD,CANM,MAMA;IACL,OAAOH,GAAG,CAACC,OAAX;EACD;AACF;;AAEc,SAASG,cAAT,CACbJ,GADa,EAEb;EACA,MAAMK,UAAU,GAAG,IAAAC,mBAAA,GAAnB;EACA,MAAMC,KAAK,GAAG,IAAAC,cAAA,GAAd;EAEAC,KAAK,CAACC,SAAN,CAAgB,MAAM;IACpB,IAAIT,OAAO,GAAGI,UAAd,CADoB,CAGpB;IACA;;IACA,OAAOJ,OAAO,IAAIA,OAAO,CAACU,QAAR,GAAmBC,IAAnB,KAA4B,KAA9C,EAAqD;MACnDX,OAAO,GAAGA,OAAO,CAACY,SAAR,EAAV;IACD;;IAED,IAAI,CAACZ,OAAL,EAAc;MACZ;IACD;;IAED,MAAMa,WAAW,GAAGb,OAAO,CAACc,WAAR,EAClB;IACA;IACA;IACA,UAJkB,EAKjBC,CAAD,IAAmC;MACjC;MACA,MAAMC,SAAS,GAAGZ,UAAU,CAACY,SAAX,EAAlB,CAFiC,CAIjC;MACA;;MACA,MAAMC,OAAO,GACXb,UAAU,KAAKJ,OAAf,IACAI,UAAU,CAACM,QAAX,GAAsBQ,MAAtB,CAA6B,CAA7B,EAAgCC,GAAhC,KAAwCb,KAAK,CAACa,GAFhD,CANiC,CAUjC;MACA;;MACAC,qBAAqB,CAAC,MAAM;QAC1B,MAAMC,UAAU,GAAGvB,iBAAiB,CAACC,GAAD,CAApC;;QAEA,IAAIiB,SAAS,IAAIC,OAAb,IAAwBI,UAAxB,IAAsC,CAACN,CAAC,CAACO,gBAA7C,EAA+D;UAC7D,IAAI,iBAAiBD,UAArB,EAAiC;YAC/BA,UAAU,CAACE,WAAX;UACD,CAFD,MAEO,IAAI,cAAcF,UAAlB,EAA8B;YACnCA,UAAU,CAACG,QAAX,CAAoB;cAAEC,CAAC,EAAE,CAAL;cAAQC,CAAC,EAAE,CAAX;cAAcC,QAAQ,EAAE;YAAxB,CAApB;UACD,CAFM,MAEA,IAAI,oBAAoBN,UAAxB,EAAoC;YACzCA,UAAU,CAACO,cAAX,CAA0B;cAAEC,MAAM,EAAE,CAAV;cAAaF,QAAQ,EAAE;YAAvB,CAA1B;UACD,CAFM,MAEA,IAAI,6BAA6BN,UAAjC,EAA6C;YAClDA,UAAU,CAACS,uBAAX,CAAmC;cAAEJ,CAAC,EAAE,CAAL;cAAQC,QAAQ,EAAE;YAAlB,CAAnC;UACD;QACF;MACF,CAdoB,CAArB;IAeD,CAhCiB,CAApB;IAmCA,OAAOd,WAAP;EACD,CAjDD,EAiDG,CAACT,UAAD,EAAaL,GAAb,EAAkBO,KAAK,CAACa,GAAxB,CAjDH;AAkDD"}
1
+ {"version":3,"names":["getScrollableNode","ref","current","getScrollResponder","getNode","useScrollToTop","navigation","useNavigation","route","useRoute","React","useEffect","tabNavigations","currentNavigation","getState","type","push","getParent","length","unsubscribers","map","tab","addListener","e","isFocused","isFirst","includes","routes","key","requestAnimationFrame","scrollable","defaultPrevented","scrollToTop","scrollTo","y","animated","scrollToOffset","offset","scrollResponderScrollTo","forEach","unsubscribe"],"sources":["useScrollToTop.tsx"],"sourcesContent":["import { EventArg, NavigationProp, useNavigation, useRoute } from '@react-navigation/core';\nimport * as React from 'react';\n\ntype ScrollOptions = { x?: number; y?: number; animated?: boolean };\n\ntype ScrollableView =\n | { scrollToTop(): void }\n | { scrollTo(options: ScrollOptions): void }\n | { scrollToOffset(options: { offset?: number; animated?: boolean }): void }\n | { scrollResponderScrollTo(options: ScrollOptions): void };\n\ntype ScrollableWrapper =\n | { getScrollResponder(): React.ReactNode }\n | { getNode(): ScrollableView }\n | ScrollableView;\n\nfunction getScrollableNode(ref: React.RefObject<ScrollableWrapper>) {\n if (ref.current == null) {\n return null;\n }\n\n if (\n 'scrollToTop' in ref.current ||\n 'scrollTo' in ref.current ||\n 'scrollToOffset' in ref.current ||\n 'scrollResponderScrollTo' in ref.current\n ) {\n // This is already a scrollable node.\n return ref.current;\n } else if ('getScrollResponder' in ref.current) {\n // If the view is a wrapper like FlatList, SectionList etc.\n // We need to use `getScrollResponder` to get access to the scroll responder\n return ref.current.getScrollResponder();\n } else if ('getNode' in ref.current) {\n // When a `ScrollView` is wraped in `Animated.createAnimatedComponent`\n // we need to use `getNode` to get the ref to the actual scrollview.\n // Note that `getNode` is deprecated in newer versions of react-native\n // this is why we check if we already have a scrollable node above.\n return ref.current.getNode();\n } else {\n return ref.current;\n }\n}\n\nexport default function useScrollToTop(\n ref: React.RefObject<ScrollableWrapper>\n) {\n const navigation = useNavigation();\n const route = useRoute();\n\n React.useEffect(() => {\n let tabNavigations: NavigationProp<ReactNavigation.RootParamList>[] = [];\n let currentNavigation = navigation;\n\n // If the screen is nested inside multiple tab navigators, we should scroll to top for any of them\n // So we need to find all the parent tab navigators and add the listeners there\n while (currentNavigation) {\n if (currentNavigation.getState().type === 'tab') {\n tabNavigations.push(currentNavigation);\n }\n\n currentNavigation = currentNavigation.getParent();\n }\n\n if (tabNavigations.length === 0) {\n return;\n }\n\n const unsubscribers = tabNavigations.map((tab) => {\n return tab.addListener(\n // We don't wanna import tab types here to avoid extra deps\n // in addition, there are multiple tab implementations\n // @ts-expect-error\n 'tabPress',\n (e: EventArg<'tabPress', true>) => {\n // We should scroll to top only when the screen is focused\n const isFocused = navigation.isFocused();\n\n // In a nested stack navigator, tab press resets the stack to first screen\n // So we should scroll to top only when we are on first screen\n const isFirst =\n tabNavigations.includes(navigation) ||\n navigation.getState().routes[0].key === route.key;\n\n // Run the operation in the next frame so we're sure all listeners have been run\n // This is necessary to know if preventDefault() has been called\n requestAnimationFrame(() => {\n const scrollable = getScrollableNode(ref) as ScrollableWrapper;\n\n if (isFocused && isFirst && scrollable && !e.defaultPrevented) {\n if ('scrollToTop' in scrollable) {\n scrollable.scrollToTop();\n } else if ('scrollTo' in scrollable) {\n scrollable.scrollTo({ y: 0, animated: true });\n } else if ('scrollToOffset' in scrollable) {\n scrollable.scrollToOffset({ offset: 0, animated: true });\n } else if ('scrollResponderScrollTo' in scrollable) {\n scrollable.scrollResponderScrollTo({ y: 0, animated: true });\n }\n }\n });\n }\n );\n });\n\n return () => {\n unsubscribers.forEach((unsubscribe) => unsubscribe());\n };\n }, [navigation, ref, route.key]);\n}\n"],"mappings":"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"}
@@ -87,7 +87,10 @@ export default function createMemoryHistory() {
87
87
  state
88
88
  } = _ref3;
89
89
  interrupt();
90
- const id = (_window$history$state2 = (_window$history$state3 = window.history.state) === null || _window$history$state3 === void 0 ? void 0 : _window$history$state3.id) !== null && _window$history$state2 !== void 0 ? _window$history$state2 : nanoid();
90
+ const id = (_window$history$state2 = (_window$history$state3 = window.history.state) === null || _window$history$state3 === void 0 ? void 0 : _window$history$state3.id) !== null && _window$history$state2 !== void 0 ? _window$history$state2 : nanoid(); // Need to keep the hash part of the path if there was no previous history entry
91
+ // or the previous history entry had the same path
92
+
93
+ let pathWithHash = path;
91
94
 
92
95
  if (!items.length || items.findIndex(item => item.id === id) < 0) {
93
96
  // There are two scenarios for creating an array with only one history record:
@@ -96,13 +99,18 @@ export default function createMemoryHistory() {
96
99
  // the page when navigating forward in history.
97
100
  // - This is the first time any state modifications are done
98
101
  // So we need to push the entry as there's nothing to replace
102
+ pathWithHash = pathWithHash + location.hash;
99
103
  items = [{
100
- path,
104
+ path: pathWithHash,
101
105
  state,
102
106
  id
103
107
  }];
104
108
  index = 0;
105
109
  } else {
110
+ if (items[index].path === path) {
111
+ pathWithHash = pathWithHash + location.hash;
112
+ }
113
+
106
114
  items[index] = {
107
115
  path,
108
116
  state,
@@ -112,7 +120,7 @@ export default function createMemoryHistory() {
112
120
 
113
121
  window.history.replaceState({
114
122
  id
115
- }, '', path);
123
+ }, '', pathWithHash);
116
124
  },
117
125
 
118
126
  // `history.go(n)` is asynchronous, there are couple of things to keep in mind:
@@ -1 +1 @@
1
- {"version":3,"names":["nanoid","createMemoryHistory","index","items","pending","interrupt","forEach","it","cb","history","id","window","state","findIndex","item","get","backIndex","path","i","push","slice","length","pushState","replace","replaceState","go","n","nextIndex","lastItemIndex","Promise","resolve","reject","done","interrupted","clearTimeout","timer","Error","title","document","ref","setTimeout","splice","onPopState","currentIndex","Math","max","last","pop","removeEventListener","addEventListener","listen","listener"],"sources":["createMemoryHistory.tsx"],"sourcesContent":["import type { NavigationState } from '@react-navigation/core';\nimport { nanoid } from 'nanoid/non-secure';\n\ntype HistoryRecord = {\n // Unique identifier for this record to match it with window.history.state\n id: string;\n // Navigation state object for the history entry\n state: NavigationState;\n // Path of the history entry\n path: string;\n};\n\nexport default function createMemoryHistory() {\n let index = 0;\n let items: HistoryRecord[] = [];\n\n // Pending callbacks for `history.go(n)`\n // We might modify the callback stored if it was interrupted, so we have a ref to identify it\n const pending: { ref: unknown; cb: (interrupted?: boolean) => void }[] = [];\n\n const interrupt = () => {\n // If another history operation was performed we need to interrupt existing ones\n // This makes sure that calls such as `history.replace` after `history.go` don't happen\n // Since otherwise it won't be correct if something else has changed\n pending.forEach((it) => {\n const cb = it.cb;\n it.cb = () => cb(true);\n });\n };\n\n const history = {\n get index(): number {\n // We store an id in the state instead of an index\n // Index could get out of sync with in-memory values if page reloads\n const id = window.history.state?.id;\n\n if (id) {\n const index = items.findIndex((item) => item.id === id);\n\n return index > -1 ? index : 0;\n }\n\n return 0;\n },\n\n get(index: number) {\n return items[index];\n },\n\n backIndex({ path }: { path: string }) {\n // We need to find the index from the element before current to get closest path to go back to\n for (let i = index - 1; i >= 0; i--) {\n const item = items[i];\n\n if (item.path === path) {\n return i;\n }\n }\n\n return -1;\n },\n\n push({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = nanoid();\n\n // When a new entry is pushed, all the existing entries after index will be inaccessible\n // So we remove any existing entries after the current index to clean them up\n items = items.slice(0, index + 1);\n\n items.push({ path, state, id });\n index = items.length - 1;\n\n // We pass empty string for title because it's ignored in all browsers except safari\n // We don't store state object in history.state because:\n // - browsers have limits on how big it can be, and we don't control the size\n // - while not recommended, there could be non-serializable data in state\n window.history.pushState({ id }, '', path);\n },\n\n replace({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = window.history.state?.id ?? nanoid();\n\n if (!items.length || items.findIndex((item) => item.id === id) < 0) {\n // There are two scenarios for creating an array with only one history record:\n // - When loaded id not found in the items array, this function by default will replace\n // the first item. We need to keep only the new updated object, otherwise it will break\n // the page when navigating forward in history.\n // - This is the first time any state modifications are done\n // So we need to push the entry as there's nothing to replace\n items = [{ path, state, id }];\n index = 0;\n } else {\n items[index] = { path, state, id };\n }\n\n window.history.replaceState({ id }, '', path);\n },\n\n // `history.go(n)` is asynchronous, there are couple of things to keep in mind:\n // - it won't do anything if we can't go `n` steps, the `popstate` event won't fire.\n // - each `history.go(n)` call will trigger a separate `popstate` event with correct location.\n // - the `popstate` event fires before the next frame after calling `history.go(n)`.\n // This method differs from `history.go(n)` in the sense that it'll go back as many steps it can.\n go(n: number) {\n interrupt();\n\n // To guard against unexpected navigation out of the app we will assume that browser history is only as deep as the length of our memory\n // history. If we don't have an item to navigate to then update our index and navigate as far as we can without taking the user out of the app.\n const nextIndex = index + n;\n const lastItemIndex = items.length - 1;\n if (n < 0 && !items[nextIndex]) {\n // Attempted to navigate beyond the first index. Negating the current index will align the browser history with the first item.\n n = -index;\n index = 0;\n } else if (n > 0 && nextIndex > lastItemIndex) {\n // Attempted to navigate past the last index. Calculate how many indices away from the last index and go there.\n n = lastItemIndex - index;\n index = lastItemIndex;\n } else {\n index = nextIndex;\n }\n\n if (n === 0) {\n return;\n }\n\n // When we call `history.go`, `popstate` will fire when there's history to go back to\n // So we need to somehow handle following cases:\n // - There's history to go back, `history.go` is called, and `popstate` fires\n // - `history.go` is called multiple times, we need to resolve on respective `popstate`\n // - No history to go back, but `history.go` was called, browser has no API to detect it\n return new Promise<void>((resolve, reject) => {\n const done = (interrupted?: boolean) => {\n clearTimeout(timer);\n\n if (interrupted) {\n reject(new Error('History was changed during navigation.'));\n return;\n }\n\n // There seems to be a bug in Chrome regarding updating the title\n // If we set a title just before calling `history.go`, the title gets lost\n // However the value of `document.title` is still what we set it to\n // It's just not displayed in the tab bar\n // To update the tab bar, we need to reset the title to something else first (e.g. '')\n // And set the title to what it was before so it gets applied\n // It won't work without setting it to empty string coz otherwise title isn't changing\n // Which means that the browser won't do anything after setting the title\n const { title } = window.document;\n\n window.document.title = '';\n window.document.title = title;\n\n resolve();\n };\n\n pending.push({ ref: done, cb: done });\n\n // If navigation didn't happen within 100ms, assume that it won't happen\n // This may not be accurate, but hopefully it won't take so much time\n // In Chrome, navigation seems to happen instantly in next microtask\n // But on Firefox, it seems to take much longer, around 50ms from our testing\n // We're using a hacky timeout since there doesn't seem to be way to know for sure\n const timer = setTimeout(() => {\n const index = pending.findIndex((it) => it.ref === done);\n\n if (index > -1) {\n pending[index].cb();\n pending.splice(index, 1);\n }\n }, 100);\n\n const onPopState = () => {\n const id = window.history.state?.id;\n const currentIndex = items.findIndex((item) => item.id === id);\n\n // Fix createMemoryHistory.index variable's value\n // as it may go out of sync when navigating in the browser.\n index = Math.max(currentIndex, 0);\n\n const last = pending.pop();\n\n window.removeEventListener('popstate', onPopState);\n last?.cb();\n };\n\n window.addEventListener('popstate', onPopState);\n window.history.go(n);\n });\n },\n\n // The `popstate` event is triggered when history changes, except `pushState` and `replaceState`\n // If we call `history.go(n)` ourselves, we don't want it to trigger the listener\n // Here we normalize it so that only external changes (e.g. user pressing back/forward) trigger the listener\n listen(listener: () => void) {\n const onPopState = () => {\n if (pending.length) {\n // This was triggered by `history.go(n)`, we shouldn't call the listener\n return;\n }\n\n listener();\n };\n\n window.addEventListener('popstate', onPopState);\n\n return () => window.removeEventListener('popstate', onPopState);\n },\n };\n\n return history;\n}\n"],"mappings":"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"}
1
+ {"version":3,"names":["nanoid","createMemoryHistory","index","items","pending","interrupt","forEach","it","cb","history","id","window","state","findIndex","item","get","backIndex","path","i","push","slice","length","pushState","replace","pathWithHash","location","hash","replaceState","go","n","nextIndex","lastItemIndex","Promise","resolve","reject","done","interrupted","clearTimeout","timer","Error","title","document","ref","setTimeout","splice","onPopState","currentIndex","Math","max","last","pop","removeEventListener","addEventListener","listen","listener"],"sources":["createMemoryHistory.tsx"],"sourcesContent":["import type { NavigationState } from '@react-navigation/core';\nimport { nanoid } from 'nanoid/non-secure';\n\ntype HistoryRecord = {\n // Unique identifier for this record to match it with window.history.state\n id: string;\n // Navigation state object for the history entry\n state: NavigationState;\n // Path of the history entry\n path: string;\n};\n\nexport default function createMemoryHistory() {\n let index = 0;\n let items: HistoryRecord[] = [];\n\n // Pending callbacks for `history.go(n)`\n // We might modify the callback stored if it was interrupted, so we have a ref to identify it\n const pending: { ref: unknown; cb: (interrupted?: boolean) => void }[] = [];\n\n const interrupt = () => {\n // If another history operation was performed we need to interrupt existing ones\n // This makes sure that calls such as `history.replace` after `history.go` don't happen\n // Since otherwise it won't be correct if something else has changed\n pending.forEach((it) => {\n const cb = it.cb;\n it.cb = () => cb(true);\n });\n };\n\n const history = {\n get index(): number {\n // We store an id in the state instead of an index\n // Index could get out of sync with in-memory values if page reloads\n const id = window.history.state?.id;\n\n if (id) {\n const index = items.findIndex((item) => item.id === id);\n\n return index > -1 ? index : 0;\n }\n\n return 0;\n },\n\n get(index: number) {\n return items[index];\n },\n\n backIndex({ path }: { path: string }) {\n // We need to find the index from the element before current to get closest path to go back to\n for (let i = index - 1; i >= 0; i--) {\n const item = items[i];\n\n if (item.path === path) {\n return i;\n }\n }\n\n return -1;\n },\n\n push({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = nanoid();\n\n // When a new entry is pushed, all the existing entries after index will be inaccessible\n // So we remove any existing entries after the current index to clean them up\n items = items.slice(0, index + 1);\n\n items.push({ path, state, id });\n index = items.length - 1;\n\n // We pass empty string for title because it's ignored in all browsers except safari\n // We don't store state object in history.state because:\n // - browsers have limits on how big it can be, and we don't control the size\n // - while not recommended, there could be non-serializable data in state\n window.history.pushState({ id }, '', path);\n },\n\n replace({ path, state }: { path: string; state: NavigationState }) {\n interrupt();\n\n const id = window.history.state?.id ?? nanoid();\n\n // Need to keep the hash part of the path if there was no previous history entry\n // or the previous history entry had the same path\n let pathWithHash = path;\n\n if (!items.length || items.findIndex((item) => item.id === id) < 0) {\n // There are two scenarios for creating an array with only one history record:\n // - When loaded id not found in the items array, this function by default will replace\n // the first item. We need to keep only the new updated object, otherwise it will break\n // the page when navigating forward in history.\n // - This is the first time any state modifications are done\n // So we need to push the entry as there's nothing to replace\n pathWithHash = pathWithHash + location.hash;\n items = [{ path: pathWithHash, state, id }];\n index = 0;\n } else {\n if (items[index].path === path) {\n pathWithHash = pathWithHash + location.hash;\n }\n items[index] = { path, state, id };\n }\n\n window.history.replaceState({ id }, '', pathWithHash);\n },\n\n // `history.go(n)` is asynchronous, there are couple of things to keep in mind:\n // - it won't do anything if we can't go `n` steps, the `popstate` event won't fire.\n // - each `history.go(n)` call will trigger a separate `popstate` event with correct location.\n // - the `popstate` event fires before the next frame after calling `history.go(n)`.\n // This method differs from `history.go(n)` in the sense that it'll go back as many steps it can.\n go(n: number) {\n interrupt();\n\n // To guard against unexpected navigation out of the app we will assume that browser history is only as deep as the length of our memory\n // history. If we don't have an item to navigate to then update our index and navigate as far as we can without taking the user out of the app.\n const nextIndex = index + n;\n const lastItemIndex = items.length - 1;\n if (n < 0 && !items[nextIndex]) {\n // Attempted to navigate beyond the first index. Negating the current index will align the browser history with the first item.\n n = -index;\n index = 0;\n } else if (n > 0 && nextIndex > lastItemIndex) {\n // Attempted to navigate past the last index. Calculate how many indices away from the last index and go there.\n n = lastItemIndex - index;\n index = lastItemIndex;\n } else {\n index = nextIndex;\n }\n\n if (n === 0) {\n return;\n }\n\n // When we call `history.go`, `popstate` will fire when there's history to go back to\n // So we need to somehow handle following cases:\n // - There's history to go back, `history.go` is called, and `popstate` fires\n // - `history.go` is called multiple times, we need to resolve on respective `popstate`\n // - No history to go back, but `history.go` was called, browser has no API to detect it\n return new Promise<void>((resolve, reject) => {\n const done = (interrupted?: boolean) => {\n clearTimeout(timer);\n\n if (interrupted) {\n reject(new Error('History was changed during navigation.'));\n return;\n }\n\n // There seems to be a bug in Chrome regarding updating the title\n // If we set a title just before calling `history.go`, the title gets lost\n // However the value of `document.title` is still what we set it to\n // It's just not displayed in the tab bar\n // To update the tab bar, we need to reset the title to something else first (e.g. '')\n // And set the title to what it was before so it gets applied\n // It won't work without setting it to empty string coz otherwise title isn't changing\n // Which means that the browser won't do anything after setting the title\n const { title } = window.document;\n\n window.document.title = '';\n window.document.title = title;\n\n resolve();\n };\n\n pending.push({ ref: done, cb: done });\n\n // If navigation didn't happen within 100ms, assume that it won't happen\n // This may not be accurate, but hopefully it won't take so much time\n // In Chrome, navigation seems to happen instantly in next microtask\n // But on Firefox, it seems to take much longer, around 50ms from our testing\n // We're using a hacky timeout since there doesn't seem to be way to know for sure\n const timer = setTimeout(() => {\n const index = pending.findIndex((it) => it.ref === done);\n\n if (index > -1) {\n pending[index].cb();\n pending.splice(index, 1);\n }\n }, 100);\n\n const onPopState = () => {\n const id = window.history.state?.id;\n const currentIndex = items.findIndex((item) => item.id === id);\n\n // Fix createMemoryHistory.index variable's value\n // as it may go out of sync when navigating in the browser.\n index = Math.max(currentIndex, 0);\n\n const last = pending.pop();\n\n window.removeEventListener('popstate', onPopState);\n last?.cb();\n };\n\n window.addEventListener('popstate', onPopState);\n window.history.go(n);\n });\n },\n\n // The `popstate` event is triggered when history changes, except `pushState` and `replaceState`\n // If we call `history.go(n)` ourselves, we don't want it to trigger the listener\n // Here we normalize it so that only external changes (e.g. user pressing back/forward) trigger the listener\n listen(listener: () => void) {\n const onPopState = () => {\n if (pending.length) {\n // This was triggered by `history.go(n)`, we shouldn't call the listener\n return;\n }\n\n listener();\n };\n\n window.addEventListener('popstate', onPopState);\n\n return () => window.removeEventListener('popstate', onPopState);\n },\n };\n\n return history;\n}\n"],"mappings":"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"}
@@ -28,55 +28,63 @@ export default function useScrollToTop(ref) {
28
28
  const navigation = useNavigation();
29
29
  const route = useRoute();
30
30
  React.useEffect(() => {
31
- let current = navigation; // The screen might be inside another navigator such as stack nested in tabs
32
- // We need to find the closest tab navigator and add the listener there
31
+ let tabNavigations = [];
32
+ let currentNavigation = navigation; // If the screen is nested inside multiple tab navigators, we should scroll to top for any of them
33
+ // So we need to find all the parent tab navigators and add the listeners there
33
34
 
34
- while (current && current.getState().type !== 'tab') {
35
- current = current.getParent();
35
+ while (currentNavigation) {
36
+ if (currentNavigation.getState().type === 'tab') {
37
+ tabNavigations.push(currentNavigation);
38
+ }
39
+
40
+ currentNavigation = currentNavigation.getParent();
36
41
  }
37
42
 
38
- if (!current) {
43
+ if (tabNavigations.length === 0) {
39
44
  return;
40
45
  }
41
46
 
42
- const unsubscribe = current.addListener( // We don't wanna import tab types here to avoid extra deps
43
- // in addition, there are multiple tab implementations
44
- // @ts-expect-error
45
- 'tabPress', e => {
46
- // We should scroll to top only when the screen is focused
47
- const isFocused = navigation.isFocused(); // In a nested stack navigator, tab press resets the stack to first screen
48
- // So we should scroll to top only when we are on first screen
47
+ const unsubscribers = tabNavigations.map(tab => {
48
+ return tab.addListener( // We don't wanna import tab types here to avoid extra deps
49
+ // in addition, there are multiple tab implementations
50
+ // @ts-expect-error
51
+ 'tabPress', e => {
52
+ // We should scroll to top only when the screen is focused
53
+ const isFocused = navigation.isFocused(); // In a nested stack navigator, tab press resets the stack to first screen
54
+ // So we should scroll to top only when we are on first screen
49
55
 
50
- const isFirst = navigation === current || navigation.getState().routes[0].key === route.key; // Run the operation in the next frame so we're sure all listeners have been run
51
- // This is necessary to know if preventDefault() has been called
56
+ const isFirst = tabNavigations.includes(navigation) || navigation.getState().routes[0].key === route.key; // Run the operation in the next frame so we're sure all listeners have been run
57
+ // This is necessary to know if preventDefault() has been called
52
58
 
53
- requestAnimationFrame(() => {
54
- const scrollable = getScrollableNode(ref);
59
+ requestAnimationFrame(() => {
60
+ const scrollable = getScrollableNode(ref);
55
61
 
56
- if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
57
- if ('scrollToTop' in scrollable) {
58
- scrollable.scrollToTop();
59
- } else if ('scrollTo' in scrollable) {
60
- scrollable.scrollTo({
61
- x: 0,
62
- y: 0,
63
- animated: true
64
- });
65
- } else if ('scrollToOffset' in scrollable) {
66
- scrollable.scrollToOffset({
67
- offset: 0,
68
- animated: true
69
- });
70
- } else if ('scrollResponderScrollTo' in scrollable) {
71
- scrollable.scrollResponderScrollTo({
72
- y: 0,
73
- animated: true
74
- });
62
+ if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
63
+ if ('scrollToTop' in scrollable) {
64
+ scrollable.scrollToTop();
65
+ } else if ('scrollTo' in scrollable) {
66
+ scrollable.scrollTo({
67
+ y: 0,
68
+ animated: true
69
+ });
70
+ } else if ('scrollToOffset' in scrollable) {
71
+ scrollable.scrollToOffset({
72
+ offset: 0,
73
+ animated: true
74
+ });
75
+ } else if ('scrollResponderScrollTo' in scrollable) {
76
+ scrollable.scrollResponderScrollTo({
77
+ y: 0,
78
+ animated: true
79
+ });
80
+ }
75
81
  }
76
- }
82
+ });
77
83
  });
78
84
  });
79
- return unsubscribe;
85
+ return () => {
86
+ unsubscribers.forEach(unsubscribe => unsubscribe());
87
+ };
80
88
  }, [navigation, ref, route.key]);
81
89
  }
82
90
  //# sourceMappingURL=useScrollToTop.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["useNavigation","useRoute","React","getScrollableNode","ref","current","getScrollResponder","getNode","useScrollToTop","navigation","route","useEffect","getState","type","getParent","unsubscribe","addListener","e","isFocused","isFirst","routes","key","requestAnimationFrame","scrollable","defaultPrevented","scrollToTop","scrollTo","x","y","animated","scrollToOffset","offset","scrollResponderScrollTo"],"sources":["useScrollToTop.tsx"],"sourcesContent":["import { EventArg, useNavigation, useRoute } from '@react-navigation/core';\nimport * as React from 'react';\n\ntype ScrollOptions = { x?: number; y?: number; animated?: boolean };\n\ntype ScrollableView =\n | { scrollToTop(): void }\n | { scrollTo(options: ScrollOptions): void }\n | { scrollToOffset(options: { offset?: number; animated?: boolean }): void }\n | { scrollResponderScrollTo(options: ScrollOptions): void };\n\ntype ScrollableWrapper =\n | { getScrollResponder(): React.ReactNode }\n | { getNode(): ScrollableView }\n | ScrollableView;\n\nfunction getScrollableNode(ref: React.RefObject<ScrollableWrapper>) {\n if (ref.current == null) {\n return null;\n }\n\n if (\n 'scrollToTop' in ref.current ||\n 'scrollTo' in ref.current ||\n 'scrollToOffset' in ref.current ||\n 'scrollResponderScrollTo' in ref.current\n ) {\n // This is already a scrollable node.\n return ref.current;\n } else if ('getScrollResponder' in ref.current) {\n // If the view is a wrapper like FlatList, SectionList etc.\n // We need to use `getScrollResponder` to get access to the scroll responder\n return ref.current.getScrollResponder();\n } else if ('getNode' in ref.current) {\n // When a `ScrollView` is wraped in `Animated.createAnimatedComponent`\n // we need to use `getNode` to get the ref to the actual scrollview.\n // Note that `getNode` is deprecated in newer versions of react-native\n // this is why we check if we already have a scrollable node above.\n return ref.current.getNode();\n } else {\n return ref.current;\n }\n}\n\nexport default function useScrollToTop(\n ref: React.RefObject<ScrollableWrapper>\n) {\n const navigation = useNavigation();\n const route = useRoute();\n\n React.useEffect(() => {\n let current = navigation;\n\n // The screen might be inside another navigator such as stack nested in tabs\n // We need to find the closest tab navigator and add the listener there\n while (current && current.getState().type !== 'tab') {\n current = current.getParent();\n }\n\n if (!current) {\n return;\n }\n\n const unsubscribe = current.addListener(\n // We don't wanna import tab types here to avoid extra deps\n // in addition, there are multiple tab implementations\n // @ts-expect-error\n 'tabPress',\n (e: EventArg<'tabPress', true>) => {\n // We should scroll to top only when the screen is focused\n const isFocused = navigation.isFocused();\n\n // In a nested stack navigator, tab press resets the stack to first screen\n // So we should scroll to top only when we are on first screen\n const isFirst =\n navigation === current ||\n navigation.getState().routes[0].key === route.key;\n\n // Run the operation in the next frame so we're sure all listeners have been run\n // This is necessary to know if preventDefault() has been called\n requestAnimationFrame(() => {\n const scrollable = getScrollableNode(ref) as ScrollableWrapper;\n\n if (isFocused && isFirst && scrollable && !e.defaultPrevented) {\n if ('scrollToTop' in scrollable) {\n scrollable.scrollToTop();\n } else if ('scrollTo' in scrollable) {\n scrollable.scrollTo({ x: 0, y: 0, animated: true });\n } else if ('scrollToOffset' in scrollable) {\n scrollable.scrollToOffset({ offset: 0, animated: true });\n } else if ('scrollResponderScrollTo' in scrollable) {\n scrollable.scrollResponderScrollTo({ y: 0, animated: true });\n }\n }\n });\n }\n );\n\n return unsubscribe;\n }, [navigation, ref, route.key]);\n}\n"],"mappings":"AAAA,SAAmBA,aAAnB,EAAkCC,QAAlC,QAAkD,wBAAlD;AACA,OAAO,KAAKC,KAAZ,MAAuB,OAAvB;;AAeA,SAASC,iBAAT,CAA2BC,GAA3B,EAAoE;EAClE,IAAIA,GAAG,CAACC,OAAJ,IAAe,IAAnB,EAAyB;IACvB,OAAO,IAAP;EACD;;EAED,IACE,iBAAiBD,GAAG,CAACC,OAArB,IACA,cAAcD,GAAG,CAACC,OADlB,IAEA,oBAAoBD,GAAG,CAACC,OAFxB,IAGA,6BAA6BD,GAAG,CAACC,OAJnC,EAKE;IACA;IACA,OAAOD,GAAG,CAACC,OAAX;EACD,CARD,MAQO,IAAI,wBAAwBD,GAAG,CAACC,OAAhC,EAAyC;IAC9C;IACA;IACA,OAAOD,GAAG,CAACC,OAAJ,CAAYC,kBAAZ,EAAP;EACD,CAJM,MAIA,IAAI,aAAaF,GAAG,CAACC,OAArB,EAA8B;IACnC;IACA;IACA;IACA;IACA,OAAOD,GAAG,CAACC,OAAJ,CAAYE,OAAZ,EAAP;EACD,CANM,MAMA;IACL,OAAOH,GAAG,CAACC,OAAX;EACD;AACF;;AAED,eAAe,SAASG,cAAT,CACbJ,GADa,EAEb;EACA,MAAMK,UAAU,GAAGT,aAAa,EAAhC;EACA,MAAMU,KAAK,GAAGT,QAAQ,EAAtB;EAEAC,KAAK,CAACS,SAAN,CAAgB,MAAM;IACpB,IAAIN,OAAO,GAAGI,UAAd,CADoB,CAGpB;IACA;;IACA,OAAOJ,OAAO,IAAIA,OAAO,CAACO,QAAR,GAAmBC,IAAnB,KAA4B,KAA9C,EAAqD;MACnDR,OAAO,GAAGA,OAAO,CAACS,SAAR,EAAV;IACD;;IAED,IAAI,CAACT,OAAL,EAAc;MACZ;IACD;;IAED,MAAMU,WAAW,GAAGV,OAAO,CAACW,WAAR,EAClB;IACA;IACA;IACA,UAJkB,EAKjBC,CAAD,IAAmC;MACjC;MACA,MAAMC,SAAS,GAAGT,UAAU,CAACS,SAAX,EAAlB,CAFiC,CAIjC;MACA;;MACA,MAAMC,OAAO,GACXV,UAAU,KAAKJ,OAAf,IACAI,UAAU,CAACG,QAAX,GAAsBQ,MAAtB,CAA6B,CAA7B,EAAgCC,GAAhC,KAAwCX,KAAK,CAACW,GAFhD,CANiC,CAUjC;MACA;;MACAC,qBAAqB,CAAC,MAAM;QAC1B,MAAMC,UAAU,GAAGpB,iBAAiB,CAACC,GAAD,CAApC;;QAEA,IAAIc,SAAS,IAAIC,OAAb,IAAwBI,UAAxB,IAAsC,CAACN,CAAC,CAACO,gBAA7C,EAA+D;UAC7D,IAAI,iBAAiBD,UAArB,EAAiC;YAC/BA,UAAU,CAACE,WAAX;UACD,CAFD,MAEO,IAAI,cAAcF,UAAlB,EAA8B;YACnCA,UAAU,CAACG,QAAX,CAAoB;cAAEC,CAAC,EAAE,CAAL;cAAQC,CAAC,EAAE,CAAX;cAAcC,QAAQ,EAAE;YAAxB,CAApB;UACD,CAFM,MAEA,IAAI,oBAAoBN,UAAxB,EAAoC;YACzCA,UAAU,CAACO,cAAX,CAA0B;cAAEC,MAAM,EAAE,CAAV;cAAaF,QAAQ,EAAE;YAAvB,CAA1B;UACD,CAFM,MAEA,IAAI,6BAA6BN,UAAjC,EAA6C;YAClDA,UAAU,CAACS,uBAAX,CAAmC;cAAEJ,CAAC,EAAE,CAAL;cAAQC,QAAQ,EAAE;YAAlB,CAAnC;UACD;QACF;MACF,CAdoB,CAArB;IAeD,CAhCiB,CAApB;IAmCA,OAAOd,WAAP;EACD,CAjDD,EAiDG,CAACN,UAAD,EAAaL,GAAb,EAAkBM,KAAK,CAACW,GAAxB,CAjDH;AAkDD"}
1
+ {"version":3,"names":["useNavigation","useRoute","React","getScrollableNode","ref","current","getScrollResponder","getNode","useScrollToTop","navigation","route","useEffect","tabNavigations","currentNavigation","getState","type","push","getParent","length","unsubscribers","map","tab","addListener","e","isFocused","isFirst","includes","routes","key","requestAnimationFrame","scrollable","defaultPrevented","scrollToTop","scrollTo","y","animated","scrollToOffset","offset","scrollResponderScrollTo","forEach","unsubscribe"],"sources":["useScrollToTop.tsx"],"sourcesContent":["import { EventArg, NavigationProp, useNavigation, useRoute } from '@react-navigation/core';\nimport * as React from 'react';\n\ntype ScrollOptions = { x?: number; y?: number; animated?: boolean };\n\ntype ScrollableView =\n | { scrollToTop(): void }\n | { scrollTo(options: ScrollOptions): void }\n | { scrollToOffset(options: { offset?: number; animated?: boolean }): void }\n | { scrollResponderScrollTo(options: ScrollOptions): void };\n\ntype ScrollableWrapper =\n | { getScrollResponder(): React.ReactNode }\n | { getNode(): ScrollableView }\n | ScrollableView;\n\nfunction getScrollableNode(ref: React.RefObject<ScrollableWrapper>) {\n if (ref.current == null) {\n return null;\n }\n\n if (\n 'scrollToTop' in ref.current ||\n 'scrollTo' in ref.current ||\n 'scrollToOffset' in ref.current ||\n 'scrollResponderScrollTo' in ref.current\n ) {\n // This is already a scrollable node.\n return ref.current;\n } else if ('getScrollResponder' in ref.current) {\n // If the view is a wrapper like FlatList, SectionList etc.\n // We need to use `getScrollResponder` to get access to the scroll responder\n return ref.current.getScrollResponder();\n } else if ('getNode' in ref.current) {\n // When a `ScrollView` is wraped in `Animated.createAnimatedComponent`\n // we need to use `getNode` to get the ref to the actual scrollview.\n // Note that `getNode` is deprecated in newer versions of react-native\n // this is why we check if we already have a scrollable node above.\n return ref.current.getNode();\n } else {\n return ref.current;\n }\n}\n\nexport default function useScrollToTop(\n ref: React.RefObject<ScrollableWrapper>\n) {\n const navigation = useNavigation();\n const route = useRoute();\n\n React.useEffect(() => {\n let tabNavigations: NavigationProp<ReactNavigation.RootParamList>[] = [];\n let currentNavigation = navigation;\n\n // If the screen is nested inside multiple tab navigators, we should scroll to top for any of them\n // So we need to find all the parent tab navigators and add the listeners there\n while (currentNavigation) {\n if (currentNavigation.getState().type === 'tab') {\n tabNavigations.push(currentNavigation);\n }\n\n currentNavigation = currentNavigation.getParent();\n }\n\n if (tabNavigations.length === 0) {\n return;\n }\n\n const unsubscribers = tabNavigations.map((tab) => {\n return tab.addListener(\n // We don't wanna import tab types here to avoid extra deps\n // in addition, there are multiple tab implementations\n // @ts-expect-error\n 'tabPress',\n (e: EventArg<'tabPress', true>) => {\n // We should scroll to top only when the screen is focused\n const isFocused = navigation.isFocused();\n\n // In a nested stack navigator, tab press resets the stack to first screen\n // So we should scroll to top only when we are on first screen\n const isFirst =\n tabNavigations.includes(navigation) ||\n navigation.getState().routes[0].key === route.key;\n\n // Run the operation in the next frame so we're sure all listeners have been run\n // This is necessary to know if preventDefault() has been called\n requestAnimationFrame(() => {\n const scrollable = getScrollableNode(ref) as ScrollableWrapper;\n\n if (isFocused && isFirst && scrollable && !e.defaultPrevented) {\n if ('scrollToTop' in scrollable) {\n scrollable.scrollToTop();\n } else if ('scrollTo' in scrollable) {\n scrollable.scrollTo({ y: 0, animated: true });\n } else if ('scrollToOffset' in scrollable) {\n scrollable.scrollToOffset({ offset: 0, animated: true });\n } else if ('scrollResponderScrollTo' in scrollable) {\n scrollable.scrollResponderScrollTo({ y: 0, animated: true });\n }\n }\n });\n }\n );\n });\n\n return () => {\n unsubscribers.forEach((unsubscribe) => unsubscribe());\n };\n }, [navigation, ref, route.key]);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/native",
3
3
  "description": "React Native integration for React Navigation",
4
- "version": "6.0.16",
4
+ "version": "6.1.0",
5
5
  "keywords": [
6
6
  "react-native",
7
7
  "react-navigation",
@@ -37,7 +37,7 @@
37
37
  "clean": "del lib"
38
38
  },
39
39
  "dependencies": {
40
- "@react-navigation/core": "^6.4.3",
40
+ "@react-navigation/core": "^6.4.4",
41
41
  "escape-string-regexp": "^4.0.0",
42
42
  "fast-deep-equal": "^3.1.3",
43
43
  "nanoid": "^3.1.23"
@@ -72,5 +72,5 @@
72
72
  ]
73
73
  ]
74
74
  },
75
- "gitHead": "4e29403ec63d9304561e296667f6cf15806c316b"
75
+ "gitHead": "f7a9b1d102e5ff04ce3f726b934019ddbdd0550c"
76
76
  }
@@ -84,6 +84,10 @@ export default function createMemoryHistory() {
84
84
 
85
85
  const id = window.history.state?.id ?? nanoid();
86
86
 
87
+ // Need to keep the hash part of the path if there was no previous history entry
88
+ // or the previous history entry had the same path
89
+ let pathWithHash = path;
90
+
87
91
  if (!items.length || items.findIndex((item) => item.id === id) < 0) {
88
92
  // There are two scenarios for creating an array with only one history record:
89
93
  // - When loaded id not found in the items array, this function by default will replace
@@ -91,13 +95,17 @@ export default function createMemoryHistory() {
91
95
  // the page when navigating forward in history.
92
96
  // - This is the first time any state modifications are done
93
97
  // So we need to push the entry as there's nothing to replace
94
- items = [{ path, state, id }];
98
+ pathWithHash = pathWithHash + location.hash;
99
+ items = [{ path: pathWithHash, state, id }];
95
100
  index = 0;
96
101
  } else {
102
+ if (items[index].path === path) {
103
+ pathWithHash = pathWithHash + location.hash;
104
+ }
97
105
  items[index] = { path, state, id };
98
106
  }
99
107
 
100
- window.history.replaceState({ id }, '', path);
108
+ window.history.replaceState({ id }, '', pathWithHash);
101
109
  },
102
110
 
103
111
  // `history.go(n)` is asynchronous, there are couple of things to keep in mind:
@@ -1,4 +1,4 @@
1
- import { EventArg, useNavigation, useRoute } from '@react-navigation/core';
1
+ import { EventArg, NavigationProp, useNavigation, useRoute } from '@react-navigation/core';
2
2
  import * as React from 'react';
3
3
 
4
4
  type ScrollOptions = { x?: number; y?: number; animated?: boolean };
@@ -49,53 +49,62 @@ export default function useScrollToTop(
49
49
  const route = useRoute();
50
50
 
51
51
  React.useEffect(() => {
52
- let current = navigation;
52
+ let tabNavigations: NavigationProp<ReactNavigation.RootParamList>[] = [];
53
+ let currentNavigation = navigation;
53
54
 
54
- // The screen might be inside another navigator such as stack nested in tabs
55
- // We need to find the closest tab navigator and add the listener there
56
- while (current && current.getState().type !== 'tab') {
57
- current = current.getParent();
55
+ // If the screen is nested inside multiple tab navigators, we should scroll to top for any of them
56
+ // So we need to find all the parent tab navigators and add the listeners there
57
+ while (currentNavigation) {
58
+ if (currentNavigation.getState().type === 'tab') {
59
+ tabNavigations.push(currentNavigation);
60
+ }
61
+
62
+ currentNavigation = currentNavigation.getParent();
58
63
  }
59
64
 
60
- if (!current) {
65
+ if (tabNavigations.length === 0) {
61
66
  return;
62
67
  }
63
68
 
64
- const unsubscribe = current.addListener(
65
- // We don't wanna import tab types here to avoid extra deps
66
- // in addition, there are multiple tab implementations
67
- // @ts-expect-error
68
- 'tabPress',
69
- (e: EventArg<'tabPress', true>) => {
70
- // We should scroll to top only when the screen is focused
71
- const isFocused = navigation.isFocused();
69
+ const unsubscribers = tabNavigations.map((tab) => {
70
+ return tab.addListener(
71
+ // We don't wanna import tab types here to avoid extra deps
72
+ // in addition, there are multiple tab implementations
73
+ // @ts-expect-error
74
+ 'tabPress',
75
+ (e: EventArg<'tabPress', true>) => {
76
+ // We should scroll to top only when the screen is focused
77
+ const isFocused = navigation.isFocused();
72
78
 
73
- // In a nested stack navigator, tab press resets the stack to first screen
74
- // So we should scroll to top only when we are on first screen
75
- const isFirst =
76
- navigation === current ||
77
- navigation.getState().routes[0].key === route.key;
79
+ // In a nested stack navigator, tab press resets the stack to first screen
80
+ // So we should scroll to top only when we are on first screen
81
+ const isFirst =
82
+ tabNavigations.includes(navigation) ||
83
+ navigation.getState().routes[0].key === route.key;
78
84
 
79
- // Run the operation in the next frame so we're sure all listeners have been run
80
- // This is necessary to know if preventDefault() has been called
81
- requestAnimationFrame(() => {
82
- const scrollable = getScrollableNode(ref) as ScrollableWrapper;
85
+ // Run the operation in the next frame so we're sure all listeners have been run
86
+ // This is necessary to know if preventDefault() has been called
87
+ requestAnimationFrame(() => {
88
+ const scrollable = getScrollableNode(ref) as ScrollableWrapper;
83
89
 
84
- if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
85
- if ('scrollToTop' in scrollable) {
86
- scrollable.scrollToTop();
87
- } else if ('scrollTo' in scrollable) {
88
- scrollable.scrollTo({ x: 0, y: 0, animated: true });
89
- } else if ('scrollToOffset' in scrollable) {
90
- scrollable.scrollToOffset({ offset: 0, animated: true });
91
- } else if ('scrollResponderScrollTo' in scrollable) {
92
- scrollable.scrollResponderScrollTo({ y: 0, animated: true });
90
+ if (isFocused && isFirst && scrollable && !e.defaultPrevented) {
91
+ if ('scrollToTop' in scrollable) {
92
+ scrollable.scrollToTop();
93
+ } else if ('scrollTo' in scrollable) {
94
+ scrollable.scrollTo({ y: 0, animated: true });
95
+ } else if ('scrollToOffset' in scrollable) {
96
+ scrollable.scrollToOffset({ offset: 0, animated: true });
97
+ } else if ('scrollResponderScrollTo' in scrollable) {
98
+ scrollable.scrollResponderScrollTo({ y: 0, animated: true });
99
+ }
93
100
  }
94
- }
95
- });
96
- }
97
- );
101
+ });
102
+ }
103
+ );
104
+ });
98
105
 
99
- return unsubscribe;
106
+ return () => {
107
+ unsubscribers.forEach((unsubscribe) => unsubscribe());
108
+ };
100
109
  }, [navigation, ref, route.key]);
101
110
  }