@marktiderman/genesis-forms-web 0.1.0 → 0.2.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 CHANGED
@@ -6,9 +6,9 @@ The web renderer for [Genesis Forms](../forms/README.md), built on Genesis UI co
6
6
  import { Form } from "@marktiderman/genesis-forms-web";
7
7
 
8
8
  <Form
9
- form={afterClass} // a defineForm result or a canonical definition
9
+ form={eventFeedback} // a defineForm result or a canonical definition
10
10
  layout="focus" // one question at a time; or "classic"
11
- options={{ hidden: { coach: "Sam" } }}
11
+ options={{ hidden: { owner: "Sam" } }}
12
12
  onChange={(state) => saveDraft(state)}
13
13
  onSubmit={(engine) => submit(engine.getState().events)}
14
14
  />
@@ -41,13 +41,13 @@ import { Form } from "@marktiderman/genesis-forms-web";
41
41
  `prefers-reduced-motion`). The ending is centred and large. In classic layout, page mode is one
42
42
  scrolling page, Google Forms style: a header card with the title and description, each question
43
43
  in its own card (red-edged while invalid), Submit at the end.
44
- `brand={{ name: "CrossFit Fairfield", logo }}` shows a practice's logo (an image URL or an
44
+ `brand={{ name: "Acme", logo }}` shows an organisation's or business's logo (an image URL or an
45
45
  element; its initials without one) and name small at the top of every step and on the ending.
46
46
  Custom slots and renderers can match with `useFormPresentation()`, which returns
47
47
  `{ presentation, layout }`.
48
48
 
49
49
  ```tsx
50
- <Form form={afterClass} presentation="page" brand={{ name: "CrossFit Fairfield" }} onSubmit={send} />
50
+ <Form form={eventFeedback} presentation="page" brand={{ name: "Acme" }} onSubmit={send} />
51
51
  ```
52
52
 
53
53
  - **Accessible by default:** group questions are a `fieldset` with a `legend`, and rating, NPS and
@@ -74,7 +74,27 @@ import { Form } from "@marktiderman/genesis-forms-web";
74
74
  `defaultRegistry.register(entry)` mutates the shared registry in place, for every form in the app.
75
75
 
76
76
  Renderers: short and long text, email, phone (`type="tel"`), rating, NPS, single choice, multiple
77
- choice, yes/no.
77
+ choice, yes/no, and the structured types:
78
+
79
+ - **`list`** (goals, action items, three wins) and **`table`** (an action plan with What / Who /
80
+ By when, pros and cons) add, remove and reorder entries (SortableList: drag, keyboard and a Move
81
+ menu).
82
+ - **`media-notes`** (lecture notes, reviewing a recorded call) takes a typed time (`m:ss`,
83
+ `h:mm:ss` or seconds). Wrap the form in `MediaTimeContext` to link it to your video or audio
84
+ player: `currentTime` prefills "Add note at 1:23" and `seek` plays from a note.
85
+ - **`text-highlights`** (annotating an article, a policy or a contract) takes highlights by
86
+ selecting words in the passage or typing them. The passage is the field's `text` prop, or the
87
+ value of `SourceTextContext` around the form.
88
+
89
+ ```tsx
90
+ <MediaTimeContext.Provider value={{ currentTime: () => player.currentTime, seek: (s) => player.seek(s) }}>
91
+ <SourceTextContext.Provider value={article.body}>
92
+ <Form form={videoNotes} onSubmit={send} />
93
+ </SourceTextContext.Provider>
94
+ </MediaTimeContext.Provider>
95
+ ```
96
+
97
+ `useMediaTime()` and `useSourceText()` read them in a custom renderer.
78
98
 
79
99
  ## License
80
100
 
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ interface HeaderSlotProps {
28
28
  className?: string;
29
29
  }
