@ez-gform/codegen 0.1.4 → 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/dist/index.cjs +146 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +146 -52
- package/dist/index.js.map +1 -1
- package/package.json +35 -7
package/dist/index.cjs
CHANGED
|
@@ -34,28 +34,29 @@ var import_core = require("@ez-gform/core");
|
|
|
34
34
|
var escapeHtml = (value) => {
|
|
35
35
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
36
36
|
};
|
|
37
|
+
var PLACEHOLDER_FBZX = "-1000000000000000000";
|
|
37
38
|
var attr = (name, value) => {
|
|
38
39
|
return `${name}="${escapeHtml(value)}"`;
|
|
39
40
|
};
|
|
40
|
-
var renderChoiceInputs = (question, inputType) => {
|
|
41
|
+
var renderChoiceInputs = (question, inputType, requiredAttr = "") => {
|
|
41
42
|
const options = question.options ?? [];
|
|
42
43
|
const rows = options.map((option) => {
|
|
43
44
|
if (option.isOther) {
|
|
44
45
|
return [
|
|
45
|
-
`<label><input type="${inputType}" ${attr("name", question.entryId)} value="__other_option__"> Other:</label>`,
|
|
46
|
+
`<label><input type="${inputType}" ${attr("name", question.entryId)} value="__other_option__"${requiredAttr}> Other:</label>`,
|
|
46
47
|
`<input type="text" ${attr("name", `${question.entryId}.other_option_response`)}>`
|
|
47
48
|
].join("\n ");
|
|
48
49
|
}
|
|
49
|
-
return `<label><input type="${inputType}" ${attr("name", question.entryId)} ${attr("value", option.value)}> ${escapeHtml(option.value)}</label>`;
|
|
50
|
+
return `<label><input type="${inputType}" ${attr("name", question.entryId)} ${attr("value", option.value)}${requiredAttr}> ${escapeHtml(option.value)}</label>`;
|
|
50
51
|
});
|
|
51
52
|
return rows.join("\n ");
|
|
52
53
|
};
|
|
53
|
-
var renderGrid = (question) => {
|
|
54
|
+
var renderGrid = (question, requiredAttr) => {
|
|
54
55
|
const inputType = question.type === "checkbox_grid" ? "checkbox" : "radio";
|
|
55
56
|
const columns = question.options ?? [];
|
|
56
57
|
const rows = (question.rows ?? []).map((row) => {
|
|
57
58
|
const cells = columns.map((col) => {
|
|
58
|
-
return `<td><input type="${inputType}" ${attr("name", row.entryId)} ${attr("value", col.value)}></td>`;
|
|
59
|
+
return `<td><input type="${inputType}" ${attr("name", row.entryId)} ${attr("value", col.value)}${requiredAttr}></td>`;
|
|
59
60
|
}).join("");
|
|
60
61
|
return ` <tr><th>${escapeHtml(row.label)}</th>${cells}</tr>`;
|
|
61
62
|
}).join("\n");
|
|
@@ -64,7 +65,9 @@ ${rows}
|
|
|
64
65
|
</table>`;
|
|
65
66
|
};
|
|
66
67
|
var renderQuestion = (question) => {
|
|
67
|
-
const
|
|
68
|
+
const description = question.description ? `
|
|
69
|
+
<p>${escapeHtml(question.description)}</p>` : "";
|
|
70
|
+
const label = `<label>${escapeHtml(question.title)}${question.required ? " *" : ""}</label>${description}`;
|
|
68
71
|
const requiredAttr = question.required ? " required" : "";
|
|
69
72
|
switch (question.type) {
|
|
70
73
|
case "short_answer":
|
|
@@ -75,14 +78,20 @@ var renderQuestion = (question) => {
|
|
|
75
78
|
<textarea ${attr("name", question.entryId)}${requiredAttr}></textarea>`;
|
|
76
79
|
case "multiple_choice":
|
|
77
80
|
return `${label}
|
|
78
|
-
${renderChoiceInputs(question, "radio")}`;
|
|
81
|
+
${renderChoiceInputs(question, "radio", requiredAttr)}`;
|
|
82
|
+
// Checkboxes and checkbox grids cannot express "at least one" natively
|
|
83
|
+
// (`required` would force every box), so only the asterisk marks them.
|
|
79
84
|
case "checkboxes":
|
|
80
85
|
return `${label}
|
|
81
86
|
${renderChoiceInputs(question, "checkbox")}`;
|
|
82
87
|
case "dropdown": {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
const blank = question.required ? [' <option value="" disabled selected>Choose</option>'] : [' <option value="" selected></option>'];
|
|
89
|
+
const options = [
|
|
90
|
+
...blank,
|
|
91
|
+
...(question.options ?? []).map((option) => {
|
|
92
|
+
return ` <option ${attr("value", option.value)}>${escapeHtml(option.value)}</option>`;
|
|
93
|
+
})
|
|
94
|
+
].join("\n");
|
|
86
95
|
return `${label}
|
|
87
96
|
<select ${attr("name", question.entryId)}${requiredAttr}>
|
|
88
97
|
${options}
|
|
@@ -94,7 +103,7 @@ ${options}
|
|
|
94
103
|
const inputs = [];
|
|
95
104
|
for (let value = min; value <= max; value++) {
|
|
96
105
|
inputs.push(
|
|
97
|
-
`<label><input type="radio" ${attr("name", question.entryId)} ${attr("value", String(value))}> ${value}</label>`
|
|
106
|
+
`<label><input type="radio" ${attr("name", question.entryId)} ${attr("value", String(value))}${requiredAttr}> ${value}</label>`
|
|
98
107
|
);
|
|
99
108
|
}
|
|
100
109
|
return `${label}
|
|
@@ -103,35 +112,44 @@ ${inputs.join("\n")}`;
|
|
|
103
112
|
case "grid":
|
|
104
113
|
case "checkbox_grid":
|
|
105
114
|
return `${label}
|
|
106
|
-
${renderGrid(question)}`;
|
|
115
|
+
${renderGrid(question, question.type === "grid" ? requiredAttr : "")}`;
|
|
107
116
|
case "date": {
|
|
108
117
|
const inputs = [];
|
|
109
118
|
if (question.date?.includeYear !== false) {
|
|
110
119
|
inputs.push(
|
|
111
|
-
`<input type="number" ${attr("name", `${question.entryId}_year`)} placeholder="Year">`
|
|
120
|
+
`<input type="number" ${attr("name", `${question.entryId}_year`)} placeholder="Year"${requiredAttr}>`
|
|
112
121
|
);
|
|
113
122
|
}
|
|
114
123
|
inputs.push(
|
|
115
|
-
`<input type="number" ${attr("name", `${question.entryId}_month`)} placeholder="Month">`
|
|
124
|
+
`<input type="number" ${attr("name", `${question.entryId}_month`)} placeholder="Month"${requiredAttr}>`
|
|
116
125
|
);
|
|
117
126
|
inputs.push(
|
|
118
|
-
`<input type="number" ${attr("name", `${question.entryId}_day`)} placeholder="Day">`
|
|
127
|
+
`<input type="number" ${attr("name", `${question.entryId}_day`)} placeholder="Day"${requiredAttr}>`
|
|
119
128
|
);
|
|
120
129
|
if (question.date?.includeTime) {
|
|
121
130
|
inputs.push(
|
|
122
|
-
`<input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour">`
|
|
131
|
+
`<input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour"${requiredAttr}>`
|
|
123
132
|
);
|
|
124
133
|
inputs.push(
|
|
125
|
-
`<input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute">`
|
|
134
|
+
`<input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute"${requiredAttr}>`
|
|
126
135
|
);
|
|
127
136
|
}
|
|
128
137
|
return `${label}
|
|
129
138
|
${inputs.join("\n")}`;
|
|
130
139
|
}
|
|
131
|
-
case "time":
|
|
140
|
+
case "time": {
|
|
141
|
+
const inputs = [
|
|
142
|
+
`<input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour"${requiredAttr}>`,
|
|
143
|
+
`<input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute"${requiredAttr}>`
|
|
144
|
+
];
|
|
145
|
+
if (question.time?.isDuration) {
|
|
146
|
+
inputs.push(
|
|
147
|
+
`<input type="number" ${attr("name", `${question.entryId}_second`)} placeholder="Second"${requiredAttr}>`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
132
150
|
return `${label}
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
${inputs.join("\n")}`;
|
|
152
|
+
}
|
|
135
153
|
case "file_upload":
|
|
136
154
|
return `${label}
|
|
137
155
|
<!-- file_upload is not supported by the Forms API without sign-in; no input rendered -->`;
|
|
@@ -144,14 +162,22 @@ var generateHtmlForm = (schema) => {
|
|
|
144
162
|
const body = schema.questions.map(renderQuestion).join("\n\n");
|
|
145
163
|
const hiddenFields = [];
|
|
146
164
|
if (schema.multiPage) {
|
|
165
|
+
const pageCount = Math.max(schema.sections.length, 1);
|
|
166
|
+
const pageHistory = Array.from({ length: pageCount }, (_, i) => {
|
|
167
|
+
return i;
|
|
168
|
+
}).join(",");
|
|
147
169
|
hiddenFields.push(
|
|
148
|
-
`<input type="hidden" ${attr("name", "fbzx")} ${attr("value",
|
|
170
|
+
`<input type="hidden" ${attr("name", "fbzx")} ${attr("value", PLACEHOLDER_FBZX)}>`,
|
|
171
|
+
`<input type="hidden" ${attr("name", "pageHistory")} ${attr("value", pageHistory)}>`,
|
|
172
|
+
`<input type="hidden" ${attr("name", "partialResponse")} ${attr("value", JSON.stringify([null, null, PLACEHOLDER_FBZX]))}>`
|
|
149
173
|
);
|
|
150
174
|
}
|
|
175
|
+
const formDescription = schema.description ? `<p>${escapeHtml(schema.description)}</p>
|
|
176
|
+
` : "";
|
|
151
177
|
return `<!-- ${escapeHtml(schema.title)} -->
|
|
152
178
|
<form action="${escapeHtml(formResponse)}" method="POST">
|
|
153
179
|
${hiddenFields.join("\n")}
|
|
154
|
-
${body}
|
|
180
|
+
${formDescription}${body}
|
|
155
181
|
|
|
156
182
|
<button type="submit">Submit</button>
|
|
157
183
|
</form>
|
|
@@ -170,7 +196,8 @@ var toPascalCase = (input) => {
|
|
|
170
196
|
const lower = word.toLowerCase();
|
|
171
197
|
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
172
198
|
}).join("");
|
|
173
|
-
|
|
199
|
+
if (pascal.length === 0) return "Form";
|
|
200
|
+
return /^[0-9]/.test(pascal) ? `_${pascal}` : pascal;
|
|
174
201
|
};
|
|
175
202
|
var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
176
203
|
"break",
|
|
@@ -218,7 +245,9 @@ var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
|
218
245
|
"await",
|
|
219
246
|
"null",
|
|
220
247
|
"true",
|
|
221
|
-
"false"
|
|
248
|
+
"false",
|
|
249
|
+
"eval",
|
|
250
|
+
"arguments"
|
|
222
251
|
]);
|
|
223
252
|
var toIdentifier = (input) => {
|
|
224
253
|
const words = splitWords(input);
|
|
@@ -248,11 +277,6 @@ var jsxEscape = (value) => {
|
|
|
248
277
|
var q = (value) => {
|
|
249
278
|
return JSON.stringify(value);
|
|
250
279
|
};
|
|
251
|
-
var hasOtherOption = (question) => {
|
|
252
|
-
return question.options?.some((option) => {
|
|
253
|
-
return option.isOther;
|
|
254
|
-
}) ?? false;
|
|
255
|
-
};
|
|
256
280
|
var renderShortText = (question, multiline) => {
|
|
257
281
|
const tag = multiline ? "textarea" : "input";
|
|
258
282
|
const typeAttr = multiline ? "" : ' type="text"';
|
|
@@ -308,12 +332,20 @@ var renderCheckboxGroup = (question) => {
|
|
|
308
332
|
return ` <label>
|
|
309
333
|
<input
|
|
310
334
|
type="checkbox"
|
|
311
|
-
onChange={(e) =>
|
|
335
|
+
onChange={(e) => {
|
|
336
|
+
const current = form.values[${entryId}];
|
|
337
|
+
const chosen = Array.isArray(current) ? current.filter((v) => typeof v === "string") : [];
|
|
338
|
+
form.setValue(${entryId}, e.target.checked ? [...chosen, { other: "" }] : chosen);
|
|
339
|
+
}}
|
|
312
340
|
/>
|
|
313
341
|
Other:
|
|
314
342
|
<input
|
|
315
343
|
type="text"
|
|
316
|
-
onChange={(e) =>
|
|
344
|
+
onChange={(e) => {
|
|
345
|
+
const current = form.values[${entryId}];
|
|
346
|
+
const chosen = Array.isArray(current) ? current.filter((v) => typeof v === "string") : [];
|
|
347
|
+
form.setValue(${entryId}, [...chosen, { other: e.target.value }]);
|
|
348
|
+
}}
|
|
317
349
|
/>
|
|
318
350
|
</label>`;
|
|
319
351
|
}
|
|
@@ -392,18 +424,77 @@ ${rows}
|
|
|
392
424
|
};
|
|
393
425
|
var renderDate = (question) => {
|
|
394
426
|
const entryId = q(question.entryId);
|
|
427
|
+
const includeYear = question.date?.includeYear ?? true;
|
|
428
|
+
const includeTime = question.date?.includeTime ?? false;
|
|
429
|
+
const required = question.required ? "true" : "false";
|
|
430
|
+
const dateParts = includeYear ? "[year, month, day]" : "[, month, day]";
|
|
431
|
+
const dateValue = includeYear ? "year, month, day" : "month, day";
|
|
432
|
+
const isObject = `typeof current === "object" && current !== null && !Array.isArray(current)`;
|
|
433
|
+
if (!includeTime) {
|
|
434
|
+
return `<input
|
|
435
|
+
type="date"
|
|
436
|
+
required={${required}}
|
|
437
|
+
onChange={(e) => {
|
|
438
|
+
const ${dateParts} = e.target.value.split("-").map(Number);
|
|
439
|
+
if (month === undefined || day === undefined) return;
|
|
440
|
+
form.setValue(${entryId}, { ${dateValue} });
|
|
441
|
+
}}
|
|
442
|
+
/>`;
|
|
443
|
+
}
|
|
444
|
+
const keepYear = includeYear ? "year: Number(current.year), " : "";
|
|
395
445
|
return `<input
|
|
396
446
|
type="date"
|
|
397
|
-
required={${
|
|
447
|
+
required={${required}}
|
|
398
448
|
onChange={(e) => {
|
|
399
|
-
const
|
|
400
|
-
const [year, month, day] = parts;
|
|
449
|
+
const ${dateParts} = e.target.value.split("-").map(Number);
|
|
401
450
|
if (month === undefined || day === undefined) return;
|
|
402
|
-
form.
|
|
451
|
+
const current = form.values[${entryId}];
|
|
452
|
+
const time = ${isObject} && "hour" in current && "minute" in current ? { hour: Number(current.hour), minute: Number(current.minute) } : {};
|
|
453
|
+
form.setValue(${entryId}, { ${dateValue}, ...time });
|
|
454
|
+
}}
|
|
455
|
+
/>
|
|
456
|
+
<input
|
|
457
|
+
type="time"
|
|
458
|
+
required={${required}}
|
|
459
|
+
onChange={(e) => {
|
|
460
|
+
const [hour, minute] = e.target.value.split(":").map(Number);
|
|
461
|
+
if (hour === undefined || minute === undefined) return;
|
|
462
|
+
const current = form.values[${entryId}];
|
|
463
|
+
const date = ${isObject} && "month" in current && "day" in current ? { ${keepYear}month: Number(current.month), day: Number(current.day) } : {};
|
|
464
|
+
form.setValue(${entryId}, { ...date, hour, minute });
|
|
403
465
|
}}
|
|
404
466
|
/>`;
|
|
405
467
|
};
|
|
406
|
-
var
|
|
468
|
+
var renderDuration = (question, typescript) => {
|
|
469
|
+
const entryId = q(question.entryId);
|
|
470
|
+
const required = question.required ? "true" : "false";
|
|
471
|
+
const isObject = `typeof current === "object" && current !== null && !Array.isArray(current)`;
|
|
472
|
+
const baseCast = typescript ? ' as Partial<Record<"hour" | "minute" | "second", number>>' : "";
|
|
473
|
+
const valueCast = typescript ? " as { hour: number; minute: number; second?: number }" : "";
|
|
474
|
+
const part = (key, label, max) => {
|
|
475
|
+
return `<input
|
|
476
|
+
type="number"
|
|
477
|
+
min={0}
|
|
478
|
+
max={${max}}
|
|
479
|
+
aria-label="${label}"
|
|
480
|
+
required={${required}}
|
|
481
|
+
onChange={(e) => {
|
|
482
|
+
const current = form.values[${entryId}];
|
|
483
|
+
const base = (${isObject} ? current : {})${baseCast};
|
|
484
|
+
const merged = { ...base, ${key}: e.target.value === "" ? undefined : Number(e.target.value) };
|
|
485
|
+
const allEmpty = Object.values(merged).every((v) => v === undefined);
|
|
486
|
+
form.setValue(${entryId}, allEmpty ? undefined : (merged${valueCast}));
|
|
487
|
+
}}
|
|
488
|
+
/>`;
|
|
489
|
+
};
|
|
490
|
+
return [
|
|
491
|
+
part("hour", "Hours", 23),
|
|
492
|
+
part("minute", "Minutes", 59),
|
|
493
|
+
part("second", "Seconds", 59)
|
|
494
|
+
].join("\n ");
|
|
495
|
+
};
|
|
496
|
+
var renderTime = (question, typescript) => {
|
|
497
|
+
if (question.time?.isDuration) return renderDuration(question, typescript);
|
|
407
498
|
const entryId = q(question.entryId);
|
|
408
499
|
return `<input
|
|
409
500
|
type="time"
|
|
@@ -416,7 +507,7 @@ var renderTime = (question) => {
|
|
|
416
507
|
}}
|
|
417
508
|
/>`;
|
|
418
509
|
};
|
|
419
|
-
var renderQuestion2 = (question) => {
|
|
510
|
+
var renderQuestion2 = (question, typescript) => {
|
|
420
511
|
const label = `<label>${jsxEscape(question.title)}${question.required ? " *" : ""}</label>`;
|
|
421
512
|
const description = question.description ? `
|
|
422
513
|
<p>${jsxEscape(question.description)}</p>` : "";
|
|
@@ -448,7 +539,7 @@ var renderQuestion2 = (question) => {
|
|
|
448
539
|
field = renderDate(question);
|
|
449
540
|
break;
|
|
450
541
|
case "time":
|
|
451
|
-
field = renderTime(question);
|
|
542
|
+
field = renderTime(question, typescript);
|
|
452
543
|
break;
|
|
453
544
|
case "file_upload":
|
|
454
545
|
field = `<p>"${jsxEscape(question.title)}" is a file upload question and is not supported by Google Forms submission without sign-in.</p>`;
|
|
@@ -461,7 +552,7 @@ var renderQuestion2 = (question) => {
|
|
|
461
552
|
${field}
|
|
462
553
|
</div>`;
|
|
463
554
|
};
|
|
464
|
-
var renderSection = (section, questionsById) => {
|
|
555
|
+
var renderSection = (section, questionsById, typescript) => {
|
|
465
556
|
const heading = section.title ? ` <h2>${jsxEscape(section.title)}</h2>
|
|
466
557
|
` : "";
|
|
467
558
|
const description = section.description ? ` <p>${jsxEscape(section.description)}</p>
|
|
@@ -470,27 +561,29 @@ var renderSection = (section, questionsById) => {
|
|
|
470
561
|
return questionsById.get(id);
|
|
471
562
|
}).filter((question) => {
|
|
472
563
|
return question !== void 0;
|
|
473
|
-
}).map(
|
|
564
|
+
}).map((question) => {
|
|
565
|
+
return renderQuestion2(question, typescript);
|
|
566
|
+
}).join("\n");
|
|
474
567
|
return `${heading}${description}${body}`;
|
|
475
568
|
};
|
|
476
|
-
var usesOther = (question) => {
|
|
477
|
-
return hasOtherOption(question) && (question.type === "multiple_choice" || question.type === "checkboxes");
|
|
478
|
-
};
|
|
479
569
|
var generateReactComponent = (schema, options = {}) => {
|
|
480
570
|
const typescript = options.typescript ?? true;
|
|
481
571
|
const name = options.name ?? `${toPascalCase(schema.title)}Form`;
|
|
482
|
-
const
|
|
483
|
-
|
|
572
|
+
const embedded = { ...schema };
|
|
573
|
+
delete embedded.fbzx;
|
|
574
|
+
const schemaJson = JSON.stringify(embedded, null, 2);
|
|
484
575
|
const questionsById = new Map(
|
|
485
576
|
schema.questions.map((qn) => {
|
|
486
577
|
return [qn.id, qn];
|
|
487
578
|
})
|
|
488
579
|
);
|
|
489
580
|
const body = schema.sections.length > 1 || schema.sections[0]?.title && schema.sections[0].title.length > 0 ? schema.sections.map((section) => {
|
|
490
|
-
return renderSection(section, questionsById);
|
|
491
|
-
}).join("\n") : schema.questions.map(
|
|
581
|
+
return renderSection(section, questionsById, typescript);
|
|
582
|
+
}).join("\n") : schema.questions.map((question) => {
|
|
583
|
+
return renderQuestion2(question, typescript);
|
|
584
|
+
}).join("\n");
|
|
492
585
|
const schemaDeclaration = typescript ? `${schemaJson} as const satisfies FormSchema` : schemaJson;
|
|
493
|
-
const typeImport = typescript ? `import type { FormSchema } from "@ez-gform/
|
|
586
|
+
const typeImport = typescript ? `import type { FormSchema } from "@ez-gform/react";
|
|
494
587
|
` : "";
|
|
495
588
|
return `${typeImport}import { useGoogleForm } from "@ez-gform/react";
|
|
496
589
|
|
|
@@ -529,7 +622,7 @@ ${body}
|
|
|
529
622
|
var quote = (value) => {
|
|
530
623
|
return JSON.stringify(value);
|
|
531
624
|
};
|
|
532
|
-
var
|
|
625
|
+
var hasOtherOption = (question) => {
|
|
533
626
|
return question.options?.some((option) => {
|
|
534
627
|
return option.isOther;
|
|
535
628
|
}) ?? false;
|
|
@@ -541,9 +634,9 @@ var fieldType = (question) => {
|
|
|
541
634
|
return "string";
|
|
542
635
|
case "multiple_choice":
|
|
543
636
|
case "dropdown":
|
|
544
|
-
return
|
|
637
|
+
return hasOtherOption(question) ? "string | { other: string }" : "string";
|
|
545
638
|
case "checkboxes":
|
|
546
|
-
return
|
|
639
|
+
return hasOtherOption(question) ? "(string | { other: string })[]" : "string[]";
|
|
547
640
|
case "linear_scale":
|
|
548
641
|
return "number";
|
|
549
642
|
case "date":
|
|
@@ -566,7 +659,8 @@ var fieldType = (question) => {
|
|
|
566
659
|
};
|
|
567
660
|
var generateTypes = (schema, options = {}) => {
|
|
568
661
|
const name = options.name ?? toPascalCase(schema.title);
|
|
569
|
-
const
|
|
662
|
+
const { fbzx: _, ...embeddedSchema } = schema;
|
|
663
|
+
const schemaJson = JSON.stringify(embeddedSchema, null, 2);
|
|
570
664
|
const usesDate = schema.questions.some((q2) => {
|
|
571
665
|
return q2.type === "date";
|
|
572
666
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/generate-html.ts","../src/names.ts","../src/generate-react.ts","../src/generate-types.ts","../src/schema-json.ts"],"sourcesContent":["export { generateHtmlForm } from \"./generate-html.js\";\nexport { generateReactComponent } from \"./generate-react.js\";\nexport type { GenerateReactComponentOptions } from \"./generate-react.js\";\nexport { generateTypes } from \"./generate-types.js\";\nexport type { GenerateTypesOptions } from \"./generate-types.js\";\nexport { toIdentifier, toPascalCase } from \"./names.js\";\nexport { generateSchemaJson } from \"./schema-json.js\";\nexport type { GenerateSchemaJsonOptions } from \"./schema-json.js\";\n","import { formUrls } from \"@ez-gform/core\";\nimport type { FormSchema, Question } from \"@ez-gform/types\";\n\nconst escapeHtml = (value: string): string => {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n};\n\nconst attr = (name: string, value: string): string => {\n return `${name}=\"${escapeHtml(value)}\"`;\n};\n\nconst renderChoiceInputs = (\n question: Question,\n inputType: \"radio\" | \"checkbox\",\n): string => {\n const options = question.options ?? [];\n const rows = options.map((option) => {\n if (option.isOther) {\n return [\n `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} value=\"__other_option__\"> Other:</label>`,\n `<input type=\"text\" ${attr(\"name\", `${question.entryId}.other_option_response`)}>`,\n ].join(\"\\n \");\n }\n return `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} ${attr(\"value\", option.value)}> ${escapeHtml(option.value)}</label>`;\n });\n return rows.join(\"\\n \");\n};\n\nconst renderGrid = (question: Question): string => {\n const inputType = question.type === \"checkbox_grid\" ? \"checkbox\" : \"radio\";\n const columns = question.options ?? [];\n const rows = (question.rows ?? [])\n .map((row) => {\n const cells = columns\n .map((col) => {\n return `<td><input type=\"${inputType}\" ${attr(\"name\", row.entryId)} ${attr(\"value\", col.value)}></td>`;\n })\n .join(\"\");\n return ` <tr><th>${escapeHtml(row.label)}</th>${cells}</tr>`;\n })\n .join(\"\\n\");\n return `<table>\\n${rows}\\n</table>`;\n};\n\nconst renderQuestion = (question: Question): string => {\n const label = `<label>${escapeHtml(question.title)}${question.required ? \" *\" : \"\"}</label>`;\n const requiredAttr = question.required ? \" required\" : \"\";\n\n switch (question.type) {\n case \"short_answer\":\n return `${label}\\n<input type=\"text\" ${attr(\"name\", question.entryId)}${requiredAttr}>`;\n case \"paragraph\":\n return `${label}\\n<textarea ${attr(\"name\", question.entryId)}${requiredAttr}></textarea>`;\n case \"multiple_choice\":\n return `${label}\\n${renderChoiceInputs(question, \"radio\")}`;\n case \"checkboxes\":\n return `${label}\\n${renderChoiceInputs(question, \"checkbox\")}`;\n case \"dropdown\": {\n const options = (question.options ?? [])\n .map((option) => {\n return ` <option ${attr(\"value\", option.value)}>${escapeHtml(option.value)}</option>`;\n })\n .join(\"\\n\");\n return `${label}\\n<select ${attr(\"name\", question.entryId)}${requiredAttr}>\\n${options}\\n</select>`;\n }\n case \"linear_scale\": {\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const inputs: string[] = [];\n for (let value = min; value <= max; value++) {\n inputs.push(\n `<label><input type=\"radio\" ${attr(\"name\", question.entryId)} ${attr(\"value\", String(value))}> ${value}</label>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"grid\":\n case \"checkbox_grid\":\n return `${label}\\n${renderGrid(question)}`;\n case \"date\": {\n const inputs: string[] = [];\n if (question.date?.includeYear !== false) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_year`)} placeholder=\"Year\">`,\n );\n }\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_month`)} placeholder=\"Month\">`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_day`)} placeholder=\"Day\">`,\n );\n if (question.date?.includeTime) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\">`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\">`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"time\":\n return `${label}\\n<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\">\\n<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\">`;\n case \"file_upload\":\n return `${label}\\n<!-- file_upload is not supported by the Forms API without sign-in; no input rendered -->`;\n default:\n return label;\n }\n};\n\n/**\n * Emits a plain HTML `<form>` (no JS framework required) posting directly to\n * the form's `formResponse` endpoint, with `name` attributes following every\n * `entry.N`(`_year`/`_month`/`_day`/`_hour`/`_minute`, `__other_option__`,\n * `.other_option_response`) encoding rule in\n * `docs/research/google-forms-internals.md` §3.\n */\nexport const generateHtmlForm = (schema: FormSchema): string => {\n const { formResponse } = formUrls(schema.formId || \"REPLACE_WITH_FORM_ID\");\n const body = schema.questions.map(renderQuestion).join(\"\\n\\n\");\n\n const hiddenFields: string[] = [];\n if (schema.multiPage) {\n hiddenFields.push(\n `<input type=\"hidden\" ${attr(\"name\", \"fbzx\")} ${attr(\"value\", schema.fbzx ?? \"\")}>`,\n );\n }\n\n return `<!-- ${escapeHtml(schema.title)} -->\n<form action=\"${escapeHtml(formResponse)}\" method=\"POST\">\n${hiddenFields.join(\"\\n\")}\n${body}\n\n<button type=\"submit\">Submit</button>\n</form>\n`;\n};\n","/** Splits a free-form string (a form title, question label, etc.) into word tokens. */\nconst splitWords = (input: string): string[] => {\n return (\n input\n // Insert boundaries between camelCase / acronym transitions.\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n // Replace anything that isn't a letter/digit with a space.\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter((word) => {\n return word.length > 0;\n })\n );\n};\n\n/** Converts a free-form string into `PascalCase`, safe for a TS identifier/type name. */\nexport const toPascalCase = (input: string): string => {\n const words = splitWords(input);\n const pascal = words\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n return pascal.length > 0 ? pascal : \"Form\";\n};\n\n/**\n * JS/TS reserved words (keywords, future reserved words, and strict-mode\n * reserved words) that are not valid as a binding identifier. Guarded here\n * because `toIdentifier` output is emitted as a real variable/prop name in\n * generated code.\n */\nconst RESERVED_WORDS = new Set([\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"export\",\n \"extends\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"enum\",\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n \"await\",\n \"null\",\n \"true\",\n \"false\",\n]);\n\n/**\n * Converts a free-form string into a valid, sanitized JS identifier\n * (`camelCase`, no leading digit, and no collision with a JS/TS reserved\n * word — a trailing underscore is appended in that case).\n */\nexport const toIdentifier = (input: string): string => {\n const words = splitWords(input);\n if (words.length === 0) return \"Form\";\n const [first, ...rest] = words;\n const camel =\n first!.toLowerCase() +\n rest\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n const prefixed = /^[0-9]/.test(camel) ? `_${camel}` : camel;\n return RESERVED_WORDS.has(prefixed) ? `${prefixed}_` : prefixed;\n};\n","import type {\n FormSchema,\n GenerateReactOptions,\n Question,\n Section,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type GenerateReactComponentOptions = GenerateReactOptions;\n\nconst JSX_TEXT_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \"{\": \"{\",\n \"}\": \"}\",\n};\n\n/** Escapes text so it's safe to drop directly into JSX element children. */\nconst jsxEscape = (value: string): string => {\n return value.replace(/[&<>{}]/g, (ch) => {\n return JSX_TEXT_ESCAPES[ch]!;\n });\n};\n\nconst q = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst hasOtherOption = (question: Question): boolean => {\n return (\n question.options?.some((option) => {\n return option.isOther;\n }) ?? false\n );\n};\n\nconst renderShortText = (question: Question, multiline: boolean): string => {\n const tag = multiline ? \"textarea\" : \"input\";\n const typeAttr = multiline ? \"\" : ' type=\"text\"';\n return `<${tag}${typeAttr} {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n } />`;\n};\n\nconst renderRadioGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={typeof form.values[${entryId}] === \"object\"}\n onChange={() => form.setValue(${entryId}, { other: \"\" })}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => form.setValue(${entryId}, { other: e.target.value })}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n value={${value}}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div role=\"radiogroup\">\n${options}\n </div>`;\n};\n\nconst renderSelect = (question: Question): string => {\n const options = (question.options ?? [])\n .map((option) => {\n return ` <option value={${q(option.value)}}>${jsxEscape(option.value)}</option>`;\n })\n .join(\"\\n\");\n return `<select {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n }>\n <option value=\"\">Select…</option>\n${options}\n </select>`;\n};\n\nconst renderCheckboxGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"checkbox\"\n onChange={(e) => form.setValue(${entryId}, e.target.checked ? { other: \"\" } : undefined)}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => form.setValue(${entryId}, { other: e.target.value })}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div>\n${options}\n </div>`;\n};\n\nconst renderLinearScale = (question: Question): string => {\n const entryId = q(question.entryId);\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const items: string[] = [];\n for (let value = min; value <= max; value++) {\n items.push(` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${q(String(value))}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${value}\n </label>`);\n }\n const lowLabel = question.scale?.lowLabel;\n const highLabel = question.scale?.highLabel;\n return `<div role=\"radiogroup\">\n ${lowLabel ? `<span>${jsxEscape(lowLabel)}</span>` : \"\"}\n${items.join(\"\\n\")}\n ${highLabel ? `<span>${jsxEscape(highLabel)}</span>` : \"\"}\n </div>`;\n};\n\nconst renderGrid = (question: Question): string => {\n const isCheckbox = question.type === \"checkbox_grid\";\n const columns = question.options ?? [];\n const headerCells = columns\n .map((col) => {\n return `<th key={${q(col.value)}}>${jsxEscape(col.value)}</th>`;\n })\n .join(\"\\n \");\n const rows = (question.rows ?? [])\n .map((row) => {\n const entryId = q(row.entryId);\n const cells = columns\n .map((col) => {\n const value = q(col.value);\n if (isCheckbox) {\n return `<td key={${value}}><input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} /></td>`;\n }\n return `<td key={${value}}>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n </td>`;\n })\n .join(\"\\n \");\n return ` <tr>\n <th>${jsxEscape(row.label)}</th>\n ${cells}\n </tr>`;\n })\n .join(\"\\n\");\n return `<table>\n <thead>\n <tr>\n <th />\n ${headerCells}\n </tr>\n </thead>\n <tbody>\n${rows}\n </tbody>\n </table>`;\n};\n\nconst renderDate = (question: Question): string => {\n const entryId = q(question.entryId);\n return `<input\n type=\"date\"\n required={${question.required ? \"true\" : \"false\"}}\n onChange={(e) => {\n const parts = e.target.value.split(\"-\").map(Number);\n const [year, month, day] = parts;\n if (month === undefined || day === undefined) return;\n form.setValue(${entryId}, { year, month, day });\n }}\n />`;\n};\n\nconst renderTime = (question: Question): string => {\n const entryId = q(question.entryId);\n return `<input\n type=\"time\"\n required={${question.required ? \"true\" : \"false\"}}\n onChange={(e) => {\n const parts = e.target.value.split(\":\").map(Number);\n const [hour, minute] = parts;\n if (hour === undefined || minute === undefined) return;\n form.setValue(${entryId}, { hour, minute });\n }}\n />`;\n};\n\nconst renderQuestion = (question: Question): string => {\n const label = `<label>${jsxEscape(question.title)}${\n question.required ? \" *\" : \"\"\n }</label>`;\n const description = question.description\n ? `\\n <p>${jsxEscape(question.description)}</p>`\n : \"\";\n\n let field: string;\n switch (question.type) {\n case \"short_answer\":\n field = renderShortText(question, false);\n break;\n case \"paragraph\":\n field = renderShortText(question, true);\n break;\n case \"multiple_choice\":\n field = renderRadioGroup(question);\n break;\n case \"dropdown\":\n field = renderSelect(question);\n break;\n case \"checkboxes\":\n field = renderCheckboxGroup(question);\n break;\n case \"linear_scale\":\n field = renderLinearScale(question);\n break;\n case \"grid\":\n case \"checkbox_grid\":\n field = renderGrid(question);\n break;\n case \"date\":\n field = renderDate(question);\n break;\n case \"time\":\n field = renderTime(question);\n break;\n case \"file_upload\":\n field = `<p>\"${jsxEscape(question.title)}\" is a file upload question and is not supported by Google Forms submission without sign-in.</p>`;\n break;\n default:\n field = `<input {...form.register(${q(question.entryId)})} />`;\n }\n\n return ` <div key={${q(question.id)}}>\n ${label}${description}\n ${field}\n </div>`;\n};\n\nconst renderSection = (\n section: Section,\n questionsById: Map<string, Question>,\n): string => {\n const heading = section.title\n ? ` <h2>${jsxEscape(section.title)}</h2>\\n`\n : \"\";\n const description = section.description\n ? ` <p>${jsxEscape(section.description)}</p>\\n`\n : \"\";\n const body = section.questionIds\n .map((id) => {\n return questionsById.get(id);\n })\n .filter((question): question is Question => {\n return question !== undefined;\n })\n .map(renderQuestion)\n .join(\"\\n\");\n return `${heading}${description}${body}`;\n};\n\nconst usesOther = (question: Question): boolean => {\n return (\n hasOtherOption(question) &&\n (question.type === \"multiple_choice\" || question.type === \"checkboxes\")\n );\n};\n\n/**\n * Generates a paste-ready React component wired to `useGoogleForm` from\n * `@ez-gform/react`. Renders every supported question type; `file_upload`\n * questions render a disabled note instead of an input (the Forms API can't\n * accept file uploads without a signed-in session).\n */\nexport const generateReactComponent = (\n schema: FormSchema,\n options: GenerateReactComponentOptions = {},\n): string => {\n const typescript = options.typescript ?? true;\n const name = options.name ?? `${toPascalCase(schema.title)}Form`;\n const schemaJson = JSON.stringify(schema, null, 2);\n void usesOther;\n\n const questionsById = new Map(\n schema.questions.map((qn) => {\n return [qn.id, qn];\n }),\n );\n const body =\n schema.sections.length > 1 ||\n (schema.sections[0]?.title && schema.sections[0].title.length > 0)\n ? schema.sections\n .map((section) => {\n return renderSection(section, questionsById);\n })\n .join(\"\\n\")\n : schema.questions.map(renderQuestion).join(\"\\n\");\n\n const schemaDeclaration = typescript\n ? `${schemaJson} as const satisfies FormSchema`\n : schemaJson;\n const typeImport = typescript\n ? `import type { FormSchema } from \"@ez-gform/core\";\\n`\n : \"\";\n\n return `${typeImport}import { useGoogleForm } from \"@ez-gform/react\";\n\nconst schema = ${schemaDeclaration};\n\nexport function ${name}() {\n const form = useGoogleForm({ formId: schema.formId, schema });\n\n return (\n <form\n onSubmit={form.submit}\n >\n <h1>${jsxEscape(schema.title)}</h1>\n ${schema.description ? `<p>${jsxEscape(schema.description)}</p>` : \"\"}\n${body}\n\n <div>Status: {form.status}</div>\n {form.errors && form.errors.length > 0 && (\n <ul>\n {form.errors.map((error, index) => (\n <li key={index}>{error.message}</li>\n ))}\n </ul>\n )}\n\n <button type=\"submit\" disabled={form.status === \"submitting\"}>\n Submit\n </button>\n </form>\n );\n}\n`;\n};\n","import type {\n FormSchema,\n GenerateTypesOptions,\n Question,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type { GenerateTypesOptions } from \"@ez-gform/types\";\n\nconst quote = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst hasOtherOption = (question: Question): boolean => {\n return (\n question.options?.some((option) => {\n return option.isOther;\n }) ?? false\n );\n};\n\n/** Returns the TS type text for a single question's value, or `undefined` to omit it entirely. */\nconst fieldType = (question: Question): string | undefined => {\n switch (question.type) {\n case \"short_answer\":\n case \"paragraph\":\n return \"string\";\n case \"multiple_choice\":\n case \"dropdown\":\n return hasOtherOption(question) ? \"string | { other: string }\" : \"string\";\n case \"checkboxes\":\n return hasOtherOption(question)\n ? \"(string | { other: string })[]\"\n : \"string[]\";\n case \"linear_scale\":\n return \"number\";\n case \"date\":\n return \"DateValue\";\n case \"time\":\n return \"TimeValue\";\n case \"grid\":\n case \"checkbox_grid\": {\n const rowIds = (question.rows ?? []).map((row) => {\n return quote(row.entryId);\n });\n const keyType = rowIds.length > 0 ? rowIds.join(\" | \") : \"string\";\n return `Record<${keyType}, string | string[]>`;\n }\n case \"file_upload\":\n // File uploads can't be submitted through the Forms API without\n // sign-in; there is no meaningful value type for them.\n return undefined;\n default:\n return \"string\";\n }\n};\n\n/**\n * Emits `const <Name>Schema = {...} as const satisfies FormSchema;` and a\n * companion `type <Name>Values = { \"entry.N\"?: ...; ... }` mapping every\n * question's entry id to its precise `FieldValue` subtype.\n */\nexport const generateTypes = (\n schema: FormSchema,\n options: GenerateTypesOptions = {},\n): string => {\n const name = options.name ?? toPascalCase(schema.title);\n const schemaJson = JSON.stringify(schema, null, 2);\n\n const usesDate = schema.questions.some((q) => {\n return q.type === \"date\";\n });\n const usesTime = schema.questions.some((q) => {\n return q.type === \"time\";\n });\n const extraTypeImports = [\n usesDate ? \"DateValue\" : undefined,\n usesTime ? \"TimeValue\" : undefined,\n ].filter((t): t is string => {\n return t !== undefined;\n });\n\n const fields: string[] = [];\n for (const question of schema.questions) {\n const type = fieldType(question);\n if (type === undefined) continue;\n const optional = question.required ? \"\" : \"?\";\n fields.push(` ${quote(question.entryId)}${optional}: ${type};`);\n }\n\n const importLine = [\"FormSchema\", ...extraTypeImports].sort().join(\", \");\n\n return `import type { ${importLine} } from \"@ez-gform/core\";\n\nexport const ${name}Schema = ${schemaJson} as const satisfies FormSchema;\n\nexport type ${name}Values = {\n${fields.join(\"\\n\")}\n};\n`;\n};\n","import type { FormSchema, GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\nexport type { GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\n/** Serializes a `FormSchema` back to JSON text. Round-trips structurally with `JSON.parse`. */\nexport const generateSchemaJson = (\n schema: FormSchema,\n options: GenerateSchemaJsonOptions = {},\n): string => {\n const pretty = options.pretty ?? true;\n return pretty ? JSON.stringify(schema, null, 2) : JSON.stringify(schema);\n};\n"],"mappings":"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","names":["renderGrid","renderQuestion","hasOtherOption","q"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/generate-html.ts","../src/names.ts","../src/generate-react.ts","../src/generate-types.ts","../src/schema-json.ts"],"sourcesContent":["export { generateHtmlForm } from \"./generate-html.js\";\nexport { generateReactComponent } from \"./generate-react.js\";\nexport type { GenerateReactComponentOptions } from \"./generate-react.js\";\nexport { generateTypes } from \"./generate-types.js\";\nexport type { GenerateTypesOptions } from \"./generate-types.js\";\nexport { toIdentifier, toPascalCase } from \"./names.js\";\nexport { generateSchemaJson } from \"./schema-json.js\";\nexport type { GenerateSchemaJsonOptions } from \"./schema-json.js\";\n","import { formUrls } from \"@ez-gform/core\";\nimport type { FormSchema, Question } from \"@ez-gform/types\";\n\nconst escapeHtml = (value: string): string => {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n};\n\n// Placeholder fbzx for multi-page forms. The research doc (section 5) says\n// Google expects fbzx echoed, which is unverified; core itself sends a random\n// token when the schema has none, so any well-formed token is treated as\n// acceptable. A real per-load token would be stale in generated code.\nconst PLACEHOLDER_FBZX = \"-1000000000000000000\";\n\nconst attr = (name: string, value: string): string => {\n return `${name}=\"${escapeHtml(value)}\"`;\n};\n\nconst renderChoiceInputs = (\n question: Question,\n inputType: \"radio\" | \"checkbox\",\n requiredAttr = \"\",\n): string => {\n const options = question.options ?? [];\n const rows = options.map((option) => {\n if (option.isOther) {\n return [\n `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} value=\"__other_option__\"${requiredAttr}> Other:</label>`,\n `<input type=\"text\" ${attr(\"name\", `${question.entryId}.other_option_response`)}>`,\n ].join(\"\\n \");\n }\n return `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} ${attr(\"value\", option.value)}${requiredAttr}> ${escapeHtml(option.value)}</label>`;\n });\n return rows.join(\"\\n \");\n};\n\nconst renderGrid = (question: Question, requiredAttr: string): string => {\n const inputType = question.type === \"checkbox_grid\" ? \"checkbox\" : \"radio\";\n const columns = question.options ?? [];\n const rows = (question.rows ?? [])\n .map((row) => {\n const cells = columns\n .map((col) => {\n return `<td><input type=\"${inputType}\" ${attr(\"name\", row.entryId)} ${attr(\"value\", col.value)}${requiredAttr}></td>`;\n })\n .join(\"\");\n return ` <tr><th>${escapeHtml(row.label)}</th>${cells}</tr>`;\n })\n .join(\"\\n\");\n return `<table>\\n${rows}\\n</table>`;\n};\n\nconst renderQuestion = (question: Question): string => {\n const description = question.description\n ? `\\n<p>${escapeHtml(question.description)}</p>`\n : \"\";\n const label = `<label>${escapeHtml(question.title)}${question.required ? \" *\" : \"\"}</label>${description}`;\n const requiredAttr = question.required ? \" required\" : \"\";\n\n switch (question.type) {\n case \"short_answer\":\n return `${label}\\n<input type=\"text\" ${attr(\"name\", question.entryId)}${requiredAttr}>`;\n case \"paragraph\":\n return `${label}\\n<textarea ${attr(\"name\", question.entryId)}${requiredAttr}></textarea>`;\n case \"multiple_choice\":\n return `${label}\\n${renderChoiceInputs(question, \"radio\", requiredAttr)}`;\n // Checkboxes and checkbox grids cannot express \"at least one\" natively\n // (`required` would force every box), so only the asterisk marks them.\n case \"checkboxes\":\n return `${label}\\n${renderChoiceInputs(question, \"checkbox\")}`;\n case \"dropdown\": {\n // Required: a disabled, selected empty-value placeholder option makes the\n // select report valueMissing until a real option is picked (without it\n // the first choice is always submitted). Optional: a selected blank\n // option posts an empty value (which core never emits and Google\n // ignores) instead of the first choice; not disabled because jsdom still\n // serialises a disabled selected option.\n const blank = question.required\n ? [' <option value=\"\" disabled selected>Choose</option>']\n : [' <option value=\"\" selected></option>'];\n const options = [\n ...blank,\n ...(question.options ?? []).map((option) => {\n return ` <option ${attr(\"value\", option.value)}>${escapeHtml(option.value)}</option>`;\n }),\n ].join(\"\\n\");\n return `${label}\\n<select ${attr(\"name\", question.entryId)}${requiredAttr}>\\n${options}\\n</select>`;\n }\n case \"linear_scale\": {\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const inputs: string[] = [];\n for (let value = min; value <= max; value++) {\n inputs.push(\n `<label><input type=\"radio\" ${attr(\"name\", question.entryId)} ${attr(\"value\", String(value))}${requiredAttr}> ${value}</label>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"grid\":\n case \"checkbox_grid\":\n return `${label}\\n${renderGrid(question, question.type === \"grid\" ? requiredAttr : \"\")}`;\n case \"date\": {\n const inputs: string[] = [];\n if (question.date?.includeYear !== false) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_year`)} placeholder=\"Year\"${requiredAttr}>`,\n );\n }\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_month`)} placeholder=\"Month\"${requiredAttr}>`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_day`)} placeholder=\"Day\"${requiredAttr}>`,\n );\n if (question.date?.includeTime) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\"${requiredAttr}>`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\"${requiredAttr}>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"time\": {\n const inputs = [\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\"${requiredAttr}>`,\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\"${requiredAttr}>`,\n ];\n // Google only reads _second for a Duration question.\n if (question.time?.isDuration) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_second`)} placeholder=\"Second\"${requiredAttr}>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"file_upload\":\n return `${label}\\n<!-- file_upload is not supported by the Forms API without sign-in; no input rendered -->`;\n default:\n return label;\n }\n};\n\n/**\n * Emits a plain HTML `<form>` (no JS framework required) posting directly to\n * the form's `formResponse` endpoint, with `name` attributes following every\n * `entry.N`(`_year`/`_month`/`_day`/`_hour`/`_minute`, `__other_option__`,\n * `.other_option_response`) encoding rule in\n * `docs/research/google-forms-internals.md` §3.\n */\nexport const generateHtmlForm = (schema: FormSchema): string => {\n const { formResponse } = formUrls(schema.formId || \"REPLACE_WITH_FORM_ID\");\n const body = schema.questions.map(renderQuestion).join(\"\\n\\n\");\n\n const hiddenFields: string[] = [];\n if (schema.multiPage) {\n const pageCount = Math.max(schema.sections.length, 1);\n const pageHistory = Array.from({ length: pageCount }, (_, i) => {\n return i;\n }).join(\",\");\n hiddenFields.push(\n `<input type=\"hidden\" ${attr(\"name\", \"fbzx\")} ${attr(\"value\", PLACEHOLDER_FBZX)}>`,\n `<input type=\"hidden\" ${attr(\"name\", \"pageHistory\")} ${attr(\"value\", pageHistory)}>`,\n `<input type=\"hidden\" ${attr(\"name\", \"partialResponse\")} ${attr(\"value\", JSON.stringify([null, null, PLACEHOLDER_FBZX]))}>`,\n );\n }\n\n const formDescription = schema.description\n ? `<p>${escapeHtml(schema.description)}</p>\\n`\n : \"\";\n\n return `<!-- ${escapeHtml(schema.title)} -->\n<form action=\"${escapeHtml(formResponse)}\" method=\"POST\">\n${hiddenFields.join(\"\\n\")}\n${formDescription}${body}\n\n<button type=\"submit\">Submit</button>\n</form>\n`;\n};\n","/** Splits a free-form string (a form title, question label, etc.) into word tokens. */\nconst splitWords = (input: string): string[] => {\n return (\n input\n // Insert boundaries between camelCase / acronym transitions.\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n // Replace anything that isn't a letter/digit with a space.\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter((word) => {\n return word.length > 0;\n })\n );\n};\n\n/** Converts a free-form string into `PascalCase`, safe for a TS identifier/type name. */\nexport const toPascalCase = (input: string): string => {\n const words = splitWords(input);\n const pascal = words\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n if (pascal.length === 0) return \"Form\";\n return /^[0-9]/.test(pascal) ? `_${pascal}` : pascal;\n};\n\n/**\n * JS/TS reserved words (keywords, future reserved words, and strict-mode\n * reserved words) that are not valid as a binding identifier. Guarded here\n * because `toIdentifier` output is emitted as a real variable/prop name in\n * generated code.\n */\nconst RESERVED_WORDS = new Set([\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"export\",\n \"extends\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"enum\",\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n \"await\",\n \"null\",\n \"true\",\n \"false\",\n \"eval\",\n \"arguments\",\n]);\n\n/**\n * Converts a free-form string into a valid, sanitized JS identifier\n * (`camelCase`, no leading digit, and no collision with a JS/TS reserved\n * word — a trailing underscore is appended in that case).\n */\nexport const toIdentifier = (input: string): string => {\n const words = splitWords(input);\n if (words.length === 0) return \"Form\";\n const [first, ...rest] = words;\n const camel =\n first!.toLowerCase() +\n rest\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n const prefixed = /^[0-9]/.test(camel) ? `_${camel}` : camel;\n return RESERVED_WORDS.has(prefixed) ? `${prefixed}_` : prefixed;\n};\n","import type {\n FormSchema,\n GenerateReactOptions,\n Question,\n Section,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type GenerateReactComponentOptions = GenerateReactOptions;\n\nconst JSX_TEXT_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \"{\": \"{\",\n \"}\": \"}\",\n};\n\n/** Escapes text so it's safe to drop directly into JSX element children. */\nconst jsxEscape = (value: string): string => {\n return value.replace(/[&<>{}]/g, (ch) => {\n return JSX_TEXT_ESCAPES[ch]!;\n });\n};\n\nconst q = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst renderShortText = (question: Question, multiline: boolean): string => {\n const tag = multiline ? \"textarea\" : \"input\";\n const typeAttr = multiline ? \"\" : ' type=\"text\"';\n return `<${tag}${typeAttr} {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n } />`;\n};\n\nconst renderRadioGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={typeof form.values[${entryId}] === \"object\"}\n onChange={() => form.setValue(${entryId}, { other: \"\" })}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => form.setValue(${entryId}, { other: e.target.value })}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n value={${value}}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div role=\"radiogroup\">\n${options}\n </div>`;\n};\n\nconst renderSelect = (question: Question): string => {\n const options = (question.options ?? [])\n .map((option) => {\n return ` <option value={${q(option.value)}}>${jsxEscape(option.value)}</option>`;\n })\n .join(\"\\n\");\n return `<select {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n }>\n <option value=\"\">Select…</option>\n${options}\n </select>`;\n};\n\nconst renderCheckboxGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"checkbox\"\n onChange={(e) => {\n const current = form.values[${entryId}];\n const chosen = Array.isArray(current) ? current.filter((v) => typeof v === \"string\") : [];\n form.setValue(${entryId}, e.target.checked ? [...chosen, { other: \"\" }] : chosen);\n }}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => {\n const current = form.values[${entryId}];\n const chosen = Array.isArray(current) ? current.filter((v) => typeof v === \"string\") : [];\n form.setValue(${entryId}, [...chosen, { other: e.target.value }]);\n }}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div>\n${options}\n </div>`;\n};\n\nconst renderLinearScale = (question: Question): string => {\n const entryId = q(question.entryId);\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const items: string[] = [];\n for (let value = min; value <= max; value++) {\n items.push(` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${q(String(value))}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${value}\n </label>`);\n }\n const lowLabel = question.scale?.lowLabel;\n const highLabel = question.scale?.highLabel;\n return `<div role=\"radiogroup\">\n ${lowLabel ? `<span>${jsxEscape(lowLabel)}</span>` : \"\"}\n${items.join(\"\\n\")}\n ${highLabel ? `<span>${jsxEscape(highLabel)}</span>` : \"\"}\n </div>`;\n};\n\nconst renderGrid = (question: Question): string => {\n const isCheckbox = question.type === \"checkbox_grid\";\n const columns = question.options ?? [];\n const headerCells = columns\n .map((col) => {\n return `<th key={${q(col.value)}}>${jsxEscape(col.value)}</th>`;\n })\n .join(\"\\n \");\n const rows = (question.rows ?? [])\n .map((row) => {\n const entryId = q(row.entryId);\n const cells = columns\n .map((col) => {\n const value = q(col.value);\n if (isCheckbox) {\n return `<td key={${value}}><input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} /></td>`;\n }\n return `<td key={${value}}>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n </td>`;\n })\n .join(\"\\n \");\n return ` <tr>\n <th>${jsxEscape(row.label)}</th>\n ${cells}\n </tr>`;\n })\n .join(\"\\n\");\n return `<table>\n <thead>\n <tr>\n <th />\n ${headerCells}\n </tr>\n </thead>\n <tbody>\n${rows}\n </tbody>\n </table>`;\n};\n\nconst renderDate = (question: Question): string => {\n const entryId = q(question.entryId);\n const includeYear = question.date?.includeYear ?? true;\n const includeTime = question.date?.includeTime ?? false;\n const required = question.required ? \"true\" : \"false\";\n const dateParts = includeYear ? \"[year, month, day]\" : \"[, month, day]\";\n const dateValue = includeYear ? \"year, month, day\" : \"month, day\";\n const isObject = `typeof current === \"object\" && current !== null && !Array.isArray(current)`;\n if (!includeTime) {\n return `<input\n type=\"date\"\n required={${required}}\n onChange={(e) => {\n const ${dateParts} = e.target.value.split(\"-\").map(Number);\n if (month === undefined || day === undefined) return;\n form.setValue(${entryId}, { ${dateValue} });\n }}\n />`;\n }\n const keepYear = includeYear ? \"year: Number(current.year), \" : \"\";\n return `<input\n type=\"date\"\n required={${required}}\n onChange={(e) => {\n const ${dateParts} = e.target.value.split(\"-\").map(Number);\n if (month === undefined || day === undefined) return;\n const current = form.values[${entryId}];\n const time = ${isObject} && \"hour\" in current && \"minute\" in current ? { hour: Number(current.hour), minute: Number(current.minute) } : {};\n form.setValue(${entryId}, { ${dateValue}, ...time });\n }}\n />\n <input\n type=\"time\"\n required={${required}}\n onChange={(e) => {\n const [hour, minute] = e.target.value.split(\":\").map(Number);\n if (hour === undefined || minute === undefined) return;\n const current = form.values[${entryId}];\n const date = ${isObject} && \"month\" in current && \"day\" in current ? { ${keepYear}month: Number(current.month), day: Number(current.day) } : {};\n form.setValue(${entryId}, { ...date, hour, minute });\n }}\n />`;\n};\n\nconst renderDuration = (question: Question, typescript: boolean): string => {\n const entryId = q(question.entryId);\n const required = question.required ? \"true\" : \"false\";\n const isObject = `typeof current === \"object\" && current !== null && !Array.isArray(current)`;\n // Parts stay undefined until typed, so a half-typed duration is rejected by\n // validation instead of submitting zeros; TimeValue needs hour/minute, hence the casts.\n const baseCast = typescript\n ? ' as Partial<Record<\"hour\" | \"minute\" | \"second\", number>>'\n : \"\";\n const valueCast = typescript\n ? \" as { hour: number; minute: number; second?: number }\"\n : \"\";\n const part = (key: string, label: string, max: number): string => {\n return `<input\n type=\"number\"\n min={0}\n max={${max}}\n aria-label=\"${label}\"\n required={${required}}\n onChange={(e) => {\n const current = form.values[${entryId}];\n const base = (${isObject} ? current : {})${baseCast};\n const merged = { ...base, ${key}: e.target.value === \"\" ? undefined : Number(e.target.value) };\n const allEmpty = Object.values(merged).every((v) => v === undefined);\n form.setValue(${entryId}, allEmpty ? undefined : (merged${valueCast}));\n }}\n />`;\n };\n return [\n part(\"hour\", \"Hours\", 23),\n part(\"minute\", \"Minutes\", 59),\n part(\"second\", \"Seconds\", 59),\n ].join(\"\\n \");\n};\n\nconst renderTime = (question: Question, typescript: boolean): string => {\n if (question.time?.isDuration) return renderDuration(question, typescript);\n const entryId = q(question.entryId);\n return `<input\n type=\"time\"\n required={${question.required ? \"true\" : \"false\"}}\n onChange={(e) => {\n const parts = e.target.value.split(\":\").map(Number);\n const [hour, minute] = parts;\n if (hour === undefined || minute === undefined) return;\n form.setValue(${entryId}, { hour, minute });\n }}\n />`;\n};\n\nconst renderQuestion = (question: Question, typescript: boolean): string => {\n const label = `<label>${jsxEscape(question.title)}${\n question.required ? \" *\" : \"\"\n }</label>`;\n const description = question.description\n ? `\\n <p>${jsxEscape(question.description)}</p>`\n : \"\";\n\n let field: string;\n switch (question.type) {\n case \"short_answer\":\n field = renderShortText(question, false);\n break;\n case \"paragraph\":\n field = renderShortText(question, true);\n break;\n case \"multiple_choice\":\n field = renderRadioGroup(question);\n break;\n case \"dropdown\":\n field = renderSelect(question);\n break;\n case \"checkboxes\":\n field = renderCheckboxGroup(question);\n break;\n case \"linear_scale\":\n field = renderLinearScale(question);\n break;\n case \"grid\":\n case \"checkbox_grid\":\n field = renderGrid(question);\n break;\n case \"date\":\n field = renderDate(question);\n break;\n case \"time\":\n field = renderTime(question, typescript);\n break;\n case \"file_upload\":\n field = `<p>\"${jsxEscape(question.title)}\" is a file upload question and is not supported by Google Forms submission without sign-in.</p>`;\n break;\n default:\n field = `<input {...form.register(${q(question.entryId)})} />`;\n }\n\n return ` <div key={${q(question.id)}}>\n ${label}${description}\n ${field}\n </div>`;\n};\n\nconst renderSection = (\n section: Section,\n questionsById: Map<string, Question>,\n typescript: boolean,\n): string => {\n const heading = section.title\n ? ` <h2>${jsxEscape(section.title)}</h2>\\n`\n : \"\";\n const description = section.description\n ? ` <p>${jsxEscape(section.description)}</p>\\n`\n : \"\";\n const body = section.questionIds\n .map((id) => {\n return questionsById.get(id);\n })\n .filter((question): question is Question => {\n return question !== undefined;\n })\n .map((question) => {\n return renderQuestion(question, typescript);\n })\n .join(\"\\n\");\n return `${heading}${description}${body}`;\n};\n\n/**\n * Generates a paste-ready React component wired to `useGoogleForm` from\n * `@ez-gform/react`. Renders every supported question type; `file_upload`\n * questions render a disabled note instead of an input (the Forms API can't\n * accept file uploads without a signed-in session).\n */\nexport const generateReactComponent = (\n schema: FormSchema,\n options: GenerateReactComponentOptions = {},\n): string => {\n const typescript = options.typescript ?? true;\n const name = options.name ?? `${toPascalCase(schema.title)}Form`;\n // fbzx is a per-request token; core generates a fresh one when it is absent.\n const embedded = { ...schema };\n delete embedded.fbzx;\n const schemaJson = JSON.stringify(embedded, null, 2);\n\n const questionsById = new Map(\n schema.questions.map((qn) => {\n return [qn.id, qn];\n }),\n );\n const body =\n schema.sections.length > 1 ||\n (schema.sections[0]?.title && schema.sections[0].title.length > 0)\n ? schema.sections\n .map((section) => {\n return renderSection(section, questionsById, typescript);\n })\n .join(\"\\n\")\n : schema.questions\n .map((question) => {\n return renderQuestion(question, typescript);\n })\n .join(\"\\n\");\n\n const schemaDeclaration = typescript\n ? `${schemaJson} as const satisfies FormSchema`\n : schemaJson;\n const typeImport = typescript\n ? `import type { FormSchema } from \"@ez-gform/react\";\\n`\n : \"\";\n\n return `${typeImport}import { useGoogleForm } from \"@ez-gform/react\";\n\nconst schema = ${schemaDeclaration};\n\nexport function ${name}() {\n const form = useGoogleForm({ formId: schema.formId, schema });\n\n return (\n <form\n onSubmit={form.submit}\n >\n <h1>${jsxEscape(schema.title)}</h1>\n ${schema.description ? `<p>${jsxEscape(schema.description)}</p>` : \"\"}\n${body}\n\n <div>Status: {form.status}</div>\n {form.errors && form.errors.length > 0 && (\n <ul>\n {form.errors.map((error, index) => (\n <li key={index}>{error.message}</li>\n ))}\n </ul>\n )}\n\n <button type=\"submit\" disabled={form.status === \"submitting\"}>\n Submit\n </button>\n </form>\n );\n}\n`;\n};\n","import type {\n FormSchema,\n GenerateTypesOptions,\n Question,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type { GenerateTypesOptions } from \"@ez-gform/types\";\n\nconst quote = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst hasOtherOption = (question: Question): boolean => {\n return (\n question.options?.some((option) => {\n return option.isOther;\n }) ?? false\n );\n};\n\n/** Returns the TS type text for a single question's value, or `undefined` to omit it entirely. */\nconst fieldType = (question: Question): string | undefined => {\n switch (question.type) {\n case \"short_answer\":\n case \"paragraph\":\n return \"string\";\n case \"multiple_choice\":\n case \"dropdown\":\n return hasOtherOption(question) ? \"string | { other: string }\" : \"string\";\n case \"checkboxes\":\n return hasOtherOption(question)\n ? \"(string | { other: string })[]\"\n : \"string[]\";\n case \"linear_scale\":\n return \"number\";\n case \"date\":\n return \"DateValue\";\n case \"time\":\n return \"TimeValue\";\n case \"grid\":\n case \"checkbox_grid\": {\n const rowIds = (question.rows ?? []).map((row) => {\n return quote(row.entryId);\n });\n const keyType = rowIds.length > 0 ? rowIds.join(\" | \") : \"string\";\n return `Record<${keyType}, string | string[]>`;\n }\n case \"file_upload\":\n // File uploads can't be submitted through the Forms API without\n // sign-in; there is no meaningful value type for them.\n return undefined;\n default:\n return \"string\";\n }\n};\n\n/**\n * Emits `const <Name>Schema = {...} as const satisfies FormSchema;` and a\n * companion `type <Name>Values = { \"entry.N\"?: ...; ... }` mapping every\n * question's entry id to its precise `FieldValue` subtype.\n */\nexport const generateTypes = (\n schema: FormSchema,\n options: GenerateTypesOptions = {},\n): string => {\n const name = options.name ?? toPascalCase(schema.title);\n // fbzx is a per-request token; core generates a fresh one when absent.\n const { fbzx: _, ...embeddedSchema } = schema;\n const schemaJson = JSON.stringify(embeddedSchema, null, 2);\n\n const usesDate = schema.questions.some((q) => {\n return q.type === \"date\";\n });\n const usesTime = schema.questions.some((q) => {\n return q.type === \"time\";\n });\n const extraTypeImports = [\n usesDate ? \"DateValue\" : undefined,\n usesTime ? \"TimeValue\" : undefined,\n ].filter((t): t is string => {\n return t !== undefined;\n });\n\n const fields: string[] = [];\n for (const question of schema.questions) {\n const type = fieldType(question);\n if (type === undefined) continue;\n const optional = question.required ? \"\" : \"?\";\n fields.push(` ${quote(question.entryId)}${optional}: ${type};`);\n }\n\n const importLine = [\"FormSchema\", ...extraTypeImports].sort().join(\", \");\n\n return `import type { ${importLine} } from \"@ez-gform/core\";\n\nexport const ${name}Schema = ${schemaJson} as const satisfies FormSchema;\n\nexport type ${name}Values = {\n${fields.join(\"\\n\")}\n};\n`;\n};\n","import type { FormSchema, GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\nexport type { GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\n/** Serializes a `FormSchema` back to JSON text. Round-trips structurally with `JSON.parse`. */\nexport const generateSchemaJson = (\n schema: FormSchema,\n options: GenerateSchemaJsonOptions = {},\n): string => {\n const pretty = options.pretty ?? true;\n return pretty ? JSON.stringify(schema, null, 2) : JSON.stringify(schema);\n};\n"],"mappings":"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","names":["renderGrid","renderQuestion","q"]}
|
package/dist/index.js
CHANGED
|
@@ -3,28 +3,29 @@ import { formUrls } from "@ez-gform/core";
|
|
|
3
3
|
var escapeHtml = (value) => {
|
|
4
4
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5
5
|
};
|
|
6
|
+
var PLACEHOLDER_FBZX = "-1000000000000000000";
|
|
6
7
|
var attr = (name, value) => {
|
|
7
8
|
return `${name}="${escapeHtml(value)}"`;
|
|
8
9
|
};
|
|
9
|
-
var renderChoiceInputs = (question, inputType) => {
|
|
10
|
+
var renderChoiceInputs = (question, inputType, requiredAttr = "") => {
|
|
10
11
|
const options = question.options ?? [];
|
|
11
12
|
const rows = options.map((option) => {
|
|
12
13
|
if (option.isOther) {
|
|
13
14
|
return [
|
|
14
|
-
`<label><input type="${inputType}" ${attr("name", question.entryId)} value="__other_option__"> Other:</label>`,
|
|
15
|
+
`<label><input type="${inputType}" ${attr("name", question.entryId)} value="__other_option__"${requiredAttr}> Other:</label>`,
|
|
15
16
|
`<input type="text" ${attr("name", `${question.entryId}.other_option_response`)}>`
|
|
16
17
|
].join("\n ");
|
|
17
18
|
}
|
|
18
|
-
return `<label><input type="${inputType}" ${attr("name", question.entryId)} ${attr("value", option.value)}> ${escapeHtml(option.value)}</label>`;
|
|
19
|
+
return `<label><input type="${inputType}" ${attr("name", question.entryId)} ${attr("value", option.value)}${requiredAttr}> ${escapeHtml(option.value)}</label>`;
|
|
19
20
|
});
|
|
20
21
|
return rows.join("\n ");
|
|
21
22
|
};
|
|
22
|
-
var renderGrid = (question) => {
|
|
23
|
+
var renderGrid = (question, requiredAttr) => {
|
|
23
24
|
const inputType = question.type === "checkbox_grid" ? "checkbox" : "radio";
|
|
24
25
|
const columns = question.options ?? [];
|
|
25
26
|
const rows = (question.rows ?? []).map((row) => {
|
|
26
27
|
const cells = columns.map((col) => {
|
|
27
|
-
return `<td><input type="${inputType}" ${attr("name", row.entryId)} ${attr("value", col.value)}></td>`;
|
|
28
|
+
return `<td><input type="${inputType}" ${attr("name", row.entryId)} ${attr("value", col.value)}${requiredAttr}></td>`;
|
|
28
29
|
}).join("");
|
|
29
30
|
return ` <tr><th>${escapeHtml(row.label)}</th>${cells}</tr>`;
|
|
30
31
|
}).join("\n");
|
|
@@ -33,7 +34,9 @@ ${rows}
|
|
|
33
34
|
</table>`;
|
|
34
35
|
};
|
|
35
36
|
var renderQuestion = (question) => {
|
|
36
|
-
const
|
|
37
|
+
const description = question.description ? `
|
|
38
|
+
<p>${escapeHtml(question.description)}</p>` : "";
|
|
39
|
+
const label = `<label>${escapeHtml(question.title)}${question.required ? " *" : ""}</label>${description}`;
|
|
37
40
|
const requiredAttr = question.required ? " required" : "";
|
|
38
41
|
switch (question.type) {
|
|
39
42
|
case "short_answer":
|
|
@@ -44,14 +47,20 @@ var renderQuestion = (question) => {
|
|
|
44
47
|
<textarea ${attr("name", question.entryId)}${requiredAttr}></textarea>`;
|
|
45
48
|
case "multiple_choice":
|
|
46
49
|
return `${label}
|
|
47
|
-
${renderChoiceInputs(question, "radio")}`;
|
|
50
|
+
${renderChoiceInputs(question, "radio", requiredAttr)}`;
|
|
51
|
+
// Checkboxes and checkbox grids cannot express "at least one" natively
|
|
52
|
+
// (`required` would force every box), so only the asterisk marks them.
|
|
48
53
|
case "checkboxes":
|
|
49
54
|
return `${label}
|
|
50
55
|
${renderChoiceInputs(question, "checkbox")}`;
|
|
51
56
|
case "dropdown": {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
const blank = question.required ? [' <option value="" disabled selected>Choose</option>'] : [' <option value="" selected></option>'];
|
|
58
|
+
const options = [
|
|
59
|
+
...blank,
|
|
60
|
+
...(question.options ?? []).map((option) => {
|
|
61
|
+
return ` <option ${attr("value", option.value)}>${escapeHtml(option.value)}</option>`;
|
|
62
|
+
})
|
|
63
|
+
].join("\n");
|
|
55
64
|
return `${label}
|
|
56
65
|
<select ${attr("name", question.entryId)}${requiredAttr}>
|
|
57
66
|
${options}
|
|
@@ -63,7 +72,7 @@ ${options}
|
|
|
63
72
|
const inputs = [];
|
|
64
73
|
for (let value = min; value <= max; value++) {
|
|
65
74
|
inputs.push(
|
|
66
|
-
`<label><input type="radio" ${attr("name", question.entryId)} ${attr("value", String(value))}> ${value}</label>`
|
|
75
|
+
`<label><input type="radio" ${attr("name", question.entryId)} ${attr("value", String(value))}${requiredAttr}> ${value}</label>`
|
|
67
76
|
);
|
|
68
77
|
}
|
|
69
78
|
return `${label}
|
|
@@ -72,35 +81,44 @@ ${inputs.join("\n")}`;
|
|
|
72
81
|
case "grid":
|
|
73
82
|
case "checkbox_grid":
|
|
74
83
|
return `${label}
|
|
75
|
-
${renderGrid(question)}`;
|
|
84
|
+
${renderGrid(question, question.type === "grid" ? requiredAttr : "")}`;
|
|
76
85
|
case "date": {
|
|
77
86
|
const inputs = [];
|
|
78
87
|
if (question.date?.includeYear !== false) {
|
|
79
88
|
inputs.push(
|
|
80
|
-
`<input type="number" ${attr("name", `${question.entryId}_year`)} placeholder="Year">`
|
|
89
|
+
`<input type="number" ${attr("name", `${question.entryId}_year`)} placeholder="Year"${requiredAttr}>`
|
|
81
90
|
);
|
|
82
91
|
}
|
|
83
92
|
inputs.push(
|
|
84
|
-
`<input type="number" ${attr("name", `${question.entryId}_month`)} placeholder="Month">`
|
|
93
|
+
`<input type="number" ${attr("name", `${question.entryId}_month`)} placeholder="Month"${requiredAttr}>`
|
|
85
94
|
);
|
|
86
95
|
inputs.push(
|
|
87
|
-
`<input type="number" ${attr("name", `${question.entryId}_day`)} placeholder="Day">`
|
|
96
|
+
`<input type="number" ${attr("name", `${question.entryId}_day`)} placeholder="Day"${requiredAttr}>`
|
|
88
97
|
);
|
|
89
98
|
if (question.date?.includeTime) {
|
|
90
99
|
inputs.push(
|
|
91
|
-
`<input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour">`
|
|
100
|
+
`<input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour"${requiredAttr}>`
|
|
92
101
|
);
|
|
93
102
|
inputs.push(
|
|
94
|
-
`<input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute">`
|
|
103
|
+
`<input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute"${requiredAttr}>`
|
|
95
104
|
);
|
|
96
105
|
}
|
|
97
106
|
return `${label}
|
|
98
107
|
${inputs.join("\n")}`;
|
|
99
108
|
}
|
|
100
|
-
case "time":
|
|
109
|
+
case "time": {
|
|
110
|
+
const inputs = [
|
|
111
|
+
`<input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour"${requiredAttr}>`,
|
|
112
|
+
`<input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute"${requiredAttr}>`
|
|
113
|
+
];
|
|
114
|
+
if (question.time?.isDuration) {
|
|
115
|
+
inputs.push(
|
|
116
|
+
`<input type="number" ${attr("name", `${question.entryId}_second`)} placeholder="Second"${requiredAttr}>`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
101
119
|
return `${label}
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
${inputs.join("\n")}`;
|
|
121
|
+
}
|
|
104
122
|
case "file_upload":
|
|
105
123
|
return `${label}
|
|
106
124
|
<!-- file_upload is not supported by the Forms API without sign-in; no input rendered -->`;
|
|
@@ -113,14 +131,22 @@ var generateHtmlForm = (schema) => {
|
|
|
113
131
|
const body = schema.questions.map(renderQuestion).join("\n\n");
|
|
114
132
|
const hiddenFields = [];
|
|
115
133
|
if (schema.multiPage) {
|
|
134
|
+
const pageCount = Math.max(schema.sections.length, 1);
|
|
135
|
+
const pageHistory = Array.from({ length: pageCount }, (_, i) => {
|
|
136
|
+
return i;
|
|
137
|
+
}).join(",");
|
|
116
138
|
hiddenFields.push(
|
|
117
|
-
`<input type="hidden" ${attr("name", "fbzx")} ${attr("value",
|
|
139
|
+
`<input type="hidden" ${attr("name", "fbzx")} ${attr("value", PLACEHOLDER_FBZX)}>`,
|
|
140
|
+
`<input type="hidden" ${attr("name", "pageHistory")} ${attr("value", pageHistory)}>`,
|
|
141
|
+
`<input type="hidden" ${attr("name", "partialResponse")} ${attr("value", JSON.stringify([null, null, PLACEHOLDER_FBZX]))}>`
|
|
118
142
|
);
|
|
119
143
|
}
|
|
144
|
+
const formDescription = schema.description ? `<p>${escapeHtml(schema.description)}</p>
|
|
145
|
+
` : "";
|
|
120
146
|
return `<!-- ${escapeHtml(schema.title)} -->
|
|
121
147
|
<form action="${escapeHtml(formResponse)}" method="POST">
|
|
122
148
|
${hiddenFields.join("\n")}
|
|
123
|
-
${body}
|
|
149
|
+
${formDescription}${body}
|
|
124
150
|
|
|
125
151
|
<button type="submit">Submit</button>
|
|
126
152
|
</form>
|
|
@@ -139,7 +165,8 @@ var toPascalCase = (input) => {
|
|
|
139
165
|
const lower = word.toLowerCase();
|
|
140
166
|
return lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
141
167
|
}).join("");
|
|
142
|
-
|
|
168
|
+
if (pascal.length === 0) return "Form";
|
|
169
|
+
return /^[0-9]/.test(pascal) ? `_${pascal}` : pascal;
|
|
143
170
|
};
|
|
144
171
|
var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
145
172
|
"break",
|
|
@@ -187,7 +214,9 @@ var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
|
187
214
|
"await",
|
|
188
215
|
"null",
|
|
189
216
|
"true",
|
|
190
|
-
"false"
|
|
217
|
+
"false",
|
|
218
|
+
"eval",
|
|
219
|
+
"arguments"
|
|
191
220
|
]);
|
|
192
221
|
var toIdentifier = (input) => {
|
|
193
222
|
const words = splitWords(input);
|
|
@@ -217,11 +246,6 @@ var jsxEscape = (value) => {
|
|
|
217
246
|
var q = (value) => {
|
|
218
247
|
return JSON.stringify(value);
|
|
219
248
|
};
|
|
220
|
-
var hasOtherOption = (question) => {
|
|
221
|
-
return question.options?.some((option) => {
|
|
222
|
-
return option.isOther;
|
|
223
|
-
}) ?? false;
|
|
224
|
-
};
|
|
225
249
|
var renderShortText = (question, multiline) => {
|
|
226
250
|
const tag = multiline ? "textarea" : "input";
|
|
227
251
|
const typeAttr = multiline ? "" : ' type="text"';
|
|
@@ -277,12 +301,20 @@ var renderCheckboxGroup = (question) => {
|
|
|
277
301
|
return ` <label>
|
|
278
302
|
<input
|
|
279
303
|
type="checkbox"
|
|
280
|
-
onChange={(e) =>
|
|
304
|
+
onChange={(e) => {
|
|
305
|
+
const current = form.values[${entryId}];
|
|
306
|
+
const chosen = Array.isArray(current) ? current.filter((v) => typeof v === "string") : [];
|
|
307
|
+
form.setValue(${entryId}, e.target.checked ? [...chosen, { other: "" }] : chosen);
|
|
308
|
+
}}
|
|
281
309
|
/>
|
|
282
310
|
Other:
|
|
283
311
|
<input
|
|
284
312
|
type="text"
|
|
285
|
-
onChange={(e) =>
|
|
313
|
+
onChange={(e) => {
|
|
314
|
+
const current = form.values[${entryId}];
|
|
315
|
+
const chosen = Array.isArray(current) ? current.filter((v) => typeof v === "string") : [];
|
|
316
|
+
form.setValue(${entryId}, [...chosen, { other: e.target.value }]);
|
|
317
|
+
}}
|
|
286
318
|
/>
|
|
287
319
|
</label>`;
|
|
288
320
|
}
|
|
@@ -361,18 +393,77 @@ ${rows}
|
|
|
361
393
|
};
|
|
362
394
|
var renderDate = (question) => {
|
|
363
395
|
const entryId = q(question.entryId);
|
|
396
|
+
const includeYear = question.date?.includeYear ?? true;
|
|
397
|
+
const includeTime = question.date?.includeTime ?? false;
|
|
398
|
+
const required = question.required ? "true" : "false";
|
|
399
|
+
const dateParts = includeYear ? "[year, month, day]" : "[, month, day]";
|
|
400
|
+
const dateValue = includeYear ? "year, month, day" : "month, day";
|
|
401
|
+
const isObject = `typeof current === "object" && current !== null && !Array.isArray(current)`;
|
|
402
|
+
if (!includeTime) {
|
|
403
|
+
return `<input
|
|
404
|
+
type="date"
|
|
405
|
+
required={${required}}
|
|
406
|
+
onChange={(e) => {
|
|
407
|
+
const ${dateParts} = e.target.value.split("-").map(Number);
|
|
408
|
+
if (month === undefined || day === undefined) return;
|
|
409
|
+
form.setValue(${entryId}, { ${dateValue} });
|
|
410
|
+
}}
|
|
411
|
+
/>`;
|
|
412
|
+
}
|
|
413
|
+
const keepYear = includeYear ? "year: Number(current.year), " : "";
|
|
364
414
|
return `<input
|
|
365
415
|
type="date"
|
|
366
|
-
required={${
|
|
416
|
+
required={${required}}
|
|
367
417
|
onChange={(e) => {
|
|
368
|
-
const
|
|
369
|
-
const [year, month, day] = parts;
|
|
418
|
+
const ${dateParts} = e.target.value.split("-").map(Number);
|
|
370
419
|
if (month === undefined || day === undefined) return;
|
|
371
|
-
form.
|
|
420
|
+
const current = form.values[${entryId}];
|
|
421
|
+
const time = ${isObject} && "hour" in current && "minute" in current ? { hour: Number(current.hour), minute: Number(current.minute) } : {};
|
|
422
|
+
form.setValue(${entryId}, { ${dateValue}, ...time });
|
|
423
|
+
}}
|
|
424
|
+
/>
|
|
425
|
+
<input
|
|
426
|
+
type="time"
|
|
427
|
+
required={${required}}
|
|
428
|
+
onChange={(e) => {
|
|
429
|
+
const [hour, minute] = e.target.value.split(":").map(Number);
|
|
430
|
+
if (hour === undefined || minute === undefined) return;
|
|
431
|
+
const current = form.values[${entryId}];
|
|
432
|
+
const date = ${isObject} && "month" in current && "day" in current ? { ${keepYear}month: Number(current.month), day: Number(current.day) } : {};
|
|
433
|
+
form.setValue(${entryId}, { ...date, hour, minute });
|
|
372
434
|
}}
|
|
373
435
|
/>`;
|
|
374
436
|
};
|
|
375
|
-
var
|
|
437
|
+
var renderDuration = (question, typescript) => {
|
|
438
|
+
const entryId = q(question.entryId);
|
|
439
|
+
const required = question.required ? "true" : "false";
|
|
440
|
+
const isObject = `typeof current === "object" && current !== null && !Array.isArray(current)`;
|
|
441
|
+
const baseCast = typescript ? ' as Partial<Record<"hour" | "minute" | "second", number>>' : "";
|
|
442
|
+
const valueCast = typescript ? " as { hour: number; minute: number; second?: number }" : "";
|
|
443
|
+
const part = (key, label, max) => {
|
|
444
|
+
return `<input
|
|
445
|
+
type="number"
|
|
446
|
+
min={0}
|
|
447
|
+
max={${max}}
|
|
448
|
+
aria-label="${label}"
|
|
449
|
+
required={${required}}
|
|
450
|
+
onChange={(e) => {
|
|
451
|
+
const current = form.values[${entryId}];
|
|
452
|
+
const base = (${isObject} ? current : {})${baseCast};
|
|
453
|
+
const merged = { ...base, ${key}: e.target.value === "" ? undefined : Number(e.target.value) };
|
|
454
|
+
const allEmpty = Object.values(merged).every((v) => v === undefined);
|
|
455
|
+
form.setValue(${entryId}, allEmpty ? undefined : (merged${valueCast}));
|
|
456
|
+
}}
|
|
457
|
+
/>`;
|
|
458
|
+
};
|
|
459
|
+
return [
|
|
460
|
+
part("hour", "Hours", 23),
|
|
461
|
+
part("minute", "Minutes", 59),
|
|
462
|
+
part("second", "Seconds", 59)
|
|
463
|
+
].join("\n ");
|
|
464
|
+
};
|
|
465
|
+
var renderTime = (question, typescript) => {
|
|
466
|
+
if (question.time?.isDuration) return renderDuration(question, typescript);
|
|
376
467
|
const entryId = q(question.entryId);
|
|
377
468
|
return `<input
|
|
378
469
|
type="time"
|
|
@@ -385,7 +476,7 @@ var renderTime = (question) => {
|
|
|
385
476
|
}}
|
|
386
477
|
/>`;
|
|
387
478
|
};
|
|
388
|
-
var renderQuestion2 = (question) => {
|
|
479
|
+
var renderQuestion2 = (question, typescript) => {
|
|
389
480
|
const label = `<label>${jsxEscape(question.title)}${question.required ? " *" : ""}</label>`;
|
|
390
481
|
const description = question.description ? `
|
|
391
482
|
<p>${jsxEscape(question.description)}</p>` : "";
|
|
@@ -417,7 +508,7 @@ var renderQuestion2 = (question) => {
|
|
|
417
508
|
field = renderDate(question);
|
|
418
509
|
break;
|
|
419
510
|
case "time":
|
|
420
|
-
field = renderTime(question);
|
|
511
|
+
field = renderTime(question, typescript);
|
|
421
512
|
break;
|
|
422
513
|
case "file_upload":
|
|
423
514
|
field = `<p>"${jsxEscape(question.title)}" is a file upload question and is not supported by Google Forms submission without sign-in.</p>`;
|
|
@@ -430,7 +521,7 @@ var renderQuestion2 = (question) => {
|
|
|
430
521
|
${field}
|
|
431
522
|
</div>`;
|
|
432
523
|
};
|
|
433
|
-
var renderSection = (section, questionsById) => {
|
|
524
|
+
var renderSection = (section, questionsById, typescript) => {
|
|
434
525
|
const heading = section.title ? ` <h2>${jsxEscape(section.title)}</h2>
|
|
435
526
|
` : "";
|
|
436
527
|
const description = section.description ? ` <p>${jsxEscape(section.description)}</p>
|
|
@@ -439,27 +530,29 @@ var renderSection = (section, questionsById) => {
|
|
|
439
530
|
return questionsById.get(id);
|
|
440
531
|
}).filter((question) => {
|
|
441
532
|
return question !== void 0;
|
|
442
|
-
}).map(
|
|
533
|
+
}).map((question) => {
|
|
534
|
+
return renderQuestion2(question, typescript);
|
|
535
|
+
}).join("\n");
|
|
443
536
|
return `${heading}${description}${body}`;
|
|
444
537
|
};
|
|
445
|
-
var usesOther = (question) => {
|
|
446
|
-
return hasOtherOption(question) && (question.type === "multiple_choice" || question.type === "checkboxes");
|
|
447
|
-
};
|
|
448
538
|
var generateReactComponent = (schema, options = {}) => {
|
|
449
539
|
const typescript = options.typescript ?? true;
|
|
450
540
|
const name = options.name ?? `${toPascalCase(schema.title)}Form`;
|
|
451
|
-
const
|
|
452
|
-
|
|
541
|
+
const embedded = { ...schema };
|
|
542
|
+
delete embedded.fbzx;
|
|
543
|
+
const schemaJson = JSON.stringify(embedded, null, 2);
|
|
453
544
|
const questionsById = new Map(
|
|
454
545
|
schema.questions.map((qn) => {
|
|
455
546
|
return [qn.id, qn];
|
|
456
547
|
})
|
|
457
548
|
);
|
|
458
549
|
const body = schema.sections.length > 1 || schema.sections[0]?.title && schema.sections[0].title.length > 0 ? schema.sections.map((section) => {
|
|
459
|
-
return renderSection(section, questionsById);
|
|
460
|
-
}).join("\n") : schema.questions.map(
|
|
550
|
+
return renderSection(section, questionsById, typescript);
|
|
551
|
+
}).join("\n") : schema.questions.map((question) => {
|
|
552
|
+
return renderQuestion2(question, typescript);
|
|
553
|
+
}).join("\n");
|
|
461
554
|
const schemaDeclaration = typescript ? `${schemaJson} as const satisfies FormSchema` : schemaJson;
|
|
462
|
-
const typeImport = typescript ? `import type { FormSchema } from "@ez-gform/
|
|
555
|
+
const typeImport = typescript ? `import type { FormSchema } from "@ez-gform/react";
|
|
463
556
|
` : "";
|
|
464
557
|
return `${typeImport}import { useGoogleForm } from "@ez-gform/react";
|
|
465
558
|
|
|
@@ -498,7 +591,7 @@ ${body}
|
|
|
498
591
|
var quote = (value) => {
|
|
499
592
|
return JSON.stringify(value);
|
|
500
593
|
};
|
|
501
|
-
var
|
|
594
|
+
var hasOtherOption = (question) => {
|
|
502
595
|
return question.options?.some((option) => {
|
|
503
596
|
return option.isOther;
|
|
504
597
|
}) ?? false;
|
|
@@ -510,9 +603,9 @@ var fieldType = (question) => {
|
|
|
510
603
|
return "string";
|
|
511
604
|
case "multiple_choice":
|
|
512
605
|
case "dropdown":
|
|
513
|
-
return
|
|
606
|
+
return hasOtherOption(question) ? "string | { other: string }" : "string";
|
|
514
607
|
case "checkboxes":
|
|
515
|
-
return
|
|
608
|
+
return hasOtherOption(question) ? "(string | { other: string })[]" : "string[]";
|
|
516
609
|
case "linear_scale":
|
|
517
610
|
return "number";
|
|
518
611
|
case "date":
|
|
@@ -535,7 +628,8 @@ var fieldType = (question) => {
|
|
|
535
628
|
};
|
|
536
629
|
var generateTypes = (schema, options = {}) => {
|
|
537
630
|
const name = options.name ?? toPascalCase(schema.title);
|
|
538
|
-
const
|
|
631
|
+
const { fbzx: _, ...embeddedSchema } = schema;
|
|
632
|
+
const schemaJson = JSON.stringify(embeddedSchema, null, 2);
|
|
539
633
|
const usesDate = schema.questions.some((q2) => {
|
|
540
634
|
return q2.type === "date";
|
|
541
635
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generate-html.ts","../src/names.ts","../src/generate-react.ts","../src/generate-types.ts","../src/schema-json.ts"],"sourcesContent":["import { formUrls } from \"@ez-gform/core\";\nimport type { FormSchema, Question } from \"@ez-gform/types\";\n\nconst escapeHtml = (value: string): string => {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n};\n\nconst attr = (name: string, value: string): string => {\n return `${name}=\"${escapeHtml(value)}\"`;\n};\n\nconst renderChoiceInputs = (\n question: Question,\n inputType: \"radio\" | \"checkbox\",\n): string => {\n const options = question.options ?? [];\n const rows = options.map((option) => {\n if (option.isOther) {\n return [\n `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} value=\"__other_option__\"> Other:</label>`,\n `<input type=\"text\" ${attr(\"name\", `${question.entryId}.other_option_response`)}>`,\n ].join(\"\\n \");\n }\n return `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} ${attr(\"value\", option.value)}> ${escapeHtml(option.value)}</label>`;\n });\n return rows.join(\"\\n \");\n};\n\nconst renderGrid = (question: Question): string => {\n const inputType = question.type === \"checkbox_grid\" ? \"checkbox\" : \"radio\";\n const columns = question.options ?? [];\n const rows = (question.rows ?? [])\n .map((row) => {\n const cells = columns\n .map((col) => {\n return `<td><input type=\"${inputType}\" ${attr(\"name\", row.entryId)} ${attr(\"value\", col.value)}></td>`;\n })\n .join(\"\");\n return ` <tr><th>${escapeHtml(row.label)}</th>${cells}</tr>`;\n })\n .join(\"\\n\");\n return `<table>\\n${rows}\\n</table>`;\n};\n\nconst renderQuestion = (question: Question): string => {\n const label = `<label>${escapeHtml(question.title)}${question.required ? \" *\" : \"\"}</label>`;\n const requiredAttr = question.required ? \" required\" : \"\";\n\n switch (question.type) {\n case \"short_answer\":\n return `${label}\\n<input type=\"text\" ${attr(\"name\", question.entryId)}${requiredAttr}>`;\n case \"paragraph\":\n return `${label}\\n<textarea ${attr(\"name\", question.entryId)}${requiredAttr}></textarea>`;\n case \"multiple_choice\":\n return `${label}\\n${renderChoiceInputs(question, \"radio\")}`;\n case \"checkboxes\":\n return `${label}\\n${renderChoiceInputs(question, \"checkbox\")}`;\n case \"dropdown\": {\n const options = (question.options ?? [])\n .map((option) => {\n return ` <option ${attr(\"value\", option.value)}>${escapeHtml(option.value)}</option>`;\n })\n .join(\"\\n\");\n return `${label}\\n<select ${attr(\"name\", question.entryId)}${requiredAttr}>\\n${options}\\n</select>`;\n }\n case \"linear_scale\": {\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const inputs: string[] = [];\n for (let value = min; value <= max; value++) {\n inputs.push(\n `<label><input type=\"radio\" ${attr(\"name\", question.entryId)} ${attr(\"value\", String(value))}> ${value}</label>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"grid\":\n case \"checkbox_grid\":\n return `${label}\\n${renderGrid(question)}`;\n case \"date\": {\n const inputs: string[] = [];\n if (question.date?.includeYear !== false) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_year`)} placeholder=\"Year\">`,\n );\n }\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_month`)} placeholder=\"Month\">`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_day`)} placeholder=\"Day\">`,\n );\n if (question.date?.includeTime) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\">`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\">`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"time\":\n return `${label}\\n<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\">\\n<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\">`;\n case \"file_upload\":\n return `${label}\\n<!-- file_upload is not supported by the Forms API without sign-in; no input rendered -->`;\n default:\n return label;\n }\n};\n\n/**\n * Emits a plain HTML `<form>` (no JS framework required) posting directly to\n * the form's `formResponse` endpoint, with `name` attributes following every\n * `entry.N`(`_year`/`_month`/`_day`/`_hour`/`_minute`, `__other_option__`,\n * `.other_option_response`) encoding rule in\n * `docs/research/google-forms-internals.md` §3.\n */\nexport const generateHtmlForm = (schema: FormSchema): string => {\n const { formResponse } = formUrls(schema.formId || \"REPLACE_WITH_FORM_ID\");\n const body = schema.questions.map(renderQuestion).join(\"\\n\\n\");\n\n const hiddenFields: string[] = [];\n if (schema.multiPage) {\n hiddenFields.push(\n `<input type=\"hidden\" ${attr(\"name\", \"fbzx\")} ${attr(\"value\", schema.fbzx ?? \"\")}>`,\n );\n }\n\n return `<!-- ${escapeHtml(schema.title)} -->\n<form action=\"${escapeHtml(formResponse)}\" method=\"POST\">\n${hiddenFields.join(\"\\n\")}\n${body}\n\n<button type=\"submit\">Submit</button>\n</form>\n`;\n};\n","/** Splits a free-form string (a form title, question label, etc.) into word tokens. */\nconst splitWords = (input: string): string[] => {\n return (\n input\n // Insert boundaries between camelCase / acronym transitions.\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n // Replace anything that isn't a letter/digit with a space.\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter((word) => {\n return word.length > 0;\n })\n );\n};\n\n/** Converts a free-form string into `PascalCase`, safe for a TS identifier/type name. */\nexport const toPascalCase = (input: string): string => {\n const words = splitWords(input);\n const pascal = words\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n return pascal.length > 0 ? pascal : \"Form\";\n};\n\n/**\n * JS/TS reserved words (keywords, future reserved words, and strict-mode\n * reserved words) that are not valid as a binding identifier. Guarded here\n * because `toIdentifier` output is emitted as a real variable/prop name in\n * generated code.\n */\nconst RESERVED_WORDS = new Set([\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"export\",\n \"extends\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"enum\",\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n \"await\",\n \"null\",\n \"true\",\n \"false\",\n]);\n\n/**\n * Converts a free-form string into a valid, sanitized JS identifier\n * (`camelCase`, no leading digit, and no collision with a JS/TS reserved\n * word — a trailing underscore is appended in that case).\n */\nexport const toIdentifier = (input: string): string => {\n const words = splitWords(input);\n if (words.length === 0) return \"Form\";\n const [first, ...rest] = words;\n const camel =\n first!.toLowerCase() +\n rest\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n const prefixed = /^[0-9]/.test(camel) ? `_${camel}` : camel;\n return RESERVED_WORDS.has(prefixed) ? `${prefixed}_` : prefixed;\n};\n","import type {\n FormSchema,\n GenerateReactOptions,\n Question,\n Section,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type GenerateReactComponentOptions = GenerateReactOptions;\n\nconst JSX_TEXT_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \"{\": \"{\",\n \"}\": \"}\",\n};\n\n/** Escapes text so it's safe to drop directly into JSX element children. */\nconst jsxEscape = (value: string): string => {\n return value.replace(/[&<>{}]/g, (ch) => {\n return JSX_TEXT_ESCAPES[ch]!;\n });\n};\n\nconst q = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst hasOtherOption = (question: Question): boolean => {\n return (\n question.options?.some((option) => {\n return option.isOther;\n }) ?? false\n );\n};\n\nconst renderShortText = (question: Question, multiline: boolean): string => {\n const tag = multiline ? \"textarea\" : \"input\";\n const typeAttr = multiline ? \"\" : ' type=\"text\"';\n return `<${tag}${typeAttr} {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n } />`;\n};\n\nconst renderRadioGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={typeof form.values[${entryId}] === \"object\"}\n onChange={() => form.setValue(${entryId}, { other: \"\" })}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => form.setValue(${entryId}, { other: e.target.value })}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n value={${value}}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div role=\"radiogroup\">\n${options}\n </div>`;\n};\n\nconst renderSelect = (question: Question): string => {\n const options = (question.options ?? [])\n .map((option) => {\n return ` <option value={${q(option.value)}}>${jsxEscape(option.value)}</option>`;\n })\n .join(\"\\n\");\n return `<select {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n }>\n <option value=\"\">Select…</option>\n${options}\n </select>`;\n};\n\nconst renderCheckboxGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"checkbox\"\n onChange={(e) => form.setValue(${entryId}, e.target.checked ? { other: \"\" } : undefined)}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => form.setValue(${entryId}, { other: e.target.value })}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div>\n${options}\n </div>`;\n};\n\nconst renderLinearScale = (question: Question): string => {\n const entryId = q(question.entryId);\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const items: string[] = [];\n for (let value = min; value <= max; value++) {\n items.push(` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${q(String(value))}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${value}\n </label>`);\n }\n const lowLabel = question.scale?.lowLabel;\n const highLabel = question.scale?.highLabel;\n return `<div role=\"radiogroup\">\n ${lowLabel ? `<span>${jsxEscape(lowLabel)}</span>` : \"\"}\n${items.join(\"\\n\")}\n ${highLabel ? `<span>${jsxEscape(highLabel)}</span>` : \"\"}\n </div>`;\n};\n\nconst renderGrid = (question: Question): string => {\n const isCheckbox = question.type === \"checkbox_grid\";\n const columns = question.options ?? [];\n const headerCells = columns\n .map((col) => {\n return `<th key={${q(col.value)}}>${jsxEscape(col.value)}</th>`;\n })\n .join(\"\\n \");\n const rows = (question.rows ?? [])\n .map((row) => {\n const entryId = q(row.entryId);\n const cells = columns\n .map((col) => {\n const value = q(col.value);\n if (isCheckbox) {\n return `<td key={${value}}><input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} /></td>`;\n }\n return `<td key={${value}}>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n </td>`;\n })\n .join(\"\\n \");\n return ` <tr>\n <th>${jsxEscape(row.label)}</th>\n ${cells}\n </tr>`;\n })\n .join(\"\\n\");\n return `<table>\n <thead>\n <tr>\n <th />\n ${headerCells}\n </tr>\n </thead>\n <tbody>\n${rows}\n </tbody>\n </table>`;\n};\n\nconst renderDate = (question: Question): string => {\n const entryId = q(question.entryId);\n return `<input\n type=\"date\"\n required={${question.required ? \"true\" : \"false\"}}\n onChange={(e) => {\n const parts = e.target.value.split(\"-\").map(Number);\n const [year, month, day] = parts;\n if (month === undefined || day === undefined) return;\n form.setValue(${entryId}, { year, month, day });\n }}\n />`;\n};\n\nconst renderTime = (question: Question): string => {\n const entryId = q(question.entryId);\n return `<input\n type=\"time\"\n required={${question.required ? \"true\" : \"false\"}}\n onChange={(e) => {\n const parts = e.target.value.split(\":\").map(Number);\n const [hour, minute] = parts;\n if (hour === undefined || minute === undefined) return;\n form.setValue(${entryId}, { hour, minute });\n }}\n />`;\n};\n\nconst renderQuestion = (question: Question): string => {\n const label = `<label>${jsxEscape(question.title)}${\n question.required ? \" *\" : \"\"\n }</label>`;\n const description = question.description\n ? `\\n <p>${jsxEscape(question.description)}</p>`\n : \"\";\n\n let field: string;\n switch (question.type) {\n case \"short_answer\":\n field = renderShortText(question, false);\n break;\n case \"paragraph\":\n field = renderShortText(question, true);\n break;\n case \"multiple_choice\":\n field = renderRadioGroup(question);\n break;\n case \"dropdown\":\n field = renderSelect(question);\n break;\n case \"checkboxes\":\n field = renderCheckboxGroup(question);\n break;\n case \"linear_scale\":\n field = renderLinearScale(question);\n break;\n case \"grid\":\n case \"checkbox_grid\":\n field = renderGrid(question);\n break;\n case \"date\":\n field = renderDate(question);\n break;\n case \"time\":\n field = renderTime(question);\n break;\n case \"file_upload\":\n field = `<p>\"${jsxEscape(question.title)}\" is a file upload question and is not supported by Google Forms submission without sign-in.</p>`;\n break;\n default:\n field = `<input {...form.register(${q(question.entryId)})} />`;\n }\n\n return ` <div key={${q(question.id)}}>\n ${label}${description}\n ${field}\n </div>`;\n};\n\nconst renderSection = (\n section: Section,\n questionsById: Map<string, Question>,\n): string => {\n const heading = section.title\n ? ` <h2>${jsxEscape(section.title)}</h2>\\n`\n : \"\";\n const description = section.description\n ? ` <p>${jsxEscape(section.description)}</p>\\n`\n : \"\";\n const body = section.questionIds\n .map((id) => {\n return questionsById.get(id);\n })\n .filter((question): question is Question => {\n return question !== undefined;\n })\n .map(renderQuestion)\n .join(\"\\n\");\n return `${heading}${description}${body}`;\n};\n\nconst usesOther = (question: Question): boolean => {\n return (\n hasOtherOption(question) &&\n (question.type === \"multiple_choice\" || question.type === \"checkboxes\")\n );\n};\n\n/**\n * Generates a paste-ready React component wired to `useGoogleForm` from\n * `@ez-gform/react`. Renders every supported question type; `file_upload`\n * questions render a disabled note instead of an input (the Forms API can't\n * accept file uploads without a signed-in session).\n */\nexport const generateReactComponent = (\n schema: FormSchema,\n options: GenerateReactComponentOptions = {},\n): string => {\n const typescript = options.typescript ?? true;\n const name = options.name ?? `${toPascalCase(schema.title)}Form`;\n const schemaJson = JSON.stringify(schema, null, 2);\n void usesOther;\n\n const questionsById = new Map(\n schema.questions.map((qn) => {\n return [qn.id, qn];\n }),\n );\n const body =\n schema.sections.length > 1 ||\n (schema.sections[0]?.title && schema.sections[0].title.length > 0)\n ? schema.sections\n .map((section) => {\n return renderSection(section, questionsById);\n })\n .join(\"\\n\")\n : schema.questions.map(renderQuestion).join(\"\\n\");\n\n const schemaDeclaration = typescript\n ? `${schemaJson} as const satisfies FormSchema`\n : schemaJson;\n const typeImport = typescript\n ? `import type { FormSchema } from \"@ez-gform/core\";\\n`\n : \"\";\n\n return `${typeImport}import { useGoogleForm } from \"@ez-gform/react\";\n\nconst schema = ${schemaDeclaration};\n\nexport function ${name}() {\n const form = useGoogleForm({ formId: schema.formId, schema });\n\n return (\n <form\n onSubmit={form.submit}\n >\n <h1>${jsxEscape(schema.title)}</h1>\n ${schema.description ? `<p>${jsxEscape(schema.description)}</p>` : \"\"}\n${body}\n\n <div>Status: {form.status}</div>\n {form.errors && form.errors.length > 0 && (\n <ul>\n {form.errors.map((error, index) => (\n <li key={index}>{error.message}</li>\n ))}\n </ul>\n )}\n\n <button type=\"submit\" disabled={form.status === \"submitting\"}>\n Submit\n </button>\n </form>\n );\n}\n`;\n};\n","import type {\n FormSchema,\n GenerateTypesOptions,\n Question,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type { GenerateTypesOptions } from \"@ez-gform/types\";\n\nconst quote = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst hasOtherOption = (question: Question): boolean => {\n return (\n question.options?.some((option) => {\n return option.isOther;\n }) ?? false\n );\n};\n\n/** Returns the TS type text for a single question's value, or `undefined` to omit it entirely. */\nconst fieldType = (question: Question): string | undefined => {\n switch (question.type) {\n case \"short_answer\":\n case \"paragraph\":\n return \"string\";\n case \"multiple_choice\":\n case \"dropdown\":\n return hasOtherOption(question) ? \"string | { other: string }\" : \"string\";\n case \"checkboxes\":\n return hasOtherOption(question)\n ? \"(string | { other: string })[]\"\n : \"string[]\";\n case \"linear_scale\":\n return \"number\";\n case \"date\":\n return \"DateValue\";\n case \"time\":\n return \"TimeValue\";\n case \"grid\":\n case \"checkbox_grid\": {\n const rowIds = (question.rows ?? []).map((row) => {\n return quote(row.entryId);\n });\n const keyType = rowIds.length > 0 ? rowIds.join(\" | \") : \"string\";\n return `Record<${keyType}, string | string[]>`;\n }\n case \"file_upload\":\n // File uploads can't be submitted through the Forms API without\n // sign-in; there is no meaningful value type for them.\n return undefined;\n default:\n return \"string\";\n }\n};\n\n/**\n * Emits `const <Name>Schema = {...} as const satisfies FormSchema;` and a\n * companion `type <Name>Values = { \"entry.N\"?: ...; ... }` mapping every\n * question's entry id to its precise `FieldValue` subtype.\n */\nexport const generateTypes = (\n schema: FormSchema,\n options: GenerateTypesOptions = {},\n): string => {\n const name = options.name ?? toPascalCase(schema.title);\n const schemaJson = JSON.stringify(schema, null, 2);\n\n const usesDate = schema.questions.some((q) => {\n return q.type === \"date\";\n });\n const usesTime = schema.questions.some((q) => {\n return q.type === \"time\";\n });\n const extraTypeImports = [\n usesDate ? \"DateValue\" : undefined,\n usesTime ? \"TimeValue\" : undefined,\n ].filter((t): t is string => {\n return t !== undefined;\n });\n\n const fields: string[] = [];\n for (const question of schema.questions) {\n const type = fieldType(question);\n if (type === undefined) continue;\n const optional = question.required ? \"\" : \"?\";\n fields.push(` ${quote(question.entryId)}${optional}: ${type};`);\n }\n\n const importLine = [\"FormSchema\", ...extraTypeImports].sort().join(\", \");\n\n return `import type { ${importLine} } from \"@ez-gform/core\";\n\nexport const ${name}Schema = ${schemaJson} as const satisfies FormSchema;\n\nexport type ${name}Values = {\n${fields.join(\"\\n\")}\n};\n`;\n};\n","import type { FormSchema, GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\nexport type { GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\n/** Serializes a `FormSchema` back to JSON text. Round-trips structurally with `JSON.parse`. */\nexport const generateSchemaJson = (\n schema: FormSchema,\n options: GenerateSchemaJsonOptions = {},\n): string => {\n const pretty = options.pretty ?? true;\n return pretty ? JSON.stringify(schema, null, 2) : JSON.stringify(schema);\n};\n"],"mappings":";AAAA,SAAS,gBAAgB;AAGzB,IAAM,aAAa,CAAC,UAA0B;AAC5C,SAAO,MACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ;AAC3B;AAEA,IAAM,OAAO,CAAC,MAAc,UAA0B;AACpD,SAAO,GAAG,IAAI,KAAK,WAAW,KAAK,CAAC;AACtC;AAEA,IAAM,qBAAqB,CACzB,UACA,cACW;AACX,QAAM,UAAU,SAAS,WAAW,CAAC;AACrC,QAAM,OAAO,QAAQ,IAAI,CAAC,WAAW;AACnC,QAAI,OAAO,SAAS;AAClB,aAAO;AAAA,QACL,uBAAuB,SAAS,KAAK,KAAK,QAAQ,SAAS,OAAO,CAAC;AAAA,QACnE,sBAAsB,KAAK,QAAQ,GAAG,SAAS,OAAO,wBAAwB,CAAC;AAAA,MACjF,EAAE,KAAK,QAAQ;AAAA,IACjB;AACA,WAAO,uBAAuB,SAAS,KAAK,KAAK,QAAQ,SAAS,OAAO,CAAC,IAAI,KAAK,SAAS,OAAO,KAAK,CAAC,KAAK,WAAW,OAAO,KAAK,CAAC;AAAA,EACxI,CAAC;AACD,SAAO,KAAK,KAAK,QAAQ;AAC3B;AAEA,IAAM,aAAa,CAAC,aAA+B;AACjD,QAAM,YAAY,SAAS,SAAS,kBAAkB,aAAa;AACnE,QAAM,UAAU,SAAS,WAAW,CAAC;AACrC,QAAM,QAAQ,SAAS,QAAQ,CAAC,GAC7B,IAAI,CAAC,QAAQ;AACZ,UAAM,QAAQ,QACX,IAAI,CAAC,QAAQ;AACZ,aAAO,oBAAoB,SAAS,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC;AAAA,IAChG,CAAC,EACA,KAAK,EAAE;AACV,WAAO,aAAa,WAAW,IAAI,KAAK,CAAC,QAAQ,KAAK;AAAA,EACxD,CAAC,EACA,KAAK,IAAI;AACZ,SAAO;AAAA,EAAY,IAAI;AAAA;AACzB;AAEA,IAAM,iBAAiB,CAAC,aAA+B;AACrD,QAAM,QAAQ,UAAU,WAAW,SAAS,KAAK,CAAC,GAAG,SAAS,WAAW,OAAO,EAAE;AAClF,QAAM,eAAe,SAAS,WAAW,cAAc;AAEvD,UAAQ,SAAS,MAAM;AAAA,IACrB,KAAK;AACH,aAAO,GAAG,KAAK;AAAA,qBAAwB,KAAK,QAAQ,SAAS,OAAO,CAAC,GAAG,YAAY;AAAA,IACtF,KAAK;AACH,aAAO,GAAG,KAAK;AAAA,YAAe,KAAK,QAAQ,SAAS,OAAO,CAAC,GAAG,YAAY;AAAA,IAC7E,KAAK;AACH,aAAO,GAAG,KAAK;AAAA,EAAK,mBAAmB,UAAU,OAAO,CAAC;AAAA,IAC3D,KAAK;AACH,aAAO,GAAG,KAAK;AAAA,EAAK,mBAAmB,UAAU,UAAU,CAAC;AAAA,IAC9D,KAAK,YAAY;AACf,YAAM,WAAW,SAAS,WAAW,CAAC,GACnC,IAAI,CAAC,WAAW;AACf,eAAO,aAAa,KAAK,SAAS,OAAO,KAAK,CAAC,IAAI,WAAW,OAAO,KAAK,CAAC;AAAA,MAC7E,CAAC,EACA,KAAK,IAAI;AACZ,aAAO,GAAG,KAAK;AAAA,UAAa,KAAK,QAAQ,SAAS,OAAO,CAAC,GAAG,YAAY;AAAA,EAAM,OAAO;AAAA;AAAA,IACxF;AAAA,IACA,KAAK,gBAAgB;AACnB,YAAM,MAAM,SAAS,OAAO,OAAO;AACnC,YAAM,MAAM,SAAS,OAAO,OAAO;AACnC,YAAM,SAAmB,CAAC;AAC1B,eAAS,QAAQ,KAAK,SAAS,KAAK,SAAS;AAC3C,eAAO;AAAA,UACL,8BAA8B,KAAK,QAAQ,SAAS,OAAO,CAAC,IAAI,KAAK,SAAS,OAAO,KAAK,CAAC,CAAC,KAAK,KAAK;AAAA,QACxG;AAAA,MACF;AACA,aAAO,GAAG,KAAK;AAAA,EAAK,OAAO,KAAK,IAAI,CAAC;AAAA,IACvC;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AACH,aAAO,GAAG,KAAK;AAAA,EAAK,WAAW,QAAQ,CAAC;AAAA,IAC1C,KAAK,QAAQ;AACX,YAAM,SAAmB,CAAC;AAC1B,UAAI,SAAS,MAAM,gBAAgB,OAAO;AACxC,eAAO;AAAA,UACL,wBAAwB,KAAK,QAAQ,GAAG,SAAS,OAAO,OAAO,CAAC;AAAA,QAClE;AAAA,MACF;AACA,aAAO;AAAA,QACL,wBAAwB,KAAK,QAAQ,GAAG,SAAS,OAAO,QAAQ,CAAC;AAAA,MACnE;AACA,aAAO;AAAA,QACL,wBAAwB,KAAK,QAAQ,GAAG,SAAS,OAAO,MAAM,CAAC;AAAA,MACjE;AACA,UAAI,SAAS,MAAM,aAAa;AAC9B,eAAO;AAAA,UACL,wBAAwB,KAAK,QAAQ,GAAG,SAAS,OAAO,OAAO,CAAC;AAAA,QAClE;AACA,eAAO;AAAA,UACL,wBAAwB,KAAK,QAAQ,GAAG,SAAS,OAAO,SAAS,CAAC;AAAA,QACpE;AAAA,MACF;AACA,aAAO,GAAG,KAAK;AAAA,EAAK,OAAO,KAAK,IAAI,CAAC;AAAA,IACvC;AAAA,IACA,KAAK;AACH,aAAO,GAAG,KAAK;AAAA,uBAA0B,KAAK,QAAQ,GAAG,SAAS,OAAO,OAAO,CAAC;AAAA,uBAA8C,KAAK,QAAQ,GAAG,SAAS,OAAO,SAAS,CAAC;AAAA,IAC3K,KAAK;AACH,aAAO,GAAG,KAAK;AAAA;AAAA,IACjB;AACE,aAAO;AAAA,EACX;AACF;AASO,IAAM,mBAAmB,CAAC,WAA+B;AAC9D,QAAM,EAAE,aAAa,IAAI,SAAS,OAAO,UAAU,sBAAsB;AACzE,QAAM,OAAO,OAAO,UAAU,IAAI,cAAc,EAAE,KAAK,MAAM;AAE7D,QAAM,eAAyB,CAAC;AAChC,MAAI,OAAO,WAAW;AACpB,iBAAa;AAAA,MACX,wBAAwB,KAAK,QAAQ,MAAM,CAAC,IAAI,KAAK,SAAS,OAAO,QAAQ,EAAE,CAAC;AAAA,IAClF;AAAA,EACF;AAEA,SAAO,QAAQ,WAAW,OAAO,KAAK,CAAC;AAAA,gBACzB,WAAW,YAAY,CAAC;AAAA,EACtC,aAAa,KAAK,IAAI,CAAC;AAAA,EACvB,IAAI;AAAA;AAAA;AAAA;AAAA;AAKN;;;AC5IA,IAAM,aAAa,CAAC,UAA4B;AAC9C,SACE,MAEG,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,yBAAyB,OAAO,EAExC,QAAQ,kBAAkB,GAAG,EAC7B,KAAK,EACL,MAAM,KAAK,EACX,OAAO,CAAC,SAAS;AAChB,WAAO,KAAK,SAAS;AAAA,EACvB,CAAC;AAEP;AAGO,IAAM,eAAe,CAAC,UAA0B;AACrD,QAAM,QAAQ,WAAW,KAAK;AAC9B,QAAM,SAAS,MACZ,IAAI,CAAC,SAAS;AACb,UAAM,QAAQ,KAAK,YAAY;AAC/B,WAAO,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC;AAAA,EACtD,CAAC,EACA,KAAK,EAAE;AACV,SAAO,OAAO,SAAS,IAAI,SAAS;AACtC;AAQA,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,IAAM,eAAe,CAAC,UAA0B;AACrD,QAAM,QAAQ,WAAW,KAAK;AAC9B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,OAAO,GAAG,IAAI,IAAI;AACzB,QAAM,QACJ,MAAO,YAAY,IACnB,KACG,IAAI,CAAC,SAAS;AACb,UAAM,QAAQ,KAAK,YAAY;AAC/B,WAAO,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC;AAAA,EACtD,CAAC,EACA,KAAK,EAAE;AACZ,QAAM,WAAW,SAAS,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK;AACtD,SAAO,eAAe,IAAI,QAAQ,IAAI,GAAG,QAAQ,MAAM;AACzD;;;AC7FA,IAAM,mBAA2C;AAAA,EAC/C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGA,IAAM,YAAY,CAAC,UAA0B;AAC3C,SAAO,MAAM,QAAQ,YAAY,CAAC,OAAO;AACvC,WAAO,iBAAiB,EAAE;AAAA,EAC5B,CAAC;AACH;AAEA,IAAM,IAAI,CAAC,UAA0B;AACnC,SAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,IAAM,iBAAiB,CAAC,aAAgC;AACtD,SACE,SAAS,SAAS,KAAK,CAAC,WAAW;AACjC,WAAO,OAAO;AAAA,EAChB,CAAC,KAAK;AAEV;AAEA,IAAM,kBAAkB,CAAC,UAAoB,cAA+B;AAC1E,QAAM,MAAM,YAAY,aAAa;AACrC,QAAM,WAAW,YAAY,KAAK;AAClC,SAAO,IAAI,GAAG,GAAG,QAAQ,sBAAsB,EAAE,SAAS,OAAO,CAAC,KAChE,SAAS,WAAW,cAAc,EACpC;AACF;AAEA,IAAM,mBAAmB,CAAC,aAA+B;AACvD,QAAM,UAAU,EAAE,SAAS,OAAO;AAClC,QAAM,WAAW,SAAS,WAAW,CAAC,GACnC,IAAI,CAAC,WAAW;AACf,QAAI,OAAO,SAAS;AAClB,aAAO;AAAA;AAAA;AAAA,kCAGmB,OAAO;AAAA,0CACC,OAAO;AAAA,4CACL,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,6CAKN,OAAO;AAAA;AAAA;AAAA,IAG9C;AACA,UAAM,QAAQ,EAAE,OAAO,KAAK;AAC5B,WAAO;AAAA;AAAA;AAAA,kCAGqB,OAAO;AAAA,qBACpB,KAAK;AAAA,qCACW,OAAO,eAAe,KAAK;AAAA,4CACpB,OAAO,KAAK,KAAK;AAAA;AAAA,YAEjD,UAAU,OAAO,KAAK,CAAC;AAAA;AAAA,EAE/B,CAAC,EACA,KAAK,IAAI;AACZ,SAAO;AAAA,EACP,OAAO;AAAA;AAET;AAEA,IAAM,eAAe,CAAC,aAA+B;AACnD,QAAM,WAAW,SAAS,WAAW,CAAC,GACnC,IAAI,CAAC,WAAW;AACf,WAAO,4BAA4B,EAAE,OAAO,KAAK,CAAC,KAAK,UAAU,OAAO,KAAK,CAAC;AAAA,EAChF,CAAC,EACA,KAAK,IAAI;AACZ,SAAO,6BAA6B,EAAE,SAAS,OAAO,CAAC,KACrD,SAAS,WAAW,cAAc,EACpC;AAAA;AAAA,EAEA,OAAO;AAAA;AAET;AAEA,IAAM,sBAAsB,CAAC,aAA+B;AAC1D,QAAM,UAAU,EAAE,SAAS,OAAO;AAClC,QAAM,WAAW,SAAS,WAAW,CAAC,GACnC,IAAI,CAAC,WAAW;AACf,QAAI,OAAO,SAAS;AAClB,aAAO;AAAA;AAAA;AAAA,6CAG8B,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,6CAKP,OAAO;AAAA;AAAA;AAAA,IAG9C;AACA,UAAM,QAAQ,EAAE,OAAO,KAAK;AAC5B,WAAO;AAAA,6DACgD,OAAO,KAAK,KAAK;AAAA,YAClE,UAAU,OAAO,KAAK,CAAC;AAAA;AAAA,EAE/B,CAAC,EACA,KAAK,IAAI;AACZ,SAAO;AAAA,EACP,OAAO;AAAA;AAET;AAEA,IAAM,oBAAoB,CAAC,aAA+B;AACxD,QAAM,UAAU,EAAE,SAAS,OAAO;AAClC,QAAM,MAAM,SAAS,OAAO,OAAO;AACnC,QAAM,MAAM,SAAS,OAAO,OAAO;AACnC,QAAM,QAAkB,CAAC;AACzB,WAAS,QAAQ,KAAK,SAAS,KAAK,SAAS;AAC3C,UAAM,KAAK;AAAA;AAAA;AAAA,kCAGmB,OAAO;AAAA,qCACJ,OAAO,eAAe,EAAE,OAAO,KAAK,CAAC,CAAC;AAAA,4CAC/B,OAAO,KAAK,KAAK;AAAA;AAAA,YAEjD,KAAK;AAAA,iBACA;AAAA,EACf;AACA,QAAM,WAAW,SAAS,OAAO;AACjC,QAAM,YAAY,SAAS,OAAO;AAClC,SAAO;AAAA,UACC,WAAW,SAAS,UAAU,QAAQ,CAAC,YAAY,EAAE;AAAA,EAC7D,MAAM,KAAK,IAAI,CAAC;AAAA,UACR,YAAY,SAAS,UAAU,SAAS,CAAC,YAAY,EAAE;AAAA;AAEjE;AAEA,IAAMA,cAAa,CAAC,aAA+B;AACjD,QAAM,aAAa,SAAS,SAAS;AACrC,QAAM,UAAU,SAAS,WAAW,CAAC;AACrC,QAAM,cAAc,QACjB,IAAI,CAAC,QAAQ;AACZ,WAAO,YAAY,EAAE,IAAI,KAAK,CAAC,KAAK,UAAU,IAAI,KAAK,CAAC;AAAA,EAC1D,CAAC,EACA,KAAK,kBAAkB;AAC1B,QAAM,QAAQ,SAAS,QAAQ,CAAC,GAC7B,IAAI,CAAC,QAAQ;AACZ,UAAM,UAAU,EAAE,IAAI,OAAO;AAC7B,UAAM,QAAQ,QACX,IAAI,CAAC,QAAQ;AACZ,YAAM,QAAQ,EAAE,IAAI,KAAK;AACzB,UAAI,YAAY;AACd,eAAO,YAAY,KAAK,sDAAsD,OAAO,KAAK,KAAK;AAAA,MACjG;AACA,aAAO,YAAY,KAAK;AAAA;AAAA;AAAA,wCAGM,OAAO;AAAA,2CACJ,OAAO,eAAe,KAAK;AAAA,kDACpB,OAAO,KAAK,KAAK;AAAA;AAAA;AAAA,IAG3D,CAAC,EACA,KAAK,kBAAkB;AAC1B,WAAO;AAAA,kBACK,UAAU,IAAI,KAAK,CAAC;AAAA,gBACtB,KAAK;AAAA;AAAA,EAEjB,CAAC,EACA,KAAK,IAAI;AACZ,SAAO;AAAA;AAAA;AAAA;AAAA,gBAIO,WAAW;AAAA;AAAA;AAAA;AAAA,EAIzB,IAAI;AAAA;AAAA;AAGN;AAEA,IAAM,aAAa,CAAC,aAA+B;AACjD,QAAM,UAAU,EAAE,SAAS,OAAO;AAClC,SAAO;AAAA;AAAA,oBAEW,SAAS,WAAW,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,0BAK9B,OAAO;AAAA;AAAA;AAGjC;AAEA,IAAM,aAAa,CAAC,aAA+B;AACjD,QAAM,UAAU,EAAE,SAAS,OAAO;AAClC,SAAO;AAAA;AAAA,oBAEW,SAAS,WAAW,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,0BAK9B,OAAO;AAAA;AAAA;AAGjC;AAEA,IAAMC,kBAAiB,CAAC,aAA+B;AACrD,QAAM,QAAQ,UAAU,UAAU,SAAS,KAAK,CAAC,GAC/C,SAAS,WAAW,OAAO,EAC7B;AACA,QAAM,cAAc,SAAS,cACzB;AAAA,WAAc,UAAU,SAAS,WAAW,CAAC,SAC7C;AAEJ,MAAI;AACJ,UAAQ,SAAS,MAAM;AAAA,IACrB,KAAK;AACH,cAAQ,gBAAgB,UAAU,KAAK;AACvC;AAAA,IACF,KAAK;AACH,cAAQ,gBAAgB,UAAU,IAAI;AACtC;AAAA,IACF,KAAK;AACH,cAAQ,iBAAiB,QAAQ;AACjC;AAAA,IACF,KAAK;AACH,cAAQ,aAAa,QAAQ;AAC7B;AAAA,IACF,KAAK;AACH,cAAQ,oBAAoB,QAAQ;AACpC;AAAA,IACF,KAAK;AACH,cAAQ,kBAAkB,QAAQ;AAClC;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AACH,cAAQD,YAAW,QAAQ;AAC3B;AAAA,IACF,KAAK;AACH,cAAQ,WAAW,QAAQ;AAC3B;AAAA,IACF,KAAK;AACH,cAAQ,WAAW,QAAQ;AAC3B;AAAA,IACF,KAAK;AACH,cAAQ,OAAO,UAAU,SAAS,KAAK,CAAC;AACxC;AAAA,IACF;AACE,cAAQ,4BAA4B,EAAE,SAAS,OAAO,CAAC;AAAA,EAC3D;AAEA,SAAO,iBAAiB,EAAE,SAAS,EAAE,CAAC;AAAA,QAChC,KAAK,GAAG,WAAW;AAAA,QACnB,KAAK;AAAA;AAEb;AAEA,IAAM,gBAAgB,CACpB,SACA,kBACW;AACX,QAAM,UAAU,QAAQ,QACpB,WAAW,UAAU,QAAQ,KAAK,CAAC;AAAA,IACnC;AACJ,QAAM,cAAc,QAAQ,cACxB,UAAU,UAAU,QAAQ,WAAW,CAAC;AAAA,IACxC;AACJ,QAAM,OAAO,QAAQ,YAClB,IAAI,CAAC,OAAO;AACX,WAAO,cAAc,IAAI,EAAE;AAAA,EAC7B,CAAC,EACA,OAAO,CAAC,aAAmC;AAC1C,WAAO,aAAa;AAAA,EACtB,CAAC,EACA,IAAIC,eAAc,EAClB,KAAK,IAAI;AACZ,SAAO,GAAG,OAAO,GAAG,WAAW,GAAG,IAAI;AACxC;AAEA,IAAM,YAAY,CAAC,aAAgC;AACjD,SACE,eAAe,QAAQ,MACtB,SAAS,SAAS,qBAAqB,SAAS,SAAS;AAE9D;AAQO,IAAM,yBAAyB,CACpC,QACA,UAAyC,CAAC,MAC/B;AACX,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,OAAO,QAAQ,QAAQ,GAAG,aAAa,OAAO,KAAK,CAAC;AAC1D,QAAM,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC;AACjD,OAAK;AAEL,QAAM,gBAAgB,IAAI;AAAA,IACxB,OAAO,UAAU,IAAI,CAAC,OAAO;AAC3B,aAAO,CAAC,GAAG,IAAI,EAAE;AAAA,IACnB,CAAC;AAAA,EACH;AACA,QAAM,OACJ,OAAO,SAAS,SAAS,KACxB,OAAO,SAAS,CAAC,GAAG,SAAS,OAAO,SAAS,CAAC,EAAE,MAAM,SAAS,IAC5D,OAAO,SACJ,IAAI,CAAC,YAAY;AAChB,WAAO,cAAc,SAAS,aAAa;AAAA,EAC7C,CAAC,EACA,KAAK,IAAI,IACZ,OAAO,UAAU,IAAIA,eAAc,EAAE,KAAK,IAAI;AAEpD,QAAM,oBAAoB,aACtB,GAAG,UAAU,mCACb;AACJ,QAAM,aAAa,aACf;AAAA,IACA;AAEJ,SAAO,GAAG,UAAU;AAAA;AAAA,iBAEL,iBAAiB;AAAA;AAAA,kBAEhB,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAOV,UAAU,OAAO,KAAK,CAAC;AAAA,QAC3B,OAAO,cAAc,MAAM,UAAU,OAAO,WAAW,CAAC,SAAS,EAAE;AAAA,EACzE,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkBN;;;AC3WA,IAAM,QAAQ,CAAC,UAA0B;AACvC,SAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,IAAMC,kBAAiB,CAAC,aAAgC;AACtD,SACE,SAAS,SAAS,KAAK,CAAC,WAAW;AACjC,WAAO,OAAO;AAAA,EAChB,CAAC,KAAK;AAEV;AAGA,IAAM,YAAY,CAAC,aAA2C;AAC5D,UAAQ,SAAS,MAAM;AAAA,IACrB,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAOA,gBAAe,QAAQ,IAAI,+BAA+B;AAAA,IACnE,KAAK;AACH,aAAOA,gBAAe,QAAQ,IAC1B,mCACA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK,iBAAiB;AACpB,YAAM,UAAU,SAAS,QAAQ,CAAC,GAAG,IAAI,CAAC,QAAQ;AAChD,eAAO,MAAM,IAAI,OAAO;AAAA,MAC1B,CAAC;AACD,YAAM,UAAU,OAAO,SAAS,IAAI,OAAO,KAAK,KAAK,IAAI;AACzD,aAAO,UAAU,OAAO;AAAA,IAC1B;AAAA,IACA,KAAK;AAGH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAOO,IAAM,gBAAgB,CAC3B,QACA,UAAgC,CAAC,MACtB;AACX,QAAM,OAAO,QAAQ,QAAQ,aAAa,OAAO,KAAK;AACtD,QAAM,aAAa,KAAK,UAAU,QAAQ,MAAM,CAAC;AAEjD,QAAM,WAAW,OAAO,UAAU,KAAK,CAACC,OAAM;AAC5C,WAAOA,GAAE,SAAS;AAAA,EACpB,CAAC;AACD,QAAM,WAAW,OAAO,UAAU,KAAK,CAACA,OAAM;AAC5C,WAAOA,GAAE,SAAS;AAAA,EACpB,CAAC;AACD,QAAM,mBAAmB;AAAA,IACvB,WAAW,cAAc;AAAA,IACzB,WAAW,cAAc;AAAA,EAC3B,EAAE,OAAO,CAAC,MAAmB;AAC3B,WAAO,MAAM;AAAA,EACf,CAAC;AAED,QAAM,SAAmB,CAAC;AAC1B,aAAW,YAAY,OAAO,WAAW;AACvC,UAAM,OAAO,UAAU,QAAQ;AAC/B,QAAI,SAAS,OAAW;AACxB,UAAM,WAAW,SAAS,WAAW,KAAK;AAC1C,WAAO,KAAK,KAAK,MAAM,SAAS,OAAO,CAAC,GAAG,QAAQ,KAAK,IAAI,GAAG;AAAA,EACjE;AAEA,QAAM,aAAa,CAAC,cAAc,GAAG,gBAAgB,EAAE,KAAK,EAAE,KAAK,IAAI;AAEvE,SAAO,iBAAiB,UAAU;AAAA;AAAA,eAErB,IAAI,YAAY,UAAU;AAAA;AAAA,cAE3B,IAAI;AAAA,EAChB,OAAO,KAAK,IAAI,CAAC;AAAA;AAAA;AAGnB;;;AC/FO,IAAM,qBAAqB,CAChC,QACA,UAAqC,CAAC,MAC3B;AACX,QAAM,SAAS,QAAQ,UAAU;AACjC,SAAO,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,KAAK,UAAU,MAAM;AACzE;","names":["renderGrid","renderQuestion","hasOtherOption","q"]}
|
|
1
|
+
{"version":3,"sources":["../src/generate-html.ts","../src/names.ts","../src/generate-react.ts","../src/generate-types.ts","../src/schema-json.ts"],"sourcesContent":["import { formUrls } from \"@ez-gform/core\";\nimport type { FormSchema, Question } from \"@ez-gform/types\";\n\nconst escapeHtml = (value: string): string => {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\");\n};\n\n// Placeholder fbzx for multi-page forms. The research doc (section 5) says\n// Google expects fbzx echoed, which is unverified; core itself sends a random\n// token when the schema has none, so any well-formed token is treated as\n// acceptable. A real per-load token would be stale in generated code.\nconst PLACEHOLDER_FBZX = \"-1000000000000000000\";\n\nconst attr = (name: string, value: string): string => {\n return `${name}=\"${escapeHtml(value)}\"`;\n};\n\nconst renderChoiceInputs = (\n question: Question,\n inputType: \"radio\" | \"checkbox\",\n requiredAttr = \"\",\n): string => {\n const options = question.options ?? [];\n const rows = options.map((option) => {\n if (option.isOther) {\n return [\n `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} value=\"__other_option__\"${requiredAttr}> Other:</label>`,\n `<input type=\"text\" ${attr(\"name\", `${question.entryId}.other_option_response`)}>`,\n ].join(\"\\n \");\n }\n return `<label><input type=\"${inputType}\" ${attr(\"name\", question.entryId)} ${attr(\"value\", option.value)}${requiredAttr}> ${escapeHtml(option.value)}</label>`;\n });\n return rows.join(\"\\n \");\n};\n\nconst renderGrid = (question: Question, requiredAttr: string): string => {\n const inputType = question.type === \"checkbox_grid\" ? \"checkbox\" : \"radio\";\n const columns = question.options ?? [];\n const rows = (question.rows ?? [])\n .map((row) => {\n const cells = columns\n .map((col) => {\n return `<td><input type=\"${inputType}\" ${attr(\"name\", row.entryId)} ${attr(\"value\", col.value)}${requiredAttr}></td>`;\n })\n .join(\"\");\n return ` <tr><th>${escapeHtml(row.label)}</th>${cells}</tr>`;\n })\n .join(\"\\n\");\n return `<table>\\n${rows}\\n</table>`;\n};\n\nconst renderQuestion = (question: Question): string => {\n const description = question.description\n ? `\\n<p>${escapeHtml(question.description)}</p>`\n : \"\";\n const label = `<label>${escapeHtml(question.title)}${question.required ? \" *\" : \"\"}</label>${description}`;\n const requiredAttr = question.required ? \" required\" : \"\";\n\n switch (question.type) {\n case \"short_answer\":\n return `${label}\\n<input type=\"text\" ${attr(\"name\", question.entryId)}${requiredAttr}>`;\n case \"paragraph\":\n return `${label}\\n<textarea ${attr(\"name\", question.entryId)}${requiredAttr}></textarea>`;\n case \"multiple_choice\":\n return `${label}\\n${renderChoiceInputs(question, \"radio\", requiredAttr)}`;\n // Checkboxes and checkbox grids cannot express \"at least one\" natively\n // (`required` would force every box), so only the asterisk marks them.\n case \"checkboxes\":\n return `${label}\\n${renderChoiceInputs(question, \"checkbox\")}`;\n case \"dropdown\": {\n // Required: a disabled, selected empty-value placeholder option makes the\n // select report valueMissing until a real option is picked (without it\n // the first choice is always submitted). Optional: a selected blank\n // option posts an empty value (which core never emits and Google\n // ignores) instead of the first choice; not disabled because jsdom still\n // serialises a disabled selected option.\n const blank = question.required\n ? [' <option value=\"\" disabled selected>Choose</option>']\n : [' <option value=\"\" selected></option>'];\n const options = [\n ...blank,\n ...(question.options ?? []).map((option) => {\n return ` <option ${attr(\"value\", option.value)}>${escapeHtml(option.value)}</option>`;\n }),\n ].join(\"\\n\");\n return `${label}\\n<select ${attr(\"name\", question.entryId)}${requiredAttr}>\\n${options}\\n</select>`;\n }\n case \"linear_scale\": {\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const inputs: string[] = [];\n for (let value = min; value <= max; value++) {\n inputs.push(\n `<label><input type=\"radio\" ${attr(\"name\", question.entryId)} ${attr(\"value\", String(value))}${requiredAttr}> ${value}</label>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"grid\":\n case \"checkbox_grid\":\n return `${label}\\n${renderGrid(question, question.type === \"grid\" ? requiredAttr : \"\")}`;\n case \"date\": {\n const inputs: string[] = [];\n if (question.date?.includeYear !== false) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_year`)} placeholder=\"Year\"${requiredAttr}>`,\n );\n }\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_month`)} placeholder=\"Month\"${requiredAttr}>`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_day`)} placeholder=\"Day\"${requiredAttr}>`,\n );\n if (question.date?.includeTime) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\"${requiredAttr}>`,\n );\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\"${requiredAttr}>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"time\": {\n const inputs = [\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_hour`)} placeholder=\"Hour\"${requiredAttr}>`,\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_minute`)} placeholder=\"Minute\"${requiredAttr}>`,\n ];\n // Google only reads _second for a Duration question.\n if (question.time?.isDuration) {\n inputs.push(\n `<input type=\"number\" ${attr(\"name\", `${question.entryId}_second`)} placeholder=\"Second\"${requiredAttr}>`,\n );\n }\n return `${label}\\n${inputs.join(\"\\n\")}`;\n }\n case \"file_upload\":\n return `${label}\\n<!-- file_upload is not supported by the Forms API without sign-in; no input rendered -->`;\n default:\n return label;\n }\n};\n\n/**\n * Emits a plain HTML `<form>` (no JS framework required) posting directly to\n * the form's `formResponse` endpoint, with `name` attributes following every\n * `entry.N`(`_year`/`_month`/`_day`/`_hour`/`_minute`, `__other_option__`,\n * `.other_option_response`) encoding rule in\n * `docs/research/google-forms-internals.md` §3.\n */\nexport const generateHtmlForm = (schema: FormSchema): string => {\n const { formResponse } = formUrls(schema.formId || \"REPLACE_WITH_FORM_ID\");\n const body = schema.questions.map(renderQuestion).join(\"\\n\\n\");\n\n const hiddenFields: string[] = [];\n if (schema.multiPage) {\n const pageCount = Math.max(schema.sections.length, 1);\n const pageHistory = Array.from({ length: pageCount }, (_, i) => {\n return i;\n }).join(\",\");\n hiddenFields.push(\n `<input type=\"hidden\" ${attr(\"name\", \"fbzx\")} ${attr(\"value\", PLACEHOLDER_FBZX)}>`,\n `<input type=\"hidden\" ${attr(\"name\", \"pageHistory\")} ${attr(\"value\", pageHistory)}>`,\n `<input type=\"hidden\" ${attr(\"name\", \"partialResponse\")} ${attr(\"value\", JSON.stringify([null, null, PLACEHOLDER_FBZX]))}>`,\n );\n }\n\n const formDescription = schema.description\n ? `<p>${escapeHtml(schema.description)}</p>\\n`\n : \"\";\n\n return `<!-- ${escapeHtml(schema.title)} -->\n<form action=\"${escapeHtml(formResponse)}\" method=\"POST\">\n${hiddenFields.join(\"\\n\")}\n${formDescription}${body}\n\n<button type=\"submit\">Submit</button>\n</form>\n`;\n};\n","/** Splits a free-form string (a form title, question label, etc.) into word tokens. */\nconst splitWords = (input: string): string[] => {\n return (\n input\n // Insert boundaries between camelCase / acronym transitions.\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1 $2\")\n // Replace anything that isn't a letter/digit with a space.\n .replace(/[^a-zA-Z0-9]+/g, \" \")\n .trim()\n .split(/\\s+/)\n .filter((word) => {\n return word.length > 0;\n })\n );\n};\n\n/** Converts a free-form string into `PascalCase`, safe for a TS identifier/type name. */\nexport const toPascalCase = (input: string): string => {\n const words = splitWords(input);\n const pascal = words\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n if (pascal.length === 0) return \"Form\";\n return /^[0-9]/.test(pascal) ? `_${pascal}` : pascal;\n};\n\n/**\n * JS/TS reserved words (keywords, future reserved words, and strict-mode\n * reserved words) that are not valid as a binding identifier. Guarded here\n * because `toIdentifier` output is emitted as a real variable/prop name in\n * generated code.\n */\nconst RESERVED_WORDS = new Set([\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"export\",\n \"extends\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"enum\",\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n \"await\",\n \"null\",\n \"true\",\n \"false\",\n \"eval\",\n \"arguments\",\n]);\n\n/**\n * Converts a free-form string into a valid, sanitized JS identifier\n * (`camelCase`, no leading digit, and no collision with a JS/TS reserved\n * word — a trailing underscore is appended in that case).\n */\nexport const toIdentifier = (input: string): string => {\n const words = splitWords(input);\n if (words.length === 0) return \"Form\";\n const [first, ...rest] = words;\n const camel =\n first!.toLowerCase() +\n rest\n .map((word) => {\n const lower = word.toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n })\n .join(\"\");\n const prefixed = /^[0-9]/.test(camel) ? `_${camel}` : camel;\n return RESERVED_WORDS.has(prefixed) ? `${prefixed}_` : prefixed;\n};\n","import type {\n FormSchema,\n GenerateReactOptions,\n Question,\n Section,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type GenerateReactComponentOptions = GenerateReactOptions;\n\nconst JSX_TEXT_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n \"{\": \"{\",\n \"}\": \"}\",\n};\n\n/** Escapes text so it's safe to drop directly into JSX element children. */\nconst jsxEscape = (value: string): string => {\n return value.replace(/[&<>{}]/g, (ch) => {\n return JSX_TEXT_ESCAPES[ch]!;\n });\n};\n\nconst q = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst renderShortText = (question: Question, multiline: boolean): string => {\n const tag = multiline ? \"textarea\" : \"input\";\n const typeAttr = multiline ? \"\" : ' type=\"text\"';\n return `<${tag}${typeAttr} {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n } />`;\n};\n\nconst renderRadioGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={typeof form.values[${entryId}] === \"object\"}\n onChange={() => form.setValue(${entryId}, { other: \"\" })}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => form.setValue(${entryId}, { other: e.target.value })}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n value={${value}}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div role=\"radiogroup\">\n${options}\n </div>`;\n};\n\nconst renderSelect = (question: Question): string => {\n const options = (question.options ?? [])\n .map((option) => {\n return ` <option value={${q(option.value)}}>${jsxEscape(option.value)}</option>`;\n })\n .join(\"\\n\");\n return `<select {...form.register(${q(question.entryId)})}${\n question.required ? \" required\" : \"\"\n }>\n <option value=\"\">Select…</option>\n${options}\n </select>`;\n};\n\nconst renderCheckboxGroup = (question: Question): string => {\n const entryId = q(question.entryId);\n const options = (question.options ?? [])\n .map((option) => {\n if (option.isOther) {\n return ` <label>\n <input\n type=\"checkbox\"\n onChange={(e) => {\n const current = form.values[${entryId}];\n const chosen = Array.isArray(current) ? current.filter((v) => typeof v === \"string\") : [];\n form.setValue(${entryId}, e.target.checked ? [...chosen, { other: \"\" }] : chosen);\n }}\n />\n Other:\n <input\n type=\"text\"\n onChange={(e) => {\n const current = form.values[${entryId}];\n const chosen = Array.isArray(current) ? current.filter((v) => typeof v === \"string\") : [];\n form.setValue(${entryId}, [...chosen, { other: e.target.value }]);\n }}\n />\n </label>`;\n }\n const value = q(option.value);\n return ` <label>\n <input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} />\n ${jsxEscape(option.value)}\n </label>`;\n })\n .join(\"\\n\");\n return `<div>\n${options}\n </div>`;\n};\n\nconst renderLinearScale = (question: Question): string => {\n const entryId = q(question.entryId);\n const min = question.scale?.min ?? 1;\n const max = question.scale?.max ?? 5;\n const items: string[] = [];\n for (let value = min; value <= max; value++) {\n items.push(` <label>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${q(String(value))}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n ${value}\n </label>`);\n }\n const lowLabel = question.scale?.lowLabel;\n const highLabel = question.scale?.highLabel;\n return `<div role=\"radiogroup\">\n ${lowLabel ? `<span>${jsxEscape(lowLabel)}</span>` : \"\"}\n${items.join(\"\\n\")}\n ${highLabel ? `<span>${jsxEscape(highLabel)}</span>` : \"\"}\n </div>`;\n};\n\nconst renderGrid = (question: Question): string => {\n const isCheckbox = question.type === \"checkbox_grid\";\n const columns = question.options ?? [];\n const headerCells = columns\n .map((col) => {\n return `<th key={${q(col.value)}}>${jsxEscape(col.value)}</th>`;\n })\n .join(\"\\n \");\n const rows = (question.rows ?? [])\n .map((row) => {\n const entryId = q(row.entryId);\n const cells = columns\n .map((col) => {\n const value = q(col.value);\n if (isCheckbox) {\n return `<td key={${value}}><input type=\"checkbox\" {...form.registerCheckbox(${entryId}, ${value})} /></td>`;\n }\n return `<td key={${value}}>\n <input\n type=\"radio\"\n name={form.register(${entryId}).name}\n checked={form.register(${entryId}).value === ${value}}\n onChange={() => form.setValue(${entryId}, ${value})}\n />\n </td>`;\n })\n .join(\"\\n \");\n return ` <tr>\n <th>${jsxEscape(row.label)}</th>\n ${cells}\n </tr>`;\n })\n .join(\"\\n\");\n return `<table>\n <thead>\n <tr>\n <th />\n ${headerCells}\n </tr>\n </thead>\n <tbody>\n${rows}\n </tbody>\n </table>`;\n};\n\nconst renderDate = (question: Question): string => {\n const entryId = q(question.entryId);\n const includeYear = question.date?.includeYear ?? true;\n const includeTime = question.date?.includeTime ?? false;\n const required = question.required ? \"true\" : \"false\";\n const dateParts = includeYear ? \"[year, month, day]\" : \"[, month, day]\";\n const dateValue = includeYear ? \"year, month, day\" : \"month, day\";\n const isObject = `typeof current === \"object\" && current !== null && !Array.isArray(current)`;\n if (!includeTime) {\n return `<input\n type=\"date\"\n required={${required}}\n onChange={(e) => {\n const ${dateParts} = e.target.value.split(\"-\").map(Number);\n if (month === undefined || day === undefined) return;\n form.setValue(${entryId}, { ${dateValue} });\n }}\n />`;\n }\n const keepYear = includeYear ? \"year: Number(current.year), \" : \"\";\n return `<input\n type=\"date\"\n required={${required}}\n onChange={(e) => {\n const ${dateParts} = e.target.value.split(\"-\").map(Number);\n if (month === undefined || day === undefined) return;\n const current = form.values[${entryId}];\n const time = ${isObject} && \"hour\" in current && \"minute\" in current ? { hour: Number(current.hour), minute: Number(current.minute) } : {};\n form.setValue(${entryId}, { ${dateValue}, ...time });\n }}\n />\n <input\n type=\"time\"\n required={${required}}\n onChange={(e) => {\n const [hour, minute] = e.target.value.split(\":\").map(Number);\n if (hour === undefined || minute === undefined) return;\n const current = form.values[${entryId}];\n const date = ${isObject} && \"month\" in current && \"day\" in current ? { ${keepYear}month: Number(current.month), day: Number(current.day) } : {};\n form.setValue(${entryId}, { ...date, hour, minute });\n }}\n />`;\n};\n\nconst renderDuration = (question: Question, typescript: boolean): string => {\n const entryId = q(question.entryId);\n const required = question.required ? \"true\" : \"false\";\n const isObject = `typeof current === \"object\" && current !== null && !Array.isArray(current)`;\n // Parts stay undefined until typed, so a half-typed duration is rejected by\n // validation instead of submitting zeros; TimeValue needs hour/minute, hence the casts.\n const baseCast = typescript\n ? ' as Partial<Record<\"hour\" | \"minute\" | \"second\", number>>'\n : \"\";\n const valueCast = typescript\n ? \" as { hour: number; minute: number; second?: number }\"\n : \"\";\n const part = (key: string, label: string, max: number): string => {\n return `<input\n type=\"number\"\n min={0}\n max={${max}}\n aria-label=\"${label}\"\n required={${required}}\n onChange={(e) => {\n const current = form.values[${entryId}];\n const base = (${isObject} ? current : {})${baseCast};\n const merged = { ...base, ${key}: e.target.value === \"\" ? undefined : Number(e.target.value) };\n const allEmpty = Object.values(merged).every((v) => v === undefined);\n form.setValue(${entryId}, allEmpty ? undefined : (merged${valueCast}));\n }}\n />`;\n };\n return [\n part(\"hour\", \"Hours\", 23),\n part(\"minute\", \"Minutes\", 59),\n part(\"second\", \"Seconds\", 59),\n ].join(\"\\n \");\n};\n\nconst renderTime = (question: Question, typescript: boolean): string => {\n if (question.time?.isDuration) return renderDuration(question, typescript);\n const entryId = q(question.entryId);\n return `<input\n type=\"time\"\n required={${question.required ? \"true\" : \"false\"}}\n onChange={(e) => {\n const parts = e.target.value.split(\":\").map(Number);\n const [hour, minute] = parts;\n if (hour === undefined || minute === undefined) return;\n form.setValue(${entryId}, { hour, minute });\n }}\n />`;\n};\n\nconst renderQuestion = (question: Question, typescript: boolean): string => {\n const label = `<label>${jsxEscape(question.title)}${\n question.required ? \" *\" : \"\"\n }</label>`;\n const description = question.description\n ? `\\n <p>${jsxEscape(question.description)}</p>`\n : \"\";\n\n let field: string;\n switch (question.type) {\n case \"short_answer\":\n field = renderShortText(question, false);\n break;\n case \"paragraph\":\n field = renderShortText(question, true);\n break;\n case \"multiple_choice\":\n field = renderRadioGroup(question);\n break;\n case \"dropdown\":\n field = renderSelect(question);\n break;\n case \"checkboxes\":\n field = renderCheckboxGroup(question);\n break;\n case \"linear_scale\":\n field = renderLinearScale(question);\n break;\n case \"grid\":\n case \"checkbox_grid\":\n field = renderGrid(question);\n break;\n case \"date\":\n field = renderDate(question);\n break;\n case \"time\":\n field = renderTime(question, typescript);\n break;\n case \"file_upload\":\n field = `<p>\"${jsxEscape(question.title)}\" is a file upload question and is not supported by Google Forms submission without sign-in.</p>`;\n break;\n default:\n field = `<input {...form.register(${q(question.entryId)})} />`;\n }\n\n return ` <div key={${q(question.id)}}>\n ${label}${description}\n ${field}\n </div>`;\n};\n\nconst renderSection = (\n section: Section,\n questionsById: Map<string, Question>,\n typescript: boolean,\n): string => {\n const heading = section.title\n ? ` <h2>${jsxEscape(section.title)}</h2>\\n`\n : \"\";\n const description = section.description\n ? ` <p>${jsxEscape(section.description)}</p>\\n`\n : \"\";\n const body = section.questionIds\n .map((id) => {\n return questionsById.get(id);\n })\n .filter((question): question is Question => {\n return question !== undefined;\n })\n .map((question) => {\n return renderQuestion(question, typescript);\n })\n .join(\"\\n\");\n return `${heading}${description}${body}`;\n};\n\n/**\n * Generates a paste-ready React component wired to `useGoogleForm` from\n * `@ez-gform/react`. Renders every supported question type; `file_upload`\n * questions render a disabled note instead of an input (the Forms API can't\n * accept file uploads without a signed-in session).\n */\nexport const generateReactComponent = (\n schema: FormSchema,\n options: GenerateReactComponentOptions = {},\n): string => {\n const typescript = options.typescript ?? true;\n const name = options.name ?? `${toPascalCase(schema.title)}Form`;\n // fbzx is a per-request token; core generates a fresh one when it is absent.\n const embedded = { ...schema };\n delete embedded.fbzx;\n const schemaJson = JSON.stringify(embedded, null, 2);\n\n const questionsById = new Map(\n schema.questions.map((qn) => {\n return [qn.id, qn];\n }),\n );\n const body =\n schema.sections.length > 1 ||\n (schema.sections[0]?.title && schema.sections[0].title.length > 0)\n ? schema.sections\n .map((section) => {\n return renderSection(section, questionsById, typescript);\n })\n .join(\"\\n\")\n : schema.questions\n .map((question) => {\n return renderQuestion(question, typescript);\n })\n .join(\"\\n\");\n\n const schemaDeclaration = typescript\n ? `${schemaJson} as const satisfies FormSchema`\n : schemaJson;\n const typeImport = typescript\n ? `import type { FormSchema } from \"@ez-gform/react\";\\n`\n : \"\";\n\n return `${typeImport}import { useGoogleForm } from \"@ez-gform/react\";\n\nconst schema = ${schemaDeclaration};\n\nexport function ${name}() {\n const form = useGoogleForm({ formId: schema.formId, schema });\n\n return (\n <form\n onSubmit={form.submit}\n >\n <h1>${jsxEscape(schema.title)}</h1>\n ${schema.description ? `<p>${jsxEscape(schema.description)}</p>` : \"\"}\n${body}\n\n <div>Status: {form.status}</div>\n {form.errors && form.errors.length > 0 && (\n <ul>\n {form.errors.map((error, index) => (\n <li key={index}>{error.message}</li>\n ))}\n </ul>\n )}\n\n <button type=\"submit\" disabled={form.status === \"submitting\"}>\n Submit\n </button>\n </form>\n );\n}\n`;\n};\n","import type {\n FormSchema,\n GenerateTypesOptions,\n Question,\n} from \"@ez-gform/types\";\nimport { toPascalCase } from \"./names.js\";\n\nexport type { GenerateTypesOptions } from \"@ez-gform/types\";\n\nconst quote = (value: string): string => {\n return JSON.stringify(value);\n};\n\nconst hasOtherOption = (question: Question): boolean => {\n return (\n question.options?.some((option) => {\n return option.isOther;\n }) ?? false\n );\n};\n\n/** Returns the TS type text for a single question's value, or `undefined` to omit it entirely. */\nconst fieldType = (question: Question): string | undefined => {\n switch (question.type) {\n case \"short_answer\":\n case \"paragraph\":\n return \"string\";\n case \"multiple_choice\":\n case \"dropdown\":\n return hasOtherOption(question) ? \"string | { other: string }\" : \"string\";\n case \"checkboxes\":\n return hasOtherOption(question)\n ? \"(string | { other: string })[]\"\n : \"string[]\";\n case \"linear_scale\":\n return \"number\";\n case \"date\":\n return \"DateValue\";\n case \"time\":\n return \"TimeValue\";\n case \"grid\":\n case \"checkbox_grid\": {\n const rowIds = (question.rows ?? []).map((row) => {\n return quote(row.entryId);\n });\n const keyType = rowIds.length > 0 ? rowIds.join(\" | \") : \"string\";\n return `Record<${keyType}, string | string[]>`;\n }\n case \"file_upload\":\n // File uploads can't be submitted through the Forms API without\n // sign-in; there is no meaningful value type for them.\n return undefined;\n default:\n return \"string\";\n }\n};\n\n/**\n * Emits `const <Name>Schema = {...} as const satisfies FormSchema;` and a\n * companion `type <Name>Values = { \"entry.N\"?: ...; ... }` mapping every\n * question's entry id to its precise `FieldValue` subtype.\n */\nexport const generateTypes = (\n schema: FormSchema,\n options: GenerateTypesOptions = {},\n): string => {\n const name = options.name ?? toPascalCase(schema.title);\n // fbzx is a per-request token; core generates a fresh one when absent.\n const { fbzx: _, ...embeddedSchema } = schema;\n const schemaJson = JSON.stringify(embeddedSchema, null, 2);\n\n const usesDate = schema.questions.some((q) => {\n return q.type === \"date\";\n });\n const usesTime = schema.questions.some((q) => {\n return q.type === \"time\";\n });\n const extraTypeImports = [\n usesDate ? \"DateValue\" : undefined,\n usesTime ? \"TimeValue\" : undefined,\n ].filter((t): t is string => {\n return t !== undefined;\n });\n\n const fields: string[] = [];\n for (const question of schema.questions) {\n const type = fieldType(question);\n if (type === undefined) continue;\n const optional = question.required ? \"\" : \"?\";\n fields.push(` ${quote(question.entryId)}${optional}: ${type};`);\n }\n\n const importLine = [\"FormSchema\", ...extraTypeImports].sort().join(\", \");\n\n return `import type { ${importLine} } from \"@ez-gform/core\";\n\nexport const ${name}Schema = ${schemaJson} as const satisfies FormSchema;\n\nexport type ${name}Values = {\n${fields.join(\"\\n\")}\n};\n`;\n};\n","import type { FormSchema, GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\nexport type { GenerateSchemaJsonOptions } from \"@ez-gform/types\";\n\n/** Serializes a `FormSchema` back to JSON text. Round-trips structurally with `JSON.parse`. */\nexport const generateSchemaJson = (\n schema: FormSchema,\n options: GenerateSchemaJsonOptions = {},\n): string => {\n const pretty = options.pretty ?? true;\n return pretty ? JSON.stringify(schema, null, 2) : JSON.stringify(schema);\n};\n"],"mappings":"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","names":["renderGrid","renderQuestion","q"]}
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ez-gform/codegen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Pure code generators (JSON schema, TS types, React component, HTML form) from a parsed Google Form schema.",
|
|
5
|
+
"homepage": "https://ez-gform.vercel.app/",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Gobind Singh",
|
|
8
|
+
"email": "thesinghgobind@gmail.com",
|
|
9
|
+
"url": "https://www.thegobindsingh.com/"
|
|
10
|
+
},
|
|
5
11
|
"type": "module",
|
|
6
12
|
"license": "MIT",
|
|
7
|
-
"homepage": "https://github.com/webadeva/ez-gform#readme",
|
|
8
13
|
"bugs": "https://github.com/webadeva/ez-gform/issues",
|
|
9
14
|
"repository": {
|
|
10
15
|
"type": "git",
|
|
@@ -13,9 +18,23 @@
|
|
|
13
18
|
},
|
|
14
19
|
"keywords": [
|
|
15
20
|
"google-forms",
|
|
21
|
+
"google-form",
|
|
22
|
+
"gform",
|
|
16
23
|
"ez-gform",
|
|
24
|
+
"forms",
|
|
25
|
+
"form-submission",
|
|
26
|
+
"no-backend",
|
|
27
|
+
"serverless",
|
|
28
|
+
"headless-forms",
|
|
29
|
+
"typescript",
|
|
17
30
|
"codegen",
|
|
18
|
-
"code-generator"
|
|
31
|
+
"code-generator",
|
|
32
|
+
"code-generation",
|
|
33
|
+
"react-component",
|
|
34
|
+
"html-form",
|
|
35
|
+
"json-schema",
|
|
36
|
+
"typescript-types",
|
|
37
|
+
"generator"
|
|
19
38
|
],
|
|
20
39
|
"publishConfig": {
|
|
21
40
|
"access": "public"
|
|
@@ -35,15 +54,24 @@
|
|
|
35
54
|
"dist"
|
|
36
55
|
],
|
|
37
56
|
"dependencies": {
|
|
38
|
-
"@ez-gform/core": "0.
|
|
39
|
-
"@ez-gform/types": "0.
|
|
57
|
+
"@ez-gform/core": "0.2.0",
|
|
58
|
+
"@ez-gform/types": "0.2.0"
|
|
40
59
|
},
|
|
41
60
|
"devDependencies": {
|
|
42
|
-
"@ez-gform/
|
|
61
|
+
"@ez-gform/react": "0.2.0",
|
|
62
|
+
"@ez-gform/tsconfig": "0.2.0",
|
|
43
63
|
"@kami-ui/eslint-config": "0.0.6",
|
|
64
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
65
|
+
"@testing-library/react": "^16.1.0",
|
|
66
|
+
"@testing-library/user-event": "^14.5.2",
|
|
44
67
|
"@types/node": "^22.10.0",
|
|
68
|
+
"@types/react": "19.3.0",
|
|
69
|
+
"@types/react-dom": "19.3.0",
|
|
45
70
|
"@vitest/coverage-v8": "5.0.1",
|
|
46
71
|
"eslint": "10.10.0",
|
|
72
|
+
"jsdom": "^25.0.1",
|
|
73
|
+
"react": "19.3.0",
|
|
74
|
+
"react-dom": "19.3.0",
|
|
47
75
|
"tsup": "8.5.1",
|
|
48
76
|
"typescript": "5.9.3",
|
|
49
77
|
"vitest": "5.0.1"
|
|
@@ -51,7 +79,7 @@
|
|
|
51
79
|
"scripts": {
|
|
52
80
|
"build": "tsup",
|
|
53
81
|
"dev": "tsup --watch",
|
|
54
|
-
"test": "vitest run",
|
|
82
|
+
"test": "vitest run --coverage",
|
|
55
83
|
"lint": "eslint .",
|
|
56
84
|
"lint:fix": "eslint . --fix",
|
|
57
85
|
"type-check": "tsc --noEmit",
|