@businessdash/sdk 0.9.81 → 0.9.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -902
- package/assets/{biab-sw.js → bd-sw.js} +2 -2
- package/dist/account-core/index.d.ts +1 -1
- package/dist/account-core/index.d.ts.map +1 -1
- package/dist/adapters-revalidate.d.ts +2 -2
- package/dist/adapters-revalidate.js +2 -2
- package/dist/adapters-revalidate.js.map +1 -1
- package/dist/analytics-core.d.ts +6 -6
- package/dist/analytics-core.d.ts.map +1 -1
- package/dist/analytics-core.js +4 -4
- package/dist/analytics-core.js.map +1 -1
- package/dist/analytics-server.d.ts +6 -6
- package/dist/analytics-server.d.ts.map +1 -1
- package/dist/analytics-server.js +5 -5
- package/dist/auth-handler.d.ts +14 -14
- package/dist/auth-handler.d.ts.map +1 -1
- package/dist/auth-handler.js +16 -16
- package/dist/auth-handler.js.map +1 -1
- package/dist/auth-links.d.ts +2 -2
- package/dist/auth-links.d.ts.map +1 -1
- package/dist/auth-links.js +2 -2
- package/dist/auth-links.js.map +1 -1
- package/dist/{biab-forms.css → bd-forms.css} +158 -158
- package/dist/cli.d.ts +4 -4
- package/dist/cli.js +78 -78
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +158 -147
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +156 -137
- package/dist/client.js.map +1 -1
- package/dist/consent-core/index.cjs +2 -2
- package/dist/consent-core/index.d.ts +2 -2
- package/dist/consent-core/index.d.ts.map +1 -1
- package/dist/consent-core/index.js +10 -8
- package/dist/consent-core/index.js.map +1 -1
- package/dist/contracts.d.ts +411 -52
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +89 -15
- package/dist/contracts.js.map +1 -1
- package/dist/data-model-schema.d.ts +15 -15
- package/dist/data-model-schema.d.ts.map +1 -1
- package/dist/data-model-schema.js +19 -17
- package/dist/data-model-schema.js.map +1 -1
- package/dist/distribution.d.ts +5 -5
- package/dist/distribution.d.ts.map +1 -1
- package/dist/distribution.js +4 -4
- package/dist/element.d.ts +36 -36
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +43 -43
- package/dist/element.js.map +1 -1
- package/dist/embed-protocol.d.ts +11 -11
- package/dist/embed-protocol.d.ts.map +1 -1
- package/dist/embed-protocol.js +1 -1
- package/dist/embed-protocol.js.map +1 -1
- package/dist/errors.d.ts +15 -15
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +18 -18
- package/dist/errors.js.map +1 -1
- package/dist/forms-core/autocomplete.d.ts +3 -3
- package/dist/forms-core/autocomplete.d.ts.map +1 -1
- package/dist/forms-core/autocomplete.js +2 -2
- package/dist/forms-core/controller.d.ts +10 -10
- package/dist/forms-core/controller.d.ts.map +1 -1
- package/dist/forms-core/controller.js +4 -4
- package/dist/forms-core/controller.js.map +1 -1
- package/dist/forms-core/elements.d.ts +4 -4
- package/dist/forms-core/elements.d.ts.map +1 -1
- package/dist/forms-core/elements.js +7 -7
- package/dist/forms-core/elements.js.map +1 -1
- package/dist/forms-core/formula.d.ts +1 -1
- package/dist/forms-core/formula.js +1 -1
- package/dist/forms-core/index.d.ts +6 -6
- package/dist/forms-core/index.d.ts.map +1 -1
- package/dist/forms-core/index.js +5 -5
- package/dist/forms-core/index.js.map +1 -1
- package/dist/forms-core/units.d.ts +1 -1
- package/dist/forms-core/units.js +1 -1
- package/dist/forms-core/us-states.d.ts +1 -1
- package/dist/forms-core/us-states.js +1 -1
- package/dist/forms-core/validation.d.ts +12 -3
- package/dist/forms-core/validation.d.ts.map +1 -1
- package/dist/forms-core/validation.js +152 -17
- package/dist/forms-core/validation.js.map +1 -1
- package/dist/forms.d.ts +2 -2
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +2 -2
- package/dist/forms.js.map +1 -1
- package/dist/framework-support.js +1 -1
- package/dist/framework-support.js.map +1 -1
- package/dist/gallery-types.d.ts +1 -1
- package/dist/graphql.d.ts +3 -3
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +3 -3
- package/dist/graphql.js.map +1 -1
- package/dist/index.cjs +350 -273
- package/dist/index.d.cts +5 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/legal-core/index.cjs +8 -8
- package/dist/legal-core/index.d.ts +3 -3
- package/dist/legal-core/index.d.ts.map +1 -1
- package/dist/legal-core/index.js +11 -11
- package/dist/legal-core/index.js.map +1 -1
- package/dist/marketing-core/index.d.ts +51 -1
- package/dist/marketing-core/index.d.ts.map +1 -1
- package/dist/marketing-core/index.js +35 -0
- package/dist/marketing-core/index.js.map +1 -1
- package/dist/marketing-schema.d.ts +9 -9
- package/dist/marketing-schema.d.ts.map +1 -1
- package/dist/marketing-schema.js +1 -1
- package/dist/mcp.d.ts +4 -4
- package/dist/mcp.js +4 -4
- package/dist/native.d.ts +6 -6
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +4 -4
- package/dist/native.js.map +1 -1
- package/dist/next-revalidate.d.ts +8 -8
- package/dist/next-revalidate.d.ts.map +1 -1
- package/dist/next-revalidate.js +7 -7
- package/dist/next-revalidate.js.map +1 -1
- package/dist/notifications-core/index.d.ts +1 -1
- package/dist/notifications-core/index.js +1 -1
- package/dist/parallel-pages.d.ts +4 -4
- package/dist/parallel-pages.d.ts.map +1 -1
- package/dist/parallel-pages.js +5 -5
- package/dist/parallel-pages.js.map +1 -1
- package/dist/portal-core/index.d.ts +1 -1
- package/dist/portal-core/index.js +1 -1
- package/dist/proxy.d.ts +18 -18
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +10 -10
- package/dist/proxy.js.map +1 -1
- package/dist/push-core/index.cjs +1 -1
- package/dist/push-core/index.d.ts +1 -1
- package/dist/push-core/index.js +1 -1
- package/dist/push-core/index.js.map +1 -1
- package/dist/react-analytics.d.ts +14 -14
- package/dist/react-analytics.d.ts.map +1 -1
- package/dist/react-analytics.js +15 -15
- package/dist/react-analytics.js.map +1 -1
- package/dist/react-attribution.d.ts +2 -2
- package/dist/react-attribution.d.ts.map +1 -1
- package/dist/react-attribution.js +1 -1
- package/dist/react-marketing.js +5 -5
- package/dist/react-marketing.js.map +1 -1
- package/dist/react.d.ts +38 -61
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +247 -261
- package/dist/react.js.map +1 -1
- package/dist/reserved-paths.d.ts +2 -2
- package/dist/reserved-paths.js +3 -3
- package/dist/reserved-paths.js.map +1 -1
- package/dist/revalidate-verify.d.ts +3 -3
- package/dist/revalidate-verify.d.ts.map +1 -1
- package/dist/revalidate-verify.js +1 -1
- package/dist/revalidate-verify.js.map +1 -1
- package/dist/sdk.d.ts +49 -49
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +47 -51
- package/dist/sdk.js.map +1 -1
- package/dist/seed-core/index.js +8 -8
- package/dist/seed-core/index.js.map +1 -1
- package/dist/seo-core/blog.d.ts +1 -1
- package/dist/seo-core/blog.js +1 -1
- package/dist/seo-core/blog.js.map +1 -1
- package/dist/seo-core/index.cjs +2 -2
- package/dist/seo-core/index.js +1 -1
- package/dist/seo-core/index.js.map +1 -1
- package/dist/seo.d.ts +1 -1
- package/dist/seo.d.ts.map +1 -1
- package/dist/seo.js +1 -1
- package/dist/seo.js.map +1 -1
- package/dist/sitemap-core/index.cjs +3 -3
- package/dist/sitemap-core/index.js +2 -2
- package/dist/sitemap-core/index.js.map +1 -1
- package/dist/socials.d.ts +6 -6
- package/dist/socials.d.ts.map +1 -1
- package/dist/socials.js +5 -5
- package/dist/socials.js.map +1 -1
- package/dist/solid-compat.d.ts +1 -1
- package/dist/solid-compat.js +4 -4
- package/dist/solid-compat.js.map +1 -1
- package/dist/store-core/index.d.ts +1 -1
- package/dist/vanilla.d.ts +12 -12
- package/dist/vanilla.d.ts.map +1 -1
- package/dist/vanilla.js +192 -145
- package/dist/vanilla.js.map +1 -1
- package/openapi.json +264 -1
- package/package.json +8 -8
- package/src/account-core/index.ts +1 -1
- package/src/adapters-revalidate.ts +3 -3
- package/src/analytics-core.ts +8 -8
- package/src/analytics-server.ts +6 -6
- package/src/angular/{biab-form.component.ts → bd-form.component.ts} +269 -161
- package/src/angular/headless.ts +12 -12
- package/src/angular/index.ts +9 -9
- package/src/auth-handler.ts +22 -22
- package/src/auth-links.ts +2 -2
- package/src/cli.ts +81 -81
- package/src/client.ts +218 -193
- package/src/consent-core/index.ts +10 -8
- package/src/contracts.ts +99 -15
- package/src/data-model-schema.ts +29 -26
- package/src/distribution.ts +5 -5
- package/src/element.ts +54 -54
- package/src/embed-protocol.ts +14 -14
- package/src/errors.ts +27 -27
- package/src/forms-core/autocomplete.ts +3 -3
- package/src/forms-core/controller.ts +12 -12
- package/src/forms-core/elements.ts +7 -7
- package/src/forms-core/formula.ts +1 -1
- package/src/forms-core/index.ts +8 -8
- package/src/forms-core/units.ts +1 -1
- package/src/forms-core/us-states.ts +1 -1
- package/src/forms-core/validation.ts +158 -14
- package/src/forms.ts +2 -2
- package/src/framework-support.ts +1 -1
- package/src/gallery-types.ts +1 -1
- package/src/graphql.ts +4 -4
- package/src/index.ts +38 -38
- package/src/legal-core/index.ts +12 -12
- package/src/marketing-core/index.ts +62 -0
- package/src/marketing-schema.ts +9 -9
- package/src/mcp.ts +4 -4
- package/src/native.ts +14 -14
- package/src/next-revalidate.ts +10 -10
- package/src/notifications-core/index.ts +1 -1
- package/src/parallel-pages.ts +6 -6
- package/src/portal-core/index.ts +1 -1
- package/src/proxy.ts +28 -28
- package/src/push-core/index.ts +2 -2
- package/src/qwik.tsx +262 -166
- package/src/react-analytics.tsx +17 -17
- package/src/react-attribution.tsx +2 -2
- package/src/react-marketing.tsx +5 -5
- package/src/react.tsx +541 -500
- package/src/reserved-paths.ts +3 -3
- package/src/revalidate-verify.ts +5 -5
- package/src/sdk.ts +106 -110
- package/src/seed-core/index.ts +8 -8
- package/src/seo-core/blog.ts +2 -2
- package/src/seo-core/index.ts +1 -1
- package/src/seo.ts +1 -1
- package/src/sitemap-core/index.ts +2 -2
- package/src/socials.ts +6 -6
- package/src/solid-compat.tsx +5 -5
- package/src/solid-surfaces.tsx +28 -28
- package/src/solid.tsx +264 -175
- package/src/store-core/index.ts +1 -1
- package/src/styles/{biab-forms.css → bd-forms.css} +158 -158
- package/src/svelte/{BiabForm.svelte → BdForm.svelte} +240 -173
- package/src/svelte/context.ts +16 -16
- package/src/svelte/headless.ts +10 -10
- package/src/svelte/index.ts +16 -16
- package/src/vanilla.ts +250 -169
- package/src/vue/{BiabForm.vue → BdForm.vue} +270 -157
- package/src/vue/headless.ts +11 -11
- package/src/vue/index.ts +26 -26
package/dist/react.js
CHANGED
|
@@ -10,7 +10,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
10
10
|
* WHY THE DIRECTIVE IS HERE
|
|
11
11
|
*
|
|
12
12
|
* Without it, a React Server Components framework treats this module as server
|
|
13
|
-
* code by default, and importing `<
|
|
13
|
+
* code by default, and importing `<BdForm>` into a page throws "you're
|
|
14
14
|
* importing a component that needs useState". The only way out is for every
|
|
15
15
|
* consumer to hand-write a `"use client"` wrapper per component — which is
|
|
16
16
|
* exactly what the Next App Router starter had to do, sixteen times, for
|
|
@@ -24,24 +24,24 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
24
24
|
*/
|
|
25
25
|
import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
|
|
26
26
|
import { createFollowersController, createUserController, } from "./account-core/index.js";
|
|
27
|
-
import { buildAuthHref,
|
|
27
|
+
import { buildAuthHref, DEFAULT_AUTH_BASE_PATH } from "./auth-links.js";
|
|
28
28
|
import { createBlogController } from "./blog-core/index.js";
|
|
29
29
|
import { createChatController } from "./chat-core/index.js";
|
|
30
30
|
import { createCheckoutController } from "./checkout-core/index.js";
|
|
31
|
-
import {
|
|
31
|
+
import { BdApiClient } from "./client.js";
|
|
32
32
|
import { DEFAULT_EMBED_SANDBOX, DEFAULT_REFRESH_LEAD_MS, refreshDelayMs, subscribeToEmbedMessages, } from "./embed-protocol.js";
|
|
33
33
|
import { buildAcceptAttr, concurrentRevealCount, createFormController, evalCondition, fetchIconNames, filterIconNames, flattenValueFields, formatCalcValue, iconUrl, isPendingFileValue, resolveFieldAutocomplete, shouldSearchOptions, validateFile, } from "./forms-core/index.js";
|
|
34
34
|
import { baseUnitFor, convertToBase, formatMeasurement, isMeasurementCategory, unitsForCategory, } from "./forms-core/units.js";
|
|
35
35
|
import { US_STATES } from "./forms-core/us-states.js";
|
|
36
|
-
import { createMarketingController } from "./marketing-core/index.js";
|
|
36
|
+
import { createMarketingController, marketingListView, marketingPageView, } from "./marketing-core/index.js";
|
|
37
37
|
import { createPortalController } from "./portal-core/index.js";
|
|
38
|
-
import {
|
|
38
|
+
import { createBdClient } from "./sdk.js";
|
|
39
39
|
import { resolveSocialLinks, SOCIAL_FALLBACK_GLYPH, SOCIAL_LIST_STYLE, } from "./social-core/index.js";
|
|
40
40
|
import { createStoreController } from "./store-core/index.js";
|
|
41
41
|
import { createVerifyController, } from "./verify-core/index.js";
|
|
42
42
|
function useEmbedSession(loader, options) {
|
|
43
43
|
const { apiKey, baseUrl, refreshLeadMs = DEFAULT_REFRESH_LEAD_MS } = options;
|
|
44
|
-
const client = useMemo(() => new
|
|
44
|
+
const client = useMemo(() => new BdApiClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
|
|
45
45
|
const [state, setState] = useState({ status: "idle" });
|
|
46
46
|
// `loader` is recreated per surface; depending on it would refetch on every
|
|
47
47
|
// render, so it's intentionally omitted from the deps below.
|
|
@@ -152,7 +152,7 @@ export function Storefront({ apiKey, baseUrl, productId, className, style, title
|
|
|
152
152
|
const { state, refresh } = useEmbedSession((client) => client.storefront.createSession(input), { apiKey, baseUrl, refreshLeadMs });
|
|
153
153
|
const handleEvent = useCallback((event) => {
|
|
154
154
|
onEvent?.(event);
|
|
155
|
-
if (event.type === "
|
|
155
|
+
if (event.type === "bd:cart:update") {
|
|
156
156
|
onCartChange?.(event.count, event.subtotalCents);
|
|
157
157
|
}
|
|
158
158
|
}, [onEvent, onCartChange]);
|
|
@@ -162,7 +162,7 @@ export function Cart({ apiKey, baseUrl, className, style, title = "Cart", loadin
|
|
|
162
162
|
const { state, refresh } = useEmbedSession((client) => client.cart.createSession(), { apiKey, baseUrl, refreshLeadMs });
|
|
163
163
|
const handleEvent = useCallback((event) => {
|
|
164
164
|
onEvent?.(event);
|
|
165
|
-
if (event.type === "
|
|
165
|
+
if (event.type === "bd:cart:update") {
|
|
166
166
|
onCartChange?.(event.count, event.subtotalCents);
|
|
167
167
|
}
|
|
168
168
|
}, [onEvent, onCartChange]);
|
|
@@ -170,7 +170,7 @@ export function Cart({ apiKey, baseUrl, className, style, title = "Cart", loadin
|
|
|
170
170
|
}
|
|
171
171
|
export function Checkout(props) {
|
|
172
172
|
const { apiKey, baseUrl, cart, mode = "redirect", className, style, title = "Checkout", children, loadingFallback, errorFallback, onEvent, onSuccess, onCancel, onError, } = props;
|
|
173
|
-
const client = useMemo(() => new
|
|
173
|
+
const client = useMemo(() => new BdApiClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
|
|
174
174
|
const [state, setState] = useState({
|
|
175
175
|
status: "idle",
|
|
176
176
|
});
|
|
@@ -191,11 +191,11 @@ export function Checkout(props) {
|
|
|
191
191
|
}, [client, cart, mode, onError]);
|
|
192
192
|
const handleEvent = useCallback((event) => {
|
|
193
193
|
onEvent?.(event);
|
|
194
|
-
if (event.type === "
|
|
194
|
+
if (event.type === "bd:checkout:success")
|
|
195
195
|
onSuccess?.(event.sessionId);
|
|
196
|
-
else if (event.type === "
|
|
196
|
+
else if (event.type === "bd:checkout:cancel")
|
|
197
197
|
onCancel?.(event.sessionId);
|
|
198
|
-
else if (event.type === "
|
|
198
|
+
else if (event.type === "bd:checkout:error")
|
|
199
199
|
onError?.(new Error(event.message));
|
|
200
200
|
}, [onEvent, onSuccess, onCancel, onError]);
|
|
201
201
|
// Lazily create the session once it's idle (iframe mode). Declared BEFORE the
|
|
@@ -220,7 +220,7 @@ export function CheckoutResult(props) {
|
|
|
220
220
|
setState({ status: "error", error: new Error("Missing session id.") });
|
|
221
221
|
return;
|
|
222
222
|
}
|
|
223
|
-
const client = new
|
|
223
|
+
const client = new BdApiClient({ apiKey, baseUrl });
|
|
224
224
|
// We hit the *public* tRPC endpoint via fetch — no API key required.
|
|
225
225
|
const trpcUrl = baseUrl.replace(/\/api\/package\/v1\/?$/, "");
|
|
226
226
|
const requestUrl = `${trpcUrl}/api/trpc/store.checkoutSessionStatus?input=${encodeURIComponent(JSON.stringify({ json: { orgSlug, sessionId } }))}`;
|
|
@@ -277,7 +277,7 @@ export function Chatbot({ apiKey, baseUrl, className, style, title = "Assistant"
|
|
|
277
277
|
const { state, refresh } = useEmbedSession((client) => client.chatbot.createSession(), { apiKey, baseUrl, refreshLeadMs });
|
|
278
278
|
const handleEvent = useCallback((event) => {
|
|
279
279
|
onEvent?.(event);
|
|
280
|
-
if (event.type === "
|
|
280
|
+
if (event.type === "bd:chatbot:assistant-message") {
|
|
281
281
|
onAssistantMessage?.(event.content, event.model);
|
|
282
282
|
}
|
|
283
283
|
}, [onEvent, onAssistantMessage]);
|
|
@@ -296,13 +296,13 @@ export function Chatbot({ apiKey, baseUrl, className, style, title = "Assistant"
|
|
|
296
296
|
export function useChatbot(options) {
|
|
297
297
|
const { apiKey, baseUrl } = options;
|
|
298
298
|
const controller = useMemo(() => createChatController({
|
|
299
|
-
client: new
|
|
299
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
300
300
|
}), [apiKey, baseUrl]);
|
|
301
301
|
useEffect(() => () => controller.destroy(), [controller]);
|
|
302
302
|
const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
|
|
303
303
|
const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
|
|
304
304
|
const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
305
|
-
const uploadFile = useCallback((args) => new
|
|
305
|
+
const uploadFile = useCallback((args) => new BdApiClient({ apiKey, baseUrl }).chatbot.uploadFile(args), [apiKey, baseUrl]);
|
|
306
306
|
// The returned shape is unchanged from before this hook was moved onto
|
|
307
307
|
// `chat-core`, so existing consumers keep working — only the implementation
|
|
308
308
|
// moved, and it now backs every other framework's chat binding too.
|
|
@@ -335,7 +335,7 @@ export function useChatbot(options) {
|
|
|
335
335
|
export function useStorefront(options) {
|
|
336
336
|
const { apiKey, baseUrl, pageSize, categoryId } = options;
|
|
337
337
|
const controller = useMemo(() => createStoreController({
|
|
338
|
-
client: new
|
|
338
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
339
339
|
...(pageSize ? { pageSize } : {}),
|
|
340
340
|
...(categoryId ? { categoryId } : {}),
|
|
341
341
|
}), [apiKey, baseUrl, pageSize, categoryId]);
|
|
@@ -399,52 +399,52 @@ const inlineLayoutStyles = {
|
|
|
399
399
|
* Opt-in default visual theme for {@link ChatbotInline}. Every rule is wrapped
|
|
400
400
|
* in `:where(…)` so it has **zero specificity** — any class a consumer passes
|
|
401
401
|
* via `classNames` (or their global CSS) overrides it without `!important`.
|
|
402
|
-
* Scoped to the `[data-
|
|
402
|
+
* Scoped to the `[data-bd-chatbot]` root (set only when `unstyled` is false),
|
|
403
403
|
* and targets the per-slot `data-slot` attributes the component stamps on each
|
|
404
404
|
* element. Injected once into `<head>` (id-guarded). This is what makes the
|
|
405
405
|
* inline component a polished drop-in instead of bare divs, while preserving
|
|
406
406
|
* the headless contract for anyone who wants to style it themselves.
|
|
407
407
|
*/
|
|
408
|
-
const CHATBOT_INLINE_STYLE_ID = "
|
|
408
|
+
const CHATBOT_INLINE_STYLE_ID = "bd-chatbot-inline-styles";
|
|
409
409
|
const CHATBOT_INLINE_CSS = `
|
|
410
|
-
:where([data-
|
|
411
|
-
:where([data-
|
|
412
|
-
:where([data-
|
|
413
|
-
:where([data-
|
|
414
|
-
:where([data-
|
|
415
|
-
:where([data-
|
|
416
|
-
:where([data-
|
|
417
|
-
:where([data-
|
|
418
|
-
:where([data-
|
|
419
|
-
:where([data-
|
|
420
|
-
:where([data-
|
|
421
|
-
:where([data-
|
|
422
|
-
:where([data-
|
|
423
|
-
:where([data-
|
|
424
|
-
:where([data-
|
|
425
|
-
:where([data-
|
|
426
|
-
:where([data-
|
|
427
|
-
:where([data-
|
|
428
|
-
:where([data-
|
|
429
|
-
:where([data-
|
|
430
|
-
:where([data-
|
|
431
|
-
:where([data-
|
|
432
|
-
:where([data-
|
|
433
|
-
:where([data-
|
|
434
|
-
:where([data-
|
|
435
|
-
:where([data-
|
|
436
|
-
:where([data-
|
|
437
|
-
:where([data-
|
|
438
|
-
:where([data-
|
|
439
|
-
:where([data-
|
|
440
|
-
:where([data-
|
|
441
|
-
:where([data-
|
|
442
|
-
:where([data-
|
|
443
|
-
@keyframes
|
|
444
|
-
@media (prefers-reduced-motion:reduce){:where([data-
|
|
445
|
-
@property --
|
|
446
|
-
@keyframes
|
|
447
|
-
@supports (animation-timeline:scroll()){:where([data-
|
|
410
|
+
:where([data-bd-chatbot]){font-size:14px;color:inherit;background:var(--bd-chatbot-bg,Canvas);border:1px solid color-mix(in srgb,currentColor 12%,transparent);border-radius:16px;}
|
|
411
|
+
:where([data-bd-chatbot] [data-slot="header"]){padding:12px 16px;border-bottom:1px solid color-mix(in srgb,currentColor 10%,transparent);}
|
|
412
|
+
:where([data-bd-chatbot] [data-slot="header-title"]){font-weight:600;}
|
|
413
|
+
:where([data-bd-chatbot] [data-slot="header-subtitle"]){font-size:12px;opacity:.6;margin-top:2px;}
|
|
414
|
+
:where([data-bd-chatbot] [data-slot="presence-strip"]){display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 16px;font-size:12px;border-bottom:1px solid color-mix(in srgb,currentColor 8%,transparent);opacity:.85;}
|
|
415
|
+
:where([data-bd-chatbot] [data-slot="message-list"]){padding:16px;gap:12px;}
|
|
416
|
+
:where([data-bd-chatbot] [data-slot="message-row"]){max-width:85%;width:fit-content;padding:8px 12px;border-radius:16px;line-height:1.5;white-space:pre-wrap;overflow-wrap:anywhere;box-shadow:0 1px 2px color-mix(in srgb,currentColor 8%,transparent);}
|
|
417
|
+
:where([data-bd-chatbot] [data-slot="message-row"][data-role="user"]){align-self:flex-end;background:var(--bd-chatbot-accent,#0d9488);color:#fff;border-bottom-right-radius:4px;}
|
|
418
|
+
:where([data-bd-chatbot] [data-slot="message-row"][data-role="assistant"]){align-self:flex-start;background:color-mix(in srgb,currentColor 8%,transparent);border-bottom-left-radius:4px;}
|
|
419
|
+
:where([data-bd-chatbot] [data-slot="message-pending"]){align-self:flex-start;width:fit-content;padding:8px 12px;background-image:linear-gradient(90deg,currentColor 35%,color-mix(in srgb,currentColor 25%,transparent) 50%,currentColor 65%);background-size:200% 100%;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:bd-chatbot-shimmer 2s linear infinite;}
|
|
420
|
+
:where([data-bd-chatbot] [data-slot="error-text"]){align-self:center;font-size:12px;color:#dc2626;}
|
|
421
|
+
:where([data-bd-chatbot] [data-slot="input-bar"]){gap:8px;padding:12px;border-top:1px solid color-mix(in srgb,currentColor 10%,transparent);}
|
|
422
|
+
:where([data-bd-chatbot] [data-slot="input-field"]){padding:8px 12px;border-radius:10px;border:1px solid color-mix(in srgb,currentColor 15%,transparent);background:transparent;color:inherit;font:inherit;outline:none;}
|
|
423
|
+
:where([data-bd-chatbot] [data-slot="input-field"]:focus-visible){border-color:var(--bd-chatbot-accent,#0d9488);}
|
|
424
|
+
:where([data-bd-chatbot] [data-slot="send-button"]){padding:8px 14px;border-radius:10px;border:none;background:var(--bd-chatbot-accent,#0d9488);color:#fff;font-weight:500;cursor:pointer;}
|
|
425
|
+
:where([data-bd-chatbot] [data-slot="send-button"]:disabled){opacity:.5;cursor:default;}
|
|
426
|
+
:where([data-bd-chatbot] [data-slot="upload-button"]){display:inline-flex;align-items:center;justify-content:center;padding:0 6px;border:none;background:transparent;color:inherit;font-size:18px;line-height:1;cursor:pointer;opacity:.7;}
|
|
427
|
+
:where([data-bd-chatbot] [data-slot="upload-button"]:hover){opacity:1;}
|
|
428
|
+
:where([data-bd-chatbot] [data-slot="upload-button"]:disabled){opacity:.4;cursor:default;}
|
|
429
|
+
:where([data-bd-chatbot] [data-slot="message-file"]){align-self:flex-end;max-width:85%;padding:8px 12px;border-radius:16px;border-bottom-right-radius:4px;background:var(--bd-chatbot-accent,#0d9488);color:#fff;text-decoration:none;font-size:13px;word-break:break-all;}
|
|
430
|
+
:where([data-bd-chatbot] [data-slot="message-image"]){align-self:flex-end;max-width:85%;border-radius:12px;overflow:hidden;}
|
|
431
|
+
:where([data-bd-chatbot] [data-slot="jump-latest"]){position:absolute;left:50%;bottom:72px;transform:translateX(-50%);display:inline-flex;align-items:center;gap:4px;padding:6px 12px;border-radius:999px;border:1px solid color-mix(in srgb,currentColor 15%,transparent);background:var(--bd-chatbot-bg,Canvas);font-size:12px;font-weight:500;cursor:pointer;box-shadow:0 2px 10px color-mix(in srgb,currentColor 20%,transparent);}
|
|
432
|
+
:where([data-bd-chatbot] [data-slot="form-card"]){display:flex;flex-direction:column;gap:10px;align-self:stretch;padding:14px;border-radius:14px;border:1px solid color-mix(in srgb,currentColor 14%,transparent);background:color-mix(in srgb,currentColor 4%,transparent);}
|
|
433
|
+
:where([data-bd-chatbot] [data-slot="form-title"]){font-weight:600;}
|
|
434
|
+
:where([data-bd-chatbot] [data-slot="form-description"]){font-size:12px;opacity:.7;}
|
|
435
|
+
:where([data-bd-chatbot] [data-slot="form-field"]){gap:4px;}
|
|
436
|
+
:where([data-bd-chatbot] [data-slot="form-label"]){font-size:12px;font-weight:500;opacity:.85;}
|
|
437
|
+
:where([data-bd-chatbot] [data-slot="form-input"],[data-bd-chatbot] [data-slot="form-textarea"],[data-bd-chatbot] [data-slot="form-select"]){padding:8px 10px;border-radius:8px;border:1px solid color-mix(in srgb,currentColor 15%,transparent);background:transparent;color:inherit;font:inherit;outline:none;}
|
|
438
|
+
:where([data-bd-chatbot] [data-slot="form-input"]:focus-visible,[data-bd-chatbot] [data-slot="form-textarea"]:focus-visible,[data-bd-chatbot] [data-slot="form-select"]:focus-visible){border-color:var(--bd-chatbot-accent,#0d9488);}
|
|
439
|
+
:where([data-bd-chatbot] [data-slot="form-submit"]){padding:8px 14px;border-radius:8px;border:none;background:var(--bd-chatbot-accent,#0d9488);color:#fff;font-weight:500;cursor:pointer;}
|
|
440
|
+
:where([data-bd-chatbot] [data-slot="form-submit"]:disabled){opacity:.5;cursor:default;}
|
|
441
|
+
:where([data-bd-chatbot] [data-slot="form-error"]){font-size:12px;color:#dc2626;}
|
|
442
|
+
:where([data-bd-chatbot] [data-slot="form-receipt"]){font-size:13px;opacity:.8;}
|
|
443
|
+
@keyframes bd-chatbot-shimmer{from{background-position:100% 0;}to{background-position:0 0;}}
|
|
444
|
+
@media (prefers-reduced-motion:reduce){:where([data-bd-chatbot] [data-slot="message-pending"]){animation:none;-webkit-text-fill-color:currentColor;}}
|
|
445
|
+
@property --bd-chatbot-fade{syntax:"<length>";inherits:false;initial-value:0px;}
|
|
446
|
+
@keyframes bd-chatbot-fade-b{from{--bd-chatbot-fade:24px;}to{--bd-chatbot-fade:0px;}}
|
|
447
|
+
@supports (animation-timeline:scroll()){:where([data-bd-chatbot] [data-slot="message-list"]){-webkit-mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - var(--bd-chatbot-fade,0px)),transparent 100%);mask-image:linear-gradient(to bottom,#000 0,#000 calc(100% - var(--bd-chatbot-fade,0px)),transparent 100%);animation:bd-chatbot-fade-b 1ms linear both;animation-timeline:scroll(self y);animation-range:calc(100% - 96px) 100%;}}
|
|
448
448
|
`;
|
|
449
449
|
/**
|
|
450
450
|
* In-tree React chatbot component. Same backend wiring as the iframe
|
|
@@ -744,7 +744,7 @@ export function ChatbotInline({ apiKey, baseUrl, welcomeMessage, title = "Assist
|
|
|
744
744
|
URL.revokeObjectURL(stagedPreview);
|
|
745
745
|
};
|
|
746
746
|
}, [stagedPreview]);
|
|
747
|
-
return (_jsxs("div", { className: [classNames.root, className].filter(Boolean).join(" ") || undefined, "data-
|
|
747
|
+
return (_jsxs("div", { className: [classNames.root, className].filter(Boolean).join(" ") || undefined, "data-bd-chatbot": unstyled ? undefined : "", style: { ...inlineLayoutStyles.root, ...style }, children: [title !== null ? (_jsxs("div", { className: classNames.header, "data-slot": "header", children: [_jsx("div", { className: classNames.headerTitle, "data-slot": "header-title", children: title }), subtitle !== null && subtitle !== undefined ? (_jsx("div", { className: classNames.headerSubtitle, "data-slot": "header-subtitle", children: subtitle })) : null] })) : null, availability?.frontDeskEnabled
|
|
748
748
|
? renderPresenceStrip
|
|
749
749
|
? renderPresenceStrip(availability)
|
|
750
750
|
: defaultPresenceStrip({ availability, classNames })
|
|
@@ -976,7 +976,7 @@ export async function signOut(options = {}) {
|
|
|
976
976
|
}
|
|
977
977
|
/**
|
|
978
978
|
* Programmatic "forgot password" — POSTs to the auth handler's
|
|
979
|
-
* `/password-reset` route, which the SDK forwards to
|
|
979
|
+
* `/password-reset` route, which the SDK forwards to BD. The server
|
|
980
980
|
* returns `{ ok: true }` regardless of whether an account matched
|
|
981
981
|
* (intentional anti-leak), and emails the platform-hosted reset link
|
|
982
982
|
* out-of-band when a match exists.
|
|
@@ -1047,7 +1047,7 @@ export function useUser(options) {
|
|
|
1047
1047
|
export function useFollowers(options) {
|
|
1048
1048
|
const { token, siteId, baseUrl } = options;
|
|
1049
1049
|
const controller = useMemo(() => createFollowersController({
|
|
1050
|
-
client:
|
|
1050
|
+
client: createBdClient({
|
|
1051
1051
|
apiKey: token,
|
|
1052
1052
|
siteId,
|
|
1053
1053
|
baseUrl: baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
|
|
@@ -1077,7 +1077,7 @@ export function useFollowers(options) {
|
|
|
1077
1077
|
export function useCheckout(options) {
|
|
1078
1078
|
const { apiKey, baseUrl } = options;
|
|
1079
1079
|
const controller = useMemo(() => createCheckoutController({
|
|
1080
|
-
client: new
|
|
1080
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
1081
1081
|
}), [apiKey, baseUrl]);
|
|
1082
1082
|
useEffect(() => () => controller.destroy(), [controller]);
|
|
1083
1083
|
const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
|
|
@@ -1091,6 +1091,17 @@ export function useCheckout(options) {
|
|
|
1091
1091
|
reset: controller.reset,
|
|
1092
1092
|
};
|
|
1093
1093
|
}
|
|
1094
|
+
/** One controller per hook instance. The cache they share lives a layer down
|
|
1095
|
+
* in the client, so this costs a subscriber set and nothing else. */
|
|
1096
|
+
function useMarketingController(apiKey, baseUrl, siteId) {
|
|
1097
|
+
const client = useMemo(() => new BdApiClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
|
|
1098
|
+
const controller = useMemo(() => createMarketingController({ client: client.site(siteId) }), [client, siteId]);
|
|
1099
|
+
useEffect(() => () => controller.destroy(), [controller]);
|
|
1100
|
+
const subscribe = useCallback((onStoreChange) => controller.subscribe(() => onStoreChange()), [controller]);
|
|
1101
|
+
const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
|
|
1102
|
+
const snap = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
1103
|
+
return { client, controller, snap };
|
|
1104
|
+
}
|
|
1094
1105
|
/**
|
|
1095
1106
|
* Lazy-fetch a single marketing page by `pageKey`. The hook reads from the
|
|
1096
1107
|
* SDK's shared cache when present (warmed by `useMarketingPagePreloader` or
|
|
@@ -1103,44 +1114,17 @@ export function useCheckout(options) {
|
|
|
1103
1114
|
*/
|
|
1104
1115
|
export function useMarketingPage(options) {
|
|
1105
1116
|
const { apiKey, baseUrl, siteId, pageKey, enabled = true } = options;
|
|
1106
|
-
const client
|
|
1107
|
-
const initial = client.site(siteId).marketingPages.getCached(pageKey);
|
|
1108
|
-
const [state, setState] = useState(initial
|
|
1109
|
-
? { status: "ready", page: initial.page }
|
|
1110
|
-
: { status: "idle", page: null });
|
|
1111
|
-
const fetchOnce = useCallback(async (skipCache = false) => {
|
|
1112
|
-
if (!enabled)
|
|
1113
|
-
return;
|
|
1114
|
-
setState({ status: "loading", page: null });
|
|
1115
|
-
try {
|
|
1116
|
-
const response = await client
|
|
1117
|
-
.site(siteId)
|
|
1118
|
-
.marketingPages.get(pageKey, { skipCache });
|
|
1119
|
-
setState({ status: "ready", page: response.page });
|
|
1120
|
-
}
|
|
1121
|
-
catch (err) {
|
|
1122
|
-
setState({
|
|
1123
|
-
status: "error",
|
|
1124
|
-
page: null,
|
|
1125
|
-
error: err instanceof Error
|
|
1126
|
-
? err
|
|
1127
|
-
: new Error("Marketing page load failed"),
|
|
1128
|
-
});
|
|
1129
|
-
}
|
|
1130
|
-
}, [client, siteId, pageKey, enabled]);
|
|
1117
|
+
const { client, controller, snap } = useMarketingController(apiKey, baseUrl, siteId);
|
|
1131
1118
|
useEffect(() => {
|
|
1132
1119
|
if (!enabled)
|
|
1133
1120
|
return;
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
}, [client, siteId, pageKey, enabled, fetchOnce]);
|
|
1142
|
-
const refresh = useCallback(() => fetchOnce(true), [fetchOnce]);
|
|
1143
|
-
return { ...state, refresh };
|
|
1121
|
+
void controller.open(pageKey);
|
|
1122
|
+
}, [controller, pageKey, enabled]);
|
|
1123
|
+
const refresh = useCallback(() => controller.open(pageKey, { skipCache: true }), [controller, pageKey]);
|
|
1124
|
+
// The synchronous cache read is React's to do — the controller cannot,
|
|
1125
|
+
// because the cache lives in the client. Everything after it is the core's.
|
|
1126
|
+
const cached = client.site(siteId).marketingPages.getCached(pageKey)?.page ?? null;
|
|
1127
|
+
return { ...marketingPageView(snap, pageKey, cached), refresh };
|
|
1144
1128
|
}
|
|
1145
1129
|
/**
|
|
1146
1130
|
* Loads the payload-free directory of marketing pages for a site. Pair with
|
|
@@ -1148,33 +1132,16 @@ export function useMarketingPage(options) {
|
|
|
1148
1132
|
*/
|
|
1149
1133
|
export function useMarketingPagesList(options) {
|
|
1150
1134
|
const { apiKey, baseUrl, siteId, enabled = true } = options;
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
const fetchOnce = useCallback(async () => {
|
|
1135
|
+
const { controller, snap } = useMarketingController(apiKey, baseUrl, siteId);
|
|
1136
|
+
// This hook only ever calls `load()`, so the controller's single `status`
|
|
1137
|
+
// is describing the index and nothing else.
|
|
1138
|
+
const refresh = useCallback(() => controller.load(), [controller]);
|
|
1139
|
+
useEffect(() => {
|
|
1157
1140
|
if (!enabled)
|
|
1158
1141
|
return;
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
.site(siteId)
|
|
1163
|
-
.marketingPages.list();
|
|
1164
|
-
setState({ status: "ready", pages: response.pages });
|
|
1165
|
-
}
|
|
1166
|
-
catch (err) {
|
|
1167
|
-
setState({
|
|
1168
|
-
status: "error",
|
|
1169
|
-
pages: null,
|
|
1170
|
-
error: err instanceof Error ? err : new Error("Marketing pages list failed"),
|
|
1171
|
-
});
|
|
1172
|
-
}
|
|
1173
|
-
}, [client, siteId, enabled]);
|
|
1174
|
-
useEffect(() => {
|
|
1175
|
-
void fetchOnce();
|
|
1176
|
-
}, [fetchOnce]);
|
|
1177
|
-
return { ...state, refresh: fetchOnce };
|
|
1142
|
+
void controller.load();
|
|
1143
|
+
}, [controller, enabled]);
|
|
1144
|
+
return { ...marketingListView(snap), refresh };
|
|
1178
1145
|
}
|
|
1179
1146
|
/**
|
|
1180
1147
|
* Returns a stable `preload(pageKey)` function that warms the SDK cache for
|
|
@@ -1184,15 +1151,15 @@ export function useMarketingPagesList(options) {
|
|
|
1184
1151
|
*/
|
|
1185
1152
|
export function useMarketingPagePreloader(options) {
|
|
1186
1153
|
const { apiKey, baseUrl, siteId } = options;
|
|
1187
|
-
const client
|
|
1154
|
+
const { client, controller } = useMarketingController(apiKey, baseUrl, siteId);
|
|
1188
1155
|
return useCallback(async (pageKey) => {
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
}, [client, siteId]);
|
|
1156
|
+
// The core swallows prefetch failures on purpose — a guess about
|
|
1157
|
+
// where the visitor is going must never surface as an error on the
|
|
1158
|
+
// page they are actually reading. Reading the cache afterwards is
|
|
1159
|
+
// how this keeps returning the response its signature promises.
|
|
1160
|
+
await controller.prefetch(pageKey);
|
|
1161
|
+
return client.site(siteId).marketingPages.getCached(pageKey) ?? null;
|
|
1162
|
+
}, [client, controller, siteId]);
|
|
1196
1163
|
}
|
|
1197
1164
|
// ── Marketing-flow hooks (schema-driven, see ./react-marketing.tsx) ────────
|
|
1198
1165
|
export { useMarketingBundlePreloader, useMarketingLocales, useMarketingPageBundle, useMarketingPageSeo, useMarketingSection, usePublishedMarketingSchema, } from "./react-marketing.js";
|
|
@@ -1205,7 +1172,7 @@ export { useMarketingBundlePreloader, useMarketingLocales, useMarketingPageBundl
|
|
|
1205
1172
|
*/
|
|
1206
1173
|
export function VerifyEmailFlow(props) {
|
|
1207
1174
|
const { apiKey, baseUrl, sessionToken, currentEmail } = props;
|
|
1208
|
-
const client = useMemo(() => new
|
|
1175
|
+
const client = useMemo(() => new BdApiClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
|
|
1209
1176
|
const [email, setEmail] = useState(currentEmail ?? "");
|
|
1210
1177
|
const [sent, setSent] = useState(false);
|
|
1211
1178
|
const [busy, setBusy] = useState(false);
|
|
@@ -1239,7 +1206,7 @@ export function VerifyEmailFlow(props) {
|
|
|
1239
1206
|
*/
|
|
1240
1207
|
export function VerifyEmailConfirm(props) {
|
|
1241
1208
|
const { apiKey, baseUrl, sessionToken, token, onVerified } = props;
|
|
1242
|
-
const client = useMemo(() => new
|
|
1209
|
+
const client = useMemo(() => new BdApiClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
|
|
1243
1210
|
const [state, setState] = useState({ kind: "idle" });
|
|
1244
1211
|
useEffect(() => {
|
|
1245
1212
|
let cancelled = false;
|
|
@@ -1279,7 +1246,7 @@ export function VerifyEmailConfirm(props) {
|
|
|
1279
1246
|
*/
|
|
1280
1247
|
export function VerifyPhoneFlow(props) {
|
|
1281
1248
|
const { apiKey, baseUrl, sessionToken, currentPhone, onVerified } = props;
|
|
1282
|
-
const client = useMemo(() => new
|
|
1249
|
+
const client = useMemo(() => new BdApiClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
|
|
1283
1250
|
const [phone, setPhone] = useState(currentPhone ?? "");
|
|
1284
1251
|
const [phase, setPhase] = useState("enter");
|
|
1285
1252
|
const [otp, setOtp] = useState("");
|
|
@@ -1342,7 +1309,7 @@ export function VerifyPhoneFlow(props) {
|
|
|
1342
1309
|
* bundled: it's ~1,600 entries that change only when Lucide does. An author who
|
|
1343
1310
|
* curated `allowedIcons` skips the fetch entirely.
|
|
1344
1311
|
*/
|
|
1345
|
-
function
|
|
1312
|
+
function BdIconSelect({ field, value, set, }) {
|
|
1346
1313
|
const cfg = field
|
|
1347
1314
|
.iconConfig ?? {};
|
|
1348
1315
|
const curated = cfg.allowedIcons ?? [];
|
|
@@ -1364,7 +1331,7 @@ function BiabIconSelect({ field, value, set, }) {
|
|
|
1364
1331
|
};
|
|
1365
1332
|
}, [curated.length]);
|
|
1366
1333
|
const results = useMemo(() => filterIconNames(all, query), [all, query]);
|
|
1367
|
-
return (_jsxs("div", { className: "
|
|
1334
|
+
return (_jsxs("div", { className: "bd-iconselect", children: [_jsx("input", { className: "bd-input bd-iconselect__search", onChange: (e) => setQuery(e.target.value), placeholder: field.placeholder || "Search icons…", type: "text", value: query }), selected && (_jsxs("span", { className: "bd-iconselect__current", children: [_jsx("img", { alt: selected, height: 16, src: iconUrl(selected), width: 16 }), _jsx("button", { className: "bd-iconselect__clear", onClick: () => set(""), type: "button", children: "\u00D7" })] })), _jsx("div", { className: "bd-iconselect__grid", children: results.map((name) => (_jsx("button", { "aria-label": name, "aria-pressed": selected === name, className: `bd-iconselect__item${selected === name ? "is-selected" : ""}`, onClick: () => set(name), title: name, type: "button", children: _jsx("img", { alt: "", height: 16, src: iconUrl(name), width: 16 }) }, name))) })] }));
|
|
1368
1335
|
}
|
|
1369
1336
|
/**
|
|
1370
1337
|
* `image_input` — one image, from an upload OR a pasted URL.
|
|
@@ -1373,13 +1340,13 @@ function BiabIconSelect({ field, value, set, }) {
|
|
|
1373
1340
|
* pending file and flushed by the controller at submit, exactly like
|
|
1374
1341
|
* `file_upload`, then replaced by its stored URL.
|
|
1375
1342
|
*/
|
|
1376
|
-
function
|
|
1343
|
+
function BdImageInput({ field, value, set, }) {
|
|
1377
1344
|
const cfg = field.imageInputConfig ?? {};
|
|
1378
1345
|
const sources = cfg.sources ?? "both";
|
|
1379
1346
|
const [mode, setMode] = useState(sources === "url" ? "url" : "upload");
|
|
1380
1347
|
const pending = isPendingFileValue(value) ? value : null;
|
|
1381
1348
|
const url = typeof value === "string" ? value : "";
|
|
1382
|
-
return (_jsxs("div", { className: "
|
|
1349
|
+
return (_jsxs("div", { className: "bd-imageinput", children: [sources === "both" && (_jsx("div", { className: "bd-imageinput__modes", children: ["upload", "url"].map((m) => (_jsx("button", { "aria-pressed": mode === m, className: `bd-imageinput__mode${mode === m ? "is-active" : ""}`, onClick: () => setMode(m), type: "button", children: m === "upload" ? "Upload" : "Paste URL" }, m))) })), mode === "upload" && sources !== "url" ? (_jsx("input", { accept: (cfg.allowedTypes ?? ["image/*"]).join(","), className: "bd-input bd-imageinput__file", onChange: (e) => {
|
|
1383
1350
|
const file = e.target.files?.[0];
|
|
1384
1351
|
if (!file)
|
|
1385
1352
|
return;
|
|
@@ -1392,44 +1359,44 @@ function BiabImageInput({ field, value, set, }) {
|
|
|
1392
1359
|
name: file.name,
|
|
1393
1360
|
size: file.size,
|
|
1394
1361
|
});
|
|
1395
|
-
}, type: "file" })) : (_jsx("input", { className: "
|
|
1362
|
+
}, type: "file" })) : (_jsx("input", { className: "bd-input bd-imageinput__url", onChange: (e) => set(e.target.value), placeholder: field.placeholder || "https://example.com/image.png", type: "url", value: url })), cfg.showPreview !== false && (pending || url) ? (_jsx("img", { alt: "", className: "bd-imageinput__preview", src: pending ? "" : url })) : null, pending && (_jsxs("span", { className: "bd-imageinput__pending", children: [pending.name, " \u2014 uploads on submit."] }))] }));
|
|
1396
1363
|
}
|
|
1397
|
-
// A `
|
|
1398
|
-
// drop `<
|
|
1399
|
-
const
|
|
1364
|
+
// A `BdFormProvider` lets a consumer supply the client once at the root and
|
|
1365
|
+
// drop `<BdForm slug="…" />` anywhere below without threading the client prop.
|
|
1366
|
+
const BdFormClientContext = createContext(null);
|
|
1400
1367
|
/** Production package-API base URL — the default target for a publishable token
|
|
1401
1368
|
* when no `baseUrl` is supplied. Override via the `baseUrl` prop for staging or
|
|
1402
1369
|
* a custom host. Use the **apex** host: `www.biab.app` 307-redirects to the
|
|
1403
1370
|
* apex, and a CORS preflight does not follow redirects (the browser fails the
|
|
1404
1371
|
* request), so a browser publishable-token client must target the apex. */
|
|
1405
1372
|
export const DEFAULT_PACKAGE_BASE_URL = "https://biab.app/api/package/v1";
|
|
1406
|
-
/** Provide a
|
|
1373
|
+
/** Provide a BD forms client to every nested `<BdForm>`. Pass a `client`, or
|
|
1407
1374
|
* a publishable `token` + `siteId` (+ optional `baseUrl`) to build a browser
|
|
1408
1375
|
* client. */
|
|
1409
|
-
export function
|
|
1376
|
+
export function BdFormProvider({ client, token, siteId, baseUrl, children, }) {
|
|
1410
1377
|
const resolved = useMemo(() => client ??
|
|
1411
1378
|
(token && siteId
|
|
1412
|
-
?
|
|
1379
|
+
? createBdClient({
|
|
1413
1380
|
apiKey: token,
|
|
1414
1381
|
siteId,
|
|
1415
1382
|
baseUrl: baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
|
|
1416
1383
|
})
|
|
1417
1384
|
: null), [client, token, siteId, baseUrl]);
|
|
1418
|
-
return (_jsx(
|
|
1385
|
+
return (_jsx(BdFormClientContext.Provider, { value: resolved, children: children }));
|
|
1419
1386
|
}
|
|
1420
|
-
/** Read the client from a surrounding `<
|
|
1421
|
-
export function
|
|
1422
|
-
return useContext(
|
|
1387
|
+
/** Read the client from a surrounding `<BdFormProvider>`, if any. */
|
|
1388
|
+
export function useBdFormsClient() {
|
|
1389
|
+
return useContext(BdFormClientContext);
|
|
1423
1390
|
}
|
|
1424
1391
|
const FetchScheduleSlotsContext = createContext(undefined);
|
|
1425
|
-
/** Autofill (`autocomplete`) config for the field tree, provided by `<
|
|
1392
|
+
/** Autofill (`autocomplete`) config for the field tree, provided by `<BdForm>`.
|
|
1426
1393
|
* Default `{}` → inference on, no overrides. */
|
|
1427
1394
|
const AutocompleteContext = createContext({});
|
|
1428
|
-
/** Auto-schedule (instant booking) picker for `<
|
|
1395
|
+
/** Auto-schedule (instant booking) picker for `<BdForm>`: fetches the assigned
|
|
1429
1396
|
* staff's open slots and lets the visitor pick one. Value `{ slotStartAt,
|
|
1430
1397
|
* timezone }`; the server books the meeting on submit. Dependency-light (plain
|
|
1431
|
-
* elements + `
|
|
1432
|
-
function
|
|
1398
|
+
* elements + `bd-field__schedule-*` classes). */
|
|
1399
|
+
function BdScheduleAuto({ field, value, onChange, }) {
|
|
1433
1400
|
const fetchSlots = useContext(FetchScheduleSlotsContext);
|
|
1434
1401
|
const v = value && typeof value === "object" ? value : {};
|
|
1435
1402
|
const tz = typeof Intl !== "undefined"
|
|
@@ -1460,16 +1427,16 @@ function BiabScheduleAuto({ field, value, onChange, }) {
|
|
|
1460
1427
|
};
|
|
1461
1428
|
}, [fetchSlots, field.id]);
|
|
1462
1429
|
if (!fetchSlots) {
|
|
1463
|
-
return (_jsx("p", { className: "
|
|
1430
|
+
return (_jsx("p", { className: "bd-field__schedule-hint", children: "Booking isn't available here." }));
|
|
1464
1431
|
}
|
|
1465
1432
|
if (phase === "loading" || phase === "idle") {
|
|
1466
|
-
return
|
|
1433
|
+
return _jsx("p", { className: "bd-field__schedule-hint", children: "Loading available times\u2026" });
|
|
1467
1434
|
}
|
|
1468
1435
|
if (phase === "error") {
|
|
1469
|
-
return (_jsx("p", { className: "
|
|
1436
|
+
return (_jsx("p", { className: "bd-field__schedule-hint", children: "Couldn't load times right now." }));
|
|
1470
1437
|
}
|
|
1471
1438
|
if (slots.length === 0) {
|
|
1472
|
-
return (_jsx("p", { className: "
|
|
1439
|
+
return (_jsx("p", { className: "bd-field__schedule-hint", children: "No open times in the next few weeks." }));
|
|
1473
1440
|
}
|
|
1474
1441
|
const byDay = new Map();
|
|
1475
1442
|
for (const s of slots) {
|
|
@@ -1487,7 +1454,7 @@ function BiabScheduleAuto({ field, value, onChange, }) {
|
|
|
1487
1454
|
arr.push({ startAt: s.startAt, label });
|
|
1488
1455
|
byDay.set(dayKey, arr);
|
|
1489
1456
|
}
|
|
1490
|
-
return (_jsx("div", { className: "
|
|
1457
|
+
return (_jsx("div", { className: "bd-field__schedule-auto", children: [...byDay.entries()].map(([day, times]) => (_jsxs("div", { className: "bd-field__schedule-day", children: [_jsx("p", { className: "bd-field__schedule-day-label", children: day }), _jsx("div", { className: "bd-field__schedule-times", children: times.map((t) => (_jsx("button", { className: "bd-field__schedule-slot", "data-selected": v.slotStartAt === t.startAt ? "true" : undefined, onClick: () => onChange({ slotStartAt: t.startAt, timezone: tz }), type: "button", children: t.label }, t.startAt))) })] }, day))) }));
|
|
1491
1458
|
}
|
|
1492
1459
|
/**
|
|
1493
1460
|
* Subscribe a component to a `FormController` via React's official external-store
|
|
@@ -1500,7 +1467,7 @@ function useFormSnapshot(controller) {
|
|
|
1500
1467
|
const getSnapshot = useCallback(() => controller.snapshot(), [controller]);
|
|
1501
1468
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
1502
1469
|
}
|
|
1503
|
-
// ──
|
|
1470
|
+
// ── bd-* class helpers (mirrors FormPreview's `fieldCssKey`) ───────────────
|
|
1504
1471
|
function cx(...parts) {
|
|
1505
1472
|
return parts.filter(Boolean).join(" ");
|
|
1506
1473
|
}
|
|
@@ -1573,7 +1540,7 @@ function safeHttpHref(raw) {
|
|
|
1573
1540
|
// ── Self-contained date-range picker (no external calendar dep) ─────────────
|
|
1574
1541
|
// A popover trigger + a month grid with click-to-select range. Stored value is
|
|
1575
1542
|
// `{ start, end }` as `yyyy-MM-dd` strings (identical to the app's contract).
|
|
1576
|
-
// Styled via
|
|
1543
|
+
// Styled via bd-* classes so consumers can theme it.
|
|
1577
1544
|
function parseIsoDate(s) {
|
|
1578
1545
|
if (!s)
|
|
1579
1546
|
return null;
|
|
@@ -1586,7 +1553,7 @@ function toIsoDate(d) {
|
|
|
1586
1553
|
const day = String(d.getDate()).padStart(2, "0");
|
|
1587
1554
|
return `${y}-${m}-${day}`;
|
|
1588
1555
|
}
|
|
1589
|
-
const
|
|
1556
|
+
const BD_MONTHS = [
|
|
1590
1557
|
"January",
|
|
1591
1558
|
"February",
|
|
1592
1559
|
"March",
|
|
@@ -1600,8 +1567,8 @@ const BIAB_MONTHS = [
|
|
|
1600
1567
|
"November",
|
|
1601
1568
|
"December",
|
|
1602
1569
|
];
|
|
1603
|
-
const
|
|
1604
|
-
function
|
|
1570
|
+
const BD_WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
1571
|
+
function BdDateRange({ value, setValue, }) {
|
|
1605
1572
|
const v = (value && typeof value === "object" ? value : {});
|
|
1606
1573
|
const start = parseIsoDate(v.start);
|
|
1607
1574
|
const end = parseIsoDate(v.end);
|
|
@@ -1658,48 +1625,48 @@ function BiabDateRange({ value, setValue, }) {
|
|
|
1658
1625
|
: `${v.start} → …`
|
|
1659
1626
|
: "Pick a date range";
|
|
1660
1627
|
// Positioning is the only inline style (so the popover never overlaps content
|
|
1661
|
-
// even before the CSS loads); everything visual lives in `
|
|
1628
|
+
// even before the CSS loads); everything visual lives in `bd-forms.css`,
|
|
1662
1629
|
// which mirrors the COSS/shadcn calendar (import it from the SDK).
|
|
1663
|
-
return (_jsxs("div", { className: "
|
|
1630
|
+
return (_jsxs("div", { className: "bd-daterange", ref: ref, style: { position: "relative" }, children: [_jsx("button", { className: "bd-input bd-field__input bd-daterange__trigger", onClick: () => setOpen((o) => !o), type: "button", children: label }), open && (_jsxs("div", { className: "bd-daterange__popover", style: { position: "absolute", zIndex: 50 }, children: [_jsxs("div", { className: "bd-daterange__nav", children: [_jsx("button", { "aria-label": "Previous month", className: "bd-daterange__navbtn", onClick: () => setView((s) => s.m === 0 ? { y: s.y - 1, m: 11 } : { y: s.y, m: s.m - 1 }), type: "button", children: "\u2039" }), _jsxs("span", { className: "bd-daterange__title", children: [BD_MONTHS[view.m], " ", view.y] }), _jsx("button", { "aria-label": "Next month", className: "bd-daterange__navbtn", onClick: () => setView((s) => s.m === 11 ? { y: s.y + 1, m: 0 } : { y: s.y, m: s.m + 1 }), type: "button", children: "\u203A" })] }), _jsxs("div", { className: "bd-daterange__grid", children: [BD_WEEKDAYS.map((w) => (_jsx("span", { className: "bd-daterange__dow", children: w }, w))), cells.map((d, i) => {
|
|
1664
1631
|
if (!d) {
|
|
1665
|
-
return
|
|
1632
|
+
return _jsx("span", { className: "bd-daterange__blank" }, `b-${i}`);
|
|
1666
1633
|
}
|
|
1667
1634
|
const st = dayState(d);
|
|
1668
|
-
return (_jsx("button", { className: "
|
|
1635
|
+
return (_jsx("button", { className: "bd-daterange__day", "data-range-end": st.end || undefined, "data-range-middle": st.middle || undefined, "data-range-start": st.start || undefined, "data-selected-single": st.single || undefined, "data-today": st.today || undefined, onClick: () => pick(d), type: "button", children: d.getDate() }, toIsoDate(d)));
|
|
1669
1636
|
})] })] }))] }));
|
|
1670
1637
|
}
|
|
1671
|
-
// ── Per-type input renderer (light DOM,
|
|
1672
|
-
function
|
|
1638
|
+
// ── Per-type input renderer (light DOM, bd-* classes, no shadcn) ──────────
|
|
1639
|
+
function BdFieldInput({ field, value, setValue, }) {
|
|
1673
1640
|
const set = (v) => setValue(field.id, v);
|
|
1674
1641
|
const ph = field.placeholder || "";
|
|
1675
1642
|
// Resolved HTML autofill token for this field (or undefined to omit the
|
|
1676
|
-
// attribute). Honors the global `<
|
|
1643
|
+
// attribute). Honors the global `<BdForm autoComplete>` switch and per-field
|
|
1677
1644
|
// overrides; see forms-core/autocomplete.ts.
|
|
1678
1645
|
const acConfig = useContext(AutocompleteContext);
|
|
1679
1646
|
const autoComplete = resolveFieldAutocomplete(field, acConfig);
|
|
1680
1647
|
switch (field.type) {
|
|
1681
1648
|
case "title":
|
|
1682
|
-
return _jsx("h2", { className: "
|
|
1649
|
+
return _jsx("h2", { className: "bd-field__title", children: field.label || "Untitled" });
|
|
1683
1650
|
case "subtitle":
|
|
1684
|
-
return _jsx("h3", { className: "
|
|
1651
|
+
return _jsx("h3", { className: "bd-field__subtitle", children: field.label || "" });
|
|
1685
1652
|
case "separator":
|
|
1686
|
-
return _jsx("hr", { className: "
|
|
1653
|
+
return _jsx("hr", { className: "bd-field__separator" });
|
|
1687
1654
|
case "richtext":
|
|
1688
1655
|
// `content` is server-sanitized HTML.
|
|
1689
|
-
return field.content ? (_jsx("div", { className: "
|
|
1656
|
+
return field.content ? (_jsx("div", { className: "bd-field__richtext",
|
|
1690
1657
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: server-sanitized
|
|
1691
1658
|
dangerouslySetInnerHTML: { __html: field.content } })) : null;
|
|
1692
1659
|
case "body_text":
|
|
1693
1660
|
// Plain body paragraph (no HTML). Display only.
|
|
1694
|
-
return field.content ? (_jsx("p", { className: "
|
|
1661
|
+
return field.content ? (_jsx("p", { className: "bd-field__body", children: field.content })) : null;
|
|
1695
1662
|
case "bullet_list": {
|
|
1696
1663
|
const lc = field.listConfig ?? {};
|
|
1697
1664
|
const variant = lc.variant ?? "bullets";
|
|
1698
1665
|
const items = lc.items ?? [];
|
|
1699
1666
|
if (variant === "details") {
|
|
1700
|
-
return (_jsx("div", { className: "
|
|
1667
|
+
return (_jsx("div", { className: "bd-field__points", children: items.map((it, i) => (_jsxs("div", { className: "bd-field__point", children: [it.icon && (_jsx("span", { "aria-hidden": "true", className: "bd-field__point-icon", "data-icon": it.icon })), _jsxs("span", { className: "bd-field__point-body", children: [_jsx("span", { className: "bd-field__point-title", children: it.text }), it.description && (_jsx("span", { className: "bd-field__point-desc", children: it.description }))] })] }, `${field.id}-li-${i}`))) }));
|
|
1701
1668
|
}
|
|
1702
|
-
return (_jsxs("div", { className: "
|
|
1669
|
+
return (_jsxs("div", { className: "bd-field__bullets", children: [lc.title && (_jsx("div", { className: "bd-field__bullets-title", children: lc.title })), _jsx("ul", { children: items.map((it, i) => (_jsx("li", { children: it.text }, `${field.id}-li-${i}`))) })] }));
|
|
1703
1670
|
}
|
|
1704
1671
|
case "table": {
|
|
1705
1672
|
const cfg = field.tableConfig ?? {};
|
|
@@ -1707,7 +1674,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1707
1674
|
const rows = cfg.rows ?? [];
|
|
1708
1675
|
if (columns.length === 0)
|
|
1709
1676
|
return null;
|
|
1710
|
-
return (_jsx("div", { className: "
|
|
1677
|
+
return (_jsx("div", { className: "bd-field__table", children: _jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((col, ci) => (_jsx("th", { "data-align": col.align ?? "left", "data-highlight": cfg.highlightColumn === ci ? "" : undefined, children: col.header }, `${field.id}-col-${ci}`))) }) }), _jsx("tbody", { children: rows.map((row, ri) => (_jsx("tr", { children: columns.map((col, ci) => {
|
|
1711
1678
|
const cell = row.cells?.[ci] ?? "";
|
|
1712
1679
|
const check = col.type === "check" ? tableCheckState(cell) : "text";
|
|
1713
1680
|
return (_jsx("td", { "data-align": col.align ?? "left", "data-check": check === "text" ? undefined : check, "data-highlight": cfg.highlightColumn === ci ? "" : undefined, children: check === "yes" ? "✓" : check === "no" ? "–" : cell }, `${field.id}-cell-${ri}-${ci}`));
|
|
@@ -1720,13 +1687,13 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1720
1687
|
.calculationConfig;
|
|
1721
1688
|
const hasVal = value !== null && value !== undefined && value !== "";
|
|
1722
1689
|
if (cfg?.sensitive && !hasVal) {
|
|
1723
|
-
return (_jsx("output", { className: "
|
|
1690
|
+
return (_jsx("output", { className: "bd-field__calc bd-field__calc--pending", children: "Calculated on submit." }));
|
|
1724
1691
|
}
|
|
1725
|
-
return (_jsx("output", { className: "
|
|
1692
|
+
return (_jsx("output", { className: "bd-field__calc", children: hasVal ? formatCalcValue(value, cfg?.display) : "—" }));
|
|
1726
1693
|
}
|
|
1727
1694
|
case "image": {
|
|
1728
1695
|
const cfg = (field["imageConfig"] ?? {});
|
|
1729
|
-
return cfg.src ? (_jsx("img", { alt: cfg.alt || "", className: "
|
|
1696
|
+
return cfg.src ? (_jsx("img", { alt: cfg.alt || "", className: "bd-field__image", src: cfg.src, style: {
|
|
1730
1697
|
width: cfg.width ? `${cfg.width}px` : "auto",
|
|
1731
1698
|
height: cfg.height ? `${cfg.height}px` : "auto",
|
|
1732
1699
|
maxWidth: "100%",
|
|
@@ -1734,7 +1701,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1734
1701
|
}
|
|
1735
1702
|
case "textarea": {
|
|
1736
1703
|
const cfg = (field["textareaConfig"] ?? {});
|
|
1737
|
-
return (_jsx("textarea", { autoComplete: autoComplete, className: "
|
|
1704
|
+
return (_jsx("textarea", { autoComplete: autoComplete, className: "bd-textarea bd-field__input", onChange: (e) => set(e.target.value), placeholder: ph, rows: cfg.rows ?? 4, style: cfg.resizable
|
|
1738
1705
|
? { resize: "vertical", minHeight: "6rem", maxHeight: "30rem" }
|
|
1739
1706
|
: { resize: "none" }, value: typeof value === "string" ? value : "" }));
|
|
1740
1707
|
}
|
|
@@ -1745,27 +1712,27 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1745
1712
|
// gives native typeahead with no JS and no styling opinion — which is
|
|
1746
1713
|
// exactly the SDK's contract.
|
|
1747
1714
|
if (shouldSearchOptions(opts.length, field.display)) {
|
|
1748
|
-
const listId = `
|
|
1749
|
-
return (_jsxs(_Fragment, { children: [_jsx("input", { className: "
|
|
1715
|
+
const listId = `bd-list-${field.id}`;
|
|
1716
|
+
return (_jsxs(_Fragment, { children: [_jsx("input", { className: "bd-input bd-combobox bd-field__input", list: listId, onChange: (e) => set(e.target.value), placeholder: ph || "Search…", type: "text", value: typeof value === "string" ? value : "" }), _jsx("datalist", { id: listId, children: opts.map((opt) => (_jsx("option", { value: opt }, opt))) })] }));
|
|
1750
1717
|
}
|
|
1751
|
-
return (_jsxs("select", { className: "
|
|
1718
|
+
return (_jsxs("select", { className: "bd-select bd-field__input", onChange: (e) => set(e.target.value), value: typeof value === "string" ? value : "", children: [_jsx("option", { value: "", children: ph || "Select…" }), opts.map((opt) => (_jsx("option", { value: opt, children: opt }, opt)))] }));
|
|
1752
1719
|
}
|
|
1753
1720
|
case "icon_select":
|
|
1754
|
-
return _jsx(
|
|
1721
|
+
return _jsx(BdIconSelect, { field: field, set: set, value: value });
|
|
1755
1722
|
case "image_input":
|
|
1756
|
-
return _jsx(
|
|
1723
|
+
return _jsx(BdImageInput, { field: field, set: set, value: value });
|
|
1757
1724
|
case "radio":
|
|
1758
|
-
return (_jsx("div", { className: "
|
|
1725
|
+
return (_jsx("div", { className: "bd-field__radios", children: (field.options ?? []).map((opt) => (_jsxs("label", { className: "bd-field__radio", children: [_jsx("input", { checked: value === opt, name: field.id, onChange: () => set(opt), type: "radio", value: opt }), _jsx("span", { children: opt })] }, opt))) }));
|
|
1759
1726
|
case "checkbox": {
|
|
1760
1727
|
const arr = Array.isArray(value) ? value : [];
|
|
1761
1728
|
// Multi-Select element: a checkbox (value = string[]) rendered as a native
|
|
1762
1729
|
// multiple-select. The SDK ships unstyled HTML; consumers style it.
|
|
1763
1730
|
if (field.display === "dropdown") {
|
|
1764
|
-
return (_jsx("select", { className: "
|
|
1731
|
+
return (_jsx("select", { className: "bd-select bd-field__input", multiple: true, onChange: (e) => set(Array.from(e.target.selectedOptions).map((o) => o.value)), value: arr, children: (field.options ?? []).map((opt) => (_jsx("option", { value: opt, children: opt }, opt))) }));
|
|
1765
1732
|
}
|
|
1766
|
-
return (_jsx("div", { className: "
|
|
1733
|
+
return (_jsx("div", { className: "bd-field__checkboxes", children: (field.options ?? []).map((opt) => {
|
|
1767
1734
|
const checked = arr.includes(opt);
|
|
1768
|
-
return (_jsxs("label", { className: "
|
|
1735
|
+
return (_jsxs("label", { className: "bd-field__checkbox", children: [_jsx("input", { checked: checked, onChange: (e) => set(e.target.checked
|
|
1769
1736
|
? [...arr, opt]
|
|
1770
1737
|
: arr.filter((x) => x !== opt)), type: "checkbox" }), _jsx("span", { children: opt })] }, opt));
|
|
1771
1738
|
}) }));
|
|
@@ -1786,17 +1753,17 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1786
1753
|
set(single === label ? "" : label);
|
|
1787
1754
|
}
|
|
1788
1755
|
};
|
|
1789
|
-
return (_jsx("div", { className: "
|
|
1756
|
+
return (_jsx("div", { className: "bd-field__choice-cards", role: ccMulti ? "group" : "radiogroup", children: ccOptions.map((opt) => {
|
|
1790
1757
|
const active = ccMulti
|
|
1791
1758
|
? arr.includes(opt.label)
|
|
1792
1759
|
: single === opt.label;
|
|
1793
|
-
return (_jsxs("button", { "aria-pressed": active, className: "
|
|
1760
|
+
return (_jsxs("button", { "aria-pressed": active, className: "bd-field__choice-card", "data-active": active ? "" : undefined, "data-product-id": opt.productId || undefined, onClick: () => toggle(opt.label), role: ccMulti ? "checkbox" : "radio", type: "button", children: [opt.imageUrl ? (_jsx("img", { alt: opt.label, className: "bd-field__choice-card-image", src: opt.imageUrl })) : opt.icon ? (_jsx("span", { "aria-hidden": "true", className: "bd-field__choice-card-icon", "data-icon": opt.icon })) : null, _jsxs("span", { className: "bd-field__choice-card-body", children: [_jsxs("span", { className: "bd-field__choice-card-header", children: [_jsx("span", { className: "bd-field__choice-card-label", children: opt.label }), opt.price ? (_jsx("span", { className: "bd-field__choice-card-price", children: opt.price })) : null] }), opt.description ? (_jsx("span", { className: "bd-field__choice-card-description", children: opt.description })) : null, safeHttpHref(opt.href) ? (_jsx("a", { className: "bd-field__choice-card-link", href: safeHttpHref(opt.href), onClick: (e) => e.stopPropagation(), rel: "noopener noreferrer nofollow", target: "_blank", children: "View" })) : null] })] }, opt.label));
|
|
1794
1761
|
}) }));
|
|
1795
1762
|
}
|
|
1796
1763
|
case "boolean":
|
|
1797
|
-
return (_jsxs("label", { className: "
|
|
1764
|
+
return (_jsxs("label", { className: "bd-field__switch", children: [_jsx("input", { checked: value === true, onChange: (e) => set(e.target.checked), type: "checkbox" }), _jsx("span", { children: field.placeholder || "Yes / No" })] }));
|
|
1798
1765
|
case "date":
|
|
1799
|
-
return (_jsx("input", { className: "
|
|
1766
|
+
return (_jsx("input", { className: "bd-input bd-field__input", onChange: (e) => set(e.target.value), type: "date", value: typeof value === "string" ? value : "" }));
|
|
1800
1767
|
case "time_picker": {
|
|
1801
1768
|
const mode = field.timeConfig?.mode ?? "simple";
|
|
1802
1769
|
if (mode === "simple") {
|
|
@@ -1817,7 +1784,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1817
1784
|
const h12 = h % 12 === 0 ? 12 : h % 12;
|
|
1818
1785
|
return `${h12}:${mStr} ${ampm}`;
|
|
1819
1786
|
};
|
|
1820
|
-
return (_jsxs("select", { className: "
|
|
1787
|
+
return (_jsxs("select", { className: "bd-select bd-field__input", onChange: (e) => set(e.target.value), value: typeof value === "string" ? value : "", children: [_jsx("option", { value: "", children: ph || "Select time" }), opts.map((o) => (_jsx("option", { value: o, children: fmt(o) }, o)))] }));
|
|
1821
1788
|
}
|
|
1822
1789
|
const pair = mode === "time_block"
|
|
1823
1790
|
? ["start", "end"]
|
|
@@ -1826,10 +1793,10 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1826
1793
|
? value
|
|
1827
1794
|
: {};
|
|
1828
1795
|
const inputType = (k) => (k === "date" ? "date" : "time");
|
|
1829
|
-
return (_jsx("div", { className: "
|
|
1796
|
+
return (_jsx("div", { className: "bd-field__grid2", children: pair.map((k) => (_jsxs("label", { className: "bd-field__sub", children: [_jsxs("span", { className: "bd-field__sublabel", children: [k[0]?.toUpperCase(), k.slice(1)] }), _jsx("input", { className: "bd-input", onChange: (e) => set({ ...v, [k]: e.target.value }), type: inputType(k), value: v[k] ?? "" })] }, k))) }));
|
|
1830
1797
|
}
|
|
1831
1798
|
case "date_range":
|
|
1832
|
-
return _jsx(
|
|
1799
|
+
return _jsx(BdDateRange, { setValue: (next) => set(next), value: value });
|
|
1833
1800
|
case "slider": {
|
|
1834
1801
|
const min = field.sliderConfig?.min ?? 0;
|
|
1835
1802
|
const max = field.sliderConfig?.max ?? 100;
|
|
@@ -1837,23 +1804,41 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1837
1804
|
const unit = field.sliderConfig?.unit;
|
|
1838
1805
|
const fmt = (n) => unit === "currency" ? `$${n}` : unit === "percent" ? `${n}%` : `${n}`;
|
|
1839
1806
|
const v = typeof value === "number" ? value : min;
|
|
1840
|
-
return (_jsxs("div", { className: "
|
|
1807
|
+
return (_jsxs("div", { className: "bd-field__slider", children: [_jsxs("div", { className: "bd-field__sliderscale", children: [_jsx("span", { children: fmt(min) }), _jsx("span", { className: "bd-field__slidervalue", children: fmt(v) }), _jsx("span", { children: fmt(max) })] }), _jsx("input", { max: max, min: min, onChange: (e) => set(Number(e.target.value)), step: step, type: "range", value: v })] }));
|
|
1841
1808
|
}
|
|
1842
1809
|
case "star_rating": {
|
|
1843
1810
|
const max = field.starRatingConfig?.maxStars ?? 5;
|
|
1844
1811
|
const v = typeof value === "number" ? value : 0;
|
|
1845
|
-
return (_jsx("div", { className: "
|
|
1812
|
+
return (_jsx("div", { className: "bd-field__stars", children: Array.from({ length: max }).map((_, i) => (_jsx("button", { "aria-label": `${i + 1} star${i ? "s" : ""}`, className: cx("bd-field__star", i < v && "bd-field__star--on"), onClick: () => set(i + 1), type: "button", children: "\u2605" }, `${field.id}-star-${i}`))) }));
|
|
1846
1813
|
}
|
|
1847
1814
|
case "happiness_rating": {
|
|
1848
1815
|
const v = typeof value === "number" ? value : -1;
|
|
1849
|
-
return (_jsx("div", { className: "
|
|
1816
|
+
return (_jsx("div", { className: "bd-field__happiness", children: ["😞", "😕", "😐", "🙂", "😄"].map((emoji, i) => (_jsx("button", { className: cx("bd-field__emoji", i === v && "bd-field__emoji--on"), onClick: () => set(i), type: "button", children: emoji }, emoji))) }));
|
|
1850
1817
|
}
|
|
1851
1818
|
case "stepper": {
|
|
1852
1819
|
const v = typeof value === "number" ? value : 0;
|
|
1853
|
-
return (_jsxs("div", { className: "
|
|
1820
|
+
return (_jsxs("div", { className: "bd-field__stepper", children: [_jsx("button", { className: "bd-btn bd-btn--ghost", onClick: () => set(v - 1), type: "button", children: "\u2212" }), _jsx("input", { className: "bd-field__steppervalue", inputMode: "numeric", onChange: (e) => {
|
|
1854
1821
|
const n = Number(e.target.value.replace(/[^\d-]/g, ""));
|
|
1855
1822
|
set(Number.isFinite(n) ? n : 0);
|
|
1856
|
-
}, value: String(v) }), _jsx("button", { className: "
|
|
1823
|
+
}, value: String(v) }), _jsx("button", { className: "bd-btn bd-btn--ghost", onClick: () => set(v + 1), type: "button", children: "+" })] }));
|
|
1824
|
+
}
|
|
1825
|
+
case "name": {
|
|
1826
|
+
const v = (value && typeof value === "object" ? value : {});
|
|
1827
|
+
const cfg = field.nameConfig ?? {};
|
|
1828
|
+
// Per-subfield autofill tokens (given/family/honorifics), suppressed when
|
|
1829
|
+
// autofill is off for this field (global switch or per-field override).
|
|
1830
|
+
const acOff = autoComplete === "off";
|
|
1831
|
+
const part = (k, label, ac, placeholder) => (_jsxs("label", { className: "bd-field__sub", children: [_jsx("span", { className: "bd-field__sublabel", children: label }), _jsx("input", { autoComplete: acOff ? "off" : ac, className: "bd-input", onChange: (e) => set({ ...v, [k]: e.target.value }), placeholder: placeholder, value: v[k] ?? "" })] }, k));
|
|
1832
|
+
// The nicknames input keeps the raw comma-separated string while the
|
|
1833
|
+
// visitor types; submit normalizes it into a string[] (and the server
|
|
1834
|
+
// re-normalizes authoritatively).
|
|
1835
|
+
const nicknamesText = Array.isArray(v.nicknames)
|
|
1836
|
+
? v.nicknames.join(", ")
|
|
1837
|
+
: typeof v.nicknames === "string"
|
|
1838
|
+
? v.nicknames
|
|
1839
|
+
: "";
|
|
1840
|
+
return (_jsxs("div", { className: "bd-field__name", children: [cfg.prefix && part("prefix", "Title", "honorific-prefix", "Dr."), cfg.middle ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bd-field__grid2", children: [part("first", "First name", "given-name", "Jane"), part("middle", "Middle name", "additional-name", "Quinn")] }), cfg.suffix ? (_jsxs("div", { className: "bd-field__grid2", children: [part("last", "Last name", "family-name", "Doe"), part("suffix", "Suffix", "honorific-suffix", "Jr.")] })) : (part("last", "Last name", "family-name", "Doe"))] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "bd-field__grid2", children: [part("first", "First name", "given-name", "Jane"), part("last", "Last name", "family-name", "Doe")] }), cfg.suffix &&
|
|
1841
|
+
part("suffix", "Suffix", "honorific-suffix", "Jr.")] })), cfg.nicknames && (_jsxs("label", { className: "bd-field__sub", children: [_jsx("span", { className: "bd-field__sublabel", children: "Nicknames" }), _jsx("input", { autoComplete: acOff ? "off" : undefined, className: "bd-input", onChange: (e) => set({ ...v, nicknames: e.target.value }), placeholder: "Johnny, JM \u2014 separate with commas", value: nicknamesText })] }, "nicknames"))] }));
|
|
1857
1842
|
}
|
|
1858
1843
|
case "address": {
|
|
1859
1844
|
const v = value && typeof value === "object"
|
|
@@ -1867,7 +1852,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1867
1852
|
{ k: "line2", label: "Address Line 2", ac: "address-line2" },
|
|
1868
1853
|
{ k: "city", label: "City", ac: "address-level2" },
|
|
1869
1854
|
];
|
|
1870
|
-
return (_jsxs("div", { className: "
|
|
1855
|
+
return (_jsxs("div", { className: "bd-field__address", children: [rows.map(({ k, label, ac }) => (_jsxs("label", { className: "bd-field__sub", children: [_jsx("span", { className: "bd-field__sublabel", children: label }), _jsx("input", { autoComplete: acOff ? "off" : ac, className: "bd-input", onChange: (e) => set({ ...v, [k]: e.target.value }), value: v[k] ?? "" })] }, k))), _jsxs("label", { className: "bd-field__sub", children: [_jsx("span", { className: "bd-field__sublabel", children: "State" }), _jsxs("select", { autoComplete: acOff ? "off" : "address-level1", className: "bd-select", onChange: (e) => set({ ...v, state: e.target.value }), value: v["state"] ?? "", children: [_jsx("option", { value: "", children: "Select state" }), US_STATES.map((s) => (_jsx("option", { value: s.abbr, children: s.name }, s.abbr)))] })] }, "state"), _jsxs("label", { className: "bd-field__sub", children: [_jsx("span", { className: "bd-field__sublabel", children: "Zip" }), _jsx("input", { autoComplete: acOff ? "off" : "postal-code", className: "bd-input", inputMode: "numeric", onChange: (e) =>
|
|
1871
1856
|
// Numbers only (allow a single hyphen for ZIP+4).
|
|
1872
1857
|
set({ ...v, zip: e.target.value.replace(/[^\d-]/g, "") }), placeholder: "90210", value: v["zip"] ?? "" })] }, "zip")] }));
|
|
1873
1858
|
}
|
|
@@ -1875,7 +1860,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1875
1860
|
const v = typeof value === "string" ? value : "";
|
|
1876
1861
|
const presets = field.options ?? [];
|
|
1877
1862
|
const isCustom = v !== "" && !presets.includes(v);
|
|
1878
|
-
return (_jsxs("div", { className: "
|
|
1863
|
+
return (_jsxs("div", { className: "bd-field__colors", children: [presets.map((color) => (_jsx("button", { className: cx("bd-field__color", v === color && "bd-field__color--on"), onClick: () => set(color), style: { backgroundColor: color }, title: color, type: "button" }, color))), _jsx("label", { className: cx("bd-field__color bd-field__color--custom", isCustom && "bd-field__color--on"), style: isCustom
|
|
1879
1864
|
? { backgroundColor: v }
|
|
1880
1865
|
: {
|
|
1881
1866
|
background: "conic-gradient(red, yellow, lime, cyan, blue, magenta, red)",
|
|
@@ -1891,7 +1876,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1891
1876
|
const cfg = field.fileConfig ?? {};
|
|
1892
1877
|
const maxFiles = cfg.maxFiles ?? 1;
|
|
1893
1878
|
const remaining = Math.max(maxFiles - files.length, 0);
|
|
1894
|
-
return (_jsxs("div", { className: "
|
|
1879
|
+
return (_jsxs("div", { className: "bd-field__files", children: [_jsx("input", { accept: buildAcceptAttr(cfg.allowedTypes), className: "bd-field__fileinput", disabled: remaining === 0, multiple: maxFiles > 1, onChange: (e) => {
|
|
1895
1880
|
const list = e.target.files;
|
|
1896
1881
|
if (!list)
|
|
1897
1882
|
return;
|
|
@@ -1913,7 +1898,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1913
1898
|
if (additions.length > 0)
|
|
1914
1899
|
set([...files, ...additions]);
|
|
1915
1900
|
e.target.value = "";
|
|
1916
|
-
}, type: "file" }), files.length > 0 && (_jsx("ul", { className: "
|
|
1901
|
+
}, type: "file" }), files.length > 0 && (_jsx("ul", { className: "bd-field__filelist", children: files.map((f, i) => (_jsxs("li", { className: "bd-field__fileitem", children: [_jsx("span", { className: "bd-field__filename", children: f.name }), isPendingFileValue(f) ? (_jsxs("span", { className: "bd-field__filepending", children: [" ", "\u00B7 uploads on submit"] })) : null, _jsx("button", { "aria-label": "Remove file", className: "bd-field__fileremove", onClick: () => set(files.filter((_, idx) => idx !== i)), type: "button", children: "\u00D7" })] }, `${f.name}-${i}`))) }))] }));
|
|
1917
1902
|
}
|
|
1918
1903
|
case "credit_card": {
|
|
1919
1904
|
const pc = field.paymentConfig;
|
|
@@ -1921,14 +1906,14 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1921
1906
|
const amount = deposit && pc?.amountCents
|
|
1922
1907
|
? `${(pc.amountCents / 100).toFixed(2)} ${(pc.currency ?? "usd").toUpperCase()}`
|
|
1923
1908
|
: null;
|
|
1924
|
-
return (_jsxs("div", { className: "
|
|
1909
|
+
return (_jsxs("div", { className: "bd-field__card", children: [_jsxs("div", { className: "bd-field__card-row", children: [_jsx("span", { className: "bd-field__card-brand", children: "CARD" }), _jsx("span", { className: "bd-field__card-dots", children: "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022" }), _jsx("span", { className: "bd-field__card-mode", children: deposit
|
|
1925
1910
|
? `Deposit${amount ? ` · ${amount}` : ""}`
|
|
1926
|
-
: "Card on file" })] }), pc?.description ? (_jsx("p", { className: "
|
|
1911
|
+
: "Card on file" })] }), pc?.description ? (_jsx("p", { className: "bd-field__card-desc", children: pc.description })) : null, _jsxs("p", { className: "bd-field__card-note", children: ["Secure card entry via Stripe. Initialize Elements with", " ", _jsxs("code", { children: ["bd.forms.paymentIntent(slug, \"", field.id, "\")"] }), " (returns the clientSecret + the org's connected account), confirm, then store the intent id on this field before submit."] })] }));
|
|
1927
1912
|
}
|
|
1928
1913
|
case "schedule": {
|
|
1929
1914
|
if (field.scheduleConfig
|
|
1930
1915
|
?.mode === "auto") {
|
|
1931
|
-
return _jsx(
|
|
1916
|
+
return _jsx(BdScheduleAuto, { field: field, onChange: set, value: value });
|
|
1932
1917
|
}
|
|
1933
1918
|
const sc = field.scheduleConfig;
|
|
1934
1919
|
const sv = (value && typeof value === "object" ? value : {});
|
|
@@ -1976,25 +1961,25 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
1976
1961
|
: [...arr, item],
|
|
1977
1962
|
});
|
|
1978
1963
|
};
|
|
1979
|
-
return (_jsxs("div", { className: "
|
|
1964
|
+
return (_jsxs("div", { className: "bd-field__schedule", children: [sc?.allowAsap !== false && (_jsxs("label", { className: "bd-field__schedule-asap", children: [_jsx("input", { checked: sv.asap === true, onChange: (e) => set({ ...sv, asap: e.target.checked }), type: "checkbox" }), " ", "As soon as possible"] })), _jsxs("div", { className: "bd-field__schedule-group", children: [_jsx("span", { className: "bd-field__schedule-label", children: "Preferred days" }), _jsx("div", { className: "bd-field__schedule-chips", children: days.map((d) => (_jsx("button", { className: cx("bd-field__chip", sv.days?.includes(d) && "bd-field__chip--on"), onClick: () => toggle("days", d), type: "button", children: DAY[d] ?? d }, d))) })] }), _jsxs("div", { className: "bd-field__schedule-group", children: [_jsx("span", { className: "bd-field__schedule-label", children: "Preferred times" }), _jsx("div", { className: "bd-field__schedule-chips", children: windows.map((w) => (_jsx("button", { className: cx("bd-field__chip", sv.times?.includes(w) && "bd-field__chip--on"), onClick: () => toggle("times", w), type: "button", children: TIME[w] ?? w }, w))) })] }), sc?.allowNotes !== false && (_jsx("textarea", { className: "bd-input bd-field__schedule-notes", onChange: (e) => set({ ...sv, notes: e.target.value }), placeholder: "Anything we should know about timing?", value: sv.notes ?? "" }))] }));
|
|
1980
1965
|
}
|
|
1981
1966
|
case "phone": {
|
|
1982
1967
|
const phoneIntl = field.phoneConfig?.international === true;
|
|
1983
1968
|
const phoneShown = formatPhoneLocal(typeof value === "string" ? value : "", phoneIntl);
|
|
1984
|
-
return (_jsxs("div", { className: "
|
|
1969
|
+
return (_jsxs("div", { className: "bd-field__phone", children: [field.phoneConfig?.showFlags && (_jsx("span", { "aria-hidden": true, className: "bd-field__flag", children: "\uD83C\uDDFA\uD83C\uDDF8" })), _jsx("input", { autoComplete: autoComplete, className: "bd-input bd-field__input", inputMode: "tel", maxLength: phoneIntl ? 20 : 14, onChange: (e) => set(formatPhoneLocal(e.target.value, phoneIntl)), placeholder: ph || (phoneIntl ? "+1 (555) 000-0000" : "(555) 000-0000"), type: "tel", value: phoneShown })] }));
|
|
1985
1970
|
}
|
|
1986
1971
|
case "email":
|
|
1987
|
-
return (_jsx("input", { autoComplete: autoComplete, className: "
|
|
1972
|
+
return (_jsx("input", { autoComplete: autoComplete, className: "bd-input bd-field__input", onChange: (e) => set(e.target.value), placeholder: ph || "you@example.com", type: "email", value: typeof value === "string" ? value : "" }));
|
|
1988
1973
|
case "url": {
|
|
1989
1974
|
// Value object: { url, verified }. Visitor edits `url`; server stamps
|
|
1990
1975
|
// `verified` on submit.
|
|
1991
1976
|
const uv = (value && typeof value === "object" ? value : {});
|
|
1992
|
-
return (_jsx("input", { autoComplete: autoComplete, className: "
|
|
1977
|
+
return (_jsx("input", { autoComplete: autoComplete, className: "bd-input bd-field__input", inputMode: "url", onChange: (e) => set({ ...uv, url: e.target.value }), placeholder: ph || "https://example.com", type: "url", value: typeof uv.url === "string" ? uv.url : "" }));
|
|
1993
1978
|
}
|
|
1994
1979
|
case "number": {
|
|
1995
1980
|
const allowDecimal = field.allowDecimal !== false;
|
|
1996
1981
|
const noNegative = field.allowNegative === false;
|
|
1997
|
-
return (_jsx("input", { className: "
|
|
1982
|
+
return (_jsx("input", { className: "bd-input bd-field__input", inputMode: allowDecimal ? "decimal" : "numeric", min: noNegative ? 0 : undefined, onChange: (e) => {
|
|
1998
1983
|
const raw = e.target.value;
|
|
1999
1984
|
if (raw === "")
|
|
2000
1985
|
return set("");
|
|
@@ -2010,7 +1995,7 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
2010
1995
|
const symbol = field.currencyConfig?.symbol || "$";
|
|
2011
1996
|
const allowNeg = field.currencyConfig?.allowNegative === true;
|
|
2012
1997
|
const allowDec = field.currencyConfig?.allowDecimal !== false;
|
|
2013
|
-
return (_jsxs("div", { className: "
|
|
1998
|
+
return (_jsxs("div", { className: "bd-field__currency", children: [_jsx("span", { "aria-hidden": "true", className: "bd-field__currency-symbol", children: symbol }), _jsx("input", { className: "bd-input bd-field__input", inputMode: "decimal", min: allowNeg ? undefined : 0, onChange: (e) => {
|
|
2014
1999
|
const raw = e.target.value;
|
|
2015
2000
|
if (raw === "")
|
|
2016
2001
|
return set("");
|
|
@@ -2048,16 +2033,16 @@ function BiabFieldInput({ field, value, setValue, }) {
|
|
|
2048
2033
|
});
|
|
2049
2034
|
};
|
|
2050
2035
|
const hint = typeof num === "number" ? formatMeasurement(num, unit, category) : "";
|
|
2051
|
-
return (_jsxs("div", { className: "
|
|
2036
|
+
return (_jsxs("div", { className: "bd-field__measurement", children: [_jsx("input", { className: "bd-input bd-field__input", inputMode: "decimal", onChange: (e) => {
|
|
2052
2037
|
const raw = e.target.value;
|
|
2053
2038
|
if (raw === "")
|
|
2054
2039
|
return emit("", unit);
|
|
2055
2040
|
const n = allowDec ? Number(raw) : Math.trunc(Number(raw));
|
|
2056
2041
|
emit(Number.isNaN(n) ? "" : n, unit);
|
|
2057
|
-
}, placeholder: ph || "0", step: allowDec ? "any" : 1, type: "number", value: num }), mode === "fixed" ? (_jsx("span", { "aria-hidden": "true", className: "
|
|
2042
|
+
}, placeholder: ph || "0", step: allowDec ? "any" : 1, type: "number", value: num }), mode === "fixed" ? (_jsx("span", { "aria-hidden": "true", className: "bd-field__unit", children: unit })) : (_jsx("select", { className: "bd-select bd-field__unit-select", onChange: (e) => emit(num, e.target.value), value: unit, children: units.map((u) => (_jsx("option", { value: u, children: u }, u))) })), hint ? (_jsxs("span", { className: "bd-field__measurement-hint", children: ["\u2248 ", hint] })) : null] }));
|
|
2058
2043
|
}
|
|
2059
2044
|
default:
|
|
2060
|
-
return (_jsx("input", { autoComplete: autoComplete, className: "
|
|
2045
|
+
return (_jsx("input", { autoComplete: autoComplete, className: "bd-input bd-field__input", onChange: (e) => set(e.target.value), placeholder: ph, value: typeof value === "string" ? value : "" }));
|
|
2061
2046
|
}
|
|
2062
2047
|
}
|
|
2063
2048
|
const NO_LABEL_TYPES = new Set([
|
|
@@ -2069,18 +2054,18 @@ const NO_LABEL_TYPES = new Set([
|
|
|
2069
2054
|
"bullet_list",
|
|
2070
2055
|
"table",
|
|
2071
2056
|
]);
|
|
2072
|
-
function
|
|
2057
|
+
function BdFieldRow({ field, value, error, setValue, upload, }) {
|
|
2073
2058
|
const showLabel = !NO_LABEL_TYPES.has(field.type);
|
|
2074
|
-
return (_jsxs("div", { className: cx("
|
|
2059
|
+
return (_jsxs("div", { className: cx("bd-field", `bd-field--${field.type}`, field.required && "bd-field--required", error && "bd-field--error"), "data-bd-field": fieldCssKey(field), "data-bd-type": field.type, "data-field-id": field.id, children: [showLabel && (
|
|
2075
2060
|
// A visual field heading, not a control-associated <label> — the field's
|
|
2076
|
-
// inputs render in
|
|
2061
|
+
// inputs render in BdFieldInput below and carry no shared id to point a
|
|
2077
2062
|
// `htmlFor` at, so a <label> here would be an orphan.
|
|
2078
|
-
_jsxs("span", { className: "
|
|
2063
|
+
_jsxs("span", { className: "bd-field__label bd-label", children: [field.label || "", field.required && _jsx("span", { className: "bd-field__required", children: "*" })] })), _jsx(BdFieldInput, { field: field, setValue: setValue, upload: upload, value: value }), error && _jsx("p", { className: "bd-field__error", children: error.message }), field.helpText && _jsx("p", { className: "bd-field__help", children: field.helpText })] }));
|
|
2079
2064
|
}
|
|
2080
2065
|
/** Recursively render a field subtree, mirroring FormPreview's `RenderTree`:
|
|
2081
2066
|
* input_group (+ concurrent reveal), condition_block, or_condition, flex_row. */
|
|
2082
|
-
function
|
|
2083
|
-
return (_jsx("div", { className: "
|
|
2067
|
+
function BdRenderTree({ fields, values, errors, setValue, upload, flat, }) {
|
|
2068
|
+
return (_jsx("div", { className: "bd-tree", children: fields.map((f) => {
|
|
2084
2069
|
if (f.type === "multi_populator") {
|
|
2085
2070
|
const cfg = f.populatorConfig ?? {};
|
|
2086
2071
|
const max = cfg.maxInstances ?? 10;
|
|
@@ -2090,14 +2075,14 @@ function BiabRenderTree({ fields, values, errors, setValue, upload, flat, }) {
|
|
|
2090
2075
|
? raw
|
|
2091
2076
|
: [{}];
|
|
2092
2077
|
const writeInstances = (next) => setValue(f.id, next);
|
|
2093
|
-
return (_jsxs("div", { className: "
|
|
2078
|
+
return (_jsxs("div", { className: "bd-populator", "data-bd-field": fieldCssKey(f), "data-field-id": f.id, children: [instances.map((inst, idx) => {
|
|
2094
2079
|
const scopedSetValue = (childId, v) => {
|
|
2095
2080
|
const nextArr = instances.map((x) => ({ ...x }));
|
|
2096
2081
|
nextArr[idx] = { ...nextArr[idx], [childId]: v };
|
|
2097
2082
|
writeInstances(nextArr);
|
|
2098
2083
|
};
|
|
2099
|
-
return (_jsxs("div", { className: "
|
|
2100
|
-
}), instances.length < max && (_jsxs("button", { className: "
|
|
2084
|
+
return (_jsxs("div", { className: "bd-populator__item", children: [_jsxs("div", { className: "bd-populator__item-head", children: [_jsxs("span", { className: "bd-populator__item-label", children: [cfg.itemLabel || f.label || "Entry", " ", idx + 1] }), instances.length > 1 && (_jsx("button", { "aria-label": "Remove entry", className: "bd-populator__remove", onClick: () => writeInstances(instances.filter((_, i) => i !== idx)), type: "button", children: "\u00D7" }))] }), _jsx(BdRenderTree, { errors: errors, fields: children, setValue: scopedSetValue, upload: upload, values: inst })] }, `${f.id}-inst-${idx}`));
|
|
2085
|
+
}), instances.length < max && (_jsxs("button", { className: "bd-populator__add", onClick: () => writeInstances([...instances, {}]), type: "button", children: ["+ ", cfg.addButtonLabel || "Add another"] }))] }, f.id));
|
|
2101
2086
|
}
|
|
2102
2087
|
if (f.type === "input_group") {
|
|
2103
2088
|
const children = (f.children ?? []);
|
|
@@ -2107,12 +2092,12 @@ function BiabRenderTree({ fields, values, errors, setValue, upload, flat, }) {
|
|
|
2107
2092
|
? concurrentRevealCount(children, values)
|
|
2108
2093
|
: children.length;
|
|
2109
2094
|
const shown = concurrent ? children.slice(0, revealCount) : children;
|
|
2110
|
-
return (_jsxs("div", { className: cx("
|
|
2095
|
+
return (_jsxs("div", { className: cx("bd-group", flat && "bd-group--flat"), "data-bd-group": fieldCssKey(f), children: [f.label && showGroupLabel && (_jsx("div", { className: "bd-group__label", children: f.label })), _jsx(BdRenderTree, { errors: errors, fields: shown, setValue: setValue, upload: upload, values: values })] }, f.id));
|
|
2111
2096
|
}
|
|
2112
2097
|
if (f.type === "condition_block") {
|
|
2113
2098
|
if (!evalCondition(f.conditionRule, values))
|
|
2114
2099
|
return null;
|
|
2115
|
-
return (_jsx(
|
|
2100
|
+
return (_jsx(BdRenderTree, { errors: errors, fields: (f.children ?? []), setValue: setValue, upload: upload, values: values }, f.id));
|
|
2116
2101
|
}
|
|
2117
2102
|
if (f.type === "or_condition") {
|
|
2118
2103
|
const branches = f.children ?? [];
|
|
@@ -2122,19 +2107,19 @@ function BiabRenderTree({ fields, values, errors, setValue, upload, flat, }) {
|
|
|
2122
2107
|
branches[i]?.label ||
|
|
2123
2108
|
["Option A", "Option B", "Option C"][i] ||
|
|
2124
2109
|
`Option ${i + 1}`;
|
|
2125
|
-
return (_jsxs("div", { className: cx("
|
|
2126
|
-
const side = (_jsxs("div", { className: "
|
|
2110
|
+
return (_jsxs("div", { className: cx("bd-or", errored && "bd-or--error"), children: [f.label && (_jsxs("div", { className: "bd-or__label", children: [f.label, _jsx("span", { className: "bd-or__hint", children: "Fill at least one side" })] })), _jsx("div", { className: "bd-or__sides", children: branches.flatMap((child, i) => {
|
|
2111
|
+
const side = (_jsxs("div", { className: "bd-or__side", children: [_jsx("div", { className: "bd-or__sidelabel", children: branchLabel(i) }), _jsx(BdRenderTree, { errors: errors, fields: [child], setValue: setValue, upload: upload, values: values })] }, child.id));
|
|
2127
2112
|
if (i === 0)
|
|
2128
2113
|
return [side];
|
|
2129
2114
|
return [
|
|
2130
|
-
_jsx("div", { className: "
|
|
2115
|
+
_jsx("div", { className: "bd-or__divider", children: "OR" }, `or-${child.id}`),
|
|
2131
2116
|
side,
|
|
2132
2117
|
];
|
|
2133
|
-
}) }), errored && (_jsx("p", { className: "
|
|
2118
|
+
}) }), errored && (_jsx("p", { className: "bd-field__error", children: "Fill in at least one side to continue." }))] }, f.id));
|
|
2134
2119
|
}
|
|
2135
2120
|
if (f.type === "flex_row") {
|
|
2136
2121
|
const cfg = (f["flexConfig"] ?? {});
|
|
2137
|
-
return (_jsx("div", { className: "
|
|
2122
|
+
return (_jsx("div", { className: "bd-flexrow", style: {
|
|
2138
2123
|
display: "flex",
|
|
2139
2124
|
justifyContent: cfg.justify === "center"
|
|
2140
2125
|
? "center"
|
|
@@ -2152,18 +2137,18 @@ function BiabRenderTree({ fields, values, errors, setValue, upload, flat, }) {
|
|
|
2152
2137
|
: "center",
|
|
2153
2138
|
gap: `${cfg.gap ?? 12}px`,
|
|
2154
2139
|
flexWrap: "wrap",
|
|
2155
|
-
}, children: (f.children ?? []).map((child) => (_jsx("div", { className: "
|
|
2140
|
+
}, children: (f.children ?? []).map((child) => (_jsx("div", { className: "bd-flexrow__cell", children: _jsx(BdRenderTree, { errors: errors, fields: [child], setValue: setValue, upload: upload, values: values }) }, child.id))) }, f.id));
|
|
2156
2141
|
}
|
|
2157
|
-
return (_jsx(
|
|
2142
|
+
return (_jsx(BdFieldRow, { error: errors[f.id], field: f, setValue: setValue, upload: upload, value: values[f.id] }, f.id));
|
|
2158
2143
|
}) }));
|
|
2159
2144
|
}
|
|
2160
|
-
export function
|
|
2161
|
-
const providerClient =
|
|
2162
|
-
// A publishable token (`pk_…`) builds a browser client that calls
|
|
2145
|
+
export function BdForm(props) {
|
|
2146
|
+
const providerClient = useBdFormsClient();
|
|
2147
|
+
// A publishable token (`pk_…`) builds a browser client that calls BD
|
|
2163
2148
|
// directly — the zero-backend path. Explicit `client` still wins, then the
|
|
2164
2149
|
// token client, then a surrounding provider.
|
|
2165
2150
|
const tokenClient = useMemo(() => props.token && props.siteId
|
|
2166
|
-
?
|
|
2151
|
+
? createBdClient({
|
|
2167
2152
|
apiKey: props.token,
|
|
2168
2153
|
siteId: props.siteId,
|
|
2169
2154
|
baseUrl: props.baseUrl ?? DEFAULT_PACKAGE_BASE_URL,
|
|
@@ -2223,9 +2208,10 @@ export function BiabForm(props) {
|
|
|
2223
2208
|
// Autofill config provided to the field tree. Keyed by the serialized
|
|
2224
2209
|
// overrides so a fresh-but-equal `fieldAutoComplete` literal doesn't churn.
|
|
2225
2210
|
const fieldAutoCompleteKey = JSON.stringify(props.fieldAutoComplete ?? null);
|
|
2226
|
-
|
|
2227
|
-
//
|
|
2228
|
-
|
|
2211
|
+
// The directive has to sit immediately above the flagged `useMemo`; inside
|
|
2212
|
+
// the call it attaches to the callback and does nothing.
|
|
2213
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: keyed by fieldAutoCompleteKey, so a fresh-but-equal literal does not churn
|
|
2214
|
+
const autocompleteConfig = useMemo(() => ({
|
|
2229
2215
|
...(props.autoComplete !== undefined
|
|
2230
2216
|
? { enabled: props.autoComplete }
|
|
2231
2217
|
: {}),
|
|
@@ -2252,30 +2238,30 @@ export function BiabForm(props) {
|
|
|
2252
2238
|
window.addEventListener("beforeunload", onBeforeUnload);
|
|
2253
2239
|
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
|
2254
2240
|
}, [exitConfirm?.enabled, exitConfirm?.message, snap.submitted, exitDirty]);
|
|
2255
|
-
const rootClass = cx("
|
|
2241
|
+
const rootClass = cx("bd-form", props.className, props.class);
|
|
2256
2242
|
if (snap.status === "loading" || snap.status === "idle") {
|
|
2257
|
-
return (_jsx("div", { className: rootClass, style: props.style, children: props.loadingFallback ?? (_jsx("div", { className: "
|
|
2243
|
+
return (_jsx("div", { className: rootClass, style: props.style, children: props.loadingFallback ?? (_jsx("div", { className: "bd-form__loading", children: "Loading\u2026" })) }));
|
|
2258
2244
|
}
|
|
2259
2245
|
if (snap.status === "error") {
|
|
2260
2246
|
const message = snap.loadError ?? "Failed to load the form.";
|
|
2261
|
-
return (_jsx("div", { className: rootClass, style: props.style, children: props.errorFallback ? (props.errorFallback(message)) : (_jsx("div", { className: "
|
|
2247
|
+
return (_jsx("div", { className: rootClass, style: props.style, children: props.errorFallback ? (props.errorFallback(message)) : (_jsx("div", { className: "bd-form__error", children: message })) }));
|
|
2262
2248
|
}
|
|
2263
2249
|
if (snap.submitted && snap.result?.ok) {
|
|
2264
2250
|
const ok = snap.result;
|
|
2265
|
-
return (_jsx("div", { className: rootClass, style: props.style, children: props.successFallback ? (props.successFallback(ok)) : (_jsx("div", { className: "
|
|
2251
|
+
return (_jsx("div", { className: rootClass, style: props.style, children: props.successFallback ? (props.successFallback(ok)) : (_jsx("div", { className: "bd-form__success", children: "Thanks \u2014 your response was received." })) }));
|
|
2266
2252
|
}
|
|
2267
2253
|
const { settings } = snap;
|
|
2268
2254
|
const consecutive = settings.isConsecutive && snap.stepCount > 1;
|
|
2269
2255
|
const animClass = settings.animation === "fade"
|
|
2270
|
-
? "
|
|
2256
|
+
? "bd-anim--fade"
|
|
2271
2257
|
: settings.animation === "scale"
|
|
2272
|
-
? "
|
|
2258
|
+
? "bd-anim--scale"
|
|
2273
2259
|
: settings.animation === "slide"
|
|
2274
2260
|
? snap.forward
|
|
2275
|
-
? "
|
|
2276
|
-
: "
|
|
2261
|
+
? "bd-anim--slide-fwd"
|
|
2262
|
+
: "bd-anim--slide-back"
|
|
2277
2263
|
: settings.animation === "scroll"
|
|
2278
|
-
? "
|
|
2264
|
+
? "bd-anim--scroll"
|
|
2279
2265
|
: "";
|
|
2280
2266
|
const handleSubmit = async () => {
|
|
2281
2267
|
const res = await controller.submit();
|
|
@@ -2303,17 +2289,17 @@ export function BiabForm(props) {
|
|
|
2303
2289
|
}, delay);
|
|
2304
2290
|
}
|
|
2305
2291
|
};
|
|
2306
|
-
return (_jsx(FetchScheduleSlotsContext.Provider, { value: fetchScheduleSlots, children: _jsx(AutocompleteContext.Provider, { value: autocompleteConfig, children: _jsxs("div", { className: rootClass, style: props.style, children: [snap.orgIcon && (_jsx("img", { alt: "", className: "
|
|
2292
|
+
return (_jsx(FetchScheduleSlotsContext.Provider, { value: fetchScheduleSlots, children: _jsx(AutocompleteContext.Provider, { value: autocompleteConfig, children: _jsxs("div", { className: rootClass, style: props.style, children: [snap.orgIcon && (_jsx("img", { alt: "", className: "bd-form__orgicon", src: snap.orgIcon })), (snap.title || snap.description) && (_jsxs("div", { className: "bd-form__head", children: [snap.title && _jsx("h2", { className: "bd-form__title", children: snap.title }), snap.description && (_jsx("p", { className: "bd-form__desc", children: snap.description }))] })), consecutive &&
|
|
2307
2293
|
settings.showProgress &&
|
|
2308
|
-
settings.progressStyle !== "bar" && (_jsx("div", { className: "
|
|
2294
|
+
settings.progressStyle !== "bar" && (_jsx("div", { className: "bd-progress bd-progress--steps", children: snap.steps.map((s, i) => {
|
|
2309
2295
|
const f = s[0];
|
|
2310
2296
|
const title = f?.stepTitle || f?.label || `Step ${i + 1}`;
|
|
2311
2297
|
const done = i < snap.step;
|
|
2312
2298
|
const current = i === snap.step;
|
|
2313
|
-
return (_jsxs(Fragment, { children: [i > 0 && (_jsx("span", { className: cx("
|
|
2299
|
+
return (_jsxs(Fragment, { children: [i > 0 && (_jsx("span", { className: cx("bd-progress__line", i <= snap.step && "bd-progress__line--done") })), _jsxs("button", { className: cx("bd-progress__step", current && "bd-progress__step--current", done && "bd-progress__step--done"), onClick: () => controller.goToStep(i), type: "button", children: [_jsx("span", { className: "bd-progress__num", children: done ? "✓" : i + 1 }), _jsx("span", { className: "bd-progress__title", children: title })] })] }, `step-${i}`));
|
|
2314
2300
|
}) })), consecutive &&
|
|
2315
2301
|
settings.showProgress &&
|
|
2316
|
-
settings.progressStyle === "bar" && (_jsxs("div", { className: "
|
|
2302
|
+
settings.progressStyle === "bar" && (_jsxs("div", { className: "bd-progress bd-progress--bar", children: [_jsxs("span", { className: "bd-progress__count", children: [snap.step + 1, " / ", snap.stepCount] }), _jsx("div", { className: "bd-progress__track", children: _jsx("div", { className: "bd-progress__fill", style: { width: `${snap.progressPercent}%` } }) })] })), snap.errorList.length > 0 && (_jsxs("div", { className: "bd-form__notice", role: "alert", children: [_jsxs("div", { className: "bd-form__notice-title", children: ["Please complete the highlighted field", snap.errorList.length === 1 ? "" : "s", "."] }), _jsx("div", { className: "bd-form__notice-list", children: snap.errorList.map((e) => e.label).join(", ") })] })), _jsx("div", { className: cx("bd-form__body", animClass), children: _jsx(BdRenderTree, { errors: snap.errors, fields: snap.visibleFields, flat: consecutive, setValue: (id, v) => controller.setValue(id, v), upload: upload, values: snap.values }) }, `bd-step-${snap.step}`), (!consecutive || snap.isLastStep) &&
|
|
2317
2303
|
(snap.schema?.actions ?? [])
|
|
2318
2304
|
.filter((a) => a.type === "grant_coupon" &&
|
|
2319
2305
|
(a.coupon?.notice || a.coupon?.code))
|
|
@@ -2322,8 +2308,8 @@ export function BiabForm(props) {
|
|
|
2322
2308
|
const text = a.coupon?.notice
|
|
2323
2309
|
? a.coupon.notice.replace(/\{code\}/g, code)
|
|
2324
2310
|
: `Coupon ${code} will be applied to this submission.`;
|
|
2325
|
-
return (_jsxs("div", { className: "
|
|
2326
|
-
}), consecutive ? (_jsxs("div", { className: "
|
|
2311
|
+
return (_jsxs("div", { className: "bd-coupon-notice", children: [_jsx("p", { className: "bd-coupon-notice__text", children: text }), a.coupon?.finePrint && (_jsx("p", { className: "bd-coupon-notice__fine", children: a.coupon.finePrint }))] }, `coupon-${i}`));
|
|
2312
|
+
}), consecutive ? (_jsxs("div", { className: "bd-nav", children: [_jsx("button", { className: "bd-btn bd-btn--back bd-btn--ghost", disabled: snap.isFirstStep, onClick: () => controller.back(), type: "button", children: "Back" }), !snap.isLastStep ? (_jsx("button", { className: "bd-btn bd-btn--next", disabled: !snap.canAdvance, onClick: () => controller.next(), type: "button", children: "Next" })) : (_jsx("button", { className: "bd-btn bd-btn--submit", disabled: snap.submitting || snap.uploading, onClick: () => void handleSubmit(), type: "button", children: snap.submitting ? "Sending…" : submitLabel }))] })) : (_jsx("div", { className: "bd-nav", children: _jsx("button", { className: "bd-btn bd-btn--submit", disabled: snap.submitting || snap.uploading, onClick: () => void handleSubmit(), type: "button", children: snap.submitting ? "Sending…" : submitLabel }) })), snap.result && !snap.result.ok && snap.errorList.length === 0 && (_jsx("div", { className: "bd-form__error", role: "alert", children: snap.result.message }))] }) }) }));
|
|
2327
2313
|
}
|
|
2328
2314
|
function SocialGlyph({ iconUrl, size, className, label, }) {
|
|
2329
2315
|
if (iconUrl) {
|
|
@@ -2335,7 +2321,7 @@ function SocialGlyph({ iconUrl, size, className, label, }) {
|
|
|
2335
2321
|
}
|
|
2336
2322
|
/**
|
|
2337
2323
|
* Render every social profile on the company as an icon + link, in canonical
|
|
2338
|
-
* order, resolving
|
|
2324
|
+
* order, resolving BD's exact keys (`meta`, `twitterX`, …) for you. Branded
|
|
2339
2325
|
* glyphs come from the simple-icons CDN; platforms without one fall back to a
|
|
2340
2326
|
* globe. Returns `null` when no socials are set.
|
|
2341
2327
|
*/
|
|
@@ -2360,7 +2346,7 @@ export function SocialLinks({ socials, color, size = 20, className, linkClassNam
|
|
|
2360
2346
|
export function useBlog(options) {
|
|
2361
2347
|
const { apiKey, baseUrl, pageSize, categoryId } = options;
|
|
2362
2348
|
const controller = useMemo(() => createBlogController({
|
|
2363
|
-
client: new
|
|
2349
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
2364
2350
|
...(pageSize ? { pageSize } : {}),
|
|
2365
2351
|
...(categoryId ? { categoryId } : {}),
|
|
2366
2352
|
}), [apiKey, baseUrl, pageSize, categoryId]);
|
|
@@ -2392,7 +2378,7 @@ export function useBlog(options) {
|
|
|
2392
2378
|
export function useVerify(options) {
|
|
2393
2379
|
const { apiKey, baseUrl, kind, resendCooldownSeconds } = options;
|
|
2394
2380
|
const controller = useMemo(() => createVerifyController({
|
|
2395
|
-
client: new
|
|
2381
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
2396
2382
|
kind,
|
|
2397
2383
|
...(resendCooldownSeconds ? { resendCooldownSeconds } : {}),
|
|
2398
2384
|
}), [apiKey, baseUrl, kind, resendCooldownSeconds]);
|
|
@@ -2427,7 +2413,7 @@ export function useVerify(options) {
|
|
|
2427
2413
|
export function usePortal(options) {
|
|
2428
2414
|
const { apiKey, baseUrl, sessionToken } = options;
|
|
2429
2415
|
const controller = useMemo(() => createPortalController({
|
|
2430
|
-
client: new
|
|
2416
|
+
client: new BdApiClient({ apiKey, baseUrl }),
|
|
2431
2417
|
sessionToken,
|
|
2432
2418
|
}), [apiKey, baseUrl, sessionToken]);
|
|
2433
2419
|
useEffect(() => {
|