@eifi1/ui-kit 0.21.0 → 0.22.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 (278) hide show
  1. package/dist/chart.d.ts +10 -8
  2. package/dist/components/account-chips.d.ts +10 -3
  3. package/dist/components/account-chips.js +5 -2
  4. package/dist/components/account-chips.js.map +1 -1
  5. package/dist/components/account-settings.d.ts +33 -2
  6. package/dist/components/account-settings.js +43 -21
  7. package/dist/components/account-settings.js.map +1 -1
  8. package/dist/components/amount-input.d.ts +27 -8
  9. package/dist/components/amount-input.js +15 -28
  10. package/dist/components/amount-input.js.map +1 -1
  11. package/dist/components/autocomplete.d.ts +8 -0
  12. package/dist/components/autocomplete.js +9 -4
  13. package/dist/components/autocomplete.js.map +1 -1
  14. package/dist/components/button-group.d.ts +10 -8
  15. package/dist/components/calculator.d.ts +10 -8
  16. package/dist/components/checkbox-group.d.ts +104 -0
  17. package/dist/components/checkbox-group.js +111 -0
  18. package/dist/components/checkbox-group.js.map +1 -0
  19. package/dist/components/checkbox.d.ts +19 -0
  20. package/dist/components/checkbox.js +30 -7
  21. package/dist/components/checkbox.js.map +1 -1
  22. package/dist/components/chip.d.ts +18 -0
  23. package/dist/components/chip.js +47 -10
  24. package/dist/components/chip.js.map +1 -1
  25. package/dist/components/combobox.d.ts +13 -2
  26. package/dist/components/combobox.js +17 -8
  27. package/dist/components/combobox.js.map +1 -1
  28. package/dist/components/confirm-dialog.d.ts +66 -161
  29. package/dist/components/copy-button.d.ts +10 -8
  30. package/dist/components/country-select.d.ts +124 -0
  31. package/dist/components/country-select.js +237 -0
  32. package/dist/components/country-select.js.map +1 -0
  33. package/dist/components/currency-select.d.ts +17 -1
  34. package/dist/components/currency-select.js +56 -23
  35. package/dist/components/currency-select.js.map +1 -1
  36. package/dist/components/danger-confirm.d.ts +66 -166
  37. package/dist/components/danger-confirm.js +133 -49
  38. package/dist/components/danger-confirm.js.map +1 -1
  39. package/dist/components/data-table-cells.d.ts +1 -2
  40. package/dist/components/data-table-filter-popover.d.ts +1 -2
  41. package/dist/components/data-table-filters.d.ts +1 -2
  42. package/dist/components/data-table-labels.d.ts +4 -97
  43. package/dist/components/data-table-labels.js +9 -1
  44. package/dist/components/data-table-labels.js.map +1 -1
  45. package/dist/components/data-table-pagination.d.ts +3 -1
  46. package/dist/components/data-table.d.ts +1 -2
  47. package/dist/components/data-table.js +98 -3
  48. package/dist/components/data-table.js.map +1 -1
  49. package/dist/components/date-picker.d.ts +25 -5
  50. package/dist/components/date-picker.js +110 -59
  51. package/dist/components/date-picker.js.map +1 -1
  52. package/dist/components/entity-combobox.d.ts +8 -1
  53. package/dist/components/entity-combobox.js +9 -4
  54. package/dist/components/entity-combobox.js.map +1 -1
  55. package/dist/components/facing-pair.d.ts +10 -8
  56. package/dist/components/field-anatomy.d.ts +95 -0
  57. package/dist/components/field-anatomy.js +84 -0
  58. package/dist/components/field-anatomy.js.map +1 -0
  59. package/dist/components/field-parts.d.ts +114 -0
  60. package/dist/components/field-parts.js +72 -0
  61. package/dist/components/field-parts.js.map +1 -0
  62. package/dist/components/file-button.d.ts +10 -8
  63. package/dist/components/file-button.js +13 -3
  64. package/dist/components/file-button.js.map +1 -1
  65. package/dist/components/file-dropzone.d.ts +32 -9
  66. package/dist/components/file-dropzone.js +57 -34
  67. package/dist/components/file-dropzone.js.map +1 -1
  68. package/dist/components/form-actions.d.ts +10 -8
  69. package/dist/components/form-actions.js +61 -3
  70. package/dist/components/form-actions.js.map +1 -1
  71. package/dist/components/iban-input.d.ts +66 -0
  72. package/dist/components/iban-input.js +125 -0
  73. package/dist/components/iban-input.js.map +1 -0
  74. package/dist/components/icon-picker.d.ts +12 -2
  75. package/dist/components/icon-picker.js +9 -2
  76. package/dist/components/icon-picker.js.map +1 -1
  77. package/dist/components/inline-edit-field.d.ts +167 -0
  78. package/dist/components/inline-edit-field.js +293 -0
  79. package/dist/components/inline-edit-field.js.map +1 -0
  80. package/dist/components/language-select.d.ts +127 -0
  81. package/dist/components/language-select.js +18 -0
  82. package/dist/components/language-select.js.map +1 -0
  83. package/dist/components/line-items.d.ts +25 -3
  84. package/dist/components/line-items.js +55 -46
  85. package/dist/components/line-items.js.map +1 -1
  86. package/dist/components/list.d.ts +25 -0
  87. package/dist/components/list.js +16 -5
  88. package/dist/components/list.js.map +1 -1
  89. package/dist/components/money-field.d.ts +20 -8
  90. package/dist/components/money-field.js.map +1 -1
  91. package/dist/components/month-picker.d.ts +52 -4
  92. package/dist/components/month-picker.js +290 -86
  93. package/dist/components/month-picker.js.map +1 -1
  94. package/dist/components/multi-select.d.ts +8 -1
  95. package/dist/components/multi-select.js +9 -4
  96. package/dist/components/multi-select.js.map +1 -1
  97. package/dist/components/number-field.d.ts +25 -13
  98. package/dist/components/number-field.js +6 -20
  99. package/dist/components/number-field.js.map +1 -1
  100. package/dist/components/number-input.d.ts +70 -13
  101. package/dist/components/number-input.js +90 -38
  102. package/dist/components/number-input.js.map +1 -1
  103. package/dist/components/numpad-sheet.d.ts +10 -8
  104. package/dist/components/one-time-code-input.d.ts +100 -0
  105. package/dist/components/one-time-code-input.js +137 -0
  106. package/dist/components/one-time-code-input.js.map +1 -0
  107. package/dist/components/phone-input.d.ts +66 -0
  108. package/dist/components/phone-input.js +171 -0
  109. package/dist/components/phone-input.js.map +1 -0
  110. package/dist/components/series-chart.d.ts +10 -8
  111. package/dist/components/settings-fields.d.ts +10 -8
  112. package/dist/components/share-card.d.ts +8 -6
  113. package/dist/components/sign-chip.d.ts +66 -0
  114. package/dist/components/sign-chip.js +39 -0
  115. package/dist/components/sign-chip.js.map +1 -0
  116. package/dist/components/swatch-picker.d.ts +27 -2
  117. package/dist/components/swatch-picker.js +73 -31
  118. package/dist/components/swatch-picker.js.map +1 -1
  119. package/dist/components/switch.d.ts +19 -0
  120. package/dist/components/switch.js +24 -6
  121. package/dist/components/switch.js.map +1 -1
  122. package/dist/components/text-link.d.ts +10 -8
  123. package/dist/components/tile-radio.d.ts +68 -13
  124. package/dist/components/tile-radio.js +3 -3
  125. package/dist/components/tile-radio.js.map +1 -1
  126. package/dist/components/time-input.d.ts +15 -9
  127. package/dist/components/time-input.js.map +1 -1
  128. package/dist/components/toggle-group.d.ts +47 -1
  129. package/dist/components/toggle-group.js +110 -69
  130. package/dist/components/toggle-group.js.map +1 -1
  131. package/dist/components/ui.d.ts +10 -8
  132. package/dist/components/ui.js +280 -78
  133. package/dist/components/ui.js.map +1 -1
  134. package/dist/components/use-table-state.d.ts +1 -2
  135. package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
  136. package/dist/data-table.d.ts +1 -2
  137. package/dist/data-table.js +2 -1
  138. package/dist/data-table.js.map +1 -1
  139. package/dist/feedback/feedback-attachment.d.ts +1 -1
  140. package/dist/feedback/feedback-attachment.js +210 -3
  141. package/dist/feedback/feedback-attachment.js.map +1 -1
  142. package/dist/feedback/feedback-dialog.d.ts +1 -1
  143. package/dist/feedback/feedback-dialog.js.map +1 -1
  144. package/dist/feedback/feedback-inbox.d.ts +1 -1
  145. package/dist/feedback/feedback-thread.d.ts +37 -3
  146. package/dist/feedback/feedback-thread.js +38 -9
  147. package/dist/feedback/feedback-thread.js.map +1 -1
  148. package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-NhUm7Zga.d.ts} +70 -5
  149. package/dist/feedback.d.ts +2 -2
  150. package/dist/hooks/use-file-drop.d.ts +10 -8
  151. package/dist/i18n/defaults.d.ts +10 -8
  152. package/dist/i18n/defaults.js +17 -2
  153. package/dist/i18n/defaults.js.map +1 -1
  154. package/dist/i18n/german.d.ts +10 -8
  155. package/dist/i18n/german.js +50 -5
  156. package/dist/i18n/german.js.map +1 -1
  157. package/dist/i18n/kit-labels.d.ts +10 -8
  158. package/dist/i18n/kit-labels.js +8 -2
  159. package/dist/i18n/kit-labels.js.map +1 -1
  160. package/dist/i18n/languages.d.ts +10 -8
  161. package/dist/i18n/locales/de-CH.d.ts +10 -8
  162. package/dist/i18n/locales/en.d.ts +10 -8
  163. package/dist/i18n/locales/en.js +9 -0
  164. package/dist/i18n/locales/en.js.map +1 -1
  165. package/dist/i18n/locales/es.d.ts +10 -8
  166. package/dist/i18n/locales/es.js +48 -5
  167. package/dist/i18n/locales/es.js.map +1 -1
  168. package/dist/i18n/locales/fr.d.ts +10 -8
  169. package/dist/i18n/locales/fr.js +48 -5
  170. package/dist/i18n/locales/fr.js.map +1 -1
  171. package/dist/i18n/locales/hu.d.ts +10 -8
  172. package/dist/i18n/locales/hu.js +49 -5
  173. package/dist/i18n/locales/hu.js.map +1 -1
  174. package/dist/i18n/locales/it.d.ts +10 -8
  175. package/dist/i18n/locales/it.js +49 -5
  176. package/dist/i18n/locales/it.js.map +1 -1
  177. package/dist/i18n/locales/zh.d.ts +10 -8
  178. package/dist/i18n/locales/zh.js +48 -5
  179. package/dist/i18n/locales/zh.js.map +1 -1
  180. package/dist/i18n/review.d.ts +10 -8
  181. package/dist/i18n/review.js +8 -1
  182. package/dist/i18n/review.js.map +1 -1
  183. package/dist/index.d.ts +17 -10
  184. package/dist/index.js +57 -2
  185. package/dist/index.js.map +1 -1
  186. package/dist/{kit-labels-D1J2_jyD.d.ts → kit-labels-R8mIAc9N.d.ts} +811 -8
  187. package/dist/lib/countries.d.ts +43 -0
  188. package/dist/lib/countries.js +39 -0
  189. package/dist/lib/countries.js.map +1 -0
  190. package/dist/lib/decimal-marks.d.ts +46 -0
  191. package/dist/lib/decimal-marks.js +40 -0
  192. package/dist/lib/decimal-marks.js.map +1 -0
  193. package/dist/lib/iban.d.ts +98 -0
  194. package/dist/lib/iban.js +154 -0
  195. package/dist/lib/iban.js.map +1 -0
  196. package/dist/lib/isin.d.ts +30 -0
  197. package/dist/lib/isin.js +36 -0
  198. package/dist/lib/isin.js.map +1 -0
  199. package/dist/lib/phone.d.ts +69 -0
  200. package/dist/lib/phone.js +121 -0
  201. package/dist/lib/phone.js.map +1 -0
  202. package/dist/rhf/fields.d.ts +148 -11
  203. package/dist/rhf/fields.js +332 -3
  204. package/dist/rhf/fields.js.map +1 -1
  205. package/dist/rhf/form.d.ts +10 -8
  206. package/dist/rhf.d.ts +13 -9
  207. package/dist/rhf.js.map +1 -1
  208. package/dist/shell/app-shell.d.ts +10 -8
  209. package/dist/shell/top-bar-brand.d.ts +10 -8
  210. package/dist/shell.d.ts +10 -8
  211. package/dist/tour/tour.js +20 -24
  212. package/dist/tour/tour.js.map +1 -1
  213. package/dist/wizard/stepper-nav.d.ts +10 -8
  214. package/dist/wizard.d.ts +10 -8
  215. package/package.json +1 -1
  216. package/src/components/account-chips.tsx +17 -3
  217. package/src/components/account-settings.tsx +92 -26
  218. package/src/components/amount-input.tsx +49 -56
  219. package/src/components/autocomplete.tsx +133 -110
  220. package/src/components/checkbox-group.tsx +229 -0
  221. package/src/components/checkbox.tsx +72 -8
  222. package/src/components/chip.tsx +80 -10
  223. package/src/components/combobox.tsx +292 -253
  224. package/src/components/country-select.tsx +443 -0
  225. package/src/components/currency-select.tsx +109 -33
  226. package/src/components/danger-confirm.tsx +345 -77
  227. package/src/components/data-table-labels.ts +16 -1
  228. package/src/components/data-table.tsx +175 -6
  229. package/src/components/date-picker.tsx +173 -62
  230. package/src/components/entity-combobox.tsx +107 -87
  231. package/src/components/field-anatomy.tsx +190 -0
  232. package/src/components/field-parts.tsx +184 -0
  233. package/src/components/file-button.tsx +36 -3
  234. package/src/components/file-dropzone.tsx +106 -38
  235. package/src/components/form-actions.tsx +115 -2
  236. package/src/components/iban-input.tsx +231 -0
  237. package/src/components/icon-picker.tsx +26 -4
  238. package/src/components/inline-edit-field.tsx +533 -0
  239. package/src/components/language-select.tsx +78 -0
  240. package/src/components/line-items.tsx +35 -2
  241. package/src/components/list.tsx +55 -6
  242. package/src/components/money-field.tsx +10 -0
  243. package/src/components/month-picker.tsx +399 -63
  244. package/src/components/multi-select.tsx +80 -58
  245. package/src/components/number-field.tsx +28 -46
  246. package/src/components/number-input.tsx +210 -43
  247. package/src/components/one-time-code-input.tsx +288 -0
  248. package/src/components/phone-input.tsx +299 -0
  249. package/src/components/sign-chip.tsx +103 -0
  250. package/src/components/swatch-picker.tsx +118 -30
  251. package/src/components/switch.tsx +58 -7
  252. package/src/components/tile-radio.tsx +71 -16
  253. package/src/components/time-input.tsx +8 -1
  254. package/src/components/toggle-group.tsx +206 -73
  255. package/src/components/ui.tsx +472 -105
  256. package/src/data-table.ts +2 -1
  257. package/src/feedback/feedback-attachment.tsx +313 -9
  258. package/src/feedback/feedback-dialog.tsx +2 -0
  259. package/src/feedback/feedback-thread.tsx +66 -5
  260. package/src/i18n/defaults.ts +16 -1
  261. package/src/i18n/german.ts +47 -0
  262. package/src/i18n/kit-labels.tsx +78 -2
  263. package/src/i18n/locales/en.ts +10 -0
  264. package/src/i18n/locales/es.ts +46 -0
  265. package/src/i18n/locales/fr.ts +45 -0
  266. package/src/i18n/locales/hu.ts +46 -0
  267. package/src/i18n/locales/it.ts +47 -0
  268. package/src/i18n/locales/zh.ts +44 -0
  269. package/src/i18n/review.ts +7 -0
  270. package/src/index.ts +53 -3
  271. package/src/lib/countries.ts +91 -0
  272. package/src/lib/decimal-marks.ts +99 -0
  273. package/src/lib/iban.ts +171 -0
  274. package/src/lib/isin.ts +62 -0
  275. package/src/lib/phone.ts +211 -0
  276. package/src/rhf/fields.tsx +606 -9
  277. package/src/rhf.ts +2 -1
  278. package/src/tour/tour.tsx +66 -28
