@cosmicdrift/kumiko-headless 0.21.1 → 0.22.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 +1 -1
- package/src/index.ts +2 -0
- package/src/view-model/__tests__/edit.test.ts +18 -10
- package/src/view-model/edit.ts +13 -1
- package/src/view-model/index.ts +2 -0
- package/src/view-model/types.ts +14 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-headless",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Headless UI logic for Kumiko — Dispatcher contract, Form-Controller, View-Model, Nav-Resolver. Plattform- und React-frei; jeder Renderer (renderer, renderer-web, renderer-native, …) komponiert darauf.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
package/src/index.ts
CHANGED
|
@@ -64,7 +64,9 @@ export { createStore, shallowEqual } from "./store";
|
|
|
64
64
|
export type {
|
|
65
65
|
ComputeEditViewModelInput,
|
|
66
66
|
ComputeListViewModelInput,
|
|
67
|
+
EditExtensionSectionViewModel,
|
|
67
68
|
EditFieldSpec,
|
|
69
|
+
EditFieldsSectionViewModel,
|
|
68
70
|
EditFieldViewModel,
|
|
69
71
|
EditSectionSpec,
|
|
70
72
|
EditSectionViewModel,
|
|
@@ -4,6 +4,14 @@ import type {
|
|
|
4
4
|
EntityEditScreenDefinition,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
6
6
|
import { computeEditViewModel } from "../edit";
|
|
7
|
+
import type { EditFieldsSectionViewModel, EditSectionViewModel } from "../types";
|
|
8
|
+
|
|
9
|
+
function asFields(s: EditSectionViewModel | undefined): EditFieldsSectionViewModel {
|
|
10
|
+
if (s === undefined || s.kind !== "fields") {
|
|
11
|
+
throw new Error(`expected fields-section, got ${s?.kind ?? "undefined"}`);
|
|
12
|
+
}
|
|
13
|
+
return s;
|
|
14
|
+
}
|
|
7
15
|
|
|
8
16
|
const orderEntity = {
|
|
9
17
|
fields: {
|
|
@@ -44,8 +52,8 @@ describe("computeEditViewModel", () => {
|
|
|
44
52
|
expect(vm.sections).toHaveLength(1);
|
|
45
53
|
const section = vm.sections[0];
|
|
46
54
|
expect(section?.title).toBe("Hauptdaten");
|
|
47
|
-
expect(section
|
|
48
|
-
expect(section
|
|
55
|
+
expect(asFields(section).columns).toBe(1); // default
|
|
56
|
+
expect(asFields(section).fields).toEqual([
|
|
49
57
|
{
|
|
50
58
|
field: "customerName",
|
|
51
59
|
label: "orders:entity:order:field:customerName",
|
|
@@ -81,8 +89,8 @@ describe("computeEditViewModel", () => {
|
|
|
81
89
|
featureName: "orders",
|
|
82
90
|
});
|
|
83
91
|
|
|
84
|
-
expect(vm.sections[0]
|
|
85
|
-
expect(vm.sections[1]
|
|
92
|
+
expect(asFields(vm.sections[0]).columns).toBe(2);
|
|
93
|
+
expect(asFields(vm.sections[1]).columns).toBe(1);
|
|
86
94
|
});
|
|
87
95
|
|
|
88
96
|
test("visible predicate evaluated against current values (live-reactive)", () => {
|
|
@@ -110,7 +118,7 @@ describe("computeEditViewModel", () => {
|
|
|
110
118
|
translate,
|
|
111
119
|
featureName: "orders",
|
|
112
120
|
});
|
|
113
|
-
const reasonHidden = hidden.sections[0]
|
|
121
|
+
const reasonHidden = asFields(hidden.sections[0]).fields[1];
|
|
114
122
|
expect(reasonHidden?.visible).toBe(false);
|
|
115
123
|
expect(reasonHidden?.required).toBe(false);
|
|
116
124
|
|
|
@@ -121,7 +129,7 @@ describe("computeEditViewModel", () => {
|
|
|
121
129
|
translate,
|
|
122
130
|
featureName: "orders",
|
|
123
131
|
});
|
|
124
|
-
const reasonShown = shown.sections[0]
|
|
132
|
+
const reasonShown = asFields(shown.sections[0]).fields[1];
|
|
125
133
|
expect(reasonShown?.visible).toBe(true);
|
|
126
134
|
expect(reasonShown?.required).toBe(true);
|
|
127
135
|
});
|
|
@@ -149,7 +157,7 @@ describe("computeEditViewModel", () => {
|
|
|
149
157
|
translate,
|
|
150
158
|
featureName: "orders",
|
|
151
159
|
});
|
|
152
|
-
expect(nonAdmin.sections[0]
|
|
160
|
+
expect(asFields(nonAdmin.sections[0]).fields[0]?.readOnly).toBe(true);
|
|
153
161
|
|
|
154
162
|
const admin = computeEditViewModel({
|
|
155
163
|
screen,
|
|
@@ -159,7 +167,7 @@ describe("computeEditViewModel", () => {
|
|
|
159
167
|
translate,
|
|
160
168
|
featureName: "orders",
|
|
161
169
|
});
|
|
162
|
-
expect(admin.sections[0]
|
|
170
|
+
expect(asFields(admin.sections[0]).fields[0]?.readOnly).toBe(false);
|
|
163
171
|
});
|
|
164
172
|
|
|
165
173
|
test("screen-level required override wins over entity-level required", () => {
|
|
@@ -180,7 +188,7 @@ describe("computeEditViewModel", () => {
|
|
|
180
188
|
featureName: "orders",
|
|
181
189
|
});
|
|
182
190
|
|
|
183
|
-
expect(vm.sections[0]
|
|
191
|
+
expect(asFields(vm.sections[0]).fields[0]?.required).toBe(false);
|
|
184
192
|
});
|
|
185
193
|
|
|
186
194
|
test("id is extracted from values or null on create (no existing row)", () => {
|
|
@@ -237,6 +245,6 @@ describe("computeEditViewModel", () => {
|
|
|
237
245
|
translate,
|
|
238
246
|
featureName: "orders",
|
|
239
247
|
});
|
|
240
|
-
expect(vm.sections[0]
|
|
248
|
+
expect(asFields(vm.sections[0]).fields[0]?.span).toBe(2);
|
|
241
249
|
});
|
|
242
250
|
});
|
package/src/view-model/edit.ts
CHANGED
|
@@ -3,7 +3,11 @@ import type {
|
|
|
3
3
|
EntityEditScreenDefinition,
|
|
4
4
|
FieldCondition,
|
|
5
5
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
isExtensionEditSection,
|
|
8
|
+
normalizeEditField,
|
|
9
|
+
parseRefTarget,
|
|
10
|
+
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
7
11
|
import { buildOptionLabels, fieldLabelKey } from "./list";
|
|
8
12
|
import type { EditFieldViewModel, EditSectionViewModel, EditViewModel, Translate } from "./types";
|
|
9
13
|
|
|
@@ -36,6 +40,13 @@ export function computeEditViewModel<
|
|
|
36
40
|
const { screen, entity, values, translate, featureName, ctx } = input;
|
|
37
41
|
|
|
38
42
|
const sections: EditSectionViewModel[] = screen.layout.sections.map((sectionSpec) => {
|
|
43
|
+
if (isExtensionEditSection(sectionSpec)) {
|
|
44
|
+
return {
|
|
45
|
+
kind: "extension" as const,
|
|
46
|
+
title: translate(sectionSpec.title),
|
|
47
|
+
component: sectionSpec.component,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
39
50
|
const fields: EditFieldViewModel[] = sectionSpec.fields.map((fieldSpec) => {
|
|
40
51
|
const normalized = normalizeEditField(fieldSpec);
|
|
41
52
|
const fieldDef = entity.fields[normalized.field];
|
|
@@ -124,6 +135,7 @@ export function computeEditViewModel<
|
|
|
124
135
|
return view;
|
|
125
136
|
});
|
|
126
137
|
return {
|
|
138
|
+
kind: "fields" as const,
|
|
127
139
|
title: translate(sectionSpec.title),
|
|
128
140
|
columns: sectionSpec.columns ?? 1,
|
|
129
141
|
fields,
|
package/src/view-model/index.ts
CHANGED
|
@@ -3,7 +3,9 @@ export { computeEditViewModel } from "./edit";
|
|
|
3
3
|
export type { ComputeListViewModelInput } from "./list";
|
|
4
4
|
export { computeListViewModel, fieldLabelKey } from "./list";
|
|
5
5
|
export type {
|
|
6
|
+
EditExtensionSectionViewModel,
|
|
6
7
|
EditFieldSpec,
|
|
8
|
+
EditFieldsSectionViewModel,
|
|
7
9
|
EditFieldViewModel,
|
|
8
10
|
EditSectionSpec,
|
|
9
11
|
EditSectionViewModel,
|
package/src/view-model/types.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
EditSectionSpec,
|
|
4
4
|
FieldRenderer,
|
|
5
5
|
ListColumnSpec,
|
|
6
|
+
PlatformComponent,
|
|
6
7
|
ScreenSlots,
|
|
7
8
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
8
9
|
|
|
@@ -117,12 +118,24 @@ export type EditFieldViewModel = {
|
|
|
117
118
|
readonly refMultiple?: boolean;
|
|
118
119
|
};
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
// Discriminated by `kind` — mirrors EditSectionSpec on the engine side.
|
|
122
|
+
// The builder always emits `kind` explicitly (no defaulting), so the
|
|
123
|
+
// renderer narrows with a strict equality check.
|
|
124
|
+
export type EditSectionViewModel = EditFieldsSectionViewModel | EditExtensionSectionViewModel;
|
|
125
|
+
|
|
126
|
+
export type EditFieldsSectionViewModel = {
|
|
127
|
+
readonly kind: "fields";
|
|
121
128
|
readonly title: string;
|
|
122
129
|
readonly columns: number;
|
|
123
130
|
readonly fields: readonly EditFieldViewModel[];
|
|
124
131
|
};
|
|
125
132
|
|
|
133
|
+
export type EditExtensionSectionViewModel = {
|
|
134
|
+
readonly kind: "extension";
|
|
135
|
+
readonly title: string;
|
|
136
|
+
readonly component: PlatformComponent;
|
|
137
|
+
};
|
|
138
|
+
|
|
126
139
|
export type EditViewModel = {
|
|
127
140
|
readonly screenId: string;
|
|
128
141
|
readonly entityName: string;
|