@cosmicdrift/kumiko-headless 0.354.1 → 0.356.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/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, 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, 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";
@@ -6,6 +6,11 @@ export type ComputeEditViewModelInput<TValues extends Readonly<Record<string, un
6
6
  readonly values: TValues;
7
7
  readonly translate: Translate;
8
8
  readonly featureName: string;
9
+ /** Which write the form drives. Applies the screen's `writeExcludedFields`
10
+ * for that verb (create hides them, update shows them read-only); omitted
11
+ * means no exclusion applies. */
12
+ readonly mode?: EditFormMode;
9
13
  };
14
+ export type EditFormMode = "create" | "update";
10
15
  export declare function computeRelatedListSectionViewModel(sectionSpec: EditRelatedListSection, translate: Translate): EditRelatedListSectionViewModel;
11
16
  export declare function computeEditViewModel<TValues extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>>(input: ComputeEditViewModelInput<TValues>): EditViewModel;
@@ -42,6 +42,9 @@ function computeWriteFormSectionViewModel(sectionSpec, screenId, values, transla
42
42
  ...(sectionSpec.actions !== undefined && { actions: sectionSpec.actions }),
43
43
  };
44
44
  }
45
+ function wasEmptiedByWriteExclusion(declaredFieldSpecs, remainingFields) {
46
+ return declaredFieldSpecs.length > 0 && remainingFields.length === 0;
47
+ }
45
48
  // relatedList runs its own query — nothing here to resolve against
46
49
  // entity/values, the spec passes through verbatim (fw#2166).
47
50
  export function computeRelatedListSectionViewModel(sectionSpec, translate) {
@@ -304,7 +307,9 @@ function buildExtensionSectionViewModel(sectionSpec, translate) {
304
307
  // section/field tree the renderer draws. FieldConditions are evaluated here
305
308
  // so the renderer never re-runs them during React render.
306
309
  export function computeEditViewModel(input) {
307
- const { screen, entity, values, translate, featureName } = input;
310
+ const { screen, entity, values, translate, featureName, mode } = input;
311
+ const excludedFields = new Set(mode === undefined ? [] : screen.writeExcludedFields?.[mode]);
312
+ const isHiddenByExclusion = (fieldSpec) => mode === "create" && excludedFields.has(normalizeEditField(fieldSpec).field);
308
313
  const sections = screen.layout.sections.map((sectionSpec) => {
309
314
  if (isExtensionEditSection(sectionSpec)) {
310
315
  return buildExtensionSectionViewModel(sectionSpec, translate);
@@ -317,7 +322,10 @@ export function computeEditViewModel(input) {
317
322
  }
318
323
  // `groups` flattens into the same per-field pipeline as plain `fields`;
319
324
  // the group structure below just re-groups the computed views by name.
320
- const fields = sectionFieldSpecs(sectionSpec).map((fieldSpec) => {
325
+ const declaredFieldSpecs = sectionFieldSpecs(sectionSpec);
326
+ const fields = declaredFieldSpecs
327
+ .filter((fieldSpec) => !isHiddenByExclusion(fieldSpec))
328
+ .map((fieldSpec) => {
321
329
  const normalized = normalizeEditField(fieldSpec);
322
330
  const fieldDef = entity.fields[normalized.field];
323
331
  if (!fieldDef) {
@@ -343,7 +351,8 @@ export function computeEditViewModel(input) {
343
351
  // `readOnly` (camelCase) is the name on both sides: EditFieldSpec
344
352
  // in the engine, and the view-model emitted here. One convention
345
353
  // through the stack beats translating at the boundary.
346
- const readOnly = evalCondition(normalized.readOnly, false, values);
354
+ const readOnly = (mode === "update" && excludedFields.has(normalized.field)) ||
355
+ evalCondition(normalized.readOnly, false, values);
347
356
  // `required` on the field-spec overrides the entity-default. A
348
357
  // field that's required at the entity-level but marked required:
349
358
  // false on the screen (e.g. a soft-onboarding wizard that
@@ -383,14 +392,17 @@ export function computeEditViewModel(input) {
383
392
  };
384
393
  return view;
385
394
  });
386
- // Boot-validator rejects fields.length === 0 with no groups (screens.ts),
387
- // so an empty section never reaches this code.
395
+ // The boot-validator rejects declared-empty sections (screens.ts), but a
396
+ // create-excluded field list can still empty one here: then `visible` is false.
388
397
  const visible = fields.some((field) => field.visible);
389
398
  // Tabs mode renders this section as its own panel, where two columns reads
390
399
  // better than the single-column default a stacked form keeps. Applies to
391
400
  // entityEdit tabs as well since fw#3134, not just projectionDetail.
392
401
  const defaultColumns = screen.layout.mode === "tabs" ? 2 : 1;
393
- const groups = sectionSpec.groups?.map((group) => ({
402
+ const groups = sectionSpec.groups
403
+ ?.map((group) => ({ ...group, fields: group.fields.filter((f) => !isHiddenByExclusion(f)) }))
404
+ .filter((group) => group.fields.length > 0)
405
+ .map((group) => ({
394
406
  title: translate(group.title),
395
407
  columns: group.columns ?? 2,
396
408
  fields: group.fields.map((fieldSpec) => {
@@ -407,6 +419,7 @@ export function computeEditViewModel(input) {
407
419
  kind: "fields",
408
420
  ...(sectionSpec.id !== undefined && { id: sectionSpec.id }),
409
421
  visible,
422
+ emptiedByWriteExclusion: wasEmptiedByWriteExclusion(declaredFieldSpecs, fields),
410
423
  // Titellose Section (flache Form) → kein h3; nur übersetzen wenn gesetzt.
411
424
  ...(sectionSpec.title !== undefined && { title: translate(sectionSpec.title) }),
412
425
  ...(sectionSpec.description !== undefined && {
@@ -1,4 +1,4 @@
1
- export type { ComputeEditViewModelInput } from "./edit.js";
1
+ export type { ComputeEditViewModelInput, EditFormMode } from "./edit.js";
2
2
  export { computeEditViewModel, computeRelatedListSectionViewModel } from "./edit.js";
3
3
  export type { DerivedCellRoundingTarget, EmbeddedDerivedOp, EmbeddedListIssueGroups, } from "./embedded-list.js";
4
4
  export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./embedded-list.js";
@@ -228,6 +228,10 @@ export type EditFieldsSectionViewModel = {
228
228
  * The renderer then draws nothing, but the section stays in the array
229
229
  * (key stability). */
230
230
  readonly visible: boolean;
231
+ /** Set when every declared field was hidden by the create handler's
232
+ * `excludeFields`. Unlike a declared-empty section (review step), the
233
+ * renderer must not give it a tab or wizard step. */
234
+ readonly emptiedByWriteExclusion?: boolean;
231
235
  /** Optional — eine titellose Section rendert nur ihre Felder (flache Form). */
232
236
  readonly title?: string;
233
237
  /** Translated help text under the block heading, from
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-headless",
3
- "version": "0.354.1",
3
+ "version": "0.356.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.354.1",
40
- "@cosmicdrift/kumiko-types": "0.354.1",
39
+ "@cosmicdrift/kumiko-framework": "0.356.0",
40
+ "@cosmicdrift/kumiko-types": "0.356.0",
41
41
  "temporal-polyfill": "^0.3.2",
42
42
  "zod": "^4.4.3"
43
43
  },