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.
Files changed (159) hide show
  1. package/README.md +149 -67
  2. package/android/src/main/java/com/externalkeyboard/delegates/FocusOrderDelegate.java +81 -75
  3. package/android/src/main/java/com/externalkeyboard/delegates/FocusOrderDelegateHost.java +14 -0
  4. package/android/src/main/java/com/externalkeyboard/helper/Linking/A11yOrderLinking.java +5 -0
  5. package/android/src/main/java/com/externalkeyboard/modules/ExternalKeyboardModule.java +10 -10
  6. package/android/src/main/java/com/externalkeyboard/services/FocusLinkObserver/FocusLinkObserver.java +26 -35
  7. package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardLockView/ExternalKeyboardLockView.java +5 -0
  8. package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardLockView/ExternalKeyboardLockViewManager.java +6 -0
  9. package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardView/ExternalKeyboardView.java +8 -307
  10. package/android/src/main/java/com/externalkeyboard/views/ExternalKeyboardView/ExternalKeyboardViewManager.java +11 -18
  11. package/android/src/main/java/com/externalkeyboard/views/TextInputFocusWrapper/TextInputFocusWrapper.java +208 -101
  12. package/android/src/main/java/com/externalkeyboard/views/TextInputFocusWrapper/TextInputFocusWrapperManager.java +123 -34
  13. package/android/src/main/java/com/externalkeyboard/views/base/FocusHighlightBase.java +38 -0
  14. package/android/src/main/java/com/externalkeyboard/views/base/ViewGroupBase.java +19 -0
  15. package/android/src/main/java/com/externalkeyboard/views/base/ViewOrderGroupBase.java +190 -0
  16. package/android/src/main/java/com/externalkeyboard/views/base/keyboard/ViewFocusChangeBase.java +39 -0
  17. package/android/src/main/java/com/externalkeyboard/views/base/keyboard/ViewFocusRequestBase.java +125 -0
  18. package/android/src/main/java/com/externalkeyboard/views/base/keyboard/ViewKeyHandlerBase.java +40 -0
  19. package/android/src/newarch/TextInputFocusWrapperManagerSpec.java +2 -8
  20. package/android/src/oldarch/ExternalKeyboardLockViewManagerSpec.java +1 -0
  21. package/android/src/oldarch/TextInputFocusWrapperManagerSpec.java +32 -2
  22. package/ios/Delegates/RNCEKVFocusLinkDelegate/RNCEKVFocusLinkDelegate.h +35 -0
  23. package/ios/Delegates/RNCEKVFocusLinkDelegate/RNCEKVFocusLinkDelegate.mm +195 -0
  24. package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusOrderProtocol.h +6 -8
  25. package/ios/Delegates/RNCEKVFocusSequenceDelegate/RNCEKVFocusSequenceDelegate.h +25 -0
  26. package/ios/Delegates/RNCEKVFocusSequenceDelegate/RNCEKVFocusSequenceDelegate.mm +163 -0
  27. package/ios/Delegates/RNCEKVGroupIdentifierDelegate/RNCEKVGroupIdentifierDelegate.h +2 -6
  28. package/ios/Delegates/RNCEKVGroupIdentifierDelegate/RNCEKVGroupIdentifierDelegate.mm +6 -78
  29. package/ios/Delegates/RNCEKVHaloDelegate/RNCEKVHaloDelegate.h +3 -4
  30. package/ios/Delegates/RNCEKVHaloDelegate/RNCEKVHaloDelegate.mm +32 -101
  31. package/ios/Delegates/RNCEKVHaloDelegate/RNCEKVHaloProtocol.h +1 -1
  32. package/ios/Extensions/RCTEnhancedScrollView+RNCEKVExternalKeyboard.mm +1 -1
  33. package/ios/Extensions/RCTTextInputComponentView+RNCEKVExternalKeyboard.mm +15 -0
  34. package/ios/Extensions/RCTViewComponentView+RNCEKVExternalKeyboard.h +9 -6
  35. package/ios/Extensions/RCTViewComponentView+RNCEKVExternalKeyboard.mm +16 -29
  36. package/ios/Extensions/UIViewController+RNCEKVExternalKeyboard.h +1 -0
  37. package/ios/Extensions/UIViewController+RNCEKVExternalKeyboard.mm +8 -0
  38. package/ios/Helpers/RNCEKVNativeProps/RNCEKVNativeProps.h +123 -0
  39. package/ios/Protocols/RNCEKVCustomFocusEffectProtocol.h +15 -0
  40. package/ios/Protocols/RNCEKVCustomGroudIdProtocol.h +15 -0
  41. package/ios/Protocols/RNCEKVKeyboardFocusableProtocol.h +15 -0
  42. package/ios/Services/RNCEKVFocusLinkObserver.mm +2 -3
  43. package/ios/Services/RNCEKVKeyboardOrderManager/RNCEKVOrderRelationship/RNCEKVOrderRelationship.mm +15 -28
  44. package/ios/Services/RNCEKVOrderLinking.mm +43 -51
  45. package/ios/Views/Base/ContextMenu/RNCEKVViewContextMenuBase.h +33 -0
  46. package/ios/Views/Base/ContextMenu/RNCEKVViewContextMenuBase.mm +84 -0
  47. package/ios/Views/Base/FocusChange/RNCEKVViewFocusChangeBase.h +37 -0
  48. package/ios/Views/Base/FocusChange/RNCEKVViewFocusChangeBase.mm +89 -0
  49. package/ios/Views/Base/FocusOrderGroup/RNCEKVViewOrderGroupBase.h +49 -0
  50. package/ios/Views/Base/FocusOrderGroup/RNCEKVViewOrderGroupBase.mm +245 -0
  51. package/ios/Views/Base/FocusRequest/RNCEKVViewFocusRequestBase.h +34 -0
  52. package/ios/Views/Base/FocusRequest/RNCEKVViewFocusRequestBase.mm +112 -0
  53. package/ios/Views/Base/GroupIdentifier/RNCEKVViewGroupIdentifierBase.h +27 -0
  54. package/ios/Views/Base/GroupIdentifier/RNCEKVViewGroupIdentifierBase.mm +69 -0
  55. package/ios/Views/Base/KeyPress/RNCEKVViewKeyPress.h +30 -0
  56. package/ios/Views/Base/KeyPress/RNCEKVViewKeyPress.mm +75 -0
  57. package/ios/Views/Base/KeyboardHallo/RNCEKVExternalKeyboardHalloBase.h +33 -0
  58. package/ios/Views/Base/KeyboardHallo/RNCEKVExternalKeyboardHalloBase.mm +92 -0
  59. package/ios/Views/Base/ViewGroup/RNCEKVViewGroupBase.h +36 -0
  60. package/ios/Views/Base/ViewGroup/RNCEKVViewGroupBase.mm +63 -0
  61. package/ios/Views/RNCEKVExternalKeyboardLockView/RNCEKVExternalKeyboardLockView.h +8 -0
  62. package/ios/Views/RNCEKVExternalKeyboardLockView/RNCEKVExternalKeyboardLockView.mm +105 -2
  63. package/ios/Views/RNCEKVExternalKeyboardLockView/RNCEKVExternalKeyboardLockViewManager.mm +11 -0
  64. package/ios/Views/RNCEKVExternalKeyboardView/RNCEKVExternalKeyboardView.h +7 -82
  65. package/ios/Views/RNCEKVExternalKeyboardView/RNCEKVExternalKeyboardView.mm +23 -493
  66. package/ios/Views/RNCEKVExternalKeyboardView/RNCEKVExternalKeyboardViewManager.mm +5 -7
  67. package/ios/Views/RNCEKVKeyboardFocusGroupView/RNCEKVKeyboardFocusGroup.mm +20 -17
  68. package/ios/Views/RNCEKVTextInputFocusWrapper/RNCEKVTextInputFocusWrapper.h +10 -39
  69. package/ios/Views/RNCEKVTextInputFocusWrapper/RNCEKVTextInputFocusWrapper.mm +40 -73
  70. package/ios/Views/RNCEKVTextInputFocusWrapper/RNCEKVTextInputFocusWrapperManager.mm +76 -8
  71. package/lib/commonjs/components/BaseKeyboardView/BaseKeyboardView.js +35 -7
  72. package/lib/commonjs/components/BaseKeyboardView/BaseKeyboardView.js.map +1 -1
  73. package/lib/commonjs/components/KeyboardExtendedInput/KeyboardExtendedInput.js +79 -1
  74. package/lib/commonjs/components/KeyboardExtendedInput/KeyboardExtendedInput.js.map +1 -1
  75. package/lib/commonjs/components/KeyboardFocusLock/FocusTrap/FocusTrap.js +18 -4
  76. package/lib/commonjs/components/KeyboardFocusLock/FocusTrap/FocusTrap.js.map +1 -1
  77. package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js +17 -2
  78. package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js.map +1 -1
  79. package/lib/commonjs/components/KeyboardFocusView/KeyboardFocusView.js +2 -0
  80. package/lib/commonjs/components/KeyboardFocusView/KeyboardFocusView.js.map +1 -1
  81. package/lib/commonjs/nativeSpec/ExternalKeyboardLockViewNativeComponent.ts +1 -0
  82. package/lib/commonjs/nativeSpec/TextInputFocusWrapperNativeComponent.ts +16 -0
  83. package/lib/commonjs/utils/useFocusStyle.js +3 -9
  84. package/lib/commonjs/utils/useFocusStyle.js.map +1 -1
  85. package/lib/commonjs/utils/withKeyboardFocus.js +32 -15
  86. package/lib/commonjs/utils/withKeyboardFocus.js.map +1 -1
  87. package/lib/commonjs/utils/wrapOrderPrefix.js +17 -0
  88. package/lib/commonjs/utils/wrapOrderPrefix.js.map +1 -0
  89. package/lib/module/components/BaseKeyboardView/BaseKeyboardView.js +35 -7
  90. package/lib/module/components/BaseKeyboardView/BaseKeyboardView.js.map +1 -1
  91. package/lib/module/components/KeyboardExtendedInput/KeyboardExtendedInput.js +80 -2
  92. package/lib/module/components/KeyboardExtendedInput/KeyboardExtendedInput.js.map +1 -1
  93. package/lib/module/components/KeyboardFocusLock/FocusTrap/FocusTrap.js +18 -4
  94. package/lib/module/components/KeyboardFocusLock/FocusTrap/FocusTrap.js.map +1 -1
  95. package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js +16 -2
  96. package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.js.map +1 -1
  97. package/lib/module/components/KeyboardFocusView/KeyboardFocusView.js +2 -0
  98. package/lib/module/components/KeyboardFocusView/KeyboardFocusView.js.map +1 -1
  99. package/lib/module/nativeSpec/ExternalKeyboardLockViewNativeComponent.ts +1 -0
  100. package/lib/module/nativeSpec/TextInputFocusWrapperNativeComponent.ts +16 -0
  101. package/lib/module/utils/useFocusStyle.js +4 -10
  102. package/lib/module/utils/useFocusStyle.js.map +1 -1
  103. package/lib/module/utils/withKeyboardFocus.js +32 -15
  104. package/lib/module/utils/withKeyboardFocus.js.map +1 -1
  105. package/lib/module/utils/wrapOrderPrefix.js +12 -0
  106. package/lib/module/utils/wrapOrderPrefix.js.map +1 -0
  107. package/lib/typescript/src/components/BaseKeyboardView/BaseKeyboardView.d.ts.map +1 -1
  108. package/lib/typescript/src/components/KeyboardExtendedInput/KeyboardExtendedInput.d.ts.map +1 -1
  109. package/lib/typescript/src/components/KeyboardExtendedInput/KeyboardExtendedInput.types.d.ts +15 -0
  110. package/lib/typescript/src/components/KeyboardExtendedInput/KeyboardExtendedInput.types.d.ts.map +1 -1
  111. package/lib/typescript/src/components/KeyboardFocusLock/FocusTrap/FocusTrap.d.ts +1 -1
  112. package/lib/typescript/src/components/KeyboardFocusLock/FocusTrap/FocusTrap.d.ts.map +1 -1
  113. package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.d.ts +2 -1
  114. package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.d.ts.map +1 -1
  115. package/lib/typescript/src/components/KeyboardFocusView/KeyboardFocusView.d.ts.map +1 -1
  116. package/lib/typescript/src/index.d.ts +1 -1
  117. package/lib/typescript/src/nativeSpec/ExternalKeyboardLockViewNativeComponent.d.ts +1 -0
  118. package/lib/typescript/src/nativeSpec/ExternalKeyboardLockViewNativeComponent.d.ts.map +1 -1
  119. package/lib/typescript/src/nativeSpec/TextInputFocusWrapperNativeComponent.d.ts +16 -1
  120. package/lib/typescript/src/nativeSpec/TextInputFocusWrapperNativeComponent.d.ts.map +1 -1
  121. package/lib/typescript/src/types/BaseKeyboardView.d.ts +2 -0
  122. package/lib/typescript/src/types/BaseKeyboardView.d.ts.map +1 -1
  123. package/lib/typescript/src/types/KeyboardFocusLock.types.d.ts +1 -0
  124. package/lib/typescript/src/types/KeyboardFocusLock.types.d.ts.map +1 -1
  125. package/lib/typescript/src/types/WithKeyboardFocus.d.ts +11 -2
  126. package/lib/typescript/src/types/WithKeyboardFocus.d.ts.map +1 -1
  127. package/lib/typescript/src/utils/useFocusStyle.d.ts +1 -0
  128. package/lib/typescript/src/utils/useFocusStyle.d.ts.map +1 -1
  129. package/lib/typescript/src/utils/withKeyboardFocus.d.ts.map +1 -1
  130. package/lib/typescript/src/utils/wrapOrderPrefix.d.ts +9 -0
  131. package/lib/typescript/src/utils/wrapOrderPrefix.d.ts.map +1 -0
  132. package/package.json +6 -2
  133. package/src/components/BaseKeyboardView/BaseKeyboardView.tsx +88 -10
  134. package/src/components/KeyboardExtendedInput/KeyboardExtendedInput.tsx +138 -2
  135. package/src/components/KeyboardExtendedInput/KeyboardExtendedInput.types.ts +15 -0
  136. package/src/components/KeyboardFocusLock/FocusTrap/FocusTrap.tsx +21 -4
  137. package/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.tsx +20 -3
  138. package/src/components/KeyboardFocusView/KeyboardFocusView.tsx +2 -0
  139. package/src/nativeSpec/ExternalKeyboardLockViewNativeComponent.ts +1 -0
  140. package/src/nativeSpec/TextInputFocusWrapperNativeComponent.ts +16 -0
  141. package/src/types/BaseKeyboardView.ts +2 -0
  142. package/src/types/KeyboardFocusLock.types.ts +1 -0
  143. package/src/types/WithKeyboardFocus.ts +19 -2
  144. package/src/utils/useFocusStyle.tsx +5 -15
  145. package/src/utils/withKeyboardFocus.tsx +44 -15
  146. package/src/utils/wrapOrderPrefix.ts +16 -0
  147. package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusGuideDelegate/RNCEKVFocusGuideDelegate.h +0 -36
  148. package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusGuideDelegate/RNCEKVFocusGuideDelegate.mm +0 -150
  149. package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusOrderDelegate.h +0 -47
  150. package/ios/Delegates/RNCEKVFocusOrderDelegate/RNCEKVFocusOrderDelegate.mm +0 -326
  151. package/ios/Services/RNCEKVKeyboardOrderManager/RNCEKVKeyboardOrderManager.h +0 -17
  152. package/ios/Services/RNCEKVKeyboardOrderManager/RNCEKVKeyboardOrderManager.mm +0 -15
  153. package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js +0 -22
  154. package/lib/commonjs/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js.map +0 -1
  155. package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js +0 -17
  156. package/lib/module/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.js.map +0 -1
  157. package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.d.ts +0 -4
  158. package/lib/typescript/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.d.ts.map +0 -1
  159. package/src/components/KeyboardFocusLock/KeyboardFocusLockBase/KeyboardFocusLockBase.android.tsx +0 -16
