@bookitive/widget-js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @bookitive/widget-js
|
|
2
|
+
|
|
3
|
+
Typed loader for the embeddable **Bookitive** seat-map widget — the same pattern
|
|
4
|
+
as [`@stripe/stripe-js`](https://github.com/stripe/stripe-js).
|
|
5
|
+
|
|
6
|
+
The widget is a UI runtime (seat map, live availability, checkout) served from a
|
|
7
|
+
versioned CDN bundle. **Don't bundle it** — this package injects that `<script>`
|
|
8
|
+
for you and returns a fully typed `mount()`, so you get type-safety and clean
|
|
9
|
+
imports while the runtime keeps auto-updating from the CDN.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i @bookitive/widget-js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { loadBookitive, type BookitiveCustomEvent } from "@bookitive/widget-js";
|
|
17
|
+
|
|
18
|
+
const bk = await loadBookitive(); // injects events.bookitive.com/widget/v1.js
|
|
19
|
+
const el = document.getElementById("seatmap")!;
|
|
20
|
+
|
|
21
|
+
const controller = bk.mount(el, {
|
|
22
|
+
org: "purples",
|
|
23
|
+
event: "purples-2026",
|
|
24
|
+
legend: true,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
el.addEventListener("bookitive:selectionchanged", (e) => {
|
|
28
|
+
const { selection, totalCents } = (
|
|
29
|
+
e as BookitiveCustomEvent<"bookitive:selectionchanged">
|
|
30
|
+
).detail;
|
|
31
|
+
// update your cart…
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// later
|
|
35
|
+
controller.destroy();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `loadBookitive(options?)`
|
|
39
|
+
|
|
40
|
+
- `scriptOrigin` — origin serving the bundle (default `https://events.bookitive.com`).
|
|
41
|
+
- `version` — widget runtime major (default `"v1"`).
|
|
42
|
+
|
|
43
|
+
Idempotent: repeated calls reuse the in-flight promise / already-loaded global.
|
|
44
|
+
Rejects during SSR (no `window`), so call it from `onMount`/`useEffect`.
|
|
45
|
+
|
|
46
|
+
The host page owns tabs, cart and the checkout button; the widget reports
|
|
47
|
+
everything through bubbling `CustomEvent`s (`bookitive:ready`,
|
|
48
|
+
`bookitive:selectionchanged`, `bookitive:seatselected`,
|
|
49
|
+
`bookitive:seatdeselected`, `bookitive:error`) — all typed via
|
|
50
|
+
`BookitiveCustomEvent<'…'>`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
interface WidgetOptions {
|
|
2
|
+
/** API origin; defaults to the production API. */
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
/** Organization slug. */
|
|
5
|
+
org: string;
|
|
6
|
+
/** Event slug. */
|
|
7
|
+
event: string;
|
|
8
|
+
/** Floor id to render first (from bookitive:ready → event.floors); default first. */
|
|
9
|
+
floor?: string;
|
|
10
|
+
/** Show seat code labels on the map. */
|
|
11
|
+
showLabels?: boolean;
|
|
12
|
+
/** Render a legend (ticket-type colours + seat statuses) above the map. */
|
|
13
|
+
legend?: boolean;
|
|
14
|
+
/** Show the "Powered by Bookitive" credit below the map (default true). */
|
|
15
|
+
credit?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface SelectionItem {
|
|
18
|
+
seatId: string;
|
|
19
|
+
code: string;
|
|
20
|
+
/** Pretty localized label, e.g. "Stůl A7 · Místo 3". */
|
|
21
|
+
label: string;
|
|
22
|
+
ticketTypeId: string;
|
|
23
|
+
ticketTypeName: string;
|
|
24
|
+
priceCents: number;
|
|
25
|
+
}
|
|
26
|
+
interface CheckoutInput {
|
|
27
|
+
customer: {
|
|
28
|
+
name: string;
|
|
29
|
+
email: string;
|
|
30
|
+
phone?: string;
|
|
31
|
+
};
|
|
32
|
+
/** Standing/GA quantities the host sells outside the map. */
|
|
33
|
+
extraItems?: {
|
|
34
|
+
ticketTypeId: string;
|
|
35
|
+
gaAreaId?: string;
|
|
36
|
+
quantity: number;
|
|
37
|
+
}[];
|
|
38
|
+
captchaToken?: string;
|
|
39
|
+
}
|
|
40
|
+
interface CheckoutResult {
|
|
41
|
+
reference?: string;
|
|
42
|
+
status?: string;
|
|
43
|
+
totalCents?: number;
|
|
44
|
+
currency?: string;
|
|
45
|
+
expiresAt?: string;
|
|
46
|
+
payment?: {
|
|
47
|
+
bankAccount?: string;
|
|
48
|
+
variableSymbol?: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The handle mount() returns. */
|
|
52
|
+
interface WidgetController {
|
|
53
|
+
setFloor: (floorId: string) => void;
|
|
54
|
+
getSelection: () => SelectionItem[];
|
|
55
|
+
clearSelection: () => void;
|
|
56
|
+
checkout: (input: CheckoutInput) => Promise<CheckoutResult>;
|
|
57
|
+
destroy: () => void;
|
|
58
|
+
}
|
|
59
|
+
/** The global the CDN runtime installs on `window`. */
|
|
60
|
+
interface Bookitive {
|
|
61
|
+
mount: (el: HTMLElement, options: WidgetOptions) => WidgetController;
|
|
62
|
+
}
|
|
63
|
+
interface BookitiveEventDetail {
|
|
64
|
+
"bookitive:ready": {
|
|
65
|
+
event: unknown;
|
|
66
|
+
};
|
|
67
|
+
"bookitive:selectionchanged": {
|
|
68
|
+
selection: SelectionItem[];
|
|
69
|
+
totalCents: number;
|
|
70
|
+
};
|
|
71
|
+
"bookitive:seatselected": {
|
|
72
|
+
seatId: string;
|
|
73
|
+
code?: string;
|
|
74
|
+
};
|
|
75
|
+
"bookitive:seatdeselected": {
|
|
76
|
+
seatId: string;
|
|
77
|
+
code?: string;
|
|
78
|
+
reason?: "taken";
|
|
79
|
+
};
|
|
80
|
+
"bookitive:error": {
|
|
81
|
+
message: string;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
type BookitiveEventName = keyof BookitiveEventDetail;
|
|
85
|
+
type BookitiveCustomEvent<K extends BookitiveEventName> = CustomEvent<BookitiveEventDetail[K]>;
|
|
86
|
+
interface LoadBookitiveOptions {
|
|
87
|
+
/** Origin serving the widget bundle. Default: https://events.bookitive.com */
|
|
88
|
+
scriptOrigin?: string;
|
|
89
|
+
/** Major version of the widget runtime. Default: "v1". */
|
|
90
|
+
version?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Inject the Bookitive widget runtime from the CDN (once) and resolve to the
|
|
94
|
+
* typed global. Safe to call repeatedly — subsequent calls reuse the same
|
|
95
|
+
* in-flight promise / already-loaded global. Rejects during SSR (no `window`).
|
|
96
|
+
*/
|
|
97
|
+
declare function loadBookitive(options?: LoadBookitiveOptions): Promise<Bookitive>;
|
|
98
|
+
|
|
99
|
+
export { type Bookitive, type BookitiveCustomEvent, type BookitiveEventDetail, type BookitiveEventName, type CheckoutInput, type CheckoutResult, type LoadBookitiveOptions, type SelectionItem, type WidgetController, type WidgetOptions, loadBookitive };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var SCRIPT_ATTR = "data-bookitive-widget";
|
|
3
|
+
var pending = null;
|
|
4
|
+
function existingGlobal() {
|
|
5
|
+
if (typeof window === "undefined") return null;
|
|
6
|
+
return window.Bookitive ?? null;
|
|
7
|
+
}
|
|
8
|
+
function loadBookitive(options = {}) {
|
|
9
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
10
|
+
return Promise.reject(new Error("loadBookitive() must run in the browser"));
|
|
11
|
+
}
|
|
12
|
+
const already = existingGlobal();
|
|
13
|
+
if (already) return Promise.resolve(already);
|
|
14
|
+
if (pending) return pending;
|
|
15
|
+
const origin = (options.scriptOrigin ?? "https://events.bookitive.com").replace(/\/+$/, "");
|
|
16
|
+
const version = options.version ?? "v1";
|
|
17
|
+
const src = `${origin}/widget/${version}.js`;
|
|
18
|
+
pending = new Promise((resolve, reject) => {
|
|
19
|
+
const settle = () => {
|
|
20
|
+
const bk = existingGlobal();
|
|
21
|
+
if (bk) resolve(bk);
|
|
22
|
+
else
|
|
23
|
+
reject(
|
|
24
|
+
new Error("Bookitive global missing after the widget script loaded")
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
const fail = () => {
|
|
28
|
+
pending = null;
|
|
29
|
+
reject(new Error(`Failed to load the Bookitive widget from ${src}`));
|
|
30
|
+
};
|
|
31
|
+
const existing = document.querySelector(
|
|
32
|
+
`script[${SCRIPT_ATTR}]`
|
|
33
|
+
);
|
|
34
|
+
if (existing) {
|
|
35
|
+
if (existingGlobal()) return settle();
|
|
36
|
+
existing.addEventListener("load", settle, { once: true });
|
|
37
|
+
existing.addEventListener("error", fail, { once: true });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const s = document.createElement("script");
|
|
41
|
+
s.src = src;
|
|
42
|
+
s.async = true;
|
|
43
|
+
s.setAttribute(SCRIPT_ATTR, "");
|
|
44
|
+
s.addEventListener("load", settle, { once: true });
|
|
45
|
+
s.addEventListener("error", fail, { once: true });
|
|
46
|
+
document.head.appendChild(s);
|
|
47
|
+
});
|
|
48
|
+
return pending;
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
loadBookitive
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["// @bookitive/widget-js — a thin, typed loader for the embeddable Bookitive\n// seat-map widget, in the spirit of @stripe/stripe-js.\n//\n// The widget itself is a UI runtime (Svelte + the seat-map renderer + live\n// availability) served from a versioned CDN bundle — you should NOT bundle it\n// into your app. This package injects that <script> for you and hands back a\n// fully typed `Bookitive.mount()`, so hosts get type-safety and clean imports\n// while the runtime keeps auto-updating from the CDN.\n//\n// import { loadBookitive } from '@bookitive/widget-js';\n// const bk = await loadBookitive();\n// const controller = bk.mount(el, { org: 'purples', event: 'purples-2026', legend: true });\n// el.addEventListener('bookitive:selectionchanged', (e) => {\n// const { selection, totalCents } = (e as BookitiveCustomEvent<'bookitive:selectionchanged'>).detail;\n// });\n\n// --- mount surface (mirrors the widget's public API) -------------------------\n\nexport interface WidgetOptions {\n /** API origin; defaults to the production API. */\n baseUrl?: string;\n /** Organization slug. */\n org: string;\n /** Event slug. */\n event: string;\n /** Floor id to render first (from bookitive:ready → event.floors); default first. */\n floor?: string;\n /** Show seat code labels on the map. */\n showLabels?: boolean;\n /** Render a legend (ticket-type colours + seat statuses) above the map. */\n legend?: boolean;\n /** Show the \"Powered by Bookitive\" credit below the map (default true). */\n credit?: boolean;\n}\n\nexport interface SelectionItem {\n seatId: string;\n code: string;\n /** Pretty localized label, e.g. \"Stůl A7 · Místo 3\". */\n label: string;\n ticketTypeId: string;\n ticketTypeName: string;\n priceCents: number;\n}\n\nexport interface CheckoutInput {\n customer: { name: string; email: string; phone?: string };\n /** Standing/GA quantities the host sells outside the map. */\n extraItems?: { ticketTypeId: string; gaAreaId?: string; quantity: number }[];\n captchaToken?: string;\n}\n\nexport interface CheckoutResult {\n reference?: string;\n status?: string;\n totalCents?: number;\n currency?: string;\n expiresAt?: string;\n payment?: { bankAccount?: string; variableSymbol?: string };\n}\n\n/** The handle mount() returns. */\nexport interface WidgetController {\n setFloor: (floorId: string) => void;\n getSelection: () => SelectionItem[];\n clearSelection: () => void;\n checkout: (input: CheckoutInput) => Promise<CheckoutResult>;\n destroy: () => void;\n}\n\n/** The global the CDN runtime installs on `window`. */\nexport interface Bookitive {\n mount: (el: HTMLElement, options: WidgetOptions) => WidgetController;\n}\n\n// --- CustomEvents the widget dispatches (bubble from the mount element) -------\n\nexport interface BookitiveEventDetail {\n \"bookitive:ready\": { event: unknown };\n \"bookitive:selectionchanged\": {\n selection: SelectionItem[];\n totalCents: number;\n };\n \"bookitive:seatselected\": { seatId: string; code?: string };\n \"bookitive:seatdeselected\": {\n seatId: string;\n code?: string;\n reason?: \"taken\";\n };\n \"bookitive:error\": { message: string };\n}\nexport type BookitiveEventName = keyof BookitiveEventDetail;\nexport type BookitiveCustomEvent<K extends BookitiveEventName> = CustomEvent<\n BookitiveEventDetail[K]\n>;\n\n// --- loader ------------------------------------------------------------------\n\nexport interface LoadBookitiveOptions {\n /** Origin serving the widget bundle. Default: https://events.bookitive.com */\n scriptOrigin?: string;\n /** Major version of the widget runtime. Default: \"v1\". */\n version?: string;\n}\n\nconst SCRIPT_ATTR = \"data-bookitive-widget\";\nlet pending: Promise<Bookitive> | null = null;\n\nfunction existingGlobal(): Bookitive | null {\n if (typeof window === \"undefined\") return null;\n return (window as unknown as { Bookitive?: Bookitive }).Bookitive ?? null;\n}\n\n/**\n * Inject the Bookitive widget runtime from the CDN (once) and resolve to the\n * typed global. Safe to call repeatedly — subsequent calls reuse the same\n * in-flight promise / already-loaded global. Rejects during SSR (no `window`).\n */\nexport function loadBookitive(\n options: LoadBookitiveOptions = {},\n): Promise<Bookitive> {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return Promise.reject(new Error(\"loadBookitive() must run in the browser\"));\n }\n const already = existingGlobal();\n if (already) return Promise.resolve(already);\n if (pending) return pending;\n\n const origin = (\n options.scriptOrigin ?? \"https://events.bookitive.com\"\n ).replace(/\\/+$/, \"\");\n const version = options.version ?? \"v1\";\n const src = `${origin}/widget/${version}.js`;\n\n pending = new Promise<Bookitive>((resolve, reject) => {\n const settle = () => {\n const bk = existingGlobal();\n if (bk) resolve(bk);\n else\n reject(\n new Error(\"Bookitive global missing after the widget script loaded\"),\n );\n };\n const fail = () => {\n pending = null;\n reject(new Error(`Failed to load the Bookitive widget from ${src}`));\n };\n\n // Reuse a script tag another loadBookitive() (or a manual embed) added.\n const existing = document.querySelector<HTMLScriptElement>(\n `script[${SCRIPT_ATTR}]`,\n );\n if (existing) {\n if (existingGlobal()) return settle();\n existing.addEventListener(\"load\", settle, { once: true });\n existing.addEventListener(\"error\", fail, { once: true });\n return;\n }\n\n const s = document.createElement(\"script\");\n s.src = src;\n s.async = true;\n s.setAttribute(SCRIPT_ATTR, \"\");\n s.addEventListener(\"load\", settle, { once: true });\n s.addEventListener(\"error\", fail, { once: true });\n document.head.appendChild(s);\n });\n return pending;\n}\n"],"mappings":";AAyGA,IAAM,cAAc;AACpB,IAAI,UAAqC;AAEzC,SAAS,iBAAmC;AAC1C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAQ,OAAgD,aAAa;AACvE;AAOO,SAAS,cACd,UAAgC,CAAC,GACb;AACpB,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,aAAa;AACpE,WAAO,QAAQ,OAAO,IAAI,MAAM,yCAAyC,CAAC;AAAA,EAC5E;AACA,QAAM,UAAU,eAAe;AAC/B,MAAI,QAAS,QAAO,QAAQ,QAAQ,OAAO;AAC3C,MAAI,QAAS,QAAO;AAEpB,QAAM,UACJ,QAAQ,gBAAgB,gCACxB,QAAQ,QAAQ,EAAE;AACpB,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,MAAM,GAAG,MAAM,WAAW,OAAO;AAEvC,YAAU,IAAI,QAAmB,CAAC,SAAS,WAAW;AACpD,UAAM,SAAS,MAAM;AACnB,YAAM,KAAK,eAAe;AAC1B,UAAI,GAAI,SAAQ,EAAE;AAAA;AAEhB;AAAA,UACE,IAAI,MAAM,yDAAyD;AAAA,QACrE;AAAA,IACJ;AACA,UAAM,OAAO,MAAM;AACjB,gBAAU;AACV,aAAO,IAAI,MAAM,4CAA4C,GAAG,EAAE,CAAC;AAAA,IACrE;AAGA,UAAM,WAAW,SAAS;AAAA,MACxB,UAAU,WAAW;AAAA,IACvB;AACA,QAAI,UAAU;AACZ,UAAI,eAAe,EAAG,QAAO,OAAO;AACpC,eAAS,iBAAiB,QAAQ,QAAQ,EAAE,MAAM,KAAK,CAAC;AACxD,eAAS,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AACvD;AAAA,IACF;AAEA,UAAM,IAAI,SAAS,cAAc,QAAQ;AACzC,MAAE,MAAM;AACR,MAAE,QAAQ;AACV,MAAE,aAAa,aAAa,EAAE;AAC9B,MAAE,iBAAiB,QAAQ,QAAQ,EAAE,MAAM,KAAK,CAAC;AACjD,MAAE,iBAAiB,SAAS,MAAM,EAAE,MAAM,KAAK,CAAC;AAChD,aAAS,KAAK,YAAY,CAAC;AAAA,EAC7B,CAAC;AACD,SAAO;AACT;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bookitive/widget-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Loader + TypeScript types for the embeddable Bookitive seat-map widget. Injects the CDN runtime (like @stripe/stripe-js) and returns a typed mount().",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"bookitive",
|
|
10
|
+
"seatmap",
|
|
11
|
+
"tickets",
|
|
12
|
+
"widget",
|
|
13
|
+
"embed"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"prettier": "^3.4.2",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^5.7.3"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup src/index.ts --format esm --dts --clean --sourcemap",
|
|
36
|
+
"check": "tsc --noEmit",
|
|
37
|
+
"lint": "prettier --check src"
|
|
38
|
+
}
|
|
39
|
+
}
|