@@ -10,6 +10,14 @@ import { Tooltip } from "./tooltip.js";
10
10
  import { DEFAULT_COMMON_LABELS, useKitLabels, useKitLink } from "../i18n/kit-labels.js";
11
11
  import { pickLinkRenderer, replacingClick, routerLinkNavigation } from "./text-link.js";
12
12
  import { useCommitReason } from "./write-lock.js";
13
+ import { mergeDescribedBy } from "./choice-parts.js";
14
+ import {
15
+ EndHintRow,
16
+ FieldCaption,
17
+ LockedReason,
18
+ useFieldHint,
19
+ useLockReason
20
+ } from "./field-parts.js";
13
21
  const BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:opacity-50 disabled:cursor-not-allowed";
14
22
  const BUTTON_SIZES = {
15
23
  md: "gap-2 px-3 py-2 text-sm",
@@ -766,7 +774,77 @@ function resolvePasswordRevealLabels(partial) {
766
774
  if (!partial) return DEFAULT_PASSWORD_REVEAL_LABELS;
767
775
  return { ...DEFAULT_PASSWORD_REVEAL_LABELS, ...partial };
768
776
  }
777
+ const DEFAULT_CHARACTER_COUNT_LABELS = {
778
+ count: (used, max) => `${used} of ${max} characters`,
779
+ remaining: (left) => left === 1 ? "1 character left" : `${left} characters left`,
780
+ limitReached: "Character limit reached"
781
+ };
782
+ function textLength(value) {
783
+ return value === void 0 || value === null ? 0 : String(value).length;
784
+ }
785
+ function useTypedLength(value, defaultValue) {
786
+ const [typed, setTyped] = useState(() => textLength(defaultValue));
787
+ return { used: value !== void 0 ? textLength(value) : typed, track: (text) => setTyped(text.length) };
788
+ }
789
+ function nearLimitAt(max) {
790
+ return Math.min(20, Math.max(1, Math.ceil(max * 0.1)));
791
+ }
792
+ function countZone(used, max) {
793
+ if (used >= max) return "limit";
794
+ return max - used <= nearLimitAt(max) ? "near" : "ok";
795
+ }
796
+ function CharacterCount({
797
+ id,
798
+ used,
799
+ max,
800
+ labels,
801
+ caption
802
+ }) {
803
+ const zone = countZone(used, max);
804
+ const [said, setSaid] = useState({ zone, text: "" });
805
+ if (said.zone !== zone) {
806
+ setSaid({
807
+ zone,
808
+ text: zone === "near" ? labels.remaining(max - used) : zone === "limit" ? labels.limitReached : ""
809
+ });
810
+ }
811
+ return (
812
+ // `relative` holds the sr-only region (see sr-only-containment.test).
813
+ /* @__PURE__ */ jsxs("div", { className: "relative mt-1 flex items-start gap-2", children: [
814
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: caption }),
815
+ /* @__PURE__ */ jsxs(
816
+ "span",
817
+ {
818
+ "aria-hidden": true,
819
+ className: cn(
820
+ "shrink-0 text-[11px] leading-tight tabular-nums",
821
+ zone === "limit" ? "text-[var(--danger)]" : zone === "near" ? "text-[var(--warning)]" : "text-[var(--text-muted)]"
822
+ ),
823
+ children: [
824
+ used,
825
+ "/",
826
+ max
827
+ ]
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsx("span", { id, hidden: true, children: labels.count(used, max) }),
831
+ /* @__PURE__ */ jsx("span", { role: "status", className: "sr-only", children: said.text })
832
+ ] })
833
+ );
834
+ }
835
+ function fieldBelow(hint, counter, errorEl) {
836
+ if (counter === null && hint.captionText === void 0 && errorEl === null) return null;
837
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
838
+ counter ?? /* @__PURE__ */ jsx(FieldCaption, { parts: hint }),
839
+ errorEl
840
+ ] });
841
+ }
842
+ const FLOATING_PLACEHOLDER_ON_FOCUS = "focus:placeholder:text-[var(--text-placeholder)]";
843
+ const hasPlaceholderText = (placeholder) => placeholder !== void 0 && placeholder.trim() !== "";
769
844
  const PICKER_TYPES = /* @__PURE__ */ new Set(["date", "datetime-local", "month", "time", "week"]);
