/**
* vapor-chamber — IIFE variant: ELEMENTS
*
* Audience: widget builders. Embeddable chat bubbles, checkout buttons,
* support pop-ups, third-party drop-ins. You ship a `
*
*
*/
function defineWidget(tagName: string, options: any, extraOptions?: any): boolean {
const El = extraOptions !== undefined
? defineVaporCustomElement(options, extraOptions)
: defineVaporCustomElement(options);
if (!El) return false;
if (typeof customElements === 'undefined') return false;
if (customElements.get(tagName)) return true;
customElements.define(tagName, El);
return true;
}
/**
* Emit a real DOM `CustomEvent` from inside a widget so host pages can
* subscribe with `addEventListener`.
*
* Vue's component `emit(...)` goes through Vue's event system — it does
* NOT bubble out as a DOM event. For widgets that need to communicate with
* the surrounding page (a `` notifying its container that
* a product was added), use this to dispatch an actual `CustomEvent`.
*
* Pattern adapted from
* [vue-custom-element](https://github.com/karol-f/vue-custom-element)'s
* `customEmit` helper (Karol-F, MIT) — original predates Vue 3.6's Vapor
* but the underlying gap (Vue emit ≠ DOM event) still exists today.
*
* @example Widget side
* VaporChamber.defineWidget('vc-cart', {
* setup() {
* return () => h('button', {
* onClick: (e) => emitDOMEvent(e.target.getRootNode().host, 'cart-added', { sku: 'X' })
* }, 'Add');
* }
* });
*
* @example Host page
* document.querySelector('vc-cart').addEventListener('cart-added', (e) => {
* console.log(e.detail.sku); // 'X'
* });
*
* @param el — the custom element instance (host element)
* @param eventName — DOM event name (kebab-case is conventional for custom events)
* @param detail — payload attached to event.detail
* @param options — bubbles/composed/cancelable. Defaults: bubbles=true, composed=true
* (composed=true escapes shadow-DOM boundaries by default — required
* for events to reach light-DOM listeners on the host page).
* @returns `false` if `event.preventDefault()` was called by a listener, `true` otherwise.
*/
function emitDOMEvent(
el: Element,
eventName: string,
detail?: any,
options: { bubbles?: boolean; composed?: boolean; cancelable?: boolean } = {},
): boolean {
if (typeof CustomEvent !== 'function' || !el) return true;
const event = new CustomEvent(eventName, {
detail,
bubbles: options.bubbles ?? true,
composed: options.composed ?? true,
cancelable: options.cancelable ?? false,
});
return el.dispatchEvent(event);
}
const VaporChamber = {
// Bus
createCommandBus,
createAsyncCommandBus,
// Convenience
createApp,
connect,
// Transport (HTTP only)
http: createHttpBridge,
// Plugins (lightweight)
logger,
validator,
debounce,
throttle,
authGuard,
retry,
// Widget surface (the variant's identity)
defineVaporCustomElement,
defineWidget,
emitDOMEvent,
} as const;
if (typeof globalThis !== 'undefined') {
(globalThis as any).VaporChamber = VaporChamber;
}
// Default export only: the IIFE build assigns the DEFAULT export to the
// `VaporChamber` global, so the API object lands directly on window
// (`VaporChamber.connect(...)`). A second named export would force the
// bundler to emit a module-namespace wrapper — `{ VaporChamber, default }`
// — and every documented call site would be undefined in a