@cosmicdrift/kumiko-headless 0.332.0 → 0.334.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.
@@ -84,6 +84,13 @@ function stripUntouchedEmptyStrings(values, initial, forceIncludeEmpty) {
84
84
  function freezeSnapshot(snapshot) {
85
85
  return Object.freeze(snapshot);
86
86
  }
87
+ function omitPayloadFields(payload, omitFields) {
88
+ if (!omitFields || omitFields.length === 0)
89
+ return payload;
90
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload))
91
+ return payload;
92
+ return Object.fromEntries(Object.entries(payload).filter(([key]) => !omitFields.includes(key)));
93
+ }
87
94
  export function createFormController(options) {
88
95
  // Shallow copy so mutating the input after creation doesn't bleed into
89
96
  // the controller's internal state. `initial` stays fixed across the
@@ -340,6 +347,7 @@ export function createFormController(options) {
340
347
  else {
341
348
  payload = stripUntouchedEmptyStrings(submittedValues, submittedSnapshot.initial, forceIncludeEmpty);
342
349
  }
350
+ payload = omitPayloadFields(payload, submitCfg.omitFields);
343
351
  // Only enforced here, right before the write actually happens — a
344
352
  // form that never reaches this point (blocked by validation, or a
345
353
  // clean payloadMode "changes" no-op) must not throw just because no
@@ -53,6 +53,10 @@ export type SubmitConfig<TValues extends FormValues = FormValues> = {
53
53
  * "values" doc for why the stripping exists. Default `true` (current
54
54
  * behavior). Set `false` to send those fields as `""` unchanged. */
55
55
  readonly stripEmptySeeds?: boolean;
56
+ /** Top-level payload keys removed right before dispatch, for fields that
57
+ * are shown or validated but must not reach the write handler. Applied
58
+ * to the built payload only when it is a plain object. */
59
+ readonly omitFields?: readonly string[];
56
60
  readonly buildPayload?: (snapshot: FormSnapshot<TValues>) => unknown;
57
61
  readonly serverFieldPathPrefix?: string;
58
62
  /** Restricts submit()'s internal validate() call to these field names —
package/dist/index.d.ts CHANGED
@@ -11,4 +11,4 @@ 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
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";
14
- export { computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./view-model/index.js";
14
+ export { buildOptionLabels, computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./view-model/index.js";
package/dist/index.js CHANGED
@@ -6,4 +6,4 @@ export { createFormController, I18N_KEY_PARAM } from "./form/index.js";
6
6
  export { applyFormatSpec, currencyDecimals, escapeHtml, escapeHtmlAttr, escapeXml, html, isSafeHref, RawHtml, raw, stripControlChars, toInstant, } from "./format/index.js";
7
7
  export { resolveNavigation } from "./nav/index.js";
8
8
  export { createStore, shallowEqual } from "./store/index.js";
9
- export { computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./view-model/index.js";
9
+ export { buildOptionLabels, computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./view-model/index.js";
@@ -63,6 +63,9 @@ export function computeRelatedListSectionViewModel(sectionSpec, translate) {
63
63
  ...(sectionSpec.facets !== undefined && { facets: sectionSpec.facets }),
64
64
  ...(sectionSpec.rowClick !== undefined && { rowClick: sectionSpec.rowClick }),
65
65
  ...(sectionSpec.rowActions !== undefined && { rowActions: sectionSpec.rowActions }),
66
+ ...(sectionSpec.rowActionMode !== undefined && { rowActionMode: sectionSpec.rowActionMode }),
67
+ ...(sectionSpec.groupBy !== undefined && { groupBy: sectionSpec.groupBy }),
68
+ ...(sectionSpec.rowTone !== undefined && { rowTone: sectionSpec.rowTone }),
66
69
  ...(sectionSpec.toolbarActions !== undefined && { toolbarActions: sectionSpec.toolbarActions }),
67
70
  ...(sectionSpec.actions !== undefined && { actions: sectionSpec.actions }),
68
71
  ...(sectionSpec.emptyState !== undefined && {
@@ -117,9 +120,11 @@ function deriveTextFieldHints(fieldDef) {
117
120
  const format = fieldDef.type === "text"
118
121
  ? fieldDef.format
119
122
  : undefined;
123
+ const writeOnly = fieldDef.type === "text" && fieldDef.writeOnly === true;
120
124
  return {
121
125
  ...(multiline !== undefined && { multiline }),
122
126
  ...(format !== undefined && { format }),
127
+ ...(writeOnly && { writeOnly: true }),
123
128
  };
124
129
  }
125
130
  function deriveDateFieldHints(fieldDef) {
@@ -378,6 +383,7 @@ export function computeEditViewModel(input) {
378
383
  ...(sectionSpec.description !== undefined && {
379
384
  description: translate(sectionSpec.description),
380
385
  }),
386
+ ...(sectionSpec.subtitle !== undefined && { subtitle: translate(sectionSpec.subtitle) }),
381
387
  columns: sectionSpec.columns ?? defaultColumns,
382
388
  fields,
383
389
  ...(groups !== undefined && { groups }),
@@ -3,5 +3,5 @@ export { computeEditViewModel, computeRelatedListSectionViewModel } from "./edit
3
3
  export type { DerivedCellRoundingTarget, EmbeddedDerivedOp, EmbeddedListIssueGroups, } from "./embedded-list.js";
4
4
  export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./embedded-list.js";
5
5
  export type { ComputeListViewModelInput } from "./list.js";
6
- export { computeListViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, } from "./list.js";
6
+ export { buildOptionLabels, computeListViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, } from "./list.js";
7
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";
@@ -1,3 +1,3 @@
1
1
  export { computeEditViewModel, computeRelatedListSectionViewModel } from "./edit.js";
2
2
  export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./embedded-list.js";
3
- export { computeListViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, } from "./list.js";
3
+ export { buildOptionLabels, computeListViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, } from "./list.js";
@@ -1,4 +1,5 @@
1
1
  import { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, normalizeListColumn, parseRefTarget, } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ const WRITE_ONLY_SET_LABEL_KEY = "kumiko.field.writeOnly.set";
2
3
  // Pure transform: takes the declared screen + entity + incoming rows, spits
3
4
  // out the flat shape the renderer draws. No conditions, no access-checks —
4
5
  // those are list-level decisions that the caller makes BEFORE calling
@@ -136,9 +137,24 @@ export function computeListViewModel(input) {
136
137
  };
137
138
  columns.push(column);
138
139
  }
140
+ const writeOnlyFieldNames = screen.columns
141
+ .map((spec) => normalizeListColumn(spec).field)
142
+ .filter((name) => {
143
+ const fieldDef = entity.fields[name];
144
+ return fieldDef?.type === "text" && fieldDef.writeOnly === true;
145
+ });
146
+ // The wire carries `true` (set) / `null` (empty) for writeOnly fields; the cell shows a word, not "true".
147
+ const showWriteOnlyAsWord = (row) => {
148
+ const shown = { ...row };
149
+ for (const name of writeOnlyFieldNames) {
150
+ if (row[name] === true)
151
+ shown[name] = translate(WRITE_ONLY_SET_LABEL_KEY);
152
+ }
153
+ return shown;
154
+ };
139
155
  const listRows = rows.map((row) => ({
140
156
  id: String(row["id"] ?? ""),
141
- values: row,
157
+ values: showWriteOnlyAsWord(row),
142
158
  }));
143
159
  return {
144
160
  screenId: screen.id,
@@ -1,4 +1,4 @@
1
- import type { EditFieldSpec, EditSectionSpec, FieldIconKey, FieldRenderer, IconKey, ListColumnSpec, ListFacetSpec, ListSortSpec, PlatformComponent, RelatedListToolbarAction, RowAction, ScreenSlots, SelectOptionTone } from "@cosmicdrift/kumiko-framework/ui-types";
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
3
  export type ListColumnViewModel = {
4
4
  readonly field: string;
@@ -85,12 +85,13 @@ export type EditFieldViewModel = {
85
85
  * `<feature>:entity:<entity>:field:<field>:option:<value>`. */
86
86
  readonly optionLabels?: Readonly<Record<string, string>>;
87
87
  /** Set for `type: "select"` when SelectFieldDef.optionsQuery is set: the
88
- * renderer loads the options from this query (`{ rows: { value, label }[] }`,
89
- * labels verbatim) instead of `options`. `payload` is the static
90
- * optionsQueryPayload, `{}` when absent. */
88
+ * renderer loads the options from this query (`{ rows: { value, label,
89
+ * description?, group? }[] }`, labels verbatim) instead of `options`. `payload`
90
+ * is optionsQueryPayload (`{ field }` refs are resolved by the renderer against
91
+ * the live form values), `{}` when absent. */
91
92
  readonly selectOptionsQuery?: {
92
93
  readonly query: string;
93
- readonly payload: Readonly<Record<string, string | number | boolean>>;
94
+ readonly payload: OptionsQueryPayload;
94
95
  };
95
96
  /** Set for `type: "multiSelect"` when MultiSelectFieldDef.display is
96
97
  * "checkboxes" — the renderer shows a checkbox grid with a select-all
@@ -116,6 +117,8 @@ export type EditFieldViewModel = {
116
117
  };
117
118
  /** From TextFieldDef.format — `password` makes the renderer mask the input. */
118
119
  readonly format?: "email" | "url" | "phone" | "password";
120
+ /** From TextFieldDef.writeOnly — value is `true` (set) / `null` (empty), never plaintext. */
121
+ readonly writeOnly?: true;
119
122
  /** Nur bei `type: "timestamp"` gesetzt wenn TimestampFieldDef.locatedBy
120
123
  * existiert — Wall-Clock-Zeit ohne Offset. Der Renderer emittiert
121
124
  * dann lokale Zeit ohne `Z` statt eines UTC-Instants. */
@@ -221,6 +224,8 @@ export type EditFieldsSectionViewModel = {
221
224
  /** Translated help text under the block heading, from
222
225
  * `EditFieldsSection.description`. Renders even without `title`. */
223
226
  readonly description?: string;
227
+ /** Translated `EditFieldsSection.subtitle` (wizard step rail / up-next box). */
228
+ readonly subtitle?: string;
224
229
  readonly columns: number;
225
230
  readonly fields: readonly EditFieldViewModel[];
226
231
  /** From `EditFieldsSection.groups` — each entry's `fields` are the same
@@ -272,6 +277,9 @@ export type EditRelatedListSectionViewModel = {
272
277
  readonly idColumn?: string;
273
278
  };
274
279
  readonly rowActions?: readonly RowAction[];
280
+ readonly rowActionMode?: RowActionMode;
281
+ readonly groupBy?: RelatedListGroupBy;
282
+ readonly rowTone?: RelatedListRowTone;
275
283
  readonly toolbarActions?: readonly RelatedListToolbarAction[];
276
284
  /** From `EditRelatedListSection.actions` — rendered in the
277
285
  * Section's title row, distinct from `toolbarActions` (which render
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-headless",
3
- "version": "0.332.0",
3
+ "version": "0.334.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.332.0",
39
+ "@cosmicdrift/kumiko-framework": "0.334.0",
40
40
  "temporal-polyfill": "^0.3.2",
41
41
  "zod": "^4.4.3"
42
42
  },