@cosmicdrift/kumiko-headless 0.359.0 → 0.361.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.
@@ -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";
@@ -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
@@ -10,6 +10,8 @@ export type ComputeEditViewModelInput<TValues extends Readonly<Record<string, un
10
10
  * for that verb (create hides them, update shows them read-only); omitted
11
11
  * means no exclusion applies. */
12
12
  readonly mode?: EditFormMode;
13
+ /** The record being edited; omitted on create. */
14
+ readonly entityId?: string;
13
15
  };
14
16
  export type EditFormMode = "create" | "update";
15
17
  export declare function computeRelatedListSectionViewModel(sectionSpec: EditRelatedListSection, translate: Translate): EditRelatedListSectionViewModel;
@@ -220,23 +220,66 @@ function deriveReferenceMultiHints(fieldDef, declaredRefTarget) {
220
220
  ...(refMultiple !== undefined && { refMultiple }),
221
221
  };
222
222
  }
223
- // file/image: entityType/fieldName travel with the upload POST so the
224
- // endpoint validates against the right field definition.
225
- function deriveFileFieldHints(fieldDef, entityName, fieldName) {
226
- const isFileType = fieldDef.type === "file" || fieldDef.type === "image" || fieldDef.type === "images";
227
- const fileDef = isFileType
228
- ? fieldDef
229
- : undefined;
230
- const imageVariant = fieldDef.type === "image" || fieldDef.type === "images"
231
- ? Object.keys(fileDef?.variants ?? {})[0]
232
- : undefined;
233
- const capture = fieldDef.type === "image" ? fileDef?.capture : undefined;
223
+ function isFileFieldType(fieldDef) {
224
+ return (fieldDef.type === "file" ||
225
+ fieldDef.type === "image" ||
226
+ fieldDef.type === "files" ||
227
+ fieldDef.type === "images");
228
+ }
229
+ function deriveImageHints(fieldDef, fileDef) {
230
+ if (fieldDef.type !== "image" && fieldDef.type !== "images")
231
+ return {};
232
+ const imageVariant = Object.keys(fileDef.variants ?? {})[0];
234
233
  return {
235
- ...(fileDef?.accept !== undefined && { accept: fileDef.accept }),
236
- ...(fileDef?.maxSize !== undefined && { maxSize: fileDef.maxSize }),
237
- ...(isFileType && { entityType: entityName, fieldName }),
238
234
  ...(imageVariant !== undefined && { imageVariant }),
239
- ...(capture !== undefined && { capture }),
235
+ ...(fileDef.capture !== undefined && { capture: fileDef.capture }),
236
+ };
237
+ }
238
+ function deriveFileFieldHints(fieldDef, entityName, fieldName, entityId) {
239
+ if (!isFileFieldType(fieldDef))
240
+ return {};
241
+ const fileDef = fieldDef;
242
+ return {
243
+ ...(fileDef.accept !== undefined && { accept: fileDef.accept }),
244
+ ...(fileDef.maxSize !== undefined && { maxSize: fileDef.maxSize }),
245
+ ...(fileDef.maxCount !== undefined && { maxCount: fileDef.maxCount }),
246
+ entityType: entityName,
247
+ fieldName,
248
+ ...(entityId !== undefined && { entityId }),
249
+ ...deriveImageHints(fieldDef, fileDef),
250
+ };
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 }),
240
283
  };
241
284
  }
242
285
  // Embedded-LIST field (`multiple: true`) — per-cell metadata for a renderer
@@ -244,39 +287,14 @@ function deriveFileFieldHints(fieldDef, entityName, fieldName) {
244
287
  // (non-list) embedded field emits none of this; the renderer tells the two
245
288
  // apart by whether embeddedListCells is set, not by `type` (which stays
246
289
  // "embedded" either way).
247
- function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, fieldName, resolvedCurrency) {
290
+ function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, fieldName, resolvedCurrency, listSpec) {
248
291
  const isEmbeddedList = fieldDef.type === "embedded" &&
249
292
  fieldDef.multiple === true;
250
293
  const embeddedListDef = isEmbeddedList
251
294
  ? fieldDef
252
295
  : undefined;
253
296
  const embeddedListCells = embeddedListDef !== undefined
254
- ? Object.entries(embeddedListDef.schema).map(([subFieldName, subField]) => {
255
- const cellLabel = translate(embeddedCellLabelKey(featureName, entityName, fieldName, subFieldName));
256
- const cellOptions = subField.type === "select" ? (subField.options ?? []) : undefined;
257
- const cellOptionLabels = cellOptions !== undefined
258
- ? buildOptionLabels(translate, (value) => embeddedCellOptionLabelKey(featureName, entityName, fieldName, subFieldName, value), cellOptions)
259
- : undefined;
260
- const cellRef = subField.type === "reference" ? subField : undefined;
261
- const cellRefTarget = cellRef?.entity !== undefined ? parseRefTarget(cellRef.entity, featureName) : undefined;
262
- const cell = {
263
- field: subFieldName,
264
- label: cellLabel,
265
- type: subField.type,
266
- required: subField.required === true,
267
- ...(cellOptions !== undefined && { options: cellOptions }),
268
- ...(cellOptionLabels !== undefined && { optionLabels: cellOptionLabels }),
269
- ...(cellRefTarget !== undefined && { refEntity: cellRefTarget.entityName }),
270
- ...(cellRefTarget !== undefined && { refFeature: cellRefTarget.featureName }),
271
- ...(cellRef?.labelField !== undefined && { refLabelField: cellRef.labelField }),
272
- ...(cellRef?.optionsQuery !== undefined && {
273
- refOptionsQuery: cellRef.optionsQuery,
274
- }),
275
- ...(subField.type === "decimal" &&
276
- subField.scale !== undefined && { scale: subField.scale }),
277
- };
278
- return cell;
279
- })
297
+ ? Object.entries(embeddedListDef.schema).map(([subFieldName, subField]) => buildEmbeddedListCell(subFieldName, subField, translate, featureName, entityName, fieldName, listSpec))
280
298
  : undefined;
281
299
  return {
282
300
  ...(embeddedListCells !== undefined && { embeddedListCells }),
@@ -295,6 +313,8 @@ function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, f
295
313
  // Currency lives on the head aggregate (entity.defaultCurrency), not per
296
314
  // row — one value for the whole embedded list.
297
315
  ...(embeddedListDef !== undefined && { embeddedListCurrency: resolvedCurrency }),
316
+ ...(embeddedListDef !== undefined &&
317
+ listSpec?.fixedRows === true && { embeddedListFixedRows: true }),
298
318
  };
299
319
  }
