@tinglinzh/react-native-markdownview 0.1.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 (38) hide show
  1. package/Package.swift +33 -0
  2. package/ios/RNMarkdownView.swift +261 -0
  3. package/ios/RNMarkdownViewComponentView.h +16 -0
  4. package/ios/RNMarkdownViewComponentView.mm +128 -0
  5. package/ios/RNMarkdownViewManager.m +34 -0
  6. package/ios/RNMarkdownViewManager.swift +16 -0
  7. package/ios/react-native-markdownview-Bridging-Header.h +12 -0
  8. package/lib/commonjs/MarkdownView.js +139 -0
  9. package/lib/commonjs/MarkdownView.js.map +1 -0
  10. package/lib/commonjs/NativeMarkdownView.js +22 -0
  11. package/lib/commonjs/NativeMarkdownView.js.map +1 -0
  12. package/lib/commonjs/index.js +13 -0
  13. package/lib/commonjs/index.js.map +1 -0
  14. package/lib/commonjs/package.json +1 -0
  15. package/lib/commonjs/types.js +6 -0
  16. package/lib/commonjs/types.js.map +1 -0
  17. package/lib/module/MarkdownView.js +134 -0
  18. package/lib/module/MarkdownView.js.map +1 -0
  19. package/lib/module/NativeMarkdownView.js +21 -0
  20. package/lib/module/NativeMarkdownView.js.map +1 -0
  21. package/lib/module/index.js +13 -0
  22. package/lib/module/index.js.map +1 -0
  23. package/lib/module/types.js +4 -0
  24. package/lib/module/types.js.map +1 -0
  25. package/lib/typescript/src/MarkdownView.d.ts +25 -0
  26. package/lib/typescript/src/MarkdownView.d.ts.map +1 -0
  27. package/lib/typescript/src/NativeMarkdownView.d.ts +53 -0
  28. package/lib/typescript/src/NativeMarkdownView.d.ts.map +1 -0
  29. package/lib/typescript/src/index.d.ts +12 -0
  30. package/lib/typescript/src/index.d.ts.map +1 -0
  31. package/lib/typescript/src/types.d.ts +126 -0
  32. package/lib/typescript/src/types.d.ts.map +1 -0
  33. package/package.json +67 -0
  34. package/react-native-markdownview.podspec +38 -0
  35. package/src/MarkdownView.tsx +164 -0
  36. package/src/NativeMarkdownView.ts +69 -0
  37. package/src/index.tsx +25 -0
  38. package/src/types.ts +143 -0
