@rachelallyson/hero-hook-form 2.10.0 → 2.12.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/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.12.0] - 2026-01-28
6
+
7
+ ### Added
8
+
9
+ - **Custom option layout for autocomplete (renderItem)** – `FormFieldHelpers.autocomplete()` and the builder chain accept an optional 6th param `options?: { renderItem?: (item) => ReactNode }` so each option can show custom content (e.g. name + email + phone). Config supports `renderItem`; `FormField`, `ServerActionForm`, and `AdvancedFormBuilder` pass it through; `AutocompleteField` wraps custom children in `AutocompleteItem` so HeroUI receives valid listbox items. Use with static options or with `getOptions` for dynamic items + custom layout.
10
+ - **StringFieldConfig.renderItem** – Optional `renderItem?: (item: { label: string; value: string | number }) => ReactNode` for autocomplete fields.
11
+ - **Component tests** – Autocomplete: `renderItem` config and render (static and getOptions + renderItem).
12
+
13
+ ### Fixed
14
+
15
+ - **AutocompleteField custom children** – Custom `children` (renderItem) are now wrapped in `AutocompleteItem` before passing to HeroUI Autocomplete so the listbox receives valid items and the form renders correctly.
16
+
17
+ ## [2.11.0] - 2026-01-28
18
+
19
+ ### Added
20
+
21
+ - **Dynamic options for autocomplete** – `FormFieldHelpers.autocomplete()` and the builder chain now accept either a static options array or a getter function `() => options`. Use a getter for API-driven autocomplete (e.g. PCO Person, search-as-you-type): the getter is called each render so items stay in sync with state. Config supports `getOptions`; `FormField`, `ServerActionForm`, and `AdvancedFormBuilder` resolve items from `getOptions()` when present, else `options`. No need for `FormFieldHelpers.custom` when you only need dynamic items.
22
+
23
+ ### Changed
24
+
25
+ - **FormFieldHelpers.autocomplete** – Third parameter can be `options[]` or `() => options[]`; JSDoc documents dynamic usage with `onInputChange`.
26
+ - **StringFieldConfig** – Added optional `getOptions?: () => { label: string; value: string | number }[]` for autocomplete fields.
27
+ - **AutocompleteField JSDoc** – Notes dynamic options via getter + `onInputChange`.
28
+
5
29
  ## [2.10.0] - 2026-01-28
6
30
 
7
31
  ### Added
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import React$1, { ComponentProps } from 'react';
1
+ import React$1, { ComponentProps, ReactNode } from 'react';
2
2
  import { Button } from '@heroui/react';
3
3
  import * as react_hook_form from 'react-hook-form';
4
4
  import { FieldValues, Path, RegisterOptions, ArrayPath, Control, UseFormReturn, FieldErrors, UseFormProps, SubmitHandler, DefaultValues, UseFormSetError, FieldPath, FieldArrayWithId } from 'react-hook-form';
@@ -80,10 +80,21 @@ interface StringFieldConfig<TFieldValues extends FieldValues> extends BaseFormFi
80
80
  textareaProps?: TextareaPassthroughProps;
81
81
  selectProps?: SelectPassthroughProps;
82
82
  autocompleteProps?: AutocompletePassthroughProps;
83
+ /** Static options for autocomplete/select. Omit when using getOptions for dynamic items. */
83
84
  options?: {
84
85
  label: string;
85
86
  value: string | number;
86
87
  }[];
88
+ /** Dynamic options for autocomplete: called each render to get current items (e.g. from API/state). */
89
+ getOptions?: () => {
90
+ label: string;
91
+ value: string | number;
92
+ }[];
93
+ /** Custom render for each autocomplete option (e.g. name + email + phone). When provided, used instead of default label-only. */
94
+ renderItem?: (item: {
95
+ label: string;
96
+ value: string | number;
97
+ }) => ReactNode;
87
98
  }
88
99
  interface BooleanFieldConfig<TFieldValues extends FieldValues> extends BaseFormFieldConfig<TFieldValues> {
89
100
  type: "checkbox" | "switch";
@@ -635,7 +646,9 @@ type AutocompleteFieldProps<TFieldValues extends FieldValues, TValue extends str
635
646
  *
636
647
  * This component provides a type-safe autocomplete field with validation support,
637
648
  * error handling, and accessibility features. It supports both static option lists
638
- * and async loading via the items prop or children render function.
649
+ * and async loading via the items prop or children render function. For dynamic
650
+ * options (e.g. API search), use FormFieldHelpers.autocomplete with a getter:
651
+ * () => people.map(p => ({ label: p.name, value: p.id })) and onInputChange to fetch.
639
652
  *
640
653
  * @template TFieldValues - The form data type
641
654
  * @template TValue - The value type for the autocomplete field (string or number)
@@ -2424,12 +2437,21 @@ declare class BasicFormBuilder<T extends FieldValues> {
2424
2437
  value: string | number;
2425
2438
  }[]): this;
