@eifi1/ui-kit 0.22.0 → 0.24.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 (208) hide show
  1. package/README.md +42 -25
  2. package/dist/chart.d.ts +8 -7
  3. package/dist/components/amount-input.d.ts +34 -7
  4. package/dist/components/amount-input.js +14 -10
  5. package/dist/components/amount-input.js.map +1 -1
  6. package/dist/components/autocomplete.d.ts +26 -0
  7. package/dist/components/autocomplete.js +164 -137
  8. package/dist/components/autocomplete.js.map +1 -1
  9. package/dist/components/button-group.d.ts +8 -7
  10. package/dist/components/calculator.d.ts +8 -7
  11. package/dist/components/column-mapper.d.ts +235 -0
  12. package/dist/components/column-mapper.js +411 -0
  13. package/dist/components/column-mapper.js.map +1 -0
  14. package/dist/components/combobox-core.d.ts +39 -2
  15. package/dist/components/combobox-core.js +29 -6
  16. package/dist/components/combobox-core.js.map +1 -1
  17. package/dist/components/combobox.d.ts +113 -3
  18. package/dist/components/combobox.js +456 -313
  19. package/dist/components/combobox.js.map +1 -1
  20. package/dist/components/confirm-dialog.d.ts +8 -7
  21. package/dist/components/confirm-dialog.js +7 -8
  22. package/dist/components/confirm-dialog.js.map +1 -1
  23. package/dist/components/copy-button.d.ts +5 -4
  24. package/dist/components/country-select.d.ts +50 -7
  25. package/dist/components/country-select.js +65 -9
  26. package/dist/components/country-select.js.map +1 -1
  27. package/dist/components/danger-confirm.d.ts +8 -7
  28. package/dist/components/danger-confirm.js +64 -7
  29. package/dist/components/danger-confirm.js.map +1 -1
  30. package/dist/components/date-picker.d.ts +41 -0
  31. package/dist/components/date-picker.js +269 -8
  32. package/dist/components/date-picker.js.map +1 -1
  33. package/dist/components/entity-combobox.d.ts +48 -2
  34. package/dist/components/entity-combobox.js +159 -114
  35. package/dist/components/entity-combobox.js.map +1 -1
  36. package/dist/components/facing-pair.d.ts +8 -7
  37. package/dist/components/field-parts.d.ts +116 -6
  38. package/dist/components/field-parts.js +84 -0
  39. package/dist/components/field-parts.js.map +1 -1
  40. package/dist/components/field-strip.d.ts +105 -0
  41. package/dist/components/field-strip.js +46 -0
  42. package/dist/components/field-strip.js.map +1 -0
  43. package/dist/components/file-button.d.ts +8 -7
  44. package/dist/components/file-dropzone.d.ts +8 -7
  45. package/dist/components/form-actions.d.ts +8 -7
  46. package/dist/components/form-actions.js +23 -3
  47. package/dist/components/form-actions.js.map +1 -1
  48. package/dist/components/iban-input.d.ts +8 -7
  49. package/dist/components/icon-picker.d.ts +10 -1
  50. package/dist/components/icon-picker.js +12 -4
  51. package/dist/components/icon-picker.js.map +1 -1
  52. package/dist/components/language-select.d.ts +8 -7
  53. package/dist/components/money-field.d.ts +11 -7
  54. package/dist/components/money-field.js.map +1 -1
  55. package/dist/components/month-picker.js +2 -1
  56. package/dist/components/month-picker.js.map +1 -1
  57. package/dist/components/multi-entity-combobox.d.ts +31 -2
  58. package/dist/components/multi-entity-combobox.js +152 -103
  59. package/dist/components/multi-entity-combobox.js.map +1 -1
  60. package/dist/components/number-field.d.ts +8 -7
  61. package/dist/components/number-input.d.ts +8 -7
  62. package/dist/components/numpad-sheet.d.ts +8 -7
  63. package/dist/components/phone-input.d.ts +8 -7
  64. package/dist/components/reauth-dialog.d.ts +2 -2
  65. package/dist/components/reauth-dialog.js +6 -8
  66. package/dist/components/reauth-dialog.js.map +1 -1
  67. package/dist/components/series-chart.d.ts +8 -7
  68. package/dist/components/settings-fields.d.ts +8 -7
  69. package/dist/components/share-card.d.ts +8 -7
  70. package/dist/components/swatch-picker.d.ts +17 -1
  71. package/dist/components/swatch-picker.js +10 -4
  72. package/dist/components/swatch-picker.js.map +1 -1
  73. package/dist/components/text-link.d.ts +8 -7
  74. package/dist/components/tile-radio.d.ts +20 -0
  75. package/dist/components/tile-radio.js +25 -8
  76. package/dist/components/tile-radio.js.map +1 -1
  77. package/dist/components/time-input.d.ts +8 -7
  78. package/dist/components/toggle-group.js +2 -2
  79. package/dist/components/toggle-group.js.map +1 -1
  80. package/dist/components/trigger-aria.d.ts +3 -0
  81. package/dist/components/trigger-aria.js +3 -1
  82. package/dist/components/trigger-aria.js.map +1 -1
  83. package/dist/components/ui.d.ts +5 -4
  84. package/dist/components/ui.js +60 -28
  85. package/dist/components/ui.js.map +1 -1
  86. package/dist/feedback/feedback-attachment.d.ts +66 -1
  87. package/dist/feedback/feedback-attachment.js +185 -65
  88. package/dist/feedback/feedback-attachment.js.map +1 -1
  89. package/dist/feedback/feedback-dialog.d.ts +66 -1
  90. package/dist/feedback/feedback-dialog.js.map +1 -1
  91. package/dist/feedback/feedback-inbox.d.ts +67 -252
  92. package/dist/feedback/feedback-inbox.js.map +1 -1
  93. package/dist/feedback/feedback-thread.d.ts +66 -287
  94. package/dist/feedback/feedback-thread.js +16 -3
  95. package/dist/feedback/feedback-thread.js.map +1 -1
  96. package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-DOwPu-Il.d.ts} +1273 -30
  97. package/dist/feedback.d.ts +65 -3
  98. package/dist/hooks/use-file-drop.d.ts +8 -7
  99. package/dist/i18n/defaults.d.ts +8 -7
  100. package/dist/i18n/defaults.js +3 -1
  101. package/dist/i18n/defaults.js.map +1 -1
  102. package/dist/i18n/german.d.ts +8 -7
  103. package/dist/i18n/german.js +36 -2
  104. package/dist/i18n/german.js.map +1 -1
  105. package/dist/i18n/kit-labels.d.ts +5 -4
  106. package/dist/i18n/kit-labels.js.map +1 -1
  107. package/dist/i18n/languages.d.ts +8 -7
  108. package/dist/i18n/locales/de-CH.d.ts +8 -7
  109. package/dist/i18n/locales/en.d.ts +8 -7
  110. package/dist/i18n/locales/en.js +7 -0
  111. package/dist/i18n/locales/en.js.map +1 -1
  112. package/dist/i18n/locales/es.d.ts +8 -7
  113. package/dist/i18n/locales/es.js +37 -2
  114. package/dist/i18n/locales/es.js.map +1 -1
  115. package/dist/i18n/locales/fr.d.ts +8 -7
  116. package/dist/i18n/locales/fr.js +36 -2
  117. package/dist/i18n/locales/fr.js.map +1 -1
  118. package/dist/i18n/locales/hu.d.ts +8 -7
  119. package/dist/i18n/locales/hu.js +38 -2
  120. package/dist/i18n/locales/hu.js.map +1 -1
  121. package/dist/i18n/locales/it.d.ts +8 -7
  122. package/dist/i18n/locales/it.js +37 -2
  123. package/dist/i18n/locales/it.js.map +1 -1
  124. package/dist/i18n/locales/zh.d.ts +8 -7
  125. package/dist/i18n/locales/zh.js +34 -2
  126. package/dist/i18n/locales/zh.js.map +1 -1
  127. package/dist/i18n/review.d.ts +8 -7
  128. package/dist/i18n/review.js +20 -1
  129. package/dist/i18n/review.js.map +1 -1
  130. package/dist/index.d.ts +6 -4
  131. package/dist/index.js +16 -0
  132. package/dist/index.js.map +1 -1
  133. package/dist/lib/column-mapping.d.ts +81 -0
  134. package/dist/lib/column-mapping.js +108 -0
  135. package/dist/lib/column-mapping.js.map +1 -0
  136. package/dist/lib/strip-fade.d.ts +2 -2
  137. package/dist/lib/strip-fade.js.map +1 -1
  138. package/dist/lib/table-text.d.ts +109 -1
  139. package/dist/lib/table-text.js +122 -1
  140. package/dist/lib/table-text.js.map +1 -1
  141. package/dist/rhf/fields.d.ts +142 -13
  142. package/dist/rhf/fields.js +185 -3
  143. package/dist/rhf/fields.js.map +1 -1
  144. package/dist/rhf/form.d.ts +8 -7
  145. package/dist/rhf.d.ts +9 -8
  146. package/dist/rhf.js.map +1 -1
  147. package/dist/shell/app-shell.d.ts +8 -7
  148. package/dist/shell/top-bar-brand.d.ts +8 -7
  149. package/dist/shell/topbar-action-menu.d.ts +36 -3
  150. package/dist/shell/topbar-action-menu.js +74 -33
  151. package/dist/shell/topbar-action-menu.js.map +1 -1
  152. package/dist/shell.d.ts +8 -7
  153. package/dist/table-text.d.ts +1 -1
  154. package/dist/wizard/stepper-nav.d.ts +38 -9
  155. package/dist/wizard/stepper-nav.js +4 -0
  156. package/dist/wizard/stepper-nav.js.map +1 -1
  157. package/dist/wizard.d.ts +8 -7
  158. package/package.json +1 -1
  159. package/src/components/amount-input.tsx +50 -14
  160. package/src/components/autocomplete.tsx +188 -116
  161. package/src/components/column-mapper.tsx +768 -0
  162. package/src/components/combobox-core.tsx +82 -9
  163. package/src/components/combobox.tsx +655 -335
  164. package/src/components/confirm-dialog.tsx +12 -8
  165. package/src/components/country-select.tsx +135 -22
  166. package/src/components/danger-confirm.tsx +274 -24
  167. package/src/components/date-picker.tsx +423 -10
  168. package/src/components/entity-combobox.tsx +243 -129
  169. package/src/components/field-parts.tsx +224 -5
  170. package/src/components/field-strip.tsx +149 -0
  171. package/src/components/form-actions.tsx +52 -5
  172. package/src/components/icon-picker.tsx +23 -4
  173. package/src/components/money-field.tsx +3 -0
  174. package/src/components/month-picker.tsx +2 -1
  175. package/src/components/multi-entity-combobox.tsx +221 -121
  176. package/src/components/reauth-dialog.tsx +17 -18
  177. package/src/components/swatch-picker.tsx +29 -5
  178. package/src/components/tile-radio.tsx +74 -13
  179. package/src/components/toggle-group.tsx +2 -2
  180. package/src/components/trigger-aria.ts +5 -0
  181. package/src/components/ui.tsx +114 -33
  182. package/src/feedback/feedback-attachment.tsx +372 -72
  183. package/src/feedback/feedback-dialog.tsx +7 -3
  184. package/src/feedback/feedback-inbox.tsx +3 -2
  185. package/src/feedback/feedback-thread.tsx +112 -9
  186. package/src/i18n/defaults.ts +2 -0
  187. package/src/i18n/german.ts +42 -0
  188. package/src/i18n/kit-labels.tsx +4 -0
  189. package/src/i18n/locales/en.ts +27 -5
  190. package/src/i18n/locales/es.ts +41 -0
  191. package/src/i18n/locales/fr.ts +42 -0
  192. package/src/i18n/locales/hu.ts +37 -0
  193. package/src/i18n/locales/it.ts +41 -0
  194. package/src/i18n/locales/zh.ts +32 -0
  195. package/src/i18n/review.ts +19 -0
  196. package/src/index.ts +18 -0
  197. package/src/lib/column-mapping.ts +234 -0
  198. package/src/lib/strip-fade.ts +2 -2
  199. package/src/lib/table-text.ts +271 -0
  200. package/src/rhf/fields.tsx +421 -6
  201. package/src/rhf.ts +2 -1
  202. package/src/shell/topbar-action-menu.tsx +134 -38
  203. package/src/wizard/stepper-nav.tsx +35 -1
  204. package/dist/components/field-anatomy.d.ts +0 -95
  205. package/dist/components/field-anatomy.js +0 -84
  206. package/dist/components/field-anatomy.js.map +0 -1
  207. package/dist/feedback-attachment-NhUm7Zga.d.ts +0 -367
  208. package/src/components/field-anatomy.tsx +0 -190
