@kishannareshpal/expo-pdf 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 (52) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintrc.js +2 -0
  3. package/.node-version +1 -0
  4. package/.prettierignore +31 -0
  5. package/.prettierrc +8 -0
  6. package/CHANGELOG.md +14 -0
  7. package/CONTRIBUTING.md +271 -0
  8. package/LICENSE +21 -0
  9. package/README.md +243 -0
  10. package/android/build.gradle +55 -0
  11. package/android/src/main/AndroidManifest.xml +9 -0
  12. package/android/src/main/java/com/kishannareshpal/expopdf/.editorconfig +12 -0
  13. package/android/src/main/java/com/kishannareshpal/expopdf/KJExpoPdfModule.kt +60 -0
  14. package/android/src/main/java/com/kishannareshpal/expopdf/KJExpoPdfView.kt +222 -0
  15. package/android/src/main/java/com/kishannareshpal/expopdf/lib/ContentPadding.kt +23 -0
  16. package/android/src/main/java/com/kishannareshpal/expopdf/lib/FitMode.kt +18 -0
  17. package/build/index.d.ts +4 -0
  18. package/build/index.d.ts.map +1 -0
  19. package/build/index.js +5 -0
  20. package/build/index.js.map +1 -0
  21. package/build/pdf-module.d.ts +7 -0
  22. package/build/pdf-module.d.ts.map +1 -0
  23. package/build/pdf-module.js +4 -0
  24. package/build/pdf-module.js.map +1 -0
  25. package/build/pdf-view.d.ts +25 -0
  26. package/build/pdf-view.d.ts.map +1 -0
  27. package/build/pdf-view.js +14 -0
  28. package/build/pdf-view.js.map +1 -0
  29. package/build/types.d.ts +20 -0
  30. package/build/types.d.ts.map +1 -0
  31. package/build/types.js +2 -0
  32. package/build/types.js.map +1 -0
  33. package/build/utils.d.ts +3 -0
  34. package/build/utils.d.ts.map +1 -0
  35. package/build/utils.js +10 -0
  36. package/build/utils.js.map +1 -0
  37. package/bun.lock +2278 -0
  38. package/eslint.config.js +5 -0
  39. package/expo-module.config.json +16 -0
  40. package/ios/KJExpoPdf.podspec +29 -0
  41. package/ios/KJExpoPdfModule.swift +51 -0
  42. package/ios/KJExpoPdfView.swift +242 -0
  43. package/ios/extensions/PdfViewExtensions.swift +94 -0
  44. package/ios/lib/ContentPadding.swift +26 -0
  45. package/ios/lib/FitMode.swift +14 -0
  46. package/package.json +60 -0
  47. package/src/index.ts +4 -0
  48. package/src/pdf-module.ts +8 -0
  49. package/src/pdf-view.tsx +68 -0
  50. package/src/types.ts +16 -0
  51. package/src/utils.ts +12 -0
  52. package/tsconfig.json +9 -0
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['universe/native', 'universe/web'],
4
+ ignorePatterns: ['build'],
5
+ };
@@ -0,0 +1,16 @@
1
+ {
2
+ "platforms": [
3
+ "apple",
4
+ "android"
5
+ ],
6
+ "apple": {
7
+ "modules": [
8
+ "KJExpoPdfModule"
9
+ ]
10
+ },
11
+ "android": {
12
+ "modules": [
13
+ "com.kishannareshpal.expopdf.KJExpoPdfModule"
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'KJExpoPdf'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author']
12
+ s.homepage = package['homepage']
13
+ s.platforms = {
14
+ :ios => '15.1',
15
+ :tvos => '15.1'
16
+ }
17
+ s.swift_version = '5.9'
18
+ s.source = { git: 'https://github.com/kishannareshpal/expo-pdf' }
19
+ s.static_framework = true
20
+
21
+ s.dependency 'ExpoModulesCore'
22
+
23
+ # Swift/Objective-C compatibility
24
+ s.pod_target_xcconfig = {
25
+ 'DEFINES_MODULE' => 'YES',
26
+ }
27
+
28
+ s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
29
+ end
@@ -0,0 +1,51 @@
1
+ import ExpoModulesCore
2
+
3
+ public class KJExpoPdfModule: Module {
4
+ // Each module class must implement the definition function. The definition consists of components
5
+ // that describes the module's functionality and behavior.
6
+ // See https://docs.expo.dev/modules/module-api for more details about available components.
7
+ public func definition() -> ModuleDefinition {
8
+ // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
9
+ // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
10
+ // The module will be accessible from `requireNativeModule('ExpoPdf')` in JavaScript.
11
+ Name("KJExpoPdf")
12
+
13
+ // Enables the module to be used as a native view. Definition components that are accepted as part of the
14
+ // view definition: Prop, Events.
15
+ View(KJExpoPdfView.self) {
16
+ Events("onLoadComplete", "onPageChanged", "onError")
17
+
18
+ Prop("uri") { (view: KJExpoPdfView, uri: String) in
19
+ view.setUri(uri)
20
+ }
21
+
22
+ Prop("password") { (view: KJExpoPdfView, password: String?) in
23
+ view.setPassword(password)
24
+ }
25
+
26
+ Prop("pagingEnabled") { (view: KJExpoPdfView, enabled: Bool?) in
27
+ view.setPagingEnabled(enabled)
28
+ }
29
+
30
+ Prop("disableDoubleTapToZoom") { (view: KJExpoPdfView, disabled: Bool?) in
31
+ view.setDoubleTapZoomEnabled(disabled != true)
32
+ }
33
+
34
+ Prop("horizontal") { (view: KJExpoPdfView, enabled: Bool?) in
35
+ view.setHorizontalModeEnabled(enabled)
36
+ }
37
+
38
+ Prop("pageGap") { (view: KJExpoPdfView, gapPx: Int?) in
39
+ view.setPageGap(gapPx)
40
+ }
41
+
42
+ Prop("contentPadding") { (view: KJExpoPdfView, contentPadding: ContentPadding?) in
43
+ view.setContentPadding(contentPadding?.toEdgeInset())
44
+ }
45
+
46
+ Prop("fitMode") { (view: KJExpoPdfView, fitMode: FitMode?) in
47
+ view.setFitMode(fitMode)
48
+ }
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,242 @@
1
+ import ExpoModulesCore
2
+ import PDFKit
3
+ import SwiftUI
4
+
5
+ class KJExpoPdfView: ExpoView {
6
+ // MARK: - Defaults
7
+ static let DEFAULT_PAGING_ENABLED = false
8
+ static let DEFAULT_DOUBLE_TAP_ZOOM_ENABLED = true
9
+ static let DEFAULT_HORIZONTAL_MODE_ENABLED = false
10
+ static let DEFAULT_PAGE_GAP = 0
11
+ static let DEFAULT_CONTENT_PADDING = UIEdgeInsets.zero
12
+ static let DEFAULT_FIT_MODE = FitMode.both
13
+
14
+ let onLoadComplete = EventDispatcher()
15
+ let onPageChanged = EventDispatcher()
16
+ let onError = EventDispatcher()
17
+
18
+ enum ErrorCode: String, Codable {
19
+ case invalidUri = "invalid_uri"
20
+ case invalidDocument = "invalid_document"
21
+ case passwordRequired = "password_required"
22
+ case passwordIncorrect = "password_incorrect"
23
+ }
24
+
25
+ private let pdfView = PDFView()
26
+
27
+ private var documentURL: URL? = nil
28
+ private var password: String? = nil
29
+ private var isPagingEnabled: Bool = DEFAULT_PAGING_ENABLED
30
+ private var isDoubleTapZoomEnabled: Bool = DEFAULT_DOUBLE_TAP_ZOOM_ENABLED
31
+ private var isHorizontalModeEnabled: Bool = DEFAULT_HORIZONTAL_MODE_ENABLED
32
+ private var pageGap: Int = DEFAULT_PAGE_GAP
33
+ private var contentPadding: UIEdgeInsets = DEFAULT_CONTENT_PADDING
34
+ private var fitMode: FitMode = DEFAULT_FIT_MODE
35
+
36
+ required init(appContext: AppContext? = nil) {
37
+ super.init(appContext: appContext)
38
+
39
+ clipsToBounds = true
40
+
41
+ // Set the primitive PdfView's content background to transparent so that it inherits
42
+ // the color from the React Native view (ExpoView), as defined by the
43
+ // style prop in the component (`style={{ backgroundColor: '#eee' }}`).
44
+ self.pdfView.backgroundColor = .clear
45
+
46
+ // We calculate the scaling manually via PDFView.scaleToFit(contentPadding:)
47
+ self.pdfView.autoScales = false
48
+
49
+ addSubview(pdfView)
50
+
51
+ setupListeners()
52
+ }
53
+
54
+ override func layoutSubviews() {
55
+ super.layoutSubviews()
56
+ pdfView.frame = bounds
57
+
58
+ // Maintain insets on rotation, but don't reset reading position
59
+ self.pdfView.scaleToFit(
60
+ contentPadding: self.contentPadding, fitMode: self.fitMode, resetOffset: false)
61
+ }
62
+
63
+ deinit {
64
+ NotificationCenter.default.removeObserver(self)
65
+ }
66
+
67
+ func setUri(_ uri: String) {
68
+ guard let parsedURL = URL(string: uri) else {
69
+ reportError(.invalidUri, "Invalid URI provided: \(uri)")
70
+ return
71
+ }
72
+
73
+ self.documentURL = parsedURL
74
+ self.reloadPdf()
75
+ }
76
+
77
+ func setPassword(_ password: String?) {
78
+ self.password = password
79
+
80
+ // Reload the PDF as it needs to perform the unlock attempt
81
+ // if password has been set, or lock if password's been removed
82
+ if self.pdfView.document?.isLocked == true {
83
+ self.reloadPdf()
84
+ }
85
+ }
86
+
87
+ func setPagingEnabled(_ enabled: Bool?) {
88
+ self.isPagingEnabled = enabled ?? Self.DEFAULT_PAGING_ENABLED
89
+ self.pdfView.displayMode = self.isPagingEnabled ? .singlePage : .singlePageContinuous
90
+ }
91
+
92
+ func setDoubleTapZoomEnabled(_ enabled: Bool?) {
93
+ self.isDoubleTapZoomEnabled = enabled ?? Self.DEFAULT_DOUBLE_TAP_ZOOM_ENABLED
94
+ self.pdfView.toggleDoubleTapToZoom(self.isDoubleTapZoomEnabled)
95
+ }
96
+
97
+ func setHorizontalModeEnabled(_ enabled: Bool?) {
98
+ self.isHorizontalModeEnabled = enabled ?? Self.DEFAULT_HORIZONTAL_MODE_ENABLED
99
+ self.pdfView.displayDirection = self.isHorizontalModeEnabled ? .horizontal : .vertical
100
+ }
101
+
102
+ func setPageGap(_ gap: Int?) {
103
+ self.pageGap = gap ?? Self.DEFAULT_PAGE_GAP
104
+ self.pdfView.pageBreakMargins = UIEdgeInsets(
105
+ top: 0,
106
+ left: 0,
107
+ bottom: isHorizontalModeEnabled ? 0 : CGFloat(pageGap),
108
+ right: isHorizontalModeEnabled ? CGFloat(pageGap) : 0
109
+ )
110
+
111
+ // PDFView pageBreakMargins not only apply insets between the pages, but also around the pages
112
+ // which is not what we always want - expo-pdf only uses pageBreakMargins for inter page spacing
113
+ // and we use our contentPadding for the spacing around the document.
114
+ // - Subtract the PDFView pageBreakMargins to prevent double spacing
115
+ var padding = self.contentPadding
116
+ let margins = self.pdfView.pageBreakMargins
117
+
118
+ padding = UIEdgeInsets(
119
+ top: padding.top - margins.top,
120
+ left: padding.left - margins.left,
121
+ bottom: padding.bottom - margins.bottom,
122
+ right: padding.right - margins.right
123
+ )
124
+ self.pdfView.scaleToFit(
125
+ contentPadding: self.contentPadding, fitMode: self.fitMode, resetOffset: false)
126
+ }
127
+
128
+ func setContentPadding(_ padding: UIEdgeInsets?) {
129
+ self.contentPadding = padding ?? Self.DEFAULT_CONTENT_PADDING
130
+
131
+ // PDFView pageBreakMargins not only apply insets between the pages, but also around the pages
132
+ // which is not what we always want - expo-pdf only uses pageBreakMargins for inter page spacing
133
+ // and we use our contentPadding for the spacing around the document.
134
+ // - Subtract the PDFView pageBreakMargins to prevent double spacing
135
+ var padding = self.contentPadding
136
+ let margins = self.pdfView.pageBreakMargins
137
+
138
+ padding = UIEdgeInsets(
139
+ top: padding.top - margins.top,
140
+ left: padding.left - margins.left,
141
+ bottom: padding.bottom - margins.bottom,
142
+ right: padding.right - margins.right
143
+ )
144
+ self.pdfView.scaleToFit(
145
+ contentPadding: self.contentPadding, fitMode: self.fitMode, resetOffset: false)
146
+ }
147
+
148
+ func setFitMode(_ mode: FitMode?) {
149
+ self.fitMode = mode ?? Self.DEFAULT_FIT_MODE
150
+
151
+ self.pdfView.scaleToFit(
152
+ contentPadding: self.contentPadding, fitMode: self.fitMode, resetOffset: false)
153
+ }
154
+
155
+ @objc private func handlePageChange() {
156
+ guard
157
+ let page = pdfView.currentPage,
158
+ let document = pdfView.document
159
+ else { return }
160
+
161
+ onPageChanged([
162
+ "pageIndex": document.index(for: page),
163
+ "pageCount": document.pageCount,
164
+ ])
165
+ }
166
+
167
+ private func reloadPdf() {
168
+ guard let document = self.loadDocument() else {
169
+ return
170
+ }
171
+
172
+ if document.isLocked {
173
+ if let password = self.password {
174
+ let unlocked = document.unlock(withPassword: password)
175
+ if !unlocked {
176
+ reportError(
177
+ .passwordIncorrect,
178
+ "The provided password was incorrect"
179
+ )
180
+ }
181
+ } else {
182
+ reportError(
183
+ .passwordRequired,
184
+ "PDF requires a password, but no password was provided"
185
+ )
186
+ }
187
+ }
188
+
189
+ self.pdfView.document = document
190
+
191
+ // Dispatch async to allow PDFView to finish its initial layout
192
+ DispatchQueue.main.async {
193
+ self.pdfView.scaleToFit(
194
+ contentPadding: self.contentPadding, fitMode: self.fitMode, resetOffset: true)
195
+ }
196
+
197
+ self.onLoadComplete([
198
+ "pageCount": document.pageCount
199
+ ])
200
+ }
201
+
202
+ private func loadDocument() -> PDFDocument? {
203
+ guard let documentURL else {
204
+ return nil
205
+ }
206
+
207
+ // TODO: Apple PDFView supports "http", "https" - but I've not implemented it on Android so for consistency I'm explicitly not allowing it here until then.
208
+ guard ["file"].contains(self.documentURL?.scheme?.lowercased()) else {
209
+ reportError(
210
+ .invalidUri,
211
+ "URL scheme '\(self.documentURL?.scheme ?? "unknown")' is not supported"
212
+ )
213
+ return nil
214
+ }
215
+
216
+ guard let document: PDFDocument = PDFDocument(url: documentURL) else {
217
+ self.reportError(
218
+ .invalidDocument,
219
+ "Failed to load the PDF document"
220
+ )
221
+ return nil
222
+ }
223
+
224
+ return document
225
+ }
226
+
227
+ private func setupListeners() {
228
+ NotificationCenter.default.addObserver(
229
+ self,
230
+ selector: #selector(handlePageChange),
231
+ name: .PDFViewPageChanged,
232
+ object: pdfView
233
+ )
234
+ }
235
+
236
+ private func reportError(_ code: ErrorCode, _ message: String) {
237
+ onError([
238
+ "code": code.rawValue,
239
+ "message": message,
240
+ ])
241
+ }
242
+ }
@@ -0,0 +1,94 @@
1
+ //
2
+ // PdfViewExtensions.swift
3
+ // Pods
4
+ //
5
+ // Created by Kishan Jadav on 05/01/2026.
6
+ //
7
+
8
+ import PDFKit
9
+
10
+ extension PDFView {
11
+ func scaleToFit(contentPadding: UIEdgeInsets, fitMode: FitMode, resetOffset: Bool = false) {
12
+ guard let page = self.currentPage else {
13
+ return
14
+ }
15
+
16
+ let viewSize = self.bounds.size
17
+ let pageSize = page.bounds(for: self.displayBox).size
18
+
19
+ // Ensure we have valid dimensions to avoid division by zero
20
+ guard viewSize.width > 0, pageSize.width > 0, pageSize.height > 0 else {
21
+ return
22
+ }
23
+
24
+ // Calculate the available space (View size minus Padding)
25
+ let availableWidth = viewSize.width - contentPadding.left - contentPadding.right
26
+ let availableHeight = viewSize.height - contentPadding.top - contentPadding.bottom
27
+
28
+ // Calculate potential scale factors
29
+ let widthScale = availableWidth / pageSize.width
30
+ let heightScale = availableHeight / pageSize.height
31
+
32
+ // Determine the target scale based on the requested FitMode
33
+ let targetScale: CGFloat
34
+ switch fitMode {
35
+ case .width:
36
+ targetScale = widthScale
37
+ case .height:
38
+ targetScale = heightScale
39
+ case .both:
40
+ // "Aspect Fit": Choose the smaller scale to ensure the whole page is visible
41
+ targetScale = min(widthScale, heightScale)
42
+ }
43
+
44
+ // Apply new scale factor
45
+ if abs(self.scaleFactor - targetScale) > 0.001 {
46
+ self.minScaleFactor = targetScale // Prevent zooming out further than the fit
47
+ self.scaleFactor = targetScale
48
+ }
49
+
50
+ self.applyContentPadding(contentPadding, resetOffset: resetOffset)
51
+ }
52
+
53
+ func applyContentPadding(_ contentPadding: UIEdgeInsets, resetOffset: Bool = false) {
54
+ // Iterate through the PDFView's subviews to find the scroll view
55
+ if let scrollView = self.subviews.first(where: { $0 is UIScrollView }) as? UIScrollView {
56
+ scrollView.contentInset = contentPadding
57
+
58
+ if resetOffset {
59
+ var offset = scrollView.contentOffset
60
+ if self.displayDirection == .horizontal {
61
+ offset.x = -contentPadding.left
62
+ } else {
63
+ offset.y = -contentPadding.top
64
+ }
65
+ scrollView.contentOffset = offset
66
+ }
67
+ }
68
+ }
69
+
70
+ func toggleDoubleTapToZoom(_ enabled: Bool) {
71
+ // Iterate through the PDFView's subviews to find the scroll view
72
+ for subview in self.subviews {
73
+ if let gestureRecognizers = subview.gestureRecognizers {
74
+ for gesture in gestureRecognizers {
75
+ if let tapGesture = gesture as? UITapGestureRecognizer, tapGesture.numberOfTapsRequired == 2 {
76
+ // Disable the double-tap recognizer
77
+ tapGesture.isEnabled = enabled
78
+ }
79
+ }
80
+ }
81
+
82
+ // Sometimes the gesture is deeper, so we check sub-subviews (like the document view)
83
+ for internalSubview in subview.subviews {
84
+ if let gestureRecognizers = internalSubview.gestureRecognizers {
85
+ for gesture in gestureRecognizers {
86
+ if let tapGesture = gesture as? UITapGestureRecognizer, tapGesture.numberOfTapsRequired == 2 {
87
+ tapGesture.isEnabled = enabled
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,26 @@
1
+ //
2
+ // ContentPadding.swift
3
+ // Pods
4
+ //
5
+ // Created by Kishan Jadav on 05/01/2026.
6
+ //
7
+
8
+ import ExpoModulesCore
9
+
10
+ struct ContentPadding: Record {
11
+ @Field
12
+ var left: Int = 0
13
+
14
+ @Field
15
+ var top: Int = 0
16
+
17
+ @Field
18
+ var right: Int = 0
19
+
20
+ @Field
21
+ var bottom: Int = 0
22
+
23
+ func toEdgeInset() -> UIEdgeInsets {
24
+ UIEdgeInsets(top: CGFloat(self.top), left: CGFloat(self.left), bottom: CGFloat(self.bottom), right: CGFloat(self.right))
25
+ }
26
+ }
@@ -0,0 +1,14 @@
1
+ //
2
+ // FitMode.swift
3
+ // Pods
4
+ //
5
+ // Created by Kishan Jadav on 05/01/2026.
6
+ //
7
+
8
+ import ExpoModulesCore
9
+
10
+ enum FitMode: String, Enumerable {
11
+ case width
12
+ case height
13
+ case both
14
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@kishannareshpal/expo-pdf",
3
+ "version": "0.1.0",
4
+ "description": "A cross-platform, performant PDF viewer component for React Native and Expo",
5
+ "main": "build/index.js",
6
+ "types": "build/index.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepublishOnly": "expo-module prepublishOnly",
13
+ "expo-module": "expo-module",
14
+ "open:ios": "xed example/ios",
15
+ "open:android": "open -a \"Android Studio\" example/android",
16
+ "format": "prettier --write .",
17
+ "format:check": "prettier --check .",
18
+ "prepare": "husky; expo-module prepare"
19
+ },
20
+ "keywords": [
21
+ "react-native",
22
+ "expo",
23
+ "@kishannareshpal/expo-pdf",
24
+ "rn-pdf",
25
+ "react-native-pdf",
26
+ "expo-pdf",
27
+ "pdf",
28
+ "expopdf"
29
+ ],
30
+ "repository": "https://github.com/kishannareshpal/expo-pdf",
31
+ "bugs": {
32
+ "url": "https://github.com/kishannareshpal/expo-pdf/issues"
33
+ },
34
+ "author": "Kishan Jadav <kishan_jadav@hotmail.com> (https://github.com/kishannareshpal)",
35
+ "license": "MIT",
36
+ "homepage": "https://github.com/kishannareshpal/expo-pdf#readme",
37
+ "dependencies": {},
38
+ "devDependencies": {
39
+ "@types/react": "~19.1.0",
40
+ "expo": "^54.0.27",
41
+ "expo-module-scripts": "^5.0.8",
42
+ "husky": "^9.1.7",
43
+ "lint-staged": "^16.2.7",
44
+ "prettier": "^3.7.4",
45
+ "react-native": "0.81.5"
46
+ },
47
+ "peerDependencies": {
48
+ "expo": "*",
49
+ "react": "*",
50
+ "react-native": "*"
51
+ },
52
+ "lint-staged": {
53
+ "*.{js,jsx,ts,tsx,json,css,md,yml,yaml}": [
54
+ "prettier --write"
55
+ ],
56
+ "*.{java,kt,swift,h,m,mm}": [
57
+ "prettier --write"
58
+ ]
59
+ }
60
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Reexport the native module
2
+ export { default } from './pdf-module';
3
+ export { PdfView } from './pdf-view';
4
+ export * from './types';
@@ -0,0 +1,8 @@
1
+ import { NativeModule, requireNativeModule } from 'expo';
2
+
3
+ type PdfModuleEvents = {}
4
+
5
+ declare class PdfModule extends NativeModule<PdfModuleEvents> { }
6
+
7
+ // This call loads the native module object from the JSI.
8
+ export default requireNativeModule<PdfModule>('KJExpoPdf');
@@ -0,0 +1,68 @@
1
+ import { requireNativeView } from 'expo';
2
+ import * as React from 'react';
3
+
4
+ import { ContentPadding, FitMode, OnErrorEventPayload, OnLoadCompleteEventPayload, OnPageChangedEventPayload } from './types';
5
+ import { NativeSyntheticEvent, StyleProp, StyleSheet, ViewStyle } from 'react-native';
6
+ import { forwardNativeEventTo } from './utils';
7
+
8
+ type BaseProps = {
9
+ style?: StyleProp<ViewStyle>;
10
+ /**
11
+ * The file URI. Accepts a remote resource (e.g. via HTTPs) or a local file path (e.g. file:///)
12
+ */
13
+ uri: string;
14
+ password?: string;
15
+ pagingEnabled?: boolean
16
+ disableDoubleTapToZoom?: boolean
17
+ horizontal?: boolean
18
+ pageGap?: number
19
+ contentPadding?: ContentPadding
20
+ fitMode?: FitMode
21
+ }
22
+
23
+ type NativePdfViewProps = BaseProps & {
24
+ onLoadComplete?: (event: NativeSyntheticEvent<OnLoadCompleteEventPayload>) => void;
25
+ onPageChanged?: (event: NativeSyntheticEvent<OnPageChangedEventPayload>) => void;
26
+ onError?: (event: NativeSyntheticEvent<OnErrorEventPayload>) => void;
27
+ };
28
+
29
+ const NativePdfView: React.ComponentType<NativePdfViewProps> = requireNativeView('KJExpoPdf');
30
+
31
+ // -----------
32
+
33
+ export type PdfViewProps = BaseProps & {
34
+ onLoadComplete?: (params: OnLoadCompleteEventPayload) => void,
35
+ onPageChanged?: (params: OnPageChangedEventPayload) => void,
36
+ onError?: (params: OnErrorEventPayload) => void
37
+ };
38
+
39
+ export const PdfView = ({
40
+ style,
41
+ onLoadComplete,
42
+ onError,
43
+ onPageChanged,
44
+ ...props
45
+ }: PdfViewProps) => {
46
+ return (
47
+ <NativePdfView
48
+ style={[styles.container, style]}
49
+ uri={props.uri}
50
+ disableDoubleTapToZoom={props.disableDoubleTapToZoom}
51
+ horizontal={props.horizontal}
52
+ pageGap={props.pageGap}
53
+ pagingEnabled={props.pagingEnabled}
54
+ password={props.password}
55
+ contentPadding={props.contentPadding}
56
+ fitMode={props.fitMode}
57
+ onLoadComplete={forwardNativeEventTo(onLoadComplete)}
58
+ onPageChanged={forwardNativeEventTo(onPageChanged)}
59
+ onError={forwardNativeEventTo(onError)}
60
+ />
61
+ )
62
+ }
63
+
64
+ const styles = StyleSheet.create({
65
+ container: {
66
+ backgroundColor: '#eeeeee'
67
+ }
68
+ })
package/src/types.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type OnLoadCompleteEventPayload = { pageCount: number }
2
+
3
+ export type OnPageChangedEventPayload = { pageIndex: number, pageCount: number }
4
+
5
+ export type ErrorCode = 'no_url' | 'invalid_url' | 'invalid_document'
6
+
7
+ export type OnErrorEventPayload = { code: ErrorCode, message: string }
8
+
9
+ export type ContentPadding = {
10
+ top?: number;
11
+ right?: number;
12
+ bottom?: number;
13
+ left?: number;
14
+ }
15
+
16
+ export type FitMode = 'width' | 'height' | 'both';
package/src/utils.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { NativeSyntheticEvent } from 'react-native';
2
+
3
+ export const forwardNativeEventTo = <T,>(handler?: (payload: T) => void) => {
4
+ if (!handler) return undefined;
5
+
6
+ return (event: NativeSyntheticEvent<T>) => {
7
+ // Native event includes a "target" property that I think corresponds to the view tag, but this is not needed in my lib
8
+ delete (event.nativeEvent as any).target;
9
+
10
+ handler(event.nativeEvent);
11
+ };
12
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ // @generated by expo-module-scripts
2
+ {
3
+ "extends": "expo-module-scripts/tsconfig.base",
4
+ "compilerOptions": {
5
+ "outDir": "./build"
6
+ },
7
+ "include": ["./src"],
8
+ "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"]
9
+ }