@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.
- package/README.md +82 -38
- package/dist/billing/billing-banner.d.ts +5 -2
- package/dist/billing/billing-banner.js.map +1 -1
- package/dist/billing/plan-limit-notice.d.ts +5 -2
- package/dist/billing/plan-limit-notice.js.map +1 -1
- package/dist/chart.js.map +1 -1
- package/dist/components/amount-input.d.ts +0 -3
- package/dist/components/amount-input.js +2 -2
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/calculator.d.ts +1 -4
- package/dist/components/calculator.js +1 -3
- package/dist/components/calculator.js.map +1 -1
- package/dist/components/chart-zoom.d.ts +0 -37
- package/dist/components/chart-zoom.js +1 -28
- package/dist/components/chart-zoom.js.map +1 -1
- package/dist/components/chip-input.d.ts +0 -4
- package/dist/components/chip-input.js +1 -5
- package/dist/components/chip-input.js.map +1 -1
- package/dist/components/combobox.d.ts +1 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -1
- package/dist/components/copy-button.js.map +1 -1
- package/dist/components/currency-option-list.d.ts +3 -8
- package/dist/components/currency-option-list.js +1 -29
- package/dist/components/currency-option-list.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +3 -3
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-filter-popover.d.ts +1 -1
- package/dist/components/data-table-filter-popover.js +1 -1
- package/dist/components/data-table-filter-popover.js.map +1 -1
- package/dist/components/data-table-labels.d.ts +1 -42
- package/dist/components/data-table-labels.js +2 -31
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-shared.d.ts +17 -1
- package/dist/components/data-table-shared.js +22 -0
- package/dist/components/data-table-shared.js.map +1 -1
- package/dist/components/data-table.d.ts +12 -2
- package/dist/components/data-table.js +18 -14
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/dialog-frame.d.ts +3 -1
- package/dist/components/dialog-frame.js.map +1 -1
- package/dist/components/dropdown.d.ts +18 -15
- package/dist/components/dropdown.js +11 -3
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +1 -1
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/field-base.d.ts +0 -5
- package/dist/components/field-base.js +0 -5
- package/dist/components/field-base.js.map +1 -1
- package/dist/components/field-parts.d.ts +3 -11
- package/dist/components/field-parts.js +1 -9
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +8 -23
- package/dist/components/field-strip.js +24 -22
- package/dist/components/field-strip.js.map +1 -1
- package/dist/components/field-sync.d.ts +0 -4
- package/dist/components/field-sync.js +0 -4
- package/dist/components/field-sync.js.map +1 -1
- package/dist/components/field.d.ts +4 -4
- package/dist/components/field.js.map +1 -1
- package/dist/components/full-bleed-dialog.js +2 -2
- package/dist/components/full-bleed-dialog.js.map +1 -1
- package/dist/components/hover-menu.d.ts +6 -13
- package/dist/components/hover-menu.js +3 -3
- package/dist/components/hover-menu.js.map +1 -1
- package/dist/components/inline-entity-combobox.d.ts +1 -1
- package/dist/components/inline-entity-combobox.js.map +1 -1
- package/dist/components/modal.d.ts +9 -1
- package/dist/components/modal.js +7 -4
- package/dist/components/modal.js.map +1 -1
- package/dist/components/multi-select.d.ts +5 -16
- package/dist/components/multi-select.js +5 -10
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-input.d.ts +0 -3
- package/dist/components/number-input.js +2 -4
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/popover.js +2 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/series-chart.js.map +1 -1
- package/dist/components/server-wake.d.ts +0 -6
- package/dist/components/server-wake.js.map +1 -1
- package/dist/components/settings-fields.d.ts +4 -2
- package/dist/components/settings-fields.js.map +1 -1
- package/dist/components/stat-tile.d.ts +8 -2
- package/dist/components/stat-tile.js.map +1 -1
- package/dist/components/swipeable-row.d.ts +2 -17
- package/dist/components/swipeable-row.js +11 -16
- package/dist/components/swipeable-row.js.map +1 -1
- package/dist/components/tooltip.d.ts +9 -1
- package/dist/components/tooltip.js +20 -5
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/translation-review.d.ts +10 -2
- package/dist/components/translation-review.js +11 -3
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/use-table-state.d.ts +31 -3
- package/dist/components/use-table-state.js +53 -8
- package/dist/components/use-table-state.js.map +1 -1
- package/dist/components/user-avatar.d.ts +2 -2
- package/dist/components/user-avatar.js.map +1 -1
- package/dist/components/wizard-stepper.d.ts +1 -4
- package/dist/components/wizard-stepper.js +2 -3
- package/dist/components/wizard-stepper.js.map +1 -1
- package/dist/currencies.d.ts +23 -0
- package/dist/currencies.js +38 -0
- package/dist/currencies.js.map +1 -0
- package/dist/demo/demo-banner.d.ts +7 -2
- package/dist/demo/demo-banner.js.map +1 -1
- package/dist/eslint/index.d.ts +83 -0
- package/dist/eslint/index.js +69 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/role-utilities.d.ts +48 -0
- package/dist/eslint/role-utilities.generated.d.ts +2 -0
- package/dist/eslint/role-utilities.generated.js +143 -0
- package/dist/eslint/role-utilities.generated.js.map +1 -0
- package/dist/eslint/role-utilities.js +43 -0
- package/dist/eslint/role-utilities.js.map +1 -0
- package/dist/feedback/feedback-menu.d.ts +2 -2
- package/dist/feedback/feedback-menu.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +3 -8
- package/dist/feedback/feedback-thread.js +0 -2
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/hooks/use-dismiss.d.ts +16 -3
- package/dist/hooks/use-dismiss.js +14 -2
- package/dist/hooks/use-dismiss.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +14 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/fr.js +3 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/it.js +3 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/refs.d.ts +13 -0
- package/dist/lib/refs.js +15 -0
- package/dist/lib/refs.js.map +1 -0
- package/dist/rhf/fields.d.ts +10 -10
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form-core.d.ts +5 -4
- package/dist/rhf/form-core.js.map +1 -1
- package/dist/rhf/form.d.ts +7 -36
- package/dist/rhf/form.js +1 -16
- package/dist/rhf/form.js.map +1 -1
- package/dist/rhf/line-items.d.ts +1 -1
- package/dist/rhf/line-items.js.map +1 -1
- package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
- package/dist/rhf.js.map +1 -1
- package/dist/shell/option-switcher-menu.d.ts +5 -17
- package/dist/shell/option-switcher-menu.js +1 -3
- package/dist/shell/option-switcher-menu.js.map +1 -1
- package/dist/shell/role-switcher.d.ts +1 -4
- package/dist/shell/role-switcher.js +2 -3
- package/dist/shell/role-switcher.js.map +1 -1
- package/dist/shell/topbar-action-menu.d.ts +7 -19
- package/dist/shell/topbar-action-menu.js +1 -3
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell/topbar-controls.d.ts +3 -12
- package/dist/shell/topbar-controls.js +6 -9
- package/dist/shell/topbar-controls.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/viewport.d.ts +25 -2
- package/dist/testing/viewport.js +61 -10
- package/dist/testing/viewport.js.map +1 -1
- package/dist/theme/palette-presets.d.ts +0 -7
- package/dist/theme/palette-presets.js +0 -279
- package/dist/theme/palette-presets.js.map +1 -1
- package/dist/wizard/types.d.ts +0 -5
- package/dist/wizard/types.js +1 -5
- package/dist/wizard/types.js.map +1 -1
- package/package.json +9 -1
- package/src/billing/billing-banner.tsx +5 -2
- package/src/billing/plan-limit-notice.tsx +5 -2
- package/src/chart.ts +5 -3
- package/src/components/amount-input.tsx +2 -6
- package/src/components/calculator.tsx +2 -7
- package/src/components/chart-zoom.tsx +4 -47
- package/src/components/chip-input.tsx +0 -7
- package/src/components/combobox.tsx +1 -1
- package/src/components/copy-button.tsx +5 -1
- package/src/components/currency-option-list.tsx +8 -42
- package/src/components/danger-confirm.tsx +3 -3
- package/src/components/data-table-filter-popover.tsx +2 -1
- package/src/components/data-table-labels.ts +2 -84
- package/src/components/data-table-shared.tsx +55 -2
- package/src/components/data-table.tsx +29 -17
- package/src/components/dialog-frame.tsx +3 -1
- package/src/components/dropdown.tsx +42 -18
- package/src/components/entity-combobox.tsx +1 -1
- package/src/components/field-base.tsx +0 -11
- package/src/components/field-parts.tsx +5 -19
- package/src/components/field-strip.tsx +9 -25
- package/src/components/field-sync.tsx +0 -7
- package/src/components/field.tsx +4 -4
- package/src/components/full-bleed-dialog.tsx +4 -3
- package/src/components/hover-menu.tsx +12 -16
- package/src/components/inline-entity-combobox.tsx +1 -1
- package/src/components/modal.tsx +20 -4
- package/src/components/multi-select.tsx +13 -30
- package/src/components/number-input.tsx +3 -8
- package/src/components/popover.tsx +5 -3
- package/src/components/series-chart.tsx +1 -1
- package/src/components/server-wake.tsx +0 -6
- package/src/components/settings-fields.tsx +4 -2
- package/src/components/stat-tile.tsx +8 -2
- package/src/components/swipeable-row.tsx +15 -37
- package/src/components/tooltip.tsx +37 -6
- package/src/components/translation-review.tsx +25 -7
- package/src/components/use-table-state.ts +91 -10
- package/src/components/user-avatar.tsx +2 -2
- package/src/components/wizard-stepper.tsx +2 -6
- package/src/currencies.ts +70 -0
- package/src/demo/demo-banner.tsx +7 -2
- package/src/eslint/index.ts +161 -0
- package/src/eslint/role-utilities.generated.ts +146 -0
- package/src/eslint/role-utilities.ts +99 -0
- package/src/feedback/feedback-menu.tsx +2 -2
- package/src/feedback/feedback-thread.tsx +3 -8
- package/src/hooks/use-dismiss.ts +39 -4
- package/src/i18n/kit-labels.tsx +15 -5
- package/src/i18n/locales/fr.ts +3 -2
- package/src/i18n/locales/it.ts +3 -2
- package/src/index.ts +3 -1
- package/src/lib/refs.ts +22 -0
- package/src/rhf/fields.tsx +10 -10
- package/src/rhf/form-core.tsx +5 -4
- package/src/rhf/form.tsx +7 -43
- package/src/rhf/line-items.tsx +1 -1
- package/src/rhf/use-rhf-wizard-step.ts +4 -4
- package/src/rhf.ts +3 -2
- package/src/shell/option-switcher-menu.tsx +5 -22
- package/src/shell/role-switcher.tsx +2 -6
- package/src/shell/topbar-action-menu.tsx +7 -24
- package/src/shell/topbar-controls.tsx +6 -18
- package/src/testing/index.ts +3 -1
- package/src/testing/viewport.ts +125 -19
- package/src/theme/palette-presets.ts +7 -293
- 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
|
-
|
|
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
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
* `
|
|
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 {
|
|
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
|
-
|
|
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;
|
package/src/components/field.tsx
CHANGED
|
@@ -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 `
|
|
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, `
|
|
35
|
-
*
|
|
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
|
-
|
|
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
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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
|
|
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;
|
package/src/components/modal.tsx
CHANGED
|
@@ -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
|
-
|
|
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={
|
|
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`
|
|
64
|
-
*
|
|
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
|
|
77
|
-
*
|
|
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 ??
|
|
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
|
|
194
|
-
//
|
|
195
|
-
//
|
|
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
|
|
199
|
-
selectAll: labelsProp?.selectAll
|
|
200
|
-
clear: labelsProp?.clear
|
|
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 ??
|
|
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
|
-
|
|
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":
|
|
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
|
-
|
|
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. */
|