@sealcode/sealgen 0.6.1 → 0.7.1

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 (91) hide show
  1. package/.nyc_output/1c5dff80-7861-427a-b1bb-d0686211f098.json +1 -0
  2. package/.nyc_output/a38a0d08-1f1f-4973-b5e2-efc434c1593d.json +1 -0
  3. package/.nyc_output/processinfo/1c5dff80-7861-427a-b1bb-d0686211f098.json +1 -0
  4. package/.nyc_output/processinfo/a38a0d08-1f1f-4973-b5e2-efc434c1593d.json +1 -0
  5. package/.nyc_output/processinfo/index.json +1 -0
  6. package/.xunit +3 -0
  7. package/@types/components/components.d.ts +1 -0
  8. package/@types/components/tabs.d.ts +2 -0
  9. package/@types/forms/controls/controls.d.ts +1 -0
  10. package/@types/forms/controls/field-group.d.ts +17 -0
  11. package/@types/forms/controls/form-control.d.ts +1 -1
  12. package/@types/forms/controls/form-field-control.d.ts +1 -1
  13. package/@types/forms/controls/simple-input.d.ts +2 -1
  14. package/@types/forms/form.d.ts +19 -6
  15. package/@types/forms/multiform.d.ts +9 -11
  16. package/@types/index.d.ts +2 -0
  17. package/@types/page/mountable-with-fields.d.ts +32 -0
  18. package/@types/page/mountable.d.ts +2 -27
  19. package/@types/page/page.d.ts +2 -2
  20. package/@types/templates/multiform.d.ts +1 -0
  21. package/@types/templates/redirect.d.ts +1 -0
  22. package/@types/utils/input-wrapper.d.ts +4 -0
  23. package/coverage/clover.xml +79 -0
  24. package/lib/add-route.js +27 -8
  25. package/lib/add-route.js.map +1 -1
  26. package/lib/components/components.js +14 -0
  27. package/lib/components/components.js.map +1 -0
  28. package/lib/components/tabs.js +92 -0
  29. package/lib/components/tabs.js.map +1 -0
  30. package/lib/forms/controls/checkboxed-list-input.js +12 -9
  31. package/lib/forms/controls/checkboxed-list-input.js.map +1 -1
  32. package/lib/forms/controls/controls.js +3 -1
  33. package/lib/forms/controls/controls.js.map +1 -1
  34. package/lib/forms/controls/dropdown.js +14 -12
  35. package/lib/forms/controls/dropdown.js.map +1 -1
  36. package/lib/forms/controls/field-group.js +26 -0
  37. package/lib/forms/controls/field-group.js.map +1 -0
  38. package/lib/forms/controls/form-control.js.map +1 -1
  39. package/lib/forms/controls/form-field-control.js.map +1 -1
  40. package/lib/forms/controls/simple-input.js +6 -8
  41. package/lib/forms/controls/simple-input.js.map +1 -1
  42. package/lib/forms/controls/textarea.js +13 -15
  43. package/lib/forms/controls/textarea.js.map +1 -1
  44. package/lib/forms/form.js +95 -57
  45. package/lib/forms/form.js.map +1 -1
  46. package/lib/forms/multiform.js +52 -62
  47. package/lib/forms/multiform.js.map +1 -1
  48. package/lib/generate-routes.js +7 -3
  49. package/lib/generate-routes.js.map +1 -1
  50. package/lib/index.js +3 -1
  51. package/lib/index.js.map +1 -1
  52. package/lib/page/list.js +1 -1
  53. package/lib/page/list.js.map +1 -1
  54. package/lib/page/mountable-with-fields.js +63 -0
  55. package/lib/page/mountable-with-fields.js.map +1 -0
  56. package/lib/page/mountable.js +2 -49
  57. package/lib/page/mountable.js.map +1 -1
  58. package/lib/page/page.js +2 -2
  59. package/lib/page/page.js.map +1 -1
  60. package/lib/templates/multiform.js +59 -0
  61. package/lib/templates/multiform.js.map +1 -0
  62. package/lib/templates/redirect.js +27 -0
  63. package/lib/templates/redirect.js.map +1 -0
  64. package/lib/tests/manual/multiform.js +14 -5
  65. package/lib/tests/manual/multiform.js.map +1 -1
  66. package/lib/utils/input-wrapper.js +20 -0
  67. package/lib/utils/input-wrapper.js.map +1 -0
  68. package/package.json +4 -3
  69. package/src/add-route.ts +30 -11
  70. package/src/components/components.ts +1 -0
  71. package/src/components/tabs.ts +95 -0
  72. package/src/forms/controls/checkboxed-list-input.ts +17 -10
  73. package/src/forms/controls/controls.ts +1 -0
  74. package/src/forms/controls/dropdown.ts +28 -23
  75. package/src/forms/controls/field-group.ts +32 -0
  76. package/src/forms/controls/form-control.ts +3 -1
  77. package/src/forms/controls/form-field-control.ts +4 -1
  78. package/src/forms/controls/simple-input.ts +19 -12
  79. package/src/forms/controls/textarea.ts +15 -14
  80. package/src/forms/form.ts +122 -76
  81. package/src/forms/multiform.ts +211 -0
  82. package/src/generate-routes.ts +7 -3
  83. package/src/index.ts +2 -0
  84. package/src/page/list.ts +1 -1
  85. package/src/page/mountable-with-fields.ts +112 -0
  86. package/src/page/mountable.ts +4 -96
  87. package/src/page/page.ts +7 -3
  88. package/src/templates/multiform.ts +58 -0
  89. package/src/templates/redirect.ts +22 -0
  90. package/src/tests/manual/multiform.ts +106 -0
  91. package/src/utils/input-wrapper.ts +18 -0
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inputWrapper = exports.getRequiredClass = void 0;
4
+ const tempstream_1 = require("tempstream");
5
+ function getRequiredClass(isRequired) {
6
+ return isRequired ? "required" : "";
7
+ }
8
+ exports.getRequiredClass = getRequiredClass;
9
+ function getModifierClasses(modifiers) {
10
+ return modifiers.map((m) => "form-input__wrapper--" + m).join(" ");
11
+ }
12
+ function inputWrapper(modifiers, input) {
13
+ return tempstream_1.tempstream /* HTML */ `<div
14
+ class="form-input form-input__wrapper ${getModifierClasses(modifiers)}"
15
+ >
16
+ ${input}
17
+ </div>`;
18
+ }
19
+ exports.inputWrapper = inputWrapper;
20
+ //# sourceMappingURL=input-wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-wrapper.js","sourceRoot":"","sources":["../../src/utils/input-wrapper.ts"],"names":[],"mappings":";;;AACA,2CAAwC;AAExC,SAAgB,gBAAgB,CAAC,UAAmB;IACnD,OAAO,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;AACrC,CAAC;AAFD,4CAEC;AAED,SAAS,kBAAkB,CAAC,SAAmB;IAC9C,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpE,CAAC;AAED,SAAgB,YAAY,CAAC,SAAmB,EAAE,KAAa;IAC9D,OAAO,uBAAU,CAAA,UAAU,CAAC;0CACa,kBAAkB,CAAC,SAAS,CAAC;;IAEnE,KAAK;QACD,CAAC;AACT,CAAC;AAND,oCAMC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -26,6 +26,7 @@
26
26
  "eslint-config-prettier": "^8.3.0",
