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

@forgebuild/sidon-booking-widget

v0.4.17

Published

Drop-in berth booking widget (custom element) backed by sidon-ai's public marina API.

Readme

@forgebuild/sidon-booking-widget

A drop-in berth booking widget for sidon-ai marinas. Ships as <sidon-booking-widget> — a self-contained custom element compiled from Svelte, so it works on any site (Astro, plain HTML, React, WordPress — anything) with one script include and one HTML tag. No framework integration required in the consumer.

It ports sidon-ai's Book*.astro wizard (document upload with AI-extracted autofill → sailor/boat details + contract + Stripe payment hold → confirmation) into a portable package, talking directly to sidon-ai's public marina API (the same cross-origin pattern portals already uses via SIDON_ORIGIN).

Usage

<script type="module" src="/path/to/sidon-booking-widget.js"></script>

<sidon-booking-widget
  marina="org_3D8ZaTo2ZvmMVRjcHgNeMBC7bIq"
  api-origin="https://sidonmarine.uk"
  from="2026-08-02"
  to="2026-08-03"
  length="25"
  berth="B32"
  locale="it"
></sidon-booking-widget>

Or as an npm dependency (once published to npm — see below):

import '@forgebuild/sidon-booking-widget';

then use <sidon-booking-widget> directly in markup — in Astro this needs no @astrojs/svelte integration, since a custom element is just an HTML tag.

Attributes

| Attribute | Required | Description | |---|---|---| | marina | yes | The marina's ownerId (e.g. org_...) — not the URL slug. Read it off the live booking page's network requests if you don't already have it (content markdown copies of this value have gone stale before — verify against a live request, don't trust it blindly). | | api-origin | no | Defaults to https://sidonmarine.uk. Override for a staging/dev sidon-ai instance. | | from / to | no | Initial YYYY-MM-DD date range. Falls back to today → +3 nights. | | length | no | Initial boat length in metres. Defaults to 15. | | berth | no | Pre-selected berth id. If omitted (or no longer fits/available), the widget auto-picks the smallest available berth that fits the current length. | | locale | no | it / en / fr / es. Defaults to en. | | currency-symbol | no | Defaults to €. | | variant | no | navy / dark. Defaults to navy. See Theming. |

The widget does not rewrite the host page's URL to stay in sync with its internal state (unlike sidon-ai's own book.astro) — it can't assume it owns the page it's embedded in. Listen for the events below if the host page wants to reflect state itself.

Events

Dispatched on the <sidon-booking-widget> element (bubbles: true, composed: true, so they cross the shadow boundary and can be caught anywhere on the page):

  • sidon-step-change — { step: 1 | 2 | 3 }, fired whenever the active wizard step changes.
  • sidon-loa-change — { loa: number }, fired on mount and whenever the sailor adjusts boat length in Summary's +/- control. Lets a host page (e.g. a map) grey out berths that no longer fit.
  • sidon-booked — { bookingId, marina }, fired once a booking is successfully submitted.
  • sidon-booking-status — { bookingId, status: 'confirmed' | 'cancelled' }, fired on step 3 if the marina confirms/declines the booking while the sailor still has the widget open (live, via Ably — see the caveat below). Never fires if the tab is closed before the marina acts.

Theming

The widget renders inside a Shadow DOM with its own compiled CSS, so it never depends on or collides with the host page's stylesheet. Brand colours cross the shadow boundary via inherited CSS custom properties — the same convention sidon-ai already uses to re-skin Pirates Bight:

sidon-booking-widget {
  --brand-navy: #0e3a3e;
  --brand-gold: #d49a3c;
  --brand-ink: #0e3a3e; /* dark card surface */
}

Falls back to sidon-ai's defaults (#16365b navy, #c9a86b gold) when unset.

Colour custom properties re-skin the palette; the variant attribute switches the Next button and document-upload rows between sidon-ai's two button conventions (same mechanism as BookStepNav/BookStep1Documents's own variant prop — kept in sync with those on purpose):

