@eifi1/ui-kit 0.18.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 (119) hide show
  1. package/README.md +60 -6
  2. package/dist/chart.d.ts +4 -1
  3. package/dist/components/amount-input.d.ts +4 -1
  4. package/dist/components/button-group.d.ts +4 -1
  5. package/dist/components/calculator.d.ts +4 -1
  6. package/dist/components/copy-button.d.ts +4 -1
  7. package/dist/components/facing-pair.d.ts +4 -1
  8. package/dist/components/file-button.d.ts +4 -1
  9. package/dist/components/file-dropzone.d.ts +4 -1
  10. package/dist/components/floating-panel.d.ts +4 -1
  11. package/dist/components/floating-panel.js +15 -1
  12. package/dist/components/floating-panel.js.map +1 -1
  13. package/dist/components/form-actions.d.ts +4 -1
  14. package/dist/components/legal.d.ts +84 -0
  15. package/dist/components/legal.js +55 -0
  16. package/dist/components/legal.js.map +1 -0
  17. package/dist/components/money-field.d.ts +4 -1
  18. package/dist/components/number-field.d.ts +4 -1
  19. package/dist/components/number-input.d.ts +4 -1
  20. package/dist/components/numpad-sheet.d.ts +4 -1
  21. package/dist/components/series-chart.d.ts +4 -1
  22. package/dist/components/server-wake.d.ts +4 -2
  23. package/dist/components/server-wake.js +23 -2
  24. package/dist/components/server-wake.js.map +1 -1
  25. package/dist/components/settings-fields.d.ts +4 -1
  26. package/dist/components/share-card.d.ts +4 -1
  27. package/dist/components/text-link.d.ts +4 -1
  28. package/dist/components/time-input.d.ts +4 -1
  29. package/dist/components/translation-review-editor.d.ts +56 -0
  30. package/dist/components/translation-review-editor.js +159 -0
  31. package/dist/components/translation-review-editor.js.map +1 -0
  32. package/dist/components/translation-review-labels.d.ts +92 -0
  33. package/dist/components/translation-review-labels.js +67 -0
  34. package/dist/components/translation-review-labels.js.map +1 -0
  35. package/dist/components/translation-review-parts.d.ts +97 -0
  36. package/dist/components/translation-review-parts.js +163 -0
  37. package/dist/components/translation-review-parts.js.map +1 -0
  38. package/dist/components/translation-review.d.ts +94 -0
  39. package/dist/components/translation-review.js +344 -0
  40. package/dist/components/translation-review.js.map +1 -0
  41. package/dist/components/ui.d.ts +4 -1
  42. package/dist/hooks/use-file-drop.d.ts +4 -1
  43. package/dist/i18n/defaults.d.ts +4 -1
  44. package/dist/i18n/defaults.js +5 -1
  45. package/dist/i18n/defaults.js.map +1 -1
  46. package/dist/i18n/german.d.ts +4 -1
  47. package/dist/i18n/german.js +51 -0
  48. package/dist/i18n/german.js.map +1 -1
  49. package/dist/i18n/kit-labels.d.ts +4 -1
  50. package/dist/i18n/kit-labels.js.map +1 -1
  51. package/dist/i18n/languages.d.ts +243 -0
  52. package/dist/i18n/languages.js +70 -0
  53. package/dist/i18n/languages.js.map +1 -0
  54. package/dist/i18n/locales/de-CH.d.ts +4 -1
  55. package/dist/i18n/locales/en.d.ts +4 -1
  56. package/dist/i18n/locales/en.js +8 -0
  57. package/dist/i18n/locales/en.js.map +1 -1
  58. package/dist/i18n/locales/es.d.ts +4 -1
  59. package/dist/i18n/locales/es.js +83 -33
  60. package/dist/i18n/locales/es.js.map +1 -1
  61. package/dist/i18n/locales/fr.d.ts +4 -1
  62. package/dist/i18n/locales/fr.js +50 -0
  63. package/dist/i18n/locales/fr.js.map +1 -1
  64. package/dist/i18n/locales/hu.d.ts +4 -1
  65. package/dist/i18n/locales/hu.js +51 -0
  66. package/dist/i18n/locales/hu.js.map +1 -1
  67. package/dist/i18n/locales/it.d.ts +4 -1
  68. package/dist/i18n/locales/it.js +90 -40
  69. package/dist/i18n/locales/it.js.map +1 -1
  70. package/dist/i18n/locales/zh.d.ts +4 -1
  71. package/dist/i18n/locales/zh.js +51 -0
  72. package/dist/i18n/locales/zh.js.map +1 -1
  73. package/dist/i18n/review.d.ts +102 -0
  74. package/dist/i18n/review.js +158 -0
  75. package/dist/i18n/review.js.map +1 -0
  76. package/dist/index.d.ts +9 -1
  77. package/dist/index.js +64 -0
  78. package/dist/index.js.map +1 -1
  79. package/dist/{kit-labels-CPbsjlXr.d.ts → kit-labels-D1J2_jyD.d.ts} +7 -0
  80. package/dist/lib/floating-stack.d.ts +31 -0
  81. package/dist/lib/floating-stack.js +42 -0
  82. package/dist/lib/floating-stack.js.map +1 -0
  83. package/dist/lib/server-wake.d.ts +24 -8
  84. package/dist/lib/server-wake.js +6 -5
  85. package/dist/lib/server-wake.js.map +1 -1
  86. package/dist/lib/translation-review.d.ts +232 -0
  87. package/dist/lib/translation-review.js +194 -0
  88. package/dist/lib/translation-review.js.map +1 -0
  89. package/dist/rhf/fields.d.ts +4 -1
  90. package/dist/rhf/form.d.ts +4 -1
  91. package/dist/rhf.d.ts +4 -1
  92. package/dist/shell/app-shell.d.ts +4 -1
  93. package/dist/shell/top-bar-brand.d.ts +4 -1
  94. package/dist/shell.d.ts +4 -1
  95. package/dist/wizard/stepper-nav.d.ts +4 -1
  96. package/dist/wizard.d.ts +4 -1
  97. package/package.json +1 -1
  98. package/src/components/floating-panel.tsx +19 -1
  99. package/src/components/legal.tsx +159 -0
  100. package/src/components/server-wake.tsx +37 -5
  101. package/src/components/translation-review-editor.tsx +261 -0
  102. package/src/components/translation-review-labels.ts +152 -0
  103. package/src/components/translation-review-parts.tsx +269 -0
  104. package/src/components/translation-review.tsx +502 -0
  105. package/src/i18n/defaults.ts +4 -0
  106. package/src/i18n/german.ts +52 -0
  107. package/src/i18n/kit-labels.tsx +7 -0
  108. package/src/i18n/languages.ts +285 -0
  109. package/src/i18n/locales/en.ts +8 -0
  110. package/src/i18n/locales/es.ts +84 -33
  111. package/src/i18n/locales/fr.ts +51 -0
  112. package/src/i18n/locales/hu.ts +52 -0
  113. package/src/i18n/locales/it.ts +91 -40
  114. package/src/i18n/locales/zh.ts +52 -0
  115. package/src/i18n/review.ts +193 -0
  116. package/src/index.ts +63 -0
  117. package/src/lib/floating-stack.ts +66 -0
  118. package/src/lib/server-wake.ts +34 -13
  119. package/src/lib/translation-review.ts +465 -0
