@torpor/ui 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/dist/Form/CheckBox.torp +91 -0
- package/dist/Form/File.torp +106 -0
- package/dist/Form/Form.torp +16 -3
- package/dist/Form/FormTypes.ts +5 -0
- package/dist/Form/Hidden.torp +25 -0
- package/dist/Form/Input.torp +12 -1
- package/dist/Form/Option.torp +24 -0
- package/dist/Form/Select.torp +97 -0
- package/dist/Form/TextArea.torp +101 -0
- package/dist/Form/index.d.ts +199 -2
- package/dist/Form/index.js +25 -1
- package/dist/metafile-esm.json +1 -1
- package/package.json +8 -8
- package/src/Form/CheckBox.torp +91 -0
- package/src/Form/File.torp +106 -0
- package/src/Form/Form.torp +16 -3
- package/src/Form/FormTypes.ts +5 -0
- package/src/Form/Hidden.torp +25 -0
- package/src/Form/Input.torp +12 -1
- package/src/Form/Option.torp +24 -0
- package/src/Form/Select.torp +97 -0
- package/src/Form/TextArea.torp +101 -0
- package/src/Form/index.ts +7 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @torpor/ui
|
|
2
2
|
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3f4eef3: Fix: File component name
|
|
8
|
+
- dddec65: Feat: Hidden UI form component
|
|
9
|
+
- fae47e6: Fix: TextArea should be a textarea
|
|
10
|
+
- e9fe640: Fix: get checkbox checked from state value
|
|
11
|
+
- 00c4d30: Feat: allow setting input required
|
|
12
|
+
- e1a3f46: Fix: string unions don't work in $props
|
|
13
|
+
- 14cd367: Fix: valibot uses a different path format
|
|
14
|
+
|
|
15
|
+
## 0.3.1
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- b670819: Feat: more UI form components
|
|
20
|
+
- Updated dependencies [3cc2168]
|
|
21
|
+
- Updated dependencies [13f2afb]
|
|
22
|
+
- @torpor/view@0.4.7
|
|
23
|
+
|
|
3
24
|
## 0.3.0
|
|
4
25
|
|
|
5
26
|
### Minor Changes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
|
|
3
|
+
|
|
4
|
+
interface CheckBoxProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Class name(s) for the root element */
|
|
8
|
+
class?: string,
|
|
9
|
+
/** The name of the checkbox's form data field */
|
|
10
|
+
name?: string,
|
|
11
|
+
/** Whether the checkbox is checked */
|
|
12
|
+
checked?: boolean,
|
|
13
|
+
/** An ARIA label for describing the checkbox to screen readers when there is no associated label element */
|
|
14
|
+
ariaLabel?: string,
|
|
15
|
+
/** Raised on change */
|
|
16
|
+
onchange?: (e: Event) => void,
|
|
17
|
+
/** Raised on input */
|
|
18
|
+
oninput?: (e: Event) => void,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A checkbox input in a form.
|
|
23
|
+
*/
|
|
24
|
+
export default function CheckBox($props: CheckBoxProps | undefined) {
|
|
25
|
+
let defaultId = getId();
|
|
26
|
+
|
|
27
|
+
let name = $props?.name
|
|
28
|
+
|
|
29
|
+
let blurred = false;
|
|
30
|
+
|
|
31
|
+
// Get stuff out of the FormContext and FieldContext
|
|
32
|
+
const formContext = $context[FormContextName] as FormContext;
|
|
33
|
+
const fieldContext = $context[FieldContextName] as FieldContext;
|
|
34
|
+
if (fieldContext) {
|
|
35
|
+
name ??= fieldContext.name;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Use the FieldContext's state, or create a new one
|
|
39
|
+
let $state = fieldContext?.state ?? $watch({
|
|
40
|
+
inputId: $props?.id ?? defaultId,
|
|
41
|
+
value: $props?.checked,
|
|
42
|
+
errors: {} as Record<string, string>,
|
|
43
|
+
valid: false,
|
|
44
|
+
message: "",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Binding
|
|
48
|
+
$run(() => {
|
|
49
|
+
if ($props !== undefined) {
|
|
50
|
+
$props.checked = $state.value === true;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Set the input ID now that we know it and it will get linked with the associated label
|
|
55
|
+
$state.inputId = $props?.id ?? defaultId;
|
|
56
|
+
|
|
57
|
+
function handleInput(e: Event) {
|
|
58
|
+
if (formContext) {
|
|
59
|
+
formContext.validate();
|
|
60
|
+
}
|
|
61
|
+
if (fieldContext && blurred) {
|
|
62
|
+
fieldContext.validate();
|
|
63
|
+
}
|
|
64
|
+
if ($props?.oninput) {
|
|
65
|
+
$props.oninput(e);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleBlur() {
|
|
70
|
+
if (fieldContext) {
|
|
71
|
+
fieldContext.validate();
|
|
72
|
+
}
|
|
73
|
+
blurred = true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@render {
|
|
77
|
+
<input
|
|
78
|
+
id={$props?.id ?? defaultId}
|
|
79
|
+
class={`torp-check-box
|
|
80
|
+
${$props?.class ?? ""}`.trim()}
|
|
81
|
+
type="checkbox"
|
|
82
|
+
{name}
|
|
83
|
+
&checked={$state.value}
|
|
84
|
+
aria-label={$props?.ariaLabel}
|
|
85
|
+
data-valid={$state.valid ? "valid" : "invalid"}
|
|
86
|
+
onchange={$props?.onchange}
|
|
87
|
+
oninput={handleInput}
|
|
88
|
+
onblur={handleBlur}
|
|
89
|
+
/>
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
|
|
3
|
+
|
|
4
|
+
interface FileProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Class name(s) for the root element */
|
|
8
|
+
class?: string,
|
|
9
|
+
/** The name of the input's form data field */
|
|
10
|
+
name?: string,
|
|
11
|
+
/** The type of files that this input accepts e.g. `image/*` */
|
|
12
|
+
accepts?: string,
|
|
13
|
+
/** The data value that this field is bound to */
|
|
14
|
+
value?: any,
|
|
15
|
+
/** Text to display when the input value is empty */
|
|
16
|
+
placeholder?: string,
|
|
17
|
+
/** Whether the input should be readonly */
|
|
18
|
+
readonly?: boolean,
|
|
19
|
+
/** The maximum number of characters allowed for the input value */
|
|
20
|
+
maxlength?: number,
|
|
21
|
+
/** An ARIA label for describing the input to screen readers when there is no associated label element */
|
|
22
|
+
ariaLabel?: string,
|
|
23
|
+
/** Raised on change */
|
|
24
|
+
onchange?: (e: Event) => void,
|
|
25
|
+
/** Raised on input */
|
|
26
|
+
oninput?: (e: Event) => void,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A file input in a form.
|
|
31
|
+
*/
|
|
32
|
+
export default function File($props: FileProps | undefined) {
|
|
33
|
+
let defaultId = getId();
|
|
34
|
+
|
|
35
|
+
let name = $props?.name
|
|
36
|
+
|
|
37
|
+
let blurred = false;
|
|
38
|
+
|
|
39
|
+
// Get stuff out of the FormContext and FieldContext
|
|
40
|
+
const formContext = $context[FormContextName] as FormContext;
|
|
41
|
+
const fieldContext = $context[FieldContextName] as FieldContext;
|
|
42
|
+
if (fieldContext) {
|
|
43
|
+
name ??= fieldContext.name;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// This will change the form's enctype to `multipart/form-data`
|
|
47
|
+
if (formContext) {
|
|
48
|
+
formContext.state.hasFile = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Use the FieldContext's state, or create a new one
|
|
52
|
+
let $state = fieldContext?.state ?? $watch({
|
|
53
|
+
inputId: $props?.id ?? defaultId,
|
|
54
|
+
value: $props?.value,
|
|
55
|
+
errors: {} as Record<string, string>,
|
|
56
|
+
valid: false,
|
|
57
|
+
message: "",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Binding
|
|
61
|
+
$run(() => {
|
|
62
|
+
if ($props !== undefined) {
|
|
63
|
+
$props.value = $state.value;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Set the input ID now that we know it and it will get linked with the associated label
|
|
68
|
+
$state.inputId = $props?.id ?? defaultId;
|
|
69
|
+
|
|
70
|
+
function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
71
|
+
if (formContext) {
|
|
72
|
+
formContext.validate();
|
|
73
|
+
}
|
|
74
|
+
if (fieldContext && blurred) {
|
|
75
|
+
fieldContext.validate();
|
|
76
|
+
}
|
|
77
|
+
if ($props?.oninput) {
|
|
78
|
+
$props.oninput(e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function handleBlur() {
|
|
83
|
+
if (fieldContext) {
|
|
84
|
+
fieldContext.validate();
|
|
85
|
+
}
|
|
86
|
+
blurred = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@render {
|
|
90
|
+
<input
|
|
91
|
+
id={$props?.id ?? defaultId}
|
|
92
|
+
class={`torp-file ${$props?.class ?? ""}`.trim()}
|
|
93
|
+
type="file"
|
|
94
|
+
{name}
|
|
95
|
+
&value={$state.value}
|
|
96
|
+
{$props?.placeholder}
|
|
97
|
+
{$props?.readonly}
|
|
98
|
+
{$props?.maxlength}
|
|
99
|
+
aria-label={$props?.ariaLabel}
|
|
100
|
+
data-valid={$state.valid ? "valid" : "invalid"}
|
|
101
|
+
onchange={$props?.onchange}
|
|
102
|
+
oninput={handleInput}
|
|
103
|
+
onblur={handleBlur}
|
|
104
|
+
/>
|
|
105
|
+
}
|
|
106
|
+
}
|
package/dist/Form/Form.torp
CHANGED
|
@@ -10,6 +10,13 @@ interface FormProps {
|
|
|
10
10
|
data?: any,
|
|
11
11
|
/** Where to post the form's data **/
|
|
12
12
|
action?: string,
|
|
13
|
+
/** The action to use when submitting the form, which defaults to POST */
|
|
14
|
+
method?: string,
|
|
15
|
+
/**
|
|
16
|
+
* The form's encoding type e.g. `multipart/form-data` where there are file
|
|
17
|
+
* inputs. This will be automatically set unless overridden
|
|
18
|
+
*/
|
|
19
|
+
enctype?: string,
|
|
13
20
|
/** The schema validator to use for this form */
|
|
14
21
|
schema?: StandardSchemaV1,
|
|
15
22
|
onsubmit?: (e: SubmitEvent) => boolean | void | Promise<boolean | void>,
|
|
@@ -21,6 +28,7 @@ interface FormProps {
|
|
|
21
28
|
export default function Form($props: FormProps | undefined) {
|
|
22
29
|
let $state = $watch({
|
|
23
30
|
data: $props?.data ?? {},
|
|
31
|
+
hasFile: false,
|
|
24
32
|
issues: [] as ValidationIssue[],
|
|
25
33
|
get valid() {
|
|
26
34
|
return this.issues.length === 0;
|
|
@@ -51,8 +59,12 @@ export default function Form($props: FormProps | undefined) {
|
|
|
51
59
|
if (result.issues) {
|
|
52
60
|
for (let issue of result.issues) {
|
|
53
61
|
if (issue.path) {
|
|
54
|
-
// TODO: arrays with `[0]`
|
|
55
|
-
issues.push({
|
|
62
|
+
// TODO: arrays with `[0]` etc
|
|
63
|
+
issues.push({
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
path: issue.path.map((p) => p.key ?? p).join("."),
|
|
66
|
+
message: issue.message
|
|
67
|
+
});
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
70
|
}
|
|
@@ -118,7 +130,8 @@ export default function Form($props: FormProps | undefined) {
|
|
|
118
130
|
<form
|
|
119
131
|
{$props?.id}
|
|
120
132
|
class={`torp-form ${$props?.class ?? ""}`.trim()}
|
|
121
|
-
method="POST"
|
|
133
|
+
method={$props?.method ?? "POST"}
|
|
134
|
+
enctype={$props?.enctype ?? ($state.hasFile ? "multipart/form-data" : undefined)}
|
|
122
135
|
onsubmit={handleSubmit}
|
|
123
136
|
@// Set novalidate only if JS is enabled (to override HTML validation)
|
|
124
137
|
onmount={(e) => e.novalidate = true }
|
package/dist/Form/FormTypes.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface ValidationIssue {
|
|
|
11
11
|
export interface FormContext {
|
|
12
12
|
schema?: StandardSchemaV1;
|
|
13
13
|
state: {
|
|
14
|
+
/**
|
|
15
|
+
* Whether there is a file input in the form. If so, the form's enctype
|
|
16
|
+
* will be set to `multipart/form-data`
|
|
17
|
+
*/
|
|
18
|
+
hasFile: boolean;
|
|
14
19
|
issues: ValidationIssue[];
|
|
15
20
|
};
|
|
16
21
|
validate: () => void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
|
|
3
|
+
|
|
4
|
+
interface HiddenProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** The name of the input's form data field */
|
|
8
|
+
name?: string,
|
|
9
|
+
/** The data value that this field is bound to */
|
|
10
|
+
value?: any,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A standard input in a form.
|
|
15
|
+
*/
|
|
16
|
+
export default function Input($props: HiddenProps | undefined) {
|
|
17
|
+
@render {
|
|
18
|
+
<input
|
|
19
|
+
{$props?.id}
|
|
20
|
+
type="hidden"
|
|
21
|
+
{$props?.name}
|
|
22
|
+
{$props?.value}
|
|
23
|
+
/>
|
|
24
|
+
}
|
|
25
|
+
}
|
package/dist/Form/Input.torp
CHANGED
|
@@ -7,7 +7,7 @@ interface InputProps {
|
|
|
7
7
|
/** Class name(s) for the root element */
|
|
8
8
|
class?: string,
|
|
9
9
|
/** The input type */
|
|
10
|
-
type?: "text" | "number" | "password" | "email" | "url" | "tel",
|
|
10
|
+
type?: string, // TODO: "text" | "number" | "password" | "email" | "url" | "tel",
|
|
11
11
|
/** The name of the input's form data field */
|
|
12
12
|
name?: string,
|
|
13
13
|
/** The data value that this field is bound to */
|
|
@@ -16,10 +16,16 @@ interface InputProps {
|
|
|
16
16
|
placeholder?: string,
|
|
17
17
|
/** Whether the input should be readonly */
|
|
18
18
|
readonly?: boolean,
|
|
19
|
+
/** Whether the input's value is required */
|
|
20
|
+
required?: boolean,
|
|
19
21
|
/** The maximum number of characters allowed for the input value */
|
|
20
22
|
maxlength?: number,
|
|
21
23
|
/** An ARIA label for describing the input to screen readers when there is no associated label element */
|
|
22
24
|
ariaLabel?: string,
|
|
25
|
+
/** Raised on change */
|
|
26
|
+
onchange?: (e: Event) => void,
|
|
27
|
+
/** Raised on input */
|
|
28
|
+
oninput?: (e: Event) => void,
|
|
23
29
|
};
|
|
24
30
|
|
|
25
31
|
/**
|
|
@@ -69,6 +75,9 @@ export default function Input($props: InputProps | undefined) {
|
|
|
69
75
|
if (fieldContext && blurred) {
|
|
70
76
|
fieldContext.validate();
|
|
71
77
|
}
|
|
78
|
+
if ($props?.oninput) {
|
|
79
|
+
$props.oninput(e);
|
|
80
|
+
}
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
function handleBlur() {
|
|
@@ -87,9 +96,11 @@ export default function Input($props: InputProps | undefined) {
|
|
|
87
96
|
&value={$state.value}
|
|
88
97
|
{$props?.placeholder}
|
|
89
98
|
{$props?.readonly}
|
|
99
|
+
{$props?.required}
|
|
90
100
|
{$props?.maxlength}
|
|
91
101
|
aria-label={$props?.ariaLabel}
|
|
92
102
|
data-valid={$state.valid ? "valid" : "invalid"}
|
|
103
|
+
onchange={$props?.onchange}
|
|
93
104
|
oninput={handleInput}
|
|
94
105
|
onblur={handleBlur}
|
|
95
106
|
/>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
|
|
3
|
+
|
|
4
|
+
interface OptionProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Class name(s) for the root element */
|
|
8
|
+
class?: string,
|
|
9
|
+
/** The data value that this option has */
|
|
10
|
+
value?: any,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* An option in a select.
|
|
15
|
+
*/
|
|
16
|
+
export default function Option($props: OptionProps | undefined) {
|
|
17
|
+
@render {
|
|
18
|
+
<option
|
|
19
|
+
{$props?.id}
|
|
20
|
+
class={`torp-option ${$props?.class ?? ""}`.trim()}
|
|
21
|
+
&value={$state.value}
|
|
22
|
+
/>
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
|
|
3
|
+
|
|
4
|
+
interface SelectProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Class name(s) for the root element */
|
|
8
|
+
class?: string,
|
|
9
|
+
/** The name of the input's form data field */
|
|
10
|
+
name?: string,
|
|
11
|
+
/** The data value that this field is bound to */
|
|
12
|
+
value?: any,
|
|
13
|
+
/** Text to display when the input value is empty */
|
|
14
|
+
placeholder?: string,
|
|
15
|
+
/** Whether the input should be readonly */
|
|
16
|
+
readonly?: boolean,
|
|
17
|
+
/** An ARIA label for describing the input to screen readers when there is no associated label element */
|
|
18
|
+
ariaLabel?: string,
|
|
19
|
+
/** Raised on change */
|
|
20
|
+
onchange?: (e: Event) => void,
|
|
21
|
+
/** Raised on input */
|
|
22
|
+
oninput?: (e: Event) => void,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A select in a form.
|
|
27
|
+
*/
|
|
28
|
+
export default function Select($props: SelectProps | undefined) {
|
|
29
|
+
let defaultId = getId();
|
|
30
|
+
|
|
31
|
+
let name = $props?.name
|
|
32
|
+
|
|
33
|
+
let blurred = false;
|
|
34
|
+
|
|
35
|
+
// Get stuff out of the FormContext and FieldContext
|
|
36
|
+
const formContext = $context[FormContextName] as FormContext;
|
|
37
|
+
const fieldContext = $context[FieldContextName] as FieldContext;
|
|
38
|
+
if (fieldContext) {
|
|
39
|
+
name ??= fieldContext.name;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Use the FieldContext's state, or create a new one
|
|
43
|
+
let $state = fieldContext?.state ?? $watch({
|
|
44
|
+
inputId: $props?.id ?? defaultId,
|
|
45
|
+
value: $props?.value,
|
|
46
|
+
errors: {} as Record<string, string>,
|
|
47
|
+
valid: false,
|
|
48
|
+
message: "",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Binding
|
|
52
|
+
$run(() => {
|
|
53
|
+
if ($props !== undefined) {
|
|
54
|
+
$props.value = $state.value;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Set the input ID now that we know it and it will get linked with the associated label
|
|
59
|
+
$state.inputId = $props?.id ?? defaultId;
|
|
60
|
+
|
|
61
|
+
function handleInput(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
|
62
|
+
if (formContext) {
|
|
63
|
+
formContext.validate();
|
|
64
|
+
}
|
|
65
|
+
if (fieldContext && blurred) {
|
|
66
|
+
fieldContext.validate();
|
|
67
|
+
}
|
|
68
|
+
if ($props?.oninput) {
|
|
69
|
+
$props.oninput(e);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleBlur() {
|
|
74
|
+
if (fieldContext) {
|
|
75
|
+
fieldContext.validate();
|
|
76
|
+
}
|
|
77
|
+
blurred = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@render {
|
|
81
|
+
<select
|
|
82
|
+
id={$props?.id ?? defaultId}
|
|
83
|
+
class={`torp-select ${$props?.class ?? ""}`.trim()}
|
|
84
|
+
{name}
|
|
85
|
+
&value={$state.value}
|
|
86
|
+
{$props?.placeholder}
|
|
87
|
+
{$props?.readonly}
|
|
88
|
+
aria-label={$props?.ariaLabel}
|
|
89
|
+
data-valid={$state.valid ? "valid" : "invalid"}
|
|
90
|
+
onchange={$props?.onchange}
|
|
91
|
+
oninput={handleInput}
|
|
92
|
+
onblur={handleBlur}
|
|
93
|
+
>
|
|
94
|
+
<slot />
|
|
95
|
+
</select>
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import getId from "../utils/getId";
|
|
2
|
+
import { FormContextName, FieldContextName, type FormContext, type FieldContext } from "./FormTypes";
|
|
3
|
+
|
|
4
|
+
interface TextAreaProps {
|
|
5
|
+
/** An ID for the root element */
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Class name(s) for the root element */
|
|
8
|
+
class?: string,
|
|
9
|
+
/** The name of the input's form data field */
|
|
10
|
+
name?: string,
|
|
11
|
+
/** The data value that this field is bound to */
|
|
12
|
+
value?: any,
|
|
13
|
+
/** The number of rows to show in the input */
|
|
14
|
+
rows?: number,
|
|
15
|
+
/** Text to display when the input's value is empty */
|
|
16
|
+
placeholder?: string,
|
|
17
|
+
/** Whether the input should be readonly */
|
|
18
|
+
readonly?: boolean,
|
|
19
|
+
/** The maximum number of characters allowed for the input value */
|
|
20
|
+
maxlength?: number,
|
|
21
|
+
/** An ARIA label for describing the input to screen readers when there is no associated label element */
|
|
22
|
+
ariaLabel?: string,
|
|
23
|
+
/** Raised on change */
|
|
24
|
+
onchange?: (e: Event) => void,
|
|
25
|
+
/** Raised on input */
|
|
26
|
+
oninput?: (e: Event) => void,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A standard input in a form.
|
|
31
|
+
*/
|
|
32
|
+
export default function TextArea($props: TextAreaProps | undefined) {
|
|
33
|
+
let defaultId = getId();
|
|
34
|
+
|
|
35
|
+
let name = $props?.name
|
|
36
|
+
|
|
37
|
+
let blurred = false;
|
|
38
|
+
|
|
39
|
+
// Get stuff out of the FormContext and FieldContext
|
|
40
|
+
const formContext = $context[FormContextName] as FormContext;
|
|
41
|
+
const fieldContext = $context[FieldContextName] as FieldContext;
|
|
42
|
+
if (fieldContext) {
|
|
43
|
+
name ??= fieldContext.name;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Use the FieldContext's state, or create a new one
|
|
47
|
+
let $state = fieldContext?.state ?? $watch({
|
|
48
|
+
inputId: $props?.id ?? defaultId,
|
|
49
|
+
value: $props?.value,
|
|
50
|
+
errors: {} as Record<string, string>,
|
|
51
|
+
valid: false,
|
|
52
|
+
message: "",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Binding
|
|
56
|
+
$run(() => {
|
|
57
|
+
if ($props !== undefined) {
|
|
58
|
+
$props.value = $state.value;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Set the input ID now that we know it and it will get linked with the associated label
|
|
63
|
+
$state.inputId = $props?.id ?? defaultId;
|
|
64
|
+
|
|
65
|
+
function handleInput(e: Event) {
|
|
66
|
+
if (formContext) {
|
|
67
|
+
formContext.validate();
|
|
68
|
+
}
|
|
69
|
+
if (fieldContext && blurred) {
|
|
70
|
+
fieldContext.validate();
|
|
71
|
+
}
|
|
72
|
+
if ($props?.oninput) {
|
|
73
|
+
$props.oninput(e);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function handleBlur() {
|
|
78
|
+
if (fieldContext) {
|
|
79
|
+
fieldContext.validate();
|
|
80
|
+
}
|
|
81
|
+
blurred = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@render {
|
|
85
|
+
<textarea
|
|
86
|
+
id={$props?.id ?? defaultId}
|
|
87
|
+
class={`torp-text-area ${$props?.class ?? ""}`.trim()}
|
|
88
|
+
{name}
|
|
89
|
+
&value={$state.value}
|
|
90
|
+
{$props?.rows}
|
|
91
|
+
{$props?.placeholder}
|
|
92
|
+
{$props?.readonly}
|
|
93
|
+
{$props?.maxlength}
|
|
94
|
+
aria-label={$props?.ariaLabel}
|
|
95
|
+
data-valid={$state.valid ? "valid" : "invalid"}
|
|
96
|
+
onchange={$props?.onchange}
|
|
97
|
+
oninput={handleInput}
|
|
98
|
+
onblur={handleBlur}
|
|
99
|
+
/>
|
|
100
|
+
}
|
|
101
|
+
}
|