@stcn52/pro 0.0.0-stage → 0.2.1

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 (163) hide show
  1. package/CHANGELOG.md +621 -0
  2. package/CONVENTIONS.md +152 -0
  3. package/LICENSE +21 -0
  4. package/README.md +226 -2
  5. package/RELEASING.md +51 -0
  6. package/THIRD_PARTY_NOTICES.md +52 -0
  7. package/dist/index.cjs +23272 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.cts +5068 -0
  10. package/dist/index.d.ts +5068 -0
  11. package/dist/index.js +23060 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/styles.css +10485 -0
  14. package/dist/tokens.css +322 -0
  15. package/docs/antd-alignment-plan.md +983 -0
  16. package/docs/api.md +3669 -0
  17. package/docs/components.md +1706 -0
  18. package/docs/measurements.md +152 -0
  19. package/docs/qa/README.md +50 -0
  20. package/docs/qa/browser-checks.json +394 -0
  21. package/docs/qa/d-chrome-checklist-dark.png +0 -0
  22. package/docs/qa/d-chrome-checks.json +234 -0
  23. package/docs/qa/d-chrome-detail-dark.png +0 -0
  24. package/docs/qa/d-chrome-filters-dark.png +0 -0
  25. package/docs/qa/d-chrome-shell-dark.png +0 -0
  26. package/docs/qa/d-detail-checks.json +222 -0
  27. package/docs/qa/d-detail-comments-failed-dark.png +0 -0
  28. package/docs/qa/d-detail-preview-dark.png +0 -0
  29. package/docs/qa/d-entity-checks.json +294 -0
  30. package/docs/qa/d-entity-failed-dark.png +0 -0
  31. package/docs/qa/d-workbench-canvas-dark.png +0 -0
  32. package/docs/qa/d-workbench-checks.json +338 -0
  33. package/docs/qa/d-workbench-edit-failed-dark.png +0 -0
  34. package/docs/qa/d-workbench-roadmap-dark.png +0 -0
  35. package/docs/qa/d-workbench-wizard-failed-dark.png +0 -0
  36. package/docs/qa/date-range-mobile-dark.jpg +0 -0
  37. package/docs/qa/p0-cascader-checks.json +228 -0
  38. package/docs/qa/p0-cascader-multiple-dark.png +0 -0
  39. package/docs/qa/p0-config-checks.json +430 -0
  40. package/docs/qa/p0-config-nested-dark.png +0 -0
  41. package/docs/qa/p0-date-checks.json +595 -0
  42. package/docs/qa/p0-date-time-dark.png +0 -0
  43. package/docs/qa/p0-form-lifecycle-checks.json +30 -0
  44. package/docs/qa/p0-form-lifecycle.png +0 -0
  45. package/docs/qa/p0-select-checks.json +378 -0
  46. package/docs/qa/p0-select-virtual-dark.png +0 -0
  47. package/docs/qa/p0-table-checks.json +290 -0
  48. package/docs/qa/p0-table-virtual-dark.png +0 -0
  49. package/docs/qa/p0-transfer-checks.json +322 -0
  50. package/docs/qa/p0-transfer-pagination-dark.png +0 -0
  51. package/docs/qa/p0-tree-checks.json +242 -0
  52. package/docs/qa/p0-tree-range-dark.png +0 -0
  53. package/docs/qa/p1-app-browser-checks.json +34 -0
  54. package/docs/qa/p1-app-feedback-dark.png +0 -0
  55. package/docs/qa/p1-avatar-checks.json +370 -0
  56. package/docs/qa/p1-avatar-dark.png +0 -0
  57. package/docs/qa/p1-browser-checks.json +218 -0
  58. package/docs/qa/p1-card-checks.json +262 -0
  59. package/docs/qa/p1-card-dark.png +0 -0
  60. package/docs/qa/p1-content-navigation-browser-checks.json +114 -0
  61. package/docs/qa/p1-controls-browser-checks.json +86 -0
  62. package/docs/qa/p1-controls-desktop-dark.png +0 -0
  63. package/docs/qa/p1-dialog-nested-dark.png +0 -0
  64. package/docs/qa/p1-display-browser-checks.json +114 -0
  65. package/docs/qa/p1-display-desktop-dark.png +0 -0
  66. package/docs/qa/p1-divider-dark.png +0 -0
  67. package/docs/qa/p1-drawer-browser-checks.json +302 -0
  68. package/docs/qa/p1-drawer-rtl-dark.png +0 -0
  69. package/docs/qa/p1-general-input-browser-checks.json +114 -0
  70. package/docs/qa/p1-general-input-desktop-dark.png +0 -0
  71. package/docs/qa/p1-image-browser-checks.json +38 -0
  72. package/docs/qa/p1-image-preview-dark.png +0 -0
  73. package/docs/qa/p1-inline-browser-checks.json +58 -0
  74. package/docs/qa/p1-inline-desktop-dark.png +0 -0
  75. package/docs/qa/p1-layout-browser-checks.json +114 -0
  76. package/docs/qa/p1-layout-desktop-dark.png +0 -0
  77. package/docs/qa/p1-menu-checks.json +357 -0
  78. package/docs/qa/p1-menu-long-dark.png +0 -0
  79. package/docs/qa/p1-nav-dark.png +0 -0
  80. package/docs/qa/p1-nav-sidebar-checks.json +218 -0
  81. package/docs/qa/p1-navigation-browser-checks.json +114 -0
  82. package/docs/qa/p1-navigation-mobile-dark.png +0 -0
  83. package/docs/qa/p1-pagination-checks.json +309 -0
  84. package/docs/qa/p1-pagination-dark.png +0 -0
  85. package/docs/qa/p1-pagination-mobile-dark.png +0 -0
  86. package/docs/qa/p1-popup-actions-browser-checks.json +58 -0
  87. package/docs/qa/p1-popup-actions-desktop-dark.png +0 -0
  88. package/docs/qa/p1-progress-browser-checks.json +30 -0
  89. package/docs/qa/p1-progress-desktop-dark.png +0 -0
  90. package/docs/qa/p1-rate-half-desktop-dark.png +0 -0
  91. package/docs/qa/p1-segment-checks.json +426 -0
  92. package/docs/qa/p1-segment-dark.png +0 -0
  93. package/docs/qa/p1-sidebar-dark.png +0 -0
  94. package/docs/qa/p1-slider-rate-browser-checks.json +114 -0
  95. package/docs/qa/p1-states-browser-checks.json +34 -0
  96. package/docs/qa/p1-states-checks.json +342 -0
  97. package/docs/qa/p1-states-dark.png +0 -0
  98. package/docs/qa/p1-states-mobile-dark.png +0 -0
  99. package/docs/qa/p1-tabs-editable-dark.png +0 -0
  100. package/docs/qa/p1-tag-dark.png +0 -0
  101. package/docs/qa/p1-tag-divider-checks.json +330 -0
  102. package/docs/qa/p1-upload-browser-checks.json +30 -0
  103. package/docs/qa/p1-upload-list-dark.png +0 -0
  104. package/docs/qa/p2-ai-panel-checks.json +401 -0
  105. package/docs/qa/p2-ai-panel-dark.png +0 -0
  106. package/docs/qa/p2-alert-dark.png +0 -0
  107. package/docs/qa/p2-app-final-checks.json +309 -0
  108. package/docs/qa/p2-app-final-dark.png +0 -0
  109. package/docs/qa/p2-badge-alert-checks.json +466 -0
  110. package/docs/qa/p2-beam-motion-checks.json +58 -0
  111. package/docs/qa/p2-calendar-dark.png +0 -0
  112. package/docs/qa/p2-calendar-timeline-checks.json +254 -0
  113. package/docs/qa/p2-charts-checks.json +242 -0
  114. package/docs/qa/p2-charts-dark.png +0 -0
  115. package/docs/qa/p2-color-gradient-dark.png +0 -0
  116. package/docs/qa/p2-content-navigation-checks.json +794 -0
  117. package/docs/qa/p2-dialog-popover-dark.png +0 -0
  118. package/docs/qa/p2-display-checks.json +128 -0
  119. package/docs/qa/p2-display-dark.png +0 -0
  120. package/docs/qa/p2-drawer-final-dark.png +0 -0
  121. package/docs/qa/p2-extras-checks.json +482 -0
  122. package/docs/qa/p2-general-inputs-checks.json +154 -0
  123. package/docs/qa/p2-general-inputs-dark.png +0 -0
  124. package/docs/qa/p2-icon-checks.json +466 -0
  125. package/docs/qa/p2-icon-dark.png +0 -0
  126. package/docs/qa/p2-image-dark.png +0 -0
  127. package/docs/qa/p2-inline-editors-checks.json +199 -0
  128. package/docs/qa/p2-input-button-checks.json +778 -0
  129. package/docs/qa/p2-input-dark.png +0 -0
  130. package/docs/qa/p2-layout-final-checks.json +136 -0
  131. package/docs/qa/p2-layout-final-dark.png +0 -0
  132. package/docs/qa/p2-lists-checks.json +290 -0
  133. package/docs/qa/p2-masonry-dark.png +0 -0
  134. package/docs/qa/p2-media-checks.json +212 -0
  135. package/docs/qa/p2-mentions-color-checks.json +362 -0
  136. package/docs/qa/p2-overlay-checks.json +370 -0
  137. package/docs/qa/p2-people-picker-dark.png +0 -0
  138. package/docs/qa/p2-popconfirm-final-dark.png +0 -0
  139. package/docs/qa/p2-popup-final-checks.json +223 -0
  140. package/docs/qa/p2-qr-browser-decode.json +30 -0
  141. package/docs/qa/p2-quick-select-dark.png +0 -0
  142. package/docs/qa/p2-quick-text-dark.png +0 -0
  143. package/docs/qa/p2-rate-dark.png +0 -0
  144. package/docs/qa/p2-scroll-navigation-checks.json +198 -0
  145. package/docs/qa/p2-scroll-navigation-dark.png +0 -0
  146. package/docs/qa/p2-selection-bar-checks.json +302 -0
  147. package/docs/qa/p2-selection-bar-dark.png +0 -0
  148. package/docs/qa/p2-slider-rate-checks.json +262 -0
  149. package/docs/qa/p2-switch-dark.png +0 -0
  150. package/docs/qa/p2-tabs-dark.png +0 -0
  151. package/docs/qa/p2-timeline-dark.png +0 -0
  152. package/docs/qa/p2-toast-dark.png +0 -0
  153. package/docs/qa/p2-toggle-checks.json +834 -0
  154. package/docs/qa/p2-tour-dark.png +0 -0
  155. package/docs/qa/p2-upload-dark.png +0 -0
  156. package/docs/qa/table-details.md +26 -0
  157. package/docs/qa/table-mobile-dark.jpg +0 -0
  158. package/docs/qa/transfer-mobile-light.jpg +0 -0
  159. package/docs/qa/tree-browser-checks.json +226 -0
  160. package/docs/qa/tree-details.md +25 -0
  161. package/docs/qa/tree-directory-mobile-dark.jpg +0 -0
  162. package/docs/tokens.md +255 -0
  163. package/package.json +95 -4
