@x15ventures/x15-ui-library 3.13.4 → 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.
Files changed (45) hide show
  1. package/dist/cjs/autoGenerated/libraryThemes.js +12 -12
  2. package/dist/cjs/autoGenerated/libraryThemes.js.map +1 -1
  3. package/dist/cjs/autoGenerated/libraryThemes.json +12 -12
  4. package/dist/cjs/components/design/banners/pageBanner/PageBanner.js +4 -1
  5. package/dist/cjs/components/design/banners/pageBanner/PageBanner.js.map +1 -1
  6. package/dist/cjs/components/form/formFields/formFieldWrapper/FormField.js +1 -1
  7. package/dist/cjs/components/form/formFields/formFieldWrapper/FormField.js.map +1 -1
  8. package/dist/cjs/components/form/formFields/formFieldWrapper/formFieldProps.js.map +1 -1
  9. package/dist/cjs/components/form/formFields/subComponents/textarea/Textarea.js +159 -18
  10. package/dist/cjs/components/form/formFields/subComponents/textarea/Textarea.js.map +1 -1
  11. package/dist/cjs/components/form/validation/ValidationMessages.js +2 -1
  12. package/dist/cjs/components/form/validation/ValidationMessages.js.map +1 -1
  13. package/dist/cjs/package.json +17 -15
  14. package/dist/cjs/styles/index.css +25 -0
  15. package/dist/cjs/styles/shared/_allPartials.css +25 -0
  16. package/dist/cjs/styles/shared/forms/_forms.css +25 -0
  17. package/dist/components/design/banners/pageBanner/PageBanner.d.ts.map +1 -1
  18. package/dist/components/form/formFields/formFieldWrapper/FormField.test.d.ts +5 -0
  19. package/dist/components/form/formFields/formFieldWrapper/FormField.test.d.ts.map +1 -0
  20. package/dist/components/form/formFields/formFieldWrapper/formFieldProps.d.ts +11 -1
  21. package/dist/components/form/formFields/formFieldWrapper/formFieldProps.d.ts.map +1 -1
  22. package/dist/components/form/formFields/subComponents/textarea/Textarea.d.ts.map +1 -1
  23. package/dist/components/form/formFields/subComponents/textarea/Textarea.test.d.ts +5 -0
  24. package/dist/components/form/formFields/subComponents/textarea/Textarea.test.d.ts.map +1 -0
  25. package/dist/components/form/validation/ValidationMessages.d.ts +2 -0
  26. package/dist/components/form/validation/ValidationMessages.d.ts.map +1 -1
  27. package/dist/esm/autoGenerated/libraryThemes.js +12 -12
  28. package/dist/esm/autoGenerated/libraryThemes.js.map +1 -1
  29. package/dist/esm/autoGenerated/libraryThemes.json +12 -12
  30. package/dist/esm/components/design/banners/pageBanner/PageBanner.js +5 -2
  31. package/dist/esm/components/design/banners/pageBanner/PageBanner.js.map +1 -1
  32. package/dist/esm/components/form/formFields/formFieldWrapper/FormField.js +1 -1
  33. package/dist/esm/components/form/formFields/formFieldWrapper/FormField.js.map +1 -1
  34. package/dist/esm/components/form/formFields/formFieldWrapper/formFieldProps.js.map +1 -1
  35. package/dist/esm/components/form/formFields/subComponents/textarea/Textarea.js +158 -17
  36. package/dist/esm/components/form/formFields/subComponents/textarea/Textarea.js.map +1 -1
  37. package/dist/esm/components/form/validation/ValidationMessages.js +1 -1
  38. package/dist/esm/components/form/validation/ValidationMessages.js.map +1 -1
  39. package/dist/esm/package.json +17 -15
  40. package/dist/esm/styles/index.css +25 -0
  41. package/dist/esm/styles/shared/_allPartials.css +25 -0
  42. package/dist/esm/styles/shared/forms/_forms.css +25 -0
  43. package/dist/package.json +17 -15
  44. package/package.json +17 -15
  45. package/readme.md +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"formFieldProps.js","names":[],"sources":["../../../../../../src/components/form/formFields/formFieldWrapper/formFieldProps.ts"],"sourcesContent":["/* eslint-disable no-unused-vars */\n\nimport {\n ChangeEvent, ChangeEventHandler, FormEventHandler,\n KeyboardEvent,\n ReactElement,\n ReactNode,\n} from 'react';\n\nimport type {\n InputHTMLAttributes,\n SelectHTMLAttributes,\n TextareaHTMLAttributes,\n} from 'react';\n\nimport {\n Message,\n Validate,\n ValidationRule\n} from 'react-hook-form';\nimport { GenericFunction } from '../../../../interfaces';\nimport { KeyboardEventHandler } from '../../../../interfaces/generic.interface';\n\n/**\n * Custom content props for additional elements around the label or field.\n */\nexport interface CustomContentProps {\n /** Element or text to display before the label. */\n preLabel?: ReactElement | string;\n /** Element or text to display after the label. */\n postLabel?: ReactElement | string;\n /** Element or text to display before the field. */\n preField?: ReactElement | string;\n /** Element or text to display after the field. */\n postField?: ReactElement | string;\n}\n\n/**\n * Props for the label element.\n */\nexport interface LabelProps {\n /** Text or element for the label. */\n text: string | ReactNode | ReactElement;\n /** The ID of the input the label is associated with. */\n inputId?: string;\n /** The ID of the label element. */\n labelId?: string;\n /** Whether to hide the label visually. */\n hide?: boolean;\n /** Position of the label relative to the field. */\n position?: 'inset' | 'left' | 'right' | string;\n /** Tooltip for the label. */\n labelTooltip?: {\n /** Text for the tooltip. */\n text: string;\n /** Optional icon for the tooltip. */\n icon?: ReactElement | string;\n };\n /** Screen reader instructions for the label. */\n srInstruction?: string;\n /** Whether the field is required. */\n isRequired?: boolean;\n /** Custom content for the label. */\n customContent?: CustomContentProps;\n}\n\n/**\n * Type for a single select option.\n */\nexport type SelectOptionObjectType = {\n /** Label of the option. */\n label: string;\n /** Value of the option. */\n value: string | number | boolean;\n};\n\n/**\n * Type for select options, which can be an object or string.\n */\nexport type SelectOptionType = SelectOptionObjectType | string;\n\n/**\n * Type for a single combobox (autocomplete) option.\n */\nexport type ComboboxOptionObjectType = {\n /** Label of the option. */\n label: string;\n /** Value of the option. */\n value: string | number | boolean;\n /** Additional data associated with the option. */\n data?: Record<string, unknown>;\n};\n\nexport type ComboboxOption = ComboboxOptionObjectType | string;\n\n// Everything we want to *override* with our own definitions in FieldProps\nexport type OverriddenKeys =\n | 'type'\n | 'id'\n | 'name'\n | 'placeholder'\n | 'defaultValue'\n | 'onChange'\n | 'onBlur'\n | 'onInput'\n | 'onKeyDown'\n | 'disabled'\n | 'readOnly'\n | 'min'\n | 'max';\n\n// All the native props we want to mix in, minus the ones we override\nexport type NativeFieldProps =\n & Omit<InputHTMLAttributes<HTMLInputElement>, OverriddenKeys>\n & Omit<SelectHTMLAttributes<HTMLSelectElement>, OverriddenKeys>\n & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, OverriddenKeys>;\n\n/**\n * Props for the field element.\n */\nexport interface FieldProps extends NativeFieldProps {\n /** The type of the input field. */\n type: string;\n /** The ID of the input field. */\n id: string;\n /** The name of the input field. */\n name: string;\n /** Placeholder text for the input field. */\n placeholder?: string;\n /** Default value for the input field. */\n defaultValue?: string | number | readonly string[];\n /** Handler for the change event. */\n onChange?: ChangeEventHandler | ((\n event: ChangeEvent<HTMLInputElement> | KeyboardEvent<HTMLSelectElement> | KeyboardEvent<HTMLTextAreaElement>\n ) => void | GenericFunction);\n /** Handler for the blur event. */\n onBlur?: GenericFunction;\n /** Handler for the input event. */\n onInput?: FormEventHandler<\n HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement\n >;\n /** Handler for the keydown event. */\n onKeyDown?: KeyboardEventHandler | ((\n event: KeyboardEvent<HTMLInputElement> | KeyboardEvent<HTMLSelectElement> | KeyboardEvent<HTMLTextAreaElement>\n ) => void | GenericFunction);\n /** Handler for marking the field as touched. */\n onTouched?: GenericFunction;\n /** Whether to include an empty option in a dropdown. */\n includeEmptyOption?: boolean;\n /** Label for the empty option. */\n emptyOptionLabel?: string;\n /** Options for a dropdown field. */\n options?: SelectOptionType[] | ComboboxOption[];\n /** Auto-complete behaviour for the field. */\n autoComplete?: string;\n /** Whether the field allows editing and interaction. Note: Setting this to true makes it invisible to screen reader users. */\n disabled?: boolean;\n /** Setting to true prevents editing and interaction whilst still making the value visible to screen reader users. */\n readonly?: boolean;\n /** Minimum value or length. */\n min?: string | number;\n /** Maximum value or length. */\n max?: string | number;\n}\n\n/**\n * Validation rules for fields.\n */\nexport type CustomValidate<TFieldValue> = (\n value: TFieldValue,\n formValues?: unknown\n) => boolean | string | Promise<boolean | string>;\n\n/**\n * Props for validation rules.\n */\n\nexport type MinMaxRule<T> = T extends boolean\n ? ValidationRule<boolean>\n : ValidationRule<number | string>;\n\nexport interface ValidationProps<\n TFieldValue = unknown | string | boolean,\n TFormValues = unknown\n> {\n /** Whether the field is required. */\n required?: Message | ValidationRule<boolean>;\n /** Minimum value or length. */\n min?: MinMaxRule<TFieldValue>;\n /** Maximum value or length. */\n max?: MinMaxRule<TFieldValue>;\n /** Maximum length of the field. */\n maxLength?: ValidationRule<number>;\n /** Minimum length of the field. */\n minLength?: ValidationRule<number>;\n /** Regex pattern for validation. */\n pattern?: ValidationRule<RegExp>;\n /** Custom validation logic. */\n validate?: CustomValidate<TFieldValue> | Record<string, CustomValidate<TFieldValue>> | Validate<TFieldValue, TFormValues>;\n}\n\nexport type ValidationKey = keyof ValidationProps<unknown, unknown>; // Restrict to known keys\n\n/**\n * Custom CSS classes for the field components.\n */\nexport interface CustomFieldCssClassesProps {\n /** CSS class for the label. */\n label?: string;\n /** CSS class for the input field. */\n input?: string;\n /** CSS class for the wrapper. */\n wrapper?: string;\n}\n\n/**\n * Props for a form field component.\n */\nexport interface FormFieldProps {\n /** Props for the label. */\n label: LabelProps;\n /** Props for the field. */\n field: FieldProps;\n /** Layout style of the form field. */\n layout?: 'stacked' | 'horizontal' | string;\n /** Validation rules for the field. */\n validation?: ValidationProps;\n /** Whether to show a spinner. */\n showSpinner?: boolean;\n /** Custom CSS classes for the field. */\n customCssClasses?: CustomFieldCssClassesProps;\n /** Custom content for the field. */\n customContent?: CustomContentProps;\n /** Override the default nesting of the input inside the label. */\n separateLabelAndField?: boolean;\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"formFieldProps.js","names":[],"sources":["../../../../../../src/components/form/formFields/formFieldWrapper/formFieldProps.ts"],"sourcesContent":["/* eslint-disable no-unused-vars */\n\nimport {\n ChangeEvent, ChangeEventHandler, FormEventHandler,\n KeyboardEvent,\n ReactElement,\n ReactNode,\n} from 'react';\n\nimport type {\n InputHTMLAttributes,\n SelectHTMLAttributes,\n TextareaHTMLAttributes,\n} from 'react';\n\nimport {\n Message,\n Validate,\n ValidationRule\n} from 'react-hook-form';\nimport { GenericFunction } from '../../../../interfaces';\nimport { KeyboardEventHandler } from '../../../../interfaces/generic.interface';\n\n/**\n * Custom content props for additional elements around the label or field.\n */\nexport interface CustomContentProps {\n /** Element or text to display before the label. */\n preLabel?: ReactElement | string;\n /** Element or text to display after the label. */\n postLabel?: ReactElement | string;\n /** Element or text to display before the field. */\n preField?: ReactElement | string;\n /** Element or text to display after the field. */\n postField?: ReactElement | string;\n}\n\n/**\n * Props for the label element.\n */\nexport interface LabelProps {\n /** Text or element for the label. */\n text: string | ReactNode | ReactElement;\n /** The ID of the input the label is associated with. */\n inputId?: string;\n /** The ID of the label element. */\n labelId?: string;\n /** Whether to hide the label visually. */\n hide?: boolean;\n /** Position of the label relative to the field. */\n position?: 'inset' | 'left' | 'right' | string;\n /** Tooltip for the label. */\n labelTooltip?: {\n /** Text for the tooltip. */\n text: string;\n /** Optional icon for the tooltip. */\n icon?: ReactElement | string;\n };\n /** Screen reader instructions for the label. */\n srInstruction?: string;\n /** Whether the field is required. */\n isRequired?: boolean;\n /** Custom content for the label. */\n customContent?: CustomContentProps;\n}\n\n/**\n * Type for a single select option.\n */\nexport type SelectOptionObjectType = {\n /** Label of the option. */\n label: string;\n /** Value of the option. */\n value: string | number | boolean;\n};\n\n/**\n * Type for select options, which can be an object or string.\n */\nexport type SelectOptionType = SelectOptionObjectType | string;\n\n/**\n * Type for a single combobox (autocomplete) option.\n */\nexport type ComboboxOptionObjectType = {\n /** Label of the option. */\n label: string;\n /** Value of the option. */\n value: string | number | boolean;\n /** Additional data associated with the option. */\n data?: Record<string, unknown>;\n};\n\nexport type ComboboxOption = ComboboxOptionObjectType | string;\n\n// Everything we want to *override* with our own definitions in FieldProps\nexport type OverriddenKeys =\n | 'type'\n | 'id'\n | 'name'\n | 'placeholder'\n | 'defaultValue'\n | 'onChange'\n | 'onBlur'\n | 'onInput'\n | 'onKeyDown'\n | 'disabled'\n | 'readOnly'\n | 'min'\n | 'max';\n\n// All the native props we want to mix in, minus the ones we override\nexport type NativeFieldProps =\n & Omit<InputHTMLAttributes<HTMLInputElement>, OverriddenKeys>\n & Omit<SelectHTMLAttributes<HTMLSelectElement>, OverriddenKeys>\n & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, OverriddenKeys>;\n\n/**\n * Props for the field element.\n */\nexport interface FieldProps extends NativeFieldProps {\n /** The type of the input field. */\n type: string;\n /** The ID of the input field. */\n id: string;\n /** The name of the input field. */\n name: string;\n /** Placeholder text for the input field. */\n placeholder?: string;\n /** Default value for the input field. */\n defaultValue?: string | number | readonly string[];\n /** Handler for the change event. */\n onChange?: ChangeEventHandler | ((\n event: ChangeEvent<HTMLInputElement> | KeyboardEvent<HTMLSelectElement> | KeyboardEvent<HTMLTextAreaElement>\n ) => void | GenericFunction);\n /** Handler for the blur event. */\n onBlur?: GenericFunction;\n /** Handler for the input event. */\n onInput?: FormEventHandler<\n HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement\n >;\n /** Handler for the keydown event. */\n onKeyDown?: KeyboardEventHandler | ((\n event: KeyboardEvent<HTMLInputElement> | KeyboardEvent<HTMLSelectElement> | KeyboardEvent<HTMLTextAreaElement>\n ) => void | GenericFunction);\n /** Handler for marking the field as touched. */\n onTouched?: GenericFunction;\n /** Whether to include an empty option in a dropdown. */\n includeEmptyOption?: boolean;\n /** Label for the empty option. */\n emptyOptionLabel?: string;\n /** Options for a dropdown field. */\n options?: SelectOptionType[] | ComboboxOption[];\n /** Auto-complete behaviour for the field. */\n autoComplete?: string;\n /** Whether the field allows editing and interaction. Note: Setting this to true makes it invisible to screen reader users. */\n disabled?: boolean;\n /** Setting to true prevents editing and interaction whilst still making the value visible to screen reader users. */\n readonly?: boolean;\n /** Minimum value or length. */\n min?: string | number;\n /** Maximum value or length. */\n max?: string | number;\n /**\n * Whether to enforce grapheme-aware max-length handling (used by textarea).\n * Uses `field.maxLength` (native field prop) as the source for the UI/input limit.\n * Keep `field.maxLength` and `validation.maxLength` aligned to avoid mixed UX/validation behaviour.\n */\n enforceMaxLength?: boolean;\n}\n\n/**\n * Validation rules for fields.\n */\nexport type CustomValidate<TFieldValue> = (\n value: TFieldValue,\n formValues?: unknown\n) => boolean | string | Promise<boolean | string>;\n\n/**\n * Props for validation rules.\n */\n\nexport type MinMaxRule<T> = T extends boolean\n ? ValidationRule<boolean>\n : ValidationRule<number | string>;\n\nexport interface ValidationProps<\n TFieldValue = unknown | string | boolean,\n TFormValues = unknown\n> {\n /** Whether the field is required. */\n required?: Message | ValidationRule<boolean>;\n /** Minimum value or length. */\n min?: MinMaxRule<TFieldValue>;\n /** Maximum value or length. */\n max?: MinMaxRule<TFieldValue>;\n /**\n * Maximum length validation rule.\n * Used for validation outcome/messages, while textarea UI/input limiting is driven by `field.maxLength`.\n * If both are provided, keep them equal.\n */\n maxLength?: ValidationRule<number>;\n /** Minimum length of the field. */\n minLength?: ValidationRule<number>;\n /** Regex pattern for validation. */\n pattern?: ValidationRule<RegExp>;\n /** Custom validation logic. */\n validate?: CustomValidate<TFieldValue> | Record<string, CustomValidate<TFieldValue>> | Validate<TFieldValue, TFormValues>;\n}\n\nexport type ValidationKey = keyof ValidationProps<unknown, unknown>; // Restrict to known keys\n\n/**\n * Custom CSS classes for the field components.\n */\nexport interface CustomFieldCssClassesProps {\n /** CSS class for the label. */\n label?: string;\n /** CSS class for the input field. */\n input?: string;\n /** CSS class for the wrapper. */\n wrapper?: string;\n}\n\n/**\n * Props for a form field component.\n */\nexport interface FormFieldProps {\n /** Props for the label. */\n label: LabelProps;\n /** Props for the field. */\n field: FieldProps;\n /** Layout style of the form field. */\n layout?: 'stacked' | 'horizontal' | string;\n /** Validation rules for the field. */\n validation?: ValidationProps;\n /** Whether to show a spinner. */\n showSpinner?: boolean;\n /** Custom CSS classes for the field. */\n customCssClasses?: CustomFieldCssClassesProps;\n /** Custom content for the field. */\n customContent?: CustomContentProps;\n /** Override the default nesting of the input inside the label. */\n separateLabelAndField?: boolean;\n}\n"],"mappings":"","ignoreList":[]}
@@ -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 HandleAutoFill = event => {
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
- target
31
- } = event;
32
- if (target) {
33
- onChange(event);
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(event);
161
+ const onTextareaKeyDown = onKeyDown;
162
+ onTextareaKeyDown?.(event);
48
163
  };
49
- const cssClass = `${customCssClasses?.input || ''}${fieldState && fieldState?.invalid ? ' invalidField' : ''}`;
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
- ...register(name, {
62
- onChange: HandleAutoFill,
63
- onBlur: HandleOnBlur,
64
- ...validation
65
- }),
66
- onKeyDown: HandleKeyEvent
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("textarea", textareaProps), customContent?.postField && /*#__PURE__*/React.createElement("span", {
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":[]}
@@ -17,7 +17,7 @@ export const GenericErrorMessages = {
17
17
  };
18
18
 
19
19
  // Helper function to extract nested errors
20
- const extractError = (errors, fieldName) => {
20
+ export const extractError = (errors, fieldName) => {
21
21
  if (!errors) {
22
22
  return undefined;
23
23
  }
@@ -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\nconst 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":[]}
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":[]}
@@ -137,7 +137,7 @@
137
137
  "files": [
138
138
  "dist"
139
139
  ],
140
- "version": "3.13.3",
140
+ "version": "3.13.5",
141
141
  "packageManager": "pnpm@10.11.0",
142
142
  "repository": {
143
143
  "type": "git",
@@ -175,7 +175,7 @@
175
175
  "dependencies": {
176
176
  "@hookform/error-message": "^2.0.1",
177
177
  "@x15stack/aws": "^1.7.2",
178
- "@x15ventures/x15-ui-library-icons": "^1.1.1",
178
+ "@x15ventures/x15-ui-library-icons": "^1.1.3",
179
179
  "babel-plugin-transform-imports": "^2.0.0",
180
180
  "html-react-parser": "^5.2.17",
181
181
  "jsdom": "^26.1.0",
@@ -195,7 +195,7 @@
195
195
  },
196
196
  "devDependencies": {
197
197
  "@babel/cli": "^7.28.6",
198
- "@babel/core": "^7.29.0",
198
+ "@babel/core": "7.29.6",
199
199
  "@babel/eslint-parser": "^7.28.6",
200
200
  "@babel/eslint-plugin": "^7.27.1",
201
201
  "@babel/plugin-syntax-jsx": "^7.28.6",
@@ -256,7 +256,7 @@
256
256
  "babel-loader": "^9.2.1",
257
257
  "babel-plugin-macros": "^3.1.0",
258
258
  "chromatic": "^11.29.0",
259
- "commitlint": "^20.5.2",
259
+ "commitlint": "^20.5.3",
260
260
  "copy-webpack-plugin": "^12.0.2",
261
261
  "cors": "^2.8.6",
262
262
  "cross-env": "^7.0.3",
@@ -301,32 +301,34 @@
301
301
  "ttab": "^0.9.0",
302
302
  "typescript": "5.5.4",
303
303
  "url-loader": "^4.1.1",
304
- "vitest": "3.1.1"
304
+ "vitest": "3.2.6"
305
305
  },
306
306
  "pnpm": {
307
307
  "overrides": {
308
+ "@babel/plugin-transform-modules-systemjs": "7.29.4",
308
309
  "@tootallnate/once@<3.0.1": ">=3.0.1",
309
- "brace-expansion": ">=5.0.5",
310
+ "brace-expansion": "5.0.6",
311
+ "fast-uri": "3.1.2",
312
+ "form-data": "4.0.6",
310
313
  "flatted": ">=3.4.2",
311
314
  "flatted@<3.4.0": ">=3.4.0",
312
315
  "handlebars": ">=4.7.9",
313
316
  "immutable@>=5.0.0 <5.1.5": ">=5.1.5",
314
- "minimatch": ">=9.0.7",
317
+ "js-yaml": "4.2.0",
318
+ "minimatch": "9.0.7",
315
319
  "path-to-regexp": ">=8.4.0",
316
320
  "picomatch": ">=4.0.4",
317
- "qs": ">=6.14.2",
321
+ "qs": "6.15.2",
318
322
  "rollup": ">=4.59.0",
319
323
  "serialize-javascript": ">=7.0.3",
324
+ "shell-quote": "1.8.4",
320
325
  "storybook": ">=10.2.10",
321
- "undici": ">=7.24.0",
326
+ "undici": "8.5.0",
327
+ "uuid": "11.1.1",
328
+ "vite": "6.4.3",
322
329
  "webpack": ">=5.104.1",
330
+ "ws": "8.21.0",
323
331
  "yaml": ">=1.10.3"
324
- },
325
- "auditConfig": {
326
- "ignoreCves": [
327
- "GHSA-rx8g-88g5-qh64",
328
- "CVE-2025-57352"
329
- ]
330
332
  }
331
333
  }
332
334
  }
@@ -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
@@ -137,7 +137,7 @@
137
137
  "files": [
138
138
  "dist"
139
139
  ],
140
- "version": "3.13.3",
140
+ "version": "3.13.5",
141
141
  "packageManager": "pnpm@10.11.0",
142
142
  "repository": {
143
143
  "type": "git",
@@ -175,7 +175,7 @@
175
175
  "dependencies": {
176
176
  "@hookform/error-message": "^2.0.1",
177
177
  "@x15stack/aws": "^1.7.2",
178
- "@x15ventures/x15-ui-library-icons": "^1.1.1",
178
+ "@x15ventures/x15-ui-library-icons": "^1.1.3",
179
179
  "babel-plugin-transform-imports": "^2.0.0",
180
180
  "html-react-parser": "^5.2.17",
181
181
  "jsdom": "^26.1.0",
@@ -195,7 +195,7 @@
195
195
  },
196
196
  "devDependencies": {
197
197
  "@babel/cli": "^7.28.6",
198
- "@babel/core": "^7.29.0",
198
+ "@babel/core": "7.29.6",
199
199
  "@babel/eslint-parser": "^7.28.6",
200
200
  "@babel/eslint-plugin": "^7.27.1",
201
201
  "@babel/plugin-syntax-jsx": "^7.28.6",
@@ -256,7 +256,7 @@
256
256
  "babel-loader": "^9.2.1",
257
257
  "babel-plugin-macros": "^3.1.0",
258
258
  "chromatic": "^11.29.0",
259
- "commitlint": "^20.5.2",
259
+ "commitlint": "^20.5.3",
260
260
  "copy-webpack-plugin": "^12.0.2",
261
261
  "cors": "^2.8.6",
262
262
  "cross-env": "^7.0.3",
@@ -301,32 +301,34 @@
301
301
  "ttab": "^0.9.0",
302
302
  "typescript": "5.5.4",
303
303
  "url-loader": "^4.1.1",
304
- "vitest": "3.1.1"
304
+ "vitest": "3.2.6"
305
305
  },
306
306
  "pnpm": {
307
307
  "overrides": {
308
+ "@babel/plugin-transform-modules-systemjs": "7.29.4",
308
309
  "@tootallnate/once@<3.0.1": ">=3.0.1",
309
- "brace-expansion": ">=5.0.5",
310
+ "brace-expansion": "5.0.6",
311
+ "fast-uri": "3.1.2",
312
+ "form-data": "4.0.6",
310
313
  "flatted": ">=3.4.2",
311
314
  "flatted@<3.4.0": ">=3.4.0",
312
315
  "handlebars": ">=4.7.9",
313
316
  "immutable@>=5.0.0 <5.1.5": ">=5.1.5",
314
- "minimatch": ">=9.0.7",
317
+ "js-yaml": "4.2.0",
318
+ "minimatch": "9.0.7",
315
319
  "path-to-regexp": ">=8.4.0",
316
320
  "picomatch": ">=4.0.4",
317
- "qs": ">=6.14.2",
321
+ "qs": "6.15.2",
318
322
  "rollup": ">=4.59.0",
319
323
  "serialize-javascript": ">=7.0.3",
324
+ "shell-quote": "1.8.4",
320
325
  "storybook": ">=10.2.10",
321
- "undici": ">=7.24.0",
326
+ "undici": "8.5.0",
327
+ "uuid": "11.1.1",
328
+ "vite": "6.4.3",
322
329
  "webpack": ">=5.104.1",
330
+ "ws": "8.21.0",
323
331
  "yaml": ">=1.10.3"
324
- },
325
- "auditConfig": {
326
- "ignoreCves": [
327
- "GHSA-rx8g-88g5-qh64",
328
- "CVE-2025-57352"
329
- ]
330
332
  }
331
333
  }
332
334
  }