@captello/ulc-webview-sdk 0.4.0 → 0.6.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 CHANGED
@@ -105,14 +105,83 @@ try {
105
105
 
106
106
  ## React — `@captello/ulc-webview-sdk/react`
107
107
 
108
- The React adapter is the smoothest way to integrate. `useCaptelloWebview` owns a
109
- `CaptelloWebview` for the iframe's lifetime: it builds the embed URL, creates the client
110
- when the iframe mounts, wires outbound messages to typed callbacks, tracks readiness,
111
- and destroys the client on unmount. You get back `iframeProps` to spread, an `isReady`
112
- flag, and stable senders so a typical form is just the hook plus an `<iframe>`.
108
+ The React adapter is the smoothest way to integrate, in two flavors:
109
+
110
+ - **`<CaptelloForm>`** a turnkey component. Drop it in with an `embedUrl` and message
111
+ callbacks; it renders the `<iframe>`, shows your loading / error overlays, and forwards
112
+ the senders on a `ref`. The shortest path.
113
+ - **`useCaptelloWebview`** — the underlying hook, for when you'd rather own the markup.
114
+
115
+ Both own one `CaptelloWebview` for the iframe's lifetime: they build the embed URL, create
116
+ the client when the iframe mounts, wire outbound messages to typed callbacks, track
117
+ readiness, and destroy the client on unmount.
113
118
 
114
119
  `react` is an optional peer dependency (React 18+).
115
120
 
121
+ ### `<CaptelloForm>` — the turnkey component
122
+
123
+ ```tsx
124
+ import { useRef } from "react";
125
+ import { FormMode, LauncherType, ActionButtonPosition, type SubmissionBody } from "@captello/ulc-webview-sdk";
126
+ import { CaptelloForm, type CaptelloFormHandle } from "@captello/ulc-webview-sdk/react";
127
+
128
+ function UlcForm({
129
+ eventWebAccessToken,
130
+ onSubmitted,
131
+ }: {
132
+ eventWebAccessToken: string;
133
+ onSubmitted: (body: SubmissionBody) => void;
134
+ }) {
135
+ const form = useRef<CaptelloFormHandle>(null);
136
+
137
+ return (
138
+ <CaptelloForm
139
+ ref={form}
140
+ style={{ height: 600 }} // an iframe has no intrinsic height — size the form here
141
+ embedUrl={{
142
+ baseUrl: "https://capture.captello.com",
143
+ eventWebAccessToken,
144
+ actionButtonPosition: ActionButtonPosition.Hidden, // b=2
145
+ mode: FormMode.Submit,
146
+ launcher: LauncherType.EventGenWeb,
147
+ }}
148
+ onSubmissionBody={(m) => onSubmitted(m.data)}
149
+ loading={<Spinner />}
150
+ error={(message) => <ErrorBanner>{message}</ErrorBanner>}
151
+ >
152
+ {({ isReady }) => (
153
+ <button disabled={!isReady} onClick={() => form.current?.submit()}>
154
+ Submit
155
+ </button>
156
+ )}
157
+ </CaptelloForm>
158
+ );
159
+ }
160
+ ```
161
+
162
+ Props are the hook options (`embedUrl` or `targetOrigin`, every message callback, and the
163
+ client options) plus a few rendering conveniences:
164
+
165
+ - **`className` / `style` / `id`** — applied to the wrapper element. Size the form here; the
166
+ iframe fills it.
167
+ - **`iframeProps`** — attributes spread onto the `<iframe>` (`title`, `allow`, `sandbox`,
168
+ `name`, …). Defaults: `title="Captello form"`, `allow="camera; microphone; geolocation"`.
169
+ When you drive the URL yourself (no `embedUrl`), set `src` here.
170
+ - **`loading`** — a node shown, centered over the iframe, until `form_load_complete`. The
171
+ iframe stays mounted underneath so it keeps loading.
172
+ - **`error`** — a node (or `(message) => node`) shown when the form reports
173
+ `form_error_message`; the function form receives the translated, display-ready text.
174
+ - **`children`** — inline controls rendered after the form. A function receives the live api
175
+ (status + senders), so a submit button needs no separate `ref`.
176
+ - **`ref`** — a `CaptelloFormHandle`: the senders (`submit`, `reset`, `updateDraft`,
177
+ `triggerValidation`, `prefill`, `submitAndWait`) plus `status` / `isReady`, `getIframe()`,
178
+ and `getClient()`. Use it to drive the form from a parent without lifting state.
179
+
180
+ ### `useCaptelloWebview` — the hook
181
+
182
+ Prefer to own the markup? The hook returns `iframeProps` to spread, an `isReady` flag, and
183
+ stable senders — so a typical form is just the hook plus an `<iframe>`.
184
+
116
185
  ```tsx
117
186
  import { FormMode, LauncherType, ActionButtonPosition, type SubmissionBody } from "@captello/ulc-webview-sdk";
118
187
  import { useCaptelloWebview } from "@captello/ulc-webview-sdk/react";
@@ -124,7 +193,7 @@ function UlcForm({
124
193
  eventWebAccessToken: string;
125
194
  onSubmitted: (body: SubmissionBody) => void;
126
195
  }) {
127
- const { iframeProps, isReady, submit, prefillInfo } = useCaptelloWebview({
196
+ const { iframeProps, isReady, submit, prefill } = useCaptelloWebview({
128
197
  embedUrl: {
129
198
  baseUrl: "https://capture.captello.com",
130
199
  eventWebAccessToken,
@@ -146,7 +215,7 @@ function UlcForm({
146
215
  }
147
216
  ```
148
217
 
149
- What the hook handles for you:
218
+ What the hook (and the component built on it) handle for you:
150
219
 
151
220
  - **URL + origin.** Pass `embedUrl: { baseUrl, ...EmbedUrlOptions }` and the hook builds
152
221
  the URL, derives `targetOrigin`, and returns it as `iframeProps.src` — no separate
@@ -155,14 +224,14 @@ What the hook handles for you:
155
224
  - **Readiness.** `isReady` and `status` (`"loading" | "ready" | "error"`) — no manual
156
225
  `useState` + `onFormLoadComplete` for a spinner.
157
226
  - **Prefill timing.** Sends made before the form loads are queued and flushed on
158
- `form_load_complete`, so you can `prefillInfo(...)` as soon as you have data — no
227
+ `form_load_complete`, so you can `prefill(...)` as soon as you have data — no
159
228
  gating on readiness, and no silently-dropped messages.
160
229
  - **No memoization.** Callbacks are read fresh via a ref, so inline arrow functions
161
230
  won't re-subscribe or re-create the client. The client is recreated only when
162
231
  `targetOrigin` (or `embedUrl`) / `hostWindow` / `matchSource` / `queueUntilReady` change.
163
- - **Stable senders** (`submit`, `reset`, `updateDraft`, `triggerValidation`,
164
- `prefillInfo`, `prefillSubmission`, `prefillSubmissionAndInfo`, `submitAndWait`) safe
165
- in deps or passed to children. `getClient()` returns the live client for escape hatches.
232
+ - **Stable senders** (`submit`, `reset`, `updateDraft`, `triggerValidation`, `prefill`,
233
+ `submitAndWait`) safe in deps or passed to children. `getClient()` returns the live
234
+ client for escape hatches.
166
235
 
167
236
  ### Callback props
168
237
 
@@ -200,31 +269,33 @@ point of view.
200
269
 
201
270
  ### Inbound — host → webview (you send)
202
271
 
203
- | `type` | Method | Notes |
204
- | -------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------- |
205
- | `submit_form` | `webview.submit()` | Submit as if the user pressed the button. |
206
- | `reset_form` | `webview.reset()` | Clear all entered values. |
207
- | `update_draft` | `webview.updateDraft()` | Switch to draft-update mode. |
208
- | `trigger_validation` | `webview.triggerValidation(target)` | `target`: `"email" \| "invitation_code" \| "all"`. |
209
- | `form_prefill` | `webview.prefillSubmission(...)` / `prefillInfo(...)` / `prefillSubmissionAndInfo(...)` | Three payload shapes (see below). |
272
+ | `type` | Method | Notes |
273
+ | -------------------- | ----------------------------------------- | -------------------------------------------------- |
274
+ | `submit_form` | `webview.submit()` | Submit as if the user pressed the button. |
275
+ | `reset_form` | `webview.reset()` | Clear all entered values. |
276
+ | `update_draft` | `webview.updateDraft()` | Switch to draft-update mode. |
277
+ | `trigger_validation` | `webview.triggerValidation(target)` | `target`: `"email" \| "invitation_code" \| "all"`. |
278
+ | `form_prefill` | `webview.prefill({ submission?, info? })` | Submission body, transcription items, or both. |
210
279
 
211
- ### Prefill variants (`form_prefill`)
280
+ ### Prefill (`form_prefill`)
212
281
 
213
- `form_prefill` carries a `data_type` selecting the payload shape:
282
+ A single `prefill({ submission?, info? })` carries either or both payloads:
214
283
 
215
- - `PrefillDataType.UlcSubmission` → `prefillSubmission(submission)` — a `SubmissionPrefill`
216
- (a received `SubmissionBody` round-tripped, or a partial `{ data?, ...} ` you assemble).
217
- - `PrefillDataType.Info` → `prefillInfo(items)` — a list of `PrefillInfoItem`. The webview
218
- matches each item by `ll_field_unique_identifier` (e.g. `"FirstName"`, `"Email"`);
219
- `ll_field_id` is optional metadata (number or string) and `value` may be a string or boolean.
220
- - `PrefillDataType.UlcSubmissionAndInfo` → `prefillSubmissionAndInfo({ submission, info })`.
284
+ - `submission` — a `SubmissionPrefill` (a received `SubmissionBody` round-tripped, or a
285
+ partial `{ data?, ... }` you assemble).
286
+ - `info` — a list of `PrefillInfoItem`. The webview matches each item by
287
+ `ll_field_unique_identifier` (e.g. `"FirstName"`, `"Email"`); `ll_field_id` is optional
288
+ metadata (number or string) and `value` may be a string or boolean.
289
+
290
+ Both ride the same wire `data_type` (`ulc_submission_and_info`); pass just the key(s) you
291
+ have.
221
292
 
222
293
  ## Rendering a submission
223
294
 
224
295
  A `submission_body` payload includes `visible_submissions_data` — one entry per filled,
225
296
  visible element, typed as `VisibleSubmissionDataItem[]` and discriminated by
226
297
  `element_type`. Narrow on `element_type` and `element_value` is precisely typed (string,
227
- string array, composite name/address, order quantities, etc.), so you render it exactly
298
+ string array, name/address objects, order quantities, etc.), so you render it exactly
228
299
  how your UI needs — no flattening helper to fight:
229
300
 
230
301
  ```ts
@@ -241,8 +312,18 @@ webview.on(OutboundMessageType.SubmissionBody, (msg) => {
241
312
  addRow(item.element_title, renderChoices(item.element_value));
242
313
  break;
243
314
  case FormElementType.simple_name:
244
- // element_value: CompositeSubmissionValue (sub-field id string)
245
- addRow(item.element_title, Object.values(item.element_value).join(" "));
315
+ // element_value: NameSubmissionValue ({ FirstName?, LastName? })
316
+ addRow(
317
+ item.element_title,
318
+ [item.element_value.FirstName, item.element_value.LastName].filter(Boolean).join(" "),
319
+ );
320
+ break;
321
+ case FormElementType.address:
322
+ // element_value: AddressSubmissionValue ({ StreetAddress?, City?, State?, Zipcode?, Country?, … })
323
+ addRow(
324
+ item.element_title,
325
+ [item.element_value.StreetAddress, item.element_value.City].filter(Boolean).join(", "),
326
+ );
246
327
  break;
247
328
  // …other element types
248
329
  }
@@ -310,13 +391,13 @@ new CaptelloWebview(iframe, {
310
391
  - `onAny(listener) => unsubscribe` — every outbound message.
311
392
  - `submit()`, `reset()`, `updateDraft()`, `triggerValidation(target)` — inbound helpers.
312
393
  - `submitAndWait(timeoutMs?)` — submit and await `submission_body` / `form_error_message` (see above).
313
- - `prefillSubmission(...)`, `prefillInfo(...)`, `prefillSubmissionAndInfo(...)`.
394
+ - `prefill({ submission?, info? })` — pre-fill from a submission body, transcription items, or both.
314
395
  - `send(message)` — low-level escape hatch for any `InboundMessage`.
315
396
  - `destroy()` — detach the listener and drop subscriptions (idempotent).
316
397
 
317
398
  **Send queueing.** With `queueUntilReady` (default `true`), any send before the webview
318
399
  reports `form_load_complete` is buffered and flushed, in order, on load — so calling
319
- `prefillInfo(...)` right after mount won't be silently dropped. A client that attaches
400
+ `prefill(...)` right after mount won't be silently dropped. A client that attaches
320
401
  _after_ the form already loaded won't observe `form_load_complete`; either create the
321
402
  client with the iframe, or pass `queueUntilReady: false` to send immediately. The
322
403
  one-shot `/promises` helpers set `queueUntilReady: false` automatically.
@@ -375,8 +456,8 @@ short keys, and ad-hoc `window.addEventListener("message")` / `iframe.contentWin
375
456
  calls. Replace them as follows.
376
457
 
377
458
  **1. Message-type constants → SDK enums.** Delete local copies (e.g. `UlcFormActionTypeSent`,
378
- `UlcFormActionTypeReceived`, `UlcFormDataType`) and import `InboundMessageType`,
379
- `OutboundMessageType`, `PrefillDataType`.
459
+ `UlcFormActionTypeReceived`, `UlcFormDataType`) and import `InboundMessageType` /
460
+ `OutboundMessageType` (the `form_prefill` `data_type` is set for you by `prefill(...)`).
380
461
 
381
462
  **2. Manual URL building → `buildEmbedUrl`.**
382
463
 
@@ -408,12 +489,12 @@ const body = await client.submitAndWait(); // throws SubmissionError on form_err
408
489
 
409
490
  **5. `document.querySelector("#ulcForm").contentWindow.postMessage(...)` → client methods.**
410
491
  Hold the `CaptelloWebview` instance in a ref/context and call `submit()` / `reset()` /
411
- `prefillInfo()` instead of re-querying the DOM and stringifying messages by hand.
492
+ `prefill()` instead of re-querying the DOM and stringifying messages by hand.
412
493
 
413
- **Prefill notes for migrators.** `prefillInfo` items are matched by
494
+ **Prefill notes for migrators.** `prefill({ info })` items are matched by
414
495
  `ll_field_unique_identifier`; `ll_field_id` is optional (number or string) and `value`
415
496
  may be a string or boolean — so existing payloads with numeric ids and boolean values
416
- type-check as-is. `prefillSubmission` accepts a loose `SubmissionPrefill`, so a
497
+ type-check as-is. `prefill({ submission })` accepts a loose `SubmissionPrefill`, so a
417
498
  previously-received `SubmissionBody` (or a partial `{ email?, data?, ... }`) can be passed
418
499
  back directly.
419
500
 
@@ -64,9 +64,26 @@ declare enum FormElementType {
64
64
  star_rating = "star_survey",
65
65
  attachments = "attachments"
66
66
  }
