@sealcode/sealgen 0.15.49 → 0.15.50

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.
@@ -9,5 +9,13 @@ export declare class CheckboxedListInput extends FormFieldControl {
9
9
  constructor(field: CheckboxedListField<boolean>, options?: {
10
10
  label: string;
11
11
  });
12
+ generateCountBasedClasses(n: number): string[];
12
13
  render(fctx: FormControlContext): Promise<string>;
14
+ renderOption({ name, value, form_id, label, checked, }: {
15
+ name: string;
16
+ form_id: string;
17
+ value: string;
18
+ label?: string;
19
+ checked: boolean;
20
+ }): Promise<string>;
13
21
  }
@@ -1,17 +1,16 @@
1
1
  import { Context } from "koa";
2
2
  import { FormDataValue } from "../form-types.js";
3
3
  import { FieldParseResult, FormField, FormFieldValidationResponse } from "./field.js";
4
+ export type CheckboxedListItem = {
5
+ value: string;
6
+ label?: string;
7
+ group?: string;
8
+ };
4
9
  export declare class CheckboxedListField<Required extends boolean> extends FormField<Required, Record<string, "on">> {
5
10
  required: Required;
6
- generateOptions: (ctx: Context) => Promise<{
7
- value: string;
8
- label?: string;
9
- }[]>;
11
+ generateOptions: (ctx: Context) => Promise<CheckboxedListItem[]>;
10
12
  isVisible: (ctx: Context) => Promise<boolean>;
11
- constructor(required: Required, generateOptions: (ctx: Context) => Promise<{
12
- value: string;
13
- label?: string;
14
- }[]>, isVisible?: (ctx: Context) => Promise<boolean>);
13
+ constructor(required: Required, generateOptions: (ctx: Context) => Promise<CheckboxedListItem[]>, isVisible?: (ctx: Context) => Promise<boolean>);
15
14
  predicate: (v: unknown, w: unknown) => {
16
15
  readonly match: boolean;
17
16
  readonly wildcard: string[];
@@ -7,6 +7,15 @@ class CheckboxedListInput extends FormFieldControl {
7
7
  this.field = field;
8
8
  this.options = options;
9
9
  }
10
+ generateCountBasedClasses(n) {
11
+ return [
12
+ "options-count--" + n.toString(),
13
+ ...n >= 5 ? ["options-count--5-or-more"] : [],
14
+ ...n >= 10 ? ["options-count--10-or-more"] : [],
15
+ ...n >= 15 ? ["options-count--15-or-more"] : [],
16
+ ...n >= 20 ? ["options-count--20-or-more"] : []
17
+ ];
18
+ }
10
19
  async render(fctx) {
11
20
  const {
12
21
  valid,
@@ -18,47 +27,84 @@ class CheckboxedListInput extends FormFieldControl {
18
27
  this.field.generateOptions(fctx.ctx),
19
28
  this.field.isVisible(fctx.ctx)
20
29
  ]);
30
+ const ungroupped = [];
31
+ const groups = {};
32
+ for (const option of options) {
33
+ if (!option.group) {
34
+ ungroupped.push(option);
35
+ } else {
36
+ if (!groups[option.group]) {
37
+ groups[option.group] = [];
38
+ }
39
+ groups[option.group].push(option);
40
+ }
41
+ }
21
42
  const { name, required } = this.field;
22
43
  return inputWrapper(
23
44
  "checkboxed-list",
24
45
  [
25
46
  name,
26
47
  getRequiredClass(required),
27
- "options-count--" + options.length.toString(),
28
- ...options.length >= 5 ? ["options-count--5-or-more"] : [],
29
- ...options.length >= 10 ? ["options-count--10-or-more"] : [],
30
- ...options.length >= 15 ? ["options-count--15-or-more"] : [],
31
- ...options.length >= 20 ? ["options-count--20-or-more"] : []
48
+ ...this.generateCountBasedClasses(options.length)
32
49
  ],
33
50
  tempstream`<label
34
51
  >${this.options.label || this.field.name}</label
35
52
  >
36
53
  ${~valid && fctx.validate ? `<div class="input__error">${message}</div>` : ""}
37
54
  <ul>
38
- ${isVisible ? options.map(
39
- ({ value, label }) => tempstream`<li>
40
- ${inputWrapper(
41
- "checkbox",
42
- [name],
43
- /* HTML */
44
- `
45
- <input
46
- type="checkbox"
47
- id="${name}.${value}"
48
- name="${name}.${value}"
49
- ${pickedValues.includes(value) ? "checked" : ""}
50
- form=${fctx.form_id}
51
- />
52
- <label for="${name}.${value}"
53
- >${label || value}</label
54
- >
55
- `
56
- )}
57
- </li>`
55
+ ${isVisible ? Object.entries(groups).map(
56
+ ([group_label, options2]) => tempstream` <h3>
57
+ ${group_label}
58
+ </h3>
59
+ ${options2.map(
60
+ ({ value, label }) => this.renderOption({
61
+ name,
62
+ value,
63
+ label,
64
+ form_id: fctx.form_id,
65
+ checked: pickedValues.includes(
66
+ value
67
+ )
68
+ })
69
+ )}`
70
+ ) : ""}
71
+ ${isVisible ? ungroupped.map(
72
+ ({ value, label }) => this.renderOption({
73
+ name,
74
+ value,
75
+ label,
76
+ form_id: fctx.form_id,
77
+ checked: pickedValues.includes(value)
78
+ })
58
79
  ) : ""}
59
80
  </ul>`
60
81
  );
61
82
  }
83
+ renderOption({
84
+ name,
85
+ value,
86
+ form_id,
87
+ label,
88
+ checked
89
+ }) {
90
+ return tempstream`<li>
91
+ ${inputWrapper(
92
+ "checkbox",
93
+ [name],
94
+ /* HTML */
95
+ `
96
+ <input
97
+ type="checkbox"
98
+ id="${name}.${value}"
99
+ name="${name}.${value}"
100
+ ${checked ? "checked" : ""}
101
+ form=${form_id}
102
+ />
103
+ <label for="${name}.${value}">${label || value}</label>
104
+ `
105
+ )}
106
+ </li>`;
107
+ }
62
108
  }
63
109
  export {
64
110
  CheckboxedListInput
@@ -231,6 +231,55 @@ describe("checkboxed-list", () => {
231
231
  await page.getByRole("button", { name: "Wy\u015Blij" }).click();
232
232
  await page.getByText(/ABC/).click({ timeout: 500 });
233
233
  });
234
+ it("groups checkboxes based on the value of the 'group' attribute", async () => {
235
+ const port = await getPort();
236
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
237
+ const app = new Koa();
238
+ const router = new Router();
239
+ const fields = {
240
+ syllables: new CheckboxedListField(true, async () => {
241
+ return [
242
+ ...["e", "y", "u", "i", "o", "a"].map((s, index) => ({
243
+ value: "vowel" + index,
244
+ label: s,
245
+ group: "vowels"
246
+ })),
247
+ ...["q", "w", "r", "t", "p", "s", "test_label"].map((s, index) => ({
248
+ value: "consonant" + index,
249
+ label: s,
250
+ group: "consonants"
251
+ }))
252
+ ];
253
+ })
254
+ };
255
+ mount(
256
+ router,
257
+ "/",
258
+ new class extends Form {
259
+ constructor() {
260
+ super(...arguments);
261
+ this.fields = fields;
262
+ this.controls = [new CheckboxedListInput(fields.syllables, { label: "Letters" })];
263
+ }
264
+ async onSubmit(ctx) {
265
+ }
266
+ async canAccess(_) {
267
+ return { canAccess: true, message: "" };
268
+ }
269
+ }(),
270
+ true
271
+ );
272
+ app.use(router.routes()).use(router.allowedMethods());
273
+ server = app.listen(port);
274
+ browser = await getBrowser();
275
+ const context = await browser.newContext();
276
+ page = await context.newPage();
277
+ await page.goto(`http://localhost:${port}`);
278
+ await page.getByRole("button", { name: "Wy\u015Blij" }).click();
279
+ await page.getByText(/vowels/).click({ timeout: 500 });
280
+ await page.getByText(/consonants/).click({ timeout: 500 });
281
+ await page.getByText("test_label").click({ timeout: 500 });
282
+ });
234
283
  });
