@amazon-devices/react-navigation__native 2.0.1758683737

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.
Files changed (189) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +5 -0
  3. package/SystemBundles/6132afb62bda3afb78ba1a1aca51924df73e9dbcc77106aa0f97a0ef68b04ebb.bundle +15196 -0
  4. package/SystemBundles/6132afb62bda3afb78ba1a1aca51924df73e9dbcc77106aa0f97a0ef68b04ebb.map +1 -0
  5. package/SystemModules/amzn__react-navigation__native-2.modules.txt +76 -0
  6. package/SystemModules/amzn__react-navigation__native-2.private.modules.txt +101 -0
  7. package/SystemModules/keplerscript-system-bundle-config.json +7 -0
  8. package/THIRD_PARTY_NOTICES.txt +24 -0
  9. package/kepler-compatibility.json +8 -0
  10. package/kepler-transformed-package-info.json +3 -0
  11. package/lib/commonjs/Link.js +50 -0
  12. package/lib/commonjs/Link.js.map +1 -0
  13. package/lib/commonjs/LinkingContext.js +15 -0
  14. package/lib/commonjs/LinkingContext.js.map +1 -0
  15. package/lib/commonjs/NavigationContainer.js +109 -0
  16. package/lib/commonjs/NavigationContainer.js.map +1 -0
  17. package/lib/commonjs/ServerContainer.js +54 -0
  18. package/lib/commonjs/ServerContainer.js.map +1 -0
  19. package/lib/commonjs/ServerContext.js +12 -0
  20. package/lib/commonjs/ServerContext.js.map +1 -0
  21. package/lib/commonjs/createMemoryHistory.js +220 -0
  22. package/lib/commonjs/createMemoryHistory.js.map +1 -0
  23. package/lib/commonjs/extractPathFromURL.js +25 -0
  24. package/lib/commonjs/extractPathFromURL.js.map +1 -0
  25. package/lib/commonjs/index.js +129 -0
  26. package/lib/commonjs/index.js.map +1 -0
  27. package/lib/commonjs/theming/DarkTheme.js +19 -0
  28. package/lib/commonjs/theming/DarkTheme.js.map +1 -0
  29. package/lib/commonjs/theming/DefaultTheme.js +19 -0
  30. package/lib/commonjs/theming/DefaultTheme.js.map +1 -0
  31. package/lib/commonjs/theming/ThemeContext.js +15 -0
  32. package/lib/commonjs/theming/ThemeContext.js.map +1 -0
  33. package/lib/commonjs/theming/ThemeProvider.js +20 -0
  34. package/lib/commonjs/theming/ThemeProvider.js.map +1 -0
  35. package/lib/commonjs/theming/useTheme.js +16 -0
  36. package/lib/commonjs/theming/useTheme.js.map +1 -0
  37. package/lib/commonjs/types.js +6 -0
  38. package/lib/commonjs/types.js.map +1 -0
  39. package/lib/commonjs/useBackButton.js +11 -0
  40. package/lib/commonjs/useBackButton.js.map +1 -0
  41. package/lib/commonjs/useBackButton.native.js +27 -0
  42. package/lib/commonjs/useBackButton.native.js.map +1 -0
  43. package/lib/commonjs/useDocumentTitle.js +32 -0
  44. package/lib/commonjs/useDocumentTitle.js.map +1 -0
  45. package/lib/commonjs/useDocumentTitle.native.js +10 -0
  46. package/lib/commonjs/useDocumentTitle.native.js.map +1 -0
  47. package/lib/commonjs/useLinkBuilder.js +63 -0
  48. package/lib/commonjs/useLinkBuilder.js.map +1 -0
  49. package/lib/commonjs/useLinkProps.js +97 -0
  50. package/lib/commonjs/useLinkProps.js.map +1 -0
  51. package/lib/commonjs/useLinkTo.js +45 -0
  52. package/lib/commonjs/useLinkTo.js.map +1 -0
  53. package/lib/commonjs/useLinking.js +303 -0
  54. package/lib/commonjs/useLinking.js.map +1 -0
  55. package/lib/commonjs/useLinking.native.js +152 -0
  56. package/lib/commonjs/useLinking.native.js.map +1 -0
  57. package/lib/commonjs/useScrollToTop.js +95 -0
  58. package/lib/commonjs/useScrollToTop.js.map +1 -0
  59. package/lib/commonjs/useThenable.js +41 -0
  60. package/lib/commonjs/useThenable.js.map +1 -0
  61. package/lib/module/Link.js +41 -0
  62. package/lib/module/Link.js.map +1 -0
  63. package/lib/module/LinkingContext.js +7 -0
  64. package/lib/module/LinkingContext.js.map +1 -0
  65. package/lib/module/NavigationContainer.js +100 -0
  66. package/lib/module/NavigationContainer.js.map +1 -0
  67. package/lib/module/ServerContainer.js +45 -0
  68. package/lib/module/ServerContainer.js.map +1 -0
  69. package/lib/module/ServerContext.js +4 -0
  70. package/lib/module/ServerContext.js.map +1 -0
  71. package/lib/module/createMemoryHistory.js +214 -0
  72. package/lib/module/createMemoryHistory.js.map +1 -0
  73. package/lib/module/extractPathFromURL.js +18 -0
  74. package/lib/module/extractPathFromURL.js.map +1 -0
  75. package/lib/module/index.js +15 -0
  76. package/lib/module/index.js.map +1 -0
  77. package/lib/module/theming/DarkTheme.js +13 -0
  78. package/lib/module/theming/DarkTheme.js.map +1 -0
  79. package/lib/module/theming/DefaultTheme.js +13 -0
  80. package/lib/module/theming/DefaultTheme.js.map +1 -0
  81. package/lib/module/theming/ThemeContext.js +6 -0
  82. package/lib/module/theming/ThemeContext.js.map +1 -0
  83. package/lib/module/theming/ThemeProvider.js +11 -0
  84. package/lib/module/theming/ThemeProvider.js.map +1 -0
  85. package/lib/module/theming/useTheme.js +7 -0
  86. package/lib/module/theming/useTheme.js.map +1 -0
  87. package/lib/module/types.js +2 -0
  88. package/lib/module/types.js.map +1 -0
  89. package/lib/module/useBackButton.js +5 -0
  90. package/lib/module/useBackButton.js.map +1 -0
  91. package/lib/module/useBackButton.native.js +19 -0
  92. package/lib/module/useBackButton.native.js.map +1 -0
  93. package/lib/module/useDocumentTitle.js +24 -0
  94. package/lib/module/useDocumentTitle.js.map +1 -0
  95. package/lib/module/useDocumentTitle.native.js +4 -0
  96. package/lib/module/useDocumentTitle.native.js.map +1 -0
  97. package/lib/module/useLinkBuilder.js +54 -0
  98. package/lib/module/useLinkBuilder.js.map +1 -0
  99. package/lib/module/useLinkProps.js +88 -0
  100. package/lib/module/useLinkProps.js.map +1 -0
  101. package/lib/module/useLinkTo.js +36 -0
  102. package/lib/module/useLinkTo.js.map +1 -0
  103. package/lib/module/useLinking.js +292 -0
  104. package/lib/module/useLinking.js.map +1 -0
  105. package/lib/module/useLinking.native.js +143 -0
  106. package/lib/module/useLinking.native.js.map +1 -0
  107. package/lib/module/useScrollToTop.js +87 -0
  108. package/lib/module/useScrollToTop.js.map +1 -0
  109. package/lib/module/useThenable.js +33 -0
  110. package/lib/module/useThenable.js.map +1 -0
  111. package/lib/typescript/src/Link.d.ts +24 -0
  112. package/lib/typescript/src/Link.d.ts.map +1 -0
  113. package/lib/typescript/src/LinkingContext.d.ts +8 -0
  114. package/lib/typescript/src/LinkingContext.d.ts.map +1 -0
  115. package/lib/typescript/src/NavigationContainer.d.ts +26 -0
  116. package/lib/typescript/src/NavigationContainer.d.ts.map +1 -0
  117. package/lib/typescript/src/ServerContainer.d.ts +15 -0
  118. package/lib/typescript/src/ServerContainer.d.ts.map +1 -0
  119. package/lib/typescript/src/ServerContext.d.ts +10 -0
  120. package/lib/typescript/src/ServerContext.d.ts.map +1 -0
  121. package/lib/typescript/src/__mocks__/window.d.ts +19 -0
  122. package/lib/typescript/src/__mocks__/window.d.ts.map +1 -0
  123. package/lib/typescript/src/createMemoryHistory.d.ts +25 -0
  124. package/lib/typescript/src/createMemoryHistory.d.ts.map +1 -0
  125. package/lib/typescript/src/extractPathFromURL.d.ts +2 -0
  126. package/lib/typescript/src/extractPathFromURL.d.ts.map +1 -0
  127. package/lib/typescript/src/index.d.ts +15 -0
  128. package/lib/typescript/src/index.d.ts.map +1 -0
  129. package/lib/typescript/src/theming/DarkTheme.d.ts +4 -0
  130. package/lib/typescript/src/theming/DarkTheme.d.ts.map +1 -0
  131. package/lib/typescript/src/theming/DefaultTheme.d.ts +4 -0
  132. package/lib/typescript/src/theming/DefaultTheme.d.ts.map +1 -0
  133. package/lib/typescript/src/theming/ThemeContext.d.ts +5 -0
  134. package/lib/typescript/src/theming/ThemeContext.d.ts.map +1 -0
  135. package/lib/typescript/src/theming/ThemeProvider.d.ts +9 -0
  136. package/lib/typescript/src/theming/ThemeProvider.d.ts.map +1 -0
  137. package/lib/typescript/src/theming/useTheme.d.ts +2 -0
  138. package/lib/typescript/src/theming/useTheme.d.ts.map +1 -0
  139. package/lib/typescript/src/types.d.ts +127 -0
  140. package/lib/typescript/src/types.d.ts.map +1 -0
  141. package/lib/typescript/src/useBackButton.d.ts +4 -0
  142. package/lib/typescript/src/useBackButton.d.ts.map +1 -0
  143. package/lib/typescript/src/useBackButton.native.d.ts +4 -0
  144. package/lib/typescript/src/useBackButton.native.d.ts.map +1 -0
  145. package/lib/typescript/src/useDocumentTitle.d.ts +8 -0
  146. package/lib/typescript/src/useDocumentTitle.d.ts.map +1 -0
  147. package/lib/typescript/src/useDocumentTitle.native.d.ts +2 -0
  148. package/lib/typescript/src/useDocumentTitle.native.d.ts.map +1 -0
  149. package/lib/typescript/src/useLinkBuilder.d.ts +6 -0
  150. package/lib/typescript/src/useLinkBuilder.d.ts.map +1 -0
  151. package/lib/typescript/src/useLinkProps.d.ts +21 -0
  152. package/lib/typescript/src/useLinkProps.d.ts.map +1 -0
  153. package/lib/typescript/src/useLinkTo.d.ts +9 -0
  154. package/lib/typescript/src/useLinkTo.d.ts.map +1 -0
  155. package/lib/typescript/src/useLinking.d.ts +55 -0
  156. package/lib/typescript/src/useLinking.d.ts.map +1 -0
  157. package/lib/typescript/src/useLinking.native.d.ts +51 -0
  158. package/lib/typescript/src/useLinking.native.d.ts.map +1 -0
  159. package/lib/typescript/src/useScrollToTop.d.ts +27 -0
  160. package/lib/typescript/src/useScrollToTop.d.ts.map +1 -0
  161. package/lib/typescript/src/useThenable.d.ts +2 -0
  162. package/lib/typescript/src/useThenable.d.ts.map +1 -0
  163. package/package.json +1 -0
  164. package/src/Link.tsx +53 -0
  165. package/src/LinkingContext.tsx +12 -0
  166. package/src/NavigationContainer.tsx +152 -0
  167. package/src/ServerContainer.tsx +56 -0
  168. package/src/ServerContext.tsx +14 -0
  169. package/src/__mocks__/window.tsx +77 -0
  170. package/src/createMemoryHistory.tsx +224 -0
  171. package/src/extractPathFromURL.tsx +29 -0
  172. package/src/index.tsx +14 -0
  173. package/src/theming/DarkTheme.tsx +15 -0
  174. package/src/theming/DefaultTheme.tsx +15 -0
  175. package/src/theming/ThemeContext.tsx +10 -0
  176. package/src/theming/ThemeProvider.tsx +15 -0
  177. package/src/theming/useTheme.tsx +9 -0
  178. package/src/types.tsx +145 -0
  179. package/src/useBackButton.native.tsx +33 -0
  180. package/src/useBackButton.tsx +11 -0
  181. package/src/useDocumentTitle.native.tsx +3 -0
  182. package/src/useDocumentTitle.tsx +41 -0
  183. package/src/useLinkBuilder.tsx +82 -0
  184. package/src/useLinkProps.tsx +126 -0
  185. package/src/useLinkTo.tsx +71 -0
  186. package/src/useLinking.native.tsx +215 -0
  187. package/src/useLinking.tsx +395 -0
  188. package/src/useScrollToTop.tsx +116 -0
  189. package/src/useThenable.tsx +41 -0
