react-native-markdown-native 1.0.0

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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/MarkdownNative.podspec +19 -0
  3. package/README.md +109 -0
  4. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  5. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  6. package/android/.gradle/8.9/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  8. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  9. package/android/.gradle/8.9/gc.properties +0 -0
  10. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  11. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  12. package/android/.gradle/vcs-1/gc.properties +0 -0
  13. package/android/build.gradle +36 -0
  14. package/android/src/main/AndroidManifest.xml +2 -0
  15. package/android/src/main/java/com/markdownnative/MarkdownPackage.java +25 -0
  16. package/android/src/main/java/com/markdownnative/MarkdownView.java +516 -0
  17. package/android/src/main/java/com/markdownnative/MarkdownViewManager.java +107 -0
  18. package/ios/MarkdownView.swift +771 -0
  19. package/ios/MarkdownViewManager.m +27 -0
  20. package/ios/MarkdownViewManager.swift +20 -0
  21. package/lib/commonjs/index.js +97 -0
  22. package/lib/commonjs/index.js.map +1 -0
  23. package/lib/commonjs/package.json +1 -0
  24. package/lib/commonjs/types.js +154 -0
  25. package/lib/commonjs/types.js.map +1 -0
  26. package/lib/index.d.ts +26 -0
  27. package/lib/index.js +97 -0
  28. package/lib/module/index.js +80 -0
  29. package/lib/module/index.js.map +1 -0
  30. package/lib/module/types.js +153 -0
  31. package/lib/module/types.js.map +1 -0
  32. package/lib/typescript/index.d.ts +30 -0
  33. package/lib/typescript/index.d.ts.map +1 -0
  34. package/lib/typescript/types.d.ts +80 -0
  35. package/lib/typescript/types.d.ts.map +1 -0
  36. package/package.json +92 -0
  37. package/react-native.config.js +11 -0
  38. package/src/index.tsx +117 -0
  39. package/src/types.ts +205 -0
