@cosmicdrift/kumiko-renderer-web 0.171.0 → 0.171.2
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer-web",
|
|
3
|
-
"version": "0.171.
|
|
3
|
+
"version": "0.171.2",
|
|
4
4
|
"description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"./styles.css": "./src/styles.css"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.171.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.171.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "0.171.
|
|
19
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.171.2",
|
|
20
|
+
"@cosmicdrift/kumiko-headless": "0.171.2",
|
|
21
|
+
"@cosmicdrift/kumiko-renderer": "0.171.2",
|
|
22
22
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
23
23
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
24
24
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -472,12 +472,17 @@ describe("DataTable", () => {
|
|
|
472
472
|
|
|
473
473
|
// Infinite-Scroll Sentinel: rendert sentinel-div, zeigt Spinner wenn
|
|
474
474
|
// loadingMore, den i18n End-of-list-Marker (kumiko.list.end-of-list)
|
|
475
|
-
// wenn !hasMore
|
|
475
|
+
// wenn !hasMore und genug Zeilen geladen sind (kurze Listen brauchen
|
|
476
|
+
// keinen Marker — sie zeigen ihr Ende selbst). IntersectionObserver
|
|
476
477
|
// selbst ist in jsdom unmocked — wir testen nur die Marker, der
|
|
477
478
|
// Observer-Fire-Pfad ist im KumikoScreen.EntityListBody.
|
|
478
479
|
describe("InfiniteSentinel", () => {
|
|
479
480
|
const cols = [{ field: "name", label: "Name", type: "string", sortable: false }] as const;
|
|
480
481
|
const oneRow = [{ id: "r1", values: { name: "A" } }];
|
|
482
|
+
const manyRows = Array.from({ length: 25 }, (_, i) => ({
|
|
483
|
+
id: `r${i}`,
|
|
484
|
+
values: { name: `A${i}` },
|
|
485
|
+
}));
|
|
481
486
|
|
|
482
487
|
test("ohne onReachEnd: kein Sentinel im DOM", () => {
|
|
483
488
|
render(<DataTable columns={cols} rows={oneRow} testId="dt" />);
|
|
@@ -516,11 +521,11 @@ describe("DataTable", () => {
|
|
|
516
521
|
expect(screen.getByTestId("dt-sentinel").querySelector("svg")).not.toBeNull();
|
|
517
522
|
});
|
|
518
523
|
|
|
519
|
-
test("hasMore=false: 'End of list' Marker statt Sentinel-Wirkung", () => {
|
|
524
|
+
test("hasMore=false + viele Zeilen: 'End of list' Marker statt Sentinel-Wirkung", () => {
|
|
520
525
|
render(
|
|
521
526
|
<DataTable
|
|
522
527
|
columns={cols}
|
|
523
|
-
rows={
|
|
528
|
+
rows={manyRows}
|
|
524
529
|
testId="dt"
|
|
525
530
|
onReachEnd={mock()}
|
|
526
531
|
loadingMore={false}
|
|
@@ -531,6 +536,21 @@ describe("DataTable", () => {
|
|
|
531
536
|
expect(screen.getByTestId("dt-sentinel-end").textContent).toContain("End of list");
|
|
532
537
|
});
|
|
533
538
|
|
|
539
|
+
test("hasMore=false + kurze Liste: kein End-Marker (#1699)", () => {
|
|
540
|
+
render(
|
|
541
|
+
<DataTable
|
|
542
|
+
columns={cols}
|
|
543
|
+
rows={oneRow}
|
|
544
|
+
testId="dt"
|
|
545
|
+
onReachEnd={mock()}
|
|
546
|
+
loadingMore={false}
|
|
547
|
+
hasMore={false}
|
|
548
|
+
/>,
|
|
549
|
+
);
|
|
550
|
+
expect(screen.queryByTestId("dt-sentinel-end")).toBeNull();
|
|
551
|
+
expect(screen.getByTestId("dt-sentinel").textContent).toBe("");
|
|
552
|
+
});
|
|
553
|
+
|
|
534
554
|
test("hasMore=false + de-Locale: Marker kommt aus i18n statt hartcodiert", async () => {
|
|
535
555
|
const { LocaleProvider, createStaticLocaleResolver, kumikoDefaultTranslations } =
|
|
536
556
|
await import("@cosmicdrift/kumiko-renderer");
|
|
@@ -541,7 +561,7 @@ describe("DataTable", () => {
|
|
|
541
561
|
>
|
|
542
562
|
<DataTable
|
|
543
563
|
columns={cols}
|
|
544
|
-
rows={
|
|
564
|
+
rows={manyRows}
|
|
545
565
|
testId="dt"
|
|
546
566
|
onReachEnd={mock()}
|
|
547
567
|
loadingMore={false}
|
package/src/primitives/index.tsx
CHANGED
|
@@ -654,6 +654,7 @@ function DefaultDataTable({
|
|
|
654
654
|
onReachEnd={onReachEnd}
|
|
655
655
|
loadingMore={loadingMore === true}
|
|
656
656
|
hasMore={hasMore !== false}
|
|
657
|
+
rowsCount={rows.length}
|
|
657
658
|
testId={testId !== undefined ? `${testId}-sentinel` : "render-list-sentinel"}
|
|
658
659
|
/>
|
|
659
660
|
</>
|
|
@@ -1018,15 +1019,23 @@ function RowActionsKebab({
|
|
|
1018
1019
|
// lädt, ignorieren wir weitere Sichtbar-Events). Kein observer in
|
|
1019
1020
|
// Server-Side-Render, kein observer wenn hasMore=false — dann zeigt
|
|
1020
1021
|
// der Sentinel nur den End-of-list-Hinweis.
|
|
1022
|
+
// ponytail: End-Marker nur ab END_LABEL_MIN_ROWS Zeilen — eine kurze
|
|
1023
|
+
// Liste (eine einzige Page) zeigt ihr Ende selbst, da wäre der Marker
|
|
1024
|
+
// nur Lärm. Schwellwert = eine Page; per Prop übersteuerbar falls Apps
|
|
1025
|
+
// andere Page-Größen fahren.
|
|
1026
|
+
const END_LABEL_MIN_ROWS = 20;
|
|
1027
|
+
|
|
1021
1028
|
function InfiniteSentinel({
|
|
1022
1029
|
onReachEnd,
|
|
1023
1030
|
loadingMore,
|
|
1024
1031
|
hasMore,
|
|
1032
|
+
rowsCount,
|
|
1025
1033
|
testId,
|
|
1026
1034
|
}: {
|
|
1027
1035
|
readonly onReachEnd: () => void;
|
|
1028
1036
|
readonly loadingMore: boolean;
|
|
1029
1037
|
readonly hasMore: boolean;
|
|
1038
|
+
readonly rowsCount: number;
|
|
1030
1039
|
readonly testId?: string;
|
|
1031
1040
|
}): ReactNode {
|
|
1032
1041
|
const t = useTranslation();
|
|
@@ -1057,23 +1066,28 @@ function InfiniteSentinel({
|
|
|
1057
1066
|
return () => observer.disconnect();
|
|
1058
1067
|
}, [onReachEnd, loadingMore, hasMore]);
|
|
1059
1068
|
|
|
1069
|
+
const showEndLabel = !hasMore && rowsCount >= END_LABEL_MIN_ROWS;
|
|
1070
|
+
|
|
1060
1071
|
return (
|
|
1061
1072
|
<div
|
|
1062
1073
|
ref={ref}
|
|
1063
1074
|
data-testid={testId}
|
|
1064
|
-
className=
|
|
1075
|
+
className={cn(
|
|
1076
|
+
"flex items-center justify-center text-sm text-muted-foreground",
|
|
1077
|
+
showEndLabel || loadingMore ? "py-4" : "",
|
|
1078
|
+
)}
|
|
1065
1079
|
>
|
|
1066
|
-
{
|
|
1080
|
+
{showEndLabel ? (
|
|
1067
1081
|
<span data-testid={testId !== undefined ? `${testId}-end` : undefined}>
|
|
1068
1082
|
{t("kumiko.list.end-of-list")}
|
|
1069
1083
|
</span>
|
|
1070
1084
|
) : loadingMore ? (
|
|
1071
1085
|
<Loader2 className="size-4 animate-spin" aria-hidden="true" />
|
|
1072
|
-
) : (
|
|
1073
|
-
// Unsichtbar-Spacer
|
|
1074
|
-
// braucht ein DOM-Node, der User soll aber nichts sehen.
|
|
1086
|
+
) : hasMore ? (
|
|
1087
|
+
// Unsichtbar-Spacer solange noch geladen werden kann — der
|
|
1088
|
+
// Observer braucht ein DOM-Node, der User soll aber nichts sehen.
|
|
1075
1089
|
<span aria-hidden="true"> </span>
|
|
1076
|
-
)}
|
|
1090
|
+
) : null}
|
|
1077
1091
|
</div>
|
|
1078
1092
|
);
|
|
1079
1093
|
}
|