diff --git a/examples/expo-example/package.json b/examples/expo-example/package.json index 6e202f9c..397ca4f0 100644 --- a/examples/expo-example/package.json +++ b/examples/expo-example/package.json @@ -14,7 +14,7 @@ "@knocklabs/expo": "workspace:^", "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/elements": "^2.6.3", - "@react-navigation/native": "^7.1.6", + "@react-navigation/native": "^7.1.25", "expo": "~53.0.22", "expo-blur": "~14.1.5", "expo-constants": "~17.1.7", diff --git a/packages/react-native/package.json b/packages/react-native/package.json index b93d7e3a..723ead7c 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -14,6 +14,13 @@ "types": "./dist/types/index.d.ts", "react-native": "./src/index.ts", "default": "./dist/esm/index.mjs" + }, + "./react-navigation": { + "require": "./dist/cjs/react-navigation.js", + "types": "./dist/types/react-navigation/index.d.ts", + "react-native": "./src/react-navigation/index.ts", + "import": "./dist/esm/react-navigation.mjs", + "default": "./dist/esm/react-navigation.mjs" } }, "files": [ @@ -41,9 +48,15 @@ "url": "https://github.com/knocklabs/javascript/issues" }, "peerDependencies": { + "@react-navigation/native": "^7.0.0", "react": "*", "react-native": "*" }, + "peerDependenciesMeta": { + "@react-navigation/native": { + "optional": true + } + }, "dependencies": { "@knocklabs/client": "workspace:^", "@knocklabs/react-core": "workspace:^", @@ -54,6 +67,7 @@ }, "devDependencies": { "@codecov/vite-plugin": "^1.9.1", + "@react-navigation/native": "^7.1.25", "@types/react": "^19.1.8", "@types/react-native-get-random-values": "^1", "@types/react-native-htmlview": "^0.16.6", diff --git a/packages/react-native/src/react-navigation/KnockGuideLocationSensor.tsx b/packages/react-native/src/react-navigation/KnockGuideLocationSensor.tsx new file mode 100644 index 00000000..9ea57e73 --- /dev/null +++ b/packages/react-native/src/react-navigation/KnockGuideLocationSensor.tsx @@ -0,0 +1,58 @@ +import { useGuideContext } from "@knocklabs/react-core"; +import { + createStaticNavigation, + getPathFromState, + useNavigationContainerRef, +} from "@react-navigation/native"; +import React from "react"; + +type Props = { + navigationRef: ReturnType>; + linking: NonNullable< + React.ComponentProps>["linking"] + >; + origin?: string; +}; + +const Static = ({ + navigationRef, + linking, + origin: givenOrigin = "http://localhost:8081", +}: Props) => { + const { origin } = new URL(givenOrigin); + const { client } = useGuideContext(); + + if (linking.enabled === false) { + throw new Error( + "KnockGuideLocationSensor requires `linking.enabled` to be `auto` or 'true'", + ); + } + + React.useEffect(() => { + client.removeLocationChangeEventListeners(); + }, [client]); + + React.useEffect(() => { + const removeListener = navigationRef.addListener("state", () => { + const state = navigationRef.getRootState(); + if (!state) return; + + const getPathFromStateHelper = + linking?.getPathFromState ?? getPathFromState; + + // path here is the full path of the location, including pathname and + // stringified params but does not include the origin. + const path = getPathFromStateHelper(state); + + client.setLocation(origin + path); + }); + + return () => removeListener(); + }, [navigationRef, linking, origin, client]); + + return null; +}; + +export const KnockGuideLocationSensor = { + Static, +}; diff --git a/packages/react-native/src/react-navigation/index.ts b/packages/react-native/src/react-navigation/index.ts new file mode 100644 index 00000000..842d6214 --- /dev/null +++ b/packages/react-native/src/react-navigation/index.ts @@ -0,0 +1 @@ +export { KnockGuideLocationSensor } from "./KnockGuideLocationSensor"; diff --git a/packages/react-native/vite.config.mts b/packages/react-native/vite.config.mts index caca707d..54cffbfe 100644 --- a/packages/react-native/vite.config.mts +++ b/packages/react-native/vite.config.mts @@ -27,13 +27,16 @@ export default defineConfig(({ mode }) => { outDir: CJS ? "dist/cjs" : "dist/esm", sourcemap: true, lib: { - entry: resolve(__dirname, "src/index.ts"), + entry: { + index: resolve(__dirname, "src"), + "react-navigation": resolve(__dirname, "src/react-navigation/index.ts"), + }, name: "react-native", formats, }, rollupOptions: { // External packages that should not be bundled into your library. - external: ["react", "react-native"], + external: ["react", "react-native", "@react-navigation/native" ], output: { interop: "compat", format: formats[0], diff --git a/yarn.lock b/yarn.lock index 7200a5f8..bd63064f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4015,7 +4015,7 @@ __metadata: "@knocklabs/expo": "workspace:^" "@react-navigation/bottom-tabs": "npm:^7.3.10" "@react-navigation/elements": "npm:^2.6.3" - "@react-navigation/native": "npm:^7.1.6" + "@react-navigation/native": "npm:^7.1.25" "@types/react": "npm:^19.1.8" eslint: "npm:^8.56.0" eslint-config-expo: "npm:~9.2.0" @@ -4177,6 +4177,7 @@ __metadata: "@codecov/vite-plugin": "npm:^1.9.1" "@knocklabs/client": "workspace:^" "@knocklabs/react-core": "workspace:^" + "@react-navigation/native": "npm:^7.1.25" "@types/react": "npm:^19.1.8" "@types/react-native-get-random-values": "npm:^1" "@types/react-native-htmlview": "npm:^0.16.6" @@ -4199,8 +4200,12 @@ __metadata: vite-plugin-dts: "npm:^4.5.0" vite-plugin-no-bundle: "npm:^4.0.0" peerDependencies: + "@react-navigation/native": ^7.0.0 react: "*" react-native: "*" + peerDependenciesMeta: + "@react-navigation/native": + optional: true languageName: unknown linkType: soft @@ -5698,6 +5703,24 @@ __metadata: languageName: node linkType: hard +"@react-navigation/core@npm:^7.13.6": + version: 7.13.6 + resolution: "@react-navigation/core@npm:7.13.6" + dependencies: + "@react-navigation/routers": "npm:^7.5.2" + escape-string-regexp: "npm:^4.0.0" + fast-deep-equal: "npm:^3.1.3" + nanoid: "npm:^3.3.11" + query-string: "npm:^7.1.3" + react-is: "npm:^19.1.0" + use-latest-callback: "npm:^0.2.4" + use-sync-external-store: "npm:^1.5.0" + peerDependencies: + react: ">= 18.2.0" + checksum: 10c0/fa9a89ab0ca1ebd36f07a6095bf0d04722abe080dce4a4622a625b386f6b047baf805543254185dcb46b72f8b4b662b1f222fc6a5a091f8a6c08107ea7276885 + languageName: node + linkType: hard + "@react-navigation/elements@npm:^2.5.2": version: 2.5.2 resolution: "@react-navigation/elements@npm:2.5.2" @@ -5754,6 +5777,22 @@ __metadata: languageName: node linkType: hard +"@react-navigation/native@npm:^7.1.25": + version: 7.1.25 + resolution: "@react-navigation/native@npm:7.1.25" + dependencies: + "@react-navigation/core": "npm:^7.13.6" + escape-string-regexp: "npm:^4.0.0" + fast-deep-equal: "npm:^3.1.3" + nanoid: "npm:^3.3.11" + use-latest-callback: "npm:^0.2.4" + peerDependencies: + react: ">= 18.2.0" + react-native: "*" + checksum: 10c0/704d38b7a56841ad12fe482ccaba25df643722eebee7d8de0a6b6a128a1a60aa05c1f6722a0dbc0b5bca9cb38fcd8e57138b6e14a333bb62b58720d3e789ecdb + languageName: node + linkType: hard + "@react-navigation/native@npm:^7.1.6": version: 7.1.14 resolution: "@react-navigation/native@npm:7.1.14" @@ -5779,6 +5818,15 @@ __metadata: languageName: node linkType: hard +"@react-navigation/routers@npm:^7.5.2": + version: 7.5.2 + resolution: "@react-navigation/routers@npm:7.5.2" + dependencies: + nanoid: "npm:^3.3.11" + checksum: 10c0/eb22a8ce464595fc78d2f748d4397dfce5acae5f9b6afa9e0361142e69399ad8346019d1f6af7ba7a4404d54f71fc99e120ae113f5c827a4a6435bf201e349b5 + languageName: node + linkType: hard + "@rolldown/pluginutils@npm:1.0.0-beta.11": version: 1.0.0-beta.11 resolution: "@rolldown/pluginutils@npm:1.0.0-beta.11"