@granite-js/react-native 1.0.18 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @granite-js/react-native
2
2
 
3
+ ## 1.0.19
4
+
5
+ ### Patch Changes
6
+
7
+ - adfe616: perf(react-native): without new URL optimize
8
+ - b084323: feat: add useKeyboardHeight
9
+ - @granite-js/native@1.0.19
10
+ - @granite-js/blur-view@1.0.19
11
+ - @granite-js/jest@1.0.19
12
+ - @granite-js/style-utils@1.0.19
13
+ - @granite-js/cli@1.0.19
14
+ - @granite-js/mpack@1.0.19
15
+ - @granite-js/plugin-core@1.0.19
16
+ - @granite-js/video@1.0.19
17
+
3
18
  ## 1.0.18
4
19
 
5
20
  ### Patch Changes
@@ -0,0 +1 @@
1
+ export declare function getSearchParamsFromUrl(url: string): Record<string, string>;
@@ -18,6 +18,4 @@
18
18
  * }
19
19
  * ```
20
20
  */
21
- export declare function useInitialSearchParams(): {
22
- [k: string]: string;
23
- };
21
+ export declare function useInitialSearchParams(): Record<string, string>;
@@ -0,0 +1 @@
1
+ export declare function getInitialKeyboardHeight(): number;
@@ -0,0 +1,7 @@
1
+ export declare function getKeyboardEventNames(): {
2
+ readonly show: "keyboardWillShow";
3
+ readonly hide: "keyboardWillHide";
4
+ } | {
5
+ readonly show: "keyboardDidShow";
6
+ readonly hide: "keyboardDidHide";
7
+ } | null;
@@ -1,2 +1,3 @@
1
1
  export { KeyboardAboveView } from './KeyboardAboveView';
2
2
  export { useKeyboardAnimatedHeight } from './useKeyboardAnimatedHeight';
3
+ export { useKeyboardHeight } from './useKeyboardHeight';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @category Hooks
3
+ * @name useKeyboardHeight
4
+ * @description
5
+ * A Hook that returns the current keyboard height as a number when the keyboard appears or disappears. Unlike `useKeyboardAnimatedHeight`, this Hook does not animate the value — it simply reflects the latest keyboard height.
6
+ *
7
+ * This Hook uses `keyboardWillShow`/`keyboardWillHide` on iOS, and `keyboardDidShow`/`keyboardDidHide` on Android when React Native New Architecture is enabled. On Android Old Architecture, it always returns `0`.
8
+ *
9
+ * @returns {number} - The current keyboard height.
10
+ * @example
11
+ * ```typescript
12
+ * const keyboardHeight = useKeyboardHeight();
13
+ *
14
+ * <View style={{ marginBottom: keyboardHeight }}>
15
+ * {children}
16
+ * </View>
17
+ * ```
18
+ */
19
+ export declare function useKeyboardHeight(): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@granite-js/react-native",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "description": "The Granite Framework",
5
5
  "bin": {
6
6
  "granite": "./bin/cli.js"
@@ -91,7 +91,7 @@
91
91
  "@babel/core": "7.28.5",
92
92
  "@babel/preset-env": "7.28.5",
93
93
  "@babel/preset-typescript": "7.28.5",
94
- "@granite-js/native": "1.0.18",
94
+ "@granite-js/native": "1.0.19",
95
95
  "@testing-library/dom": "^10.4.0",
96
96
  "@testing-library/react": "^16.1.0",
97
97
  "@types/babel__core": "^7",
@@ -113,20 +113,20 @@
113
113
  "zod": "^4.1.12"
114
114
  },
115
115
  "peerDependencies": {
116
- "@granite-js/native": "1.0.18",
116
+ "@granite-js/native": "1.0.19",
117
117
  "@types/react": "*",
118
118
  "brick-module": "*",
119
119
  "react": "*",
120
120
  "react-native": "*"
121
121
  },
122
122
  "dependencies": {
123
- "@granite-js/blur-view": "1.0.18",
124
- "@granite-js/cli": "1.0.18",
125
- "@granite-js/jest": "1.0.18",
126
- "@granite-js/mpack": "1.0.18",
127
- "@granite-js/plugin-core": "1.0.18",
128
- "@granite-js/style-utils": "1.0.18",
129
- "@granite-js/video": "1.0.18",
123
+ "@granite-js/blur-view": "1.0.19",
124
+ "@granite-js/cli": "1.0.19",
125
+ "@granite-js/jest": "1.0.19",
126
+ "@granite-js/mpack": "1.0.19",
127
+ "@granite-js/plugin-core": "1.0.19",
128
+ "@granite-js/style-utils": "1.0.19",
129
+ "@granite-js/video": "1.0.19",
130
130
  "@standard-schema/spec": "^1.0.0",
131
131
  "es-toolkit": "^1.39.8",
132
132
  "react-native-url-polyfill": "3.0.0"
@@ -0,0 +1,32 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getSearchParamsFromUrl } from './getSearchParamsFromUrl';
3
+
4
+ describe('getSearchParamsFromUrl', () => {
5
+ it('returns query parameters from a custom scheme URL', () => {
6
+ expect(getSearchParamsFromUrl('myapp://home?userId=42&theme=dark')).toEqual({
7
+ userId: '42',
8
+ theme: 'dark',
9
+ });
10
+ });
11
+
12
+ it('decodes query parameters the same way as URLSearchParams', () => {
13
+ expect(getSearchParamsFromUrl('myapp://home?name=foo+bar&message=hello%20world')).toEqual({
14
+ name: 'foo bar',
15
+ message: 'hello world',
16
+ });
17
+ });
18
+
19
+ it('ignores the hash fragment and keeps the last duplicated key', () => {
20
+ expect(getSearchParamsFromUrl('myapp://home?a=1&a=2#section')).toEqual({
21
+ a: '2',
22
+ });
23
+ });
24
+
25
+ it('returns an empty object for URLs without a query string', () => {
26
+ expect(getSearchParamsFromUrl('myapp://home')).toEqual({});
27
+ });
28
+
29
+ it('returns an empty object for invalid URLs', () => {
30
+ expect(getSearchParamsFromUrl('not a url')).toEqual({});
31
+ });
32
+ });
@@ -0,0 +1,26 @@
1
+ export function getSearchParamsFromUrl(url: string): Record<string, string> {
2
+ if (typeof URLSearchParams === 'undefined') {
3
+ return {};
4
+ }
5
+
6
+ const queryString = getQueryString(url);
7
+
8
+ if (queryString == null || queryString.length === 0) {
9
+ return {};
10
+ }
11
+
12
+ return Object.fromEntries(new URLSearchParams(queryString));
13
+ }
14
+
15
+ function getQueryString(url: string) {
16
+ const questionMarkIndex = url.indexOf('?');
17
+
18
+ if (questionMarkIndex === -1) {
19
+ return null;
20
+ }
21
+
22
+ const fragmentIndex = url.indexOf('#', questionMarkIndex + 1);
23
+ const queryEndIndex = fragmentIndex === -1 ? url.length : fragmentIndex;
24
+
25
+ return url.slice(questionMarkIndex + 1, queryEndIndex);
26
+ }
@@ -1,4 +1,6 @@
1
+ import { useMemo } from 'react';
1
2
  import { useInitialProps } from './InitialPropsContext';
3
+ import { getSearchParamsFromUrl } from './getSearchParamsFromUrl';
2
4
  import { getSchemeUri } from '../../native-modules';
3
5
 
4
6
  /**
@@ -24,9 +26,5 @@ import { getSchemeUri } from '../../native-modules';
24
26
  export function useInitialSearchParams() {
25
27
  const scheme = useInitialProps().scheme ?? getSchemeUri();
26
28
 
27
- try {
28
- return Object.fromEntries(new URL(scheme).searchParams);
29
- } catch {
30
- return {};
31
- }
29
+ return useMemo(() => getSearchParamsFromUrl(scheme), [scheme]);
32
30
  }
@@ -0,0 +1,21 @@
1
+ import { Platform, Keyboard } from "react-native";
2
+ import { isNewArchEnabled } from "../utils/isNewArchEnabled";
3
+
4
+ export function getInitialKeyboardHeight() {
5
+ if (Platform.OS === 'android' && !isNewArchEnabled()) {
6
+ return 0;
7
+ }
8
+
9
+ /**
10
+ * Branch handling for React Native 0.68.0 version where `metrics()` does not exist
11
+ */
12
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
13
+ // @ts-ignore
14
+ if (typeof Keyboard?.metrics === 'function') {
15
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
16
+ // @ts-ignore
17
+ return Keyboard.metrics()?.height ?? 0;
18
+ } else {
19
+ return 0;
20
+ }
21
+ }
@@ -0,0 +1,20 @@
1
+ import { Platform } from "react-native";
2
+ import { isNewArchEnabled } from "../utils/isNewArchEnabled";
3
+
4
+ export function getKeyboardEventNames() {
5
+ if (Platform.OS === 'ios') {
6
+ return {
7
+ show: 'keyboardWillShow',
8
+ hide: 'keyboardWillHide',
9
+ } as const;
10
+ }
11
+
12
+ if (Platform.OS === 'android' && isNewArchEnabled()) {
13
+ return {
14
+ show: 'keyboardDidShow',
15
+ hide: 'keyboardDidHide',
16
+ } as const;
17
+ }
18
+
19
+ return null;
20
+ }
@@ -1,2 +1,3 @@
1
1
  export { KeyboardAboveView } from './KeyboardAboveView';
2
2
  export { useKeyboardAnimatedHeight } from './useKeyboardAnimatedHeight';
3
+ export { useKeyboardHeight } from './useKeyboardHeight';
@@ -1,43 +1,7 @@
1
1
  import { useEffect, useRef } from 'react';
2
- import { Animated, Keyboard, Platform } from 'react-native';
3
- import { isNewArchEnabled } from '../utils/isNewArchEnabled';
4
-
5
- function getKeyboardEventNames() {
6
- if (Platform.OS === 'ios') {
7
- return {
8
- show: 'keyboardWillShow',
9
- hide: 'keyboardWillHide',
10
- } as const;
11
- }
12
-
13
- if (Platform.OS === 'android' && isNewArchEnabled()) {
14
- return {
15
- show: 'keyboardDidShow',
16
- hide: 'keyboardDidHide',
17
- } as const;
18
- }
19
-
20
- return null;
21
- }
22
-
23
- function getInitialKeyboardHeight() {
24
- if (Platform.OS === 'android' && !isNewArchEnabled()) {
25
- return 0;
26
- }
27
-
28
- /**
29
- * Branch handling for React Native 0.68.0 version where `metrics()` does not exist
30
- */
31
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
32
- // @ts-ignore
33
- if (typeof Keyboard?.metrics === 'function') {
34
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
35
- // @ts-ignore
36
- return Keyboard.metrics()?.height ?? 0;
37
- } else {
38
- return 0;
39
- }
40
- }
2
+ import { Animated, Keyboard } from 'react-native';
3
+ import { getInitialKeyboardHeight } from './getInitialKeyboardHeight';
4
+ import { getKeyboardEventNames } from './getKeyboardEventNames';
41
5
 
42
6
  /**
43
7
  * @category Hooks
@@ -0,0 +1,49 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Keyboard } from 'react-native';
3
+ import { getInitialKeyboardHeight } from './getInitialKeyboardHeight';
4
+ import { getKeyboardEventNames } from './getKeyboardEventNames';
5
+
6
+ /**
7
+ * @category Hooks
8
+ * @name useKeyboardHeight
9
+ * @description
10
+ * A Hook that returns the current keyboard height as a number when the keyboard appears or disappears. Unlike `useKeyboardAnimatedHeight`, this Hook does not animate the value — it simply reflects the latest keyboard height.
11
+ *
12
+ * This Hook uses `keyboardWillShow`/`keyboardWillHide` on iOS, and `keyboardDidShow`/`keyboardDidHide` on Android when React Native New Architecture is enabled. On Android Old Architecture, it always returns `0`.
13
+ *
14
+ * @returns {number} - The current keyboard height.
15
+ * @example
16
+ * ```typescript
17
+ * const keyboardHeight = useKeyboardHeight();
18
+ *
19
+ * <View style={{ marginBottom: keyboardHeight }}>
20
+ * {children}
21
+ * </View>
22
+ * ```
23
+ */
24
+ export function useKeyboardHeight(): number {
25
+ const [keyboardHeight, setKeyboardHeight] = useState<number>(getInitialKeyboardHeight);
26
+
27
+ useEffect(() => {
28
+ const keyboardEventNames = getKeyboardEventNames();
29
+
30
+ if (keyboardEventNames == null) {
31
+ return;
32
+ }
33
+
34
+ const showSubscription = Keyboard.addListener(keyboardEventNames.show, (event) => {
35
+ setKeyboardHeight(event.endCoordinates.height);
36
+ });
37
+
38
+ const hideSubscription = Keyboard.addListener(keyboardEventNames.hide, () => {
39
+ setKeyboardHeight(0);
40
+ });
41
+
42
+ return () => {
43
+ showSubscription.remove();
44
+ hideSubscription.remove();
45
+ };
46
+ }, []);
47
+
48
+ return keyboardHeight;
49
+ }
@@ -1,5 +1,3 @@
1
1
  export function getSchemePrefix({ scheme, appName, host }: { scheme: string; appName: string; host: string }) {
2
- const base = host.length > 0 ? `${scheme}://${host}/` : `${scheme}://`;
3
-
4
- return new URL(`${base}${appName}`).toString();
2
+ return host.length > 0 ? `${scheme}://${host}/${appName}` : `${scheme}://${appName}`;
5
3
  }