<sidon-booking-widget variant="dark" ...></sidon-booking-widget>
  • navy (default, Positano) — gold Next button, navy-on-hover, rounded corners.
  • dark (Fox/Pevero) — solid --brand-ink Next button, gold-on-hover, flush corners.

Live booking status (Ably)

Step 3 subscribes to sidon-ai's booking:<id> Ably channel (same mechanism as book-success-live.ts) and repaints the panel in place — gold "pending" → green "confirmed" / red "cancelled" — the moment the marina acts, without a page reload. ably is dynamically imported only when step 3 is reached, but note the current build inlines all dynamic imports into the single output file (see vite.config.ts), so it does add to every consumer's download regardless of whether they ever reach step 3. Fails open on any error — the sailor just keeps the static "pending" state, exactly like before this existed.

Known gap: sidon-ai's /api/marina/public/[marinaId]/* endpoints have CORS enabled (src/lib/cors.ts) so this widget can call them from any embedding origin, but /api/ably/token currently does not — a cross-origin token request 404s on preflight. Live status updates work today only when the widget is embedded on a sidon-ai-served page; on a marina's own domain (the actual npm/script-tag use case) the token fetch fails, is swallowed by the fail-open error handling, and the panel silently stays in the static "pending" state. Needs a fix on the sidon-ai side (add preflight/corsJson from lib/cors.ts to src/pages/api/ably/token.ts, matching the other public endpoints) before this is live for real embeds.

What's deliberately different from sidon-ai's own wizard (v1 scope)

  • No resend-confirmation-email on the confirmation step.
  • No season-gated date picker / berth map integration — this widget doesn't ship a map. Berth auto-pick is length + availability only, same math as sidon-ai's recalc(), minus the map-driven manual-pick override.
  • Plain CSS, not Tailwind — kept the build dependency-free; visuals are a close but not pixel-exact port (no Teko/Hind webfonts bundled — system font stack instead).
  • UI scope is the full-page wizard only (Book*.astro, matches the /services/book flow) — not portals' map-embedded BerthsSizeBar popover, which was a different, more tightly-coupled UI left for a future package version. That version now exists as <sidon-berth-map> — see below.

<sidon-berth-map>

A second custom element in this package: a self-contained berth map (buoy circles or SVG pontoon overlays) with a floating quick-book card (dates, boat length, selected berth, submit). Ports sea-company's BerthsGoogleMap.astro/berth-map-google.ts + BerthsBookingCard.astro/ berths-booking-card.ts — previously coordinated via a window.sidonBerthMap global — into one globals-free widget, same "one drop-in tag" model as <sidon-booking-widget>.

Usage

import '@forgebuild/sidon-booking-widget/berth-map';
<sidon-berth-map
  marina="org_3D8ZaTo2ZvmMVRjcHgNeMBC7bIq"
  maps-api-key="YOUR_GOOGLE_MAPS_JS_API_KEY"
  api-origin="https://sidonmarine.uk"
  map-style="chart"
  locale="en"
  book-path="/services/book"
></sidon-berth-map>

Attributes

| Attribute | Required | Description | |---|---|---| | marina | yes | Same ownerId convention as <sidon-booking-widget> | | maps-api-key | yes | Google Maps JS API key — this element's only extra credential | | api-origin | no | Defaults to https://sidonmarine.uk | | map-style | no | chart (buoy circles, default) or coastal (SVG pontoon overlays) | | locale / currency-symbol | no | Same as <sidon-booking-widget> | | from / to / length / berth | no | Same initial-state convention as <sidon-booking-widget> | | book-path | no | If set, auto-navigates here with from/to/length/berth query params on submit — unless a sidon-quickbook-submit listener calls preventDefault(). Omit it and the element only fires the event; the host handles routing itself. | | logo-mark / marina-name | no | Centre-pin logo for this widget's own marina (symmetric with otherMarinas below) | | eyebrow / card-title / intro | no | Optional heading copy rendered above the quick-book card's form fields (eyebrow line, title, intro paragraph). Omit any of the three to skip that line — there's no default text, unlike the labels below. card-title (not title) to avoid colliding with the native HTML title attribute's browser tooltip. | | range-label / range-placeholder / length-label / selected-label / selected-placeholder / cta-label | no | Override the quick-book card's copy (defaults: "Arrival – Departure", "", "Boat length (m)", "Selected buoy", "Tap a buoy", "Book a mooring") |

