@eifi1/ui-kit 0.21.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 +19 -14
- package/dist/components/account-chips.d.ts +10 -3
- package/dist/components/account-chips.js +5 -2
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/account-settings.d.ts +33 -2
- package/dist/components/account-settings.js +43 -21
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +53 -14
- package/dist/components/amount-input.js +17 -28
- package/dist/components/amount-input.js.map +1 -1
- package/dist/components/autocomplete.d.ts +29 -0
- package/dist/components/autocomplete.js +134 -104
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button-group.d.ts +19 -14
- package/dist/components/calculator.d.ts +19 -14
- package/dist/components/checkbox-group.d.ts +104 -0
- package/dist/components/checkbox-group.js +111 -0
- package/dist/components/checkbox-group.js.map +1 -0
- package/dist/components/checkbox.d.ts +19 -0
- package/dist/components/checkbox.js +30 -7
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/chip.d.ts +18 -0
- package/dist/components/chip.js +47 -10
- package/dist/components/chip.js.map +1 -1
- 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 +91 -2
- package/dist/components/combobox.js +454 -308
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/confirm-dialog.d.ts +69 -161
- package/dist/components/confirm-dialog.js +7 -8
- package/dist/components/confirm-dialog.js.map +1 -1
- package/dist/components/copy-button.d.ts +16 -11
- package/dist/components/country-select.d.ts +167 -0
- package/dist/components/country-select.js +293 -0
- package/dist/components/country-select.js.map +1 -0
- package/dist/components/currency-select.d.ts +17 -1
- package/dist/components/currency-select.js +56 -23
- package/dist/components/currency-select.js.map +1 -1
- package/dist/components/danger-confirm.d.ts +69 -166
- package/dist/components/danger-confirm.js +144 -50
- package/dist/components/danger-confirm.js.map +1 -1
- package/dist/components/data-table-cells.d.ts +1 -2
- package/dist/components/data-table-filter-popover.d.ts +1 -2
- package/dist/components/data-table-filters.d.ts +1 -2
- package/dist/components/data-table-labels.d.ts +4 -97
- package/dist/components/data-table-labels.js +9 -1
- package/dist/components/data-table-labels.js.map +1 -1
- package/dist/components/data-table-pagination.d.ts +3 -1
- package/dist/components/data-table.d.ts +1 -2
- package/dist/components/data-table.js +98 -3
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-picker.d.ts +66 -5
- package/dist/components/date-picker.js +378 -66
- package/dist/components/date-picker.js.map +1 -1
- package/dist/components/entity-combobox.d.ts +42 -1
- package/dist/components/entity-combobox.js +154 -113
- package/dist/components/entity-combobox.js.map +1 -1
- package/dist/components/facing-pair.d.ts +19 -14
- package/dist/components/field-parts.d.ts +224 -0
- package/dist/components/field-parts.js +156 -0
- package/dist/components/field-parts.js.map +1 -0
- 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 +19 -14
- package/dist/components/file-button.js +13 -3
- package/dist/components/file-button.js.map +1 -1
- package/dist/components/file-dropzone.d.ts +41 -15
- package/dist/components/file-dropzone.js +57 -34
- package/dist/components/file-dropzone.js.map +1 -1
- package/dist/components/form-actions.d.ts +18 -13
- package/dist/components/form-actions.js +69 -5
- package/dist/components/form-actions.js.map +1 -1
- package/dist/components/iban-input.d.ts +69 -0
- package/dist/components/iban-input.js +125 -0
- package/dist/components/iban-input.js.map +1 -0
- package/dist/components/icon-picker.d.ts +21 -2
- package/dist/components/icon-picker.js +19 -4
- package/dist/components/icon-picker.js.map +1 -1
- package/dist/components/inline-edit-field.d.ts +167 -0
- package/dist/components/inline-edit-field.js +293 -0
- package/dist/components/inline-edit-field.js.map +1 -0
- package/dist/components/language-select.d.ts +130 -0
- package/dist/components/language-select.js +18 -0
- package/dist/components/language-select.js.map +1 -0
- package/dist/components/line-items.d.ts +25 -3
- package/dist/components/line-items.js +55 -46
- package/dist/components/line-items.js.map +1 -1
- package/dist/components/list.d.ts +25 -0
- package/dist/components/list.js +16 -5
- package/dist/components/list.js.map +1 -1
- package/dist/components/money-field.d.ts +32 -14
- package/dist/components/money-field.js.map +1 -1
- package/dist/components/month-picker.d.ts +52 -4
- package/dist/components/month-picker.js +291 -86
- 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/multi-select.d.ts +8 -1
- package/dist/components/multi-select.js +9 -4
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/number-field.d.ts +34 -19
- package/dist/components/number-field.js +6 -20
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/number-input.d.ts +79 -19
- package/dist/components/number-input.js +90 -38
- package/dist/components/number-input.js.map +1 -1
- package/dist/components/numpad-sheet.d.ts +19 -14
- package/dist/components/one-time-code-input.d.ts +100 -0
- package/dist/components/one-time-code-input.js +137 -0
- package/dist/components/one-time-code-input.js.map +1 -0
- package/dist/components/phone-input.d.ts +69 -0
- package/dist/components/phone-input.js +171 -0
- package/dist/components/phone-input.js.map +1 -0
- 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 +19 -14
- package/dist/components/settings-fields.d.ts +19 -14
- package/dist/components/share-card.d.ts +16 -11
- package/dist/components/sign-chip.d.ts +66 -0
- package/dist/components/sign-chip.js +39 -0
- package/dist/components/sign-chip.js.map +1 -0
- package/dist/components/swatch-picker.d.ts +43 -2
- package/dist/components/swatch-picker.js +80 -32
- package/dist/components/swatch-picker.js.map +1 -1
- package/dist/components/switch.d.ts +19 -0
- package/dist/components/switch.js +24 -6
- package/dist/components/switch.js.map +1 -1
- package/dist/components/text-link.d.ts +18 -13
- package/dist/components/tile-radio.d.ts +88 -13
- package/dist/components/tile-radio.js +28 -11
- package/dist/components/tile-radio.js.map +1 -1
- package/dist/components/time-input.d.ts +24 -15
- package/dist/components/time-input.js.map +1 -1
- package/dist/components/toggle-group.d.ts +47 -1
- package/dist/components/toggle-group.js +110 -69
- 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 +16 -11
- package/dist/components/ui.js +284 -88
- package/dist/components/ui.js.map +1 -1
- package/dist/components/use-table-state.d.ts +1 -2
- package/dist/{data-table-3mi2R3so.d.ts → data-table-labels-B7OdnM0S.d.ts} +140 -3
- package/dist/data-table.d.ts +1 -2
- package/dist/data-table.js +2 -1
- package/dist/data-table.js.map +1 -1
- package/dist/feedback/feedback-attachment.d.ts +1 -1
- package/dist/feedback/feedback-attachment.js +348 -41
- 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 -253
- package/dist/feedback/feedback-thread.js +46 -10
- package/dist/feedback/feedback-thread.js.map +1 -1
- package/dist/{kit-labels-D1J2_jyD.d.ts → feedback-BxeQVzwq.d.ts} +1206 -20
- package/dist/{feedback-attachment-WFiXJ8We.d.ts → feedback-attachment-fGAzZPf0.d.ts} +178 -11
- package/dist/feedback.d.ts +66 -2
- package/dist/hooks/use-file-drop.d.ts +19 -14
- package/dist/i18n/defaults.d.ts +19 -14
- package/dist/i18n/defaults.js +19 -2
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +19 -14
- package/dist/i18n/german.js +84 -5
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +15 -10
- package/dist/i18n/kit-labels.js +8 -2
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +19 -14
- package/dist/i18n/locales/de-CH.d.ts +19 -14
- package/dist/i18n/locales/en.d.ts +19 -14
- package/dist/i18n/locales/en.js +16 -0
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +19 -14
- package/dist/i18n/locales/es.js +83 -5
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +19 -14
- package/dist/i18n/locales/fr.js +82 -5
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +19 -14
- package/dist/i18n/locales/hu.js +85 -5
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +19 -14
- package/dist/i18n/locales/it.js +84 -5
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +19 -14
- package/dist/i18n/locales/zh.js +80 -5
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +19 -14
- package/dist/i18n/review.js +27 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +21 -10
- package/dist/index.js +73 -2
- 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/countries.d.ts +43 -0
- package/dist/lib/countries.js +39 -0
- package/dist/lib/countries.js.map +1 -0
- package/dist/lib/decimal-marks.d.ts +46 -0
- package/dist/lib/decimal-marks.js +40 -0
- package/dist/lib/decimal-marks.js.map +1 -0
- package/dist/lib/iban.d.ts +98 -0
- package/dist/lib/iban.js +154 -0
- package/dist/lib/iban.js.map +1 -0
- package/dist/lib/isin.d.ts +30 -0
- package/dist/lib/isin.js +36 -0
- package/dist/lib/isin.js.map +1 -0
- package/dist/lib/phone.d.ts +69 -0
- package/dist/lib/phone.js +121 -0
- package/dist/lib/phone.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 +233 -18
- package/dist/rhf/fields.js +456 -3
- package/dist/rhf/fields.js.map +1 -1
- package/dist/rhf/form.d.ts +19 -14
- package/dist/rhf.d.ts +22 -15
- package/dist/rhf.js.map +1 -1
- package/dist/shell/app-shell.d.ts +18 -13
- package/dist/shell/top-bar-brand.d.ts +19 -14
- 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 +18 -13
- package/dist/table-text.d.ts +1 -1
- package/dist/tour/tour.js +20 -24
- package/dist/tour/tour.js.map +1 -1
- package/dist/wizard/stepper-nav.d.ts +49 -16
- package/dist/wizard/stepper-nav.js +4 -0
- package/dist/wizard/stepper-nav.js.map +1 -1
- package/dist/wizard.d.ts +19 -14
- package/package.json +1 -1
- package/src/components/account-chips.tsx +17 -3
- package/src/components/account-settings.tsx +92 -26
- package/src/components/amount-input.tsx +68 -56
- package/src/components/autocomplete.tsx +198 -114
- package/src/components/checkbox-group.tsx +229 -0
- package/src/components/checkbox.tsx +72 -8
- package/src/components/chip.tsx +80 -10
- package/src/components/column-mapper.tsx +666 -0
- package/src/components/combobox-core.tsx +82 -9
- package/src/components/combobox.tsx +661 -340
- package/src/components/confirm-dialog.tsx +12 -8
- package/src/components/country-select.tsx +556 -0
- package/src/components/currency-select.tsx +109 -33
- package/src/components/danger-confirm.tsx +429 -82
- package/src/components/data-table-labels.ts +16 -1
- package/src/components/data-table.tsx +175 -6
- package/src/components/date-picker.tsx +595 -71
- package/src/components/entity-combobox.tsx +235 -124
- package/src/components/field-parts.tsx +403 -0
- package/src/components/field-strip.tsx +149 -0
- package/src/components/file-button.tsx +36 -3
- package/src/components/file-dropzone.tsx +106 -38
- package/src/components/form-actions.tsx +147 -6
- package/src/components/iban-input.tsx +231 -0
- package/src/components/icon-picker.tsx +47 -6
- package/src/components/inline-edit-field.tsx +533 -0
- package/src/components/language-select.tsx +78 -0
- package/src/components/line-items.tsx +35 -2
- package/src/components/list.tsx +55 -6
- package/src/components/money-field.tsx +13 -0
- package/src/components/month-picker.tsx +400 -63
- package/src/components/multi-entity-combobox.tsx +207 -120
- package/src/components/multi-select.tsx +80 -58
- package/src/components/number-field.tsx +28 -46
- package/src/components/number-input.tsx +210 -43
- package/src/components/one-time-code-input.tsx +288 -0
- package/src/components/phone-input.tsx +299 -0
- package/src/components/reauth-dialog.tsx +17 -18
- package/src/components/sign-chip.tsx +103 -0
- package/src/components/swatch-picker.tsx +143 -31
- package/src/components/switch.tsx +58 -7
- package/src/components/tile-radio.tsx +145 -29
- package/src/components/time-input.tsx +8 -1
- package/src/components/toggle-group.tsx +206 -73
- package/src/components/trigger-aria.ts +5 -0
- package/src/components/ui.tsx +479 -138
- package/src/data-table.ts +2 -1
- package/src/feedback/feedback-attachment.tsx +597 -46
- package/src/feedback/feedback-dialog.tsx +9 -3
- package/src/feedback/feedback-inbox.tsx +3 -2
- package/src/feedback/feedback-thread.tsx +113 -9
- package/src/i18n/defaults.ts +18 -1
- package/src/i18n/german.ts +89 -0
- package/src/i18n/kit-labels.tsx +82 -2
- package/src/i18n/locales/en.ts +36 -4
- package/src/i18n/locales/es.ts +87 -0
- package/src/i18n/locales/fr.ts +87 -0
- package/src/i18n/locales/hu.ts +83 -0
- package/src/i18n/locales/it.ts +88 -0
- package/src/i18n/locales/zh.ts +76 -0
- package/src/i18n/review.ts +26 -0
- package/src/index.ts +71 -3
- package/src/lib/column-mapping.ts +234 -0
- package/src/lib/countries.ts +91 -0
- package/src/lib/decimal-marks.ts +99 -0
- package/src/lib/iban.ts +171 -0
- package/src/lib/isin.ts +62 -0
- package/src/lib/phone.ts +211 -0
- package/src/lib/table-text.ts +271 -0
- package/src/rhf/fields.tsx +860 -9
- package/src/rhf.ts +3 -1
- package/src/shell/topbar-action-menu.tsx +134 -38
- package/src/tour/tour.tsx +66 -28
- package/src/wizard/stepper-nav.tsx +35 -1
|
@@ -1,161 +1,69 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
typedMatch?: TypedMatch;
|
|
71
|
-
}
|
|
72
|
-
type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;
|
|
73
|
-
/**
|
|
74
|
-
* `const confirm = useConfirm()` → `if (!(await confirm({ title }))) return`.
|
|
75
|
-
*
|
|
76
|
-
* The returned function is STABLE for the life of the provider, so it can sit in an
|
|
77
|
-
* effect's or a `useCallback`'s dependency list without re-running it.
|
|
78
|
-
*
|
|
79
|
-
* Throws outside a {@link ConfirmProvider}, rather than falling back to
|
|
80
|
-
* `window.confirm`: a silent fallback is exactly the native dialog the three apps are
|
|
81
|
-
* migrating away from, and it would look like the migration had worked.
|
|
82
|
-
*/
|
|
83
|
-
declare function useConfirm(): ConfirmFn;
|
|
84
|
-
interface ConfirmProviderProps {
|
|
85
|
-
children: ReactNode;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* The host for {@link useConfirm}: ONE confirm dialog for the whole app, rendered on
|
|
89
|
-
* {@link DialogFrame}, answering a promise.
|
|
90
|
-
*
|
|
91
|
-
* Asked for by all three apps at once — keksdose has 20+ `window.confirm` calls,
|
|
92
|
-
* kastlan 7, lenkbank 1. The native dialog cannot be translated past its OK/Cancel
|
|
93
|
-
* (those are the BROWSER's language, not the app's), cannot say which button is the
|
|
94
|
-
* destructive one, and on an installed PWA it prints the origin over the question.
|
|
95
|
-
* A promise is what lets each of those call sites change by one `await`:
|
|
96
|
-
*
|
|
97
|
-
* if (!window.confirm(t("budget.delete_confirm"))) return;
|
|
98
|
-
* // becomes
|
|
99
|
-
* if (!(await confirm({ title: t("budget.delete_confirm"), tone: "danger" }))) return;
|
|
100
|
-
*
|
|
101
|
-
* ## Its own provider, not part of `UiKitProvider`
|
|
102
|
-
*
|
|
103
|
-
* `UiKitProvider` renders nothing: it is strings and a locale, it is optional, and it
|
|
104
|
-
* NESTS (an inner one re-labels one table). A dialog host inside it would mount a host
|
|
105
|
-
* per nested provider, and a component under the inner one would open a second dialog
|
|
106
|
-
* layer of its own. So the host is its own component, mounted once — inside the
|
|
107
|
-
* `UiKitProvider` whose strings it should speak, which is also the order the labels
|
|
108
|
-
* need.
|
|
109
|
-
*
|
|
110
|
-
* ## Focus
|
|
111
|
-
*
|
|
112
|
-
* On CANCEL for `tone: "danger"`, on CONFIRM otherwise. A confirm is often answered by
|
|
113
|
-
* a reflexive Enter — the key that pressed the Delete button in the first place may
|
|
114
|
-
* still be down — and for an action that cannot be undone that reflex must land on the
|
|
115
|
-
* harmless answer; `DangerConfirm` makes the same call ("never to the destructive
|
|
116
|
-
* button itself"). For everything else the confirm is the expected answer and Enter
|
|
117
|
-
* accepting it is what `window.confirm` taught users, which the migrating call sites'
|
|
118
|
-
* users already rely on. Focus returns to whatever opened it, through `Modal`'s trap.
|
|
119
|
-
*
|
|
120
|
-
* ## Escape, the backdrop, Back and Cancel all answer `false`
|
|
121
|
-
*
|
|
122
|
-
* Only the confirm button answers `true`. Anything that merely dismisses is a no.
|
|
123
|
-
*
|
|
124
|
-
* ## A second `confirm` while one is open QUEUES
|
|
125
|
-
*
|
|
126
|
-
* It is shown when the first is answered, and each call gets its own answer. The
|
|
127
|
-
* alternative — answering the open one `false` to make room — would answer a question
|
|
128
|
-
* the user never answered: a background prompt ("your session is about to expire")
|
|
129
|
-
* would cancel the delete they were reading. `false` has to mean "the user said no".
|
|
130
|
-
* A double-click cannot queue two of the same: the first click opens the modal
|
|
131
|
-
* synchronously (a discrete event's update is flushed before the next one), so the
|
|
132
|
-
* second lands on the open dialog rather than on the button — at worst on its
|
|
133
|
-
* backdrop, which answers `false`, the safe way to be wrong.
|
|
134
|
-
*
|
|
135
|
-
* Unmounting the provider answers everything still pending `false`, so no caller's
|
|
136
|
-
* `await` hangs forever.
|
|
137
|
-
*
|
|
138
|
-
* ## keksdose's synchronous call (transaction-swipe-plan.ts)
|
|
139
|
-
*
|
|
140
|
-
* `buildTxSwipeActions` is synchronous, but its `window.confirm` sits inside the
|
|
141
|
-
* action THUNK (`confirmed(key, run)` returns `() => { if (confirm(…)) run() }`), and a
|
|
142
|
-
* thunk may be async without its caller knowing. So the plan builder stays
|
|
143
|
-
* synchronous; it takes the confirm as an argument and the thunk awaits it:
|
|
144
|
-
*
|
|
145
|
-
* // mobile-transaction-list.tsx
|
|
146
|
-
* const confirm = useConfirm();
|
|
147
|
-
* buildTxSwipeActions({ tx, …, confirm });
|
|
148
|
-
*
|
|
149
|
-
* // transaction-swipe-plan.ts
|
|
150
|
-
* const confirmed = (messageKey: string, run: () => void) => () => {
|
|
151
|
-
* void confirm({ title: t(messageKey), tone: "danger" }).then((ok) => ok && run());
|
|
152
|
-
* };
|
|
153
|
-
*
|
|
154
|
-
* Its test then passes `confirm: vi.fn(async () => false)` instead of stubbing the
|
|
155
|
-
* global, and asserts after an `await` (the mutation now runs a microtask later). The
|
|
156
|
-
* row snaps back when the swipe commits, before the answer — which is right: the
|
|
157
|
-
* dialog, not the half-swiped row, is where the question now is.
|
|
158
|
-
*/
|
|
159
|
-
declare function ConfirmProvider({ children }: ConfirmProviderProps): react.JSX.Element;
|
|
160
|
-
|
|
161
|
-
export { type ConfirmDialogLabels, type ConfirmFn, type ConfirmOptions, ConfirmProvider, type ConfirmProviderProps, type ConfirmTone, DEFAULT_CONFIRM_DIALOG_LABELS, useConfirm };
|
|
1
|
+
import 'react';
|
|
2
|
+
export { I as ConfirmDialogLabels, J as ConfirmFn, K as ConfirmOptions, L as ConfirmProvider, M as ConfirmProviderProps, N as ConfirmTone, _ as DEFAULT_CONFIRM_DIALOG_LABELS, c7 as useConfirm } from '../feedback-BxeQVzwq.js';
|
|
3
|
+
import '../feedback-attachment-fGAzZPf0.js';
|
|
4
|
+
import '../feedback/feedback-inbox.js';
|
|
5
|
+
import 'lucide-react';
|
|
6
|
+
import './tooltip.js';
|
|
7
|
+
import '../hooks/use-anchored-rect.js';
|
|
8
|
+
import '../lib/clipping.js';
|
|
9
|
+
import '../data-table-labels-B7OdnM0S.js';
|
|
10
|
+
import './data-table-sort.js';
|
|
11
|
+
import './swipeable-row.js';
|
|
12
|
+
import './mini-calendar.js';
|
|
13
|
+
import './calendar-heatmap.js';
|
|
14
|
+
import './popover.js';
|
|
15
|
+
import './chip.js';
|
|
16
|
+
import './field-sync.js';
|
|
17
|
+
import '../wizard/types.js';
|
|
18
|
+
import '../tour/tour.js';
|
|
19
|
+
import '../search/command-palette.js';
|
|
20
|
+
import '../search/global-search.js';
|
|
21
|
+
import '../search/search-index.js';
|
|
22
|
+
import './month-picker.js';
|
|
23
|
+
import './page-contents.js';
|
|
24
|
+
import './series-chart-labels.js';
|
|
25
|
+
import './pie-chart-labels.js';
|
|
26
|
+
import './sparkline.js';
|
|
27
|
+
import './stat-tile.js';
|
|
28
|
+
import './signature-pad.js';
|
|
29
|
+
import './password-strength.js';
|
|
30
|
+
import './swatch-picker.js';
|
|
31
|
+
import './tile-radio.js';
|
|
32
|
+
import './icon-picker.js';
|
|
33
|
+
import './dialog-frame.js';
|
|
34
|
+
import './modal.js';
|
|
35
|
+
import '../hooks/use-search-param-state.js';
|
|
36
|
+
import './measured-grid.js';
|
|
37
|
+
import './account-settings-labels.js';
|
|
38
|
+
import './floating-panel.js';
|
|
39
|
+
import './bulk-action-bar.js';
|
|
40
|
+
import './list.js';
|
|
41
|
+
import './signed-amount.js';
|
|
42
|
+
import './breadcrumbs.js';
|
|
43
|
+
import './toast.js';
|
|
44
|
+
import './description-list.js';
|
|
45
|
+
import './line-items.js';
|
|
46
|
+
import './progress-bar.js';
|
|
47
|
+
import './error-boundary.js';
|
|
48
|
+
import './authed-image.js';
|
|
49
|
+
import '../hooks/use-authed-src.js';
|
|
50
|
+
import './image-grid.js';
|
|
51
|
+
import './lightbox.js';
|
|
52
|
+
import './write-lock.js';
|
|
53
|
+
import './legal.js';
|
|
54
|
+
import './account-chips.js';
|
|
55
|
+
import '../lib/format.js';
|
|
56
|
+
import './reauth-dialog.js';
|
|
57
|
+
import './server-wake.js';
|
|
58
|
+
import '../lib/server-wake.js';
|
|
59
|
+
import './translation-review-labels.js';
|
|
60
|
+
import '../lib/translation-review.js';
|
|
61
|
+
import './country-select.js';
|
|
62
|
+
import './inline-edit-field.js';
|
|
63
|
+
import '../lib/iban.js';
|
|
64
|
+
import '../lib/phone.js';
|
|
65
|
+
import './sign-chip.js';
|
|
66
|
+
import './column-mapper.js';
|
|
67
|
+
import '../lib/column-mapping.js';
|
|
68
|
+
import '../lib/table-text.js';
|
|
69
|
+
import './field-parts.js';
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
4
4
|
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
5
|
-
import { typedMatches } from "./danger-confirm.js";
|
|
5
|
+
import { TypedConfirmField, typedMatches } from "./danger-confirm.js";
|
|
6
6
|
import { DialogFrame } from "./dialog-frame.js";
|
|
7
|
-
import { Button
|
|
7
|
+
import { Button } from "./ui.js";
|
|
8
8
|
const DEFAULT_CONFIRM_DIALOG_LABELS = {
|
|
9
9
|
confirm: "Confirm",
|
|
10
10
|
cancel: "Cancel",
|
|
@@ -102,16 +102,15 @@ function ConfirmDialogView({
|
|
|
102
102
|
confirmRef.current?.click();
|
|
103
103
|
},
|
|
104
104
|
children: /* @__PURE__ */ jsx(
|
|
105
|
-
|
|
105
|
+
TypedConfirmField,
|
|
106
106
|
{
|
|
107
107
|
ref: typedRef,
|
|
108
|
+
target,
|
|
109
|
+
match: request.typedMatch ?? "caseless",
|
|
108
110
|
label: typedLabel,
|
|
111
|
+
placeholder: "",
|
|
109
112
|
value: typed,
|
|
110
|
-
|
|
111
|
-
autoCapitalize: "off",
|
|
112
|
-
autoCorrect: "off",
|
|
113
|
-
spellCheck: false,
|
|
114
|
-
onChange: (e) => setTyped(e.target.value)
|
|
113
|
+
onValueChange: setTyped
|
|
115
114
|
}
|
|
116
115
|
)
|
|
117
116
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/confirm-dialog.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { typedMatches } from \"./danger-confirm\";\nimport type { TypedMatch } from \"./danger-confirm\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { Button, Input } from \"./ui\";\n\n/**\n * The `confirmDialog` namespace of `<UiKitProvider labels>`: the two buttons' fallback\n * text. A call that knows what it is confirming says so per call (`confirmLabel:\n * \"Delete budget\"`), which is what the dialog is for — these are for the rest.\n */\nexport interface ConfirmDialogLabels {\n confirm: string;\n cancel: string;\n /**\n * 0.18: label of the field a `requireTyped` confirm shows. A FUNCTION of the text to\n * type by default — where the address sits in the sentence moves with the language —\n * or a finished STRING. A call's own `typedLabel` wins over it.\n *\n * Required, like every key of a namespace: the shipped catalogues\n * (`@eifi1/ui-kit/i18n/<code>`) translate it, and an app's own full catalogue should\n * hear about the new string from the compiler rather than show it in English.\n */\n typed: string | ((text: string) => string);\n}\n\n/** `satisfies` rather than a type annotation, so `.typed` stays known-callable for a\n * caller composing its own label from it. */\nexport const DEFAULT_CONFIRM_DIALOG_LABELS = {\n confirm: \"Confirm\",\n cancel: \"Cancel\",\n typed: (text: string) => `Type “${text}” to confirm`,\n} satisfies ConfirmDialogLabels;\n\nexport type ConfirmTone = \"danger\" | \"warning\" | \"neutral\";\n\nexport interface ConfirmOptions {\n /** The question, and the dialog's accessible name. */\n title: ReactNode;\n /** The smaller line under it — the consequence. Wired to `aria-describedby`. */\n body?: ReactNode;\n /** Default: `confirmDialog.confirm` from the provider, else \"Confirm\". */\n confirmLabel?: ReactNode;\n /** Default: `confirmDialog.cancel` from the provider, else \"Cancel\". */\n cancelLabel?: ReactNode;\n /**\n * `\"danger\"` for what cannot be undone (a delete): the confirm is the destructive\n * button and focus starts on CANCEL. `\"warning\"` and `\"neutral\"` (default) start on\n * the confirm. See {@link ConfirmProvider} for why the two differ.\n */\n tone?: ConfirmTone;\n /**\n * 0.18: make the user TYPE this before confirm is enabled — the target account's\n * e-mail address, a budget's name. For the action whose risk is not \"did you mean\n * it\" but \"is this the right row\": an admin who deactivates somebody has already\n * proven who THEY are (they passed the admin gate); typing the address is what\n * proves they read which account they are on. Both keksdose (`TYPE_EMAIL`) and\n * Kurvenschmiede (`TypedConfirm`) built this beside `useConfirm` because it had no\n * field.\n *\n * The dialog then shows one text field under the body and focuses it (whatever the\n * tone: a reflexive Enter cannot confirm a field that does not match yet). Confirm\n * stays disabled until the text matches; Enter in the field confirms once it does.\n * The answer is still `true` / `false` — the text matched what you passed, so send\n * that to the server if it asks for the address too, not the user's spelling.\n */\n requireTyped?: string;\n /** Label of that field. Default: `confirmDialog.typed` — `Type “a@b.c” to confirm`. */\n typedLabel?: string;\n /**\n * How the typed text is compared. Default `\"caseless\"`: surrounding spaces and case\n * ignored, the rule both apps applied to an address. `\"trim\"` keeps case, `\"exact\"`\n * keeps spaces too — for a phrase like \"DELETE\" whose contract is \"exactly this\".\n */\n typedMatch?: TypedMatch;\n}\n\nexport type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;\n\nconst ConfirmContext = createContext<ConfirmFn | null>(null);\n\n/**\n * `const confirm = useConfirm()` → `if (!(await confirm({ title }))) return`.\n *\n * The returned function is STABLE for the life of the provider, so it can sit in an\n * effect's or a `useCallback`'s dependency list without re-running it.\n *\n * Throws outside a {@link ConfirmProvider}, rather than falling back to\n * `window.confirm`: a silent fallback is exactly the native dialog the three apps are\n * migrating away from, and it would look like the migration had worked.\n */\nexport function useConfirm(): ConfirmFn {\n const confirm = useContext(ConfirmContext);\n if (!confirm) {\n throw new Error(\"useConfirm() must be called below a <ConfirmProvider>.\");\n }\n return confirm;\n}\n\ninterface Request extends ConfirmOptions {\n id: number;\n resolve: (answer: boolean) => void;\n}\n\nexport interface ConfirmProviderProps {\n children: ReactNode;\n}\n\n/**\n * The host for {@link useConfirm}: ONE confirm dialog for the whole app, rendered on\n * {@link DialogFrame}, answering a promise.\n *\n * Asked for by all three apps at once — keksdose has 20+ `window.confirm` calls,\n * kastlan 7, lenkbank 1. The native dialog cannot be translated past its OK/Cancel\n * (those are the BROWSER's language, not the app's), cannot say which button is the\n * destructive one, and on an installed PWA it prints the origin over the question.\n * A promise is what lets each of those call sites change by one `await`:\n *\n * if (!window.confirm(t(\"budget.delete_confirm\"))) return;\n * // becomes\n * if (!(await confirm({ title: t(\"budget.delete_confirm\"), tone: \"danger\" }))) return;\n *\n * ## Its own provider, not part of `UiKitProvider`\n *\n * `UiKitProvider` renders nothing: it is strings and a locale, it is optional, and it\n * NESTS (an inner one re-labels one table). A dialog host inside it would mount a host\n * per nested provider, and a component under the inner one would open a second dialog\n * layer of its own. So the host is its own component, mounted once — inside the\n * `UiKitProvider` whose strings it should speak, which is also the order the labels\n * need.\n *\n * ## Focus\n *\n * On CANCEL for `tone: \"danger\"`, on CONFIRM otherwise. A confirm is often answered by\n * a reflexive Enter — the key that pressed the Delete button in the first place may\n * still be down — and for an action that cannot be undone that reflex must land on the\n * harmless answer; `DangerConfirm` makes the same call (\"never to the destructive\n * button itself\"). For everything else the confirm is the expected answer and Enter\n * accepting it is what `window.confirm` taught users, which the migrating call sites'\n * users already rely on. Focus returns to whatever opened it, through `Modal`'s trap.\n *\n * ## Escape, the backdrop, Back and Cancel all answer `false`\n *\n * Only the confirm button answers `true`. Anything that merely dismisses is a no.\n *\n * ## A second `confirm` while one is open QUEUES\n *\n * It is shown when the first is answered, and each call gets its own answer. The\n * alternative — answering the open one `false` to make room — would answer a question\n * the user never answered: a background prompt (\"your session is about to expire\")\n * would cancel the delete they were reading. `false` has to mean \"the user said no\".\n * A double-click cannot queue two of the same: the first click opens the modal\n * synchronously (a discrete event's update is flushed before the next one), so the\n * second lands on the open dialog rather than on the button — at worst on its\n * backdrop, which answers `false`, the safe way to be wrong.\n *\n * Unmounting the provider answers everything still pending `false`, so no caller's\n * `await` hangs forever.\n *\n * ## keksdose's synchronous call (transaction-swipe-plan.ts)\n *\n * `buildTxSwipeActions` is synchronous, but its `window.confirm` sits inside the\n * action THUNK (`confirmed(key, run)` returns `() => { if (confirm(…)) run() }`), and a\n * thunk may be async without its caller knowing. So the plan builder stays\n * synchronous; it takes the confirm as an argument and the thunk awaits it:\n *\n * // mobile-transaction-list.tsx\n * const confirm = useConfirm();\n * buildTxSwipeActions({ tx, …, confirm });\n *\n * // transaction-swipe-plan.ts\n * const confirmed = (messageKey: string, run: () => void) => () => {\n * void confirm({ title: t(messageKey), tone: \"danger\" }).then((ok) => ok && run());\n * };\n *\n * Its test then passes `confirm: vi.fn(async () => false)` instead of stubbing the\n * global, and asserts after an `await` (the mutation now runs a microtask later). The\n * row snaps back when the swipe commits, before the answer — which is right: the\n * dialog, not the half-swiped row, is where the question now is.\n */\nexport function ConfirmProvider({ children }: ConfirmProviderProps) {\n const [queue, setQueue] = useState<Request[]>([]);\n const nextId = useRef(0);\n // Every unanswered request, including the ones not yet in state — the unmount\n // cleanup below must reach a request made in the same tick it unmounts.\n const pending = useRef(new Set<Request>());\n\n const confirm = useCallback<ConfirmFn>(\n (options) =>\n new Promise<boolean>((resolve) => {\n const request: Request = { ...options, id: ++nextId.current, resolve };\n pending.current.add(request);\n setQueue((q) => [...q, request]);\n }),\n [],\n );\n\n useEffect(() => {\n const open = pending.current;\n return () => {\n for (const r of open) r.resolve(false);\n open.clear();\n };\n }, []);\n\n const answer = useCallback((request: Request, value: boolean) => {\n if (!pending.current.delete(request)) return;\n request.resolve(value);\n setQueue((q) => q.filter((r) => r !== request));\n }, []);\n\n const current = queue[0];\n return (\n <ConfirmContext.Provider value={confirm}>\n {children}\n {current && (\n // Keyed per request, so a queued one gets a fresh Modal: the first one's trap\n // hands focus back to the page, and the next one's captures that as its own\n // restore target instead of a button that has just been unmounted.\n <ConfirmDialogView key={current.id} request={current} onAnswer={answer} />\n )}\n </ConfirmContext.Provider>\n );\n}\n\nfunction ConfirmDialogView({\n request,\n onAnswer,\n}: {\n request: Request;\n onAnswer: (request: Request, value: boolean) => void;\n}) {\n const labels = useKitLabels(\"confirmDialog\", DEFAULT_CONFIRM_DIALOG_LABELS);\n const tone = request.tone ?? \"neutral\";\n const confirmRef = useRef<HTMLButtonElement>(null);\n const cancelRef = useRef<HTMLButtonElement>(null);\n // Set by the confirm button, read once the panel has finished lowering. Everything\n // else that ends in `onClose` — Escape, the backdrop, Back, Cancel — leaves it false.\n const accepted = useRef(false);\n const typedRef = useRef<HTMLInputElement>(null);\n const [typed, setTyped] = useState(\"\");\n const target = request.requireTyped;\n const matches = target === undefined || typedMatches(typed, target, request.typedMatch ?? \"caseless\");\n\n // In THIS component's effect, which runs after `Modal`'s own (a parent's effects run\n // after its children's). `Modal` engages its focus trap on the panel first — and\n // records the page's focused element as the restore target — and only then does\n // focus move to the button. Focusing it from inside the panel would run first and\n // make the button the restore target. A typed confirm starts in its field: typing is\n // the one thing the dialog is waiting for.\n useEffect(() => {\n (target !== undefined ? typedRef : tone === \"danger\" ? cancelRef : confirmRef).current?.focus();\n }, [tone, target]);\n\n const typedLabel =\n target === undefined\n ? \"\"\n : (request.typedLabel ??\n (typeof labels.typed === \"string\" ? labels.typed : labels.typed(target)));\n\n return (\n <DialogFrame\n title={request.title}\n description={request.body}\n onClose={() => onAnswer(request, accepted.current)}\n // A confirmation interrupts to ask a question that must be answered.\n role=\"alertdialog\"\n data-confirm-tone={tone}\n actions={(close) => (\n <>\n <Button ref={cancelRef} type=\"button\" variant=\"ghost\" onClick={close}>\n {request.cancelLabel ?? labels.cancel}\n </Button>\n <Button\n ref={confirmRef}\n type=\"button\"\n variant={tone === \"danger\" ? \"danger\" : \"primary\"}\n disabled={!matches}\n onClick={() => {\n accepted.current = true;\n close();\n }}\n >\n {request.confirmLabel ?? labels.confirm}\n </Button>\n </>\n )}\n >\n {/* The field, when there is one, is the body; a plain confirm keeps having none. */}\n {target !== undefined && (\n <form\n noValidate\n onSubmit={(e) => {\n e.preventDefault();\n // Through the button, so Enter and a click are one path — and a disabled\n // (not yet matching) confirm ignores the click.\n confirmRef.current?.click();\n }}\n >\n <Input\n ref={typedRef}\n label={typedLabel}\n value={typed}\n autoComplete=\"off\"\n autoCapitalize=\"off\"\n autoCorrect=\"off\"\n spellCheck={false}\n onChange={(e) => setTyped(e.target.value)}\n />\n </form>\n )}\n </DialogFrame>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/confirm-dialog.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { TypedConfirmField, typedMatches } from \"./danger-confirm\";\nimport type { TypedMatch } from \"./danger-confirm\";\nimport { DialogFrame } from \"./dialog-frame\";\nimport { Button } from \"./ui\";\n\n/**\n * The `confirmDialog` namespace of `<UiKitProvider labels>`: the two buttons' fallback\n * text. A call that knows what it is confirming says so per call (`confirmLabel:\n * \"Delete budget\"`), which is what the dialog is for — these are for the rest.\n */\nexport interface ConfirmDialogLabels {\n confirm: string;\n cancel: string;\n /**\n * 0.18: label of the field a `requireTyped` confirm shows. A FUNCTION of the text to\n * type by default — where the address sits in the sentence moves with the language —\n * or a finished STRING. A call's own `typedLabel` wins over it.\n *\n * Required, like every key of a namespace: the shipped catalogues\n * (`@eifi1/ui-kit/i18n/<code>`) translate it, and an app's own full catalogue should\n * hear about the new string from the compiler rather than show it in English.\n */\n typed: string | ((text: string) => string);\n}\n\n/** `satisfies` rather than a type annotation, so `.typed` stays known-callable for a\n * caller composing its own label from it. */\nexport const DEFAULT_CONFIRM_DIALOG_LABELS = {\n confirm: \"Confirm\",\n cancel: \"Cancel\",\n typed: (text: string) => `Type “${text}” to confirm`,\n} satisfies ConfirmDialogLabels;\n\nexport type ConfirmTone = \"danger\" | \"warning\" | \"neutral\";\n\nexport interface ConfirmOptions {\n /** The question, and the dialog's accessible name. */\n title: ReactNode;\n /** The smaller line under it — the consequence. Wired to `aria-describedby`. */\n body?: ReactNode;\n /** Default: `confirmDialog.confirm` from the provider, else \"Confirm\". */\n confirmLabel?: ReactNode;\n /** Default: `confirmDialog.cancel` from the provider, else \"Cancel\". */\n cancelLabel?: ReactNode;\n /**\n * `\"danger\"` for what cannot be undone (a delete): the confirm is the destructive\n * button and focus starts on CANCEL. `\"warning\"` and `\"neutral\"` (default) start on\n * the confirm. See {@link ConfirmProvider} for why the two differ.\n */\n tone?: ConfirmTone;\n /**\n * 0.18: make the user TYPE this before confirm is enabled — the target account's\n * e-mail address, a budget's name. For the action whose risk is not \"did you mean\n * it\" but \"is this the right row\": an admin who deactivates somebody has already\n * proven who THEY are (they passed the admin gate); typing the address is what\n * proves they read which account they are on. Both keksdose (`TYPE_EMAIL`) and\n * Kurvenschmiede (`TypedConfirm`) built this beside `useConfirm` because it had no\n * field.\n *\n * The dialog then shows one text field under the body and focuses it (whatever the\n * tone: a reflexive Enter cannot confirm a field that does not match yet). Confirm\n * stays disabled until the text matches; Enter in the field confirms once it does.\n * The answer is still `true` / `false` — the text matched what you passed, so send\n * that to the server if it asks for the address too, not the user's spelling.\n */\n requireTyped?: string;\n /** Label of that field. Default: `confirmDialog.typed` — `Type “a@b.c” to confirm`. */\n typedLabel?: string;\n /**\n * How the typed text is compared. Default `\"caseless\"`: surrounding spaces and case\n * ignored, the rule both apps applied to an address. `\"trim\"` keeps case, `\"exact\"`\n * keeps spaces too — for a phrase like \"DELETE\" whose contract is \"exactly this\".\n */\n typedMatch?: TypedMatch;\n}\n\nexport type ConfirmFn = (options: ConfirmOptions) => Promise<boolean>;\n\nconst ConfirmContext = createContext<ConfirmFn | null>(null);\n\n/**\n * `const confirm = useConfirm()` → `if (!(await confirm({ title }))) return`.\n *\n * The returned function is STABLE for the life of the provider, so it can sit in an\n * effect's or a `useCallback`'s dependency list without re-running it.\n *\n * Throws outside a {@link ConfirmProvider}, rather than falling back to\n * `window.confirm`: a silent fallback is exactly the native dialog the three apps are\n * migrating away from, and it would look like the migration had worked.\n */\nexport function useConfirm(): ConfirmFn {\n const confirm = useContext(ConfirmContext);\n if (!confirm) {\n throw new Error(\"useConfirm() must be called below a <ConfirmProvider>.\");\n }\n return confirm;\n}\n\ninterface Request extends ConfirmOptions {\n id: number;\n resolve: (answer: boolean) => void;\n}\n\nexport interface ConfirmProviderProps {\n children: ReactNode;\n}\n\n/**\n * The host for {@link useConfirm}: ONE confirm dialog for the whole app, rendered on\n * {@link DialogFrame}, answering a promise.\n *\n * Asked for by all three apps at once — keksdose has 20+ `window.confirm` calls,\n * kastlan 7, lenkbank 1. The native dialog cannot be translated past its OK/Cancel\n * (those are the BROWSER's language, not the app's), cannot say which button is the\n * destructive one, and on an installed PWA it prints the origin over the question.\n * A promise is what lets each of those call sites change by one `await`:\n *\n * if (!window.confirm(t(\"budget.delete_confirm\"))) return;\n * // becomes\n * if (!(await confirm({ title: t(\"budget.delete_confirm\"), tone: \"danger\" }))) return;\n *\n * ## Its own provider, not part of `UiKitProvider`\n *\n * `UiKitProvider` renders nothing: it is strings and a locale, it is optional, and it\n * NESTS (an inner one re-labels one table). A dialog host inside it would mount a host\n * per nested provider, and a component under the inner one would open a second dialog\n * layer of its own. So the host is its own component, mounted once — inside the\n * `UiKitProvider` whose strings it should speak, which is also the order the labels\n * need.\n *\n * ## Focus\n *\n * On CANCEL for `tone: \"danger\"`, on CONFIRM otherwise. A confirm is often answered by\n * a reflexive Enter — the key that pressed the Delete button in the first place may\n * still be down — and for an action that cannot be undone that reflex must land on the\n * harmless answer; `DangerConfirm` makes the same call (\"never to the destructive\n * button itself\"). For everything else the confirm is the expected answer and Enter\n * accepting it is what `window.confirm` taught users, which the migrating call sites'\n * users already rely on. Focus returns to whatever opened it, through `Modal`'s trap.\n *\n * ## Escape, the backdrop, Back and Cancel all answer `false`\n *\n * Only the confirm button answers `true`. Anything that merely dismisses is a no.\n *\n * ## A second `confirm` while one is open QUEUES\n *\n * It is shown when the first is answered, and each call gets its own answer. The\n * alternative — answering the open one `false` to make room — would answer a question\n * the user never answered: a background prompt (\"your session is about to expire\")\n * would cancel the delete they were reading. `false` has to mean \"the user said no\".\n * A double-click cannot queue two of the same: the first click opens the modal\n * synchronously (a discrete event's update is flushed before the next one), so the\n * second lands on the open dialog rather than on the button — at worst on its\n * backdrop, which answers `false`, the safe way to be wrong.\n *\n * Unmounting the provider answers everything still pending `false`, so no caller's\n * `await` hangs forever.\n *\n * ## keksdose's synchronous call (transaction-swipe-plan.ts)\n *\n * `buildTxSwipeActions` is synchronous, but its `window.confirm` sits inside the\n * action THUNK (`confirmed(key, run)` returns `() => { if (confirm(…)) run() }`), and a\n * thunk may be async without its caller knowing. So the plan builder stays\n * synchronous; it takes the confirm as an argument and the thunk awaits it:\n *\n * // mobile-transaction-list.tsx\n * const confirm = useConfirm();\n * buildTxSwipeActions({ tx, …, confirm });\n *\n * // transaction-swipe-plan.ts\n * const confirmed = (messageKey: string, run: () => void) => () => {\n * void confirm({ title: t(messageKey), tone: \"danger\" }).then((ok) => ok && run());\n * };\n *\n * Its test then passes `confirm: vi.fn(async () => false)` instead of stubbing the\n * global, and asserts after an `await` (the mutation now runs a microtask later). The\n * row snaps back when the swipe commits, before the answer — which is right: the\n * dialog, not the half-swiped row, is where the question now is.\n */\nexport function ConfirmProvider({ children }: ConfirmProviderProps) {\n const [queue, setQueue] = useState<Request[]>([]);\n const nextId = useRef(0);\n // Every unanswered request, including the ones not yet in state — the unmount\n // cleanup below must reach a request made in the same tick it unmounts.\n const pending = useRef(new Set<Request>());\n\n const confirm = useCallback<ConfirmFn>(\n (options) =>\n new Promise<boolean>((resolve) => {\n const request: Request = { ...options, id: ++nextId.current, resolve };\n pending.current.add(request);\n setQueue((q) => [...q, request]);\n }),\n [],\n );\n\n useEffect(() => {\n const open = pending.current;\n return () => {\n for (const r of open) r.resolve(false);\n open.clear();\n };\n }, []);\n\n const answer = useCallback((request: Request, value: boolean) => {\n if (!pending.current.delete(request)) return;\n request.resolve(value);\n setQueue((q) => q.filter((r) => r !== request));\n }, []);\n\n const current = queue[0];\n return (\n <ConfirmContext.Provider value={confirm}>\n {children}\n {current && (\n // Keyed per request, so a queued one gets a fresh Modal: the first one's trap\n // hands focus back to the page, and the next one's captures that as its own\n // restore target instead of a button that has just been unmounted.\n <ConfirmDialogView key={current.id} request={current} onAnswer={answer} />\n )}\n </ConfirmContext.Provider>\n );\n}\n\nfunction ConfirmDialogView({\n request,\n onAnswer,\n}: {\n request: Request;\n onAnswer: (request: Request, value: boolean) => void;\n}) {\n const labels = useKitLabels(\"confirmDialog\", DEFAULT_CONFIRM_DIALOG_LABELS);\n const tone = request.tone ?? \"neutral\";\n const confirmRef = useRef<HTMLButtonElement>(null);\n const cancelRef = useRef<HTMLButtonElement>(null);\n // Set by the confirm button, read once the panel has finished lowering. Everything\n // else that ends in `onClose` — Escape, the backdrop, Back, Cancel — leaves it false.\n const accepted = useRef(false);\n const typedRef = useRef<HTMLInputElement>(null);\n const [typed, setTyped] = useState(\"\");\n const target = request.requireTyped;\n const matches = target === undefined || typedMatches(typed, target, request.typedMatch ?? \"caseless\");\n\n // In THIS component's effect, which runs after `Modal`'s own (a parent's effects run\n // after its children's). `Modal` engages its focus trap on the panel first — and\n // records the page's focused element as the restore target — and only then does\n // focus move to the button. Focusing it from inside the panel would run first and\n // make the button the restore target. A typed confirm starts in its field: typing is\n // the one thing the dialog is waiting for.\n useEffect(() => {\n (target !== undefined ? typedRef : tone === \"danger\" ? cancelRef : confirmRef).current?.focus();\n }, [tone, target]);\n\n const typedLabel =\n target === undefined\n ? \"\"\n : (request.typedLabel ??\n (typeof labels.typed === \"string\" ? labels.typed : labels.typed(target)));\n\n return (\n <DialogFrame\n title={request.title}\n description={request.body}\n onClose={() => onAnswer(request, accepted.current)}\n // A confirmation interrupts to ask a question that must be answered.\n role=\"alertdialog\"\n data-confirm-tone={tone}\n actions={(close) => (\n <>\n <Button ref={cancelRef} type=\"button\" variant=\"ghost\" onClick={close}>\n {request.cancelLabel ?? labels.cancel}\n </Button>\n <Button\n ref={confirmRef}\n type=\"button\"\n variant={tone === \"danger\" ? \"danger\" : \"primary\"}\n disabled={!matches}\n onClick={() => {\n accepted.current = true;\n close();\n }}\n >\n {request.confirmLabel ?? labels.confirm}\n </Button>\n </>\n )}\n >\n {/* The field, when there is one, is the body; a plain confirm keeps having none. */}\n {target !== undefined && (\n <form\n noValidate\n onSubmit={(e) => {\n e.preventDefault();\n // Through the button, so Enter and a click are one path — and a disabled\n // (not yet matching) confirm ignores the click.\n confirmRef.current?.click();\n }}\n >\n {/* The kit's one typed-confirmation field (0.23), DangerConfirm's too: the\n keyboard told to leave the text alone, `data-matched` on the input. The\n label is this namespace's (`confirmDialog.typed`), and `placeholder=\"\"`\n keeps out the `dangerConfirm.phrasePlaceholder` a provider may set for\n the tile — this field never had one. */}\n <TypedConfirmField\n ref={typedRef}\n target={target}\n match={request.typedMatch ?? \"caseless\"}\n label={typedLabel}\n placeholder=\"\"\n value={typed}\n onValueChange={setTyped}\n />\n </form>\n )}\n </DialogFrame>\n );\n}\n"],"mappings":";AAwNI,SAwDI,UAlDA,KANJ;AAxNJ,SAAS,eAAe,aAAa,YAAY,WAAW,QAAQ,gBAAgB;AAGpF,SAAS,oBAAoB;AAC7B,SAAS,mBAAmB,oBAAoB;AAEhD,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AAwBhB,MAAM,gCAAgC;AAAA,EAC3C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO,CAAC,SAAiB,cAAS,IAAI;AACxC;AA+CA,MAAM,iBAAiB,cAAgC,IAAI;AAYpD,SAAS,aAAwB;AACtC,QAAM,UAAU,WAAW,cAAc;AACzC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,wDAAwD;AAAA,EAC1E;AACA,SAAO;AACT;AAmFO,SAAS,gBAAgB,EAAE,SAAS,GAAyB;AAClE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAoB,CAAC,CAAC;AAChD,QAAM,SAAS,OAAO,CAAC;AAGvB,QAAM,UAAU,OAAO,oBAAI,IAAa,CAAC;AAEzC,QAAM,UAAU;AAAA,IACd,CAAC,YACC,IAAI,QAAiB,CAAC,YAAY;AAChC,YAAM,UAAmB,EAAE,GAAG,SAAS,IAAI,EAAE,OAAO,SAAS,QAAQ;AACrE,cAAQ,QAAQ,IAAI,OAAO;AAC3B,eAAS,CAAC,MAAM,CAAC,GAAG,GAAG,OAAO,CAAC;AAAA,IACjC,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,YAAU,MAAM;AACd,UAAM,OAAO,QAAQ;AACrB,WAAO,MAAM;AACX,iBAAW,KAAK,KAAM,GAAE,QAAQ,KAAK;AACrC,WAAK,MAAM;AAAA,IACb;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,SAAS,YAAY,CAAC,SAAkB,UAAmB;AAC/D,QAAI,CAAC,QAAQ,QAAQ,OAAO,OAAO,EAAG;AACtC,YAAQ,QAAQ,KAAK;AACrB,aAAS,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,MAAM,OAAO,CAAC;AAAA,EAChD,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM,CAAC;AACvB,SACE,qBAAC,eAAe,UAAf,EAAwB,OAAO,SAC7B;AAAA;AAAA,IACA;AAAA;AAAA;AAAA,IAIC,oBAAC,qBAAmC,SAAS,SAAS,UAAU,UAAxC,QAAQ,EAAwC;AAAA,KAE5E;AAEJ;AAEA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AACF,GAGG;AACD,QAAM,SAAS,aAAa,iBAAiB,6BAA6B;AAC1E,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,aAAa,OAA0B,IAAI;AACjD,QAAM,YAAY,OAA0B,IAAI;AAGhD,QAAM,WAAW,OAAO,KAAK;AAC7B,QAAM,WAAW,OAAyB,IAAI;AAC9C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,SAAS,QAAQ;AACvB,QAAM,UAAU,WAAW,UAAa,aAAa,OAAO,QAAQ,QAAQ,cAAc,UAAU;AAQpG,YAAU,MAAM;AACd,KAAC,WAAW,SAAY,WAAW,SAAS,WAAW,YAAY,YAAY,SAAS,MAAM;AAAA,EAChG,GAAG,CAAC,MAAM,MAAM,CAAC;AAEjB,QAAM,aACJ,WAAW,SACP,KACC,QAAQ,eACR,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,OAAO,MAAM,MAAM;AAE5E,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,QAAQ;AAAA,MACf,aAAa,QAAQ;AAAA,MACrB,SAAS,MAAM,SAAS,SAAS,SAAS,OAAO;AAAA,MAEjD,MAAK;AAAA,MACL,qBAAmB;AAAA,MACnB,SAAS,CAAC,UACR,iCACE;AAAA,4BAAC,UAAO,KAAK,WAAW,MAAK,UAAS,SAAQ,SAAQ,SAAS,OAC5D,kBAAQ,eAAe,OAAO,QACjC;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,SAAS,SAAS,WAAW,WAAW;AAAA,YACxC,UAAU,CAAC;AAAA,YACX,SAAS,MAAM;AACb,uBAAS,UAAU;AACnB,oBAAM;AAAA,YACR;AAAA,YAEC,kBAAQ,gBAAgB,OAAO;AAAA;AAAA,QAClC;AAAA,SACF;AAAA,MAID,qBAAW,UACV;AAAA,QAAC;AAAA;AAAA,UACC,YAAU;AAAA,UACV,UAAU,CAAC,MAAM;AACf,cAAE,eAAe;AAGjB,uBAAW,SAAS,MAAM;AAAA,UAC5B;AAAA,UAOA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL;AAAA,cACA,OAAO,QAAQ,cAAc;AAAA,cAC7B,OAAO;AAAA,cACP,aAAY;AAAA,cACZ,OAAO;AAAA,cACP,eAAe;AAAA;AAAA,UACjB;AAAA;AAAA,MACF;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import 'react';
|
|
2
|
-
export {
|
|
2
|
+
export { O as CopyButton, P as CopyButtonLabels, Q as CopyButtonProps, $ as DEFAULT_COPY_BUTTON_LABELS } from '../feedback-BxeQVzwq.js';
|
|
3
3
|
import './tooltip.js';
|
|
4
|
-
import '
|
|
4
|
+
import '../feedback-attachment-fGAzZPf0.js';
|
|
5
|
+
import '../feedback/feedback-inbox.js';
|
|
6
|
+
import 'lucide-react';
|
|
7
|
+
import '../data-table-labels-B7OdnM0S.js';
|
|
8
|
+
import './data-table-sort.js';
|
|
9
|
+
import './swipeable-row.js';
|
|
5
10
|
import './mini-calendar.js';
|
|
6
11
|
import './calendar-heatmap.js';
|
|
7
12
|
import './popover.js';
|
|
8
13
|
import './chip.js';
|
|
9
|
-
import 'lucide-react';
|
|
10
14
|
import './field-sync.js';
|
|
11
15
|
import '../wizard/types.js';
|
|
12
16
|
import '../tour/tour.js';
|
|
@@ -21,7 +25,6 @@ import './sparkline.js';
|
|
|
21
25
|
import './stat-tile.js';
|
|
22
26
|
import './signature-pad.js';
|
|
23
27
|
import './password-strength.js';
|
|
24
|
-
import './danger-confirm.js';
|
|
25
28
|
import './swatch-picker.js';
|
|
26
29
|
import './tile-radio.js';
|
|
27
30
|
import './icon-picker.js';
|
|
@@ -29,11 +32,7 @@ import './dialog-frame.js';
|
|
|
29
32
|
import './modal.js';
|
|
30
33
|
import '../hooks/use-search-param-state.js';
|
|
31
34
|
import './measured-grid.js';
|
|
32
|
-
import '../feedback-attachment-WFiXJ8We.js';
|
|
33
|
-
import '../feedback/feedback-thread.js';
|
|
34
|
-
import '../feedback/feedback-inbox.js';
|
|
35
35
|
import './account-settings-labels.js';
|
|
36
|
-
import './confirm-dialog.js';
|
|
37
36
|
import './floating-panel.js';
|
|
38
37
|
import '../hooks/use-anchored-rect.js';
|
|
39
38
|
import '../lib/clipping.js';
|
|
@@ -54,11 +53,17 @@ import './write-lock.js';
|
|
|
54
53
|
import './legal.js';
|
|
55
54
|
import './account-chips.js';
|
|
56
55
|
import '../lib/format.js';
|
|
57
|
-
import '../data-table-3mi2R3so.js';
|
|
58
|
-
import './data-table-sort.js';
|
|
59
|
-
import './swipeable-row.js';
|
|
60
56
|
import './reauth-dialog.js';
|
|
61
57
|
import './server-wake.js';
|
|
62
58
|
import '../lib/server-wake.js';
|
|
63
59
|
import './translation-review-labels.js';
|
|
64
60
|
import '../lib/translation-review.js';
|
|
61
|
+
import './country-select.js';
|
|
62
|
+
import './inline-edit-field.js';
|
|
63
|
+
import '../lib/iban.js';
|
|
64
|
+
import '../lib/phone.js';
|
|
65
|
+
import './sign-chip.js';
|
|
66
|
+
import './column-mapper.js';
|
|
67
|
+
import '../lib/column-mapping.js';
|
|
68
|
+
import '../lib/table-text.js';
|
|
69
|
+
import './field-parts.js';
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, Ref, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/** The words {@link CountrySelect} renders on its own behalf. The list's "no results",
|
|
5
|
+
* "loading" and result-count announcements are the shared `combobox` namespace's,
|
|
6
|
+
* as on every other searchable picker. */
|
|
7
|
+
interface CountrySelectLabels {
|
|
8
|
+
/** What the field is, when the caller gives no `label`: the first half of the
|
|
9
|
+
* trigger's accessible name, the phone sheet's title, and the empty trigger's
|
|
10
|
+
* text. */
|
|
11
|
+
country: string;
|
|
12
|
+
/** The search box in the open list. */
|
|
13
|
+
search: string;
|
|
14
|
+
/** The heading between the `preferred` countries and all the others. Shown only
|
|
15
|
+
* when `preferred` puts something above it. */
|
|
16
|
+
others: string;
|
|
17
|
+
}
|
|
18
|
+
declare const DEFAULT_COUNTRY_SELECT_LABELS: CountrySelectLabels;
|
|
19
|
+
/**
|
|
20
|
+
* `onChange` is the kit's — "a country was picked", carrying its code — rather than
|
|
21
|
+
* the div's form event. `id` and the `aria-*` wiring below go to the TRIGGER, which is
|
|
22
|
+
* the control a reader meets; everything else in `rest` dresses the wrapper.
|
|
23
|
+
*
|
|
24
|
+
* `Clearable` follows `clearable`, the way {@link EntityCombobox}'s `C` follows its
|
|
25
|
+
* `clearValue`: it is inferred from the prop, so `onChange` is `(code: string) => void`
|
|
26
|
+
* for the field as it always was (`onChange={setCountry}` over a `string` state still
|
|
27
|
+
* type-checks), and `(code: string | null) => void` once `clearable` is set — the
|
|
28
|
+
* `null` of a clear cannot reach a handler that was never told about it.
|
|
29
|
+
*/
|
|
30
|
+
interface CountrySelectProps<Clearable extends boolean = false> extends Omit<ComponentPropsWithoutRef<"div">, "onChange" | "defaultValue"> {
|
|
31
|
+
/** The chosen country as an ISO 3166-1 alpha-2 code ("CH"), in either case;
|
|
32
|
+
* `null`, `undefined` or `""` when none is. A code outside the list still shows
|
|
33
|
+
* its name — the list restricts what can be PICKED, not what can be shown. */
|
|
34
|
+
value: string | null | undefined;
|
|
35
|
+
/** A country was picked: its alpha-2 code, always upper-case. With `clearable`,
|
|
36
|
+
* also `null`: the "×" (or Delete on the trigger) emptied the field. */
|
|
37
|
+
onChange: (code: Clearable extends true ? string | null : string) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Offer a clear "×" in place of the chevron while a country is chosen; it hands
|
|
40
|
+
* `onChange` `null` (the kit's later list: an optional country — a contact's
|
|
41
|
+
* nationality, a bank filter's "any country" — could be set but never unset again).
|
|
42
|
+
*
|
|
43
|
+
* The combobox family's clear, as {@link EntityCombobox} draws it: a pointer
|
|
44
|
+
* shortcut inside the trigger, out of the tab order so it is not a second stop
|
|
45
|
+
* between this field and the next, named by `clearLabel` (default: the
|
|
46
|
+
* `combobox.clear` label every picker in the family shares). The keyboard way is
|
|
47
|
+
* Delete or Backspace on the closed trigger. Not offered while `disabled` or locked
|
|
48
|
+
* (`commit` / `disabledReason`): a clear is a change like any other.
|
|
49
|
+
*/
|
|
50
|
+
clearable?: Clearable;
|
|
51
|
+
/** The clear "×"'s accessible name. Wins over `combobox.clear` from the
|
|
52
|
+
* {@link UiKitProvider}, as {@link EntityCombobox}'s `clearLabel` does. */
|
|
53
|
+
clearLabel?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The TRIGGER — the focusable `<button>` a reader meets — as a React 19 ref prop,
|
|
56
|
+
* the way {@link MoneyField} takes its input's (kastlan: react-hook-form's
|
|
57
|
+
* focus-on-error calls `focus()` on whatever `field.ref` was handed, and without a
|
|
58
|
+
* ref the first field in error on an address form was never focused). See
|
|
59
|
+
* `RhfCountrySelect`, which passes it.
|
|
60
|
+
*/
|
|
61
|
+
ref?: Ref<HTMLButtonElement>;
|
|
62
|
+
/**
|
|
63
|
+
* Offer only these codes (any case; duplicates and malformed entries dropped).
|
|
64
|
+
* Default: every ISO 3166-1 country, {@link COUNTRY_CODES}. Sorted by name either
|
|
65
|
+
* way — the order given is not kept, because the reader looks a country up by its
|
|
66
|
+
* name in their own language, and any other order is one they cannot predict.
|
|
67
|
+
*
|
|
68
|
+
* keksdose's bank picker offers the fifteen countries its banks are in
|
|
69
|
+
* (DE AT CH FR IT ES NL BE PT PL SE DK FI GB IE). A code that is not in ISO's list
|
|
70
|
+
* is allowed here on purpose: `XK` (Kosovo) is the app's decision, not the kit's.
|
|
71
|
+
*/
|
|
72
|
+
countries?: readonly string[];
|
|
73
|
+
/**
|
|
74
|
+
* A short group shown FIRST, in the order given, above the alphabetical rest —
|
|
75
|
+
* kastlan's CH, LI, DE, AT, FR, IT, the six countries most of its customers live
|
|
76
|
+
* in, out of a list of 249. Each code appears once: a preferred country is not
|
|
77
|
+
* repeated in the rest. The rest gets a heading (`labels.others`) so the boundary
|
|
78
|
+
* is visible. Search keeps the split: matching preferred countries stay on top.
|
|
79
|
+
* Codes not in `countries` are ignored — `countries` decides what can be picked.
|
|
80
|
+
*/
|
|
81
|
+
preferred?: readonly string[];
|
|
82
|
+
/**
|
|
83
|
+
* Draw each country's flag as a flag-icons span (`fi fi-ch`), in the rows and on
|
|
84
|
+
* the trigger. Default `false`, because the kit ships no flag stylesheet — the same
|
|
85
|
+
* convention as `LanguageMenu`'s `country` and `CurrencySelect`: the host imports
|
|
86
|
+
* `flag-icons/css/flag-icons.min.css`, and without it a flag is an empty box.
|
|
87
|
+
*/
|
|
88
|
+
flags?: boolean;
|
|
89
|
+
/** The floating label, as on every field. A string also names the trigger
|
|
90
|
+
* ("Country: Switzerland"). */
|
|
91
|
+
label?: ReactNode;
|
|
92
|
+
/** As {@link Select}'s `hint`: plain TEXT is a caption under the field, attached
|
|
93
|
+
* through `aria-describedby`; a {@link FieldHint} rides the label line (or the
|
|
94
|
+
* field's end, when there is no label). */
|
|
95
|
+
hint?: ReactNode;
|
|
96
|
+
/** As {@link Input}'s `error`: rendered under the field, on the trigger's
|
|
97
|
+
* `aria-describedby`, and implies `invalid`. */
|
|
98
|
+
error?: ReactNode;
|
|
99
|
+
/** Required and unanswered — {@link FIELD_INVALID}, as on every field. */
|
|
100
|
+
invalid?: boolean;
|
|
101
|
+
/** The empty trigger's text. Default: nothing when the field has a `label` or an
|
|
102
|
+
* `aria-labelledby` (the label already says what goes here), else `labels.country`. */
|
|
103
|
+
placeholder?: string;
|
|
104
|
+
/** As on the other pickers: no focus, no list, the settled look. */
|
|
105
|
+
disabled?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Why the country cannot be changed — {@link Button}'s `disabledReason`, for a field
|
|
108
|
+
* that saves itself. The trigger stays focusable and `aria-disabled`, the list does
|
|
109
|
+
* not open and `onChange` never fires; the reason is in the kit {@link Tooltip} and
|
|
110
|
+
* on the trigger's `aria-describedby`. Wins over `disabled`.
|
|
111
|
+
*/
|
|
112
|
+
disabledReason?: ReactNode;
|
|
113
|
+
/**
|
|
114
|
+
* This select SAVES on change (a settings row with no Save button). Under a locked
|
|
115
|
+
* {@link WriteLockProvider} it is locked the `disabledReason` way, with the lock's
|
|
116
|
+
* reason. A country field inside a form that has its own Save stays editable under
|
|
117
|
+
* the lock — leave this off there, and put `commit` on the Save.
|
|
118
|
+
*/
|
|
119
|
+
commit?: boolean;
|
|
120
|
+
/** The language the names are in. Default: the `<UiKitProvider locale>`, else
|
|
121
|
+
* English (see {@link countryName}). */
|
|
122
|
+
locale?: string;
|
|
123
|
+
/** Per-instance overrides of {@link CountrySelectLabels}, over the provider's
|
|
124
|
+
* `countrySelect` namespace, over English. */
|
|
125
|
+
labels?: Partial<CountrySelectLabels>;
|
|
126
|
+
/**
|
|
127
|
+
* Name the trigger by an element of the caller's — a form's own label above the
|
|
128
|
+
* field, which is how `RhfCountrySelect` names it. The trigger is then named by
|
|
129
|
+
* that element AND the chosen country (two references, as {@link DatePicker}'s
|
|
130
|
+
* trigger is named, so "Country Switzerland" needs no punctuation to translate),
|
|
131
|
+
* instead of the composed "label: country". An `aria-label` still wins. Before
|
|
132
|
+
* 0.23 this landed on the wrapper `<div>`, which has no role to be named.
|
|
133
|
+
*/
|
|
134
|
+
"aria-labelledby"?: string;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* A country picker: the value is the ISO 3166-1 alpha-2 code ("CH"), the names are the
|
|
138
|
+
* runtime's own (`Intl.DisplayNames`) in the provider's locale, sorted by that
|
|
139
|
+
* locale's collation. Requested by kastlan (customer and handover addresses, with its
|
|
140
|
+
* six neighbours on top) and keksdose (the bank picker's fifteen countries) — both
|
|
141
|
+
* apps carried a hand-written list of names in two languages.
|
|
142
|
+
*
|
|
143
|
+
* ## Built on the kit's searchable combobox
|
|
144
|
+
*
|
|
145
|
+
* 249 countries is not a menu, it is a lookup, and the list has to work on a phone. So
|
|
146
|
+
* this is the entity pickers' machinery — `useComboboxCore` and `ComboboxPanel`, which
|
|
147
|
+
* {@link EntityCombobox} is also made of — not a native `<select>`: the anchored panel
|
|
148
|
+
* with a search box on a pointer device, the full-screen sheet with its own search box
|
|
149
|
+
* on a phone (live #200), the same keyboard (APG listbox), the same announcements, the
|
|
150
|
+
* same group headings. Only the trigger is this component's own, because the trigger
|
|
151
|
+
* is where the write lock lives: `EntityCombobox`'s is a native `disabled` button,
|
|
152
|
+
* and a locked field has to stay focusable to say why.
|
|
153
|
+
*
|
|
154
|
+
* The search is this component's own too: it folds accents and case ("osterreich"
|
|
155
|
+
* finds Österreich), matches the code ("ch" ranks Switzerland first), and ranks
|
|
156
|
+
* name-starts above name-contains. The panel is handed those rows in place of the
|
|
157
|
+
* core's plain substring filter.
|
|
158
|
+
*
|
|
159
|
+
* Field anatomy as on every field: floating `label`, `hint`, `error`, `invalid`; the
|
|
160
|
+
* trigger is named "label: value" as the other trigger pickers are.
|
|
161
|
+
*
|
|
162
|
+
* Since 0.23 it takes a `ref` to the trigger (react-hook-form's focus-on-error, kastlan)
|
|
163
|
+
* and `clearable`, which hands `onChange` `null`.
|
|
164
|
+
*/
|
|
165
|
+
declare function CountrySelect<Clearable extends boolean = false>({ value, onChange, countries, preferred, flags, label, hint, error, invalid, placeholder, disabled, disabledReason: ownReason, commit, clearable, clearLabel, locale: localeProp, labels: labelsProp, className, id, ref, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, ...rest }: CountrySelectProps<Clearable>): react.JSX.Element;
|
|
166
|
+
|
|
167
|
+
export { CountrySelect, type CountrySelectLabels, type CountrySelectProps, DEFAULT_COUNTRY_SELECT_LABELS };
|