@eifi1/ui-kit 0.17.0 → 0.19.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.
Files changed (210) hide show
  1. package/README.md +83 -4
  2. package/dist/chart.d.ts +13 -1
  3. package/dist/components/account-chips.d.ts +153 -0
  4. package/dist/components/account-chips.js +135 -0
  5. package/dist/components/account-chips.js.map +1 -0
  6. package/dist/components/account-settings-labels.d.ts +10 -2
  7. package/dist/components/account-settings-labels.js +3 -0
  8. package/dist/components/account-settings-labels.js.map +1 -1
  9. package/dist/components/amount-input.d.ts +13 -1
  10. package/dist/components/button-group.d.ts +13 -1
  11. package/dist/components/calculator.d.ts +13 -1
  12. package/dist/components/chip.d.ts +25 -1
  13. package/dist/components/chip.js +143 -6
  14. package/dist/components/chip.js.map +1 -1
  15. package/dist/components/confirm-dialog.d.ts +42 -1
  16. package/dist/components/confirm-dialog.js +36 -5
  17. package/dist/components/confirm-dialog.js.map +1 -1
  18. package/dist/components/copy-button.d.ts +13 -1
  19. package/dist/components/danger-confirm.d.ts +55 -4
  20. package/dist/components/danger-confirm.js +67 -30
  21. package/dist/components/danger-confirm.js.map +1 -1
  22. package/dist/components/data-table-cells.d.ts +1 -1
  23. package/dist/components/data-table-filter-popover.d.ts +1 -1
  24. package/dist/components/data-table-filters.d.ts +1 -1
  25. package/dist/components/data-table.d.ts +1 -1
  26. package/dist/components/data-table.js +2 -0
  27. package/dist/components/data-table.js.map +1 -1
  28. package/dist/components/facing-pair.d.ts +13 -1
  29. package/dist/components/file-button.d.ts +13 -1
  30. package/dist/components/file-dropzone.d.ts +13 -1
  31. package/dist/components/floating-panel.d.ts +4 -1
  32. package/dist/components/floating-panel.js +15 -1
  33. package/dist/components/floating-panel.js.map +1 -1
  34. package/dist/components/form-actions.d.ts +13 -1
  35. package/dist/components/form-actions.js +3 -0
  36. package/dist/components/form-actions.js.map +1 -1
  37. package/dist/components/legal.d.ts +84 -0
  38. package/dist/components/legal.js +55 -0
  39. package/dist/components/legal.js.map +1 -0
  40. package/dist/components/money-field.d.ts +13 -1
  41. package/dist/components/number-field.d.ts +14 -2
  42. package/dist/components/number-field.js +23 -22
  43. package/dist/components/number-field.js.map +1 -1
  44. package/dist/components/number-input.d.ts +13 -1
  45. package/dist/components/numpad-sheet.d.ts +13 -1
  46. package/dist/components/passkeys-setting.d.ts +27 -1
  47. package/dist/components/passkeys-setting.js +31 -5
  48. package/dist/components/passkeys-setting.js.map +1 -1
  49. package/dist/components/progress-bar.d.ts +15 -1
  50. package/dist/components/progress-bar.js +8 -1
  51. package/dist/components/progress-bar.js.map +1 -1
  52. package/dist/components/reauth-dialog.d.ts +90 -0
  53. package/dist/components/reauth-dialog.js +158 -0
  54. package/dist/components/reauth-dialog.js.map +1 -0
  55. package/dist/components/series-chart.d.ts +13 -1
  56. package/dist/components/server-wake.d.ts +60 -0
  57. package/dist/components/server-wake.js +79 -0
  58. package/dist/components/server-wake.js.map +1 -0
  59. package/dist/components/settings-fields.d.ts +13 -1
  60. package/dist/components/share-card.d.ts +64 -0
  61. package/dist/components/share-card.js +350 -0
  62. package/dist/components/share-card.js.map +1 -0
  63. package/dist/components/status-dot.d.ts +2 -0
  64. package/dist/components/text-link.d.ts +13 -1
  65. package/dist/components/time-input.d.ts +13 -1
  66. package/dist/components/toast.d.ts +10 -1
  67. package/dist/components/toast.js +42 -2
  68. package/dist/components/toast.js.map +1 -1
  69. package/dist/components/toggle-group.js +5 -2
  70. package/dist/components/toggle-group.js.map +1 -1
  71. package/dist/components/translation-review-editor.d.ts +56 -0
  72. package/dist/components/translation-review-editor.js +159 -0
  73. package/dist/components/translation-review-editor.js.map +1 -0
  74. package/dist/components/translation-review-labels.d.ts +92 -0
  75. package/dist/components/translation-review-labels.js +67 -0
  76. package/dist/components/translation-review-labels.js.map +1 -0
  77. package/dist/components/translation-review-parts.d.ts +97 -0
  78. package/dist/components/translation-review-parts.js +163 -0
  79. package/dist/components/translation-review-parts.js.map +1 -0
  80. package/dist/components/translation-review.d.ts +94 -0
  81. package/dist/components/translation-review.js +344 -0
  82. package/dist/components/translation-review.js.map +1 -0
  83. package/dist/components/ui.d.ts +13 -1
  84. package/dist/components/ui.js +43 -28
  85. package/dist/components/ui.js.map +1 -1
  86. package/dist/components/use-table-state.d.ts +1 -1
  87. package/dist/components/user-avatar.d.ts +2 -0
  88. package/dist/components/write-lock.d.ts +68 -0
  89. package/dist/components/write-lock.js +33 -0
  90. package/dist/components/write-lock.js.map +1 -0
  91. package/dist/{data-table-CpkWUJBY.d.ts → data-table-3mi2R3so.d.ts} +19 -1
  92. package/dist/data-table.d.ts +1 -1
  93. package/dist/hooks/use-file-drop.d.ts +13 -1
  94. package/dist/hooks/use-overlay-history.js +16 -0
  95. package/dist/hooks/use-overlay-history.js.map +1 -1
  96. package/dist/i18n/defaults.d.ts +13 -1
  97. package/dist/i18n/defaults.js +15 -1
  98. package/dist/i18n/defaults.js.map +1 -1
  99. package/dist/i18n/german.d.ts +81 -0
  100. package/dist/i18n/{locales/de.js → german.js} +110 -9
  101. package/dist/i18n/german.js.map +1 -0
  102. package/dist/i18n/kit-labels.d.ts +13 -1
  103. package/dist/i18n/kit-labels.js +5 -0
  104. package/dist/i18n/kit-labels.js.map +1 -1
  105. package/dist/i18n/languages.d.ts +243 -0
  106. package/dist/i18n/languages.js +70 -0
  107. package/dist/i18n/languages.js.map +1 -0
  108. package/dist/i18n/locales/de-CH.d.ts +26 -8
  109. package/dist/i18n/locales/de-CH.js +7 -3
  110. package/dist/i18n/locales/de-CH.js.map +1 -1
  111. package/dist/i18n/locales/en.d.ts +13 -1
  112. package/dist/i18n/locales/en.js +8 -0
  113. package/dist/i18n/locales/en.js.map +1 -1
  114. package/dist/i18n/locales/es.d.ts +13 -1
  115. package/dist/i18n/locales/es.js +127 -27
  116. package/dist/i18n/locales/es.js.map +1 -1
  117. package/dist/i18n/locales/fr.d.ts +13 -1
  118. package/dist/i18n/locales/fr.js +101 -1
  119. package/dist/i18n/locales/fr.js.map +1 -1
  120. package/dist/i18n/locales/hu.d.ts +13 -1
  121. package/dist/i18n/locales/hu.js +105 -1
  122. package/dist/i18n/locales/hu.js.map +1 -1
  123. package/dist/i18n/locales/it.d.ts +13 -1
  124. package/dist/i18n/locales/it.js +134 -34
  125. package/dist/i18n/locales/it.js.map +1 -1
  126. package/dist/i18n/locales/zh.d.ts +13 -1
  127. package/dist/i18n/locales/zh.js +101 -1
  128. package/dist/i18n/locales/zh.js.map +1 -1
  129. package/dist/i18n/review.d.ts +102 -0
  130. package/dist/i18n/review.js +158 -0
  131. package/dist/i18n/review.js.map +1 -0
  132. package/dist/i18n/swiss.d.ts +3 -3
  133. package/dist/i18n/swiss.js.map +1 -1
  134. package/dist/index.d.ts +17 -4
  135. package/dist/index.js +97 -1
  136. package/dist/index.js.map +1 -1
  137. package/dist/{kit-labels-C4uB_zmg.d.ts → kit-labels-D1J2_jyD.d.ts} +236 -9
  138. package/dist/lib/floating-stack.d.ts +31 -0
  139. package/dist/lib/floating-stack.js +42 -0
  140. package/dist/lib/floating-stack.js.map +1 -0
  141. package/dist/lib/server-wake.d.ts +146 -0
  142. package/dist/lib/server-wake.js +122 -0
  143. package/dist/lib/server-wake.js.map +1 -0
  144. package/dist/lib/translation-review.d.ts +232 -0
  145. package/dist/lib/translation-review.js +194 -0
  146. package/dist/lib/translation-review.js.map +1 -0
  147. package/dist/rhf/fields.d.ts +13 -1
  148. package/dist/rhf/form.d.ts +13 -1
  149. package/dist/rhf.d.ts +13 -1
  150. package/dist/search/global-search.d.ts +3 -1
  151. package/dist/search/global-search.js.map +1 -1
  152. package/dist/shell/app-shell.d.ts +13 -1
  153. package/dist/shell/top-bar-brand.d.ts +13 -1
  154. package/dist/shell/topbar-action-menu.d.ts +2 -0
  155. package/dist/shell.d.ts +13 -1
  156. package/dist/wizard/stepper-nav.d.ts +13 -1
  157. package/dist/wizard.d.ts +13 -1
  158. package/package.json +1 -1
  159. package/src/components/account-chips.tsx +302 -0
  160. package/src/components/account-settings-labels.ts +16 -2
  161. package/src/components/chip.tsx +239 -6
  162. package/src/components/confirm-dialog.tsx +82 -7
  163. package/src/components/danger-confirm.tsx +118 -33
  164. package/src/components/data-table.tsx +20 -0
  165. package/src/components/floating-panel.tsx +19 -1
  166. package/src/components/form-actions.tsx +22 -3
  167. package/src/components/legal.tsx +159 -0
  168. package/src/components/number-field.tsx +32 -26
  169. package/src/components/passkeys-setting.tsx +68 -7
  170. package/src/components/progress-bar.tsx +21 -1
  171. package/src/components/reauth-dialog.tsx +273 -0
  172. package/src/components/server-wake.tsx +156 -0
  173. package/src/components/share-card.tsx +615 -0
  174. package/src/components/toast.tsx +72 -2
  175. package/src/components/toggle-group.tsx +5 -2
  176. package/src/components/translation-review-editor.tsx +261 -0
  177. package/src/components/translation-review-labels.ts +152 -0
  178. package/src/components/translation-review-parts.tsx +269 -0
  179. package/src/components/translation-review.tsx +502 -0
  180. package/src/components/ui.tsx +89 -32
  181. package/src/components/write-lock.tsx +96 -0
  182. package/src/hooks/use-overlay-history.ts +51 -0
  183. package/src/i18n/defaults.ts +14 -0
  184. package/src/i18n/{locales/de.ts → german.ts} +120 -13
  185. package/src/i18n/kit-labels.tsx +29 -2
  186. package/src/i18n/languages.ts +285 -0
  187. package/src/i18n/locales/de-CH.ts +17 -8
  188. package/src/i18n/locales/en.ts +8 -0
  189. package/src/i18n/locales/es.ts +128 -26
  190. package/src/i18n/locales/fr.ts +103 -0
  191. package/src/i18n/locales/hu.ts +107 -0
  192. package/src/i18n/locales/it.ts +135 -33
  193. package/src/i18n/locales/zh.ts +102 -0
  194. package/src/i18n/review.ts +193 -0
  195. package/src/i18n/swiss.ts +3 -3
  196. package/src/index.ts +86 -1
  197. package/src/lib/floating-stack.ts +66 -0
  198. package/src/lib/server-wake.ts +291 -0
  199. package/src/lib/translation-review.ts +465 -0
  200. package/src/search/global-search.tsx +3 -1
  201. package/dist/i18n/locales/de-CH-informal.d.ts +0 -61
  202. package/dist/i18n/locales/de-CH-informal.js +0 -8
  203. package/dist/i18n/locales/de-CH-informal.js.map +0 -1
  204. package/dist/i18n/locales/de-informal.d.ts +0 -82
  205. package/dist/i18n/locales/de-informal.js +0 -37
  206. package/dist/i18n/locales/de-informal.js.map +0 -1
  207. package/dist/i18n/locales/de.d.ts +0 -66
  208. package/dist/i18n/locales/de.js.map +0 -1
  209. package/src/i18n/locales/de-CH-informal.ts +0 -10
  210. package/src/i18n/locales/de-informal.ts +0 -61
