@momo-kits/calculator-keyboard 0.150.2-beta.36 → 0.150.2-beta.37

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,22 +1,19 @@
1
1
  package com.calculatorkeyboard
2
2
 
3
3
  import android.annotation.SuppressLint
4
- import android.content.Context
5
4
  import android.content.res.ColorStateList
6
5
  import android.graphics.Color
7
6
  import android.graphics.drawable.GradientDrawable
7
+ import android.text.Editable
8
8
  import android.view.Gravity
9
9
  import android.widget.Button
10
+ import android.widget.EditText
10
11
  import android.widget.ImageButton
11
12
  import androidx.constraintlayout.widget.ConstraintLayout
12
- import com.facebook.react.uimanager.ThemedReactContext
13
- import org.mariuszgromada.math.mxparser.Expression
14
13
  import androidx.core.graphics.toColorInt
15
14
  import com.calculatorkeyboard.InputCalculatorViewManager.Companion.calculatorHeight
16
- import com.facebook.react.bridge.Arguments
17
- import com.facebook.react.bridge.ReactContext
18
- import com.facebook.react.uimanager.UIManagerHelper
19
- import com.facebook.react.uimanager.events.RCTEventEmitter
15
+ import com.facebook.react.uimanager.ThemedReactContext
16
+ import org.mariuszgromada.math.mxparser.Expression
20
17
 
21
18
  @SuppressLint("SetTextI18n", "ViewConstructor")
22
19
  class CustomKeyboardView(
@@ -170,27 +167,77 @@ class CustomKeyboardView(
170
167
  }
171
168
  }
172
169
 
170
+ private fun String.endsWithAny(vararg tails: String): Boolean =
171
+ tails.any { endsWith(it) }
172
+
173
+ private fun Editable.insertAtCaret(editText: EditText, s: String) {
174
+ insert(editText.selectionStart.coerceAtLeast(0), s)
175
+ }
176
+
173
177
  private fun onKeyPress(key: String, isMainCTAKey: Boolean) {
174
178
  if (isMainCTAKey) {
175
- emitCustomKey(context)
179
+ Event.emitCustomKey(context, editText.id)
176
180
  return
177
181
  }
178
182
 
179
- emitKeyPress(context, key)
183
+ Event.emitKeyPress(context, editText.id, key)
184
+
185
+ val text = editText.text ?: return
186
+ val plain = text.toString()
187
+ val isEmpty = plain.isEmpty()
188
+ val isJustZero = plain == "0"
189
+ val endsWithOperator = plain.endsWithAny(" + ", " - ", " × ", " ÷ ")
190
+ val endsWithSingleZeroAfterOp = plain.endsWith(" 0")
191
+
180
192
  when (key) {
181
193
  "AC" -> clearText()
182
194
  "back" -> onBackSpace()
183
195
  "=" -> calculateResult()
184
- "×", "+", "-", "÷" -> keyDidPress(" $key ")
185
- else -> editText.text?.insert(editText.selectionStart, key)
196
+ "×", "+", "-", "÷" -> if (!text.isEmpty()) keyDidPress(" $key ")
197
+ "0", "000" -> {
198
+ when {
199
+ isEmpty -> {}
200
+ key == "000" && endsWithOperator -> text.insertAtCaret(editText, "0")
201
+ endsWithSingleZeroAfterOp || isJustZero -> {}
202
+ else -> text.insertAtCaret(editText, key)
203
+ }
204
+ }
205
+ else -> {
206
+ when {
207
+ isJustZero -> {
208
+ text.replace(0, 1, key)
209
+ }
210
+ endsWithSingleZeroAfterOp -> {
211
+ val i = text.length - 1
212
+ text.replace(i, i + 1, key)
213
+ }
214
+ else -> text.insertAtCaret(editText, key)
215
+ }
216
+ }
186
217
  }
187
218
 
188
219
  reformatAndKeepSelection(editText)
189
220
  }
190
221
 
191
222
  private fun keyDidPress(key: String) {
192
- println("Key pressed: $key")
193
- editText.text?.replace(editText.selectionStart, editText.selectionEnd, key)
223
+ val editable = editText.text ?: return
224
+ val selStart = editText.selectionStart.coerceAtLeast(0)
225
+ val selEnd = editText.selectionEnd.coerceAtLeast(selStart)
226
+
227
+ if (selStart != selEnd) {
228
+ editable.replace(selStart, selEnd, key)
229
+ return
230
+ }
231
+
232
+ val s = editable.toString()
233
+ val ops3 = setOf(" + ", " - ", " × ", " ÷ ")
234
+
235
+ if (selStart >= 3 && ops3.contains(s.substring(selStart - 3, selStart))) {
236
+ editable.replace(selStart - 3, selStart, key)
237
+ return
238
+ }
239
+
240
+ editable.replace(selStart, selEnd, key)
194
241
  }
195
242
 
