@cosmicdrift/kumiko-renderer 0.289.0 → 0.290.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.290.0",
|
|
4
4
|
"description": "Platform-agnostic React renderer for Kumiko screens. Contains the shared logic — primitives-contract, hooks, KumikoScreen, navigation & SSE abstractions — that any platform-specific renderer (web, native) composes. No DOM, no EventSource, no react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
19
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
20
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
18
|
+
"@cosmicdrift/kumiko-framework": "0.290.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.290.0",
|
|
20
|
+
"@cosmicdrift/kumiko-types": "0.290.0",
|
|
21
21
|
"react": "^19.2.6",
|
|
22
22
|
"temporal-polyfill": "^0.3.2",
|
|
23
23
|
"zod": "^4.4.3"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@types/react-dom": "^19.2.3",
|
|
29
29
|
"jsdom": "^29.1.1",
|
|
30
30
|
"react-dom": "^19.2.6",
|
|
31
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
31
|
+
"@cosmicdrift/kumiko-locale-de": "0.290.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, spyOn, test } from "bun:test";
|
|
2
|
+
import type { EntityEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
3
|
import { mergeSearchParamsIntoInitial } from "../app/kumiko-screen";
|
|
4
|
+
import { layoutFieldNames } from "../app/layout-fields";
|
|
3
5
|
|
|
4
6
|
type FieldDef = {
|
|
5
7
|
type?: string;
|
|
@@ -188,6 +190,22 @@ describe("mergeSearchParamsIntoInitial — coercion (every field URL-prefillable
|
|
|
188
190
|
expect(result["ownerId"]).toBe("");
|
|
189
191
|
});
|
|
190
192
|
|
|
193
|
+
test("renderableFields from a groups-only layout: searchParam on a group field is kept", () => {
|
|
194
|
+
const screen: EntityEditScreenDefinition = {
|
|
195
|
+
id: "s",
|
|
196
|
+
type: "entityEdit",
|
|
197
|
+
entity: "e",
|
|
198
|
+
layout: { sections: [{ fields: [], groups: [{ title: "g", fields: ["status"] }] }] },
|
|
199
|
+
};
|
|
200
|
+
const fields: Record<string, FieldDef> = { status: { type: "text", default: "draft" } };
|
|
201
|
+
const result = mergeWithAllFieldsUrlPrefillable(
|
|
202
|
+
fields,
|
|
203
|
+
{ status: "approved" },
|
|
204
|
+
layoutFieldNames(screen),
|
|
205
|
+
);
|
|
206
|
+
expect(result["status"]).toBe("approved");
|
|
207
|
+
});
|
|
208
|
+
|
|
191
209
|
test("no renderableFields set given (undefined): behaves as before, all fields eligible", () => {
|
|
192
210
|
const fields: Record<string, FieldDef> = { ownerId: { type: "text" } };
|
|
193
211
|
const result = mergeWithAllFieldsUrlPrefillable(fields, { ownerId: "user-123" });
|
|
@@ -41,6 +41,21 @@ describe("buildFormSchema", () => {
|
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
+
test("required field inside a groups-only section → presence issue", () => {
|
|
45
|
+
const entity = entityWith({ name: { type: "text", required: true } });
|
|
46
|
+
const screen: EntityEditScreenDefinition = {
|
|
47
|
+
id: "s",
|
|
48
|
+
type: "entityEdit",
|
|
49
|
+
entity: "e",
|
|
50
|
+
layout: { sections: [{ fields: [], groups: [{ title: "g", fields: ["name"] }] }] },
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const result = buildFormSchema(entity, screen).safeParse({ name: "" });
|
|
54
|
+
expect(result.success).toBe(false);
|
|
55
|
+
if (result.success) return;
|
|
56
|
+
expect(result.error.issues[0]?.path).toEqual(["name"]);
|
|
57
|
+
});
|
|
58
|
+
|
|
44
59
|
test("required field missing → issue carries the required-field i18nKey override", () => {
|
|
45
60
|
const entity = entityWith({ name: { type: "text", required: true } });
|
|
46
61
|
const screen = screenWith(["name"]);
|
package/src/app/layout-fields.ts
CHANGED
|
@@ -2,7 +2,11 @@ import type {
|
|
|
2
2
|
EditFieldSpec,
|
|
3
3
|
EntityEditScreenDefinition,
|
|
4
4
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
isFieldsEditSection,
|
|
7
|
+
normalizeEditField,
|
|
8
|
+
sectionFieldSpecs,
|
|
9
|
+
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
6
10
|
|
|
7
11
|
// Normalized field specs actually rendered by the screen's layout, extension
|
|
8
12
|
// (and relatedList) sections skipped. Both this and `layoutFieldNames` key
|
|
@@ -15,7 +19,7 @@ export function layoutEditFields(
|
|
|
15
19
|
const specs: Exclude<EditFieldSpec, string>[] = [];
|
|
16
20
|
for (const section of screen.layout.sections) {
|
|
17
21
|
if (!isFieldsEditSection(section)) continue;
|
|
18
|
-
for (const spec of section
|
|
22
|
+
for (const spec of sectionFieldSpecs(section)) {
|
|
19
23
|
specs.push(normalizeEditField(spec));
|
|
20
24
|
}
|
|
21
25
|
}
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
import type {
|
|
24
24
|
EditFieldSpec,
|
|
25
|
-
EditFieldsSection,
|
|
26
25
|
EditLayout,
|
|
27
26
|
EntityDefinition,
|
|
28
27
|
EntityEditScreenDefinition,
|
|
@@ -32,18 +31,9 @@ import {
|
|
|
32
31
|
isFieldsEditSection,
|
|
33
32
|
normalizeEditField,
|
|
34
33
|
PROJECTION_DETAIL_ENTITY as PROJECTION_DETAIL_PSEUDO_ENTITY,
|
|
34
|
+
sectionFieldSpecs,
|
|
35
35
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
36
36
|
|
|
37
|
-
// The boot-validator treats `fields`/`groups` as mutually exclusive per
|
|
38
|
-
// section, but this reads both anyway: an extra name the current section
|
|
39
|
-
// shape can never carry is harmless, while a name computeEditViewModel does
|
|
40
|
-
// reference and this shim omitted throws downstream ("references unknown
|
|
41
|
-
// field ... on entity"). Union is strictly safer than picking one branch.
|
|
42
|
-
function allFieldSpecs(section: EditFieldsSection): readonly EditFieldSpec[] {
|
|
43
|
-
const grouped = section.groups?.flatMap((group) => group.fields) ?? [];
|
|
44
|
-
return [...section.fields, ...grouped];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
/** Minimale EntityDefinition aus den Layout-Feldern: jedes Feld ein Text-
|
|
48
38
|
* Feld — computeEditViewModel liest nur `fields[<f>].type`, Text reicht für
|
|
49
39
|
* eine reine Anzeige (kein Select/Number-spezifisches Rendering nötig). */
|
|
@@ -52,7 +42,7 @@ export function synthesizeProjectionDetailEntity(layout: EditLayout): EntityDefi
|
|
|
52
42
|
for (const section of layout.sections) {
|
|
53
43
|
// relatedList and extension sections carry no `fields` to synthesize.
|
|
54
44
|
if (!isFieldsEditSection(section)) continue;
|
|
55
|
-
for (const spec of
|
|
45
|
+
for (const spec of sectionFieldSpecs(section)) {
|
|
56
46
|
fields[normalizeEditField(spec).field] = { type: "text" };
|
|
57
47
|
}
|
|
58
48
|
}
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.290.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Layout field readers see groups[].fields",
|
|
6
|
+
"detail": "layoutEditFields flattens section.groups, so required fields inside a groups-only section are presence-validated by buildFormSchema and survive the renderableFields gate for search-param prefills."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.283.0",
|
|
4
10
|
"type": "fix",
|