@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.
package/dist/index.d.cts CHANGED
@@ -1,9 +1,68 @@
1
- import { a as FieldBlock, C as ContentBlock, P as PageBreakBlock, F as FormDefinition, H as HiddenInput, b as FormLayout, L as LogicRule, E as Ending, c as FormSettings, J as Json, d as Condition, B as Block, O as Operator } from './types-BBKBQ8sp.cjs';
2
- export { e as FormEvent, f as Operand } from './types-BBKBQ8sp.cjs';
3
- import { F as FormEngine } from './engine-BkNuzlna.cjs';
4
- export { C as CreateFormOptions, a as FormState, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-BkNuzlna.cjs';
5
- import { F as FieldValidator } from './validate-BXXqi0fq.cjs';
6
- export { R as REQUIRED_MESSAGE, S as StandardSchemaV1, v as validateField } from './validate-BXXqi0fq.cjs';
1
+ import { a as FieldBlock, J as Json, C as ContentBlock, P as PageBreakBlock, O as OptionsSource, F as FormDefinition, H as HiddenInput, b as FormLayout, L as LogicRule, E as Ending, c as FormSettings, d as Condition, B as Block } from './types-BhYUbLJ-.cjs';
2
+ export { e as FormEvent, f as Operand, g as Operator } from './types-BhYUbLJ-.cjs';
3
+ import { F as FormEngine, a as FormState } from './engine-BDq0XJ21.cjs';
4
+ export { C as CreateFormOptions, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-BDq0XJ21.cjs';
5
+ import { F as FieldValidator } from './validate-BsZ0YTQS.cjs';
6
+ export { C as ChoiceOption, a as FieldTypeManifest, H as HostOptions, O as OTHER_MAX_LENGTH, R as REQUIRED_MESSAGE, S as StandardSchemaV1, c as choiceOptions, d as dateLimits, g as getFieldType, i as isEmptyValue, b as isIsoDate, e as isOtherValue, f as isSensitive, l as listFieldTypes, n as normalizeValue, h as numberLimits, o as optionsSource, j as otherOption, r as ratingDisplay, k as registerFieldType, s as scaleLabels, m as summarizeValue, v as validateField, w as withHostOptions } from './validate-BsZ0YTQS.cjs';
7
+
8
+ /**
9
+ * The `photo` field type: a respondent attaches up to a few photos, such as a
10
+ * picture of broken equipment. The answer holds references the host's storage
11
+ * handed back (`PhotoRef`), never the bytes: the renderer uploads through the
12
+ * host (`onUpload` on genesis-forms-web's `Form`), so the engine and the
13
+ * server stay storage-agnostic. A host's server should check that each
14
+ * reference is a file it stored for this respondent before saving.
15
+ *
16
+ * Opt-in in a builder palette (`UPLOAD_FIELD_TYPES`), since a host must be
17
+ * able to store the files.
18
+ */
19
+ declare const PHOTO_TYPE = "photo";
20
+ /** Field types that need the host to store files; a builder offers them only when the host names them. */
21
+ declare const UPLOAD_FIELD_TYPES: readonly ["photo"];
22
+ /** One stored photo, as the host's upload returned it. */
23
+ type PhotoRef = {
24
+ /** The host's id for the stored file. */
25
+ id: string;
26
+ /** The file's name on the respondent's device. */
27
+ name: string;
28
+ /** Its MIME type, such as `image/jpeg`. */
29
+ type: string;
30
+ /** Bytes. */
31
+ size: number;
32
+ };
33
+ type PhotoFormat = "jpeg" | "png" | "heic" | "webp";
34
+ /** The MIME types each format covers. */
35
+ declare const PHOTO_FORMATS: Record<PhotoFormat, readonly string[]>;
36
+ declare const DEFAULT_PHOTO_FILES = 3;
37
+ declare const MAX_PHOTO_FILES = 5;
38
+ declare const DEFAULT_PHOTO_SIZE_MB = 10;
39
+ declare const MAX_PHOTO_SIZE_MB = 25;
40
+ /** The longest reference id and file name a photo answer may carry. */
41
+ declare const MAX_PHOTO_ID_LENGTH = 200;
42
+ declare const MAX_PHOTO_NAME_LENGTH = 255;
43
+ declare const isPhotoFormat: (v: unknown) => v is PhotoFormat;
44
+ interface PhotoLimits {
45
+ maxFiles: number;
46
+ maxSizeMb: number;
47
+ maxBytes: number;
48
+ formats: PhotoFormat[];
49
+ /** Every MIME type the formats cover. */
50
+ mimeTypes: string[];
51
+ }
52
+ /** A photo field's limits; a prop that is missing or out of range uses its default. */
53
+ declare function photoLimits(field: FieldBlock): PhotoLimits;
54
+ /** Whether `v` has a photo reference's shape (any type and size). */
55
+ declare function isPhotoRef(v: unknown): v is PhotoRef;
56
+ /** The well-formed references in a photo answer, as `{ id, name, type, size }` only. */
57
+ declare function photoRefs(value: Json | undefined): PhotoRef[];
58
+ /** "JPEG, PNG or WebP". */
59
+ declare function photoFormatList(formats: readonly PhotoFormat[]): string;
60
+ /** The message for a file of a type the field doesn't take. */
61
+ declare const photoTypeMessage: (field: FieldBlock) => string;
62
+ /** The message for a file over the field's size limit. */
63
+ declare const photoSizeMessage: (field: FieldBlock) => string;
64
+ /** The message for more photos than the field takes. */
65
+ declare const photoCountMessage: (field: FieldBlock) => string;
7
66
 
8
67
  /** A field block that remembers its key and value type, for answer inference. */
9
68
  type FieldSpec<K extends string = string, V = unknown> = FieldBlock & {
@@ -24,10 +83,24 @@ interface FieldOptions {
24
83
  id?: string | undefined;
25
84
  }
26
85
  interface ChoiceFieldOptions<O extends string> extends FieldOptions {
27
- options: readonly {
86
+ /** The options. Optional only with `optionsFrom`, whose options follow these. */
87
+ options?: readonly {
28
88
  value: O;
29
89
  label?: string;
30
- }[];
90
+ }[] | undefined;
91
+ /**
92
+ * Options the app showing the form supplies, by key (`OptionsSource`), such
93
+ * as `{ source: "host", key: "coaches" }`; pass the lists as `hostOptions`
94
+ * to `createForm` and `validateSubmission`.
95
+ */
96
+ optionsFrom?: OptionsSource | undefined;
97
+ /**
98
+ * An "Other" option the respondent fills in (`true`, or `{ label }` to name it): their text is
99
+ * the answer (one more item on a multiple choice). See `otherOption`.
100
+ */
101
+ other?: boolean | {
102
+ label?: string;
103
+ } | undefined;
31
104
  }
32
105
  interface HiddenOptions {
33
106
  source?: HiddenInput["source"] | undefined;
@@ -137,6 +210,16 @@ declare const f: {
137
210
  quote: string;
138
211
  note: string;
139
212
  }[]>;
213
+ /**
214
+ * Up to a few photos (a picture of broken equipment). The answer holds the
215
+ * references the host's upload returned, never the bytes; `formats` from
216
+ * jpeg, png, heic and webp (default all four).
217
+ */
218
+ photo: <K extends string>(key: K, o: FieldOptions & {
219
+ maxFiles?: number | undefined;
220
+ maxSizeMb?: number | undefined;
221
+ formats?: readonly PhotoFormat[] | undefined;
222
+ }) => FieldSpec<K, PhotoRef[]>;
140
223
  /** Any registered field type, including plugins. */
141
224
  custom: <K extends string, V = Json>(type: string, key: K, o: FieldOptions & {
142
225
  props?: Record<string, Json>;
@@ -333,76 +416,38 @@ declare function computePath(def: FormDefinition, scope: EvalScope): PathResult;
333
416
  declare function ruleVisible(def: FormDefinition, blockId: string, scope: EvalScope): boolean;
334
417
 
335
418
  /**
336
- * A field type, without any UI. Renderers are registered separately per
337
- * platform (`genesis-forms-react`), so a manifest runs anywhere.
419
+ * How far a respondent has got, for saving answers as they go
420
+ * (`settings.saveProgress`) and for drop-off by question. Pure: reads a form's
421
+ * state, never changes it.
338
422
  */
339
- interface FieldTypeManifest {
340
- type: string;
341
- label: string;
342
- category: "text" | "choice" | "scale" | "date" | "media" | "advanced";
343
- /** `json: false` types must convert, so drafts and server replay never lose an answer. */
344
- value: {
345
- json: true;
346
- } | {
347
- json: false;
348
- serialize: (value: unknown) => Json;
349
- deserialize: (json: Json) => unknown;
350
- };
351
- operators: Operator[];
352
- a11y: {
353
- semantics: "control" | "group";
354
- /** The HTML `autocomplete` token, such as `"email"` or `"tel"`. */
355
- autocomplete?: string;
356
- /** The HTML `inputmode` hint for on-screen keyboards, such as `"email"` or `"tel"`. */
357
- inputMode?: "text" | "email" | "tel" | "numeric" | "decimal" | "url" | "search";
358
- };
359
- isEmpty?: (value: Json | undefined) => boolean;
360
- /** Return an error message, or null when the (non-empty) value is valid. */
361
- validate?: (value: Json, field: FieldBlock) => string | null;
362
- /** Plain text for recall, exports and emails. */
363
- summarize?: (value: Json, field: FieldBlock) => string;
423
+
424
+ interface FormProgress {
364
425
  /**
365
- * The form a (valid, non-empty) value is stored in, such as an email
366
- * without surrounding spaces. Applied by `engine.answers()` and the server.
426
+ * The furthest block reached, 1-based, on the path as it is now (logic can
427
+ * lengthen or shorten the path, so store `fieldId`, not this). 0 before any.
367
428
  */
368
- normalize?: (value: Json, field: FieldBlock) => Json;
369
- /** Valid example answers, used by `coverage()` and other test tools to explore a path. */
370
- samples?: (field: FieldBlock) => Json[];
371
- }
372
- declare function registerFieldType(manifest: FieldTypeManifest): void;
373
- declare function getFieldType(type: string): FieldTypeManifest | undefined;
374
- declare function listFieldTypes(): FieldTypeManifest[];
375
- declare function isEmptyValue(value: Json | undefined, field?: FieldBlock): boolean;
376
- /** A value in its stored form (see `FieldTypeManifest.normalize`). */
377
- declare function normalizeValue(value: Json, field: FieldBlock): Json;
378
- /** Whether a block holds personal or secret data: `sensitive: true`, or the `props.sensitive: true` alias. */
379
- declare function isSensitive(block: Block): boolean;
380
- declare function summarizeValue(value: Json | undefined, field: FieldBlock): string;
381
- interface ChoiceOption {
382
- value: string;
383
- label?: string;
429
+ step: number;
430
+ /** How many blocks the path has now. */
431
+ total: number;
432
+ /**
433
+ * The id of the furthest question (field) reached: shown in focus layout, or
434
+ * answered. Null before any. A content block on the way is not a question,
435
+ * so the question before it counts.
436
+ */
437
+ fieldId: string | null;
384
438
  }
385
- declare function choiceOptions(field: FieldBlock): ChoiceOption[];
386
- /** The optional words under the ends of a rating or NPS scale ("Poor", "Great"). */
387
- declare function scaleLabels(field: FieldBlock): {
388
- low?: string;
389
- high?: string;
390
- };
391
- /** How a rating is drawn: `"numbers"` (default) or `"stars"`. */
392
- declare function ratingDisplay(field: FieldBlock): "numbers" | "stars";
393
- /** A `number` field's limits: `min`, `max` and `step` (each optional; `step` is above 0). */
394
- declare function numberLimits(field: FieldBlock): {
395
- min?: number;
396
- max?: number;
397
- step?: number;
398
- };
399
- /** `YYYY-MM-DD`, a real calendar day (no 2026-02-30), years 0001 to 9999. */
400
- declare function isIsoDate(value: string): boolean;
401
- /** A `date` field's earliest and latest days (`YYYY-MM-DD`), when set. */
402
- declare function dateLimits(field: FieldBlock): {
403
- min?: string;
404
- max?: string;
405
- };
439
+ /**
440
+ * The furthest block on the path the respondent has reached: the one on screen,
441
+ * one they went back from, or (in classic layout, where every question shows at
442
+ * once) the last one they answered. Null when the path is empty.
443
+ */
444
+ declare function furthestReached(state: FormState, definition: FormDefinition): string | null;
445
+ /**
446
+ * Where the respondent is: the furthest step and the furthest question reached.
447
+ * What genesis-forms-web's `onProgress` passes; `engine.answers()` has the
448
+ * answers so far.
449
+ */
450
+ declare function formProgress(engine: Pick<FormEngine, "definition" | "getState">): FormProgress;
406
451
 
407
452
  declare function listLimits(field: FieldBlock): {
408
453
  min: number;
@@ -676,4 +721,4 @@ declare function explain(source: FormEngine | ExplainInput, blockId: string, opt
676
721
  /** A condition as a plain-English clause: `contact_me is not Yes`. */
677
722
  declare function describeCondition(condition: Condition, def: FormDefinition, fields?: Map<string, FieldBlock>): string;
678
723
 
679
- export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type ChoiceOption, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldTypeManifest, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, FormSettings, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, Operator, PageBreakBlock, type ParseResult, type PathResult, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isIsoDate, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, when };
724
+ export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, DEFAULT_PHOTO_FILES, DEFAULT_PHOTO_SIZE_MB, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, type FormProgress, FormSettings, FormState, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_PHOTO_FILES, MAX_PHOTO_ID_LENGTH, MAX_PHOTO_NAME_LENGTH, MAX_PHOTO_SIZE_MB, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, OptionsSource, PHOTO_FORMATS, PHOTO_TYPE, PageBreakBlock, type ParseResult, type PathResult, type PhotoFormat, type PhotoLimits, type PhotoRef, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, UPLOAD_FIELD_TYPES, calculatedProps, calculatedValue, canonicalJson, coerceHidden, coerceHiddenValue, computePath, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formProgress, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, furthestReached, isPhotoFormat, isPhotoRef, listLimits, parseForm, parseFormula, parseTimestamp, photoCountMessage, photoFormatList, photoLimits, photoRefs, photoSizeMessage, photoTypeMessage, recall, renameFormulaKey, ruleVisible, steps, tableColumns, tableLimits, when };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,68 @@
1
- import { a as FieldBlock, C as ContentBlock, P as PageBreakBlock, F as FormDefinition, H as HiddenInput, b as FormLayout, L as LogicRule, E as Ending, c as FormSettings, J as Json, d as Condition, B as Block, O as Operator } from './types-BBKBQ8sp.js';
2
- export { e as FormEvent, f as Operand } from './types-BBKBQ8sp.js';
3
- import { F as FormEngine } from './engine-Dz9J5ej2.js';
4
- export { C as CreateFormOptions, a as FormState, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-Dz9J5ej2.js';
5
- import { F as FieldValidator } from './validate-BjNwF1UE.js';
6
- export { R as REQUIRED_MESSAGE, S as StandardSchemaV1, v as validateField } from './validate-BjNwF1UE.js';
1
+ import { a as FieldBlock, J as Json, C as ContentBlock, P as PageBreakBlock, O as OptionsSource, F as FormDefinition, H as HiddenInput, b as FormLayout, L as LogicRule, E as Ending, c as FormSettings, d as Condition, B as Block } from './types-BhYUbLJ-.js';
2
+ export { e as FormEvent, f as Operand, g as Operator } from './types-BhYUbLJ-.js';
3
+ import { F as FormEngine, a as FormState } from './engine-Qzp6bX6O.js';
4
+ export { C as CreateFormOptions, c as createForm, i as isFormEvent, b as isJson, r as replay } from './engine-Qzp6bX6O.js';
5
+ import { F as FieldValidator } from './validate-Bs1lyoOl.js';
6
+ export { C as ChoiceOption, a as FieldTypeManifest, H as HostOptions, O as OTHER_MAX_LENGTH, R as REQUIRED_MESSAGE, S as StandardSchemaV1, c as choiceOptions, d as dateLimits, g as getFieldType, i as isEmptyValue, b as isIsoDate, e as isOtherValue, f as isSensitive, l as listFieldTypes, n as normalizeValue, h as numberLimits, o as optionsSource, j as otherOption, r as ratingDisplay, k as registerFieldType, s as scaleLabels, m as summarizeValue, v as validateField, w as withHostOptions } from './validate-Bs1lyoOl.js';
7
+
8
+ /**
9
+ * The `photo` field type: a respondent attaches up to a few photos, such as a
10
+ * picture of broken equipment. The answer holds references the host's storage
11
+ * handed back (`PhotoRef`), never the bytes: the renderer uploads through the
12
+ * host (`onUpload` on genesis-forms-web's `Form`), so the engine and the
13
+ * server stay storage-agnostic. A host's server should check that each
14
+ * reference is a file it stored for this respondent before saving.
15
+ *
16
+ * Opt-in in a builder palette (`UPLOAD_FIELD_TYPES`), since a host must be
17
+ * able to store the files.
18
+ */
19
+ declare const PHOTO_TYPE = "photo";
20
+ /** Field types that need the host to store files; a builder offers them only when the host names them. */
21
+ declare const UPLOAD_FIELD_TYPES: readonly ["photo"];
22
+ /** One stored photo, as the host's upload returned it. */
23
+ type PhotoRef = {
24
+ /** The host's id for the stored file. */
25
+ id: string;
26
+ /** The file's name on the respondent's device. */
27
+ name: string;
28
+ /** Its MIME type, such as `image/jpeg`. */
29
+ type: string;
30
+ /** Bytes. */
31
+ size: number;
32
+ };
33
+ type PhotoFormat = "jpeg" | "png" | "heic" | "webp";
34
+ /** The MIME types each format covers. */
35
+ declare const PHOTO_FORMATS: Record<PhotoFormat, readonly string[]>;
36
+ declare const DEFAULT_PHOTO_FILES = 3;
37
+ declare const MAX_PHOTO_FILES = 5;
38
+ declare const DEFAULT_PHOTO_SIZE_MB = 10;
39
+ declare const MAX_PHOTO_SIZE_MB = 25;
40
+ /** The longest reference id and file name a photo answer may carry. */
41
+ declare const MAX_PHOTO_ID_LENGTH = 200;
42
+ declare const MAX_PHOTO_NAME_LENGTH = 255;
43
+ declare const isPhotoFormat: (v: unknown) => v is PhotoFormat;
44
+ interface PhotoLimits {
45
+ maxFiles: number;
46
+ maxSizeMb: number;
47
+ maxBytes: number;
48
+ formats: PhotoFormat[];
49
+ /** Every MIME type the formats cover. */
50
+ mimeTypes: string[];
51
+ }
52
+ /** A photo field's limits; a prop that is missing or out of range uses its default. */
53
+ declare function photoLimits(field: FieldBlock): PhotoLimits;
54
+ /** Whether `v` has a photo reference's shape (any type and size). */
55
+ declare function isPhotoRef(v: unknown): v is PhotoRef;
56
+ /** The well-formed references in a photo answer, as `{ id, name, type, size }` only. */
57
+ declare function photoRefs(value: Json | undefined): PhotoRef[];
58
+ /** "JPEG, PNG or WebP". */
59
+ declare function photoFormatList(formats: readonly PhotoFormat[]): string;
60
+ /** The message for a file of a type the field doesn't take. */
61
+ declare const photoTypeMessage: (field: FieldBlock) => string;
62
+ /** The message for a file over the field's size limit. */
63
+ declare const photoSizeMessage: (field: FieldBlock) => string;
64
+ /** The message for more photos than the field takes. */
65
+ declare const photoCountMessage: (field: FieldBlock) => string;
7
66
 
8
67
  /** A field block that remembers its key and value type, for answer inference. */
9
68
  type FieldSpec<K extends string = string, V = unknown> = FieldBlock & {
@@ -24,10 +83,24 @@ interface FieldOptions {
24
83
  id?: string | undefined;
25
84
  }
26
85
  interface ChoiceFieldOptions<O extends string> extends FieldOptions {
27
- options: readonly {
86
+ /** The options. Optional only with `optionsFrom`, whose options follow these. */
87
+ options?: readonly {
28
88
  value: O;
29
89
  label?: string;
30
- }[];
90
+ }[] | undefined;
91
+ /**
92
+ * Options the app showing the form supplies, by key (`OptionsSource`), such
93
+ * as `{ source: "host", key: "coaches" }`; pass the lists as `hostOptions`
94
+ * to `createForm` and `validateSubmission`.
95
+ */
96
+ optionsFrom?: OptionsSource | undefined;
97
+ /**
98
+ * An "Other" option the respondent fills in (`true`, or `{ label }` to name it): their text is
99
+ * the answer (one more item on a multiple choice). See `otherOption`.
100
+ */
101
+ other?: boolean | {
102
+ label?: string;
103
+ } | undefined;
31
104
  }
32
105
  interface HiddenOptions {
33
106
  source?: HiddenInput["source"] | undefined;
@@ -137,6 +210,16 @@ declare const f: {
137
210
  quote: string;
138
211
  note: string;
139
212
  }[]>;
213
+ /**
214
+ * Up to a few photos (a picture of broken equipment). The answer holds the
215
+ * references the host's upload returned, never the bytes; `formats` from
216
+ * jpeg, png, heic and webp (default all four).
217
+ */
218
+ photo: <K extends string>(key: K, o: FieldOptions & {
219
+ maxFiles?: number | undefined;
220
+ maxSizeMb?: number | undefined;
221
+ formats?: readonly PhotoFormat[] | undefined;
222
+ }) => FieldSpec<K, PhotoRef[]>;
140
223
  /** Any registered field type, including plugins. */
141
224
  custom: <K extends string, V = Json>(type: string, key: K, o: FieldOptions & {
142
225
  props?: Record<string, Json>;
@@ -333,76 +416,38 @@ declare function computePath(def: FormDefinition, scope: EvalScope): PathResult;
333
416
  declare function ruleVisible(def: FormDefinition, blockId: string, scope: EvalScope): boolean;
334
417
 
335
418
  /**
336
- * A field type, without any UI. Renderers are registered separately per
337
- * platform (`genesis-forms-react`), so a manifest runs anywhere.
419
+ * How far a respondent has got, for saving answers as they go
420
+ * (`settings.saveProgress`) and for drop-off by question. Pure: reads a form's
421
+ * state, never changes it.
338
422
  */
339
- interface FieldTypeManifest {
340
- type: string;
341
- label: string;
342
- category: "text" | "choice" | "scale" | "date" | "media" | "advanced";
343
- /** `json: false` types must convert, so drafts and server replay never lose an answer. */
344
- value: {
345
- json: true;
346
- } | {
347
- json: false;
348
- serialize: (value: unknown) => Json;
349
- deserialize: (json: Json) => unknown;
350
- };
351
- operators: Operator[];
352
- a11y: {
353
- semantics: "control" | "group";
354
- /** The HTML `autocomplete` token, such as `"email"` or `"tel"`. */
355
- autocomplete?: string;
356
- /** The HTML `inputmode` hint for on-screen keyboards, such as `"email"` or `"tel"`. */
357
- inputMode?: "text" | "email" | "tel" | "numeric" | "decimal" | "url" | "search";
358
- };
359
- isEmpty?: (value: Json | undefined) => boolean;
360
- /** Return an error message, or null when the (non-empty) value is valid. */
361
- validate?: (value: Json, field: FieldBlock) => string | null;
362
- /** Plain text for recall, exports and emails. */
363
- summarize?: (value: Json, field: FieldBlock) => string;
423
+
424
+ interface FormProgress {
364
425
  /**
365
- * The form a (valid, non-empty) value is stored in, such as an email
366
- * without surrounding spaces. Applied by `engine.answers()` and the server.
426
+ * The furthest block reached, 1-based, on the path as it is now (logic can
427
+ * lengthen or shorten the path, so store `fieldId`, not this). 0 before any.
367
428
  */
368
- normalize?: (value: Json, field: FieldBlock) => Json;
369
- /** Valid example answers, used by `coverage()` and other test tools to explore a path. */
370
- samples?: (field: FieldBlock) => Json[];
371
- }
372
- declare function registerFieldType(manifest: FieldTypeManifest): void;
373
- declare function getFieldType(type: string): FieldTypeManifest | undefined;
374
- declare function listFieldTypes(): FieldTypeManifest[];
375
- declare function isEmptyValue(value: Json | undefined, field?: FieldBlock): boolean;
376
- /** A value in its stored form (see `FieldTypeManifest.normalize`). */
377
- declare function normalizeValue(value: Json, field: FieldBlock): Json;
378
- /** Whether a block holds personal or secret data: `sensitive: true`, or the `props.sensitive: true` alias. */
379
- declare function isSensitive(block: Block): boolean;
380
- declare function summarizeValue(value: Json | undefined, field: FieldBlock): string;
381
- interface ChoiceOption {
382
- value: string;
383
- label?: string;
429
+ step: number;
430
+ /** How many blocks the path has now. */
431
+ total: number;
432
+ /**
433
+ * The id of the furthest question (field) reached: shown in focus layout, or
434
+ * answered. Null before any. A content block on the way is not a question,
435
+ * so the question before it counts.
436
+ */
437
+ fieldId: string | null;
384
438
  }
385
- declare function choiceOptions(field: FieldBlock): ChoiceOption[];
386
- /** The optional words under the ends of a rating or NPS scale ("Poor", "Great"). */
387
- declare function scaleLabels(field: FieldBlock): {
388
- low?: string;
389
- high?: string;
390
- };
391
- /** How a rating is drawn: `"numbers"` (default) or `"stars"`. */
392
- declare function ratingDisplay(field: FieldBlock): "numbers" | "stars";
393
- /** A `number` field's limits: `min`, `max` and `step` (each optional; `step` is above 0). */
394
- declare function numberLimits(field: FieldBlock): {
395
- min?: number;
396
- max?: number;
397
- step?: number;
398
- };
399
- /** `YYYY-MM-DD`, a real calendar day (no 2026-02-30), years 0001 to 9999. */
400
- declare function isIsoDate(value: string): boolean;
401
- /** A `date` field's earliest and latest days (`YYYY-MM-DD`), when set. */
402
- declare function dateLimits(field: FieldBlock): {
403
- min?: string;
404
- max?: string;
405
- };
439
+ /**
440
+ * The furthest block on the path the respondent has reached: the one on screen,
441
+ * one they went back from, or (in classic layout, where every question shows at
442
+ * once) the last one they answered. Null when the path is empty.
443
+ */
444
+ declare function furthestReached(state: FormState, definition: FormDefinition): string | null;
445
+ /**
446
+ * Where the respondent is: the furthest step and the furthest question reached.
447
+ * What genesis-forms-web's `onProgress` passes; `engine.answers()` has the
448
+ * answers so far.
449
+ */
450
+ declare function formProgress(engine: Pick<FormEngine, "definition" | "getState">): FormProgress;
406
451
 
407
452
  declare function listLimits(field: FieldBlock): {
408
453
  min: number;
@@ -676,4 +721,4 @@ declare function explain(source: FormEngine | ExplainInput, blockId: string, opt
676
721
  /** A condition as a plain-English clause: `contact_me is not Yes`. */
677
722
  declare function describeCondition(condition: Condition, def: FormDefinition, fields?: Map<string, FieldBlock>): string;
678
723
 
679
- export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type ChoiceOption, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldTypeManifest, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, FormSettings, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, Operator, PageBreakBlock, type ParseResult, type PathResult, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, calculatedProps, calculatedValue, canonicalJson, choiceOptions, coerceHidden, coerceHiddenValue, computePath, dateLimits, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, getFieldType, isEmptyValue, isIsoDate, isSensitive, listFieldTypes, listLimits, normalizeValue, numberLimits, parseForm, parseFormula, parseTimestamp, ratingDisplay, recall, registerFieldType, renameFormulaKey, ruleVisible, scaleLabels, steps, summarizeValue, tableColumns, tableLimits, when };
724
+ export { type AnswersOf, Block, CALCULATED_TYPE, COMPUTED_FIELD_TYPES, type CalculatedFormat, type CalculatedProps, type ChoiceFieldOptions, type CoerceHiddenOptions, type CoercedHidden, Condition, ContentBlock, DEFAULT_PHOTO_FILES, DEFAULT_PHOTO_SIZE_MB, Ending, type ExplainInput, type ExplainOptions, type Explanation, FORMULA_FUNCTIONS, FieldBlock, type FieldOptions, type FieldSpec, type FieldValidation, FieldValidator, type Form, FormDefinition, FormEngine, type FormInput, type FormIssue, FormLayout, type FormProgress, FormSettings, FormState, type FormulaFunction, type FormulaNode, type FormulaParseResult, type FormulaReader, type FormulaRef, type FormulaValue, HIGHLIGHT_NOTE_MESSAGE, HiddenInput, type Highlight, Json, type JumpTrace, type KeyResolver, LogicRule, MAX_CALCULATED_DECIMALS, MAX_FORMULA_ITEMS, MAX_FORMULA_LENGTH, MAX_FORM_FORMULA_LENGTH, MAX_HIDDEN_STRING_LENGTH, MAX_HIGHLIGHT_NOTE_LENGTH, MAX_HIGHLIGHT_QUOTE_LENGTH, MAX_PHOTO_FILES, MAX_PHOTO_ID_LENGTH, MAX_PHOTO_NAME_LENGTH, MAX_PHOTO_SIZE_MB, MAX_SETTLE_ITEMS, MAX_TABLE_CELL_LENGTH, MAX_TABLE_COLUMNS, MAX_TIMESTAMP_SECONDS, type MediaNote, OptionsSource, PHOTO_FORMATS, PHOTO_TYPE, PageBreakBlock, type ParseResult, type PathResult, type PhotoFormat, type PhotoLimits, type PhotoRef, type RuleSpec, type RuleTrace, STRUCTURED_FIELD_TYPES, type TableColumn, UPLOAD_FIELD_TYPES, calculatedProps, calculatedValue, canonicalJson, coerceHidden, coerceHiddenValue, computePath, defineForm, describeCondition, evaluate, evaluateFormula, explain, f, fingerprint, formProgress, formatCalculated, formatTimestamp, formulaInput, formulaNumber, formulaRefs, furthestReached, isPhotoFormat, isPhotoRef, listLimits, parseForm, parseFormula, parseTimestamp, photoCountMessage, photoFormatList, photoLimits, photoRefs, photoSizeMessage, photoTypeMessage, recall, renameFormulaKey, ruleVisible, steps, tableColumns, tableLimits, when };