@cosmicdrift/kumiko-headless 0.360.0 → 0.362.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/dist/format/index.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/nav/resolve.js +14 -1
- package/dist/view-model/edit.js +38 -28
- package/dist/view-model/index.d.ts +1 -1
- package/dist/view-model/list-file-display.d.ts +3 -0
- package/dist/view-model/list-file-display.js +28 -0
- package/dist/view-model/list.js +3 -0
- package/dist/view-model/types.d.ts +18 -0
- package/package.json +3 -3
- package/src/changes.json +18 -0
package/dist/format/index.js
CHANGED
|
@@ -198,6 +198,11 @@ export function applyFormatSpec(spec, value, translate) {
|
|
|
198
198
|
// multiSelect-style columns hold an array of option values
|
|
199
199
|
return Array.isArray(value) ? value.map(labelFor).join(", ") : labelFor(value);
|
|
200
200
|
}
|
|
201
|
+
// Thumbnails and file links are drawn by the web list cell; this is the
|
|
202
|
+
// text fallback for every other surface.
|
|
203
|
+
case "image":
|
|
204
|
+
case "file":
|
|
205
|
+
return Array.isArray(value) ? value.join(", ") : String(value);
|
|
201
206
|
case "json":
|
|
202
207
|
return stringifyJsonOrString(value, typeof spec["indent"] === "number" ? spec["indent"] : 2);
|
|
203
208
|
default:
|
package/dist/index.d.ts
CHANGED
|
@@ -10,5 +10,5 @@ export type { NavDefinition, NavNode, NavRegistrySlice, NavTree, ResolveNavigati
|
|
|
10
10
|
export { resolveNavigation } from "./nav/index.js";
|
|
11
11
|
export type { Store, WritableStore } from "./store/index.js";
|
|
12
12
|
export { createStore, shallowEqual } from "./store/index.js";
|
|
13
|
-
export type { ComputeEditViewModelInput, ComputeListViewModelInput, DerivedCellRoundingTarget, EditExtensionSectionViewModel, EditFieldSpec, EditFieldsSectionViewModel, EditFieldViewModel, EditFormMode, EditRelatedListSectionViewModel, EditSectionSpec, EditSectionViewModel, EditViewModel, EditWriteFormSectionViewModel, EmbeddedDerivedOp, EmbeddedListCellViewModel, EmbeddedListIssueGroups, FieldConditionCtx, FieldRenderer, ListColumnSpec, ListColumnViewModel, ListRowViewModel, ListViewModel, RuntimeRenderer, ScreenSlots, Translate, } from "./view-model/index.js";
|
|
13
|
+
export type { ComputeEditViewModelInput, ComputeListViewModelInput, DerivedCellRoundingTarget, EditExtensionSectionViewModel, EditFieldSpec, EditFieldsSectionViewModel, EditFieldViewModel, EditFormMode, EditRelatedListSectionViewModel, EditSectionSpec, EditSectionViewModel, EditViewModel, EditWriteFormSectionViewModel, EmbeddedDerivedOp, EmbeddedListCellViewModel, EmbeddedListIssueGroups, FieldConditionCtx, FieldRenderer, ListColumnFileDisplay, ListColumnSpec, ListColumnViewModel, ListRowViewModel, ListViewModel, RuntimeRenderer, ScreenSlots, Translate, } from "./view-model/index.js";
|
|
14
14
|
export { buildOptionLabels, computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./view-model/index.js";
|
package/dist/nav/resolve.js
CHANGED
|
@@ -4,7 +4,9 @@ import { isUiAccessGranted } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
|
4
4
|
// access-checked; a hidden parent drops its entire subtree implicitly
|
|
5
5
|
// because we never recurse into it. Siblings sort by `order` (ascending,
|
|
6
6
|
// default 0), tie-broken by qualified name so renders stay deterministic
|
|
7
|
-
// across registry iteration orders.
|
|
7
|
+
// across registry iteration orders. A pure group (no screen, target,
|
|
8
|
+
// provider or createAction) whose children are all hidden or absent is
|
|
9
|
+
// dropped too.
|
|
8
10
|
//
|
|
9
11
|
// Pure — same inputs produce the same tree. The renderer memoizes the
|
|
10
12
|
// result and only recomputes when the registry changes (rare — boot
|
|
@@ -24,6 +26,10 @@ export function resolveNavigation(options) {
|
|
|
24
26
|
if (node !== null)
|
|
25
27
|
children.push(node);
|
|
26
28
|
}
|
|
29
|
+
// A pure group (nothing to open itself) with nothing visible below it
|
|
30
|
+
// would render as an empty, unclickable heading.
|
|
31
|
+
if (children.length === 0 && isPureGroup(entry))
|
|
32
|
+
return null;
|
|
27
33
|
children.sort(bySortKey);
|
|
28
34
|
return {
|
|
29
35
|
qualifiedName: entry.id,
|
|
@@ -47,6 +53,13 @@ export function resolveNavigation(options) {
|
|
|
47
53
|
roots.sort(bySortKey);
|
|
48
54
|
return roots;
|
|
49
55
|
}
|
|
56
|
+
function isPureGroup(entry) {
|
|
57
|
+
return (entry.screen === undefined &&
|
|
58
|
+
entry.target === undefined &&
|
|
59
|
+
entry.provider === undefined &&
|
|
60
|
+
entry.actions === undefined &&
|
|
61
|
+
entry.createAction === undefined);
|
|
62
|
+
}
|
|
50
63
|
function bySortKey(a, b) {
|
|
51
64
|
// Primary key: `order` ascending. Secondary: qualifiedName alphabetic,
|
|
52
65
|
// which is a stable fallback — the registry-iteration order isn't
|
package/dist/view-model/edit.js
CHANGED
|
@@ -249,44 +249,52 @@ function deriveFileFieldHints(fieldDef, entityName, fieldName, entityId) {
|
|
|
249
249
|
...deriveImageHints(fieldDef, fileDef),
|
|
250
250
|
};
|
|
251
251
|
}
|
|
252
|
+
function deriveEmbeddedCellSelectHints(subField, translate, featureName, entityName, fieldName, subFieldName) {
|
|
253
|
+
if (subField.type !== "select")
|
|
254
|
+
return {};
|
|
255
|
+
const options = subField.options ?? [];
|
|
256
|
+
return {
|
|
257
|
+
options,
|
|
258
|
+
optionLabels: buildOptionLabels(translate, (value) => embeddedCellOptionLabelKey(featureName, entityName, fieldName, subFieldName, value), options),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function deriveEmbeddedCellReferenceHints(subField, featureName) {
|
|
262
|
+
if (subField.type !== "reference")
|
|
263
|
+
return {};
|
|
264
|
+
const target = subField.entity !== undefined ? parseRefTarget(subField.entity, featureName) : undefined;
|
|
265
|
+
return {
|
|
266
|
+
...(target !== undefined && { refEntity: target.entityName, refFeature: target.featureName }),
|
|
267
|
+
...(subField.labelField !== undefined && { refLabelField: subField.labelField }),
|
|
268
|
+
...(subField.optionsQuery !== undefined && { refOptionsQuery: subField.optionsQuery }),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function buildEmbeddedListCell(subFieldName, subField, translate, featureName, entityName, fieldName, listSpec) {
|
|
272
|
+
return {
|
|
273
|
+
field: subFieldName,
|
|
274
|
+
label: translate(embeddedCellLabelKey(featureName, entityName, fieldName, subFieldName)),
|
|
275
|
+
type: subField.type,
|
|
276
|
+
required: subField.required === true,
|
|
277
|
+
...deriveEmbeddedCellSelectHints(subField, translate, featureName, entityName, fieldName, subFieldName),
|
|
278
|
+
...deriveEmbeddedCellReferenceHints(subField, featureName),
|
|
279
|
+
...(subField.type === "decimal" && subField.scale !== undefined && { scale: subField.scale }),
|
|
280
|
+
...(subField.type === "select" &&
|
|
281
|
+
subField.display !== undefined && { display: subField.display }),
|
|
282
|
+
...(listSpec?.readOnlyCells?.includes(subFieldName) === true && { readOnly: true }),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
252
285
|
// Embedded-LIST field (`multiple: true`) — per-cell metadata for a renderer
|
|
253
286
|
// to draw one row per array item (invoice-positions-style table). A plain
|
|
254
287
|
// (non-list) embedded field emits none of this; the renderer tells the two
|
|
255
288
|
// apart by whether embeddedListCells is set, not by `type` (which stays
|
|
256
289
|
// "embedded" either way).
|
|
257
|
-
function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, fieldName, resolvedCurrency) {
|
|
290
|
+
function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, fieldName, resolvedCurrency, listSpec) {
|
|
258
291
|
const isEmbeddedList = fieldDef.type === "embedded" &&
|
|
259
292
|
fieldDef.multiple === true;
|
|
260
293
|
const embeddedListDef = isEmbeddedList
|
|
261
294
|
? fieldDef
|
|
262
295
|
: undefined;
|
|
263
296
|
const embeddedListCells = embeddedListDef !== undefined
|
|
264
|
-
? Object.entries(embeddedListDef.schema).map(([subFieldName, subField]) =>
|
|
265
|
-
const cellLabel = translate(embeddedCellLabelKey(featureName, entityName, fieldName, subFieldName));
|
|
266
|
-
const cellOptions = subField.type === "select" ? (subField.options ?? []) : undefined;
|
|
267
|
-
const cellOptionLabels = cellOptions !== undefined
|
|
268
|
-
? buildOptionLabels(translate, (value) => embeddedCellOptionLabelKey(featureName, entityName, fieldName, subFieldName, value), cellOptions)
|
|
269
|
-
: undefined;
|
|
270
|
-
const cellRef = subField.type === "reference" ? subField : undefined;
|
|
271
|
-
const cellRefTarget = cellRef?.entity !== undefined ? parseRefTarget(cellRef.entity, featureName) : undefined;
|
|
272
|
-
const cell = {
|
|
273
|
-
field: subFieldName,
|
|
274
|
-
label: cellLabel,
|
|
275
|
-
type: subField.type,
|
|
276
|
-
required: subField.required === true,
|
|
277
|
-
...(cellOptions !== undefined && { options: cellOptions }),
|
|
278
|
-
...(cellOptionLabels !== undefined && { optionLabels: cellOptionLabels }),
|
|
279
|
-
...(cellRefTarget !== undefined && { refEntity: cellRefTarget.entityName }),
|
|
280
|
-
...(cellRefTarget !== undefined && { refFeature: cellRefTarget.featureName }),
|
|
281
|
-
...(cellRef?.labelField !== undefined && { refLabelField: cellRef.labelField }),
|
|
282
|
-
...(cellRef?.optionsQuery !== undefined && {
|
|
283
|
-
refOptionsQuery: cellRef.optionsQuery,
|
|
284
|
-
}),
|
|
285
|
-
...(subField.type === "decimal" &&
|
|
286
|
-
subField.scale !== undefined && { scale: subField.scale }),
|
|
287
|
-
};
|
|
288
|
-
return cell;
|
|
289
|
-
})
|
|
297
|
+
? Object.entries(embeddedListDef.schema).map(([subFieldName, subField]) => buildEmbeddedListCell(subFieldName, subField, translate, featureName, entityName, fieldName, listSpec))
|
|
290
298
|
: undefined;
|
|
291
299
|
return {
|
|
292
300
|
...(embeddedListCells !== undefined && { embeddedListCells }),
|
|
@@ -305,6 +313,8 @@ function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, f
|
|
|
305
313
|
// Currency lives on the head aggregate (entity.defaultCurrency), not per
|
|
306
314
|
// row — one value for the whole embedded list.
|
|
307
315
|
...(embeddedListDef !== undefined && { embeddedListCurrency: resolvedCurrency }),
|
|
316
|
+
...(embeddedListDef !== undefined &&
|
|
317
|
+
listSpec?.fixedRows === true && { embeddedListFixedRows: true }),
|
|
308
318
|
};
|
|
309
319
|
}
|
|
310
320
|
function buildExtensionSectionViewModel(sectionSpec, translate) {
|
|
@@ -389,7 +399,7 @@ export function computeEditViewModel(input) {
|
|
|
389
399
|
const numericHints = deriveNumericFieldHints(fieldDef, resolvedCurrency);
|
|
390
400
|
const referenceHints = deriveReferenceFieldHints(fieldDef, normalized, declaredRefTarget, featureName);
|
|
391
401
|
const fileHints = deriveFileFieldHints(fieldDef, screen.entity, normalized.field, entityId);
|
|
392
|
-
const embeddedListHints = deriveEmbeddedListHints(fieldDef, translate, featureName, screen.entity, normalized.field, resolvedCurrency);
|
|
402
|
+
const embeddedListHints = deriveEmbeddedListHints(fieldDef, translate, featureName, screen.entity, normalized.field, resolvedCurrency, normalized.embeddedList);
|
|
393
403
|
const view = {
|
|
394
404
|
field: normalized.field,
|
|
395
405
|
label,
|
|
@@ -4,4 +4,4 @@ export type { DerivedCellRoundingTarget, EmbeddedDerivedOp, EmbeddedListIssueGro
|
|
|
4
4
|
export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./embedded-list.js";
|
|
5
5
|
export type { ComputeListViewModelInput } from "./list.js";
|
|
6
6
|
export { buildOptionLabels, computeListViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, } from "./list.js";
|
|
7
|
-
export type { EditExtensionSectionViewModel, EditFieldSpec, EditFieldsSectionViewModel, EditFieldViewModel, EditRelatedListSectionViewModel, EditSectionSpec, EditSectionViewModel, EditViewModel, EditWriteFormSectionViewModel, EmbeddedListCellViewModel, FieldConditionCtx, FieldRenderer, ListColumnSpec, ListColumnViewModel, ListRowViewModel, ListViewModel, RuntimeRenderer, ScreenSlots, Translate, } from "./types.js";
|
|
7
|
+
export type { EditExtensionSectionViewModel, EditFieldSpec, EditFieldsSectionViewModel, EditFieldViewModel, EditRelatedListSectionViewModel, EditSectionSpec, EditSectionViewModel, EditViewModel, EditWriteFormSectionViewModel, EmbeddedListCellViewModel, FieldConditionCtx, FieldRenderer, ListColumnFileDisplay, ListColumnSpec, ListColumnViewModel, ListRowViewModel, ListViewModel, RuntimeRenderer, ScreenSlots, Translate, } from "./types.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { FieldDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { ListColumnFileDisplay, ListColumnViewModel } from "./types.js";
|
|
3
|
+
export declare function deriveListFileDisplay(fieldDef: FieldDefinition, renderer: ListColumnViewModel["renderer"]): ListColumnFileDisplay | undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function firstVariantKey(fieldDef) {
|
|
2
|
+
const variants = fieldDef.variants;
|
|
3
|
+
return variants === undefined ? undefined : Object.keys(variants)[0];
|
|
4
|
+
}
|
|
5
|
+
function fromFormatSpec(renderer) {
|
|
6
|
+
if (typeof renderer !== "object" || renderer === null || !("format" in renderer)) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
if (renderer.format === "file")
|
|
10
|
+
return { kind: "file" };
|
|
11
|
+
if (renderer.format !== "image")
|
|
12
|
+
return undefined;
|
|
13
|
+
return renderer.variant === undefined
|
|
14
|
+
? { kind: "image" }
|
|
15
|
+
: { kind: "image", variant: renderer.variant };
|
|
16
|
+
}
|
|
17
|
+
function fromFieldType(fieldDef) {
|
|
18
|
+
if (fieldDef.type === "file" || fieldDef.type === "files")
|
|
19
|
+
return { kind: "file" };
|
|
20
|
+
if (fieldDef.type !== "image" && fieldDef.type !== "images")
|
|
21
|
+
return undefined;
|
|
22
|
+
const variant = firstVariantKey(fieldDef);
|
|
23
|
+
return variant === undefined ? { kind: "image" } : { kind: "image", variant };
|
|
24
|
+
}
|
|
25
|
+
// An explicit `{ format: "image" | "file" }` renderer wins over the field type.
|
|
26
|
+
export function deriveListFileDisplay(fieldDef, renderer) {
|
|
27
|
+
return fromFormatSpec(renderer) ?? fromFieldType(fieldDef);
|
|
28
|
+
}
|
package/dist/view-model/list.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, normalizeListColumn, parseRefTarget, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { deriveListFileDisplay } from "./list-file-display.js";
|
|
2
3
|
const WRITE_ONLY_SET_LABEL_KEY = "kumiko.field.writeOnly.set";
|
|
3
4
|
// Pure transform: takes the declared screen + entity + incoming rows, spits
|
|
4
5
|
// out the flat shape the renderer draws. No conditions, no access-checks —
|
|
@@ -130,6 +131,7 @@ export function computeListViewModel(input) {
|
|
|
130
131
|
: undefined;
|
|
131
132
|
const optionTones = fieldDef.type === "select" ? fieldDef.optionTones : undefined;
|
|
132
133
|
const grouping = fieldDef.type === "number" ? fieldDef.grouping : undefined;
|
|
134
|
+
const fileDisplay = deriveListFileDisplay(fieldDef, normalized.renderer);
|
|
133
135
|
const column = {
|
|
134
136
|
field: normalized.field,
|
|
135
137
|
label,
|
|
@@ -142,6 +144,7 @@ export function computeListViewModel(input) {
|
|
|
142
144
|
...(refFeature !== undefined && { refFeature }),
|
|
143
145
|
...(refLabelField !== undefined && { refLabelField }),
|
|
144
146
|
...(grouping !== undefined && { grouping }),
|
|
147
|
+
...(fileDisplay !== undefined && { fileDisplay }),
|
|
145
148
|
...(normalized.hideOnNarrow === true && { hideOnNarrow: true }),
|
|
146
149
|
};
|
|
147
150
|
columns.push(column);
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { EditFieldSpec, EditSectionSpec, FieldIconKey, FieldRenderer, IconKey, ListColumnSpec, ListFacetSpec, ListSortSpec, OptionsQueryPayload, PlatformComponent, RelatedListGroupBy, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionMode, ScreenSlots, SelectOptionTone } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
2
|
export type RuntimeRenderer = (value: unknown, row?: Readonly<Record<string, unknown>>) => string;
|
|
3
|
+
/** How a list cell shows a FileRef value (id or id array). */
|
|
4
|
+
export type ListColumnFileDisplay = {
|
|
5
|
+
readonly kind: "image";
|
|
6
|
+
readonly variant?: string;
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: "file";
|
|
9
|
+
};
|
|
3
10
|
export type ListColumnViewModel = {
|
|
4
11
|
readonly field: string;
|
|
5
12
|
readonly label: string;
|
|
@@ -34,6 +41,10 @@ export type ListColumnViewModel = {
|
|
|
34
41
|
* `true`). `false` renders without thousands-separators (e.g. a model
|
|
35
42
|
* year). */
|
|
36
43
|
readonly grouping?: boolean;
|
|
44
|
+
/** Set for file/image columns: from the entity field type, or from a
|
|
45
|
+
* `{ format: "image" | "file" }` renderer. The cell shows a thumbnail or the
|
|
46
|
+
* file name instead of the FileRef UUID. */
|
|
47
|
+
readonly fileDisplay?: ListColumnFileDisplay;
|
|
37
48
|
};
|
|
38
49
|
export type ListRowViewModel = {
|
|
39
50
|
readonly id: string;
|
|
@@ -55,6 +66,10 @@ export type EmbeddedListCellViewModel = {
|
|
|
55
66
|
/** Only for `type: "select"`. */
|
|
56
67
|
readonly options?: readonly string[];
|
|
57
68
|
readonly optionLabels?: Readonly<Record<string, string>>;
|
|
69
|
+
/** Only for `type: "select"`; `"radio"` renders segmented buttons instead of a dropdown. */
|
|
70
|
+
readonly display?: "radio" | "dropdown";
|
|
71
|
+
/** Cell is shown but not editable (`EditFieldSpec.embeddedList.readOnlyCells`). */
|
|
72
|
+
readonly readOnly?: boolean;
|
|
58
73
|
/** Only for `type: "reference"`. */
|
|
59
74
|
readonly refEntity?: string;
|
|
60
75
|
readonly refFeature?: string;
|
|
@@ -209,6 +224,9 @@ export type EditFieldViewModel = {
|
|
|
209
224
|
}>>;
|
|
210
225
|
/** Numeric sub-field names to sum in a totals row. From EmbeddedFieldDef.totals. */
|
|
211
226
|
readonly embeddedListTotals?: readonly string[];
|
|
227
|
+
/** No add, remove, duplicate or move; cells stay editable. From
|
|
228
|
+
* EditFieldSpec.embeddedList.fixedRows. */
|
|
229
|
+
readonly embeddedListFixedRows?: boolean;
|
|
212
230
|
/** Currency for the whole embedded list's money cells and totals row —
|
|
213
231
|
* currency lives on the head aggregate (EntityDefinition.defaultCurrency),
|
|
214
232
|
* not per row. Falls back to "EUR" when the entity has no defaultCurrency. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-headless",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.362.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,8 +36,8 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
40
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
39
|
+
"@cosmicdrift/kumiko-framework": "0.362.0",
|
|
40
|
+
"@cosmicdrift/kumiko-types": "0.362.0",
|
|
41
41
|
"temporal-polyfill": "^0.3.2",
|
|
42
42
|
"zod": "^4.4.3"
|
|
43
43
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.361.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Edit view model carries read-only cells and fixed rows",
|
|
6
|
+
"detail": "EmbeddedListCellViewModel.readOnly and EditFieldViewModel.embeddedListFixedRows are derived from EditFieldSpec.embeddedList."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.361.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "Embedded list cells carry the select display",
|
|
12
|
+
"detail": "EmbeddedListCellViewModel.display is taken from the sub-field."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.361.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "resolveNavigation no longer renders empty groups or entries that open nothing",
|
|
18
|
+
"detail": "A nav node with no screen, target, provider, actions and createAction is dropped when none of its children is visible, also when it never had children. Allowlisted sidebars lose groups whose children are all filtered out. Give the entry a screen or keep a child visible to retain it."
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"version": "0.360.0",
|
|
4
22
|
"type": "improvement",
|