@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.53.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, ConfigValueSource } from "@cosmicdrift/kumiko-framework/engine";
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, ConfigSourceBadge, ConfigCascadeView } = usePrimitives();
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": {
@@ -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 die Datum-Formatierung im Trigger. Default = Browser-
180
- * Locale via navigator.language. Apps mit eigenem LocaleResolver
181
- * können ihren current locale durchreichen. */
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 nutzt `<input type="datetime-local">` und konvertiert. */
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;