@sealcode/sealgen 0.8.0 → 0.8.2

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 (73) hide show
  1. package/@types/forms/controls/autocomplete.d.ts +20 -0
  2. package/@types/forms/controls/checkbox-group.d.ts +21 -0
  3. package/@types/forms/controls/checkbox.d.ts +8 -1
  4. package/@types/forms/controls/collapsible-checkbox-group.d.ts +12 -0
  5. package/@types/forms/controls/collapsible-field-group.d.ts +21 -0
  6. package/@types/forms/controls/controls.d.ts +4 -0
  7. package/@types/forms/controls/field-group.d.ts +12 -4
  8. package/@types/forms/controls/simple-input.d.ts +9 -2
  9. package/@types/forms/controls/tickable.d.ts +9 -12
  10. package/@types/forms/fields/enum-multiple.d.ts +18 -0
  11. package/@types/forms/fields/existing-sealious-value.d.ts +6 -0
  12. package/@types/forms/fields/field.d.ts +8 -4
  13. package/@types/forms/fields/proxy-field.d.ts +13 -0
  14. package/@types/page/list.d.ts +3 -3
  15. package/@types/utils/input-wrapper.d.ts +2 -1
  16. package/lib/forms/controls/autocomplete.js +27 -0
  17. package/lib/forms/controls/autocomplete.js.map +1 -0
  18. package/lib/forms/controls/checkbox-group.js +27 -0
  19. package/lib/forms/controls/checkbox-group.js.map +1 -0
  20. package/lib/forms/controls/checkbox.js +12 -1
  21. package/lib/forms/controls/checkbox.js.map +1 -1
  22. package/lib/forms/controls/collapsible-checkbox-group.js +17 -0
  23. package/lib/forms/controls/collapsible-checkbox-group.js.map +1 -0
  24. package/lib/forms/controls/collapsible-field-group.js +82 -0
  25. package/lib/forms/controls/collapsible-field-group.js.map +1 -0
  26. package/lib/forms/controls/controls.js +4 -0
  27. package/lib/forms/controls/controls.js.map +1 -1
  28. package/lib/forms/controls/field-group.js +39 -8
  29. package/lib/forms/controls/field-group.js.map +1 -1
  30. package/lib/forms/controls/simple-input.js +64 -29
  31. package/lib/forms/controls/simple-input.js.map +1 -1
  32. package/lib/forms/controls/tickable.js +4 -2
  33. package/lib/forms/controls/tickable.js.map +1 -1
  34. package/lib/forms/fields/boolean.js +7 -0
  35. package/lib/forms/fields/boolean.js.map +1 -1
  36. package/lib/forms/fields/enum-multiple.js +45 -0
  37. package/lib/forms/fields/enum-multiple.js.map +1 -0
  38. package/lib/forms/fields/existing-sealious-value.js +20 -0
  39. package/lib/forms/fields/existing-sealious-value.js.map +1 -0
  40. package/lib/forms/fields/field.js +7 -0
  41. package/lib/forms/fields/field.js.map +1 -1
  42. package/lib/forms/fields/pick-from-list.js +1 -1
  43. package/lib/forms/fields/pick-from-list.js.map +1 -1
  44. package/lib/forms/fields/proxy-field.js +24 -0
  45. package/lib/forms/fields/proxy-field.js.map +1 -0
  46. package/lib/mount.js +1 -0
  47. package/lib/mount.js.map +1 -1
  48. package/lib/page/list.js +9 -6
  49. package/lib/page/list.js.map +1 -1
  50. package/lib/sanitize.js +1 -1
  51. package/lib/sanitize.js.map +1 -1
  52. package/lib/utils/input-wrapper.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/forms/controls/autocomplete.ts +46 -0
  55. package/src/forms/controls/checkbox-group.ts +81 -0
  56. package/src/forms/controls/checkbox.ts +16 -1
  57. package/src/forms/controls/collapsible-checkbox-group.ts +31 -0
  58. package/src/forms/controls/collapsible-field-group.ts +96 -0
  59. package/src/forms/controls/controls.ts +4 -0
  60. package/src/forms/controls/field-group.ts +60 -9
  61. package/src/forms/controls/simple-input.ts +90 -42
  62. package/src/forms/controls/tickable.ts +14 -10
  63. package/src/forms/fields/boolean.ts +6 -0
  64. package/src/forms/fields/enum-multiple.ts +58 -0
  65. package/src/forms/fields/existing-sealious-value.ts +22 -0
  66. package/src/forms/fields/field.ts +14 -2
  67. package/src/forms/fields/pick-from-list.ts +1 -1
  68. package/src/forms/fields/proxy-field.ts +24 -0
  69. package/src/forms/forms.scss +11 -0
  70. package/src/mount.ts +1 -0
  71. package/src/page/list.ts +12 -10
  72. package/src/sanitize.ts +1 -1
  73. package/src/utils/input-wrapper.ts +5 -2