@@ -9,10 +9,13 @@
9
9
  * <RhfDateField name="start" label="Start" />
10
10
  * <RhfSelect name="type" label="Type" options={TYPES} />
11
11
  * <RhfCombobox name="tenantId" label="Tenant" options={tenants} clearable />
12
+ * <RhfInlineEntityCombobox name="accountId" label="Account" options={accounts} />
12
13
  * <RhfCheckbox name="isDefault" label="Default account" />
13
14
  * <RhfTimeInput name="meetingTime" label="Time" />
14
15
  * <RhfDateRangePicker fromName="periodFrom" toName="periodTo" label="Period" />
15
16
  * <RhfToggleGroup name="interval" label="Interval" options={INTERVALS} />
17
+ * <RhfCountrySelect name="country" label="Country" preferred={["CH", "DE"]} />
18
+ * <RhfMonthPicker name="fiscalYear" label="Fiscal year" mode="year" valueAsNumber />
16
19
  * </Form>
17
20
  * ```
18
21
  *
@@ -66,6 +69,8 @@ import {
66
69
  import { TimeInput, type TimeInputProps } from "../components/time-input";
67
70
  import { IbanInput, type IbanInputProps } from "../components/iban-input";
68
71
  import { PhoneInput, type PhoneInputProps } from "../components/phone-input";
72
+ import { CountrySelect, type CountrySelectProps } from "../components/country-select";
73
+ import { MonthPicker, type MonthPickerProps } from "../components/month-picker";
69
74
  import {
70
75
  ToggleGroup,
71
76
  type ToggleGroupBaseProps,
@@ -74,7 +79,12 @@ import {
74
79
  } from "../components/toggle-group";
75
80
  import { Checkbox, type CheckboxProps } from "../components/checkbox";
76
81
  import { EntityCombobox, type EntityComboboxProps } from "../components/entity-combobox";
77
- import { Combobox, type ComboboxProps } from "../components/combobox";
82
+ import {
83
+ Combobox,
84
+ InlineEntityCombobox,
85
+ type ComboboxProps,
86
+ type InlineEntityComboboxProps,
87
+ } from "../components/combobox";
78
88
  import type { ComboClearValue } from "../components/combobox-core";
79
89
  import { cn } from "../lib/cn";
80
90
 
@@ -571,6 +581,10 @@ export type RhfMoneyFieldProps<
571
581
  * with `rules` as well if a clamp needs explaining. */
572
582
  min?: number;
573
583
  max?: number;
584
+ /** Classes for the `<input>` — `className` is the item's box. {@link RhfTextField},
585
+ * {@link RhfTextarea} and {@link RhfNumberField} take it; since 0.23 AmountInput
586
+ * does too (keksdose G7: `text-end` for a money column). */
587
+ inputClassName?: string;
574
588
  };
575
589
 
576
590
  /** The amount a text reads as, or `undefined` for a draft that is not one yet. */
@@ -1197,6 +1211,252 @@ export function RhfPhoneInput<
1197
1211
  );
1198
1212
  }
1199
1213
 
1214
+ // ── country and month (0.23) ─────────────────────────────────────────────────
1215
+
1216
+ type OwnCountrySelectProps = Omit<
1217
+ CountrySelectProps<boolean>,
1218
+ | "value"
1219
+ | "onChange"
1220
+ | "onBlur"
1221
+ | "label"
1222
+ | "hint"
1223
+ | "error"
1224
+ | "invalid"
1225
+ | "disabled"
1226
+ | "className"
1227
+ | "id"
1228
+ | "ref"
1229
+ | "clearable"
1230
+ >;
1231
+
1232
+ export type RhfCountrySelectProps<
1233
+ TFieldValues extends FieldValues = FieldValues,
1234
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1235
+ TTransformed = TFieldValues,
1236
+ > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1237
+ OwnCountrySelectProps & {
1238
+ /** Offer the picker's clear "×" (and Delete on the trigger) while a country is
1239
+ * chosen. A clear stores `clearValue`. */
1240
+ clearable?: boolean;
1241
+ /** What a clear stores. Default `null`; `""` for a schema that spells "no
1242
+ * country" as an empty string (a zod `z.string().max(2)`, kastlan's). The picker
1243
+ * reads either back as empty. */
1244
+ clearValue?: ComboClearValue;
1245
+ /** Classes for the picker — `className` is the item's box. */
1246
+ selectClassName?: string;
1247
+ };
1248
+
1249
+ /**
1250
+ * {@link CountrySelect} bound to one field, which stores the ISO 3166-1 alpha-2 code
1251
+ * ("CH", always upper-case), or `clearValue` once cleared (`clearable`). kastlan asked
1252
+ * for it (0.22 adoption): its address form wired a `CountrySelect` through
1253
+ * {@link RhfField}'s render, and with no ref on the picker react-hook-form's
1254
+ * focus-on-error had nothing to focus — the one field on the form a failed submit
1255
+ * could not take you to.
1256
+ *
1257
+ * Bound the way {@link RhfIbanInput} and {@link RhfPhoneInput} are: `field.ref` reaches
1258
+ * the trigger (the picker's `ref`, 0.23), the form's error paints the trigger and is
1259
+ * the shell's message under it, the hint is the shell's description — so neither is
1260
+ * ever handed to the picker, and its box never changes when one comes or goes. The
1261
+ * form's label names the trigger together with the chosen country, and is the phone
1262
+ * sheet's title. `commit` and `disabledReason` are the picker's own: a country that
1263
+ * saves itself stays focusable under a lock and says why.
1264
+ *
1265
+ * ```tsx
1266
+ * <RhfCountrySelect name="country" label="Country" required
1267
+ * preferred={["CH", "LI", "DE", "AT", "FR", "IT"]} rules={{ required: "Choose a country" }} />
1268
+ * ```
1269
+ */
1270
+ export function RhfCountrySelect<
1271
+ TFieldValues extends FieldValues = FieldValues,
1272
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1273
+ TTransformed = TFieldValues,
1274
+ >({
1275
+ name,
1276
+ control,
1277
+ rules,
1278
+ label,
1279
+ hint,
1280
+ required,
1281
+ disabled,
1282
+ excludeWhenDisabled,
1283
+ className,
1284
+ clearable,
1285
+ clearValue = null,
1286
+ selectClassName,
1287
+ labels,
1288
+ ...countryProps
1289
+ }: RhfCountrySelectProps<TFieldValues, TName, TTransformed>) {
1290
+ const labelled = hasContent(label);
1291
+ // A string label is also what the field IS — the picker's `country` word, which
1292
+ // titles the phone sheet. A caller's own `labels.country` still wins.
1293
+ const ownLabels = typeof label === "string" ? { country: label, ...labels } : labels;
1294
+ return (
1295
+ <RhfField
1296
+ {...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
1297
+ render={({ field, invalid, labelId }) => {
1298
+ const value: unknown = field.value;
1299
+ return (
1300
+ // FormControl clones this element with `id` / `aria-describedby` /
1301
+ // `aria-invalid`, which the picker routes to its trigger.
1302
+ <CountrySelect<boolean>
1303
+ aria-required={required || undefined}
1304
+ // The form's label, then the country (see the picker's `aria-labelledby`).
1305
+ aria-labelledby={labelled ? labelId : undefined}
1306
+ {...countryProps}
1307
+ labels={ownLabels}
1308
+ className={selectClassName}
1309
+ ref={field.ref}
1310
+ value={typeof value === "string" ? value : null}
1311
+ clearable={clearable}
1312
+ onChange={(code) => field.onChange(code ?? clearValue)}
1313
+ // Focus leaving the picker (the wrapper's focusout) marks the field touched.
1314
+ onBlur={field.onBlur}
1315
+ disabled={field.disabled}
1316
+ invalid={invalid}
1317
+ />
1318
+ );
1319
+ }}
1320
+ />
1321
+ );
1322
+ }
1323
+
1324
+ type OwnMonthPickerProps = Omit<
1325
+ MonthPickerProps,
1326
+ "value" | "onChange" | "onBlur" | "label" | "hint" | "error" | "invalid" | "disabled" | "className" | "id" | "mode"
1327
+ >;
1328
+
1329
+ /** `mode` picks what the form stores, and `valueAsNumber` is only offered for a year. */
1330
+ type RhfMonthPickerMode =
1331
+ | {
1332
+ /** A month, stored as `"YYYY-MM"` (the default). */
1333
+ mode?: "month";
1334
+ valueAsNumber?: undefined;
1335
+ }
1336
+ | {
1337
+ /** A year, stored as `"YYYY"` — or as a number with `valueAsNumber`. */
1338
+ mode: "year";
1339
+ /** Store the year as a number (`2026`) rather than `"2026"` — kastlan's budget
1340
+ * `fiscal_year` is an integer column. A number in the form reads back as its
1341
+ * year. */
1342
+ valueAsNumber?: boolean;
1343
+ };
1344
+
1345
+ export type RhfMonthPickerProps<
1346
+ TFieldValues extends FieldValues = FieldValues,
1347
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1348
+ TTransformed = TFieldValues,
1349
+ > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1350
+ OwnMonthPickerProps & {
1351
+ /** Classes for the picker — `className` is the item's box. As {@link RhfDateField}'s. */
1352
+ inputClassName?: string;
1353
+ } & RhfMonthPickerMode;
1354
+
1355
+ function MonthControl({
1356
+ field,
1357
+ invalid,
1358
+ id,
1359
+ valueAsNumber,
1360
+ mode,
1361
+ inputClassName,
1362
+ pickerProps,
1363
+ required,
1364
+ ...aria
1365
+ }: {
1366
+ field: ControllerRenderProps;
1367
+ invalid: boolean;
1368
+ id: string;
1369
+ valueAsNumber?: boolean;
1370
+ mode?: "month" | "year";
1371
+ inputClassName?: string;
1372
+ pickerProps: OwnMonthPickerProps;
1373
+ required?: boolean;
1374
+ "aria-describedby"?: string;
1375
+ "aria-invalid"?: MonthPickerProps["aria-invalid"];
1376
+ }) {
1377
+ // MonthPicker forwards no ref; it routes `id` to its trigger, where the handle finds
1378
+ // it — RhfDateField's way.
1379
+ useFocusHandle(field.ref, () => document.getElementById(id));
1380
+ const value: unknown = field.value;
1381
+ return (
1382
+ <MonthPicker
1383
+ {...pickerProps}
1384
+ {...aria}
1385
+ // MonthPicker routes `aria-required` to its trigger since 0.23.0.
1386
+ aria-required={required || undefined}
1387
+ id={id}
1388
+ mode={mode}
1389
+ value={value === null || value === undefined ? "" : String(value)}
1390
+ onChange={(key) => {
1391
+ field.onChange(mode === "year" && valueAsNumber ? Number(key) : key);
1392
+ // A pick is the whole interaction — there is no text to leave — so it touches.
1393
+ field.onBlur();
1394
+ }}
1395
+ disabled={field.disabled}
1396
+ invalid={invalid}
1397
+ className={inputClassName}
1398
+ />
1399
+ );
1400
+ }
1401
+
1402
+ /**
1403
+ * {@link MonthPicker} bound to one field: a month as `"YYYY-MM"`, or with `mode="year"` a
1404
+ * year as `"YYYY"` (`valueAsNumber` stores `2026`). kastlan asked for it with
1405
+ * {@link RhfCountrySelect}: its budget wizard's fiscal year was a `MonthPicker
1406
+ * mode="year"` inside {@link RhfField}'s render — the `String()` in, the `Number()`
1407
+ * out, `invalid` by hand — and a failed submit could not focus it, because the picker
1408
+ * forwards no ref.
1409
+ *
1410
+ * Bound as {@link RhfDateField} is: a focus handle finds the trigger by its id, so
1411
+ * focus-on-error lands there; the form's error paints the trigger and is the shell's
1412
+ * message; the hint is the shell's description; neither is handed to the picker, so
1413
+ * its box never changes. Picking marks the field touched.
1414
+ *
1415
+ * ```tsx
1416
+ * <RhfMonthPicker name="fiscalYear" label="Fiscal year" mode="year" valueAsNumber
1417
+ * min="2020" max="2029" rules={{ required: "Choose a year" }} />
1418
+ * ```
1419
+ */
1420
+ export function RhfMonthPicker<
1421
+ TFieldValues extends FieldValues = FieldValues,
1422
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1423
+ TTransformed = TFieldValues,
1424
+ >({
1425
+ name,
1426
+ control,
1427
+ rules,
1428
+ label,
1429
+ hint,
1430
+ required,
1431
+ disabled,
1432
+ excludeWhenDisabled,
1433
+ className,
1434
+ inputClassName,
1435
+ mode,
1436
+ valueAsNumber,
1437
+ ...pickerProps
1438
+ }: RhfMonthPickerProps<TFieldValues, TName, TTransformed>) {
1439
+ return (
1440
+ <RhfField
1441
+ {...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
1442
+ render={({ field, invalid, id }) => (
1443
+ // FormControl clones this element with `aria-describedby` / `aria-invalid`,
1444
+ // which MonthControl hands on to the picker's trigger.
1445
+ <MonthControl
1446
+ field={field as unknown as ControllerRenderProps}
1447
+ invalid={invalid}
1448
+ id={id}
1449
+ mode={mode}
1450
+ valueAsNumber={valueAsNumber}
1451
+ inputClassName={inputClassName}
1452
+ pickerProps={pickerProps}
1453
+ required={required}
1454
+ />
1455
+ )}
1456
+ />
1457
+ );
1458
+ }
1459
+
1200
1460
  // ── select ───────────────────────────────────────────────────────────────────
1201
1461
 
1202
1462
  export interface RhfSelectOption {
@@ -1515,11 +1775,17 @@ export type RhfComboboxProps<
1515
1775
  | "emptyLabel"
1516
1776
  | "clearable"
1517
1777
  | "clearLabel"
1778
+ | "closeLabel"
1518
1779
  | "onCreate"
1519
1780
  | "createLabel"
1781
+ | "createEmptyLabel"
1782
+ | "createCommit"
1783
+ | "commit"
1784
+ | "disabledReason"
1520
1785
  | "filter"
1521
1786
  | "minChars"
1522
1787
  | "debounceMs"
1788
+ | "aria-label"
1523
1789
  > & {
1524
1790
  /** What a clear stores — the picker's `clearValue`. Default `null`; `""` for a
1525
1791
  * schema that spells "no choice" as an empty string. */
@@ -1537,6 +1803,8 @@ function EntityControl<V extends string | number>({
1537
1803
  describedBy,
1538
1804
  clearValue,
1539
1805
  comboClassName,
1806
+ sheetTitle,
1807
+ focusRef,
1540
1808
  comboProps,
1541
1809
  }: {
1542
1810
  field: ControllerRenderProps;
@@ -1547,12 +1815,15 @@ function EntityControl<V extends string | number>({
1547
1815
  describedBy: string | undefined;
1548
1816
  clearValue: ComboClearValue;
1549
1817
  comboClassName?: string;
1818
+ sheetTitle: ReactNode;
1819
+ /** `field.ref`, handed over on its own so the compiler reads `field` as data. */
1820
+ focusRef: ControllerRenderProps["ref"];
1550
1821
  comboProps: Omit<RhfComboboxProps<FieldValues, string, V>, keyof RhfFieldBaseProps | "clearValue" | "comboClassName">;
1551
1822
  }) {
1552
1823
  const box = useRef<HTMLDivElement>(null);
1553
1824
  // EntityCombobox puts `id` and the ARIA below on its trigger, so the label's
1554
- // `htmlFor` names it, the hint describes it and the focus handle finds it by id.
1555
- useFocusHandle(field.ref, () => document.getElementById(id));
1825
+ // `htmlFor` names it and the hint describes it; `field.ref` reaches the trigger
1826
+ // through the picker's own `ref` (0.24), as RhfCountrySelect's does.
1556
1827
  // EntityCombobox reports no blur: focus leaving the picker marks the field touched.
1557
1828
  const onBlur = field.onBlur;
1558
1829
  useLayoutEffect(() => {
@@ -1565,7 +1836,11 @@ function EntityControl<V extends string | number>({
1565
1836
  return (
1566
1837
  <div ref={box}>
1567
1838
  <EntityCombobox<V, ComboClearValue>
1839
+ // The form's label titles the phone sheet: the picker is not handed `label`,
1840
+ // which the FormLabel above already draws.
1841
+ sheetTitle={sheetTitle}
1568
1842
  {...comboProps}
1843
+ ref={focusRef}
1569
1844
  className={comboClassName}
1570
1845
  id={id}
1571
1846
  aria-describedby={describedBy}
@@ -1587,6 +1862,14 @@ function EntityControl<V extends string | number>({
1587
1862
  * The kit's {@link EntityCombobox}: an id-keyed pick from `options` or `loadOptions`.
1588
1863
  * Stores the option's `value`, or `clearValue` on a clear (only offered with
1589
1864
  * `clearable`).
1865
+ *
1866
+ * 0.24: `field.ref` goes through the picker's own `ref` to its trigger (it used to be
1867
+ * a focus handle that found the trigger by id — the same element, now by the route
1868
+ * every other binding takes); the write lock (`commit`, `disabledReason`), the create
1869
+ * row's `createEmptyLabel` / `createCommit`, `closeLabel` and `aria-label` pass
1870
+ * through; and the form's label titles the phone sheet, which fell back to the
1871
+ * placeholder. For the INLINE picker — a text input, the account field typed into —
1872
+ * see {@link RhfInlineEntityCombobox}.
1590
1873
  */
1591
1874
  export function RhfCombobox<
1592
1875
  TFieldValues extends FieldValues = FieldValues,
@@ -1622,6 +1905,8 @@ export function RhfCombobox<
1622
1905
  describedBy={describedBy}
1623
1906
  clearValue={clearValue}
1624
1907
  comboClassName={comboClassName}
1908
+ sheetTitle={hasContent(label) ? label : undefined}
1909
+ focusRef={field.ref}
1625
1910
  comboProps={comboProps}
1626
1911
  />
1627
1912
  )}
@@ -1629,6 +1914,123 @@ export function RhfCombobox<
1629
1914
  );
1630
1915
  }
1631
1916
 
1917
+ type OwnInlineEntityComboboxProps<V extends string | number> = Omit<
1918
+ InlineEntityComboboxProps<V, ComboClearValue>,
1919
+ | "value"
1920
+ | "defaultValue"
1921
+ | "onChange"
1922
+ | "onBlur"
1923
+ | "label"
1924
+ | "hint"
1925
+ | "error"
1926
+ | "invalid"
1927
+ | "disabled"
1928
+ | "className"
1929
+ | "id"
1930
+ | "ref"
1931
+ | "clearValue"
1932
+ >;
1933
+
1934
+ export type RhfInlineEntityComboboxProps<
1935
+ TFieldValues extends FieldValues = FieldValues,
1936
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1937
+ V extends string | number = string | number,
1938
+ TTransformed = TFieldValues,
1939
+ > = RhfFieldBaseProps<TFieldValues, TName, TTransformed> &
1940
+ OwnInlineEntityComboboxProps<V> & {
1941
+ /** What a clear stores — emptying the text, or the "×" with `clearable`. Default
1942
+ * `null`; `""` for a schema that spells "no choice" as an empty string. The
1943
+ * picker reads either back as empty. */
1944
+ clearValue?: ComboClearValue;
1945
+ /** Classes for the picker — `className` is the item's box. As {@link RhfCombobox}'s. */
1946
+ comboClassName?: string;
1947
+ };
1948
+
1949
+ /**
1950
+ * {@link InlineEntityCombobox} bound to one field, which stores the chosen option's
1951
+ * `value` (an id), or `clearValue` once the text is emptied or the "×" pressed.
1952
+ *
1953
+ * kastlan asked for it (0.24): its account picker IS an `InlineEntityCombobox` — the
1954
+ * account number is typed straight into the field — and the budget line-item form
1955
+ * wired it through {@link RhfField}'s render. The picker took no `ref`, so react-hook-
1956
+ * form's focus-on-error had nothing to focus: a line item saved without an account
1957
+ * failed its submit and left the caret where it was, the one field on the form a
1958
+ * failed submit could not take you to. (The same gap {@link RhfCountrySelect} closed
1959
+ * for the address form's country in 0.23.)
1960
+ *
1961
+ * Bound the way {@link RhfCountrySelect} is: `field.ref` reaches the `<input>` (the
1962
+ * picker's `ref`, 0.24), the form's error paints the input and is the shell's message
1963
+ * under it, and the hint is the shell's description — so neither is ever handed to the
1964
+ * picker, and its box never changes when one comes or goes. The form's label names
1965
+ * the input through `<label for>` and titles the phone sheet (`sheetTitle`); the picker
1966
+ * draws no floating label of its own. Focus leaving the picker marks the field
1967
+ * touched.
1968
+ *
1969
+ * Everything else is the picker's and passes through: `commit` and `disabledReason`
1970
+ * (a picker whose choice saves stays focusable under a lock and says why), `clearable`,
1971
+ * `autoFocus`, `aria-label` for an unlabelled cell, and the create row — `onCreate`,
1972
+ * `createLabel`, `createEmptyLabel`, `createCommit`. Making the record is the
1973
+ * caller's, and so is storing its id once it exists:
1974
+ *
1975
+ * ```tsx
1976
+ * <RhfInlineEntityCombobox<LineItem, "account_id", number>
1977
+ * name="account_id" label="Account" required options={accounts}
1978
+ * rules={{ required: "Choose an account" }}
1979
+ * onCreate={async (name) => form.setValue("account_id", (await createAccount(name)).id)} />
1980
+ * ```
1981
+ *
1982
+ * For the button-shaped picker with an async `loadOptions`, see {@link RhfCombobox}.
1983
+ */
1984
+ export function RhfInlineEntityCombobox<
1985
+ TFieldValues extends FieldValues = FieldValues,
1986
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
1987
+ V extends string | number = string | number,
1988
+ TTransformed = TFieldValues,
1989
+ >({
1990
+ name,
1991
+ control,
1992
+ rules,
1993
+ label,
1994
+ hint,
1995
+ required,
1996
+ disabled,
1997
+ excludeWhenDisabled,
1998
+ className,
1999
+ clearValue = null,
2000
+ comboClassName,
2001
+ ...comboProps
2002
+ }: RhfInlineEntityComboboxProps<TFieldValues, TName, V, TTransformed>) {
2003
+ return (
2004
+ <RhfField
2005
+ {...{ name, control, rules, label, hint, required, disabled, excludeWhenDisabled, className }}
2006
+ render={({ field, invalid }) => {
2007
+ const value: unknown = field.value;
2008
+ return (
2009
+ // FormControl clones this element with `id` / `aria-describedby` /
2010
+ // `aria-invalid`, which the picker routes to its <input>.
2011
+ <InlineEntityCombobox<V, ComboClearValue>
2012
+ aria-required={required || undefined}
2013
+ // The form's label is the phone sheet's heading — the picker is not handed
2014
+ // `label`, which the FormLabel above already draws. A caller's own wins.
2015
+ sheetTitle={hasContent(label) ? label : undefined}
2016
+ {...comboProps}
2017
+ className={comboClassName}
2018
+ ref={field.ref}
2019
+ value={(value ?? null) as V | ComboClearValue | null}
2020
+ clearValue={clearValue}
2021
+ onChange={(v) => field.onChange(v)}
2022
+ // The wrapper's blur, which the <input>'s bubbles to after the picker has
2023
+ // judged any loose text — so the value is in before the field is touched.
2024
+ onBlur={field.onBlur}
2025
+ disabled={field.disabled}
2026
+ invalid={invalid}
2027
+ />
2028
+ );
2029
+ }}
2030
+ />
2031
+ );
2032
+ }
2033
+
1632
2034
  export type RhfTextComboboxProps<
1633
2035
  TFieldValues extends FieldValues = FieldValues,
1634
2036
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
@@ -1645,6 +2047,9 @@ export type RhfTextComboboxProps<
1645
2047
  | "optionAdornment"
1646
2048
  | "autoFocus"
1647
2049
  | "aria-label"
2050
+ | "closeLabel"
2051
+ | "commit"
2052
+ | "disabledReason"
1648
2053
  > & {
1649
2054
  /** Classes for the combobox. */
1650
2055
  comboClassName?: string;
@@ -1685,6 +2090,8 @@ export function RhfTextCombobox<
1685
2090
  id={id}
1686
2091
  describedBy={describedBy}
1687
2092
  className={comboClassName}
2093
+ sheetTitle={hasContent(label) ? label : undefined}
2094
+ focusRef={field.ref}
1688
2095
  comboProps={comboProps}
1689
2096
  />
1690
2097
  )}
@@ -1700,6 +2107,8 @@ function TextComboControl({
1700
2107
  id,
1701
2108
  describedBy,
1702
2109
  className,
2110
+ sheetTitle,
2111
+ focusRef,
1703
2112
  comboProps,
1704
2113
  }: {
1705
2114
  field: ControllerRenderProps;
@@ -1709,16 +2118,22 @@ function TextComboControl({
1709
2118
  id: string;
1710
2119
  describedBy: string | undefined;
1711
2120
  className?: string;
2121
+ sheetTitle: ReactNode;
2122
+ /** `field.ref` — see EntityControl. */
2123
+ focusRef: ControllerRenderProps["ref"];
1712
2124
  comboProps: Omit<RhfTextComboboxProps, keyof RhfFieldBaseProps | "comboClassName">;
1713
2125
  }) {
1714
2126
  // The combobox routes `id` and its ARIA to its <input>, so the label's `htmlFor`
1715
- // names it, the hint describes it and the focus handle finds it; the error is its
1716
- // own `error` prop, which it merges in after the hint.
1717
- useFocusHandle(field.ref, () => document.getElementById(id));
2127
+ // names it and the hint describes it; `field.ref` reaches the <input> through the
2128
+ // combobox's own `ref` (0.24). The error is its own `error` prop, which it merges
2129
+ // in after the hint.
1718
2130
  const value: unknown = field.value;
1719
2131
  return (
1720
2132
  <Combobox
2133
+ // The form's label titles the phone sheet (see EntityControl).
2134
+ sheetTitle={sheetTitle}
1721
2135
  {...comboProps}
2136
+ ref={focusRef}
1722
2137
  id={id}
1723
2138
  aria-describedby={describedBy}
1724
2139
  aria-required={required || undefined}
package/src/rhf.ts CHANGED
@@ -11,7 +11,8 @@ export * from "./rhf/form";
11
11
  export * from "./rhf/use-rhf-wizard-step";
12
12
  // Bound fields — one line per field (RhfTextField, RhfNumberField, RhfIntegerField, RhfMoneyField,
13
13
  // RhfDateField, RhfTextarea, RhfSelect, RhfCheckbox, RhfCombobox, RhfTextCombobox; 0.22: RhfTimeInput,
14
- // RhfDateRangePicker, RhfToggleGroup, RhfIbanInput, RhfPhoneInput)
14
+ // RhfDateRangePicker, RhfToggleGroup, RhfIbanInput, RhfPhoneInput; 0.23: RhfCountrySelect,
15
+ // RhfMonthPicker; 0.24: RhfInlineEntityCombobox)
15
16
  // and RhfField, the shell they are built on.
16
17
  export * from "./rhf/fields";
17
18
  // LineItems over useFieldArray.