@eifi1/ui-kit 0.22.0 → 0.24.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 +42 -25
- package/dist/chart.d.ts +8 -7
- package/dist/components/amount-input.d.ts +34 -7
- package/dist/components/amount-input.js +14 -10
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +26 -0
- package/dist/components/autocomplete.js +164 -137
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +8 -7
- package/dist/components/calculator.d.ts +8 -7
- package/dist/components/column-mapper.d.ts +235 -0
- package/dist/components/column-mapper.js +411 -0
- package/dist/components/column-mapper.js.map +1 -0
- package/dist/components/combobox-core.d.ts +39 -2
- package/dist/components/combobox-core.js +29 -6
- package/dist/components/combobox-core.js.map +1 -1
- package/dist/components/combobox.d.ts +113 -3
- package/dist/components/combobox.js +456 -313
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +8 -7
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +5 -4
- package/dist/components/country-select.d.ts +50 -7
- package/dist/components/country-select.js +65 -9
- package/dist/components/country-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +8 -7
- package/dist/components/danger-confirm.js +64 -7
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/date-picker.d.ts +41 -0
- package/dist/components/date-picker.js +269 -8
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +48 -2
- package/dist/components/entity-combobox.js +159 -114
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +8 -7
- package/dist/components/field-parts.d.ts +116 -6
- package/dist/components/field-parts.js +84 -0
- package/dist/components/field-parts.js.map +1 -1
- package/dist/components/field-strip.d.ts +105 -0
- package/dist/components/field-strip.js +46 -0
- package/dist/components/field-strip.js.map +1 -0
- package/dist/components/file-button.d.ts +8 -7
- package/dist/components/file-dropzone.d.ts +8 -7
- package/dist/components/form-actions.d.ts +8 -7
- package/dist/components/form-actions.js +23 -3
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +8 -7
- package/dist/components/icon-picker.d.ts +10 -1
- package/dist/components/icon-picker.js +12 -4
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/language-select.d.ts +8 -7
- package/dist/components/money-field.d.ts +11 -7
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.js +2 -1
- package/dist/components/month-picker.js.map +1 -1
- package/dist/components/multi-entity-combobox.d.ts +31 -2
- package/dist/components/multi-entity-combobox.js +152 -103
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +8 -7
- package/dist/components/number-input.d.ts +8 -7
- package/dist/components/numpad-sheet.d.ts +8 -7
- package/dist/components/phone-input.d.ts +8 -7
- package/dist/components/reauth-dialog.d.ts +2 -2
- package/dist/components/reauth-dialog.js +6 -8
- package/dist/components/reauth-dialog.js.map +1 -1
- package/dist/components/series-chart.d.ts +8 -7
- package/dist/components/settings-fields.d.ts +8 -7
- package/dist/components/share-card.d.ts +8 -7
- package/dist/components/swatch-picker.d.ts +17 -1
- package/dist/components/swatch-picker.js +10 -4
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/text-link.d.ts +8 -7
- package/dist/components/tile-radio.d.ts +20 -0
- package/dist/components/tile-radio.js +25 -8
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +8 -7
- package/dist/components/toggle-group.js +2 -2
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/trigger-aria.d.ts +3 -0
- package/dist/components/trigger-aria.js +3 -1
- package/dist/components/trigger-aria.js.map +1 -1
- package/dist/components/ui.d.ts +5 -4
- package/dist/components/ui.js +60 -28
- package/dist/components/ui.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +66 -1
- package/dist/feedback/feedback-attachment.js +185 -65
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +66 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +67 -252
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +66 -287
- package/dist/feedback/feedback-thread.js +16 -3
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-DOwPu-Il.d.ts} +1273 -30
- package/dist/feedback.d.ts +65 -3
- package/dist/hooks/use-file-drop.d.ts +8 -7
- package/dist/i18n/defaults.d.ts +8 -7
- package/dist/i18n/defaults.js +3 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +8 -7
- package/dist/i18n/german.js +36 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +5 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +8 -7
- package/dist/i18n/locales/de-CH.d.ts +8 -7
- package/dist/i18n/locales/en.d.ts +8 -7
- package/dist/i18n/locales/en.js +7 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +8 -7
- package/dist/i18n/locales/es.js +37 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +8 -7
- package/dist/i18n/locales/fr.js +36 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +8 -7
- package/dist/i18n/locales/hu.js +38 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +8 -7
- package/dist/i18n/locales/it.js +37 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +8 -7
- package/dist/i18n/locales/zh.js +34 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +8 -7
- package/dist/i18n/review.js +20 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/column-mapping.d.ts +81 -0
- package/dist/lib/column-mapping.js +108 -0
- package/dist/lib/column-mapping.js.map +1 -0
- package/dist/lib/strip-fade.d.ts +2 -2
- package/dist/lib/strip-fade.js.map +1 -1
- package/dist/lib/table-text.d.ts +109 -1
- package/dist/lib/table-text.js +122 -1
- package/dist/lib/table-text.js.map +1 -1
- package/dist/rhf/fields.d.ts +142 -13
- package/dist/rhf/fields.js +185 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +8 -7
- package/dist/rhf.d.ts +9 -8
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +8 -7
- package/dist/shell/top-bar-brand.d.ts +8 -7
- package/dist/shell/topbar-action-menu.d.ts +36 -3
- package/dist/shell/topbar-action-menu.js +74 -33
- package/dist/shell/topbar-action-menu.js.map +1 -1
- package/dist/shell.d.ts +8 -7
- package/dist/table-text.d.ts +1 -1
- package/dist/wizard/stepper-nav.d.ts +38 -9
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +8 -7
- package/package.json +1 -1
- package/src/components/amount-input.tsx +50 -14
- package/src/components/autocomplete.tsx +188 -116
- package/src/components/column-mapper.tsx +768 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +655 -335
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +135 -22
- package/src/components/danger-confirm.tsx +274 -24
- package/src/components/date-picker.tsx +423 -10
- package/src/components/entity-combobox.tsx +243 -129
- package/src/components/field-parts.tsx +224 -5
- package/src/components/field-strip.tsx +149 -0
- package/src/components/form-actions.tsx +52 -5
- package/src/components/icon-picker.tsx +23 -4
- package/src/components/money-field.tsx +3 -0
- package/src/components/month-picker.tsx +2 -1
- package/src/components/multi-entity-combobox.tsx +221 -121
- package/src/components/reauth-dialog.tsx +17 -18
- package/src/components/swatch-picker.tsx +29 -5
- package/src/components/tile-radio.tsx +74 -13
- package/src/components/toggle-group.tsx +2 -2
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +114 -33
- package/src/feedback/feedback-attachment.tsx +372 -72
- package/src/feedback/feedback-dialog.tsx +7 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +112 -9
- package/src/i18n/defaults.ts +2 -0
- package/src/i18n/german.ts +42 -0
- package/src/i18n/kit-labels.tsx +4 -0
- package/src/i18n/locales/en.ts +27 -5
- package/src/i18n/locales/es.ts +41 -0
- package/src/i18n/locales/fr.ts +42 -0
- package/src/i18n/locales/hu.ts +37 -0
- package/src/i18n/locales/it.ts +41 -0
- package/src/i18n/locales/zh.ts +32 -0
- package/src/i18n/review.ts +19 -0
- package/src/index.ts +18 -0
- package/src/lib/column-mapping.ts +234 -0
- package/src/lib/strip-fade.ts +2 -2
- package/src/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +421 -6
- package/src/rhf.ts +2 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/wizard/stepper-nav.tsx +35 -1
- package/dist/components/field-anatomy.d.ts +0 -95
- package/dist/components/field-anatomy.js +0 -84
- package/dist/components/field-anatomy.js.map +0 -1
- package/dist/feedback-attachment-NhUm7Zga.d.ts +0 -367
- package/src/components/field-anatomy.tsx +0 -190
|
@@ -2,10 +2,10 @@ import { createContext, useCallback, useContext, useEffect, useRef, useState } f
|
|
|
2
2
|
import type { ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
import { useKitLabels } from "../i18n/kit-labels";
|
|
5
|
-
import { typedMatches } from "./danger-confirm";
|
|
5
|
+
import { TypedConfirmField, typedMatches } from "./danger-confirm";
|
|
6
6
|
import type { TypedMatch } from "./danger-confirm";
|
|
7
7
|
import { DialogFrame } from "./dialog-frame";
|
|
8
|
-
import { Button
|
|
8
|
+
import { Button } from "./ui";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* The `confirmDialog` namespace of `<UiKitProvider labels>`: the two buttons' fallback
|
|
@@ -300,15 +300,19 @@ function ConfirmDialogView({
|
|
|
300
300
|
confirmRef.current?.click();
|
|
301
301
|
}}
|
|
302
302
|
>
|
|
303
|
-
|
|
303
|
+
{/* The kit's one typed-confirmation field (0.23), DangerConfirm's too: the
|
|
304
|
+
keyboard told to leave the text alone, `data-matched` on the input. The
|
|
305
|
+
label is this namespace's (`confirmDialog.typed`), and `placeholder=""`
|
|
306
|
+
keeps out the `dangerConfirm.phrasePlaceholder` a provider may set for
|
|
307
|
+
the tile — this field never had one. */}
|
|
308
|
+
<TypedConfirmField
|
|
304
309
|
ref={typedRef}
|
|
310
|
+
target={target}
|
|
311
|
+
match={request.typedMatch ?? "caseless"}
|
|
305
312
|
label={typedLabel}
|
|
313
|
+
placeholder=""
|
|
306
314
|
value={typed}
|
|
307
|
-
|
|
308
|
-
autoCapitalize="off"
|
|
309
|
-
autoCorrect="off"
|
|
310
|
-
spellCheck={false}
|
|
311
|
-
onChange={(e) => setTyped(e.target.value)}
|
|
315
|
+
onValueChange={setTyped}
|
|
312
316
|
/>
|
|
313
317
|
</form>
|
|
314
318
|
)}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useId, useMemo } from "react";
|
|
2
|
-
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
1
|
+
import { useCallback, useId, useMemo } from "react";
|
|
2
|
+
import type { ComponentPropsWithoutRef, ReactNode, Ref } from "react";
|
|
3
|
+
import { X } from "lucide-react";
|
|
3
4
|
import { cn } from "../lib/cn";
|
|
4
5
|
import { COUNTRY_CODES, countryName, normalizeCountryCode } from "../lib/countries";
|
|
5
6
|
import { FieldChevron, FIELD_FLOATING_PAD, FIELD_INVALID, FIELD_TRIGGER, FloatingField } from "./ui";
|
|
@@ -11,9 +12,14 @@ import {
|
|
|
11
12
|
useComboboxFieldError,
|
|
12
13
|
type ComboOption,
|
|
13
14
|
} from "./combobox-core";
|
|
14
|
-
import { hasMessage, mergeDescribedBy } from "./choice-parts";
|
|
15
|
+
import { assignRef, hasMessage, mergeDescribedBy } from "./choice-parts";
|
|
15
16
|
import { useCommitReason } from "./write-lock";
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
DEFAULT_COMBOBOX_LABELS,
|
|
19
|
+
DEFAULT_COMMON_LABELS,
|
|
20
|
+
useKitLabels,
|
|
21
|
+
useKitLocale,
|
|
22
|
+
} from "../i18n/kit-labels";
|
|
17
23
|
|
|
18
24
|
// ── Labels ────────────────────────────────────────────────────────────────────
|
|
19
25
|
|
|
@@ -44,15 +50,46 @@ export const DEFAULT_COUNTRY_SELECT_LABELS: CountrySelectLabels = {
|
|
|
44
50
|
* `onChange` is the kit's — "a country was picked", carrying its code — rather than
|
|
45
51
|
* the div's form event. `id` and the `aria-*` wiring below go to the TRIGGER, which is
|
|
46
52
|
* the control a reader meets; everything else in `rest` dresses the wrapper.
|
|
53
|
+
*
|
|
54
|
+
* `Clearable` follows `clearable`, the way {@link EntityCombobox}'s `C` follows its
|
|
55
|
+
* `clearValue`: it is inferred from the prop, so `onChange` is `(code: string) => void`
|
|
56
|
+
* for the field as it always was (`onChange={setCountry}` over a `string` state still
|
|
57
|
+
* type-checks), and `(code: string | null) => void` once `clearable` is set — the
|
|
58
|
+
* `null` of a clear cannot reach a handler that was never told about it.
|
|
47
59
|
*/
|
|
48
|
-
export interface CountrySelectProps
|
|
60
|
+
export interface CountrySelectProps<Clearable extends boolean = false>
|
|
49
61
|
extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "defaultValue"> {
|
|
50
62
|
/** The chosen country as an ISO 3166-1 alpha-2 code ("CH"), in either case;
|
|
51
63
|
* `null`, `undefined` or `""` when none is. A code outside the list still shows
|
|
52
64
|
* its name — the list restricts what can be PICKED, not what can be shown. */
|
|
53
65
|
value: string | null | undefined;
|
|
54
|
-
/** A country was picked: its alpha-2 code, always upper-case.
|
|
55
|
-
|
|
66
|
+
/** A country was picked: its alpha-2 code, always upper-case. With `clearable`,
|
|
67
|
+
* also `null`: the "×" (or Delete on the trigger) emptied the field. */
|
|
68
|
+
onChange: (code: Clearable extends true ? string | null : string) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Offer a clear "×" in place of the chevron while a country is chosen; it hands
|
|
71
|
+
* `onChange` `null` (the kit's later list: an optional country — a contact's
|
|
72
|
+
* nationality, a bank filter's "any country" — could be set but never unset again).
|
|
73
|
+
*
|
|
74
|
+
* The combobox family's clear, as {@link EntityCombobox} draws it: a pointer
|
|
75
|
+
* shortcut inside the trigger, out of the tab order so it is not a second stop
|
|
76
|
+
* between this field and the next, named by `clearLabel` (default: the
|
|
77
|
+
* `combobox.clear` label every picker in the family shares). The keyboard way is
|
|
78
|
+
* Delete or Backspace on the closed trigger. Not offered while `disabled` or locked
|
|
79
|
+
* (`commit` / `disabledReason`): a clear is a change like any other.
|
|
80
|
+
*/
|
|
81
|
+
clearable?: Clearable;
|
|
82
|
+
/** The clear "×"'s accessible name. Wins over `combobox.clear` from the
|
|
83
|
+
* {@link UiKitProvider}, as {@link EntityCombobox}'s `clearLabel` does. */
|
|
84
|
+
clearLabel?: string;
|
|
85
|
+
/**
|
|
86
|
+
* The TRIGGER — the focusable `<button>` a reader meets — as a React 19 ref prop,
|
|
87
|
+
* the way {@link MoneyField} takes its input's (kastlan: react-hook-form's
|
|
88
|
+
* focus-on-error calls `focus()` on whatever `field.ref` was handed, and without a
|
|
89
|
+
* ref the first field in error on an address form was never focused). See
|
|
90
|
+
* `RhfCountrySelect`, which passes it.
|
|
91
|
+
*/
|
|
92
|
+
ref?: Ref<HTMLButtonElement>;
|
|
56
93
|
/**
|
|
57
94
|
* Offer only these codes (any case; duplicates and malformed entries dropped).
|
|
58
95
|
* Default: every ISO 3166-1 country, {@link COUNTRY_CODES}. Sorted by name either
|
|
@@ -92,8 +129,8 @@ export interface CountrySelectProps
|
|
|
92
129
|
error?: ReactNode;
|
|
93
130
|
/** Required and unanswered — {@link FIELD_INVALID}, as on every field. */
|
|
94
131
|
invalid?: boolean;
|
|
95
|
-
/** The empty trigger's text. Default: nothing when the field has a `label`
|
|
96
|
-
* label already says what goes here), else `labels.country`. */
|
|
132
|
+
/** The empty trigger's text. Default: nothing when the field has a `label` or an
|
|
133
|
+
* `aria-labelledby` (the label already says what goes here), else `labels.country`. */
|
|
97
134
|
placeholder?: string;
|
|
98
135
|
/** As on the other pickers: no focus, no list, the settled look. */
|
|
99
136
|
disabled?: boolean;
|
|
@@ -117,6 +154,15 @@ export interface CountrySelectProps
|
|
|
117
154
|
/** Per-instance overrides of {@link CountrySelectLabels}, over the provider's
|
|
118
155
|
* `countrySelect` namespace, over English. */
|
|
119
156
|
labels?: Partial<CountrySelectLabels>;
|
|
157
|
+
/**
|
|
158
|
+
* Name the trigger by an element of the caller's — a form's own label above the
|
|
159
|
+
* field, which is how `RhfCountrySelect` names it. The trigger is then named by
|
|
160
|
+
* that element AND the chosen country (two references, as {@link DatePicker}'s
|
|
161
|
+
* trigger is named, so "Country Switzerland" needs no punctuation to translate),
|
|
162
|
+
* instead of the composed "label: country". An `aria-label` still wins. Before
|
|
163
|
+
* 0.23 this landed on the wrapper `<div>`, which has no role to be named.
|
|
164
|
+
*/
|
|
165
|
+
"aria-labelledby"?: string;
|
|
120
166
|
}
|
|
121
167
|
|
|
122
168
|
/** Fold a name for search: no case, no accents — "osterreich" finds Österreich, "cote"
|
|
@@ -192,8 +238,11 @@ function collator(locale: string): Intl.Collator {
|
|
|
192
238
|
*
|
|
193
239
|
* Field anatomy as on every field: floating `label`, `hint`, `error`, `invalid`; the
|
|
194
240
|
* trigger is named "label: value" as the other trigger pickers are.
|
|
241
|
+
*
|
|
242
|
+
* Since 0.23 it takes a `ref` to the trigger (react-hook-form's focus-on-error, kastlan)
|
|
243
|
+
* and `clearable`, which hands `onChange` `null`.
|
|
195
244
|
*/
|
|
196
|
-
export function CountrySelect({
|
|
245
|
+
export function CountrySelect<Clearable extends boolean = false>({
|
|
197
246
|
value,
|
|
198
247
|
onChange,
|
|
199
248
|
countries,
|
|
@@ -207,18 +256,25 @@ export function CountrySelect({
|
|
|
207
256
|
disabled,
|
|
208
257
|
disabledReason: ownReason,
|
|
209
258
|
commit,
|
|
259
|
+
clearable,
|
|
260
|
+
clearLabel,
|
|
210
261
|
locale: localeProp,
|
|
211
262
|
labels: labelsProp,
|
|
212
263
|
className,
|
|
213
264
|
id,
|
|
265
|
+
ref,
|
|
214
266
|
"aria-label": ariaLabel,
|
|
267
|
+
"aria-labelledby": ariaLabelledBy,
|
|
215
268
|
"aria-describedby": ariaDescribedBy,
|
|
216
269
|
"aria-invalid": ariaInvalid,
|
|
217
270
|
"aria-required": ariaRequired,
|
|
218
271
|
...rest
|
|
219
|
-
}: CountrySelectProps) {
|
|
272
|
+
}: CountrySelectProps<Clearable>) {
|
|
220
273
|
const text = useKitLabels("countrySelect", DEFAULT_COUNTRY_SELECT_LABELS, labelsProp);
|
|
221
274
|
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
275
|
+
// The clear's name is the family's (`combobox.clear`), as on EntityCombobox; a prop
|
|
276
|
+
// left `undefined` falls through to the provider rather than masking it.
|
|
277
|
+
const comboText = useKitLabels("combobox", DEFAULT_COMBOBOX_LABELS, { clear: clearLabel });
|
|
222
278
|
// English without a locale — see `countryName`.
|
|
223
279
|
const locale = useKitLocale(localeProp) ?? "en";
|
|
224
280
|
|
|
@@ -268,6 +324,15 @@ export function CountrySelect({
|
|
|
268
324
|
);
|
|
269
325
|
const core = useComboboxCore<string>({ options: allOptions, filter: false });
|
|
270
326
|
const { open, setOpen, query, triggerRef, closeToTrigger } = core;
|
|
327
|
+
// The core anchors the panel to the trigger and hands focus back to it; the caller's
|
|
328
|
+
// `ref` wants the same element. One callback feeds both.
|
|
329
|
+
const setTrigger = useCallback(
|
|
330
|
+
(el: HTMLButtonElement | null) => {
|
|
331
|
+
triggerRef.current = el;
|
|
332
|
+
assignRef(ref, el);
|
|
333
|
+
},
|
|
334
|
+
[ref, triggerRef],
|
|
335
|
+
);
|
|
271
336
|
|
|
272
337
|
const q = fold(query.trim());
|
|
273
338
|
const results = useMemo(() => {
|
|
@@ -306,19 +371,40 @@ export function CountrySelect({
|
|
|
306
371
|
|
|
307
372
|
const code = normalizeCountryCode(value);
|
|
308
373
|
const selectedName = code ? countryName(code, locale) : undefined;
|
|
309
|
-
|
|
374
|
+
// A field named by a label of the caller's (`aria-labelledby`) is a labelled field too:
|
|
375
|
+
// the label already says what goes here.
|
|
376
|
+
const emptyText = placeholder ?? (label === undefined && !ariaLabelledBy ? text.country : "");
|
|
310
377
|
const fieldName = typeof label === "string" ? label : text.country;
|
|
378
|
+
const valueId = `${generated}-value`;
|
|
379
|
+
// Named by the caller's element (a form's label) — then the shown text is the second
|
|
380
|
+
// reference, as the composed name below has the value second; but only when it IS a
|
|
381
|
+
// value or the caller's placeholder, never the `country` fallback word.
|
|
382
|
+
const labelledBy =
|
|
383
|
+
ariaLabel === undefined && ariaLabelledBy
|
|
384
|
+
? mergeDescribedBy(ariaLabelledBy, (selectedName !== undefined || Boolean(placeholder)) && valueId)
|
|
385
|
+
: undefined;
|
|
311
386
|
const name =
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
387
|
+
labelledBy !== undefined
|
|
388
|
+
? undefined
|
|
389
|
+
: (ariaLabel ??
|
|
390
|
+
(selectedName !== undefined
|
|
391
|
+
? common.fieldValue(fieldName, selectedName)
|
|
392
|
+
: placeholder
|
|
393
|
+
? common.fieldValue(fieldName, placeholder)
|
|
394
|
+
: fieldName));
|
|
395
|
+
|
|
396
|
+
const showClear = Boolean(clearable && code && !inert);
|
|
397
|
+
// `onChange` widened to take the clear's `null` — its type says so once `clearable`
|
|
398
|
+
// is set, and the clear is only ever offered then.
|
|
399
|
+
const emit = onChange as (code: string | null) => void;
|
|
400
|
+
const clear = () => {
|
|
401
|
+
if (!showClear) return;
|
|
402
|
+
emit(null);
|
|
403
|
+
};
|
|
318
404
|
|
|
319
405
|
const choose = (o: ComboOption<string>) => {
|
|
320
406
|
if (inert) return;
|
|
321
|
-
|
|
407
|
+
emit(o.value);
|
|
322
408
|
// Back to the trigger: the panel holding focus is about to unmount, and a keyboard
|
|
323
409
|
// user who has answered this field should stand on it, ready to Tab on.
|
|
324
410
|
closeToTrigger();
|
|
@@ -326,7 +412,7 @@ export function CountrySelect({
|
|
|
326
412
|
|
|
327
413
|
const trigger = (
|
|
328
414
|
<button
|
|
329
|
-
ref={
|
|
415
|
+
ref={setTrigger}
|
|
330
416
|
id={triggerId}
|
|
331
417
|
type="button"
|
|
332
418
|
// A closed choice that expands into the list below — the role every trigger
|
|
@@ -337,6 +423,7 @@ export function CountrySelect({
|
|
|
337
423
|
aria-controls={listboxId}
|
|
338
424
|
aria-expanded={open}
|
|
339
425
|
aria-label={name}
|
|
426
|
+
aria-labelledby={labelledBy}
|
|
340
427
|
aria-invalid={field.isInvalid || undefined}
|
|
341
428
|
aria-required={ariaRequired}
|
|
342
429
|
aria-describedby={mergeDescribedBy(field.describedBy, locked && reasonId)}
|
|
@@ -358,6 +445,10 @@ export function CountrySelect({
|
|
|
358
445
|
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
359
446
|
e.preventDefault();
|
|
360
447
|
setOpen(true);
|
|
448
|
+
} else if (showClear && !open && (e.key === "Delete" || e.key === "Backspace")) {
|
|
449
|
+
// The keyboard's clear: the "×" is out of the tab order (see `clearable`).
|
|
450
|
+
e.preventDefault();
|
|
451
|
+
clear();
|
|
361
452
|
}
|
|
362
453
|
}}
|
|
363
454
|
className={cn(
|
|
@@ -373,11 +464,33 @@ export function CountrySelect({
|
|
|
373
464
|
{flags && code && <CurrencyFlag country={code.toLowerCase()} />}
|
|
374
465
|
{/* A long name ("Saint Vincent and the Grenadines") truncates rather than
|
|
375
466
|
widening the field: the kit is checked at 390px. */}
|
|
376
|
-
<span
|
|
467
|
+
<span
|
|
468
|
+
id={valueId}
|
|
469
|
+
className={cn("truncate", selectedName === undefined && "text-[var(--text-placeholder)]")}
|
|
470
|
+
>
|
|
377
471
|
{selectedName ?? emptyText}
|
|
378
472
|
</span>
|
|
379
473
|
</span>
|
|
380
|
-
|
|
474
|
+
{showClear ? (
|
|
475
|
+
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events -- tabIndex -1 inside
|
|
476
|
+
the trigger <button>, so it never holds focus and a key handler here could never
|
|
477
|
+
fire; the keys go to the trigger, where Delete and Backspace clear. A pointer
|
|
478
|
+
shortcut, as on EntityCombobox. */
|
|
479
|
+
<span
|
|
480
|
+
role="button"
|
|
481
|
+
tabIndex={-1}
|
|
482
|
+
aria-label={comboText.clear}
|
|
483
|
+
onClick={(e) => {
|
|
484
|
+
e.stopPropagation();
|
|
485
|
+
clear();
|
|
486
|
+
}}
|
|
487
|
+
className="absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]"
|
|
488
|
+
>
|
|
489
|
+
<X aria-hidden className="size-4" />
|
|
490
|
+
</span>
|
|
491
|
+
) : (
|
|
492
|
+
<FieldChevron />
|
|
493
|
+
)}
|
|
381
494
|
</button>
|
|
382
495
|
);
|
|
383
496
|
|