@cosmicdrift/kumiko-headless 0.190.0 → 0.192.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.190.0",
3
+ "version": "0.192.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.190.0",
39
+ "@cosmicdrift/kumiko-framework": "0.192.0",
40
40
  "temporal-polyfill": "^0.3.2",
41
41
  "zod": "^4.4.3"
42
42
  },
@@ -549,4 +549,48 @@ describe("computeEditViewModel — embedded-list cells (#1835)", () => {
549
549
  const field = asFields(vm.sections[0]).fields[0];
550
550
  expect(field?.embeddedListCurrency).toBeUndefined();
551
551
  });
552
+
553
+ test("imageVariant is the FIRST variant declared on the field def", () => {
554
+ const entity = {
555
+ fields: {
556
+ avatar: {
557
+ type: "image",
558
+ variants: {
559
+ profile: { fit: "cover", size: { width: 512, height: 512 }, format: "webp" },
560
+ big: { fit: "cover", size: { width: 1024, height: 1024 }, format: "webp" },
561
+ },
562
+ },
563
+ },
564
+ } as unknown as EntityDefinition;
565
+
566
+ const vm = computeEditViewModel({
567
+ screen: editScreen({ sections: [{ title: "x", fields: ["avatar"] }] }),
568
+ entity,
569
+ values: {},
570
+ translate,
571
+ featureName: "orders",
572
+ });
573
+
574
+ const field = asFields(vm.sections[0]).fields[0];
575
+ expect(field?.imageVariant).toBe("profile");
576
+ });
577
+
578
+ test("imageVariant is undefined for an image field without variants", () => {
579
+ const entity = {
580
+ fields: {
581
+ avatar: { type: "image" },
582
+ },
583
+ } as unknown as EntityDefinition;
584
+
585
+ const vm = computeEditViewModel({
586
+ screen: editScreen({ sections: [{ title: "x", fields: ["avatar"] }] }),
587
+ entity,
588
+ values: {},
589
+ translate,
590
+ featureName: "orders",
591
+ });
592
+
593
+ const field = asFields(vm.sections[0]).fields[0];
594
+ expect(field?.imageVariant).toBeUndefined();
595
+ });
552
596
  });
@@ -170,8 +170,14 @@ export function computeEditViewModel<
170
170
  // importing it just for one fallback string.
171
171
  const resolvedCurrency = entity.defaultCurrency ?? "EUR";
172
172
  const fileDef = isFileType
173
- ? (fieldDef as unknown as { accept?: readonly string[]; maxSize?: string })
173
+ ? (fieldDef as unknown as {
174
+ accept?: readonly string[];
175
+ maxSize?: string;
176
+ variants?: Readonly<Record<string, unknown>>;
177
+ })
174
178
  : undefined;
179
+ const imageVariant =
180
+ fieldDef.type === "image" ? Object.keys(fileDef?.variants ?? {})[0] : undefined;
175
181
  // Embedded-LIST field (`multiple: true`) — per-cell metadata for a
176
182
  // renderer to draw one row per array item (invoice-positions-style
177
183
  // table). A plain (non-list) embedded field emits none of this; the
@@ -261,6 +267,7 @@ export function computeEditViewModel<
261
267
  ...(fileDef?.accept !== undefined && { accept: fileDef.accept }),
262
268
  ...(fileDef?.maxSize !== undefined && { maxSize: fileDef.maxSize }),
263
269
  ...(isFileType && { entityType: screen.entity, fieldName: normalized.field }),
270
+ ...(imageVariant !== undefined && { imageVariant }),
264
271
  ...(normalized.icon !== undefined && { icon: normalized.icon }),
265
272
  ...(embeddedListCells !== undefined && { embeddedListCells }),
266
273
  ...(embeddedListDef?.minItems !== undefined && {
@@ -181,6 +181,13 @@ export type EditFieldViewModel = {
181
181
  * die richtige Field-Def prüfen kann. */
182
182
  readonly entityType?: string;
183
183
  readonly fieldName?: string;
184
+ /** Only for `type: "image"` — the variant name the preview requests
185
+ * (`/api/files/:id/variant/:name`) instead of the original. The FIRST
186
+ * variant declared on the field def wins; a spec may legitimately carry
187
+ * neither `size` nor `maxEdge` (format-/blur-only), so "the smallest one"
188
+ * is not a rule that always has an answer. Absent when the field declares
189
+ * no variants — the preview then loads the original. */
190
+ readonly imageVariant?: string;
184
191
  /** Prefix-icon key from `EditFieldSpec.icon`, passed through unchanged
185
192
  * (no i18n — it's a symbolic key, not a display string). The renderer
186
193
  * resolves it against the FIELD_ICONS registry; unknown keys silently