package/src/index.ts CHANGED
@@ -341,3 +341,66 @@ export * from "./search/global-search";
341
341
  // prop > the provider > the English default. See src/i18n/kit-labels.tsx.
342
342
  export * from "./i18n/kit-labels";
343
343
  export * from "./i18n/defaults";
344
+ // The kit's words as `key → text` rows for an app's translation review (keksdose).
345
+ export { kitLabelStrings } from "./i18n/review";
346
+ // The seven languages, how an app resolves one, and the kit catalogue loader for it.
347
+ export * from "./i18n/languages";
348
+ // Translation review (0.19): one contract for kastlan's and keksdose's review pages —
349
+ // the pure rows/status/placeholder logic and the presentational page parts.
350
+ export {
351
+ REVIEW_STATUSES,
352
+ fromApiReview,
353
+ toApiWrite,
354
+ flattenStrings,
355
+ keyNamespace,
356
+ placeholderTokens,
357
+ placeholderMismatch,
358
+ reviewStatus,
359
+ keyInArea,
360
+ keyInAreas,
361
+ translationRows,
362
+ summariseRows,
363
+ DEFAULT_TRANSLATION_REVIEW_FILTER,
364
+ filterTranslationRows,
365
+ reviewWrite,
366
+ mergeReviews,
367
+ dropReviews,
368
+ translationCorrections,
369
+ } from "./lib/translation-review";
370
+ export type {
371
+ TranslationVerdict,
372
+ TranslationReview,
373
+ TranslationReviewWrite,
374
+ TranslationReviewKey,
375
+ ReviewStatus,
376
+ TranslationRow,
377
+ ApiTranslationReview,
378
+ ApiTranslationReviewWrite,
379
+ TranslationRowsInput,
380
+ TranslationSummary,
381
+ TranslationReviewFilter,
382
+ TranslationCorrection,
383
+ } from "./lib/translation-review";
384
+ export {
385
+ TranslationReviewPanel,
386
+ TranslationReviewEditor,
387
+ ReviewStatusChip,
388
+ TranslationProgress,
389
+ TranslationLocaleTabs,
390
+ TranslationExportButton,
391
+ REVIEW_STATUS_TONES,
392
+ DEFAULT_TRANSLATION_REVIEW_LABELS,
393
+ } from "./components/translation-review";
394
+ export type {
395
+ TranslationReviewPanelProps,
396
+ TranslationReviewEditorProps,
397
+ ReviewStatusChipProps,
398
+ TranslationProgressProps,
399
+ TranslationLocaleTab,
400
+ TranslationLocaleTabsProps,
401
+ TranslationExportButtonProps,
402
+ TranslationReviewLabels,
403
+ } from "./components/translation-review";
404
+ // The legal pages' shell (0.19, H10): Imprint, Privacy Policy, Terms in every app.
405
+ export { LegalLayout, LegalSection, LegalLinks, DEFAULT_LEGAL_LABELS } from "./components/legal";
406
+ export type { LegalLabels, LegalLink, LegalLinksProps, LegalSectionProps, LegalLayoutProps } from "./components/legal";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * What floats over the bottom of the screen right now, so a notice that comes and goes
3
+ * can sit above it instead of on it.
4
+ *
5
+ * kastlan 0.18: its offline pill is a `bottom-start` FloatingActionButton, and
6
+ * `ServerWakeNotice` sits in the same corner — right after a break both show at once
7
+ * (the photo outbox syncs while the first reads wake the server), one over the other.
8
+ * The FAB's `offset` cannot help: the notice comes and goes, and on a phone it is
9
+ * nearly as wide as the screen, so a FAB in EITHER corner can be under it.
10
+ *
11
+ * Measured, not declared: a FloatingActionButton registers its fixed element while it
12
+ * is mounted, and a notice asks {@link clearanceAbove} for the height it has to clear —
13
+ * whatever offset, `extended` label or `hidden` state the controls are in. A control
14
+ * that is `hidden` measures 0 × 0 and is skipped, so kastlan's pill, which stays
15
+ * mounted and hidden while there is nothing to report, costs the notice nothing.
16
+ *
17
+ * Internal: not exported from the barrel.
18
+ */
19
+
20
+ const occupants = new Set<HTMLElement>();
21
+ const listeners = new Set<() => void>();
22
+ let observer: ResizeObserver | null = null;
23
+
24
+ function notify() {
25
+ for (const listener of listeners) listener();
26
+ }
27
+
28
+ /** Track `element` until the returned function is called. Size changes — a label that
29
+ * grows, `hidden` toggling — notify the subscribers too. */
30
+ export function registerFloating(element: HTMLElement): () => void {
31
+ occupants.add(element);
32
+ if (observer === null && typeof ResizeObserver !== "undefined") observer = new ResizeObserver(notify);
33
+ observer?.observe(element);
34
+ notify();
35
+ return () => {
36
+ occupants.delete(element);
37
+ observer?.unobserve(element);
38
+ notify();
39
+ };
40
+ }
41
+
42
+ /** Hear every registration, removal and size change. */
43
+ export function subscribeFloating(listener: () => void): () => void {
44
+ listeners.add(listener);
45
+ return () => {
46
+ listeners.delete(listener);
47
+ };
48
+ }
49
+
50
+ /**
51
+ * The distance from the viewport's bottom edge to the top of the highest visible
52
+ * floating control that shares a column with `element` — what `element` has to clear —
53
+ * or `null` when nothing is under it.
54
+ */
55
+ export function clearanceAbove(element: HTMLElement | null): number | null {
56
+ if (!element) return null;
57
+ const own = element.getBoundingClientRect();
58
+ let top = Infinity;
59
+ for (const other of occupants) {
60
+ const box = other.getBoundingClientRect();
61
+ if (box.width === 0 || box.height === 0) continue;
62
+ if (box.right <= own.left || box.left >= own.right) continue;
63
+ top = Math.min(top, box.top);
64
+ }
65
+ return top === Infinity ? null : Math.max(0, window.innerHeight - top);
66
+ }
@@ -25,6 +25,13 @@
25
25
  * simply be false. With the default that holds by itself (an upload is a POST);
