@susilkumar006/widgets-test 1.0.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.
Files changed (107) hide show
  1. package/README.md +445 -0
  2. package/dist/api/FacePeApiClient.d.cts +74 -0
  3. package/dist/api/FacePeApiClient.d.ts +74 -0
  4. package/dist/api/FacePeApiContext.d.cts +8 -0
  5. package/dist/api/FacePeApiContext.d.ts +8 -0
  6. package/dist/api/decoders.d.cts +48 -0
  7. package/dist/api/decoders.d.ts +48 -0
  8. package/dist/api/index.d.cts +8 -0
  9. package/dist/api/index.d.ts +8 -0
  10. package/dist/api/json.d.cts +5 -0
  11. package/dist/api/json.d.ts +5 -0
  12. package/dist/api/useFacePeResource.d.cts +21 -0
  13. package/dist/api/useFacePeResource.d.ts +21 -0
  14. package/dist/components/Avatar/FacePeAvatar.d.cts +23 -0
  15. package/dist/components/Avatar/FacePeAvatar.d.ts +23 -0
  16. package/dist/components/Avatar/engines.d.cts +30 -0
  17. package/dist/components/Avatar/engines.d.ts +30 -0
  18. package/dist/components/Avatar/index.d.cts +1 -0
  19. package/dist/components/Avatar/index.d.ts +1 -0
  20. package/dist/components/Form/FacePeForm.d.cts +19 -0
  21. package/dist/components/Form/FacePeForm.d.ts +19 -0
  22. package/dist/components/Form/index.d.cts +1 -0
  23. package/dist/components/Form/index.d.ts +1 -0
  24. package/dist/components/Picker/FacePePicker.d.cts +22 -0
  25. package/dist/components/Picker/FacePePicker.d.ts +22 -0
  26. package/dist/components/Picker/index.d.cts +1 -0
  27. package/dist/components/Picker/index.d.ts +1 -0
  28. package/dist/components/Placement/FacePeOverlay.d.cts +13 -0
  29. package/dist/components/Placement/FacePeOverlay.d.ts +13 -0
  30. package/dist/components/Placement/FacePePage.d.cts +10 -0
  31. package/dist/components/Placement/FacePePage.d.ts +10 -0
  32. package/dist/components/Placement/index.d.cts +2 -0
  33. package/dist/components/Placement/index.d.ts +2 -0
  34. package/dist/components/Timeline/FacePeTimeline.d.cts +23 -0
  35. package/dist/components/Timeline/FacePeTimeline.d.ts +23 -0
  36. package/dist/components/Timeline/index.d.cts +1 -0
  37. package/dist/components/Timeline/index.d.ts +1 -0
  38. package/dist/components/shared/FacePeErrorBoundary.d.cts +28 -0
  39. package/dist/components/shared/FacePeErrorBoundary.d.ts +28 -0
  40. package/dist/components/shared/surface.d.cts +6 -0
  41. package/dist/components/shared/surface.d.ts +6 -0
  42. package/dist/context/FacePeContext.d.cts +22 -0
  43. package/dist/context/FacePeContext.d.ts +22 -0
  44. package/dist/context/avatar.d.cts +6 -0
  45. package/dist/context/avatar.d.ts +6 -0
  46. package/dist/context/index.d.cts +5 -0
  47. package/dist/context/index.d.ts +5 -0
  48. package/dist/context/navigation.d.cts +10 -0
  49. package/dist/context/navigation.d.ts +10 -0
  50. package/dist/events/FacePeEventEmitter.d.cts +59 -0
  51. package/dist/events/FacePeEventEmitter.d.ts +59 -0
  52. package/dist/events/createCorrelationId.d.cts +9 -0
  53. package/dist/events/createCorrelationId.d.ts +9 -0
  54. package/dist/events/index.d.cts +8 -0
  55. package/dist/events/index.d.ts +8 -0
  56. package/dist/events/telemetry.d.cts +11 -0
  57. package/dist/events/telemetry.d.ts +11 -0
  58. package/dist/events/useFacePeEmitter.d.cts +15 -0
  59. package/dist/events/useFacePeEmitter.d.ts +15 -0
  60. package/dist/index.cjs +1925 -0
  61. package/dist/index.cjs.map +1 -0
  62. package/dist/index.d.cts +26 -0
  63. package/dist/index.d.ts +26 -0
  64. package/dist/index.js +1903 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/schemas/FacePeCustomerPayload.schema.json +31 -0
  67. package/dist/schemas/FacePeError.schema.json +63 -0
  68. package/dist/schemas/FacePeEventMeta.schema.json +53 -0
  69. package/dist/schemas/FacePeFormValues.schema.json +27 -0
  70. package/dist/schemas/FacePeNavigationRequest.schema.json +33 -0
  71. package/dist/schemas/FacePePickerOption.schema.json +34 -0
  72. package/dist/schemas/FacePePickerOptionsPayload.schema.json +49 -0
  73. package/dist/schemas/FacePeTelemetryEvent.schema.json +66 -0
  74. package/dist/schemas/FacePeTimelineItem.schema.json +91 -0
  75. package/dist/schemas/FacePeTimelinePayload.schema.json +109 -0
  76. package/dist/styles.css +887 -0
  77. package/dist/types/api.d.cts +60 -0
  78. package/dist/types/api.d.ts +60 -0
  79. package/dist/types/avatar.d.cts +102 -0
  80. package/dist/types/avatar.d.ts +102 -0
  81. package/dist/types/common.d.cts +42 -0
  82. package/dist/types/common.d.ts +42 -0
  83. package/dist/types/components.d.cts +53 -0
  84. package/dist/types/components.d.ts +53 -0
  85. package/dist/types/config.d.cts +27 -0
  86. package/dist/types/config.d.ts +27 -0
  87. package/dist/types/events.d.cts +85 -0
  88. package/dist/types/events.d.ts +85 -0
  89. package/dist/types/form.d.cts +83 -0
  90. package/dist/types/form.d.ts +83 -0
  91. package/dist/types/index.d.cts +18 -0
  92. package/dist/types/index.d.ts +18 -0
  93. package/dist/types/picker.d.cts +82 -0
  94. package/dist/types/picker.d.ts +82 -0
  95. package/dist/types/placement.d.cts +69 -0
  96. package/dist/types/placement.d.ts +69 -0
  97. package/dist/types/provider.d.cts +75 -0
  98. package/dist/types/provider.d.ts +75 -0
  99. package/dist/types/results.d.cts +28 -0
  100. package/dist/types/results.d.ts +28 -0
  101. package/dist/types/telemetry.d.cts +38 -0
  102. package/dist/types/telemetry.d.ts +38 -0
  103. package/dist/types/timeline.d.cts +103 -0
  104. package/dist/types/timeline.d.ts +103 -0
  105. package/dist/version.d.cts +2 -0
  106. package/dist/version.d.ts +2 -0
  107. package/package.json +71 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1925 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
