@eifi1/ui-kit 0.48.2 → 0.49.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 (292) hide show
  1. package/README.md +26 -12
  2. package/dist/admin/admin-action-confirm.js +1 -1
  3. package/dist/admin/admin-action-confirm.js.map +1 -1
  4. package/dist/admin/invitations-panel.js +5 -1
  5. package/dist/admin/invitations-panel.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +11 -5
  7. package/dist/admin/user-roster.js +15 -4
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/accept-invitation.js +2 -2
  10. package/dist/auth/accept-invitation.js.map +1 -1
  11. package/dist/auth/not-found-page.js +1 -1
  12. package/dist/auth/not-found-page.js.map +1 -1
  13. package/dist/auth/reset-password-form.js +2 -2
  14. package/dist/auth/reset-password-form.js.map +1 -1
  15. package/dist/auth/verify-email.js +2 -2
  16. package/dist/auth/verify-email.js.map +1 -1
  17. package/dist/billing/plan-card.js +2 -2
  18. package/dist/billing/plan-card.js.map +1 -1
  19. package/dist/components/breadcrumbs.d.ts +19 -1
  20. package/dist/components/breadcrumbs.js +11 -4
  21. package/dist/components/breadcrumbs.js.map +1 -1
  22. package/dist/components/button-look.d.ts +37 -7
  23. package/dist/components/button-look.js +33 -6
  24. package/dist/components/button-look.js.map +1 -1
  25. package/dist/components/button.d.ts +4 -1
  26. package/dist/components/button.js +2 -2
  27. package/dist/components/button.js.map +1 -1
  28. package/dist/components/calendar-heatmap.js +2 -1
  29. package/dist/components/calendar-heatmap.js.map +1 -1
  30. package/dist/components/chart-edge.d.ts +10 -0
  31. package/dist/components/chart-edge.js +57 -0
  32. package/dist/components/chart-edge.js.map +1 -0
  33. package/dist/components/chart.js +2 -1
  34. package/dist/components/chart.js.map +1 -1
  35. package/dist/components/chip.d.ts +8 -1
  36. package/dist/components/chip.js +4 -1
  37. package/dist/components/chip.js.map +1 -1
  38. package/dist/components/confirm-context.d.ts +5 -0
  39. package/dist/components/confirm-context.js +7 -0
  40. package/dist/components/confirm-context.js.map +1 -0
  41. package/dist/components/confirm-dialog.js +3 -3
  42. package/dist/components/confirm-dialog.js.map +1 -1
  43. package/dist/components/danger-confirm.d.ts +2 -2
  44. package/dist/components/danger-confirm.js +1 -1
  45. package/dist/components/danger-confirm.js.map +1 -1
  46. package/dist/components/data-table-fit.js +21 -3
  47. package/dist/components/data-table-fit.js.map +1 -1
  48. package/dist/components/data-table-mobile.d.ts +48 -2
  49. package/dist/components/data-table-mobile.js +87 -10
  50. package/dist/components/data-table-mobile.js.map +1 -1
  51. package/dist/components/data-table-row-actions.d.ts +2 -1
  52. package/dist/components/data-table-row-actions.js +7 -0
  53. package/dist/components/data-table-row-actions.js.map +1 -1
  54. package/dist/components/data-table-row.d.ts +1 -1
  55. package/dist/components/data-table-row.js.map +1 -1
  56. package/dist/components/data-table.d.ts +34 -1
  57. package/dist/components/data-table.js +34 -9
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/error-boundary.js +2 -2
  60. package/dist/components/error-boundary.js.map +1 -1
  61. package/dist/components/floating-panel.d.ts +36 -13
  62. package/dist/components/floating-panel.js +27 -7
  63. package/dist/components/floating-panel.js.map +1 -1
  64. package/dist/components/form-actions.d.ts +1 -1
  65. package/dist/components/form-actions.js +1 -1
  66. package/dist/components/form-actions.js.map +1 -1
  67. package/dist/components/icon-button.d.ts +11 -0
  68. package/dist/components/icon-button.js +7 -5
  69. package/dist/components/icon-button.js.map +1 -1
  70. package/dist/components/nav-pills.d.ts +8 -1
  71. package/dist/components/nav-pills.js +61 -38
  72. package/dist/components/nav-pills.js.map +1 -1
  73. package/dist/components/page-header-context.d.ts +8 -0
  74. package/dist/components/page-header-context.js +7 -0
  75. package/dist/components/page-header-context.js.map +1 -0
  76. package/dist/components/page-header.d.ts +14 -2
  77. package/dist/components/page-header.js +10 -1
  78. package/dist/components/page-header.js.map +1 -1
  79. package/dist/components/pie-chart.js +2 -1
  80. package/dist/components/pie-chart.js.map +1 -1
  81. package/dist/components/progress-bar.js +5 -4
  82. package/dist/components/progress-bar.js.map +1 -1
  83. package/dist/components/reauth-dialog.js +1 -1
  84. package/dist/components/reauth-dialog.js.map +1 -1
  85. package/dist/components/row-actions-labels.js +3 -1
  86. package/dist/components/row-actions-labels.js.map +1 -1
  87. package/dist/components/row-actions.d.ts +43 -9
  88. package/dist/components/row-actions.js +78 -15
  89. package/dist/components/row-actions.js.map +1 -1
  90. package/dist/components/section-nav.d.ts +37 -0
  91. package/dist/components/section-nav.js +33 -0
  92. package/dist/components/section-nav.js.map +1 -0
  93. package/dist/components/series-chart.js +8 -3
  94. package/dist/components/series-chart.js.map +1 -1
  95. package/dist/components/share-card.js +1 -1
  96. package/dist/components/share-card.js.map +1 -1
  97. package/dist/components/signature-pad.js +1 -1
  98. package/dist/components/signature-pad.js.map +1 -1
  99. package/dist/components/sparkline.js +2 -1
  100. package/dist/components/sparkline.js.map +1 -1
  101. package/dist/components/stat-tile.js +14 -7
  102. package/dist/components/stat-tile.js.map +1 -1
  103. package/dist/components/status-dot.d.ts +5 -1
  104. package/dist/components/status-dot.js +4 -1
  105. package/dist/components/status-dot.js.map +1 -1
  106. package/dist/components/tooltip.js +1 -1
  107. package/dist/components/tooltip.js.map +1 -1
  108. package/dist/components/translation-review-editor.js +2 -2
  109. package/dist/components/translation-review-editor.js.map +1 -1
  110. package/dist/components/translation-review.js +2 -2
  111. package/dist/components/translation-review.js.map +1 -1
  112. package/dist/components/treemap.d.ts +5 -2
  113. package/dist/components/treemap.js +26 -6
  114. package/dist/components/treemap.js.map +1 -1
  115. package/dist/components/use-table-state.js +37 -6
  116. package/dist/components/use-table-state.js.map +1 -1
  117. package/dist/data-table.js.map +1 -1
  118. package/dist/demo/demo-banner.js +1 -1
  119. package/dist/demo/demo-banner.js.map +1 -1
  120. package/dist/demo/demo-ended.js +1 -1
  121. package/dist/demo/demo-ended.js.map +1 -1
  122. package/dist/demo/demo-start.js +1 -1
  123. package/dist/demo/demo-start.js.map +1 -1
  124. package/dist/eslint/index.js +1 -1
  125. package/dist/feedback/feedback-inbox.js +1 -1
  126. package/dist/feedback/feedback-inbox.js.map +1 -1
  127. package/dist/hooks/use-dismiss.d.ts +9 -1
  128. package/dist/hooks/use-dismiss.js +25 -5
  129. package/dist/hooks/use-dismiss.js.map +1 -1
  130. package/dist/hooks/use-document-title.d.ts +14 -0
  131. package/dist/hooks/use-document-title.js +22 -0
  132. package/dist/hooks/use-document-title.js.map +1 -0
  133. package/dist/i18n/german.js +6 -2
  134. package/dist/i18n/german.js.map +1 -1
  135. package/dist/i18n/kit-labels.d.ts +31 -1
  136. package/dist/i18n/kit-labels.js +43 -2
  137. package/dist/i18n/kit-labels.js.map +1 -1
  138. package/dist/i18n/locales/en.js +6 -2
  139. package/dist/i18n/locales/en.js.map +2 -2
  140. package/dist/i18n/locales/es.js +6 -2
  141. package/dist/i18n/locales/es.js.map +1 -1
  142. package/dist/i18n/locales/fr.js +6 -2
  143. package/dist/i18n/locales/fr.js.map +1 -1
  144. package/dist/i18n/locales/hu.js +6 -2
  145. package/dist/i18n/locales/hu.js.map +1 -1
  146. package/dist/i18n/locales/it.js +6 -2
  147. package/dist/i18n/locales/it.js.map +1 -1
  148. package/dist/i18n/locales/zh.js +6 -2
  149. package/dist/i18n/locales/zh.js.map +1 -1
  150. package/dist/i18n/review.js +2 -0
  151. package/dist/i18n/review.js.map +1 -1
  152. package/dist/i18n/shared-labels.js +1 -0
  153. package/dist/i18n/shared-labels.js.map +1 -1
  154. package/dist/index.d.ts +8 -1
  155. package/dist/index.js +11 -0
  156. package/dist/index.js.map +1 -1
  157. package/dist/landing/landing-actions.js +2 -2
  158. package/dist/landing/landing-actions.js.map +1 -1
  159. package/dist/landing/public-header.js +2 -2
  160. package/dist/landing/public-header.js.map +1 -1
  161. package/dist/lib/clipboard-minus.d.ts +27 -0
  162. package/dist/lib/clipboard-minus.js +63 -0
  163. package/dist/lib/clipboard-minus.js.map +1 -0
  164. package/dist/lib/document-title.d.ts +17 -0
  165. package/dist/lib/document-title.js +29 -0
  166. package/dist/lib/document-title.js.map +1 -0
  167. package/dist/lib/float-clearance.js +8 -2
  168. package/dist/lib/float-clearance.js.map +1 -1
  169. package/dist/lib/format.d.ts +35 -10
  170. package/dist/lib/format.js +55 -11
  171. package/dist/lib/format.js.map +1 -1
  172. package/dist/lib/minus.d.ts +22 -0
  173. package/dist/lib/minus.js +14 -0
  174. package/dist/lib/minus.js.map +1 -0
  175. package/dist/lib/status-tone.d.ts +30 -0
  176. package/dist/lib/status-tone.js +17 -0
  177. package/dist/lib/status-tone.js.map +1 -0
  178. package/dist/settings/settings-layout.js +3 -2
  179. package/dist/settings/settings-layout.js.map +1 -1
  180. package/dist/shell/account-menu-language.d.ts +1 -1
  181. package/dist/shell/account-menu-language.js +9 -1
  182. package/dist/shell/account-menu-language.js.map +1 -1
  183. package/dist/shell/app-shell-labels.js +2 -1
  184. package/dist/shell/app-shell-labels.js.map +1 -1
  185. package/dist/shell/app-shell.d.ts +58 -16
  186. package/dist/shell/app-shell.js +151 -43
  187. package/dist/shell/app-shell.js.map +1 -1
  188. package/dist/shell/language-code.d.ts +3 -0
  189. package/dist/shell/language-code.js +8 -0
  190. package/dist/shell/language-code.js.map +1 -0
  191. package/dist/shell/topbar-controls.d.ts +14 -6
  192. package/dist/shell/topbar-controls.js +14 -17
  193. package/dist/shell/topbar-controls.js.map +1 -1
  194. package/dist/testing/css-variables.generated.d.ts +1 -1
  195. package/dist/testing/css-variables.generated.js +1 -1
  196. package/dist/testing/css-variables.generated.js.map +1 -1
  197. package/dist/theme/chart-palette.d.ts +82 -15
  198. package/dist/theme/chart-palette.js +51 -24
  199. package/dist/theme/chart-palette.js.map +1 -1
  200. package/dist/theme/palette-presets.d.ts +9 -0
  201. package/dist/theme/palette-presets.js +77 -62
  202. package/dist/theme/palette-presets.js.map +1 -1
  203. package/dist/tour/tour.js +1 -1
  204. package/dist/tour/tour.js.map +1 -1
  205. package/dist/wizard/stepper-nav.d.ts +1 -1
  206. package/dist/wizard/stepper-nav.js +3 -3
  207. package/dist/wizard/stepper-nav.js.map +1 -1
  208. package/package.json +1 -1
  209. package/src/admin/admin-action-confirm.tsx +1 -1
  210. package/src/admin/invitations-panel.tsx +7 -3
  211. package/src/admin/user-roster.tsx +23 -6
  212. package/src/auth/accept-invitation.tsx +3 -3
  213. package/src/auth/not-found-page.tsx +1 -1
  214. package/src/auth/reset-password-form.tsx +3 -3
  215. package/src/auth/verify-email.tsx +3 -3
  216. package/src/billing/plan-card.tsx +2 -2
  217. package/src/components/breadcrumbs.tsx +32 -4
  218. package/src/components/button-look.ts +86 -15
  219. package/src/components/button.tsx +6 -3
  220. package/src/components/calendar-heatmap.tsx +2 -1
  221. package/src/components/chart-edge.ts +73 -0
  222. package/src/components/chart.tsx +3 -1
  223. package/src/components/chip.tsx +10 -1
  224. package/src/components/confirm-context.ts +7 -0
  225. package/src/components/confirm-dialog.tsx +3 -3
  226. package/src/components/danger-confirm.tsx +3 -3
  227. package/src/components/data-table-fit.ts +41 -6
  228. package/src/components/data-table-mobile.tsx +162 -11
  229. package/src/components/data-table-row-actions.tsx +9 -1
  230. package/src/components/data-table-row.tsx +2 -0
  231. package/src/components/data-table.tsx +82 -18
  232. package/src/components/error-boundary.tsx +2 -2
  233. package/src/components/floating-panel.tsx +67 -16
  234. package/src/components/form-actions.tsx +2 -2
  235. package/src/components/icon-button.tsx +21 -8
  236. package/src/components/nav-pills.tsx +29 -2
  237. package/src/components/page-header-context.ts +8 -0
  238. package/src/components/page-header.tsx +34 -2
  239. package/src/components/pie-chart.tsx +2 -1
  240. package/src/components/progress-bar.tsx +6 -3
  241. package/src/components/reauth-dialog.tsx +1 -1
  242. package/src/components/row-actions-labels.ts +2 -0
  243. package/src/components/row-actions.tsx +154 -33
  244. package/src/components/section-nav.tsx +65 -0
  245. package/src/components/series-chart.tsx +12 -3
  246. package/src/components/share-card.tsx +1 -1
  247. package/src/components/signature-pad.tsx +1 -1
  248. package/src/components/sparkline.tsx +2 -1
  249. package/src/components/stat-tile.tsx +19 -7
  250. package/src/components/status-dot.tsx +7 -1
  251. package/src/components/tooltip.tsx +3 -1
  252. package/src/components/translation-review-editor.tsx +2 -2
  253. package/src/components/translation-review.tsx +2 -2
  254. package/src/components/treemap.tsx +34 -8
  255. package/src/components/use-table-state.ts +63 -8
  256. package/src/data-table.ts +3 -3
  257. package/src/demo/demo-banner.tsx +1 -1
  258. package/src/demo/demo-ended.tsx +1 -1
  259. package/src/demo/demo-start.tsx +1 -1
  260. package/src/feedback/feedback-inbox.tsx +1 -1
  261. package/src/hooks/use-dismiss.ts +43 -4
  262. package/src/hooks/use-document-title.ts +31 -0
  263. package/src/i18n/german.ts +4 -0
  264. package/src/i18n/kit-labels.tsx +76 -1
  265. package/src/i18n/locales/es.ts +4 -0
  266. package/src/i18n/locales/fr.ts +4 -0
  267. package/src/i18n/locales/hu.ts +4 -0
  268. package/src/i18n/locales/it.ts +4 -0
  269. package/src/i18n/locales/zh.ts +4 -0
  270. package/src/i18n/review.ts +2 -0
  271. package/src/i18n/shared-labels.ts +1 -0
  272. package/src/index.ts +13 -0
  273. package/src/landing/landing-actions.tsx +2 -2
  274. package/src/landing/public-header.tsx +2 -2
  275. package/src/lib/clipboard-minus.ts +94 -0
  276. package/src/lib/document-title.ts +41 -0
  277. package/src/lib/float-clearance.ts +19 -3
  278. package/src/lib/format.ts +115 -30
  279. package/src/lib/minus.ts +40 -0
  280. package/src/lib/status-tone.ts +43 -0
  281. package/src/settings/settings-layout.tsx +3 -2
  282. package/src/shell/account-menu-language.tsx +14 -2
  283. package/src/shell/app-shell-labels.ts +1 -0
  284. package/src/shell/app-shell.tsx +291 -68
  285. package/src/shell/language-code.ts +5 -0
  286. package/src/shell/topbar-controls.tsx +35 -15
  287. package/src/testing/css-variables.generated.ts +6 -6
  288. package/src/theme/chart-palette.ts +102 -20
  289. package/src/theme/palette-presets.ts +121 -84
  290. package/src/tour/tour.tsx +1 -1
  291. package/src/wizard/stepper-nav.tsx +4 -4
  292. package/tokens.css +28 -18
