@eifi1/ui-kit 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/README.md +82 -38
  2. package/dist/billing/billing-banner.d.ts +5 -2
  3. package/dist/billing/billing-banner.js.map +1 -1
  4. package/dist/billing/plan-limit-notice.d.ts +5 -2
  5. package/dist/billing/plan-limit-notice.js.map +1 -1
  6. package/dist/chart.js.map +1 -1
  7. package/dist/components/amount-input.d.ts +0 -3
  8. package/dist/components/amount-input.js +2 -2
  9. package/dist/components/amount-input.js.map +1 -1
  10. package/dist/components/calculator.d.ts +1 -4
  11. package/dist/components/calculator.js +1 -3
  12. package/dist/components/calculator.js.map +1 -1
  13. package/dist/components/chart-zoom.d.ts +0 -37
  14. package/dist/components/chart-zoom.js +1 -28
  15. package/dist/components/chart-zoom.js.map +1 -1
  16. package/dist/components/chip-input.d.ts +0 -4
  17. package/dist/components/chip-input.js +1 -5
  18. package/dist/components/chip-input.js.map +1 -1
  19. package/dist/components/combobox.d.ts +1 -1
  20. package/dist/components/combobox.js.map +1 -1
  21. package/dist/components/copy-button.d.ts +5 -1
  22. package/dist/components/copy-button.js.map +1 -1
  23. package/dist/components/currency-option-list.d.ts +3 -8
  24. package/dist/components/currency-option-list.js +1 -29
  25. package/dist/components/currency-option-list.js.map +1 -1
  26. package/dist/components/danger-confirm.d.ts +3 -3
  27. package/dist/components/danger-confirm.js.map +1 -1
  28. package/dist/components/data-table-filter-popover.d.ts +1 -1
  29. package/dist/components/data-table-filter-popover.js +1 -1
  30. package/dist/components/data-table-filter-popover.js.map +1 -1
  31. package/dist/components/data-table-labels.d.ts +1 -42
  32. package/dist/components/data-table-labels.js +2 -31
  33. package/dist/components/data-table-labels.js.map +1 -1
  34. package/dist/components/data-table-shared.d.ts +17 -1
  35. package/dist/components/data-table-shared.js +22 -0
  36. package/dist/components/data-table-shared.js.map +1 -1
  37. package/dist/components/data-table.d.ts +12 -2
  38. package/dist/components/data-table.js +18 -14
  39. package/dist/components/data-table.js.map +1 -1
  40. package/dist/components/dialog-frame.d.ts +3 -1
  41. package/dist/components/dialog-frame.js.map +1 -1
  42. package/dist/components/dropdown.d.ts +18 -15
  43. package/dist/components/dropdown.js +11 -3
  44. package/dist/components/dropdown.js.map +1 -1
  45. package/dist/components/entity-combobox.d.ts +1 -1
  46. package/dist/components/entity-combobox.js.map +1 -1
  47. package/dist/components/field-base.d.ts +0 -5
  48. package/dist/components/field-base.js +0 -5
  49. package/dist/components/field-base.js.map +1 -1
  50. package/dist/components/field-parts.d.ts +3 -11
  51. package/dist/components/field-parts.js +1 -9
  52. package/dist/components/field-parts.js.map +1 -1
  53. package/dist/components/field-strip.d.ts +8 -23
  54. package/dist/components/field-strip.js +24 -22
  55. package/dist/components/field-strip.js.map +1 -1
  56. package/dist/components/field-sync.d.ts +0 -4
  57. package/dist/components/field-sync.js +0 -4
  58. package/dist/components/field-sync.js.map +1 -1
  59. package/dist/components/field.d.ts +4 -4
  60. package/dist/components/field.js.map +1 -1
  61. package/dist/components/full-bleed-dialog.js +2 -2
  62. package/dist/components/full-bleed-dialog.js.map +1 -1
  63. package/dist/components/hover-menu.d.ts +6 -13
  64. package/dist/components/hover-menu.js +3 -3
  65. package/dist/components/hover-menu.js.map +1 -1
  66. package/dist/components/inline-entity-combobox.d.ts +1 -1
  67. package/dist/components/inline-entity-combobox.js.map +1 -1
  68. package/dist/components/modal.d.ts +9 -1
  69. package/dist/components/modal.js +7 -4
  70. package/dist/components/modal.js.map +1 -1
  71. package/dist/components/multi-select.d.ts +5 -16
  72. package/dist/components/multi-select.js +5 -10
  73. package/dist/components/multi-select.js.map +1 -1
  74. package/dist/components/number-input.d.ts +0 -3
  75. package/dist/components/number-input.js +2 -4
  76. package/dist/components/number-input.js.map +1 -1
  77. package/dist/components/popover.js +2 -2
  78. package/dist/components/popover.js.map +1 -1
  79. package/dist/components/series-chart.js.map +1 -1
  80. package/dist/components/server-wake.d.ts +0 -6
  81. package/dist/components/server-wake.js.map +1 -1
  82. package/dist/components/settings-fields.d.ts +4 -2
  83. package/dist/components/settings-fields.js.map +1 -1
  84. package/dist/components/stat-tile.d.ts +8 -2
  85. package/dist/components/stat-tile.js.map +1 -1
  86. package/dist/components/swipeable-row.d.ts +2 -17
  87. package/dist/components/swipeable-row.js +11 -16
  88. package/dist/components/swipeable-row.js.map +1 -1
  89. package/dist/components/tooltip.d.ts +9 -1
  90. package/dist/components/tooltip.js +20 -5
  91. package/dist/components/tooltip.js.map +1 -1
  92. package/dist/components/translation-review.d.ts +10 -2
  93. package/dist/components/translation-review.js +11 -3
  94. package/dist/components/translation-review.js.map +1 -1
  95. package/dist/components/use-table-state.d.ts +31 -3
  96. package/dist/components/use-table-state.js +53 -8
  97. package/dist/components/use-table-state.js.map +1 -1
  98. package/dist/components/user-avatar.d.ts +2 -2
  99. package/dist/components/user-avatar.js.map +1 -1
  100. package/dist/components/wizard-stepper.d.ts +1 -4
  101. package/dist/components/wizard-stepper.js +2 -3
  102. package/dist/components/wizard-stepper.js.map +1 -1
  103. package/dist/currencies.d.ts +23 -0
  104. package/dist/currencies.js +38 -0
  105. package/dist/currencies.js.map +1 -0
  106. package/dist/demo/demo-banner.d.ts +7 -2
  107. package/dist/demo/demo-banner.js.map +1 -1
  108. package/dist/eslint/index.d.ts +83 -0
  109. package/dist/eslint/index.js +69 -0
  110. package/dist/eslint/index.js.map +1 -0
  111. package/dist/eslint/role-utilities.d.ts +48 -0
  112. package/dist/eslint/role-utilities.generated.d.ts +2 -0
  113. package/dist/eslint/role-utilities.generated.js +143 -0
  114. package/dist/eslint/role-utilities.generated.js.map +1 -0
  115. package/dist/eslint/role-utilities.js +43 -0
  116. package/dist/eslint/role-utilities.js.map +1 -0
  117. package/dist/feedback/feedback-menu.d.ts +2 -2
  118. package/dist/feedback/feedback-menu.js.map +1 -1
  119. package/dist/feedback/feedback-thread.d.ts +3 -8
  120. package/dist/feedback/feedback-thread.js +0 -2
  121. package/dist/feedback/feedback-thread.js.map +1 -1
  122. package/dist/hooks/use-dismiss.d.ts +16 -3
  123. package/dist/hooks/use-dismiss.js +14 -2
  124. package/dist/hooks/use-dismiss.js.map +1 -1
  125. package/dist/i18n/kit-labels.d.ts +14 -4
  126. package/dist/i18n/kit-labels.js.map +1 -1
  127. package/dist/i18n/locales/en.js +1 -1
  128. package/dist/i18n/locales/en.js.map +2 -2
  129. package/dist/i18n/locales/fr.js +3 -2
  130. package/dist/i18n/locales/fr.js.map +1 -1
  131. package/dist/i18n/locales/it.js +3 -2
  132. package/dist/i18n/locales/it.js.map +1 -1
  133. package/dist/index.d.ts +2 -1
  134. package/dist/index.js +2 -2
  135. package/dist/index.js.map +1 -1
  136. package/dist/lib/refs.d.ts +13 -0
  137. package/dist/lib/refs.js +15 -0
  138. package/dist/lib/refs.js.map +1 -0
  139. package/dist/rhf/fields.d.ts +10 -10
  140. package/dist/rhf/fields.js.map +1 -1
  141. package/dist/rhf/form-core.d.ts +5 -4
  142. package/dist/rhf/form-core.js.map +1 -1
  143. package/dist/rhf/form.d.ts +7 -36
  144. package/dist/rhf/form.js +1 -16
  145. package/dist/rhf/form.js.map +1 -1
  146. package/dist/rhf/line-items.d.ts +1 -1
  147. package/dist/rhf/line-items.js.map +1 -1
  148. package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
  149. package/dist/rhf.js.map +1 -1
  150. package/dist/shell/option-switcher-menu.d.ts +5 -17
  151. package/dist/shell/option-switcher-menu.js +1 -3
  152. package/dist/shell/option-switcher-menu.js.map +1 -1
  153. package/dist/shell/role-switcher.d.ts +1 -4
  154. package/dist/shell/role-switcher.js +2 -3
  155. package/dist/shell/role-switcher.js.map +1 -1
  156. package/dist/shell/topbar-action-menu.d.ts +7 -19
  157. package/dist/shell/topbar-action-menu.js +1 -3
  158. package/dist/shell/topbar-action-menu.js.map +1 -1
  159. package/dist/shell/topbar-controls.d.ts +3 -12
  160. package/dist/shell/topbar-controls.js +6 -9
  161. package/dist/shell/topbar-controls.js.map +1 -1
  162. package/dist/testing/index.d.ts +1 -1
  163. package/dist/testing/index.js +2 -0
  164. package/dist/testing/index.js.map +1 -1
  165. package/dist/testing/viewport.d.ts +25 -2
  166. package/dist/testing/viewport.js +61 -10
  167. package/dist/testing/viewport.js.map +1 -1
  168. package/dist/theme/palette-presets.d.ts +0 -7
  169. package/dist/theme/palette-presets.js +0 -279
  170. package/dist/theme/palette-presets.js.map +1 -1
  171. package/dist/wizard/types.d.ts +0 -5
  172. package/dist/wizard/types.js +1 -5
  173. package/dist/wizard/types.js.map +1 -1
  174. package/package.json +9 -1
  175. package/src/billing/billing-banner.tsx +5 -2
  176. package/src/billing/plan-limit-notice.tsx +5 -2
  177. package/src/chart.ts +5 -3
  178. package/src/components/amount-input.tsx +2 -6
  179. package/src/components/calculator.tsx +2 -7
  180. package/src/components/chart-zoom.tsx +4 -47
  181. package/src/components/chip-input.tsx +0 -7
  182. package/src/components/combobox.tsx +1 -1
  183. package/src/components/copy-button.tsx +5 -1
  184. package/src/components/currency-option-list.tsx +8 -42
  185. package/src/components/danger-confirm.tsx +3 -3
  186. package/src/components/data-table-filter-popover.tsx +2 -1
  187. package/src/components/data-table-labels.ts +2 -84
  188. package/src/components/data-table-shared.tsx +55 -2
  189. package/src/components/data-table.tsx +29 -17
  190. package/src/components/dialog-frame.tsx +3 -1
  191. package/src/components/dropdown.tsx +42 -18
  192. package/src/components/entity-combobox.tsx +1 -1
  193. package/src/components/field-base.tsx +0 -11
  194. package/src/components/field-parts.tsx +5 -19
  195. package/src/components/field-strip.tsx +9 -25
  196. package/src/components/field-sync.tsx +0 -7
  197. package/src/components/field.tsx +4 -4
  198. package/src/components/full-bleed-dialog.tsx +4 -3
  199. package/src/components/hover-menu.tsx +12 -16
  200. package/src/components/inline-entity-combobox.tsx +1 -1
  201. package/src/components/modal.tsx +20 -4
  202. package/src/components/multi-select.tsx +13 -30
  203. package/src/components/number-input.tsx +3 -8
  204. package/src/components/popover.tsx +5 -3
  205. package/src/components/series-chart.tsx +1 -1
  206. package/src/components/server-wake.tsx +0 -6
  207. package/src/components/settings-fields.tsx +4 -2
  208. package/src/components/stat-tile.tsx +8 -2
  209. package/src/components/swipeable-row.tsx +15 -37
  210. package/src/components/tooltip.tsx +37 -6
  211. package/src/components/translation-review.tsx +25 -7
  212. package/src/components/use-table-state.ts +91 -10
  213. package/src/components/user-avatar.tsx +2 -2
  214. package/src/components/wizard-stepper.tsx +2 -6
  215. package/src/currencies.ts +70 -0
  216. package/src/demo/demo-banner.tsx +7 -2
  217. package/src/eslint/index.ts +161 -0
  218. package/src/eslint/role-utilities.generated.ts +146 -0
  219. package/src/eslint/role-utilities.ts +99 -0
  220. package/src/feedback/feedback-menu.tsx +2 -2
  221. package/src/feedback/feedback-thread.tsx +3 -8
  222. package/src/hooks/use-dismiss.ts +39 -4
  223. package/src/i18n/kit-labels.tsx +15 -5
  224. package/src/i18n/locales/fr.ts +3 -2
  225. package/src/i18n/locales/it.ts +3 -2
  226. package/src/index.ts +3 -1
  227. package/src/lib/refs.ts +22 -0
  228. package/src/rhf/fields.tsx +10 -10
  229. package/src/rhf/form-core.tsx +5 -4
  230. package/src/rhf/form.tsx +7 -43
  231. package/src/rhf/line-items.tsx +1 -1
  232. package/src/rhf/use-rhf-wizard-step.ts +4 -4
  233. package/src/rhf.ts +3 -2
  234. package/src/shell/option-switcher-menu.tsx +5 -22
  235. package/src/shell/role-switcher.tsx +2 -6
  236. package/src/shell/topbar-action-menu.tsx +7 -24
  237. package/src/shell/topbar-controls.tsx +6 -18
  238. package/src/testing/index.ts +3 -1
  239. package/src/testing/viewport.ts +125 -19
  240. package/src/theme/palette-presets.ts +7 -293
  241. package/src/wizard/types.ts +0 -8