845
+ function useCharacterCountLabels(prop) {
846
+ return useKitLabels("characterCount", DEFAULT_CHARACTER_COUNT_LABELS, prop);
847
+ }
770
848
  const Input = forwardRef(function Input2(props, ref) {
771
849
  const {
772
850
  className,
@@ -778,18 +856,43 @@ const Input = forwardRef(function Input2(props, ref) {
778
856
  variant = "field",
779
857
  invalid,
780
858
  error,
859
+ hint,
860
+ showCount,
861
+ countLabels,
781
862
  passwordLabels,
782
863
  ...rest
783
864
  } = props;
784
- const reserve = "error" in props;
865
+ const reserve = "error" in props || "hint" in props || "showCount" in props;
785
866
  const generated = useId();
786
867
  const fieldId = id ?? generated;
868
+ const countId = useId();
869
+ const hintParts = useFieldHint(hint, rest["aria-describedby"]);
870
+ const maxLength = rest.maxLength;
871
+ const counting = Boolean(showCount) && typeof maxLength === "number" && maxLength > 0;
872
+ const length = useTypedLength(rest.value, rest.defaultValue);
873
+ const countText = useCharacterCountLabels(countLabels);
787
874
  const { isInvalid, describedBy, errorEl } = useFieldError(
788
875
  error,
789
876
  invalid,
790
- rest["aria-describedby"],
877
+ // Caller's, caption, count — then the error, appended by the hook.
878
+ mergeDescribedBy(hintParts.describedBy, counting && countId),
791
879
  rest["aria-invalid"]
792
880
  );
881
+ const counter = counting ? /* @__PURE__ */ jsx(
882
+ CharacterCount,
883
+ {
884
+ id: countId,
885
+ used: length.used,
886
+ max: maxLength,
887
+ labels: countText,
888
+ caption: /* @__PURE__ */ jsx(FieldCaption, { parts: hintParts, className: "mt-0" })
889
+ }
890
+ ) : null;
891
+ const below = fieldBelow(hintParts, counter, errorEl);
892
+ const onChange = counting ? (e) => {
893
+ length.track(e.target.value);
894
+ rest.onChange?.(e);
895
+ } : rest.onChange;
793
896
  const asDisplay = useMediaQuery(PHONE_QUERY, false) && variant === "display";
794
897
  const isPassword = type === "password";
795
898
  const [revealed, setRevealed] = useState(false);
@@ -815,8 +918,10 @@ const Input = forwardRef(function Input2(props, ref) {
815
918
  }
816
919
  ) : null;
817
920
  if (label === void 0) {
921
+ const endHint = hintParts.labelHint;
922
+ const ownClass = endHint === void 0 ? className : void 0;
818
923
  if (!isPassword) {
819
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsx(
924
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsx(EndHintRow, { hint: endHint, className, children: /* @__PURE__ */ jsx(
820
925
  "input",
821
926
  {
822
927
  ref,
@@ -827,11 +932,12 @@ const Input = forwardRef(function Input2(props, ref) {
827
932
  "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
828
933
  "aria-describedby": describedBy,
829
934
  onClick: handleClick,
830
- className: cn(FIELD_BASE, className, inputClassName, isInvalid && FIELD_INVALID)
935
+ onChange,
936
+ className: cn(FIELD_BASE, ownClass, inputClassName, isInvalid && FIELD_INVALID)
831
937
  }
832
- ) });
938
+ ) }) });
833
939
  }
834
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
940
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsx(EndHintRow, { hint: endHint, className, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", ownClass), children: [
835
941
  /* @__PURE__ */ jsx(
836
942
  "input",
837
943
  {
@@ -842,41 +948,74 @@ const Input = forwardRef(function Input2(props, ref) {
842
948
  ...rest,
843
949
  "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
844
950
  "aria-describedby": describedBy,
951
+ onChange,
845
952
  className: cn(FIELD_BASE, "pe-9", inputClassName, isInvalid && FIELD_INVALID)
846
953
  }
847
954
  ),
848
955
  revealToggle
849
- ] }) });
956
+ ] }) }) });
850
957
  }
