@eifi1/ui-kit 0.22.0 → 0.23.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 +11 -8
- package/dist/components/amount-input.d.ts +28 -8
- package/dist/components/amount-input.js +3 -1
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +21 -0
- package/dist/components/autocomplete.js +128 -103
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +11 -8
- package/dist/components/calculator.d.ts +11 -8
- package/dist/components/column-mapper.d.ts +182 -0
- package/dist/components/column-mapper.js +375 -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 +80 -2
- package/dist/components/combobox.js +448 -311
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +11 -8
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +8 -5
- 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 +11 -8
- package/dist/components/danger-confirm.js +13 -3
- 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 +35 -1
- package/dist/components/entity-combobox.js +148 -112
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +11 -8
- 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 +11 -8
- package/dist/components/file-dropzone.d.ts +11 -8
- package/dist/components/form-actions.d.ts +10 -7
- package/dist/components/form-actions.js +8 -2
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +11 -8
- 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 +11 -8
- package/dist/components/money-field.d.ts +14 -8
- 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 +26 -1
- package/dist/components/multi-entity-combobox.js +143 -102
- package/dist/components/multi-entity-combobox.js.map +1 -1
- package/dist/components/number-field.d.ts +11 -8
- package/dist/components/number-input.d.ts +11 -8
- package/dist/components/numpad-sheet.d.ts +11 -8
- package/dist/components/phone-input.d.ts +11 -8
- 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 +11 -8
- package/dist/components/settings-fields.d.ts +11 -8
- package/dist/components/share-card.d.ts +10 -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 +10 -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 +11 -8
- 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 +8 -5
- package/dist/components/ui.js +5 -11
- package/dist/components/ui.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-attachment.js +156 -56
- package/dist/feedback/feedback-attachment.js.map +1 -1
- package/dist/feedback/feedback-dialog.d.ts +1 -1
- package/dist/feedback/feedback-dialog.js.map +1 -1
- package/dist/feedback/feedback-inbox.d.ts +3 -2
- package/dist/feedback/feedback-inbox.js.map +1 -1
- package/dist/feedback/feedback-thread.d.ts +68 -287
- package/dist/feedback/feedback-thread.js +8 -1
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-R8mIAc9N.d.ts → feedback-BxeQVzwq.d.ts} +405 -22
- package/dist/{feedback-attachment-NhUm7Zga.d.ts → feedback-attachment-fGAzZPf0.d.ts} +112 -10
- package/dist/feedback.d.ts +66 -2
- package/dist/hooks/use-file-drop.d.ts +11 -8
- package/dist/i18n/defaults.d.ts +11 -8
- package/dist/i18n/defaults.js +3 -1
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +11 -8
- package/dist/i18n/german.js +36 -2
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +7 -4
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +11 -8
- package/dist/i18n/locales/de-CH.d.ts +11 -8
- package/dist/i18n/locales/en.d.ts +11 -8
- package/dist/i18n/locales/en.js +7 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +11 -8
- package/dist/i18n/locales/es.js +37 -2
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +11 -8
- package/dist/i18n/locales/fr.js +36 -2
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +11 -8
- package/dist/i18n/locales/hu.js +38 -2
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +11 -8
- package/dist/i18n/locales/it.js +37 -2
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +11 -8
- package/dist/i18n/locales/zh.js +34 -2
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +11 -8
- package/dist/i18n/review.js +20 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +7 -3
- 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/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 +89 -11
- package/dist/rhf/fields.js +124 -0
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +11 -8
- package/dist/rhf.d.ts +12 -9
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +10 -7
- package/dist/shell/top-bar-brand.d.ts +11 -8
- 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 +10 -7
- package/dist/table-text.d.ts +1 -1
- package/dist/wizard/stepper-nav.d.ts +41 -10
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +11 -8
- package/package.json +1 -1
- package/src/components/amount-input.tsx +20 -1
- package/src/components/autocomplete.tsx +172 -111
- package/src/components/column-mapper.tsx +666 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +614 -332
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +135 -22
- package/src/components/danger-confirm.tsx +91 -12
- package/src/components/date-picker.tsx +423 -10
- package/src/components/entity-combobox.tsx +217 -126
- package/src/components/field-parts.tsx +224 -5
- package/src/components/field-strip.tsx +149 -0
- package/src/components/form-actions.tsx +32 -4
- 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 +207 -120
- 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 +7 -33
- package/src/feedback/feedback-attachment.tsx +308 -61
- package/src/feedback/feedback-dialog.tsx +7 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +47 -4
- 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/table-text.ts +271 -0
- package/src/rhf/fields.tsx +254 -0
- 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/src/components/field-anatomy.tsx +0 -190
|
@@ -5,13 +5,25 @@ import { cn } from "../lib/cn";
|
|
|
5
5
|
import { FieldChevron, FieldLabel, FIELD_TRIGGER, FIELD_FLOATING_PAD, FIELD_INVALID } from "./ui";
|
|
6
6
|
import {
|
|
7
7
|
ComboboxPanel,
|
|
8
|
+
endHintRowProps,
|
|
9
|
+
offersCreate,
|
|
8
10
|
useComboboxCore,
|
|
9
11
|
useComboboxFieldError,
|
|
10
12
|
type ComboClearValue,
|
|
11
13
|
type ComboOption,
|
|
12
14
|
} from "./combobox-core";
|
|
13
15
|
import { DEFAULT_COMBOBOX_LABELS, DEFAULT_COMMON_LABELS, useKitLabels } from "../i18n/kit-labels";
|
|
14
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
EndHintRow,
|
|
18
|
+
FieldCaption,
|
|
19
|
+
LABEL_IN_ROW,
|
|
20
|
+
LockedReason,
|
|
21
|
+
StaticLabelRow,
|
|
22
|
+
useFieldHint,
|
|
23
|
+
useLockReason,
|
|
24
|
+
} from "./field-parts";
|
|
25
|
+
import { mergeDescribedBy } from "./choice-parts";
|
|
26
|
+
import { useCommitReason } from "./write-lock";
|
|
15
27
|
|
|
16
28
|
export type { ComboClearValue, ComboOption } from "./combobox-core";
|
|
17
29
|
|
|
@@ -46,6 +58,10 @@ export interface EntityComboboxProps<V extends string | number, C extends ComboC
|
|
|
46
58
|
placeholder?: string;
|
|
47
59
|
searchPlaceholder?: string;
|
|
48
60
|
emptyLabel?: string;
|
|
61
|
+
/** Offer a clear "×" in place of the chevron while something is chosen; it emits
|
|
62
|
+
* `clearValue`. A pointer shortcut inside the trigger, out of the tab order — the
|
|
63
|
+
* keyboard's way is Delete or Backspace on the CLOSED trigger (0.23, as
|
|
64
|
+
* {@link CountrySelect}). Not offered while `disabled` or locked. */
|
|
49
65
|
clearable?: boolean;
|
|
50
66
|
clearLabel?: string;
|
|
51
67
|
/** The phone sheet's close button. Its own prop rather than a reuse of
|
|
@@ -54,9 +70,39 @@ export interface EntityComboboxProps<V extends string | number, C extends ComboC
|
|
|
54
70
|
* so it is the one control here that MUST be in the reader's language. */
|
|
55
71
|
closeLabel?: string;
|
|
56
72
|
disabled?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Why the choice cannot be changed — {@link Button}'s `disabledReason`, for a picker
|
|
75
|
+
* that SAVES on change (keksdose G2: the statement's account, where picking IS the
|
|
76
|
+
* write). The trigger stays focusable and `aria-disabled`, the list does not open,
|
|
77
|
+
* the clear "×" is gone and `onChange` never fires; the reason is in the kit
|
|
78
|
+
* {@link Tooltip} and on the trigger's `aria-describedby`. Wins over `disabled`.
|
|
79
|
+
* {@link CountrySelect}'s lock, which is built on the same core.
|
|
80
|
+
*/
|
|
81
|
+
disabledReason?: ReactNode;
|
|
82
|
+
/**
|
|
83
|
+
* This picker COMMITS — choosing saves. Under a locked {@link WriteLockProvider} it is
|
|
84
|
+
* locked the `disabledReason` way with the lock's reason (which wins over its own).
|
|
85
|
+
* A picker inside a form with its own Save stays editable under the lock: leave this
|
|
86
|
+
* off there and put `commit` on the Save. No provider, or an unlocked one: no effect.
|
|
87
|
+
*/
|
|
88
|
+
commit?: boolean;
|
|
57
89
|
/** When set, a "create" row appears for a non-empty query with no exact match. */
|
|
58
90
|
onCreate?: (query: string) => void;
|
|
59
91
|
createLabel?: (query: string) => string;
|
|
92
|
+
/**
|
|
93
|
+
* Offer the create row with NOTHING typed too, worded as this — keksdose G9's
|
|
94
|
+
* "Create cash account", which mints a record under a default name and so needs no
|
|
95
|
+
* query. `onCreate` then receives `""`. Left out, the row needs a query, as before.
|
|
96
|
+
* See {@link InlineEntityCombobox}'s `createEmptyLabel`.
|
|
97
|
+
*/
|
|
98
|
+
createEmptyLabel?: string;
|
|
99
|
+
/**
|
|
100
|
+
* The create row WRITES (it mints the record on the server) while picking does not.
|
|
101
|
+
* Under a locked {@link WriteLockProvider} the row stays in the list but cannot be
|
|
102
|
+
* taken, with the lock's reason as its second line — the picker itself stays live.
|
|
103
|
+
* keksdose dev#496: on the read-only demo choosing is draft state, minting is not.
|
|
104
|
+
*/
|
|
105
|
+
createCommit?: boolean;
|
|
60
106
|
/** Required and unanswered — {@link FIELD_INVALID}. See {@link Input}'s `invalid`. */
|
|
61
107
|
invalid?: boolean;
|
|
62
108
|
/** What is wrong with the value, as {@link Input}'s `error`: rendered under the
|
|
@@ -88,42 +134,49 @@ export interface EntityComboboxProps<V extends string | number, C extends ComboC
|
|
|
88
134
|
* the shared field/anchor/dismiss/search primitives (no cmdk/Radix). For picking
|
|
89
135
|
* several entities use {@link MultiEntityCombobox}.
|
|
90
136
|
*/
|
|
91
|
-
export function EntityCombobox<V extends string | number, C extends ComboClearValue = null>(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
export function EntityCombobox<V extends string | number, C extends ComboClearValue = null>(
|
|
138
|
+
props: EntityComboboxProps<V, C>,
|
|
139
|
+
) {
|
|
140
|
+
const {
|
|
141
|
+
value,
|
|
142
|
+
onChange,
|
|
143
|
+
clearValue = null as C,
|
|
144
|
+
options,
|
|
145
|
+
loadOptions,
|
|
146
|
+
loading,
|
|
147
|
+
label,
|
|
148
|
+
placeholder,
|
|
149
|
+
searchPlaceholder,
|
|
150
|
+
emptyLabel,
|
|
151
|
+
clearable,
|
|
152
|
+
clearLabel,
|
|
153
|
+
closeLabel,
|
|
154
|
+
disabled,
|
|
155
|
+
disabledReason,
|
|
156
|
+
commit,
|
|
157
|
+
onCreate,
|
|
158
|
+
createLabel,
|
|
159
|
+
createEmptyLabel,
|
|
160
|
+
createCommit,
|
|
161
|
+
className,
|
|
162
|
+
invalid,
|
|
163
|
+
error,
|
|
164
|
+
hint,
|
|
165
|
+
filter,
|
|
166
|
+
minChars,
|
|
167
|
+
debounceMs,
|
|
168
|
+
loadErrorLabel,
|
|
169
|
+
"aria-label": ariaLabel,
|
|
170
|
+
// The control's own wiring, taken off `rest` so it lands on the TRIGGER rather than
|
|
171
|
+
// the wrapper — see MultiEntityCombobox: `Field`'s render-prop spreads `{ id,
|
|
172
|
+
// aria-describedby, aria-invalid, aria-required }`, and on the role-less wrapper
|
|
173
|
+
// div the label's `htmlFor` named nothing and the hint described nothing.
|
|
174
|
+
id,
|
|
175
|
+
"aria-describedby": ariaDescribedBy,
|
|
176
|
+
"aria-invalid": ariaInvalid,
|
|
177
|
+
"aria-required": ariaRequired,
|
|
178
|
+
...rest
|
|
179
|
+
} = props;
|
|
127
180
|
const core = useComboboxCore<V>({
|
|
128
181
|
options,
|
|
129
182
|
loadOptions,
|
|
@@ -148,6 +201,16 @@ export function EntityCombobox<V extends string | number, C extends ComboClearVa
|
|
|
148
201
|
});
|
|
149
202
|
const common = useKitLabels("common", DEFAULT_COMMON_LABELS);
|
|
150
203
|
const { open, results, resolve, setOpen, rememberOption, query, triggerRef } = core;
|
|
204
|
+
// The lock: this picker's own reason, or the provider's under `commit`.
|
|
205
|
+
const lock = useLockReason(commit, disabledReason);
|
|
206
|
+
const inert = lock.locked || Boolean(disabled);
|
|
207
|
+
// A lock arriving while the list is up closes it — adjusted while
|
|
208
|
+
// rendering, as the core adjusts its own query, so no frame offers a pick that would
|
|
209
|
+
// be thrown away.
|
|
210
|
+
if (lock.locked && open) setOpen(false);
|
|
211
|
+
// The create row's own lock, for a picker whose choosing is draft and whose create
|
|
212
|
+
// is the write (`createCommit`).
|
|
213
|
+
const createReason = useCommitReason(createCommit, undefined);
|
|
151
214
|
// One id per instance, generated here rather than in the core: `aria-controls` on
|
|
152
215
|
// the trigger has to name the list while the list is still closed, so the id
|
|
153
216
|
// belongs to whoever renders both ends of it.
|
|
@@ -157,13 +220,28 @@ export function EntityCombobox<V extends string | number, C extends ComboClearVa
|
|
|
157
220
|
const chosen: V | null = value == null || value === clearValue ? null : (value as V);
|
|
158
221
|
const selectedOption = chosen == null ? null : resolve(chosen);
|
|
159
222
|
const q = query.trim();
|
|
160
|
-
const showCreate =
|
|
161
|
-
|
|
162
|
-
|
|
223
|
+
const showCreate = offersCreate(
|
|
224
|
+
onCreate,
|
|
225
|
+
q,
|
|
226
|
+
results.map((o) => o.label),
|
|
227
|
+
createEmptyLabel,
|
|
228
|
+
);
|
|
229
|
+
const showClear = Boolean(clearable && chosen != null && !inert);
|
|
163
230
|
/** What the closed control is showing — the second half of its accessible name. */
|
|
164
231
|
const triggerText = selectedOption?.label ?? placeholder ?? "";
|
|
232
|
+
// While locked the trigger sits in the reason's Tooltip — in a fragment with a hidden
|
|
233
|
+
// copy of the reason, Button's anatomy — `block` so a full-width field stays full width.
|
|
234
|
+
const withLock = (trigger: ReactNode) =>
|
|
235
|
+
lock.locked ? (
|
|
236
|
+
<LockedReason lock={lock} className="block">
|
|
237
|
+
{trigger}
|
|
238
|
+
</LockedReason>
|
|
239
|
+
) : (
|
|
240
|
+
trigger
|
|
241
|
+
);
|
|
165
242
|
|
|
166
243
|
const choose = (o: ComboOption<V>) => {
|
|
244
|
+
if (lock.locked) return;
|
|
167
245
|
rememberOption(o);
|
|
168
246
|
onChange(o.value);
|
|
169
247
|
// Back to the trigger, not to <body>: the panel that held focus is about to
|
|
@@ -179,94 +257,105 @@ export function EntityCombobox<V extends string | number, C extends ComboClearVa
|
|
|
179
257
|
// handlers that open the panel cannot be clobbered from outside.
|
|
180
258
|
<div {...rest} className={cn("relative", className)}>
|
|
181
259
|
{label !== undefined && hintParts.labelHint === undefined && <FieldLabel>{label}</FieldLabel>}
|
|
182
|
-
<EndHintRow hint
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
disabled={disabled}
|
|
211
|
-
aria-invalid={field.isInvalid || undefined}
|
|
212
|
-
aria-describedby={field.describedBy}
|
|
213
|
-
aria-required={ariaRequired}
|
|
214
|
-
onClick={() => !disabled && setOpen((o) => !o)}
|
|
215
|
-
// Down/Up opens the list from the closed trigger, per the APG. Enter and
|
|
216
|
-
// Space already do it through the button's own click.
|
|
217
|
-
onKeyDown={(e) => {
|
|
218
|
-
if (disabled) return;
|
|
219
|
-
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
220
|
-
e.preventDefault();
|
|
221
|
-
setOpen(true);
|
|
260
|
+
<EndHintRow {...endHintRowProps("hint" in props, label !== undefined, hintParts.labelHint)}>
|
|
261
|
+
{withLock(
|
|
262
|
+
<button
|
|
263
|
+
ref={triggerRef}
|
|
264
|
+
id={id}
|
|
265
|
+
type="button"
|
|
266
|
+
// A combobox, not a button. The distinction is not pedantry: this control
|
|
267
|
+
// carried `aria-invalid`, which `button` does not support, so a required
|
|
268
|
+
// field left empty painted a rose border and told a reader nothing at all —
|
|
269
|
+
// and ESLint flagged it as exactly that (`role-supports-aria-props`). The
|
|
270
|
+
// fix the audit asked for is the role that describes what this IS: a closed
|
|
271
|
+
// choice that expands into the list named below. `combobox` supports
|
|
272
|
+
// `aria-invalid`, so the border and the announcement finally agree.
|
|
273
|
+
role="combobox"
|
|
274
|
+
aria-expanded={open}
|
|
275
|
+
aria-controls={listboxId}
|
|
276
|
+
aria-haspopup="listbox"
|
|
277
|
+
// The label is a floating <span>, not a <label for>, so without this the
|
|
278
|
+
// trigger's accessible name is whatever value happens to be selected —
|
|
279
|
+
// "Checking" with nothing saying it is the account. The label AND the
|
|
280
|
+
// value, because `aria-label` replaces the content rather than adding to
|
|
281
|
+
// it, and a control that announces only its name has lost the answer.
|
|
282
|
+
// A caller's own name wins over the composition: two fields labelled
|
|
283
|
+
// "Account" on a transfer form are the from and the to, and only the
|
|
284
|
+
// caller knows which is which.
|
|
285
|
+
aria-label={
|
|
286
|
+
ariaLabel ??
|
|
287
|
+
(typeof label === "string" ? common.fieldValue(label, triggerText) : undefined)
|
|
222
288
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
{
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
|
|
243
|
-
// agrees on.
|
|
244
|
-
!selectedOption && "text-[var(--text-placeholder)]",
|
|
245
|
-
)}
|
|
246
|
-
>
|
|
247
|
-
{selectedOption?.label ?? placeholder ?? ""}
|
|
248
|
-
</span>
|
|
249
|
-
</span>
|
|
250
|
-
{showClear ? (
|
|
251
|
-
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events -- tabIndex -1 inside
|
|
252
|
-
the trigger <button>, so it never holds focus and a key handler here could
|
|
253
|
-
never fire; the keys go to the trigger. A pointer shortcut only. */
|
|
254
|
-
<span
|
|
255
|
-
role="button"
|
|
256
|
-
tabIndex={-1}
|
|
257
|
-
aria-label={labels.clear}
|
|
258
|
-
onClick={(e) => {
|
|
259
|
-
e.stopPropagation();
|
|
289
|
+
// The focusable kind of disabled while locked (Button's rule): a native
|
|
290
|
+
// `disabled` would take the field out of the tab order, and the reason with it.
|
|
291
|
+
disabled={lock.locked ? undefined : disabled}
|
|
292
|
+
aria-disabled={lock.locked || undefined}
|
|
293
|
+
aria-invalid={field.isInvalid || undefined}
|
|
294
|
+
aria-describedby={mergeDescribedBy(field.describedBy, lock.locked && lock.reasonId)}
|
|
295
|
+
aria-required={ariaRequired}
|
|
296
|
+
onClick={() => !inert && setOpen((o) => !o)}
|
|
297
|
+
// Down/Up opens the list from the closed trigger, per the APG. Enter and
|
|
298
|
+
// Space already do it through the button's own click.
|
|
299
|
+
onKeyDown={(e) => {
|
|
300
|
+
if (inert) return;
|
|
301
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
302
|
+
e.preventDefault();
|
|
303
|
+
setOpen(true);
|
|
304
|
+
} else if (showClear && !open && (e.key === "Delete" || e.key === "Backspace")) {
|
|
305
|
+
// The keyboard's clear: the "×" is a pointer shortcut out of the tab
|
|
306
|
+
// order, so without this a cleared-by-mouse field was set-only from the
|
|
307
|
+
// keyboard. The closed trigger only — CountrySelect's rule (0.23).
|
|
308
|
+
e.preventDefault();
|
|
260
309
|
onChange(clearValue);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
310
|
+
}
|
|
311
|
+
}}
|
|
312
|
+
className={cn(
|
|
313
|
+
FIELD_TRIGGER,
|
|
314
|
+
"pe-9",
|
|
315
|
+
label !== undefined && FIELD_FLOATING_PAD,
|
|
316
|
+
inert && "cursor-not-allowed opacity-50",
|
|
317
|
+
field.isInvalid && FIELD_INVALID,
|
|
318
|
+
)}
|
|
319
|
+
>
|
|
320
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
321
|
+
{selectedOption?.icon && <span className="shrink-0">{selectedOption.icon}</span>}
|
|
322
|
+
<span
|
|
323
|
+
className={cn(
|
|
324
|
+
"truncate",
|
|
325
|
+
// A chosen value is the field's VALUE, so it is set in the same ink an
|
|
326
|
+
// <input>'s value is — FIELD_BASE's own text colour, inherited rather
|
|
327
|
+
// than restated (Keksdose dev#477). It used to be one notch lighter
|
|
328
|
+
// than the typeahead fields beside it, which is visible when a picker
|
|
329
|
+
// and a text field share a form row. Nothing selected keeps the
|
|
330
|
+
// placeholder tone (`--text-placeholder`), which every field here
|
|
331
|
+
// agrees on.
|
|
332
|
+
!selectedOption && "text-[var(--text-placeholder)]",
|
|
333
|
+
)}
|
|
334
|
+
>
|
|
335
|
+
{selectedOption?.label ?? placeholder ?? ""}
|
|
336
|
+
</span>
|
|
265
337
|
</span>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
338
|
+
{showClear ? (
|
|
339
|
+
/* eslint-disable-next-line jsx-a11y/click-events-have-key-events -- tabIndex -1 inside
|
|
340
|
+
the trigger <button>, so it never holds focus and a key handler here could
|
|
341
|
+
never fire; the keys go to the trigger, where Delete and Backspace clear. */
|
|
342
|
+
<span
|
|
343
|
+
role="button"
|
|
344
|
+
tabIndex={-1}
|
|
345
|
+
aria-label={labels.clear}
|
|
346
|
+
onClick={(e) => {
|
|
347
|
+
e.stopPropagation();
|
|
348
|
+
onChange(clearValue);
|
|
349
|
+
}}
|
|
350
|
+
className="absolute end-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)] hover:text-[var(--text-secondary)]"
|
|
351
|
+
>
|
|
352
|
+
<X className="size-4" />
|
|
353
|
+
</span>
|
|
354
|
+
) : (
|
|
355
|
+
<FieldChevron />
|
|
356
|
+
)}
|
|
357
|
+
</button>,
|
|
358
|
+
)}
|
|
270
359
|
</EndHintRow>
|
|
271
360
|
{/* With a "?" the label shares the top strip with it — after the trigger, so the
|
|
272
361
|
"?" follows the control in the tab order, as on Input and Select. */}
|
|
@@ -289,10 +378,12 @@ export function EntityCombobox<V extends string | number, C extends ComboClearVa
|
|
|
289
378
|
onChoose={choose}
|
|
290
379
|
showCreate={showCreate}
|
|
291
380
|
onCreate={() => {
|
|
381
|
+
if (lock.locked) return;
|
|
292
382
|
onCreate?.(q);
|
|
293
383
|
core.closeToTrigger();
|
|
294
384
|
}}
|
|
295
|
-
createContent={labels.create(q)}
|
|
385
|
+
createContent={q ? labels.create(q) : createEmptyLabel}
|
|
386
|
+
createDisabledReason={createReason}
|
|
296
387
|
/>
|
|
297
388
|
<FieldCaption parts={hintParts} />
|
|
298
389
|
{field.errorEl}
|