@cosmicdrift/kumiko-renderer 0.372.0 → 0.374.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/dist/app/action-form-support.d.ts +51 -0
- package/dist/app/action-form-support.js +74 -0
- package/dist/app/client-actions.d.ts +46 -0
- package/dist/app/client-actions.js +60 -0
- package/dist/app/entity-list-expanded-row.js +4 -0
- package/dist/app/header-actions-bar.d.ts +19 -0
- package/dist/app/header-actions-bar.js +30 -0
- package/dist/app/kumiko-screen.js +129 -62
- package/dist/app/list-empty-content.d.ts +1 -1
- package/dist/app/list-empty-content.js +3 -2
- package/dist/app/row-actions.d.ts +9 -1
- package/dist/app/row-actions.js +85 -6
- package/dist/app/use-record-actions.d.ts +12 -0
- package/dist/app/use-record-actions.js +30 -0
- package/dist/components/grid-cell-for-field.d.ts +3 -1
- package/dist/components/grid-cell-for-field.js +2 -2
- package/dist/components/related-list-section.js +16 -3
- package/dist/components/render-edit.js +1 -1
- package/dist/components/render-field.d.ts +4 -1
- package/dist/components/render-field.js +99 -8
- package/dist/i18n-defaults.js +7 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -0
- package/dist/primitives.d.ts +18 -0
- package/package.json +5 -5
- package/src/changes.json +24 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RowAction } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { Translate } from "@cosmicdrift/kumiko-headless";
|
|
3
|
+
import type { RenderEditAction } from "../components/render-edit-types.ts";
|
|
4
|
+
/** Header actions on a record shown by a screen that has no drawer host
|
|
5
|
+
* (dashboards): writeHandler, navigate and clientAction, `onWriteSuccess` after a success. */
|
|
6
|
+
export declare function useRecordActions(options: {
|
|
7
|
+
readonly actions: readonly RowAction[] | undefined;
|
|
8
|
+
readonly record: Readonly<Record<string, unknown>>;
|
|
9
|
+
readonly recordId: string;
|
|
10
|
+
readonly translate: Translate;
|
|
11
|
+
readonly onWriteSuccess: () => void | Promise<void>;
|
|
12
|
+
}): readonly RenderEditAction[] | undefined;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useOptionalDispatcher } from "../context/dispatcher-context.js";
|
|
3
|
+
import { useClientActionRuntime } from "./client-actions.js";
|
|
4
|
+
import { useNav } from "./nav.js";
|
|
5
|
+
import { useReturnHost } from "./return-to.js";
|
|
6
|
+
import { buildRecordActions } from "./row-actions.js";
|
|
7
|
+
const NO_DRAWER = () => { };
|
|
8
|
+
/** Header actions on a record shown by a screen that has no drawer host
|
|
9
|
+
* (dashboards): writeHandler, navigate and clientAction, `onWriteSuccess` after a success. */
|
|
10
|
+
export function useRecordActions(options) {
|
|
11
|
+
const { actions, record, recordId, translate, onWriteSuccess } = options;
|
|
12
|
+
const nav = useNav();
|
|
13
|
+
const host = useReturnHost();
|
|
14
|
+
const dispatcher = useOptionalDispatcher();
|
|
15
|
+
const clientActions = useClientActionRuntime();
|
|
16
|
+
return useMemo(() => actions === undefined
|
|
17
|
+
? undefined
|
|
18
|
+
: buildRecordActions({
|
|
19
|
+
actions,
|
|
20
|
+
record,
|
|
21
|
+
translate,
|
|
22
|
+
nav,
|
|
23
|
+
host,
|
|
24
|
+
dispatcher,
|
|
25
|
+
openDrawer: NO_DRAWER,
|
|
26
|
+
onWriteSuccess,
|
|
27
|
+
defaultWritePayloadId: recordId,
|
|
28
|
+
clientActions,
|
|
29
|
+
}), [actions, record, recordId, translate, nav, host, dispatcher, onWriteSuccess, clientActions]);
|
|
30
|
+
}
|
|
@@ -26,6 +26,8 @@ export type GridCellForFieldProps = {
|
|
|
26
26
|
readonly flow?: boolean;
|
|
27
27
|
readonly layout?: FieldProps["layout"];
|
|
28
28
|
readonly accent?: boolean;
|
|
29
|
+
/** Passed through to RenderField — see RenderFieldProps.fillDisabled. */
|
|
30
|
+
readonly fillDisabled?: boolean;
|
|
29
31
|
};
|
|
30
32
|
export declare function fieldCellWidth(field: EditFieldViewModel): FieldCellWidth;
|
|
31
|
-
export declare function GridCellForField({ field, columns, issues, onChange, GridCell, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay, row, changed, flow, layout, accent, }: GridCellForFieldProps): ReactNode;
|
|
33
|
+
export declare function GridCellForField({ field, columns, issues, onChange, GridCell, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay, row, changed, flow, layout, accent, fillDisabled, }: GridCellForFieldProps): ReactNode;
|
|
@@ -29,10 +29,10 @@ export function fieldCellWidth(field) {
|
|
|
29
29
|
return "full";
|
|
30
30
|
return FIELD_CELL_WIDTH_BY_TYPE[field.type] ?? "text";
|
|
31
31
|
}
|
|
32
|
-
export function GridCellForField({ field, columns, issues, onChange, GridCell, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay, row, changed, flow, layout, accent, }) {
|
|
32
|
+
export function GridCellForField({ field, columns, issues, onChange, GridCell, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay, row, changed, flow, layout, accent, fillDisabled, }) {
|
|
33
33
|
// RenderField renders nothing for a hidden field, but the GridCell around it still claims the row.
|
|
34
34
|
if (!field.visible)
|
|
35
35
|
return null;
|
|
36
36
|
const effectiveSpan = field.span !== undefined ? Math.min(field.span, columns) : 1;
|
|
37
|
-
return (_jsx(GridCell, { span: effectiveSpan, ...(flow === true && { width: fieldCellWidth(field) }), children: _jsx(RenderField, { field: field, ...(issues !== undefined && { issues }), onChange: onChange, featureName: featureName, ...(labelAppendix !== undefined && { labelAppendix }), ...(fieldAppendix !== undefined && { fieldAppendix }), ...(appendixPlacement !== undefined && { appendixPlacement }), ...(description !== undefined && { description }), ...(status !== undefined && { status }), allIssues: allIssues, valueDisplay: valueDisplay, row: row, ...(changed === true && { changed }), ...(layout !== undefined && { layout }), ...(accent === true && { accent }) }) }));
|
|
37
|
+
return (_jsx(GridCell, { span: effectiveSpan, ...(flow === true && { width: fieldCellWidth(field) }), children: _jsx(RenderField, { field: field, ...(issues !== undefined && { issues }), onChange: onChange, featureName: featureName, ...(labelAppendix !== undefined && { labelAppendix }), ...(fieldAppendix !== undefined && { fieldAppendix }), ...(appendixPlacement !== undefined && { appendixPlacement }), ...(description !== undefined && { description }), ...(status !== undefined && { status }), allIssues: allIssues, valueDisplay: valueDisplay, row: row, ...(changed === true && { changed }), ...(layout !== undefined && { layout }), ...(accent === true && { accent }), ...(fillDisabled === true && { fillDisabled }) }) }));
|
|
38
38
|
}
|
|
@@ -2,11 +2,12 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { evalFieldCondition, normalizeListColumn, parseRefTarget, relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useAppFeatures } from "../app/app-features-context.js";
|
|
5
|
+
import { useClientActionRuntime } from "../app/client-actions.js";
|
|
5
6
|
import { buildFilterFacets, buildFilterPayload, mergeReferenceFacetOptions, resolveProjectionFacetSpecs, } from "../app/list-facets.js";
|
|
6
7
|
import { useNav } from "../app/nav.js";
|
|
7
8
|
import { ReferenceFacetBridges, } from "../app/reference-facet-bridge.js";
|
|
8
9
|
import { navigateWithReturnTo, useReturnHost } from "../app/return-to.js";
|
|
9
|
-
import { buildDefaultEditRowAction, buildProjectionRowActions, buildProjectionToolbarActions, buildRecordActions, runProjectionRowNavigate, } from "../app/row-actions.js";
|
|
10
|
+
import { buildDefaultEditRowAction, buildProjectionRowActions, buildProjectionToolbarActions, buildRecordActions, findRowClickAction, runDrawerRowAction, runProjectionRowNavigate, } from "../app/row-actions.js";
|
|
10
11
|
import { findEditScreenFor } from "../app/screen-access.js";
|
|
11
12
|
import { dispatcherErrorText } from "../app/write-failed-error.js";
|
|
12
13
|
import { useOptionalDispatcher } from "../context/dispatcher-context.js";
|
|
@@ -95,6 +96,7 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
|
|
|
95
96
|
const nav = useNav();
|
|
96
97
|
const host = useReturnHost();
|
|
97
98
|
const dispatcher = useOptionalDispatcher();
|
|
99
|
+
const clientActions = useClientActionRuntime();
|
|
98
100
|
const appFeatures = useAppFeatures();
|
|
99
101
|
const userRoles = useUserRoles();
|
|
100
102
|
const defaultEditScreen = useMemo(() => {
|
|
@@ -196,7 +198,7 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
|
|
|
196
198
|
// A row-body click target comes from EITHER the legacy `rowClick` field OR
|
|
197
199
|
// a `rowActions` entry marked rowClick:true — the boot-validator rejects
|
|
198
200
|
// both being set, so this order is just a fallback, not a precedence rule.
|
|
199
|
-
const rowClickAction = section.rowActions
|
|
201
|
+
const rowClickAction = findRowClickAction(section.rowActions);
|
|
200
202
|
const onRowClick = rowClick !== undefined
|
|
201
203
|
? (row) => {
|
|
202
204
|
const id = String(row.values[rowClick.idColumn ?? "id"] ?? "");
|
|
@@ -205,7 +207,14 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
|
|
|
205
207
|
navigateWithReturnTo(nav, { entity: rowClick.entity, id }, host);
|
|
206
208
|
}
|
|
207
209
|
: rowClickAction !== undefined
|
|
208
|
-
? (row) =>
|
|
210
|
+
? (row) => {
|
|
211
|
+
if (rowClickAction.kind === "navigate") {
|
|
212
|
+
runProjectionRowNavigate(nav, rowClickAction, row, host);
|
|
213
|
+
}
|
|
214
|
+
else if (onOpenDrawer !== undefined) {
|
|
215
|
+
runDrawerRowAction(rowClickAction, onOpenDrawer, row);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
209
218
|
: undefined;
|
|
210
219
|
// Same execution path as projectionList's rowActions (kumiko-screen.tsx) —
|
|
211
220
|
// a relatedList row has the identical "no guaranteed id field" shape a
|
|
@@ -220,10 +229,12 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
|
|
|
220
229
|
openDrawer: onOpenDrawer,
|
|
221
230
|
defaultEditRowAction,
|
|
222
231
|
host,
|
|
232
|
+
clientActions,
|
|
223
233
|
}), [
|
|
224
234
|
section.rowActions,
|
|
225
235
|
effectiveTranslate,
|
|
226
236
|
dispatcher,
|
|
237
|
+
clientActions,
|
|
227
238
|
nav,
|
|
228
239
|
refetchSelfAndHost,
|
|
229
240
|
onOpenDrawer,
|
|
@@ -283,6 +294,7 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
|
|
|
283
294
|
dispatcher,
|
|
284
295
|
openDrawer: onOpenDrawer ?? (() => { }),
|
|
285
296
|
onWriteSuccess: refetchSelfAndHost,
|
|
297
|
+
clientActions,
|
|
286
298
|
})?.[0];
|
|
287
299
|
}, [
|
|
288
300
|
section.emptyState,
|
|
@@ -291,6 +303,7 @@ export function RelatedListSection({ section, parentId, record, featureName, tra
|
|
|
291
303
|
nav,
|
|
292
304
|
host,
|
|
293
305
|
dispatcher,
|
|
306
|
+
clientActions,
|
|
294
307
|
onOpenDrawer,
|
|
295
308
|
refetchSelfAndHost,
|
|
296
309
|
]);
|
|
@@ -1430,7 +1430,7 @@ export function RenderEdit(props) {
|
|
|
1430
1430
|
appendixPlacement: fieldAppendixPlacement,
|
|
1431
1431
|
}), ...(fieldDescription !== undefined && {
|
|
1432
1432
|
description: fieldDescription(field.field),
|
|
1433
|
-
}), allIssues: snapshot.errors, valueDisplay: valueDisplay, row: snapshot.values, ...((!isCreate || markChangedFields) &&
|
|
1433
|
+
}), allIssues: snapshot.errors, valueDisplay: valueDisplay, row: snapshot.values, ...(disabled && { fillDisabled: true }), ...((!isCreate || markChangedFields) &&
|
|
1434
1434
|
field.field in snapshot.changes && { changed: true }) }, field.field))) }, gridKey));
|
|
1435
1435
|
// Tabs mode: the tab panel is the surface, so field sections render
|
|
1436
1436
|
// as unframed padded panels; group cards inside stay framed.
|
|
@@ -39,6 +39,9 @@ export type RenderFieldProps = {
|
|
|
39
39
|
readonly changed?: boolean;
|
|
40
40
|
readonly layout?: FieldProps["layout"];
|
|
41
41
|
readonly accent?: boolean;
|
|
42
|
+
/** The surrounding form is locked as a whole: the fill button stays visible
|
|
43
|
+
* but inert, unlike a single readOnly field whose fill button keeps working. */
|
|
44
|
+
readonly fillDisabled?: boolean;
|
|
42
45
|
};
|
|
43
46
|
export declare const AllFieldsRequiredProvider: import("react").Provider<boolean>;
|
|
44
|
-
export declare function RenderField({ field, issues, onChange, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay, row, changed, layout, accent, }: RenderFieldProps): ReactNode;
|
|
47
|
+
export declare function RenderField({ field, issues, onChange, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay, row, changed, layout, accent, fillDisabled, }: RenderFieldProps): ReactNode;
|
|
@@ -2,13 +2,15 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { isFormatSpec, SYSTEM_REFERENCE_LABELS, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
3
|
import { applyFormatSpec, currencyDecimals, EMPTY_VALUE_PLACEHOLDER, } from "@cosmicdrift/kumiko-headless";
|
|
4
4
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
5
|
-
import { createContext, Fragment, useCallback, useContext, useMemo, useState, } from "react";
|
|
5
|
+
import { createContext, Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
6
6
|
import { useAppFeatures } from "../app/app-features-context.js";
|
|
7
7
|
import { useColumnRenderer } from "../app/column-renderers.js";
|
|
8
8
|
import { extensionSectionName } from "../app/extension-sections.js";
|
|
9
9
|
import { toKebab } from "../app/qn.js";
|
|
10
10
|
import { useReferenceLinkResolver } from "../app/reference-link.js";
|
|
11
|
+
import { evalRowExtractor } from "../app/row-actions.js";
|
|
11
12
|
import { screenAccessAllows } from "../app/screen-access.js";
|
|
13
|
+
import { useOptionalDispatcher } from "../context/dispatcher-context.js";
|
|
12
14
|
import { useUserRoles } from "../context/user-roles-context.js";
|
|
13
15
|
import { REFERENCE_COMBOBOX_LIMIT } from "../hooks/reference-limits.js";
|
|
14
16
|
import { useQuery } from "../hooks/use-query.js";
|
|
@@ -23,7 +25,7 @@ import { QueryOptionsSelect } from "./query-options-select.js";
|
|
|
23
25
|
import { ReferenceCreateDialog } from "./reference-create-dialog.js";
|
|
24
26
|
const AllFieldsRequiredContext = createContext(false);
|
|
25
27
|
export const AllFieldsRequiredProvider = AllFieldsRequiredContext.Provider;
|
|
26
|
-
export function RenderField({ field, issues, onChange, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay = "form", row, changed, layout, accent, }) {
|
|
28
|
+
export function RenderField({ field, issues, onChange, featureName, labelAppendix, fieldAppendix, appendixPlacement, description, status, allIssues, valueDisplay = "form", row, changed, layout, accent, fillDisabled, }) {
|
|
27
29
|
const { Field, Input, Banner, Text, JsonView } = usePrimitives();
|
|
28
30
|
// App-Locale (i18n) für money/date-Inputs — sonst fielen sie auf
|
|
29
31
|
// navigator.language (Browser-Sprache) zurück statt der gewählten
|
|
@@ -48,7 +50,20 @@ export function RenderField({ field, issues, onChange, featureName, labelAppendi
|
|
|
48
50
|
// Komponente gemountet werden (nicht als pure render-Call).
|
|
49
51
|
const control = field.readOnly && field.renderer !== undefined ? (_jsx(FieldRendererOutput, { field: field, renderer: field.renderer, appLocale: appLocale, ...(row !== undefined && { row }) })) : field.type === "embedded" && field.embeddedListCells !== undefined ? (_jsx(EmbeddedListField, { field: field, id: id, onChange: onChange, allIssues: allIssues ?? {}, featureName: featureName ?? "" })) : field.type === "reference" ? (readOnlyText ? (_jsx(ReadOnlyReferenceValue, { field: field, featureName: featureName ?? "" })) : (_jsx(ReferenceInput, { field: field, id: id, hasError: hasError, onChange: onChange, Input: Input, featureName: featureName ?? "" }))) : field.type === "select" && field.selectOptionsQuery !== undefined && !readOnlyText ? (_jsx(QueryOptionsSelect, { field: field, query: field.selectOptionsQuery.query, payload: field.selectOptionsQuery.payload, row: row ?? { [field.field]: field.value }, id: id, hasError: hasError, onChange: onChange })) : field.type === "select" &&
|
|
50
52
|
field.selectOptionsAvailabilityQuery !== undefined &&
|
|
51
|
-
!readOnlyText ? (_jsx(AvailabilityOptionsSelect, { field: field, query: field.selectOptionsAvailabilityQuery.query, payload: field.selectOptionsAvailabilityQuery.payload, row: row ?? { [field.field]: field.value }, id: id, hasError: hasError, onChange: onChange })) : readOnlyText && !isComplexFieldType(field.type) && !isFileFieldType(field.type) ? (_jsx(Text, { testId: `field-value-${field.field}`, children: readOnlyDisplayText(field, appLocale, t) })) : field.type === "text" && field.writeOnly === true && !readOnlyText ? (_jsx(WriteOnlyTextInput, { field: field, id: id, hasError: hasError, onChange: onChange, changed: changed === true })) : (renderInput({
|
|
53
|
+
!readOnlyText ? (_jsx(AvailabilityOptionsSelect, { field: field, query: field.selectOptionsAvailabilityQuery.query, payload: field.selectOptionsAvailabilityQuery.payload, row: row ?? { [field.field]: field.value }, id: id, hasError: hasError, onChange: onChange })) : readOnlyText && !isComplexFieldType(field.type) && !isFileFieldType(field.type) ? (_jsx(Text, { testId: `field-value-${field.field}`, children: readOnlyDisplayText(field, appLocale, t) })) : field.type === "text" && field.writeOnly === true && !readOnlyText ? (_jsx(WriteOnlyTextInput, { field: field, id: id, hasError: hasError, onChange: onChange, changed: changed === true })) : field.fillAction !== undefined ? (_jsx(FillActionControl, { field: field, fillAction: field.fillAction, row: row ?? { [field.field]: field.value }, onChange: onChange, disabled: fillDisabled === true, Banner: Banner, renderControl: (fill) => renderInput({
|
|
54
|
+
field,
|
|
55
|
+
id,
|
|
56
|
+
hasError,
|
|
57
|
+
onChange,
|
|
58
|
+
Input,
|
|
59
|
+
appLocale,
|
|
60
|
+
Banner,
|
|
61
|
+
Text,
|
|
62
|
+
JsonView,
|
|
63
|
+
t,
|
|
64
|
+
row,
|
|
65
|
+
fill,
|
|
66
|
+
}) })) : (renderInput({
|
|
52
67
|
field,
|
|
53
68
|
id,
|
|
54
69
|
hasError,
|
|
@@ -63,6 +78,63 @@ export function RenderField({ field, issues, onChange, featureName, labelAppendi
|
|
|
63
78
|
}));
|
|
64
79
|
return (_jsx(Field, { id: id, label: field.label, required: field.required && field.type !== "boolean" && !allFieldsRequired, ...(issues !== undefined && { issues }), ...(labelAppendix !== undefined && { labelAppendix }), ...(fieldAppendix !== undefined && { fieldAppendix }), ...(appendixPlacement !== undefined && { appendixPlacement }), ...(description !== undefined && { description }), ...(status !== undefined && { status }), ...(changed === true && { changed }), ...(layout !== undefined && { layout }), ...(accent === true && { accent }), testId: `field-${field.field}`, children: control }));
|
|
65
80
|
}
|
|
81
|
+
const NUMERIC_FIELD_TYPES = new Set(["number", "bigInt", "decimal"]);
|
|
82
|
+
// A text field takes a number as its string form; a numeric field takes only a
|
|
83
|
+
// number — a string would be sent as a wrong-typed value.
|
|
84
|
+
function fillValueFor(fieldType, result) {
|
|
85
|
+
if (NUMERIC_FIELD_TYPES.has(fieldType))
|
|
86
|
+
return typeof result === "number" ? result : undefined;
|
|
87
|
+
if (typeof result === "string")
|
|
88
|
+
return result;
|
|
89
|
+
return typeof result === "number" ? String(result) : undefined;
|
|
90
|
+
}
|
|
91
|
+
// Owns the query state of a fill button. A separate component because RenderField
|
|
92
|
+
// returns early for hidden fields and the dispatcher hook must not be conditional.
|
|
93
|
+
function FillActionControl({ field, fillAction, row, onChange, disabled, Banner, renderControl, }) {
|
|
94
|
+
const dispatcher = useOptionalDispatcher();
|
|
95
|
+
const t = useTranslation();
|
|
96
|
+
const [busy, setBusy] = useState(false);
|
|
97
|
+
const [failed, setFailed] = useState(false);
|
|
98
|
+
// A newer click or an unmount cancels the in-flight query so a late answer
|
|
99
|
+
// cannot overwrite a value the user has since typed.
|
|
100
|
+
const activeRun = useRef(null);
|
|
101
|
+
useEffect(() => () => activeRun.current?.abort(), []);
|
|
102
|
+
const run = async () => {
|
|
103
|
+
if (dispatcher === undefined) {
|
|
104
|
+
setFailed(true);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
activeRun.current?.abort();
|
|
108
|
+
const ctrl = new AbortController();
|
|
109
|
+
activeRun.current = ctrl;
|
|
110
|
+
setBusy(true);
|
|
111
|
+
setFailed(false);
|
|
112
|
+
const payload = fillAction.params === undefined ? {} : evalRowExtractor(fillAction.params, row);
|
|
113
|
+
const result = await dispatcher.query(fillAction.query, payload, {
|
|
114
|
+
signal: ctrl.signal,
|
|
115
|
+
});
|
|
116
|
+
if (ctrl.signal.aborted)
|
|
117
|
+
return;
|
|
118
|
+
setBusy(false);
|
|
119
|
+
if (!result.isSuccess) {
|
|
120
|
+
setFailed(true);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const value = fillValueFor(field.type, propertyOf(result.data, fillAction.field));
|
|
124
|
+
if (value === undefined) {
|
|
125
|
+
setFailed(true);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
onChange(value);
|
|
129
|
+
};
|
|
130
|
+
return (_jsxs(_Fragment, { children: [renderControl({
|
|
131
|
+
label: fillAction.label,
|
|
132
|
+
onPress: () => void run(),
|
|
133
|
+
busy,
|
|
134
|
+
disabled,
|
|
135
|
+
testId: `field-fill-${field.field}`,
|
|
136
|
+
}), failed && (_jsx(Banner, { variant: "error", testId: `field-fill-error-${field.field}`, children: t("kumiko.field.fillAction.failed") }))] }));
|
|
137
|
+
}
|
|
66
138
|
// Wire value of a writeOnly field is `true` (set) / `null` (empty), never the
|
|
67
139
|
// secret. Typing sets a new value; an untouched or emptied input submits
|
|
68
140
|
// nothing / "" which the server reads as "keep". `null` after a loaded `true`
|
|
@@ -330,7 +402,7 @@ function resolveFieldIcon(field) {
|
|
|
330
402
|
// SelectFieldDef.options). Structural types without a widget (embedded,
|
|
331
403
|
// jsonb, multiSelect) render read-only instead of a data-destroying text
|
|
332
404
|
// fallback (#1834); truly unknown scalar types still fall back to text.
|
|
333
|
-
function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner, Text, JsonView, t, row, }) {
|
|
405
|
+
function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner, Text, JsonView, t, row, fill, }) {
|
|
334
406
|
const common = {
|
|
335
407
|
id,
|
|
336
408
|
name: field.field,
|
|
@@ -338,6 +410,14 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
|
|
|
338
410
|
required: field.required,
|
|
339
411
|
hasError,
|
|
340
412
|
};
|
|
413
|
+
const placeholderProp = field.placeholder !== undefined ? { placeholder: field.placeholder } : {};
|
|
414
|
+
const prefix = resolveAffix(field.prefix, row);
|
|
415
|
+
const suffix = resolveAffix(field.suffix, row);
|
|
416
|
+
const affixProps = {
|
|
417
|
+
...(prefix !== undefined && { prefix }),
|
|
418
|
+
...(suffix !== undefined && { suffix }),
|
|
419
|
+
...(fill !== undefined && { fill }),
|
|
420
|
+
};
|
|
341
421
|
switch (field.type) {
|
|
342
422
|
// decimal/bigInt are both plain numbers on the wire (fieldToZod:
|
|
343
423
|
// z.number() / z.number().int().safe()) — the number input's onChange
|
|
@@ -347,11 +427,11 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
|
|
|
347
427
|
case "bigInt": {
|
|
348
428
|
const icon = resolveFieldIcon(field);
|
|
349
429
|
const unit = resolveNumberUnit(field.unit, row);
|
|
350
|
-
return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...(field.type === "bigInt" && { integer: true }), ...(icon !== undefined && { icon }), ...(unit !== undefined && { unit }) }));
|
|
430
|
+
return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...(field.type === "bigInt" && { integer: true }), ...placeholderProp, ...affixProps, ...(icon !== undefined && { icon }), ...(unit !== undefined && { unit }) }));
|
|
351
431
|
}
|
|
352
432
|
case "decimal": {
|
|
353
433
|
const icon = resolveFieldIcon(field);
|
|
354
|
-
return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...(icon !== undefined && { icon }) }));
|
|
434
|
+
return (_jsx(Input, { kind: "number", ...common, value: numberValue(field.value), onChange: (v) => onChange(v), locale: appLocale, ...(field.grouping !== undefined && { grouping: field.grouping }), ...placeholderProp, ...affixProps, ...(icon !== undefined && { icon }) }));
|
|
355
435
|
}
|
|
356
436
|
case "tz":
|
|
357
437
|
return (_jsx(Input, { kind: "tz", ...common, value: stringValue(field.value), onChange: (v) => onChange(v) }));
|
|
@@ -421,10 +501,10 @@ function renderInput({ field, id, hasError, onChange, Input, appLocale, Banner,
|
|
|
421
501
|
// only supplies an optional `{ rows }` override for it (#1925).
|
|
422
502
|
if (field.type === "longText" || (field.type === "text" && field.multiline)) {
|
|
423
503
|
const rows = typeof field.multiline === "object" ? field.multiline.rows : undefined;
|
|
424
|
-
return (_jsx(Input, { kind: "textarea", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...(rows !== undefined && { rows }) }));
|
|
504
|
+
return (_jsx(Input, { kind: "textarea", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...(rows !== undefined && { rows }), ...placeholderProp }));
|
|
425
505
|
}
|
|
426
506
|
const icon = resolveFieldIcon(field);
|
|
427
|
-
return (_jsx(Input, { kind: "text", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...(icon !== undefined && { icon }), ...(field.autoComplete !== undefined && { autoComplete: field.autoComplete }) }));
|
|
507
|
+
return (_jsx(Input, { kind: "text", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), ...placeholderProp, ...affixProps, ...(icon !== undefined && { icon }), ...(field.autoComplete !== undefined && { autoComplete: field.autoComplete }) }));
|
|
428
508
|
}
|
|
429
509
|
}
|
|
430
510
|
}
|
|
@@ -479,6 +559,17 @@ function resolveNumberUnit(unit, row) {
|
|
|
479
559
|
const trimmed = sibling.trim();
|
|
480
560
|
return trimmed.length > 0 && trimmed.length <= MAX_SIBLING_UNIT_LENGTH ? trimmed : undefined;
|
|
481
561
|
}
|
|
562
|
+
// String affix → used as-is (the view model already translated it). Field
|
|
563
|
+
// affix → the live form value; missing, empty or non-scalar means no affix.
|
|
564
|
+
function resolveAffix(affix, row) {
|
|
565
|
+
if (affix === undefined)
|
|
566
|
+
return undefined;
|
|
567
|
+
if (typeof affix === "string")
|
|
568
|
+
return affix;
|
|
569
|
+
const value = row?.[affix.field];
|
|
570
|
+
const text = typeof value === "number" ? String(value) : value;
|
|
571
|
+
return typeof text === "string" && text.trim().length > 0 ? text.trim() : undefined;
|
|
572
|
+
}
|
|
482
573
|
function stringArrayOf(value) {
|
|
483
574
|
return Array.isArray(value)
|
|
484
575
|
? value.filter((item) => typeof item === "string")
|
package/dist/i18n-defaults.js
CHANGED
|
@@ -14,6 +14,7 @@ export const kumikoDefaultTranslations = {
|
|
|
14
14
|
"kumiko.actions.delete": "Delete",
|
|
15
15
|
"kumiko.actions.delete-confirm": "Confirm delete?",
|
|
16
16
|
"kumiko.actions.reload": "Reload",
|
|
17
|
+
"kumiko.actionForm.success": "Done.",
|
|
17
18
|
"kumiko.actions.retry": "Try again",
|
|
18
19
|
"kumiko.actions.create": "New",
|
|
19
20
|
"kumiko.actions.edit": "Edit",
|
|
@@ -39,6 +40,7 @@ export const kumikoDefaultTranslations = {
|
|
|
39
40
|
"kumiko.field.locatedTzHint": "Time local to the given location",
|
|
40
41
|
"kumiko.field.reference-created-no-id": "Record was created but could not be selected automatically. Please select it manually.",
|
|
41
42
|
"kumiko.field.unsupported": "This field type can't be edited here yet.",
|
|
43
|
+
"kumiko.field.fillAction.failed": "Could not fetch a suggestion.",
|
|
42
44
|
"kumiko.field.writeOnly.remove": "Remove stored value",
|
|
43
45
|
"kumiko.field.writeOnly.set": "Set",
|
|
44
46
|
"kumiko.pii.erased": "Deleted",
|
|
@@ -128,6 +130,8 @@ export const kumikoDefaultTranslations = {
|
|
|
128
130
|
"kumiko.dashboard.panel.error.isolated": "The other panels are not affected.",
|
|
129
131
|
"kumiko.dashboard.panel.error.retry": "Try again",
|
|
130
132
|
"kumiko.dashboard.panel.error.title": "{label} could not be loaded",
|
|
133
|
+
"kumiko.dashboard.progress.unlimited": "unlimited",
|
|
134
|
+
"kumiko.dashboard.record.missing": "Open this page from a record to see its dashboard.",
|
|
131
135
|
"kumiko.dashboard.time-range": "Time range",
|
|
132
136
|
"kumiko.screen.unavailable": "This page is not available right now.",
|
|
133
137
|
"kumiko.dashboard.brush-end": "Window end",
|
|
@@ -192,6 +196,9 @@ export const kumikoDefaultTranslations = {
|
|
|
192
196
|
"kumiko.aiText.style.expand": "Expand",
|
|
193
197
|
"kumiko.aiText.capExceeded": "Monthly AI limit reached.",
|
|
194
198
|
"kumiko.rowAction.failed": "Action failed",
|
|
199
|
+
"kumiko.clientAction.success": "Done.",
|
|
200
|
+
"kumiko.clientAction.unregistered": "This action is not available in this app.",
|
|
201
|
+
"kumiko.clientAction.failed": "The action could not be completed.",
|
|
195
202
|
"kumiko.contentEditor.insertVariable": "Insert {name}",
|
|
196
203
|
"kumiko.contentEditor.preview": "Preview",
|
|
197
204
|
"kumiko.contentEditor.editMode": "Edit",
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export { type Formality, formalLocaleTag } from "@cosmicdrift/kumiko-framework/u
|
|
|
2
2
|
export { synthesizeActionFormEntity, synthesizeActionFormScreen } from "./app/action-form-shim.ts";
|
|
3
3
|
export type { AppFeaturesProviderProps } from "./app/app-features-context.tsx";
|
|
4
4
|
export { AppFeaturesProvider, useAppFeatures } from "./app/app-features-context.tsx";
|
|
5
|
+
export type { ActionNotice, ActionNotifier, ClientActionHandler, ClientActionOutcome, ClientActionRuntime, ClientActionsMap, } from "./app/client-actions.tsx";
|
|
6
|
+
export { ActionNoticeProvider, ClientActionsProvider, runClientAction, useClientActionHandler, useClientActionRuntime, } from "./app/client-actions.tsx";
|
|
5
7
|
export type { ColumnRendererComponent, ColumnRendererProps, ColumnRenderersMap, ColumnRenderersProviderProps, } from "./app/column-renderers.tsx";
|
|
6
8
|
export { ColumnRenderersProvider, useColumnRenderer } from "./app/column-renderers.tsx";
|
|
7
9
|
export type { ContentEditorComponent, ContentEditorProps, ContentEditorsMap, ContentEditorsProviderProps, } from "./app/content-editors.tsx";
|
|
@@ -18,6 +20,7 @@ export type { ExtensionSectionComponent, ExtensionSectionProps, ExtensionSection
|
|
|
18
20
|
export { ExtensionSectionsProvider, extensionSectionName, useExtensionSectionComponent, } from "./app/extension-sections.tsx";
|
|
19
21
|
export type { AppSchema, FeatureSchema, QualifiedContentCollection, WorkspaceSchema, } from "./app/feature-schema.ts";
|
|
20
22
|
export { isAppSchema, toAppSchema } from "./app/feature-schema.ts";
|
|
23
|
+
export { HeaderActionsBar } from "./app/header-actions-bar.tsx";
|
|
21
24
|
export type { KumikoScreenProps } from "./app/kumiko-screen.tsx";
|
|
22
25
|
export { KumikoScreen, qualifyNavId, qualifyScreenId, ScreenMissingState, } from "./app/kumiko-screen.tsx";
|
|
23
26
|
export type { DateRangeBound, DateRangeValue, ResolvedDateRangeFacet } from "./app/list-facets.ts";
|
|
@@ -29,6 +32,7 @@ export type { ReturnHost, ReturnTo } from "./app/return-to.tsx";
|
|
|
29
32
|
export { navigateToReturn, navigateToReturnOr, navigateWithReturnTo, RETURN_TO_PARAM, ReturnHostProvider, resolveReturnTarget, splitReturnTo, useReturnHost, useReturnTarget, } from "./app/return-to.tsx";
|
|
30
33
|
export type { EmbeddedScreenTarget } from "./app/use-embedded-screen.ts";
|
|
31
34
|
export { useEmbeddedScreen } from "./app/use-embedded-screen.ts";
|
|
35
|
+
export { useRecordActions } from "./app/use-record-actions.ts";
|
|
32
36
|
export type { VariableChipsProps } from "./app/variable-chips.tsx";
|
|
33
37
|
export { VariableChips } from "./app/variable-chips.tsx";
|
|
34
38
|
export { evalVisibleWhen, type VisibleWhenVerdict } from "./app/visible-when.ts";
|
|
@@ -75,7 +79,7 @@ export { createStaticLocaleResolver, FormalityProvider, LocaleProvider, mergeTra
|
|
|
75
79
|
export { kumikoDefaultTranslations } from "./i18n-defaults.ts";
|
|
76
80
|
export { InsideDrawerProvider, useInsideDrawer } from "./inside-drawer.ts";
|
|
77
81
|
export { PageHeaderCompactProvider, PageHeaderSlotAvailableProvider, usePageHeaderCompact, usePageHeaderSlotAvailable, } from "./page-header-slot.ts";
|
|
78
|
-
export type { ActionMenuItemSpec, ActionOverflowMenuProps, AppPrimitives, BannerProps, ButtonProps, CardOptions, CardProps, CardSlots, CopyButtonProps, CorePrimitives, DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, DataTableProps, DataTableRowAction, DataTableRowActionMode, DataTableRowGrouping, DataTableSort, DataTableSortDir, DialogProps, DrawerProps, EmbeddedListCellType, EmbeddedListColumn, EmbeddedListInputProps, EmbeddedListTotal, FieldCellWidth, FieldProps, FillContainerProps, FooterActionMarker, FooterActionRole, FormProps, FormSectionNavItem, FormWidth, GridCellProps, GridProps, HeadingProps, InputProps, JsonViewProps, LightboxImage, LightboxProps, LinkProps, MetricBandProps, MetricProps, ModalProps, PageHeaderProps, PickedFile, PrimitivesProviderProps, PrimitivesRegistry, ProgressProps, ProgressTone, PromoPanelAction, PromoPanelProps, RuntimeRenderer, SecretRevealProps, SecretRevealValue, SectionProps, ShareButtonProps, StatusBadgeProps, StatusTone, StepBarProps, StickyActionBarProps, StickyPrimaryActionMarker, TabsProps, TextProps, TrailingInputAction, WizardStepGroupProps, } from "./primitives.tsx";
|
|
82
|
+
export type { ActionMenuItemSpec, ActionOverflowMenuProps, AppPrimitives, BannerProps, ButtonProps, CardOptions, CardProps, CardSlots, CopyButtonProps, CorePrimitives, DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, DataTableProps, DataTableRowAction, DataTableRowActionMode, DataTableRowGrouping, DataTableSort, DataTableSortDir, DialogProps, DrawerProps, EmbeddedListCellType, EmbeddedListColumn, EmbeddedListInputProps, EmbeddedListTotal, FieldCellWidth, FieldProps, FillContainerProps, FooterActionMarker, FooterActionRole, FormProps, FormSectionNavItem, FormWidth, GridCellProps, GridProps, HeadingProps, InputFillAction, InputProps, JsonViewProps, LightboxImage, LightboxProps, LinkProps, MetricBandProps, MetricProps, ModalProps, PageHeaderProps, PickedFile, PrimitivesProviderProps, PrimitivesRegistry, ProgressProps, ProgressTone, PromoPanelAction, PromoPanelProps, RuntimeRenderer, SecretRevealProps, SecretRevealValue, SectionProps, ShareButtonProps, StatusBadgeProps, StatusTone, StepBarProps, StickyActionBarProps, StickyPrimaryActionMarker, TabsProps, TextProps, TrailingInputAction, WizardStepGroupProps, } from "./primitives.tsx";
|
|
79
83
|
export { FOOTER_ACTION_ROLE_PROP, NARROW_LABEL_PROP, PrimitivesProvider, STICKY_PRIMARY_ACTION_PROP, shouldRenderActionsIconOnly, statusToneForOptionTone, statusToneForValue, useOptionalPrimitives, usePrimitives, } from "./primitives.tsx";
|
|
80
84
|
export type { SessionEndedSignal, SessionEndedSignalProviderProps, } from "./session/session-ended.tsx";
|
|
81
85
|
export { createSessionEndedSignal, isSessionEndedError, SESSION_ENDED_ERROR_CODES, SessionEndedSignalProvider, useSessionEndedSignal, withSessionEndedDetection, } from "./session/session-ended.tsx";
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
export { formalLocaleTag } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
12
12
|
export { synthesizeActionFormEntity, synthesizeActionFormScreen } from "./app/action-form-shim.js";
|
|
13
13
|
export { AppFeaturesProvider, useAppFeatures } from "./app/app-features-context.js";
|
|
14
|
+
export { ActionNoticeProvider, ClientActionsProvider, runClientAction, useClientActionHandler, useClientActionRuntime, } from "./app/client-actions.js";
|
|
14
15
|
export { ColumnRenderersProvider, useColumnRenderer } from "./app/column-renderers.js";
|
|
15
16
|
export { CONTENT_EDITOR_ELEMENT_ID, ContentEditorsProvider, TextareaContentEditor, useContentEditor, } from "./app/content-editors.js";
|
|
16
17
|
export { ContentPreview, substituteVariables } from "./app/content-preview.js";
|
|
@@ -19,12 +20,14 @@ export { DashboardBodyProvider, useDashboardBody } from "./app/dashboard-body.js
|
|
|
19
20
|
export { ExtensionFormRegistryProvider, useExtensionFormSubmit, } from "./app/extension-form-submit.js";
|
|
20
21
|
export { ExtensionSectionsProvider, extensionSectionName, useExtensionSectionComponent, } from "./app/extension-sections.js";
|
|
21
22
|
export { isAppSchema, toAppSchema } from "./app/feature-schema.js";
|
|
23
|
+
export { HeaderActionsBar } from "./app/header-actions-bar.js";
|
|
22
24
|
export { KumikoScreen, qualifyNavId, qualifyScreenId, ScreenMissingState, } from "./app/kumiko-screen.js";
|
|
23
25
|
export { buildDateRangePayload, clampDateRange, readDateRange, resolveDateRangeFacets, } from "./app/list-facets.js";
|
|
24
26
|
export { formatPath, hasDetailScreen, NavProvider, parsePath, resolveTarget, useNav, useNavigateWithInitialValues, useOptionalNav, } from "./app/nav.js";
|
|
25
27
|
export { lastSegment } from "./app/qn.js";
|
|
26
28
|
export { navigateToReturn, navigateToReturnOr, navigateWithReturnTo, RETURN_TO_PARAM, ReturnHostProvider, resolveReturnTarget, splitReturnTo, useReturnHost, useReturnTarget, } from "./app/return-to.js";
|
|
27
29
|
export { useEmbeddedScreen } from "./app/use-embedded-screen.js";
|
|
30
|
+
export { useRecordActions } from "./app/use-record-actions.js";
|
|
28
31
|
export { VariableChips } from "./app/variable-chips.js";
|
|
29
32
|
export { evalVisibleWhen } from "./app/visible-when.js";
|
|
30
33
|
export { dispatcherErrorText, WriteFailedError } from "./app/write-failed-error.js";
|
package/dist/primitives.d.ts
CHANGED
|
@@ -174,6 +174,17 @@ export type FieldProps = {
|
|
|
174
174
|
/** Discriminated union — jede Input-Sorte hat ihre eigene value/onChange
|
|
175
175
|
* Signatur. Custom-Impls dispatchen intern, rendern anders (Toggle
|
|
176
176
|
* statt Checkbox), oder nur einzelne kinds unterschiedlich. */
|
|
177
|
+
/** Button beside a single-line text/number input that fills it with a
|
|
178
|
+
* suggestion. Stays usable on a `disabled` input (the readOnly+fill case). */
|
|
179
|
+
export type InputFillAction = {
|
|
180
|
+
readonly label: string;
|
|
181
|
+
readonly onPress: () => void;
|
|
182
|
+
/** A suggestion is being fetched: the button shows a spinner and is inert. */
|
|
183
|
+
readonly busy?: boolean;
|
|
184
|
+
/** The surrounding form is locked as a whole. */
|
|
185
|
+
readonly disabled?: boolean;
|
|
186
|
+
readonly testId?: string;
|
|
187
|
+
};
|
|
177
188
|
export type TrailingInputAction = {
|
|
178
189
|
readonly icon: "clear" | "undo";
|
|
179
190
|
readonly label: string;
|
|
@@ -211,6 +222,10 @@ export type InputProps = {
|
|
|
211
222
|
readonly readOnly?: boolean;
|
|
212
223
|
/** Closed FieldIconKey vocabulary (FIELD_ICONS registry, renderer-web). */
|
|
213
224
|
readonly icon?: FieldIconKey;
|
|
225
|
+
/** Resolved inline text before/after the input (EditFieldSpec.prefix/suffix). */
|
|
226
|
+
readonly prefix?: string;
|
|
227
|
+
readonly suffix?: string;
|
|
228
|
+
readonly fill?: InputFillAction;
|
|
214
229
|
readonly dataAttributes?: DataAttributes;
|
|
215
230
|
/** Raw keydown access (e.g. Enter-to-submit at the field instead of
|
|
216
231
|
* the surrounding Form). Web forwards it, native impls ignore it
|
|
@@ -276,6 +291,9 @@ export type InputProps = {
|
|
|
276
291
|
/** Resolved display suffix (static or from a sibling field) — never
|
|
277
292
|
* part of the numeric value. */
|
|
278
293
|
readonly unit?: string;
|
|
294
|
+
readonly prefix?: string;
|
|
295
|
+
readonly suffix?: string;
|
|
296
|
+
readonly fill?: InputFillAction;
|
|
279
297
|
} | {
|
|
280
298
|
readonly kind: "range";
|
|
281
299
|
readonly id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.374.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.374.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.374.0",
|
|
20
|
+
"@cosmicdrift/kumiko-types": "0.374.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.374.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.374.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Success box with links, form reset and field placeholder",
|
|
6
|
+
"detail": "The actionForm success box shows the default text \"Done.\" (kumiko.actionForm.success) next to successMessage links, resetOnSuccess remounts the form with its initial values, and EditFieldSpec.placeholder reaches text, number and textarea inputs."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.374.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "Client action registry for row actions",
|
|
12
|
+
"detail": "ClientActionsProvider, ActionNoticeProvider and runClientAction execute clientAction row actions on list, related list and record actions, with confirm, toast feedback and refetch."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.374.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "actionForm load states and drawer row click",
|
|
18
|
+
"detail": "An actionForm with load queries by the route, drawer or URL id and shows loading, error, not-found and missing-id states; loaded values count as prefill for submitPrefilled. A row click fires a rowClick drawer action in entityList, projectionList and relatedList."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.374.0",
|
|
22
|
+
"type": "improvement",
|
|
23
|
+
"title": "Input prefix, suffix and fill button",
|
|
24
|
+
"detail": "Text and number inputs show prefix and suffix text, and a fillAction renders a button that fetches a suggestion into the field, also for a readOnly field. The Input primitive takes prefix, suffix and fill; renderer-web draws them beside the input."
|
|
25
|
+
},
|
|
2
26
|
{
|
|
3
27
|
"version": "0.372.0",
|
|
4
28
|
"type": "fix",
|