@@ -11,6 +11,7 @@ import { createPortal } from "react-dom";
11
11
  import { Search } from "lucide-react";
12
12
  import { anchoredInline, useAnchoredPanel } from "../hooks/use-anchored-panel";
13
13
  import { runOwnKeyDown, takeEscape, useDismissLayer, useOutsideClick } from "../hooks/use-dismiss";
14
+ import type { DismissLayer } from "../hooks/use-dismiss";
14
15
  import { useOverlayHistory } from "../hooks/use-overlay-history";
15
16
  import { cn } from "../lib/cn";
16
17
  import { FIELD_TOUCH_TEXT } from "./field-base";
@@ -18,6 +19,12 @@ import { mergeRefs } from "./field-parts";
18
19
  import { FOCUS_RING } from "./focus-ring";
19
20
  import { useAnchorDir } from "./use-anchor-dir";
20
21
 
22
+ /** Each list's Escape layer, filed under the refs {@link useDropdown} and
23
+ * {@link useDropdownSearch} hand out (`panelRef`, `inputRef`), for the panel and the
24
+ * search box those refs are passed to — see "The caller's own handler" on
25
+ * {@link useDropdown} (round 0.35 R2). Weak: a list's refs go with its component. */
26
+ const listLayers = new WeakMap<object, DismissLayer>();
27
+
21
28
  /**
22
29
  * Open state + close-on-outside-click for a custom dropdown — the two input-shaped
23
30
  * comboboxes, and an app's own panel. (MultiSelect, CurrencySelect, GroupedPicker and
@@ -88,6 +95,16 @@ import { useAnchorDir } from "./use-anchor-dir";
88
95
  * and stop it there — so a caller passes nothing new, AmountInput's currency chip
89
96
  * included.
90
97
  *
98
+ * ## The caller's own handler (round 0.35 R2)
99
+ *
100
+ * A caller's `onKeyDown` on {@link DropdownPanel} or {@link DropdownSearchHeader} hears
101
+ * Escape only while this list is the layer on top — the rule the dialogs got first. A
102
+ * tooltip over a row takes that press first, and the next one is the handler's, whose
103
+ * `preventDefault()` still keeps the list. The two find the list's layer through the
104
+ * refs this hook hands out — the panel through `panelRef`, the search box through
105
+ * {@link useDropdownSearch}'s `inputRef` — so a caller passes nothing new. One handed
106
+ * neither (an un-anchored panel may leave `panelRef` off) hears every key, as before.
107
+ *
91
108
  * `escapeLayer: false` is for a caller that registers its own layer: the editable
92
109
  * comboboxes, whose list can be "open" with nothing in it to show — an Escape there
93
110
  * belongs to the dialog.
@@ -129,7 +146,11 @@ export function useDropdown<T extends HTMLElement = HTMLDivElement>({
129
146
  }, []);
130
147
  useOutsideClick([wrapperRef, panelRef], close, open && !panelIsSheet);
131
148
  // See "Escape inside a dialog" above.
132
- useDismissLayer(open && escapeLayer, closeToTrigger);
149
+ const layer = useDismissLayer(open && escapeLayer, closeToTrigger);
150
+ // Filed under `panelRef` for the panel's own handler — "The caller's own handler" above.
151
+ useEffect(() => {
152
+ listLayers.set(panelRef, layer);
153
+ }, [panelRef, layer]);
133
154
  // Same close path Escape and an outside click take, so a list dismissed by Back
134
155
  // cannot end up in a different state from one dismissed any other way.
135
156
  useOverlayHistory(open && backCloses, close);
@@ -145,6 +166,12 @@ export function useDropdownSearch<T extends HTMLElement = HTMLDivElement>() {
145
166
  useDropdown<T>();
146
167
  const [query, setQuery] = useState("");
147
168
  const inputRef = useRef<HTMLInputElement>(null);
169
+ // The list's layer again, under `inputRef` for the search box's handler (round 0.35
170
+ // R2). `useDropdown`'s effect, which runs first, has filed it under `panelRef`.
171
+ useEffect(() => {
172
+ const layer = listLayers.get(panelRef);
173
+ if (layer) listLayers.set(inputRef, layer);
174
+ }, [panelRef]);
148
175
  useEffect(() => {
149
176
  if (open) {
150
177
  // eslint-disable-next-line react-hooks/set-state-in-effect -- reset the query on open
@@ -237,7 +264,9 @@ export function DropdownSearchHeader({
237
264
  value={query}
238
265
  onChange={(e) => onQueryChange(e.target.value)}
239
266
  onKeyDown={(e) => {
240
- runOwnKeyDown(e, onKeyDown);
267
+ // The caller's handler hears Escape only while the list is on top (round 0.35
268
+ // R2) — the layer filed under `inputRef`, when {@link useDropdownSearch} made it.
269
+ runOwnKeyDown(e, onKeyDown, listLayers.get(inputRef));
241
270
  // Focus is in this box while the list is up, so this is where Escape lands
242
271
  // first — see "Escape inside a dialog" on {@link useDropdown}.
243
272
  takeEscape(e);
@@ -271,30 +300,23 @@ export interface DropdownPanelProps extends ComponentPropsWithoutRef<"div"> {
271
300
  anchorRef?: RefObject<HTMLElement | null>;
272
301
  /** {@link useDropdown}'s `panelRef`, so an outside-click sees this as inside. Escape
273
302
  * anywhere in the panel, on a row as in the search box, closes the list rather than
274
- * a dialog around it. */
303
+ * a dialog around it. It is also how the panel finds the list's Escape layer, for
304
+ * its `onKeyDown` (round 0.35 R2: see "The caller's own handler" there). */
275
305
  panelRef?: RefObject<HTMLDivElement | null>;