196
243
  private fun clearText() {
@@ -206,67 +253,77 @@ class CustomKeyboardView(
206
253
 
207
254
  if (caretRaw <= 0) return
208
255
 
209
- val rawAfter = buildString(rawBefore.length - 1) {
210
- append(rawBefore, 0, caretRaw - 1)
211
- append(rawBefore, caretRaw, rawBefore.length)
256
+ val ops = setOf(" + ", " - ", " × ", " ÷ ")
257
+ val len = rawBefore.length
258
+
259
+ var delStart = (caretRaw - 1).coerceAtLeast(0)
260
+ var delEnd = caretRaw
261
+
262
+ fun matches(start: Int, end: Int): Boolean {
263
+ if (start < 0 || end > len) return false
264
+ if (end - start != 3) return false
265
+ return ops.contains(rawBefore.substring(start, end))
266
+ }
267
+
268
+ if (matches(caretRaw - 3, caretRaw)) {
269
+ delStart = caretRaw - 3
270
+ delEnd = caretRaw
271
+ }
272
+ else if (matches(caretRaw - 2, caretRaw + 1)) {
273
+ delStart = caretRaw - 2
274
+ delEnd = caretRaw + 1
275
+ }
276
+ else if (matches(caretRaw - 1, caretRaw + 2)) {
277
+ delStart = caretRaw - 1
278
+ delEnd = caretRaw + 2
279
+ }
280
+
281
+ val rawAfter = buildString(len - (delEnd - delStart)) {
282
+ append(rawBefore, 0, delStart)
283
+ append(rawBefore, delEnd, len)
212
284
  }
213
285
 
214
286
  val formattedAfter = formatNumberGroups(rawAfter)
215
287
  editText.setText(formattedAfter)
216
- val newCaretFmt = rawCaretToFormatted(caretRaw - 1, formattedAfter)
288
+
289
+ val newCaretFmt = rawCaretToFormatted(delStart, formattedAfter)
217
290
  editText.setSelection(newCaretFmt.coerceIn(0, formattedAfter.length))
218
291
  }
219
292
 
220
-
221
- private fun calculateResult() {
222
- val raw = editText.text?.toString().orEmpty()
223
- val normalized = raw.replace(".", "")
224
- .replace("×", "*")
225
- .replace("÷", "/")
226
-
227
- val pattern = "^\\s*(-?\\d+(\\.\\d+)?\\s*[-+*/]\\s*)*-?\\d+(\\.\\d+)?\\s*$"
228
- if (Regex(pattern).matches(normalized)) {
229
- try {
230
- val result = eval(normalized)?.toString() ?: return
231
- editText.setTextKeepState(formatNumberGroups(result))
232
- editText.setSelection(editText.text?.length ?: 0)
233
- } catch (_: Exception) { /* ignore */ }
234
- } else {
235
- println("Invalid expression")
236
- }
293
+ private fun calculateResult(){
294
+ val result = editText.calculate()
295
+ if (result == "invalid") return
296
+ editText.setTextKeepState(formatNumberGroups(result))
297
+ editText.setSelection(editText.text?.length ?: 0)
237
298
  }
238
299
 
239
- private fun eval(str: String): Long? {
240
- val e = Expression(str)
241
- println("Expression: $e")
242
- return if (e.checkSyntax()) {
243
- e.calculate().toLong()
244
- } else {
245
- null
300
+ fun formatNumberGroups(input: String): String {
301
+ val noSep = stripGroupDots(input)
302
+ return Regex("\\d+").replace(noSep) { m ->
303
+ val s = m.value
304
+ val rev = s.reversed().chunked(3).joinToString(".")
305
+ rev.reversed()
246
306
  }
247
307
  }
248
308
 
249
- private fun emitKeyPress(context: Context, key: String) {
250
- val reactContext = context as ReactContext
251
- val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
252
- val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, editText.id)
253
- val payload =
254
- Arguments.createMap().apply {
255
- putString("key", key)
256
- }
257
- val event = OnKeyPressEvent(surfaceId, editText.id, payload)
258
309
 
259
- eventDispatcher?.dispatchEvent(event)
310
+ fun stripGroupDots(input: String): String {
311
+ val out = StringBuilder(input.length)
312
+ for (i in input.indices) {
313
+ val c = input[i]
314
+ if (c == '.' && isGroupDotAt(input, i)) {
315
+ } else {
316
+ out.append(c)
317
+ }
318
+ }
319
+ return out.toString()
260
320
  }
261
-
262
- private fun emitCustomKey(context: Context) {
263
- val reactContext = context as ReactContext
264
- val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
265
- val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, editText.id)
266
-
267
- val event = OnCustomKeyPressEvent(surfaceId, editText.id)
268
-
269
- eventDispatcher?.dispatchEvent(event)
321
+ private fun isGroupDotAt(s: String, i: Int): Boolean {
322
+ if (i < 0 || i >= s.length) return false
323
+ if (s[i] != '.') return false
324
+ val leftIsDigit = i - 1 >= 0 && s[i - 1].isDigit()
325
+ val rightIsDigit = i + 1 < s.length && s[i + 1].isDigit()
326
+ return leftIsDigit && rightIsDigit
270
327
  }
271
328
 
272
329
  fun setCustomKeyText(text: String) {
@@ -304,7 +361,7 @@ class CustomKeyboardView(
304
361
  customKeyButton?.setTextColor(textColor)
305
362
  }
306
363
 
307
- private fun reformatAndKeepSelection(editText: CalculatorEditText) {
364
+ fun reformatAndKeepSelection(editText: CalculatorEditText) {
308
365
  val formattedBefore = editText.text?.toString() ?: return
309
366
  val caretFmtBefore = editText.selectionStart.coerceAtLeast(0)
310
367
 
@@ -319,34 +376,6 @@ class CustomKeyboardView(
319
376
  val caretFmtAfter = rawCaretToFormatted(caretRaw, formattedAfter)
320
377
  editText.setSelection(caretFmtAfter.coerceIn(0, formattedAfter.length))
321
378
  }
322
- private fun stripGroupDots(input: String): String {
323
- val out = StringBuilder(input.length)
324
- for (i in input.indices) {
325
- val c = input[i]
326
- if (c == '.' && isGroupDotAt(input, i)) {
327
- } else {
328
- out.append(c)
329
- }
330
- }
331
- return out.toString()
332
- }
333
-
334
- private fun formatNumberGroups(input: String): String {
335
- val noSep = stripGroupDots(input)
336
- return Regex("\\d+").replace(noSep) { m ->
337
- val s = m.value
338
- val rev = s.reversed().chunked(3).joinToString(".")
339
- rev.reversed()
340
- }
341
- }
342
-
343
- private fun isGroupDotAt(s: String, i: Int): Boolean {
344
- if (i < 0 || i >= s.length) return false
345
- if (s[i] != '.') return false
346
- val leftIsDigit = i - 1 >= 0 && s[i - 1].isDigit()
347
- val rightIsDigit = i + 1 < s.length && s[i + 1].isDigit()
348
- return leftIsDigit && rightIsDigit
349
- }
350
379
 
351
380
  private fun formattedCaretToRaw(formatted: String, caretFmt: Int): Int {
352
381
  var rawIdx = 0
@@ -374,4 +403,33 @@ class CustomKeyboardView(
374
403
  }
375
404
  return formatted.length
376
405
  }
406
+
407
+ companion object {
408
+ fun CalculatorEditText.calculate() : String {
409
+ val invalid = "invalid"
410
+ val raw = this.text?.toString().orEmpty()
411
+ val normalized = raw.replace(".", "")
412
+ .replace("×", "*")
413
+ .replace("÷", "/")
414
+
415
+ val pattern = "^\\s*(-?\\d+(\\.\\d+)?\\s*[-+*/]\\s*)*-?\\d+(\\.\\d+)?\\s*$"
416
+ if (Regex(pattern).matches(normalized)) {
417
+ try {
418
+ val result = eval(normalized)?.toString() ?: return invalid
419
+ return result
420
+ } catch (_: Exception) { return invalid }
421
+ } else {
422
+ return invalid
423
+ }
424
+ }
425
+ private fun eval(str: String): Long? {
426
+ val e = Expression(str)
427
+ println("Expression: $e")
428
+ return if (e.checkSyntax()) {
429
+ e.calculate().toLong()
430
+ } else {
431
+ null
432
+ }
433
+ }
434
+ }
377
435
  }
@@ -1,6 +1,10 @@
1
1
  package com.calculatorkeyboard
2
2
 
3
+ import android.content.Context
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.ReactContext
3
6
  import com.facebook.react.bridge.WritableMap
7
+ import com.facebook.react.uimanager.UIManagerHelper
4
8
  import com.facebook.react.uimanager.events.Event
5
9
 
6
10
  class OnChangeEvent(
@@ -17,20 +21,109 @@ class OnKeyPressEvent(
17
21
  surfaceId: Int,
18
22
  viewId: Int,
19
23
  private val payload: WritableMap
20
- ) : Event<OnChangeEvent>(surfaceId, viewId) {
24
+ ) : Event<OnKeyPressEvent>(surfaceId, viewId) {
21
25
  override fun getEventName() = "onKeyPress"
22
26
 
23
27
  override fun getEventData() = payload
24
28
  }
25
29
 
30
+ class OnResultEvent(
31
+ surfaceId: Int,
32
+ viewId: Int,
33
+ private val payload: WritableMap
34
+ ) : Event<OnResultEvent>(surfaceId, viewId) {
35
+ override fun getEventName() = "onResult"
36
+
37
+ override fun getEventData() = payload
38
+ }
39
+
40
+ class OnFocusEvent(
41
+ surfaceId: Int,
42
+ viewId: Int
43
+ ) : Event<OnFocusEvent>(surfaceId, viewId) {
44
+ override fun getEventName() = "onFocus"
45
+
46
+ override fun getEventData() = null
47
+ }
48
+
49
+ class OnBlurEvent(
50
+ surfaceId: Int,
51
+ viewId: Int
52
+ ) : Event<OnBlurEvent>(surfaceId, viewId) {
53
+ override fun getEventName() = "onBlur"
54
+
55
+ override fun getEventData() = null
56
+ }
57
+
26
58
  class OnCustomKeyPressEvent(
27
59
  surfaceId: Int,
28
60
  viewId: Int
29
- ) : Event<OnChangeEvent>(surfaceId, viewId) {
61
+ ) : Event<OnCustomKeyPressEvent>(surfaceId, viewId) {
30
62
  override fun getEventName() = "onCustomKeyEvent"
31
63
 
32
64
  override fun getEventData() = null
33
65
  }
34
66
 
67
+ object Event {
68
+ fun emitKeyPress(context: Context, tag: Int, key: String) {
69
+ val reactContext = context as ReactContext
70
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
71
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
72
+ val payload =
73
+ Arguments.createMap().apply {
74
+ putString("key", key)
75
+ }
76
+ val event = OnKeyPressEvent(surfaceId, tag, payload)
77
+
78
+ eventDispatcher?.dispatchEvent(event)
79
+ }
80
+
81
+ fun emitCustomKey(context: Context, tag: Int) {
82
+ val reactContext = context as ReactContext
83
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
84
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
85
+
86
+ val event = OnCustomKeyPressEvent(surfaceId, tag)
87
+
88
+ eventDispatcher?.dispatchEvent(event)
89
+ }
90
+
91
+ fun emitOnChange(context: Context, tag: Int, text: String) {
92
+ val reactContext = context as ReactContext
93
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
94
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
95
+ val payload =
96
+ Arguments.createMap().apply {
97
+ putString("text", text)
98
+ }
99
+ val event = OnChangeEvent(surfaceId, tag, payload)
100
+
101
+ eventDispatcher?.dispatchEvent(event)
102
+ }
103
+
104
+ fun emitOnBlurFocus(context: Context, tag: Int, isFocus: Boolean) {
105
+ val reactContext = context as ReactContext
106
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
107
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
108
+
109
+ val event = if (isFocus) OnFocusEvent(surfaceId, tag) else OnBlurEvent(surfaceId, tag)
110
+
111
+ eventDispatcher?.dispatchEvent(event)
112
+ }
113
+
114
+ fun emitOnResult(context: Context, tag: Int, result: String) {
115
+ val reactContext = context as ReactContext
116
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
117
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, tag)
118
+ val payload =
119
+ Arguments.createMap().apply {
120
+ putString("result", result)
121
+ }
122
+ val event = OnResultEvent(surfaceId, tag, payload)
123
+
124
+ eventDispatcher?.dispatchEvent(event)
125
+ }
126
+ }
127
+
35
128
 
36
129
 
@@ -5,7 +5,6 @@ import android.content.Context
5
5
  import android.graphics.Color
6
6
  import android.graphics.Typeface
7
7
  import android.os.Build
8
- import android.provider.Settings.Global.putString
9
8
  import android.text.Editable
10
9
  import android.text.TextWatcher
11
10
  import android.view.KeyEvent
@@ -13,24 +12,18 @@ import android.view.inputmethod.InputMethodManager
13
12
  import androidx.core.graphics.toColorInt
14
13
  import androidx.core.view.ViewCompat
15
14
  import androidx.core.view.WindowInsetsCompat
16
- import androidx.core.widget.addTextChangedListener
17
- import com.facebook.react.bridge.Arguments
18
- import com.facebook.react.bridge.ReactContext
19
- import com.facebook.react.bridge.ReadableArray
15
+ import com.calculatorkeyboard.CustomKeyboardView.Companion.calculateResult
20
16
  import com.facebook.react.bridge.ReadableMap
21
17
  import com.facebook.react.bridge.UiThreadUtil
22
18
  import com.facebook.react.module.annotations.ReactModule
23
19
  import com.facebook.react.uimanager.PixelUtil.dpToPx
24
20
  import com.facebook.react.uimanager.SimpleViewManager
25
21
  import com.facebook.react.uimanager.ThemedReactContext
26
- import com.facebook.react.uimanager.UIManagerHelper
27
22
  import com.facebook.react.uimanager.ViewManagerDelegate
28
23
  import com.facebook.react.uimanager.annotations.ReactProp
29
- import com.facebook.react.uimanager.events.RCTEventEmitter
30
24
  import com.facebook.react.viewmanagers.NativeInputCalculatorManagerDelegate
31
25
  import com.facebook.react.viewmanagers.NativeInputCalculatorManagerInterface
32
26
  import com.facebook.react.views.textinput.ReactEditText
33
- import com.facebook.react.views.textinput.ReactTextInputManager
34
27
 
35
28
  @ReactModule(name = InputCalculatorViewManager.REACT_CLASS)
36
29
  class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInputCalculatorManagerInterface<ReactEditText> {
@@ -67,12 +60,15 @@ class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInp
67
60
  val atEnd = wasFocused && view.selectionStart == e.length && view.selectionEnd == e.length
68
61
  e.replace(0, e.length, newText)
69
62
  if (!wasFocused || atEnd) view.setSelection(newText.length)
63
+ keyboardView?.reformatAndKeepSelection(editText)
70
64
  }
71
65
  }
72
66
 
73
67
  override fun setTextAttributes(view: ReactEditText?, value: ReadableMap?) {
74
68
  val fontWeightStr = value?.getString("fontWeight")
75
69
  val fontSize = value?.getDouble("fontSize")
70
+ val placeHolder = value?.getString("placeholder")
71
+ val placeHolderTextColor = value?.getString("placeholderTextColor")
76
72
 
77
73
  fontWeightStr?.let { weightStr ->
78
74
  val weight = when (weightStr) {
@@ -102,6 +98,12 @@ class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInp
102
98
  fontSize?.let {
103
99
  view?.textSize = it.toFloat()
104
100
  }
101
+ placeHolder?.let {
102
+ view?.setPlaceholder(placeHolder)
103
+ }
104
+ placeHolderTextColor?.let {
105
+ view?.setHintTextColor(it.toColorInt())
106
+ }
105
107
  }
106
108
 
107
109
 
@@ -145,6 +147,8 @@ class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInp
145
147
  editText = CalculatorEditText(context).apply {
146
148
  showSoftInputOnFocus = false
147
149
  isFocusableInTouchMode = true
150
+ isSingleLine = true
151
+
148
152
  setOnTouchListener { v, event ->
149
153
  if (event.action == android.view.MotionEvent.ACTION_DOWN) {
150
154
  showSoftInputOnFocus = false
@@ -172,10 +176,15 @@ class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInp
172
176
  override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
173
177
 
174
178
  override fun afterTextChanged(s: Editable?) {
175
- emitOnChange(context, s.toString())
179
+ Event.emitOnChange(context, editText.id, s.toString())
180
+ Event.emitOnResult(context, editText.id, editText.calculateResult())
176
181
  }
177
182
 
178
183
  })
184
+
185
+ setOnFocusChangeListener { view, hasFocus ->
186
+ Event.emitOnBlurFocus(context, editText.id, hasFocus)
187
+ }
179
188
  }
180
189
 
181
190
  keyboardView = CustomKeyboardView(context, editText).apply {
@@ -230,7 +239,7 @@ class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInp
230
239
 
231
240
  private fun disableSystemImeFor(v: ReactEditText) {
232
241
  v.showSoftInputOnFocus = false
233
- if (android.os.Build.VERSION.SDK_INT >= 30) {
242
+ if (Build.VERSION.SDK_INT >= 30) {
234
243
  ViewCompat.getWindowInsetsController(v)?.hide(WindowInsetsCompat.Type.ime())
235
244
  }
236
245
  imm.hideSoftInputFromWindow(v.windowToken, 0)
@@ -240,28 +249,18 @@ class InputCalculatorViewManager : SimpleViewManager<ReactEditText>(), NativeInp
240
249
  v.showSoftInputOnFocus = true
241
250
  }
242
251
 
243
- private fun emitOnChange(context: Context, text: String) {
244
- val reactContext = context as ReactContext
245
- val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
246
- val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, editText.id)
247
- val payload =
248
- Arguments.createMap().apply {
249
- putString("text", text)
250
- }
251
- val event = OnChangeEvent(surfaceId, editText.id, payload)
252
-
253
- eventDispatcher?.dispatchEvent(event)
254
- }
255
-
256
252
  override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
257
253
  val base = super.getExportedCustomBubblingEventTypeConstants()?.toMutableMap() ?: mutableMapOf()
258
254
  base["onKeyPress"] = mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onKeyPress"))
259
255
  base["onChange"] = mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onChange"))
256
+ base["onResult"] = mapOf("phasedRegistrationNames" to mapOf("bubbled" to "onResult"))
260
257
  return base
261
258
  }
262
259
 
263
260
  override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
264
261
  val base = super.getExportedCustomDirectEventTypeConstants()?.toMutableMap() ?: mutableMapOf()
262
+ base["onBlur"] = mapOf("registrationName" to "onBlur")
263
+ base["onFocus"] = mapOf("registrationName" to "onFocus")
265
264
  base["onCustomKeyEvent"] = mapOf("registrationName" to "onCustomKeyEvent")
266
265
  return base
267
266
  }
@@ -6,7 +6,9 @@ NS_ASSUME_NONNULL_BEGIN
6
6
 
7
7
  - (void)keyDidPress:(NSString *)key;
8
8
  - (void)clearText;
9
+ - (NSString *)getText;
9
10
  - (void)onBackSpace;
11
+ - (NSString *)calculate;
10
12
  - (void)calculateResult;
11
13
  - (void)emitCustomKey;
12
14
  - (void)emitKeyPress:(NSString *)key;
@@ -1,4 +1,5 @@
1
1
  #import "CalculatorKeyboardView.h"
2
+ #import "Utils.h"
2
3
 
3
4
  @interface CalculatorKeyboardView ()
4
5
  @property (nonatomic, strong) NSArray<NSArray<NSString *> *> *numWithCTAKeys;
@@ -82,7 +83,7 @@
82
83
  [subview removeFromSuperview];
83
84
  }
84
85
 
85
- self.backgroundColor = [self colorFromHex:@"#f2f2f6"];
86
+ self.backgroundColor = [Utils colorFromHex:@"#f2f2f6"];
86
87
 
87
88
  CGFloat buttonWidth = (UIScreen.mainScreen.bounds.size.width - _separatorWidth * 2 - 4 * _separatorWidth) / 5;
88
89
  CGFloat buttonHeight = (240 - _separatorWidth * 2 - 3 * _separatorWidth) / 4;
@@ -143,7 +144,7 @@
143
144
 
144
145
  if ([_specialKeys containsObject:key] || isMainKey) {
145
146
  [button setTitleColor:UIColor.blackColor forState:UIControlStateNormal];
146
- button.backgroundColor = [self colorFromHex:@"#d8d8d8"];
147
+ button.backgroundColor = [Utils colorFromHex:@"#d8d8d8"];
147
148
  }
148
149
 
149
150
  if (isMainKey) {
@@ -181,51 +182,93 @@
181
182
  self.customKeyButton.enabled = YES;
182
183
  }
183
184
 
184
- self.customKeyButton.backgroundColor = [self colorFromHex:_customKeyBackground];
185
- [self.customKeyButton setTitleColor:[self colorFromHex:_customKeyTextColor] forState:UIControlStateNormal];
185
+ self.customKeyButton.backgroundColor = [Utils colorFromHex:_customKeyBackground];
186
+ [self.customKeyButton setTitleColor:[Utils colorFromHex:_customKeyTextColor] forState:UIControlStateNormal];
186
187
  }
187
188
 
188
189
  - (void)keyPressed:(UIButton *)sender
189
190
  {
190
191
  NSString *key = sender.accessibilityIdentifier;
191
- if (!key) {
192
- return;
193
- }
194
-
195
- BOOL isCustomKeyCTA = (sender.tag == 1);
196
- if (isCustomKeyCTA) {
192
+ if (!key) return;
193
+
194
+ if (sender.tag == 1) {
197
195
  [self.input emitCustomKey];
198
196
  return;
199
197
  }
200
198
 
201
199
  [self.input emitKeyPress:key];
202
-
200
+
201
+ NSString *text = self.input.getText ?: @"";
202
+ BOOL isEmpty = (text.length == 0);
203
+ BOOL isJustZero = [text isEqualToString:@"0"];
204
+
205
+ BOOL endsWithPlus = [text hasSuffix:@" + "];
206
+ BOOL endsWithMinus = [text hasSuffix:@" - "];
207
+ BOOL endsWithMul = [text hasSuffix:@" × "];
208
+ BOOL endsWithDiv = [text hasSuffix:@" ÷ "];
209
+ BOOL endsWithOperator = (endsWithPlus || endsWithMinus || endsWithMul || endsWithDiv);
210
+
211
+ BOOL endsWithSingleZeroAfterOp = [text hasSuffix:@" 0"];
212
+
213
+ BOOL (^isOpKey)(NSString *) = ^BOOL(NSString *k) {
214
+ return [k isEqualToString:@"+"] || [k isEqualToString:@"-"] ||
215
+ [k isEqualToString:@"×"] || [k isEqualToString:@"÷"];
216
+ };
217
+ void (^insert)(NSString *) = ^(NSString *s) { [self.input keyDidPress:s]; };
218
+ void (^replaceTrailingZeroWith)(NSString *) = ^(NSString *s) {
219
+ [self.input onBackSpace];
220
+ [self.input keyDidPress:s];
221
+ };
222
+ void (^replaceAllWith)(NSString *) = ^(NSString *s) {
223
+ [self.input clearText];
224
+ [self.input keyDidPress:s];
225
+ };
226
+
203
227
  if ([key isEqualToString:@"AC"]) {
204
228
  [self.input clearText];
205
- } else if ([key isEqualToString:@"back"]) {
229
+ return;
230
+ }
231
+
232
+ if ([key isEqualToString:@"back"]) {
206
233
  [self.input onBackSpace];
207
- } else if ([key isEqualToString:@"="]) {
234
+ return;
235
+ }
236
+
237
+ if ([key isEqualToString:@"="]) {
208
238
  [self.input calculateResult];
209
- } else if ([key isEqualToString:@"+"] || [key isEqualToString:@"-"] ||
210
- [key isEqualToString:@"÷"] || [key isEqualToString:@"×"]) {
211
- [self.input keyDidPress:[NSString stringWithFormat:@" %@ ", key]];
239
+ return;
240
+ }
241
+
242
+ if (isOpKey(key)) {
243
+ if (!isEmpty) {
244
+ insert([NSString stringWithFormat:@" %@ ", key]);
245
+ }
246
+ return;
247
+ }
248
+
249
+ if ([key isEqualToString:@"0"] || [key isEqualToString:@"000"]) {
250
+ if (isEmpty) return;
251
+
252
+ if ([key isEqualToString:@"000"] && endsWithOperator) {
253
+ insert(@"0");
254
+ return;
255
+ }
256
+
257
+ if (endsWithSingleZeroAfterOp || isJustZero) return;
258
+
259
+ insert(key);
260
+ return;
261
+ }
262
+
263
+ if (isJustZero) {
264
+ replaceAllWith(key);
265
+ } else if (endsWithSingleZeroAfterOp) {
266
+ replaceTrailingZeroWith(key);
212
267
  } else {
213
- [self.input keyDidPress:key];
268
+ insert(key);
214
269
  }
215
270
  }
216
271
 
217
- - (UIColor *)colorFromHex:(NSString *)hexString
218
- {
219
- unsigned rgbValue = 0;
220
- NSString *cleanHex = [hexString stringByReplacingOccurrencesOfString:@"#" withString:@""];
221
- NSScanner *scanner = [NSScanner scannerWithString:cleanHex];
222
- [scanner scanHexInt:&rgbValue];
223
-
224
- return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
225
- green:((rgbValue & 0x00FF00) >> 8) / 255.0
226
- blue:(rgbValue & 0x0000FF) / 255.0
227
- alpha:1.0];
228
- }
229
272
 
230
273
  @end
231
274
 
@@ -8,6 +8,8 @@
8
8
  #import <react/renderer/components/CalculatorKeyboardSpecs/Props.h>
9
9
  #import <react/renderer/components/CalculatorKeyboardSpecs/RCTComponentViewHelpers.h>
10
10
 
11
+ #import "Utils.h"
12
+
11
13
  using namespace facebook::react;
12
14
 
13
15
  @interface NativeInputCalculator () <RCTNativeInputCalculatorViewProtocol>
@@ -38,12 +40,37 @@ using namespace facebook::react;
38
40
 
39
41
  // Set custom keyboard
40
42
  _textField.inputView = _keyboardView;
41
-
43
+ [self startObservingFocusNotifications];
44
+
42
45
  [self addSubview:_textField];
43
46
  }
44
47
  return self;
45
48
  }
46
49
 
50
+ - (void)startObservingFocusNotifications
51
+ {
52
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
53
+ [nc addObserver:self selector:@selector(_didBeginEditing:)
54
+ name:UITextFieldTextDidBeginEditingNotification object:_textField];
55
+ [nc addObserver:self selector:@selector(_didEndEditing:)
56
+ name:UITextFieldTextDidEndEditingNotification object:_textField];
57
+ }
58
+
59
+ - (void)_didBeginEditing:(NSNotification *)notification
60
+ {
61
+ [self emitFocus];
62
+ }
63
+
64
+ - (void)_didEndEditing:(NSNotification *)notification
65
+ {
66
+ [self emitBlur];
67
+ }
68
+
69
+ - (void)dealloc
70
+ {
71
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
72
+ }
73
+
47
74
  - (CGFloat)getBottomInset
48
75
  {
49
76
  UIWindow *window = UIApplication.sharedApplication.windows.firstObject;
@@ -68,6 +95,8 @@ using namespace facebook::react;
68
95
  if (![_lastValue isEqualToString:newValue]) {
69
96
  _textField.text = newValue;
70
97
  _lastValue = newValue;
98
+ [self reformatAndKeepSelection];
99
+ [self notifyTextChange];
71
100
  }
72
101
  }
73
102
 
@@ -102,6 +131,14 @@ using namespace facebook::react;
102
131
  CGFloat size = _textField.font ? _textField.font.pointSize : UIFont.systemFontSize;
103
132
  _textField.font = [UIFont systemFontOfSize:size weight:weight];
104
133
  }
134
+
135
+ if (oldViewProps.textAttributes.placeholder != newViewProps.textAttributes.placeholder
136
+ || oldViewProps.textAttributes.placeholderTextColor != newViewProps.textAttributes.placeholderTextColor) {
137
+ _textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:RCTNSStringFromString(newViewProps.textAttributes.placeholder)
138
+ attributes:@{
139
+ NSForegroundColorAttributeName: [Utils colorFromHex:RCTNSStringFromString(newViewProps.textAttributes.placeholderTextColor)]
140
+ }];
141
+ }
105
142
 
106
143
  [super updateProps:props oldProps:oldProps];
107
144
  }
