@printwithsynergy/artwork-pdf-editor 0.1.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/LICENSE +62 -0
  2. package/README.md +63 -2
  3. package/dist/components/AccessibilityHintsPanel.d.ts +142 -0
  4. package/dist/components/AccessibilityHintsPanel.d.ts.map +1 -0
  5. package/dist/components/AccessibilityHintsPanel.js +158 -0
  6. package/dist/components/AccessibilityHintsPanel.js.map +1 -0
  7. package/dist/components/AnnotationOverlay.d.ts +142 -0
  8. package/dist/components/AnnotationOverlay.d.ts.map +1 -0
  9. package/dist/components/AnnotationOverlay.js +141 -0
  10. package/dist/components/AnnotationOverlay.js.map +1 -0
  11. package/dist/components/AnnotationsSidebar.d.ts +98 -0
  12. package/dist/components/AnnotationsSidebar.d.ts.map +1 -0
  13. package/dist/components/AnnotationsSidebar.js +100 -0
  14. package/dist/components/AnnotationsSidebar.js.map +1 -0
  15. package/dist/components/BarcodeGeneratorPanel.d.ts +58 -0
  16. package/dist/components/BarcodeGeneratorPanel.d.ts.map +1 -0
  17. package/dist/components/BarcodeGeneratorPanel.js +91 -0
  18. package/dist/components/BarcodeGeneratorPanel.js.map +1 -0
  19. package/dist/components/BraillePanel.d.ts +99 -0
  20. package/dist/components/BraillePanel.d.ts.map +1 -0
  21. package/dist/components/BraillePanel.js +221 -0
  22. package/dist/components/BraillePanel.js.map +1 -0
  23. package/dist/components/BrandAssetsPanel.d.ts +130 -0
  24. package/dist/components/BrandAssetsPanel.d.ts.map +1 -0
  25. package/dist/components/BrandAssetsPanel.js +125 -0
  26. package/dist/components/BrandAssetsPanel.js.map +1 -0
  27. package/dist/components/BrandConsistencyPanel.d.ts +140 -0
  28. package/dist/components/BrandConsistencyPanel.d.ts.map +1 -0
  29. package/dist/components/BrandConsistencyPanel.js +158 -0
  30. package/dist/components/BrandConsistencyPanel.js.map +1 -0
  31. package/dist/components/ComplianceFindingsPanel.d.ts +62 -0
  32. package/dist/components/ComplianceFindingsPanel.d.ts.map +1 -0
  33. package/dist/components/ComplianceFindingsPanel.js +118 -0
  34. package/dist/components/ComplianceFindingsPanel.js.map +1 -0
  35. package/dist/components/DesignSuggestionsPanel.d.ts +148 -0
  36. package/dist/components/DesignSuggestionsPanel.d.ts.map +1 -0
  37. package/dist/components/DesignSuggestionsPanel.js +154 -0
  38. package/dist/components/DesignSuggestionsPanel.js.map +1 -0
  39. package/dist/components/DielineParametersPanel.d.ts +62 -0
  40. package/dist/components/DielineParametersPanel.d.ts.map +1 -0
  41. package/dist/components/DielineParametersPanel.js +170 -0
  42. package/dist/components/DielineParametersPanel.js.map +1 -0
  43. package/dist/components/DielinePreview.d.ts +150 -0
  44. package/dist/components/DielinePreview.d.ts.map +1 -0
  45. package/dist/components/DielinePreview.js +146 -0
  46. package/dist/components/DielinePreview.js.map +1 -0
  47. package/dist/components/EditorApp.d.ts +39 -5
  48. package/dist/components/EditorApp.d.ts.map +1 -1
  49. package/dist/components/EditorApp.js +110 -5
  50. package/dist/components/EditorApp.js.map +1 -1
  51. package/dist/components/EditorCanvas.d.ts +51 -2
  52. package/dist/components/EditorCanvas.d.ts.map +1 -1
  53. package/dist/components/EditorCanvas.js +117 -48
  54. package/dist/components/EditorCanvas.js.map +1 -1
  55. package/dist/components/EmailNotifyPanel.d.ts +165 -0
  56. package/dist/components/EmailNotifyPanel.d.ts.map +1 -0
  57. package/dist/components/EmailNotifyPanel.js +211 -0
  58. package/dist/components/EmailNotifyPanel.js.map +1 -0
  59. package/dist/components/FileDropZone.d.ts +9 -1
  60. package/dist/components/FileDropZone.d.ts.map +1 -1
  61. package/dist/components/FileDropZone.js +53 -5
  62. package/dist/components/FileDropZone.js.map +1 -1
  63. package/dist/components/FoldEditorPanel.d.ts +68 -0
  64. package/dist/components/FoldEditorPanel.d.ts.map +1 -0
  65. package/dist/components/FoldEditorPanel.js +65 -0
  66. package/dist/components/FoldEditorPanel.js.map +1 -0
  67. package/dist/components/FoldPreviewOverlay.d.ts +48 -0
  68. package/dist/components/FoldPreviewOverlay.d.ts.map +1 -0
  69. package/dist/components/FoldPreviewOverlay.js +182 -0
  70. package/dist/components/FoldPreviewOverlay.js.map +1 -0
  71. package/dist/components/Gs1DigitalLinkPanel.d.ts +103 -0
  72. package/dist/components/Gs1DigitalLinkPanel.d.ts.map +1 -0
  73. package/dist/components/Gs1DigitalLinkPanel.js +199 -0
  74. package/dist/components/Gs1DigitalLinkPanel.js.map +1 -0
  75. package/dist/components/HistoryPanel.d.ts +39 -0
  76. package/dist/components/HistoryPanel.d.ts.map +1 -0
  77. package/dist/components/HistoryPanel.js +72 -0
  78. package/dist/components/HistoryPanel.js.map +1 -0
  79. package/dist/components/IccSoftProofOverlay.d.ts +67 -0
  80. package/dist/components/IccSoftProofOverlay.d.ts.map +1 -0
  81. package/dist/components/IccSoftProofOverlay.js +119 -0
  82. package/dist/components/IccSoftProofOverlay.js.map +1 -0
  83. package/dist/components/ImposePanel.d.ts +71 -0
  84. package/dist/components/ImposePanel.d.ts.map +1 -0
  85. package/dist/components/ImposePanel.js +127 -0
  86. package/dist/components/ImposePanel.js.map +1 -0
  87. package/dist/components/InksPanel.d.ts +61 -0
  88. package/dist/components/InksPanel.d.ts.map +1 -0
  89. package/dist/components/InksPanel.js +84 -0
  90. package/dist/components/InksPanel.js.map +1 -0
  91. package/dist/components/JobSetupPanel.d.ts +118 -0
  92. package/dist/components/JobSetupPanel.d.ts.map +1 -0
  93. package/dist/components/JobSetupPanel.js +169 -0
  94. package/dist/components/JobSetupPanel.js.map +1 -0
  95. package/dist/components/LayersPanel.d.ts.map +1 -1
  96. package/dist/components/LayersPanel.js +1 -0
  97. package/dist/components/LayersPanel.js.map +1 -1
  98. package/dist/components/MarkLibraryPanel.d.ts +131 -0
  99. package/dist/components/MarkLibraryPanel.d.ts.map +1 -0
  100. package/dist/components/MarkLibraryPanel.js +184 -0
  101. package/dist/components/MarkLibraryPanel.js.map +1 -0
  102. package/dist/components/MisEstimateButton.d.ts +73 -0
  103. package/dist/components/MisEstimateButton.d.ts.map +1 -0
  104. package/dist/components/MisEstimateButton.js +57 -0
  105. package/dist/components/MisEstimateButton.js.map +1 -0
  106. package/dist/components/NutritionPanel.d.ts +118 -0
  107. package/dist/components/NutritionPanel.d.ts.map +1 -0
  108. package/dist/components/NutritionPanel.js +169 -0
  109. package/dist/components/NutritionPanel.js.map +1 -0
  110. package/dist/components/PageNavigator.d.ts +26 -0
  111. package/dist/components/PageNavigator.d.ts.map +1 -0
  112. package/dist/components/PageNavigator.js +96 -0
  113. package/dist/components/PageNavigator.js.map +1 -0
  114. package/dist/components/PaletteManager.d.ts +32 -0
  115. package/dist/components/PaletteManager.d.ts.map +1 -0
  116. package/dist/components/PaletteManager.js +89 -0
  117. package/dist/components/PaletteManager.js.map +1 -0
  118. package/dist/components/PaletteToSpotPanel.d.ts +122 -0
  119. package/dist/components/PaletteToSpotPanel.d.ts.map +1 -0
  120. package/dist/components/PaletteToSpotPanel.js +160 -0
  121. package/dist/components/PaletteToSpotPanel.js.map +1 -0
  122. package/dist/components/PreflightAutoFixPanel.d.ts +110 -0
  123. package/dist/components/PreflightAutoFixPanel.d.ts.map +1 -0
  124. package/dist/components/PreflightAutoFixPanel.js +119 -0
  125. package/dist/components/PreflightAutoFixPanel.js.map +1 -0
  126. package/dist/components/PreflightDiffPanel.d.ts +127 -0
  127. package/dist/components/PreflightDiffPanel.d.ts.map +1 -0
  128. package/dist/components/PreflightDiffPanel.js +0 -0
  129. package/dist/components/PreflightDiffPanel.js.map +1 -0
  130. package/dist/components/ProcessRulesPanel.d.ts +81 -0
  131. package/dist/components/ProcessRulesPanel.d.ts.map +1 -0
  132. package/dist/components/ProcessRulesPanel.js +143 -0
  133. package/dist/components/ProcessRulesPanel.js.map +1 -0
  134. package/dist/components/SlackNotifyPanel.d.ts +139 -0
  135. package/dist/components/SlackNotifyPanel.d.ts.map +1 -0
  136. package/dist/components/SlackNotifyPanel.js +133 -0
  137. package/dist/components/SlackNotifyPanel.js.map +1 -0
  138. package/dist/components/SmartSpotMatchPanel.d.ts +143 -0
  139. package/dist/components/SmartSpotMatchPanel.d.ts.map +1 -0
  140. package/dist/components/SmartSpotMatchPanel.js +159 -0
  141. package/dist/components/SmartSpotMatchPanel.js.map +1 -0
  142. package/dist/components/SwatchesPicker.d.ts +83 -0
  143. package/dist/components/SwatchesPicker.d.ts.map +1 -0
  144. package/dist/components/SwatchesPicker.js +151 -0
  145. package/dist/components/SwatchesPicker.js.map +1 -0
  146. package/dist/components/TacOverlay.d.ts +47 -0
  147. package/dist/components/TacOverlay.d.ts.map +1 -0
  148. package/dist/components/TacOverlay.js +116 -0
  149. package/dist/components/TacOverlay.js.map +1 -0
  150. package/dist/components/TrapEditorPanel.d.ts +52 -0
  151. package/dist/components/TrapEditorPanel.d.ts.map +1 -0
  152. package/dist/components/TrapEditorPanel.js +64 -0
  153. package/dist/components/TrapEditorPanel.js.map +1 -0
  154. package/dist/components/TrapPreviewOverlay.d.ts +64 -0
  155. package/dist/components/TrapPreviewOverlay.d.ts.map +1 -0
  156. package/dist/components/TrapPreviewOverlay.js +120 -0
  157. package/dist/components/TrapPreviewOverlay.js.map +1 -0
  158. package/dist/components/VariantMatrixPanel.d.ts +61 -0
  159. package/dist/components/VariantMatrixPanel.d.ts.map +1 -0
  160. package/dist/components/VariantMatrixPanel.js +97 -0
  161. package/dist/components/VariantMatrixPanel.js.map +1 -0
  162. package/dist/components/VariantMatrixVersionPanel.d.ts +122 -0
  163. package/dist/components/VariantMatrixVersionPanel.d.ts.map +1 -0
  164. package/dist/components/VariantMatrixVersionPanel.js +162 -0
  165. package/dist/components/VariantMatrixVersionPanel.js.map +1 -0
  166. package/dist/components/WebhookNotifyPanel.d.ts +160 -0
  167. package/dist/components/WebhookNotifyPanel.d.ts.map +1 -0
  168. package/dist/components/WebhookNotifyPanel.js +100 -0
  169. package/dist/components/WebhookNotifyPanel.js.map +1 -0
  170. package/dist/components/WhiteUnderbasePanel.d.ts +107 -0
  171. package/dist/components/WhiteUnderbasePanel.d.ts.map +1 -0
  172. package/dist/components/WhiteUnderbasePanel.js +104 -0
  173. package/dist/components/WhiteUnderbasePanel.js.map +1 -0
  174. package/dist/data/dielines.json +35 -0
  175. package/dist/hooks/useEditorMode.d.ts +25 -5
  176. package/dist/hooks/useEditorMode.d.ts.map +1 -1
  177. package/dist/hooks/useEditorMode.js +18 -5
  178. package/dist/hooks/useEditorMode.js.map +1 -1
  179. package/dist/index.d.ts +49 -2
  180. package/dist/index.d.ts.map +1 -1
  181. package/dist/index.js +49 -2
  182. package/dist/index.js.map +1 -1
  183. package/dist/lens/dieline-overlay.d.ts +25 -0
  184. package/dist/lens/dieline-overlay.d.ts.map +1 -0
  185. package/dist/lens/dieline-overlay.js +50 -0
  186. package/dist/lens/dieline-overlay.js.map +1 -0
  187. package/dist/lens/index.d.ts +27 -0
  188. package/dist/lens/index.d.ts.map +1 -0
  189. package/dist/lens/index.js +28 -0
  190. package/dist/lens/index.js.map +1 -0
  191. package/dist/lens/preflight-findings.d.ts +21 -0
  192. package/dist/lens/preflight-findings.d.ts.map +1 -0
  193. package/dist/lens/preflight-findings.js +82 -0
  194. package/dist/lens/preflight-findings.js.map +1 -0
  195. package/dist/lib/barcode-scan.d.ts +154 -0
  196. package/dist/lib/barcode-scan.d.ts.map +1 -0
  197. package/dist/lib/barcode-scan.js +152 -0
  198. package/dist/lib/barcode-scan.js.map +1 -0
  199. package/dist/lib/color-math.d.ts +76 -0
  200. package/dist/lib/color-math.d.ts.map +1 -0
  201. package/dist/lib/color-math.js +96 -0
  202. package/dist/lib/color-math.js.map +1 -0
  203. package/dist/lib/dieline-template.d.ts +169 -0
  204. package/dist/lib/dieline-template.d.ts.map +1 -1
  205. package/dist/lib/dieline-template.js +229 -1
  206. package/dist/lib/dieline-template.js.map +1 -1
  207. package/dist/lib/editor-config.d.ts +384 -1
  208. package/dist/lib/editor-config.d.ts.map +1 -1
  209. package/dist/lib/editor-config.js +89 -2
  210. package/dist/lib/editor-config.js.map +1 -1
  211. package/dist/lib/fold-geometry.d.ts +144 -0
  212. package/dist/lib/fold-geometry.d.ts.map +1 -0
  213. package/dist/lib/fold-geometry.js +138 -0
  214. package/dist/lib/fold-geometry.js.map +1 -0
  215. package/dist/lib/merge-tokens.d.ts +81 -0
  216. package/dist/lib/merge-tokens.d.ts.map +1 -0
  217. package/dist/lib/merge-tokens.js +88 -0
  218. package/dist/lib/merge-tokens.js.map +1 -0
  219. package/dist/lib/palette-registry.d.ts +40 -0
  220. package/dist/lib/palette-registry.d.ts.map +1 -0
  221. package/dist/lib/palette-registry.js +49 -0
  222. package/dist/lib/palette-registry.js.map +1 -0
  223. package/dist/lib/panel-anchor.d.ts +101 -0
  224. package/dist/lib/panel-anchor.d.ts.map +1 -0
  225. package/dist/lib/panel-anchor.js +68 -0
  226. package/dist/lib/panel-anchor.js.map +1 -0
  227. package/dist/lib/preflight/checks.d.ts.map +1 -1
  228. package/dist/lib/preflight/checks.js +71 -0
  229. package/dist/lib/preflight/checks.js.map +1 -1
  230. package/dist/lib/preflight/types.d.ts.map +1 -1
  231. package/dist/lib/preflight/types.js +11 -0
  232. package/dist/lib/preflight/types.js.map +1 -1
  233. package/dist/lib/rasterize.d.ts +93 -0
  234. package/dist/lib/rasterize.d.ts.map +1 -0
  235. package/dist/lib/rasterize.js +117 -0
  236. package/dist/lib/rasterize.js.map +1 -0
  237. package/dist/lib/separations-registry.d.ts +99 -0
  238. package/dist/lib/separations-registry.d.ts.map +1 -0
  239. package/dist/lib/separations-registry.js +59 -0
  240. package/dist/lib/separations-registry.js.map +1 -0
  241. package/dist/lib/unwired.d.ts +29 -0
  242. package/dist/lib/unwired.d.ts.map +1 -0
  243. package/dist/lib/unwired.js +58 -0
  244. package/dist/lib/unwired.js.map +1 -0
  245. package/package.json +29 -11
  246. package/dist/components/SeparationsPanel.d.ts +0 -9
  247. package/dist/components/SeparationsPanel.d.ts.map +0 -1
  248. package/dist/components/SeparationsPanel.js +0 -168
  249. package/dist/components/SeparationsPanel.js.map +0 -1
