@cosmicdrift/kumiko-renderer 0.379.0 → 0.381.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.
|
@@ -44,6 +44,10 @@ export type ExtensionSectionProps = {
|
|
|
44
44
|
* `snapshot.errors` on the field instead of a collective message.
|
|
45
45
|
* Undefined outside entityEdit sections. */
|
|
46
46
|
readonly validate?: () => boolean;
|
|
47
|
+
/** Marks the current host form values as saved after the section wrote
|
|
48
|
+
* them itself, so a following navigation does not ask to discard.
|
|
49
|
+
* Undefined outside entityEdit sections and the entityEdit footer slot. */
|
|
50
|
+
readonly markSaved?: () => void;
|
|
47
51
|
/** Whether the host form has unsaved changes. Only set in the entityEdit
|
|
48
52
|
* footer slot (`screen.slots.footer`); undefined in all other mounts. */
|
|
49
53
|
readonly hasUnsavedChanges?: boolean;
|
|
@@ -165,7 +165,7 @@ function isFieldsStepFilled(section, parse) {
|
|
|
165
165
|
// mounts it with the host entity name + id. Hook lives in its own
|
|
166
166
|
// component so we don't call `use*` inside vm.sections.map (rules-of-
|
|
167
167
|
// hooks would punish reordering sections between renders).
|
|
168
|
-
function ExtensionSectionMount({ section, entityName: hostEntityName, entityId, initialValues, values, patch, validate, hideTitle, actions, stepKey, onReportStepComplete, }) {
|
|
168
|
+
function ExtensionSectionMount({ section, entityName: hostEntityName, entityId, initialValues, values, patch, validate, markSaved, hideTitle, actions, stepKey, onReportStepComplete, }) {
|
|
169
169
|
const { Banner, FillContainer, Section, Text } = usePrimitives();
|
|
170
170
|
const name = extensionSectionName(section.component);
|
|
171
171
|
const Component = useExtensionSectionComponent(name);
|
|
@@ -184,7 +184,7 @@ function ExtensionSectionMount({ section, entityName: hostEntityName, entityId,
|
|
|
184
184
|
// uses for its own hideTitle branch).
|
|
185
185
|
return hideTitle === true ? (FillContainer !== undefined ? (_jsx(FillContainer, { testId: testId, children: placeholder })) : (placeholder)) : (_jsx(Section, { title: section.title, testId: testId, children: placeholder }, section.title));
|
|
186
186
|
}
|
|
187
|
-
const mounted = (_jsx(Component, { entityName: section.entityName ?? hostEntityName, entityId: entityId, initialValues: initialValues, values: values, patch: patch, validate: validate, ...(onReportStepComplete !== undefined && { reportStepComplete }) }));
|
|
187
|
+
const mounted = (_jsx(Component, { entityName: section.entityName ?? hostEntityName, entityId: entityId, initialValues: initialValues, values: values, patch: patch, validate: validate, markSaved: markSaved, ...(onReportStepComplete !== undefined && { reportStepComplete }) }));
|
|
188
188
|
// Same reasoning as the placeholder branch above: tabs mode's outer Card
|
|
189
189
|
// is the only frame this content gets, no nested Section divider.
|
|
190
190
|
return hideTitle === true ? (FillContainer !== undefined ? (_jsx(FillContainer, { testId: testId, children: mounted })) : (mounted)) : (_jsx(Section, { ...(actions !== undefined && { actions }), title: section.title, testId: testId, children: mounted }));
|
|
@@ -195,7 +195,7 @@ function ExtensionSectionMount({ section, entityName: hostEntityName, entityId,
|
|
|
195
195
|
// component for both slots — header passes no hasUnsavedChanges/wizardStep,
|
|
196
196
|
// footer does. Own component for rules-of-hooks (useExtensionSectionComponent
|
|
197
197
|
// must not run conditionally inside RenderEdit's render body).
|
|
198
|
-
function EditSlotMount({ slot, slotName, screenId, entityName, entityId, values, hasUnsavedChanges, wizardStep, }) {
|
|
198
|
+
function EditSlotMount({ slot, slotName, screenId, entityName, entityId, values, markSaved, hasUnsavedChanges, wizardStep, }) {
|
|
199
199
|
const name = extensionSectionName(slot);
|
|
200
200
|
const Component = useExtensionSectionComponent(name);
|
|
201
201
|
useEffect(() => {
|
|
@@ -206,7 +206,7 @@ function EditSlotMount({ slot, slotName, screenId, entityName, entityId, values,
|
|
|
206
206
|
}, [name, Component, screenId, slotName]);
|
|
207
207
|
if (Component === undefined)
|
|
208
208
|
return null;
|
|
209
|
-
return (_jsx(Component, { entityName: entityName, entityId: entityId, values: values, ...(hasUnsavedChanges !== undefined && { hasUnsavedChanges }), ...(wizardStep !== undefined && { wizardStep }) }));
|
|
209
|
+
return (_jsx(Component, { entityName: entityName, entityId: entityId, values: values, ...(markSaved !== undefined && { markSaved }), ...(hasUnsavedChanges !== undefined && { hasUnsavedChanges }), ...(wizardStep !== undefined && { wizardStep }) }));
|
|
210
210
|
}
|
|
211
211
|
function formSectionDomId(sectionIndex) {
|
|
212
212
|
return `form-section-${sectionIndex}`;
|
|
@@ -442,6 +442,11 @@ export function RenderEdit(props) {
|
|
|
442
442
|
const scopeFieldNamesRef = useRef(scopeFieldNames);
|
|
443
443
|
scopeFieldNamesRef.current = scopeFieldNames;
|
|
444
444
|
const scopedValidate = useCallback(() => controller.validate(scopeFieldNamesRef.current), [controller]);
|
|
445
|
+
const markSaved = useCallback(() => {
|
|
446
|
+
controller.rebase();
|
|
447
|
+
extensionDirtyRef.current = false;
|
|
448
|
+
setExtensionDirty(false);
|
|
449
|
+
}, [controller]);
|
|
445
450
|
// `saveDraft` (defined below) is a fresh closure every render, over that
|
|
446
451
|
// render's `draftKey`/`draftId` — the ref lets the debounce timer below
|
|
447
452
|
// always call the CURRENT one even though the timer was scheduled by an
|
|
@@ -1226,7 +1231,7 @@ export function RenderEdit(props) {
|
|
|
1226
1231
|
? 1
|
|
1227
1232
|
: 0;
|
|
1228
1233
|
const isSettingsList = screen.layout.variant === "settings-list" && hideSectionTitles !== true;
|
|
1229
|
-
const formActions = (_jsxs(_Fragment, { children: [showWizardBack && (_jsx(Button, { type: "button", variant: "secondary", icon: "chevron-left", onClick: handleWizardBack, testId: "render-edit-wizard-back", [FOOTER_ACTION_ROLE_PROP]: "back", children: translate("kumiko.actions.back") })), footerSlot !== undefined && (_jsx(EditSlotMount, { slot: footerSlot, slotName: "footer", screenId: screen.id, entityName: vm.entityName, entityId: resolveExtensionEntityId(entityIdProp, vm.id), values: snapshot.values, hasUnsavedChanges: hasUnsavedInput, wizardStep: isWizard ? { index: currentStep, isLast: isLastWizardStep } : undefined, ...(screen.slots?.footerPrimary === true && { [STICKY_PRIMARY_ACTION_PROP]: true }) })), isWizard && !isLastWizardStep && showsSubmit && !isCreateMode && (_jsx(Button, { type: "button", variant: "ghost", disabled: isSubmitting || disabled, onClick: () => void handleSubmit({ saveNow: true }), testId: "render-edit-wizard-save-close", [STICKY_PRIMARY_ACTION_PROP]: true, children: translate("kumiko.wizard.save-and-close") })), showWizardNext && (_jsx(Button, { type: "submit", variant: "primary", iconEnd: "chevron-right", testId: "render-edit-wizard-next", [FOOTER_ACTION_ROLE_PROP]: "primary",
|
|
1234
|
+
const formActions = (_jsxs(_Fragment, { children: [showWizardBack && (_jsx(Button, { type: "button", variant: "secondary", icon: "chevron-left", onClick: handleWizardBack, testId: "render-edit-wizard-back", [FOOTER_ACTION_ROLE_PROP]: "back", children: translate("kumiko.actions.back") })), footerSlot !== undefined && (_jsx(EditSlotMount, { slot: footerSlot, slotName: "footer", screenId: screen.id, entityName: vm.entityName, entityId: resolveExtensionEntityId(entityIdProp, vm.id), values: snapshot.values, markSaved: markSaved, hasUnsavedChanges: hasUnsavedInput, wizardStep: isWizard ? { index: currentStep, isLast: isLastWizardStep } : undefined, ...(screen.slots?.footerPrimary === true && { [STICKY_PRIMARY_ACTION_PROP]: true }) })), isWizard && !isLastWizardStep && showsSubmit && !isCreateMode && (_jsx(Button, { type: "button", variant: "ghost", disabled: isSubmitting || disabled, onClick: () => void handleSubmit({ saveNow: true }), testId: "render-edit-wizard-save-close", [STICKY_PRIMARY_ACTION_PROP]: true, children: translate("kumiko.wizard.save-and-close") })), showWizardNext && (_jsx(Button, { type: "submit", variant: "primary", iconEnd: "chevron-right", testId: "render-edit-wizard-next", [FOOTER_ACTION_ROLE_PROP]: "primary",
|
|
1230
1235
|
[NARROW_LABEL_PROP]: translate("kumiko.actions.next"), children: nextStepTitle !== undefined
|
|
1231
1236
|
? translate("kumiko.wizard.next-with-title", { title: nextStepTitle })
|
|
1232
1237
|
: translate("kumiko.actions.next") })), showSubmit &&
|
|
@@ -1403,7 +1408,7 @@ export function RenderEdit(props) {
|
|
|
1403
1408
|
const mount = (_jsx(ExtensionSectionMount, { section: section, entityName: vm.entityName, entityId: resolveExtensionEntityId(entityIdProp, vm.id), initialValues: extensionInitialValues, values: snapshot.values,
|
|
1404
1409
|
// @cast-boundary form-values: ExtensionSectionProps is not generic
|
|
1405
1410
|
// over TValues; controller is mount-lifetime-stable, see onControlsReady above.
|
|
1406
|
-
patch: patchAndScheduleDraftSave, validate: scopedValidate, hideTitle: hideSectionTitles, ...(isWizard &&
|
|
1411
|
+
patch: patchAndScheduleDraftSave, validate: scopedValidate, markSaved: markSaved, hideTitle: hideSectionTitles, ...(isWizard &&
|
|
1407
1412
|
!isCreateMode && {
|
|
1408
1413
|
stepKey: wizardStepKey(section, sectionIndex),
|
|
1409
1414
|
onReportStepComplete: reportExtensionStepComplete,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.381.0",
|
|
4
4
|
"description": "React renderer for Kumiko screens. The root export holds the platform-agnostic logic (primitives contract, hooks, KumikoScreen, navigation and SSE abstractions); ./headless the DOM-free UI logic, ./live the HTTP dispatcher, ./web the browser bindings (HTML primitives, history navigation, EventSource live events, createKumikoApp).",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
52
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
51
|
+
"@cosmicdrift/kumiko-framework": "0.381.0",
|
|
52
|
+
"@cosmicdrift/kumiko-types": "0.381.0",
|
|
53
53
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
54
54
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
55
55
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
96
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
95
|
+
"@cosmicdrift/kumiko-locale-de": "0.381.0",
|
|
96
|
+
"@cosmicdrift/kumiko-testing": "0.381.0",
|
|
97
97
|
"@tailwindcss/cli": "^4.3.0",
|
|
98
98
|
"@testing-library/react": "^16.3.2",
|
|
99
99
|
"@testing-library/user-event": "^14.6.1",
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.381.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "ExtensionSectionProps.markSaved marks host form values as saved",
|
|
6
|
+
"detail": "ExtensionSectionProps gains an optional markSaved callback, set in entityEdit sections and the entityEdit footer slot. An extension section that writes the host entity's values through its own command calls it after the write succeeds; the host form then counts as clean and a following navigation passes the leave guard without the discard dialog. Additive API.",
|
|
7
|
+
"migration": "None. Additive API."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.379.0",
|
|
4
11
|
"type": "fix",
|