300
320
  function buildExtensionSectionViewModel(sectionSpec, translate) {
@@ -316,7 +336,7 @@ function buildExtensionSectionViewModel(sectionSpec, translate) {
316
336
  // section/field tree the renderer draws. FieldConditions are evaluated here
317
337
  // so the renderer never re-runs them during React render.
318
338
  export function computeEditViewModel(input) {
319
- const { screen, entity, values, translate, featureName, mode } = input;
339
+ const { screen, entity, values, translate, featureName, mode, entityId } = input;
320
340
  const excludedFields = new Set(mode === undefined ? [] : screen.writeExcludedFields?.[mode]);
321
341
  const isHiddenByExclusion = (fieldSpec) => mode === "create" && excludedFields.has(normalizeEditField(fieldSpec).field);
322
342
  const sections = screen.layout.sections.map((sectionSpec) => {
@@ -378,8 +398,8 @@ export function computeEditViewModel(input) {
378
398
  const dateHints = deriveDateFieldHints(fieldDef);
379
399
  const numericHints = deriveNumericFieldHints(fieldDef, resolvedCurrency);
380
400
  const referenceHints = deriveReferenceFieldHints(fieldDef, normalized, declaredRefTarget, featureName);
381
- const fileHints = deriveFileFieldHints(fieldDef, screen.entity, normalized.field);
382
- const embeddedListHints = deriveEmbeddedListHints(fieldDef, translate, featureName, screen.entity, normalized.field, resolvedCurrency);
401
+ const fileHints = deriveFileFieldHints(fieldDef, screen.entity, normalized.field, entityId);
402
+ const embeddedListHints = deriveEmbeddedListHints(fieldDef, translate, featureName, screen.entity, normalized.field, resolvedCurrency, normalized.embeddedList);
383
403
  const view = {
384
404
  field: normalized.field,
385
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
+ }
@@ -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;
@@ -171,12 +186,17 @@ export type EditFieldViewModel = {
171
186
  * Upload-Endpoint gegen die Field-Def. */
172
187
  readonly accept?: readonly string[];
173
188
  readonly maxSize?: string;
189
+ /** Only for `type: "files" | "images"` — upper bound on list length. */
190
+ readonly maxCount?: number;
174
191
  /** Nur bei `type: "file" | "image"` — Entity-Name + Field-Name für den
175
192
  * Upload-POST (`/api/files`), damit der Endpoint maxSize/accept gegen
176
193
  * die richtige Field-Def prüfen kann. */
177
194
  readonly entityType?: string;
178
195
  readonly fieldName?: string;
179
- /** Only for `type: "image"` — the variant name the preview requests
196
+ /** Record being edited (update mode only). Sent with the upload so the
197
+ * FileRef is attached to the record. */
198
+ readonly entityId?: string;
199
+ /** Only for `type: "image"` and `"images"` — the variant name the preview requests
180
200
  * (`/api/files/:id/variant/:name`) instead of the original. The FIRST
181
201
  * variant declared on the field def wins; a spec may legitimately carry
182
202
  * neither `size` nor `maxEdge` (format-/blur-only), so "the smallest one"
@@ -204,6 +224,9 @@ export type EditFieldViewModel = {
204
224
  }>>;
205
225
  /** Numeric sub-field names to sum in a totals row. From EmbeddedFieldDef.totals. */
206
226
  readonly embeddedListTotals?: readonly string[];
227
+ /** No add, remove, duplicate or move; cells stay editable. From
228
+ * EditFieldSpec.embeddedList.fixedRows. */
229
+ readonly embeddedListFixedRows?: boolean;
207
230
  /** Currency for the whole embedded list's money cells and totals row —
208
231
  * currency lives on the head aggregate (EntityDefinition.defaultCurrency),
209
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.359.0",
3
+ "version": "0.361.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.359.0",
40
- "@cosmicdrift/kumiko-types": "0.359.0",
39
+ "@cosmicdrift/kumiko-framework": "0.361.0",
40
+ "@cosmicdrift/kumiko-types": "0.361.0",
41
41
  "temporal-polyfill": "^0.3.2",
42
42
  "zod": "^4.4.3"
43
43
  },
package/src/changes.json CHANGED
@@ -1,4 +1,28 @@
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
+ },
20
+ {
21
+ "version": "0.360.0",
22
+ "type": "improvement",
23
+ "title": "Edit view model carries entityId and maxCount for file fields",
24
+ "detail": "ComputeEditViewModelInput has an optional entityId (the record being edited, omitted on create). EditFieldViewModel gains entityId for file, image, files and images fields and maxCount for files and images. The file-field hints now also cover type files, which was missing."
25
+ },
2
26
  {
3
27
  "version": "0.357.0",
4
28
  "type": "improvement",