@@ -0,0 +1,65 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ "use client";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ const DEFAULT_MIN = -180;
5
+ const DEFAULT_MAX = 180;
6
+ /**
7
+ * @public
8
+ */
9
+ export function FoldEditorPanel({ value, onChange, showFolded, onShowFoldedChange, minAngleDeg, maxAngleDeg, }) {
10
+ const min = minAngleDeg ?? DEFAULT_MIN;
11
+ const max = maxAngleDeg ?? DEFAULT_MAX;
12
+ if (!value || value.edges.length === 0) {
13
+ return (_jsx("div", { "data-testid": "fold-editor-panel", style: { padding: "0.5rem", opacity: 0.6 }, children: "Load a dieline with fold metadata to edit panel angles." }));
14
+ }
15
+ const setEdgeAngle = (id, raw) => {
16
+ // In-progress edits ("", "-", "1e") parse to `NaN` / partial
17
+ // values — silently keep the last good angle so the user can
18
+ // type through to a valid one. The browser owns its `<input>`
19
+ // DOM value mid-edit, so the field still shows whatever they
20
+ // typed even though we haven't propagated it.
21
+ if (raw === "" || raw === "-" || raw === "+")
22
+ return;
23
+ const n = Number(raw);
24
+ if (!Number.isFinite(n))
25
+ return;
26
+ const clamped = Math.min(Math.max(n, min), max);
27
+ onChange({
28
+ ...value,
29
+ edges: value.edges.map((e) => (e.id === id ? { ...e, angleDeg: clamped } : e)),
30
+ });
31
+ };
32
+ const setEdgeDirection = (id, direction) => {
33
+ onChange({
34
+ ...value,
35
+ edges: value.edges.map((e) => {
36
+ if (e.id !== id)
37
+ return e;
38
+ // Clearing the select drops the field entirely — restores the
39
+ // "no direction declared" state so the renderer can fall back
40
+ // to its own heuristic.
41
+ if (direction === undefined) {
42
+ const { direction: _omit, ...rest } = e;
43
+ return rest;
44
+ }
45
+ return { ...e, direction };
46
+ }),
47
+ });
48
+ };
49
+ return (_jsxs("div", { "data-testid": "fold-editor-panel", style: { padding: "0.5rem" }, children: [_jsxs("header", { style: { display: "flex", alignItems: "baseline", gap: "0.5rem" }, children: [_jsx("h3", { style: { margin: 0 }, children: "Fold angles" }), onShowFoldedChange && (_jsxs("label", { style: { marginLeft: "auto", fontSize: "0.85rem" }, children: [_jsx("input", { type: "checkbox", checked: !!showFolded, onChange: (e) => onShowFoldedChange(e.target.checked), "aria-label": "Show folded preview" }), " ", "Show folded"] }))] }), _jsx("ul", { style: { listStyle: "none", padding: 0, margin: "0.5rem 0 0 0" }, children: value.edges.map((edge) => (_jsxs("li", { style: {
50
+ display: "grid",
51
+ gridTemplateColumns: "auto 1fr auto auto",
52
+ gap: "0.5rem",
53
+ alignItems: "center",
54
+ padding: "0.25rem 0",
55
+ }, children: [_jsxs("span", { style: { fontFamily: "monospace", fontSize: "0.85rem" }, children: [edge.panelA, " \u2194 ", edge.panelB] }), _jsx("input", { type: "range", min: min, max: max, step: 1, value: edge.angleDeg, onChange: (e) => setEdgeAngle(edge.id, e.target.value), "aria-label": `Angle for ${edge.id}` }), _jsx("input", { type: "number", min: min, max: max, step: 1, value: edge.angleDeg, onChange: (e) => setEdgeAngle(edge.id, e.target.value), "aria-label": `Numeric angle for ${edge.id}`, style: { width: "4rem" } }), _jsxs("select", { value: edge.direction ?? "", onChange: (e) => {
56
+ const v = e.target.value;
57
+ if (v === "mountain" || v === "valley") {
58
+ setEdgeDirection(edge.id, v);
59
+ }
60
+ else if (v === "") {
61
+ setEdgeDirection(edge.id, undefined);
62
+ }
63
+ }, "aria-label": `Fold direction for ${edge.id}`, children: [_jsx("option", { value: "", children: "\u2014" }), _jsx("option", { value: "mountain", children: "mountain" }), _jsx("option", { value: "valley", children: "valley" })] })] }, edge.id))) })] }));
64
+ }
65
+ //# sourceMappingURL=FoldEditorPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoldEditorPanel.js","sourceRoot":"","sources":["../../src/components/FoldEditorPanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import { type FoldGeometryConfig, type FoldGeometryPanelMetadata } from "../lib/fold-geometry";
2
+ /**
3
+ * S4 — 3D fold preview overlay (Wave 2 PR-3 scaffold).
4
+ *
5
+ * Renders a Three.js scene of the dieline's panels in 3D space.
6
+ * The PR-3 scaffold ships a *flat* preview (every panel at Z=0)
7
+ * with the hinge axes drawn in colored lines so the operator can
8
+ * see where folds will happen. Interactive fold-angle scrubbing
9
+ * lands in PR-4.
10
+ *
11
+ * Bundle note: Three.js is a hard dependency (~150 KB) so hosts
12
+ * paying for the editor get fold preview out of the box. Hosts that
13
+ * never need it can disable via {@link EditorConfig.enable_3d_fold_preview}
14
+ * — when the flag is `false`, this component is never rendered and
15
+ * the Three.js code path stays cold, but the symbols still ship
16
+ * with the bundle. A future refactor may switch to a dynamic
17
+ * `import("three")` for tighter chunking; that's deferred until the
18
+ * editor's bundle profile shows it's worth the complexity.
19
+ *
20
+ * The Three.js scene is owned by this component — it creates the
21
+ * renderer / scene / camera on mount, recomputes geometry on prop
22
+ * change, and disposes on unmount. Hosts get a controlled component:
23
+ * pass `panelMetadata` + `foldConfig`, get a rendered scene.
24
+ *
25
+ * @public
26
+ */
27
+ export type FoldPreviewOverlayProps = {
28
+ /** Panel registry derived from the active page's dieline.
29
+ * Structurally compatible with `document-model`'s `PanelMetadata`.
30
+ * When `undefined`, the overlay no-ops (renders an empty container). */
31
+ panelMetadata: FoldGeometryPanelMetadata | undefined;
32
+ /** Fold-edge config for the active page. Structurally compatible
33
+ * with `document-model`'s `FoldConfig`. When `undefined`, panels
34
+ * are still rendered (flat layout), but no hinge lines appear. */
35
+ foldConfig?: FoldGeometryConfig | undefined;
36
+ /** Container dimensions in CSS pixels. */
37
+ width: number;
38
+ height: number;
39
+ /** Optional background color for the 3D viewport (CSS color
40
+ * string). Defaults to a transparent canvas so the overlay can
41
+ * layer over the editor's existing chrome. */
42
+ backgroundColor?: string;
43
+ };
44
+ /**
45
+ * @public
46
+ */
47
+ export declare function FoldPreviewOverlay({ panelMetadata, foldConfig, width, height, backgroundColor, }: FoldPreviewOverlayProps): import("react/jsx-runtime").JSX.Element;
48
+ //# sourceMappingURL=FoldPreviewOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoldPreviewOverlay.d.ts","sourceRoot":"","sources":["../../src/components/FoldPreviewOverlay.tsx"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAG/B,MAAM,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;6EAEyE;IACzE,aAAa,EAAE,yBAAyB,GAAG,SAAS,CAAC;IACrD;;uEAEmE;IACnE,UAAU,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC5C,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf;;mDAE+C;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,aAAa,EACb,UAAU,EACV,KAAK,EACL,MAAM,EACN,eAAe,GAChB,EAAE,uBAAuB,2CA6FzB"}
@@ -0,0 +1,182 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ "use client";
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ import { useEffect, useRef } from "react";
5
+ import { buildFoldScene, } from "../lib/fold-geometry";
6
+ /**
7
+ * @public
8
+ */
9
+ export function FoldPreviewOverlay({ panelMetadata, foldConfig, width, height, backgroundColor, }) {
10
+ const containerRef = useRef(null);
11
+ const sceneRef = useRef(null);
12
+ // Hold the latest props in a ref so the mount effect's initial
13
+ // scene picks them up without depending on them — depending on
14
+ // `panelMetadata` / `foldConfig` here would tear down + recreate
15
+ // the renderer on every edit, defeating the per-update apply path
16
+ // in the second effect.
17
+ const propsRef = useRef({ panelMetadata, foldConfig });
18
+ propsRef.current = { panelMetadata, foldConfig };
19
+ useEffect(() => {
20
+ const container = containerRef.current;
21
+ if (!container)
22
+ return;
23
+ // Guard zero / negative dimensions — `setSize(0, 0)` produces a
24
+ // degenerate canvas and `width / height` on the camera becomes
25
+ // NaN/Infinity. Layout transitions can briefly hit this during
26
+ // mount; defer scene creation until both dims are positive.
27
+ if (width <= 0 || height <= 0)
28
+ return;
29
+ let runtime = null;
30
+ let disposed = false;
31
+ (async () => {
32
+ const three = await import("three");
33
+ if (disposed)
34
+ return;
35
+ const created = await createScene(three, container, width, height, backgroundColor);
36
+ // Re-check after the second await — cleanup may have fired
37
+ // during `await createScene(...)`. At that point `runtime` was
38
+ // still null in the cleanup closure, so it couldn't dispose;
39
+ // do it here ourselves before the renderer / canvas escape.
40
+ if (disposed) {
41
+ disposeScene(created);
42
+ return;
43
+ }
44
+ runtime = created;
45
+ sceneRef.current = runtime;
46
+ const { panelMetadata: pm, foldConfig: fc } = propsRef.current;
47
+ const spec = pm ? buildFoldScene(pm, fc) : EMPTY_SCENE;
48
+ applyScene(three, runtime, spec);
49
+ // Render-on-change: PR-3's scaffold is static, so one draw per
50
+ // mutation is enough. PR-4 swaps this for a RAF loop driven by
51
+ // user interaction (drag-to-fold), so the loop machinery isn't
52
+ // worth keeping cold here.
53
+ runtime.renderer.render(runtime.scene, runtime.camera);
54
+ })().catch((err) => {
55
+ // Swallow rejections after unmount; surface real errors during
56
+ // mount (chunk-load failures from `import("three")`, WebGL
57
+ // context creation failure, etc.) via console so devtools can
58
+ // catch them.
59
+ if (!disposed)
60
+ console.error("[FoldPreviewOverlay] scene init failed", err);
61
+ });
62
+ return () => {
63
+ disposed = true;
64
+ sceneRef.current = null;
65
+ if (runtime)
66
+ disposeScene(runtime);
67
+ };
68
+ }, [width, height, backgroundColor]);
69
+ useEffect(() => {
70
+ const runtime = sceneRef.current;
71
+ if (!runtime)
72
+ return;
73
+ let disposed = false;
74
+ (async () => {
75
+ const three = await import("three");
76
+ if (disposed)
77
+ return;
78
+ // When panelMetadata becomes undefined, apply an empty scene
79
+ // so previously rendered meshes / hinges clear off the canvas —
80
+ // matches the documented no-op behaviour for absent metadata.
81
+ const spec = panelMetadata ? buildFoldScene(panelMetadata, foldConfig) : EMPTY_SCENE;
82
+ applyScene(three, runtime, spec);
83
+ runtime.renderer.render(runtime.scene, runtime.camera);
84
+ })().catch((err) => {
85
+ if (!disposed)
86
+ console.error("[FoldPreviewOverlay] scene update failed", err);
87
+ });
88
+ return () => {
89
+ disposed = true;
90
+ };
91
+ }, [panelMetadata, foldConfig]);
92
+ return (_jsx("div", { ref: containerRef, "data-testid": "fold-preview-overlay", style: {
93
+ position: "relative",
94
+ width,
95
+ height,
96
+ pointerEvents: "auto",
97
+ } }));
98
+ }
99
+ /** Empty scene spec used to clear the canvas when `panelMetadata`
100
+ * is absent. Keeps the meshes / hinges from a previous render off
101
+ * the screen. */
102
+ const EMPTY_SCENE = {
103
+ panels: [],
104
+ hinges: [],
105
+ bounds: { min: [0, 0, 0], max: [0, 0, 0] },
106
+ };
107
+ async function createScene(three, container, width, height, backgroundColor) {
108
+ const renderer = new three.WebGLRenderer({ antialias: true, alpha: !backgroundColor });
109
+ renderer.setSize(width, height);
110
+ renderer.setPixelRatio(typeof window !== "undefined" ? window.devicePixelRatio : 1);
111
+ container.replaceChildren(renderer.domElement);
112
+ const scene = new three.Scene();
113
+ if (backgroundColor) {
114
+ scene.background = new three.Color(backgroundColor);
115
+ }
116
+ const camera = new three.PerspectiveCamera(45, width / height, 0.1, 10000);
117
+ camera.position.set(0, 0, 500);
118
+ const light = new three.HemisphereLight(0xffffff, 0x202020, 1);
119
+ scene.add(light);
120
+ const panelGroup = new three.Group();
121
+ const hingeGroup = new three.Group();
122
+ scene.add(panelGroup, hingeGroup);
123
+ const runtime = {
124
+ renderer,
125
+ scene,
126
+ camera,
127
+ panelGroup,
128
+ hingeGroup,
129
+ resources: [],
130
+ };
131
+ return runtime;
132
+ }
133
+ function applyScene(three, runtime, spec) {
134
+ // Tear down previous geometry without disposing the renderer.
135
+ runtime.panelGroup.clear();
136
+ runtime.hingeGroup.clear();
137
+ for (const r of runtime.resources)
138
+ r.dispose?.();
139
+ runtime.resources = [];
140
+ for (const panel of spec.panels) {
141
+ const geo = new three.BufferGeometry();
142
+ const positions = new Float32Array(panel.corners.flatMap((c) => [c[0], c[1], c[2]]));
143
+ geo.setAttribute("position", new three.BufferAttribute(positions, 3));
144
+ geo.setIndex([0, 1, 2, 0, 2, 3]);
145
+ geo.computeVertexNormals();
146
+ const mat = new three.MeshStandardMaterial({
147
+ color: 0xe6e6e6,
148
+ side: three.DoubleSide,
149
+ roughness: 0.6,
150
+ metalness: 0.0,
151
+ });
152
+ runtime.panelGroup.add(new three.Mesh(geo, mat));
153
+ runtime.resources.push(geo, mat);
154
+ }
155
+ for (const hinge of spec.hinges) {
156
+ const geo = new three.BufferGeometry().setFromPoints([
157
+ new three.Vector3(hinge.from[0], hinge.from[1], hinge.from[2]),
158
+ new three.Vector3(hinge.to[0], hinge.to[1], hinge.to[2]),
159
+ ]);
160
+ const mat = new three.LineBasicMaterial({
161
+ color: hinge.direction === "mountain" ? 0x2563eb : 0xdc2626,
162
+ linewidth: 2,
163
+ });
164
+ runtime.hingeGroup.add(new three.Line(geo, mat));
165
+ runtime.resources.push(geo, mat);
166
+ }
167
+ // Frame the camera around the scene bounds.
168
+ const { min, max } = spec.bounds;
169
+ const cx = (min[0] + max[0]) / 2;
170
+ const cy = (min[1] + max[1]) / 2;
171
+ const span = Math.max(max[0] - min[0], max[1] - min[1], 1);
172
+ runtime.camera.position.set(cx, cy, span * 1.2);
173
+ runtime.camera.lookAt(cx, cy, 0);
174
+ }
175
+ function disposeScene(runtime) {
176
+ for (const r of runtime.resources)
177
+ r.dispose?.();
178
+ runtime.resources = [];
179
+ runtime.renderer.dispose?.();
180
+ runtime.renderer.domElement?.parentElement?.removeChild(runtime.renderer.domElement);
181
+ }
182
+ //# sourceMappingURL=FoldPreviewOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FoldPreviewOverlay.js","sourceRoot":"","sources":["../../src/components/FoldPreviewOverlay.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ import type { BarcodeRenderFn, BarcodeRenderResult } from "./BarcodeGeneratorPanel";
2
+ /**
3
+ * One additional GS1 Application Identifier carried in either the URL
4
+ * path or query string. `ai` is the numeric AI (e.g. `"10"` for lot,
5
+ * `"17"` for expiration date `YYMMDD`); `value` is the raw payload
6
+ * before percent-encoding.
7
+ *
8
+ * The composer percent-encodes `value` automatically; callers should
9
+ * pass the human-readable form.
10
+ *
11
+ * @public
12
+ */
13
+ export type Gs1AiEntry = {
14
+ ai: string;
15
+ value: string;
16
+ };
17
+ /**
18
+ * Result returned by {@link composeGs1DigitalLink}. `url` is the
19
+ * canonical GS1 Digital Link form ready to be QR-encoded;
20
+ * `pathSegment` and `querySegment` are the split halves the host can
21
+ * surface separately (e.g. a "preview" UI that highlights mandatory
22
+ * vs. optional segments).
23
+ *
24
+ * @public
25
+ */
26
+ export type Gs1DigitalLinkResult = {
27
+ url: string;
28
+ pathSegment: string;
29
+ querySegment: string;
30
+ };
31
+ /**
32
+ * @public
33
+ */
34
+ export type Gs1DigitalLinkPanelProps = {
35
+ /** Optional renderer (typically the same adapter wired for
36
+ * {@link import("./BarcodeGeneratorPanel").BarcodeGeneratorPanel}).
37
+ * When supplied, "Generate QR" calls it with `format: "QR"` and the
38
+ * composed Digital Link URL; the bitmap flows to `onRendered`. When
39
+ * absent, the panel only emits the URL via `onLink`. */
40
+ renderer?: BarcodeRenderFn;
41
+ /** Fired when the user clicks **Preview URL** or **Generate QR**
42
+ * with the composed URL. The panel does not fire on every
43
+ * keystroke — hosts that want live preview should poll the
44
+ * current form values via their own state or wrap
45
+ * {@link composeGs1DigitalLink} themselves. */
46
+ onLink?: (result: Gs1DigitalLinkResult) => void;
47
+ /** Fired with the rendered bitmap when the host has wired a
48
+ * `renderer` and the user clicks "Generate QR". */
49
+ onRendered?: (result: BarcodeRenderResult) => void;
50
+ /** Default resolver domain — typically `"id.gs1.org"` or a
51
+ * brand-owned alternate. Defaults to `"id.gs1.org"` (the GS1
52
+ * community default) when omitted. */
53
+ defaultDomain?: string;
54
+ };
55
+ /**
56
+ * The GS1 community-default resolver domain. Re-exported for hosts
57
+ * that want to display it as a hint or seed a settings UI.
58
+ *
59
+ * @public
60
+ */
61
+ export declare const DEFAULT_GS1_DOMAIN = "id.gs1.org";
62
+ /**
63
+ * Validate a GS1 AI 17 (expiration date) value: must be exactly six
64
+ * digits in `YYMMDD` order with month 1-12 and a real calendar day.
65
+ * Year is two digits per the GS1 spec so the validator checks the
66
+ * day against an arbitrary 21st-century year — the year value itself
67
+ * does not constrain the calendar.
68
+ *
69
+ * Exported alongside {@link composeGs1DigitalLink} so server-side
70
+ * callers can reject invalid expiries before composing.
71
+ *
72
+ * @public
73
+ */
74
+ export declare function isValidGs1Ai17(value: string): boolean;
75
+ /**
76
+ * Compose a canonical GS1 Digital Link URL from a GTIN + optional
77
+ * path-AIs + optional query-AIs. Pure function — exported so server
78
+ * components (Next.js RSC, Astro frontmatter) can pre-compute URLs
79
+ * without bundling the panel.
80
+ *
81
+ * Path-AI order is `22 → 10 → 21` per the GS1 Digital Link Standard;
82
+ * any other AIs flow into the query string in the order supplied.
83
+ *
84
+ * @public
85
+ */
86
+ export declare function composeGs1DigitalLink(input: {
87
+ domain: string;
88
+ gtin: string;
89
+ pathAis?: readonly Gs1AiEntry[];
90
+ queryAis?: readonly Gs1AiEntry[];
91
+ }): Gs1DigitalLinkResult;
92
+ /**
93
+ * GS1 Digital Link composer panel. The user picks a domain, types a
94
+ * GTIN, optionally adds CPV / lot / serial path-AIs and free-form
95
+ * query-AIs; the panel emits the composed URL via `onLink`. When the
96
+ * host wires a `renderer`, a "Generate QR" button hands the URL to
97
+ * the adapter with `format: "QR"` and forwards the bitmap to
98
+ * `onRendered`.
99
+ *
100
+ * @public
101
+ */
102
+ export declare function Gs1DigitalLinkPanel({ renderer, onLink, onRendered, defaultDomain, }: Gs1DigitalLinkPanelProps): import("react/jsx-runtime").JSX.Element;
103
+ //# sourceMappingURL=Gs1DigitalLinkPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Gs1DigitalLinkPanel.d.ts","sourceRoot":"","sources":["../../src/components/Gs1DigitalLinkPanel.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEpF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;;6DAIyD;IACzD,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;;oDAIgD;IAChD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChD;wDACoD;IACpD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACnD;;2CAEuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAI/C;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAUrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAChC,QAAQ,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CAClC,GAAG,oBAAoB,CA6BvB;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,UAAU,EACV,aAAkC,GACnC,EAAE,wBAAwB,2CA6K1B"}
@@ -0,0 +1,199 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ "use client";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ /**
5
+ * Wave 3 G3 — GS1 Digital Link composer panel.
6
+ *
7
+ * GS1 Digital Link (ISO/IEC 15459 + GS1 Digital Link Standard v1.3) is
8
+ * a structured URL form for product identifiers that can be encoded
9
+ * into a single QR code and resolved on-device. Shape:
10
+ *
11
+ * `https://<domain>/01/<gtin>[/22/<cpv>][/10/<lot>][/21/<serial>][?...]`
12
+ *
13
+ * Path AIs (Application Identifiers) carry the "primary key" segments
14
+ * — `01` (GTIN) is mandatory, then `22`/`10`/`21` in canonical order.
15
+ * Query-string AIs carry the long tail (expiration date, batch, etc.)
16
+ * via `<ai>=<value>` pairs.
17
+ *
18
+ * The panel handles URL composition only — emitting the URL via
19
+ * `onLink`. Hosts that also want a rendered QR bitmap wire the Wave 3
20
+ * G2g {@link import("./BarcodeGeneratorPanel").BarcodeRenderFn} shape
21
+ * via the optional `renderer` prop and receive the bitmap via
22
+ * `onRendered`. This keeps the composer free of any rendering library
23
+ * dep and lets G2g + G3 share one adapter.
24
+ *
25
+ * @public
26
+ */
27
+ import { useState } from "react";
28
+ /**
29
+ * The GS1 community-default resolver domain. Re-exported for hosts
30
+ * that want to display it as a hint or seed a settings UI.
31
+ *
32
+ * @public
33
+ */
34
+ export const DEFAULT_GS1_DOMAIN = "id.gs1.org";
35
+ const PATH_AIS_IN_ORDER = ["22", "10", "21"];
36
+ /**
37
+ * Validate a GS1 AI 17 (expiration date) value: must be exactly six
38
+ * digits in `YYMMDD` order with month 1-12 and a real calendar day.
39
+ * Year is two digits per the GS1 spec so the validator checks the
40
+ * day against an arbitrary 21st-century year — the year value itself
41
+ * does not constrain the calendar.
42
+ *
43
+ * Exported alongside {@link composeGs1DigitalLink} so server-side
44
+ * callers can reject invalid expiries before composing.
45
+ *
46
+ * @public
47
+ */
48
+ export function isValidGs1Ai17(value) {
49
+ if (!/^\d{6}$/.test(value))
50
+ return false;
51
+ const yy = Number(value.slice(0, 2));
52
+ const mm = Number(value.slice(2, 4));
53
+ const dd = Number(value.slice(4, 6));
54
+ if (mm < 1 || mm > 12)
55
+ return false;
56
+ if (dd < 1 || dd > 31)
57
+ return false;
58
+ const fullYear = 2000 + yy;
59
+ const date = new Date(fullYear, mm - 1, dd);
60
+ return date.getFullYear() === fullYear && date.getMonth() === mm - 1 && date.getDate() === dd;
61
+ }
62
+ /**
63
+ * Compose a canonical GS1 Digital Link URL from a GTIN + optional
64
+ * path-AIs + optional query-AIs. Pure function — exported so server
65
+ * components (Next.js RSC, Astro frontmatter) can pre-compute URLs
66
+ * without bundling the panel.
67
+ *
68
+ * Path-AI order is `22 → 10 → 21` per the GS1 Digital Link Standard;
69
+ * any other AIs flow into the query string in the order supplied.
70
+ *
71
+ * @public
72
+ */
73
+ export function composeGs1DigitalLink(input) {
74
+ // Normalize the host: trim whitespace, strip any explicit
75
+ // `http(s)://` scheme the user pasted (the composer always emits
76
+ // `https://`), then drop trailing slashes. Empty / whitespace-only
77
+ // input falls back to the GS1 community default rather than
78
+ // producing the malformed `https:///01/...`.
79
+ const trimmed = input.domain.trim().replace(/^https?:\/\//i, "");
80
+ const stripped = trimmed.replace(/\/+$/, "");
81
+ const domain = stripped === "" ? DEFAULT_GS1_DOMAIN : stripped;
82
+ const pathAisByCode = new Map();
83
+ for (const entry of input.pathAis ?? []) {
84
+ pathAisByCode.set(entry.ai, entry.value);
85
+ }
86
+ let pathSegment = `/01/${encodeURIComponent(input.gtin)}`;
87
+ for (const code of PATH_AIS_IN_ORDER) {
88
+ const value = pathAisByCode.get(code);
89
+ if (value !== undefined && value !== "") {
90
+ pathSegment += `/${code}/${encodeURIComponent(value)}`;
91
+ }
92
+ }
93
+ const querySegments = (input.queryAis ?? [])
94
+ .filter((entry) => entry.value !== "")
95
+ .map((entry) => `${encodeURIComponent(entry.ai)}=${encodeURIComponent(entry.value)}`);
96
+ const querySegment = querySegments.length > 0 ? `?${querySegments.join("&")}` : "";
97
+ return {
98
+ url: `https://${domain}${pathSegment}${querySegment}`,
99
+ pathSegment,
100
+ querySegment,
101
+ };
102
+ }
103
+ /**
104
+ * GS1 Digital Link composer panel. The user picks a domain, types a
105
+ * GTIN, optionally adds CPV / lot / serial path-AIs and free-form
106
+ * query-AIs; the panel emits the composed URL via `onLink`. When the
107
+ * host wires a `renderer`, a "Generate QR" button hands the URL to
108
+ * the adapter with `format: "QR"` and forwards the bitmap to
109
+ * `onRendered`.
110
+ *
111
+ * @public
112
+ */
113
+ export function Gs1DigitalLinkPanel({ renderer, onLink, onRendered, defaultDomain = DEFAULT_GS1_DOMAIN, }) {
114
+ const [domain, setDomain] = useState(defaultDomain);
115
+ const [gtin, setGtin] = useState("");
116
+ const [cpv, setCpv] = useState("");
117
+ const [lot, setLot] = useState("");
118
+ const [serial, setSerial] = useState("");
119
+ const [expiration, setExpiration] = useState("");
120
+ const [busy, setBusy] = useState(false);
121
+ const [error, setError] = useState(null);
122
+ /**
123
+ * Validate the form. Returns the composed result on success or an
124
+ * error message; null `result` means the caller must not proceed.
125
+ * Centralised here so `handlePreview` and `handleGenerate` share
126
+ * the same checks.
127
+ */
128
+ function validateAndCompose() {
129
+ const trimmedGtin = gtin.trim();
130
+ if (!trimmedGtin) {
131
+ return { result: null, error: "GTIN is required." };
132
+ }
133
+ // GS1 GTINs are 8 / 12 / 13 / 14 digits (GTIN-8, UPC-A, EAN-13,
134
+ // GTIN-14). The check digit isn't validated here — leave that to
135
+ // the host renderer / server, which can also surface check-digit
136
+ // errors with a specific message.
137
+ if (!/^\d{8}$|^\d{12}$|^\d{13}$|^\d{14}$/.test(trimmedGtin)) {
138
+ return {
139
+ result: null,
140
+ error: "GTIN must be 8, 12, 13, or 14 digits.",
141
+ };
142
+ }
143
+ const trimmedExpiration = expiration.trim();
144
+ if (trimmedExpiration !== "" && !isValidGs1Ai17(trimmedExpiration)) {
145
+ return {
146
+ result: null,
147
+ error: "Expiration date (AI 17) must be a valid YYMMDD value.",
148
+ };
149
+ }
150
+ return {
151
+ error: null,
152
+ result: composeGs1DigitalLink({
153
+ domain,
154
+ gtin: trimmedGtin,
155
+ pathAis: [
156
+ ...(cpv.trim() ? [{ ai: "22", value: cpv.trim() }] : []),
157
+ ...(lot.trim() ? [{ ai: "10", value: lot.trim() }] : []),
158
+ ...(serial.trim() ? [{ ai: "21", value: serial.trim() }] : []),
159
+ ],
160
+ queryAis: trimmedExpiration ? [{ ai: "17", value: trimmedExpiration }] : [],
161
+ }),
162
+ };
163
+ }
164
+ function handlePreview() {
165
+ const { result, error: validationError } = validateAndCompose();
166
+ if (!result) {
167
+ setError(validationError);
168
+ return;
169
+ }
170
+ setError(null);
171
+ onLink?.(result);
172
+ }
173
+ async function handleGenerate() {
174
+ const { result, error: validationError } = validateAndCompose();
175
+ if (!result) {
176
+ setError(validationError);
177
+ return;
178
+ }
179
+ if (!renderer) {
180
+ setError("No QR renderer wired.");
181
+ return;
182
+ }
183
+ setBusy(true);
184
+ setError(null);
185
+ try {
186
+ onLink?.(result);
187
+ const bitmap = await renderer({ format: "QR", payload: result.url });
188
+ onRendered?.(bitmap);
189
+ }
190
+ catch (err) {
191
+ setError(err instanceof Error ? err.message : String(err));
192
+ }
193
+ finally {
194
+ setBusy(false);
195
+ }
196
+ }
197
+ return (_jsxs("div", { "data-testid": "gs1-digital-link-panel", style: { padding: "0.5rem" }, children: [_jsx("h3", { style: { margin: "0 0 0.5rem 0" }, children: "GS1 Digital Link" }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Resolver domain", _jsx("input", { type: "text", value: domain, onChange: (e) => setDomain(e.target.value), "aria-label": "GS1 resolver domain", style: { marginLeft: "0.5rem", width: "16em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["GTIN (AI 01)", _jsx("input", { type: "text", value: gtin, onChange: (e) => setGtin(e.target.value), "aria-label": "GTIN", style: { marginLeft: "0.5rem", width: "16em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["CPV (AI 22, optional)", _jsx("input", { type: "text", value: cpv, onChange: (e) => setCpv(e.target.value), "aria-label": "Consumer product variant", style: { marginLeft: "0.5rem", width: "12em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Lot (AI 10, optional)", _jsx("input", { type: "text", value: lot, onChange: (e) => setLot(e.target.value), "aria-label": "Lot or batch", style: { marginLeft: "0.5rem", width: "12em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Serial (AI 21, optional)", _jsx("input", { type: "text", value: serial, onChange: (e) => setSerial(e.target.value), "aria-label": "Serial number", style: { marginLeft: "0.5rem", width: "12em" } })] }), _jsxs("label", { style: { display: "block", marginBottom: "0.5rem" }, children: ["Expiration date (AI 17, YYMMDD, optional)", _jsx("input", { type: "text", value: expiration, onChange: (e) => setExpiration(e.target.value), "aria-label": "Expiration date", style: { marginLeft: "0.5rem", width: "8em" } })] }), _jsx("button", { type: "button", onClick: handlePreview, style: { padding: "0.4rem 0.8rem", marginRight: "0.5rem" }, children: "Preview URL" }), renderer && (_jsx("button", { type: "button", onClick: handleGenerate, disabled: busy, style: { padding: "0.4rem 0.8rem" }, children: busy ? "Generating…" : "Generate QR" })), error && (_jsx("div", { role: "alert", style: { marginTop: "0.5rem", color: "#a00" }, children: error }))] }));
198
+ }
199
+ //# sourceMappingURL=Gs1DigitalLinkPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Gs1DigitalLinkPanel.js","sourceRoot":"","sources":["../../src/components/Gs1DigitalLinkPanel.tsx"],"names":[],"mappings":"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"}