@@ -143,11 +180,35 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
143
180
 
144
181
  - (void)keyDidPress:(NSString *)key {
145
182
  UITextRange *sel = _textField.selectedTextRange;
146
- if (sel) {
183
+ if (!sel) return;
184
+
185
+ NSArray<NSString *> *ops = @[ @" + ", @" - ", @" × ", @" ÷ " ];
186
+ BOOL isOperatorKey = [ops containsObject:key];
187
+
188
+ if (!sel.isEmpty) {
147
189
  [_textField replaceRange:sel withText:key];
148
190
  } else {
149
- [_textField insertText:key];
191
+ NSInteger caret = [_textField offsetFromPosition:_textField.beginningOfDocument
192
+ toPosition:sel.start];
193
+ NSString *text = _textField.text ?: @"";
194
+
195
+ if (isOperatorKey && caret >= 3 && caret <= (NSInteger)text.length) {
196
+ NSString *last3 = [text substringWithRange:NSMakeRange(caret - 3, 3)];
197
+ if ([ops containsObject:last3]) {
198
+ UITextPosition *start = [_textField positionFromPosition:_textField.beginningOfDocument
199
+ offset:(caret - 3)];
200
+ UITextPosition *end = [_textField positionFromPosition:_textField.beginningOfDocument
201
+ offset:caret];
202
+ UITextRange *range = [_textField textRangeFromPosition:start toPosition:end];
203
+ [_textField replaceRange:range withText:key];
204
+ } else {
205
+ [_textField insertText:key];
206
+ }
207
+ } else {
208
+ [_textField insertText:key];
209
+ }
150
210
  }
211
+
151
212
  [self reformatAndKeepSelection];
152
213
  [self notifyTextChange];
153
214
  }
@@ -158,6 +219,11 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
158
219
  [self notifyTextChange];
159
220
  }