235
284
  });
236
285
  //# sourceMappingURL=checkboxed-list-input.test.js.map
@@ -1,11 +1,7 @@
1
1
  import { toKebabCase, toPascalCase } from "js-convert-case";
2
2
  function jddComponentTemplate(component_name) {
3
- return `import type { FlatTemplatable } from "tempstream";
4
- import { TempstreamJSX } from "tempstream";
5
- import type {
6
- ExtractStructuredComponentArgumentsParsed,
7
- JDDContext,
8
- } from "@sealcode/jdd";
3
+ return `import { TempstreamJSX } from "tempstream";
4
+ import type { ComponentToHTMLArgs } from "@sealcode/jdd";
9
5
  import { Component, ComponentArguments } from "@sealcode/jdd";
10
6
 
11
7
  const component_arguments = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.15.49",
3
+ "version": "0.15.50",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -255,5 +255,58 @@ describe("checkboxed-list", () => {
255
255
  await page.getByRole("button", { name: "Wyślij" }).click();
256
256
  await page.getByText(/ABC/).click({ timeout: 500 }); // should display an error message for that field
257
257
  });
258
+
259
+ it("groups checkboxes based on the value of the 'group' attribute", async () => {
260
+ const port = await getPort();
261
+ console.log(`Using port ${port} form checkboxed-list-input.test.ts`);
262
+ const app = new Koa();
263
+ const router = new Router();
264
+
265
+ const fields = {
266
+ syllables: new CheckboxedListField(true, async () => {
267
+ return [
268
+ ...["e", "y", "u", "i", "o", "a"].map((s, index) => ({
269
+ value: "vowel" + index,
270
+ label: s,
271
+ group: "vowels",
272
+ })),
273
+ ...["q", "w", "r", "t", "p", "s", "test_label"].map((s, index) => ({
274
+ value: "consonant" + index,
275
+ label: s,
276
+ group: "consonants",
277
+ })),
278
+ ];
279
+ }),
280
+ };
281
+
282
+ mount(
283
+ router,
284
+ "/",
285
+ new (class extends Form<typeof fields, unknown> {
286
+ fields = fields;
287
+ controls = [new CheckboxedListInput(fields.syllables, { label: "Letters" })];
288
+
289
+ async onSubmit(ctx: Context) {
290
+ // noop
291
+ }
292
+
293
+ async canAccess(_: Context) {
294
+ return { canAccess: true, message: "" };
295
+ }
296
+ })(),
297
+ true
298
+ );
299
+ app.use(router.routes()).use(router.allowedMethods());
300
+ server = app.listen(port);
301
+
302
+ browser = await getBrowser();
303
+ const context = await browser.newContext();
304
+ page = await context.newPage();
305
+ await page.goto(`http://localhost:${port}`);
306
+ await page.getByRole("button", { name: "Wyślij" }).click();
307
+ await page.getByText(/vowels/).click({ timeout: 500 }); // should display an error message for that field
308
+ await page.getByText(/consonants/).click({ timeout: 500 }); // should display an error message for that field
309
+ await page.getByText("test_label").click({ timeout: 500 });
310
+ });
258
311
  });