@@ -12,12 +12,14 @@ import { useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
12
12
  import type { ComponentProps, ComponentPropsWithoutRef, KeyboardEvent, ReactNode } from "react";
13
13
  import { Treemap as RechartsTreemap } from "recharts";
14
14
  import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "./chart";
15
- import { PALETTE_HEX, textOn } from "../theme/chart-palette";
15
+ import { PALETTE_HEX, PALETTE_SIZE, textOn } from "../theme/chart-palette";
16
+ import { resolveChartColor, useChartEdge } from "./chart-edge";
16
17
  import { parseHex } from "../theme/color";
17
18
  import { dirOf, type Direction } from "../lib/direction";
18
19
  import { cn } from "../lib/cn";
19
20
  import { useTextSize } from "../theme/text-size";
20
21
  import { chartHeightProps, type ChartHeight } from "./chart-height";
22
+ import { formatWithMinus } from "../lib/minus";
21
23
 
22
24
  /**
23
25
  * One tile.
@@ -39,7 +41,9 @@ export interface TreemapNode {
39
41
  value: number;
40
42
  /** Overrides the by-index palette colour for this tile — e.g. to paint every child
41
43
  * in its parent group's colour, so the groups stay legible in one picture. A
42
- * concrete `#rrggbb` or a bare `var(--chart-N)` keeps the label ink measured;
44
+ * concrete `#rrggbb` or a bare `var(--chart-N)` keeps the label ink measured — so
45
+ * does `CHART_ROLES.other` (`var(--chart-muted)`), the colour for a "rest" bucket,
46
+ * which should not wrap round to a hue the reader matches to a real node;
43
47
  * anything else (another var, a `color-mix()`) gets `currentColor` ink, because
44
48
  * there is nothing to measure — set `labelColor` then. */
45
49
  fill?: string;
@@ -101,7 +105,8 @@ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "chi
101
105
  maxTiles?: number;
102
106
  /**
103
107
  * The categorical ramp tiles take by index, as concrete hex. Default: the page's
104
- * live `--chart-1…9` tokens (see `useChartTokenHex`). Pass one when the host
108
+ * live `--chart-1…8` tokens (see `useChartTokenHex`), cycled after eight like
109
+ * `paletteFor` (0.49). Pass one when the host
105
110
  * already holds the active palette — e.g. `createPaletteStore`'s `useChartHex()` —
106
111
  * so a caller colouring a legend beside the map is guaranteed the same list.
107
112
  */
@@ -119,7 +124,7 @@ export interface TreemapProps extends Omit<ComponentPropsWithoutRef<"div">, "chi
119
124
  // re-read whenever <html>'s class or style changes — which is exactly when a theme
120
125
  // toggle or a palette switch lands.
121
126
 
122
- const CHART_TOKENS = 9;
127
+ const CHART_TOKENS = PALETTE_SIZE;
123
128
 
124
129
  /** The resolved ramp, joined into ONE string so `useSyncExternalStore` compares a
125
130
  * primitive: a fresh array per read would never be `Object.is`-equal to the last and
@@ -163,9 +168,12 @@ const CHART_TOKEN_REF = /^var\(\s*--chart-(\d+)\s*\)$/;
163
168
  function measurableFill(fill: string, tokens: readonly string[]): string | null {
164
169
  if (parseHex(fill)) return fill;
165
170
  const m = CHART_TOKEN_REF.exec(fill.trim());
166
- if (!m) return null;
167
- const hex = tokens[Number(m[1]) - 1];
168
- return hex && parseHex(hex) ? hex : null;
171
+ const hex = m ? tokens[Number(m[1]) - 1] : undefined;
172
+ if (hex && parseHex(hex)) return hex;
173
+ // Any other bare token the page declares — `var(--chart-muted)` for a rest bucket,
174
+ // `var(--chart-9)`, a money colour — read off <html> as it resolves.
175
+ if (typeof document === "undefined") return null;
176
+ return resolveChartColor(fill, getComputedStyle(document.documentElement));
169
177
  }
170
178
 
171
179
  // ── Tile ──────────────────────────────────────────────────────────────────
@@ -241,6 +249,7 @@ export function TreemapCell({
241
249
  // Before the early return: a hook has to run on every render of the cell.
242
250
  const tokenHex = useChartTokenHex();
243
251
  const { scale } = useTextSize();
252
+ const edgeFor = useChartEdge();
244
253
  // Depth 0 is recharts' synthetic root, which spans the whole chart.
245
254
  if (!depth) return null;
246
255
  // Whole pixels: a rect on a half pixel renders its edge across two half-lit
@@ -252,6 +261,9 @@ export function TreemapCell({
252
261
  const tileFill = fill ?? "var(--chart-1)";
253
262
  const measured = measurableFill(tileFill, tokenHex);
254
263
  const ink = labelColor ?? (measured ? textOn(measured) : "currentColor");
264
+ // A tile in a hue under 3:1 against the card gets a 1px ink edge inside the gutter
265
+ // (WCAG 1.4.11, anyplot's rule); a hue that clears it stays bare.
266
+ const edge = measured ? edgeFor(measured) : undefined;
255
267
  // The halo only on an OPAQUE fill we could measure. A translucent fill (a
256
268
  // `color-mix(… transparent)`) composited twice — once as the tile, once as the halo
257
269
  // over it — draws a darker ring round every glyph, which read as doubled text on the
@@ -310,6 +322,20 @@ export function TreemapCell({
310
322
  strokeWidth={2}
311
323
  rx={3}
312
324
  />
325
+ {edge && pw > 3 && ph > 3 && (
326
+ <rect
327
+ data-edge=""
328
+ x={px + 1.5}
329
+ y={py + 1.5}
330
+ width={pw - 3}
331
+ height={ph - 3}
332
+ fill="none"
333
+ stroke={edge}
334
+ strokeWidth={1}
335
+ rx={2}
336
+ style={{ pointerEvents: "none" }}
337
+ />
338
+ )}
313
339
  {label != null && (
314
340
  <text
315
341
  // A plain attribute, so the host's `[data-private]` rule reaches an SVG
@@ -437,7 +463,7 @@ export function Treemap({
437
463
  const total = nodes.reduce((acc, n) => acc + n.size, 0);
438
464
  const format = (v: number): ReactNode => {
439
465
  const share = total > 0 ? v / total : undefined;
440
- return valueFormatter ? valueFormatter(v, share) : DEFAULT_NUMBER.format(v);
466
+ return valueFormatter ? valueFormatter(v, share) : formatWithMinus(DEFAULT_NUMBER, v);
441
467
  };
442
468
  const Content = redactNames ? RedactedTooltipContent : ChartTooltipContent;
443
469
  return (
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import type { SetStateAction } from "react";
3
+ import * as ReactRouter from "react-router";
3
4
  import { useLocation, useSearchParams } from "react-router";
4
5
  import { useKitTableStorageKeyPrefix } from "../i18n/kit-labels";
5
6
  import { readStored, writeStored } from "../lib/safe-storage";
@@ -103,6 +104,62 @@ function urlKeys(prefix = ""): UrlKeys {
103
104
  };
104
105
  }
105
106
 
107
+ /** What a table reads and writes the URL through: react-router's, or none. */
108
+ interface TableSearch {
109
+ searchParams: URLSearchParams;
110
+ setSearchParams: ReturnType<typeof useSearchParams>[1];
111
+ locationState: unknown;
112
+ }
113
+
114
+ function useRouterSearch(): TableSearch {
115
+ const [searchParams, setSearchParams] = useSearchParams();
116
+ const location = useLocation();
117
+ return { searchParams, setSearchParams, locationState: location.state };
118
+ }
119
+
120
+ const NO_ROUTER_SEARCH: TableSearch = {
121
+ searchParams: new URLSearchParams(),
122
+ setSearchParams: () => {},
123
+ locationState: null,
124
+ };
125
+ function useNoRouterSearch(): TableSearch {
126
+ return NO_ROUTER_SEARCH;
127
+ }
128
+
129
+ /**
130
+ * `useInRouterContext`, read off the namespace inside a `try` like
131
+ * use-search-param-state's probe: a test's `vi.mock("react-router", () => ({ … }))`
132
+ * without it throws on the read. Missing, a table assumes a Router, as before 0.49.
133
+ */
134
+ function readInRouterContext(): () => boolean {
135
+ try {
136
+ return (ReactRouter as unknown as { useInRouterContext?: () => boolean }).useInRouterContext ?? (() => true);
137
+ } catch {
138
+ return () => true;
139
+ }
140
+ }
141
+ const useInRouterContext = readInRouterContext();
142
+
143
+ /**
144
+ * The URL access a table needs (0.49, KIT-3). Without `urlSync` a table needs no Router:
145
+ * before 0.49 it called `useSearchParams()` unconditionally and threw outside one even
146
+ * with URL sync off. With `urlSync` and no Router, a development build throws a message
147
+ * that names the fix; a production build keeps the view in local state rather than
148
+ * crash the page. Whether a Router is around does not change during a table's life, so
149
+ * which hook runs is stable from render to render.
150
+ */
151
+ function useTableSearch(urlSync: boolean, owner: string): TableSearch {
152
+ const inRouter = useInRouterContext();
153
+ if (!inRouter && urlSync && import.meta.env?.DEV) {
154
+ throw new Error(
155
+ `${owner} urlSync requires a Router: render it inside a react-router Router ` +
156
+ "(BrowserRouter, a data router or a MemoryRouter), or leave urlSync off.",
157
+ );
158
+ }
159
+ const useSearch = inRouter ? useRouterSearch : useNoRouterSearch;
160
+ return useSearch();
161
+ }
162
+
106
163
  /** `urlSync` split into "is it on" and "under which prefix". */
107
164
  function resolveTableUrlSync(urlSync: TableUrlSync | undefined): { enabled: boolean; prefix: string } {
108
165
  if (!urlSync) return { enabled: false, prefix: "" };
@@ -295,10 +352,9 @@ export function useTableState<T>({
295
352
  }) {
296
353
  const { enabled: urlSync, prefix: urlPrefix } = resolveTableUrlSync(urlSyncOption);
297
354
  const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);
298
- // Called unconditionally (a Router is required either way — see src/data-table.ts),
299
- // and the SAME pair is used to read and to write; see `readTableUrlState`.
300
- const [searchParams, setSearchParams] = useSearchParams();
301
- const location = useLocation();
355
+ // The SAME pair is used to read and to write; see `readTableUrlState`. No Router is
356
+ // needed without `urlSync` (0.49, KIT-3; see `useTableSearch`).
357
+ const { searchParams, setSearchParams, locationState } = useTableSearch(urlSync, "DataTable");
302
358
  // Read URL once on mount so URL-encoded views (e.g. shared links, deep links from
303
359
  // other pages) populate initial state. After mount, state is local.
304
360
  const [urlInitial] = useState<TableUrlState>(() =>
@@ -436,7 +492,7 @@ export function useTableState<T>({
436
492
  // `columns` is deliberately not a dependency and does not need to be: the
437
493
  // signature above is computed from it, so a column set that would produce a
438
494
  // different address produces a different signature first.
439
- writeUrlState(setSearchParams, columns, sorts, filters, pageSize, page, defaultPageSize, undefined, location.state, keys);
495
+ writeUrlState(setSearchParams, columns, sorts, filters, pageSize, page, defaultPageSize, undefined, locationState, keys);
440
496
  // eslint-disable-next-line react-hooks/exhaustive-deps
441
497
  }, [urlSync, urlSig]);
442
498
 
@@ -620,8 +676,7 @@ export function useTableUrlState<T>({
620
676
  const urlSync = resolved.enabled;
621
677
  const urlPrefix = urlPrefixOption ?? resolved.prefix;
622
678
  const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);
623
- const [searchParams, setSearchParams] = useSearchParams();
624
- const location = useLocation();
679
+ const { searchParams, setSearchParams, locationState } = useTableSearch(urlSync, "useTableUrlState");
625
680
  const [state, setState] = useState<OwnedState>(() => {
626
681
  const url = urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {};
627
682
  return {
@@ -653,7 +708,7 @@ export function useTableUrlState<T>({
653
708
  state.page,
654
709
  defaultPageSize,
655
710
  manageSearch ? state.search : undefined,
656
- location.state,
711
+ locationState,
657
712
  keys,
658
713
  );
659
714
  // Keyed on the signature, which is computed from everything the write reads — see
package/src/data-table.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  // `@eifi1/ui-kit/data-table` — the table suite.
2
2
  //
3
- // A re-slicing of the main barrel, not a new API. This entry point requires
4
- // `react-router`: DataTable calls useSearchParams unconditionally, above any `urlSync`
5
- // branch, so a Router is needed even with URL sync switched off.
3
+ // A re-slicing of the main barrel, not a new API. This entry point imports
4
+ // `react-router` (a peer dependency), but since 0.49 (KIT-3) a Router is needed only for
5
+ // a table with `urlSync`: without it DataTable keeps its view in local state alone.
6
6
  export * from "./components/data-table-labels";
7
7
  export * from "./components/data-table-sort";
8
8
  export * from "./components/data-table-filters";
@@ -116,7 +116,7 @@ export function DemoBanner({
116
116
  <>
117
117
  {!collapsed && (
118
118
  <>
119
- <Button href={accessLink.href} variant="brand" size="sm">
119
+ <Button href={accessLink.href} variant="accent" size="sm">
120
120
  {accessLink.label}
121
121
  </Button>
122
122
  <Button href={signInHref} variant="secondary" size="sm">
@@ -75,7 +75,7 @@ export function DemoEnded({
75
75
  </p>
76
76
  {children}
77
77
  <div className="mt-4 flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:flex-wrap sm:justify-center">
78
- <Button href={restartHref} variant="brand">
78
+ <Button href={restartHref} variant="accent">
79
79
  {labels.restart}
80
80
  </Button>
81
81
  <Button href={accessLink.href} variant="secondary">
@@ -139,7 +139,7 @@ export function DemoStart<T>({
139
139
  </div>
140
140
  {refusal !== null && (
141
141
  <div className="flex flex-wrap justify-center gap-3">
142
- <Button href={accessLink.href} variant="brand">
142
+ <Button href={accessLink.href} variant="accent">
143
143
  {accessLink.label}
144
144
  </Button>
145
145
  <Button href={backHref} variant="secondary">
@@ -469,7 +469,7 @@ export function FeedbackNoteEditor({
469
469
  <Button variant="ghost" onClick={onCancel}>
470
470
  {cancelLabel}
471
471
  </Button>
472
- <Button variant="brand" disabled={pending || blank} commit={commit} onClick={submit}>
472
+ <Button variant="accent" disabled={pending || blank} commit={commit} onClick={submit}>
473
473
  {saveLabel}
474
474
  </Button>
475
475
  </div>
@@ -1,4 +1,4 @@
1
- import { useCallback, useEffect, useRef } from "react";
1
+ import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
2
2
  import type { KeyboardEvent as ReactKeyboardEvent, RefObject } from "react";
3
3
  import { isComposing } from "../lib/ime";
4
4
 
@@ -60,6 +60,9 @@ import { isComposing } from "../lib/ime";
60
60
  /** A layer's handler, read through a ref so the stack holds one stable entry per open. */
61
61
  interface Layer {
62
62
  escape: (e: KeyboardEvent) => void;
63
+ /** Whether it covers the page — a dialog, a sheet, a popover, a list — rather than
64
+ * annotating it (a tooltip). Counted by {@link useOverlayOpen}. */
65
+ overlay: boolean;
63
66
  }
64
67
 
65
68
  /** What {@link useDismissLayer} hands back: its layer while open, for
@@ -114,11 +117,13 @@ function push(layer: Layer): void {
114
117
  document.addEventListener("keydown", onBubble);
115
118
  }
116
119
  layers.push(layer);
120
+ if (layer.overlay) notifyOverlays();
117
121
  }
118
122
 
119
123
  function pop(layer: Layer): void {
120
124
  const at = layers.indexOf(layer);
121
125
  if (at >= 0) layers.splice(at, 1);
126
+ if (at >= 0 && layer.overlay) notifyOverlays();
122
127
  if (layers.length > 0) return;
123
128
  document.removeEventListener("keydown", onCapture, true);
124
129
  document.removeEventListener("keydown", onBubble);
@@ -135,7 +140,11 @@ function pop(layer: Layer): void {
135
140
  * read live, so it need not be memoised; the layer is pushed and popped only when
136
141
  * `open` flips. The layer it returns is for the owner's {@link runOwnKeyDown}.
137
142
  */
138
- export function useDismissLayer(open: boolean, onEscape: (e: KeyboardEvent) => void): DismissLayer {
143
+ export function useDismissLayer(
144
+ open: boolean,
145
+ onEscape: (e: KeyboardEvent) => void,
146
+ { overlay = true }: { overlay?: boolean } = {},
147
+ ): DismissLayer {
139
148
  const handlerRef = useRef(onEscape);
140
149
  const layerRef = useRef<Layer | null>(null);
141
150
  // Written in an effect, not during render, so it satisfies react-hooks/refs: a
@@ -148,17 +157,47 @@ export function useDismissLayer(open: boolean, onEscape: (e: KeyboardEvent) => v
148
157
  });
149
158
  useEffect(() => {
150
159
  if (!open || typeof document === "undefined") return;
151
- const layer: Layer = { escape: (e) => handlerRef.current(e) };
160
+ const layer: Layer = { escape: (e) => handlerRef.current(e), overlay };
152
161
  layerRef.current = layer;
153
162
  push(layer);
154
163
  return () => {
155
164
  pop(layer);
156
165
  layerRef.current = null;
157
166
  };
158
- }, [open]);
167
+ }, [open, overlay]);
159
168
  return layerRef;
160
169
  }
161
170
 
171
+ /*
172
+ * Whether an overlay is open (0.49 KIT-11): every layer on the stack but a tooltip's is a
173
+ * dialog, a sheet, a popover, a hover menu or a list covering part of the page, so the
174
+ * stack is also the one place that knows. The floating launchers (`hideWhenOverlay`) step
175
+ * aside while one is open instead of floating over it or beside it.
176
+ */
177
+ const overlayListeners = new Set<() => void>();
178
+
179
+ function notifyOverlays(): void {
180
+ for (const listener of overlayListeners) listener();
181
+ }
182
+
183
+ function subscribeOverlays(listener: () => void): () => void {
184
+ overlayListeners.add(listener);
185
+ return () => {
186
+ overlayListeners.delete(listener);
187
+ };
188
+ }
189
+
190
+ function anyOverlayOpen(): boolean {
191
+ return layers.some((layer) => layer.overlay);
192
+ }
193
+
194
+ /** True while a kit dialog, sheet, popover, hover menu or list is open (any layer of the
195
+ * Escape stack but a tooltip's). Internal. */
196
+ export function useOverlayOpen(enabled = true): boolean {
197
+ const open = useSyncExternalStore(subscribeOverlays, anyOverlayOpen, () => false);
198
+ return enabled && open;
199
+ }
200
+
162
201
  /**
163
202
  * For a layer's own element, in its `onKeyDown`: hands an Escape on its way up to the
164
203
  * topmost layer — this one, unless something opened over it without taking focus (a
@@ -0,0 +1,31 @@
1
+ import { useEffect } from "react";
2
+ import { useKitDocumentTitle } from "../i18n/kit-labels";
3
+ import { DEFAULT_DOCUMENT_TITLE_FORMAT, formatDocumentTitle } from "../lib/document-title";
4
+
5
+ /**
6
+ * Writes the window's title (`document.title`) while the calling component is mounted,
7
+ * and puts back the title it found when it unmounts (0.49 KIT-9).
8
+ *
9
+ * `parts` are positional: `[page, section, app]`. The app falls back to the provider's
10
+ * `appName`, the format to the provider's `documentTitleFormat` (default
11
+ * "{page} · {section} · {app}"); a missing part drops with its separator
12
+ * ({@link formatDocumentTitle}). Parts that are ALL empty write nothing — the provider's
13
+ * app alone is not a page's title.
14
+ *
15
+ * `PageHeader` calls it for its `h1` (`documentTitle`); call it yourself for a page
16
+ * without one.
17
+ */
18
+ export function useDocumentTitle(parts: ReadonlyArray<string | null | undefined | false>): void {
19
+ const { format, appName } = useKitDocumentTitle();
20
+ const [page, section, app] = parts.map((p) => (typeof p === "string" && p.trim() !== "" ? p : undefined));
21
+ const any = parts.some((p) => typeof p === "string" && p.trim() !== "");
22
+ const title = any ? formatDocumentTitle(format ?? DEFAULT_DOCUMENT_TITLE_FORMAT, { page, section, app: app ?? appName }) : null;
23
+ useEffect(() => {
24
+ if (title === null || title === "") return;
25
+ const before = document.title;
26
+ document.title = title;
27
+ return () => {
28
+ document.title = before;
29
+ };
30
+ }, [title]);
31
+ }
@@ -556,6 +556,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
556
556
  theme: "Theme wechseln",
557
557
  palette: "Darstellungsvorlage",
558
558
  language: "Sprache",
559
+ languageCurrent: (name) => `Sprache wählen, aktuell: ${name}`,
559
560
  switchRole: "Rolle wechseln",
560
561
  role: (value) => `Rolle: ${value}`,
561
562
  },
@@ -1929,10 +1930,13 @@ export function germanLabels(numberLocale: string): UiKitLabels {
1929
1930
  rowActions: {
1930
1931
  actions: "Aktionen",
1931
1932
  actionsFor: (name) => `Aktionen für ${name}`,
1933
+ confirmTitle: (action, name) => (name ? `${action}: «${name}»?` : `${action}?`),
1934
+ confirmBody: "Das lässt sich nicht rückgängig machen.",
1932
1935
  },
1933
1936
  appShellMore: {
1934
1937
  more: "Mehr",
1935
1938
  moreTitle: "Weitere Seiten",
1939
+ overview: "Übersicht",
1936
1940
  },
1937
1941
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Verwaltung"
1938
1942
  // as settings §5 names the page; "mit Administratorrechten" rather than "für
@@ -283,6 +283,9 @@ export interface TopBarLabels {
283
283
  theme: string;
284
284
  palette: string;
285
285
  language: string;
286
+ /** 0.49 KIT-10: the language menu's trigger, given the current language's name —
287
+ * "Choose language, current: Deutsch". */
288
+ languageCurrent: (name: string) => string;
286
289
  switchRole: string;
287
290
  /** The role switcher's tooltip, given the active role's name. */
288
291
  role: (value: string) => string;
@@ -557,6 +560,8 @@ interface KitI18n {
557
560
  formatDate?: KitDateFormatter;
558
561
  tableStorageKeyPrefix?: string;
559
562
  timeZone?: string;
563
+ documentTitleFormat?: string;
564
+ appName?: string;
560
565
  }
561
566
 
562
567
  /** What a {@link KitDateFormatter} is told about the date it is formatting. */
@@ -699,6 +704,25 @@ export interface UiKitProviderProps {
699
704
  * it names in every zone). Left out, timestamps follow the device, as before.
700
705
  */
701
706
  timeZone?: string;
707
+ /**
708
+ * 0.49 KIT-9: how `useDocumentTitle` and every `PageHeader`'s `h1` write the window's
709
+ * title, from `{page}`, `{section}` and `{app}`. Default
710
+ * `"{page} · {section} · {app}"` (`DEFAULT_DOCUMENT_TITLE_FORMAT`); a missing part
711
+ * drops with its separator.
712
+ */
713
+ documentTitleFormat?: string;
714
+ /** 0.49 KIT-9: the app's name, the `{app}` of the window's title ("Kastlan"). Left
715
+ * out, titles end at the page or section. */
716
+ appName?: string;
717
+ /**
718
+ * 0.49 KIT-1b: while a person copies a selection in the page, a U+2212 minus standing
719
+ * before a number ("−CHF 1,250.50") goes to the clipboard as "-", in `text/plain` and
720
+ * `text/html` (what Excel pastes), so a spreadsheet reads a number; the page keeps "−".
721
+ * A copy out of an input, a textarea or an editable element is left alone. Installed
722
+ * once per document, by the first provider that has it on. Default `true`; `false` on
723
+ * the outermost provider leaves the clipboard to the browser.
724
+ */
725
+ copyAsciiMinus?: boolean;
702
726
  children: ReactNode;
703
727
  }
704
728
 
@@ -719,6 +743,9 @@ export function UiKitProvider({
719
743
  formatDate,
720
744
  tableStorageKeyPrefix,
721
745
  timeZone,
746
+ documentTitleFormat,
747
+ appName,
748
+ copyAsciiMinus = true,
722
749
  children,
723
750
  }: UiKitProviderProps) {
724
751
  const outer = useContext(KitI18nContext);
@@ -732,10 +759,25 @@ export function UiKitProvider({
732
759
  formatDate: formatDate ?? outer.formatDate,
733
760
  tableStorageKeyPrefix: tableStorageKeyPrefix ?? outer.tableStorageKeyPrefix,
734
761
  timeZone: timeZone ?? outer.timeZone,
762
+ documentTitleFormat: documentTitleFormat ?? outer.documentTitleFormat,
763
+ appName: appName ?? outer.appName,
735
764
  }),
736
- [outer, labels, locale, weekStartsOn, linkComponent, chartTooltipPlacement, formatDate, tableStorageKeyPrefix, timeZone],
765
+ [
766
+ outer,
767
+ labels,
768
+ locale,
769
+ weekStartsOn,
770
+ linkComponent,
771
+ chartTooltipPlacement,
772
+ formatDate,
773
+ tableStorageKeyPrefix,
774
+ timeZone,
775
+ documentTitleFormat,
776
+ appName,
777
+ ],
737
778
  );
738
779
  useTableViewsJoin(value.tableStorageKeyPrefix ?? DEFAULT_TABLE_STORAGE_KEY_PREFIX);
780
+ useClipboardMinus(copyAsciiMinus);
739
781
  return <KitI18nContext.Provider value={value}>{children}</KitI18nContext.Provider>;
740
782
  }
741
783
 
@@ -773,6 +815,30 @@ function useTableViewsJoin(prefix: string): void {
773
815
  }, [prefix]);
774
816
  }
775
817
 
818
+ /**
819
+ * The copy handler (lib/clipboard-minus, 0.49 KIT-1b), through a literal `import()` for
820
+ * the reason {@link useTableViewsJoin} gives: a catalogue may reach nothing outside i18n/.
821
+ * It is in place a microtask after the mount — long before a person selects and copies.
822
+ */
823
+ function useClipboardMinus(on: boolean): void {
824
+ useEffect(() => {
825
+ if (!on || typeof document === "undefined") return;
826
+ let uninstall: (() => void) | undefined;
827
+ let gone = false;
828
+ import("../lib/clipboard-minus").then(
829
+ ({ installClipboardMinus }) => {
830
+ if (!gone) uninstall = installClipboardMinus(document);
831
+ },
832
+ // Not loaded: copying works as the browser does it.
833
+ () => undefined,
834
+ );
835
+ return () => {
836
+ gone = true;
837
+ uninstall?.();
838
+ };
839
+ }, [on]);
840
+ }
841
+
776
842
  function isRecord(v: unknown): v is Record<string, unknown> {
777
843
  return typeof v === "object" && v !== null && !Array.isArray(v);
778
844
  }
@@ -892,6 +958,15 @@ export function useKitTimeZone(): string | undefined {
892
958
  return useContext(KitI18nContext).timeZone;
893
959
  }
894
960
 
961
+ /** The nearest provider's `documentTitleFormat` and `appName`, or `undefined` each —
962
+ * what `useDocumentTitle` writes with (the format's default is
963
+ * `DEFAULT_DOCUMENT_TITLE_FORMAT`, applied there: this module reaches nothing outside
964
+ * i18n/). */
965
+ export function useKitDocumentTitle(): { format: string | undefined; appName: string | undefined } {
966
+ const { documentTitleFormat, appName } = useContext(KitI18nContext);
967
+ return { format: documentTitleFormat, appName };
968
+ }
969
+
895
970
  /** {@link DEFAULT_FILE_LABELS}, but formatting in the provider's locale. */
896
971
  export function useKitFileLabels(prop?: Partial<FileLabels>): FileLabels {
897
972
  const locale = useKitLocale();
@@ -543,6 +543,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
543
543
  theme: "Cambiar de tema",
544
544
  palette: "Ajuste de apariencia",
545
545
  language: "Idioma",
546
+ languageCurrent: (name) => `Elegir idioma, actual: ${name}`,
546
547
  switchRole: "Cambiar de rol",
547
548
  role: (value) => `Rol: ${value}`,
548
549
  },
@@ -1869,10 +1870,13 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
1869
1870
  rowActions: {
1870
1871
  actions: "Acciones",
1871
1872
  actionsFor: (name) => `Acciones de ${name}`,
1873
+ confirmTitle: (action, name) => (name ? `¿${action} «${name}»?` : `¿${action}?`),
1874
+ confirmBody: "Esta acción no se puede deshacer.",
1872
1875
  },
1873
1876
  appShellMore: {
1874
1877
  more: "Más",
1875
1878
  moreTitle: "Más páginas",
1879
+ overview: "Resumen",
1876
1880
  },
1877
1881
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Usted",
1878
1882
  // as the rest of the catalogue.
@@ -551,6 +551,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
551
551
  theme: "Changer de thème",
552
552
  palette: "Préréglage d’apparence",
553
553
  language: "Langue",
554
+ languageCurrent: (name) => `Choisir la langue, actuelle\u00a0: ${name}`,
554
555
  switchRole: "Changer de rôle",
555
556
  role: (value) => `Rôle\u00a0: ${value}`,
556
557
  },
@@ -1909,10 +1910,13 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
1909
1910
  rowActions: {
1910
1911
  actions: "Actions",
1911
1912
  actionsFor: (name) => `Actions pour ${name}`,
1913
+ confirmTitle: (action, name) => (name ? `${action} «\u00a0${name}\u00a0»\u00a0?` : `${action}\u00a0?`),
1914
+ confirmBody: "Cette action est irréversible.",
1912
1915
  },
1913
1916
  appShellMore: {
1914
1917
  more: "Plus",
1915
1918
  moreTitle: "Autres pages",
1919
+ overview: "Vue d’ensemble",
1916
1920
  },
1917
1921
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words.
1918
1922
  mailTestCard: {
@@ -541,6 +541,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
541
541
  theme: "Téma váltása",
542
542
  palette: "Megjelenési séma",
543
543
  language: "Nyelv",
544
+ languageCurrent: (name) => `Nyelv kiválasztása, jelenlegi: ${name}`,
544
545
  switchRole: "Szerepkör váltása",
545
546
  role: (value) => `Szerepkör: ${value}`,
546
547
  },
@@ -1875,10 +1876,13 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
1875
1876
  rowActions: {
1876
1877
  actions: "Műveletek",
1877
1878
  actionsFor: (name) => `${name} – műveletek`,
1879
+ confirmTitle: (action, name) => (name ? `${action}: „${name}”?` : `${action}?`),
1880
+ confirmBody: "Ez nem vonható vissza.",
1878
1881
  },
1879
1882
  appShellMore: {
1880
1883
  more: "Továbbiak",
1881
1884
  moreTitle: "További oldalak",
1885
+ overview: "Áttekintés",
1882
1886
  },
1883
1887
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Ön", as
1884
1888
  // the rest of the catalogue.
@@ -546,6 +546,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
546
546
  theme: "Cambia tema",
547
547
  palette: "Preset di aspetto",
548
548
  language: "Lingua",
549
+ languageCurrent: (name) => `Scegli la lingua, attuale: ${name}`,
549
550
  switchRole: "Cambia ruolo",
550
551
  role: (value) => `Ruolo: ${value}`,
551
552
  },
@@ -1888,10 +1889,13 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
1888
1889
  rowActions: {
1889
1890
  actions: "Azioni",
1890
1891
  actionsFor: (name) => `Azioni per ${name}`,
1892
+ confirmTitle: (action, name) => (name ? `${action} «${name}»?` : `${action}?`),
1893
+ confirmBody: "Questa azione non può essere annullata.",
1891
1894
  },
1892
1895
  appShellMore: {
1893
1896
  more: "Altro",
1894
1897
  moreTitle: "Altre pagine",
1898
+ overview: "Panoramica",
1895
1899
  },
1896
1900
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words. "Lei", as
1897
1901
  // the rest of the catalogue.
@@ -529,6 +529,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
529
529
  theme: "切换主题",
530
530
  palette: "外观预设",
531
531
  language: "语言",
532
+ languageCurrent: (name) => `选择语言,当前:${name}`,
532
533
  switchRole: "切换角色",
533
534
  role: (value) => `角色:${value}`,
534
535
  },
@@ -1795,10 +1796,13 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
1795
1796
  rowActions: {
1796
1797
  actions: "操作",
1797
1798
  actionsFor: (name) => `${name} 的操作`,
1799
+ confirmTitle: (action, name) => (name ? `${action}“${name}”?` : `${action}?`),
1800
+ confirmBody: "此操作无法撤销。",
1798
1801
  },
1799
1802
  appShellMore: {
1800
1803
  more: "更多",
1801
1804
  moreTitle: "更多页面",
1805
+ overview: "概览",
1802
1806
  },
1803
1807
  // 0.42.0 — docs/admin-harmonization.md §3.1: the admin area's shared words.
1804
1808
  mailTestCard: {