@cosmicdrift/kumiko-renderer 0.386.0 → 0.388.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 (222) hide show
  1. package/LICENSE +21 -57
  2. package/dist/app/action-form-body.d.ts +2 -2
  3. package/dist/app/action-form-body.js +12 -4
  4. package/dist/app/action-form-shim.d.ts +8 -8
  5. package/dist/app/action-form-shim.js +20 -22
  6. package/dist/app/action-form-support.d.ts +1 -0
  7. package/dist/app/action-form-support.js +1 -1
  8. package/dist/app/app-features-context.js +4 -4
  9. package/dist/app/column-renderers.d.ts +7 -9
  10. package/dist/app/column-renderers.js +15 -18
  11. package/dist/app/config-edit-body.js +7 -3
  12. package/dist/app/config-edit-shim.d.ts +7 -8
  13. package/dist/app/config-edit-shim.js +18 -20
  14. package/dist/app/custom-screens.d.ts +3 -3
  15. package/dist/app/custom-screens.js +8 -8
  16. package/dist/app/dashboard-body.d.ts +2 -2
  17. package/dist/app/dashboard-body.js +2 -2
  18. package/dist/app/entity-edit-prefill.d.ts +11 -0
  19. package/dist/app/entity-edit-prefill.js +18 -13
  20. package/dist/app/entity-edit-screen.js +4 -4
  21. package/dist/app/entity-list-screen.js +3 -3
  22. package/dist/app/extension-form-submit.d.ts +1 -1
  23. package/dist/app/extension-form-submit.js +14 -15
  24. package/dist/app/extension-sections.d.ts +29 -28
  25. package/dist/app/extension-sections.js +10 -10
  26. package/dist/app/feature-schema.js +11 -11
  27. package/dist/app/form-schema.js +2 -2
  28. package/dist/app/header-actions-bar.js +2 -2
  29. package/dist/app/kumiko-screen.js +8 -10
  30. package/dist/app/layout-fields.js +1 -1
  31. package/dist/app/list-facets.js +3 -3
  32. package/dist/app/nav.d.ts +10 -10
  33. package/dist/app/nav.js +13 -13
  34. package/dist/app/projection-detail-body.js +4 -4
  35. package/dist/app/projection-detail-shim.d.ts +6 -5
  36. package/dist/app/projection-detail-shim.js +23 -22
  37. package/dist/app/projection-list-body.js +10 -10
  38. package/dist/app/projection-list-shim.d.ts +5 -5
  39. package/dist/app/projection-list-shim.js +17 -18
  40. package/dist/app/qn.js +10 -12
  41. package/dist/app/qualify-screen-id.js +1 -1
  42. package/dist/app/screen-access.js +2 -2
  43. package/dist/app/screen-shared.js +1 -1
  44. package/dist/app/secret-mint-body.js +4 -4
  45. package/dist/app/write-failed-error.js +8 -7
  46. package/dist/components/edit-mounts.d.ts +2 -2
  47. package/dist/components/embedded-list-field.js +5 -5
  48. package/dist/components/grid-cell-for-field.d.ts +1 -1
  49. package/dist/components/reference-create-dialog.js +3 -3
  50. package/dist/components/related-list-section.d.ts +1 -1
  51. package/dist/components/related-list-section.js +15 -2
  52. package/dist/components/render-edit-action-button.d.ts +1 -3
  53. package/dist/components/render-edit-action-button.js +2 -2
  54. package/dist/components/render-edit-logic.js +1 -1
  55. package/dist/components/render-edit-types.d.ts +6 -6
  56. package/dist/components/render-edit.js +74 -73
  57. package/dist/components/render-field.d.ts +7 -7
  58. package/dist/components/render-field.js +62 -61
  59. package/dist/components/render-list.d.ts +42 -43
  60. package/dist/components/render-list.js +93 -84
  61. package/dist/components/use-wizard-form-draft.js +10 -10
  62. package/dist/context/dispatcher-context.js +5 -5
  63. package/dist/context/user-roles-context.js +2 -2
  64. package/dist/flow-diagram/render.js +9 -1
  65. package/dist/headless/apex/index.d.ts +1 -2
  66. package/dist/headless/apex/index.js +5 -6
  67. package/dist/headless/form/form-controller.js +14 -6
  68. package/dist/headless/form/types.d.ts +1 -0
  69. package/dist/headless/form/zod-bridge.js +2 -2
  70. package/dist/headless/format/index.js +89 -64
  71. package/dist/headless/format/money.js +1 -1
  72. package/dist/headless/index.js +2 -2
  73. package/dist/headless/view-model/edit.js +4 -2
  74. package/dist/headless/view-model/list.js +184 -150
  75. package/dist/headless/view-model/types.d.ts +6 -3
  76. package/dist/hooks/live-refetch-scheduler.js +4 -0
  77. package/dist/hooks/reference-limits.js +11 -12
  78. package/dist/hooks/use-list-url-state.d.ts +13 -13
  79. package/dist/hooks/use-list-url-state.js +21 -22
  80. package/dist/hooks/use-query.js +3 -3
  81. package/dist/hooks/use-reference-lookup.d.ts +9 -1
  82. package/dist/hooks/use-reference-lookup.js +32 -19
  83. package/dist/hooks/use-store.js +18 -22
  84. package/dist/hooks/use-stream-handler.js +4 -2
  85. package/dist/i18n-defaults.js +6 -8
  86. package/dist/i18n.d.ts +10 -10
  87. package/dist/i18n.js +25 -25
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.js +6 -4
  90. package/dist/live/dispatcher-live.js +2 -2
  91. package/dist/primitives.d.ts +244 -221
  92. package/dist/primitives.js +1 -1
  93. package/dist/sse/live-events.d.ts +5 -5
  94. package/dist/sse/live-events.js +17 -17
  95. package/dist/tokens.d.ts +8 -8
  96. package/dist/tokens.js +16 -17
  97. package/dist/web/app/browser-locale.d.ts +9 -9
  98. package/dist/web/app/browser-locale.js +19 -19
  99. package/dist/web/app/client-plugin.d.ts +44 -52
  100. package/dist/web/app/client-plugin.js +4 -5
  101. package/dist/web/app/create-app.d.ts +28 -31
  102. package/dist/web/app/create-app.js +29 -25
  103. package/dist/web/app/dashboard-body.js +17 -1
  104. package/dist/web/app/dashboard-panels.js +12 -2
  105. package/dist/web/app/document-lang-sync.js +1 -1
  106. package/dist/web/app/draft-storage.js +1 -1
  107. package/dist/web/app/nav-providers-context.d.ts +3 -3
  108. package/dist/web/app/nav-providers-context.js +3 -3
  109. package/dist/web/app/nav.d.ts +18 -17
  110. package/dist/web/app/nav.js +66 -64
  111. package/dist/web/app/rich-content-editor.js +1 -1
  112. package/dist/web/components/config-cascade.js +17 -18
  113. package/dist/web/components/config-source-badge.js +4 -4
  114. package/dist/web/index.d.ts +1 -1
  115. package/dist/web/index.js +1 -1
  116. package/dist/web/layout/app-layout.d.ts +9 -9
  117. package/dist/web/layout/app-layout.js +2 -2
  118. package/dist/web/layout/avatar.d.ts +4 -3
  119. package/dist/web/layout/avatar.js +15 -17
  120. package/dist/web/layout/default-app-shell.d.ts +22 -23
  121. package/dist/web/layout/editor-panel.js +3 -3
  122. package/dist/web/layout/nav-tree.js +86 -84
  123. package/dist/web/layout/profile-menu.d.ts +1 -1
  124. package/dist/web/layout/shell-header.js +2 -1
  125. package/dist/web/layout/sidebar-brand.d.ts +7 -7
  126. package/dist/web/layout/sidebar-user.d.ts +1 -1
  127. package/dist/web/layout/sidebar-user.js +4 -4
  128. package/dist/web/layout/sidebar.d.ts +8 -8
  129. package/dist/web/layout/sidebar.js +7 -7
  130. package/dist/web/layout/target-resolver-stub.d.ts +5 -5
  131. package/dist/web/layout/target-resolver-stub.js +20 -20
  132. package/dist/web/layout/target-url.d.ts +7 -7
  133. package/dist/web/layout/target-url.js +22 -22
  134. package/dist/web/layout/theme-toggle.js +4 -5
  135. package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
  136. package/dist/web/layout/uniform-bar-controls.js +4 -0
  137. package/dist/web/layout/workspace-shell.d.ts +21 -21
  138. package/dist/web/layout/workspace-shell.js +29 -30
  139. package/dist/web/lib/cn.d.ts +4 -4
  140. package/dist/web/lib/cn.js +4 -4
  141. package/dist/web/lib/file-ref-download.js +4 -1
  142. package/dist/web/primitives/action-menu.d.ts +8 -8
  143. package/dist/web/primitives/action-menu.js +14 -13
  144. package/dist/web/primitives/calendar-popover.d.ts +1 -1
  145. package/dist/web/primitives/calendar-popover.js +10 -10
  146. package/dist/web/primitives/card-styles.js +1 -1
  147. package/dist/web/primitives/card.js +2 -2
  148. package/dist/web/primitives/code-input.d.ts +6 -0
  149. package/dist/web/primitives/code-input.js +31 -0
  150. package/dist/web/primitives/combobox.d.ts +12 -12
  151. package/dist/web/primitives/combobox.js +34 -35
  152. package/dist/web/primitives/data-table-cell.js +2 -2
  153. package/dist/web/primitives/data-table.js +6 -6
  154. package/dist/web/primitives/date-field.js +2 -2
  155. package/dist/web/primitives/date-input.js +4 -4
  156. package/dist/web/primitives/default-form.js +2 -2
  157. package/dist/web/primitives/dropdown-menu.js +14 -15
  158. package/dist/web/primitives/embedded-list-input.js +3 -3
  159. package/dist/web/primitives/facet-filter.js +2 -2
  160. package/dist/web/primitives/field.js +9 -8
  161. package/dist/web/primitives/file-thumbnail.d.ts +3 -0
  162. package/dist/web/primitives/file-thumbnail.js +5 -0
  163. package/dist/web/primitives/form.d.ts +1 -1
  164. package/dist/web/primitives/form.js +6 -6
  165. package/dist/web/primitives/input.js +8 -3
  166. package/dist/web/primitives/layout-elements.js +7 -5
  167. package/dist/web/primitives/layout.js +2 -2
  168. package/dist/web/primitives/located-timestamp-input.js +4 -4
  169. package/dist/web/primitives/money-input.js +13 -17
  170. package/dist/web/primitives/page-header.js +3 -1
  171. package/dist/web/primitives/pager.js +1 -1
  172. package/dist/web/primitives/row-actions.js +6 -7
  173. package/dist/web/primitives/secret-reveal.js +1 -1
  174. package/dist/web/primitives/timestamp-input.d.ts +9 -9
  175. package/dist/web/primitives/timestamp-input.js +21 -21
  176. package/dist/web/primitives/toast.js +12 -13
  177. package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
  178. package/dist/web/primitives/use-focus-on-mount.js +9 -0
  179. package/dist/web/sse/live-events.d.ts +6 -6
  180. package/dist/web/sse/live-events.js +3 -3
  181. package/dist/web/tokens.d.ts +11 -12
  182. package/dist/web/tokens.js +35 -37
  183. package/dist/web/version/update-checker.js +14 -13
  184. package/dist/web/widgets/ai-text-field.js +1 -1
  185. package/dist/web/widgets/bounded-load-cache.d.ts +1 -1
  186. package/dist/web/widgets/bounded-load-cache.js +10 -4
  187. package/dist/web/widgets/charts.d.ts +10 -10
  188. package/dist/web/widgets/charts.js +8 -8
  189. package/dist/web/widgets/choice-cards.d.ts +17 -0
  190. package/dist/web/widgets/choice-cards.js +12 -0
  191. package/dist/web/widgets/collapsible-section.d.ts +3 -3
  192. package/dist/web/widgets/collapsible-section.js +7 -7
  193. package/dist/web/widgets/detail-list.d.ts +5 -3
  194. package/dist/web/widgets/detail-list.js +5 -3
  195. package/dist/web/widgets/feed-list.d.ts +2 -2
  196. package/dist/web/widgets/feed-list.js +2 -2
  197. package/dist/web/widgets/form-fields.d.ts +14 -14
  198. package/dist/web/widgets/form-fields.js +14 -13
  199. package/dist/web/widgets/index.d.ts +1 -0
  200. package/dist/web/widgets/index.js +5 -4
  201. package/dist/web/widgets/infinity-list.js +3 -3
  202. package/dist/web/widgets/mode-switch.d.ts +2 -2
  203. package/dist/web/widgets/mode-switch.js +2 -2
  204. package/dist/web/widgets/pdf-preview.js +1 -1
  205. package/dist/web/widgets/plan-card.d.ts +2 -0
  206. package/dist/web/widgets/plan-card.js +1 -1
  207. package/dist/web/widgets/progress-list.d.ts +1 -2
  208. package/dist/web/widgets/progress-list.js +1 -2
  209. package/dist/web/widgets/query-table.d.ts +6 -6
  210. package/dist/web/widgets/result-panel.d.ts +16 -16
  211. package/dist/web/widgets/result-panel.js +10 -10
  212. package/dist/web/widgets/section-card.d.ts +3 -3
  213. package/dist/web/widgets/section-card.js +3 -3
  214. package/dist/web/widgets/stat.d.ts +4 -4
  215. package/dist/web/widgets/stat.js +6 -6
  216. package/dist/web/widgets/states.d.ts +4 -4
  217. package/dist/web/widgets/states.js +4 -4
  218. package/dist/web/widgets/upload-zone.js +13 -5
  219. package/dist/web/widgets/use-draft.d.ts +7 -7
  220. package/dist/web/widgets/use-draft.js +7 -7
  221. package/package.json +6 -6
  222. package/src/changes.json +49 -0
