@momo-kits/calculator-keyboard 0.150.2-beta.28 → 0.150.2-beta.30
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.
|
@@ -184,6 +184,8 @@ class CustomKeyboardView(
|
|
|
184
184
|
"×", "+", "-", "÷" -> keyDidPress(" $key ")
|
|
185
185
|
else -> editText.text?.insert(editText.selectionStart, key)
|
|
186
186
|
}
|
|
187
|
+
|
|
188
|
+
reformatAndKeepSelection(editText)
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
private fun keyDidPress(key: String) {
|
|
@@ -196,26 +198,39 @@ class CustomKeyboardView(
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
private fun onBackSpace() {
|
|
199
|
-
val
|
|
200
|
-
val
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
val formatted = editText.text?.toString().orEmpty()
|
|
202
|
+
val caretFmt = editText.selectionStart.coerceAtLeast(0)
|
|
203
|
+
|
|
204
|
+
val rawBefore = stripGroupDots(formatted)
|
|
205
|
+
val caretRaw = formattedCaretToRaw(formatted, caretFmt)
|
|
206
|
+
|
|
207
|
+
if (caretRaw <= 0) return
|
|
208
|
+
|
|
209
|
+
val rawAfter = buildString(rawBefore.length - 1) {
|
|
210
|
+
append(rawBefore, 0, caretRaw - 1)
|
|
211
|
+
append(rawBefore, caretRaw, rawBefore.length)
|
|
205
212
|
}
|
|
213
|
+
|
|
214
|
+
val formattedAfter = formatNumberGroups(rawAfter)
|
|
215
|
+
editText.setText(formattedAfter)
|
|
216
|
+
val newCaretFmt = rawCaretToFormatted(caretRaw - 1, formattedAfter)
|
|
217
|
+
editText.setSelection(newCaretFmt.coerceIn(0, formattedAfter.length))
|
|
206
218
|
}
|
|
207
219
|
|
|
220
|
+
|
|
208
221
|
private fun calculateResult() {
|
|
209
|
-
val
|
|
222
|
+
val raw = editText.text?.toString().orEmpty()
|
|
223
|
+
val normalized = raw.replace(".", "")
|
|
224
|
+
.replace("×", "*")
|
|
225
|
+
.replace("÷", "/")
|
|
226
|
+
|
|
210
227
|
val pattern = "^\\s*(-?\\d+(\\.\\d+)?\\s*[-+*/]\\s*)*-?\\d+(\\.\\d+)?\\s*$"
|
|
211
|
-
|
|
212
|
-
if (regex.matches(text)) {
|
|
228
|
+
if (Regex(pattern).matches(normalized)) {
|
|
213
229
|
try {
|
|
214
|
-
val result = eval(
|
|
215
|
-
editText.setTextKeepState(result)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
230
|
+
val result = eval(normalized)?.toString() ?: return
|
|
231
|
+
editText.setTextKeepState(formatNumberGroups(result))
|
|
232
|
+
editText.setSelection(editText.text?.length ?: 0)
|
|
233
|
+
} catch (_: Exception) { /* ignore */ }
|
|
219
234
|
} else {
|
|
220
235
|
println("Invalid expression")
|
|
221
236
|
}
|
|
@@ -289,4 +304,74 @@ class CustomKeyboardView(
|
|
|
289
304
|
customKeyButton?.setTextColor(textColor)
|
|
290
305
|
}
|
|
291
306
|
|
|
307
|
+
private fun reformatAndKeepSelection(editText: CalculatorEditText) {
|
|
308
|
+
val formattedBefore = editText.text?.toString() ?: return
|
|
309
|
+
val caretFmtBefore = editText.selectionStart.coerceAtLeast(0)
|
|
310
|
+
|
|
311
|
+
val caretRaw = formattedCaretToRaw(formattedBefore, caretFmtBefore)
|
|
312
|
+
|
|
313
|
+
val formattedAfter = formatNumberGroups(formattedBefore)
|
|
314
|
+
|
|
315
|
+
if (formattedAfter != formattedBefore) {
|
|
316
|
+
editText.setText(formattedAfter)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
val caretFmtAfter = rawCaretToFormatted(caretRaw, formattedAfter)
|
|
320
|
+
editText.setSelection(caretFmtAfter.coerceIn(0, formattedAfter.length))
|
|
321
|
+
}
|
|
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
|
+
|
|
351
|
+
private fun formattedCaretToRaw(formatted: String, caretFmt: Int): Int {
|
|
352
|
+
var rawIdx = 0
|
|
353
|
+
var i = 0
|
|
354
|
+
while (i < caretFmt && i < formatted.length) {
|
|
355
|
+
val c = formatted[i]
|
|
356
|
+
if (!(c == '.' && isGroupDotAt(formatted, i))) {
|
|
357
|
+
rawIdx++
|
|
358
|
+
}
|
|
359
|
+
i++
|
|
360
|
+
}
|
|
361
|
+
return rawIdx
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private fun rawCaretToFormatted(rawCaret: Int, formatted: String): Int {
|
|
365
|
+
var rawSeen = 0
|
|
366
|
+
var i = 0
|
|
367
|
+
while (i < formatted.length) {
|
|
368
|
+
val c = formatted[i]
|
|
369
|
+
if (!(c == '.' && isGroupDotAt(formatted, i))) {
|
|
370
|
+
if (rawSeen == rawCaret) return i
|
|
371
|
+
rawSeen++
|
|
372
|
+
}
|
|
373
|
+
i++
|
|
374
|
+
}
|
|
375
|
+
return formatted.length
|
|
376
|
+
}
|
|
292
377
|
}
|
|
@@ -63,7 +63,6 @@ using namespace facebook::react;
|
|
|
63
63
|
const auto &oldViewProps = *std::static_pointer_cast<const NativeInputCalculatorProps>(_props);
|
|
64
64
|
const auto &newViewProps = *std::static_pointer_cast<const NativeInputCalculatorProps>(props);
|
|
65
65
|
|
|
66
|
-
// Update value
|
|
67
66
|
if (oldViewProps.value != newViewProps.value) {
|
|
68
67
|
NSString *newValue = RCTNSStringFromString(newViewProps.value);
|
|
69
68
|
if (![_lastValue isEqualToString:newValue]) {
|
|
@@ -72,33 +71,26 @@ using namespace facebook::react;
|
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
// Update mode
|
|
76
74
|
if (oldViewProps.mode != newViewProps.mode) {
|
|
77
75
|
_keyboardView.keyboardMode = RCTNSStringFromString(newViewProps.mode);
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
// Update customKeyText
|
|
81
78
|
if (oldViewProps.customKeyText != newViewProps.customKeyText) {
|
|
82
79
|
_keyboardView.customKeyText = RCTNSStringFromString(newViewProps.customKeyText);
|
|
83
80
|
}
|
|
84
81
|
|
|
85
|
-
// Update customKeyBackground
|
|
86
82
|
if (oldViewProps.customKeyBackground != newViewProps.customKeyBackground) {
|
|
87
83
|
_keyboardView.customKeyBackground = RCTNSStringFromString(newViewProps.customKeyBackground);
|
|
88
84
|
}
|
|
89
85
|
|
|
90
|
-
// Update customKeyTextColor
|
|
91
86
|
if (oldViewProps.customKeyTextColor != newViewProps.customKeyTextColor) {
|
|
92
87
|
_keyboardView.customKeyTextColor = RCTNSStringFromString(newViewProps.customKeyTextColor);
|
|
93
88
|
}
|
|
94
89
|
|
|
95
|
-
// Update customKeyState
|
|
96
90
|
if (oldViewProps.customKeyState != newViewProps.customKeyState) {
|
|
97
91
|
_keyboardView.customKeyState = RCTNSStringFromString(newViewProps.customKeyState);
|
|
98
92
|
}
|
|
99
93
|
|
|
100
|
-
// ===== textAttributes (fontSize, fontWeight) =====
|
|
101
|
-
// Codegen should have a prop like: std::optional<folly::dynamic> textAttributes;
|
|
102
94
|
if (oldViewProps.textAttributes.fontSize != newViewProps.textAttributes.fontSize > 0.0f) {
|
|
103
95
|
CGFloat newSize = (CGFloat)newViewProps.textAttributes.fontSize;
|
|
104
96
|
UIFont *current = _textField.font ?: [UIFont systemFontOfSize:UIFont.systemFontSize];
|
|
@@ -149,9 +141,14 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
|
|
|
149
141
|
|
|
150
142
|
#pragma mark - Keyboard callbacks (called by CalculatorKeyboardView)
|
|
151
143
|
|
|
152
|
-
- (void)keyDidPress:(NSString *)key
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
- (void)keyDidPress:(NSString *)key {
|
|
145
|
+
UITextRange *sel = _textField.selectedTextRange;
|
|
146
|
+
if (sel) {
|
|
147
|
+
[_textField replaceRange:sel withText:key];
|
|
148
|
+
} else {
|
|
149
|
+
[_textField insertText:key];
|
|
150
|
+
}
|
|
151
|
+
[self reformatAndKeepSelection];
|
|
155
152
|
[self notifyTextChange];
|
|
156
153
|
}
|
|
157
154
|
|
|
@@ -161,31 +158,61 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
|
|
|
161
158
|
[self notifyTextChange];
|
|
162
159
|
}
|
|
163
160
|
|
|
164
|
-
- (void)onBackSpace
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
- (void)onBackSpace {
|
|
162
|
+
NSString *formatted = _textField.text ?: @"";
|
|
163
|
+
if (formatted.length == 0) return;
|
|
164
|
+
|
|
165
|
+
UITextRange *selRange = _textField.selectedTextRange;
|
|
166
|
+
NSInteger caretFmt = (NSInteger)[_textField offsetFromPosition:_textField.beginningOfDocument
|
|
167
|
+
toPosition:selRange.start];
|
|
168
|
+
|
|
169
|
+
NSString *rawBefore = [self stripGroupDots:formatted];
|
|
170
|
+
NSInteger caretRaw = [self formattedCaretToRaw:formatted caret:caretFmt];
|
|
171
|
+
if (caretRaw <= 0) return;
|
|
172
|
+
|
|
173
|
+
NSMutableString *rawAfter = [rawBefore mutableCopy];
|
|
174
|
+
[rawAfter deleteCharactersInRange:NSMakeRange(caretRaw - 1, 1)];
|
|
175
|
+
|
|
176
|
+
NSString *formattedAfter = [self formatNumberGroups:rawAfter];
|
|
177
|
+
_textField.text = formattedAfter;
|
|
178
|
+
|
|
179
|
+
NSInteger newCaretFmt = [self rawCaretToFormatted:(caretRaw - 1) inFormatted:formattedAfter];
|
|
180
|
+
UITextPosition *pos = [_textField positionFromPosition:_textField.beginningOfDocument offset:newCaretFmt];
|
|
181
|
+
if (pos) {
|
|
182
|
+
_textField.selectedTextRange = [_textField textRangeFromPosition:pos toPosition:pos];
|
|
169
183
|
}
|
|
184
|
+
|
|
185
|
+
[self notifyTextChange];
|
|
170
186
|
}
|
|
171
187
|
|
|
172
|
-
- (void)calculateResult
|
|
173
|
-
|
|
174
|
-
|
|
188
|
+
- (void)calculateResult {
|
|
189
|
+
NSString *text = _textField.text ?: @"";
|
|
190
|
+
|
|
191
|
+
text = [self stripGroupDots:text];
|
|
192
|
+
text = [text stringByReplacingOccurrencesOfString:@"×" withString:@"*"];
|
|
175
193
|
text = [text stringByReplacingOccurrencesOfString:@"÷" withString:@"/"];
|
|
176
194
|
|
|
177
195
|
NSString *pattern = @"^\\s*(-?\\d+(\\.\\d+)?\\s*[-+*/]\\s*)*-?\\d+(\\.\\d+)?\\s*$";
|
|
178
196
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
|
|
179
|
-
|
|
197
|
+
if (![regex firstMatchInString:text options:0 range:NSMakeRange(0, text.length)]) {
|
|
198
|
+
NSLog(@"Invalid expression");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@try {
|
|
203
|
+
NSExpression *expr = [NSExpression expressionWithFormat:text];
|
|
204
|
+
id val = [expr expressionValueWithObject:nil context:nil];
|
|
205
|
+
if ([val isKindOfClass:[NSNumber class]]) {
|
|
206
|
+
NSString *result = [(NSNumber *)val stringValue];
|
|
207
|
+
NSString *formatted = [self formatNumberGroups:result];
|
|
208
|
+
_textField.text = formatted;
|
|
209
|
+
|
|
210
|
+
UITextPosition *end = _textField.endOfDocument;
|
|
211
|
+
_textField.selectedTextRange = [_textField textRangeFromPosition:end toPosition:end];
|
|
180
212
|
|
|
181
|
-
if ([regex firstMatchInString:text options:0 range:range]) {
|
|
182
|
-
NSExpression *expression = [NSExpression expressionWithFormat:text];
|
|
183
|
-
id result = [expression expressionValueWithObject:nil context:nil];
|
|
184
|
-
if ([result isKindOfClass:[NSNumber class]]) {
|
|
185
|
-
_textField.text = [result stringValue];
|
|
186
213
|
[self notifyTextChange];
|
|
187
214
|
}
|
|
188
|
-
}
|
|
215
|
+
} @catch (__unused NSException *e) { }
|
|
189
216
|
}
|
|
190
217
|
|
|
191
218
|
- (void)emitCustomKey
|
|
@@ -221,6 +248,118 @@ static UIFontWeight _UIFontWeightFromString(std::string_view s) {
|
|
|
221
248
|
}
|
|
222
249
|
}
|
|
223
250
|
|
|
251
|
+
#pragma mark - Thousand grouping helpers (strip/format + caret mapping)
|
|
252
|
+
|
|
253
|
+
- (BOOL)isGroupDotAt:(NSInteger)i inString:(NSString *)s {
|
|
254
|
+
if (i < 0 || i >= (NSInteger)s.length) return NO;
|
|
255
|
+
unichar c = [s characterAtIndex:i];
|
|
256
|
+
if (c != '.') return NO;
|
|
257
|
+
BOOL leftIsDigit = (i - 1 >= 0) && [[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[s characterAtIndex:i-1]];
|
|
258
|
+
BOOL rightIsDigit = (i + 1 < (NSInteger)s.length) && [[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[s characterAtIndex:i+1]];
|
|
259
|
+
return leftIsDigit && rightIsDigit;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
- (NSString *)stripGroupDots:(NSString *)input {
|
|
263
|
+
if (input.length == 0) return input;
|
|
264
|
+
NSMutableString *out = [NSMutableString stringWithCapacity:input.length];
|
|
265
|
+
for (NSInteger i = 0; i < (NSInteger)input.length; i++) {
|
|
266
|
+
unichar c = [input characterAtIndex:i];
|
|
267
|
+
if (c == '.' && [self isGroupDotAt:i inString:input]) {
|
|
268
|
+
// skip
|
|
269
|
+
} else {
|
|
270
|
+
[out appendFormat:@"%C", c];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return out;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
- (NSString *)formatNumberGroups:(NSString *)input {
|
|
277
|
+
NSString *noSep = [self stripGroupDots:input];
|
|
278
|
+
if (noSep.length == 0) return noSep;
|
|
279
|
+
|
|
280
|
+
NSError *err = nil;
|
|
281
|
+
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:@"\\d+" options:0 error:&err];
|
|
282
|
+
if (err) return noSep;
|
|
283
|
+
|
|
284
|
+
NSMutableString *result = [noSep mutableCopy];
|
|
285
|
+
__block NSInteger delta = 0;
|
|
286
|
+
[re enumerateMatchesInString:noSep options:0 range:NSMakeRange(0, noSep.length) usingBlock:^(NSTextCheckingResult * _Nullable match, NSMatchingFlags flags, BOOL * _Nonnull stop) {
|
|
287
|
+
if (!match) return;
|
|
288
|
+
NSRange mr = match.range;
|
|
289
|
+
mr.location += delta;
|
|
290
|
+
|
|
291
|
+
NSString *digits = [result substringWithRange:mr];
|
|
292
|
+
|
|
293
|
+
NSMutableString *rev = [NSMutableString stringWithCapacity:digits.length];
|
|
294
|
+
for (NSInteger i = digits.length - 1; i >= 0; i--) {
|
|
295
|
+
[rev appendFormat:@"%C", [digits characterAtIndex:i]];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
NSMutableArray<NSString *> *chunks = [NSMutableArray array];
|
|
299
|
+
for (NSInteger i = 0; i < (NSInteger)rev.length; i += 3) {
|
|
300
|
+
NSRange r = NSMakeRange(i, MIN(3, (NSInteger)rev.length - i));
|
|
301
|
+
[chunks addObject:[rev substringWithRange:r]];
|
|
302
|
+
}
|
|
303
|
+
NSString *joined = [chunks componentsJoinedByString:@"."];
|
|
304
|
+
|
|
305
|
+
NSMutableString *final = [NSMutableString stringWithCapacity:joined.length];
|
|
306
|
+
for (NSInteger i = joined.length - 1; i >= 0; i--) {
|
|
307
|
+
[final appendFormat:@"%C", [joined characterAtIndex:i]];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
[result replaceCharactersInRange:mr withString:final];
|
|
311
|
+
delta += (final.length - mr.length);
|
|
312
|
+
}];
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
- (NSInteger)formattedCaretToRaw:(NSString *)formatted caret:(NSInteger)caretFmt {
|
|
318
|
+
NSInteger rawIdx = 0;
|
|
319
|
+
NSInteger limit = MIN(caretFmt, (NSInteger)formatted.length);
|
|
320
|
+
for (NSInteger i = 0; i < limit; i++) {
|
|
321
|
+
unichar c = [formatted characterAtIndex:i];
|
|
322
|
+
if (!(c == '.' && [self isGroupDotAt:i inString:formatted])) {
|
|
323
|
+
rawIdx++;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return rawIdx;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
- (NSInteger)rawCaretToFormatted:(NSInteger)rawCaret inFormatted:(NSString *)formatted {
|
|
330
|
+
NSInteger rawSeen = 0;
|
|
331
|
+
for (NSInteger i = 0; i < (NSInteger)formatted.length; i++) {
|
|
332
|
+
unichar c = [formatted characterAtIndex:i];
|
|
333
|
+
if (!(c == '.' && [self isGroupDotAt:i inString:formatted])) {
|
|
334
|
+
if (rawSeen == rawCaret) return i;
|
|
335
|
+
rawSeen++;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return (NSInteger)formatted.length;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
- (void)reformatAndKeepSelection {
|
|
342
|
+
NSString *formattedBefore = _textField.text ?: @"";
|
|
343
|
+
|
|
344
|
+
UITextRange *selRange = _textField.selectedTextRange;
|
|
345
|
+
NSInteger caretFmtBefore = (NSInteger)[_textField offsetFromPosition:_textField.beginningOfDocument
|
|
346
|
+
toPosition:selRange.start];
|
|
347
|
+
|
|
348
|
+
NSInteger caretRaw = [self formattedCaretToRaw:formattedBefore caret:caretFmtBefore];
|
|
349
|
+
NSString *formattedAfter = [self formatNumberGroups:formattedBefore];
|
|
350
|
+
|
|
351
|
+
if (![formattedAfter isEqualToString:formattedBefore]) {
|
|
352
|
+
_textField.text = formattedAfter;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
NSInteger caretFmtAfter = [self rawCaretToFormatted:caretRaw inFormatted:formattedAfter];
|
|
356
|
+
UITextPosition *pos = [_textField positionFromPosition:_textField.beginningOfDocument offset:caretFmtAfter];
|
|
357
|
+
if (pos) {
|
|
358
|
+
_textField.selectedTextRange = [_textField textRangeFromPosition:pos toPosition:pos];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
224
363
|
@end
|
|
225
364
|
|
|
226
365
|
Class<RCTNativeInputCalculatorViewProtocol> NativeInputCalculatorCls(void)
|