Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a848f5b
build(barcode-generator-web): add @bwip-js/browser dependency
samuelreichert Jul 9, 2026
6d9f985
feat(barcode-generator-web): add Data Matrix widget properties
samuelreichert Jul 9, 2026
54941b4
feat(barcode-generator-web): add Data Matrix config model and validation
samuelreichert Jul 9, 2026
b9b51c9
feat(barcode-generator-web): render Data Matrix via bwip-js
samuelreichert Jul 9, 2026
79c3069
feat(barcode-generator-web): add Data Matrix editor preview
samuelreichert Jul 9, 2026
226ed16
test(barcode-generator-web): cover Data Matrix generation
samuelreichert Jul 9, 2026
ffe3724
docs(barcode-generator-web): changelog for Data Matrix support
samuelreichert Jul 9, 2026
32f7940
docs(openspec): add-datamatrix-generation change
samuelreichert Jul 9, 2026
f5bb090
docs(openspec): relocate the Data Matrix change into its owning package
samuelreichert Aug 13, 2026
663ef8d
fix(barcode-generator-web): size Data Matrix SVG by viewBox aspect ratio
samuelreichert Jul 16, 2026
72fe4b7
fix(barcode-generator-web): sanitize Data Matrix SVG, prefix preview …
samuelreichert Jul 20, 2026
b8ecc7e
fix(barcode-generator-web): use constant for maximum Data Matrix and …
samuelreichert Jul 29, 2026
fc85c81
fix(barcode-generator-web): add a dedicated Data Matrix margin property
samuelreichert Aug 13, 2026
0b916de
fix(barcode-generator-web): scope property visibility to the selected…
samuelreichert Aug 13, 2026
6447a85
test(barcode-generator-web): add Data Matrix e2e coverage
samuelreichert Aug 13, 2026
8086499
docs(openspec): record margin, visibility and verification outcomes
samuelreichert Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- Added Data Matrix and GS1 Data Matrix generation support, including square and rectangular symbol shapes.

## [1.0.0] - 2026-04-17

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,70 @@
import { test, expect } from "@mendix/run-e2e/fixtures";
import { waitForMendixApp } from "@mendix/run-e2e/mendix-helpers";

/**
* These tests are dormant until the test project exists: `package.json` still has
* `"e2e": "echo ..."` because https://github.com/mendix/testProjects has no
* `barcode-generator-web` branch yet (only `barcode-scanner-web`).
*
* To enable, create that branch with a `/p/datamatrix` page containing:
* - dataMatrixPlain Data Matrix, GS1 off, square, value "ABC-12345"
* - dataMatrixGs1 Data Matrix, GS1 on, value "(01)09501101020917(17)261231(10)ABC123"
* - dataMatrixRectangle Data Matrix, shape Rectangle
* - dataMatrixDownload Data Matrix with "Allow download" on, file name "datamatrix"
* - textBoxCodeValue text box bound to the attribute dataMatrixBound reads from
* - dataMatrixBound Data Matrix bound to that attribute
* then swap the `e2e` script to `run-e2e ci`.
*/
test.describe("BarcodeGenerator", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
await waitForMendixApp(page);
await page.goto("/p/datamatrix");
});

