@sealcode/sealgen 0.8.30 → 0.8.32

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 (117) hide show
  1. package/@types/forms/controls/autocomplete.d.ts +2 -2
  2. package/@types/forms/controls/big-radio-group.d.ts +4 -5
  3. package/@types/forms/controls/checkbox-group.d.ts +3 -4
  4. package/@types/forms/controls/checkboxed-list-input.d.ts +2 -3
  5. package/@types/forms/controls/collapsible-checkbox-group.d.ts +2 -3
  6. package/@types/forms/controls/controls.d.ts +2 -1
  7. package/@types/forms/controls/derived-frame.d.ts +21 -0
  8. package/@types/forms/controls/derived.d.ts +6 -6
  9. package/@types/forms/controls/dropdown.d.ts +23 -14
  10. package/@types/forms/controls/editable-collection-subset.d.ts +2 -3
  11. package/@types/forms/controls/field-group.d.ts +2 -4
  12. package/@types/forms/controls/file.d.ts +3 -2
  13. package/@types/forms/controls/form-control.d.ts +10 -1
  14. package/@types/forms/controls/form-field-control.d.ts +3 -4
  15. package/@types/forms/controls/form-header.d.ts +4 -4
  16. package/@types/forms/controls/hidden-input.d.ts +2 -3
  17. package/@types/forms/controls/html.d.ts +3 -4
  18. package/@types/forms/controls/multiple-files.d.ts +3 -3
  19. package/@types/forms/controls/number.d.ts +2 -3
  20. package/@types/forms/controls/photo.d.ts +2 -2
  21. package/@types/forms/controls/simple-input.d.ts +5 -4
  22. package/@types/forms/controls/textarea.d.ts +2 -1
  23. package/@types/forms/controls/tickable.d.ts +2 -1
  24. package/@types/forms/fields/pick-from-list.d.ts +3 -6
  25. package/@types/page/mountable-with-fields.d.ts +3 -2
  26. package/@types/utils/format-external-url.d.ts +1 -0
  27. package/@types/utils/utils.d.ts +1 -0
  28. package/lib/forms/controls/autocomplete.js +2 -2
  29. package/lib/forms/controls/autocomplete.js.map +1 -1
  30. package/lib/forms/controls/big-radio-group.js +4 -4
  31. package/lib/forms/controls/big-radio-group.js.map +1 -1
  32. package/lib/forms/controls/checkbox-group.js +5 -5
  33. package/lib/forms/controls/checkbox-group.js.map +1 -1
  34. package/lib/forms/controls/checkboxed-list-input.js +4 -4
  35. package/lib/forms/controls/checkboxed-list-input.js.map +1 -1
  36. package/lib/forms/controls/collapsible-checkbox-group.js +2 -2
  37. package/lib/forms/controls/collapsible-checkbox-group.js.map +1 -1
  38. package/lib/forms/controls/controls.js +3 -3
  39. package/lib/forms/controls/controls.js.map +1 -1
  40. package/lib/forms/controls/derived-frame.js +57 -0
  41. package/lib/forms/controls/derived-frame.js.map +1 -0
  42. package/lib/forms/controls/derived.js +15 -16
  43. package/lib/forms/controls/derived.js.map +1 -1
  44. package/lib/forms/controls/dropdown.js +23 -9
  45. package/lib/forms/controls/dropdown.js.map +1 -1
  46. package/lib/forms/controls/editable-collection-subset.js +6 -6
  47. package/lib/forms/controls/editable-collection-subset.js.map +1 -1
  48. package/lib/forms/controls/field-group.js +2 -2
  49. package/lib/forms/controls/field-group.js.map +1 -1
  50. package/lib/forms/controls/file.js +4 -4
  51. package/lib/forms/controls/file.js.map +1 -1
  52. package/lib/forms/controls/form-control.js +12 -1
  53. package/lib/forms/controls/form-control.js.map +1 -1
  54. package/lib/forms/controls/form-field-control.js.map +1 -1
  55. package/lib/forms/controls/form-header.js +2 -2
  56. package/lib/forms/controls/form-header.js.map +1 -1
  57. package/lib/forms/controls/hidden-input.js +3 -3
  58. package/lib/forms/controls/hidden-input.js.map +1 -1
  59. package/lib/forms/controls/html.js +2 -2
  60. package/lib/forms/controls/html.js.map +1 -1
  61. package/lib/forms/controls/multiple-files.js +4 -4
  62. package/lib/forms/controls/multiple-files.js.map +1 -1
  63. package/lib/forms/controls/number.js +2 -2
  64. package/lib/forms/controls/number.js.map +1 -1
  65. package/lib/forms/controls/photo.js +3 -3
  66. package/lib/forms/controls/photo.js.map +1 -1
  67. package/lib/forms/controls/simple-input.js +11 -12
  68. package/lib/forms/controls/simple-input.js.map +1 -1
  69. package/lib/forms/controls/textarea.js +2 -2
  70. package/lib/forms/controls/textarea.js.map +1 -1
  71. package/lib/forms/controls/tickable.js +12 -9
  72. package/lib/forms/controls/tickable.js.map +1 -1
  73. package/lib/forms/fields/field.js +1 -1
  74. package/lib/forms/fields/field.js.map +1 -1
  75. package/lib/forms/fields/pick-from-list.js +1 -1
  76. package/lib/forms/fields/pick-from-list.js.map +1 -1
  77. package/lib/forms/form.js +1 -1
  78. package/lib/forms/form.js.map +1 -1
  79. package/lib/page/list.js +1 -1
  80. package/lib/page/list.js.map +1 -1
  81. package/lib/page/mountable-with-fields.js +6 -2
  82. package/lib/page/mountable-with-fields.js.map +1 -1
  83. package/lib/utils/format-external-url.js +15 -0
  84. package/lib/utils/format-external-url.js.map +1 -0
  85. package/lib/utils/utils.js +1 -0
  86. package/lib/utils/utils.js.map +1 -1
  87. package/package.json +1 -1
  88. package/src/forms/controls/autocomplete.ts +3 -12
  89. package/src/forms/controls/big-radio-group.ts +9 -28
  90. package/src/forms/controls/checkbox-group.ts +10 -31
  91. package/src/forms/controls/checkboxed-list-input.ts +8 -4
  92. package/src/forms/controls/collapsible-checkbox-group.ts +4 -15
  93. package/src/forms/controls/controls.ts +2 -1
  94. package/src/forms/controls/derived-frame.ts +92 -0
  95. package/src/forms/controls/derived.ts +19 -18
  96. package/src/forms/controls/dropdown.ts +41 -29
  97. package/src/forms/controls/editable-collection-subset.ts +25 -25
  98. package/src/forms/controls/field-group.ts +3 -19
  99. package/src/forms/controls/file.ts +5 -12
  100. package/src/forms/controls/form-control.ts +12 -6
  101. package/src/forms/controls/form-field-control.ts +2 -7
  102. package/src/forms/controls/form-header.ts +6 -3
  103. package/src/forms/controls/hidden-input.ts +6 -6
  104. package/src/forms/controls/html.ts +4 -9
  105. package/src/forms/controls/multiple-files.ts +5 -10
  106. package/src/forms/controls/number.ts +3 -10
  107. package/src/forms/controls/photo.ts +7 -14
  108. package/src/forms/controls/simple-input.ts +21 -34
  109. package/src/forms/controls/textarea.ts +6 -14
  110. package/src/forms/controls/tickable.ts +16 -14
  111. package/src/forms/fields/field.ts +1 -1
  112. package/src/forms/fields/pick-from-list.ts +3 -4
  113. package/src/forms/form.ts +3 -1
  114. package/src/page/list.ts +7 -1
  115. package/src/page/mountable-with-fields.ts +23 -14
  116. package/src/utils/format-external-url.ts +13 -0
  117. package/src/utils/utils.ts +1 -0