26
26
  * an app widening `shouldWatch` keeps its upload routes out of it.
27
27
  *
28
+ * Nor a DOWNLOAD, for the same reason (kastlan 0.18): axios answers only once the
29
+ * whole body is in, so a generated PDF or a large photo — a GET — ran past both
30
+ * thresholds on a warm server, and held the count for every read beside it. The
31
+ * default filter skips a request whose `responseType` is `"blob"`, `"arraybuffer"` or
32
+ * `"stream"`; a custom filter gets the request as its third argument to do the same.
33
+ * (`wrapFetch` ends the wait at the headers, so a download through it is harmless.)
34
+ *
28
35
  * It never arms while the browser is offline, whatever `shouldWatch` says: that state
29
36
  * has its own, truthful indicator, and a request that never left the device is not
30
37
  * evidence of anything about the server.
@@ -35,8 +42,10 @@
35
42
  export type ServerWakeStage = "idle" | "slow" | "waking";
36
43
 
37
44
  /** Decides which requests count. `method` arrives lower-cased (`"get"` when the
38
- * request named none — axios's and fetch's default); `url` as the client had it. */
39
- export type ServerWakeFilter = (method: string, url: string) => boolean;
45
+ * request named none — axios's and fetch's default); `url` as the client had it;
46
+ * `request` is what the adapter saw — for axios the request config itself, so
47
+ * `responseType` or the app's own fields can decide. */
48
+ export type ServerWakeFilter = (method: string, url: string, request: ServerWakeRequest) => boolean;
40
49
 