851
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsxs(FloatingField, { className, htmlFor: fieldId, label, srOnlyLabel: asDisplay, children: [
852
- /* @__PURE__ */ jsx(
853
- "input",
854
- {
855
- ref,
856
- id: fieldId,
857
- type: effectiveType,
858
- placeholder: asDisplay ? placeholder ?? (typeof label === "string" ? label : " ") : " ",
859
- ...rest,
860
- "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
861
- "aria-describedby": describedBy,
862
- onClick: handleClick,
863
- className: cn(
864
- asDisplay ? cn(FIELD_DISPLAY, "text-xl font-semibold leading-snug") : FLOATING_INPUT_CLASS,
865
- isPassword && "pe-9",
866
- inputClassName,
867
- isInvalid && FIELD_INVALID
868
- )
869
- }
870
- ),
871
- revealToggle
872
- ] }) });
958
+ const ownPlaceholder = hasPlaceholderText(placeholder);
959
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsxs(
960
+ FloatingField,
961
+ {
962
+ className,
963
+ htmlFor: fieldId,
964
+ label,
965
+ srOnlyLabel: asDisplay,
966
+ hint: hintParts.labelHint,
967
+ children: [
968
+ /* @__PURE__ */ jsx(
969
+ "input",
970
+ {
971
+ ref,
972
+ id: fieldId,
973
+ type: effectiveType,
974
+ placeholder: asDisplay ? placeholder ?? (typeof label === "string" ? label : " ") : ownPlaceholder ? placeholder : " ",
975
+ ...rest,
976
+ "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
977
+ "aria-describedby": describedBy,
978
+ onClick: handleClick,
979
+ onChange,
980
+ className: cn(
981
+ asDisplay ? cn(FIELD_DISPLAY, "text-xl font-semibold leading-snug") : cn(FLOATING_INPUT_CLASS, ownPlaceholder && FLOATING_PLACEHOLDER_ON_FOCUS),
982
+ isPassword && "pe-9",
983
+ inputClassName,
984
+ isInvalid && FIELD_INVALID
985
+ )
986
+ }
987
+ ),
988
+ revealToggle
989
+ ]
990
+ }
991
+ ) });
873
992
  });