67
- /** Composite element value (simple_name / address): sub-field values keyed by sub-element id. */
68
- type CompositeSubmissionValue = {
69
- [subElementId: string]: string;
67
+ /**
68
+ * `simple_name` value: the visible sub-fields keyed by their transcription identifier
69
+ * (`ll_field_unique_identifier`), the same identifiers used by prefill. Hidden sub-fields are
70
+ * omitted, so keys are optional.
71
+ */
72
+ type NameSubmissionValue = {
73
+ FirstName?: string;
74
+ LastName?: string;
75
+ };
76
+ /**
77
+ * `address` value: the visible sub-fields keyed by their transcription identifier
78
+ * (`ll_field_unique_identifier`). Hidden sub-fields are omitted, so keys are optional.
79
+ */
80
+ type AddressSubmissionValue = {
81
+ StreetAddress?: string;
82
+ StreetAddress2?: string;
83
+ City?: string;
84
+ State?: string;
85
+ Zipcode?: string;
86
+ Country?: string;
70
87
  };
71
88
  /** Checkbox value when the element is an "order" checkbox (quantities + note). */
72
89
  type OrderCheckboxSubmissionData = {
@@ -134,8 +151,8 @@ type VisibleSubmissionElementValueMap = {
134
151
  [FormElementType.business_card]: Partial<BusinessCardValue>;
135
152
  [FormElementType.boolean]: boolean;
136
153
  [FormElementType.attachments]: AttachmentValue[];
137
- [FormElementType.simple_name]: CompositeSubmissionValue;
138
- [FormElementType.address]: CompositeSubmissionValue;
154
+ [FormElementType.simple_name]: NameSubmissionValue;
155
+ [FormElementType.address]: AddressSubmissionValue;
139
156
  };
140
157
  /** Element types that carry a user-facing value (i.e. can appear in visible data). */
141
158
  type VisibleSubmissionElementType = keyof VisibleSubmissionElementValueMap;
@@ -497,4 +514,4 @@ declare class CaptelloWebview {
497
514
  private handleMessage;
498
515
  }
499
516
 
500
- export { type AnyOutboundListener as A, type BusinessCardValue as B, type CaptelloWebviewOptions as C, FormElementType as F, type InboundMessage as I, OutboundMessageType as O, type PrefillInfoItem as P, type SubmissionBody as S, type Unsubscribe as U, type ValidationTarget as V, type OutboundMessageMap as a, SubmissionError as b, SubmissionTimeoutError as c, type OutboundMessage as d, CaptelloWebview as e, type SubmissionPrefill as f, type AttachmentValue as g, type CompositeSubmissionValue as h, InboundMessageType as i, type OrderCheckboxSubmissionData as j, type OrderRadioSubmissionData as k, type OutboundListener as l, type SubmissionQuestionData as m, type VisibleSubmissionDataItem as n, type VisibleSubmissionElementType as o, type VisibleSubmissionElementValueMap as p, parseOutboundMessage as q };
517
+ export { type AddressSubmissionValue as A, type BusinessCardValue as B, type CaptelloWebviewOptions as C, FormElementType as F, type InboundMessage as I, type NameSubmissionValue as N, OutboundMessageType as O, type PrefillInfoItem as P, type SubmissionBody as S, type Unsubscribe as U, type ValidationTarget as V, type OutboundMessageMap as a, SubmissionError as b, SubmissionTimeoutError as c, type OutboundMessage as d, CaptelloWebview as e, type SubmissionPrefill as f, type AnyOutboundListener as g, type AttachmentValue as h, InboundMessageType as i, type OrderCheckboxSubmissionData as j, type OrderRadioSubmissionData as k, type OutboundListener as l, type SubmissionQuestionData as m, type VisibleSubmissionDataItem as n, type VisibleSubmissionElementType as o, type VisibleSubmissionElementValueMap as p, parseOutboundMessage as q };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AnyOutboundListener, g as AttachmentValue, B as BusinessCardValue, e as CaptelloWebview, C as CaptelloWebviewOptions, h as CompositeSubmissionValue, F as FormElementType, I as InboundMessage, i as InboundMessageType, j as OrderCheckboxSubmissionData, k as OrderRadioSubmissionData, l as OutboundListener, d as OutboundMessage, a as OutboundMessageMap, O as OutboundMessageType, P as PrefillInfoItem, S as SubmissionBody, b as SubmissionError, f as SubmissionPrefill, m as SubmissionQuestionData, c as SubmissionTimeoutError, U as Unsubscribe, V as ValidationTarget, n as VisibleSubmissionDataItem, o as VisibleSubmissionElementType, p as VisibleSubmissionElementValueMap, q as parseOutboundMessage } from './client-Dik-Mjid.js';
1
+ export { A as AddressSubmissionValue, g as AnyOutboundListener, h as AttachmentValue, B as BusinessCardValue, e as CaptelloWebview, C as CaptelloWebviewOptions, F as FormElementType, I as InboundMessage, i as InboundMessageType, N as NameSubmissionValue, j as OrderCheckboxSubmissionData, k as OrderRadioSubmissionData, l as OutboundListener, d as OutboundMessage, a as OutboundMessageMap, O as OutboundMessageType, P as PrefillInfoItem, S as SubmissionBody, b as SubmissionError, f as SubmissionPrefill, m as SubmissionQuestionData, c as SubmissionTimeoutError, U as Unsubscribe, V as ValidationTarget, n as VisibleSubmissionDataItem, o as VisibleSubmissionElementType, p as VisibleSubmissionElementValueMap, q as parseOutboundMessage } from './client-DOm9cwoe.js';
2
2
 
3
3
  /**
4
4
  * Builder for the Captello capture webview embed URL.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/submission-data.ts"],"names":["FormElementType"],"mappings":";;;;AAiCO,IAAK,eAAA,qBAAAA,gBAAAA,KAAL;AACH,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,SAAA,CAAA,GAAU,eAAA;AACV,EAAAA,iBAAA,YAAA,CAAA,GAAa,SAAA;AACb,EAAAA,iBAAA,KAAA,CAAA,GAAM,KAAA;AACN,EAAAA,iBAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,iBAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,aAAA,CAAA,GAAc,aAAA;AACd,EAAAA,iBAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,iBAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,iBAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,cAAA,CAAA,GAAe,cAAA;AACf,EAAAA,iBAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,eAAA,CAAA,GAAgB,eAAA;AAChB,EAAAA,iBAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,WAAA,CAAA,GAAY,kBAAA;AACZ,EAAAA,iBAAA,YAAA,CAAA,GAAa,YAAA;AACb,EAAAA,iBAAA,UAAA,CAAA,GAAW,WAAA;AACX,EAAAA,iBAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,iBAAA,cAAA,CAAA,GAAe,cAAA;AACf,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,gBAAA,CAAA,GAAiB,gBAAA;AACjB,EAAAA,iBAAA,iBAAA,CAAA,GAAkB,uBAAA;AAClB,EAAAA,iBAAA,iBAAA,CAAA,GAAkB,uBAAA;AAClB,EAAAA,iBAAA,mBAAA,CAAA,GAAoB,mBAAA;AACpB,EAAAA,iBAAA,aAAA,CAAA,GAAc,aAAA;AACd,EAAAA,iBAAA,aAAA,CAAA,GAAc,aAAA;AApCN,EAAA,OAAAA,gBAAAA;AAAA,CAAA,EAAA,eAAA,IAAA,EAAA","file":"index.js","sourcesContent":["/**\n * Types for a submission's **visible** data — the list a host renders as key/value\n * pairs (one row per filled, visible form element).\n *\n * The webview emits this as `visible_submissions_data` inside the `submission_body`\n * payload: an array of {@link VisibleSubmissionDataItem}, discriminated by\n * `element_type`, where `element_value`'s shape depends on the type. Narrow on\n * `element_type` to get a precisely-typed `element_value` and render it however you\n * like:\n *\n * @example\n * for (const item of submission.visible_submissions_data ?? []) {\n * switch (item.element_type) {\n * case FormElementType.email:\n * row(item.element_title, item.element_value); // element_value: string\n * break;\n * case FormElementType.checkbox:\n * // element_value: string[] | OrderCheckboxSubmissionData\n * break;\n * // …\n * }\n * }\n */\n\n/* ------------------------------------------------------------------ *\n * Element types\n * ------------------------------------------------------------------ */\n\n/**\n * Every form element type, mirroring the webview's `FormElementType`. The string\n * values are the wire values. Structural types (sections, separators, …) never carry\n * a value and never appear in visible submission data.\n */\nexport enum FormElementType {\n email = \"email\",\n section = \"section_block\",\n html_block = \"section\",\n url = \"url\",\n text = \"text\",\n select = \"select\",\n radio = \"radio\",\n simple_name = \"simple_name\",\n textarea = \"textarea\",\n time = \"time\",\n address = \"address\",\n money = \"money\",\n number = \"number\",\n date = \"date\",\n phone = \"phone\",\n simple_phone = \"simple_phone\",\n checkbox = \"checkbox\",\n image = \"image\",\n business_card = \"business_card\",\n signature = \"signature\",\n barcode = \"barcode\",\n separator = \"column_separator\",\n activation = \"activation\",\n document = \"documents\",\n datetime = \"datetime\",\n meeting = \"meeting\",\n audio = \"audio\",\n rating = \"rating\",\n assign_owner = \"assign_owner\",\n boolean = \"boolean\",\n engagem_feeder = \"engagem_feeder\",\n speaker_section = \"speaker_section_block\",\n session_section = \"session_section_block\",\n image_placeholder = \"image_placeholder\",\n star_rating = \"star_survey\",\n attachments = \"attachments\",\n}\n\n/* ------------------------------------------------------------------ *\n * Element value shapes\n * ------------------------------------------------------------------ */\n\n/** Composite element value (simple_name / address): sub-field values keyed by sub-element id. */\nexport type CompositeSubmissionValue = { [subElementId: string]: string };\n\n/** Checkbox value when the element is an \"order\" checkbox (quantities + note). */\nexport type OrderCheckboxSubmissionData = {\n values: { value: string; quantity: number }[];\n note: string;\n};\n\n/** Radio/select value when the element is an \"order\" radio/select (quantity + note). */\nexport type OrderRadioSubmissionData = { value: string; quantity: number; note: string };\n\n/** An uploaded attachment. `blob` is not present on the wire (postMessage JSON drops it). */\nexport type AttachmentValue = { token: string; url: string; name: string; size: number };\n\n/** A business-card capture: front/back image references (either may be absent). */\nexport type BusinessCardValue = { front: string; back: string };\n\n/** One engagement-feeder question/answer entry. */\nexport type SubmissionQuestionData = {\n question: string;\n answers: string[];\n /** `\"\"` when the element is hidden. */\n correct_answer: number | \"\";\n};\n\n/**\n * Maps each value-carrying element type to its `element_value` shape. Structural\n * types are intentionally absent, so they can never appear in a visible item.\n */\nexport type VisibleSubmissionElementValueMap = {\n [FormElementType.email]: string;\n [FormElementType.url]: string;\n [FormElementType.text]: string;\n [FormElementType.textarea]: string;\n [FormElementType.money]: string;\n [FormElementType.number]: string;\n [FormElementType.phone]: string;\n [FormElementType.simple_phone]: string;\n [FormElementType.date]: string;\n [FormElementType.time]: string;\n [FormElementType.datetime]: string;\n [FormElementType.audio]: string;\n [FormElementType.rating]: string;\n [FormElementType.star_rating]: string;\n [FormElementType.assign_owner]: string;\n [FormElementType.signature]: string;\n [FormElementType.barcode]: string;\n [FormElementType.meeting]: string;\n [FormElementType.activation]: string;\n [FormElementType.checkbox]: string[] | OrderCheckboxSubmissionData;\n [FormElementType.radio]: string | OrderRadioSubmissionData;\n [FormElementType.select]: string | OrderRadioSubmissionData;\n [FormElementType.image]: string[];\n [FormElementType.document]: number[];\n [FormElementType.engagem_feeder]: SubmissionQuestionData[];\n [FormElementType.business_card]: Partial<BusinessCardValue>;\n [FormElementType.boolean]: boolean;\n [FormElementType.attachments]: AttachmentValue[];\n [FormElementType.simple_name]: CompositeSubmissionValue;\n [FormElementType.address]: CompositeSubmissionValue;\n};\n\n/** Element types that carry a user-facing value (i.e. can appear in visible data). */\nexport type VisibleSubmissionElementType = keyof VisibleSubmissionElementValueMap;\n\n/**\n * One entry in `visible_submissions_data`, discriminated by `element_type`. Narrowing\n * on `element_type` gives a precisely-typed `element_value`.\n */\nexport type VisibleSubmissionDataItem = {\n [T in VisibleSubmissionElementType]: {\n element_id: string;\n element_title: string;\n element_type: T;\n element_value: VisibleSubmissionElementValueMap[T];\n };\n}[VisibleSubmissionElementType];\n"]}