259
312
  });
@@ -1,6 +1,9 @@
1
1
  import { tempstream } from "tempstream";
2
2
  import { getRequiredClass, inputWrapper } from "../../utils/input-wrapper.js";
3
- import { CheckboxedListField } from "../fields/checkboxed-list.js";
3
+ import {
4
+ CheckboxedListField,
5
+ CheckboxedListItem,
6
+ } from "../fields/checkboxed-list.js";
4
7
  import { FormControlContext } from "./form-control.js";
5
8
  import { FormFieldControl } from "./form-field-control.js";
6
9
 
@@ -12,6 +15,16 @@ export class CheckboxedListInput extends FormFieldControl {
12
15
  super([field]);
13
16
  }
14
17
 
18
+ generateCountBasedClasses(n: number) {
19
+ return [
20
+ "options-count--" + n.toString(),
21
+ ...(n >= 5 ? ["options-count--5-or-more"] : []),
22
+ ...(n >= 10 ? ["options-count--10-or-more"] : []),
23
+ ...(n >= 15 ? ["options-count--15-or-more"] : []),
24
+ ...(n >= 20 ? ["options-count--20-or-more"] : []),
25
+ ];
26
+ }
27
+
15
28
  async render(fctx: FormControlContext) {
16
29
  const {
17
30
  valid,
@@ -26,17 +39,26 @@ export class CheckboxedListInput extends FormFieldControl {
26
39
  this.field.isVisible(fctx.ctx),
27
40
  ]);
28
41
 
42
+ const ungroupped = [];
43
+ const groups: Record<string, CheckboxedListItem[] | undefined> = {};
44
+ for (const option of options) {
45
+ if (!option.group) {
46
+ ungroupped.push(option);
47
+ } else {
48
+ if (!groups[option.group]) {
49
+ groups[option.group] = [];
50
+ }
51
+ groups[option.group]!.push(option);
52
+ }
53
+ }
54
+
29
55
  const { name, required } = this.field;
30
56
  return inputWrapper(
31
57
  "checkboxed-list",
32
58
  [
33
59
  name,
34
60
  getRequiredClass(required),
35
- "options-count--" + options.length.toString(),
36
- ...(options.length >= 5 ? ["options-count--5-or-more"] : []),
37
- ...(options.length >= 10 ? ["options-count--10-or-more"] : []),
38
- ...(options.length >= 15 ? ["options-count--15-or-more"] : []),
39
- ...(options.length >= 20 ? ["options-count--20-or-more"] : []),
61
+ ...this.generateCountBasedClasses(options.length),
40
62
  ],
41
63
  tempstream/* HTML */ `<label
42
64
  >${this.options.label || this.field.name}</label
@@ -46,30 +68,68 @@ export class CheckboxedListInput extends FormFieldControl {
46
68
  : ""}
47
69
  <ul>
48
70
  ${isVisible
49
- ? options.map(
50
- ({ value, label }) => tempstream/* HTML */ `<li>
51
- ${inputWrapper(
52
- "checkbox",
53
- [name],
54
- /* HTML */ `
55
- <input
56
- type="checkbox"
57
- id="${name}.${value}"
58
- name="${name}.${value}"
59
- ${pickedValues.includes(value)
60
- ? "checked"
61
- : ""}
62
- form=${fctx.form_id}
63
- />
64
- <label for="${name}.${value}"
65
- >${label || value}</label
66
- >
67
- `
68
- )}
69
- </li>`
71
+ ? Object.entries(groups).map(
72
+ ([group_label, options]) =>
73
+ tempstream/* HTML */ ` <h3>
74
+ ${group_label}
75
+ </h3>
76
+ ${options!.map(({ value, label }) =>
77
+ this.renderOption({
78
+ name,
79
+ value,
80
+ label,
81
+ form_id: fctx.form_id,
82
+ checked:
83
+ pickedValues.includes(
84
+ value
85
+ ),
86
+ })
87
+ )}`
88
+ )
89
+ : ""}
90
+ ${isVisible
91
+ ? ungroupped.map(({ value, label }) =>
92
+ this.renderOption({
93
+ name,
94
+ value,
95
+ label,
96
+ form_id: fctx.form_id,
97
+ checked: pickedValues.includes(value),
98
+ })
70
99
  )
71
100
  : ""}
72
101
  </ul>`
73
102
  );
74
103
  }
