@cosmicdrift/kumiko-renderer 0.53.0 → 0.55.1
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.55.1",
|
|
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>",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConfigCascade
|
|
1
|
+
import type { ConfigCascade } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import type {
|
|
3
3
|
ActionFormScreenDefinition,
|
|
4
4
|
ConfigEditScreenDefinition,
|
|
@@ -1032,7 +1032,7 @@ function ConfigEditBody({
|
|
|
1032
1032
|
readonly screen: ConfigEditScreenDefinition;
|
|
1033
1033
|
readonly translate?: Translate;
|
|
1034
1034
|
}): ReactNode {
|
|
1035
|
-
const { Banner,
|
|
1035
|
+
const { Banner, ConfigCascadeView } = usePrimitives();
|
|
1036
1036
|
const dispatcher = useDispatcher();
|
|
1037
1037
|
|
|
1038
1038
|
// Detail-Load: config:query:values returnt ALLE Keys des Tenants.
|
|
@@ -1081,19 +1081,6 @@ function ConfigEditBody({
|
|
|
1081
1081
|
return out as FormValues;
|
|
1082
1082
|
}, [valuesQuery.data, screen.fields, screen.configKeys]);
|
|
1083
1083
|
|
|
1084
|
-
// Sources-Lookup: qualifiedKey → ConfigValueSource für das
|
|
1085
|
-
// ConfigSourceBadge. Wird via labelAppendix an RenderEdit
|
|
1086
|
-
// durchgereicht.
|
|
1087
|
-
const sources = useMemo<Record<string, ConfigValueSource>>(() => {
|
|
1088
|
-
if (valuesQuery.data === null) return {};
|
|
1089
|
-
const out: Record<string, ConfigValueSource> = {};
|
|
1090
|
-
for (const [shortName, qualified] of Object.entries(screen.configKeys)) {
|
|
1091
|
-
const source = valuesQuery.data[qualified]?.source as ConfigValueSource | undefined; // @cast-boundary engine-payload
|
|
1092
|
-
if (source !== undefined) out[shortName] = source;
|
|
1093
|
-
}
|
|
1094
|
-
return out;
|
|
1095
|
-
}, [valuesQuery.data, screen.configKeys]);
|
|
1096
|
-
|
|
1097
1084
|
// Cascade-Lookup: qualifiedKey → ConfigCascade für die
|
|
1098
1085
|
// Cascade-Anzeige unter jedem Feld. Defensive `levels`-Shape-Check
|
|
1099
1086
|
// damit fremde Query-Mocks (z.B. configEdit-Unit-Tests die nur
|
|
@@ -1172,12 +1159,6 @@ function ConfigEditBody({
|
|
|
1172
1159
|
customSubmit={customSubmit}
|
|
1173
1160
|
{...(screen.submitLabel !== undefined && { submitLabel: screen.submitLabel })}
|
|
1174
1161
|
{...(translate !== undefined && { translate })}
|
|
1175
|
-
labelAppendix={(fieldName: string) => {
|
|
1176
|
-
const source = sources[fieldName];
|
|
1177
|
-
return source ? (
|
|
1178
|
-
<ConfigSourceBadge source={source} screenScope={screen.scope} />
|
|
1179
|
-
) : undefined;
|
|
1180
|
-
}}
|
|
1181
1162
|
fieldAppendix={(fieldName: string) => {
|
|
1182
1163
|
const cascade = cascades[fieldName];
|
|
1183
1164
|
if (cascade === undefined) return undefined;
|
|
@@ -97,3 +97,42 @@ describe("RenderField — App-Locale an date durchreichen", () => {
|
|
|
97
97
|
if (captured?.kind === "date") expect(captured.locale).toBe("de-DE");
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
|
+
|
|
101
|
+
describe("RenderField — min/max/dateLocale ans Picker-Input durchreichen (#369)", () => {
|
|
102
|
+
test("date-Feld: min/max/dateLocale landen in den InputProps", () => {
|
|
103
|
+
const field: EditFieldViewModel = {
|
|
104
|
+
...dateField(),
|
|
105
|
+
min: "2020-01-01",
|
|
106
|
+
max: "2026-12-31",
|
|
107
|
+
dateLocale: "en-US",
|
|
108
|
+
};
|
|
109
|
+
renderUnderLocale("de-DE", field);
|
|
110
|
+
expect(captured?.kind).toBe("date");
|
|
111
|
+
if (captured?.kind === "date") {
|
|
112
|
+
expect(captured.min).toBe("2020-01-01");
|
|
113
|
+
expect(captured.max).toBe("2026-12-31");
|
|
114
|
+
// dateLocale (per-Feld) hat Vorrang vor dem App-Locale (de-DE).
|
|
115
|
+
expect(captured.locale).toBe("en-US");
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("timestamp-Feld: min/max landen in den InputProps", () => {
|
|
120
|
+
const field: EditFieldViewModel = {
|
|
121
|
+
field: "at",
|
|
122
|
+
label: "Zeitpunkt",
|
|
123
|
+
type: "timestamp",
|
|
124
|
+
value: "",
|
|
125
|
+
visible: true,
|
|
126
|
+
readOnly: false,
|
|
127
|
+
required: false,
|
|
128
|
+
min: "2026-01-01T00:00:00Z",
|
|
129
|
+
max: "2026-12-31T23:59:59Z",
|
|
130
|
+
};
|
|
131
|
+
renderUnderLocale("de-DE", field);
|
|
132
|
+
expect(captured?.kind).toBe("timestamp");
|
|
133
|
+
if (captured?.kind === "timestamp") {
|
|
134
|
+
expect(captured.min).toBe("2026-01-01T00:00:00Z");
|
|
135
|
+
expect(captured.max).toBe("2026-12-31T23:59:59Z");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -252,7 +252,9 @@ function renderInput({
|
|
|
252
252
|
{...common}
|
|
253
253
|
value={stringValue(field.value)}
|
|
254
254
|
onChange={(v) => onChange(v)}
|
|
255
|
-
locale={appLocale}
|
|
255
|
+
locale={field.dateLocale ?? appLocale}
|
|
256
|
+
{...(field.min !== undefined && { min: field.min })}
|
|
257
|
+
{...(field.max !== undefined && { max: field.max })}
|
|
256
258
|
/>
|
|
257
259
|
);
|
|
258
260
|
case "timestamp":
|
|
@@ -262,7 +264,10 @@ function renderInput({
|
|
|
262
264
|
{...common}
|
|
263
265
|
value={stringValue(field.value)}
|
|
264
266
|
onChange={(v) => onChange(v)}
|
|
267
|
+
locale={field.dateLocale ?? appLocale}
|
|
265
268
|
{...(field.wallClock !== undefined && { wallClock: field.wallClock })}
|
|
269
|
+
{...(field.min !== undefined && { min: field.min })}
|
|
270
|
+
{...(field.max !== undefined && { max: field.max })}
|
|
266
271
|
/>
|
|
267
272
|
);
|
|
268
273
|
case "select": {
|
package/src/primitives.tsx
CHANGED
|
@@ -176,10 +176,14 @@ export type InputProps =
|
|
|
176
176
|
readonly name: string;
|
|
177
177
|
readonly value: string;
|
|
178
178
|
readonly onChange: (v: string | undefined) => void;
|
|
179
|
-
/** Locale für
|
|
180
|
-
*
|
|
181
|
-
*
|
|
179
|
+
/** Locale für Anzeige UND Eingabe-Parsing. Default = Browser-Locale
|
|
180
|
+
* via navigator.language. Apps mit eigenem LocaleResolver können
|
|
181
|
+
* ihren current locale durchreichen. */
|
|
182
182
|
readonly locale?: string;
|
|
183
|
+
/** Datumsgrenzen als ISO `yyyy-mm-dd` — begrenzen Picker (Jahres-
|
|
184
|
+
* Dropdown-Range + ausgegraute Tage). */
|
|
185
|
+
readonly min?: string;
|
|
186
|
+
readonly max?: string;
|
|
183
187
|
readonly disabled?: boolean;
|
|
184
188
|
readonly required?: boolean;
|
|
185
189
|
readonly hasError?: boolean;
|
|
@@ -267,13 +271,21 @@ export type InputProps =
|
|
|
267
271
|
/** ISO-8601 Datetime-String. UTC-Instant mit `Z`
|
|
268
272
|
* ("2026-04-25T13:45:00Z") oder Wall-Clock ohne Offset
|
|
269
273
|
* ("2026-04-25T13:45", nur bei wallClock). Empty-State = `""`.
|
|
270
|
-
* Web
|
|
274
|
+
* Web rendert getrennte Datums- (tippbar + Kalender) und
|
|
275
|
+
* Uhrzeit-Eingabe und konvertiert. */
|
|
271
276
|
readonly value: string;
|
|
272
277
|
readonly onChange: (v: string | undefined) => void;
|
|
273
278
|
/** true = locatedTimestamp (Wall-Clock ohne Offset, Server
|
|
274
279
|
* validiert z.iso.datetime({local:true})). false/undefined =
|
|
275
280
|
* UTC-Instant, onChange MUSS mit `Z`-Suffix emittieren. */
|
|
276
281
|
readonly wallClock?: boolean;
|
|
282
|
+
/** Locale für Anzeige UND Eingabe-Parsing des Datums-Teils.
|
|
283
|
+
* Default = Browser-Locale. */
|
|
284
|
+
readonly locale?: string;
|
|
285
|
+
/** Datumsgrenzen als ISO-Datetime — begrenzen den Kalender auf
|
|
286
|
+
* Tages-Granularität. */
|
|
287
|
+
readonly min?: string;
|
|
288
|
+
readonly max?: string;
|
|
277
289
|
readonly disabled?: boolean;
|
|
278
290
|
readonly required?: boolean;
|
|
279
291
|
readonly hasError?: boolean;
|