27
27
  "eslint-plugin-prettier": "^3.4.1",
28
28
  "eslint-plugin-with-tsc-error": "^0.0.7",
29
+ "koa-body": "^5.0.0",
29
30
  "mocha": "^9.1.1",
30
31
  "mri": "^1.1.6",
31
32
  "nyc": "^15.1.0",
@@ -42,10 +43,10 @@
42
43
  "koa": "^2.13.0",
43
44
  "locreq": "^2.0.2",
44
45
  "merge": "^2.1.1",
46
+ "prettier": "^2.7.1",
45
47
  "prompts": "^2.4.2",
46
48
  "tempstream": "^0.0.21",
47
- "tiny-glob": "^0.2.9",
48
- "prettier": "^2.7.1"
49
+ "tiny-glob": "^0.2.9"
49
50
  },
50
51
  "peerDependencies": {
51
52
  "@koa/router": "^10.1.1",
package/src/add-route.ts CHANGED
@@ -1,14 +1,17 @@
1
+ import { existsSync, promises as fs } from "fs";
2
+ import _locreq from "locreq";
1
3
  import { resolve } from "path";
2
- import { promises as fs, existsSync } from "fs";
3
- import { question } from "./utils/question";
4
- import { pageTemplate } from "./templates/page";
4
+ import { generateRoutes } from "./generate-routes";
5
5
  import { formTemplate } from "./templates/form";
6
+ import { listTemplate } from "./templates/list";
7
+ import { multiformTemplate } from "./templates/multiform";
8
+ import { pageTemplate } from "./templates/page";
9
+ import { redirectTemplate } from "./templates/redirect";
6
10
  import { testTemplate } from "./templates/route-test";
7
11
  import { escape_url_params } from "./utils/escape-url-params";
12
+ import { question } from "./utils/question";
13
+ import prompts = require("prompts");
8
14
 
9
- import _locreq from "locreq";
10
- import { generateRoutes } from "./generate-routes";
11
- import { listTemplate } from "./templates/list";
12
15
  const target_locreq = _locreq(process.cwd());
13
16
 
14
17
  export async function addRoute(): Promise<void> {
@@ -20,11 +23,21 @@ export async function addRoute(): Promise<void> {
20
23
  "Enter a full absolute path for the new route (e.g. /admin/users/add): ",
21
24
  (s: string) => s.trim()[0] == "/"
22
25
  );
23
- const mode_answer = (await question(
24
- "Is this a page (p), a form (f), or a list (l)? ",
25
- (a: string) => ["p", "f", "l"].includes(a.toLowerCase())
26
- )) as "p" | "f" | "l";
27
- const mode = (<const>{ p: "page", f: "form", l: "list" })[mode_answer];
26
+
27
+ const response = await prompts({
28
+ type: "select",
29
+ name: "mode",
30
+ message: "What kind of route is it?",
31
+ choices: [
32
+ { title: "page", value: "page" },
33
+ { title: "form", value: "form" },
34
+ { title: "list", value: "list" },
35
+ { title: "multiform", value: "multiform" },
36
+ { title: "redirect", value: "redirect" },
37
+ ],
38
+ });
39
+
40
+ const mode = response.mode as string;
28
41
 
29
42
  const file_path = target_locreq.resolve(
30
43
  `src/back/routes/${escape_url_params(url)}.${mode}.ts`
@@ -36,8 +49,14 @@ export async function addRoute(): Promise<void> {
36
49
  page: pageTemplate,
37
50
  form: formTemplate,
38
51
  list: listTemplate,
52
+ multiform: multiformTemplate,
53
+ redirect: redirectTemplate,
39
54
  }[mode];
40
55
 
56
+ if (!template) {
57
+ throw new Error(`Could not get template ${mode}`);
58
+ }
59
+
41
60
  if (existsSync(file_path)) {
42
61
  // eslint-disable-next-line no-console
43
62
  console.error(`ERROR: File ${file_path} already exists.`);
@@ -0,0 +1 @@
1
+ export * from "./tabs";
@@ -0,0 +1,95 @@
1
+ import { FlatTemplatable } from "tempstream";
2
+
3
+ function name_to_id(str: string) {
4
+ return str.replace(/\W/g, "-");
5
+ }
6
+
7
+ const style = /* HTML */ `<style>
8
+ .tab-label {
9
+ height: 45px;
10
+ line-height: 45px;
11
+ padding: 0 45px;
12
+ display: block;
13
+ float: left;
14
+ box-sizing: border-box;
15
+ border-bottom: 3px solid transparent;
16
+ cursor: pointer;
17
+ font-size: 18px;
18
+ user-select: none;
19
+ }
20
+
21
+ @media screen and (max-width: 869px) {
22
+ .tab-label {
23
+ display: none;
24
+ }
25
+ }
26
+
27
+ .tab-label--mobile {
28
+ line-height: unset;
29
+ padding-top: 30px;
30
+ padding-left: 0;
31
+ pointer-events: none;
32
+ padding-bottom: 30px;
33
+ color: $font-graphite;
34
+ border-bottom: 2px solid $font-primary;
35
+ display: none;
36
+ @media screen and (max-width: 869px) {
37
+ display: block;
38
+ }
39
+ }
40
+
41
+ .tab-content {
42
+ padding-top: 30px;
43
+ clear: both;
44
+ }
45
+
46
+ @media screen and (max-width: 869px) {
47
+ .tab-content {
48
+ display: block !important;
49
+ }
50
+ }
51
+ </style>`;
52
+
53
+ export function tabs(
54
+ tabs: Record<string, string>,
55
+ selected_tab_style = "font-weight: bold;",
56
+ selected_index = 0
57
+ ): FlatTemplatable {
58
+ return /* HTML */ `${style}
59
+ ${Object.keys(tabs)
60
+ .map((tab_title, index) => {
61
+ const id = name_to_id(tab_title);
62
+ return /* HTML */ `<input
63
+ type="radio"
64
+ name="tabs"
65
+ id="${id}"
66
+ class="tab-radio--${id}"
67
+ style="display: none"
68
+ ${selected_index == index ? "checked" : ""}
69
+ />
70
+ <label class="tab-label tab-label--${id}" for="${id}">
71
+ ${tab_title}
72
+ </label>`;
73
+ })
74
+ .join("\n")}
75
+ ${Object.entries(tabs)
76
+ .map(([title, content]) => {
77
+ const id = name_to_id(title);
78
+ return /* HTML */ ` <div class="tab-label tab-label--mobile">
79
+ ${title}
80
+ </div>
81
+ <div class="tab-content tab-content--${id}">${content}</div>
82
+ <style>
83
+ .tab-radio--${id} ~ .tab-content--${id} {
84
+ display:none;
85
+ }
86
+ .tab-radio--${id}:checked ~ .tab-content--${id} {
87
+ display:block;
88
+ }
89
+ .tab-radio--${id}:checked ~ label.tab-label--${id} {
90
+ ${selected_tab_style}
91
+ }
92
+ </style>`;
93
+ })
94
+ .join("\n")}`;
95
+ }
@@ -1,6 +1,7 @@
1
1
  import { is, predicates } from "@sealcode/ts-predicates";
2
2
  import { Context } from "koa";
3
3
  import { tempstream } from "tempstream";
4
+ import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
4
5
  import { ChekboxedListField } from "../fields/field";
5
6
  import { FormDataValue } from "../form";
6
7
  import { FormFieldControl } from "./controls";
@@ -22,17 +23,23 @@ export class CheckboxedListInput extends FormFieldControl {
22
23
  this.field.generateOptions(ctx),
23
24
  this.field.isVisible(ctx),
24
25
  ]);
26
+
27
+ const { name, required } = this.field;
28
+
25
29
  return tempstream/* HTML */ `${isVisible
26
- ? Object.entries(options).map(
27
- ([value, text]) => /* HTML */ `<div>
28
- <input
29
- type="checkbox"
30
- id="${this.field.name}.${value}"
31
- name="${this.field.name}.${value}"
32
- ${pickedValues.includes(value) ? "checked" : ""}
33
- />
34
- <label for="${this.field.name}.${value}">${text}</label>
35
- </div>`
30
+ ? Object.entries(options).map(([value, text]) =>
31
+ inputWrapper(
32
+ [name, getRequiredClass(required)],
33
+ /* HTML */ `
34
+ <input
35
+ type="checkbox"
36
+ id="${name}.${value}"
37
+ name="${name}.${value}"
38
+ ${pickedValues.includes(value) ? "checked" : ""}
39
+ />
40
+ <label for="${name}.${value}">${text}</label>
41
+ `
42
+ )
36
43
  )
37
44
  : ""}`;
38
45
  }
@@ -10,3 +10,4 @@ export { FormParagraph } from "./form-paragraph";
10
10
  export { HTML } from "./html";
11
11
  export { HiddenInput } from "./hidden-input";
12
12
  export { Textarea } from "./textarea";
13
+ export { FieldGroup } from "./field-group";
@@ -1,6 +1,6 @@
1
1
  import { Context } from "koa";
2
- import { tempstream } from "tempstream";
3
2
  import { attribute } from "../../sanitize";
3
+ import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
4
4
  import { FormField, PickFromListField } from "../fields/field";
5
5
  import { FormDataValue } from "../form";
6
6
  import { FormFieldControl } from "./controls";
@@ -26,28 +26,33 @@ export class Dropdown extends FormFieldControl {
26
26
  data: Record<string, FormDataValue>
27
27
  ) {
28
28
  const { parsed: picked_value } = await this.field.getValue(ctx, data);
29
- const id = this.field.name;
30
- return tempstream/* HTML */ `<label for="${id}"
31
- >${this.options.label}</label
32
- ><select
33
- name="${this.field.name}"
34
- id="${id}"
35
- ${this.options.autosubmit
36
- ? `onchange='this.form.submit()'`
37
- : ""}
38
- ${!this.options.autocomplete ? `autocomplete="off"` : ""}
39
- >
40
- ${Promise.resolve(this.field.generateOptions(ctx)).then(
41
- (options) =>
42
- Object.entries(options).map(
43
- ([value, text]) =>
44
- `<option value="${attribute(value)}" ${
45
- (value || "") == picked_value
46
- ? "selected"
47
- : ""
48
- }>${text}</option>`
29
+ const name = this.field.name;
30
+ return inputWrapper(
31
+ [name, getRequiredClass(this.field.required)],
32
+ /* HTML */ `<label for="${name}">${this.options.label}</label
33
+ ><select
34
+ name="${this.field.name}"
35
+ id="${name}"
36
+ ${this.options.autosubmit
37
+ ? `onchange='this.form.submit()'`
38
+ : ""}
39
+ ${!this.options.autocomplete ? `autocomplete="off"` : ""}
40
+ >
41
+ ${
42
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
43
+ Promise.resolve(this.field.generateOptions(ctx)).then(
44
+ (options) =>
45
+ Object.entries(options).map(
46
+ ([value, text]) =>
47
+ `<option value="${attribute(value)}" ${
48
+ (value || "") == picked_value
49
+ ? "selected"
50
+ : ""
51
+ }>${text}</option>`
52
+ )
49
53
  )
50
- )}
51
- </select>`;
54
+ }
55
+ </select>`
56
+ );
52
57
  }
