@cosmicdrift/kumiko-renderer 0.389.0 → 0.390.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/projection-detail-body.js +59 -394
- package/dist/app/projection-detail-header-model.d.ts +11 -0
- package/dist/app/projection-detail-header-model.js +33 -0
- package/dist/app/projection-detail-header-region.d.ts +28 -0
- package/dist/app/projection-detail-header-region.js +81 -0
- package/dist/app/projection-detail-header.d.ts +29 -0
- package/dist/app/projection-detail-header.js +84 -0
- package/dist/app/projection-detail-navigation.d.ts +13 -0
- package/dist/app/projection-detail-navigation.js +91 -0
- package/dist/app/projection-detail-states.d.ts +22 -0
- package/dist/app/projection-detail-states.js +26 -0
- package/dist/app/projection-drawer-host.d.ts +12 -0
- package/dist/app/projection-drawer-host.js +10 -0
- package/dist/app/projection-list-body.d.ts +1 -1
- package/dist/app/projection-list-body.js +69 -189
- package/dist/app/projection-list-render-props.d.ts +56 -0
- package/dist/app/projection-list-render-props.js +47 -0
- package/dist/app/projection-list-render.d.ts +26 -0
- package/dist/app/projection-list-render.js +20 -0
- package/dist/app/projection-list-row-click.d.ts +12 -0
- package/dist/app/projection-list-row-click.js +14 -0
- package/dist/app/use-projection-detail-actions.d.ts +35 -0
- package/dist/app/use-projection-detail-actions.js +156 -0
- package/dist/app/use-projection-detail-data.d.ts +19 -0
- package/dist/app/use-projection-detail-data.js +47 -0
- package/dist/app/use-projection-detail-layout.d.ts +17 -0
- package/dist/app/use-projection-detail-layout.js +23 -0
- package/dist/app/use-projection-list-actions.d.ts +31 -0
- package/dist/app/use-projection-list-actions.js +73 -0
- package/dist/app/use-projection-list-data.d.ts +20 -0
- package/dist/app/use-projection-list-data.js +26 -0
- package/dist/app/use-projection-list-definitions.d.ts +11 -0
- package/dist/app/use-projection-list-definitions.js +20 -0
- package/dist/app/use-projection-list-query.d.ts +28 -0
- package/dist/app/use-projection-list-query.js +83 -0
- package/dist/components/render-edit-banners.d.ts +12 -0
- package/dist/components/render-edit-banners.js +90 -0
- package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
- package/dist/components/render-edit-draft-save-helpers.js +42 -0
- package/dist/components/render-edit-field-grid.d.ts +4 -0
- package/dist/components/render-edit-field-grid.js +40 -0
- package/dist/components/render-edit-fields-section.d.ts +4 -0
- package/dist/components/render-edit-fields-section.js +76 -0
- package/dist/components/render-edit-form-actions.d.ts +4 -0
- package/dist/components/render-edit-form-actions.js +66 -0
- package/dist/components/render-edit-header-actions.d.ts +11 -0
- package/dist/components/render-edit-header-actions.js +120 -0
- package/dist/components/render-edit-layout-flags.d.ts +29 -0
- package/dist/components/render-edit-layout-flags.js +106 -0
- package/dist/components/render-edit-layout.d.ts +4 -0
- package/dist/components/render-edit-layout.js +79 -0
- package/dist/components/render-edit-render-model.d.ts +63 -0
- package/dist/components/render-edit-render-model.js +14 -0
- package/dist/components/render-edit-section-kinds.d.ts +4 -0
- package/dist/components/render-edit-section-kinds.js +115 -0
- package/dist/components/render-edit-submit-result.d.ts +8 -0
- package/dist/components/render-edit-submit-result.js +77 -0
- package/dist/components/render-edit-submit.d.ts +7 -0
- package/dist/components/render-edit-submit.js +113 -0
- package/dist/components/render-edit-titles.d.ts +17 -0
- package/dist/components/render-edit-titles.js +99 -0
- package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
- package/dist/components/render-edit-wizard-navigation.js +115 -0
- package/dist/components/render-edit.d.ts +2 -2
- package/dist/components/render-edit.js +63 -1128
- package/dist/components/render-field-choice-inputs.d.ts +7 -0
- package/dist/components/render-field-choice-inputs.js +52 -0
- package/dist/components/render-field-control.d.ts +12 -0
- package/dist/components/render-field-control.js +93 -0
- package/dist/components/render-field-display-text.d.ts +4 -0
- package/dist/components/render-field-display-text.js +48 -0
- package/dist/components/render-field-fill-action.d.ts +13 -0
- package/dist/components/render-field-fill-action.js +63 -0
- package/dist/components/render-field-input-shared.d.ts +39 -0
- package/dist/components/render-field-input-shared.js +20 -0
- package/dist/components/render-field-input.d.ts +3 -0
- package/dist/components/render-field-input.js +34 -0
- package/dist/components/render-field-meta.d.ts +6 -0
- package/dist/components/render-field-meta.js +51 -0
- package/dist/components/render-field-read-only.d.ts +11 -0
- package/dist/components/render-field-read-only.js +75 -0
- package/dist/components/render-field-reference.d.ts +15 -0
- package/dist/components/render-field-reference.js +103 -0
- package/dist/components/render-field-scalar-inputs.d.ts +10 -0
- package/dist/components/render-field-scalar-inputs.js +35 -0
- package/dist/components/render-field-text-inputs.d.ts +3 -0
- package/dist/components/render-field-text-inputs.js +34 -0
- package/dist/components/render-field-values.d.ts +18 -0
- package/dist/components/render-field-values.js +111 -0
- package/dist/components/render-field-write-only.d.ts +9 -0
- package/dist/components/render-field-write-only.js +29 -0
- package/dist/components/render-field.d.ts +2 -2
- package/dist/components/render-field.js +39 -670
- package/dist/components/render-list-header-slot.d.ts +5 -0
- package/dist/components/render-list-header-slot.js +30 -0
- package/dist/components/render-list-reference-bridge.d.ts +3 -0
- package/dist/components/render-list-reference-bridge.js +25 -0
- package/dist/components/render-list-reference-columns.d.ts +25 -0
- package/dist/components/render-list-reference-columns.js +137 -0
- package/dist/components/render-list-table-props.d.ts +52 -0
- package/dist/components/render-list-table-props.js +53 -0
- package/dist/components/render-list-toolbar-model.d.ts +42 -0
- package/dist/components/render-list-toolbar-model.js +82 -0
- package/dist/components/render-list-toolbar.d.ts +38 -0
- package/dist/components/render-list-toolbar.js +40 -0
- package/dist/components/render-list-types.d.ts +113 -0
- package/dist/components/render-list-types.js +1 -0
- package/dist/components/render-list.d.ts +4 -113
- package/dist/components/render-list.js +91 -307
- package/dist/components/use-reference-input-state.d.ts +20 -0
- package/dist/components/use-reference-input-state.js +85 -0
- package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
- package/dist/components/use-render-edit-controlled-sync.js +26 -0
- package/dist/components/use-render-edit-draft-save.d.ts +29 -0
- package/dist/components/use-render-edit-draft-save.js +141 -0
- package/dist/components/use-render-edit-form.d.ts +10 -0
- package/dist/components/use-render-edit-form.js +101 -0
- package/dist/components/use-render-edit-sections.d.ts +15 -0
- package/dist/components/use-render-edit-sections.js +100 -0
- package/dist/components/use-render-edit-state.d.ts +65 -0
- package/dist/components/use-render-edit-state.js +158 -0
- package/dist/components/use-render-edit-steps.d.ts +13 -0
- package/dist/components/use-render-edit-steps.js +52 -0
- package/dist/components/use-render-list-columns.d.ts +18 -0
- package/dist/components/use-render-list-columns.js +41 -0
- package/dist/components/use-render-list-search.d.ts +4 -0
- package/dist/components/use-render-list-search.js +50 -0
- package/dist/primitives.d.ts +1 -1
- package/dist/web/app/dashboard-body.d.ts +1 -1
- package/dist/web/app/dashboard-body.js +42 -246
- package/dist/web/app/dashboard-header.d.ts +34 -0
- package/dist/web/app/dashboard-header.js +58 -0
- package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
- package/dist/web/app/dashboard-screen-controls.js +42 -0
- package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
- package/dist/web/app/dashboard-screen-tiles.js +42 -0
- package/dist/web/app/dashboard-visibility.d.ts +42 -0
- package/dist/web/app/dashboard-visibility.js +102 -0
- package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
- package/dist/web/app/use-dashboard-screen-state.js +69 -0
- package/dist/web/primitives/data-table/actions-column.js +6 -5
- package/dist/web/primitives/data-table/card-body.d.ts +6 -0
- package/dist/web/primitives/data-table/card-body.js +32 -0
- package/dist/web/primitives/data-table/card-row.d.ts +5 -0
- package/dist/web/primitives/data-table/card-row.js +31 -0
- package/dist/web/primitives/data-table/card-view.d.ts +3 -0
- package/dist/web/primitives/data-table/card-view.js +13 -0
- package/dist/web/primitives/data-table/column-resize-handle.js +3 -1
- package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
- package/dist/web/primitives/data-table/data-cell-props.js +8 -0
- package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
- package/dist/web/primitives/data-table/row-render-parts.js +35 -0
- package/dist/web/primitives/data-table/table-body.d.ts +3 -0
- package/dist/web/primitives/data-table/table-body.js +32 -0
- package/dist/web/primitives/data-table/table-content.d.ts +3 -0
- package/dist/web/primitives/data-table/table-content.js +41 -0
- package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
- package/dist/web/primitives/data-table/table-data-cell.js +25 -0
- package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
- package/dist/web/primitives/data-table/table-header-row.js +21 -0
- package/dist/web/primitives/data-table/table-view.d.ts +3 -0
- package/dist/web/primitives/data-table/table-view.js +25 -0
- package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
- package/dist/web/primitives/data-table/toolbar.js +53 -0
- package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
- package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
- package/dist/web/primitives/data-table.d.ts +3 -4
- package/dist/web/primitives/data-table.js +32 -252
- package/dist/web/primitives/file-value-list.js +1 -1
- package/dist/web/primitives/form-footer-parts.d.ts +25 -0
- package/dist/web/primitives/form-footer-parts.js +55 -0
- package/dist/web/primitives/form.js +33 -43
- package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
- package/dist/web/primitives/input-choice-kinds.js +67 -0
- package/dist/web/primitives/input-file-kinds.d.ts +3 -0
- package/dist/web/primitives/input-file-kinds.js +6 -0
- package/dist/web/primitives/input-number-kinds.d.ts +4 -0
- package/dist/web/primitives/input-number-kinds.js +16 -0
- package/dist/web/primitives/input-optional-props.d.ts +20 -0
- package/dist/web/primitives/input-optional-props.js +24 -0
- package/dist/web/primitives/input-select-presentation.d.ts +13 -0
- package/dist/web/primitives/input-select-presentation.js +19 -0
- package/dist/web/primitives/input-shared.d.ts +22 -0
- package/dist/web/primitives/input-shared.js +10 -0
- package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
- package/dist/web/primitives/input-temporal-kinds.js +10 -0
- package/dist/web/primitives/input-text-kinds.d.ts +7 -0
- package/dist/web/primitives/input-text-kinds.js +33 -0
- package/dist/web/primitives/input.js +35 -161
- package/dist/web/widgets/chart-data.d.ts +35 -0
- package/dist/web/widgets/chart-data.js +37 -0
- package/dist/web/widgets/chart-markers.d.ts +26 -0
- package/dist/web/widgets/chart-markers.js +64 -0
- package/dist/web/widgets/chart-parts.d.ts +20 -0
- package/dist/web/widgets/chart-parts.js +15 -0
- package/dist/web/widgets/charts.d.ts +5 -78
- package/dist/web/widgets/charts.js +7 -485
- package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
- package/dist/web/widgets/drawer-panel-class.js +40 -0
- package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
- package/dist/web/widgets/drawer-resize-controls.js +12 -0
- package/dist/web/widgets/drawer-sizing.d.ts +19 -0
- package/dist/web/widgets/drawer-sizing.js +60 -0
- package/dist/web/widgets/drawer.js +17 -116
- package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
- package/dist/web/widgets/stacked-area-brush.js +91 -0
- package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
- package/dist/web/widgets/stacked-area-chart.js +45 -0
- package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
- package/dist/web/widgets/stacked-area-chrome.js +77 -0
- package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
- package/dist/web/widgets/stacked-area-geometry.js +83 -0
- package/dist/web/widgets/stacked-area-model.d.ts +11 -0
- package/dist/web/widgets/stacked-area-model.js +1 -0
- package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
- package/dist/web/widgets/stacked-area-plot.js +51 -0
- package/dist/web/widgets/stacked-area-props.d.ts +83 -0
- package/dist/web/widgets/stacked-area-props.js +16 -0
- package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
- package/dist/web/widgets/stacked-area-svg.js +38 -0
- package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
- package/dist/web/widgets/stacked-area-tooltip.js +86 -0
- package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
- package/dist/web/widgets/use-drawer-resize.js +52 -0
- package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
- package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
- package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
- package/dist/web/widgets/use-stacked-area-window.js +76 -0
- package/package.json +9 -9
- package/src/changes.json +54 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { resolveActionIcon } from "@cosmicdrift/kumiko-types/action-icon";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { lastSegment } from "./qn.js";
|
|
4
|
+
import { navigateToReturnOr, navigateWithReturnTo, } from "./return-to.js";
|
|
5
|
+
import { buildRecordActions } from "./row-actions.js";
|
|
6
|
+
import { findEditScreenFor } from "./screen-access.js";
|
|
7
|
+
import { findEntityListScreen, navigateAfterRedirect } from "./screen-shared.js";
|
|
8
|
+
export function useProjectionDetailActions(input) {
|
|
9
|
+
const { schema, screen, effectiveTranslate, effectiveEntityId, nav, host, dispatcher, clientActions, appFeatures, userRoles, returnTarget, openDrawer, detailQuery, reloadDetail, } = input;
|
|
10
|
+
const handleRecordLeft = useCallback((action, resultData, deletedShownRecord) => {
|
|
11
|
+
const record = detailQuery.data ?? {};
|
|
12
|
+
// Never return onto the just-deleted record.
|
|
13
|
+
const target = deletedShownRecord && returnTarget?.entityId === effectiveEntityId
|
|
14
|
+
? undefined
|
|
15
|
+
: returnTarget;
|
|
16
|
+
if (action.redirect !== undefined) {
|
|
17
|
+
navigateAfterRedirect({
|
|
18
|
+
nav,
|
|
19
|
+
schema,
|
|
20
|
+
appFeatures,
|
|
21
|
+
returnTarget: target,
|
|
22
|
+
redirect: action.redirect,
|
|
23
|
+
resultData,
|
|
24
|
+
record,
|
|
25
|
+
});
|
|
26
|
+
// skip: the redirect already navigated
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
navigateToReturnOr(nav, target, () => {
|
|
30
|
+
const listScreen = screen.listScreenId ??
|
|
31
|
+
(screen.detailFor !== undefined
|
|
32
|
+
? findEntityListScreen(schema, screen.detailFor)?.id
|
|
33
|
+
: undefined);
|
|
34
|
+
if (listScreen !== undefined)
|
|
35
|
+
nav.navigate({ screenId: lastSegment(listScreen) });
|
|
36
|
+
else
|
|
37
|
+
void detailQuery.refetch();
|
|
38
|
+
});
|
|
39
|
+
}, [
|
|
40
|
+
detailQuery.data,
|
|
41
|
+
detailQuery.refetch,
|
|
42
|
+
nav,
|
|
43
|
+
schema,
|
|
44
|
+
appFeatures,
|
|
45
|
+
returnTarget,
|
|
46
|
+
effectiveEntityId,
|
|
47
|
+
screen.listScreenId,
|
|
48
|
+
screen.detailFor,
|
|
49
|
+
]);
|
|
50
|
+
// Default edit action: resolved cross-feature over ALL mounted
|
|
51
|
+
// features, not just this feature's own schema — detailFor itself is
|
|
52
|
+
// resolved cross-feature by the boot-validator (detail-screens.ts), and
|
|
53
|
+
// the motivating case is a projectionDetail whose query belongs to one
|
|
54
|
+
// feature while the entity's entityEdit screen lives in another. Unlike
|
|
55
|
+
// useNavigateToCreateFor this must NOT filter on allowCreate/singleton —
|
|
56
|
+
// those gate create-targets, we're resolving an update-target by a
|
|
57
|
+
// known id.
|
|
58
|
+
const editScreen = useMemo(() => {
|
|
59
|
+
const detailFor = screen.detailFor;
|
|
60
|
+
// skip: no detailFor means there is no edit screen to resolve
|
|
61
|
+
if (detailFor === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
return findEditScreenFor(detailFor, appFeatures, userRoles);
|
|
64
|
+
}, [appFeatures, screen.detailFor, userRoles]);
|
|
65
|
+
const defaultEditAction = useMemo(() => {
|
|
66
|
+
// skip: no edit screen for this entity, so no default edit action
|
|
67
|
+
if (editScreen === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
// editScreen.id is registry-qualified ("feature:screen:contact-edit");
|
|
70
|
+
// nav.navigate expects the short form (see useNavigateToCreateFor).
|
|
71
|
+
const targetScreenId = lastSegment(editScreen.id);
|
|
72
|
+
return {
|
|
73
|
+
id: "edit",
|
|
74
|
+
label: effectiveTranslate("kumiko.actions.edit"),
|
|
75
|
+
icon: resolveActionIcon("edit"),
|
|
76
|
+
onPress: () => {
|
|
77
|
+
const target = {
|
|
78
|
+
screenId: targetScreenId,
|
|
79
|
+
...(effectiveEntityId !== undefined && { entityId: effectiveEntityId }),
|
|
80
|
+
};
|
|
81
|
+
navigateWithReturnTo(nav, target, host);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}, [editScreen, effectiveTranslate, nav, effectiveEntityId, host]);
|
|
85
|
+
// a navigate-screen action targeting an entityEdit screen for the
|
|
86
|
+
// SAME entity this projectionDetail shows (screen.detailFor) defaults its
|
|
87
|
+
// entityId to the shown record's own id — without this fallback the target
|
|
88
|
+
// opens an empty create-form instead of the shown record, silently.
|
|
89
|
+
// Searched cross-feature, consistent with editScreen above.
|
|
90
|
+
const sameEntityScreenId = useCallback((targetScreen) => {
|
|
91
|
+
const record = detailQuery.data ?? {};
|
|
92
|
+
const targetIsEntityEditSameEntity = screen.detailFor !== undefined &&
|
|
93
|
+
appFeatures.some((feature) => feature.screens.some((s) => s.type === "entityEdit" &&
|
|
94
|
+
s.entity === screen.detailFor &&
|
|
95
|
+
lastSegment(s.id) === targetScreen));
|
|
96
|
+
return targetIsEntityEditSameEntity ? String(record["id"] ?? "") : undefined;
|
|
97
|
+
}, [detailQuery.data, screen.detailFor, appFeatures]);
|
|
98
|
+
const buildSectionActions = useCallback((actions) => buildRecordActions({
|
|
99
|
+
actions,
|
|
100
|
+
record: detailQuery.data ?? {},
|
|
101
|
+
translate: effectiveTranslate,
|
|
102
|
+
nav,
|
|
103
|
+
host,
|
|
104
|
+
dispatcher,
|
|
105
|
+
openDrawer,
|
|
106
|
+
onWriteSuccess: reloadDetail,
|
|
107
|
+
onRecordLeft: handleRecordLeft,
|
|
108
|
+
sameEntityScreenId,
|
|
109
|
+
clientActions,
|
|
110
|
+
}), [
|
|
111
|
+
detailQuery.data,
|
|
112
|
+
reloadDetail,
|
|
113
|
+
effectiveTranslate,
|
|
114
|
+
nav,
|
|
115
|
+
host,
|
|
116
|
+
dispatcher,
|
|
117
|
+
clientActions,
|
|
118
|
+
openDrawer,
|
|
119
|
+
handleRecordLeft,
|
|
120
|
+
sameEntityScreenId,
|
|
121
|
+
]);
|
|
122
|
+
const headerActions = useMemo(() => {
|
|
123
|
+
const declaredHasEdit = screen.actions?.some((a) => a.id === "edit") === true;
|
|
124
|
+
const resolved = buildRecordActions({
|
|
125
|
+
actions: screen.actions ?? [],
|
|
126
|
+
record: detailQuery.data ?? {},
|
|
127
|
+
translate: effectiveTranslate,
|
|
128
|
+
nav,
|
|
129
|
+
host,
|
|
130
|
+
dispatcher,
|
|
131
|
+
openDrawer,
|
|
132
|
+
onWriteSuccess: reloadDetail,
|
|
133
|
+
onRecordLeft: handleRecordLeft,
|
|
134
|
+
sameEntityScreenId,
|
|
135
|
+
clientActions,
|
|
136
|
+
});
|
|
137
|
+
// skip: a declared or unavailable edit action leaves the resolved list untouched
|
|
138
|
+
if (defaultEditAction === undefined || declaredHasEdit)
|
|
139
|
+
return resolved;
|
|
140
|
+
return [defaultEditAction, ...(resolved ?? [])];
|
|
141
|
+
}, [
|
|
142
|
+
screen.actions,
|
|
143
|
+
defaultEditAction,
|
|
144
|
+
effectiveTranslate,
|
|
145
|
+
nav,
|
|
146
|
+
host,
|
|
147
|
+
dispatcher,
|
|
148
|
+
clientActions,
|
|
149
|
+
detailQuery.data,
|
|
150
|
+
reloadDetail,
|
|
151
|
+
openDrawer,
|
|
152
|
+
handleRecordLeft,
|
|
153
|
+
sameEntityScreenId,
|
|
154
|
+
]);
|
|
155
|
+
return { buildSectionActions, headerActions };
|
|
156
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProjectionDetailScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { usePrimitives } from "../primitives.tsx";
|
|
3
|
+
import type { NavApi } from "./nav.tsx";
|
|
4
|
+
type DetailDataInput = {
|
|
5
|
+
readonly screen: ProjectionDetailScreenDefinition;
|
|
6
|
+
readonly Tabs: ReturnType<typeof usePrimitives>["Tabs"];
|
|
7
|
+
readonly nav: NavApi;
|
|
8
|
+
readonly effectiveEntityId: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
export declare function useProjectionDetailData(input: DetailDataInput): {
|
|
11
|
+
activeSection: import("@cosmicdrift/kumiko-framework/ui-types").EditSectionSpec | undefined;
|
|
12
|
+
hasTabs: boolean;
|
|
13
|
+
entity: Pick<import("@cosmicdrift/kumiko-types/fields").EntityDefinition, "fields">;
|
|
14
|
+
detailScreen: import("@cosmicdrift/kumiko-framework/ui-types").EntityEditScreenDefinition;
|
|
15
|
+
detailQuery: import("../hooks/use-query.ts").UseQueryResult<Readonly<Record<string, unknown>>>;
|
|
16
|
+
reloadNonce: number;
|
|
17
|
+
reloadDetail: () => Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { rendererWarn } from "../dev-console.js";
|
|
3
|
+
import { useQuery } from "../hooks/use-query.js";
|
|
4
|
+
import { synthesizeProjectionDetailEntity, synthesizeProjectionDetailScreen, } from "./projection-detail-shim.js";
|
|
5
|
+
import { refetchAfterWrite } from "./row-actions.js";
|
|
6
|
+
export function useProjectionDetailData(input) {
|
|
7
|
+
const { screen, Tabs, nav, effectiveEntityId } = input;
|
|
8
|
+
const idParam = screen.idParam ?? "id";
|
|
9
|
+
const isTabsMode = screen.layout.mode === "tabs";
|
|
10
|
+
const activeSection = useMemo(() => {
|
|
11
|
+
// skip: only tabs mode with a registered Tabs primitive has an active section
|
|
12
|
+
if (!isTabsMode || Tabs === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
const tabParam = nav.searchParams["tab"];
|
|
15
|
+
return (screen.layout.sections.find((section) => section.id === tabParam) ?? screen.layout.sections[0]);
|
|
16
|
+
}, [isTabsMode, Tabs, screen.layout.sections, nav.searchParams]);
|
|
17
|
+
const hasTabs = isTabsMode && Tabs !== undefined && activeSection !== undefined;
|
|
18
|
+
// Tabs is an optional Core-Primitive: without it the screen falls back to
|
|
19
|
+
// the stacked all-sections layout instead of silently truncating to section 1.
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (isTabsMode && Tabs === undefined) {
|
|
22
|
+
rendererWarn(`[kumiko] screen "${screen.id}" uses layout.mode: "tabs", but no <Tabs> primitive is registered — the tab strip will not render. createKumikoApp() from kumiko-renderer-web wires it automatically.`);
|
|
23
|
+
}
|
|
24
|
+
}, [isTabsMode, Tabs, screen.id]);
|
|
25
|
+
const entity = useMemo(() => synthesizeProjectionDetailEntity(screen.layout), [screen.layout]);
|
|
26
|
+
const detailScreen = useMemo(() => {
|
|
27
|
+
const source = activeSection !== undefined
|
|
28
|
+
? { ...screen, layout: { ...screen.layout, sections: [activeSection] } }
|
|
29
|
+
: screen;
|
|
30
|
+
return synthesizeProjectionDetailScreen(source);
|
|
31
|
+
}, [screen, activeSection]);
|
|
32
|
+
const detailQuery = useQuery(screen.query, effectiveEntityId !== undefined ? { [idParam]: effectiveEntityId } : {});
|
|
33
|
+
// A writeForm section's handler creates a new record (see EditWriteFormSection's
|
|
34
|
+
// doc) — both the outer record (header/metrics/other sections) and a sibling
|
|
35
|
+
// relatedList section's own independently-fetched rows must reflect it. A
|
|
36
|
+
// query refetch alone only refreshes `record` below; bumping this as
|
|
37
|
+
// <RenderEdit>'s refreshToken makes RelatedListSection's own useQuery
|
|
38
|
+
// re-run too (it has no `live` subscription of its own). Refetch instead of
|
|
39
|
+
// remount (a `key`) so unsaved input in the other writeForm sections and
|
|
40
|
+
// the related lists' local state survive.
|
|
41
|
+
const [reloadNonce, setReloadNonce] = useState(0);
|
|
42
|
+
const reloadDetail = useCallback(async () => {
|
|
43
|
+
await refetchAfterWrite(detailQuery.refetch);
|
|
44
|
+
setReloadNonce((n) => n + 1);
|
|
45
|
+
}, [detailQuery.refetch]);
|
|
46
|
+
return { activeSection, hasTabs, entity, detailScreen, detailQuery, reloadNonce, reloadDetail };
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ProjectionDetailScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { RenderEditAction } from "../components/render-edit.tsx";
|
|
3
|
+
import type { useProjectionDetailData } from "./use-projection-detail-data.ts";
|
|
4
|
+
type DetailLayoutInput = {
|
|
5
|
+
readonly screen: ProjectionDetailScreenDefinition;
|
|
6
|
+
readonly headerActions: readonly RenderEditAction[] | undefined;
|
|
7
|
+
readonly hasTabs: boolean;
|
|
8
|
+
readonly detailScreen: ReturnType<typeof useProjectionDetailData>["detailScreen"];
|
|
9
|
+
};
|
|
10
|
+
export declare function useProjectionDetailLayout(input: DetailLayoutInput): {
|
|
11
|
+
hasMetrics: boolean;
|
|
12
|
+
headerActionsList: readonly RenderEditAction[];
|
|
13
|
+
hasHeaderActions: boolean;
|
|
14
|
+
hasHeaderCard: boolean;
|
|
15
|
+
renderedDetailScreen: import("@cosmicdrift/kumiko-framework/ui-types").EntityEditScreenDefinition;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { screenFillsHeight } from "../screen-fills-height.js";
|
|
3
|
+
const HEADER_CARD_DEFAULT_WIDTH = "4xl";
|
|
4
|
+
export function useProjectionDetailLayout(input) {
|
|
5
|
+
const { screen, headerActions, hasTabs, detailScreen } = input;
|
|
6
|
+
const hasHeader = screen.header !== undefined;
|
|
7
|
+
const hasMetrics = screen.metrics !== undefined && screen.metrics.length > 0;
|
|
8
|
+
// ?? [] rather than threading `headerActions !== undefined` through every
|
|
9
|
+
// use below — an empty array is a safe no-op for .map/.length.
|
|
10
|
+
const headerActionsList = headerActions ?? [];
|
|
11
|
+
const hasHeaderActions = headerActionsList.length > 0;
|
|
12
|
+
const hasHeaderCard = hasHeader || hasMetrics || hasHeaderActions;
|
|
13
|
+
// A header card (metric band, status, actions) needs more room than the
|
|
14
|
+
// 640px form column; screens with only a record title keep the narrow one.
|
|
15
|
+
const widensScreenFormColumn = hasHeaderCard &&
|
|
16
|
+
!hasTabs &&
|
|
17
|
+
screenFillsHeight(screen) &&
|
|
18
|
+
detailScreen.layout.width === undefined;
|
|
19
|
+
const renderedDetailScreen = useMemo(() => widensScreenFormColumn
|
|
20
|
+
? { ...detailScreen, layout: { ...detailScreen.layout, width: HEADER_CARD_DEFAULT_WIDTH } }
|
|
21
|
+
: detailScreen, [detailScreen, widensScreenFormColumn]);
|
|
22
|
+
return { hasMetrics, headerActionsList, hasHeaderActions, hasHeaderCard, renderedDetailScreen };
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ProjectionListScreenDefinition, RowActionNavigate } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { useOptionalDispatcher } from "../context/dispatcher-context.tsx";
|
|
3
|
+
import type { useUserRoles } from "../context/user-roles-context.tsx";
|
|
4
|
+
import type { ListRowViewModel, Translate } from "../headless/index.ts";
|
|
5
|
+
import type { useDrawerAction } from "./action-form-body.tsx";
|
|
6
|
+
import type { useAppFeatures } from "./app-features-context.tsx";
|
|
7
|
+
import type { useClientActionRuntime } from "./client-actions.tsx";
|
|
8
|
+
import type { useNav } from "./nav.tsx";
|
|
9
|
+
import type { useReturnHost } from "./return-to.tsx";
|
|
10
|
+
import type { useProjectionListDefinitions } from "./use-projection-list-definitions.ts";
|
|
11
|
+
type ProjectionListActionsInput = {
|
|
12
|
+
readonly screen: ProjectionListScreenDefinition;
|
|
13
|
+
readonly effectiveTranslate: Translate;
|
|
14
|
+
readonly dispatcher: ReturnType<typeof useOptionalDispatcher>;
|
|
15
|
+
readonly clientActions: ReturnType<typeof useClientActionRuntime>;
|
|
16
|
+
readonly nav: ReturnType<typeof useNav>;
|
|
17
|
+
readonly host: ReturnType<typeof useReturnHost>;
|
|
18
|
+
readonly refreshRows: () => Promise<void>;
|
|
19
|
+
readonly openDrawer: ReturnType<typeof useDrawerAction>["openDrawer"];
|
|
20
|
+
readonly appFeatures: ReturnType<typeof useAppFeatures>;
|
|
21
|
+
readonly userRoles: ReturnType<typeof useUserRoles>;
|
|
22
|
+
readonly defaultEditScreen: ReturnType<typeof useProjectionListDefinitions>["defaultEditScreen"];
|
|
23
|
+
};
|
|
24
|
+
export declare function useProjectionListActions(input: ProjectionListActionsInput): {
|
|
25
|
+
runNavigate: (action: RowActionNavigate, row: ListRowViewModel) => void;
|
|
26
|
+
rowActions: readonly import("../primitives.tsx").DataTableRowAction[] | undefined;
|
|
27
|
+
toolbarActions: readonly import("../components/render-list-toolbar-action.tsx").ToolbarActionButton[] | undefined;
|
|
28
|
+
bulk: import("../data-table-bulk-types.ts").DataTableBulk | undefined;
|
|
29
|
+
emptyContent: import("../components/list-empty-state.tsx").ListEmptyStateContent | undefined;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
import { useListBulk } from "./list-bulk-actions.js";
|
|
3
|
+
import { useListEmptyContent } from "./list-empty-content.js";
|
|
4
|
+
import { buildDefaultEditRowAction, buildProjectionRowActions, buildProjectionToolbarActions, runProjectionRowNavigate, } from "./row-actions.js";
|
|
5
|
+
export function useProjectionListActions(input) {
|
|
6
|
+
const { screen, effectiveTranslate, dispatcher, clientActions, nav, host, refreshRows, openDrawer, appFeatures, userRoles, defaultEditScreen, } = input;
|
|
7
|
+
// Entity targets: see EntityListBody.runNavigate for why the resolution happens
|
|
8
|
+
// in the NavApi impl. Unlike there: NO row["id"]
|
|
9
|
+
// fallback — projectionList rows come from an arbitrary query projection
|
|
10
|
+
// with no guaranteed "id" field. The boot validator therefore enforces an
|
|
11
|
+
// explicit entityId for projectionList entity targets. Same helper
|
|
12
|
+
// relatedList's rowActions reuse (related-list-section.tsx) — a
|
|
13
|
+
// projectionDetail relatedList row has the identical "no guaranteed id"
|
|
14
|
+
// shape.
|
|
15
|
+
const runNavigate = useCallback((action, row) => runProjectionRowNavigate(nav, action, row, host), [nav, host]);
|
|
16
|
+
const defaultEditRowAction = useMemo(() => buildDefaultEditRowAction(defaultEditScreen), [defaultEditScreen]);
|
|
17
|
+
const rowActions = useMemo(() => buildProjectionRowActions({
|
|
18
|
+
rowActions: screen.rowActions,
|
|
19
|
+
translate: effectiveTranslate,
|
|
20
|
+
dispatcher,
|
|
21
|
+
nav,
|
|
22
|
+
refetch: refreshRows,
|
|
23
|
+
openDrawer,
|
|
24
|
+
defaultEditRowAction,
|
|
25
|
+
host,
|
|
26
|
+
clientActions,
|
|
27
|
+
}), [
|
|
28
|
+
screen.rowActions,
|
|
29
|
+
effectiveTranslate,
|
|
30
|
+
dispatcher,
|
|
31
|
+
clientActions,
|
|
32
|
+
nav,
|
|
33
|
+
refreshRows,
|
|
34
|
+
openDrawer,
|
|
35
|
+
defaultEditRowAction,
|
|
36
|
+
host,
|
|
37
|
+
]);
|
|
38
|
+
const toolbarActions = useMemo(() => buildProjectionToolbarActions({
|
|
39
|
+
toolbarActions: screen.toolbarActions,
|
|
40
|
+
translate: effectiveTranslate,
|
|
41
|
+
dispatcher,
|
|
42
|
+
nav,
|
|
43
|
+
refetch: refreshRows,
|
|
44
|
+
openDrawer,
|
|
45
|
+
host,
|
|
46
|
+
access: { appFeatures, userRoles },
|
|
47
|
+
}), [
|
|
48
|
+
screen.toolbarActions,
|
|
49
|
+
effectiveTranslate,
|
|
50
|
+
nav,
|
|
51
|
+
host,
|
|
52
|
+
dispatcher,
|
|
53
|
+
refreshRows,
|
|
54
|
+
openDrawer,
|
|
55
|
+
appFeatures,
|
|
56
|
+
userRoles,
|
|
57
|
+
]);
|
|
58
|
+
const bulk = useListBulk({
|
|
59
|
+
bulkActions: screen.bulkActions,
|
|
60
|
+
rowActions: screen.rowActions,
|
|
61
|
+
translate: effectiveTranslate,
|
|
62
|
+
dispatcher,
|
|
63
|
+
reload: refreshRows,
|
|
64
|
+
});
|
|
65
|
+
const emptyContent = useListEmptyContent(screen.emptyState, effectiveTranslate, {
|
|
66
|
+
nav,
|
|
67
|
+
host,
|
|
68
|
+
dispatcher,
|
|
69
|
+
openDrawer,
|
|
70
|
+
onWriteSuccess: refreshRows,
|
|
71
|
+
});
|
|
72
|
+
return { runNavigate, rowActions, toolbarActions, bulk, emptyContent };
|
|
73
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { Translate } from "../headless/index.ts";
|
|
3
|
+
import type { DataTableFacet } from "../primitives.tsx";
|
|
4
|
+
import { type ResolvedFacetSpec } from "./list-facets.ts";
|
|
5
|
+
import { useScreenListRows } from "./use-screen-list-rows.ts";
|
|
6
|
+
type ProjectionListDataInput = {
|
|
7
|
+
readonly screen: ProjectionListScreenDefinition;
|
|
8
|
+
readonly featureName: string;
|
|
9
|
+
readonly queryPayload: Parameters<typeof useScreenListRows>[0]["payload"];
|
|
10
|
+
readonly limit: number;
|
|
11
|
+
readonly usePager: boolean;
|
|
12
|
+
readonly effectiveTranslate: Translate;
|
|
13
|
+
readonly resolvedFacetSpecs: readonly ResolvedFacetSpec[];
|
|
14
|
+
};
|
|
15
|
+
export declare function useProjectionListData(input: ProjectionListDataInput): {
|
|
16
|
+
listTable: import("./use-screen-list-table.ts").ScreenListTable;
|
|
17
|
+
listRows: import("./use-screen-list-rows.ts").ScreenListRows;
|
|
18
|
+
filterFacets: DataTableFacet[];
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { buildFilterFacets } from "./list-facets.js";
|
|
3
|
+
import { useScreenListRows } from "./use-screen-list-rows.js";
|
|
4
|
+
import { useScreenListTable } from "./use-screen-list-table.js";
|
|
5
|
+
export function useProjectionListData(input) {
|
|
6
|
+
const { screen, featureName, queryPayload, limit, usePager, effectiveTranslate, resolvedFacetSpecs, } = input;
|
|
7
|
+
const facetFilterFields = useMemo(() => (screen.facets ?? []).map((facet) => facet.field), [screen.facets]);
|
|
8
|
+
const listTable = useScreenListTable({
|
|
9
|
+
screen,
|
|
10
|
+
featureName,
|
|
11
|
+
payload: queryPayload,
|
|
12
|
+
filterFields: facetFilterFields,
|
|
13
|
+
queryType: screen.query,
|
|
14
|
+
pageSize: limit,
|
|
15
|
+
paging: usePager ? "offset" : "cursor",
|
|
16
|
+
translate: effectiveTranslate,
|
|
17
|
+
});
|
|
18
|
+
const listRows = useScreenListRows({
|
|
19
|
+
queryType: screen.query,
|
|
20
|
+
payload: queryPayload,
|
|
21
|
+
infinite: false,
|
|
22
|
+
aggregates: listTable.aggregates,
|
|
23
|
+
});
|
|
24
|
+
const filterFacets = useMemo(() => buildFilterFacets(resolvedFacetSpecs), [resolvedFacetSpecs]);
|
|
25
|
+
return { listTable, listRows, filterFacets };
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { useUserRoles } from "../context/user-roles-context.tsx";
|
|
3
|
+
import type { useAppFeatures } from "./app-features-context.tsx";
|
|
4
|
+
export declare function useProjectionListDefinitions(screen: ProjectionListScreenDefinition, appFeatures: ReturnType<typeof useAppFeatures>, userRoles: ReturnType<typeof useUserRoles>): {
|
|
5
|
+
defaultEditScreen: import("@cosmicdrift/kumiko-framework/ui-types").EntityEditScreenDefinition | undefined;
|
|
6
|
+
searchable: boolean;
|
|
7
|
+
sortable: boolean;
|
|
8
|
+
paginated: boolean;
|
|
9
|
+
entity: Pick<import("@cosmicdrift/kumiko-types/fields").EntityDefinition, "fields">;
|
|
10
|
+
listScreen: import("@cosmicdrift/kumiko-framework/ui-types").EntityListScreenDefinition;
|
|
11
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { synthesizeProjectionEntity, synthesizeProjectionScreen } from "./projection-list-shim.js";
|
|
3
|
+
import { findEditScreenFor } from "./screen-access.js";
|
|
4
|
+
export function useProjectionListDefinitions(screen, appFeatures, userRoles) {
|
|
5
|
+
const defaultEditScreen = useMemo(() => {
|
|
6
|
+
const detailFor = screen.detailFor;
|
|
7
|
+
// skip: no detail screen declared, so rows get no default edit action
|
|
8
|
+
if (detailFor === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
return findEditScreenFor(detailFor, appFeatures, userRoles);
|
|
11
|
+
}, [appFeatures, screen.detailFor, userRoles]);
|
|
12
|
+
// searchable/sortable/paginated are derived at buildAppSchema time from the
|
|
13
|
+
// query handler's Zod schema — not authored on the screen.
|
|
14
|
+
const searchable = screen.searchable ?? false;
|
|
15
|
+
const sortable = screen.sortable ?? false;
|
|
16
|
+
const paginated = screen.paginated ?? false;
|
|
17
|
+
const entity = useMemo(() => synthesizeProjectionEntity(screen.columns, sortable), [screen.columns, sortable]);
|
|
18
|
+
const listScreen = useMemo(() => synthesizeProjectionScreen(screen), [screen]);
|
|
19
|
+
return { defaultEditScreen, searchable, sortable, paginated, entity, listScreen };
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { Translate } from "../headless/index.ts";
|
|
3
|
+
import { type ListUrlStateApi } from "../hooks/use-list-url-state.ts";
|
|
4
|
+
import type { synthesizeProjectionEntity } from "./projection-list-shim.ts";
|
|
5
|
+
import type { ReferenceFacetOption } from "./reference-facet-bridge.tsx";
|
|
6
|
+
type ProjectionListQueryInput = {
|
|
7
|
+
readonly screen: ProjectionListScreenDefinition;
|
|
8
|
+
readonly featureName: string;
|
|
9
|
+
readonly effectiveTranslate: Translate;
|
|
10
|
+
readonly entity: ReturnType<typeof synthesizeProjectionEntity>;
|
|
11
|
+
readonly searchable: boolean;
|
|
12
|
+
readonly sortable: boolean;
|
|
13
|
+
readonly paginated: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function useProjectionListQuery(input: ProjectionListQueryInput): {
|
|
16
|
+
urlState: ListUrlStateApi;
|
|
17
|
+
activeSearch: string;
|
|
18
|
+
activeSort: import("../primitives.tsx").DataTableSort | null;
|
|
19
|
+
limit: number;
|
|
20
|
+
usePager: boolean;
|
|
21
|
+
facetSpecs: import("./list-facets.ts").ResolvedFacetSpec[];
|
|
22
|
+
handleProjectionFacetOptions: (field: string, options: readonly ReferenceFacetOption[]) => void;
|
|
23
|
+
resolvedFacetSpecs: import("./list-facets.ts").ResolvedFacetSpec[];
|
|
24
|
+
dateRangeFacets: import("../data-table-facet-types.ts").DataTableDateRangeFacet[];
|
|
25
|
+
handleDateRangeChange: (field: string, bound: "from" | "to", value: string) => void;
|
|
26
|
+
queryPayload: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { useListUrlState } from "../hooks/use-list-url-state.js";
|
|
3
|
+
import { buildFilterPayload, mergeReferenceFacetOptions, resolveProjectionFacetSpecs, } from "./list-facets.js";
|
|
4
|
+
import { buildListQueryPayload } from "./screen-shared.js";
|
|
5
|
+
import { useProjectionDateRange } from "./use-projection-date-range.js";
|
|
6
|
+
// Same predicate that makes a header clickable: a stale link onto a
|
|
7
|
+
// sortable:false/unknown column must not reach the server (it answers 400).
|
|
8
|
+
function resolveActiveSort(urlSort, input) {
|
|
9
|
+
const { screen, entity, sortable } = input;
|
|
10
|
+
const urlSortField = urlSort === null ? undefined : entity.fields[urlSort.field];
|
|
11
|
+
const urlSortAllowed = urlSortField !== undefined && "sortable" in urlSortField && urlSortField.sortable === true;
|
|
12
|
+
return sortable ? ((urlSortAllowed ? urlSort : null) ?? screen.defaultSort ?? null) : null;
|
|
13
|
+
}
|
|
14
|
+
export function useProjectionListQuery(input) {
|
|
15
|
+
const { screen, featureName, effectiveTranslate, searchable, paginated } = input;
|
|
16
|
+
const urlState = useListUrlState(screen.id);
|
|
17
|
+
// Gated on the derived capability, not just on whether URL-state happens to
|
|
18
|
+
// carry a value — a stale/hand-crafted URL (?…q=x on a non-searchable
|
|
19
|
+
// screen) must not smuggle a param the query's Zod schema doesn't accept.
|
|
20
|
+
const activeSearch = searchable ? urlState.q : "";
|
|
21
|
+
const activeSort = resolveActiveSort(urlState.sort, input);
|
|
22
|
+
const limit = urlState.pageSize ?? screen.pageSize ?? 50;
|
|
23
|
+
// Pages-mode only (see header comment) — an author-set pagination:
|
|
24
|
+
// "infinite" on a paginated projectionList is silently a no-op today.
|
|
25
|
+
const usePager = paginated && (screen.pagination ?? "pages") === "pages";
|
|
26
|
+
// Facets — a projectionList has no entity, so screen.facets is
|
|
27
|
+
// the only field inventory; resolveProjectionFacetSpecs reshapes +
|
|
28
|
+
// translates its explicit labels into the same ResolvedFacetSpec the
|
|
29
|
+
// entityList adapter produces, so both feed the same
|
|
30
|
+
// buildFilterFacets/buildFilterPayload.
|
|
31
|
+
const facetSpecs = useMemo(() => resolveProjectionFacetSpecs(screen.facets, effectiveTranslate, featureName), [screen.facets, effectiveTranslate, featureName]);
|
|
32
|
+
const [projectionReferenceFacetOptions, setProjectionReferenceFacetOptions] = useState({});
|
|
33
|
+
const handleProjectionFacetOptions = useCallback((field, options) => setProjectionReferenceFacetOptions((prev) => prev[field] === options ? prev : { ...prev, [field]: options }), []);
|
|
34
|
+
const resolvedFacetSpecs = useMemo(() => mergeReferenceFacetOptions(facetSpecs, projectionReferenceFacetOptions), [facetSpecs, projectionReferenceFacetOptions]);
|
|
35
|
+
const filterPayload = useMemo(() => buildFilterPayload(urlState.filters, (field) => facetSpecs.find((spec) => spec.field === field)?.type), [urlState.filters, facetSpecs]);
|
|
36
|
+
const { dateRangePayload, dateRangeFacets, handleDateRangeChange } = useProjectionDateRange(screen.facets, effectiveTranslate, urlState);
|
|
37
|
+
const queryPayload = useMemo(() => {
|
|
38
|
+
const payload = buildListQueryPayload({
|
|
39
|
+
limit,
|
|
40
|
+
search: activeSearch,
|
|
41
|
+
sort: activeSort,
|
|
42
|
+
usePager,
|
|
43
|
+
page: urlState.page,
|
|
44
|
+
useInfinite: false,
|
|
45
|
+
cursor: undefined,
|
|
46
|
+
});
|
|
47
|
+
// Gated on screen.filter/screen.facets being declared (not just on
|
|
48
|
+
// urlState/filterPayload happening to carry a value) — same rule as
|
|
49
|
+
// activeSearch/activeSort above: a stale URL must not smuggle a param
|
|
50
|
+
// the bound query's Zod schema doesn't accept.
|
|
51
|
+
if (screen.filter !== undefined) {
|
|
52
|
+
payload["filter"] = screen.filter;
|
|
53
|
+
}
|
|
54
|
+
if (screen.facets !== undefined && filterPayload.length > 0) {
|
|
55
|
+
payload["filters"] = filterPayload;
|
|
56
|
+
}
|
|
57
|
+
Object.assign(payload, dateRangePayload);
|
|
58
|
+
return payload;
|
|
59
|
+
}, [
|
|
60
|
+
limit,
|
|
61
|
+
activeSearch,
|
|
62
|
+
activeSort,
|
|
63
|
+
usePager,
|
|
64
|
+
urlState.page,
|
|
65
|
+
screen.filter,
|
|
66
|
+
screen.facets,
|
|
67
|
+
filterPayload,
|
|
68
|
+
dateRangePayload,
|
|
69
|
+
]);
|
|
70
|
+
return {
|
|
71
|
+
urlState,
|
|
72
|
+
activeSearch,
|
|
73
|
+
activeSort,
|
|
74
|
+
limit,
|
|
75
|
+
usePager,
|
|
76
|
+
facetSpecs,
|
|
77
|
+
handleProjectionFacetOptions,
|
|
78
|
+
resolvedFacetSpecs,
|
|
79
|
+
dateRangeFacets,
|
|
80
|
+
handleDateRangeChange,
|
|
81
|
+
queryPayload,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { FormValues } from "../headless/index.ts";
|
|
3
|
+
import type { RenderEditModel } from "./render-edit-render-model.ts";
|
|
4
|
+
export declare function renderAllRequiredHint<TValues extends FormValues, TCtx>({ engine: { state }, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
5
|
+
export declare function renderDraftPicker<TValues extends FormValues, TCtx>({ engine: { state, draftActions }, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
6
|
+
export declare function renderWizardProgress<TValues extends FormValues, TCtx>({ engine: { state, steps, sections, nav }, titles, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
7
|
+
export declare function renderTabStrip<TValues extends FormValues, TCtx>({ engine: { state, steps, sections, nav }, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
8
|
+
export declare function renderFormErrorBanner<TValues extends FormValues, TCtx>({ engine: { props, state }, layout, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
9
|
+
export declare function renderRootIssueBanners<TValues extends FormValues, TCtx>({ engine: { state }, layout, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
10
|
+
export declare function renderExtensionErrorBanner<TValues extends FormValues, TCtx>({ engine: { state }, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
11
|
+
export declare function renderActionErrorBanner<TValues extends FormValues, TCtx>({ engine: { state }, }: RenderEditModel<TValues, TCtx>): ReactNode;
|
|
12
|
+
export declare function renderDeleteDialog<TValues extends FormValues, TCtx>({ engine: { props, state }, }: RenderEditModel<TValues, TCtx>): ReactNode;
|