@marktiderman/genesis-forms 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,6 +32,18 @@ interface FieldBlock {
32
32
  identifying?: boolean;
33
33
  props?: Record<string, Json>;
34
34
  }
35
+ /**
36
+ * Where a choice field (single choice, multiple choice, dropdown) also takes
37
+ * options from, as `props.optionsFrom`. `source: "host"`: the app showing the
38
+ * form supplies the list named `key` when the form opens and again when a
39
+ * response is checked (`hostOptions` on `createForm` and `validateSubmission`),
40
+ * such as a practice's current coaches. They follow the field's own
41
+ * `props.options`, if it has any.
42
+ */
43
+ interface OptionsSource {
44
+ source: "host";
45
+ key: string;
46
+ }
35
47
  interface ContentBlock {
36
48
  kind: "content";
37
49
  id: string;
@@ -112,6 +124,14 @@ interface FormSettings {
112
124
  anonymous?: boolean;
113
125
  /** Clear the values of fields hidden by logic on submit. Default true. */
114
126
  clearHiddenValues?: boolean;
127
+ /**
128
+ * Save answers as they go, off by default (Typeform's partial responses). When
129
+ * true, a host saves the answers so far each time the respondent moves on
130
+ * (genesis-forms-web's `onProgress`, with `formProgress` saying how far they
131
+ * got), so an unfinished response is kept even if they never submit. The form
132
+ * tells respondents so. Saving is the host's: the engine only says it's on.
133
+ */
134
+ saveProgress?: boolean;
115
135
  }
116
136
  type FormLayout = {
117
137
  mode: "classic";
@@ -152,4 +172,4 @@ type FormEvent = {
152
172
  at?: number;
153
173
  };
154
174
 
155
- export type { Block as B, ContentBlock as C, Ending as E, FormDefinition as F, HiddenInput as H, Json as J, LogicRule as L, Operator as O, PageBreakBlock as P, FieldBlock as a, FormLayout as b, FormSettings as c, Condition as d, FormEvent as e, Operand as f };
175
+ export type { Block as B, ContentBlock as C, Ending as E, FormDefinition as F, HiddenInput as H, Json as J, LogicRule as L, OptionsSource as O, PageBreakBlock as P, FieldBlock as a, FormLayout as b, FormSettings as c, Condition as d, FormEvent as e, Operand as f, Operator as g };
@@ -32,6 +32,18 @@ interface FieldBlock {
32
32
  identifying?: boolean;
33
33
  props?: Record<string, Json>;
34
34
  }
35
+ /**
36
+ * Where a choice field (single choice, multiple choice, dropdown) also takes
37
+ * options from, as `props.optionsFrom`. `source: "host"`: the app showing the
38
+ * form supplies the list named `key` when the form opens and again when a
39
+ * response is checked (`hostOptions` on `createForm` and `validateSubmission`),
40
+ * such as a practice's current coaches. They follow the field's own
41
+ * `props.options`, if it has any.
42
+ */
43
+ interface OptionsSource {
44
+ source: "host";
45
+ key: string;
46
+ }
35
47
  interface ContentBlock {
36
48
  kind: "content";
37
49
  id: string;
@@ -112,6 +124,14 @@ interface FormSettings {
112
124
  anonymous?: boolean;
113
125
  /** Clear the values of fields hidden by logic on submit. Default true. */
114
126
  clearHiddenValues?: boolean;
127
+ /**
128
+ * Save answers as they go, off by default (Typeform's partial responses). When
129
+ * true, a host saves the answers so far each time the respondent moves on
130
+ * (genesis-forms-web's `onProgress`, with `formProgress` saying how far they
131
+ * got), so an unfinished response is kept even if they never submit. The form
132
+ * tells respondents so. Saving is the host's: the engine only says it's on.
133
+ */
134
+ saveProgress?: boolean;
115
135
  }
116
136
  type FormLayout = {
117
137
  mode: "classic";
@@ -152,4 +172,4 @@ type FormEvent = {
152
172
  at?: number;
153
173
  };
154
174
 
155
- export type { Block as B, ContentBlock as C, Ending as E, FormDefinition as F, HiddenInput as H, Json as J, LogicRule as L, Operator as O, PageBreakBlock as P, FieldBlock as a, FormLayout as b, FormSettings as c, Condition as d, FormEvent as e, Operand as f };
175
+ export type { Block as B, ContentBlock as C, Ending as E, FormDefinition as F, HiddenInput as H, Json as J, LogicRule as L, OptionsSource as O, PageBreakBlock as P, FieldBlock as a, FormLayout as b, FormSettings as c, Condition as d, FormEvent as e, Operand as f, Operator as g };
@@ -0,0 +1,131 @@
1
+ import { J as Json, g as Operator, a as FieldBlock, B as Block, O as OptionsSource, F as FormDefinition } from './types-BhYUbLJ-.js';
2
+
3
+ /**
4
+ * A field type, without any UI. Renderers are registered separately per
5
+ * platform (`genesis-forms-react`), so a manifest runs anywhere.
6
+ */
7
+ interface FieldTypeManifest {
8
+ type: string;
9
+ label: string;
10
+ category: "text" | "choice" | "scale" | "date" | "media" | "advanced";
11
+ /** `json: false` types must convert, so drafts and server replay never lose an answer. */
12
+ value: {
13
+ json: true;
14
+ } | {
15
+ json: false;
16
+ serialize: (value: unknown) => Json;
17
+ deserialize: (json: Json) => unknown;
18
+ };
19
+ operators: Operator[];
20
+ a11y: {
21
+ semantics: "control" | "group";
22
+ /** The HTML `autocomplete` token, such as `"email"` or `"tel"`. */
23
+ autocomplete?: string;
24
+ /** The HTML `inputmode` hint for on-screen keyboards, such as `"email"` or `"tel"`. */
25
+ inputMode?: "text" | "email" | "tel" | "numeric" | "decimal" | "url" | "search";
26
+ };
27
+ isEmpty?: (value: Json | undefined) => boolean;
28
+ /** Return an error message, or null when the (non-empty) value is valid. */
29
+ validate?: (value: Json, field: FieldBlock) => string | null;
30
+ /** Plain text for recall, exports and emails. */
31
+ summarize?: (value: Json, field: FieldBlock) => string;
32
+ /**
33
+ * The form a (valid, non-empty) value is stored in, such as an email
34
+ * without surrounding spaces. Applied by `engine.answers()` and the server.
35
+ */
36
+ normalize?: (value: Json, field: FieldBlock) => Json;
37
+ /** Valid example answers, used by `coverage()` and other test tools to explore a path. */
38
+ samples?: (field: FieldBlock) => Json[];
39
+ }
40
+ declare function registerFieldType(manifest: FieldTypeManifest): void;
41
+ declare function getFieldType(type: string): FieldTypeManifest | undefined;
42
+ declare function listFieldTypes(): FieldTypeManifest[];
43
+ declare function isEmptyValue(value: Json | undefined, field?: FieldBlock): boolean;
44
+ /** A value in its stored form (see `FieldTypeManifest.normalize`). */
45
+ declare function normalizeValue(value: Json, field: FieldBlock): Json;
46
+ /** Whether a block holds personal or secret data: `sensitive: true`, or the `props.sensitive: true` alias. */
47
+ declare function isSensitive(block: Block): boolean;
48
+ declare function summarizeValue(value: Json | undefined, field: FieldBlock): string;
49
+ interface ChoiceOption {
50
+ value: string;
51
+ label?: string;
52
+ }
53
+ /** The longest answer an "Other" option takes. */
54
+ declare const OTHER_MAX_LENGTH = 200;
55
+ /**
56
+ * A choice question's "Other" option (`props.other: true` or `{ label }`): the respondent types
57
+ * their own answer, stored as the answer's value itself (single choice, dropdown) or as one more
58
+ * item (multiple choice). Null when the question has none.
59
+ */
60
+ declare function otherOption(field: FieldBlock): {
61
+ label: string;
62
+ } | null;
63
+ /** Whether a value is a typed "Other" answer: text that isn't one of the question's options. */
64
+ declare function isOtherValue(field: FieldBlock, value: unknown): value is string;
65
+ declare function choiceOptions(field: FieldBlock): ChoiceOption[];
66
+ /**
67
+ * Option lists the app showing a form supplies, by key (`OptionsSource.key`):
68
+ * `{ coaches: [{ value: "8c1…", label: "Ada Lovelace" }] }`.
69
+ */
70
+ type HostOptions = Readonly<Record<string, readonly ChoiceOption[]>>;
71
+ /** A choice field's `props.optionsFrom`, when it is a well-formed `{ source: "host", key }`. */
72
+ declare function optionsSource(field: FieldBlock): OptionsSource | undefined;
73
+ /**
74
+ * The definition with each `optionsFrom: { source: "host" }` field's options
75
+ * filled in from `hostOptions`: its own `props.options` first, then the host's
76
+ * list for its key (a value the field already has is not added twice). A key
77
+ * the host does not supply adds nothing, so the field offers its own options
78
+ * only. `createForm` and `validateSubmission` call it with their `hostOptions`,
79
+ * so a respondent sees, and a submit accepts, exactly these. Returns
80
+ * `definition` itself when no field takes options from the host.
81
+ */
82
+ declare function withHostOptions(definition: FormDefinition, hostOptions: HostOptions | undefined): FormDefinition;
83
+ /** The optional words under the ends of a rating or NPS scale ("Poor", "Great"). */
84
+ declare function scaleLabels(field: FieldBlock): {
85
+ low?: string;
86
+ high?: string;
87
+ };
88
+ /** How a rating is drawn: `"numbers"` (default) or `"stars"`. */
89
+ declare function ratingDisplay(field: FieldBlock): "numbers" | "stars";
90
+ /** A `number` field's limits: `min`, `max` and `step` (each optional; `step` is above 0). */
91
+ declare function numberLimits(field: FieldBlock): {
92
+ min?: number;
93
+ max?: number;
94
+ step?: number;
95
+ };
96
+ /** `YYYY-MM-DD`, a real calendar day (no 2026-02-30), years 0001 to 9999. */
97
+ declare function isIsoDate(value: string): boolean;
98
+ /** A `date` field's earliest and latest days (`YYYY-MM-DD`), when set. */
99
+ declare function dateLimits(field: FieldBlock): {
100
+ min?: string;
101
+ max?: string;
102
+ };
103
+
104
+ /** The Standard Schema interface (standardschema.dev), as far as validation needs it. */
105
+ interface StandardSchemaV1<T = unknown> {
106
+ readonly "~standard": {
107
+ readonly version: 1;
108
+ readonly vendor: string;
109
+ readonly validate: (value: unknown) => {
110
+ value: T;
111
+ issues?: undefined;
112
+ } | {
113
+ issues: ReadonlyArray<{
114
+ message: string;
115
+ }>;
116
+ } | Promise<unknown>;
117
+ };
118
+ }
119
+ /** A custom check for one field: a function, or any Standard Schema (Zod, Valibot, ArkType…). */
120
+ type FieldValidator = ((value: Json) => string | null) | StandardSchemaV1;
121
+ declare const REQUIRED_MESSAGE = "This question needs an answer.";
122
+ /**
123
+ * Validate one field's value. `requireAnswers: false` (drafts) checks the
124
+ * format of what was answered without requiring the rest.
125
+ */
126
+ declare function validateField(field: FieldBlock, value: Json | undefined, options?: {
127
+ requireAnswers?: boolean;
128
+ custom?: FieldValidator;
129
+ }): string | null;
130
+
131
+ export { type ChoiceOption as C, type FieldValidator as F, type HostOptions as H, OTHER_MAX_LENGTH as O, REQUIRED_MESSAGE as R, type StandardSchemaV1 as S, type FieldTypeManifest as a, isIsoDate as b, choiceOptions as c, dateLimits as d, isOtherValue as e, isSensitive as f, getFieldType as g, numberLimits as h, isEmptyValue as i, otherOption as j, registerFieldType as k, listFieldTypes as l, summarizeValue as m, normalizeValue as n, optionsSource as o, ratingDisplay as r, scaleLabels as s, validateField as v, withHostOptions as w };
@@ -0,0 +1,131 @@
1
+ import { J as Json, g as Operator, a as FieldBlock, B as Block, O as OptionsSource, F as FormDefinition } from './types-BhYUbLJ-.cjs';
2
+
3
+ /**
4
+ * A field type, without any UI. Renderers are registered separately per
5
+ * platform (`genesis-forms-react`), so a manifest runs anywhere.
6
+ */
7
+ interface FieldTypeManifest {
8
+ type: string;
9
+ label: string;
10
+ category: "text" | "choice" | "scale" | "date" | "media" | "advanced";
11
+ /** `json: false` types must convert, so drafts and server replay never lose an answer. */
12
+ value: {
13
+ json: true;
14
+ } | {
15
+ json: false;
16
+ serialize: (value: unknown) => Json;
17
+ deserialize: (json: Json) => unknown;
18
+ };
19
+ operators: Operator[];
20
+ a11y: {
21
+ semantics: "control" | "group";
22
+ /** The HTML `autocomplete` token, such as `"email"` or `"tel"`. */
23
+ autocomplete?: string;
24
+ /** The HTML `inputmode` hint for on-screen keyboards, such as `"email"` or `"tel"`. */
25
+ inputMode?: "text" | "email" | "tel" | "numeric" | "decimal" | "url" | "search";
26
+ };
27
+ isEmpty?: (value: Json | undefined) => boolean;
28
+ /** Return an error message, or null when the (non-empty) value is valid. */
29
+ validate?: (value: Json, field: FieldBlock) => string | null;
30
+ /** Plain text for recall, exports and emails. */
31
+ summarize?: (value: Json, field: FieldBlock) => string;
32
+ /**
33
+ * The form a (valid, non-empty) value is stored in, such as an email
34
+ * without surrounding spaces. Applied by `engine.answers()` and the server.
35
+ */
36
+ normalize?: (value: Json, field: FieldBlock) => Json;
37
+ /** Valid example answers, used by `coverage()` and other test tools to explore a path. */
38
+ samples?: (field: FieldBlock) => Json[];
39
+ }
40
+ declare function registerFieldType(manifest: FieldTypeManifest): void;
41
+ declare function getFieldType(type: string): FieldTypeManifest | undefined;
42
+ declare function listFieldTypes(): FieldTypeManifest[];
43
+ declare function isEmptyValue(value: Json | undefined, field?: FieldBlock): boolean;
44
+ /** A value in its stored form (see `FieldTypeManifest.normalize`). */
45
+ declare function normalizeValue(value: Json, field: FieldBlock): Json;
46
+ /** Whether a block holds personal or secret data: `sensitive: true`, or the `props.sensitive: true` alias. */
47
+ declare function isSensitive(block: Block): boolean;
48
+ declare function summarizeValue(value: Json | undefined, field: FieldBlock): string;
49
+ interface ChoiceOption {
50
+ value: string;
51
+ label?: string;
52
+ }
53
+ /** The longest answer an "Other" option takes. */
54
+ declare const OTHER_MAX_LENGTH = 200;
55
+ /**
56
+ * A choice question's "Other" option (`props.other: true` or `{ label }`): the respondent types
57
+ * their own answer, stored as the answer's value itself (single choice, dropdown) or as one more
58
+ * item (multiple choice). Null when the question has none.
59
+ */
60
+ declare function otherOption(field: FieldBlock): {
61
+ label: string;
62
+ } | null;
63
+ /** Whether a value is a typed "Other" answer: text that isn't one of the question's options. */
64
+ declare function isOtherValue(field: FieldBlock, value: unknown): value is string;
65
+ declare function choiceOptions(field: FieldBlock): ChoiceOption[];
66
+ /**
67
+ * Option lists the app showing a form supplies, by key (`OptionsSource.key`):
68
+ * `{ coaches: [{ value: "8c1…", label: "Ada Lovelace" }] }`.
69
+ */
70
+ type HostOptions = Readonly<Record<string, readonly ChoiceOption[]>>;
71
+ /** A choice field's `props.optionsFrom`, when it is a well-formed `{ source: "host", key }`. */
72
+ declare function optionsSource(field: FieldBlock): OptionsSource | undefined;
73
+ /**
74
+ * The definition with each `optionsFrom: { source: "host" }` field's options
75
+ * filled in from `hostOptions`: its own `props.options` first, then the host's
76
+ * list for its key (a value the field already has is not added twice). A key
77
+ * the host does not supply adds nothing, so the field offers its own options
78
+ * only. `createForm` and `validateSubmission` call it with their `hostOptions`,
79
+ * so a respondent sees, and a submit accepts, exactly these. Returns
80
+ * `definition` itself when no field takes options from the host.
81
+ */
82
+ declare function withHostOptions(definition: FormDefinition, hostOptions: HostOptions | undefined): FormDefinition;
83
+ /** The optional words under the ends of a rating or NPS scale ("Poor", "Great"). */
84
+ declare function scaleLabels(field: FieldBlock): {
85
+ low?: string;
86
+ high?: string;
87
+ };
88
+ /** How a rating is drawn: `"numbers"` (default) or `"stars"`. */
89
+ declare function ratingDisplay(field: FieldBlock): "numbers" | "stars";
90
+ /** A `number` field's limits: `min`, `max` and `step` (each optional; `step` is above 0). */
91
+ declare function numberLimits(field: FieldBlock): {
92
+ min?: number;
93
+ max?: number;
94
+ step?: number;
95
+ };
96
+ /** `YYYY-MM-DD`, a real calendar day (no 2026-02-30), years 0001 to 9999. */
97
+ declare function isIsoDate(value: string): boolean;
98
+ /** A `date` field's earliest and latest days (`YYYY-MM-DD`), when set. */
99
+ declare function dateLimits(field: FieldBlock): {
100
+ min?: string;
101
+ max?: string;
102
+ };
103
+
104
+ /** The Standard Schema interface (standardschema.dev), as far as validation needs it. */
105
+ interface StandardSchemaV1<T = unknown> {
106
+ readonly "~standard": {
107
+ readonly version: 1;
108
+ readonly vendor: string;
109
+ readonly validate: (value: unknown) => {
110
+ value: T;
111
+ issues?: undefined;
112
+ } | {
113
+ issues: ReadonlyArray<{
114
+ message: string;
115
+ }>;
116
+ } | Promise<unknown>;
117
+ };
118
+ }
119
+ /** A custom check for one field: a function, or any Standard Schema (Zod, Valibot, ArkType…). */
120
+ type FieldValidator = ((value: Json) => string | null) | StandardSchemaV1;
121
+ declare const REQUIRED_MESSAGE = "This question needs an answer.";
122
+ /**
123
+ * Validate one field's value. `requireAnswers: false` (drafts) checks the
124
+ * format of what was answered without requiring the rest.
125
+ */
126
+ declare function validateField(field: FieldBlock, value: Json | undefined, options?: {
127
+ requireAnswers?: boolean;
128
+ custom?: FieldValidator;
129
+ }): string | null;
130
+
131
+ export { type ChoiceOption as C, type FieldValidator as F, type HostOptions as H, OTHER_MAX_LENGTH as O, REQUIRED_MESSAGE as R, type StandardSchemaV1 as S, type FieldTypeManifest as a, isIsoDate as b, choiceOptions as c, dateLimits as d, isOtherValue as e, isSensitive as f, getFieldType as g, numberLimits as h, isEmptyValue as i, otherOption as j, registerFieldType as k, listFieldTypes as l, summarizeValue as m, normalizeValue as n, optionsSource as o, ratingDisplay as r, scaleLabels as s, validateField as v, withHostOptions as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marktiderman/genesis-forms",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -1,30 +0,0 @@
1
- import { J as Json, a as FieldBlock } from './types-BBKBQ8sp.cjs';
2
-
3
- /** The Standard Schema interface (standardschema.dev), as far as validation needs it. */
4
- interface StandardSchemaV1<T = unknown> {
5
- readonly "~standard": {
6
- readonly version: 1;
7
- readonly vendor: string;
8
- readonly validate: (value: unknown) => {
9
- value: T;
10
- issues?: undefined;
11
- } | {
12
- issues: ReadonlyArray<{
13
- message: string;
14
- }>;
15
- } | Promise<unknown>;
16
- };
17
- }
18
- /** A custom check for one field: a function, or any Standard Schema (Zod, Valibot, ArkType…). */
19
- type FieldValidator = ((value: Json) => string | null) | StandardSchemaV1;
20
- declare const REQUIRED_MESSAGE = "This question needs an answer.";
21
- /**
22
- * Validate one field's value. `requireAnswers: false` (drafts) checks the
23
- * format of what was answered without requiring the rest.
24
- */
25
- declare function validateField(field: FieldBlock, value: Json | undefined, options?: {
26
- requireAnswers?: boolean;
27
- custom?: FieldValidator;
28
- }): string | null;
29
-
30
- export { type FieldValidator as F, REQUIRED_MESSAGE as R, type StandardSchemaV1 as S, validateField as v };
@@ -1,30 +0,0 @@
1
- import { J as Json, a as FieldBlock } from './types-BBKBQ8sp.js';
2
-
3
- /** The Standard Schema interface (standardschema.dev), as far as validation needs it. */
4
- interface StandardSchemaV1<T = unknown> {
5
- readonly "~standard": {
6
- readonly version: 1;
7
- readonly vendor: string;
8
- readonly validate: (value: unknown) => {
9
- value: T;
10
- issues?: undefined;
11
- } | {
12
- issues: ReadonlyArray<{
13
- message: string;
14
- }>;
15
- } | Promise<unknown>;
16
- };
17
- }
18
- /** A custom check for one field: a function, or any Standard Schema (Zod, Valibot, ArkType…). */
19
- type FieldValidator = ((value: Json) => string | null) | StandardSchemaV1;
20
- declare const REQUIRED_MESSAGE = "This question needs an answer.";
21
- /**
22
- * Validate one field's value. `requireAnswers: false` (drafts) checks the
23
- * format of what was answered without requiring the rest.
24
- */
25
- declare function validateField(field: FieldBlock, value: Json | undefined, options?: {
26
- requireAnswers?: boolean;
27
- custom?: FieldValidator;
28
- }): string | null;
29
-
30
- export { type FieldValidator as F, REQUIRED_MESSAGE as R, type StandardSchemaV1 as S, validateField as v };