1
+ {"version":3,"sources":["../src/submission-data.ts"],"names":["FormElementType"],"mappings":";;;;AAiCO,IAAK,eAAA,qBAAAA,gBAAAA,KAAL;AACH,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,SAAA,CAAA,GAAU,eAAA;AACV,EAAAA,iBAAA,YAAA,CAAA,GAAa,SAAA;AACb,EAAAA,iBAAA,KAAA,CAAA,GAAM,KAAA;AACN,EAAAA,iBAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,iBAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,aAAA,CAAA,GAAc,aAAA;AACd,EAAAA,iBAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,iBAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,iBAAA,MAAA,CAAA,GAAO,MAAA;AACP,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,cAAA,CAAA,GAAe,cAAA;AACf,EAAAA,iBAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,eAAA,CAAA,GAAgB,eAAA;AAChB,EAAAA,iBAAA,WAAA,CAAA,GAAY,WAAA;AACZ,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,WAAA,CAAA,GAAY,kBAAA;AACZ,EAAAA,iBAAA,YAAA,CAAA,GAAa,YAAA;AACb,EAAAA,iBAAA,UAAA,CAAA,GAAW,WAAA;AACX,EAAAA,iBAAA,UAAA,CAAA,GAAW,UAAA;AACX,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,OAAA,CAAA,GAAQ,OAAA;AACR,EAAAA,iBAAA,QAAA,CAAA,GAAS,QAAA;AACT,EAAAA,iBAAA,cAAA,CAAA,GAAe,cAAA;AACf,EAAAA,iBAAA,SAAA,CAAA,GAAU,SAAA;AACV,EAAAA,iBAAA,gBAAA,CAAA,GAAiB,gBAAA;AACjB,EAAAA,iBAAA,iBAAA,CAAA,GAAkB,uBAAA;AAClB,EAAAA,iBAAA,iBAAA,CAAA,GAAkB,uBAAA;AAClB,EAAAA,iBAAA,mBAAA,CAAA,GAAoB,mBAAA;AACpB,EAAAA,iBAAA,aAAA,CAAA,GAAc,aAAA;AACd,EAAAA,iBAAA,aAAA,CAAA,GAAc,aAAA;AApCN,EAAA,OAAAA,gBAAAA;AAAA,CAAA,EAAA,eAAA,IAAA,EAAA","file":"index.js","sourcesContent":["/**\n * Types for a submission's **visible** data — the list a host renders as key/value\n * pairs (one row per filled, visible form element).\n *\n * The webview emits this as `visible_submissions_data` inside the `submission_body`\n * payload: an array of {@link VisibleSubmissionDataItem}, discriminated by\n * `element_type`, where `element_value`'s shape depends on the type. Narrow on\n * `element_type` to get a precisely-typed `element_value` and render it however you\n * like:\n *\n * @example\n * for (const item of submission.visible_submissions_data ?? []) {\n * switch (item.element_type) {\n * case FormElementType.email:\n * row(item.element_title, item.element_value); // element_value: string\n * break;\n * case FormElementType.checkbox:\n * // element_value: string[] | OrderCheckboxSubmissionData\n * break;\n * // …\n * }\n * }\n */\n\n/* ------------------------------------------------------------------ *\n * Element types\n * ------------------------------------------------------------------ */\n\n/**\n * Every form element type, mirroring the webview's `FormElementType`. The string\n * values are the wire values. Structural types (sections, separators, …) never carry\n * a value and never appear in visible submission data.\n */\nexport enum FormElementType {\n email = \"email\",\n section = \"section_block\",\n html_block = \"section\",\n url = \"url\",\n text = \"text\",\n select = \"select\",\n radio = \"radio\",\n simple_name = \"simple_name\",\n textarea = \"textarea\",\n time = \"time\",\n address = \"address\",\n money = \"money\",\n number = \"number\",\n date = \"date\",\n phone = \"phone\",\n simple_phone = \"simple_phone\",\n checkbox = \"checkbox\",\n image = \"image\",\n business_card = \"business_card\",\n signature = \"signature\",\n barcode = \"barcode\",\n separator = \"column_separator\",\n activation = \"activation\",\n document = \"documents\",\n datetime = \"datetime\",\n meeting = \"meeting\",\n audio = \"audio\",\n rating = \"rating\",\n assign_owner = \"assign_owner\",\n boolean = \"boolean\",\n engagem_feeder = \"engagem_feeder\",\n speaker_section = \"speaker_section_block\",\n session_section = \"session_section_block\",\n image_placeholder = \"image_placeholder\",\n star_rating = \"star_survey\",\n attachments = \"attachments\",\n}\n\n/* ------------------------------------------------------------------ *\n * Element value shapes\n * ------------------------------------------------------------------ */\n\n/**\n * `simple_name` value: the visible sub-fields keyed by their transcription identifier\n * (`ll_field_unique_identifier`), the same identifiers used by prefill. Hidden sub-fields are\n * omitted, so keys are optional.\n */\nexport type NameSubmissionValue = {\n FirstName?: string;\n LastName?: string;\n};\n\n/**\n * `address` value: the visible sub-fields keyed by their transcription identifier\n * (`ll_field_unique_identifier`). Hidden sub-fields are omitted, so keys are optional.\n */\nexport type AddressSubmissionValue = {\n StreetAddress?: string;\n StreetAddress2?: string;\n City?: string;\n State?: string;\n Zipcode?: string;\n Country?: string;\n};\n\n/** Checkbox value when the element is an \"order\" checkbox (quantities + note). */\nexport type OrderCheckboxSubmissionData = {\n values: { value: string; quantity: number }[];\n note: string;\n};\n\n/** Radio/select value when the element is an \"order\" radio/select (quantity + note). */\nexport type OrderRadioSubmissionData = { value: string; quantity: number; note: string };\n\n/** An uploaded attachment. `blob` is not present on the wire (postMessage JSON drops it). */\nexport type AttachmentValue = { token: string; url: string; name: string; size: number };\n\n/** A business-card capture: front/back image references (either may be absent). */\nexport type BusinessCardValue = { front: string; back: string };\n\n/** One engagement-feeder question/answer entry. */\nexport type SubmissionQuestionData = {\n question: string;\n answers: string[];\n /** `\"\"` when the element is hidden. */\n correct_answer: number | \"\";\n};\n\n/**\n * Maps each value-carrying element type to its `element_value` shape. Structural\n * types are intentionally absent, so they can never appear in a visible item.\n */\nexport type VisibleSubmissionElementValueMap = {\n [FormElementType.email]: string;\n [FormElementType.url]: string;\n [FormElementType.text]: string;\n [FormElementType.textarea]: string;\n [FormElementType.money]: string;\n [FormElementType.number]: string;\n [FormElementType.phone]: string;\n [FormElementType.simple_phone]: string;\n [FormElementType.date]: string;\n [FormElementType.time]: string;\n [FormElementType.datetime]: string;\n [FormElementType.audio]: string;\n [FormElementType.rating]: string;\n [FormElementType.star_rating]: string;\n [FormElementType.assign_owner]: string;\n [FormElementType.signature]: string;\n [FormElementType.barcode]: string;\n [FormElementType.meeting]: string;\n [FormElementType.activation]: string;\n [FormElementType.checkbox]: string[] | OrderCheckboxSubmissionData;\n [FormElementType.radio]: string | OrderRadioSubmissionData;\n [FormElementType.select]: string | OrderRadioSubmissionData;\n [FormElementType.image]: string[];\n [FormElementType.document]: number[];\n [FormElementType.engagem_feeder]: SubmissionQuestionData[];\n [FormElementType.business_card]: Partial<BusinessCardValue>;\n [FormElementType.boolean]: boolean;\n [FormElementType.attachments]: AttachmentValue[];\n [FormElementType.simple_name]: NameSubmissionValue;\n [FormElementType.address]: AddressSubmissionValue;\n};\n\n/** Element types that carry a user-facing value (i.e. can appear in visible data). */\nexport type VisibleSubmissionElementType = keyof VisibleSubmissionElementValueMap;\n\n/**\n * One entry in `visible_submissions_data`, discriminated by `element_type`. Narrowing\n * on `element_type` gives a precisely-typed `element_value`.\n */\nexport type VisibleSubmissionDataItem = {\n [T in VisibleSubmissionElementType]: {\n element_id: string;\n element_title: string;\n element_type: T;\n element_value: VisibleSubmissionElementValueMap[T];\n };\n}[VisibleSubmissionElementType];\n"]}
@@ -1,5 +1,5 @@
1
- import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-Dik-Mjid.js';
2
- export { b as SubmissionError, c as SubmissionTimeoutError } from './client-Dik-Mjid.js';
1
+ import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-DOm9cwoe.js';
2
+ export { b as SubmissionError, c as SubmissionTimeoutError } from './client-DOm9cwoe.js';
3
3
 
4
4
  /**
5
5
  * Promise-based, one-shot helpers for imperative flows — `@captello/ulc-webview-sdk/promises`.
package/dist/react.d.ts CHANGED
@@ -1,47 +1,9 @@
1
- import { RefCallback } from 'react';
2
- import { a as OutboundMessageMap, O as OutboundMessageType, d as OutboundMessage, C as CaptelloWebviewOptions, e as CaptelloWebview, V as ValidationTarget, f as SubmissionPrefill, P as PrefillInfoItem, S as SubmissionBody } from './client-Dik-Mjid.js';
3
- export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-Dik-Mjid.js';
1
+ import * as react from 'react';
2
+ import { CSSProperties, IframeHTMLAttributes, ReactNode, RefCallback } from 'react';
3
+ import { C as CaptelloWebviewOptions, a as OutboundMessageMap, O as OutboundMessageType, d as OutboundMessage, e as CaptelloWebview, V as ValidationTarget, f as SubmissionPrefill, P as PrefillInfoItem, S as SubmissionBody } from './client-DOm9cwoe.js';
4
+ export { b as SubmissionError, c as SubmissionTimeoutError, U as Unsubscribe } from './client-DOm9cwoe.js';
4
5
  import { EmbedUrlOptions } from './index.js';
5
6
 
6
- /**
7
- * React adapter for the Captello webview SDK — `@captello/ulc-webview-sdk/react`.
8
- *
9
- * {@link useCaptelloWebview} owns a {@link CaptelloWebview} for the lifetime of an
10
- * iframe: it creates the client once the iframe mounts, wires the outbound messages
11
- * you care about to typed callbacks, tracks readiness, and destroys the client on
12
- * unmount. You get back `iframeProps` to spread onto your `<iframe>` (or a bare `ref`),
13
- * an `isReady` flag, and stable senders (`submit`, `reset`, `prefill`, …).
14
- *
15
- * Sends made before the form loads are queued by the client and flushed on
16
- * `form_load_complete`, so you can call `prefill(...)` as soon as you have data —
17
- * no need to gate on readiness yourself.
18
- *
19
- * Callbacks are held in a ref and always called fresh, so you do NOT need to memoize
20
- * them — passing inline arrow functions will not re-subscribe or re-create the client.
21
- *
22
- * `react` is an optional peer dependency; importing this entry point requires React 18+.
23
- *
24
- * @example
25
- * function UlcForm({ token, onSubmitted }: { token: string; onSubmitted: (b: SubmissionBody) => void }) {
26
- * const { iframeProps, isReady, submit } = useCaptelloWebview({
27
- * embedUrl: {
28
- * baseUrl: "https://capture.captello.com",
29
- * eventWebAccessToken: token,
30
- * mode: FormMode.Submit,
31
- * launcher: LauncherType.EventGenWeb,
32
- * },
33
- * onSubmissionBody: (m) => onSubmitted(m.data),
34
- * });
35
- * return (
36
- * <>
37
- * {!isReady && <Spinner />}
38
- * <iframe {...iframeProps} title="UlcForm" allow="camera; microphone" />
39
- * <button onClick={submit}>Submit</button>
40
- * </>
41
- * );
42
- * }
43
- */
44
-
45
7
  /** Per-message-type callback props accepted by {@link useCaptelloWebview}. */
