@vergelijkdirect/comparison-forms
v2.0.2
Published
Headless Vue 3 comparison forms for vergelijkdirect — consumable as a Vue 3 library or as self-contained custom elements.
Keywords
Readme
@vergelijkdirect/comparison-forms — Vue 3 library (2.x)
Headless comparison forms for vergelijkdirect, consumable two ways:
- Vue 3 hosts — npm package export
@vergelijkdirect/comparison-forms/vue - Vue 2.7 / non-Vue hosts — self-contained Web Component
@vergelijkdirect/comparison-forms/wc
The library is the whole of src/. The legacy Vue 2.7 app it replaced was migrated form by form
and has since been removed — its <embedded-comparison-forms> plugin API does not exist here
(see the migration table below). If you still need 1.x, it lives on the v1 branch, in
maintenance mode: bugfixes and security only, no new forms or features.
Install & prerequisites
The ITC client (@vergelijkdirect/insurance-transmission-client) is your peer dependency:
the library never creates or exports it for Vue hosts. You install and initialize it yourself,
once, before mounting any form — the library's forms/flow lazily pull the already-initialized
services (see preview/main.ts for a working bootstrap example).
npm i @vergelijkdirect/comparison-formsLoad the styles once per page:
import '@vergelijkdirect/comparison-forms/vue/styles';Usage
1. Drop-in (recommended for VD) — the form handles everything
<script setup>
import { ComparisonFormFlow } from '@vergelijkdirect/comparison-forms/vue';
// ITC must already be initialized (see "Install & prerequisites" above)
</script>
<template>
<ComparisonFormFlow
form="bike"
link="https://www.vergelijkdirect.com/fietsverzekering/"
layout="horizontal"
@submitted="(url) => track(url)"
@error="showFallbackLead"
/>
</template>Fills the form → creates the comparison via ITC → appends the comparison route (#/<id>/…)
to the base link → redirects.
On failure it emits error(error, payload) and does nothing else — the fallback-lead UI is
yours to wire, with the user's data available in payload.user.
2. Headless — you own the submit
<script setup>
import {
BikeForm,
bikePresenter,
useComparisonSubmit,
} from '@vergelijkdirect/comparison-forms/vue';
const { submit, isSubmitting } = useComparisonSubmit({
link: 'https://…/fietsverzekering/funnel/',
onError: showFallbackLead,
});
</script>
<template>
<BikeForm @submitted="(data) => submit(bikePresenter(data))" />
</template>Or skip the flow entirely (result-page edit, custom persistence):
<BikeForm :initial-data="savedBike" @submitted="updateMyComparison" />initialData is read once on mount — remount with :key when it arrives async.
3. Web Component (Vue 2.7 / plain HTML hosts)
<script
type="module"
src=".../comparison-forms/wc"
></script>
<link
rel="stylesheet"
href=".../comparison-forms/wc/styles"
/>
<vd-comparison-form
form="bike"
link="https://…/fietsverzekering/funnel/"
api-url="https://api.vergelijkdirect.com"
></vd-comparison-form>
<script>
document
.querySelector('vd-comparison-form')
.addEventListener('error', (e) => console.error(e.detail[0]));
</script>The element initializes the shared ITC client from the url attributes — it is its own consumer,
no host-side setup needed. Events are native CustomEvents (submitted, error) with the
emit arguments in detail — error carries the error in detail[0] and, when the failure
happened on submit, the ComparisonPayload in detail[1] (client-init failures have no
payload). initial-data accepts either a JSON-string attribute (initial-data='{"kind":1}', for
plain HTML) or an object set as a DOM property (el.initialData = {…}); other complex props are
DOM properties only.
Migrating from the 1.x <embedded-comparison-forms> events
The 2.x event contract is a deliberate break. How each legacy event maps:
| Legacy (1.x) | When it fired | 2.x equivalent |
| ------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @change | result/coverage page — "update, don't create" | headless @submitted with typed form data; run it through the form's presenter and payload.user carries the fields hosts wrote back (zipCode, birthdate, houseNumber, houseNumberAddition, street, city) |
| @emit-event { event: 'open-quotation-modal', data } | comparison create failed → fallback lead | @error — emits (error, payload); build the fallback lead from payload.user. ITC rejects an unknown postcode the same way as a transport failure — check error instanceof Error if you need to tell them apart |
| @emit-event (no arguments) | submitted with unchanged data → "close me" | none by design — the form always emits submitted; the host owns update semantics, so compare the submitted data with what it passed as initial-data (e.g. lodash isEqual) and skip the update |
| @redirect-page | never reached hosts — the 1.x embedded shell consumed it internally | submitted(url) fires before the redirect; the redirect prop overrides navigation |
Development
npm run dev # live sandbox (preview/) — demos for every component and form
npm run verify # lint + format:check + typecheck + test:unit — the one gate
npm run test:unit # vitest run
npm run typecheck # vue-tsc --noEmit
npm run lint # eslint src tests preview (lint:fix to autofix)
npm run format # prettier --write . (format:check to verify only)Node version is pinned in .nvmrc (Node 24) — run nvm use (or fnm) before installing. Install
with npm ci, not npm install: it installs exactly from the committed package-lock.json and
avoids the npm optional-dependency bug (npm#4828) that
can drop rolldown's native binding and break vite. If a machine gets into that state, a clean
rm -rf node_modules && npm ci fixes it.
Bumping a @vergelijkdirect/* package has rules of its own (exact pins, and a second step for
ITC) — see DEPENDENCIES.md.
src/environment/environment.ts is gitignored — create it from the checked-in
environment.dev.ts / environment.prod.ts first.
Builds
npm run build # typecheck, then every build below (prepublishOnly runs verify + this)
npm run build:lib # dist/vue — Vue 3 library, vue external
npm run build:wc # dist/wc — self-contained <vd-comparison-form> (ES; ITC + Vue inlined)
npm run build:wc-noitc # dist/wc-noitc — same element, UMD, ITC left external (host resolves it)
npm run build:wc-forms # dist/wc-forms — one CJS element bundle per form (<name>.cjs), ITC externalbuild:wc is the self-contained drop-in used from a plain <script> tag. The wc-noitc /
wc-forms variants keep @vergelijkdirect/insurance-transmission-client external so a host
bundler (webpack 5) shares its own configured client with the forms.
Publishing to npm
Manual — no pipeline publishes. Needs membership of the @vergelijkdirect npm org (npm whoami)
and a clean working tree.
nvm use
npm ci
npm run verify # also runs on publish — but fail before the version commit
npm version <patch|minor|major|prerelease> # bumps + commits; the tag it makes stays local
npm publish --tag <latest|next|legacy|rc>
git push
npm view @vergelijkdirect/comparison-forms dist-tags--tag is not optional: a bare npm publish writes latest even for a prerelease.
latest = stable 2.x, next = 2.x RCs, legacy = 1.3.x, rc = 1.x prereleases.
Architecture
entries (lib.ts, wc*.ts) → flow (useComparisonSubmit, ComparisonFormFlow)
→ comparisons (registry + one dir per comparison: Form, schema, models, presenter)
→ components (fields: address/licensePlate/company → ui: Vd* primitives)
entries → questionnaires → components (one-off track; never through flow/ or comparisons/)
data/itc.ts — the single gateway to the ITC client
constants / models / utils / schemas / enums / interfaces — shared leaves, any layer may importPath alias: @ → src/. The rules that matter:
- Forms are headless. They validate (yup via
useValidator) and emitsubmittedwith typed, valid data — no API calls, no redirects, no global state. Whoever handlessubmitteddecides the scenario. - One component per comparison. Vertical/horizontal is the
layoutprop, not a second file. - Dependency arrows point down only. Comparisons never import each other, and never
flow/. - Only
data/touches the ITC client — everyone else goes through the@/databarrel. - Barrels export named symbols only.
export *is an ESLint error undersrc/(tree shaking). - All CSS comes from
@vergelijkdirect/styles(.vd-*) — the library writes none of its own.
Adding a new comparison form
CONTRIBUTING.md — step by step, from the field list to the definition of done, including the seven places a new form must be registered.
