@cosmicdrift/kumiko-renderer 0.368.0 → 0.369.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/kumiko-screen.js +52 -4
- package/package.json +5 -5
|
@@ -2655,6 +2655,13 @@ function buildConfigEditDefaults(fields) {
|
|
|
2655
2655
|
}
|
|
2656
2656
|
return defaults;
|
|
2657
2657
|
}
|
|
2658
|
+
// A field hidden by its `visible` condition (a provider's settings while another
|
|
2659
|
+
// provider is picked) is not saved: the form saves what it shows.
|
|
2660
|
+
function hiddenLayoutFieldNames(screen, values) {
|
|
2661
|
+
return new Set(layoutEditFields(screen)
|
|
2662
|
+
.filter((spec) => spec.visible !== undefined && !evalFieldCondition(spec.visible, values))
|
|
2663
|
+
.map((spec) => spec.field));
|
|
2664
|
+
}
|
|
2658
2665
|
function isEmptyNumericFormValue(value) {
|
|
2659
2666
|
if (value === undefined || value === null || value === "")
|
|
2660
2667
|
return true;
|
|
@@ -2670,6 +2677,8 @@ function ConfigEditBody({ schema, screen, translate, }) {
|
|
|
2670
2677
|
// unsere Form-Field-Werte.
|
|
2671
2678
|
const valuesQuery = useQuery("config:query:values", {});
|
|
2672
2679
|
const cascadeQuery = useQuery("config:query:cascade", {});
|
|
2680
|
+
const secretKeys = screen.secretKeys;
|
|
2681
|
+
const secretsQuery = useQuery("secrets:query:list", {}, { enabled: secretKeys !== undefined });
|
|
2673
2682
|
const synthEntity = useMemo(() => synthesizeConfigEditEntity(screen.fields), [screen.fields]);
|
|
2674
2683
|
const synthScreen = useMemo(() => synthesizeConfigEditScreen(screen), [screen]);
|
|
2675
2684
|
const boundsSchema = useMemo(() => buildConfigEditSchema(screen.fields), [screen.fields]);
|
|
@@ -2681,9 +2690,17 @@ function ConfigEditBody({ schema, screen, translate, }) {
|
|
|
2681
2690
|
const initial = useMemo(() => {
|
|
2682
2691
|
if (valuesQuery.data === null)
|
|
2683
2692
|
return null;
|
|
2693
|
+
if (secretKeys !== undefined && secretsQuery.data === null)
|
|
2694
|
+
return null;
|
|
2695
|
+
const storedSecretKeys = new Set((secretsQuery.data ?? []).map((row) => row.key));
|
|
2684
2696
|
const out = {};
|
|
2685
2697
|
const defaults = buildConfigEditDefaults(screen.fields);
|
|
2686
2698
|
for (const [shortName, fieldDef] of Object.entries(screen.fields)) {
|
|
2699
|
+
const secretKey = secretKeys?.[shortName];
|
|
2700
|
+
if (secretKey !== undefined) {
|
|
2701
|
+
out[shortName] = storedSecretKeys.has(secretKey) ? true : null;
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2687
2704
|
const qualified = screen.configKeys[shortName];
|
|
2688
2705
|
if (qualified === undefined) {
|
|
2689
2706
|
// Author hat ein Field deklariert ohne Mapping — nimm Default.
|
|
@@ -2719,7 +2736,14 @@ function ConfigEditBody({ schema, screen, translate, }) {
|
|
|
2719
2736
|
}
|
|
2720
2737
|
}
|
|
2721
2738
|
return out;
|
|
2722
|
-
}, [
|
|
2739
|
+
}, [
|
|
2740
|
+
valuesQuery.data,
|
|
2741
|
+
secretsQuery.data,
|
|
2742
|
+
secretKeys,
|
|
2743
|
+
screen.fields,
|
|
2744
|
+
screen.configKeys,
|
|
2745
|
+
screen.scope,
|
|
2746
|
+
]);
|
|
2723
2747
|
// Cascade-Lookup: qualifiedKey → ConfigCascade für die
|
|
2724
2748
|
// Cascade-Anzeige unter jedem Feld. Defensive `levels`-Shape-Check
|
|
2725
2749
|
// damit fremde Query-Mocks (z.B. configEdit-Unit-Tests die nur
|
|
@@ -2749,7 +2773,23 @@ function ConfigEditBody({ schema, screen, translate, }) {
|
|
|
2749
2773
|
const customSubmit = useCallback(async (snapshot) => {
|
|
2750
2774
|
const commands = [];
|
|
2751
2775
|
const commandFieldNames = [];
|
|
2776
|
+
const hiddenFieldNames = hiddenLayoutFieldNames(synthScreen, snapshot.values);
|
|
2752
2777
|
for (const [shortName, value] of Object.entries(snapshot.changes)) {
|
|
2778
|
+
if (hiddenFieldNames.has(shortName))
|
|
2779
|
+
continue;
|
|
2780
|
+
const secretKey = secretKeys?.[shortName];
|
|
2781
|
+
if (secretKey !== undefined) {
|
|
2782
|
+
// Same wire shape as a writeOnly config field: null removes, a string replaces.
|
|
2783
|
+
if (value === null) {
|
|
2784
|
+
commandFieldNames.push(shortName);
|
|
2785
|
+
commands.push({ type: "secrets:write:delete", payload: { key: secretKey } });
|
|
2786
|
+
}
|
|
2787
|
+
else if (typeof value === "string" && value.trim() !== "") {
|
|
2788
|
+
commandFieldNames.push(shortName);
|
|
2789
|
+
commands.push({ type: "secrets:write:set", payload: { key: secretKey, value } });
|
|
2790
|
+
}
|
|
2791
|
+
continue;
|
|
2792
|
+
}
|
|
2753
2793
|
const qualified = screen.configKeys[shortName];
|
|
2754
2794
|
if (qualified === undefined)
|
|
2755
2795
|
continue;
|
|
@@ -2821,20 +2861,28 @@ function ConfigEditBody({ schema, screen, translate, }) {
|
|
|
2821
2861
|
// bleibt die Anzeige stale bis Reload — gleiche refetch-Calls wie onReset.
|
|
2822
2862
|
// allSettled: der Write ist schon committed, ein gescheitertes Refetch
|
|
2823
2863
|
// (stale Anzeige) darf das Erfolgsergebnis nicht in einen Fehler kippen.
|
|
2824
|
-
await Promise.allSettled([
|
|
2864
|
+
await Promise.allSettled([
|
|
2865
|
+
valuesQuery.refetch?.(),
|
|
2866
|
+
cascadeQuery.refetch?.(),
|
|
2867
|
+
...(secretKeys !== undefined ? [secretsQuery.refetch?.()] : []),
|
|
2868
|
+
]);
|
|
2825
2869
|
return { validationBlocked: false, isSuccess: true, data: undefined };
|
|
2826
2870
|
}, [
|
|
2827
2871
|
dispatcher,
|
|
2872
|
+
synthScreen,
|
|
2873
|
+
secretKeys,
|
|
2828
2874
|
screen.configKeys,
|
|
2829
2875
|
screen.fields,
|
|
2830
2876
|
screen.scope,
|
|
2831
2877
|
valuesQuery.refetch,
|
|
2832
2878
|
cascadeQuery.refetch,
|
|
2879
|
+
secretsQuery.refetch,
|
|
2833
2880
|
]);
|
|
2834
2881
|
if (valuesQuery.loading && valuesQuery.data === null)
|
|
2835
2882
|
return _jsx(QueryLoadingBanner, {});
|
|
2836
|
-
|
|
2837
|
-
|
|
2883
|
+
const failedQuery = valuesQuery.error ? valuesQuery : secretsQuery.error ? secretsQuery : null;
|
|
2884
|
+
if (failedQuery?.error) {
|
|
2885
|
+
return (_jsx(QueryErrorBanner, { error: failedQuery.error, translate: effectiveTranslate, onRetry: failedQuery.refetch }));
|
|
2838
2886
|
}
|
|
2839
2887
|
if (initial === null)
|
|
2840
2888
|
return _jsx(QueryLoadingBanner, {});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.369.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,9 +15,9 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
19
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
20
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
18
|
+
"@cosmicdrift/kumiko-framework": "0.369.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.369.0",
|
|
20
|
+
"@cosmicdrift/kumiko-types": "0.369.0",
|
|
21
21
|
"react": "^19.2.6",
|
|
22
22
|
"temporal-polyfill": "^0.3.2",
|
|
23
23
|
"zod": "^4.4.3"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@types/react-dom": "^19.2.3",
|
|
29
29
|
"jsdom": "^29.1.1",
|
|
30
30
|
"react-dom": "^19.2.6",
|
|
31
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
31
|
+
"@cosmicdrift/kumiko-locale-de": "0.369.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|