@cosmicdrift/kumiko-renderer 0.249.0 → 0.250.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 +4 -4
- package/src/app/kumiko-screen.tsx +13 -0
- package/src/app/row-actions.ts +2 -0
- package/src/components/__tests__/render-edit-action-button.test.tsx +20 -0
- package/src/components/render-edit-action-button.tsx +5 -2
- package/src/components/render-edit-types.ts +6 -0
- package/src/components/render-edit.tsx +2 -1
- package/src/components/render-list.tsx +6 -1
- package/src/primitives.tsx +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.250.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.250.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.250.0",
|
|
20
20
|
"react": "^19.2.6",
|
|
21
21
|
"temporal-polyfill": "^0.3.2",
|
|
22
22
|
"zod": "^4.4.3"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"@types/react-dom": "^19.2.3",
|
|
28
28
|
"jsdom": "^29.1.1",
|
|
29
29
|
"react-dom": "^19.2.6",
|
|
30
|
-
"@cosmicdrift/kumiko-locale-de": "0.
|
|
30
|
+
"@cosmicdrift/kumiko-locale-de": "0.250.0"
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
@@ -763,6 +763,7 @@ function EntityEditUpdateForm({
|
|
|
763
763
|
id: action.id,
|
|
764
764
|
label: effectiveTranslate(action.label),
|
|
765
765
|
...(action.style !== undefined && { style: action.style }),
|
|
766
|
+
confirmRequired: false,
|
|
766
767
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
767
768
|
onPress: () => {
|
|
768
769
|
if (id === "") return;
|
|
@@ -783,6 +784,7 @@ function EntityEditUpdateForm({
|
|
|
783
784
|
id: action.id,
|
|
784
785
|
label: effectiveTranslate(action.label),
|
|
785
786
|
...(action.style !== undefined && { style: action.style }),
|
|
787
|
+
confirmRequired: false,
|
|
786
788
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
787
789
|
onPress: () => {
|
|
788
790
|
nav.navigate({
|
|
@@ -802,6 +804,7 @@ function EntityEditUpdateForm({
|
|
|
802
804
|
id: action.id,
|
|
803
805
|
label: effectiveTranslate(action.label),
|
|
804
806
|
...(action.style !== undefined && { style: action.style }),
|
|
807
|
+
confirmRequired: false,
|
|
805
808
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
806
809
|
onPress: () => {
|
|
807
810
|
const initialValues =
|
|
@@ -1612,6 +1615,7 @@ function EntityListBody({
|
|
|
1612
1615
|
id: action.id,
|
|
1613
1616
|
label: effectiveTranslate(action.label),
|
|
1614
1617
|
...(action.style !== undefined && { style: action.style }),
|
|
1618
|
+
confirmRequired: false,
|
|
1615
1619
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
1616
1620
|
onTrigger: (row: ListRowViewModel) => runNavigate(navigateAction, row),
|
|
1617
1621
|
...(actionVisible !== undefined && {
|
|
@@ -1627,6 +1631,7 @@ function EntityListBody({
|
|
|
1627
1631
|
id: action.id,
|
|
1628
1632
|
label: effectiveTranslate(action.label),
|
|
1629
1633
|
...(action.style !== undefined && { style: action.style }),
|
|
1634
|
+
confirmRequired: false,
|
|
1630
1635
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
1631
1636
|
onTrigger: (row: ListRowViewModel) => {
|
|
1632
1637
|
const initialValues =
|
|
@@ -1709,6 +1714,7 @@ function EntityListBody({
|
|
|
1709
1714
|
id: action.id,
|
|
1710
1715
|
label: effectiveTranslate(action.label),
|
|
1711
1716
|
...(action.style !== undefined && { style: action.style }),
|
|
1717
|
+
confirmRequired: false,
|
|
1712
1718
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
1713
1719
|
onTrigger: () => nav.navigate({ screenId: action.screen }),
|
|
1714
1720
|
};
|
|
@@ -1718,6 +1724,7 @@ function EntityListBody({
|
|
|
1718
1724
|
id: action.id,
|
|
1719
1725
|
label: effectiveTranslate(action.label),
|
|
1720
1726
|
...(action.style !== undefined && { style: action.style }),
|
|
1727
|
+
confirmRequired: false,
|
|
1721
1728
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
1722
1729
|
onTrigger: () => openDrawer(action),
|
|
1723
1730
|
};
|
|
@@ -2017,6 +2024,7 @@ function ProjectionListBody({
|
|
|
2017
2024
|
id: action.id,
|
|
2018
2025
|
label: effectiveTranslate(action.label),
|
|
2019
2026
|
...(action.style !== undefined && { style: action.style }),
|
|
2027
|
+
confirmRequired: false,
|
|
2020
2028
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
2021
2029
|
onTrigger: () => nav.navigate({ screenId: target }),
|
|
2022
2030
|
});
|
|
@@ -2027,6 +2035,7 @@ function ProjectionListBody({
|
|
|
2027
2035
|
id: action.id,
|
|
2028
2036
|
label: effectiveTranslate(action.label),
|
|
2029
2037
|
...(action.style !== undefined && { style: action.style }),
|
|
2038
|
+
confirmRequired: false,
|
|
2030
2039
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
2031
2040
|
onTrigger: () => openDrawer(action),
|
|
2032
2041
|
});
|
|
@@ -2315,6 +2324,7 @@ function ProjectionDetailBody({
|
|
|
2315
2324
|
id: action.id,
|
|
2316
2325
|
label: effectiveTranslate(action.label),
|
|
2317
2326
|
...(action.style !== undefined && { style: action.style }),
|
|
2327
|
+
confirmRequired: false,
|
|
2318
2328
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
2319
2329
|
onPress: () => {
|
|
2320
2330
|
if (id === "") return;
|
|
@@ -2348,6 +2358,7 @@ function ProjectionDetailBody({
|
|
|
2348
2358
|
id: action.id,
|
|
2349
2359
|
label: effectiveTranslate(action.label),
|
|
2350
2360
|
...(action.style !== undefined && { style: action.style }),
|
|
2361
|
+
confirmRequired: false,
|
|
2351
2362
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
2352
2363
|
onPress: () => {
|
|
2353
2364
|
nav.navigate({
|
|
@@ -2367,6 +2378,7 @@ function ProjectionDetailBody({
|
|
|
2367
2378
|
id: action.id,
|
|
2368
2379
|
label: effectiveTranslate(action.label),
|
|
2369
2380
|
...(action.style !== undefined && { style: action.style }),
|
|
2381
|
+
confirmRequired: false,
|
|
2370
2382
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
2371
2383
|
onPress: () => {
|
|
2372
2384
|
const initialValues =
|
|
@@ -2728,6 +2740,7 @@ function ActionFormBody({
|
|
|
2728
2740
|
onSubmit={handleSubmitted}
|
|
2729
2741
|
{...(handleCancel !== undefined && { onCancel: handleCancel })}
|
|
2730
2742
|
{...(screen.submitLabel !== undefined && { submitLabel: screen.submitLabel })}
|
|
2743
|
+
{...(screen.submitStyle !== undefined && { submitVariant: screen.submitStyle })}
|
|
2731
2744
|
{...(translate !== undefined && { translate })}
|
|
2732
2745
|
/>
|
|
2733
2746
|
);
|
package/src/app/row-actions.ts
CHANGED
|
@@ -156,6 +156,7 @@ function buildNavigateRowAction(
|
|
|
156
156
|
id: action.id,
|
|
157
157
|
label: translate(action.label),
|
|
158
158
|
...(action.style !== undefined && { style: action.style }),
|
|
159
|
+
confirmRequired: false,
|
|
159
160
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
160
161
|
onTrigger: (row: ListRowViewModel) => runProjectionRowNavigate(nav, action, row),
|
|
161
162
|
...(visible !== undefined && {
|
|
@@ -195,6 +196,7 @@ function buildDrawerRowAction(
|
|
|
195
196
|
id: action.id,
|
|
196
197
|
label: translate(action.label),
|
|
197
198
|
...(action.style !== undefined && { style: action.style }),
|
|
199
|
+
confirmRequired: false,
|
|
198
200
|
...(actionIcon !== undefined && { icon: actionIcon }),
|
|
199
201
|
onTrigger: (row: ListRowViewModel) => {
|
|
200
202
|
openDrawer(action, params !== undefined ? evalRowExtractor(params, row.values) : undefined);
|
|
@@ -137,6 +137,26 @@ describe("RenderEditActionButton", () => {
|
|
|
137
137
|
await waitFor(() => expect(pressed).toBe(1));
|
|
138
138
|
});
|
|
139
139
|
|
|
140
|
+
// fw#2752: schema-driven navigate/drawer actions set confirmRequired: false
|
|
141
|
+
// to opt a danger-styled action out of the forced dialog — the colour still
|
|
142
|
+
// marks it destructive, but the target form is itself the confirmation.
|
|
143
|
+
test("danger style with confirmRequired=false fires onPress directly, no dialog", async () => {
|
|
144
|
+
let pressed = 0;
|
|
145
|
+
renderAction({
|
|
146
|
+
id: "open-terminate-form",
|
|
147
|
+
label: "Terminate",
|
|
148
|
+
style: "danger",
|
|
149
|
+
confirmRequired: false,
|
|
150
|
+
onPress: async () => {
|
|
151
|
+
pressed += 1;
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
fireEvent.click(rtlScreen.getByTestId("render-edit-action-open-terminate-form"));
|
|
156
|
+
expect(rtlScreen.queryByTestId("render-edit-action-open-terminate-form-dialog")).toBeNull();
|
|
157
|
+
await waitFor(() => expect(pressed).toBe(1));
|
|
158
|
+
});
|
|
159
|
+
|
|
140
160
|
test("onPress failure reports via onError", async () => {
|
|
141
161
|
const errors: Array<string | null> = [];
|
|
142
162
|
renderAction(
|
|
@@ -39,8 +39,11 @@ export function RenderEditActionButton({
|
|
|
39
39
|
|
|
40
40
|
const variant = action.style ?? "secondary";
|
|
41
41
|
// Same rule as RowActionWriteHandler: "danger" forces a confirm even
|
|
42
|
-
// without an explicit confirm key
|
|
43
|
-
|
|
42
|
+
// without an explicit confirm key — unless `confirmRequired` overrides it
|
|
43
|
+
// (schema-driven navigate/drawer actions, where the target form is itself
|
|
44
|
+
// the confirmation).
|
|
45
|
+
const needsConfirm =
|
|
46
|
+
action.confirm !== undefined || (action.confirmRequired ?? action.style === "danger");
|
|
44
47
|
const showIconOnly = iconOnly && action.icon !== undefined;
|
|
45
48
|
|
|
46
49
|
return (
|
|
@@ -76,6 +76,8 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
|
|
|
76
76
|
* "Save" can be replaced by domain-specific strings ("Approve" /
|
|
77
77
|
* "Dispatch" / etc.). */
|
|
78
78
|
readonly submitLabel?: string;
|
|
79
|
+
/** Visual style of the submit button (actionForm `submitStyle`). Default "primary". */
|
|
80
|
+
readonly submitVariant?: "primary" | "danger";
|
|
79
81
|
/** Per-field extra content inline after the label (e.g.
|
|
80
82
|
* ConfigSourceBadge). Called with the field name, returns a ReactNode or
|
|
81
83
|
* undefined. */
|
|
@@ -168,6 +170,10 @@ export type RenderEditAction = {
|
|
|
168
170
|
readonly style?: "primary" | "secondary" | "danger";
|
|
169
171
|
readonly confirm?: string;
|
|
170
172
|
readonly confirmLabel?: string;
|
|
173
|
+
/** Overrides the default "danger implies a confirm dialog" rule. Schema-driven
|
|
174
|
+
* navigate/drawer actions set it to false: the colour marks the action as
|
|
175
|
+
* destructive, but the target form is the confirmation. */
|
|
176
|
+
readonly confirmRequired?: boolean;
|
|
171
177
|
/** Resolved icon (author `RowAction.icon` or the id-derived default) —
|
|
172
178
|
* drives both the icon-left-of-text render and the icon-only collapse
|
|
173
179
|
* rule (see `shouldRenderActionsIconOnly`). */
|
|
@@ -212,6 +212,7 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
212
212
|
actions,
|
|
213
213
|
onRelatedListDrawerAction,
|
|
214
214
|
submitLabel,
|
|
215
|
+
submitVariant,
|
|
215
216
|
labelAppendix,
|
|
216
217
|
fieldAppendix,
|
|
217
218
|
entityId: entityIdProp,
|
|
@@ -1032,7 +1033,7 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
1032
1033
|
type="submit"
|
|
1033
1034
|
disabled={(snapshot.isUnchanged && !extensionDirty) || isSubmitting || disabled}
|
|
1034
1035
|
loading={isSubmitting}
|
|
1035
|
-
variant="primary"
|
|
1036
|
+
variant={submitVariant ?? "primary"}
|
|
1036
1037
|
icon="check"
|
|
1037
1038
|
testId="render-edit-submit"
|
|
1038
1039
|
>
|
|
@@ -130,6 +130,10 @@ export type ToolbarActionButton = {
|
|
|
130
130
|
readonly style?: "primary" | "secondary" | "danger";
|
|
131
131
|
readonly confirm?: string;
|
|
132
132
|
readonly confirmLabel?: string;
|
|
133
|
+
/** Overrides the default "danger implies a confirm dialog" rule. Schema-driven
|
|
134
|
+
* navigate/drawer actions set it to false: the colour marks the action as
|
|
135
|
+
* destructive, but the target form is the confirmation. */
|
|
136
|
+
readonly confirmRequired?: boolean;
|
|
133
137
|
readonly onTrigger: () => Promise<void> | void;
|
|
134
138
|
/** Id-derived default icon (ACTION_ICON_BY_ID in kumiko-screen.tsx) —
|
|
135
139
|
* ToolbarAction has no author-declared icon field, unlike RowAction. */
|
|
@@ -492,7 +496,8 @@ function ToolbarActionView({
|
|
|
492
496
|
};
|
|
493
497
|
|
|
494
498
|
const variant: "primary" | "secondary" | "danger" = action.style ?? "secondary";
|
|
495
|
-
const needsConfirm =
|
|
499
|
+
const needsConfirm =
|
|
500
|
+
action.confirm !== undefined || (action.confirmRequired ?? action.style === "danger");
|
|
496
501
|
const showIconOnly = iconOnly && action.icon !== undefined;
|
|
497
502
|
|
|
498
503
|
return (
|
package/src/primitives.tsx
CHANGED
|
@@ -488,8 +488,9 @@ export type DataTableRowAction = {
|
|
|
488
488
|
readonly id: string;
|
|
489
489
|
/** Translated Label. */
|
|
490
490
|
readonly label: string;
|
|
491
|
-
/** Visual
|
|
492
|
-
*
|
|
491
|
+
/** Visual style — "danger" renders the red variant in the default
|
|
492
|
+
* primitive. Whether it also forces a confirm dialog is governed by
|
|
493
|
+
* `confirmRequired` (falls back to `style === "danger"` when unset). */
|
|
493
494
|
readonly style?: "primary" | "secondary" | "danger";
|
|
494
495
|
/** Translated Confirm-Prompt (Description im Dialog) — wenn gesetzt,
|
|
495
496
|
* öffnet ein Modal vor der Ausführung. Bei style=danger ohne expliziten
|
|
@@ -498,6 +499,10 @@ export type DataTableRowAction = {
|
|
|
498
499
|
/** Translated Confirm-Button-Label im Dialog. Default = `label`
|
|
499
500
|
* (Action-Label wird wiederverwendet). */
|
|
500
501
|
readonly confirmLabel?: string;
|
|
502
|
+
/** Overrides the default "danger implies a confirm dialog" rule. Schema-driven
|
|
503
|
+
* navigate/drawer actions set it to false: the colour marks the action as
|
|
504
|
+
* destructive, but the target form is the confirmation. */
|
|
505
|
+
readonly confirmRequired?: boolean;
|
|
501
506
|
/** Wird mit der ListRowViewModel der geklickten Row aufgerufen. Async
|
|
502
507
|
* erlaubt — der Renderer kann während der Promise-Resolution einen
|
|
503
508
|
* Loading-State auf dem Button zeigen. */
|