@cosmicdrift/kumiko-renderer 0.399.0 → 0.400.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/app/entity-edit-prefill.js +7 -1
- package/dist/app/form-schema.js +42 -17
- package/dist/app/projection-detail-body.js +7 -0
- package/dist/app/projection-detail-header-model.d.ts +1 -1
- package/dist/app/projection-detail-header-model.js +2 -2
- package/dist/app/projection-detail-header-region.d.ts +1 -0
- package/dist/app/projection-detail-header-region.js +1 -1
- package/dist/app/projection-detail-header.js +4 -2
- package/dist/app/projection-detail-navigation.d.ts +1 -0
- package/dist/app/projection-detail-navigation.js +9 -1
- package/dist/app/use-related-list-tab-counts.d.ts +5 -0
- package/dist/app/use-related-list-tab-counts.js +68 -0
- package/dist/components/embedded-list-field.js +69 -91
- package/dist/components/embedded-list-paste.d.ts +2 -0
- package/dist/components/embedded-list-paste.js +73 -0
- package/dist/components/related-list-section.js +4 -8
- package/dist/components/render-edit-helpers.js +2 -2
- package/dist/components/render-field-choice-inputs.js +1 -1
- package/dist/embedded-list-types.d.ts +117 -0
- package/dist/embedded-list-types.js +1 -0
- package/dist/headless/form/form-controller.js +9 -1
- package/dist/headless/index.d.ts +1 -1
- package/dist/headless/index.js +1 -1
- package/dist/headless/view-model/edit.js +40 -50
- package/dist/headless/view-model/embedded-list-cells.d.ts +12 -0
- package/dist/headless/view-model/embedded-list-cells.js +49 -0
- package/dist/headless/view-model/embedded-list.d.ts +1 -1
- package/dist/headless/view-model/embedded-list.js +1 -1
- package/dist/headless/view-model/index.d.ts +1 -1
- package/dist/headless/view-model/index.js +1 -1
- package/dist/headless/view-model/list.js +2 -3
- package/dist/headless/view-model/types.d.ts +23 -6
- package/dist/hooks/related-list-parent-payload.d.ts +2 -0
- package/dist/hooks/related-list-parent-payload.js +7 -0
- package/dist/i18n-defaults.js +3 -0
- package/dist/primitives.d.ts +4 -88
- package/dist/web/lib/file-upload-form-data.d.ts +2 -0
- package/dist/web/lib/file-upload-form-data.js +2 -0
- package/dist/web/lib/upload-file-ref.d.ts +1 -1
- package/dist/web/primitives/data-table-cell.d.ts +2 -0
- package/dist/web/primitives/data-table-cell.js +1 -1
- package/dist/web/primitives/embedded-file-cell.d.ts +11 -0
- package/dist/web/primitives/embedded-file-cell.js +50 -0
- package/dist/web/primitives/embedded-list-cards.d.ts +38 -0
- package/dist/web/primitives/embedded-list-cards.js +71 -0
- package/dist/web/primitives/embedded-list-cell.d.ts +41 -0
- package/dist/web/primitives/embedded-list-cell.js +132 -0
- package/dist/web/primitives/embedded-list-input.d.ts +1 -1
- package/dist/web/primitives/embedded-list-input.js +29 -142
- package/dist/web/primitives/file-upload.d.ts +2 -1
- package/dist/web/primitives/file-upload.js +2 -1
- package/dist/web/primitives/input-choice-kinds.js +1 -1
- package/dist/web/primitives/input-optional-props.d.ts +2 -1
- package/dist/web/primitives/input-optional-props.js +2 -1
- package/dist/web/primitives/locale-number.d.ts +3 -0
- package/dist/web/primitives/locale-number.js +8 -0
- package/dist/web/primitives/number-input.d.ts +2 -1
- package/dist/web/primitives/number-input.js +4 -11
- package/dist/web/primitives/segmented-select.d.ts +6 -1
- package/dist/web/primitives/segmented-select.js +8 -4
- package/dist/web/widgets/status-badge.d.ts +1 -0
- package/dist/web/widgets/status-badge.js +1 -1
- package/package.json +5 -5
|
@@ -73,7 +73,7 @@ export function wizardStepKey(section, index) {
|
|
|
73
73
|
return declaredId ?? section.title ?? `step-${index}`;
|
|
74
74
|
}
|
|
75
75
|
function isMoneyValue(value) {
|
|
76
|
-
return typeof value === "object" && value !== null && "amount" in value;
|
|
76
|
+
return typeof value === "object" && value !== null && ("amount" in value || "currency" in value);
|
|
77
77
|
}
|
|
78
78
|
// The seeds buildInitialValues writes for NULL columns of an existing record.
|
|
79
79
|
function isEmptyFormValue(value) {
|
|
@@ -82,7 +82,7 @@ function isEmptyFormValue(value) {
|
|
|
82
82
|
}
|
|
83
83
|
if (Array.isArray(value))
|
|
84
84
|
return value.length === 0;
|
|
85
|
-
return isMoneyValue(value) && value.amount === 0;
|
|
85
|
+
return isMoneyValue(value) && (value.amount === 0 || value.amount === undefined);
|
|
86
86
|
}
|
|
87
87
|
// select/boolean are excluded from the "holds user data" check: they are
|
|
88
88
|
// often pre-filled (defaults, server-set values), so they say nothing about
|
|
@@ -25,7 +25,7 @@ export function renderSelectInput({ field, Input, onChange }, { common }) {
|
|
|
25
25
|
// `display` is shared with multiSelect's "checkboxes", which is not a
|
|
26
26
|
// select presentation — narrow to the two a select field can request.
|
|
27
27
|
const display = field.display === "radio" || field.display === "dropdown" ? field.display : undefined;
|
|
28
|
-
return (_jsx(Input, { kind: "select", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), options: selectOptions, ...(display !== undefined && { display }) }));
|
|
28
|
+
return (_jsx(Input, { kind: "select", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), options: selectOptions, ...(display !== undefined && { display }), ...(field.optionTones !== undefined && { optionTones: field.optionTones }) }));
|
|
29
29
|
}
|
|
30
30
|
export function renderFileInput({ field, Input, onChange }, { common }) {
|
|
31
31
|
const kind = field.type === "image" ? "image" : "file";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { SelectOptionTone } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { FieldIssue } from "./headless/index.ts";
|
|
3
|
+
/** Cell type for one column of an embedded-list field. Mirrors
|
|
4
|
+
* EmbeddedListCellViewModel["type"] from `@cosmicdrift/kumiko-renderer/headless`. */
|
|
5
|
+
export type EmbeddedListCellType = "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp" | "images" | "files";
|
|
6
|
+
/** One column of an embedded-list table (one entry per key of the source
|
|
7
|
+
* EmbeddedFieldDef's `schema`). */
|
|
8
|
+
export type EmbeddedListColumn = {
|
|
9
|
+
readonly field: string;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly type: EmbeddedListCellType;
|
|
12
|
+
readonly required: boolean;
|
|
13
|
+
/** Read-only, value supplied by the caller (already computed from
|
|
14
|
+
* sibling cells) — not directly user-editable. */
|
|
15
|
+
readonly derived: boolean;
|
|
16
|
+
/** Shown but not editable by the user; unlike `derived` it is not computed. */
|
|
17
|
+
readonly readOnly?: boolean;
|
|
18
|
+
/** Phone cards skip this column; the table still shows it. */
|
|
19
|
+
readonly hiddenInCard?: boolean;
|
|
20
|
+
/** Phone cards show the row's min/max (sub-field names) as a hint under this column's control. */
|
|
21
|
+
readonly cardLimits?: {
|
|
22
|
+
readonly min: string;
|
|
23
|
+
readonly max: string;
|
|
24
|
+
};
|
|
25
|
+
/** Phone cards render this column unlabeled beside the named column's control. */
|
|
26
|
+
readonly cardAttachedTo?: string;
|
|
27
|
+
/** Only for `type: "select"`. */
|
|
28
|
+
readonly options?: readonly string[];
|
|
29
|
+
readonly optionLabels?: Readonly<Record<string, string>>;
|
|
30
|
+
/** Only for `type: "select"`; `"radio"` renders segmented buttons, the
|
|
31
|
+
* default is a dropdown. */
|
|
32
|
+
readonly display?: "radio" | "dropdown";
|
|
33
|
+
/** Only for `type: "reference"` — pre-fetched by the caller, ONE query
|
|
34
|
+
* per column shared across every row (not one query per cell). */
|
|
35
|
+
readonly referenceOptions?: readonly {
|
|
36
|
+
readonly value: string;
|
|
37
|
+
readonly label: string;
|
|
38
|
+
}[];
|
|
39
|
+
readonly referenceLoading?: boolean;
|
|
40
|
+
/** Only for `type: "select"`; tone per option (selected segment, derived badge). */
|
|
41
|
+
readonly optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>;
|
|
42
|
+
/** Only for `type: "images" | "files"`. */
|
|
43
|
+
readonly capture?: "user" | "environment";
|
|
44
|
+
readonly maxCount?: number;
|
|
45
|
+
readonly accept?: readonly string[];
|
|
46
|
+
/** Per-row state, evaluated by the caller (conditions are engine logic the primitive must not import).
|
|
47
|
+
* A hidden cell keeps its value but shows no control. */
|
|
48
|
+
readonly isHiddenInRow?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
49
|
+
readonly isRequiredInRow?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
50
|
+
/** Overrides `derived` per row: a conditional derived cell is editable on rows where it does not apply. */
|
|
51
|
+
readonly isDerivedInRow?: (row: Readonly<Record<string, unknown>>) => boolean;
|
|
52
|
+
};
|
|
53
|
+
/** One entry of an embedded-list totals row (e.g. an invoice's total
|
|
54
|
+
* amount, summed across all rows by the caller). */
|
|
55
|
+
export type EmbeddedListTotal = {
|
|
56
|
+
readonly field: string;
|
|
57
|
+
readonly label: string;
|
|
58
|
+
readonly value: number;
|
|
59
|
+
/** Set for labelled multi-column totals, which have no single column to
|
|
60
|
+
* read the money type from. */
|
|
61
|
+
readonly money?: boolean;
|
|
62
|
+
};
|
|
63
|
+
/** Row-array + totals table for a `createEmbeddedListField` (invoice-
|
|
64
|
+
* positions-style) field — structurally too different from `Input`'s
|
|
65
|
+
* value/onChange union to fit there, so it's its own primitive, same as
|
|
66
|
+
* `DataTable`. Controlled: the caller (the eventual form-field wrapper)
|
|
67
|
+
* computes rows/derived values/issue groups and wires every callback;
|
|
68
|
+
* this primitive only renders and reports interaction. */
|
|
69
|
+
export type EmbeddedListInputProps = {
|
|
70
|
+
readonly id: string;
|
|
71
|
+
readonly columns: readonly EmbeddedListColumn[];
|
|
72
|
+
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
73
|
+
readonly totals?: readonly EmbeddedListTotal[];
|
|
74
|
+
/** Currency for money cells and the totals row — one value for the whole
|
|
75
|
+
* list (currency lives on the head aggregate, not the row). Web impl
|
|
76
|
+
* falls back to "EUR" when absent. */
|
|
77
|
+
readonly currency?: string;
|
|
78
|
+
/** Whole-list read-only (e.g. a released invoice) — hides row-mutation
|
|
79
|
+
* affordances and disables every cell. */
|
|
80
|
+
readonly disabled?: boolean;
|
|
81
|
+
/** Rows are fixed: no add, remove, duplicate or move; cells stay editable. */
|
|
82
|
+
readonly fixedRows?: boolean;
|
|
83
|
+
readonly minItems?: number;
|
|
84
|
+
readonly maxItems?: number;
|
|
85
|
+
/** Upload target of file cells: `entityType`/`entityId`/`fieldName` of the list, the column name is the `subField`. */
|
|
86
|
+
readonly uploadTarget?: {
|
|
87
|
+
readonly entityType: string;
|
|
88
|
+
readonly fieldName: string;
|
|
89
|
+
readonly entityId?: string;
|
|
90
|
+
};
|
|
91
|
+
/** Shown under the whole list (e.g. under the totals row). */
|
|
92
|
+
readonly listIssues?: readonly FieldIssue[];
|
|
93
|
+
/** Keyed by row index. */
|
|
94
|
+
readonly rowIssues?: Readonly<Record<number, readonly FieldIssue[]>>;
|
|
95
|
+
/** Keyed `${rowIndex}.${field}`. */
|
|
96
|
+
readonly cellIssues?: Readonly<Record<string, readonly FieldIssue[]>>;
|
|
97
|
+
readonly onCellChange: (rowIndex: number, field: string, value: unknown) => void;
|
|
98
|
+
readonly onAddRow: () => void;
|
|
99
|
+
readonly onRemoveRow: (rowIndex: number) => void;
|
|
100
|
+
readonly onDuplicateRow: (rowIndex: number) => void;
|
|
101
|
+
readonly onMoveRow: (fromIndex: number, toIndex: number) => void;
|
|
102
|
+
/** Tab/newline-delimited clipboard paste starting at (rowIndex,
|
|
103
|
+
* columnIndex) — parsed to a 2D string grid by the Web impl (paste
|
|
104
|
+
* events are a browser-only concept); undefined caller = no paste
|
|
105
|
+
* handling wired up. */
|
|
106
|
+
readonly onPasteCells?: (rowIndex: number, columnIndex: number, grid: readonly (readonly string[])[]) => void;
|
|
107
|
+
readonly addLabel: string;
|
|
108
|
+
/** Accessible name of the (otherwise headerless) row-actions column. */
|
|
109
|
+
readonly actionsLabel: string;
|
|
110
|
+
readonly removeLabel: string;
|
|
111
|
+
readonly duplicateLabel: string;
|
|
112
|
+
readonly moveUpLabel: string;
|
|
113
|
+
readonly moveDownLabel: string;
|
|
114
|
+
readonly emptyLabel: string;
|
|
115
|
+
readonly emptyCtaLabel: string;
|
|
116
|
+
readonly testId?: string;
|
|
117
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -62,6 +62,13 @@ function valuesDiff(current, initial) {
|
|
|
62
62
|
// of being omitted — no validation error surfaces it, unlike the "" case.
|
|
63
63
|
// Fixing that needs real per-field seed-tracking (which value was actually
|
|
64
64
|
// user-set vs. defaulted), not just a wider strip predicate here.
|
|
65
|
+
// The seed of an untouched optional money field (buildInitialValues); never a valid payload value.
|
|
66
|
+
function isCurrencyOnlyMoney(value) {
|
|
67
|
+
return (typeof value === "object" &&
|
|
68
|
+
value !== null &&
|
|
69
|
+
"currency" in value &&
|
|
70
|
+
!("amount" in value && value.amount !== undefined));
|
|
71
|
+
}
|
|
65
72
|
function stripUntouchedEmptyStrings(values, initial, forceIncludeEmpty) {
|
|
66
73
|
const v = values; // @cast-boundary form-values
|
|
67
74
|
const ini = initial; // @cast-boundary form-values
|
|
@@ -69,7 +76,8 @@ function stripUntouchedEmptyStrings(values, initial, forceIncludeEmpty) {
|
|
|
69
76
|
for (const key of Object.keys(v)) {
|
|
70
77
|
// Value-based seed strip, except keys previously submitted as "" after a
|
|
71
78
|
// real clear — those must survive post-rebase submits.
|
|
72
|
-
|
|
79
|
+
const untouchedEmptyString = !forceIncludeEmpty.has(key) && v[key] === "" && ini[key] === "";
|
|
80
|
+
if (untouchedEmptyString || isCurrencyOnlyMoney(v[key])) {
|
|
73
81
|
out ??= { ...v };
|
|
74
82
|
delete out[key];
|
|
75
83
|
}
|
package/dist/headless/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ export { pluralKey } from "./plural-key.ts";
|
|
|
12
12
|
export type { Store, WritableStore } from "./store/index.ts";
|
|
13
13
|
export { createStore, shallowEqual } from "./store/index.ts";
|
|
14
14
|
export type { CellLink, CellLinkItem, ComputeEditViewModelInput, ComputeListViewModelInput, DerivedCellRoundingTarget, EditExtensionSectionViewModel, EditFieldSpec, EditFieldsSectionViewModel, EditFieldViewModel, EditFormMode, EditRelatedListSectionViewModel, EditSectionSpec, EditSectionViewModel, EditViewModel, EditWriteFormSectionViewModel, EmbeddedDerivedOp, EmbeddedListCellViewModel, EmbeddedListIssueGroups, FieldConditionCtx, FieldRenderer, ListColumnFileDisplay, ListColumnSpec, ListColumnViewModel, ListRowViewModel, ListViewModel, RuntimeCellLink, RuntimeCellLinkItems, RuntimeRenderer, ScreenSlots, Translate, } from "./view-model/index.ts";
|
|
15
|
-
export { buildOptionLabels, computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./view-model/index.ts";
|
|
15
|
+
export { buildOptionLabels, computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, withDerivedCells, } from "./view-model/index.ts";
|
package/dist/headless/index.js
CHANGED
|
@@ -7,4 +7,4 @@ export { applyFormatSpec, currencyDecimals, DOCUMENT_FALLBACK_CURRENCY, EMPTY_VA
|
|
|
7
7
|
export { resolveNavigation } from "./nav/index.js";
|
|
8
8
|
export { pluralKey } from "./plural-key.js";
|
|
9
9
|
export { createStore, shallowEqual } from "./store/index.js";
|
|
10
|
-
export { buildOptionLabels, computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./view-model/index.js";
|
|
10
|
+
export { buildOptionLabels, computeDerivedCellValue, computeEditViewModel, computeListViewModel, computeRelatedListSectionViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, withDerivedCells, } from "./view-model/index.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { availableSelectOptions, evalFieldCondition, isExtensionEditSection, isFieldsEditSection, isFormDerivationInputMode, isUnusedFallbackOperand, isWriteFormEditSection, normalizeEditField, parseRefTarget, sectionFieldSpecs, WRITE_FORM_SECTION_ENTITY, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
-
import {
|
|
1
|
+
import { availableSelectOptions, evalFieldCondition, isExtensionEditSection, isFieldRequired, isFieldsEditSection, isFieldVisible, isFormDerivationInputMode, isUnusedFallbackOperand, isWriteFormEditSection, normalizeEditField, parseRefTarget, sectionFieldSpecs, WRITE_FORM_SECTION_ENTITY, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { buildEmbeddedListCell } from "./embedded-list-cells.js";
|
|
3
|
+
import { buildOptionLabels, fieldLabelKey, fieldOptionLabelKey } from "./list.js";
|
|
3
4
|
// Resolves a writeForm section's own fieldDefs/fields through the same
|
|
4
5
|
// per-field pipeline as a plain fields section, by reusing the pipeline
|
|
5
6
|
// itself rather than duplicating it: wrap fieldDefs/fields as a throwaway
|
|
@@ -161,15 +162,22 @@ function selectableOptions(fieldDef, fieldName, values) {
|
|
|
161
162
|
const current = values[fieldName];
|
|
162
163
|
return fieldDef.options.filter((option) => available.includes(option) || option === current);
|
|
163
164
|
}
|
|
165
|
+
function deriveSelectDisplay(fieldDef) {
|
|
166
|
+
const declaredDisplay = fieldDef.type === "multiSelect" || fieldDef.type === "select" ? fieldDef.display : undefined;
|
|
167
|
+
// The renderer's heuristic counts options, so a filtered list would switch widgets with the discriminator.
|
|
168
|
+
const display = declaredDisplay ??
|
|
169
|
+
(fieldDef.type === "select" && fieldDef.conditionalOptions ? "dropdown" : undefined);
|
|
170
|
+
return {
|
|
171
|
+
...(display !== undefined && { display }),
|
|
172
|
+
...(fieldDef.type === "select" &&
|
|
173
|
+
fieldDef.optionTones !== undefined && { optionTones: fieldDef.optionTones }),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
164
176
|
function deriveSelectFieldHints(fieldDef, translate, featureName, entityName, fieldName, values) {
|
|
165
177
|
const options = selectableOptions(fieldDef, fieldName, values);
|
|
166
178
|
const optionLabels = options !== undefined
|
|
167
179
|
? buildOptionLabels(translate, (value) => fieldOptionLabelKey(featureName, entityName, fieldName, value), options)
|
|
168
180
|
: undefined;
|
|
169
|
-
const declaredDisplay = fieldDef.type === "multiSelect" || fieldDef.type === "select" ? fieldDef.display : undefined;
|
|
170
|
-
// The renderer's heuristic counts options, so a filtered list would switch widgets with the discriminator.
|
|
171
|
-
const display = declaredDisplay ??
|
|
172
|
-
(fieldDef.type === "select" && fieldDef.conditionalOptions ? "dropdown" : undefined);
|
|
173
181
|
const columns = fieldDef.type === "multiSelect" ? fieldDef.columns : undefined;
|
|
174
182
|
const maxRows = fieldDef.type === "multiSelect" ? fieldDef.maxRows : undefined;
|
|
175
183
|
return {
|
|
@@ -177,7 +185,7 @@ function deriveSelectFieldHints(fieldDef, translate, featureName, entityName, fi
|
|
|
177
185
|
...deriveSelectOptionsAvailabilityQuery(fieldDef),
|
|
178
186
|
...(options !== undefined && { options }),
|
|
179
187
|
...(optionLabels !== undefined && { optionLabels }),
|
|
180
|
-
...(
|
|
188
|
+
...deriveSelectDisplay(fieldDef),
|
|
181
189
|
...(columns !== undefined && { columns }),
|
|
182
190
|
...(maxRows !== undefined && { maxRows }),
|
|
183
191
|
};
|
|
@@ -195,10 +203,6 @@ function deriveTextFieldHints(fieldDef) {
|
|
|
195
203
|
...("sensitive" in fieldDef && fieldDef.sensitive === true && { sensitive: true }),
|
|
196
204
|
};
|
|
197
205
|
}
|
|
198
|
-
// files/images field defs carry no `required` property.
|
|
199
|
-
function isRequiredByEntity(fieldDef) {
|
|
200
|
-
return "required" in fieldDef && fieldDef.required === true;
|
|
201
|
-
}
|
|
202
206
|
function deriveDateFieldHints(fieldDef) {
|
|
203
207
|
const wallClock = fieldDef.type === "timestamp" && fieldDef.locatedBy !== undefined ? true : undefined;
|
|
204
208
|
const dateBounds = fieldDef.type === "date" ||
|
|
@@ -287,48 +291,16 @@ function deriveFileFieldHints(fieldDef, entityName, fieldName, entityId, actionF
|
|
|
287
291
|
...deriveImageHints(fieldDef),
|
|
288
292
|
};
|
|
289
293
|
}
|
|
290
|
-
function deriveEmbeddedCellSelectHints(subField, translate, featureName, entityName, fieldName, subFieldName) {
|
|
291
|
-
if (subField.type !== "select")
|
|
292
|
-
return {};
|
|
293
|
-
const options = subField.options ?? [];
|
|
294
|
-
return {
|
|
295
|
-
options,
|
|
296
|
-
optionLabels: buildOptionLabels(translate, (value) => embeddedCellOptionLabelKey(featureName, entityName, fieldName, subFieldName, value), options),
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
function deriveEmbeddedCellReferenceHints(subField, featureName) {
|
|
300
|
-
if (subField.type !== "reference")
|
|
301
|
-
return {};
|
|
302
|
-
const target = subField.entity !== undefined ? parseRefTarget(subField.entity, featureName) : undefined;
|
|
303
|
-
return {
|
|
304
|
-
...(target !== undefined && { refEntity: target.entityName, refFeature: target.featureName }),
|
|
305
|
-
...(subField.labelField !== undefined && { refLabelField: subField.labelField }),
|
|
306
|
-
...(subField.optionsQuery !== undefined && { refOptionsQuery: subField.optionsQuery }),
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
function buildEmbeddedListCell(subFieldName, subField, translate, featureName, entityName, fieldName, listSpec) {
|
|
310
|
-
return {
|
|
311
|
-
field: subFieldName,
|
|
312
|
-
label: translate(embeddedCellLabelKey(featureName, entityName, fieldName, subFieldName)),
|
|
313
|
-
type: subField.type,
|
|
314
|
-
required: subField.required === true,
|
|
315
|
-
...deriveEmbeddedCellSelectHints(subField, translate, featureName, entityName, fieldName, subFieldName),
|
|
316
|
-
...deriveEmbeddedCellReferenceHints(subField, featureName),
|
|
317
|
-
...(subField.type === "decimal" && subField.scale !== undefined && { scale: subField.scale }),
|
|
318
|
-
...(subField.type === "select" &&
|
|
319
|
-
subField.display !== undefined && { display: subField.display }),
|
|
320
|
-
...(listSpec?.readOnlyCells?.includes(subFieldName) === true && { readOnly: true }),
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
294
|
// Embedded-LIST field (`multiple: true`) — per-cell metadata for a renderer
|
|
324
295
|
// to draw one row per array item (invoice-positions-style table). A plain
|
|
325
296
|
// (non-list) embedded field emits none of this; the renderer tells the two
|
|
326
297
|
// apart by whether embeddedListCells is set, not by `type` (which stays
|
|
327
298
|
// "embedded" either way).
|
|
328
|
-
function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, fieldName, resolvedCurrency, listSpec) {
|
|
299
|
+
function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, fieldName, resolvedCurrency, listSpec, entityId) {
|
|
329
300
|
const embeddedListDef = fieldDef.type === "embedded" && fieldDef.multiple === true ? fieldDef : undefined;
|
|
301
|
+
const cellContext = { translate, featureName, entityName, fieldName, listSpec };
|
|
330
302
|
const embeddedListCells = embeddedListDef !== undefined
|
|
331
|
-
? Object.entries(embeddedListDef.schema).map(([subFieldName, subField]) => buildEmbeddedListCell(subFieldName, subField,
|
|
303
|
+
? Object.entries(embeddedListDef.schema).map(([subFieldName, subField]) => buildEmbeddedListCell(subFieldName, subField, cellContext))
|
|
332
304
|
: undefined;
|
|
333
305
|
return {
|
|
334
306
|
...(embeddedListCells !== undefined && { embeddedListCells }),
|
|
@@ -349,6 +321,14 @@ function deriveEmbeddedListHints(fieldDef, translate, featureName, entityName, f
|
|
|
349
321
|
...(embeddedListDef !== undefined && { embeddedListCurrency: resolvedCurrency }),
|
|
350
322
|
...(embeddedListDef !== undefined &&
|
|
351
323
|
listSpec?.fixedRows === true && { embeddedListFixedRows: true }),
|
|
324
|
+
...(embeddedListCells?.some((cell) => cell.type === "images" || cell.type === "files") ===
|
|
325
|
+
true && {
|
|
326
|
+
embeddedListUploadTarget: {
|
|
327
|
+
entityType: entityName,
|
|
328
|
+
fieldName,
|
|
329
|
+
...(entityId !== undefined && { entityId }),
|
|
330
|
+
},
|
|
331
|
+
}),
|
|
352
332
|
};
|
|
353
333
|
}
|
|
354
334
|
function buildExtensionSectionViewModel(sectionSpec, translate) {
|
|
@@ -378,6 +358,17 @@ function isLockedByDerivation(derivation, values, field) {
|
|
|
378
358
|
!isFormDerivationInputMode(derivation, values, field);
|
|
379
359
|
return isDerivedAndLocked || isUnusedFallbackOperand(derivation, values, field);
|
|
380
360
|
}
|
|
361
|
+
// A screen-spec condition overrides the entity's own visibleWhen / required / requiredWhen.
|
|
362
|
+
function isEditFieldVisible(normalized, fieldDef, values) {
|
|
363
|
+
return normalized.visible !== undefined
|
|
364
|
+
? evalFieldCondition(normalized.visible, values)
|
|
365
|
+
: isFieldVisible(fieldDef, values);
|
|
366
|
+
}
|
|
367
|
+
function isEditFieldRequired(normalized, fieldDef, values) {
|
|
368
|
+
return normalized.required !== undefined
|
|
369
|
+
? evalFieldCondition(normalized.required, values)
|
|
370
|
+
: isFieldRequired(fieldDef, values);
|
|
371
|
+
}
|
|
381
372
|
export function computeEditViewModel(input) {
|
|
382
373
|
const { screen, entity, values, translate, featureName, mode, entityId, derivation, actionFormScreenQn, } = input;
|
|
383
374
|
const excludedFields = new Set(mode === undefined ? [] : screen.writeExcludedFields?.[mode]);
|
|
@@ -419,7 +410,7 @@ export function computeEditViewModel(input) {
|
|
|
419
410
|
const effectiveType = declaredRefTarget !== undefined ? "reference" : fieldDef.type;
|
|
420
411
|
const label = translate(screen.fieldLabels?.[normalized.field] ??
|
|
421
412
|
fieldLabelKey(featureName, screen.entity, normalized.field));
|
|
422
|
-
const visible =
|
|
413
|
+
const visible = isEditFieldVisible(normalized, fieldDef, values);
|
|
423
414
|
// `readOnly` (camelCase) is the name on both sides: EditFieldSpec
|
|
424
415
|
// in the engine, and the view-model emitted here. One convention
|
|
425
416
|
// through the stack beats translating at the boundary.
|
|
@@ -431,8 +422,7 @@ export function computeEditViewModel(input) {
|
|
|
431
422
|
// field that's required at the entity-level but marked required:
|
|
432
423
|
// false on the screen (e.g. a soft-onboarding wizard that
|
|
433
424
|
// collects less up-front) respects the screen override.
|
|
434
|
-
const
|
|
435
|
-
const required = !lockedByDerivation && evalCondition(normalized.required, entityRequired, values);
|
|
425
|
+
const required = !lockedByDerivation && isEditFieldRequired(normalized, fieldDef, values);
|
|
436
426
|
// ponytail: "EUR" mirrors DEFAULT_CURRENCIES[0] from
|
|
437
427
|
// framework/src/engine/field-helpers.ts — headless has no dependency
|
|
438
428
|
// on that module, so the literal is duplicated here instead of
|
|
@@ -444,7 +434,7 @@ export function computeEditViewModel(input) {
|
|
|
444
434
|
const numericHints = deriveNumericFieldHints(fieldDef, resolvedCurrency);
|
|
445
435
|
const referenceHints = deriveReferenceFieldHints(fieldDef, normalized, declaredRefTarget, featureName);
|
|
446
436
|
const fileHints = deriveFileFieldHints(fieldDef, screen.entity, normalized.field, entityId, actionFormScreenQn);
|
|
447
|
-
const embeddedListHints = deriveEmbeddedListHints(fieldDef, translate, featureName, screen.entity, normalized.field, resolvedCurrency, normalized.embeddedList);
|
|
437
|
+
const embeddedListHints = deriveEmbeddedListHints(fieldDef, translate, featureName, screen.entity, normalized.field, resolvedCurrency, normalized.embeddedList, entityId);
|
|
448
438
|
const view = {
|
|
449
439
|
field: normalized.field,
|
|
450
440
|
label,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EmbeddedListEditSpec } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { EmbeddedSubFieldDef } from "@cosmicdrift/kumiko-types/fields";
|
|
3
|
+
import type { EmbeddedListCellViewModel, Translate } from "./types.ts";
|
|
4
|
+
type CellContext = {
|
|
5
|
+
readonly translate: Translate;
|
|
6
|
+
readonly featureName: string;
|
|
7
|
+
readonly entityName: string;
|
|
8
|
+
readonly fieldName: string;
|
|
9
|
+
readonly listSpec: EmbeddedListEditSpec | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare function buildEmbeddedListCell(subFieldName: string, subField: EmbeddedSubFieldDef, ctx: CellContext): EmbeddedListCellViewModel;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { parseRefTarget } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { buildOptionLabels, embeddedCellLabelKey, embeddedCellOptionLabelKey } from "./list.js";
|
|
3
|
+
function selectHints(subField, subFieldName, ctx) {
|
|
4
|
+
if (subField.type !== "select")
|
|
5
|
+
return {};
|
|
6
|
+
return {
|
|
7
|
+
options: subField.options,
|
|
8
|
+
optionLabels: buildOptionLabels(ctx.translate, (value) => embeddedCellOptionLabelKey(ctx.featureName, ctx.entityName, ctx.fieldName, subFieldName, value), subField.options),
|
|
9
|
+
...(subField.display !== undefined && { display: subField.display }),
|
|
10
|
+
...(subField.optionTones !== undefined && { optionTones: subField.optionTones }),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function referenceHints(subField, featureName) {
|
|
14
|
+
if (subField.type !== "reference")
|
|
15
|
+
return {};
|
|
16
|
+
const target = parseRefTarget(subField.entity, featureName);
|
|
17
|
+
return {
|
|
18
|
+
refEntity: target.entityName,
|
|
19
|
+
refFeature: target.featureName,
|
|
20
|
+
...(subField.labelField !== undefined && { refLabelField: subField.labelField }),
|
|
21
|
+
...(subField.optionsQuery !== undefined && { refOptionsQuery: subField.optionsQuery }),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function fileHints(subField) {
|
|
25
|
+
if (subField.type !== "images" && subField.type !== "files")
|
|
26
|
+
return {};
|
|
27
|
+
return {
|
|
28
|
+
...(subField.maxCount !== undefined && { maxCount: subField.maxCount }),
|
|
29
|
+
...(subField.accept !== undefined && { accept: subField.accept }),
|
|
30
|
+
...(subField.type === "images" &&
|
|
31
|
+
subField.capture !== undefined && { capture: subField.capture }),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function buildEmbeddedListCell(subFieldName, subField, ctx) {
|
|
35
|
+
return {
|
|
36
|
+
field: subFieldName,
|
|
37
|
+
label: ctx.translate(embeddedCellLabelKey(ctx.featureName, ctx.entityName, ctx.fieldName, subFieldName)),
|
|
38
|
+
type: subField.type,
|
|
39
|
+
required: subField.required === true,
|
|
40
|
+
...(subField.visibleWhen !== undefined && { visibleWhen: subField.visibleWhen }),
|
|
41
|
+
...(subField.requiredWhen !== undefined && { requiredWhen: subField.requiredWhen }),
|
|
42
|
+
...selectHints(subField, subFieldName, ctx),
|
|
43
|
+
...referenceHints(subField, ctx.featureName),
|
|
44
|
+
...(subField.type === "decimal" && subField.scale !== undefined && { scale: subField.scale }),
|
|
45
|
+
...fileHints(subField),
|
|
46
|
+
...(ctx.listSpec?.readOnlyCells?.includes(subFieldName) === true && { readOnly: true }),
|
|
47
|
+
...(ctx.listSpec?.cardHiddenCells?.includes(subFieldName) === true && { hiddenInCard: true }),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { EmbeddedDerivedCellDef } from "@cosmicdrift/kumiko-framework/ui-ty
|
|
|
2
2
|
import type { FieldIssue } from "../dispatcher/index.ts";
|
|
3
3
|
export type EmbeddedDerivedOp = EmbeddedDerivedCellDef["op"];
|
|
4
4
|
export type { DerivedCellRoundingTarget } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
5
|
-
export { computeDerivedCellValue, roundDerivedCellValue, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
5
|
+
export { computeDerivedCellValue, roundDerivedCellValue, withDerivedCells, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
6
6
|
/** Sums a numeric/money/decimal column across all rows. Non-numeric or
|
|
7
7
|
* missing values count as 0. Money columns are minor-unit integers —
|
|
8
8
|
* the sum stays in minor units, never rounds through a major-unit float. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Canonical implementation moved to packages/framework/src/engine/embedded-derived.ts
|
|
2
2
|
// (the write-schema preprocess needs it server-side too).
|
|
3
|
-
export { computeDerivedCellValue, roundDerivedCellValue, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
|
+
export { computeDerivedCellValue, roundDerivedCellValue, withDerivedCells, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
4
4
|
/** Sums a numeric/money/decimal column across all rows. Non-numeric or
|
|
5
5
|
* missing values count as 0. Money columns are minor-unit integers —
|
|
6
6
|
* the sum stays in minor units, never rounds through a major-unit float. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { ComputeEditViewModelInput, EditFormMode } from "./edit.ts";
|
|
2
2
|
export { computeEditViewModel, computeRelatedListSectionViewModel } from "./edit.ts";
|
|
3
3
|
export type { DerivedCellRoundingTarget, EmbeddedDerivedOp, EmbeddedListIssueGroups, } from "./embedded-list.ts";
|
|
4
|
-
export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./embedded-list.ts";
|
|
4
|
+
export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, withDerivedCells, } from "./embedded-list.ts";
|
|
5
5
|
export type { ComputeListViewModelInput } from "./list.ts";
|
|
6
6
|
export { buildOptionLabels, computeListViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, } from "./list.ts";
|
|
7
7
|
export type { CellLink, CellLinkItem, EditExtensionSectionViewModel, EditFieldSpec, EditFieldsSectionViewModel, EditFieldViewModel, EditRelatedListSectionViewModel, EditSectionSpec, EditSectionViewModel, EditViewModel, EditWriteFormSectionViewModel, EmbeddedListCellViewModel, FieldConditionCtx, FieldRenderer, ListColumnFileDisplay, ListColumnSpec, ListColumnViewModel, ListRowViewModel, ListViewModel, RuntimeCellLink, RuntimeCellLinkItems, RuntimeRenderer, ScreenSlots, Translate, } from "./types.ts";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { computeEditViewModel, computeRelatedListSectionViewModel } from "./edit.js";
|
|
2
|
-
export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, } from "./embedded-list.js";
|
|
2
|
+
export { computeDerivedCellValue, groupEmbeddedListIssues, roundDerivedCellValue, sumEmbeddedListColumn, withDerivedCells, } from "./embedded-list.js";
|
|
3
3
|
export { buildOptionLabels, computeListViewModel, embeddedCellLabelKey, embeddedCellOptionLabelKey, fieldLabelKey, fieldOptionLabelKey, fieldOptionLabelKeyPrefix, } from "./list.js";
|
|
@@ -136,9 +136,8 @@ function derivedFieldColumn(input, column) {
|
|
|
136
136
|
field: column.field,
|
|
137
137
|
label: translatedColumnLabel(input, column),
|
|
138
138
|
type: derivedDef.valueType,
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
sortable: false,
|
|
139
|
+
// The client schema carries only `sortable` (never `sql`), so test the flag, not the SQL shape.
|
|
140
|
+
sortable: "sortable" in derivedDef && derivedDef.sortable === true,
|
|
142
141
|
...(column.renderer !== undefined && { renderer: column.renderer }),
|
|
143
142
|
...(column.hideOnNarrow === true && { hideOnNarrow: true }),
|
|
144
143
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EditFieldSpec, EditSectionSpec, FieldAffix, FieldFillAction, FieldIconKey, FieldRenderer, IconKey, ListBulkAction, ListColumnSpec, ListFacetSpec, ListSortSpec, ListTableOptions, OptionsQueryPayload, PlatformComponent, RelatedListEmptyStateVariant, RelatedListRowTone, RelatedListToolbarAction, RowAction, RowActionMode, ScreenSlots, SelectOptionTone } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
1
|
+
import type { EditFieldSpec, EditSectionSpec, EmbeddedDerivedCellDef, FieldAffix, FieldCondition, FieldFillAction, FieldIconKey, FieldRenderer, IconKey, ListBulkAction, ListColumnSpec, ListFacetSpec, ListSortSpec, ListTableOptions, OptionsQueryPayload, PlatformComponent, RelatedListEmptyStateVariant, 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
|
/** Where a cell's text links to. `href` comes from NavApi.hrefFor (new-tab and
|
|
4
4
|
* copy-link work); `onPress` is the in-app navigation for a plain click. */
|
|
@@ -79,15 +79,26 @@ export type ListViewModel = {
|
|
|
79
79
|
export type EmbeddedListCellViewModel = {
|
|
80
80
|
readonly field: string;
|
|
81
81
|
readonly label: string;
|
|
82
|
-
readonly type: "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp";
|
|
82
|
+
readonly type: "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp" | "images" | "files";
|
|
83
83
|
readonly required: boolean;
|
|
84
|
+
/** Per-row state: hidden / required for a row where the condition holds. */
|
|
85
|
+
readonly visibleWhen?: Exclude<FieldCondition, boolean>;
|
|
86
|
+
readonly requiredWhen?: Exclude<FieldCondition, boolean>;
|
|
84
87
|
/** Only for `type: "select"`. */
|
|
85
88
|
readonly options?: readonly string[];
|
|
89
|
+
/** Only for `type: "select"`; tone of each option's badge or selected segment. */
|
|
90
|
+
readonly optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>;
|
|
91
|
+
/** Only for `type: "images" | "files"`. */
|
|
92
|
+
readonly capture?: "user" | "environment";
|
|
93
|
+
readonly maxCount?: number;
|
|
94
|
+
readonly accept?: readonly string[];
|
|
86
95
|
readonly optionLabels?: Readonly<Record<string, string>>;
|
|
87
96
|
/** Only for `type: "select"`; `"radio"` renders segmented buttons instead of a dropdown. */
|
|
88
97
|
readonly display?: "radio" | "dropdown";
|
|
89
98
|
/** Cell is shown but not editable (`EditFieldSpec.embeddedList.readOnlyCells`). */
|
|
90
99
|
readonly readOnly?: boolean;
|
|
100
|
+
/** Not shown on phone cards (`EditFieldSpec.embeddedList.cardHiddenCells`). */
|
|
101
|
+
readonly hiddenInCard?: boolean;
|
|
91
102
|
/** Only for `type: "reference"`. */
|
|
92
103
|
readonly refEntity?: string;
|
|
93
104
|
readonly refFeature?: string;
|
|
@@ -140,6 +151,8 @@ export type EditFieldViewModel = {
|
|
|
140
151
|
* `type: "select"` when SelectFieldDef.display is "radio" or "dropdown" —
|
|
141
152
|
* the renderer then skips its own radio-vs-dropdown heuristic. */
|
|
142
153
|
readonly display?: "checkboxes" | "dropdown" | "radio";
|
|
154
|
+
/** `type: "select"` only: tone per option, shown on the selected radio segment. */
|
|
155
|
+
readonly optionTones?: Readonly<Partial<Record<string, SelectOptionTone>>>;
|
|
143
156
|
/** Set for `type: "multiSelect"` when MultiSelectFieldDef.columns is
|
|
144
157
|
* defined — column count for the checkbox grid at the widest breakpoint.
|
|
145
158
|
* Ignored unless `display` is "checkboxes". */
|
|
@@ -249,10 +262,7 @@ export type EditFieldViewModel = {
|
|
|
249
262
|
readonly embeddedListMaxItems?: number;
|
|
250
263
|
/** Sub-field name → how to compute its cell from other sub-fields of the
|
|
251
264
|
* same row. From EmbeddedFieldDef.derived. */
|
|
252
|
-
readonly embeddedListDerived?: Readonly<Record<string,
|
|
253
|
-
readonly op: "multiply" | "sum" | "subtract" | "percent";
|
|
254
|
-
readonly from: readonly string[];
|
|
255
|
-
}>>;
|
|
265
|
+
readonly embeddedListDerived?: Readonly<Record<string, EmbeddedDerivedCellDef>>;
|
|
256
266
|
/** Footer totals: a sub-field name, or a labelled sum of several. From EmbeddedFieldDef.totals. */
|
|
257
267
|
readonly embeddedListTotals?: readonly (string | {
|
|
258
268
|
readonly label: string;
|
|
@@ -261,6 +271,13 @@ export type EditFieldViewModel = {
|
|
|
261
271
|
/** No add, remove, duplicate or move; cells stay editable. From
|
|
262
272
|
* EditFieldSpec.embeddedList.fixedRows. */
|
|
263
273
|
readonly embeddedListFixedRows?: boolean;
|
|
274
|
+
/** Where photos of a row's file cells are uploaded: the entity and the top-level list field;
|
|
275
|
+
* the cell name travels as `subField`. Set when the list has a file cell. */
|
|
276
|
+
readonly embeddedListUploadTarget?: {
|
|
277
|
+
readonly entityType: string;
|
|
278
|
+
readonly fieldName: string;
|
|
279
|
+
readonly entityId?: string;
|
|
280
|
+
};
|
|
264
281
|
/** Currency for the whole embedded list's money cells and totals row —
|
|
265
282
|
* currency lives on the head aggregate (EntityDefinition.defaultCurrency),
|
|
266
283
|
* not per row. Falls back to "EUR" when the entity has no defaultCurrency. */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// `parentFilter` sends the parent id as a server-side `filter` clause: the generic
|
|
2
|
+
// `<entity>:list` query understands `filter`, not an arbitrary `parentParam` key.
|
|
3
|
+
export function buildRelatedListParentPayload(section, parentId) {
|
|
4
|
+
return section.parentFilter !== undefined
|
|
5
|
+
? { filter: { field: section.parentFilter.field, op: "eq", value: parentId } }
|
|
6
|
+
: { [section.parentParam ?? "id"]: parentId };
|
|
7
|
+
}
|
package/dist/i18n-defaults.js
CHANGED
|
@@ -50,6 +50,9 @@ export const kumikoDefaultTranslations = {
|
|
|
50
50
|
"kumiko.field.writeOnly.willRemove": "Removed on save",
|
|
51
51
|
"kumiko.field.embedded-list.actions": "Actions",
|
|
52
52
|
"kumiko.field.embedded-list.cell-label": "{column}, row {row}",
|
|
53
|
+
"kumiko.field.embedded-list.limit-range": "Limit {min} to {max}",
|
|
54
|
+
"kumiko.field.embedded-list.limit-min": "At least {min}",
|
|
55
|
+
"kumiko.field.embedded-list.limit-max": "At most {max}",
|
|
53
56
|
"kumiko.field.file-list.add": "Add file",
|
|
54
57
|
"kumiko.field.file-list.move-up": "Move file up",
|
|
55
58
|
"kumiko.field.file-list.move-down": "Move file down",
|