104
+
105
+ renderOption({
106
+ name,
107
+ value,
108
+ form_id,
109
+ label,
110
+ checked,
111
+ }: {
112
+ name: string;
113
+ form_id: string;
114
+ value: string;
115
+ label?: string;
116
+ checked: boolean;
117
+ }) {
118
+ return tempstream/* HTML */ `<li>
119
+ ${inputWrapper(
120
+ "checkbox",
121
+ [name],
122
+ /* HTML */ `
123
+ <input
124
+ type="checkbox"
125
+ id="${name}.${value}"
126
+ name="${name}.${value}"
127
+ ${checked ? "checked" : ""}
128
+ form=${form_id}
129
+ />
130
+ <label for="${name}.${value}">${label || value}</label>
131
+ `
132
+ )}
133
+ </li>`;
134
+ }
75
135
  }
@@ -7,15 +7,19 @@ import {
7
7
  FormFieldValidationResponse,
8
8
  } from "./field.js";
9
9
 
10
+ export type CheckboxedListItem = {
11
+ value: string;
12
+ label?: string;
13
+ group?: string;
14
+ };
15
+
10
16
  export class CheckboxedListField<Required extends boolean> extends FormField<
11
17
  Required,
12
18
  Record<string, "on">
13
19
  > {
14
20
  constructor(
15
21
  public required: Required,
16
- public generateOptions: (
17
- ctx: Context
18
- ) => Promise<{ value: string; label?: string }[]>,
22
+ public generateOptions: (ctx: Context) => Promise<CheckboxedListItem[]>,
19
23
  public isVisible: (ctx: Context) => Promise<boolean> = () =>
20
24
  Promise.resolve(true)
21
25
  ) {
@@ -1,12 +1,8 @@
1
1
  import { toKebabCase, toPascalCase } from "js-convert-case";
2
2
 
3
3
  export function jddComponentTemplate(component_name: string) {
4
- return `import type { FlatTemplatable } from "tempstream";
5
- import { TempstreamJSX } from "tempstream";
6
- import type {
7
- ExtractStructuredComponentArgumentsParsed,
8
- JDDContext,
9
- } from "@sealcode/jdd";
4
+ return `import { TempstreamJSX } from "tempstream";
5
+ import type { ComponentToHTMLArgs } from "@sealcode/jdd";
10
6
  import { Component, ComponentArguments } from "@sealcode/jdd";
11
7
 
12
8
  const component_arguments = {