41
50
  export interface ServerWakeOptions {
42
51
  /** Past this, a warm round trip is out of the question (a cold database query on a
@@ -44,15 +53,18 @@ export interface ServerWakeOptions {
44
53
  slowMs?: number;
45
54
  /** Past this, the container was almost certainly scaled to zero. Default 7000 ms. */
46
55
  wakingMs?: number;
47
- /** Which requests count. Default {@link watchReadsAnd}`()` — GETs only. The offline
48
- * guard runs before it and cannot be overridden. */
56
+ /** Which requests count. Default {@link watchReadsAnd}`()` — GETs only, downloads
57
+ * excluded. The offline guard runs before it and cannot be overridden. */
49
58
  shouldWatch?: ServerWakeFilter;
50
59
  }
51
60
 
52
- /** What a request is, for {@link ServerWakeWatcher.track}. */
61
+ /** What a request is, for {@link ServerWakeWatcher.track} and a {@link ServerWakeFilter}. */
53
62
  export interface ServerWakeRequest {
54
63
  method?: string;
55
64
  url?: string;
65
+ /** axios's `responseType`. `"blob"`, `"arraybuffer"` and `"stream"` mark a download,
66
+ * which the default filter does not count. */
67
+ responseType?: string;
56
68
  }
57
69
 