@@ -3,7 +3,7 @@ export { FormFieldControl } from "./form-field-control";
3
3
  export { EditableCollectionSubset } from "./editable-collection-subset";
4
4
  export { Frame } from "./frame";
5
5
  export { CheckboxedListInput } from "./checkboxed-list-input";
6
- export { Dropdown } from "./dropdown";
6
+ export * from "./dropdown";
7
7
  export { SimpleInput } from "./simple-input";
8
8
  export { FormHeader } from "./form-header";
9
9
  export { FormParagraph } from "./form-paragraph";
@@ -12,6 +12,7 @@ export { HiddenInput } from "./hidden-input";
12
12
  export { Textarea } from "./textarea";
13
13
  export { FieldGroup } from "./field-group";
14
14
  export * from "./derived";
15
+ export * from "./derived-frame";
15
16
  export * from "./tickable";
16
17
  export * from "./checkbox";
17
18
  export * from "./radio";
@@ -0,0 +1,92 @@
1
+ import Router from "@koa/router";
2
+ import { hasShape, predicates } from "@sealcode/ts-predicates";
3
+ import { Context } from "koa";
4
+ import { ParsedUrlQuery } from "querystring";
5
+ import { FlatTemplatable, tempstream } from "tempstream";
6
+ import { makeSlug } from "../../utils/make-slug";
7
+ import { FormField } from "../fields/field";
8
+ import { Derived } from "./derived";
9
+ import { FormControlContext } from "./form-control";
10
+
11
+ export class DerivedFrame extends Derived<{
12
+ frame_id: string;
13
+ form_id: string;
14
+ field_name_prefix: string;
15
+ }> {
16
+ constructor(
17
+ public frame_name: string,
18
+ fields: FormField[],
19
+ public generateFrameContent: (
20
+ ctx: FormControlContext,
21
+ args: ParsedUrlQuery
22
+ ) => Promise<FlatTemplatable>,
23
+ // "a" is to make it a valid DOM id
24
+ public frame_id = "a" + makeSlug(frame_name)
25
+ ) {
26
+ super(
27
+ fields,
28
+ async (values, consts) => /* HTML */ `<turbo-frame
29
+ id="${consts.frame_id}"
30
+ src="./${consts.frame_id}?${Object.entries({
31
+ ...consts,
32
+ ...(values || {}),
33
+ })
34
+ .map(([key, value]) => `${key}=${value}`)
35
+ .join("&")}"
36
+ ></turbo-frame>`,
37
+ async (fctx) => ({
38
+ form_id: fctx.form_id,
39
+ field_name_prefix: fctx.field_name_prefix,
40
+ frame_id,
41
+ validate: fctx.validate,
42
+ })
43
+ );
44
+ }
45
+
46
+ renderBackendResponseFrame(_ctx: Context, content: FlatTemplatable) {
47
+ return tempstream/* HTML */ `<turbo-frame
48
+ ${content ? "" : `src="${this.frame_id}"`}
49
+ id="${this.frame_id}"
50
+ target="_top"
51
+ >
52
+ ${content || ""}
53
+ </turbo-frame>`;
54
+ }
55
+
56
+ mount(router: Router) {
57
+ router.get(this.frame_id, async (ctx) => {
58
+ const query = ctx.query;
59
+ if (
60
+ !hasShape(
61
+ {
62
+ field_name_prefix: predicates.string,
63
+ form_id: predicates.string,
64
+ validate: predicates.string,
65
+ },
66
+ query
67
+ )
68
+ ) {
69
+ ctx.body = this.renderBackendResponseFrame(
70
+ ctx,
71
+ "Missing query params"
72
+ );
73
+ return;
74
+ }
75
+ ctx.body = this.renderBackendResponseFrame(
76
+ ctx,
77
+ await this.generateFrameContent(
78
+ new FormControlContext(
79
+ ctx,
80
+ ctx.query,
81
+ [],
82
+ query.field_name_prefix,
83
+ query.form_id,
84
+ query.validate === "true" ? true : false
85
+ ),
86
+ ctx.query
87
+ )
88
+ );
89
+ });
90
+ return;
91
+ }
92
+ }
@@ -1,10 +1,10 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import { FlatTemplatable, tempstream } from "tempstream";
3
3
  import { FormField } from "../fields/field";