@@ -1,6 +1,8 @@
1
1
  // Evicts the oldest entry once `max` is exceeded and disposes only that one,
2
- // so several documents on one page keep their own loading tasks alive.
3
- export function createBoundedLoadCache(max, dispose) {
2
+ // so several documents on one page keep their own loading tasks alive. A
3
+ // resolved value that `shouldKeep` rejects (a handled failure) is dropped so
4
+ // the next mount retries.
5
+ export function createBoundedLoadCache(max, dispose, shouldKeep = () => true) {
4
6
  const entries = new Map();
5
7
  return (key, load) => {
6
8
  const cached = entries.get(key);
@@ -9,10 +11,14 @@ export function createBoundedLoadCache(max, dispose) {
9
11
  const loading = load();
10
12
  entries.set(key, loading);
11
13
  // A failed load must not stick: the next mount retries instead of replaying the rejection.
12
- loading.catch(() => {
14
+ const forget = () => {
13
15
  if (entries.get(key) === loading)
14
16
  entries.delete(key);
15
- });
17
+ };
18
+ loading.then((value) => {
19
+ if (!shouldKeep(value))
20
+ forget();
21
+ }, forget);
16
22
  if (entries.size > max) {
17
23
  const [oldestKey, oldest] = entries.entries().next().value ?? [];
18
24
  if (oldestKey !== undefined && oldest !== undefined) {
@@ -4,10 +4,10 @@ import { type StatusTone } from "./status-badge.tsx";
4
4
  export type StatusBarEntry = {
5
5
  /** Stable key (z.B. ISO-Datum). */
6
6
  readonly key: string;
7
- /** Balkenhöhe 0..1 (z.B. operational=1, degraded=0.75, outage=0.25). */
7
+ /** Bar height from 0 to 1 (e.g. operational=1, degraded=0.75, outage=0.25). */
8
8
  readonly level: number;
9
9
  readonly tone: StatusTone;
10
- /** Tooltip-Text (<title>) — translated vom Caller. */
10
+ /** Tooltip text (<title>), supplied translated by the caller. */
11
11
  readonly label?: string;
12
12
  };
13
13
  /** Status bar strip (e.g. 90-day uptime): variable-height bars with
@@ -17,7 +17,7 @@ export type StatusBarEntry = {
17
17
  export declare function StatusBarChart({ entries, ariaLabel, startLabel, endLabel, highlightLast, dense, testId, }: {
18
18
  readonly entries: readonly StatusBarEntry[];
19
19
  readonly ariaLabel: string;
20
- /** Achsen-Beschriftung links/rechts unter dem Chart — translated. */
20
+ /** Left and right axis labels below the chart, supplied translated. */
21
21
  readonly startLabel?: string;
22
22
  readonly endLabel?: string;
23
23
  readonly highlightLast?: boolean;
@@ -25,15 +25,15 @@ export declare function StatusBarChart({ entries, ariaLabel, startLabel, endLabe
25
25
  readonly dense?: boolean;
26
26
  readonly testId?: string;
27
27
  }): ReactNode;
28
- /** Quadratic-durch-Mittelpunkte-Trick: glättet die Zick-Zack-Linie ohne
29
- * Overshoot (~5 Zeilen statt Catmull-Rom/Bezier-Fit). */
28
+ /** Quadratic midpoint technique: smooths the zigzag line without overshoot
29
+ * (~5 lines instead of a Catmull-Rom/Bezier fit). */
30
30
  export declare function smoothPath(pts: ReadonlyArray<{
31
31
  readonly x: number;
32
32
  readonly y: number;
33
33
  }>): string;
34
34
  export type TimeseriesPoint = {
35
35
  readonly atMs: number;
36
- /** null = Ausfall/kein Wert → fällt auf die Grundlinie (sichtbarer Einbruch). */
36
+ /** null = outage/no value → falls to the baseline for a visible drop. */
37
37
  readonly value: number | null;
38
38
  };
39
39
  export type TimeseriesYAxis = {
@@ -65,16 +65,16 @@ export type TimeseriesReferenceLine = {
65
65
  readonly label: string;
66
66
  readonly tone?: StatusTone;
67
67
  };
68
- /** Zeitreihen-Linien-Chart (geglättete Linie + Flächen-Verlauf). x-Achse =
69
- * ZEIT im Fenster windowStartMs..windowEndMs, nicht Index — 5 Min Daten in
70
- * einem 30-Tage-Fenster ergeben ehrlich einen schmalen Streifen rechts. */
68
+ /** Time-series line chart (smoothed line with area fill). The x-axis uses
69
+ * TIME within windowStartMs..windowEndMs rather than the index, so 5-minute
70
+ * data in a 30-day window correctly appears as a narrow strip on the right. */
71
71
  export declare function TimeseriesChart({ points, windowStartMs, windowEndMs, tone, ariaLabel, axisLabels, xAxis, yAxis, height, markers, formatMarkerTime, referenceLines, emptyContent, testId, }: {
72
72
  readonly points: readonly TimeseriesPoint[];
73
73
  readonly windowStartMs: number;
74
74
  readonly windowEndMs: number;
75
75
  readonly tone?: StatusTone;
76
76
  readonly ariaLabel: string;
77
- /** Achsen-Zeile unter dem Chart (translated/formatiert vom Caller). */
77
+ /** Axis row below the chart, formatted and translated by the caller. */
78
78
  readonly axisLabels?: {
79
79
  readonly start: string;
80
80
  readonly mid?: string;
@@ -5,9 +5,9 @@ import { NARROW_VIEWPORT_BREAKPOINT_PX, useIsViewportBelow, } from "../primitive
5
5
  import { bucketIndexAtFraction, initialWindowSelection, nearestIndex, pointsWithin, resolveStackedAreaWindow, snapBrushWindow, snapMovedBrushWindow, stackedAreaAxisTicks, stackedAreaDateFormatFor, } from "./chart-window.js";
6
6
  import { ModeSwitch } from "./mode-switch.js";
7
7
  import { STATUS_TONE_TEXT } from "./status-badge.js";
8
- // Inline-SVG-Charts — kein Chart-Dep. Farben ausschließlich über die
9
- // --color-status-* / --color-foreground Theme-Tokens; Achsen-Labels
10
- // kommen translated vom Caller (keine Locale-Annahmen im Widget).
8
+ // Inline SVG charts without a chart dependency. Colors come exclusively from the
9
+ // --color-status-* / --color-foreground theme tokens; axis labels are supplied
10
+ // translated by the caller, so the widget makes no locale assumptions.
11
11
  const HOUR_MS = 3_600_000;
12
12
  const DAY_MS = 24 * HOUR_MS;
13
13
  const TONE_VAR = {
@@ -47,8 +47,8 @@ const SINGLE_POINT_RADIUS = 3;
47
47
  function clampDotY(y, height) {
48
48
  return Math.max(SINGLE_POINT_RADIUS, Math.min(height - SINGLE_POINT_RADIUS, y));
49
49
  }
50
- /** Quadratic-durch-Mittelpunkte-Trick: glättet die Zick-Zack-Linie ohne
51
- * Overshoot (~5 Zeilen statt Catmull-Rom/Bezier-Fit). */
50
+ /** Quadratic midpoint technique: smooths the zigzag line without overshoot
51
+ * (~5 lines instead of a Catmull-Rom/Bezier fit). */
52
52
  export function smoothPath(pts) {
53
53
  const first = pts[0];
54
54
  if (!first)
@@ -85,9 +85,9 @@ function yTickPosition(index, count) {
85
85
  return "";
86
86
  return "-translate-y-1/2";
87
87
  }
88
- /** Zeitreihen-Linien-Chart (geglättete Linie + Flächen-Verlauf). x-Achse =
89
- * ZEIT im Fenster windowStartMs..windowEndMs, nicht Index — 5 Min Daten in
90
- * einem 30-Tage-Fenster ergeben ehrlich einen schmalen Streifen rechts. */
88
+ /** Time-series line chart (smoothed line with area fill). The x-axis uses
89
+ * TIME within windowStartMs..windowEndMs rather than the index, so 5-minute
90
+ * data in a 30-day window correctly appears as a narrow strip on the right. */
91
91
  export function TimeseriesChart({ points, windowStartMs, windowEndMs, tone = "ok", ariaLabel, axisLabels, xAxis, yAxis, height, markers, formatMarkerTime, referenceLines, emptyContent, testId, }) {
92
92
  const gradientId = useId();
93
93
  const descId = useId();
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from "react";
2
+ /** Card-styled radio group for mutually exclusive choices that need a description or price line. */
3
+ export declare function ChoiceCards<T extends string>({ value, options, onChange, ariaLabel, testId, }: {
4
+ /** `null`: nothing selected. */
5
+ readonly value: T | null;
6
+ readonly options: readonly {
7
+ readonly value: T;
8
+ readonly title: string;
9
+ readonly description?: string;
10
+ /** Right-aligned next to the title, e.g. a price. */
11
+ readonly aside?: string;
12
+ readonly disabled?: boolean;
13
+ }[];
14
+ readonly onChange: (value: T) => void;
15
+ readonly ariaLabel?: string;
16
+ readonly testId?: string;
17
+ }): ReactNode;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Check } from "lucide-react";
3
+ import { useId } from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ /** Card-styled radio group for mutually exclusive choices that need a description or price line. */
6
+ export function ChoiceCards({ value, options, onChange, ariaLabel, testId, }) {
7
+ const groupName = useId();
8
+ return (_jsx("div", { role: "radiogroup", "aria-label": ariaLabel, "data-testid": testId, className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: options.map((option) => {
9
+ const selected = option.value === value;
10
+ return (_jsxs("label", { "data-testid": testId !== undefined ? `${testId}-${option.value}` : undefined, className: cn("relative flex cursor-pointer flex-col gap-1 rounded-lg border bg-card p-4 transition-colors", "hover:bg-muted/50 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring", selected ? "border-primary ring-1 ring-primary" : "border-border", option.disabled && "cursor-not-allowed opacity-50"), children: [_jsx("input", { type: "radio", className: "sr-only", name: groupName, value: option.value, checked: selected, disabled: option.disabled, onChange: () => onChange(option.value) }), _jsxs("span", { className: "flex items-start justify-between gap-2", children: [_jsx("span", { className: "font-semibold", children: option.title }), _jsxs("span", { className: "flex items-center gap-2", children: [option.aside !== undefined && (_jsx("span", { className: "font-semibold tabular-nums", children: option.aside })), _jsx("span", { "aria-hidden": "true", className: cn("flex size-5 shrink-0 items-center justify-center rounded-full border", selected ? "border-primary bg-primary text-primary-foreground" : "border-input"), children: selected && _jsx(Check, { className: "size-3" }) })] })] }), option.description !== undefined && (_jsx("span", { className: "text-sm text-muted-foreground", children: option.description }))] }, option.value));
11
+ }) }));
12
+ }
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from "react";
2
- /** Aufklappbare Sektion (native <details>, aber React-kontrolliert: sonst
3
- * springt das open-Attribut bei jedem Parent-Re-Render auf den Initialwert
4
- * zurück). Eine Optik für alle „Erweitert"/„Mehr"-Abschnitte. */
2
+ /** Collapsible section using native <details> with React-controlled state; otherwise
3
+ * the open attribute resets to its initial value on every parent re-render.
4
+ * Provides consistent styling for all “Erweitert”/“Mehr” sections. */
5
5
  export declare function CollapsibleSection({ title, defaultOpen, children, testId, }: {
6
6
  readonly title: string;
7
7
  readonly defaultOpen?: boolean;
@@ -1,17 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChevronRight } from "lucide-react";
3
3
  import { useEffect, useState } from "react";
4
- /** Aufklappbare Sektion (native <details>, aber React-kontrolliert: sonst
5
- * springt das open-Attribut bei jedem Parent-Re-Render auf den Initialwert
6
- * zurück). Eine Optik für alle „Erweitert"/„Mehr"-Abschnitte. */
4
+ /** Collapsible section using native <details> with React-controlled state; otherwise
5
+ * the open attribute resets to its initial value on every parent re-render.
6
+ * Provides consistent styling for all “Erweitert”/“Mehr” sections. */
7
7
  export function CollapsibleSection({ title, defaultOpen = false, children, testId, }) {
8
8
  const [open, setOpen] = useState(defaultOpen);
9
- // useState(defaultOpen) liest nur den Mount-Wert — ein async hydrierter
10
- // Draft flippt `defaultOpen` zu spät für den Lazy-Initializer. Open-only
11
- // Effect: kämpft nie gegen ein manuelles Zuklappen des Users.
9
+ // useState(defaultOpen) reads only the mount-time value; an asynchronously hydrated
10
+ // draft flips `defaultOpen` too late for the lazy initializer. An open-only
11
+ // effect never overrides the user's manual collapse.
12
12
  useEffect(() => {
13
13
  if (defaultOpen)
14
14
  setOpen(true);
15
15
  }, [defaultOpen]);
16
- return (_jsxs("details", { "data-testid": testId, className: "group rounded-lg border bg-card", open: open, onToggle: (e) => setOpen(e.currentTarget.open), children: [_jsxs("summary", { className: "flex cursor-pointer select-none items-center justify-between gap-2 rounded-lg px-4 py-2.5 text-sm font-medium hover:bg-muted/50 [&::-webkit-details-marker]:hidden", children: [_jsx("span", { children: title }), _jsx(ChevronRight, { "aria-hidden": "true", className: "size-4 text-muted-foreground transition-transform group-open:rotate-90" })] }), _jsx("div", { className: "border-t px-4 py-3", children: children })] }));
16
+ return (_jsxs("details", { "data-testid": testId, className: "group rounded-lg border bg-card", open: open, onToggle: (e) => setOpen(e.currentTarget.open), children: [_jsxs("summary", { className: "flex h-9 cursor-pointer select-none items-center justify-between gap-2 rounded-lg px-4 text-sm font-medium hover:bg-muted/50 [&::-webkit-details-marker]:hidden", children: [_jsx("span", { children: title }), _jsx(ChevronRight, { "aria-hidden": "true", className: "size-4 text-muted-foreground transition-transform group-open:rotate-90" })] }), _jsx("div", { className: "border-t px-4 py-3", children: children })] }));
17
17
  }
@@ -1,7 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
- /** Read-only Schlüssel-Wert-Liste für Detail-Masken (Label links gedimmt,
3
- * Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
4
- * eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
2
+ /**
3
+ * Read-only key-value list for detail views (dimmed label on the left, value on the right). The
4
+ * value is a ReactNode, so badges and chips are supported. `emphasize` highlights a row, such as
5
+ * the final result in a calculator.
6
+ */
5
7
  export declare function DetailList({ rows, testId, }: {
6
8
  readonly rows: readonly {
7
9
  /** Optional stable key -- falls back to the label when omitted (fine as
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/cn.js";
3
- /** Read-only Schlüssel-Wert-Liste für Detail-Masken (Label links gedimmt,
4
- * Wert rechts). Wert ist ReactNode → Badges/Chips möglich. `emphasize` hebt
5
- * eine Zeile hervor (z.B. das Endergebnis in einem Rechner). */
3
+ /**
4
+ * Read-only key-value list for detail views (dimmed label on the left, value on the right). The
5
+ * value is a ReactNode, so badges and chips are supported. `emphasize` highlights a row, such as
6
+ * the final result in a calculator.
7
+ */
6
8
  export function DetailList({ rows, testId, }) {
7
9
  return (_jsx("dl", { "data-testid": testId, className: "@container flex flex-col divide-y", children: rows.map((row) => (_jsxs("div", { className: "grid grid-cols-1 gap-0.5 py-2.5 @md:grid-cols-[200px_1fr] @md:gap-4", children: [_jsx("dt", { className: cn("text-sm text-muted-foreground", row.emphasize === true && "font-semibold text-foreground"), children: row.label }), _jsx("dd", { className: cn("text-sm font-medium", row.emphasize === true && "font-semibold"), children: row.value })] }, row.id ?? row.label))) }));
8
10
  }
@@ -4,8 +4,8 @@ export type FeedRow = {
4
4
  readonly primary: string;
5
5
  readonly trailing?: string;
6
6
  };
7
- /** Nicht-tabellarische Kurzliste (z.B. "nächste Termine") — Primary-Text +
8
- * optionaler rechtsbündiger Trailing-Wert pro Zeile. */
7
+ /** Non-tabular compact list (e.g. "upcoming appointments") — primary text plus
8
+ * an optional right-aligned trailing value for each row. */
9
9
  export declare function FeedList({ rows, emptyContent, testId, }: {
10
10
  readonly rows: readonly FeedRow[];
11
11
  readonly emptyContent?: ReactNode;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /** Nicht-tabellarische Kurzliste (z.B. "nächste Termine") — Primary-Text +
3
- * optionaler rechtsbündiger Trailing-Wert pro Zeile. */
2
+ /** Non-tabular compact list (e.g. "upcoming appointments") — primary text plus
3
+ * an optional right-aligned trailing value for each row. */
4
4
  export function FeedList({ rows, emptyContent, testId, }) {
5
5
  if (rows.length === 0) {
6
6
  return _jsx("div", { "data-testid": testId, children: emptyContent });
@@ -12,10 +12,10 @@ export interface NumberFieldProps {
12
12
  /** `false` renders without thousands separators (e.g. a year). Default `true`. */
13
13
  readonly grouping?: boolean;
14
14
  }
15
- /** Zahlenfeld = Field + Input(kind:"number") in einem — nimmt der Screen die
16
- * wiederholte id/name/value/onChange-Verdrahtung ab. `value` darf `undefined`
17
- * sein (leeres Feld), intern auf den `""`-Empty-State des Inputs gemappt.
18
- * Einheiten (€/%) gehören ins Label (`t("…Summe (€)")`), nicht als Badge. */
15
+ /** Number field = Field + Input(kind:"number"); avoids repeating the
16
+ * id/name/value/onChange wiring at each screen. `value` may be `undefined`
17
+ * (empty field) and is mapped internally to the Input's `""` empty state.
18
+ * Units (€/%) belong in the label (`t("…Summe (€)")`), not in a badge. */
19
19
  export declare function NumberField({ label, id, name, value, onChange, required, disabled, hideLabel, testId, grouping, }: NumberFieldProps): ReactNode;
20
20
  export declare const MoneyField: typeof NumberField;
21
21
  export declare const PercentField: typeof NumberField;
@@ -34,7 +34,7 @@ export interface TextFieldProps extends FieldBase {
34
34
  readonly placeholder?: string;
35
35
  readonly autoComplete?: string;
36
36
  }
37
- /** Textfeld = Field + Input(kind:"text"). */
37
+ /** Text field = Field + Input(kind:"text"). */
38
38
  export declare function TextField({ label, id, name, value, onChange, required, disabled, placeholder, autoComplete, hideLabel, testId, }: TextFieldProps): ReactNode;
39
39
  export interface SelectFieldProps extends FieldBase {
40
40
  readonly value: string;
@@ -44,7 +44,7 @@ export interface SelectFieldProps extends FieldBase {
44
44
  readonly label: string;
45
45
  }[];
46
46
  }
47
- /** Auswahlfeld = Field + Input(kind:"select"). */
47
+ /** Select field = Field + Input(kind:"select"). */
48
48
  export declare function SelectField({ label, id, name, value, onChange, options, required, disabled, hideLabel, testId, }: SelectFieldProps): ReactNode;
49
49
  export interface DateFieldProps extends FieldBase {
50
50
  readonly value: string;
@@ -52,7 +52,7 @@ export interface DateFieldProps extends FieldBase {
52
52
  readonly min?: string;
53
53
  readonly max?: string;
54
54
  }
55
- /** Datumsfeld = Field + Input(kind:"date"), ISO yyyy-mm-dd. */
55
+ /** Date field = Field + Input(kind:"date"), ISO yyyy-mm-dd. */
56
56
  export declare function DateField({ label, id, name, value, onChange, min, max, required, disabled, hideLabel, testId, }: DateFieldProps): ReactNode;
57
57
  export interface BooleanFieldProps extends FieldBase {
58
58
  readonly value: boolean;
@@ -65,7 +65,7 @@ export interface TextareaFieldProps extends FieldBase {
65
65
  readonly onChange: (v: string) => void;
66
66
  readonly rows?: number;
67
67
  }
68
- /** Mehrzeiliges Textfeld = Field + Input(kind:"textarea"). */
68
+ /** Multi-line text field = Field + Input(kind:"textarea"). */
69
69
  export declare function TextareaField({ label, id, name, value, onChange, rows, required, disabled, hideLabel, testId, }: TextareaFieldProps): ReactNode;
70
70
  export interface RangeFieldProps extends FieldBase {
71
71
  readonly value: number;
@@ -74,20 +74,20 @@ export interface RangeFieldProps extends FieldBase {
74
74
  readonly max: number;
75
75
  readonly step?: number;
76
76
  }
77
- /** Schieberegler = Field + Input(kind:"range"). */
77
+ /** Slider = Field + Input(kind:"range"). */
78
78
  export declare function RangeField({ label, id, name, value, onChange, min, max, step, required, disabled, hideLabel, testId, }: RangeFieldProps): ReactNode;
79
79
  export interface FileFieldProps extends FieldBase {
80
- /** FileRef-UUID der gespeicherten Datei, oder null. */
80
+ /** FileRef UUID of the stored file, or null. */
81
81
  readonly value: string | null;
82
82
  readonly onChange: (fileId: string | null) => void;
83
83
  readonly accept?: readonly string[];
84
- /** "image" zeigt eine Vorschau, "file" nur den Dateinamen. Default "file". */
84
+ /** "image" shows a preview; "file" shows only the file name. Default "file". */
85
85
  readonly variant?: "file" | "image";
86
- /** Bindet den Upload an ein konkretes Entity-Feld — der `/api/files`-Endpoint
87
- * prüft `accept`/`maxSize` dann serverseitig gegen dessen Feld-Definition. */
86
+ /** Binds the upload to a specific entity field; the `/api/files` endpoint
87
+ * then validates `accept`/`maxSize` server-side against that field's definition. */
88
88
  readonly entityType?: string;
89
89
  readonly fieldName?: string;
90
90
  }
91
- /** Datei-Upload = Field + Input(kind:"file"|"image") — FileRef-basiert. */
91
+ /** File upload = Field + Input(kind:"file"|"image"), based on FileRef. */
92
92
  export declare function FileField({ label, id, name, value, onChange, accept, variant, entityType, fieldName, required, disabled, hideLabel, testId, }: FileFieldProps): ReactNode;
93
93
  export {};
@@ -1,29 +1,30 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { usePrimitives } from "../../index.js";
3
- /** Zahlenfeld = Field + Input(kind:"number") in einem — nimmt der Screen die
4
- * wiederholte id/name/value/onChange-Verdrahtung ab. `value` darf `undefined`
5
- * sein (leeres Feld), intern auf den `""`-Empty-State des Inputs gemappt.
6
- * Einheiten (€/%) gehören ins Label (`t("…Summe (€)")`), nicht als Badge. */
3
+ /** Number field = Field + Input(kind:"number"); avoids repeating the
4
+ * id/name/value/onChange wiring at each screen. `value` may be `undefined`
5
+ * (empty field) and is mapped internally to the Input's `""` empty state.
6
+ * Units (€/%) belong in the label (`t("…Summe (€)")`), not in a badge. */
7
7
  export function NumberField({ label, id, name, value, onChange, required, disabled, hideLabel, testId, grouping, }) {
8
8
  const { Field, Input } = usePrimitives();
9
9
  return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "number", id: id, name: name, value: value ?? "", onChange: onChange, required: required, disabled: disabled, ...(grouping !== undefined && { grouping }) }) }));
10
10
  }
11
- // Aliase markieren die Feld-Absicht am Call-Site (lesbarer als NumberField
12
- // überall) ohne eigene Wrapper-Funktion vorzutäuschen — beide sind identisch
13
- // zu NumberField, kein Ort für künftige geld-/prozent-spezifische Formatierung.
11
+ // Aliases convey the field's intent at the call site (more readable than
12
+ // using NumberField everywhere) without pretending to be wrapper functions;
13
+ // both are identical to NumberField and are not intended for future
14
+ // money-/percentage-specific formatting.
14
15
  export const MoneyField = NumberField;
15
16
  export const PercentField = NumberField;
16
- /** Textfeld = Field + Input(kind:"text"). */
17
+ /** Text field = Field + Input(kind:"text"). */
17
18
  export function TextField({ label, id, name, value, onChange, required, disabled, placeholder, autoComplete, hideLabel, testId, }) {
18
19
  const { Field, Input } = usePrimitives();
19
20
  return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "text", id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled, ...(placeholder !== undefined && { placeholder }), ...(autoComplete !== undefined && { autoComplete }) }) }));
20
21
  }
21
- /** Auswahlfeld = Field + Input(kind:"select"). */
22
+ /** Select field = Field + Input(kind:"select"). */
22
23
  export function SelectField({ label, id, name, value, onChange, options, required, disabled, hideLabel, testId, }) {
23
24
  const { Field, Input } = usePrimitives();
24
25
  return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "select", id: id, name: name, value: value, onChange: onChange, options: options, required: required, disabled: disabled }) }));
25
26
  }
26
- /** Datumsfeld = Field + Input(kind:"date"), ISO yyyy-mm-dd. */
27
+ /** Date field = Field + Input(kind:"date"), ISO yyyy-mm-dd. */
27
28
  export function DateField({ label, id, name, value, onChange, min, max, required, disabled, hideLabel, testId, }) {
28
29
  const { Field, Input } = usePrimitives();
29
30
  return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "date", id: id, name: name, value: value, onChange: (v) => onChange(v ?? ""), required: required, disabled: disabled, ...(min !== undefined && { min }), ...(max !== undefined && { max }) }) }));
@@ -33,17 +34,17 @@ export function BooleanField({ label, id, name, value, onChange, required, disab
33
34
  const { Field, Input } = usePrimitives();
34
35
  return (_jsx(Field, { id: id, label: label, required: required, layout: "inline", hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "boolean", id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled }) }));
35
36
  }
36
- /** Mehrzeiliges Textfeld = Field + Input(kind:"textarea"). */
37
+ /** Multi-line text field = Field + Input(kind:"textarea"). */
37
38
  export function TextareaField({ label, id, name, value, onChange, rows, required, disabled, hideLabel, testId, }) {
38
39
  const { Field, Input } = usePrimitives();
39
40
  return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "textarea", id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled, ...(rows !== undefined && { rows }) }) }));