160
221
 
222
+ - (NSString *)getText
223
+ {
224
+ return _textField.text;
225
+ }
226
+
161
227
  - (void)onBackSpace {
162
228
  NSString *formatted = _textField.text ?: @"";
163
229
  if (formatted.length == 0) return;
@@ -170,13 +236,44 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
170
236
  NSInteger caretRaw = [self formattedCaretToRaw:formatted caret:caretFmt];
171
237
  if (caretRaw <= 0) return;
172
238
 
239
+ NSArray<NSString *> *ops = @[ @" + ", @" - ", @" × ", @" ÷ " ];
240
+ NSInteger len = (NSInteger)rawBefore.length;
241
+
242
+ NSInteger delStart = MAX(caretRaw - 1, 0);
243
+ NSInteger delLen = 1;
244
+
245
+ BOOL (^matches)(NSInteger, NSInteger) = ^BOOL(NSInteger start, NSInteger end) {
246
+ if (start < 0 || end > len) return NO;
247
+ if (end - start != 3) return NO;
248
+ NSString *sub = [rawBefore substringWithRange:NSMakeRange(start, 3)];
249
+ for (NSString *op in ops) {
250
+ if ([sub isEqualToString:op]) return YES;
251
+ }
252
+ return NO;
253
+ };
254
+
255
+ if (matches(caretRaw - 3, caretRaw)) {
256
+ delStart = caretRaw - 3;
257
+ delLen = 3;
258
+ }
259
+ else if (matches(caretRaw - 2, caretRaw + 1)) {
260
+ delStart = caretRaw - 2;
261
+ delLen = 3;
262
+ }
263
+ else if (matches(caretRaw - 1, caretRaw + 2)) {
264
+ delStart = caretRaw - 1;
265
+ delLen = 3;
266
+ }
267
+
173
268
  NSMutableString *rawAfter = [rawBefore mutableCopy];
174
- [rawAfter deleteCharactersInRange:NSMakeRange(caretRaw - 1, 1)];
269
+ if (delStart >= 0 && delStart + delLen <= (NSInteger)rawAfter.length) {
270
+ [rawAfter deleteCharactersInRange:NSMakeRange(delStart, delLen)];
271
+ }
175
272
 
176
273
  NSString *formattedAfter = [self formatNumberGroups:rawAfter];
177
274
  _textField.text = formattedAfter;
178
275
 
179
- NSInteger newCaretFmt = [self rawCaretToFormatted:(caretRaw - 1) inFormatted:formattedAfter];
276
+ NSInteger newCaretFmt = [self rawCaretToFormatted:delStart inFormatted:formattedAfter];
180
277
  UITextPosition *pos = [_textField positionFromPosition:_textField.beginningOfDocument offset:newCaretFmt];
181
278
  if (pos) {
182
279
  _textField.selectedTextRange = [_textField textRangeFromPosition:pos toPosition:pos];
@@ -185,8 +282,9 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
185
282
  [self notifyTextChange];
186
283
  }
187
284
 
188
- - (void)calculateResult {
285
+ - (NSString *)calculate {
189
286
  NSString *text = _textField.text ?: @"";
287
+ NSString *invalid = @"invalid";
190
288
 
191
289
  text = [self stripGroupDots:text];
192
290
  text = [text stringByReplacingOccurrencesOfString:@"×" withString:@"*"];
@@ -196,23 +294,29 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
196
294
  NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
197
295
  if (![regex firstMatchInString:text options:0 range:NSMakeRange(0, text.length)]) {
198
296
  NSLog(@"Invalid expression");
199
- return;
297
+ return invalid;
200
298
  }
201
299
 
202
300
  @try {
203
301
  NSExpression *expr = [NSExpression expressionWithFormat:text];
204
302
  id val = [expr expressionValueWithObject:nil context:nil];
205
303
  if ([val isKindOfClass:[NSNumber class]]) {
206
- NSString *result = [(NSNumber *)val stringValue];
207
- NSString *formatted = [self formatNumberGroups:result];
208
- _textField.text = formatted;
304
+ return [(NSNumber *)val stringValue];
305
+ }
306
+ } @catch (__unused NSException *e) { return invalid; }
307
+ return invalid;
308
+ }
209
309
 
210
- UITextPosition *end = _textField.endOfDocument;
211
- _textField.selectedTextRange = [_textField textRangeFromPosition:end toPosition:end];
310
+ - (void)calculateResult {
311
+ NSString *result = [self calculate];
312
+ if ([result isEqual: @"invalid"]) return;
313
+ NSString *formatted = [self formatNumberGroups:result];
314
+ _textField.text = formatted;
212
315
 
213
- [self notifyTextChange];
214
- }
215
- } @catch (__unused NSException *e) { }
316
+ UITextPosition *end = _textField.endOfDocument;
317
+ _textField.selectedTextRange = [_textField textRangeFromPosition:end toPosition:end];
318
+
319
+ [self notifyTextChange];
216
320
  }