4
- import { FormControl } from "./form-control";
4
+ import { FormControl, FormControlContext } from "./form-control";
5
5
 
6
6
  export class Derived<
7
- Consts extends Record<string, unknown> | undefined
7
+ Consts extends Record<string, unknown | undefined>
8
8
  > extends FormControl {
9
9
  role = <const>"decoration";
10
10
 
@@ -13,19 +13,24 @@ export class Derived<
13
13
  public _render: (
14
14
  values: Record<string, string>,
15
15
  consts: Consts
16
- ) => string,
17
- public consts: Consts
16
+ ) => Promise<FlatTemplatable>,
17
+ public getConsts: (fctx: FormControlContext) => Promise<Consts>
18
18
  ) {
19
19
  super();
20
20
  }
21
21
 
22
- render(): FlatTemplatable {
23
- const id = "A" + randomUUID(); // the A makes it always a valid id, as it cannot start with a number
22
+ async render(fctx: FormControlContext): Promise<FlatTemplatable> {
23
+ // the A makes it always a valid id, as it cannot start with a number
24
+ const id = "A" + randomUUID();
25
+
26
+ const consts = this.getConsts(fctx);
27
+
28
+ const frontend_side_refresh_code = `render(getValues(), consts).then(result=>document.querySelector("#${id}").innerHTML=result)`;
24
29
  return tempstream/* HTML */ `<div>
25
30
  <script>
26
31
  (function () {
27
32
  const consts = ${"{" +
28
- Object.entries(this.consts || {})
33
+ Object.entries((await consts) || {})
29
34
  .map(([key, value]) => {
30
35
  return `"${key}": ${
31
36
  typeof value === "object"
@@ -61,24 +66,20 @@ export class Derived<
61
66
  form_container
62
67
  .querySelectorAll(\`input[name="\${field_name}"]\`)
63
68
  .forEach((input) => {
64
- input.onchange = () => {
65
- document.querySelector("#${id}").innerHTML =
66
- render(getValues(), consts);
67
- };
69
+ input.addEventListener("change", () => {
70
+ ${frontend_side_refresh_code};
71
+ });
68
72
  });
69
73
  }
70
74
  ${JSON.stringify(
71
75
  this.fields.map((field) => field.name)
72
76
  )}.forEach(setupWatcher);
73
- setTimeout(
74
- () =>
75
- (document.querySelector("#${id}").innerHTML =
76
- render(getValues(), consts)),
77
- 0
78
- );
77
+ setTimeout(() => {
78
+ ${frontend_side_refresh_code};
79
+ }, 0);
79
80
  })();
80
81
  </script>
81
- <div id="${id}">${this._render({}, this.consts)}</div>
82
+ <div id="${id}">${this._render({}, await consts)}</div>
82
83
  </div>`;
83
84
  }
84
85
  }
@@ -1,38 +1,40 @@
1
1
  import { Context } from "koa";
2
2
  import { FlatTemplatable, tempstream } from "tempstream";
3
3
  import { attribute } from "../../sanitize";
4
- import { PickFromListField } from "../fields/field";
4
+ import { FormField, PickFromListField } from "../fields/field";
5
5
  import { FormDataValue } from "../form";
6
+ import { FormControlContext } from "./form-control";
6
7
  import { SimpleInput } from "./simple-input";
7
8
 
8
- export class Dropdown extends SimpleInput<string | null> {
9
+ export type DropdownOptions = {
10
+ label: string;
11
+ autosubmit?: boolean;
12
+ autocomplete?: boolean;
13
+ };
14
+
15
+ export type DropdownOption = { value: string; label: string };
16
+
17
+ export class FreeformDropdown extends SimpleInput<string | null> {
9
18
  constructor(
10
- public field: PickFromListField<boolean>,
11
- public options: {
12
- label: string;
13
- autosubmit?: boolean;
14
- autocomplete?: boolean;
15
- } = {
19
+ public field: FormField<boolean, string | null>,
20
+ public _generateOptions: (ctx: Context) => Promise<DropdownOption[]>,
21
+ public options: DropdownOptions = {
16
22
  label: field.name,
17
23
  autosubmit: false,
18
24
  autocomplete: true,
19
25
  }
20
26
  ) {
21
- super(field);
27
+ super(field, options);
28
+ }
29
+
30
+ async generateOptions(ctx: Context): Promise<DropdownOption[]> {
31
+ return this._generateOptions(ctx);
22
32
  }
23
33
 
24
34
  async getInputAttributes(
25
- ctx: Context,
26
- data: Record<string, FormDataValue>,
27
- field_prefix: string,
28
- form_id: string
35
+ fctx: FormControlContext
29
36
  ): Promise<Record<string, string | boolean>> {
30
- const original = await super.getInputAttributes(
31
- ctx,
32
- data,
33
- field_prefix,
34
- form_id
35
- );
37
+ const original = await super.getInputAttributes(fctx);
36
38
  return {
37
39
  ...original,
38
40
  ...Object.fromEntries([
@@ -58,18 +60,28 @@ export class Dropdown extends SimpleInput<string | null> {
58
60
  return /* HTML */ tempstream`<select ${attributes_str}>
59
61
  ${
60
62
  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
61
- Promise.resolve(this.field.generateOptions(ctx)).then(
62
- (options) =>
63
- Object.entries(options).map(
64
- ([value, text]) =>
65
- `<option value="${attribute(value)}" ${
66
- (value || "") == picked_value
67
- ? "selected"
68
- : ""
69
- }>${text}</option>`
70
- )
63
+ Promise.resolve(this.generateOptions(ctx)).then((options) =>
64
+ options.map(
65
+ ({ value, label }) =>
66
+ `<option value="${attribute(value)}" ${
67
+ (value || "") == picked_value ? "selected" : ""
68
+ }>${label}</option>`
69
+ )
71
70
  )
72
71
  }
73
72
  </select>`;
74
73
  }
75
74
  }
75
+
76
+ export class Dropdown extends FreeformDropdown {
77
+ constructor(
78
+ public field: PickFromListField<boolean>,
79
+ public options: DropdownOptions = {
80
+ label: field.name,
81
+ autosubmit: false,
82
+ autocomplete: true,
83
+ }
84
+ ) {
85
+ super(field, (ctx) => field.generateOptions(ctx), options);
86
+ }
87
+ }
@@ -1,9 +1,8 @@
1
- import { Context } from "koa";
2
1
  import { FlatTemplatable, tempstream } from "tempstream";
3
2
  import { attribute } from "../../sanitize";
4
3
  import { PickFromListField } from "../fields/field";
5
- import { FormDataValue } from "../form";
6
4
  import { FormFieldControl } from "./controls";
5
+ import { FormControlContext } from "./form-control";
7
6
 
8
7
  /**
9
8
  * This control has own forms in it so if you want to use it you
@@ -22,15 +21,15 @@ export class EditableCollectionSubset extends FormFieldControl {
22
21
  super([field]);
23
22
  }
24
23
 
25
- async render(
26
- ctx: Context,
27
- data: Record<string, FormDataValue>
28
- ): Promise<FlatTemplatable> {
29
- const { parsed: values } = await this.field.getValue(ctx, data);
24
+ async render(fctx: FormControlContext): Promise<FlatTemplatable> {
25
+ const { parsed: values } = await this.field.getValue(
26
+ fctx.ctx,
27
+ fctx.data
28
+ );
30
29
 
31
30
  return tempstream/* HTML */ `<div>
32
31
  <ul>
33
- ${Promise.resolve(this.field.generateOptions(ctx)).then(
32
+ ${Promise.resolve(this.field.generateOptions(fctx.ctx)).then(
34
33
  (options) =>
35
34
  Object.entries(options)
36
35
  .filter(([value]) =>
@@ -41,7 +40,7 @@ export class EditableCollectionSubset extends FormFieldControl {
41
40
  <li>
42
41
  <form
43
42
  method="POST"
44
- action="${ctx.path}"
43
+ action="${fctx.ctx.path}"
45
44
  >
46
45
  <span>${text}</span>
47
46
  <input
@@ -66,23 +65,24 @@ export class EditableCollectionSubset extends FormFieldControl {
66
65
  )
67
66
  )}
68
67
  </ul>
69
- <form method="POST" action="${ctx.path}">
68
+ <form method="POST" action="${fctx.ctx.path}">
70
69
  <select name="${this.field.name}">
71
- ${Promise.resolve(this.field.generateOptions(ctx)).then(
72
- (options) =>
73
- Object.entries(options)
74
- .filter(
75
- ([value]) =>
76
- !(values || ([] as string[])).includes(
77
- value
78
- )
79
- )
80
- .map(
81
- ([value, text]) =>
82
- `<option value="${attribute(
83
- value
84
- )}">${text}</option>`
85
- )
70
+ ${Promise.resolve(
71
+ this.field.generateOptions(fctx.ctx)
72
+ ).then((options) =>
73
+ Object.entries(options)
74
+ .filter(
75
+ ([value]) =>
76
+ !(values || ([] as string[])).includes(
77
+ value
78
+ )
79
+ )
80
+ .map(
81
+ ([value, text]) =>
82
+ `<option value="${attribute(
83
+ value
84
+ )}">${text}</option>`
85
+ )
86
86
  )}
87
87
  </select>
88
88
  <input type="hidden" name="${this.actionname}" value="select" />
@@ -1,7 +1,7 @@
1
1
  import { Context } from "koa";
2
2
  import { FlatTemplatable, tempstream } from "tempstream";
3
3
  import { FormDataValue, FormMessage } from "../form";
4
- import { FormControl } from "./form-control";
4
+ import { FormControl, FormControlContext } from "./form-control";
5
5
  import { FormFieldControl } from "./form-field-control";
6
6
 
7
7
  export class FieldGroup extends FormFieldControl {
@@ -40,14 +40,7 @@ export class FieldGroup extends FormFieldControl {
40
40
  return `class="field-group__label"`;
41
41
  }
42
42
 
43
- async render(
44
- ctx: Context,
45
- data: Record<string, FormDataValue>,
46
- messages: FormMessage[],
47
- field_prefix: string,
48
- form_id: string,
49
- show_field_errors: boolean
50
- ): Promise<FlatTemplatable> {
43
+ async render(fctx: FormControlContext): Promise<FlatTemplatable> {
51
44
  const { label, classes } = this.options;
52
45
 
53
46
  return tempstream/* HTML */ `
@@ -67,16 +60,7 @@ export class FieldGroup extends FormFieldControl {
67
60
  </div>
68
61
  ${this.preFields()}
69
62
  <div class="field-group__fields">
70
- ${this.controls.map((control) =>
71
- control.render(
72
- ctx,
73
- data,
74
- messages,
75
- field_prefix,
76
- form_id,
77
- show_field_errors
78
- )
79
- )}
63
+ ${this.controls.map((control) => control.render(fctx))}
80
64
  </div>
81
65
  ${this.postFields()}
82
66
  </div>
@@ -2,6 +2,7 @@ import { Context } from "koa";
2
2
  import { FlatTemplatable, tempstream } from "tempstream";
3
3
  import { FileContainer } from "../fields/file";
4
4
  import { FormDataValue } from "../form";
5
+ import { FormControlContext } from "./form-control";
5
6
  import { SimpleInput } from "./simple-input";
6
7
 
7
8
  export class File extends SimpleInput<FileContainer> {
@@ -10,7 +11,7 @@ export class File extends SimpleInput<FileContainer> {
10
11
  }
11
12
 
12
13
  async renderFilePreview(
13
- ctx: Context,
14
+ _ctx: Context,
14
15
  file_data: { id?: string; filename?: string } | null
15
16
  ): Promise<FlatTemplatable> {
16
17
  return file_data?.filename || "";
@@ -32,18 +33,10 @@ export class File extends SimpleInput<FileContainer> {
32
33
  }
33
34
 
34
35
  async getInputAttributes(
35
- ctx: Context,
36
- data: Record<string, FormDataValue>,
37
- field_prefix: string,
38
- form_id: string
36
+ fctx: FormControlContext
39
37
  ): Promise<Record<string, string | boolean>> {
40
- const original = await super.getInputAttributes(
41
- ctx,
42
- data,
43
- field_prefix,
44
- form_id
45
- );
46
- const { parsed } = await this.field.getValue(ctx, data);
38
+ const original = await super.getInputAttributes(fctx);
39
+ const { parsed } = await this.field.getValue(fctx.ctx, fctx.data);
47
40
  return {
48
41
  ...original,
49
42
  name: this.field.name + "[new]",
@@ -3,14 +3,20 @@ import { Context } from "koa";
3
3
  import { FlatTemplatable } from "tempstream";
4
4
  import { FormDataValue, FormMessage } from "../form";
5
5
 
6
+ export class FormControlContext {
7
+ constructor(
8
+ public ctx: Context,
9
+ public data: Record<string, FormDataValue>,
10
+ public messages: FormMessage[],
11
+ public field_name_prefix: string,
12
+ public form_id: string,
13
+ public validate: boolean
14
+ ) {}
15
+ }
16
+
6
17
  export abstract class FormControl {
7
18
  abstract render(
8
- ctx: Context,
9
- data: Record<string, FormDataValue>,
10
- messages: FormMessage[],
11
- field_name_prefix: string,
12
- form_id: string,
13
- validate: boolean
19
+ fctx: FormControlContext
14
20
  ): FlatTemplatable | Promise<FlatTemplatable>;
15
21
  abstract role: "input" | "decoration" | "messages" | "submit";
16
22
 
@@ -2,7 +2,7 @@ import { Context } from "koa";
2
2
  import { FlatTemplatable } from "tempstream";
3
3
  import { FormField } from "../fields/field";
4
4
  import { FieldValueType, FormDataValue } from "../form";
5
- import { FormControl } from "./form-control";
5
+ import { FormControl, FormControlContext } from "./form-control";
6
6
 
7
7
  export type FormFieldsToTypes<FormFields extends readonly FormField[]> = {
8
8
  [Index in keyof FormFields]: FormFields[Index] extends FormField
@@ -17,11 +17,6 @@ export abstract class FormFieldControl extends FormControl {
17
17
  }
18
18
 
19
19
  abstract render(
20
- ctx: Context,
21
- data: Record<string, FormDataValue>,
22
- messages: [],
23
- field_name_prefix: string,
24
- form_id: string,
25
- validate: boolean
20
+ fctx: FormControlContext
26
21
  ): FlatTemplatable | Promise<FlatTemplatable>;
27
22
  }
@@ -1,16 +1,19 @@
1
1
  import { Context } from "koa";
2
2
  import { FormControl } from "./controls";
3
+ import { FormControlContext } from "./form-control";
3
4
 
4
5
  export class FormHeader extends FormControl {
5
6
  role = <const>"decoration";
6
7
  constructor(
7
8
  public text: string,
8
- public isVisible: (ctx: Context) => Promise<boolean> = async () => true
9
+ public isVisible: (
10
+ fctx: FormControlContext
11
+ ) => Promise<boolean> = async () => true
9
12
  ) {
10
13
  super();
11
14
  }
12
- async render(ctx: Context) {
13
- const isVsbl = await this.isVisible(ctx);
15
+ async render(fctx: FormControlContext) {
16
+ const isVsbl = await this.isVisible(fctx);
14
17
  return isVsbl ? `<h2>${this.text}</h2>` : "";
15
18
  }
16
19
  }
@@ -3,6 +3,7 @@ import { FlatTemplatable, tempstream } from "tempstream";
3
3
  import { attribute } from "../../sanitize";
4
4
  import { FormField } from "../fields/field";
5
5
  import { FormDataValue } from "../form";
6
+ import { FormControlContext } from "./form-control";
6
7
  import { FormFieldControl } from "./form-field-control";
7
8
 
8
9
  export class HiddenInput extends FormFieldControl {
@@ -10,13 +11,12 @@ export class HiddenInput extends FormFieldControl {
10
11
  super([field]);
11
12
  }
12
13
 
13
- async render(
14
- ctx: Context,
15
- data: Record<string, FormDataValue>
16
- ): Promise<FlatTemplatable> {
17
- const { raw } = await this.field.getValue(ctx, data);
14
+ async render(fctx: FormControlContext): Promise<FlatTemplatable> {
15
+ const { raw } = await this.field.getValue(fctx.ctx, fctx.data);
18
16
  return tempstream`<input type="hidden" name="${
19
17
  this.field.name
20
- }" value="${attribute((raw || "").toString())}">`;
18
+ }" value="${attribute((raw || "").toString())}" form="${
19
+ fctx.form_id
20
+ }">`;
21
21
  }
22
22
  }
@@ -2,9 +2,10 @@ import { Context } from "koa";
2
2
  import { FlatTemplatable } from "tempstream";
3
3
  import { FormDataValue, FormMessage } from "../form";
4
4
  import { FormControl } from "./controls";
5
+ import { FormControlContext } from "./form-control";
5
6
 
6
7
  type Renderer =
7
- | ((ctx: Context, form_id: string) => FlatTemplatable)
8
+ | ((fctx: FormControlContext) => FlatTemplatable)
8
9
  | FlatTemplatable;
9
10
 
10
11
  export class HTML extends FormControl {
@@ -26,18 +27,12 @@ export class HTML extends FormControl {
26
27
  }
27
28
  }
28
29
 
29
- async render(
30
- ctx: Context,
31
- _data: Record<string, FormDataValue>,
32
- _messages: FormMessage[],
33
- _field_name_prefix: string,
34
- form_id: string
35
- ): Promise<FlatTemplatable> {
30
+ async render(fctx: FormControlContext): Promise<FlatTemplatable> {
36
31
  if (typeof this.renderer === "string") {
37
32
  return this.renderer;
38
33
  }
39
34
  return typeof this.renderer == "function"
40
- ? this.renderer(ctx, form_id)
35
+ ? this.renderer(fctx)
41
36
  : this.renderer;
42
37
  }
43
38
  }
@@ -9,10 +9,10 @@ import { Context } from "koa";
9
9
  import { inputWrapper } from "../../utils/input-wrapper";
10
10
  import { FlatTemplatable, tempstream } from "tempstream";
11
11
  import { MultipleFiles as MultipleFilesField } from "../fields/multiple-files";
12
- import { FormDataValue } from "../form";
13
12
  import { FormFieldControl } from "./form-field-control";
14
13
  import { is, predicates } from "@sealcode/ts-predicates";
15
14
  import { renderAttributes } from "../../utils/render-attributes";
15
+ import { FormControlContext } from "./form-control";
16
16
 
17
17
  export type MultipleFilesOptions = {
18
18
  label?: string;
@@ -122,14 +122,9 @@ export class MultipleFiles extends FormFieldControl {
122
122
  }
123
123
 
124
124
  render(
125
- ctx: Context,
126
- _data: Record<string, FormDataValue>,
127
- _messages: [],
128
- field_name_prefix: string,
129
- form_id: string,
130
- validate: boolean
125
+ fctx: FormControlContext
131
126
  ): FlatTemplatable | Promise<FlatTemplatable> {
132
- return this.renderFrame(ctx, "");
127
+ return this.renderFrame(fctx.ctx, "");
133
128
  }
134
129
 
135
130
  getReferencingCollection() {
@@ -141,7 +136,7 @@ export class MultipleFiles extends FormFieldControl {
141
136
  }
142
137
 
143
138
  getFileField(): SealiousField | null {
144
- for (const [field_name, field] of Object.entries(
139
+ for (const [_, field] of Object.entries(
145
140
  this.getReferencingCollection().fields
146
141
  )) {
147
142
  if (field.handles_large_data) {
@@ -165,7 +160,7 @@ export class MultipleFiles extends FormFieldControl {
165
160
  }
166
161
 
167
162
  async renderFileItemPreview(
168
- fileItem: CollectionItem,
163
+ _fileItem: CollectionItem,
169
164
  file: SealiousFile
170
165
  ): Promise<FlatTemplatable> {
171
166
  return file.filename || "no filename";
@@ -2,6 +2,7 @@ import { Context } from "koa";
2
2
  import { FlatTemplatable } from "tempstream";
3
3
  import { FormField, NumberField } from "../fields/field";
4
4
  import { FormDataValue } from "../form";
5
+ import { FormControlContext } from "./form-control";
5
6
  import { SimpleInput, SimpleInputOptions } from "./simple-input";
6
7
 
7
8
  export type NumberOptions = SimpleInputOptions & Partial<{ step: number }>;
@@ -18,17 +19,9 @@ export class Number extends SimpleInput<number | null> {
18
19
  }
19
20
 
20
21
  async getInputAttributes(
21
- ctx: Context,
22
- data: Record<string, FormDataValue>,
23
- field_prefix: string,
24
- form_id: string
22
+ fctx: FormControlContext
25
23
  ): Promise<Record<string, string | boolean>> {
26
- const original = await super.getInputAttributes(
27
- ctx,
28
- data,
29
- field_prefix,
30
- form_id
31
- );
24
+ const original = await super.getInputAttributes(fctx);
32
25
  return {
33
26
  ...original,
34
27
  ...Object.fromEntries([