@bookinglab/booking-ui-react 1.4.0 → 1.6.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/README.md CHANGED
@@ -353,6 +353,7 @@ A form component for collecting contact details (first name, last name, email) a
353
353
  | Prop | Type | Default | Description |
354
354
  |------|------|---------|-------------|
355
355
  | `questions` | `Question[]` | `[]` | Additional questions to render after the contact fields |
356
+ | `initialValues` | `ContactDetailsInitialValues` | `undefined` | Initial values for pre-populating the form |
356
357
  | `onSubmit` | `(values: ContactDetailsValues) => void` | required | Callback fired on valid submission |
357
358
  | `onChange` | `(values: Partial<ContactDetailsValues>, isValid: boolean) => void` | `undefined` | Callback fired on every field change |
358
359
  | `submitLabel` | `string` | `"Submit"` | Text for the submit button |
@@ -399,7 +400,39 @@ interface ContactDetailsFieldSettings {
399
400
  }
400
401
  ```
401
402
 
402
- ### Dynamic Questions
403
+ ### Initial Values
404
+
405
+ Pre-populate the form with existing data using `initialValues`:
406
+
407
+ ```tsx
408
+ <ContactDetailsForm
409
+ onSubmit={handleSubmit}
410
+ initialValues={{
411
+ firstName: 'Sarah',
412
+ lastName: 'Grey',
413
+ email: 'sarah@example.com',
414
+ questions: {
415
+ 2: '5', // select option ID as string
416
+ 7: 25, // number value
417
+ 10: 'Wheelchair access needed',
418
+ 12: true, // checkbox
419
+ },
420
+ }}
421
+ />
422
+ ```
423
+
424
+ ```typescript
425
+ interface ContactDetailsInitialValues {
426
+ firstName?: string;
427
+ lastName?: string;
428
+ email?: string;
429
+ questions?: Record<number, string | number | boolean>;
430
+ }
431
+ ```
432
+
433
+ When `reset()` is called via ref, the form restores to these initial values rather than empty fields.
434
+
435
+
403
436
 
404
437
  Pass additional questions to render after the contact fields:
405
438
 
@@ -448,6 +481,85 @@ const formRef = useRef<ContactDetailsFormRef>(null);
448
481
 
449
482
  ---
450
483
 
484
+ ## ResetPasswordForm
485
+
486
+ A reusable password reset form with current password, new password, and confirm new password fields. Includes built-in validation, customizable submit button text, and programmatic control via ref.
487
+
488
+ ### Props
489
+
490
+ | Prop | Type | Default | Description |
491
+ |------|------|---------|-------------|
492
+ | `onSubmit` | `(values: ResetPasswordFormValues) => void` | required | Called with all 3 values on valid submit |
493
+ | `onChange` | `(values: Partial<ResetPasswordFormValues>, isValid: boolean) => void` | `undefined` | Called on every field change |
494
+ | `submitLabel` | `string` | `"Update Password"` | Text for the submit button |
495
+ | `validateOnBlur` | `boolean` | `true` | Whether to validate fields on blur |
496
+ | `className` | `string` | `""` | Class name for the form element |
497
+ | `classNames` | `ResetPasswordFormClassNames` | `undefined` | Custom class names for styling |
498
+
499
+ ### Fields
500
+
501
+ | Field | Label | Required | Validation |
502
+ |-------|-------|----------|------------|
503
+ | `currentPassword` | Current Password | Yes | required |
504
+ | `newPassword` | New Password | Yes | required |
505
+ | `confirmNewPassword` | Confirm New Password | Yes | required, must match New Password |
506
+
507
+ ### Usage
508
+
509
+ ```tsx
510
+ import { useRef } from 'react';
511
+ import {
512
+ ResetPasswordForm,
513
+ ResetPasswordFormRef,
514
+ ResetPasswordFormValues,
515
+ } from '@bookinglab/booking-ui-react';
516
+
517
+ function MyComponent() {
518
+ const formRef = useRef<ResetPasswordFormRef>(null);
519
+
520
+ const handleSubmit = (values: ResetPasswordFormValues) => {
521
+ console.log('Password reset:', values);
522
+ // { currentPassword: '...', newPassword: '...', confirmNewPassword: '...' }
523
+ };
524
+
525
+ return (
526
+ <>
527
+ <ResetPasswordForm
528
+ ref={formRef}
529
+ onSubmit={handleSubmit}
530
+ submitLabel="Change Password"
531
+ />
532
+ <button onClick={() => formRef.current?.reset()}>Reset</button>
533
+ </>
534
+ );
535
+ }
536
+ ```
537
+
538
+ ### Ref Methods
539
+
540
+ | Method | Description |
541
+ |--------|-------------|
542
+ | `reset()` | Clear all values, errors, and touched state |
543
+ | `setValues(values)` | Programmatically set field values |
544
+
545
+ ### Styling
546
+
547
+ ```tsx
548
+ <ResetPasswordForm
549
+ onSubmit={handleSubmit}
550
+ classNames={{
551
+ fieldWrapper: 'mb-6',
552
+ label: 'text-white font-bold',
553
+ input: 'border-2 border-gray-400 rounded-lg p-3',
554
+ inputError: 'border-red-500',
555
+ errorText: 'text-red-400 text-sm',
556
+ button: 'bg-primary text-white py-3 px-6 rounded-lg',
557
+ }}
558
+ />
559
+ ```
560
+
561
+ ---
562
+
451
563
  ## Requirements
452
564
 
453
565
  - React 17.0.0 or higher
@@ -480,7 +592,12 @@ import type {
480
592
  ContactDetailsQuestionEntry,
481
593
  ContactDetailsAnswerEntry,
482
594
  ContactDetailsFieldSettings,
595
+ ContactDetailsInitialValues,
483
596
  ContactFieldSettings,
597
+ ResetPasswordFormProps,
598
+ ResetPasswordFormRef,
599
+ ResetPasswordFormValues,
600
+ ResetPasswordFormClassNames,
484
601
  } from '@bookinglab/booking-ui-react';
485
602
  ```