53
58
  }
@@ -0,0 +1,32 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable, tempstream } from "tempstream";
3
+ import { FormDataValue } from "../form";
4
+ import { FormControl } from "./form-control";
5
+
6
+ export class FieldGroup extends FormControl {
7
+ constructor(
8
+ public controls: FormControl[],
9
+ public options: { label?: string; classes?: string } = {}
10
+ ) {
11
+ super();
12
+ }
13
+
14
+ async render(
15
+ ctx: Context,
16
+ data: Record<string, FormDataValue>
17
+ ): Promise<FlatTemplatable> {
18
+ const { label, classes } = this.options;
19
+
20
+ return tempstream/* HTML */ `
21
+ <div class="field-group ${classes || ""}">
22
+ ${label ? `<label>${label}</label>` : ""}
23
+ <div class="field-group__fields">
24
+ ${this.controls.map((control) =>
25
+ control.render(ctx, data, [], "", "")
26
+ )}
27
+ </div>
28
+ </div>
29
+ `;
30
+ }
31
+ role = <const>"decoration";
32
+ }
@@ -6,7 +6,9 @@ export abstract class FormControl {
6
6
  abstract render(
7
7
  ctx: Context,
8
8
  data: Record<string, FormDataValue>,
9
- messages: FormMessage[]
9
+ messages: FormMessage[],
10
+ field_name_prefix: string,
11
+ form_id: string
10
12
  ): FlatTemplatable | Promise<FlatTemplatable>;
11
13
  abstract role: "input" | "decoration" | "messages" | "submit";
12
14
  }