@@ -0,0 +1,516 @@
1
+ package com.markdownnative;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.Color;
5
+ import android.graphics.Typeface;
6
+ import android.text.method.ArrowKeyMovementMethod;
7
+ import android.text.method.LinkMovementMethod;
8
+ import android.text.style.ForegroundColorSpan;
9
+ import android.widget.TextView;
10
+
11
+ import com.facebook.react.bridge.Arguments;
12
+ import com.facebook.react.bridge.ReactContext;
13
+ import com.facebook.react.bridge.WritableMap;
14
+ import com.facebook.react.uimanager.PixelUtil;
15
+ import com.facebook.react.uimanager.events.RCTEventEmitter;
16
+
17
+ import android.text.Layout;
18
+ import android.text.Spannable;
19
+ import android.text.style.ClickableSpan;
20
+ import android.view.MotionEvent;
21
+ import android.graphics.PorterDuff;
22
+ import android.graphics.drawable.Drawable;
23
+ import android.os.Build;
24
+
25
+ import io.noties.markwon.Markwon;
26
+ import io.noties.markwon.MarkwonSpansFactory;
27
+ import io.noties.markwon.core.MarkwonTheme;
28
+ import io.noties.markwon.html.HtmlPlugin;
29
+ import com.facebook.react.views.text.ReactFontManager;
30
+
31
+ public class MarkdownView extends TextView {
32
+
33
+ private Markwon markwon;
34
+ private String markdownText = "";
35
+ private Context context;
36
+
37
+ // Styling props
38
+ private int linkColor = Color.parseColor("#0066CC");
39
+ private float lineSpacing = 0f;
40
+ private float paragraphSpacing = 0f;
41
+ private float bulletIndent = 0f;
42
+ private float listLeftInset = 0f;
43
+ private float listSpacingBefore = 0f;
44
+ private float listSpacingAfter = 0f;
45
+
46
+ // Rules-based styling system
47
+ private String markdownRulesJson = "";
48
+ private org.json.JSONObject parsedRules = null;
49
+
50
+ public MarkdownView(Context context) {
51
+ super(context);
52
+ this.context = context;
53
+ this.setTextIsSelectable(true);
54
+ this.setFocusable(true);
55
+ this.setFocusableInTouchMode(true);
56
+ rebuildMarkwon();
57
+ }
58
+
59
+ private void rebuildMarkwon() {
60
+ // Get values from rules if available, otherwise use defaults
61
+ final int finalBulletIndent = parsedRules != null ?
62
+ (int) PixelUtil.toPixelFromDIP(getRuleFloatValue("listItem", "bulletIndent", bulletIndent > 0 ? bulletIndent : 16f)) :
63
+ bulletIndent > 0 ? (int) PixelUtil.toPixelFromDIP(bulletIndent) : (int) PixelUtil.toPixelFromDIP(16);
64
+
65
+ final int finalListLeftInset = parsedRules != null ?
66
+ (int) PixelUtil.toPixelFromDIP(getRuleFloatValue("listItem", "listLeftInset", listLeftInset)) :
67
+ (int) PixelUtil.toPixelFromDIP(listLeftInset);
68
+
69
+ final int finalLinkColor = parsedRules != null ?
70
+ getRuleColorValue("link", "color", linkColor) :
71
+ linkColor;
72
+
73
+ // Get heading size multipliers from rules or use defaults
74
+ final float[] headingMultipliers = new float[6];
75
+ for (int i = 0; i < 6; i++) {
76
+ String headingType = "heading" + (i + 1);
77
+ float defaultSize;
78
+ switch (i) {
79
+ case 0: defaultSize = 32f; break; // H1
80
+ case 1: defaultSize = 28f; break; // H2
81
+ case 2: defaultSize = 24f; break; // H3
82
+ case 3: defaultSize = 20f; break; // H4
83
+ case 4: defaultSize = 18f; break; // H5
84
+ case 5: defaultSize = 16f; break; // H6
85
+ default: defaultSize = 16f;
86
+ }
87
+
88
+ if (parsedRules != null) {
89
+ float ruleSize = getRuleFloatValue(headingType, "fontSize", defaultSize);
90
+ // Convert to multiplier based on base font size (16)
91
+ headingMultipliers[i] = ruleSize / 16f;
92
+ } else {
93
+ // Use default multipliers
94
+ headingMultipliers[i] = defaultSize / 16f;
95
+ }
96
+ }
97
+
98
+ this.markwon = Markwon.builder(context)
99
+ .usePlugin(HtmlPlugin.create())
100
+ .usePlugin(new io.noties.markwon.AbstractMarkwonPlugin() {
101
+ @Override
102
+ public void configureTheme(@androidx.annotation.NonNull MarkwonTheme.Builder builder) {
103
+ builder
104
+ .headingBreakHeight(0)
105
+ .bulletWidth(finalBulletIndent)
106
+ .listItemColor(markdownColor)
107
+ .linkColor(finalLinkColor)
108
+ .headingTextSizeMultipliers(headingMultipliers);
109
+ }
110
+ })
111
+ .build();
112
+ }
113
+
114
+
115
+ private int lastDispatchedHeight = -1;
116
+
117
+ @Override
118
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
119
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
120
+
121
+ android.text.Layout layout = getLayout();
122
+ if (layout != null) {
123
+ int textHeight = layout.getHeight() + getPaddingTop() + getPaddingBottom();
124
+ int width = getMeasuredWidth();
125
+
126
+ if (width > 0 && textHeight > 0 && Math.abs(textHeight - lastDispatchedHeight) > 1) {
127
+ lastDispatchedHeight = textHeight;
128
+
129
+ WritableMap map = Arguments.createMap();
130
+ map.putDouble("width", PixelUtil.toDIPFromPixel(width));
131
+ map.putDouble("height", PixelUtil.toDIPFromPixel(textHeight));
132
+
133
+ Context ctx = getContext();
134
+ if (ctx instanceof ReactContext) {
135
+ ((ReactContext) ctx)
136
+ .getJSModule(RCTEventEmitter.class)
137
+ .receiveEvent(getId(), "onNativeSizeChange", map);
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ @Override
144
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
145
+ super.onLayout(changed, left, top, right, bottom);
146
+ }
147
+
148
+
149
+ private boolean isInLongPress = false;
150
+
151
+ @Override
152
+ public boolean onTouchEvent(MotionEvent event) {
153
+ // If text is selectable, we need to handle touch events carefully
154
+ // We only want to block parent scrolling when:
155
+ // 1. User is long-pressing (text selection is starting)
156
+ // 2. User is dragging selection handles (text is already selected)
157
+ // Otherwise, allow normal scrolling
158
+
159
+ if (isTextSelectable()) {
160
+ int action = event.getAction();
161
+
162
+ switch (action) {
163
+ case MotionEvent.ACTION_DOWN:
164
+ // Don't block on ACTION_DOWN - allow scrolling to work normally
165
+ // We'll only block if long press is detected or text is selected
166
+ isInLongPress = false;
167
+ break;
168
+
169
+ case MotionEvent.ACTION_MOVE:
170
+ // Only block scrolling if:
171
+ // - We're in a long press (selection menu is showing)
172
+ // - OR text is currently selected (user might be dragging handles)
173
+ if (isInLongPress || hasTextSelection()) {
174
+ if (getParent() != null) {
175
+ getParent().requestDisallowInterceptTouchEvent(true);
176
+ }
177
+ }
178
+ break;
179
+
180
+ case MotionEvent.ACTION_UP:
181
+ case MotionEvent.ACTION_CANCEL:
182
+ // Always release on touch end
183
+ isInLongPress = false;
184
+ if (getParent() != null) {
185
+ getParent().requestDisallowInterceptTouchEvent(false);
186
+ }
187
+ break;
188
+ }
189
+ }
190
+
191
+ return super.onTouchEvent(event);
192
+ }
193
+
194
+ /**
195
+ * Check if this TextView currently has selected text
196
+ */
197
+ private boolean hasTextSelection() {
198
+ return getSelectionStart() != getSelectionEnd();
199
+ }
200
+
201
+ @Override
202
+ public boolean performLongClick() {
203
+ // Mark that we're in a long press - this will prevent scrolling
204
+ // during the selection process
205
+ isInLongPress = true;
206
+ if (getParent() != null) {
207
+ getParent().requestDisallowInterceptTouchEvent(true);
208
+ }
209
+ return super.performLongClick();
210
+ }
211
+
212
+ public void setMarkdownText(String text) {
213
+ if (text == null) text = "";
214
+ if (this.markdownText.equals(text)) return;
215
+ this.markdownText = text;
216
+ renderMarkdown();
217
+ }
218
+
219
+ private void renderMarkdown() {
220
+ if (this.markdownText == null) return;
221
+
222
+ try {
223
+ markwon.setMarkdown(this, this.markdownText);
224
+ if (lineSpacing > 0) {
225
+ this.setLineSpacing(PixelUtil.toPixelFromDIP(lineSpacing), 1.0f);
226
+ }
227
+ } catch (Exception e) {
228
+ this.setText(this.markdownText);
229
+ }
230
+
231
+ if (isTextSelectable()) {
232
+ // Re-apply properties ensuring it captures touches
233
+ this.setTextIsSelectable(true);
234
+ this.setFocusable(true);
235
+ this.setFocusableInTouchMode(true);
236
+ this.setClickable(true);
237
+ this.setLongClickable(true);
238
+ this.setMovementMethod(MarkdownMovementMethod.getInstance());
239
+ }
240
+ requestLayout();
241
+ }
242
+
243
+ private int markdownColor = Integer.MIN_VALUE;
244
+
245
+ public void setMarkdownColor(String colorStr) {
246
+ try {
247
+ int color = (colorStr != null && !colorStr.isEmpty()) ? Color.parseColor(colorStr) : Color.BLACK;
248
+ if (this.markdownColor == color) return;
249
+ this.markdownColor = color;
250
+ this.setTextColor(color);
251
+ rebuildMarkwon();
252
+ renderMarkdown();
253
+ } catch (Exception e) {
254
+ this.setTextColor(Color.BLACK);
255
+ }
256
+ }
257
+
258
+ private float markdownFontSize = Float.NaN;
259
+
260
+ public void setMarkdownFontSize(float size) {
261
+ if (size == this.markdownFontSize) return;
262
+ this.markdownFontSize = size;
263
+ this.setTextSize(size);
264
+ renderMarkdown();
265
+ }
266
+
267
+ public void setMarkdownFontFamily(String fontFamily) {
268
+ if (fontFamily != null && !fontFamily.isEmpty()) {
269
+ try {
270
+ Typeface tf = ReactFontManager.getInstance().getTypeface(fontFamily, Typeface.NORMAL, this.context.getAssets());
271
+ if (tf == null) {
272
+ tf = Typeface.create(fontFamily, Typeface.NORMAL);
273
+ }
274
+ this.setTypeface(tf);
275
+ this.setTypeface(tf);
276
+
277
+ rebuildMarkwon();
278
+ renderMarkdown();
279
+ } catch (Exception e) {
280
+ // Keep default
281
+ }
282
+ }
283
+ }
284
+
285
+ public void setMarkdownSelectionColor(String color) {
286
+ try {
287
+ if (color != null && !color.isEmpty()) {
288
+ int parsedColor = Color.parseColor(color);
289
+ this.setHighlightColor(parsedColor);
290
+
291
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
292
+ Drawable left = getTextSelectHandleLeft();
293
+ if (left != null) {
294
+ left.mutate().setColorFilter(parsedColor, PorterDuff.Mode.SRC_IN);
295
+ setTextSelectHandleLeft(left);
296
+ }
297
+
298
+ Drawable right = getTextSelectHandleRight();
299
+ if (right != null) {
300
+ right.mutate().setColorFilter(parsedColor, PorterDuff.Mode.SRC_IN);
301
+ setTextSelectHandleRight(right);
302
+ }
303
+
304
+ Drawable handle = getTextSelectHandle();
305
+ if (handle != null) {
306
+ handle.mutate().setColorFilter(parsedColor, PorterDuff.Mode.SRC_IN);
307
+ setTextSelectHandle(handle);
308
+ }
309
+ }
310
+ }
311
+ } catch (Exception e) {
312
+ // Ignore
313
+ }
314
+ }
315
+
316
+ public void setMarkdownSelectable(boolean selectable) {
317
+ this.setTextIsSelectable(selectable);
318
+ this.setFocusable(selectable);
319
+ this.setFocusableInTouchMode(selectable);
320
+
321
+ // We do NOT set the movement method here directly anymore if we rely on renderMarkdown
322
+ // determining it, OR we set it here but ensure renderMarkdown respects it.
323
+ // Actually, renderMarkdown re-applies it if isTextSelectable() is true.
324
+ // So just calling requestLayout() or letting the next render pass handle it?
325
+ // Better:
326
+ if (selectable) {
327
+ this.setMovementMethod(MarkdownMovementMethod.getInstance());
328
+ } else {
329
+ this.setMovementMethod(LinkMovementMethod.getInstance());
330
+ }
331
+ }
332
+
333
+ public void setMarkdownScrollEnabled(boolean scrollEnabled) {
334
+ this.setVerticalScrollBarEnabled(scrollEnabled);
335
+ }
336
+
337
+ // New customization methods
338
+ public void setMarkdownLinkColor(String color) {
339
+ try {
340
+ if (color != null && !color.isEmpty()) {
341
+ this.linkColor = Color.parseColor(color);
342
+ rebuildMarkwon();
343
+ renderMarkdown();
344
+ }
345
+ } catch (Exception e) {
346
+ // Ignore
347
+ }
348
+ }
349
+
350
+ public void setMarkdownLineSpacing(float spacing) {
351
+ this.lineSpacing = spacing;
352
+ if (spacing > 0) {
353
+ this.setLineSpacing(PixelUtil.toPixelFromDIP(spacing), 1.0f);
354
+ } else {
355
+ this.setLineSpacing(0, 1.0f);
356
+ }
357
+ }
358
+
359
+ public void setMarkdownParagraphSpacing(float spacing) {
360
+ this.paragraphSpacing = spacing;
361
+ }
362
+
363
+ public void setMarkdownBulletIndent(float indent) {
364
+ this.bulletIndent = indent;
365
+ rebuildMarkwon();
366
+ renderMarkdown();
367
+ }
368
+
369
+ public void setMarkdownListLeftInset(float inset) {
370
+ this.listLeftInset = inset;
371
+ }
372
+
373
+ public void setMarkdownListSpacingBefore(float spacing) {
374
+ this.listSpacingBefore = spacing;
375
+ }
376
+
377
+ public void setMarkdownListSpacingAfter(float spacing) {
378
+ this.listSpacingAfter = spacing;
379
+ }
380
+
381
+ private static class MarkdownMovementMethod extends ArrowKeyMovementMethod {
382
+ private static MarkdownMovementMethod instance;
383
+
384
+ public static android.text.method.MovementMethod getInstance() {
385
+ if (instance == null) {
386
+ instance = new MarkdownMovementMethod();
387
+ }
388
+ return instance;
389
+ }
390
+
391
+ @Override
392
+ public boolean onTouchEvent(android.widget.TextView widget, android.text.Spannable buffer, android.view.MotionEvent event) {
393
+ int action = event.getAction();
394
+
395
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
396
+ int x = (int) event.getX();
397
+ int y = (int) event.getY();
398
+
399
+ x -= widget.getTotalPaddingLeft();
400
+ y -= widget.getTotalPaddingTop();
401
+
402
+ x += widget.getScrollX();
403
+ y += widget.getScrollY();
404
+
405
+ Layout layout = widget.getLayout();
406
+ // Check if layout is null
407
+ if (layout == null) {
408
+ return super.onTouchEvent(widget, buffer, event);
409
+ }
410
+
411
+ int line = layout.getLineForVertical(y);
412
+
413
+ if (x < layout.getLineLeft(line) || x > layout.getLineRight(line)) {
414
+ return super.onTouchEvent(widget, buffer, event);
415
+ }
416
+
417
+ int off = layout.getOffsetForHorizontal(line, x);
418
+
419
+ ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);
420
+
421
+ if (links.length != 0) {
422
+ ClickableSpan link = links[0];
423
+
424
+ int start = buffer.getSpanStart(link);
425
+ int end = buffer.getSpanEnd(link);
426
+
427
+ if (off >= start && off < end) {
428
+ if (action == MotionEvent.ACTION_UP) {
429
+ if (android.text.Selection.getSelectionStart(buffer) == android.text.Selection.getSelectionEnd(buffer)) {
430
+ link.onClick(widget);
431
+ }
432
+ return true;
433
+ }
434
+ else if (action == MotionEvent.ACTION_DOWN) {
435
+ // We don't return true here for links, because we want to allow long press
436
+ // for selection to still work even if the touch started on a link.
437
+ // super.onTouchEvent() handles the long press check.
438
+ return super.onTouchEvent(widget, buffer, event);
439
+ }
440
+ }
441
+ }
442
+ }
443
+ return super.onTouchEvent(widget, buffer, event);
444
+ }
445
+ }
446
+
447
+ // MARK: - Rules Parsing and Application
448
+
449
+ public void setMarkdownRulesJson(String rulesJson) {
450
+ if (rulesJson == null) rulesJson = "";
451
+ if (this.markdownRulesJson.equals(rulesJson)) return;
452
+ this.markdownRulesJson = rulesJson;
453
+ parseAndApplyRules();
454
+ }
455
+
456
+ private void parseAndApplyRules() {
457
+ if (markdownRulesJson == null || markdownRulesJson.isEmpty()) {
458
+ parsedRules = null;
459
+ return;
460
+ }
461
+
462
+ try {
463
+ parsedRules = new org.json.JSONObject(markdownRulesJson);
464
+ rebuildMarkwon();
465
+ renderMarkdown();
466
+ } catch (org.json.JSONException e) {
467
+ parsedRules = null;
468
+ }
469
+ }
470
+
471
+ private int getRuleColorValue(String elementType, String key, int defaultValue) {
472
+ if (parsedRules == null) return defaultValue;
473
+
474
+ try {
475
+ org.json.JSONObject element = parsedRules.optJSONObject(elementType);
476
+ if (element == null) return defaultValue;
477
+
478
+ String colorStr = element.optString(key, null);
479
+ if (colorStr != null && !colorStr.isEmpty()) {
480
+ return Color.parseColor(colorStr);
481
+ }
482
+ } catch (Exception e) {
483
+ // Ignore parsing errors
484
+ }
485
+
486
+ return defaultValue;
487
+ }
488
+
489
+ private float getRuleFloatValue(String elementType, String key, float defaultValue) {
490
+ if (parsedRules == null) return defaultValue;
491
+
492
+ try {
493
+ org.json.JSONObject element = parsedRules.optJSONObject(elementType);
494
+ if (element == null) return defaultValue;
495
+
496
+ return (float) element.optDouble(key, defaultValue);
497
+ } catch (Exception e) {
498
+ return defaultValue;
499
+ }
500
+ }
501
+
502
+ private int getRuleIntValue(String elementType, String key, int defaultValue) {
503
+ if (parsedRules == null) return defaultValue;
504
+
505
+ try {
506
+ org.json.JSONObject element = parsedRules.optJSONObject(elementType);
507
+ if (element == null) return defaultValue;
508
+
509
+ return element.optInt(key, defaultValue);
510
+ } catch (Exception e) {
511
+ return defaultValue;
512
+ }
513
+ }
514
+ }
515
+
516
+
@@ -0,0 +1,107 @@
1
+ package com.markdownnative;
2
+
3
+ import com.facebook.react.common.MapBuilder;
4
+ import com.facebook.react.uimanager.SimpleViewManager;
5
+ import com.facebook.react.uimanager.ThemedReactContext;
6
+ import com.facebook.react.uimanager.annotations.ReactProp;
7
+ import java.util.Map;
8
+
9
+ public class MarkdownViewManager extends SimpleViewManager<MarkdownView> {
10
+
11
+ public static final String REACT_CLASS = "MarkdownView";
12
+
13
+ @Override
14
+ public String getName() {
15
+ return REACT_CLASS;
16
+ }
17
+
18
+ @Override
19
+ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
20
+ return MapBuilder.<String, Object>builder()
21
+ .put("onNativeSizeChange", MapBuilder.of("registrationName", "onNativeSizeChange"))
22
+ .build();
23
+ }
24
+
25
+ @Override
26
+ protected MarkdownView createViewInstance(ThemedReactContext reactContext) {
27
+ return new MarkdownView(reactContext);
28
+ }
29
+
30
+ @ReactProp(name = "markdownText")
31
+ public void setMarkdownText(MarkdownView view, String text) {
32
+ view.setMarkdownText(text);
33
+ }
34
+
35
+ @ReactProp(name = "markdownColor")
36
+ public void setMarkdownColor(MarkdownView view, String color) {
37
+ view.setMarkdownColor(color);
38
+ }
39
+
40
+ @ReactProp(name = "markdownFontSize")
41
+ public void setMarkdownFontSize(MarkdownView view, float size) {
42
+ view.setMarkdownFontSize(size);
43
+ }
44
+
45
+ @ReactProp(name = "markdownFontFamily")
46
+ public void setMarkdownFontFamily(MarkdownView view, String fontFamily) {
47
+ view.setMarkdownFontFamily(fontFamily);
48
+ }
49
+
50
+ @ReactProp(name = "markdownSelectionColor")
51
+ public void setMarkdownSelectionColor(MarkdownView view, String color) {
52
+ view.setMarkdownSelectionColor(color);
53
+ }
54
+
55
+ @ReactProp(name = "markdownSelectable")
56
+ public void setMarkdownSelectable(MarkdownView view, boolean selectable) {
57
+ view.setMarkdownSelectable(selectable);
58
+ }
59
+
60
+ @ReactProp(name = "markdownScrollEnabled")
61
+ public void setMarkdownScrollEnabled(MarkdownView view, boolean scrollEnabled) {
62
+ view.setMarkdownScrollEnabled(scrollEnabled);
63
+ }
64
+
65
+ // New customization props
66
+ @ReactProp(name = "markdownLinkColor")
67
+ public void setMarkdownLinkColor(MarkdownView view, String color) {
68
+ view.setMarkdownLinkColor(color);
69
+ }
70
+
71
+ @ReactProp(name = "markdownLineSpacing", defaultFloat = 0f)
72
+ public void setMarkdownLineSpacing(MarkdownView view, float spacing) {
73
+ view.setMarkdownLineSpacing(spacing);
74
+ }
75
+
76
+ @ReactProp(name = "markdownParagraphSpacing", defaultFloat = 0f)
77
+ public void setMarkdownParagraphSpacing(MarkdownView view, float spacing) {
78
+ view.setMarkdownParagraphSpacing(spacing);
79
+ }
80
+
81
+ @ReactProp(name = "markdownBulletIndent", defaultFloat = 0f)
82
+ public void setMarkdownBulletIndent(MarkdownView view, float indent) {
83
+ view.setMarkdownBulletIndent(indent);
84
+ }
85
+
86
+ @ReactProp(name = "markdownListLeftInset", defaultFloat = 0f)
87
+ public void setMarkdownListLeftInset(MarkdownView view, float inset) {
88
+ view.setMarkdownListLeftInset(inset);
89
+ }
90
+
91
+ @ReactProp(name = "markdownListSpacingBefore", defaultFloat = 0f)
92
+ public void setMarkdownListSpacingBefore(MarkdownView view, float spacing) {
93
+ view.setMarkdownListSpacingBefore(spacing);
94
+ }
95
+
96
+ @ReactProp(name = "markdownListSpacingAfter", defaultFloat = 0f)
97
+ public void setMarkdownListSpacingAfter(MarkdownView view, float spacing) {
98
+ view.setMarkdownListSpacingAfter(spacing);
99
+ }
100
+
101
+ // New rules-based styling system
102
+ @ReactProp(name = "markdownRulesJson")
103
+ public void setMarkdownRulesJson(MarkdownView view, String rulesJson) {
104
+ view.setMarkdownRulesJson(rulesJson);
105
+ }
106
+ }
107
+