+ const jsxRuntime = require("react/jsx-runtime");
26
+ const react = require("react");
27
+ function createCorrelationId() {
28
+ if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
29
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
30
+ }
31
+ const SDK_VERSION = "1.0.0";
32
+ function isJsonValue(value) {
33
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
34
+ if (typeof value === "number") return Number.isFinite(value);
35
+ if (Array.isArray(value)) return value.every(isJsonValue);
36
+ if (typeof value === "object") return Object.values(value).every(isJsonValue);
37
+ return false;
38
+ }
39
+ function isJsonObject(value) {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ }
42
+ const CORRELATION_HEADER = "X-Request-Id";
43
+ const SDK_VERSION_HEADER = "X-FacePe-SDK-Version";
44
+ const TOKEN_AUDIENCE = "app-b";
45
+ const DEFAULT_TIMEOUT_MS = 15e3;
46
+ const asJson = (data) => ({ success: true, data });
47
+ function fail(code, message2, info, status2) {
48
+ return {
49
+ success: false,
50
+ error: {
51
+ code,
52
+ message: message2,
53
+ details: {
54
+ method: info.method,
55
+ path: info.path,
56
+ correlationId: info.correlationId,
57
+ ...status2 === void 0 ? {} : { status: status2 }
58
+ }
59
+ }
60
+ };
61
+ }
62
+ function buildUrl(baseUrl, path, query) {
63
+ if (/^[a-z][a-z\d+.-]*:/i.test(path) || path.startsWith("//")) return null;
64
+ try {
65
+ const base = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`, globalThis.location?.href);
66
+ const url = new URL(path.replace(/^\/+/, ""), base);
67
+ for (const [key, value] of Object.entries(query ?? {})) {
68
+ if (value !== null && value !== void 0) url.searchParams.set(key, String(value));
69
+ }
70
+ return url.href;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ class FacePeApiClient {
76
+ constructor(options2) {
77
+ this.options = options2;
78
+ this.lastToken = null;
79
+ }
80
+ /**
81
+ * A full address for an asset the API returned as a path (e.g. an avatar's
82
+ * picture, "/avatars/aria.jpg"): resolved against the API's address, not
83
+ * the host page's. Absolute URLs are returned unchanged.
84
+ */
85
+ assetUrl(url) {
86
+ if (!url) return null;
87
+ if (/^(https?:|data:|blob:)/i.test(url) || url.startsWith("//")) return url;
88
+ const base = this.options.settings().baseUrl;
89
+ if (!base) return url;
90
+ try {
91
+ return new URL(url, new URL(base.endsWith("/") ? base : `${base}/`, globalThis.location?.href)).href;
92
+ } catch {
93
+ return url;
94
+ }
95
+ }
96
+ get(path, options2) {
97
+ return options2?.decode ? this.send("GET", path, void 0, options2.decode, options2) : this.send("GET", path, void 0, asJson, options2);
98
+ }
99
+ post(path, body2, options2) {
100
+ return options2?.decode ? this.send("POST", path, body2, options2.decode, options2) : this.send("POST", path, body2, asJson, options2);
101
+ }
102
+ put(path, body2, options2) {
103
+ return options2?.decode ? this.send("PUT", path, body2, options2.decode, options2) : this.send("PUT", path, body2, asJson, options2);
104
+ }
105
+ patch(path, body2, options2) {
106
+ return options2?.decode ? this.send("PATCH", path, body2, options2.decode, options2) : this.send("PATCH", path, body2, asJson, options2);
107
+ }
108
+ delete(path, options2) {
109
+ return options2?.decode ? this.send("DELETE", path, void 0, options2.decode, options2) : this.send("DELETE", path, void 0, asJson, options2);
110
+ }
111
+ async send(method, path, body2, decode, options2) {
112
+ const info = { method, path, correlationId: createCorrelationId() };
113
+ const started = Date.now();
114
+ const result = await this.perform(body2, decode, options2, info);
115
+ this.options.onTelemetry?.({
116
+ name: "api.request",
117
+ source: "FacePeApiClient",
118
+ correlationId: info.correlationId,
119
+ timestamp: new Date(started).toISOString(),
120
+ sdkVersion: SDK_VERSION,
121
+ attributes: {
122
+ method,
123
+ path,
124
+ outcome: result.success ? "success" : result.error.code,
125
+ durationMs: Date.now() - started,
126
+ ...info.status === void 0 ? {} : { status: info.status }
127
+ }
128
+ });
129
+ return result;
130
+ }
131
+ async perform(body2, decode, options2, info) {
132
+ const { method, path } = info;
133
+ const settings = this.options.settings();
134
+ if (!settings.baseUrl) return fail("API_NOT_CONFIGURED", "No apiBaseUrl was given to FacePeProvider.", info);
135
+ const url = buildUrl(settings.baseUrl, path, options2?.query);
136
+ if (url === null) return fail("INVALID_URL", "The request URL could not be built.", info);
137
+ const timeoutMs = options2?.timeoutMs ?? settings.timeoutMs ?? DEFAULT_TIMEOUT_MS;
138
+ const token = options2?.keepalive && this.lastToken !== null ? { success: true, data: this.lastToken } : await this.token(settings.getToken, false, info);
139
+ if (!token.success) return token;
140
+ let attempt = await this.attempt(url, body2, token.data, timeoutMs, options2?.signal, info, options2?.keepalive);
141
+ if (attempt.kind === "failure") return attempt.failure;
142
+ if (attempt.status === 401) {
143
+ if (!settings.getToken) return fail("UNAUTHORIZED", "The request was not authorised.", info, 401);
144
+ const fresh = await this.token(settings.getToken, true, info);
145
+ if (!fresh.success) return fresh;
146
+ attempt = await this.attempt(url, body2, fresh.data, timeoutMs, options2?.signal, info, options2?.keepalive);
147
+ if (attempt.kind === "failure") return attempt.failure;
148
+ if (attempt.status === 401) {
149
+ this.options.onSessionExpired?.({ method, path });
150
+ return fail("SESSION_EXPIRED", "The session has expired. Sign in again.", info, 401);
151
+ }
152
+ }
153
+ return this.read(attempt.status, attempt.ok, attempt.text, decode, info);
154
+ }
155
+ /** The token to send, or `null` when the provider has no token provider. */
156
+ async token(getToken, forceRefresh, info) {
157
+ if (!getToken) return { success: true, data: null };
158
+ try {
159
+ const token = await getToken({ audience: TOKEN_AUDIENCE, forceRefresh });
160
+ if (typeof token === "string" && token !== "") {
161
+ this.lastToken = token;
162
+ return { success: true, data: token };
163
+ }
164
+ } catch {
165
+ }
166
+ return fail("TOKEN_UNAVAILABLE", "getToken did not return a token.", info);
167
+ }
168
+ /** One HTTP attempt, response body included, under one timeout. */
169
+ async attempt(url, body2, token, timeoutMs, signal, info, keepalive = false) {
170
+ if (typeof globalThis.fetch !== "function") {
171
+ return { kind: "failure", failure: fail("NETWORK_ERROR", "fetch is not available in this environment.", info) };
172
+ }
173
+ if (signal?.aborted) return { kind: "failure", failure: fail("ABORTED", "The request was cancelled.", info) };
174
+ const controller = new AbortController();
175
+ let timedOut = false;
176
+ const timer = setTimeout(() => {
177
+ timedOut = true;
178
+ controller.abort();
179
+ }, timeoutMs);
180
+ const onCallerAbort = () => controller.abort();
181
+ signal?.addEventListener("abort", onCallerAbort, { once: true });
182
+ const headers = {
183
+ Accept: "application/json",
184
+ [CORRELATION_HEADER]: info.correlationId,
185
+ [SDK_VERSION_HEADER]: SDK_VERSION
186
+ };
187
+ if (token !== null) headers.Authorization = `Bearer ${token}`;
188
+ if (body2 !== void 0) headers["Content-Type"] = "application/json";
189
+ try {
190
+ const response = await globalThis.fetch(url, {
191
+ method: info.method,
192
+ headers,
193
+ body: body2 === void 0 ? void 0 : JSON.stringify(body2),
194
+ signal: controller.signal,
195
+ ...keepalive ? { keepalive: true } : {}
196
+ });
197
+ const text2 = await response.text();
198
+ info.status = response.status;
199
+ return { kind: "response", status: response.status, ok: response.ok, text: text2 };
200
+ } catch {
201
+ if (timedOut) return { kind: "failure", failure: fail("TIMEOUT", `No response within ${timeoutMs} ms.`, info) };
202
+ if (signal?.aborted) return { kind: "failure", failure: fail("ABORTED", "The request was cancelled.", info) };
203
+ return { kind: "failure", failure: fail("NETWORK_ERROR", "The API could not be reached.", info) };
204
+ } finally {
205
+ clearTimeout(timer);
206
+ signal?.removeEventListener("abort", onCallerAbort);
207
+ }
208
+ }
209
+ /**
210
+ * Parses the body. The FacePe backend answers `{ success: true, data }` or
211
+ * `{ success: false, error: { code, message } }`; the envelope is unwrapped
212
+ * and its error code kept. Any other JSON body is taken as the data itself.
213
+ */
214
+ read(status2, ok, text2, decode, info) {
215
+ let parsed;
216
+ if (text2.trim() !== "") {
217
+ try {
218
+ const raw = JSON.parse(text2);
219
+ parsed = isJsonValue(raw) ? raw : void 0;
220
+ } catch {
221
+ parsed = void 0;
222
+ }
223
+ if (parsed === void 0 && ok) return fail("INVALID_RESPONSE", "The API returned a response that is not JSON.", info, status2);
224
+ }
225
+ if (!ok) {
226
+ const error2 = isJsonObject(parsed) ? parsed.error : void 0;
227
+ if (isJsonObject(error2) && typeof error2.code === "string" && typeof error2.message === "string") {
228
+ return fail(error2.code, error2.message, info, status2);
229
+ }
230
+ return fail("HTTP_ERROR", `The API answered with status ${status2}.`, info, status2);
231
+ }
232
+ const data = isJsonObject(parsed) && parsed.success === true && "data" in parsed ? parsed.data : parsed;
233
+ return decode(data ?? null);
234
+ }
235
+ }
236
+ const FacePeApiContext = react.createContext(null);
237
+ FacePeApiContext.displayName = "FacePeApiContext";
238
+ function useFacePeApi() {
239
+ const client = react.useContext(FacePeApiContext);
240
+ if (client === null) throw new Error("useFacePeApi must be used within a FacePeProvider");
241
+ return client;
242
+ }
243
+ const STATUSES = ["completed", "current", "pending", "error"];
244
+ const invalid$1 = (what) => ({
245
+ success: false,
246
+ error: { code: "INVALID_PAYLOAD", message: `The gateway returned an invalid ${what}.` }
247
+ });
248
+ const str = (o, key) => typeof o[key] === "string" ? o[key] : void 0;
249
+ function listIn(data, key) {
250
+ if (!isJsonObject(data)) return void 0;
251
+ const list2 = data[key];
252
+ return Array.isArray(list2) ? list2 : void 0;
253
+ }
254
+ function timelineItem(value) {
255
+ if (!isJsonObject(value)) return void 0;
256
+ const id = str(value, "id");
257
+ const title2 = str(value, "title");
258
+ const timestamp = str(value, "timestamp");
259
+ const status2 = str(value, "status");
260
+ if (id === void 0 || title2 === void 0 || timestamp === void 0) return void 0;
261
+ if (status2 === void 0 || !STATUSES.includes(status2)) return void 0;
262
+ const description2 = str(value, "description");
263
+ return { id, title: title2, timestamp, status: status2, ...description2 === void 0 ? {} : { description: description2 } };
264
+ }
265
+ const decodeTimeline = (data) => {
266
+ const list2 = listIn(data, "items");
267
+ if (list2 === void 0) return invalid$1("timeline");
268
+ const items = list2.map(timelineItem);
269
+ if (items.some((item2) => item2 === void 0)) return invalid$1("timeline item");
270
+ return { success: true, data: items };
271
+ };
272
+ function pickerOption(value) {
273
+ if (!isJsonObject(value)) return void 0;
274
+ const optionValue = str(value, "value");
275
+ const label2 = str(value, "label");
276
+ if (optionValue === void 0 || label2 === void 0) return void 0;
277
+ const description2 = str(value, "description");
278
+ return {
279
+ value: optionValue,
280
+ label: label2,
281
+ ...description2 === void 0 ? {} : { description: description2 },
282
+ ...value.disabled === true ? { disabled: true } : {}
283
+ };
284
+ }
285
+ const decodePickerOptions = (data) => {
286
+ const list2 = listIn(data, "options");
287
+ if (list2 === void 0) return invalid$1("option list");
288
+ const options2 = list2.map(pickerOption);
289
+ if (options2.some((option2) => option2 === void 0)) return invalid$1("option");
290
+ return { success: true, data: options2 };
291
+ };
292
+ const decodeFormValues = (data) => {
293
+ if (!isJsonObject(data)) return invalid$1("customer");
294
+ const name = str(data, "name");
295
+ const email = str(data, "email");
296
+ const notes = str(data, "notes");
297
+ if (name === void 0 || email === void 0 || notes === void 0) return invalid$1("customer");
298
+ return { success: true, data: { name, email, notes } };
299
+ };
300
+ function avatarInfo(value) {
301
+ if (value === void 0 || !isJsonObject(value)) return void 0;
302
+ const id = str(value, "id");
303
+ const name = str(value, "name");
304
+ if (id === void 0 || name === void 0) return void 0;
305
+ const languages = Array.isArray(value.languages) ? value.languages.filter((l) => typeof l === "string") : [];
306
+ return {
307
+ id,
308
+ name,
309
+ gender: str(value, "gender") ?? "",
310
+ previewUrl: str(value, "previewUrl") ?? null,
311
+ description: str(value, "description") ?? null,
312
+ languages
313
+ };
314
+ }
315
+ const decodeAvatar = (data) => {
316
+ const avatar2 = isJsonObject(data) ? avatarInfo(data.avatar) : void 0;
317
+ return avatar2 ? { success: true, data: avatar2 } : invalid$1("avatar");
318
+ };
319
+ const decodeAvatarList = (data) => {
320
+ if (!isJsonObject(data) || !Array.isArray(data.avatars)) return invalid$1("avatars");
321
+ return { success: true, data: data.avatars.map(avatarInfo).filter((a) => a !== void 0) };
322
+ };
323
+ const decodeAvatarConnection = (data) => {
324
+ if (!isJsonObject(data)) return invalid$1("avatar session");
325
+ const avatar2 = avatarInfo(data.avatar);
326
+ const usageId = typeof data.usageId === "string" ? data.usageId : null;
327
+ const c = data.connection;
328
+ if (!avatar2 || !isJsonObject(c) || typeof c.token !== "string" || c.token === "") return invalid$1("avatar session");
329
+ if (c.type === "stream") {
330
+ const greeting = typeof c.greeting === "string" ? c.greeting : null;
331
+ return { success: true, data: { type: "stream", usageId, avatar: avatar2, token: c.token, greeting } };
332
+ }
333
+ if (c.type === "room" && typeof c.url === "string" && c.url !== "") {
334
+ return { success: true, data: { type: "room", usageId, avatar: avatar2, url: c.url, token: c.token } };
335
+ }
336
+ return invalid$1("avatar session");
337
+ };
338
+ const gatewayPath = {
339
+ avatars: "/avatars",
340
+ avatar: (avatarId) => `/avatars/${encodeURIComponent(avatarId)}`,
341
+ avatarSessions: (avatarId) => `/avatars/${encodeURIComponent(avatarId)}/sessions`,
342
+ endAvatarSession: (usageId) => `/avatar-sessions/${encodeURIComponent(usageId)}/end`,
343
+ timeline: (orderId) => `/orders/${encodeURIComponent(orderId)}/timeline`,
344
+ pickerOptions: (locationId) => `/locations/${encodeURIComponent(locationId)}/categories`,
345
+ customer: (orderId) => `/orders/${encodeURIComponent(orderId)}/customer`
346
+ };
347
+ const IDLE = { status: "idle" };
348
+ function useFacePeResource(path, decode) {
349
+ const api = useFacePeApi();
350
+ const [state, setState] = react.useState({
351
+ path: null,
352
+ resource: IDLE
353
+ });
354
+ react.useEffect(() => {
355
+ if (path === null) return;
356
+ const controller = new AbortController();
357
+ setState({ path, resource: { status: "loading" } });
358
+ void api.get(path, { decode, signal: controller.signal }).then((result) => {
359
+ if (controller.signal.aborted) return;
360
+ setState({
361
+ path,
362
+ resource: result.success ? { status: "ready", data: result.data } : { status: "failed", error: result.error }
363
+ });
364
+ });
365
+ return () => controller.abort();
366
+ }, [api, path]);
367
+ if (path === null) return IDLE;
368
+ return state.path === path ? state.resource : { status: "loading" };
369
+ }
370
+ const noop = () => void 0;
371
+ const FacePeTelemetryContext = react.createContext(noop);
372
+ FacePeTelemetryContext.displayName = "FacePeTelemetryContext";
373
+ function useFacePeTelemetryReporter() {
374
+ return react.useContext(FacePeTelemetryContext);
375
+ }
376
+ function telemetryOf(event) {
377
+ const { meta: meta2 } = event;
378
+ const payload = event.payload;
379
+ const code = event.type === "error" && typeof payload?.code === "string" ? payload.code : void 0;
380
+ return {
381
+ name: event.type,
382
+ source: meta2.source,
383
+ correlationId: meta2.correlationId,
384
+ timestamp: meta2.timestamp,
385
+ sdkVersion: meta2.sdkVersion,
386
+ ...meta2.entityId === void 0 ? {} : { entityId: meta2.entityId },
387
+ ...code === void 0 ? {} : { attributes: { code } }
388
+ };
389
+ }
390
+ function reportListenerError(error2, source, type) {
391
+ if (typeof globalThis.reportError === "function") {
392
+ globalThis.reportError(error2);
393
+ return;
394
+ }
395
+ console.error(`[FacePe] A "${type}" listener of ${source} threw:`, error2);
396
+ }
397
+ class FacePeEventEmitter {
398
+ /**
399
+ * @param source - the emitting component's name, stamped on every event's meta.
400
+ * @param context - read on every emit, e.g. the component's current `entityId`.
401
+ * @param report - receives the telemetry record of every emitted event.
402
+ */
403
+ constructor(source, context = () => ({}), report = () => void 0) {
404
+ this.source = source;
405
+ this.context = context;
406
+ this.report = report;
407
+ this.listeners = {};
408
+ }
409
+ /**
410
+ * Subscribes a listener to one event type. The same function subscribed
411
+ * twice is held once. Returns the matching unsubscribe function.
412
+ */
413
+ on(type, listener) {
414
+ const set = this.listeners[type] ?? /* @__PURE__ */ new Set();
415
+ set.add(listener);
416
+ this.listeners[type] = set;
417
+ return () => this.off(type, listener);
418
+ }
419
+ /** Unsubscribes one listener; others on the same event are untouched. */
420
+ off(type, listener) {
421
+ const set = this.listeners[type];
422
+ if (set === void 0) return;
423
+ set.delete(listener);
424
+ if (set.size === 0) delete this.listeners[type];
425
+ }
426
+ /** How many listeners an event type has (for cleanup checks). */
427
+ listenerCount(type) {
428
+ return this.listeners[type]?.size ?? 0;
429
+ }
430
+ /** Removes every listener. */
431
+ clear() {
432
+ this.listeners = {};
433
+ }
434
+ /**
435
+ * Wraps `payload` in the standard envelope and delivers it to every
436
+ * listener of `type`, in subscription order. A listener that throws is
437
+ * reported and skipped; the rest still run. The event is also reported to
438
+ * telemetry (without its payload). Returns the event delivered.
439
+ *
440
+ * @param correlationId - pass one to tie several events to one action
441
+ * (e.g. a submit attempt's `submit` or `error`); a new one otherwise.
442
+ */
443
+ emit(type, payload, correlationId = createCorrelationId()) {
444
+ const { entityId } = this.context();
445
+ const event = {
446
+ type,
447
+ payload,
448
+ meta: {
449
+ schemaVersion: 1,
450
+ correlationId,
451
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
452
+ sdkVersion: SDK_VERSION,
453
+ source: this.source,
454
+ ...entityId === void 0 ? {} : { entityId }
455
+ }
456
+ };
457
+ this.dispatch(event);
458
+ this.report(telemetryOf(event));
459
+ return event;
460
+ }
461
+ /**
462
+ * Delivers an event that already has its envelope — e.g. a component's
463
+ * `navigate` relayed to the provider, so both receive the very same event.
464
+ * Same isolation as `emit`. Not reported to telemetry: `emit` already did.
465
+ */
466
+ dispatch(event) {
467
+ const set = this.listeners[event.type];
468
+ if (set === void 0) return;
469
+ for (const listener of [...set]) {
470
+ try {
471
+ listener(event);
472
+ } catch (error2) {
473
+ reportListenerError(error2, this.source, event.type);
474
+ }
475
+ }
476
+ }
477
+ }
478
+ function useFacePeEmitter(source, context, report) {
479
+ const fromProvider = useFacePeTelemetryReporter();
480
+ const [emitter] = react.useState(() => new FacePeEventEmitter(source, context, report ?? fromProvider));
481
+ return emitter;
482
+ }
483
+ const FacePeAvatarIdContext = react.createContext(void 0);
484
+ FacePeAvatarIdContext.displayName = "FacePeAvatarIdContext";
485
+ function useFacePeAvatarId() {
486
+ return react.useContext(FacePeAvatarIdContext);
487
+ }
488
+ const FacePeNavigationContext = react.createContext(() => void 0);
489
+ FacePeNavigationContext.displayName = "FacePeNavigationContext";
490
+ function useFacePeNavigationRelay() {
491
+ return react.useContext(FacePeNavigationContext);
492
+ }
493
+ const NO_CONFIG = Object.freeze({});
494
+ function themeStyle(theme, colorScheme) {
495
+ const style = { display: "contents" };
496
+ if (colorScheme !== void 0) style.colorScheme = colorScheme;
497
+ const tokens2 = [
498
+ [theme.colorSurface, "--fp-color-surface"],
499
+ [theme.colorSurfaceMuted, "--fp-color-surface-muted"],
500
+ [theme.colorText, "--fp-color-text"],
501
+ [theme.colorMuted, "--fp-color-muted"],
502
+ [theme.colorBorder, "--fp-color-border"],
503
+ [theme.colorAccent, "--fp-color-accent"],
504
+ [theme.colorOnAccent, "--fp-color-on-accent"],
505
+ [theme.colorDanger, "--fp-color-danger"],
506
+ [theme.colorSuccess, "--fp-color-success"],
507
+ [theme.fontFamily, "--fp-font-family"],
508
+ [theme.fontSize, "--fp-font-size"],
509
+ [theme.spacing, "--fp-spacing"],
510
+ [theme.radius, "--fp-radius"],
511
+ [theme.shadow, "--fp-shadow"]
512
+ ];
513
+ for (const [value, variable] of tokens2) {
514
+ if (value !== void 0) style[variable] = value;
515
+ }
516
+ return style;
517
+ }
518
+ const FacePeContext = react.createContext(null);
519
+ FacePeContext.displayName = "FacePeContext";
520
+ function FacePeProvider({ config, children, ...settings }) {
521
+ const value = react.useMemo(() => ({ config: config ?? NO_CONFIG }), [config]);
522
+ const settingsRef = react.useRef(settings);
523
+ settingsRef.current = settings;
524
+ const reportTelemetry = react.useCallback((record) => {
525
+ const sink = settingsRef.current.telemetry;
526
+ if (sink === void 0) return;
527
+ try {
528
+ sink.track(record);
529
+ } catch (error2) {
530
+ if (typeof globalThis.reportError === "function") globalThis.reportError(error2);
531
+ else console.error("[FacePe] The telemetry sink threw:", error2);
532
+ }
533
+ }, []);
534
+ const events = useFacePeEmitter("FacePeProvider", void 0, reportTelemetry);
535
+ react.useEffect(() => {
536
+ const unsubscribe = [
537
+ events.on("sessionExpired", (e) => settingsRef.current.onSessionExpired?.(e)),
538
+ events.on("navigate", (e) => settingsRef.current.onNavigate?.(e))
539
+ ];
540
+ return () => unsubscribe.forEach((off) => off());
541
+ }, [events]);
542
+ const relayNavigation = react.useCallback((event) => events.dispatch(event), [events]);
543
+ const [api] = react.useState(
544
+ () => new FacePeApiClient({
545
+ settings: () => ({
546
+ baseUrl: settingsRef.current.apiBaseUrl,
547
+ getToken: settingsRef.current.getToken,
548
+ timeoutMs: settingsRef.current.requestTimeoutMs
549
+ }),
550
+ onSessionExpired: (payload) => {
551
+ events.emit("sessionExpired", payload);
552
+ },
553
+ onTelemetry: reportTelemetry
554
+ })
555
+ );
556
+ const { theme, darkTheme, colorScheme } = settings;
557
+ const style = react.useMemo(() => {
558
+ if (theme === void 0 && darkTheme === void 0 && colorScheme === void 0) return void 0;
559
+ const active = colorScheme === "dark" ? { ...theme, ...darkTheme } : { ...theme };
560
+ return themeStyle(active, colorScheme);
561
+ }, [theme, darkTheme, colorScheme]);
562
+ return /* @__PURE__ */ jsxRuntime.jsx(FacePeContext.Provider, { value, children: /* @__PURE__ */ jsxRuntime.jsx(FacePeApiContext.Provider, { value: api, children: /* @__PURE__ */ jsxRuntime.jsx(FacePeTelemetryContext.Provider, { value: reportTelemetry, children: /* @__PURE__ */ jsxRuntime.jsx(FacePeNavigationContext.Provider, { value: relayNavigation, children: /* @__PURE__ */ jsxRuntime.jsx(FacePeAvatarIdContext.Provider, { value: settings.avatarId, children: style === void 0 ? children : /* @__PURE__ */ jsxRuntime.jsx("div", { style, children }) }) }) }) }) });
563
+ }
564
+ function useFacePe() {
565
+ const value = react.useContext(FacePeContext);
566
+ if (value === null) {
567
+ throw new Error("useFacePe must be used within a FacePeProvider");
568
+ }
569
+ return value;
570
+ }
571
+ const root = "_root_ho4ob_11";
572
+ const subtle = "_subtle_ho4ob_41";
573
+ const framed = "_framed_ho4ob_45";
574
+ const elevated = "_elevated_ho4ob_46";
575
+ const small = "_small_ho4ob_61";
576
+ const medium = "_medium_ho4ob_65";
577
+ const large = "_large_ho4ob_69";
578
+ const tokens = {
579
+ root,
580
+ subtle,
581
+ framed,
582
+ elevated,
583
+ small,
584
+ medium,
585
+ large
586
+ };
587
+ const EMPHASIS = {
588
+ subtle: tokens.subtle,
589
+ default: tokens.framed,
590
+ strong: tokens.elevated
591
+ };
592
+ const SIZE = {
593
+ small: tokens.small,
594
+ medium: tokens.medium,
595
+ large: tokens.large
596
+ };
597
+ function surfaceClass(emphasis = "default", size = "medium") {
598
+ return `${tokens.root} ${EMPHASIS[emphasis]} ${SIZE[size]}`;
599
+ }
600
+ const fallback = "_fallback_1rdem_3";
601
+ const styles$5 = {
602
+ fallback
603
+ };
604
+ const RENDER_FAILED = { code: "RENDER_FAILED", message: "The component failed to render." };
605
+ class FacePeErrorBoundary extends react.Component {
606
+ constructor(props) {
607
+ super(props);
608
+ this.state = { error: null };
609
+ this.events = new FacePeEventEmitter(this.props.source, () => ({
610
+ entityId: this.props.entityId
611
+ }));
612
+ this.events.on("error", (event) => this.props.onError?.(event));
613
+ }
614
+ static getDerivedStateFromError() {
615
+ return { error: RENDER_FAILED };
616
+ }
617
+ componentDidCatch() {
618
+ this.events.emit("error", RENDER_FAILED);
619
+ }
620
+ render() {
621
+ const { error: error2 } = this.state;
622
+ if (error2 === null) return this.props.children;
623
+ const { fallback: fallback2 } = this.props;
624
+ if (fallback2 !== void 0) return typeof fallback2 === "function" ? fallback2({ error: error2 }) : fallback2;
625
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", className: `${surfaceClass()} ${styles$5.fallback}`, children: "This content could not be displayed." });
626
+ }
627
+ }
628
+ const form = "_form_1clyx_5";
629
+ const comfortable$3 = "_comfortable_1clyx_12";
630
+ const compact$3 = "_compact_1clyx_18";
631
+ const field = "_field_1clyx_24";
632
+ const stacked = "_stacked_1clyx_32";
633
+ const inline = "_inline_1clyx_38";
634
+ const half = "_half_1clyx_48";
635
+ const label$1 = "_label_1clyx_53";
636
+ const control = "_control_1clyx_58";
637
+ const invalid = "_invalid_1clyx_99";
638
+ const error$1 = "_error_1clyx_105";
639
+ const formError = "_formError_1clyx_111";
640
+ const actions = "_actions_1clyx_120";
641
+ const primary$1 = "_primary_1clyx_128";
642
+ const secondary$1 = "_secondary_1clyx_129";
643
+ const styles$4 = {
644
+ form,
645
+ comfortable: comfortable$3,
646
+ compact: compact$3,
647
+ field,
648
+ stacked,
649
+ inline,
650
+ half,
651
+ label: label$1,
652
+ control,
653
+ invalid,
654
+ error: error$1,
655
+ formError,
656
+ actions,
657
+ primary: primary$1,
658
+ secondary: secondary$1
659
+ };
660
+ const SOURCE$3 = "FacePeForm";
661
+ const FIELDS = ["name", "email", "notes"];
662
+ const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
663
+ function initialValuesOf(initial) {
664
+ return { name: initial?.name ?? "", email: initial?.email ?? "", notes: initial?.notes ?? "" };
665
+ }
666
+ function trimmed(values) {
667
+ return { name: values.name.trim(), email: values.email.trim(), notes: values.notes.trim() };
668
+ }
669
+ function validateValues(values) {
670
+ const errors = {};
671
+ if (!values.name) errors.name = "Name is required.";
672
+ if (!values.email) errors.email = "Email is required.";
673
+ else if (!EMAIL.test(values.email)) errors.email = "Enter a valid email address.";
674
+ return errors;
675
+ }
676
+ function failure$3(code, message2, details) {
677
+ return { success: false, error: details === void 0 ? { code, message: message2 } : { code, message: message2, details } };
678
+ }
679
+ const FacePeForm = react.forwardRef(function FacePeForm2(props, ref) {
680
+ const { config } = useFacePe();
681
+ return /* @__PURE__ */ jsxRuntime.jsx(
682
+ FacePeErrorBoundary,
683
+ {
684
+ source: SOURCE$3,
685
+ entityId: props.entityId ?? config.entityId,
686
+ onError: props.onError,
687
+ fallback: props.slots?.fallback,
688
+ children: /* @__PURE__ */ jsxRuntime.jsx(FacePeFormContent, { ...props, ref })
689
+ }
690
+ );
691
+ });
692
+ FacePeForm.displayName = "FacePeForm";
693
+ const FacePeFormContent = react.forwardRef(function FacePeFormContent2(props, ref) {
694
+ const { config } = useFacePe();
695
+ const mode = props.mode ?? config.mode ?? "view";
696
+ const entityId = props.entityId ?? config.entityId;
697
+ const density = props.density ?? config.density ?? "comfortable";
698
+ const locale = props.locale ?? config.locale;
699
+ const readOnly = mode === "view";
700
+ const byReference = props.initialValues === void 0 && entityId !== void 0;
701
+ const resource = useFacePeResource(byReference ? gatewayPath.customer(entityId) : null, decodeFormValues);
702
+ const loading = resource.status === "loading";
703
+ const loaded = resource.status === "ready" ? resource.data : null;
704
+ const loadError = resource.status === "failed" ? resource.error : null;
705
+ const api = useFacePeApi();
706
+ const [values, setValues] = react.useState(() => initialValuesOf(props.initialValues));
707
+ const [errors, setErrors] = react.useState({});
708
+ const [submitting, setSubmitting] = react.useState(false);
709
+ const [formError2, setFormError] = react.useState(null);
710
+ const propsRef = react.useRef(props);
711
+ propsRef.current = props;
712
+ const entityRef = react.useRef(entityId);
713
+ entityRef.current = entityId;
714
+ const valuesRef = react.useRef(values);
715
+ valuesRef.current = values;
716
+ const submittingRef = react.useRef(false);
717
+ const baselineRef = react.useRef(null);
718
+ const byReferenceRef = react.useRef(byReference);
719
+ byReferenceRef.current = byReference;
720
+ const readyRef = react.useRef(false);
721
+ const fieldRefs = react.useRef({});
722
+ const id = react.useId();
723
+ const fieldId = (field2) => `${id}-${field2}`;
724
+ const errorId = (field2) => `${id}-${field2}-error`;
725
+ const events = useFacePeEmitter(SOURCE$3, () => ({ entityId: entityRef.current }));
726
+ react.useEffect(() => {
727
+ const unsubscribe = [
728
+ events.on("ready", (e) => propsRef.current.onReady?.(e)),
729
+ events.on("submit", (e) => propsRef.current.onSubmit?.(e)),
730
+ events.on("cancel", (e) => propsRef.current.onCancel?.(e)),
731
+ events.on("error", (e) => propsRef.current.onError?.(e))
732
+ ];
733
+ return () => unsubscribe.forEach((off) => off());
734
+ }, [events]);
735
+ react.useEffect(() => {
736
+ if (readyRef.current) return;
737
+ readyRef.current = true;
738
+ events.emit("ready", null);
739
+ }, [events]);
740
+ react.useEffect(() => {
741
+ if (loaded === null) return;
742
+ baselineRef.current = loaded;
743
+ setValues(loaded);
744
+ setErrors({});
745
+ }, [loaded]);
746
+ react.useEffect(() => {
747
+ if (loadError !== null) events.emit("error", loadError);
748
+ }, [loadError, events]);
749
+ const check = react.useCallback(() => {
750
+ const clean = trimmed(valuesRef.current);
751
+ const found = validateValues(clean);
752
+ setErrors(found);
753
+ const firstInvalid = FIELDS.find((field2) => found[field2] !== void 0);
754
+ if (firstInvalid === void 0) return { success: true, data: clean };
755
+ fieldRefs.current[firstInvalid]?.focus();
756
+ const fields = {};
757
+ for (const field2 of FIELDS) {
758
+ const message2 = found[field2];
759
+ if (message2 !== void 0) fields[field2] = message2;
760
+ }
761
+ return failure$3("VALIDATION_FAILED", "One or more fields are invalid.", { fields });
762
+ }, []);
763
+ const submit = react.useCallback(async () => {
764
+ if (readOnly) return failure$3("READ_ONLY", "The form is in view mode.");
765
+ if (submittingRef.current) return failure$3("SUBMIT_IN_PROGRESS", "A submission is already in progress.");
766
+ const checked = check();
767
+ if (!checked.success) return checked;
768
+ const correlationId = createCorrelationId();
769
+ submittingRef.current = true;
770
+ setSubmitting(true);
771
+ setFormError(null);
772
+ try {
773
+ const action = propsRef.current.submitAction;
774
+ let outcome = { success: true, data: void 0 };
775
+ let submitted = checked.data;
776
+ if (action) {
777
+ try {
778
+ outcome = await action(checked.data);
779
+ } catch (err) {
780
+ outcome = failure$3("SUBMIT_FAILED", err instanceof Error ? err.message : "Submission failed.");
781
+ }
782
+ } else if (byReferenceRef.current && entityRef.current !== void 0) {
783
+ const { name, email, notes } = checked.data;
784
+ const saved = await api.put(gatewayPath.customer(entityRef.current), { name, email, notes }, { decode: decodeFormValues });
785
+ if (saved.success) {
786
+ submitted = saved.data;
787
+ baselineRef.current = saved.data;
788
+ } else {
789
+ outcome = saved;
790
+ }
791
+ }
792
+ if (!outcome.success) {
793
+ setFormError("Submission failed. Please try again.");
794
+ events.emit("error", outcome.error, correlationId);
795
+ return outcome;
796
+ }
797
+ events.emit("submit", submitted, correlationId);
798
+ return { success: true, data: submitted };
799
+ } finally {
800
+ submittingRef.current = false;
801
+ setSubmitting(false);
802
+ }
803
+ }, [check, readOnly, events, api]);
804
+ const validate = react.useCallback(async () => check(), [check]);
805
+ const reset = react.useCallback(async () => {
806
+ setValues(baselineRef.current ?? initialValuesOf(propsRef.current.initialValues));
807
+ setErrors({});
808
+ setFormError(null);
809
+ return { success: true, data: void 0 };
810
+ }, []);
811
+ const focus = react.useCallback(async () => {
812
+ const first = fieldRefs.current[FIELDS[0]];
813
+ if (!first) return failure$3("NOTHING_TO_FOCUS", "The form has no field to focus.");
814
+ first.focus();
815
+ return { success: true, data: void 0 };
816
+ }, []);
817
+ react.useImperativeHandle(ref, () => ({ validate, reset, focus }), [validate, reset, focus]);
818
+ const handleSubmit = (event) => {
819
+ event.preventDefault();
820
+ void submit();
821
+ };
822
+ const handleCancel = () => {
823
+ events.emit("cancel", null);
824
+ };
825
+ const handleChange = (field2) => (event) => {
826
+ const value = event.target.value;
827
+ setValues((prev) => ({ ...prev, [field2]: value }));
828
+ if (errors[field2] !== void 0) setErrors((prev) => ({ ...prev, [field2]: void 0 }));
829
+ };
830
+ const fieldProps = (field2) => {
831
+ const hasError = errors[field2] !== void 0;
832
+ return {
833
+ id: fieldId(field2),
834
+ name: field2,
835
+ value: values[field2],
836
+ onChange: handleChange(field2),
837
+ readOnly,
838
+ disabled: submitting || loading,
839
+ "aria-invalid": hasError,
840
+ "aria-describedby": hasError ? errorId(field2) : void 0,
841
+ className: hasError ? `${styles$4.control} ${styles$4.invalid}` : styles$4.control,
842
+ ref: (element) => {
843
+ fieldRefs.current[field2] = element;
844
+ }
845
+ };
846
+ };
847
+ const fieldError = (field2) => errors[field2] === void 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("p", { id: errorId(field2), className: styles$4.error, children: errors[field2] });
848
+ const actionsSlot = props.slots?.actions;
849
+ return /* @__PURE__ */ jsxRuntime.jsxs(
850
+ "form",
851
+ {
852
+ className: [
853
+ surfaceClass(props.emphasis, props.size),
854
+ styles$4.form,
855
+ density === "compact" ? styles$4.compact : styles$4.comfortable,
856
+ props.variant === "inline" ? styles$4.inline : styles$4.stacked
857
+ ].join(" "),
858
+ lang: locale,
859
+ noValidate: true,
860
+ "aria-busy": submitting || loading,
861
+ onSubmit: handleSubmit,
862
+ children: [
863
+ props.slots?.header,
864
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles$4.field} ${styles$4.half}`, children: [
865
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId("name"), className: styles$4.label, children: [
866
+ "Name ",
867
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "*" })
868
+ ] }),
869
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "text", autoComplete: "name", required: true, ...fieldProps("name") }),
870
+ fieldError("name")
871
+ ] }),
872
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${styles$4.field} ${styles$4.half}`, children: [
873
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId("email"), className: styles$4.label, children: [
874
+ "Email ",
875
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "*" })
876
+ ] }),
877
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "email", autoComplete: "email", inputMode: "email", required: true, ...fieldProps("email") }),
878
+ fieldError("email")
879
+ ] }),
880
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles$4.field, children: [
881
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: fieldId("notes"), className: styles$4.label, children: "Notes" }),
882
+ /* @__PURE__ */ jsxRuntime.jsx("textarea", { rows: 3, ...fieldProps("notes") })
883
+ ] }),
884
+ loadError !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: styles$4.formError, children: "The details could not be loaded." }),
885
+ formError2 !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: styles$4.formError, children: formError2 }),
886
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles$4.actions, children: actionsSlot !== void 0 ? typeof actionsSlot === "function" ? actionsSlot({ submit: () => void submit(), cancel: handleCancel, submitting, readOnly }) : actionsSlot : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
887
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: styles$4.secondary, onClick: handleCancel, disabled: submitting, children: "Cancel" }),
888
+ !readOnly && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: styles$4.primary, disabled: submitting || loading, children: submitting ? "Submitting…" : "Submit" })
889
+ ] }) }),
890
+ props.slots?.footer
891
+ ]
892
+ }
893
+ );
894
+ });
895
+ FacePeFormContent.displayName = "FacePeFormContent";
896
+ const picker = "_picker_2easv_11";
897
+ const legend = "_legend_2easv_27";
898
+ const comfortable$2 = "_comfortable_2easv_51";
899
+ const option = "_option_2easv_69";
900
+ const compact$2 = "_compact_2easv_77";
901
+ const options = "_options_2easv_103";
902
+ const list$1 = "_list_2easv_117";
903
+ const grid = "_grid_2easv_131";
904
+ const selected$1 = "_selected_2easv_169";
905
+ const disabled = "_disabled_2easv_181";
906
+ const input = "_input_2easv_199";
907
+ const text = "_text_2easv_227";
908
+ const label = "_label_2easv_241";
909
+ const description$1 = "_description_2easv_259";
910
+ const empty = "_empty_2easv_271";
911
+ const styles$3 = {
912
+ picker,
913
+ legend,
914
+ comfortable: comfortable$2,
915
+ option,
916
+ compact: compact$2,
917
+ options,
918
+ list: list$1,
919
+ grid,
920
+ selected: selected$1,
921
+ disabled,
922
+ input,
923
+ text,
924
+ label,
925
+ description: description$1,
926
+ empty
927
+ };
928
+ const SOURCE$2 = "FacePePicker";
929
+ const DESCRIPTIONS_FLAG = "picker.descriptions";
930
+ const NO_OPTIONS = [];
931
+ function failure$2(code, message2) {
932
+ return { success: false, error: { code, message: message2 } };
933
+ }
934
+ function resolveDefault(defaultValue, options2) {
935
+ if (defaultValue === void 0) return { value: null, error: null };
936
+ const option2 = options2.find((o) => o.value === defaultValue);
937
+ if (option2 === void 0) {
938
+ return { value: null, error: { code: "INVALID_DEFAULT_VALUE", message: `No option has the value "${defaultValue}".` } };
939
+ }
940
+ if (option2.disabled === true) {
941
+ return { value: null, error: { code: "INVALID_DEFAULT_VALUE", message: `Option "${defaultValue}" is disabled.` } };
942
+ }
943
+ return { value: option2.value, error: null };
944
+ }
945
+ const FacePePicker = react.forwardRef(function FacePePicker2(props, ref) {
946
+ const { config } = useFacePe();
947
+ return /* @__PURE__ */ jsxRuntime.jsx(
948
+ FacePeErrorBoundary,
949
+ {
950
+ source: SOURCE$2,
951
+ entityId: props.entityId ?? config.entityId,
952
+ onError: props.onError,
953
+ fallback: props.slots?.fallback,
954
+ children: /* @__PURE__ */ jsxRuntime.jsx(FacePePickerContent, { ...props, ref })
955
+ }
956
+ );
957
+ });
958
+ FacePePicker.displayName = "FacePePicker";
959
+ const FacePePickerContent = react.forwardRef(function FacePePickerContent2(props, ref) {
960
+ const { config } = useFacePe();
961
+ const mode = props.mode ?? config.mode ?? "view";
962
+ const entityId = props.entityId ?? config.entityId;
963
+ const density = props.density ?? config.density ?? "comfortable";
964
+ const locale = props.locale ?? config.locale;
965
+ const showDescriptions = props.featureFlags?.[DESCRIPTIONS_FLAG] ?? config.featureFlags?.[DESCRIPTIONS_FLAG] ?? false;
966
+ const readOnly = mode === "view";
967
+ const resource = useFacePeResource(
968
+ props.options === void 0 && entityId !== void 0 ? gatewayPath.pickerOptions(entityId) : null,
969
+ decodePickerOptions
970
+ );
971
+ const options2 = props.options ?? (resource.status === "ready" ? resource.data : NO_OPTIONS);
972
+ const loading = resource.status === "loading";
973
+ const loadError = resource.status === "failed" ? resource.error : null;
974
+ const [initial, setInitial] = react.useState(
975
+ () => loading ? null : resolveDefault(props.defaultValue, options2)
976
+ );
977
+ const [selectedValue, setSelectedValue] = react.useState(initial?.value ?? null);
978
+ const optionsByValue = react.useMemo(() => new Map(options2.map((o) => [o.value, o])), [options2]);
979
+ const selected2 = selectedValue === null ? void 0 : optionsByValue.get(selectedValue);
980
+ const propsRef = react.useRef(props);
981
+ propsRef.current = props;
982
+ const entityRef = react.useRef(entityId);
983
+ entityRef.current = entityId;
984
+ const selectedRef = react.useRef(selected2?.value ?? null);
985
+ selectedRef.current = selected2?.value ?? null;
986
+ const readyRef = react.useRef(false);
987
+ const groupRef = react.useRef(null);
988
+ const id = react.useId();
989
+ const legendId = `${id}-legend`;
990
+ const events = useFacePeEmitter(SOURCE$2, () => ({ entityId: entityRef.current }));
991
+ react.useEffect(() => {
992
+ const unsubscribe = [
993
+ events.on("ready", (e) => propsRef.current.onReady?.(e)),
994
+ events.on("select", (e) => propsRef.current.onSelect?.(e)),
995
+ events.on("error", (e) => propsRef.current.onError?.(e))
996
+ ];
997
+ return () => unsubscribe.forEach((off) => off());
998
+ }, [events]);
999
+ react.useEffect(() => {
1000
+ if (readyRef.current) return;
1001
+ readyRef.current = true;
1002
+ events.emit("ready", null);
1003
+ }, [events]);
1004
+ react.useEffect(() => {
1005
+ if (initial !== null || loading) return;
1006
+ const resolved = loadError === null ? resolveDefault(propsRef.current.defaultValue, options2) : { value: null, error: null };
1007
+ selectedRef.current = resolved.value;
1008
+ setSelectedValue(resolved.value);
1009
+ setInitial(resolved);
1010
+ }, [initial, loading, loadError, options2]);
1011
+ const reportedRef = react.useRef(null);
1012
+ react.useEffect(() => {
1013
+ if (initial === null || initial.error === null || reportedRef.current === initial) return;
1014
+ reportedRef.current = initial;
1015
+ events.emit("error", initial.error);
1016
+ }, [initial, events]);
1017
+ react.useEffect(() => {
1018
+ if (loadError !== null) events.emit("error", loadError);
1019
+ }, [loadError, events]);
1020
+ const selectValue = react.useCallback(
1021
+ async (value) => {
1022
+ if (readOnly) return failure$2("READ_ONLY", "The picker is in view mode.");
1023
+ const option2 = optionsByValue.get(value);
1024
+ if (option2 === void 0) return failure$2("OPTION_NOT_FOUND", `No option has the value "${value}".`);
1025
+ if (option2.disabled === true) return failure$2("OPTION_DISABLED", `Option "${value}" is disabled.`);
1026
+ const selection = {
1027
+ value: option2.value,
1028
+ label: option2.label,
1029
+ previousValue: selectedRef.current
1030
+ };
1031
+ if (selectedRef.current === option2.value) return { success: true, data: selection };
1032
+ selectedRef.current = option2.value;
1033
+ setSelectedValue(option2.value);
1034
+ events.emit("select", selection);
1035
+ return { success: true, data: selection };
1036
+ },
1037
+ [optionsByValue, readOnly, events]
1038
+ );
1039
+ const focus = react.useCallback(async () => {
1040
+ const target = groupRef.current?.querySelector("input:checked") ?? groupRef.current?.querySelector("input:not(:disabled)");
1041
+ if (target == null) return failure$2("NOTHING_TO_FOCUS", "The picker has no selectable option.");
1042
+ target.focus();
1043
+ return { success: true, data: void 0 };
1044
+ }, []);
1045
+ const reset = react.useCallback(async () => {
1046
+ if (readOnly) return failure$2("READ_ONLY", "The picker is in view mode.");
1047
+ const value = initial?.value ?? null;
1048
+ selectedRef.current = value;
1049
+ setSelectedValue(value);
1050
+ return { success: true, data: void 0 };
1051
+ }, [readOnly, initial]);
1052
+ react.useImperativeHandle(ref, () => ({ focus, reset }), [focus, reset]);
1053
+ const handleChange = react.useCallback(
1054
+ (event) => {
1055
+ void selectValue(event.target.value);
1056
+ },
1057
+ [selectValue]
1058
+ );
1059
+ const optionSlot = props.slots?.option;
1060
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1061
+ "fieldset",
1062
+ {
1063
+ className: [
1064
+ surfaceClass(props.emphasis, props.size),
1065
+ styles$3.picker,
1066
+ density === "compact" ? styles$3.compact : styles$3.comfortable,
1067
+ props.variant === "grid" ? styles$3.grid : styles$3.list
1068
+ ].join(" "),
1069
+ lang: locale,
1070
+ "aria-busy": loading,
1071
+ children: [
1072
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { id: legendId, className: styles$3.legend, children: props.label ?? "Choose an option" }),
1073
+ props.slots?.header,
1074
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: styles$3.empty, children: "Loading options…" }) : loadError !== null ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: styles$3.empty, children: "The options could not be loaded." }) : options2.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles$3.empty, children: props.slots?.emptyState ?? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No options available." }) }) : /* @__PURE__ */ jsxRuntime.jsx(
1075
+ "div",
1076
+ {
1077
+ ref: groupRef,
1078
+ role: "radiogroup",
1079
+ "aria-labelledby": legendId,
1080
+ "aria-readonly": readOnly,
1081
+ className: styles$3.options,
1082
+ children: options2.map((option2) => {
1083
+ const isSelected = option2.value === selected2?.value;
1084
+ const isDisabled = option2.disabled === true;
1085
+ const inputId = `${id}-${option2.value}`;
1086
+ const descriptionId = `${inputId}-description`;
1087
+ const hasDescription = optionSlot === void 0 && showDescriptions && option2.description !== void 0;
1088
+ const className = [styles$3.option, isSelected ? styles$3.selected : "", isDisabled ? styles$3.disabled : ""].filter(Boolean).join(" ");
1089
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: inputId, className, children: [
1090
+ /* @__PURE__ */ jsxRuntime.jsx(
1091
+ "input",
1092
+ {
1093
+ type: "radio",
1094
+ id: inputId,
1095
+ name: `${id}-choice`,
1096
+ value: option2.value,
1097
+ checked: isSelected,
1098
+ disabled: isDisabled,
1099
+ onChange: handleChange,
1100
+ "aria-describedby": hasDescription ? descriptionId : void 0,
1101
+ className: styles$3.input
1102
+ }
1103
+ ),
1104
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles$3.text, children: optionSlot !== void 0 ? typeof optionSlot === "function" ? optionSlot({ option: option2, selected: isSelected, disabled: isDisabled }) : optionSlot : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1105
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles$3.label, children: option2.label }),
1106
+ hasDescription && /* @__PURE__ */ jsxRuntime.jsx("span", { id: descriptionId, className: styles$3.description, children: option2.description })
1107
+ ] }) })
1108
+ ] }, option2.value);
1109
+ })
1110
+ }
1111
+ )
1112
+ ]
1113
+ }
1114
+ );
1115
+ });
1116
+ FacePePickerContent.displayName = "FacePePickerContent";
1117
+ const timeline = "_timeline_plcgb_13";
1118
+ const heading = "_heading_plcgb_25";
1119
+ const message = "_message_plcgb_39";
1120
+ const list = "_list_plcgb_51";
1121
+ const item = "_item_plcgb_63";
1122
+ const marker = "_marker_plcgb_95";
1123
+ const completed = "_completed_plcgb_129";
1124
+ const status = "_status_plcgb_141";
1125
+ const current = "_current_plcgb_149";
1126
+ const pending = "_pending_plcgb_171";
1127
+ const error = "_error_plcgb_179";
1128
+ const body = "_body_plcgb_199";
1129
+ const title = "_title_plcgb_215";
1130
+ const link = "_link_plcgb_265";
1131
+ const selected = "_selected_plcgb_317";
1132
+ const meta = "_meta_plcgb_339";
1133
+ const description = "_description_plcgb_359";
1134
+ const metadata = "_metadata_plcgb_369";
1135
+ const metadataEntry = "_metadataEntry_plcgb_387";
1136
+ const comfortable$1 = "_comfortable_plcgb_423";
1137
+ const compact$1 = "_compact_plcgb_449";
1138
+ const styles$2 = {
1139
+ timeline,
1140
+ heading,
1141
+ message,
1142
+ list,
1143
+ item,
1144
+ marker,
1145
+ completed,
1146
+ status,
1147
+ current,
1148
+ pending,
1149
+ error,
1150
+ body,
1151
+ title,
1152
+ link,
1153
+ selected,
1154
+ meta,
1155
+ description,
1156
+ metadata,
1157
+ metadataEntry,
1158
+ comfortable: comfortable$1,
1159
+ compact: compact$1
1160
+ };
1161
+ const SOURCE$1 = "FacePeTimeline";
1162
+ const NO_ITEMS = [];
1163
+ const RELATIVE_TIME_FLAG = "timeline.relativeTime";
1164
+ const STATUS = {
1165
+ completed: { marker: "✓", text: "Completed", className: styles$2.completed },
1166
+ current: { marker: "●", text: "In progress", className: styles$2.current },
1167
+ pending: { marker: "○", text: "Pending", className: styles$2.pending },
1168
+ error: { marker: "!", text: "Failed", className: styles$2.error }
1169
+ };
1170
+ const RELATIVE_UNITS = [
1171
+ ["year", 365 * 24 * 3600],
1172
+ ["month", 30 * 24 * 3600],
1173
+ ["week", 7 * 24 * 3600],
1174
+ ["day", 24 * 3600],
1175
+ ["hour", 3600],
1176
+ ["minute", 60],
1177
+ ["second", 1]
1178
+ ];
1179
+ function failure$1(code, message2) {
1180
+ return { success: false, error: { code, message: message2 } };
1181
+ }
1182
+ function formattersFor(locale) {
1183
+ const make = (tag) => ({
1184
+ absolute: new Intl.DateTimeFormat(tag, { dateStyle: "medium", timeStyle: "short" }),
1185
+ relative: new Intl.RelativeTimeFormat(tag, { numeric: "auto" })
1186
+ });
1187
+ try {
1188
+ return make(locale);
1189
+ } catch {
1190
+ return make(void 0);
1191
+ }
1192
+ }
1193
+ function relativeTime(format, time, now) {
1194
+ const seconds = Math.round((time - now) / 1e3);
1195
+ for (const [unit, size] of RELATIVE_UNITS) {
1196
+ if (Math.abs(seconds) >= size || unit === "second") return format.format(Math.round(seconds / size), unit);
1197
+ }
1198
+ return format.format(0, "second");
1199
+ }
1200
+ function ordered(items, sort) {
1201
+ if (sort === "none") return items;
1202
+ const direction = sort === "ascending" ? 1 : -1;
1203
+ return items.map((item2, index) => ({ item: item2, index, time: Date.parse(item2.timestamp) })).sort((a, b) => {
1204
+ const aValid = !Number.isNaN(a.time);
1205
+ const bValid = !Number.isNaN(b.time);
1206
+ if (aValid !== bValid) return aValid ? -1 : 1;
1207
+ if (aValid && a.time !== b.time) return (a.time - b.time) * direction;
1208
+ return a.index - b.index;
1209
+ }).map((entry) => entry.item);
1210
+ }
1211
+ function problemsIn(items) {
1212
+ const problems = [];
1213
+ const invalid2 = items.filter((item2) => Number.isNaN(Date.parse(item2.timestamp))).map((item2) => item2.id);
1214
+ if (invalid2.length > 0) {
1215
+ problems.push({ code: "INVALID_TIMESTAMP", message: "Some items have an unparseable timestamp.", details: { ids: invalid2 } });
1216
+ }
1217
+ const seen = /* @__PURE__ */ new Set();
1218
+ const duplicates = /* @__PURE__ */ new Set();
1219
+ for (const item2 of items) {
1220
+ if (seen.has(item2.id)) duplicates.add(item2.id);
1221
+ seen.add(item2.id);
1222
+ }
1223
+ if (duplicates.size > 0) {
1224
+ problems.push({ code: "DUPLICATE_ITEM_ID", message: "Item ids must be unique.", details: { ids: [...duplicates] } });
1225
+ }
1226
+ return problems;
1227
+ }
1228
+ const FacePeTimeline = react.forwardRef(function FacePeTimeline2(props, ref) {
1229
+ const { config } = useFacePe();
1230
+ return /* @__PURE__ */ jsxRuntime.jsx(
1231
+ FacePeErrorBoundary,
1232
+ {
1233
+ source: SOURCE$1,
1234
+ entityId: props.entityId ?? config.entityId,
1235
+ onError: props.onError,
1236
+ fallback: props.slots?.fallback,
1237
+ children: /* @__PURE__ */ jsxRuntime.jsx(FacePeTimelineContent, { ...props, ref })
1238
+ }
1239
+ );
1240
+ });
1241
+ FacePeTimeline.displayName = "FacePeTimeline";
1242
+ const FacePeTimelineContent = react.forwardRef(function FacePeTimelineContent2(props, ref) {
1243
+ const { config } = useFacePe();
1244
+ const entityId = props.entityId ?? config.entityId;
1245
+ const density = props.density ?? config.density ?? "comfortable";
1246
+ const locale = props.locale ?? config.locale;
1247
+ const showRelative = props.featureFlags?.[RELATIVE_TIME_FLAG] ?? config.featureFlags?.[RELATIVE_TIME_FLAG] ?? false;
1248
+ const interactive = props.onSelect !== void 0;
1249
+ const sort = props.sort ?? "none";
1250
+ const resource = useFacePeResource(
1251
+ props.items === void 0 && entityId !== void 0 ? gatewayPath.timeline(entityId) : null,
1252
+ decodeTimeline
1253
+ );
1254
+ const items = props.items ?? (resource.status === "ready" ? resource.data : NO_ITEMS);
1255
+ const loading = props.loading === true || resource.status === "loading";
1256
+ const loadError = resource.status === "failed" ? resource.error : null;
1257
+ const visibleItems = react.useMemo(() => ordered(items, sort), [items, sort]);
1258
+ const itemsById = react.useMemo(() => new Map(items.map((item2) => [item2.id, item2])), [items]);
1259
+ const formatters = react.useMemo(() => formattersFor(locale), [locale]);
1260
+ const [selectedId, setSelectedId] = react.useState(null);
1261
+ const currentSelectedId = selectedId !== null && itemsById.has(selectedId) ? selectedId : null;
1262
+ const propsRef = react.useRef(props);
1263
+ propsRef.current = props;
1264
+ const entityRef = react.useRef(entityId);
1265
+ entityRef.current = entityId;
1266
+ const selectedRef = react.useRef(currentSelectedId);
1267
+ selectedRef.current = currentSelectedId;
1268
+ const readyRef = react.useRef(false);
1269
+ const reportedRef = react.useRef("");
1270
+ const itemRefs = react.useRef(/* @__PURE__ */ new Map());
1271
+ const listRef = react.useRef(null);
1272
+ const id = react.useId();
1273
+ const labelId = `${id}-label`;
1274
+ const events = useFacePeEmitter(SOURCE$1, () => ({ entityId: entityRef.current }));
1275
+ react.useEffect(() => {
1276
+ const unsubscribe = [
1277
+ events.on("ready", (e) => propsRef.current.onReady?.(e)),
1278
+ events.on("select", (e) => propsRef.current.onSelect?.(e)),
1279
+ events.on("navigate", (e) => propsRef.current.onNavigate?.(e)),
1280
+ events.on("error", (e) => propsRef.current.onError?.(e))
1281
+ ];
1282
+ return () => unsubscribe.forEach((off) => off());
1283
+ }, [events]);
1284
+ const relayNavigation = useFacePeNavigationRelay();
1285
+ const navigate = react.useCallback(
1286
+ (request) => {
1287
+ relayNavigation(events.emit("navigate", request));
1288
+ },
1289
+ [events, relayNavigation]
1290
+ );
1291
+ react.useEffect(() => {
1292
+ if (readyRef.current) return;
1293
+ readyRef.current = true;
1294
+ events.emit("ready", null);
1295
+ }, [events]);
1296
+ react.useEffect(() => {
1297
+ if (loadError !== null) events.emit("error", loadError);
1298
+ }, [loadError, events]);
1299
+ react.useEffect(() => {
1300
+ const problems = problemsIn(items);
1301
+ const signature = JSON.stringify(problems);
1302
+ if (signature === reportedRef.current) return;
1303
+ reportedRef.current = signature;
1304
+ for (const problem2 of problems) events.emit("error", problem2);
1305
+ }, [items, events]);
1306
+ const selectItem = react.useCallback(
1307
+ async (itemId) => {
1308
+ const item2 = itemsById.get(itemId);
1309
+ if (item2 === void 0) return failure$1("ITEM_NOT_FOUND", `No item has the id "${itemId}".`);
1310
+ const selection = {
1311
+ id: item2.id,
1312
+ title: item2.title,
1313
+ status: item2.status,
1314
+ previousId: selectedRef.current
1315
+ };
1316
+ if (selectedRef.current === item2.id) return { success: true, data: selection };
1317
+ selectedRef.current = item2.id;
1318
+ setSelectedId(item2.id);
1319
+ events.emit("select", selection);
1320
+ return { success: true, data: selection };
1321
+ },
1322
+ [itemsById, events]
1323
+ );
1324
+ const reset = react.useCallback(async () => {
1325
+ selectedRef.current = null;
1326
+ setSelectedId(null);
1327
+ return { success: true, data: void 0 };
1328
+ }, []);
1329
+ const scrollToSection = react.useCallback(
1330
+ async (itemId) => {
1331
+ const element = itemRefs.current.get(itemId);
1332
+ if (element === void 0) return failure$1("ITEM_NOT_FOUND", `No item has the id "${itemId}".`);
1333
+ if (typeof element.scrollIntoView === "function") element.scrollIntoView({ block: "nearest" });
1334
+ element.querySelector("button")?.focus();
1335
+ return { success: true, data: void 0 };
1336
+ },
1337
+ []
1338
+ );
1339
+ const focus = react.useCallback(async () => {
1340
+ const selected2 = selectedRef.current === null ? void 0 : itemRefs.current.get(selectedRef.current);
1341
+ const target = selected2?.querySelector("button") ?? listRef.current?.querySelector("button");
1342
+ if (target == null) return failure$1("NOTHING_TO_FOCUS", "The timeline has no interactive item to focus.");
1343
+ target.focus();
1344
+ return { success: true, data: void 0 };
1345
+ }, []);
1346
+ react.useImperativeHandle(ref, () => ({ focus, reset, scrollToSection }), [focus, reset, scrollToSection]);
1347
+ const rootClass = `${surfaceClass(props.emphasis, props.size)} ${styles$2.timeline} ${density === "compact" ? styles$2.compact : styles$2.comfortable}`;
1348
+ const heading2 = /* @__PURE__ */ jsxRuntime.jsx("p", { id: labelId, className: styles$2.heading, children: props.label ?? "Timeline" });
1349
+ if (loading) {
1350
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: rootClass, lang: locale, "aria-labelledby": labelId, "aria-busy": "true", children: [
1351
+ heading2,
1352
+ /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: styles$2.message, children: "Loading…" })
1353
+ ] });
1354
+ }
1355
+ const now = Date.now();
1356
+ const itemSlot = props.slots?.item;
1357
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: rootClass, lang: locale, "aria-labelledby": labelId, children: [
1358
+ heading2,
1359
+ loadError !== null ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: styles$2.message, children: "The timeline could not be loaded." }) : visibleItems.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", className: styles$2.message, children: props.slots?.emptyState ?? /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No events yet." }) }) : /* @__PURE__ */ jsxRuntime.jsx("ol", { ref: listRef, className: styles$2.list, "aria-labelledby": labelId, children: visibleItems.map((item2, index) => {
1360
+ const status2 = STATUS[item2.status];
1361
+ const time = Date.parse(item2.timestamp);
1362
+ const valid = !Number.isNaN(time);
1363
+ const formattedTimestamp = valid ? formatters.absolute.format(time) : item2.timestamp;
1364
+ const isSelected = item2.id === currentSelectedId;
1365
+ const key = `${item2.id}\0${index}`;
1366
+ const statusId = `${id}-${index}-status`;
1367
+ const timeId = `${id}-${index}-time`;
1368
+ const titleId = `${id}-${index}-title`;
1369
+ const { link: link2 } = item2;
1370
+ const metadata2 = item2.metadata === void 0 ? [] : Object.entries(item2.metadata);
1371
+ const itemClass = [styles$2.item, status2.className, isSelected ? styles$2.selected : ""].filter(Boolean).join(" ");
1372
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1373
+ "li",
1374
+ {
1375
+ className: itemClass,
1376
+ "aria-current": item2.status === "current" ? "step" : void 0,
1377
+ ref: (element) => {
1378
+ if (element === null) itemRefs.current.delete(item2.id);
1379
+ else if (!itemRefs.current.has(item2.id)) itemRefs.current.set(item2.id, element);
1380
+ },
1381
+ children: [
1382
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles$2.marker, "aria-hidden": "true", children: status2.marker }),
1383
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles$2.body, children: [
1384
+ interactive ? /* @__PURE__ */ jsxRuntime.jsx(
1385
+ "button",
1386
+ {
1387
+ type: "button",
1388
+ id: titleId,
1389
+ className: styles$2.title,
1390
+ "aria-pressed": isSelected,
1391
+ "aria-describedby": `${statusId} ${timeId}`,
1392
+ onClick: () => {
1393
+ void selectItem(item2.id);
1394
+ },
1395
+ children: item2.title
1396
+ }
1397
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { id: titleId, className: styles$2.title, children: item2.title }),
1398
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: styles$2.meta, children: [
1399
+ /* @__PURE__ */ jsxRuntime.jsx("span", { id: statusId, className: styles$2.status, children: status2.text }),
1400
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: " · " }),
1401
+ valid ? /* @__PURE__ */ jsxRuntime.jsxs("time", { id: timeId, dateTime: item2.timestamp, className: styles$2.time, children: [
1402
+ formattedTimestamp,
1403
+ showRelative && ` (${relativeTime(formatters.relative, time, now)})`
1404
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { id: timeId, className: styles$2.time, children: formattedTimestamp })
1405
+ ] }),
1406
+ itemSlot !== void 0 ? typeof itemSlot === "function" ? itemSlot({ item: item2, selected: isSelected, formattedTimestamp }) : itemSlot : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1407
+ item2.description !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: styles$2.description, children: item2.description }),
1408
+ metadata2.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("dl", { className: styles$2.metadata, children: metadata2.map(([name, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles$2.metadataEntry, children: [
1409
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { children: name }),
1410
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: String(value) })
1411
+ ] }, name)) })
1412
+ ] }),
1413
+ link2 !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
1414
+ "button",
1415
+ {
1416
+ type: "button",
1417
+ className: styles$2.link,
1418
+ "aria-describedby": titleId,
1419
+ onClick: () => navigate(link2),
1420
+ children: "View details"
1421
+ }
1422
+ )
1423
+ ] })
1424
+ ]
1425
+ },
1426
+ key
1427
+ );
1428
+ }) })
1429
+ ] });
1430
+ });
1431
+ FacePeTimelineContent.displayName = "FacePeTimelineContent";
1432
+ let anamModule = null;
1433
+ let livekitModule = null;
1434
+ const loadAnam = () => anamModule ??= import("@anam-ai/js-sdk");
1435
+ const loadLiveKit = () => livekitModule ??= import("livekit-client");
1436
+ function preloadEngines() {
1437
+ loadLiveKit().catch(() => {
1438
+ livekitModule = null;
1439
+ });
1440
+ loadAnam().catch(() => {
1441
+ anamModule = null;
1442
+ });
1443
+ }
1444
+ const roleOf = (role) => String(role).toLowerCase().includes("user") ? "user" : "assistant";
1445
+ async function connectAnam(video2, conn, on) {
1446
+ const { createClient, AnamEvent } = await loadAnam();
1447
+ const client = createClient(conn.token);
1448
+ let ended = false;
1449
+ const seen = /* @__PURE__ */ new Map();
1450
+ client.addListener(AnamEvent.VIDEO_PLAY_STARTED, () => {
1451
+ on.onLive();
1452
+ try {
1453
+ client.unmuteInputAudio();
1454
+ } catch {
1455
+ }
1456
+ if (conn.greeting) void client.talk(conn.greeting).catch(() => void 0);
1457
+ });
1458
+ client.addListener(AnamEvent.CONNECTION_CLOSED, () => {
1459
+ if (!ended) on.onDisconnected();
1460
+ });
1461
+ client.addListener(AnamEvent.MESSAGE_HISTORY_UPDATED, (messages) => {
1462
+ for (const m of messages) {
1463
+ const text2 = (m.content || "").trim();
1464
+ if (!text2 || seen.get(m.id) === text2) continue;
1465
+ seen.set(m.id, text2);
1466
+ on.onTranscript({ role: roleOf(m.role), text: text2, id: m.id });
1467
+ }
1468
+ });
1469
+ if (!video2.id) video2.id = `facepe-avatar-${Math.random().toString(36).slice(2, 10)}`;
1470
+ await client.streamToVideoElement(video2.id);
1471
+ return {
1472
+ async end() {
1473
+ ended = true;
1474
+ await client.stopStreaming().catch(() => void 0);
1475
+ },
1476
+ async setMuted(muted) {
1477
+ if (muted) client.muteInputAudio();
1478
+ else client.unmuteInputAudio();
1479
+ }
1480
+ };
1481
+ }
1482
+ async function connectLiveKit(video2, conn, on) {
1483
+ const { Room, RoomEvent, Track, createLocalAudioTrack } = await loadLiveKit();
1484
+ const room = new Room({ adaptiveStream: false, dynacast: false });
1485
+ let ended = false;
1486
+ let audio = null;
1487
+ const dropAudio = () => {
1488
+ if (!audio) return;
1489
+ try {
1490
+ audio.pause();
1491
+ } catch {
1492
+ }
1493
+ audio.remove();
1494
+ audio = null;
1495
+ };
1496
+ room.on(RoomEvent.TrackSubscribed, (track) => {
1497
+ if (track.kind === Track.Kind.Video) {
1498
+ track.attach(video2);
1499
+ void video2.play().catch(() => void 0);
1500
+ on.onLive();
1501
+ } else if (track.kind === Track.Kind.Audio) {
1502
+ dropAudio();
1503
+ const el = track.attach();
1504
+ el.style.display = "none";
1505
+ document.body.appendChild(el);
1506
+ audio = el;
1507
+ }
1508
+ });
1509
+ room.on(RoomEvent.DataReceived, (payload) => {
1510
+ let msg;
1511
+ try {
1512
+ msg = JSON.parse(new TextDecoder().decode(payload));
1513
+ } catch {
1514
+ return;
1515
+ }
1516
+ const type = String(msg.type ?? msg.event_type ?? "");
1517
+ const text2 = String(msg.text ?? msg.transcript ?? msg.content ?? "").trim();
1518
+ if (type === "avatar.transcription" && text2) on.onTranscript({ role: "assistant", text: text2 });
1519
+ else if (type === "user.transcription" && text2) on.onTranscript({ role: "user", text: text2 });
1520
+ else if (type === "order.update" && Array.isArray(msg.items)) {
1521
+ on.onOrder(msg.items.map((i) => ({ name: String(i.name ?? ""), qty: Number(i.qty) || 0 })).filter((i) => i.name && i.qty > 0));
1522
+ }
1523
+ });
1524
+ room.on(RoomEvent.Disconnected, () => {
1525
+ dropAudio();
1526
+ if (!ended) on.onDisconnected();
1527
+ });
1528
+ await room.connect(conn.url, conn.token, { autoSubscribe: true });
1529
+ await room.startAudio().catch(() => void 0);
1530
+ const mic = await createLocalAudioTrack({ echoCancellation: true, noiseSuppression: true, autoGainControl: true });
1531
+ await room.localParticipant.publishTrack(mic);
1532
+ const sessionUpdate = new TextEncoder().encode(JSON.stringify({
1533
+ type: "session.update",
1534
+ session: { input_audio_transcription: { model: "whisper-1" }, turn_detection: { type: "server_vad" } }
1535
+ }));
1536
+ const send = () => {
1537
+ if (!ended) void room.localParticipant.publishData(sessionUpdate, { reliable: true }).catch(() => void 0);
1538
+ };
1539
+ send();
1540
+ [600, 1500, 3500].forEach((ms) => setTimeout(send, ms));
1541
+ return {
1542
+ async end() {
1543
+ ended = true;
1544
+ mic.stop();
1545
+ dropAudio();
1546
+ await room.disconnect().catch(() => void 0);
1547
+ },
1548
+ async setMuted(muted) {
1549
+ if (muted) await mic.mute();
1550
+ else await mic.unmute();
1551
+ }
1552
+ };
1553
+ }
1554
+ function connectEngine(video2, conn, on) {
1555
+ return conn.type === "stream" ? connectAnam(video2, conn, on) : connectLiveKit(video2, conn, on);
1556
+ }
1557
+ const avatar = "_avatar_1ugh0_9";
1558
+ const comfortable = "_comfortable_1ugh0_25";
1559
+ const compact = "_compact_1ugh0_35";
1560
+ const stage = "_stage_1ugh0_47";
1561
+ const video = "_video_1ugh0_79";
1562
+ const portrait = "_portrait_1ugh0_95";
1563
+ const initials = "_initials_1ugh0_111";
1564
+ const overlay$1 = "_overlay_1ugh0_131";
1565
+ const problem = "_problem_1ugh0_153";
1566
+ const captions = "_captions_1ugh0_165";
1567
+ const speaker = "_speaker_1ugh0_187";
1568
+ const user = "_user_1ugh0_195";
1569
+ const assistant = "_assistant_1ugh0_203";
1570
+ const controls = "_controls_1ugh0_211";
1571
+ const primary = "_primary_1ugh0_225";
1572
+ const secondary = "_secondary_1ugh0_227";
1573
+ const danger = "_danger_1ugh0_229";
1574
+ const choices = "_choices_1ugh0_313";
1575
+ const choice = "_choice_1ugh0_313";
1576
+ const thumb = "_thumb_1ugh0_387";
1577
+ const styles$1 = {
1578
+ avatar,
1579
+ comfortable,
1580
+ compact,
1581
+ stage,
1582
+ video,
1583
+ portrait,
1584
+ initials,
1585
+ overlay: overlay$1,
1586
+ problem,
1587
+ captions,
1588
+ speaker,
1589
+ user,
1590
+ assistant,
1591
+ controls,
1592
+ primary,
1593
+ secondary,
1594
+ danger,
1595
+ choices,
1596
+ choice,
1597
+ thumb
1598
+ };
1599
+ const SOURCE = "FacePeAvatar";
1600
+ const CAPTION_LINES = 2;
1601
+ const NO_AVATARS = { code: "NO_AVATAR", message: "No avatars are assigned to this user." };
1602
+ function failure(code, message2) {
1603
+ return { success: false, error: { code, message: message2 } };
1604
+ }
1605
+ const FacePeAvatar = react.forwardRef(function FacePeAvatar2(props, ref) {
1606
+ const { config } = useFacePe();
1607
+ return /* @__PURE__ */ jsxRuntime.jsx(
1608
+ FacePeErrorBoundary,
1609
+ {
1610
+ source: SOURCE,
1611
+ entityId: props.entityId ?? config.entityId,
1612
+ onError: props.onError,
1613
+ fallback: props.slots?.fallback,
1614
+ children: /* @__PURE__ */ jsxRuntime.jsx(FacePeAvatarContent, { ...props, ref })
1615
+ }
1616
+ );
1617
+ });
1618
+ FacePeAvatar.displayName = "FacePeAvatar";
1619
+ const FacePeAvatarContent = react.forwardRef(function FacePeAvatarContent2(props, ref) {
1620
+ const { config } = useFacePe();
1621
+ const providerAvatarId = useFacePeAvatarId();
1622
+ const fixedId = props.avatarId ?? providerAvatarId;
1623
+ const entityId = props.entityId ?? config.entityId;
1624
+ const locale = props.locale ?? config.locale;
1625
+ const density = props.density ?? config.density ?? "comfortable";
1626
+ const api = useFacePeApi();
1627
+ const [status2, setStatus] = react.useState("idle");
1628
+ const [muted, setMutedState] = react.useState(false);
1629
+ const [lines, setLines] = react.useState([]);
1630
+ const [problem2, setProblem] = react.useState(null);
1631
+ const propsRef = react.useRef(props);
1632
+ propsRef.current = props;
1633
+ const entityRef = react.useRef(entityId);
1634
+ entityRef.current = entityId;
1635
+ const videoRef = react.useRef(null);
1636
+ const mainButtonRef = react.useRef(null);
1637
+ const engineRef = react.useRef(null);
1638
+ const connRef = react.useRef(null);
1639
+ const statusRef = react.useRef(status2);
1640
+ statusRef.current = status2;
1641
+ const runRef = react.useRef(0);
1642
+ const readyRef = react.useRef(false);
1643
+ const list2 = useFacePeResource(fixedId === void 0 ? gatewayPath.avatars : null, decodeAvatarList);
1644
+ const choices2 = list2.status === "ready" ? list2.data : [];
1645
+ const [chosenId, setChosenId] = react.useState(null);
1646
+ const chosen = choices2.find((a) => a.id === chosenId) ?? choices2[0];
1647
+ const avatarId = fixedId ?? chosen?.id;
1648
+ const info = useFacePeResource(fixedId === void 0 ? null : gatewayPath.avatar(fixedId), decodeAvatar);
1649
+ const avatar2 = fixedId === void 0 ? chosen ?? null : info.status === "ready" ? info.data : null;
1650
+ const portrait2 = api.assetUrl(avatar2?.previewUrl);
1651
+ const known = avatar2 !== null;
1652
+ react.useEffect(() => {
1653
+ if (known) preloadEngines();
1654
+ }, [known]);
1655
+ const [portraitFailed, setPortraitFailed] = react.useState(null);
1656
+ const events = useFacePeEmitter(SOURCE, () => ({ entityId: entityRef.current }));
1657
+ react.useEffect(() => {
1658
+ const unsubscribe = [
1659
+ events.on("ready", (e) => propsRef.current.onReady?.(e)),
1660
+ events.on("sessionStart", (e) => propsRef.current.onSessionStart?.(e)),
1661
+ events.on("sessionEnd", (e) => propsRef.current.onSessionEnd?.(e)),
1662
+ events.on("transcript", (e) => propsRef.current.onTranscript?.(e)),
1663
+ events.on("orderUpdate", (e) => propsRef.current.onOrderUpdate?.(e)),
1664
+ events.on("error", (e) => propsRef.current.onError?.(e))
1665
+ ];
1666
+ return () => unsubscribe.forEach((off) => off());
1667
+ }, [events]);
1668
+ react.useEffect(() => {
1669
+ if (readyRef.current) return;
1670
+ readyRef.current = true;
1671
+ events.emit("ready", null);
1672
+ }, [events]);
1673
+ const noAvatars = list2.status === "ready" && list2.data.length === 0;
1674
+ const loadError = info.status === "failed" ? info.error : list2.status === "failed" ? list2.error : null;
1675
+ const infoError = loadError ?? (noAvatars ? NO_AVATARS : null);
1676
+ react.useEffect(() => {
1677
+ if (infoError === null) return;
1678
+ setProblem(
1679
+ infoError.code === "FORBIDDEN" ? "This avatar is not available to this account." : infoError.code === "NO_AVATAR" ? "No avatars are assigned to this account yet." : "The avatar could not be loaded."
1680
+ );
1681
+ events.emit("error", infoError);
1682
+ }, [infoError, events]);
1683
+ const choose = (id) => {
1684
+ if (statusRef.current === "connecting" || statusRef.current === "live" || id === avatarId) return;
1685
+ setChosenId(id);
1686
+ setStatus("idle");
1687
+ setProblem(null);
1688
+ setLines([]);
1689
+ };
1690
+ const report = react.useCallback(
1691
+ (error2, shown) => {
1692
+ setProblem(shown);
1693
+ setStatus("error");
1694
+ events.emit("error", error2);
1695
+ },
1696
+ [events]
1697
+ );
1698
+ const finish = react.useCallback(
1699
+ async (reason) => {
1700
+ runRef.current += 1;
1701
+ const engine = engineRef.current;
1702
+ const conn = connRef.current;
1703
+ engineRef.current = null;
1704
+ connRef.current = null;
1705
+ if (conn?.usageId) {
1706
+ void api.post(gatewayPath.endAvatarSession(conn.usageId), void 0, { keepalive: true });
1707
+ }
1708
+ if (engine) await engine.end().catch(() => void 0);
1709
+ if (conn) events.emit("sessionEnd", { avatarId: conn.avatar.id, usageId: conn.usageId, reason });
1710
+ },
1711
+ [api, events]
1712
+ );
1713
+ const start = react.useCallback(async () => {
1714
+ if (statusRef.current === "connecting" || statusRef.current === "live") return { success: true, data: void 0 };
1715
+ if (avatarId === void 0) {
1716
+ const result = failure("NO_AVATAR", "No avatar is available: none was given, and none is assigned to this user.");
1717
+ report(result.error, "No avatar is available.");
1718
+ return result;
1719
+ }
1720
+ const run = ++runRef.current;
1721
+ setProblem(null);
1722
+ setLines([]);
1723
+ setMutedState(false);
1724
+ setStatus("connecting");
1725
+ const language = propsRef.current.language;
1726
+ const session = await api.post(
1727
+ gatewayPath.avatarSessions(avatarId),
1728
+ language ? { language } : {},
1729
+ { decode: decodeAvatarConnection }
1730
+ );
1731
+ if (run !== runRef.current) return failure("CANCELLED", "The conversation was ended before it started.");
1732
+ if (!session.success) {
1733
+ report(session.error, session.error.code === "FORBIDDEN" ? "This avatar is not available to this account." : "The conversation could not be started.");
1734
+ return session;
1735
+ }
1736
+ connRef.current = session.data;
1737
+ events.emit("sessionStart", { avatarId: session.data.avatar.id, usageId: session.data.usageId });
1738
+ const video2 = videoRef.current;
1739
+ if (video2 === null) return failure("NOT_MOUNTED", "The avatar is not on the page.");
1740
+ try {
1741
+ const engine = await connectEngine(video2, session.data, {
1742
+ onLive: () => {
1743
+ if (run === runRef.current) setStatus("live");
1744
+ },
1745
+ onTranscript: (line) => {
1746
+ if (run !== runRef.current) return;
1747
+ setLines((prev) => [...prev, line].slice(-CAPTION_LINES));
1748
+ events.emit("transcript", line);
1749
+ },
1750
+ onOrder: (items) => {
1751
+ if (run === runRef.current) events.emit("orderUpdate", { items });
1752
+ },
1753
+ onDisconnected: () => {
1754
+ if (run !== runRef.current) return;
1755
+ setStatus("ended");
1756
+ void finish("disconnected");
1757
+ }
1758
+ });
1759
+ if (run !== runRef.current) {
1760
+ await engine.end().catch(() => void 0);
1761
+ return failure("CANCELLED", "The conversation was ended before it started.");
1762
+ }
1763
+ engineRef.current = engine;
1764
+ return { success: true, data: void 0 };
1765
+ } catch (err) {
1766
+ const error2 = { code: "AVATAR_CONNECT_FAILED", message: err instanceof Error ? err.message : "Could not connect to the avatar." };
1767
+ await finish("disconnected");
1768
+ report(error2, "Could not connect to the avatar. Check the microphone permission and try again.");
1769
+ return { success: false, error: error2 };
1770
+ }
1771
+ }, [api, avatarId, events, finish, report]);
1772
+ const end = react.useCallback(async () => {
1773
+ if (statusRef.current !== "connecting" && statusRef.current !== "live") return { success: true, data: void 0 };
1774
+ setStatus("ended");
1775
+ await finish("ended");
1776
+ return { success: true, data: void 0 };
1777
+ }, [finish]);
1778
+ const setMuted = react.useCallback(async (next) => {
1779
+ const engine = engineRef.current;
1780
+ if (engine === null) return failure("NOT_LIVE", "There is no conversation to mute.");
1781
+ try {
1782
+ await engine.setMuted(next);
1783
+ setMutedState(next);
1784
+ return { success: true, data: void 0 };
1785
+ } catch {
1786
+ return failure("MUTE_FAILED", "The microphone could not be changed.");
1787
+ }
1788
+ }, []);
1789
+ const focus = react.useCallback(async () => {
1790
+ if (!mainButtonRef.current) return failure("NOTHING_TO_FOCUS", "The avatar has no control to focus.");
1791
+ mainButtonRef.current.focus();
1792
+ return { success: true, data: void 0 };
1793
+ }, []);
1794
+ react.useImperativeHandle(ref, () => ({ focus, start, end, setMuted }), [focus, start, end, setMuted]);
1795
+ const autoStarted = react.useRef(false);
1796
+ react.useEffect(() => {
1797
+ if (!props.autoStart || autoStarted.current || avatarId === void 0) return;
1798
+ autoStarted.current = true;
1799
+ void start();
1800
+ }, [props.autoStart, avatarId, start]);
1801
+ react.useEffect(() => {
1802
+ const onPageHide = () => void finish("unmounted");
1803
+ window.addEventListener("pagehide", onPageHide);
1804
+ return () => {
1805
+ window.removeEventListener("pagehide", onPageHide);
1806
+ void finish("unmounted");
1807
+ };
1808
+ }, [finish]);
1809
+ const live = status2 === "live";
1810
+ const busy = status2 === "connecting";
1811
+ const name = avatar2?.name ?? "FacePe avatar";
1812
+ const controls2 = {
1813
+ status: status2,
1814
+ muted,
1815
+ start: () => void start(),
1816
+ end: () => void end(),
1817
+ setMuted: (next) => void setMuted(next)
1818
+ };
1819
+ const controlsSlot = props.slots?.controls;
1820
+ const statusText = status2 === "connecting" ? `Connecting to ${name}…` : status2 === "ended" ? "Conversation ended." : status2 === "idle" ? `Talk to ${name}` : null;
1821
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1822
+ "section",
1823
+ {
1824
+ className: [surfaceClass(props.emphasis, props.size), styles$1.avatar, density === "compact" ? styles$1.compact : styles$1.comfortable].join(" "),
1825
+ lang: locale,
1826
+ "aria-label": name,
1827
+ "aria-busy": busy,
1828
+ children: [
1829
+ props.slots?.header,
1830
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles$1.stage, children: [
1831
+ /* @__PURE__ */ jsxRuntime.jsx("video", { ref: videoRef, className: styles$1.video, autoPlay: true, playsInline: true, "aria-label": `${name}, video` }),
1832
+ !live && (portrait2 && portraitFailed !== portrait2 ? /* @__PURE__ */ jsxRuntime.jsx("img", { className: styles$1.portrait, src: portrait2, alt: name, onError: () => setPortraitFailed(portrait2) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles$1.initials, "aria-hidden": "true", children: name.split(/\s+/).map((w) => w[0]).join("").slice(0, 2).toUpperCase() })),
1833
+ !live && statusText !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: styles$1.overlay, children: statusText })
1834
+ ] }),
1835
+ choices2.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles$1.choices, role: "radiogroup", "aria-label": "Choose an avatar", children: choices2.map((a) => {
1836
+ const thumb2 = api.assetUrl(a.previewUrl);
1837
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1838
+ "button",
1839
+ {
1840
+ type: "button",
1841
+ role: "radio",
1842
+ "aria-checked": a.id === avatarId,
1843
+ className: styles$1.choice,
1844
+ disabled: live || busy,
1845
+ onClick: () => choose(a.id),
1846
+ children: [
1847
+ thumb2 ? /* @__PURE__ */ jsxRuntime.jsx("img", { className: styles$1.thumb, src: thumb2, alt: "" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles$1.thumb, "aria-hidden": "true", children: a.name.slice(0, 1).toUpperCase() }),
1848
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: a.name })
1849
+ ]
1850
+ },
1851
+ a.id
1852
+ );
1853
+ }) }),
1854
+ problem2 !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", className: styles$1.problem, children: problem2 }),
1855
+ props.captions !== false && lines.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles$1.captions, "aria-live": "polite", children: lines.map((line, i) => /* @__PURE__ */ jsxRuntime.jsxs("p", { className: line.role === "user" ? styles$1.user : styles$1.assistant, children: [
1856
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles$1.speaker, children: [
1857
+ line.role === "user" ? "You" : name,
1858
+ ": "
1859
+ ] }),
1860
+ line.text
1861
+ ] }, line.id ?? `${i}-${line.text}`)) }),
1862
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles$1.controls, children: controlsSlot !== void 0 ? typeof controlsSlot === "function" ? controlsSlot(controls2) : controlsSlot : live || busy ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1863
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: styles$1.secondary, disabled: !live, "aria-pressed": muted, onClick: () => void setMuted(!muted), children: muted ? "Unmute" : "Mute" }),
1864
+ /* @__PURE__ */ jsxRuntime.jsx("button", { ref: mainButtonRef, type: "button", className: styles$1.danger, onClick: () => void end(), children: "End" })
1865
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1866
+ "button",
1867
+ {
1868
+ ref: mainButtonRef,
1869
+ type: "button",
1870
+ className: styles$1.primary,
1871
+ disabled: avatarId === void 0 || infoError !== null,
1872
+ onClick: () => void start(),
1873
+ children: status2 === "ended" || status2 === "error" ? "Start again" : "Start conversation"
1874
+ }
1875
+ ) })
1876
+ ]
1877
+ }
1878
+ );
1879
+ });
1880
+ FacePeAvatarContent.displayName = "FacePeAvatarContent";
1881
+ const page = "_page_1q0su_11";
1882
+ const overlay = "_overlay_1q0su_31";
1883
+ const modal = "_modal_1q0su_75";
1884
+ const drawer = "_drawer_1q0su_87";
1885
+ const styles = {
1886
+ page,
1887
+ overlay,
1888
+ modal,
1889
+ drawer
1890
+ };
1891
+ function FacePePage({ children }) {
1892
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.page, children });
1893
+ }
1894
+ function FacePeOverlay({ open, label: label2, variant = "modal", onClose, children }) {
1895
+ const dialogRef = react.useRef(null);
1896
+ const onCloseRef = react.useRef(onClose);
1897
+ onCloseRef.current = onClose;
1898
+ react.useEffect(() => {
1899
+ const dialog = dialogRef.current;
1900
+ if (dialog === null) return;
1901
+ const shown = dialog.hasAttribute("open");
1902
+ if (open && !shown) {
1903
+ if (typeof dialog.showModal === "function") dialog.showModal();
1904
+ else dialog.setAttribute("open", "");
1905
+ } else if (!open && shown) {
1906
+ if (typeof dialog.close === "function") dialog.close();
1907
+ else dialog.removeAttribute("open");
1908
+ }
1909
+ }, [open]);
1910
+ const handleCancel = (event) => {
1911
+ event.preventDefault();
1912
+ onCloseRef.current?.();
1913
+ };
1914
+ return /* @__PURE__ */ jsxRuntime.jsx("dialog", { ref: dialogRef, "aria-label": label2, className: `${styles.overlay} ${variant === "drawer" ? styles.drawer : styles.modal}`, onCancel: handleCancel, children });
1915
+ }
1916
+ exports.FacePeAvatar = FacePeAvatar;
1917
+ exports.FacePeForm = FacePeForm;
1918
+ exports.FacePeOverlay = FacePeOverlay;
1919
+ exports.FacePePage = FacePePage;
1920
+ exports.FacePePicker = FacePePicker;
1921
+ exports.FacePeProvider = FacePeProvider;
1922
+ exports.FacePeTimeline = FacePeTimeline;
1923
+ exports.SDK_VERSION = SDK_VERSION;
1924
+ exports.useFacePe = useFacePe;
1925
+ //# sourceMappingURL=index.cjs.map