@@ -18,6 +18,9 @@ export abstract class FormFieldControl extends FormControl {
18
18
 
19
19
  abstract render(
20
20
  ctx: Context,
21
- data: Record<string, FormDataValue>
21
+ data: Record<string, FormDataValue>,
22
+ messages: [],
23
+ field_name_prefix: string,
24
+ form_id: string
22
25
  ): FlatTemplatable | Promise<FlatTemplatable>;
23
26
  }
@@ -1,5 +1,7 @@
1
1
  import { Context } from "koa";
2
+ import { FlatTemplatable } from "tempstream";
2
3
  import { attribute } from "../../sanitize";
4
+ import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
3
5
  import { FormField } from "../fields/field";
4
6
  import { FormDataValue } from "../form";
5
7
  import { FormFieldControl } from "./controls";
@@ -37,8 +39,11 @@ export class SimpleInput extends FormFieldControl {
37
39
 
38
40
  async render<Fields extends Record<string, FormField>>(
39
41
  ctx: Context,
40
- data: Record<string, FormDataValue>
41
- ) {
42
+ data: Record<string, FormDataValue>,
43
+ _: unknown[],
44
+ field_prefix: string,
45
+ form_id: string
46
+ ): Promise<FlatTemplatable> {
42
47
  const id = this.options.id || this.field.name;
43
48
  const label = this.options.label || this.field.name;
44
49
  const type = this.options.type || "text";
@@ -52,16 +57,14 @@ export class SimpleInput extends FormFieldControl {
52
57
  const readonly = this.options.readonly || false;
53
58
  const required = this.field.required;
54
59
 
55
- return /* HTML */ `<div
56
- class="form-input form-input__wrapper ${required
57
- ? "form-input--required"
58
- : ""}"
59
- >
60
+ return /* HTML */ inputWrapper(
61
+ [id, type, getRequiredClass(required)],
62
+ `
60
63
  <label for="${id}">${label}</label>
61
64
  <input
62
65
  id="${id}"
63
66
  type="${type}"
64
- name="${this.field.name}"
67
+ name="${field_prefix}${this.field.name}"
65
68
  value="${attribute(
66
69
  !valid && raw
67
70
  ? raw.toString()
@@ -70,15 +73,19 @@ export class SimpleInput extends FormFieldControl {
70
73
  ((parsed as any)?.toString() as string) || ""
71
74
  : ""
72
75
  )}"
76
+ ${form_id ? `form="${form_id}"` : ""}
73
77
  placeholder="${placeholder}"
74
78
  ${readonly ? "readonly" : ""}
75
79
  ${required ? "required" : ""}
76
80
  ${!this.options.autocomplete ? `autocomplete="off"` : ""}
77
81
  ${this.options.step ? `step="${this.options.step}"` : ""}
78
82
  />
79
- ${~valid && !this.options.hide_errors
80
- ? `<div class="input__error">${message}</div>`
81
- : ""}
82
- </div>`;
83
+ ${
84
+ ~valid && !this.options.hide_errors
85
+ ? `<div class="input__error">${message}</div>`
86
+ : ""
87
+ }
88
+ `
89
+ );
83
90
  }
