@cosmicdrift/kumiko-renderer 0.56.1 → 0.57.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.56.1",
3
+ "version": "0.57.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>",
@@ -1124,11 +1124,28 @@ function ConfigEditBody({
1124
1124
  if (!result.isSuccess) {
1125
1125
  return { validationBlocked: false, isSuccess: false, error: result.error };
1126
1126
  }
1127
+ // Cascade-Disclosure + Maskenwerte nach dem Write nachziehen, sonst
1128
+ // bleibt die Anzeige stale bis Reload — gleiche refetch-Calls wie onReset.
1129
+ await valuesQuery.refetch?.();
1130
+ await cascadeQuery.refetch?.();
1127
1131
  return { validationBlocked: false, isSuccess: true, data: undefined };
1128
1132
  },
1129
- [dispatcher, screen.configKeys, screen.scope],
1133
+ [dispatcher, screen.configKeys, screen.scope, valuesQuery.refetch, cascadeQuery.refetch],
1130
1134
  );
1131
1135
 
1136
+ // Cascade-Disclosure (#429): Trigger sitzt in der Label-Row, das Panel
1137
+ // unter dem Input — zwei getrennte Render-Slots teilen sich den
1138
+ // expanded-State, der daher hier (pro Feld) statt in der Komponente lebt.
1139
+ const [expandedFields, setExpandedFields] = useState<ReadonlySet<string>>(new Set());
1140
+ const toggleExpanded = useCallback((fieldName: string) => {
1141
+ setExpandedFields((prev) => {
1142
+ const next = new Set(prev);
1143
+ if (next.has(fieldName)) next.delete(fieldName);
1144
+ else next.add(fieldName);
1145
+ return next;
1146
+ });
1147
+ }, []);
1148
+
1132
1149
  if (valuesQuery.loading && valuesQuery.data === null) {
1133
1150
  return (
1134
1151
  <Banner padded variant="loading" testId="kumiko-screen-loading">
@@ -1159,13 +1176,30 @@ function ConfigEditBody({
1159
1176
  customSubmit={customSubmit}
1160
1177
  {...(screen.submitLabel !== undefined && { submitLabel: screen.submitLabel })}
1161
1178
  {...(translate !== undefined && { translate })}
1162
- fieldAppendix={(fieldName: string) => {
1179
+ labelAppendix={(fieldName: string) => {
1163
1180
  const cascade = cascades[fieldName];
1164
1181
  if (cascade === undefined) return undefined;
1165
1182
  return (
1166
1183
  <ConfigCascadeView
1184
+ slot="trigger"
1185
+ cascade={cascade}
1186
+ screenScope={screen.scope}
1187
+ expanded={expandedFields.has(fieldName)}
1188
+ onToggle={() => toggleExpanded(fieldName)}
1189
+ />
1190
+ );
1191
+ }}
1192
+ fieldAppendix={(fieldName: string) => {
1193
+ const cascade = cascades[fieldName];
1194
+ // Panel nur rendern wenn aufgeklappt — sonst kein leerer Abstand
1195
+ // unter dem Input.
1196
+ if (cascade === undefined || !expandedFields.has(fieldName)) return undefined;
1197
+ return (
1198
+ <ConfigCascadeView
1199
+ slot="panel"
1167
1200
  cascade={cascade}
1168
1201
  screenScope={screen.scope}
1202
+ expanded
1169
1203
  qualifiedKey={screen.configKeys[fieldName]}
1170
1204
  onReset={async (key, scope) => {
1171
1205
  await dispatcher.write("config:write:reset", { key, scope });
@@ -521,14 +521,19 @@ export type ConfigSourceBadgeProps = {
521
521
  readonly screenScope?: ConfigScope;
522
522
  };
523
523
 
524
- /** Collapsible cascade-view that lives under a config-edit input.
525
- * Shows the active level inline; click expands the full cascade and
526
- * exposes a Reset-button if the active level matches `screenScope`. */
524
+ /** Collapsible cascade-view for a config-edit field. Split into two slots
525
+ * (#429): "trigger" is the collapsed + source + value line for the label
526
+ * row; "panel" is the expanded cascade + reset shown below the input. In
527
+ * split mode the screen owns the `expanded` state (both slots share it);
528
+ * without `slot` the component renders both with its own state. */
527
529
  export type ConfigCascadeViewProps = {
528
530
  readonly cascade: ConfigCascade;
529
531
  readonly screenScope: ConfigScope;
530
532
  readonly onReset?: (key: string, scope: ConfigScope) => void;
531
533
  readonly qualifiedKey?: string;
534
+ readonly slot?: "trigger" | "panel";
535
+ readonly expanded?: boolean;
536
+ readonly onToggle?: () => void;
532
537
  };
533
538
 
534
539
  // ---- Core-Registry (Kumiko-eigene Primitives) ----