@@ -2,40 +2,42 @@ package com.externalkeyboard.views.TextInputFocusWrapper;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.graphics.Rect;
5
+ import android.os.Build;
5
6
  import android.text.Editable;
6
7
  import android.view.KeyEvent;
8
+ import android.view.MotionEvent;
7
9
  import android.view.View;
8
- import android.view.ViewGroup;
9
10
  import android.widget.EditText;
10
11
 
11
12
  import androidx.annotation.NonNull;
12
13
 
13
14
  import com.externalkeyboard.events.EventHelper;
14
- import com.externalkeyboard.helper.ReactNativeVersionChecker;
15
15
  import com.externalkeyboard.modules.ExternalKeyboardModule;
16
+ import com.externalkeyboard.views.base.FocusHighlightBase;
16
17
  import com.facebook.react.bridge.ReactContext;
17
- import com.facebook.react.modules.systeminfo.ReactNativeVersion;
18
18
  import com.facebook.react.views.textinput.ReactEditText;
19
19
 
20
- import java.lang.reflect.Field;
21
-
22
- public class TextInputFocusWrapper extends ViewGroup implements View.OnFocusChangeListener {
20
+ public class TextInputFocusWrapper extends FocusHighlightBase implements View.OnFocusChangeListener {
23
21
  private final Context context;
24
22
  public static final byte FOCUS_BY_PRESS = 1;
23
+
24
+ // RN version is a compile-time constant — cache it once instead of re-reading on every call.
25
+ private static final boolean IS_NATIVELY_FIXED_VERSION = resolveIsNativelyFixedVersion();
26
+
25
27
  private ReactEditText reactEditText = null;
26
28
  private boolean focusEventIgnore = false;
27
29
  private int focusType = 0;
28
- private View.OnAttachStateChangeListener onAttachListener;
30
+ // Used only to re-apply focusability after React Native attaches the EditText to the window.
31
+ private View.OnAttachStateChangeListener focusabilityListener;
29
32
  private boolean blurOnSubmit = true;
30
33
  private boolean multiline = false;
31
34
  private boolean keyboardFocusable = true;
32
- private static View focusedView = null;
33
- public boolean getIsFocusByPress() {
34
- return focusType == FOCUS_BY_PRESS;
35
- }
36
- private boolean getIsNativelyFixedVersion () {
35
+
36
+ private static boolean resolveIsNativelyFixedVersion() {
37
37
  try {
38
- Object minorValue = ReactNativeVersion.VERSION.getOrDefault("minor", 0);
38
+ Object minorValue = com.facebook.react.modules.systeminfo.ReactNativeVersion.VERSION.containsKey("minor")
39
+ ? com.facebook.react.modules.systeminfo.ReactNativeVersion.VERSION.get("minor")
40
+ : 0;
39
41
  int minor = (minorValue instanceof Integer) ? (int) minorValue : 0;
40
42
  return minor >= 79;
41
43
  } catch (Exception e) {
@@ -43,84 +45,141 @@ public class TextInputFocusWrapper extends ViewGroup implements View.OnFocusChan
43
45
  }
44
46
  }
45
47
 
46
- public void setKeyboardFocusable(boolean canBeFocusable) {
47
- if (keyboardFocusable == canBeFocusable) {
48
- return;
49
- }
48
+ // For FOCUS_BY_PRESS: wrapper must always intercept focus so the user navigates
49
+ // to the wrapper first, then presses Enter/Space to enter edit mode.
50
+ // For regular focus: pre-0.79 had a backward-direction bug, so the wrapper handled
51
+ // focus transfer. In 0.79+ that is natively fixed and the EditText gets focus directly.
52
+ private boolean shouldWrapperBeFocusable() {
53
+ if (!keyboardFocusable) return false;
54
+ if (focusType == FOCUS_BY_PRESS) return true;
55
+ return !IS_NATIVELY_FIXED_VERSION;
56
+ }
50
57
 
51
- keyboardFocusable = canBeFocusable;
58
+ private boolean shouldEditTextBeFocusable() {
59
+ return keyboardFocusable && !shouldWrapperBeFocusable();
60
+ }
52
61
 
53
- this.setFocusable(keyboardFocusable);
62
+ private void updateFocusability() {
63
+ this.setFocusable(shouldWrapperBeFocusable());
54
64
  if (this.reactEditText != null) {
55
- boolean isAlreadyFixed = getIsNativelyFixedVersion();
56
- this.reactEditText.setFocusable(isAlreadyFixed);
65
+ this.reactEditText.setFocusable(shouldEditTextBeFocusable());
66
+ }
67
+ }
68
+
69
+ @Override
70
+ protected void syncFocusHighlight () {
71
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
72
+
73
+ this.setDefaultFocusHighlightEnabled(focusHighlight);
74
+ if(this.reactEditText != null) {
75
+ reactEditText.setDefaultFocusHighlightEnabled(focusHighlight);
76
+ }
77
+ }
78
+ }
79
+
80
+
81
+ @Override
82
+ public View getFirstChild() {
83
+ // In 0.79+ with regular focus, the EditText receives focus directly.
84
+ // For FOCUS_BY_PRESS the wrapper itself is the focus target.
85
+ if (IS_NATIVELY_FIXED_VERSION && focusType != FOCUS_BY_PRESS && this.reactEditText != null) {
86
+ return this.reactEditText;
87
+ }
88
+ return this;
89
+ }
90
+
91
+ @Override
92
+ public void setNextFocusForwardId(int nextFocusForwardId) {
93
+ super.setNextFocusForwardId(nextFocusForwardId);
94
+ if (reactEditText != null) {
95
+ reactEditText.setNextFocusForwardId(nextFocusForwardId);
96
+ }
97
+ }
98
+
99
+ // --- Child lifecycle (mirrors ExternalKeyboardView pattern) ---
100
+
101
+ // Overrides ViewOrderGroupBase.linkAddView so the manager can call it uniformly
102
+ // for all children — the ReactEditText filter lives here, not in the manager.
103
+ @Override
104
+ public void linkAddView(View child) {
105
+ if (!(child instanceof ReactEditText)) return;
106
+ setEditText((ReactEditText) child); // configure listeners before linking
107
+ this.syncFocusHighlight();
108
+ super.linkAddView(child); // store firstChild + call focusOrderDelegate.link()
109
+ }
110
+
111
+ // Symmetric to linkAddView: manager calls linkRemoveView for all children.
112
+ @Override
113
+ public void linkRemoveView(View view) {
114
+ if (view != this.reactEditText) return;
115
+ super.linkRemoveView(view); // call focusOrderDelegate.unlink() + clear firstChild
116
+ setEditText(null);
117
+ }
118
+
119
+ // --- EditText setup / teardown ---
120
+
121
+ public void setEditText(ReactEditText editText) {
122
+ if (editText != null) {
123
+ this.reactEditText = editText;
124
+ updateFocusability();
125
+ this.reactEditText.addOnAttachStateChangeListener(getFocusabilityListener());
126
+ subscribeEditTextFocusListener();
127
+ onMultiplyBlurSubmitHandle();
128
+ } else {
129
+ clearEditText();
57
130
  }
58
131
  }
59
132
 
60
- private View.OnAttachStateChangeListener getOnAttachListener() {
61
- if (onAttachListener == null) {
62
- onAttachListener = new View.OnAttachStateChangeListener() {
133
+ private View.OnAttachStateChangeListener getFocusabilityListener() {
134
+ if (focusabilityListener == null) {
135
+ focusabilityListener = new View.OnAttachStateChangeListener() {
63
136
  @Override
64
137
  public void onViewAttachedToWindow(@NonNull View view) {
65
- boolean isAlreadyFixed = getIsNativelyFixedVersion();
66
- view.setFocusable(isAlreadyFixed);
138
+ // Re-apply focusability after React Native attaches the view; the
139
+ // framework may reset it during the layout/commit phase.
140
+ view.setFocusable(shouldEditTextBeFocusable());
67
141
  }
68
-
69
142
  @Override
70
- public void onViewDetachedFromWindow(@NonNull View view) {
71
- }
143
+ public void onViewDetachedFromWindow(@NonNull View view) {}
72
144
  };
73
145
  }
74
- return onAttachListener;
146
+ return focusabilityListener;
75
147
  }
76
148
 
77
149
  private void clearEditText() {
78
150
  if (this.reactEditText != null) {
151
+ focusOrderDelegate.unlink();
152
+ if (focusabilityListener != null) {
153
+ this.reactEditText.removeOnAttachStateChangeListener(focusabilityListener);
154
+ }
79
155
  this.reactEditText.setOnFocusChangeListener(null);
80
156
  this.reactEditText.setOnKeyListener(null);
81
-
82
- if (onAttachListener != null) {
83
- this.reactEditText.removeOnAttachStateChangeListener(onAttachListener);
84
- }
85
157
  }
86
158
  this.reactEditText = null;
87
159
  }
88
160
 
89
- public void setEditText(ReactEditText editText) {
90
- if (editText != null) {
91
- this.reactEditText = editText;
92
- boolean isAlreadyFixed = getIsNativelyFixedVersion();
93
- if(isAlreadyFixed) {
94
- this.setFocusable(false);
161
+ private void subscribeEditTextFocusListener() {
162
+ OnFocusChangeListener reactListener = this.reactEditText.getOnFocusChangeListener();
163
+ this.reactEditText.setOnFocusChangeListener((textInput, hasTextEditFocus) -> {
164
+ reactListener.onFocusChange(textInput, hasTextEditFocus);
165
+ this.focusEventIgnore = false;
166
+ if (focusType != FOCUS_BY_PRESS || !hasTextEditFocus) {
167
+ onFocusChange(textInput, hasTextEditFocus);
95
168
  }
96
-
97
- this.reactEditText.addOnAttachStateChangeListener(getOnAttachListener());
98
- if (focusType == FOCUS_BY_PRESS) {
99
- this.reactEditText.setFocusable(isAlreadyFixed);
169
+ if (hasTextEditFocus) {
170
+ ExternalKeyboardModule.setFocusedTextInput(textInput);
100
171
  }
101
- OnFocusChangeListener reactListener = this.reactEditText.getOnFocusChangeListener();
102
- this.reactEditText.setOnFocusChangeListener((textInput, hasTextEditFocus) -> {
103
- reactListener.onFocusChange(textInput, hasTextEditFocus);
104
- focusedView = textInput;
105
- this.focusEventIgnore = false;
106
- if (focusType != FOCUS_BY_PRESS || !hasTextEditFocus) {
107
- onFocusChange(textInput, hasTextEditFocus);
108
- }
109
-
110
- if (hasTextEditFocus) {
111
- ExternalKeyboardModule.setFocusedTextInput(textInput);
112
- }
113
- if (!hasTextEditFocus) {
114
- this.setFocusable(!isAlreadyFixed);
115
- this.reactEditText.setFocusable(isAlreadyFixed);
172
+ if (!hasTextEditFocus) {
173
+ updateFocusability();
174
+ if (focusType == FOCUS_BY_PRESS) {
175
+ post(() -> TextInputFocusWrapper.this.requestFocus());
116
176
  }
117
- });
118
- onMultiplyBlurSubmitHandle();
119
- } else {
120
- this.clearEditText();
121
- }
177
+ }
178
+ });
122
179
  }
123
180
 
181
+ // --- Focus change event ---
182
+
124
183
  @Override
125
184
  public void onFocusChange(View v, boolean hasFocus) {
126
185
  if (!this.focusEventIgnore) {
@@ -132,22 +191,29 @@ public class TextInputFocusWrapper extends ViewGroup implements View.OnFocusChan
132
191
  this.setOnFocusChangeListener(this);
133
192
  }
134
193
 
194
+ // --- Props ---
195
+
196
+ public void setKeyboardFocusable(boolean canBeFocusable) {
197
+ if (keyboardFocusable == canBeFocusable) return;
198
+ keyboardFocusable = canBeFocusable;
199
+ updateFocusability();
200
+ }
201
+
135
202
  public void setFocusType(int focusType) {
203
+ if (this.focusType == focusType) return;
136
204
  this.focusType = focusType;
205
+ updateFocusability();
206
+ // On 0.79+ getFirstChild() switches between wrapper and reactEditText based on
207
+ // focusType. If the EditText is already attached, re-link so the delegate
208
+ // registers the correct child for the new mode.
209
+ if (reactEditText != null && reactEditText.isAttachedToWindow()) {
210
+ focusOrderDelegate.unlink();
211
+ focusOrderDelegate.link();
212
+ }
137
213
  }
138
214
 
139
215
  public void setBlurType(int blurType) {
140
- // Stub, Android does not allow to type in EditField from another view. Even focus remains typing with soft or hard keyboard won't work
141
- }
142
-
143
- public TextInputFocusWrapper(Context context) {
144
- super(context);
145
- this.context = context;
146
-
147
- if (keyboardFocusable) {
148
- boolean isAlreadyFixed = getIsNativelyFixedVersion();
149
- setFocusable(!isAlreadyFixed);
150
- }
216
+ // Stub: Android does not allow typing in an EditText from another view.
151
217
  }
152
218
 
153
219
  public void setBlurOnSubmit(boolean blurOnSubmit) {
@@ -159,9 +225,20 @@ public class TextInputFocusWrapper extends ViewGroup implements View.OnFocusChan
159
225
  onMultiplyBlurSubmitHandle();
160
226
  }
161
227
 
228
+ // --- Constructor ---
229
+
230
+ public TextInputFocusWrapper(Context context) {
231
+ super(context);
232
+ this.context = context;
233
+ setFocusable(shouldWrapperBeFocusable());
234
+ }
235
+
236
+ // --- Key handling ---
237
+
162
238
  @Override
163
239
  public boolean onKeyDown(int keyCode, KeyEvent event) {
164
- if (focusType == FOCUS_BY_PRESS) {
240
+ if (isFocusLocked(event)) return true;
241
+ if (focusType == FOCUS_BY_PRESS && this.reactEditText != null) {
165
242
  this.reactEditText.setFocusable(false);
166
243
  }
167
244
  if (keyCode == KeyEvent.KEYCODE_SPACE || keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
@@ -171,36 +248,69 @@ public class TextInputFocusWrapper extends ViewGroup implements View.OnFocusChan
171
248
  return super.onKeyDown(keyCode, event);
172
249
  }
173
250
 
174
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
175
- boolean isAlreadyFixed = getIsNativelyFixedVersion();
176
- if(isAlreadyFixed) {
177
- return super.requestFocus(direction, previouslyFocusedRect);
251
+ // --- Touch handling ---
252
+
253
+ @Override
254
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
255
+ // In FOCUS_BY_PRESS mode the EditText is non-focusable (wrapper holds focus).
256
+ // Intercept the down event so a tap activates edit mode, same as a keyboard press.
257
+ if (focusType == FOCUS_BY_PRESS && ev.getAction() == MotionEvent.ACTION_DOWN) {
258
+ handleTextInputFocus();
178
259
  }
179
- if ((direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) && focusType != FOCUS_BY_PRESS) {
180
- this.handleTextInputFocus();
181
- return true;
260
+ return false; // don't consume let the touch reach the EditText for cursor positioning
261
+ }
262
+
263
+ // --- Focus search / request ---
264
+
265
+ @Override
266
+ public View focusSearch(int direction) {
267
+ // focusSearch(View, int) is only called when a descendant is focused.
268
+ // When the wrapper itself is focused (FOCUS_BY_PRESS idle state), we must
269
+ // handle orderForward/orderBackward here instead.
270
+
271
+ if (focusType == FOCUS_BY_PRESS) {
272
+ if (direction == FOCUS_FORWARD && orderForward != null) {
273
+ View next = focusOrderDelegate.getLink(orderForward);
274
+ if (next != null && next.isAttachedToWindow()) return next;
275
+ }
276
+ if (direction == FOCUS_BACKWARD && orderBackward != null) {
277
+ View prev = focusOrderDelegate.getLink(orderBackward);
278
+ if (prev != null && prev.isAttachedToWindow()) return prev;
279
+ }
182
280
  }
281
+ return super.focusSearch(direction);
282
+ }
183
283
 
284
+ @Override
285
+ public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
286
+ if (focusType != FOCUS_BY_PRESS) {
287
+ // 0.79+: wrapper is not focusable, pass through.
288
+ if (IS_NATIVELY_FIXED_VERSION) return super.requestFocus(direction, previouslyFocusedRect);
289
+ // Pre-0.79: intercept forward/backward and transfer focus directly to EditText.
290
+ if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
291
+ this.handleTextInputFocus();
292
+ return true;
293
+ }
294
+ }
184
295
  return super.requestFocus(direction, previouslyFocusedRect);
185
296
  }
186
297
 
298
+ // --- Internal helpers ---
299
+
187
300
  private void onMultiplyBlurSubmitHandle() {
188
301
  if (this.reactEditText == null) return;
189
302
  if (this.multiline) {
190
- this.reactEditText.setOnKeyListener(new View.OnKeyListener() {
191
- @Override
192
- public boolean onKey(View v, int keyCode, KeyEvent event) {
193
- if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && !event.isShiftPressed()) {
194
- Editable editableText = reactEditText.getText();
195
- String text = editableText == null ? "" : String.valueOf(editableText);
196
- EventHelper.multiplyTextSubmit((ReactContext) context, getId(), text);
197
- if (blurOnSubmit && v instanceof EditText) {
198
- v.clearFocus();
199
- return true;
200
- }
303
+ this.reactEditText.setOnKeyListener((v, keyCode, event) -> {
304
+ if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER && !event.isShiftPressed()) {
305
+ Editable editableText = reactEditText.getText();
306
+ String text = editableText == null ? "" : String.valueOf(editableText);
307
+ EventHelper.multiplyTextSubmit((ReactContext) context, getId(), text);
308
+ if (blurOnSubmit && v instanceof EditText) {
309
+ v.clearFocus();
310
+ return true;
201
311
  }
202
- return false;
203
312
  }
313
+ return false;
204
314
  });
205
315
  } else {
206
316
  this.reactEditText.setOnKeyListener(null);
@@ -211,14 +321,11 @@ public class TextInputFocusWrapper extends ViewGroup implements View.OnFocusChan
211
321
  this.focusEventIgnore = true;
212
322
  this.setFocusable(false);
213
323
  this.reactEditText.setFocusable(true);
214
-
324
+ // focusableInTouchMode is required for requestFocus() to succeed when the device
325
+ // is in touch mode (canTakeFocus() returns false without it).
326
+ this.reactEditText.setFocusableInTouchMode(true);
215
327
  if (!this.reactEditText.hasFocus()) {
216
328
  this.reactEditText.requestFocusFromJS();
217
329
  }
218
330
  }
219
-
220
- @Override
221
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
222
- // No-op since UIManagerModule handles actually laying out children.
223
- }
224
331
  }
@@ -1,20 +1,15 @@
1
1
  package com.externalkeyboard.views.TextInputFocusWrapper;
2
2
 
3
- import android.view.View;
4
- import android.view.ViewGroup;
5
-
6
3
  import androidx.annotation.NonNull;
7
4
  import androidx.annotation.Nullable;
8
5
 
6
+ import java.util.Objects;
7
+
9
8
  import com.externalkeyboard.events.FocusChangeEvent;
10
9
  import com.externalkeyboard.events.MultiplyTextSubmit;
11
- import com.externalkeyboard.views.ExternalKeyboardView.ExternalKeyboardView;
12
- import com.facebook.react.bridge.ReadableArray;
13
- import com.facebook.react.common.MapBuilder;
14
10
  import com.facebook.react.module.annotations.ReactModule;
15
11
  import com.facebook.react.uimanager.ThemedReactContext;
16
12
  import com.facebook.react.uimanager.annotations.ReactProp;
17
- import com.facebook.react.views.textinput.ReactEditText;
18
13
  import com.facebook.react.views.view.ReactViewGroup;
19
14
 
20
15
  import java.util.HashMap;
@@ -32,32 +27,14 @@ public class TextInputFocusWrapperManager extends com.externalkeyboard.TextInput
32
27
 
33
28
  @Override
34
29
  public TextInputFocusWrapper createViewInstance(ThemedReactContext context) {
35
- return subscribeOnHierarchy(new TextInputFocusWrapper(context));
30
+ return new TextInputFocusWrapper(context);
36
31
  }
37
32
 
38
33
  @Override
39
- protected void addEventEmitters(final ThemedReactContext reactContext, TextInputFocusWrapper viewGroup) {
40
- viewGroup.subscribeOnFocus();
41
- }
42
-
43
- protected TextInputFocusWrapper subscribeOnHierarchy(TextInputFocusWrapper viewGroup) {
44
- viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
45
- @Override
46
- public void onChildViewAdded(View parent, View child) {
47
- if (child instanceof ReactEditText) {
48
- viewGroup.setEditText((ReactEditText) child);
49
- }
50
- }
51
-
52
- @Override
53
- public void onChildViewRemoved(View parent, View child) {
54
- if (child instanceof ReactEditText) {
55
- viewGroup.setEditText(null);
56
- }
57
- }
58
- });
59
-
60
- return viewGroup;
34
+ protected void addEventEmitters(final ThemedReactContext reactContext, ReactViewGroup viewGroup) {
35
+ if(viewGroup instanceof TextInputFocusWrapper) {
36
+ ((TextInputFocusWrapper)viewGroup).subscribeOnFocus();
37
+ }
61
38
  }
62
39
 
63
40
  @Override
@@ -89,6 +66,96 @@ public class TextInputFocusWrapperManager extends com.externalkeyboard.TextInput
89
66
  //stub
90
67
  }
91
68
 
69
+ @Override
70
+ @ReactProp(name = "orderGroup")
71
+ public void setOrderGroup(TextInputFocusWrapper view, @Nullable String value) {
72
+ if (!Objects.equals(view.getOrderGroup(), value)) {
73
+ view.setOrderGroup(value);
74
+ }
75
+ }
76
+
77
+ @Override
78
+ @ReactProp(name = "orderIndex")
79
+ public void setOrderIndex(TextInputFocusWrapper view, int value) {
80
+ if (!Objects.equals(view.getOrderIndex(), value)) {
81
+ view.setOrderIndex(value);
82
+ }
83
+ }
84
+
85
+ @Override
86
+ @ReactProp(name = "orderId")
87
+ public void setOrderId(TextInputFocusWrapper view, @Nullable String value) {
88
+ if (!Objects.equals(view.orderId, value)) {
89
+ view.orderId = value;
90
+ }
91
+ }
92
+
93
+ @Override
94
+ @ReactProp(name = "orderLeft")
95
+ public void setOrderLeft(TextInputFocusWrapper view, @Nullable String value) {
96
+ if (!Objects.equals(view.getOrderLeft(), value)) {
97
+ view.setOrderLeft(value);
98
+ }
99
+ }
100
+
101
+ @Override
102
+ @ReactProp(name = "orderRight")
103
+ public void setOrderRight(TextInputFocusWrapper view, @Nullable String value) {
104
+ if (!Objects.equals(view.getOrderRight(), value)) {
105
+ view.setOrderRight(value);
106
+ }
107
+ }
108
+
109
+ @Override
110
+ @ReactProp(name = "orderUp")
111
+ public void setOrderUp(TextInputFocusWrapper view, @Nullable String value) {
112
+ if (!Objects.equals(view.getOrderUp(), value)) {
113
+ view.setOrderUp(value);
114
+ }
115
+ }
116
+
117
+ @Override
118
+ @ReactProp(name = "orderDown")
119
+ public void setOrderDown(TextInputFocusWrapper view, @Nullable String value) {
120
+ if (!Objects.equals(view.getOrderDown(), value)) {
121
+ view.setOrderDown(value);
122
+ }
123
+ }
124
+
125
+ @Override
126
+ @ReactProp(name = "orderForward")
127
+ public void setOrderForward(TextInputFocusWrapper view, @Nullable String value) {
128
+ if (!Objects.equals(view.orderForward, value)) {
129
+ view.orderForward = value;
130
+ }
131
+ }
132
+
133
+ @Override
134
+ @ReactProp(name = "orderBackward")
135
+ public void setOrderBackward(TextInputFocusWrapper view, @Nullable String value) {
136
+ if (!Objects.equals(view.orderBackward, value)) {
137
+ view.orderBackward = value;
138
+ }
139
+ }
140
+
141
+ @Override
142
+ @ReactProp(name = "lockFocus")
143
+ public void setLockFocus(TextInputFocusWrapper view, int value) {
144
+ if (view.lockFocus != value) {
145
+ view.lockFocus = value;
146
+ }
147
+ }
148
+
149
+ @Override
150
+ public void setOrderFirst(TextInputFocusWrapper view, @Nullable String value) {
151
+ //stub
152
+ }
153
+
154
+ @Override
155
+ public void setOrderLast(TextInputFocusWrapper view, @Nullable String value) {
156
+ //stub
157
+ }
158
+
92
159
 
93
160
  @Override
94
161
  @ReactProp(name = "canBeFocused", defaultBoolean = true)
@@ -97,8 +164,9 @@ public class TextInputFocusWrapperManager extends com.externalkeyboard.TextInput
97
164
  }
98
165
 
99
166
  @Override
167
+ @ReactProp(name = "haloEffect", defaultBoolean = true)
100
168
  public void setHaloEffect(TextInputFocusWrapper view, boolean value) {
101
- //stub
169
+ view.setFocusHighlight(value);
102
170
  }
103
171
 
104
172
  @Override
@@ -107,9 +175,12 @@ public class TextInputFocusWrapperManager extends com.externalkeyboard.TextInput
107
175
  }
108
176
 
109
177
  @Override
110
- public void onDropViewInstance(@NonNull TextInputFocusWrapper viewGroup) {
111
- viewGroup.setEditText(null);
112
- viewGroup.setOnFocusChangeListener(null);
178
+ public void onDropViewInstance(@NonNull ReactViewGroup viewGroup) {
179
+ if(viewGroup instanceof TextInputFocusWrapper) {
180
+ ((TextInputFocusWrapper)viewGroup).onDropViewInstance();
181
+ ((TextInputFocusWrapper)viewGroup).setEditText(null);
182
+ viewGroup.setOnFocusChangeListener(null);
183
+ }
113
184
  super.onDropViewInstance(viewGroup);
114
185
  }
115
186
 
@@ -129,4 +200,22 @@ public class TextInputFocusWrapperManager extends com.externalkeyboard.TextInput
129
200
 
130
201
  return export;
131
202
  }
203
+
204
+ @Override
205
+ @ReactProp(name = "haloExpendY")
206
+ public void setHaloExpendY(TextInputFocusWrapper view, float value) {
207
+
208
+ }
209
+
210
+ @Override
211
+ @ReactProp(name = "haloExpendX")
212
+ public void setHaloExpendX(TextInputFocusWrapper view, float value) {
213
+
214
+ }
215
+
216
+ @Override
217
+ @ReactProp(name = "haloCornerRadius")
218
+ public void setHaloCornerRadius(TextInputFocusWrapper view, float value) {
219
+
220
+ }
132
221
  }
@@ -0,0 +1,38 @@
1
+ package com.externalkeyboard.views.base;
2
+
3
+ import android.content.Context;
4
+ import android.os.Build;
5
+ import android.view.View;
6
+
7
+ public class FocusHighlightBase extends ViewOrderGroupBase {
8
+ protected boolean focusHighlight = true;
9
+
10
+ public void setFocusHighlight (boolean defaultFocusHighlightEnabled) {
11
+ focusHighlight = defaultFocusHighlightEnabled;
12
+ syncFocusHighlight();
13
+ }
14
+
15
+ protected View getFocusHighlightView () {
16
+ return this.getFirstChild();
17
+ }
18
+
19
+ protected void syncFocusHighlight () {
20
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
21
+ View child = this.getFocusHighlightView();
22
+ if(child != null) {
23
+ child.setDefaultFocusHighlightEnabled(focusHighlight);
24
+ }
25
+ }
26
+ }
27
+
28
+ @Override
29
+ public void linkAddView(View child) {
30
+ super.linkAddView(child);
31
+ syncFocusHighlight();
32
+ }
33
+
34
+ public FocusHighlightBase(Context context) {
35
+ super(context);
36
+ }
37
+
38
+ }
@@ -0,0 +1,19 @@
1
+ package com.externalkeyboard.views.base;
2
+
3
+ import android.content.Context;
4
+ import android.view.View;
5
+
6
+ import com.externalkeyboard.helper.FocusHelper;
7
+ import com.facebook.react.views.view.ReactViewGroup;
8
+
9
+ public class ViewGroupBase extends ReactViewGroup {
10
+
11
+ public ViewGroupBase(Context context) {
12
+ super(context);
13
+ }
14
+
15
+ protected View getFocusingView() {
16
+ View focusableView = FocusHelper.getFocusableView(this);
17
+ return focusableView != null ? focusableView : this;
18
+ }
19
+ }