Feat/zpl fe templates#92
Merged
Merged
Conversation
…solve Adds round-trip support for ZPL's `^FE` directive that lets a single `^FD` payload reference multiple `^FN` slots inline. ZPL ground form: ^XA ^FN1^FDsku-default^FS ^FN2^FDprice-default^FS ^FO50,50^A0N,30,30^FD#1#-#2#^FS ^XZ Stored in our model as `«name»` markers in `props.content` — same syntax the canvas already uses for schema-mode variable placeholders, so the marker reads naturally in the properties panel and the single-bind + template paths compose at render time. Three layers: - lib/fnTemplate.ts: marker primitives (extract/resolve/embeds-to- markers / markers-to-embeds / rename / pickEmbedChar). Fresh regex per call so the /g `lastIndex` state can't bleed across callers. - parser: tracks ^FE embedChar (default '#'), bootstraps Variables for unknown FN refs in `^FD` content via the SAME regex shape markersToEmbeds expects (`<e><n><e>` or `<e><n>,...<e>`) so a literal like `Item #5 special` does not phantom-bootstrap. Distinguishes bare `^FN<n>^FD<default>^FS` Variable declarations from implicit-text fields by skipping the FD→text promotion when pendingFn is set. Three former bootstrap paths (bare-decl, single-bind, embed-reference) funnel through one `bootstrapVariable` helper. - generator: pre-scan collects template-referenced fnNumbers, picks an embedChar that doesn't clash with any literal payload text, emits `^FE<char>` (only when ≠ '#'), emits one `^FN<n>^FD<default>^FS` header per referenced Variable that isn't already covered by an inline single-bind field. When pickEmbedChar finds NO safe candidate (pathological payload with every safe char taken) the template emit path is skipped and markers fall through as literal text. Whole header pass + emit-context is extracted to `planTemplateHeader` so generateZPL stays a sequence of label parts. applyBindingToObject scans content for `«name»` markers and substitutes the resolved Variable value (CSV-row-aware, schema-mode aware). Single-bind resolves first, then template markers — so an unusual single-bind value that itself contains markers also resolves. Shared `getObjectStringContent(obj)` helper in variableBinding.ts consolidates the five sites that previously cast `(obj as { props?: { content?: unknown } })` to read content from a heterogeneous LabelObject tree. 23 new unit tests (fnTemplate primitives + parser/generator roundtrip + embedChar fallback to '@' when '#' literal in payload).
TemplateContentInput wraps the plain text input in the Text and 1D-
barcode properties panels with a small `{x}` button that opens a
dropdown of every defined Variable; picking one splices its
`«name»` marker into the input at the current cursor position.
Lets non-technical users compose multi-variable fields without
hand-typing the marker syntax. Templates resolve at render time via
the lib/fnTemplate + variableBinding chain from the previous commit.
Dropdown closes on outside-click and Esc. The barcode1d wiring
splits content at marker boundaries before sanitising literal
slices so the restricted-charset filter (e.g. numeric-only EAN13)
preserves `«…»` markers on every keystroke instead of stripping
them as out-of-charset bytes.
Locale: one new key (insertVariable) across all 32 locales for the
button title attribute.
VariablesPanel's per-row binding count now includes `«name»` template references in addition to single-bind `variableId` references, so the user sees real usage of each Variable across both binding styles. De-duped per object — a field that carries both `variableId === V` and `«V»` in its content counts as one place, not two. updateVariable rewrites every `«oldName»` marker in every object's content to `«newName»` when a Variable is renamed — without this ripple, renames would silently dangle the templates (resolve to literal text). Identity-preserving at the leaf level so React memoisation downstream stays effective for objects whose content didn't carry the renamed name. Reuses the shared `getObjectStringContent` helper from variableBinding.ts so the content-reading shape check stays in one place. 3 new tests cover the rename ripple (multiple markers, identity preservation, no-op when name unchanged).
There was a problem hiding this comment.
Code Review
This pull request introduces support for ZPL ^FE inline embeds, enabling multiple variables to be used within a single text or barcode field. It adds a TemplateContentInput component for variable insertion, a "rename ripple" mechanism to update markers when variables are renamed, and updates the ZPL parser and generator to handle the conversion between ZPL numeric embeds and human-readable markers. Feedback identifies accessibility gaps in the new UI component and suggests performance optimizations in the binding, generation, and parsing logic by replacing linear searches with map-based lookups.
Address gemini's perf findings on PR #92: - variableBinding.applyBindingToObject: build a name→Variable map once per call so a field with N markers resolves in O(N+V) instead of N linear scans across the V Variables. - zplGenerator.planTemplateHeader: build id/name/fn maps once before the leaf walk so the per-leaf single-bind lookup + per-marker name lookup + per-fn header emit all stay constant- time. Cuts the worst case from O(N·V) to O(N+V). Per-field Map rebuild inside zplParser.applyFnEmbeds (gemini's 4th finding) deferred — that path also bootstraps Variables so a hoisted-cache invalidation strategy adds more complexity than the inner-loop scan saves at current scale. TemplateContentInput a11y findings (role=menuitem, keyboard nav) deferred to a separate a11y pass alongside other Properties components — keeping this PR scoped to the ^FE feature surface.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.