@parag.more.withsurface.com/forms-sdk 0.1.5 → 0.1.7
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 +12 -6
- package/dist/index.cjs +1 -2736
- package/dist/index.mjs +1 -2713
- package/dist/react/index.cjs +1 -43
- package/dist/react/index.mjs +1 -20
- package/dist/surface-forms-sdk.min.js +1 -1
- package/dist/types/bind/bindForm.d.ts +2 -0
- package/dist/types/bind/bookingWidget.d.ts +29 -6
- package/dist/types/bind/scheduler/embedUtils.d.ts +51 -0
- package/dist/types/bind/scheduler/mountScheduler.d.ts +2 -0
- package/dist/types/bind/scheduler/providers/cal.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/calendly.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/chilical.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/clari.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/hubspot.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/index.d.ts +4 -0
- package/dist/types/bind/scheduler/providers/reclaimai.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/revenuehero.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/savvycal.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/surface.d.ts +3 -0
- package/dist/types/bind/scheduler/providers/zoom.d.ts +3 -0
- package/dist/types/bind/scheduler/styles.d.ts +3 -0
- package/dist/types/bind/scheduler/types.d.ts +65 -0
- package/dist/types/core/client/emailValidation.d.ts +3 -0
- package/dist/types/core/client/responses.d.ts +3 -0
- package/dist/types/core/client/runtimeConfig.d.ts +40 -0
- package/dist/types/core/client/scheduler.d.ts +4 -0
- package/dist/types/core/client/transport.d.ts +4 -0
- package/dist/types/core/engine/answerAudit.d.ts +3 -0
- package/dist/types/core/engine/answerStates.d.ts +4 -0
- package/dist/types/core/engine/formEngine.d.ts +18 -0
- package/dist/types/core/engine/redirect.d.ts +1 -0
- package/dist/types/core/engine/responsePayload.d.ts +4 -0
- package/dist/types/core/engine/schedulerEvents.d.ts +40 -0
- package/dist/types/core/engine/urlData.d.ts +2 -0
- package/dist/types/core/engine/urlPrefill.d.ts +1 -0
- package/dist/types/core/events/dropoffDetection.d.ts +1 -0
- package/dist/types/core/events/emitter.d.ts +1 -0
- package/dist/types/core/events/formJourneyTracker.d.ts +1 -0
- package/dist/types/core/events/pushEvents.d.ts +2 -0
- package/dist/types/core/events/startedDetection.d.ts +1 -0
- package/dist/types/core/events/viewDetection.d.ts +1 -0
- package/dist/types/core/identity/browserFingerprint.d.ts +3 -0
- package/dist/types/core/identity/ensureIdentified.d.ts +1 -0
- package/dist/types/core/identity/identify.d.ts +1 -0
- package/dist/types/core/identity/journey.d.ts +3 -0
- package/dist/types/core/identity/leadDataCache.d.ts +1 -0
- package/dist/types/enrichment/attachEnrichment.d.ts +1 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/react/useSurfaceForm.d.ts +1 -0
- package/dist/types/surfaceForms.d.ts +28 -1
- package/dist/types/tracking/attachTracking.d.ts +1 -0
- package/dist/types/tracking/conversionRules.d.ts +3 -0
- package/dist/types/tracking/vendors.d.ts +3 -0
- package/docs/api.md +59 -43
- package/docs/reference/binding.md +86 -13
- package/docs/reference/clients.md +21 -1
- package/docs/reference/engine.md +14 -7
- package/docs/reference/events.md +4 -1
- package/llms.txt +95 -29
- package/package.json +1 -1
- package/skills/surface-forms-sdk/SKILL.md +66 -26
- package/skills/surface-forms-sdk/debugging.md +21 -19
- package/skills/surface-forms-sdk/patterns.md +71 -5
- package/dist/types/bind/bindForm.test.d.ts +0 -1
- package/dist/types/bind/bookingWidget.test.d.ts +0 -1
- package/dist/types/core/client/emailValidation.test.d.ts +0 -1
- package/dist/types/core/client/responses.test.d.ts +0 -1
- package/dist/types/core/client/runtimeConfig.contract.test.d.ts +0 -1
- package/dist/types/core/client/runtimeConfig.test.d.ts +0 -1
- package/dist/types/core/client/scheduler.contract.test.d.ts +0 -1
- package/dist/types/core/client/scheduler.test.d.ts +0 -1
- package/dist/types/core/client/transport.test.d.ts +0 -1
- package/dist/types/core/engine/answerAudit.test.d.ts +0 -1
- package/dist/types/core/engine/answerStates.contract.test.d.ts +0 -1
- package/dist/types/core/engine/formEngine.test.d.ts +0 -1
- package/dist/types/core/engine/redirect.test.d.ts +0 -1
- package/dist/types/core/engine/responsePayload.contract.test.d.ts +0 -1
- package/dist/types/core/engine/responsePayload.test.d.ts +0 -1
- package/dist/types/core/engine/urlData.contract.test.d.ts +0 -1
- package/dist/types/core/engine/urlData.test.d.ts +0 -1
- package/dist/types/core/engine/urlPrefill.test.d.ts +0 -1
- package/dist/types/core/events/dropoffDetection.test.d.ts +0 -1
- package/dist/types/core/events/emitter.test.d.ts +0 -1
- package/dist/types/core/events/formJourneyTracker.contract.test.d.ts +0 -1
- package/dist/types/core/events/formJourneyTracker.test.d.ts +0 -1
- package/dist/types/core/events/pushEvents.contract.test.d.ts +0 -1
- package/dist/types/core/events/pushEvents.test.d.ts +0 -1
- package/dist/types/core/events/startedDetection.test.d.ts +0 -1
- package/dist/types/core/events/viewDetection.test.d.ts +0 -1
- package/dist/types/core/identity/browserFingerprint.test.d.ts +0 -1
- package/dist/types/core/identity/ensureIdentified.test.d.ts +0 -1
- package/dist/types/core/identity/identify.contract.test.d.ts +0 -1
- package/dist/types/core/identity/identify.test.d.ts +0 -1
- package/dist/types/core/identity/journey.contract.test.d.ts +0 -1
- package/dist/types/core/identity/journey.test.d.ts +0 -1
- package/dist/types/core/identity/journey.testStubs.d.ts +0 -3
- package/dist/types/core/identity/leadDataCache.test.d.ts +0 -1
- package/dist/types/enrichment/attachEnrichment.test.d.ts +0 -1
- package/dist/types/react/ssr.test.d.ts +0 -1
- package/dist/types/react/useSurfaceForm.test.d.ts +0 -1
- package/dist/types/surfaceForms.test.d.ts +0 -1
- package/dist/types/tracking/attachTracking.test.d.ts +0 -1
- package/dist/types/tracking/conversionRules.contract.test.d.ts +0 -1
- package/dist/types/tracking/conversionRules.test.d.ts +0 -1
- package/dist/types/tracking/vendors.test.d.ts +0 -1
- package/dist/types/validation/attachEmailValidation.test.d.ts +0 -1
package/docs/api.md
CHANGED
|
@@ -35,25 +35,25 @@ exact URL and host it tried.
|
|
|
35
35
|
|
|
36
36
|
### `SurfaceFormsInitOptions`
|
|
37
37
|
|
|
38
|
-
| Option | Type | Meaning
|
|
39
|
-
| --------------------- | ---------------------------- |
|
|
40
|
-
| `environmentId` | `string` | Required. From `get_sdk_binding_map`.
|
|
41
|
-
| `formId` | `string` | Required.
|
|
42
|
-
| `apiBaseUrl` | `string?` | The environment's host. **Required unless the environment is on `https://forms.withsurface.com`** — `get_sdk_binding_map` returns it.
|
|
43
|
-
| `container` | `Element?` | Enables the `data-*` binding layer + view detection on this root (§4). Omit for fully headless use.
|
|
44
|
-
| `steps` | `Array<{id, endStepKind?}>?` | Caller-provided step order — **skips the record fetch entirely** (offline mode). Normally omitted.
|
|
45
|
-
| `settings` | object? | Behavioral gates + tracking config (`trackPartialResponses`, `trackCookies`, `gtmId`, `gaMeasurementId`, `metaPixelId`, `hubspotTracking`, `adsConversions`). Caller values win over the record's.
|
|
46
|
-
| `expectedQuestionIds` | `string[]?` | Overrides the record-derived list behind the unbound-question console warning.
|
|
47
|
-
| `resolveNextStep` | `ResolveNextStep?` | Routing hook for `next()`/`submit()` (§7.3); defaults to linear record order.
|
|
48
|
-
| `urlDataStateId` | `string?` | Seeds a `URL_DATA` attribution pseudo-answer under this id (rides every save).
|
|
49
|
-
| `source` | `LeadSource?` | Lead source for identify; defaults `"sdkForm"`.
|
|
50
|
-
| `journey` | `boolean?` | Page-view tracking module (§7.6), default off.
|
|
51
|
-
| `emailValidation` | `boolean?` | Automatic waterfall email validation (§5), default off — verdicts bill the form owner's validator quota.
|
|
52
|
-
| `preview` | `boolean?` | Blocks ALL network writes (identify, responses, events, journey, enrichment, tracking). Scheduler returns synthetic bookings.
|
|
53
|
-
| `previewToken` | `string?` | With `preview: true`, serves a never-published form's DRAFT config. From `create_form` / `get_sdk_binding_map`.
|
|
54
|
-
| `resume` | `{responseId, resumeToken}?` | Continue a stored response (§3). Ignored in preview.
|
|
55
|
-
| `debug` | `boolean?` | Logs lifecycle + audits every answer against the record; warns on states the server would reject and on inert config (declared enrichment source, no provider). Dev only.
|
|
56
|
-
| `meta` | `Record<string, unknown>?` | Seed response meta. `surfaceTagStatus` is always overwritten to `"sdk"`.
|
|
38
|
+
| Option | Type | Meaning |
|
|
39
|
+
| --------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| `environmentId` | `string` | Required. From `get_sdk_binding_map`. |
|
|
41
|
+
| `formId` | `string` | Required. |
|
|
42
|
+
| `apiBaseUrl` | `string?` | The environment's host. **Required unless the environment is on `https://forms.withsurface.com`** — `get_sdk_binding_map` returns it. |
|
|
43
|
+
| `container` | `Element?` | Enables the `data-*` binding layer + view detection on this root (§4). Omit for fully headless use. |
|
|
44
|
+
| `steps` | `Array<{id, endStepKind?}>?` | Caller-provided step order — **skips the record fetch entirely** (offline mode). Normally omitted. |
|
|
45
|
+
| `settings` | object? | Behavioral gates + tracking config (`trackPartialResponses`, `trackCookies`, `gtmId`, `gaMeasurementId`, `metaPixelId`, `hubspotTracking`, `adsConversions`). Caller values win over the record's. |
|
|
46
|
+
| `expectedQuestionIds` | `string[]?` | Overrides the record-derived list behind the unbound-question console warning. |
|
|
47
|
+
| `resolveNextStep` | `ResolveNextStep?` | Routing hook for `next()`/`submit()` (§7.3); defaults to linear record order. |
|
|
48
|
+
| `urlDataStateId` | `string?` | Seeds a `URL_DATA` attribution pseudo-answer under this id (rides every save). |
|
|
49
|
+
| `source` | `LeadSource?` | Lead source for identify; defaults `"sdkForm"`. |
|
|
50
|
+
| `journey` | `boolean?` | Page-view tracking module (§7.6), default off. |
|
|
51
|
+
| `emailValidation` | `boolean?` | Automatic waterfall email validation (§5), default off — verdicts bill the form owner's validator quota. |
|
|
52
|
+
| `preview` | `boolean?` | Blocks ALL network writes (identify, responses, events, journey, enrichment, tracking). Scheduler returns synthetic bookings. |
|
|
53
|
+
| `previewToken` | `string?` | With `preview: true`, serves a never-published form's DRAFT config. From `create_form` / `get_sdk_binding_map`. |
|
|
54
|
+
| `resume` | `{responseId, resumeToken}?` | Continue a stored response (§3). Ignored in preview. |
|
|
55
|
+
| `debug` | `boolean?` | Logs lifecycle + navigation intent (`next requested from step …`, distinguishing an un-fired click from a blocked advance) + audits every answer against the record; warns on states the server would reject and on inert config (declared enrichment source, no provider). Dev only. |
|
|
56
|
+
| `meta` | `Record<string, unknown>?` | Seed response meta. `surfaceTagStatus` is always overwritten to `"sdk"`. |
|
|
57
57
|
|
|
58
58
|
## 2. The form handle — `SurfaceForm`
|
|
59
59
|
|
|
@@ -61,26 +61,28 @@ Everything the engine exposes (§7.3) minus internals, plus init-level helpers.
|
|
|
61
61
|
Full method semantics, event payloads, and delivery guarantees:
|
|
62
62
|
[`reference/engine.md`](reference/engine.md).
|
|
63
63
|
|
|
64
|
-
| Member
|
|
65
|
-
|
|
|
66
|
-
| `setAnswer`
|
|
67
|
-
| `next` / `back`
|
|
68
|
-
| `goToStep`
|
|
69
|
-
| `submit`
|
|
70
|
-
| `disqualify`
|
|
71
|
-
| `savePartial`
|
|
72
|
-
| `flush`
|
|
73
|
-
| `markViewed`
|
|
74
|
-
| `state`
|
|
75
|
-
| `subscribe`
|
|
76
|
-
| `on`
|
|
77
|
-
| `setExternalState`
|
|
78
|
-
| `mergeMeta`
|
|
79
|
-
| `identify`
|
|
80
|
-
| `validateEmail`
|
|
81
|
-
| `capabilities`
|
|
82
|
-
| `runtimeConfig`
|
|
83
|
-
| `
|
|
64
|
+
| Member | Signature | Semantics |
|
|
65
|
+
| ------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
66
|
+
| `setAnswer` | `(questionId, state) => void` | Merges a partial state into the question's answer. Use the typed answer states (§6) — wrong shapes are the classic production 400. |
|
|
67
|
+
| `next` / `back` | `() => Promise<void>` / `() => void` | Linear advance — switches the step immediately and resolves on the step change; the partial save (stamped with the step being LEFT) lands behind it / rewind (never writes). |
|
|
68
|
+
| `goToStep` | `(stepId) => Promise<void>` | Customer-owned routing: forward jumps save like `next()`; jumps to a visited step rewind like `back()`. Rejects unknown ids. |
|
|
69
|
+
| `submit` | `() => Promise<void>` | Finishes the form. **The terminal write is awaited** — when this resolves (and `saved` fires with `finished: true`), the server has the finished response; a delivery failure emits `error` instead. Then reveals the record's `thank_you` step. |
|
|
70
|
+
| `disqualify` | `() => Promise<void>` | Finishes as disqualified (`invalidResponse: true`); reveals the `disqualified` ending. |
|
|
71
|
+
| `savePartial` | `() => Promise<void>` | Explicit mid-form save (fire-and-forget beacon delivery). |
|
|
72
|
+
| `flush` | `() => Promise<void>` | Resolves when every queued write (including post-finish amends) has settled. Await before programmatic teardown. |
|
|
73
|
+
| `markViewed` | `() => void` | Emits `form_viewed` + first-step `form_step_viewed`, once. The bind layer calls it via IntersectionObserver; headless callers call it themselves. |
|
|
74
|
+
| `state` | `() => FormEngineState` | `{ currentStepId, answers, externalStates, responseId, resumeToken, finished }`. Referentially stable between mutations (safe for `useSyncExternalStore`). |
|
|
75
|
+
| `subscribe` | `(cb) => Unsubscribe` | Store subscription (fires on any state change). |
|
|
76
|
+
| `on` | `(event, cb) => Unsubscribe` | Lifecycle events: `viewed`, `started`, `stepChanged {fromStepId,toStepId}`, `stepCompleted {stepId}`, `completed {formId}`, `disqualified {stepId}`, `saved {responseId, finished, resumeToken}`, `error {scope, error}`, `scheduler` (`SchedulerLifecycleEvent`, §7.8). |
|
|
77
|
+
| `setExternalState` | `(id, {type, ...}) => void` | Writes a pseudo-answer that rides every save (enrichment results, custom attribution). Post-finish it amends the stored response. |
|
|
78
|
+
| `mergeMeta` | `(partial) => void` | Records caller-computed values (qualification score, experiment bucket) onto response `meta`; rides the next save or amends post-finish. Reserved SDK keys (`surfaceTagStatus`, email-validation verdicts) are dropped with a warning. |
|
|
79
|
+
| `identify` | `() => Promise<unknown>` | Re-runs visitor identify (SPA route change); later writes attribute to the fresh session. |
|
|
80
|
+
| `validateEmail` | `(email) => Promise<EmailValidationResult>` | On-demand deliverability verdict (§5). |
|
|
81
|
+
| `capabilities` | `{enrichment, emailValidation, tracking}` | What actually runs in THIS environment — a record can declare features the environment doesn't back. |
|
|
82
|
+
| `runtimeConfig` | `RuntimeConfig?` | The fetched record (undefined when `steps` was caller-provided). |
|
|
83
|
+
| `notifyScheduler` | `(event) => void` | Emits on the `scheduler` channel (used by `mountScheduler`; emit-only, never writes). |
|
|
84
|
+
| `transport` / `identity` / `preview` | advanced | The boot transport, the live identity getter, and the preview flag — for modules acting on the form's behalf (`mountScheduler` uses all three). |
|
|
85
|
+
| `destroy` | `() => void` | Tears down every module binding. One engine per page-life — re-`init` without `destroy` creates duplicate responses. |
|
|
84
86
|
|
|
85
87
|
## 3. Resume
|
|
86
88
|
|
|
@@ -184,9 +186,12 @@ Semantics that matter:
|
|
|
184
186
|
|
|
185
187
|
- Partial saves happen on forward movement only, stamped with the step being
|
|
186
188
|
left; the first forward move creates the response, later moves update it.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
189
|
+
They run BEHIND the step change — `next()`/`goToStep()` never wait on the
|
|
190
|
+
network.
|
|
191
|
+
- Every write runs through one queue (the first move's create settles the
|
|
192
|
+
responseId before any update needs it). Finishing is the only navigation
|
|
193
|
+
that waits; a re-entrant `submit()`/`next()`/`disqualify()` while a finish
|
|
194
|
+
is pending joins it, and nothing writes after the finishing submit.
|
|
190
195
|
- The FINISHING write is an awaited `fetch(keepalive)`; partial updates are
|
|
191
196
|
beacons. A failed terminal write emits `error` (with a best-effort beacon
|
|
192
197
|
fallback) and never fires `saved`.
|
|
@@ -268,6 +273,16 @@ true` page-view module (tag-compatible cookies, duplicate-URL skip, SPA
|
|
|
268
273
|
|
|
269
274
|
Full `SchedulerClient` signatures and widget options: [`reference/clients.md`](reference/clients.md) + [`reference/binding.md`](reference/binding.md).
|
|
270
275
|
|
|
276
|
+
- **`mountScheduler(options): () => void`** — the DEFAULT scheduler path: one
|
|
277
|
+
call renders the record's scheduler question for ANY supported provider
|
|
278
|
+
(Surface's widget default-styled, or the third-party embed native forms
|
|
279
|
+
use), emits the `SchedulerLifecycleEvent` union on `form.on("scheduler")` +
|
|
280
|
+
`onEvent`, and auto-persists the booking into the response
|
|
281
|
+
(`persist: false` opts out). Options table, provider matrix, and
|
|
282
|
+
persistence semantics: [`reference/binding.md`](reference/binding.md).
|
|
283
|
+
- **`schedulerQuestionsOf(config): SchedulerQuestion[]`** — the record's
|
|
284
|
+
scheduler questions with provider + embed content
|
|
285
|
+
([`reference/clients.md`](reference/clients.md)).
|
|
271
286
|
- **`createSchedulerClient({ transport, preview? }): SchedulerClient`** —
|
|
272
287
|
public Surface-scheduler endpoints: `getPublicEventType`,
|
|
273
288
|
`getAvailabilitySummary`, `getMonthAvailability` (`YYYY-MM`), `getSlots`,
|
|
@@ -303,7 +318,8 @@ dependency; the main entry is React-free.
|
|
|
303
318
|
throwing; event-handler exceptions are isolated.
|
|
304
319
|
- **Preview writes nothing**: identify, responses, events, journey, enrichment,
|
|
305
320
|
tracking, and email validation (synthetic verdicts) are all blocked.
|
|
306
|
-
- **Delivery**:
|
|
321
|
+
- **Delivery**: the finishing update is awaited; the create and mid-form
|
|
322
|
+
updates run behind the step change (navigation never waits on the network)
|
|
307
323
|
and analytics are beacon-first fire-and-forget. `flush()` before
|
|
308
324
|
programmatic teardown.
|
|
309
325
|
- Send `X-Surface-Sdk-Version` on everything — visible server-side for rate
|
|
@@ -63,24 +63,97 @@ flash.)
|
|
|
63
63
|
- `expectedQuestionIds` (init derives them from the record) minus the ids actually bound →
|
|
64
64
|
a console warning naming the unbound ids; the runtime counterpart of `validate_form_html`.
|
|
65
65
|
|
|
66
|
+
## `mountScheduler(options): () => void`
|
|
67
|
+
|
|
68
|
+
The DEFAULT scheduler path: renders the record's scheduler question for any supported provider,
|
|
69
|
+
emits the lifecycle on the form's `scheduler` event channel, and persists the booking into the
|
|
70
|
+
response automatically. Reach for `mountBookingWidget` (below) only when building a custom UI.
|
|
71
|
+
|
|
72
|
+
### Options (`MountSchedulerOptions`)
|
|
73
|
+
|
|
74
|
+
| Option | Type | Semantics |
|
|
75
|
+
| ------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
76
|
+
| `form` | `SurfaceForm` | The `init` handle — config, transport, identity, and persistence all come from it. |
|
|
77
|
+
| `container` | `Element` | The embed mounts here; teardown removes it. |
|
|
78
|
+
| `questionId` | `string?` | Required only when the record has >1 scheduler question (the error names the candidates). |
|
|
79
|
+
| `stepId` | `string?` | Alternative selector: the step carrying the scheduler question. |
|
|
80
|
+
| `styled` | `boolean?` (default `true`) | Injects a stylesheet scoped under `.surface-scheduler` on THIS container (Surface widget look + frame sizing). Override via `--surface-scheduler-*` custom props or the same class names; `false` keeps everything bare. |
|
|
81
|
+
| `persist` | `boolean?` (default `true`) | Auto-persist lifecycle + booking into the response. `false` still emits events but never writes (and never creates a response). |
|
|
82
|
+
| `prefill` | `{name?, lastName?, email?}?` | Overrides the prefill resolved from the record's linked answer refs (`linkNameToStep` etc.). |
|
|
83
|
+
| `formatters` | `{month?, date?, time?}?` | Surface provider only — forwarded to the booking widget. |
|
|
84
|
+
| `calendarId` | `string?` | DynamicScheduler only: pick a routed calendar by name. Without it: the default calendar, or a lone `calendars[0]`; ambiguous routing throws (conditions are your page's own routing). |
|
|
85
|
+
| `onEvent` | `(SchedulerLifecycleEvent) => void?` | Same events as `form.on("scheduler", …)`. |
|
|
86
|
+
| `onComplete` | `(SchedulerBookingSummary) => void?` | Fires alongside `booking_confirmed`. NOT needed for persistence — that's automatic. |
|
|
87
|
+
|
|
88
|
+
### Lifecycle events (`SchedulerLifecycleEvent`)
|
|
89
|
+
|
|
90
|
+
Every event carries `{questionId, stepId, provider}` plus:
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
| { type: "viewed" } // once per mount
|
|
94
|
+
| { type: "date_selected"; date? /* "YYYY-MM-DD" */ }
|
|
95
|
+
| { type: "slot_selected"; slot?: { start; end? } }
|
|
96
|
+
| { type: "form_opened" }
|
|
97
|
+
| { type: "booking_confirmed"; booking: SchedulerBookingSummary }
|
|
98
|
+
| { type: "booking_cancelled"; reason? }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`SchedulerBookingSummary` = `{provider, uri, meetingTime?, meetingEndTime?, inviteeName?,
|
|
102
|
+
guestEmails?, ownerEmails?, joinLink?, raw?}` — `uri` is the provider-native booking id
|
|
103
|
+
(HubSpot's embed reports none; its uri is the single space native forms persist), `raw` the
|
|
104
|
+
untouched provider payload.
|
|
105
|
+
|
|
106
|
+
### Providers
|
|
107
|
+
|
|
108
|
+
| Provider (record `calendarType`) | Embed | Booking signal | Extra lifecycle |
|
|
109
|
+
| -------------------------------- | -------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
|
|
110
|
+
| `Surface` | SDK booking widget (styled by default) | widget booking | `date_selected`, `slot_selected`, `form_opened`; sub-steps persist as `schedulerSubStep` |
|
|
111
|
+
| `Calendly` | iframe | postMessage + server-poll fallback | provider-reported `viewed`, `slot_selected`; poll-reported cancellation |
|
|
112
|
+
| `Cal` (Cal.com) | vendor script | `bookingSuccessful` CustomEvent | — |
|
|
113
|
+
| `SavvyCal` | vendor script | `savvycal.scheduled` CustomEvent | — |
|
|
114
|
+
| `ChiliCal` (Chili Piper) | iframe | `booking-confirmed` postMessage | — |
|
|
115
|
+
| `Hubspot` | vendor script | `meetingBookSucceeded` postMessage | — |
|
|
116
|
+
| `RevenueHero` | vendor script + router submit | `MEETING_BOOKED` postMessage | — |
|
|
117
|
+
| `Zoom` | iframe | `bookingForm` postMessage | server-poll backfills host email / times / join link into the answer |
|
|
118
|
+
| `ReclaimAI` | iframe | server poll ONLY (no client events) | poll carries the booking |
|
|
119
|
+
| `Clari` | iframe | **none** — embed-only; nothing is tracked or persisted | — |
|
|
120
|
+
|
|
121
|
+
postMessage listeners are origin-checked strictly (exact origin or anchored host suffix — the
|
|
122
|
+
SDK mirrors the vendor protocols, not the laxness some native checks have).
|
|
123
|
+
|
|
124
|
+
### Persistence semantics
|
|
125
|
+
|
|
126
|
+
- Confirmed booking → the frozen scheduler answer keys (`eventScheduled`, `uri`,
|
|
127
|
+
`meetingTime`…, `type` = the record's component type) merged via `setAnswer`, then a partial
|
|
128
|
+
save; after `submit()` the engine amends the stored response instead. `meta.bookingId` (Surface
|
|
129
|
+
bookings only) and the `meeting_booked` ad-conversion trigger follow automatically.
|
|
130
|
+
- Poll-reported cancellation (after a confirmed booking) → `eventCancelled: true` + save.
|
|
131
|
+
- Calendly/Zoom/Reclaim embeds carry the `responseId` in their URL, so mounting them creates the
|
|
132
|
+
response with a partial save when none exists yet. The Surface widget resolves identity lazily
|
|
133
|
+
at book time instead — lookers who never book create nothing.
|
|
134
|
+
- `preview: true` on the form blocks every write and poll; the Surface widget books through the
|
|
135
|
+
fabricated preview client, so the whole flow demos offline.
|
|
136
|
+
|
|
66
137
|
## `mountBookingWidget(options): () => void`
|
|
67
138
|
|
|
68
|
-
The unstyled Surface-scheduler booking flow (date → time →
|
|
69
|
-
`SchedulerClient` ([`clients.md`](clients.md)). Never iframe
|
|
139
|
+
The custom-UI escape hatch: the unstyled Surface-scheduler booking flow (date → time →
|
|
140
|
+
name/email form → booked) over a `SchedulerClient` ([`clients.md`](clients.md)). Never iframe
|
|
141
|
+
the hosted booking page. For the zero-effort path use `mountScheduler` (above).
|
|
70
142
|
|
|
71
143
|
### Options (`BookingWidgetOptions`)
|
|
72
144
|
|
|
73
|
-
| Option | Type
|
|
74
|
-
| ------------- |
|
|
75
|
-
| `container` | `Element`
|
|
76
|
-
| `scheduler` | `SchedulerClient`
|
|
77
|
-
| `eventTypeId` | `string`
|
|
78
|
-
| `timezone` | `string?`
|
|
79
|
-
| `duration` | `number?`
|
|
80
|
-
| `prefill` | `{name?, email?}?`
|
|
81
|
-
| `identity` | `
|
|
82
|
-
| `formatters` | `{month?, date?, time?}?`
|
|
83
|
-
| `
|
|
145
|
+
| Option | Type | Semantics |
|
|
146
|
+
| ------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
147
|
+
| `container` | `Element` | The widget appends its own `div.surface-booking` root here; teardown removes it. |
|
|
148
|
+
| `scheduler` | `SchedulerClient` | From `createSchedulerClient` (share the transport; pass `preview` there for synthetic bookings). |
|
|
149
|
+
| `eventTypeId` | `string` | From the record's `CalendlyScreen` content. |
|
|
150
|
+
| `timezone` | `string?` | Defaults to the visitor's (`Intl.DateTimeFormat().resolvedOptions().timeZone`); slot labels format in it. |
|
|
151
|
+
| `duration` | `number?` | Threaded into availability/slots/book queries. |
|
|
152
|
+
| `prefill` | `{name?, email?}?` | Pre-fills the widget's name/email inputs. |
|
|
153
|
+
| `identity` | `BookingIdentity \| (() => BookingIdentity \| Promise<BookingIdentity>)?` | Threaded into `book()` for booking↔response linkage. The getter form resolves at book time — a response created after mount still links. |
|
|
154
|
+
| `formatters` | `{month?, date?, time?}?` | Override the copy: `month("YYYY-MM")`, `date("YYYY-MM-DD")`, `time(slot)` (ISO instants; format in the widget timezone). Defaults are locale-formatted via `Intl`. |
|
|
155
|
+
| `onEvent` | `(BookingWidgetEvent) => void?` | Sub-step transitions: `viewed`, `date_selected {date}`, `slot_selected {slot}`, `form_opened` (fresh opens only, not error re-renders), `booking_confirmed {booking}` (alongside `onComplete`). |
|
|
156
|
+
| `onComplete` | `(booking: Booking) => void` | Fires after a successful booking — persist via `setAnswer({...toSchedulerAnswerState(booking), type: "<record type>"})` and navigate. |
|
|
84
157
|
|
|
85
158
|
### Behavior + DOM
|
|
86
159
|
|
|
@@ -158,7 +158,27 @@ server links booking↔response with them; pass what you have.
|
|
|
158
158
|
override `type` to the record's component type when persisting
|
|
159
159
|
(`{...toSchedulerAnswerState(b), type: "CalendlyScreen"}`).
|
|
160
160
|
|
|
161
|
-
The DOM widget over this client (`mountBookingWidget`)
|
|
161
|
+
The DOM widget over this client (`mountBookingWidget`) and the zero-effort embedded path
|
|
162
|
+
(`mountScheduler`) are in [`binding.md`](binding.md).
|
|
163
|
+
|
|
164
|
+
- **`schedulerQuestionsOf(config): SchedulerQuestion[]`** (`core/client/runtimeConfig`) — the
|
|
165
|
+
record's scheduler questions with everything `mountScheduler` reads:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
type SchedulerQuestion = {
|
|
169
|
+
questionId;
|
|
170
|
+
stepId;
|
|
171
|
+
componentType: "CalendlyScreen" | "DynamicScheduler"; // also the persisted answer `type`
|
|
172
|
+
calendarType: string; // provider; the record omits it for the original default, Calendly
|
|
173
|
+
content: {
|
|
174
|
+
dataUrl?; eventTypeId?; savvyCalLinkId?; revenueHeroRouterId?;
|
|
175
|
+
dataUrlParams?: Record<string, { stepId; questionId; variable }>; // answer-state refs → embed params
|
|
176
|
+
linkNameToStep? / linkLastNameToStep? / linkEmailToStep?: { stepId; questionId }; // prefill refs
|
|
177
|
+
addSchedulerQueryParams?; skipFormIfPrefilled?;
|
|
178
|
+
calendars?; defaultCalendar?; // DynamicScheduler routing
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
```
|
|
162
182
|
|
|
163
183
|
## Email validation (`core/client/emailValidation` + `validation/attachEmailValidation`)
|
|
164
184
|
|
package/docs/reference/engine.md
CHANGED
|
@@ -30,7 +30,10 @@ creates one for you; construct directly for offline/custom stacks.
|
|
|
30
30
|
|
|
31
31
|
- **`setAnswer(questionId, state)`** — merges (`{...prev, ...state}`) into the current step's
|
|
32
32
|
answer for that question. Fires `started` (and the `form_started` event) exactly once, on the
|
|
33
|
-
first deep divergence from the initial answers. Notifies subscribers.
|
|
33
|
+
first deep divergence from the initial answers. Notifies subscribers. After the finishing
|
|
34
|
+
write it amends the stored response (like `setExternalState`/`mergeMeta`) — a booking
|
|
35
|
+
confirmed on a thank-you-step scheduler still persists; before that, it never writes on its
|
|
36
|
+
own (writes ride navigation/`savePartial`).
|
|
34
37
|
- **`next(): Promise<void>`** — resolves the next step. Not finished → emits `stepCompleted`,
|
|
35
38
|
pushes the `form_step_submitted`+`form_step_viewed` batch (`stepCompletionTime` = seconds since
|
|
36
39
|
engine creation), records the journey transition, writes a partial (if enabled) stamped with the
|
|
@@ -78,15 +81,19 @@ resumeToken, finished}`. The snapshot is cached until the next mutation, so it i
|
|
|
78
81
|
| `disqualified` | `{stepId}` | Disqualified finish. |
|
|
79
82
|
| `saved` | `{responseId, finished, resumeToken}` | After a CONFIRMED write: every create, every partial dispatch, the awaited terminal update. Never fires for a failed terminal write. |
|
|
80
83
|
| `error` | `{scope: "submit", error}` | Failed create (typed `TransportError`) or failed terminal update. |
|
|
84
|
+
| `scheduler` | `SchedulerLifecycleEvent` | Scheduler lifecycle, emitted via `notifyScheduler` (the `mountScheduler` path — union in [`binding.md`](binding.md)). Emit-only. |
|
|
81
85
|
|
|
82
86
|
### Write serialization + delivery
|
|
83
87
|
|
|
84
|
-
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
88
|
+
- Navigation is synchronous: `next()`/`goToStep()` switch the step and resolve immediately; the
|
|
89
|
+
partial save is queued behind the transition. Finishing is the only navigation that waits, and
|
|
90
|
+
a re-entrant `submit()`/`next()`/`disqualify()` while a finish is pending joins it (a
|
|
91
|
+
double-clicked submit finishes once; submit intent never dropped).
|
|
92
|
+
- One queue serializes every write; queued writes re-check `finished` when they run, so nothing
|
|
93
|
+
writes after the finishing submit.
|
|
94
|
+
- First forward write CREATES the response (`POST /api/v1/responses/`, credentialed, settled
|
|
95
|
+
before any later write runs); later writes UPDATE it. Every write resends the FULL answer set +
|
|
96
|
+
external states.
|
|
90
97
|
- A failed create: `error` event; `response_submission_error` pushEvent only for network-kind
|
|
91
98
|
failures (the server logs HTTP-level ones itself); the responseId stays null (the next write
|
|
92
99
|
retries the create).
|
package/docs/reference/events.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Exhaustive reference for the `core/events` domain. Map of everything: [`../api.md`](../api.md).
|
|
4
4
|
(The engine's own lifecycle events — `viewed`/`started`/`stepChanged`/… — are in
|
|
5
|
-
[`engine.md`](engine.md);
|
|
5
|
+
[`engine.md`](engine.md); the `scheduler` channel's `SchedulerLifecycleEvent` union is in
|
|
6
|
+
[`binding.md`](binding.md) with `mountScheduler`. This file covers the modules beneath them.
|
|
7
|
+
Scheduler lifecycle events are client-side only — the push-event vocabulary below is a closed
|
|
8
|
+
server contract and gains no scheduler types.)
|
|
6
9
|
|
|
7
10
|
## Push events (`core/events/pushEvents`)
|
|
8
11
|
|
package/llms.txt
CHANGED
|
@@ -45,9 +45,11 @@ Options beyond the four above: `preview: true` (no network writes at all — ide
|
|
|
45
45
|
responses, events, journey all blocked; pass `previewToken` from the `create_form`
|
|
46
46
|
or `get_sdk_binding_map` reply alongside it and a never-published form serves its
|
|
47
47
|
DRAFT config, so you can preview before the first publish), `debug: true`
|
|
48
|
-
(logs every captured answer + lifecycle event
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
(logs every captured answer + lifecycle event + navigation intent — a
|
|
49
|
+
`next requested from step …` line per `next()`/`submit()`/… call, so a click
|
|
50
|
+
that never fired is distinguishable from a blocked advance — and warns on
|
|
51
|
+
states the server would reject AND on inert configuration like a declared
|
|
52
|
+
enrichment source with no enabled provider; development only), `journey: true` (page-view tracking),
|
|
51
53
|
`urlDataStateId` (attribution pseudo-answer), `emailValidation: true`
|
|
52
54
|
(automatic verdicts, see below), `resume`, `meta`, `source`.
|
|
53
55
|
|
|
@@ -95,7 +97,12 @@ Then:
|
|
|
95
97
|
### Choice questions bind as a GROUP
|
|
96
98
|
|
|
97
99
|
Five types are group-built — `MultipleOptionsQuestion`, `MultipleChoiceQuestion`,
|
|
98
|
-
`MMChoiceQuestion`, `GraphicOptions`, and `MultipleChoiceSingleAnswer`.
|
|
100
|
+
`MMChoiceQuestion`, `GraphicOptions`, and `MultipleChoiceSingleAnswer`. All five
|
|
101
|
+
bind when present in the record, but only the first three are creatable via
|
|
102
|
+
`create_form` (`list_component_types` is the authority) —
|
|
103
|
+
`MultipleChoiceQuestion` and `MultipleChoiceSingleAnswer` are legacy types you
|
|
104
|
+
will only meet on pre-existing forms; build new records with
|
|
105
|
+
`MultipleOptionsQuestion`. Their
|
|
99
106
|
answers are structured (the record's whole option list / a selection object,
|
|
100
107
|
and their shapes differ — only `MultipleOptionsQuestion` carries an option
|
|
101
108
|
`index`; trust each question's `answerShape` from the binding map, never a
|
|
@@ -129,9 +136,18 @@ Markers, most specific wins, ONE per element:
|
|
|
129
136
|
|
|
130
137
|
A `<form>` container's native submit also finishes. Conditional branching =
|
|
131
138
|
your JS choosing which goto button to show, or calling the API below. Driving
|
|
132
|
-
navigation/submit entirely from JS (
|
|
133
|
-
Put `data-surface-nav="js"` on any element — it tells
|
|
134
|
-
the page submits in code, silencing its advance/submit
|
|
139
|
+
navigation/submit entirely from JS (custom branching, an owner-requested
|
|
140
|
+
gate)? Put `data-surface-nav="js"` on any element — it tells
|
|
141
|
+
`validate_form_html` the page submits in code, silencing its advance/submit
|
|
142
|
+
warnings.
|
|
143
|
+
|
|
144
|
+
Timing: `next()`/`goToStep()` switch the step IMMEDIATELY and resolve on the
|
|
145
|
+
step change — the partial save runs behind it (`saved`/`error` report it). A
|
|
146
|
+
visitor never waits on a partial save, so don't disable the CTA for one, and
|
|
147
|
+
don't put async work of your own in front of the step change unless the form
|
|
148
|
+
owner asked for a gate (see Email validation). `submit()`/`disqualify()` are
|
|
149
|
+
the one wait: they await the finishing write before the ending step shows —
|
|
150
|
+
disable that button while the promise is pending.
|
|
135
151
|
|
|
136
152
|
Finishing reveals the record's matching ending step: after the terminal write,
|
|
137
153
|
`submit()` moves to the step with `endStepKind: "thank_you"` and `disqualify()`
|
|
@@ -152,12 +168,19 @@ SPA route change), `validateEmail(email)`, `mergeMeta(partial)`,
|
|
|
152
168
|
`disqualified`, `saved` (carries `responseId` + `resumeToken`), `error`. Every
|
|
153
169
|
`on`/`subscribe` call returns an unsubscribe function.
|
|
154
170
|
|
|
171
|
+
`state().answers` is keyed by stepId, then questionId
|
|
172
|
+
(`Record<stepId, Record<questionId, state>>`) — reading an EARLIER step's
|
|
173
|
+
answer (booking prefill, a summary screen) indexes by that step's id:
|
|
174
|
+
`state().answers["step_about"]?.["q_name"]`, not by the current step.
|
|
175
|
+
|
|
155
176
|
Delivery guarantees: the FINISHING write is awaited — when `submit()` resolves
|
|
156
177
|
(and `saved` fires with `finished: true`) the server has the finished response;
|
|
157
|
-
a failure emits `error` instead of lying.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
a failure emits `error` instead of lying. Partial saves never block: `next()`
|
|
179
|
+
resolves on the step change and the write lands behind it — the first one
|
|
180
|
+
creates the response and settles `responseId` (later writes queue behind it),
|
|
181
|
+
the rest are fire-and-forget beacons reconciled server-side. `await
|
|
182
|
+
form.flush()` before tearing the page down programmatically (SPA unmount, test
|
|
183
|
+
harness closing the browser) so no queued write is cut off.
|
|
161
184
|
|
|
162
185
|
`mergeMeta(partial)` records caller-computed values (a qualification score, an
|
|
163
186
|
experiment bucket) onto the response `meta` — they ride the next save, or amend
|
|
@@ -198,28 +221,38 @@ wire shapes; during development, `debug: true` audits them at runtime too.
|
|
|
198
221
|
|
|
199
222
|
## Email validation
|
|
200
223
|
|
|
224
|
+
Two mechanisms share one per-address verdict cache (an address is billed once
|
|
225
|
+
no matter which path asks). Automatic mode — `emailValidation: true` on
|
|
226
|
+
`init` — validates email answers as they're captured (debounced) and writes
|
|
227
|
+
the same `meta` verdict keys native forms write (`isEmailValid`,
|
|
228
|
+
`emailValidation`, `emailValidationFailed`) for dashboards and workflows.
|
|
201
229
|
`form.validateEmail(email)` resolves the server-side deliverability verdict
|
|
202
230
|
(provider waterfall + the environment's spam blacklist; keys stay
|
|
203
|
-
server-side): `{ isValid, status, reason, failed? }`.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
231
|
+
server-side) on demand: `{ isValid, status, reason, failed? }`.
|
|
232
|
+
|
|
233
|
+
**What happens on an invalid address is the form owner's decision — ask
|
|
234
|
+
before authoring, never assume.** The default is ANNOTATE ONLY: pass
|
|
235
|
+
`emailValidation: true`, let the visitor continue, and the response carries
|
|
236
|
+
`isEmailValid: false` for the owner's workflows and lead scoring to act on.
|
|
237
|
+
Automatic mode never blocks navigation or submission, and the visitor never
|
|
238
|
+
waits on a verdict. Only when the owner explicitly wants a gate do you call
|
|
239
|
+
`validateEmail` in your own navigation:
|
|
211
240
|
|
|
212
241
|
```js
|
|
242
|
+
// Owner chose BLOCK: keep the visitor on the step with a message.
|
|
213
243
|
const { isValid, reason, failed } = await form.validateEmail(emailInput.value);
|
|
214
244
|
if (isValid) form.next();
|
|
215
245
|
else showError(`Email rejected: ${reason}`);
|
|
246
|
+
// Owner chose DISQUALIFY: form.disqualify() instead of showError.
|
|
216
247
|
```
|
|
217
248
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
249
|
+
Verdicts FAIL OPEN: when no validator can run, the result is
|
|
250
|
+
`{ isValid: true, failed: true, status: "unknown" }` — never lock a visitor
|
|
251
|
+
out on a vendor outage. Two consequences to design for: (1) check `failed` if
|
|
252
|
+
you want to soft-warn instead of hard-block; (2) the same address can be
|
|
253
|
+
accepted today (fail-open) and return a definitive `invalid` tomorrow — that's
|
|
254
|
+
the waterfall resolving, not a bug; don't write tests that assume a fixed
|
|
255
|
+
verdict for a fresh domain. Preview resolves synthetic valid verdicts with no
|
|
223
256
|
network. Offline callers wire it themselves: `createEmailValidator({
|
|
224
257
|
transport, environmentId })` and optionally `attachEmailValidation({ engine,
|
|
225
258
|
validator })`.
|
|
@@ -241,11 +274,41 @@ callers (caller-provided `steps`) wire it themselves: `attachEnrichment({
|
|
|
241
274
|
engine, transport, formId, sources })` with sources from
|
|
242
275
|
`enrichmentSourcesOf(config)`.
|
|
243
276
|
|
|
244
|
-
## Scheduler booking
|
|
277
|
+
## Scheduler booking
|
|
278
|
+
|
|
279
|
+
**Default path — one call, any provider.** `mountScheduler` reads the record's
|
|
280
|
+
scheduler question (provider, event URL/id, prefill links), renders a working
|
|
281
|
+
scheduler, tracks the lifecycle, and persists the booking into the response.
|
|
282
|
+
No `onComplete` wiring, no answer writing, no HTML binding:
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
import { mountScheduler } from "@parag.more.withsurface.com/forms-sdk";
|
|
286
|
+
|
|
287
|
+
const teardown = mountScheduler({
|
|
288
|
+
form, // the SurfaceForms.init handle
|
|
289
|
+
container: document.querySelector("#booking"),
|
|
290
|
+
// questionId: "q_booking", // only when the record has >1 scheduler question
|
|
291
|
+
onEvent: (e) => console.log(e.type), // optional; also on form.on("scheduler", …)
|
|
292
|
+
});
|
|
293
|
+
```
|
|
245
294
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
295
|
+
Works for every provider native forms support — Surface's own scheduler
|
|
296
|
+
(rendered through the SDK widget, default-styled; `styled: false` keeps it
|
|
297
|
+
bare), Calendly, Cal.com, SavvyCal, Chili Piper, HubSpot, RevenueHero, Zoom,
|
|
298
|
+
Reclaim.ai, Clari — embedding each third-party widget and listening to its
|
|
299
|
+
booking events exactly like native forms do. Lifecycle events
|
|
300
|
+
(`viewed` / `date_selected` / `slot_selected` / `form_opened` /
|
|
301
|
+
`booking_confirmed` / `booking_cancelled`) emit on `form.on("scheduler")` and
|
|
302
|
+
the `onEvent` option; the confirmed booking is written to the response
|
|
303
|
+
automatically (`persist: false` opts out). Caveats: Clari exposes no booking
|
|
304
|
+
signal (embed-only — the visitor advances manually); Calendly/Zoom/Reclaim
|
|
305
|
+
create the response at mount when none exists yet (their embeds carry the
|
|
306
|
+
`responseId` for booking↔response linkage). Full options + provider matrix:
|
|
307
|
+
`docs/reference/binding.md`.
|
|
308
|
+
|
|
309
|
+
**Custom-UI path (advanced).** When you're building your own booking UI for a
|
|
310
|
+
Surface scheduler, mount the SDK's unstyled widget directly — do NOT iframe
|
|
311
|
+
the hosted booking page, and do NOT bind the question in HTML (the
|
|
249
312
|
`onComplete` below writes the answer):
|
|
250
313
|
|
|
251
314
|
```js
|
|
@@ -269,7 +332,10 @@ The widget walks date → time → name/email form → booked. Labels are
|
|
|
269
332
|
human-readable by default (`Intl` in the visitor's locale; every month/date/slot
|
|
270
333
|
element also carries its raw value in `data-value` — `YYYY-MM`, `YYYY-MM-DD`,
|
|
271
334
|
ISO instant); pass `formatters: { month?, date?, time? }` to override the copy.
|
|
272
|
-
|
|
335
|
+
The widget itself injects no CSS — style it from your page. (`mountScheduler`'s
|
|
336
|
+
default styling is a stylesheet scoped under `.surface-scheduler` on ITS
|
|
337
|
+
container only; bare `mountBookingWidget` mounts are never restyled.) The
|
|
338
|
+
rendered tree:
|
|
273
339
|
|
|
274
340
|
```
|
|
275
341
|
div.surface-booking
|
package/package.json
CHANGED