@godxjp/ui 21.0.0 → 22.0.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 (103) hide show
  1. package/dist/app/app-provider.js +2 -1
  2. package/dist/components/data-display/badge.d.ts +28 -1
  3. package/dist/components/data-display/badge.js +2 -0
  4. package/dist/components/data-display/data-table.d.ts +18 -2
  5. package/dist/components/data-display/data-table.js +4 -0
  6. package/dist/components/data-display/index.d.ts +1 -1
  7. package/dist/components/data-display/popover.d.ts +19 -1
  8. package/dist/components/data-display/popover.js +9 -1
  9. package/dist/components/data-display/progress.d.ts +37 -0
  10. package/dist/components/data-display/progress.js +57 -5
  11. package/dist/components/data-entry/date-picker.d.ts +10 -3
  12. package/dist/components/data-entry/date-picker.js +503 -238
  13. package/dist/components/data-entry/form-field.d.ts +1 -1
  14. package/dist/components/data-entry/form-field.js +4 -1
  15. package/dist/components/data-entry/form.js +10 -1
  16. package/dist/components/data-entry/index.d.ts +0 -6
  17. package/dist/components/data-entry/index.js +0 -6
  18. package/dist/components/data-entry/label.d.ts +14 -1
  19. package/dist/components/data-entry/label.js +6 -1
  20. package/dist/components/data-entry/radio.d.ts +27 -6
  21. package/dist/components/data-entry/radio.js +88 -43
  22. package/dist/components/data-entry/search-select.js +8 -1
  23. package/dist/components/data-entry/select.js +2 -0
  24. package/dist/components/data-entry/switch.d.ts +6 -3
  25. package/dist/components/data-entry/switch.js +34 -11
  26. package/dist/components/layout/mobile-shell.d.ts +1 -1
  27. package/dist/components/layout/mobile-shell.js +2 -0
  28. package/dist/components/navigation/menubar.d.ts +10 -3
  29. package/dist/components/navigation/steps.d.ts +1 -1
  30. package/dist/components/navigation/steps.js +3 -1
  31. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  32. package/dist/components/navigation/tabs-scroll.js +52 -1
  33. package/dist/components/navigation/tabs.d.ts +2 -2
  34. package/dist/components/navigation/tabs.js +88 -23
  35. package/dist/components/ui/index.d.ts +0 -1
  36. package/dist/components/ui/index.js +0 -1
  37. package/dist/components/ui/segmented.d.ts +15 -1
  38. package/dist/components/ui/segmented.js +13 -6
  39. package/dist/i18n/messages/en.json +3 -2
  40. package/dist/i18n/messages/ja.json +3 -2
  41. package/dist/i18n/messages/vi.json +3 -2
  42. package/dist/lib/datetime/picker-format.d.ts +18 -0
  43. package/dist/lib/datetime/picker-format.js +35 -1
  44. package/dist/lib/field-a11y.d.ts +12 -0
  45. package/dist/lib/field-a11y.js +14 -1
  46. package/dist/props/components/data-display.prop.d.ts +7 -0
  47. package/dist/props/components/data-entry.prop.d.ts +180 -99
  48. package/dist/props/components/index.d.ts +1 -1
  49. package/dist/props/components/layout.prop.d.ts +12 -1
  50. package/dist/props/components/navigation.prop.d.ts +76 -1
  51. package/dist/props/registry.d.ts +16 -16
  52. package/dist/props/registry.js +16 -15
  53. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  54. package/dist/props/vocabulary/index.d.ts +2 -2
  55. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  56. package/dist/styles/badge-layout.css +4 -0
  57. package/dist/styles/card-layout.css +6 -6
  58. package/dist/styles/control.css +15 -6
  59. package/dist/styles/data-display-layout.css +76 -13
  60. package/dist/styles/focus-ring.css +4 -2
  61. package/dist/styles/navigation-layout.css +25 -0
  62. package/dist/styles/shell-layout.css +5 -0
  63. package/dist/styles/table-layout.css +34 -0
  64. package/dist/tokens/components/data-display.css +9 -0
  65. package/dist/tokens/components/navigation.css +6 -0
  66. package/dist/tokens/components/segmented.css +0 -2
  67. package/dist/tokens/components/shell.css +2 -0
  68. package/dist/tokens/components/table.css +3 -0
  69. package/dist/tokens/foundation.css +7 -0
  70. package/docs/COMPONENTS.md +14 -14
  71. package/docs/CONSUMER-RULES.md +24 -1
  72. package/docs/FRAME-COVERAGE-REPORT.md +3 -6
  73. package/docs/TOKENS.md +65 -0
  74. package/docs/data-display/badge.tsx +37 -0
  75. package/docs/data-display/data-table/index.tsx +18 -0
  76. package/docs/data-display/popover.tsx +41 -1
  77. package/docs/data-display/progress.tsx +28 -0
  78. package/docs/data-entry/calendar.tsx +2 -2
  79. package/docs/data-entry/date-picker.tsx +143 -3
  80. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  81. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  82. package/docs/data-entry/form.tsx +4 -4
  83. package/docs/data-entry/number-input.tsx +31 -0
  84. package/docs/data-entry/segmented.tsx +103 -1
  85. package/docs/layout/mobile-shell.tsx +5 -0
  86. package/docs/navigation/tabs.tsx +149 -0
  87. package/docs/roadmap/antd-parity.md +35 -34
  88. package/docs/roadmap/parity-audit-data-entry.md +178 -165
  89. package/docs/roadmap/parity-backlog.md +6 -7
  90. package/docs/showcase/acme-portal.tsx +7 -0
  91. package/package.json +11 -23
  92. package/scripts/ui-audit.mjs +17 -4
  93. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  94. package/dist/components/data-entry/date-range-picker.js +0 -349
  95. package/dist/components/data-entry/month-picker.d.ts +0 -10
  96. package/dist/components/data-entry/month-picker.js +0 -241
  97. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  98. package/dist/components/data-entry/month-range-picker.js +0 -301
  99. package/dist/components/ui/date-range-picker.d.ts +0 -1
  100. package/dist/components/ui/date-range-picker.js +0 -2
  101. package/docs/data-entry/date-range-picker.tsx +0 -175
  102. package/docs/data-entry/month-picker.tsx +0 -101
  103. package/docs/data-entry/month-range-picker.tsx +0 -115
@@ -41,9 +41,16 @@ The five that matter, in order:
41
41
 
42
42
  `Input` · `Textarea` · `PasswordInput` · `InputOTP` · `Switch` · `Checkbox` · `Radio` · `Field` ·
43
43
  `Form` / `FormField` / `FormErrors` / `FormRoot` / `FormFieldControl` / `FormFieldArray` /
44
- `useZodForm` · `Toggle` / `ToggleGroup` (they *are* the coverage for antd `CheckableTag`) ·
45
- `MonthPicker` / `MonthRangePicker` · `TimeRangePicker` · `UploadCropDialog` (antd has no
46
- counterpart — it documents `antd-img-crop` as a third-party add-on).
44
+ `useZodForm` · `Toggle` / `ToggleGroup` (they _are_ the coverage for antd `CheckableTag`) ·
45
+ `TimeRangePicker` · `UploadCropDialog` (antd has no counterpart — it documents `antd-img-crop` as a
46
+ third-party add-on).
47
+
48
+ `MonthPicker` / `MonthRangePicker` used to be listed here as "fully aligned". They were not: each
49
+ was a hand copy of `DatePicker` / `DateRangePicker` carrying four contracts the original had right —
50
+ a `disabled` picker that still opened, bounds that greyed a chevron and clamped nothing, an
51
+ unhandled Enter, and a non-ISO `name`. Reading them against ANT found nothing, because their
52
+ divergence was from the component beside them. They were merged into `DatePicker` in 22.0.0
53
+ (`picker="month"` / `range picker="month"`).
47
54
 
48
55
  `Command` / `CommandPalette` / `BranchScopePicker` have **no Ant Design counterpart at all**
49
56
  (namethatui: "command palette / command menu"); there is nothing to diff. `AutoComplete` is
@@ -58,96 +65,102 @@ counterpart — it documents `antd-img-crop` as a third-party add-on).
58
65
 
59
66
  `src/props/components/data-entry.prop.ts:941-1142`, `src/components/data-entry/search-select.tsx`.
60
67
 