@@ -1,6 +1,55 @@
1
1
  import * as react from 'react';
2
- import { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ import { Ref, ComponentPropsWithoutRef, ReactNode } from 'react';
3
3
 
4
+ /**
5
+ * How typed text is compared with the text it has to repeat — shared by
6
+ * `DangerConfirm`'s `phraseMatch` and `useConfirm`'s `typedMatch`.
7
+ *
8
+ * - `"trim"` ignores spaces around the typed text — a phone keyboard's autocomplete
9
+ * likes to add one. Case counts: "delete" is not "DELETE".
10
+ * - `"exact"` compares character for character, spaces included.
11
+ * - `"caseless"` trims AND ignores case: for an e-mail address, whose domain is
12
+ * case-insensitive and whose local part every real mail server treats so. Both apps'
13
+ * "type the account's address" rules (keksdose's `TYPE_EMAIL`, Kurvenschmiede's
14
+ * `TypedConfirm`) compared `trim().toLowerCase()`, and a capital the user's keyboard
15
+ * put at the start must not make the right row look like the wrong one.
16
+ */
17
+ type TypedMatch = "trim" | "exact" | "caseless";
18
+ /** Whether `typed` repeats `target` under `mode` — see {@link TypedMatch}. Exported for
19
+ * a caller that draws its own field (a dialog that also picks a recipient) and wants
20
+ * the same rule as the kit's. */
21
+ declare function typedMatches(typed: string, target: string, mode?: TypedMatch): boolean;
22
+ /**
23
+ * Busy-while-a-promise-runs, for an action that MAY return one: `run(result, onResolved)`
24
+ * marks the caller pending until `result` settles, calls `onResolved` if it fulfilled,
25
+ * and only clears the flag if it rejected — the caller shows why and the user retries.
26
+ * Nothing runs after unmount. Shared by `DangerConfirm` and `ReauthDialog`, whose
27
+ * contract is the same: resolve = done, reject = stay put.
28
+ *
29
+ * @internal
30
+ */
31
+ declare function usePromisePending(): {
32
+ pending: boolean;
33
+ run: (result: unknown, onResolved: () => void) => boolean;
34
+ };
35
+ /**
36
+ * The "prove it is you" field: a password input the browser's password manager fills
37
+ * with the CURRENT password (`autocomplete="current-password"`), never offers to
38
+ * generate a new one for. `readOnly` rather than `disabled` while busy: disabling the
39
+ * field that has focus (Enter was pressed in it) drops focus to <body>.
40
+ *
41
+ * @internal DangerConfirm's field. ReauthDialog spells it out with the field's own
42
+ * `error`, which keeps the `<input>` mounted while its message comes and goes.
43
+ */
44
+ declare function CurrentPasswordInput({ inputRef, label, value, onValueChange, busy, invalid, describedBy, }: {
45
+ inputRef?: Ref<HTMLInputElement>;
46
+ label: string;
47
+ value: string;
48
+ onValueChange: (value: string) => void;
49
+ busy: boolean;
50
+ invalid?: boolean;
51
+ describedBy?: string;
52
+ }): react.JSX.Element;
4
53
  /**
5
54
  * Every string the tile renders — the `dangerConfirm` namespace of
6
55
  * `<UiKitProvider labels>`, overridable per instance through `labels`. The arm and
@@ -59,9 +108,11 @@ interface DangerConfirmProps extends Omit<ComponentPropsWithoutRef<"div">, "onCh
59
108
  * How the typed text is compared with `phrase`. `"trim"` (default) ignores spaces
60
109
  * around it — a phone keyboard's autocomplete likes to add one. `"exact"` compares
61
110
  * character for character, spaces included, for an app whose contract is "type
62
- * exactly this".
111
+ * exactly this". `"caseless"` (0.18) also ignores case — for a phrase that is an
112
+ * e-mail address (keksdose's "type the user's address" admin rule). See
113
+ * {@link TypedMatch}.
63
114
  */
64
- phraseMatch?: "trim" | "exact";
115
+ phraseMatch?: TypedMatch;
65
116
  /** The warning above the fields. Defaults to `labels.prompt`. */
66
117
  prompt?: ReactNode;
67
118
  /** `"danger"` (default) for what cannot be undone; `"warning"` for what can, at a
@@ -112,4 +163,4 @@ interface DangerConfirmProps extends Omit<ComponentPropsWithoutRef<"div">, "onCh
112
163
  */
113
164
  declare function DangerConfirm({ onConfirm, requirePassword, phrase, phraseMatch, prompt, tone, armLabel, confirmLabel, busy: busyProp, disabled, lockedReason, armed: armedProp, onArmedChange, labels: labelsProp, className, ...rest }: DangerConfirmProps): react.JSX.Element;
114
165
 
115
- export { DEFAULT_DANGER_CONFIRM_LABELS, DangerConfirm, type DangerConfirmLabels, type DangerConfirmProps };
166
+ export { CurrentPasswordInput, DEFAULT_DANGER_CONFIRM_LABELS, DangerConfirm, type DangerConfirmLabels, type DangerConfirmProps, type TypedMatch, typedMatches, usePromisePending };
@@ -1,10 +1,65 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useId, useRef, useState } from "react";
3
+ import { useCallback, useEffect, useId, useRef, useState } from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { useKitLabels } from "../i18n/kit-labels.js";
6
6
  import { Button, Input, Label, Spinner } from "./ui.js";
