@godxjp/ui 20.0.0 → 20.1.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 (250) hide show
  1. package/README.md +8 -2
  2. package/dist/app/theme-axes.d.ts +2 -0
  3. package/dist/app/theme-axes.js +45 -0
  4. package/dist/components/charts/chart-cartesian.d.ts +2 -1
  5. package/dist/components/charts/chart-cartesian.js +52 -3
  6. package/dist/components/charts/chart-category-axis.d.ts +55 -0
  7. package/dist/components/charts/chart-category-axis.js +93 -0
  8. package/dist/components/charts/chart-frame.d.ts +10 -1
  9. package/dist/components/charts/chart-frame.js +19 -3
  10. package/dist/components/charts/compact-bar-trend.d.ts +1 -1
  11. package/dist/components/charts/compact-bar-trend.js +2 -0
  12. package/dist/components/charts/pie-chart.d.ts +1 -1
  13. package/dist/components/charts/pie-chart.js +12 -1
  14. package/dist/components/charts/recharts-peer.d.ts +49 -0
  15. package/dist/components/charts/recharts-peer.js +45 -0
  16. package/dist/components/data-display/card.d.ts +12 -1
  17. package/dist/components/data-display/card.js +22 -4
  18. package/dist/components/data-display/code-block.js +6 -2
  19. package/dist/components/data-display/data-table.d.ts +39 -4
  20. package/dist/components/data-display/data-table.js +802 -270
  21. package/dist/components/data-display/descriptions.d.ts +27 -6
  22. package/dist/components/data-display/descriptions.js +58 -17
  23. package/dist/components/data-display/index.d.ts +2 -0
  24. package/dist/components/data-display/index.js +2 -0
  25. package/dist/components/data-display/list-row.d.ts +10 -1
  26. package/dist/components/data-display/list-row.js +7 -1
  27. package/dist/components/data-display/popover.d.ts +2 -2
  28. package/dist/components/data-display/popover.js +61 -11
  29. package/dist/components/data-display/range-timeline.d.ts +38 -0
  30. package/dist/components/data-display/range-timeline.js +161 -0
  31. package/dist/components/data-display/scroll-area.js +13 -2
  32. package/dist/components/data-display/service-launcher-card.js +10 -10
  33. package/dist/components/data-display/table.d.ts +11 -3
  34. package/dist/components/data-display/table.js +48 -18
  35. package/dist/components/data-display/tree-list.js +4 -9
  36. package/dist/components/data-entry/calendar.d.ts +2 -2
  37. package/dist/components/data-entry/calendar.js +12 -1
  38. package/dist/components/data-entry/cascader.d.ts +1 -1
  39. package/dist/components/data-entry/cascader.js +188 -24
  40. package/dist/components/data-entry/color-picker.d.ts +1 -1
  41. package/dist/components/data-entry/color-picker.js +17 -5
  42. package/dist/components/data-entry/control-appearance.d.ts +64 -0
  43. package/dist/components/data-entry/control-appearance.js +39 -0
  44. package/dist/components/data-entry/control-surface.d.ts +61 -0
  45. package/dist/components/data-entry/control-surface.js +39 -0
  46. package/dist/components/data-entry/date-picker.d.ts +1 -1
  47. package/dist/components/data-entry/date-picker.js +344 -113
  48. package/dist/components/data-entry/date-range-picker.d.ts +1 -1
  49. package/dist/components/data-entry/date-range-picker.js +278 -140
  50. package/dist/components/data-entry/form-field.d.ts +1 -1
  51. package/dist/components/data-entry/form-field.js +39 -4
  52. package/dist/components/data-entry/form.d.ts +4 -0
  53. package/dist/components/data-entry/form.js +4 -2
  54. package/dist/components/data-entry/index.d.ts +7 -3
  55. package/dist/components/data-entry/index.js +10 -1
  56. package/dist/components/data-entry/input-otp.d.ts +1 -1
  57. package/dist/components/data-entry/input.d.ts +12 -34
  58. package/dist/components/data-entry/input.js +92 -24
  59. package/dist/components/data-entry/month-picker.d.ts +1 -1
  60. package/dist/components/data-entry/month-picker.js +47 -10
  61. package/dist/components/data-entry/month-range-picker.d.ts +1 -1
  62. package/dist/components/data-entry/month-range-picker.js +51 -11
  63. package/dist/components/data-entry/number-input.d.ts +7 -0
  64. package/dist/components/data-entry/number-input.js +147 -98
  65. package/dist/components/data-entry/password-input.d.ts +1 -1
  66. package/dist/components/data-entry/radio.d.ts +1 -1
  67. package/dist/components/data-entry/radio.js +61 -13
  68. package/dist/components/data-entry/search-input.d.ts +1 -1
  69. package/dist/components/data-entry/search-input.js +16 -2
  70. package/dist/components/data-entry/search-select.d.ts +2 -2
  71. package/dist/components/data-entry/search-select.js +209 -78
  72. package/dist/components/data-entry/select.d.ts +22 -4
  73. package/dist/components/data-entry/select.js +215 -163
  74. package/dist/components/data-entry/slider.d.ts +9 -1
  75. package/dist/components/data-entry/slider.js +87 -9
  76. package/dist/components/data-entry/switch.d.ts +3 -0
  77. package/dist/components/data-entry/switch.js +29 -3
  78. package/dist/components/data-entry/textarea.d.ts +14 -56
  79. package/dist/components/data-entry/textarea.js +80 -33
  80. package/dist/components/data-entry/time-picker.d.ts +2 -2
  81. package/dist/components/data-entry/time-picker.js +333 -109
  82. package/dist/components/data-entry/time-range-picker.d.ts +5 -0
  83. package/dist/components/data-entry/time-range-picker.js +89 -0
  84. package/dist/components/data-entry/transfer.d.ts +1 -1
  85. package/dist/components/data-entry/transfer.js +86 -28
  86. package/dist/components/data-entry/tree-select.d.ts +1 -1
  87. package/dist/components/data-entry/tree-select.js +201 -113
  88. package/dist/components/data-entry/tree-utils.js +7 -14
  89. package/dist/components/data-entry/upload-files.d.ts +2 -0
  90. package/dist/components/data-entry/upload-files.js +31 -0
  91. package/dist/components/data-entry/upload-request.d.ts +4 -0
  92. package/dist/components/data-entry/upload-request.js +53 -0
  93. package/dist/components/data-entry/upload-types.d.ts +28 -0
  94. package/dist/components/data-entry/upload-types.js +2 -0
  95. package/dist/components/data-entry/upload.d.ts +2 -2
  96. package/dist/components/data-entry/upload.js +475 -115
  97. package/dist/components/feedback/dialog.js +16 -19
  98. package/dist/components/general/button.d.ts +1 -1
  99. package/dist/components/general/button.js +5 -1
  100. package/dist/components/general/index.d.ts +1 -0
  101. package/dist/components/general/index.js +2 -0
  102. package/dist/components/general/typography.d.ts +3 -0
  103. package/dist/components/general/typography.js +15 -1
  104. package/dist/components/general/visually-hidden.d.ts +3 -0
  105. package/dist/components/general/visually-hidden.js +9 -0
  106. package/dist/components/layout/app-launcher.d.ts +34 -0
  107. package/dist/components/layout/app-launcher.js +228 -0
  108. package/dist/components/layout/app-shell.d.ts +2 -0
  109. package/dist/components/layout/app-shell.js +20 -7
  110. package/dist/components/layout/breadcrumb.d.ts +14 -2
  111. package/dist/components/layout/breadcrumb.js +46 -4
  112. package/dist/components/layout/flex.d.ts +1 -1
  113. package/dist/components/layout/flex.js +23 -2
  114. package/dist/components/layout/index.d.ts +3 -0
  115. package/dist/components/layout/index.js +5 -1
  116. package/dist/components/layout/org-switcher.js +20 -1
  117. package/dist/components/layout/page-container.d.ts +1 -1
  118. package/dist/components/layout/page-container.js +5 -3
  119. package/dist/components/layout/responsive-grid.d.ts +16 -2
  120. package/dist/components/layout/responsive-grid.js +29 -2
  121. package/dist/components/layout/topbar-item.d.ts +3 -0
  122. package/dist/components/layout/topbar-item.js +18 -3
  123. package/dist/components/layout/topbar.d.ts +1 -1
  124. package/dist/components/layout/topbar.js +27 -6
  125. package/dist/components/navigation/dropdown-menu.d.ts +30 -2
  126. package/dist/components/navigation/dropdown-menu.js +68 -11
  127. package/dist/components/navigation/pagination.d.ts +2 -2
  128. package/dist/components/navigation/pagination.js +155 -83
  129. package/dist/components/navigation/steps.d.ts +2 -2
  130. package/dist/components/navigation/steps.js +29 -4
  131. package/dist/components/navigation/tabs.d.ts +5 -33
  132. package/dist/components/navigation/tabs.js +232 -64
  133. package/dist/components/ui/input-otp.d.ts +26 -28
  134. package/dist/components/ui/input-otp.js +50 -7
  135. package/dist/components/ui/password-input.d.ts +36 -2
  136. package/dist/components/ui/password-input.js +27 -7
  137. package/dist/components/ui/rating.d.ts +25 -0
  138. package/dist/components/ui/rating.js +67 -27
  139. package/dist/components/ui/segmented.d.ts +9 -0
  140. package/dist/components/ui/segmented.js +17 -2
  141. package/dist/components/ui/separator.d.ts +4 -0
  142. package/dist/components/ui/separator.js +13 -0
  143. package/dist/components/ui/tag-input.d.ts +45 -0
  144. package/dist/components/ui/tag-input.js +109 -27
  145. package/dist/form/form-context.d.ts +29 -0
  146. package/dist/form/form-context.js +44 -4
  147. package/dist/form/form-field-array.d.ts +22 -0
  148. package/dist/form/form-field-array.js +50 -0
  149. package/dist/form/form-field-control.d.ts +2 -1
  150. package/dist/form/form-field-control.js +116 -40
  151. package/dist/form/form-root.d.ts +2 -1
  152. package/dist/form/form-root.js +142 -23
  153. package/dist/form/index.d.ts +3 -1
  154. package/dist/form/index.js +12 -1
  155. package/dist/i18n/messages/en.json +56 -5
  156. package/dist/i18n/messages/ja.json +56 -5
  157. package/dist/i18n/messages/vi.json +56 -5
  158. package/dist/inertia/index.d.ts +20 -0
  159. package/dist/inertia/index.js +60 -1
  160. package/dist/lib/control-styles.d.ts +25 -3
  161. package/dist/lib/control-styles.js +9 -3
  162. package/dist/lib/datetime/picker-format.d.ts +8 -0
  163. package/dist/lib/datetime/picker-format.js +54 -0
  164. package/dist/props/components/charts.prop.d.ts +21 -0
  165. package/dist/props/components/data-display.prop.d.ts +55 -4
  166. package/dist/props/components/data-entry.prop.d.ts +687 -43
  167. package/dist/props/components/form.prop.d.ts +142 -4
  168. package/dist/props/components/general.prop.d.ts +17 -1
  169. package/dist/props/components/index.d.ts +2 -2
  170. package/dist/props/components/layout.prop.d.ts +163 -6
  171. package/dist/props/components/navigation.prop.d.ts +113 -3
  172. package/dist/props/registry.d.ts +376 -10
  173. package/dist/props/registry.js +493 -9
  174. package/dist/props/vocabulary/data.prop.d.ts +179 -1
  175. package/dist/props/vocabulary/index.d.ts +4 -4
  176. package/dist/props/vocabulary/interaction.prop.d.ts +51 -2
  177. package/dist/props/vocabulary/navigation.prop.d.ts +40 -0
  178. package/dist/props/vocabulary/shared.prop.d.ts +33 -0
  179. package/dist/styles/badge-layout.css +2 -1
  180. package/dist/styles/card-layout.css +26 -4
  181. package/dist/styles/chart-layout.css +15 -0
  182. package/dist/styles/control.css +515 -14
  183. package/dist/styles/data-display-layout.css +241 -6
  184. package/dist/styles/data-entry-layout.css +13 -0
  185. package/dist/styles/layout.css +221 -4
  186. package/dist/styles/navigation-layout.css +227 -0
  187. package/dist/styles/shell-layout.css +229 -5
  188. package/dist/styles/table-layout.css +95 -6
  189. package/dist/styles/text-layout.css +5 -0
  190. package/dist/tokens/base.css +1 -0
  191. package/dist/tokens/components/badge.css +1 -0
  192. package/dist/tokens/components/chart.css +6 -0
  193. package/dist/tokens/components/control.css +79 -2
  194. package/dist/tokens/components/data-display.css +9 -0
  195. package/dist/tokens/components/descriptions.css +11 -0
  196. package/dist/tokens/components/flex.css +8 -0
  197. package/dist/tokens/components/navigation.css +55 -0
  198. package/dist/tokens/components/shell.css +50 -2
  199. package/dist/tokens/components/table.css +14 -0
  200. package/dist/tokens/semantic/layout.css +9 -0
  201. package/docs/CONSUMER-RULES.md +13 -0
  202. package/docs/DESIGN-AUTHORITY.md +43 -6
  203. package/docs/DEVELOPMENT.md +4 -3
  204. package/docs/FORMS.md +151 -6
  205. package/docs/FRAME-COVERAGE-REPORT.md +29 -16
  206. package/docs/README.md +1 -1
  207. package/docs/STANDARDS-vocabulary-tokens.md +1 -1
  208. package/docs/TESTING.md +15 -6
  209. package/docs/charts/cjk-category-axis.tsx +81 -0
  210. package/docs/data-display/card/index.tsx +22 -0
  211. package/docs/data-display/data-table/examples/antd-parity.tsx +257 -0
  212. package/docs/data-display/data-table/index.tsx +23 -0
  213. package/docs/data-display/descriptions.tsx +41 -0
  214. package/docs/data-display/scroll-area.tsx +31 -25
  215. package/docs/data-display/table.tsx +104 -45
  216. package/docs/data-display/timeline.tsx +41 -0
  217. package/docs/data-entry/date-picker.tsx +85 -0
  218. package/docs/data-entry/date-range-picker.tsx +26 -0
  219. package/docs/data-entry/form-dynamic-fields.tsx +199 -0
  220. package/docs/data-entry/form.tsx +74 -4
  221. package/docs/data-entry/input-otp.tsx +24 -0
  222. package/docs/data-entry/input.tsx +46 -1
  223. package/docs/data-entry/month-range-picker.tsx +1 -1
  224. package/docs/data-entry/select.tsx +27 -0
  225. package/docs/data-entry/switch.tsx +41 -0
  226. package/docs/data-entry/textarea.tsx +38 -0
  227. package/docs/data-entry/time-picker.tsx +85 -1
  228. package/docs/data-entry/time-range-picker.tsx +55 -0
  229. package/docs/data-entry/transfer.tsx +17 -0
  230. package/docs/data-entry/upload.tsx +56 -12
  231. package/docs/feedback/tooltip.tsx +1 -1
  232. package/docs/general/activity.tsx +2 -2
  233. package/docs/general/button/index.tsx +14 -1
  234. package/docs/general/typography.tsx +14 -1
  235. package/docs/layout/app-launcher.tsx +151 -0
  236. package/docs/layout/app-shell.tsx +11 -0
  237. package/docs/layout/flex.tsx +50 -0
  238. package/docs/layout/responsive-grid.tsx +21 -1
  239. package/docs/layout/topbar.tsx +5 -9
  240. package/docs/navigation/app-setting-picker.tsx +11 -0
  241. package/docs/navigation/breadcrumb.tsx +48 -0
  242. package/docs/navigation/dropdown-menu.tsx +21 -0
  243. package/docs/navigation/pagination.tsx +52 -0
  244. package/docs/navigation/steps.tsx +37 -0
  245. package/docs/navigation/tabs.tsx +97 -1
  246. package/docs/query/button-refetch.tsx +1 -0
  247. package/package.json +19 -2
  248. package/scripts/_agent-setup.mjs +19 -2
  249. package/scripts/init-guinea-pig.mjs +26 -4
  250. package/scripts/ui-audit.mjs +243 -30