30
30
  interface BrandSlotProps {
31
- /** The practice or business name, shown beside the logo. */
31
+ /** The organisation or business name, shown beside the logo. */
32
32
  name: string;
33
33
  /** An image URL or your own element. Without one, the name's initials are shown. */
34
34
  logo?: ReactNode;
@@ -90,7 +90,7 @@ interface FormSlotMap {
90
90
  interface FormBrand {
91
91
  name: string;
92
92
  /** An image URL or your own element (decorative: the name is the text). Without one, the name's initials are shown. */
93
- logo?: ReactNode;
93
+ logo?: ReactNode | undefined;
94
94
  }
95
95
  interface FormProps {
96
96
  /** A `defineForm` result or a canonical definition. */
@@ -98,10 +98,10 @@ interface FormProps {
98
98
  definition: FormDefinition;
99
99
  };
100
100
  /** Bring your own engine (from `useForm`) to read state outside the form. `form` and `options` are then unused. */
101
- engine?: FormEngine;
102
- options?: CreateFormOptions;
101
+ engine?: FormEngine | undefined;
102
+ options?: CreateFormOptions | undefined;
103
103
  /** Overrides the definition's layout. */
104
- layout?: "focus" | "classic";
104
+ layout?: "focus" | "classic" | undefined;
105
105
  /**
106
106
  * `"embedded"` (default): a block inside your own page. `"page"`: the
107
107
  * respondent's whole screen, for a QR code or a shared link. It fills the
@@ -109,36 +109,39 @@ interface FormProps {
109
109
  * under a thin progress bar with large type and tap targets, and in classic
110
110
  * layout the questions are sections on one scrolling page.
111
111
  */
112
- presentation?: FormPresentation;
113
- /** A practice's name and logo, shown small at the top of every step and on the ending. */
114
- brand?: FormBrand;
115
- registry?: RendererRegistry;
112
+ presentation?: FormPresentation | undefined;
113
+ /** An organisation's or business's name and logo, shown small at the top of every step and on the ending. */
114
+ brand?: FormBrand | undefined;
115
+ registry?: RendererRegistry | undefined;
116
116
  /**
117
117
  * Called once the form completes. The ending shows only after it resolves.
118
118
  * If it rejects, the answers stay on screen, read-only (a completed response
119
119
  * is final), with an error and a "Try again" button that sends them again.
120
120
  */
121
- onSubmit?: (engine: FormEngine) => void | Promise<void>;
122
- /** Called after every answer (debounce it to save drafts). */
123
- onChange?: (state: FormState, engine: FormEngine) => void;
121
+ onSubmit?: ((engine: FormEngine) => void | Promise<void>) | undefined;
122
+ /**
123
+ * Called on every state change: an answer, a step forward or back, a
124
+ * submit attempt (debounce it to save drafts).
125
+ */
126
+ onChange?: ((state: FormState, engine: FormEngine) => void) | undefined;
124
127
  /**
125
128
  * In focus layout, move on after a single pick (rating, yes/no, one choice).
126
129
  * Arrow keys only move through the options; a click, tap or Space picks.
127
130
  * Never moves on from the last question. Default true.
128
131
  */
129
- autoAdvance?: boolean;
130
- submitLabel?: string;
131
- nextLabel?: string;
132
+ autoAdvance?: boolean | undefined;
133
+ submitLabel?: string | undefined;
134
+ nextLabel?: string | undefined;
132
135
  /** The submit button's label after `onSubmit` failed. Default "Try again". */
133
- retryLabel?: string;
136
+ retryLabel?: string | undefined;
134
137
  slots?: {
135
138
  [K in keyof FormSlotMap]?: SlotOverride<FormSlotMap[K]>;
136
- };
139
+ } | undefined;
137
140
  slotProps?: {
138
141
  [K in keyof FormSlotMap]?: Partial<FormSlotMap[K]>;
139
- };
140
- className?: string;
141
- testID?: string;
142
+ } | undefined;
143
+ className?: string | undefined;
144
+ testID?: string | undefined;
142
145
  }
143
146
  declare function Form(props: FormProps): react.JSX.Element;
144
147
 
@@ -161,6 +164,7 @@ interface FieldChromeProps {
161
164
  */
162
165
  declare function FieldChrome({ props, children, className }: FieldChromeProps): react.JSX.Element;
163
166
  declare function describedBy(props: FieldRendererProps): string | undefined;
167
+
164
168
  declare const builtInRenderers: RendererEntry[];
165
169
  /**
166
170
  * The built-in web renderers, shared by every `<Form>` that gets no
@@ -169,4 +173,29 @@ declare const builtInRenderers: RendererEntry[];
169
173
  */
170
174
  declare const defaultRegistry: _marktiderman_genesis_forms_react.RendererRegistry;
171
175
 
172
- export { type BrandSlotProps, type EndingSlotProps, type ErrorSummaryItem, type ErrorSummarySlotProps, FieldChrome, type FieldChromeProps, Form, type FormBrand, type FormPresentation, type FormPresentationValue, type FormProps, type FormSlotMap, type HeaderSlotProps, type NavigationSlotProps, type ProgressSlotProps, builtInRenderers, defaultRegistry, describedBy, useFormPresentation };
176
+ /**
177
+ * The video or audio a form is about, as the host app plays it. `media-notes`
178
+ * fields read it: with `currentTime` the add row starts at the current moment
179
+ * ("Add note at 1:23"), with `seek` a note's time plays the media from there.
180
+ * Both are in seconds from the start of the full recording.
181
+ */
182
+ interface MediaTime {
183
+ /** The playhead now, or undefined when it isn't known yet. */
184
+ currentTime?: (() => number | undefined) | undefined;
185
+ /** Move the playhead. */
186
+ seek?: ((seconds: number) => void) | undefined;
187
+ }
188
+ /** Provide it around `<Form>` to link `media-notes` fields to a media player. */
189
+ declare const MediaTimeContext: react.Context<MediaTime | null>;
190
+ /** The enclosing `MediaTimeContext` value, or null. */
191
+ declare function useMediaTime(): MediaTime | null;
192
+ /**
193
+ * The text a form is about (an article, a policy, a contract). `text-highlights`
194
+ * fields highlight passages of it; a field's own `props.text` is used when no
195
+ * host provides one. Offsets are UTF-16 indices into this exact string.
196
+ */
197
+ declare const SourceTextContext: react.Context<string | null>;
198
+ /** The enclosing `SourceTextContext` value, or null. */
199
+ declare function useSourceText(): string | null;
200
+
201
+ export { type BrandSlotProps, type EndingSlotProps, type ErrorSummaryItem, type ErrorSummarySlotProps, FieldChrome, type FieldChromeProps, Form, type FormBrand, type FormPresentation, type FormPresentationValue, type FormProps, type FormSlotMap, type HeaderSlotProps, type MediaTime, MediaTimeContext, type NavigationSlotProps, type ProgressSlotProps, SourceTextContext, builtInRenderers, defaultRegistry, describedBy, useFormPresentation, useMediaTime, useSourceText };