reactivated 0.25.0 → 1.0.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.
Files changed (98) hide show
  1. package/{babel.config.d.ts → dist/babel.config.d.ts} +0 -0
  2. package/{babel.config.js → dist/babel.config.js} +0 -0
  3. package/dist/babel.config.js.map +1 -0
  4. package/dist/build.client.d.ts +2 -0
  5. package/{build.client.js → dist/build.client.js} +14 -13
  6. package/dist/build.client.js.map +1 -0
  7. package/dist/build.renderer.d.ts +2 -0
  8. package/{build.renderer.js → dist/build.renderer.js} +21 -14
  9. package/dist/build.renderer.js.map +1 -0
  10. package/{client.d.ts → dist/client.d.ts} +0 -0
  11. package/dist/client.js +19 -0
  12. package/dist/client.js.map +1 -0
  13. package/{components → dist/components}/Form.d.ts +1 -0
  14. package/{components → dist/components}/Form.js +4 -5
  15. package/dist/components/Form.js.map +1 -0
  16. package/{components → dist/components}/Widget.d.ts +0 -0
  17. package/{components → dist/components}/Widget.js +5 -7
  18. package/dist/components/Widget.js.map +1 -0
  19. package/{context.d.ts → dist/context.d.ts} +0 -0
  20. package/dist/context.js +20 -0
  21. package/dist/context.js.map +1 -0
  22. package/{eslintrc.d.ts → dist/eslintrc.d.ts} +0 -0
  23. package/{eslintrc.js → dist/eslintrc.js} +0 -0
  24. package/dist/eslintrc.js.map +1 -0
  25. package/{forms → dist/forms}/index.d.ts +0 -0
  26. package/dist/forms/index.js +377 -0
  27. package/dist/forms/index.js.map +1 -0
  28. package/{forms → dist/forms}/widgets.d.ts +1 -0
  29. package/{forms → dist/forms}/widgets.js +11 -11
  30. package/dist/forms/widgets.js.map +1 -0
  31. package/{generated.d.ts → dist/generated.d.ts} +0 -0
  32. package/{generated.js → dist/generated.js} +0 -0
  33. package/dist/generated.js.map +1 -0
  34. package/dist/generator/constants.d.ts +2 -0
  35. package/{generator → dist/generator}/constants.js +11 -11
  36. package/dist/generator/constants.js.map +1 -0
  37. package/dist/generator.d.ts +2 -0
  38. package/dist/generator.js +155 -0
  39. package/dist/generator.js.map +1 -0
  40. package/{index.d.ts → dist/index.d.ts} +0 -0
  41. package/{index.js → dist/index.js} +0 -0
  42. package/dist/index.js.map +1 -0
  43. package/{models.d.ts → dist/models.d.ts} +0 -0
  44. package/{models.js → dist/models.js} +0 -0
  45. package/dist/models.js.map +1 -0
  46. package/{renderer.d.ts → dist/renderer.d.ts} +0 -0
  47. package/dist/renderer.js +117 -0
  48. package/dist/renderer.js.map +1 -0
  49. package/{types.d.ts → dist/types.d.ts} +0 -0
  50. package/{types.js → dist/types.js} +0 -0
  51. package/dist/types.js.map +1 -0
  52. package/package.json +23 -19
  53. package/scripts/setup_environment.sh +39 -0
  54. package/{README.md → src/README.md} +0 -0
  55. package/src/babel.config.tsx +14 -0
  56. package/src/build.client.tsx +59 -0
  57. package/src/build.renderer.tsx +69 -0
  58. package/src/client.tsx +15 -0
  59. package/src/components/Form.tsx +73 -0
  60. package/src/components/Widget.tsx +202 -0
  61. package/src/context.tsx +28 -0
  62. package/src/eslintrc.tsx +117 -0
  63. package/src/forms/index.tsx +769 -0
  64. package/src/forms/widgets.tsx +85 -0
  65. package/src/generated.tsx +267 -0
  66. package/src/generator/constants.tsx +40 -0
  67. package/src/generator.tsx +181 -0
  68. package/src/index.tsx +4 -0
  69. package/src/models.tsx +12 -0
  70. package/src/renderer.tsx +165 -0
  71. package/src/types.tsx +6 -0
  72. package/tsconfig.json +10 -20
  73. package/babel.config.js.map +0 -1
  74. package/build.client.d.ts +0 -1
  75. package/build.client.js.map +0 -1
  76. package/build.renderer.d.ts +0 -1
  77. package/build.renderer.js.map +0 -1
  78. package/client.js +0 -30
  79. package/client.js.map +0 -1
  80. package/components/Form.js.map +0 -1
  81. package/components/Widget.js.map +0 -1
  82. package/context.js +0 -31
  83. package/context.js.map +0 -1
  84. package/eslintrc.js.map +0 -1
  85. package/forms/index.js +0 -401
  86. package/forms/index.js.map +0 -1
  87. package/forms/widgets.js.map +0 -1
  88. package/generated.js.map +0 -1
  89. package/generator/constants.d.ts +0 -1
  90. package/generator/constants.js.map +0 -1
  91. package/generator.d.ts +0 -1
  92. package/generator.js +0 -109
  93. package/generator.js.map +0 -1
  94. package/index.js.map +0 -1
  95. package/models.js.map +0 -1
  96. package/renderer.js +0 -108
  97. package/renderer.js.map +0 -1
  98. package/types.js.map +0 -1
