From 007983737d65483cc1264df56ab66f57868eb350 Mon Sep 17 00:00:00 2001 From: Yordan Date: Thu, 6 Nov 2025 15:16:30 +0100 Subject: [PATCH 1/8] feat: add download functionality and improve architecture --- .../src/BarcodeGenerator.tsx | 44 +++++++------ .../src/BarcodeGenerator.xml | 4 ++ .../src/components/BarcodeRenderer.tsx | 33 ++++++++++ .../src/components/CodeRenderer.tsx | 46 ++++++++++++++ .../src/components/QRCodeRenderer.tsx | 17 ++++++ .../src/hooks/useDownload.ts | 61 +++++++++++++++++++ .../typings/BarcodeGeneratorProps.d.ts | 2 + 7 files changed, 187 insertions(+), 20 deletions(-) create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx index a6e3c41aae..3a260e9d64 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx @@ -1,7 +1,7 @@ -import JsBarcode from "jsbarcode"; -import { QRCodeSVG } from "qrcode.react"; -import { ReactElement, useEffect, useRef } from "react"; +import { ReactElement, useRef } from "react"; import { BarcodeGeneratorContainerProps } from "../typings/BarcodeGeneratorProps"; +import { useDownload } from "./hooks/useDownload"; +import { CodeRenderer } from "./components/CodeRenderer"; import "./ui/BarcodeGenerator.scss"; @@ -13,9 +13,11 @@ export default function BarcodeGenerator({ codeMargin, displayValue, qrSize, - tabIndex + tabIndex, + allowDownload }: BarcodeGeneratorContainerProps): ReactElement { const svgRef = useRef(null); + const qrContainerRef = useRef(null); const value = codeValue?.status === "available" ? codeValue.value : ""; const width = codeWidth ?? 128; @@ -23,23 +25,10 @@ export default function BarcodeGenerator({ const format = codeFormat ?? "CODE128"; const margin = codeMargin ?? 2; const showValue = displayValue ?? false; + const download = allowDownload ?? false; const size = qrSize ?? 128; - useEffect(() => { - if (format !== "QRCode" && svgRef.current && value) { - try { - JsBarcode(svgRef.current, value, { - format, - width, - height, - margin, - displayValue: showValue - }); - } catch (error) { - console.error("Error generating barcode:", error); - } - } - }, [value, width, height, format, margin, showValue]); + const { downloadSVG } = useDownload({ format, svgRef, qrContainerRef }); if (!value) { return No barcode value provided; @@ -47,7 +36,22 @@ export default function BarcodeGenerator({ return (
- {format === "QRCode" ? : } + + {download && ( + + )}
); } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml index d4ea655914..54fef1ac5c 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml @@ -24,6 +24,10 @@ Custom Format + + Allow download + Adds a download button + diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx new file mode 100644 index 0000000000..86f2598593 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx @@ -0,0 +1,33 @@ +import JsBarcode from "jsbarcode"; +import { forwardRef, useEffect } from "react"; + +interface BarcodeRendererProps { + value: string; + width: number; + height: number; + format: string; + margin: number; + displayValue: boolean; +} + +export const BarcodeRenderer = forwardRef( + ({ value, width, height, format, margin, displayValue }, ref) => { + useEffect(() => { + if (ref && typeof ref !== "function" && ref.current && value) { + try { + JsBarcode(ref.current, value, { + format, + width, + height, + margin, + displayValue + }); + } catch (error) { + console.error("Error generating barcode:", error); + } + } + }, [value, width, height, format, margin, displayValue, ref]); + + return ; + } +); diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx new file mode 100644 index 0000000000..300224d854 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx @@ -0,0 +1,46 @@ +import { ReactElement } from "react"; +import { QRCodeRenderer } from "./QRCodeRenderer"; +import { BarcodeRenderer } from "./BarcodeRenderer"; + +interface CodeRendererProps { + format: string; + value: string; + // QR Code props + size: number; + // Barcode props + width: number; + height: number; + margin: number; + displayValue: boolean; + // Refs for download functionality + svgRef: React.RefObject; + qrContainerRef: React.RefObject; +} + +export function CodeRenderer({ + format, + value, + size, + width, + height, + margin, + displayValue, + svgRef, + qrContainerRef +}: CodeRendererProps): ReactElement { + if (format === "QRCode") { + return ; + } + + return ( + + ); +} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx new file mode 100644 index 0000000000..38ffe8c792 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx @@ -0,0 +1,17 @@ +import { QRCodeSVG } from "qrcode.react"; +import { forwardRef } from "react"; + +const QRCode = QRCodeSVG as React.ComponentType<{ value: string; size: number }>; + +interface QRCodeRendererProps { + value: string; + size: number; +} + +export const QRCodeRenderer = forwardRef(({ value, size }, ref) => { + return ( +
+ +
+ ); +}); diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts new file mode 100644 index 0000000000..6bec31a094 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts @@ -0,0 +1,61 @@ +import { useCallback } from "react"; + +interface UseDownloadParams { + format: string; + svgRef: React.RefObject; + qrContainerRef: React.RefObject; +} +interface UseDownloadReturn { + downloadSVG: () => void; +} + +export function useDownload({ format, svgRef, qrContainerRef }: UseDownloadParams): UseDownloadReturn { + const downloadSVG = useCallback(() => { + let svgElement: SVGSVGElement | null = null; + let filename = ""; + + if (format === "QRCode") { + // Find the SVG element inside the QR container + svgElement = qrContainerRef.current?.querySelector("svg") || null; + filename = "qrcode.svg"; + } else { + svgElement = svgRef.current; + filename = "barcode.svg"; + } + + if (!svgElement) { + console.error("SVG element not found for download"); + return; + } + + try { + // Clone the SVG to avoid modifying the original + const clonedSvg = svgElement.cloneNode(true) as SVGSVGElement; + + // Ensure proper SVG namespace and attributes + clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + + // Serialize the SVG + const serializer = new XMLSerializer(); + const svgString = serializer.serializeToString(clonedSvg); + + // Create download link + const blob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" }); + const url = URL.createObjectURL(blob); + + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // Clean up the URL object + URL.revokeObjectURL(url); + } catch (error) { + console.error("Error downloading SVG:", error); + } + }, [format, svgRef, qrContainerRef]); + + return { downloadSVG }; +} diff --git a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts index ff747ea4cf..4aae96ff1d 100644 --- a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts +++ b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts @@ -17,6 +17,7 @@ export interface BarcodeGeneratorContainerProps { tabIndex?: number; codeValue: EditableValue; codeFormat: CodeFormatEnum; + allowDownload: boolean; displayValue: boolean; codeWidth: number; codeHeight: number; @@ -38,6 +39,7 @@ export interface BarcodeGeneratorPreviewProps { translate: (text: string) => string; codeValue: string; codeFormat: CodeFormatEnum; + allowDownload: boolean; displayValue: boolean; codeWidth: number | null; codeHeight: number | null; From 61ea1cf358654b484f320b8393854557d931a7fe Mon Sep 17 00:00:00 2001 From: Yordan Date: Fri, 7 Nov 2025 17:00:45 +0100 Subject: [PATCH 2/8] feat: enhance generator with qr styling options and improve download support --- .../src/BarcodeGenerator.editorConfig.ts | 21 ++- .../src/BarcodeGenerator.tsx | 39 +++++- .../src/BarcodeGenerator.xml | 98 ++++++++++--- .../src/components/CodeRenderer.tsx | 52 +++++-- .../src/components/QRCodeRenderer.tsx | 78 +++++++++-- .../src/hooks/useDownload.ts | 131 ++++++++++++++---- .../typings/BarcodeGeneratorProps.d.ts | 37 ++++- 7 files changed, 377 insertions(+), 79 deletions(-) diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts index 077e6c5783..337c0bf7f8 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts @@ -12,9 +12,26 @@ export type Problem = { export function getProperties(values: BarcodeGeneratorPreviewProps, defaultProperties: Properties): Properties { if (values.codeFormat === "QRCode") { - hidePropertiesIn(defaultProperties, values, ["codeWidth", "codeHeight", "displayValue"]); + hidePropertiesIn(defaultProperties, values, ["codeWidth", "codeHeight", "displayValue", "codeMargin"]); } else { - hidePropertiesIn(defaultProperties, values, ["qrSize"]); + hidePropertiesIn(defaultProperties, values, ["qrImage", "qrSize", "qrMargin", "qrLevel", "qrTitle"]); + } + + if (values.codeFormat !== "QRCode" || !values.qrImage) { + hidePropertiesIn(defaultProperties, values, [ + "qrImageSrc", + "qrImageCenter", + "qrImageWidth", + "qrImageHeight", + "qrImageX", + "qrImageY", + "qrImageOpacity", + "qrImageExcavate" + ]); + } + + if (values.qrImageCenter) { + hidePropertiesIn(defaultProperties, values, ["qrImageX", "qrImageY"]); } if (values.codeFormat !== "Custom") { diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx index 3a260e9d64..986e8978b3 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx @@ -10,9 +10,20 @@ export default function BarcodeGenerator({ codeWidth, codeHeight, codeFormat, + customCodeFormat, codeMargin, displayValue, qrSize, + qrMargin, + qrTitle, + qrLevel, + qrImageSrc, + qrImageX, + qrImageY, + qrImageHeight, + qrImageWidth, + qrImageOpacity, + qrImageExcavate, tabIndex, allowDownload }: BarcodeGeneratorContainerProps): ReactElement { @@ -22,11 +33,21 @@ export default function BarcodeGenerator({ const value = codeValue?.status === "available" ? codeValue.value : ""; const width = codeWidth ?? 128; const height = codeHeight ?? 128; - const format = codeFormat ?? "CODE128"; + const format = codeFormat === "Custom" ? (customCodeFormat ?? "CODE128") : (codeFormat ?? "CODE128"); const margin = codeMargin ?? 2; const showValue = displayValue ?? false; const download = allowDownload ?? false; - const size = qrSize ?? 128; + const qrsize = qrSize ?? 128; + const qrmargin = qrMargin ?? 2; + const qrtitle = qrTitle ?? ""; + const qrlevel = qrLevel ?? "L"; + const qrimageSrc = qrImageSrc?.status === "available" && qrImageSrc.value ? qrImageSrc.value.uri : ""; + const qrimageX = qrImageX === 0 ? undefined : qrImageX; + const qrimageY = qrImageY === 0 ? undefined : qrImageY; + const qrimageHeight = qrImageHeight ?? 24; + const qrimageWidth = qrImageWidth ?? 24; + const qrimageOpacity = qrImageOpacity?.toNumber() ?? 1; + const qrimageExcavate = qrImageExcavate ?? true; const { downloadSVG } = useDownload({ format, svgRef, qrContainerRef }); @@ -37,13 +58,23 @@ export default function BarcodeGenerator({ return (
diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml index 54fef1ac5c..f351fa3fb1 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml @@ -21,7 +21,25 @@ Barcode QR Code - Custom Format + Custom + + + + Custom Format + Choose between barcode types format + + CODE128 + EAN-13 + EAN-8 + EAN-5 + EAN-2 + UPC + CODE39 + ITF-14 + MSI + Pharmacode + Codabar + CODE93 @@ -42,35 +60,69 @@ Code height In pixels + + Margin size + In pixels + QR Size The size of the QR box - + Margin size - In pixels + + + + Title + Used for accessibility reasons. + + + Level + The Error Correction Level to use + + L + M + Q + H + + + + Image + Include an image on top the QR code + + + Image source + URL or path to the image to display on the QR code + + + Center image + Center the image in the QR code + + + Image X position + Horizontal position of the image + + + Image Y position + Vertical position of the image + + + Image height + Height of the image in pixels + + + Image width + Width of the image in pixels + + + Image opacity + Opacity of the image (0.0 to 1.0) + + + Excavate background + Remove QR code dots behind the image - - - Barcode Format - Choose between barcode types format - - CODE128 - EAN-13 - EAN-8 - EAN-5 - EAN-2 - UPC - CODE39 - ITF-14 - MSI - Pharmacode - Codabar - CODE93 - - - diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx index 300224d854..95e73fc5c0 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx @@ -3,44 +3,80 @@ import { QRCodeRenderer } from "./QRCodeRenderer"; import { BarcodeRenderer } from "./BarcodeRenderer"; interface CodeRendererProps { - format: string; value: string; - // QR Code props - size: number; + format: string; // Barcode props width: number; height: number; margin: number; displayValue: boolean; + // QR Code props + qrsize: number; + qrmargin: number; + qrtitle: string; + qrlevel: string; + qrimageSrc: string; + qrimageX?: number; + qrimageY?: number; + qrimageHeight: number; + qrimageWidth: number; + qrimageOpacity: number; + qrimageExcavate: boolean; // Refs for download functionality svgRef: React.RefObject; qrContainerRef: React.RefObject; } export function CodeRenderer({ - format, value, - size, + format, width, height, margin, displayValue, + qrsize, + qrmargin, + qrtitle, + qrlevel, + qrimageSrc, + qrimageX, + qrimageY, + qrimageHeight, + qrimageWidth, + qrimageOpacity, + qrimageExcavate, svgRef, qrContainerRef }: CodeRendererProps): ReactElement { if (format === "QRCode") { - return ; + return ( + + ); } return ( ); } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx index 38ffe8c792..d1d2dc32d1 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx @@ -1,17 +1,79 @@ import { QRCodeSVG } from "qrcode.react"; import { forwardRef } from "react"; -const QRCode = QRCodeSVG as React.ComponentType<{ value: string; size: number }>; +const QRCode = QRCodeSVG as React.ComponentType<{ + value: string; + size: number; + level: string; + marginSize: number; + title: string; + imageSettings?: { + src: string; + x?: number; + y?: number; + height: number; + width: number; + opacity: number; + excavate: boolean; + }; +}>; interface QRCodeRendererProps { value: string; size: number; + margin: number; + title: string; + level: string; + imageSrc?: string; + imageX?: number; + imageY?: number; + imageHeight: number; + imageWidth: number; + imageOpacity: number; + imageExcavate: boolean; } -export const QRCodeRenderer = forwardRef(({ value, size }, ref) => { - return ( -
- -
- ); -}); +export const QRCodeRenderer = forwardRef( + ( + { + value, + size, + margin, + title, + level, + imageSrc, + imageX, + imageY, + imageHeight, + imageWidth, + imageOpacity, + imageExcavate + }, + ref + ) => { + const imageSettings = imageSrc + ? { + src: imageSrc, + x: imageX, + y: imageY, + height: imageHeight, + width: imageWidth, + opacity: imageOpacity, + excavate: imageExcavate + } + : undefined; + + return ( +
+ +
+ ); + } +); diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts index 6bec31a094..ed9197b27c 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts @@ -1,57 +1,130 @@ import { useCallback } from "react"; +// Constants +const NAMESPACES = { + SVG: "http://www.w3.org/2000/svg", + XLINK: "http://www.w3.org/1999/xlink" +} as const; + +const FILENAMES = { + QRCode: "qrcode.svg", + default: "barcode.svg" +} as const; + interface UseDownloadParams { format: string; svgRef: React.RefObject; qrContainerRef: React.RefObject; } interface UseDownloadReturn { - downloadSVG: () => void; + downloadSVG: () => Promise; } -export function useDownload({ format, svgRef, qrContainerRef }: UseDownloadParams): UseDownloadReturn { - const downloadSVG = useCallback(() => { - let svgElement: SVGSVGElement | null = null; - let filename = ""; - - if (format === "QRCode") { - // Find the SVG element inside the QR container - svgElement = qrContainerRef.current?.querySelector("svg") || null; - filename = "qrcode.svg"; - } else { - svgElement = svgRef.current; - filename = "barcode.svg"; +// Private helper functions + +// Get the appropriate SVG element based on format +const getSvgElement = ( + format: string, + svgRef: React.RefObject, + qrContainerRef: React.RefObject +): SVGSVGElement | null => { + if (format === "QRCode") { + return qrContainerRef.current?.querySelector("svg") || null; + } + return svgRef.current; +}; + +// Get filename based on format +const getFilename = (format: string): string => { + return format === "QRCode" ? FILENAMES.QRCode : FILENAMES.default; +}; + +// Prepare SVG for download by setting namespaces +const prepareSvgForDownload = (svgElement: SVGSVGElement): SVGSVGElement => { + const clonedSvg = svgElement.cloneNode(true) as SVGSVGElement; + clonedSvg.setAttribute("xmlns", NAMESPACES.SVG); + clonedSvg.setAttribute("xmlns:xlink", NAMESPACES.XLINK); + return clonedSvg; +}; + +// Convert image URL to base64 +const convertImageToBase64 = async (url: string): Promise => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`); } + const blob = await response.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = () => reject(new Error("Failed to convert image to base64")); + reader.readAsDataURL(blob); + }); + } catch (error) { + console.warn("Failed to convert image to base64:", error); + return url; // Return original URL as fallback + } +}; +// Check if URL is external (http/https) +const isExternalUrl = (url: string): boolean => { + return url.startsWith("http://") || url.startsWith("https://"); +}; + +// Convert overlay images to base64 for QR codes +const processQRImages = async (clonedSvg: SVGSVGElement): Promise => { + const imageElement = clonedSvg.querySelector("image"); + if (imageElement) { + const hrefValue = imageElement.getAttribute("href") || imageElement.getAttribute("xlink:href"); + if (hrefValue && isExternalUrl(hrefValue)) { + const base64 = await convertImageToBase64(hrefValue); + // Use modern href attribute and remove any existing xlink:href + imageElement.setAttribute("href", base64); + imageElement.removeAttribute("xlink:href"); + } + } +}; + +// Create and trigger download +const downloadBlob = (blob: Blob, filename: string): void => { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +}; + +export function useDownload({ format, svgRef, qrContainerRef }: UseDownloadParams): UseDownloadReturn { + const downloadSVG = useCallback(async () => { + // Get the appropriate SVG element + const svgElement = getSvgElement(format, svgRef, qrContainerRef); if (!svgElement) { console.error("SVG element not found for download"); return; } try { - // Clone the SVG to avoid modifying the original - const clonedSvg = svgElement.cloneNode(true) as SVGSVGElement; + // Prepare SVG for download (clone and set namespaces) + const clonedSvg = prepareSvgForDownload(svgElement); - // Ensure proper SVG namespace and attributes - clonedSvg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + // Process QR code images if needed + if (format === "QRCode") { + await processQRImages(clonedSvg); + } // Serialize the SVG const serializer = new XMLSerializer(); const svgString = serializer.serializeToString(clonedSvg); - // Create download link + // Create download blob and trigger download const blob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" }); - const url = URL.createObjectURL(blob); - - const link = document.createElement("a"); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - - // Clean up the URL object - URL.revokeObjectURL(url); + const filename = getFilename(format); + downloadBlob(blob, filename); } catch (error) { console.error("Error downloading SVG:", error); } diff --git a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts index 4aae96ff1d..928a1b1913 100644 --- a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts +++ b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts @@ -4,12 +4,15 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { EditableValue } from "mendix"; +import { DynamicValue, EditableValue, WebImage } from "mendix"; +import { Big } from "big.js"; export type CodeFormatEnum = "CODE128" | "QRCode" | "Custom"; export type CustomCodeFormatEnum = "CODE128" | "EAN13" | "EAN8" | "EAN5" | "EAN2" | "UPC" | "CODE39" | "ITF14" | "MSI" | "pharmacode" | "codabar" | "CODE93"; +export type QrLevelEnum = "L" | "M" | "Q" | "H"; + export interface BarcodeGeneratorContainerProps { name: string; class: string; @@ -17,13 +20,25 @@ export interface BarcodeGeneratorContainerProps { tabIndex?: number; codeValue: EditableValue; codeFormat: CodeFormatEnum; + customCodeFormat: CustomCodeFormatEnum; allowDownload: boolean; displayValue: boolean; codeWidth: number; codeHeight: number; - qrSize: number; codeMargin: number; - customCodeFormat: CustomCodeFormatEnum; + qrSize: number; + qrMargin: number; + qrTitle: string; + qrLevel: QrLevelEnum; + qrImage: boolean; + qrImageSrc: DynamicValue; + qrImageCenter: boolean; + qrImageX: number; + qrImageY: number; + qrImageHeight: number; + qrImageWidth: number; + qrImageOpacity: Big; + qrImageExcavate: boolean; } export interface BarcodeGeneratorPreviewProps { @@ -39,11 +54,23 @@ export interface BarcodeGeneratorPreviewProps { translate: (text: string) => string; codeValue: string; codeFormat: CodeFormatEnum; + customCodeFormat: CustomCodeFormatEnum; allowDownload: boolean; displayValue: boolean; codeWidth: number | null; codeHeight: number | null; - qrSize: number | null; codeMargin: number | null; - customCodeFormat: CustomCodeFormatEnum; + qrSize: number | null; + qrMargin: number | null; + qrTitle: string; + qrLevel: QrLevelEnum; + qrImage: boolean; + qrImageSrc: { type: "static"; imageUrl: string; } | { type: "dynamic"; entity: string; } | null; + qrImageCenter: boolean; + qrImageX: number | null; + qrImageY: number | null; + qrImageHeight: number | null; + qrImageWidth: number | null; + qrImageOpacity: number | null; + qrImageExcavate: boolean; } From e1ea92d1a4ca3a9991d12a27eca8c57c62d6c4e9 Mon Sep 17 00:00:00 2001 From: Yordan Date: Mon, 10 Nov 2025 16:47:34 +0100 Subject: [PATCH 3/8] feat: code cleanup & enhance barcode config and subconfig --- .../barcode-generator-web/package.json | 3 +- .../src/BarcodeGenerator.editorConfig.ts | 30 ++++- .../src/BarcodeGenerator.tsx | 21 ++++ .../src/BarcodeGenerator.xml | 46 ++++++- .../src/__tests__/BarcodeGenerator.spec.tsx | 73 ++++++++++- .../src/components/BarcodeRenderer.tsx | 118 ++++++++++++++++-- .../src/components/CodeRenderer.tsx | 30 ++++- .../src/components/QRCodeRenderer.tsx | 4 +- .../src/hooks/useDownload.ts | 25 ++-- .../typings/BarcodeGeneratorProps.d.ts | 18 ++- pnpm-lock.yaml | 3 + 11 files changed, 326 insertions(+), 45 deletions(-) diff --git a/packages/pluggableWidgets/barcode-generator-web/package.json b/packages/pluggableWidgets/barcode-generator-web/package.json index f11aecb8cb..bf1e3a0227 100644 --- a/packages/pluggableWidgets/barcode-generator-web/package.json +++ b/packages/pluggableWidgets/barcode-generator-web/package.json @@ -56,6 +56,7 @@ "@mendix/widget-plugin-component-kit": "workspace:*", "@mendix/widget-plugin-platform": "workspace:*", "@mendix/widget-plugin-test-utils": "workspace:*", - "cross-env": "^7.0.3" + "cross-env": "^7.0.3", + "eslint": "^9.37.0" } } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts index 337c0bf7f8..daad1d8e56 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts @@ -1,4 +1,4 @@ -import { hidePropertiesIn, Properties } from "@mendix/pluggable-widgets-tools"; +import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { BarcodeGeneratorPreviewProps } from "../typings/BarcodeGeneratorProps"; export type Problem = { @@ -30,6 +30,33 @@ export function getProperties(values: BarcodeGeneratorPreviewProps, defaultPrope ]); } + if (values.codeFormat !== "CODE128" && values.customCodeFormat !== "CODE128") { + hidePropertyIn(defaultProperties, values, "enableEan128"); + } + + if ( + values.customCodeFormat !== "EAN13" && + values.customCodeFormat !== "EAN8" && + values.customCodeFormat !== "UPC" + ) { + hidePropertyIn(defaultProperties, values, "enableFlat"); + } + + if (values.customCodeFormat !== "EAN13") { + hidePropertyIn(defaultProperties, values, "lastChar"); + } + + if (values.customCodeFormat !== "EAN13" && values.customCodeFormat !== "EAN8") { + hidePropertiesIn(defaultProperties, values, ["addonFormat", "addonValue", "addonSpacing"]); + } + if (values.addonFormat !== "EAN5" && values.addonFormat !== "EAN2") { + hidePropertiesIn(defaultProperties, values, ["addonValue", "addonSpacing"]); + } + + if (values.customCodeFormat !== "CODE39") { + hidePropertyIn(defaultProperties, values, "enableMod43"); + } + if (values.qrImageCenter) { hidePropertiesIn(defaultProperties, values, ["qrImageX", "qrImageY"]); } @@ -37,6 +64,7 @@ export function getProperties(values: BarcodeGeneratorPreviewProps, defaultPrope if (values.codeFormat !== "Custom") { hidePropertiesIn(defaultProperties, values, ["customCodeFormat"]); } + return defaultProperties; } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx index 986e8978b3..e837af114a 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx @@ -13,6 +13,13 @@ export default function BarcodeGenerator({ customCodeFormat, codeMargin, displayValue, + enableEan128, + enableFlat, + lastChar, + enableMod43, + addonValue, + addonFormat, + addonSpacing, qrSize, qrMargin, qrTitle, @@ -48,6 +55,13 @@ export default function BarcodeGenerator({ const qrimageWidth = qrImageWidth ?? 24; const qrimageOpacity = qrImageOpacity?.toNumber() ?? 1; const qrimageExcavate = qrImageExcavate ?? true; + const supportsEan128 = enableEan128 ?? false; + const supportsFlat = enableFlat ?? false; + const lastCharacter = lastChar ?? ""; + const supportsMod43 = enableMod43 ?? false; + const processedAddonValue = addonValue?.status === "available" ? addonValue.value : ""; + const processedAddonFormat = addonFormat ?? "EAN5"; + const processedAddonSpacing = addonSpacing ?? 20; const { downloadSVG } = useDownload({ format, svgRef, qrContainerRef }); @@ -64,6 +78,13 @@ export default function BarcodeGenerator({ height={height} margin={margin} displayValue={showValue} + enableEan128={supportsEan128} + enableFlat={supportsFlat} + lastChar={lastCharacter} + enableMod43={supportsMod43} + addonValue={processedAddonValue} + addonFormat={processedAddonFormat} + addonSpacing={processedAddonSpacing} qrsize={qrsize} qrmargin={qrmargin} qrlevel={qrlevel} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml index f351fa3fb1..5a8dc88633 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml @@ -31,8 +31,6 @@ CODE128 EAN-13 EAN-8 - EAN-5 - EAN-2 UPC CODE39 ITF-14 @@ -42,11 +40,49 @@ CODE93
+ + EAN-128 + Enable encoding CODE128 as GS1-128/EAN-128 + + + Flat + Enable flat barcode, skip guard bars + + + Last character + Character after the barcode + + + Mod43 + For code 39 if used with modulo 43 check digit + Allow download Adds a download button + + + Addon format + Choose between EAN-5 or EAN-2 addon format + + None + EAN-5 + EAN-2 + + + + Addon value + Value for the addon barcode (5 digits for EAN-5, 2 digits for EAN-2) + + + + + + Addon spacing + Space between main barcode and addon (in pixels) + + Display value @@ -54,11 +90,11 @@ Bar width - Width of the barcode bars + Width of a single bar Code height - In pixels + Height of the barcode Margin size @@ -74,7 +110,7 @@ Title - Used for accessibility reasons. + Used for accessibility reasons Level diff --git a/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx b/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx index ff4c4e1464..5f68f79909 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx @@ -25,12 +25,32 @@ describe("BarcodeGenerator", () => { class: "mx-barcode-generator", tabIndex: -1, codeFormat: "QRCode" as CodeFormatEnum, + customCodeFormat: "CODE128" as CustomCodeFormatEnum, + enableEan128: false, + enableFlat: false, + lastChar: "", + enableMod43: false, + allowDownload: false, displayValue: false, codeWidth: 2, codeHeight: 200, - qrSize: 128, codeMargin: 4, - customCodeFormat: "CODE128" as CustomCodeFormatEnum, + qrSize: 128, + qrMargin: 2, + qrTitle: "", + qrLevel: "L" as any, + qrImage: false, + qrImageSrc: { status: "unavailable" } as any, + qrImageCenter: true, + qrImageX: 0, + qrImageY: 0, + qrImageHeight: 24, + qrImageWidth: 24, + qrImageOpacity: { toNumber: () => 1 } as any, + qrImageExcavate: true, + addonFormat: "None" as any, + addonValue: { status: "unavailable" } as any, + addonSpacing: 20, codeValue: new EditableValueBuilder().withValue(barcodeDefaultValue).build() }; @@ -87,7 +107,7 @@ describe("BarcodeGenerator", () => { it("renders CODE128 barcode when format is not QR", () => { const props = { ...defaultProps, - codeFormat: "CODE128" as const, + codeFormat: "CODE128" as CodeFormatEnum, codeValue: { value: "123456789", status: "available" @@ -108,7 +128,11 @@ describe("BarcodeGenerator", () => { width: 2, height: 200, margin: 4, - displayValue: false + displayValue: false, + ean128: false, + flat: false, + lastChar: "", + mod43: false } ); }); @@ -207,7 +231,46 @@ describe("BarcodeGenerator", () => { width: 2, // from defaultProps height: 200, // from defaultProps margin: 4, // from defaultProps - displayValue: false + displayValue: false, + ean128: false, + flat: false, + lastChar: "", + mod43: false }); }); + + it("supports EAN addon functionality", () => { + const mockBarcodeInstance = { + EAN13: jest.fn().mockReturnThis(), + blank: jest.fn().mockReturnThis(), + EAN5: jest.fn().mockReturnThis(), + render: jest.fn() + }; + + mockJsBarcode.mockReturnValue(mockBarcodeInstance); + + const props = { + ...defaultProps, + codeFormat: "Custom" as CodeFormatEnum, + customCodeFormat: "EAN13" as any, + addonValue: { + value: "12345", + status: "available" + } as any, + addonFormat: "EAN5" as any, + addonSpacing: 25, + codeValue: { + value: "1234567890128", + status: "available" + } as any + }; + + render(); + + expect(mockJsBarcode).toHaveBeenCalled(); + expect(mockBarcodeInstance.EAN13).toHaveBeenCalledWith("1234567890128", expect.any(Object)); + expect(mockBarcodeInstance.blank).toHaveBeenCalledWith(25); + expect(mockBarcodeInstance.EAN5).toHaveBeenCalledWith("12345", expect.any(Object)); + expect(mockBarcodeInstance.render).toHaveBeenCalled(); + }); }); diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx index 86f2598593..464f974c98 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx @@ -1,6 +1,18 @@ import JsBarcode from "jsbarcode"; import { forwardRef, useEffect } from "react"; +interface BarcodeOptions { + format: string; + width: number; + height: number; + margin: number; + displayValue: boolean; + ean128?: boolean; + flat?: boolean; + lastChar?: string; + mod43?: boolean; +} + interface BarcodeRendererProps { value: string; width: number; @@ -8,25 +20,113 @@ interface BarcodeRendererProps { format: string; margin: number; displayValue: boolean; + enableEan128: boolean; + enableFlat: boolean; + lastChar: string; + enableMod43: boolean; + addonValue?: string; + addonFormat?: string; + addonSpacing?: number; } export const BarcodeRenderer = forwardRef( - ({ value, width, height, format, margin, displayValue }, ref) => { + ( + { + value, + width, + height, + format, + margin, + displayValue, + addonValue, + enableEan128, + enableFlat, + lastChar, + enableMod43, + addonFormat, + addonSpacing + }, + ref + ) => { useEffect(() => { if (ref && typeof ref !== "function" && ref.current && value) { try { - JsBarcode(ref.current, value, { - format, - width, - height, - margin, - displayValue - }); + // Check if format supports addons and addon data is provided + const supportsAddon = + addonValue && + (format === "EAN13" || format === "EAN8") && + (addonFormat === "EAN5" || addonFormat === "EAN2"); + + if (supportsAddon) { + const barcodeInstance = JsBarcode(ref.current); + + // Generate main barcode + if (format === "EAN13") { + barcodeInstance.EAN13(value, { + width, + height, + margin, + displayValue + }); + } else if (format === "EAN8") { + barcodeInstance.EAN8(value, { + width, + height, + margin, + displayValue + }); + } + + // Add spacing + barcodeInstance.blank(addonSpacing || 20); + + // Add addon + if (addonFormat === "EAN5") { + barcodeInstance.EAN5(addonValue, { + width: 1 + }); + } else if (addonFormat === "EAN2") { + barcodeInstance.EAN2(addonValue, { + width: 1 + }); + } + barcodeInstance.render(); + } else { + // Standard single barcode generation + const options: BarcodeOptions = { + format, + width, + height, + margin, + displayValue, + ean128: enableEan128, + flat: enableFlat, + lastChar, + mod43: enableMod43 + }; + + JsBarcode(ref.current, value, options); + } } catch (error) { console.error("Error generating barcode:", error); } } - }, [value, width, height, format, margin, displayValue, ref]); + }, [ + value, + width, + height, + format, + margin, + displayValue, + enableEan128, + enableFlat, + enableMod43, + lastChar, + addonValue, + addonFormat, + addonSpacing, + ref + ]); return ; } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx index 95e73fc5c0..0fbdc3d5e4 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx @@ -1,16 +1,21 @@ -import { ReactElement } from "react"; +import { ReactElement, RefObject } from "react"; import { QRCodeRenderer } from "./QRCodeRenderer"; import { BarcodeRenderer } from "./BarcodeRenderer"; interface CodeRendererProps { value: string; format: string; - // Barcode props width: number; height: number; margin: number; displayValue: boolean; - // QR Code props + enableEan128: boolean; + enableFlat: boolean; + lastChar: string; + enableMod43: boolean; + addonValue?: string; + addonFormat?: string; + addonSpacing?: number; qrsize: number; qrmargin: number; qrtitle: string; @@ -22,9 +27,8 @@ interface CodeRendererProps { qrimageWidth: number; qrimageOpacity: number; qrimageExcavate: boolean; - // Refs for download functionality - svgRef: React.RefObject; - qrContainerRef: React.RefObject; + svgRef: RefObject; + qrContainerRef: RefObject; } export function CodeRenderer({ @@ -34,6 +38,13 @@ export function CodeRenderer({ height, margin, displayValue, + enableEan128, + enableFlat, + lastChar, + enableMod43, + addonValue, + addonFormat, + addonSpacing, qrsize, qrmargin, qrtitle, @@ -76,6 +87,13 @@ export function CodeRenderer({ height={height} margin={margin} displayValue={displayValue} + enableEan128={enableEan128} + enableFlat={enableFlat} + lastChar={lastChar} + enableMod43={enableMod43} + addonValue={addonValue} + addonFormat={addonFormat} + addonSpacing={addonSpacing} ref={svgRef} /> ); diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx index d1d2dc32d1..d7e797cf7b 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx @@ -1,7 +1,7 @@ import { QRCodeSVG } from "qrcode.react"; -import { forwardRef } from "react"; +import { ComponentType, forwardRef } from "react"; -const QRCode = QRCodeSVG as React.ComponentType<{ +const QRCode = QRCodeSVG as ComponentType<{ value: string; size: number; level: string; diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts index ed9197b27c..a5aa696501 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { RefObject, useCallback } from "react"; // Constants const NAMESPACES = { @@ -13,20 +13,17 @@ const FILENAMES = { interface UseDownloadParams { format: string; - svgRef: React.RefObject; - qrContainerRef: React.RefObject; + svgRef: RefObject; + qrContainerRef: RefObject; } interface UseDownloadReturn { downloadSVG: () => Promise; } -// Private helper functions - -// Get the appropriate SVG element based on format const getSvgElement = ( format: string, - svgRef: React.RefObject, - qrContainerRef: React.RefObject + svgRef: RefObject, + qrContainerRef: RefObject ): SVGSVGElement | null => { if (format === "QRCode") { return qrContainerRef.current?.querySelector("svg") || null; @@ -34,7 +31,6 @@ const getSvgElement = ( return svgRef.current; }; -// Get filename based on format const getFilename = (format: string): string => { return format === "QRCode" ? FILENAMES.QRCode : FILENAMES.default; }; @@ -47,7 +43,6 @@ const prepareSvgForDownload = (svgElement: SVGSVGElement): SVGSVGElement => { return clonedSvg; }; -// Convert image URL to base64 const convertImageToBase64 = async (url: string): Promise => { try { const response = await fetch(url); @@ -87,7 +82,6 @@ const processQRImages = async (clonedSvg: SVGSVGElement): Promise => { } }; -// Create and trigger download const downloadBlob = (blob: Blob, filename: string): void => { const url = URL.createObjectURL(blob); const link = document.createElement("a"); @@ -101,7 +95,6 @@ const downloadBlob = (blob: Blob, filename: string): void => { export function useDownload({ format, svgRef, qrContainerRef }: UseDownloadParams): UseDownloadReturn { const downloadSVG = useCallback(async () => { - // Get the appropriate SVG element const svgElement = getSvgElement(format, svgRef, qrContainerRef); if (!svgElement) { console.error("SVG element not found for download"); @@ -109,7 +102,6 @@ export function useDownload({ format, svgRef, qrContainerRef }: UseDownloadParam } try { - // Prepare SVG for download (clone and set namespaces) const clonedSvg = prepareSvgForDownload(svgElement); // Process QR code images if needed @@ -117,12 +109,15 @@ export function useDownload({ format, svgRef, qrContainerRef }: UseDownloadParam await processQRImages(clonedSvg); } - // Serialize the SVG const serializer = new XMLSerializer(); const svgString = serializer.serializeToString(clonedSvg); // Create download blob and trigger download - const blob = new Blob([svgString], { type: "image/svg+xml;charset=utf-8" }); + const blobOptions = { + type: "image/svg+xml;charset=utf-8", + lastModified: Date.now() + }; + const blob = new Blob([svgString], blobOptions); const filename = getFilename(format); downloadBlob(blob, filename); } catch (error) { diff --git a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts index 928a1b1913..e1657e47ef 100644 --- a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts +++ b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts @@ -9,7 +9,9 @@ import { Big } from "big.js"; export type CodeFormatEnum = "CODE128" | "QRCode" | "Custom"; -export type CustomCodeFormatEnum = "CODE128" | "EAN13" | "EAN8" | "EAN5" | "EAN2" | "UPC" | "CODE39" | "ITF14" | "MSI" | "pharmacode" | "codabar" | "CODE93"; +export type CustomCodeFormatEnum = "CODE128" | "EAN13" | "EAN8" | "UPC" | "CODE39" | "ITF14" | "MSI" | "pharmacode" | "codabar" | "CODE93"; + +export type AddonFormatEnum = "None" | "EAN5" | "EAN2"; export type QrLevelEnum = "L" | "M" | "Q" | "H"; @@ -21,7 +23,14 @@ export interface BarcodeGeneratorContainerProps { codeValue: EditableValue; codeFormat: CodeFormatEnum; customCodeFormat: CustomCodeFormatEnum; + enableEan128: boolean; + enableFlat: boolean; + lastChar: string; + enableMod43: boolean; allowDownload: boolean; + addonFormat: AddonFormatEnum; + addonValue?: EditableValue; + addonSpacing: number; displayValue: boolean; codeWidth: number; codeHeight: number; @@ -55,7 +64,14 @@ export interface BarcodeGeneratorPreviewProps { codeValue: string; codeFormat: CodeFormatEnum; customCodeFormat: CustomCodeFormatEnum; + enableEan128: boolean; + enableFlat: boolean; + lastChar: string; + enableMod43: boolean; allowDownload: boolean; + addonFormat: AddonFormatEnum; + addonValue: string; + addonSpacing: number | null; displayValue: boolean; codeWidth: number | null; codeHeight: number | null; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7774b684ae..09e3e43308 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -635,6 +635,9 @@ importers: cross-env: specifier: ^7.0.3 version: 7.0.3 + eslint: + specifier: ^9.37.0 + version: 9.37.0(jiti@2.6.1) packages/pluggableWidgets/barcode-scanner-web: dependencies: From 71b857863231c049a263ef0e020609ff3b7c5b54 Mon Sep 17 00:00:00 2001 From: Yordan Date: Mon, 10 Nov 2025 17:02:55 +0100 Subject: [PATCH 4/8] fix: resolve type issues & update changelog --- .../pluggableWidgets/barcode-generator-web/CHANGELOG.md | 5 +++++ .../barcode-generator-web/src/components/CodeRenderer.tsx | 8 ++++---- .../barcode-generator-web/src/hooks/useDownload.ts | 8 ++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/pluggableWidgets/barcode-generator-web/CHANGELOG.md b/packages/pluggableWidgets/barcode-generator-web/CHANGELOG.md index 861d88dfaf..21b358e32a 100644 --- a/packages/pluggableWidgets/barcode-generator-web/CHANGELOG.md +++ b/packages/pluggableWidgets/barcode-generator-web/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Comprehensive configuration and styling settings for various barcode types +- Download functionality for barcodes + ## [1.0.0] - 2025-10-09 ### Added diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx index 0fbdc3d5e4..a168f1d99a 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx @@ -27,8 +27,8 @@ interface CodeRendererProps { qrimageWidth: number; qrimageOpacity: number; qrimageExcavate: boolean; - svgRef: RefObject; - qrContainerRef: RefObject; + svgRef: RefObject; + qrContainerRef: RefObject; } export function CodeRenderer({ @@ -62,7 +62,7 @@ export function CodeRenderer({ if (format === "QRCode") { return ( } value={value} size={qrsize} margin={qrmargin} @@ -94,7 +94,7 @@ export function CodeRenderer({ addonValue={addonValue} addonFormat={addonFormat} addonSpacing={addonSpacing} - ref={svgRef} + ref={svgRef as RefObject} /> ); } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts index a5aa696501..c251931c98 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts @@ -13,8 +13,8 @@ const FILENAMES = { interface UseDownloadParams { format: string; - svgRef: RefObject; - qrContainerRef: RefObject; + svgRef: RefObject; + qrContainerRef: RefObject; } interface UseDownloadReturn { downloadSVG: () => Promise; @@ -22,8 +22,8 @@ interface UseDownloadReturn { const getSvgElement = ( format: string, - svgRef: RefObject, - qrContainerRef: RefObject + svgRef: RefObject, + qrContainerRef: RefObject ): SVGSVGElement | null => { if (format === "QRCode") { return qrContainerRef.current?.querySelector("svg") || null; From 4e334979b165dd6a98731afcd3528166648ffa69 Mon Sep 17 00:00:00 2001 From: Yordan Date: Tue, 11 Nov 2025 13:59:03 +0100 Subject: [PATCH 5/8] refactor: improve architecture and rendering logic --- .../src/BarcodeGenerator.xml | 4 +- .../src/components/BarcodeRenderer.tsx | 89 +---------- .../src/hooks/useRenderBarcode.ts | 145 ++++++++++++++++++ .../typings/BarcodeGeneratorProps.d.ts | 2 +- 4 files changed, 155 insertions(+), 85 deletions(-) create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml index 5a8dc88633..279ea9732d 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml @@ -48,7 +48,7 @@ Flat Enable flat barcode, skip guard bars - + Last character Character after the barcode @@ -71,7 +71,7 @@ EAN-2 - + Addon value Value for the addon barcode (5 digits for EAN-5, 2 digits for EAN-2) diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx index 464f974c98..caec5a6b4d 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx @@ -1,17 +1,5 @@ -import JsBarcode from "jsbarcode"; -import { forwardRef, useEffect } from "react"; - -interface BarcodeOptions { - format: string; - width: number; - height: number; - margin: number; - displayValue: boolean; - ean128?: boolean; - flat?: boolean; - lastChar?: string; - mod43?: boolean; -} +import { forwardRef } from "react"; +import { useRenderBarcode } from "../hooks/useRenderBarcode"; interface BarcodeRendererProps { value: string; @@ -48,70 +36,8 @@ export const BarcodeRenderer = forwardRef( }, ref ) => { - useEffect(() => { - if (ref && typeof ref !== "function" && ref.current && value) { - try { - // Check if format supports addons and addon data is provided - const supportsAddon = - addonValue && - (format === "EAN13" || format === "EAN8") && - (addonFormat === "EAN5" || addonFormat === "EAN2"); - - if (supportsAddon) { - const barcodeInstance = JsBarcode(ref.current); - - // Generate main barcode - if (format === "EAN13") { - barcodeInstance.EAN13(value, { - width, - height, - margin, - displayValue - }); - } else if (format === "EAN8") { - barcodeInstance.EAN8(value, { - width, - height, - margin, - displayValue - }); - } - - // Add spacing - barcodeInstance.blank(addonSpacing || 20); - - // Add addon - if (addonFormat === "EAN5") { - barcodeInstance.EAN5(addonValue, { - width: 1 - }); - } else if (addonFormat === "EAN2") { - barcodeInstance.EAN2(addonValue, { - width: 1 - }); - } - barcodeInstance.render(); - } else { - // Standard single barcode generation - const options: BarcodeOptions = { - format, - width, - height, - margin, - displayValue, - ean128: enableEan128, - flat: enableFlat, - lastChar, - mod43: enableMod43 - }; - - JsBarcode(ref.current, value, options); - } - } catch (error) { - console.error("Error generating barcode:", error); - } - } - }, [ + useRenderBarcode( + ref, value, width, height, @@ -120,13 +46,12 @@ export const BarcodeRenderer = forwardRef( displayValue, enableEan128, enableFlat, - enableMod43, lastChar, + enableMod43, addonValue, addonFormat, - addonSpacing, - ref - ]); + addonSpacing + ); return ; } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts new file mode 100644 index 0000000000..4afb1f9aab --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts @@ -0,0 +1,145 @@ +import JsBarcode from "jsbarcode"; +import { type ForwardedRef, useEffect } from "react"; + +interface BarcodeMethodOptions { + width?: number; + height?: number; + margin?: number; + displayValue?: boolean; +} + +interface BarcodeInstance { + EAN13: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; + EAN8: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; + EAN5: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; + EAN2: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; + blank: (spacing: number) => BarcodeInstance; + render: () => void; + [key: string]: any; +} + +interface BarcodeOptions { + format: string; + width: number; + height: number; + margin: number; + displayValue: boolean; + ean128?: boolean; + flat?: boolean; + lastChar?: string; + mod43?: boolean; +} + +const createBarcodeWithAddon = ( + ref: ForwardedRef, + value: string, + mainFormat: string, + addonValue: string, + addonFormat: string, + options: BarcodeOptions, + addonSpacing: number +): void => { + if (ref && typeof ref !== "function" && ref.current) { + const barcodeInstance = JsBarcode(ref.current) as BarcodeInstance; + + // Generate main barcode dynamically + barcodeInstance[mainFormat](value, { + width: options.width, + height: options.height, + margin: options.margin, + displayValue: options.displayValue + }); + + // Add spacing + barcodeInstance.blank(addonSpacing); + + // Add addon dynamically + barcodeInstance[addonFormat](addonValue, { width: 1 }); + + barcodeInstance.render(); + } +}; + +const createStandardBarcode = (ref: ForwardedRef, value: string, options: BarcodeOptions): void => { + if (ref && typeof ref !== "function" && ref.current) { + JsBarcode(ref.current, value, options); + } +}; + +const renderBarcode = ( + ref: ForwardedRef, + value: string, + format: string, + addonValue: string | undefined, + addonFormat: string | undefined, + addonSpacing: number, + options: BarcodeOptions +): void => { + switch (addonFormat) { + case "EAN5": + createBarcodeWithAddon(ref, value, format, addonValue!, addonFormat, options, addonSpacing); + break; + + case "EAN2": + createBarcodeWithAddon(ref, value, format, addonValue!, addonFormat, options, addonSpacing); + break; + + default: + createStandardBarcode(ref, value, options); + break; + } +}; + +export const useRenderBarcode = ( + ref: ForwardedRef, + value: string, + width: number, + height: number, + format: string, + margin: number, + displayValue: boolean, + enableEan128: boolean, + enableFlat: boolean, + lastChar: string, + enableMod43: boolean, + addonValue?: string, + addonFormat?: string, + addonSpacing?: number +): void => { + useEffect(() => { + if (ref && typeof ref !== "function" && ref.current && value) { + try { + const options: BarcodeOptions = { + format, + width, + height, + margin, + displayValue, + ean128: enableEan128, + flat: enableFlat, + lastChar, + mod43: enableMod43 + }; + + renderBarcode(ref, value, format, addonValue, addonFormat, addonSpacing || 20, options); + } catch (error) { + console.error("Error generating barcode:", error); + } + } + }, [ + value, + width, + height, + format, + margin, + displayValue, + enableEan128, + enableFlat, + enableMod43, + lastChar, + addonValue, + addonFormat, + addonSpacing, + ref + ]); +}; diff --git a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts index e1657e47ef..6c0b1aecad 100644 --- a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts +++ b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts @@ -29,7 +29,7 @@ export interface BarcodeGeneratorContainerProps { enableMod43: boolean; allowDownload: boolean; addonFormat: AddonFormatEnum; - addonValue?: EditableValue; + addonValue: EditableValue; addonSpacing: number; displayValue: boolean; codeWidth: number; From 51ca766d660f839f482fdcb3dc05563e0b35ac9d Mon Sep 17 00:00:00 2001 From: Yordan Date: Tue, 11 Nov 2025 15:00:03 +0100 Subject: [PATCH 6/8] fix: improve property hiding logic --- .../src/BarcodeGenerator.editorConfig.ts | 33 ++++-- .../src/BarcodeGenerator.tsx | 108 ++---------------- .../src/components/BarcodeGeneratorInner.tsx | 54 +++++++++ .../src/config/Barcode.config.ts | 75 ++++++++++++ .../src/config/BarcodeConfigContext.tsx | 21 ++++ 5 files changed, 186 insertions(+), 105 deletions(-) create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts index daad1d8e56..1dfe503b00 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts @@ -35,25 +35,44 @@ export function getProperties(values: BarcodeGeneratorPreviewProps, defaultPrope } if ( - values.customCodeFormat !== "EAN13" && - values.customCodeFormat !== "EAN8" && - values.customCodeFormat !== "UPC" + values.codeFormat === "QRCode" || + values.codeFormat === "CODE128" || + (values.codeFormat === "Custom" && + values.customCodeFormat !== "EAN13" && + values.customCodeFormat !== "EAN8" && + values.customCodeFormat !== "UPC") ) { hidePropertyIn(defaultProperties, values, "enableFlat"); } - if (values.customCodeFormat !== "EAN13") { + if ( + values.codeFormat === "QRCode" || + values.codeFormat === "CODE128" || + (values.codeFormat === "Custom" && values.customCodeFormat !== "EAN13") + ) { hidePropertyIn(defaultProperties, values, "lastChar"); } - if (values.customCodeFormat !== "EAN13" && values.customCodeFormat !== "EAN8") { + if ( + values.codeFormat === "QRCode" || + values.codeFormat === "CODE128" || + (values.codeFormat === "Custom" && values.customCodeFormat !== "EAN13" && values.customCodeFormat !== "EAN8") + ) { hidePropertiesIn(defaultProperties, values, ["addonFormat", "addonValue", "addonSpacing"]); } - if (values.addonFormat !== "EAN5" && values.addonFormat !== "EAN2") { + if ( + values.codeFormat === "QRCode" || + values.codeFormat === "CODE128" || + (values.codeFormat === "Custom" && values.addonFormat !== "EAN5" && values.addonFormat !== "EAN2") + ) { hidePropertiesIn(defaultProperties, values, ["addonValue", "addonSpacing"]); } - if (values.customCodeFormat !== "CODE39") { + if ( + values.codeFormat === "QRCode" || + values.codeFormat === "CODE128" || + (values.codeFormat === "Custom" && values.customCodeFormat !== "CODE39") + ) { hidePropertyIn(defaultProperties, values, "enableMod43"); } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx index e837af114a..19ce98e2ff 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx @@ -1,109 +1,21 @@ -import { ReactElement, useRef } from "react"; +import { ReactElement } from "react"; import { BarcodeGeneratorContainerProps } from "../typings/BarcodeGeneratorProps"; -import { useDownload } from "./hooks/useDownload"; -import { CodeRenderer } from "./components/CodeRenderer"; +import { barcodeConfig } from "./config/Barcode.config"; +import { BarcodeConfigProvider } from "./config/BarcodeConfigContext"; +import { BarcodeGeneratorInner } from "./components/BarcodeGeneratorInner"; import "./ui/BarcodeGenerator.scss"; -export default function BarcodeGenerator({ - codeValue, - codeWidth, - codeHeight, - codeFormat, - customCodeFormat, - codeMargin, - displayValue, - enableEan128, - enableFlat, - lastChar, - enableMod43, - addonValue, - addonFormat, - addonSpacing, - qrSize, - qrMargin, - qrTitle, - qrLevel, - qrImageSrc, - qrImageX, - qrImageY, - qrImageHeight, - qrImageWidth, - qrImageOpacity, - qrImageExcavate, - tabIndex, - allowDownload -}: BarcodeGeneratorContainerProps): ReactElement { - const svgRef = useRef(null); - const qrContainerRef = useRef(null); +export default function BarcodeGenerator(props: BarcodeGeneratorContainerProps): ReactElement { + const config = barcodeConfig(props); - const value = codeValue?.status === "available" ? codeValue.value : ""; - const width = codeWidth ?? 128; - const height = codeHeight ?? 128; - const format = codeFormat === "Custom" ? (customCodeFormat ?? "CODE128") : (codeFormat ?? "CODE128"); - const margin = codeMargin ?? 2; - const showValue = displayValue ?? false; - const download = allowDownload ?? false; - const qrsize = qrSize ?? 128; - const qrmargin = qrMargin ?? 2; - const qrtitle = qrTitle ?? ""; - const qrlevel = qrLevel ?? "L"; - const qrimageSrc = qrImageSrc?.status === "available" && qrImageSrc.value ? qrImageSrc.value.uri : ""; - const qrimageX = qrImageX === 0 ? undefined : qrImageX; - const qrimageY = qrImageY === 0 ? undefined : qrImageY; - const qrimageHeight = qrImageHeight ?? 24; - const qrimageWidth = qrImageWidth ?? 24; - const qrimageOpacity = qrImageOpacity?.toNumber() ?? 1; - const qrimageExcavate = qrImageExcavate ?? true; - const supportsEan128 = enableEan128 ?? false; - const supportsFlat = enableFlat ?? false; - const lastCharacter = lastChar ?? ""; - const supportsMod43 = enableMod43 ?? false; - const processedAddonValue = addonValue?.status === "available" ? addonValue.value : ""; - const processedAddonFormat = addonFormat ?? "EAN5"; - const processedAddonSpacing = addonSpacing ?? 20; - - const { downloadSVG } = useDownload({ format, svgRef, qrContainerRef }); - - if (!value) { + if (!config.value) { return No barcode value provided; } return ( -
- - {download && ( - - )} -
+ + + ); } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx new file mode 100644 index 0000000000..a0001ab944 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx @@ -0,0 +1,54 @@ +import { ReactElement, useRef } from "react"; +import { useDownload } from "../hooks/useDownload"; +import { CodeRenderer } from "./CodeRenderer"; +import { useBarcodeConfig } from "../config/BarcodeConfigContext"; + +interface BarcodeGeneratorInnerProps { + tabIndex?: number; +} + +export function BarcodeGeneratorInner({ tabIndex }: BarcodeGeneratorInnerProps): ReactElement { + const config = useBarcodeConfig(); + const svgRef = useRef(null); + const qrContainerRef = useRef(null); + + const { downloadSVG } = useDownload({ format: config.format, svgRef, qrContainerRef }); + + return ( +
+ + {config.allowDownload && ( + + )} +
+ ); +} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts b/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts new file mode 100644 index 0000000000..7a249956a6 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts @@ -0,0 +1,75 @@ +import { BarcodeGeneratorContainerProps } from "../../typings/BarcodeGeneratorProps"; + +/** Configuration for static values that don't change at runtime. */ +export interface BarcodeConfig { + // Basic barcode properties + value: string; + width: number; + height: number; + format: string; + margin: number; + displayValue: boolean; + allowDownload: boolean; + + // Advanced barcode options + enableEan128: boolean; + enableFlat: boolean; + lastChar: string; + enableMod43: boolean; + addonValue: string; + addonFormat: string; + addonSpacing: number; + + // QR Code properties + qrSize: number; + qrMargin: number; + qrTitle: string; + qrLevel: string; + qrImageSrc: string; + qrImageX: number | undefined; + qrImageY: number | undefined; + qrImageHeight: number; + qrImageWidth: number; + qrImageOpacity: number; + qrImageExcavate: boolean; +} + +export function barcodeConfig(props: BarcodeGeneratorContainerProps): BarcodeConfig { + const value = props.codeValue?.status === "available" ? (props.codeValue.value ?? "") : ""; + const format = + props.codeFormat === "Custom" ? (props.customCodeFormat ?? "CODE128") : (props.codeFormat ?? "CODE128"); + + return Object.freeze({ + // Basic barcode properties + value, + width: props.codeWidth ?? 128, + height: props.codeHeight ?? 128, + format, + margin: props.codeMargin ?? 2, + displayValue: props.displayValue ?? false, + allowDownload: props.allowDownload ?? false, + + // Advanced barcode options + enableEan128: props.enableEan128 ?? false, + enableFlat: props.enableFlat ?? false, + lastChar: props.lastChar ?? "", + enableMod43: props.enableMod43 ?? false, + addonValue: props.addonValue?.status === "available" ? (props.addonValue.value ?? "") : "", + addonFormat: props.addonFormat, + addonSpacing: props.addonSpacing ?? 20, + + // QR Code properties + qrSize: props.qrSize ?? 128, + qrMargin: props.qrMargin ?? 2, + qrTitle: props.qrTitle ?? "", + qrLevel: props.qrLevel ?? "L", + qrImageSrc: + props.qrImageSrc?.status === "available" && props.qrImageSrc.value ? props.qrImageSrc.value.uri : "", + qrImageX: props.qrImageX === 0 ? undefined : props.qrImageX, + qrImageY: props.qrImageY === 0 ? undefined : props.qrImageY, + qrImageHeight: props.qrImageHeight ?? 24, + qrImageWidth: props.qrImageWidth ?? 24, + qrImageOpacity: props.qrImageOpacity?.toNumber() ?? 1, + qrImageExcavate: props.qrImageExcavate ?? true + }); +} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx b/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx new file mode 100644 index 0000000000..c992900679 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx @@ -0,0 +1,21 @@ +import { createContext, ReactNode, useContext } from "react"; +import { BarcodeConfig } from "./Barcode.config"; + +const BarcodeConfigContext = createContext(null); + +interface BarcodeConfigProviderProps { + config: BarcodeConfig; + children: ReactNode; +} + +export function BarcodeConfigProvider({ config, children }: BarcodeConfigProviderProps): ReactNode { + return {children}; +} + +export function useBarcodeConfig(): BarcodeConfig { + const config = useContext(BarcodeConfigContext); + if (!config) { + throw new Error("useBarcodeConfig must be used within a BarcodeConfigProvider"); + } + return config; +} From 0eedeb90299b561578344e042b756c4c317e36c5 Mon Sep 17 00:00:00 2001 From: Yordan Date: Mon, 17 Nov 2025 14:30:34 +0100 Subject: [PATCH 7/8] fix: update @types/react version in package.json --- .../src/BarcodeGenerator.tsx | 21 ++- .../src/components/Barcode.tsx | 22 +++ .../src/components/BarcodeGeneratorInner.tsx | 54 ------ .../src/components/BarcodeRenderer.tsx | 58 ------- .../src/components/CodeRenderer.tsx | 100 ------------ .../src/components/QRCode.tsx | 55 +++++++ .../src/components/QRCodeRenderer.tsx | 79 --------- .../src/config/Barcode.config.ts | 3 + .../src/config/BarcodeConfigContext.tsx | 21 --- .../src/config/BarcodeContext.tsx | 21 +++ .../src/hooks/useDownload.ts | 129 --------------- .../src/hooks/useDownloadBarcode.ts | 38 +++++ .../src/hooks/useDownloadQRCode.ts | 42 +++++ .../src/hooks/useRenderBarcode.ts | 154 ++++-------------- .../src/utils/barcodeRenderer-utils.ts | 119 ++++++++++++++ .../src/utils/download-utils.ts | 71 ++++++++ pnpm-lock.yaml | 19 +-- 17 files changed, 422 insertions(+), 584 deletions(-) create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx delete mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx delete mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx delete mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx delete mode 100644 packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx delete mode 100644 packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeContext.tsx delete mode 100644 packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadBarcode.ts create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadQRCode.ts create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/utils/barcodeRenderer-utils.ts create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/utils/download-utils.ts diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx index 19ce98e2ff..444dc6df37 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.tsx @@ -1,11 +1,22 @@ import { ReactElement } from "react"; import { BarcodeGeneratorContainerProps } from "../typings/BarcodeGeneratorProps"; import { barcodeConfig } from "./config/Barcode.config"; -import { BarcodeConfigProvider } from "./config/BarcodeConfigContext"; -import { BarcodeGeneratorInner } from "./components/BarcodeGeneratorInner"; +import { BarcodeContextProvider, useBarcodeConfig } from "./config/BarcodeContext"; +import { QRCodeRenderer } from "./components/QRCode"; +import { BarcodeRenderer } from "./components/Barcode"; import "./ui/BarcodeGenerator.scss"; +function BarcodeContainer({ tabIndex }: { tabIndex?: number }): ReactElement { + const config = useBarcodeConfig(); + + return ( +
+ {config.isQRCode ? : } +
+ ); +} + export default function BarcodeGenerator(props: BarcodeGeneratorContainerProps): ReactElement { const config = barcodeConfig(props); @@ -14,8 +25,8 @@ export default function BarcodeGenerator(props: BarcodeGeneratorContainerProps): } return ( - - - + + + ); } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx new file mode 100644 index 0000000000..3f25d8cfac --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx @@ -0,0 +1,22 @@ +import { useRenderBarcode } from "../hooks/useRenderBarcode"; +import { useDownloadBarcode } from "../hooks/useDownloadBarcode"; +import { useBarcodeConfig } from "../config/BarcodeContext"; + +import { Fragment } from "react"; + +export const BarcodeRenderer = () => { + const ref = useRenderBarcode(); + const { allowDownload } = useBarcodeConfig(); + const { downloadBarcode } = useDownloadBarcode({ ref }); + + return ( + + + {allowDownload && ( + + )} + + ); +}; diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx deleted file mode 100644 index a0001ab944..0000000000 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeGeneratorInner.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { ReactElement, useRef } from "react"; -import { useDownload } from "../hooks/useDownload"; -import { CodeRenderer } from "./CodeRenderer"; -import { useBarcodeConfig } from "../config/BarcodeConfigContext"; - -interface BarcodeGeneratorInnerProps { - tabIndex?: number; -} - -export function BarcodeGeneratorInner({ tabIndex }: BarcodeGeneratorInnerProps): ReactElement { - const config = useBarcodeConfig(); - const svgRef = useRef(null); - const qrContainerRef = useRef(null); - - const { downloadSVG } = useDownload({ format: config.format, svgRef, qrContainerRef }); - - return ( -
- - {config.allowDownload && ( - - )} -
- ); -} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx deleted file mode 100644 index caec5a6b4d..0000000000 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/BarcodeRenderer.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { forwardRef } from "react"; -import { useRenderBarcode } from "../hooks/useRenderBarcode"; - -interface BarcodeRendererProps { - value: string; - width: number; - height: number; - format: string; - margin: number; - displayValue: boolean; - enableEan128: boolean; - enableFlat: boolean; - lastChar: string; - enableMod43: boolean; - addonValue?: string; - addonFormat?: string; - addonSpacing?: number; -} - -export const BarcodeRenderer = forwardRef( - ( - { - value, - width, - height, - format, - margin, - displayValue, - addonValue, - enableEan128, - enableFlat, - lastChar, - enableMod43, - addonFormat, - addonSpacing - }, - ref - ) => { - useRenderBarcode( - ref, - value, - width, - height, - format, - margin, - displayValue, - enableEan128, - enableFlat, - lastChar, - enableMod43, - addonValue, - addonFormat, - addonSpacing - ); - - return ; - } -); diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx deleted file mode 100644 index a168f1d99a..0000000000 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/CodeRenderer.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { ReactElement, RefObject } from "react"; -import { QRCodeRenderer } from "./QRCodeRenderer"; -import { BarcodeRenderer } from "./BarcodeRenderer"; - -interface CodeRendererProps { - value: string; - format: string; - width: number; - height: number; - margin: number; - displayValue: boolean; - enableEan128: boolean; - enableFlat: boolean; - lastChar: string; - enableMod43: boolean; - addonValue?: string; - addonFormat?: string; - addonSpacing?: number; - qrsize: number; - qrmargin: number; - qrtitle: string; - qrlevel: string; - qrimageSrc: string; - qrimageX?: number; - qrimageY?: number; - qrimageHeight: number; - qrimageWidth: number; - qrimageOpacity: number; - qrimageExcavate: boolean; - svgRef: RefObject; - qrContainerRef: RefObject; -} - -export function CodeRenderer({ - value, - format, - width, - height, - margin, - displayValue, - enableEan128, - enableFlat, - lastChar, - enableMod43, - addonValue, - addonFormat, - addonSpacing, - qrsize, - qrmargin, - qrtitle, - qrlevel, - qrimageSrc, - qrimageX, - qrimageY, - qrimageHeight, - qrimageWidth, - qrimageOpacity, - qrimageExcavate, - svgRef, - qrContainerRef -}: CodeRendererProps): ReactElement { - if (format === "QRCode") { - return ( - } - value={value} - size={qrsize} - margin={qrmargin} - title={qrtitle} - level={qrlevel} - imageSrc={qrimageSrc} - imageX={qrimageX} - imageY={qrimageY} - imageHeight={qrimageHeight} - imageWidth={qrimageWidth} - imageOpacity={qrimageOpacity} - imageExcavate={qrimageExcavate} - /> - ); - } - - return ( - } - /> - ); -} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx new file mode 100644 index 0000000000..b7642ca5ea --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx @@ -0,0 +1,55 @@ +import { QRCodeSVG } from "qrcode.react"; +import { Fragment, useRef } from "react"; +import { useDownloadQrCode } from "../hooks/useDownloadQRCode"; +import { useBarcodeConfig } from "../config/BarcodeContext"; + +export const QRCodeRenderer = () => { + const ref = useRef(null); + const { downloadQrCode } = useDownloadQrCode({ ref }); + + const { + value, + allowDownload, + qrSize: size, + qrMargin: margin, + qrTitle: title, + qrLevel: level, + qrImageSrc: imageSrc, + qrImageX: imageX, + qrImageY: imageY, + qrImageHeight: imageHeight, + qrImageWidth: imageWidth, + qrImageOpacity: imageOpacity, + qrImageExcavate: imageExcavate + } = useBarcodeConfig(); + const imageSettings = imageSrc + ? { + src: imageSrc, + x: imageX, + y: imageY, + height: imageHeight, + width: imageWidth, + opacity: imageOpacity, + excavate: imageExcavate + } + : undefined; + + return ( + + + {allowDownload && ( + + )} + + ); +}; diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx deleted file mode 100644 index d7e797cf7b..0000000000 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCodeRenderer.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { QRCodeSVG } from "qrcode.react"; -import { ComponentType, forwardRef } from "react"; - -const QRCode = QRCodeSVG as ComponentType<{ - value: string; - size: number; - level: string; - marginSize: number; - title: string; - imageSettings?: { - src: string; - x?: number; - y?: number; - height: number; - width: number; - opacity: number; - excavate: boolean; - }; -}>; - -interface QRCodeRendererProps { - value: string; - size: number; - margin: number; - title: string; - level: string; - imageSrc?: string; - imageX?: number; - imageY?: number; - imageHeight: number; - imageWidth: number; - imageOpacity: number; - imageExcavate: boolean; -} - -export const QRCodeRenderer = forwardRef( - ( - { - value, - size, - margin, - title, - level, - imageSrc, - imageX, - imageY, - imageHeight, - imageWidth, - imageOpacity, - imageExcavate - }, - ref - ) => { - const imageSettings = imageSrc - ? { - src: imageSrc, - x: imageX, - y: imageY, - height: imageHeight, - width: imageWidth, - opacity: imageOpacity, - excavate: imageExcavate - } - : undefined; - - return ( -
- -
- ); - } -); diff --git a/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts b/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts index 7a249956a6..8c0149fc52 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts @@ -7,6 +7,7 @@ export interface BarcodeConfig { width: number; height: number; format: string; + isQRCode: boolean; margin: number; displayValue: boolean; allowDownload: boolean; @@ -38,6 +39,7 @@ export function barcodeConfig(props: BarcodeGeneratorContainerProps): BarcodeCon const value = props.codeValue?.status === "available" ? (props.codeValue.value ?? "") : ""; const format = props.codeFormat === "Custom" ? (props.customCodeFormat ?? "CODE128") : (props.codeFormat ?? "CODE128"); + const isQRCode = format === "QRCode"; return Object.freeze({ // Basic barcode properties @@ -45,6 +47,7 @@ export function barcodeConfig(props: BarcodeGeneratorContainerProps): BarcodeCon width: props.codeWidth ?? 128, height: props.codeHeight ?? 128, format, + isQRCode, margin: props.codeMargin ?? 2, displayValue: props.displayValue ?? false, allowDownload: props.allowDownload ?? false, diff --git a/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx b/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx deleted file mode 100644 index c992900679..0000000000 --- a/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeConfigContext.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { createContext, ReactNode, useContext } from "react"; -import { BarcodeConfig } from "./Barcode.config"; - -const BarcodeConfigContext = createContext(null); - -interface BarcodeConfigProviderProps { - config: BarcodeConfig; - children: ReactNode; -} - -export function BarcodeConfigProvider({ config, children }: BarcodeConfigProviderProps): ReactNode { - return {children}; -} - -export function useBarcodeConfig(): BarcodeConfig { - const config = useContext(BarcodeConfigContext); - if (!config) { - throw new Error("useBarcodeConfig must be used within a BarcodeConfigProvider"); - } - return config; -} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeContext.tsx b/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeContext.tsx new file mode 100644 index 0000000000..a84fe138b4 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/config/BarcodeContext.tsx @@ -0,0 +1,21 @@ +import { createContext, ReactNode, useContext } from "react"; +import { BarcodeConfig } from "./Barcode.config"; + +const BarcodeContext = createContext(null); + +interface BarcodeContextProviderProps { + config: BarcodeConfig; + children: ReactNode; +} + +export function BarcodeContextProvider({ config, children }: BarcodeContextProviderProps): ReactNode { + return {children}; +} + +export function useBarcodeConfig(): BarcodeConfig { + const config = useContext(BarcodeContext); + if (!config) { + throw new Error("useBarcodeConfig must be used within a BarcodeConfigProvider"); + } + return config; +} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts deleted file mode 100644 index c251931c98..0000000000 --- a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownload.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { RefObject, useCallback } from "react"; - -// Constants -const NAMESPACES = { - SVG: "http://www.w3.org/2000/svg", - XLINK: "http://www.w3.org/1999/xlink" -} as const; - -const FILENAMES = { - QRCode: "qrcode.svg", - default: "barcode.svg" -} as const; - -interface UseDownloadParams { - format: string; - svgRef: RefObject; - qrContainerRef: RefObject; -} -interface UseDownloadReturn { - downloadSVG: () => Promise; -} - -const getSvgElement = ( - format: string, - svgRef: RefObject, - qrContainerRef: RefObject -): SVGSVGElement | null => { - if (format === "QRCode") { - return qrContainerRef.current?.querySelector("svg") || null; - } - return svgRef.current; -}; - -const getFilename = (format: string): string => { - return format === "QRCode" ? FILENAMES.QRCode : FILENAMES.default; -}; - -// Prepare SVG for download by setting namespaces -const prepareSvgForDownload = (svgElement: SVGSVGElement): SVGSVGElement => { - const clonedSvg = svgElement.cloneNode(true) as SVGSVGElement; - clonedSvg.setAttribute("xmlns", NAMESPACES.SVG); - clonedSvg.setAttribute("xmlns:xlink", NAMESPACES.XLINK); - return clonedSvg; -}; - -const convertImageToBase64 = async (url: string): Promise => { - try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to fetch image: ${response.statusText}`); - } - const blob = await response.blob(); - - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result as string); - reader.onerror = () => reject(new Error("Failed to convert image to base64")); - reader.readAsDataURL(blob); - }); - } catch (error) { - console.warn("Failed to convert image to base64:", error); - return url; // Return original URL as fallback - } -}; - -// Check if URL is external (http/https) -const isExternalUrl = (url: string): boolean => { - return url.startsWith("http://") || url.startsWith("https://"); -}; - -// Convert overlay images to base64 for QR codes -const processQRImages = async (clonedSvg: SVGSVGElement): Promise => { - const imageElement = clonedSvg.querySelector("image"); - if (imageElement) { - const hrefValue = imageElement.getAttribute("href") || imageElement.getAttribute("xlink:href"); - if (hrefValue && isExternalUrl(hrefValue)) { - const base64 = await convertImageToBase64(hrefValue); - // Use modern href attribute and remove any existing xlink:href - imageElement.setAttribute("href", base64); - imageElement.removeAttribute("xlink:href"); - } - } -}; - -const downloadBlob = (blob: Blob, filename: string): void => { - const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = filename; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); -}; - -export function useDownload({ format, svgRef, qrContainerRef }: UseDownloadParams): UseDownloadReturn { - const downloadSVG = useCallback(async () => { - const svgElement = getSvgElement(format, svgRef, qrContainerRef); - if (!svgElement) { - console.error("SVG element not found for download"); - return; - } - - try { - const clonedSvg = prepareSvgForDownload(svgElement); - - // Process QR code images if needed - if (format === "QRCode") { - await processQRImages(clonedSvg); - } - - const serializer = new XMLSerializer(); - const svgString = serializer.serializeToString(clonedSvg); - - // Create download blob and trigger download - const blobOptions = { - type: "image/svg+xml;charset=utf-8", - lastModified: Date.now() - }; - const blob = new Blob([svgString], blobOptions); - const filename = getFilename(format); - downloadBlob(blob, filename); - } catch (error) { - console.error("Error downloading SVG:", error); - } - }, [format, svgRef, qrContainerRef]); - - return { downloadSVG }; -} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadBarcode.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadBarcode.ts new file mode 100644 index 0000000000..aeeca7e261 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadBarcode.ts @@ -0,0 +1,38 @@ +import { RefObject, useCallback } from "react"; +import { downloadBlob, FILENAMES, prepareSvgForDownload } from "../utils/download-utils"; + +interface UseDownloadBarcodeParams { + ref: RefObject; +} +interface UseDownloadBarcodeReturn { + downloadBarcode: () => Promise; +} + +export function useDownloadBarcode({ ref }: UseDownloadBarcodeParams): UseDownloadBarcodeReturn { + const downloadBarcode = useCallback(async () => { + const svgElement = ref.current; + if (!svgElement) { + console.error("SVG element not found for download"); + return; + } + + try { + const clonedSvg = prepareSvgForDownload(svgElement); + const serializer = new XMLSerializer(); + const svgString = serializer.serializeToString(clonedSvg); + + // Create download blob and trigger download + const blobOptions = { + type: "image/svg+xml;charset=utf-8", + lastModified: Date.now() + }; + const blob = new Blob([svgString], blobOptions); + const filename = FILENAMES.Barcode; + downloadBlob(blob, filename); + } catch (error) { + console.error("Error downloading barcode:", error); + } + }, [ref]); + + return { downloadBarcode }; +} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadQRCode.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadQRCode.ts new file mode 100644 index 0000000000..47be31d8c4 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useDownloadQRCode.ts @@ -0,0 +1,42 @@ +import { RefObject, useCallback } from "react"; +import { downloadBlob, FILENAMES, prepareSvgForDownload, processQRImages } from "../utils/download-utils"; + +interface UseDownloadParams { + ref: RefObject; +} +interface UseDownloadReturn { + downloadQrCode: () => Promise; +} + +export function useDownloadQrCode({ ref }: UseDownloadParams): UseDownloadReturn { + const downloadQrCode = useCallback(async () => { + const svgElement = ref.current; + if (!svgElement) { + console.error("SVG element not found for download"); + return; + } + + try { + const clonedSvg = prepareSvgForDownload(svgElement); + + // Process overlay images for QR codes + await processQRImages(clonedSvg); + + const serializer = new XMLSerializer(); + const svgString = serializer.serializeToString(clonedSvg); + + // Create download blob and trigger download + const blobOptions = { + type: "image/svg+xml;charset=utf-8", + lastModified: Date.now() + }; + const blob = new Blob([svgString], blobOptions); + const filename = FILENAMES.QRCode; + downloadBlob(blob, filename); + } catch (error) { + console.error("Error downloading SVG:", error); + } + }, [ref]); + + return { downloadQrCode }; +} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts index 4afb1f9aab..5ea15389a2 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/hooks/useRenderBarcode.ts @@ -1,115 +1,31 @@ -import JsBarcode from "jsbarcode"; -import { type ForwardedRef, useEffect } from "react"; +import { useBarcodeConfig } from "../config/BarcodeContext"; +import { RefObject, useEffect, useRef } from "react"; +import { type BarcodeRenderOptions, renderBarcode } from "../utils/barcodeRenderer-utils"; -interface BarcodeMethodOptions { - width?: number; - height?: number; - margin?: number; - displayValue?: boolean; -} +export const useRenderBarcode = (): RefObject => { + const ref = useRef(null); -interface BarcodeInstance { - EAN13: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; - EAN8: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; - EAN5: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; - EAN2: (value: string, options: BarcodeMethodOptions) => BarcodeInstance; - blank: (spacing: number) => BarcodeInstance; - render: () => void; - [key: string]: any; -} - -interface BarcodeOptions { - format: string; - width: number; - height: number; - margin: number; - displayValue: boolean; - ean128?: boolean; - flat?: boolean; - lastChar?: string; - mod43?: boolean; -} - -const createBarcodeWithAddon = ( - ref: ForwardedRef, - value: string, - mainFormat: string, - addonValue: string, - addonFormat: string, - options: BarcodeOptions, - addonSpacing: number -): void => { - if (ref && typeof ref !== "function" && ref.current) { - const barcodeInstance = JsBarcode(ref.current) as BarcodeInstance; - - // Generate main barcode dynamically - barcodeInstance[mainFormat](value, { - width: options.width, - height: options.height, - margin: options.margin, - displayValue: options.displayValue - }); - - // Add spacing - barcodeInstance.blank(addonSpacing); - - // Add addon dynamically - barcodeInstance[addonFormat](addonValue, { width: 1 }); - - barcodeInstance.render(); - } -}; - -const createStandardBarcode = (ref: ForwardedRef, value: string, options: BarcodeOptions): void => { - if (ref && typeof ref !== "function" && ref.current) { - JsBarcode(ref.current, value, options); - } -}; - -const renderBarcode = ( - ref: ForwardedRef, - value: string, - format: string, - addonValue: string | undefined, - addonFormat: string | undefined, - addonSpacing: number, - options: BarcodeOptions -): void => { - switch (addonFormat) { - case "EAN5": - createBarcodeWithAddon(ref, value, format, addonValue!, addonFormat, options, addonSpacing); - break; - - case "EAN2": - createBarcodeWithAddon(ref, value, format, addonValue!, addonFormat, options, addonSpacing); - break; - - default: - createStandardBarcode(ref, value, options); - break; - } -}; + const { + value, + width, + height, + format, + margin, + displayValue, + addonValue, + enableEan128, + enableFlat, + lastChar, + enableMod43, + addonFormat, + addonSpacing + } = useBarcodeConfig(); -export const useRenderBarcode = ( - ref: ForwardedRef, - value: string, - width: number, - height: number, - format: string, - margin: number, - displayValue: boolean, - enableEan128: boolean, - enableFlat: boolean, - lastChar: string, - enableMod43: boolean, - addonValue?: string, - addonFormat?: string, - addonSpacing?: number -): void => { useEffect(() => { if (ref && typeof ref !== "function" && ref.current && value) { try { - const options: BarcodeOptions = { + const renderOptions: BarcodeRenderOptions = { + value, format, width, height, @@ -118,28 +34,18 @@ export const useRenderBarcode = ( ean128: enableEan128, flat: enableFlat, lastChar, - mod43: enableMod43 + mod43: enableMod43, + addonValue, + addonFormat, + addonSpacing }; - renderBarcode(ref, value, format, addonValue, addonFormat, addonSpacing || 20, options); + renderBarcode(ref, renderOptions); } catch (error) { console.error("Error generating barcode:", error); } } - }, [ - value, - width, - height, - format, - margin, - displayValue, - enableEan128, - enableFlat, - enableMod43, - lastChar, - addonValue, - addonFormat, - addonSpacing, - ref - ]); + }, [value, addonValue]); + + return ref; }; diff --git a/packages/pluggableWidgets/barcode-generator-web/src/utils/barcodeRenderer-utils.ts b/packages/pluggableWidgets/barcode-generator-web/src/utils/barcodeRenderer-utils.ts new file mode 100644 index 0000000000..97802f0428 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/utils/barcodeRenderer-utils.ts @@ -0,0 +1,119 @@ +import JsBarcode from "jsbarcode"; +import { type ForwardedRef } from "react"; + +interface BarcodeMethodOptions { + width?: number; + height?: number; + margin?: number; + displayValue?: boolean; +} + +interface BarcodeService { + EAN13: (value: string, options: BarcodeMethodOptions) => BarcodeService; + EAN8: (value: string, options: BarcodeMethodOptions) => BarcodeService; + EAN5: (value: string, options: BarcodeMethodOptions) => BarcodeService; + EAN2: (value: string, options: BarcodeMethodOptions) => BarcodeService; + blank: (spacing: number) => BarcodeService; + render: () => void; + [key: string]: any; +} + +export interface BarcodeOptions { + format: string; + width: number; + height: number; + margin: number; + displayValue: boolean; + ean128?: boolean; + flat?: boolean; + lastChar?: string; + mod43?: boolean; +} + +export interface BarcodeRenderOptions { + value: string; + format: string; + width: number; + height: number; + margin: number; + displayValue: boolean; + ean128?: boolean; + flat?: boolean; + lastChar?: string; + mod43?: boolean; + addonValue?: string; + addonFormat?: string; + addonSpacing?: number; +} + +/** + * Creates a barcode with an addon (EAN2 or EAN5) + */ +export const createBarcodeWithAddon = ( + ref: ForwardedRef, + value: string, + mainFormat: string, + addonValue: string, + addonFormat: string, + options: BarcodeOptions, + addonSpacing: number +): void => { + if (ref && typeof ref !== "function" && ref.current) { + const BarcodeService = JsBarcode(ref.current) as BarcodeService; + + // Generate main barcode dynamically + BarcodeService[mainFormat](value, { + width: options.width, + height: options.height, + margin: options.margin, + displayValue: options.displayValue + }); + + // Add spacing + BarcodeService.blank(addonSpacing); + + // Add addon dynamically + BarcodeService[addonFormat](addonValue, { width: 1 }); + + BarcodeService.render(); + } +}; + +/** + * Creates a standard barcode without addons + */ +export const createStandardBarcode = ( + ref: ForwardedRef, + value: string, + options: BarcodeOptions +): void => { + if (ref && typeof ref !== "function" && ref.current) { + JsBarcode(ref.current, value, options); + } +}; + +/** + * Renders a barcode with optional addon support + */ +export const renderBarcode = (ref: ForwardedRef, renderOptions: BarcodeRenderOptions): void => { + const { value, format, addonValue, addonFormat, addonSpacing = 20, ...barcodeOptions } = renderOptions; + + const options: BarcodeOptions = { + format, + ...barcodeOptions + }; + + switch (addonFormat) { + case "EAN5": + createBarcodeWithAddon(ref, value, format, addonValue!, addonFormat, options, addonSpacing); + break; + + case "EAN2": + createBarcodeWithAddon(ref, value, format, addonValue!, addonFormat, options, addonSpacing); + break; + + default: + createStandardBarcode(ref, value, options); + break; + } +}; diff --git a/packages/pluggableWidgets/barcode-generator-web/src/utils/download-utils.ts b/packages/pluggableWidgets/barcode-generator-web/src/utils/download-utils.ts new file mode 100644 index 0000000000..2ee913a689 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/utils/download-utils.ts @@ -0,0 +1,71 @@ +// SVG download and processing utilities + +const NAMESPACES = { + SVG: "http://www.w3.org/2000/svg", + XLINK: "http://www.w3.org/1999/xlink" +} as const; + +const FILENAMES = { + QRCode: "qrcode.svg", + Barcode: "barcode.svg" +} as const; + +// Prepare SVG for download by setting namespaces +export const prepareSvgForDownload = (svgElement: SVGSVGElement): SVGSVGElement => { + const clonedSvg = svgElement.cloneNode(true) as SVGSVGElement; + clonedSvg.setAttribute("xmlns", NAMESPACES.SVG); + clonedSvg.setAttribute("xmlns:xlink", NAMESPACES.XLINK); + return clonedSvg; +}; + +export const convertImageToBase64 = async (url: string): Promise => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`); + } + const blob = await response.blob(); + + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = () => reject(new Error("Failed to convert image to base64")); + reader.readAsDataURL(blob); + }); + } catch (error) { + console.warn("Failed to convert image to base64:", error); + return url; // Return original URL as fallback + } +}; + +// Check if URL is external (http/https) +export const isExternalUrl = (url: string): boolean => { + return url.startsWith("http://") || url.startsWith("https://"); +}; + +// Convert overlay images to base64 for QR codes +export const processQRImages = async (clonedSvg: SVGSVGElement): Promise => { + const imageElement = clonedSvg.querySelector("image"); + if (imageElement) { + const hrefValue = imageElement.getAttribute("href") || imageElement.getAttribute("xlink:href"); + if (hrefValue && isExternalUrl(hrefValue)) { + const base64 = await convertImageToBase64(hrefValue); + // Use modern href attribute and remove any existing xlink:href + imageElement.setAttribute("href", base64); + imageElement.removeAttribute("xlink:href"); + } + } +}; + +export const downloadBlob = (blob: Blob, filename: string): void => { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +}; + +export { FILENAMES }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09e3e43308..11b30e7f82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -632,6 +632,9 @@ importers: '@mendix/widget-plugin-test-utils': specifier: workspace:* version: link:../../shared/widget-plugin-test-utils + '@types/react': + specifier: '>=18.2.36' + version: 19.2.2 cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -12486,7 +12489,7 @@ snapshots: identity-obj-proxy: 3.0.0 jasmine: 3.99.0 jasmine-core: 3.99.1 - jest: 29.7.0(@types/node@22.14.1) + jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3)) jest-environment-jsdom: 29.7.0 jest-jasmine2: 29.7.0 jest-junit: 13.2.0 @@ -17029,18 +17032,6 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.14.1): - dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3)) - '@jest/types': 29.6.3 - import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3)) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - jest@29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3)) @@ -19907,7 +19898,7 @@ snapshots: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 29.7.0(@types/node@22.14.1) + jest: 29.7.0(@types/node@22.14.1)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.14.1)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 From 3a819932c0197e64114f407ae630679d7329f7c7 Mon Sep 17 00:00:00 2001 From: Yordan Date: Mon, 24 Nov 2025 15:12:43 +0100 Subject: [PATCH 8/8] fix: add missing downloadAriaLabel property in BarcodeGenerator test --- .../src/BarcodeGenerator.editorConfig.ts | 40 ++++- .../src/BarcodeGenerator.xml | 168 +++++++++--------- .../src/__tests__/BarcodeGenerator.spec.tsx | 1 + .../src/components/Barcode.tsx | 4 +- .../src/components/QRCode.tsx | 10 +- .../src/config/Barcode.config.ts | 2 + .../src/config/validation.ts | 160 +++++++++++++++++ .../typings/BarcodeGeneratorProps.d.ts | 6 +- pnpm-lock.yaml | 3 - 9 files changed, 304 insertions(+), 90 deletions(-) create mode 100644 packages/pluggableWidgets/barcode-generator-web/src/config/validation.ts diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts index 1dfe503b00..fc1902a028 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.editorConfig.ts @@ -1,5 +1,6 @@ import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { BarcodeGeneratorPreviewProps } from "../typings/BarcodeGeneratorProps"; +import { validateAddonValue, validateBarcodeValue } from "./config/validation"; export type Problem = { property?: string; // key of the property, at which the problem exists @@ -114,5 +115,42 @@ export function check(_values: BarcodeGeneratorPreviewProps): Problem[] { }); } - return errors; + // Design-time validation for static barcode value(s) + const valueProblems = validateCodeValues(_values); + return errors.concat(valueProblems); +} + +function getActiveFormat(values: BarcodeGeneratorPreviewProps): string { + if (values.codeFormat === "Custom") { + return values.customCodeFormat || "CODE128"; + } + + return values.codeFormat; +} + +function validateCodeValues(values: BarcodeGeneratorPreviewProps): Problem[] { + const problems: Problem[] = []; + const val = values.codeValue ?? ""; + const addon = values.addonValue ?? ""; + const format = getActiveFormat(values); + + // Only validate static (design-time) values — if empty, skip (user may bind dynamically) + if (!val) { + // still validate addon if present + } else { + const result = validateBarcodeValue(format, val); + if (!result.valid) { + const msg = result.message || "Invalid barcode value for selected format."; + problems.push({ property: "codeValue", severity: "warning", message: msg }); + } + } + + // Validate addon value if visible + const addonResult = validateAddonValue(values.addonFormat, addon); + if (!addonResult.valid) { + const msg = addonResult.message || "Invalid addon value."; + problems.push({ property: "addonValue", severity: "warning", message: msg }); + } + + return problems; } diff --git a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml index 279ea9732d..284517b5da 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml +++ b/packages/pluggableWidgets/barcode-generator-web/src/BarcodeGenerator.xml @@ -24,6 +24,18 @@ Custom
+ + Allow download + Adds a download button + + + Button aria-label + + +
+ + + Custom Format Choose between barcode types format @@ -56,10 +68,6 @@ Mod43 For code 39 if used with modulo 43 check digit - - Allow download - Adds a download button - @@ -83,82 +91,82 @@ Space between main barcode and addon (in pixels) - - - Display value - Display the value below the code - - - Bar width - Width of a single bar - - - Code height - Height of the barcode - - - Margin size - In pixels - - - QR Size - The size of the QR box - - - Margin size - - - - Title - Used for accessibility reasons - - - Level - The Error Correction Level to use - - L - M - Q - H - - - - Image - Include an image on top the QR code - - - Image source - URL or path to the image to display on the QR code - - - Center image - Center the image in the QR code - - - Image X position - Horizontal position of the image - - - Image Y position - Vertical position of the image - - - Image height - Height of the image in pixels - - - Image width - Width of the image in pixels - - - Image opacity - Opacity of the image (0.0 to 1.0) - - - Excavate background - Remove QR code dots behind the image - - + + + + Display value + Display the value below the code + + + Bar width + Width of a single bar + + + Code height + Height of the barcode + + + Margin size + In pixels + + + QR Size + The size of the QR box + + + Margin size + + + + Title + Used for accessibility reasons + + + Level + The Error Correction Level to use + + L + M + Q + H + + + + Image + Include an image on top the QR code + + + Image source + URL or path to the image to display on the QR code + + + Center image + Center the image in the QR code + + + Image X position + Horizontal position of the image + + + Image Y position + Vertical position of the image + + + Image height + Height of the image in pixels + + + Image width + Width of the image in pixels + + + Image opacity + Opacity of the image (0.0 to 1.0) + + + Excavate background + Remove QR code dots behind the image + diff --git a/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx b/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx index 5f68f79909..9c04ef785e 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/__tests__/BarcodeGenerator.spec.tsx @@ -31,6 +31,7 @@ describe("BarcodeGenerator", () => { lastChar: "", enableMod43: false, allowDownload: false, + downloadAriaLabel: "Download barcode", displayValue: false, codeWidth: 2, codeHeight: 200, diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx index 3f25d8cfac..ed7b714f25 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/Barcode.tsx @@ -6,14 +6,14 @@ import { Fragment } from "react"; export const BarcodeRenderer = () => { const ref = useRenderBarcode(); - const { allowDownload } = useBarcodeConfig(); + const { allowDownload, downloadAriaLabel } = useBarcodeConfig(); const { downloadBarcode } = useDownloadBarcode({ ref }); return ( {allowDownload && ( - )} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx index b7642ca5ea..879e0f8462 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx +++ b/packages/pluggableWidgets/barcode-generator-web/src/components/QRCode.tsx @@ -20,7 +20,8 @@ export const QRCodeRenderer = () => { qrImageHeight: imageHeight, qrImageWidth: imageWidth, qrImageOpacity: imageOpacity, - qrImageExcavate: imageExcavate + qrImageExcavate: imageExcavate, + downloadAriaLabel: downloadAriaLabel } = useBarcodeConfig(); const imageSettings = imageSrc ? { @@ -46,7 +47,12 @@ export const QRCodeRenderer = () => { imageSettings={imageSettings} /> {allowDownload && ( - )} diff --git a/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts b/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts index 8c0149fc52..bc93f8e0b7 100644 --- a/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts +++ b/packages/pluggableWidgets/barcode-generator-web/src/config/Barcode.config.ts @@ -11,6 +11,7 @@ export interface BarcodeConfig { margin: number; displayValue: boolean; allowDownload: boolean; + downloadAriaLabel?: string; // Advanced barcode options enableEan128: boolean; @@ -51,6 +52,7 @@ export function barcodeConfig(props: BarcodeGeneratorContainerProps): BarcodeCon margin: props.codeMargin ?? 2, displayValue: props.displayValue ?? false, allowDownload: props.allowDownload ?? false, + downloadAriaLabel: props.downloadAriaLabel, // Advanced barcode options enableEan128: props.enableEan128 ?? false, diff --git a/packages/pluggableWidgets/barcode-generator-web/src/config/validation.ts b/packages/pluggableWidgets/barcode-generator-web/src/config/validation.ts new file mode 100644 index 0000000000..14b9538fd7 --- /dev/null +++ b/packages/pluggableWidgets/barcode-generator-web/src/config/validation.ts @@ -0,0 +1,160 @@ +export type ValidationResult = { + valid: boolean; + // `message` is a plain, non-localized message shown in Studio if validation fails. + message?: string; +}; + +/** Validate barcode value for a given format. */ +export function validateBarcodeValue(format: string, value: string): ValidationResult { + // If no value is present at design time, assume dynamic binding will provide it at runtime. + if (!value) { + return { valid: true }; + } + + switch (format) { + case "EAN13": + // EAN-13: numeric only; allow 12 (data) or 13 (data+checksum) digits + if (!/^[0-9]+$/.test(value) || !(value.length === 12 || value.length === 13)) { + return { + valid: false, + message: "EAN-13 requires 12 or 13 numeric digits (12 data digits + optional checksum)." + }; + } + return { valid: true }; + case "EAN8": + // EAN-8: numeric only; allow 7 (data) or 8 (with checksum) + if (!/^[0-9]+$/.test(value) || !(value.length === 7 || value.length === 8)) { + return { + valid: false, + message: "EAN-8 requires 7 or 8 numeric digits (7 data digits + optional checksum)." + }; + } + return { valid: true }; + case "UPC": + // UPC: numeric only; allow 11 (data) or 12 (data+checksum) digits + if (!/^[0-9]+$/.test(value) || !(value.length === 11 || value.length === 12)) { + return { + valid: false, + message: "UPC requires 11 or 12 numeric digits (11 data digits + optional checksum)." + }; + } + return { valid: true }; + case "ITF14": + // ITF-14: numeric only; must be exactly 14 digits + if (!/^[0-9]+$/.test(value) || value.length !== 14) { + return { valid: false, message: "ITF-14 requires exactly 14 numeric digits." }; + } + return { valid: true }; + case "CODE39": + // CODE39: uppercase letters, digits, space and - . $ / + % allowed + if (value.length > 43) { + return { + valid: false, + message: "CODE39 supports up to 43 characters for readability." + }; + } + if (!/^[0-9A-Z .$/+%-]+$/.test(value)) { + return { + valid: false, + message: "CODE39 supports only uppercase A-Z, digits, space and - . $ / + % characters." + }; + } + return { valid: true }; + case "MSI": + // MSI: numeric only + if (value.length > 30) { + return { + valid: false, + message: "MSI supports up to 30 digits for readability." + }; + } + if (!/^[0-9]+$/.test(value)) { + return { valid: false, message: "MSI requires numeric digits only." }; + } + return { valid: true }; + case "pharmacode": + // Pharmacode: numeric only + if (value.length > 7) { + return { + valid: false, + message: "Pharmacode supports up to 7 digits for readability." + }; + } + if (!/^[0-9]+$/.test(value)) { + return { valid: false, message: "Pharmacode requires numeric digits only." }; + } + return { valid: true }; + case "codabar": + // Codabar: digits and A-D (start/stop) plus - $ : / . + allowed + if (value.length > 20) { + return { + valid: false, + message: "Codabar supports up to 20 characters for readability." + }; + } + if (!/^[0-9A-Da-d$:/.+-]+$/.test(value)) { + return { valid: false, message: "Codabar allows digits, A-D start/stop characters and - $ : / . +." }; + } + return { valid: true }; + case "CODE93": + // CODE93: supports a wide ASCII set — basic check: no control characters + if (value.length > 47) { + return { + valid: false, + message: "CODE93 supports up to 47 characters for readability." + }; + } + if (/\p{Cc}/u.test(value)) { + return { valid: false, message: "CODE93 should not contain control characters." }; + } + return { valid: true }; + case "QRCode": + // QRCode: accepts most characters, but warn for extremely long static values + if (value.length > 1200) { + return { + valid: false, + message: + "The QR code value is very long; consider using a shorter value or a dynamic attribute to avoid rendering issues." + }; + } + return { valid: true }; + case "CODE128": + default: + // CODE128: flexible charset but disallow control characters as a basic sanity check + if (value.length > 80) { + return { + valid: false, + message: "CODE128 supports up to 80 characters for readability." + }; + } + if (/\p{Cc}/u.test(value)) { + return { valid: false, message: "CODE128 should not contain control characters." }; + } + return { valid: true }; + } +} + +/** Validate addon (EAN-5 / EAN-2) values. */ +export function validateAddonValue(addonFormat: string | null | undefined, value: string): ValidationResult { + if (!addonFormat || addonFormat === "None") { + return { valid: true }; + } + + if (addonFormat === "EAN5") { + // EAN-5: exactly 5 numeric digits + if (!/^[0-9]{5}$/.test(value)) { + return { valid: false, message: "EAN-5 addon requires exactly 5 numeric digits." }; + } + return { valid: true }; + } + + if (addonFormat === "EAN2") { + // EAN-2: exactly 2 numeric digits + if (!/^[0-9]{2}$/.test(value)) { + return { valid: false, message: "EAN-2 addon requires exactly 2 numeric digits." }; + } + return { valid: true }; + } + + return { valid: true }; +} diff --git a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts index 6c0b1aecad..8776621ad5 100644 --- a/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts +++ b/packages/pluggableWidgets/barcode-generator-web/typings/BarcodeGeneratorProps.d.ts @@ -22,12 +22,13 @@ export interface BarcodeGeneratorContainerProps { tabIndex?: number; codeValue: EditableValue; codeFormat: CodeFormatEnum; + allowDownload: boolean; + downloadAriaLabel: string; customCodeFormat: CustomCodeFormatEnum; enableEan128: boolean; enableFlat: boolean; lastChar: string; enableMod43: boolean; - allowDownload: boolean; addonFormat: AddonFormatEnum; addonValue: EditableValue; addonSpacing: number; @@ -63,12 +64,13 @@ export interface BarcodeGeneratorPreviewProps { translate: (text: string) => string; codeValue: string; codeFormat: CodeFormatEnum; + allowDownload: boolean; + downloadAriaLabel: string; customCodeFormat: CustomCodeFormatEnum; enableEan128: boolean; enableFlat: boolean; lastChar: string; enableMod43: boolean; - allowDownload: boolean; addonFormat: AddonFormatEnum; addonValue: string; addonSpacing: number | null; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 11b30e7f82..8c28eb42ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -632,9 +632,6 @@ importers: '@mendix/widget-plugin-test-utils': specifier: workspace:* version: link:../../shared/widget-plugin-test-utils - '@types/react': - specifier: '>=18.2.36' - version: 19.2.2 cross-env: specifier: ^7.0.3 version: 7.0.3