@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
package/dist/wizard.d.ts
CHANGED
|
@@ -6,7 +6,13 @@ export { WizardStep, WizardStepProps } from './wizard/wizard-step.js';
|
|
|
6
6
|
export { WizardSummary } from './wizard/wizard-summary.js';
|
|
7
7
|
export { StepperNav } from './wizard/stepper-nav.js';
|
|
8
8
|
import 'react';
|
|
9
|
-
import './
|
|
9
|
+
import './feedback-BxeQVzwq.js';
|
|
10
|
+
import './feedback-attachment-fGAzZPf0.js';
|
|
11
|
+
import './feedback/feedback-inbox.js';
|
|
12
|
+
import 'lucide-react';
|
|
13
|
+
import './components/tooltip.js';
|
|
14
|
+
import './hooks/use-anchored-rect.js';
|
|
15
|
+
import './lib/clipping.js';
|
|
10
16
|
import './data-table-labels-B7OdnM0S.js';
|
|
11
17
|
import './components/data-table-sort.js';
|
|
12
18
|
import './components/swipeable-row.js';
|
|
@@ -14,11 +20,7 @@ import './components/mini-calendar.js';
|
|
|
14
20
|
import './components/calendar-heatmap.js';
|
|
15
21
|
import './components/popover.js';
|
|
16
22
|
import './components/chip.js';
|
|
17
|
-
import 'lucide-react';
|
|
18
23
|
import './components/field-sync.js';
|
|
19
|
-
import './components/tooltip.js';
|
|
20
|
-
import './hooks/use-anchored-rect.js';
|
|
21
|
-
import './lib/clipping.js';
|
|
22
24
|
import './tour/tour.js';
|
|
23
25
|
import './search/command-palette.js';
|
|
24
26
|
import './search/global-search.js';
|
|
@@ -38,9 +40,6 @@ import './components/dialog-frame.js';
|
|
|
38
40
|
import './components/modal.js';
|
|
39
41
|
import './hooks/use-search-param-state.js';
|
|
40
42
|
import './components/measured-grid.js';
|
|
41
|
-
import './feedback-attachment-NhUm7Zga.js';
|
|
42
|
-
import './feedback/feedback-thread.js';
|
|
43
|
-
import './feedback/feedback-inbox.js';
|
|
44
43
|
import './components/account-settings-labels.js';
|
|
45
44
|
import './components/floating-panel.js';
|
|
46
45
|
import './components/bulk-action-bar.js';
|
|
@@ -70,3 +69,7 @@ import './components/inline-edit-field.js';
|
|
|
70
69
|
import './lib/iban.js';
|
|
71
70
|
import './lib/phone.js';
|
|
72
71
|
import './components/sign-chip.js';
|
|
72
|
+
import './components/column-mapper.js';
|
|
73
|
+
import './lib/column-mapping.js';
|
|
74
|
+
import './lib/table-text.js';
|
|
75
|
+
import './components/field-parts.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eifi1/ui-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic shared design system: domain-free React components, theme and tokens.",
|
|
6
6
|
"_sideEffects": "The barrel (src/index.ts) re-exports EVERY component, including components/chart, which statically imports recharts. Without this field a bundler must assume each of those modules has side effects, so it cannot drop the unused ones: importing a Button from '@eifi1/ui-kit' dragged all 433KB of recharts into the consumer's ENTRY chunk, and Keksdose was preloading it on every page load, login included. Nothing in src/ imports for side effects (no bare imports, no CSS imports from JS, no module-level global writes) — the token stylesheet is its own './tokens.css' export — so this is safe. CSS is still listed so any future stylesheet survives. Re-checked when `flag-icons` left `dependencies` and tsup started emitting a `\"use client\"` banner: neither touches this. The flag stylesheet was never imported from JS in the first place (that was the finding), and a directive prologue is a string literal every bundler lifts out before it decides whether a module is inert.",
|
|
@@ -55,7 +55,24 @@ interface AmountInputProps {
|
|
|
55
55
|
* a phone keeps its numpad, exactly as {@link NumberInput}'s `calculator={false}`.
|
|
56
56
|
*/
|
|
57
57
|
calculator?: boolean;
|
|
58
|
+
/** The field's WRAPPER — width, a margin, a flex share. The `<input>` is
|
|
59
|
+
* `inputClassName`'s. */
|
|
58
60
|
className?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Classes for the `<input>` itself — {@link NumberInput}'s and {@link Input}'s prop of
|
|
63
|
+
* the same name, merged the same way: after the field's own base classes, so it can
|
|
64
|
+
* restyle the figure (`text-end` for a money column whose decimal points line up,
|
|
65
|
+
* `font-medium`, a tighter `py-1` in a table cell), and before the end padding the
|
|
66
|
+
* calculator and the currency chip reserve and before the invalid border, so neither
|
|
67
|
+
* can be taken away by accident.
|
|
68
|
+
*
|
|
69
|
+
* keksdose G7: the invoice's VAT cell right-aligns its figure, and with only
|
|
70
|
+
* `className` (the wrapper) it reached the element through `[&_input]:text-end` — a
|
|
71
|
+
* descendant selector that depends on the field's inner markup, which the kit is free
|
|
72
|
+
* to change, and that a reader of the call site has to decode. {@link MoneyField}
|
|
73
|
+
* passes it through, and `RhfMoneyField` takes it too.
|
|
74
|
+
*/
|
|
75
|
+
inputClassName?: string;
|
|
59
76
|
id?: string;
|
|
60
77
|
ariaLabel?: string;
|
|
61
78
|
/**
|
|
@@ -291,7 +308,7 @@ function isResultOf(previous: string, text: string, settle: (text: string) => st
|
|
|
291
308
|
}
|
|
292
309
|
|
|
293
310
|
export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
294
|
-
({ value, onChange, currency, onCurrencyChange, placeholder, label, disabled, invalid: invalidProp, error, calculator = true, className, id, ariaLabel, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, hint, autoFocus, tone = "neutral", negative = false, onNegativeChange, variant = "field", align = "start", labels, currencyNames, digits: digitsProp, min, max, onCommit }, ref) => {
|
|
311
|
+
({ value, onChange, currency, onCurrencyChange, placeholder, label, disabled, invalid: invalidProp, error, calculator = true, className, inputClassName, id, ariaLabel, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, hint, autoFocus, tone = "neutral", negative = false, onNegativeChange, variant = "field", align = "start", labels, currencyNames, digits: digitsProp, min, max, onCommit }, ref) => {
|
|
295
312
|
const generatedId = useId();
|
|
296
313
|
const fieldId = id ?? generatedId;
|
|
297
314
|
const hintId = useId();
|
|
@@ -504,6 +521,8 @@ export const AmountInput = forwardRef<HTMLInputElement, AmountInputProps>(
|
|
|
504
521
|
// (feedback #314). FLOATING_INPUT_CLASS already bundles the peer +
|
|
505
522
|
// transparent-placeholder bits.
|
|
506
523
|
asDisplay ? DISPLAY_INPUT_CLASS : label !== undefined ? FLOATING_INPUT_CLASS : FIELD_BASE,
|
|
524
|
+
// The caller's, over the base and under everything below (see `inputClassName`).
|
|
525
|
+
inputClassName,
|
|
507
526
|
// Room for the trailing controls. showCalc is always false on a phone,
|
|
508
527
|
// so the display shape only ever has to clear the currency chip — and
|
|
509
528
|
// it clears it by the chip's actual width (a text-sm code plus a
|
|
@@ -22,7 +22,16 @@ import {
|
|
|
22
22
|
type ComboOption,
|
|
23
23
|
} from "./combobox-core";
|
|
24
24
|
import { DEFAULT_COMBOBOX_LABELS, useKitLabels } from "../i18n/kit-labels";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
EndHintRow,
|
|
27
|
+
FieldCaption,
|
|
28
|
+
LABEL_IN_ROW,
|
|
29
|
+
LockedReason,
|
|
30
|
+
StaticLabelRow,
|
|
31
|
+
useFieldHint,
|
|
32
|
+
useLockReason,
|
|
33
|
+
} from "./field-parts";
|
|
34
|
+
import { mergeDescribedBy } from "./choice-parts";
|
|
26
35
|
|
|
27
36
|
/**
|
|
28
37
|
* `value`/`onChange` are the TEXT's, and `onSelect` is "a suggestion was taken" —
|
|
@@ -126,6 +135,27 @@ export interface AutocompleteProps<V extends string | number = string>
|
|
|
126
135
|
loadErrorLabel?: string;
|
|
127
136
|
/** Classes for the `<input>` itself; `className` styles the wrapper. */
|
|
128
137
|
inputClassName?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Why the field cannot be changed — {@link Button}'s `disabledReason`, the Combobox
|
|
140
|
+
* family's write lock (keksdose G2, 0.23), for a field whose pick SAVES: an address
|
|
141
|
+
* search whose taken row writes the address, a lookup that links a record.
|
|
142
|
+
*
|
|
143
|
+
* With a reason the input stays focusable and `aria-disabled`, is `readOnly` (the
|
|
144
|
+
* settled grey FIELD_BASE gives a `[readonly]` field), opens no list and asks no
|
|
145
|
+
* `loadOptions` — whatever `open` says — and nothing reaches `onChange`, `onSelect` or
|
|
146
|
+
* `onOpenChange(true)`. Enter is swallowed, so a locked field does not submit the form
|
|
147
|
+
* around it, and a caller's `onKeyDown` does not hear it; every other key still
|
|
148
|
+
* reaches the caller (an Escape that closes its panel) and the caret. The reason is in
|
|
149
|
+
* the kit {@link Tooltip} and on the input's `aria-describedby`. Wins over `disabled`.
|
|
150
|
+
*/
|
|
151
|
+
disabledReason?: ReactNode;
|
|
152
|
+
/**
|
|
153
|
+
* This field COMMITS. Under a locked {@link WriteLockProvider} it is locked the
|
|
154
|
+
* `disabledReason` way with the lock's reason (which wins over its own). A search
|
|
155
|
+
* whose result is only draft state — the form's Save writes it — leaves this off and
|
|
156
|
+
* stays live under the lock. No provider, or an unlocked one: no effect.
|
|
157
|
+
*/
|
|
158
|
+
commit?: boolean;
|
|
129
159
|
}
|
|
130
160
|
|
|
131
161
|
function AutocompleteInner<V extends string | number = string>(
|
|
@@ -155,6 +185,8 @@ function AutocompleteInner<V extends string | number = string>(
|
|
|
155
185
|
inputClassName,
|
|
156
186
|
id,
|
|
157
187
|
disabled,
|
|
188
|
+
disabledReason,
|
|
189
|
+
commit,
|
|
158
190
|
onKeyDown,
|
|
159
191
|
onFocus,
|
|
160
192
|
onBlur,
|
|
@@ -191,7 +223,11 @@ function AutocompleteInner<V extends string | number = string>(
|
|
|
191
223
|
if (next !== open) onOpenChange?.(next);
|
|
192
224
|
};
|
|
193
225
|
const [active, setActive] = useState(-1);
|
|
194
|
-
|
|
226
|
+
// The lock: focusable, `readOnly`, no list — see `disabledReason`. Folded into `live`,
|
|
227
|
+
// so a locked field asks nothing and shows nothing even with `open` held true.
|
|
228
|
+
const lock = useLockReason(commit, disabledReason);
|
|
229
|
+
const locked = lock.locked;
|
|
230
|
+
const live = open && !disabled && !locked;
|
|
195
231
|
const { results, busy, failed, tooShort } = useOptionSource<V>({
|
|
196
232
|
options,
|
|
197
233
|
loadOptions,
|
|
@@ -230,7 +266,7 @@ function AutocompleteInner<V extends string | number = string>(
|
|
|
230
266
|
setActive(-1);
|
|
231
267
|
};
|
|
232
268
|
const take = (o: ComboOption<V>) => {
|
|
233
|
-
if (o.disabled) return;
|
|
269
|
+
if (o.disabled || locked) return;
|
|
234
270
|
if (fillOnSelect) onChange(o.label);
|
|
235
271
|
onSelect?.(o);
|
|
236
272
|
close();
|
|
@@ -245,6 +281,16 @@ function AutocompleteInner<V extends string | number = string>(
|
|
|
245
281
|
// label still names the field.
|
|
246
282
|
const labelledBy =
|
|
247
283
|
ariaLabel === undefined ? (ariaLabelledBy ?? (hasLabel ? labelId : undefined)) : undefined;
|
|
284
|
+
// While locked the field sits in the reason's Tooltip — in a fragment with a hidden
|
|
285
|
+
// copy of the reason, Button's anatomy; `block` so a full-width field stays full width.
|
|
286
|
+
const withLock = (box: ReactNode) =>
|
|
287
|
+
locked ? (
|
|
288
|
+
<LockedReason lock={lock} className="block">
|
|
289
|
+
{box}
|
|
290
|
+
</LockedReason>
|
|
291
|
+
) : (
|
|
292
|
+
box
|
|
293
|
+
);
|
|
248
294
|
|
|
249
295
|
return (
|
|
250
296
|
// `relative`: the floating label and the live region's `sr-only` both need a
|
|
@@ -258,119 +304,134 @@ function AutocompleteInner<V extends string | number = string>(
|
|
|
258
304
|
</ComboboxFieldLabel>
|
|
259
305
|
)}
|
|
260
306
|
<EndHintRow hint={hasLabel ? undefined : hintParts.labelHint}>
|
|
261
|
-
|
|
262
|
-
{
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}}
|
|
316
|
-
onKeyDown={(e) => {
|
|
317
|
-
// An Escape that closes the OPEN list is the list's, and is consumed before
|
|
318
|
-
// the caller sees it. A caller whose Escape means "close the panel" (keksdose's
|
|
319
|
-
// address search) otherwise closed the whole panel when the user only meant
|
|
320
|
-
// to dismiss the suggestions — the opposite of what the docs promised.
|
|
321
|
-
if (e.key === "Escape" && expanded) {
|
|
322
|
-
e.preventDefault();
|
|
323
|
-
e.stopPropagation();
|
|
307
|
+
{withLock(
|
|
308
|
+
<div ref={fieldRef} className={cn("relative", locked && "cursor-not-allowed")}>
|
|
309
|
+
{icon && (
|
|
310
|
+
<span
|
|
311
|
+
aria-hidden
|
|
312
|
+
className={cn(
|
|
313
|
+
"pointer-events-none absolute flex text-[var(--text-muted)]",
|
|
314
|
+
small ? "start-2 [&>svg]:size-3.5" : "start-2.5 [&>svg]:size-4",
|
|
315
|
+
// Labelled, the icon sits on the VALUE's line — the one line box below
|
|
316
|
+
// FIELD_FLOATING_PAD's `pt-4` and the 1px border — not on the middle of
|
|
317
|
+
// the box. Centred on the box it rose into the top strip and sat on the
|
|
318
|
+
// label's first letters, which float at the same start inset. On the
|
|
319
|
+
// value line it is beside what it describes, and the label keeps the
|
|
320
|
+
// start edge every other label in the form's column shares.
|
|
321
|
+
hasLabel
|
|
322
|
+
? "top-[calc(1rem+1px)] h-5 items-center"
|
|
323
|
+
: "top-1/2 -translate-y-1/2",
|
|
324
|
+
)}
|
|
325
|
+
>
|
|
326
|
+
{icon}
|
|
327
|
+
</span>
|
|
328
|
+
)}
|
|
329
|
+
<input
|
|
330
|
+
{...rest}
|
|
331
|
+
ref={ref}
|
|
332
|
+
id={fieldId}
|
|
333
|
+
size={nativeSize}
|
|
334
|
+
type="text"
|
|
335
|
+
value={value}
|
|
336
|
+
// Locked: focusable, `readOnly`, `aria-disabled` — see `disabledReason`.
|
|
337
|
+
disabled={locked ? undefined : disabled}
|
|
338
|
+
readOnly={locked || rest.readOnly}
|
|
339
|
+
aria-disabled={locked || rest["aria-disabled"]}
|
|
340
|
+
role="combobox"
|
|
341
|
+
aria-autocomplete="list"
|
|
342
|
+
aria-expanded={expanded}
|
|
343
|
+
// Required by the role, and set while closed too, as on the siblings.
|
|
344
|
+
aria-controls={listboxId}
|
|
345
|
+
aria-activedescendant={activeId}
|
|
346
|
+
aria-label={ariaLabel}
|
|
347
|
+
aria-labelledby={labelledBy}
|
|
348
|
+
aria-invalid={field.isInvalid || rest["aria-invalid"] || undefined}
|
|
349
|
+
aria-describedby={mergeDescribedBy(field.describedBy, locked && lock.reasonId)}
|
|
350
|
+
autoComplete="off"
|
|
351
|
+
onFocus={(e) => {
|
|
352
|
+
onFocus?.(e);
|
|
353
|
+
// A locked field takes focus to say why, and opens nothing.
|
|
354
|
+
if (locked) return;
|
|
355
|
+
// A seeded field (keksdose's `initialQuery`) is looked up as it is
|
|
356
|
+
// focused; an empty one asks nothing, because of `minChars`.
|
|
357
|
+
setOpen(true);
|
|
358
|
+
}}
|
|
359
|
+
onBlur={(e) => {
|
|
360
|
+
onBlur?.(e);
|
|
324
361
|
close();
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
|
|
362
|
+
}}
|
|
363
|
+
onChange={(e) => {
|
|
364
|
+
if (locked) return;
|
|
365
|
+
onChange(e.target.value);
|
|
366
|
+
setOpen(true);
|
|
367
|
+
setActive(-1);
|
|
368
|
+
}}
|
|
369
|
+
onKeyDown={(e) => {
|
|
370
|
+
// Locked: Enter is swallowed (no form submit, no caller's "search now");
|
|
371
|
+
// every other key is the caller's and the caret's. No list to drive.
|
|
372
|
+
if (locked) {
|
|
373
|
+
if (e.key === "Enter") e.preventDefault();
|
|
374
|
+
else onKeyDown?.(e);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
// An Escape that closes the OPEN list is the list's, and is consumed before
|
|
378
|
+
// the caller sees it. A caller whose Escape means "close the panel" (keksdose's
|
|
379
|
+
// address search) otherwise closed the whole panel when the user only meant
|
|
380
|
+
// to dismiss the suggestions — the opposite of what the docs promised.
|
|
381
|
+
if (e.key === "Escape" && expanded) {
|
|
343
382
|
e.preventDefault();
|
|
344
|
-
|
|
383
|
+
e.stopPropagation();
|
|
384
|
+
close();
|
|
385
|
+
return;
|
|
345
386
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
387
|
+
onKeyDown?.(e);
|
|
388
|
+
if (e.defaultPrevented) return;
|
|
389
|
+
// Disabled rows are passed over; at either end the highlight stays put.
|
|
390
|
+
if (e.key === "ArrowDown") {
|
|
391
|
+
e.preventDefault();
|
|
392
|
+
if (!open) setOpen(true);
|
|
393
|
+
else setActive((i) => stepEnabled(results, i, 1));
|
|
394
|
+
} else if (e.key === "ArrowUp") {
|
|
395
|
+
e.preventDefault();
|
|
396
|
+
if (!open) setOpen(true);
|
|
397
|
+
// From "nothing highlighted", Up goes to the bottom, as the APG has it.
|
|
398
|
+
else setActive((i) => stepEnabled(results, i < 0 ? results.length : i, -1));
|
|
399
|
+
} else if (e.key === "Enter") {
|
|
400
|
+
// No row highlighted: the text is the answer, and a form's own submit
|
|
401
|
+
// is left alone.
|
|
402
|
+
if (expanded && activeId) {
|
|
403
|
+
e.preventDefault();
|
|
404
|
+
take(results[active]);
|
|
405
|
+
}
|
|
406
|
+
} else if (e.key === "Tab") {
|
|
407
|
+
close();
|
|
408
|
+
}
|
|
409
|
+
}}
|
|
365
410
|
className={cn(
|
|
366
|
-
|
|
367
|
-
|
|
411
|
+
FIELD_BASE,
|
|
412
|
+
hasLabel && FIELD_FLOATING_PAD,
|
|
413
|
+
// Select's `SELECT_SM` box: a fixed 28px, so the caller's line height
|
|
414
|
+
// cannot grow it.
|
|
415
|
+
small && "h-7 py-0 pe-2 ps-2 text-xs",
|
|
416
|
+
icon ? (small ? "ps-7" : "ps-8") : undefined,
|
|
417
|
+
busy && (small ? "pe-7" : "pe-9"),
|
|
418
|
+
field.isInvalid && FIELD_INVALID,
|
|
419
|
+
inputClassName,
|
|
368
420
|
)}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
421
|
+
/>
|
|
422
|
+
{busy && live && (
|
|
423
|
+
// Decorative: the live region below already says "Loading…".
|
|
424
|
+
<span
|
|
425
|
+
className={cn(
|
|
426
|
+
"pointer-events-none absolute top-1/2 flex -translate-y-1/2",
|
|
427
|
+
small ? "end-2" : "end-2.5",
|
|
428
|
+
)}
|
|
429
|
+
>
|
|
430
|
+
<Spinner label={null} className={small ? "h-3.5 w-3.5" : "h-4 w-4"} />
|
|
431
|
+
</span>
|
|
432
|
+
)}
|
|
433
|
+
</div>,
|
|
434
|
+
)}
|
|
374
435
|
</EndHintRow>
|
|
375
436
|
{/* With a "?" the label shares the top strip with it, after the field so the "?"
|
|
376
437
|
follows the control in the tab order (as on Input and Select). */}
|