@cosmicdrift/kumiko-renderer 0.305.0 → 0.306.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 +5 -5
- package/src/changes.json +11 -0
- package/src/components/render-edit.tsx +29 -18
- package/src/components/render-list.tsx +10 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.306.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.306.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.306.0",
|
|
20
|
+
"@cosmicdrift/kumiko-types": "0.306.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.306.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.306.0",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "List search no longer drops input typed while the list mounts"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"version": "0.306.0",
|
|
9
|
+
"type": "fix",
|
|
10
|
+
"title": "projectionDetail without a fields section no longer shows a no-op Save; extension tabs no longer repeat their title",
|
|
11
|
+
"detail": "A projectionDetail layout of only extension/relatedList sections synthesized a fieldless entity and drew a Save that did nothing. Extensions that register with the form host still get their Save. In layout.mode tabs, extension sections now honour hideSectionTitles like relatedList and writeForm."
|
|
12
|
+
},
|
|
2
13
|
{
|
|
3
14
|
"version": "0.296.0",
|
|
4
15
|
"type": "improvement",
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
evalFieldCondition,
|
|
8
8
|
isFieldsEditSection,
|
|
9
9
|
normalizeEditField,
|
|
10
|
+
PROJECTION_DETAIL_ENTITY,
|
|
10
11
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
11
12
|
import type {
|
|
12
13
|
DispatcherError,
|
|
@@ -158,6 +159,7 @@ function ExtensionSectionMount({
|
|
|
158
159
|
values,
|
|
159
160
|
patch,
|
|
160
161
|
validate,
|
|
162
|
+
hideTitle,
|
|
161
163
|
}: {
|
|
162
164
|
readonly section: EditExtensionSectionViewModel;
|
|
163
165
|
readonly entityName: string;
|
|
@@ -166,6 +168,7 @@ function ExtensionSectionMount({
|
|
|
166
168
|
readonly values?: Readonly<Record<string, unknown>>;
|
|
167
169
|
readonly patch?: (partial: Readonly<Record<string, unknown>>) => void;
|
|
168
170
|
readonly validate?: () => boolean;
|
|
171
|
+
readonly hideTitle?: boolean;
|
|
169
172
|
}): ReactNode {
|
|
170
173
|
const { Banner, Section, Text } = usePrimitives();
|
|
171
174
|
const name = extensionSectionName(section.component);
|
|
@@ -188,7 +191,10 @@ function ExtensionSectionMount({
|
|
|
188
191
|
);
|
|
189
192
|
}
|
|
190
193
|
return (
|
|
191
|
-
<Section
|
|
194
|
+
<Section
|
|
195
|
+
{...(hideTitle !== true && { title: section.title })}
|
|
196
|
+
testId={`section-extension-${section.title}`}
|
|
197
|
+
>
|
|
192
198
|
<Component
|
|
193
199
|
entityName={section.entityName ?? hostEntityName}
|
|
194
200
|
entityId={entityId}
|
|
@@ -1128,6 +1134,12 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
1128
1134
|
values={snapshot.values}
|
|
1129
1135
|
/>
|
|
1130
1136
|
) : undefined;
|
|
1137
|
+
// A projectionDetail's synthesized entity is empty when every section is an
|
|
1138
|
+
// extension or relatedList; that is not a fieldless submit form, so it must not
|
|
1139
|
+
// draw a no-op Save. Extension registrations still get their Save, since
|
|
1140
|
+
// persistExtensions() runs on the customSubmit path too.
|
|
1141
|
+
const isFieldlessSubmitForm = isFieldless && screen.entity !== PROJECTION_DETAIL_ENTITY;
|
|
1142
|
+
const showsSubmit = isFormEditable || hasExtensionRegistrations || isFieldlessSubmitForm;
|
|
1131
1143
|
const footerSlot = screen.slots?.footer;
|
|
1132
1144
|
// Mirrors every branch inside formActions below — without this guard
|
|
1133
1145
|
// DefaultForm renders an empty footer strip (border + padding, no content)
|
|
@@ -1136,8 +1148,7 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
1136
1148
|
const hasFormActions =
|
|
1137
1149
|
(isWizard && currentStep > 0) ||
|
|
1138
1150
|
(isWizard && !isLastWizardStep) ||
|
|
1139
|
-
(
|
|
1140
|
-
(!isWizard || isLastWizardStep)) ||
|
|
1151
|
+
(showsSubmit && (!isWizard || isLastWizardStep)) ||
|
|
1141
1152
|
footerSlot !== undefined;
|
|
1142
1153
|
const formActions = (
|
|
1143
1154
|
<>
|
|
@@ -1174,21 +1185,20 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
1174
1185
|
{translate("kumiko.actions.next")}
|
|
1175
1186
|
</Button>
|
|
1176
1187
|
)}
|
|
1177
|
-
{
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
disabled
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
)}
|
|
1188
|
+
{showsSubmit && (!isWizard || isLastWizardStep) && (
|
|
1189
|
+
<Button
|
|
1190
|
+
type="submit"
|
|
1191
|
+
disabled={
|
|
1192
|
+
(snapshot.isUnchanged && !extensionDirty && !isFieldless) || isSubmitting || disabled
|
|
1193
|
+
}
|
|
1194
|
+
loading={isSubmitting}
|
|
1195
|
+
variant={submitVariant ?? "primary"}
|
|
1196
|
+
icon="check"
|
|
1197
|
+
testId="render-edit-submit"
|
|
1198
|
+
>
|
|
1199
|
+
{translate(submitLabel ?? (isWizard ? "kumiko.actions.finish" : "kumiko.actions.save"))}
|
|
1200
|
+
</Button>
|
|
1201
|
+
)}
|
|
1192
1202
|
</>
|
|
1193
1203
|
);
|
|
1194
1204
|
|
|
@@ -1391,6 +1401,7 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
1391
1401
|
patchAndScheduleDraftSave as (partial: Readonly<Record<string, unknown>>) => void
|
|
1392
1402
|
}
|
|
1393
1403
|
validate={scopedValidate}
|
|
1404
|
+
hideTitle={hideSectionTitles}
|
|
1394
1405
|
/>
|
|
1395
1406
|
);
|
|
1396
1407
|
return wrapWizardStep(section.title, mount);
|
|
@@ -192,13 +192,18 @@ export function RenderList(props: RenderListProps): ReactNode {
|
|
|
192
192
|
const translate: Translate = translateProp ?? t;
|
|
193
193
|
const { DataTable, Button, Dialog, Input, Text, Banner } = usePrimitives();
|
|
194
194
|
|
|
195
|
-
// Local
|
|
196
|
-
//
|
|
197
|
-
//
|
|
195
|
+
// Local search buffer + debounce. External changes (browser back,
|
|
196
|
+
// cross-component reset) are mirrored back; typing only fires
|
|
197
|
+
// onSearchChange after 300ms without further keystrokes.
|
|
198
198
|
const [localQ, setLocalQ] = useState(searchValue ?? "");
|
|
199
|
-
useEffect
|
|
199
|
+
// Sync during render, not in a useEffect: the passive effect also fires on
|
|
200
|
+
// mount and can flush after a keystroke landing in the same commit,
|
|
201
|
+
// resetting localQ and swallowing the input.
|
|
202
|
+
const [syncedSearchValue, setSyncedSearchValue] = useState(searchValue);
|
|
203
|
+
if (searchValue !== syncedSearchValue) {
|
|
204
|
+
setSyncedSearchValue(searchValue);
|
|
200
205
|
setLocalQ(searchValue ?? "");
|
|
201
|
-
}
|
|
206
|
+
}
|
|
202
207
|
useEffect(() => {
|
|
203
208
|
if (onSearchChange === undefined) return;
|
|
204
209
|
if (localQ === (searchValue ?? "")) return;
|