npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tvsgroup/appointment-form

v0.1.8

Published

TVS Engineering appointment booking form as a <tvs-appointment-form> web component with a typed Vue wrapper

Readme

@tvsgroup/appointment-form

Multi-step appointment booking form for TVS Engineering, shipped as the <tvs-appointment-form> web component (Vue 3 inside a shadow root) with an optional typed Vue wrapper. Consumable three ways:

  1. Web component from any framework or plain HTML (Astro, WordPress, …)
  2. Vue wrapper component (AppointmentForm) for Vue apps / Astro with @astrojs/vue
  3. Standalone bundle (dist/appointment-form.es.js) from a CDN or self-hosted — the legacy embed, unchanged

Installation

npm install @tvsgroup/appointment-form

Vue 3 is a peer dependency (npm ≥7 installs it automatically). All other runtime dependencies (Pinia, intl-tel-input, js-datepicker, …) are bundled.

Attributes / Props

| Custom-element attribute | Vue wrapper prop | Type (wrapper) | Default | Description | |---|---|---|---|---| | lang | lang | string | 'nl' | UI language (nl or en) | | aws_url | awsUrl | string | — (required) | Base URL of the appointment API | | vacation_mode | vacationMode | boolean | false | Holiday closure: disables queue/direct lanes, shows closure banner. Attribute form accepts "true" / "1" / "yes" / bare attribute | | vacation_start | vacationStart | string | '' | First closed day, ISO YYYY-MM-DD | | vacation_end | vacationEnd | string | '' | Last closed day, ISO YYYY-MM-DD | | vacation_reopen | vacationReopen | string | '' | First day back at work, ISO YYYY-MM-DD |

Usage

Astro — web component (no framework integration needed)

---
// src/pages/afspraak.astro
---
<tvs-appointment-form
  lang="nl"
  aws_url="https://<api-id>.execute-api.us-east-1.amazonaws.com"
></tvs-appointment-form>

<script>
  import '@tvsgroup/appointment-form/register';
</script>

The /register import defines the element on the client (idempotent, SSR-safe).

Astro + @astrojs/vue — Vue wrapper

---
import { AppointmentForm } from '@tvsgroup/appointment-form';
---
<AppointmentForm
  client:only="vue"
  lang="nl"
  awsUrl="https://<api-id>.execute-api.us-east-1.amazonaws.com"
  vacationMode={false}
/>

client:only="vue" is recommended — the form is fully interactive and gains nothing from SSR.

Plain HTML (standalone bundle — legacy embed)

<script type="module" src="https://unpkg.com/@tvsgroup/appointment-form/dist/appointment-form.es.js"></script>

<tvs-appointment-form
  lang="nl"
  aws_url="https://<api-id>.execute-api.us-east-1.amazonaws.com"
></tvs-appointment-form>

Host-page notes

  • Multiple instances supported. Each <tvs-appointment-form> element gets its own Pinia instance (installed per element via configureApp), so several forms on one page keep fully independent state.
  • Fonts: the component inherits the host page's font stack; the TVS look expects Inter to be loaded by the host.
  • Styles are encapsulated in a shadow root — the host page's CSS (and Tailwind config) won't clash with the form's.
  • Runtime network: the phone input loads the intl-tel-input utils script from jsDelivr and does a geo-IP lookup (ipapi.co) for the default country flag.

Package layout

| Entry | File | Vue | |---|---|---| | @tvsgroup/appointment-form | dist/index.js — AppointmentForm, register(), TAG_NAME, types | external (peer) | | @tvsgroup/appointment-form/register | dist/register.js — side-effect: defines the element | external (peer) | | standalone | dist/appointment-form.es.js — self-contained, self-registering | bundled |

Development

npm install
npm run dev          # dev harness (src/main.ts + App.vue)
npm run build        # standalone bundle + npm library (dist/)
npm run test:unit    # vitest
npm run e2e:dry      # Playwright suite in dry-run mode
npm run type-check

Releasing

Publishing is automated by .github/workflows/publish-npm.yml: every push to main builds the package and publishes it to npm, skipping the publish when that version is already on the registry. So a release is just a version bump in package.json merged to main.

Auth uses npm trusted publishing (OIDC) — there is no NPM_TOKEN secret to rotate, and provenance is attached automatically.

The standalone bundle is no longer deployed to S3; it ships inside the npm package as dist/appointment-form.es.js and is reachable from any npm CDN.

Known Issues / Pending Fixes

These items are tracked here so the team has a single source of truth for outstanding tech debt. Update this list as items are resolved or new issues are discovered.

Cross-repo: Lambda createAppointment (tvs-cloud-services/services/rest/src/createAppointment.ts)

  1. loanCar contract mismatch (worked around in this repo). The Lambda types loanCar as string (scalar) and forwards it directly to Frappe's scalar fields loan_car and custom_loan_car. This webcomponent originally sent loanCar as string[] (multiselect), which caused Frappe to throw frappe.exceptions.ValidationError: Waar voor Loan car kan geen lijst worden (HTTP 417).

    • Current workaround: buildSubmitPayload in src/stores/appointmentForm.ts serializes the array to a CSV string before fetch.
    • Proper fix: keep loanCar: string[] end-to-end and let the Lambda do the join. The transformation belongs in the anti-corruption layer (Lambda), not the UI.
  2. Bug at createAppointment.ts:152 (latent). In the PUT branch (when a project already exists), the field assignment is custom_is_loan_car: data.loanCar — it should be data.needsLoanCar (boolean). Today it never fires because the flow always goes through POST, but it will break the moment a customer updates an existing appointment.

Internal: this repo

  1. Duplicate LOAN_CAR_LABELS mapping. The map golf6 → "GOLF 6 DSG", up → "VW UP!", caddy → "VW CADDY" lives in two places:

    • src/stores/appointmentForm.ts — used by buildSubmitPayload for the wire payload.
    • src/views/steps/ConfirmView.vue — used by formatLoanCars for the confirmation summary shown to the customer.

    If a label changes in only one place, the customer will see one value on Confirm but a different one will land in ERPNext. Candidate to dedupe into a shared util / constants module.