@symbiote-native/print 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @symbiote-native/print
2
+
3
+ Print an invoice, a ticket or any HTML or PDF through the system print UI, or render HTML to a PDF
4
+ file you can share. One API for every [SymbioteNative](../../README.md) adapter (React, Vue,
5
+ Svelte, Solid and Angular).
6
+
7
+ It wraps [`expo-print`](https://github.com/expo/expo/tree/main/packages/expo-print) (AirPrint on
8
+ iOS, the print framework on Android) the same way
9
+ [`@symbiote-native/mail-composer`](../mail-composer) wraps its upstream: `expo-modules-core` is a
10
+ direct dependency, never the `expo` meta-package, and the upstream JS is hand-ported into `core/`.
11
+ The mechanics live in the `symbiote-expo-native-module` project skill.
12
+
13
+ ## Install
14
+
15
+ **New app:**
16
+
17
+ ```bash
18
+ npx @symbiote-native/cli new my-app --print
19
+ ```
20
+
21
+ **Existing SymbioteNative app:**
22
+
23
+ ```bash
24
+ npx @symbiote-native/cli add --print
25
+ ```
26
+
27
+ Either way: installs `@symbiote-native/print` and wires the native autolinking automatically -
28
+ see [`@symbiote-native/cli`](../cli).
29
+
30
+ <details>
31
+ <summary>Manual install (no CLI - installing and wiring native autolinking by hand)</summary>
32
+
33
+ ```bash
34
+ npm install @symbiote-native/print
35
+ ```
36
+
37
+ `expo-print` and `expo-modules-core` come along as regular, pinned dependencies - never install
38
+ either yourself, and never add the `expo` meta-package to this project (it bundles its own
39
+ Metro/Babel pipeline that conflicts with this project's own).
40
+
41
+ ### Required one-time step: native autolinking wiring
42
+
43
+ Unlike a plain RN native module, `expo-print`'s native code is discovered by
44
+ `expo-modules-autolinking` - this needs wiring into the native host app **once**, covering this
45
+ package and every other `expo-modules-core` package with zero further changes:
46
+
47
+ | Platform | Touches |
48
+ | -------- | ------------------------------------------------------------------------------------- |
49
+ | iOS | `ios/Podfile` - add `use_expo_modules!` |
50
+ | iOS | `AppDelegate.swift` - Expo's runtime-bootstrap hook |
51
+ | Android | `settings.gradle` / `app/build.gradle` - resolve and include the Expo Gradle projects |
52
+ | Android | `MainApplication.kt` - Expo's bootstrap hook, plus a native-module name map |
53
+
54
+ Full mechanics live in the `symbiote-expo-native-module` skill. The per-package half of that
55
+ table - the Gradle dependency and the module map entry - is generated by
56
+ [`@symbiote-native/expo-modules-link`](../expo-modules-link) from this package's
57
+ `native-link.json` on every install.
58
+
59
+ ### No permissions, no config plugin
60
+
61
+ - **No runtime permission on either platform.** Both the AirPrint sheet and Android's print
62
+ framework are OS chrome the user drives directly - nothing our code touches needs a grant.
63
+ - **No manifest edit.** `expo-print`'s own `AndroidManifest.xml` declares nothing.
64
+ - **No config plugin.** Upstream ships none (`app.plugin.js` doesn't exist in the package) - a
65
+ clean `native-link.json` with only the Android module entry is the whole file.
66
+
67
+ </details>
68
+
69
+ ## Shape
70
+
71
+ ```
72
+ src/core/ the whole API: printAsync + selectPrinterAsync + printToFileAsync +
73
+ Orientation. native-module.ts resolves ExpoPrint through
74
+ expo-modules-core's requireNativeModule.
75
+ src/angular/ @symbiote-native/print/angular
76
+ ```
77
+
78
+ `./react`, `./vue`, `./svelte`, and `./solid` are `exports`-map aliases straight onto `src/core/` -
79
+ no physical per-framework file, same reason [`@symbiote-native/mail-composer`](../mail-composer)
80
+ does the same: every export here is a stateless free function or a plain constant, nothing to wrap
81
+ in a hook, composable, or service. `./angular` stays a physical file/subpath since Angular ships
82
+ through a separate `ngc`/AOT build (`build-ngc/`).
83
+
84
+ ## Use it
85
+
86
+ ```ts
87
+ import { printAsync, printToFileAsync } from '@symbiote-native/print';
88
+
89
+ await printAsync({ html: '<h1>Invoice #1234</h1>' });
90
+
91
+ const { uri } = await printToFileAsync({ html: '<h1>Invoice #1234</h1>' });
92
+ ```
93
+
94
+ Selecting a printer first (iOS only):
95
+
96
+ ```ts
97
+ import { printAsync, selectPrinterAsync } from '@symbiote-native/print';
98
+
99
+ const { url } = await selectPrinterAsync();
100
+ await printAsync({ html: '<h1>Invoice #1234</h1>', printerUrl: url });
101
+ ```
102
+
103
+ ## API
104
+
105
+ | Export | Signature | Notes |
106
+ | ------------------- | ------------------------------------------------------- | ------------------------------------------- |
107
+ | `printAsync` | `(options: IPrintOptions) => Promise<void>` | Exactly one of `uri`/`html`. One at a time. |
108
+ | `selectPrinterAsync` | `() => Promise<IPrinter>` | iOS only - throws elsewhere. |
109
+ | `printToFileAsync` | `(options?: IFilePrintOptions) => Promise<IFilePrintResult>` | Renders HTML to a PDF in the cache dir. |
110
+ | `Orientation` | `{ portrait: string; landscape: string }` | Values for `IPrintOptions.orientation`. |
111
+
112
+ ## Notes
113
+
114
+ - **One print job at a time.** A second `printAsync` call while one is in flight rejects
115
+ immediately - same guard upstream keeps as module-level state.
116
+ - **`markupFormatterIOS` is kept as upstream's deprecated option.** On iOS it stands in for
117
+ `html`/`uri`, warns on every call, and `useMarkupFormatter` is the replacement.
118
+
119
+ ## Common questions
120
+
121
+ - **Images missing from the page or PDF.** A local `file://` image can fail inside HTML, and may
122
+ work in development but not in release. Embed it as a base64 `data:` URI or use an `https://` URL.
123
+ - **Extra blank page at the end.** A known HTML-to-PDF quirk: keep content slightly shorter than the
124
+ page, and set margins with an `@page { margin: ... }` rule.
125
+ - **Page size and margins.** `width`/`height` (pixels, default 612 by 792) and `margins`, or `@page`.
126
+ - **Save or share the PDF.** Pass the `uri` from `printToFileAsync` to
127
+ [`@symbiote-native/sharing`](../sharing).
128
+
129
+ Sources: [Expo docs: Print](https://docs.expo.dev/versions/latest/sdk/print/),
130
+ [expo/expo#6169](https://github.com/expo/expo/issues/6169),
131
+ [expo/expo#7435](https://github.com/expo/expo/issues/7435),
132
+ [expo/expo#24011](https://github.com/expo/expo/issues/24011).
133
+
134
+ ## Test it
135
+
136
+ ```bash
137
+ pnpm vitest run packages/print
138
+ ```
139
+
140
+ The core tests fake the native module in place of `requireNativeModule`'s runtime resolution -
141
+ `ExpoPrint` only exists on a device, so a headless run would otherwise throw at import. Upstream
142
+ ships no test suite of its own (`expo-print`'s `src/` has no `__tests__`), so nothing to port -
143
+ every test here is net-new coverage of the ported logic (input validation, the in-flight guard,
144
+ the `selectPrinter`-unavailable branch).
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,2 @@
1
+ // No per-instance state or event stream to wrap in an injectable service — plain re-export.
2
+ export * from '../core/index.js';
@@ -0,0 +1,2 @@
1
+ export { Orientation, printAsync, printToFileAsync, selectPrinterAsync, } from './print';
2
+ export type { IFilePrintOptions, IFilePrintResult, IPageMargins, IPrintOptions, IPrintOrientation, IPrinter, } from './types';
@@ -0,0 +1 @@
1
+ export { Orientation, printAsync, printToFileAsync, selectPrinterAsync, } from './print.js';
@@ -0,0 +1,9 @@
1
+ import type { IFilePrintOptions, IFilePrintResult, IPrintOptions, IPrintOrientation, IPrinter } from './types';
2
+ export type INativePrintModule = {
3
+ Orientation: IPrintOrientation;
4
+ print(options: IPrintOptions): Promise<void>;
5
+ /** iOS only — absent on Android's native module. */
6
+ selectPrinter?(): Promise<IPrinter>;
7
+ printToFileAsync(options: IFilePrintOptions): Promise<IFilePrintResult>;
8
+ };
9
+ export declare const expoPrint: INativePrintModule;
@@ -0,0 +1,3 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_PRINT_MODULE_NAME = 'ExpoPrint';
3
+ export const expoPrint = requireNativeModule(EXPO_PRINT_MODULE_NAME);
@@ -0,0 +1,8 @@
1
+ import type { IFilePrintOptions, IFilePrintResult, IPrintOptions, IPrinter } from './types';
2
+ export declare const Orientation: import("./types").IPrintOrientation;
3
+ /** Opens the native print window (AirPrint / Android's print framework). */
4
+ export declare function printAsync(options: IPrintOptions): Promise<void>;
5
+ /** Opens a printer picker; the result feeds `printerUrl` into `printAsync`. iOS only. */
6
+ export declare function selectPrinterAsync(): Promise<IPrinter>;
7
+ /** Renders HTML to a PDF file in the app's cache directory. */
8
+ export declare function printToFileAsync(options?: IFilePrintOptions): Promise<IFilePrintResult>;
@@ -0,0 +1,42 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { expoPrint } from './native-module.js';
3
+ export const Orientation = expoPrint.Orientation;
4
+ let isPrinting = false;
5
+ /** Opens the native print window (AirPrint / Android's print framework). */
6
+ export async function printAsync(options) {
7
+ // The deprecated formatter option stands in for `html` and `uri` on iOS only
8
+ const hasMarkupFormatter = Platform.select({
9
+ ios: Boolean(options.markupFormatterIOS),
10
+ default: false,
11
+ });
12
+ if (!options.uri && !options.html && !hasMarkupFormatter) {
13
+ throw new Error('Must provide either `html` or `uri` to print');
14
+ }
15
+ if (options.uri && options.html) {
16
+ throw new Error('Must provide exactly one of `html` and `uri` but both were specified');
17
+ }
18
+ if (options.markupFormatterIOS !== undefined) {
19
+ console.warn('The markupFormatterIOS option is deprecated. Use useMarkupFormatter instead.');
20
+ }
21
+ if (isPrinting) {
22
+ throw new Error('Another print request is already in progress');
23
+ }
24
+ isPrinting = true;
25
+ try {
26
+ return await expoPrint.print(options);
27
+ }
28
+ finally {
29
+ isPrinting = false;
30
+ }
31
+ }
32
+ /** Opens a printer picker; the result feeds `printerUrl` into `printAsync`. iOS only. */
33
+ export async function selectPrinterAsync() {
34
+ if (expoPrint.selectPrinter) {
35
+ return await expoPrint.selectPrinter();
36
+ }
37
+ throw new UnavailabilityError('Print', 'selectPrinterAsync');
38
+ }
39
+ /** Renders HTML to a PDF file in the app's cache directory. */
40
+ export async function printToFileAsync(options = {}) {
41
+ return await expoPrint.printToFileAsync(options);
42
+ }
@@ -0,0 +1,48 @@
1
+ export type IPageMargins = {
2
+ top: number;
3
+ right: number;
4
+ bottom: number;
5
+ left: number;
6
+ };
7
+ export type IPrintOrientation = {
8
+ portrait: string;
9
+ landscape: string;
10
+ };
11
+ export type IPrintOptions = {
12
+ /** PDF file URI — remote, local, or a `data:application/pdf;base64,` URI. iOS + Android. */
13
+ uri?: string;
14
+ /** HTML string to print. iOS + Android. */
15
+ html?: string;
16
+ /** Page width in pixels. Defaults to 612 (US Letter @ 72 PPI). Only with `html`. */
17
+ width?: number;
18
+ /** Page height in pixels. Defaults to 792 (US Letter @ 72 PPI). Only with `html`. */
19
+ height?: number;
20
+ /** Printer URL from `selectPrinterAsync`. iOS only. */
21
+ printerUrl?: string;
22
+ /** Uses UIMarkupTextPrintFormatter instead of WebView — no images. iOS only. */
23
+ useMarkupFormatter?: boolean;
24
+ /** @deprecated Use `useMarkupFormatter` instead. iOS only */
25
+ markupFormatterIOS?: string;
26
+ orientation?: string;
27
+ margins?: IPageMargins;
28
+ };
29
+ export type IPrinter = {
30
+ name: string;
31
+ url: string;
32
+ };
33
+ export type IFilePrintOptions = {
34
+ html?: string;
35
+ useMarkupFormatter?: boolean;
36
+ width?: number;
37
+ height?: number;
38
+ margins?: IPageMargins;
39
+ base64?: boolean;
40
+ /** Android only. Default 100. */
41
+ textZoom?: number;
42
+ };
43
+ export type IFilePrintResult = {
44
+ uri: string;
45
+ numberOfPages: number;
46
+ /** Present only when `base64` was requested; no `data:` prefix. */
47
+ base64?: string;
48
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from '../core';
@@ -0,0 +1,3 @@
1
+ // No per-instance state or event stream to wrap in an injectable service — plain re-export.
2
+ export * from '../core';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,cAAc,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { Orientation, printAsync, printToFileAsync, selectPrinterAsync, } from './print';
2
+ export type { IFilePrintOptions, IFilePrintResult, IPageMargins, IPrintOptions, IPrintOrientation, IPrinter, } from './types';
@@ -0,0 +1,2 @@
1
+ export { Orientation, printAsync, printToFileAsync, selectPrinterAsync, } from './print';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { IFilePrintOptions, IFilePrintResult, IPrintOptions, IPrintOrientation, IPrinter } from './types';
2
+ export type INativePrintModule = {
3
+ Orientation: IPrintOrientation;
4
+ print(options: IPrintOptions): Promise<void>;
5
+ /** iOS only — absent on Android's native module. */
6
+ selectPrinter?(): Promise<IPrinter>;
7
+ printToFileAsync(options: IFilePrintOptions): Promise<IFilePrintResult>;
8
+ };
9
+ export declare const expoPrint: INativePrintModule;
@@ -0,0 +1,4 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ const EXPO_PRINT_MODULE_NAME = 'ExpoPrint';
3
+ export const expoPrint = requireNativeModule(EXPO_PRINT_MODULE_NAME);
4
+ //# sourceMappingURL=native-module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AASxD,MAAM,sBAAsB,GAAG,WAAW,CAAC;AAU3C,MAAM,CAAC,MAAM,SAAS,GAAG,mBAAmB,CAC1C,sBAAsB,CACvB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { IFilePrintOptions, IFilePrintResult, IPrintOptions, IPrinter } from './types';
2
+ export declare const Orientation: import("./types").IPrintOrientation;
3
+ /** Opens the native print window (AirPrint / Android's print framework). */
4
+ export declare function printAsync(options: IPrintOptions): Promise<void>;
5
+ /** Opens a printer picker; the result feeds `printerUrl` into `printAsync`. iOS only. */
6
+ export declare function selectPrinterAsync(): Promise<IPrinter>;
7
+ /** Renders HTML to a PDF file in the app's cache directory. */
8
+ export declare function printToFileAsync(options?: IFilePrintOptions): Promise<IFilePrintResult>;
@@ -0,0 +1,43 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { expoPrint } from './native-module';
3
+ export const Orientation = expoPrint.Orientation;
4
+ let isPrinting = false;
5
+ /** Opens the native print window (AirPrint / Android's print framework). */
6
+ export async function printAsync(options) {
7
+ // The deprecated formatter option stands in for `html` and `uri` on iOS only
8
+ const hasMarkupFormatter = Platform.select({
9
+ ios: Boolean(options.markupFormatterIOS),
10
+ default: false,
11
+ });
12
+ if (!options.uri && !options.html && !hasMarkupFormatter) {
13
+ throw new Error('Must provide either `html` or `uri` to print');
14
+ }
15
+ if (options.uri && options.html) {
16
+ throw new Error('Must provide exactly one of `html` and `uri` but both were specified');
17
+ }
18
+ if (options.markupFormatterIOS !== undefined) {
19
+ console.warn('The markupFormatterIOS option is deprecated. Use useMarkupFormatter instead.');
20
+ }
21
+ if (isPrinting) {
22
+ throw new Error('Another print request is already in progress');
23
+ }
24
+ isPrinting = true;
25
+ try {
26
+ return await expoPrint.print(options);
27
+ }
28
+ finally {
29
+ isPrinting = false;
30
+ }
31
+ }
32
+ /** Opens a printer picker; the result feeds `printerUrl` into `printAsync`. iOS only. */
33
+ export async function selectPrinterAsync() {
34
+ if (expoPrint.selectPrinter) {
35
+ return await expoPrint.selectPrinter();
36
+ }
37
+ throw new UnavailabilityError('Print', 'selectPrinterAsync');
38
+ }
39
+ /** Renders HTML to a PDF file in the app's cache directory. */
40
+ export async function printToFileAsync(options = {}) {
41
+ return await expoPrint.printToFileAsync(options);
42
+ }
43
+ //# sourceMappingURL=print.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print.js","sourceRoot":"","sources":["../../src/core/print.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQ5C,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;AAEjD,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAsB;IACrD,6EAA6E;IAC7E,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC;QACzC,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC;QACxC,OAAO,EAAE,KAAK;KACf,CAAC,CAAC;IACH,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,kBAAkB,KAAK,SAAS,EAAE,CAAC;QAC7C,OAAO,CAAC,IAAI,CACV,8EAA8E,CAC/E,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,UAAU,GAAG,IAAI,CAAC;IAClB,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;YAAS,CAAC;QACT,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,IAAI,SAAS,CAAC,aAAa,EAAE,CAAC;QAC5B,OAAO,MAAM,SAAS,CAAC,aAAa,EAAE,CAAC;IACzC,CAAC;IACD,MAAM,IAAI,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;AAC/D,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAA6B,EAAE;IAE/B,OAAO,MAAM,SAAS,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,48 @@
1
+ export type IPageMargins = {
2
+ top: number;
3
+ right: number;
4
+ bottom: number;
5
+ left: number;
6
+ };
7
+ export type IPrintOrientation = {
8
+ portrait: string;
9
+ landscape: string;
10
+ };
11
+ export type IPrintOptions = {
12
+ /** PDF file URI — remote, local, or a `data:application/pdf;base64,` URI. iOS + Android. */
13
+ uri?: string;
14
+ /** HTML string to print. iOS + Android. */
15
+ html?: string;
16
+ /** Page width in pixels. Defaults to 612 (US Letter @ 72 PPI). Only with `html`. */
17
+ width?: number;
18
+ /** Page height in pixels. Defaults to 792 (US Letter @ 72 PPI). Only with `html`. */
19
+ height?: number;
20
+ /** Printer URL from `selectPrinterAsync`. iOS only. */
21
+ printerUrl?: string;
22
+ /** Uses UIMarkupTextPrintFormatter instead of WebView — no images. iOS only. */
23
+ useMarkupFormatter?: boolean;
24
+ /** @deprecated Use `useMarkupFormatter` instead. iOS only */
25
+ markupFormatterIOS?: string;
26
+ orientation?: string;
27
+ margins?: IPageMargins;
28
+ };
29
+ export type IPrinter = {
30
+ name: string;
31
+ url: string;
32
+ };
33
+ export type IFilePrintOptions = {
34
+ html?: string;
35
+ useMarkupFormatter?: boolean;
36
+ width?: number;
37
+ height?: number;
38
+ margins?: IPageMargins;
39
+ base64?: boolean;
40
+ /** Android only. Default 100. */
41
+ textZoom?: number;
42
+ };
43
+ export type IFilePrintResult = {
44
+ uri: string;
45
+ numberOfPages: number;
46
+ /** Present only when `base64` was requested; no `data:` prefix. */
47
+ base64?: string;
48
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ {
2
+ "android": {
3
+ "gradleProjectName": "expo-print",
4
+ "modules": [
5
+ {
6
+ "importPath": "expo.modules.print.PrintModule",
7
+ "className": "PrintModule",
8
+ "nativeName": "ExpoPrint"
9
+ }
10
+ ]
11
+ }
12
+ }
package/package.json ADDED
@@ -0,0 +1,151 @@
1
+ {
2
+ "name": "@symbiote-native/print",
3
+ "version": "0.1.0",
4
+ "description": "expo-print wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Prints a PDF/HTML document (AirPrint on iOS, the print framework on Android) or renders HTML to a PDF file.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "react-native",
8
+ "symbiote-native",
9
+ "expo-print",
10
+ "react",
11
+ "vue",
12
+ "svelte",
13
+ "solid",
14
+ "angular",
15
+ "print",
16
+ "airprint",
17
+ "pdf"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
22
+ "directory": "packages/print"
23
+ },
24
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/print#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
27
+ },
28
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
29
+ "type": "module",
30
+ "main": "./build/core/index.js",
31
+ "module": "./build/core/index.js",
32
+ "types": "./build/core/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./build/core/index.d.ts",
36
+ "default": "./build/core/index.js"
37
+ },
38
+ "./vue": {
39
+ "types": "./build/core/index.d.ts",
40
+ "default": "./build/core/index.js"
41
+ },
42
+ "./react": {
43
+ "types": "./build/core/index.d.ts",
44
+ "default": "./build/core/index.js"
45
+ },
46
+ "./svelte": {
47
+ "types": "./build/core/index.d.ts",
48
+ "default": "./build/core/index.js"
49
+ },
50
+ "./solid": {
51
+ "types": "./build/core/index.d.ts",
52
+ "default": "./build/core/index.js"
53
+ },
54
+ "./angular": {
55
+ "types": "./build-ngc/angular/index.d.ts",
56
+ "react-native": "./build-ngc/angular/index.js",
57
+ "default": "./src/angular/index.ts"
58
+ }
59
+ },
60
+ "files": [
61
+ "src",
62
+ "build",
63
+ "build-ngc",
64
+ "native-link.json",
65
+ "!src/**/*.test.*",
66
+ "!src/**/*.spec.*",
67
+ "!src/**/*.detox.*"
68
+ ],
69
+ "publishConfig": {
70
+ "access": "public"
71
+ },
72
+ "dependencies": {
73
+ "expo-print": "57.0.2",
74
+ "expo-modules-core": "57.0.20"
75
+ },
76
+ "peerDependencies": {
77
+ "@angular/core": ">=20",
78
+ "@vue/runtime-core": "^3.5.13",
79
+ "react": ">=19.0.0",
80
+ "react-native": ">=0.86",
81
+ "solid-js": ">=1.9.0",
82
+ "svelte": ">=5.56.0",
83
+ "vue": ">=3.5.0",
84
+ "@symbiote-native/angular": "^3.1.3",
85
+ "@symbiote-native/solid": "^3.0.5",
86
+ "@symbiote-native/react": "^3.1.0",
87
+ "@symbiote-native/engine": "^1.4.0",
88
+ "@symbiote-native/svelte": "^3.0.5",
89
+ "@symbiote-native/vue": "^3.1.0"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@symbiote-native/angular": {
93
+ "optional": true
94
+ },
95
+ "@symbiote-native/react": {
96
+ "optional": true
97
+ },
98
+ "@symbiote-native/solid": {
99
+ "optional": true
100
+ },
101
+ "@symbiote-native/svelte": {
102
+ "optional": true
103
+ },
104
+ "@symbiote-native/vue": {
105
+ "optional": true
106
+ },
107
+ "@angular/core": {
108
+ "optional": true
109
+ },
110
+ "@vue/runtime-core": {
111
+ "optional": true
112
+ },
113
+ "react": {
114
+ "optional": true
115
+ },
116
+ "solid-js": {
117
+ "optional": true
118
+ },
119
+ "svelte": {
120
+ "optional": true
121
+ },
122
+ "vue": {
123
+ "optional": true
124
+ }
125
+ },
126
+ "devDependencies": {
127
+ "@angular/compiler": "~22.0.8",
128
+ "@angular/compiler-cli": "~22.0.8",
129
+ "@angular/core": "~22.0.8",
130
+ "@types/node": "^26.0.0",
131
+ "@types/react": "^19.2.0",
132
+ "@vue/runtime-core": "^3.5.13",
133
+ "react": "19.2.3",
134
+ "solid-js": "^1.9.14",
135
+ "svelte": "^5.56.0",
136
+ "typescript": "~6.0.0",
137
+ "@symbiote-native/angular": "3.1.3",
138
+ "@symbiote-native/react": "3.1.0",
139
+ "@symbiote-native/engine": "1.4.0",
140
+ "@symbiote-native/solid": "3.0.5",
141
+ "@symbiote-native/test-utils": "0.4.5",
142
+ "@symbiote-native/vue": "3.1.0",
143
+ "@symbiote-native/svelte": "3.0.5"
144
+ },
145
+ "scripts": {
146
+ "typecheck": "tsc --build",
147
+ "clean": "rm -rf build-ngc",
148
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
149
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
150
+ }
151
+ }
@@ -0,0 +1,2 @@
1
+ // No per-instance state or event stream to wrap in an injectable service — plain re-export.
2
+ export * from '../core';
@@ -0,0 +1,14 @@
1
+ export {
2
+ Orientation,
3
+ printAsync,
4
+ printToFileAsync,
5
+ selectPrinterAsync,
6
+ } from './print';
7
+ export type {
8
+ IFilePrintOptions,
9
+ IFilePrintResult,
10
+ IPageMargins,
11
+ IPrintOptions,
12
+ IPrintOrientation,
13
+ IPrinter,
14
+ } from './types';
@@ -0,0 +1,22 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+ import type {
3
+ IFilePrintOptions,
4
+ IFilePrintResult,
5
+ IPrintOptions,
6
+ IPrintOrientation,
7
+ IPrinter,
8
+ } from './types';
9
+
10
+ const EXPO_PRINT_MODULE_NAME = 'ExpoPrint';
11
+
12
+ export type INativePrintModule = {
13
+ Orientation: IPrintOrientation;
14
+ print(options: IPrintOptions): Promise<void>;
15
+ /** iOS only — absent on Android's native module. */
16
+ selectPrinter?(): Promise<IPrinter>;
17
+ printToFileAsync(options: IFilePrintOptions): Promise<IFilePrintResult>;
18
+ };
19
+
20
+ export const expoPrint = requireNativeModule<INativePrintModule>(
21
+ EXPO_PRINT_MODULE_NAME,
22
+ );
@@ -0,0 +1,59 @@
1
+ import { Platform, UnavailabilityError } from 'expo-modules-core';
2
+ import { expoPrint } from './native-module';
3
+ import type {
4
+ IFilePrintOptions,
5
+ IFilePrintResult,
6
+ IPrintOptions,
7
+ IPrinter,
8
+ } from './types';
9
+
10
+ export const Orientation = expoPrint.Orientation;
11
+
12
+ let isPrinting = false;
13
+
14
+ /** Opens the native print window (AirPrint / Android's print framework). */
15
+ export async function printAsync(options: IPrintOptions): Promise<void> {
16
+ // The deprecated formatter option stands in for `html` and `uri` on iOS only
17
+ const hasMarkupFormatter = Platform.select({
18
+ ios: Boolean(options.markupFormatterIOS),
19
+ default: false,
20
+ });
21
+ if (!options.uri && !options.html && !hasMarkupFormatter) {
22
+ throw new Error('Must provide either `html` or `uri` to print');
23
+ }
24
+ if (options.uri && options.html) {
25
+ throw new Error(
26
+ 'Must provide exactly one of `html` and `uri` but both were specified',
27
+ );
28
+ }
29
+ if (options.markupFormatterIOS !== undefined) {
30
+ console.warn(
31
+ 'The markupFormatterIOS option is deprecated. Use useMarkupFormatter instead.',
32
+ );
33
+ }
34
+ if (isPrinting) {
35
+ throw new Error('Another print request is already in progress');
36
+ }
37
+
38
+ isPrinting = true;
39
+ try {
40
+ return await expoPrint.print(options);
41
+ } finally {
42
+ isPrinting = false;
43
+ }
44
+ }
45
+
46
+ /** Opens a printer picker; the result feeds `printerUrl` into `printAsync`. iOS only. */
47
+ export async function selectPrinterAsync(): Promise<IPrinter> {
48
+ if (expoPrint.selectPrinter) {
49
+ return await expoPrint.selectPrinter();
50
+ }
51
+ throw new UnavailabilityError('Print', 'selectPrinterAsync');
52
+ }
53
+
54
+ /** Renders HTML to a PDF file in the app's cache directory. */
55
+ export async function printToFileAsync(
56
+ options: IFilePrintOptions = {},
57
+ ): Promise<IFilePrintResult> {
58
+ return await expoPrint.printToFileAsync(options);
59
+ }
@@ -0,0 +1,53 @@
1
+ export type IPageMargins = {
2
+ top: number;
3
+ right: number;
4
+ bottom: number;
5
+ left: number;
6
+ };
7
+
8
+ export type IPrintOrientation = {
9
+ portrait: string;
10
+ landscape: string;
11
+ };
12
+
13
+ export type IPrintOptions = {
14
+ /** PDF file URI — remote, local, or a `data:application/pdf;base64,` URI. iOS + Android. */
15
+ uri?: string;
16
+ /** HTML string to print. iOS + Android. */
17
+ html?: string;
18
+ /** Page width in pixels. Defaults to 612 (US Letter @ 72 PPI). Only with `html`. */
19
+ width?: number;
20
+ /** Page height in pixels. Defaults to 792 (US Letter @ 72 PPI). Only with `html`. */
21
+ height?: number;
22
+ /** Printer URL from `selectPrinterAsync`. iOS only. */
23
+ printerUrl?: string;
24
+ /** Uses UIMarkupTextPrintFormatter instead of WebView — no images. iOS only. */
25
+ useMarkupFormatter?: boolean;
26
+ /** @deprecated Use `useMarkupFormatter` instead. iOS only */
27
+ markupFormatterIOS?: string;
28
+ orientation?: string;
29
+ margins?: IPageMargins;
30
+ };
31
+
32
+ export type IPrinter = {
33
+ name: string;
34
+ url: string;
35
+ };
36
+
37
+ export type IFilePrintOptions = {
38
+ html?: string;
39
+ useMarkupFormatter?: boolean;
40
+ width?: number;
41
+ height?: number;
42
+ margins?: IPageMargins;
43
+ base64?: boolean;
44
+ /** Android only. Default 100. */
45
+ textZoom?: number;
46
+ };
47
+
48
+ export type IFilePrintResult = {
49
+ uri: string;
50
+ numberOfPages: number;
51
+ /** Present only when `base64` was requested; no `data:` prefix. */
52
+ base64?: string;
53
+ };