@@ -0,0 +1,31 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable } from "tempstream";
3
+ import { FormDataValue, FormMessage } from "../form";
4
+ import { CheckboxWithValue } from "./checkbox";
5
+ import { FieldGroup, FormFieldControl } from "./controls";
6
+ import { CheckboxGroup, DefaultCheckboxOptions } from "./checkbox-group";
7
+ import { CollapsibleFieldGroup } from "./collapsible-field-group";
8
+
9
+ export class CollapsibleCheckboxGroup extends CheckboxGroup<
10
+ DefaultCheckboxOptions & {
11
+ textWhenOpen?: string;
12
+ textWhenClosed?: string;
13
+ groupID?: string;
14
+ }
15
+ > {
16
+ wrapGroup(
17
+ ctx: Context,
18
+ data: Record<string, FormDataValue>,
19
+ messages: FormMessage[],
20
+ field_prefix: string,
21
+ form_id: string,
22
+ checkboxes: CheckboxWithValue[]
23
+ ): Promise<FlatTemplatable> {
24
+ return new CollapsibleFieldGroup(checkboxes, {
25
+ label: this.options.label,
26
+ textWhenOpen: this.options.textWhenOpen,
27
+ textWhenClosed: this.options.textWhenClosed,
28
+ groupID: this.options.groupID,
29
+ }).render(ctx, data, messages, field_prefix, form_id);
30
+ }
31
+ }
@@ -0,0 +1,96 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable, tempstream } from "tempstream";
3
+ import { FormDataValue, FormMessage } from "../form";
4
+ import { FieldGroup } from "./field-group";
5
+ import { randomUUID } from "crypto";
6
+ import { FormControl } from "./form-control";
7
+
8
+ type CollapsibleFieldGroupOptions = {
9
+ label?: string;
10
+ classes?: string;
11
+ openOnStart?: boolean;
12
+ textWhenOpen?: string;
13
+ textWhenClosed?: string;
14
+ groupID?: string;
15
+ };
16
+
17
+ export class CollapsibleFieldGroup extends FieldGroup {
18
+ public collapseID: string;
19
+ constructor(
20
+ public controls: FormControl[],
21
+ public options: CollapsibleFieldGroupOptions = {}
22
+ ) {
23
+ super([]);
24
+ if (this.options.groupID == undefined) {
25
+ this.options.groupID = "A" + randomUUID(); // "A" is needed as ids always have to start with a letter
26
+ }
27
+ this.collapseID = this.options.groupID + "collapse";
28
+ }
29
+
30
+ getGroupLabelProps() {
31
+ return super.getGroupLabelProps() + ` for="${this.collapseID}"`;
32
+ }
33
+
34
+ preLabelContainer(): FlatTemplatable {
35
+ return /* HTML */ `<style>
36
+ input#${this.collapseID} {
37
+ display: none;
38
+ }
39
+
40
+ input#${this.collapseID} ~ .field-group__fields {
41
+ display: none;
42
+ }
43
+
44
+ input#${this.collapseID}:checked ~ .field-group__fields {
45
+ display: block;
46
+ }
47
+
48
+ input#${this.collapseID}
49
+ ~ .field-group__label-container
50
+ .field-group__collapse-label-text--open {
51
+ display: none;
52
+ }
53
+
54
+ input#${this.collapseID}:checked
55
+ ~ .field-group__label-container
56
+ .field-group__collapse-label-text--open {
57
+ display: inline-block;
58
+ }
59
+
60
+ input#${this.collapseID}
61
+ ~ .field-group__label-container
62
+ .field-group__collapse-label-text--closed {
63
+ display: inline-block;
64
+ }
65
+
66
+ input#${this.collapseID}:checked
67
+ ~ .field-group__label-container
68
+ .field-group__collapse-label-text--closed {
69
+ display: none;
70
+ }
71
+ </style>
72
+ <input
73
+ type="checkbox"
74
+ id="${this.collapseID}"
75
+ ${this.options.openOnStart ? "checked" : ""}
76
+ />`;
77
+ }
78
+
79
+ postLabel(): FlatTemplatable {
80
+ return /* HTML */ `<label
81
+ class="field-group__collapse-label"
82
+ for="${this.collapseID}"
83
+ >
84
+ <span
85
+ class="field-group__collapse-label-text field-group__collapse-label-text--open"
86
+ >
87
+ ${this.options.textWhenOpen || "close"}
88
+ </span>
89
+ <span
90
+ class="field-group__collapse-label-text field-group__collapse-label-text--closed"
91
+ >
92
+ ${this.options.textWhenClosed || "open"}
93
+ </span>
94
+ </label>`;
95
+ }
96
+ }
@@ -16,3 +16,7 @@ export * from "./tickable";
16
16
  export * from "./checkbox";