46
8
  interface CaptelloWebviewCallbacks {
47
9
  onFormLoadComplete?: (message: OutboundMessageMap[OutboundMessageType.FormLoadComplete]) => void;
@@ -106,5 +68,81 @@ interface UseCaptelloWebviewResult {
106
68
  * Binds a {@link CaptelloWebview} to an iframe's lifecycle. See the module doc for usage.
107
69
  */
108
70
  declare function useCaptelloWebview(options: UseCaptelloWebviewOptions): UseCaptelloWebviewResult;
71
+ /**
72
+ * Imperative handle exposed on {@link CaptelloForm}'s `ref` — the same senders the hook
73
+ * returns, plus the current status and the underlying `<iframe>` node. Lets a parent
74
+ * drive the form (e.g. an external submit button) without lifting state.
75
+ */
76
+ interface CaptelloFormHandle extends Pick<UseCaptelloWebviewResult, "submit" | "reset" | "updateDraft" | "triggerValidation" | "prefill" | "submitAndWait" | "getClient"> {
77
+ /** Current readiness: `"loading" | "ready" | "error"`. */
78
+ readonly status: CaptelloWebviewStatus;
79
+ /** `true` once the form has reported `form_load_complete`. */
80
+ readonly isReady: boolean;
81
+ /** The underlying `<iframe>` DOM node, or `null` before it mounts. */
82
+ getIframe: () => HTMLIFrameElement | null;
83
+ }
84
+ /**
85
+ * Props for {@link CaptelloForm}: every {@link useCaptelloWebviewOptions} option (embed
86
+ * config + message callbacks + client options) plus rendering conveniences.
87
+ */
88
+ interface CaptelloFormProps extends UseCaptelloWebviewOptions {
89
+ /** `className` for the wrapper element. */
90
+ className?: string;
91
+ /** `style` for the wrapper element — size the form here. The component adds `position: relative`; your values win. */
92
+ style?: CSSProperties;
93
+ /** `id` for the wrapper element. */
94
+ id?: string;
95
+ /**
96
+ * Attributes spread onto the `<iframe>` — `title`, `allow`, `sandbox`, `name`, etc.
97
+ * Defaults: `title="Captello form"`, `allow="camera; microphone; geolocation"`. When you
98
+ * drive the URL yourself (no `embedUrl`), set `src` here.
99
+ */
100
+ iframeProps?: Omit<IframeHTMLAttributes<HTMLIFrameElement>, "ref">;
101
+ /** Rendered, centered over the iframe, while it is loading. The iframe stays mounted underneath. */
102
+ loading?: ReactNode;
103
+ /**
104
+ * Rendered, centered over the iframe, when the form reports `form_error_message`.
105
+ * Pass a function to receive the translated, display-ready error text.
106
+ */
107
+ error?: ReactNode | ((message: string | undefined) => ReactNode);
108
+ /**
109
+ * Inline controls rendered after the form. A function receives the live api
110
+ * (status + senders), so you can wire a submit button without a `ref`.
111
+ */
112
+ children?: ReactNode | ((api: UseCaptelloWebviewResult) => ReactNode);
113
+ }
114
+ /**
115
+ * Turnkey component for embedding a Captello capture form — the shortest path to a
116
+ * working integration. Renders the `<iframe>`, wires {@link useCaptelloWebview} to it,
117
+ * shows your `loading` / `error` overlays, and forwards a {@link CaptelloFormHandle} on
118
+ * `ref` so a parent can `submit()` / `prefill()` without lifting state.
119
+ *
120
+ * Pass `embedUrl` and the form fills its wrapper — size the form via `className` / `style`
121
+ * (an iframe has no intrinsic height). Reach for {@link useCaptelloWebview} instead when
122
+ * you need to own the markup.
123
+ *
124
+ * @example
125
+ * function UlcForm({ token }: { token: string }) {
126
+ * const ref = useRef<CaptelloFormHandle>(null);
127
+ * return (
128
+ * <CaptelloForm
129
+ * ref={ref}
130
+ * style={{ height: 600 }}
131
+ * embedUrl={{
132
+ * baseUrl: "https://capture.captello.com",
133
+ * eventWebAccessToken: token,
134
+ * mode: FormMode.Submit,
135
+ * launcher: LauncherType.EventGenWeb,
136
+ * }}
137
+ * onSubmissionBody={(m) => save(m.data)}
138
+ * loading={<Spinner />}
139
+ * error={(msg) => <ErrorBanner>{msg}</ErrorBanner>}
140
+ * >
141
+ * {({ isReady }) => <button disabled={!isReady} onClick={() => ref.current?.submit()}>Submit</button>}
142
+ * </CaptelloForm>
143
+ * );
144
+ * }
145
+ */
146
+ declare const CaptelloForm: react.ForwardRefExoticComponent<CaptelloFormProps & react.RefAttributes<CaptelloFormHandle>>;
109
147
 
110
- export { type CaptelloIframeProps, CaptelloWebview, type CaptelloWebviewCallbacks, type CaptelloWebviewStatus, type EmbedUrlConfig, type UseCaptelloWebviewOptions, type UseCaptelloWebviewResult, useCaptelloWebview };
148
+ export { CaptelloForm, type CaptelloFormHandle, type CaptelloFormProps, type CaptelloIframeProps, CaptelloWebview, type CaptelloWebviewCallbacks, type CaptelloWebviewStatus, type EmbedUrlConfig, type UseCaptelloWebviewOptions, type UseCaptelloWebviewResult, useCaptelloWebview };
package/dist/react.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { buildEmbedUrl } from './chunk-4E7OW4RJ.js';
2
2
  import { CaptelloWebview, OutboundMessageType } from './chunk-PFFBCSJ2.js';
3
3
  export { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from './chunk-PFFBCSJ2.js';
4
- import { useRef, useState, useCallback, useEffect } from 'react';
4
+ import { forwardRef, useState, useRef, useCallback, useImperativeHandle, useEffect } from 'react';
5
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
5
6
 
6
7
  var CALLBACK_BY_TYPE = {
7
8
  ["form_load_complete" /* FormLoadComplete */]: "onFormLoadComplete",
@@ -99,7 +100,94 @@ function useCaptelloWebview(options) {
99
100
  submitAndWait
100
101
  };
101
102
  }
103
+ var DEFAULT_ALLOW = "camera; microphone; geolocation";
104
+ var WRAPPER_STYLE = { position: "relative" };
105
+ var IFRAME_STYLE = { display: "block", width: "100%", height: "100%", border: 0 };
106
+ var OVERLAY_STYLE = {
107
+ position: "absolute",
108
+ inset: 0,
109
+ display: "flex",
110
+ alignItems: "center",
111
+ justifyContent: "center"
112
+ };
113
+ function CaptelloFormImpl(props, ref) {
114
+ const {
115
+ className,
116
+ style,
117
+ id,
118
+ iframeProps,
119
+ loading,
120
+ error,
121
+ children,
122
+ onFormLoadComplete,
123
+ onFormErrorMessage,
124
+ ...options
125
+ } = props;
126
+ const [errorMessage, setErrorMessage] = useState(void 0);
127
+ const api = useCaptelloWebview({
128
+ ...options,
129
+ // Wrap the two status-bearing callbacks to track the error text, then forward to
130
+ // the caller's handler. The hook reads callbacks fresh, so these inline wrappers
131
+ // don't re-subscribe or re-create the client.
132
+ onFormLoadComplete: (message) => {
133
+ setErrorMessage(void 0);
134
+ onFormLoadComplete?.(message);
135
+ },
136
+ onFormErrorMessage: (message) => {
137
+ setErrorMessage(message.data);
138
+ onFormErrorMessage?.(message);
139
+ }
140
+ });
141
+ const nodeRef = useRef(null);
142
+ const hookRef = api.iframeProps.ref;
143
+ const setIframe = useCallback(
144
+ (node) => {
145
+ nodeRef.current = node;
146
+ hookRef(node);
147
+ },
148
+ [hookRef]
149
+ );
150
+ useImperativeHandle(
151
+ ref,
152
+ () => ({
153
+ submit: api.submit,
154
+ reset: api.reset,
155
+ updateDraft: api.updateDraft,
156
+ triggerValidation: api.triggerValidation,
157
+ prefill: api.prefill,
158
+ submitAndWait: api.submitAndWait,
159
+ getClient: api.getClient,
160
+ getIframe: () => nodeRef.current,
161
+ status: api.status,
162
+ isReady: api.isReady
163
+ }),
164
+ [api]
165
+ );
166
+ const src = api.iframeProps.src ?? iframeProps?.src;
167
+ const showLoading = api.status === "loading" && loading != null;
168
+ const showError = api.status === "error" && error != null;
169
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
170
+ /* @__PURE__ */ jsxs("div", { className, id, style: { ...WRAPPER_STYLE, ...style }, children: [
171
+ /* @__PURE__ */ jsx(
172
+ "iframe",
173
+ {
174
+ title: "Captello form",
175
+ allow: DEFAULT_ALLOW,
176
+ ...iframeProps,
177
+ ref: setIframe,
178
+ src,
179
+ style: { ...IFRAME_STYLE, ...iframeProps?.style }
180
+ }
181
+ ),
182
+ showLoading ? /* @__PURE__ */ jsx("div", { style: OVERLAY_STYLE, children: loading }) : null,
183
+ showError ? /* @__PURE__ */ jsx("div", { style: OVERLAY_STYLE, children: typeof error === "function" ? error(errorMessage) : error }) : null
184
+ ] }),
185
+ typeof children === "function" ? children(api) : children
186
+ ] });
187
+ }
188
+ var CaptelloForm = forwardRef(CaptelloFormImpl);
189
+ CaptelloForm.displayName = "CaptelloForm";
102
190
 
