@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
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
@businessdash/sdk/svelte — <
|
|
2
|
+
@businessdash/sdk/svelte — <BdForm> drop-in (Svelte 5, runes).
|
|
3
3
|
|
|
4
|
-
Renders a
|
|
4
|
+
Renders a BD form end-to-end from the headless core
|
|
5
5
|
(`createFormController` in `@businessdash/sdk/forms`). It is a faithful port of
|
|
6
6
|
the dashboard `FormPreview` renderer: per-type field rendering, the
|
|
7
7
|
condition_block / or_condition / input_group / flex_row tree walk,
|
|
8
8
|
multi-step ("consecutive") navigation + progress (bar | steps),
|
|
9
9
|
concurrent-reveal gating, and the schema's render settings
|
|
10
|
-
(animation, org icon). All markup uses the `
|
|
10
|
+
(animation, org icon). All markup uses the `bd-*` light-DOM class
|
|
11
11
|
convention so the consumer's CSS cascades — no shadow root.
|
|
12
12
|
|
|
13
13
|
Two data paths (consistent with the other framework bindings):
|
|
14
14
|
|
|
15
|
-
• client mode — pass a `
|
|
15
|
+
• client mode — pass a `BdClient` (prop or `setBdClient` context).
|
|
16
16
|
The controller fetches the schema (when only `slug` is given) and
|
|
17
17
|
submits / uploads through the SDK directly. Only safe where the bearer
|
|
18
18
|
key never reaches the browser.
|
|
19
19
|
|
|
20
20
|
• schema + submitAction mode — pass a pre-fetched `schema` (SSR-loaded
|
|
21
|
-
via your server `
|
|
22
|
-
POSTs to your own endpoint (e.g. SvelteKit `/api/
|
|
21
|
+
via your server `BdClient`) plus a `submitAction(payload, opts)` that
|
|
22
|
+
POSTs to your own endpoint (e.g. SvelteKit `/api/bd/forms/[slug]`).
|
|
23
23
|
The bearer key stays in `$lib/server`. This is the recommended
|
|
24
24
|
SvelteKit pattern and the one the starter wires up.
|
|
25
25
|
-->
|
|
26
26
|
<script lang="ts" module>
|
|
27
|
-
import type {
|
|
27
|
+
import type { BdClient } from "../sdk.js";
|
|
28
28
|
import type {
|
|
29
29
|
FormCoreField,
|
|
30
30
|
FormSnapshot,
|
|
@@ -34,34 +34,34 @@
|
|
|
34
34
|
} from "../forms-core/index.js";
|
|
35
35
|
|
|
36
36
|
/** A successful submit result (`ok === true`). */
|
|
37
|
-
export type
|
|
37
|
+
export type BdFormSuccess = Extract<FormSubmitResult, { ok: true }>;
|
|
38
38
|
/** A failed submit result (`ok === false`). */
|
|
39
|
-
export type
|
|
39
|
+
export type BdFormFailure = Extract<FormSubmitResult, { ok: false }>;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Re-key the working data (by field `id`) to the published output `key`,
|
|
43
43
|
* then POST it to your own endpoint. Returning a {@link FormSubmitResult}
|
|
44
|
-
* lets `<
|
|
44
|
+
* lets `<BdForm>` surface inline server-side field errors; returning
|
|
45
45
|
* void/anything else is treated as a generic success.
|
|
46
46
|
*/
|
|
47
|
-
export type
|
|
47
|
+
export type BdFormSubmitAction = (
|
|
48
48
|
payload: Record<string, unknown>,
|
|
49
49
|
options: FormSubmitOptions,
|
|
50
50
|
) => Promise<FormSubmitResult | void> | FormSubmitResult | void;
|
|
51
51
|
|
|
52
|
-
export interface
|
|
52
|
+
export interface BdFormProps {
|
|
53
53
|
/** Form slug — required in client mode (or when `schema` lacks a slug). */
|
|
54
54
|
slug?: string;
|
|
55
55
|
/** Pre-fetched schema (SSR). When present, no client is needed to render. */
|
|
56
56
|
schema?: FormSchema;
|
|
57
|
-
/** SDK client. Falls back to `
|
|
58
|
-
client?:
|
|
57
|
+
/** SDK client. Falls back to `setBdClient` context. Omit in schema+action mode. */
|
|
58
|
+
client?: BdClient;
|
|
59
59
|
/**
|
|
60
|
-
* Submit override. When provided, `<
|
|
60
|
+
* Submit override. When provided, `<BdForm>` validates locally, re-keys
|
|
61
61
|
* the payload to output keys, then calls this instead of the SDK — the
|
|
62
62
|
* server-safe SvelteKit path. Takes precedence over `client.forms.submit`.
|
|
63
63
|
*/
|
|
64
|
-
submitAction?:
|
|
64
|
+
submitAction?: BdFormSubmitAction;
|
|
65
65
|
/** Submit button label (final step). Default "Submit". */
|
|
66
66
|
submitLabel?: string;
|
|
67
67
|
/** Merged into every submit (e.g. `source`, `metadata`). */
|
|
@@ -69,12 +69,12 @@
|
|
|
69
69
|
/** Server-validates but stores nothing. */
|
|
70
70
|
dryRun?: boolean;
|
|
71
71
|
/** Fired after a successful submit. */
|
|
72
|
-
onSuccess?: (result:
|
|
72
|
+
onSuccess?: (result: BdFormSuccess) => void;
|
|
73
73
|
/** Fired after a failed submit. */
|
|
74
|
-
onError?: (result:
|
|
74
|
+
onError?: (result: BdFormFailure) => void;
|
|
75
75
|
/** Convenience: fired with the re-keyed payload on a successful submit. */
|
|
76
76
|
onSubmit?: (payload: Record<string, unknown>) => void;
|
|
77
|
-
/** Extra class on the root `.
|
|
77
|
+
/** Extra class on the root `.bd-form`. (`className` alias also accepted.) */
|
|
78
78
|
class?: string;
|
|
79
79
|
/** React-style alias for `class`. */
|
|
80
80
|
className?: string;
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
resolveFieldAutocomplete,
|
|
113
113
|
} from "../forms-core/autocomplete.js";
|
|
114
114
|
|
|
115
|
-
import { formStore,
|
|
115
|
+
import { formStore, getBdClient } from "./context.js";
|
|
116
116
|
|
|
117
117
|
let {
|
|
118
118
|
slug,
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
successMessage = "Thanks — your response was received.",
|
|
131
131
|
autoComplete,
|
|
132
132
|
fieldAutoComplete,
|
|
133
|
-
}:
|
|
133
|
+
}: BdFormProps = $props();
|
|
134
134
|
|
|
135
135
|
// HTML autofill config threaded to every value-bearing input. `enabled` only
|
|
136
136
|
// when the global switch is set; `overrides` only when per-field tokens exist
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
// values without registering a reactive dependency, which also silences
|
|
147
147
|
// Svelte's `state_referenced_locally` hint for this deliberate one-time read.
|
|
148
148
|
const init = untrack(() => ({
|
|
149
|
-
client: client ??
|
|
149
|
+
client: client ?? getBdClient(),
|
|
150
150
|
slug,
|
|
151
151
|
schema,
|
|
152
152
|
submitOptions: { ...submitOptions, ...(dryRun ? { dryRun: true } : {}) },
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
const snap = formStore(controller);
|
|
177
177
|
|
|
178
178
|
const rootClass = $derived(
|
|
179
|
-
["
|
|
179
|
+
["bd-form", klass, className].filter(Boolean).join(" "),
|
|
180
180
|
);
|
|
181
181
|
|
|
182
182
|
// ── Submit: route through `submitAction` (server-safe) or the SDK. ─────────
|
|
@@ -207,23 +207,23 @@
|
|
|
207
207
|
return;
|
|
208
208
|
}
|
|
209
209
|
// No structured result → treat as success.
|
|
210
|
-
const synthetic:
|
|
210
|
+
const synthetic: BdFormSuccess = {
|
|
211
211
|
ok: true,
|
|
212
212
|
status: 200,
|
|
213
213
|
submissionId: "",
|
|
214
|
-
} as
|
|
214
|
+
} as BdFormSuccess;
|
|
215
215
|
actionResult = synthetic;
|
|
216
216
|
onSubmit?.(payload);
|
|
217
217
|
onSuccess?.(synthetic);
|
|
218
218
|
} catch (err) {
|
|
219
219
|
submittingViaAction = false;
|
|
220
|
-
const failure:
|
|
220
|
+
const failure: BdFormFailure = {
|
|
221
221
|
ok: false,
|
|
222
222
|
status: 0,
|
|
223
223
|
reason: "network_error",
|
|
224
224
|
message:
|
|
225
225
|
err instanceof Error ? err.message : "The submission failed.",
|
|
226
|
-
} as
|
|
226
|
+
} as BdFormFailure;
|
|
227
227
|
actionResult = failure;
|
|
228
228
|
onError?.(failure);
|
|
229
229
|
}
|
|
@@ -424,22 +424,22 @@
|
|
|
424
424
|
{@const ac = resolveFieldAutocomplete(field, autocompleteConfig)}
|
|
425
425
|
{@const acOff = ac === "off"}
|
|
426
426
|
{#if field.type === "title"}
|
|
427
|
-
<h2 class="
|
|
427
|
+
<h2 class="bd-field__title">{field.label || "Untitled"}</h2>
|
|
428
428
|
{:else if field.type === "subtitle"}
|
|
429
|
-
<h3 class="
|
|
429
|
+
<h3 class="bd-field__subtitle">{field.label || ""}</h3>
|
|
430
430
|
{:else if field.type === "separator"}
|
|
431
|
-
<hr class="
|
|
431
|
+
<hr class="bd-field__separator" />
|
|
432
432
|
{:else if field.type === "richtext"}
|
|
433
433
|
<!-- richtext is pre-sanitized server-side; rendered as-is. -->
|
|
434
|
-
<div class="
|
|
434
|
+
<div class="bd-field__richtext">{@html field.content ?? ""}</div>
|
|
435
435
|
{:else if field.type === "image"}
|
|
436
436
|
{#if asStr(field.content)}
|
|
437
|
-
<img class="
|
|
437
|
+
<img class="bd-field__image" src={asStr(field.content)} alt={field.label ?? ""} />
|
|
438
438
|
{/if}
|
|
439
439
|
{:else if field.type === "textarea"}
|
|
440
440
|
{@const taRows = (field.textareaConfig as { rows?: number } | undefined)?.rows ?? 4}
|
|
441
441
|
<textarea
|
|
442
|
-
class="
|
|
442
|
+
class="bd-textarea bd-field__control"
|
|
443
443
|
rows={taRows}
|
|
444
444
|
placeholder={field.placeholder ?? ""}
|
|
445
445
|
autocomplete={ac ?? undefined}
|
|
@@ -451,21 +451,21 @@
|
|
|
451
451
|
default. `input list` + `datalist` is native typeahead: no JS, no
|
|
452
452
|
styling opinion, which is the SDK's contract. -->
|
|
453
453
|
<input
|
|
454
|
-
class="
|
|
454
|
+
class="bd-input bd-combobox bd-field__control"
|
|
455
455
|
type="text"
|
|
456
|
-
list={`
|
|
456
|
+
list={`bd-list-${field.id}`}
|
|
457
457
|
placeholder={field.placeholder || "Search…"}
|
|
458
458
|
value={asStr(value)}
|
|
459
459
|
oninput={(e) => onFieldInput(field.id, e.currentTarget.value)}
|
|
460
460
|
/>
|
|
461
|
-
<datalist id={`
|
|
461
|
+
<datalist id={`bd-list-${field.id}`}>
|
|
462
462
|
{#each field.options ?? [] as opt (opt)}
|
|
463
463
|
<option value={opt}></option>
|
|
464
464
|
{/each}
|
|
465
465
|
</datalist>
|
|
466
466
|
{:else if field.type === "select"}
|
|
467
467
|
<select
|
|
468
|
-
class="
|
|
468
|
+
class="bd-select bd-field__control"
|
|
469
469
|
value={asStr(value)}
|
|
470
470
|
onchange={(e) => onFieldInput(field.id, e.currentTarget.value)}
|
|
471
471
|
>
|
|
@@ -478,25 +478,25 @@
|
|
|
478
478
|
<!-- The value is an icon NAME; art resolves against the platform's icon
|
|
479
479
|
route, so no icon library ships in the SDK bundle. -->
|
|
480
480
|
{@const names = iconPool(field)}
|
|
481
|
-
<div class="
|
|
481
|
+
<div class="bd-iconselect">
|
|
482
482
|
<input
|
|
483
|
-
class="
|
|
483
|
+
class="bd-input bd-iconselect__search"
|
|
484
484
|
type="text"
|
|
485
485
|
placeholder={field.placeholder || "Search icons…"}
|
|
486
486
|
value={iconQuery}
|
|
487
487
|
oninput={(e) => (iconQuery = e.currentTarget.value)}
|
|
488
488
|
/>
|
|
489
489
|
{#if asStr(value)}
|
|
490
|
-
<span class="
|
|
490
|
+
<span class="bd-iconselect__current">
|
|
491
491
|
<img src={iconUrl(asStr(value))} alt={asStr(value)} width="16" height="16" />
|
|
492
|
-
<button type="button" class="
|
|
492
|
+
<button type="button" class="bd-iconselect__clear" onclick={() => onFieldInput(field.id, "")}>×</button>
|
|
493
493
|
</span>
|
|
494
494
|
{/if}
|
|
495
|
-
<div class="
|
|
495
|
+
<div class="bd-iconselect__grid">
|
|
496
496
|
{#each filterIconNames(names, iconQuery) as name (name)}
|
|
497
497
|
<button
|
|
498
498
|
type="button"
|
|
499
|
-
class="
|
|
499
|
+
class="bd-iconselect__item"
|
|
500
500
|
class:is-selected={asStr(value) === name}
|
|
501
501
|
title={name}
|
|
502
502
|
aria-label={name}
|
|
@@ -512,19 +512,19 @@
|
|
|
512
512
|
<!-- One image, either source; the submitted value is a plain URL string. -->
|
|
513
513
|
{@const cfg = field.imageInputConfig ?? {}}
|
|
514
514
|
{@const sources = cfg.sources ?? "both"}
|
|
515
|
-
<div class="
|
|
515
|
+
<div class="bd-imageinput">
|
|
516
516
|
{#if sources === "both"}
|
|
517
|
-
<div class="
|
|
517
|
+
<div class="bd-imageinput__modes">
|
|
518
518
|
<button
|
|
519
519
|
type="button"
|
|
520
|
-
class="
|
|
520
|
+
class="bd-imageinput__mode"
|
|
521
521
|
class:is-active={imageMode === "upload"}
|
|
522
522
|
aria-pressed={imageMode === "upload"}
|
|
523
523
|
onclick={() => (imageMode = "upload")}>Upload</button
|
|
524
524
|
>
|
|
525
525
|
<button
|
|
526
526
|
type="button"
|
|
527
|
-
class="
|
|
527
|
+
class="bd-imageinput__mode"
|
|
528
528
|
class:is-active={imageMode === "url"}
|
|
529
529
|
aria-pressed={imageMode === "url"}
|
|
530
530
|
onclick={() => (imageMode = "url")}>Paste URL</button
|
|
@@ -533,14 +533,14 @@
|
|
|
533
533
|
{/if}
|
|
534
534
|
{#if imageMode === "upload" && sources !== "url" && hasClient}
|
|
535
535
|
<input
|
|
536
|
-
class="
|
|
536
|
+
class="bd-input bd-imageinput__file"
|
|
537
537
|
type="file"
|
|
538
538
|
accept={(cfg.allowedTypes ?? ["image/*"]).join(",")}
|
|
539
539
|
onchange={(e) => onImagePick(field, e.currentTarget.files, cfg.maxSize ?? 5)}
|
|
540
540
|
/>
|
|
541
541
|
{:else}
|
|
542
542
|
<input
|
|
543
|
-
class="
|
|
543
|
+
class="bd-input bd-imageinput__url"
|
|
544
544
|
type="url"
|
|
545
545
|
placeholder={field.placeholder || "https://example.com/image.png"}
|
|
546
546
|
value={asStr(value)}
|
|
@@ -548,13 +548,13 @@
|
|
|
548
548
|
/>
|
|
549
549
|
{/if}
|
|
550
550
|
{#if cfg.showPreview !== false && asStr(value)}
|
|
551
|
-
<img class="
|
|
551
|
+
<img class="bd-imageinput__preview" src={asStr(value)} alt="" />
|
|
552
552
|
{/if}
|
|
553
553
|
</div>
|
|
554
554
|
{:else if field.type === "radio"}
|
|
555
|
-
<div class="
|
|
555
|
+
<div class="bd-field__radios">
|
|
556
556
|
{#each field.options ?? [] as opt (opt)}
|
|
557
|
-
<label class="
|
|
557
|
+
<label class="bd-field__radio">
|
|
558
558
|
<input
|
|
559
559
|
type="radio"
|
|
560
560
|
name={field.id}
|
|
@@ -568,9 +568,9 @@
|
|
|
568
568
|
</div>
|
|
569
569
|
{:else if field.type === "checkbox"}
|
|
570
570
|
{@const arr = Array.isArray(value) ? (value as string[]) : []}
|
|
571
|
-
<div class="
|
|
571
|
+
<div class="bd-field__checks">
|
|
572
572
|
{#each field.options ?? [] as opt (opt)}
|
|
573
|
-
<label class="
|
|
573
|
+
<label class="bd-field__check">
|
|
574
574
|
<input
|
|
575
575
|
type="checkbox"
|
|
576
576
|
checked={arr.includes(opt)}
|
|
@@ -587,7 +587,7 @@
|
|
|
587
587
|
{/each}
|
|
588
588
|
</div>
|
|
589
589
|
{:else if field.type === "boolean"}
|
|
590
|
-
<label class="
|
|
590
|
+
<label class="bd-field__toggle">
|
|
591
591
|
<input
|
|
592
592
|
type="checkbox"
|
|
593
593
|
checked={value === true}
|
|
@@ -597,7 +597,7 @@
|
|
|
597
597
|
</label>
|
|
598
598
|
{:else if field.type === "date"}
|
|
599
599
|
<input
|
|
600
|
-
class="
|
|
600
|
+
class="bd-input bd-field__control"
|
|
601
601
|
type="date"
|
|
602
602
|
value={asStr(value)}
|
|
603
603
|
onchange={(e) => onFieldInput(field.id, e.currentTarget.value)}
|
|
@@ -606,7 +606,7 @@
|
|
|
606
606
|
{@const mode = field.timeConfig?.mode ?? "simple"}
|
|
607
607
|
{#if mode === "simple"}
|
|
608
608
|
<select
|
|
609
|
-
class="
|
|
609
|
+
class="bd-select bd-field__control"
|
|
610
610
|
value={asStr(value)}
|
|
611
611
|
onchange={(e) => onFieldInput(field.id, e.currentTarget.value)}
|
|
612
612
|
>
|
|
@@ -617,21 +617,21 @@
|
|
|
617
617
|
</select>
|
|
618
618
|
{:else if mode === "time_block"}
|
|
619
619
|
{@const v = asObj(value)}
|
|
620
|
-
<div class="
|
|
621
|
-
<label class="
|
|
622
|
-
<span class="
|
|
620
|
+
<div class="bd-field__grid2">
|
|
621
|
+
<label class="bd-field__sub">
|
|
622
|
+
<span class="bd-field__sublabel">Start</span>
|
|
623
623
|
<input
|
|
624
|
-
class="
|
|
624
|
+
class="bd-input"
|
|
625
625
|
type="time"
|
|
626
626
|
value={asStr(v.start)}
|
|
627
627
|
onchange={(e) =>
|
|
628
628
|
onFieldInput(field.id, { ...v, start: e.currentTarget.value })}
|
|
629
629
|
/>
|
|
630
630
|
</label>
|
|
631
|
-
<label class="
|
|
632
|
-
<span class="
|
|
631
|
+
<label class="bd-field__sub">
|
|
632
|
+
<span class="bd-field__sublabel">End</span>
|
|
633
633
|
<input
|
|
634
|
-
class="
|
|
634
|
+
class="bd-input"
|
|
635
635
|
type="time"
|
|
636
636
|
value={asStr(v.end)}
|
|
637
637
|
onchange={(e) =>
|
|
@@ -641,21 +641,21 @@
|
|
|
641
641
|
</div>
|
|
642
642
|
{:else}
|
|
643
643
|
{@const v = asObj(value)}
|
|
644
|
-
<div class="
|
|
645
|
-
<label class="
|
|
646
|
-
<span class="
|
|
644
|
+
<div class="bd-field__grid2">
|
|
645
|
+
<label class="bd-field__sub">
|
|
646
|
+
<span class="bd-field__sublabel">Date</span>
|
|
647
647
|
<input
|
|
648
|
-
class="
|
|
648
|
+
class="bd-input"
|
|
649
649
|
type="date"
|
|
650
650
|
value={asStr(v.date)}
|
|
651
651
|
onchange={(e) =>
|
|
652
652
|
onFieldInput(field.id, { ...v, date: e.currentTarget.value })}
|
|
653
653
|
/>
|
|
654
654
|
</label>
|
|
655
|
-
<label class="
|
|
656
|
-
<span class="
|
|
655
|
+
<label class="bd-field__sub">
|
|
656
|
+
<span class="bd-field__sublabel">Time</span>
|
|
657
657
|
<input
|
|
658
|
-
class="
|
|
658
|
+
class="bd-input"
|
|
659
659
|
type="time"
|
|
660
660
|
value={asStr(v.time)}
|
|
661
661
|
onchange={(e) =>
|
|
@@ -666,21 +666,21 @@
|
|
|
666
666
|
{/if}
|
|
667
667
|
{:else if field.type === "date_range"}
|
|
668
668
|
{@const v = asObj(value)}
|
|
669
|
-
<div class="
|
|
670
|
-
<label class="
|
|
671
|
-
<span class="
|
|
669
|
+
<div class="bd-field__grid2">
|
|
670
|
+
<label class="bd-field__sub">
|
|
671
|
+
<span class="bd-field__sublabel">Start</span>
|
|
672
672
|
<input
|
|
673
|
-
class="
|
|
673
|
+
class="bd-input"
|
|
674
674
|
type="date"
|
|
675
675
|
value={asStr(v.start)}
|
|
676
676
|
onchange={(e) =>
|
|
677
677
|
onFieldInput(field.id, { ...v, start: e.currentTarget.value })}
|
|
678
678
|
/>
|
|
679
679
|
</label>
|
|
680
|
-
<label class="
|
|
681
|
-
<span class="
|
|
680
|
+
<label class="bd-field__sub">
|
|
681
|
+
<span class="bd-field__sublabel">End</span>
|
|
682
682
|
<input
|
|
683
|
-
class="
|
|
683
|
+
class="bd-input"
|
|
684
684
|
type="date"
|
|
685
685
|
value={asStr(v.end)}
|
|
686
686
|
onchange={(e) =>
|
|
@@ -693,8 +693,8 @@
|
|
|
693
693
|
{@const max = field.sliderConfig?.max ?? 100}
|
|
694
694
|
{@const step = field.sliderConfig?.step ?? 1}
|
|
695
695
|
{@const v = asNum(value, min)}
|
|
696
|
-
<div class="
|
|
697
|
-
<div class="
|
|
696
|
+
<div class="bd-field__slider">
|
|
697
|
+
<div class="bd-field__slider-meta">
|
|
698
698
|
<span>{min}</span><span>{v}</span><span>{max}</span>
|
|
699
699
|
</div>
|
|
700
700
|
<input
|
|
@@ -710,12 +710,12 @@
|
|
|
710
710
|
{@const max = field.starRatingConfig?.maxStars ?? 5}
|
|
711
711
|
{@const v = asNum(value, 0)}
|
|
712
712
|
{@const stars = Array.from({ length: max }, (_unused, i) => i)}
|
|
713
|
-
<div class="
|
|
713
|
+
<div class="bd-field__stars">
|
|
714
714
|
{#each stars as i (i)}
|
|
715
715
|
<button
|
|
716
716
|
type="button"
|
|
717
|
-
class="
|
|
718
|
-
class:
|
|
717
|
+
class="bd-field__star"
|
|
718
|
+
class:bd-field__star--on={i < v}
|
|
719
719
|
aria-label={`Rate ${i + 1}`}
|
|
720
720
|
onclick={() => onFieldInput(field.id, i + 1)}
|
|
721
721
|
>
|
|
@@ -725,12 +725,12 @@
|
|
|
725
725
|
</div>
|
|
726
726
|
{:else if field.type === "happiness_rating"}
|
|
727
727
|
{@const v = asNum(value, -1)}
|
|
728
|
-
<div class="
|
|
728
|
+
<div class="bd-field__happiness">
|
|
729
729
|
{#each ["😞", "😕", "😐", "🙂", "😄"] as emoji, i (emoji)}
|
|
730
730
|
<button
|
|
731
731
|
type="button"
|
|
732
|
-
class="
|
|
733
|
-
class:
|
|
732
|
+
class="bd-field__emoji"
|
|
733
|
+
class:bd-field__emoji--on={i === v}
|
|
734
734
|
onclick={() => onFieldInput(field.id, i)}
|
|
735
735
|
>
|
|
736
736
|
{emoji}
|
|
@@ -739,48 +739,115 @@
|
|
|
739
739
|
</div>
|
|
740
740
|
{:else if field.type === "stepper"}
|
|
741
741
|
{@const v = asNum(value, 0)}
|
|
742
|
-
<div class="
|
|
743
|
-
<button type="button" class="
|
|
744
|
-
<span class="
|
|
745
|
-
<button type="button" class="
|
|
742
|
+
<div class="bd-field__stepper">
|
|
743
|
+
<button type="button" class="bd-btn bd-btn--ghost" onclick={() => onFieldInput(field.id, v - 1)}>−</button>
|
|
744
|
+
<span class="bd-field__stepper-value">{v}</span>
|
|
745
|
+
<button type="button" class="bd-btn bd-btn--ghost" onclick={() => onFieldInput(field.id, v + 1)}>+</button>
|
|
746
|
+
</div>
|
|
747
|
+
{:else if field.type === "name"}
|
|
748
|
+
{@const v = asObj(value)}
|
|
749
|
+
{@const cfg = field.nameConfig ?? {}}
|
|
750
|
+
{@const nicknamesText = Array.isArray(v.nicknames) ? v.nicknames.join(", ") : asStr(v.nicknames)}
|
|
751
|
+
<div class="bd-field__name">
|
|
752
|
+
{#if cfg.prefix}
|
|
753
|
+
<label class="bd-field__sub">
|
|
754
|
+
<span class="bd-field__sublabel">Title</span>
|
|
755
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "honorific-prefix"} placeholder="Dr." value={asStr(v.prefix)}
|
|
756
|
+
oninput={(e) => onFieldInput(field.id, { ...v, prefix: e.currentTarget.value })} />
|
|
757
|
+
</label>
|
|
758
|
+
{/if}
|
|
759
|
+
<div class="bd-field__grid2">
|
|
760
|
+
<label class="bd-field__sub">
|
|
761
|
+
<span class="bd-field__sublabel">First name</span>
|
|
762
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "given-name"} placeholder="Jane" value={asStr(v.first)}
|
|
763
|
+
oninput={(e) => onFieldInput(field.id, { ...v, first: e.currentTarget.value })} />
|
|
764
|
+
</label>
|
|
765
|
+
{#if cfg.middle}
|
|
766
|
+
<label class="bd-field__sub">
|
|
767
|
+
<span class="bd-field__sublabel">Middle name</span>
|
|
768
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "additional-name"} placeholder="Quinn" value={asStr(v.middle)}
|
|
769
|
+
oninput={(e) => onFieldInput(field.id, { ...v, middle: e.currentTarget.value })} />
|
|
770
|
+
</label>
|
|
771
|
+
{:else}
|
|
772
|
+
<label class="bd-field__sub">
|
|
773
|
+
<span class="bd-field__sublabel">Last name</span>
|
|
774
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "family-name"} placeholder="Doe" value={asStr(v.last)}
|
|
775
|
+
oninput={(e) => onFieldInput(field.id, { ...v, last: e.currentTarget.value })} />
|
|
776
|
+
</label>
|
|
777
|
+
{/if}
|
|
778
|
+
</div>
|
|
779
|
+
{#if cfg.middle && cfg.suffix}
|
|
780
|
+
<div class="bd-field__grid2">
|
|
781
|
+
<label class="bd-field__sub">
|
|
782
|
+
<span class="bd-field__sublabel">Last name</span>
|
|
783
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "family-name"} placeholder="Doe" value={asStr(v.last)}
|
|
784
|
+
oninput={(e) => onFieldInput(field.id, { ...v, last: e.currentTarget.value })} />
|
|
785
|
+
</label>
|
|
786
|
+
<label class="bd-field__sub">
|
|
787
|
+
<span class="bd-field__sublabel">Suffix</span>
|
|
788
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "honorific-suffix"} placeholder="Jr." value={asStr(v.suffix)}
|
|
789
|
+
oninput={(e) => onFieldInput(field.id, { ...v, suffix: e.currentTarget.value })} />
|
|
790
|
+
</label>
|
|
791
|
+
</div>
|
|
792
|
+
{:else if cfg.middle}
|
|
793
|
+
<label class="bd-field__sub">
|
|
794
|
+
<span class="bd-field__sublabel">Last name</span>
|
|
795
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "family-name"} placeholder="Doe" value={asStr(v.last)}
|
|
796
|
+
oninput={(e) => onFieldInput(field.id, { ...v, last: e.currentTarget.value })} />
|
|
797
|
+
</label>
|
|
798
|
+
{:else if cfg.suffix}
|
|
799
|
+
<label class="bd-field__sub">
|
|
800
|
+
<span class="bd-field__sublabel">Suffix</span>
|
|
801
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "honorific-suffix"} placeholder="Jr." value={asStr(v.suffix)}
|
|
802
|
+
oninput={(e) => onFieldInput(field.id, { ...v, suffix: e.currentTarget.value })} />
|
|
803
|
+
</label>
|
|
804
|
+
{/if}
|
|
805
|
+
{#if cfg.nicknames}
|
|
806
|
+
<!-- Raw comma-separated string while typing; submit normalizes it into a string[]. -->
|
|
807
|
+
<label class="bd-field__sub">
|
|
808
|
+
<span class="bd-field__sublabel">Nicknames</span>
|
|
809
|
+
<input class="bd-input" placeholder="Johnny, JM — separate with commas" value={nicknamesText}
|
|
810
|
+
oninput={(e) => onFieldInput(field.id, { ...v, nicknames: e.currentTarget.value })} />
|
|
811
|
+
</label>
|
|
812
|
+
{/if}
|
|
746
813
|
</div>
|
|
747
814
|
{:else if field.type === "address"}
|
|
748
815
|
{@const v = asObj(value)}
|
|
749
|
-
<div class="
|
|
750
|
-
<label class="
|
|
751
|
-
<span class="
|
|
752
|
-
<input class="
|
|
816
|
+
<div class="bd-field__address">
|
|
817
|
+
<label class="bd-field__sub">
|
|
818
|
+
<span class="bd-field__sublabel">Address Line 1</span>
|
|
819
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "street-address"} value={asStr(v.line1)}
|
|
753
820
|
oninput={(e) => onFieldInput(field.id, { ...v, line1: e.currentTarget.value })} />
|
|
754
821
|
</label>
|
|
755
|
-
<label class="
|
|
756
|
-
<span class="
|
|
757
|
-
<input class="
|
|
822
|
+
<label class="bd-field__sub">
|
|
823
|
+
<span class="bd-field__sublabel">Address Line 2</span>
|
|
824
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "address-line2"} value={asStr(v.line2)}
|
|
758
825
|
oninput={(e) => onFieldInput(field.id, { ...v, line2: e.currentTarget.value })} />
|
|
759
826
|
</label>
|
|
760
|
-
<label class="
|
|
761
|
-
<span class="
|
|
762
|
-
<input class="
|
|
827
|
+
<label class="bd-field__sub">
|
|
828
|
+
<span class="bd-field__sublabel">City</span>
|
|
829
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "address-level2"} value={asStr(v.city)}
|
|
763
830
|
oninput={(e) => onFieldInput(field.id, { ...v, city: e.currentTarget.value })} />
|
|
764
831
|
</label>
|
|
765
|
-
<label class="
|
|
766
|
-
<span class="
|
|
767
|
-
<input class="
|
|
832
|
+
<label class="bd-field__sub">
|
|
833
|
+
<span class="bd-field__sublabel">State</span>
|
|
834
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "address-level1"} value={asStr(v.state)}
|
|
768
835
|
oninput={(e) => onFieldInput(field.id, { ...v, state: e.currentTarget.value })} />
|
|
769
836
|
</label>
|
|
770
|
-
<label class="
|
|
771
|
-
<span class="
|
|
772
|
-
<input class="
|
|
837
|
+
<label class="bd-field__sub">
|
|
838
|
+
<span class="bd-field__sublabel">Zip</span>
|
|
839
|
+
<input class="bd-input" autocomplete={acOff ? "off" : "postal-code"} value={asStr(v.zip)}
|
|
773
840
|
oninput={(e) => onFieldInput(field.id, { ...v, zip: e.currentTarget.value })} />
|
|
774
841
|
</label>
|
|
775
842
|
</div>
|
|
776
843
|
{:else if field.type === "color_picker"}
|
|
777
844
|
{@const v = asStr(value)}
|
|
778
|
-
<div class="
|
|
845
|
+
<div class="bd-field__colors">
|
|
779
846
|
{#each field.options ?? [] as color (color)}
|
|
780
847
|
<button
|
|
781
848
|
type="button"
|
|
782
|
-
class="
|
|
783
|
-
class:
|
|
849
|
+
class="bd-field__color"
|
|
850
|
+
class:bd-field__color--on={v === color}
|
|
784
851
|
style={`background-color:${color}`}
|
|
785
852
|
title={color}
|
|
786
853
|
aria-label={color}
|
|
@@ -791,7 +858,7 @@
|
|
|
791
858
|
{:else if field.type === "file_upload"}
|
|
792
859
|
{@const list = Array.isArray(value) ? (value as Array<{ name?: string; size?: number }>) : []}
|
|
793
860
|
{#if hasClient}
|
|
794
|
-
<div class="
|
|
861
|
+
<div class="bd-field__file">
|
|
795
862
|
<input
|
|
796
863
|
type="file"
|
|
797
864
|
multiple={(field.fileConfig?.maxFiles ?? 1) > 1}
|
|
@@ -799,24 +866,24 @@
|
|
|
799
866
|
disabled={snapshot.uploading}
|
|
800
867
|
onchange={(e) => onFilePick(field, e.currentTarget.files)}
|
|
801
868
|
/>
|
|
802
|
-
{#if snapshot.uploading}<span class="
|
|
869
|
+
{#if snapshot.uploading}<span class="bd-field__file-status">Uploading…</span>{/if}
|
|
803
870
|
{#if list.length > 0}
|
|
804
|
-
<ul class="
|
|
871
|
+
<ul class="bd-field__file-list">
|
|
805
872
|
{#each list as f, i (i)}
|
|
806
873
|
<li>{f.name ?? "file"}{f.size ? ` · ${Math.round((f.size ?? 0) / 1024)} KB` : ""}</li>
|
|
807
874
|
{/each}
|
|
808
875
|
</ul>
|
|
809
876
|
{/if}
|
|
810
|
-
{#if uploadError}<p class="
|
|
877
|
+
{#if uploadError}<p class="bd-field__error">{uploadError}</p>{/if}
|
|
811
878
|
</div>
|
|
812
879
|
{:else}
|
|
813
|
-
<div class="
|
|
880
|
+
<div class="bd-field__file bd-field__file--disabled">
|
|
814
881
|
File upload requires an SDK client (uploads are disabled in schema-only mode).
|
|
815
882
|
</div>
|
|
816
883
|
{/if}
|
|
817
884
|
{:else if field.type === "phone"}
|
|
818
885
|
<input
|
|
819
|
-
class="
|
|
886
|
+
class="bd-input bd-field__control"
|
|
820
887
|
type="tel"
|
|
821
888
|
placeholder={field.placeholder ||
|
|
822
889
|
(field.phoneConfig?.international ? "+1 (555) 000-0000" : "(555) 000-0000")}
|
|
@@ -826,7 +893,7 @@
|
|
|
826
893
|
/>
|
|
827
894
|
{:else if field.type === "email"}
|
|
828
895
|
<input
|
|
829
|
-
class="
|
|
896
|
+
class="bd-input bd-field__control"
|
|
830
897
|
type="email"
|
|
831
898
|
placeholder={field.placeholder || "you@example.com"}
|
|
832
899
|
autocomplete={ac ?? undefined}
|
|
@@ -835,7 +902,7 @@
|
|
|
835
902
|
/>
|
|
836
903
|
{:else if field.type === "number"}
|
|
837
904
|
<input
|
|
838
|
-
class="
|
|
905
|
+
class="bd-input bd-field__control"
|
|
839
906
|
type="number"
|
|
840
907
|
placeholder={field.placeholder ?? ""}
|
|
841
908
|
value={typeof value === "number" ? value : asStr(value)}
|
|
@@ -844,7 +911,7 @@
|
|
|
844
911
|
/>
|
|
845
912
|
{:else}
|
|
846
913
|
<input
|
|
847
|
-
class="
|
|
914
|
+
class="bd-input bd-field__control"
|
|
848
915
|
placeholder={field.placeholder ?? ""}
|
|
849
916
|
autocomplete={ac ?? undefined}
|
|
850
917
|
value={asStr(value)}
|
|
@@ -856,38 +923,38 @@
|
|
|
856
923
|
{#snippet fieldRow(field: FormCoreField, snapshot: FormSnapshot)}
|
|
857
924
|
{@const errored = Boolean(snapshot.errors[field.id])}
|
|
858
925
|
<div
|
|
859
|
-
class="
|
|
860
|
-
class:
|
|
861
|
-
class:
|
|
926
|
+
class="bd-field bd-field--{field.type}"
|
|
927
|
+
class:bd-field--required={field.required}
|
|
928
|
+
class:bd-field--error={errored}
|
|
862
929
|
data-field-id={field.id}
|
|
863
|
-
data-
|
|
864
|
-
data-
|
|
930
|
+
data-bd-field={fieldCssKey(field)}
|
|
931
|
+
data-bd-type={field.type}
|
|
865
932
|
>
|
|
866
933
|
{#if !LABELLESS.has(field.type)}
|
|
867
|
-
<span class="
|
|
868
|
-
{field.label || ""}{#if field.required}<span class="
|
|
934
|
+
<span class="bd-label bd-field__label">
|
|
935
|
+
{field.label || ""}{#if field.required}<span class="bd-field__required"> *</span>{/if}
|
|
869
936
|
</span>
|
|
870
937
|
{/if}
|
|
871
938
|
{@render fieldInput(field, snapshot)}
|
|
872
939
|
{#if errored}
|
|
873
|
-
<p class="
|
|
940
|
+
<p class="bd-field__error">{snapshot.errors[field.id]?.message}</p>
|
|
874
941
|
{/if}
|
|
875
942
|
{#if field.helpText}
|
|
876
|
-
<p class="
|
|
943
|
+
<p class="bd-field__help">{field.helpText}</p>
|
|
877
944
|
{/if}
|
|
878
945
|
</div>
|
|
879
946
|
{/snippet}
|
|
880
947
|
|
|
881
948
|
<!-- The recursive tree walk — mirrors FormPreview's RenderTree. -->
|
|
882
949
|
{#snippet renderTree(nodes: FormCoreField[], snapshot: FormSnapshot, flat: boolean)}
|
|
883
|
-
<div class="
|
|
950
|
+
<div class="bd-tree">
|
|
884
951
|
{#each nodes as f (f.id)}
|
|
885
952
|
{#if f.type === "input_group"}
|
|
886
953
|
{@const kids = (f.children ?? []) as FormCoreField[]}
|
|
887
954
|
{@const reveal = revealCountFor(f, snapshot.values)}
|
|
888
|
-
<div class="
|
|
955
|
+
<div class="bd-group" class:bd-group--card={!flat} data-bd-group={fieldCssKey(f)}>
|
|
889
956
|
{#if f.label && f.showGroupLabel}
|
|
890
|
-
<div class="
|
|
957
|
+
<div class="bd-group__label">{f.label}</div>
|
|
891
958
|
{/if}
|
|
892
959
|
{@render renderTree(kids.slice(0, reveal), snapshot, false)}
|
|
893
960
|
</div>
|
|
@@ -900,32 +967,32 @@
|
|
|
900
967
|
{@const errored = Boolean(snapshot.errors[f.id])}
|
|
901
968
|
{@const left = f.children?.[0]}
|
|
902
969
|
{@const right = f.children?.[1]}
|
|
903
|
-
<div class="
|
|
970
|
+
<div class="bd-or" class:bd-or--error={errored}>
|
|
904
971
|
{#if f.label}
|
|
905
|
-
<div class="
|
|
906
|
-
{f.label}<span class="
|
|
972
|
+
<div class="bd-or__label">
|
|
973
|
+
{f.label}<span class="bd-or__hint"> Fill at least one side</span>
|
|
907
974
|
</div>
|
|
908
975
|
{/if}
|
|
909
|
-
<div class="
|
|
910
|
-
<div class="
|
|
911
|
-
<div class="
|
|
976
|
+
<div class="bd-or__cols">
|
|
977
|
+
<div class="bd-or__col">
|
|
978
|
+
<div class="bd-or__col-label">{f.orConditionConfig?.leftLabel || "Option A"}</div>
|
|
912
979
|
{#if left}{@render renderTree([left], snapshot, false)}{/if}
|
|
913
980
|
</div>
|
|
914
|
-
<div class="
|
|
915
|
-
<div class="
|
|
916
|
-
<div class="
|
|
981
|
+
<div class="bd-or__sep">OR</div>
|
|
982
|
+
<div class="bd-or__col">
|
|
983
|
+
<div class="bd-or__col-label">{f.orConditionConfig?.rightLabel || "Option B"}</div>
|
|
917
984
|
{#if right}{@render renderTree([right], snapshot, false)}{/if}
|
|
918
985
|
</div>
|
|
919
986
|
</div>
|
|
920
987
|
{#if errored}
|
|
921
|
-
<p class="
|
|
988
|
+
<p class="bd-field__error">Fill in at least one side to continue.</p>
|
|
922
989
|
{/if}
|
|
923
990
|
</div>
|
|
924
991
|
{:else if f.type === "flex_row"}
|
|
925
992
|
{@const rowKids = (f.children ?? []) as FormCoreField[]}
|
|
926
|
-
<div class="
|
|
993
|
+
<div class="bd-flex-row">
|
|
927
994
|
{#each rowKids as child (child.id)}
|
|
928
|
-
<div class="
|
|
995
|
+
<div class="bd-flex-row__cell">
|
|
929
996
|
{@render fieldRow(child, snapshot)}
|
|
930
997
|
</div>
|
|
931
998
|
{/each}
|
|
@@ -939,43 +1006,43 @@
|
|
|
939
1006
|
|
|
940
1007
|
<section class={rootClass}>
|
|
941
1008
|
{#if $snap.orgIcon}
|
|
942
|
-
<img class="
|
|
1009
|
+
<img class="bd-form__org-icon" src={$snap.orgIcon} alt="" />
|
|
943
1010
|
{/if}
|
|
944
1011
|
|
|
945
1012
|
{#if $snap.status === "loading"}
|
|
946
|
-
<div class="
|
|
1013
|
+
<div class="bd-loading">Loading form…</div>
|
|
947
1014
|
{:else if $snap.status === "error"}
|
|
948
|
-
<div class="
|
|
1015
|
+
<div class="bd-empty bd-form__error">{$snap.loadError ?? "Couldn't load the form."}</div>
|
|
949
1016
|
{:else if submitted}
|
|
950
|
-
<div class="
|
|
951
|
-
<span class="
|
|
1017
|
+
<div class="bd-card bd-form__success" role="status">
|
|
1018
|
+
<span class="bd-badge">Received</span>
|
|
952
1019
|
<p>{successMessage}</p>
|
|
953
1020
|
</div>
|
|
954
1021
|
{:else}
|
|
955
1022
|
{#if $snap.title || $snap.description}
|
|
956
|
-
<div class="
|
|
957
|
-
{#if $snap.title}<h2 class="
|
|
958
|
-
{#if $snap.description}<p class="
|
|
1023
|
+
<div class="bd-form__lead">
|
|
1024
|
+
{#if $snap.title}<h2 class="bd-section__title">{$snap.title}</h2>{/if}
|
|
1025
|
+
{#if $snap.description}<p class="bd-section__sub">{$snap.description}</p>{/if}
|
|
959
1026
|
</div>
|
|
960
1027
|
{/if}
|
|
961
1028
|
|
|
962
1029
|
<!-- Multi-step progress: "steps" indicator. -->
|
|
963
1030
|
{#if $snap.settings.isConsecutive && $snap.settings.showProgress && $snap.settings.progressStyle !== "bar" && $snap.stepCount > 1}
|
|
964
|
-
<div class="
|
|
965
|
-
<div class="
|
|
966
|
-
<ol class="
|
|
1031
|
+
<div class="bd-progress bd-progress--steps">
|
|
1032
|
+
<div class="bd-progress__heading">Form Progress</div>
|
|
1033
|
+
<ol class="bd-progress__steps">
|
|
967
1034
|
{#each $snap.steps as s, i (i)}
|
|
968
1035
|
{@const head = s[0]}
|
|
969
1036
|
{@const stepLabel = head?.stepTitle || head?.label || `Step ${i + 1}`}
|
|
970
1037
|
<li
|
|
971
|
-
class="
|
|
972
|
-
class:
|
|
973
|
-
class:
|
|
1038
|
+
class="bd-progress__step"
|
|
1039
|
+
class:bd-progress__step--current={i === $snap.step}
|
|
1040
|
+
class:bd-progress__step--done={i < $snap.step}
|
|
974
1041
|
>
|
|
975
|
-
<button type="button" class="
|
|
1042
|
+
<button type="button" class="bd-progress__dot" onclick={() => controller.goToStep(i)}>
|
|
976
1043
|
{i < $snap.step ? "✓" : i + 1}
|
|
977
1044
|
</button>
|
|
978
|
-
<span class="
|
|
1045
|
+
<span class="bd-progress__label">{stepLabel}</span>
|
|
979
1046
|
</li>
|
|
980
1047
|
{/each}
|
|
981
1048
|
</ol>
|
|
@@ -984,20 +1051,20 @@
|
|
|
984
1051
|
|
|
985
1052
|
<!-- Multi-step progress: thin "bar". -->
|
|
986
1053
|
{#if $snap.settings.isConsecutive && $snap.settings.showProgress && $snap.settings.progressStyle === "bar" && $snap.stepCount > 1}
|
|
987
|
-
<div class="
|
|
988
|
-
<span class="
|
|
989
|
-
<div class="
|
|
990
|
-
<div class="
|
|
1054
|
+
<div class="bd-progress bd-progress--bar">
|
|
1055
|
+
<span class="bd-progress__count">{$snap.step + 1} / {$snap.stepCount}</span>
|
|
1056
|
+
<div class="bd-progress__track">
|
|
1057
|
+
<div class="bd-progress__fill" style={`width:${$snap.progressPercent}%`}></div>
|
|
991
1058
|
</div>
|
|
992
1059
|
</div>
|
|
993
1060
|
{/if}
|
|
994
1061
|
|
|
995
1062
|
{#if $snap.errorList.length > 0}
|
|
996
|
-
<div class="
|
|
997
|
-
<div class="
|
|
1063
|
+
<div class="bd-form__errors" role="alert">
|
|
1064
|
+
<div class="bd-form__errors-title">
|
|
998
1065
|
Please complete the highlighted required field{$snap.errorList.length === 1 ? "" : "s"}.
|
|
999
1066
|
</div>
|
|
1000
|
-
<div class="
|
|
1067
|
+
<div class="bd-form__errors-list">
|
|
1001
1068
|
{$snap.errorList.map((e) => e.label).join(", ")}
|
|
1002
1069
|
</div>
|
|
1003
1070
|
</div>
|
|
@@ -1005,7 +1072,7 @@
|
|
|
1005
1072
|
|
|
1006
1073
|
<!-- The active step's subtree (single-page = all fields). -->
|
|
1007
1074
|
{#if $snap.settings.animation === "scroll"}
|
|
1008
|
-
<div class="
|
|
1075
|
+
<div class="bd-tree">
|
|
1009
1076
|
{#each $snap.visibleFields as vf (vf.id)}
|
|
1010
1077
|
{@render renderTree([vf], $snap, $snap.settings.isConsecutive)}
|
|
1011
1078
|
{/each}
|
|
@@ -1019,15 +1086,15 @@
|
|
|
1019
1086
|
{/if}
|
|
1020
1087
|
|
|
1021
1088
|
{#if submitError}
|
|
1022
|
-
<div class="
|
|
1089
|
+
<div class="bd-form__submit-error" role="alert">{submitError}</div>
|
|
1023
1090
|
{/if}
|
|
1024
1091
|
|
|
1025
1092
|
<!-- Nav: multi-step (Back / Next / Submit) or single-page (Submit). -->
|
|
1026
1093
|
{#if $snap.settings.isConsecutive && $snap.stepCount > 1}
|
|
1027
|
-
<div class="
|
|
1094
|
+
<div class="bd-nav">
|
|
1028
1095
|
<button
|
|
1029
1096
|
type="button"
|
|
1030
|
-
class="
|
|
1097
|
+
class="bd-btn bd-btn--ghost bd-btn--back"
|
|
1031
1098
|
disabled={$snap.isFirstStep}
|
|
1032
1099
|
onclick={() => controller.back()}
|
|
1033
1100
|
>
|
|
@@ -1036,7 +1103,7 @@
|
|
|
1036
1103
|
{#if !$snap.isLastStep}
|
|
1037
1104
|
<button
|
|
1038
1105
|
type="button"
|
|
1039
|
-
class="
|
|
1106
|
+
class="bd-btn bd-btn--next"
|
|
1040
1107
|
disabled={!$snap.canAdvance}
|
|
1041
1108
|
onclick={() => controller.next()}
|
|
1042
1109
|
>
|
|
@@ -1045,7 +1112,7 @@
|
|
|
1045
1112
|
{:else}
|
|
1046
1113
|
<button
|
|
1047
1114
|
type="button"
|
|
1048
|
-
class="
|
|
1115
|
+
class="bd-btn bd-btn--submit"
|
|
1049
1116
|
disabled={submitting}
|
|
1050
1117
|
onclick={() => void handleSubmit()}
|
|
1051
1118
|
>
|
|
@@ -1054,10 +1121,10 @@
|
|
|
1054
1121
|
{/if}
|
|
1055
1122
|
</div>
|
|
1056
1123
|
{:else}
|
|
1057
|
-
<div class="
|
|
1124
|
+
<div class="bd-nav">
|
|
1058
1125
|
<button
|
|
1059
1126
|
type="button"
|
|
1060
|
-
class="
|
|
1127
|
+
class="bd-btn bd-btn--submit"
|
|
1061
1128
|
disabled={submitting}
|
|
1062
1129
|
onclick={() => void handleSubmit()}
|
|
1063
1130
|
>
|