7
7
  import { Tooltip } from "./tooltip.js";
8
+ function typedMatches(typed, target, mode = "trim") {
9
+ if (mode === "exact") return typed === target;
10
+ if (mode === "trim") return typed.trim() === target;
11
+ return typed.trim().toLowerCase() === target.trim().toLowerCase();
12
+ }
13
+ function usePromisePending() {
14
+ const [pending, setPending] = useState(false);
15
+ const mounted = useRef(true);
16
+ useEffect(() => {
17
+ mounted.current = true;
18
+ return () => {
19
+ mounted.current = false;
20
+ };
21
+ }, []);
22
+ const run = useCallback((result, onResolved) => {
23
+ if (!result || typeof result.then !== "function") return false;
24
+ setPending(true);
25
+ result.then(
26
+ () => {
27
+ if (!mounted.current) return;
28
+ setPending(false);
29
+ onResolved();
30
+ },
31
+ () => {
32
+ if (mounted.current) setPending(false);
33
+ }
34
+ );
35
+ return true;
36
+ }, []);
37
+ return { pending, run };
38
+ }
39
+ function CurrentPasswordInput({
40
+ inputRef,
41
+ label,
42
+ value,
43
+ onValueChange,
44
+ busy,
45
+ invalid,
46
+ describedBy
47
+ }) {
48
+ return /* @__PURE__ */ jsx(
49
+ Input,
50
+ {
51
+ ref: inputRef,
52
+ type: "password",
53
+ autoComplete: "current-password",
54
+ label,
55
+ value,
56
+ readOnly: busy,
57
+ invalid,
58
+ "aria-describedby": describedBy,
59
+ onChange: (e) => onValueChange(e.target.value)
60
+ }
61
+ );
62
+ }
8
63
  const DEFAULT_DANGER_CONFIRM_LABELS = {
9
64
  arm: "Delete\u2026",
10
65
  confirm: "Delete",
@@ -36,7 +91,7 @@ function DangerConfirm({
36
91
  const armed = armedProp ?? armedState;
37
92
  const [password, setPassword] = useState("");
38
93
  const [typed, setTyped] = useState("");
39
- const [pending, setPending] = useState(false);
94
+ const { pending, run } = usePromisePending();
40
95
  const busy = Boolean(busyProp) || pending;
41
96
  const promptId = useId();
42
97
  const reasonId = useId();
@@ -45,13 +100,6 @@ function DangerConfirm({
45
100
  const cancelId = useId();
46
101
  const firstFieldRef = useRef(null);
47
102
  const moveFocus = useRef(false);
48
- const mounted = useRef(true);
49
- useEffect(() => {
50
- mounted.current = true;
51
- return () => {
52
- mounted.current = false;
53
- };
54
- }, []);
55
103
  const [wasArmed, setWasArmed] = useState(armed);
56
104
  if (wasArmed !== armed) {
57
105
  setWasArmed(armed);
@@ -81,24 +129,12 @@ function DangerConfirm({
81
129
  };
82
130
  const locked = lockedReason !== void 0 && lockedReason !== null && lockedReason !== false && lockedReason !== "";
83
131
  const passwordOk = !requirePassword || password !== "";
84
- const phraseOk = phrase === void 0 || (phraseMatch === "exact" ? typed : typed.trim()) === phrase;
132
+ const phraseOk = phrase === void 0 || typedMatches(typed, phrase, phraseMatch);
85
133
  const canConfirm = passwordOk && phraseOk && !busy;
86
134
  const submit = (e) => {
87
135
  e.preventDefault();
88
136
  if (!canConfirm) return;
89
- const result = onConfirm(requirePassword ? password : void 0);
90
- if (!result || typeof result.then !== "function") return;
91
- setPending(true);
92
- result.then(
93
- () => {
94
- if (!mounted.current) return;
95
- setPending(false);
96
- setArmed(false);
97
- },
98
- () => {
99
- if (mounted.current) setPending(false);
100
- }
101
- );
137
+ run(onConfirm(requirePassword ? password : void 0), () => setArmed(false));
102
138
  };
103
139
  const phrasePlaceholder = phrase === void 0 || labels.phrasePlaceholder === void 0 ? void 0 : typeof labels.phrasePlaceholder === "function" ? labels.phrasePlaceholder(phrase) : labels.phrasePlaceholder;
104
140
  const phraseLabel = phrase === void 0 ? "" : typeof labels.phrase === "function" ? labels.phrase(phrase) : labels.phrase;
@@ -162,15 +198,13 @@ function DangerConfirm({
162
198
  ] })
163
199
  ) : /* @__PURE__ */ jsx(Input, { label: phraseLabel, ...phraseFieldProps })),
164
200
  requirePassword && /* @__PURE__ */ jsx(
165
- Input,
201
+ CurrentPasswordInput,
166
202
  {
167
- ref: phrase === void 0 ? firstFieldRef : void 0,
168
- type: "password",
169
- autoComplete: "current-password",
203
+ inputRef: phrase === void 0 ? firstFieldRef : void 0,
170
204
  label: labels.password,
171
205
  value: password,
172
- readOnly: busy,
173
- onChange: (e) => setPassword(e.target.value)
206
+ busy,
207
+ onValueChange: setPassword
174
208
  }
175
209
  ),
176
210
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
@@ -194,7 +228,10 @@ function DangerConfirm({
194
228
  ) });
195
229
  }
196
230
  export {
231
+ CurrentPasswordInput,
197
232
  DEFAULT_DANGER_CONFIRM_LABELS,
198
- DangerConfirm
233
+ DangerConfirm,
234
+ typedMatches,
235
+ usePromisePending
199
236
  };
200
237
  //# sourceMappingURL=danger-confirm.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/danger-confirm.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from \"react\";\nimport type { ChangeEvent, ComponentPropsWithoutRef, FormEvent, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { Button, Input, Label, Spinner } from \"./ui\";\nimport { Tooltip } from \"./tooltip\";\n\n/**\n * Every string the tile renders — the `dangerConfirm` namespace of\n * `<UiKitProvider labels>`, overridable per instance through `labels`. The arm and\n * confirm buttons are usually worded per action (\"Delete budget\"), which is what the\n * `armLabel` / `confirmLabel` props are for; these are the fallbacks.\n */\nexport interface DangerConfirmLabels {\n /** The button that arms the tile. */\n arm: string;\n /** The button that runs the action once the guards are satisfied. */\n confirm: string;\n cancel: string;\n /** The warning above the fields, when no `prompt` is given. */\n prompt: string;\n /** Label of the password field (`requirePassword`). */\n password: string;\n /** Label of the type-to-confirm field. A FUNCTION of the phrase by default, like\n * every message that carries a value: where the phrase sits in the sentence moves\n * with the language. A plain STRING is taken as the finished label — for an app\n * whose catalogue already words it (\"Type DELETE to confirm\") and would otherwise\n * wrap it as `() => label`. */\n phrase: string | ((phrase: string) => string);\n /** Placeholder of the type-to-confirm field — a string, or a function of the\n * phrase (`(p) => p` echoes it). Optional and unset by default: the field keeps its\n * floating label, as before. Given, the label moves ABOVE the field (a floating\n * label occupies the empty field, which is exactly where a placeholder shows) and\n * the placeholder fills the field. An empty string means none. */\n phrasePlaceholder?: string | ((phrase: string) => string);\n}\n\n/** `satisfies` rather than a type annotation, so `DEFAULT_DANGER_CONFIRM_LABELS.phrase`\n * stays callable for a caller that composes its own label from it. */\nexport const DEFAULT_DANGER_CONFIRM_LABELS = {\n arm: \"Delete…\",\n confirm: \"Delete\",\n cancel: \"Cancel\",\n prompt: \"This cannot be undone.\",\n password: \"Password\",\n phrase: (phrase: string) => `Type “${phrase}” to confirm`,\n} satisfies DangerConfirmLabels;\n\nexport interface DangerConfirmProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /**\n * Runs the action, with the entered password when `requirePassword` is on.\n *\n * Return a promise and the tile manages itself: busy until it settles, disarmed\n * (fields wiped) when it resolves, still armed when it rejects — so the user can\n * correct a wrong password and retry. The rejection is not swallowed for you to\n * miss: handle it (and show why) in the caller, as with any mutation.\n */\n onConfirm: (password?: string) => void | Promise<unknown>;\n /** Show a password field; confirm stays disabled until it is filled. */\n requirePassword?: boolean;\n /** Show a \"type <phrase> to confirm\" field; confirm stays disabled until the field\n * matches (case-sensitive; surrounding spaces ignored unless `phraseMatch=\"exact\"`). */\n phrase?: string;\n /**\n * How the typed text is compared with `phrase`. `\"trim\"` (default) ignores spaces\n * around it — a phone keyboard's autocomplete likes to add one. `\"exact\"` compares\n * character for character, spaces included, for an app whose contract is \"type\n * exactly this\".\n */\n phraseMatch?: \"trim\" | \"exact\";\n /** The warning above the fields. Defaults to `labels.prompt`. */\n prompt?: ReactNode;\n /** `\"danger\"` (default) for what cannot be undone; `\"warning\"` for what can, at a\n * cost (loading demo data over your own). Colours the prompt and the confirm. */\n tone?: \"danger\" | \"warning\";\n /** Visible text of the arm button; defaults to `labels.arm`. */\n armLabel?: ReactNode;\n /** Visible text of the confirm button; defaults to `labels.confirm`. */\n confirmLabel?: ReactNode;\n /** The action is running: confirm shows a spinner and nothing can be pressed. For a\n * caller that tracks the mutation itself (a `useMutation`'s `isPending`); a\n * promise returned from `onConfirm` does the same on its own. */\n busy?: boolean;\n /** The arm button is disabled. */\n disabled?: boolean;\n /**\n * Why the action is not available — a read-only demo, a write lock, a missing\n * permission. Disables the arm button like `disabled`, and SAYS so: the sentence is\n * shown under the button and attached to it with `aria-describedby`, and the button\n * stays focusable (`aria-disabled`) so a keyboard user can land on it and hear why.\n *\n * On the ARM button rather than the confirm: arming asks for a password, and asking\n * for a password for a write that can never land is the worse of the two.\n */\n lockedReason?: ReactNode;\n /** Controlled armed state. The parent can then collapse the tile from a mutation's\n * own `onSuccess` without returning a promise. */\n armed?: boolean;\n /** Called with the next armed state — on arm, cancel, and a resolved `onConfirm`. */\n onArmedChange?: (armed: boolean) => void;\n /** User-facing strings; see {@link DangerConfirmLabels}. */\n labels?: Partial<DangerConfirmLabels>;\n}\n\n/**\n * An \"arm → confirm\" tile for destructive actions: one button, which expands into a\n * warning, an optional password field, an optional type-to-confirm field and a\n * confirm that stays disabled until every guard is satisfied.\n *\n * Keksdose hand-rolled it three times (load demo data, wipe everything, reset a\n * budget) and then as `shared/components/danger-confirm.tsx`; the only app-specific\n * part was its write-lock hook, which is `lockedReason` here.\n *\n * The fields are a `<form>`, so Enter confirms once the guards allow it. They are\n * wiped every time the tile disarms — a password must not sit in a collapsed tile.\n * Arming moves focus to the first field (or Cancel, when there is none — never to the\n * destructive button itself), and disarming moves it back to the arm button, so the\n * keyboard user is never left on an element that just vanished.\n */\nexport function DangerConfirm({\n onConfirm,\n requirePassword,\n phrase,\n phraseMatch = \"trim\",\n prompt,\n tone = \"danger\",\n armLabel,\n confirmLabel,\n busy: busyProp,\n disabled,\n lockedReason,\n armed: armedProp,\n onArmedChange,\n labels: labelsProp,\n className,\n ...rest\n}: DangerConfirmProps) {\n const labels = useKitLabels(\"dangerConfirm\", DEFAULT_DANGER_CONFIRM_LABELS, labelsProp);\n const [armedState, setArmedState] = useState(false);\n const armed = armedProp ?? armedState;\n const [password, setPassword] = useState(\"\");\n const [typed, setTyped] = useState(\"\");\n const [pending, setPending] = useState(false);\n const busy = Boolean(busyProp) || pending;\n // `readOnly` rather than `disabled` on the fields while busy: disabling the field\n // that has focus (Enter was pressed in it) drops focus to <body>.\n const promptId = useId();\n const reasonId = useId();\n const phraseFieldId = useId();\n\n // The kit's Button takes no ref, so the two buttons focus is moved to are found by id.\n const armId = useId();\n const cancelId = useId();\n const firstFieldRef = useRef<HTMLInputElement>(null);\n // Focus moves only after a transition — never on mount, so a tile that renders armed\n // (controlled) does not take the page's focus merely by existing.\n const moveFocus = useRef(false);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n // A disarm from anywhere (cancel, a resolved confirm, the parent) wipes the fields.\n // During render, like NumberField's draft, so no frame shows a collapsed tile that\n // still holds a password.\n const [wasArmed, setWasArmed] = useState(armed);\n if (wasArmed !== armed) {\n setWasArmed(armed);\n if (!armed) {\n setPassword(\"\");\n setTyped(\"\");\n }\n }\n\n const prevArmed = useRef(armed);\n useEffect(() => {\n const changed = prevArmed.current !== armed;\n prevArmed.current = armed;\n const byUser = moveFocus.current;\n moveFocus.current = false;\n if (!changed) return;\n if (armed) {\n if (byUser) (firstFieldRef.current ?? document.getElementById(cancelId))?.focus();\n return;\n }\n // A controlled parent collapsing the tile from its own `onSuccess` did not go\n // through `setArmed`; if focus went down with the form, bring it back too.\n const lost = document.activeElement === null || document.activeElement === document.body;\n if (byUser || lost) document.getElementById(armId)?.focus();\n }, [armed, armId, cancelId]);\n\n const setArmed = (next: boolean) => {\n moveFocus.current = true;\n if (armedProp === undefined) setArmedState(next);\n onArmedChange?.(next);\n };\n\n const locked = lockedReason !== undefined && lockedReason !== null && lockedReason !== false && lockedReason !== \"\";\n const passwordOk = !requirePassword || password !== \"\";\n const phraseOk = phrase === undefined || (phraseMatch === \"exact\" ? typed : typed.trim()) === phrase;\n const canConfirm = passwordOk && phraseOk && !busy;\n\n const submit = (e: FormEvent) => {\n e.preventDefault();\n if (!canConfirm) return;\n const result = onConfirm(requirePassword ? password : undefined);\n if (!result || typeof (result as Promise<unknown>).then !== \"function\") return;\n setPending(true);\n (result as Promise<unknown>).then(\n () => {\n if (!mounted.current) return;\n setPending(false);\n setArmed(false);\n },\n () => {\n // Stays armed, fields kept: the caller shows why, the user retries.\n if (mounted.current) setPending(false);\n },\n );\n };\n\n const phrasePlaceholder =\n phrase === undefined || labels.phrasePlaceholder === undefined\n ? undefined\n : typeof labels.phrasePlaceholder === \"function\"\n ? labels.phrasePlaceholder(phrase)\n : labels.phrasePlaceholder;\n\n const phraseLabel =\n phrase === undefined ? \"\" : typeof labels.phrase === \"function\" ? labels.phrase(phrase) : labels.phrase;\n const phraseFieldProps = {\n ref: firstFieldRef,\n value: typed,\n autoComplete: \"off\",\n autoCapitalize: \"off\",\n spellCheck: false,\n readOnly: busy,\n onChange: (e: ChangeEvent<HTMLInputElement>) => setTyped(e.target.value),\n };\n\n const toneText = tone === \"warning\" ? \"text-[var(--warning)]\" : \"text-[var(--danger)]\";\n\n if (!armed) {\n const arm = (\n <Button\n id={armId}\n type=\"button\"\n variant={tone === \"warning\" ? \"secondary\" : \"danger\"}\n disabled={disabled}\n aria-disabled={locked || undefined}\n aria-describedby={locked ? reasonId : undefined}\n onClick={() => {\n if (!locked) setArmed(true);\n }}\n className=\"aria-disabled:cursor-not-allowed aria-disabled:opacity-50\"\n >\n {armLabel ?? labels.arm}\n </Button>\n );\n return (\n <div {...rest} className={cn(\"space-y-1\", className)}>\n {locked ? (\n // The reason on the button itself too, where the pointer that tried it is —\n // the line below can sit out of view in a long settings card. Portalled, so\n // a card that scrolls neither clips it nor grows by it.\n //\n // The button in a FRAGMENT on purpose: Tooltip clones an element child to\n // add the bubble to its `aria-describedby`, and the button is already\n // described by the visible line below — the same sentence twice, read out\n // on every focus. A fragment is left as it is, so the bubble stays visual.\n <Tooltip label={lockedReason} portal>\n <>{arm}</>\n </Tooltip>\n ) : (\n arm\n )}\n {locked && (\n <p id={reasonId} className=\"text-xs text-[var(--text-muted)]\">\n {lockedReason}\n </p>\n )}\n </div>\n );\n }\n\n return (\n <div {...rest} className={cn(\"space-y-2\", className)}>\n <form\n className=\"space-y-2\"\n aria-describedby={promptId}\n aria-busy={busy || undefined}\n onSubmit={submit}\n noValidate\n >\n <p id={promptId} className={cn(\"text-xs font-medium\", toneText)}>\n {prompt ?? labels.prompt}\n </p>\n {phrase !== undefined &&\n (phrasePlaceholder ? (\n // A static label above, so the placeholder has the empty field to itself.\n <div className=\"space-y-1.5\">\n <Label htmlFor={phraseFieldId}>{phraseLabel}</Label>\n <Input id={phraseFieldId} placeholder={phrasePlaceholder} {...phraseFieldProps} />\n </div>\n ) : (\n <Input label={phraseLabel} {...phraseFieldProps} />\n ))}\n {requirePassword && (\n <Input\n // The phrase field takes the ref when both are there — it comes first.\n ref={phrase === undefined ? firstFieldRef : undefined}\n type=\"password\"\n autoComplete=\"current-password\"\n label={labels.password}\n value={password}\n readOnly={busy}\n onChange={(e) => setPassword(e.target.value)}\n />\n )}\n <div className=\"flex flex-wrap gap-2\">\n <Button id={cancelId} type=\"button\" variant=\"ghost\" disabled={busy} onClick={() => setArmed(false)}>\n {labels.cancel}\n </Button>\n <Button\n type=\"submit\"\n variant={tone === \"warning\" ? \"primary\" : \"danger\"}\n disabled={!canConfirm}\n aria-busy={busy || undefined}\n >\n {busy && <Spinner label={null} className=\"h-4 w-4\" />}\n {confirmLabel ?? labels.confirm}\n </Button>\n </div>\n </form>\n </div>\n );\n}\n"],"mappings":";AAuPM,SA2BM,UA3BN,KAgBA,YAhBA;AAvPN,SAAS,WAAW,OAAO,QAAQ,gBAAgB;AAEnD,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,QAAQ,OAAO,OAAO,eAAe;AAC9C,SAAS,eAAe;AAkCjB,MAAM,gCAAgC;AAAA,EAC3C,KAAK;AAAA,EACL,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,QAAQ,CAAC,WAAmB,cAAS,MAAM;AAC7C;AAyEO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAuB;AACrB,QAAM,SAAS,aAAa,iBAAiB,+BAA+B,UAAU;AACtF,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,QAAQ,aAAa;AAC3B,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,EAAE;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,OAAO,QAAQ,QAAQ,KAAK;AAGlC,QAAM,WAAW,MAAM;AACvB,QAAM,WAAW,MAAM;AACvB,QAAM,gBAAgB,MAAM;AAG5B,QAAM,QAAQ,MAAM;AACpB,QAAM,WAAW,MAAM;AACvB,QAAM,gBAAgB,OAAyB,IAAI;AAGnD,QAAM,YAAY,OAAO,KAAK;AAC9B,QAAM,UAAU,OAAO,IAAI;AAC3B,YAAU,MAAM;AACd,YAAQ,UAAU;AAClB,WAAO,MAAM;AACX,cAAQ,UAAU;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,CAAC;AAKL,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,MAAI,aAAa,OAAO;AACtB,gBAAY,KAAK;AACjB,QAAI,CAAC,OAAO;AACV,kBAAY,EAAE;AACd,eAAS,EAAE;AAAA,IACb;AAAA,EACF;AAEA,QAAM,YAAY,OAAO,KAAK;AAC9B,YAAU,MAAM;AACd,UAAM,UAAU,UAAU,YAAY;AACtC,cAAU,UAAU;AACpB,UAAM,SAAS,UAAU;AACzB,cAAU,UAAU;AACpB,QAAI,CAAC,QAAS;AACd,QAAI,OAAO;AACT,UAAI,OAAQ,EAAC,cAAc,WAAW,SAAS,eAAe,QAAQ,IAAI,MAAM;AAChF;AAAA,IACF;AAGA,UAAM,OAAO,SAAS,kBAAkB,QAAQ,SAAS,kBAAkB,SAAS;AACpF,QAAI,UAAU,KAAM,UAAS,eAAe,KAAK,GAAG,MAAM;AAAA,EAC5D,GAAG,CAAC,OAAO,OAAO,QAAQ,CAAC;AAE3B,QAAM,WAAW,CAAC,SAAkB;AAClC,cAAU,UAAU;AACpB,QAAI,cAAc,OAAW,eAAc,IAAI;AAC/C,oBAAgB,IAAI;AAAA,EACtB;AAEA,QAAM,SAAS,iBAAiB,UAAa,iBAAiB,QAAQ,iBAAiB,SAAS,iBAAiB;AACjH,QAAM,aAAa,CAAC,mBAAmB,aAAa;AACpD,QAAM,WAAW,WAAW,WAAc,gBAAgB,UAAU,QAAQ,MAAM,KAAK,OAAO;AAC9F,QAAM,aAAa,cAAc,YAAY,CAAC;AAE9C,QAAM,SAAS,CAAC,MAAiB;AAC/B,MAAE,eAAe;AACjB,QAAI,CAAC,WAAY;AACjB,UAAM,SAAS,UAAU,kBAAkB,WAAW,MAAS;AAC/D,QAAI,CAAC,UAAU,OAAQ,OAA4B,SAAS,WAAY;AACxE,eAAW,IAAI;AACf,IAAC,OAA4B;AAAA,MAC3B,MAAM;AACJ,YAAI,CAAC,QAAQ,QAAS;AACtB,mBAAW,KAAK;AAChB,iBAAS,KAAK;AAAA,MAChB;AAAA,MACA,MAAM;AAEJ,YAAI,QAAQ,QAAS,YAAW,KAAK;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,oBACJ,WAAW,UAAa,OAAO,sBAAsB,SACjD,SACA,OAAO,OAAO,sBAAsB,aAClC,OAAO,kBAAkB,MAAM,IAC/B,OAAO;AAEf,QAAM,cACJ,WAAW,SAAY,KAAK,OAAO,OAAO,WAAW,aAAa,OAAO,OAAO,MAAM,IAAI,OAAO;AACnG,QAAM,mBAAmB;AAAA,IACvB,KAAK;AAAA,IACL,OAAO;AAAA,IACP,cAAc;AAAA,IACd,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,UAAU,CAAC,MAAqC,SAAS,EAAE,OAAO,KAAK;AAAA,EACzE;AAEA,QAAM,WAAW,SAAS,YAAY,0BAA0B;AAEhE,MAAI,CAAC,OAAO;AACV,UAAM,MACJ;AAAA,MAAC;AAAA;AAAA,QACC,IAAI;AAAA,QACJ,MAAK;AAAA,QACL,SAAS,SAAS,YAAY,cAAc;AAAA,QAC5C;AAAA,QACA,iBAAe,UAAU;AAAA,QACzB,oBAAkB,SAAS,WAAW;AAAA,QACtC,SAAS,MAAM;AACb,cAAI,CAAC,OAAQ,UAAS,IAAI;AAAA,QAC5B;AAAA,QACA,WAAU;AAAA,QAET,sBAAY,OAAO;AAAA;AAAA,IACtB;AAEF,WACE,qBAAC,SAAK,GAAG,MAAM,WAAW,GAAG,aAAa,SAAS,GAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASC,oBAAC,WAAQ,OAAO,cAAc,QAAM,MAClC,0CAAG,eAAI,GACT;AAAA,UAEA;AAAA,MAED,UACC,oBAAC,OAAE,IAAI,UAAU,WAAU,oCACxB,wBACH;AAAA,OAEJ;AAAA,EAEJ;AAEA,SACE,oBAAC,SAAK,GAAG,MAAM,WAAW,GAAG,aAAa,SAAS,GACjD;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,oBAAkB;AAAA,MAClB,aAAW,QAAQ;AAAA,MACnB,UAAU;AAAA,MACV,YAAU;AAAA,MAEV;AAAA,4BAAC,OAAE,IAAI,UAAU,WAAW,GAAG,uBAAuB,QAAQ,GAC3D,oBAAU,OAAO,QACpB;AAAA,QACC,WAAW,WACT;AAAA;AAAA,UAEC,qBAAC,SAAI,WAAU,eACb;AAAA,gCAAC,SAAM,SAAS,eAAgB,uBAAY;AAAA,YAC5C,oBAAC,SAAM,IAAI,eAAe,aAAa,mBAAoB,GAAG,kBAAkB;AAAA,aAClF;AAAA,YAEA,oBAAC,SAAM,OAAO,aAAc,GAAG,kBAAkB;AAAA,QAEpD,mBACC;AAAA,UAAC;AAAA;AAAA,YAEC,KAAK,WAAW,SAAY,gBAAgB;AAAA,YAC5C,MAAK;AAAA,YACL,cAAa;AAAA,YACb,OAAO,OAAO;AAAA,YACd,OAAO;AAAA,YACP,UAAU;AAAA,YACV,UAAU,CAAC,MAAM,YAAY,EAAE,OAAO,KAAK;AAAA;AAAA,QAC7C;AAAA,QAEF,qBAAC,SAAI,WAAU,wBACb;AAAA,8BAAC,UAAO,IAAI,UAAU,MAAK,UAAS,SAAQ,SAAQ,UAAU,MAAM,SAAS,MAAM,SAAS,KAAK,GAC9F,iBAAO,QACV;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,SAAS,YAAY,YAAY;AAAA,cAC1C,UAAU,CAAC;AAAA,cACX,aAAW,QAAQ;AAAA,cAElB;AAAA,wBAAQ,oBAAC,WAAQ,OAAO,MAAM,WAAU,WAAU;AAAA,gBAClD,gBAAgB,OAAO;AAAA;AAAA;AAAA,UAC1B;AAAA,WACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/danger-confirm.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { ChangeEvent, ComponentPropsWithoutRef, FormEvent, ReactNode, Ref } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { Button, Input, Label, Spinner } from \"./ui\";\nimport { Tooltip } from \"./tooltip\";\n\n/**\n * How typed text is compared with the text it has to repeat — shared by\n * `DangerConfirm`'s `phraseMatch` and `useConfirm`'s `typedMatch`.\n *\n * - `\"trim\"` ignores spaces around the typed text — a phone keyboard's autocomplete\n * likes to add one. Case counts: \"delete\" is not \"DELETE\".\n * - `\"exact\"` compares character for character, spaces included.\n * - `\"caseless\"` trims AND ignores case: for an e-mail address, whose domain is\n * case-insensitive and whose local part every real mail server treats so. Both apps'\n * \"type the account's address\" rules (keksdose's `TYPE_EMAIL`, Kurvenschmiede's\n * `TypedConfirm`) compared `trim().toLowerCase()`, and a capital the user's keyboard\n * put at the start must not make the right row look like the wrong one.\n */\nexport type TypedMatch = \"trim\" | \"exact\" | \"caseless\";\n\n/** Whether `typed` repeats `target` under `mode` — see {@link TypedMatch}. Exported for\n * a caller that draws its own field (a dialog that also picks a recipient) and wants\n * the same rule as the kit's. */\nexport function typedMatches(typed: string, target: string, mode: TypedMatch = \"trim\"): boolean {\n if (mode === \"exact\") return typed === target;\n if (mode === \"trim\") return typed.trim() === target;\n // `toLowerCase`, not `toLocaleLowerCase`: the comparison must not change with the\n // reader's locale (a Turkish dotless i would otherwise fail an ASCII address).\n return typed.trim().toLowerCase() === target.trim().toLowerCase();\n}\n\n/**\n * Busy-while-a-promise-runs, for an action that MAY return one: `run(result, onResolved)`\n * marks the caller pending until `result` settles, calls `onResolved` if it fulfilled,\n * and only clears the flag if it rejected — the caller shows why and the user retries.\n * Nothing runs after unmount. Shared by `DangerConfirm` and `ReauthDialog`, whose\n * contract is the same: resolve = done, reject = stay put.\n *\n * @internal\n */\nexport function usePromisePending() {\n const [pending, setPending] = useState(false);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const run = useCallback((result: unknown, onResolved: () => void): boolean => {\n if (!result || typeof (result as Promise<unknown>).then !== \"function\") return false;\n setPending(true);\n (result as Promise<unknown>).then(\n () => {\n if (!mounted.current) return;\n setPending(false);\n onResolved();\n },\n () => {\n if (mounted.current) setPending(false);\n },\n );\n return true;\n }, []);\n return { pending, run };\n}\n\n/**\n * The \"prove it is you\" field: a password input the browser's password manager fills\n * with the CURRENT password (`autocomplete=\"current-password\"`), never offers to\n * generate a new one for. `readOnly` rather than `disabled` while busy: disabling the\n * field that has focus (Enter was pressed in it) drops focus to <body>.\n *\n * @internal DangerConfirm's field. ReauthDialog spells it out with the field's own\n * `error`, which keeps the `<input>` mounted while its message comes and goes.\n */\nexport function CurrentPasswordInput({\n inputRef,\n label,\n value,\n onValueChange,\n busy,\n invalid,\n describedBy,\n}: {\n inputRef?: Ref<HTMLInputElement>;\n label: string;\n value: string;\n onValueChange: (value: string) => void;\n busy: boolean;\n invalid?: boolean;\n describedBy?: string;\n}) {\n return (\n <Input\n ref={inputRef}\n type=\"password\"\n autoComplete=\"current-password\"\n label={label}\n value={value}\n readOnly={busy}\n invalid={invalid}\n aria-describedby={describedBy}\n onChange={(e) => onValueChange(e.target.value)}\n />\n );\n}\n\n/**\n * Every string the tile renders — the `dangerConfirm` namespace of\n * `<UiKitProvider labels>`, overridable per instance through `labels`. The arm and\n * confirm buttons are usually worded per action (\"Delete budget\"), which is what the\n * `armLabel` / `confirmLabel` props are for; these are the fallbacks.\n */\nexport interface DangerConfirmLabels {\n /** The button that arms the tile. */\n arm: string;\n /** The button that runs the action once the guards are satisfied. */\n confirm: string;\n cancel: string;\n /** The warning above the fields, when no `prompt` is given. */\n prompt: string;\n /** Label of the password field (`requirePassword`). */\n password: string;\n /** Label of the type-to-confirm field. A FUNCTION of the phrase by default, like\n * every message that carries a value: where the phrase sits in the sentence moves\n * with the language. A plain STRING is taken as the finished label — for an app\n * whose catalogue already words it (\"Type DELETE to confirm\") and would otherwise\n * wrap it as `() => label`. */\n phrase: string | ((phrase: string) => string);\n /** Placeholder of the type-to-confirm field — a string, or a function of the\n * phrase (`(p) => p` echoes it). Optional and unset by default: the field keeps its\n * floating label, as before. Given, the label moves ABOVE the field (a floating\n * label occupies the empty field, which is exactly where a placeholder shows) and\n * the placeholder fills the field. An empty string means none. */\n phrasePlaceholder?: string | ((phrase: string) => string);\n}\n\n/** `satisfies` rather than a type annotation, so `DEFAULT_DANGER_CONFIRM_LABELS.phrase`\n * stays callable for a caller that composes its own label from it. */\nexport const DEFAULT_DANGER_CONFIRM_LABELS = {\n arm: \"Delete…\",\n confirm: \"Delete\",\n cancel: \"Cancel\",\n prompt: \"This cannot be undone.\",\n password: \"Password\",\n phrase: (phrase: string) => `Type “${phrase}” to confirm`,\n} satisfies DangerConfirmLabels;\n\nexport interface DangerConfirmProps extends Omit<ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n /**\n * Runs the action, with the entered password when `requirePassword` is on.\n *\n * Return a promise and the tile manages itself: busy until it settles, disarmed\n * (fields wiped) when it resolves, still armed when it rejects — so the user can\n * correct a wrong password and retry. The rejection is not swallowed for you to\n * miss: handle it (and show why) in the caller, as with any mutation.\n */\n onConfirm: (password?: string) => void | Promise<unknown>;\n /** Show a password field; confirm stays disabled until it is filled. */\n requirePassword?: boolean;\n /** Show a \"type <phrase> to confirm\" field; confirm stays disabled until the field\n * matches (case-sensitive; surrounding spaces ignored unless `phraseMatch=\"exact\"`). */\n phrase?: string;\n /**\n * How the typed text is compared with `phrase`. `\"trim\"` (default) ignores spaces\n * around it — a phone keyboard's autocomplete likes to add one. `\"exact\"` compares\n * character for character, spaces included, for an app whose contract is \"type\n * exactly this\". `\"caseless\"` (0.18) also ignores case — for a phrase that is an\n * e-mail address (keksdose's \"type the user's address\" admin rule). See\n * {@link TypedMatch}.\n */\n phraseMatch?: TypedMatch;\n /** The warning above the fields. Defaults to `labels.prompt`. */\n prompt?: ReactNode;\n /** `\"danger\"` (default) for what cannot be undone; `\"warning\"` for what can, at a\n * cost (loading demo data over your own). Colours the prompt and the confirm. */\n tone?: \"danger\" | \"warning\";\n /** Visible text of the arm button; defaults to `labels.arm`. */\n armLabel?: ReactNode;\n /** Visible text of the confirm button; defaults to `labels.confirm`. */\n confirmLabel?: ReactNode;\n /** The action is running: confirm shows a spinner and nothing can be pressed. For a\n * caller that tracks the mutation itself (a `useMutation`'s `isPending`); a\n * promise returned from `onConfirm` does the same on its own. */\n busy?: boolean;\n /** The arm button is disabled. */\n disabled?: boolean;\n /**\n * Why the action is not available — a read-only demo, a write lock, a missing\n * permission. Disables the arm button like `disabled`, and SAYS so: the sentence is\n * shown under the button and attached to it with `aria-describedby`, and the button\n * stays focusable (`aria-disabled`) so a keyboard user can land on it and hear why.\n *\n * On the ARM button rather than the confirm: arming asks for a password, and asking\n * for a password for a write that can never land is the worse of the two.\n */\n lockedReason?: ReactNode;\n /** Controlled armed state. The parent can then collapse the tile from a mutation's\n * own `onSuccess` without returning a promise. */\n armed?: boolean;\n /** Called with the next armed state — on arm, cancel, and a resolved `onConfirm`. */\n onArmedChange?: (armed: boolean) => void;\n /** User-facing strings; see {@link DangerConfirmLabels}. */\n labels?: Partial<DangerConfirmLabels>;\n}\n\n/**\n * An \"arm → confirm\" tile for destructive actions: one button, which expands into a\n * warning, an optional password field, an optional type-to-confirm field and a\n * confirm that stays disabled until every guard is satisfied.\n *\n * Keksdose hand-rolled it three times (load demo data, wipe everything, reset a\n * budget) and then as `shared/components/danger-confirm.tsx`; the only app-specific\n * part was its write-lock hook, which is `lockedReason` here.\n *\n * The fields are a `<form>`, so Enter confirms once the guards allow it. They are\n * wiped every time the tile disarms — a password must not sit in a collapsed tile.\n * Arming moves focus to the first field (or Cancel, when there is none — never to the\n * destructive button itself), and disarming moves it back to the arm button, so the\n * keyboard user is never left on an element that just vanished.\n */\nexport function DangerConfirm({\n onConfirm,\n requirePassword,\n phrase,\n phraseMatch = \"trim\",\n prompt,\n tone = \"danger\",\n armLabel,\n confirmLabel,\n busy: busyProp,\n disabled,\n lockedReason,\n armed: armedProp,\n onArmedChange,\n labels: labelsProp,\n className,\n ...rest\n}: DangerConfirmProps) {\n const labels = useKitLabels(\"dangerConfirm\", DEFAULT_DANGER_CONFIRM_LABELS, labelsProp);\n const [armedState, setArmedState] = useState(false);\n const armed = armedProp ?? armedState;\n const [password, setPassword] = useState(\"\");\n const [typed, setTyped] = useState(\"\");\n const { pending, run } = usePromisePending();\n const busy = Boolean(busyProp) || pending;\n // `readOnly` rather than `disabled` on the fields while busy: disabling the field\n // that has focus (Enter was pressed in it) drops focus to <body>.\n const promptId = useId();\n const reasonId = useId();\n const phraseFieldId = useId();\n\n // The kit's Button takes no ref, so the two buttons focus is moved to are found by id.\n const armId = useId();\n const cancelId = useId();\n const firstFieldRef = useRef<HTMLInputElement>(null);\n // Focus moves only after a transition — never on mount, so a tile that renders armed\n // (controlled) does not take the page's focus merely by existing.\n const moveFocus = useRef(false);\n\n // A disarm from anywhere (cancel, a resolved confirm, the parent) wipes the fields.\n // During render, like NumberField's draft, so no frame shows a collapsed tile that\n // still holds a password.\n const [wasArmed, setWasArmed] = useState(armed);\n if (wasArmed !== armed) {\n setWasArmed(armed);\n if (!armed) {\n setPassword(\"\");\n setTyped(\"\");\n }\n }\n\n const prevArmed = useRef(armed);\n useEffect(() => {\n const changed = prevArmed.current !== armed;\n prevArmed.current = armed;\n const byUser = moveFocus.current;\n moveFocus.current = false;\n if (!changed) return;\n if (armed) {\n if (byUser) (firstFieldRef.current ?? document.getElementById(cancelId))?.focus();\n return;\n }\n // A controlled parent collapsing the tile from its own `onSuccess` did not go\n // through `setArmed`; if focus went down with the form, bring it back too.\n const lost = document.activeElement === null || document.activeElement === document.body;\n if (byUser || lost) document.getElementById(armId)?.focus();\n }, [armed, armId, cancelId]);\n\n const setArmed = (next: boolean) => {\n moveFocus.current = true;\n if (armedProp === undefined) setArmedState(next);\n onArmedChange?.(next);\n };\n\n const locked = lockedReason !== undefined && lockedReason !== null && lockedReason !== false && lockedReason !== \"\";\n const passwordOk = !requirePassword || password !== \"\";\n const phraseOk = phrase === undefined || typedMatches(typed, phrase, phraseMatch);\n const canConfirm = passwordOk && phraseOk && !busy;\n\n const submit = (e: FormEvent) => {\n e.preventDefault();\n if (!canConfirm) return;\n // Disarms when it resolves; a rejection leaves it armed, fields kept: the caller\n // shows why, the user retries.\n run(onConfirm(requirePassword ? password : undefined), () => setArmed(false));\n };\n\n const phrasePlaceholder =\n phrase === undefined || labels.phrasePlaceholder === undefined\n ? undefined\n : typeof labels.phrasePlaceholder === \"function\"\n ? labels.phrasePlaceholder(phrase)\n : labels.phrasePlaceholder;\n\n const phraseLabel =\n phrase === undefined ? \"\" : typeof labels.phrase === \"function\" ? labels.phrase(phrase) : labels.phrase;\n const phraseFieldProps = {\n ref: firstFieldRef,\n value: typed,\n autoComplete: \"off\",\n autoCapitalize: \"off\",\n spellCheck: false,\n readOnly: busy,\n onChange: (e: ChangeEvent<HTMLInputElement>) => setTyped(e.target.value),\n };\n\n const toneText = tone === \"warning\" ? \"text-[var(--warning)]\" : \"text-[var(--danger)]\";\n\n if (!armed) {\n const arm = (\n <Button\n id={armId}\n type=\"button\"\n variant={tone === \"warning\" ? \"secondary\" : \"danger\"}\n disabled={disabled}\n aria-disabled={locked || undefined}\n aria-describedby={locked ? reasonId : undefined}\n onClick={() => {\n if (!locked) setArmed(true);\n }}\n className=\"aria-disabled:cursor-not-allowed aria-disabled:opacity-50\"\n >\n {armLabel ?? labels.arm}\n </Button>\n );\n return (\n <div {...rest} className={cn(\"space-y-1\", className)}>\n {locked ? (\n // The reason on the button itself too, where the pointer that tried it is —\n // the line below can sit out of view in a long settings card. Portalled, so\n // a card that scrolls neither clips it nor grows by it.\n //\n // The button in a FRAGMENT on purpose: Tooltip clones an element child to\n // add the bubble to its `aria-describedby`, and the button is already\n // described by the visible line below — the same sentence twice, read out\n // on every focus. A fragment is left as it is, so the bubble stays visual.\n <Tooltip label={lockedReason} portal>\n <>{arm}</>\n </Tooltip>\n ) : (\n arm\n )}\n {locked && (\n <p id={reasonId} className=\"text-xs text-[var(--text-muted)]\">\n {lockedReason}\n </p>\n )}\n </div>\n );\n }\n\n return (\n <div {...rest} className={cn(\"space-y-2\", className)}>\n <form\n className=\"space-y-2\"\n aria-describedby={promptId}\n aria-busy={busy || undefined}\n onSubmit={submit}\n noValidate\n >\n <p id={promptId} className={cn(\"text-xs font-medium\", toneText)}>\n {prompt ?? labels.prompt}\n </p>\n {phrase !== undefined &&\n (phrasePlaceholder ? (\n // A static label above, so the placeholder has the empty field to itself.\n <div className=\"space-y-1.5\">\n <Label htmlFor={phraseFieldId}>{phraseLabel}</Label>\n <Input id={phraseFieldId} placeholder={phrasePlaceholder} {...phraseFieldProps} />\n </div>\n ) : (\n <Input label={phraseLabel} {...phraseFieldProps} />\n ))}\n {requirePassword && (\n <CurrentPasswordInput\n // The phrase field takes the ref when both are there — it comes first.\n inputRef={phrase === undefined ? firstFieldRef : undefined}\n label={labels.password}\n value={password}\n busy={busy}\n onValueChange={setPassword}\n />\n )}\n <div className=\"flex flex-wrap gap-2\">\n <Button id={cancelId} type=\"button\" variant=\"ghost\" disabled={busy} onClick={() => setArmed(false)}>\n {labels.cancel}\n </Button>\n <Button\n type=\"submit\"\n variant={tone === \"warning\" ? \"primary\" : \"danger\"}\n disabled={!canConfirm}\n aria-busy={busy || undefined}\n >\n {busy && <Spinner label={null} className=\"h-4 w-4\" />}\n {confirmLabel ?? labels.confirm}\n </Button>\n </div>\n </form>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { DataTableLabels } from './data-table-labels.js';
4
- import { b as DataTableColumn } from '../data-table-CpkWUJBY.js';
4
+ import { b as DataTableColumn } from '../data-table-3mi2R3so.js';
5
5
  import './data-table-sort.js';
6
6
  import './swipeable-row.js';
7
7
 
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { b as DataTableColumn, f as FilterValue } from '../data-table-CpkWUJBY.js';
2
+ import { b as DataTableColumn, f as FilterValue } from '../data-table-3mi2R3so.js';
3
3
  import { DataTableLabels } from './data-table-labels.js';
4
4
  import './data-table-sort.js';
5
5
  import './swipeable-row.js';
@@ -1,4 +1,4 @@
1
- export { C as ColumnFilter, f as FilterValue, j as dateFilter, k as decodeFilterValue, l as decodeFilterValueOfType, m as defaultFilterState, n as encodeFilterValue, o as filterHref, p as isFilterActive, q as numberFilter, s as resolveFilter, t as rowMatches, u as selectFilter, v as textFilter } from '../data-table-CpkWUJBY.js';
1
+ export { C as ColumnFilter, f as FilterValue, j as dateFilter, k as decodeFilterValue, l as decodeFilterValueOfType, m as defaultFilterState, n as encodeFilterValue, o as filterHref, p as isFilterActive, q as numberFilter, s as resolveFilter, t as rowMatches, u as selectFilter, v as textFilter } from '../data-table-3mi2R3so.js';
2
2
  import 'react';
3
3
  import './data-table-sort.js';
4
4
  import './swipeable-row.js';
@@ -1,5 +1,5 @@
1
1
  import 'react';
2
- export { D as DataTable, x as DataTableCellProps, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, y as DataTableHeadProps, d as DataTableProps, e as DataTableRowAction, F as FilterState, M as MobileSwipeActions, S as ServerPagination } from '../data-table-CpkWUJBY.js';
2
+ export { D as DataTable, x as DataTableCellProps, a as DataTableChrome, b as DataTableColumn, c as DataTableDensity, y as DataTableHeadProps, d as DataTableProps, e as DataTableRowAction, F as FilterState, M as MobileSwipeActions, S as ServerPagination } from '../data-table-3mi2R3so.js';
3
3
  export { SortState } from './data-table-sort.js';
4
4
  import './swipeable-row.js';
5
5
  import './data-table-labels.js';
@@ -181,6 +181,7 @@ function DataTable({
181
181
  locale: localeProp,
182
182
  storageKeyPrefix = DEFAULT_PERSIST_PREFIX,
183
183
  mobileExpandAsDialog = false,
184
+ mobileDialogBackCloses = true,
184
185
  mobileGroupBy,
185
186
  mobileGroupLabel,
186
187
  mobileCard,
@@ -839,6 +840,7 @@ function DataTable({
839
840
  onClose: closeDialog,
840
841
  closeLabel: labels.close,
841
842
  header: mobileDialogRow ? mobilePrimaryCol?.cell(mobileDialogRow) : null,
843
+ backCloses: mobileDialogBackCloses,
842
844
  children: mobileDialogContent
843
845
  }
844
846
  )