276
306
  /** Panel width in px, for the anchored form (the CSS `w-*` cannot be measured). */
277
307
  width?: number;
278
- /** Which of the panel's edges lines up with the trigger's, room permitting.
279
- * `start`/`end` follow the trigger's reading direction — `end` is the right edge
280
- * in a left-to-right form and the left one in a right-to-left form — and are what
281
- * the kit's own pickers pass; `left`/`right` are physical and deprecated
282
- * ({@link PhysicalAlign}). Default `end`, which is the old `right` in a
283
- * left-to-right page. */
284
- align?: "start" | "end" | PhysicalAlign;
308
+ /** Which of the panel's edges lines up with the trigger's, room permitting, by the
309
+ * trigger's reading direction — `end` is the right edge in a left-to-right form and
310
+ * the left one in a right-to-left form. Default `end`. The physical `left` / `right`
311
+ * went in 0.35 (round 0.35 R1, refactor audit 2026-10, API-6e): `right` is `end` in a
312
+ * left-to-right page, `left` is `start`. */
313
+ align?: "start" | "end";
285
314
  children: ReactNode;
286
315
  /** The panel, where the rest of the props go (0.34, API-2, refactor audit 2026-10) —
287
316
  * merged with `panelRef`, so the outside-click still sees it. */
288
317
  ref?: Ref<HTMLDivElement>;