84
91
  }
@@ -1,6 +1,7 @@
1
1
  import { Context } from "koa";
2
2
  import { FlatTemplatable, tempstream } from "tempstream";
3
3
  import { attribute } from "../../sanitize";
4
+ import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper";
4
5
  import { FormField } from "../fields/field";
5
6
  import { FormDataValue } from "../form";
6
7
  import { FormFieldControl } from "./form-field-control";
@@ -18,20 +19,20 @@ export class Textarea extends FormFieldControl {
18
19
  data: Record<string, FormDataValue>
19
20
  ): Promise<FlatTemplatable> {
20
21
  const { raw } = await this.field.getValue(ctx, data);
21
- return tempstream/* HTML */ `<div
22
- class="form-input form-input__wrapper"
23
- >
24
- <label for="${this.field.name}"
25
- >${this.options.label || this.field.name}</label
26
- >
27
- <textarea
28
- name="${this.field.name}"
29
- id="${this.field.name}"
30
- rows="${this.options.rows || 5}"
31
- cols="${this.options.cols || 33}"
32
- >
22
+ const { name, required } = this.field;
23
+ return inputWrapper(
24
+ [name, getRequiredClass(required)],
25
+ /* HTML */ ` <label for="${name}"
26
+ >${this.options.label || name}</label
27
+ >
28
+ <textarea
29
+ name="${name}"
30
+ id="${name}"
31
+ rows="${this.options.rows || 5}"
32
+ cols="${this.options.cols || 33}"
33
+ >
33
34
  ${attribute((raw || "").toString())}</textarea
34
- >
35
- </div>`;
35
+ >`
36
+ );
36
37
  }
37
38
  }