@angadie/chittie-react 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 +24 -0
- package/README.md +104 -0
- package/dist/index.d.mts +132 -0
- package/dist/index.mjs +157 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Asyncdot Engineering
|
|
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
|
+
Portions of this software are vendored from MIT-licensed projects and retain
|
|
16
|
+
their original copyright notices; see VENDOR.md.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @angadie/chittie-react
|
|
2
|
+
|
|
3
|
+
Pure JSX receipt authoring → ESC/POS bytes. **RN-safe**: no `react-dom`, no HTML host elements — components render straight onto the [`chittie-core`](../chittie-core) builder, so the same `<Printer>` tree works on web and React Native.
|
|
4
|
+
|
|
5
|
+
> Most apps install [`@angadie/chittie`](../chittie) (which re-exports this). Install this directly only if you don't want the builder re-export.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @angadie/chittie-react react
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Printer, Text, Row, Line, Cut, render } from '@angadie/chittie-react';
|
|
17
|
+
|
|
18
|
+
const bytes = render(
|
|
19
|
+
<Printer width={48}>
|
|
20
|
+
<Text align="center" bold size={{ width: 2, height: 2 }}>SHOP</Text>
|
|
21
|
+
<Line />
|
|
22
|
+
<Row left="Item" right="Rs. 100" />
|
|
23
|
+
<Cut />
|
|
24
|
+
</Printer>
|
|
25
|
+
);
|
|
26
|
+
// bytes: Uint8Array — hand to any transport
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`render(element, options?)` returns a `Uint8Array`. It is synchronous and pure.
|
|
30
|
+
|
|
31
|
+
## Components
|
|
32
|
+
|
|
33
|
+
| Component | Props | Emits |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `<Printer>` | `width` (columns, default 48) | the root; sets line width |
|
|
36
|
+
| `<Text>` | `align`, `bold`, `underline`, `invert`, `size` ({width,height} multipliers), `inline` | text (or image — see below) + newline |
|
|
37
|
+
| `<Row>` | `left`, `right` | a two-column justified row |
|
|
38
|
+
| `<Line>` | — | a horizontal rule |
|
|
39
|
+
| `<Br>` | `lines` | blank line(s) |
|
|
40
|
+
| `<Cut>` | `partial` | paper cut |
|
|
41
|
+
| `<Cashdraw>` | `device` | cash-drawer kick pulse |
|
|
42
|
+
| `<Barcode>` | `value`, `symbology`, `height` | a barcode |
|
|
43
|
+
| `<QRCode>` | `value`, `size`, `model` | a QR code |
|
|
44
|
+
| `<Image>` | `image` (ImageData), `align`, `dither`, `threshold`, `width`, `height` | a raster image (logo, etc.) |
|
|
45
|
+
|
|
46
|
+
### `<Image>` — logos and bitmaps
|
|
47
|
+
|
|
48
|
+
Pass `ImageData` (from a `<canvas>`, a decoded PNG, or a rasterizer). Dimensions are auto-padded to multiples of 8 (the ESC/POS raster requirement), so any size works. Use `dither="threshold"` for crisp line-art/logos, or `floydsteinberg`/`atkinson` for photos.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
const logo = canvas.getContext('2d')!.getImageData(0, 0, canvas.width, canvas.height);
|
|
52
|
+
render(
|
|
53
|
+
<Printer width={48}>
|
|
54
|
+
<Image image={logo} align="center" dither="threshold" />
|
|
55
|
+
<Text align="center" bold>ARTISAN HAUS</Text>
|
|
56
|
+
</Printer>
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
User-defined wrapper components are supported — `render` walks function components and fragments.
|
|
61
|
+
|
|
62
|
+
## Custom components
|
|
63
|
+
|
|
64
|
+
Compose your own components — they're plain functions returning chittie elements. `render` invokes them and walks their output, so `.map()`, fragments, and conditionals all work:
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
const LineItem = ({ name, qty, price }: Item) => (
|
|
68
|
+
<Row left={`${qty}x ${name}`} right={`Rs. ${qty * price}`} />
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
function Receipt({ items }: { items: Item[] }) {
|
|
72
|
+
return (
|
|
73
|
+
<Printer width={48}>
|
|
74
|
+
<Text align="center" bold>MY SHOP</Text>
|
|
75
|
+
<Line />
|
|
76
|
+
{items.map((it) => <LineItem key={it.id} {...it} />)}
|
|
77
|
+
{items.length === 0 && <Text align="center">No items</Text>}
|
|
78
|
+
<Cut />
|
|
79
|
+
</Printer>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// a whole receipt can be ONE component — render resolves it down to <Printer>
|
|
84
|
+
render(<Receipt items={cart} />);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Constraints** (it's a pure renderer, not the React reconciler):
|
|
88
|
+
- Components must be pure `props → elements`. **No hooks** (`useState`/`useEffect`/`useContext`) and no `react-dom` — there's no component tree, state, or lifecycle, by design (a receipt is a one-shot render).
|
|
89
|
+
- `<Text>` accepts only **text** (strings/numbers). Nesting a component (`<Text><Row/></Text>`) **throws a clear error** — put `<Row>`, `<Image>`, etc. as siblings. (Fragments wrapping text are fine.)
|
|
90
|
+
|
|
91
|
+
## Non-Latin scripts (Sinhala / Tamil / …)
|
|
92
|
+
|
|
93
|
+
`<Text>` content that a code page can't represent is auto-rasterized **when you pass a rasterizer**; otherwise `render` throws (never a silent `?`). See [`@angadie/chittie-text`](../chittie-text).
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
render(<Printer><Text>ආයුබෝවන්</Text></Printer>, {
|
|
97
|
+
rasterizer: myRasterizer, // TextRasterizer
|
|
98
|
+
codepage: 'cp437', // what counts as "encodable as text"
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { Codepage, Codepage as Codepage$1, RasterOptions, TextRasterizer, TextRasterizer as TextRasterizer$1 } from "@angadie/chittie-text";
|
|
3
|
+
import ReceiptPrinterEncoder, { BarcodeSymbology, DitherAlgorithm } from "@angadie/chittie-core";
|
|
4
|
+
|
|
5
|
+
//#region src/components.d.ts
|
|
6
|
+
type Alignment = 'left' | 'center' | 'right';
|
|
7
|
+
/** Character magnification (width/height multipliers, e.g. 2 = double). */
|
|
8
|
+
type TextScale = {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Pure recursive text extraction — no react-dom, no DOM. Strings/numbers pass
|
|
14
|
+
* through; fragments are transparent. A component element is a mistake (it would
|
|
15
|
+
* be silently dropped — its print() never runs from here), so we throw loudly.
|
|
16
|
+
*/
|
|
17
|
+
declare function toText(node: ReactNode): string;
|
|
18
|
+
interface PrinterProps {
|
|
19
|
+
/** Printer model passed to the encoder, e.g. 'epson'. */
|
|
20
|
+
type?: string;
|
|
21
|
+
/** Characters per line (default 48 / 80mm). */
|
|
22
|
+
width?: number;
|
|
23
|
+
children?: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/** Root element — render() reads its props; it has no print() of its own. */
|
|
26
|
+
declare const Printer: (_props: PrinterProps) => null;
|
|
27
|
+
interface TextProps {
|
|
28
|
+
align?: Alignment;
|
|
29
|
+
bold?: boolean;
|
|
30
|
+
underline?: boolean | number;
|
|
31
|
+
invert?: boolean;
|
|
32
|
+
size?: TextScale;
|
|
33
|
+
/** If true, don't append a newline after the text. */
|
|
34
|
+
inline?: boolean;
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
declare const Text: (_props: TextProps) => null;
|
|
38
|
+
interface RowProps {
|
|
39
|
+
left?: ReactNode;
|
|
40
|
+
right?: ReactNode;
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
declare const Row: (_props: RowProps) => null;
|
|
44
|
+
declare const Line: (_props: {
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
}) => null;
|
|
47
|
+
interface BrProps {
|
|
48
|
+
lines?: number;
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
declare const Br: (_props: BrProps) => null;
|
|
52
|
+
interface CutProps {
|
|
53
|
+
partial?: boolean;
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
}
|
|
56
|
+
declare const Cut: (_props: CutProps) => null;
|
|
57
|
+
interface CashdrawProps {
|
|
58
|
+
device?: number;
|
|
59
|
+
children?: ReactNode;
|
|
60
|
+
}
|
|
61
|
+
declare const Cashdraw: (_props: CashdrawProps) => null;
|
|
62
|
+
interface BarcodeProps {
|
|
63
|
+
value: string;
|
|
64
|
+
symbology?: BarcodeSymbology | number;
|
|
65
|
+
height?: number;
|
|
66
|
+
children?: ReactNode;
|
|
67
|
+
}
|
|
68
|
+
declare const Barcode: (_props: BarcodeProps) => null;
|
|
69
|
+
interface QRCodeProps {
|
|
70
|
+
value: string;
|
|
71
|
+
size?: number;
|
|
72
|
+
model?: number;
|
|
73
|
+
children?: ReactNode;
|
|
74
|
+
}
|
|
75
|
+
declare const QRCode: (_props: QRCodeProps) => null;
|
|
76
|
+
interface ImageProps {
|
|
77
|
+
/** Pixel data (from a <canvas>, decoded PNG, or a rasterizer). */
|
|
78
|
+
image: ImageData;
|
|
79
|
+
/** Output width/height in dots; default the image's own (padded to /8). */
|
|
80
|
+
width?: number;
|
|
81
|
+
height?: number;
|
|
82
|
+
align?: Alignment;
|
|
83
|
+
/** Dithering: 'threshold' (crisp line-art/logos) | 'bayer' | 'floydsteinberg' | 'atkinson' (photos). */
|
|
84
|
+
dither?: DitherAlgorithm;
|
|
85
|
+
threshold?: number;
|
|
86
|
+
children?: ReactNode;
|
|
87
|
+
}
|
|
88
|
+
declare const Image: (_props: ImageProps) => null;
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region src/render.d.ts
|
|
91
|
+
interface RenderOptions {
|
|
92
|
+
/** Characters per line; overridden by <Printer width>. */
|
|
93
|
+
columns?: number;
|
|
94
|
+
/** Printable width in dots; defaults to columns × 12 (203 DPI, font A). */
|
|
95
|
+
dotWidth?: number;
|
|
96
|
+
/** Supply to print non-encodable scripts (Sinhala/Tamil/…) as images. */
|
|
97
|
+
rasterizer?: TextRasterizer$1;
|
|
98
|
+
/** Code page used to decide what's encodable as text (default cp437). */
|
|
99
|
+
codepage?: Codepage$1;
|
|
100
|
+
}
|
|
101
|
+
/** Common thermal printer profiles: characters/line + printable dot width (203 DPI). */
|
|
102
|
+
declare const PRINTER_PROFILES: {
|
|
103
|
+
readonly '58mm': {
|
|
104
|
+
readonly columns: 32;
|
|
105
|
+
readonly dotWidth: 384;
|
|
106
|
+
};
|
|
107
|
+
readonly '80mm': {
|
|
108
|
+
readonly columns: 48;
|
|
109
|
+
readonly dotWidth: 576;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Render a <Printer> element tree to ESC/POS bytes by driving the vendored
|
|
114
|
+
* builder. Pure: no react-dom, no DOM host elements.
|
|
115
|
+
*/
|
|
116
|
+
declare function render(element: ReactElement, options?: RenderOptions): Uint8Array;
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/printable.d.ts
|
|
119
|
+
/** The vendored builder instance the components drive. */
|
|
120
|
+
type Encoder = InstanceType<typeof ReceiptPrinterEncoder>;
|
|
121
|
+
/** Render-time context threaded to every component's print(). */
|
|
122
|
+
interface RenderContext {
|
|
123
|
+
columns: number;
|
|
124
|
+
/** Printable width in dots (e.g. 384 for 58mm, 576 for 80mm) — used when rasterizing. */
|
|
125
|
+
dotWidth: number;
|
|
126
|
+
/** When set, <Text>/<Row> with non-encodable scripts (Sinhala/Tamil/…) is rasterized. */
|
|
127
|
+
rasterizer?: TextRasterizer$1;
|
|
128
|
+
/** Code page used to decide what's encodable as text (default cp437). */
|
|
129
|
+
codepage?: Codepage$1;
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
export { type Alignment, Barcode, type BarcodeProps, Br, type BrProps, Cashdraw, type CashdrawProps, type Codepage, Cut, type CutProps, type Encoder, Image, type ImageProps, Line, PRINTER_PROFILES, Printer, type PrinterProps, QRCode, type QRCodeProps, type RasterOptions, type RenderContext, type RenderOptions, Row, type RowProps, Text, type TextProps, type TextRasterizer, type TextScale, render, toText };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Children, Fragment, isValidElement } from "react";
|
|
2
|
+
import { needsRaster, padTo8, rasterizeRow, smartText } from "@angadie/chittie-text";
|
|
3
|
+
import ReceiptPrinterEncoder from "@angadie/chittie-core";
|
|
4
|
+
//#region src/components.ts
|
|
5
|
+
/**
|
|
6
|
+
* Pure recursive text extraction — no react-dom, no DOM. Strings/numbers pass
|
|
7
|
+
* through; fragments are transparent. A component element is a mistake (it would
|
|
8
|
+
* be silently dropped — its print() never runs from here), so we throw loudly.
|
|
9
|
+
*/
|
|
10
|
+
function toText(node) {
|
|
11
|
+
if (node == null || typeof node === "boolean") return "";
|
|
12
|
+
if (typeof node === "string") return node;
|
|
13
|
+
if (typeof node === "number" || typeof node === "bigint") return String(node);
|
|
14
|
+
if (Array.isArray(node)) return node.map(toText).join("");
|
|
15
|
+
if (isValidElement(node)) {
|
|
16
|
+
if (node.type === Fragment) return toText(node.props.children);
|
|
17
|
+
throw new Error("chittie: <Text> (and <Row>) accept only string/number text, not components. Put <Row>, <Image>, etc. as siblings — not nested inside <Text>.");
|
|
18
|
+
}
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
const printable = (fn) => {
|
|
22
|
+
const comp = (_props) => null;
|
|
23
|
+
comp.print = fn;
|
|
24
|
+
return comp;
|
|
25
|
+
};
|
|
26
|
+
/** Root element — render() reads its props; it has no print() of its own. */
|
|
27
|
+
const Printer = (_props) => null;
|
|
28
|
+
const Text = printable((e, p, ctx) => {
|
|
29
|
+
if (p.align) e.align(p.align);
|
|
30
|
+
if (p.bold) e.bold(true);
|
|
31
|
+
if (p.underline) e.underline(p.underline);
|
|
32
|
+
if (p.invert) e.invert(true);
|
|
33
|
+
if (p.size) e.size(p.size.width, p.size.height);
|
|
34
|
+
smartText(e, toText(p.children), {
|
|
35
|
+
rasterizer: ctx.rasterizer,
|
|
36
|
+
codepage: ctx.codepage,
|
|
37
|
+
raster: {
|
|
38
|
+
bold: p.bold,
|
|
39
|
+
fontSize: p.size ? p.size.height * 24 : void 0,
|
|
40
|
+
maxWidth: ctx.dotWidth
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
if (!p.inline) e.newline();
|
|
44
|
+
if (p.size) e.size(1, 1);
|
|
45
|
+
if (p.invert) e.invert(false);
|
|
46
|
+
if (p.underline) e.underline(false);
|
|
47
|
+
if (p.bold) e.bold(false);
|
|
48
|
+
if (p.align) e.align("left");
|
|
49
|
+
});
|
|
50
|
+
const Row = printable((e, p, ctx) => {
|
|
51
|
+
const left = toText(p.left);
|
|
52
|
+
const right = toText(p.right);
|
|
53
|
+
if (needsRaster(left, ctx.codepage) || needsRaster(right, ctx.codepage)) {
|
|
54
|
+
if (!ctx.rasterizer) throw new Error("chittie: <Row> contains non-encodable text (e.g. Sinhala/Tamil). Pass a rasterizer to render(), or use code-page text.");
|
|
55
|
+
const img = rasterizeRow(ctx.rasterizer, left, right, { dotWidth: ctx.dotWidth });
|
|
56
|
+
e.image(img, img.width, img.height);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const rightW = Math.min(right.length, ctx.columns);
|
|
60
|
+
const leftW = Math.max(0, ctx.columns - rightW);
|
|
61
|
+
e.table([{
|
|
62
|
+
width: leftW,
|
|
63
|
+
align: "left"
|
|
64
|
+
}, {
|
|
65
|
+
width: rightW,
|
|
66
|
+
align: "right"
|
|
67
|
+
}], [[left, right]]);
|
|
68
|
+
});
|
|
69
|
+
const Line = printable((e) => {
|
|
70
|
+
e.rule();
|
|
71
|
+
});
|
|
72
|
+
const Br = printable((e, p) => {
|
|
73
|
+
e.newline(p.lines ?? 1);
|
|
74
|
+
});
|
|
75
|
+
const Cut = printable((e, p) => {
|
|
76
|
+
e.cut(p.partial ? "partial" : "full");
|
|
77
|
+
});
|
|
78
|
+
const Cashdraw = printable((e, p) => {
|
|
79
|
+
e.pulse(p.device);
|
|
80
|
+
});
|
|
81
|
+
const Barcode = printable((e, p) => {
|
|
82
|
+
e.barcode(p.value, p.symbology ?? "code128", p.height);
|
|
83
|
+
});
|
|
84
|
+
const QRCode = printable((e, p) => {
|
|
85
|
+
e.qrcode(p.value, p.model, p.size);
|
|
86
|
+
});
|
|
87
|
+
const Image = printable((e, p) => {
|
|
88
|
+
const img = padTo8(p.image);
|
|
89
|
+
if (p.align) e.align(p.align);
|
|
90
|
+
e.image(img, p.width ?? img.width, p.height ?? img.height, p.dither, p.threshold);
|
|
91
|
+
if (p.align) e.align("left");
|
|
92
|
+
});
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/printable.ts
|
|
95
|
+
function isPrintable(type) {
|
|
96
|
+
return (typeof type === "function" || typeof type === "object") && type !== null && typeof type.print === "function";
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/render.ts
|
|
100
|
+
/**
|
|
101
|
+
* Resolve a custom function-component root down to the <Printer> element it
|
|
102
|
+
* returns, so a whole receipt can be authored as one component:
|
|
103
|
+
* `render(<MyReceipt items={…} />)`. Stops at <Printer> or any printable.
|
|
104
|
+
*/
|
|
105
|
+
function resolveRoot(element) {
|
|
106
|
+
let current = element;
|
|
107
|
+
for (let i = 0; i < 100; i++) {
|
|
108
|
+
if (!isValidElement(current)) break;
|
|
109
|
+
const type = current.type;
|
|
110
|
+
if (type === Printer || isPrintable(type) || typeof type !== "function") break;
|
|
111
|
+
current = type(current.props);
|
|
112
|
+
}
|
|
113
|
+
return isValidElement(current) ? current : element;
|
|
114
|
+
}
|
|
115
|
+
/** Common thermal printer profiles: characters/line + printable dot width (203 DPI). */
|
|
116
|
+
const PRINTER_PROFILES = {
|
|
117
|
+
"58mm": {
|
|
118
|
+
columns: 32,
|
|
119
|
+
dotWidth: 384
|
|
120
|
+
},
|
|
121
|
+
"80mm": {
|
|
122
|
+
columns: 48,
|
|
123
|
+
dotWidth: 576
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Render a <Printer> element tree to ESC/POS bytes by driving the vendored
|
|
128
|
+
* builder. Pure: no react-dom, no DOM host elements.
|
|
129
|
+
*/
|
|
130
|
+
function render(element, options = {}) {
|
|
131
|
+
const props = resolveRoot(element).props ?? {};
|
|
132
|
+
const columns = props.width ?? options.columns ?? 48;
|
|
133
|
+
const dotWidth = options.dotWidth ?? columns * 12;
|
|
134
|
+
const encoder = new ReceiptPrinterEncoder({ columns });
|
|
135
|
+
encoder.initialize();
|
|
136
|
+
walk(props.children, encoder, {
|
|
137
|
+
columns,
|
|
138
|
+
dotWidth,
|
|
139
|
+
rasterizer: options.rasterizer,
|
|
140
|
+
codepage: options.codepage
|
|
141
|
+
});
|
|
142
|
+
return encoder.encode();
|
|
143
|
+
}
|
|
144
|
+
function walk(node, encoder, ctx) {
|
|
145
|
+
for (const child of Children.toArray(node)) {
|
|
146
|
+
if (!isValidElement(child)) continue;
|
|
147
|
+
const type = child.type;
|
|
148
|
+
if (isPrintable(type)) {
|
|
149
|
+
type.print(encoder, child.props ?? {}, ctx);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (typeof type === "function") walk(type(child.props), encoder, ctx);
|
|
153
|
+
else walk(child.props.children, encoder, ctx);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { Barcode, Br, Cashdraw, Cut, Image, Line, PRINTER_PROFILES, Printer, QRCode, Row, Text, render, toText };
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@angadie/chittie-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pure JSX receipt authoring → ESC/POS bytes. RN-safe (no react-dom, no HTML host elements). Renders onto chittie-core.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
|
+
"import": "./dist/index.mjs"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@angadie/chittie-core": "0.1.0",
|
|
19
|
+
"@angadie/chittie-text": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": "^18 || ^19"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@canvas/image-data": "^1.1.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsdown",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "tsx spikes/jsx.spike.tsx"
|
|
34
|
+
}
|
|
35
|
+
}
|