@marktiderman/genesis-forms-web 0.3.0 → 0.4.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/README.md +34 -0
- package/dist/index.d.ts +69 -2
- package/dist/index.js +487 -54
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -50,6 +50,19 @@ import { Form } from "@marktiderman/genesis-forms-web";
|
|
|
50
50
|
<Form form={eventFeedback} presentation="page" brand={{ name: "Acme" }} onSubmit={send} />
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
- **Save answers as they go:** with the definition's `settings.saveProgress` on, `onProgress(engine,
|
|
54
|
+
{ step, total, fieldId })` is called a moment after each step forward (`PROGRESS_DELAY_MS`; in
|
|
55
|
+
classic layout, `CLASSIC_PROGRESS_DELAY_MS` after the answers change), with `fieldId` the
|
|
56
|
+
furthest question reached (`formProgress` from genesis-forms) and `engine.answers()` the answers
|
|
57
|
+
so far. It never holds up Next: a rejection is ignored (the next call carries every answer
|
|
58
|
+
again), a pending call goes at once on `pagehide`, and none is made once the form is submitted.
|
|
59
|
+
Under the first question a note says "Your answers save as you go." (`progressNotice`; `null`
|
|
60
|
+
hides it). Without `onProgress`, or with the setting off, nothing is saved and nothing is said.
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<Form form={feedback} presentation="page" onProgress={(engine, p) => saveDraft(engine.answers(), p.fieldId)} onSubmit={send} />
|
|
64
|
+
```
|
|
65
|
+
|
|
53
66
|
- **Accessible by default:** group questions are a `fieldset` with a `legend`, and rating, NPS and
|
|
54
67
|
yes/no are real radio groups (arrows select). Every control carries `aria-invalid`,
|
|
55
68
|
errors are linked with `aria-describedby`, and the NPS end labels are read with 0 and 10. In
|
|
@@ -108,6 +121,27 @@ choice, yes/no, and the structured types:
|
|
|
108
121
|
|
|
109
122
|
`useMediaTime()` and `useSourceText()` read them in a custom renderer.
|
|
110
123
|
|
|
124
|
+
**`photo`** (a picture of broken equipment) has "Choose a photo" and, on a phone or tablet, "Take a
|
|
125
|
+
photo" (the camera), checks each file's type, size and the count before uploading, shows a thumbnail
|
|
126
|
+
(or the file's name when the browser can't draw it, such as HEIC) and removes one. The file goes to
|
|
127
|
+
the host, which stores it and resolves to its reference; the answer keeps only that:
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<Form
|
|
131
|
+
form={feedback}
|
|
132
|
+
onUpload={async (file, field) => {
|
|
133
|
+
const { ref } = await uploadSomewhere(file, field.id); // your storage
|
|
134
|
+
return ref; // { id, name, type, size }
|
|
135
|
+
}}
|
|
136
|
+
onSubmit={send}
|
|
137
|
+
/>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A rejected upload shows under the field. While a photo is uploading the form neither moves on nor
|
|
141
|
+
sends (it says "Wait for the photo to finish uploading."). Without `onUpload`, or an
|
|
142
|
+
`UploadContext` around the form, a photo field says photos can't be added here. `photoFileProblem`,
|
|
143
|
+
`photoFileType` and `photoAccept` are the renderer's own checks, for a custom one.
|
|
144
|
+
|
|
111
145
|
A **`calculated`** field shows the value the engine worked out from other answers, read-only, in an
|
|
112
146
|
`<output>` named by the question that announces changes politely. It follows the field's `format`
|
|
113
147
|
and `decimals`, and shows a dash until there is a value.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ComponentType } from 'react';
|
|
3
|
-
import { FormDefinition, FormEngine, CreateFormOptions, FormState } from '@marktiderman/genesis-forms';
|
|
3
|
+
import { FormDefinition, FormEngine, CreateFormOptions, HostOptions, FormState, FormProgress, FieldBlock, PhotoRef } from '@marktiderman/genesis-forms';
|
|
4
4
|
import * as _marktiderman_genesis_forms_react from '@marktiderman/genesis-forms-react';
|
|
5
5
|
import { RendererRegistry, FieldRendererProps, RendererEntry } from '@marktiderman/genesis-forms-react';
|
|
6
6
|
|
|
@@ -146,6 +146,16 @@ interface FormProps {
|
|
|
146
146
|
/** Bring your own engine (from `useForm`) to read state outside the form. `form` and `options` are then unused. */
|
|
147
147
|
engine?: FormEngine | undefined;
|
|
148
148
|
options?: CreateFormOptions | undefined;
|
|
149
|
+
/**
|
|
150
|
+
* Option lists this app supplies, by key, for choice questions whose
|
|
151
|
+
* options come from the app (`props.optionsFrom: { source: "host", key }`),
|
|
152
|
+
* such as `{ coaches: [{ value: "8c1…", label: "Ada Lovelace" }] }`. Shown
|
|
153
|
+
* after the question's own options. Like `options`, read when the response
|
|
154
|
+
* starts; unused with `engine` (pass `hostOptions` to `useForm` then). Check
|
|
155
|
+
* answers on the server with the same lists (`validateSubmission`'s
|
|
156
|
+
* `hostOptions`).
|
|
157
|
+
*/
|
|
158
|
+
hostOptions?: HostOptions | undefined;
|
|
149
159
|
/** Overrides the definition's layout. */
|
|
150
160
|
layout?: "focus" | "classic" | undefined;
|
|
151
161
|
/**
|
|
@@ -177,6 +187,22 @@ interface FormProps {
|
|
|
177
187
|
* submit attempt (debounce it to save drafts).
|
|
178
188
|
*/
|
|
179
189
|
onChange?: ((state: FormState, engine: FormEngine) => void) | undefined;
|
|
190
|
+
/**
|
|
191
|
+
* Save answers as they go: with the definition's `settings.saveProgress` on,
|
|
192
|
+
* called a moment after each step forward (in classic layout, a moment after
|
|
193
|
+
* the answers change) with how far the respondent got (`formProgress`); read
|
|
194
|
+
* the answers so far with `engine.answers()`. It never holds them up: it runs
|
|
195
|
+
* after the step, and a rejection is ignored (the next call carries every
|
|
196
|
+
* answer again). A pending call goes at once when the page is hidden, and none
|
|
197
|
+
* is made once the form is submitted.
|
|
198
|
+
*/
|
|
199
|
+
onProgress?: ((engine: FormEngine, progress: FormProgress) => void | Promise<void>) | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* The note under the first question while answers save as they go (only with
|
|
202
|
+
* `onProgress` and `settings.saveProgress`). `null` hides it. Default
|
|
203
|
+
* "Your answers save as you go."
|
|
204
|
+
*/
|
|
205
|
+
progressNotice?: string | null | undefined;
|
|
180
206
|
/**
|
|
181
207
|
* In focus layout, move on after a single pick (rating, yes/no, one choice).
|
|
182
208
|
* Arrow keys only move through the options; a click, tap or Space picks.
|
|
@@ -194,6 +220,14 @@ interface FormProps {
|
|
|
194
220
|
requiredLabel?: string | null | undefined;
|
|
195
221
|
/** The submit button's label after `onSubmit` failed. Default "Try again". */
|
|
196
222
|
retryLabel?: string | undefined;
|
|
223
|
+
/**
|
|
224
|
+
* Stores a file a respondent adds to a `photo` field and resolves to its
|
|
225
|
+
* reference (`{ id, name, type, size }`), which is all the answer keeps; a
|
|
226
|
+
* rejection shows under the field. While any upload is in flight the form
|
|
227
|
+
* neither moves on nor sends. Without it (and without an `UploadContext`
|
|
228
|
+
* above the form), photo fields say photos can't be added.
|
|
229
|
+
*/
|
|
230
|
+
onUpload?: ((file: File, field: FieldBlock) => Promise<PhotoRef>) | undefined;
|
|
197
231
|
slots?: {
|
|
198
232
|
[K in keyof FormSlotMap]?: SlotOverride<FormSlotMap[K]>;
|
|
199
233
|
} | undefined;
|
|
@@ -203,6 +237,10 @@ interface FormProps {
|
|
|
203
237
|
className?: string | undefined;
|
|
204
238
|
testID?: string | undefined;
|
|
205
239
|
}
|
|
240
|
+
/** How long after a step forward `onProgress` waits, so quick steps save once. */
|
|
241
|
+
declare const PROGRESS_DELAY_MS = 400;
|
|
242
|
+
/** In classic layout, how long after the last answer changed. */
|
|
243
|
+
declare const CLASSIC_PROGRESS_DELAY_MS = 1500;
|
|
206
244
|
declare function Form(props: FormProps): react.JSX.Element;
|
|
207
245
|
|
|
208
246
|
interface FieldChromeProps {
|
|
@@ -233,6 +271,35 @@ declare const builtInRenderers: RendererEntry[];
|
|
|
233
271
|
*/
|
|
234
272
|
declare const defaultRegistry: _marktiderman_genesis_forms_react.RendererRegistry;
|
|
235
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Where a form's files go. The host stores each file (its own bucket, its own
|
|
276
|
+
* rules) and resolves to a reference, which is all the answer keeps; a
|
|
277
|
+
* rejection is shown under the field. `photo` fields read it.
|
|
278
|
+
*/
|
|
279
|
+
interface UploadHost {
|
|
280
|
+
upload(file: File, field: FieldBlock): Promise<PhotoRef>;
|
|
281
|
+
}
|
|
282
|
+
/** Provide it around `<Form>` (or pass `onUpload`) so `photo` fields can store files. */
|
|
283
|
+
declare const UploadContext: react.Context<UploadHost | null>;
|
|
284
|
+
/** The enclosing `UploadContext` value, or null when no host stores files. */
|
|
285
|
+
declare function useUpload(): UploadHost | null;
|
|
286
|
+
/** Said when the respondent tries to move on or send while a photo is still uploading. */
|
|
287
|
+
declare const UPLOAD_WAIT_MESSAGE = "Wait for the photo to finish uploading.";
|
|
288
|
+
|
|
289
|
+
/** The file's type as the field judges it: some browsers give a HEIC photo no type, so its name decides. */
|
|
290
|
+
declare function photoFileType(file: {
|
|
291
|
+
name: string;
|
|
292
|
+
type: string;
|
|
293
|
+
}): string;
|
|
294
|
+
/** Why a picked file can't be added, or null when it can. */
|
|
295
|
+
declare function photoFileProblem(field: FieldBlock, file: {
|
|
296
|
+
name: string;
|
|
297
|
+
type: string;
|
|
298
|
+
size: number;
|
|
299
|
+
}): string | null;
|
|
300
|
+
/** The `accept` attribute for a field: its MIME types, plus HEIC's file names for browsers that give it no type. */
|
|
301
|
+
declare function photoAccept(field: FieldBlock): string;
|
|
302
|
+
|
|
236
303
|
/**
|
|
237
304
|
* The video or audio a form is about, as the host app plays it. `media-notes`
|
|
238
305
|
* fields read it: with `currentTime` the add row starts at the current moment
|
|
@@ -258,4 +325,4 @@ declare const SourceTextContext: react.Context<string | null>;
|
|
|
258
325
|
/** The enclosing `SourceTextContext` value, or null. */
|
|
259
326
|
declare function useSourceText(): string | null;
|
|
260
327
|
|
|
261
|
-
export { type BrandSlotProps, type EndingSlotProps, type ErrorSummaryItem, type ErrorSummarySlotProps, FieldChrome, type FieldChromeProps, Form, type FormBrand, type FormPresentation, type FormPresentationValue, type FormProps, type FormSlotMap, type FormTheme, type HeaderSlotProps, type MediaTime, MediaTimeContext, type NavigationSlotProps, type ProgressSlotProps, SourceTextContext, type ThemeColors, builtInRenderers, defaultRegistry, describedBy, themeColors, themeCss, useFormPresentation, useMediaTime, useSourceText };
|
|
328
|
+
export { type BrandSlotProps, CLASSIC_PROGRESS_DELAY_MS, type EndingSlotProps, type ErrorSummaryItem, type ErrorSummarySlotProps, FieldChrome, type FieldChromeProps, Form, type FormBrand, type FormPresentation, type FormPresentationValue, type FormProps, type FormSlotMap, type FormTheme, type HeaderSlotProps, type MediaTime, MediaTimeContext, type NavigationSlotProps, PROGRESS_DELAY_MS, type ProgressSlotProps, SourceTextContext, type ThemeColors, UPLOAD_WAIT_MESSAGE, UploadContext, type UploadHost, builtInRenderers, defaultRegistry, describedBy, photoAccept, photoFileProblem, photoFileType, themeColors, themeCss, useFormPresentation, useMediaTime, useSourceText, useUpload };
|