217
321
 
218
322
  - (void)emitCustomKey
@@ -224,6 +328,26 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
224
328
  }
225
329
  }
226
330
 
331
+ - (void)emitFocus
332
+ {
333
+ printf("emitFocus.");
334
+ if (_eventEmitter) {
335
+ auto emitter = std::static_pointer_cast<const NativeInputCalculatorEventEmitter>(_eventEmitter);
336
+ NativeInputCalculatorEventEmitter::OnFocus event{};
337
+ emitter->onFocus(event);
338
+ }
339
+ }
340
+
341
+ - (void)emitBlur
342
+ {
343
+ printf("emitBlur.");
344
+ if (_eventEmitter) {
345
+ auto emitter = std::static_pointer_cast<const NativeInputCalculatorEventEmitter>(_eventEmitter);
346
+ NativeInputCalculatorEventEmitter::OnBlur event{};
347
+ emitter->onBlur(event);
348
+ }
349
+ }
350
+
227
351
  - (void)emitKeyPress:(NSString *)key
228
352
  {
229
353
  if (_eventEmitter) {
@@ -245,6 +369,13 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
245
369
  .text = std::string([_textField.text UTF8String])
246
370
  };
247
371
  emitter->onChange(event);
372
+
373
+ NSString *result = [self calculate];
374
+ NativeInputCalculatorEventEmitter::OnResult event1{
375
+ .result = std::string([result UTF8String])
376
+ };
377
+ emitter->onResult(event1);
378
+
248
379
  }
