@cosmicdrift/kumiko-renderer 0.186.2 → 0.187.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-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.187.0",
|
|
4
4
|
"description": "Platform-agnostic React renderer for Kumiko screens. Contains the shared logic — primitives-contract, hooks, KumikoScreen, navigation & SSE abstractions — that any platform-specific renderer (web, native) composes. No DOM, no EventSource, no react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
19
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
18
|
+
"@cosmicdrift/kumiko-framework": "0.187.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.187.0",
|
|
20
20
|
"react": "^19.2.6",
|
|
21
21
|
"temporal-polyfill": "^0.3.2"
|
|
22
22
|
},
|
|
@@ -24,6 +24,10 @@ export type ColumnRendererProps = {
|
|
|
24
24
|
readonly column: {
|
|
25
25
|
readonly field: string;
|
|
26
26
|
};
|
|
27
|
+
/** Set only when the caller (DataTable's `onCellChange`) wired an
|
|
28
|
+
* editable path for this cell — undefined means read-only. A renderer
|
|
29
|
+
* can branch its own markup (input vs. plain text) on presence alone. */
|
|
30
|
+
readonly onChange?: (value: unknown) => void;
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
export type ColumnRendererComponent = ComponentType<ColumnRendererProps>;
|
|
@@ -151,6 +151,21 @@ describe("EmbeddedListField — cell change recomputes derived", () => {
|
|
|
151
151
|
{ product: "p1", unit: "pcs", quantity: 4, unitPrice: 500, amount: 2000 },
|
|
152
152
|
]);
|
|
153
153
|
});
|
|
154
|
+
|
|
155
|
+
test("a fractional product on the money-typed amount cell shows the rounded live preview, not the raw fraction", () => {
|
|
156
|
+
const rows = [{ product: "p1", unit: "pcs", quantity: 2, unitPrice: 923, amount: 1846 }];
|
|
157
|
+
let lastValue: unknown;
|
|
158
|
+
renderEmbeddedListField(invoiceLinesField({ value: rows }), (v) => {
|
|
159
|
+
lastValue = v;
|
|
160
|
+
});
|
|
161
|
+
// 2.5 * 923 = 2307.5 — a fractional minor-unit amount the money target
|
|
162
|
+
// can't store as-is. The live preview must show the rounded value
|
|
163
|
+
// (2308), matching what the server would compute and persist.
|
|
164
|
+
captured?.onCellChange(0, "quantity", 2.5);
|
|
165
|
+
expect(lastValue).toEqual([
|
|
166
|
+
{ product: "p1", unit: "pcs", quantity: 2.5, unitPrice: 923, amount: 2308 },
|
|
167
|
+
]);
|
|
168
|
+
});
|
|
154
169
|
});
|
|
155
170
|
|
|
156
171
|
describe("EmbeddedListField — row operations", () => {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
EditFieldViewModel,
|
|
3
|
+
EmbeddedListCellViewModel,
|
|
4
|
+
FieldIssue,
|
|
5
|
+
} from "@cosmicdrift/kumiko-headless";
|
|
2
6
|
import {
|
|
3
7
|
computeDerivedCellValue,
|
|
4
8
|
groupEmbeddedListIssues,
|
|
9
|
+
roundDerivedCellValue,
|
|
5
10
|
sumEmbeddedListColumn,
|
|
6
11
|
} from "@cosmicdrift/kumiko-headless";
|
|
7
12
|
import type { ReactNode } from "react";
|
|
@@ -25,6 +30,7 @@ type EmbeddedRow = Readonly<Record<string, unknown>>;
|
|
|
25
30
|
function withRecomputedDerived(
|
|
26
31
|
row: EmbeddedRow,
|
|
27
32
|
derived: EditFieldViewModel["embeddedListDerived"],
|
|
33
|
+
cells: readonly EmbeddedListCellViewModel[],
|
|
28
34
|
): EmbeddedRow {
|
|
29
35
|
if (derived === undefined) return row;
|
|
30
36
|
const result: Record<string, unknown> = { ...row };
|
|
@@ -33,7 +39,12 @@ function withRecomputedDerived(
|
|
|
33
39
|
const v = result[src];
|
|
34
40
|
return typeof v === "number" ? v : undefined;
|
|
35
41
|
});
|
|
36
|
-
|
|
42
|
+
const computed = computeDerivedCellValue(def.op, values);
|
|
43
|
+
const target = cells.find((c) => c.field === derivedField);
|
|
44
|
+
result[derivedField] =
|
|
45
|
+
computed !== undefined && target !== undefined
|
|
46
|
+
? roundDerivedCellValue(computed, target)
|
|
47
|
+
: computed;
|
|
37
48
|
}
|
|
38
49
|
return result;
|
|
39
50
|
}
|
|
@@ -154,11 +165,13 @@ export function EmbeddedListField({
|
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
function handleCellChange(rowIndex: number, cellField: string, value: unknown): void {
|
|
157
|
-
replaceRow(rowIndex, (row) =>
|
|
168
|
+
replaceRow(rowIndex, (row) =>
|
|
169
|
+
withRecomputedDerived({ ...row, [cellField]: value }, derived, cells),
|
|
170
|
+
);
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
function handleAddRow(): void {
|
|
161
|
-
onChange([...rows, withRecomputedDerived({}, derived)]);
|
|
174
|
+
onChange([...rows, withRecomputedDerived({}, derived, cells)]);
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
function handleRemoveRow(rowIndex: number): void {
|
|
@@ -210,7 +223,7 @@ export function EmbeddedListField({
|
|
|
210
223
|
});
|
|
211
224
|
|
|
212
225
|
const recomputed = nextRows.map((row, i) =>
|
|
213
|
-
touchedIndices.has(i) ? withRecomputedDerived(row, derived) : row,
|
|
226
|
+
touchedIndices.has(i) ? withRecomputedDerived(row, derived, cells) : row,
|
|
214
227
|
);
|
|
215
228
|
onChange(recomputed);
|
|
216
229
|
}
|
package/src/i18n-defaults.ts
CHANGED
|
@@ -31,6 +31,9 @@ export const kumikoDefaultTranslations: TranslationsByLocale = {
|
|
|
31
31
|
|
|
32
32
|
// Field — aria-Labels der Date/Timestamp-Primitives.
|
|
33
33
|
"kumiko.field.open-calendar": "Kalender öffnen",
|
|
34
|
+
"kumiko.field.dateField.placeholderYear": "J",
|
|
35
|
+
"kumiko.field.dateField.placeholderMonth": "M",
|
|
36
|
+
"kumiko.field.dateField.placeholderDay": "T",
|
|
34
37
|
"kumiko.field.time": "Uhrzeit",
|
|
35
38
|
"kumiko.field.timezone": "Zeitzone",
|
|
36
39
|
"kumiko.field.locatedTzHint": "Zeit lokal am angegebenen Ort",
|
|
@@ -195,6 +198,9 @@ export const kumikoDefaultTranslations: TranslationsByLocale = {
|
|
|
195
198
|
"kumiko.toast.learn-more": "Learn more",
|
|
196
199
|
|
|
197
200
|
"kumiko.field.open-calendar": "Open calendar",
|
|
201
|
+
"kumiko.field.dateField.placeholderYear": "Y",
|
|
202
|
+
"kumiko.field.dateField.placeholderMonth": "M",
|
|
203
|
+
"kumiko.field.dateField.placeholderDay": "D",
|
|
198
204
|
"kumiko.field.time": "Time",
|
|
199
205
|
"kumiko.field.timezone": "Time zone",
|
|
200
206
|
"kumiko.field.locatedTzHint": "Time local to the given location",
|
package/src/primitives.tsx
CHANGED
|
@@ -548,6 +548,18 @@ export type DataTableProps = {
|
|
|
548
548
|
* "Ende der Liste"-Hinweis statt des Sentinels. Default true. */
|
|
549
549
|
readonly hasMore?: boolean;
|
|
550
550
|
readonly testId?: string;
|
|
551
|
+
/** Wires an edit path into a component column-renderer: `DataTableCell`
|
|
552
|
+
* passes this down as `ColumnRendererProps.onChange` for that cell,
|
|
553
|
+
* bound to `(rowId, column.field)`. Renderers without a component
|
|
554
|
+
* renderer (format-spec, type-default) ignore it — read-only stays
|
|
555
|
+
* read-only unless the column has a custom renderer that reads
|
|
556
|
+
* `onChange`. */
|
|
557
|
+
readonly onCellChange?: (rowId: string, field: string, value: unknown) => void;
|
|
558
|
+
/** Overrides the row `data-testid` (default `row-${row.id}`) — for a
|
|
559
|
+
* screen that already has its own DOM-test/e2e naming scheme. */
|
|
560
|
+
readonly getRowTestId?: (row: ListRowViewModel) => string;
|
|
561
|
+
/** Overrides the cell `data-testid` (default `cell-${row.id}-${field}`). */
|
|
562
|
+
readonly getCellTestId?: (row: ListRowViewModel, field: string) => string;
|
|
551
563
|
};
|
|
552
564
|
|
|
553
565
|
// ---- EmbeddedListInput (createEmbeddedListField widget) ----
|