react-native-enriched-markdown-scaffold 0.7.4-scaffold.1 → 0.7.4-scaffold.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt
CHANGED
|
@@ -848,18 +848,36 @@ class EnrichedMarkdownTextInputView(
|
|
|
848
848
|
return true
|
|
849
849
|
}
|
|
850
850
|
|
|
851
|
-
|
|
851
|
+
/** Chip glyph rect in [Layout] coordinates — no padding, no scroll offset. */
|
|
852
|
+
private fun chipBoundsInLayout(
|
|
852
853
|
layout: Layout,
|
|
853
854
|
start: Int,
|
|
854
855
|
): Rect {
|
|
855
856
|
val line = layout.getLineForOffset(start)
|
|
856
|
-
val xStart =
|
|
857
|
-
val xEnd =
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
857
|
+
val xStart = layout.getPrimaryHorizontal(start).toInt()
|
|
858
|
+
val xEnd = layout.getPrimaryHorizontal(start + 1).toInt()
|
|
859
|
+
// Ordered, not raw start/end: in an RTL run xStart > xEnd, which would make an
|
|
860
|
+
// inverted Rect that contains() always rejects.
|
|
861
|
+
val left = xStart.coerceAtMost(xEnd)
|
|
862
|
+
var right = xStart.coerceAtLeast(xEnd)
|
|
863
|
+
if (right <= left) {
|
|
864
|
+
// A chip that is the last character has start + 1 == length, where some layouts
|
|
865
|
+
// report the same horizontal for both offsets. Fall back to the line's content edge
|
|
866
|
+
// so the rect keeps a real width instead of collapsing.
|
|
867
|
+
right = layout.getLineRight(line).toInt()
|
|
868
|
+
}
|
|
869
|
+
return Rect(left, layout.getLineTop(line), right, layout.getLineBottom(line))
|
|
861
870
|
}
|
|
862
871
|
|
|
872
|
+
/** Chip glyph rect in view coordinates, for reporting to JS. */
|
|
873
|
+
private fun computeChipBounds(
|
|
874
|
+
layout: Layout,
|
|
875
|
+
start: Int,
|
|
876
|
+
): Rect =
|
|
877
|
+
chipBoundsInLayout(layout, start).apply {
|
|
878
|
+
offset(totalPaddingLeft - scrollX, totalPaddingTop - scrollY)
|
|
879
|
+
}
|
|
880
|
+
|
|
863
881
|
fun mathChipPositionAtTouch(
|
|
864
882
|
x: Float,
|
|
865
883
|
y: Float,
|
|
@@ -869,13 +887,21 @@ class EnrichedMarkdownTextInputView(
|
|
|
869
887
|
val layoutNow = layout ?: return -1
|
|
870
888
|
val adjustedX = (x - totalPaddingLeft + scrollX).toInt()
|
|
871
889
|
val adjustedY = (y - totalPaddingTop + scrollY).toInt()
|
|
872
|
-
|
|
890
|
+
// getLineForVertical and getOffsetForHorizontal both clamp into the laid-out text, so
|
|
891
|
+
// a touch in the padding or in the blank space below the last line resolves onto the
|
|
892
|
+
// nearest character. Bound the touch to the text box, then require it to land inside
|
|
893
|
+
// the chip's own rect — otherwise a tap beside a chip reads as a tap on it.
|
|
894
|
+
if (adjustedY < 0 || adjustedY > layoutNow.height) return -1
|
|
873
895
|
val line = layoutNow.getLineForVertical(adjustedY)
|
|
874
896
|
val offset = layoutNow.getOffsetForHorizontal(line, adjustedX.toFloat())
|
|
875
|
-
if (offset < 0
|
|
897
|
+
if (offset < 0) return -1
|
|
876
898
|
val candidates = intArrayOf(offset, offset - 1).filter { it in 0 until editable.length }
|
|
877
899
|
for (candidate in candidates) {
|
|
878
900
|
val range = controller.chipRangeAt(editable, candidate) ?: continue
|
|
901
|
+
// An empty rect means the layout could not give us the chip's extent; accept the
|
|
902
|
+
// candidate then, rather than making the chip untappable.
|
|
903
|
+
val bounds = chipBoundsInLayout(layoutNow, range.first)
|
|
904
|
+
if (!bounds.isEmpty && !bounds.contains(adjustedX, adjustedY)) continue
|
|
879
905
|
return range.first
|
|
880
906
|
}
|
|
881
907
|
return -1
|
|
@@ -253,6 +253,20 @@ using namespace facebook::react;
|
|
|
253
253
|
|
|
254
254
|
#if ENRICHED_MARKDOWN_MATH && !TARGET_OS_OSX
|
|
255
255
|
|
|
256
|
+
// Glyph rect of the chip at `index`, in _textView coordinates (the same space
|
|
257
|
+
// `locationInView:_textView` reports). Single source of truth for both the tap
|
|
258
|
+
// hit-test and the rect reported to JS.
|
|
259
|
+
- (CGRect)mathChipRectInTextViewAtIndex:(NSUInteger)index layoutManager:(NSLayoutManager *)lm
|
|
260
|
+
{
|
|
261
|
+
if (lm == nil)
|
|
262
|
+
return CGRectNull;
|
|
263
|
+
NSRange glyphRange = [lm glyphRangeForCharacterRange:NSMakeRange(index, 1) actualCharacterRange:NULL];
|
|
264
|
+
CGRect rect = [lm boundingRectForGlyphRange:glyphRange inTextContainer:_textView.textContainer];
|
|
265
|
+
rect.origin.x += _textView.textContainerInset.left;
|
|
266
|
+
rect.origin.y += _textView.textContainerInset.top;
|
|
267
|
+
return rect;
|
|
268
|
+
}
|
|
269
|
+
|
|
256
270
|
- (void)handleTapForMathChip:(UITapGestureRecognizer *)gesture
|
|
257
271
|
{
|
|
258
272
|
if (gesture.state != UIGestureRecognizerStateRecognized)
|
|
@@ -265,23 +279,28 @@ using namespace facebook::react;
|
|
|
265
279
|
if (idx < 0)
|
|
266
280
|
return;
|
|
267
281
|
NSTextStorage *storage = _textView.textStorage;
|
|
268
|
-
|
|
282
|
+
NSLayoutManager *lm = storage.layoutManagers.firstObject;
|
|
283
|
+
// The closest position can land just past the chip, so probe both `idx` and `idx-1`.
|
|
284
|
+
// `closestPositionToPoint:` has no distance bound — it clamps a tap anywhere in the
|
|
285
|
+
// text view (blank space past the text, textContainerInset padding) onto the nearest
|
|
286
|
+
// character. Requiring the touch to fall inside the chip's own glyph rect is what
|
|
287
|
+
// keeps a tap beside a chip from reading as a tap on it.
|
|
269
288
|
ENRMMathInlineAttachment *att = nil;
|
|
270
289
|
NSUInteger chipIdx = NSNotFound;
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
290
|
+
for (NSInteger probe = idx; probe >= idx - 1; probe--) {
|
|
291
|
+
if (probe < 0 || (NSUInteger)probe >= storage.length)
|
|
292
|
+
continue;
|
|
293
|
+
id v = [storage attribute:NSAttachmentAttributeName atIndex:(NSUInteger)probe effectiveRange:NULL];
|
|
294
|
+
if (![v isKindOfClass:[ENRMMathInlineAttachment class]])
|
|
295
|
+
continue;
|
|
296
|
+
// An empty rect means the layout could not give us the chip's extent; accept the
|
|
297
|
+
// candidate then, rather than making the chip untappable.
|
|
298
|
+
CGRect rect = [self mathChipRectInTextViewAtIndex:(NSUInteger)probe layoutManager:lm];
|
|
299
|
+
if (!CGRectIsEmpty(rect) && !CGRectContainsPoint(rect, loc))
|
|
300
|
+
continue;
|
|
301
|
+
att = v;
|
|
302
|
+
chipIdx = (NSUInteger)probe;
|
|
303
|
+
break;
|
|
285
304
|
}
|
|
286
305
|
if (!att)
|
|
287
306
|
return;
|
|
@@ -290,7 +309,6 @@ using namespace facebook::react;
|
|
|
290
309
|
[self clearMathChipSelectionsExcept:chipRange];
|
|
291
310
|
att.isSelected = YES;
|
|
292
311
|
[att invalidateRender];
|
|
293
|
-
NSLayoutManager *lm = storage.layoutManagers.firstObject;
|
|
294
312
|
if (lm) {
|
|
295
313
|
[lm invalidateLayoutForCharacterRange:chipRange actualCharacterRange:NULL];
|
|
296
314
|
[lm ensureLayoutForCharacterRange:chipRange];
|
|
@@ -306,10 +324,8 @@ using namespace facebook::react;
|
|
|
306
324
|
_suppressMathChipClearOnce = YES;
|
|
307
325
|
_textView.selectedRange = NSMakeRange(caretIndex, 0);
|
|
308
326
|
|
|
309
|
-
|
|
310
|
-
CGRect glyphRect = [
|
|
311
|
-
glyphRect.origin.x += _textView.textContainerInset.left;
|
|
312
|
-
glyphRect.origin.y += _textView.textContainerInset.top;
|
|
327
|
+
// Recomputed rather than reused from the hit-test: the layout was invalidated above.
|
|
328
|
+
CGRect glyphRect = [self mathChipRectInTextViewAtIndex:chipIdx layoutManager:lm];
|
|
313
329
|
CGRect rectInSelf = [self.contentView convertRect:glyphRect fromView:_textView];
|
|
314
330
|
|
|
315
331
|
[self emitOnMathPressWithLatex:att.latex start:chipIdx end:chipIdx + 1 displayMode:att.displayMode rect:rectInSelf];
|
package/package.json
CHANGED