@cosmicdrift/kumiko-renderer-web 0.172.0 → 0.173.1
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.
|
|
3
|
+
"version": "0.173.1",
|
|
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.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
19
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.173.1",
|
|
20
|
+
"@cosmicdrift/kumiko-headless": "0.173.1",
|
|
21
|
+
"@cosmicdrift/kumiko-renderer": "0.173.1",
|
|
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",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, mock, test } from "bun:test";
|
|
11
11
|
import userEvent from "@testing-library/user-event";
|
|
12
|
-
import { defaultPrimitives } from "../primitives";
|
|
12
|
+
import { defaultPrimitives, END_LABEL_MIN_ROWS } from "../primitives";
|
|
13
13
|
import { PageSection, Stack } from "../primitives/layout";
|
|
14
14
|
import { fireEvent, render, screen } from "./test-utils";
|
|
15
15
|
|
|
@@ -483,6 +483,8 @@ describe("DataTable", () => {
|
|
|
483
483
|
id: `r${i}`,
|
|
484
484
|
values: { name: `A${i}` },
|
|
485
485
|
}));
|
|
486
|
+
const rowsOfCount = (n: number) =>
|
|
487
|
+
Array.from({ length: n }, (_, i) => ({ id: `r${i}`, values: { name: `A${i}` } }));
|
|
486
488
|
|
|
487
489
|
test("ohne onReachEnd: kein Sentinel im DOM", () => {
|
|
488
490
|
render(<DataTable columns={cols} rows={oneRow} testId="dt" />);
|
|
@@ -551,6 +553,34 @@ describe("DataTable", () => {
|
|
|
551
553
|
expect(screen.getByTestId("dt-sentinel").textContent).toBe("");
|
|
552
554
|
});
|
|
553
555
|
|
|
556
|
+
test(`hasMore=false + ${END_LABEL_MIN_ROWS - 1} Zeilen: kein End-Marker (Grenzfall unter Threshold)`, () => {
|
|
557
|
+
render(
|
|
558
|
+
<DataTable
|
|
559
|
+
columns={cols}
|
|
560
|
+
rows={rowsOfCount(END_LABEL_MIN_ROWS - 1)}
|
|
561
|
+
testId="dt"
|
|
562
|
+
onReachEnd={mock()}
|
|
563
|
+
loadingMore={false}
|
|
564
|
+
hasMore={false}
|
|
565
|
+
/>,
|
|
566
|
+
);
|
|
567
|
+
expect(screen.queryByTestId("dt-sentinel-end")).toBeNull();
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
test(`hasMore=false + ${END_LABEL_MIN_ROWS} Zeilen: End-Marker (Grenzfall auf Threshold)`, () => {
|
|
571
|
+
render(
|
|
572
|
+
<DataTable
|
|
573
|
+
columns={cols}
|
|
574
|
+
rows={rowsOfCount(END_LABEL_MIN_ROWS)}
|
|
575
|
+
testId="dt"
|
|
576
|
+
onReachEnd={mock()}
|
|
577
|
+
loadingMore={false}
|
|
578
|
+
hasMore={false}
|
|
579
|
+
/>,
|
|
580
|
+
);
|
|
581
|
+
expect(screen.getByTestId("dt-sentinel-end")).not.toBeNull();
|
|
582
|
+
});
|
|
583
|
+
|
|
554
584
|
test("hasMore=false + de-Locale: Marker kommt aus i18n statt hartcodiert", async () => {
|
|
555
585
|
const { LocaleProvider, createStaticLocaleResolver, kumikoDefaultTranslations } =
|
|
556
586
|
await import("@cosmicdrift/kumiko-renderer");
|
|
@@ -178,4 +178,71 @@ describe("Reference-field create-in-place (#1681)", () => {
|
|
|
178
178
|
expect(dispatcher.writes).toHaveLength(0);
|
|
179
179
|
expect(screen.getByTestId("combobox-kumiko-edit-assignee").textContent).not.toContain("widget");
|
|
180
180
|
});
|
|
181
|
+
|
|
182
|
+
// The three negative gates that decide whether the "+ Create" footer
|
|
183
|
+
// renders at all (render-field.tsx ReferenceInput.canCreate). A regression
|
|
184
|
+
// here silently shows a create button the server would reject.
|
|
185
|
+
describe("create-footer gates: no combobox-*-create in the DOM", () => {
|
|
186
|
+
test("allowCreate: false on the target screen", async () => {
|
|
187
|
+
const noCreateSchema: FeatureSchema = {
|
|
188
|
+
...catalogSchema,
|
|
189
|
+
screens: [{ ...widgetCreateScreen, allowCreate: false }],
|
|
190
|
+
};
|
|
191
|
+
render(
|
|
192
|
+
<AppFeaturesProvider features={[tasksSchema, noCreateSchema]}>
|
|
193
|
+
<DispatcherProvider dispatcher={makeDispatcher()}>
|
|
194
|
+
<KumikoScreen schema={tasksSchema} qn="tasks:screen:task-edit" />
|
|
195
|
+
</DispatcherProvider>
|
|
196
|
+
</AppFeaturesProvider>,
|
|
197
|
+
);
|
|
198
|
+
await waitFor(() => screen.getByTestId("render-edit-form"));
|
|
199
|
+
await userEvent.setup().click(screen.getByTestId("combobox-kumiko-edit-assignee"));
|
|
200
|
+
expect(screen.queryByTestId("combobox-kumiko-edit-assignee-create")).toBeNull();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("target screen gated by a role the user doesn't have (no UserRolesProvider mounted)", async () => {
|
|
204
|
+
const roleGatedSchema: FeatureSchema = {
|
|
205
|
+
...catalogSchema,
|
|
206
|
+
screens: [{ ...widgetCreateScreen, access: { roles: ["Admin"] } }],
|
|
207
|
+
};
|
|
208
|
+
render(
|
|
209
|
+
<AppFeaturesProvider features={[tasksSchema, roleGatedSchema]}>
|
|
210
|
+
<DispatcherProvider dispatcher={makeDispatcher()}>
|
|
211
|
+
<KumikoScreen schema={tasksSchema} qn="tasks:screen:task-edit" />
|
|
212
|
+
</DispatcherProvider>
|
|
213
|
+
</AppFeaturesProvider>,
|
|
214
|
+
);
|
|
215
|
+
await waitFor(() => screen.getByTestId("render-edit-form"));
|
|
216
|
+
await userEvent.setup().click(screen.getByTestId("combobox-kumiko-edit-assignee"));
|
|
217
|
+
expect(screen.queryByTestId("combobox-kumiko-edit-assignee-create")).toBeNull();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("field.readOnly on the reference field itself", async () => {
|
|
221
|
+
const readOnlySchema: FeatureSchema = {
|
|
222
|
+
...tasksSchema,
|
|
223
|
+
screens: [
|
|
224
|
+
{
|
|
225
|
+
...taskEditScreen,
|
|
226
|
+
layout: {
|
|
227
|
+
sections: [
|
|
228
|
+
{
|
|
229
|
+
title: "Basics",
|
|
230
|
+
fields: ["title", { field: "assignee", readOnly: true }, "tags"],
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
render(
|
|
238
|
+
<AppFeaturesProvider features={[readOnlySchema, catalogSchema]}>
|
|
239
|
+
<DispatcherProvider dispatcher={makeDispatcher()}>
|
|
240
|
+
<KumikoScreen schema={readOnlySchema} qn="tasks:screen:task-edit" />
|
|
241
|
+
</DispatcherProvider>
|
|
242
|
+
</AppFeaturesProvider>,
|
|
243
|
+
);
|
|
244
|
+
await waitFor(() => screen.getByTestId("render-edit-form"));
|
|
245
|
+
expect(screen.queryByTestId("combobox-kumiko-edit-assignee-create")).toBeNull();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
181
248
|
});
|
|
@@ -443,6 +443,23 @@ describe("WorkspaceShell", () => {
|
|
|
443
443
|
expect(innerMain?.classList.contains("flex-1")).toBe(true);
|
|
444
444
|
expect(innerMain?.classList.contains("overflow-auto")).toBe(true);
|
|
445
445
|
});
|
|
446
|
+
|
|
447
|
+
// #1692: default (no fill) must not force viewport-lock classes
|
|
448
|
+
test("without fill does not apply h-svh or min-h-0", () => {
|
|
449
|
+
renderShell(
|
|
450
|
+
<WorkspaceShell
|
|
451
|
+
brand={<div>Brand</div>}
|
|
452
|
+
schema={schema}
|
|
453
|
+
user={{ id: "u1", roles: ["admin"] }}
|
|
454
|
+
>
|
|
455
|
+
<div>content</div>
|
|
456
|
+
</WorkspaceShell>,
|
|
457
|
+
);
|
|
458
|
+
const root = document.querySelector('[data-slot="sidebar-wrapper"]');
|
|
459
|
+
expect(root?.classList.contains("h-svh")).toBe(false);
|
|
460
|
+
const inset = document.querySelector('[data-slot="sidebar-inset"]');
|
|
461
|
+
expect(inset?.classList.contains("min-h-0")).toBe(false);
|
|
462
|
+
});
|
|
446
463
|
});
|
|
447
464
|
|
|
448
465
|
// ---------------------------------------------------------------------------
|
|
@@ -108,4 +108,15 @@ describe("formatDateForInput", () => {
|
|
|
108
108
|
expect(roundtrip).toBeDefined();
|
|
109
109
|
if (roundtrip !== undefined) expect(toIso(roundtrip)).toBe("2026-04-25");
|
|
110
110
|
});
|
|
111
|
+
|
|
112
|
+
// #1659: y/m/d locale branch of localeDateOrder (ISO-like)
|
|
113
|
+
test("ja-JP locale (y/m/d order), re-parseable", () => {
|
|
114
|
+
const formatted = formatDateForInput(
|
|
115
|
+
Temporal.PlainDate.from({ year: 2026, month: 4, day: 25 }),
|
|
116
|
+
"ja-JP",
|
|
117
|
+
);
|
|
118
|
+
const roundtrip = parseTypedDate(formatted, "ja-JP");
|
|
119
|
+
expect(roundtrip).toBeDefined();
|
|
120
|
+
if (roundtrip !== undefined) expect(toIso(roundtrip)).toBe("2026-04-25");
|
|
121
|
+
});
|
|
111
122
|
});
|
package/src/primitives/index.tsx
CHANGED
|
@@ -1019,11 +1019,8 @@ function RowActionsKebab({
|
|
|
1019
1019
|
// lädt, ignorieren wir weitere Sichtbar-Events). Kein observer in
|
|
1020
1020
|
// Server-Side-Render, kein observer wenn hasMore=false — dann zeigt
|
|
1021
1021
|
// der Sentinel nur den End-of-list-Hinweis.
|
|
1022
|
-
// ponytail:
|
|
1023
|
-
|
|
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;
|
|
1022
|
+
// ponytail: Short lists do not need an end marker.
|
|
1023
|
+
export const END_LABEL_MIN_ROWS = 20;
|
|
1027
1024
|
|
|
1028
1025
|
function InfiniteSentinel({
|
|
1029
1026
|
onReachEnd,
|