@marktiderman/genesis-forms-studio 0.4.0 → 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/README.md +34 -13
- package/dist/{chunk-7XQXES5I.js → chunk-KUYYXBEN.js} +48 -94
- package/dist/chunk-KUYYXBEN.js.map +1 -0
- package/dist/index.d.ts +43 -195
- package/dist/index.js +33 -585
- package/dist/index.js.map +1 -1
- package/dist/rules.d.ts +500 -3
- package/dist/rules.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-7XQXES5I.js.map +0 -1
- package/dist/rules-DZ1gTZoH.d.ts +0 -528
package/dist/rules-DZ1gTZoH.d.ts
DELETED
|
@@ -1,528 +0,0 @@
|
|
|
1
|
-
import { FieldBlock, Json, FormDefinition } from '@marktiderman/genesis-forms';
|
|
2
|
-
import * as react from 'react';
|
|
3
|
-
import { ReactNode } from 'react';
|
|
4
|
-
import { SubNavItem } from '@marktiderman/genesis-ui/layout';
|
|
5
|
-
|
|
6
|
-
type Cell = string | number | boolean;
|
|
7
|
-
|
|
8
|
-
/** One answer as a cell: option labels for choices, Yes/No, lists joined with commas. */
|
|
9
|
-
declare function answerCell(field: FieldBlock, value: Json | undefined): Cell;
|
|
10
|
-
/** A label with recall tokens replaced by their fallback. */
|
|
11
|
-
declare const plainLabel: (label: string) => string;
|
|
12
|
-
interface AnswerColumn {
|
|
13
|
-
/** The field's key: stable across versions, so one column covers every version's answers. */
|
|
14
|
-
key: string;
|
|
15
|
-
label: string;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* One column per field key across the given definitions, in the order the newest
|
|
19
|
-
* definition asks them, then keys only older versions had (in their order). Labels come
|
|
20
|
-
* from the newest definition that has the key.
|
|
21
|
-
*/
|
|
22
|
-
declare function answerColumns(definitions: readonly FormDefinition[], skip?: ReadonlySet<string>): AnswerColumn[];
|
|
23
|
-
/** A response's answers by field key, as cells (answers are stored by field id). */
|
|
24
|
-
declare function answersByKey(definition: FormDefinition, answers: Record<string, Json>): Record<string, Cell>;
|
|
25
|
-
/** A response's raw answers by field key, with the field each was given to (answers are stored by field id). */
|
|
26
|
-
declare function valuesByKey(definition: FormDefinition, answers: Record<string, Json>): Map<string, {
|
|
27
|
-
field: FieldBlock;
|
|
28
|
-
value: Json;
|
|
29
|
-
}>;
|
|
30
|
-
/**
|
|
31
|
-
* CSV text (RFC 4180, CRLF) with a header row; every cell is quoted when it needs to be.
|
|
32
|
-
* Text that a spreadsheet would run as a formula (starting with = + - @, tab or CR) gets a
|
|
33
|
-
* leading apostrophe, so a respondent's answer can't run in Excel when staff open the
|
|
34
|
-
* download. Numbers stay as they are. (Sheets delivery writes RAW values and needs none.)
|
|
35
|
-
*/
|
|
36
|
-
declare function toCsv(header: readonly string[], rows: readonly (readonly Cell[])[]): string;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Where a response is, on a form that saves answers as they go (`settings.saveProgress`):
|
|
40
|
-
* `in_progress` while someone is answering, `partial` once they stopped without sending
|
|
41
|
-
* (kept, and counted like a sent one), `completed` once sent.
|
|
42
|
-
*/
|
|
43
|
-
type ResponseStatus = "in_progress" | "partial" | "completed";
|
|
44
|
-
/** One answered form (a feedback response, a worksheet entry). */
|
|
45
|
-
interface SummaryItem {
|
|
46
|
-
id: string;
|
|
47
|
-
/** The day it was answered, YYYY-MM-DD. */
|
|
48
|
-
date: string;
|
|
49
|
-
definition: FormDefinition;
|
|
50
|
-
answers: Record<string, Json>;
|
|
51
|
-
/** Extra plain values a filter can match, such as the class or coach. */
|
|
52
|
-
extra?: Record<string, Cell>;
|
|
53
|
-
/** Default `completed`. */
|
|
54
|
-
status?: ResponseStatus | undefined;
|
|
55
|
-
/**
|
|
56
|
-
* The furthest question reached (a field id of `definition`), for an unfinished
|
|
57
|
-
* response: genesis-forms `formProgress`'s `fieldId`. Drop-off by question reads it.
|
|
58
|
-
*/
|
|
59
|
-
furthest?: string | null | undefined;
|
|
60
|
-
/**
|
|
61
|
-
* Its hidden inputs, as stored. Drop-off by question rebuilds the respondent's path
|
|
62
|
-
* from them and `answers`, so logic that reads a hidden input is followed.
|
|
63
|
-
*/
|
|
64
|
-
hidden?: Record<string, Json> | undefined;
|
|
65
|
-
/**
|
|
66
|
-
* Answers the host keeps apart from `answers` (contact details on their own row, say),
|
|
67
|
-
* by field key. Only the path reads them: summaries and filters don't.
|
|
68
|
-
*/
|
|
69
|
-
extraAnswers?: Record<string, Json | null | undefined> | undefined;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Which responses to show by status. `counted` (the default): sent and partial ones,
|
|
73
|
-
* which summaries count; `in_progress` ones are still being answered.
|
|
74
|
-
*/
|
|
75
|
-
type StatusFilter = "counted" | "completed" | "partial" | "in_progress";
|
|
76
|
-
/** The status filter's words, in the menu's order. */
|
|
77
|
-
declare const STATUS_FILTER_LABEL: Record<StatusFilter, string>;
|
|
78
|
-
/** A response status's word, as a table or a detail panel shows it. */
|
|
79
|
-
declare const RESPONSE_STATUS_LABEL: Record<ResponseStatus, string>;
|
|
80
|
-
/** Whether `item` passes a status filter (no status is `completed`). */
|
|
81
|
-
declare function matchesStatus(item: Pick<SummaryItem, "status">, filter?: StatusFilter): boolean;
|
|
82
|
-
interface ScaleSummary {
|
|
83
|
-
kind: "scale";
|
|
84
|
-
key: string;
|
|
85
|
-
label: string;
|
|
86
|
-
type: "rating" | "nps";
|
|
87
|
-
/** The field type, as for every summary. */
|
|
88
|
-
fieldType: "rating" | "nps";
|
|
89
|
-
/** How many responses answered it. */
|
|
90
|
-
count: number;
|
|
91
|
-
min: number;
|
|
92
|
-
max: number;
|
|
93
|
-
/** Null when no one answered. */
|
|
94
|
-
average: number | null;
|
|
95
|
-
/** One per value from min to max, in order, zeros included. */
|
|
96
|
-
buckets: {
|
|
97
|
-
value: number;
|
|
98
|
-
count: number;
|
|
99
|
-
}[];
|
|
100
|
-
/**
|
|
101
|
-
* Answers given on an older version's different scale (a 1–10 rating now 1–5), moved
|
|
102
|
-
* onto this one in proportion: they count, at their nearest point on this scale.
|
|
103
|
-
*/
|
|
104
|
-
rescaled: number;
|
|
105
|
-
/** Answers left out: outside their own question's scale, or not a whole number. */
|
|
106
|
-
excluded: number;
|
|
107
|
-
/** NPS only: promoters 9–10, passives 7–8, detractors 0–6, score = % promoters − % detractors. */
|
|
108
|
-
nps: {
|
|
109
|
-
promoters: number;
|
|
110
|
-
passives: number;
|
|
111
|
-
detractors: number;
|
|
112
|
-
score: number;
|
|
113
|
-
} | null;
|
|
114
|
-
}
|
|
115
|
-
interface ChoiceSummary {
|
|
116
|
-
kind: "choice";
|
|
117
|
-
key: string;
|
|
118
|
-
label: string;
|
|
119
|
-
/** single-choice, multi-choice, dropdown, yes-no or consent. */
|
|
120
|
-
fieldType: string;
|
|
121
|
-
/** How many responses answered it. */
|
|
122
|
-
count: number;
|
|
123
|
-
/** Several options per response, so the percentages can add up to more than 100. */
|
|
124
|
-
multiple: boolean;
|
|
125
|
-
/** The newest version's options in order, then any only older versions had. */
|
|
126
|
-
options: {
|
|
127
|
-
value: string;
|
|
128
|
-
label: string;
|
|
129
|
-
count: number;
|
|
130
|
-
percent: number;
|
|
131
|
-
}[];
|
|
132
|
-
}
|
|
133
|
-
interface TextSummary {
|
|
134
|
-
kind: "text";
|
|
135
|
-
key: string;
|
|
136
|
-
label: string;
|
|
137
|
-
/** The field type: short-text, long-text, email… */
|
|
138
|
-
fieldType: string;
|
|
139
|
-
count: number;
|
|
140
|
-
/** Every answer, newest day first. */
|
|
141
|
-
answers: {
|
|
142
|
-
id: string;
|
|
143
|
-
date: string;
|
|
144
|
-
text: string;
|
|
145
|
-
}[];
|
|
146
|
-
}
|
|
147
|
-
type QuestionSummary = ScaleSummary | ChoiceSummary | TextSummary;
|
|
148
|
-
/**
|
|
149
|
-
* One summary per question across `definitions` (newest first: it sets the order, labels,
|
|
150
|
-
* type and options), counting each item's answers by field key. Keys in `skip` are left out.
|
|
151
|
-
*/
|
|
152
|
-
declare function summarizeResponses(items: readonly SummaryItem[], definitions: readonly FormDefinition[], skip?: ReadonlySet<string>): QuestionSummary[];
|
|
153
|
-
/** The highest score any rating question in `definitions` asks for (NPS left out); null when none. */
|
|
154
|
-
declare function highestRating(definitions: readonly FormDefinition[]): number | null;
|
|
155
|
-
/** The lowest rating-question answer in an item (NPS left out), or null when it has none. */
|
|
156
|
-
declare function lowestRating(item: SummaryItem): number | null;
|
|
157
|
-
/**
|
|
158
|
-
* The highest answer that counts as low on a rating or NPS question: the bottom 40% of a
|
|
159
|
-
* rating's scale (at least 1), or 0–6 (detractors) on NPS. Null for other questions.
|
|
160
|
-
*/
|
|
161
|
-
declare function lowRatingAtMost(field: FieldBlock): number | null;
|
|
162
|
-
/** lowRatingAtMost for a scale known only by its type and top score (a ScaleSummary's). */
|
|
163
|
-
declare function lowScaleAtMost(type: "rating" | "nps", max: number): number;
|
|
164
|
-
/** Whether any rating or NPS answer in an item is low on its own question's scale. */
|
|
165
|
-
declare function hasLowRating(item: SummaryItem): boolean;
|
|
166
|
-
/** hasLowRating, leaving out the answers to keys in `skip` (as summaries and needsFollowUp do). */
|
|
167
|
-
declare function hasLowRatingExcept(item: SummaryItem, skip: ReadonlySet<string>): boolean;
|
|
168
|
-
/**
|
|
169
|
-
* What "low" means across `definitions`, in words: "2 or below" for one 1–5 scale, "2 or
|
|
170
|
-
* below out of 5, 4 or below out of 10" for two, "0–6 on NPS" beside them ("0–6" alone).
|
|
171
|
-
* Null when no question has a rating or NPS answer.
|
|
172
|
-
*/
|
|
173
|
-
declare function lowRatingLabel(definitions: readonly FormDefinition[]): string | null;
|
|
174
|
-
interface ResponseFilter {
|
|
175
|
-
/** First and last day, YYYY-MM-DD, both included; open-ended when absent. */
|
|
176
|
-
from?: string | null;
|
|
177
|
-
to?: string | null;
|
|
178
|
-
/** Extra values that must match exactly, such as { class: "6am WOD" }. */
|
|
179
|
-
match?: Record<string, string | null | undefined>;
|
|
180
|
-
/** Only items with a rating at or below this (any rating question). */
|
|
181
|
-
ratingAtMost?: number | null;
|
|
182
|
-
/** Only items with a low answer on its question's own scale (hasLowRating). */
|
|
183
|
-
low?: boolean;
|
|
184
|
-
/** By status (matchesStatus); default `counted`: sent and partial, not in progress. */
|
|
185
|
-
status?: StatusFilter | undefined;
|
|
186
|
-
}
|
|
187
|
-
/** Whether a filter leaves anything out beyond the default (in-progress responses). */
|
|
188
|
-
declare function isFiltered(filter: ResponseFilter): boolean;
|
|
189
|
-
/** The items a filter keeps, in their order. */
|
|
190
|
-
declare function filterResponses<T extends SummaryItem>(items: readonly T[], filter: ResponseFilter): T[];
|
|
191
|
-
type DatePreset = "all" | "today" | "week" | "7d" | "30d" | "month";
|
|
192
|
-
/**
|
|
193
|
-
* A preset's first and last day, given today (YYYY-MM-DD, on the clock the items' days
|
|
194
|
-
* use). "week" starts on Monday; "7d" and "30d" include today.
|
|
195
|
-
*/
|
|
196
|
-
declare function presetRange(preset: DatePreset, today: string): {
|
|
197
|
-
from: string | null;
|
|
198
|
-
to: string | null;
|
|
199
|
-
};
|
|
200
|
-
/** What the filter bar holds; `toResponseFilter` turns it into a filter. */
|
|
201
|
-
interface FiltersValue {
|
|
202
|
-
when: DatePreset | "custom";
|
|
203
|
-
/** Custom range, YYYY-MM-DD; empty for open-ended. */
|
|
204
|
-
from: string;
|
|
205
|
-
to: string;
|
|
206
|
-
/** By choice filter key, e.g. { class: "6am WOD" }; missing or empty for any. */
|
|
207
|
-
match: Record<string, string>;
|
|
208
|
-
ratingAtMost: number | null;
|
|
209
|
-
/** Low ratings, each on its own question's scale (lowRatingAtMost). */
|
|
210
|
-
low: boolean;
|
|
211
|
-
/** By status, on a form that saves answers as they go; missing is `counted`. */
|
|
212
|
-
status?: StatusFilter | undefined;
|
|
213
|
-
}
|
|
214
|
-
declare const NO_FILTERS: FiltersValue;
|
|
215
|
-
/** The filter for `value`, given today's date on the clock the responses' days use. */
|
|
216
|
-
declare function toResponseFilter(value: FiltersValue, today: string): ResponseFilter;
|
|
217
|
-
/**
|
|
218
|
-
* The one-click "Low ratings this week": this week, and low on each question's own scale
|
|
219
|
-
* (lowRatingAtMost: the bottom 40% of a rating, NPS detractors), other filters kept.
|
|
220
|
-
*/
|
|
221
|
-
declare const lowThisWeek: (value: FiltersValue) => FiltersValue;
|
|
222
|
-
|
|
223
|
-
/** One headline number: a label, the number, and a short line under it. */
|
|
224
|
-
interface HeadlineNumber {
|
|
225
|
-
key: string;
|
|
226
|
-
label: string;
|
|
227
|
-
value: ReactNode;
|
|
228
|
-
detail?: ReactNode;
|
|
229
|
-
/** The detail's colour: good news, bad news, a warning, or plain. Default plain. */
|
|
230
|
-
tone?: "positive" | "negative" | "warning" | "muted" | undefined;
|
|
231
|
-
}
|
|
232
|
-
/** A row of headline numbers (KPI cards): two across on a phone, up to four on a wide screen. */
|
|
233
|
-
declare function HeadlineNumbers({ numbers, isLoading, className, }: {
|
|
234
|
-
numbers: readonly HeadlineNumber[];
|
|
235
|
-
isLoading?: boolean | undefined;
|
|
236
|
-
className?: string | undefined;
|
|
237
|
-
}): react.JSX.Element | null;
|
|
238
|
-
|
|
239
|
-
interface Headline {
|
|
240
|
-
responses: {
|
|
241
|
-
count: number;
|
|
242
|
-
/** Responses in the period before, under the same other filters; null with no period. */
|
|
243
|
-
previous: number | null;
|
|
244
|
-
/** count − previous; null with no period. */
|
|
245
|
-
change: number | null;
|
|
246
|
-
/** How many of `count` are partial (never sent); left out when there are none. */
|
|
247
|
-
partial?: number;
|
|
248
|
-
};
|
|
249
|
-
/** The first rating question (newest definition order); null when the form has none. */
|
|
250
|
-
rating: {
|
|
251
|
-
key: string;
|
|
252
|
-
label: string;
|
|
253
|
-
max: number;
|
|
254
|
-
/** Null when nobody answered it. */
|
|
255
|
-
average: number | null;
|
|
256
|
-
/** The same question's average in the period before; null without one or without answers. */
|
|
257
|
-
previousAverage: number | null;
|
|
258
|
-
/** average − previousAverage, when both are known. */
|
|
259
|
-
change: number | null;
|
|
260
|
-
} | null;
|
|
261
|
-
/** Responses with a low answer on any rating or NPS question; null when there is none. */
|
|
262
|
-
low: {
|
|
263
|
-
count: number;
|
|
264
|
-
} | null;
|
|
265
|
-
/** The first consent question, else the first yes/no one; null when there is none. */
|
|
266
|
-
rate: {
|
|
267
|
-
key: string;
|
|
268
|
-
label: string;
|
|
269
|
-
type: "consent" | "yes-no";
|
|
270
|
-
/** Agreed or Yes. */
|
|
271
|
-
yes: number;
|
|
272
|
-
/** How many answered it. */
|
|
273
|
-
count: number;
|
|
274
|
-
/** 0–100; null when nobody answered. */
|
|
275
|
-
percent: number | null;
|
|
276
|
-
} | null;
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* The headline numbers for `items` (already filtered), with `previous` (the same filters
|
|
280
|
-
* over the period before, from `comparisonFilter`) for the changes. `definitions` are
|
|
281
|
-
* newest first, as `summarizeResponses` takes them; `skip` leaves keys out.
|
|
282
|
-
*/
|
|
283
|
-
declare function headlineNumbers(items: readonly SummaryItem[], definitions: readonly FormDefinition[], options?: {
|
|
284
|
-
previous?: readonly SummaryItem[] | null;
|
|
285
|
-
skip?: ReadonlySet<string>;
|
|
286
|
-
}): Headline;
|
|
287
|
-
/**
|
|
288
|
-
* The period before the one the filter bar picks, with the bar's other filters kept, and
|
|
289
|
-
* what to call it ("last week"). "This week" compares with the same weekdays last week,
|
|
290
|
-
* "This month" with last month up to the same day, any other range with as many days
|
|
291
|
-
* just before it. Null for "All time", or a
|
|
292
|
-
* custom range with an open end.
|
|
293
|
-
*/
|
|
294
|
-
declare function comparisonFilter(value: FiltersValue, today: string): {
|
|
295
|
-
filter: ResponseFilter;
|
|
296
|
-
label: string;
|
|
297
|
-
} | null;
|
|
298
|
-
/**
|
|
299
|
-
* The cards for `headlineNumbers`' answer: Responses (with the change against `period`,
|
|
300
|
-
* e.g. "last week"), Average rating, Low ratings, and the consent or yes/no rate. A card
|
|
301
|
-
* the form has no question for is left out.
|
|
302
|
-
*/
|
|
303
|
-
declare function headlineCards(headline: Headline, options?: {
|
|
304
|
-
period?: string | null | undefined;
|
|
305
|
-
/** How many low-rated responses still need a follow-up (not handled); the card says so. */
|
|
306
|
-
lowOpen?: number | undefined;
|
|
307
|
-
}): HeadlineNumber[];
|
|
308
|
-
|
|
309
|
-
interface QuestionDropOff {
|
|
310
|
-
key: string;
|
|
311
|
-
label: string;
|
|
312
|
-
/** How many responses reached this question (it was on their path, on screen or answered). */
|
|
313
|
-
views: number;
|
|
314
|
-
/** How many stopped here: their furthest question, never sent. */
|
|
315
|
-
stopped: number;
|
|
316
|
-
/** stopped ÷ views as a whole percentage; null when nobody reached it. */
|
|
317
|
-
dropOff: number | null;
|
|
318
|
-
}
|
|
319
|
-
interface DropOffSummary {
|
|
320
|
-
/**
|
|
321
|
-
* How many people started: distinct responses of any status (sent, partial, in progress),
|
|
322
|
-
* each once. Someone is saved once they answer a question or move on from the first.
|
|
323
|
-
*/
|
|
324
|
-
started: number;
|
|
325
|
-
/** Each question's views and drop-off (`dropOffByQuestion`). */
|
|
326
|
-
dropOffs: QuestionDropOff[];
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Each question's views and drop-off, in the newest definition's order (`definitions`
|
|
330
|
-
* newest first, as `summarizeResponses` takes them), matched across versions by field key.
|
|
331
|
-
* Each response counts once (by `id`).
|
|
332
|
-
*
|
|
333
|
-
* - A sent response (`completed`, or no status) reached every question on its path and
|
|
334
|
-
* stopped at none. A question logic hid or jumped over for them was never reached.
|
|
335
|
-
* - A `partial` response reached the questions on its path before its `furthest` (the
|
|
336
|
-
* path is rebuilt from the answers so far, so it can run past it), then `furthest`, and
|
|
337
|
-
* stopped there. One without `furthest` (saved before it was kept) is left out.
|
|
338
|
-
* - An `in_progress` response is left out: they may still be answering.
|
|
339
|
-
*
|
|
340
|
-
* The path is rebuilt from `answers`, `hidden` and `extraAnswers`; give the item every
|
|
341
|
-
* answer logic can read, or a rule reading a missing one sees it unanswered. Two things a
|
|
342
|
-
* stored response can't tell, so they count as on the path:
|
|
343
|
-
*
|
|
344
|
-
* - A question a prefilled link answered and hid (`props.prefill.hideWhenPrefilled`):
|
|
345
|
-
* which answers came from the link lives in the form's answer events, which aren't kept.
|
|
346
|
-
* - A rule comparing a pick from a host-supplied option list to an option value, when the
|
|
347
|
-
* host stores the pick as its label: the rule sees the label.
|
|
348
|
-
*
|
|
349
|
-
* Someone who opened the form and left before moving on from the first question was
|
|
350
|
-
* never saved, so they aren't counted.
|
|
351
|
-
*/
|
|
352
|
-
declare function dropOffByQuestion(items: readonly SummaryItem[], definitions: readonly FormDefinition[], skip?: ReadonlySet<string>): QuestionDropOff[];
|
|
353
|
-
/**
|
|
354
|
-
* What `<DropOffByQuestion>` shows: how many people started and each question's drop-off.
|
|
355
|
-
* Pass every response under the host's filters except status (in-progress ones count as
|
|
356
|
-
* started, never per question).
|
|
357
|
-
*/
|
|
358
|
-
declare function dropOffSummary(items: readonly SummaryItem[], definitions: readonly FormDefinition[], skip?: ReadonlySet<string>): DropOffSummary;
|
|
359
|
-
|
|
360
|
-
interface FollowUp<T extends SummaryItem = SummaryItem> {
|
|
361
|
-
item: T;
|
|
362
|
-
/** The lowest low answer, on its own question's scale. */
|
|
363
|
-
rating: {
|
|
364
|
-
value: number;
|
|
365
|
-
max: number;
|
|
366
|
-
type: "rating" | "nps";
|
|
367
|
-
};
|
|
368
|
-
/** The first written answer (short or long text), trimmed; null when there is none. */
|
|
369
|
-
comment: string | null;
|
|
370
|
-
}
|
|
371
|
-
/** One item's follow-up, or null when none of its answers is low. */
|
|
372
|
-
declare function followUpOf<T extends SummaryItem>(item: T, skip?: ReadonlySet<string>): FollowUp<T> | null;
|
|
373
|
-
/**
|
|
374
|
-
* The items that need a follow-up, newest day first: a low answer on a rating or NPS
|
|
375
|
-
* question (lowRatingAtMost), and not handled yet. `skip` leaves keys out (contact details).
|
|
376
|
-
*/
|
|
377
|
-
declare function needsFollowUp<T extends SummaryItem>(items: readonly T[], options?: {
|
|
378
|
-
isHandled?: (item: T) => boolean;
|
|
379
|
-
skip?: ReadonlySet<string>;
|
|
380
|
-
}): FollowUp<T>[];
|
|
381
|
-
|
|
382
|
-
/** A day (YYYY-MM-DD) in words, given today on the same clock: "Today", "Yesterday", "Oct 21", "Oct 21, 2025". */
|
|
383
|
-
declare function dayLabel(day: string, today?: string): string;
|
|
384
|
-
|
|
385
|
-
/** One search param a prefilled link may set, and the values staff pick from. */
|
|
386
|
-
interface PrefillChoice {
|
|
387
|
-
key: string;
|
|
388
|
-
/** The picker's accessible name, e.g. "Class". */
|
|
389
|
-
label: string;
|
|
390
|
-
/** The picker's "leave it out" choice, e.g. "Any class". */
|
|
391
|
-
anyLabel: string;
|
|
392
|
-
options: readonly {
|
|
393
|
-
value: string;
|
|
394
|
-
label: string;
|
|
395
|
-
}[];
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* `url` with `values` set as search params (empty values left out), keeping what it has.
|
|
399
|
-
* An absolute link stays absolute; a relative one (`/f/a?x=1`) stays relative, so it never
|
|
400
|
-
* throws while a page is still working out its origin.
|
|
401
|
-
*/
|
|
402
|
-
declare function prefilledUrl(url: string, values: Record<string, string | undefined>): string;
|
|
403
|
-
/** An `<iframe>` that shows the form on another website, full width. */
|
|
404
|
-
declare function embedSnippet(url: string, title: string): string;
|
|
405
|
-
|
|
406
|
-
type FormAvailability = "draft" | "archived" | "paused" | "scheduled" | "closed" | "full" | "live";
|
|
407
|
-
interface AvailabilityInput {
|
|
408
|
-
status: "draft" | "active" | "archived";
|
|
409
|
-
/** False when the form has never been published (it has no version to serve). */
|
|
410
|
-
published?: boolean;
|
|
411
|
-
/**
|
|
412
|
-
* ISO times it takes responses from and until; open-ended when absent. Each needs `Z` or
|
|
413
|
-
* an offset (`+02:00`): a time without one would be read on whatever clock the code runs
|
|
414
|
-
* on. One that doesn't parse, or has no offset, keeps the form closed (fail closed).
|
|
415
|
-
* Normalise times where they are written (`new Date(…).toISOString()`).
|
|
416
|
-
*/
|
|
417
|
-
opensAt?: string | null | undefined;
|
|
418
|
-
closesAt?: string | null | undefined;
|
|
419
|
-
paused?: boolean | undefined;
|
|
420
|
-
/** At most this many completed responses; null or absent for no cap. */
|
|
421
|
-
responseCap?: number | null | undefined;
|
|
422
|
-
/** Completed, non-deleted responses so far, when the caller knows the count. */
|
|
423
|
-
completedResponses?: number | null | undefined;
|
|
424
|
-
/** Whether the cap is reached, when only that is known (the public page never sees the count). */
|
|
425
|
-
full?: boolean | undefined;
|
|
426
|
-
}
|
|
427
|
-
/** Whether the form has its response cap: from `full` when given, else from the cap and count. */
|
|
428
|
-
declare function isFull(form: Pick<AvailabilityInput, "responseCap" | "completedResponses" | "full">): boolean;
|
|
429
|
-
/** An ISO time with `Z` or an offset, as epoch milliseconds; NaN for anything else. */
|
|
430
|
-
declare function parseInstant(value: string): number;
|
|
431
|
-
/**
|
|
432
|
-
* Where a form stands at `at`. Archived and draft first, then paused (staff chose it,
|
|
433
|
-
* whatever the clock says), then the opening window (a time that can't be read, or has no
|
|
434
|
-
* offset, counts as closed), then the cap. Only "live" takes
|
|
435
|
-
* responses.
|
|
436
|
-
*/
|
|
437
|
-
declare function formAvailability(form: AvailabilityInput, at?: Date): FormAvailability;
|
|
438
|
-
/** Whether the form takes responses at `at`. */
|
|
439
|
-
declare function takesResponses(form: AvailabilityInput, at?: Date): boolean;
|
|
440
|
-
|
|
441
|
-
/** Where the host's draft saving stands. */
|
|
442
|
-
type SaveState = "saved" | "saving" | "unsaved" | "failed" | "stopped" | "conflict";
|
|
443
|
-
declare const SAVE_STATE_LABEL: Record<SaveState, string>;
|
|
444
|
-
type ChipTone = "success" | "warning" | "danger" | "info" | "muted";
|
|
445
|
-
/** Whether something can be published, from an editor's checks. */
|
|
446
|
-
interface Readiness {
|
|
447
|
-
state: "ready" | "problems" | "warnings" | "empty" | "pending";
|
|
448
|
-
/** Problems or warnings to look at (0 when ready, empty or pending). */
|
|
449
|
-
count: number;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Readiness from an editor's findings (genesis-forms-builder's `Analysis.findings`, or any
|
|
453
|
-
* list shaped like it): errors block publishing; `no-questions` (nothing to answer yet)
|
|
454
|
-
* is "empty", not broken. `pending` says errors are held back while a new question is being written.
|
|
455
|
-
*/
|
|
456
|
-
declare function readinessOf(findings: readonly {
|
|
457
|
-
level: "error" | "warning" | "info";
|
|
458
|
-
code?: string;
|
|
459
|
-
}[], options?: {
|
|
460
|
-
pending?: boolean;
|
|
461
|
-
}): Readiness;
|
|
462
|
-
declare function readinessChip(readiness: Readiness): {
|
|
463
|
-
label: string;
|
|
464
|
-
tone: ChipTone;
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* A form's status chip: "Live · version 3", "Draft", "Paused · version 3"… `availability`
|
|
469
|
-
* is formAvailability's answer; `version` the live version, when there is one.
|
|
470
|
-
*/
|
|
471
|
-
declare function formStatusChip(availability: FormAvailability, version?: number | null): {
|
|
472
|
-
label: string;
|
|
473
|
-
tone: ChipTone;
|
|
474
|
-
};
|
|
475
|
-
/** The form page's four places, in order. */
|
|
476
|
-
type FormTab = "questions" | "logic" | "responses" | "settings";
|
|
477
|
-
/** One of a form page's child navigation items: genesis-ui's `SubNavItem`, plus its place. */
|
|
478
|
-
type FormNavItem = SubNavItem & {
|
|
479
|
-
id: FormTab;
|
|
480
|
-
badge?: number;
|
|
481
|
-
};
|
|
482
|
-
/** How many questions a definition asks, and how many rules plus endings it has. */
|
|
483
|
-
declare function formCounts(definition: FormDefinition | null | undefined): {
|
|
484
|
-
questions: number;
|
|
485
|
-
logic: number;
|
|
486
|
-
};
|
|
487
|
-
/**
|
|
488
|
-
* The form page's child navigation items (Questions, Logic and endings, Responses,
|
|
489
|
-
* Settings) with their counts as badges, for the app shell's sub-navigation:
|
|
490
|
-
* `useAppShellSubNav(formNavItems("/forms/f1", counts))`. `at` is the form page's path
|
|
491
|
-
* (each place is `?tab=<id>` on it, Questions the path itself, which the shell's most
|
|
492
|
-
* specific match keeps from lighting up on the others) or a function giving each place's
|
|
493
|
-
* address. A count left out (or null) shows no badge.
|
|
494
|
-
*/
|
|
495
|
-
declare function formNavItems(at: string | ((tab: FormTab) => string), counts?: {
|
|
496
|
-
questions?: number | null;
|
|
497
|
-
logic?: number | null;
|
|
498
|
-
responses?: number | null;
|
|
499
|
-
},
|
|
500
|
-
/** Each place's icon, for the rail and the phone's bottom bar (`FORM_NAV_ICONS`). */
|
|
501
|
-
icons?: Partial<Record<FormTab, SubNavItem["icon"]>>): FormNavItem[];
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* "How was class?" → "how-was-class": the public link's last part (lowercase words and
|
|
505
|
-
* hyphens, at most 60). `fallback` is used when the title has no letters or numbers.
|
|
506
|
-
*/
|
|
507
|
-
declare function slugFromTitle(title: string, fallback?: string): string;
|
|
508
|
-
/** A form link: lowercase letters and numbers, in words joined by single hyphens. */
|
|
509
|
-
declare const SLUG_PATTERN: RegExp;
|
|
510
|
-
/** Why a form link can't be used, or nothing when it fits SLUG_PATTERN. */
|
|
511
|
-
declare function validateSlug(slug: string): string | undefined;
|
|
512
|
-
/** A duplicate's name: "Copy of …", within the 120 characters a form name may have. */
|
|
513
|
-
declare function copyTitle(title: string): string;
|
|
514
|
-
/** The first of `slug`, `slug-2`, `slug-3`… that isn't in `taken`, within 60 characters. */
|
|
515
|
-
declare function freeSlug(slug: string, taken: ReadonlySet<string>): string;
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Whether the thank-you page offers "Send another response" (on unless `submitAnother`
|
|
519
|
-
* is false). Never on a form that takes one response per person: a second response
|
|
520
|
-
* would be refused. Anonymous forms don't know who answered, so one per person doesn't apply.
|
|
521
|
-
*/
|
|
522
|
-
declare function offersSubmitAnother(form: {
|
|
523
|
-
submitAnother?: boolean | null | undefined;
|
|
524
|
-
onePerPerson?: boolean | null | undefined;
|
|
525
|
-
anonymous?: boolean | null | undefined;
|
|
526
|
-
}): boolean;
|
|
527
|
-
|
|
528
|
-
export { isFull as $, type AnswerColumn as A, dropOffByQuestion as B, type ChipTone as C, type DatePreset as D, dropOffSummary as E, type FiltersValue as F, embedSnippet as G, type Headline as H, filterResponses as I, followUpOf as J, formAvailability as K, formCounts as L, formNavItems as M, NO_FILTERS as N, formStatusChip as O, type PrefillChoice as P, type QuestionSummary as Q, type Readiness as R, type SaveState as S, type TextSummary as T, freeSlug as U, hasLowRating as V, hasLowRatingExcept as W, headlineCards as X, headlineNumbers as Y, highestRating as Z, isFiltered as _, type QuestionDropOff as a, lowRatingAtMost as a0, lowRatingLabel as a1, lowScaleAtMost as a2, lowThisWeek as a3, lowestRating as a4, matchesStatus as a5, needsFollowUp as a6, offersSubmitAnother as a7, parseInstant as a8, plainLabel as a9, prefilledUrl as aa, presetRange as ab, readinessChip as ac, readinessOf as ad, slugFromTitle as ae, summarizeResponses as af, takesResponses as ag, toCsv as ah, toResponseFilter as ai, validateSlug as aj, valuesByKey as ak, type AvailabilityInput as b, type Cell as c, type ChoiceSummary as d, type DropOffSummary as e, type FollowUp as f, type FormAvailability as g, type FormNavItem as h, type FormTab as i, type HeadlineNumber as j, HeadlineNumbers as k, RESPONSE_STATUS_LABEL as l, type ResponseFilter as m, type ResponseStatus as n, SAVE_STATE_LABEL as o, SLUG_PATTERN as p, STATUS_FILTER_LABEL as q, type ScaleSummary as r, type StatusFilter as s, type SummaryItem as t, answerCell as u, answerColumns as v, answersByKey as w, comparisonFilter as x, copyTitle as y, dayLabel as z };
|