58
70
  export interface ServerWakeWatcher {
@@ -61,8 +73,9 @@ export interface ServerWakeWatcher {
61
73
  * Returns whether it was counted — the caller MUST call {@link end} exactly once
62
74
  * for every `true`, or the counter never returns to zero and the notice sticks.
63
75
  * (The axios adapter stashes it on the request config for that reason.)
76
+ * `request` reaches the filter as its third argument; default `{ method, url }`.
64
77
  */
65
- start(method?: string, url?: string): boolean;
78
+ start(method?: string, url?: string, request?: ServerWakeRequest): boolean;
66
79
  /** A counted request settled — either way: an error still proves the server
67
80
  * answered or gave up. When the last one does, the notice goes away. Never takes
68
81
  * the counter below zero. */
@@ -79,13 +92,19 @@ export interface ServerWakeWatcher {
79
92
  getInFlight(): number;
80
93
  }
81
94
 
95
+ /** Response types that make a request a download — see the module comment. */
96
+ const DOWNLOAD_RESPONSE_TYPES = new Set(["blob", "arraybuffer", "stream"]);
97
+
82
98
  /**
83
99
  * The usual filter: every GET, plus any other request whose URL matches `writes` —
84
- * keksdose's `/\/auth\/(login|register|demo-session)\b/`. Keep upload routes out of
100
+ * keksdose's `/\/auth\/(login|register|demo-session)\b/` — but never a download
101
+ * (`responseType` `"blob"`, `"arraybuffer"` or `"stream"`). Keep upload routes out of
85
102
  * the pattern (see the module comment).
86
103
  */
87
104
  export function watchReadsAnd(writes?: RegExp): ServerWakeFilter {
88
- return (method, url) => method === "get" || (writes !== undefined && writes.test(url));
105
+ return (method, url, request) =>
106
+ !DOWNLOAD_RESPONSE_TYPES.has(request?.responseType ?? "") &&
107
+ (method === "get" || (writes !== undefined && writes.test(url)));
89
108
  }
90
109
 
91
110
  function isOffline(): boolean {
@@ -116,9 +135,9 @@ export function createServerWake({
116
135
  wakingTimer = null;
117
136
  };
118
137
 
119
- const start = (method?: string, url?: string): boolean => {
138
+ const start = (method?: string, url?: string, request: ServerWakeRequest = { method, url }): boolean => {
120
139
  if (isOffline()) return false;
121
- if (!shouldWatch((method ?? "get").toLowerCase(), url ?? "")) return false;
140
+ if (!shouldWatch((method ?? "get").toLowerCase(), url ?? "", request)) return false;
122
141
  inFlight += 1;
123
142
  // The clock runs from the FIRST outstanding request: a page firing a second read
124
143
  // at 1.9 s is still waiting on the same sleeping server, not starting afresh.
@@ -141,7 +160,7 @@ export function createServerWake({
141
160
  start,
142
161
  end,
143
162
  track<T>(work: PromiseLike<T> | (() => PromiseLike<T>), request: ServerWakeRequest = {}) {
144
- const counted = start(request.method, request.url);
163
+ const counted = start(request.method, request.url, request);
145
164
  let promise: Promise<T>;
146
165
  try {
147
166
  promise = Promise.resolve(typeof work === "function" ? work() : work);
@@ -177,10 +196,12 @@ export const serverWake: ServerWakeWatcher = createServerWake();
177
196
 
178
197
  // ── axios ─────────────────────────────────────────────────────────────────────
179
198
 
180
- /** The two config fields the watchdog reads — what axios's request config has. */
199
+ /** The config fields the watchdog reads — what axios's request config has. The whole
200
+ * config reaches the filter, so an app's filter can read the rest of it. */
181
201
  export interface ServerWakeAxiosConfig {
182
202
  method?: string;
183
203
  url?: string;
204
+ responseType?: string;
184
205
  }
185
206
 
186
207
  /** The slice of an interceptor manager the adapter uses. A method (not a function
@@ -229,7 +250,7 @@ export function attachServerWake<C extends ServerWakeAxiosConfig, R extends { co
229
250
  };
230
251
  const request = instance.interceptors.request.use(
231
252
  (config) => {
232
- (config as C & Marked)[COUNTED] = watcher.start(config.method, config.url);
253
+ (config as C & Marked)[COUNTED] = watcher.start(config.method, config.url, config);
233
254
  return config;
234
255
  },
235
256
  (error) => Promise.reject(error),