/** * app.js — Vue 3 application root for one form instance. * * Exports `mountBookingFormInstance(instanceId, initialState)` which: * 1. Creates the per-instance composables (state, api, readiness, nav, * timeslots, submission, bus). * 2. Builds a Vue 3 app from the App component with those composables * injected via props. * 3. Mounts onto the shell `
`. * 4. Registers the instance under `window.BookingPressFormV3.instances[id]` * so add-ons can find it. */ import { createApp } from 'vue'; // Relative imports resolve to URLs without WP's `?ver=` cache-buster, so a // query string is appended manually. Bump the `?v=` suffix when you edit any // of the imported files to force a fresh fetch even when the browser has // cached an earlier copy under a no-query URL. import App from './components/App.js?v=32'; import { createApiClient } from './api/client.js?v=2'; import { useFormState } from './composables/useFormState.js?v=3'; import { useReadiness } from './composables/useReadiness.js?v=4'; import { useStepNavigation } from './composables/useStepNavigation.js?v=4'; import { useTimeslots } from './composables/useTimeslots.js?v=13'; import { useSubmission } from './composables/useSubmission.js?v=6'; // eslint-disable-next-line no-console console.info('[bp-v3] app module loaded'); /** * Mount a single Vue 3 form instance. * * @param {string} instanceId * @param {object} initialState * @returns {object} The mounted-instance handle (also stored on the global registry). */ export function mountBookingFormInstance(instanceId, initialState) { // The shortcode root carries `data-instance=""` (matching the released // markup) AND `data-bp-v3-instance=""` (back-compat for any add-on that // wrote against the M5 path). Either selector finds the same node. const mountSel = `[data-instance="${instanceId}"], [data-bp-v3-instance="${instanceId}"]`; const mountNode = document.querySelector(`[data-instance="${instanceId}"]`) || document.querySelector(`[data-bp-v3-instance="${instanceId}"]`) || document.querySelector(`#bookingpress-form-vue3-${instanceId}`) || document.querySelector(`#bookingpress-form-vue3-bpv3_${instanceId}`) || document.querySelector(`#bp-v3-form-${instanceId}`); if (!mountNode) { // eslint-disable-next-line no-console console.warn('[bp-v3] mount node missing:', mountSel); return null; } // Compose the per-instance world. const { state, bus } = useFormState(initialState); const api = createApiClient({ restRoot: (initialState.rest && initialState.rest.root) || '', instanceId: instanceId, wpRestNonce: (initialState.nonces && initialState.nonces.wpRestNonce) || '', formNonce: (initialState.nonces && initialState.nonces.formNonce) || '', instanceToken: (initialState.nonces && initialState.nonces.instanceToken) || '', }); const readiness = useReadiness(state); const nav = useStepNavigation(state, readiness, bus); const timeslots = useTimeslots(state, api, bus); const submission = useSubmission(state, readiness, api, bus); // Wrap with explicit value-access for the consumer (.value on refs). const handle = { instanceId, state, api, readiness, nav, timeslots, submission, bus, app: null, mountNode }; const app = createApp(App, { instanceId, state, api, readiness, nav, timeslots, submission, bus }); app.config.errorHandler = (err, _vm, info) => { // eslint-disable-next-line no-console console.error('[bp-v3] runtime error', { instanceId, info, error: err }); bus.emit('bp-v3:error', { instanceId, error: err, info }); }; // Register the BookingPress UI plugin if it loaded — exposes // `` (the country-flag phone field used in Basic Details) // and many other components. The bootstrap module side-effect-imports // `bookingpress-ui` so `window.BookingPressUI` should already exist by // the time we mount; guard defensively just in case. if (typeof window !== 'undefined' && window.BookingPressUI && typeof window.BookingPressUI.install === 'function') { try { app.use(window.BookingPressUI); } catch (e) { // eslint-disable-next-line no-console console.warn('[bp-v3] BookingPressUI install failed', e); } } handle.app = app; app.mount(mountNode); // Register the handle so add-ons can interact. if (!window.BookingPressFormV3) window.BookingPressFormV3 = { instances: {} }; if (!window.BookingPressFormV3.instances) window.BookingPressFormV3.instances = {}; window.BookingPressFormV3.instances[instanceId] = handle; // Per plan §3.3. The bus auto-mirrors to the global // `window.BookingPressFormV3.bus`, so a single emit reaches both. bus.emit('bp-v3:form-mounted', { instanceId, state }); return handle; } export default { mountBookingFormInstance };