@@ -0,0 +1,769 @@
1
+ import produce, {castDraft} from "immer";
2
+ import {FileInfoResult} from "prettier";
3
+ import React from "react";
4
+
5
+ import {DjangoFormsWidgetsHiddenInput, Types} from "../generated";
6
+ import {DiscriminateUnion} from "../types";
7
+ import * as widgets from "./widgets";
8
+
9
+ export type Optgroup = Types["Optgroup"];
10
+
11
+ export interface WidgetLike {
12
+ name: string;
13
+ tag: string;
14
+ is_hidden: boolean;
15
+ attrs: {
16
+ disabled?: boolean;
17
+ id: string;
18
+ };
19
+ subwidgets?: WidgetLike[];
20
+ value: unknown;
21
+ }
22
+
23
+ interface FieldLike<W = WidgetLike> {
24
+ name: string;
25
+ widget: W;
26
+ label: string;
27
+ help_text: string | null;
28
+ }
29
+
30
+ export interface FieldMap<W = WidgetLike> {
31
+ [name: string]: FieldLike<W>;
32
+ }
33
+
34
+ export interface FormLike<T extends FieldMap<W>, W = WidgetLike> {
35
+ name: string;
36
+ fields: T;
37
+ errors: {[P in keyof T]?: string[]} | null;
38
+ iterator: Array<Extract<keyof T, string>>;
39
+ prefix: string;
40
+ }
41
+
42
+ export interface FormSetLike<T extends FieldMap<W>, W = WidgetLike> {
43
+ initial_form_count: number;
44
+ total_form_count: number;
45
+ max_num: number;
46
+ min_num: number;
47
+ can_delete: boolean;
48
+ can_order: boolean;
49
+ non_form_errors: string[];
50
+
51
+ // Technically we don't need management form.
52
+ // Since we have ManagementForm as a component that uses initial and total.
53
+ management_form: unknown;
54
+ prefix: string;
55
+
56
+ forms: Array<FormLike<T, W>>;
57
+ empty_form: FormLike<T, W>;
58
+ }
59
+
60
+ export type FormErrors<T extends FieldMap> = {[P in keyof T]?: string[]} | null;
61
+
62
+ export type FormValues<T extends FieldMap> = {
63
+ [K in keyof T]: T[K] extends {enum: unknown}
64
+ ? T[K]["enum"] | null
65
+ : T[K]["widget"] extends {_reactivated_value_do_not_use?: unknown}
66
+ ? NonNullable<T[K]["widget"]["_reactivated_value_do_not_use"]>
67
+ : T[K]["widget"]["value"];
68
+ };
69
+
70
+ export interface FormHandler<T extends FieldMap> {
71
+ form: FormLike<T>;
72
+ values: FormValues<T>;
73
+ initial: FormValues<T>;
74
+ errors: FormErrors<T>;
75
+
76
+ fields: {[K in keyof T]: FieldHandler<T[keyof T]["widget"]>};
77
+ visibleFields: FieldHandler<T[keyof T]["widget"]>[];
78
+ hiddenFields: FieldHandler<DjangoFormsWidgetsHiddenInput>[];
79
+ nonFieldErrors: string[] | null;
80
+
81
+ setValue: <K extends keyof T>(name: K, value: FormValues<T>[K]) => void;
82
+ setErrors: (errors: FormErrors<T>) => void;
83
+ iterate: (
84
+ iterator: Array<Extract<keyof T, string>>,
85
+ callback: (
86
+ fieldName: keyof T,
87
+ field: FieldHandler<T[keyof T]["widget"]>,
88
+ ) => React.ReactNode,
89
+ ) => React.ReactNode[];
90
+ reset: () => void;
91
+ }
92
+
93
+ export const getInitialFormState = <T extends FieldMap>(form: FormLike<T>) => {
94
+ const initialValuesAsEntries = form.iterator.map((fieldName) => {
95
+ const field = form.fields[fieldName];
96
+ const widget = field.widget;
97
+
98
+ if (widget.subwidgets != null) {
99
+ const subwidgetValue = Object.fromEntries(
100
+ widget.subwidgets.map((subwidget) => {
101
+ const formPrefix = form.prefix === "" ? "" : `${form.prefix}-`;
102
+ const unprefixedName = subwidget.name.replace(
103
+ `${formPrefix}${fieldName}_`,
104
+ "",
105
+ );
106
+ return [unprefixedName, subwidget.value];
107
+ }),
108
+ );
109
+ return [fieldName, subwidgetValue];
110
+ }
111
+ return [fieldName, widget.value];
112
+ });
113
+
114
+ return Object.fromEntries(initialValuesAsEntries) as FormValues<T>;
115
+ };
116
+
117
+ export const getInitialFormSetState = <T extends FieldMap>(
118
+ forms: Array<FormLike<T>>,
119
+ ) => {
120
+ return Object.fromEntries(
121
+ forms.map((form) => [form.prefix, getInitialFormState(form)] as const),
122
+ );
123
+ };
124
+
125
+ export const getInitialFormSetErrors = <T extends FieldMap>(
126
+ forms: Array<FormLike<T>>,
127
+ ) => {
128
+ return Object.fromEntries(forms.map((form) => [form.prefix, form.errors] as const));
129
+ };
130
+
131
+ export const getFormHandler = <T extends FieldMap>({
132
+ form,
133
+ values,
134
+ initial,
135
+ errors,
136
+ setValues,
137
+ setErrors,
138
+ ...options
139
+ }: {
140
+ form: FormLike<T>;
141
+ values: FormValues<T>;
142
+ initial: FormValues<T>;
143
+ errors: FormErrors<T>;
144
+ setErrors: (errors: FormErrors<T>) => void;
145
+ setValues: (
146
+ getValuesToSetFromPrevValues: (values: FormValues<T>) => FormValues<T>,
147
+ ) => void;
148
+ fieldInterceptor?: (
149
+ fieldName: keyof T,
150
+ field: FieldHandler<T[keyof T]["widget"]>,
151
+ values: FormValues<T>,
152
+ ) => typeof field;
153
+ changeInterceptor?: (
154
+ name: keyof T,
155
+ prevValues: FormValues<T>,
156
+ nextValues: FormValues<T>,
157
+ ) => FormValues<T>;
158
+ }): FormHandler<T> => {
159
+ const changeInterceptor =
160
+ options.changeInterceptor ?? ((_, prevValues, nextValues) => nextValues);
161
+ const fieldInterceptor = options.fieldInterceptor ?? ((fieldName, field) => field);
162
+
163
+ const changeValues = (
164
+ fieldName: keyof T,
165
+ getIncomingValues: (prevValues: FormValues<T>) => FormValues<T>,
166
+ ) => {
167
+ setValues((prevValues) => {
168
+ const incomingValues = getIncomingValues(prevValues);
169
+ const nextValues = changeInterceptor(fieldName, prevValues, incomingValues);
170
+ return nextValues;
171
+ });
172
+ };
173
+
174
+ const reset = () => {
175
+ setValues(() => initial);
176
+ setErrors({});
177
+ };
178
+
179
+ const fields = Object.fromEntries(
180
+ form.iterator.map((fieldName) => {
181
+ const field = form.fields[fieldName];
182
+ const error = errors?.[fieldName]?.[0] ?? null;
183
+ const {help_text} = field;
184
+
185
+ if (field.widget.subwidgets != null) {
186
+ const subwidgets = field.widget.subwidgets.map((subwidget) => {
187
+ const formPrefix = form.prefix === "" ? "" : `${form.prefix}-`;
188
+ const unprefixedName = subwidget.name.replace(
189
+ `${formPrefix}${fieldName}_`,
190
+ "",
191
+ );
192
+
193
+ const setSubwidgetValue = (value: unknown) => {
194
+ changeValues(fieldName, (prevValues) => {
195
+ const subwidgetValues = prevValues[fieldName] as any;
196
+ const subwidgetValue = subwidgetValues[unprefixedName];
197
+
198
+ return {
199
+ ...prevValues,
200
+ [fieldName]: {
201
+ ...subwidgetValues,
202
+ [unprefixedName]: value,
203
+ },
204
+ };
205
+ });
206
+ };
207
+ const subfieldHandler: WidgetHandler<any> = {
208
+ name: subwidget.name,
209
+ error,
210
+ help_text,
211
+ label: field.label,
212
+ disabled: false,
213
+ tag: subwidget.tag,
214
+ widget: subwidget,
215
+ value: (values[fieldName] as any)[unprefixedName],
216
+ handler: setSubwidgetValue,
217
+ };
218
+ return subfieldHandler;
219
+ });
220
+
221
+ const subwidgetHandler: SubwidgetHandler<
222
+ typeof field.widget,
223
+ typeof field.widget.subwidgets
224
+ > = {
225
+ name: field.widget.name,
226
+ disabled: field.widget.attrs.disabled ?? false,
227
+ label: field.label,
228
+ error,
229
+ help_text,
230
+ tag: field.widget.tag,
231
+ subwidgets,
232
+ };
233
+
234
+ return [
235
+ fieldName,
236
+ fieldInterceptor(
237
+ fieldName,
238
+ subwidgetHandler as FieldHandler<T[keyof T]["widget"]>,
239
+ values,
240
+ ),
241
+ ] as const;
242
+ }
243
+
244
+ const fieldHandler: WidgetHandler<typeof field.widget> = {
245
+ name: field.widget.name,
246
+ error,
247
+ help_text,
248
+ label: field.label,
249
+ disabled: field.widget.attrs.disabled ?? false,
250
+ tag: field.widget.tag,
251
+ widget: field.widget,
252
+ value: values[fieldName],
253
+ handler: (value) => {
254
+ changeValues(fieldName, (prevValues) => ({
255
+ ...prevValues,
256
+ [fieldName]: value,
257
+ }));
258
+ },
259
+ };
260
+
261
+ return [
262
+ fieldName,
263
+ fieldInterceptor(
264
+ fieldName,
265
+ fieldHandler as FieldHandler<T[keyof T]["widget"]>,
266
+ values,
267
+ ),
268
+ ] as const;
269
+ }),
270
+ ) as FormHandler<T>["fields"];
271
+
272
+ const iterate = (
273
+ iterator: Array<Extract<keyof T, string>>,
274
+ callback: (
275
+ fieldName: keyof T,
276
+ field: FieldHandler<T[keyof T]["widget"]>,
277
+ ) => React.ReactNode,
278
+ ) => {
279
+ return iterator.map((fieldName) => {
280
+ return callback(fieldName, fields[fieldName]);
281
+ });
282
+ };
283
+
284
+ const visibleFields: FormHandler<T>["visibleFields"] = form.iterator
285
+ .filter(
286
+ (fieldName) => fields[fieldName].tag !== "django.forms.widgets.HiddenInput",
287
+ )
288
+ .map((fieldName) => fields[fieldName]);
289
+
290
+ const hiddenFields: FormHandler<T>["hiddenFields"] = form.iterator
291
+ .filter(
292
+ (fieldName) => fields[fieldName].tag === "django.forms.widgets.HiddenInput",
293
+ )
294
+ .map(
295
+ (fieldName) =>
296
+ fields[
297
+ fieldName
298
+ ] as unknown as FieldHandler<DjangoFormsWidgetsHiddenInput>,
299
+ );
300
+ const nonFieldErrors = errors?.["__all__"] ?? null;
301
+
302
+ return {
303
+ form,
304
+ values,
305
+ initial,
306
+ errors,
307
+ fields,
308
+ nonFieldErrors,
309
+ visibleFields,
310
+ hiddenFields,
311
+ iterate,
312
+ reset,
313
+ setErrors,
314
+ setValue: (fieldName, value) => {
315
+ changeValues(fieldName, (prevValues) => ({
316
+ ...prevValues,
317
+ [fieldName]: value,
318
+ }));
319
+ },
320
+ };
321
+ };
322
+
323
+ export const useForm = <T extends FieldMap>({
324
+ form,
325
+ ...options
326
+ }: {
327
+ form: FormLike<T>;
328
+ fieldInterceptor?: (
329
+ fieldName: keyof T,
330
+ field: FieldHandler<T[keyof T]["widget"]>,
331
+ values: FormValues<T>,
332
+ ) => typeof field;
333
+ changeInterceptor?: (
334
+ name: keyof T,
335
+ prevValues: FormValues<T>,
336
+ nextValues: FormValues<T>,
337
+ ) => FormValues<T>;
338
+ }): FormHandler<T> => {
339
+ const initial = getInitialFormState(form);
340
+ const [values, formSetValues] = React.useState(initial);
341
+ const [errors, setErrors] = React.useState(form.errors);
342
+
343
+ return getFormHandler({
344
+ ...options,
345
+ form,
346
+ errors,
347
+ setErrors,
348
+ initial,
349
+ values,
350
+ setValues: (getValuesToSetFromPrevValues) => {
351
+ formSetValues((prevValues) => getValuesToSetFromPrevValues(prevValues));
352
+ },
353
+ });
354
+ };
355
+
356
+ export type CreateFieldHandler<T> = T extends {
357
+ tag: string;
358
+ name: string;
359
+ subwidgets: infer U;
360
+ }
361
+ ? SubwidgetHandler<T, U>
362
+ : T extends WidgetLike
363
+ ? WidgetHandler<T>
364
+ : never;
365
+
366
+ export interface SubwidgetHandler<T extends {tag: string; name: string}, U> {
367
+ tag: T["tag"];
368
+ name: string;
369
+ label: string;
370
+ error: string | null;
371
+ help_text: string | null;
372
+ disabled: boolean;
373
+ subwidgets: {[K in keyof U]: U[K] extends WidgetLike ? WidgetHandler<U[K]> : never};
374
+ }
375
+
376
+ export interface WidgetHandler<T extends WidgetLike> {
377
+ tag: T["tag"];
378
+ name: string;
379
+ value: T["value"];
380
+ label: string;
381
+ error: string | null;
382
+ help_text: string | null;
383
+ disabled: boolean;
384
+ widget: T;
385
+ handler: (value: T["value"]) => void;
386
+ }
387
+
388
+ export type FieldHandler<TWidget extends WidgetLike> = {
389
+ [K in TWidget["tag"]]: CreateFieldHandler<DiscriminateUnion<TWidget, "tag", K>>;
390
+ }[TWidget["tag"]];
391
+
392
+ interface BaseRendererProps<T extends FieldMap, F extends WidgetLike> {
393
+ fieldInterceptor?: (
394
+ fieldName: keyof T,
395
+ field: FieldHandler<F>,
396
+ values: FormValues<T>,
397
+ ) => typeof field;
398
+ changeInterceptor?: (
399
+ name: keyof T,
400
+ prevValues: FormValues<T>,
401
+ nextValues: FormValues<T>,
402
+ ) => FormValues<T>;
403
+ form: FormLike<T> | FormHandler<T>;
404
+ children: (field: FieldHandler<F>) => React.ReactNode;
405
+ }
406
+
407
+ interface IncludeRendererProps<T extends FieldMap, F extends WidgetLike>
408
+ extends BaseRendererProps<T, F> {
409
+ fields?: Array<Extract<keyof T, string>>;
410
+ exclude?: never;
411
+ }
412
+
413
+ interface ExcludeRendererProps<T extends FieldMap, F extends WidgetLike>
414
+ extends BaseRendererProps<T, F> {
415
+ fields?: never;
416
+ exclude: Array<Extract<keyof T, string>>;
417
+ }
418
+
419
+ export type RendererProps<T extends FieldMap, F extends WidgetLike> =
420
+ | IncludeRendererProps<T, F>
421
+ | ExcludeRendererProps<T, F>;
422
+
423
+ export const createIterator =
424
+ <F extends WidgetLike>() =>
425
+ <U extends FieldMap>(props: RendererProps<U, F>) => {
426
+ const defaultHandler =
427
+ "form" in props.form
428
+ ? props.form
429
+ : useForm({
430
+ form: props.form,
431
+ changeInterceptor: props.changeInterceptor,
432
+ });
433
+ const handler = "form" in props.form ? props.form : defaultHandler;
434
+ const fieldInterceptor =
435
+ props.fieldInterceptor ?? ((fieldName, field, values) => field);
436
+
437
+ const getIterator = () => {
438
+ if (props.fields != null) {
439
+ return props.fields;
440
+ }
441
+
442
+ if (props.exclude != null) {
443
+ return handler.form.iterator.filter(
444
+ (field) => !props.exclude.includes(field),
445
+ );
446
+ }
447
+
448
+ return handler.form.iterator;
449
+ };
450
+
451
+ return (
452
+ <>
453
+ {handler.iterate(getIterator(), (fieldName, field) => (
454
+ <React.Fragment key={field.name}>
455
+ {props.children(
456
+ fieldInterceptor(fieldName, field as any, handler.values),
457
+ )}
458
+ </React.Fragment>
459
+ ))}
460
+ </>
461
+ );
462
+ };
463
+
464
+ export const Widget = (props: {field: FieldHandler<widgets.CoreWidget>}) => {
465
+ const {field} = props;
466
+
467
+ if ("subwidgets" in field) {
468
+ return (
469
+ <>
470
+ {field.subwidgets.map((subwidget) => {
471
+ return <Widget key={subwidget.name} field={subwidget} />;
472
+ })}
473
+ </>
474
+ );
475
+ }
476
+
477
+ if (field.tag === "django.forms.widgets.HiddenInput") {
478
+ return <input type="hidden" name={field.name} value={field.value ?? ""} />;
479
+ } else if (field.tag === "django.forms.widgets.CheckboxInput") {
480
+ return (
481
+ <widgets.CheckboxInput
482
+ name={field.name}
483
+ value={field.value}
484
+ onChange={field.handler}
485
+ />
486
+ );
487
+ } else if (field.tag === "django.forms.widgets.Textarea") {
488
+ return (
489
+ <widgets.Textarea
490
+ name={field.name}
491
+ value={field.value}
492
+ onChange={field.handler}
493
+ />
494
+ );
495
+ } else if (
496
+ field.tag === "django.forms.widgets.TextInput" ||
497
+ field.tag === "django.forms.widgets.DateInput" ||
498
+ field.tag === "django.forms.widgets.URLInput" ||
499
+ field.tag === "django.forms.widgets.PasswordInput" ||
500
+ field.tag === "django.forms.widgets.EmailInput" ||
501
+ field.tag === "django.forms.widgets.TimeInput" ||
502
+ field.tag === "django.forms.widgets.NumberInput"
503
+ ) {
504
+ return (
505
+ <widgets.TextInput
506
+ name={field.name}
507
+ value={field.value}
508
+ onChange={field.handler}
509
+ />
510
+ );
511
+ } else if (field.tag === "django.forms.widgets.Select") {
512
+ return (
513
+ <widgets.Select
514
+ name={field.name}
515
+ value={field.value}
516
+ optgroups={field.widget.optgroups}
517
+ onChange={field.handler}
518
+ />
519
+ );
520
+ } else if (field.tag === "django.forms.widgets.ClearableFileInput") {
521
+ return <input type="file" name={field.name} value={field.value ?? ""} />;
522
+ } else if (field.tag === "django.forms.widgets.SelectMultiple") {
523
+ return (
524
+ <select
525
+ name={field.name}
526
+ multiple
527
+ value={field.value}
528
+ onChange={(event) => {
529
+ const value = Array.from(
530
+ event.target.selectedOptions,
531
+ (option) => option.value,
532
+ );
533
+ field.handler(value);
534
+ }}
535
+ >
536
+ {field.widget.optgroups.map((optgroup) => {
537
+ const value = (optgroup[1][0].value ?? "").toString();
538
+ return (
539
+ <option key={value} value={value}>
540
+ {optgroup[1][0].label}
541
+ </option>
542
+ );
543
+ })}
544
+ </select>
545
+ );
546
+ }
547
+
548
+ const exhastive: never = field;
549
+ throw new Error(`Exhaustive {field.tag}`);
550
+ };
551
+
552
+ export const ManagementForm = <T extends FieldMap>({
553
+ formSet,
554
+ }: {
555
+ formSet: FormSetLike<T>;
556
+ }) => {
557
+ return (
558
+ <>
559
+ <input
560
+ type="hidden"
561
+ name={`${formSet.prefix}-INITIAL_FORMS`}
562
+ value={formSet.initial_form_count}
563
+ />
564
+ <input
565
+ type="hidden"
566
+ name={`${formSet.prefix}-TOTAL_FORMS`}
567
+ value={formSet.total_form_count}
568
+ />
569
+ </>
570
+ );
571
+ };
572
+
573
+ export const useFormSet = <T extends FieldMap>(options: {
574
+ formSet: FormSetLike<T>;
575
+ onAddForm?: (form: FormLike<T>) => void;
576
+ fieldInterceptor?: (
577
+ fieldName: keyof T,
578
+ field: FieldHandler<T[keyof T]["widget"]>,
579
+ values: FormValues<T>,
580
+ ) => FieldHandler<T[keyof T]["widget"]>;
581
+ changeInterceptor?: (
582
+ name: keyof T,
583
+ prevValues: FormValues<T>,
584
+ nextValues: FormValues<T>,
585
+ ) => FormValues<T>;
586
+ }) => {
587
+ const [formSet, setFormSet] = React.useState(options.formSet);
588
+
589
+ const initialFormSetState = getInitialFormSetState(options.formSet.forms);
590
+ const initialFormSetErrors = getInitialFormSetErrors(options.formSet.forms);
591
+ const [values, formSetSetValues] =
592
+ React.useState<Partial<typeof initialFormSetState>>(initialFormSetState);
593
+ const [errors, formSetSetErrors] =
594
+ React.useState<Partial<typeof initialFormSetErrors>>(initialFormSetErrors);
595
+
596
+ const emptyFormValues = getInitialFormState(formSet.empty_form);
597
+
598
+ const forms = formSet.forms.map((form, index) => {
599
+ return getFormHandler({
600
+ form,
601
+ changeInterceptor: options.changeInterceptor,
602
+ fieldInterceptor: options.fieldInterceptor,
603
+ values: values[form.prefix] ?? emptyFormValues,
604
+ errors: errors[form.prefix] ?? {},
605
+ setErrors: (nextErrors) => {
606
+ formSetSetErrors((prevErrors) => ({
607
+ ...prevErrors,
608
+ [form.prefix]: nextErrors,
609
+ }));
610
+ },
611
+ initial: initialFormSetState[index] ?? emptyFormValues,
612
+ setValues: (getValuesToSetFromPrevValues) => {
613
+ formSetSetValues((prevValues) => {
614
+ const nextValues = getValuesToSetFromPrevValues(
615
+ prevValues[form.prefix] ?? emptyFormValues,
616
+ );
617
+ return {
618
+ ...prevValues,
619
+ [form.prefix]: nextValues,
620
+ };
621
+ });
622
+ },
623
+ });
624
+ });
625
+
626
+ const addForm = () => {
627
+ const {total_form_count} = formSet;
628
+ type AdditionalForm = typeof formSet["forms"][number];
629
+
630
+ const extraForm = produce(formSet.empty_form, (draftState) => {
631
+ for (const fieldName of draftState.iterator) {
632
+ const prefix = `${formSet.prefix}-${formSet.total_form_count}`;
633
+ const field = draftState.fields[fieldName];
634
+ const htmlName = `${prefix}-${field.name}`;
635
+ draftState.fields[fieldName].widget.name = htmlName;
636
+ draftState.fields[fieldName].widget.attrs.id = `id_${htmlName}`;
637
+ draftState.prefix = prefix;
638
+ }
639
+ });
640
+ const updated = produce(formSet, (draftState) => {
641
+ draftState.forms.push(castDraft(extraForm));
642
+ draftState.total_form_count += 1;
643
+ });
644
+
645
+ setFormSet(updated);
646
+ options.onAddForm?.(extraForm);
647
+ };
648
+
649
+ return {schema: formSet, values, forms, addForm};
650
+ };
651
+
652
+ export const bindWidgetType = <W extends WidgetLike>() => {
653
+ const Iterator = createIterator<W>();
654
+
655
+ function createRenderer<TProps = Record<never, never>>(
656
+ callback: (field: FieldHandler<W>, props: TProps) => React.ReactNode,
657
+ ) {
658
+ const Renderer = <T extends FieldMap>(props: TProps & RendererProps<T, W>) => {
659
+ return <Iterator {...props}>{(field) => callback(field, props)}</Iterator>;
660
+ };
661
+ // This is not used, but here so that children is excluded from required props.
662
+ Renderer.defaultProps = {
663
+ children: () => null,
664
+ };
665
+ return Renderer;
666
+ }
667
+
668
+ return {
669
+ createRenderer,
670
+ Iterator,
671
+ };
672
+ };
673
+
674
+ export const createCSRFToken = <TContext extends {csrf_token: string}>(
675
+ Context: React.Context<TContext>,
676
+ ) => {
677
+ const CSRFToken = (props: {}) => {
678
+ const context = React.useContext(Context);
679
+
680
+ return (
681
+ <input
682
+ type="hidden"
683
+ name="csrfmiddlewaretoken"
684
+ value={context.csrf_token}
685
+ />
686
+ );
687
+ };
688
+ return CSRFToken;
689
+ };
690
+
691
+ export const Form = <T extends FieldMap<widgets.CoreWidget>>(props: {
692
+ form: FormLike<T> | FormHandler<T>;
693
+ as: "p" | "table";
694
+ }) => {
695
+ const form = "form" in props.form ? props.form : useForm({form: props.form});
696
+
697
+ if (props.as == "p") {
698
+ return (
699
+ <>
700
+ {form.nonFieldErrors?.map((error, index) => (
701
+ <div key={index}>{error}</div>
702
+ ))}
703
+ {form.hiddenFields.map((field, index) => (
704
+ <Widget key={index} field={field} />
705
+ ))}
706
+ {form.visibleFields.map((field, index) => (
707
+ <React.Fragment key={field.name}>
708
+ {field.error != null && <div>{field.error}</div>}
709
+ <p>
710
+ <label>{field.label}</label>
711
+ {field.help_text != null && <span>{field.help_text}</span>}
712
+ <Widget field={field} />
713
+ </p>
714
+ </React.Fragment>
715
+ ))}
716
+ </>
717
+ );
718
+ } else {
719
+ return (
720
+ <>
721
+ {form.nonFieldErrors != null && (
722
+ <tr>
723
+ {form.nonFieldErrors.map((error, index) => (
724
+ <td key={index} colSpan={2}>
725
+ {error}
726
+ </td>
727
+ ))}
728
+ </tr>
729
+ )}
730
+ <tr style={{display: "none"}}>
731
+ {form.hiddenFields.map((field, index) => (
732
+ <Widget key={index} field={field} />
733
+ ))}
734
+ </tr>
735
+ {form.visibleFields.map((field, index) => (
736
+ <tr key={field.name}>
737
+ <th>
738
+ <label>{field.label}</label>
739
+ </th>
740
+ <td>
741
+ {field.error != null && <div>{field.error}</div>}
742
+ {field.help_text != null && (
743
+ <>
744
+ <br />
745
+ <span>{field.help_text}</span>
746
+ </>
747
+ )}
748
+ <Widget field={field} />
749
+ </td>
750
+ </tr>
751
+ ))}
752
+ </>
753
+ );
754
+ }
755
+ };
756
+
757
+ export const FormSet = <T extends FieldMap<widgets.CoreWidget>>(props: {
758
+ formSet: FormSetLike<T>;
759
+ as: "p" | "table";
760
+ }) => {
761
+ return (
762
+ <>
763
+ <ManagementForm formSet={props.formSet} />
764
+ {props.formSet.forms.map((form) => (
765
+ <Form key={form.prefix} form={form} as={props.as} />
766
+ ))}
767
+ </>
768
+ );
769
+ };