289
318
  }
290
319
 
291
- /**
292
- * @deprecated 0.34 — `"start"` / `"end"`, which follow the reading direction: `"right"`
293
- * is `"end"` in a left-to-right page, the default; removed in 0.35 (refactor audit
294
- * 2026-10, API-6e). The physical edges DropdownPanel's `align` still takes.
295
- */
296
- type PhysicalAlign = "left" | "right";
297
-
298
320
  /**
299
321
  * The floating popover surface for the searchable dropdowns: bordered/shadowed
300
322
  * panel, an optional `header` slot (search box, plus any actions), and a
@@ -344,7 +366,9 @@ export function DropdownPanel({
344
366
  // A row the keyboard reached by Tab holds focus too, and its Escape has to close the
345
367
  // list as the search box's does (BUG-1). The caller's handler first, as everywhere.
346
368
  const handleKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {
347
- runOwnKeyDown(e, onKeyDown);
369
+ // It hears Escape only while the list is on top, a tooltip over a row taking the
370
+ // press first (round 0.35 R2) — the layer filed under `panelRef`, when handed one.
371
+ runOwnKeyDown(e, onKeyDown, panelRef && listLayers.get(panelRef));
348
372
  takeEscape(e);
349
373
  };
350
374
  const body = (
@@ -73,7 +73,7 @@ export interface EntityComboboxProps<V extends string | number, C extends ComboC
73
73
  * so it is the one control here that MUST be in the reader's language. */
74
74
  closeLabel?: string;
75
75
  /** The phone sheet's heading (and, as a string, its accessible name) for a picker
76
- * whose label is drawn by someone else — a form's `FormLabel`; `RhfCombobox`
76
+ * whose label is drawn by someone else — a bound field's label; `RhfCombobox`
77
77
  * hands its label over here (0.24). Default: `label`, then `placeholder`. */
78
78
  sheetTitle?: ReactNode;
79
79
  disabled?: boolean;
