@rachelallyson/hero-hook-form 2.6.0 → 2.7.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/index.js CHANGED
@@ -76,7 +76,7 @@ function useFormHelper({
76
76
 
77
77
  // src/components/FormField.tsx
78
78
  import React17 from "react";
79
- import { useWatch as useWatch3 } from "react-hook-form";
79
+ import { get, useWatch as useWatch3 } from "react-hook-form";
80
80
 
81
81
  // src/fields/AutocompleteField.tsx
82
82
  import React from "react";
@@ -432,18 +432,23 @@ function FieldArrayField({
432
432
  }) {
433
433
  const {
434
434
  addButtonText = "Add Item",
435
+ defaultItem,
436
+ enableReordering = false,
435
437
  fields: fieldConfigs,
436
438
  max = 10,
437
439
  min = 0,
438
440
  name,
439
- removeButtonText = "Remove"
441
+ removeButtonText = "Remove",
442
+ renderAddButton,
443
+ renderItem,
444
+ reorderButtonText = { down: "\u2193", up: "\u2191" }
440
445
  } = config;
441
446
  const form = useFormContext3();
442
447
  if (!form || !form.control) {
443
448
  return null;
444
449
  }
445
450
  const { control } = form;
446
- const { append, fields, remove } = useFieldArray({
451
+ const { append, fields, move, remove } = useFieldArray({
447
452
  control,
448
453
  name
449
454
  // FieldArray name
@@ -452,18 +457,22 @@ function FieldArrayField({
452
457
  const canRemove = fields.length > min;
453
458
  const handleAdd = () => {
454
459
  if (canAdd) {
455
- const defaultValues = fieldConfigs.reduce((acc, fieldConfig) => {
456
- const fieldName = fieldConfig.name;
457
- if (fieldConfig.type === "checkbox" || fieldConfig.type === "switch") {
458
- acc[fieldName] = false;
459
- } else if (fieldConfig.type === "slider") {
460
- acc[fieldName] = 0;
461
- } else {
462
- acc[fieldName] = "";
463
- }
464
- return acc;
465
- }, {});
466
- append(defaultValues);
460
+ if (defaultItem) {
461
+ append(defaultItem());
462
+ } else {
463
+ const defaultValues = fieldConfigs.reduce((acc, fieldConfig) => {
464
+ const fieldName = fieldConfig.name;
465
+ if (fieldConfig.type === "checkbox" || fieldConfig.type === "switch") {
466
+ acc[fieldName] = false;
467
+ } else if (fieldConfig.type === "slider") {
468
+ acc[fieldName] = 0;
469
+ } else {
470
+ acc[fieldName] = "";
471
+ }
472
+ return acc;
473
+ }, {});
474
+ append(defaultValues);
475
+ }
467
476
  }
468
477
  };
469
478
  const handleRemove = (index) => {
@@ -471,60 +480,131 @@ function FieldArrayField({
471
480
  remove(index);
472
481
  }
473
482
  };
474
- return /* @__PURE__ */ React8.createElement("div", { className }, /* @__PURE__ */ React8.createElement("div", { className: "space-y-4" }, fields.map((field2, index) => /* @__PURE__ */ React8.createElement(
475
- "div",
476
- {
477
- key: field2.id,
478
- className: "border border-gray-200 rounded-lg p-4 space-y-4"
479
- },
480
- /* @__PURE__ */ React8.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React8.createElement("h4", { className: "text-sm font-medium text-gray-700" }, config.label, " #", index + 1), canRemove && /* @__PURE__ */ React8.createElement(
483
+ const handleMoveUp = (index) => {
484
+ if (index > 0) {
485
+ move(index, index - 1);
486
+ }
487
+ };
488
+ const handleMoveDown = (index) => {
489
+ if (index < fields.length - 1) {
490
+ move(index, index + 1);
491
+ }
492
+ };
493
+ const renderFieldArrayItems = () => {
494
+ return fields.map((field2, index) => {
495
+ const canMoveUp = enableReordering && index > 0;
496
+ const canMoveDown = enableReordering && index < fields.length - 1;
497
+ const itemCanRemove = canRemove;
498
+ const fieldElements = fieldConfigs.map((fieldConfig) => {
499
+ const fieldName = fieldConfig.name;
500
+ const fullPath = `${name}.${index}.${fieldName}`;
501
+ let processedConfig = { ...fieldConfig, name: fullPath };
502
+ if ("dependsOn" in fieldConfig && fieldConfig.dependsOn && typeof fieldConfig.dependsOn === "string") {
503
+ const dependsOnPath = fieldConfig.dependsOn;
504
+ if (!dependsOnPath.startsWith(`${name}.`)) {
505
+ processedConfig = {
506
+ ...processedConfig,
507
+ dependsOn: `${name}.${index}.${dependsOnPath}`,
508
+ // Preserve dependsOnValue if it exists
509
+ ..."dependsOnValue" in fieldConfig && {
510
+ dependsOnValue: fieldConfig.dependsOnValue
511
+ }
512
+ };
513
+ }
514
+ }
515
+ return /* @__PURE__ */ React8.createElement(
516
+ FormField,
517
+ {
518
+ key: `${fieldConfig.name}-${index}`,
519
+ config: processedConfig,
520
+ form,
521
+ submissionState: {
522
+ error: void 0,
523
+ isSubmitted: false,
524
+ isSubmitting: false,
525
+ isSuccess: false
526
+ }
527
+ }
528
+ );
529
+ });
530
+ if (renderItem) {
531
+ return /* @__PURE__ */ React8.createElement(React8.Fragment, { key: field2.id }, renderItem({
532
+ canMoveDown,
533
+ canMoveUp,
534
+ canRemove: itemCanRemove,
535
+ children: /* @__PURE__ */ React8.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, fieldElements),
536
+ field: field2,
537
+ fields,
538
+ index,
539
+ onMoveDown: () => handleMoveDown(index),
540
+ onMoveUp: () => handleMoveUp(index),
541
+ onRemove: () => handleRemove(index)
542
+ }));
543
+ }
544
+ return /* @__PURE__ */ React8.createElement(
545
+ "div",
546
+ {
547
+ key: field2.id,
548
+ className: "border border-gray-200 rounded-lg p-4 space-y-4"
549
+ },
550
+ /* @__PURE__ */ React8.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React8.createElement("h4", { className: "text-sm font-medium text-gray-700" }, config.label, " #", index + 1), /* @__PURE__ */ React8.createElement("div", { className: "flex gap-2" }, enableReordering && /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
551
+ Button2,
552
+ {
553
+ size: "sm",
554
+ variant: "light",
555
+ isDisabled: !canMoveUp,
556
+ onPress: () => handleMoveUp(index),
557
+ "aria-label": `Move ${config.label} ${index + 1} up`
558
+ },
559
+ reorderButtonText.up
560
+ ), /* @__PURE__ */ React8.createElement(
561
+ Button2,
562
+ {
563
+ size: "sm",
564
+ variant: "light",
565
+ isDisabled: !canMoveDown,
566
+ onPress: () => handleMoveDown(index),
567
+ "aria-label": `Move ${config.label} ${index + 1} down`
568
+ },
569
+ reorderButtonText.down
570
+ )), itemCanRemove && /* @__PURE__ */ React8.createElement(
571
+ Button2,
572
+ {
573
+ size: "sm",
574
+ variant: "light",
575
+ color: "danger",
576
+ startContent: "\u{1F5D1}\uFE0F",
577
+ onPress: () => handleRemove(index),
578
+ "aria-label": `${removeButtonText} ${config.label} ${index + 1}`
579
+ },
580
+ removeButtonText
581
+ ))),
582
+ /* @__PURE__ */ React8.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, fieldElements)
583
+ );
584
+ });
585
+ };
586
+ const renderAddButtonElement = () => {
587
+ if (renderAddButton) {
588
+ return renderAddButton({
589
+ canAdd,
590
+ onAdd: handleAdd
591
+ });
592
+ }
593
+ if (!canAdd) {
594
+ return null;
595
+ }
596
+ return /* @__PURE__ */ React8.createElement(
481
597
  Button2,
482
598
  {
483
- size: "sm",
484
- variant: "light",
485
- color: "danger",
486
- startContent: "\u{1F5D1}\uFE0F",
487
- onPress: () => handleRemove(index),
488
- "aria-label": `${removeButtonText} ${config.label} ${index + 1}`
599
+ variant: "bordered",
600
+ startContent: "\u2795",
601
+ onPress: handleAdd,
602
+ className: "w-full"
489
603
  },
490
- removeButtonText
491
- )),
492
- /* @__PURE__ */ React8.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, fieldConfigs.map((fieldConfig) => /* @__PURE__ */ React8.createElement(
493
- FormField,
494
- {
495
- key: `${fieldConfig.name}-${index}`,
496
- config: {
497
- ...fieldConfig,
498
- name: `${name}.${index}.${fieldConfig.name}`
499
- },
500
- form,
501
- submissionState: {
502
- error: void 0,
503
- isSubmitted: false,
504
- isSubmitting: false,
505
- isSuccess: false
506
- }
507
- }
508
- )))
509
- )), canAdd && /* @__PURE__ */ React8.createElement(
510
- Button2,
511
- {
512
- variant: "bordered",
513
- startContent: "\u2795",
514
- onPress: handleAdd,
515
- className: "w-full"
516
- },
517
- addButtonText
518
- ), fields.length === 0 && /* @__PURE__ */ React8.createElement("div", { className: "text-center py-8 text-gray-500" }, /* @__PURE__ */ React8.createElement("p", null, "No ", config.label?.toLowerCase(), " added yet."), /* @__PURE__ */ React8.createElement(
519
- Button2,
520
- {
521
- variant: "bordered",
522
- startContent: "\u2795",
523
- onPress: handleAdd,
524
- className: "mt-2"
525
- },
526
- addButtonText
527
- ))));
604
+ addButtonText
605
+ );
606
+ };
607
+ return /* @__PURE__ */ React8.createElement("div", { className }, /* @__PURE__ */ React8.createElement("div", { className: "space-y-4" }, fields.length > 0 ? renderFieldArrayItems() : /* @__PURE__ */ React8.createElement("div", { className: "text-center py-8 text-gray-500" }, /* @__PURE__ */ React8.createElement("p", null, "No ", config.label?.toLowerCase(), " added yet."), renderAddButtonElement()), fields.length > 0 && renderAddButtonElement()));
528
608
  }
529
609
 
530
610
  // src/fields/FileField.tsx
@@ -1050,7 +1130,7 @@ var FormField = React17.memo(
1050
1130
  return null;
1051
1131
  }
1052
1132
  if (config.dependsOn) {
1053
- const dependentValue = watchedValues[config.dependsOn];
1133
+ const dependentValue = get(watchedValues, config.dependsOn);
1054
1134
  if (config.dependsOnValue !== void 0 && dependentValue !== config.dependsOnValue) {
1055
1135
  return null;
1056
1136
  }
@@ -2334,6 +2414,44 @@ function ZodForm({
2334
2414
  ))));
2335
2415
  }