486
603
 
package/dist/index.d.cts CHANGED
@@ -168,12 +168,27 @@ interface ContactDetailsFieldSettings {
168
168
  lastName?: ContactFieldSettings;
169
169
  email?: ContactFieldSettings;
170
170
  }
171
+ /**
172
+ * Initial values for pre-populating the form
173
+ */
174
+ interface ContactDetailsInitialValues {
175
+ /** Initial first name value */
176
+ firstName?: string;
177
+ /** Initial last name value */
178
+ lastName?: string;
179
+ /** Initial email value */
180
+ email?: string;
181
+ /** Initial question values keyed by question ID */
182
+ questions?: Record<number, string | number | boolean>;
183
+ }
171
184
  /**
172
185
  * Props for the ContactDetailsForm component
173
186
  */
174
187
  interface ContactDetailsFormProps {
175
188
  /** Additional questions to render after the contact fields */
176
189
  questions?: Question[];
190
+ /** Initial values for pre-populating the form fields */
191
+ initialValues?: ContactDetailsInitialValues;
177
192
  /** Callback fired when form is submitted with valid values */
178
193
  onSubmit: (values: ContactDetailsValues) => void;
179
194
  /** Callback fired on every field change */
@@ -203,6 +218,58 @@ interface ContactDetailsFormRef {
203
218
  }>) => void;
204
219
  }
205
220
 