test("renders barcode generator widget", async ({ page }) => {
// TODO: Replace with actual barcode generator test when implementation is complete
// Example test structure for barcode generator:
// await expect(page.locator(".mx-name-barcodeGenerator").first()).toBeVisible();
// await page.locator(".mx-name-textInput").fill("Test QR Code");
// await expect(page.locator(".mx-name-barcodeGenerator canvas")).toBeVisible();
test("renders a Data Matrix symbol as inline SVG @smoke", async ({ page }) => {
const symbol = page.locator(".mx-name-dataMatrixPlain .datamatrix-svg svg");

// Placeholder test for now
await expect(page.locator("body")).toBeVisible();
await expect(symbol).toBeVisible();
await expect(symbol).toHaveAttribute("viewBox", /^0 0 \d+(\.\d+)? \d+(\.\d+)?$/);
});

test("renders a GS1 Data Matrix without falling back to the error state", async ({ page }) => {
const widget = page.locator(".mx-name-dataMatrixGs1");

await expect(widget.locator(".datamatrix-svg svg")).toBeVisible();
await expect(widget.locator(".alert-danger")).toHaveCount(0);
});

test("renders the rectangular shape wider than it is tall", async ({ page }) => {
const symbol = page.locator(".mx-name-dataMatrixRectangle .datamatrix-svg svg");
await expect(symbol).toBeVisible();

await expect
.poll(async () => {
const box = await symbol.boundingBox();
return box ? box.width > box.height : false;
})
.toBe(true);
});

test("re-renders when the bound value changes", async ({ page }) => {
const symbol = page.locator(".mx-name-dataMatrixBound .datamatrix-svg svg");
await expect(symbol).toBeVisible();
const before = await symbol.getAttribute("viewBox");

// A longer value needs more modules, so the symbol grows
await page.locator(".mx-name-textBoxCodeValue input").fill("ABC-12345-67890-LONGER-VALUE");
await page.locator(".mx-name-textBoxCodeValue input").blur();

await expect(symbol).not.toHaveAttribute("viewBox", before);
});

test("downloads the Data Matrix as a PNG", async ({ page }) => {
await expect(page.locator(".mx-name-dataMatrixDownload .datamatrix-svg svg")).toBeVisible();

// Start waiting for the download before clicking
const downloadPromise = page.waitForEvent("download");
await page.locator(".mx-name-dataMatrixDownload .barcode-generator-download-button").click();
const download = await downloadPromise;

expect(download.suggestedFilename()).toMatch(/\.png$/);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-09
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## Context

`@mendix/barcode-generator-web` renders three-way today: 1D barcodes via `jsbarcode` (isolated in `src/utils/barcodeRenderer-utils.ts`) and QR via `qrcode.react` (isolated in `src/components/QRCode.tsx`). `src/config/Barcode.config.ts` maps Mendix props into a lib-agnostic discriminated union `BarcodeConfig = BarcodeTypeConfig | QRCodeTypeConfig`, and `src/BarcodeGenerator.tsx:25` dispatches on `config.type`. Rendering is SVG-first; download converts SVG→PNG via `src/utils/download-code.ts` + `download-utils.ts`.

Neither current lib can produce DataMatrix. Pharma requires **GS1 DataMatrix** (FNC1 + GS1 Application Identifiers), not just plain DataMatrix.

## Goals / Non-Goals

**Goals:**

- Add DataMatrix and GS1 DataMatrix generation as a third render path, reusing the existing config-union + dispatch + SVG→PNG download architecture.
- Keep the new library isolated behind a single seam module, mirroring the QR split.
- No regression to existing barcode/QR behavior.

**Non-Goals:**

- Replacing jsbarcode or qrcode.react.
- Building a GS1 AI parser/validator beyond loose syntax checks (bwip-js validates the encoding).
- Adding non-DataMatrix 2D symbologies (Aztec, PDF417) — out of scope.

## Decisions

**Library: `@bwip-js/browser`.** Only maintained (v4.11.x, ~676k dl/wk, MIT) library with native GS1 DataMatrix (`bcid: "gs1datamatrix"`, accepts human-readable AI syntax) and rectangular support. Import the named `datamatrix` + `drawingSVG` exports so bundlers tree-shake — do NOT use `toSVG()`, which links all ~100 BWIPP encoders.

- _Alternatives:_ `datamatrix-svg` (no GS1, unmaintained since 2020) rejected; `@zxing/library` (used by scanner) is decode-only, cannot encode.

**Third render path, not a `customCodeFormat` sub-option.** DataMatrix is 2D with its own options (GS1 toggle, shape), like QR. Add `DataMatrix` to the top-level `codeFormat` enum and a `DataMatrixTypeConfig` (`type: "datamatrix"`) to the union. `barcodeConfig()` branches on `format === "DataMatrix"` before the QR check.

- _Alternative:_ nesting under `customCodeFormat` (the 1D list) rejected — that list is jsbarcode-specific and 1D-shaped.

**GS1 as a boolean toggle under Data Matrix, not a separate top-level format.** Matches the pharma mental model ("same symbology, GS1-encoded") and keeps the top-level enum small. `dmGs1Mode` selects `gs1datamatrix` vs `datamatrix`.

**New seam `src/components/DataMatrix.tsx`.** Mirrors `QRCode.tsx`: builds bwip-js options, renders SVG, exposes an `SVGSVGElement` ref so the existing `DownloadButton` + `downloadCode(ref, config, ...)` pipeline works unchanged. Validation + try/catch error state live in this component (the `useRenderBarcode` hook is 1D/jsbarcode-specific).

**Validation.** Extend `validateBarcodeValue` in `src/config/validation.ts` with a `DataMatrix` case (the `format` param already unions `CodeFormatEnum`): plain mode = charset/length sanity; GS1 mode = loose balanced-`(nn)` AI-syntax check. Encoder errors caught at render.

## Risks / Trade-offs

- **Bundle size** → bwip-js is large if fully linked. Mitigation: import only `datamatrix`/`drawingSVG` named exports; verify bundle delta at build time.
- **Download ref shape** → bwip-js `drawingSVG()` returns SVG markup, not a React-managed `<svg>`. Mitigation: inject markup into a container and target the real `SVGSVGElement` for the ref so SVG→PNG works.
- **GS1 AI validation drift** → hand-rolled loose check may diverge from bwip-js. Mitigation: keep it minimal (structure only), let bwip-js be the source of truth and catch its errors.
- **Two enums to keep in sync** → `codeFormat` value must round-trip through config + validation + preview. Mitigation: covered by unit tests.

## Migration Plan

Additive, no data migration. New dependency added to `package.json`; `typings/BarcodeGeneratorProps.d.ts` regenerated by build. Rollback = revert the change and drop the dependency; existing barcode/QR configs unaffected.

## Open Questions

- Exact bwip-js option for rectangular DataMatrix (shape flag vs explicit `rows`/`columns`) — confirm at implementation.
- ~~Whether to reuse `codeMargin`/`qrSize`-style sizing or add dedicated `dmSize`/`dmMargin` props — lean toward dedicated to avoid overloading 1D "bar width" semantics.~~ Resolved: dedicated `dmSize` and `dmMargin`. bwip-js `paddingwidth`/`paddingheight` are multiplied by `scale`, so the Data Matrix margin is in module units like `qrMargin`, not pixels like `codeMargin` — reusing `codeMargin` would have mislabelled the unit and let a 1D margin of 0 strip the required quiet zone.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Why

The Barcode Generator widget (`@mendix/barcode-generator-web`) can only produce 1D linear barcodes (jsbarcode) and QR codes (qrcode.react); it cannot generate DataMatrix. DataMatrix — specifically **GS1 DataMatrix** (FNC1 + GS1 Application Identifiers) — is mandatory for pharma serialization (EU FMD, US DSCSA). The Barcode Scanner widget already decodes DataMatrix, so generation closes the round-trip gap for pharma and logistics apps.

## What Changes

- Add **Data Matrix** as a top-level `codeFormat` option in the Barcode Generator.
- Support two encodings: plain DataMatrix and **GS1 DataMatrix** (pharma), selectable via a boolean toggle.
- Support square and rectangular symbol shapes.
- Add `@bwip-js/browser` as a dependency (tree-shakeable DataMatrix encoder) — the only maintained library with native GS1 DataMatrix support. jsbarcode and qrcode.react remain for their existing formats.
- Render DataMatrix as inline SVG (consistent with existing render + SVG→PNG download pipeline).
- Add runtime/design-time validation for DataMatrix values (plain charset/length; GS1 AI syntax).
- Add Studio Pro editor preview for the DataMatrix format.

No breaking changes — existing barcode/QR behavior is untouched; DataMatrix is additive.

## Capabilities

### New Capabilities

- `barcode-generation`: Generation of barcodes, QR codes, and (new) DataMatrix / GS1 DataMatrix from a string input in the Barcode Generator widget, including format selection, encoding options, validation, rendering, and download.

### Modified Capabilities

<!-- None: no prior spec exists for this widget. -->

## Impact

- **Package**: `packages/pluggableWidgets/barcode-generator-web`
- **New dependency**: `@bwip-js/browser` (MIT, tree-shakeable)
- **Widget config**: `src/BarcodeGenerator.xml` (new enum value + Data Matrix property group); regenerated `typings/BarcodeGeneratorProps.d.ts`
- **Source**: new `src/components/DataMatrix.tsx` (bwip-js seam), extended `src/config/Barcode.config.ts` (discriminated union), `src/BarcodeGenerator.tsx` (dispatch), `src/config/validation.ts`, editor-preview files
- **Reuses** existing `DownloadButton` + `src/utils/download-code.ts` SVG→PNG pipeline unchanged
- **Tests**: Jest unit tests + Playwright E2E; `CHANGELOG.md` entry
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
## ADDED Requirements

### Requirement: DataMatrix format selection

The Barcode Generator widget SHALL offer "Data Matrix" as a top-level barcode format alongside the existing Barcode (1D) and QR Code options.

#### Scenario: Data Matrix appears in format list

- **WHEN** a developer configures the Barcode Generator in Studio Pro and opens the Barcode Format property
- **THEN** "Data Matrix" is available as a selectable format value

#### Scenario: Selecting Data Matrix routes to the DataMatrix renderer

- **WHEN** the Barcode Format is set to "Data Matrix" and a non-empty value is provided
- **THEN** the widget renders a DataMatrix symbol as inline SVG, and does not invoke the 1D barcode or QR code renderers

### Requirement: Plain DataMatrix generation

The widget SHALL encode the provided string value as a standard (non-GS1) DataMatrix symbol when GS1 mode is off.

#### Scenario: Encode a plain string

- **WHEN** the format is "Data Matrix", GS1 mode is off, and the value is `ABC-12345`
- **THEN** the widget renders a scannable DataMatrix symbol encoding exactly that string

#### Scenario: Round-trip with the scanner

- **WHEN** a plain DataMatrix generated by the widget is scanned by the Barcode Scanner widget
- **THEN** the decoded value equals the original input string

### Requirement: GS1 DataMatrix generation

The widget SHALL support GS1 DataMatrix encoding (FNC1 leading character, GS1 Application Identifier data) when GS1 mode is enabled, so pharma serialization data (GTIN, expiry, batch, serial) can be encoded.

#### Scenario: Encode GS1 Application Identifier data

- **WHEN** GS1 mode is enabled and the value is `(01)09501101020917(17)261231(10)ABC123`
- **THEN** the widget renders a GS1 DataMatrix symbol with the FNC1 indicator and the AI-structured data intact

#### Scenario: GS1 mode toggles the encoder

- **WHEN** the same value is rendered first with GS1 mode off and then on
- **THEN** the off result is a plain DataMatrix and the on result is a GS1 DataMatrix (distinct symbols)

### Requirement: DataMatrix symbol shape

The widget SHALL allow choosing between square and rectangular DataMatrix symbol shapes.

#### Scenario: Rectangular shape

- **WHEN** the DataMatrix shape option is set to "rectangle"
- **THEN** the rendered symbol uses a rectangular DataMatrix layout

#### Scenario: Square shape is the default

- **WHEN** no shape is explicitly chosen
- **THEN** the widget renders a square DataMatrix symbol

### Requirement: DataMatrix value validation

The widget SHALL validate the DataMatrix value and surface errors consistently with existing formats, honoring the configured log level.

#### Scenario: Empty value at design time

- **WHEN** no value is present (design time, before dynamic binding resolves)
- **THEN** validation passes and no error is shown, matching existing barcode behavior

#### Scenario: Malformed GS1 AI syntax

- **WHEN** GS1 mode is on and the value has unbalanced or malformed Application Identifier syntax
- **THEN** the widget reports a validation error and, when the log level permits, displays the generic "unable to generate" message and logs detail to the console

#### Scenario: Encoding failure

- **WHEN** the DataMatrix encoder throws for an invalid input
- **THEN** the error is caught, the error UI is shown per log level, and the widget does not crash

### Requirement: DataMatrix download

The widget SHALL allow downloading a generated DataMatrix as a PNG using the existing download control, when downloads are enabled.

#### Scenario: Download a DataMatrix as PNG

- **WHEN** downloads are enabled and the user activates the download button on a DataMatrix
- **THEN** a PNG file of the rendered DataMatrix is downloaded, using the configured or an auto-generated filename

### Requirement: DataMatrix sizing and quiet zone

The widget SHALL expose dedicated size and margin properties for Data Matrix, with the margin expressed in module units so the required quiet zone can be controlled independently of the 1D barcode margin.

#### Scenario: Margin is independent of the 1D and QR margins

- **WHEN** the format is "Data Matrix" and the Data Matrix margin is set to 6 while the 1D margin is 4 and the QR margin is 8
- **THEN** the rendered symbol uses a quiet zone of 6 module units, and changing the 1D or QR margin has no effect on it

#### Scenario: Missing quiet zone is flagged

- **WHEN** the format is "Data Matrix" and the Data Matrix margin is set to 0
- **THEN** Studio Pro shows a warning that at least 1 module unit is needed for the symbol to stay scannable

### Requirement: Format-scoped property visibility

The widget SHALL only show the property groups that apply to the selected barcode format, so Data Matrix settings are hidden for other formats and 1D/QR settings are hidden for Data Matrix.

#### Scenario: Data Matrix settings are scoped to the Data Matrix format

- **WHEN** the Barcode Format is "Barcode", "QR Code" or "Custom"
- **THEN** the "Advanced Data Matrix Settings" properties (GS1 mode, symbol shape, size) are hidden in Studio Pro

#### Scenario: 1D and QR settings are hidden for Data Matrix

- **WHEN** the Barcode Format is "Data Matrix"
- **THEN** bar width, code height, display value, the 1D pixel margin, the advanced barcode settings (EAN-128, flat, last character, Mod43), the EAN addon properties and the QR properties are all hidden, and the Data Matrix margin is shown in their place

#### Scenario: Design-time validation follows the visible properties

- **WHEN** the Barcode Format is "Data Matrix" and the Data Matrix size is below the supported minimum
- **THEN** Studio Pro reports the problem on the Data Matrix size property and does not report problems on hidden 1D or QR sizing properties

### Requirement: DataMatrix editor preview

The widget SHALL show a representative DataMatrix preview in the Studio Pro editor when the Data Matrix format is selected.

#### Scenario: Preview in Studio Pro

- **WHEN** the Data Matrix format is selected in the Studio Pro page editor
- **THEN** the widget preview displays a DataMatrix-style glyph rather than a 1D barcode or QR preview
Loading
Loading