61
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
62
- | --- | --- | --- | --- |
63
- | `virtual` — virtualized option list (default ON in antd) | `MISSING` | `grep -rl "virtual\|useVirtualizer\|react-window\|@tanstack/react-virtual" src/` → no files | Add `virtual?: boolean` + a windowed list. Shared work with `List` (`list-masonry.md`) — do it once. |
64
- | `fieldNames` — map `{label,value,options}` onto foreign row shapes | `MISSING` | `grep -n fieldNames src/props/components/data-entry.prop.ts` → only `:1281` (Cascader), `:1362` (TreeSelect) | Add `fieldNames?: TreeFieldNamesProp`-shaped sibling. The spelling already exists on two neighbours; a Select that cannot eat `{id,name}` is the odd one out. |
65
- | `labelInValue` — report `{value,label}` instead of a bare value | `COVERED-ELSEWHERE` | `onValueChange?: (value, option?)` — `:1091`, `:1110` | The option already rides the callback. No second dialect. |
66
- | `optionFilterProp` / `optionLabelProp` | `COVERED-ELSEWHERE` | `filterOption` `:1012`, `labelRender` `:954`, `optionRender` `:1031` | Function form is strictly more expressive than a prop-name string. |
67
- | `maxTagTextLength` — truncate each chip's text | `WONT-PORT` | trigger is a `<button>` with collapsed labels, `:1096-1104` | A character-count truncation is a text contract; use `maxTagCount` + `maxTagPlaceholder`, or CSS. |
68
- | `listHeight` — popup max height in px | `WONT-PORT` | `search-select.tsx:534` uses `collisionPadding` + available height | Pixel height is a design knob → token (`--select-popup-*`), cardinal rule #44. |
69
- | `placement` (`bottomLeft`…) | `COVERED-ELSEWHERE` | Radix `PopoverContent` collision flip, `search-select.tsx:529-534` | Auto-placement beats a manual physical spelling (ground rule #4). |
70
- | `suffixIcon` / `removeIcon` / `loadingIcon` | `WONT-PORT` | `allowClear` object form already carries `clearIcon` — `src/props/vocabulary/shared.prop.ts:105` | Icon-per-slot props are a token/`allowClear` job, not four more props. |
71
- | `getPopupContainer` / `styles` / `classNames` / `popupClassName` | `WONT-PORT` | ground rule #4 | Inline-style twins and portal escape hatches. |
72
- | `tagRender` in `mode="multiple"` | `WONT-PORT` | `:1096-1104` states the reason verbatim (button-in-button) | Documented refusal; `TagInput` owns `tagRender` where chips are legal. |
73
- | `onPopupScroll` | `COVERED-ELSEWHERE` | built-in infinite scroll + `renderLoadMore` `:1062` | |
74
- | `defaultActiveFirstOption` | `MISSING` | `grep -n defaultActiveFirstOption src/` → nothing | P2. Add as `defaultActiveFirstOption?: boolean`; a search box that pre-highlights row 1 makes Enter a one-key pick. |
75
- | `onInputKeyDown` | `MISSING` | grep → nothing | P2. Real for "Enter creates a new record" flows. |
76
- | `maxTagCount: "responsive"` | `WONT-PORT` | `src/props/vocabulary/shared.prop.ts:109-113` documents the refusal | Container queries instead. Already recorded. |
77
- | `variant: "underlined"` | `WONT-PORT` | `src/props/vocabulary/interaction.prop.ts:126-130` documents the refusal | SmartHR draws full boxes. Already recorded. |
78
- | `onSelect` / `onDeselect` / `maxTagPlaceholder` **exist but are undocumented** | `MISSING` (catalog) | props at `:1093`, `:1112-1114`, `:1124`; `sed -n '5560,5990p' mcp/src/data/components.ts \| grep -n "onSelect\|onDeselect\|maxTagPlaceholder"` → **no match** | Catalog drift: three shipped props the MCP never tells a consumer about. Add three catalog rows (`godxjp-ui-mcp-catalog-sync`). |
68
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
69
+ | ------------------------------------------------------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `virtual` — virtualized option list (default ON in antd) | `MISSING` | `grep -rl "virtual\|useVirtualizer\|react-window\|@tanstack/react-virtual" src/` → no files | Add `virtual?: boolean` + a windowed list. Shared work with `List` (`list-masonry.md`) — do it once. |
71
+ | `fieldNames` — map `{label,value,options}` onto foreign row shapes | `MISSING` | `grep -n fieldNames src/props/components/data-entry.prop.ts` → only `:1281` (Cascader), `:1362` (TreeSelect) | Add `fieldNames?: TreeFieldNamesProp`-shaped sibling. The spelling already exists on two neighbours; a Select that cannot eat `{id,name}` is the odd one out. |
72
+ | `labelInValue` — report `{value,label}` instead of a bare value | `COVERED-ELSEWHERE` | `onValueChange?: (value, option?)` — `:1091`, `:1110` | The option already rides the callback. No second dialect. |
73
+ | `optionFilterProp` / `optionLabelProp` | `COVERED-ELSEWHERE` | `filterOption` `:1012`, `labelRender` `:954`, `optionRender` `:1031` | Function form is strictly more expressive than a prop-name string. |
74
+ | `maxTagTextLength` — truncate each chip's text | `WONT-PORT` | trigger is a `<button>` with collapsed labels, `:1096-1104` | A character-count truncation is a text contract; use `maxTagCount` + `maxTagPlaceholder`, or CSS. |
75
+ | `listHeight` — popup max height in px | `WONT-PORT` | `search-select.tsx:534` uses `collisionPadding` + available height | Pixel height is a design knob → token (`--select-popup-*`), cardinal rule #44. |
76
+ | `placement` (`bottomLeft`…) | `COVERED-ELSEWHERE` | Radix `PopoverContent` collision flip, `search-select.tsx:529-534` | Auto-placement beats a manual physical spelling (ground rule #4). |
77
+ | `suffixIcon` / `removeIcon` / `loadingIcon` | `WONT-PORT` | `allowClear` object form already carries `clearIcon` — `src/props/vocabulary/shared.prop.ts:105` | Icon-per-slot props are a token/`allowClear` job, not four more props. |
78
+ | `getPopupContainer` / `styles` / `classNames` / `popupClassName` | `WONT-PORT` | ground rule #4 | Inline-style twins and portal escape hatches. |
79
+ | `tagRender` in `mode="multiple"` | `WONT-PORT` | `:1096-1104` states the reason verbatim (button-in-button) | Documented refusal; `TagInput` owns `tagRender` where chips are legal. |
80
+ | `onPopupScroll` | `COVERED-ELSEWHERE` | built-in infinite scroll + `renderLoadMore` `:1062` | |
81
+ | `defaultActiveFirstOption` | `MISSING` | `grep -n defaultActiveFirstOption src/` → nothing | P2. Add as `defaultActiveFirstOption?: boolean`; a search box that pre-highlights row 1 makes Enter a one-key pick. |
82
+ | `onInputKeyDown` | `MISSING` | grep → nothing | P2. Real for "Enter creates a new record" flows. |
83
+ | `maxTagCount: "responsive"` | `WONT-PORT` | `src/props/vocabulary/shared.prop.ts:109-113` documents the refusal | Container queries instead. Already recorded. |
84
+ | `variant: "underlined"` | `WONT-PORT` | `src/props/vocabulary/interaction.prop.ts:126-130` documents the refusal | SmartHR draws full boxes. Already recorded. |
85
+ | `onSelect` / `onDeselect` / `maxTagPlaceholder` **exist but are undocumented** | `MISSING` (catalog) | props at `:1093`, `:1112-1114`, `:1124`; `sed -n '5560,5990p' mcp/src/data/components.ts \| grep -n "onSelect\|onDeselect\|maxTagPlaceholder"` → **no match** | Catalog drift: three shipped props the MCP never tells a consumer about. Add three catalog rows (`godxjp-ui-mcp-catalog-sync`). |
79
86
 
80
87
  ### 2.2 `Cascader` — `CascaderProp` `src/props/components/data-entry.prop.ts:1253-1330`
81
88
 
82
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
83
- | --- | --- | --- | --- |
84
- | `showSearch` **object form** — `{filter, sort, limit, render, matchInputWidth}` | `MISSING` | `showSearch?: boolean` only, `:1263`; search runs `filterTreeOptions(options, search)` uncapped, `cascader.tsx:389-393` | Add `filterOption` + `filterSort` + a result cap, spelled as Select already spells them (`:1012`, `:1017`) — **not** a nested antd-shaped `showSearch` object. antd's own `limit` default is 50; here a 5 000-leaf tree renders every match. |
85
- | `expandIcon` | `WONT-PORT` | no grep hit | Token (`--cascader-expand-*`), rule #44. |
86
- | `Cascader.Panel` — the picker without its trigger | `MISSING` | grep `CascaderPanel` → nothing | P2. Real for a "browse the catalogue" page. Extend `Cascader`, do not add a component. |
87
- | `maxTagTextLength` | `WONT-PORT` | same reasoning as Select | |
88
- | `suffixIcon` / `removeIcon` / `prefix` / `loadingIcon` / `popupRender` / `getPopupContainer` / `styles` | `WONT-PORT` | ground rule #4 | |
89
- | `placement` | `COVERED-ELSEWHERE` | Radix `PopoverContent align="start"`, `cascader.tsx:555` | |
90
- | `tagRender` | `WONT-PORT` | button-in-button, same as Select | |
91
- | `virtual` | `MISSING` | no virtualization in `src/` | Same P1 row as Select. |
89
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
90
+ | ------------------------------------------------------------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
91
+ | `showSearch` **object form** — `{filter, sort, limit, render, matchInputWidth}` | `MISSING` | `showSearch?: boolean` only, `:1263`; search runs `filterTreeOptions(options, search)` uncapped, `cascader.tsx:389-393` | Add `filterOption` + `filterSort` + a result cap, spelled as Select already spells them (`:1012`, `:1017`) — **not** a nested antd-shaped `showSearch` object. antd's own `limit` default is 50; here a 5 000-leaf tree renders every match. |
92
+ | `expandIcon` | `WONT-PORT` | no grep hit | Token (`--cascader-expand-*`), rule #44. |
93
+ | `Cascader.Panel` — the picker without its trigger | `MISSING` | grep `CascaderPanel` → nothing | P2. Real for a "browse the catalogue" page. Extend `Cascader`, do not add a component. |
94
+ | `maxTagTextLength` | `WONT-PORT` | same reasoning as Select | |
95
+ | `suffixIcon` / `removeIcon` / `prefix` / `loadingIcon` / `popupRender` / `getPopupContainer` / `styles` | `WONT-PORT` | ground rule #4 | |
96
+ | `placement` | `COVERED-ELSEWHERE` | Radix `PopoverContent align="start"`, `cascader.tsx:555` | |
97
+ | `tagRender` | `WONT-PORT` | button-in-button, same as Select | |
98
+ | `virtual` | `MISSING` | no virtualization in `src/` | Same P1 row as Select. |
92
99
 
93
100
  ### 2.3 `NumberInput` — `NumberInputProp` `:228-282` · `src/components/data-entry/number-input.tsx`
94
101
 
95
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
96
- | --- | --- | --- | --- |
97
- | **locale round-trip** (antd `decimalSeparator` exists precisely for this) | `MISSING` — **defect** | Formats with `new Intl.NumberFormat(locale, …)` `number-input.tsx:100`; parses with `raw.normalize("NFKC").replace(/,/g, "")` `:41`; `handleBlur` re-parses the displayed draft `:220`. Measured: `Intl.NumberFormat("vi",{useGrouping:false}).format(1.5)` → `"1,5"`; `Number("1,5".normalize("NFKC").replace(/,/g,""))` → **`15`**. | **P0.** Derive the locale's decimal + group separators from `Intl.NumberFormat(locale).formatToParts()` and normalise against *those*, instead of hardcoding `,` = thousands. Keep the NFKC 全角 fold — it is correct and load-bearing for `ja`. A regression test per shipped locale (`en`, `ja`, `vi`) is the acceptance. |
98
- | `stringMode` — string in/out for values past `Number.MAX_SAFE_INTEGER` or exact decimals | `MISSING` | grep `stringMode` → nothing; value type is `number \| null` `:180-182` | P1. Money and 13-digit ids are the ordinary enterprise case; `roundTo` `:45-49` mitigates FP drift but cannot fix range. Add as an opt-in that widens the triad to `string`. |
99
- | `onStep` — fired on stepper / arrow / wheel with the direction | `MISSING` | grep `onStep` → nothing | P2. Add `onStep?: (value, info: { offset, type: "up" \| "down" }) => void`. |
100
- | `addonBefore` / `addonAfter` | `WONT-PORT` | deprecated in antd itself (use `Space.Compact`) | `prefix`/`suffix` present `:215-217`. |
101
- | `onPressEnter` | `COVERED-ELSEWHERE` | commit-on-Enter is already the behaviour, `:237` | |
102
- | `decimalSeparator` as an explicit prop | `WONT-PORT` | should follow the locale, not a prop | Fixing the round-trip above removes the need. |
102
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
103
+ | ---------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
+ | **locale round-trip** (antd `decimalSeparator` exists precisely for this) | `MISSING` — **defect** | Formats with `new Intl.NumberFormat(locale, …)` `number-input.tsx:100`; parses with `raw.normalize("NFKC").replace(/,/g, "")` `:41`; `handleBlur` re-parses the displayed draft `:220`. Measured: `Intl.NumberFormat("vi",{useGrouping:false}).format(1.5)` → `"1,5"`; `Number("1,5".normalize("NFKC").replace(/,/g,""))` → **`15`**. | **P0.** Derive the locale's decimal + group separators from `Intl.NumberFormat(locale).formatToParts()` and normalise against _those_, instead of hardcoding `,` = thousands. Keep the NFKC 全角 fold — it is correct and load-bearing for `ja`. A regression test per shipped locale (`en`, `ja`, `vi`) is the acceptance. |
105
+ | `stringMode` — string in/out for values past `Number.MAX_SAFE_INTEGER` or exact decimals | `MISSING` | grep `stringMode` → nothing; value type is `number \| null` `:180-182` | P1. Money and 13-digit ids are the ordinary enterprise case; `roundTo` `:45-49` mitigates FP drift but cannot fix range. Add as an opt-in that widens the triad to `string`. |
106
+ | `onStep` — fired on stepper / arrow / wheel with the direction | `MISSING` | grep `onStep` → nothing | P2. Add `onStep?: (value, info: { offset, type: "up" \| "down" }) => void`. |
107
+ | `addonBefore` / `addonAfter` | `WONT-PORT` | deprecated in antd itself (use `Space.Compact`) | `prefix`/`suffix` present `:215-217`. |
108
+ | `onPressEnter` | `COVERED-ELSEWHERE` | commit-on-Enter is already the behaviour, `:237` | |
109
+ | `decimalSeparator` as an explicit prop | `WONT-PORT` | should follow the locale, not a prop | Fixing the round-trip above removes the need. |
103
110
 
104
111
  ### 2.4 `Slider` — `SliderProp` `:547-568` · `src/components/data-entry/slider.tsx`
105
112
 
106
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
107
- | --- | --- | --- | --- |
113
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
114
+ | ------------------------------------------------------------------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
108
115
  | **`aria-valuetext` mirroring `tooltip.formatter`** (rc-slider `ariaValueTextFormatterForHandle`) | `MISSING` — **a11y** | `tooltipContent` returns the bare number `slider.tsx:33,35`; the bubble is `aria-hidden` `:152`; the `Thumb` `:138-157` sets no `aria-valuetext` | **P1.** A `¥50,000` / `3 件` slider announces "50000". Feed the resolved `tooltip.formatter` (or `Intl.NumberFormat`) into `aria-valuetext`. |
109
- | default tooltip / mark text is not `Intl`-formatted | `MISSING` — i18n | `tooltipContent` `:33` returns `value` verbatim; no `Intl.NumberFormat` in the file | P2 (same fix as the row above). |
110
- | `onChangeComplete` | `RENAMED` | Radix `onValueCommit`, inherited via `React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>` `:547` | No work. Record both names in the catalog. |
111
- | `vertical` | `RENAMED` | Radix `orientation="vertical"`, `slider.tsx:55,88,112` | No work. |
112
- | `reverse` | `PRESENT` (aliased) | `inverted ?? reverse`, `slider.tsx:115` | |
113
- | `keyboard: false` | `WONT-PORT` | — | Removing keyboard operation from a `role="slider"` fails WCAG 2.1.1. |
114
- | `range: { draggableTrack }` — drag the whole span | `MISSING` | grep `draggableTrack` → nothing; Radix has no equivalent | P2. |
115
- | `tooltip.placement` / `getPopupContainer` / `autoAdjustOverflow` | `WONT-PORT` | ground rule #4 | Token + collision-aware positioning. |
116
+ | default tooltip / mark text is not `Intl`-formatted | `MISSING` — i18n | `tooltipContent` `:33` returns `value` verbatim; no `Intl.NumberFormat` in the file | P2 (same fix as the row above). |
117
+ | `onChangeComplete` | `RENAMED` | Radix `onValueCommit`, inherited via `React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>` `:547` | No work. Record both names in the catalog. |
118
+ | `vertical` | `RENAMED` | Radix `orientation="vertical"`, `slider.tsx:55,88,112` | No work. |
119
+ | `reverse` | `PRESENT` (aliased) | `inverted ?? reverse`, `slider.tsx:115` | |
120
+ | `keyboard: false` | `WONT-PORT` | — | Removing keyboard operation from a `role="slider"` fails WCAG 2.1.1. |
121
+ | `range: { draggableTrack }` — drag the whole span | `MISSING` | grep `draggableTrack` → nothing; Radix has no equivalent | P2. |
122
+ | `tooltip.placement` / `getPopupContainer` / `autoAdjustOverflow` | `WONT-PORT` | ground rule #4 | Token + collision-aware positioning. |
116
123
 
117
124
  ### 2.5 `ColorPicker` — `ColorPickerProp` `:893-908` · `src/components/data-entry/color-picker.tsx` (114 lines)
118
125
 
119
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
120
- | --- | --- | --- | --- |
121
- | `presets` — named swatch groups | `MISSING` | whole file read; only `<input type="color">` `color-picker.tsx:84-93` + a hex `Input` `:96-108` | **P1.** A tenant/brand palette is the ordinary case; today the consumer hand-rolls a swatch row of `Button`s beside it. |
122
- | `size` / `status` / `variant` | `MISSING` | prop type `:893-908` has none of the three; every other control in the group carries all three | **P1 (consistency).** A `ColorPicker` cannot line up with the `Input` next to it in a form row, and cannot paint a validation error. |
123
- | `allowClear` + `onClear` | `MISSING` | `AllowClearProp` not imported into `ColorPickerProp` `:893` | P2. The value can legitimately be `""` `:46` but nothing clears it back. |
124
- | `readOnly` | `MISSING` | `disabled` only `:902` | P2. `Select`/`Cascader`/`TagInput`/`Transfer` all state a `readOnly` contract; this one does not. |
125
- | `format` / `defaultFormat` / `onFormatChange` (rgb/hex/hsb) | `MISSING` | `HEX_PATTERN` `:14` is the only accepted shape | P2. |
126
- | `disabledAlpha` / alpha channel | `WONT-PORT` | `<input type="color">` has no alpha | Would require replacing the native panel. Not worth it unless `presets` forces a custom panel anyway — decide the two together. |
127
- | `mode: "gradient"` | `WONT-PORT` | — | A gradient editor is a different control. |
128
- | `showText` | `MISSING` | `showHexInput` `:38` shows an *editable* hex box, not a read-only label | P2 — `showHexInput` is the house spelling and covers the intent; record as `RENAMED` if the read-only variant is not wanted. |
129
- | `panelRender` / `trigger` / `arrow` / `placement` / `destroyOnHidden` | `WONT-PORT` | native panel; ground rule #4 | |
130
-
131
- ### 2.6 `DatePicker` / `DateRangePicker` — `:653-716`, `:770-804` (+ `PickerChromeProp` `:635-647`)
126
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
127
+ | --------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
128
+ | `presets` — named swatch groups | `MISSING` | whole file read; only `<input type="color">` `color-picker.tsx:84-93` + a hex `Input` `:96-108` | **P1.** A tenant/brand palette is the ordinary case; today the consumer hand-rolls a swatch row of `Button`s beside it. |
129
+ | `size` / `status` / `variant` | `MISSING` | prop type `:893-908` has none of the three; every other control in the group carries all three | **P1 (consistency).** A `ColorPicker` cannot line up with the `Input` next to it in a form row, and cannot paint a validation error. |
130
+ | `allowClear` + `onClear` | `MISSING` | `AllowClearProp` not imported into `ColorPickerProp` `:893` | P2. The value can legitimately be `""` `:46` but nothing clears it back. |
131
+ | `readOnly` | `MISSING` | `disabled` only `:902` | P2. `Select`/`Cascader`/`TagInput`/`Transfer` all state a `readOnly` contract; this one does not. |
132
+ | `format` / `defaultFormat` / `onFormatChange` (rgb/hex/hsb) | `MISSING` | `HEX_PATTERN` `:14` is the only accepted shape | P2. |
133
+ | `disabledAlpha` / alpha channel | `WONT-PORT` | `<input type="color">` has no alpha | Would require replacing the native panel. Not worth it unless `presets` forces a custom panel anyway — decide the two together. |
134
+ | `mode: "gradient"` | `WONT-PORT` | — | A gradient editor is a different control. |
135
+ | `showText` | `MISSING` | `showHexInput` `:38` shows an _editable_ hex box, not a read-only label | P2 — `showHexInput` is the house spelling and covers the intent; record as `RENAMED` if the read-only variant is not wanted. |
136
+ | `panelRender` / `trigger` / `arrow` / `placement` / `destroyOnHidden` | `WONT-PORT` | native panel; ground rule #4 | |
137
+
138
+ ### 2.6 `DatePicker` (one control: `picker` × `range`/`multiple`) `data-entry.prop.ts` `DatePickerBaseProp` / `DatePickerProp` (+ `PickerChromeProp`)
139
+
140
+ As of 22.0.0 this is ONE component. antd ships one `DatePicker` with `picker` plus a
141
+ `DatePicker.RangePicker`; this library had drifted to four (`DatePicker`, `DateRangePicker`,
142
+ `MonthPicker`, `MonthRangePicker`), three of them hand copies of the first. `picker` is the
143
+ granularity axis and `range` the cardinality axis, so the eight combinations are one implementation.
132
144
 
133
145
  Present and correct: `format` (string | `Intl.DateTimeFormatOptions` incl. Japanese era | fn),
134
- `parseFormat`, `minDate`/`maxDate`, `showWeek`, `needConfirm`, `picker`, `order`, `showTime`,
135
- `presets`, `disabledDate`, `cellRender`, `allowClear`, `allowEmpty`, `showNow`/`showToday`,
136
- `inputReadOnly`, `preserveInvalidOnBlur`, `renderExtraFooter`, `placement` (logical `bottom-start`…),
137
- `open`/`defaultOpen`/`onOpenChange`, `multiple`, ISO native submission.
138
-
139
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
140
- | --- | --- | --- | --- |
141
- | `mode` + `onPanelChange` — controlled panel level (date→month→year) | `MISSING` | `grep -rn "onPanelChange" src/` → nothing; `picker` `:663` selects the *value granularity*, not the panel level | P2. Distinct from `picker`; needed only for a picker that drives an external month/year state. |
142
- | `defaultPickerValue` / `pickerValue` / `onPickerValueChange` which month the panel opens on | `MISSING` | grep → nothing | **P1.** "Open on the fiscal-year start", "open on the month of the row being edited" has no expression today; the panel always opens on the value or today. |
143
- | `onOk` — the confirm click | `MISSING` | grep `onOk` in `src/components/data-entry/` → nothing, although `needConfirm` `:662` renders the button | P2. `onValueChange` already fires on confirm; `onOk` is a convenience. |
144
- | `separator` (RangePicker) | `MISSING` | hardcoded `<ArrowRight />`, `date-range-picker.tsx:264` | P2. RTL is handled (logical icon flip), so this is cosmetic — a token would do. |
145
- | `disabled: [boolean, boolean]` lock one end of a range | `MISSING` | `disabled?: DisabledProp` (scalar) `:787` | **P1.** "The start date is fixed by the contract, only the end is editable" is a real screen and has no expression. |
146
- | `onCalendarChange` — fires on each end of the range | `MISSING` | grep nothing | P2. Needed to compute a live duration while the range is half-picked. |
147
- | `panelRender` / `prevIcon` / `nextIcon` / `superPrevIcon` / `superNextIcon` / `suffixIcon` / `components` | `WONT-PORT` | grep nothing; ground rule #4 | Tokens, not props. |
148
- | `getPopupContainer` / `styles` / `classNames` / `popupClassName` | `WONT-PORT` | ground rule #4 | |
149
- | `maxTagCount` for `multiple` | `MISSING` | `multiple` `:710-715` has no collapse knob, unlike `Select`/`Cascader`/`TreeSelect` which all take `MaxTagCountProp` | P2 reuse `MaxTagCountProp` (`shared.prop.ts:113`), do not invent a spelling. |
150
- | `dateRender` | `RENAMED` | `cellRender` `:682` (antd renamed it too) | No work. |
146
+ `parseFormat`, `minDate`/`maxDate`, `showWeek`, `needConfirm`, `picker`, `range`, `order`,
147
+ `showTime`, `presets`, `disabledDate`, `cellRender`, `allowClear`, `allowEmpty`,
148
+ `showNow`/`showToday`, `inputReadOnly`, `preserveInvalidOnBlur`, `renderExtraFooter`, `placement`
149
+ (logical `bottom-start`…), `open`/`defaultOpen`/`onOpenChange`, `multiple`, ISO native submission
150
+ at the granularity `picker` selects.
151
+
152
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
153
+ | --------------------------------------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
154
+ | `mode` + `onPanelChange` — controlled panel level (date→month→year) | `MISSING` | `grep -rn "onPanelChange" src/` → nothing; `picker` selects the _value granularity_, not the panel level | P2. Distinct from `picker`; needed only for a picker that drives an external month/year state. |
155
+ | `defaultPickerValue` / `pickerValue` / `onPickerValueChange` which month the panel opens on | `MISSING` | grep nothing | **P1.** "Open on the fiscal-year start", "open on the month of the row being edited" has no expression today; the panel always opens on the value or today. |
156
+ | `onOk` the confirm click | `MISSING` | grep `onOk` in `src/components/data-entry/` → nothing, although `needConfirm` renders the button | P2. `onValueChange` already fires on confirm; `onOk` is a convenience. |
157
+ | `separator` (RangePicker) | `MISSING` | hardcoded `<ArrowRight />` in the range shell of `date-picker.tsx` | P2. RTL is handled (logical icon flip), so this is cosmetic a token would do. |
158
+ | `disabled: [boolean, boolean]` — lock one end of a range | `MISSING` | `disabled?: DisabledProp` (scalar) on `DatePickerBaseProp` | **P1.** "The start date is fixed by the contract, only the end is editable" is a real screen and has no expression. |
159
+ | `onCalendarChange` fires on each end of the range | `MISSING` | grep nothing | P2. Needed to compute a live duration while the range is half-picked. |
160
+ | `panelRender` / `prevIcon` / `nextIcon` / `superPrevIcon` / `superNextIcon` / `suffixIcon` / `components` | `WONT-PORT` | grep → nothing; ground rule #4 | Tokens, not props. |
161
+ | `getPopupContainer` / `styles` / `classNames` / `popupClassName` | `WONT-PORT` | ground rule #4 | |
162
+ | `maxTagCount` for `multiple` | `MISSING` | the `multiple` arm has no collapse knob, unlike `Select`/`Cascader`/`TreeSelect` which all take `MaxTagCountProp` | P2 — reuse `MaxTagCountProp` (`shared.prop.ts:113`), do not invent a spelling. |
163
+ | `dateRender` | `RENAMED` | `cellRender` (antd renamed it too) | No work. |
151
164
 
152
165
  ### 2.7 `TimePicker` — `TimePickerProp` `:822-877`
153
166
 
@@ -155,23 +168,23 @@ Present: `hourStep`/`minuteStep`/`secondStep`, `showSeconds`, `use12Hours`, `for
155
168
  `hideDisabledOptions`, `showNow`, `needConfirm`, `changeOnScroll`, `inputReadOnly`, `allowClear`,
156
169
  `renderExtraFooter`, canonical 24-h storage.
157
170
 
158
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
159
- | --- | --- | --- | --- |
160
- | `cellRender` | `MISSING` | `grep -n cellRender src/components/data-entry/time-picker.tsx` → nothing | P2. Marking "already booked" times is the same need `Calendar cellRender` already serves. |
161
- | `onSelect` — fires per column pick, before commit | `MISSING` | grep → nothing | P2. |
162
- | `needConfirm` default | `PRESENT` — **deliberate divergence** | `:864-871` states it: antd defaults `true`, this library `false`, with the reason | No work; already documented. Keep. |
163
- | `suffixIcon` / `popupClassName` / `getPopupContainer` | `WONT-PORT` | ground rule #4 | |
171
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
172
+ | ----------------------------------------------------- | ------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
173
+ | `cellRender` | `MISSING` | `grep -n cellRender src/components/data-entry/time-picker.tsx` → nothing | P2. Marking "already booked" times is the same need `Calendar cellRender` already serves. |
174
+ | `onSelect` — fires per column pick, before commit | `MISSING` | grep → nothing | P2. |
175
+ | `needConfirm` default | `PRESENT` — **deliberate divergence** | `:864-871` states it: antd defaults `true`, this library `false`, with the reason | No work; already documented. Keep. |
176
+ | `suffixIcon` / `popupClassName` / `getPopupContainer` | `WONT-PORT` | ground rule #4 | |
164
177
 
165
178
  ### 2.8 `Calendar` — `CalendarProp` `:585-619`
166
179
 
167
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
168
- | --- | --- | --- | --- |
169
- | `mode: "month" \| "year"` + `onPanelChange` — the 12-month year board | `MISSING` | `grep -rn "onPanelChange\|fullCellRender" src/` → nothing; `CalendarProp` is `DayPickerProps` + footer + `width`/`bordered`/`cellRender` | P2. `MonthPicker` covers *picking* a month; it does not cover a year board a consumer can decorate. |
170
- | `headerRender` | `COVERED-ELSEWHERE` | `CalendarProp extends DayPickerProps` `:585` → react-day-picker `captionLayout="dropdown" \| "dropdown-months" \| "dropdown-years"` + `startMonth`/`endMonth` (used at `calendar.tsx:60-61`) | Add a `related`/usage note; no code. |
171
- | `validRange` | `COVERED-ELSEWHERE` | DayPicker `startMonth`/`endMonth` + `disabled`, `calendar.tsx:60-61` | |
172
- | `fullscreen` | `RENAMED` | `width="full"` + `bordered` `:604,616`, with the measured rationale for the split | No work. |
173
- | `fullCellRender` — replace the whole cell | `WONT-PORT` | `CalendarCellRenderProp` `:571-583` states the refusal: `cellRender` **wraps** `originNode` so selection state, `aria-selected`, disabled handling and the roving tabindex survive | Correct call. Record it in the catalog so nobody re-adds it. |
174
- | `showWeek` | `COVERED-ELSEWHERE` | DayPicker `showWeekNumber` | |
180
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
181
+ | --------------------------------------------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
182
+ | `mode: "month" \| "year"` + `onPanelChange` — the 12-month year board | `MISSING` | `grep -rn "onPanelChange\|fullCellRender" src/` → nothing; `CalendarProp` is `DayPickerProps` + footer + `width`/`bordered`/`cellRender` | P2. `<DatePicker picker="month">` covers _picking_ a month; it does not cover a year board a consumer can decorate. |
183
+ | `headerRender` | `COVERED-ELSEWHERE` | `CalendarProp extends DayPickerProps` `:585` → react-day-picker `captionLayout="dropdown" \| "dropdown-months" \| "dropdown-years"` + `startMonth`/`endMonth` (used at `calendar.tsx:60-61`) | Add a `related`/usage note; no code. |
184
+ | `validRange` | `COVERED-ELSEWHERE` | DayPicker `startMonth`/`endMonth` + `disabled`, `calendar.tsx:60-61` | |
185
+ | `fullscreen` | `RENAMED` | `width="full"` + `bordered` `:604,616`, with the measured rationale for the split | No work. |
186
+ | `fullCellRender` — replace the whole cell | `WONT-PORT` | `CalendarCellRenderProp` `:571-583` states the refusal: `cellRender` **wraps** `originNode` so selection state, `aria-selected`, disabled handling and the roving tabindex survive | Correct call. Record it in the catalog so nobody re-adds it. |
187
+ | `showWeek` | `COVERED-ELSEWHERE` | DayPicker `showWeekNumber` | |
175
188
 
176
189
  ### 2.9 `Upload` — `UploadProp` `:1152-1234`
177
190
 
@@ -180,17 +193,17 @@ Present and unusually complete: `action`/`method`/`headers`/`data`/`withCredenti
180
193
  `maxCount`/`maxSizeBytes`, `itemRender`, `previewFile`, `onPreview`/`onDownload`/`onRemove`/`onDrop`,
181
194
  per-item progress + cancel (`upload.tsx:234-289`, `upload-request.ts:16-54`).
182
195
 
183
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
184
- | --- | --- | --- | --- |
185
- | `customRequest` | `RENAMED` | `onUpload(file, item, context)` `:1206-1210` with an abort signal + `onProgress` `upload-types.ts:87,106` — strictly richer than antd's | No work; record both names. |
186
- | `listType` | `RENAMED` | `variant` `:1148-1149` (`dropzone`/`button`/`picture-card`/`picture`/`avatar`/`avatar-crop`) | No work. |
187
- | `fileList` / `defaultFileList` / `onChange` | `RENAMED` | the controlled triad `:1154-1156` | No work. |
188
- | `showUploadList` **object form** — `{showPreviewIcon, showRemoveIcon, showDownloadIcon, extra}` | `MISSING` | `showUploadList?: boolean` `:1196`, consumed as a boolean at `upload.tsx:461,644` | **P1.** "Files can be previewed but not removed" is a permissions case; today the only escape is `itemRender`, which hands the consumer the whole row and its a11y with it. Widen to `boolean \| { … }`. |
189
- | `iconRender` — per-status file icon | `MISSING` | `grep -n iconRender src/components/data-entry/upload.tsx` → nothing (only `PasswordInput` has one) | P2 — or `WONT-PORT` in favour of tokens; decide with the row above. |
190
- | `isImageUrl` — decide whether a thumbnail is an `<img>` | `MISSING` | grep → nothing; `createUploadItem` infers from `file.type` `upload-types.ts:43` | P2. Bites a `value` restored from the server with a `url` but no `mimeType`. |
191
- | `progress` — antd's Progress config | `WONT-PORT` | renders this library's own `Progress` `upload.tsx:866,979` | Token, rule #44. |
192
- | `capture` — mobile camera | `MISSING` | grep `capture` → nothing on the `<input type="file">` | P2. |
193
- | `styles` / `classNames` | `WONT-PORT` | ground rule #4 | |
196
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
197
+ | ----------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
198
+ | `customRequest` | `RENAMED` | `onUpload(file, item, context)` `:1206-1210` with an abort signal + `onProgress` `upload-types.ts:87,106` — strictly richer than antd's | No work; record both names. |
199
+ | `listType` | `RENAMED` | `variant` `:1148-1149` (`dropzone`/`button`/`picture-card`/`picture`/`avatar`/`avatar-crop`) | No work. |
200
+ | `fileList` / `defaultFileList` / `onChange` | `RENAMED` | the controlled triad `:1154-1156` | No work. |
201
+ | `showUploadList` **object form** — `{showPreviewIcon, showRemoveIcon, showDownloadIcon, extra}` | `MISSING` | `showUploadList?: boolean` `:1196`, consumed as a boolean at `upload.tsx:461,644` | **P1.** "Files can be previewed but not removed" is a permissions case; today the only escape is `itemRender`, which hands the consumer the whole row and its a11y with it. Widen to `boolean \| { … }`. |
202
+ | `iconRender` — per-status file icon | `MISSING` | `grep -n iconRender src/components/data-entry/upload.tsx` → nothing (only `PasswordInput` has one) | P2 — or `WONT-PORT` in favour of tokens; decide with the row above. |
203
+ | `isImageUrl` — decide whether a thumbnail is an `<img>` | `MISSING` | grep → nothing; `createUploadItem` infers from `file.type` `upload-types.ts:43` | P2. Bites a `value` restored from the server with a `url` but no `mimeType`. |
204
+ | `progress` — antd's Progress config | `WONT-PORT` | renders this library's own `Progress` `upload.tsx:866,979` | Token, rule #44. |
205
+ | `capture` — mobile camera | `MISSING` | grep `capture` → nothing on the `<input type="file">` | P2. |
206
+ | `styles` / `classNames` | `WONT-PORT` | ground rule #4 | |
194
207
 
195
208
  ### 2.10 `Transfer` — `TransferProp` `:1413-1459`
196
209
 
@@ -198,40 +211,40 @@ Present: `dataSource`, `targetKeys`/`defaultTargetKeys` + the `value`/`defaultVa
198
211
  `showSearch`, `render`, `filterOption`, `showSelectAll`, `oneWay`, `pagination`, `selectedKeys` +
199
212
  `onSelectChange`, `readOnly`, `name`.
200
213
 
201
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
202
- | --- | --- | --- | --- |
203
- | `status` | `MISSING` | no `status` in `:1413-1459`; every other data-entry control takes `ControlStatusProp` (`interaction.prop.ts:117`) | **P1.** A required Transfer inside a `FormField` cannot paint its own validation error. |
204
- | `rowKey` | `MISSING` | `TransferItemProp` requires a literal `key` `:1405-1410` | P2. The house spelling is `getRowId` (`GetRowIdProp`, `PROPS-VOCABULARY.md` · Data collections) — reuse it, do not add `rowKey`. |
205
- | `footer` | `MISSING` | grep → nothing | P2. |
206
- | `operations` / `actions` — relabel the ⇄ buttons | `MISSING` | grep → nothing | P2. The buttons are localized via `t()`; a per-instance override ("承認する" / "取り消す") has no expression. |
207
- | `selectAllLabels` | `MISSING` | grep → nothing | P2. |
208
- | `onSearch` | `MISSING` | `showSearch` `:1434` is a boolean; no query callback | P2 — spell it `onSearchChange` (`OnSearchChangeProp`), as Select and Cascader already do. |
209
- | `onScroll` | `MISSING` | grep → nothing | P2. `pagination` `:1420` covers the large-list case instead. |
210
- | `locale` object | `COVERED-ELSEWHERE` | `t()` + the `i18n/messages/*` bundles | |
211
- | `listStyle` / `styles` / `classNames` | `WONT-PORT` | ground rule #4 | |
214
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
215
+ | ------------------------------------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
216
+ | `status` | `MISSING` | no `status` in `:1413-1459`; every other data-entry control takes `ControlStatusProp` (`interaction.prop.ts:117`) | **P1.** A required Transfer inside a `FormField` cannot paint its own validation error. |
217
+ | `rowKey` | `MISSING` | `TransferItemProp` requires a literal `key` `:1405-1410` | P2. The house spelling is `getRowId` (`GetRowIdProp`, `PROPS-VOCABULARY.md` · Data collections) — reuse it, do not add `rowKey`. |
218
+ | `footer` | `MISSING` | grep → nothing | P2. |
219
+ | `operations` / `actions` — relabel the ⇄ buttons | `MISSING` | grep → nothing | P2. The buttons are localized via `t()`; a per-instance override ("承認する" / "取り消す") has no expression. |
220
+ | `selectAllLabels` | `MISSING` | grep → nothing | P2. |
221
+ | `onSearch` | `MISSING` | `showSearch` `:1434` is a boolean; no query callback | P2 — spell it `onSearchChange` (`OnSearchChangeProp`), as Select and Cascader already do. |
222
+ | `onScroll` | `MISSING` | grep → nothing | P2. `pagination` `:1420` covers the large-list case instead. |
223
+ | `locale` object | `COVERED-ELSEWHERE` | `t()` + the `i18n/messages/*` bundles | |
224
+ | `listStyle` / `styles` / `classNames` | `WONT-PORT` | ground rule #4 | |
212
225
 
213
226
  ### 2.11 `SearchInput` — `SearchInputProp` `:418-437` · `src/components/data-entry/search-input.tsx`
214
227
 
215
228
  This maps to antd `Input.Search`.
216
229
 
217
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
218
- | --- | --- | --- | --- |
219
- | **`onSearch` on Enter** — commit the query immediately instead of waiting out the debounce | `MISSING` — UX | no `onKeyDown` anywhere in the file (read in full, 125 lines); `onSearch` fires only from the debounced effect `search-input.tsx:59-63` | **P1.** Pressing Enter in a search box is the universal "search now" gesture; here it does nothing and the user waits `debounce` ms. The IME guard `:43-46` is correct and must be preserved — Enter that *confirms a conversion* must not submit. |
220
- | `enterButton` — a visible 検索 button | `MISSING` | grep → nothing | P2. Common on JP admin screens; today the consumer puts a `Button` beside it and re-wires the query. |
221
- | `loading` | `MISSING` | grep → nothing; `PendingProp` exists in the vocabulary | P2. Reuse `loading?: PendingProp` as `Select` `:998` does. |
222
- | `size` | `MISSING` | `SearchInputProp` `:418-437` has `status` and `variant` but no `size`, while the `Input` it wraps does (`:110`) | P2 (consistency) — a `SearchInput` cannot match an `sm` toolbar row. |
223
- | `allowClear` | `PRESENT` (always on) | hardcoded clear button `search-input.tsx:110-121` | Fine; note it is not switchable. |
224
- | `searchIcon` | `WONT-PORT` | ground rule #4 | Token. |
230
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
231
+ | ------------------------------------------------------------------------------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
232
+ | **`onSearch` on Enter** — commit the query immediately instead of waiting out the debounce | `MISSING` — UX | no `onKeyDown` anywhere in the file (read in full, 125 lines); `onSearch` fires only from the debounced effect `search-input.tsx:59-63` | **P1.** Pressing Enter in a search box is the universal "search now" gesture; here it does nothing and the user waits `debounce` ms. The IME guard `:43-46` is correct and must be preserved — Enter that _confirms a conversion_ must not submit. |
233
+ | `enterButton` — a visible 検索 button | `MISSING` | grep → nothing | P2. Common on JP admin screens; today the consumer puts a `Button` beside it and re-wires the query. |
234
+ | `loading` | `MISSING` | grep → nothing; `PendingProp` exists in the vocabulary | P2. Reuse `loading?: PendingProp` as `Select` `:998` does. |
235
+ | `size` | `MISSING` | `SearchInputProp` `:418-437` has `status` and `variant` but no `size`, while the `Input` it wraps does (`:110`) | P2 (consistency) — a `SearchInput` cannot match an `sm` toolbar row. |
236
+ | `allowClear` | `PRESENT` (always on) | hardcoded clear button `search-input.tsx:110-121` | Fine; note it is not switchable. |
237
+ | `searchIcon` | `WONT-PORT` | ground rule #4 | Token. |
225
238
 
226
239
  ### 2.12 `CheckboxGroup` / `RadioGroup` — `:457-470`, `:472-494`
227
240
 
228
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
229
- | --- | --- | --- | --- |
230
- | `options` as bare `string \| number` | `MISSING` | `ChoiceOptionProp` requires `{label, value}` `:449-455` | P2. Identical to the `Segmented` row already logged in `antd-parity.md` §3.2 — fix all three together, one shared widening. |
231
- | `Radio.Group block` — stretch to the parent width (antd 5.21) | `MISSING` | `grep -n "block" src/props/components/data-entry.prop.ts` → no `block` on `RadioGroupProp` `:472-494` | P2. `Segmented` already has `block`; a `optionType="button"` group `:440` wants the same. Reuse the spelling. |
232
- | `optionType` / `buttonStyle` | `PRESENT` | `:440`, `:442` | |
233
- | option `title` (hover hint) | `COVERED-ELSEWHERE` | wrap in `Tooltip` — same ruling as `Segmented` in §3.2 | |
234
- | `size` on `Radio.Group` | `MISSING` | no `size` on `RadioGroupProp` `:472-494` | P2 — only meaningful with `optionType="button"`. Reuse `SizeProp`. |
241
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
242
+ | ------------------------------------------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
243
+ | `options` as bare `string \| number` | `MISSING` | `ChoiceOptionProp` requires `{label, value}` `:449-455` | P2. Identical to the `Segmented` row already logged in `antd-parity.md` §3.2 — fix all three together, one shared widening. |
244
+ | `Radio.Group block` — stretch to the parent width (antd 5.21) | `MISSING` | `grep -n "block" src/props/components/data-entry.prop.ts` → no `block` on `RadioGroupProp` `:472-494` | P2. `Segmented` already has `block`; a `optionType="button"` group `:440` wants the same. Reuse the spelling. |
245
+ | `optionType` / `buttonStyle` | `PRESENT` | `:440`, `:442` | |
246
+ | option `title` (hover hint) | `COVERED-ELSEWHERE` | wrap in `Tooltip` — same ruling as `Segmented` in §3.2 | |
247
+ | `size` on `Radio.Group` | `MISSING` | no `size` on `RadioGroupProp` `:472-494` | P2 — only meaningful with `optionType="button"`. Reuse `SizeProp`. |
235
248
 
236
249
  ### 2.13 `Rating` (antd `Rate`) — `src/components/ui/rating.tsx:7-42`
237
250
 
@@ -239,19 +252,19 @@ Present: `count` (+ the legacy `max`), `allowHalf`, `allowClear`, `character` (n
239
252
  `tooltips` folded into the accessible name `:32-36`, `readOnly`, `disabled`, `name`. a11y is correct:
240
253
  `role="radiogroup"` + `role="radio"` + roving tabindex `:131-155`.
241
254
 
242
- | Ant Design prop / behaviour | Status | Evidence | Verdict |
243
- | --- | --- | --- | --- |
244
- | `onHoverChange` | `MISSING` | `hover` state is internal, `rating.tsx:70` | P2. |
245
- | `keyboard: false` | `WONT-PORT` | — | Disabling arrow keys on a `radiogroup` fails WCAG 2.1.1. |
255
+ | Ant Design prop / behaviour | Status | Evidence | Verdict |
256
+ | ---------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
257
+ | `onHoverChange` | `MISSING` | `hover` state is internal, `rating.tsx:70` | P2. |
258
+ | `keyboard: false` | `WONT-PORT` | — | Disabling arrow keys on a `radiogroup` fails WCAG 2.1.1. |
246
259
  | `RatingProps` is not registered in `src/props/components/` | `MISSING` (house standard, not parity) | `grep -rn "RatingProp" src/props/` → no hit; the type lives in `src/components/ui/rating.tsx:7` | Same for `TagInputProps`, `ToggleProp`, `ToggleGroupItemProp`, `PasswordInputProps`, `UploadCropDialog`. Out of Ant-parity scope, but `STANDARDS-vocabulary-tokens.md` rules 1–2 want them in the registry. Flagged, not scheduled. |
247
260
 
248
261
  ### 2.14 `PasswordStrength` — `src/components/data-entry/password-strength.tsx` (no antd counterpart)
249
262
 
250
- | Behaviour | Status | Evidence | Verdict |
251
- | --- | --- | --- | --- |
252
- | Every visible string is hardcoded English | `MISSING` — **i18n** | `DEFAULT_LABELS = { weak: "Weak", fair: "Fair", strong: "Strong" }` `:4-8`; `labelForRule` returns `"8+ characters"`, `"Contains uppercase letter"`, … `:119-133`; `` aria-label={`Password strength ${score}/4`} `` `:75`; `"Passed: "` / `"Failed: "` `:106`; the live region `{scoreLabel(…)} password strength` `:113`. The file never imports `useTranslation` (`grep -L useTranslation` over `src/components/data-entry/*.tsx` — this is the only non-shim file with real copy that misses it). | **P0.** Route every string through `t()` + the `en`/`ja`/`vi` bundles. `labels` `:21` overrides only 3 of the ~10 strings, so a consumer cannot even work around it. |
253
- | `role="img"` on the meter | `MISSING` — a11y | `:74-76` | P1. It is a value indicator, not an image: `role="meter"` (or `progressbar`) with `aria-valuenow`/`aria-valuemin`/`aria-valuemax`/`aria-valuetext`. Screen-reader users get a static label today and never hear the value change except via the separate live region `:112-114`. |
254
- | Rule set is closed | `MISSING` | `PasswordRule` is a fixed 5-member union `:3,10`; thresholds hardcoded `:35-39` | P2. A tenant policy of "12+ characters" has no expression. |
263
+ | Behaviour | Status | Evidence | Verdict |
264
+ | ----------------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
265
+ | Every visible string is hardcoded English | `MISSING` — **i18n** | `DEFAULT_LABELS = { weak: "Weak", fair: "Fair", strong: "Strong" }` `:4-8`; `labelForRule` returns `"8+ characters"`, `"Contains uppercase letter"`, … `:119-133`; ``aria-label={`Password strength ${score}/4`}`` `:75`; `"Passed: "` / `"Failed: "` `:106`; the live region `{scoreLabel(…)} password strength` `:113`. The file never imports `useTranslation` (`grep -L useTranslation` over `src/components/data-entry/*.tsx` — this is the only non-shim file with real copy that misses it). | **P0.** Route every string through `t()` + the `en`/`ja`/`vi` bundles. `labels` `:21` overrides only 3 of the ~10 strings, so a consumer cannot even work around it. |
266
+ | `role="img"` on the meter | `MISSING` — a11y | `:74-76` | P1. It is a value indicator, not an image: `role="meter"` (or `progressbar`) with `aria-valuenow`/`aria-valuemin`/`aria-valuemax`/`aria-valuetext`. Screen-reader users get a static label today and never hear the value change except via the separate live region `:112-114`. |
267
+ | Rule set is closed | `MISSING` | `PasswordRule` is a fixed 5-member union `:3,10`; thresholds hardcoded `:35-39` | P2. A tenant policy of "12+ characters" has no expression. |
255
268
 
256
269
  ---
257
270
 
@@ -259,28 +272,28 @@ Present: `count` (+ the legacy `max`), `allowHalf`, `allowClear`, `character` (n
259
272
 
260
273
  ### P0 — breaks a real screen
261
274
 
262
- | # | Component | Gap | Cite |
263
- | --- | --- | --- | --- |
264
- | 1 | `NumberInput` | Locale round-trip corrupts the value: formatted with `Intl` for the active locale, parsed with a hardcoded `,`-is-a-thousands-separator rule. `vi` (shipped) turns `1.5` into `15` on blur. | `number-input.tsx:41,100,220` |
265
- | 2 | `PasswordStrength` | Entirely hardcoded English — labels, checklist, `aria-label`, live region. No `t()` in the file. | `password-strength.tsx:4-8,75,106,113,119-133` |
275
+ | # | Component | Gap | Cite |
276
+ | --- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
277
+ | 1 | `NumberInput` | Locale round-trip corrupts the value: formatted with `Intl` for the active locale, parsed with a hardcoded `,`-is-a-thousands-separator rule. `vi` (shipped) turns `1.5` into `15` on blur. | `number-input.tsx:41,100,220` |
278
+ | 2 | `PasswordStrength` | Entirely hardcoded English — labels, checklist, `aria-label`, live region. No `t()` in the file. | `password-strength.tsx:4-8,75,106,113,119-133` |
266
279
 
267
280
  ### P1 — a workaround exists, but it is hand-rolled and a11y-risky
268
281
 
269
- | # | Component | Gap | Cite |
270
- | --- | --- | --- | --- |
271
- | 3 | `Slider` | No `aria-valuetext`; `tooltip.formatter` paints an `aria-hidden` bubble only. Formatted sliders announce a bare number. | `slider.tsx:33,138-157` |
272
- | 4 | `Select` / `Cascader` | No list virtualization anywhere in `src/` (antd `virtual` is on by default). | `grep -rl "virtual\|useVirtualizer\|react-window\|@tanstack/react-virtual" src/` → ∅ |
273
- | 5 | `ColorPicker` | No `presets`, and the only control in the group with no `size`/`status`/`variant`. | `data-entry.prop.ts:893-908` |
274
- | 6 | `PasswordStrength` | `role="img"` on a value meter instead of `role="meter"` + `aria-valuenow`. | `password-strength.tsx:74-76` |
275
- | 7 | `SearchInput` | Enter does not commit the query — `onSearch` fires only after the debounce; no `onKeyDown` in the file. | `search-input.tsx:59-63` (whole file read) |
276
- | 8 | `Upload` | `showUploadList` is boolean-only; "preview but do not remove" forces `itemRender`, which hands the consumer the row's a11y. | `data-entry.prop.ts:1196`, `upload.tsx:461,644` |
277
- | 9 | `DateRangePicker` | `disabled` is scalar; one end of a range cannot be locked (antd `disabled: [boolean, boolean]`). | `data-entry.prop.ts:787` |
278
- | 10 | `DatePicker` / `DateRangePicker` | No `defaultPickerValue`/`pickerValue` — the panel cannot be opened on a chosen month. | grep → ∅ |
279
- | 11 | `Transfer` | No `status`; cannot paint a validation error inside a `FormField`. | `data-entry.prop.ts:1413-1459` |
280
- | 12 | `Cascader` | Search is uncapped and uncustomizable (no `filterOption`/`filterSort`/result limit; antd's `limit` default is 50). | `cascader.tsx:389-393`, `data-entry.prop.ts:1263` |
281
- | 13 | `Select` | `fieldNames` absent although `Cascader` and `TreeSelect` both have it — a foreign row shape must be pre-mapped. | `data-entry.prop.ts:1281,1362` vs `:941-1080` |
282
- | 14 | `NumberInput` | No `stringMode`; money and >2^53 ids lose precision. | grep → ∅ |
283
- | 15 | `Select` (catalog) | `onSelect` / `onDeselect` / `maxTagPlaceholder` ship but are absent from the MCP entry — consumers cannot discover them. | props `:1093,1112-1114,1124`; catalog `mcp/src/data/components.ts:5572-5987` |
282
+ | # | Component | Gap | Cite |
283
+ | --- | --------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
284
+ | 3 | `Slider` | No `aria-valuetext`; `tooltip.formatter` paints an `aria-hidden` bubble only. Formatted sliders announce a bare number. | `slider.tsx:33,138-157` |
285
+ | 4 | `Select` / `Cascader` | No list virtualization anywhere in `src/` (antd `virtual` is on by default). | `grep -rl "virtual\|useVirtualizer\|react-window\|@tanstack/react-virtual" src/` → ∅ |
286
+ | 5 | `ColorPicker` | No `presets`, and the only control in the group with no `size`/`status`/`variant`. | `data-entry.prop.ts:893-908` |
287
+ | 6 | `PasswordStrength` | `role="img"` on a value meter instead of `role="meter"` + `aria-valuenow`. | `password-strength.tsx:74-76` |
288
+ | 7 | `SearchInput` | Enter does not commit the query — `onSearch` fires only after the debounce; no `onKeyDown` in the file. | `search-input.tsx:59-63` (whole file read) |
289
+ | 8 | `Upload` | `showUploadList` is boolean-only; "preview but do not remove" forces `itemRender`, which hands the consumer the row's a11y. | `data-entry.prop.ts:1196`, `upload.tsx:461,644` |
290
+ | 9 | `DatePicker range` | `disabled` is scalar; one end of a range cannot be locked (antd `disabled: [boolean, boolean]`). | `DatePickerBaseProp.disabled` |
291
+ | 10 | `DatePicker` | No `defaultPickerValue`/`pickerValue` — the panel cannot be opened on a chosen month. | grep → ∅ |
292
+ | 11 | `Transfer` | No `status`; cannot paint a validation error inside a `FormField`. | `data-entry.prop.ts:1413-1459` |
293
+ | 12 | `Cascader` | Search is uncapped and uncustomizable (no `filterOption`/`filterSort`/result limit; antd's `limit` default is 50). | `cascader.tsx:389-393`, `data-entry.prop.ts:1263` |
294
+ | 13 | `Select` | `fieldNames` absent although `Cascader` and `TreeSelect` both have it — a foreign row shape must be pre-mapped. | `data-entry.prop.ts:1281,1362` vs `:941-1080` |
295
+ | 14 | `NumberInput` | No `stringMode`; money and >2^53 ids lose precision. | grep → ∅ |
296
+ | 15 | `Select` (catalog) | `onSelect` / `onDeselect` / `maxTagPlaceholder` ship but are absent from the MCP entry — consumers cannot discover them. | props `:1093,1112-1114,1124`; catalog `mcp/src/data/components.ts:5572-5987` |
284
297
 
285
298
  ### P2 — nice to have
286
299
 
@@ -289,8 +302,8 @@ Present: `count` (+ the legacy `max`), `allowHalf`, `allowClear`, `character` (n
289
302
  `NumberInput`: `onStep`. ·
290
303
  `Slider`: `Intl`-formatted default tooltip, `draggableTrack`. ·
291
304
  `ColorPicker`: `allowClear`+`onClear`, `readOnly`, `format`/`defaultFormat`/`onFormatChange`. ·
292
- `DatePicker`: `mode`+`onPanelChange`, `onOk`, `maxTagCount` for `multiple`. ·
293
- `DateRangePicker`: `separator` (token), `onCalendarChange`. ·
305
+ `DatePicker`: `mode`+`onPanelChange`, `onOk`, `maxTagCount` for `multiple`, `format` as an
306
+ array of accepted inputs, and — with `range` `separator` (token) and `onCalendarChange`. ·
294
307
  `TimePicker`: `cellRender`, `onSelect`. ·
295
308
  `Calendar`: `mode`/`onPanelChange` (year board). ·
296
309
  `Upload`: `iconRender`, `isImageUrl`, `capture`. ·
@@ -309,13 +322,13 @@ Present: `count` (+ the legacy `max`), `allowHalf`, `allowClear`, `character` (n
309
322
  component that already exists. Three candidates were considered and rejected before reaching a C1–C7
310
323
  ledger:
311
324
 
312
- | Candidate | Why it is not a new component |
313
- | --- | --- |
314
- | `ColorSwatchPicker` (for `presets`) | Fails **C3** — it is a `presets` prop on the existing `ColorPicker`, or a row of `Toggle`s in the app. Adding a second colour control would be the duplication ground rule #3 forbids. |
315
- | `CascaderPanel` (for antd `Cascader.Panel`) | Fails **C3/C4** — it is `Cascader` rendered without its trigger, i.e. one prop (or a `<Cascader.Panel>` sub-part on the same component), not a second component with a second API. |
316
- | `SearchButton` / `Input.Search` twin (for `enterButton`) | Fails **C1/C3** — `SearchInput` exists; `enterButton` is a prop on it. |
325
+ | Candidate | Why it is not a new component |
326
+ | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
327
+ | `ColorSwatchPicker` (for `presets`) | Fails **C3** — it is a `presets` prop on the existing `ColorPicker`, or a row of `Toggle`s in the app. Adding a second colour control would be the duplication ground rule #3 forbids. |
328
+ | `CascaderPanel` (for antd `Cascader.Panel`) | Fails **C3/C4** — it is `Cascader` rendered without its trigger, i.e. one prop (or a `<Cascader.Panel>` sub-part on the same component), not a second component with a second API. |
329
+ | `SearchButton` / `Input.Search` twin (for `enterButton`) | Fails **C1/C3** — `SearchInput` exists; `enterButton` is a prop on it. |
317
330
 
318
- The one place where a *new* thing is arguably justified is the **virtualized list engine** behind
331
+ The one place where a _new_ thing is arguably justified is the **virtualized list engine** behind
319
332
  `Select virtual` / `Cascader virtual`. That is not a data-entry component: it is the same engine
320
333
  `list-masonry.md` needs for `List`. It should be specced once, there, and consumed here — not
321
334
  invented twice.