221
+ /**
222
+ * Values emitted by the ResetPasswordForm on submit
223
+ */
224
+ interface ResetPasswordFormValues {
225
+ currentPassword: string;
226
+ newPassword: string;
227
+ confirmNewPassword: string;
228
+ }
229
+ /**
230
+ * Custom class names for styling ResetPasswordForm elements
231
+ */
232
+ interface ResetPasswordFormClassNames {
233
+ /** Wrapper for each form field */
234
+ fieldWrapper?: string;
235
+ /** All labels */
236
+ label?: string;
237
+ /** All input elements */
238
+ input?: string;
239
+ /** Input elements in error state */
240
+ inputError?: string;
241
+ /** Error message text */
242
+ errorText?: string;
243
+ /** Submit button */
244
+ button?: string;
245
+ }
246
+ /**
247
+ * Props for the ResetPasswordForm component
248
+ */
249
+ interface ResetPasswordFormProps {
250
+ /** Called with all 3 password values on valid submit */
251
+ onSubmit: (values: ResetPasswordFormValues) => void;
252
+ /** Called on every field change with current values and validity */
253
+ onChange?: (values: Partial<ResetPasswordFormValues>, isValid: boolean) => void;
254
+ /** Whether to validate on blur (default: true) */
255
+ validateOnBlur?: boolean;
256
+ /** Text for the submit button (default: "Update Password") */
257
+ submitLabel?: string;
258
+ /** Class name for the form element */
259
+ className?: string;
260
+ /** Custom class names for styling individual elements */
261
+ classNames?: ResetPasswordFormClassNames;
262
+ }
263
+ /**
264
+ * Imperative handle methods exposed via ref
265
+ */
266
+ interface ResetPasswordFormRef {
267
+ /** Reset all form values and errors */
268
+ reset: () => void;
269
+ /** Programmatically set form values */
270
+ setValues: (values: Partial<ResetPasswordFormValues>) => void;
271
+ }
272
+
206
273
  interface QuestionOption {
207
274
  id: number;
208
275
  name: string;
@@ -340,6 +407,26 @@ declare const RegistrationForm: React.ForwardRefExoticComponent<RegistrationForm
340
407
  */
341
408
  declare const ContactDetailsForm: React.ForwardRefExoticComponent<ContactDetailsFormProps & React.RefAttributes<ContactDetailsFormRef>>;
342
409
 
410
+ /**
411
+ * A reusable, accessible reset-password form component.
412
+ *
413
+ * Features:
414
+ * - Three password fields: Current, New, Confirm New
415
+ * - Validation: all required; confirm must match new password
416
+ * - forwardRef for programmatic reset()/setValues()
417
+ * - onChange callback with live validity
418
+ * - Granular styling via classNames prop
419
+ *
420
+ * @example
421
+ * ```tsx
422
+ * <ResetPasswordForm
423
+ * onSubmit={(values) => console.log(values)}
424
+ * submitLabel="Change Password"
425
+ * />
426
+ * ```
427
+ */
428
+ declare const ResetPasswordForm: React.ForwardRefExoticComponent<ResetPasswordFormProps & React.RefAttributes<ResetPasswordFormRef>>;
429
+
343
430
  /**
344
431
  * Validation helper functions for form fields
345
432
  * Each validator returns an error message string if invalid, or null if valid
@@ -373,4 +460,4 @@ declare function minLen(min: number): (value: string) => string | null;
373
460
  */
374
461
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
375
462
 
376
- export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, compose, email, minLen, phone, required, ukPostcode };
463
+ export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsInitialValues, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, ResetPasswordForm, type ResetPasswordFormClassNames, type ResetPasswordFormProps, type ResetPasswordFormRef, type ResetPasswordFormValues, compose, email, minLen, phone, required, ukPostcode };
package/dist/index.d.ts CHANGED
@@ -168,12 +168,27 @@ interface ContactDetailsFieldSettings {
168
168
  lastName?: ContactFieldSettings;
169
169
  email?: ContactFieldSettings;
170
170
  }
171
+ /**
172
+ * Initial values for pre-populating the form
173
+ */
174
+ interface ContactDetailsInitialValues {
175
+ /** Initial first name value */
176
+ firstName?: string;
177
+ /** Initial last name value */
178
+ lastName?: string;
179
+ /** Initial email value */
180
+ email?: string;
181
+ /** Initial question values keyed by question ID */
182
+ questions?: Record<number, string | number | boolean>;
183
+ }
171
184
  /**
172
185
  * Props for the ContactDetailsForm component
173
186
  */
