@ai-matrx/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.
@@ -0,0 +1,301 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * @ai-matrx/print/core — the Block Print contract & print-window composition.
5
+ *
6
+ * Ported VERBATIM from matrx-frontend `lib/block-print/block-print-utils.ts`
7
+ * (2026-08-29 state, including the `number` and `select` PrintSetting types the
8
+ * commerce label lane added). Zero dependencies; touches `window` only at call
9
+ * time, so the module stays import-inert and RSC-safe.
10
+ *
11
+ * BlockPrinter is the only interface a host system needs to know about.
12
+ * Each printable unit exports its own BlockPrinter alongside its render
13
+ * component; host code only ever calls print(data, variantId, settings).
14
+ *
15
+ * `buildFitTextScript` is the ONE auto-shrink script (the flashcards
16
+ * FIT_TEXT pattern, parameterized by selector so the label lane shares it).
17
+ */
18
+ interface PrintVariant {
19
+ id: string;
20
+ label: string;
21
+ description?: string;
22
+ }
23
+ /**
24
+ * A single user-configurable setting shown in the print dialog.
25
+ * Extend the discriminated union for new control types
26
+ * without breaking existing printers.
27
+ */
28
+ type PrintSetting = {
29
+ type: "boolean";
30
+ id: string;
31
+ label: string;
32
+ description?: string;
33
+ /** Default value shown when the dialog first opens */
34
+ defaultValue: boolean;
35
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
36
+ appliesTo?: string[];
37
+ } | {
38
+ /**
39
+ * A numeric "from → to" range control, rendered as two small number inputs.
40
+ * Writes two entries into the resolved PrintSettings map — one under fromId,
41
+ * one under toId. Printers interpret 0 (or missing) as "no bound" by convention.
42
+ */
43
+ type: "range";
44
+ /** Stable id used as React key — not written to the settings map */
45
+ id: string;
46
+ label: string;
47
+ description?: string;
48
+ /** Settings map key for the lower bound value */
49
+ fromId: string;
50
+ /** Settings map key for the upper bound value */
51
+ toId: string;
52
+ defaultFrom: number;
53
+ defaultTo: number;
54
+ /** Minimum accepted value for each input (inclusive). Default 0. */
55
+ min?: number;
56
+ /** Placeholder shown when the input is empty (0). Default "start" / "end". */
57
+ fromPlaceholder?: string;
58
+ toPlaceholder?: string;
59
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
60
+ appliesTo?: string[];
61
+ } | {
62
+ /** A single numeric input (e.g. "start at label #", "copies"). */
63
+ type: "number";
64
+ id: string;
65
+ label: string;
66
+ description?: string;
67
+ defaultValue: number;
68
+ /** Minimum accepted value (inclusive). Default 0. */
69
+ min?: number;
70
+ /** Maximum accepted value (inclusive). Omit = unbounded. */
71
+ max?: number;
72
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
73
+ appliesTo?: string[];
74
+ } | {
75
+ /** A one-of-N choice rendered as a compact segmented row. */
76
+ type: "select";
77
+ id: string;
78
+ label: string;
79
+ description?: string;
80
+ options: {
81
+ value: string;
82
+ label: string;
83
+ }[];
84
+ defaultValue: string;
85
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
86
+ appliesTo?: string[];
87
+ };
88
+ /** A resolved map of setting id → current value, passed to print(). */
89
+ type PrintSettings = Record<string, boolean | string | number>;
90
+ /**
91
+ * The self-contained print capability that each block can export.
92
+ * Host code only ever calls print(data, variantId, settings) — it never inspects internals.
93
+ */
94
+ interface BlockPrinter {
95
+ /** Tooltip / aria-label for the print button in the block header */
96
+ label: string;
97
+ /**
98
+ * Variants offered to the user before printing.
99
+ * Empty array = no options dialog shown; prints immediately with defaults.
100
+ */
101
+ variants: PrintVariant[];
102
+ /**
103
+ * Optional user-configurable settings shown below the variant picker.
104
+ * Omit entirely if the block has no configurable settings.
105
+ */
106
+ settings?: PrintSetting[];
107
+ /**
108
+ * Execute the print.
109
+ * data — the block's structured data (serverData or parsed content)
110
+ * variantId — which variant was chosen (undefined = default / no variants)
111
+ * settings — resolved setting values chosen by the user
112
+ */
113
+ print: (data: unknown, variantId?: string, settings?: PrintSettings) => void | Promise<void>;
114
+ }
115
+
116
+ /**
117
+ * @ai-matrx/print/labels — the label-sheet engine.
118
+ *
119
+ * ONE module carrying the three matrx-frontend files this lane was extracted
120
+ * from, ported behaviour-verbatim (2026-08-29 state):
121
+ * lib/label-print/label-templates.ts → the template registry
122
+ * lib/label-print/qr-labels-printer.ts → qrLabelsPrinter / printQrLabelSheet
123
+ * lib/label-print/qr-labels-pdf.ts → downloadLabelsPdf
124
+ *
125
+ * Warehouse-grade QR labels on standard 8.5" × 11" label stock (Avery and
126
+ * compatibles) AND on continuous/die-cut roll stock. ONE generic grid renderer
127
+ * driven by the LabelTemplate registry — geometry is data, never per-stock CSS.
128
+ *
129
+ * Fidelity rules (the reasons this exists):
130
+ * - The print window is a fresh, unauthenticated document: every QR image is
131
+ * inlined as a data URI, never fetched.
132
+ * - Spec-compliant quiet zone: the `qrcode` lib renders 4 modules of white
133
+ * margin INSIDE each data URI (its default; we pass it explicitly).
134
+ * - `print-color-adjust: exact` on code cells so the browser never "helps".
135
+ * - Modules are crisp: each QR is rasterized at 300 dpi for its printed size,
136
+ * and `image-rendering: pixelated` stops resampling blur.
137
+ * - Captions auto-shrink to fit their box (FIT_TEXT pattern) — they never
138
+ * overflow onto the neighboring label.
139
+ * - `startAtLabel` inserts LEADING blank cells so a partially-used sheet can
140
+ * be reused; trailing cells pad out the last page.
141
+ *
142
+ * THE IMAGE RULE — the one deliberate exception to "data URIs only": a label's
143
+ * optional `imageUrl` is a DURABLE http(s) URL and is emitted as a plain
144
+ * `<img src>`. Anything that is not http(s) is SKIPPED rather than rendered.
145
+ * The QR itself is never a URL; it is always an inlined data URI.
146
+ *
147
+ * SEAM INVERSIONS versus the matrx-frontend originals (the only changes to the
148
+ * ported behaviour):
149
+ * 1. `@/lib/block-print/block-print-utils` → `./core` (identical export names).
150
+ * 2. The deferred `import("qrcode")` + `QRCode.toDataURL(...)` call is now
151
+ * `generateQrDataUrl` from `./qr`, which performs the same lazy import at
152
+ * call time and maps 1:1 — errorCorrectionLevel → ecLevel, margin →
153
+ * quietZoneModules, width → widthPx. The original also passed
154
+ * `color: { dark: "#000000", light: "#ffffff" }`, which is byte-for-byte the
155
+ * `qrcode` default, so dropping it changes no output.
156
+ * 3. LP_FIT_SCRIPT is KEPT VERBATIM rather than replaced by core's
157
+ * `buildFitTextScript`. The two scripts are NOT behaviourally identical for
158
+ * label cells: core's steps and writes font sizes in **pt** (and pre-assigns
159
+ * `size + "pt"` from a px-derived number before measuring) with a **6pt**
160
+ * floor, while the label script steps and writes **px** with a **4px** floor
161
+ * and no pre-assignment. On a 9pt caption inside a 0.28in box those two
162
+ * produce different final sizes, so swapping them would regress captions on
163
+ * the small Avery stocks.
164
+ *
165
+ * Docs: lib/label-print/FEATURE.md (matrx-frontend), NOTES-CHANGELOG.md.
166
+ */
167
+
168
+ /**
169
+ * Label-sheet template registry — the geometry source of truth for QR label
170
+ * printing on standard 8.5" × 11" label stock (Avery and compatibles) and on
171
+ * roll stock.
172
+ *
173
+ * ONE generic grid renderer is driven entirely by these numbers — never a
174
+ * hand-written CSS block per stock. All dimensions in inches, taken from
175
+ * Avery's published templates:
176
+ * - horizontal pitch = labelWIn + gutterXIn; vertical pitch = labelHIn + gutterYIn
177
+ * - a template is valid when marginLeft*2 + cols*labelW + (cols-1)*gutterX = sheetW
178
+ * (same vertically) — `assertTemplateGeometry` checks this in dev.
179
+ */
180
+ interface LabelTemplate {
181
+ id: string;
182
+ name: string;
183
+ /** Stock code as printed on the box (e.g. "Avery 5160"). */
184
+ stockCode: string;
185
+ sheetWIn: number;
186
+ sheetHIn: number;
187
+ marginTopIn: number;
188
+ marginLeftIn: number;
189
+ labelWIn: number;
190
+ labelHIn: number;
191
+ cols: number;
192
+ rows: number;
193
+ /** Horizontal gap BETWEEN columns (not the outer margin). */
194
+ gutterXIn: number;
195
+ /** Vertical gap BETWEEN rows. */
196
+ gutterYIn: number;
197
+ /** Corner radius; equal to labelWIn/2 for round labels. */
198
+ cornerRadiusIn?: number;
199
+ /** True round label (die-cut circle) — preview/calibration draw a circle. */
200
+ round?: boolean;
201
+ /**
202
+ * Stock family. Absent means "sheet" — a multi-up page of labels. "roll" is
203
+ * a continuous/die-cut roll where the sheet IS the label: margins 0, a 1×1
204
+ * grid, and `@page` sized to the single label.
205
+ */
206
+ kind?: "sheet" | "roll";
207
+ }
208
+ interface QrLabel {
209
+ /** The value encoded in the QR code (SKU, URL, id…). */
210
+ qrValue: string;
211
+ /** Bold headline under/next to the code. Defaults to qrValue when captions are on. */
212
+ caption?: string;
213
+ /** Additional small text lines (wide labels only — there is no room on tiny stock). */
214
+ lines?: string[];
215
+ /**
216
+ * A single short, LARGE, bold token (a garment size, a bin letter, a grade).
217
+ * Rendered in the caption zone ABOVE the caption at ~2× the caption size and
218
+ * auto-shrunk by the fit script.
219
+ */
220
+ badge?: string;
221
+ /**
222
+ * Durable http(s) image URL rendered in the lines zone with
223
+ * `object-fit: contain`. Anything that is not http(s) is skipped — THE IMAGE
224
+ * RULE. Ignored by the PDF lane, which cannot fetch a remote image.
225
+ */
226
+ imageUrl?: string;
227
+ }
228
+ type QrEcLevel = "L" | "M" | "Q";
229
+
230
+ /**
231
+ * @ai-matrx/print/react — the package's ONLY React entry.
232
+ *
233
+ * Two components, ported verbatim from matrx-frontend:
234
+ * PrintOptionsDialog ← lib/block-print/PrintOptionsDialog.tsx
235
+ * LabelSheetPreview ← lib/label-print/LabelSheetPreview.tsx
236
+ *
237
+ * The app seams are inverted here and NOWHERE else:
238
+ * @/components/ui/dialog → inline minimal wrappers over @radix-ui/react-dialog
239
+ * @/components/ui/drawer → inline minimal wrappers over vaul
240
+ * @/components/ui/button → PrintDialogButton (the design-system Button's
241
+ * exact class strings for the used variants)
242
+ * @/hooks/use-mobile → useIsMobile, reproduced behavior-for-behavior
243
+ * lucide-react → inline SVGs carrying lucide's own path data
244
+ * @/lib/block-print/* → ./core
245
+ * ./label-templates, ./qr-labels-printer → ./labels
246
+ *
247
+ * NO "use client" directive lives in this file — tsup stamps this entry via a
248
+ * banner (see tsup.config.ts); a second literal directive here would be
249
+ * duplicated into dist.
250
+ *
251
+ * Tailwind class strings are the originals verbatim: the consuming app's theme
252
+ * tokens (bg-primary, text-muted-foreground, pb-safe…) style these components,
253
+ * exactly as they did in matrx-frontend.
254
+ */
255
+
256
+ interface PrintOptionsDialogProps {
257
+ printer: BlockPrinter;
258
+ data: unknown;
259
+ open: boolean;
260
+ onOpenChange: (open: boolean) => void;
261
+ }
262
+ /**
263
+ * Shared pre-print options UI — shown when a BlockPrinter has variants or settings.
264
+ * Desktop: Dialog. Mobile: Drawer (bottom sheet).
265
+ */
266
+ declare function PrintOptionsDialog({ printer, data, open, onOpenChange, }: PrintOptionsDialogProps): React.JSX.Element;
267
+ /**
268
+ * Manages open state for PrintOptionsDialog.
269
+ * If the printer has variants or settings, shows the dialog.
270
+ * If it has neither, calls printer.print() immediately with no settings.
271
+ */
272
+ declare function usePrintOptions(printer: BlockPrinter | null, data: unknown): {
273
+ open: boolean;
274
+ setOpen: React.Dispatch<React.SetStateAction<boolean>>;
275
+ triggerPrint: () => Promise<void>;
276
+ };
277
+ /**
278
+ * LabelSheetPreview — scaled on-screen preview of one label sheet, driven by
279
+ * the same LabelTemplate geometry as the printer (percent-positioned cells,
280
+ * so it is exactly proportional at any width). Also renders a calibration
281
+ * view (outlines only) so a user can eyeball the grid before printing.
282
+ *
283
+ * QR images are generated client-side to data URIs via `qrcode` (deferred
284
+ * import at effect time).
285
+ */
286
+ interface LabelSheetPreviewProps {
287
+ template: LabelTemplate;
288
+ labels: QrLabel[];
289
+ /** 1-based first cell to fill — leading cells render empty. */
290
+ startAtLabel?: number;
291
+ showCaption?: boolean;
292
+ ecLevel?: QrEcLevel;
293
+ /** Outlines-only calibration view. */
294
+ calibration?: boolean;
295
+ /** Which sheet to show (0-based). */
296
+ pageIndex?: number;
297
+ className?: string;
298
+ }
299
+ declare function LabelSheetPreview({ template: t, labels, startAtLabel, showCaption, ecLevel, calibration, pageIndex, className, }: LabelSheetPreviewProps): React.JSX.Element;
300
+
301
+ export { LabelSheetPreview, type LabelSheetPreviewProps, PrintOptionsDialog, type PrintOptionsDialogProps, usePrintOptions };
@@ -0,0 +1,301 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * @ai-matrx/print/core — the Block Print contract & print-window composition.
5
+ *
6
+ * Ported VERBATIM from matrx-frontend `lib/block-print/block-print-utils.ts`
7
+ * (2026-08-29 state, including the `number` and `select` PrintSetting types the
8
+ * commerce label lane added). Zero dependencies; touches `window` only at call
9
+ * time, so the module stays import-inert and RSC-safe.
10
+ *
11
+ * BlockPrinter is the only interface a host system needs to know about.
12
+ * Each printable unit exports its own BlockPrinter alongside its render
13
+ * component; host code only ever calls print(data, variantId, settings).
14
+ *
15
+ * `buildFitTextScript` is the ONE auto-shrink script (the flashcards
16
+ * FIT_TEXT pattern, parameterized by selector so the label lane shares it).
17
+ */
18
+ interface PrintVariant {
19
+ id: string;
20
+ label: string;
21
+ description?: string;
22
+ }
23
+ /**
24
+ * A single user-configurable setting shown in the print dialog.
25
+ * Extend the discriminated union for new control types
26
+ * without breaking existing printers.
27
+ */
28
+ type PrintSetting = {
29
+ type: "boolean";
30
+ id: string;
31
+ label: string;
32
+ description?: string;
33
+ /** Default value shown when the dialog first opens */
34
+ defaultValue: boolean;
35
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
36
+ appliesTo?: string[];
37
+ } | {
38
+ /**
39
+ * A numeric "from → to" range control, rendered as two small number inputs.
40
+ * Writes two entries into the resolved PrintSettings map — one under fromId,
41
+ * one under toId. Printers interpret 0 (or missing) as "no bound" by convention.
42
+ */
43
+ type: "range";
44
+ /** Stable id used as React key — not written to the settings map */
45
+ id: string;
46
+ label: string;
47
+ description?: string;
48
+ /** Settings map key for the lower bound value */
49
+ fromId: string;
50
+ /** Settings map key for the upper bound value */
51
+ toId: string;
52
+ defaultFrom: number;
53
+ defaultTo: number;
54
+ /** Minimum accepted value for each input (inclusive). Default 0. */
55
+ min?: number;
56
+ /** Placeholder shown when the input is empty (0). Default "start" / "end". */
57
+ fromPlaceholder?: string;
58
+ toPlaceholder?: string;
59
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
60
+ appliesTo?: string[];
61
+ } | {
62
+ /** A single numeric input (e.g. "start at label #", "copies"). */
63
+ type: "number";
64
+ id: string;
65
+ label: string;
66
+ description?: string;
67
+ defaultValue: number;
68
+ /** Minimum accepted value (inclusive). Default 0. */
69
+ min?: number;
70
+ /** Maximum accepted value (inclusive). Omit = unbounded. */
71
+ max?: number;
72
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
73
+ appliesTo?: string[];
74
+ } | {
75
+ /** A one-of-N choice rendered as a compact segmented row. */
76
+ type: "select";
77
+ id: string;
78
+ label: string;
79
+ description?: string;
80
+ options: {
81
+ value: string;
82
+ label: string;
83
+ }[];
84
+ defaultValue: string;
85
+ /** Which variant IDs this setting applies to. Omit = applies to all. */
86
+ appliesTo?: string[];
87
+ };
88
+ /** A resolved map of setting id → current value, passed to print(). */
89
+ type PrintSettings = Record<string, boolean | string | number>;
90
+ /**
91
+ * The self-contained print capability that each block can export.
92
+ * Host code only ever calls print(data, variantId, settings) — it never inspects internals.
93
+ */
94
+ interface BlockPrinter {
95
+ /** Tooltip / aria-label for the print button in the block header */
96
+ label: string;
97
+ /**
98
+ * Variants offered to the user before printing.
99
+ * Empty array = no options dialog shown; prints immediately with defaults.
100
+ */
101
+ variants: PrintVariant[];
102
+ /**
103
+ * Optional user-configurable settings shown below the variant picker.
104
+ * Omit entirely if the block has no configurable settings.
105
+ */
106
+ settings?: PrintSetting[];
107
+ /**
108
+ * Execute the print.
109
+ * data — the block's structured data (serverData or parsed content)
110
+ * variantId — which variant was chosen (undefined = default / no variants)
111
+ * settings — resolved setting values chosen by the user
112
+ */
113
+ print: (data: unknown, variantId?: string, settings?: PrintSettings) => void | Promise<void>;
114
+ }
115
+
116
+ /**
117
+ * @ai-matrx/print/labels — the label-sheet engine.
118
+ *
119
+ * ONE module carrying the three matrx-frontend files this lane was extracted
120
+ * from, ported behaviour-verbatim (2026-08-29 state):
121
+ * lib/label-print/label-templates.ts → the template registry
122
+ * lib/label-print/qr-labels-printer.ts → qrLabelsPrinter / printQrLabelSheet
123
+ * lib/label-print/qr-labels-pdf.ts → downloadLabelsPdf
124
+ *
125
+ * Warehouse-grade QR labels on standard 8.5" × 11" label stock (Avery and
126
+ * compatibles) AND on continuous/die-cut roll stock. ONE generic grid renderer
127
+ * driven by the LabelTemplate registry — geometry is data, never per-stock CSS.
128
+ *
129
+ * Fidelity rules (the reasons this exists):
130
+ * - The print window is a fresh, unauthenticated document: every QR image is
131
+ * inlined as a data URI, never fetched.
132
+ * - Spec-compliant quiet zone: the `qrcode` lib renders 4 modules of white
133
+ * margin INSIDE each data URI (its default; we pass it explicitly).
134
+ * - `print-color-adjust: exact` on code cells so the browser never "helps".
135
+ * - Modules are crisp: each QR is rasterized at 300 dpi for its printed size,
136
+ * and `image-rendering: pixelated` stops resampling blur.
137
+ * - Captions auto-shrink to fit their box (FIT_TEXT pattern) — they never
138
+ * overflow onto the neighboring label.
139
+ * - `startAtLabel` inserts LEADING blank cells so a partially-used sheet can
140
+ * be reused; trailing cells pad out the last page.
141
+ *
142
+ * THE IMAGE RULE — the one deliberate exception to "data URIs only": a label's
143
+ * optional `imageUrl` is a DURABLE http(s) URL and is emitted as a plain
144
+ * `<img src>`. Anything that is not http(s) is SKIPPED rather than rendered.
145
+ * The QR itself is never a URL; it is always an inlined data URI.
146
+ *
147
+ * SEAM INVERSIONS versus the matrx-frontend originals (the only changes to the
148
+ * ported behaviour):
149
+ * 1. `@/lib/block-print/block-print-utils` → `./core` (identical export names).
150
+ * 2. The deferred `import("qrcode")` + `QRCode.toDataURL(...)` call is now
151
+ * `generateQrDataUrl` from `./qr`, which performs the same lazy import at
152
+ * call time and maps 1:1 — errorCorrectionLevel → ecLevel, margin →
153
+ * quietZoneModules, width → widthPx. The original also passed
154
+ * `color: { dark: "#000000", light: "#ffffff" }`, which is byte-for-byte the
155
+ * `qrcode` default, so dropping it changes no output.
156
+ * 3. LP_FIT_SCRIPT is KEPT VERBATIM rather than replaced by core's
157
+ * `buildFitTextScript`. The two scripts are NOT behaviourally identical for
158
+ * label cells: core's steps and writes font sizes in **pt** (and pre-assigns
159
+ * `size + "pt"` from a px-derived number before measuring) with a **6pt**
160
+ * floor, while the label script steps and writes **px** with a **4px** floor
161
+ * and no pre-assignment. On a 9pt caption inside a 0.28in box those two
162
+ * produce different final sizes, so swapping them would regress captions on
163
+ * the small Avery stocks.
164
+ *
165
+ * Docs: lib/label-print/FEATURE.md (matrx-frontend), NOTES-CHANGELOG.md.
166
+ */
167
+
168
+ /**
169
+ * Label-sheet template registry — the geometry source of truth for QR label
170
+ * printing on standard 8.5" × 11" label stock (Avery and compatibles) and on
171
+ * roll stock.
172
+ *
173
+ * ONE generic grid renderer is driven entirely by these numbers — never a
174
+ * hand-written CSS block per stock. All dimensions in inches, taken from
175
+ * Avery's published templates:
176
+ * - horizontal pitch = labelWIn + gutterXIn; vertical pitch = labelHIn + gutterYIn
177
+ * - a template is valid when marginLeft*2 + cols*labelW + (cols-1)*gutterX = sheetW
178
+ * (same vertically) — `assertTemplateGeometry` checks this in dev.
179
+ */
180
+ interface LabelTemplate {
181
+ id: string;
182
+ name: string;
183
+ /** Stock code as printed on the box (e.g. "Avery 5160"). */
184
+ stockCode: string;
185
+ sheetWIn: number;
186
+ sheetHIn: number;
187
+ marginTopIn: number;
188
+ marginLeftIn: number;
189
+ labelWIn: number;
190
+ labelHIn: number;
191
+ cols: number;
192
+ rows: number;
193
+ /** Horizontal gap BETWEEN columns (not the outer margin). */
194
+ gutterXIn: number;
195
+ /** Vertical gap BETWEEN rows. */
196
+ gutterYIn: number;
197
+ /** Corner radius; equal to labelWIn/2 for round labels. */
198
+ cornerRadiusIn?: number;
199
+ /** True round label (die-cut circle) — preview/calibration draw a circle. */
200
+ round?: boolean;
201
+ /**
202
+ * Stock family. Absent means "sheet" — a multi-up page of labels. "roll" is
203
+ * a continuous/die-cut roll where the sheet IS the label: margins 0, a 1×1
204
+ * grid, and `@page` sized to the single label.
205
+ */
206
+ kind?: "sheet" | "roll";
207
+ }
208
+ interface QrLabel {
209
+ /** The value encoded in the QR code (SKU, URL, id…). */
210
+ qrValue: string;
211
+ /** Bold headline under/next to the code. Defaults to qrValue when captions are on. */
212
+ caption?: string;
213
+ /** Additional small text lines (wide labels only — there is no room on tiny stock). */
214
+ lines?: string[];
215
+ /**
216
+ * A single short, LARGE, bold token (a garment size, a bin letter, a grade).
217
+ * Rendered in the caption zone ABOVE the caption at ~2× the caption size and
218
+ * auto-shrunk by the fit script.
219
+ */
220
+ badge?: string;
221
+ /**
222
+ * Durable http(s) image URL rendered in the lines zone with
223
+ * `object-fit: contain`. Anything that is not http(s) is skipped — THE IMAGE
224
+ * RULE. Ignored by the PDF lane, which cannot fetch a remote image.
225
+ */
226
+ imageUrl?: string;
227
+ }
228
+ type QrEcLevel = "L" | "M" | "Q";
229
+
230
+ /**
231
+ * @ai-matrx/print/react — the package's ONLY React entry.
232
+ *
233
+ * Two components, ported verbatim from matrx-frontend:
234
+ * PrintOptionsDialog ← lib/block-print/PrintOptionsDialog.tsx
235
+ * LabelSheetPreview ← lib/label-print/LabelSheetPreview.tsx
236
+ *
237
+ * The app seams are inverted here and NOWHERE else:
238
+ * @/components/ui/dialog → inline minimal wrappers over @radix-ui/react-dialog
239
+ * @/components/ui/drawer → inline minimal wrappers over vaul
240
+ * @/components/ui/button → PrintDialogButton (the design-system Button's
241
+ * exact class strings for the used variants)
242
+ * @/hooks/use-mobile → useIsMobile, reproduced behavior-for-behavior
243
+ * lucide-react → inline SVGs carrying lucide's own path data
244
+ * @/lib/block-print/* → ./core
245
+ * ./label-templates, ./qr-labels-printer → ./labels
246
+ *
247
+ * NO "use client" directive lives in this file — tsup stamps this entry via a
248
+ * banner (see tsup.config.ts); a second literal directive here would be
249
+ * duplicated into dist.
250
+ *
251
+ * Tailwind class strings are the originals verbatim: the consuming app's theme
252
+ * tokens (bg-primary, text-muted-foreground, pb-safe…) style these components,
253
+ * exactly as they did in matrx-frontend.
254
+ */
255
+
256
+ interface PrintOptionsDialogProps {
257
+ printer: BlockPrinter;
258
+ data: unknown;
259
+ open: boolean;
260
+ onOpenChange: (open: boolean) => void;
261
+ }
262
+ /**
263
+ * Shared pre-print options UI — shown when a BlockPrinter has variants or settings.
264
+ * Desktop: Dialog. Mobile: Drawer (bottom sheet).
265
+ */
266
+ declare function PrintOptionsDialog({ printer, data, open, onOpenChange, }: PrintOptionsDialogProps): React.JSX.Element;
267
+ /**
268
+ * Manages open state for PrintOptionsDialog.
269
+ * If the printer has variants or settings, shows the dialog.
270
+ * If it has neither, calls printer.print() immediately with no settings.
271
+ */
272
+ declare function usePrintOptions(printer: BlockPrinter | null, data: unknown): {
273
+ open: boolean;
274
+ setOpen: React.Dispatch<React.SetStateAction<boolean>>;
275
+ triggerPrint: () => Promise<void>;
276
+ };
277
+ /**
278
+ * LabelSheetPreview — scaled on-screen preview of one label sheet, driven by
279
+ * the same LabelTemplate geometry as the printer (percent-positioned cells,
280
+ * so it is exactly proportional at any width). Also renders a calibration
281
+ * view (outlines only) so a user can eyeball the grid before printing.
282
+ *
283
+ * QR images are generated client-side to data URIs via `qrcode` (deferred
284
+ * import at effect time).
285
+ */
286
+ interface LabelSheetPreviewProps {
287
+ template: LabelTemplate;
288
+ labels: QrLabel[];
289
+ /** 1-based first cell to fill — leading cells render empty. */
290
+ startAtLabel?: number;
291
+ showCaption?: boolean;
292
+ ecLevel?: QrEcLevel;
293
+ /** Outlines-only calibration view. */
294
+ calibration?: boolean;
295
+ /** Which sheet to show (0-based). */
296
+ pageIndex?: number;
297
+ className?: string;
298
+ }
299
+ declare function LabelSheetPreview({ template: t, labels, startAtLabel, showCaption, ecLevel, calibration, pageIndex, className, }: LabelSheetPreviewProps): React.JSX.Element;
300
+
301
+ export { LabelSheetPreview, type LabelSheetPreviewProps, PrintOptionsDialog, type PrintOptionsDialogProps, usePrintOptions };