@cosmicdrift/kumiko-renderer 0.104.0 → 0.105.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 +3 -3
- package/src/app/kumiko-screen.tsx +53 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.105.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.105.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.105.0",
|
|
20
20
|
"react": "^19.2.6"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
EntityEditScreenDefinition,
|
|
7
7
|
EntityListScreenDefinition,
|
|
8
8
|
RowAction,
|
|
9
|
+
RowActionNavigate,
|
|
9
10
|
RowActionWriteHandler,
|
|
10
11
|
RowFieldExtractor,
|
|
11
12
|
ScreenDefinition,
|
|
@@ -770,6 +771,45 @@ function EntityListBody({
|
|
|
770
771
|
// Discriminated Union: writeHandler (default) dispatched einen Server-
|
|
771
772
|
// Handler, navigate ruft nav.navigate() ggf. mit URL-Search-Params aus
|
|
772
773
|
// params(row). nav lebt schon weiter unten in EntityListBody-Scope.
|
|
774
|
+
// Gemeinsame navigate-Ausführung: sowohl das "…"-Aktionsmenü (rowActions) als
|
|
775
|
+
// auch der Row-Body-Klick (rowClick) laufen hierdurch — ein Pfad, damit beide
|
|
776
|
+
// nicht auseinanderdriften.
|
|
777
|
+
const runNavigate = useCallback(
|
|
778
|
+
(action: RowActionNavigate, row: ListRowViewModel) => {
|
|
779
|
+
// Default entityId für entityEdit-Targets: row["id"] wenn kein expliziter
|
|
780
|
+
// entityId-Feldname gesetzt ist. Nur für Targets DERSELBEN Entity — sonst
|
|
781
|
+
// bekäme ein Cross-Entity-Edit-Screen die falsche row.id injiziert.
|
|
782
|
+
const targetIsEntityEdit = schema.screens.some(
|
|
783
|
+
(s) =>
|
|
784
|
+
s.type === "entityEdit" &&
|
|
785
|
+
s.entity === screen.entity &&
|
|
786
|
+
lastSegment(s.id) === action.screen,
|
|
787
|
+
);
|
|
788
|
+
const explicit =
|
|
789
|
+
action.entityId !== undefined ? String(row.values[action.entityId] ?? "") : undefined;
|
|
790
|
+
const fallback = targetIsEntityEdit ? String(row.values["id"] ?? "") : undefined;
|
|
791
|
+
const entityId = explicit ?? fallback;
|
|
792
|
+
nav.navigate({
|
|
793
|
+
screenId: action.screen,
|
|
794
|
+
...(entityId !== undefined && entityId !== "" && { entityId }),
|
|
795
|
+
});
|
|
796
|
+
const params =
|
|
797
|
+
action.params !== undefined ? evalRowExtractor(action.params, row.values) : undefined;
|
|
798
|
+
if (params !== undefined) {
|
|
799
|
+
// setSearchParams nimmt string|null — komplexe Werte zu String (der Reader
|
|
800
|
+
// kennt via URL nur Strings). Known-edge: zielt die Action auf den
|
|
801
|
+
// AKTUELLEN pathname, mergen die Params auf den alten ?-String (für
|
|
802
|
+
// Row-Actions praktisch nicht erreichbar, Pfad differiert).
|
|
803
|
+
const stringified: Record<string, string | null> = {};
|
|
804
|
+
for (const [k, v] of Object.entries(params)) {
|
|
805
|
+
stringified[k] = v === null || v === undefined ? null : String(v);
|
|
806
|
+
}
|
|
807
|
+
nav.setSearchParams(stringified);
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
[nav, schema.screens, screen.entity],
|
|
811
|
+
);
|
|
812
|
+
|
|
773
813
|
const rowActions = useMemo(() => {
|
|
774
814
|
if (screen.rowActions === undefined) return undefined;
|
|
775
815
|
return screen.rowActions
|
|
@@ -777,58 +817,13 @@ function EntityListBody({
|
|
|
777
817
|
// navigate-Variante braucht keinen Dispatcher; nav ist
|
|
778
818
|
// immer da (Provider von createKumikoApp).
|
|
779
819
|
if (action.kind === "navigate") {
|
|
780
|
-
|
|
781
|
-
// kein expliziter entityId-Feldname gesetzt ist. Nur für Targets
|
|
782
|
-
// DERSELBEN Entity — ein Cross-Entity-Edit-Screen bekäme sonst die
|
|
783
|
-
// falsche row.id injiziert, und "Duplicate → Create"-Patterns
|
|
784
|
-
// würden in den Update-Mode gezwungen. Cross-Entity-Navigation
|
|
785
|
-
// setzt action.entityId explizit.
|
|
786
|
-
const targetIsEntityEdit = schema.screens.some(
|
|
787
|
-
(s) =>
|
|
788
|
-
s.type === "entityEdit" &&
|
|
789
|
-
s.entity === screen.entity &&
|
|
790
|
-
lastSegment(s.id) === action.screen,
|
|
791
|
-
);
|
|
820
|
+
const navigateAction = action;
|
|
792
821
|
const actionVisible = action.visible;
|
|
793
822
|
return {
|
|
794
823
|
id: action.id,
|
|
795
824
|
label: effectiveTranslate(action.label),
|
|
796
825
|
...(action.style !== undefined && { style: action.style }),
|
|
797
|
-
onTrigger: (row: ListRowViewModel) =>
|
|
798
|
-
const explicit =
|
|
799
|
-
action.entityId !== undefined
|
|
800
|
-
? String(row.values[action.entityId] ?? "")
|
|
801
|
-
: undefined;
|
|
802
|
-
const fallback = targetIsEntityEdit ? String(row.values["id"] ?? "") : undefined;
|
|
803
|
-
const entityId = explicit ?? fallback;
|
|
804
|
-
nav.navigate({
|
|
805
|
-
screenId: action.screen,
|
|
806
|
-
...(entityId !== undefined && entityId !== "" && { entityId }),
|
|
807
|
-
});
|
|
808
|
-
const params =
|
|
809
|
-
action.params !== undefined
|
|
810
|
-
? evalRowExtractor(action.params, row.values)
|
|
811
|
-
: undefined;
|
|
812
|
-
if (params !== undefined) {
|
|
813
|
-
// setSearchParams nimmt string|null. Komplexe Werte
|
|
814
|
-
// (number/boolean) wandeln wir zu String — der Reader
|
|
815
|
-
// (use-list-url-state / actionForm-init) kennt nur
|
|
816
|
-
// Strings via URL.
|
|
817
|
-
const stringified: Record<string, string | null> = {};
|
|
818
|
-
for (const [k, v] of Object.entries(params)) {
|
|
819
|
-
stringified[k] = v === null || v === undefined ? null : String(v);
|
|
820
|
-
}
|
|
821
|
-
// NACH navigate: pushState trägt keine Query — Params die
|
|
822
|
-
// vor dem Push gesetzt werden, kleben an der ALTEN URL und
|
|
823
|
-
// sind auf dem Ziel-Screen weg (actionForm-Prefill leer).
|
|
824
|
-
// Bekannte Kante (bewusst offen): zielt die Action auf den
|
|
825
|
-
// AKTUELLEN pathname, short-circuit't pushPath ohne die Query
|
|
826
|
-
// zu leeren — die neuen Params mergen dann auf den alten
|
|
827
|
-
// ?-String. Für Row-Actions praktisch nicht erreichbar
|
|
828
|
-
// (Pfad differiert über entityId/screen).
|
|
829
|
-
nav.setSearchParams(stringified);
|
|
830
|
-
}
|
|
831
|
-
},
|
|
826
|
+
onTrigger: (row: ListRowViewModel) => runNavigate(navigateAction, row),
|
|
832
827
|
...(actionVisible !== undefined && {
|
|
833
828
|
isVisible: (row: ListRowViewModel) => evalFieldCondition(actionVisible, row.values),
|
|
834
829
|
}),
|
|
@@ -872,7 +867,7 @@ function EntityListBody({
|
|
|
872
867
|
};
|
|
873
868
|
})
|
|
874
869
|
.filter((a: DataTableRowAction | null): a is DataTableRowAction => a !== null);
|
|
875
|
-
}, [screen.rowActions,
|
|
870
|
+
}, [screen.rowActions, effectiveTranslate, dispatcher, runNavigate]);
|
|
876
871
|
|
|
877
872
|
// ToolbarActions: Schema → Resolved-Form (analog rowActions).
|
|
878
873
|
// navigate-kind → useNav().navigate({ screenId }), writeHandler-kind
|
|
@@ -930,8 +925,16 @@ function EntityListBody({
|
|
|
930
925
|
// 2-arg shape (row, entityName) is the public surface — thread the
|
|
931
926
|
// screen's entity through here so callers don't have to re-derive
|
|
932
927
|
// it.
|
|
933
|
-
|
|
934
|
-
|
|
928
|
+
// Row-Body-Klick: eine explizit als rowClick markierte navigate-rowAction
|
|
929
|
+
// gewinnt (deklarierte Author-Absicht "Klick auf die Zeile tut X"). Ohne Flag
|
|
930
|
+
// bleibt der Pfad unverändert — create-app's entityEdit-Default bzw. ein vom
|
|
931
|
+
// App-Author gesetztes onRowClick.
|
|
932
|
+
const rowClickAction = screen.rowActions?.find(
|
|
933
|
+
(a): a is RowActionNavigate => a.kind === "navigate" && a.rowClick === true,
|
|
934
|
+
);
|
|
935
|
+
const wrappedOnRowClick = rowClickAction
|
|
936
|
+
? (row: ListRowViewModel) => runNavigate(rowClickAction, row)
|
|
937
|
+
: onRowClick !== undefined
|
|
935
938
|
? (row: ListRowViewModel) => onRowClick(row, screen.entity)
|
|
936
939
|
: undefined;
|
|
937
940
|
|