package/src/Link.tsx ADDED
@@ -0,0 +1,53 @@
1
+ import type { NavigationAction } from '@amazon-devices/react-navigation__core';
2
+ import * as React from 'react';
3
+ import { GestureResponderEvent, Platform, Text, TextProps } from 'react-native';
4
+
5
+ import useLinkProps from './useLinkProps';
6
+ import type { To } from './useLinkTo';
7
+
8
+ type Props<ParamList extends ReactNavigation.RootParamList> = {
9
+ to: To<ParamList>;
10
+ action?: NavigationAction;
11
+ target?: string;
12
+ onPress?: (
13
+ e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
14
+ ) => void;
15
+ } & (Omit<TextProps, 'disabled'> & {
16
+ disabled?: boolean | null;
17
+ children: React.ReactNode;
18
+ });
19
+
20
+ /**
21
+ * Component to render link to another screen using a path.
22
+ * Uses an anchor tag on the web.
23
+ *
24
+ * @param props.to Absolute path to screen (e.g. `/feeds/hot`).
25
+ * @param props.action Optional action to use for in-page navigation. By default, the path is parsed to an action based on linking config.
26
+ * @param props.children Child elements to render the content.
27
+ */
28
+ export default function Link<ParamList extends ReactNavigation.RootParamList>({
29
+ to,
30
+ action,
31
+ ...rest
32
+ }: Props<ParamList>) {
33
+ const props = useLinkProps<ParamList>({ to, action });
34
+
35
+ const onPress = (
36
+ e: React.MouseEvent<HTMLAnchorElement, MouseEvent> | GestureResponderEvent
37
+ ) => {
38
+ if ('onPress' in rest) {
39
+ rest.onPress?.(e);
40
+ }
41
+
42
+ props.onPress(e);
43
+ };
44
+
45
+ return React.createElement(Text, {
46
+ ...props,
47
+ ...rest,
48
+ ...Platform.select({
49
+ web: { onClick: onPress } as any,
50
+ default: { onPress },
51
+ }),
52
+ });
53
+ }
@@ -0,0 +1,12 @@
1
+ import type { ParamListBase } from '@amazon-devices/react-navigation__core';
2
+ import * as React from 'react';
3
+
4
+ import type { LinkingOptions } from './types';
5
+
6
+ const LinkingContext = React.createContext<{
7
+ options: LinkingOptions<ParamListBase> | undefined;
8
+ }>({ options: undefined });
9
+
10
+ LinkingContext.displayName = 'LinkingContext';
11
+
12
+ export default LinkingContext;
@@ -0,0 +1,152 @@
1
+ import {
2
+ BaseNavigationContainer,
3
+ getActionFromState,
4
+ getPathFromState,
5
+ getStateFromPath,
6
+ NavigationContainerProps,
7
+ NavigationContainerRef,
8
+ ParamListBase,
9
+ validatePathConfig,
10
+ } from '@amazon-devices/react-navigation__core';
11
+ import * as React from 'react';
12
+
13
+ import LinkingContext from './LinkingContext';
14
+ import DefaultTheme from './theming/DefaultTheme';
15
+ import ThemeProvider from './theming/ThemeProvider';
16
+ import type { DocumentTitleOptions, LinkingOptions, Theme } from './types';
17
+ import useBackButton from './useBackButton';
18
+ import useDocumentTitle from './useDocumentTitle';
19
+ import useLinking from './useLinking';
20
+ import useThenable from './useThenable';
21
+
22
+ declare global {
23
+ var REACT_NAVIGATION_DEVTOOLS: WeakMap<
24
+ NavigationContainerRef<any>,
25
+ { readonly linking: LinkingOptions<any> }
26
+ >;
27
+ }
28
+
29
+ global.REACT_NAVIGATION_DEVTOOLS = new WeakMap();
30
+
31
+ type Props<ParamList extends {}> = NavigationContainerProps & {
32
+ theme?: Theme;
33
+ linking?: LinkingOptions<ParamList>;
34
+ fallback?: React.ReactNode;
35
+ documentTitle?: DocumentTitleOptions;
36
+ onReady?: () => void;
37
+ };
38
+
39
+ /**
40
+ * Container component which holds the navigation state designed for React Native apps.
41
+ * This should be rendered at the root wrapping the whole app.
42
+ *
43
+ * @param props.initialState Initial state object for the navigation tree. When deep link handling is enabled, this will override deep links when specified. Make sure that you don't specify an `initialState` when there's a deep link (`Linking.getInitialURL()`).
44
+ * @param props.onReady Callback which is called after the navigation tree mounts.
45
+ * @param props.onStateChange Callback which is called with the latest navigation state when it changes.
46
+ * @param props.theme Theme object for the navigators.
47
+ * @param props.linking Options for deep linking. Deep link handling is enabled when this prop is provided, unless `linking.enabled` is `false`.
48
+ * @param props.fallback Fallback component to render until we have finished getting initial state when linking is enabled. Defaults to `null`.
49
+ * @param props.documentTitle Options to configure the document title on Web. Updating document title is handled by default unless `documentTitle.enabled` is `false`.
50
+ * @param props.children Child elements to render the content.
51
+ * @param props.ref Ref object which refers to the navigation object containing helper methods.
52
+ */
53
+ function NavigationContainerInner(
54
+ {
55
+ theme = DefaultTheme,
56
+ linking,
57
+ fallback = null,
58
+ documentTitle,
59
+ onReady,
60
+ ...rest
61
+ }: Props<ParamListBase>,
62
+ ref?: React.Ref<NavigationContainerRef<ParamListBase> | null>
63
+ ) {
64
+ const isLinkingEnabled = linking ? linking.enabled !== false : false;
65
+
66
+ if (linking?.config) {
67
+ validatePathConfig(linking.config);
68
+ }
69
+
70
+ const refContainer =
71
+ React.useRef<NavigationContainerRef<ParamListBase>>(null);
72
+
73
+ useBackButton(refContainer);
74
+ useDocumentTitle(refContainer, documentTitle);
75
+
76
+ const { getInitialState } = useLinking(refContainer, {
77
+ independent: rest.independent,
78
+ enabled: isLinkingEnabled,
79
+ prefixes: [],
80
+ ...linking,
81
+ });
82
+
83
+ // Add additional linking related info to the ref
84
+ // This will be used by the devtools
85
+ React.useEffect(() => {
86
+ if (refContainer.current) {
87
+ REACT_NAVIGATION_DEVTOOLS.set(refContainer.current, {
88
+ get linking() {
89
+ return {
90
+ ...linking,
91
+ enabled: isLinkingEnabled,
92
+ prefixes: linking?.prefixes ?? [],
93
+ getStateFromPath: linking?.getStateFromPath ?? getStateFromPath,
94
+ getPathFromState: linking?.getPathFromState ?? getPathFromState,
95
+ getActionFromState:
96
+ linking?.getActionFromState ?? getActionFromState,
97
+ };
98
+ },
99
+ });
100
+ }
101
+ });
102
+
103
+ const [isResolved, initialState] = useThenable(getInitialState);
104
+
105
+ React.useImperativeHandle(ref, () => refContainer.current);
106
+
107
+ const linkingContext = React.useMemo(() => ({ options: linking }), [linking]);
108
+
109
+ const isReady = rest.initialState != null || !isLinkingEnabled || isResolved;
110
+
111
+ const onReadyRef = React.useRef(onReady);
112
+
113
+ React.useEffect(() => {
114
+ onReadyRef.current = onReady;
115
+ });
116
+
117
+ React.useEffect(() => {
118
+ if (isReady) {
119
+ onReadyRef.current?.();
120
+ }
121
+ }, [isReady]);
122
+
123
+ if (!isReady) {
124
+ // This is temporary until we have Suspense for data-fetching
125
+ // Then the fallback will be handled by a parent `Suspense` component
126
+ return fallback as React.ReactElement;
127
+ }
128
+
129
+ return (
130
+ <LinkingContext.Provider value={linkingContext}>
131
+ <ThemeProvider value={theme}>
132
+ <BaseNavigationContainer
133
+ {...rest}
134
+ initialState={
135
+ rest.initialState == null ? initialState : rest.initialState
136
+ }
137
+ ref={refContainer}
138
+ />
139
+ </ThemeProvider>
140
+ </LinkingContext.Provider>
141
+ );
142
+ }
143
+
144
+ const NavigationContainer = React.forwardRef(NavigationContainerInner) as <
145
+ RootParamList extends {} = ReactNavigation.RootParamList
146
+ >(
147
+ props: Props<RootParamList> & {
148
+ ref?: React.Ref<NavigationContainerRef<RootParamList>>;
149
+ }
150
+ ) => React.ReactElement;
151
+
152
+ export default NavigationContainer;
@@ -0,0 +1,56 @@
1
+ import { CurrentRenderContext } from '@amazon-devices/react-navigation__core';
2
+ import * as React from 'react';
3
+
4
+ import ServerContext, { ServerContextType } from './ServerContext';
5
+ import type { ServerContainerRef } from './types';
6
+
7
+ type Props = ServerContextType & {
8
+ children: React.ReactNode;
9
+ };
10
+
11
+ /**
12
+ * Container component for server rendering.
13
+ *
14
+ * @param props.location Location object to base the initial URL for SSR.
15
+ * @param props.children Child elements to render the content.
16
+ * @param props.ref Ref object which contains helper methods.
17
+ */
18
+ export default React.forwardRef(function ServerContainer(
19
+ { children, location }: Props,
20
+ ref: React.Ref<ServerContainerRef>
21
+ ) {
22
+ React.useEffect(() => {
23
+ console.error(
24
+ "'ServerContainer' should only be used on the server with 'react-dom/server' for SSR."
25
+ );
26
+ }, []);
27
+
28
+ const current: { options?: object } = {};
29
+
30
+ if (ref) {
31
+ const value = {
32
+ getCurrentOptions() {
33
+ return current.options;
34
+ },
35
+ };
36
+
37
+ // We write to the `ref` during render instead of `React.useImperativeHandle`
38
+ // This is because `useImperativeHandle` will update the ref after 'commit',
39
+ // and there's no 'commit' phase during SSR.
40
+ // Mutating ref during render is unsafe in concurrent mode, but we don't care about it for SSR.
41
+ if (typeof ref === 'function') {
42
+ ref(value);
43
+ } else {
44
+ // @ts-expect-error: the TS types are incorrect and say that ref.current is readonly
45
+ ref.current = value;
46
+ }
47
+ }
48
+
49
+ return (
50
+ <ServerContext.Provider value={{ location }}>
51
+ <CurrentRenderContext.Provider value={current}>
52
+ {children}
53
+ </CurrentRenderContext.Provider>
54
+ </ServerContext.Provider>
55
+ );
56
+ });
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+
3
+ export type ServerContextType = {
4
+ location?: {
5
+ pathname: string;
6
+ search: string;
7
+ };
8
+ };
9
+
10
+ const ServerContext = React.createContext<ServerContextType | undefined>(
11
+ undefined
12
+ );
13
+
14
+ export default ServerContext;
@@ -0,0 +1,77 @@
1
+ const location = new URL('', 'http://example.com');
2
+
3
+ let listeners: (() => void)[] = [];
4
+ let entries = [{ state: null, href: location.href }];
5
+ let index = 0;
6
+
7
+ let currentState: any = null;
8
+
9
+ const history = {
10
+ get state() {
11
+ return currentState;
12
+ },
13
+
14
+ pushState(state: any, _: string, path: string) {
15
+ Object.assign(location, new URL(path, location.origin));
16
+
17
+ currentState = state;
18
+ entries = entries.slice(0, index + 1);
19
+ entries.push({ state, href: location.href });
20
+ index = entries.length - 1;
21
+ },
22
+
23
+ replaceState(state: any, _: string, path: string) {
24
+ Object.assign(location, new URL(path, location.origin));
25
+
26
+ currentState = state;
27
+ entries[index] = { state, href: location.href };
28
+ },
29
+
30
+ go(n: number) {
31
+ setTimeout(() => {
32
+ if (
33
+ (n > 0 && n < entries.length - index) ||
34
+ (n < 0 && Math.abs(n) <= index)
35
+ ) {
36
+ index += n;
37
+ const entry = entries[index];
38
+ Object.assign(location, new URL(entry.href));
39
+ currentState = entry.state;
40
+ listeners.forEach((cb) => cb());
41
+ }
42
+ }, 0);
43
+ },
44
+
45
+ back() {
46
+ this.go(-1);
47
+ },
48
+
49
+ forward() {
50
+ this.go(1);
51
+ },
52
+ };
53
+
54
+ const addEventListener = (type: 'popstate', listener: () => void) => {
55
+ if (type === 'popstate') {
56
+ listeners.push(listener);
57
+ }
58
+ };
59
+
60
+ const removeEventListener = (type: 'popstate', listener: () => void) => {
61
+ if (type === 'popstate') {
62
+ listeners = listeners.filter((cb) => cb !== listener);
63
+ }
64
+ };
65
+
66
+ const window = {
67
+ document: { title: '' },
68
+ location,
69
+ history,
70
+ addEventListener,
71
+ removeEventListener,
72
+ get window() {
73
+ return window;
74
+ },
75
+ };
76
+
77
+ export default window;
@@ -0,0 +1,224 @@
1
+ import type { NavigationState } from '@amazon-devices/react-navigation__core';
2
+ import { nanoid } from 'nanoid/non-secure';
3
+
4
+ type HistoryRecord = {
5
+ // Unique identifier for this record to match it with window.history.state
6
+ id: string;
7
+ // Navigation state object for the history entry
8
+ state: NavigationState;
9
+ // Path of the history entry
10
+ path: string;
11
+ };
12
+
13
+ export default function createMemoryHistory() {
14
+ let index = 0;
15
+ let items: HistoryRecord[] = [];
16
+
17
+ // Pending callbacks for `history.go(n)`
18
+ // We might modify the callback stored if it was interrupted, so we have a ref to identify it
19
+ const pending: { ref: unknown; cb: (interrupted?: boolean) => void }[] = [];
20
+
21
+ const interrupt = () => {
22
+ // If another history operation was performed we need to interrupt existing ones
23
+ // This makes sure that calls such as `history.replace` after `history.go` don't happen
24
+ // Since otherwise it won't be correct if something else has changed
25
+ pending.forEach((it) => {
26
+ const cb = it.cb;
27
+ it.cb = () => cb(true);
28
+ });
29
+ };
30
+
31
+ const history = {
32
+ get index(): number {
33
+ // We store an id in the state instead of an index
34
+ // Index could get out of sync with in-memory values if page reloads
35
+ const id = window.history.state?.id;
36
+
37
+ if (id) {
38
+ const index = items.findIndex((item) => item.id === id);
39
+
40
+ return index > -1 ? index : 0;
41
+ }
42
+
43
+ return 0;
44
+ },
45
+
46
+ get(index: number) {
47
+ return items[index];
48
+ },
49
+
50
+ backIndex({ path }: { path: string }) {
51
+ // We need to find the index from the element before current to get closest path to go back to
52
+ for (let i = index - 1; i >= 0; i--) {
53
+ const item = items[i];
54
+
55
+ if (item.path === path) {
56
+ return i;
57
+ }
58
+ }
59
+
60
+ return -1;
61
+ },
62
+
63
+ push({ path, state }: { path: string; state: NavigationState }) {
64
+ interrupt();
65
+
66
+ const id = nanoid();
67
+
68
+ // When a new entry is pushed, all the existing entries after index will be inaccessible
69
+ // So we remove any existing entries after the current index to clean them up
70
+ items = items.slice(0, index + 1);
71
+
72
+ items.push({ path, state, id });
73
+ index = items.length - 1;
74
+
75
+ // We pass empty string for title because it's ignored in all browsers except safari
76
+ // We don't store state object in history.state because:
77
+ // - browsers have limits on how big it can be, and we don't control the size
78
+ // - while not recommended, there could be non-serializable data in state
79
+ window.history.pushState({ id }, '', path);
80
+ },
81
+
82
+ replace({ path, state }: { path: string; state: NavigationState }) {
83
+ interrupt();
84
+
85
+ const id = window.history.state?.id ?? nanoid();
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
+
91
+ if (!items.length || items.findIndex((item) => item.id === id) < 0) {
92
+ // There are two scenarios for creating an array with only one history record:
93
+ // - When loaded id not found in the items array, this function by default will replace
94
+ // the first item. We need to keep only the new updated object, otherwise it will break
95
+ // the page when navigating forward in history.
96
+ // - This is the first time any state modifications are done
97
+ // So we need to push the entry as there's nothing to replace
98
+ pathWithHash = pathWithHash + location.hash;
99
+ items = [{ path: pathWithHash, state, id }];
100
+ index = 0;
101
+ } else {
102
+ if (items[index].path === path) {
103
+ pathWithHash = pathWithHash + location.hash;
104
+ }
105
+ items[index] = { path, state, id };
106
+ }
107
+
108
+ window.history.replaceState({ id }, '', pathWithHash);
109
+ },
110
+
111
+ // `history.go(n)` is asynchronous, there are couple of things to keep in mind:
112
+ // - it won't do anything if we can't go `n` steps, the `popstate` event won't fire.
113
+ // - each `history.go(n)` call will trigger a separate `popstate` event with correct location.
114
+ // - the `popstate` event fires before the next frame after calling `history.go(n)`.
115
+ // This method differs from `history.go(n)` in the sense that it'll go back as many steps it can.
116
+ go(n: number) {
117
+ interrupt();
118
+
119
+ // 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
120
+ // 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.
121
+ const nextIndex = index + n;
122
+ const lastItemIndex = items.length - 1;
123
+ if (n < 0 && !items[nextIndex]) {
124
+ // Attempted to navigate beyond the first index. Negating the current index will align the browser history with the first item.
125
+ n = -index;
126
+ index = 0;
127
+ } else if (n > 0 && nextIndex > lastItemIndex) {
128
+ // Attempted to navigate past the last index. Calculate how many indices away from the last index and go there.
129
+ n = lastItemIndex - index;
130
+ index = lastItemIndex;
131
+ } else {
132
+ index = nextIndex;
133
+ }
134
+
135
+ if (n === 0) {
136
+ return;
137
+ }
138
+
139
+ // When we call `history.go`, `popstate` will fire when there's history to go back to
140
+ // So we need to somehow handle following cases:
141
+ // - There's history to go back, `history.go` is called, and `popstate` fires
142
+ // - `history.go` is called multiple times, we need to resolve on respective `popstate`
143
+ // - No history to go back, but `history.go` was called, browser has no API to detect it
144
+ return new Promise<void>((resolve, reject) => {
145
+ const done = (interrupted?: boolean) => {
146
+ clearTimeout(timer);
147
+
148
+ if (interrupted) {
149
+ reject(new Error('History was changed during navigation.'));
150
+ return;
151
+ }
152
+
153
+ // There seems to be a bug in Chrome regarding updating the title
154
+ // If we set a title just before calling `history.go`, the title gets lost
155
+ // However the value of `document.title` is still what we set it to
156
+ // It's just not displayed in the tab bar
157
+ // To update the tab bar, we need to reset the title to something else first (e.g. '')
158
+ // And set the title to what it was before so it gets applied
159
+ // It won't work without setting it to empty string coz otherwise title isn't changing
160
+ // Which means that the browser won't do anything after setting the title
161
+ const { title } = window.document;
162
+
163
+ window.document.title = '';
164
+ window.document.title = title;
165
+
166
+ resolve();
167
+ };
168
+
169
+ pending.push({ ref: done, cb: done });
170
+
171
+ // If navigation didn't happen within 100ms, assume that it won't happen
172
+ // This may not be accurate, but hopefully it won't take so much time
173
+ // In Chrome, navigation seems to happen instantly in next microtask
174
+ // But on Firefox, it seems to take much longer, around 50ms from our testing
175
+ // We're using a hacky timeout since there doesn't seem to be way to know for sure
176
+ const timer = setTimeout(() => {
177
+ const index = pending.findIndex((it) => it.ref === done);
178
+
179
+ if (index > -1) {
180
+ pending[index].cb();
181
+ pending.splice(index, 1);
182
+ }
183
+ }, 100);
184
+
185
+ const onPopState = () => {
186
+ const id = window.history.state?.id;
187
+ const currentIndex = items.findIndex((item) => item.id === id);
188
+
189
+ // Fix createMemoryHistory.index variable's value
190
+ // as it may go out of sync when navigating in the browser.
191
+ index = Math.max(currentIndex, 0);
192
+
193
+ const last = pending.pop();
194
+
195
+ window.removeEventListener('popstate', onPopState);
196
+ last?.cb();
197
+ };
198
+
199
+ window.addEventListener('popstate', onPopState);
200
+ window.history.go(n);
201
+ });
202
+ },
203
+
204
+ // The `popstate` event is triggered when history changes, except `pushState` and `replaceState`
205
+ // If we call `history.go(n)` ourselves, we don't want it to trigger the listener
206
+ // Here we normalize it so that only external changes (e.g. user pressing back/forward) trigger the listener
207
+ listen(listener: () => void) {
208
+ const onPopState = () => {
209
+ if (pending.length) {
210
+ // This was triggered by `history.go(n)`, we shouldn't call the listener
211
+ return;
212
+ }
213
+
214
+ listener();
215
+ };
216
+
217
+ window.addEventListener('popstate', onPopState);
218
+
219
+ return () => window.removeEventListener('popstate', onPopState);
220
+ },
221
+ };
222
+
223
+ return history;
224
+ }
@@ -0,0 +1,29 @@
1
+ import escapeStringRegexp from 'escape-string-regexp';
2
+
3
+ export default function extractPathFromURL(prefixes: string[], url: string) {
4
+ for (const prefix of prefixes) {
5
+ const protocol = prefix.match(/^[^:]+:/)?.[0] ?? '';
6
+ const host = prefix
7
+ .replace(new RegExp(`^${escapeStringRegexp(protocol)}`), '')
8
+ .replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
9
+ .replace(/^\//, ''); // Remove extra leading slash
10
+
11
+ const prefixRegex = new RegExp(
12
+ `^${escapeStringRegexp(protocol)}(/)*${host
13
+ .split('.')
14
+ .map((it) => (it === '*' ? '[^/]+' : escapeStringRegexp(it)))
15
+ .join('\\.')}`
16
+ );
17
+
18
+ const [originAndPath, searchParams] = url.split('?');
19
+ const normalizedURL = originAndPath
20
+ .replace(/\/+/g, '/')
21
+ .concat(searchParams ? `?${searchParams}` : '');
22
+
23
+ if (prefixRegex.test(normalizedURL)) {
24
+ return normalizedURL.replace(prefixRegex, '');
25
+ }
26
+ }
27
+
28
+ return undefined;
29
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,14 @@
1
+ export { default as Link } from './Link';
2
+ export { default as LinkingContext } from './LinkingContext';
3
+ export { default as NavigationContainer } from './NavigationContainer';
4
+ export { default as ServerContainer } from './ServerContainer';
5
+ export { default as DarkTheme } from './theming/DarkTheme';
6
+ export { default as DefaultTheme } from './theming/DefaultTheme';
7
+ export { default as ThemeProvider } from './theming/ThemeProvider';
8
+ export { default as useTheme } from './theming/useTheme';
9
+ export * from './types';
10
+ export { default as useLinkBuilder } from './useLinkBuilder';
11
+ export { default as useLinkProps } from './useLinkProps';
12
+ export { default as useLinkTo } from './useLinkTo';
13
+ export { default as useScrollToTop } from './useScrollToTop';
14
+ export * from '@amazon-devices/react-navigation__core';
@@ -0,0 +1,15 @@
1
+ import type { Theme } from '../types';
2
+
3
+ const DarkTheme: Theme = {
4
+ dark: true,
5
+ colors: {
6
+ primary: 'rgb(10, 132, 255)',
7
+ background: 'rgb(1, 1, 1)',
8
+ card: 'rgb(18, 18, 18)',
9
+ text: 'rgb(229, 229, 231)',
10
+ border: 'rgb(39, 39, 41)',
11
+ notification: 'rgb(255, 69, 58)',
12
+ },
13
+ };
14
+
15
+ export default DarkTheme;
@@ -0,0 +1,15 @@
1
+ import type { Theme } from '../types';
2
+
3
+ const DefaultTheme: Theme = {
4
+ dark: false,
5
+ colors: {
6
+ primary: 'rgb(0, 122, 255)',
7
+ background: 'rgb(242, 242, 242)',
8
+ card: 'rgb(255, 255, 255)',
9
+ text: 'rgb(28, 28, 30)',
10
+ border: 'rgb(216, 216, 216)',
11
+ notification: 'rgb(255, 59, 48)',
12
+ },
13
+ };
14
+
15
+ export default DefaultTheme;
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+
3
+ import type { Theme } from '../types';
4
+ import DefaultTheme from './DefaultTheme';
5
+
6
+ const ThemeContext = React.createContext<Theme>(DefaultTheme);
7
+
8
+ ThemeContext.displayName = 'ThemeContext';
9
+
10
+ export default ThemeContext;