@@ -7,6 +7,8 @@ import {
7
7
  TabPanel as AriaTabPanel,
8
8
  Tabs as AriaTabs
9
9
  } from "react-aria-components";
10
+ import { Plus, X } from "lucide-react";
11
+ import { useTranslation } from "../../i18n/use-translation.js";
10
12
  import { cn } from "../../lib/utils.js";
11
13
  import { useKeepActiveTabVisible } from "./tabs-scroll.js";
12
14
  const TabsFrameContext = React.createContext({
@@ -31,9 +33,27 @@ function withDomProps(element, slot, raw, domProps, own) {
31
33
  ...own
32
34
  });
33
35
  }
36
+ function resolveTabsExtra(extra) {
37
+ if (extra === void 0 || extra === null || extra === false) return {};
38
+ const isSlotMap = typeof extra === "object" && !React.isValidElement(extra) && !Array.isArray(extra) && ("start" in extra || "end" in extra);
39
+ if (isSlotMap) return extra;
40
+ return { end: extra };
41
+ }
42
+ function resolveTabsAxis(tabPlacement, orientation) {
43
+ if (tabPlacement) {
44
+ return {
45
+ placement: tabPlacement,
46
+ orientation: orientation ?? (tabPlacement === "start" || tabPlacement === "end" ? "vertical" : "horizontal")
47
+ };
48
+ }
49
+ return {
50
+ placement: orientation === "vertical" ? "start" : "top",
51
+ orientation: orientation ?? "horizontal"
52
+ };
53
+ }
34
54
  function Tabs({
35
55
  className,
36
- orientation = "horizontal",
56
+ orientation,
37
57
  items,
38
58
  value,
39
59
  defaultValue,
@@ -41,95 +61,235 @@ function Tabs({
41
61
  activationMode,
42
62
  dir,
43
63
  variant = "default",
64
+ tabPlacement,
65
+ size = "md",
66
+ centered,
67
+ extra,
68
+ destroyOnHidden = true,
69
+ onEdit,
70
+ addIcon,
71
+ hideAdd,
44
72
  listClassName,
45
73
  contentClassName,
46
74
  children,
47
75
  ...props
48
76
  }) {
77
+ const { t } = useTranslation();
49
78
  const resolvedDefault = resolveFallbackTabValue(items, defaultValue);
79
+ const { placement, orientation: resolvedOrientation } = resolveTabsAxis(
80
+ tabPlacement,
81
+ orientation
82
+ );
50
83
  const selectionSuppressed = value === void 0 && items != null && items.length > 0 && resolvedDefault === void 0;
51
84
  const frame = React.useMemo(
52
- () => ({ orientation, selectionSuppressed }),
53
- [orientation, selectionSuppressed]
85
+ () => ({ orientation: resolvedOrientation, selectionSuppressed }),
86
+ [resolvedOrientation, selectionSuppressed]
87
+ );
88
+ const editable = variant === "editable-card";
89
+ const [mirroredValue, setMirroredValue] = React.useState(
90
+ value ?? resolvedDefault
91
+ );
92
+ React.useEffect(() => {
93
+ if (value !== void 0) setMirroredValue(value);
94
+ }, [value]);
95
+ const activeValue = value ?? mirroredValue;
96
+ const handleValueChange = React.useCallback(
97
+ (next) => {
98
+ setMirroredValue(next);
99
+ onValueChange?.(next);
100
+ },
101
+ [onValueChange]
54
102
  );
103
+ const { start: extraStart, end: extraEnd } = resolveTabsExtra(extra);
104
+ const showAdd = editable && !hideAdd && Boolean(onEdit);
105
+ const needsBar = Boolean(extraStart || extraEnd || showAdd);
106
+ const card = variant === "card" || variant === "editable-card";
107
+ const sizeTriggerClassName = cn(
108
+ size === "sm" && "min-h-[var(--tabs-trigger-height-sm)] px-[var(--tabs-trigger-padding-x-sm)] text-[length:var(--tabs-trigger-font-size-sm)]",
109
+ size === "md" && "min-h-[var(--tabs-trigger-height-md)] px-[var(--tabs-trigger-padding-x-md)] text-[length:var(--tabs-trigger-font-size-md)]",
110
+ size === "lg" && "min-h-[var(--tabs-trigger-height-lg)] px-[var(--tabs-trigger-padding-x-lg)] text-[length:var(--tabs-trigger-font-size-lg)]"
111
+ );
112
+ const list = items ? /* @__PURE__ */ jsx(
113
+ TabsList,
114
+ {
115
+ "data-slot": "tabs-list",
116
+ variant: variant === "line" ? "line" : "default",
117
+ className: cn(
118
+ // The inset override is an arbitrary value reading the knob (never `p-0`): it must
119
+ // stay a UTILITY so tailwind-merge still drops the strip's own padding step.
120
+ //
121
+ // IT IS SPLIT INTO `px-`/`py-` FOR THE FOCUS RING (gh#376). The strip clips its
122
+ // block axis (`overflow-y: hidden` beside `overflow-x: auto`), and this variant's
123
+ // inset is 0px, so a focused trigger had NO block headroom at all — measured in
124
+ // Chromium, 0px top and bottom, i.e. the entire ring was shaved. The headroom has to
125
+ // come from layout: `overflow-y: visible` next to `overflow-x: auto` computes back
126
+ // to `auto` per spec (a second scroll container, not a ring), and Chromium honours
127
+ // `overflow-clip-margin` only when BOTH axes are `clip`. So the block padding carries
128
+ // the ring's outer reach and an equal negative block margin hands it straight back —
129
+ // the ring paints inside the scrollport and the strip's OUTER box is unchanged
130
+ // (measured: 38px before and after). Both values read the same token the ring is
131
+ // sized from, so the two can never drift apart.
132
+ //
133
+ // The cost, recorded rather than hidden: the rail hairline (`border-b`, drawn at the
134
+ // border box) now sits the ring's reach below the triggers instead of flush. That is
135
+ // where a hand-composed <TabsList variant="line"> already put it — its `p-1` gives
136
+ // the same block inset — so the two construction paths now agree. A service that
137
+ // wants the active bar parked back on the hairline raises `--tabs-indicator-offset`,
138
+ // which exists for exactly that.
139
+ variant === "line" && "my-[calc(-1_*_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))] h-auto w-full justify-start border-b px-[var(--tabs-list-line-space-inset)] py-[calc(var(--tabs-list-line-space-inset)_+_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))]",
140
+ // CARD strip. The list keeps `data-variant="default"` on purpose (a hand-composed
141
+ // <TabsList> must be unaffected), so the card face is selected from the ROOT — but the
142
+ // three properties the base list already claims as utilities (`bg-muted`, `p-1`,
143
+ // `rounded-lg`) have to be replaced with utilities too, or the components layer loses.
144
+ card && "w-full items-end justify-start gap-[var(--tabs-card-list-space-gap)] rounded-[var(--tabs-card-list-radius)] p-[var(--tabs-card-list-space-inset)] data-[variant=default]:bg-transparent",
145
+ // CENTERED. Two independent moves, because the strip has two shapes: the pill/card strip
146
+ // is `w-fit` (auto inline margins centre the BOX) and the line strip is `w-full`
147
+ // (`justify-content` centres its CONTENT). `safe` keeps the overflow rule the strip
148
+ // already depends on — plain `center` strands the leading tab outside the scrollport.
149
+ centered && "mx-auto justify-center-safe",
150
+ listClassName
151
+ ),
152
+ children: items.map((item) => {
153
+ const removable = editable && Boolean(onEdit) && item.closable !== false && !item.disabled;
154
+ return /* @__PURE__ */ jsxs(
155
+ TabsTrigger,
156
+ {
157
+ value: item.value,
158
+ disabled: item.disabled,
159
+ "aria-keyshortcuts": removable ? "Delete" : void 0,
160
+ onKeyDown: removable ? (event) => {
161
+ if (event.key !== "Delete" && event.key !== "Backspace") return;
162
+ event.preventDefault();
163
+ onEdit?.(item.value, "remove");
164
+ } : void 0,
165
+ className: cn(
166
+ // Geometry only — the active underline is the token-owned `::after` bar the
167
+ // line variant already owns (never a second, hand-rolled border-b indicator).
168
+ // They are arbitrary-value utilities so tailwind-merge keeps dropping the pill
169
+ // trigger's own radius / padding steps from the base class list.
170
+ variant === "line" && "rounded-[var(--tabs-trigger-line-radius)] px-[var(--tabs-trigger-line-padding-x)] py-[var(--tabs-trigger-line-padding-y)]",
171
+ sizeTriggerClassName,
172
+ // A centred strip must let its triggers SHRINK to their labels first. The base
173
+ // trigger is `flex-1`, so on a full-width (`line`) strip they already fill every
174
+ // pixel and `justify-content` has nothing left to centre — measured, the leading and
175
+ // trailing gaps were both 0px with `centered` on and off.
176
+ centered && "flex-none",
177
+ // CARD face: a real boundary on every side, rounded on the leading block edge only.
178
+ // The ACTIVE face's block-end edge is the SURFACE colour, never `transparent`: the
179
+ // rail is an inset shadow at the strip's padding-box edge, so a see-through border
180
+ // would let that 1px of rail run straight across the tab it is joined to.
181
+ card && "data-[state=active]:border-b-background rounded-[var(--tabs-card-radius)_var(--tabs-card-radius)_0_0] border-[color:hsl(var(--border))] bg-[hsl(var(--tabs-card-background,var(--muted)))]"
182
+ ),
183
+ children: [
184
+ item.icon ? /* @__PURE__ */ jsx(
185
+ "span",
186
+ {
187
+ "data-slot": "tabs-trigger-icon",
188
+ className: "ui-tabs-trigger-icon",
189
+ "aria-hidden": "true",
190
+ children: item.icon
191
+ }
192
+ ) : null,
193
+ item.label,
194
+ removable ? (
195
+ // A POINTER SHORTCUT, not a control — and that is measured, not stylistic. antd's own
196
+ // TabNode puts a real <button> beside the role="tab" element; rendered through
197
+ // vitest-axe that markup fails `aria-required-children`, because a tablist may own
198
+ // nothing but tabs and axe walks straight through the generic wrapper to find the
199
+ // button. Putting the button INSIDE the tab is worse still (`nested-interactive`,
200
+ // plus a <button> in a <button> is invalid HTML). So the × is an aria-hidden span and
201
+ // the real, announced route is the Delete/Backspace shortcut above — the WAI-ARIA APG
202
+ // "tabs with deletion" shape.
203
+ /* @__PURE__ */ jsx(
204
+ "span",
205
+ {
206
+ "data-slot": "tabs-tab-remove",
207
+ className: "ui-tabs-tab-remove",
208
+ "aria-hidden": "true",
209
+ title: t("navigation.tabs.removeTab"),
210
+ onPointerDown: (event) => {
211
+ event.preventDefault();
212
+ event.stopPropagation();
213
+ },
214
+ onMouseDown: (event) => {
215
+ event.preventDefault();
216
+ event.stopPropagation();
217
+ },
218
+ onClick: (event) => {
219
+ event.preventDefault();
220
+ event.stopPropagation();
221
+ onEdit?.(item.value, "remove");
222
+ },
223
+ children: item.closeIcon ?? /* @__PURE__ */ jsx(X, { className: "ui-tabs-tab-remove-icon", "aria-hidden": "true" })
224
+ }
225
+ )
226
+ ) : null
227
+ ]
228
+ },
229
+ item.value
230
+ );
231
+ })
232
+ }
233
+ ) : null;
55
234
  return /* @__PURE__ */ jsx(TabsFrameContext.Provider, { value: frame, children: /* @__PURE__ */ jsx(
56
235
  AriaTabs,
57
236
  {
58
- orientation,
237
+ "data-slot": "tabs",
238
+ "data-orientation": resolvedOrientation,
239
+ "data-variant": variant,
240
+ "data-placement": placement,
241
+ "data-size": size,
242
+ "data-centered": centered ? "true" : void 0,
243
+ orientation: resolvedOrientation,
59
244
  keyboardActivation: activationMode,
60
245
  selectedKey: value,
61
246
  defaultSelectedKey: value === void 0 ? resolvedDefault : void 0,
62
- onSelectionChange: onValueChange ? (key) => {
63
- onValueChange(String(key));
64
- } : void 0,
247
+ onSelectionChange: (key) => handleValueChange(String(key)),
65
248
  className: cn(
66
- // Structure only.
67
- // live in src/styles/navigation-layout.css so the gap reads --tabs-root-gap and a service
249
+ // Structure only. The paint (and the placement flip, which is `order`/`flex-direction`)
250
+ // lives in src/styles/navigation-layout.css so the gap reads --tabs-root-gap and a service
251
+ // can retune it.
68
252
  "group/tabs flex data-[orientation=horizontal]:flex-col",
253
+ // PLACEMENT is a flex REVERSAL, never a re-ordered tree: the strip stays first in the DOM
254
+ // at every placement, so reading order and the APG tablist → tabpanel relationship do not
255
+ // depend on which edge the bar is painted on. Both classes carry the same modifier as the
256
+ // base step they replace, so tailwind-merge drops that step instead of stacking two
257
+ // flex-direction values on one element.
258
+ placement === "bottom" && "data-[orientation=horizontal]:flex-col-reverse",
259
+ placement === "end" && "flex-row-reverse",
69
260
  className
70
261
  ),
71
262
  render: (domProps) => withDomProps("div", "tabs", { dir, ...props }, domProps, {
72
- "data-orientation": orientation,
73
- "data-variant": variant
263
+ "data-orientation": resolvedOrientation,
264
+ "data-variant": variant,
265
+ "data-placement": placement,
266
+ "data-size": size,
267
+ "data-centered": centered ? "true" : void 0
74
268
  }),
75
269
  children: items ? /* @__PURE__ */ jsxs(Fragment, { children: [
76
- /* @__PURE__ */ jsx(
77
- TabsList,
78
- {
79
- "data-slot": "tabs-list",
80
- variant: variant === "line" ? "line" : "default",
81
- className: cn(
82
- // The inset override is an arbitrary value reading the knob (never `p-0`): it must
83
- // stay a UTILITY so tailwind-merge still drops the strip's own padding step.
84
- //
85
- // IT IS SPLIT INTO `px-`/`py-` FOR THE FOCUS RING (gh#376). The strip clips its
86
- // block axis (`overflow-y: hidden` beside `overflow-x: auto`), and this variant's
87
- // inset is 0px, so a focused trigger had NO block headroom at all — measured in
88
- // Chromium, 0px top and bottom, i.e. the entire ring was shaved. The headroom has to
89
- // come from layout: `overflow-y: visible` next to `overflow-x: auto` computes back
90
- // to `auto` per spec (a second scroll container, not a ring), and Chromium honours
91
- // `overflow-clip-margin` only when BOTH axes are `clip`. So the block padding carries
92
- // the ring's outer reach and an equal negative block margin hands it straight back —
93
- // the ring paints inside the scrollport and the strip's OUTER box is unchanged
94
- // (measured: 38px before and after). Both values read the same token the ring is
95
- // sized from, so the two can never drift apart.
96
- //
97
- // The cost, recorded rather than hidden: the rail hairline (`border-b`, drawn at the
98
- // border box) now sits the ring's reach below the triggers instead of flush. That is
99
- // where a hand-composed <TabsList variant="line"> already put it — its `p-1` gives
100
- // the same block inset — so the two construction paths now agree. A service that
101
- // wants the active bar parked back on the hairline raises `--tabs-indicator-offset`,
102
- // which exists for exactly that.
103
- variant === "line" && "my-[calc(-1_*_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))] h-auto w-full justify-start border-b px-[var(--tabs-list-line-space-inset)] py-[calc(var(--tabs-list-line-space-inset)_+_var(--tabs-list-focus-ring-space-inset,calc(var(--focus-ring-width)_+_var(--focus-ring-glow-width))))]",
104
- variant === "card" && "w-full justify-start",
105
- listClassName
106
- ),
107
- children: items.map((item) => /* @__PURE__ */ jsx(
108
- TabsTrigger,
109
- {
110
- value: item.value,
111
- disabled: item.disabled,
112
- className: cn(
113
- // Geometry only — the active underline is the token-owned `::after` bar the
114
- // line variant already owns (never a second, hand-rolled border-b indicator).
115
- // utilities so tailwind-merge keeps dropping the pill trigger's own radius /
116
- // padding steps from the base class list.
117
- // `card` adds NOTHING here — its list is forwarded as data-variant="default", so
118
- // the active lift already comes from the base trigger's
119
- // `group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm`.
120
- variant === "line" && "rounded-[var(--tabs-trigger-line-radius)] px-[var(--tabs-trigger-line-padding-x)] py-[var(--tabs-trigger-line-padding-y)]"
121
- ),
122
- children: item.label
123
- },
124
- item.value
125
- ))
126
- }
127
- ),
270
+ needsBar ? /* @__PURE__ */ jsxs("div", { "data-slot": "tabs-bar", className: "ui-tabs-bar", children: [
271
+ extraStart ? /* @__PURE__ */ jsx("div", { "data-slot": "tabs-extra", "data-side": "start", className: "ui-tabs-extra", children: extraStart }) : null,
272
+ list,
273
+ showAdd ? /* @__PURE__ */ jsx(
274
+ "button",
275
+ {
276
+ type: "button",
277
+ "data-slot": "tabs-add",
278
+ className: "ui-tabs-add",
279
+ "aria-label": t("navigation.tabs.addTab"),
280
+ onClick: (event) => onEdit?.(event, "add"),
281
+ children: addIcon ?? /* @__PURE__ */ jsx(Plus, { className: "ui-tabs-add-icon", "aria-hidden": "true" })
282
+ }
283
+ ) : null,
284
+ extraEnd ? /* @__PURE__ */ jsx("div", { "data-slot": "tabs-extra", "data-side": "end", className: "ui-tabs-extra", children: extraEnd }) : null
285
+ ] }) : list,
128
286
  items.map((item) => /* @__PURE__ */ jsx(
129
287
  TabsContent,
130
288
  {
131
289
  value: item.value,
132
290
  "data-slot": "tabs-panel",
291
+ forceMount: destroyOnHidden ? void 0 : true,
292
+ hidden: destroyOnHidden ? void 0 : item.value !== activeValue,
133
293
  className: contentClassName,
134
294
  children: item.content
135
295
  },
@@ -181,7 +341,7 @@ const TabsList = React.forwardRef(
181
341
  );
182
342
  TabsList.displayName = "TabsList";
183
343
  const TabsTrigger = React.forwardRef(
184
- ({ className, value, disabled, children, ...props }, ref) => {
344
+ ({ className, value, disabled, children, onKeyDown, ...props }, ref) => {
185
345
  const { orientation, selectionSuppressed } = React.useContext(TabsFrameContext);
186
346
  return /* @__PURE__ */ jsx(
187
347
  AriaTab,
@@ -219,6 +379,14 @@ const TabsTrigger = React.forwardRef(
219
379
  "aria-selected": renderProps.isSelected && !selectionSuppressed,
220
380
  "data-orientation": orientation,
221
381
  disabled,
382
+ onKeyDown: (event) => {
383
+ onKeyDown?.(event);
384
+ if (!event.defaultPrevented) {
385
+ domProps.onKeyDown?.(
386
+ event
387
+ );
388
+ }
389
+ },
222
390
  tabIndex: props.tabIndex ?? domProps.tabIndex
223
391
  }),
224
392
  children
@@ -1,5 +1,4 @@
1
1
  import * as React from "react";
2
- import type { InputOTPAlignProp } from "../../props/components/data-entry.prop.js";
3
2
  /**
4
3
  * InputOTP — the code-challenge field.
5
4
  *
@@ -8,39 +7,38 @@ import type { InputOTPAlignProp } from "../../props/components/data-entry.prop.j
8
7
  * hidden input because `input-otp` owns the container element; `.ui-otp-container` reads it back
9
8
  * through `:has()`, exactly as it already does for the invalid and disabled states. A service that
10
9
  * wants every code field centred sets `--otp-container-align` once instead.
10
+ *
11
+ * `size`/`status`/`variant` ride the same `controlSurfaceAttrs` helper as Input, Select, Cascader
12
+ * and TagInput, so the code field sits on the shared control-height ladder and paints the same
13
+ * error edge — they reach the CSS by the same `:has()` route as `align`.
14
+ *
15
+ * The VALUE is owned here, not by `input-otp`: `formatter` and `readOnly` have to hold for paste
16
+ * too, and `input-otp` writes its own internal state inside its paste handler, which a wrapper
17
+ * that only intercepted `onChange` could not undo.
11
18
  */
12
- export declare const InputOTP: React.ForwardRefExoticComponent<((Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "containerClassName" | "maxLength" | "noScriptCSSFallback" | "nonce" | "onChange" | "onComplete" | "pasteTransformer" | "pushPasswordManagerStrategy" | "textAlign" | "value"> & {
13
- value?: string;
14
- onChange?: (newValue: string) => unknown;
19
+ export declare const InputOTP: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "children" | "defaultValue" | "onChange" | "size" | "value"> & {
15
20
  maxLength: number;
16
- textAlign?: 'left' | 'center' | 'right';
17
- onComplete?: (...args: any[]) => unknown;
18
- pushPasswordManagerStrategy?: 'increase-width' | 'none';
19
- pasteTransformer?: (pasted: string) => string;
20
- containerClassName?: string;
21
- noScriptCSSFallback?: string | null;
22
- nonce?: string;
23
- } & {
24
- render: (props: import("input-otp").RenderProps) => React.ReactNode;
25
- children?: never;
26
- } & React.RefAttributes<HTMLInputElement>, "ref"> | Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "containerClassName" | "maxLength" | "noScriptCSSFallback" | "nonce" | "onChange" | "onComplete" | "pasteTransformer" | "pushPasswordManagerStrategy" | "textAlign" | "value"> & {
27
21
  value?: string;
28
- onChange?: (newValue: string) => unknown;
29
- maxLength: number;
30
- textAlign?: 'left' | 'center' | 'right';
31
- onComplete?: (...args: any[]) => unknown;
32
- pushPasswordManagerStrategy?: 'increase-width' | 'none';
22
+ defaultValue?: string;
23
+ onValueChange?: (value: string) => void;
24
+ onChange?: (value: string) => void;
25
+ onComplete?: (value: string) => void;
26
+ formatter?: (value: string) => string;
27
+ mask?: import("./input-otp.js").InputOTPMaskProp;
28
+ align?: import("./input-otp.js").InputOTPAlignProp;
29
+ size?: import("../../props/index.js").SizeProp;
30
+ status?: import("../../props/index.js").ControlStatusProp;
31
+ variant?: import("../../props/index.js").ControlVariantProp;
32
+ pattern?: string;
33
33
  pasteTransformer?: (pasted: string) => string;
34
- containerClassName?: string;
34
+ containerClassName?: import("../../props/index.js").ClassNameProp;
35
+ pushPasswordManagerStrategy?: "increase-width" | "none";
35
36
  noScriptCSSFallback?: string | null;
36
37
  nonce?: string;
37
- } & {
38
- render?: never;
39
- children: React.ReactNode;
40
- } & React.RefAttributes<HTMLInputElement>, "ref">) & {
41
- align?: InputOTPAlignProp;
42
- }) & React.RefAttributes<HTMLInputElement>>;
43
- export type { InputOTPAlignProp, InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, } from "../../props/components/data-entry.prop.js";
38
+ children?: React.ReactNode;
39
+ render?: (props: import("input-otp").RenderProps) => React.ReactNode;
40
+ } & React.RefAttributes<HTMLInputElement>>;
41
+ export type { InputOTPAlignProp, InputOTPGroupAppearanceProp, InputOTPGroupProp, InputOTPGroupProp as InputOTPGroupProps, InputOTPMaskProp, InputOTPProp, InputOTPProp as InputOTPProps, } from "../../props/components/data-entry.prop.js";
44
42
  export declare const InputOTPGroup: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & {
45
43
  appearance?: import("./input-otp.js").InputOTPGroupAppearanceProp;
46
44
  } & React.RefAttributes<HTMLDivElement>>;
@@ -4,18 +4,58 @@ import * as React from "react";
4
4
  import { OTPInput, OTPInputContext } from "input-otp";
5
5
  import { Minus } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
- const InputOTP = React.forwardRef(({ className, containerClassName, align, ...props }, ref) => /* @__PURE__ */ jsx(
8
- OTPInput,
9
- {
7
+ import { controlSurfaceAttrs, resolveAriaInvalid } from "../data-entry/control-surface.js";
8
+ const DEFAULT_MASK_CHAR = "\u2022";
9
+ const InputOTPMaskContext = React.createContext(null);
10
+ const InputOTP = React.forwardRef(function InputOTP2({
11
+ className,
12
+ containerClassName,
13
+ align,
14
+ mask,
15
+ formatter,
16
+ size,
17
+ status,
18
+ variant,
19
+ value,
20
+ defaultValue,
21
+ onChange,
22
+ onValueChange,
23
+ readOnly,
24
+ "aria-invalid": ariaInvalid,
25
+ children,
26
+ render,
27
+ ...props
28
+ }, ref) {
29
+ const [uncontrolled, setUncontrolled] = React.useState(defaultValue ?? "");
30
+ const isControlled = value !== void 0;
31
+ const current = isControlled ? value : uncontrolled;
32
+ const handleChange = (next) => {
33
+ if (readOnly) return;
34
+ const formatted = formatter ? formatter(next) : next;
35
+ if (!isControlled) setUncontrolled(formatted);
36
+ onValueChange?.(formatted);
37
+ if (onChange !== onValueChange) onChange?.(formatted);
38
+ };
39
+ const maskChar = mask === true ? DEFAULT_MASK_CHAR : typeof mask === "string" && mask ? mask.charAt(0) : null;
40
+ const shared = {
10
41
  ref,
11
42
  "data-slot": "input-otp",
43
+ // INERT DEFAULT: `start` emits no attribute, so an existing field keeps its exact DOM.
12
44
  "data-align": align && align !== "start" ? align : void 0,
45
+ ...controlSurfaceAttrs({ size, status, variant }),
46
+ "aria-invalid": resolveAriaInvalid(ariaInvalid, status),
47
+ // Native `readonly` already conveys the state, but the library's readOnly contract names
48
+ // `aria-readonly` explicitly and half the controls did not honour it.
49
+ "aria-readonly": readOnly ? true : void 0,
50
+ readOnly,
51
+ value: current,
52
+ onChange: handleChange,
13
53
  containerClassName: cn("ui-otp-container", containerClassName),
14
54
  className: cn("ui-otp-input", className),
15
55
  ...props
16
- }
17
- ));
18
- InputOTP.displayName = "InputOTP";
56
+ };
57
+ return /* @__PURE__ */ jsx(InputOTPMaskContext.Provider, { value: maskChar, children: render ? /* @__PURE__ */ jsx(OTPInput, { ...shared, render }) : /* @__PURE__ */ jsx(OTPInput, { ...shared, children }) });
58
+ });
19
59
  const InputOTPGroup = React.forwardRef(
20
60
  ({ appearance = "slots", className, ...props }, ref) => /* @__PURE__ */ jsx(
21
61
  "div",
@@ -31,17 +71,20 @@ const InputOTPGroup = React.forwardRef(
31
71
  InputOTPGroup.displayName = "InputOTPGroup";
32
72
  const InputOTPSlot = React.forwardRef(({ index, className, ...props }, ref) => {
33
73
  const context = React.useContext(OTPInputContext);
74
+ const maskChar = React.useContext(InputOTPMaskContext);
34
75
  const slot = context.slots[index] ?? { char: null, hasFakeCaret: false, isActive: false };
76
+ const painted = slot.char === null ? null : maskChar ?? slot.char;
35
77
  return /* @__PURE__ */ jsxs(
36
78
  "div",
37
79
  {
38
80
  ref,
39
81
  "data-slot": "input-otp-slot",
40
82
  "data-active": slot.isActive || void 0,
83
+ "data-masked": maskChar && slot.char !== null ? "" : void 0,
41
84
  className: cn("ui-otp-slot", className),
42
85
  ...props,
43
86
  children: [
44
- slot.char,
87
+ painted,
45
88
  slot.hasFakeCaret ? /* @__PURE__ */ jsx("div", { className: "ui-otp-caret-wrapper", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { className: "ui-otp-caret" }) }) : null
46
89
  ]
47
90
  }
@@ -1,4 +1,38 @@
1
1
  import * as React from "react";
2
2
  import { Input } from "../data-entry/input.js";
3
- export type PasswordInputProps = Omit<React.ComponentPropsWithoutRef<typeof Input>, "type">;
4
- export declare const PasswordInput: React.ForwardRefExoticComponent<PasswordInputProps & React.RefAttributes<HTMLInputElement>>;
3
+ /**
4
+ * antd `Input.Password.visibilityToggle`. `false` removes the eye entirely — a kiosk, a shared
5
+ * screen, or a security policy that forbids ever revealing a secret has no other way to say so.
6
+ * The object form is the controlled triad for the reveal state: `visible` wins when provided, and
7
+ * `onVisibleChange` fires on both the controlled and the uncontrolled path.
8
+ */
9
+ export type PasswordVisibilityToggleProp = boolean | {
10
+ visible?: boolean;
11
+ onVisibleChange?: (visible: boolean) => void;
12
+ };
13
+ export type PasswordInputProps = Omit<React.ComponentPropsWithoutRef<typeof Input>, "type"> & {
14
+ /** antd `visibilityToggle` — see {@link PasswordVisibilityToggleProp}. Default `true`. */
15
+ visibilityToggle?: PasswordVisibilityToggleProp;
16
+ /** antd `iconRender` — replace the eye glyph. Receives the CURRENT visibility. */
17
+ iconRender?: (visible: boolean) => React.ReactNode;
18
+ };
19
+ export declare const PasswordInput: React.ForwardRefExoticComponent<Omit<Omit<Omit<React.InputHTMLAttributes<HTMLInputElement>, "prefix" | "size"> & {
20
+ onValueChange?: (value: string) => void;
21
+ size?: "sm" | "md" | "lg";
22
+ status?: import("../../props/index.js").ControlStatusProp;
23
+ variant?: import("../../props/index.js").ControlVariantProp;
24
+ allowClear?: import("../../props/index.js").AllowClearProp;
25
+ onClear?: () => void;
26
+ leadingIcon?: React.ReactNode;
27
+ trailingIcon?: React.ReactNode;
28
+ prefix?: React.ReactNode;
29
+ suffix?: React.ReactNode;
30
+ addonBefore?: React.ReactNode;
31
+ addonAfter?: React.ReactNode;
32
+ count?: import("../../props/components/data-entry.prop.js").ControlCountProp;
33
+ } & React.RefAttributes<HTMLInputElement>, "ref">, "type"> & {
34
+ /** antd `visibilityToggle` — see {@link PasswordVisibilityToggleProp}. Default `true`. */
35
+ visibilityToggle?: PasswordVisibilityToggleProp;
36
+ /** antd `iconRender` — replace the eye glyph. Receives the CURRENT visibility. */
37
+ iconRender?: (visible: boolean) => React.ReactNode;
38
+ } & React.RefAttributes<HTMLInputElement>>;
@@ -6,30 +6,50 @@ import { useTranslation } from "../../i18n/use-translation.js";
6
6
  import { cn } from "../../lib/utils.js";
7
7
  import { Input } from "../data-entry/input.js";
8
8
  const PasswordInput = React.forwardRef(
9
- ({ className, ...props }, ref) => {
9
+ ({ className, visibilityToggle = true, iconRender, suffix, trailingIcon, allowClear, ...props }, ref) => {
10
10
  const { t } = useTranslation();
11
- const [visible, setVisible] = React.useState(false);
11
+ const controlledVisible = typeof visibilityToggle === "object" ? visibilityToggle.visible : void 0;
12
+ const onVisibleChange = typeof visibilityToggle === "object" ? visibilityToggle.onVisibleChange : void 0;
13
+ const showToggle = visibilityToggle !== false;
14
+ const [internalVisible, setInternalVisible] = React.useState(false);
15
+ const isVisibleControlled = controlledVisible !== void 0;
16
+ const visible = showToggle ? isVisibleControlled ? controlledVisible : internalVisible : false;
17
+ const setVisible = (next) => {
18
+ if (!isVisibleControlled) setInternalVisible(next);
19
+ onVisibleChange?.(next);
20
+ };
21
+ const toggleOwnsTrailing = showToggle;
22
+ if (toggleOwnsTrailing && process.env?.NODE_ENV !== "production" && (suffix != null || trailingIcon != null || allowClear)) {
23
+ console.warn(
24
+ "[godxjp-ui] PasswordInput: the reveal toggle owns the trailing slot, so `suffix` / `trailingIcon` / `allowClear` are ignored. Use `leadingIcon` (or `prefix`), or pass `visibilityToggle={false}` to hand the slot back."
25
+ );
26
+ }
12
27
  return /* @__PURE__ */ jsxs("div", { className: "ui-password-input", "data-slot": "password-input", children: [
13
28
  /* @__PURE__ */ jsx(
14
29
  Input,
15
30
  {
16
31
  ref,
17
32
  type: visible ? "text" : "password",
18
- className: cn("ui-password-input-field", className),
33
+ className: cn(showToggle && "ui-password-input-field", className),
34
+ suffix: toggleOwnsTrailing ? void 0 : suffix,
35
+ trailingIcon: toggleOwnsTrailing ? void 0 : trailingIcon,
36
+ allowClear: toggleOwnsTrailing ? false : allowClear,
19
37
  ...props
20
38
  }
21
39
  ),
22
- /* @__PURE__ */ jsx(
40
+ showToggle ? /* @__PURE__ */ jsx(
23
41
  "button",
24
42
  {
25
43
  type: "button",
26
44
  className: "ui-password-input-toggle",
27
- onClick: () => setVisible((v) => !v),
45
+ disabled: props.disabled,
46
+ onMouseDown: (event) => event.preventDefault(),
47
+ onClick: () => setVisible(!visible),
28
48
  "aria-label": visible ? t("ui.passwordInput.hide") : t("ui.passwordInput.show"),
29
49
  "aria-pressed": visible,
30
- children: visible ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
50
+ children: iconRender ? iconRender(visible) : visible ? /* @__PURE__ */ jsx(EyeOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Eye, { "aria-hidden": "true" })
31
51
  }
32
- )
52
+ ) : null
33
53
  ] });
34
54
  }
35
55
  );
@@ -3,7 +3,32 @@ export type RatingProps = {
3
3
  value?: number;
4
4
  defaultValue?: number;
5
5
  onValueChange?: (value: number) => void;
6
+ /** Number of symbols. This library's older name for antd's `count`; both are accepted. */
6
7
  max?: number;
8
+ /** antd `count` — number of symbols. Default 5. */
9
+ count?: number;
10
+ /**
11
+ * antd `allowHalf` — a symbol can be half-filled, so the scale is 0.5 … count in steps of 0.5.
12
+ * The keyboard steps by a half too, and each symbol becomes TWO hit areas (leading / trailing).
13
+ */
14
+ allowHalf?: boolean;
15
+ /**
16
+ * antd `allowClear` — choosing the value that is already chosen clears it back to 0. On by
17
+ * default in antd; off here, because a rating inside a form is usually required and a silent
18
+ * reset on a second click reads as a lost answer.
19
+ */
20
+ allowClear?: boolean;
21
+ /**
22
+ * antd `character` — what a symbol IS. A node for every symbol, or a function of the 1-based
23
+ * index for a scale whose symbols differ (A/B/C, 松竹梅, a heart for the top step).
24
+ */
25
+ character?: React.ReactNode | ((index: number) => React.ReactNode);
26
+ /**
27
+ * antd `tooltips` — a label per step, in order. It is folded into each symbol's ACCESSIBLE NAME
28
+ * rather than shown only on hover: a `title` attribute is invisible to a keyboard and to touch,
29
+ * and the whole point of the prop is to say what "3 of 5" means.
30
+ */
31
+ tooltips?: readonly string[];
7
32
  readOnly?: boolean;
8
33
  disabled?: boolean;
9
34
  name?: string;