@x15ventures/x15-ui-library 3.13.5 → 3.14.0
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/dist/cjs/autoGenerated/libraryThemes.js +12 -12
- package/dist/cjs/autoGenerated/libraryThemes.js.map +1 -1
- package/dist/cjs/autoGenerated/libraryThemes.json +12 -12
- package/dist/cjs/components/form/formFields/formFieldWrapper/FormField.js +1 -1
- package/dist/cjs/components/form/formFields/formFieldWrapper/FormField.js.map +1 -1
- package/dist/cjs/components/form/formFields/formFieldWrapper/formFieldProps.js.map +1 -1
- package/dist/cjs/components/form/formFields/subComponents/textarea/Textarea.js +159 -18
- package/dist/cjs/components/form/formFields/subComponents/textarea/Textarea.js.map +1 -1
- package/dist/cjs/components/form/validation/ValidationMessages.js +2 -1
- package/dist/cjs/components/form/validation/ValidationMessages.js.map +1 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/styles/index.css +25 -0
- package/dist/cjs/styles/shared/_allPartials.css +25 -0
- package/dist/cjs/styles/shared/forms/_forms.css +25 -0
- package/dist/components/form/formFields/formFieldWrapper/FormField.test.d.ts +5 -0
- package/dist/components/form/formFields/formFieldWrapper/FormField.test.d.ts.map +1 -0
- package/dist/components/form/formFields/formFieldWrapper/formFieldProps.d.ts +11 -1
- package/dist/components/form/formFields/formFieldWrapper/formFieldProps.d.ts.map +1 -1
- package/dist/components/form/formFields/subComponents/textarea/Textarea.d.ts.map +1 -1
- package/dist/components/form/formFields/subComponents/textarea/Textarea.test.d.ts +5 -0
- package/dist/components/form/formFields/subComponents/textarea/Textarea.test.d.ts.map +1 -0
- package/dist/components/form/validation/ValidationMessages.d.ts +2 -0
- package/dist/components/form/validation/ValidationMessages.d.ts.map +1 -1
- package/dist/esm/autoGenerated/libraryThemes.js +12 -12
- package/dist/esm/autoGenerated/libraryThemes.js.map +1 -1
- package/dist/esm/autoGenerated/libraryThemes.json +12 -12
- package/dist/esm/components/form/formFields/formFieldWrapper/FormField.js +1 -1
- package/dist/esm/components/form/formFields/formFieldWrapper/FormField.js.map +1 -1
- package/dist/esm/components/form/formFields/formFieldWrapper/formFieldProps.js.map +1 -1
- package/dist/esm/components/form/formFields/subComponents/textarea/Textarea.js +158 -17
- package/dist/esm/components/form/formFields/subComponents/textarea/Textarea.js.map +1 -1
- package/dist/esm/components/form/validation/ValidationMessages.js +1 -1
- package/dist/esm/components/form/validation/ValidationMessages.js.map +1 -1
- package/dist/esm/package.json +1 -1
- package/dist/esm/styles/index.css +25 -0
- package/dist/esm/styles/shared/_allPartials.css +25 -0
- package/dist/esm/styles/shared/forms/_forms.css +25 -0
- package/dist/package.json +1 -1
- package/package.json +1 -1
- package/readme.md +1 -0
|
@@ -1,19 +1,49 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { useMemo, useState } from 'react';
|
|
4
4
|
import { useFormContext } from 'react-hook-form';
|
|
5
5
|
import { HookFormWrapper } from '../../../hookFormWrapper/HookFormWrapper';
|
|
6
6
|
import { NoHookFormContext } from '../../utilities/formUtilities';
|
|
7
|
+
import { extractError, ValidationMessages } from '../../../validation/ValidationMessages';
|
|
8
|
+
/**
|
|
9
|
+
* Counts user-perceived characters (grapheme clusters).
|
|
10
|
+
* Treats emojis and composed characters (e.g. accented letters) as a single character.
|
|
11
|
+
* Falls back to basic code-point counting if Intl.Segmenter is not available.
|
|
12
|
+
*/
|
|
13
|
+
function getCharacterCount(value) {
|
|
14
|
+
if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {
|
|
15
|
+
const segmenter = new Intl.Segmenter(undefined, {
|
|
16
|
+
granularity: 'grapheme'
|
|
17
|
+
});
|
|
18
|
+
return Array.from(segmenter.segment(value)).length;
|
|
19
|
+
}
|
|
20
|
+
return Array.from(value).length;
|
|
21
|
+
}
|
|
22
|
+
function clampToGraphemeMax(value, maxAllowed) {
|
|
23
|
+
if (getCharacterCount(value) <= maxAllowed) {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {
|
|
27
|
+
const segmenter = new Intl.Segmenter(undefined, {
|
|
28
|
+
granularity: 'grapheme'
|
|
29
|
+
});
|
|
30
|
+
return Array.from(segmenter.segment(value)).slice(0, maxAllowed).map(segment => segment.segment).join('');
|
|
31
|
+
}
|
|
32
|
+
return Array.from(value).slice(0, maxAllowed).join('');
|
|
33
|
+
}
|
|
7
34
|
export const Textarea = ({
|
|
8
35
|
field: {
|
|
9
36
|
id,
|
|
10
37
|
name,
|
|
11
38
|
placeholder,
|
|
12
39
|
defaultValue,
|
|
40
|
+
maxLength,
|
|
41
|
+
enforceMaxLength,
|
|
13
42
|
onBlur = () => {},
|
|
14
43
|
onChange = () => {},
|
|
15
44
|
onKeyDown = () => {},
|
|
16
|
-
onTouched = () => {}
|
|
45
|
+
onTouched = () => {},
|
|
46
|
+
...htmlNativeDomProps
|
|
17
47
|
},
|
|
18
48
|
validation,
|
|
19
49
|
showSpinner,
|
|
@@ -22,17 +52,101 @@ export const Textarea = ({
|
|
|
22
52
|
}) => {
|
|
23
53
|
const {
|
|
24
54
|
register,
|
|
25
|
-
getFieldState
|
|
55
|
+
getFieldState,
|
|
56
|
+
formState: {
|
|
57
|
+
errors
|
|
58
|
+
}
|
|
26
59
|
} = useFormContext() || NoHookFormContext;
|
|
27
60
|
const fieldState = getFieldState(name);
|
|
28
|
-
const
|
|
61
|
+
const hasFieldError = Boolean(extractError(errors, name));
|
|
62
|
+
// Combine fieldState.invalid with a direct errors lookup so this component re-renders
|
|
63
|
+
// whenever formState.errors changes (getFieldState alone does not subscribe to errors).
|
|
64
|
+
const isInvalid = Boolean(fieldState?.invalid ?? hasFieldError);
|
|
65
|
+
const [count, setCount] = useState(() => getCharacterCount(defaultValue?.toString() || ''));
|
|
66
|
+
const [hasStartedTyping, setHasStartedTyping] = useState(false);
|
|
67
|
+
const hasMaxLength = typeof maxLength === 'number';
|
|
68
|
+
const isAtLimit = hasMaxLength ? count >= maxLength : false;
|
|
69
|
+
// Only hide counter for 'required' error. All other errors (maxLength, pattern, etc)
|
|
70
|
+
// should show counter instead of error message.
|
|
71
|
+
const isRequiredError = fieldState?.error?.type === 'required';
|
|
72
|
+
const characterCounter = !hasStartedTyping || !hasMaxLength || isRequiredError ? undefined : `${count}/${maxLength}`;
|
|
73
|
+
const cssClass = `${customCssClasses?.input || ''}${isInvalid ? ' invalidField' : ''}`;
|
|
74
|
+
const normalizedValidation = useMemo(() => {
|
|
75
|
+
if (!validation || typeof validation.maxLength === 'undefined') {
|
|
76
|
+
return validation;
|
|
77
|
+
}
|
|
78
|
+
const maxLengthRule = validation.maxLength;
|
|
79
|
+
const maxLengthValue = typeof maxLengthRule === 'number' ? maxLengthRule : typeof maxLengthRule === 'object' && maxLengthRule && 'value' in maxLengthRule ? Number(maxLengthRule.value) : undefined;
|
|
80
|
+
if (typeof maxLengthValue !== 'number' || Number.isNaN(maxLengthValue)) {
|
|
81
|
+
return validation;
|
|
82
|
+
}
|
|
83
|
+
const maxLengthMessage = `Maximum length is ${maxLengthValue}`;
|
|
84
|
+
const graphemeMaxLengthValidator = value => getCharacterCount(value || '') <= maxLengthValue || maxLengthMessage;
|
|
85
|
+
|
|
86
|
+
// eslint-disable-next-line no-unused-vars
|
|
29
87
|
const {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
88
|
+
maxLength: _maxLength,
|
|
89
|
+
validate,
|
|
90
|
+
...restValidation
|
|
91
|
+
} = validation;
|
|
92
|
+
if (typeof validate === 'function') {
|
|
93
|
+
return {
|
|
94
|
+
...restValidation,
|
|
95
|
+
validate: async (value, formValues) => {
|
|
96
|
+
const validationResult = await validate(value, formValues);
|
|
97
|
+
if (validationResult !== true) {
|
|
98
|
+
return validationResult;
|
|
99
|
+
}
|
|
100
|
+
return graphemeMaxLengthValidator(value);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (validate && typeof validate === 'object') {
|
|
105
|
+
return {
|
|
106
|
+
...restValidation,
|
|
107
|
+
validate: {
|
|
108
|
+
...validate,
|
|
109
|
+
graphemeMaxLength: graphemeMaxLengthValidator
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
...restValidation,
|
|
115
|
+
validate: graphemeMaxLengthValidator
|
|
116
|
+
};
|
|
117
|
+
}, [validation]);
|
|
118
|
+
const registered = register(name, normalizedValidation);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Prevents input that would exceed the maxLength when counting grapheme clusters.
|
|
122
|
+
* Native <textarea maxLength> counts UTF-16 units, so emojis and composed characters
|
|
123
|
+
* can be treated as multiple characters. We avoid using native maxLength and enforce
|
|
124
|
+
* the limit here instead.
|
|
125
|
+
*/
|
|
126
|
+
const HandleBeforeInput = event => {
|
|
127
|
+
if (enforceMaxLength !== true || !hasMaxLength) return;
|
|
128
|
+
const nativeEvent = event.nativeEvent;
|
|
129
|
+
const inserted = nativeEvent.data ?? '';
|
|
130
|
+
if (!inserted) return;
|
|
131
|
+
const target = event.currentTarget;
|
|
132
|
+
const start = target.selectionStart ?? target.value.length;
|
|
133
|
+
const end = target.selectionEnd ?? target.value.length;
|
|
134
|
+
const nextValue = target.value.slice(0, start) + inserted + target.value.slice(end);
|
|
135
|
+
if (getCharacterCount(nextValue) > maxLength) {
|
|
136
|
+
event.preventDefault();
|
|
34
137
|
}
|
|
35
138
|
};
|
|
139
|
+
const HandleChange = event => {
|
|
140
|
+
const rawValue = event.target.value || '';
|
|
141
|
+
const newValue = enforceMaxLength === true && hasMaxLength ? clampToGraphemeMax(rawValue, maxLength) : rawValue;
|
|
142
|
+
if (newValue !== rawValue) {
|
|
143
|
+
event.target.value = newValue;
|
|
144
|
+
}
|
|
145
|
+
setHasStartedTyping(true);
|
|
146
|
+
setCount(getCharacterCount(newValue));
|
|
147
|
+
const onTextareaChange = onChange;
|
|
148
|
+
onTextareaChange?.(event);
|
|
149
|
+
};
|
|
36
150
|
const HandleOnBlur = event => {
|
|
37
151
|
const {
|
|
38
152
|
target
|
|
@@ -44,9 +158,14 @@ export const Textarea = ({
|
|
|
44
158
|
};
|
|
45
159
|
const HandleKeyEvent = event => {
|
|
46
160
|
onTouched();
|
|
47
|
-
onKeyDown
|
|
161
|
+
const onTextareaKeyDown = onKeyDown;
|
|
162
|
+
onTextareaKeyDown?.(event);
|
|
48
163
|
};
|
|
49
|
-
const
|
|
164
|
+
const characterCounterId = `${id}_characterCount`;
|
|
165
|
+
const errorsId = `${name}_errors`;
|
|
166
|
+
const existingDescribedBy = typeof htmlNativeDomProps['aria-describedby'] === 'string' ? htmlNativeDomProps['aria-describedby'] : undefined;
|
|
167
|
+
const showsRequiredError = isInvalid && isRequiredError;
|
|
168
|
+
const mergedDescribedBy = showsRequiredError ? [existingDescribedBy, errorsId].filter(Boolean).join(' ') : characterCounter !== undefined ? [existingDescribedBy, characterCounterId].filter(Boolean).join(' ') : existingDescribedBy;
|
|
50
169
|
const textareaProps = {
|
|
51
170
|
id,
|
|
52
171
|
className: cssClass,
|
|
@@ -58,16 +177,38 @@ export const Textarea = ({
|
|
|
58
177
|
...(validation && typeof validation.required !== 'undefined' ? {
|
|
59
178
|
required: true
|
|
60
179
|
} : {}),
|
|
61
|
-
...
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
180
|
+
...registered,
|
|
181
|
+
onBeforeInput: HandleBeforeInput,
|
|
182
|
+
onChange: event => {
|
|
183
|
+
HandleChange(event);
|
|
184
|
+
registered?.onChange?.(event);
|
|
185
|
+
},
|
|
186
|
+
onBlur: event => {
|
|
187
|
+
HandleOnBlur(event);
|
|
188
|
+
registered?.onBlur?.(event);
|
|
189
|
+
},
|
|
190
|
+
onKeyDown: HandleKeyEvent,
|
|
191
|
+
...htmlNativeDomProps,
|
|
192
|
+
'aria-describedby': mergedDescribedBy
|
|
67
193
|
};
|
|
68
194
|
return /*#__PURE__*/React.createElement(HookFormWrapper, null, customContent?.preField && /*#__PURE__*/React.createElement("span", {
|
|
69
195
|
className: "preField"
|
|
70
|
-
}, customContent?.preField), /*#__PURE__*/React.createElement("
|
|
196
|
+
}, customContent?.preField), /*#__PURE__*/React.createElement("div", {
|
|
197
|
+
className: "textareaFieldContainer"
|
|
198
|
+
}, /*#__PURE__*/React.createElement("textarea", textareaProps), /*#__PURE__*/React.createElement("div", {
|
|
199
|
+
className: "textareaFooter"
|
|
200
|
+
}, isInvalid && isRequiredError ? /*#__PURE__*/React.createElement(ValidationMessages, {
|
|
201
|
+
name: name,
|
|
202
|
+
validation: {
|
|
203
|
+
required: validation?.required
|
|
204
|
+
}
|
|
205
|
+
}) : /*#__PURE__*/React.createElement("div", {
|
|
206
|
+
id: characterCounterId,
|
|
207
|
+
className: `textareaCharacterCount ${isAtLimit ? 'textareaCharacterCountExceeded' : ''}`,
|
|
208
|
+
role: isAtLimit ? 'status' : undefined,
|
|
209
|
+
"aria-live": isAtLimit ? 'polite' : undefined,
|
|
210
|
+
"aria-atomic": isAtLimit ? 'true' : undefined
|
|
211
|
+
}, characterCounter))), customContent?.postField && /*#__PURE__*/React.createElement("span", {
|
|
71
212
|
className: "postField"
|
|
72
213
|
}, customContent?.postField));
|
|
73
214
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Textarea.js","names":["React","useFormContext","HookFormWrapper","NoHookFormContext","Textarea","field","id","name","placeholder","defaultValue","onBlur","onChange","onKeyDown","onTouched","validation","showSpinner","customCssClasses","customContent","register","getFieldState","fieldState","HandleAutoFill","event","target","HandleOnBlur","HandleKeyEvent","cssClass","input","invalid","textareaProps","className","disabled","required","createElement","preField","postField"],"sources":["../../../../../../../src/components/form/formFields/subComponents/textarea/Textarea.tsx"],"sourcesContent":["'use client';\n\nimport React from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { HookFormWrapper } from '../../../hookFormWrapper/HookFormWrapper';\nimport { FormFieldProps } from '../../formFieldWrapper/formFieldProps';\nimport { NoHookFormContext } from '../../utilities/formUtilities';\n\ninterface TextareaProps extends FormFieldProps, React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\nexport const Textarea: React.FC<TextareaProps> = (\n {\n field: {\n id,\n name,\n placeholder,\n defaultValue,\n onBlur = () => {},\n onChange = () => {},\n onKeyDown = () => {},\n onTouched = () => {}\n },\n validation,\n showSpinner,\n customCssClasses,\n customContent\n }\n) => {\n const { register, getFieldState } = useFormContext() || NoHookFormContext;\n const fieldState = getFieldState(name);\n\n const HandleAutoFill = (event: React.FocusEvent<HTMLInputElement>) => {\n const { target } = event;\n if (target) {\n onChange(event);\n }\n };\n\n const HandleOnBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const { target } = event;\n if (target) {\n onTouched();\n onBlur(event);\n }\n };\n\n const HandleKeyEvent = (\n event: React.KeyboardEvent<HTMLInputElement> &\n React.KeyboardEvent<HTMLSelectElement> &\n React.KeyboardEvent<HTMLTextAreaElement>\n ) => {\n onTouched();\n onKeyDown(event);\n };\n\n const cssClass = `${ customCssClasses?.input || '' }${ fieldState && fieldState?.invalid ? ' invalidField' : '' }`;\n\n const textareaProps = {\n id,\n className: cssClass,\n placeholder,\n defaultValue,\n ...(showSpinner ? { disabled: true } : {}),\n ...(validation && typeof validation.required !== 'undefined' ? { required: true } : {}),\n ...register(\n name,\n {\n onChange: HandleAutoFill,\n onBlur: HandleOnBlur,\n ...validation\n }\n ),\n onKeyDown: HandleKeyEvent\n };\n\n return (\n <HookFormWrapper>\n { customContent?.preField && <span className=\"preField\">{ customContent?.preField }</span> }\n\n <textarea { ...textareaProps } />\n\n { customContent?.postField && <span className=\"postField\">{ customContent?.postField }</span> }\n </HookFormWrapper>\n );\n};\n"],"mappings":"AAAA,YAAY;;AAEZ,OAAOA,KAAK,MAAM,OAAO;AACzB,SAASC,cAAc,QAAQ,iBAAiB;AAChD,SAASC,eAAe,QAAQ,0CAA0C;AAE1E,SAASC,iBAAiB,QAAQ,+BAA+B;AAIjE,OAAO,MAAMC,QAAiC,GAAGA,CAC/C;EACEC,KAAK,EAAE;IACLC,EAAE;IACFC,IAAI;IACJC,WAAW;IACXC,YAAY;IACZC,MAAM,GAAGA,CAAA,KAAM,CAAC,CAAC;IACjBC,QAAQ,GAAGA,CAAA,KAAM,CAAC,CAAC;IACnBC,SAAS,GAAGA,CAAA,KAAM,CAAC,CAAC;IACpBC,SAAS,GAAGA,CAAA,KAAM,CAAC;EACrB,CAAC;EACDC,UAAU;EACVC,WAAW;EACXC,gBAAgB;EAChBC;AACF,CAAC,KACE;EACH,MAAM;IAAEC,QAAQ;IAAEC;EAAc,CAAC,GAAGlB,cAAc,CAAC,CAAC,IAAIE,iBAAiB;EACzE,MAAMiB,UAAU,GAAGD,aAAa,CAACZ,IAAI,CAAC;EAEtC,MAAMc,cAAc,GAAIC,KAAyC,IAAK;IACpE,MAAM;MAAEC;IAAO,CAAC,GAAGD,KAAK;IACxB,IAAIC,MAAM,EAAE;MACVZ,QAAQ,CAACW,KAAK,CAAC;IACjB;EACF,CAAC;EAED,MAAME,YAAY,GAAIF,KAAyC,IAAK;IAClE,MAAM;MAAEC;IAAO,CAAC,GAAGD,KAAK;IACxB,IAAIC,MAAM,EAAE;MACVV,SAAS,CAAC,CAAC;MACXH,MAAM,CAACY,KAAK,CAAC;IACf;EACF,CAAC;EAED,MAAMG,cAAc,GAClBH,KAE0C,IACvC;IACHT,SAAS,CAAC,CAAC;IACXD,SAAS,CAACU,KAAK,CAAC;EAClB,CAAC;EAED,MAAMI,QAAQ,GAAG,GAAIV,gBAAgB,EAAEW,KAAK,IAAI,EAAE,GAAKP,UAAU,IAAIA,UAAU,EAAEQ,OAAO,GAAG,eAAe,GAAG,EAAE,EAAG;EAElH,MAAMC,aAAa,GAAG;IACpBvB,EAAE;IACFwB,SAAS,EAAEJ,QAAQ;IACnBlB,WAAW;IACXC,YAAY;IACZ,IAAIM,WAAW,GAAG;MAAEgB,QAAQ,EAAE;IAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,IAAIjB,UAAU,IAAI,OAAOA,UAAU,CAACkB,QAAQ,KAAK,WAAW,GAAG;MAAEA,QAAQ,EAAE;IAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACvF,GAAGd,QAAQ,CACTX,IAAI,EACJ;MACEI,QAAQ,EAAEU,cAAc;MACxBX,MAAM,EAAEc,YAAY;MACpB,GAAGV;IACL,CACF,CAAC;IACDF,SAAS,EAAEa;EACb,CAAC;EAED,oBACEzB,KAAA,CAAAiC,aAAA,CAAC/B,eAAe,QACZe,aAAa,EAAEiB,QAAQ,iBAAIlC,KAAA,CAAAiC,aAAA;IAAMH,SAAS,EAAC;EAAU,GAAGb,aAAa,EAAEiB,QAAgB,CAAC,eAE1FlC,KAAA,CAAAiC,aAAA,aAAeJ,aAAiB,CAAC,EAE/BZ,aAAa,EAAEkB,SAAS,iBAAInC,KAAA,CAAAiC,aAAA;IAAMH,SAAS,EAAC;EAAW,GAAGb,aAAa,EAAEkB,SAAiB,CAC7E,CAAC;AAEtB,CAAC","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"Textarea.js","names":["React","useMemo","useState","useFormContext","HookFormWrapper","NoHookFormContext","extractError","ValidationMessages","getCharacterCount","value","Intl","segmenter","Segmenter","undefined","granularity","Array","from","segment","length","clampToGraphemeMax","maxAllowed","slice","map","join","Textarea","field","id","name","placeholder","defaultValue","maxLength","enforceMaxLength","onBlur","onChange","onKeyDown","onTouched","htmlNativeDomProps","validation","showSpinner","customCssClasses","customContent","register","getFieldState","formState","errors","fieldState","hasFieldError","Boolean","isInvalid","invalid","count","setCount","toString","hasStartedTyping","setHasStartedTyping","hasMaxLength","isAtLimit","isRequiredError","error","type","characterCounter","cssClass","input","normalizedValidation","maxLengthRule","maxLengthValue","Number","isNaN","maxLengthMessage","graphemeMaxLengthValidator","_maxLength","validate","restValidation","formValues","validationResult","graphemeMaxLength","registered","HandleBeforeInput","event","nativeEvent","inserted","data","target","currentTarget","start","selectionStart","end","selectionEnd","nextValue","preventDefault","HandleChange","rawValue","newValue","onTextareaChange","HandleOnBlur","HandleKeyEvent","onTextareaKeyDown","characterCounterId","errorsId","existingDescribedBy","showsRequiredError","mergedDescribedBy","filter","textareaProps","className","disabled","required","onBeforeInput","createElement","preField","role","postField"],"sources":["../../../../../../../src/components/form/formFields/subComponents/textarea/Textarea.tsx"],"sourcesContent":["'use client';\n\nimport React, { useMemo, useState } from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport { HookFormWrapper } from '../../../hookFormWrapper/HookFormWrapper';\nimport { FormFieldProps } from '../../formFieldWrapper/formFieldProps';\nimport { NoHookFormContext } from '../../utilities/formUtilities';\nimport { extractError, ValidationMessages } from '../../../validation/ValidationMessages';\n\ninterface TextareaProps extends FormFieldProps, React.TextareaHTMLAttributes<HTMLTextAreaElement> {}\n\n/**\n* Counts user-perceived characters (grapheme clusters).\n* Treats emojis and composed characters (e.g. accented letters) as a single character.\n* Falls back to basic code-point counting if Intl.Segmenter is not available.\n*/\nfunction getCharacterCount(value: string): number {\n if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\n return Array.from(segmenter.segment(value)).length;\n }\n return Array.from(value).length;\n}\n\nfunction clampToGraphemeMax(value: string, maxAllowed: number): string {\n if (getCharacterCount(value) <= maxAllowed) {\n return value;\n }\n\n if (typeof Intl !== 'undefined' && 'Segmenter' in Intl) {\n const segmenter = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\n\n return Array.from(segmenter.segment(value))\n .slice(0, maxAllowed)\n .map((segment) => segment.segment)\n .join('');\n }\n\n return Array.from(value).slice(0, maxAllowed).join('');\n}\n\nexport const Textarea: React.FC<TextareaProps> = (\n {\n field: {\n id,\n name,\n placeholder,\n defaultValue,\n maxLength,\n enforceMaxLength,\n onBlur = () => {},\n onChange = () => {},\n onKeyDown = () => {},\n onTouched = () => {},\n ...htmlNativeDomProps\n },\n validation,\n showSpinner,\n customCssClasses,\n customContent\n }\n) => {\n const { register, getFieldState, formState: { errors } } = useFormContext() || NoHookFormContext;\n const fieldState = getFieldState(name);\n const hasFieldError = Boolean(extractError(errors, name));\n // Combine fieldState.invalid with a direct errors lookup so this component re-renders\n // whenever formState.errors changes (getFieldState alone does not subscribe to errors).\n const isInvalid = Boolean(fieldState?.invalid ?? hasFieldError);\n const [count, setCount] = useState<number>(() =>\n getCharacterCount(defaultValue?.toString() || '')\n );\n const [hasStartedTyping, setHasStartedTyping] = useState(false);\n const hasMaxLength = typeof maxLength === 'number';\n const isAtLimit = hasMaxLength ? count >= maxLength : false;\n // Only hide counter for 'required' error. All other errors (maxLength, pattern, etc)\n // should show counter instead of error message.\n const isRequiredError = fieldState?.error?.type === 'required';\n const characterCounter =\n !hasStartedTyping || !hasMaxLength || isRequiredError\n ? undefined\n : `${ count }/${ maxLength }`;\n const cssClass = `${ customCssClasses?.input || '' }${ isInvalid ? ' invalidField' : '' }`;\n\n const normalizedValidation = useMemo(() => {\n if (!validation || typeof validation.maxLength === 'undefined') {\n return validation;\n }\n\n const maxLengthRule = validation.maxLength;\n const maxLengthValue =\n typeof maxLengthRule === 'number'\n ? maxLengthRule\n : typeof maxLengthRule === 'object' && maxLengthRule && 'value' in maxLengthRule\n ? Number(maxLengthRule.value)\n : undefined;\n\n if (typeof maxLengthValue !== 'number' || Number.isNaN(maxLengthValue)) {\n return validation;\n }\n\n const maxLengthMessage = `Maximum length is ${ maxLengthValue }`;\n\n const graphemeMaxLengthValidator = (value: string) =>\n getCharacterCount(value || '') <= maxLengthValue || maxLengthMessage;\n\n // eslint-disable-next-line no-unused-vars\n const { maxLength: _maxLength, validate, ...restValidation } = validation;\n\n if (typeof validate === 'function') {\n return {\n ...restValidation,\n validate: async (value: string, formValues?: unknown) => {\n const validationResult = await validate(value, formValues);\n if (validationResult !== true) {\n return validationResult;\n }\n return graphemeMaxLengthValidator(value);\n },\n };\n }\n\n if (validate && typeof validate === 'object') {\n return {\n ...restValidation,\n validate: {\n ...validate,\n graphemeMaxLength: graphemeMaxLengthValidator,\n },\n };\n }\n\n return {\n ...restValidation,\n validate: graphemeMaxLengthValidator,\n };\n }, [validation]);\n\n const registered = register(name, normalizedValidation);\n\n /**\n * Prevents input that would exceed the maxLength when counting grapheme clusters.\n * Native <textarea maxLength> counts UTF-16 units, so emojis and composed characters\n * can be treated as multiple characters. We avoid using native maxLength and enforce\n * the limit here instead.\n */\n const HandleBeforeInput = (event: React.FormEvent<HTMLTextAreaElement>) => {\n if (enforceMaxLength !== true || !hasMaxLength) return;\n\n const nativeEvent = event.nativeEvent as InputEvent;\n const inserted = nativeEvent.data ?? '';\n\n if (!inserted) return;\n\n const target = event.currentTarget;\n const start = target.selectionStart ?? target.value.length;\n const end = target.selectionEnd ?? target.value.length;\n\n const nextValue =\n target.value.slice(0, start) +\n inserted +\n target.value.slice(end);\n\n if (getCharacterCount(nextValue) > maxLength) {\n event.preventDefault();\n }\n };\n\n const HandleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n const rawValue = event.target.value || '';\n const newValue =\n enforceMaxLength === true && hasMaxLength\n ? clampToGraphemeMax(rawValue, maxLength)\n : rawValue;\n\n if (newValue !== rawValue) {\n event.target.value = newValue;\n }\n\n setHasStartedTyping(true);\n setCount(getCharacterCount(newValue));\n const onTextareaChange = onChange as\n | React.ChangeEventHandler<HTMLTextAreaElement>\n | undefined;\n onTextareaChange?.(event);\n };\n\n const HandleOnBlur = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n const { target } = event;\n if (target) {\n onTouched();\n onBlur(event);\n }\n };\n\n const HandleKeyEvent = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n onTouched();\n const onTextareaKeyDown = onKeyDown as\n | React.KeyboardEventHandler<HTMLTextAreaElement>\n | undefined;\n onTextareaKeyDown?.(event);\n };\n\n const characterCounterId = `${ id }_characterCount`;\n const errorsId = `${ name }_errors`;\n const existingDescribedBy =\n typeof htmlNativeDomProps['aria-describedby'] === 'string'\n ? htmlNativeDomProps['aria-describedby']\n : undefined;\n const showsRequiredError = isInvalid && isRequiredError;\n const mergedDescribedBy = showsRequiredError\n ? [existingDescribedBy, errorsId].filter(Boolean).join(' ')\n : characterCounter !== undefined\n ? [existingDescribedBy, characterCounterId].filter(Boolean).join(' ')\n : existingDescribedBy;\n \n const textareaProps = {\n id,\n className: cssClass,\n placeholder,\n defaultValue,\n ...(showSpinner ? { disabled: true } : {}),\n ...(validation && typeof validation.required !== 'undefined' ? { required: true } : {}),\n ...registered,\n onBeforeInput: HandleBeforeInput,\n onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n HandleChange(event);\n registered?.onChange?.(event);\n },\n onBlur: (event: React.FocusEvent<HTMLTextAreaElement>) => {\n HandleOnBlur(event);\n registered?.onBlur?.(event);\n },\n onKeyDown: HandleKeyEvent,\n ...htmlNativeDomProps,\n 'aria-describedby': mergedDescribedBy,\n };\n\n return (\n <HookFormWrapper>\n { customContent?.preField && <span className=\"preField\">{ customContent?.preField }</span> }\n\n <div className=\"textareaFieldContainer\">\n <textarea { ...textareaProps } /> \n\n <div className=\"textareaFooter\">\n { isInvalid && isRequiredError\n ? <ValidationMessages name={ name } validation={ { required: validation?.required } } />\n : (\n <div\n id={ characterCounterId }\n className={ `textareaCharacterCount ${\n isAtLimit\n ? 'textareaCharacterCountExceeded'\n : ''\n }` }\n role={ isAtLimit ? 'status' : undefined }\n aria-live={ isAtLimit ? 'polite' : undefined }\n aria-atomic={ isAtLimit ? 'true' : undefined }\n >\n { characterCounter }\n </div>\n )\n }\n </div>\n </div>\n\n { customContent?.postField && <span className=\"postField\">{ customContent?.postField }</span> }\n </HookFormWrapper>\n );\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValidationMessages.js","names":["React","ErrorMessage","useFormContext","NoHookFormContext","GenericErrorMessages","required","minLength","value","maxLength","min","max","unknown","extractError","errors","fieldName","undefined","pathComponents","split","reduce","accumulator","pathComponent","ValidationMessages","name","validation","formState","fieldError","Object","entries","forEach","validatorKey","validator","type","types","message","createElement","className","id","render"],"sources":["../../../../../src/components/form/validation/ValidationMessages.tsx"],"sourcesContent":["/* eslint-disable no-unused-vars */\n\n'use client';\n\nimport React from 'react';\nimport { ErrorMessage } from '@hookform/error-message';\nimport {\n FieldError,\n FieldErrors,\n FieldErrorsImpl,\n Merge, useFormContext\n} from 'react-hook-form';\nimport {\n ValidationProps\n} from '../formFields/formFieldWrapper/formFieldProps';\nimport { NoHookFormContext } from '../formFields/utilities/formUtilities';\nimport { InputCheckboxValidationProps } from '../formFields/subComponents/inputs/checkbox/inputCheckboxProps';\n\n// Type for generic error messages\nexport const GenericErrorMessages: Record<string, (value?: never) => string> = {\n required: () => 'This field is required. Please enter a value.',\n minLength: (value) => `Please provide a minimum of ${ value } characters.`,\n maxLength: (value) => `Please provide a maximum of ${ value } characters.`,\n min: (value) => `The value must be a minimum of ${ value }.`,\n max: (value) => `The value must be a maximum of ${ value }.`,\n unknown: () => 'An unknown error has occurred.'\n};\n\n// Helper function to extract nested errors\
|
|
1
|
+
{"version":3,"file":"ValidationMessages.js","names":["React","ErrorMessage","useFormContext","NoHookFormContext","GenericErrorMessages","required","minLength","value","maxLength","min","max","unknown","extractError","errors","fieldName","undefined","pathComponents","split","reduce","accumulator","pathComponent","ValidationMessages","name","validation","formState","fieldError","Object","entries","forEach","validatorKey","validator","type","types","message","createElement","className","id","render"],"sources":["../../../../../src/components/form/validation/ValidationMessages.tsx"],"sourcesContent":["/* eslint-disable no-unused-vars */\n\n'use client';\n\nimport React from 'react';\nimport { ErrorMessage } from '@hookform/error-message';\nimport {\n FieldError,\n FieldErrors,\n FieldErrorsImpl,\n Merge, useFormContext\n} from 'react-hook-form';\nimport {\n ValidationProps\n} from '../formFields/formFieldWrapper/formFieldProps';\nimport { NoHookFormContext } from '../formFields/utilities/formUtilities';\nimport { InputCheckboxValidationProps } from '../formFields/subComponents/inputs/checkbox/inputCheckboxProps';\n\n// Type for generic error messages\nexport const GenericErrorMessages: Record<string, (value?: never) => string> = {\n required: () => 'This field is required. Please enter a value.',\n minLength: (value) => `Please provide a minimum of ${ value } characters.`,\n maxLength: (value) => `Please provide a maximum of ${ value } characters.`,\n min: (value) => `The value must be a minimum of ${ value }.`,\n max: (value) => `The value must be a maximum of ${ value }.`,\n unknown: () => 'An unknown error has occurred.'\n};\n\n// Helper function to extract nested errors\nexport const extractError = (\n errors: FieldErrors,\n fieldName: string\n): FieldError | Merge<FieldError, FieldErrorsImpl<never>> | undefined => {\n if (!errors) {\n return undefined;\n }\n\n const pathComponents = fieldName.split('.');\n\n return pathComponents.reduce<FieldError | Merge<FieldError, FieldErrorsImpl<never>> | undefined>(\n (accumulator, pathComponent) => {\n if (!accumulator) {\n return undefined;\n }\n\n return (accumulator as never)[pathComponent];\n },\n errors as never\n );\n};\n\ninterface ValidationMessagesProps {\n name: string;\n validation?: ValidationProps | InputCheckboxValidationProps\n}\n\nexport const ValidationMessages: React.FC<ValidationMessagesProps> = (\n {\n name,\n validation\n }\n) => {\n const { formState: { errors } } = useFormContext() || NoHookFormContext;\n\n const fieldError = extractError(errors, name);\n\n if (!fieldError) {\n return null;\n }\n\n // Check all the validators have a message\n if (validation) {\n Object.entries(validation).forEach(([validatorKey, validator]) => {\n if (GenericErrorMessages[validatorKey]) {\n if (typeof validator === 'boolean') {\n if (\n fieldError.type &&\n fieldError.types &&\n typeof fieldError.types[fieldError.type] === 'string'\n ) {\n fieldError.message = fieldError.types[fieldError.type] as string;\n } else {\n fieldError.message = GenericErrorMessages[validatorKey]();\n }\n } else if (typeof validator === 'object' && 'value' in validator) {\n // @ts-ignore The hook form types don't seem to cater for versions with value properties properly\n fieldError.message = GenericErrorMessages[validatorKey](validator.value || undefined);\n }\n }\n });\n }\n\n return (\n <span className=\"validation\" id={ `${ name }_errors` }>\n <span className=\"message\">\n { !fieldError?.message && (\n <span className=\"error\">{ GenericErrorMessages.unknown() }</span>\n ) }\n\n <ErrorMessage\n errors={ errors }\n name={ name }\n render={ ({ message }) => <span className=\"error\">{ message }</span> }\n />\n </span>\n </span>\n );\n};\n\nexport default ValidationMessages;\n"],"mappings":"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","ignoreList":[]}
|
package/dist/esm/package.json
CHANGED
|
@@ -1914,6 +1914,31 @@ form select:placeholder-shown {
|
|
|
1914
1914
|
font-weight: 200;
|
|
1915
1915
|
font-family: var(--bodyFont);
|
|
1916
1916
|
}
|
|
1917
|
+
form .textareaFieldContainer {
|
|
1918
|
+
display: flex;
|
|
1919
|
+
flex-direction: column;
|
|
1920
|
+
row-gap: 5px;
|
|
1921
|
+
align-items: stretch;
|
|
1922
|
+
justify-content: flex-start;
|
|
1923
|
+
width: 100%;
|
|
1924
|
+
}
|
|
1925
|
+
form .textareaFooter {
|
|
1926
|
+
min-height: 1.5em;
|
|
1927
|
+
}
|
|
1928
|
+
form .textarea_Field.stacked label .textareaFooter .validation {
|
|
1929
|
+
position: static;
|
|
1930
|
+
height: auto;
|
|
1931
|
+
}
|
|
1932
|
+
form .textarea_Field.withValidation label {
|
|
1933
|
+
padding-bottom: 0;
|
|
1934
|
+
}
|
|
1935
|
+
form .textareaCharacterCount {
|
|
1936
|
+
line-height: 1.5;
|
|
1937
|
+
color: var(--inputLabelColor);
|
|
1938
|
+
}
|
|
1939
|
+
form .textareaCharacterCountExceeded {
|
|
1940
|
+
color: var(--inputErrorColor);
|
|
1941
|
+
}
|
|
1917
1942
|
form input[type=number] {
|
|
1918
1943
|
padding-right: 0;
|
|
1919
1944
|
}
|
|
@@ -1262,6 +1262,31 @@ form select:placeholder-shown {
|
|
|
1262
1262
|
font-weight: 200;
|
|
1263
1263
|
font-family: var(--bodyFont);
|
|
1264
1264
|
}
|
|
1265
|
+
form .textareaFieldContainer {
|
|
1266
|
+
display: flex;
|
|
1267
|
+
flex-direction: column;
|
|
1268
|
+
row-gap: 5px;
|
|
1269
|
+
align-items: stretch;
|
|
1270
|
+
justify-content: flex-start;
|
|
1271
|
+
width: 100%;
|
|
1272
|
+
}
|
|
1273
|
+
form .textareaFooter {
|
|
1274
|
+
min-height: 1.5em;
|
|
1275
|
+
}
|
|
1276
|
+
form .textarea_Field.stacked label .textareaFooter .validation {
|
|
1277
|
+
position: static;
|
|
1278
|
+
height: auto;
|
|
1279
|
+
}
|
|
1280
|
+
form .textarea_Field.withValidation label {
|
|
1281
|
+
padding-bottom: 0;
|
|
1282
|
+
}
|
|
1283
|
+
form .textareaCharacterCount {
|
|
1284
|
+
line-height: 1.5;
|
|
1285
|
+
color: var(--inputLabelColor);
|
|
1286
|
+
}
|
|
1287
|
+
form .textareaCharacterCountExceeded {
|
|
1288
|
+
color: var(--inputErrorColor);
|
|
1289
|
+
}
|
|
1265
1290
|
form input[type=number] {
|
|
1266
1291
|
padding-right: 0;
|
|
1267
1292
|
}
|
|
@@ -261,6 +261,31 @@ form select:placeholder-shown {
|
|
|
261
261
|
font-weight: 200;
|
|
262
262
|
font-family: var(--bodyFont);
|
|
263
263
|
}
|
|
264
|
+
form .textareaFieldContainer {
|
|
265
|
+
display: flex;
|
|
266
|
+
flex-direction: column;
|
|
267
|
+
row-gap: 5px;
|
|
268
|
+
align-items: stretch;
|
|
269
|
+
justify-content: flex-start;
|
|
270
|
+
width: 100%;
|
|
271
|
+
}
|
|
272
|
+
form .textareaFooter {
|
|
273
|
+
min-height: 1.5em;
|
|
274
|
+
}
|
|
275
|
+
form .textarea_Field.stacked label .textareaFooter .validation {
|
|
276
|
+
position: static;
|
|
277
|
+
height: auto;
|
|
278
|
+
}
|
|
279
|
+
form .textarea_Field.withValidation label {
|
|
280
|
+
padding-bottom: 0;
|
|
281
|
+
}
|
|
282
|
+
form .textareaCharacterCount {
|
|
283
|
+
line-height: 1.5;
|
|
284
|
+
color: var(--inputLabelColor);
|
|
285
|
+
}
|
|
286
|
+
form .textareaCharacterCountExceeded {
|
|
287
|
+
color: var(--inputErrorColor);
|
|
288
|
+
}
|
|
264
289
|
form input[type=number] {
|
|
265
290
|
padding-right: 0;
|
|
266
291
|
}
|
package/dist/package.json
CHANGED
package/package.json
CHANGED
package/readme.md
CHANGED