@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 +117 -36
- package/dist/{client-Dik-Mjid.d.ts → client-DOm9cwoe.d.ts} +23 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/promises.d.ts +2 -2
- package/dist/react.d.ts +81 -43
- package/dist/react.js +90 -2
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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,
|
|
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
|
|
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 `
|
|
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
|
-
`
|
|
165
|
-
|
|
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
|
|
204
|
-
| -------------------- |
|
|
205
|
-
| `submit_form` | `webview.submit()`
|
|
206
|
-
| `reset_form` | `webview.reset()`
|
|
207
|
-
| `update_draft` | `webview.updateDraft()`
|
|
208
|
-
| `trigger_validation` | `webview.triggerValidation(target)`
|
|
209
|
-
| `form_prefill` | `webview.
|
|
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
|
|
280
|
+
### Prefill (`form_prefill`)
|
|
212
281
|
|
|
213
|
-
`
|
|
282
|
+
A single `prefill({ submission?, info? })` carries either or both payloads:
|
|
214
283
|
|
|
215
|
-
- `
|
|
216
|
-
|
|
217
|
-
- `
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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,
|
|
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:
|
|
245
|
-
addRow(
|
|
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
|
-
- `
|
|
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
|
-
`
|
|
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
|
|
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
|
-
`
|
|
492
|
+
`prefill()` instead of re-querying the DOM and stringifying messages by hand.
|
|
412
493
|
|
|
413
|
-
**Prefill notes for migrators.** `
|
|
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. `
|
|
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
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
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]:
|
|
138
|
-
[FormElementType.address]:
|
|
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
|
|
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
|
|
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
|
|
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"]}
|
package/dist/promises.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CaptelloWebviewOptions, S as SubmissionBody, O as OutboundMessageType, a as OutboundMessageMap } from './client-
|
|
2
|
-
export { b as SubmissionError, c as SubmissionTimeoutError } from './client-
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
|
|
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 {
|
|
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