@eifi1/ui-kit 0.51.2 → 0.51.4

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 (96) hide show
  1. package/README.md +5 -5
  2. package/dist/components/combobox-core.js +4 -5
  3. package/dist/components/combobox-core.js.map +1 -1
  4. package/dist/components/currency-option-list.js +3 -3
  5. package/dist/components/currency-option-list.js.map +1 -1
  6. package/dist/components/data-table-fit.js +7 -5
  7. package/dist/components/data-table-fit.js.map +1 -1
  8. package/dist/components/data-table-head.js +5 -1
  9. package/dist/components/data-table-head.js.map +1 -1
  10. package/dist/components/data-table.d.ts +8 -0
  11. package/dist/components/data-table.js.map +1 -1
  12. package/dist/components/grouped-picker.js +5 -4
  13. package/dist/components/grouped-picker.js.map +1 -1
  14. package/dist/components/icon-picker.js +5 -4
  15. package/dist/components/icon-picker.js.map +1 -1
  16. package/dist/components/inline-entity-combobox.js +4 -4
  17. package/dist/components/inline-entity-combobox.js.map +1 -1
  18. package/dist/components/legal-labels.js +9 -2
  19. package/dist/components/legal-labels.js.map +1 -1
  20. package/dist/components/legal-page.d.ts +35 -3
  21. package/dist/components/legal-page.js +22 -0
  22. package/dist/components/legal-page.js.map +1 -1
  23. package/dist/components/legal.d.ts +49 -5
  24. package/dist/components/legal.js +31 -3
  25. package/dist/components/legal.js.map +1 -1
  26. package/dist/components/list-labels.js +3 -1
  27. package/dist/components/list-labels.js.map +1 -1
  28. package/dist/components/list.d.ts +18 -1
  29. package/dist/components/list.js +32 -3
  30. package/dist/components/list.js.map +1 -1
  31. package/dist/eslint/index.js +1 -1
  32. package/dist/feedback/feedback-table.d.ts +3 -1
  33. package/dist/feedback/feedback-table.js +18 -4
  34. package/dist/feedback/feedback-table.js.map +1 -1
  35. package/dist/i18n/german.js +12 -3
  36. package/dist/i18n/german.js.map +1 -1
  37. package/dist/i18n/locales/en.js +16 -3
  38. package/dist/i18n/locales/en.js.map +2 -2
  39. package/dist/i18n/locales/es.js +12 -3
  40. package/dist/i18n/locales/es.js.map +1 -1
  41. package/dist/i18n/locales/fr.js +12 -3
  42. package/dist/i18n/locales/fr.js.map +1 -1
  43. package/dist/i18n/locales/hu.js +12 -3
  44. package/dist/i18n/locales/hu.js.map +1 -1
  45. package/dist/i18n/locales/it.js +12 -3
  46. package/dist/i18n/locales/it.js.map +1 -1
  47. package/dist/i18n/locales/zh.js +12 -3
  48. package/dist/i18n/locales/zh.js.map +1 -1
  49. package/dist/i18n/review.js +4 -0
  50. package/dist/i18n/review.js.map +1 -1
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/lib/text-match.d.ts +16 -0
  54. package/dist/lib/text-match.js +11 -1
  55. package/dist/lib/text-match.js.map +1 -1
  56. package/dist/settings/use-settings-focus.d.ts +7 -0
  57. package/dist/settings/use-settings-focus.js +2 -0
  58. package/dist/settings/use-settings-focus.js.map +1 -1
  59. package/dist/shell/dev-menu.d.ts +5 -0
  60. package/dist/shell/dev-menu.js +2 -1
  61. package/dist/shell/dev-menu.js.map +1 -1
  62. package/dist/shell/topbar-action-menu.d.ts +14 -1
  63. package/dist/shell/topbar-action-menu.js +85 -34
  64. package/dist/shell/topbar-action-menu.js.map +1 -1
  65. package/dist/shell.d.ts +1 -1
  66. package/dist/shell.js +2 -1
  67. package/dist/shell.js.map +1 -1
  68. package/package.json +1 -1
  69. package/src/components/combobox-core.tsx +5 -5
  70. package/src/components/currency-option-list.tsx +4 -5
  71. package/src/components/data-table-fit.ts +7 -5
  72. package/src/components/data-table-head.tsx +5 -1
  73. package/src/components/data-table.tsx +8 -0
  74. package/src/components/grouped-picker.tsx +15 -8
  75. package/src/components/icon-picker.tsx +6 -4
  76. package/src/components/inline-entity-combobox.tsx +5 -4
  77. package/src/components/legal-labels.ts +8 -1
  78. package/src/components/legal-page.tsx +72 -2
  79. package/src/components/legal.tsx +80 -5
  80. package/src/components/list-labels.ts +2 -0
  81. package/src/components/list.tsx +52 -2
  82. package/src/feedback/feedback-table.tsx +17 -1
  83. package/src/i18n/german.ts +10 -1
  84. package/src/i18n/locales/en.ts +4 -0
  85. package/src/i18n/locales/es.ts +10 -1
  86. package/src/i18n/locales/fr.ts +10 -1
  87. package/src/i18n/locales/hu.ts +10 -1
  88. package/src/i18n/locales/it.ts +10 -1
  89. package/src/i18n/locales/zh.ts +10 -1
  90. package/src/i18n/review.ts +4 -0
  91. package/src/index.ts +1 -0
  92. package/src/lib/text-match.ts +30 -0
  93. package/src/settings/use-settings-focus.ts +8 -0
  94. package/src/shell/dev-menu.tsx +6 -0
  95. package/src/shell/topbar-action-menu.tsx +122 -38
  96. package/src/shell.ts +1 -1
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useId, useState } from "react";
1
+ import { Children, createContext, useContext, useId, useState } from "react";
2
2
  import type { ComponentPropsWithoutRef, HTMLAttributes, MouseEvent, ReactElement, ReactNode, Ref } from "react";
