@cosmicdrift/kumiko-headless 0.359.0 → 0.360.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.
@@ -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,33 @@ 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),
240
250
  };
241
251
  }
242
252
  // Embedded-LIST field (`multiple: true`) — per-cell metadata for a renderer
@@ -316,7 +326,7 @@ function buildExtensionSectionViewModel(sectionSpec, translate) {
316
326
  // section/field tree the renderer draws. FieldConditions are evaluated here
317
327
  // so the renderer never re-runs them during React render.
318
328
  export function computeEditViewModel(input) {
319
- const { screen, entity, values, translate, featureName, mode } = input;
329
+ const { screen, entity, values, translate, featureName, mode, entityId } = input;
320
330
  const excludedFields = new Set(mode === undefined ? [] : screen.writeExcludedFields?.[mode]);
321
331
  const isHiddenByExclusion = (fieldSpec) => mode === "create" && excludedFields.has(normalizeEditField(fieldSpec).field);
322
332
  const sections = screen.layout.sections.map((sectionSpec) => {
@@ -378,7 +388,7 @@ export function computeEditViewModel(input) {
378
388
  const dateHints = deriveDateFieldHints(fieldDef);
379
389
  const numericHints = deriveNumericFieldHints(fieldDef, resolvedCurrency);
380
390
  const referenceHints = deriveReferenceFieldHints(fieldDef, normalized, declaredRefTarget, featureName);
381
- const fileHints = deriveFileFieldHints(fieldDef, screen.entity, normalized.field);
391
+ const fileHints = deriveFileFieldHints(fieldDef, screen.entity, normalized.field, entityId);
382
392
  const embeddedListHints = deriveEmbeddedListHints(fieldDef, translate, featureName, screen.entity, normalized.field, resolvedCurrency);
383
393
  const view = {
384
394
  field: normalized.field,
@@ -171,12 +171,17 @@ export type EditFieldViewModel = {
171
171
  * Upload-Endpoint gegen die Field-Def. */
172
172
  readonly accept?: readonly string[];
173
173
  readonly maxSize?: string;
174
+ /** Only for `type: "files" | "images"` — upper bound on list length. */
175
+ readonly maxCount?: number;
174
176
  /** Nur bei `type: "file" | "image"` — Entity-Name + Field-Name für den
175
177
  * Upload-POST (`/api/files`), damit der Endpoint maxSize/accept gegen
176
178
  * die richtige Field-Def prüfen kann. */
177
179
  readonly entityType?: string;
178
180
  readonly fieldName?: string;
179
- /** Only for `type: "image"` — the variant name the preview requests
181
+ /** Record being edited (update mode only). Sent with the upload so the
182
+ * FileRef is attached to the record. */
183
+ readonly entityId?: string;
184
+ /** Only for `type: "image"` and `"images"` — the variant name the preview requests
180
185
  * (`/api/files/:id/variant/:name`) instead of the original. The FIRST
181
186
  * variant declared on the field def wins; a spec may legitimately carry
182
187
  * neither `size` nor `maxEdge` (format-/blur-only), so "the smallest one"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-headless",
3
- "version": "0.359.0",
3
+ "version": "0.360.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.360.0",
40
+ "@cosmicdrift/kumiko-types": "0.360.0",
41
41
  "temporal-polyfill": "^0.3.2",
42
42
  "zod": "^4.4.3"
43
43
  },
package/src/changes.json CHANGED
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.360.0",
4
+ "type": "improvement",
5
+ "title": "Edit view model carries entityId and maxCount for file fields",
6
+ "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."
7
+ },
2
8
  {
3
9
  "version": "0.357.0",
4
10
  "type": "improvement",