@sealcode/sealgen 0.19.2 → 0.19.4

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 (57) hide show
  1. package/@types/forms/controls/hybrid.d.ts +10 -0
  2. package/@types/forms/controls/simple-input.d.ts +1 -0
  3. package/@types/forms/fields/field.d.ts +2 -0
  4. package/@types/forms/fields/fields.d.ts +2 -0
  5. package/@types/forms/fields/phone-number-with-country-code.d.ts +10 -0
  6. package/@types/forms/fields/phone-number.d.ts +17 -0
  7. package/@types/forms/fields/simple-form-field.d.ts +5 -3
  8. package/@types/utils/input-wrapper.d.ts +2 -0
  9. package/lib/forms/controls/checkboxed-list-input.js +2 -2
  10. package/lib/forms/controls/dropdown.js +3 -3
  11. package/lib/forms/controls/hybrid.js +41 -0
  12. package/lib/forms/controls/multiple-files.js +3 -1
  13. package/lib/forms/controls/simple-input.js +7 -2
  14. package/lib/forms/controls/single-reference.js +1 -1
  15. package/lib/forms/controls/table.js +1 -1
  16. package/lib/forms/fields/boolean.js +1 -1
  17. package/lib/forms/fields/checkboxed-list.js +3 -1
  18. package/lib/forms/fields/coordinates.js +1 -1
  19. package/lib/forms/fields/date.js +4 -1
  20. package/lib/forms/fields/datetime.js +1 -1
  21. package/lib/forms/fields/deep-reverse-single-reference-table.js +1 -1
  22. package/lib/forms/fields/email.js +4 -1
  23. package/lib/forms/fields/field.js +4 -0
  24. package/lib/forms/fields/fields.js +2 -0
  25. package/lib/forms/fields/phone-number-with-country-code.js +50 -0
  26. package/lib/forms/fields/phone-number.js +33 -0
  27. package/lib/forms/fields/regexp.js +1 -1
  28. package/lib/forms/fields/simple-form-field.js +8 -4
  29. package/lib/forms/fields/single-reference.js +1 -1
  30. package/lib/forms/fields/structured-array.js +1 -1
  31. package/lib/forms/fields/table.js +1 -1
  32. package/lib/utils/input-wrapper.js +13 -0
  33. package/package.json +2 -1
  34. package/src/forms/controls/checkboxed-list-input.ts +4 -2
  35. package/src/forms/controls/dropdown.ts +3 -3
  36. package/src/forms/controls/hybrid.ts +44 -0
  37. package/src/forms/controls/multiple-files.ts +5 -1
  38. package/src/forms/controls/simple-input.ts +9 -1
  39. package/src/forms/controls/single-reference.ts +1 -1
  40. package/src/forms/controls/table.ts +1 -1
  41. package/src/forms/fields/boolean.ts +1 -1
  42. package/src/forms/fields/checkboxed-list.ts +3 -1
  43. package/src/forms/fields/coordinates.ts +1 -1
  44. package/src/forms/fields/date.ts +4 -1
  45. package/src/forms/fields/datetime.ts +1 -1
  46. package/src/forms/fields/deep-reverse-single-reference-table.ts +1 -1
  47. package/src/forms/fields/email.ts +4 -1
  48. package/src/forms/fields/field.ts +6 -0
  49. package/src/forms/fields/fields.ts +2 -0
  50. package/src/forms/fields/phone-number-with-country-code.ts +59 -0
  51. package/src/forms/fields/phone-number.ts +39 -0
  52. package/src/forms/fields/regexp.ts +1 -1
  53. package/src/forms/fields/simple-form-field.ts +14 -6
  54. package/src/forms/fields/single-reference.ts +1 -1
  55. package/src/forms/fields/structured-array.ts +1 -1
  56. package/src/forms/fields/table.ts +1 -1
  57. package/src/utils/input-wrapper.ts +14 -0
@@ -0,0 +1,10 @@
1
+ import { FlatTemplatable } from "tempstream";
2
+ import { FormControlContext } from "./form-control.js";
3
+ import { FormFieldControl } from "./form-field-control.js";
4
+ import { FormField } from "../fields/field.js";
5
+ export declare class Hybrid extends FormFieldControl {
6
+ main_field: FormField;
7
+ subfields: Record<string, FormField>;
8
+ constructor(main_field: FormField, subfields: Record<string, FormField>);
9
+ render(fctx: FormControlContext): Promise<FlatTemplatable>;
10
+ }
@@ -16,6 +16,7 @@ export type SimpleInputOptions = {
16
16
  step?: number;
17
17
  suffix?: string;
18
18
  pattern?: string;
19
+ inputmode?: string;
19
20
  };