874
993
  Input.displayName = "Input";
875
994
  const SELECT_SM = "h-7 py-0 ps-2 pe-7 text-xs";
995
+ const LOCKED_SELECT_KEYS = /* @__PURE__ */ new Set(["Tab", "Escape", "Shift"]);
996
+ const FIELD_LOCKED = "cursor-not-allowed bg-[var(--bg-surface-2)] text-[var(--text-muted)]";
876
997
  const Select = forwardRef(function Select2(props, ref) {
877
- const { className, label, id, children, invalid, error, hint, size, selectClassName, ...rest } = props;
878
- const reserve = "error" in props;
998
+ const {
999
+ className,
1000
+ label,
1001
+ id,
1002
+ children,
1003
+ invalid,
1004
+ error,
1005
+ hint,
1006
+ size,
1007
+ selectClassName,
1008
+ disabledReason,
1009
+ commit,
1010
+ onMouseDown,
1011
+ onKeyDown,
1012
+ onChange,
1013
+ ...rest
1014
+ } = props;
1015
+ const reserve = "error" in props || "hint" in props || "showCount" in props;
879
1016
  const generated = useId();
1017
+ const lock = useLockReason(commit, disabledReason);
1018
+ const locked = lock.locked;
880
1019
  const nativeSize = typeof size === "number" ? size : void 0;
881
1020
  const listBox = nativeSize !== void 0 && nativeSize > 1 || Boolean(rest.multiple);
882
1021
  const small = size === "sm" && label === void 0;
@@ -887,16 +1026,29 @@ const Select = forwardRef(function Select2(props, ref) {
887
1026
  error,
888
1027
  invalid,
889
1028
  // The caption goes in BEFORE the error — the standing advice first, the news
890
- // second, the order useFieldError keeps for a caller's own description.
891
- textHint ? rest["aria-describedby"] ? `${rest["aria-describedby"]} ${hintId}` : hintId : rest["aria-describedby"],
1029
+ // second, the order useFieldError keeps for a caller's own description. A lock's
1030
+ // reason sits between the two.
1031
+ mergeDescribedBy(rest["aria-describedby"], textHint && hintId, locked && lock.reasonId),
892
1032
  rest["aria-invalid"]
893
1033
  );
894
1034
  const below = textHint || errorEl !== null ? /* @__PURE__ */ jsxs(Fragment, { children: [
895
1035
  textHint && /* @__PURE__ */ jsx("p", { id: hintId, className: "mt-1 text-[11px] leading-tight text-[var(--text-muted)]", children: hint }),
896
1036
  errorEl
897
1037
  ] }) : null;
898
- const chevron = rest.disabled || listBox ? null : /* @__PURE__ */ jsx(FieldChevron, { className: small ? "end-1.5 size-3.5" : void 0 });
1038
+ const chevron = rest.disabled || listBox || locked ? null : /* @__PURE__ */ jsx(FieldChevron, { className: small ? "end-1.5 size-3.5" : void 0 });
899
1039
  const dress = listBox ? "overflow-y-auto [&_option]:py-1" : "appearance-none pe-9";
1040
+ const interaction = locked ? {
1041
+ disabled: void 0,
1042
+ "aria-disabled": true,
1043
+ onMouseDown: (e) => e.preventDefault(),
1044
+ onKeyDown: (e) => {
1045
+ if (!LOCKED_SELECT_KEYS.has(e.key) && !e.ctrlKey && !e.metaKey) e.preventDefault();
1046
+ },
1047
+ onChange: () => {
1048
+ }
1049
+ } : { onMouseDown, onKeyDown, onChange };
1050
+ const boxClass = locked ? void 0 : className;
1051
+ const withReason = (box) => locked ? /* @__PURE__ */ jsx(LockedReason, { lock, className: cn("block", className), children: box }) : box;
900
1052
  if (label === void 0) {
901
1053
  const select = /* @__PURE__ */ jsx(
902
1054
  "select",
@@ -905,6 +1057,7 @@ const Select = forwardRef(function Select2(props, ref) {
905
1057
  id,
906
1058
  size: nativeSize,
907
1059
  ...rest,
1060
+ ...interaction,
908
1061
  "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
909
1062
  "aria-describedby": describedBy,
910
1063
  className: cn(
@@ -912,6 +1065,7 @@ const Select = forwardRef(function Select2(props, ref) {
912
1065
  dress,
913
1066
  listBox && "py-0",
914
1067
  small && SELECT_SM,
1068
+ locked && FIELD_LOCKED,
915
1069
  selectClassName,
916
1070
  isInvalid && FIELD_INVALID
917
1071
  ),
@@ -919,60 +1073,100 @@ const Select = forwardRef(function Select2(props, ref) {
919
1073
  }
920
1074
  );
921
1075
  if (hasContent(hint) && !textHint) {
922
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5", className), children: [
923
- /* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
924
- select,
925
- chevron
926
- ] }),
927
- /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: hint })
928
- ] }) });
1076
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: withReason(
1077
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5", boxClass), children: [
1078
+ /* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
1079
+ select,
1080
+ chevron
1081
+ ] }),
1082
+ /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", children: hint })
1083
+ ] })
1084
+ ) });
929
1085
  }
930
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsxs("div", { className: cn("relative", className), children: [
931
- select,
932
- chevron
933
- ] }) });
1086
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: withReason(
1087
+ /* @__PURE__ */ jsxs("div", { className: cn("relative", boxClass), children: [
1088
+ select,
1089
+ chevron
1090
+ ] })
1091
+ ) });
934
1092
  }
935
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsxs(FloatingField, { className, htmlFor: fieldId, label, staticLabel: true, hint: textHint ? void 0 : hint, children: [
936
- /* @__PURE__ */ jsx(
937
- "select",
1093
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: withReason(
1094
+ /* @__PURE__ */ jsxs(
1095
+ FloatingField,
938
1096
  {
939
- ref,
940
- id: fieldId,
941
- size: nativeSize,
942
- ...rest,
943
- "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
944
- "aria-describedby": describedBy,
945
- className: cn(
946
- FIELD_BASE,
947
- // The floated label takes the top strip of a list box too, so its first
948
- // row starts under the label rather than behind it. No bottom padding — see
949
- // `dress`.
950
- listBox ? "pt-5 pb-0" : FIELD_FLOATING_PAD,
951
- "peer",
952
- dress,
953
- selectClassName,
954
- isInvalid && FIELD_INVALID
955
- ),
956
- children
1097
+ className: boxClass,
1098
+ htmlFor: fieldId,
1099
+ label,
1100
+ staticLabel: true,
1101
+ hint: textHint ? void 0 : hint,
1102
+ children: [
1103
+ /* @__PURE__ */ jsx(
1104
+ "select",
1105
+ {
1106
+ ref,
1107
+ id: fieldId,
1108
+ size: nativeSize,
1109
+ ...rest,
1110
+ ...interaction,
1111
+ "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
1112
+ "aria-describedby": describedBy,
1113
+ className: cn(
1114
+ FIELD_BASE,
1115
+ // The floated label takes the top strip of a list box too, so its first
1116
+ // row starts under the label rather than behind it. No bottom padding — see
1117
+ // `dress`.
1118
+ listBox ? "pt-5 pb-0" : FIELD_FLOATING_PAD,
1119
+ "peer",
1120
+ dress,
1121
+ locked && FIELD_LOCKED,
1122
+ selectClassName,
1123
+ isInvalid && FIELD_INVALID
1124
+ ),
1125
+ children
1126
+ }
1127
+ ),
1128
+ chevron
1129
+ ]
957
1130
  }
958
- ),
959
- chevron
960
- ] }) });
1131
+ )
1132
+ ) });
961
1133
  });