174
187
  interface ContactDetailsFormProps {
175
188
  /** Additional questions to render after the contact fields */
176
189
  questions?: Question[];
190
+ /** Initial values for pre-populating the form fields */
191
+ initialValues?: ContactDetailsInitialValues;
177
192
  /** Callback fired when form is submitted with valid values */
178
193
  onSubmit: (values: ContactDetailsValues) => void;
179
194
  /** Callback fired on every field change */
@@ -203,6 +218,58 @@ interface ContactDetailsFormRef {
203
218
  }>) => void;
204
219
  }
205
220
 
221
+ /**
222
+ * Values emitted by the ResetPasswordForm on submit
223
+ */
224
+ interface ResetPasswordFormValues {
225
+ currentPassword: string;
226
+ newPassword: string;
227
+ confirmNewPassword: string;
228
+ }
229
+ /**
230
+ * Custom class names for styling ResetPasswordForm elements
231
+ */
232
+ interface ResetPasswordFormClassNames {
233
+ /** Wrapper for each form field */
234
+ fieldWrapper?: string;
235
+ /** All labels */
236
+ label?: string;
237
+ /** All input elements */
238
+ input?: string;
239
+ /** Input elements in error state */
240
+ inputError?: string;
241
+ /** Error message text */
242
+ errorText?: string;
243
+ /** Submit button */
244
+ button?: string;
245
+ }
246
+ /**
247
+ * Props for the ResetPasswordForm component
248
+ */
249
+ interface ResetPasswordFormProps {
250
+ /** Called with all 3 password values on valid submit */
251
+ onSubmit: (values: ResetPasswordFormValues) => void;
252
+ /** Called on every field change with current values and validity */
253
+ onChange?: (values: Partial<ResetPasswordFormValues>, isValid: boolean) => void;
254
+ /** Whether to validate on blur (default: true) */
255
+ validateOnBlur?: boolean;
256
+ /** Text for the submit button (default: "Update Password") */
257
+ submitLabel?: string;
258
+ /** Class name for the form element */
259
+ className?: string;
260
+ /** Custom class names for styling individual elements */
261
+ classNames?: ResetPasswordFormClassNames;
262
+ }
263
+ /**
264
+ * Imperative handle methods exposed via ref
265
+ */
266
+ interface ResetPasswordFormRef {
267
+ /** Reset all form values and errors */
268
+ reset: () => void;
269
+ /** Programmatically set form values */
270
+ setValues: (values: Partial<ResetPasswordFormValues>) => void;
271
+ }
272
+
206
273
  interface QuestionOption {
207
274
  id: number;
208
275
  name: string;
@@ -340,6 +407,26 @@ declare const RegistrationForm: React.ForwardRefExoticComponent<RegistrationForm
340
407
  */
341
408
  declare const ContactDetailsForm: React.ForwardRefExoticComponent<ContactDetailsFormProps & React.RefAttributes<ContactDetailsFormRef>>;
342
409
 
410
+ /**
411
+ * A reusable, accessible reset-password form component.
412
+ *
413
+ * Features:
414
+ * - Three password fields: Current, New, Confirm New
415
+ * - Validation: all required; confirm must match new password
416
+ * - forwardRef for programmatic reset()/setValues()
417
+ * - onChange callback with live validity
418
+ * - Granular styling via classNames prop
419
+ *
420
+ * @example
421
+ * ```tsx
422
+ * <ResetPasswordForm
423
+ * onSubmit={(values) => console.log(values)}
424
+ * submitLabel="Change Password"
425
+ * />
426
+ * ```
427
+ */
428
+ declare const ResetPasswordForm: React.ForwardRefExoticComponent<ResetPasswordFormProps & React.RefAttributes<ResetPasswordFormRef>>;
429
+
343
430
  /**
344
431
  * Validation helper functions for form fields
345
432
  * Each validator returns an error message string if invalid, or null if valid
@@ -373,4 +460,4 @@ declare function minLen(min: number): (value: string) => string | null;
373
460
  */
374
461
  declare function compose(...validators: Array<(value: string) => string | null>): (value: string) => string | null;
375
462
 
376
- export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, compose, email, minLen, phone, required, ukPostcode };
463
+ export { BookingForm, type BookingFormClassNames, type BookingFormProps, type BookingUIConfig, type ContactDetailsAnswerEntry, type ContactDetailsFieldSettings, ContactDetailsForm, type ContactDetailsFormClassNames, type ContactDetailsFormProps, type ContactDetailsFormRef, type ContactDetailsInitialValues, type ContactDetailsQuestionEntry, type ContactDetailsValues, type ContactFieldSettings, type FieldConfig, type FieldOption, type FormErrors, type FormValues, type Question, type QuestionOption, type QuestionSettings, RegistrationForm, type RegistrationFormClassNames, type RegistrationFormErrors, type RegistrationFormProps, type RegistrationFormRef, type RegistrationFormValues, ResetPasswordForm, type ResetPasswordFormClassNames, type ResetPasswordFormProps, type ResetPasswordFormRef, type ResetPasswordFormValues, compose, email, minLen, phone, required, ukPostcode };
package/dist/index.js CHANGED
@@ -674,6 +674,7 @@ var ContactDetailsForm = react.forwardRef(
674
674
  questions = [],
675
675
  onSubmit,
676
676
  onChange: _onChange,
677
+ initialValues,
677
678
  validateOnBlur = true,
678
679
  submitLabel = "Submit",
679
680
  className = "",
@@ -681,12 +682,19 @@ var ContactDetailsForm = react.forwardRef(
681
682
  fieldSettings = {}
682
683
  }, ref) => {
683
684
  const formId = react.useId();
684
- const [firstName, setFirstName] = react.useState("");
685
- const [lastName, setLastName] = react.useState("");
686
- const [emailValue, setEmailValue] = react.useState("");
685
+ const [firstName, setFirstName] = react.useState(initialValues?.firstName || "");
686
+ const [lastName, setLastName] = react.useState(initialValues?.lastName || "");
687
+ const [emailValue, setEmailValue] = react.useState(initialValues?.email || "");
687
688
  const [contactErrors, setContactErrors] = react.useState({});
688
689
  const [contactTouched, setContactTouched] = react.useState({});
689
- const [questionValues, setQuestionValues] = react.useState({});
690
+ const [questionValues, setQuestionValues] = react.useState(() => {
691
+ if (!initialValues?.questions) return {};
692
+ const init = {};
693
+ for (const [key, val] of Object.entries(initialValues.questions)) {
694
+ init[Number(key)] = val;
695
+ }
696
+ return init;
697
+ });
690
698
  const [questionErrors, setQuestionErrors] = react.useState({});
691
699
  const [questionTouched, setQuestionTouched] = react.useState({});
692
700
  const getFieldRequired = (name) => {
@@ -850,12 +858,20 @@ var ContactDetailsForm = react.forwardRef(
850
858
  }, [contactFields, questions, validateAllContacts, validateAllQuestions, onSubmit, buildOutput]);
851
859
  react.useImperativeHandle(ref, () => ({
852
860
  reset: () => {
853
- setFirstName("");
854
- setLastName("");
855
- setEmailValue("");
861
+ setFirstName(initialValues?.firstName || "");
862
+ setLastName(initialValues?.lastName || "");
863
+ setEmailValue(initialValues?.email || "");
856
864
  setContactErrors({});
857
865
  setContactTouched({});
858
- setQuestionValues({});
866
+ if (initialValues?.questions) {
867
+ const init = {};
868
+ for (const [key, val] of Object.entries(initialValues.questions)) {
869
+ init[Number(key)] = val;
870
+ }
871
+ setQuestionValues(init);
872
+ } else {
873
+ setQuestionValues({});
874
+ }
859
875
  setQuestionErrors({});
860
876
  setQuestionTouched({});
861
877
  },
@@ -864,7 +880,7 @@ var ContactDetailsForm = react.forwardRef(
864
880
  if (vals.lastName !== void 0) setLastName(vals.lastName);
865
881
  if (vals.email !== void 0) setEmailValue(vals.email);
866
882
  }
867
- }), []);
883
+ }), [initialValues]);
868
884
  const styles = {
869
885
  fieldWrapper: classNames.fieldWrapper || "mb-4",
870
886
  label: classNames.label || "block text-sm font-medium mb-1",
@@ -1072,10 +1088,182 @@ var ContactDetailsForm = react.forwardRef(
1072
1088
  }
1073
1089
  );