2426
2439
  /**
2427
- * Add an autocomplete field
2440
+ * Add an autocomplete field (static options array or dynamic getOptions getter).
2441
+ * Optional options.renderItem for custom option layout (e.g. name + email + phone).
2428
2442
  */
2429
2443
  autocomplete(name: Path<T>, label: string, items: {
2430
2444
  label: string;
2431
2445
  value: string | number;
2432
- }[], placeholder?: string): this;
2446
+ }[] | (() => {
2447
+ label: string;
2448
+ value: string | number;
2449
+ }[]), placeholder?: string, options?: {
2450
+ renderItem?: (item: {
2451
+ label: string;
2452
+ value: string | number;
2453
+ }) => React$1.ReactNode;
2454
+ }): this;
2433
2455
  /**
2434
2456
  * Add a checkbox field
2435
2457
  */
@@ -2530,27 +2552,42 @@ declare function inputHelper<T extends FieldValues>(name: Path<T>, label: string
2530
2552
  declare function inputHelper<T extends FieldValues>(name: Path<T>, label: string, type: "text" | "email" | "tel" | "password", inputProps: InputPassthroughProps): ZodFormFieldConfig<T>;
2531
2553
  declare const FormFieldHelpers: {
2532
2554
  /**
2533
- * Create an autocomplete field
2555
+ * Create an autocomplete field with static or dynamic options.
2556
+ *
2557
+ * Pass an array for a fixed list, or a getter function for dynamic/API-driven options
2558
+ * (e.g. search-as-you-type). The getter is called each render so it sees current state;
2559
+ * use with autocompleteProps.onInputChange to fetch options when the user types.
2534
2560
  *
2535
2561
  * @example
2536
2562
  * ```tsx
2537
- * // Simple autocomplete
2538
- * FormFieldHelpers.autocomplete("country", "Country", options)
2563
+ * // Static
2564
+ * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries", { allowsCustomValue: true })
2539
2565
  *
2540
- * // With placeholder
2541
- * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries")
2566
+ * // Dynamic (e.g. PCO Person)
2567
+ * const [people, setPeople] = useState([]);
2568
+ * FormFieldHelpers.autocomplete("personId", "Person", () => people.map(p => ({ label: p.name, value: p.id })), "Search people", {
2569
+ * onInputChange: (q) => fetchPeople(q).then(setPeople),
2570
+ * })
2542
2571
  *
2543
- * // With full customization
2544
- * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries", {
2545
- * classNames: { base: "custom-autocomplete" },
2546
- * allowsCustomValue: true
2572
+ * // Custom option layout (e.g. name + email + phone per option)
2573
+ * FormFieldHelpers.autocomplete("personId", "Person", getOptions, "Search", undefined, {
2574
+ * renderItem: (item) => <div><strong>{item.label}</strong><br /><small>{getSubtitle(item.value)}</small></div>,
2547
2575
  * })
2548
2576
  * ```
2549
2577
  */
2550
2578
  autocomplete: <T extends FieldValues>(name: Path<T>, label: string, items: {
2551
2579
  label: string;
2552
2580
  value: string | number;
2553
- }[], placeholder?: string, autocompleteProps?: AutocompletePassthroughProps) => ZodFormFieldConfig<T>;
2581
+ }[] | (() => {
2582
+ label: string;
2583
+ value: string | number;
2584
+ }[]), placeholder?: string, autocompleteProps?: AutocompletePassthroughProps, options?: {
2585
+ /** Custom render for each option (e.g. name + email + phone). When provided, used instead of default label-only. */
2586
+ renderItem?: (item: {
2587
+ label: string;
2588
+ value: string | number;
2589
+ }) => React$1.ReactNode;
2590
+ }) => ZodFormFieldConfig<T>;
2554
2591
  /**
2555
2592
  * Create a checkbox field
2556
2593
  *
@@ -2988,11 +3025,19 @@ type FieldCreationParams<T extends FieldValues> = {
2988
3025
  type: "autocomplete";
2989
3026
  name: Path<T>;
2990
3027
  label: string;
2991
- options: {
3028
+ options?: {
3029
+ label: string;
3030
+ value: string | number;
3031
+ }[];
3032
+ getOptions?: () => {
2992
3033
  label: string;
2993
3034
  value: string | number;
2994
3035
  }[];
2995
3036
  props?: Record<string, unknown>;
3037
+ renderItem?: (item: {
3038
+ label: string;
3039
+ value: string | number;
3040
+ }) => React$1.ReactNode;
2996
3041
  } | {
2997
3042
  type: "checkbox";
2998
3043
  name: Path<T>;
package/dist/index.js CHANGED
@@ -338,7 +338,16 @@ function AutocompleteField(props) {
338
338
  inputValue: shouldShowInputValue ? field2.value ?? "" : void 0,
339
339
  items
340
340
  },
341
- children ? children : (item) => /* @__PURE__ */ React.createElement(
341
+ children ? (item) => /* @__PURE__ */ React.createElement(
342
+ AutocompleteItem,
343
+ {
344
+ key: String(item.value),
345
+ textValue: String(item.value),
346
+ description: item.description,
347
+ isDisabled: item.disabled
348
+ },
349
+ children(item)
350
+ ) : (item) => /* @__PURE__ */ React.createElement(
342
351
  AutocompleteItem,
343
352
  {
344
353
  key: String(item.value),
@@ -2335,6 +2344,8 @@ function FormFieldComponent({
2335
2344
  );
2336
2345
  }
2337
2346
  case "autocomplete": {
2347
+ const autocompleteOptions = "getOptions" in fieldConfig && typeof fieldConfig.getOptions === "function" ? fieldConfig.getOptions() : "options" in fieldConfig && fieldConfig.options ? fieldConfig.options : [];
2348
+ const autocompleteRenderItem = "renderItem" in fieldConfig && typeof fieldConfig.renderItem === "function" ? (item) => /* @__PURE__ */ React19.createElement(React19.Fragment, null, fieldConfig.renderItem(item)) : void 0;
2338
2349
  return /* @__PURE__ */ React19.createElement(
2339
2350
  AutocompleteField,
2340
2351
  {
@@ -2342,11 +2353,12 @@ function FormFieldComponent({
2342
2353
  name: fieldConfig.name,
2343
2354
  control,
2344
2355
  defaultValue: "defaultValue" in fieldConfig ? fieldConfig.defaultValue : void 0,
2345
- items: ("options" in fieldConfig && fieldConfig.options ? fieldConfig.options : []).map((opt) => ({
2356
+ items: autocompleteOptions.map((opt) => ({
2346
2357
  label: opt.label,
2347
2358
  value: String(opt.value)
2348
2359
  })),
2349
- autocompleteProps: "autocompleteProps" in fieldConfig ? fieldConfig.autocompleteProps : void 0
2360
+ autocompleteProps: "autocompleteProps" in fieldConfig ? fieldConfig.autocompleteProps : void 0,
2361
+ children: autocompleteRenderItem
2350
2362
  }
2351
2363
  );
2352
2364
  }
@@ -3120,10 +3132,11 @@ function ServerActionField({
3120
3132
  }
3121
3133
  case "autocomplete": {
3122
3134
  const stringConfig = fieldConfig;
3123
- const items = stringConfig.options?.map((opt) => ({
3135
+ const rawItems = typeof stringConfig.getOptions === "function" ? stringConfig.getOptions() : stringConfig.options ?? [];
3136
+ const items = rawItems.map((opt) => ({
3124
3137
  label: opt.label,
3125
3138
  value: String(opt.value)
3126
- })) || [];
3139
+ }));
3127
3140
  return /* @__PURE__ */ React21.createElement(
3128
3141
  Autocomplete,
3129
3142
  {
@@ -3143,7 +3156,7 @@ function ServerActionField({
3143
3156
  onInputChange: setValue,
3144
3157
  items
3145
3158
  },
3146
- items.map((item) => /* @__PURE__ */ React21.createElement(AutocompleteItem, { key: String(item.value) }, item.label))
3159
+ items.map((item) => /* @__PURE__ */ React21.createElement(AutocompleteItem, { key: String(item.value) }, typeof stringConfig.renderItem === "function" ? stringConfig.renderItem(item) : item.label))
3147
3160
  );
3148
3161
  }
3149
3162
  case "slider": {
@@ -4048,14 +4061,17 @@ var BasicFormBuilder = class {
4048
4061
  return this;
4049
4062
  }
4050
4063
  /**
4051
- * Add an autocomplete field
4064
+ * Add an autocomplete field (static options array or dynamic getOptions getter).
4065
+ * Optional options.renderItem for custom option layout (e.g. name + email + phone).
4052
4066
  */
4053
- autocomplete(name, label, items, placeholder) {
4067
+ autocomplete(name, label, items, placeholder, options) {
4068
+ const isGetter = typeof items === "function";
4054
4069
  this.fields.push({
4055
4070
  autocompleteProps: placeholder ? { placeholder } : void 0,
4056
4071
  label,
4057
4072
  name,
4058
- options: items,
4073
+ ...isGetter ? { getOptions: items } : { options: items },
4074
+ ...options?.renderItem && { renderItem: options.renderItem },
4059
4075
  type: "autocomplete"
4060
4076
  });
4061
4077
  return this;
@@ -4132,33 +4148,43 @@ function inputHelper(name, label, typeOrProps, inputProps) {
4132
4148
  }
4133
4149
  var FormFieldHelpers = {
4134
4150
  /**
4135
- * Create an autocomplete field
4151
+ * Create an autocomplete field with static or dynamic options.
4152
+ *
4153
+ * Pass an array for a fixed list, or a getter function for dynamic/API-driven options
4154
+ * (e.g. search-as-you-type). The getter is called each render so it sees current state;
4155
+ * use with autocompleteProps.onInputChange to fetch options when the user types.
4136
4156
  *
4137
4157
  * @example
4138
4158
  * ```tsx
4139
- * // Simple autocomplete
4140
- * FormFieldHelpers.autocomplete("country", "Country", options)
4159
+ * // Static
4160
+ * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries", { allowsCustomValue: true })
4141
4161
  *
4142
- * // With placeholder
4143
- * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries")
4162
+ * // Dynamic (e.g. PCO Person)
4163
+ * const [people, setPeople] = useState([]);
4164
+ * FormFieldHelpers.autocomplete("personId", "Person", () => people.map(p => ({ label: p.name, value: p.id })), "Search people", {
4165
+ * onInputChange: (q) => fetchPeople(q).then(setPeople),
4166
+ * })
4144
4167
  *
4145
- * // With full customization
4146
- * FormFieldHelpers.autocomplete("country", "Country", options, "Search countries", {
4147
- * classNames: { base: "custom-autocomplete" },
4148
- * allowsCustomValue: true
4168
+ * // Custom option layout (e.g. name + email + phone per option)
4169
+ * FormFieldHelpers.autocomplete("personId", "Person", getOptions, "Search", undefined, {
4170
+ * renderItem: (item) => <div><strong>{item.label}</strong><br /><small>{getSubtitle(item.value)}</small></div>,
4149
4171
  * })
4150
4172
  * ```
4151
4173
  */
4152
- autocomplete: (name, label, items, placeholder, autocompleteProps) => ({
4153
- autocompleteProps: {
4154
- ...placeholder && { placeholder },
4155
- ...autocompleteProps
4156
- },
4157
- label,
4158
- name,
4159
- options: items,
4160
- type: "autocomplete"
4161
- }),
4174
+ autocomplete: (name, label, items, placeholder, autocompleteProps, options) => {
4175
+ const isGetter = typeof items === "function";
4176
+ return {
4177
+ autocompleteProps: {
4178
+ ...placeholder && { placeholder },
4179
+ ...autocompleteProps
4180
+ },
4181
+ ...isGetter ? { getOptions: items } : { options: items },
4182
+ ...options?.renderItem && { renderItem: options.renderItem },
4183
+ label,
4184
+ name,
4185
+ type: "autocomplete"
4186
+ };
4187
+ },
4162
4188
  /**
4163
4189
  * Create a checkbox field
4164
4190
  *
@@ -4830,7 +4856,8 @@ function createFieldFromParams(params) {
4830
4856
  autocompleteProps: params.props,
4831
4857
  label: params.label,
4832
4858
  name: params.name,
4833
- options: params.options,
4859
+ ...typeof params.getOptions === "function" ? { getOptions: params.getOptions } : { options: params.options },
4860
+ ...params.renderItem && { renderItem: params.renderItem },
4834
4861
  type: "autocomplete"
4835
4862
  };
4836
4863
  case "content":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rachelallyson/hero-hook-form",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Typed form helpers that combine React Hook Form and HeroUI components.",
5
5
  "author": "Rachel Higley",
6
6
  "homepage": "https://rachelallyson.github.io/hero-hook-form/",