@@ -457,17 +457,6 @@ export interface PasswordRevealLabels {
457
457
  // Defined in ./ui-labels, which holds no component code (0.34, KS U8).
458
458
  export { DEFAULT_PASSWORD_REVEAL_LABELS };
459
459
 
460
- /** @deprecated 0.34 — `useKitLabels("passwordReveal", DEFAULT_PASSWORD_REVEAL_LABELS, labels)`,
461
- * which the provider reaches too, or Input's own `passwordLabels`; removed in 0.35
462
- * (refactor audit 2026-10, API-6a). A merge from before the provider, which no kit
463
- * component calls. */
464
- export function resolvePasswordRevealLabels(
465
- partial?: Partial<PasswordRevealLabels>,
466
- ): PasswordRevealLabels {
467
- if (!partial) return DEFAULT_PASSWORD_REVEAL_LABELS;
468
- return { ...DEFAULT_PASSWORD_REVEAL_LABELS, ...partial };
469
- }
470
-
471
460
  /**
472
461
  * What the character counter of {@link Input} and {@link Textarea} (`showCount`) says.
473
462
  * The visible "12/80" is digits and a slash and is not translated; these are the words
@@ -1,5 +1,5 @@
1
1
  import { createContext, isValidElement, useContext, useId } from "react";
2
- import type { ComponentPropsWithoutRef, ComponentPropsWithRef, ReactNode, Ref, RefCallback } from "react";
2
+ import type { ComponentPropsWithoutRef, ComponentPropsWithRef, ReactNode } from "react";
3
3
  import { HelpCircle } from "lucide-react";
4
4
  import { cn } from "../lib/cn";
5
5
  import { hasContent } from "../lib/presence";
@@ -41,23 +41,9 @@ export function mergeDescribedBy(...ids: Array<string | false | null | undefined
41
41
  return list.length ? list.join(" ") : undefined;
42
42
  }
43
43
 
44
- /** Point a ref — a callback or an object — at `node`. `undefined` and `null` are no ref. */
45
- export function assignRef<T>(ref: Ref<T> | undefined, node: T | null): void {
46
- if (typeof ref === "function") ref(node);
47
- else if (ref) (ref as { current: T | null }).current = node;
48
- }
49
-
50
- /**
51
- * One callback ref feeding several — the caller's forwarded `ref` and a field's own
52
- * (DUP-11: AmountInput, FormActions, ScrollArea and SearchField each wrote this by hand).
53
- * Wrap it in `useCallback` (or `useMemo`) over the same refs where a new callback every
54
- * render would detach and re-attach the node.
55
- */
56
- export function mergeRefs<T>(...refs: Array<Ref<T> | undefined>): RefCallback<T> {
57
- return (node) => {
58
- for (const ref of refs) assignRef(ref, node);
59
- };
60
- }
44
+ // The ref plumbing lives in ../lib/refs since round 0.35 (Tooltip needs it, and this module
45
+ // renders a Tooltip); the fields keep importing it from here.
46
+ export { assignRef, mergeRefs } from "../lib/refs";
61
47
 
62
48
  /* ── the static label ─────────────────────────────────────────────────────── */
63
49
 
@@ -312,7 +298,7 @@ export function useFieldError(
312
298
  invalid: boolean | undefined,
313
299
  describedBy: string | undefined,
314
300
  /** The caller's own `aria-invalid`. A form library sets THIS (see `@eifi1/ui-kit/rhf`
315
- * `FormControl`), not our `invalid` prop — and a field that announces invalid should
301
+ * `RhfField`), not our `invalid` prop — and a field that announces invalid should
316
302
  * look it too. */
317
303
  ariaInvalid?: unknown,
318
304
  ): { isInvalid: boolean; describedBy: string | undefined; errorEl: ReactNode } {
@@ -1,13 +1,7 @@
1
1
  import { useId } from "react";
2
- import type { ComponentPropsWithoutRef, ReactNode } from "react";
2
+ import type { ComponentPropsWithRef, ReactNode } from "react";
3
3
  import { LabelStrip, useFieldMessages } from "./field-parts";
4
4
 
5
- /**
6
- * How far down the content starts, under the 11px label — see {@link FieldStripProps.pad}.
7
- * Since 0.34 both are the one 20px strip.
8
- */
9
- export type FieldStripPad = "clear" | "field";
10
-
11
5
  /** What a {@link FieldStrip} hands render-prop children. */
12
6
  export interface FieldStripIds {
13
7
  /** The label's id — for content that names ITSELF by the label (`aria-labelledby`)
@@ -19,7 +13,12 @@ export interface FieldStripIds {
19
13
  describedBy: string | undefined;
20
14
  }
21
15
 
22
- export interface FieldStripProps extends Omit<ComponentPropsWithoutRef<"div">, "children"> {
16
+ /**
17
+ * A `div`'s props, `ref` included since 0.35 (round 0.35 R4, as Card and EmptyState took
18
+ * it in 0.34, API-2): the ref is the root — the group, or the plain box under
19
+ * `group={false}` — which a caller scrolls into view or measures.
20
+ */
21
+ export interface FieldStripProps extends Omit<ComponentPropsWithRef<"div">, "children"> {
23
22
  /** The 11px static label, on the line a labelled field's label sits on. */
24
23
  label: ReactNode;
25
24
  /**
@@ -37,21 +36,6 @@ export interface FieldStripProps extends Omit<ComponentPropsWithoutRef<"div">, "
37
36
  * turns its map into a picture and drops its buttons, which no flag here could guess.
38
37
  */
39
38
  disabled?: boolean;
40
- /**
41
- * @deprecated 0.34 — drop it; removed in 0.35. Both values are the same 20px strip
42
- * since 0.34, and the prop does nothing; it stays so a caller that passes it still
43
- * compiles.
44
- *
45
- * It chose between `"clear"` (20px) and `"field"` (16px: the strip plus a 26px
46
- * bordered control made a labelled Input's 42px). The label row — the label and its
47
- * "?" — ends 18px down, so over a 16px strip it crossed the content's top border by
48
- * 2px (keksdose dev#593; the arithmetic is on `LABEL_STRIP_PAD` in field-parts.tsx).
49
- * For a row of 42px fields, make the content 22px tall: 20 + 22 = 42, the shape
50
- * ToggleGroup's `labelPlacement="strip"` has since 0.34.
51
- *
52
- * A `className` still merges after the strip's padding.
53
- */
54
- pad?: FieldStripPad;
55
39
  /**
56
40
  * The wrapper is a `role="group"` named by the label (default), so a screen reader
57
41
  * entering the map or the first button hears "Location, group" — what the visible
@@ -104,8 +88,6 @@ export function FieldStrip({
104
88
  hint,
105
89
  error,
106
90
  disabled = false,
107
- // Taken off so it never reaches the DOM; see `pad` (deprecated, one strip since 0.34).
108
- pad: _pad,
109
91
  group = true,
110
92
  className,
111
93
  children,
@@ -118,6 +100,8 @@ export function FieldStrip({
118
100
  // every kit field reads them in.
119
101
  const groupDescribedBy = [rest["aria-describedby"], messages.describedBy].filter(Boolean).join(" ") || undefined;
120
102
  return (
103
+ // `rest` carries the caller's `ref` too (a plain prop in React 19): LabelStrip spreads
104
+ // its own rest onto the wrapper `<div>`, so the ref lands on the root.
121
105
  <LabelStrip
122
106
  {...rest}
123
107
  {...(group
@@ -38,13 +38,6 @@ export interface FieldSyncLabels {
38
38
  // Defined in ./field-sync-labels, which holds no component code (0.34, KS U8).
39
39
  export { DEFAULT_FIELD_SYNC_LABELS };
40
40
 
41
- /** @deprecated 0.34 — `useKitLabels("fieldSync", DEFAULT_FIELD_SYNC_LABELS, labels)`, which
42
- * the provider reaches too, or the field's own `labels`; removed in 0.35 (refactor audit
43
- * 2026-10, API-6a). A merge from before the provider, which no kit component calls. */
44
- export function resolveFieldSyncLabels(labels?: Partial<FieldSyncLabels>): FieldSyncLabels {
45
- return labels ? { ...DEFAULT_FIELD_SYNC_LABELS, ...labels } : DEFAULT_FIELD_SYNC_LABELS;
46
- }
47
-
48
41
  export interface UseFieldSyncOptions<T> {
49
42
  /** What the database currently holds. Changing it re-seeds a CLEAN field. */
50
43
  value: T;
@@ -29,10 +29,10 @@
29
29
  *
30
30
  * Deliberately knows neither the wizard nor react-hook-form: `error` is a plain
31
31
  * value. Pass `wizard.fieldErrors.iban`, a step-local error, or
32
- * `fieldState.error?.message` from a `FormField` render — the markup is the same
33
- * whichever of them gates the step, so a step can move between them without its
34
- * fields changing. (Inside a react-hook-form form, `FormItem` + `FormLabel` from
35
- * `@eifi1/ui-kit/rhf` do the same job with the ids taken from the form.)
32
+ * `fieldState.error?.message` from a react-hook-form `Controller` — the markup is the
33
+ * same whichever of them gates the step, so a step can move between them without its
34
+ * fields changing. (Inside a react-hook-form form, `RhfField` from `@eifi1/ui-kit/rhf`
35
+ * does the same job with the ids taken from the form.)
36
36
  *
37
37
  * The error is NOT `role="alert"`, for the reason the kit's `error` prop gives (see
38
38
  * `useFieldError` in components/field-parts.tsx): it is read when focus reaches the control.
@@ -174,14 +174,15 @@ export function FullBleedDialog({
174
174
  // to whichever of them is topmost — one press dismissing the sheet AND the edit
175
175
  // behind it was Keksdose live #309 (*"Mouse Back does not only close the select but
176
176
  // also the whole edit or create dialog"*) on a different key.
177
- useDismissLayer(open, requestClose);
177
+ const layer = useDismissLayer(open, requestClose);
178
178
  const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
179
179
  // The caller's handler first (keksdose's transaction-search keeps its list keys on
180
180
  // the dialog); a `preventDefault()` IN THAT HANDLER claims Escape. Only there: a
181
181
  // descendant's does not count. A SearchField in the header prevents Escape to stop
182
182
  // Chromium clearing a type=search input, and in 0.8.0 that silently stopped Escape
183
- // from closing the dialog (keksdose). The one rule `Modal` now shares.
184
- runOwnKeyDown(e, onKeyDown);
183
+ // from closing the dialog (keksdose). The one rule `Modal` now shares — as is this
184
+ // one: the handler sees Escape only while this dialog is the layer on top (0.35).
185
+ runOwnKeyDown(e, onKeyDown, layer);
185
186
  takeEscape(e);
186
187
  };
187
188
 
@@ -20,12 +20,13 @@ export interface HoverMenuProps extends Omit<ComponentPropsWithoutRef<"div">, "c
20
20
  trigger: (state: { open: boolean; toggle: () => void; close: () => void }) => ReactNode;
21
21
  children: (close: () => void) => ReactNode;
22
22
  /**
23
- * Which edge of the trigger the panel lines up with. `start`/`end` follow the
24
- * reading direction (`end` is the right edge in LTR, the left in RTL); `left`/`right`
25
- * are physical and deprecated ({@link PhysicalAlign}). Default `end` — the same
26
- * right-aligned panel as before in LTR, mirrored in RTL.
23
+ * Which edge of the trigger the panel lines up with, by the reading direction: `end`
24
+ * is the right edge in LTR and the left in RTL. Default `end` — the same right-aligned
25
+ * panel as before in LTR, mirrored in RTL. The physical `left` / `right` went in 0.35
26
+ * (round 0.35 R1, refactor audit 2026-10, API-6e): `right` is `end` in a left-to-right
27
+ * page, `left` is `start`.
27
28
  */
28
- align?: "start" | "end" | PhysicalAlign;
29
+ align?: "start" | "end";
29
30
  panelClassName?: string;
30
31
  /** Extra classes for the WRAPPER (the panel has {@link panelClassName}). */
31
32
  className?: string;
@@ -34,14 +35,6 @@ export interface HoverMenuProps extends Omit<ComponentPropsWithoutRef<"div">, "c
34
35
  ref?: Ref<HTMLDivElement>;
35
36
  }
36
37
 
37
- /**
38
- * @deprecated 0.34 — `"start"` / `"end"`, which follow the reading direction: `"right"`
39
- * is `"end"` in a left-to-right page, the default; removed in 0.35 (refactor audit
40
- * 2026-10, API-6e). The physical edges HoverMenu's `align` — and TopBarActionMenu's and
41
- * FeedbackMenu's, which hand it on — still take.
42
- */
43
- type PhysicalAlign = "left" | "right";
44
-
45
38
  // Hovering waits briefly before opening so sweeping the cursor across a row of
46
39
  // triggers doesn't flash menus; closing waits so the cursor can travel from
47
40
  // trigger to panel. Click/keyboard toggling is instant (no "opening" phase).
@@ -50,7 +43,7 @@ const CLOSE_DELAY_MS = 120;
50
43
 
51
44
  type Phase = "closed" | "opening" | "open" | "closing";
52
45
 
53
- const ALIGN_CLASS = { start: "start-0", end: "end-0", left: "left-0", right: "right-0" } as const;
46
+ const ALIGN_CLASS = { start: "start-0", end: "end-0" } as const;
54
47
 
55
48
  /** Explicit menu-item roles a caller may already have written. */
56
49
  const ITEM_ROLES = '[role="menuitem"],[role="menuitemcheckbox"],[role="menuitemradio"]';
@@ -188,7 +181,7 @@ export function HoverMenu({
188
181
  // DUP-1). Focus goes back to the trigger only when it was inside the menu: a panel
189
182
  // opened by hover while the user is typing elsewhere must not yank the caret over
190
183
  // here on the way out.
191
- useDismissLayer(open, () => {
184
+ const layer = useDismissLayer(open, () => {
192
185
  const hadFocus = wrapperRef.current?.contains(document.activeElement) ?? false;
193
186
  close();
194
187
  if (hadFocus) triggerEl()?.focus();
@@ -298,7 +291,10 @@ export function HoverMenu({
298
291
  onMouseEnter={handleMouseEnter}
299
292
  onMouseLeave={handleMouseLeave}
300
293
  onKeyDown={(e) => {
301
- runOwnKeyDown(e, rest.onKeyDown);
294
+ // The caller's handler sees Escape only while this menu is the layer on top: a
295
+ // tooltip on one of its rows takes that press first (round 0.35 R2, the rule
296
+ // the dialogs got first). Every other key, and a closed menu's, as before.
297
+ runOwnKeyDown(e, rest.onKeyDown, layer);
302
298
  // Focus on the trigger or in the menu: the key stops here, so a panel around
303
299
  // the menu does not close with it.
304
300
  if (open) takeEscape(e);
@@ -83,7 +83,7 @@ export interface InlineEntityComboboxProps<V extends string | number, C extends
83
83
  emptyLabel?: string;
84
84
  closeLabel?: string;
85
85
  /** The phone sheet's heading (and, as a string, its accessible name) for a field
86
- * whose label is drawn by someone else — a form's `FormLabel`;
86
+ * whose label is drawn by someone else — a bound field's label;
87
87
  * `RhfInlineEntityCombobox` hands its label over here (0.24). Default: `label`.
88
88
  * See {@link Combobox}'s `sheetTitle`. */
89
89
  sheetTitle?: ReactNode;
@@ -1,4 +1,4 @@
1
- import { createContext, useEffect, useRef, useState } from "react";
1
+ import { createContext, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import type {
4
4
  ComponentPropsWithoutRef,
@@ -6,8 +6,10 @@ import type {
6
6
  MouseEvent as ReactMouseEvent,
7
7
  PointerEvent as ReactPointerEvent,
8
8
  ReactNode,
9
+ Ref,
9
10
  } from "react";
10
11
  import { cn } from "../lib/cn";
12
+ import { mergeRefs } from "./field-parts";
11
13
  import { useFocusTrap } from "../hooks/use-focus-trap";
12
14
  import { useBodyScrollLock } from "../hooks/use-body-scroll-lock";
13
15
  import { useOverlayHistory } from "../hooks/use-overlay-history";
@@ -228,6 +230,14 @@ export interface ModalProps extends Omit<ComponentPropsWithoutRef<"div">, "role"
228
230
  * `onClose` still runs, after the param is cleared. Requires a react-router Router.
229
231
  */
230
232
  urlParam?: DialogParam;
233
+ /**
234
+ * The PANEL — the `role="dialog"` element the rest of the props go on, not the
235
+ * backdrop around it (0.35, round 0.35 R4, the follow-up to API-2; as on
236
+ * `FullBleedDialog`). Merged with the panel's own ref, which the focus
237
+ * trap holds. Set while the panel is in the document: `null` while a dialog with
238
+ * `open={false}` is closed, and after a `urlParam` dialog's param is cleared.
239
+ */
240
+ ref?: Ref<HTMLDivElement>;
231
241
  }
232
242
 
233
243
  /**
@@ -286,12 +296,16 @@ function ModalPanel({
286
296
  role = "dialog",
287
297
  urlParam: _urlParam,
288
298
  ownHistoryEntry = true,
299
+ ref,
289
300
  ...rest
290
301
  }: ModalProps & {
291
302
  /** False when the URL already holds an entry for this dialog — see `urlParam`. */
292
303
  ownHistoryEntry?: boolean;
293
304
  }) {
294
305
  const panelRef = useRef<HTMLDivElement>(null);
306
+ // The caller's `ref` beside the trap's (round 0.35 R4): memoised, so the panel is not
307
+ // detached and re-attached on every render.
308
+ const setPanel = useMemo(() => mergeRefs(panelRef, ref), [ref]);
295
309
  const drag = useDragOffset(Boolean(draggable));
296
310
  // Without `open`, mounted means open — the constant `true` every hook below used to
297
311
  // be given. With it, `open` itself.
@@ -357,9 +371,11 @@ function ModalPanel({
357
371
  // One layer on the Escape stack while open (Wave 2, DUP-1): a list, a popover or a
358
372
  // tooltip opened inside takes the key before the dialog does, wherever focus is —
359
373
  // and the dialog gets it even when focus is not in the panel.
360
- useDismissLayer(active, requestClose);
374
+ const layer = useDismissLayer(active, requestClose);
361
375
  const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
362
- runOwnKeyDown(e, onKeyDown);
376
+ // The caller's handler sees Escape only while this dialog is the layer on top: a
377
+ // tooltip or a list open over it takes that press first (0.35, KS's gear wizard).
378
+ runOwnKeyDown(e, onKeyDown, layer);
363
379
  // To whatever is on top — this dialog, unless something opened over it — and
364
380
  // stopped here. Tab containment lives in useFocusTrap, which listens on the panel.
365
381
  takeEscape(e);
@@ -399,7 +415,7 @@ function ModalPanel({
399
415
  // losing any of the three is silent, and the symptom (a dialog a screen reader
400
416
  // walks straight out of) shows up nowhere near the call site that caused it.
401
417
  {...rest}
402
- ref={panelRef}
418
+ ref={setPanel}
403
419
  // Normalised at runtime too: an untyped caller's `role="presentation"` must not
404
420
  // take the dialog role away (props-passthrough pins that).
405
421
  role={role === "alertdialog" ? "alertdialog" : "dialog"}
@@ -50,32 +50,21 @@ export interface MultiSelectProps<V extends string | number = string | number>
50
50
  options: MultiSelectOption<V>[];
51
51
  /** The chosen values — the select family's `value` (API-4). */
52
52
  value?: V[];
53
- /** @deprecated 0.34 — use `value`; removed in 0.35 (refactor audit 2026-10, API-4). */
54
- values?: V[];
55
53
  onChange: (next: V[]) => void;
56
- /** @deprecated 0.34 — use `labels.all`; removed in 0.35 (API-4). Displayed when no
57
- * value is selected (i.e. "all"); wins over `placeholder`, as `labels.all` does. */
58
- allLabel?: string;
59
54
  /** Singular/plural countable label, used when 1+ items are selected. Default:
60
55
  * `labels.selectedCount`, else `multiSelect.selectedCount` from the provider — the
61
56
  * bare count in English. */
62
57
  itemLabel?: (count: number) => string;
63
- /** Shown when nothing is picked, unless `labels.all` (or `allLabel`) says otherwise —
64
- * it has always doubled as the "nothing picked" text. */
58
+ /** Shown when nothing is picked, unless `labels.all` says otherwise — it has always
59
+ * doubled as the "nothing picked" text. */
65
60
  placeholder?: string;
66
61
  /** Embedded top-boundary label, matching the native Input/Select fields. */
67
62
  label?: ReactNode;
68
- /** @deprecated 0.34 — use `labels.search`; removed in 0.35 (API-4). */
69
- searchLabel?: string;
70
- /** @deprecated 0.34 — use `labels.selectAll`; removed in 0.35 (API-4). */
71
- selectAllLabel?: string;
72
- /** @deprecated 0.34 — use `labels.clear`; removed in 0.35 (API-4). */
73
- clearLabel?: string;
74
63
  /**
75
64
  * Per-instance overrides of the provider's `multiSelect` namespace, over English — the
76
- * kit's `labels` shape, where this took four string props (API-4). `all` is the trigger
77
- * when nothing is picked: `labels.all`, then `allLabel`, then `placeholder`, then the
78
- * provider's.
65
+ * kit's `labels` shape, where this took four string props until 0.35 (API-4: `allLabel`,
66
+ * `searchLabel`, `selectAllLabel`, `clearLabel`). `all` is the trigger when nothing is
67
+ * picked: `labels.all`, then `placeholder`, then the provider's.
79
68
  */
80
69
  labels?: LabelOverride<MultiSelectLabels>;
81
70
  /**
@@ -141,15 +130,10 @@ export function MultiSelect<V extends string | number = string | number>(props:
141
130
  const {
142
131
  options,
143
132
  value: valueProp,
144
- values,
145
133
  onChange,
146
- allLabel,
147
134
  itemLabel,
148
135
  placeholder,
149
136
  label,
150
- searchLabel,
151
- selectAllLabel,
152
- clearLabel,
153
137
  labels: labelsProp,
154
138
  className,
155
139
  panelClassName,
@@ -172,7 +156,7 @@ export function MultiSelect<V extends string | number = string | number>(props:
172
156
  "aria-required": ariaRequired,
173
157
  ...rest
174
158
  } = props;
175
- const value = useMemo<V[]>(() => valueProp ?? values ?? [], [valueProp, values]);
159
+ const value = useMemo<V[]>(() => valueProp ?? [], [valueProp]);
176
160
  // The rows as the family's options — the hint is the sublabel, searched like the label.
177
161
  const comboOptions = useMemo(
178
162
  () => options.map((o): ComboOption<V> => ({ value: o.value, label: o.label, sublabel: o.hint, disabled: o.disabled })),
@@ -190,14 +174,13 @@ export function MultiSelect<V extends string | number = string | number>(props:
190
174
  },
191
175
  [ref, triggerRef],
192
176
  );
193
- // `labels` > the deprecated string props > the provider's `multiSelect` > English. `all`
194
- // is resolved apart below: between the instance's and the provider's sits `placeholder`,
195
- // which has always doubled as the "nothing picked" text, and a caller who set it must
196
- // keep seeing it.
177
+ // `labels` > the provider's `multiSelect` > English. `all` is resolved apart below:
178
+ // between the instance's and the provider's sits `placeholder`, which has always
179
+ // doubled as the "nothing picked" text, and a caller who set it must keep seeing it.
197
180
  const text = useKitLabels("multiSelect", DEFAULT_MULTI_SELECT_LABELS, {
198
- search: labelsProp?.search ?? searchLabel,
199
- selectAll: labelsProp?.selectAll ?? selectAllLabel,
200
- clear: labelsProp?.clear ?? clearLabel,
181
+ search: labelsProp?.search,
182
+ selectAll: labelsProp?.selectAll,
183
+ clear: labelsProp?.clear,
201
184
  selectedCount: labelsProp?.selectedCount ?? itemLabel,
202
185
  });
203
186
  const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
@@ -226,7 +209,7 @@ export function MultiSelect<V extends string | number = string | number>(props:
226
209
  const allChecked = options.length > 0 && options.every((o) => valueSet.has(o.value));
227
210
  const selectedSummary =
228
211
  value.length === 0 || allChecked
229
- ? (labelsProp?.all ?? allLabel ?? placeholder ?? text.all)
212
+ ? (labelsProp?.all ?? placeholder ?? text.all)
230
213
  : text.selectedCount(value.length);
231
214
 
232
215
  const toggle = (o: ComboOption<V>) => {
@@ -9,7 +9,8 @@ import {
9
9
  FLOATING_INPUT_CLASS,
10
10
  FloatingField,
11
11
  } from "./field-base";
12
- import { CURRENCIES } from "./currency-select";
12
+ // The data alone (0.35): not the picker, which this field does not render.
13
+ import { CURRENCIES } from "../currencies";
13
14
  import { FieldCaption, LockedReason, mergeRefs, type DisabledReasonDisplay } from "./field-parts";
14
15
  import { useEndWidth, useNumericField } from "./numeric-field";
15
16
  import type { CommitScope } from "./write-lock";
@@ -35,9 +36,6 @@ export interface NumberInputProps {
35
36
  /** Names the field when it has no visible `label` (a table cell, a phone's display
36
37
  * shape). */
37
38
  "aria-label"?: string;
38
- /** @deprecated 0.34 — use `aria-label`; removed in 0.35 (refactor audit 2026-10, API-1).
39
- * Names the field only when `aria-label` is absent. */
40
- ariaLabel?: string;
41
39
  /** Names for the calculator this field renders — its trigger, and the controls
42
40
  * inside the popover — and for the numpad sheet it opens on a phone. Overrides
43
41
  * for THIS field only: both resolve the `calculator` namespace of
@@ -319,8 +317,7 @@ export function NumberInput(props: NumberInputProps) {
319
317
  onChange,
320
318
  onCommit,
321
319
  label,
322
- "aria-label": ariaLabelAttr,
323
- ariaLabel: ariaLabelProp,
320
+ "aria-label": ariaLabel,
324
321
  labels,
325
322
  placeholder,
326
323
  autoFocus,
@@ -334,8 +331,6 @@ export function NumberInput(props: NumberInputProps) {
334
331
  max,
335
332
  disabledReasonDisplay,
336
333
  } = props;
337
- // The DOM spelling wins; `ariaLabel` is the fallback for call sites not moved yet.
338
- const ariaLabel = ariaLabelAttr ?? ariaLabelProp;
339
334
  const generatedId = useId();
340
335
  const fieldId = id ?? generatedId;
341
336
  // The engine AmountInput shares (DUP-10): the lock (focusable, `readOnly`, nothing
@@ -159,7 +159,7 @@ export function Popover({
159
159
  // A layer on the Escape stack while open (Wave 2, DUP-1): a list opened inside closes
160
160
  // first, and the dialog this popover sits in only after it — the calculator on an
161
161
  // amount field in a Modal is exactly that.
162
- useDismissLayer(open, close);
162
+ const layer = useDismissLayer(open, close);
163
163
 
164
164
  useFocusTrap(panelRef, {
165
165
  active: open && pos !== null,
@@ -178,8 +178,10 @@ export function Popover({
178
178
  const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
179
179
  // A caller's own handler first (0.32.2: RowActions stops Enter / Space at the panel);
180
180
  // it used to be dropped, overwritten by this one. A `preventDefault()` on Escape
181
- // there keeps the popover open — the owner's claim, as on the dialogs.
182
- runOwnKeyDown(e, onKeyDownProp);
181
+ // there keeps the popover open — the owner's claim, as on the dialogs. And as there,
182
+ // the handler sees Escape only while this popover is the layer on top: a tooltip or a
183
+ // menu open over it takes that press first (round 0.35 R2, the dialogs' rule).
184
+ runOwnKeyDown(e, onKeyDownProp, layer);
183
185
  // Focus is in here, the normal case: the key is handed to the top layer and STOPPED,
184
186
  // so it does not travel on up the React tree to a dialog around the popover.
185
187
  takeEscape(e);
@@ -25,7 +25,7 @@
25
25
  // "area"` (and `stack`), and the abscissa may be a row of PERIODS (`x.type:
26
26
  // "category"`) or real dates (`x.type: "time"`) instead of a number the app had to
27
27
  // invent. What each of those does to the zoom is decided in one place,
28
- // `defaultZoomAxes` in `chart-zoom.tsx`.
28
+ // `defaultZoomAxes` in `chart-zoom-core.tsx`.
29
29
  //
30
30
  // **An axis budget (0.15.1).** Every visible axis reserves its band, and four of them
31
31
  // leave a phone nothing to plot. A chart draws at most `maxVisibleAxes`, and — on its
@@ -69,12 +69,6 @@ export interface ServerWakeNoticeProps {
69
69
  * label says "the app". */
70
70
  appName?: string;
71
71
  labels?: LabelOverride<ServerWakeLabels>;
72
- /**
73
- * @deprecated 0.34 — drop it; removed in 0.35. The notice sits at the TOP of the
74
- * screen since 0.34, so there is no bottom nav to clear and this does nothing. It was
75
- * the height kept clear at the bottom (AppShell's `--app-nav-h`).
76
- */
77
- navOffset?: string | number;
78
72
  /** Extra classes on the fixed anchor — a different width (`max-w-*`), or another
79
73
  * `top-*` for an app that wants it under its top bar rather than over it. Merged, so
80
74
  * a class given here replaces the kit's of the same kind. */