1074
1090
  ContactDetailsForm.displayName = "ContactDetailsForm";
1091
+ var FIELDS = [
1092
+ { name: "currentPassword", label: "Current Password" },
1093
+ { name: "newPassword", label: "New Password" },
1094
+ { name: "confirmNewPassword", label: "Confirm New Password" }
1095
+ ];
1096
+ var ResetPasswordForm = react.forwardRef(
1097
+ ({
1098
+ onSubmit,
1099
+ onChange,
1100
+ validateOnBlur = true,
1101
+ submitLabel = "Update Password",
1102
+ className = "",
1103
+ classNames = {}
1104
+ }, ref) => {
1105
+ const formId = react.useId();
1106
+ const [values, setValues] = react.useState({
1107
+ currentPassword: "",
1108
+ newPassword: "",
1109
+ confirmNewPassword: ""
1110
+ });
1111
+ const [errors, setErrors] = react.useState({});
1112
+ const [touched, setTouched] = react.useState({});
1113
+ const validateField = react.useCallback(
1114
+ (name, val, allValues) => {
1115
+ if (!val || val.trim() === "") return "This field is required";
1116
+ if (name === "confirmNewPassword" && val !== allValues.newPassword) {
1117
+ return "Passwords do not match";
1118
+ }
1119
+ return null;
1120
+ },
1121
+ []
1122
+ );
1123
+ const checkIsValid = react.useCallback(
1124
+ (v) => {
1125
+ for (const field of FIELDS) {
1126
+ if (validateField(field.name, v[field.name], v)) return false;
1127
+ }
1128
+ return true;
1129
+ },
1130
+ [validateField]
1131
+ );
1132
+ const handleChange = react.useCallback(
1133
+ (name, val) => {
1134
+ const newValues = { ...values, [name]: val };
1135
+ setValues(newValues);
1136
+ if (touched[name]) {
1137
+ const error = validateField(name, val, newValues);
1138
+ if (!error) {
1139
+ setErrors((prev) => {
1140
+ const next = { ...prev };
1141
+ delete next[name];
1142
+ return next;
1143
+ });
1144
+ }
1145
+ }
1146
+ if (name === "newPassword" && touched.confirmNewPassword) {
1147
+ const confirmError = validateField("confirmNewPassword", newValues.confirmNewPassword, newValues);
1148
+ if (confirmError) {
1149
+ setErrors((prev) => ({ ...prev, confirmNewPassword: confirmError }));
1150
+ } else {
1151
+ setErrors((prev) => {
1152
+ const next = { ...prev };
1153
+ delete next.confirmNewPassword;
1154
+ return next;
1155
+ });
1156
+ }
1157
+ }
1158
+ onChange?.(newValues, checkIsValid(newValues));
1159
+ },
1160
+ [values, touched, validateField, onChange, checkIsValid]
1161
+ );
1162
+ const handleBlur = react.useCallback(
1163
+ (name) => {
1164
+ setTouched((prev) => ({ ...prev, [name]: true }));
1165
+ if (validateOnBlur) {
1166
+ const error = validateField(name, values[name], values);
1167
+ if (error) {
1168
+ setErrors((prev) => ({ ...prev, [name]: error }));
1169
+ } else {
1170
+ setErrors((prev) => {
1171
+ const next = { ...prev };
1172
+ delete next[name];
1173
+ return next;
1174
+ });
1175
+ }
1176
+ }
1177
+ },
1178
+ [validateOnBlur, values, validateField]
1179
+ );
1180
+ const handleSubmit = react.useCallback(
1181
+ (e) => {
1182
+ e.preventDefault();
1183
+ const allTouched = {};
1184
+ const newErrors = {};
1185
+ let isValid = true;
1186
+ for (const field of FIELDS) {
1187
+ allTouched[field.name] = true;
1188
+ const error = validateField(field.name, values[field.name], values);
1189
+ if (error) {
1190
+ newErrors[field.name] = error;
1191
+ isValid = false;
1192
+ }
1193
+ }
1194
+ setTouched(allTouched);
1195
+ setErrors(newErrors);
1196
+ if (isValid) onSubmit(values);
1197
+ },
1198
+ [values, validateField, onSubmit]
1199
+ );
1200
+ react.useImperativeHandle(ref, () => ({
1201
+ reset: () => {
1202
+ setValues({ currentPassword: "", newPassword: "", confirmNewPassword: "" });
1203
+ setErrors({});
1204
+ setTouched({});
1205
+ },
1206
+ setValues: (newVals) => {
1207
+ setValues((prev) => {
1208
+ const merged = { ...prev };
1209
+ for (const [key, value] of Object.entries(newVals)) {
1210
+ if (value !== void 0) {
1211
+ merged[key] = value;
1212
+ }
1213
+ }
1214
+ return merged;
1215
+ });
1216
+ }
1217
+ }), []);
1218
+ const styles = {
1219
+ fieldWrapper: classNames.fieldWrapper || "mb-4",
1220
+ label: classNames.label || "block text-sm font-medium mb-1",
1221
+ input: classNames.input || "w-full px-3 py-2 border rounded-md",
1222
+ inputError: classNames.inputError || "border-red-500",
1223
+ errorText: classNames.errorText || "mt-1 text-xs text-red-600",
1224
+ button: classNames.button || "w-full mt-4 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
1225
+ };
1226
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className, noValidate: true, children: [
1227
+ FIELDS.map((field) => {
1228
+ const fieldId = `${formId}-${field.name}`;
1229
+ const errorId = `${fieldId}-error`;
1230
+ const value = values[field.name];
1231
+ const error = errors[field.name];
1232
+ const isTouched = touched[field.name];
1233
+ const showError = isTouched && error;
1234
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.fieldWrapper, children: [
1235
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: fieldId, className: styles.label, children: [
1236
+ field.label,
1237
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500 ml-1", "aria-hidden": "true", children: "*" })
1238
+ ] }),
1239
+ /* @__PURE__ */ jsxRuntime.jsx(
1240
+ "input",
1241
+ {
1242
+ id: fieldId,
1243
+ name: field.name,
1244
+ type: "password",
1245
+ value,
1246
+ onChange: (e) => handleChange(field.name, e.target.value),
1247
+ onBlur: () => handleBlur(field.name),
1248
+ className: `${styles.input} ${showError ? styles.inputError : ""}`,
1249
+ "aria-invalid": showError ? "true" : "false",
1250
+ "aria-describedby": showError ? errorId : void 0,
1251
+ "aria-required": "true"
1252
+ }
1253
+ ),
1254
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { id: errorId, className: styles.errorText, role: "alert", children: error })
1255
+ ] }, field.name);
1256
+ }),
1257
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: styles.button, children: submitLabel })
1258
+ ] });
1259
+ }
1260
+ );
1261
+ ResetPasswordForm.displayName = "ResetPasswordForm";
1075
1262
 
1076
1263
  exports.BookingForm = BookingForm;
1077
1264
  exports.ContactDetailsForm = ContactDetailsForm;
1078
1265
  exports.RegistrationForm = RegistrationForm;
1266
+ exports.ResetPasswordForm = ResetPasswordForm;
1079
1267
  exports.compose = compose;
1080
1268
  exports.email = email;
1081
1269
  exports.minLen = minLen;