3
3
  import { ChevronDown, ExternalLink } from "lucide-react";
4
4
  import type { LucideIcon } from "lucide-react";
@@ -14,6 +14,7 @@ import { pickLinkRenderer } from "./text-link";
14
14
  import { LockedReason, mergeDescribedBy, useLockReason, type DisabledReasonDisplay } from "./field-parts";
15
15
  import type { CommitScope } from "./write-lock";
16
16
  import { DEFAULT_LIST_LABELS } from "./list-labels";
17
+ import { Button } from "./button";
17
18
 
18
19
  /* ── Labels ───────────────────────────────────────────────────────────────── */
19
20
 
@@ -22,6 +23,12 @@ export interface ListLabels {
22
23
  unread: string;
23
24
  /** Read after an external row's title: the row leaves the app in a new tab. */
24
25
  opensInNewTab: string;
26
+ /** 0.51.4: the row under a {@link ListProps.limit}ed list that opens the rest, with
27
+ * the WHOLE list's count — "Show all (12)". Optional, as every key added to a
28
+ * namespace an app may annotate: left out, the provider's, then English. */
29
+ showAll?: (count: number) => string;
30
+ /** 0.51.4: the same row once the list is open, folding it back. */
31
+ showFewer?: string;
25
32
  }
26
33
 
27
34
  // Defined in ./list-labels, which holds no component code (0.34, KS U8).
@@ -53,8 +60,26 @@ export interface ListProps extends Omit<ComponentPropsWithoutRef<"ul">, "role">
53
60
  /** The `<ul>` (or `<ol>`). A React 19 ref prop since 0.34 (MOD-4, refactor audit
54
61
  * 2026-10); it was `forwardRef`. */
55
62
  ref?: Ref<HTMLUListElement>;
63
+ /**
64
+ * Show the first `limit` rows, then a "Show all (n)" row that opens the rest and turns
65
+ * into "Show fewer" (0.51.4, keksdose run 89: a long "Placed only roughly" list on its
66
+ * Matching report could only fold as a whole, so it opened as nothing or everything).
67
+ * A list of `limit` rows or fewer draws them all, with no such row. Rows are counted as
68
+ * `Children` counts them: one per `ListItem`, a fragment as one.
69
+ *
70
+ * The row is a button with `aria-expanded`, controlling the list; it sits in the list
71
+ * as `role="none"`, so a reader still hears "list, 5 items" for five rows.
72
+ */
73
+ limit?: number;
56
74
  }
57
75
 
