Skip to content

Commit 9eeb915

Browse files
committed
Release 4.1.0
1 parent 8955adc commit 9eeb915

File tree

161 files changed

+5119
-1585
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+5119
-1585
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## 4.1.0 — 30 Jan 2026
2+
3+
- Adds the `updateAnnotations` API to `PDFDocument` to update existing annotation properties. (J#HYB-828)
4+
- Adds the `setUserInterfaceVisible` API to `NutrientView` to toggle the visibility of the user interface controls. (J#HYB-923)
5+
- Adds the `toolbarPosition` and `supportedToolbarPositions` options to the `PDFConfiguration` object to control the annotation toolbar position. (J#HYB-922)
6+
- Adds the `addTextFormField` and `addElectronicSignatureFormField` APIs to support programmatic form field creation. (J#HYB-924)
7+
- Updates the base `Annotation` model to expose the `group` property to control annotation grouping. (J#HYB-889)
8+
- Updates for Nutrient Android SDK 10.10.1.
9+
- Updates for Nutrient iOS SDK 26.4.0.
10+
- Fixes an issue where the `RemoteDocumentConfiguration` wasn’t being applied on iOS when opening a remote document. (J#HYB-939)
11+
112
## 4.0.1 — 28 Nov 2025
213

314
- Fixes an issue where the `setLicenseKeys` API could throw an error on iOS. (J#HYB-916)
@@ -6,6 +17,8 @@
617

718
- Adds support for React Native’s new architecture. (J#HYB-847)
819
- Updates the `onDocumentLoadFailed` and `DocumentEvent.LOAD_FAILED` events to include a reason code and message. (J#HYB-903)
20+
- Updates for Nutrient Android SDK 10.8.0.
21+
- Updates for Nutrient iOS SDK 26.2.0.
922

1023
## 3.2.0 — 20 Oct 2025
1124

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
All items and source code Copyright © 2010-2025 PSPDFKit GmbH.
1+
All items and source code Copyright © 2010-2026 PSPDFKit GmbH.
22

33
The Nutrient SDK is a commercial product and requires a license to be used.
44

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Nutrient.setLicenseKey('YOUR_REACT_NATIVE_LICENSE_KEY_GOES_HERE');
5151

5252
### Installation
5353

54-
The Nutrient React Native SDK dependency is installed from the GitHub repository and not the `npm` registry. To install the Nutrient React Native SDK, run `yarn add @nutrient-sdk/react-native` in your project directory or `npm install @nutrient-sdk/react-native` if you’re using `npm`.
54+
To install the Nutrient React Native SDK, run `yarn add @nutrient-sdk/react-native` in your project directory or `npm install @nutrient-sdk/react-native` if you’re using `npm`.
5555

5656
### Getting Started
5757

@@ -244,7 +244,7 @@ Take a look at the instructions to get started [here](/samples/Catalog/README.md
244244

245245
### Configuration
246246

247-
The behaviour of the `NutrientView` component can be customized using the configuration object. Refer to the [`PDFConfiguration`](https://www.nutrient.io/api/react-native/PDFConfiguration.html) API documentation. The `PDFConfiguration` object can be passed as parameter in when creating the `NutrientView` component, or when using the `Nutrient.present()` Native Module API.
247+
The behaviour of the `NutrientView` component can be customized using the configuration object. Refer to the [`PDFConfiguration`](https://www.nutrient.io/api/react-native/PDFConfiguration.html) API documentation. The `PDFConfiguration` object can be passed as a prop when creating the `NutrientView` component, or a parameter when using the `Nutrient.present()` Native Module API.
248248

249249
```typescript
250250
const configuration: PDFConfiguration = {
@@ -266,6 +266,6 @@ For Troubleshooting common issues you might encounter when setting up the Nutrie
266266
## License
267267

268268
This project can be used for evaluation or if you have a valid Nutrient license.
269-
All items and source code Copyright © 2010-2025 PSPDFKit GmbH.
269+
All items and source code Copyright © 2010-2026 PSPDFKit GmbH.
270270

271271
See [LICENSE](./LICENSE) for details.

__tests__/pdfdocument.test.ts

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,26 @@ jest.mock('react-native', () => {
88

99
import { PDFDocument } from '../src/document/PDFDocument';
1010
import { NativeModules, findNodeHandle } from 'react-native';
11+
import {
12+
ButtonFormElement,
13+
ChoiceFormElement,
14+
FormElement,
15+
SignatureFormElement,
16+
TextFieldFormElement,
17+
} from '../src/forms/FormElement';
18+
import {
19+
ButtonFormField,
20+
ChoiceFormField,
21+
FormField,
22+
SignatureFormField,
23+
TextFormField,
24+
} from '../src/forms/FormField';
25+
import { WidgetAnnotation } from '../src/annotations/AnnotationModels';
1126

1227
describe('PDFDocument JS helpers', () => {
1328
beforeEach(() => {
1429
jest.clearAllMocks();
30+
(findNodeHandle as jest.Mock).mockReturnValue(999);
1531
});
1632

1733
test('getRef returns numeric reference when findNodeHandle returns null (Fabric fallback)', () => {
@@ -27,6 +43,239 @@ describe('PDFDocument JS helpers', () => {
2743
(NativeModules.PDFDocumentManager.getPageCount as jest.Mock).mockResolvedValueOnce(3);
2844
await expect(doc.setPageIndex(10)).rejects.toBeInstanceOf(Error);
2945
});
46+
47+
describe('createAnnotationInstance - Widget annotations with form elements', () => {
48+
let doc: PDFDocument;
49+
const mockPdfViewRef = { current: {} };
50+
51+
beforeEach(() => {
52+
doc = new PDFDocument(1234);
53+
// @ts-ignore access private
54+
doc.pdfViewRef = mockPdfViewRef;
55+
});
56+
57+
test('creates ButtonFormElement and ButtonFormField for button formTypeName', () => {
58+
const annotationData = {
59+
type: 'pspdfkit/widget',
60+
pageIndex: 0,
61+
bbox: [0, 0, 100, 50],
62+
horizontalAlign: 'left',
63+
verticalAlign: 'top',
64+
formElement: {
65+
formTypeName: 'button',
66+
name: 'test-button',
67+
formField: {
68+
type: 'button',
69+
name: 'test-button',
70+
value: 'button-value',
71+
},
72+
},
73+
};
74+
75+
// @ts-ignore access private
76+
const result = doc.createAnnotationInstance(annotationData);
77+
78+
expect(result).toBeInstanceOf(WidgetAnnotation);
79+
expect(result.formElement).toBeInstanceOf(ButtonFormElement);
80+
expect(result.formElement.formField).toBeInstanceOf(ButtonFormField);
81+
expect(result.formElement.pdfViewRef).toBe(999);
82+
expect(findNodeHandle).toHaveBeenCalledWith(mockPdfViewRef);
83+
});
84+
85+
test('creates ChoiceFormElement and ChoiceFormField for choice formTypeName', () => {
86+
const annotationData = {
87+
type: 'widget',
88+
pageIndex: 0,
89+
bbox: [0, 0, 100, 50],
90+
horizontalAlign: 'left',
91+
verticalAlign: 'top',
92+
formElement: {
93+
formTypeName: 'choice',
94+
name: 'test-choice',
95+
formField: {
96+
type: 'choice',
97+
name: 'test-choice',
98+
value: 'choice-value',
99+
},
100+
},
101+
};
102+
103+
// @ts-ignore access private
104+
const result = doc.createAnnotationInstance(annotationData);
105+
106+
expect(result).toBeInstanceOf(WidgetAnnotation);
107+
expect(result.formElement).toBeInstanceOf(ChoiceFormElement);
108+
expect(result.formElement.formField).toBeInstanceOf(ChoiceFormField);
109+
expect(result.formElement.pdfViewRef).toBe(999);
110+
});
111+
112+
test('creates SignatureFormElement and SignatureFormField for signature formTypeName', () => {
113+
const annotationData = {
114+
type: 'pspdfkit/widget',
115+
pageIndex: 0,
116+
bbox: [0, 0, 100, 50],
117+
horizontalAlign: 'left',
118+
verticalAlign: 'top',
119+
formElement: {
120+
formTypeName: 'signature',
121+
name: 'test-signature',
122+
formField: {
123+
type: 'signature',
124+
name: 'test-signature',
125+
value: 'signature-value',
126+
},
127+
},
128+
};
129+
130+
// @ts-ignore access private
131+
const result = doc.createAnnotationInstance(annotationData);
132+
133+
expect(result).toBeInstanceOf(WidgetAnnotation);
134+
expect(result.formElement).toBeInstanceOf(SignatureFormElement);
135+
expect(result.formElement.formField).toBeInstanceOf(SignatureFormField);
136+
expect(result.formElement.pdfViewRef).toBe(999);
137+
});
138+
139+
test('creates TextFieldFormElement and TextFormField for textfield formTypeName', () => {
140+
const annotationData = {
141+
type: 'pspdfkit/widget',
142+
pageIndex: 0,
143+
bbox: [0, 0, 100, 50],
144+
horizontalAlign: 'left',
145+
verticalAlign: 'top',
146+
formElement: {
147+
formTypeName: 'textField',
148+
name: 'test-textfield',
149+
formField: {
150+
type: 'text',
151+
name: 'test-textfield',
152+
value: 'text-value',
153+
},
154+
},
155+
};
156+
157+
// @ts-ignore access private
158+
const result = doc.createAnnotationInstance(annotationData);
159+
160+
expect(result).toBeInstanceOf(WidgetAnnotation);
161+
expect(result.formElement).toBeInstanceOf(TextFieldFormElement);
162+
expect(result.formElement.formField).toBeInstanceOf(TextFormField);
163+
expect(result.formElement.pdfViewRef).toBe(999);
164+
});
165+
166+
test('creates FormElement and FormField for unknown formTypeName', () => {
167+
const annotationData = {
168+
type: 'pspdfkit/widget',
169+
pageIndex: 0,
170+
bbox: [0, 0, 100, 50],
171+
horizontalAlign: 'left',
172+
verticalAlign: 'top',
173+
formElement: {
174+
formTypeName: 'unknown-type',
175+
name: 'test-unknown',
176+
formField: {
177+
type: 'unknown',
178+
name: 'test-unknown',
179+
value: 'unknown-value',
180+
},
181+
},
182+
};
183+
184+
// @ts-ignore access private
185+
const result = doc.createAnnotationInstance(annotationData);
186+
187+
expect(result).toBeInstanceOf(WidgetAnnotation);
188+
expect(result.formElement).toBeInstanceOf(FormElement);
189+
expect(result.formElement.formField).toBeInstanceOf(FormField);
190+
expect(result.formElement.pdfViewRef).toBe(999);
191+
});
192+
193+
test('handles formTypeName case insensitivity', () => {
194+
const annotationData = {
195+
type: 'pspdfkit/widget',
196+
pageIndex: 0,
197+
bbox: [0, 0, 100, 50],
198+
horizontalAlign: 'left',
199+
verticalAlign: 'top',
200+
formElement: {
201+
formTypeName: 'BUTTON', // uppercase
202+
name: 'test-button',
203+
formField: {
204+
type: 'button',
205+
name: 'test-button',
206+
},
207+
},
208+
};
209+
210+
// @ts-ignore access private
211+
const result = doc.createAnnotationInstance(annotationData);
212+
213+
expect(result.formElement).toBeInstanceOf(ButtonFormElement);
214+
});
215+
216+
test('handles widget annotation without formElement', () => {
217+
const annotationData = {
218+
type: 'pspdfkit/widget',
219+
pageIndex: 0,
220+
bbox: [0, 0, 100, 50],
221+
horizontalAlign: 'left',
222+
verticalAlign: 'top',
223+
};
224+
225+
// @ts-ignore access private
226+
const result = doc.createAnnotationInstance(annotationData);
227+
228+
expect(result).toBeInstanceOf(WidgetAnnotation);
229+
expect(result.formElement).toBeUndefined();
230+
});
231+
232+
test('handles formElement without formField', () => {
233+
const annotationData = {
234+
type: 'pspdfkit/widget',
235+
pageIndex: 0,
236+
bbox: [0, 0, 100, 50],
237+
horizontalAlign: 'left',
238+
verticalAlign: 'top',
239+
formElement: {
240+
formTypeName: 'button',
241+
name: 'test-button',
242+
},
243+
};
244+
245+
// @ts-ignore access private
246+
const result = doc.createAnnotationInstance(annotationData);
247+
248+
expect(result.formElement).toBeInstanceOf(ButtonFormElement);
249+
expect(result.formElement.formField).toBeUndefined();
250+
});
251+
252+
test('sets pdfViewRef on formElementInstance for all form types', () => {
253+
const formTypes = ['button', 'choice', 'signature', 'textField', 'unknown'];
254+
255+
formTypes.forEach((formType) => {
256+
const annotationData = {
257+
type: 'pspdfkit/widget',
258+
pageIndex: 0,
259+
bbox: [0, 0, 100, 50],
260+
horizontalAlign: 'left',
261+
verticalAlign: 'top',
262+
formElement: {
263+
formTypeName: formType,
264+
name: `test-${formType}`,
265+
formField: {
266+
type: formType,
267+
name: `test-${formType}`,
268+
},
269+
},
270+
};
271+
272+
// @ts-ignore access private
273+
const result = doc.createAnnotationInstance(annotationData);
274+
275+
expect(result.formElement.pdfViewRef).toBe(999);
276+
});
277+
});
278+
});
30279
});
31280

32281

android/build.gradle

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* Nutrient
55
*
6-
* Copyright © 2021-2025 PSPDFKit GmbH. All rights reserved.
6+
* Copyright © 2021-2026 PSPDFKit GmbH. All rights reserved.
77
*
88
* THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
99
* AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT.
@@ -15,7 +15,7 @@
1515
* Contains gradle configuration constants
1616
*/
1717
ext {
18-
NUTRIENT_VERSION = '10.8.0'
18+
NUTRIENT_VERSION = '10.10.1'
1919
}
2020

2121
buildscript {
@@ -25,7 +25,7 @@ buildscript {
2525

2626
ext.getComposeVersion = { kotlin_version ->
2727
switch (kotlin_version) {
28-
case ~/2\..*/:
28+
case ~/2\..*/:
2929
// For Kotlin 2.x+, compose compiler is handled by the plugin
3030
return null
3131
case '1.9.25': return '1.5.15'
@@ -122,26 +122,41 @@ android {
122122
}
123123
}
124124

125+
configurations.all {
126+
resolutionStrategy {
127+
force "androidx.compose.foundation:foundation:1.9.4"
128+
force "androidx.compose.ui:ui:1.9.4"
129+
force "androidx.compose.runtime:runtime:1.9.4"
130+
force "androidx.compose.material:material:1.9.4"
131+
force "androidx.compose.material3:material3:1.4.0"
132+
}
133+
}
134+
125135
dependencies {
126136
api("io.nutrient:nutrient:${NUTRIENT_VERSION}") {
127137
exclude group: 'com.google.auto.value', module: 'auto-value'
128138
exclude group: 'androidx.recyclerview', module: 'recyclerview'
129139
}
130140

131141
implementation "com.facebook.react:react-native:+"
142+
143+
// OkHttp dependencies
132144
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
133145
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
134-
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
135-
implementation "androidx.compose.material:material:1.7.8"
136-
implementation "androidx.compose.material3:material3:1.3.1"
137-
implementation "androidx.recyclerview:recyclerview:1.3.2"
138146

139-
// AI Assistant
147+
implementation "androidx.recyclerview:recyclerview:1.4.0"
148+
149+
// Explicitly declare Compose Foundation to ensure version 1.9.4 (matches PSPDFKit SDK)
150+
implementation "androidx.compose.foundation:foundation:1.9.4"
151+
152+
// Let PSPDFKit SDK manage Compose versions - only specify lifecycle-viewmodel-compose to match SDK requirement
153+
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.9.4"
154+
155+
// AI Assistant dependencies
140156
implementation("io.noties.markwon:core:4.6.2")
141157
implementation("io.noties.markwon:html:4.6.2")
142158
implementation("io.noties.markwon:linkify:4.6.2")
143159
implementation("io.noties.markwon:ext-tables:4.6.2")
144160
implementation("io.noties.markwon:ext-strikethrough:4.6.2")
145-
implementation("io.socket:socket.io-client:2.1.1")
146-
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0"
161+
implementation("io.socket:socket.io-client:2.1.2")
147162
}

0 commit comments

Comments
 (0)