249
380
  }
250
381
 
package/ios/Utils.h ADDED
@@ -0,0 +1,10 @@
1
+ //
2
+ // Utils.h
3
+ // Pods
4
+ //
5
+ // Created by Phuc on 21/10/25.
6
+ //
7
+
8
+ @interface Utils : NSObject
9
+ + (UIColor *)colorFromHex:(NSString *)hexString;
10
+ @end
package/ios/Utils.mm ADDED
@@ -0,0 +1,25 @@
1
+ //
2
+ // Utils.mm
3
+ // Pods
4
+ //
5
+ // Created by Phuc on 21/10/25.
6
+ //
7
+
8
+ #import <UIKit/UIKit.h>
9
+ #import <Utils.h>
10
+
11
+ @implementation Utils
12
+
13
+ + (UIColor *)colorFromHex:(NSString *)hexString
14
+ {
15
+ unsigned rgbValue = 0;
16
+ NSString *cleanHex = [hexString stringByReplacingOccurrencesOfString:@"#" withString:@""];
17
+ NSScanner *scanner = [NSScanner scannerWithString:cleanHex];
18
+ [scanner scanHexInt:&rgbValue];
19
+
20
+ return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0
21
+ green:((rgbValue & 0x00FF00) >> 8) / 255.0
22
+ blue:(rgbValue & 0x0000FF) / 255.0
23
+ alpha:1.0];
24
+ }
25
+ @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/calculator-keyboard",
3
- "version": "0.150.2-beta.36",
3
+ "version": "0.150.2-beta.37",
4
4
  "description": "react native calculator keyboard",
