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

@odeva/booking-widgets

v0.1.8

Published

Web components for Odeva-powered booking widgets

Readme

Odeva Booking Widgets

Web components for adding Odeva-powered booking flows to any site.

Install

npm install @odeva/booking-widgets

Use from the CDN

For sites that are not using npm or a bundler, load the hosted widget script directly:

<script src="https://booking.odeva.app/widgets/booking.js" defer></script>
<odeva-booking-widget org="my-org"></odeva-booking-widget>

Use

Import the package once, then render the widget.

import "@odeva/booking-widgets";
<odeva-booking-widget
  org="my-org"
  api-url="https://booking.odeva.app/graphql"
  api-key="pk_live_…"
></odeva-booking-widget>

Attributes

  • org — Odeva organisation slug.
  • api-url — GraphQL API URL. Use https://booking.odeva.app/graphql for embeds on third-party sites.
  • api-key — publishable API key (pk_live_…). Required for cross-origin embeds. Create one in the admin under Settings → API keys with type Publishable.
  • checkout-url — optional override for the hosted checkout base URL.
  • accommodation-id — optional accommodation ID to deep-link straight into the detail view.

The package also exports the lower-level components: <odeva-search>, <odeva-search-and-book>, <odeva-accommodations>, <odeva-detail>, <odeva-booking>. They each accept api-url and api-key with the same semantics.

<odeva-search-and-book> is a full "search & book" results page: sidebar with dates, guests, and facility (metafield) filters grouped by category, plus a results list. It emits odeva-select-accommodation on card click — wire it into <odeva-booking-widget> or handle navigation yourself.

Static website components

The package also includes reusable content components for marketing pages that want to match Odeva-hosted websites without using the full website editor:

  • <odeva-content-grid> — feature, facilities, policies, benefits, and location highlight grids.
  • <odeva-category-grid> — image cards for stay types or accommodation categories.
  • <odeva-faq> — frequently asked questions.
  • <odeva-gallery-mosaic> — responsive image gallery.
  • <odeva-review-summary> — rating plus guest quotes.
  • <odeva-cta-panel> — seasonal notices, map prompts, and direct booking CTAs.

All static components use the same --odeva-* theme variables as the booking widgets. Collection-style attributes accept JSON:

<odeva-content-grid
  heading="Why book direct?"
  intro="Book directly for clear prices and personal help."
  columns="3"
  items='[
    {"title":"Best direct price","body":"No hidden platform fees."},
    {"title":"Personal contact","body":"Questions go straight to the host."},
    {"title":"Secure booking","body":"Reserve through the Odeva booking flow."}
  ]'
></odeva-content-grid>

<odeva-faq
  heading="Good to know"
  items='[
    {"question":"Can I book directly?","answer":"Yes, use the search form on this site."},
    {"question":"Can I ask a question first?","answer":"Yes, contact the host before booking."}
  ]'
></odeva-faq>

Theming

Widgets are Shadow DOM web components, so host sites theme them through CSS custom properties on the element or any ancestor:

odeva-booking-widget {
  --odeva-color-primary: #b86116;
  --odeva-color-secondary: #6f6253;
  --odeva-color-background: #ffffff;
  --odeva-color-surface: #f7f5f1;
  --odeva-color-surface-raised: #ffffff;
  --odeva-color-border: #ded8cf;
  --odeva-color-text: #4c443c;
  --odeva-color-text-muted: #81766a;
  --odeva-border-radius: 0.375rem;
  --odeva-font-family: "Your Site Font", system-ui, sans-serif;
}

Layout-specific hooks:

  • --odeva-detail-max-width — maximum width of <odeva-detail>.
  • --odeva-booking-max-width — maximum width of <odeva-booking>.
  • --odeva-sticky-top — top offset for sticky detail/summary panels.
  • --odeva-search-background — search panel background override.

Embedding on a third-party site

Publishable keys are intended to be visible in HTML. They unlock only the public scope — search, list accommodations, availability, pricing, reservation creation, and checkout. They cannot read other organisations' data or perform admin actions.

When creating the key in the admin, set Allowed origins to the domains you'll embed on. The server rejects requests whose Origin header doesn't match.

Note on the origin check: This is anti-griefing, not security. Browsers send Origin reliably, so it stops casual copy-paste abuse. A determined attacker can spoof the header from a non-browser client. The real security comes from the publishable scope — a pk_live_… only unlocks public operations.

Examples of allowed origins:

https://example.com
https://*.example.com
http://localhost:5173

Leave empty to allow any origin (recommended only during development).

Proxy pattern (for secret keys)

If you need to call privileged operations (server-side reservation tools, audit logs, etc.), use a secret key (sk_live_…) and proxy through your own backend. Never expose secret keys in the browser.

// app server (e.g. Next.js route handler)
export async function POST(req: Request) {
  const upstream = await fetch("https://booking.odeva.app/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": process.env.ODEVA_SECRET_KEY,
    },
    body: await req.text(),
  });
  return new Response(upstream.body, { status: upstream.status });
}

Point the widget at the proxy:

<odeva-booking-widget org="my-org" api-url="/api/odeva"></odeva-booking-widget>