@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 CHANGED
@@ -34,28 +34,29 @@ var import_core = require("@ez-gform/core");
34
34
  var escapeHtml = (value) => {
35
35
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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 label = `<label>${escapeHtml(question.title)}${question.required ? " *" : ""}</label>`;
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 options = (question.options ?? []).map((option) => {
84
- return ` <option ${attr("value", option.value)}>${escapeHtml(option.value)}</option>`;
85
- }).join("\n");
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
- <input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour">
134
- <input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute">`;
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", schema.fbzx ?? "")}>`
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
- return pascal.length > 0 ? pascal : "Form";
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) => form.setValue(${entryId}, e.target.checked ? { other: "" } : undefined)}
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) => form.setValue(${entryId}, { other: e.target.value })}
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={${question.required ? "true" : "false"}}
447
+ required={${required}}
398
448
  onChange={(e) => {
399
- const parts = e.target.value.split("-").map(Number);
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.setValue(${entryId}, { year, month, day });
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 renderTime = (question) => {
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(renderQuestion2).join("\n");
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 schemaJson = JSON.stringify(schema, null, 2);
483
- void usesOther;
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(renderQuestion2).join("\n");
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/core";
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 hasOtherOption2 = (question) => {
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 hasOtherOption2(question) ? "string | { other: string }" : "string";
637
+ return hasOtherOption(question) ? "string | { other: string }" : "string";
545
638
  case "checkboxes":
546
- return hasOtherOption2(question) ? "(string | { other: string })[]" : "string[]";
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 schemaJson = JSON.stringify(schema, null, 2);
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
  });
@@ -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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\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 \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"{\": \"&#123;\",\n \"}\": \"&#125;\",\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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\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 \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"{\": \"&#123;\",\n \"}\": \"&#125;\",\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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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 label = `<label>${escapeHtml(question.title)}${question.required ? " *" : ""}</label>`;
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 options = (question.options ?? []).map((option) => {
53
- return ` <option ${attr("value", option.value)}>${escapeHtml(option.value)}</option>`;
54
- }).join("\n");
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
- <input type="number" ${attr("name", `${question.entryId}_hour`)} placeholder="Hour">
103
- <input type="number" ${attr("name", `${question.entryId}_minute`)} placeholder="Minute">`;
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", schema.fbzx ?? "")}>`
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
- return pascal.length > 0 ? pascal : "Form";
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) => form.setValue(${entryId}, e.target.checked ? { other: "" } : undefined)}
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) => form.setValue(${entryId}, { other: e.target.value })}
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={${question.required ? "true" : "false"}}
416
+ required={${required}}
367
417
  onChange={(e) => {
368
- const parts = e.target.value.split("-").map(Number);
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.setValue(${entryId}, { year, month, day });
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 renderTime = (question) => {
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(renderQuestion2).join("\n");
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 schemaJson = JSON.stringify(schema, null, 2);
452
- void usesOther;
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(renderQuestion2).join("\n");
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/core";
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 hasOtherOption2 = (question) => {
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 hasOtherOption2(question) ? "string | { other: string }" : "string";
606
+ return hasOtherOption(question) ? "string | { other: string }" : "string";
514
607
  case "checkboxes":
515
- return hasOtherOption2(question) ? "(string | { other: string })[]" : "string[]";
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 schemaJson = JSON.stringify(schema, null, 2);
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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\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 \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"{\": \"&#123;\",\n \"}\": \"&#125;\",\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/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, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\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 \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"{\": \"&#123;\",\n \"}\": \"&#125;\",\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.1.4",
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.1.4",
39
- "@ez-gform/types": "0.1.4"
57
+ "@ez-gform/core": "0.2.0",
58
+ "@ez-gform/types": "0.2.0"
40
59
  },
41
60
  "devDependencies": {
42
- "@ez-gform/tsconfig": "0.1.4",
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",