@cosmicdrift/kumiko-renderer 0.384.0 → 0.386.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-body.d.ts +50 -0
- package/dist/app/action-form-body.js +275 -0
- package/dist/app/client-actions.js +2 -2
- package/dist/app/config-edit-body.d.ts +9 -0
- package/dist/app/config-edit-body.js +340 -0
- package/dist/app/entity-edit-prefill.d.ts +27 -0
- package/dist/app/entity-edit-prefill.js +377 -0
- package/dist/app/entity-edit-screen.d.ts +11 -0
- package/dist/app/entity-edit-screen.js +395 -0
- package/dist/app/entity-list-screen.d.ts +10 -0
- package/dist/app/entity-list-screen.js +547 -0
- package/dist/app/kumiko-screen.d.ts +1 -36
- package/dist/app/kumiko-screen.js +16 -2855
- package/dist/app/projection-detail-body.d.ts +12 -0
- package/dist/app/projection-detail-body.js +464 -0
- package/dist/app/projection-detail-shim.d.ts +1 -1
- package/dist/app/projection-detail-shim.js +5 -1
- package/dist/app/projection-list-body.d.ts +10 -0
- package/dist/app/projection-list-body.js +245 -0
- package/dist/app/projection-list-shim.d.ts +2 -1
- package/dist/app/projection-list-shim.js +4 -0
- package/dist/app/reference-facet-bridge.js +2 -2
- package/dist/app/row-actions.js +3 -4
- package/dist/app/screen-shared.d.ts +72 -0
- package/dist/app/screen-shared.js +325 -0
- package/dist/app/secret-mint-body.js +3 -3
- package/dist/components/edit-mounts.d.ts +33 -0
- package/dist/components/edit-mounts.js +53 -0
- package/dist/components/error-boundary.js +2 -2
- package/dist/components/reference-create-dialog.js +1 -1
- package/dist/components/related-list-section.js +29 -6
- package/dist/components/render-edit-helpers.d.ts +29 -0
- package/dist/components/render-edit-helpers.js +121 -0
- package/dist/components/render-edit.d.ts +1 -4
- package/dist/components/render-edit.js +41 -443
- package/dist/components/render-field.js +3 -4
- package/dist/components/render-list.d.ts +1 -1
- package/dist/components/render-list.js +2 -2
- package/dist/components/use-wizard-form-draft.d.ts +51 -0
- package/dist/components/use-wizard-form-draft.js +291 -0
- package/dist/dev-console.d.ts +3 -0
- package/dist/dev-console.js +15 -0
- package/dist/headless/contracts/primitives.js +14 -17
- package/dist/headless/format/index.js +3 -3
- package/dist/headless/view-model/edit.d.ts +1 -1
- package/dist/headless/view-model/edit.js +9 -16
- package/dist/headless/view-model/list.d.ts +1 -1
- package/dist/headless/view-model/list.js +3 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +5 -5
- package/dist/live/error-mapping.js +3 -4
- package/dist/web/app/create-app.js +5 -8
- package/dist/web/app/dashboard-body.js +4 -599
- package/dist/web/app/dashboard-panels.d.ts +53 -0
- package/dist/web/app/dashboard-panels.js +373 -0
- package/dist/web/app/dashboard-shared.d.ts +90 -0
- package/dist/web/app/dashboard-shared.js +171 -0
- package/dist/web/app/dashboard-text.d.ts +6 -0
- package/dist/web/app/dashboard-text.js +65 -0
- package/dist/web/layout/nav-tree.js +3 -4
- package/dist/web/layout/target-resolver-stub.js +2 -2
- package/dist/web/primitives/banner.d.ts +4 -0
- package/dist/web/primitives/banner.js +28 -0
- package/dist/web/primitives/button.d.ts +7 -0
- package/dist/web/primitives/button.js +48 -0
- package/dist/web/primitives/card-styles.d.ts +8 -0
- package/dist/web/primitives/card-styles.js +18 -0
- package/dist/web/primitives/card.d.ts +4 -0
- package/dist/web/primitives/card.js +41 -0
- package/dist/web/primitives/contexts.d.ts +22 -0
- package/dist/web/primitives/contexts.js +50 -0
- package/dist/web/primitives/data-table-cell.d.ts +76 -0
- package/dist/web/primitives/data-table-cell.js +403 -0
- package/dist/web/primitives/data-table.d.ts +4 -0
- package/dist/web/primitives/data-table.js +279 -0
- package/dist/web/primitives/default-form.d.ts +4 -0
- package/dist/web/primitives/default-form.js +79 -0
- package/dist/web/primitives/facet-filter.d.ts +14 -0
- package/dist/web/primitives/facet-filter.js +39 -0
- package/dist/web/primitives/field.d.ts +63 -0
- package/dist/web/primitives/field.js +181 -0
- package/dist/web/primitives/form.d.ts +53 -0
- package/dist/web/primitives/form.js +252 -0
- package/dist/web/primitives/index.d.ts +7 -30
- package/dist/web/primitives/index.js +27 -2006
- package/dist/web/primitives/input.d.ts +4 -0
- package/dist/web/primitives/input.js +166 -0
- package/dist/web/primitives/layout-elements.d.ts +14 -0
- package/dist/web/primitives/layout-elements.js +194 -0
- package/dist/web/primitives/modal.js +1 -1
- package/dist/web/primitives/pager.d.ts +22 -0
- package/dist/web/primitives/pager.js +89 -0
- package/dist/web/primitives/row-actions.d.ts +13 -0
- package/dist/web/primitives/row-actions.js +178 -0
- package/dist/web/primitives/secret-reveal.d.ts +4 -0
- package/dist/web/primitives/secret-reveal.js +67 -0
- package/package.json +5 -5
- package/src/changes.json +6 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useMemo, useState } from "react";
|
|
3
|
+
import { RenderList } from "../components/render-list.js";
|
|
4
|
+
import { useOptionalDispatcher } from "../context/dispatcher-context.js";
|
|
5
|
+
import { useIsEmbeddedScreen } from "../context/embedded-screen-context.js";
|
|
6
|
+
import { useUserRoles } from "../context/user-roles-context.js";
|
|
7
|
+
import { useListUrlState } from "../hooks/use-list-url-state.js";
|
|
8
|
+
import { useQuery } from "../hooks/use-query.js";
|
|
9
|
+
import { useOptionalTimeZone, useTranslation } from "../i18n.js";
|
|
10
|
+
import { usePrimitives, } from "../primitives.js";
|
|
11
|
+
import { screenFillsHeight } from "../screen-fills-height.js";
|
|
12
|
+
import { DrawerHost, useDrawerAction } from "./action-form-body.js";
|
|
13
|
+
import { useAppFeatures } from "./app-features-context.js";
|
|
14
|
+
import { useClientActionRuntime } from "./client-actions.js";
|
|
15
|
+
import { isListNarrowed, useListEmptyContent } from "./list-empty-content.js";
|
|
16
|
+
import { buildDateRangePayload, buildFilterFacets, buildFilterPayload, clampDateRange, mergeReferenceFacetOptions, readDateRange, resolveDateRangeFacets, resolveProjectionFacetSpecs, } from "./list-facets.js";
|
|
17
|
+
import { useNav } from "./nav.js";
|
|
18
|
+
import { synthesizeProjectionEntity, synthesizeProjectionScreen } from "./projection-list-shim.js";
|
|
19
|
+
import { QueryErrorBanner, QueryLoadingBanner } from "./query-state-banners.js";
|
|
20
|
+
import { ReferenceFacetBridges } from "./reference-facet-bridge.js";
|
|
21
|
+
import { useReturnHost } from "./return-to.js";
|
|
22
|
+
import { buildDefaultEditRowAction, buildProjectionRowActions, buildProjectionToolbarActions, findRowClickAction, runDrawerRowAction, runProjectionRowNavigate, } from "./row-actions.js";
|
|
23
|
+
import { findEditScreenFor } from "./screen-access.js";
|
|
24
|
+
import { buildListQueryPayload, pageSizeOptionsFor } from "./screen-shared.js";
|
|
25
|
+
// ---- projection-list ----
|
|
26
|
+
// Like entityList, but the list query comes DIRECTLY from `screen.query`
|
|
27
|
+
// (instead of being derived from an entity) — cross-feature capable.
|
|
28
|
+
// search/sort/pagination reuse the same URL-state + buildListQueryPayload
|
|
29
|
+
// wiring as EntityListBody; which of them are actually offered is derived
|
|
30
|
+
// from the query handler's Zod schema at buildAppSchema time (fw#2165), not
|
|
31
|
+
// declared here. Pages-mode pagination only — infinite-scroll accumulation
|
|
32
|
+
// isn't wired for projectionList.
|
|
33
|
+
export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
34
|
+
const isEmbeddedScreen = useIsEmbeddedScreen();
|
|
35
|
+
const { Banner, Text } = usePrimitives();
|
|
36
|
+
const t = useTranslation();
|
|
37
|
+
const nav = useNav();
|
|
38
|
+
const host = useReturnHost();
|
|
39
|
+
const dispatcher = useOptionalDispatcher();
|
|
40
|
+
const clientActions = useClientActionRuntime();
|
|
41
|
+
const effectiveTranslate = translate ?? t;
|
|
42
|
+
const userRoles = useUserRoles();
|
|
43
|
+
const appFeatures = useAppFeatures();
|
|
44
|
+
const { drawerAction, drawerScreen, drawerInitialValues, openDrawer, closeDrawer } = useDrawerAction(schema);
|
|
45
|
+
const defaultEditScreen = useMemo(() => {
|
|
46
|
+
const detailFor = screen.detailFor;
|
|
47
|
+
if (detailFor === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
return findEditScreenFor(detailFor, appFeatures, userRoles);
|
|
50
|
+
}, [appFeatures, screen.detailFor, userRoles]);
|
|
51
|
+
// searchable/sortable/paginated are derived at buildAppSchema time from the
|
|
52
|
+
// query handler's Zod schema (fw#2165) — not authored on the screen.
|
|
53
|
+
const searchable = screen.searchable ?? false;
|
|
54
|
+
const sortable = screen.sortable ?? false;
|
|
55
|
+
const paginated = screen.paginated ?? false;
|
|
56
|
+
const entity = useMemo(() => synthesizeProjectionEntity(screen.columns, sortable), [screen.columns, sortable]);
|
|
57
|
+
const listScreen = useMemo(() => synthesizeProjectionScreen(screen), [screen]);
|
|
58
|
+
const urlState = useListUrlState(screen.id);
|
|
59
|
+
// Gated on the derived capability, not just on whether URL-state happens to
|
|
60
|
+
// carry a value — a stale/hand-crafted URL (?…q=x on a non-searchable
|
|
61
|
+
// screen) must not smuggle a param the query's Zod schema doesn't accept.
|
|
62
|
+
const activeSearch = searchable ? urlState.q : "";
|
|
63
|
+
// Same predicate that makes a header clickable: a stale link onto a
|
|
64
|
+
// sortable:false/unknown column must not reach the server (it answers 400).
|
|
65
|
+
const urlSort = urlState.sort;
|
|
66
|
+
const urlSortField = urlSort === null ? undefined : entity.fields[urlSort.field];
|
|
67
|
+
const urlSortAllowed = urlSortField !== undefined && "sortable" in urlSortField && urlSortField.sortable === true;
|
|
68
|
+
const activeSort = sortable
|
|
69
|
+
? ((urlSortAllowed ? urlSort : null) ?? screen.defaultSort ?? null)
|
|
70
|
+
: null;
|
|
71
|
+
const limit = urlState.pageSize ?? screen.pageSize ?? 50;
|
|
72
|
+
// Pages-mode only (see header comment) — an author-set pagination:
|
|
73
|
+
// "infinite" on a paginated projectionList is silently a no-op today.
|
|
74
|
+
const usePager = paginated && (screen.pagination ?? "pages") === "pages";
|
|
75
|
+
// Facets (fw#2224) — a projectionList has no entity, so screen.facets is
|
|
76
|
+
// the only field inventory; resolveProjectionFacetSpecs reshapes +
|
|
77
|
+
// translates its explicit labels into the same ResolvedFacetSpec the
|
|
78
|
+
// entityList adapter produces, so both feed the same
|
|
79
|
+
// buildFilterFacets/buildFilterPayload.
|
|
80
|
+
const facetSpecs = useMemo(() => resolveProjectionFacetSpecs(screen.facets, effectiveTranslate, schema.featureName), [screen.facets, effectiveTranslate, schema.featureName]);
|
|
81
|
+
const [projectionReferenceFacetOptions, setProjectionReferenceFacetOptions] = useState({});
|
|
82
|
+
const handleProjectionFacetOptions = useCallback((field, options) => setProjectionReferenceFacetOptions((prev) => prev[field] === options ? prev : { ...prev, [field]: options }), []);
|
|
83
|
+
const resolvedFacetSpecs = useMemo(() => mergeReferenceFacetOptions(facetSpecs, projectionReferenceFacetOptions), [facetSpecs, projectionReferenceFacetOptions]);
|
|
84
|
+
const filterPayload = useMemo(() => buildFilterPayload(urlState.filters, (field) => facetSpecs.find((spec) => spec.field === field)?.type), [urlState.filters, facetSpecs]);
|
|
85
|
+
// dateRange facets (fw#3104) bypass `filters` entirely — their two bounds
|
|
86
|
+
// become the top-level query params the facet declares, resolved from a
|
|
87
|
+
// calendar date to an instant in the viewer's zone.
|
|
88
|
+
const timeZone = useOptionalTimeZone();
|
|
89
|
+
const dateRangeSpecs = useMemo(() => resolveDateRangeFacets(screen.facets, effectiveTranslate), [screen.facets, effectiveTranslate]);
|
|
90
|
+
const dateRangePayload = useMemo(() => buildDateRangePayload(dateRangeSpecs, urlState.filters, timeZone), [dateRangeSpecs, urlState.filters, timeZone]);
|
|
91
|
+
const queryPayload = useMemo(() => {
|
|
92
|
+
const payload = buildListQueryPayload({
|
|
93
|
+
limit,
|
|
94
|
+
search: activeSearch,
|
|
95
|
+
sort: activeSort,
|
|
96
|
+
usePager,
|
|
97
|
+
page: urlState.page,
|
|
98
|
+
useInfinite: false,
|
|
99
|
+
cursor: undefined,
|
|
100
|
+
});
|
|
101
|
+
// Gated on screen.filter/screen.facets being declared (not just on
|
|
102
|
+
// urlState/filterPayload happening to carry a value) — same rule as
|
|
103
|
+
// activeSearch/activeSort above: a stale URL must not smuggle a param
|
|
104
|
+
// the bound query's Zod schema doesn't accept (fw#2165 pattern).
|
|
105
|
+
if (screen.filter !== undefined) {
|
|
106
|
+
payload["filter"] = screen.filter;
|
|
107
|
+
}
|
|
108
|
+
if (screen.facets !== undefined && filterPayload.length > 0) {
|
|
109
|
+
payload["filters"] = filterPayload;
|
|
110
|
+
}
|
|
111
|
+
Object.assign(payload, dateRangePayload);
|
|
112
|
+
return payload;
|
|
113
|
+
}, [
|
|
114
|
+
limit,
|
|
115
|
+
activeSearch,
|
|
116
|
+
activeSort,
|
|
117
|
+
usePager,
|
|
118
|
+
urlState.page,
|
|
119
|
+
screen.filter,
|
|
120
|
+
screen.facets,
|
|
121
|
+
filterPayload,
|
|
122
|
+
dateRangePayload,
|
|
123
|
+
]);
|
|
124
|
+
const rowsQuery = useQuery(screen.query, queryPayload, { live: true });
|
|
125
|
+
const filterFacets = useMemo(() => buildFilterFacets(resolvedFacetSpecs), [resolvedFacetSpecs]);
|
|
126
|
+
const dateRangeFacets = useMemo(() => dateRangeSpecs.map((spec) => ({
|
|
127
|
+
field: spec.field,
|
|
128
|
+
label: spec.label,
|
|
129
|
+
...readDateRange(urlState.filters, spec.field),
|
|
130
|
+
})), [dateRangeSpecs, urlState.filters]);
|
|
131
|
+
const handleDateRangeChange = useCallback((field, bound, value) => {
|
|
132
|
+
urlState.setDateRange(field, clampDateRange(readDateRange(urlState.filters, field), bound, value));
|
|
133
|
+
}, [urlState.setDateRange, urlState.filters]);
|
|
134
|
+
// Entity-Targets (fw#2228) — see EntityListBody.runNavigate for why the
|
|
135
|
+
// resolution happens in the NavApi impl. Unlike there: NO row["id"]
|
|
136
|
+
// fallback — projectionList rows come from an arbitrary query projection
|
|
137
|
+
// with no guaranteed "id" field. The boot validator therefore enforces an
|
|
138
|
+
// explicit entityId for projectionList entity targets. Same helper
|
|
139
|
+
// relatedList's rowActions reuse (related-list-section.tsx) — a
|
|
140
|
+
// projectionDetail relatedList row has the identical "no guaranteed id"
|
|
141
|
+
// shape.
|
|
142
|
+
const runNavigate = useCallback((action, row) => runProjectionRowNavigate(nav, action, row, host), [nav, host]);
|
|
143
|
+
const defaultEditRowAction = useMemo(() => buildDefaultEditRowAction(defaultEditScreen), [defaultEditScreen]);
|
|
144
|
+
const rowActions = useMemo(() => buildProjectionRowActions({
|
|
145
|
+
rowActions: screen.rowActions,
|
|
146
|
+
translate: effectiveTranslate,
|
|
147
|
+
dispatcher,
|
|
148
|
+
nav,
|
|
149
|
+
refetch: rowsQuery.refetch,
|
|
150
|
+
openDrawer,
|
|
151
|
+
defaultEditRowAction,
|
|
152
|
+
host,
|
|
153
|
+
clientActions,
|
|
154
|
+
}), [
|
|
155
|
+
screen.rowActions,
|
|
156
|
+
effectiveTranslate,
|
|
157
|
+
dispatcher,
|
|
158
|
+
clientActions,
|
|
159
|
+
nav,
|
|
160
|
+
rowsQuery.refetch,
|
|
161
|
+
openDrawer,
|
|
162
|
+
defaultEditRowAction,
|
|
163
|
+
host,
|
|
164
|
+
]);
|
|
165
|
+
const toolbarActions = useMemo(() => buildProjectionToolbarActions({
|
|
166
|
+
toolbarActions: screen.toolbarActions,
|
|
167
|
+
translate: effectiveTranslate,
|
|
168
|
+
dispatcher,
|
|
169
|
+
nav,
|
|
170
|
+
refetch: rowsQuery.refetch,
|
|
171
|
+
openDrawer,
|
|
172
|
+
host,
|
|
173
|
+
access: { appFeatures, userRoles },
|
|
174
|
+
}), [
|
|
175
|
+
screen.toolbarActions,
|
|
176
|
+
effectiveTranslate,
|
|
177
|
+
nav,
|
|
178
|
+
host,
|
|
179
|
+
dispatcher,
|
|
180
|
+
rowsQuery.refetch,
|
|
181
|
+
openDrawer,
|
|
182
|
+
appFeatures,
|
|
183
|
+
userRoles,
|
|
184
|
+
]);
|
|
185
|
+
const emptyContent = useListEmptyContent(screen.emptyState, effectiveTranslate, {
|
|
186
|
+
nav,
|
|
187
|
+
host,
|
|
188
|
+
dispatcher,
|
|
189
|
+
openDrawer,
|
|
190
|
+
onWriteSuccess: rowsQuery.refetch,
|
|
191
|
+
});
|
|
192
|
+
if (rowsQuery.loading && rowsQuery.data === null) {
|
|
193
|
+
return _jsx(QueryLoadingBanner, {});
|
|
194
|
+
}
|
|
195
|
+
if (rowsQuery.error && rowsQuery.data === null) {
|
|
196
|
+
return (_jsx(QueryErrorBanner, { error: rowsQuery.error, translate: effectiveTranslate, onRetry: rowsQuery.refetch }));
|
|
197
|
+
}
|
|
198
|
+
// A failed refetch keeps the last good data on screen.
|
|
199
|
+
const refetchErrorBanner = rowsQuery.error ? (_jsx(QueryErrorBanner, { error: rowsQuery.error, translate: effectiveTranslate, onRetry: rowsQuery.refetch })) : null;
|
|
200
|
+
// rowsQuery.data is typed as PagedRows but the query handler is free to
|
|
201
|
+
// return anything at runtime (fw#2216: a bare array silently rendered an
|
|
202
|
+
// empty table instead of surfacing the mismatch), so the shape is checked.
|
|
203
|
+
if (rowsQuery.data !== null && !Array.isArray(rowsQuery.data.rows)) {
|
|
204
|
+
return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-projection-list-bad-shape", children: ["Screen ", _jsx(Text, { variant: "code", children: screen.id }), " query", " ", _jsx(Text, { variant: "code", children: screen.query }), " did not return a PagedRows envelope; a projectionList query must return ", _jsx(Text, { variant: "code", children: "{ rows, nextCursor }" }), "."] }));
|
|
205
|
+
}
|
|
206
|
+
const rowClickAction = findRowClickAction(screen.rowActions);
|
|
207
|
+
const wrappedOnRowClick = rowClickAction
|
|
208
|
+
? (row) => rowClickAction.kind === "drawer"
|
|
209
|
+
? runDrawerRowAction(rowClickAction, openDrawer, row)
|
|
210
|
+
: runNavigate(rowClickAction, row)
|
|
211
|
+
: onRowClick !== undefined
|
|
212
|
+
? (row) => onRowClick(row, listScreen.entity)
|
|
213
|
+
: undefined;
|
|
214
|
+
// Same pager-construction as EntityListBody: no Pager UI until the
|
|
215
|
+
// server-provided `total` arrives (guards pagination="pages" without
|
|
216
|
+
// totalCount support, see fw#2165 report on solon's leaseOverviewHandler).
|
|
217
|
+
const total = rowsQuery.data?.total;
|
|
218
|
+
const pager = usePager && total !== undefined
|
|
219
|
+
? {
|
|
220
|
+
page: urlState.page,
|
|
221
|
+
limit,
|
|
222
|
+
total,
|
|
223
|
+
onPageChange: urlState.setPage,
|
|
224
|
+
pageSizeOptions: pageSizeOptionsFor(limit),
|
|
225
|
+
onPageSizeChange: urlState.setPageSize,
|
|
226
|
+
}
|
|
227
|
+
: undefined;
|
|
228
|
+
return (_jsxs(_Fragment, { children: [refetchErrorBanner, _jsx(ReferenceFacetBridges, { specs: facetSpecs, onOptions: handleProjectionFacetOptions }), _jsx(RenderList, { screen: listScreen, entity: entity, rows: rowsQuery.data?.rows ?? [], featureName: schema.featureName, searchable: searchable, narrowed: isListNarrowed(activeSearch, urlState.filters), ...(screen.description !== undefined && {
|
|
229
|
+
description: effectiveTranslate(screen.description),
|
|
230
|
+
}), ...(emptyContent !== undefined && { emptyContent }), searchValue: urlState.q, onSearchChange: urlState.setQ, sort: activeSort, onSortChange: urlState.setSort, ...(isEmbeddedScreen
|
|
231
|
+
? { screenPadding: false, chromeless: true }
|
|
232
|
+
: { screenPadding: true }), ...(screenFillsHeight(listScreen) && { scrollBody: true }), ...(pager !== undefined && { pager }), ...(rowActions !== undefined && { rowActions }), ...(screen.rowActionMode !== undefined && { rowActionMode: screen.rowActionMode }), ...(toolbarActions !== undefined && { toolbarActions }), ...(translate !== undefined && { translate }), ...(wrappedOnRowClick !== undefined && { onRowClick: wrappedOnRowClick }), ...(filterFacets.length > 0 && {
|
|
233
|
+
filterFacets,
|
|
234
|
+
onFilterChange: urlState.setFilter,
|
|
235
|
+
}), ...(dateRangeFacets.length > 0 && {
|
|
236
|
+
dateRangeFacets,
|
|
237
|
+
onDateRangeChange: handleDateRangeChange,
|
|
238
|
+
}), ...((filterFacets.length > 0 || dateRangeFacets.length > 0) && {
|
|
239
|
+
filterValues: urlState.filters,
|
|
240
|
+
onFilterReset: urlState.clearFilters,
|
|
241
|
+
}) }), _jsx(DrawerHost, { schema: schema, drawerAction: drawerAction, drawerScreen: drawerScreen, ...(drawerInitialValues !== undefined && { drawerInitialValues }), userRoles: userRoles, ...(translate !== undefined && { translate }), onClose: closeDrawer, onSuccess: () => {
|
|
242
|
+
closeDrawer();
|
|
243
|
+
void rowsQuery.refetch();
|
|
244
|
+
} })] }));
|
|
245
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntityDefinition, EntityListScreenDefinition, ListColumnSpec, ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
export declare const SYNTHESIZED_FIELD_PII_REASON = "synthesized display field, not stored entity data";
|
|
2
3
|
/** Minimal EntityDefinition from the column list: every field is a text
|
|
3
4
|
* field. `sortable` applies to all columns — buildAppSchema derives it from
|
|
4
5
|
* the query's Zod schema (`screen.sortable`, fw#2165); the query itself has
|
|
@@ -6,7 +7,7 @@ import type { EntityDefinition, EntityListScreenDefinition, ListColumnSpec, Proj
|
|
|
6
7
|
* `sortable: false` when the handler cannot order by it.
|
|
7
8
|
* computeListViewModel only reads `fields[<col>].type` — text is enough,
|
|
8
9
|
* presentation comes from the column renderer + explicit label. */
|
|
9
|
-
export declare function synthesizeProjectionEntity(columns: readonly ListColumnSpec[], sortable: boolean): EntityDefinition
|
|
10
|
+
export declare function synthesizeProjectionEntity(columns: readonly ListColumnSpec[], sortable: boolean): Pick<EntityDefinition, "fields">;
|
|
10
11
|
/** Wandelt ein ProjectionListScreenDefinition in die EntityListScreen-Shape die
|
|
11
12
|
* RenderList erwartet. `type` = "entityList" + Pseudo-Entity halten den Type-
|
|
12
13
|
* Constraint; RenderList branched nicht auf `type` und liest `entity` nur für
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
// query-native ListView-Komponente.
|
|
16
16
|
import { normalizeListColumn } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
17
17
|
const PROJECTION_PSEUDO_ENTITY = "__projection__";
|
|
18
|
+
// Satisfies the text-field PII stance of the entity field type; these synthesized
|
|
19
|
+
// fields describe query columns and never map to stored entity data.
|
|
20
|
+
export const SYNTHESIZED_FIELD_PII_REASON = "synthesized display field, not stored entity data";
|
|
18
21
|
/** Minimal EntityDefinition from the column list: every field is a text
|
|
19
22
|
* field. `sortable` applies to all columns — buildAppSchema derives it from
|
|
20
23
|
* the query's Zod schema (`screen.sortable`, fw#2165); the query itself has
|
|
@@ -28,6 +31,7 @@ export function synthesizeProjectionEntity(columns, sortable) {
|
|
|
28
31
|
const normalized = normalizeListColumn(col);
|
|
29
32
|
fields[normalized.field] = {
|
|
30
33
|
type: "text",
|
|
34
|
+
allowPlaintext: SYNTHESIZED_FIELD_PII_REASON,
|
|
31
35
|
sortable: sortable && normalized.sortable !== false,
|
|
32
36
|
};
|
|
33
37
|
}
|
|
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
// One bridge component per reference facet, mirroring ReferenceLookupBridge —
|
|
3
3
|
// facet count varies per screen, so the hook can't be called in a loop.
|
|
4
4
|
import { useEffect } from "react";
|
|
5
|
+
import { rendererWarn } from "../dev-console.js";
|
|
5
6
|
import { REFERENCE_LIST_LOOKUP_LIMIT } from "../hooks/reference-limits.js";
|
|
6
7
|
import { useReferenceLookup } from "../hooks/use-reference-lookup.js";
|
|
7
8
|
function ReferenceFacetOptionsBridge({ field, refEntity, refFeature, labelField, onOptions, }) {
|
|
@@ -12,8 +13,7 @@ function ReferenceFacetOptionsBridge({ field, refEntity, refFeature, labelField,
|
|
|
12
13
|
// The lookup is one capped list fetch; a full page means the facet may be
|
|
13
14
|
// missing values the user wants to filter by.
|
|
14
15
|
if (lookup.map.size >= REFERENCE_LIST_LOOKUP_LIMIT) {
|
|
15
|
-
|
|
16
|
-
console.warn(`[kumiko] reference facet "${field}" shows only the first ${REFERENCE_LIST_LOOKUP_LIMIT} ${refEntity} rows; further values cannot be filtered by`);
|
|
16
|
+
rendererWarn(`[kumiko] reference facet "${field}" shows only the first ${REFERENCE_LIST_LOOKUP_LIMIT} ${refEntity} rows; further values cannot be filtered by`);
|
|
17
17
|
}
|
|
18
18
|
onOptions(field, [...lookup.map.entries()].map(([value, label]) => ({ value, label })));
|
|
19
19
|
}, [lookup.loading, lookup.map, field, refEntity, onOptions]);
|
package/dist/app/row-actions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { evalFieldCondition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
2
|
import { resolveActionIcon } from "@cosmicdrift/kumiko-types/action-icon";
|
|
3
|
+
import { rendererError, rendererWarn } from "../dev-console.js";
|
|
3
4
|
import { runClientAction } from "./client-actions.js";
|
|
4
5
|
import { lastSegment } from "./qn.js";
|
|
5
6
|
import { navigateWithReturnTo } from "./return-to.js";
|
|
@@ -57,8 +58,7 @@ export function navigateActionSearchParams(action, values) {
|
|
|
57
58
|
}
|
|
58
59
|
export async function refetchAfterWrite(refetch) {
|
|
59
60
|
await refetch().catch((err) => {
|
|
60
|
-
|
|
61
|
-
console.error("kumiko-screen: refetch after write action failed", err);
|
|
61
|
+
rendererError("kumiko-screen: refetch after write action failed", err);
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
// Navigate execution for query-driven rows (projectionList, and relatedList
|
|
@@ -118,8 +118,7 @@ function warnDrawerActionDropped(actionKind, actionId) {
|
|
|
118
118
|
if (warnedDrawerActionIds.has(key))
|
|
119
119
|
return;
|
|
120
120
|
warnedDrawerActionIds.add(key);
|
|
121
|
-
|
|
122
|
-
console.warn(`[kumiko] ${actionKind} "${actionId}" is kind:"drawer", but the host did not wire openDrawer (RelatedListSection: pass onOpenDrawer) — it will not render.`);
|
|
121
|
+
rendererWarn(`[kumiko] ${actionKind} "${actionId}" is kind:"drawer", but the host did not wire openDrawer (RelatedListSection: pass onOpenDrawer) — it will not render.`);
|
|
123
122
|
}
|
|
124
123
|
export function runDrawerRowAction(action, openDrawer, row) {
|
|
125
124
|
openDrawer(action, action.params !== undefined ? evalRowExtractor(action.params, row.values) : undefined);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ActionFormRedirect, ActionFormScreenDefinition, ActionFormSuccessLink, ScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import type { Dispatcher, Translate } from "../headless/index.ts";
|
|
4
|
+
import { type ListSort } from "../hooks/use-list-url-state.ts";
|
|
5
|
+
import type { ResolvedSuccessLink } from "./action-form-support.tsx";
|
|
6
|
+
import type { FeatureSchema } from "./feature-schema.ts";
|
|
7
|
+
import { type NavApi, type ScreenTarget } from "./nav.tsx";
|
|
8
|
+
/** Empty state for a screen QN that is not in the schema (feature disabled for
|
|
9
|
+
* the tenant, stale link, miswired screen). The QN only goes to the console. */
|
|
10
|
+
export declare function ScreenMissingState({ screenQn, homeScreenId, testId, }: {
|
|
11
|
+
readonly screenQn: string;
|
|
12
|
+
readonly homeScreenId?: string;
|
|
13
|
+
readonly testId?: string;
|
|
14
|
+
}): ReactNode;
|
|
15
|
+
export declare function ScreenAccessDeniedBanner({ screenQn, testId, }: {
|
|
16
|
+
readonly screenQn: string;
|
|
17
|
+
readonly testId: string;
|
|
18
|
+
}): ReactNode;
|
|
19
|
+
export declare function entityWriteCommand(featureName: string, entity: string, verb: "create" | "update" | "delete"): string;
|
|
20
|
+
export declare function findEntityListScreen(schema: FeatureSchema, entityName: string): ScreenDefinition | undefined;
|
|
21
|
+
export declare function RecordNotFoundState({ translate, onBack, }: {
|
|
22
|
+
readonly translate: Translate;
|
|
23
|
+
readonly onBack?: () => void;
|
|
24
|
+
}): ReactNode;
|
|
25
|
+
export declare function useNavigateToListAfter(schema: FeatureSchema, entityName: string): () => void;
|
|
26
|
+
export declare function pageSizeOptionsFor(currentLimit: number): readonly number[];
|
|
27
|
+
export declare function useNavigateToCreateFor(schema: FeatureSchema, entityName: string, createScreen?: string, createUnavailable?: boolean): (() => void) | undefined;
|
|
28
|
+
export declare function formatSummaryParams(prefill: Readonly<Record<string, unknown>> | undefined, locale: string): Record<string, string>;
|
|
29
|
+
export declare function resolveSuccessMessageParams(screen: ActionFormScreenDefinition, featureName: string, values: Readonly<Record<string, unknown>>, dispatcher: Dispatcher, locale: string): Promise<Record<string, string>>;
|
|
30
|
+
export declare function resolveActionFormSummary(summary: ActionFormScreenDefinition["summary"], prefill: Readonly<Record<string, unknown>> | undefined, translate: Translate, locale: string): {
|
|
31
|
+
readonly title: string;
|
|
32
|
+
readonly subtitle?: string;
|
|
33
|
+
} | undefined;
|
|
34
|
+
export declare function entityQueryCommand(featureName: string, entity: string, verb: "list"): string;
|
|
35
|
+
export type PagedRows = {
|
|
36
|
+
readonly rows: Readonly<Record<string, unknown>>[];
|
|
37
|
+
readonly nextCursor: string | null;
|
|
38
|
+
readonly total?: number;
|
|
39
|
+
};
|
|
40
|
+
export declare function buildListQueryPayload(state: {
|
|
41
|
+
readonly limit: number;
|
|
42
|
+
readonly search: string;
|
|
43
|
+
readonly sort: ListSort | null;
|
|
44
|
+
readonly usePager: boolean;
|
|
45
|
+
readonly page: number;
|
|
46
|
+
readonly useInfinite: boolean;
|
|
47
|
+
readonly cursor: string | undefined;
|
|
48
|
+
}): Record<string, unknown>;
|
|
49
|
+
type RedirectSpec = string | ActionFormRedirect | ActionFormSuccessLink;
|
|
50
|
+
export declare function redirectScreenTarget(redirect: RedirectSpec): string;
|
|
51
|
+
export declare function isEntityEditScreenOfEntity(entity: string | undefined, targetScreen: string, schema: FeatureSchema, appFeatures: readonly FeatureSchema[]): boolean;
|
|
52
|
+
export declare function isEntityEditScreenOfOtherEntity(entity: string, targetScreen: string, schema: FeatureSchema, appFeatures: readonly FeatureSchema[]): boolean;
|
|
53
|
+
export declare function returnToWinsOverRedirect(returnTarget: ScreenTarget | undefined, redirect: string | ActionFormRedirect, schema: FeatureSchema, appFeatures: readonly FeatureSchema[]): returnTarget is ScreenTarget;
|
|
54
|
+
export declare function resolveRedirectTarget(redirect: RedirectSpec, submittedData: unknown, schema: FeatureSchema, appFeatures: readonly FeatureSchema[], fallbackRecord?: Readonly<Record<string, unknown>>): ResolvedRedirectTarget;
|
|
55
|
+
type ResolvedRedirectTarget = {
|
|
56
|
+
readonly screenId: string;
|
|
57
|
+
readonly entityId: string | undefined;
|
|
58
|
+
readonly tab: string | undefined;
|
|
59
|
+
};
|
|
60
|
+
export declare function navigateToRedirectTarget(nav: NavApi, target: ResolvedRedirectTarget): void;
|
|
61
|
+
export declare function resolveSuccessLinks(links: readonly ActionFormSuccessLink[] | undefined, data: unknown, schema: FeatureSchema, appFeatures: readonly FeatureSchema[], nav: NavApi, translate: Translate): readonly ResolvedSuccessLink[];
|
|
62
|
+
export declare function navigateAfterRedirect(options: {
|
|
63
|
+
readonly nav: NavApi;
|
|
64
|
+
readonly schema: FeatureSchema;
|
|
65
|
+
readonly appFeatures: readonly FeatureSchema[];
|
|
66
|
+
readonly returnTarget: ScreenTarget | undefined;
|
|
67
|
+
readonly redirect: string | ActionFormRedirect;
|
|
68
|
+
readonly resultData: unknown;
|
|
69
|
+
readonly record: Readonly<Record<string, unknown>>;
|
|
70
|
+
}): void;
|
|
71
|
+
export declare function overlayUpdatedProjection(record: Readonly<Record<string, unknown>>, resultData: unknown): Readonly<Record<string, unknown>>;
|
|
72
|
+
export {};
|