feat: Svelte 5 adapter (@formepdf/svelte) + framework-neutral @formepdf/shared core#21
Open
cmjoseph07 wants to merge 18 commits into
Open
feat: Svelte 5 adapter (@formepdf/svelte) + framework-neutral @formepdf/shared core#21cmjoseph07 wants to merge 18 commits into
cmjoseph07 wants to merge 18 commits into
Conversation
Nested <Text> children serialize to styled runs matching the react
adapter's output exactly, including boundary whitespace and deep-nesting
flattening. New Fixed component emits header/footer nodes. PAGE_NUMBER /
TOTAL_PAGES constants are the documented way to emit page-number
placeholders, since {{pageNumber}} parses as a Svelte expression.
The table trio serializes with full prop fidelity: Table columns
(fraction / fixed / auto widths via shared mapColumnWidth), Row header
flag driving repeat-on-continuation, and Cell colSpan / rowSpan with
arbitrary nested content. Nesting is validated with the same error
messages as the react adapter. Parity fixture: a 50-row {#each} table
deep-equal against its TSX twin, smoke-rendered to a multi-page PDF
with the header row repeated on every page.
Four media leaf components serializing to their document-model node kinds, mirroring the react adapter: Image passes src through unresolved (data URI or file path - resolution stays a rendering concern), Svg takes a content markup string with viewBox, QrCode and Barcode map their color prop into style.color. alt and href pass through on Image and Svg. encodeProps now accepts any object (interfaces lack index signatures) and decodeProps returns unknown so parser-side prop interfaces can declare required fields. Covered by parser unit tests (src byte-identity, svg content round-trip, engine defaults), cross-adapter parity fixtures, and a combined WASM smoke render.
Canvas executes its draw callback at emit time against the shared CanvasContext recorder, so the one function-valued prop becomes an attribute-safe operation list. Watermark mirrors react's color-alpha-to-opacity mapping; PageBreak is a bare leaf. Parity fixtures cover all three, plus a two-page WASM smoke render.
Add the five engine-native chart components (BarChart, LineChart, PieChart, AreaChart, DotPlot) to the svelte adapter. The camelCase-to-snake_case kind mapping and chart prop types move from the react adapter into @formepdf/shared (charts.ts) so both adapters serialize charts through the same builders and cannot drift. React re-exports the moved prop types and its serializers now call the shared builders - behavior-neutral, proven by its untouched suite. The svelte parser handles all five placeholder tags through one generic parseChart that delegates to the shared builders and maps style itself. Covered by parser unit tests (snake_case mapping, defaults, per-datum colors, axis bounds), a dashboard-style parity fixture pair exercising multi-series, grouped, and minimal-props variants of every chart type, and a WASM smoke render asserting each chart's drawn output.
TextField, Checkbox, Dropdown, and RadioButton serialize to their AcroForm node kinds with the same defaults and conditional props as the react adapter. Covered by parser tests, cross-adapter parity fixtures (registration form), and a WASM smoke test asserting the rendered PDF contains the AcroForm field dictionaries.
- Re-export Font from the shared store singleton; <Document fonts> merges with globals, document fonts winning on family:weight:italic - Tunnel Uint8Array font sources through a base64 marker in the props attribute so byte sources survive serialization as in react; reserved-key collisions fail loudly at encode time - Add fonts and tailwind parity fixtures, font merge/override tests, and a WASM smoke render embedding NotoSans
- renderDocument/renderDocumentWithLayout serialize a template and
render via a bare dynamic import('@formepdf/core') so core's
conditional exports pick the runtime entry; core is an optional
peer (^0.10.4) and a missing install rejects with an actionable
error naming the install command
- render options (embedData, flattenForms) forward to core untouched,
matching react-path semantics
- core: add renderSerializedDoc/renderSerializedDocWithLayout to the
Node entry for parity with the browser/worker entries; the
react-facing renderDocument pair now delegates to them
formePreview() serves the copied renderer preview UI from a catch-all route: prefix-relative pdf/layout endpoints and a version-polling reload replacing the WebSocket channel. Preview HTML is embedded at build time as a generated module; build fails loudly when the asset is missing.
Ports the 0.10.5 react additions (H1-H6, OrderedList/UnorderedList/ ListItem, Strong/Em/Code/Link, CSS transform styles) to the Svelte adapter. The framework-neutral pieces (heading and inline-formatting default styles, list marker mapping, transform parsing) live in @formepdf/shared so the adapters cannot drift. The parser's run builder is now recursive with style/href accumulation, matching react's buildTextRuns cascade semantics. Parity tests compare run-normalized JSON: react splits runs at JSX child boundaries, Svelte's SSR merges contiguous text before the parser sees it, and adjacent same-style runs render identically.
…coverage - Page components in both adapters now type the style prop the serializers already handled - formePreview 503 responses return a generic message in production instead of the raw render error (dev keeps the full message - the preview page shows template errors in the browser) - encode.ts documents why undefined JSON drops a prop (functions have no JSON representation and must not emit invalid JSON) - CI builds, svelte-checks, and tests @formepdf/svelte
renderDocument returned Promise<Uint8Array<ArrayBufferLike>>, which the DOM BodyInit rejects - new Response(pdf) failed typecheck in a fresh SvelteKit app, including the README quickstart verbatim. The WASM bridge always returns an ArrayBuffer-backed view, so narrow the return types (renderDocument, and RenderDocumentWithLayoutResult for the WithLayout variant) instead of making every endpoint cast. Found by user-perspective E2E against packed tarballs.
cmjoseph07
force-pushed
the
svelte-adapter
branch
from
July 19, 2026 21:50
98105a4 to
ed1cc8a
Compare
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.
feat: Svelte 5 adapter (
@formepdf/svelte) + framework-neutral@formepdf/sharedcoreAdds a first-class Svelte 5 authoring adapter with the full component set, plus the refactor that makes multi-framework adapters sustainable: a framework-neutral
@formepdf/sharedpackage extracted from@formepdf/react.What's in here
@formepdf/shared(new)The framework-neutral serialization core: document-model (
Forme*) types,Stylemapping and CSS shorthand/color/edge/grid/transform parsing, theFontregistration store, theCanvasoperation recorder, chart kind builders, and semantic-component constants (heading defaults, inline-formatting defaults, list marker mapping).@formepdf/reactre-exports everything it previously exported - its public API is unchanged (all 215 react tests pass, including the 35 added in 0.10.5 for headings/lists/inline formatting/transforms, which now exercise the shared code paths).@formepdf/svelte(new)The same components with the same props as
@formepdf/react, authored as ordinary.sveltefiles:Document,Page,View,Text,Image,Fixed,PageBreakH1-H6,OrderedList/UnorderedList/ListItem,Strong/Em/Code/Link(the 0.10.5 additions, at parity from day one)Table,Row,CellSvg,QrCode,Barcode,Canvas,WatermarkBarChart,LineChart,PieChart,AreaChart,DotPlotTextField,Checkbox,Dropdown,RadioButtonPlus:
renderDocument()/renderDocumentWithLayout()over the optional@formepdf/corepeer, aformePreview()SvelteKit route helper (live preview with layout overlays and click-to-inspect),PAGE_NUMBER/TOTAL_PAGESconstants,Font/StyleSheetparity, andtw()support.How it works
Svelte components can't be walked like a React element tree, so each component SSR-renders a namespaced placeholder tag (
<forme-view props="...">) carrying its JSON-encoded props.serialize()renders the template on the server and a parse5-based parser converts the markup into the identical document-model JSON the react adapter produces. The placeholder vocabulary is an internal contract between the components and the parser inside this package, documented as such.{#each},{#if}, snippets, and interpolation just work - templates are plain Svelte 5 components with no special template language. Whitespace is normalized to JSX-equivalent semantics so.svelteand.tsxtwins serialize identically.Drift protection
.svelteand.tsx, asserted deep-equal after normalizing adjacent same-style text runs (react splits runs at JSX child boundaries, e.g.<Strong>${'$'}{price}.00</Strong>is three string children; Svelte's SSR merges contiguous text before the parser ever sees it - adjacent runs with identical style/href render identically, and runs with different styles are never merged, so real drift still fails).@formepdf/sharedand are consumed by both adapters, so they cannot drift by construction.@formepdf/core(engine 0.10.5), asserting page counts, header repetition, roman list markers, transform matrices, embedded fonts, and page-number substitution in content streams.docs/svelte.mdxand the README is a fixture compiled and serialized in tests.Scope notes
sourceLocation: SSR markup carries no source positions, so click-to-inspect maps to elements, not.sveltesource lines.[Unreleased](shared and svelte initial entries, react extraction note); versions aligned with the 0.10.5 monorepo line for the maintainer to stamp at release.Test results