react-native-nitro-markdown 0.6.1 → 0.7.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.
- package/README.md +77 -8
- package/android/gradle.properties +2 -2
- package/android/src/main/java/com/margelo/nitro/com/nitromarkdown/HybridMarkdownSession.kt +21 -3
- package/ios/HybridMarkdownSession.swift +53 -3
- package/lib/commonjs/MarkdownContext.js.map +1 -1
- package/lib/commonjs/headless.js +0 -4
- package/lib/commonjs/headless.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/markdown-stream.js +40 -15
- package/lib/commonjs/markdown-stream.js.map +1 -1
- package/lib/commonjs/markdown.js +4 -3
- package/lib/commonjs/markdown.js.map +1 -1
- package/lib/commonjs/renderers/heading.js +1 -0
- package/lib/commonjs/renderers/heading.js.map +1 -1
- package/lib/commonjs/renderers/image.js +22 -8
- package/lib/commonjs/renderers/image.js.map +1 -1
- package/lib/commonjs/renderers/link.js +1 -1
- package/lib/commonjs/renderers/link.js.map +1 -1
- package/lib/commonjs/renderers/list.js +4 -0
- package/lib/commonjs/renderers/list.js.map +1 -1
- package/lib/commonjs/renderers/math.js +0 -1
- package/lib/commonjs/renderers/math.js.map +1 -1
- package/lib/commonjs/use-markdown-stream.js +9 -1
- package/lib/commonjs/use-markdown-stream.js.map +1 -1
- package/lib/commonjs/utils/link-security.js +42 -5
- package/lib/commonjs/utils/link-security.js.map +1 -1
- package/lib/module/MarkdownContext.js.map +1 -1
- package/lib/module/headless.js +0 -4
- package/lib/module/headless.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/markdown-stream.js +40 -15
- package/lib/module/markdown-stream.js.map +1 -1
- package/lib/module/markdown.js +4 -3
- package/lib/module/markdown.js.map +1 -1
- package/lib/module/renderers/heading.js +1 -0
- package/lib/module/renderers/heading.js.map +1 -1
- package/lib/module/renderers/image.js +22 -8
- package/lib/module/renderers/image.js.map +1 -1
- package/lib/module/renderers/link.js +1 -1
- package/lib/module/renderers/link.js.map +1 -1
- package/lib/module/renderers/list.js +4 -0
- package/lib/module/renderers/list.js.map +1 -1
- package/lib/module/renderers/math.js +0 -1
- package/lib/module/renderers/math.js.map +1 -1
- package/lib/module/use-markdown-stream.js +9 -1
- package/lib/module/use-markdown-stream.js.map +1 -1
- package/lib/module/utils/link-security.js +40 -4
- package/lib/module/utils/link-security.js.map +1 -1
- package/lib/typescript/commonjs/MarkdownContext.d.ts +44 -7
- package/lib/typescript/commonjs/MarkdownContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/headless.d.ts +6 -3
- package/lib/typescript/commonjs/headless.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/markdown.d.ts +12 -11
- package/lib/typescript/commonjs/markdown.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/image.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/link.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/list.d.ts.map +1 -1
- package/lib/typescript/commonjs/renderers/math.d.ts.map +1 -1
- package/lib/typescript/commonjs/use-markdown-stream.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/link-security.d.ts +5 -0
- package/lib/typescript/commonjs/utils/link-security.d.ts.map +1 -1
- package/lib/typescript/module/MarkdownContext.d.ts +44 -7
- package/lib/typescript/module/MarkdownContext.d.ts.map +1 -1
- package/lib/typescript/module/headless.d.ts +6 -3
- package/lib/typescript/module/headless.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/markdown.d.ts +12 -11
- package/lib/typescript/module/markdown.d.ts.map +1 -1
- package/lib/typescript/module/renderers/heading.d.ts.map +1 -1
- package/lib/typescript/module/renderers/image.d.ts.map +1 -1
- package/lib/typescript/module/renderers/link.d.ts.map +1 -1
- package/lib/typescript/module/renderers/list.d.ts.map +1 -1
- package/lib/typescript/module/renderers/math.d.ts.map +1 -1
- package/lib/typescript/module/use-markdown-stream.d.ts.map +1 -1
- package/lib/typescript/module/utils/link-security.d.ts +5 -0
- package/lib/typescript/module/utils/link-security.d.ts.map +1 -1
- package/package.json +5 -5
- package/react-native-nitro-markdown.podspec +1 -1
- package/src/MarkdownContext.ts +51 -11
- package/src/headless.ts +35 -34
- package/src/index.ts +14 -1
- package/src/markdown-stream.tsx +49 -17
- package/src/markdown.tsx +20 -13
- package/src/renderers/heading.tsx +5 -1
- package/src/renderers/image.tsx +31 -9
- package/src/renderers/link.tsx +5 -2
- package/src/renderers/list.tsx +5 -1
- package/src/renderers/math.tsx +0 -1
- package/src/use-markdown-stream.ts +9 -1
- package/src/utils/link-security.ts +68 -4
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# react-native-nitro-markdown
|
|
2
2
|
|
|
3
|
-
[](https://reactnative.dev/)
|
|
6
|
-
[](https://www.npmjs.com/package/react-native-nitro-markdown)
|
|
4
|
+
[](https://github.com/JoaoPauloCMarra/react-native-nitro-markdown/blob/main/LICENSE)
|
|
5
|
+
[](https://reactnative.dev/docs/environment-setup)
|
|
6
|
+
[](https://nitro.margelo.com/)
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<img src="https://raw.githubusercontent.com/JoaoPauloCMarra/react-native-nitro-markdown/main/readme/demo.gif" alt="react-native-nitro-markdown demo" width="400" />
|
|
@@ -29,7 +29,7 @@ Native Markdown parsing and rendering for React Native, powered by [md4c](https:
|
|
|
29
29
|
| Dependency | Version |
|
|
30
30
|
|---|---|
|
|
31
31
|
| React Native | `>=0.75.0` |
|
|
32
|
-
| react-native-nitro-modules | `>=0.35.
|
|
32
|
+
| react-native-nitro-modules | `>=0.35.7` |
|
|
33
33
|
| ratex-react-native | `>=0.1.4` |
|
|
34
34
|
|
|
35
35
|
## Installation
|
|
@@ -51,8 +51,8 @@ cd ios && pod install
|
|
|
51
51
|
**Expo** (development build):
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
bunx expo install react-native-nitro-markdown react-native-nitro-modules ratex-react-native
|
|
55
|
+
bunx expo prebuild
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
## Quick Start
|
|
@@ -114,6 +114,7 @@ import { Markdown } from "react-native-nitro-markdown";
|
|
|
114
114
|
| `onError` | `(error, phase, pluginName?) => void` | -- | Error handler for parse/plugin failures |
|
|
115
115
|
| `highlightCode` | `boolean \| CodeHighlighter` | -- | Enable syntax highlighting for code blocks |
|
|
116
116
|
| `tableOptions` | `{ minColumnWidth?; measurementStabilizeMs? }` | -- | Table layout tuning |
|
|
117
|
+
| `imageOptions` | `UrlSafetyOptions` | `{ allowedProtocols: ["http:", "https:"] }` | Built-in image URL allowlist |
|
|
117
118
|
| `virtualize` | `boolean \| "auto"` | `false` | Top-level block virtualization |
|
|
118
119
|
| `virtualizationMinBlocks` | `number` | `40` | Block threshold for `"auto"` virtualization |
|
|
119
120
|
| `virtualization` | `MarkdownVirtualizationOptions` | -- | FlatList tuning (windowSize, batching, etc.) |
|
|
@@ -163,6 +164,7 @@ session.append("Streaming content...");
|
|
|
163
164
|
| `getTextRange` | `(from, to) => string` | Get substring range |
|
|
164
165
|
| `addListener` | `(listener) => () => void` | Subscribe to mutation events; returns unsubscribe |
|
|
165
166
|
| `highlightPosition` | `number` | Mutable cursor for stream highlight |
|
|
167
|
+
| `dispose` | `() => void` | Release native listener and buffer storage; called automatically by `useMarkdownSession` on unmount |
|
|
166
168
|
|
|
167
169
|
### `useMarkdownSession()`
|
|
168
170
|
|
|
@@ -253,6 +255,44 @@ Renderers receive `EnhancedRendererProps` with `node`, `children`, and `Renderer
|
|
|
253
255
|
|
|
254
256
|
Return `undefined` to fall back to the built-in renderer, or `null` to render nothing.
|
|
255
257
|
|
|
258
|
+
TypeScript users can import specific renderer props for stronger IDE feedback:
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
import type {
|
|
262
|
+
CustomRenderers,
|
|
263
|
+
ImageRendererProps,
|
|
264
|
+
LinkRendererProps,
|
|
265
|
+
MathRendererProps,
|
|
266
|
+
} from "react-native-nitro-markdown";
|
|
267
|
+
|
|
268
|
+
const renderers: CustomRenderers = {
|
|
269
|
+
link: ({ href, children }: LinkRendererProps) => (
|
|
270
|
+
<MyLink href={href}>{children}</MyLink>
|
|
271
|
+
),
|
|
272
|
+
image: ({ url, alt }: ImageRendererProps) => (
|
|
273
|
+
<MyImage source={{ uri: url }} accessibilityLabel={alt} />
|
|
274
|
+
),
|
|
275
|
+
math_block: ({ content }: MathRendererProps) => (
|
|
276
|
+
<MyMathBlock latex={content} />
|
|
277
|
+
),
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Use `satisfies CustomRenderers` when you want excess-property checking while preserving the exact function types for each renderer:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import type { CustomRenderers } from "react-native-nitro-markdown";
|
|
285
|
+
|
|
286
|
+
const renderers = {
|
|
287
|
+
heading: ({ level, children }) => (
|
|
288
|
+
<MyHeading variant={`h${level}`}>{children}</MyHeading>
|
|
289
|
+
),
|
|
290
|
+
code_block: ({ language, content }) => (
|
|
291
|
+
<MyCodeBlock language={language ?? "text"} code={content} />
|
|
292
|
+
),
|
|
293
|
+
} satisfies CustomRenderers;
|
|
294
|
+
```
|
|
295
|
+
|
|
256
296
|
#### Rendering HTML Nodes
|
|
257
297
|
|
|
258
298
|
HTML parsing is opt-in and produces raw AST nodes. The built-in renderer intentionally renders nothing for `html_inline` and `html_block`; applications decide what is safe to display.
|
|
@@ -474,6 +514,35 @@ const myHighlighter: CodeHighlighter = (language, code) => {
|
|
|
474
514
|
|
|
475
515
|
Default link behavior: trims href, calls `onLinkPress`, validates scheme (`http:`, `https:`, `mailto:`, `tel:`, `sms:`), then opens via `Linking.openURL`. Relative URLs and anchors are ignored unless handled in `onLinkPress`.
|
|
476
516
|
|
|
517
|
+
### Image URL Safety
|
|
518
|
+
|
|
519
|
+
Built-in image rendering only loads `http:` and `https:` URLs by default. Use `imageOptions` to narrow the allowed hosts or, when your app owns the source content, add another protocol explicitly.
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
import { Markdown, type UrlSafetyOptions } from "react-native-nitro-markdown";
|
|
523
|
+
|
|
524
|
+
const imageOptions: UrlSafetyOptions = {
|
|
525
|
+
allowedProtocols: ["https:"],
|
|
526
|
+
allowedHosts: ["cdn.example.com", "images.example.com"],
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
<Markdown imageOptions={imageOptions}>{content}</Markdown>;
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Relative image URLs are not loaded by the default renderer. Map them in a custom `image` renderer when your app has a trusted asset resolver.
|
|
533
|
+
|
|
534
|
+
### TypeScript Surface
|
|
535
|
+
|
|
536
|
+
The package exports public types for every commonly customized surface:
|
|
537
|
+
|
|
538
|
+
- `MarkdownProps`, `MarkdownStreamProps`, `MarkdownParseCompleteResult`, `MarkdownErrorPhase`
|
|
539
|
+
- `MarkdownNode`, `MarkdownNodeType`, `HeadingLevel`, `TableCellAlign`, `ParserOptions`, `AstTransform`, `MarkdownPlugin`
|
|
540
|
+
- `CustomRenderers`, `CustomRendererPropsByNode`, and node-specific renderer props
|
|
541
|
+
- `MarkdownTheme`, `PartialMarkdownTheme`, `NodeStyleOverrides`, `TableOptions`, `UrlSafetyOptions`
|
|
542
|
+
- `MarkdownSession`, `CodeHighlighter`, `HighlightedToken`, `TokenType`
|
|
543
|
+
|
|
544
|
+
Prefer typing shared `renderers`, `plugins`, `theme`, `tableOptions`, and `imageOptions` constants instead of relying on inference from inline JSX props. That keeps mistakes visible in IDEs and gives AI-assisted edits a stricter contract to follow.
|
|
545
|
+
|
|
477
546
|
## Supported Node Types
|
|
478
547
|
|
|
479
548
|
`document`, `heading`, `paragraph`, `text`, `bold`, `italic`, `strikethrough`, `link`, `image`, `code_inline`, `code_block`, `blockquote`, `horizontal_rule`, `line_break`, `soft_break`, `table`, `table_head`, `table_body`, `table_row`, `table_cell`, `list`, `list_item`, `task_list_item`, `math_inline`, `math_block`, `html_block`, `html_inline`
|
|
@@ -527,7 +596,7 @@ The `apps/example` directory contains a full demo app with these screens:
|
|
|
527
596
|
|
|
528
597
|
## Contributing
|
|
529
598
|
|
|
530
|
-
See [CONTRIBUTING.md](
|
|
599
|
+
See [CONTRIBUTING.md](https://github.com/JoaoPauloCMarra/react-native-nitro-markdown/blob/main/CONTRIBUTING.md).
|
|
531
600
|
|
|
532
601
|
## License
|
|
533
602
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
NitroMarkdown_compileSdkVersion=
|
|
1
|
+
NitroMarkdown_compileSdkVersion=36
|
|
2
2
|
# minSdkVersion stays at 24 (Android 7.0) to match the example app's minimum SDK requirement.
|
|
3
3
|
# Bumping to 26 would be preferable for security (full ASLR, etc.) but would break example builds.
|
|
4
4
|
NitroMarkdown_minSdkVersion=24
|
|
5
|
-
NitroMarkdown_targetSdkVersion=
|
|
5
|
+
NitroMarkdown_targetSdkVersion=36
|
|
6
6
|
NitroMarkdown_ndkVersion=27.1.12297006
|
|
@@ -19,8 +19,14 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
19
19
|
|
|
20
20
|
@GuardedBy("lock")
|
|
21
21
|
override var highlightPosition: Double = 0.0
|
|
22
|
-
get() = synchronized(lock) {
|
|
23
|
-
|
|
22
|
+
get() = synchronized(lock) {
|
|
23
|
+
checkActiveLocked()
|
|
24
|
+
field
|
|
25
|
+
}
|
|
26
|
+
set(value) = synchronized(lock) {
|
|
27
|
+
checkActiveLocked()
|
|
28
|
+
field = value
|
|
29
|
+
}
|
|
24
30
|
// No notify for highlighting to avoid flood
|
|
25
31
|
|
|
26
32
|
override val memorySize: Long
|
|
@@ -30,6 +36,7 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
30
36
|
val from: Int
|
|
31
37
|
val to: Int
|
|
32
38
|
synchronized(lock) {
|
|
39
|
+
checkActiveLocked()
|
|
33
40
|
if (buffer.length + chunk.length > MAX_BUFFER_SIZE) {
|
|
34
41
|
throw IllegalArgumentException("Buffer size limit exceeded (max ${MAX_BUFFER_SIZE} chars)")
|
|
35
42
|
}
|
|
@@ -43,6 +50,7 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
43
50
|
|
|
44
51
|
override fun clear() {
|
|
45
52
|
synchronized(lock) {
|
|
53
|
+
checkActiveLocked()
|
|
46
54
|
buffer.clear()
|
|
47
55
|
highlightPosition = 0.0
|
|
48
56
|
}
|
|
@@ -51,12 +59,14 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
51
59
|
|
|
52
60
|
override fun getAllText(): String {
|
|
53
61
|
synchronized(lock) {
|
|
62
|
+
checkActiveLocked()
|
|
54
63
|
return buffer.toString()
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
|
|
58
67
|
override fun getLength(): Double {
|
|
59
68
|
synchronized(lock) {
|
|
69
|
+
checkActiveLocked()
|
|
60
70
|
return buffer.length.toDouble()
|
|
61
71
|
}
|
|
62
72
|
}
|
|
@@ -64,6 +74,7 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
64
74
|
override fun getTextRange(from: Double, to: Double): String {
|
|
65
75
|
if (!from.isFinite() || !to.isFinite() || from < 0.0 || to < 0.0 || from > to) return ""
|
|
66
76
|
synchronized(lock) {
|
|
77
|
+
checkActiveLocked()
|
|
67
78
|
val start = from.toLong().coerceIn(0L, buffer.length.toLong()).toInt()
|
|
68
79
|
val end = to.toLong().coerceIn(start.toLong(), buffer.length.toLong()).toInt()
|
|
69
80
|
return buffer.substring(start, end)
|
|
@@ -72,7 +83,7 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
72
83
|
|
|
73
84
|
override fun addListener(listener: (Double, Double) -> Unit): () -> Unit {
|
|
74
85
|
synchronized(lock) {
|
|
75
|
-
|
|
86
|
+
checkActiveLocked()
|
|
76
87
|
val id = nextListenerId++
|
|
77
88
|
listeners[id] = listener
|
|
78
89
|
return { synchronized(lock) { listeners.remove(id) } }
|
|
@@ -81,6 +92,7 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
81
92
|
|
|
82
93
|
override fun reset(text: String) {
|
|
83
94
|
synchronized(lock) {
|
|
95
|
+
checkActiveLocked()
|
|
84
96
|
if (text.length > MAX_BUFFER_SIZE) {
|
|
85
97
|
throw IllegalArgumentException("Buffer size limit exceeded (max $MAX_BUFFER_SIZE chars)")
|
|
86
98
|
}
|
|
@@ -98,6 +110,7 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
98
110
|
val notifyFrom: Double
|
|
99
111
|
val notifyTo: Double
|
|
100
112
|
synchronized(lock) {
|
|
113
|
+
checkActiveLocked()
|
|
101
114
|
val start = from.toLong().coerceIn(0L, buffer.length.toLong()).toInt()
|
|
102
115
|
val end = to.toLong().coerceIn(start.toLong(), buffer.length.toLong()).toInt()
|
|
103
116
|
val newSize = buffer.length - (end - start) + text.length
|
|
@@ -127,6 +140,11 @@ class HybridMarkdownSession : HybridMarkdownSessionSpec() {
|
|
|
127
140
|
}
|
|
128
141
|
}
|
|
129
142
|
|
|
143
|
+
@GuardedBy("lock")
|
|
144
|
+
private fun checkActiveLocked() {
|
|
145
|
+
if (isDestroyed) throw IllegalStateException("HybridMarkdownSession is destroyed")
|
|
146
|
+
}
|
|
147
|
+
|
|
130
148
|
fun onDestroyed() {
|
|
131
149
|
synchronized(lock) {
|
|
132
150
|
isDestroyed = true
|
|
@@ -6,21 +6,51 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
6
6
|
|
|
7
7
|
private var buffer = ""
|
|
8
8
|
private var listeners: [UUID: (Double, Double) -> Void] = [:]
|
|
9
|
+
private var isDisposed = false
|
|
9
10
|
private let lock = NSLock()
|
|
10
11
|
|
|
11
12
|
var highlightPosition: Double {
|
|
12
|
-
get {
|
|
13
|
-
|
|
13
|
+
get {
|
|
14
|
+
lock.lock()
|
|
15
|
+
defer { lock.unlock() }
|
|
16
|
+
if isDisposed { return 0 }
|
|
17
|
+
return _highlightPosition
|
|
18
|
+
}
|
|
19
|
+
set {
|
|
20
|
+
lock.lock()
|
|
21
|
+
defer { lock.unlock() }
|
|
22
|
+
if isDisposed { return }
|
|
23
|
+
_highlightPosition = newValue
|
|
24
|
+
}
|
|
14
25
|
}
|
|
15
26
|
private var _highlightPosition: Double = 0
|
|
16
27
|
|
|
17
28
|
var memorySize: Int {
|
|
29
|
+
lock.lock()
|
|
30
|
+
defer { lock.unlock() }
|
|
18
31
|
return buffer.utf8.count
|
|
19
32
|
+ MemoryLayout<HybridMarkdownSession>.size
|
|
20
33
|
+ MemoryLayout<NSLock>.size
|
|
21
34
|
+ listeners.count * 128 // UUID key (16 bytes) + closure overhead estimate
|
|
22
35
|
}
|
|
23
36
|
|
|
37
|
+
deinit {
|
|
38
|
+
releaseStorage()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func dispose() {
|
|
42
|
+
releaseStorage()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private func releaseStorage() {
|
|
46
|
+
lock.lock()
|
|
47
|
+
defer { lock.unlock() }
|
|
48
|
+
listeners.removeAll()
|
|
49
|
+
buffer = ""
|
|
50
|
+
_highlightPosition = 0
|
|
51
|
+
isDisposed = true
|
|
52
|
+
}
|
|
53
|
+
|
|
24
54
|
private func utf16Length(_ value: String) -> Int {
|
|
25
55
|
return (value as NSString).length
|
|
26
56
|
}
|
|
@@ -43,6 +73,7 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
43
73
|
do {
|
|
44
74
|
lock.lock()
|
|
45
75
|
defer { lock.unlock() }
|
|
76
|
+
try validateActiveLocked()
|
|
46
77
|
let fromInt = utf16Length(buffer)
|
|
47
78
|
try validateBufferSize(fromInt + utf16Length(chunk))
|
|
48
79
|
buffer += chunk
|
|
@@ -58,6 +89,7 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
58
89
|
do {
|
|
59
90
|
lock.lock()
|
|
60
91
|
defer { lock.unlock() }
|
|
92
|
+
try validateActiveLocked()
|
|
61
93
|
buffer = ""
|
|
62
94
|
_highlightPosition = 0
|
|
63
95
|
}
|
|
@@ -67,12 +99,14 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
67
99
|
func getAllText() throws -> String {
|
|
68
100
|
lock.lock()
|
|
69
101
|
defer { lock.unlock() }
|
|
102
|
+
try validateActiveLocked()
|
|
70
103
|
return buffer
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
func getLength() throws -> Double {
|
|
74
107
|
lock.lock()
|
|
75
108
|
defer { lock.unlock() }
|
|
109
|
+
try validateActiveLocked()
|
|
76
110
|
return Double(utf16Length(buffer))
|
|
77
111
|
}
|
|
78
112
|
|
|
@@ -82,6 +116,7 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
82
116
|
}
|
|
83
117
|
lock.lock()
|
|
84
118
|
defer { lock.unlock() }
|
|
119
|
+
try validateActiveLocked()
|
|
85
120
|
|
|
86
121
|
let text = buffer as NSString
|
|
87
122
|
let length = text.length
|
|
@@ -97,8 +132,9 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
97
132
|
func addListener(listener: @escaping (Double, Double) -> Void) throws -> () -> Void {
|
|
98
133
|
let id = UUID()
|
|
99
134
|
lock.lock()
|
|
135
|
+
defer { lock.unlock() }
|
|
136
|
+
try validateActiveLocked()
|
|
100
137
|
listeners[id] = listener
|
|
101
|
-
lock.unlock()
|
|
102
138
|
|
|
103
139
|
return { [weak self] in
|
|
104
140
|
guard let self else { return }
|
|
@@ -113,6 +149,7 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
113
149
|
do {
|
|
114
150
|
lock.lock()
|
|
115
151
|
defer { lock.unlock() }
|
|
152
|
+
try validateActiveLocked()
|
|
116
153
|
try validateBufferSize(utf16Length(text))
|
|
117
154
|
buffer = text
|
|
118
155
|
_highlightPosition = 0
|
|
@@ -130,6 +167,7 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
130
167
|
do {
|
|
131
168
|
lock.lock()
|
|
132
169
|
defer { lock.unlock() }
|
|
170
|
+
try validateActiveLocked()
|
|
133
171
|
guard from.isFinite && to.isFinite && from >= 0 && to >= 0 && from <= to else {
|
|
134
172
|
throw NSError(
|
|
135
173
|
domain: "NitroMarkdown",
|
|
@@ -154,6 +192,18 @@ class HybridMarkdownSession: HybridMarkdownSessionSpec {
|
|
|
154
192
|
return newLength
|
|
155
193
|
}
|
|
156
194
|
|
|
195
|
+
private func validateActiveLocked() throws {
|
|
196
|
+
if isDisposed {
|
|
197
|
+
throw NSError(
|
|
198
|
+
domain: "NitroMarkdown",
|
|
199
|
+
code: 3,
|
|
200
|
+
userInfo: [
|
|
201
|
+
NSLocalizedDescriptionKey: "HybridMarkdownSession is destroyed"
|
|
202
|
+
]
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
157
207
|
/// Notifies all registered listeners about a buffer change.
|
|
158
208
|
/// Called OUTSIDE the lock to prevent deadlock (listeners may call back into this session).
|
|
159
209
|
/// The `from`/`to` values reflect the buffer state AT THE TIME OF MUTATION.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_theme","MarkdownContext","exports","createContext","renderers","theme","defaultMarkdownTheme","styles","undefined","stylingStrategy","onLinkPress","useMarkdownContext","useContext"],"sourceRoot":"../../src","sources":["MarkdownContext.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAOA,IAAAC,MAAA,GAAAD,OAAA;
|
|
1
|
+
{"version":3,"names":["_react","require","_theme","MarkdownContext","exports","createContext","renderers","theme","defaultMarkdownTheme","styles","undefined","stylingStrategy","onLinkPress","useMarkdownContext","useContext"],"sourceRoot":"../../src","sources":["MarkdownContext.ts"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AAOA,IAAAC,MAAA,GAAAD,OAAA;AAsIO,MAAME,eAAe,GAAAC,OAAA,CAAAD,eAAA,gBAAG,IAAAE,oBAAa,EAAuB;EACjEC,SAAS,EAAE,CAAC,CAAC;EACbC,KAAK,EAAEC,2BAAoB;EAC3BC,MAAM,EAAEC,SAAS;EACjBC,eAAe,EAAE,aAAa;EAC9BC,WAAW,EAAEF;AACf,CAAC,CAAC;AAEK,MAAMG,kBAAkB,GAAGA,CAAA,KAAM,IAAAC,iBAAU,EAACX,eAAe,CAAC;AAACC,OAAA,CAAAS,kBAAA,GAAAA,kBAAA","ignoreList":[]}
|
package/lib/commonjs/headless.js
CHANGED
|
@@ -35,7 +35,6 @@ const createEmptyDocument = () => ({
|
|
|
35
35
|
});
|
|
36
36
|
function reportNativeParserFailure(methodName, error) {
|
|
37
37
|
if (__DEV__) {
|
|
38
|
-
// eslint-disable-next-line no-console
|
|
39
38
|
console.error(`[NitroMarkdown] ${methodName}: native parser failed.`, error);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -44,7 +43,6 @@ try {
|
|
|
44
43
|
exports.MarkdownParserModule = MarkdownParserModule = _reactNativeNitroModules.NitroModules.createHybridObject("MarkdownParser");
|
|
45
44
|
} catch (e) {
|
|
46
45
|
if (__DEV__) {
|
|
47
|
-
// eslint-disable-next-line no-console
|
|
48
46
|
console.error("[NitroMarkdown] Failed to create native MarkdownParser:", e);
|
|
49
47
|
}
|
|
50
48
|
}
|
|
@@ -75,7 +73,6 @@ function parseMarkdown(text, options) {
|
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
if (__DEV__) {
|
|
78
|
-
// eslint-disable-next-line no-console
|
|
79
76
|
console.error("[NitroMarkdown] parseMarkdown: native parser unavailable — check installation.");
|
|
80
77
|
}
|
|
81
78
|
return createEmptyDocument();
|
|
@@ -98,7 +95,6 @@ function parseMarkdownWithOptions(text, options) {
|
|
|
98
95
|
}
|
|
99
96
|
}
|
|
100
97
|
if (__DEV__) {
|
|
101
|
-
// eslint-disable-next-line no-console
|
|
102
98
|
console.error("[NitroMarkdown] parseMarkdownWithOptions: native parser unavailable — check installation.");
|
|
103
99
|
}
|
|
104
100
|
return createEmptyDocument();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNativeNitroModules","require","createEmptyDocument","type","children","reportNativeParserFailure","methodName","error","__DEV__","console","MarkdownParserModule","exports","NitroModules","createHybridObject","e","parseMarkdown","text","options","parseMarkdownWithOptions","parse","jsonStr","JSON","parseWithOptions","extractPlainText","getFlattenedText","extractPlainTextWithOptions","getTextContent","node","content","map","join","blockContent","trim","alt","title","childrenText","stripSourceOffsets","beg","_beg","end","_end","rest"],"sourceRoot":"../../src","sources":["headless.ts"],"mappings":";;;;;;;;;;;;AAYA,IAAAA,wBAAA,GAAAC,OAAA;AAZA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;
|
|
1
|
+
{"version":3,"names":["_reactNativeNitroModules","require","createEmptyDocument","type","children","reportNativeParserFailure","methodName","error","__DEV__","console","MarkdownParserModule","exports","NitroModules","createHybridObject","e","parseMarkdown","text","options","parseMarkdownWithOptions","parse","jsonStr","JSON","parseWithOptions","extractPlainText","getFlattenedText","extractPlainTextWithOptions","getTextContent","node","content","map","join","blockContent","trim","alt","title","childrenText","stripSourceOffsets","beg","_beg","end","_end","rest"],"sourceRoot":"../../src","sources":["headless.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_headless","require","_markdown","_markdownStream","_MarkdownContext","_theme","_heading","_paragraph","_link","_blockquote","_horizontalRule","_code","_list","_table","_image","_math","_MarkdownSession","_useMarkdownStream","_codeHighlight"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,SAAA,GAAAC,OAAA;
|
|
1
|
+
{"version":3,"names":["_headless","require","_markdown","_markdownStream","_MarkdownContext","_theme","_heading","_paragraph","_link","_blockquote","_horizontalRule","_code","_list","_table","_image","_math","_MarkdownSession","_useMarkdownStream","_codeHighlight"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,SAAA,GAAAC,OAAA;AAmBA,IAAAC,SAAA,GAAAD,OAAA;AASA,IAAAE,eAAA,GAAAF,OAAA;AAGA,IAAAG,gBAAA,GAAAH,OAAA;AAsBA,IAAAI,MAAA,GAAAJ,OAAA;AAYA,IAAAK,QAAA,GAAAL,OAAA;AACA,IAAAM,UAAA,GAAAN,OAAA;AACA,IAAAO,KAAA,GAAAP,OAAA;AACA,IAAAQ,WAAA,GAAAR,OAAA;AACA,IAAAS,eAAA,GAAAT,OAAA;AACA,IAAAU,KAAA,GAAAV,OAAA;AACA,IAAAW,KAAA,GAAAX,OAAA;AACA,IAAAY,MAAA,GAAAZ,OAAA;AACA,IAAAa,MAAA,GAAAb,OAAA;AACA,IAAAc,KAAA,GAAAd,OAAA;AAEA,IAAAe,gBAAA,GAAAf,OAAA;AAEA,IAAAgB,kBAAA,GAAAhB,OAAA;AAOA,IAAAiB,cAAA,GAAAjB,OAAA","ignoreList":[]}
|
|
@@ -43,6 +43,13 @@ const resolveStreamText = ({
|
|
|
43
43
|
}
|
|
44
44
|
return session.getAllText();
|
|
45
45
|
};
|
|
46
|
+
function warnStreamError(message, error) {
|
|
47
|
+
if (!__DEV__) return;
|
|
48
|
+
const warn = Reflect.get(console, "warn");
|
|
49
|
+
if (typeof warn === "function") {
|
|
50
|
+
warn.call(console, message, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
46
53
|
/**
|
|
47
54
|
* A component that renders streaming Markdown from a MarkdownSession.
|
|
48
55
|
* It efficiently subscribes to session updates to minimize parent re-renders.
|
|
@@ -75,10 +82,17 @@ const MarkdownStream = ({
|
|
|
75
82
|
const forceFullSyncRef = (0, _react.useRef)(false);
|
|
76
83
|
const updateTimerRef = (0, _react.useRef)(null);
|
|
77
84
|
const rafRef = (0, _react.useRef)(null);
|
|
85
|
+
const mountedRef = (0, _react.useRef)(true);
|
|
78
86
|
const allowIncremental = incrementalParsing && !hasBeforeParsePlugins;
|
|
79
87
|
(0, _react.useEffect)(() => {
|
|
80
88
|
renderStateRef.current = renderState;
|
|
81
89
|
}, [renderState]);
|
|
90
|
+
(0, _react.useEffect)(() => {
|
|
91
|
+
mountedRef.current = true;
|
|
92
|
+
return () => {
|
|
93
|
+
mountedRef.current = false;
|
|
94
|
+
};
|
|
95
|
+
}, []);
|
|
82
96
|
(0, _react.useEffect)(() => {
|
|
83
97
|
const initialText = session.getAllText();
|
|
84
98
|
const initialState = {
|
|
@@ -128,8 +142,10 @@ const MarkdownStream = ({
|
|
|
128
142
|
ast: nextAst
|
|
129
143
|
};
|
|
130
144
|
renderStateRef.current = nextState;
|
|
145
|
+
if (!mountedRef.current) return;
|
|
131
146
|
if (useTransitionUpdates) {
|
|
132
147
|
(0, _react.startTransition)(() => {
|
|
148
|
+
if (!mountedRef.current) return;
|
|
133
149
|
setRenderState(nextState);
|
|
134
150
|
});
|
|
135
151
|
} else {
|
|
@@ -147,22 +163,31 @@ const MarkdownStream = ({
|
|
|
147
163
|
updateTimerRef.current = setTimeout(flushUpdate, updateIntervalMs);
|
|
148
164
|
}
|
|
149
165
|
};
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
let unsubscribe = null;
|
|
167
|
+
try {
|
|
168
|
+
unsubscribe = session.addListener((from, to) => {
|
|
169
|
+
const nextFrom = normalizeOffset(from);
|
|
170
|
+
const nextTo = normalizeOffset(to);
|
|
171
|
+
if (nextFrom === null || nextTo === null || nextTo < nextFrom) {
|
|
172
|
+
forceFullSyncRef.current = true;
|
|
173
|
+
} else {
|
|
174
|
+
const currentFrom = pendingFromRef.current;
|
|
175
|
+
const currentTo = pendingToRef.current;
|
|
176
|
+
pendingFromRef.current = currentFrom === null ? nextFrom : Math.min(currentFrom, nextFrom);
|
|
177
|
+
pendingToRef.current = currentTo === null ? nextTo : Math.max(currentTo, nextTo);
|
|
178
|
+
}
|
|
179
|
+
pendingUpdateRef.current = true;
|
|
180
|
+
scheduleFlush();
|
|
181
|
+
});
|
|
182
|
+
} catch (error) {
|
|
183
|
+
warnStreamError("[NitroMarkdown] Failed to subscribe to stream:", error);
|
|
184
|
+
}
|
|
164
185
|
return () => {
|
|
165
|
-
|
|
186
|
+
try {
|
|
187
|
+
unsubscribe?.();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
warnStreamError("[NitroMarkdown] Failed to unsubscribe from stream:", error);
|
|
190
|
+
}
|
|
166
191
|
if (updateTimerRef.current) {
|
|
167
192
|
clearTimeout(updateTimerRef.current);
|
|
168
193
|
updateTimerRef.current = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","require","_markdown","_incrementalAst","_jsxRuntime","normalizeOffset","value","Number","isFinite","Math","floor","resolveStreamText","forceFullSync","pendingFrom","pendingTo","previousText","session","getAllText","length","appendedChunk","getTextRange","MarkdownStream","updateIntervalMs","updateStrategy","useTransitionUpdates","incrementalParsing","options","plugins","props","parseText","useCallback","text","parseMarkdownAst","createEmptyAst","type","children","initialText","hasBeforeParsePlugins","some","plugin","beforeParse","renderState","setRenderState","useState","ast","renderStateRef","useRef","pendingUpdateRef","pendingFromRef","pendingToRef","forceFullSyncRef","updateTimerRef","rafRef","allowIncremental","useEffect","current","initialState","flushUpdate","cancelAnimationFrame","previousState","latest","nextAst","getNextStreamAst","nextText","previousAst","nextState","startTransition","scheduleFlush","requestAnimationFrame","setTimeout","unsubscribe","addListener","from","to","nextFrom","nextTo","currentFrom","currentTo","min","max","clearTimeout","jsx","Markdown","sourceAst","undefined","exports"],"sourceRoot":"../../src","sources":["markdown-stream.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,OAAA;AASA,IAAAC,SAAA,GAAAD,OAAA;AAEA,IAAAE,eAAA,GAAAF,OAAA;AAA6E,IAAAG,WAAA,GAAAH,OAAA;AAE7E,MAAMI,eAAe,GAAIC,KAAa,IAAoB;EACxD,IAAI,CAACC,MAAM,CAACC,QAAQ,CAACF,KAAK,CAAC,EAAE,OAAO,IAAI;EACxC,IAAIA,KAAK,IAAI,CAAC,EAAE,OAAO,CAAC;EACxB,OAAOG,IAAI,CAACC,KAAK,CAACJ,KAAK,CAAC;AAC1B,CAAC;AAED,MAAMK,iBAAiB,GAAGA,CAAC;EACzBC,aAAa;EACbC,WAAW;EACXC,SAAS;EACTC,YAAY;EACZC;AAOF,CAAC,KAAa;EACZ,IAAIJ,aAAa,IAAIC,WAAW,KAAK,IAAI,IAAIC,SAAS,KAAK,IAAI,EAAE;IAC/D,OAAOE,OAAO,CAACC,UAAU,CAAC,CAAC;EAC7B;EAEA,IAAIH,SAAS,GAAGD,WAAW,EAAE;IAC3B,OAAOG,OAAO,CAACC,UAAU,CAAC,CAAC;EAC7B;EAEA,IAAIJ,WAAW,KAAKE,YAAY,CAACG,MAAM,EAAE;IACvC,IAAI;MACF,MAAMC,aAAa,GAAGH,OAAO,CAACI,YAAY,CAACP,WAAW,EAAEC,SAAS,CAAC;MAClE,OAAO,GAAGC,YAAY,GAAGI,aAAa,EAAE;IAC1C,CAAC,CAAC,MAAM;MACN,OAAOH,OAAO,CAACC,UAAU,CAAC,CAAC;IAC7B;EACF;EAEA,IAAIJ,WAAW,KAAK,CAAC,EAAE;IACrB,IAAI;MACF,OAAOG,OAAO,CAACI,YAAY,CAAC,CAAC,EAAEN,SAAS,CAAC;IAC3C,CAAC,CAAC,MAAM;MACN,OAAOE,OAAO,CAACC,UAAU,CAAC,CAAC;IAC7B;EACF;EAEA,OAAOD,OAAO,CAACC,UAAU,CAAC,CAAC;AAC7B,CAAC;AA+
|
|
1
|
+
{"version":3,"names":["_react","require","_markdown","_incrementalAst","_jsxRuntime","normalizeOffset","value","Number","isFinite","Math","floor","resolveStreamText","forceFullSync","pendingFrom","pendingTo","previousText","session","getAllText","length","appendedChunk","getTextRange","warnStreamError","message","error","__DEV__","warn","Reflect","get","console","call","MarkdownStream","updateIntervalMs","updateStrategy","useTransitionUpdates","incrementalParsing","options","plugins","props","parseText","useCallback","text","parseMarkdownAst","createEmptyAst","type","children","initialText","hasBeforeParsePlugins","some","plugin","beforeParse","renderState","setRenderState","useState","ast","renderStateRef","useRef","pendingUpdateRef","pendingFromRef","pendingToRef","forceFullSyncRef","updateTimerRef","rafRef","mountedRef","allowIncremental","useEffect","current","initialState","flushUpdate","cancelAnimationFrame","previousState","latest","nextAst","getNextStreamAst","nextText","previousAst","nextState","startTransition","scheduleFlush","requestAnimationFrame","setTimeout","unsubscribe","addListener","from","to","nextFrom","nextTo","currentFrom","currentTo","min","max","clearTimeout","jsx","Markdown","sourceAst","undefined","exports"],"sourceRoot":"../../src","sources":["markdown-stream.tsx"],"mappings":"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","ignoreList":[]}
|
package/lib/commonjs/markdown.js
CHANGED
|
@@ -44,7 +44,6 @@ function safeOnError(onError, error, phase, pluginName) {
|
|
|
44
44
|
onError?.(error instanceof Error ? error : new Error(String(error)), phase, pluginName);
|
|
45
45
|
} catch (callbackError) {
|
|
46
46
|
if (__DEV__) {
|
|
47
|
-
// eslint-disable-next-line no-console
|
|
48
47
|
console.warn("[NitroMarkdown] onError callback threw an exception:", callbackError);
|
|
49
48
|
}
|
|
50
49
|
}
|
|
@@ -188,6 +187,7 @@ const Markdown = ({
|
|
|
188
187
|
virtualizationMinBlocks = 40,
|
|
189
188
|
virtualization,
|
|
190
189
|
tableOptions,
|
|
190
|
+
imageOptions,
|
|
191
191
|
highlightCode
|
|
192
192
|
}) => {
|
|
193
193
|
const parserOptionGfm = options?.gfm;
|
|
@@ -255,8 +255,9 @@ const Markdown = ({
|
|
|
255
255
|
stylingStrategy,
|
|
256
256
|
onLinkPress,
|
|
257
257
|
tableOptions,
|
|
258
|
+
imageOptions,
|
|
258
259
|
highlightCode
|
|
259
|
-
}), [renderers, theme, nodeStyles, stylingStrategy, onLinkPress, tableOptions, highlightCode]);
|
|
260
|
+
}), [renderers, theme, nodeStyles, stylingStrategy, onLinkPress, tableOptions, imageOptions, highlightCode]);
|
|
260
261
|
const topLevelBlocks = parseResult.ast?.type === "document" ? parseResult.ast.children ?? [] : parseResult.ast ? [parseResult.ast] : [];
|
|
261
262
|
const shouldVirtualizeBySetting = virtualize === true || virtualize === "auto" && topLevelBlocks.length >= virtualizationMinBlocks;
|
|
262
263
|
const shouldVirtualize = parseResult.ast !== null && shouldVirtualizeBySetting;
|
|
@@ -294,7 +295,7 @@ const Markdown = ({
|
|
|
294
295
|
maxToRenderPerBatch: virtualization?.maxToRenderPerBatch ?? 12,
|
|
295
296
|
windowSize: virtualization?.windowSize ?? 10,
|
|
296
297
|
updateCellsBatchingPeriod: virtualization?.updateCellsBatchingPeriod ?? 16,
|
|
297
|
-
removeClippedSubviews: virtualization?.removeClippedSubviews ??
|
|
298
|
+
removeClippedSubviews: virtualization?.removeClippedSubviews ?? _reactNative.Platform.OS === "android",
|
|
298
299
|
bounces: false,
|
|
299
300
|
alwaysBounceVertical: false,
|
|
300
301
|
overScrollMode: "never",
|