17
17
  export * from "./radio";
18
18
  export * from "./big-radio-group";
19
+ export * from "./checkbox-group";
20
+ export * from "./collapsible-field-group";
21
+ export * from "./collapsible-checkbox-group";
22
+ export * from "./autocomplete";
@@ -1,32 +1,83 @@
1
1
  import { Context } from "koa";
2
2
  import { FlatTemplatable, tempstream } from "tempstream";
3
- import { FormDataValue } from "../form";
3
+ import { FormDataValue, FormMessage } from "../form";
4
4
  import { FormControl } from "./form-control";
5
+ import { FormFieldControl } from "./form-field-control";
5
6
 
6
- export class FieldGroup extends FormControl {
7
+ export class FieldGroup extends FormFieldControl {
7
8
  constructor(
8
9
  public controls: FormControl[],
9
- public options: { label?: string; classes?: string } = {}
10
+ public options: {
11
+ label?: string;
12
+ classes?: string;
13
+ containerID?: string;
14
+ } = {}
10
15
  ) {
11
- super();
16
+ super([]);
17
+ }
18
+
19
+ preLabel(): FlatTemplatable {
20
+ return "";
21
+ }
22
+
23
+ preLabelContainer(): FlatTemplatable {
24
+ return "";
25
+ }
26
+
27
+ postLabel(): FlatTemplatable {
28
+ return "";
29
+ }
30
+
31
+ preFields(): FlatTemplatable {
32
+ return "";
33
+ }
34
+
35
+ postFields(): FlatTemplatable {
36
+ return "";
37
+ }
38
+
39
+ getGroupLabelProps(): FlatTemplatable {
40
+ return `class="field-group__label"`;
12
41
  }
13
42
 
14
43
  async render(
15
44
  ctx: Context,
16
- data: Record<string, FormDataValue>
45
+ data: Record<string, FormDataValue>,
46
+ messages: FormMessage[],
47
+ field_prefix: string,
48
+ form_id: string
17
49
  ): Promise<FlatTemplatable> {
18
50
  const { label, classes } = this.options;
19
51
 
20
52
  return tempstream/* HTML */ `
21
- <div class="field-group ${classes || ""}">
22
- ${label ? `<label>${label}</label>` : ""}
53
+ <div
54
+ class="field-group ${classes || ""}"
55
+ ${this.options.containerID
56
+ ? `id="${this.options.containerID}"`
57
+ : ""}
58
+ >
59
+ ${this.preLabelContainer()}
60
+ <div class="field-group__label-container">
61
+ ${this.preLabel()}
62
+ ${label
63
+ ? `<label ${this.getGroupLabelProps()}>${label}</label>`
64
+ : ""}
65
+ ${this.postLabel()}
66
+ </div>
67
+ ${this.preFields()}
23
68
  <div class="field-group__fields">
24
69
  ${this.controls.map((control) =>
25
- control.render(ctx, data, [], "", "")
70
+ control.render(
71
+ ctx,
72
+ data,
73
+ messages,
74
+ field_prefix,
75
+ form_id
76
+ )
26
77
  )}
27
78
  </div>
79
+ ${this.postFields()}
28
80
  </div>
29
81
  `;
30
82
  }
31
- role = <const>"decoration";
32
83
  }
@@ -1,9 +1,9 @@
1
1
  import { Context } from "koa";
2
- import { FlatTemplatable } from "tempstream";
2
+ import { FlatTemplatable, tempstream } from "tempstream";
3
3
  import { attribute } from "../../sanitize";
4
4
  import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
5
5
  import { FormField } from "../fields/field";
6
- import { FormDataValue } from "../form";
6
+ import { FormDataValue, FormMessage } from "../form";
7
7
  import { FormFieldControl } from "./controls";
8
8
 
9
9
  export class SimpleInput extends FormFieldControl {
@@ -37,58 +37,106 @@ export class SimpleInput extends FormFieldControl {
37
37
  super([field]);
38
38
  }
39
39
 
40
+ getType(): string {
41
+ return this.options.type || "text";
42
+ }
43
+
44
+ getLabel(): string {
45
+ return this.options.label != undefined
46
+ ? this.options.label
47
+ : this.field.name;
48
+ }
49
+
50
+ getID(): string {
51
+ return this.options.id || this.field.name;
52
+ }
53
+
54
+ getPlaceholder(): string {
55
+ return this.options.placeholder || this.getType();
56
+ }
57
+
58
+ async getInputAttributes(
59
+ ctx: Context,
60
+ data: Record<string, FormDataValue>,
61
+ field_prefix: string,
62
+ form_id: string
63
+ ): Promise<Record<string, string | true>> {
64
+ const readonly = this.options.readonly || false;
65
+ const required = this.field.required;
66
+ const { parsed, raw, valid } = await this.field.getValue(ctx, data);
67
+ return Object.fromEntries([
68
+ <const>["id", this.getID()],
69
+ <const>["type", this.getType()],
70
+ <const>["name", `${field_prefix}${this.field.name}`],
71
+ <const>[
72
+ "value",
73
+ !valid && raw
74
+ ? raw.toString()
75
+ : parsed !== undefined && parsed !== null
76
+ ? ((parsed as any)?.toString() as string) || ""
77
+ : "",
78
+ ],
79
+ ...(form_id ? [["form", form_id]] : []),
80
+ <const>["placeholder", this.getPlaceholder()],
81
+ ...(readonly ? [["readonly", true]] : []),
82
+ ...(required ? [["required", true]] : []),
83
+ ...(!this.options.autocomplete ? [["autocomplete", "off"]] : []),
84
+ ...(this.options.step ? [["step", this.options.step]] : []),
85
+ ]);
86
+ }
87
+
88
+ renderAttributes(attrs: Record<string, string | true>) {
89
+ return Object.entries(attrs)
90
+ .map(([key, value]) => {
91
+ if (value === true) {
92
+ return key;
93
+ } else {
94
+ return `${key}="${attribute(value)}"`;
95
+ }
96
+ })
97
+ .join(" ");
98
+ }
99
+
100
+ async postInput(ctx: Context): Promise<FlatTemplatable> {
101
+ return "";
102
+ }
103
+
40
104
  async render<Fields extends Record<string, FormField>>(
41
105
  ctx: Context,
42
106
  data: Record<string, FormDataValue>,
43
- _: unknown[],
107
+ _messages: FormMessage[],
44
108
  field_prefix: string,
45
109
  form_id: string
46
110
  ): Promise<FlatTemplatable> {
47
- const id = this.options.id || this.field.name;
48
- const label =
49
- this.options.label != undefined
50
- ? this.options.label
51
- : this.field.name;
52
- const type = this.options.type || "text";
53
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
111
+ const id = this.getID();
112
+ const label = this.getLabel();
113
+ const type = this.getType();
114
+
54
115
  const { parsed, raw, valid, message } = await this.field.getValue(
55
116
  ctx,
56
117
  data
57
118
  );
58
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
59
- const placeholder = this.options.placeholder || type;
60
- const readonly = this.options.readonly || false;
61
- const required = this.field.required;
119
+ const placeholder = this.getPlaceholder();
62
120
 
63
- return /* HTML */ inputWrapper(
64
- [id, type, getRequiredClass(required)],
65
- `
66
- <label for="${id}">${label}</label>
67
- <input
68
- id="${id}"
69
- type="${type}"
70
- name="${field_prefix}${this.field.name}"
71
- value="${attribute(
72
- !valid && raw
73
- ? raw.toString()
74
- : parsed !== undefined && parsed !== null
75
- ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
76
- ((parsed as any)?.toString() as string) || ""
77
- : ""
78
- )}"
79
- ${form_id ? `form="${form_id}"` : ""}
80
- placeholder="${placeholder}"
81
- ${readonly ? "readonly" : ""}
82
- ${required ? "required" : ""}
83
- ${!this.options.autocomplete ? `autocomplete="off"` : ""}
84
- ${this.options.step ? `step="${this.options.step}"` : ""}
85
- />
86
- ${
87
- ~valid && !this.options.hide_errors
121
+ return inputWrapper(
122
+ [id, type, getRequiredClass(this.field.required)],
123
+ tempstream/* HTML */ `
124
+ <label for="${id}">${label}</label>
125
+ <input
126
+ ${this.renderAttributes(
127
+ await this.getInputAttributes(
128
+ ctx,
129
+ data,
130
+ field_prefix,
131
+ form_id
132
+ )
133
+ )}
134
+ />
135
+ ${this.postInput(ctx)}
136
+ ${~valid && !this.options.hide_errors
88
137
  ? `<div class="input__error">${message}</div>`
89
- : ""
90
- }
91
- `
138
+ : ""}
139
+ `
92
140
  );
93
141
  }
94
142
  }
@@ -6,15 +6,18 @@ import { FormField } from "../fields/field";
6
6
  import { FormDataValue } from "../form";
7
7
  import { FormFieldControl } from "./controls";
8
8
 
9
+ export type TickableOptions = {
10
+ id?: string;
11
+ name?: string;
12
+ label?: string;
13
+ hide_errors?: boolean;
14
+ readonly?: boolean;
15
+ };
16
+
9
17
  export abstract class Tickable<T extends unknown> extends FormFieldControl {
10
18
  constructor(
11
19
  public field: FormField<boolean, T>,
12
- public options: {
13
- id?: string;
14
- label?: string;
15
- hide_errors?: boolean;
16
- readonly?: boolean;
17
- } = {}
20
+ public options: TickableOptions = {}
18
21
  ) {
19
22
  super([field]);
20
23
  }
@@ -69,6 +72,8 @@ export abstract class Tickable<T extends unknown> extends FormFieldControl {
69
72
  const readonly = this.options.readonly || false;
70
73
  const required = this.field.required;
71
74
 
75
+ const value = this.getValueAttribute(ctx, data, parsed);
76
+
72
77
  return inputWrapper(
73
78
  this.getWrapperClasses(ctx, data, parsed),
74
79
 
@@ -76,10 +81,9 @@ export abstract class Tickable<T extends unknown> extends FormFieldControl {
76
81
  <input
77
82
  id="${id}"
78
83
  type="${this.type}"
79
- name="${field_prefix}${this.field.name}"
80
- value="${attribute(
81
- this.getValueAttribute(ctx, data, parsed)
82
- )}"
84
+ name="${field_prefix}${this.options.name ||
85
+ this.field.name}"
86
+ ${value ? `value="${attribute(value)}"` : ""}
83
87
  ${form_id ? `form="${form_id}"` : ""}
84
88
  ${this.isChecked(ctx, data, parsed) ? "checked" : ""}
85
89
  ${readonly ? "readonly" : ""}
@@ -40,6 +40,12 @@ export class Boolean<Required extends boolean> extends FormField<
40
40
  parsed_value: truthy.includes(value),
41
41
  error: null,
42
42
  };
43
+ } else if (is(value, predicates.undefined)) {
44
+ return {
45
+ parsable: true,
46
+ parsed_value: false,
47
+ error: null,
48
+ };
43
49
  } else {
44
50
  return {
45
51
  parsable: false,
@@ -0,0 +1,58 @@
1
+ import { is, predicates } from "@sealcode/ts-predicates";
2
+ import { Context } from "koa";
3
+ import { FormDataValue } from "../form";
4
+ import { FieldParseResult, FormField } from "./field";
5
+
6
+ export class EnumMultipleField<
7
+ Options extends readonly string[],
8
+ Required extends boolean
9
+ > extends FormField<Required, string[]> {
10
+ constructor(
11
+ required: Required,
12
+ public options: Options,
13
+ public error_message: (options: Options) => string = (options) =>
14
+ options.join(", ")
15
+ ) {
16
+ super(required);
17
+ }
18
+
19
+ predicate = predicates.enum<Options>(this.options);
20
+
21
+ public async isValueValid(
22
+ _: Context,
23
+ values: string[]
24
+ ): Promise<{ valid: boolean; message: string }> {
25
+ for (const value of values) {
26
+ if (!is(value, predicates.string)) {
27
+ return { valid: false, message: "expected a string" };
28
+ }
29
+ if (!this.options.includes(value)) {
30
+ return { valid: false, message: "invalid value" };
31
+ }
32
+ }
33
+ return { valid: true, message: "" };
34
+ }
35
+
36
+ async parse(
37
+ _: Context,
38
+ value: FormDataValue
39
+ ): Promise<FieldParseResult<string[]>> {
40
+ if (is(value, predicates.array(predicates.string))) {
41
+ return {
42
+ parsable: true,
43
+ parsed_value: value,
44
+ error: null,
45
+ };
46
+ } else {
47
+ return {
48
+ parsable: false,
49
+ parsed_value: null,
50
+ error: "Please enter a string",
51
+ };
52
+ }
53
+ }
54
+
55
+ public getEmptyValue(): string[] {
56
+ return [];
57
+ }
58
+ }
@@ -0,0 +1,22 @@
1
+ import { Field } from "sealious";
2
+ import { PickFromListField } from "./pick-from-list";
3
+
4
+ export class ExistingSealiousValue<
5
+ Required extends boolean
6
+ > extends PickFromListField<Required> {
7
+ constructor(required: Required, field: Field) {
8
+ super(required, async (ctx) => {
9
+ const { items } = await field.collection.list(ctx.$context).fetch();
10
+ return Object.fromEntries(
11
+ items.map((item) => {
12
+ const value = item.get(field.name);
13
+ return [value, value];
14
+ })
15
+ );
16
+ });
17
+ }
18
+
19
+ getEmptyValue() {
20
+ return "";
21
+ }
22
+ }
@@ -37,12 +37,16 @@ export function fieldsToShape<Fields extends Record<string, FormField>>(
37
37
  }
38
38
 
39
39
  export type FieldParseResult<T> =
40
+ | {
41
+ parsable: true;
42
+ error: null;
43
+ parsed_value: T;
44
+ }
40
45
  | {
41
46
  parsable: false;
42
47
  error: string;
43
48
  parsed_value: null;
44
- }
45
- | { parsable: true; error: null; parsed_value: T };
49
+ };
46
50
 
47
51
  export abstract class FormField<
48
52
  Required extends boolean = boolean,
@@ -51,6 +55,7 @@ export abstract class FormField<
51
55
  name: string;
52
56
  constructor(public readonly required: Required) {}
53
57
  predicate: Predicate = predicates.unknown;
58
+ mapToFilter: (parsed: ParsedValue) => unknown = (value) => value;
54
59
 
55
60
  init(fieldname: string): void {
56
61
  this.name = fieldname;
@@ -135,6 +140,11 @@ export abstract class FormField<
135
140
  raw: all_form_values[this.name],
136
141
  };
137
142
  }
