@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,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@businessdash/sdk/angular` — `<
|
|
2
|
+
* `@businessdash/sdk/angular` — `<bd-form>` drop-in Angular component.
|
|
3
3
|
*
|
|
4
4
|
* Ship mode: SOURCE. This file is a single standalone Angular component shipped
|
|
5
5
|
* as plain `.ts` (no ng-packagr / APF) and compiled by the CONSUMER's Angular
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
* multi-step nav + progress, concurrent reveal, submission + file uploads — live
|
|
15
15
|
* in the core. This component:
|
|
16
16
|
*
|
|
17
|
-
* 1. Creates a controller from `slug` + an injected/`@Input` `
|
|
17
|
+
* 1. Creates a controller from `slug` + an injected/`@Input` `BdClient` (or
|
|
18
18
|
* a pre-fetched `schema` for SSR).
|
|
19
19
|
* 2. Mirrors the controller's `snapshot()` into a single Angular `signal`,
|
|
20
20
|
* re-publishing on every `subscribe` notify (so OnPush change detection
|
|
21
21
|
* fires precisely when the form state changes).
|
|
22
22
|
* 3. Renders the FULL nested field tree from `snapshot.visibleFields` using
|
|
23
|
-
* the `
|
|
23
|
+
* the `bd-*` light-DOM class convention (no shadow root), so the org's
|
|
24
24
|
* own CSS cascades exactly as it does on the dashboard renderer.
|
|
25
25
|
* 4. Applies `schema.settings` — animation, multi-step + progress
|
|
26
26
|
* (bar/steps), concurrent reveal, and the org icon — so the drop-in
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
* field renderer, mapped onto Angular template control flow. Because the tree is
|
|
31
31
|
* recursive (`input_group` / `condition_block` / `or_condition` / `flex_row`
|
|
32
32
|
* with children), the component renders itself recursively via the private
|
|
33
|
-
* `<
|
|
34
|
-
* `<
|
|
33
|
+
* `<bd-form-node>` sub-component below — the public surface stays a single
|
|
34
|
+
* `<bd-form>` tag.
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import {
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
} from "@angular/core";
|
|
50
50
|
|
|
51
51
|
import {
|
|
52
|
-
type
|
|
52
|
+
type BdClient,
|
|
53
53
|
type FieldError,
|
|
54
54
|
type FormController,
|
|
55
55
|
type FormCoreField,
|
|
@@ -72,42 +72,42 @@ import {
|
|
|
72
72
|
} from "../forms-core/autocomplete.js";
|
|
73
73
|
|
|
74
74
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
75
|
-
// DI — provide a shared
|
|
76
|
-
// every `<
|
|
75
|
+
// DI — provide a shared BdClient app-wide so consumers don't repeat it on
|
|
76
|
+
// every `<bd-form>`. Mirrors the React `<DashboardProvider>` ergonomics but
|
|
77
77
|
// uses Angular's idiomatic InjectionToken + provider helper.
|
|
78
78
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Optional DI token carrying the shared {@link
|
|
82
|
-
* the app root (e.g. via {@link
|
|
81
|
+
* Optional DI token carrying the shared {@link BdClient}. Provide it once at
|
|
82
|
+
* the app root (e.g. via {@link provideBdClient}) and every `<bd-form>`
|
|
83
83
|
* picks it up automatically — no `[client]` binding required. A `[client]`
|
|
84
84
|
* input still wins per-instance when both are present.
|
|
85
85
|
*/
|
|
86
|
-
export const
|
|
86
|
+
export const BD_CLIENT = new InjectionToken<BdClient>("BD_CLIENT");
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
89
|
* Provider helper for the application config. Drop into `bootstrapApplication`'s
|
|
90
|
-
* `providers` (or a route's `providers`) so `<
|
|
90
|
+
* `providers` (or a route's `providers`) so `<bd-form slug="…">` works with no
|
|
91
91
|
* per-instance `[client]`:
|
|
92
92
|
*
|
|
93
93
|
* ```ts
|
|
94
94
|
* bootstrapApplication(App, {
|
|
95
|
-
* providers: [
|
|
95
|
+
* providers: [provideBdClient(myBdClient)],
|
|
96
96
|
* });
|
|
97
97
|
* ```
|
|
98
98
|
*/
|
|
99
|
-
export function
|
|
100
|
-
return { provide:
|
|
99
|
+
export function provideBdClient(client: BdClient) {
|
|
100
|
+
return { provide: BD_CLIENT, useValue: client };
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
104
104
|
// Recursive field-tree renderer — one node + its subtree. Private to the module;
|
|
105
|
-
// the public API is only `<
|
|
106
|
-
// `RenderTree` + `FieldRow` + `FieldInput`, using the same `
|
|
107
|
-
// `data-
|
|
105
|
+
// the public API is only `<bd-form>`. Faithful port of FormPreview's
|
|
106
|
+
// `RenderTree` + `FieldRow` + `FieldInput`, using the same `bd-*` classes and
|
|
107
|
+
// `data-bd-*` hooks so org CSS targets the same selectors as the dashboard.
|
|
108
108
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
109
109
|
|
|
110
|
-
/** Stable CSS hook for a field (the `data-
|
|
110
|
+
/** Stable CSS hook for a field (the `data-bd-field` value): explicit key →
|
|
111
111
|
* camelCase label slug → id. Mirrors FormPreview's `fieldCssKey`. */
|
|
112
112
|
function fieldCssKey(field: FormCoreField): string {
|
|
113
113
|
const explicit =
|
|
@@ -136,17 +136,17 @@ function fieldCssKey(field: FormCoreField): string {
|
|
|
136
136
|
const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
137
137
|
|
|
138
138
|
@Component({
|
|
139
|
-
selector: "
|
|
139
|
+
selector: "bd-form-node",
|
|
140
140
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
141
141
|
template: `
|
|
142
142
|
@switch (field.type) {
|
|
143
143
|
@case ("input_group") {
|
|
144
|
-
<div class="
|
|
144
|
+
<div class="bd-group" [attr.data-bd-group]="cssKey()">
|
|
145
145
|
@if (field.label && field.showGroupLabel) {
|
|
146
|
-
<div class="
|
|
146
|
+
<div class="bd-group__label">{{ field.label }}</div>
|
|
147
147
|
}
|
|
148
148
|
@for (child of revealedChildren(); track child.id) {
|
|
149
|
-
<
|
|
149
|
+
<bd-form-node
|
|
150
150
|
[field]="child"
|
|
151
151
|
[values]="values"
|
|
152
152
|
[errors]="errors"
|
|
@@ -161,7 +161,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
161
161
|
@case ("condition_block") {
|
|
162
162
|
@if (conditionPasses()) {
|
|
163
163
|
@for (child of children(); track child.id) {
|
|
164
|
-
<
|
|
164
|
+
<bd-form-node
|
|
165
165
|
[field]="child"
|
|
166
166
|
[values]="values"
|
|
167
167
|
[errors]="errors"
|
|
@@ -175,23 +175,23 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
175
175
|
}
|
|
176
176
|
@case ("or_condition") {
|
|
177
177
|
<div
|
|
178
|
-
class="
|
|
179
|
-
[class.
|
|
180
|
-
[attr.data-
|
|
178
|
+
class="bd-or"
|
|
179
|
+
[class.bd-or--error]="isErrored()"
|
|
180
|
+
[attr.data-bd-or]="cssKey()"
|
|
181
181
|
>
|
|
182
182
|
@if (field.label) {
|
|
183
|
-
<div class="
|
|
183
|
+
<div class="bd-or__label">
|
|
184
184
|
{{ field.label }}
|
|
185
|
-
<span class="
|
|
185
|
+
<span class="bd-or__hint">Fill at least one side</span>
|
|
186
186
|
</div>
|
|
187
187
|
}
|
|
188
|
-
<div class="
|
|
189
|
-
<div class="
|
|
190
|
-
<div class="
|
|
188
|
+
<div class="bd-or__grid">
|
|
189
|
+
<div class="bd-or__side">
|
|
190
|
+
<div class="bd-or__side-label">
|
|
191
191
|
{{ orLeftLabel() }}
|
|
192
192
|
</div>
|
|
193
193
|
@if (children()[0]; as left) {
|
|
194
|
-
<
|
|
194
|
+
<bd-form-node
|
|
195
195
|
[field]="left"
|
|
196
196
|
[values]="values"
|
|
197
197
|
[errors]="errors"
|
|
@@ -202,13 +202,13 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
202
202
|
/>
|
|
203
203
|
}
|
|
204
204
|
</div>
|
|
205
|
-
<div class="
|
|
206
|
-
<div class="
|
|
207
|
-
<div class="
|
|
205
|
+
<div class="bd-or__divider"><span>OR</span></div>
|
|
206
|
+
<div class="bd-or__side">
|
|
207
|
+
<div class="bd-or__side-label">
|
|
208
208
|
{{ orRightLabel() }}
|
|
209
209
|
</div>
|
|
210
210
|
@if (children()[1]; as right) {
|
|
211
|
-
<
|
|
211
|
+
<bd-form-node
|
|
212
212
|
[field]="right"
|
|
213
213
|
[values]="values"
|
|
214
214
|
[errors]="errors"
|
|
@@ -221,15 +221,15 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
221
221
|
</div>
|
|
222
222
|
</div>
|
|
223
223
|
@if (isErrored()) {
|
|
224
|
-
<p class="
|
|
224
|
+
<p class="bd-field__error">Fill in at least one side to continue.</p>
|
|
225
225
|
}
|
|
226
226
|
</div>
|
|
227
227
|
}
|
|
228
228
|
@case ("flex_row") {
|
|
229
|
-
<div class="
|
|
229
|
+
<div class="bd-flex-row">
|
|
230
230
|
@for (child of children(); track child.id) {
|
|
231
|
-
<div class="
|
|
232
|
-
<
|
|
231
|
+
<div class="bd-flex-row__cell">
|
|
232
|
+
<bd-form-node
|
|
233
233
|
[field]="child"
|
|
234
234
|
[values]="values"
|
|
235
235
|
[errors]="errors"
|
|
@@ -245,44 +245,44 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
245
245
|
@default {
|
|
246
246
|
<!-- Leaf field row -->
|
|
247
247
|
<div
|
|
248
|
-
class="
|
|
248
|
+
class="bd-field"
|
|
249
249
|
[class]="rowClass()"
|
|
250
250
|
[attr.data-field-id]="field.id"
|
|
251
|
-
[attr.data-
|
|
252
|
-
[attr.data-
|
|
251
|
+
[attr.data-bd-field]="cssKey()"
|
|
252
|
+
[attr.data-bd-type]="field.type"
|
|
253
253
|
>
|
|
254
254
|
@if (showLabel()) {
|
|
255
|
-
<label class="
|
|
255
|
+
<label class="bd-field__label" [for]="field.id">
|
|
256
256
|
{{ field.label || "" }}
|
|
257
257
|
@if (field.required) {
|
|
258
|
-
<span class="
|
|
258
|
+
<span class="bd-field__required">*</span>
|
|
259
259
|
}
|
|
260
260
|
</label>
|
|
261
261
|
}
|
|
262
262
|
|
|
263
263
|
@switch (field.type) {
|
|
264
264
|
@case ("title") {
|
|
265
|
-
<h2 class="
|
|
265
|
+
<h2 class="bd-field__title">{{ field.label || "Untitled" }}</h2>
|
|
266
266
|
}
|
|
267
267
|
@case ("subtitle") {
|
|
268
|
-
<h3 class="
|
|
268
|
+
<h3 class="bd-field__subtitle">{{ field.label || "" }}</h3>
|
|
269
269
|
}
|
|
270
270
|
@case ("separator") {
|
|
271
|
-
<hr class="
|
|
271
|
+
<hr class="bd-field__separator" />
|
|
272
272
|
}
|
|
273
273
|
@case ("richtext") {
|
|
274
274
|
<!-- content is org-authored HTML; the host page is trusted same-origin.
|
|
275
275
|
Sanitize upstream if you accept untrusted form authors. -->
|
|
276
|
-
<div class="
|
|
276
|
+
<div class="bd-field__richtext" [innerHTML]="field.content || ''"></div>
|
|
277
277
|
}
|
|
278
278
|
@case ("image") {
|
|
279
279
|
@if (imageSrc()) {
|
|
280
|
-
<img class="
|
|
280
|
+
<img class="bd-field__image" [src]="imageSrc()" alt="" />
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
@case ("textarea") {
|
|
284
284
|
<textarea
|
|
285
|
-
class="
|
|
285
|
+
class="bd-input bd-input--textarea"
|
|
286
286
|
[id]="field.id"
|
|
287
287
|
[value]="stringValue()"
|
|
288
288
|
[placeholder]="field.placeholder || ''"
|
|
@@ -297,22 +297,22 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
297
297
|
the worse default. A list attribute plus a datalist gives
|
|
298
298
|
native typeahead: no JS, no styling opinion. -->
|
|
299
299
|
<input
|
|
300
|
-
class="
|
|
300
|
+
class="bd-input bd-combobox"
|
|
301
301
|
type="text"
|
|
302
302
|
[id]="field.id"
|
|
303
|
-
[attr.list]="'
|
|
303
|
+
[attr.list]="'bd-list-' + field.id"
|
|
304
304
|
[placeholder]="field.placeholder || 'Search…'"
|
|
305
305
|
[value]="stringValue()"
|
|
306
306
|
(input)="onInput($event)"
|
|
307
307
|
/>
|
|
308
|
-
<datalist [id]="'
|
|
308
|
+
<datalist [id]="'bd-list-' + field.id">
|
|
309
309
|
@for (opt of field.options || []; track opt) {
|
|
310
310
|
<option [value]="opt"></option>
|
|
311
311
|
}
|
|
312
312
|
</datalist>
|
|
313
313
|
} @else {
|
|
314
314
|
<select
|
|
315
|
-
class="
|
|
315
|
+
class="bd-input bd-input--select"
|
|
316
316
|
[id]="field.id"
|
|
317
317
|
[value]="stringValue()"
|
|
318
318
|
(change)="onInput($event)"
|
|
@@ -328,16 +328,16 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
328
328
|
<!-- The value is an icon NAME; art resolves against the
|
|
329
329
|
platform's icon route, so no icon library ships in the
|
|
330
330
|
SDK bundle. -->
|
|
331
|
-
<div class="
|
|
331
|
+
<div class="bd-iconselect">
|
|
332
332
|
<input
|
|
333
|
-
class="
|
|
333
|
+
class="bd-input bd-iconselect__search"
|
|
334
334
|
type="text"
|
|
335
335
|
[placeholder]="field.placeholder || 'Search icons…'"
|
|
336
336
|
[value]="iconQuery()"
|
|
337
337
|
(input)="onIconQuery($event)"
|
|
338
338
|
/>
|
|
339
339
|
@if (stringValue()) {
|
|
340
|
-
<span class="
|
|
340
|
+
<span class="bd-iconselect__current">
|
|
341
341
|
<img
|
|
342
342
|
[src]="iconSrc(stringValue())"
|
|
343
343
|
[alt]="stringValue()"
|
|
@@ -346,18 +346,18 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
346
346
|
/>
|
|
347
347
|
<button
|
|
348
348
|
type="button"
|
|
349
|
-
class="
|
|
349
|
+
class="bd-iconselect__clear"
|
|
350
350
|
(click)="setIconValue('')"
|
|
351
351
|
>
|
|
352
352
|
×
|
|
353
353
|
</button>
|
|
354
354
|
</span>
|
|
355
355
|
}
|
|
356
|
-
<div class="
|
|
356
|
+
<div class="bd-iconselect__grid">
|
|
357
357
|
@for (name of visibleIcons(); track name) {
|
|
358
358
|
<button
|
|
359
359
|
type="button"
|
|
360
|
-
class="
|
|
360
|
+
class="bd-iconselect__item"
|
|
361
361
|
[class.is-selected]="stringValue() === name"
|
|
362
362
|
[title]="name"
|
|
363
363
|
[attr.aria-label]="name"
|
|
@@ -372,12 +372,12 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
372
372
|
}
|
|
373
373
|
@case ("image_input") {
|
|
374
374
|
<!-- One image, either source; the value is a plain URL string. -->
|
|
375
|
-
<div class="
|
|
375
|
+
<div class="bd-imageinput">
|
|
376
376
|
@if (imageSources() === "both") {
|
|
377
|
-
<div class="
|
|
377
|
+
<div class="bd-imageinput__modes">
|
|
378
378
|
<button
|
|
379
379
|
type="button"
|
|
380
|
-
class="
|
|
380
|
+
class="bd-imageinput__mode"
|
|
381
381
|
[class.is-active]="imageMode() === 'upload'"
|
|
382
382
|
[attr.aria-pressed]="imageMode() === 'upload'"
|
|
383
383
|
(click)="setImageMode('upload')"
|
|
@@ -386,7 +386,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
386
386
|
</button>
|
|
387
387
|
<button
|
|
388
388
|
type="button"
|
|
389
|
-
class="
|
|
389
|
+
class="bd-imageinput__mode"
|
|
390
390
|
[class.is-active]="imageMode() === 'url'"
|
|
391
391
|
[attr.aria-pressed]="imageMode() === 'url'"
|
|
392
392
|
(click)="setImageMode('url')"
|
|
@@ -397,14 +397,14 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
397
397
|
}
|
|
398
398
|
@if (imageMode() === "upload" && imageSources() !== "url") {
|
|
399
399
|
<input
|
|
400
|
-
class="
|
|
400
|
+
class="bd-input bd-imageinput__file"
|
|
401
401
|
type="file"
|
|
402
402
|
[attr.accept]="imageAccept()"
|
|
403
403
|
(change)="onImagePick($event)"
|
|
404
404
|
/>
|
|
405
405
|
} @else {
|
|
406
406
|
<input
|
|
407
|
-
class="
|
|
407
|
+
class="bd-input bd-imageinput__url"
|
|
408
408
|
type="url"
|
|
409
409
|
[placeholder]="
|
|
410
410
|
field.placeholder || 'https://example.com/image.png'
|
|
@@ -415,7 +415,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
415
415
|
}
|
|
416
416
|
@if (showImagePreview() && stringValue()) {
|
|
417
417
|
<img
|
|
418
|
-
class="
|
|
418
|
+
class="bd-imageinput__preview"
|
|
419
419
|
[src]="stringValue()"
|
|
420
420
|
alt=""
|
|
421
421
|
/>
|
|
@@ -423,9 +423,9 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
423
423
|
</div>
|
|
424
424
|
}
|
|
425
425
|
@case ("radio") {
|
|
426
|
-
<div class="
|
|
426
|
+
<div class="bd-input bd-input--radio">
|
|
427
427
|
@for (opt of field.options || []; track opt) {
|
|
428
|
-
<label class="
|
|
428
|
+
<label class="bd-radio__item">
|
|
429
429
|
<input
|
|
430
430
|
type="radio"
|
|
431
431
|
[name]="field.id"
|
|
@@ -439,9 +439,9 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
439
439
|
</div>
|
|
440
440
|
}
|
|
441
441
|
@case ("checkbox") {
|
|
442
|
-
<div class="
|
|
442
|
+
<div class="bd-input bd-input--checkbox">
|
|
443
443
|
@for (opt of field.options || []; track opt) {
|
|
444
|
-
<label class="
|
|
444
|
+
<label class="bd-checkbox__item">
|
|
445
445
|
<input
|
|
446
446
|
type="checkbox"
|
|
447
447
|
[value]="opt"
|
|
@@ -454,7 +454,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
454
454
|
</div>
|
|
455
455
|
}
|
|
456
456
|
@case ("boolean") {
|
|
457
|
-
<label class="
|
|
457
|
+
<label class="bd-input bd-input--boolean">
|
|
458
458
|
<input
|
|
459
459
|
type="checkbox"
|
|
460
460
|
[checked]="booleanValue()"
|
|
@@ -465,7 +465,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
465
465
|
}
|
|
466
466
|
@case ("date") {
|
|
467
467
|
<input
|
|
468
|
-
class="
|
|
468
|
+
class="bd-input bd-input--date"
|
|
469
469
|
type="date"
|
|
470
470
|
[id]="field.id"
|
|
471
471
|
[value]="stringValue()"
|
|
@@ -474,7 +474,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
474
474
|
}
|
|
475
475
|
@case ("email") {
|
|
476
476
|
<input
|
|
477
|
-
class="
|
|
477
|
+
class="bd-input bd-input--email"
|
|
478
478
|
type="email"
|
|
479
479
|
[id]="field.id"
|
|
480
480
|
[value]="stringValue()"
|
|
@@ -485,7 +485,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
485
485
|
}
|
|
486
486
|
@case ("phone") {
|
|
487
487
|
<input
|
|
488
|
-
class="
|
|
488
|
+
class="bd-input bd-input--phone"
|
|
489
489
|
type="tel"
|
|
490
490
|
[id]="field.id"
|
|
491
491
|
[value]="stringValue()"
|
|
@@ -496,7 +496,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
496
496
|
}
|
|
497
497
|
@case ("number") {
|
|
498
498
|
<input
|
|
499
|
-
class="
|
|
499
|
+
class="bd-input bd-input--number"
|
|
500
500
|
type="number"
|
|
501
501
|
[id]="field.id"
|
|
502
502
|
[value]="numberInputValue()"
|
|
@@ -505,21 +505,21 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
505
505
|
/>
|
|
506
506
|
}
|
|
507
507
|
@case ("stepper") {
|
|
508
|
-
<div class="
|
|
509
|
-
<button type="button" class="
|
|
508
|
+
<div class="bd-input bd-input--stepper">
|
|
509
|
+
<button type="button" class="bd-btn bd-stepper__dec" (click)="bumpStepper(-1)">
|
|
510
510
|
−
|
|
511
511
|
</button>
|
|
512
|
-
<span class="
|
|
513
|
-
<button type="button" class="
|
|
512
|
+
<span class="bd-stepper__value">{{ numberValue() }}</span>
|
|
513
|
+
<button type="button" class="bd-btn bd-stepper__inc" (click)="bumpStepper(1)">
|
|
514
514
|
+
|
|
515
515
|
</button>
|
|
516
516
|
</div>
|
|
517
517
|
}
|
|
518
518
|
@case ("slider") {
|
|
519
|
-
<div class="
|
|
520
|
-
<div class="
|
|
519
|
+
<div class="bd-input bd-input--slider">
|
|
520
|
+
<div class="bd-slider__readout">
|
|
521
521
|
<span>{{ sliderFmt(sliderMin()) }}</span>
|
|
522
|
-
<span class="
|
|
522
|
+
<span class="bd-slider__current">{{ sliderFmt(numberValue()) }}</span>
|
|
523
523
|
<span>{{ sliderFmt(sliderMax()) }}</span>
|
|
524
524
|
</div>
|
|
525
525
|
<input
|
|
@@ -533,12 +533,12 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
533
533
|
</div>
|
|
534
534
|
}
|
|
535
535
|
@case ("star_rating") {
|
|
536
|
-
<div class="
|
|
536
|
+
<div class="bd-input bd-input--stars">
|
|
537
537
|
@for (i of starIndexes(); track i) {
|
|
538
538
|
<button
|
|
539
539
|
type="button"
|
|
540
|
-
class="
|
|
541
|
-
[class.
|
|
540
|
+
class="bd-star"
|
|
541
|
+
[class.bd-star--on]="i < numberValue()"
|
|
542
542
|
(click)="setValue(i + 1)"
|
|
543
543
|
[attr.aria-label]="i + 1 + ' stars'"
|
|
544
544
|
>
|
|
@@ -548,12 +548,12 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
548
548
|
</div>
|
|
549
549
|
}
|
|
550
550
|
@case ("happiness_rating") {
|
|
551
|
-
<div class="
|
|
551
|
+
<div class="bd-input bd-input--happiness">
|
|
552
552
|
@for (face of happinessFaces(); track $index) {
|
|
553
553
|
<button
|
|
554
554
|
type="button"
|
|
555
|
-
class="
|
|
556
|
-
[class.
|
|
555
|
+
class="bd-happiness__face"
|
|
556
|
+
[class.bd-happiness__face--on]="$index === numberValue()"
|
|
557
557
|
(click)="setValue($index)"
|
|
558
558
|
>
|
|
559
559
|
{{ face }}
|
|
@@ -562,12 +562,12 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
562
562
|
</div>
|
|
563
563
|
}
|
|
564
564
|
@case ("color_picker") {
|
|
565
|
-
<div class="
|
|
565
|
+
<div class="bd-input bd-input--colors">
|
|
566
566
|
@for (color of field.options || []; track color) {
|
|
567
567
|
<button
|
|
568
568
|
type="button"
|
|
569
|
-
class="
|
|
570
|
-
[class.
|
|
569
|
+
class="bd-color"
|
|
570
|
+
[class.bd-color--on]="stringValue() === color"
|
|
571
571
|
[style.backgroundColor]="color"
|
|
572
572
|
[title]="color"
|
|
573
573
|
(click)="setValue(color)"
|
|
@@ -576,12 +576,12 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
576
576
|
</div>
|
|
577
577
|
}
|
|
578
578
|
@case ("date_range") {
|
|
579
|
-
<div class="
|
|
580
|
-
<label class="
|
|
579
|
+
<div class="bd-input bd-input--date-range">
|
|
580
|
+
<label class="bd-subfield">
|
|
581
581
|
<span>Start</span>
|
|
582
582
|
<input type="date" [value]="rangeStart()" (input)="onRange('start', $event)" />
|
|
583
583
|
</label>
|
|
584
|
-
<label class="
|
|
584
|
+
<label class="bd-subfield">
|
|
585
585
|
<span>End</span>
|
|
586
586
|
<input type="date" [value]="rangeEnd()" (input)="onRange('end', $event)" />
|
|
587
587
|
</label>
|
|
@@ -590,24 +590,24 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
590
590
|
@case ("time_picker") {
|
|
591
591
|
@switch (timeMode()) {
|
|
592
592
|
@case ("time_block") {
|
|
593
|
-
<div class="
|
|
594
|
-
<label class="
|
|
593
|
+
<div class="bd-input bd-input--time-block">
|
|
594
|
+
<label class="bd-subfield">
|
|
595
595
|
<span>Start</span>
|
|
596
596
|
<input type="time" [value]="rangeStart()" (input)="onRange('start', $event)" />
|
|
597
597
|
</label>
|
|
598
|
-
<label class="
|
|
598
|
+
<label class="bd-subfield">
|
|
599
599
|
<span>End</span>
|
|
600
600
|
<input type="time" [value]="rangeEnd()" (input)="onRange('end', $event)" />
|
|
601
601
|
</label>
|
|
602
602
|
</div>
|
|
603
603
|
}
|
|
604
604
|
@case ("appointment") {
|
|
605
|
-
<div class="
|
|
606
|
-
<label class="
|
|
605
|
+
<div class="bd-input bd-input--appointment">
|
|
606
|
+
<label class="bd-subfield">
|
|
607
607
|
<span>Date</span>
|
|
608
608
|
<input type="date" [value]="apptDate()" (input)="onAppt('date', $event)" />
|
|
609
609
|
</label>
|
|
610
|
-
<label class="
|
|
610
|
+
<label class="bd-subfield">
|
|
611
611
|
<span>Time</span>
|
|
612
612
|
<input type="time" [value]="apptTime()" (input)="onAppt('time', $event)" />
|
|
613
613
|
</label>
|
|
@@ -615,7 +615,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
615
615
|
}
|
|
616
616
|
@default {
|
|
617
617
|
<input
|
|
618
|
-
class="
|
|
618
|
+
class="bd-input bd-input--time"
|
|
619
619
|
type="time"
|
|
620
620
|
[value]="stringValue()"
|
|
621
621
|
(input)="onInput($event)"
|
|
@@ -623,9 +623,75 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
623
623
|
}
|
|
624
624
|
}
|
|
625
625
|
}
|
|
626
|
+
@case ("name") {
|
|
627
|
+
<div class="bd-input bd-input--name">
|
|
628
|
+
@if (nameCfg().prefix) {
|
|
629
|
+
<label class="bd-subfield bd-subfield--full">
|
|
630
|
+
<span>Title</span>
|
|
631
|
+
<input
|
|
632
|
+
placeholder="Dr."
|
|
633
|
+
[value]="nameField('prefix')"
|
|
634
|
+
[attr.autocomplete]="nameAutoComplete('honorific-prefix')"
|
|
635
|
+
(input)="onName('prefix', $event)"
|
|
636
|
+
/>
|
|
637
|
+
</label>
|
|
638
|
+
}
|
|
639
|
+
<label class="bd-subfield">
|
|
640
|
+
<span>First name</span>
|
|
641
|
+
<input
|
|
642
|
+
placeholder="Jane"
|
|
643
|
+
[value]="nameField('first')"
|
|
644
|
+
[attr.autocomplete]="nameAutoComplete('given-name')"
|
|
645
|
+
(input)="onName('first', $event)"
|
|
646
|
+
/>
|
|
647
|
+
</label>
|
|
648
|
+
@if (nameCfg().middle) {
|
|
649
|
+
<label class="bd-subfield">
|
|
650
|
+
<span>Middle name</span>
|
|
651
|
+
<input
|
|
652
|
+
placeholder="Quinn"
|
|
653
|
+
[value]="nameField('middle')"
|
|
654
|
+
[attr.autocomplete]="nameAutoComplete('additional-name')"
|
|
655
|
+
(input)="onName('middle', $event)"
|
|
656
|
+
/>
|
|
657
|
+
</label>
|
|
658
|
+
}
|
|
659
|
+
<label class="bd-subfield" [class.bd-subfield--full]="nameLastFull()">
|
|
660
|
+
<span>Last name</span>
|
|
661
|
+
<input
|
|
662
|
+
placeholder="Doe"
|
|
663
|
+
[value]="nameField('last')"
|
|
664
|
+
[attr.autocomplete]="nameAutoComplete('family-name')"
|
|
665
|
+
(input)="onName('last', $event)"
|
|
666
|
+
/>
|
|
667
|
+
</label>
|
|
668
|
+
@if (nameCfg().suffix) {
|
|
669
|
+
<label class="bd-subfield" [class.bd-subfield--full]="nameSuffixFull()">
|
|
670
|
+
<span>Suffix</span>
|
|
671
|
+
<input
|
|
672
|
+
placeholder="Jr."
|
|
673
|
+
[value]="nameField('suffix')"
|
|
674
|
+
[attr.autocomplete]="nameAutoComplete('honorific-suffix')"
|
|
675
|
+
(input)="onName('suffix', $event)"
|
|
676
|
+
/>
|
|
677
|
+
</label>
|
|
678
|
+
}
|
|
679
|
+
@if (nameCfg().nicknames) {
|
|
680
|
+
<!-- Raw comma-separated string while typing; submit normalizes it into a string[]. -->
|
|
681
|
+
<label class="bd-subfield bd-subfield--full">
|
|
682
|
+
<span>Nicknames</span>
|
|
683
|
+
<input
|
|
684
|
+
placeholder="Johnny, JM — separate with commas"
|
|
685
|
+
[value]="nameNicknamesText()"
|
|
686
|
+
(input)="onName('nicknames', $event)"
|
|
687
|
+
/>
|
|
688
|
+
</label>
|
|
689
|
+
}
|
|
690
|
+
</div>
|
|
691
|
+
}
|
|
626
692
|
@case ("address") {
|
|
627
|
-
<div class="
|
|
628
|
-
<label class="
|
|
693
|
+
<div class="bd-input bd-input--address">
|
|
694
|
+
<label class="bd-subfield bd-subfield--full">
|
|
629
695
|
<span>Address Line 1</span>
|
|
630
696
|
<input
|
|
631
697
|
[value]="addrField('line1')"
|
|
@@ -633,7 +699,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
633
699
|
(input)="onAddr('line1', $event)"
|
|
634
700
|
/>
|
|
635
701
|
</label>
|
|
636
|
-
<label class="
|
|
702
|
+
<label class="bd-subfield bd-subfield--full">
|
|
637
703
|
<span>Address Line 2</span>
|
|
638
704
|
<input
|
|
639
705
|
[value]="addrField('line2')"
|
|
@@ -641,7 +707,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
641
707
|
(input)="onAddr('line2', $event)"
|
|
642
708
|
/>
|
|
643
709
|
</label>
|
|
644
|
-
<label class="
|
|
710
|
+
<label class="bd-subfield">
|
|
645
711
|
<span>City</span>
|
|
646
712
|
<input
|
|
647
713
|
[value]="addrField('city')"
|
|
@@ -649,7 +715,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
649
715
|
(input)="onAddr('city', $event)"
|
|
650
716
|
/>
|
|
651
717
|
</label>
|
|
652
|
-
<label class="
|
|
718
|
+
<label class="bd-subfield">
|
|
653
719
|
<span>State</span>
|
|
654
720
|
<input
|
|
655
721
|
[value]="addrField('state')"
|
|
@@ -657,7 +723,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
657
723
|
(input)="onAddr('state', $event)"
|
|
658
724
|
/>
|
|
659
725
|
</label>
|
|
660
|
-
<label class="
|
|
726
|
+
<label class="bd-subfield bd-subfield--full">
|
|
661
727
|
<span>Zip</span>
|
|
662
728
|
<input
|
|
663
729
|
[value]="addrField('zip')"
|
|
@@ -668,7 +734,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
668
734
|
</div>
|
|
669
735
|
}
|
|
670
736
|
@case ("file_upload") {
|
|
671
|
-
<div class="
|
|
737
|
+
<div class="bd-input bd-input--file">
|
|
672
738
|
<input
|
|
673
739
|
type="file"
|
|
674
740
|
[multiple]="fileMultiple()"
|
|
@@ -676,9 +742,9 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
676
742
|
(change)="onFile($event)"
|
|
677
743
|
/>
|
|
678
744
|
@if (fileList().length) {
|
|
679
|
-
<ul class="
|
|
745
|
+
<ul class="bd-file__list">
|
|
680
746
|
@for (f of fileList(); track f.storageId) {
|
|
681
|
-
<li class="
|
|
747
|
+
<li class="bd-file__item">{{ f.name }}</li>
|
|
682
748
|
}
|
|
683
749
|
</ul>
|
|
684
750
|
}
|
|
@@ -687,7 +753,7 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
687
753
|
@default {
|
|
688
754
|
<!-- text and any plain-text input -->
|
|
689
755
|
<input
|
|
690
|
-
class="
|
|
756
|
+
class="bd-input bd-input--text"
|
|
691
757
|
type="text"
|
|
692
758
|
[id]="field.id"
|
|
693
759
|
[value]="stringValue()"
|
|
@@ -699,24 +765,24 @@ const NO_LABEL_TYPES = new Set(["title", "subtitle", "separator", "richtext"]);
|
|
|
699
765
|
}
|
|
700
766
|
|
|
701
767
|
@if (isErrored()) {
|
|
702
|
-
<p class="
|
|
768
|
+
<p class="bd-field__error">{{ errorMessage() }}</p>
|
|
703
769
|
}
|
|
704
770
|
@if (field.helpText) {
|
|
705
|
-
<p class="
|
|
771
|
+
<p class="bd-field__help">{{ field.helpText }}</p>
|
|
706
772
|
}
|
|
707
773
|
</div>
|
|
708
774
|
}
|
|
709
775
|
}
|
|
710
776
|
`,
|
|
711
777
|
})
|
|
712
|
-
export class
|
|
778
|
+
export class BdFormNodeComponent {
|
|
713
779
|
/** The field (subtree root) to render. */
|
|
714
780
|
@Input({ required: true }) field!: FormCoreField;
|
|
715
781
|
/** Working values, keyed by field id (the snapshot's `values`). */
|
|
716
782
|
@Input({ required: true }) values!: Record<string, unknown>;
|
|
717
783
|
/** Field-level errors, keyed by field id (the snapshot's `errors`). */
|
|
718
784
|
@Input({ required: true }) errors!: Record<string, FieldError>;
|
|
719
|
-
/** Autofill config threaded from `<
|
|
785
|
+
/** Autofill config threaded from `<bd-form>` (global switch + per-field
|
|
720
786
|
* overrides). Drives the `autocomplete` attribute on value-bearing inputs. */
|
|
721
787
|
@Input() autocompleteConfig: AutocompleteConfig = {};
|
|
722
788
|
|
|
@@ -759,6 +825,12 @@ export class BiabFormNodeComponent {
|
|
|
759
825
|
return this.autoCompleteOff() ? "off" : token;
|
|
760
826
|
}
|
|
761
827
|
|
|
828
|
+
/** Fixed token for a name composite sub-field, suppressed to "off" when the
|
|
829
|
+
* field's resolved token is "off". */
|
|
830
|
+
nameAutoComplete(token: string): string {
|
|
831
|
+
return this.autoCompleteOff() ? "off" : token;
|
|
832
|
+
}
|
|
833
|
+
|
|
762
834
|
children(): FormCoreField[] {
|
|
763
835
|
return (this.field.children ?? []) as FormCoreField[];
|
|
764
836
|
}
|
|
@@ -787,9 +859,9 @@ export class BiabFormNodeComponent {
|
|
|
787
859
|
}
|
|
788
860
|
|
|
789
861
|
rowClass(): string {
|
|
790
|
-
const parts = [`
|
|
791
|
-
if (this.field.required) parts.push("
|
|
792
|
-
if (this.isErrored()) parts.push("
|
|
862
|
+
const parts = [`bd-field--${this.field.type}`];
|
|
863
|
+
if (this.field.required) parts.push("bd-field--required");
|
|
864
|
+
if (this.isErrored()) parts.push("bd-field--error");
|
|
793
865
|
return parts.join(" ");
|
|
794
866
|
}
|
|
795
867
|
|
|
@@ -1036,6 +1108,42 @@ export class BiabFormNodeComponent {
|
|
|
1036
1108
|
this.setValue({ ...this.addrObj(), [key]: value });
|
|
1037
1109
|
}
|
|
1038
1110
|
|
|
1111
|
+
// ── name ──
|
|
1112
|
+
private nameObj(): Record<string, unknown> {
|
|
1113
|
+
const v = this.raw();
|
|
1114
|
+
return v && typeof v === "object" ? (v as Record<string, unknown>) : {};
|
|
1115
|
+
}
|
|
1116
|
+
nameCfg(): NonNullable<FormCoreField["nameConfig"]> {
|
|
1117
|
+
return this.field.nameConfig ?? {};
|
|
1118
|
+
}
|
|
1119
|
+
nameField(key: string): string {
|
|
1120
|
+
const v = this.nameObj()[key];
|
|
1121
|
+
return typeof v === "string" ? v : "";
|
|
1122
|
+
}
|
|
1123
|
+
/** Last name spans the row only when a middle name shares the first row with
|
|
1124
|
+
* the first name and no suffix follows to pair with it. */
|
|
1125
|
+
nameLastFull(): boolean {
|
|
1126
|
+
const cfg = this.nameCfg();
|
|
1127
|
+
return cfg.middle === true && cfg.suffix !== true;
|
|
1128
|
+
}
|
|
1129
|
+
/** Suffix spans the row when it isn't paired with the last name. */
|
|
1130
|
+
nameSuffixFull(): boolean {
|
|
1131
|
+
return this.nameCfg().middle !== true;
|
|
1132
|
+
}
|
|
1133
|
+
/** The nicknames input holds the raw comma-separated string while the
|
|
1134
|
+
* visitor types; submit normalizes it into a string[]. */
|
|
1135
|
+
nameNicknamesText(): string {
|
|
1136
|
+
const v = this.nameObj()["nicknames"];
|
|
1137
|
+
if (Array.isArray(v)) {
|
|
1138
|
+
return v.filter((n): n is string => typeof n === "string").join(", ");
|
|
1139
|
+
}
|
|
1140
|
+
return typeof v === "string" ? v : "";
|
|
1141
|
+
}
|
|
1142
|
+
onName(key: string, event: Event): void {
|
|
1143
|
+
const value = (event.target as HTMLInputElement).value;
|
|
1144
|
+
this.setValue({ ...this.nameObj(), [key]: value });
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1039
1147
|
// ── phone ──
|
|
1040
1148
|
phonePlaceholder(): string {
|
|
1041
1149
|
if (this.field.placeholder) return this.field.placeholder;
|
|
@@ -1072,62 +1180,62 @@ export class BiabFormNodeComponent {
|
|
|
1072
1180
|
}
|
|
1073
1181
|
|
|
1074
1182
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1075
|
-
// Public component — `<
|
|
1183
|
+
// Public component — `<bd-form>`. Owns the controller, mirrors its snapshot
|
|
1076
1184
|
// into a signal, and orchestrates steps / progress / submit. Renders each
|
|
1077
|
-
// visible top-level node via `<
|
|
1185
|
+
// visible top-level node via `<bd-form-node>`.
|
|
1078
1186
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
1079
1187
|
|
|
1080
1188
|
@Component({
|
|
1081
|
-
selector: "
|
|
1189
|
+
selector: "bd-form",
|
|
1082
1190
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1083
|
-
imports: [
|
|
1191
|
+
imports: [BdFormNodeComponent],
|
|
1084
1192
|
host: {
|
|
1085
|
-
class: "
|
|
1086
|
-
"[class.
|
|
1087
|
-
"[class.
|
|
1088
|
-
"[attr.data-
|
|
1193
|
+
class: "bd-form",
|
|
1194
|
+
"[class.bd-form--submitting]": "snapshot().submitting",
|
|
1195
|
+
"[class.bd-form--submitted]": "snapshot().submitted",
|
|
1196
|
+
"[attr.data-bd-animation]": "snapshot().settings.animation",
|
|
1089
1197
|
},
|
|
1090
1198
|
template: `
|
|
1091
1199
|
@let snap = snapshot();
|
|
1092
1200
|
|
|
1093
1201
|
@if (snap.orgIcon) {
|
|
1094
|
-
<img class="
|
|
1202
|
+
<img class="bd-form__org-icon" [src]="snap.orgIcon" alt="" />
|
|
1095
1203
|
}
|
|
1096
1204
|
|
|
1097
1205
|
@switch (snap.status) {
|
|
1098
1206
|
@case ("loading") {
|
|
1099
|
-
<div class="
|
|
1207
|
+
<div class="bd-form__loading">Loading…</div>
|
|
1100
1208
|
}
|
|
1101
1209
|
@case ("error") {
|
|
1102
|
-
<div class="
|
|
1210
|
+
<div class="bd-form__load-error">
|
|
1103
1211
|
{{ snap.loadError || "Couldn't load this form." }}
|
|
1104
1212
|
</div>
|
|
1105
1213
|
}
|
|
1106
1214
|
@default {
|
|
1107
1215
|
@if (snap.submitted) {
|
|
1108
|
-
<div class="
|
|
1216
|
+
<div class="bd-form__success">
|
|
1109
1217
|
{{ successMessage() }}
|
|
1110
1218
|
</div>
|
|
1111
1219
|
} @else {
|
|
1112
1220
|
<form
|
|
1113
|
-
class="
|
|
1114
|
-
[attr.data-
|
|
1221
|
+
class="bd-form__body"
|
|
1222
|
+
[attr.data-bd-step]="snap.step"
|
|
1115
1223
|
(ngSubmit)="onSubmit()"
|
|
1116
1224
|
>
|
|
1117
1225
|
@if (showSteps()) {
|
|
1118
|
-
<div class="
|
|
1119
|
-
<div class="
|
|
1120
|
-
<ol class="
|
|
1226
|
+
<div class="bd-progress bd-progress--steps">
|
|
1227
|
+
<div class="bd-progress__title">Form Progress</div>
|
|
1228
|
+
<ol class="bd-progress__list">
|
|
1121
1229
|
@for (s of snap.steps; track $index; let i = $index) {
|
|
1122
1230
|
<li
|
|
1123
|
-
class="
|
|
1124
|
-
[class.
|
|
1125
|
-
[class.
|
|
1231
|
+
class="bd-progress__step"
|
|
1232
|
+
[class.bd-progress__step--current]="i === snap.step"
|
|
1233
|
+
[class.bd-progress__step--done]="i < snap.step"
|
|
1126
1234
|
>
|
|
1127
|
-
<span class="
|
|
1235
|
+
<span class="bd-progress__index">
|
|
1128
1236
|
{{ i < snap.step ? "✓" : i + 1 }}
|
|
1129
1237
|
</span>
|
|
1130
|
-
<span class="
|
|
1238
|
+
<span class="bd-progress__label">{{ stepTitle(s, i) }}</span>
|
|
1131
1239
|
</li>
|
|
1132
1240
|
}
|
|
1133
1241
|
</ol>
|
|
@@ -1135,13 +1243,13 @@ export class BiabFormNodeComponent {
|
|
|
1135
1243
|
}
|
|
1136
1244
|
|
|
1137
1245
|
@if (showBar()) {
|
|
1138
|
-
<div class="
|
|
1139
|
-
<span class="
|
|
1246
|
+
<div class="bd-progress bd-progress--bar">
|
|
1247
|
+
<span class="bd-progress__count">
|
|
1140
1248
|
{{ snap.step + 1 }} / {{ snap.stepCount }}
|
|
1141
1249
|
</span>
|
|
1142
|
-
<div class="
|
|
1250
|
+
<div class="bd-progress__track">
|
|
1143
1251
|
<div
|
|
1144
|
-
class="
|
|
1252
|
+
class="bd-progress__fill"
|
|
1145
1253
|
[style.width.%]="snap.progressPercent"
|
|
1146
1254
|
></div>
|
|
1147
1255
|
</div>
|
|
@@ -1149,21 +1257,21 @@ export class BiabFormNodeComponent {
|
|
|
1149
1257
|
}
|
|
1150
1258
|
|
|
1151
1259
|
@if (snap.errorList.length) {
|
|
1152
|
-
<div class="
|
|
1153
|
-
<div class="
|
|
1260
|
+
<div class="bd-form__errors" role="alert">
|
|
1261
|
+
<div class="bd-form__errors-title">
|
|
1154
1262
|
Please complete the highlighted required field{{
|
|
1155
1263
|
snap.errorList.length === 1 ? "" : "s"
|
|
1156
1264
|
}}.
|
|
1157
1265
|
</div>
|
|
1158
|
-
<div class="
|
|
1266
|
+
<div class="bd-form__errors-list">
|
|
1159
1267
|
{{ errorLabels() }}
|
|
1160
1268
|
</div>
|
|
1161
1269
|
</div>
|
|
1162
1270
|
}
|
|
1163
1271
|
|
|
1164
|
-
<div class="
|
|
1272
|
+
<div class="bd-form__fields" [attr.data-bd-direction]="snap.forward ? 'forward' : 'back'">
|
|
1165
1273
|
@for (node of snap.visibleFields; track node.id) {
|
|
1166
|
-
<
|
|
1274
|
+
<bd-form-node
|
|
1167
1275
|
[field]="node"
|
|
1168
1276
|
[values]="snap.values"
|
|
1169
1277
|
[errors]="snap.errors"
|
|
@@ -1175,11 +1283,11 @@ export class BiabFormNodeComponent {
|
|
|
1175
1283
|
}
|
|
1176
1284
|
</div>
|
|
1177
1285
|
|
|
1178
|
-
<div class="
|
|
1286
|
+
<div class="bd-nav">
|
|
1179
1287
|
@if (isMultiStep()) {
|
|
1180
1288
|
<button
|
|
1181
1289
|
type="button"
|
|
1182
|
-
class="
|
|
1290
|
+
class="bd-btn bd-btn--back"
|
|
1183
1291
|
[disabled]="snap.isFirstStep"
|
|
1184
1292
|
(click)="back()"
|
|
1185
1293
|
>
|
|
@@ -1189,7 +1297,7 @@ export class BiabFormNodeComponent {
|
|
|
1189
1297
|
@if (isMultiStep() && !snap.isLastStep) {
|
|
1190
1298
|
<button
|
|
1191
1299
|
type="button"
|
|
1192
|
-
class="
|
|
1300
|
+
class="bd-btn bd-btn--next"
|
|
1193
1301
|
[disabled]="!snap.canAdvance"
|
|
1194
1302
|
(click)="next()"
|
|
1195
1303
|
>
|
|
@@ -1198,7 +1306,7 @@ export class BiabFormNodeComponent {
|
|
|
1198
1306
|
} @else {
|
|
1199
1307
|
<button
|
|
1200
1308
|
type="submit"
|
|
1201
|
-
class="
|
|
1309
|
+
class="bd-btn bd-btn--submit"
|
|
1202
1310
|
[disabled]="snap.submitting || snap.uploading"
|
|
1203
1311
|
>
|
|
1204
1312
|
{{ snap.submitting ? "Sending…" : submitLabel() }}
|
|
@@ -1207,7 +1315,7 @@ export class BiabFormNodeComponent {
|
|
|
1207
1315
|
</div>
|
|
1208
1316
|
|
|
1209
1317
|
@if (failureMessage()) {
|
|
1210
|
-
<p class="
|
|
1318
|
+
<p class="bd-form__submit-error" role="alert">{{ failureMessage() }}</p>
|
|
1211
1319
|
}
|
|
1212
1320
|
</form>
|
|
1213
1321
|
}
|
|
@@ -1215,10 +1323,10 @@ export class BiabFormNodeComponent {
|
|
|
1215
1323
|
}
|
|
1216
1324
|
`,
|
|
1217
1325
|
})
|
|
1218
|
-
export class
|
|
1326
|
+
export class BdFormComponent implements OnInit {
|
|
1219
1327
|
private readonly destroyRef = inject(DestroyRef);
|
|
1220
1328
|
/** App-wide client from DI (optional). A `[client]` input wins over it. */
|
|
1221
|
-
private readonly injectedClient = inject(
|
|
1329
|
+
private readonly injectedClient = inject(BD_CLIENT, { optional: true });
|
|
1222
1330
|
|
|
1223
1331
|
// ── Inputs (signal inputs; the public, framework-consistent surface) ──
|
|
1224
1332
|
|
|
@@ -1229,7 +1337,7 @@ export class BiabFormComponent implements OnInit {
|
|
|
1229
1337
|
* submit / upload. */
|
|
1230
1338
|
readonly schema = input<FormSchema | undefined>(undefined);
|
|
1231
1339
|
/** Per-instance client override. Falls back to the DI-provided client. */
|
|
1232
|
-
readonly client = input<
|
|
1340
|
+
readonly client = input<BdClient | undefined>(undefined);
|
|
1233
1341
|
/** Submit button label (final step). Defaults to "Submit". */
|
|
1234
1342
|
readonly submitLabel = input<string>("Submit");
|
|
1235
1343
|
/** Validate-only submit: nothing is stored, no side effects. */
|
|
@@ -1275,7 +1383,7 @@ export class BiabFormComponent implements OnInit {
|
|
|
1275
1383
|
private controller: FormController | null = null;
|
|
1276
1384
|
|
|
1277
1385
|
// Resolve the effective client: per-instance input → DI token.
|
|
1278
|
-
private resolveClient():
|
|
1386
|
+
private resolveClient(): BdClient | undefined {
|
|
1279
1387
|
return this.client() ?? this.injectedClient ?? undefined;
|
|
1280
1388
|
}
|
|
1281
1389
|
|