otherMarinas (JS property)

Peer-marina logo pins — set as a JS property, not a flat HTML attribute, since it's structured data:

document.querySelector('sidon-berth-map').otherMarinas = [
  { name: 'Pevero Mooring', lat: 41.117, lng: 9.394, logoMark: 'https://.../pevero-mark.svg', href: '/m/pevero-mooring/berths' },
];

href is always host-supplied — the package owns no site's routing convention, so it never builds peer-marina links itself.

Events

Same sidon-*, { bubbles: true, composed: true } convention as <sidon-booking-widget>:

  • sidon-berth-selected — { berthId: string | null }
  • sidon-loa-change — { loa: number } (same name/shape <sidon-booking-widget> already uses, so one listener on a host page covers either element)
  • sidon-quickbook-submit — { from, to, length, berth }, dispatched cancelable: true

Theming

Same --brand-navy / --brand-gold / --brand-ink CSS custom properties as <sidon-booking-widget> — one theme block styles both elements consistently.

What's different from sea-company's original map + card

  • No shared globals — doesn't read or write window.sidonBerthMap; all state lives in the element itself, exposed only via attributes/properties/events.
  • No #book-stepper selection lock — that assumed a shared page-level stepper from sea-company's /book page layout, which has no equivalent in a self-contained widget.
  • Plain CSS, not Tailwind, system font stack — not Teko/Hind — same dependency-free trade-off as <sidon-booking-widget>.
  • Submit navigates via URL params, not an in-place wizard hand-off — clicking "Book a mooring" does not mount <sidon-booking-widget> itself; it fires an event and/or navigates to book-path.

Development

npm install
npm run dev      # demo/ harness — hits sidon-ai's live public API directly
npm run build    # library build → dist/sidon-booking-widget.js + dist/sidon-berth-map.js
                  # (each a single self-contained ES module, Svelte runtime bundled in —
                  # zero peer deps for consumers) + matching dist/*.d.ts for each

demo/index.html renders three <sidon-booking-widget> instances (default theme, re-themed, and variant="dark") plus one <sidon-berth-map> instance, all against Fox Mooring's real marina data — useful for a quick visual/functional check without touching any consumer repo. The berth-map instance needs a real Google Maps JS API key substituted in for local testing (don't commit one).

Verified live (2026-08-02) against sidon-ai's production API: document upload, contract preview with live placeholder substitution, and Stripe's Payment Element mounting and initializing inside the widget's Shadow DOM all work — confirmed with Fox Mooring (real marina, test-mode Stripe key). Did not complete an actual booking submission, to avoid leaving a test record in a live dashboard.

Publishing (public npm)

Published to github.com/robooko/sidon-booking-widget (private repo) and the public npm registry as @forgebuild/sidon-booking-widget. To publish a new version:

npm version <patch|minor|major>
npm publish   # requires npm login as an account with publish rights on the @forgebuild scope
git push && git push --tags

npm publish always rebuilds dist/ first via a prepublishOnly script — no need to run npm run build separately, and no way to accidentally publish a stale build from an earlier version (0.4.8 shipped this way before prepublishOnly existed: identical dist/ output to 0.4.7, silently missing that release's actual changes).

To install it as a dependency elsewhere: npm install @forgebuild/sidon-booking-widget. No registry config or auth token needed — it's a public package.