40
41
  }
41
- /** Schieberegler = Field + Input(kind:"range"). */
42
+ /** Slider = Field + Input(kind:"range"). */
42
43
  export function RangeField({ label, id, name, value, onChange, min, max, step, required, disabled, hideLabel, testId, }) {
43
44
  const { Field, Input } = usePrimitives();
44
45
  return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: "range", id: id, name: name, value: value, onChange: onChange, min: min, max: max, required: required, disabled: disabled, ...(step !== undefined && { step }) }) }));
45
46
  }
46
- /** Datei-Upload = Field + Input(kind:"file"|"image") — FileRef-basiert. */
47
+ /** File upload = Field + Input(kind:"file"|"image"), based on FileRef. */
47
48
  export function FileField({ label, id, name, value, onChange, accept, variant = "file", entityType, fieldName, required, disabled, hideLabel, testId, }) {
48
49
  const { Field, Input } = usePrimitives();
49
50
  return (_jsx(Field, { id: id, label: label, required: required, hideLabel: hideLabel, testId: testId, children: _jsx(Input, { kind: variant, id: id, name: name, value: value, onChange: onChange, required: required, disabled: disabled, ...(accept !== undefined && { accept }), ...(entityType !== undefined && { entityType }), ...(fieldName !== undefined && { fieldName }) }) }));
@@ -1,6 +1,7 @@
1
1
  export { AiTextArea, type AiTextAreaProps, AiTextField, type AiTextFieldProps, } from "./ai-text-field.tsx";
2
2
  export type { StackedAreaDateFormat, StackedAreaInitialWindow, StackedAreaRangeOption, StackedAreaRanges, StackedAreaWindowSelection, } from "./chart-window.ts";
3
3
  export { type ChartMarker, type ChartSeries, type ChartTone, SegmentBarChart, type SegmentBarRow, StackedAreaChart, StackedBarChart, StatusBarChart, type StatusBarEntry, smoothPath, TimeseriesChart, type TimeseriesHeight, type TimeseriesPoint, type TimeseriesReferenceLine, type TimeseriesXAxis, type TimeseriesYAxis, } from "./charts.tsx";
4
+ export { ChoiceCards } from "./choice-cards.tsx";
4
5
  export { CollapsibleSection } from "./collapsible-section.tsx";
5
6
  export { type DashboardListColumn, DashboardListTable } from "./dashboard-list.tsx";
6
7
  export { DetailList } from "./detail-list.tsx";
@@ -1,9 +1,10 @@
1
- // Mid-Level-Widgets — Kompositionen über den Primitives (Card, DataTable,
2
- // Banner) + Theme-Tokens. Für Custom-Screens: erst hier schauen, dann bauen.
3
- // Katalog: docs.kumiko.rocks → Guides → Widgets; visueller Überblick im
4
- // styleguide-Sample.
1
+ // Mid-level widgets — compositions built from primitives (Card, DataTable,
2
+ // Banner) and theme tokens. For custom screens, check here before building.
3
+ // Catalog: docs.kumiko.rocks → Guides → Widgets; visual overview in the
4
+ // styleguide sample.
5
5
  export { AiTextArea, AiTextField, } from "./ai-text-field.js";
6
6
  export { SegmentBarChart, StackedAreaChart, StackedBarChart, StatusBarChart, smoothPath, TimeseriesChart, } from "./charts.js";
7
+ export { ChoiceCards } from "./choice-cards.js";
7
8
  export { CollapsibleSection } from "./collapsible-section.js";
8
9
  export { DashboardListTable } from "./dashboard-list.js";
9
10
  export { DetailList } from "./detail-list.js";
@@ -28,7 +28,7 @@ export function InfinityList({ query, payload, pageSize = 50, rows, nextCursor,
28
28
  // Discards a response whose request was superseded by a newer one before
29
29
  // it resolved (e.g. two searches fired in quick succession) — without
30
30
  // this, a slow earlier response can land after a faster later one and
31
- // overwrite it, leaving stale rows on screen (fw#1705).
31
+ // overwrite it, leaving stale rows on screen.
32
32
  const requestSeq = useRef(0);
33
33
  // IDs of the most recently loaded first page. refreshFirstPage() needs
34
34
  // this to tell "row dropped from page 1 elsewhere" (must be pruned) apart
@@ -84,7 +84,7 @@ export function InfinityList({ query, payload, pageSize = 50, rows, nextCursor,
84
84
  // to the front (newest-first feeds), rows it dropped (edited/deleted
85
85
  // elsewhere) are pruned, and everything beyond page 1 stays untouched.
86
86
  // A full reload would collapse already-accumulated pages and jump the
87
- // scroll position; see fw#1827.
87
+ // scroll position.
88
88
  // biome-ignore lint/correctness/useExhaustiveDependencies: payload goes through payloadKey
89
89
  const refreshFirstPage = useCallback(async () => {
90
90
  // Read, don't bump: a concurrent load() (e.g. the mount fetch still
@@ -127,7 +127,7 @@ export function InfinityList({ query, payload, pageSize = 50, rows, nextCursor,
127
127
  });
128
128
  }, [dispatcher, query, pageSize, payloadKey]);
129
129
  // Trailing debounce + in-flight coalesce: bulk SSE storms must not fan out
130
- // into one query per event (fw#1829). A refresh already running schedules
130
+ // into one query per event. A refresh already running schedules
131
131
  // exactly one trailing pass after it settles.
132
132
  const liveTimerRef = useRef(null);
133
133
  const liveInFlightRef = useRef(false);
@@ -1,7 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  export type ModeSwitchVariant = "outline" | "pill";
3
- /** Segmented-Control für sich ausschließende Modi — die prominente
4
- * Alternative zum vergrabenen <select>. */
3
+ /** Segmented control for mutually exclusive modes—the prominent
4
+ * alternative to the buried <select>. */
5
5
  export declare function ModeSwitch<T extends string>({ value, options, onChange, ariaLabel, variant, className, testId, }: {
6
6
  /** `null`: no option active. */
7
7
  readonly value: T | null;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/cn.js";
3
- /** Segmented-Control für sich ausschließende Modi — die prominente
4
- * Alternative zum vergrabenen <select>. */
3
+ /** Segmented control for mutually exclusive modes—the prominent
4
+ * alternative to the buried <select>. */
5
5
  export function ModeSwitch({ value, options, onChange, ariaLabel, variant = "outline", className, testId, }) {
6
6
  const pill = variant === "pill";
7
7
  return (_jsx("div", { "data-testid": testId, role: "group", "aria-label": ariaLabel, className: cn("flex min-h-8", pill
@@ -10,7 +10,7 @@ const MAX_CACHED_DOCUMENTS = 4;
10
10
  const cachedDocument = createBoundedLoadCache(MAX_CACHED_DOCUMENTS, (loaded) => {
11
11
  if (loaded.ok)
12
12
  void loaded.loadingTask.destroy();
13
- });
13
+ }, (loaded) => loaded.ok);
14
14
  function decodeBase64(base64) {
15
15
  const binary = atob(base64);
16
16
  const bytes = new Uint8Array(binary.length);
@@ -2,6 +2,8 @@ import type { ReactNode } from "react";
2
2
  export type PlanCardPrice = {
3
3
  readonly amount: string;
4
4
  readonly period?: string;
5
+ /** Shown before the amount, e.g. "from" when prices differ per provider. */
6
+ readonly prefix?: string;
5
7
  };
6
8
  export type PlanCardActionSlot = {
7
9
  readonly label: string;
@@ -6,7 +6,7 @@ export function PlanCard({ title, description, price, features, current = false,
6
6
  const t = useTranslation();
7
7
  const compact = variant === "compact";
8
8
  const footer = cta !== undefined || ctaLink !== undefined || secondaryAction !== undefined ? (_jsxs("div", { className: "flex w-full flex-col gap-2", children: [ctaLink !== undefined && (_jsx(Link, { href: ctaLink.href, variant: "button", target: ctaLink.target, className: "w-full justify-center", testId: testId !== undefined ? `${testId}-cta` : "plan-card-cta", children: ctaLink.label })), ctaLink === undefined && cta !== undefined && (_jsx(Button, { width: "full", disabled: cta.disabled, onClick: cta.onClick, testId: testId !== undefined ? `${testId}-cta` : "plan-card-cta", children: cta.label })), secondaryAction !== undefined && (_jsx(Button, { variant: "secondary", width: "full", disabled: secondaryAction.disabled, onClick: secondaryAction.onClick, children: secondaryAction.label }))] })) : undefined;
9
- return (_jsxs(Card, { testId: testId ?? "plan-card", className: cn(current && "border-primary"), slots: { footer }, children: [_jsxs("div", { className: cn("flex items-start justify-between gap-2", compact && "items-center"), children: [_jsx("h2", { className: cn("font-semibold", compact ? "text-base" : "text-lg"), children: title }), current && (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-current` : "plan-card-current", className: "inline-block rounded-xl bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary", children: t("kumiko.planCard.current") }))] }), description !== undefined && !compact && (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })), price !== undefined && (_jsx("div", { className: cn(compact ? "mt-1" : "mt-1 mb-3"), children: price === null ? (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-price-unavailable` : "plan-card-price-unavailable", className: "text-sm text-muted-foreground", children: t("kumiko.planCard.priceUnavailable") })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-2xl font-bold", children: price.amount }), price.period !== undefined && (_jsx("span", { className: "ml-1 text-sm text-muted-foreground", children: price.period }))] })) })), !compact && features !== undefined && features.length > 0 && (_jsx("ul", { className: "space-y-1 text-sm", children: features.map((feature, index) => (_jsxs("li", { className: "flex gap-2", children: [_jsx("span", { "aria-hidden": "true", children: "\u2713" }), _jsx("span", { children: feature })] }, index))) }))] }));
9
+ return (_jsxs(Card, { testId: testId ?? "plan-card", className: cn(current && "border-primary"), slots: { footer }, children: [_jsxs("div", { className: cn("flex items-start justify-between gap-2", compact && "items-center"), children: [_jsx("h2", { className: cn("font-semibold", compact ? "text-base" : "text-lg"), children: title }), current && (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-current` : "plan-card-current", className: "inline-block rounded-xl bg-primary/10 px-2.5 py-1 text-xs font-semibold text-primary", children: t("kumiko.planCard.current") }))] }), description !== undefined && !compact && (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description })), price !== undefined && (_jsx("div", { className: cn(compact ? "mt-1" : "mt-1 mb-3"), children: price === null ? (_jsx("span", { "data-testid": testId !== undefined ? `${testId}-price-unavailable` : "plan-card-price-unavailable", className: "text-sm text-muted-foreground", children: t("kumiko.planCard.priceUnavailable") })) : (_jsxs(_Fragment, { children: [price.prefix !== undefined && (_jsx("span", { className: "mr-1 text-sm text-muted-foreground", children: price.prefix })), _jsx("span", { className: "text-2xl font-bold", children: price.amount }), price.period !== undefined && (_jsx("span", { className: "ml-1 text-sm text-muted-foreground", children: price.period }))] })) })), !compact && features !== undefined && features.length > 0 && (_jsx("ul", { className: "space-y-1 text-sm", children: features.map((feature, index) => (_jsxs("li", { className: "flex gap-2", children: [_jsx("span", { "aria-hidden": "true", children: "\u2713" }), _jsx("span", { children: feature })] }, index))) }))] }));
10
10
  }
11
11
  export function PlanGrid({ children, testId }) {
12
12
  return (_jsx("div", { "data-testid": testId, className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: children }));
@@ -10,8 +10,7 @@ export type ProgressListRow = {
10
10
  /** Small line under the bar, e.g. "42 % repaid". */
11
11
  readonly sub?: string;
12
12
  };
13
- /** Liste aus Label/Wert-Kopfzeile + Fortschrittsbalken pro Eintrag (z.B.
14
- * Tilgungsfortschritt pro Kredit). */
13
+ /** Label/value header and progress bar for each entry (for example, loan repayment progress). */
15
14
  export declare function ProgressList({ rows, emptyContent, testId, }: {
16
15
  readonly rows: readonly ProgressListRow[];
17
16
  readonly emptyContent?: ReactNode;
@@ -1,8 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { ProgressBar } from "./progress-bar.js";
4
- /** Liste aus Label/Wert-Kopfzeile + Fortschrittsbalken pro Eintrag (z.B.
5
- * Tilgungsfortschritt pro Kredit). */
4
+ /** Label/value header and progress bar for each entry (for example, loan repayment progress). */
6
5
  export function ProgressList({ rows, emptyContent, testId, }) {
7
6
  if (rows.length === 0) {
8
7
  return _jsx("div", { "data-testid": testId, children: emptyContent });
@@ -2,25 +2,25 @@ import type { ReactNode } from "react";
2
2
  import { type DataTableProps } from "../../index.ts";
3
3
  export type QueryTableColumn = {
4
4
  readonly field: string;
5
- /** Translated Header-Label (i18n-Key auflösen liegt beim Caller). */
5
+ /** Translated header label (resolving the i18n key is the caller's responsibility). */
6
6
  readonly label: string;
7
- /** Field-Type für die Zell-Formatierung. Default "text". */
7
+ /** Field type for cell formatting. Default: "text". */
8
8
  readonly type?: string;
9
9
  };
10
10
  export type QueryTableProps<TData = unknown> = {
11
11
  /** Dispatcher-Query-Type (`<feature>:query:<entity>:<verb>`). */
12
12
  readonly query: string;
13
13
  readonly payload?: unknown;
14
- /** SSE-Invalidierung — refetcht bei Entity-Events (useQuery live-mode). */
14
+ /** SSE invalidation — refetches on entity events (useQuery live mode). */
15
15
  readonly live?: boolean;
16
16
  readonly columns: readonly QueryTableColumn[];
17
- /** Rows aus dem Query-Result ziehen. Default: Result selbst als Array. */
17
+ /** Extracts rows from the query result. Default: the result itself as an array. */
18
18
  readonly rows?: (data: TData) => readonly Readonly<Record<string, unknown>>[];
19
- /** Row-Id für Keys/Row-Click. Default: `row.id`, sonst der Index. */
19
+ /** Row ID for keys and row clicks. Default: `row.id`, otherwise the index. */
20
20
  readonly rowId?: (row: Readonly<Record<string, unknown>>, index: number) => string;
21
21
  readonly onRowClick?: DataTableProps["onRowClick"];
22
22
  readonly rowActions?: DataTableProps["rowActions"];
23
- /** Empty-Inhalt — Default ist der Standard-Empty-State der Listen. */
23
+ /** Empty content — defaults to the standard list empty state. */
24
24
  readonly emptyState?: ReactNode;
25
25
  readonly toolbarTitle?: ReactNode;
26
26
  readonly toolbarEnd?: ReactNode;