20
21
  export declare class SimpleInput<Options extends SimpleInputOptions = SimpleInputOptions> extends FormFieldControl {
21
22
  field: FormField<boolean, any>;
@@ -32,11 +32,13 @@ export type FieldParseResult<T> = {
32
32
  export declare abstract class FormField<Required extends boolean = boolean, ParsedValue = unknown, DefaultFormControl extends FormControl = FormControl, SealiousValue = any> {
33
33
  readonly required: Required;
34
34
  name: string;
35
+ label: string;
35
36
  constructor(required: Required);
36
37
  predicate: Predicate;
37
38
  mapToFilter: (parsed: ParsedValue) => unknown;
38
39
  init(): Promise<void>;
39
40
  setName(name: string): this;
41
+ setLabel(label: string): this;
40
42
  _validate(ctx: Context, value: ParsedValue): Promise<FormFieldValidationResponse>;
41
43
  abstract getEmptyValue(): ParsedValue;
42
44
  isValueValid(_ctx: Context, _value: ParsedValue): Promise<FormFieldValidationResponse>;
@@ -20,6 +20,8 @@ export * from "./markdown.js";
20
20
  export * from "./multiple-files.js";
21
21
  export * from "./number.js";
22
22
  export * from "./password.js";
23
+ export * from "./phone-number.js";
24
+ export * from "./phone-number-with-country-code.js";
23
25
  export * from "./pick-from-list-sealious.js";
24
26
  export * from "./pick-from-list.js";
25
27
  export * from "./regexp.js";
@@ -0,0 +1,10 @@
1
+ import { Context } from "koa";
2
+ import { FieldTypes as SealiousFieldTypes } from "sealious";
3
+ import { FormControl } from "../controls/controls.js";
4
+ import { FormDataValue } from "../form-types.js";
5
+ import { FieldParseResult, FormField } from "./field.js";
6
+ export declare class PhoneNumberWithCountryCode<Required extends boolean> extends FormField<Required, SealiousFieldTypes.PhoneNumberValue> {
7
+ getEmptyValue(): SealiousFieldTypes.PhoneNumberValue;
8
+ parse(ctx: Context, raw_value: FormDataValue): Promise<FieldParseResult<SealiousFieldTypes.PhoneNumberValue>>;
9
+ getControl(): FormControl;
10
+ }
@@ -0,0 +1,17 @@
1
+ import { Context } from "koa";
2
+ import { FormControl } from "../controls/controls.js";
3
+ import { FormDataValue } from "../form-types.js";
4
+ import { FormField } from "./field.js";
5
+ export declare class PhoneNumberWithoutCountryCode<Required extends boolean> extends FormField<Required, string> {
6
+ getEmptyValue(): string;
7
+ parse(ctx: Context, raw_value: FormDataValue): Promise<{
8
+ parsable: true;
9
+ parsed_value: string;
10
+ error: null;
11
+ } | {
12
+ parsable: false;
13
+ parsed_value: null;
14
+ error: string;
15
+ }>;
16
+ getControl(): FormControl;
17
+ }
@@ -1,4 +1,4 @@
1
- import { FormDataValue } from "../form-types.js";
1
+ import { FormData, FormDataValue } from "../form-types.js";
2
2
  import { Context } from "koa";
3
3
  import { FieldParseResult, FormField } from "./field.js";
4
4
  import { SimpleInput } from "../controls/simple-input.js";
@@ -8,14 +8,16 @@ import { FormControlContext } from "../controls/form-control.js";
8
8
  export declare abstract class SimpleFormField<Required extends boolean, DefaultControl extends FormControl = SimpleInput, Parsed = string> extends FormField<Required, Parsed, DefaultControl> {
9
9
  type: string;
10
10
  predicate: import("@sealcode/ts-predicates").Predicate<string>;
11
+ placeholder: string;
11
12
  constructor(required: Required, type?: string);
12
13
  sealiousValueToForm(_ctx: Context, value: unknown): Promise<FormDataValue>;
14
+ setPlaceholder(placeholder: string): this;
13
15
  }
14
16
  export declare class TextBasedSimpleField<Required extends boolean, DefaultControl extends FormControl = SimpleInput> extends SimpleFormField<Required, DefaultControl, string | null> {
15
17
  parse(_: Context, raw_value: FormDataValue): Promise<FieldParseResult<string>>;
16
18
  getSealiousCreateValue(fctx: FormControlContext): Promise<string | null>;
17
- postSealiousCreate(ctx: Context, created_item: CollectionItem<any>): Promise<void>;
18
- postSealiousEdit(ctx: Context, edited_item: CollectionItem<any>): Promise<void>;
19
+ postSealiousCreate(_ctx: Context, _created_item: CollectionItem<any>, _formData: FormData): Promise<void>;
20
+ postSealiousEdit(ctx: Context, _edited_item: CollectionItem<any>, _formData: FormData): Promise<void>;
19
21
  getControl(): DefaultControl;
20
22
  getEmptyValue(): string;
21
23
  }
@@ -1,3 +1,5 @@
1
1
  import { FlatTemplatable } from "tempstream";
2
+ import { FormField } from "../index.js";
2
3
  export declare function getRequiredClass(isRequired: boolean): string;
3
4
  export declare function inputWrapper(type: string, modifiers: string[], input: FlatTemplatable): Promise<string>;
5
+ export declare function autoWrapInput(field: FormField, input: FlatTemplatable): Promise<string>;
@@ -2,7 +2,7 @@ import { tempstream } from "tempstream";
2
2
  import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper.js";
3
3
  import { FormFieldControl } from "./form-field-control.js";
4
4
  class CheckboxedListInput extends FormFieldControl {
5
- constructor(field, options = { label: field.name }) {
5
+ constructor(field, options = { label: field.label || field.name }) {
6
6
  super([field]);
7
7
  this.field = field;
8
8
  this.options = options;
@@ -52,7 +52,7 @@ class CheckboxedListInput extends FormFieldControl {
52
52
  ...this.generateCountBasedClasses(options.length)
53
53
  ],
54
54
  tempstream`<label
55
- >${this.options.label || this.field.name}</label
55
+ >${this.options.label || this.field.label || this.field.name}</label
56
56
  >
57
57
  ${~valid && fctx.validate ? `<div class="input__error">${message}</div>` : ""}
58
58
  <ul>
@@ -3,7 +3,7 @@ import { attribute } from "../../sanitize.js";
3
3
  import { SimpleInput } from "./simple-input.js";
4
4
  class FreeformDropdown extends SimpleInput {
5
5
  constructor(field, _generateOptions, options = {
6
- label: field.name,
6
+ label: field.label || field.name,
7
7
  autosubmit: false,
8
8
  autocomplete: true
9
9
  }) {
@@ -36,7 +36,7 @@ class FreeformDropdown extends SimpleInput {
36
36
  );
37
37
  return (
38
38
  /* HTML */
39
- tempstream`<select ${attributes_str}>
39
+ tempstream`<select ${attributes_str} autocomplete="off">
40
40
  ${Promise.resolve(this.generateOptions(ctx)).then(
41
41
  (options) => options.map(
42
42
  ({ value, label }) => `<option value="${attribute(value)}" ${(value || "") == picked_value ? "selected" : ""}>${label}</option>`
@@ -48,7 +48,7 @@ class FreeformDropdown extends SimpleInput {
48
48
  }
49
49
  class Dropdown extends FreeformDropdown {
50
50
  constructor(field, options = {
51
- label: field.name,
51
+ label: field.label || field.name,
52
52
  autosubmit: false,
53
53
  autocomplete: true
54
54
  }) {
@@ -0,0 +1,41 @@
1
+ import { tempstream } from "tempstream";
2
+ import { FormControlContext } from "./form-control.js";
3
+ import { FormFieldControl } from "./form-field-control.js";
4
+ import { autoWrapInput } from "../../utils/input-wrapper.js";
5
+ class Hybrid extends FormFieldControl {
6
+ constructor(main_field, subfields) {
7
+ super(Object.values(subfields));
8
+ this.main_field = main_field;
9
+ this.subfields = subfields;
10
+ for (const [field_name, field] of Object.entries(subfields)) {
11
+ field.setName(`${main_field.name}[${field_name}]`);
12
+ }
13
+ }
14
+ async render(fctx) {
15
+ const raw_values = fctx.data.raw_values[this.main_field.name] || {};
16
+ const new_fctx = new FormControlContext(
17
+ fctx.ctx,
18
+ {
19
+ raw_values,
20
+ messages: [],
21
+ field_messages: {}
22
+ },
23
+ [],
24
+ fctx.field_name_prefix,
25
+ fctx.form_id,
26
+ fctx.validate
27
+ );
28
+ return tempstream`<fieldset><legend>${this.main_field.label || this.main_field.name}</legend>${Object.values(
29
+ this.subfields
30
+ ).map(
31
+ async (subfield) => tempstream`${autoWrapInput(
32
+ subfield,
33
+ await subfield.getControl().render(new_fctx)
34
+ )}`
35
+ )}</fieldset>`;
36
+ }
37
+ }
38
+ export {
39
+ Hybrid
40
+ };
41
+ //# sourceMappingURL=hybrid.js.map
@@ -164,7 +164,9 @@ class MultipleFiles extends FormFieldControl {
164
164
  "multiple-files",
165
165
  ["multiple-files", this.field.name, ...this.getClassModifiers()],
166
166
  tempstream`
167
- <label>${this.options.label || this.field.name}</label>
167
+ <label
168
+ >${this.options.label || this.field.label || this.field.name}</label
169
+ >
168
170
  <ul class="multiple-files__list">
169
171
  ${files.map(
170
172
  ([item, file]) => this.renderFileItem(item, file)
@@ -2,6 +2,7 @@ import { tempstream } from "tempstream";
2
2
  import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper.js";
3
3
  import { renderAttributes } from "../../utils/render-attributes.js";
4
4
  import { FormFieldControl } from "./form-field-control.js";
5
+ import { SimpleFormField } from "../fields/simple-form-field.js";
5
6
  class SimpleInput extends FormFieldControl {
6
7
  constructor(field, options) {
7
8
  super([field]);
@@ -15,12 +16,15 @@ class SimpleInput extends FormFieldControl {
15
16
  return this.options.type || "text";
16
17
  }
17
18
  getLabel() {
18
- return this.options.label != void 0 ? this.options.label : this.field.name;
19
+ return this.options.label != void 0 ? this.options.label : this.field.label || this.field.name;
19
20
  }
20
21
  getID() {
21
22
  return this.options.id || this.field.name;
22
23
  }
23
24
  getPlaceholder() {
25
+ if (this.field instanceof SimpleFormField && this.field.placeholder) {
26
+ return this.field.placeholder;
27
+ }
24
28
  return this.options.placeholder === "" ? "" : this.options.placeholder || this.getType();
25
29
  }
26
30
  async getInputAttributes(fctx) {
@@ -42,7 +46,8 @@ class SimpleInput extends FormFieldControl {
42
46
  ...required ? [["required", true]] : [],
43
47
  ...!this.options.autocomplete ? [["autocomplete", "off"]] : [],
44
48
  ...this.options.step ? [["step", this.options.step]] : [],
45
- ["aria-invalid", String(!valid)]
49
+ ["aria-invalid", String(!valid)],
50
+ ...this.options.inputmode ? [["inputmode", this.options.inputmode]] : []
46
51
  ]);
47
52
  }
48
53
  async postInput(_) {
@@ -1,7 +1,7 @@
1
1
  import { FreeformDropdown } from "./dropdown.js";
2
2
  class SingleReferenceDropdown extends FreeformDropdown {
3
3
  constructor(field, options = {
4
- label: field.name,
4
+ label: field.label || field.name,
5
5
  autosubmit: false,
6
6
  autocomplete: true
7
7
  }) {
@@ -88,7 +88,7 @@ class Table extends FormControl {
88
88
  ...rows.length >= 20 ? ["form-input__wrapper--options-count--20-or-more"] : []
89
89
  ].join(" ")}"
90
90
  >
91
- <label>${this.options.label}</label>
91
+ <label>${this.options.label || this.table_field.label}</label>
92
92
  <div class="table__wrapper">
93
93
  <table>
94
94
  <tbody>
@@ -44,7 +44,7 @@ class Boolean extends FormField {
44
44
  return false;
45
45
  }
46
46
  getControl() {
47
- return new Checkbox(this, { label: this.name });
47
+ return new Checkbox(this, { label: this.label || this.name });
48
48
  }
49
49
  async getSealiousCreateValue(fctx) {
50
50
  const { parsed } = await this.getParsedValue(
@@ -44,7 +44,9 @@ class CheckboxedListField extends FormField {
44
44
  return {};
45
45
  }
46
46
  getControl() {
47
- return new CheckboxedListInput(this, { label: this.name });
47
+ return new CheckboxedListInput(this, {
48
+ label: this.label || this.name
49
+ });
48
50
  }
49
51
  }
50
52
  export {
@@ -35,7 +35,7 @@ class Coordinates extends SimpleFormField {
35
35
  }
36
36
  getControl() {
37
37
  return new SimpleInput(this, {
38
- label: this.name,
38
+ label: this.label || this.name,
39
39
  type: this.type
40
40
  });
41
41
  }
@@ -15,7 +15,10 @@ class DateField extends SimpleFormField {
15
15
  };
16
16
  }
17
17
  getControl() {
18
- return new SimpleInput(this, { label: this.name, type: this.type });
18
+ return new SimpleInput(this, {
19
+ label: this.label || this.name,
20
+ type: this.type
21
+ });
19
22
  }
20
23
  async getSealiousCreateValue(fctx) {
21
24
  const { parsed } = await this.getParsedValue(
@@ -26,7 +26,7 @@ class DateTime extends SimpleFormField {
26
26
  }
27
27
  getControl() {
28
28
  return new SimpleInput(this, {
29
- label: this.name,
29
+ label: this.label || this.name,
30
30
  type: this.type
31
31
  });
32
32
  }
@@ -31,7 +31,7 @@ class DeepReverseSingleReferenceTable extends Table {
31
31
  }
32
32
  getControl() {
33
33
  return new TableControl(this, {
34
- label: this.name,
34
+ label: this.label || this.name,
35
35
  allow_adding: false,
36
36
  subfield_controls: {
37
37
  selected: this.columns.selected.getControl(),
@@ -17,7 +17,10 @@ class EmailField extends RegExpField {
17
17
  this.error_message = message;
18
18
  }
19
19
  getControl() {
20
- return new SimpleInput(this, { label: this.name, type: "email" });
20
+ return new SimpleInput(this, {
21
+ label: this.label || this.name,
22
+ type: "email"
23
+ });
21
24
  }
22
25
  }
23
26
  export {
@@ -18,6 +18,10 @@ class FormField {
18
18
  this.name = name;
19
19
  return this;
20
20
  }
21
+ setLabel(label) {
22
+ this.label = label;
23
+ return this;
24
+ }
21
25
  async _validate(ctx, value) {
22
26
  if (value === "" || value === null || value === void 0) {
23
27
  if (this.required) {
@@ -20,6 +20,8 @@ export * from "./markdown.js";
20
20
  export * from "./multiple-files.js";
21
21
  export * from "./number.js";
22
22
  export * from "./password.js";
23
+ export * from "./phone-number.js";
24
+ export * from "./phone-number-with-country-code.js";
23
25
  export * from "./pick-from-list-sealious.js";
24
26
  export * from "./pick-from-list.js";
25
27
  export * from "./regexp.js";
@@ -0,0 +1,50 @@
1
+ import * as countryCodes from "country-codes-list";
2
+ import { FieldTypes as SealiousFieldTypes } from "sealious";
3
+ import { Hybrid } from "../controls/hybrid.js";
4
+ import { FormField } from "./field.js";
5
+ import { PickFromListField } from "./pick-from-list.js";
6
+ import { PhoneNumberWithoutCountryCode } from "./phone-number.js";
7
+ class PhoneNumberWithCountryCode extends FormField {
8
+ getEmptyValue() {
9
+ return new SealiousFieldTypes.PhoneNumberValue("48", "0000000");
10
+ }
11
+ async parse(ctx, raw_value) {
12
+ try {
13
+ return {
14
+ parsable: true,
15
+ parsed_value: SealiousFieldTypes.PhoneNumberValue.fromInput(
16
+ ctx.$context,
17
+ raw_value
18
+ ),
19
+ error: null
20
+ };
21
+ } catch (e) {
22
+ return {
23
+ parsable: false,
24
+ parsed_value: null,
25
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
26
+ error: e.message
27
+ };
28
+ }
29
+ }
30
+ getControl() {
31
+ return new Hybrid(this, {
32
+ country_code: new PickFromListField(
33
+ true,
34
+ async () => countryCodes.all().sort(
35
+ (a, b) => a.countryCallingCode < b.countryCallingCode ? -1 : 1
36
+ ).map((code) => ({
37
+ value: code.countryCallingCode,
38
+ label: "+" + code.countryCallingCode + " " + code.flag
39
+ }))
40
+ ).setLabel("Country code"),
41
+ number: new PhoneNumberWithoutCountryCode(true).setLabel(
42
+ "Phone number"
43
+ )
44
+ });
45
+ }
46
+ }
47
+ export {
48
+ PhoneNumberWithCountryCode
49
+ };
50
+ //# sourceMappingURL=phone-number-with-country-code.js.map
@@ -0,0 +1,33 @@
1
+ import { SimpleInput } from "../controls/controls.js";
2
+ import { FormField } from "./field.js";
3
+ class PhoneNumberWithoutCountryCode extends FormField {
4
+ getEmptyValue() {
5
+ return "000 000 000";
6
+ }
7
+ async parse(ctx, raw_value) {
8
+ if (typeof raw_value == "string") {
9
+ return {
10
+ parsable: true,
11
+ parsed_value: raw_value.replaceAll(/[^0-9]/, "").replace(/(\d{3})(?=\d)/g, "$1 ").trim(),
12
+ error: null
13
+ };
14
+ } else {
15
+ return {
16
+ parsable: false,
17
+ parsed_value: null,
18
+ error: "Expected string"
19
+ };
20
+ }
21
+ }
22
+ getControl() {
23
+ return new SimpleInput(this, {
24
+ inputmode: "tel",
25
+ type: "text",
26
+ placeholder: "123 456 789"
27
+ });
28
+ }
29
+ }
30
+ export {
31
+ PhoneNumberWithoutCountryCode
32
+ };
33
+ //# sourceMappingURL=phone-number.js.map
@@ -37,7 +37,7 @@ class RegExpField extends FormField {
37
37
  return this.default_value || "";
38
38
  }
39
39
  getControl() {
40
- return new SimpleInput(this, { label: this.name });
40
+ return new SimpleInput(this, { label: this.label || this.name });
41
41
  }
42
42
  async getSealiousCreateValue(fctx) {
43
43
  const { parsed } = await this.getParsedValue(
@@ -13,6 +13,10 @@ class SimpleFormField extends FormField {
13
13
  }
14
14
  return String(value);
15
15
  }
16
+ setPlaceholder(placeholder) {
17
+ this.placeholder = placeholder;
18
+ return this;
19
+ }
16
20
  }
17
21
  class TextBasedSimpleField extends SimpleFormField {
18
22
  async parse(_, raw_value) {
@@ -30,13 +34,13 @@ class TextBasedSimpleField extends SimpleFormField {
30
34
  );
31
35
  return parsed || "";
32
36
  }
33
- async postSealiousCreate(ctx, created_item) {
37
+ async postSealiousCreate(_ctx, _created_item, _formData) {
34
38
  }
35
- async postSealiousEdit(ctx, edited_item) {
39
+ async postSealiousEdit(ctx, _edited_item, _formData) {
36
40
  }
37
41
  getControl() {
38
42
  return new SimpleInput(this, {
39
- label: this.name,
43
+ label: this.label || this.name,
40
44
  type: this.type
41
45
  });
42
46
  }
@@ -65,7 +69,7 @@ class NumberBasedSimpleField extends SimpleFormField {
65
69
  }
66
70
  getControl() {
67
71
  return new SimpleInput(this, {
68
- label: this.name,
72
+ label: this.label || this.name,
69
73
  type: this.type
70
74
  });
71
75
  }
@@ -4,7 +4,7 @@ import { toPascalCase } from "js-convert-case";
4
4
  class SingleReference extends CollectionField {
5
5
  getControl() {
6
6
  return new SingleReferenceDropdown(this, {
7
- label: this.name,
7
+ label: this.label || this.name,
8
8
  getLabel: (item) => item.id
9
9
  });
10
10
  }
@@ -18,7 +18,7 @@ class StructuredArray extends Table {
18
18
  }
19
19
  getControl() {
20
20
  return new TableControl(this, {
21
- label: this.name,
21
+ label: this.label || this.name,
22
22
  subfield_controls: Object.fromEntries(
23
23
  Object.entries(this.columns).map(([column_name, subfield]) => [
24
24
  column_name,
@@ -70,7 +70,7 @@ class Table extends FormField {
70
70
  }
71
71
  getControl() {
72
72
  return new TableControl(this, {
73
- label: this.name,
73
+ label: this.label || this.name,
74
74
  subfield_controls: Object.fromEntries(
75
75
  Object.entries(this.columns).map(([column_name, field]) => [
76
76
  column_name,
@@ -1,4 +1,5 @@
1
1
  import { tempstream } from "tempstream";
2
+ import { toKebabCase } from "js-convert-case";
2
3
  function getRequiredClass(isRequired) {
3
4
  return isRequired ? "required" : "";
4
5
  }
@@ -14,7 +15,19 @@ async function inputWrapper(type, modifiers, input) {
14
15
  ${input}
15
16
  </div>`;
16
17
  }
18
+ async function autoWrapInput(field, input) {
19
+ return inputWrapper(
20
+ toKebabCase(field.constructor.name),
21
+ [
22
+ `type--` + toKebabCase(field.constructor.name),
23
+ `name--` + toKebabCase(field.name),
24
+ getRequiredClass(field.required)
25
+ ],
26
+ input
27
+ );
28
+ }
17
29
  export {
30
+ autoWrapInput,
18
31
  getRequiredClass,
19
32
  inputWrapper
20
33
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.19.2",
3
+ "version": "0.19.4",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -66,6 +66,7 @@
66
66
  "@sealcode/ts-predicates": "^0.4.3",
67
67
  "chokidar": "^3.6.0",
68
68
  "colord": "^2.9.3",
69
+ "country-codes-list": "^2.0.0",
69
70
  "deepmerge": "^4.3.1",
70
71
  "esbuild": "^0.20.0",
71
72
  "escape-goat": "^4.0.0",
@@ -12,7 +12,7 @@ export class CheckboxedListInput<
12
12
  > extends FormFieldControl {
13
13
  constructor(
14
14
  public field: CheckboxedListField<boolean, Values>,
15
- public options: { label: string } = { label: field.name }
15
+ public options: { label: string } = { label: field.label || field.name }
16
16
  ) {
17
17
  super([field]);
18
18
  }
@@ -67,7 +67,9 @@ export class CheckboxedListInput<
67
67
  ...this.generateCountBasedClasses(options.length),
68
68
  ],
69
69
  tempstream /* HTML */ `<label
70
- >${this.options.label || this.field.name}</label
70
+ >${this.options.label ||
71
+ this.field.label ||
72
+ this.field.name}</label
71
73
  >
72
74
  ${~valid && fctx.validate
73
75
  ? `<div class="input__error">${message}</div>`
@@ -20,7 +20,7 @@ export class FreeformDropdown extends SimpleInput {
20
20
  public field: FormField<boolean, string | null>,
21
21
  public _generateOptions: (ctx: Context) => Promise<DropdownOption[]>,
22
22
  public options: DropdownOptions = {
23
- label: field.name,
23
+ label: field.label || field.name,
24
24
  autosubmit: false,
25
25
  autocomplete: true,
26
26
  }
@@ -61,7 +61,7 @@ export class FreeformDropdown extends SimpleInput {
61
61
  ctx,
62
62
  data
63
63
  );
64
- return /* HTML */ tempstream`<select ${attributes_str}>
64
+ return /* HTML */ tempstream`<select ${attributes_str} autocomplete="off">
65
65
  ${Promise.resolve(this.generateOptions(ctx)).then((options) =>
66
66
  options.map(
67
67
  ({ value, label }) =>
@@ -78,7 +78,7 @@ export class Dropdown extends FreeformDropdown {
78
78
  constructor(
79
79
  public field: PickFromListField<boolean>,
80
80
  public options: DropdownOptions = {
81
- label: field.name,
81
+ label: field.label || field.name,
82
82
  autosubmit: false,
83
83
  autocomplete: true,
84
84
  }
@@ -0,0 +1,44 @@
1
+ import { FlatTemplatable, tempstream } from "tempstream";
2
+ import { FormControlContext } from "./form-control.js";
3
+ import { FormFieldControl } from "./form-field-control.js";
4
+ import { FormField } from "../fields/field.js";
5
+ import { FormDataValue } from "../form-types.js";
6
+ import { autoWrapInput } from "../../utils/input-wrapper.js";
7
+
8
+ export class Hybrid extends FormFieldControl {
9
+ constructor(
10
+ public main_field: FormField,
11
+ public subfields: Record<string, FormField>
12
+ ) {
13
+ super(Object.values(subfields));
14
+ for (const [field_name, field] of Object.entries(subfields)) {
15
+ field.setName(`${main_field.name}[${field_name}]`);
16
+ }
17
+ }
18
+
19
+ async render(fctx: FormControlContext): Promise<FlatTemplatable> {
20
+ const raw_values = (fctx.data.raw_values[this.main_field.name] ||
21
+ {}) as Record<string, FormDataValue>;
22
+ const new_fctx = new FormControlContext(
23
+ fctx.ctx,
24
+ {
25
+ raw_values,
26
+ messages: [],
27
+ field_messages: {},
28
+ },
29
+ [],
30
+ fctx.field_name_prefix,
31
+ fctx.form_id,
32
+ fctx.validate
33
+ );
34
+ return tempstream`<fieldset><legend>${this.main_field.label || this.main_field.name}</legend>${Object.values(
35
+ this.subfields
36
+ ).map(
37
+ async (subfield) =>
38
+ tempstream`${autoWrapInput(
39
+ subfield,
40
+ await subfield.getControl().render(new_fctx)
41
+ )}`
42
+ )}</fieldset>`;
43
+ }
44
+ }
@@ -231,7 +231,11 @@ export class MultipleFiles extends FormFieldControl {
231
231
  "multiple-files",
232
232
  ["multiple-files", this.field.name, ...this.getClassModifiers()],
233
233
  tempstream /* HTML */ `
234
- <label>${this.options.label || this.field.name}</label>
234
+ <label
235
+ >${this.options.label ||
236
+ this.field.label ||
237
+ this.field.name}</label
238
+ >
235
239
  <ul class="multiple-files__list">
236
240
  ${files.map(([item, file]) =>
237
241
  this.renderFileItem(item, file)
@@ -6,6 +6,7 @@ import { FormField } from "../fields/field.js";
6
6
  import { FormDataValue } from "../form-types.js";
7
7
  import { FormControlContext } from "./form-control.js";
8
8
  import { FormFieldControl } from "./form-field-control.js";
9
+ import { SimpleFormField } from "../fields/simple-form-field.js";
9
10
 
10
11
  export type SimpleInputOptions = {
11
12
  id?: string;
@@ -19,6 +20,7 @@ export type SimpleInputOptions = {
19
20
  step?: number;
20
21
  suffix?: string;
21
22
  pattern?: string;
23
+ inputmode?: string;
22
24
  };
23
25
 
24
26
  export class SimpleInput<
@@ -48,7 +50,7 @@ export class SimpleInput<
48
50
  getLabel(): string {
49
51
  return this.options.label != undefined
50
52
  ? this.options.label
51
- : this.field.name;
53
+ : this.field.label || this.field.name;
52
54
  }
53
55
 
54
56
  getID(): string {
@@ -56,6 +58,9 @@ export class SimpleInput<
56
58
  }
57
59
 
58
60
  getPlaceholder(): string {
61
+ if (this.field instanceof SimpleFormField && this.field.placeholder) {
62
+ return this.field.placeholder;
63
+ }
59
64
  return this.options.placeholder === ""
60
65
  ? ""
61
66
  : this.options.placeholder || this.getType();
@@ -83,6 +88,9 @@ export class SimpleInput<
83
88
  ...(!this.options.autocomplete ? [["autocomplete", "off"]] : []),
84
89
  ...(this.options.step ? [["step", this.options.step]] : []),
85
90
  ["aria-invalid", String(!valid)],
91
+ ...(this.options.inputmode
92
+ ? [["inputmode", this.options.inputmode]]
93
+ : []),
86
94
  ]) as Record<string, string>;
87
95
  }
88
96
 
@@ -13,7 +13,7 @@ export class SingleReferenceDropdown<
13
13
  public options: DropdownOptions & {
14
14
  getLabel?: (item: CollectionItem<Collection>) => string;
15
15
  } = {
16
- label: field.name,
16
+ label: field.label || field.name,
17
17
  autosubmit: false,
18
18
  autocomplete: true,
19
19
  }
@@ -159,7 +159,7 @@ export class Table<F extends Record<string, FormField>> extends FormControl {
159
159
  : []),
160
160
  ].join(" ")}"
161
161
  >
162
- <label>${this.options.label}</label>
162
+ <label>${this.options.label || this.table_field.label}</label>
163
163
  <div class="table__wrapper">
164
164
  <table>
165
165
  <tbody>
@@ -68,7 +68,7 @@ export class Boolean<Required extends boolean> extends FormField<
68
68
  }
69
69
 
70
70
  public getControl(): Checkbox {
71
- return new Checkbox(this, { label: this.name });
71
+ return new Checkbox(this, { label: this.label || this.name });
72
72
  }
73
73
 
74
74
  async getSealiousCreateValue(fctx: FormControlContext): Promise<boolean> {
@@ -74,6 +74,8 @@ export class CheckboxedListField<
74
74
  }
75
75
 
76
76
  getControl(): CheckboxedListInput<Values> {
77
- return new CheckboxedListInput(this, { label: this.name });
77
+ return new CheckboxedListInput(this, {
78
+ label: this.label || this.name,
79
+ });
78
80
  }
79
81
  }
@@ -55,7 +55,7 @@ export class Coordinates<
55
55
 
56
56
  getControl(): DefaultControl {
57
57
  return new SimpleInput(this, {
58
- label: this.name,
58
+ label: this.label || this.name,
59
59
  type: this.type,
60
60
  }) as unknown as DefaultControl;
61
61
  }
@@ -30,7 +30,10 @@ export class DateField<Required extends boolean> extends SimpleFormField<
30
30
  }
31
31
 
32
32
  getControl(): SimpleInput<SimpleInputOptions> {
33
- return new SimpleInput(this, { label: this.name, type: this.type });
33
+ return new SimpleInput(this, {
34
+ label: this.label || this.name,
35
+ type: this.type,
36
+ });
34
37
  }
35
38
 
36
39
  async getSealiousCreateValue(
@@ -45,7 +45,7 @@ export class DateTime<Required extends boolean> extends SimpleFormField<
45
45
 
46
46
  getControl(): SimpleInput {
47
47
  return new SimpleInput(this, {
48
- label: this.name,
48
+ label: this.label || this.name,
49
49
  type: this.type,
50
50
  });
51
51
  }
@@ -56,7 +56,7 @@ export class DeepReverseSingleReferenceTable extends Table<
56
56
 
57
57
  getControl(): TableControl<any> {
58
58
  return new TableControl(this, {
59
- label: this.name,
59
+ label: this.label || this.name,
60
60
  allow_adding: false,
61
61
  subfield_controls: {
62
62
  selected: this.columns.selected.getControl(),
@@ -24,6 +24,9 @@ export class EmailField<
24
24
  }
25
25
 
26
26
  public getControl(): SimpleInput {
27
- return new SimpleInput(this, { label: this.name, type: "email" });
27
+ return new SimpleInput(this, {
28
+ label: this.label || this.name,
29
+ type: "email",
30
+ });
28
31
  }
29
32
  }
@@ -66,6 +66,7 @@ export abstract class FormField<
66
66
  SealiousValue = any,
67
67
  > {
68
68
  name: string;
69
+ label: string;
69
70
  constructor(public readonly required: Required) {}
70
71
  predicate: Predicate = predicates.unknown;
71
72
  mapToFilter: (parsed: ParsedValue) => unknown = (value) => value;
@@ -77,6 +78,11 @@ export abstract class FormField<
77
78
  return this;
78
79
  }
79
80
 
81
+ setLabel(label: string): this {
82
+ this.label = label;
83
+ return this;
84
+ }
85
+
80
86
  public async _validate(
81
87
  ctx: Context,
82
88
  value: ParsedValue
@@ -20,6 +20,8 @@ export * from "./markdown.js";
20
20
  export * from "./multiple-files.js";
21
21
  export * from "./number.js";
22
22
  export * from "./password.js";
23
+ export * from "./phone-number.js";
24
+ export * from "./phone-number-with-country-code.js";
23
25
  export * from "./pick-from-list-sealious.js";
24
26
  export * from "./pick-from-list.js";
25
27
  export * from "./regexp.js";
@@ -0,0 +1,59 @@
1
+ import * as countryCodes from "country-codes-list";
2
+ import { Context } from "koa";
3
+ import { FieldTypes as SealiousFieldTypes } from "sealious";
4
+ import { FormControl } from "../controls/controls.js";
5
+ import { Hybrid } from "../controls/hybrid.js";
6
+ import { FormDataValue } from "../form-types.js";
7
+ import { FieldParseResult, FormField } from "./field.js";
8
+ import { PickFromListField } from "./pick-from-list.js";
9
+ import { PhoneNumberWithoutCountryCode } from "./phone-number.js";
10
+
11
+ export class PhoneNumberWithCountryCode<
12
+ Required extends boolean,
13
+ > extends FormField<Required, SealiousFieldTypes.PhoneNumberValue> {
14
+ public getEmptyValue(): SealiousFieldTypes.PhoneNumberValue {
15
+ return new SealiousFieldTypes.PhoneNumberValue("48", "0000000");
16
+ }
17
+
18
+ async parse(
19
+ ctx: Context,
20
+ raw_value: FormDataValue
21
+ ): Promise<FieldParseResult<SealiousFieldTypes.PhoneNumberValue>> {
22
+ try {
23
+ return {
24
+ parsable: true,
25
+ parsed_value: SealiousFieldTypes.PhoneNumberValue.fromInput(
26
+ ctx.$context,
27
+ raw_value
28
+ ),
29
+ error: null,
30
+ };
31
+ } catch (e) {
32
+ return {
33
+ parsable: false,
34
+ parsed_value: null,
35
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
36
+ error: e.message as string,
37
+ };
38
+ }
39
+ }
40
+
41
+ getControl(): FormControl {
42
+ return new Hybrid(this, {
43
+ country_code: new PickFromListField(true, async () =>
44
+ countryCodes
45
+ .all()
46
+ .sort((a, b) =>
47
+ a.countryCallingCode < b.countryCallingCode ? -1 : 1
48
+ )
49
+ .map((code) => ({
50
+ value: code.countryCallingCode,
51
+ label: "+" + code.countryCallingCode + " " + code.flag,
52
+ }))
53
+ ).setLabel("Country code"),
54
+ number: new PhoneNumberWithoutCountryCode(true).setLabel(
55
+ "Phone number"
56
+ ),
57
+ });
58
+ }
59
+ }
@@ -0,0 +1,39 @@
1
+ import { Context } from "koa";
2
+ import { FormControl, SimpleInput } from "../controls/controls.js";
3
+ import { FormDataValue } from "../form-types.js";
4
+ import { FormField } from "./field.js";
5
+
6
+ export class PhoneNumberWithoutCountryCode<
7
+ Required extends boolean,
8
+ > extends FormField<Required, string> {
9
+ public getEmptyValue(): string {
10
+ return "000 000 000";
11
+ }
12
+
13
+ async parse(ctx: Context, raw_value: FormDataValue) {
14
+ if (typeof raw_value == "string") {
15
+ return {
16
+ parsable: <const>true,
17
+ parsed_value: raw_value
18
+ .replaceAll(/[^0-9]/, "")
19
+ .replace(/(\d{3})(?=\d)/g, "$1 ")
20
+ .trim(),
21
+ error: null,
22
+ };
23
+ } else {
24
+ return {
25
+ parsable: <const>false,
26
+ parsed_value: null,
27
+ error: "Expected string",
28
+ };
29
+ }
30
+ }
31
+
32
+ getControl(): FormControl {
33
+ return new SimpleInput(this, {
34
+ inputmode: "tel",
35
+ type: "text",
36
+ placeholder: "123 456 789",
37
+ });
38
+ }
39
+ }
@@ -59,7 +59,7 @@ export class RegExpField<Required extends boolean> extends FormField<
59
59
  }
60
60
 
61
61
  public getControl(): SimpleInput {
62
- return new SimpleInput(this, { label: this.name });
62
+ return new SimpleInput(this, { label: this.label || this.name });
63
63
  }
64
64
 
65
65
  async getSealiousCreateValue(fctx: FormControlContext): Promise<string> {
@@ -1,5 +1,5 @@
1
1
  /* eslint @typescript-eslint/no-unused-vars: off */
2
- import { FormDataValue } from "../form-types.js";
2
+ import { FormData, FormDataValue } from "../form-types.js";
3
3
  import { Context } from "koa";
4
4
  import { predicates } from "@sealcode/ts-predicates";
5
5
  import { FieldParseResult, FormField } from "./field.js";
@@ -14,6 +14,7 @@ export abstract class SimpleFormField<
14
14
  Parsed = string,
15
15
  > extends FormField<Required, Parsed, DefaultControl> {
16
16
  predicate = predicates.string;
17
+ public placeholder: string;
17
18
 
18
19
  constructor(
19
20
  required: Required,
@@ -32,6 +33,11 @@ export abstract class SimpleFormField<
32
33
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
33
34
  return String(value) as FormDataValue;
34
35
  }
36
+
37
+ setPlaceholder(placeholder: string) {
38
+ this.placeholder = placeholder;
39
+ return this;
40
+ }
35
41
  }
36
42
 
37
43
  export class TextBasedSimpleField<
@@ -61,20 +67,22 @@ export class TextBasedSimpleField<
61
67
  }
62
68
 
63
69
  async postSealiousCreate(
64
- ctx: Context,
70
+ _ctx: Context,
65
71
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
- created_item: CollectionItem<any>
72
+ _created_item: CollectionItem<any>,
73
+ _formData: FormData
67
74
  ): Promise<void> {}
68
75
 
69
76
  async postSealiousEdit(
70
77
  ctx: Context,
71
78
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
- edited_item: CollectionItem<any>
79
+ _edited_item: CollectionItem<any>,
80
+ _formData: FormData
73
81
  ): Promise<void> {}
74
82
 
75
83
  getControl(): DefaultControl {
76
84
  return new SimpleInput(this, {
77
- label: this.name,
85
+ label: this.label || this.name,
78
86
  type: this.type,
79
87
  }) as unknown as DefaultControl;
80
88
  }
@@ -115,7 +123,7 @@ export class NumberBasedSimpleField<
115
123
 
116
124
  getControl(): DefaultControl {
117
125
  return new SimpleInput(this, {
118
- label: this.name,
126
+ label: this.label || this.name,
119
127
  type: this.type,
120
128
  }) as unknown as DefaultControl;
121
129
  }
@@ -13,7 +13,7 @@ export class SingleReference<
13
13
  > extends CollectionField<F, Required> {
14
14
  getControl(): SingleReferenceDropdown<TargetCollection, F, Required> {
15
15
  return new SingleReferenceDropdown(this, {
16
- label: this.name,
16
+ label: this.label || this.name,
17
17
  getLabel: (item) => item.id,
18
18
  });
19
19
  }
@@ -55,7 +55,7 @@ export class StructuredArray<
55
55
  SealiousFields[name]
56
56
  >;
57
57
  }>(this as any, {
58
- label: this.name,
58
+ label: this.label || this.name,
59
59
  subfield_controls: Object.fromEntries(
60
60
  Object.entries(this.columns).map(([column_name, subfield]) => [
61
61
  column_name as keyof SealiousFields,
@@ -98,7 +98,7 @@ export class Table<F extends Record<string, FormField>> extends FormField<
98
98
 
99
99
  getControl(): TableControl<F> {
100
100
  return new TableControl<F>(this, {
101
- label: this.name,
101
+ label: this.label || this.name,
102
102
  subfield_controls: Object.fromEntries(
103
103
  Object.entries(this.columns).map(([column_name, field]) => [
104
104
  column_name as keyof F,
@@ -1,4 +1,6 @@
1
1
  import { FlatTemplatable, tempstream } from "tempstream";
2
+ import { FormField } from "../index.js";
3
+ import { toKebabCase } from "js-convert-case";
2
4
 
3
5
  export function getRequiredClass(isRequired: boolean): string {
4
6
  return isRequired ? "required" : "";
@@ -21,3 +23,15 @@ export async function inputWrapper(
21
23
  ${input}
22
24
  </div>`;
23
25
  }
26
+
27
+ export async function autoWrapInput(field: FormField, input: FlatTemplatable) {
28
+ return inputWrapper(
29
+ toKebabCase(field.constructor.name),
30
+ [
31
+ `type--` + toKebabCase(field.constructor.name),
32
+ `name--` + toKebabCase(field.name),
33
+ getRequiredClass(field.required),
34
+ ],
35
+ input
36
+ );
37
+ }