react-native-keyboard-toolkit 1.0.0 → 1.0.2

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.
@@ -1,14 +1,21 @@
1
- import { useSyncExternalStore } from 'react';
1
+ import { useEffect, useSyncExternalStore } from 'react';
2
2
  import {
3
3
  getKeyboardState,
4
4
  subscribeKeyboardState,
5
+ synchronizeKeyboardState,
5
6
  } from '../utils/keyboardEvents';
6
7
 
7
8
  /** Subscribe to the shared, normalized keyboard state. A provider is optional. */
8
- export function useKeyboardState() {
9
- return useSyncExternalStore(
9
+ export function useKeyboardState(options: { isFocused?: boolean } = {}) {
10
+ const keyboard = useSyncExternalStore(
10
11
  subscribeKeyboardState,
11
12
  getKeyboardState,
12
13
  getKeyboardState,
13
14
  );
15
+
16
+ useEffect(() => {
17
+ synchronizeKeyboardState(options.isFocused !== false);
18
+ }, [options.isFocused]);
19
+
20
+ return keyboard;
14
21
  }
@@ -49,15 +49,19 @@ export interface KeyboardAwareScrollViewProps extends ScrollViewProps {
49
49
  keyboardSpacing?: number;
50
50
  /** Disable keyboard-driven padding and focus scrolling. */
51
51
  enabled?: boolean;
52
+ /** Pass the navigation focus state to resync keyboard metrics on refocus. */
53
+ isFocused?: boolean;
52
54
  }
53
55
 
54
56
  export interface KeyboardAwareViewProps extends ViewProps {
55
- /** Defaults to `smart`. `resize` relies on the platform window resize behavior. */
57
+ /** Defaults to `smart`; absolute-positioned views are moved above the keyboard. */
56
58
  behavior?: KeyboardBehavior;
57
59
  /** Extra space between the view content and the keyboard, in points. */
58
60
  keyboardSpacing?: number;
59
61
  /** Disable keyboard-driven layout changes. */
60
62
  enabled?: boolean;
63
+ /** Pass the navigation focus state to resync keyboard metrics on refocus. */
64
+ isFocused?: boolean;
61
65
  }
62
66
 
63
67
  export interface KeyboardProviderProps {
@@ -1,4 +1,6 @@
1
1
  import {
2
+ AppState,
3
+ Dimensions,
2
4
  Keyboard,
3
5
  type KeyboardEvent,
4
6
  type EmitterSubscription,
@@ -18,9 +20,23 @@ let state = initialState;
18
20
  let listeners: EmitterSubscription[] = [];
19
21
  const subscribers = new Set<() => void>();
20
22
 
23
+ function publish(nextState: KeyboardState): void {
24
+ if (
25
+ state.isVisible === nextState.isVisible &&
26
+ state.height === nextState.height &&
27
+ state.progress === nextState.progress &&
28
+ state.animationDuration === nextState.animationDuration &&
29
+ state.screenY === nextState.screenY
30
+ ) {
31
+ return;
32
+ }
33
+ state = nextState;
34
+ subscribers.forEach(subscriber => subscriber());
35
+ }
36
+
21
37
  function update(event: KeyboardEvent, visible: boolean): void {
22
38
  const coordinates = event.endCoordinates;
23
- state = {
39
+ publish({
24
40
  isVisible: visible,
25
41
  height: visible ? Math.max(0, coordinates?.height ?? 0) : 0,
26
42
  progress: visible ? 1 : 0,
@@ -31,29 +47,39 @@ function update(event: KeyboardEvent, visible: boolean): void {
31
47
  ...(visible && typeof coordinates?.screenY === 'number'
32
48
  ? { screenY: coordinates.screenY }
33
49
  : {}),
34
- };
35
- subscribers.forEach(subscriber => subscriber());
50
+ });
51
+ }
52
+
53
+ function resetKeyboardState(): void {
54
+ publish(initialState);
36
55
  }
37
56
 
38
- function syncCurrentKeyboardState(): void {
57
+ /** Reconcile the shared snapshot with the platform's current keyboard metrics. */
58
+ export function synchronizeKeyboardState(isFocused = true): void {
59
+ if (!isFocused) {
60
+ resetKeyboardState();
61
+ return;
62
+ }
63
+
39
64
  const metrics =
40
65
  typeof Keyboard.metrics === 'function' ? Keyboard.metrics() : undefined;
41
66
  const isVisible =
42
67
  typeof Keyboard.isVisible === 'function'
43
68
  ? Keyboard.isVisible()
44
69
  : metrics != null;
70
+
45
71
  if (!isVisible || !metrics) {
72
+ resetKeyboardState();
46
73
  return;
47
74
  }
48
75
 
49
- state = {
76
+ publish({
50
77
  isVisible: true,
51
78
  height: metrics.height,
52
79
  progress: 1,
53
80
  animationDuration: 0,
54
81
  screenY: metrics.screenY,
55
- };
56
- subscribers.forEach(subscriber => subscriber());
82
+ });
57
83
  }
58
84
 
59
85
  function startListening(): void {
@@ -64,23 +90,39 @@ function startListening(): void {
64
90
  update(event, event.endCoordinates.height > 0),
65
91
  ),
66
92
  ];
67
- syncCurrentKeyboardState();
68
- return;
93
+ } else {
94
+ const showSubscription = Keyboard.addListener(names.show, event =>
95
+ update(event, true),
96
+ );
97
+ const hideSubscription = Keyboard.addListener(names.hide, event =>
98
+ update(event, false),
99
+ );
100
+ listeners = [showSubscription, hideSubscription];
69
101
  }
70
102
 
71
- const showSubscription = Keyboard.addListener(names.show, event =>
72
- update(event, true),
103
+ listeners.push(
104
+ AppState.addEventListener('change', nextState => {
105
+ if (nextState === 'active') {
106
+ synchronizeKeyboardState();
107
+ } else {
108
+ resetKeyboardState();
109
+ }
110
+ }),
73
111
  );
74
- const hideSubscription = Keyboard.addListener(names.hide, event =>
75
- update(event, false),
112
+ listeners.push(
113
+ Dimensions.addEventListener('change', () => {
114
+ if (typeof Keyboard.metrics === 'function') {
115
+ synchronizeKeyboardState();
116
+ }
117
+ }),
76
118
  );
77
- listeners = [showSubscription, hideSubscription];
78
- syncCurrentKeyboardState();
119
+ synchronizeKeyboardState();
79
120
  }
80
121
 
81
122
  function stopListening(): void {
82
123
  listeners.forEach(subscription => subscription.remove());
83
124
  listeners = [];
125
+ resetKeyboardState();
84
126
  }
85
127
 
86
128
  /** Subscribe to the shared keyboard event stream. */
@@ -13,6 +13,18 @@ export interface ScrollCalculation {
13
13
  delta: number;
14
14
  }
15
15
 
16
+ /** Return how far a bottom edge must move to clear the keyboard and spacing. */
17
+ export function calculateKeyboardDisplacement(
18
+ viewport: ScrollViewport,
19
+ keyboardTop: number,
20
+ spacing: number,
21
+ ): number {
22
+ return Math.max(
23
+ 0,
24
+ viewport.y + viewport.height - keyboardTop + Math.max(0, spacing),
25
+ );
26
+ }
27
+
16
28
  /** Return the part of the measured view actually covered by the keyboard. */
17
29
  export function calculateKeyboardOverlap(
18
30
  viewport: ScrollViewport,