962
1134
  Select.displayName = "Select";
963
1135
  const Textarea = forwardRef(function Textarea2(props, ref) {
964
- const { className, label, id, placeholder, invalid, error, ...rest } = props;
965
- const reserve = "error" in props;
1136
+ const { className, label, id, placeholder, invalid, error, hint, showCount, countLabels, ...rest } = props;
1137
+ const reserve = "error" in props || "hint" in props || "showCount" in props;
966
1138
  const generated = useId();
967
1139
  const fieldId = id ?? generated;
1140
+ const countId = useId();
1141
+ const hintParts = useFieldHint(hint, rest["aria-describedby"]);
1142
+ const maxLength = rest.maxLength;
1143
+ const counting = Boolean(showCount) && typeof maxLength === "number" && maxLength > 0;
1144
+ const length = useTypedLength(rest.value, rest.defaultValue);
1145
+ const countText = useCharacterCountLabels(countLabels);
968
1146
  const { isInvalid, describedBy, errorEl } = useFieldError(
969
1147
  error,
970
1148
  invalid,
971
- rest["aria-describedby"],
1149
+ mergeDescribedBy(hintParts.describedBy, counting && countId),
972
1150
  rest["aria-invalid"]
973
1151
  );
1152
+ const counter = counting ? /* @__PURE__ */ jsx(
1153
+ CharacterCount,
1154
+ {
1155
+ id: countId,
1156
+ used: length.used,
1157
+ max: maxLength,
1158
+ labels: countText,
1159
+ caption: /* @__PURE__ */ jsx(FieldCaption, { parts: hintParts, className: "mt-0" })
1160
+ }
1161
+ ) : null;
1162
+ const below = fieldBelow(hintParts, counter, errorEl);
1163
+ const onChange = counting ? (e) => {
1164
+ length.track(e.target.value);
1165
+ rest.onChange?.(e);
1166
+ } : rest.onChange;
974
1167
  if (label === void 0) {
975
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsx(
1168
+ const endHint = hintParts.labelHint;
1169
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsx(EndHintRow, { hint: endHint, className, children: /* @__PURE__ */ jsx(
976
1170
  "textarea",
977
1171
  {
978
1172
  ref,
@@ -981,20 +1175,27 @@ const Textarea = forwardRef(function Textarea2(props, ref) {
981
1175
  ...rest,
982
1176
  "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
983
1177
  "aria-describedby": describedBy,
984
- className: cn(FIELD_BASE, className, isInvalid && FIELD_INVALID)
1178
+ onChange,
1179
+ className: cn(FIELD_BASE, endHint === void 0 && className, isInvalid && FIELD_INVALID)
985
1180
  }
986
- ) });
1181
+ ) }) });
987
1182
  }