@@ -0,0 +1,1706 @@
1
+ # Component reference
2
+
3
+ Every component below ships from `@stcn52/pro`. Props are the ones exported from
4
+ `src/`, states are the ones actually styled, and the keyboard column is what the
5
+ implementation handles — not an intention.
6
+
7
+ Conventions used throughout:
8
+
9
+ - **Controlled by default where it matters.** Anything with a selection
10
+ (`Nav`, `Tree`, `Segment`, `Select`, `DataTable`, `DetailSheet`) takes a value
11
+ and an `onChange`; the host owns the state and the URL.
12
+ - **Refs are forwarded** on every form control (`Button`, `Input`, `Select`,
13
+ `Checkbox`, `Switch`), so a page can `searchRef.current?.focus()` — that is how
14
+ `⌘K` works.
15
+ - **One focus ring.** Defined once in `base.css` as `:focus-visible` with
16
+ `outline: 2px solid var(--accent)`; components never invent their own.
17
+ - **Tokens only.** No component hard-codes a colour, radius or duration.
18
+
19
+ ---
20
+
21
+ ## 1. Actions
22
+
23
+ ### `Button`
24
+
25
+ `variant: 'primary' | 'default' | 'text' | 'danger'` · `size: 'sm' | 'md' | 'lg'` ·
26
+ `icon?` / `iconAfter?` (`IconName`) · `block?` · `loading?` · ref forwarded.
27
+
28
+ - `loading` renders an inline spinner **and** sets `disabled` + `aria-busy`, so
29
+ the label width never jumps.
30
+ - Icon-only (no children) adds `pro-btn--icon-only` for a square hit area.
31
+ - States: hover, pressed (0.5px sink), focus-visible, disabled, loading.
32
+
33
+ ### `ActionButton`
34
+
35
+ `icon` (required) · `label` (required) · `size` · `active?` · `tone?: 'default' | 'accent'`.
36
+
37
+ The bare icon button used by the chrome, toolbars and rows. `label` becomes both
38
+ `aria-label` and `title`; `active` sets `aria-pressed` and the accent tint. If you
39
+ pass `children` it becomes a pill-shaped button with an icon.
40
+
41
+ ### `SplitButton`
42
+
43
+ `label` · `onPrimary` · `menuLabel` · `menuItems?` · `menuOpen?` / `onToggleMenu?`.
44
+
45
+ The product's "New ▾" control. The caret owns its own `Popover` + `Menu`
46
+ anchored to itself, so a host only supplies the items; pass `menuOpen` +
47
+ `onToggleMenu` to control it instead.
48
+
49
+ ---
50
+
51
+ ## 2. Forms
52
+
53
+ ### `Input` / `Textarea` / `SearchInput`
54
+
55
+ `size?: 'md' | 'lg'` · `invalid?` · ref forwarded. `invalid` sets
56
+ `aria-invalid` and the red border; `SearchInput` adds a leading magnifier and a
57
+ `suffix` slot (used for the `⌘K` hint), and forwards a ref.
58
+
59
+ ### `Field`
60
+
61
+ `label?` · `required?` · `hint?` · `error?` · `htmlFor?`.
62
+
63
+ Wraps any control: generates an `id` with `useId` when none is given, wires
64
+ `label[for]`, renders the required asterisk, and shows `error` (with an icon,
65
+ `role="alert"`) **instead of** the hint. The rule the pages follow: pass an error
66
+ only after the first blur or a submit attempt, and clear it the moment the value
67
+ becomes valid.
68
+
69
+ ### `Select` / `MultiSelect`
70
+
71
+ `value: string | null` · `options: { value, label, icon?, color?, disabled? }[]` ·
72
+ `onChange` · `placeholder?` · `size?` · `invalid?` · `disabled?`.
73
+
74
+ - Trigger shows `pro-select-trigger--placeholder` when nothing is chosen; the
75
+ caret rotates with `aria-expanded`.
76
+ - Options can be rich (a `StatusTag`, a colour dot), which is how the status
77
+ pickers render.
78
+ - Opening from the keyboard puts focus on the first item; choosing one closes
79
+ the layer by requesting `onOpenChange(false)`.
80
+ - `MultiSelect` takes `values: string[]` and an optional `label` before the count badge.
81
+ - `searchable` defaults to false for Select and true for MultiSelect. `searchValue`
82
+ controls the query; `onSearch` reports typing and a nonempty query reset on close.
83
+ `searchPlaceholder` customizes the search field. Arrows enter enabled choices;
84
+ MultiSelect also supports arrow navigation between checkboxes.
85
+ - Default filtering reads text from rich label children. Supply `searchText` for
86
+ opaque custom labels, or `filterOption(query, option)` for a custom predicate.
87
+ `filterOption={false}` leaves filtering to a remote host. Optional `filterSort`
88
+ sorts within each group without changing the supplied options.
89
+ - A controlled query stays host-owned; update `searchValue` in response to
90
+ `onSearch`. Search is ignored when `searchable={false}`.
91
+
92
+ - `virtual` opts into fixed-height windowing. `listHeight` defaults to 256 and
93
+ `itemHeight` to 36. Match `itemHeight` to your custom row geometry. Arrows,
94
+ Home/End and typeahead navigate the complete result set; a focused row remains
95
+ mounted during wheel scrolling. Use `virtual={false}` to render all options.
96
+ - `MultiSelect` supports `mode="tags"`: Enter or a checkbox commits a trimmed
97
+ custom value. Created tags remain selectable and clearable through `values`.
98
+ `tokenSeparators={[',', ';']}` commits complete tokens from typing/paste and
99
+ leaves the trailing fragment in the search field. In multiple mode, tokens
100
+ resolve existing values/label text only. IME text is committed after composition.
101
+ - `maxCount` limits distinct values in MultiSelect, including values absent from
102
+ the current options. At the limit, new choices are disabled, selected choices
103
+ remain removable, and excess batch tokens are ignored. Disabled selections
104
+ remain locked. Tags mode always displays the search field.
105
+
106
+ - Both controls forward a native button ref and attributes, accept controlled
107
+ `open` / `onOpenChange` or `defaultOpen`, and keep search intact when a host
108
+ refuses to close. Queries reset only after the popup actually closes.
109
+ - Groups use `{ key?, label, options, disabled? }`; their disabled flag applies
110
+ to every child. Values remain unique strings. Group headers count toward
111
+ virtual geometry but not option positions or keyboard selection.
112
+ - `labelInValue` changes single values to `{ value, label? } | null` and multiple
113
+ values to arrays of those objects. Selected labels survive remote option
114
+ replacement; unknown values display their supplied label or literal value.
115
+ `defaultValue` / `defaultValues` enable uncontrolled selection.
116
+ - Single `allowClear` adds a separate accessible clear button and emits `null`;
117
+ its callback therefore accepts `string | null`. Without it, the existing
118
+ string-only callback contract is unchanged. MultiSelect keeps its default
119
+ Clear footer and in multiple mode preserves values absent from current options; explicit
120
+ `allowClear={true}` also clears unknown values and `allowClear={false}` hides
121
+ it. Tags mode also clears created tags by default. Disabled selections stay locked.
122
+ - `optionRender(option, { index })` customizes presentation without replacing
123
+ native option keyboard/disabled semantics; keep interactive controls outside
124
+ the rendered option. `labelRender` customizes the single selected label;
125
+ MultiSelect `tagRender({ value, label, disabled, closable, onClose })` renders
126
+ accessible removable tags beside the trigger without nested buttons.
127
+ - `loading`, `error` and `notFoundContent` present host-owned async state;
128
+ requests, cancellation and option updates remain the host's responsibility.
129
+ Existing options remain usable while loading.
130
+ - Klun retains separate Select/MultiSelect, string keys, native button refs and
131
+ dialog/menu or checkbox semantics. AntD's numeric values, combobox imperative
132
+ handle, popup internals and full prop naming are not drop-in compatible.
133
+
134
+ ### `Checkbox` / `Switch`
135
+
136
+ `Checkbox`: `label?` · `indeterminate?` · `compact?`. The indeterminate flag is
137
+ applied to the DOM node in an effect (React has no prop for it); the box is
138
+ `.pro-checkbox__box` so the focus ring can sit on the visual square.
139
+ `Switch`: `label` (required) · `size?: 'sm' | 'md'`; renders `role="switch"` and a
140
+ 36×20 track (20×16 in `sm`).
141
+
142
+ ---
143
+
144
+ ## 3. Overlays
145
+
146
+ ### `Dialog`
147
+
148
+ `open` · `onClose` · `title` · `tools?` · `footer?` · `size?: 'sm'|'md'|'lg'|'xl'` ·
149
+ `dismissable?` · `bodyClassName?`.
150
+
151
+ Portalled to `<body>`; locks body scroll; **traps Tab** (wraps at both ends);
152
+ `Esc` and scrim click only close when `dismissable` (false while a form is
153
+ dirty); focuses the first control on open and **restores focus to the opener** on
154
+ close.
155
+
156
+ ### `Popover`
157
+
158
+ `anchor: HTMLElement | null` · `open` · `onClose` · `placement?` · `matchAnchorWidth?` ·
159
+ `dismissOnOutsideClick?` · `autoFocus?`.
160
+
161
+ A single floating layer: measured against the anchor, flipped when the preferred
162
+ side has no room, clamped to the viewport, re-measured on scroll/resize. Hides
163
+ itself with `visibility` until it has coordinates (so it never flashes at 0,0),
164
+ focuses its first control when opened from the keyboard, and returns focus to the
165
+ anchor when Escape closes it. Provides `usePopoverClose()` so nested menus can
166
+ dismiss it.
167
+
168
+ ### `Menu`
169
+
170
+ `items: { id, label, icon?, hint?, danger?, disabled?, checked?, onSelect? }[]` ·
171
+ `searchable?` · `onSelect?`.
172
+
173
+ `role="menu"` with `menuitem` / `menuitemradio` (the latter when `checked` is
174
+ defined). Keyboard: **↑ ↓** move, **Home/End** jump, a printable character runs
175
+ typeahead, **Enter/Space** activate (native button), and choosing an item closes
176
+ the surrounding popover. `hint` is the right-aligned shortcut/count slot.
177
+
178
+ ### `ContextMenu`
179
+
180
+ `items` · `onOpen?` · render-prop `children({ onContextMenu })` · `aria-label`.
181
+
182
+ Opens at the **pointer** (not the element rect), flipped and clamped to an 8px
183
+ viewport margin, portalled to `body` so `overflow: hidden` never clips it.
184
+ Dividers, disabled and danger items; `role="menu"` with roving focus, ↑↓,
185
+ Home/End and typeahead. The **Menu key** and **Shift+F10** open it on the focused
186
+ trigger; Escape closes and returns focus to the element that was right-clicked;
187
+ the native menu is suppressed only while a non-empty menu shows. `useContextMenu(items)`
188
+ is the imperative form — `openAt(x, y, context)` → `onSelect(context)`.
189
+
190
+ A press inside a **stacked** layer (`.pro-popover`, `.pro-portal`,
191
+ `[role=listbox]`, `[role=menu]`) does not dismiss the parent — without that guard
192
+ a `Select` inside a `Popover` could never be used with a mouse.
193
+
194
+ ### `Tooltip`
195
+
196
+ `label` · `delay = 400` · `placement?: 'top' | 'bottom'`.
197
+
198
+ Shows on hover after the delay and **immediately on focus**; hides on blur,
199
+ Escape and any scroll. Portalled, `role="tooltip"`, non-interactive.
200
+
201
+ ### `Menu` submenus and dividers
202
+
203
+ An item may carry `items` — it then renders a caret, `aria-haspopup="menu"` /
204
+ `aria-expanded`, and opens those entries beside its own row while the parent menu
205
+ stays open (choosing in the child closes the whole tree). `ArrowRight` opens it,
206
+ `ArrowLeft` closes it and returns the focus to the row. A `divider: true` item
207
+ renders the hairline between groups that `ContextMenuItem` already had.
208
+
209
+ ### `Toast` — `ToastProvider` + `useToast()`
210
+
211
+ ```ts
212
+ useToast().push(tone, message, options?) // returns the id, so the host can dismiss it early
213
+ useToast().dismiss(id)
214
+ ```
215
+
216
+ - **tone** `'info' | 'success' | 'warning' | 'error'` — the hairline and the glyph
217
+ carry it (`warning` is the product's amber; the sprite has no warning mark, so
218
+ it reuses the filled disc, and `icon` overrides the glyph outright).
219
+ - **options** `{ duration?, title?, action?, variant?, closeable?, icon? }`. The
220
+ third argument also still takes a bare number, which is the duration.
221
+ `duration: 0` keeps a toast until it is dismissed; `title` prints a bold line
222
+ with the message as the description under it; `action: { label, onClick }` adds
223
+ one inline command (Undo, View, Retry) and does **not** dismiss anything itself.
224
+ - **placement** on the provider: `top-left | top-center | top-right | bottom-left |
225
+ bottom-center | bottom-right`. The stack grows away from the pinned edge and the
226
+ toast animates in from it. The default, `top-center`, is where the product
227
+ raises its messages.
228
+ - **variant** `'surface'` (the white card, default) or `'filled'` (the message on
229
+ the tone's own colour; amber keeps dark ink because white on `--amber-500` is
230
+ illegible).
231
+ - **`max`** caps the stack; the oldest toast leaves first.
232
+
233
+ One live region (`aria-live="polite"`) exists **before** any content arrives;
234
+ each toast has a dismiss button (unless `closeable: false`) and **pausing on
235
+ hover** (the timer is cleared on `mouseenter`, restarted at 1.5s on
236
+ `mouseleave`). Outside a provider `useToast` degrades to `console.info`, so the
237
+ library never crashes a host.
238
+
239
+ ---
240
+
241
+ ## 4. Display
242
+
243
+ | Component | Props that matter | Notes |
244
+ | ---------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
245
+ | `Icon` | `name`, `size`, `label?`, `style?` | 146 glyphs taken verbatim from the product's sprite. `size` sets `font-size` (the SVG is 1em). `label` → `role="img"`; otherwise `aria-hidden`. An unknown name warns and renders an empty box instead of throwing. |
246
+ | `Tag` | `variant: solid \| soft \| outline`, `color`, `icon`, `pill` | `StatusTag` maps 12 status words onto the measured palette; `LevelTag` renders the L0–L4 badge; `CountTag` the counted badge. |
247
+ | `Avatar` | `name`, `src`, `size: xs \| sm \| md \| lg`, `title` | Initials + a deterministic colour hashed from the name, so the same person is always the same colour. `AvatarStack` overlaps up to `max` and shows `+N`; `AvatarPlaceholder` is the dashed "not set" ring. |
248
+ | `Card` | `title?`, `extra?`, `flush?` | Section shell. `KeyValue` renders label/value rows; `Progress` takes 0–1 (clamped) with a tone and `aria-valuenow`; `Metric` is the label/value/delta/trend/icon tile. |
249
+ | `BarChart` / `Donut` / `Sparkline` | `data[]`, sizing props | No chart dependency: bars are flex heights, the donut is a `conic-gradient` with a punched hole and a legend, the sparkline is an SVG polyline. |
250
+ | `Crumbs` | `items: { id, label, onClick? }[]` | Marks the last item `aria-current="page"`. |
251
+ | `Divider` | `orientation` | Horizontal / vertical hairline on the divider token. |
252
+ | `States` | see below | `EmptyState` (`variant: empty \| error \| offline`, `title`, `hint`, `action`, `compact`) always renders **title + explanation + action**; `ErrorState` answers what/why/what-now and offers a retry; `EmptyArt` is the product's `</null>` illustration; `Skeleton`, `TableSkeleton`, `LoadingBlock`, `InlineSpinner`, `Banner`. |
253
+
254
+ ---
255
+
256
+ ## 5. Navigation
257
+
258
+ | Component | Props | Behaviour |
259
+ | -------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
260
+ | `Nav` | `items`, `value`, `onChange`, `overflowItems?` | The product tab strip. The 2px ink bar is **measured from the live DOM** (and re-measured with a `ResizeObserver`) so variable-width and CJK labels stay centred. **← →** move the selection and focus; extra destinations fold into a "More" menu. |
261
+ | `Tree` / `TreeGroup` | `nodes`, `value`, `onSelect`, `defaultExpanded` | Accessible tree outline with optional controlled state, checks, lazy loading, virtual scrolling, and reorder callbacks. |
262
+ | `Sidebar` | `title`, `operations?`, `footer?`, `defaultWidth = 260`, `minWidth`, `maxWidth`, `collapsed?` | The category column, **resizable** by dragging the gutter (pointer capture) or with ← → on the handle, which exposes `role="separator"` + `aria-value*`. |
263
+ | `Segment` / `Chip` | `options`/`pressed`, `onChange` | Compact view switchers and filter pills; both use `aria-pressed`. |
264
+ | `Pagination` | `page`, `pageSize`, `total`, `onPageChange`, `onPageSizeChange?`, `pageSizeOptions?`, `left?`, `children?` | A windowed page list with an ellipsis, a page-size menu, and a "Go to" field (digits only, commits on Enter, clamped). Prev/next disable at the bounds. |
265
+
266
+ ---
267
+
268
+ ## 6. `DataTable<T>` — the grid everything sits on
269
+
270
+ ```tsx
271
+ <DataTable
272
+ columns={columns} rows={rows} rowKey={rowKey}
273
+ status="ready" // loading | error | ready
274
+ sort={sort} onSortChange={setSort}
275
+ selectable selectedKeys onSelectedKeysChange
276
+ groupBy={(row) => row.status} // grouped <tbody> blocks, foldable
277
+ expandedGroups={open} onExpandedGroupsChange={setOpen} // or uncontrolled
278
+ showIndex rowActions onRowClick
279
+ emptyState={…} errorState={…} loadingRows={8} loadingColumns={[…]}
280
+ ariaLabel="All ideas"
281
+ />
282
+ ```
283
+
284
+ - **Five states are structural**: `loading` renders skeleton rows at the real row
285
+ height, `error`/`empty` render the supplied nodes in an overlay, `ready` with
286
+ rows renders the grid, and any column can carry edge content.
287
+ - Column definition: `{ id, header, width?, align?, sortable?, fixed?, render(row, index), className? }`.
288
+ `fixed: 'left' | 'right'` pins a column while the grid scrolls sideways; the
289
+ component measures the offsets from the numeric `width`s of the columns pinned
290
+ before it, and the outer column of each pinned run carries the edge shadow. A
291
+ non-numeric width still pins, it just cannot be offset. The checkbox and actions
292
+ columns join their edge **only when a data column is frozen** — an unfrozen grid
293
+ has no frozen edge, and pinning them anyway left a shadow and an opaque
294
+ background with nothing to mark.
295
+ - **The selection bar covers the header row.** While rows are selected the table
296
+ renders `SelectionBar` over the header (50px, full width, white), so the columns
297
+ never move: the box centres in the pick column, the count starts at the first
298
+ data column, and the covered header is `inert` — its select-all is not rendered
299
+ twice. Cancel clears the selection.
300
+ - A **selected row** wears `--surface-table-row-selected` — the head's neutral tone,
301
+ not the accent: the product's grid keeps the accent for the checkbox, so a
302
+ selected row still reads as a row. Everything else keeps `--surface-selected`.
303
+ - **Commands can live inside a cell**: `Column.actions` (`[{ id, icon, label,
304
+ onSelect }]`) renders boxes over the cell's right edge, revealed by the pointer
305
+ being on that cell, each with its tooltip. The row's own commands (`rowActions`)
306
+ are a different layer and sit at the row's **end** — the actions cell is
307
+ zero-width and must stay out of the positioning chain, or the overlay anchors to
308
+ the scrollport instead of the row.
309
+ - The pointer is **two levels**: the row under it takes the light wash (the pick
310
+ cell's gutter with it) and the cell under it the accent tint — the row reads as a
311
+ row, the cell as the target.
312
+ - The pick cell's **checkbox** appears when that cell is pointed at (or the row is
313
+ selected, holds the cursor, or a bulk selection is running) — never because the
314
+ pointer happens to be somewhere else in the row.
315
+ - The bar is exactly the header row's height (`--table-head-height`, shared with
316
+ `thead th`): it stands in for that row, so it must not be taller than it.
317
+ - The pick column is the grid's **gutter**: it wears the header's tone
318
+ (`--surface-table-head`) on every row, while a hovered or selected row keeps its
319
+ own colour there too.
320
+ - **The grid is drawn as a grid**: every column carries a hairline on its right, so
321
+ the columns are divided as well as the rows. The filler and the structural cells
322
+ (pick, row actions, header tools) draw no trailing edge — they are not columns.
323
+ - **The grid behaves like a spreadsheet.** `cursor` turns the keyboard cursor into
324
+ a **cell**: ← → walk the columns, ↑ ↓ the rows, Home/End jump to the ends of a
325
+ row, Enter opens the record, Space toggles its selection; focus follows the
326
+ cursor through a roving tabindex (one tab stop for the whole grid). Hovering
327
+ lights up the cell under the pointer, never the row.
328
+ - **Selection needs a writer.** The 44px pick column keeps its row number whenever
329
+ `selectable` is on, but the checkbox (and the select-all box) only renders when
330
+ `onSelectedKeysChange` is given — a control that cannot write is worse than no
331
+ control. A column that declares `sortable` is disabled with a title when the
332
+ host passed no `onSortChange`.
333
+ - The pick cell is the product's: the row number at rest, the checkbox on hover,
334
+ on a selected row, while a bulk selection is active, and on the **cursor row**
335
+ (the keyboard's hover — the number steps aside only when a checkbox exists).
336
+ - **The column menu** (`onColumnCommand`): each data column's header grows the
337
+ product's ⋯ (on hover or focus) with Sort by (a submenu: Ascending / Descending
338
+ / Clear sort, the active one checked when the column is sorted), Move left,
339
+ Move right (disabled at the ends), Insert left, Insert right, Freeze or Unfreeze
340
+ and Remove this column. The library reports a `ColumnCommand` and changes
341
+ nothing: the host rewrites its `columns` and hands the array back, exactly like
342
+ `sort` / `onSortChange`.
343
+ - **The bulk bar is built here**, not in a page. `bulk` supplies the data and the
344
+ write for the product's commands; the component owns the bar, the submenus, the
345
+ round-robin spread, the confirmation and the dialog:
346
+
347
+ ```tsx
348
+ bulk={{
349
+ statuses: { options, apply(keys, value) },
350
+ baselines: { options, apply(keys, id) }, // "Move in baseline"
351
+ schedules: { options, apply(keys, id) }, // "Schedule"
352
+ distribute: { people, apply(key, person) }, // round-robin, one write per row
353
+ properties: { fields, initialValues?, title?, apply(keys, values) },
354
+ }}
355
+ ```
356
+
357
+ The order is the product's — Update status, Move in baseline, Schedule,
358
+ Distribute workitems, Edit properties — then anything `bulkActions(keys)` adds,
359
+ then the workbench's More (export / delete) and Cancel. Only the capabilities the
360
+ host supplied appear. Items sharing a `group` fold into one submenu (named after
361
+ the group, icon from `groupIcon`); an item with no group stays a button of its
362
+ own, keeping its label, icon and handler.
363
+
364
+ - **Frozen columns belong to the view.** `ListView.frozen` is the list of column
365
+ ids pinned to the left edge (the product's "frozen columns"), kept next to
366
+ `sort` and `group` because it is part of the view: `ListWorkbench` applies
367
+ `fixed: 'left'` to those columns and answers the column menu's Freeze /
368
+ Unfreeze itself, while Move / Insert / Remove rewrite the host's `columns` array
369
+ and leave through `onColumnCommand`. The demo freezes `id`, which is how the
370
+ product's grid opens: the checkbox column and the ID scroll together and the
371
+ rest goes under them. Host-defined `column.fixed` remains host-owned: Unfreeze
372
+ forwards to `onColumnCommand`, and the host updates `columns` to remove it.
373
+ Without that callback the workbench locally unpins the column for this session.
374
+ Selected export requires every selected record to be present in `rows`; missing
375
+ records produce an error rather than an incomplete file.
376
+ - **The keyboard cursor is the cell.** `cursor` makes the body one tab stop whose
377
+ unit is the **cell** — ← → walk the columns, ↑ ↓ the rows, Home / End jump to
378
+ the ends of the row, Enter opens the record, Space toggles its row's selection
379
+ when the host can write one, a pointer click parks the cursor on the cell it
380
+ landed in, and focus rides with the cursor through a roving tabindex. The
381
+ cursor is clamped when the rows shrink under it, and it follows the columns too,
382
+ so removing one cannot leave it pointing at a cell that is gone.
383
+ `ListWorkbench` turns it on, which is what the product's grid does; without it
384
+ every row that has an `onRowClick` is its own tab stop.
385
+ - **A widget inside a cell keeps its own keys.** The row's key listener sits on
386
+ the `<tr>`, so everything pressed inside a cell bubbles to it; the grid answers
387
+ only when the key is not the widget's (`src/components/table/keyboard.ts`). A
388
+ rename field takes its space and keeps its caret, an in-cell editor walks its
389
+ own text, and a `Column.actions` box answers Enter itself instead of opening the
390
+ record. The rule is a list of roles — `input`, `button`, `a[href]`,
391
+ `[contenteditable]`, the combobox / listbox / textbox roles — so a host's own
392
+ editor inside a cell gets the same treatment without having to remember
393
+ `stopPropagation`.
394
+ - **The frozen geometry is shared.** The pinned offsets, the edge shadow and the
395
+ skeleton rows all read the same measured offsets
396
+ (`src/components/table/geometry.ts`), so a loading grid does not jump sideways
397
+ when the data lands, and the row cursor stays put.
398
+ - **Density** `'default'` (45px rows, the measured grid) or `'compact'` (36px
399
+ rows and header, `--text-sm` cells). Compact also reserves the actions column —
400
+ a 36px row has no room for the floating command overlay — which is what
401
+ `.pro-table__cell--actions` right-aligns.
402
+ - **Groups fold** from their own header (`aria-expanded`), uncontrolled and open
403
+ by default, or controlled through `expandedGroups` / `onExpandedGroupsChange`.
404
+ String/number results are wrapped in a truncating span automatically, so a
405
+ value like `SLC-T1` never wraps and the row keeps its measured 52px pitch.
406
+ - Sorting is three-state (`asc → desc → none`) and sets `aria-sort`; the header
407
+ is a real `<button>`.
408
+ - Row checkboxes are a **hover affordance** (hidden until hover/selection/focus);
409
+ the select-all header checkbox supports `indeterminate`.
410
+ - Rows are keyboard-activatable when `onRowClick` is given (`Enter`/`Space`) and
411
+ announce via a live region.
412
+
413
+ ---
414
+
415
+ ## 7. Ship surface
416
+
417
+ | Component | Props | Behaviour |
418
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
419
+ | `AppRail` | `items`, `footerItems?`, `activeId?` | The 60px product switcher; the active item gets the lighter band and `aria-current="page"`. |
420
+ | `AppHeader` / `ProductHeader` | `search`, `searchRef`, `actions`, `tabs`, `activeTab`, `onTabChange`, `overflowTabs`, `starred`, `onToggleStar`, `products?`, `onSelectProduct?`, `onCollapseSidebar?`, `sidebarCollapsed?` | Products level vs workbench level. `searchRef` is what `⌘K` focuses; `search.onSubmit` fires on Enter; the product name opens a switcher when `products` is given; the outdent control reports its pressed state. |
421
+ | `HeaderActions` | `onCreate?`, `onHelp?`, `onNotifications?`, `unread?`, `user` | The `+`, `?`, bell (with a dot badge) and avatar cluster; the avatar's click receives the event so the host can anchor a menu to it. |
422
+ | `BrandMark` | `size?` | The four-dot product mark, drawn inline so the brand colour stays under our control. |
423
+ | `ListHeader` | `title`, `titleIcon?`, `actions?` | The 48px strip above a grid. |
424
+ | `ListToolbar` | `search`, `count?`, `fields?`, `rules?`, `onRulesChange?`, `sortFields?`, `sort?`, `onSortChange?`, `groupFields?`, `group?`, `onGroupChange?`, `onToggleExpand?`, `expanded?`, `extraControls?` | Search, then Filter / Sort / Group / More, with the count pinned right and active filters rendered as removable chips. Opening the filter builder on a blank slate seeds one condition, as the product does. |
425
+ | `FilterPanel` / `FilterChip` | `fields`, `rules`, `onRulesChange` | Field / condition / value rows with add, clear-all and apply. |
426
+ | `DetailSheet` | `kind`, `code`, `title`, `tools?`, `properties?`, `tabs`, `activeTab`, `onTabChange`, `aside`, `composer?`, `onClose` | The entity panel that covers everything right of the rail: identity bar, title, property strip, tabs, main column, metadata rail. `DetailSection` is a collapsible rail group, `DetailField` a labelled row, `Feed` a comment/activity timeline, `FeedFilter` its chip bar, `CommentComposer` the composer with a mention affordance. |
427
+ | `TypeBadge` | `kind` (11 entity kinds) | The coloured square in front of every entity title. `TitleCell` = badge + truncating title with a completed strikethrough; `ProductCell` = module icon + name + lock; `StarButton`, `QuickStart`, `TableBanner`, `SelectionBar` fill out the chrome. |
428
+ | `BoardView` / `RoadmapView` / `DocSplit` | `columns` / `periods`+`lanes` / `tree`+children | Kanban columns, the quarter timeline grid, and the document tree + body split. |
429
+
430
+ ---
431
+
432
+ ## 8. Extracted composites (third pass)
433
+
434
+ These nine components were store-coupled inside the demo site and are now pure and
435
+ controlled, so any host can drive them. Their store wiring lives in the demo's
436
+ `src/app/adapters/*`.
437
+
438
+ | Component | Controlled props | Behaviour kept |
439
+ | --------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
440
+ | `CommentThread` | `comments`, `currentUser`, `onAdd`, `onUpdate`, `onRemove`, `onReact` | replies nest, inline edit with an "edited" marker, delete confirmation naming the comment, emoji reactions with `aria-pressed`, `@mention` highlighting |
441
+ | `AttachmentUpload` | `files`, `onUpload`, `onRemove`, `maxSizeMb` | drag-and-drop + picker, per-file progress that completes, oversize refused before reading, attached rows with preview and unlink |
442
+ | `LinkPicker` | `targets`, `selected`, `candidates`, `onLink`, `onUnlink` | dialog with a target segment, search, already-linked rows disabled, current links grouped with unlink |
443
+ | `FilePreview` | `file`, `open`, `onClose` | md/txt/csv document view, csv table, image frame, unsupported-type state, zoom, real download |
444
+ | `TransitionsTimeline` | `entries`, `statuses`, `currentStatus` | newest-first rail, flow strip, `old → new` chips, status-coloured dots, real empty state |
445
+ | `EntityDialog` | `fields`, `initialValues`, `onSubmit` | two-column sheet, blur-then-validate, reset on `open` only (typing never wiped), drag/scroll guard |
446
+ | `ShareDialog` | `value`, `onChange`, `onSave`, `onCopy` | visibility, copyable link, expiry menu, scheduled export with validation |
447
+ | `ReportCanvas` | `widgets`, `onChange`, `loading` | drag reorder, 4/6/12 width, keyboard ▲▼, remove confirmation, empty state, live-region announcements |
448
+ | `RoadmapEditor` | `lanes`, `periods`, `bars`, `onChange` | drag between quarters, ←/→ moves, inline rename, recolour, zoom, weekend toggle, bounded weekend maths |
449
+
450
+ ## 9. Extracted in the fourth pass
451
+
452
+ Lifted out of the demo site once its own copies had drifted into four variants of
453
+ the same thing. Everything below is pure: the store, the router and the demo's
454
+ seed data stay in the host.
455
+
456
+ | Component | Props that matter | Behaviour kept |
457
+ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
458
+ | `DetailBlock` | `title?`, `actions?`, `children`, `className` | The content block a detail sheet is written with — heading row plus optional right-aligned actions — so a page never reaches for `pro-detail-block__*` itself. |
459
+ | `DetailTool` | `icon?`, `label?`, `children`, `onClick` | The compact tool in the identity bar ("Attachments", "Links", "Export"). With only `label` it is icon-only and the label becomes its accessible name; tooltips stay with the host. |
460
+ | `EditableText` | `title`, `text`, `onSave`, `rows`, `label`, `editSeed`, `paragraphs`, `note`, `readOnly`, `editing`/`draft` (controlled or not) | Read view, editor and Cancel/Save row in one place: uncontrolled by default so two blocks on a sheet never share state, liftable when a host owns the state machine, and a blank draft re-commits the current text rather than blanking a record. |
461
+ | `EntityPicker<T>` | `rows`, `rowKey`, `value`, `onChange`, `onConfirm`, `linked`, `variant` (`list` \| `table`), `scopes`, `search`, `fields`/`rules`/`sortFields`, `hint`, `confirmLabel` | "Select ideas" in both shapes the product uses: the compact tick list (a plan picking what it scores) and the workbench table with a scope tree (a roadmap picking from the whole product). `linked` is what turns ticks into `N to link · M to unlink`, and the dialog is only dismissable when nothing would be written. |
462
+ | `ScatterPlot` | `points: { id, label, x, y, name?, render? }[]`, `xLabel`, `yLabel`, `title`, `extra`, `onSelect?`, `emptyState`, `height` | The Plan matrix: percentage-positioned points with computed ticks, so a point can never fall outside the frame. No chart dependency; points are `role="img"` with the value in their accessible name unless `onSelect` makes them buttons. |
463
+ | `ProjectWizard` | `types`, `members`, `owner`, `existingKeys?`, `belongsToOptions?`, `categoryOptions?`, `onSubmit(draft)`, `onClose` | The three-step `New project` modal: the type preview pane, the type cards with arrow-key roving focus, blur-then-validate details, the member step with its own roster dialog, and the discard guard. |
464
+
465
+ | `AccountMenu` | `user: { name, email? }`, `items`, `label?` | The account popover body: identity row, then the menu. The host keeps the anchor and the popover. |
466
+ | `ShortcutsDialog` | `open`, `onClose`, `shortcuts: { keys, label }[]`, `title?` | The keyboard help sheet — `<kbd>` on the left, the action on the right. |
467
+ | `QuickStartDialog` | `open`, `onClose`, `steps`, `checked`, `onToggle`, `onGo?`, `onReset?`, `intro?` | The getting-started checklist: progress bar, one tickable row per step, and a "go" action for the steps that lead somewhere. |
468
+
469
+ Alongside them: `VoteCount`, `QuickStart` grew `done` / `total` for the checklist
470
+ counter, `Tag` grew `tone="accent"` and `size="sm"` for the quarter chip, and
471
+ `primaryComponents` / `componentLine` moved in with the wizard.
472
+
473
+ ## 10. Hooks and helpers
474
+
475
+ | Export | Use |
476
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
477
+ | `useToast()` | Push a toast from anywhere under `ToastProvider`. |
478
+ | `useControllableState({ value, defaultValue, onChange })` | The pattern behind every controlled/uncontrolled component. |
479
+ | `usePopoverClose()` | Lets a menu close the popover that hosts it. |
480
+ | `useAnchorRef()` · `useSelectAnchor()` | Keep a trigger element in state so an anchored layer can measure it. |
481
+ | `formatListTime` | `Today 15:44` / `Yesterday 09:12` / `Tuesday 09:12` / `Mar 3` — the product's relative list timestamps. |
482
+ | `formatDateTime` · `formatDay` · `formatNumber` · `formatCount` · `formatCompact` · `pluralize` | All through `Intl`; nothing is hand-formatted. |
483
+ | `cx` · `statusColor` · `avatarColor` · `initials` · `nextRuleId` | Class joining, status→token mapping, deterministic avatar colours, initials, filter-rule ids. |
484
+
485
+ ## 11. Inline editors, the assistant and the newer surfaces
486
+
487
+ The components added after the fourth pass, in the order a host meets them.
488
+
489
+ | Component | Props that matter | Behaviour |
490
+ | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
491
+ | `PeoplePicker` | `tabs`, `value`, `onPick`, `searchPlaceholder`, `emptyHint`, `onClose` | The inline assignee editor the grid opens on a cell: the person already assigned starts the cursor (or `Unassigned`), type to filter, ↑↓ walk the rows, Enter picks. Returns `null` for the `Unassigned` row. |
492
+ | `QuickSelect` | `options`, `value`, `onPick`, `placeholder`, `allowClear`, `renderOption`, `onClose` | The enum cell editor: the field is the control, the list is portalled, typeahead jumps to an option, Escape closes, and `allowClear` adds the row that empties the field. |
493
+ | `QuickText` | `value`, `onCommit`, `display`, `trigger`, `maxLength`, `onClose` | The inline rename editor. `trigger` is `click` or `dblclick`; `display` is normally the very node the column already draws, so opening the editor changes nothing but the input. An unchanged value never commits. |
494
+ | `useListCursor` / `useSelectAnchor` | `count` / — | The two shared pieces behind those editors: the cursor arithmetic that keeps ↑↓ inside a shrinking list, and the anchor ref a portalled list measures against. |
495
+ | `ContextMenuList` / `useContextMenu` | `items`, `quick`, `aria-label` / `items` | The menu the right-click layer renders, and its imperative form (`openAt(x, y, context)`) for a host that owns the gesture itself. |
496
+ | `IconAvatar` | `icon`, `label?`, `tone`, `size` | The avatar shell carrying a glyph instead of initials — a row whose leading mark identifies a kind of record. Decorative unless `label` is given. |
497
+ | `RichTextToolbar` | — | The inert rich-text strip that sits above a description textarea (the editor itself is out of scope). |
498
+ | `AiPanel` / `PingMark` | `open`, `messages`, `onSubmit`, `pending`, `suggestions`, `quickChips`, `onClose` | The assistant side panel: a `role="dialog"` whose transcript is one `role="log"`, a composer that submits on Enter, suggestion cards and quick chips that fill the composer. It never invents a reply — the host owns the transcript and the thinking state. |
499
+ | `SidebarCollapseProvider` / `useSidebarCollapsed` | `collapsed` | Collapsing a sidebar is a shell decision, so a shell wraps its page tree once and every `Sidebar` inside follows; a `collapsed` prop on one sidebar still wins. |
500
+ | `ListWorkbench<T>` | `rows`, `columns`, `view` + `onViewChange`, `selection`, `title`/`noun`, `status`, `searchText`, `matches`, `compare`, `views`, `boardView`, `enumFields`/`textFields`/`assigneeField`, `bulkActions`, `onDelete` | The whole list screen in one component: header, toolbar (search / filter / sort / group / view switch), the five states, inline cell editors, selection with its bulk bar, pagination and the row commands. Everything it cannot decide alone — how to search, filter, compare, open, export — arrives as a callback. |
501
+ | `DEFAULT_SHARING` | — | The value a report's sharing starts from (`ReportSharing`): visibility, link, expiry, schedule, hour, recipients, format. |
502
+
503
+ ## Additional common components
504
+
505
+ ### `Radio` / `RadioGroup`
506
+
507
+ `Radio` forwards its input ref and native input attributes, with a `label` slot.
508
+ `RadioGroup` takes string-valued `options`, `value` / `defaultValue`, `onChange`,
509
+ `name`, `disabled`, `required`, and `orientation`. An `aria-label` is required.
510
+ Same-name native radios provide arrow-key selection, Space activation and form submission.
511
+ Each group gets a unique name when omitted.
512
+
513
+ ### `Tabs`
514
+
515
+ `items: { key, label, children, disabled? }[]`, `activeKey` / `defaultActiveKey`,
516
+ `onChange`, `keepMounted`, and a required `aria-label`. Left/Right wrap past disabled
517
+ tabs; Home/End select the first/last enabled tab. One tab is in the Tab sequence.
518
+ Linked tab panels retain their content when `keepMounted` is true; otherwise inactive
519
+ content unmounts. A removed or disabled active key displays the first enabled tab.
520
+
521
+ ### `Collapse`
522
+
523
+ `items: { key, label, children, disabled? }[]`, `activeKeys` / `defaultActiveKeys`,
524
+ `onChange`, and `accordion`. Native buttons respond to Enter/Space and expose
525
+ `aria-expanded` / `aria-controls`. Accordion opens at most one panel. Hidden content
526
+ remains mounted to preserve input state.
527
+
528
+ ### `Badge`
529
+
530
+ `count`, `overflowCount` (99), `showZero`, `dot`, `label`, and optional `children`.
531
+ Counts above the limit display `99+` while retaining the full accessible count.
532
+ Zero is hidden by default; negative and non-finite counts are hidden. Children
533
+ receive an attached badge. Supply a descriptive `label` for dots.
534
+
535
+ Reference: [Ant Design component library](https://github.com/ant-design/ant-design).
536
+ These are independent token-based implementations, not drop-in antd replacements.
537
+
538
+ ### `Slider`
539
+
540
+ Native single-thumb `input[type="range"]`, with forwarded ref and input attributes.
541
+ `min` (0), `max` (100), `step` (1), native `value` / `defaultValue`, `disabled`,
542
+ `name`, and `onChange(event)` work like `Input`. Supply a visible `Field` label or
543
+ `aria-label`. Arrow keys, Home/End, rounding, bounds and form submission are native
544
+ browser behaviour. No custom multi-thumb range or tooltip is provided.
545
+
546
+ ### `Steps`
547
+
548
+ `items: { key, title, description?, disabled? }[]`, zero-based `current`,
549
+ `status: 'process' | 'error'`, `orientation`, optional `onChange(index)`, and a required
550
+ `aria-label`. An ordered list exposes the current step with `aria-current="step"`
551
+ and names each step's status. Passing `current={items.length}` marks all complete;
552
+ out-of-range indices are clamped. With `onChange`, native buttons allow Enter/Space
553
+ activation and respect disabled steps; the host controls progression and validation.
554
+
555
+ ### `Alert`
556
+
557
+ `message`, `description`, `type: 'info' | 'success' | 'warning' | 'error'`, `showIcon`,
558
+ `action`, `closable`, `closeLabel`, `open` / `defaultOpen`, `onOpenChange`, `onClose`.
559
+ Persistent inline feedback with `role="alert"` for errors and `role="status"` otherwise.
560
+ Closing uses a labelled native button, supports Enter/Space, and reports visibility
561
+ changes. For temporary notifications use `useToast`; for table strips use `Banner`.
562
+
563
+ References: [Slider](https://ant.design/components/slider/),
564
+ [Steps](https://ant.design/components/steps/), [Alert](https://ant.design/components/alert/).
565
+
566
+ ### `CheckboxGroup`
567
+
568
+ `options: { value, label, disabled? }[]`, string-array `value` / `defaultValue`,
569
+ `onChange`, `name`, `disabled`, `orientation`, and required `aria-label`.
570
+ Reuses `Checkbox`; Tab reaches enabled choices and Space toggles them.
571
+ Native same-name checkbox inputs submit multiple form values. Selection is
572
+ preserved when options change; the host owns removal of obsolete selections.
573
+
574
+ ### `PasswordInput`
575
+
576
+ Reuses `Input` and forwards its ref, value, native input attributes, `size`, and
577
+ `invalid`. Visibility uses `visible` / `defaultVisible` (false), `onVisibleChange`,
578
+ `showLabel` / `hideLabel`. A labelled toggle supports Enter/Space and exposes
579
+ `aria-pressed`; pointer toggles keep focus in the field. `disabled` disables both
580
+ controls. Works with `Field`; supply the appropriate password `autoComplete` value.
581
+
582
+ ### `Rate`
583
+
584
+ Whole-star rating from 0 to 5. `value` / `defaultValue`, `onChange(number)`,
585
+ `disabled`, `readOnly`, `allowClear` (true), `clearLabel`, `name`, and required
586
+ `aria-label`. Reuses native `RadioGroup` for arrow navigation and Space selection.
587
+ The clear button resets to zero. Read-only mode exposes a labelled image without
588
+ interactive controls. Non-finite values display zero; others round and clamp.
589
+ Half-star input and hover previews are not implemented.
590
+
591
+ References: [Checkbox](https://ant.design/components/checkbox/),
592
+ [Input](https://ant.design/components/input/), [Rate](https://ant.design/components/rate/).
593
+
594
+ ### `Cascader`
595
+
596
+ Hierarchical leaf selection with `options: { value, label, disabled?, children? }[]`,
597
+ string-array `value` / `defaultValue`, `onChange`, `searchable`, `allowClear`,
598
+ `disabled`, `invalid`, and required `aria-label`. Values are the complete path.
599
+ Reuses `Popover` and `Menu`: Up/Down, Home/End and typeahead walk each level; Right
600
+ opens a child menu, Left returns, Enter chooses and Escape closes. Search matches
601
+ full leaf paths; disabled ancestors also disable descendants. Closing returns focus
602
+ to the trigger. Rich labels use their text for full-path search.
603
+
604
+ `multiple` changes value/defaultValue/onChange to arrays of complete string paths.
605
+ Parent/child checks share Tree's conduction engine; `showCheckedStrategy` is
606
+ `'SHOW_PARENT'` by default or `'SHOW_CHILD'` for leaf paths. Disabled ancestors
607
+ block descendants; `disableCheckbox` blocks that node's check conduction while
608
+ children remain independently checkable. Search toggles retain checks outside
609
+ results. Unknown selected paths survive changes; selected labels survive remote
610
+ options replacement, including leaf values displayed through parent compaction.
611
+ Multi clear preserves disabled/checkbox-disabled paths.
612
+
613
+ `changeOnSelect` allows intermediate single paths: pointer selection keeps the
614
+ hierarchy open, while keyboard selection or a leaf requests closing.
615
+ `loadData(selectedOptions)` resolves child options or lets the host update options
616
+ and resolve void. Set `isLeaf={false}` on unloaded branches. Loads deduplicate,
617
+ show loading, offer Retry on failure, and reject stale source/loader replacements
618
+ or late unmounted results. Returned children are validated against the full
619
+ hierarchy before display. Keep source objects immutable and loader callbacks stable;
620
+ changing their identity invalidates pending requests and loaded metadata.
621
+
622
+ `fieldNames` maps value/label/children; string values are required and unique per
623
+ complete path. Cyclic input, malformed children and duplicate paths are rejected.
624
+ `displayRender(labels, selectedOptions)` and `optionRender(option)` customize
625
+ presentation. `filterOption(query, selectedOptions)`, searchValue/onSearch and
626
+ notFoundContent support host search. `open`/`defaultOpen`/`onOpenChange` keep popup
627
+ ownership explicit; a refused close does not erase its query. loading presents
628
+ host-owned async state; requests remain the host's responsibility. Native button
629
+ attributes and ref support Form and focus.
630
+
631
+ Klun keeps the original single Menu/submenu UI and string paths. Multiple,
632
+ intermediate and lazy hierarchy use existing Tree semantics rather than copying
633
+ AntD's column UI or private combobox handle. It does not alias numeric/null values,
634
+ hover expansion, Panel, semantic styles or every AntD prop. Menu closeOnSelect can
635
+ be disabled when a composite control owns closing, keeping single radio semantics.
636
+
637
+ ### `Transfer`
638
+
639
+ `items: { key, label, disabled? }[]`, `targetKeys` / `defaultTargetKeys`, `onChange`,
640
+ `titles`, `searchable`, `disabled`, `status`, `emptyState` / `errorState`, and required
641
+ `aria-label`. Two checkbox lists support Tab/Space and labelled move buttons. Search
642
+ and select-all affect visible enabled rows; moves preserve unknown and disabled
643
+ target keys. A live region reports selected counts. Compact viewports stack the lists.
644
+
645
+ `selectedKeys` / `defaultSelectedKeys` control the checked rows independently of
646
+ `targetKeys`; `onSelectChange(leftKeys, rightKeys)` reports both partitions.
647
+ `onChange(targetKeys, direction, moveKeys)` reports only known enabled rows that
648
+ actually move, with `right` meaning add to targets and `left` meaning remove.
649
+ Existing single-argument callbacks remain assignable. A controlled host can refuse
650
+ both target and check changes; each displayed state stays supplied by the host.
651
+
652
+ `pagination` enables independent pages per side, default size 10;
653
+ `pagination={{ pageSize, showSizeChanger?, pageSizeOptions? }}` sets a controlled
654
+ size when supplied. Search resets that side to page 1 and reduced totals clamp the
655
+ visible page. Checks survive page/search changes; select-all touches only enabled
656
+ rows on the displayed page. `filterOption(query, item, direction)` and
657
+ `onSearch(direction, query)` support host filtering. Rich labels use their text by
658
+ default, or `searchText` for opaque content; `render(item)` changes row presentation.
659
+
660
+ A render-function child receives `direction`, `disabled`, `dataSource`,
661
+ `filteredItems`, `selectedKeys`, `onItemSelect` and `onItemSelectAll`; custom lists
662
+ own paging, as in AntD. Selection callbacks reject disabled, unknown and wrong-side
663
+ keys, including saved callbacks after the component becomes disabled/loading.
664
+ Bulk selection accepts true/false or `'replace'`; replace preserves disabled checks
665
+ and checks on the opposite side. `showSelectAll={false}` hides the checkbox header;
666
+ `footer(listProps)` adds per-side content. Native div attributes and refs are forwarded.
667
+
668
+ Klun keeps `items`, string keys, textual titles and ready/loading/error status;
669
+ it does not alias AntD's `dataSource`, numeric keys, oneWay or semantic style APIs.
670
+
671
+ ### `DatePicker` / `DateRangePicker`
672
+
673
+ ISO calendar dates (`YYYY-MM-DD`) avoid implicit time-zone conversions. `DatePicker`
674
+ forwards an input ref, `name`, `id`, and `required`; accepts `value` / `defaultValue`,
675
+ `onChange(string)`, `min`, `max`, `disabledDate`, `locale`, `disabled`, `invalid`, and
676
+ required `aria-label`. Native date entry and a themed calendar share the same bounds.
677
+ Custom disabled dates also fail native form validity when provided by the host.
678
+
679
+ The calendar offers month/year selection, previous/next month, Today and Clear.
680
+ Left/Right move a day, Up/Down a week, Home/End within the week and PageUp/PageDown
681
+ a month (clamping the day for shorter months). Enter/Space chooses; unavailable
682
+ days can be inspected with keyboard focus but cannot be selected. Escape closes
683
+ and restores trigger focus. Locale controls calendar names; the browser localizes
684
+ native input display. Invalid host dates are marked invalid and never normalized
685
+ into a different date. Calendar values use years 0001 through 9999.
686
+
687
+ `DateRangePicker` accepts a `[start, end]` tuple, optional `names`, and shared date
688
+ constraints. Each endpoint bounds the other; partial ranges are allowed, reversed
689
+ ranges fail native validation. Time and multi-date selection are separate needs
690
+ and are not included in this date-only contract.
691
+
692
+ ### MultiSelect corrections
693
+
694
+ The trigger opens a labelled dialog containing a checkbox group. Choices expose
695
+ actual checkbox semantics; disabled items cannot be toggled or cleared. Clear
696
+ removes enabled selections present in this options page and preserves locked or
697
+ unknown keys. The panel closes when the whole control becomes disabled.
698
+
699
+ References: [Cascader](https://ant.design/components/cascader/),
700
+ [Transfer](https://ant.design/components/transfer/),
701
+ [DatePicker](https://ant.design/components/date-picker/).
702
+
703
+ ### DataTable: paging, filtering and row details
704
+
705
+ `pagination` reuses `Pagination` and stays controlled. By default the table derives
706
+ `total` from supplied rows and slices locally. Set `mode: 'remote'` for server-paged
707
+ rows; the host then supplies the total and fetches on `onPageChange`. Invalid or
708
+ out-of-range page values are clamped for display. Row numbers and render indexes
709
+ are relative to the current page.
710
+
711
+ A column `sorter(a, b)` enables local sorting without mutating `rows`. Columns with
712
+ only `sortable` retain the existing host-managed sorting contract. `defaultSort`
713
+ and `defaultFilters` initialize uncontrolled state; `sort` and `filters` make it
714
+ controlled. A column's `filters` supplies named choices, and `onFilter(values, row)`
715
+ implements its local predicate. Without a predicate the host handles the filter
716
+ callback. The pipeline is filter → sort → page, and filter changes request page 1.
717
+ For remote data omit local predicates/comparators and perform them on the server.
718
+
719
+ `isRowSelectable` disables selection per row. Select-all touches only eligible
720
+ rows on the current page, preserving locked and off-page keys. Selection remains
721
+ host-managed through `selectedKeys`/`onSelectedKeysChange`.
722
+
723
+ `expandedRowRender` supplies detail content, including a nested table when needed.
724
+ `rowExpandable` excludes individual rows. Expansion is uncontrolled by default;
725
+ use `defaultExpandedRowKeys`, or control it with `expandedRowKeys` and
726
+ `onExpandedRowKeysChange`.
727
+
728
+ Columns support `hidden`, `ellipsis: false` for wrapping (disables fixed-height virtual scrolling), and `onCell` for native
729
+ cell attributes. Use `rowSpan`/`colSpan` and a zero span on covered cells. Keep
730
+ `onCell` pure: layout and cursor navigation both read it. Cursor navigation skips
731
+ omitted cells and follows visible grouped rows in their rendered order.
732
+
733
+ `title`, `footer`, `rowClassName`, `summary` (native rows inside `tfoot`),
734
+ `showHeader`, `stickyHeader` and `scroll: { x, y }` cover layout customization.
735
+ The default empty and error states provide feedback even without custom content.
736
+ Interactive controls within cells keep their own click and keyboard behavior.
737
+
738
+ ### Tree and DirectoryTree — full hierarchy interactions
739
+
740
+ Both accept existing `nodes: { id, label, children }[]` or `treeData:
741
+ { key, title, children }[]`. `fieldNames` maps custom data fields. Keys must be
742
+ unique across the entire tree; duplicate keys and cycles are rejected. Numeric
743
+ treeData keys normalize to strings, including keys used by state and `scrollTo`.
744
+
745
+ | Capability | Props and behavior |
746
+ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
747
+ | Expansion | `expandedKeys` / `defaultExpandedKeys` (`defaultExpanded` remains compatible), `defaultExpandAll`, `defaultExpandParent`, `autoExpandParent`, `onExpand` |
748
+ | Selection | `selectedKeys` / `defaultSelectedKeys`, `multiple`, `selectable`, `onSelectionChange(keys, info)`; legacy `value` / `onSelect(node)` remain unchanged |
749
+ | Checks | `checkable`, `checkedKeys` / `defaultCheckedKeys`, `onCheck`; hierarchical checks report full checked keys plus half-checked keys in info |
750
+ | Strict checks | `checkStrictly` disables parent/child conduction and accepts/reports `{ checked, halfChecked }` |
751
+ | Node boundaries | `disabled`, `disableCheckbox`, `checkable: false`, `selectable: false`; disabled check nodes stop conduction without disabling descendants independently |
752
+ | Async branches | `loadData(node)` resolves children or updates host data and resolves void; `isLeaf`, `loadedKeys`, `defaultLoadedKeys`, `onLoad`, `onLoadError`; requests deduplicate, retry after failure and reject stale replacements |
753
+ | Search | `searchValue` highlights labels and reveals ancestors; `filterTreeNode` highlights predicate matches without removing nodes; `titleRender` owns custom content |
754
+ | Appearance | `showLine` (including custom leaf icons), `showIcon`, `icon`, `switcherIcon`, `switcherLoadingIcon`, `blockNode`, `indent`, CSS `motion`, semantic `classNames` / `styles`, node `className` / `style` |
755
+ | Dragging | `draggable` boolean, predicate or icon/config object; `movable`, `allowDrop`, `onMove`, all drag lifecycle callbacks and `onDrop`; cycles and disabled targets are refused; hovering a closed branch opens it |
756
+ | Virtual rows | `height`, `itemHeight`, `virtual`; fixed-height windowing retains an active descendant for accessibility; set `virtual={false}` for the complete DOM |
757
+ | Imperative control | `ref: TreeRef` exposes `nativeElement`, `focus()` and `scrollTo({ key, align, offset })`; nested keys reveal their ancestors, controlled expansion must be accepted by the host |
758
+ | Directory behavior | `DirectoryTree` defaults to click expansion and folder/file icons; `expandAction` also supports `doubleClick` and `false`; Ctrl/Meta toggles and Shift ranges merge the last ordinary/Ctrl selection; directory selection stays independent of expandAction |
759
+ | Commands | `nodeActions`, `nodeContextMenu`, `onRightClick`, `onDoubleClick`; visible command equivalents must accompany right-click menus |
760
+
761
+ Keyboard: Up/Down walk visible nodes; Right opens or enters a branch; Left closes
762
+ or goes to its parent; Home/End jump; typing searches visible labels; `*` opens
763
+ sibling branches; Enter selects; Space checks (or selects without checks).
764
+ RTL mirrors Left/Right navigation and Alt nesting/lifting. IME keys do not
765
+ change tree state. Ctrl/Meta+Space supports additive directory selection, Shift+navigation extends
766
+ ranges, and Alt+arrows reorder/nest/lift through the same move validation used
767
+ by pointer dragging. Embedded controls retain their own keys. Disabled nodes
768
+ remain discoverable by navigation while refusing changes.
769
+
770
+ Keep host data immutable and retain node identities while an async request is
771
+ in flight. A newly replaced node invalidates the old request/cache. When using
772
+ controlled `loadedKeys`, update them from `onLoad` and clear replaced nodes yourself.
773
+ Use a node’s `ariaLabel` when its rich title needs an explicit accessible name. Newly loaded children inherit
774
+ checked ancestor state in hierarchical mode. Default values are initialization
775
+ values; changing them later does not overwrite user state.
776
+
777
+ Virtualization uses a fixed `itemHeight` and truncates labels to one line, as
778
+ Ant Design's virtual Tree does. CSS transitions respect reduced motion; `motion`
779
+ is a boolean here, not Ant Design's private rc-motion configuration. This is
780
+ capability alignment with existing Klun APIs, not a drop-in `antd` import.
781
+
782
+ DirectoryTree caches the selection from the last plain/Ctrl/Meta pick, merging
783
+ Shift ranges with that base; a later Shift pick shrinks only its previous range.
784
+ Ctrl/Meta wins when held together with Shift. A plain directory pick replaces the
785
+ selection even with `expandAction={false}`, and Directory selection info reports
786
+ `selected: true`, including Ctrl deselection, matching AntD DirectoryTree.
787
+ Regular Tree keeps its existing additive multiple selection independently of
788
+ click expansion. Boolean `showLine={{ showLeafIcon: true }}` renders the default
789
+ file icon; false keeps only the connector, and functions receive node state.
790
+
791
+ Native dragging resolves upper-half gaps through the previous visible node,
792
+ inside expanded branches and horizontal nesting/lifting of last descendants,
793
+ mirrored in RTL. `allowDrop.dropPosition` remains relative (-1/0/1).
794
+ **Migration:** `onDrop.dropPosition` now matches AntD's target sibling index plus
795
+ relative direction; use `onDrop.relativeDropPosition` for the previous relative
796
+ meaning. `onMove` keeps before/after/inside. Unknown targets, cycles, disabled
797
+ boundaries, no-op moves and explicit leaves reject invalid drops. The host owns
798
+ reordering. Numeric treeData keys (including 0) intentionally normalize to string
799
+ state/callback/ref keys; 1 and '1' collide and are rejected. Nonfinite numeric
800
+ keys are invalid. This avoids changing existing TreeSelect/route string APIs.
801
+
802
+ ## Global configuration and Form foundation
803
+
804
+ `ConfigProvider` inherits `locale`, `direction` and `disabled` from its parent.
805
+ Omitted options inherit; explicit `disabled={false}` overrides the provider.
806
+ `useProConfig()` reads configuration and `useComponentDisabled(disabled)` resolves
807
+ the disabled fallback for custom controls. Core input controls, Button,
808
+ ActionButton, Select/MultiSelect, radio/checkbox groups, DatePicker, Cascader,
809
+ Transfer, Rate and Tree consume it. Portalled Dialog, Popover, Tooltip and Toast
810
+ retain provider language and direction. This provides native text direction;
811
+ full mirroring of every physical CSS offset and Ship business control is not
812
+ implemented. Locale currently supplies Form messages and DatePicker date labels;
813
+ other existing English copy is unchanged.
814
+
815
+ `Form` / `FormItem` use `NamePath`: a string or number is a literal key; an array
816
+ of string/number segments describes a nested object/array path. `user.name`
817
+ remains a literal key; `['user', 'name']` accesses the nested value. Numeric array
818
+ indexes are nonnegative integers below 100,000; string numeric keys remain object
819
+ keys. Records/arrays are copied at store boundaries; reserved prototype keys,
820
+ cycles and structures deeper than 100 levels are rejected before writing.
821
+ Other object types must remain immutable.
822
+
823
+ `initialValues` initializes the instance and is the reset baseline; changing
824
+ that prop does not overwrite edits. Unmounted fields preserve values but clear
825
+ errors and pending validation. `setFieldsValue` merges records recursively and
826
+ replaces arrays; `setFieldValue` changes one exact path. Partial updates retain
827
+ unmodified sibling metadata. Reset can target a subtree and remounts affected
828
+ controls/lists, including multiple resets in one React batch. Dependencies and
829
+ externally controlled field metadata are not implemented yet.
830
+
831
+ ```tsx
832
+ const [form] = useForm();
833
+
834
+ <Form form={form} initialValues={{ name: '', subscribed: true }} onFinish={save}>
835
+ <FormItem name="name" label="Name" required rules={[{ min: 2, message: 'Too short' }]}>
836
+ <Input />
837
+ </FormItem>
838
+ <FormItem name="subscribed" valuePropName="checked">
839
+ <Checkbox label="Receive updates" />
840
+ </FormItem>
841
+ <Button type="submit">Save</Button>
842
+ </Form>;
843
+ ```
844
+
845
+ Rules support required, whitespace, min/max (numeric value or string/array
846
+ length), regular expressions and synchronous/asynchronous `validator(value,
847
+ values)`. Validators return a message or throw/reject an Error to fail. Omitted
848
+ messages use provider locale. `validateTrigger` defaults to `onBlur`; use
849
+ `onChange` when desired. `onFinish` runs only for valid, current values; repeated
850
+ submissions are suppressed while pending. `onFinishFailed` receives validation
851
+ failures; `onSubmitError` receives rejected submissions, which also show a retry
852
+ message without clearing values. Stale edits, reset, superseded validation and
853
+ unmount prevent old results from becoming current errors.
854
+
855
+ `useForm()` returns `[form]`. The instance exposes `getFieldValue`,
856
+ `getFieldsValue`, `setFieldValue`, `setFieldsValue`, `getFieldError`,
857
+ `isFieldTouched`, `isFieldValidating`, `resetFields`, `validateFields`,
858
+ `scrollToField` and `submit`. Programmatic setters do not emit `onValuesChange`.
859
+ `useFormInstance()` reads the enclosing instance; `useWatch(name, form?)`
860
+ subscribes to one field. Do not attach one instance to multiple Forms at once.
861
+
862
+ FormItem binds one child control and preserves its change/blur handlers. Native
863
+ inputs use `value`; Checkbox/Switch use `valuePropName="checked"`. For array
864
+ controls provide `emptyValue={[]}`; MultiSelect additionally needs
865
+ `valuePropName="values"`, while DateRangePicker needs `emptyValue={['', '']}`.
866
+ `getValueFromEvent` adapts custom callback arguments. Custom controls must forward
867
+ the supplied id, aria attributes and blur callback to their actual control;
868
+ groups should supply their own `aria-label`. Errors use a live alert and are
869
+ linked to supporting controls with `aria-describedby`.
870
+
871
+ ### Dynamic Form lists
872
+
873
+ `FormList` and `Form.List` are the same component; `Form.Item` aliases `FormItem`,
874
+ and `Form.useForm`, `Form.useFormInstance`, `Form.useWatch` alias the named hooks.
875
+ List callbacks receive `{ name: index, key: stableIdentity }[]`, operations
876
+ `add(value?, index?)`, `remove(index | indexes)` and `move(from, to)`, plus
877
+ `{ errors }`. Child field/list names are relative to their enclosing list.
878
+ Imperative instance methods and `useWatch` always use absolute paths.
879
+
880
+ ```tsx
881
+ <Form initialValues={{ people: [{ name: 'Ada' }] }} onFinish={save}>
882
+ <Form.List name="people" rules={[{ required: true, message: 'Add a person' }]}>
883
+ {(fields, operations, meta) => (
884
+ <>
885
+ {fields.map((field) => (
886
+ <div key={field.key}>
887
+ <Form.Item name={[field.name, 'name']} label="Name" required>
888
+ <Input />
889
+ </Form.Item>
890
+ <Button onClick={() => operations.remove(field.name)}>Remove</Button>
891
+ </div>
892
+ ))}
893
+ <Button onClick={() => operations.add({ name: '' })}>Add person</Button>
894
+ {meta.errors.map((error) => (
895
+ <p key={error} role="alert">
896
+ {error}
897
+ </p>
898
+ ))}
899
+ </>
900
+ )}
901
+ </Form.List>
902
+ <Button type="submit">Save</Button>
903
+ </Form>
904
+ ```
905
+
906
+ Always use `field.key` as React key and `field.name` in field paths. Moves and
907
+ insertions preserve the existing rows' identities. Structural list changes
908
+ clear descendant validation/touched metadata and invalidate pending validators
909
+ so errors cannot attach to the wrong row. List rules validate after operations;
910
+ child rules validate through blur/change/submit. `meta.errors` is rendered by the
911
+ host. Provider/Form disabled state prevents list operations. Arrays initialize
912
+ from Form `initialValues`; list-level `initialValue` is not supported.
913
+
914
+ `validateFields(names?)` validates each selected path and its registered
915
+ descendants; pass `{ recursive: false }` as its second argument for exact fields.
916
+ Validation errors preserve string names for legacy fields and array paths for
917
+ nested/list fields. Programmatic array replacements preserve keys by index;
918
+ reset remounts rows. Dynamic lists support nesting and multiple operations in
919
+ one event. There is no automatic dependency-validation engine in this batch.
920
+
921
+ ### Form dependencies
922
+
923
+ `FormItem.dependencies` takes absolute form `NamePath[]`, including the complete
924
+ list prefix and index (e.g. `dependencies={[["users", 0, "password"]]}`). A user
925
+ change revalidates dependent fields that have been touched or previously
926
+ validated; pristine fields do not immediately display errors. Transitive
927
+ relationships are traversed once per field, including cyclic dependencies.
928
+ Validators receive a snapshot of all current values, and superseded async
929
+ results cannot overwrite newer validation results.
930
+
931
+ As in the reference AntD contract, programmatic `setFieldValue`/`setFieldsValue`
932
+ writes do not trigger dependency validation. Use `validateFields` explicitly
933
+ when required, and `useWatch` for conditional content. `shouldUpdate` and
934
+ render-function FormItem children remain unsupported.
935
+
936
+ ### P1 pagination and display contracts
937
+
938
+ Pagination accepts controlled `page`/`pageSize` or `defaultPage` (1) /
939
+ `defaultPageSize` (20). Existing `onPageChange`/`onPageSizeChange` callbacks remain;
940
+ `onChange(page, pageSize)` combines them. Size changes reset to page 1.
941
+ `showSizeChanger`, `showQuickJumper` (true), `hideOnSinglePage`, `showTotal(total,
942
+ range)` and provider-aware `disabled` configure the footer. Displayed page/range
943
+ are clamped to valid finite totals and sizes. DataTable pagination retains its
944
+ required controlled page/pageSize/onPageChange contract.
945
+
946
+ Segment adds defaultValue, per-option/provider disabled state and vertical
947
+ orientation. Arrow/Home/End keys move focus and select enabled options; horizontal
948
+ arrows follow the provider's direction. Existing group/pressed-button semantics
949
+ are retained rather than exposing AntD radio-style markup.
950
+
951
+ Avatar accepts native span attributes/ref, children/icon fallback, numeric size,
952
+ circle/square shape and gap-based text scaling measured with ResizeObserver.
953
+ Images support srcSet (including srcSet-only), crossOrigin and loading; failure
954
+ falls back unless onError returns false, and changing src/srcSet retries.
955
+ The wrapper owns the accessible name; its image is decorative. Unicode initials
956
+ preserve graphemes with Intl.Segmenter (older engines use code points).
957
+ AvatarStack applies size/shape to every member and +N, and exposes hidden names
958
+ in the overflow label/title. IconAvatar supports numeric size/native span ref;
959
+ AvatarPlaceholder forwards native button attributes/ref and provider disabled.
960
+ Klun retains deterministic name colors, xs/sm/md/lg dimensions and people/max
961
+ stack data. AntD responsive breakpoint sizes, ReactNode src, Group children
962
+ context/popover and semantic style maps are not part of this contract.
963
+
964
+ Tag supports closable/onClose (preventDefault cancels removal), closeLabel,
965
+ checkable/checked/defaultChecked/onChange and disabled. Check and close use
966
+ separate native buttons so both interactions remain reachable by keyboard.
967
+ Color remains a CSS color; existing Klun variants and icons are preserved.
968
+
969
+ Card adds loading, cover, actions, bordered and hoverable; extra renders even
970
+ without a title. Loading hides pending children and announces status. Progress
971
+ maps nonfinite values to zero. Divider adds children/titlePlacement and dashed
972
+ rails with logical start/end alignment. These APIs preserve Klun's existing
973
+ geometry. Card now exposes Grid/Meta composition; semantic-style APIs remain outside the contract.
974
+
975
+ ### P1 menu and navigation contracts
976
+
977
+ Menu retains action-menu defaults. Set `selectable` for selectedKeys /
978
+ defaultSelectedKeys / onSelectedKeysChange; `multiple` uses checkbox semantics
979
+ and keeps the menu open while toggling. Existing item.checked and onSelect(id)
980
+ remain available. openKeys / defaultOpenKeys / onOpenChange control expansion;
981
+ vertical/horizontal modes use popup submenus, inline mode displays nested rows.
982
+ Single leaf selection closes standalone popup submenus and the surrounding
983
+ Popover. Controlled state changes are requested through callbacks. Item IDs
984
+ must be unique throughout a hierarchy. Rich label children are searchable;
985
+ opaque labels can provide searchText. Arrow keys, Home/End, typeahead and disabled
986
+ choices work across supported modes; directions follow ConfigProvider.
987
+
988
+ Independent Menu/Select instances do not inherit the selection or expansion of
989
+ a containing menu. Only internal recursive submenus share that state. Klun's
990
+ item.id and onSelect(id) contracts remain distinct from AntD key/event objects;
991
+ AntD hover delays, horizontal overflow and inlineCollapsed are not supplied.
992
+
993
+ Nav stays route-controlled and adds provider-aware disabled, RTL arrows and
994
+ Home/End. Arbitrary IDs are matched as data, without CSS selector interpolation.
995
+ Sidebar adds controlled width/onWidthChange; finite min/max bounds apply to both
996
+ rendered dimensions and resizing, with RTL pointer/keyboard arithmetic and a
997
+ logical content-edge gutter. Collapsed sidebars are inert and hidden from the
998
+ accessibility tree. Existing mobile hiding and shell collapse provider remain.
999
+
1000
+ ContextMenu and ContextMenuList consume disabled defaults (explicit false wins).
1001
+ Disabled requests cannot open or persist an imperative menu; disabled parents
1002
+ hide and lock submenus. Context menu portals retain dir/lang, and submenu
1003
+ placement/navigation respects RTL. Pointer-bound menus remain separate from
1004
+ AntD Dropdown, keeping the existing openAt/row context and icon-strip contracts.
1005
+
1006
+ ### P1 general feedback
1007
+
1008
+ `Empty` adds the general Empty entry alongside `EmptyState`: custom image nodes
1009
+ or image URLs, description (null/false hides it), children as actions and localized
1010
+ English/Chinese defaults. `Spin` supports controlled spinning, cancellable delay,
1011
+ custom indicator, description, three sizes and fullscreen; busy child content is
1012
+ inert until loading finishes. `Skeleton` preserves the original single block API
1013
+ and adds title/avatar/paragraph placeholders, static animation and loading=false
1014
+ content. `Result` supports success/error/info/warning and 403/404/500, custom icon,
1015
+ title, subtitle, actions and detail content; static results are not live alerts.
1016
+
1017
+ These entries reuse Klun tokens and existing feedback primitives. AntD's semantic
1018
+ classNames/styles API, global indicator setter and Spin auto-percent animation
1019
+ are not exposed. Existing Progress retains the 0–1 value contract in Card.
1020
+
1021
+ ### P1 Slider and Rate
1022
+
1023
+ `Slider` preserves native single-value `onChange(event)` and adds a discriminated
1024
+ `range` mode with `[start,end]` value/defaultValue/onChange, distinct form names,
1025
+ non-crossing handles, dynamic accessible bounds, step normalization, marks,
1026
+ vertical layout, native title tooltips and onChangeComplete. The forwarded ref
1027
+ points to the first native input. Tooltips use the browser title UI, not a popup;
1028
+ AntD's editable multi-handle range, draggable whole track and null-step marks-only
1029
+ selection are not exposed.
1030
+
1031
+ `Rate` adds count, allowHalf, hover preview/onHoverChange, per-star tooltips and
1032
+ custom characters. Native same-name radios keep form submission and arrow keys;
1033
+ readonly values expose one accessible rating name. Clear remains an explicit
1034
+ button. Half-star labels expose each 0.5 choice so keyboard and pointer interaction
1035
+ share the same discrete values.
1036
+
1037
+ ### P1 general Progress
1038
+
1039
+ `Progress` keeps value in 0–1 and adds percent in 0–100 (value takes precedence),
1040
+ line/circle/dashboard, active/success/exception styling, segmented steps, bounded
1041
+ success overlays, custom colors/format/ARIA naming, size/stroke controls and
1042
+ logical dashboard gap placement in RTL. showInfo controls formatted percentages;
1043
+ showValue remains the existing line label option. SVG rings use local paths;
1044
+ AntD gradient objects, arbitrary semantic styles and percentage-position variants
1045
+ are not part of this API.
1046
+
1047
+ ### P1 Tabs, Collapse and Steps
1048
+
1049
+ `Tabs` adds top/bottom/start/end placement, RTL and vertical keyboard navigation,
1050
+ provider disabled, card/editable-card, add/remove requests and focus recovery after
1051
+ closing either active or inactive tabs. Hosts own edited items via onEdit(action,
1052
+ key). Dynamic removal repairs uncontrolled selection without resurrecting stale
1053
+ keys. keepMounted preserves the previous API; item forceRender and destroyOnHidden
1054
+ control content lifecycle. forceRender takes precedence over destroyOnHidden,
1055
+ as in the underlying rc-motion stable hidden branch. Overflow uses native scrolling.
1056
+
1057
+ `Collapse` adds provider disabled, Up/Down/Home/End header focus, independent extra
1058
+ actions, borderless/ghost and keepMounted=false teardown; accordion ignores keys
1059
+ for removed items. Extra actions are separate controls outside the toggle button.
1060
+ `Steps` adds item status/icon/subTitle, initial numbering and current/onChange
1061
+ absolute step offsets, localized status, provider disabled and direction keys that
1062
+ move focus without changing the step until activation. Horizontal responsive
1063
+ stacking remains the existing CSS behavior.
1064
+
1065
+ AntD animated panel motion, tabs automatic overflow menu/custom tab-bar rendering,
1066
+ Steps panel/dot types and semantic styling are separate remaining differences.
1067
+
1068
+ ### P1 input and action enhancements
1069
+
1070
+ Input preserves the native input ref and event contract. It adds `sm`, prefix/suffix,
1071
+ `allowClear`, `showCount` (UTF-16 length, optional formatter), warning/error status and
1072
+ outlined/filled/borderless/underlined variants. Clearing emits the native input event
1073
+ and restores focus; read-only/disabled fields hide the clear button. Runtime feature
1074
+ changes preserve the input node and uncontrolled value. Native form reset updates counts.
1075
+ PasswordInput inherits these props. Textarea retains its existing native API.
1076
+
1077
+ Button adds dashed/link variants, round/circle shape, ghost and danger combinations.
1078
+ ActionButton and Switch support loading (busy and disabled). Switch supports checked /
1079
+ unchecked text and Left=off / Right=on keyboard operation; its native checkbox events,
1080
+ form value and ref remain intact. RadioGroup and CheckboxGroup options accept native
1081
+ id/title/required/style/change handlers; checkbox changes report registered values in
1082
+ option order. Group values remain strings to preserve the existing typed API. AntD
1083
+ semantic styles, delayed loading, numeric group values and boolean Switch value aliases
1084
+ are not provided by these native-control APIs.
1085
+
1086
+ ### P1 inline feedback enhancements
1087
+
1088
+ Badge adds rich counts, status/text, custom color, small size, logical attached offsets,
1089
+ null/false title suppression and native span attributes/ref. Numeric overflow keeps the
1090
+ full accessible description; invalid numeric counts are hidden. The original explicit
1091
+ `dot` remains visible at zero. Processing uses a reduced-motion-aware pulse. AntD Ribbon
1092
+ and animated rolling digits remain compatibility gaps.
1093
+
1094
+ Alert adds a `title` alias (preferred over message), warning banner, filled variant,
1095
+ custom decorative icon, native div attributes/ref and configurable close button. Close
1096
+ handlers may preventDefault; inherited disabled can be explicitly overridden. Controlled
1097
+ hosts retain ownership of visibility. afterClose runs after actual removal, once, with no
1098
+ exit animation. Existing showIcon=true and open/defaultOpen contracts remain unchanged.
1099
+ AntD semantic style callbacks and animated exit are not implemented.
1100
+
1101
+ ### `InputNumber`
1102
+
1103
+ Numeric `value/defaultValue` and `onChange(number | null)` integrate with Form; native
1104
+ input ref/attributes, affixes, status and variants come from Input. Invalid/incomplete
1105
+ editing drafts stay visible while focused, and valid in-range edits update the value.
1106
+ Blur aligns range/precision and restores the host's controlled value. External values
1107
+ update focused drafts. Step controls and Up/Down support Shift×10, disabled/read-only,
1108
+ precision, formatter/parser and onStep. Uses JavaScript numbers (small IEEE-754 artifacts
1109
+ are rounded without truncating large precise values); AntD stringMode/high-precision arithmetic and press-and-hold are gaps.
1110
+ `formatter` receives userTyping/input to preserve caret-friendly drafts. Composition
1111
+ commits once at its end, with modified/IME navigation protected; finite stepping rejects
1112
+ unrepresentable results and bound no-ops without calling onStep. Step controls use locale.
1113
+ Host formatter/parser exceptions remain host-owned; no stringMode or arbitrary precision.
1114
+
1115
+ ### `AutoComplete`
1116
+
1117
+ Editable string value, options `{value,label?,disabled?,searchText?}`, filtering/custom
1118
+ filter, onSearch/onSelect, controlled open, clear and native ref. Combobox focus stays
1119
+ in the input; Up/Down skip disabled options, active choices scroll into view, Enter
1120
+ selects, Escape closes. IME composition avoids premature search/selection. Empty options
1121
+ never render a popup. Uses the existing Popover with autofocus disabled; grouped options,
1122
+ virtual suggestions and arbitrary custom input children remain compatibility gaps.
1123
+ Selecting the existing value still calls onSelect but not onChange/onSearch. Disabled or
1124
+ read-only drops uncontrolled popup state; explicit controlled open remains host-owned.
1125
+ Modified and key229 navigation is ignored. Native input form/reset semantics are retained.
1126
+
1127
+ ### `TreeSelect`
1128
+
1129
+ Tree data uses `{value: string,title,children?,disabled?,disableCheckbox?,selectable?,isLeaf?}`.
1130
+ Single values are string/null; multiple or treeCheckable values are string arrays. Change
1131
+ receives values and labels. Reuses Tree expansion, lazy loading, checks, search and virtual
1132
+ scrolling via treeProps; lazy node identities and selected labels survive parent renders.
1133
+ Checkbox mode selects via checkboxes and returns Tree's full checked keys; AntD strategy
1134
+ constants / labelInValue / numeric values are compatibility gaps. Native trigger ref,
1135
+ ARIA, hidden named form values, clear, controlled open and Form blur validation are supported.
1136
+ Node identities remain nonempty strings as required by Tree (null clears). Repeated identical selection does not
1137
+ notify onChange. Locale covers placeholder/search/clear; explicit disabled=false reaches
1138
+ both search and Tree under a disabled provider. Disabling drops uncontrolled popup state.
1139
+ Search clears on actual close; rejected controlled close requests retain the search.
1140
+
1141
+ ### `TimePicker`
1142
+
1143
+ Native local-clock input: string HH:mm / HH:mm:ss (empty clears), min/max, second-based
1144
+ step, native ref/attributes, Input affixes/variants/clear, Form and ConfigProvider. DisabledTime
1145
+ marks unavailable values invalid without discarding drafts, with a localized validity
1146
+ message. Native min/max supports overnight ranges; repeated same-value changes do not
1147
+ notify. Values remain local clock strings and explicit disabled=false is supported. No Date or timezone conversion.
1148
+ The native picker provides localized interaction; AntD Dayjs, custom time columns, range
1149
+ picker, 12-hour formatting and disabled unit callbacks are not provided by this entry.
1150
+
1151
+ ### General information display
1152
+
1153
+ `Breadcrumb` complements Crumbs with native links, configurable separators, disabled
1154
+ items, custom rendering and current-page semantics. `Descriptions` complements KeyValue
1155
+ with responsive columns (xs→xxxl), horizontal/vertical layout, borders, header/extra and
1156
+ bounded spans; overflow spans truncate to the current row, and the final cell fills it.
1157
+ Breadcrumb labels follow locale, a null per-item separator hides it; host itemRender owns
1158
+ its own interaction/disabled behavior. Descriptions preserves zero labels and values.
1159
+ Both forward native attributes/ref. Legacy Crumbs and KeyValue APIs remain intact.
1160
+
1161
+ `Statistic` complements Metric with string-preserving digit grouping, configurable
1162
+ separators/precision, prefix/suffix, formatter, loading and value style. Precision truncates
1163
+ and pads decimals as in AntD's Number.tsx; fractional-only strings gain a leading zero,
1164
+ empty string displays zero, punctuation-only strings remain raw; it does not round or convert big integer strings.
1165
+ Timer/countdown and semantic style callbacks remain compatibility gaps.
1166
+
1167
+ `Typography.Text/Title/Paragraph/Link` provide native text/heading/link semantics, emphasis,
1168
+ code/keyboard/mark/delete, CSS multi-line ellipsis with controlled expansion, copying and
1169
+ editing. Clipboard errors expose retry and invoke onError; async copy stays busy. Editing
1170
+ accepts empty strings, supports Enter-save/Escape-cancel and restores focus; disabled
1171
+ configuration blocks actions. Copy/edit labels follow locale, and copy requires
1172
+ the native Clipboard API. Measurement-based suffix ellipsis, autosizing editor, semantic
1173
+ styling and HTML clipboard formats remain gaps. Link uses safe defaults for target=_blank.
1174
+
1175
+ ### Layout / Grid / Flex / Space / Splitter
1176
+
1177
+ Layout 提供 Header/Footer/Content/Sider;Sider 支持受控/默认折叠、零宽、响应式断点、触发器和折叠原因回调。Sider仅实际折叠状态变化通知onCollapse,onBreakpoint仍报告初始/变化断点;支持locale与显式disabled=false、有限非负数字width。Sider xxxl保持AntD1840阈值,Grid xxxl保留原库1920,不强改已有断点。Row/Col 使用 24 栅格、逻辑方向 gutter/offset/push/pull、响应式尺寸/排列;Grid.useBreakpoint 返回断点状态。Flex 使用原生 CSS flex,有限非负数字gap;Col数字flex/order防止NaN/Infinity;Space 支持间距、分隔及 Compact 输入/按钮连接。
1178
+
1179
+ Splitter.Panel 提供数字像素或百分比 size/defaultSize/min/max,受控尺寸、指针/方向键/Home/End 调整、折叠恢复、保留或销毁隐藏内容。回调返回像素尺寸,宿主控制 size 时必须写回才能持久更新;尺寸计算扣除每个 8px 分隔条。整组受控尺寸按可用空间比例归一化,部分受控面板保持固定尺寸。未提供 size 的面板内部管理调整结果。拖动结束/取消/lost capture只提交当前pointer draft一次,非当前pointer不终止;axis/RTL/extent/disabled/panel身份变化使旧draft失效,修饰键/IME不resize;不可满足pair min/max时保留当前分配而不制造越界。复杂对象 collapsible、拖动预览延迟、多panel穿透resize、dragger doubleclick、AntD semantic 样式入口仍未提供。
1180
+
1181
+ ### Dropdown / Popconfirm
1182
+
1183
+ Dropdown 使用原生 Button 触发器(children 是按钮内容,ref 指向按钮),menu 接收现有 MenuProps。支持 open/defaultOpen/onOpenChange、方向键打开、嵌套菜单、禁用、选择关闭及现有 Placement 定位。IME/key229/修饰键不打开;外部loading阻止内置交互,禁用/loading丢弃非受控开关但保留host受控open,重复相同开关不通知。多选菜单遵循 Menu 保留打开的规则。未复制 AntD 的任意子元素克隆、hover/contextMenu 触发、Dropdown.Button 或 popupRender 扩展。
1184
+
1185
+ Popconfirm 同样使用原生 Button 触发器,title 是字符串可访问名称,description 支持 ReactNode。onConfirm 可以异步,false 保留打开,拒绝会显示重试提示并通知 onError;等待期间阻止重复、取消和外部关闭。onCancel 表示取消按钮操作。受控 open 必须由宿主写回,禁用/关闭/卸载使旧请求结果失效。使用现有 Popover 的焦点恢复和上下文;文本可通过 okText/cancelText 自定义,locale提供确认/取消/失败重试默认,0 description和button内容不丢失。外部loading隐藏内置操作;禁用丢弃非受控popup且使pending generation失效,受控open仍由host拥有。未实现参考的任意子元素触发、函数 title/description、箭头和完整 semantic 样式。
1186
+
1187
+ ### Drawer
1188
+
1189
+ Drawer 复用 Dialog 的模态栈、焦点循环/恢复、滚动锁及配置上下文。open 由宿主管理;placement 支持 top/right/bottom/left 物理边缘,RTL 不交换指定边缘。width/height 接收像素数字或 CSS 长度并限制到视口;默认 378px,非法非有限数字回退378,负数归0。title/tools/footer/bodyClassName/style 使用 Dialog 协议,keyboard/maskClosable/closable 独立控制关闭方式;dismissable 仍作为前两项默认值。关闭时销毁内容,原 DetailSheet 业务组件保持独立。参考的 push、resizable、destroyOnHidden=false、动画结束回调、非模态/自定义挂载与 semantic 样式尚未提供。
1190
+
1191
+ ### Image
1192
+
1193
+ Image 使用真实原生 img,alt 必填,ref 指向图片;src/fallback/placeholder 处理加载及失败,原生 img 属性透传。preview=false 关闭预览,配置对象支持 open/defaultOpen/onOpenChange/src;预览使用 Dialog,提供缩放、旋转、重置和焦点恢复。disabled 消费配置,取消 onClick 默认行为可阻止打开。className/style 作用于包裹容器,width/height 作用于 img。
1194
+
1195
+ 预览/工具/关闭沿用locale;禁用丢弃非受控预览,受控open仍由host持有。旋转按360度归一化,0 placeholder可见。
1196
+
1197
+ Image.PreviewGroup 使用显式 items(src/alt)列表,支持 current/defaultCurrent/onChange、受控开关和前后导航;重复打开当前图片不重复通知onChange;子 Image 的 src 匹配列表时打开分组,否则打开独立预览。未实现参考的自动子图片注册、拖动/滚轮/翻转、下载、自定义动作、进度/semantic 样式;业务 FilePreview 仍仅展示附件元数据,不代替此真实图片组件。
1198
+
1199
+ ### Upload
1200
+
1201
+ Upload 的 originFileObj 是实际 File,ref 指向原生 file input,children 是按钮内容。默认无传输,只保留手动列表;指定 action 后发送 POST multipart(name/data/headers/withCredentials),或 customRequest 获得实际 file 和 progress/success/error 回调,可返回 abort。成功 response 是 XHR 原始文本,由宿主解析。
1202
+
1203
+ beforeUpload 支持异步,false 保留手动项,Upload.LIST_IGNORE 忽略,File/Blob 转换实际字节;失败(包括falsy拒绝原因)保留 error 项。accept 在选择和拖放时都检查,multiple/maxCount 控制数量,maxCount=1 替换并取消旧请求。fileList/defaultFileList/onChange 提供受控/默认列表,onRemove false 或拒绝保留文件;删除/卸载后旧回调不再更新;异步移除结束检查最新列表,已移除项不重复通知。异步预处理完成后使用最新传输配置/maxCount/回调;禁用期间不开始新请求,已开始的请求仍由移除/卸载取消。中文locale覆盖列表与控件;显式disabled=false传给内置按钮。列表只展示文本/进度/错误和删除,不自动渲染不可信文件链接。beforeUpload 是客户端检查,服务端仍需验证文件。目录、粘贴、缩略图列表、自动重试、任意 trigger 克隆、动态 action/data、完整 semantic 样式仍为参考扩展差距;AttachmentUpload 业务元数据契约保持独立。
1204
+
1205
+ ### App / message / notification
1206
+
1207
+ App 内包含独立 ToastProvider 栈,message 默认顶部居中,notification 默认右上,配置支持 placement/max,嵌套 App 按字段继承父配置但各自持有独立栈;max=0 抑制展示;默认右上通知在窄屏移至右下以避免与顶部 message 重叠。放在 ConfigProvider 内即可继承 direction/locale。App.useApp() 返回最近 App 的绑定 API;根导出的 message/notification 调用最后注册且仍挂载的 App,没有宿主时抛出明确错误,不创建丢失上下文的独立根。
1208
+
1209
+ message.open({content,type,key,duration,onClose})、notification.open({message,description,type,key,duration,onClose}) notification 返回可用于 destroy 的字符串 key;message 返回可调用关闭且可 await 的句柄(key 属性可用于 destroy),提供 info/success/warning/error/loading,loading 默认持续至关闭。duration 继续使用 Klun 的毫秒契约,0 为持久;destroy() 删除本栈全部,destroy(key) 删除指定项。同 key 更新替换原内容并重置计时,注册的 onClose 在最终移除时各执行一次;封顶删除和卸载清理计时器。已关闭句柄再次调用不会删除后来复用相同 key 的消息;批量移除先清理旧项,再执行全部回调,回调抛错仍结清所有等待者并保留新消息计时,随后向宿主抛出首个错误。悬停或键盘焦点暂停正数计时,离开后沿用原有 1500ms 宽限;持久项不会因悬停而关闭。现有 useToast.push/dismiss 保持兼容,新增 key/onClose 和无参数 dismiss。
1210
+
1211
+ 参考的独立 useMessage/useNotification contextHolder、自定义静态根/config、可堆叠折叠/进度条、App DOM wrapper/modal API 和任意挂载点有意不提供;静态 API 推荐只在单根 App 中使用,多根/嵌套场景使用 App.useApp 绑定准确上下文。
1212
+
1213
+ ### Form 条件项与字段生命周期
1214
+
1215
+ 无 name 的 Form.Item 可接收 `(form) => ReactNode`,shouldUpdate=true 默认监听值变化,比较函数接收前后值快照;单独 dependencies 时只跟随指定路径变化。noStyle 支持无名输出或命名控件的 display:contents 包装。命名项仍要求单个可绑定控制元素。
1216
+
1217
+ Form/Item preserve 默认 true,Item 可覆盖 Form。preserve=false 实际卸载后恢复 Form initialValues,无初始值则删除路径;列表子字段没有单字段初始值,且清理保护列表重排/移位、StrictMode 重注册、父列表整体卸载和宿主最新写值。卸载清理在微任务中发生,读取最终值时等待提交后的微任务。字段错误与请求元数据同时清理。scrollToField 支持 focus/block/behavior,保持原默认聚焦;focusField 只定位可见、可用控制,不聚焦隐藏输入或禁用字段集。
1218
+
1219
+ validateTrigger 支持 onChange/onBlur、事件数组及 false;false 关闭自动校验,显式 validateFields/submit 仍校验。保持 Klun 默认 onBlur,已出现错误的字段在修改时自动复验(false 除外)。scrollToFirstError 支持 false 或 focus/block/behavior,保持 Klun 默认 true;AntD 默认不自动滚动,这一差异用于兼容既有调用。
1220
+
1221
+ ### Calendar / Timeline
1222
+
1223
+ Calendar 是独立月/年日历,value/defaultValue 使用 YYYY-MM-DD ISO 字符串,与 DatePicker 共用校验与日期运算;mode/defaultMode 为 month/year,fullscreen=false 为紧凑单元格。onChange 只在日期变化时触发,onSelect 额外给出 date/month/year/customize 来源;onPanelChange 在面板年月或模式改变时通知。validRange 是含端点有效范围,disabledDate 与 ConfigProvider.disabled 组合限制选择。cellRender 追加内容,fullCellRender 替换内容;headerRender 接收 value/type/onChange/onTypeChange,自定义操作仍受范围/禁用限制。网格方向键/Home/End/PageUp/PageDown 支持跨月聚焦和 RTL。时间、周编号和 Dayjs 类型不属于这一入口,保留 Klun 日期字符串契约。
1224
+
1225
+ Timeline 使用语义 ol/li,items 支持 key/title/content/icon/color/loading/placement,以及 AntD 的 label/children/dot 别名。pending 追加加载项,reverse 复制后反转,不改宿主数组;orientation 为 vertical/horizontal,mode 为 start/end/alternate(left/right 兼容逻辑别名)。空数据输出空列表,具体空状态由宿主组合 Empty;水平列表在窄屏内部滚动,颜色和布局随主题/方向变化。业务 TransitionsTimeline 仍负责活动含义与时间格式,不混入通用数据模型。
1226
+
1227
+ ### List / Listy / Masonry
1228
+
1229
+ List 泛型 dataSource/renderItem/rowKey 配合现有 Pagination(Klun 的 page/pageSize 命名)、受控/非受控页码和容量;数据缩短会夹到有效页。header/footer/loadMore、bordered/split/size/itemLayout、grid 响应式列数与 gutter、loading 和 emptyText 可组合。List.Item 支持 actions/extra,List.Item.Meta(也导出 ListItem/ListItemMeta)支持 avatar/title/description;原生 li 保留,不产生嵌套列表项。children 分页忽略 null/false,与总数采用同一集合。
1230
+
1231
+ Listy 为独立泛型长列表,items/rowKey/itemRender、group.key/title、sticky、height 和 virtual。无有效 height 时使用正常流;virtual=true 使用固定 itemHeight(默认44,分组标题也同高),只渲染可见区及3行缓冲,并保留完整滚动高度与 aria-posinset/setsize。变高内容请使用普通 Listy 或 Masonry,不承诺 rc-listy 动态测量行高。ref.nativeElement 和 scrollTo(number / {top,left} / {key,align,offset} / {groupKey,align,offset}) 支持定位;align 为 top/bottom/auto。分组保持首次出现的分组顺序,itemRender 的 index 保留原数据索引;可访问序号按最终分组顺序。
1232
+
1233
+ Masonry items 的 key/data/children/height/column 与 itemRender 配合;columns 支持断点对象,gutter 支持数字或 [横向,纵向]。使用原生 ResizeObserver 测实际单元高度,依次放入最短列;column 可固定列且夹到有效范围,改变高度/列数/内容自动重算。DOM 顺序保持数据顺序,RTL 使用逻辑位置。onLayoutChange 通知 key/column 分配变化,不复制 AntD motion/fresh 动画或语义样式注入。旧浏览器无 ResizeObserver 时仅挂载/属性更新时测量;宿主需要动态媒体尺寸更新应提供原生 ResizeObserver 支持。
1234
+
1235
+ ### Affix / Anchor / FloatButton / BackTop
1236
+
1237
+ Affix 在 window 或 target() 返回的滚动容器边缘固定同一个子 DOM,offsetTop 默认0、offsetBottom 可选;占位保留测量高度,滚动/resize/原生 ResizeObserver 重算位置,onChange 仅通知固定状态变化。ref.nativeElement/updatePosition 可主动更新;无目标解除固定,卸载移除监听和动画帧。使用 CSS fixed,宿主须避免会改变 fixed 包含块的 transformed 祖先;不引入复制节点或门户导致表单/焦点重新挂载。
1238
+
1239
+ Anchor 是 nav/原生链接,items 支持 key/href/title/children/target/disabled,提供纵横方向、affix、offsetTop/targetOffset/bounds、getContainer、getCurrentAnchor、onChange/onClick 和 replace。字面 ID 使用 getElementById/decodeURIComponent;只拦截同页普通主键点击,Ctrl/Meta/外部 target 保持浏览器行为,preventDefault 可取消。href 只接受 HTTP/HTTPS/mailto/tel 或相对地址,不执行脚本协议。消费 ConfigProvider disabled/locale,滚动检测活动章节。更换容器时更新 getContainer 函数身份,以重新绑定监听;不复制弃用 Anchor.Link 或 semantic 样式。
1240
+
1241
+ FloatButton 保留 Button 原生属性、ref、variant 和原生 type,扩展 icon/content(description 别名)/shape/tooltip/badge;icon-only 需 aria-label 或文字 tooltip。FloatButton.Group(也导出 FloatButtonGroup)支持常驻组与 click/hover、open/defaultOpen/onOpenChange,禁用原生字段集阻止所有按钮;Escape 关闭后恢复内部焦点,鼠标离开但键盘仍在组内时保留,随后失焦关闭。逻辑角落位置可通过 style 覆盖;不重复实现链接按钮协议或语义样式注入。
1242
+
1243
+ AntD 6.6.5 独立 BackTop 已弃用并推荐 FloatButton.BackTop;Klun 同时提供这两个入口并复用同一实现。target 接受 Window/Document/HTMLElement,visibilityHeight 默认400、showProgress 显示0–100滚动进度;behavior 默认 smooth,系统减少动画时自动 auto。onClick.preventDefault 可取消滚动,target 不存在时隐藏,监听在卸载时移除。沿用浏览器平滑滚动,不提供毫秒 duration 动画引擎。
1244
+
1245
+ ## Mentions
1246
+
1247
+ Native textarea ref; string value/defaultValue/onChange integrates with Form. options accepts value/label/disabled;
1248
+ prefix supports one or multiple nonempty tokens, split defaults to space. Searches the last prefix before the collapsed caret,
1249
+ filters case-insensitively by value (filterOption=false for remote search), invokes onSearch(query,prefix), and inserts onSelect.
1250
+ IME delays suggestions until composition ends. Up/Down skips disabled options and scrolls the active option into view;
1251
+ Enter inserts without submitting a form, Escape closes, and Tab retains normal navigation. onKeyDown may cancel default handling.
1252
+ Loading and notFoundContent represent pending/empty results. Mentions.getMentions parses completed prefix tokens.
1253
+ Controlled refusal leaves the original text and focus unchanged after the selection event. Disable/readOnly suppress editing suggestions.
1254
+
1255
+ Compared with AntD 6.6.5 and @rc-component/mentions 1.12.0: textarea anchored Popover rather than mirrored caret geometry;
1256
+ no deprecated Option children, semantic styles, popup container or auto-size wrapper. The replacement reuses matching trailing text
1257
+ and adds split boundaries without deleting unrelated trailing characters. Multiple-character split is supported.
1258
+
1259
+ ## ColorPicker
1260
+
1261
+ Native Button ref; string/null or gradient stop array controlled/default values, onChange(value), onChangeComplete(value), controlled open/defaultOpen/onOpenChange.
1262
+ Solid colors and each gradient stop accept #RGB/#RGBA/#RRGGBB/#RRGGBBAA, comma RGB/RGBA (including percentages), HSB/HSBA with percentage saturation/brightness,
1263
+ and transparent. Invalid/out-of-range input is rejected before callbacks or swatch CSS; unsupported external values display an empty swatch.
1264
+ HEX/RGB/HSB format switching changes presentation; native color input and keyboard-operable hue/saturation/brightness/opacity ranges edit values.
1265
+ HSB stays internal for grayscale/black so changing opacity or restoring saturation retains hue. disabledAlpha forces opacity on user edits.
1266
+ Completion fires on typed commit, presets, clear, range release/navigation or native picker blur. allowClear emits null and onClear.
1267
+ Presets label/colors/defaultOpen, showText, panelRender and resolved global disabled settings are supported.
1268
+
1269
+ Compared with AntD 6.6.5 and @rc-component/color-picker 3.1.1: strings replace AggregationColor objects;
1270
+ uses native color selection and accessible channel ranges rather than a custom 2D drag plane. Gradient values use {color,percent}[]; native stop selection/add/remove/position editing supports at least two stops, validates CSS, sorts only the preview and preserves input order. mode selects available single/gradient modes; controlled values determine the current mode. No custom stop drag plane, hover trigger, semantic styles or popup mounting.
1271
+
1272
+ ## Carousel
1273
+
1274
+ Single-visible-slide carousel; current/initialSlide/onChange, beforeChange/afterChange, ref nativeElement/goTo/next/prev.
1275
+ Finite/infinite bounds, arrow/dot navigation, horizontal/vertical/RTL keyboard, optional native pointer swipe,
1276
+ fade/scrollx entrance effects and logical dot placement. Inactive slides stay mounted but hidden and unfocusable.
1277
+ Autoplay pauses on pointer hover, focus, document hiding or reduced motion; a Play/Pause control is always available.
1278
+ Changing speed during navigation does not discard the completion event; unmount or later navigation cancels superseded timers.
1279
+ Compared with fixed AntD/react-slick: no clone track, multi-slide/slick settings, internal slider or legacy autoplay imperative API;
1280
+ CSS entry transitions replace outgoing track animation. initialSlide is the uncontrolled seed, current controls later changes.
1281
+
1282
+ ## Tour
1283
+
1284
+ Target-aware modal tour reuses Dialog's stack, keyboard trapping, Escape, focus restoration and scroll lock;
1285
+ Dialog now accepts maskStyle/maskContent for custom visual masks. steps title/description/cover/target/placement/mask,
1286
+ current/defaultCurrent/onChange, open/defaultOpen/onOpenChange, onClose(current)/onFinish.
1287
+ Default open is true when steps exist (reference behavior). Target is scrolled into view, followed on scroll/resize,
1288
+ and highlighted through an SVG mask; missing targets center the panel. Gap, mask color, primary border,
1289
+ custom indicators/actions and cancellable next/previous button props are supported. Resolved disabled=false overrides provider disable.
1290
+ Compared with @rc-component/tour: target interactions remain blocked during this modal flow;
1291
+ no nonmodal focus behavior, animation placeholder, arbitrary popup mounting or semantic styles. Positions use viewport bounds.
1292
+
1293
+ ## QRCode
1294
+
1295
+ Uses the exact reference @rc-component/qrcode 2.0.0 encoder, rather than decorative modules. SVG default/canvas,
1296
+ UTF-8 strings or segment arrays, errorLevel L/M/Q/H, boostLevel, four-module quiet margin,
1297
+ size/color/bgColor, embedded icon/imageSettings, bordered, active/loading/expired/scanned and refresh/custom status controls.
1298
+ Native wrapper ref. Empty values have a visible state; excessive payloads show an alert rather than crashing callers;
1299
+ valid value changes recover an encoding failure. Keep high contrast and quiet margins for scanning.
1300
+ The exact encoding package is a build-time dependency bundled into the artifact with its MIT notices; public types do not reference it. jsqr is a development-only independent decoder used by tests.
1301
+ SVG default intentionally differs from AntD canvas default, with no semantic style injection or QR internals exposed.
1302
+
1303
+ ## Watermark
1304
+
1305
+ Native repeated DOM tiles support text/multiple styled lines, native image/failure fallback, font, rotation,
1306
+ width/height, gap/offset and zIndex; ResizeObserver follows content dimensions. Text and controls remain usable.
1307
+ Native root ref. MutationObserver reattaches the same removed overlay or tile and restores altered overlay styles,
1308
+ then invokes onRemove. Normal React dimension/content/style updates do not count as tampering.
1309
+ Compared with AntD: DOM marks replace raster canvas caching, image requests use native browser loading,
1310
+ no inherited watermark context; tamper repair is visual deterrence, never access control.
1311
+ Very dense surfaces are limited to 4096 tiles; use larger tile/gap values for large scroll documents.
1312
+
1313
+ ## BorderBeam
1314
+
1315
+ A layout wrapper with native root ref adds pointer-transparent SVG perimeter segments, independent of child DOM refs.
1316
+ Responsive measurement, count (1..32), duration seconds, pixel size/lineWidth/outset and validated solid/gradient colors.
1317
+ Native SVG gradient stops use finite bounded percentages. Reduced motion keeps stationary segments.
1318
+ Compared with AntD: wrapper layout and top-left uniform corner radius replace child-host injection and nonuniform corner geometry;
1319
+ SVG dash motion replaces CSS offset-path and tail coloring. Numeric dimensions avoid CSS-unit measurement dependencies.
1320
+
1321
+ ### DataTable: hierarchy, header groups and virtual windows
1322
+
1323
+ `columns` accepts `ColumnDefinition<T>`: either an existing `Column<T>` or a
1324
+ `ColumnGroup<T>` with `id`, `header`, `children`, `hidden` and `className`. Hidden
1325
+ branches disappear; native colSpan/rowSpan reflect the visible leaves. Numeric
1326
+ leaf widths also size grouped headers through colgroup. Group headers pin only
1327
+ when all their leaves share the same fixed edge. Duplicate column IDs are rejected.
1328
+
1329
+ `getChildren(row)` opts into hierarchy; root records are filtered, sorted and
1330
+ paged before expanded descendants are flattened. Sorting and filters apply at
1331
+ all levels, with a failed parent filter excluding its branch. Original records
1332
+ remain unchanged. Use the existing expansion keys/callback, `rowExpandable` and
1333
+ `indentSize` (20px default). Duplicate or empty row keys, including cycles, are
1334
+ rejected. Row indexes refer to the visible flattened page. `groupBy` groups that
1335
+ visible page; `expandedRowRender` can coexist with tree expansion.
1336
+
1337
+ `sortPriority` enables additive multi-sort (higher numbers first). `sorts`,
1338
+ `defaultSorts` and `onSortsChange` expose the array; each header cycles asc → desc →
1339
+ removed. Existing controlled `sort` stays single-sort unless the array API is
1340
+ explicitly used. `onSortChange` retains its nullable single-state payload.
1341
+ `onChange(change)` reports sort/filter/paginate once, including proposed filters,
1342
+ sorts, pagination and processed **root records before pagination** in
1343
+ `currentDataSource`. Controlled rejection does not suppress proposed events.
1344
+ `summary(rows)` now receives that pre-pagination root scope, matching AntD's
1345
+ summary scope; render native rows/cells inside the supplied tfoot.
1346
+
1347
+ `selectionMode="single"` renders same-name native radios without select-all or a
1348
+ bulk selection bar; a pick replaces the selected keys and disabled rows cannot
1349
+ be picked. Multiple selection retains its existing page selection contract.
1350
+
1351
+ `virtual` enables a fixed-height native table window, or use
1352
+ `{ rowHeight, overscan }` (45px/36px according to density, three overscan rows by
1353
+ default). `scroll.y` supplies viewport height, otherwise 300px. Rich cells are
1354
+ clipped to this pitch; choose a sufficient height. Tree expansion, grouped
1355
+ headers, fixed columns and cursor navigation work within the window. The active
1356
+ row stays mounted outside the window until focus leaves, preserving editors and
1357
+ keyboard navigation. Non-finite dimensions fall back to valid defaults. Grouped
1358
+ bodies, detail expansion and any `onCell` use full rendering, preserving merged
1359
+ cell geometry. This is an intentional difference from AntD's measured virtual
1360
+ rows and merged-span repair. Arbitrary variable-height virtual content and its
1361
+ internal rc-table ref are not exposed. Klun keeps string keys, rows/rowKey,
1362
+ getChildren and its existing native table semantics.
1363
+
1364
+ ### DatePicker / DateRangePicker: periods, local time and typed formats
1365
+
1366
+ The existing ISO string/native input ref contract is retained. `picker` is date,
1367
+ week, month, quarter or year; emitted period values use the ISO Monday, first day
1368
+ of the month/quarter, or January 1. Native week input uses ISO week years (for
1369
+ example 2021-01-01 displays 2020-W53). Period bounds compare their period starts,
1370
+ so a mid-month min/max does not discard the entire boundary month. `disabledDate`
1371
+ receives the candidate ISO day/period and `{ type }`.
1372
+
1373
+ `showTime` (date mode only) emits local `YYYY-MM-DDTHH:mm[:ss]` without timezone
1374
+ conversion. Use `{ defaultValue: '09:00', step: 1 }` to supply the initial clock
1375
+ and expose seconds. `min`/`max` accept local datetimes; a plain-date maximum ends
1376
+ at 23:59:59. `disabledTime(value)` rejects the complete local datetime. Date
1377
+ availability is independent of clock availability: select the day, then adjust
1378
+ its clock. `needConfirm` defaults to true with time, false otherwise. Calendar
1379
+ choices remain provisional until Apply when enabled; Escape/reopen cancels the
1380
+ proposal. Native entry and preset commits are immediate. `onOk` reports Apply.
1381
+
1382
+ `multiple` changes value/defaultValue/onChange to `string[]`, toggles dates or
1383
+ periods, sorts and deduplicates the proposal, keeps the panel open, and renders
1384
+ controlled removable tags. Time is excluded from this mode. With needConfirm,
1385
+ panel selections are staged until Apply. The ref points to the entry input;
1386
+ `name` submits a hidden JSON array and required validation reads committed dates.
1387
+ An explicit disabled=false also enables nested month/time controls under a
1388
+ disabled ConfigProvider. readOnly blocks both entry and panel edits.
1389
+
1390
+ `presets` contain a ReactNode label and ISO value or lazy function, evaluated on
1391
+ click and checked against constraints. `open/defaultOpen/onOpenChange` control
1392
+ the popup; rejected closing retains the panel. `format` accepts strict token
1393
+ strings YYYY/MM/DD/HH/mm/ss/Q with literal separators, or an explicit
1394
+ `{ format(value), parse(text) }` pair. Invalid typed drafts do not emit and fail
1395
+ native validity; blur restores the committed display. Display format changes do
1396
+ not change ISO values. Locale affects panel labels and native controls; token
1397
+ parsing is explicit and locale-independent.
1398
+
1399
+ DateRangePicker keeps editable partial tuples and never silently swaps endpoints.
1400
+ Shared date/time bounds constrain both endpoints, including same-day clocks.
1401
+ Its disabledDate/disabledTime receive `{ from, range: 'start' | 'end' }`; preset
1402
+ pairs are strictly checked, normalized to the picker period, then atomically
1403
+ validated and committed. Range open is 'start', 'end' or null, with only one
1404
+ panel open at once. Range presets use tuple values/lazy functions.
1405
+
1406
+ These are Klun ISO values and native refs, rather than AntD Dayjs objects and
1407
+ picker handles. Year/quarter panels and strict formats cover the documented
1408
+ contract; decade drilldown, masked editing, arbitrary Dayjs tokens/locale parsing,
1409
+ millisecond/12-hour column panels and measured rc-picker internals are not exposed.
1410
+
1411
+ ### ConfigProvider: inherited themes and portalled controls
1412
+
1413
+ `theme="light" | "dark"` chooses a subtree theme; omission inherits the nearest
1414
+ provider, or the document theme when no provider selects one. `tokens` merges Klun
1415
+ CSS custom properties (`--accent`, `--fg`, etc.) with the parent. Undefined options
1416
+ and token values inherit, and explicit component `disabled={false}` enables a
1417
+ control inside a disabled provider. Dialog, Popover, Tooltip, Toast, ContextMenu,
1418
+ fullscreen Spin and AiPanel carry language/direction/theme/tokens into their body
1419
+ portals. Native browser controls retain the browser's own formatting and UI.
1420
+
1421
+ Pagination consumes Chinese/English text and mirrors navigation arrows. Select,
1422
+ MultiSelect and Cascader localize built-in entry/search/loading labels while
1423
+ preserving caller-provided content; DatePicker localizes calendar actions.
1424
+ DataTable consumes configuration for selection/sort/filter/paging and live status;
1425
+ its fixed left/right column runs mirror to leading/trailing edges in RTL, including
1426
+ loading rows. Host-rendered cell actions remain owned by the host. Text spacing,
1427
+ borders and edge affordances use logical CSS; chart coordinates and centered
1428
+ transforms retain physical geometry.
1429
+
1430
+ This configuration uses Klun CSS tokens, not AntD's CSS-in-JS token algorithms or
1431
+ AntD component configuration objects. It does not claim compatibility with AntD
1432
+ `theme.algorithm`, `prefixCls`, `getPopupContainer` or arbitrary locale objects.
1433
+
1434
+ ### Menu and ContextMenu: complete navigation contract
1435
+
1436
+ Menu supports controlled/default selectedKeys and openKeys, single/multiple
1437
+ selection, vertical/horizontal/inline modes, nested items, disabled branches and
1438
+ per-item callbacks. A standalone root has one Tab entry; commands use programmatic
1439
+ focus, arrow keys/Home/End/typeahead, and native Enter/Space activation. Inline
1440
+ navigation walks expanded descendants. Popup Escape/backward closes only the
1441
+ focused level and restores its trigger, including RTL and deeper nesting. Selecting
1442
+ a leaf dismisses the tree unless closeOnSelect=false (the default for multiple).
1443
+ When the focused command disappears or becomes disabled, focus moves to the first
1444
+ available command without stealing focus from an external control.
1445
+
1446
+ ContextMenu supports pointer/ContextMenu-key/Shift+F10 opening, typed context passed
1447
+ to commands, icon strips, nested menus and host cancellation through preventDefault
1448
+ in onOpen or the trigger handler. Internal menu scrolling preserves the menu;
1449
+ page scroll dismisses the pointer anchor. Long menus are bounded and scroll within
1450
+ the viewport, submenus flip to available space, and Escape/backward restores one
1451
+ level at a time. Omitted configuration inherits through all portal levels.
1452
+
1453
+ Klun uses string ids/items and command callbacks instead of AntD key/domEvent/
1454
+ keyPath info objects. Root menus remain visible components; a host owns overall
1455
+ opening via Popover/Dropdown. Search is a Klun level filter, not an AntD API.
1456
+ AntD Menu.Item/ItemGroup/children syntax, inlineCollapsed, overflow collection,
1457
+ hover-delay configuration and CSS-in-JS semantic customization are not replicated.
1458
+ ContextMenu is the Klun command surface corresponding to AntD Dropdown's
1459
+ contextMenu trigger; its hook/list APIs and quick strip remain Klun-specific.
1460
+
1461
+ ### Pagination: state and boundary behavior
1462
+
1463
+ `page`/`pageSize` are controlled when supplied, otherwise defaultPage/defaultPageSize
1464
+ initialize internal state. User changes notify onPageChange/onPageSizeChange and
1465
+ onChange with the proposed pair even if a controlled host refuses the update.
1466
+ Clicking the active page is a no-op; empty results show a disabled page 1 and do
1467
+ not notify. showTotal receives the normalized total and inclusive visible range
1468
+ (`[0, 0]` for empty results). Numeric boundaries are finite integer values capped
1469
+ at Number.MAX_SAFE_INTEGER; invalid pageSize falls back to 20, negative total to 0.
1470
+
1471
+ A shrinking total clamps the displayed page without a synthetic event. If results
1472
+ return, the originally requested page returns until the user changes it, matching
1473
+ the reference state model. Klun preserves its existing page-size contract: selecting
1474
+ a size resets the proposed page to 1 (AntD preserves/clamps the current page).
1475
+ Choices deduplicate and reject non-positive or unsafe integer values. The uncontrolled
1476
+ size menu discards open state when disabled/hidden. The numeric quick jumper commits
1477
+ on Enter, clamps to the available range, discards its draft on blur and leaves IME
1478
+ Enter untouched. hideOnSinglePage, showQuickJumper/showSizeChanger and host slots
1479
+ remain independent. Chinese/English labels and RTL arrows inherit configuration;
1480
+ the existing footer layout wraps for narrow viewports.
1481
+
1482
+ The API keeps page/onPageChange rather than AntD current/onShowSizeChange. It does
1483
+ not add AntD simple mode, prev/next/jump itemRender or arbitrary native prop forwarding.
1484
+
1485
+ ### Card: composition and narrow content
1486
+
1487
+ Card retains its section/flush API and forwards native attributes/style/ref.
1488
+ size="small", type="inner" and variant (taking precedence over bordered) select
1489
+ compact/inner/borderless layouts. Loading hides the pending body and localizes
1490
+ its status label, while cover/actions remain available. Card.Grid/Card.Meta are
1491
+ also exported as CardGrid/CardMeta, with native div attributes/ref. Grid defaults
1492
+ to three columns and one column below 480px; host width styles override defaults.
1493
+ Titles, metadata and action slots wrap inside their available width. Existing
1494
+ Tabs can be composed in extra/body rather than adding Card tabList aliases.
1495
+ Klun does not implement AntD prefixCls/semantic style maps or its breakpoint grid
1496
+ system inside Card; use the library Grid for custom responsive columns.
1497
+
1498
+ ### Tag and Divider: native composition
1499
+
1500
+ Tag forwards native span attributes/ref and lets host style override its CSS
1501
+ color. icon accepts existing Icon names or ReactNode. Close stops propagation
1502
+ before onClose; preventDefault retains the tag and the check state. Check/close
1503
+ use separate native buttons, so Enter/Space and disabled semantics work without
1504
+ nested controls. checked is controlled; defaultChecked initializes local state.
1505
+ Close labels consume locale; disabled=false overrides provider disabled. Long
1506
+ labels truncate inside the available width while preserving their full accessible
1507
+ text (supply title for a pointer tooltip). Klun retains CSS color/variants and
1508
+ pressed-button semantics rather than AntD preset color maps/CheckableTag aliases.
1509
+
1510
+ Divider forwards native div attributes/style/ref. orientation remains
1511
+ horizontal/vertical; titlePlacement start/center/end follows inherited direction.
1512
+ variant solid/dashed/dotted takes precedence over the legacy dashed flag. Host
1513
+ style controls spacing; captions wrap on narrow screens, and vertical captions
1514
+ are ignored. Klun keeps logical placement and existing margin geometry rather
1515
+ than AntD physical left/right aliases, semantic style maps or responsive sizes.
1516
+
1517
+ ### Segment: focus and dynamic data
1518
+
1519
+ Segment retains string-valued options and group/pressed-button semantics. There
1520
+ is one tab stop among enabled options. Horizontal arrows follow provider RTL;
1521
+ vertical arrows use Up/Down; Home/End skip disabled options. Re-selecting the
1522
+ current value sends no change. Missing selected values remain host-owned, with
1523
+ a first-enabled keyboard entry; removing/disabling the focused option restores
1524
+ owned focus without stealing external focus. IME and canceled native key events
1525
+ are ignored. Empty/all-disabled groups have a focusable root. Native div
1526
+ attributes/ref/style, ReactNode icon/label and block layout are supported; label
1527
+ content must be noninteractive. Chip forwards native button/ref and consumes
1528
+ global disabled with explicit overrides. Default View labels consume locale.
1529
+ Klun keeps its string values/button markup rather than AntD radio/name form
1530
+ submission, primitive/number option shorthand, size/shape or semantic aliases.
1531
+
1532
+ ### Nav / Sidebar: host-owned navigation and shell state
1533
+
1534
+ Nav keeps route value/onChange controlled; current clicks send no change.
1535
+ Arrow/Home/End navigation starts from the focused destination even when the host
1536
+ refuses a route change. More and portaled menus keep their own keyboard behavior;
1537
+ disabling Nav/removing overflow discards pending open state. Item context commands
1538
+ inherit item/Nav disabled, including explicit false under disabled provider.
1539
+ Rich-label command names use their text. Missing routes do not auto-commit;
1540
+ removing/disabling a focused destination restores owned focus. Ink tracks both
1541
+ strip and destination resizes. Nav native attributes/style/ref are forwarded;
1542
+ internal built-in labels consume locale. It is page navigation (aria-current),
1543
+ not a Tabs tabpanel controller or an automatic horizontal overflow collector.
1544
+
1545
+ Sidebar forwards aside attributes/style/ref; shell provider collapse can be
1546
+ explicitly overridden. Collapsed content stays mounted but inert/aria-hidden.
1547
+ Configured finite min/max bounds constrain pointer and keyboard proposals;
1548
+ controlled hosts may refuse them. Only primary-pointer drags resize; lost
1549
+ capture/cancel ends a drag. RTL reverses drag/arrow direction; Home/End reach
1550
+ bounds, composing/modified keys are ignored. Categories/resize labels consume
1551
+ locale. Existing CSS hides Sidebar at <=1024px; shell owns the external collapse
1552
+ control/focus and mobile navigation. Nested Menu provides navigation expansion.
1553
+ Klun retains 300px default and zero-width collapse instead of AntD Sider's
1554
+ breakpoint callbacks, built-in trigger/80px rail, theme and collapsed state aliases.
1555
+
1556
+ ### States and general feedback: mapping and boundaries
1557
+
1558
+ EmptyState is the illustrated empty/offline explanation; ErrorState is the
1559
+ existing alert with optional host-owned retry; Empty is the general image/
1560
+ description/action entry; Result displays status/exception outcomes without
1561
+ an automatic live alert. LoadingBlock/InlineSpinner/Spin announce loading;
1562
+ Skeleton/TableSkeleton are decorative. Default state/retry/loading/progress
1563
+ names consume locale; explicit host content is preserved. Empty/Spin/Result
1564
+ forward native div refs and attributes. Spin delay changes restart the wait,
1565
+ cancel/unmount clear timers, nested content becomes inert only while shown, and
1566
+ fullscreen uses the configured body portal. It is a loading overlay, not a
1567
+ modal focus trap or a replacement for host request cancellation.
1568
+
1569
+ Primitive Skeleton defaults to 12px height and sanitizes nonfinite geometry;
1570
+ compound placeholders retain avatar/title/paragraph/loading/active behavior.
1571
+ Skeleton/TableSkeleton rows and Progress visual steps cap at 1000 to prevent
1572
+ unbounded DOM allocation; use virtual data lists/continuous progress above that.
1573
+ Progress retains the 0–1 value contract (precedence over percent), line/circle/
1574
+ dashboard, bounded success, RTL logical gap placement, explicit formatting and
1575
+ accessible values. format returning null hides info, and label=0 stays visible.
1576
+ Step gaps shrink to fit narrow containers. State descriptions/actions wrap;
1577
+ table placeholders scroll locally. Existing Klun art/status geometry remains.
1578
+ AntD semantic style maps, compound Skeleton element aliases, Spin percent=auto/
1579
+ global indicator and gradient/array Progress colors remain intentional differences.
1580
+
1581
+ ### Icon: names, fallback and meaning
1582
+
1583
+ Icon resolves only own glyph definitions; prototype-like names safely use the
1584
+ same empty box as unknown names and warn. The fallback preserves a supplied
1585
+ label/aria-label. Decorative icons remain aria-hidden; meaningful spans use
1586
+ role=img and their SVG stays nonfocusable/hidden. Native span attributes/ref and
1587
+ host styles are forwarded. Numeric sizes include zero, negative values clamp to
1588
+ zero and nonfinite values use CSS defaults; CSS dimensions remain supported.
1589
+ Klun retains the PingCode glyph catalog rather than importing AntD icon exports,
1590
+ two-tone colors, icon-font scripts or component/viewBox aliases. Use a real Button
1591
+ or ActionButton for interactive actions rather than relying on an icon alone.
1592
+
1593
+ ### Charts: finite data, empty states and descriptions
1594
+
1595
+ AntD core has no equivalent chart family; Klun preserves its bar, CSS donut,
1596
+ SVG trend and positioned score/effort matrix, composing the existing Card/Avatar.
1597
+ Bars/donuts treat negative or nonfinite amounts as zero; zero bars have no height.
1598
+ Donut totals scale before summing so very large finite data retains proportions;
1599
+ empty/zero distributions use a neutral rail. Sparkline preserves finite signed
1600
+ values, scales before measuring its range, uses zero for nonfinite samples and
1601
+ shows a dot for one sample. Invalid/nonpositive dimensions use defaults.
1602
+ Scatter drops nonfinite coordinates and clamps negative score/effort positions
1603
+ to its nonnegative origin; math axes stay physical in RTL. Empty charts have
1604
+ localized feedback; explicit emptyState and accessible names are retained.
1605
+ Bar/Sparkline provide data descriptions; Donut keeps its data name (or description
1606
+ when a host name is supplied); points expose coordinate descriptions and consume
1607
+ provider disabled, with explicit overrides. Duplicate category names are allowed;
1608
+ point IDs remain unique host keys. Rings retain aspect ratio and legends wrap,
1609
+ long labels truncate, and chart overflow stays local on narrow screens. These
1610
+ are lightweight displays, not Ant Design Charts axis/tooltip/zoom replacements.
1611
+
1612
+ ### SelectionBar batch contract
1613
+
1614
+ SelectionBar composes Checkbox/Menu/Popover; AntD 6.6.5 core has no matching bulk strip. The reference is `table/hooks/useSelection.tsx` for selection ownership and Menu/Popover for command interaction. Preserve the Klun table-header-height overlay (`--table-head-height`, currently 44px) and use `overlay={false}` for a wrapping board strip. Excess overlay commands scroll locally.
1615
+
1616
+ Counts are finite nonnegative integers; zero selections disable commands, while Cancel and select-all remain available. `disabled` consumes ConfigProvider with explicit false overriding it; each action's disabled remains authoritative. Default count, Cancel, More and select-all labels consume locale. Only the count has live status semantics; interactive commands remain exposed in a labelled group.
1617
+
1618
+ `loading` locks selection and all commands for host-owned work. Commands returning a Promise are automatically locked, expose aria-busy and prevent repeat clicks until settlement. Throws/rejections retain the selection, show a retryable error and invoke `onError`; the next command clears the error. Menus close when disabled/loading or removed and do not reopen after unlock. Focus owned by an inline command or its menu trigger is preserved across pending work; unrelated external focus is left alone.
1619
+
1620
+ `BulkActions.apply` accepts void or Promise<void>. Standard status/baseline/schedule and round-robin commands notify and clear only after success. Round-robin waits for every started write, including rejected writes; empty people disable distribution. Hosts own request cancellation, authorization, stale selection reconciliation, error details and transactional rollback for partially successful distribution. Extra/More commands retain host-owned notifications and clearing. No AntD semantic style/prefixCls API or server mutation layer is introduced.
1621
+
1622
+ ### Inline editor contracts
1623
+
1624
+ PeoplePicker/QuickSelect are Klun cell controls composed from Select-style listbox/search behavior, Avatar, Tag and Popover; compare AntD 6.6.5 `components/select/index.tsx` and `@rc-component/select@1.10.1` `Select.js`/`OptionList.js`. QuickText maps to `components/typography/Editable.tsx` while retaining a single-line cell input, untrimmed commit values and the 220ms click/double-click distinction. No new runtime dependency or AntD API aliases are introduced.
1625
+
1626
+ All three consume ConfigProvider disabled with explicit false override and preserve host-owned controlled values, including unknown choices. Default text consumes locale; custom labels stay intact. QuickSelect and QuickText additionally support readOnly. ListWorkbench forwards the field configurations: TextField disabled/readOnly/validate(row,next)/onError(row,error), EnumField disabled/readOnly, and AssigneeField disabled/loading/error/onRetry. Workbench assignee triggers honor the same resolved disabled state as their picker.
1627
+
1628
+ QuickText offers a keyboard entry even without an interactive display node. Enter/F2/Space opens, Enter/blur commits once, Escape cancels; IME and modified commit keys do not save/cancel. `validate(next)` returns an accessible error and keeps the draft; synchronous write failures show retry feedback and call onError. Successful keyboard completion restores its own display focus, external blur retains external focus. Empty nonnullable drafts and unchanged writes keep the existing behavior; invalid maxLength is ignored, finite nonnegative lengths use native UTF-16 counting. Mid-edit external values do not replace the draft; disabling/readOnly cancels without a write. QuickText validation/commit are synchronous; hosts manage async writes and reconciliation.
1629
+
1630
+ QuickSelect skips disabled options for keyboard/pointer picks, suppresses unchanged writes, retains typed search on keyboard opening, scrolls the active option and keeps combobox focus. Unknown values remain visible. Clear is a separate command row, so a real `\u0000clear` value is safe; clearing still returns the existing empty-string API. There is no pending multi-value confirmation mode: a pick commits immediately and Escape/outside dismisses without a write. Hosts keep option values unique and provide immutable lists.
1631
+
1632
+ PeoplePicker supports disabled people/sources, fallback when a tab disappears, RTL tab navigation and real row focus for Arrow/Home/End. Unknown assigned IDs remain unselected, and `__unassigned__` can be a real ID. Loading and error are host-owned; cached rows are protected during them, onRetry requests a host retry, and async results recover owned removed/disabled focus without moving external focus. It is a single-select picker; Unassigned returns null, unchanged picks close without redundant writes. Requests, authorization, caching and cancellation belong to the host.
1633
+
1634
+ ### AiPanel interaction contract
1635
+
1636
+ AntD 6.6.5 core has no AI panel; map its layer to `components/modal/Modal.tsx` and `@rc-component/dialog@1.10.0` `Dialog/index.js`, and compose a native multiline input. AiPanel and Dialog now share the existing focus/scroll stack with ordered modal z-index, topmost-only Escape, hidden/inert focus filtering and opener restoration. Callback changes do not refocus the composer. Preserve the Klun floating panel/brand geometry, with viewport-bounded width/height, logical RTL placement and reduced-motion styling.
1637
+
1638
+ `onSubmit(text)` receives trimmed plain text and may return void or Promise<void>. Host pending or a returned Promise locks sending, suggestions/chips/history/attachment; the composer stays readable while pending. Throws/rejections keep the draft, show a local error and call onError. `error` and `onRetry` also support host-owned failures; retry uses the host callback when present, otherwise resubmits the retained draft. Successful submission clears the draft; stale completions after close/reopen/unmount cannot modify the new session. Close remains available while pending; hosts own aborting requests.
1639
+
1640
+ Enter sends, Shift+Enter inserts a newline, IME/modified keys do not submit or dismiss. Default greeting/control/status text consumes locale, disabled consumes ConfigProvider with explicit false override, and existing portal theme/tokens remain intact. Body scrolling follows new or streamed text only while the reader remains near the end. The log exposes only host-supplied plain-text messages; pending/error feedback remains separately announced. History/attachment invoke host callbacks and are unavailable when no callback exists. No model, network, upload, Markdown execution, transcript persistence or automatic assistant reply is introduced; host controls messages, request cancellation and business authorization.
1641
+
1642
+ Typography interaction alignment: editor composition, key229, modified and repeated keys
1643
+ cannot submit/cancel. Finishing restores only owned focus; external focus and child updates
1644
+ are preserved. Controls follow locale; native textarea draft/Shift+Enter and synchronous
1645
+ host save callbacks retain the existing contract. Async copy text stops before clipboard
1646
+ write after unmount/disable, serializes one attempt and reports failure with retry. Once a
1647
+ native clipboard write has begun, it cannot be canceled. CSS ellipsis remains line-clamp
1648
+ without AntD text measurement/suffix/tooltip algorithms; no typography autosize/onBlur-save,
1649
+ copy fallback API or Promise-based edit validation is added. Title runtime levels stay1–5.
1650
+
1651
+ ### Ship chrome 与阅读/编辑组合契约
1652
+
1653
+ 这些业务模块没有 AntD 核心库的一对一组件,按组合行为对照固定 6.6.5 的 Layout/Menu/Tabs/Input/Typography/Empty/Modal/Progress,并复用本库已验收的 Nav、Menu、Popover、Input、Dialog、Checkbox 和 Progress;不引入业务路由、存储或请求依赖。
1654
+
1655
+ | 模块 | 已支持能力 | 有意保留的边界 |
1656
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
1657
+ | Rail | 受控 activeId、主/底部按钮、aria-current、中文导航名、provider 禁用及 item.disabled 显式覆盖 | 路由由 onClick 宿主实现;不模拟 Menu 的选择模型 |
1658
+ | Header | 受控 search/tabs、搜索 ref、产品切换、账户/通知/Ping/收藏/sidebar 操作、IME/修饰/重复 Enter 保护,禁用和中文名称 | actions 是宿主 ReactNode;快捷键全局注册和通知加载由宿主实现 |
1659
+ | Toolbar | 受控搜索/rules/sort/group、builder/菜单/可移除chip、零值、空schema安全、切字段重置operator、动态禁用关闭popup | operators/freeText 是宿主声明;不执行查询/校验业务筛选语义,不提供异步加载API |
1660
+ | Bits | 有限size/未知类型fallback、title F2或双击编辑、IME保护/trim/空值保留/恢复焦点、收藏/快捷入口/提示条、provider 禁用 | title同步onEdit,空标题不提交;统计数据/权限和富suffix由宿主提供 |
1661
+ | Views | Board/quarter Roadmap/DocSplit 受控阅读数据、中文空状态、内部滚动/移动布局;零period不生成非法CSS,无动作卡片不占tab位置 | 无看板拖放/卡片提交/请求;RoadmapView按顺序周期分配,不替代有日期的 RoadmapEditor |
1662
+ | ShellSurfaces | AccountMenu复用Menu;Shortcuts/QuickStart复用Dialog;受控checked/toggle/go/reset、实际checkbox下一值、中文labels、0 intro | 身份、快捷键注册、权限/导航/持久化由host提供;没有请求的空状态是实际空列表 |
1663
+ | DetailBlock | 可省略/零标题actions、原生section/h3;DetailTool原生按钮provider禁用 | host自定义actions自行处理禁用/权限 |
1664
+ | EditableText | text/editSeed、受控/默认editing与draft、readOnly/disabled显式覆盖、中文save/cancel、editor及触发器焦点、paragraph/note | 同步onSave;空draft重提交原文兼容防误删,Promise提交/失败/授权由host封装,不假报异步成功 |
1665
+
1666
+ 窄屏 Header/Toolbar换行,Board/Roadmap横向滚动限定在本身,DocSplit上下排布;保留既有主题tokens与API。组合验收示例见 Ship/Alignment/Chrome,回归见 ShipChromeAlignment.test.tsx。
1667
+
1668
+ ### Ship 详情、评论与附件组合契约
1669
+
1670
+ 对照固定 AntD 6.6.5 ActionButton 的异步提交锁、Input/TextArea、Modal、Upload 和 Timeline/Steps 的组合行为,业务模块继续采用本库组件,不冒充 AntD 的业务扩展。
1671
+
1672
+ | 模块 | 行为 | 保留差异 |
1673
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
1674
+ | DetailSheet / CommentComposer | 受控详情/分区/字段/过滤,零内容;provider 禁用、中文操作名称;IME 安全发送,Promise 待定锁,失败保留草稿并提示,false 不清空,成功仅清空提交时草稿;关闭/禁用隔离旧结果,恢复本组件拥有的焦点 | Pin 暂无宿主回调而禁用;加粗/代码是展示工具;附件、引用、授权和业务请求由宿主实现 |
1675
+ | CommentThread | 受控评论/回复/编辑/删除/反应;Promise 提交防重复,失败保留草稿并可重试;确认删除,剪贴板结果提示;孤儿和循环父链有限遍历,RTL 缩进 | 评论 ID 必须唯一;实体切换由宿主重新挂载;无虚拟列表或自动网络请求,部分宿主文本/时间格式保留原文 |
1676
+ | AttachmentUpload | 文件选择/拖入、读取前大小限制、真实 FileReader 读取、有限进度、读取后最新回调;Promise 失败提示,计时器/reader 卸载清理;provider 禁用,预览/删除确认/复制真实结果 | onUpload 仍收到 name/size 元数据,不传送或保存文件字节;读取与进度不是服务器上传。需要真实传输时使用 Upload 或宿主传输层 |
1677
+ | LinkPicker | 受控关系、按类型候选/搜索/已关联状态,候选移除后禁止提交,类型失效重置,provider 禁用也保护自定义 opener,卸载关联确认 | onLink/onUnlink 为同步宿主提交;候选加载、错误和业务授权由宿主管理;不凭空查询远端实体 |
1678
+ | FilePreview | 受控元数据预览,按文件身份重置缩放,RTL 缩放原点;下载生成与当前展示一致的示意内容,CSV 防公式并正确转义,URL 延后释放 | 没有原始文件字节/鉴权地址,预览和下载明确为示意,不表示原始附件下载成功;下载提示仅表示交给浏览器 |
1679
+ | TransitionsTimeline | 受控活动/当前状态/工作流标签、空状态、本地化和 RTL;排除空/重复状态,安全处理状态 ID | 只读展示,保留既有状态词汇和摘要识别规则;不执行工作流、不持久化、不替代通用 Timeline |
1680
+
1681
+ 验收示例 Ship/Alignment/Detail;回归 ShipDetailAlignment.test.tsx。无需新增依赖。
1682
+
1683
+ ### Ship 实体与分享弹窗契约
1684
+
1685
+ 对照固定 AntD6.6.5 Modal/ActionButton、Form(实际 form1.8.6)以及本库 Dialog、Field、Select、DataTable、Tree、Menu;复用宿主受控协议,不创建请求或权限系统。
1686
+
1687
+ | 模块 | 已支持行为 | 边界 |
1688
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1689
+ | EntityDialog | 字段/侧栏/初始值、blur 与提交必填校验、唯一 label ID、零计数、prototype 字段安全;打开捕获基线,不因 inline fields 改变清空草稿;关闭重开重置;Promise 提交锁/失败提示/retry/继续创建;禁用与关闭隔离旧结果 | 不再人工延时;宿主 onSubmit 负责真实提交,工具节点由宿主管理;附件/工单尚无 host callback 的按钮明确禁用;无富文本、远端字段校验、通用 Form 实例 |
1690
+ | EntityPicker | list/table、scope/search/filter/sort、受控选择与差异计数、去重,保留跨页 ID;provider 禁用不能被非零 counts 覆盖;Promise confirm 锁/错误/retry,原标签缺失时 ID 提供 accessible name | rows 和分页候选由 host 提供;本身不因当前页缺少 ID 删除选择;成功是否关闭由 onConfirm 宿主决定,toolbar 需要 search 配置;无服务端查询/授权 |
1691
+ | ShareDialog | 打开/报表切换重置草稿,分享范围/expiry/schedule/hour/recipients/format;定时发送必须有效非空收件人且 hour 为6–20整数;Promise save/copy 锁和失败重试,旧会话结果忽略,复制成功才 Copied | link 必须来自 host,空 link 不伪造示例地址;onCopy 缺失或空 link 禁用复制;保留 onChange→onSave 顺序,onChange 收到尝试值,持久化失败时 host 负责回滚外部状态;UI 不执行后台导出或授予实际访问权 |
1692
+
1693
+ 中文 RTL、空链接及失败重试故事见 Ship/Alignment/Entity,回归 ShipEntityAlignment.test.tsx;选项值是既有英文枚举,宿主业务文本不自动翻译。
1694
+
1695
+ ### Ship 画布、路线图、工作台与项目向导契约
1696
+
1697
+ 没有 AntD 核心库对应的业务编辑器。按固定6.6.5 Card/Layout、Table/InternalTable 与实际 table1.11.1、Timeline/Steps 与实际 steps1.2.3、Form 与实际 form1.8.6、Modal/ActionButton,以及本库的已验收组合行为审查。
1698
+
1699
+ | 模块 | 已支持行为 | 有意保留的边界 |
1700
+ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1701
+ | ReportCanvas | 受控 widgets、5种内置主体、宿主 dataset/renderBody/actions、12列宽度、按钮与自身拖动排序、删除确认/新增/公告;provider禁用/loading保护自定义 opener和写事件;自身handle与active drag ID验证,禁用旧drag失效;IME安全新增、unique label ID、非法span回退6,窄屏全宽卡片 | 无持久化/请求、跨画布拖放、布局碰撞算法;缺少有效 chartTypes 时回退现有五类。自定义 body/controls由host管理;onChange同步,host失败状态由host展示,未假报服务器保存 |
1702
+ | RoadmapEditor | 受控 lane/period/bar、rename/recolour/move/add/delete、按钮路径/自身drag、状态公告、weekends/zoom/fit;每个period独立year,quarter参数整数/日期范围保护及year0正确;空lane/period禁止创建、动态候选校验;provider禁用/RTL箭头/IME、unique IDs | quarter/month是既有显示粒度,不生成真实月级日期跨度;未知lane过滤,host IDs/periods须唯一;onChange/onRename同步,不运行排程或持久化。时间线横向滚动在本身;只提供已声明颜色,不创建存储 |
1703
+ | ListWorkbench | 受控 view/selection,过滤/排序/分组/table/board/paging/列设置、行与批量菜单、表格inline editors;同tick patches合并、最新callback、卸载/identity/禁用取消旧patch、finite page/size、原型字段安全;board也遵守loading/error/empty;Promise create/edit/bulk-properties/import/delete与真实clipboard结果,失败保留选择/草稿并提示,等待期间新选择保留;CSV引号/转义/多行/CRLF/BOM解析,拒绝坏header/列数/引号,导出公式保护与延迟URL释放 | 全量客户端rows过滤分页,无服务端查询/授权/路由/持久化;CSV仅平坦primitive,host自定义onExport负责自身内容安全;import读取完成用最新callback,卸载不启动写,已开始host请求不能自动撤销;onChange类view/inline协议保持同步,其他bulk能力沿SelectionBar协议;identity变化重置本地会话,entity切换host须正确设置identity |
1704
+ | ProjectWizard | 三步type/details/members、radio键盘/RTL/IME保护、step focus/公告、脏草稿退出确认、唯一field IDs;挂载捕获baseline不被inline defaults清空;empty catalogue与name256/key15/enum/candidate校验;owner包含且不可移除、成员去重、过期roster选择拒绝;Promise提交锁/error/retry/禁用与卸载旧结果隔离 | open由host通过mount/unmount管理,每次重开需重新挂载,owner/initialdefaults为会话配置;member协议仍name,重名唯一性由host负责;onSubmit成功的关闭由host决定;无创建网络、邀请、身份或真正权限授予 |
1705
+
1706
+ 中文 RTL及loading/error/empty/disabled示例见 Ship/Alignment/Workbench;部分既有领域枚举、菜单、提示和统计说明仍为英文,宿主内容不自动翻译。回归 ShipWorkbenchAlignment.test.tsx。画布和路线图文案只声明应用更改,不再虚构 browser/localStorage 保存。