@thi.ng/rdom-forms 0.1.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-09T19:12:03Z
3
+ - **Last updated**: 2023-12-12T15:03:05Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,18 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/rdom-forms@0.2.0) (2023-12-11)
13
+
14
+ #### 🚀 Features
15
+
16
+ - update range() value label handling ([41f97d3](https://github.com/thi-ng/umbrella/commit/41f97d3))
17
+ - add value type generics for selectXX/multiSelectXX ([55d9897](https://github.com/thi-ng/umbrella/commit/55d9897))
18
+ - make attribs type-specifc ([5c6de7f](https://github.com/thi-ng/umbrella/commit/5c6de7f))
19
+
20
+ #### 🩹 Bug fixes
21
+
22
+ - fix trigger() event handler ([9faaf26](https://github.com/thi-ng/umbrella/commit/9faaf26))
23
+
12
24
  ## [0.1.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/rdom-forms@0.1.0) (2023-12-09)
13
25
 
14
26
  #### 🚀 Features
package/README.md CHANGED
@@ -68,7 +68,7 @@ For Node.js REPL:
68
68
  const rdomForms = await import("@thi.ng/rdom-forms");
69
69
  ```
70
70
 
71
- Package sizes (brotli'd, pre-treeshake): ESM: 2.13 KB
71
+ Package sizes (brotli'd, pre-treeshake): ESM: 2.18 KB
72
72
 
73
73
  ## Dependencies
74
74
 
@@ -81,13 +81,18 @@ Package sizes (brotli'd, pre-treeshake): ESM: 2.13 KB
81
81
 
82
82
  ## Usage examples
83
83
 
84
- One project in this repo's
84
+ Several projects in this repo's
85
85
  [/examples](https://github.com/thi-ng/umbrella/tree/develop/examples)
86
- directory is using this package:
87
-
88
- | Screenshot | Description | Live demo | Source |
89
- |:--------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------|:---------------------------------------------------|:--------------------------------------------------------------------------------|
90
- | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/rdom-formgen.jpg" width="240"/> | Basic usage of the declarative rdom-forms generator | [Demo](https://demo.thi.ng/umbrella/rdom-formgen/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/rdom-formgen) |
86
+ directory are using this package:
87
+
88
+ | Screenshot | Description | Live demo | Source |
89
+ |:-------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------|:--------------------------------------------------------|:-------------------------------------------------------------------------------------|
90
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/big-font.png" width="240"/> | Large ASCII font text generator using @thi.ng/rdom | [Demo](https://demo.thi.ng/umbrella/big-font/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/big-font) |
91
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/dominant-colors.png" width="240"/> | Color palette generation via dominant color extraction from uploaded images | [Demo](https://demo.thi.ng/umbrella/dominant-colors/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/dominant-colors) |
92
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/pixel-colormatrix.jpg" width="240"/> | Matrix-based image color adjustments | [Demo](https://demo.thi.ng/umbrella/pixel-colormatrix/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/pixel-colormatrix) |
93
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/pixel-sorting.png" width="240"/> | Interactive pixel sorting tool using thi.ng/color & thi.ng/pixel | [Demo](https://demo.thi.ng/umbrella/pixel-sorting/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/pixel-sorting) |
94
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/rdom-formgen.jpg" width="240"/> | Basic usage of the declarative rdom-forms generator | [Demo](https://demo.thi.ng/umbrella/rdom-formgen/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/rdom-formgen) |
95
+ | <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/rdom-lissajous.png" width="240"/> | rdom & hiccup-canvas interop test | [Demo](https://demo.thi.ng/umbrella/rdom-lissajous/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/rdom-lissajous) |
91
96
 
92
97
  ## API
93
98
 
package/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { Predicate } from "@thi.ng/api";
2
- import type { Attribs, FormAttribs, InputFileAttribs, InputRadioAttribs } from "@thi.ng/hiccup-html";
1
+ import type { Fn, Predicate } from "@thi.ng/api";
2
+ import type { Attribs, FormAttribs, InputAttribs, InputCheckboxAttribs, InputFileAttribs, InputNumericAttribs, InputRadioAttribs, InputTextAttribs, SelectAttribs, TextAreaAttribs } from "@thi.ng/hiccup-html";
3
3
  import type { ComponentLike } from "@thi.ng/rdom";
4
4
  import type { ISubscriber, ISubscription } from "@thi.ng/rstream";
5
5
  export interface CommonAttribs {
@@ -42,6 +42,7 @@ export interface Value extends FormItem, Partial<CommonAttribs> {
42
42
  desc?: any;
43
43
  required?: boolean;
44
44
  readonly?: boolean;
45
+ attribs?: Partial<InputAttribs>;
45
46
  }
46
47
  export interface WithPresets<T> {
47
48
  /**
@@ -57,11 +58,11 @@ export interface Num extends Value, WithPresets<number> {
57
58
  size?: number;
58
59
  step?: number;
59
60
  value?: ISubscription<number, number>;
61
+ attribs?: Partial<InputNumericAttribs>;
60
62
  }
61
63
  export interface Range extends Omit<Num, "type" | "placeholder" | "size"> {
62
64
  type: "range";
63
- vlabel?: boolean;
64
- vlabelPrec?: number;
65
+ vlabel?: boolean | number | Fn<number, string>;
65
66
  }
66
67
  export interface Str extends Value, WithPresets<string> {
67
68
  type: "str";
@@ -71,6 +72,7 @@ export interface Str extends Value, WithPresets<string> {
71
72
  placeholder?: string;
72
73
  size?: number;
73
74
  value?: ISubscription<string, string>;
75
+ attribs?: Partial<InputTextAttribs>;
74
76
  }
75
77
  export interface Email extends Omit<Str, "type"> {
76
78
  type: "email";
@@ -93,6 +95,7 @@ export interface Text extends Value {
93
95
  rows?: number;
94
96
  placeholder?: string;
95
97
  value?: ISubscription<string, string>;
98
+ attribs?: Partial<TextAreaAttribs>;
96
99
  }
97
100
  export interface Color extends Value, WithPresets<string> {
98
101
  type: "color";
@@ -120,6 +123,7 @@ export interface Month extends Omit<DateTime, "type" | "list"> {
120
123
  export interface Select<T> extends Value {
121
124
  items: (T | SelectItem<T> | SelectItemGroup<T>)[];
122
125
  value?: ISubscription<T, T>;
126
+ attribs?: Partial<Omit<SelectAttribs, "multiple">>;
123
127
  }
124
128
  export interface SelectItemGroup<T> {
125
129
  name: string;
@@ -130,21 +134,22 @@ export interface SelectItem<T> {
130
134
  label?: string;
131
135
  desc?: string;
132
136
  }
133
- export interface SelectStr extends Select<string> {
137
+ export interface SelectStr<T extends string = string> extends Select<T> {
134
138
  type: "selectStr";
135
139
  }
136
- export interface SelectNum extends Select<number> {
140
+ export interface SelectNum<T extends number = number> extends Select<T> {
137
141
  type: "selectNum";
138
142
  }
139
143
  export interface MultiSelect<T> extends Value {
140
144
  items: (T | SelectItem<T> | SelectItemGroup<T>)[];
141
145
  value?: ISubscription<T[], T[]>;
142
146
  size?: number;
147
+ attribs?: Partial<Omit<SelectAttribs, "multiple">>;
143
148
  }
144
- export interface MultiSelectStr extends MultiSelect<string> {
149
+ export interface MultiSelectStr<T extends string = string> extends MultiSelect<T> {
145
150
  type: "multiSelectStr";
146
151
  }
147
- export interface MultiSelectNum extends MultiSelect<number> {
152
+ export interface MultiSelectNum<T extends number = number> extends MultiSelect<T> {
148
153
  type: "multiSelectNum";
149
154
  }
150
155
  export interface Toggle extends Value {
@@ -155,10 +160,12 @@ export interface Trigger extends Value {
155
160
  type: "trigger";
156
161
  title: string;
157
162
  value?: ISubscriber<boolean>;
163
+ attribs?: Partial<InputCheckboxAttribs>;
158
164
  }
159
165
  export interface Radio<T> extends Value {
160
166
  items: (T | SelectItem<T>)[];
161
167
  value?: ISubscription<T, T>;
168
+ attribs?: Partial<InputRadioAttribs>;
162
169
  }
163
170
  export interface RadioNum extends Radio<number> {
164
171
  type: "radioNum";
@@ -171,11 +178,13 @@ export interface FileVal extends Value {
171
178
  accept?: string[];
172
179
  capture?: InputFileAttribs["capture"];
173
180
  value?: ISubscriber<File>;
181
+ attribs?: Partial<InputFileAttribs>;
174
182
  }
175
183
  export interface MultiFileVal extends Value {
176
184
  type: "multiFile";
177
185
  accept?: string[];
178
186
  value?: ISubscriber<FileList>;
187
+ attribs?: Partial<InputFileAttribs>;
179
188
  }
180
189
  type KnownTypes = Color | Container | DateTime | DateVal | Email | FileVal | Group | Month | MultiFileVal | MultiSelectNum | MultiSelectStr | Num | Password | Phone | Range | SelectNum | SelectStr | Str | Text | Time | Toggle | Trigger | UrlVal | Week;
181
190
  /**
@@ -212,7 +221,12 @@ export interface TypeAttribs extends Record<KnownTypes["type"], Partial<Attribs>
212
221
  /**
213
222
  * Attribs for {@link range} label elements
214
223
  */
215
- rangeLabelAttribs: Partial<Attribs>;
224
+ rangeLabel: Partial<Attribs>;
225
+ /**
226
+ * Attribs for the wrapper element of a single {@link range} widget (incl.
227
+ * input element and optional value label)
228
+ */
229
+ rangeWrapper: Partial<Attribs>;
216
230
  [id: string]: Partial<Attribs>;
217
231
  }
218
232
  export interface FormOpts extends CommonAttribs {
@@ -271,6 +285,18 @@ export interface BehaviorOpts {
271
285
  * @defaultValue false
272
286
  */
273
287
  radioLabelBefore: boolean;
288
+ /**
289
+ * Number of fractional digits for range sliders.
290
+ *
291
+ * @defaultValue 2
292
+ */
293
+ rangeLabelFmt: number | Fn<number, string>;
294
+ /**
295
+ * If true, the label for toggle widgets will come before the actual
296
+ * input element. By default, the order is reversed.
297
+ *
298
+ * @defaultValue false
299
+ */
274
300
  toggleLabelBefore: boolean;
275
301
  }
276
302
  export {};
package/api.js CHANGED
@@ -1 +0,0 @@
1
- export {};
package/compile.d.ts CHANGED
@@ -24,8 +24,6 @@ export declare const email: (spec: PartialSpec<Email> | ReadonlyPartialSpec<Emai
24
24
  export declare const file: (spec: PartialSpec<FileVal> | ReadonlyPartialSpec<FileVal, never>) => FileVal;
25
25
  export declare const month: (spec: PartialSpec<Month> | ReadonlyPartialSpec<Month, string>) => Month;
26
26
  export declare const multiFile: (spec: PartialSpec<MultiFileVal> | ReadonlyPartialSpec<MultiFileVal, never>) => MultiFileVal;
27
- export declare const multiSelectNum: (spec: PartialSpec<MultiSelectNum> | ReadonlyPartialSpec<MultiSelectNum, number>) => MultiSelectNum;
28
- export declare const multiSelectStr: (spec: PartialSpec<MultiSelectStr> | ReadonlyPartialSpec<MultiSelectStr, string>) => MultiSelectStr;
29
27
  export declare const num: (spec: PartialSpec<Num> | ReadonlyPartialSpec<Num, number>) => Num;
30
28
  export declare const password: (spec: PartialSpec<Password> | ReadonlyPartialSpec<Password, string>) => Password;
31
29
  export declare const phone: (spec: PartialSpec<Email> | ReadonlyPartialSpec<Email, string>) => Email;
@@ -33,8 +31,6 @@ export declare const radioNum: (spec: PartialSpec<RadioNum> | ReadonlyPartialSpe
33
31
  export declare const radioStr: (spec: PartialSpec<RadioStr> | ReadonlyPartialSpec<RadioStr, string>) => RadioStr;
34
32
  export declare const range: (spec: PartialSpec<Range> | ReadonlyPartialSpec<Range, number>) => Range;
35
33
  export declare const search: (spec: PartialSpec<Str> | ReadonlyPartialSpec<Str, string>) => Str;
36
- export declare const selectNum: (spec: PartialSpec<SelectNum> | ReadonlyPartialSpec<SelectNum, number>) => SelectNum;
37
- export declare const selectStr: (spec: PartialSpec<SelectStr> | ReadonlyPartialSpec<SelectStr, string>) => SelectStr;
38
34
  export declare const str: (spec: PartialSpec<Str> | ReadonlyPartialSpec<Str, string>) => Str;
39
35
  export declare const text: (spec: PartialSpec<Text> | ReadonlyPartialSpec<Text, string>) => Text;
40
36
  export declare const time: (spec: PartialSpec<Time> | ReadonlyPartialSpec<Time, string>) => Time;
@@ -42,6 +38,10 @@ export declare const toggle: (spec: PartialSpec<Toggle> | ReadonlyPartialSpec<To
42
38
  export declare const trigger: (spec: PartialSpec<Trigger> | ReadonlyPartialSpec<Trigger, string>) => Trigger;
43
39
  export declare const url: (spec: PartialSpec<UrlVal> | ReadonlyPartialSpec<UrlVal, string>) => UrlVal;
44
40
  export declare const week: (spec: PartialSpec<Week> | ReadonlyPartialSpec<Week, string>) => Week;
41
+ export declare const selectNum: <T extends number = number>(spec: PartialSpec<SelectNum<T>> | ReadonlyPartialSpec<SelectNum<T>, string>) => SelectNum<T>;
42
+ export declare const selectStr: <T extends string = string>(spec: PartialSpec<SelectStr<T>> | ReadonlyPartialSpec<SelectStr<T>, string>) => SelectStr<T>;
43
+ export declare const multiSelectNum: <T extends number = number>(spec: PartialSpec<MultiSelectNum<T>> | ReadonlyPartialSpec<MultiSelectNum<T>, string>) => MultiSelectNum<T>;
44
+ export declare const multiSelectStr: <T extends string = string>(spec: PartialSpec<MultiSelectStr<T>> | ReadonlyPartialSpec<MultiSelectStr<T>, string>) => MultiSelectStr<T>;
45
45
  /**
46
46
  * Compiles given {@link FormItem} spec into a hiccup/rdom component, using
47
47
  * provided options to customize attributes and behaviors.
package/compile.js CHANGED
@@ -4,136 +4,157 @@ import { isPlainObject } from "@thi.ng/checks/is-plain-object";
4
4
  import { isString } from "@thi.ng/checks/is-string";
5
5
  import { defmulti } from "@thi.ng/defmulti/defmulti";
6
6
  import { div } from "@thi.ng/hiccup-html/blocks";
7
- import { form as $form, button, checkbox, fieldset, inputColor, inputFile, inputNumber, inputRange, inputText, label, legend, optGroup, option, radio, select, textArea, } from "@thi.ng/hiccup-html/forms";
7
+ import {
8
+ form as $form,
9
+ button,
10
+ checkbox,
11
+ fieldset,
12
+ inputColor,
13
+ inputFile,
14
+ inputNumber,
15
+ inputRange,
16
+ inputText,
17
+ label,
18
+ legend,
19
+ optGroup,
20
+ option,
21
+ radio,
22
+ select,
23
+ textArea
24
+ } from "@thi.ng/hiccup-html/forms";
8
25
  import { span } from "@thi.ng/hiccup-html/inline";
9
26
  import { datalist } from "@thi.ng/hiccup-html/lists";
10
- import { $attribs, $input, $inputCheckbox, $inputFile, $inputFiles, $inputNum, $inputTrigger, $replace, } from "@thi.ng/rdom";
11
- export const form = (attribs, ...items) => ({
12
- type: "form",
13
- attribs,
14
- items,
27
+ import {
28
+ $attribs,
29
+ $input,
30
+ $inputCheckbox,
31
+ $inputFile,
32
+ $inputFiles,
33
+ $inputNum,
34
+ $inputTrigger,
35
+ $replace
36
+ } from "@thi.ng/rdom";
37
+ const form = (attribs, ...items) => ({
38
+ type: "form",
39
+ attribs,
40
+ items
15
41
  });
16
- export const container = (attribs, ...items) => ({
17
- type: "container",
18
- attribs,
19
- items,
42
+ const container = (attribs, ...items) => ({
43
+ type: "container",
44
+ attribs,
45
+ items
20
46
  });
21
- export const group = (spec, ...items) => ({
22
- ...spec,
23
- type: "group",
24
- items,
47
+ const group = (spec, ...items) => ({
48
+ ...spec,
49
+ type: "group",
50
+ items
25
51
  });
26
- export const custom = (body) => ({
27
- type: "custom",
28
- body,
52
+ const custom = (body) => ({
53
+ type: "custom",
54
+ body
29
55
  });
30
56
  let __nextID = 0;
31
57
  const $ = (type, defaults) => (spec) => ({
32
- id: spec.id || `${type}-${__nextID++}`,
33
- type,
34
- ...defaults,
35
- ...spec,
58
+ id: spec.id || `${type}-${__nextID++}`,
59
+ type,
60
+ ...defaults,
61
+ ...spec
36
62
  });
37
- export const color = $("color");
38
- export const date = $("date");
39
- export const dateTime = $("dateTime");
40
- export const email = $("email", { autocomplete: true });
41
- export const file = $("file");
42
- export const month = $("month");
43
- export const multiFile = $("multiFile");
44
- export const multiSelectNum = $("multiSelectNum");
45
- export const multiSelectStr = $("multiSelectStr");
46
- export const num = $("num");
47
- export const password = $("password", { autocomplete: true });
48
- export const phone = $("tel", { autocomplete: true });
49
- export const radioNum = $("radioNum");
50
- export const radioStr = $("radioStr");
51
- export const range = $("range");
52
- export const search = $("search");
53
- export const selectNum = $("selectNum");
54
- export const selectStr = $("selectStr");
55
- export const str = $("str");
56
- export const text = $("text");
57
- export const time = $("time");
58
- export const toggle = $("toggle");
59
- export const trigger = $("trigger");
60
- export const url = $("url");
61
- export const week = $("week");
62
- /** @internal */
63
+ const color = $("color");
64
+ const date = $("date");
65
+ const dateTime = $("dateTime");
66
+ const email = $("email", { autocomplete: true });
67
+ const file = $("file");
68
+ const month = $("month");
69
+ const multiFile = $("multiFile");
70
+ const num = $("num");
71
+ const password = $("password", { autocomplete: true });
72
+ const phone = $("tel", { autocomplete: true });
73
+ const radioNum = $("radioNum");
74
+ const radioStr = $("radioStr");
75
+ const range = $("range");
76
+ const search = $("search");
77
+ const str = $("str");
78
+ const text = $("text");
79
+ const time = $("time");
80
+ const toggle = $("toggle");
81
+ const trigger = $("trigger");
82
+ const url = $("url");
83
+ const week = $("week");
84
+ const selectNum = (spec) => $("selectNum")(spec);
85
+ const selectStr = (spec) => $("selectStr")(spec);
86
+ const multiSelectNum = (spec) => $("multiSelectNum")(spec);
87
+ const multiSelectStr = (spec) => $("multiSelectStr")(spec);
63
88
  const __genID = (id, opts) => opts.prefix ? opts.prefix + id : id;
64
- /** @internal */
65
- const __genLabel = (x, opts) => label({ ...opts.labelAttribs, ...x.labelAttribs, for: __genID(x.id, opts) }, x.label ?? x.id, x.desc ? span({ ...opts.descAttribs, ...x.descAttribs }, x.desc) : null);
66
- /** @internal */
89
+ const __genLabel = (x, opts) => label(
90
+ { ...opts.labelAttribs, ...x.labelAttribs, for: __genID(x.id, opts) },
91
+ x.label ?? x.id,
92
+ x.desc ? span({ ...opts.descAttribs, ...x.descAttribs }, x.desc) : null
93
+ );
67
94
  const __genList = (id, list) => datalist({ id: id + "--list" }, ...list.map((value) => option({ value })));
68
- /** @internal */
69
95
  const __genCommon = (val, opts) => {
70
- const res = [];
71
- if (val.label !== false && opts.behaviors?.labels !== false) {
72
- res.push(__genLabel(val, opts));
73
- }
74
- if (val.list) {
75
- res.push(__genList(__genID(val.id, opts), val.list));
76
- }
77
- return res;
96
+ const res = [];
97
+ if (val.label !== false && opts.behaviors?.labels !== false) {
98
+ res.push(__genLabel(val, opts));
99
+ }
100
+ if (val.list) {
101
+ res.push(__genList(__genID(val.id, opts), val.list));
102
+ }
103
+ return res;
78
104
  };
79
- /** @internal */
80
105
  const __attribs = (attribs, events, val, opts, value = "value") => {
81
- const id = __genID(val.id, opts);
82
- Object.assign(attribs, {
83
- id,
84
- name: val.name || val.id,
85
- list: val.list ? id + "--list" : undefined,
86
- required: val.required,
87
- readonly: val.readonly,
88
- }, val.attribs);
89
- if (__useValues(opts)) {
90
- if (!val.readonly) {
91
- Object.assign(attribs, events);
92
- }
93
- if (value !== false) {
94
- attribs[value] = val.value;
95
- }
106
+ const id = __genID(val.id, opts);
107
+ Object.assign(
108
+ attribs,
109
+ {
110
+ id,
111
+ name: val.name || val.id,
112
+ list: val.list ? id + "--list" : void 0,
113
+ required: val.required,
114
+ readonly: val.readonly
115
+ },
116
+ val.attribs
117
+ );
118
+ if (__useValues(opts)) {
119
+ if (!val.readonly) {
120
+ Object.assign(attribs, events);
121
+ }
122
+ if (value !== false) {
123
+ attribs[value] = val.value;
96
124
  }
97
- return attribs;
125
+ }
126
+ return attribs;
98
127
  };
99
- /** @internal */
100
- const __component = (val, opts, el, attribs, events, value = "value", ...body) => div({ ...opts.wrapperAttribs, ...val.wrapperAttribs }, ...__genCommon(val, opts),
101
- // @ts-ignore extra args
102
- el(__attribs(attribs, events, val, opts, value), ...body));
103
- /** @internal */
128
+ const __component = (val, opts, el, attribs, events, value = "value", ...body) => div(
129
+ { ...opts.wrapperAttribs, ...val.wrapperAttribs },
130
+ ...__genCommon(val, opts),
131
+ // @ts-ignore extra args
132
+ el(__attribs(attribs, events, val, opts, value), ...body)
133
+ );
104
134
  const __edit = (val) => {
105
- if (val.pattern) {
106
- let match;
107
- if (isFunction(val.pattern)) {
108
- match = val.pattern;
109
- }
110
- else {
111
- const re = isString(val.pattern)
112
- ? new RegExp(val.pattern)
113
- : val.pattern;
114
- match = (x) => re.test(x);
115
- }
116
- return (e) => {
117
- const target = e.target;
118
- const body = target.value;
119
- const ok = match(body);
120
- if (ok)
121
- val.value.next(body);
122
- $attribs(target, { invalid: !ok });
123
- };
135
+ if (val.pattern) {
136
+ let match;
137
+ if (isFunction(val.pattern)) {
138
+ match = val.pattern;
139
+ } else {
140
+ const re = isString(val.pattern) ? new RegExp(val.pattern) : val.pattern;
141
+ match = (x) => re.test(x);
124
142
  }
125
- return $input(val.value);
143
+ return (e) => {
144
+ const target = e.target;
145
+ const body = target.value;
146
+ const ok = match(body);
147
+ if (ok)
148
+ val.value.next(body);
149
+ $attribs(target, { invalid: !ok });
150
+ };
151
+ }
152
+ return $input(val.value);
126
153
  };
127
154
  const __useValues = (opts) => opts.behaviors?.values !== false;
128
- /**
129
- * Compiles given {@link FormItem} spec into a hiccup/rdom component, using
130
- * provided options to customize attributes and behaviors.
131
- *
132
- * @remarks
133
- * This function is polymorphic and dynamically extensible for new/custom form
134
- * element types. See thi.ng/defmulti readme for instructions.
135
- */
136
- export const compileForm = defmulti((x) => x.type, {
155
+ const compileForm = defmulti(
156
+ (x) => x.type,
157
+ {
137
158
  multiFile: "file",
138
159
  dateTime: "date",
139
160
  time: "date",
@@ -149,205 +170,353 @@ export const compileForm = defmulti((x) => x.type, {
149
170
  selectNum: "select",
150
171
  selectStr: "select",
151
172
  multiSelectNum: "multiSelect",
152
- multiSelectStr: "multiSelect",
153
- }, {
173
+ multiSelectStr: "multiSelect"
174
+ },
175
+ {
154
176
  form: ($val, opts) => {
155
- const val = $val;
156
- return $form({ ...opts.typeAttribs?.form, ...val.attribs }, ...val.items.map((x) => compileForm(x, opts)));
177
+ const val = $val;
178
+ return $form(
179
+ { ...opts.typeAttribs?.form, ...val.attribs },
180
+ ...val.items.map((x) => compileForm(x, opts))
181
+ );
157
182
  },
158
183
  container: ($val, opts) => {
159
- const val = $val;
160
- return div({ ...opts.typeAttribs?.container, ...val.attribs }, ...val.items.map((x) => compileForm(x, opts)));
184
+ const val = $val;
185
+ return div(
186
+ { ...opts.typeAttribs?.container, ...val.attribs },
187
+ ...val.items.map((x) => compileForm(x, opts))
188
+ );
161
189
  },
162
190
  group: ($val, opts) => {
163
- const val = $val;
164
- const children = [];
165
- if (val.label) {
166
- children.push(legend({ ...opts.typeAttribs?.groupLabel }, val.label));
167
- }
168
- return fieldset({ ...opts.typeAttribs?.group, ...val.attribs }, ...children, ...val.items.map((x) => compileForm(x, opts)));
191
+ const val = $val;
192
+ const children = [];
193
+ if (val.label) {
194
+ children.push(
195
+ legend({ ...opts.typeAttribs?.groupLabel }, val.label)
196
+ );
197
+ }
198
+ return fieldset(
199
+ { ...opts.typeAttribs?.group, ...val.attribs },
200
+ ...children,
201
+ ...val.items.map((x) => compileForm(x, opts))
202
+ );
169
203
  },
170
204
  custom: (val) => val.body,
171
205
  toggle: ($val, opts) => {
172
- const val = $val;
173
- const label = __genLabel(val, opts);
174
- const ctrl = checkbox(__attribs({ ...opts.typeAttribs?.toggle }, { onchange: $inputCheckbox($val.value) }, val, opts, "checked"));
175
- return div({ ...opts.wrapperAttribs, ...val.wrapperAttribs }, ...(opts.behaviors?.toggleLabelBefore !== false
176
- ? [label, ctrl]
177
- : [ctrl, label]));
206
+ const val = $val;
207
+ const label2 = __genLabel(val, opts);
208
+ const ctrl = checkbox(
209
+ __attribs(
210
+ { ...opts.typeAttribs?.toggle },
211
+ { onchange: $inputCheckbox($val.value) },
212
+ val,
213
+ opts,
214
+ "checked"
215
+ )
216
+ );
217
+ return div(
218
+ { ...opts.wrapperAttribs, ...val.wrapperAttribs },
219
+ ...opts.behaviors?.toggleLabelBefore !== false ? [label2, ctrl] : [ctrl, label2]
220
+ );
178
221
  },
179
- trigger: ($val, opts) => __component($val, opts, button, { ...opts.typeAttribs?.trigger }, { onchange: $inputTrigger($val.value) }, false, $val.title),
222
+ trigger: ($val, opts) => __component(
223
+ $val,
224
+ opts,
225
+ button,
226
+ { ...opts.typeAttribs?.trigger },
227
+ { onclick: $inputTrigger($val.value) },
228
+ false,
229
+ $val.title
230
+ ),
180
231
  radio: ($val, opts) => {
181
- const val = $val;
182
- const labelAttribs = {
183
- ...opts.typeAttribs?.radioItemLabel,
184
- ...val.labelAttribs,
185
- };
186
- const $option = ($item) => {
187
- const item = isPlainObject($item) ? $item : { value: $item };
188
- const id = val.id + "-" + item.value;
189
- const label = __genLabel({
190
- id,
191
- label: item.label || item.value,
192
- desc: item.desc,
193
- labelAttribs,
194
- descAttribs: val.descAttribs,
195
- }, opts);
196
- const ctrl = radio({
197
- ...opts.typeAttribs?.radio,
198
- ...val.attribs,
199
- onchange: val.value && __useValues(opts)
200
- ? () => val.value.next(item.value)
201
- : undefined,
202
- id: __genID(id, opts),
203
- name: val.name || val.id,
204
- checked: val.value && __useValues(opts)
205
- ? val.value.map((x) => x === item.value)
206
- : undefined,
207
- value: item.value,
208
- });
209
- return div({ ...opts.typeAttribs?.radioItem }, ...(opts.behaviors?.radioLabelBefore
210
- ? [label, ctrl]
211
- : [ctrl, label]));
212
- };
213
- return div({
214
- ...opts.wrapperAttribs,
215
- ...opts.typeAttribs?.radioWrapper,
216
- ...val.wrapperAttribs,
217
- }, ...__genCommon(val, opts), div({ ...opts.typeAttribs?.radioItems }, ...val.items.map($option)));
232
+ const val = $val;
233
+ const labelAttribs = {
234
+ ...opts.typeAttribs?.radioItemLabel,
235
+ ...val.labelAttribs
236
+ };
237
+ const $option = ($item) => {
238
+ const item = isPlainObject($item) ? $item : { value: $item };
239
+ const id = val.id + "-" + item.value;
240
+ const label2 = __genLabel(
241
+ {
242
+ id,
243
+ label: item.label || item.value,
244
+ desc: item.desc,
245
+ labelAttribs,
246
+ descAttribs: val.descAttribs
247
+ },
248
+ opts
249
+ );
250
+ const ctrl = radio({
251
+ ...opts.typeAttribs?.radio,
252
+ ...val.attribs,
253
+ onchange: val.value && __useValues(opts) ? () => val.value.next(item.value) : void 0,
254
+ id: __genID(id, opts),
255
+ name: val.name || val.id,
256
+ checked: val.value && __useValues(opts) ? val.value.map((x) => x === item.value) : void 0,
257
+ value: item.value
258
+ });
259
+ return div(
260
+ { ...opts.typeAttribs?.radioItem },
261
+ ...opts.behaviors?.radioLabelBefore ? [label2, ctrl] : [ctrl, label2]
262
+ );
263
+ };
264
+ return div(
265
+ {
266
+ ...opts.wrapperAttribs,
267
+ ...opts.typeAttribs?.radioWrapper,
268
+ ...val.wrapperAttribs
269
+ },
270
+ ...__genCommon(val, opts),
271
+ div(
272
+ { ...opts.typeAttribs?.radioItems },
273
+ ...val.items.map($option)
274
+ )
275
+ );
218
276
  },
219
- color: ($val, opts) => __component($val, opts, inputColor, { ...opts.typeAttribs?.color }, { onchange: $input($val.value) }),
277
+ color: ($val, opts) => __component(
278
+ $val,
279
+ opts,
280
+ inputColor,
281
+ { ...opts.typeAttribs?.color },
282
+ { onchange: $input($val.value) }
283
+ ),
220
284
  file: ($val, opts) => {
221
- const val = $val;
222
- const isMulti = val.id.startsWith("multi");
223
- return __component(val, opts, inputFile, {
224
- ...opts.typeAttribs?.num,
225
- accept: val.accept,
226
- capture: val.capture,
227
- multiple: isMulti,
228
- }, {
229
- onchange: isMulti
230
- ? $inputFiles($val.value)
231
- : $inputFile(val.value),
232
- }, false);
285
+ const val = $val;
286
+ const isMulti = val.id.startsWith("multi");
287
+ return __component(
288
+ val,
289
+ opts,
290
+ inputFile,
291
+ {
292
+ ...opts.typeAttribs?.num,
293
+ accept: val.accept,
294
+ capture: val.capture,
295
+ multiple: isMulti
296
+ },
297
+ {
298
+ onchange: isMulti ? $inputFiles($val.value) : $inputFile(val.value)
299
+ },
300
+ false
301
+ );
233
302
  },
234
303
  num: ($val, opts) => {
235
- const val = $val;
236
- return __component(val, opts, inputNumber, {
237
- ...opts.typeAttribs?.num,
238
- min: val.min,
239
- max: val.max,
240
- step: val.step,
241
- placeholder: val.placeholder,
242
- size: val.size,
243
- }, { onchange: $inputNum(val.value) });
304
+ const val = $val;
305
+ return __component(
306
+ val,
307
+ opts,
308
+ inputNumber,
309
+ {
310
+ ...opts.typeAttribs?.num,
311
+ min: val.min,
312
+ max: val.max,
313
+ step: val.step,
314
+ placeholder: val.placeholder,
315
+ size: val.size
316
+ },
317
+ { onchange: $inputNum(val.value) }
318
+ );
244
319
  },
245
320
  range: ($val, opts) => {
246
- const val = $val;
247
- const edit = opts.behaviors?.rangeOnInput === false ? "onchange" : "oninput";
248
- return div({ ...opts.wrapperAttribs, ...val.wrapperAttribs }, ...__genCommon(val, opts), div({}, inputRange(__attribs({
249
- ...opts.typeAttribs?.range,
250
- min: val.min,
251
- max: val.max,
252
- step: val.step,
253
- }, { [edit]: $inputNum(val.value) }, val, opts)), val.value && val.vlabel !== false && __useValues(opts)
254
- ? span({ ...opts.typeAttribs?.rangeLabel }, val.value.map((x) => x.toFixed(val.vlabelPrec ?? 3)))
255
- : undefined));
321
+ const val = $val;
322
+ const edit = opts.behaviors?.rangeOnInput === false ? "onchange" : "oninput";
323
+ const children = [
324
+ inputRange(
325
+ __attribs(
326
+ {
327
+ ...opts.typeAttribs?.range,
328
+ min: val.min,
329
+ max: val.max,
330
+ step: val.step
331
+ },
332
+ { [edit]: $inputNum(val.value) },
333
+ val,
334
+ opts
335
+ )
336
+ )
337
+ ];
338
+ if (val.value && val.vlabel !== false && __useValues(opts)) {
339
+ const fmt = val.vlabel === true || val.vlabel === void 0 ? opts.behaviors?.rangeLabelFmt ?? 2 : val.vlabel;
340
+ children.push(
341
+ span(
342
+ { ...opts.typeAttribs?.rangeLabel },
343
+ val.value.map(
344
+ isFunction(fmt) ? fmt : (x) => x.toFixed(fmt)
345
+ )
346
+ )
347
+ );
348
+ }
349
+ return div(
350
+ { ...opts.wrapperAttribs, ...val.wrapperAttribs },
351
+ ...__genCommon(val, opts),
352
+ div({ ...opts.typeAttribs?.rangeWrapper }, ...children)
353
+ );
256
354
  },
257
355
  str: ($val, opts) => {
258
- const val = $val;
259
- const type = { dateTime: "datetime-local" }[$val.type] ||
260
- ($val.type !== "str" ? $val.type : "text");
261
- const edit = opts.behaviors?.strOnInput === false ? "onchange" : "oninput";
262
- return __component(val, opts, inputText, {
263
- ...(opts.typeAttribs?.[val.type] || opts.typeAttribs?.str),
264
- type,
265
- autocomplete: val.autocomplete,
266
- minlength: val.min,
267
- maxlength: val.max,
268
- placeholder: val.placeholder,
269
- pattern: isString(val.pattern) ? val.pattern : undefined,
270
- size: val.size,
271
- }, { [edit]: __edit(val) });
356
+ const val = $val;
357
+ const type = { dateTime: "datetime-local" }[$val.type] || ($val.type !== "str" ? $val.type : "text");
358
+ const edit = opts.behaviors?.strOnInput === false ? "onchange" : "oninput";
359
+ return __component(
360
+ val,
361
+ opts,
362
+ inputText,
363
+ {
364
+ ...opts.typeAttribs?.[val.type] || opts.typeAttribs?.str,
365
+ type,
366
+ autocomplete: val.autocomplete,
367
+ minlength: val.min,
368
+ maxlength: val.max,
369
+ placeholder: val.placeholder,
370
+ pattern: isString(val.pattern) ? val.pattern : void 0,
371
+ size: val.size
372
+ },
373
+ { [edit]: __edit(val) }
374
+ );
272
375
  },
273
376
  text: ($val, opts) => {
274
- const val = $val;
275
- const edit = opts.behaviors?.textOnInput === false ? "onchange" : "oninput";
276
- return __component(val, opts, textArea, {
277
- ...opts.typeAttribs?.text,
278
- cols: val.cols,
279
- rows: val.rows,
280
- placeholder: val.placeholder,
281
- }, { [edit]: $input(val.value) });
377
+ const val = $val;
378
+ const edit = opts.behaviors?.textOnInput === false ? "onchange" : "oninput";
379
+ return __component(
380
+ val,
381
+ opts,
382
+ textArea,
383
+ {
384
+ ...opts.typeAttribs?.text,
385
+ cols: val.cols,
386
+ rows: val.rows,
387
+ placeholder: val.placeholder
388
+ },
389
+ { [edit]: $input(val.value) }
390
+ );
282
391
  },
283
392
  date: ($val, opts) => {
284
- const val = $val;
285
- const type = { dateTime: "datetime-local" }[$val.type] || $val.type;
286
- return __component(val, opts, inputText, {
287
- ...(opts.typeAttribs?.[$val.type] ||
288
- opts.typeAttribs?.date),
289
- type,
290
- min: val.min,
291
- max: val.max,
292
- step: val.step,
293
- }, { onchange: $input(val.value) });
393
+ const val = $val;
394
+ const type = { dateTime: "datetime-local" }[$val.type] || $val.type;
395
+ return __component(
396
+ val,
397
+ opts,
398
+ inputText,
399
+ {
400
+ ...opts.typeAttribs?.[$val.type] || opts.typeAttribs?.date,
401
+ type,
402
+ min: val.min,
403
+ max: val.max,
404
+ step: val.step
405
+ },
406
+ { onchange: $input(val.value) }
407
+ );
294
408
  },
295
409
  select: ($val, opts) => {
296
- const val = $val;
297
- const isNumeric = val.type.endsWith("Num");
298
- const $option = ($item, sel) => {
299
- const item = isPlainObject($item) ? $item : { value: $item };
300
- return option({
301
- value: item.value,
302
- selected: sel === item.value,
303
- }, item.label || item.value);
304
- };
305
- const $select = (sel) => select(__attribs({
306
- ...(opts.typeAttribs?.[val.type] ||
307
- opts.typeAttribs?.select),
308
- }, {
309
- onchange: isNumeric
310
- ? $inputNum(val.value)
311
- : $input(val.value),
312
- }, val, opts, false), ...val.items.map((item) => isPlainObject(item) && "items" in item
313
- ? optGroup({ label: item.name }, ...item.items.map((i) => $option(i, sel)))
314
- : $option(item, sel)));
315
- return div({ ...opts.wrapperAttribs, ...val.wrapperAttribs }, ...__genCommon(val, opts), val.value && __useValues(opts)
316
- ? $replace(val.value.map($select))
317
- : $select());
410
+ const val = $val;
411
+ const isNumeric = val.type.endsWith("Num");
412
+ const $option = ($item, sel) => {
413
+ const item = isPlainObject($item) ? $item : { value: $item };
414
+ return option(
415
+ {
416
+ value: item.value,
417
+ selected: sel === item.value
418
+ },
419
+ item.label || item.value
420
+ );
421
+ };
422
+ const $select = (sel) => select(
423
+ __attribs(
424
+ {
425
+ ...opts.typeAttribs?.[val.type] || opts.typeAttribs?.select
426
+ },
427
+ {
428
+ onchange: isNumeric ? $inputNum(val.value) : $input(val.value)
429
+ },
430
+ val,
431
+ opts,
432
+ false
433
+ ),
434
+ ...val.items.map(
435
+ (item) => isPlainObject(item) && "items" in item ? optGroup(
436
+ { label: item.name },
437
+ ...item.items.map((i) => $option(i, sel))
438
+ ) : $option(item, sel)
439
+ )
440
+ );
441
+ return div(
442
+ { ...opts.wrapperAttribs, ...val.wrapperAttribs },
443
+ ...__genCommon(val, opts),
444
+ val.value && __useValues(opts) ? $replace(val.value.map($select)) : $select()
445
+ );
318
446
  },
319
447
  multiSelect: ($val, opts) => {
320
- const val = $val;
321
- const isNumeric = val.type.endsWith("Num");
322
- const coerce = isNumeric
323
- ? (x) => parseFloat(x.value)
324
- : (x) => x.value;
325
- const sel = val.value && __useValues(opts)
326
- ? val.value.map((x) => (isArray(x) ? x : [x]))
327
- : null;
328
- const $option = ($item) => {
329
- const item = isPlainObject($item) ? $item : { value: $item };
330
- return option({
331
- value: item.value,
332
- selected: sel
333
- ? sel.map(($sel) => $sel.includes(item.value))
334
- : false,
335
- }, item.label || item.value);
336
- };
337
- return __component(val, opts, select, {
338
- ...(opts.typeAttribs?.[val.type] ||
339
- opts.typeAttribs?.multiSelect),
340
- multiple: true,
341
- size: val.size,
342
- }, {
343
- onchange: (e) => {
344
- val.value.next([
345
- ...e.target
346
- .selectedOptions,
347
- ].map(coerce));
348
- },
349
- }, false, ...val.items.map((item) => isPlainObject(item) && "items" in item
350
- ? optGroup({ label: item.name }, ...item.items.map($option))
351
- : $option(item)));
352
- },
353
- });
448
+ const val = $val;
449
+ const isNumeric = val.type.endsWith("Num");
450
+ const coerce = isNumeric ? (x) => parseFloat(x.value) : (x) => x.value;
451
+ const sel = val.value && __useValues(opts) ? val.value.map((x) => isArray(x) ? x : [x]) : null;
452
+ const $option = ($item) => {
453
+ const item = isPlainObject($item) ? $item : { value: $item };
454
+ return option(
455
+ {
456
+ value: item.value,
457
+ selected: sel ? sel.map(($sel) => $sel.includes(item.value)) : false
458
+ },
459
+ item.label || item.value
460
+ );
461
+ };
462
+ return __component(
463
+ val,
464
+ opts,
465
+ select,
466
+ {
467
+ ...opts.typeAttribs?.[val.type] || opts.typeAttribs?.multiSelect,
468
+ multiple: true,
469
+ size: val.size
470
+ },
471
+ {
472
+ onchange: (e) => {
473
+ val.value.next(
474
+ [
475
+ ...e.target.selectedOptions
476
+ ].map(coerce)
477
+ );
478
+ }
479
+ },
480
+ false,
481
+ ...val.items.map(
482
+ (item) => isPlainObject(item) && "items" in item ? optGroup(
483
+ { label: item.name },
484
+ ...item.items.map($option)
485
+ ) : $option(item)
486
+ )
487
+ );
488
+ }
489
+ }
490
+ );
491
+ export {
492
+ color,
493
+ compileForm,
494
+ container,
495
+ custom,
496
+ date,
497
+ dateTime,
498
+ email,
499
+ file,
500
+ form,
501
+ group,
502
+ month,
503
+ multiFile,
504
+ multiSelectNum,
505
+ multiSelectStr,
506
+ num,
507
+ password,
508
+ phone,
509
+ radioNum,
510
+ radioStr,
511
+ range,
512
+ search,
513
+ selectNum,
514
+ selectStr,
515
+ str,
516
+ text,
517
+ time,
518
+ toggle,
519
+ trigger,
520
+ url,
521
+ week
522
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/rdom-forms",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Data-driven declarative & extensible HTML form generation",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,22 +35,34 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.11",
37
- "@thi.ng/checks": "^3.4.11",
38
- "@thi.ng/defmulti": "^3.0.9",
39
- "@thi.ng/hiccup-html": "^2.3.0",
40
- "@thi.ng/rdom": "^0.13.3",
41
- "@thi.ng/rstream": "^8.2.13"
38
+ "@thi.ng/api": "^8.9.12",
39
+ "@thi.ng/checks": "^3.4.12",
40
+ "@thi.ng/defmulti": "^3.0.10",
41
+ "@thi.ng/hiccup-html": "^2.3.1",
42
+ "@thi.ng/rdom": "^0.13.4",
43
+ "@thi.ng/rstream": "^8.2.14"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@microsoft/api-extractor": "^7.38.3",
47
+ "esbuild": "^0.19.8",
45
48
  "rimraf": "^5.0.5",
46
49
  "tools": "^0.0.1",
47
50
  "typedoc": "^0.25.4",
48
51
  "typescript": "^5.3.2"
49
52
  },
50
53
  "keywords": [
51
- "typescript"
54
+ "browser",
55
+ "component",
56
+ "declarative",
57
+ "dom",
58
+ "form",
59
+ "hiccup",
60
+ "html",
61
+ "rdom",
62
+ "rstream",
63
+ "reactive",
64
+ "typescript",
65
+ "ui"
52
66
  ],
53
67
  "publishConfig": {
54
68
  "access": "public"
@@ -83,5 +97,5 @@
83
97
  "status": "alpha",
84
98
  "year": 2023
85
99
  },
86
- "gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
100
+ "gitHead": "22e36fa838e5431d40165384918b395603bbd92f\n"
87
101
  }