react-native-external-keyboard 0.8.5 → 0.9.1
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/README.md +149 -67
- package/android/src/main/java/com/externalkeyboard/delegates/FocusOrderDelegate.java +81 -75
- package/android/src/main/java/com/externalkeyboard/delegates/FocusOrderDelegateHost.java +14 -0
- package/android/src/main/java/com/externalkeyboard/helper/Linking/A11yOrderLinking.java +5 -0
- package/android/src/main/java/com/externalkeyboard/modules/ExternalKeyboardModule.java +10 -10
- package/android/src/main/java/com/externalkeyboard/services/FocusLinkObserver/FocusLinkObserver.java +26 -35
- package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardLockView/ExternalKeyboardLockView.java +5 -0
- package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardLockView/ExternalKeyboardLockViewManager.java +6 -0
- package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardView/ExternalKeyboardView.java +8 -307
- package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardView/ExternalKeyboardViewManager.java +11 -18
- package/android/src/main/java/com/externalkeyboard/views/TextInputFocusWrapper/TextInputFocusWrapper.java +208 -101
- package/android/src/main/java/com/externalkeyboard/views/TextInputFocusWrapper/TextInputFocusWrapperManager.java +123 -34
- package/android/src/main/java/com/externalkeyboard/views/base/FocusHighlightBase.java +38 -0
- package/android/src/main/java/com/externalkeyboard/views/base/ViewGroupBase.java +19 -0
- package/android/src/main/java/com/externalkeyboard/views/base/ViewOrderGroupBase.java +190 -0
- package/android/src/main/java/com/externalkeyboard/views/base/keyboard/ViewFocusChangeBase.java +39 -0
- package/android/src/main/java/com/externalkeyboard/views/base/keyboard/ViewFocusRequestBase.java +125 -0
- package/android/src/main/java/com/externalkeyboard/views/base/keyboard/ViewKeyHandlerBase.java +40 -0
- package/android/src/newarch/TextInputFocusWrapperManagerSpec.java +2 -8
- package/android/src/oldarch/ExternalKeyboardLockViewManagerSpec.java +1 -0
- package/android/src/oldarch/TextInputFocusWrapperManagerSpec.java +32 -2
- package/ios/Delegates/RNCEKVFocusLinkDelegate/RNCEKVFocusLinkDelegate.h +35 -0
- package/ios/Delegates/RNCEKVFocusLinkDelegate/RNCEKVFocusLinkDelegate.mm +195 -0
- package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusOrderProtocol.h +6 -8
- package/ios/Delegates/RNCEKVFocusSequenceDelegate/RNCEKVFocusSequenceDelegate.h +25 -0
- package/ios/Delegates/RNCEKVFocusSequenceDelegate/RNCEKVFocusSequenceDelegate.mm +163 -0
- package/ios/Delegates/RNCEKVGroupIdentifierDelegate/RNCEKVGroupIdentifierDelegate.h +2 -6
- package/ios/Delegates/RNCEKVGroupIdentifierDelegate/RNCEKVGroupIdentifierDelegate.mm +6 -78
- package/ios/Delegates/RNCEKVHaloDelegate/RNCEKVHaloDelegate.h +3 -4
- package/ios/Delegates/RNCEKVHaloDelegate/RNCEKVHaloDelegate.mm +32 -101
- package/ios/Delegates/RNCEKVHaloDelegate/RNCEKVHaloProtocol.h +1 -1
- package/ios/Extensions/RCTEnhancedScrollView+RNCEKVExternalKeyboard.mm +1 -1
- package/ios/Extensions/RCTTextInputComponentView+RNCEKVExternalKeyboard.mm +15 -0
- package/ios/Extensions/RCTViewComponentView+RNCEKVExternalKeyboard.h +9 -6
- package/ios/Extensions/RCTViewComponentView+RNCEKVExternalKeyboard.mm +16 -29
- package/ios/Extensions/UIViewController+RNCEKVExternalKeyboard.h +1 -0
- package/ios/Extensions/UIViewController+RNCEKVExternalKeyboard.mm +8 -0
- package/ios/Helpers/RNCEKVNativeProps/RNCEKVNativeProps.h +123 -0
- package/ios/Protocols/RNCEKVCustomFocusEffectProtocol.h +15 -0
- package/ios/Protocols/RNCEKVCustomGroudIdProtocol.h +15 -0
- package/ios/Protocols/RNCEKVKeyboardFocusableProtocol.h +15 -0
- package/ios/Services/RNCEKVFocusLinkObserver.mm +2 -3
- package/ios/Services/RNCEKVKeyboardOrderManager/RNCEKVOrderRelationship/RNCEKVOrderRelationship.mm +15 -28
- package/ios/Services/RNCEKVOrderLinking.mm +43 -51
- package/ios/Views/Base/ContextMenu/RNCEKVViewContextMenuBase.h +33 -0
- package/ios/Views/Base/ContextMenu/RNCEKVViewContextMenuBase.mm +84 -0
- package/ios/Views/Base/FocusChange/RNCEKVViewFocusChangeBase.h +37 -0
- package/ios/Views/Base/FocusChange/RNCEKVViewFocusChangeBase.mm +89 -0
- package/ios/Views/Base/FocusOrderGroup/RNCEKVViewOrderGroupBase.h +49 -0
- package/ios/Views/Base/FocusOrderGroup/RNCEKVViewOrderGroupBase.mm +245 -0
- package/ios/Views/Base/FocusRequest/RNCEKVViewFocusRequestBase.h +34 -0
- package/ios/Views/Base/FocusRequest/RNCEKVViewFocusRequestBase.mm +112 -0
- package/ios/Views/Base/GroupIdentifier/RNCEKVViewGroupIdentifierBase.h +27 -0
- package/ios/Views/Base/GroupIdentifier/RNCEKVViewGroupIdentifierBase.mm +69 -0
- package/ios/Views/Base/KeyPress/RNCEKVViewKeyPress.h +30 -0
- package/ios/Views/Base/KeyPress/RNCEKVViewKeyPress.mm +75 -0
- package/ios/Views/Base/KeyboardHallo/RNCEKVExternalKeyboardHalloBase.h +33 -0
- package/ios/Views/Base/KeyboardHallo/RNCEKVExternalKeyboardHalloBase.mm +92 -0
- package/ios/Views/Base/ViewGroup/RNCEKVViewGroupBase.h +36 -0
- package/ios/Views/Base/ViewGroup/RNCEKVViewGroupBase.mm +63 -0
- package/ios/Views/RNCEKVExternalKeyboardLockView/RNCEKVExternalKeyboardLockView.h +8 -0
- package/ios/Views/RNCEKVExternalKeyboardLockView/RNCEKVExternalKeyboardLockView.mm +105 -2
- package/ios/Views/RNCEKVExternalKeyboardLockView/RNCEKVExternalKeyboardLockViewManager.mm +11 -0
- package/ios/Views/RNCEKVExternalKeyboardView/RNCEKVExternalKeyboardView.h +7 -82
- package/ios/Views/RNCEKVExternalKeyboardView/RNCEKVExternalKeyboardView.mm +23 -493
- package/ios/Views/RNCEKVExternalKeyboardView/RNCEKVExternalKeyboardViewManager.mm +5 -7
- package/ios/Views/RNCEKVKeyboardFocusGroupView/RNCEKVKeyboardFocusGroup.mm +20 -17
- package/ios/Views/RNCEKVTextInputFocusWrapper/RNCEKVTextInputFocusWrapper.h +10 -39
- package/ios/Views/RNCEKVTextInputFocusWrapper/RNCEKVTextInputFocusWrapper.mm +40 -73
- package/ios/Views/RNCEKVTextInputFocusWrapper/RNCEKVTextInputFocusWrapperManager.mm +76 -8
- package/lib/commonjs/components/BaseKeyboardView/BaseKeyboardView.js +35 -7
- package/lib/commonjs/components/BaseKeyboardView/BaseKeyboardView.js.map +1 -1
- package/lib/commonjs/components/KeyboardExtendedInput/KeyboardExtendedInput.js +79 -1
- package/lib/commonjs/components/KeyboardExtendedInput/KeyboardExtendedInput.js.map +1 -1
- package/lib/commonjs/components/KeyboardFocusLock/FocusTrap/FocusTrap.js +18 -4
- package/lib/commonjs/components/KeyboardFocusLock/FocusTrap/FocusTrap.js.map +1 -1
- package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js +17 -2
- package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js.map +1 -1
- package/lib/commonjs/components/KeyboardFocusView/KeyboardFocusView.js +2 -0
- package/lib/commonjs/components/KeyboardFocusView/KeyboardFocusView.js.map +1 -1
- package/lib/commonjs/nativeSpec/ExternalKeyboardLockViewNativeComponent.ts +1 -0
- package/lib/commonjs/nativeSpec/TextInputFocusWrapperNativeComponent.ts +16 -0
- package/lib/commonjs/utils/useFocusStyle.js +3 -9
- package/lib/commonjs/utils/useFocusStyle.js.map +1 -1
- package/lib/commonjs/utils/withKeyboardFocus.js +32 -15
- package/lib/commonjs/utils/withKeyboardFocus.js.map +1 -1
- package/lib/commonjs/utils/wrapOrderPrefix.js +17 -0
- package/lib/commonjs/utils/wrapOrderPrefix.js.map +1 -0
- package/lib/module/components/BaseKeyboardView/BaseKeyboardView.js +35 -7
- package/lib/module/components/BaseKeyboardView/BaseKeyboardView.js.map +1 -1
- package/lib/module/components/KeyboardExtendedInput/KeyboardExtendedInput.js +80 -2
- package/lib/module/components/KeyboardExtendedInput/KeyboardExtendedInput.js.map +1 -1
- package/lib/module/components/KeyboardFocusLock/FocusTrap/FocusTrap.js +18 -4
- package/lib/module/components/KeyboardFocusLock/FocusTrap/FocusTrap.js.map +1 -1
- package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js +16 -2
- package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js.map +1 -1
- package/lib/module/components/KeyboardFocusView/KeyboardFocusView.js +2 -0
- package/lib/module/components/KeyboardFocusView/KeyboardFocusView.js.map +1 -1
- package/lib/module/nativeSpec/ExternalKeyboardLockViewNativeComponent.ts +1 -0
- package/lib/module/nativeSpec/TextInputFocusWrapperNativeComponent.ts +16 -0
- package/lib/module/utils/useFocusStyle.js +4 -10
- package/lib/module/utils/useFocusStyle.js.map +1 -1
- package/lib/module/utils/withKeyboardFocus.js +32 -15
- package/lib/module/utils/withKeyboardFocus.js.map +1 -1
- package/lib/module/utils/wrapOrderPrefix.js +12 -0
- package/lib/module/utils/wrapOrderPrefix.js.map +1 -0
- package/lib/typescript/src/components/BaseKeyboardView/BaseKeyboardView.d.ts.map +1 -1
- package/lib/typescript/src/components/KeyboardExtendedInput/KeyboardExtendedInput.d.ts.map +1 -1
- package/lib/typescript/src/components/KeyboardExtendedInput/KeyboardExtendedInput.types.d.ts +15 -0
- package/lib/typescript/src/components/KeyboardExtendedInput/KeyboardExtendedInput.types.d.ts.map +1 -1
- package/lib/typescript/src/components/KeyboardFocusLock/FocusTrap/FocusTrap.d.ts +1 -1
- package/lib/typescript/src/components/KeyboardFocusLock/FocusTrap/FocusTrap.d.ts.map +1 -1
- package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.d.ts +2 -1
- package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.d.ts.map +1 -1
- package/lib/typescript/src/components/KeyboardFocusView/KeyboardFocusView.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/nativeSpec/ExternalKeyboardLockViewNativeComponent.d.ts +1 -0
- package/lib/typescript/src/nativeSpec/ExternalKeyboardLockViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/nativeSpec/TextInputFocusWrapperNativeComponent.d.ts +16 -1
- package/lib/typescript/src/nativeSpec/TextInputFocusWrapperNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/types/BaseKeyboardView.d.ts +2 -0
- package/lib/typescript/src/types/BaseKeyboardView.d.ts.map +1 -1
- package/lib/typescript/src/types/KeyboardFocusLock.types.d.ts +1 -0
- package/lib/typescript/src/types/KeyboardFocusLock.types.d.ts.map +1 -1
- package/lib/typescript/src/types/WithKeyboardFocus.d.ts +11 -2
- package/lib/typescript/src/types/WithKeyboardFocus.d.ts.map +1 -1
- package/lib/typescript/src/utils/useFocusStyle.d.ts +1 -0
- package/lib/typescript/src/utils/useFocusStyle.d.ts.map +1 -1
- package/lib/typescript/src/utils/withKeyboardFocus.d.ts.map +1 -1
- package/lib/typescript/src/utils/wrapOrderPrefix.d.ts +9 -0
- package/lib/typescript/src/utils/wrapOrderPrefix.d.ts.map +1 -0
- package/package.json +6 -2
- package/src/components/BaseKeyboardView/BaseKeyboardView.tsx +88 -10
- package/src/components/KeyboardExtendedInput/KeyboardExtendedInput.tsx +138 -2
- package/src/components/KeyboardExtendedInput/KeyboardExtendedInput.types.ts +15 -0
- package/src/components/KeyboardFocusLock/FocusTrap/FocusTrap.tsx +21 -4
- package/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.tsx +20 -3
- package/src/components/KeyboardFocusView/KeyboardFocusView.tsx +2 -0
- package/src/nativeSpec/ExternalKeyboardLockViewNativeComponent.ts +1 -0
- package/src/nativeSpec/TextInputFocusWrapperNativeComponent.ts +16 -0
- package/src/types/BaseKeyboardView.ts +2 -0
- package/src/types/KeyboardFocusLock.types.ts +1 -0
- package/src/types/WithKeyboardFocus.ts +19 -2
- package/src/utils/useFocusStyle.tsx +5 -15
- package/src/utils/withKeyboardFocus.tsx +44 -15
- package/src/utils/wrapOrderPrefix.ts +16 -0
- package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusGuideDelegate/RNCEKVFocusGuideDelegate.h +0 -36
- package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusGuideDelegate/RNCEKVFocusGuideDelegate.mm +0 -150
- package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusOrderDelegate.h +0 -47
- package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusOrderDelegate.mm +0 -326
- package/ios/Services/RNCEKVKeyboardOrderManager/RNCEKVKeyboardOrderManager.h +0 -17
- package/ios/Services/RNCEKVKeyboardOrderManager/RNCEKVKeyboardOrderManager.mm +0 -15
- package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js +0 -22
- package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js.map +0 -1
- package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js +0 -17
- package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js.map +0 -1
- package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.d.ts +0 -4
- package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.d.ts.map +0 -1
- package/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.tsx +0 -16
package/README.md
CHANGED
|
@@ -15,50 +15,10 @@ iOS | Android
|
|
|
15
15
|
- Key press event handling.
|
|
16
16
|
- Focus management for `TextInput` and `Pressable` components.
|
|
17
17
|
- Customization of the `Halo Effect` and `tintColor` for iOS.
|
|
18
|
+
- `defaultFocusHighlightEnabled` support for Android.
|
|
18
19
|
- Keyboard focus order.
|
|
19
20
|
- Focus Lock.
|
|
20
21
|
|
|
21
|
-
## New Release: Focus Lock
|
|
22
|
-
|
|
23
|
-
| iOS | Android |
|
|
24
|
-
| :-- | :-- |
|
|
25
|
-
| <img src="/.github/images/rnek-focus-lock-ios.gif" height="500" /> | <img src="/.github/images/rnek-focus-lock-android.gif" height="500" /> |
|
|
26
|
-
|
|
27
|
-
> A new type of focus lock functionality has been introduced, featuring two new components: `Focus.Frame` and `Focus.Trap`. These components help manage and lock focus within specific areas of the screen.
|
|
28
|
-
|
|
29
|
-
<details>
|
|
30
|
-
<summary>More Information</summary>
|
|
31
|
-
|
|
32
|
-
- On iOS, `Focus.Trap` uses the native `accessibilityViewIsModal` property to keep the focus within a defined area.
|
|
33
|
-
- On Android, where no equivalent to `accessibilityViewIsModal` exists, custom logic has been implemented as a workaround. By default, Android uses a custom Activity or Modal to limit focus. While using a Modal is considered the best practice for focus locking on Android, some scenarios—such as issues with React Native's Modal or library-specific constraints—may require alternative implementations.
|
|
34
|
-
|
|
35
|
-
#### How It Works
|
|
36
|
-
|
|
37
|
-
The focus lock functionality should be used as a pair:
|
|
38
|
-
|
|
39
|
-
- `Focus.Frame`: This component is used at the root level of a "screen" to detect focus leaks and ensure that focus remains contained.
|
|
40
|
-
- `Focus.Trap`: This component wraps the content area where focus should be explicitly locked.
|
|
41
|
-
|
|
42
|
-
| Prop | Description |
|
|
43
|
-
| :-- | :-- |
|
|
44
|
-
| ViewProps | Includes all standard React Native View properties, such as style, testID, etc. |
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
<Focus.Frame>
|
|
48
|
-
...
|
|
49
|
-
<Focus.Trap>
|
|
50
|
-
<Text accessibilityRole="header">Locked Area</Text>
|
|
51
|
-
<Button
|
|
52
|
-
title="Confirm"
|
|
53
|
-
accessibilityLabel="Confirm action"
|
|
54
|
-
/>
|
|
55
|
-
</Focus.Trap>
|
|
56
|
-
...
|
|
57
|
-
</Focus.Frame>
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
</details>
|
|
61
|
-
|
|
62
22
|
## Installation
|
|
63
23
|
|
|
64
24
|
```sh
|
|
@@ -79,9 +39,9 @@ cd ios && pod install && cd ..
|
|
|
79
39
|
The `withKeyboardFocus` HOC is a helper for integrating keyboard focus functionality. It significantly simplifies integration by wrapping the provided component in `KeyboardFocusView` and extending it with additional features, such as focus and blur events.
|
|
80
40
|
|
|
81
41
|
```js
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
42
|
+
const KeyboardPressable = withKeyboardFocus(Pressable);
|
|
43
|
+
const KeyboardTouchable = withKeyboardFocus(TouchableOpacity);
|
|
44
|
+
const KeyboardButton = withKeyboardFocus(Button);
|
|
85
45
|
|
|
86
46
|
...
|
|
87
47
|
|
|
@@ -123,8 +83,14 @@ focusable?: | Indicates if the component can be focused by keyboard | `boolean |
|
|
|
123
83
|
tintColor?: | Color used for tinting the component | `string`
|
|
124
84
|
tintType?: | Tint behavior type | `'default' \| 'hover' \| 'background' \| 'none'`
|
|
125
85
|
FocusHoverComponent?: | Component displayed on focus | `\| ReactElement \| FunctionComponent \| (() => ReactElement);`
|
|
86
|
+
renderContent?: | Render prop for components whose `children` is itself a render function (e.g. `Pressable`). Receives the component's own render state merged with `{ focused: boolean }`, so you can style content based on both the component state (e.g. `pressed`) and keyboard focus simultaneously. Only available when the wrapped component exposes a render-prop `children`. | `(state: ComponentRenderState & { focused: boolean }) => ReactNode`
|
|
87
|
+
renderFocusable?: | Render prop available on any `withKeyboardFocus`-wrapped component. Replaces `children` and receives `{ focused: boolean }`, allowing you to render different content based on keyboard focus state. Use this when the wrapped component does not expose a render-prop `children`. | `(state: { focused: boolean }) => ReactNode`
|
|
126
88
|
group?: | Indicates if the component is a focusable group | `boolean`
|
|
127
89
|
haloEffect?: | Enables halo effect on focus (iOS only) | `boolean`
|
|
90
|
+
defaultFocusHighlightEnabled?: | **Android only.** Enables Android's default focus highlight for the focused native view. | `boolean \| undefined`, default: `true`
|
|
91
|
+
haloCornerRadius?: | Corner radius of the halo ring (iOS only) | `number`
|
|
92
|
+
haloExpendX?: | Horizontal expansion of the halo ring in points (iOS only) | `number`
|
|
93
|
+
haloExpendY?: | Vertical expansion of the halo ring in points (iOS only) | `number`
|
|
128
94
|
ref?: | Provides a reference to the component, allowing programmatic focus control | `{ focus: () => void}`
|
|
129
95
|
viewRef?: | Provides a reference to the underlying view component | `RefObject<View>`
|
|
130
96
|
onBubbledContextMenuPress | Handler for bubbled long-press events triggered by the context menu command (iOS only) | () => void;
|
|
@@ -140,12 +106,51 @@ orderLeft? | ID of the target for navigation to the left. | `string`
|
|
|
140
106
|
orderRight? | ID of the target for navigation to the right. | `string`
|
|
141
107
|
orderUp? | ID of the target for navigation upward. | `string`
|
|
142
108
|
orderDown? | ID of the target for navigation downward. | `string`
|
|
109
|
+
orderGroup? | The name of the group for index-based focus ordering. | `string`
|
|
110
|
+
orderIndex? | The order index of the element within its group. | `number`
|
|
143
111
|
lockFocus? | An array of directions to lock focus. | Array of 'left' \| 'right' \| 'up' \| 'down' \| 'forward' \| 'backward' \| 'first' \| 'last'
|
|
144
112
|
...rest | Remaining component props | `Type of Component`
|
|
145
113
|
|
|
114
|
+
#### renderContent — Pressable with pressed + focused state
|
|
115
|
+
|
|
116
|
+
`Pressable` passes a `{ pressed }` state to its `children` render prop. Use `renderContent` to access both `pressed` and the keyboard `focused` state at the same time:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
const KeyboardPressable = withKeyboardFocus(Pressable);
|
|
120
|
+
|
|
121
|
+
<KeyboardPressable
|
|
122
|
+
onPress={onPress}
|
|
123
|
+
renderContent={({ pressed, focused }) => (
|
|
124
|
+
<View style={[
|
|
125
|
+
styles.button,
|
|
126
|
+
pressed && styles.pressed,
|
|
127
|
+
focused && styles.focused,
|
|
128
|
+
]}>
|
|
129
|
+
<Text>{pressed ? 'Pressed' : focused ? 'Focused' : 'Default'}</Text>
|
|
130
|
+
</View>
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### renderFocusable — TouchableOpacity and other components
|
|
136
|
+
|
|
137
|
+
`TouchableOpacity` and similar components do not expose a render-prop `children`, so `renderContent` is not available. Use `renderFocusable` instead — it receives only `{ focused }`:
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
const KeyboardTouchable = withKeyboardFocus(TouchableOpacity);
|
|
141
|
+
|
|
142
|
+
<KeyboardTouchable
|
|
143
|
+
onPress={onPress}
|
|
144
|
+
renderFocusable={({ focused }) => (
|
|
145
|
+
<View style={[styles.button, focused && styles.focused]}>
|
|
146
|
+
<Text>{focused ? 'Focused' : 'Default'}</Text>
|
|
147
|
+
</View>
|
|
148
|
+
)}
|
|
149
|
+
/>
|
|
150
|
+
```
|
|
146
151
|
|
|
147
152
|
> [!NOTE]
|
|
148
|
-
> You may discover that `long press on spacebar` does not trigger a long press event on `iOS`. This is because `iOS`
|
|
153
|
+
> You may discover that `long press on spacebar` does not trigger a long press event on `iOS`. This is because `iOS` uses the `Full Keyboard Access` system that provides commands for interacting with the system. Rather than holding down the spacebar, you can use `Tab+M` (the default action for opening the context menu).
|
|
149
154
|
> You can change `Commands` in: `Full Keyboard Access` -> `Commands`
|
|
150
155
|
|
|
151
156
|
|
|
@@ -187,6 +192,10 @@ tintType?: | Tint behavior type | `'default' \| 'hover' \| 'background' \| 'none
|
|
|
187
192
|
FocusHoverComponent?: | Component displayed on focus | `\| ReactElement \| FunctionComponent \| (() => ReactElement);`
|
|
188
193
|
group?: | Indicates if the component is a focusable group | `boolean`
|
|
189
194
|
haloEffect?: | Enables halo effect on focus (iOS only) | `boolean`
|
|
195
|
+
defaultFocusHighlightEnabled?: | **Android only.** Enables Android's default focus highlight for the focused native view. | `boolean \| undefined`, default: `true`
|
|
196
|
+
haloCornerRadius?: | Corner radius of the halo ring (iOS only) | `number`
|
|
197
|
+
haloExpendX?: | Horizontal expansion of the halo ring in points (iOS only) | `number`
|
|
198
|
+
haloExpendY?: | Vertical expansion of the halo ring in points (iOS only) | `number`
|
|
190
199
|
triggerCodes?: | `onPress` and `onLongPress` trigger codes | `number[] \| undefined`, spacebar and enter by default
|
|
191
200
|
enableA11yFocus?: | Can be used to move the screen reader focus within the keyboard using `ref.current.focus`. | `boolean \| undefined`
|
|
192
201
|
screenAutoA11yFocus?: | Enables screen reader auto-focus functionality. | `boolean \| undefined`
|
|
@@ -199,6 +208,8 @@ orderLeft? | ID of the target for navigation to the left. | `string`
|
|
|
199
208
|
orderRight? | ID of the target for navigation to the right. | `string`
|
|
200
209
|
orderUp? | ID of the target for navigation upward. | `string`
|
|
201
210
|
orderDown? | ID of the target for navigation downward. | `string`
|
|
211
|
+
orderGroup? | The name of the group for index-based focus ordering. | `string`
|
|
212
|
+
orderIndex? | The order index of the element within its group. | `number`
|
|
202
213
|
lockFocus? | An array of directions to lock focus. | Array of 'left' \| 'right' \| 'up' \| 'down' \| 'forward' \| 'backward' \| 'first' \| 'last'
|
|
203
214
|
...rest | Remaining View props | `View`
|
|
204
215
|
|
|
@@ -225,6 +236,7 @@ onFocusChange?: | Callback for focus change handling | `(isFocused: boolean) =>
|
|
|
225
236
|
focusType?: | Focus type can be `default`, `auto`, or `press`. Based on investigation, Android and iOS typically have different default behaviors. On Android, the `TextInput` is focused by default, while on iOS, you need to press to focus. `auto` is used for automatic focusing, while keyboard focus targets the input. With `press`, you need to press the spacebar to focus an input. | `"default" \\| "press" \\| "auto"`
|
|
226
237
|
blurType?: | Only for iOS. This defines the behavior for blurring input when focus moves away from the component. By default, iOS allows typing when the keyboard focus is on another component. You can use disable to blur input when focus moves away. (Further investigation is needed for Android.) | `"default"\\| "disable" \\| "auto"`
|
|
227
238
|
haloEffect?: | Enables halo effect on focus (iOS only) | `boolean`
|
|
239
|
+
defaultFocusHighlightEnabled?: | **Android only.** Enables Android's default focus highlight for the focused native view. | `boolean \| undefined`, default: `true`
|
|
228
240
|
tintColor?: | Color used for tinting the component | `string`
|
|
229
241
|
style?: | Style for the inner TextInput | `StyleProp<ViewStyle>`
|
|
230
242
|
focusStyle? | Style applied to the inner TextInput when focused | `FocusStyle`
|
|
@@ -263,6 +275,9 @@ onKeyDownPress | Handler for the key-down event | `(e: OnKeyPress) => void`
|
|
|
263
275
|
onContextMenuPress?: | Handler for long press events triggered by the context menu command (iOS only) | () => void;
|
|
264
276
|
onBubbledContextMenuPress | Handler for bubbled long-press events triggered by the context menu command (iOS only) | () => void;
|
|
265
277
|
haloEffect | Enables halo effect on focus (iOS only) | `boolean \| undefined`
|
|
278
|
+
haloCornerRadius? | Corner radius of the halo ring (iOS only) | `number`
|
|
279
|
+
haloExpendX? | Horizontal expansion of the halo ring in points (iOS only) | `number`
|
|
280
|
+
haloExpendY? | Vertical expansion of the halo ring in points (iOS only) | `number`
|
|
266
281
|
autoFocus | Indicates if the component should automatically gain focus | `boolean \| undefined`
|
|
267
282
|
tintColor | Color used for tinting the component | `string`
|
|
268
283
|
ref->focus | Command to programmatically focus the component | () => void;
|
|
@@ -328,6 +343,40 @@ import { Keyboard } from 'react-native-external-keyboard';
|
|
|
328
343
|
|
|
329
344
|
This is needed for hiding the soft keyboard using a hardware keyboard. Additionally, the soft keyboard can be hidden from the settings or by pressing `Alt + K`.
|
|
330
345
|
|
|
346
|
+
### Focus.Frame and Focus.Trap
|
|
347
|
+
|
|
348
|
+
`Focus.Frame` and `Focus.Trap` are two components that help manage and lock focus within specific areas of the screen.
|
|
349
|
+
|
|
350
|
+
- On iOS, `Focus.Trap` uses the native `accessibilityViewIsModal` property to keep the screen reader focus within a defined area. For stronger containment — such as preventing focus from reaching system elements like navigation bars or headers — pass `forceLock`. It observes focus changes and moves focus back into the trap when it escapes. Note: because focus is corrected reactively, VoiceOver may briefly jump to an element outside the trap before being returned.
|
|
351
|
+
- On Android, where no equivalent to `accessibilityViewIsModal` exists, custom logic has been implemented as a workaround. By default, Android uses a custom Activity or Modal to limit focus. While using a Modal is considered the best practice for focus locking on Android, some scenarios—such as issues with React Native's Modal or library-specific constraints—may require alternative implementations.
|
|
352
|
+
|
|
353
|
+
#### How It Works
|
|
354
|
+
|
|
355
|
+
The focus lock functionality should be used as a pair:
|
|
356
|
+
|
|
357
|
+
- `Focus.Frame`: This component is used at the root level of a "screen" to detect focus leaks and ensure that focus remains contained.
|
|
358
|
+
- `Focus.Trap`: This component wraps the content area where focus should be explicitly locked.
|
|
359
|
+
|
|
360
|
+
| Prop | Description |
|
|
361
|
+
| :-- | :-- |
|
|
362
|
+
| ViewProps | Includes all standard React Native View properties, such as style, testID, etc. |
|
|
363
|
+
| forceLock? | **iOS only.** Strengthens focus containment beyond `accessibilityViewIsModal` by observing focus changes and returning focus back into the trap whenever it escapes to system elements (e.g. navigation bars, headers). Note: because focus is corrected reactively, VoiceOver may briefly jump to an element outside the trap before being returned. |
|
|
364
|
+
| lockDisabled? | **Android only.** Disables the focus lock when `true`. |
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
<Focus.Frame>
|
|
368
|
+
...
|
|
369
|
+
<Focus.Trap forceLock>
|
|
370
|
+
<Text accessibilityRole="header">Locked Area</Text>
|
|
371
|
+
<Button
|
|
372
|
+
title="Confirm"
|
|
373
|
+
accessibilityLabel="Confirm action"
|
|
374
|
+
/>
|
|
375
|
+
</Focus.Trap>
|
|
376
|
+
...
|
|
377
|
+
</Focus.Frame>
|
|
378
|
+
```
|
|
379
|
+
|
|
331
380
|
## Focus order features
|
|
332
381
|
|
|
333
382
|
## Link Focus Order
|
|
@@ -361,6 +410,37 @@ This is needed for hiding the soft keyboard using a hardware keyboard. Additiona
|
|
|
361
410
|
</View>
|
|
362
411
|
```
|
|
363
412
|
|
|
413
|
+
> [!IMPORTANT]
|
|
414
|
+
> `orderId` values are global. If the same IDs appear more than once on screen — e.g. in a list where each row renders the same component — duplicate IDs will cause incorrect focus jumps. Use `KeyboardOrderFocusGroup` or `orderPrefix` to keep IDs unique per instance.
|
|
415
|
+
>
|
|
416
|
+
> When a link prop is used without any prefix, a console warning is shown reminding you to add one.
|
|
417
|
+
>
|
|
418
|
+
> **`KeyboardOrderFocusGroup` (auto namespace).** Wraps a component tree and automatically namespaces all `orderId` values inside. Ideal for screens and containers where uniqueness is needed but the exact prefix doesn't matter.
|
|
419
|
+
>
|
|
420
|
+
> ```tsx
|
|
421
|
+
> // Each card gets its own isolated namespace
|
|
422
|
+
> {items.map((item) => (
|
|
423
|
+
> <KeyboardOrderFocusGroup key={item.id}>
|
|
424
|
+
> <Pressable orderId="title" orderForward="action">…</Pressable>
|
|
425
|
+
> <Pressable orderId="action" orderBackward="title">…</Pressable>
|
|
426
|
+
> </KeyboardOrderFocusGroup>
|
|
427
|
+
> ))}
|
|
428
|
+
> ```
|
|
429
|
+
>
|
|
430
|
+
> **Static namespace with `groupId` / `orderPrefix`.** Use an explicit string when you need a stable, predictable namespace — for example, to create intentional focus links between two sibling components that know about each other.
|
|
431
|
+
>
|
|
432
|
+
> ```tsx
|
|
433
|
+
> // groupId on KeyboardOrderFocusGroup
|
|
434
|
+
> <KeyboardOrderFocusGroup groupId="card_42">
|
|
435
|
+
> <Pressable orderId="title" orderForward="action">…</Pressable>
|
|
436
|
+
> <Pressable orderId="action" orderBackward="title">…</Pressable>
|
|
437
|
+
> </KeyboardOrderFocusGroup>
|
|
438
|
+
>
|
|
439
|
+
> // or orderPrefix directly on each component
|
|
440
|
+
> <Pressable orderPrefix="card_42" orderId="title" orderForward="action">…</Pressable>
|
|
441
|
+
> <Pressable orderPrefix="card_42" orderId="action" orderBackward="title">…</Pressable>
|
|
442
|
+
> ```
|
|
443
|
+
|
|
364
444
|
You can find more examples here: [Focus Link Order](https://github.com/ArturKalach/react-native-external-keyboard/blob/release/0.6.0-rc/example/src/components/FocusOrderExample/FocusLinkOrder.tsx), [DPad Order](https://github.com/ArturKalach/react-native-external-keyboard/blob/release/0.6.0-rc/example/src/components/FocusOrderExample/FocusDPadOrder.tsx)
|
|
365
445
|
|
|
366
446
|
| Props | Description | Type |
|
|
@@ -372,45 +452,48 @@ You can find more examples here: [Focus Link Order](https://github.com/ArturKala
|
|
|
372
452
|
| orderRight? | ID of the target for navigation to the right. | `string` |
|
|
373
453
|
| orderUp? | ID of the target for navigation upward. | `string` |
|
|
374
454
|
| orderDown? | ID of the target for navigation downward. | `string` |
|
|
455
|
+
| orderPrefix? | Prefix prepended to this component's `orderId` and all `order*` target IDs. Use to namespace IDs in repeated components (lists, cards) or alongside a static `groupId`. | `string` |
|
|
375
456
|
|
|
376
457
|
## Indexes Focus Order
|
|
377
458
|
|
|
378
|
-
Linking is one of the best ways to set up focus order. However, there may be cases where you need to define the order of multiple elements
|
|
459
|
+
Linking is one of the best ways to set up focus order. However, there may be cases where you need to define the order of multiple elements within a group. As an alternative, you can use index-based ordering.
|
|
460
|
+
|
|
461
|
+
### KeyboardOrderFocusGroup
|
|
462
|
+
|
|
463
|
+
`KeyboardOrderFocusGroup` is a context provider that defines a named focus group. All children that declare `orderIndex` will be ordered within that group. You can optionally provide a `groupId`; if omitted, a unique ID is generated automatically.
|
|
379
464
|
|
|
380
465
|
```tsx
|
|
466
|
+
import { KeyboardOrderFocusGroup } from 'react-native-external-keyboard';
|
|
467
|
+
|
|
381
468
|
<KeyboardOrderFocusGroup>
|
|
382
469
|
<View>
|
|
383
|
-
<Pressable
|
|
384
|
-
onPress={onPress}
|
|
385
|
-
orderIndex={0}
|
|
386
|
-
>
|
|
470
|
+
<Pressable onPress={onPress} orderIndex={0}>
|
|
387
471
|
<Text>First</Text>
|
|
388
472
|
</Pressable>
|
|
389
|
-
<Pressable
|
|
390
|
-
onPress={onPress}
|
|
391
|
-
orderIndex={2}
|
|
392
|
-
>
|
|
473
|
+
<Pressable onPress={onPress} orderIndex={2}>
|
|
393
474
|
<Text>Third</Text>
|
|
394
475
|
</Pressable>
|
|
395
|
-
<Pressable
|
|
396
|
-
onPress={onPress}
|
|
397
|
-
orderIndex={1}
|
|
398
|
-
>
|
|
476
|
+
<Pressable onPress={onPress} orderIndex={1}>
|
|
399
477
|
<Text>Second</Text>
|
|
400
478
|
</Pressable>
|
|
401
479
|
</View>
|
|
402
480
|
</KeyboardOrderFocusGroup>
|
|
403
481
|
```
|
|
404
|
-
|
|
482
|
+
|
|
483
|
+
| Props | Description | Type |
|
|
484
|
+
| :-- | :-- | :-- |
|
|
485
|
+
| groupId? | Optional explicit group name. Auto-generated when omitted. | `string` |
|
|
486
|
+
| children? | Child components | `ReactNode` |
|
|
487
|
+
|
|
488
|
+
Alternatively, provide `orderGroup` directly on each component to skip the wrapper:
|
|
405
489
|
|
|
406
490
|
```tsx
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
</Pressable>
|
|
491
|
+
<Pressable orderGroup="main" onPress={onPress} orderIndex={0}>
|
|
492
|
+
<Text>First</Text>
|
|
493
|
+
</Pressable>
|
|
494
|
+
<Pressable orderGroup="main" onPress={onPress} orderIndex={1}>
|
|
495
|
+
<Text>Second</Text>
|
|
496
|
+
</Pressable>
|
|
414
497
|
```
|
|
415
498
|
|
|
416
499
|
| Props | Description | Type |
|
|
@@ -418,7 +501,6 @@ Indexing requres `orderGroup` param for proper order set, you can use `Keyboard
|
|
|
418
501
|
| orderGroup? | The name of the group containing ordered elements. | `string` |
|
|
419
502
|
| orderIndex? | The order index of the element within the group. | `number` |
|
|
420
503
|
|
|
421
|
-
|
|
422
504
|
You can find more examples here: [Focus Order via indexes](https://github.com/ArturKalach/react-native-external-keyboard/blob/release/0.6.0-rc/example/src/components/FocusOrderExample/FocusOrder.tsx)
|
|
423
505
|
|
|
424
506
|
## Focus Lock
|
|
@@ -5,10 +5,22 @@ import android.view.View;
|
|
|
5
5
|
import com.externalkeyboard.helper.Linking.A11yOrderLinking;
|
|
6
6
|
import com.externalkeyboard.services.FocusLinkObserver.FocusLinkObserver;
|
|
7
7
|
import com.externalkeyboard.services.FocusLinkObserver.FocusLinkObserverSingleton;
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
import java.lang.ref.WeakReference;
|
|
9
10
|
|
|
10
11
|
public class FocusOrderDelegate {
|
|
11
|
-
private final
|
|
12
|
+
private final FocusOrderDelegateHost delegate;
|
|
13
|
+
|
|
14
|
+
// The view that was registered during the last link() call. Stored here so
|
|
15
|
+
// that unlink() does not depend on getFirstChild() returning the same value
|
|
16
|
+
// (it may have changed by the time unlink() is called, e.g. after reactEditText
|
|
17
|
+
// is cleared or focusType switches between FOCUS_BY_PRESS and regular focus).
|
|
18
|
+
// WeakReference so a detached view is not kept alive by this delegate.
|
|
19
|
+
private WeakReference<View> linkedChild = null;
|
|
20
|
+
|
|
21
|
+
private View getLinkedChild() {
|
|
22
|
+
return linkedChild != null ? linkedChild.get() : null;
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
FocusLinkObserver.LinkUpdatedCallback leftUpdated = null;
|
|
14
26
|
FocusLinkObserver.LinkRemovedCallback leftRemoved = null;
|
|
@@ -19,14 +31,15 @@ public class FocusOrderDelegate {
|
|
|
19
31
|
FocusLinkObserver.LinkUpdatedCallback downUpdated = null;
|
|
20
32
|
FocusLinkObserver.LinkRemovedCallback downRemoved = null;
|
|
21
33
|
|
|
22
|
-
|
|
23
|
-
public FocusOrderDelegate(ExternalKeyboardView delegate) {
|
|
34
|
+
public FocusOrderDelegate(FocusOrderDelegateHost delegate) {
|
|
24
35
|
super();
|
|
25
36
|
this.delegate = delegate;
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
public void link() {
|
|
29
40
|
View child = delegate.getFirstChild();
|
|
41
|
+
this.linkedChild = child != null ? new WeakReference<>(child) : null;
|
|
42
|
+
|
|
30
43
|
String orderGroup = delegate.getOrderGroup();
|
|
31
44
|
Integer orderIndex = delegate.getOrderIndex();
|
|
32
45
|
String orderId = delegate.getOrderId();
|
|
@@ -41,6 +54,8 @@ public class FocusOrderDelegate {
|
|
|
41
54
|
A11yOrderLinking.getInstance().addOrderLink(child, orderId);
|
|
42
55
|
}
|
|
43
56
|
|
|
57
|
+
// Lambdas capture the local strong reference — fine for the subscription lifetime;
|
|
58
|
+
// subscriptions are released in unlink().
|
|
44
59
|
if (delegate.getOrderLeft() != null && child != null) {
|
|
45
60
|
leftUpdated = link -> child.setNextFocusLeftId(link.getId());
|
|
46
61
|
leftRemoved = () -> child.setNextFocusLeftId(View.NO_ID);
|
|
@@ -66,8 +81,44 @@ public class FocusOrderDelegate {
|
|
|
66
81
|
}
|
|
67
82
|
}
|
|
68
83
|
|
|
84
|
+
public void unlink() {
|
|
85
|
+
View child = getLinkedChild();
|
|
86
|
+
String orderGroup = delegate.getOrderGroup();
|
|
87
|
+
Integer orderIndex = delegate.getOrderIndex();
|
|
88
|
+
String orderId = delegate.getOrderId();
|
|
89
|
+
|
|
90
|
+
if (orderGroup != null && orderIndex != null) {
|
|
91
|
+
A11yOrderLinking.getInstance().removeRelationship(orderGroup, orderIndex);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (orderId != null) {
|
|
95
|
+
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
96
|
+
View storedView = A11yOrderLinking.getInstance().getOrderLink(orderId);
|
|
97
|
+
if (storedView == child) {
|
|
98
|
+
observer.emitRemove(orderId);
|
|
99
|
+
A11yOrderLinking.getInstance().removeOrderLink(orderId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
104
|
+
if (delegate.getOrderLeft() != null) {
|
|
105
|
+
observer.unsubscribe(delegate.getOrderLeft(), leftUpdated, leftRemoved);
|
|
106
|
+
}
|
|
107
|
+
if (delegate.getOrderRight() != null) {
|
|
108
|
+
observer.unsubscribe(delegate.getOrderRight(), rightUpdated, rightRemoved);
|
|
109
|
+
}
|
|
110
|
+
if (delegate.getOrderUp() != null) {
|
|
111
|
+
observer.unsubscribe(delegate.getOrderUp(), upUpdated, upRemoved);
|
|
112
|
+
}
|
|
113
|
+
if (delegate.getOrderDown() != null) {
|
|
114
|
+
observer.unsubscribe(delegate.getOrderDown(), downUpdated, downRemoved);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.linkedChild = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
69
120
|
public void refreshOrder() {
|
|
70
|
-
View child =
|
|
121
|
+
View child = getLinkedChild();
|
|
71
122
|
String orderGroup = delegate.getOrderGroup();
|
|
72
123
|
Integer orderIndex = delegate.getOrderIndex();
|
|
73
124
|
|
|
@@ -78,15 +129,13 @@ public class FocusOrderDelegate {
|
|
|
78
129
|
|
|
79
130
|
public void refreshLeft(String prev, String next) {
|
|
80
131
|
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
132
|
+
View child = getLinkedChild();
|
|
81
133
|
if (prev != null && leftUpdated != null && leftRemoved != null) {
|
|
82
134
|
observer.unsubscribe(prev, leftUpdated, leftRemoved);
|
|
83
|
-
|
|
84
|
-
if(child != null) {
|
|
135
|
+
if (child != null) {
|
|
85
136
|
child.setNextFocusLeftId(View.NO_ID);
|
|
86
137
|
}
|
|
87
138
|
}
|
|
88
|
-
|
|
89
|
-
View child = delegate.getFirstChild();
|
|
90
139
|
if (next != null && child != null) {
|
|
91
140
|
leftUpdated = link -> child.setNextFocusLeftId(link.getId());
|
|
92
141
|
leftRemoved = () -> child.setNextFocusLeftId(View.NO_ID);
|
|
@@ -96,15 +145,13 @@ public class FocusOrderDelegate {
|
|
|
96
145
|
|
|
97
146
|
public void refreshRight(String prev, String next) {
|
|
98
147
|
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
99
|
-
|
|
148
|
+
View child = getLinkedChild();
|
|
149
|
+
if (prev != null && rightUpdated != null && rightRemoved != null) {
|
|
100
150
|
observer.unsubscribe(prev, rightUpdated, rightRemoved);
|
|
101
|
-
|
|
102
|
-
if(child != null) {
|
|
151
|
+
if (child != null) {
|
|
103
152
|
child.setNextFocusRightId(View.NO_ID);
|
|
104
153
|
}
|
|
105
154
|
}
|
|
106
|
-
|
|
107
|
-
View child = delegate.getFirstChild();
|
|
108
155
|
if (next != null && child != null) {
|
|
109
156
|
rightUpdated = link -> child.setNextFocusRightId(link.getId());
|
|
110
157
|
rightRemoved = () -> child.setNextFocusRightId(View.NO_ID);
|
|
@@ -114,16 +161,13 @@ public class FocusOrderDelegate {
|
|
|
114
161
|
|
|
115
162
|
public void refreshUp(String prev, String next) {
|
|
116
163
|
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
164
|
+
View child = getLinkedChild();
|
|
117
165
|
if (prev != null && upUpdated != null && upRemoved != null) {
|
|
118
166
|
observer.unsubscribe(prev, upUpdated, upRemoved);
|
|
119
|
-
|
|
120
|
-
View child = delegate.getFirstChild();
|
|
121
|
-
if(child != null) {
|
|
167
|
+
if (child != null) {
|
|
122
168
|
child.setNextFocusUpId(View.NO_ID);
|
|
123
169
|
}
|
|
124
170
|
}
|
|
125
|
-
|
|
126
|
-
View child = delegate.getFirstChild();
|
|
127
171
|
if (next != null && child != null) {
|
|
128
172
|
upUpdated = link -> child.setNextFocusUpId(link.getId());
|
|
129
173
|
upRemoved = () -> child.setNextFocusUpId(View.NO_ID);
|
|
@@ -131,27 +175,15 @@ public class FocusOrderDelegate {
|
|
|
131
175
|
}
|
|
132
176
|
}
|
|
133
177
|
|
|
134
|
-
public void updateOrderGroup(String prev, String next) {
|
|
135
|
-
View child = delegate.getFirstChild();
|
|
136
|
-
Integer position = delegate.getOrderIndex();
|
|
137
|
-
|
|
138
|
-
if (child != null && position != null) {
|
|
139
|
-
A11yOrderLinking.getInstance().updateGroup(prev, next, position, child);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
178
|
public void refreshDown(String prev, String next) {
|
|
144
179
|
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
180
|
+
View child = getLinkedChild();
|
|
145
181
|
if (prev != null && downUpdated != null && downRemoved != null) {
|
|
146
182
|
observer.unsubscribe(prev, downUpdated, downRemoved);
|
|
147
|
-
|
|
148
|
-
View child = delegate.getFirstChild();
|
|
149
|
-
if(child != null) {
|
|
183
|
+
if (child != null) {
|
|
150
184
|
child.setNextFocusDownId(View.NO_ID);
|
|
151
185
|
}
|
|
152
186
|
}
|
|
153
|
-
|
|
154
|
-
View child = delegate.getFirstChild();
|
|
155
187
|
if (next != null && child != null) {
|
|
156
188
|
downUpdated = link -> child.setNextFocusDownId(link.getId());
|
|
157
189
|
downRemoved = () -> child.setNextFocusDownId(View.NO_ID);
|
|
@@ -159,44 +191,11 @@ public class FocusOrderDelegate {
|
|
|
159
191
|
}
|
|
160
192
|
}
|
|
161
193
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (orderGroup != null && orderIndex != null) {
|
|
169
|
-
A11yOrderLinking.getInstance().removeRelationship(orderGroup, orderIndex);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (orderId != null) {
|
|
173
|
-
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
174
|
-
View storedView = A11yOrderLinking.getInstance().getOrderLink(orderId);
|
|
175
|
-
if(storedView == unlinkView) {
|
|
176
|
-
observer.emitRemove(orderId);
|
|
177
|
-
A11yOrderLinking.getInstance().removeOrderLink(orderId);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (delegate.getOrderLeft() != null) {
|
|
183
|
-
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
184
|
-
observer.unsubscribe(delegate.getOrderLeft(), leftUpdated, leftRemoved);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (delegate.getOrderRight() != null) {
|
|
188
|
-
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
189
|
-
observer.unsubscribe(delegate.getOrderRight(), rightUpdated, rightRemoved);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (delegate.getOrderUp() != null) {
|
|
193
|
-
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
194
|
-
observer.unsubscribe(delegate.getOrderUp(), upUpdated, upRemoved);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (delegate.getOrderDown() != null) {
|
|
198
|
-
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
199
|
-
observer.unsubscribe(delegate.getOrderDown(), downUpdated, downRemoved);
|
|
194
|
+
public void updateOrderGroup(String prev, String next) {
|
|
195
|
+
View child = getLinkedChild();
|
|
196
|
+
Integer position = delegate.getOrderIndex();
|
|
197
|
+
if (child != null && position != null) {
|
|
198
|
+
A11yOrderLinking.getInstance().updateGroup(prev, next, position, child);
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
201
|
|
|
@@ -204,10 +203,17 @@ public class FocusOrderDelegate {
|
|
|
204
203
|
return A11yOrderLinking.getInstance().getOrderLink(linkId);
|
|
205
204
|
}
|
|
206
205
|
|
|
207
|
-
public void
|
|
208
|
-
if(
|
|
209
|
-
|
|
206
|
+
public void cleanByOrderId(String orderId) {
|
|
207
|
+
if (orderId == null) return;
|
|
208
|
+
|
|
209
|
+
String orderGroup = delegate.getOrderGroup();
|
|
210
|
+
Integer orderIndex = delegate.getOrderIndex();
|
|
211
|
+
if (orderGroup != null && orderIndex != null) {
|
|
212
|
+
A11yOrderLinking.getInstance().removeRelationship(orderGroup, orderIndex);
|
|
210
213
|
}
|
|
211
|
-
}
|
|
212
214
|
|
|
215
|
+
FocusLinkObserver observer = FocusLinkObserverSingleton.getInstance();
|
|
216
|
+
observer.emitRemove(orderId);
|
|
217
|
+
A11yOrderLinking.getInstance().removeOrderLink(orderId);
|
|
218
|
+
}
|
|
213
219
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.externalkeyboard.delegates;
|
|
2
|
+
|
|
3
|
+
import android.view.View;
|
|
4
|
+
|
|
5
|
+
public interface FocusOrderDelegateHost {
|
|
6
|
+
View getFirstChild();
|
|
7
|
+
String getOrderGroup();
|
|
8
|
+
Integer getOrderIndex();
|
|
9
|
+
String getOrderId();
|
|
10
|
+
String getOrderLeft();
|
|
11
|
+
String getOrderRight();
|
|
12
|
+
String getOrderUp();
|
|
13
|
+
String getOrderDown();
|
|
14
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.externalkeyboard.helper.Linking;
|
|
2
2
|
|
|
3
|
+
import android.util.Log;
|
|
3
4
|
import android.view.View;
|
|
4
5
|
|
|
5
6
|
import java.util.HashMap;
|
|
@@ -59,6 +60,10 @@ public class A11yOrderLinking {
|
|
|
59
60
|
if (queue == null) return;
|
|
60
61
|
|
|
61
62
|
queue.removeFromOrder(index);
|
|
63
|
+
|
|
64
|
+
if (queue.isEmpty()) {
|
|
65
|
+
relationships.remove(key);
|
|
66
|
+
}
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
public void updateGroup(String prev, String next, Integer position, View child) {
|
|
@@ -9,33 +9,33 @@ import com.facebook.react.bridge.Promise;
|
|
|
9
9
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
10
10
|
import com.facebook.react.bridge.ReactMethod;
|
|
11
11
|
|
|
12
|
+
import java.lang.ref.WeakReference;
|
|
13
|
+
|
|
12
14
|
public class ExternalKeyboardModule extends ExternalKeyboardModuleSpec {
|
|
13
15
|
public static final String NAME = "ExternalKeyboardModule";
|
|
14
|
-
private static View
|
|
15
|
-
private final ReactApplicationContext context;
|
|
16
|
-
|
|
16
|
+
private static WeakReference<View> focusedViewRef = new WeakReference<>(null);
|
|
17
17
|
|
|
18
18
|
private boolean dismiss() {
|
|
19
|
-
|
|
19
|
+
View focusedView = focusedViewRef.get();
|
|
20
|
+
if (focusedView == null) return false;
|
|
20
21
|
try {
|
|
21
|
-
InputMethodManager imm = (InputMethodManager)
|
|
22
|
+
InputMethodManager imm = (InputMethodManager) getReactApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
22
23
|
if (imm != null) {
|
|
23
24
|
imm.hideSoftInputFromWindow(focusedView.getWindowToken(), 0);
|
|
24
25
|
return true;
|
|
25
26
|
}
|
|
26
|
-
} catch (Exception ignored){
|
|
27
|
+
} catch (Exception ignored) {
|
|
28
|
+
}
|
|
27
29
|
|
|
28
30
|
return false;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
focusedView = _focusedView;
|
|
33
|
+
public static void setFocusedTextInput(View _focusedView) {
|
|
34
|
+
focusedViewRef = new WeakReference<>(_focusedView);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
public ExternalKeyboardModule(ReactApplicationContext reactContext) {
|
|
37
38
|
super(reactContext);
|
|
38
|
-
context = reactContext;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
@Override
|