@cosmicdrift/kumiko-headless 0.289.0 → 0.291.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-headless",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.291.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>",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
39
|
+
"@cosmicdrift/kumiko-framework": "0.291.0",
|
|
40
40
|
"temporal-polyfill": "^0.3.2",
|
|
41
41
|
"zod": "^4.4.3"
|
|
42
42
|
},
|
|
@@ -424,6 +424,31 @@ describe("computeEditViewModel — embedded-list cells (#1835)", () => {
|
|
|
424
424
|
refFeature: "orders",
|
|
425
425
|
refLabelField: "name",
|
|
426
426
|
});
|
|
427
|
+
expect(byField["product"]?.refOptionsQuery).toBeUndefined();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("a reference sub-field's optionsQuery reaches the cell view-model (fw#2780)", () => {
|
|
431
|
+
const vm = computeEditViewModel({
|
|
432
|
+
screen: editScreen({ sections: [{ title: "x", fields: ["lines"] }] }),
|
|
433
|
+
entity: embeddedListEntity({
|
|
434
|
+
schema: {
|
|
435
|
+
...lineFieldSchema,
|
|
436
|
+
product: {
|
|
437
|
+
type: "reference",
|
|
438
|
+
entity: "product",
|
|
439
|
+
labelField: "name",
|
|
440
|
+
optionsQuery: "orders:query:product:options",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
}),
|
|
444
|
+
values: { lines: [] },
|
|
445
|
+
translate,
|
|
446
|
+
featureName: "orders",
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const cells = asFields(vm.sections[0]).fields[0]?.embeddedListCells;
|
|
450
|
+
const product = (cells ?? []).find((cell) => cell.field === "product");
|
|
451
|
+
expect(product?.refOptionsQuery).toBe("orders:query:product:options");
|
|
427
452
|
});
|
|
428
453
|
|
|
429
454
|
test("minItems/maxItems/derived/totals pass through onto the field view-model when set", () => {
|
|
@@ -720,6 +745,34 @@ describe("computeEditViewModel — declared reference metadata (fw#2662)", () =>
|
|
|
720
745
|
expect(field?.refMultiple).toBeUndefined();
|
|
721
746
|
});
|
|
722
747
|
|
|
748
|
+
test("a reference field's optionsQuery reaches the field view-model, labelField untouched (fw#2780)", () => {
|
|
749
|
+
const entity = {
|
|
750
|
+
fields: {
|
|
751
|
+
leaseId: {
|
|
752
|
+
type: "reference",
|
|
753
|
+
entity: "lease",
|
|
754
|
+
labelField: "startDate",
|
|
755
|
+
optionsQuery: "deposits:query:lease:options",
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
} as unknown as EntityDefinition;
|
|
759
|
+
const vm = computeEditViewModel({
|
|
760
|
+
screen: editScreen({ sections: [{ fields: ["leaseId"] }] }),
|
|
761
|
+
entity,
|
|
762
|
+
values: {},
|
|
763
|
+
translate,
|
|
764
|
+
featureName: "deposits",
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
const field = asFields(vm.sections[0]).fields[0];
|
|
768
|
+
expect(field).toMatchObject({
|
|
769
|
+
type: "reference",
|
|
770
|
+
refEntity: "lease",
|
|
771
|
+
refLabelField: "startDate",
|
|
772
|
+
refOptionsQuery: "deposits:query:lease:options",
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
|
|
723
776
|
test("non-regression: a projectionDetail field without refEntity metadata stays 'text', unchanged (fw#2662)", () => {
|
|
724
777
|
const vm = computeEditViewModel({
|
|
725
778
|
screen: editScreen({ sections: [{ fields: ["ip"] }] }),
|
package/src/view-model/edit.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
isWriteFormEditSection,
|
|
13
13
|
normalizeEditField,
|
|
14
14
|
parseRefTarget,
|
|
15
|
+
sectionFieldSpecs,
|
|
15
16
|
WRITE_FORM_SECTION_ENTITY,
|
|
16
17
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
17
18
|
import {
|
|
@@ -51,6 +52,7 @@ type EmbeddedSubFieldShape = {
|
|
|
51
52
|
readonly options?: readonly string[];
|
|
52
53
|
readonly entity?: string;
|
|
53
54
|
readonly labelField?: string;
|
|
55
|
+
readonly optionsQuery?: string;
|
|
54
56
|
readonly scale?: number;
|
|
55
57
|
};
|
|
56
58
|
|
|
@@ -167,11 +169,7 @@ export function computeEditViewModel<
|
|
|
167
169
|
}
|
|
168
170
|
// `groups` flattens into the same per-field pipeline as plain `fields`;
|
|
169
171
|
// the group structure below just re-groups the computed views by name.
|
|
170
|
-
const
|
|
171
|
-
sectionSpec.groups !== undefined
|
|
172
|
-
? sectionSpec.groups.flatMap((group) => group.fields)
|
|
173
|
-
: sectionSpec.fields;
|
|
174
|
-
const fields: EditFieldViewModel[] = flatFieldSpecs.map((fieldSpec) => {
|
|
172
|
+
const fields: EditFieldViewModel[] = sectionFieldSpecs(sectionSpec).map((fieldSpec) => {
|
|
175
173
|
const normalized = normalizeEditField(fieldSpec);
|
|
176
174
|
const fieldDef = entity.fields[normalized.field];
|
|
177
175
|
if (!fieldDef) {
|
|
@@ -286,13 +284,17 @@ export function computeEditViewModel<
|
|
|
286
284
|
: fieldDef.type === "reference"
|
|
287
285
|
? ((fieldDef as unknown as { labelField?: string }).labelField ?? "id")
|
|
288
286
|
: undefined;
|
|
289
|
-
// Declared reference metadata has no `multiple`
|
|
290
|
-
// row-meta/derived fields, always single-valued
|
|
291
|
-
//
|
|
292
|
-
|
|
287
|
+
// Declared reference metadata has no `multiple` or `optionsQuery`
|
|
288
|
+
// concept (it targets row-meta/derived fields, always single-valued and
|
|
289
|
+
// resolved through the target entity) — only a real ReferenceFieldDef
|
|
290
|
+
// carries them.
|
|
291
|
+
const ownReferenceDef =
|
|
293
292
|
declaredRefTarget === undefined && fieldDef.type === "reference"
|
|
294
|
-
? (
|
|
293
|
+
? (fieldDef as unknown as { multiple?: boolean; optionsQuery?: string })
|
|
295
294
|
: undefined;
|
|
295
|
+
const refOptionsQuery = ownReferenceDef?.optionsQuery;
|
|
296
|
+
const refMultiple =
|
|
297
|
+
ownReferenceDef === undefined ? undefined : (ownReferenceDef.multiple ?? false);
|
|
296
298
|
// file/image: accept/maxSize ins ViewModel + entityType/fieldName für
|
|
297
299
|
// den Upload-POST (Endpoint validiert gegen die richtige Field-Def).
|
|
298
300
|
const isFileType =
|
|
@@ -359,9 +361,10 @@ export function computeEditViewModel<
|
|
|
359
361
|
cellOptions,
|
|
360
362
|
)
|
|
361
363
|
: undefined;
|
|
364
|
+
const cellRef = subField.type === "reference" ? subField : undefined;
|
|
362
365
|
const cellRefTarget =
|
|
363
|
-
|
|
364
|
-
? parseRefTarget(
|
|
366
|
+
cellRef?.entity !== undefined
|
|
367
|
+
? parseRefTarget(cellRef.entity, featureName)
|
|
365
368
|
: undefined;
|
|
366
369
|
const cell: EmbeddedListCellViewModel = {
|
|
367
370
|
field: subFieldName,
|
|
@@ -372,8 +375,10 @@ export function computeEditViewModel<
|
|
|
372
375
|
...(cellOptionLabels !== undefined && { optionLabels: cellOptionLabels }),
|
|
373
376
|
...(cellRefTarget !== undefined && { refEntity: cellRefTarget.entityName }),
|
|
374
377
|
...(cellRefTarget !== undefined && { refFeature: cellRefTarget.featureName }),
|
|
375
|
-
...(
|
|
376
|
-
|
|
378
|
+
...(cellRef?.labelField !== undefined && { refLabelField: cellRef.labelField }),
|
|
379
|
+
...(cellRef?.optionsQuery !== undefined && {
|
|
380
|
+
refOptionsQuery: cellRef.optionsQuery,
|
|
381
|
+
}),
|
|
377
382
|
...(subField.type === "decimal" &&
|
|
378
383
|
subField.scale !== undefined && { scale: subField.scale }),
|
|
379
384
|
};
|
|
@@ -404,6 +409,7 @@ export function computeEditViewModel<
|
|
|
404
409
|
...(refEntity !== undefined && { refEntity }),
|
|
405
410
|
...(refFeature !== undefined && { refFeature }),
|
|
406
411
|
...(refLabelField !== undefined && { refLabelField }),
|
|
412
|
+
...(refOptionsQuery !== undefined && { refOptionsQuery }),
|
|
407
413
|
...(refMultiple !== undefined && { refMultiple }),
|
|
408
414
|
...(fileDef?.accept !== undefined && { accept: fileDef.accept }),
|
|
409
415
|
...(fileDef?.maxSize !== undefined && { maxSize: fileDef.maxSize }),
|
package/src/view-model/types.ts
CHANGED
|
@@ -112,6 +112,7 @@ export type EmbeddedListCellViewModel = {
|
|
|
112
112
|
readonly refEntity?: string;
|
|
113
113
|
readonly refFeature?: string;
|
|
114
114
|
readonly refLabelField?: string;
|
|
115
|
+
readonly refOptionsQuery?: string;
|
|
115
116
|
/** Only for `type: "decimal"`. */
|
|
116
117
|
readonly scale?: number;
|
|
117
118
|
};
|
|
@@ -190,6 +191,11 @@ export type EditFieldViewModel = {
|
|
|
190
191
|
/** Nur bei `type: "reference"` gesetzt — Welches Feld der referenced
|
|
191
192
|
* Entity als Display-Label im Dropdown erscheint. Default: "id". */
|
|
192
193
|
readonly refLabelField?: string;
|
|
194
|
+
/** Only for `type: "reference"` — query handler QN the picker sources its
|
|
195
|
+
* options from (fw#2780), replacing the referenced entity's list handler.
|
|
196
|
+
* Rows carry `{ id, label }`; `refLabelField` is then unused for the
|
|
197
|
+
* picker but still drives list cells. */
|
|
198
|
+
readonly refOptionsQuery?: string;
|
|
193
199
|
/** Nur bei `type: "reference"` — Multi-Mode (Tier 2.7e-Multi):
|
|
194
200
|
* Wert ist UUID-Array, Renderer mountet Multi-Combobox mit Tags. */
|
|
195
201
|
readonly refMultiple?: boolean;
|