76
+ /** The show-all row's inset, in step with each density's row padding (`PAD`). */
77
+ const MORE_PAD: Record<ListDensity, string> = {
78
+ compact: "px-2 py-1",
79
+ default: "px-3 py-1.5",
80
+ comfortable: "px-4 py-2",
81
+ };
82
+
58
83
  /**
59
84
  * The list of {@link ListItem}s.
60
85
  *
@@ -67,15 +92,23 @@ export function List({
67
92
  as = "ul",
68
93
  density = "default",
69
94
  separator = "gap",
95
+ limit,
70
96
  className,
71
97
  children,
72
98
  ...rest
73
99
  }: ListProps) {
74
100
  const Tag = as;
101
+ const labels = useKitLabels("list", DEFAULT_LIST_LABELS);
102
+ const [open, setOpen] = useState(false);
103
+ const generatedId = useId();
104
+ const id = rest.id ?? generatedId;
105
+ const rows = limit === undefined ? null : Children.toArray(children);
106
+ const folds = rows !== null && rows.length > Math.max(0, limit!);
75
107
  return (
76
108
  <ListContext.Provider value={{ density, separator }}>
77
109
  <Tag
78
110
  {...rest}
111
+ id={folds ? id : rest.id}
79
112
  ref={ref as Ref<HTMLUListElement & HTMLOListElement>}
80
113
  role="list"
81
114
  className={cn(
@@ -84,7 +117,24 @@ export function List({
84
117
  className,
85
118
  )}
86
119
  >
87
- {children}
120
+ {folds && !open ? rows.slice(0, Math.max(0, limit!)) : children}
121
+ {folds && (
122
+ <li role="none" className={MORE_PAD[density]}>
123
+ <Button
124
+ type="button"
125
+ variant="link"
126
+ size="sm"
127
+ className="h-auto p-0"
128
+ aria-expanded={open}
129
+ aria-controls={id}
130
+ onClick={() => setOpen((was) => !was)}
131
+ >
132
+ {open
133
+ ? (labels.showFewer ?? DEFAULT_LIST_LABELS.showFewer)
134
+ : (labels.showAll ?? DEFAULT_LIST_LABELS.showAll!)(rows.length)}
135
+ </Button>
136
+ </li>
137
+ )}
88
138
  </Tag>
89
139
  </ListContext.Provider>
90
140
  );
@@ -453,7 +453,9 @@ function statusRank(status: FeedbackStatus): number {
453
453
  * - Fitted to the table's width (0.48, keksdose live #451): Subject `grow`s and
454
454
  * ellipsises, and a narrow table leaves out URL, then Email, then Resolved
455
455
  * (`fitPriority` 1–3) — the open report repeats all three — then Votes and Category
456
- * (4, 5) before Subject would drop below 10rem. Drop the two keys with
456
+ * (4, 5), then User, Date and # (6–8, 0.51.4) before Subject would drop below 8rem
457
+ * (`growMin`, 0.51.4; the table's default floor is 10).
458
+ * Drop the two keys with
457
459
  * `{ ...col, grow: undefined, fitPriority: undefined }` for the 0.47 layout.
458
460
  * - `/my-feedback` shows the same nine: the Email cell's context fallback exists for it
459
461
  * (the admin list's join is absent there, §3.1), and its phone card drops the submitter
@@ -499,6 +501,9 @@ export function feedbackColumns<T extends FeedbackTableRow>({
499
501
  sortBy: (row) => row.id,
500
502
  filter: { type: "number", getValue: (row) => row.id },
501
503
  mobileHidden: true,
504
+ // The very last to go (0.51.4, Kurvenschmiede): replies and the feedback loop name a
505
+ // report by its number, so it stays while anything else can give way.
506
+ fitPriority: 8,
502
507
  },
503
508
  {
504
509
  key: "date",
@@ -506,6 +511,8 @@ export function feedbackColumns<T extends FeedbackTableRow>({
506
511
  cell: (row) => <FeedbackDate value={row.created_at} renderDate={renderDate} />,
507
512
  sortBy: (row) => instant(row.created_at),
508
513
  filter: { type: "date", getValue: (row) => row.created_at },
514
+ // Seventh to go (0.51.4): the open report shows it.
515
+ fitPriority: 7,
509
516
  },
510
517
  {
511
518
  key: "category",
@@ -536,6 +543,11 @@ export function feedbackColumns<T extends FeedbackTableRow>({
536
543
  // The rest of the row, one line and an ellipsis (0.48, keksdose live #451: one long
537
544
  // crash title pushed Status and the row actions off the edge).
538
545
  grow: true,
546
+ // 8rem, not the table's 10 (0.51.4, Kurvenschmiede): at 768px beside a sidebar only
547
+ // Subject, Status (a fixed 13.5rem) and the row actions are left, and they were up
548
+ // to 20px too wide (Hungarian). A subject's first words carry it; the open report
549
+ // has the rest.
550
+ growMin: 8,
539
551
  },
540
552
  {
541
553
  key: "user",
@@ -559,6 +571,10 @@ export function feedbackColumns<T extends FeedbackTableRow>({
559
571
  row.user_id == null
560
572
  ? labels.deletedUser
561
573
  : `${feedbackSubmitter(row, labels)} ${feedbackSubmitterEmail(row)}`,
574
+ // Sixth to go (0.51.4, Kurvenschmiede): with only 1–5, Subject reached its floor while
575
+ // #, Date and User were still drawn, and the inbox scrolled sideways from 768 to
576
+ // ~1115px beside an open sidebar. The open report names the submitter.
577
+ fitPriority: 6,
562
578
  },
563
579
  {
564
580
  key: "email",
@@ -782,6 +782,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
782
782
  list: {
783
783
  unread: "Ungelesen",
784
784
  opensInNewTab: "öffnet in einem neuen Tab",
785
+ showAll: (count) => `Alle anzeigen (${n(count)})`,
786
+ showFewer: "Weniger anzeigen",
785
787
  },
786
788
  breadcrumbs: {
787
789
  label: "Brotkrümelnavigation",
@@ -917,6 +919,8 @@ export function germanLabels(numberLocale: string): UiKitLabels {
917
919
  impressum: "Impressum",
918
920
  privacy: "Datenschutz",
919
921
  terms: "Nutzungsbedingungen",
922
+ security: "Sicherheit",
923
+ accessibility: "Barrierefreiheit",
920
924
  },
921
925
  titles: {
922
926
  impressum: "Impressum",
@@ -935,6 +939,11 @@ export function germanLabels(numberLocale: string): UiKitLabels {
935
939
  `Dies ist eine Übersetzung zu Ihrer Information. Massgebend ist die Fassung auf ${o.bindingLanguage}.`,
936
940
  show: (o) => `Fassung auf ${o.bindingLanguage} anzeigen`,
937
941
  },
942
+ updated: (date) => `Stand: ${date}`,
943
+ summary: {
944
+ title: "Das Wichtigste in Kürze",
945
+ note: "Diese Zusammenfassung dient der Orientierung; verbindlich ist allein der vollständige Text.",
946
+ },
938
947
  sections: {
939
948
  impressum: {
940
949
  operator: {
@@ -991,7 +1000,7 @@ export function germanLabels(numberLocale: string): UiKitLabels {
991
1000
  },
992
1001
  changes: {
993
1002
  title: "Änderungen dieser Bedingungen",
994
- body: "Diese Bedingungen können im Zuge der Weiterentwicklung des Dienstes angepasst werden. Wesentliche Änderungen werden per E-Mail oder in der App angekündigt; die weitere Nutzung nach einer Änderung gilt als Zustimmung.",
1003
+ body: "Wir können diese Bedingungen im Zuge der Weiterentwicklung des Dienstes anpassen. Wesentliche Änderungen kündigen wir mindestens 30 Tage vor ihrem Inkrafttreten per E-Mail und in der App an, zusammen mit dem neuen Text. Wenn Sie nicht einverstanden sind, können Sie vor diesem Datum widersprechen oder Ihr Konto löschen und bis dahin Ihre Daten exportieren. Nutzen Sie den Dienst nach diesem Datum weiter, gelten die geänderten Bedingungen. Änderungen, die nur zu Ihrem Vorteil sind oder gesetzlich vorgeschrieben sind, gelten ohne diese Ankündigung; ihnen kann nicht widersprochen werden. Widersprechen Sie einer Änderung, können wir Ihr Konto auf den Tag ihres Inkrafttretens schließen. Haben Sie einen Zeitraum bezahlt, der über dieses Datum hinausgeht, erstatten wir den nicht genutzten Teil.",
995
1004
  },
996
1005
  language: {
997
1006
  title: "Sprache",
@@ -95,6 +95,10 @@ export function uiKitLabelsEn(numberLocale = "en-US"): UiKitLabels {
95
95
  ...d.chipInput,
96
96
  atLimit: (max) => `Limit of ${n(max)} reached`,
97
97
  },
98
+ list: {
99
+ ...d.list,
100
+ showAll: (count) => `Show all (${n(count)})`,
101
+ },
98
102
  iconPicker: {
99
103
  ...d.iconPicker,
100
104
  resultCount: (count) => (count === 1 ? "1 icon" : `${n(count)} icons`),
@@ -765,6 +765,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
765
765
  list: {
766
766
  unread: "No leído",
767
767
  opensInNewTab: "se abre en una pestaña nueva",
768
+ showAll: (count) => `Mostrar todo (${n(count)})`,
769
+ showFewer: "Mostrar menos",
768
770
  },
769
771
  breadcrumbs: {
770
772
  label: "Ruta de navegación",
@@ -897,6 +899,8 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
897
899
  impressum: "Aviso legal",
898
900
  privacy: "Política de privacidad",
899
901
  terms: "Condiciones de uso",
902
+ security: "Seguridad",
903
+ accessibility: "Accesibilidad",
900
904
  },
901
905
  titles: {
902
906
  impressum: "Aviso legal",
@@ -915,6 +919,11 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
915
919
  `Esta es una traducción ofrecida para su comodidad; solo la versión en ${o.bindingLanguage} es vinculante.`,
916
920
  show: (o) => `Ver la versión en ${o.bindingLanguage}`,
917
921
  },
922
+ updated: (date) => `Última actualización: ${date}`,
923
+ summary: {
924
+ title: "En resumen",
925
+ note: "Este resumen es solo orientativo; únicamente el texto completo es vinculante.",
926
+ },
918
927
  sections: {
919
928
  impressum: {
920
929
  operator: {
@@ -971,7 +980,7 @@ export function uiKitLabelsEs(numberLocale = "es-ES"): UiKitLabels {
971
980
  },
972
981
  changes: {
973
982
  title: "Cambios en estas condiciones",
974
- body: "Estas condiciones pueden actualizarse a medida que evolucione el servicio. Los cambios importantes se anunciarán por correo electrónico o en la aplicación; si sigue usando el servicio después de un cambio, lo acepta.",
983
+ body: "Podemos actualizar estas condiciones a medida que evolucione el servicio. Anunciamos cualquier cambio importante al menos 30 días antes de que entre en vigor, por correo electrónico y en la aplicación, junto con el nuevo texto. Si no está de acuerdo, puede oponerse o eliminar su cuenta antes de esa fecha, y puede exportar sus datos hasta entonces. Si sigue usando el servicio después de esa fecha, se aplican las condiciones modificadas. Los cambios que solo le benefician, o que exige la ley, se aplican sin este preaviso y no admiten oposición. Si se opone a un cambio, podemos cerrar su cuenta en la fecha en que entre en vigor. Si ha pagado un periodo que va más allá de esa fecha, le reembolsamos la parte no utilizada.",
975
984
  },
976
985
  language: {
977
986
  title: "Idioma",
@@ -781,6 +781,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
781
781
  list: {
782
782
  unread: "Non lu",
783
783
  opensInNewTab: "s’ouvre dans un nouvel onglet",
784
+ showAll: (count) => `Tout afficher (${n(count)})`,
785
+ showFewer: "Afficher moins",
784
786
  },
785
787
  breadcrumbs: {
786
788
  label: "Fil d’Ariane",
@@ -916,6 +918,8 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
916
918
  impressum: "Mentions légales",
917
919
  privacy: "Confidentialité",
918
920
  terms: "Conditions d’utilisation",
921
+ security: "Sécurité",
922
+ accessibility: "Accessibilité",
919
923
  },
920
924
  titles: {
921
925
  impressum: "Mentions légales",
@@ -934,6 +938,11 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
934
938
  `Ceci est une traduction fournie pour votre commodité. Seule la version en ${o.bindingLanguage} fait foi.`,
935
939
  show: (o) => `Afficher la version en ${o.bindingLanguage}`,
936
940
  },
941
+ updated: (date) => `Dernière mise à jour\u00a0: ${date}`,
942
+ summary: {
943
+ title: "En bref",
944
+ note: "Ce résumé sert d’orientation\u202f; seul le texte complet fait foi.",
945
+ },
937
946
  sections: {
938
947
  impressum: {
939
948
  operator: {
@@ -990,7 +999,7 @@ export function uiKitLabelsFr(numberLocale = "fr-FR"): UiKitLabels {
990
999
  },
991
1000
  changes: {
992
1001
  title: "Modifications des présentes conditions",
993
- body: "Ces conditions peuvent être mises à jour au fil de l’évolution du service. Les modifications importantes seront annoncées par e-mail ou dans l’app\u202f; continuer à utiliser le service après une modification vaut acceptation.",
1002
+ body: "Nous pouvons mettre à jour ces conditions au fil de l’évolution du service. Nous annonçons toute modification importante au moins 30 jours avant son entrée en vigueur, par e-mail et dans l’app, avec le nouveau texte. Si vous n’êtes pas d’accord, vous pouvez vous y opposer ou supprimer votre compte avant cette date, et exporter vos données jusque-là. Si vous continuez à utiliser le service après cette date, les conditions modifiées s’appliquent. Les modifications qui sont uniquement à votre avantage, ou que la loi impose, s’appliquent sans ce préavis et ne peuvent pas faire l’objet d’une opposition. Si vous vous opposez à une modification, nous pouvons fermer votre compte à la date de son entrée en vigueur. Si vous avez payé une période qui s’étend au-delà de cette date, nous remboursons la part non utilisée.",
994
1003
  },
995
1004
  language: {
996
1005
  title: "Langue",
@@ -766,6 +766,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
766
766
  list: {
767
767
  unread: "Olvasatlan",
768
768
  opensInNewTab: "új lapon nyílik meg",
769
+ showAll: (count) => `Összes megjelenítése (${n(count)})`,
770
+ showFewer: "Kevesebb megjelenítése",
769
771
  },
770
772
  breadcrumbs: {
771
773
  label: "Morzsamenü",
@@ -901,6 +903,8 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
901
903
  impressum: "Impresszum",
902
904
  privacy: "Adatvédelmi tájékoztató",
903
905
  terms: "Felhasználási feltételek",
906
+ security: "Biztonság",
907
+ accessibility: "Akadálymentesség",
904
908
  },
905
909
  titles: {
906
910
  impressum: "Impresszum",
@@ -922,6 +926,11 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
922
926
  `Ez a szöveg tájékoztató jellegű fordítás; az irányadó változat nyelve: ${o.bindingLanguage}.`,
923
927
  show: (o) => `Az irányadó változat megjelenítése (${o.bindingLanguage})`,
924
928
  },
929
+ updated: (date) => `Utolsó frissítés: ${date}`,
930
+ summary: {
931
+ title: "Röviden",
932
+ note: "Ez az összefoglaló tájékoztató jellegű; kizárólag a teljes szöveg az irányadó.",
933
+ },
925
934
  sections: {
926
935
  impressum: {
927
936
  operator: {
@@ -978,7 +987,7 @@ export function uiKitLabelsHu(numberLocale = "hu-HU"): UiKitLabels {
978
987
  },
979
988
  changes: {
980
989
  title: "A feltételek módosítása",
981
- body: "Ezek a feltételek a szolgáltatás fejlődésével módosulhatnak. A lényeges változásokat e-mailben vagy az alkalmazásban jelentjük be; aki a változás után tovább használja a szolgáltatást, elfogadja azt.",
990
+ body: "Ezeket a feltételeket a szolgáltatás fejlődésével módosíthatjuk. A lényeges változásokat legalább 30 nappal a hatálybalépésük előtt bejelentjük e-mailben és az alkalmazásban, az új szöveggel együtt. Ha nem ért egyet, ezen időpont előtt tiltakozhat, vagy törölheti a fiókját, és addig exportálhatja az adatait. Ha ezen időpont után is használja a szolgáltatást, a módosított feltételek érvényesek. Azok a változások, amelyek kizárólag az Ön javát szolgálják, vagy amelyeket jogszabály ír elő, e bejelentés nélkül is hatályosak, és ellenük nem lehet tiltakozni. Ha tiltakozik egy változás ellen, a hatálybalépés napjával megszüntethetjük a fiókját. Ha olyan időszakra fizetett, amely ezen a napon túlnyúlik, a fel nem használt részt visszatérítjük.",
982
991
  },
983
992
  language: {
984
993
  title: "Nyelv",
@@ -769,6 +769,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
769
769
  list: {
770
770
  unread: "Non letto",
771
771
  opensInNewTab: "si apre in una nuova scheda",
772
+ showAll: (count) => `Mostra tutto (${n(count)})`,
773
+ showFewer: "Mostra meno",
772
774
  },
773
775
  breadcrumbs: {
774
776
  label: "Percorso di navigazione",
@@ -902,6 +904,8 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
902
904
  impressum: "Impressum",
903
905
  privacy: "Privacy",
904
906
  terms: "Condizioni d’uso",
907
+ security: "Sicurezza",
908
+ accessibility: "Accessibilità",
905
909
  },
906
910
  titles: {
907
911
  impressum: "Impressum",
@@ -921,6 +925,11 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
921
925
  `Questa è una traduzione fornita per Sua comodità. Fa fede la versione in ${o.bindingLanguage}.`,
922
926
  show: (o) => `Mostra la versione in ${o.bindingLanguage}`,
923
927
  },
928
+ updated: (date) => `Ultimo aggiornamento: ${date}`,
929
+ summary: {
930
+ title: "In breve",
931
+ note: "Questo riepilogo serve da orientamento; fa fede solo il testo completo.",
932
+ },
924
933
  sections: {
925
934
  impressum: {
926
935
  operator: {
@@ -979,7 +988,7 @@ export function uiKitLabelsIt(numberLocale = "it-IT"): UiKitLabels {
979
988
  },
980
989
  changes: {
981
990
  title: "Modifiche a queste condizioni",
982
- body: "Queste condizioni possono essere aggiornate con l’evoluzione del servizio. Le modifiche sostanziali saranno annunciate via email o nell’app; l’uso continuato dopo una modifica ne implica l’accettazione.",
991
+ body: "Possiamo aggiornare queste condizioni con l’evoluzione del servizio. Annunciamo ogni modifica sostanziale almeno 30 giorni prima della sua entrata in vigore, via email e nell’app, insieme al nuovo testo. Se non è d’accordo, può opporsi o eliminare il Suo account prima di tale data, e può esportare i Suoi dati fino ad allora. Se continua a usare il servizio dopo tale data, si applicano le condizioni modificate. Le modifiche che vanno solo a Suo vantaggio, o che sono imposte dalla legge, si applicano senza questo preavviso e non è possibile opporvisi. Se si oppone a una modifica, possiamo chiudere il Suo account alla data della sua entrata in vigore. Se ha pagato un periodo che va oltre tale data, Le rimborsiamo la parte non utilizzata.",
983
992
  },
984
993
  language: {
985
994
  title: "Lingua",
@@ -746,6 +746,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
746
746
  list: {
747
747
  unread: "未读",
748
748
  opensInNewTab: "在新标签页中打开",
749
+ showAll: (count) => `显示全部(${n(count)})`,
750
+ showFewer: "收起",
749
751
  },
750
752
  breadcrumbs: {
751
753
  label: "面包屑导航",
@@ -876,6 +878,8 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
876
878
  impressum: "运营者信息",
877
879
  privacy: "隐私政策",
878
880
  terms: "使用条款",
881
+ security: "安全",
882
+ accessibility: "无障碍",
879
883
  },
880
884
  titles: {
881
885
  impressum: "运营者信息",
@@ -895,6 +899,11 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
895
899
  `本页为方便阅读而提供的译文,以${o.bindingLanguage}版本为准。`,
896
900
  show: (o) => `查看${o.bindingLanguage}版本`,
897
901
  },
902
+ updated: (date) => `最后更新:${date}`,
903
+ summary: {
904
+ title: "要点",
905
+ note: "本摘要仅供参考;只有完整文本具有约束力。",
906
+ },
898
907
  sections: {
899
908
  impressum: {
900
909
  operator: {
@@ -951,7 +960,7 @@ export function uiKitLabelsZh(numberLocale = "zh-CN"): UiKitLabels {
951
960
  },
952
961
  changes: {
953
962
  title: "条款变更",
954
- body: "本条款可能会随着服务的发展而更新。重大变更将通过电子邮件或在应用程序中公布;变更后继续使用本服务即表示您接受变更。",
963
+ body: "我们可能会随着服务的发展更新本条款。重大变更将至少在生效前 30 天通过电子邮件和应用程序公布,并附上新文本。如您不同意,可在该日期前提出异议或删除您的账户,并可在此之前导出您的数据。如您在该日期后继续使用本服务,则适用变更后的条款。仅对您有利的变更或法律要求的变更,无需上述提前通知即可生效,且不得提出异议。如您对某项变更提出异议,我们可在该变更生效之日关闭您的账户。如您已付费的期间超过该日期,我们将退还未使用部分的费用。",
955
964
  },
956
965
  language: {
957
966
  title: "语言",
@@ -309,6 +309,10 @@ export const KIT_LABEL_SAMPLES: Readonly<Record<string, readonly (readonly unkno
309
309
  "legal.sections.terms.language.body": operatorSample,
310
310
  "legal.translation.note": operatorSample,
311
311
  "legal.translation.show": operatorSample,
312
+ // 0.51.3: the as-of line, with the date already written out.
313
+ "legal.updated": [["{{date}}"]],
314
+ // 0.51.4: a limited list's "Show all (n)", with the whole list's count.
315
+ "list.showAll": [[12], [1240]],
312
316
  // 0.29.0: the sign-in and sign-up parts (docs/auth-harmonization.md).
313
317
  "signIn.tagHint": [["{{taggedAddress}}"]],
314
318
  "register.emailTagUse": [["{{address}}"]],
package/src/index.ts CHANGED
@@ -548,6 +548,7 @@ export type {
548
548
  LegalOperator,
549
549
  LegalOperatorText,
550
550
  LegalPageKey,
551
+ LegalInfoPageKey,
551
552
  LegalTextSectionLabels,
552
553
  LegalOperatorSectionLabels,
553
554
  LegalFramedSectionLabels,
@@ -29,6 +29,36 @@ export function foldForSearch(text: string, locale?: string): string {
29
29
  return lower.normalize("NFD").replace(/\p{M}+/gu, "").replace(/ß/g, "ss");
30
30
  }
31
31
 
32
+ /**
33
+ * A picker's query as its words, each folded (0.51.4, keksdose's /prices shop picker):
34
+ * "Migros Zürich" → `["migros", "zurich"]`. An empty or blank query has no words.
35
+ */
36
+ export function searchWords(query: string, locale?: string): string[] {
37
+ return foldForSearch(query, locale).split(/\s+/).filter((word) => word !== "");
38
+ }
39
+
40
+ /**
41
+ * Whether a row matches a picker's query: EVERY word appears in at least one of the row's
42
+ * texts — "migros 8004" finds a row named "Migros · 8004 Zürich", and a row whose chain is
43
+ * its group and whose place is its label (pass both). Until 0.51.4 the whole query had to
44
+ * be ONE substring of one text, so two words found nothing unless they stood side by side
45
+ * in that order. One word matches exactly as before, and so does the old query when it
46
+ * was a substring. No words match everything.
47
+ *
48
+ * `words` from {@link searchWords}; `texts` as they are (folded here), `undefined` skipped.
49
+ */
50
+ export function matchesSearch(
51
+ words: readonly string[],
52
+ texts: readonly (string | undefined)[],
53
+ locale?: string,
54
+ ): boolean {
55
+ if (words.length === 0) return true;
56
+ // One haystack per row; a word holds no whitespace, so it cannot match across the
57
+ // newline between two texts.
58
+ const hay = texts.filter((t): t is string => t !== undefined).map((t) => foldForSearch(t, locale)).join("\n");
59
+ return words.every((word) => hay.includes(word));
60
+ }
61
+
32
62
  /** {@link rankMatch}'s answer: lower is better, `-1` is no match. */