2336
2416
 
2417
+ // src/components/SimpleForm.tsx
2418
+ import React24 from "react";
2419
+ function SimpleForm({
2420
+ className,
2421
+ defaultValues,
2422
+ field: field2,
2423
+ hideSubmitButton = false,
2424
+ onError,
2425
+ onSubmit,
2426
+ onSuccess,
2427
+ schema,
2428
+ submitButton,
2429
+ subtitle,
2430
+ title
2431
+ }) {
2432
+ return /* @__PURE__ */ React24.createElement(
2433
+ ZodForm,
2434
+ {
2435
+ className,
2436
+ config: {
2437
+ defaultValues,
2438
+ fields: [field2],
2439
+ schema
2440
+ },
2441
+ onError,
2442
+ onSubmit,
2443
+ onSuccess,
2444
+ showResetButton: false,
2445
+ submitButtonText: hideSubmitButton ? "" : "Submit",
2446
+ subtitle,
2447
+ title,
2448
+ submitButtonProps: hideSubmitButton && submitButton ? {
2449
+ style: { display: "none" }
2450
+ } : {}
2451
+ }
2452
+ );
2453
+ }
2454
+
2337
2455
  // src/builders/BasicFormBuilder.ts
2338
2456
  var BasicFormBuilder = class {
2339
2457
  constructor() {
@@ -3742,6 +3860,131 @@ function useMemoizedFieldProps(props, deps) {
3742
3860
  return useMemo2(() => props, deps);
3743
3861
  }
3744
3862
 
3863
+ // src/utils/arraySync.ts
3864
+ function syncArrays(options) {
3865
+ const { current, existing, getId } = options;
3866
+ const existingMap = /* @__PURE__ */ new Map();
3867
+ const currentMap = /* @__PURE__ */ new Map();
3868
+ existing.forEach((item) => {
3869
+ const id = getId(item);
3870
+ if (id !== void 0) {
3871
+ existingMap.set(id, item);
3872
+ }
3873
+ });
3874
+ current.forEach((item) => {
3875
+ const id = getId(item);
3876
+ if (id !== void 0) {
3877
+ currentMap.set(id, item);
3878
+ }
3879
+ });
3880
+ const toDelete = [];
3881
+ existingMap.forEach((item, id) => {
3882
+ if (!currentMap.has(id)) {
3883
+ toDelete.push(item);
3884
+ }
3885
+ });
3886
+ const toUpdate = [];
3887
+ existingMap.forEach((existingItem, id) => {
3888
+ const currentItem = currentMap.get(id);
3889
+ if (currentItem) {
3890
+ toUpdate.push({ current: currentItem, existing: existingItem });
3891
+ }
3892
+ });
3893
+ const toCreate = [];
3894
+ currentMap.forEach((item, id) => {
3895
+ if (!existingMap.has(id)) {
3896
+ toCreate.push(item);
3897
+ }
3898
+ });
3899
+ return {
3900
+ toCreate,
3901
+ toDelete,
3902
+ toUpdate
3903
+ };
3904
+ }
3905
+
3906
+ // src/utils/createFieldArrayCustomConfig.tsx
3907
+ import React25 from "react";
3908
+ import { useFieldArray as useFieldArray2 } from "react-hook-form";
3909
+ import { Button as Button6 } from "@heroui/react";
3910
+ function createFieldArrayCustomConfig(options) {
3911
+ const {
3912
+ className,
3913
+ defaultItem,
3914
+ enableReordering = false,
3915
+ label,
3916
+ max = 10,
3917
+ min = 0,
3918
+ name,
3919
+ renderAddButton,
3920
+ renderItem
3921
+ } = options;
3922
+ return {
3923
+ className,
3924
+ label,
3925
+ name,
3926
+ // ArrayPath is compatible with Path for CustomFieldConfig
3927
+ render: ({ control, errors, form }) => {
3928
+ const { append, fields, move, remove } = useFieldArray2({
3929
+ control,
3930
+ name
3931
+ });
3932
+ const canAdd = fields.length < max;
3933
+ const canRemove = fields.length > min;
3934
+ const handleAdd = () => {
3935
+ if (canAdd) {
3936
+ if (defaultItem) {
3937
+ append(defaultItem());
3938
+ } else {
3939
+ append({});
3940
+ }
3941
+ }
3942
+ };
3943
+ const handleRemove = (index) => {
3944
+ if (canRemove) {
3945
+ remove(index);
3946
+ }
3947
+ };
3948
+ const handleMoveUp = (index) => {
3949
+ if (enableReordering && index > 0) {
3950
+ move(index, index - 1);
3951
+ }
3952
+ };
3953
+ const handleMoveDown = (index) => {
3954
+ if (enableReordering && index < fields.length - 1) {
3955
+ move(index, index + 1);
3956
+ }
3957
+ };
3958
+ return /* @__PURE__ */ React25.createElement("div", { className }, /* @__PURE__ */ React25.createElement("div", { className: "space-y-4" }, fields.map((field2, index) => {
3959
+ const canMoveUp = enableReordering && index > 0;
3960
+ const canMoveDown = enableReordering && index < fields.length - 1;
3961
+ return /* @__PURE__ */ React25.createElement(React25.Fragment, { key: field2.id }, renderItem({
3962
+ canMoveDown,
3963
+ canMoveUp,
3964
+ control,
3965
+ errors,
3966
+ field: field2,
3967
+ fields,
3968
+ form,
3969
+ index,
3970
+ onMoveDown: () => handleMoveDown(index),
3971
+ onMoveUp: () => handleMoveUp(index),
3972
+ onRemove: () => handleRemove(index)
3973
+ }));
3974
+ }), fields.length === 0 && renderAddButton ? /* @__PURE__ */ React25.createElement("div", { className: "text-center py-8 text-gray-500" }, /* @__PURE__ */ React25.createElement("p", null, "No ", label?.toLowerCase() || "items", " added yet."), renderAddButton({ canAdd, onAdd: handleAdd })) : null, fields.length > 0 && renderAddButton ? renderAddButton({ canAdd, onAdd: handleAdd }) : canAdd && /* @__PURE__ */ React25.createElement(
3975
+ Button6,
3976
+ {
3977
+ variant: "bordered",
3978
+ onPress: handleAdd,
3979
+ className: "w-full"
3980
+ },
3981
+ "Add Item"
3982
+ )));
3983
+ },
3984
+ type: "custom"
3985
+ };
3986
+ }
3987
+
3745
3988
  // src/builders/validation-helpers.ts
3746
3989
  import { z as z4 } from "zod";
3747
3990
  var validationPatterns = {
@@ -3915,6 +4158,7 @@ export {
3915
4158
  RadioGroupField,
3916
4159
  SelectField,
3917
4160
  ServerActionForm,
4161
+ SimpleForm,
3918
4162
  SliderField,
3919
4163
  SubmitButton,
3920
4164
  SwitchField,
@@ -3930,6 +4174,7 @@ export {
3930
4174
  createEmailSchema,
3931
4175
  createField,
3932
4176
  createFieldArrayBuilder,
4177
+ createFieldArrayCustomConfig,
3933
4178
  createFieldArrayItemBuilder,
3934
4179
  createFileSchema,
3935
4180
  createFormTestUtils,
@@ -3963,6 +4208,7 @@ export {
3963
4208
  shallowEqual,
3964
4209
  simulateFieldInput,
3965
4210
  simulateFormSubmission,
4211
+ syncArrays,
3966
4212
  throttle,
3967
4213
  useDebouncedFieldValidation,
3968
4214
  useDebouncedValidation,