988
- return /* @__PURE__ */ jsx(FieldGroup, { errorEl, reserve, children: /* @__PURE__ */ jsx(FloatingField, { className, htmlFor: fieldId, label, children: /* @__PURE__ */ jsx(
1183
+ const ownPlaceholder = hasPlaceholderText(placeholder);
1184
+ return /* @__PURE__ */ jsx(FieldGroup, { errorEl: below, reserve, children: /* @__PURE__ */ jsx(FloatingField, { className, htmlFor: fieldId, label, hint: hintParts.labelHint, children: /* @__PURE__ */ jsx(
989
1185
  "textarea",
990
1186
  {
991
1187
  ref,
992
1188
  id: fieldId,
993
- placeholder: " ",
1189
+ placeholder: ownPlaceholder ? placeholder : " ",
994
1190
  ...rest,
995
1191
  "aria-invalid": isInvalid || rest["aria-invalid"] || void 0,
996
1192
  "aria-describedby": describedBy,
997
- className: cn(FLOATING_INPUT_CLASS, isInvalid && FIELD_INVALID)
1193
+ onChange,
1194
+ className: cn(
1195
+ FLOATING_INPUT_CLASS,
1196
+ ownPlaceholder && FLOATING_PLACEHOLDER_ON_FOCUS,
1197
+ isInvalid && FIELD_INVALID
1198
+ )
998
1199
  }
999
1200
  ) }) });
1000
1201
  });
@@ -1546,6 +1747,7 @@ export {
1546
1747
  CardFooter,
1547
1748
  CardHeader,
1548
1749
  CardTitle,
1750
+ DEFAULT_CHARACTER_COUNT_LABELS,
1549
1751
  DEFAULT_PASSWORD_REVEAL_LABELS,
1550
1752
  DEFAULT_TABS_LABELS,
1551
1753
  EmptyState,