33
63
  export type MatchRank = -1 | 0 | 1 | 2;
34
64
 
@@ -115,3 +115,11 @@ export function useSettingsFocus(anchor?: string | null): void {
115
115
  [],
116
116
  );
117
117
  }
118
+
119
+ /**
120
+ * {@link useSettingsFocus} under a name for any page (0.51.4, keksdose run 89): the hook
121
+ * was always allowed on an app's own pages (keksdose's /reports/matching lands
122
+ * `?focus=matching-unmatched` on a card), but its name said Settings at every such call
123
+ * site. The same function, so the two cannot drift; `useSettingsFocus` stays.
124
+ */
125
+ export const useFocusParam: typeof useSettingsFocus = useSettingsFocus;
@@ -126,6 +126,11 @@ const isRow = (entry: TopBarMenuEntry) => entry.kind !== "heading" && entry.kind
126
126
  *
127
127
  * Nothing renders unless `enabled`, and nothing with no row to show. The panel scrolls
128
128
  * (`TopBarActionMenu`'s `scrollable`): some 25 rows ran off a 390×844 phone.
129
+ *
130
+ * From `md` up the groups stand side by side, one column each, in that order (0.51.4,
131
+ * `TopBarActionMenu`'s `columns`; keksdose dev #640, Marcel: "Each group gets a column").
132
+ * "Back to {role}" stays under the roles. A desktop too narrow for all of them wraps
133
+ * the columns onto a second row; a phone keeps the one scrolling column.
129
134
  */
130
135
  export function DevMenu<R extends string, P extends string>({
131
136
  enabled,
@@ -254,6 +259,7 @@ export function DevMenu<R extends string, P extends string>({
254
259
  aria-label={name}
255
260
  entries={entries}
256
261
  scrollable
262
+ columns
257
263
  />
258
264
  );
259
265
  }