103
- export { useCaptelloWebview };
191
+ export { CaptelloForm, useCaptelloWebview };
104
192
  //# sourceMappingURL=react.js.map
105
193
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.ts"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["/**\n * React adapter for the Captello webview SDK — `@captello/ulc-webview-sdk/react`.\n *\n * {@link useCaptelloWebview} owns a {@link CaptelloWebview} for the lifetime of an\n * iframe: it creates the client once the iframe mounts, wires the outbound messages\n * you care about to typed callbacks, tracks readiness, and destroys the client on\n * unmount. You get back `iframeProps` to spread onto your `<iframe>` (or a bare `ref`),\n * an `isReady` flag, and stable senders (`submit`, `reset`, `prefill`, …).\n *\n * Sends made before the form loads are queued by the client and flushed on\n * `form_load_complete`, so you can call `prefill(...)` as soon as you have data —\n * no need to gate on readiness yourself.\n *\n * Callbacks are held in a ref and always called fresh, so you do NOT need to memoize\n * them — passing inline arrow functions will not re-subscribe or re-create the client.\n *\n * `react` is an optional peer dependency; importing this entry point requires React 18+.\n *\n * @example\n * function UlcForm({ token, onSubmitted }: { token: string; onSubmitted: (b: SubmissionBody) => void }) {\n * const { iframeProps, isReady, submit } = useCaptelloWebview({\n * embedUrl: {\n * baseUrl: \"https://capture.captello.com\",\n * eventWebAccessToken: token,\n * mode: FormMode.Submit,\n * launcher: LauncherType.EventGenWeb,\n * },\n * onSubmissionBody: (m) => onSubmitted(m.data),\n * });\n * return (\n * <>\n * {!isReady && <Spinner />}\n * <iframe {...iframeProps} title=\"UlcForm\" allow=\"camera; microphone\" />\n * <button onClick={submit}>Submit</button>\n * </>\n * );\n * }\n */\n\nimport { useCallback, useEffect, useRef, useState, type RefCallback } from \"react\";\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions, Unsubscribe } from \"./client\";\nimport { buildEmbedUrl } from \"./embed-url\";\nimport type { EmbedUrlOptions } from \"./embed-url\";\nimport { OutboundMessageType } from \"./messages\";\nimport type {\n OutboundMessage,\n OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/** Per-message-type callback props accepted by {@link useCaptelloWebview}. */\nexport interface CaptelloWebviewCallbacks {\n onFormLoadComplete?: (message: OutboundMessageMap[OutboundMessageType.FormLoadComplete]) => void;\n onFormErrorMessage?: (message: OutboundMessageMap[OutboundMessageType.FormErrorMessage]) => void;\n onSubmissionBody?: (message: OutboundMessageMap[OutboundMessageType.SubmissionBody]) => void;\n onFormSubmitSuccess?: (message: OutboundMessageMap[OutboundMessageType.FormSubmitSuccess]) => void;\n onConnexionsProfileRedirect?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsProfileRedirect]) => void;\n onConnexionsDownloadVcard?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsDownloadVcard]) => void;\n /** Catch-all: called for every outbound message, after the specific handler above. */\n onAnyMessage?: (message: OutboundMessage) => void;\n}\n\n/** Embed-URL config: a base URL plus {@link EmbedUrlOptions}. */\nexport interface EmbedUrlConfig extends EmbedUrlOptions {\n /**\n * The capture **origin**, e.g. `\"https://capture.captello.com\"`. The SDK appends the\n * capture path for you, so the origin, a trailing slash, or the full\n * `…/capture/submission` URL all work — see {@link buildEmbedUrl}.\n */\n baseUrl: string;\n}\n\n/**\n * Options for {@link useCaptelloWebview}.\n *\n * Provide **either** `embedUrl` (the hook builds the URL and derives `targetOrigin`,\n * returning `iframeProps.src`) **or** your own `targetOrigin` (you set the iframe `src`\n * yourself). Plus message callbacks and the usual client options.\n */\nexport interface UseCaptelloWebviewOptions extends CaptelloWebviewOptions, CaptelloWebviewCallbacks {\n /** Build the iframe URL and derive `targetOrigin` from it. Sets `iframeProps.src`. */\n embedUrl?: EmbedUrlConfig;\n}\n\n/** Readiness of the embedded form. */\nexport type CaptelloWebviewStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Props to spread onto the `<iframe>`. `src` is present only when `embedUrl` is given. */\nexport interface CaptelloIframeProps {\n ref: RefCallback<HTMLIFrameElement | null>;\n src?: string;\n}\n\n/** What {@link useCaptelloWebview} returns. */\nexport interface UseCaptelloWebviewResult {\n /** Spread onto your iframe: `<iframe {...iframeProps} />`. Includes `src` if `embedUrl` was given. */\n iframeProps: CaptelloIframeProps;\n /** The iframe ref callback (same as `iframeProps.ref`), if you'd rather set `src` yourself. */\n ref: RefCallback<HTMLIFrameElement | null>;\n /** `true` once the form has reported `form_load_complete`. */\n isReady: boolean;\n /** `\"loading\"` → `\"ready\"`; flips to `\"error\"` if a `form_error_message` arrives. */\n status: CaptelloWebviewStatus;\n /** The live client, or `null` before the iframe mounts. For escape-hatch use. */\n getClient: () => CaptelloWebview | null;\n submit: () => void;\n reset: () => void;\n updateDraft: () => void;\n triggerValidation: (target: ValidationTarget) => void;\n prefill: (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) => void;\n submitAndWait: (timeoutMs?: number) => Promise<SubmissionBody>;\n}\n\nconst CALLBACK_BY_TYPE: Record<OutboundMessageType, keyof CaptelloWebviewCallbacks> = {\n [OutboundMessageType.FormLoadComplete]: \"onFormLoadComplete\",\n [OutboundMessageType.FormErrorMessage]: \"onFormErrorMessage\",\n [OutboundMessageType.SubmissionBody]: \"onSubmissionBody\",\n [OutboundMessageType.FormSubmitSuccess]: \"onFormSubmitSuccess\",\n [OutboundMessageType.ConnexionsProfileRedirect]: \"onConnexionsProfileRedirect\",\n [OutboundMessageType.ConnexionsDownloadVcard]: \"onConnexionsDownloadVcard\",\n};\n\n/**\n * Binds a {@link CaptelloWebview} to an iframe's lifecycle. See the module doc for usage.\n */\nexport function useCaptelloWebview(options: UseCaptelloWebviewOptions): UseCaptelloWebviewResult {\n const { embedUrl, matchSource, queueUntilReady, hostWindow } = options;\n\n // Resolve the URL + the targetOrigin to use. embedUrl wins; otherwise use the\n // explicit targetOrigin. Recompute only when the URL-affecting inputs change.\n const src = embedUrl ? buildEmbedUrl(embedUrl.baseUrl, embedUrl) : undefined;\n const targetOrigin = src ? new URL(src).origin : options.targetOrigin;\n\n // Latest options/callbacks, read fresh inside listeners so callers needn't memoize.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const clientRef = useRef<CaptelloWebview | null>(null);\n const frameRef = useRef<HTMLIFrameElement | null>(null);\n const teardown = useRef<(() => void) | null>(null);\n\n const [status, setStatus] = useState<CaptelloWebviewStatus>(\"loading\");\n\n const attach = useCallback(\n (frame: HTMLIFrameElement | null) => {\n // Tear down any previous client (ref changed or unmounting).\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n frameRef.current = frame;\n setStatus(\"loading\");\n\n if (!frame) return;\n\n const client = new CaptelloWebview(frame, {\n targetOrigin,\n hostWindow: optionsRef.current.hostWindow,\n matchSource: optionsRef.current.matchSource,\n queueUntilReady: optionsRef.current.queueUntilReady,\n });\n clientRef.current = client;\n\n const offs: Unsubscribe[] = [];\n for (const type of Object.values(OutboundMessageType)) {\n offs.push(\n client.on(type, (message) => {\n if (type === OutboundMessageType.FormLoadComplete) setStatus(\"ready\");\n else if (type === OutboundMessageType.FormErrorMessage) setStatus(\"error\");\n\n const handler = optionsRef.current[CALLBACK_BY_TYPE[type]] as\n | ((m: typeof message) => void)\n | undefined;\n handler?.(message);\n optionsRef.current.onAnyMessage?.(message);\n }),\n );\n }\n\n teardown.current = () => {\n for (const off of offs) off();\n client.destroy();\n };\n },\n // Re-create the client only when connection-level inputs change.\n // Callbacks are read via optionsRef, so they intentionally aren't deps.\n [targetOrigin, matchSource, queueUntilReady, hostWindow],\n );\n\n useEffect(() => {\n if (frameRef.current) attach(frameRef.current);\n return () => {\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n };\n }, [attach]);\n\n const getClient = useCallback(() => clientRef.current, []);\n\n const submit = useCallback(() => clientRef.current?.submit(), []);\n const reset = useCallback(() => clientRef.current?.reset(), []);\n const updateDraft = useCallback(() => clientRef.current?.updateDraft(), []);\n const triggerValidation = useCallback(\n (target: ValidationTarget) => clientRef.current?.triggerValidation(target),\n [],\n );\n const prefill = useCallback(\n (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) =>\n clientRef.current?.prefill(data),\n [],\n );\n const submitAndWait = useCallback((timeoutMs?: number) => {\n const client = clientRef.current;\n if (!client) {\n return Promise.reject(new Error(\"CaptelloWebview: iframe is not mounted yet.\"));\n }\n return client.submitAndWait(timeoutMs);\n }, []);\n\n const iframeProps: CaptelloIframeProps = src ? { ref: attach, src } : { ref: attach };\n\n return {\n iframeProps,\n ref: attach,\n isReady: status === \"ready\",\n status,\n getClient,\n submit,\n reset,\n updateDraft,\n triggerValidation,\n prefill,\n submitAndWait,\n };\n}\n\nexport { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from \"./client\";\nexport type { Unsubscribe } from \"./client\";\n"]}
1
+ {"version":3,"sources":["../src/react.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["/**\n * React adapter for the Captello webview SDK — `@captello/ulc-webview-sdk/react`.\n *\n * Two entry points, same engine:\n * - {@link CaptelloForm} — a turnkey `<iframe>` component. Drop it in with an `embedUrl`\n * and message callbacks; it renders the frame, shows your `loading` / `error` overlays,\n * and exposes the senders via an imperative `ref`. This is the shortest path.\n * - {@link useCaptelloWebview} — the underlying hook, for when you want to own the markup.\n *\n * {@link useCaptelloWebview} owns a {@link CaptelloWebview} for the lifetime of an\n * iframe: it creates the client once the iframe mounts, wires the outbound messages\n * you care about to typed callbacks, tracks readiness, and destroys the client on\n * unmount. You get back `iframeProps` to spread onto your `<iframe>` (or a bare `ref`),\n * an `isReady` flag, and stable senders (`submit`, `reset`, `prefill`, …).\n *\n * Sends made before the form loads are queued by the client and flushed on\n * `form_load_complete`, so you can call `prefill(...)` as soon as you have data —\n * no need to gate on readiness yourself.\n *\n * Callbacks are held in a ref and always called fresh, so you do NOT need to memoize\n * them — passing inline arrow functions will not re-subscribe or re-create the client.\n *\n * `react` is an optional peer dependency; importing this entry point requires React 18+.\n *\n * @example\n * function UlcForm({ token, onSubmitted }: { token: string; onSubmitted: (b: SubmissionBody) => void }) {\n * const { iframeProps, isReady, submit } = useCaptelloWebview({\n * embedUrl: {\n * baseUrl: \"https://capture.captello.com\",\n * eventWebAccessToken: token,\n * mode: FormMode.Submit,\n * launcher: LauncherType.EventGenWeb,\n * },\n * onSubmissionBody: (m) => onSubmitted(m.data),\n * });\n * return (\n * <>\n * {!isReady && <Spinner />}\n * <iframe {...iframeProps} title=\"UlcForm\" allow=\"camera; microphone\" />\n * <button onClick={submit}>Submit</button>\n * </>\n * );\n * }\n */\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type IframeHTMLAttributes,\n type ReactElement,\n type ReactNode,\n type Ref,\n type RefCallback,\n} from \"react\";\n\nimport { CaptelloWebview } from \"./client\";\nimport type { CaptelloWebviewOptions, Unsubscribe } from \"./client\";\nimport { buildEmbedUrl } from \"./embed-url\";\nimport type { EmbedUrlOptions } from \"./embed-url\";\nimport { OutboundMessageType } from \"./messages\";\nimport type {\n OutboundMessage,\n OutboundMessageMap,\n PrefillInfoItem,\n SubmissionBody,\n SubmissionPrefill,\n ValidationTarget,\n} from \"./messages\";\n\n/** Per-message-type callback props accepted by {@link useCaptelloWebview}. */\nexport interface CaptelloWebviewCallbacks {\n onFormLoadComplete?: (message: OutboundMessageMap[OutboundMessageType.FormLoadComplete]) => void;\n onFormErrorMessage?: (message: OutboundMessageMap[OutboundMessageType.FormErrorMessage]) => void;\n onSubmissionBody?: (message: OutboundMessageMap[OutboundMessageType.SubmissionBody]) => void;\n onFormSubmitSuccess?: (message: OutboundMessageMap[OutboundMessageType.FormSubmitSuccess]) => void;\n onConnexionsProfileRedirect?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsProfileRedirect]) => void;\n onConnexionsDownloadVcard?: (message: OutboundMessageMap[OutboundMessageType.ConnexionsDownloadVcard]) => void;\n /** Catch-all: called for every outbound message, after the specific handler above. */\n onAnyMessage?: (message: OutboundMessage) => void;\n}\n\n/** Embed-URL config: a base URL plus {@link EmbedUrlOptions}. */\nexport interface EmbedUrlConfig extends EmbedUrlOptions {\n /**\n * The capture **origin**, e.g. `\"https://capture.captello.com\"`. The SDK appends the\n * capture path for you, so the origin, a trailing slash, or the full\n * `…/capture/submission` URL all work — see {@link buildEmbedUrl}.\n */\n baseUrl: string;\n}\n\n/**\n * Options for {@link useCaptelloWebview}.\n *\n * Provide **either** `embedUrl` (the hook builds the URL and derives `targetOrigin`,\n * returning `iframeProps.src`) **or** your own `targetOrigin` (you set the iframe `src`\n * yourself). Plus message callbacks and the usual client options.\n */\nexport interface UseCaptelloWebviewOptions extends CaptelloWebviewOptions, CaptelloWebviewCallbacks {\n /** Build the iframe URL and derive `targetOrigin` from it. Sets `iframeProps.src`. */\n embedUrl?: EmbedUrlConfig;\n}\n\n/** Readiness of the embedded form. */\nexport type CaptelloWebviewStatus = \"loading\" | \"ready\" | \"error\";\n\n/** Props to spread onto the `<iframe>`. `src` is present only when `embedUrl` is given. */\nexport interface CaptelloIframeProps {\n ref: RefCallback<HTMLIFrameElement | null>;\n src?: string;\n}\n\n/** What {@link useCaptelloWebview} returns. */\nexport interface UseCaptelloWebviewResult {\n /** Spread onto your iframe: `<iframe {...iframeProps} />`. Includes `src` if `embedUrl` was given. */\n iframeProps: CaptelloIframeProps;\n /** The iframe ref callback (same as `iframeProps.ref`), if you'd rather set `src` yourself. */\n ref: RefCallback<HTMLIFrameElement | null>;\n /** `true` once the form has reported `form_load_complete`. */\n isReady: boolean;\n /** `\"loading\"` → `\"ready\"`; flips to `\"error\"` if a `form_error_message` arrives. */\n status: CaptelloWebviewStatus;\n /** The live client, or `null` before the iframe mounts. For escape-hatch use. */\n getClient: () => CaptelloWebview | null;\n submit: () => void;\n reset: () => void;\n updateDraft: () => void;\n triggerValidation: (target: ValidationTarget) => void;\n prefill: (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) => void;\n submitAndWait: (timeoutMs?: number) => Promise<SubmissionBody>;\n}\n\nconst CALLBACK_BY_TYPE: Record<OutboundMessageType, keyof CaptelloWebviewCallbacks> = {\n [OutboundMessageType.FormLoadComplete]: \"onFormLoadComplete\",\n [OutboundMessageType.FormErrorMessage]: \"onFormErrorMessage\",\n [OutboundMessageType.SubmissionBody]: \"onSubmissionBody\",\n [OutboundMessageType.FormSubmitSuccess]: \"onFormSubmitSuccess\",\n [OutboundMessageType.ConnexionsProfileRedirect]: \"onConnexionsProfileRedirect\",\n [OutboundMessageType.ConnexionsDownloadVcard]: \"onConnexionsDownloadVcard\",\n};\n\n/**\n * Binds a {@link CaptelloWebview} to an iframe's lifecycle. See the module doc for usage.\n */\nexport function useCaptelloWebview(options: UseCaptelloWebviewOptions): UseCaptelloWebviewResult {\n const { embedUrl, matchSource, queueUntilReady, hostWindow } = options;\n\n // Resolve the URL + the targetOrigin to use. embedUrl wins; otherwise use the\n // explicit targetOrigin. Recompute only when the URL-affecting inputs change.\n const src = embedUrl ? buildEmbedUrl(embedUrl.baseUrl, embedUrl) : undefined;\n const targetOrigin = src ? new URL(src).origin : options.targetOrigin;\n\n // Latest options/callbacks, read fresh inside listeners so callers needn't memoize.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n const clientRef = useRef<CaptelloWebview | null>(null);\n const frameRef = useRef<HTMLIFrameElement | null>(null);\n const teardown = useRef<(() => void) | null>(null);\n\n const [status, setStatus] = useState<CaptelloWebviewStatus>(\"loading\");\n\n const attach = useCallback(\n (frame: HTMLIFrameElement | null) => {\n // Tear down any previous client (ref changed or unmounting).\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n frameRef.current = frame;\n setStatus(\"loading\");\n\n if (!frame) return;\n\n const client = new CaptelloWebview(frame, {\n targetOrigin,\n hostWindow: optionsRef.current.hostWindow,\n matchSource: optionsRef.current.matchSource,\n queueUntilReady: optionsRef.current.queueUntilReady,\n });\n clientRef.current = client;\n\n const offs: Unsubscribe[] = [];\n for (const type of Object.values(OutboundMessageType)) {\n offs.push(\n client.on(type, (message) => {\n if (type === OutboundMessageType.FormLoadComplete) setStatus(\"ready\");\n else if (type === OutboundMessageType.FormErrorMessage) setStatus(\"error\");\n\n const handler = optionsRef.current[CALLBACK_BY_TYPE[type]] as\n | ((m: typeof message) => void)\n | undefined;\n handler?.(message);\n optionsRef.current.onAnyMessage?.(message);\n }),\n );\n }\n\n teardown.current = () => {\n for (const off of offs) off();\n client.destroy();\n };\n },\n // Re-create the client only when connection-level inputs change.\n // Callbacks are read via optionsRef, so they intentionally aren't deps.\n [targetOrigin, matchSource, queueUntilReady, hostWindow],\n );\n\n useEffect(() => {\n if (frameRef.current) attach(frameRef.current);\n return () => {\n teardown.current?.();\n teardown.current = null;\n clientRef.current = null;\n };\n }, [attach]);\n\n const getClient = useCallback(() => clientRef.current, []);\n\n const submit = useCallback(() => clientRef.current?.submit(), []);\n const reset = useCallback(() => clientRef.current?.reset(), []);\n const updateDraft = useCallback(() => clientRef.current?.updateDraft(), []);\n const triggerValidation = useCallback(\n (target: ValidationTarget) => clientRef.current?.triggerValidation(target),\n [],\n );\n const prefill = useCallback(\n (data: { submission?: SubmissionPrefill; info?: PrefillInfoItem[] }) => clientRef.current?.prefill(data),\n [],\n );\n const submitAndWait = useCallback((timeoutMs?: number) => {\n const client = clientRef.current;\n if (!client) {\n return Promise.reject(new Error(\"CaptelloWebview: iframe is not mounted yet.\"));\n }\n return client.submitAndWait(timeoutMs);\n }, []);\n\n const iframeProps: CaptelloIframeProps = src ? { ref: attach, src } : { ref: attach };\n\n return {\n iframeProps,\n ref: attach,\n isReady: status === \"ready\",\n status,\n getClient,\n submit,\n reset,\n updateDraft,\n triggerValidation,\n prefill,\n submitAndWait,\n };\n}\n\n/* ------------------------------------------------------------------ *\n * <CaptelloForm /> — the turnkey component\n * ------------------------------------------------------------------ */\n\n/** Default iframe permissions for a capture form (business-card camera scan, mic, geo). */\nconst DEFAULT_ALLOW = \"camera; microphone; geolocation\";\n\n/** Wrapper is the positioning context for the loading / error overlays. */\nconst WRAPPER_STYLE: CSSProperties = { position: \"relative\" };\n\n/** The iframe fills the wrapper; size the component, not this. */\nconst IFRAME_STYLE: CSSProperties = { display: \"block\", width: \"100%\", height: \"100%\", border: 0 };\n\n/** Centers the `loading` / `error` node over the iframe. */\nconst OVERLAY_STYLE: CSSProperties = {\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n};\n\n/**\n * Imperative handle exposed on {@link CaptelloForm}'s `ref` — the same senders the hook\n * returns, plus the current status and the underlying `<iframe>` node. Lets a parent\n * drive the form (e.g. an external submit button) without lifting state.\n */\nexport interface CaptelloFormHandle\n extends Pick<\n UseCaptelloWebviewResult,\n \"submit\" | \"reset\" | \"updateDraft\" | \"triggerValidation\" | \"prefill\" | \"submitAndWait\" | \"getClient\"\n > {\n /** Current readiness: `\"loading\" | \"ready\" | \"error\"`. */\n readonly status: CaptelloWebviewStatus;\n /** `true` once the form has reported `form_load_complete`. */\n readonly isReady: boolean;\n /** The underlying `<iframe>` DOM node, or `null` before it mounts. */\n getIframe: () => HTMLIFrameElement | null;\n}\n\n/**\n * Props for {@link CaptelloForm}: every {@link useCaptelloWebviewOptions} option (embed\n * config + message callbacks + client options) plus rendering conveniences.\n */\nexport interface CaptelloFormProps extends UseCaptelloWebviewOptions {\n /** `className` for the wrapper element. */\n className?: string;\n /** `style` for the wrapper element — size the form here. The component adds `position: relative`; your values win. */\n style?: CSSProperties;\n /** `id` for the wrapper element. */\n id?: string;\n /**\n * Attributes spread onto the `<iframe>` — `title`, `allow`, `sandbox`, `name`, etc.\n * Defaults: `title=\"Captello form\"`, `allow=\"camera; microphone; geolocation\"`. When you\n * drive the URL yourself (no `embedUrl`), set `src` here.\n */\n iframeProps?: Omit<IframeHTMLAttributes<HTMLIFrameElement>, \"ref\">;\n /** Rendered, centered over the iframe, while it is loading. The iframe stays mounted underneath. */\n loading?: ReactNode;\n /**\n * Rendered, centered over the iframe, when the form reports `form_error_message`.\n * Pass a function to receive the translated, display-ready error text.\n */\n error?: ReactNode | ((message: string | undefined) => ReactNode);\n /**\n * Inline controls rendered after the form. A function receives the live api\n * (status + senders), so you can wire a submit button without a `ref`.\n */\n children?: ReactNode | ((api: UseCaptelloWebviewResult) => ReactNode);\n}\n\nfunction CaptelloFormImpl(props: CaptelloFormProps, ref: Ref<CaptelloFormHandle>): ReactElement {\n const {\n className,\n style,\n id,\n iframeProps,\n loading,\n error,\n children,\n onFormLoadComplete,\n onFormErrorMessage,\n ...options\n } = props;\n\n // The translated error text from the last form_error_message, for the `error` render.\n const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);\n\n const api = useCaptelloWebview({\n ...options,\n // Wrap the two status-bearing callbacks to track the error text, then forward to\n // the caller's handler. The hook reads callbacks fresh, so these inline wrappers\n // don't re-subscribe or re-create the client.\n onFormLoadComplete: (message) => {\n setErrorMessage(undefined);\n onFormLoadComplete?.(message);\n },\n onFormErrorMessage: (message) => {\n setErrorMessage(message.data);\n onFormErrorMessage?.(message);\n },\n });\n\n // Merge the hook's iframe ref with our own node ref so getIframe() can return the DOM node.\n const nodeRef = useRef<HTMLIFrameElement | null>(null);\n const hookRef = api.iframeProps.ref;\n const setIframe = useCallback<RefCallback<HTMLIFrameElement | null>>(\n (node) => {\n nodeRef.current = node;\n hookRef(node);\n },\n [hookRef],\n );\n\n useImperativeHandle(\n ref,\n () => ({\n submit: api.submit,\n reset: api.reset,\n updateDraft: api.updateDraft,\n triggerValidation: api.triggerValidation,\n prefill: api.prefill,\n submitAndWait: api.submitAndWait,\n getClient: api.getClient,\n getIframe: () => nodeRef.current,\n status: api.status,\n isReady: api.isReady,\n }),\n [api],\n );\n\n // embedUrl-derived src wins; otherwise use a src the caller passed via iframeProps.\n const src = api.iframeProps.src ?? iframeProps?.src;\n const showLoading = api.status === \"loading\" && loading != null;\n const showError = api.status === \"error\" && error != null;\n\n return (\n <>\n <div className={className} id={id} style={{ ...WRAPPER_STYLE, ...style }}>\n <iframe\n title=\"Captello form\"\n allow={DEFAULT_ALLOW}\n {...iframeProps}\n ref={setIframe}\n src={src}\n style={{ ...IFRAME_STYLE, ...iframeProps?.style }}\n />\n {showLoading ? <div style={OVERLAY_STYLE}>{loading}</div> : null}\n {showError ? (\n <div style={OVERLAY_STYLE}>{typeof error === \"function\" ? error(errorMessage) : error}</div>\n ) : null}\n </div>\n {typeof children === \"function\" ? children(api) : children}\n </>\n );\n}\n\n/**\n * Turnkey component for embedding a Captello capture form — the shortest path to a\n * working integration. Renders the `<iframe>`, wires {@link useCaptelloWebview} to it,\n * shows your `loading` / `error` overlays, and forwards a {@link CaptelloFormHandle} on\n * `ref` so a parent can `submit()` / `prefill()` without lifting state.\n *\n * Pass `embedUrl` and the form fills its wrapper — size the form via `className` / `style`\n * (an iframe has no intrinsic height). Reach for {@link useCaptelloWebview} instead when\n * you need to own the markup.\n *\n * @example\n * function UlcForm({ token }: { token: string }) {\n * const ref = useRef<CaptelloFormHandle>(null);\n * return (\n * <CaptelloForm\n * ref={ref}\n * style={{ height: 600 }}\n * embedUrl={{\n * baseUrl: \"https://capture.captello.com\",\n * eventWebAccessToken: token,\n * mode: FormMode.Submit,\n * launcher: LauncherType.EventGenWeb,\n * }}\n * onSubmissionBody={(m) => save(m.data)}\n * loading={<Spinner />}\n * error={(msg) => <ErrorBanner>{msg}</ErrorBanner>}\n * >\n * {({ isReady }) => <button disabled={!isReady} onClick={() => ref.current?.submit()}>Submit</button>}\n * </CaptelloForm>\n * );\n * }\n */\nexport const CaptelloForm = forwardRef(CaptelloFormImpl);\nCaptelloForm.displayName = \"CaptelloForm\";\n\nexport { CaptelloWebview, SubmissionError, SubmissionTimeoutError } from \"./client\";\nexport type { Unsubscribe } from \"./client\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@captello/ulc-webview-sdk",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Typed SDK for embedding the Captello capture webview: message protocol, host client, and embed-URL builder.",
5
5
  "author": "Lead Liaison",
6
6
  "license": "MIT",