package/Package.swift ADDED
@@ -0,0 +1,33 @@
1
+ // swift-tools-version: 5.9
2
+ // When consumed via Swift Package Manager, MarkdownView is fetched automatically.
3
+ // When consumed via CocoaPods, add MarkdownView to your Xcode project manually via
4
+ // File → Add Package Dependencies → https://github.com/HumanInterfaceDesign/MarkdownView
5
+
6
+ import PackageDescription
7
+
8
+ let package = Package(
9
+ name: "ReactNativeMarkdownView",
10
+ platforms: [.iOS(.v16)],
11
+ products: [
12
+ .library(
13
+ name: "ReactNativeMarkdownView",
14
+ targets: ["ReactNativeMarkdownView"]
15
+ )
16
+ ],
17
+ dependencies: [
18
+ .package(
19
+ url: "https://github.com/HumanInterfaceDesign/MarkdownView",
20
+ branch: "main"
21
+ )
22
+ ],
23
+ targets: [
24
+ .target(
25
+ name: "ReactNativeMarkdownView",
26
+ dependencies: [
27
+ .product(name: "MarkdownView", package: "MarkdownView")
28
+ ],
29
+ path: "ios",
30
+ sources: ["RNMarkdownView.swift", "RNMarkdownViewManager.swift"]
31
+ )
32
+ ]
33
+ )
@@ -0,0 +1,261 @@
1
+ import UIKit
2
+ import MarkdownView
3
+
4
+ /// UIView wrapper around MarkdownTextView for use in React Native.
5
+ /// Handles prop → native API mapping and event forwarding for both
6
+ /// the Old Architecture (via RNMarkdownViewManager) and the New
7
+ /// Architecture (Fabric, via RNMarkdownViewComponentView).
8
+ @objc public final class RNMarkdownView: UIView {
9
+
10
+ // MARK: - Internal view
11
+
12
+ private let markdownTextView = MarkdownTextView()
13
+ private var lastContentSize: CGSize = .zero
14
+
15
+ // MARK: - React props (Old Architecture uses KVC to set these)
16
+
17
+ /// The raw markdown string to render.
18
+ @objc public var markdown: String = "" {
19
+ didSet {
20
+ guard markdown != oldValue else { return }
21
+ markdownTextView.setMarkdown(string: markdown)
22
+ scheduleContentSizeNotification()
23
+ }
24
+ }
25
+
26
+ /// JSON-encoded MarkdownTheme overrides. See ThemeOptions in types.ts for the shape.
27
+ @objc public var themeJSON: String? {
28
+ didSet {
29
+ guard themeJSON != oldValue else { return }
30
+ applyThemeJSON(themeJSON)
31
+ if !markdown.isEmpty {
32
+ markdownTextView.setMarkdown(string: markdown)
33
+ scheduleContentSizeNotification()
34
+ }
35
+ }
36
+ }
37
+
38
+ // MARK: - Old Architecture event blocks
39
+
40
+ @objc public var onLinkPress: (([AnyHashable: Any]?) -> Void)?
41
+ @objc public var onImagePress: (([AnyHashable: Any]?) -> Void)?
42
+ @objc public var onLineSelection: (([AnyHashable: Any]?) -> Void)?
43
+ /// Fires when the natural content height changes. Payload: { contentSize: { width, height } }
44
+ @objc public var onContentSizeChange: (([AnyHashable: Any]?) -> Void)?
45
+
46
+ // MARK: - New Architecture (Fabric) callbacks – set by RNMarkdownViewComponentView.mm
47
+
48
+ var onLinkPressFabric: ((_ url: String, _ isURL: Bool) -> Void)?
49
+ var onImagePressFabric: ((_ source: String) -> Void)?
50
+ var onLineSelectionFabric: ((_ start: Int, _ end: Int, _ contents: [String], _ language: String?) -> Void)?
51
+ var onContentSizeChangeFabric: ((_ width: Double, _ height: Double) -> Void)?
52
+
53
+ // MARK: - Initializers
54
+
55
+ public override init(frame: CGRect) {
56
+ super.init(frame: frame)
57
+ setupView()
58
+ }
59
+
60
+ @available(*, unavailable)
61
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
62
+
63
+ // MARK: - Setup
64
+
65
+ private func setupView() {
66
+ addSubview(markdownTextView)
67
+ markdownTextView.translatesAutoresizingMaskIntoConstraints = false
68
+ NSLayoutConstraint.activate([
69
+ markdownTextView.topAnchor.constraint(equalTo: topAnchor),
70
+ markdownTextView.leadingAnchor.constraint(equalTo: leadingAnchor),
71
+ markdownTextView.trailingAnchor.constraint(equalTo: trailingAnchor),
72
+ markdownTextView.bottomAnchor.constraint(equalTo: bottomAnchor),
73
+ ])
74
+ setupHandlers()
75
+ }
76
+
77
+ private func setupHandlers() {
78
+ markdownTextView.linkHandler = { [weak self] payload, _, _ in
79
+ guard let self else { return }
80
+ let (url, isURL): (String, Bool) = {
81
+ switch payload {
82
+ case .url(let u): return (u.absoluteString, true)
83
+ case .string(let s): return (s, false)
84
+ }
85
+ }()
86
+ self.onLinkPress?(["url": url, "isURL": isURL])
87
+ self.onLinkPressFabric?(url, isURL)
88
+ }
89
+
90
+ markdownTextView.imageTapHandler = { [weak self] source, _ in
91
+ guard let self else { return }
92
+ self.onImagePress?(["source": source])
93
+ self.onImagePressFabric?(source)
94
+ }
95
+
96
+ markdownTextView.lineSelectionHandler = { [weak self] info in
97
+ guard let self else { return }
98
+ guard let info else { return }
99
+ let body: [AnyHashable: Any] = [
100
+ "startLine": info.lineRange.lowerBound,
101
+ "endLine": info.lineRange.upperBound,
102
+ "contents": info.contents,
103
+ "language": info.language as Any,
104
+ ]
105
+ self.onLineSelection?(body)
106
+ self.onLineSelectionFabric?(
107
+ info.lineRange.lowerBound,
108
+ info.lineRange.upperBound,
109
+ info.contents,
110
+ info.language
111
+ )
112
+ }
113
+ }
114
+
115
+ // MARK: - Layout
116
+
117
+ public override func layoutSubviews() {
118
+ super.layoutSubviews()
119
+ scheduleContentSizeNotification()
120
+ }
121
+
122
+ private func scheduleContentSizeNotification() {
123
+ DispatchQueue.main.async { [weak self] in
124
+ self?.emitContentSizeIfChanged()
125
+ }
126
+ }
127
+
128
+ private func emitContentSizeIfChanged() {
129
+ let size = markdownTextView.intrinsicContentSize
130
+ guard size.height > 0, size != lastContentSize else { return }
131
+ lastContentSize = size
132
+ let body: [AnyHashable: Any] = [
133
+ "contentSize": ["width": Double(size.width), "height": Double(size.height)]
134
+ ]
135
+ onContentSizeChange?(body)
136
+ onContentSizeChangeFabric?(Double(size.width), Double(size.height))
137
+ }
138
+
139
+ // MARK: - Fabric entry-points (called from RNMarkdownViewComponentView.mm)
140
+
141
+ @objc public func applyMarkdownString(_ value: String) {
142
+ self.markdown = value
143
+ }
144
+
145
+ @objc public func applyThemeJSONString(_ value: String?) {
146
+ self.themeJSON = value
147
+ }
148
+
149
+ // MARK: - Theme
150
+
151
+ private func applyThemeJSON(_ json: String?) {
152
+ guard
153
+ let json,
154
+ let data = json.data(using: .utf8),
155
+ let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
156
+ else { return }
157
+
158
+ var theme = MarkdownTheme()
159
+
160
+ applyColors(from: dict["colors"] as? [String: String], to: &theme)
161
+ applyFonts(from: dict["fonts"] as? [String: Any], to: &theme)
162
+ applySpacings(from: dict["spacings"] as? [String: CGFloat], to: &theme)
163
+ applyTable(from: dict["table"] as? [String: Any], to: &theme)
164
+
165
+ if let raw = dict["fontScale"] as? String,
166
+ let scale = MarkdownTheme.FontScale(rawValue: raw) {
167
+ theme.scaleFont(by: scale)
168
+ }
169
+ if let shows = dict["showsBlockHeaders"] as? Bool {
170
+ theme.showsBlockHeaders = shows
171
+ }
172
+
173
+ markdownTextView.theme = theme
174
+ }
175
+
176
+ private func applyColors(from dict: [String: String]?, to theme: inout MarkdownTheme) {
177
+ guard let dict else { return }
178
+ func set(_ key: String, _ apply: (UIColor) -> Void) {
179
+ if let hex = dict[key], let color = UIColor(hexString: hex) { apply(color) }
180
+ }
181
+ set("body") { theme.colors.body = $0 }
182
+ set("code") { theme.colors.code = $0 }
183
+ set("codeBackground") { theme.colors.codeBackground = $0 }
184
+ set("highlight") { theme.colors.highlight = $0 }
185
+ set("emphasis") { theme.colors.emphasis = $0 }
186
+ set("selectionTint") { theme.colors.selectionTint = $0 }
187
+ }
188
+
189
+ private func applyFonts(from dict: [String: Any]?, to theme: inout MarkdownTheme) {
190
+ guard let dict else { return }
191
+ let bodySize = dict["bodySize"] as? CGFloat ?? theme.fonts.body.pointSize
192
+ let codeSize = dict["codeSize"] as? CGFloat ?? theme.fonts.code.pointSize
193
+
194
+ let bodyFont: UIFont
195
+ if let name = dict["bodyName"] as? String, !name.isEmpty,
196
+ let f = UIFont(name: name, size: bodySize) {
197
+ bodyFont = f
198
+ } else {
199
+ bodyFont = .systemFont(ofSize: bodySize)
200
+ }
201
+ theme.fonts.body = bodyFont
202
+ theme.fonts.bold = UIFont(descriptor: bodyFont.fontDescriptor.withSymbolicTraits(.traitBold) ?? bodyFont.fontDescriptor, size: bodySize)
203
+ theme.fonts.italic = UIFont(descriptor: bodyFont.fontDescriptor.withSymbolicTraits(.traitItalic) ?? bodyFont.fontDescriptor, size: bodySize)
204
+
205
+ let codeFont: UIFont
206
+ if let name = dict["codeName"] as? String, !name.isEmpty,
207
+ let f = UIFont(name: name, size: codeSize) {
208
+ codeFont = f
209
+ } else {
210
+ codeFont = .monospacedSystemFont(ofSize: codeSize, weight: .regular)
211
+ }
212
+ theme.fonts.code = codeFont
213
+ theme.fonts.codeInline = codeFont
214
+ }
215
+
216
+ private func applySpacings(from dict: [String: CGFloat]?, to theme: inout MarkdownTheme) {
217
+ guard let dict else { return }
218
+ if let v = dict["general"] { theme.spacings.general = v }
219
+ if let v = dict["list"] { theme.spacings.list = v }
220
+ if let v = dict["final"] { theme.spacings.final = v }
221
+ if let v = dict["cell"] { theme.spacings.cell = v }
222
+ }
223
+
224
+ private func applyTable(from dict: [String: Any]?, to theme: inout MarkdownTheme) {
225
+ guard let dict else { return }
226
+ if let r = dict["cornerRadius"] as? CGFloat { theme.table.cornerRadius = r }
227
+ if let w = dict["borderWidth"] as? CGFloat { theme.table.borderWidth = w }
228
+ if let hex = dict["borderColor"] as? String,
229
+ let c = UIColor(hexString: hex) { theme.table.borderColor = c }
230
+ if let hex = dict["headerBackgroundColor"] as? String,
231
+ let c = UIColor(hexString: hex) { theme.table.headerBackgroundColor = c }
232
+ }
233
+ }
234
+
235
+ // MARK: - UIColor hex initializer
236
+
237
+ private extension UIColor {
238
+ /// Accepts "#RRGGBB", "#RRGGBBAA", "RRGGBB", "RRGGBBAA".
239
+ convenience init?(hexString: String) {
240
+ var hex = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
241
+ if hex.hasPrefix("#") { hex = String(hex.dropFirst()) }
242
+ guard hex.count == 6 || hex.count == 8 else { return nil }
243
+ var value: UInt64 = 0
244
+ guard Scanner(string: hex).scanHexInt64(&value) else { return nil }
245
+ if hex.count == 6 {
246
+ self.init(
247
+ red: CGFloat((value & 0xFF0000) >> 16) / 255,
248
+ green: CGFloat((value & 0x00FF00) >> 8) / 255,
249
+ blue: CGFloat( value & 0x0000FF) / 255,
250
+ alpha: 1
251
+ )
252
+ } else {
253
+ self.init(
254
+ red: CGFloat((value & 0xFF000000) >> 24) / 255,
255
+ green: CGFloat((value & 0x00FF0000) >> 16) / 255,
256
+ blue: CGFloat((value & 0x0000FF00) >> 8) / 255,
257
+ alpha: CGFloat( value & 0x000000FF) / 255
258
+ )
259
+ }
260
+ }
261
+ }
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+ #import <React/RCTViewComponentView.h>
5
+ #import <UIKit/UIKit.h>
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ /// Fabric component view for the New Architecture.
10
+ /// Wraps RNMarkdownView and bridges Fabric props/events.
11
+ @interface RNMarkdownViewComponentView : RCTViewComponentView
12
+ @end
13
+
14
+ NS_ASSUME_NONNULL_END
15
+
16
+ #endif // RCT_NEW_ARCH_ENABLED
@@ -0,0 +1,128 @@
1
+ /**
2
+ * New Architecture (Fabric) component view for RNMarkdownView.
3
+ *
4
+ * Codegen generates C++ headers from the NativeMarkdownView.ts spec.
5
+ * This file bridges those generated types to the Swift RNMarkdownView class.
6
+ */
7
+ #ifdef RCT_NEW_ARCH_ENABLED
8
+
9
+ #import "RNMarkdownViewComponentView.h"
10
+
11
+ // Generated by codegen from NativeMarkdownView.ts
12
+ #import <react/renderer/components/RNMarkdownViewSpec/ComponentDescriptors.h>
13
+ #import <react/renderer/components/RNMarkdownViewSpec/EventEmitters.h>
14
+ #import <react/renderer/components/RNMarkdownViewSpec/Props.h>
15
+ #import <react/renderer/components/RNMarkdownViewSpec/RCTComponentViewHelpers.h>
16
+
17
+ #import "RCTFabricComponentsPlugins.h"
18
+
19
+ // Swift-generated header (module name derived from pod name with hyphens → underscores)
20
+ #import "react_native_markdownview-Swift.h"
21
+
22
+ using namespace facebook::react;
23
+
24
+ @interface RNMarkdownViewComponentView () <RCTRNMarkdownViewViewProtocol>
25
+ @end
26
+
27
+ @implementation RNMarkdownViewComponentView {
28
+ RNMarkdownView *_markdownView;
29
+ }
30
+
31
+ // ─── Lifecycle ───────────────────────────────────────────────────────────────
32
+
33
+ - (instancetype)initWithFrame:(CGRect)frame
34
+ {
35
+ if (self = [super initWithFrame:frame]) {
36
+ static const auto defaultProps = std::make_shared<const RNMarkdownViewProps>();
37
+ _props = defaultProps;
38
+
39
+ _markdownView = [[RNMarkdownView alloc] initWithFrame:frame];
40
+ self.contentView = _markdownView;
41
+ }
42
+ return self;
43
+ }
44
+
45
+ // ─── Fabric boilerplate ──────────────────────────────────────────────────────
46
+
47
+ + (ComponentDescriptorProvider)componentDescriptorProvider
48
+ {
49
+ return concreteComponentDescriptorProvider<RNMarkdownViewComponentDescriptor>();
50
+ }
51
+
52
+ // ─── Props ───────────────────────────────────────────────────────────────────
53
+
54
+ - (void)updateProps:(Props::Shared const &)props
55
+ oldProps:(Props::Shared const &)oldProps
56
+ {
57
+ const auto &newTyped = *std::static_pointer_cast<RNMarkdownViewProps const>(props);
58
+ const auto &oldTyped = *std::static_pointer_cast<RNMarkdownViewProps const>(oldProps);
59
+
60
+ if (newTyped.markdown != oldTyped.markdown) {
61
+ [_markdownView applyMarkdownString:@(newTyped.markdown.c_str())];
62
+ }
63
+
64
+ if (newTyped.themeJSON != oldTyped.themeJSON) {
65
+ NSString *json = newTyped.themeJSON.empty() ? nil : @(newTyped.themeJSON.c_str());
66
+ [_markdownView applyThemeJSONString:json];
67
+ }
68
+
69
+ [super updateProps:props oldProps:oldProps];
70
+ }
71
+
72
+ // ─── Events ──────────────────────────────────────────────────────────────────
73
+
74
+ - (void)updateEventEmitter:(EventEmitter::Shared const &)eventEmitter
75
+ {
76
+ [super updateEventEmitter:eventEmitter];
77
+ auto emitter = std::static_pointer_cast<RNMarkdownViewEventEmitter const>(eventEmitter);
78
+
79
+ __weak typeof(self) weakSelf = self;
80
+
81
+ _markdownView.onLinkPressFabric = ^(NSString *url, BOOL isURL) {
82
+ if (!emitter) return;
83
+ RNMarkdownViewEventEmitter::OnLinkPress event;
84
+ event.url = std::string(url.UTF8String ?: "");
85
+ event.isURL = isURL;
86
+ emitter->onLinkPress(event);
87
+ (void)weakSelf;
88
+ };
89
+
90
+ _markdownView.onImagePressFabric = ^(NSString *source) {
91
+ if (!emitter) return;
92
+ RNMarkdownViewEventEmitter::OnImagePress event;
93
+ event.source = std::string(source.UTF8String ?: "");
94
+ emitter->onImagePress(event);
95
+ };
96
+
97
+ _markdownView.onLineSelectionFabric = ^(NSInteger start, NSInteger end, NSArray<NSString *> *contents, NSString * _Nullable language) {
98
+ if (!emitter) return;
99
+ RNMarkdownViewEventEmitter::OnLineSelection event;
100
+ event.startLine = static_cast<int>(start);
101
+ event.endLine = static_cast<int>(end);
102
+ for (NSString *line in contents) {
103
+ event.contents.push_back(std::string(line.UTF8String ?: ""));
104
+ }
105
+ event.language = language ? std::string(language.UTF8String) : "";
106
+ emitter->onLineSelection(event);
107
+ };
108
+
109
+ _markdownView.onContentSizeChangeFabric = ^(double width, double height) {
110
+ if (!emitter) return;
111
+ RNMarkdownViewEventEmitter::OnContentSizeChange event;
112
+ RNMarkdownViewEventEmitter::OnContentSizeChangeContentSize size;
113
+ size.width = width;
114
+ size.height = height;
115
+ event.contentSize = size;
116
+ emitter->onContentSizeChange(event);
117
+ };
118
+ }
119
+
120
+ @end
121
+
122
+ // Required by Fabric's component registry
123
+ Class<RCTComponentViewProtocol> RNMarkdownViewCls(void)
124
+ {
125
+ return RNMarkdownViewComponentView.class;
126
+ }
127
+
128
+ #endif // RCT_NEW_ARCH_ENABLED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Old Architecture bridge.
3
+ *
4
+ * React Native uses KVC to set these properties on the UIView returned by
5
+ * RNMarkdownViewManager.view(). The property names must match exactly what
6
+ * RNMarkdownView exposes via @objc.
7
+ */
8
+ #import <React/RCTViewManager.h>
9
+
10
+ @interface RCT_EXTERN_MODULE(RNMarkdownViewManager, RCTViewManager)
11
+
12
+ // ─── Content ────────────────────────────────────────────────────────────────
13
+ /// Raw markdown string.
14
+ RCT_EXPORT_VIEW_PROPERTY(markdown, NSString)
15
+
16
+ /// JSON-encoded theme object (see ThemeOptions in types.ts).
17
+ RCT_EXPORT_VIEW_PROPERTY(themeJSON, NSString)
18
+
19
+ // ─── Events ─────────────────────────────────────────────────────────────────
20
+ /// Fires when a link is tapped. Payload: { url: string, isURL: boolean }
21
+ RCT_EXPORT_VIEW_PROPERTY(onLinkPress, RCTDirectEventBlock)
22
+
23
+ /// Fires when an inline image is tapped. Payload: { source: string }
24
+ RCT_EXPORT_VIEW_PROPERTY(onImagePress, RCTDirectEventBlock)
25
+
26
+ /// Fires when lines are selected in a code block.
27
+ /// Payload: { startLine: number, endLine: number, contents: string[], language?: string }
28
+ RCT_EXPORT_VIEW_PROPERTY(onLineSelection, RCTDirectEventBlock)
29
+
30
+ /// Fires when the intrinsic content height changes.
31
+ /// Payload: { contentSize: { width: number, height: number } }
32
+ RCT_EXPORT_VIEW_PROPERTY(onContentSizeChange, RCTDirectEventBlock)
33
+
34
+ @end
@@ -0,0 +1,16 @@
1
+ import React
2
+ import UIKit
3
+
4
+ /// Old Architecture view manager.
5
+ /// The ObjC bridge in RNMarkdownViewManager.m exports props and this class.
6
+ @objc(RNMarkdownViewManager)
7
+ final class RNMarkdownViewManager: RCTViewManager {
8
+
9
+ override func view() -> UIView! {
10
+ RNMarkdownView()
11
+ }
12
+
13
+ override static func requiresMainQueueSetup() -> Bool {
14
+ true
15
+ }
16
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Bridging header for Swift ↔ Objective-C interop within this pod.
3
+ *
4
+ * The CocoaPods build system picks up SWIFT_OBJC_BRIDGING_HEADER via
5
+ * the pod_target_xcconfig in the podspec and makes these React Native
6
+ * Objective-C headers visible to all Swift source files in the pod.
7
+ */
8
+ #import <React/RCTViewManager.h>
9
+ #import <React/RCTBridgeModule.h>
10
+ #import <React/RCTEventEmitter.h>
11
+ #import <React/RCTView.h>
12
+ #import <React/RCTUIManager.h>
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.MarkdownView = MarkdownView;
7
+ var _react = _interopRequireWildcard(require("react"));
8
+ var _reactNative = require("react-native");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ // ─── Native component handle ──────────────────────────────────────────────────
12
+
13
+ const COMPONENT_NAME = 'RNMarkdownView';
14
+
15
+ /**
16
+ * Old Architecture – loaded lazily so non-iOS platforms don't throw on import.
17
+ * New Architecture uses the codegen-generated component automatically when
18
+ * RCT_NEW_ARCH_ENABLED is set at build time.
19
+ */
20
+ const NativeMarkdownView = _reactNative.Platform.OS === 'ios' ? (0, _reactNative.requireNativeComponent)(COMPONENT_NAME) : null;
21
+
22
+ // ─── Theme serialisation ──────────────────────────────────────────────────────
23
+
24
+ function serializeTheme(theme) {
25
+ if (!theme) return undefined;
26
+ try {
27
+ return JSON.stringify(theme);
28
+ } catch {
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ // ─── Component ────────────────────────────────────────────────────────────────
34
+
35
+ /**
36
+ * `MarkdownView` renders GitHub Flavored Markdown using Apple's native
37
+ * MarkdownView library (tree-sitter syntax highlighting, LaTeX math,
38
+ * unified diff, VoiceOver). iOS 16+ only.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * <MarkdownView markdown="# Hello\n\nSome **bold** text." />
43
+ * ```
44
+ *
45
+ * @example Custom theme
46
+ * ```tsx
47
+ * <MarkdownView
48
+ * markdown={content}
49
+ * theme={{
50
+ * colors: { body: '#1a1a1a', codeBackground: '#f5f5f5' },
51
+ * fonts: { bodySize: 16 },
52
+ * }}
53
+ * />
54
+ * ```
55
+ */
56
+ function MarkdownView({
57
+ markdown,
58
+ theme,
59
+ style,
60
+ onLinkPress,
61
+ onImagePress,
62
+ onLineSelection,
63
+ onContentSizeChange
64
+ }) {
65
+ if (_reactNative.Platform.OS !== 'ios' || NativeMarkdownView === null) {
66
+ if (__DEV__) {
67
+ console.warn('[MarkdownView] Only supported on iOS 16+.');
68
+ }
69
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
70
+ style: style
71
+ });
72
+ }
73
+
74
+ // eslint-disable-next-line react-hooks/rules-of-hooks
75
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(MarkdownViewInner, {
76
+ markdown: markdown,
77
+ theme: theme,
78
+ style: style,
79
+ onLinkPress: onLinkPress,
80
+ onImagePress: onImagePress,
81
+ onLineSelection: onLineSelection,
82
+ onContentSizeChange: onContentSizeChange,
83
+ NativeComponent: NativeMarkdownView
84
+ });
85
+ }
86
+
87
+ // Split out so hooks run unconditionally (Platform.OS is constant at runtime).
88
+
89
+ function MarkdownViewInner({
90
+ markdown,
91
+ theme,
92
+ style,
93
+ onLinkPress,
94
+ onImagePress,
95
+ onLineSelection,
96
+ onContentSizeChange,
97
+ NativeComponent
98
+ }) {
99
+ const [autoHeight, setAutoHeight] = (0, _react.useState)(undefined);
100
+ const themeJSONRef = (0, _react.useRef)(undefined);
101
+
102
+ // Re-serialize only when theme reference changes.
103
+ themeJSONRef.current = serializeTheme(theme);
104
+ const handleContentSizeChange = (0, _react.useCallback)(e => {
105
+ const {
106
+ height
107
+ } = e.nativeEvent.contentSize;
108
+ if (height > 0) setAutoHeight(height);
109
+ onContentSizeChange?.(e);
110
+ }, [onContentSizeChange]);
111
+ const handleLinkPress = (0, _react.useCallback)(e => {
112
+ onLinkPress?.(e);
113
+ }, [onLinkPress]);
114
+ const handleImagePress = (0, _react.useCallback)(e => {
115
+ onImagePress?.(e);
116
+ }, [onImagePress]);
117
+ const handleLineSelection = (0, _react.useCallback)(e => {
118
+ onLineSelection?.(e);
119
+ }, [onLineSelection]);
120
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(NativeComponent, {
121
+ markdown: markdown,
122
+ themeJSON: themeJSONRef.current,
123
+ style: [autoHeight !== undefined && styles.autoHeight(autoHeight), style],
124
+ onLinkPress: handleLinkPress,
125
+ onImagePress: handleImagePress,
126
+ onLineSelection: handleLineSelection,
127
+ onContentSizeChange: handleContentSizeChange
128
+ });
129
+ }
130
+
131
+ // ─── Styles ───────────────────────────────────────────────────────────────────
132
+
133
+ const styles = {
134
+ /** Applied when the native view has reported its content height. */
135
+ autoHeight: h => ({
136
+ height: h
137
+ })
138
+ };
139
+ //# sourceMappingURL=MarkdownView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","COMPONENT_NAME","NativeMarkdownView","Platform","OS","requireNativeComponent","serializeTheme","theme","undefined","JSON","stringify","MarkdownView","markdown","style","onLinkPress","onImagePress","onLineSelection","onContentSizeChange","__DEV__","console","warn","jsx","View","MarkdownViewInner","NativeComponent","autoHeight","setAutoHeight","useState","themeJSONRef","useRef","current","handleContentSizeChange","useCallback","height","nativeEvent","contentSize","handleLinkPress","handleImagePress","handleLineSelection","themeJSON","styles","h"],"sourceRoot":"../../src","sources":["MarkdownView.tsx"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _codegenNativeComponent = _interopRequireDefault(require("react-native/Libraries/Utilities/codegenNativeComponent"));
8
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ /**
10
+ * Codegen spec for the New Architecture (Fabric).
11
+ *
12
+ * Running `react-native codegen` (or the build system) reads this file and
13
+ * generates C++ component descriptors, props structs, and event emitters under
14
+ * build/generated/ios/RNMarkdownViewSpec/.
15
+ *
16
+ * All prop/event types must use the codegen-compatible primitives from
17
+ * react-native/Libraries/Types/CodegenTypes.
18
+ */
19
+ // ─── Codegen event payload types ─────────────────────────────────────────────
20
+ // ─── Native component props ───────────────────────────────────────────────────
21
+ var _default = exports.default = (0, _codegenNativeComponent.default)('RNMarkdownView');
22
+ //# sourceMappingURL=NativeMarkdownView.js.map