143
+
144
+ setMapToFilter(fn: (parsed: ParsedValue) => unknown) {
145
+ this.mapToFilter = fn;
146
+ return this;
147
+ }
138
148
  }
139
149
 
140
150
  export * from "./simple-form-field";
@@ -146,3 +156,5 @@ export * from "./regexp";
146
156
  export * from "./email";
147
157
  export * from "./enum";
148
158
  export * from "./boolean";
159
+ export * from "./enum-multiple";
160
+ export * from "./existing-sealious-value";
@@ -38,7 +38,7 @@ export class PickFromListField<
38
38
  if (!is(value, predicates.string)) {
39
39
  return { valid: false, message: "not a string" };
40
40
  }
41
- if (!Object.keys(options).includes(value)) {
41
+ if (this.required && !Object.keys(options).includes(value)) {
42
42
  return {
43
43
  valid: false,
44
44
  message: `"${value}" is not one of the options`,
@@ -0,0 +1,24 @@
1
+ import { FormField } from "./field";
2
+
3
+ export class ProxyFormField<T> extends FormField<boolean, T> {
4
+ constructor(
5
+ required: boolean,
6
+ public name: string,
7
+ public empty_value: T,
8
+ public parsed_value: T
9
+ ) {
10
+ super(required);
11
+ }
12
+
13
+ getEmptyValue() {
14
+ return this.empty_value;
15
+ }
16
+
17
+ async parse() {
18
+ return <const>{
19
+ parsable: true,
20
+ error: null,
21
+ parsed_value: this.parsed_value,
22
+ };
23
+ }
24
+ }
@@ -16,3 +16,14 @@
16
16
  .form__error {
17
17
  color: #ff4136;
18
18
  }
19
+
20
+ .form-input__wrapper--checkbox {
21
+ display: flex;
22
+ flex-flow: row nowrap;
23
+ align-items: center;
24
+ & > input[type="checkbox"] {
25
+ margin-right: 10px;
26
+ width: 15px;
27
+ height: 15px;
28
+ }
29
+ }
package/src/mount.ts CHANGED
@@ -35,6 +35,7 @@ export function mount(router: Router, url: string, mountable: Mountable): void {
35
35
  ctx.set("content-type", "text/html;charset=utf-8");
36
36
  const access_result = await mountable.canAccess(ctx);
37
37
  if (!access_result.canAccess) {
38
+ ctx.status = 403;
38
39
  ctx.body = access_result.message || "no access";
39
40
  return;
40
41
  }