5
5
  "main": "./src/index.tsx",
6
6
  "files": [
@@ -26,9 +26,15 @@ export type OnChangeEvent = Readonly<{
26
26
  text: string;
27
27
  }>;
28
28
 
29
+ export type OnResultEvent = Readonly<{
30
+ result: string;
31
+ }>;
32
+
29
33
  export type TextAttributes = Readonly<{
30
- fontSize?: CodegenTypes.Float;
31
- fontWeight?: string;
34
+ fontSize?: CodegenTypes.Float | undefined;
35
+ fontWeight?: string | undefined;
36
+ placeholder?: string | undefined;
37
+ placeholderTextColor?: string | undefined;
32
38
  }>;
33
39
 
34
40
  export interface NativeInputCalculatorProps extends ViewProps {
@@ -41,6 +47,9 @@ export interface NativeInputCalculatorProps extends ViewProps {
41
47
  customKeyState?: string;
42
48
  onChange?: CodegenTypes.BubblingEventHandler<OnChangeEvent>;
43
49
  onKeyPress?: CodegenTypes.BubblingEventHandler<OnKeyPressEvent>;
50
+ onResult?: CodegenTypes.BubblingEventHandler<OnResultEvent>;
51
+ onBlur?: CodegenTypes.BubblingEventHandler<{}>;
52
+ onFocus?: CodegenTypes.BubblingEventHandler<{}>;
44
53
  onCustomKeyEvent?: CodegenTypes.DirectEventHandler<{}>;
45
54
  }
46
55
 
package/src/index.tsx CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  TextStyle,
10
10
  } from 'react-native';
11
11
  import NativeInputCalculator, {
12
+ OnResultEvent,
12
13
  TextAttributes,
13
14
  } from './InputCalculatorNativeComponent';
14
15
  import { Commands } from './InputCalculatorNativeComponent';
@@ -20,12 +21,17 @@ type KeyPressEvent = { nativeEvent: { key: string } };
20
21
  interface InputCalculatorProps extends TextInputProps {
21
22
  text?: string | undefined;
22
23
  mode?: Mode;
24
+ onBlur?: () => void;
25
+ onFocus?: () => void;
26
+ onResult?: (e: OnResultEvent) => void;
23
27
  onKeyPress?: (e: KeyPressEvent) => void;
24
28
  customKeyText?: string | undefined;
25
29
  customKeyBackground?: CustomKeyBackground;
26
30
  customKeyState?: CustomKeyState;
27
31
  onCustomKeyEvent?: () => void;
28
32
  style?: StyleProp<TextStyle>;
33
+ placeholder?: string | undefined;
34
+ placeholderTextColor?: string | undefined;
29
35
  }
30
36
 
31
37
  export type CustomKeyBackground = 'primary' | 'default' | string;
@@ -54,13 +60,17 @@ function isEmpty(obj: object | undefined | null) {
54
60
  return !obj || Object.keys(obj).length === 0;
55
61
  }
56
62
 
57
- export function styleToTextAttributes(
63
+ function styleToTextAttributes(
58
64
  style?: StyleProp<TextStyle>,
65
+ placeholder?: string,
66
+ placeholderTextColor?: string,
59
67
  ): Readonly<TextAttributes> {
60
68
  const s = StyleSheet.flatten(style) ?? {};
61
69
  const attrs: TextAttributes = {
62
70
  fontSize: s.fontSize ?? undefined,
63
71
  fontWeight: (s.fontWeight as TextAttributes['fontWeight']) ?? undefined,
72
+ placeholder,
73
+ placeholderTextColor,
64
74
  };
65
75
  return Object.freeze(omitUndefined(attrs));
66
76
  }
@@ -74,6 +84,9 @@ const InputCalculator = React.forwardRef<
74
84
  customKeyBackground = 'default',
75
85
  mode = Mode.NumDefault,
76
86
  customKeyText,
87
+ onBlur,
88
+ onFocus,
89
+ onResult,
77
90
  onKeyPress,
78
91
  customKeyState = CustomKeyState.Default,
79
92
  onCustomKeyEvent,
@@ -117,8 +130,8 @@ const InputCalculator = React.forwardRef<
117
130
  }));
118
131
 
119
132
  const derivedTA = React.useMemo(
120
- () => styleToTextAttributes(style),
121
- [style],
133
+ () => styleToTextAttributes(style, props.placeholder, props.placeholderTextColor),
134
+ [style, props.placeholder, props.placeholderTextColor],
122
135
  );
123
136
 
124
137
  return (
@@ -128,6 +141,9 @@ const InputCalculator = React.forwardRef<
128
141
  textAttributes={isEmpty(derivedTA) ? undefined : derivedTA}
129
142
  ref={nativeRef}
130
143
  onChange={_onChange}
144
+ onBlur={onBlur}
145
+ onFocus={onFocus}
146
+ onResult={onResult}
131
147
  onKeyPress={onKeyPress}
132
148
  value={text}
133
149
  mode={mode}