@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.
Files changed (230) hide show
  1. package/dist/app/projection-detail-body.js +59 -394
  2. package/dist/app/projection-detail-header-model.d.ts +11 -0
  3. package/dist/app/projection-detail-header-model.js +33 -0
  4. package/dist/app/projection-detail-header-region.d.ts +28 -0
  5. package/dist/app/projection-detail-header-region.js +81 -0
  6. package/dist/app/projection-detail-header.d.ts +29 -0
  7. package/dist/app/projection-detail-header.js +84 -0
  8. package/dist/app/projection-detail-navigation.d.ts +13 -0
  9. package/dist/app/projection-detail-navigation.js +91 -0
  10. package/dist/app/projection-detail-states.d.ts +22 -0
  11. package/dist/app/projection-detail-states.js +26 -0
  12. package/dist/app/projection-drawer-host.d.ts +12 -0
  13. package/dist/app/projection-drawer-host.js +10 -0
  14. package/dist/app/projection-list-body.d.ts +1 -1
  15. package/dist/app/projection-list-body.js +69 -189
  16. package/dist/app/projection-list-render-props.d.ts +56 -0
  17. package/dist/app/projection-list-render-props.js +47 -0
  18. package/dist/app/projection-list-render.d.ts +26 -0
  19. package/dist/app/projection-list-render.js +20 -0
  20. package/dist/app/projection-list-row-click.d.ts +12 -0
  21. package/dist/app/projection-list-row-click.js +14 -0
  22. package/dist/app/use-projection-detail-actions.d.ts +35 -0
  23. package/dist/app/use-projection-detail-actions.js +156 -0
  24. package/dist/app/use-projection-detail-data.d.ts +19 -0
  25. package/dist/app/use-projection-detail-data.js +47 -0
  26. package/dist/app/use-projection-detail-layout.d.ts +17 -0
  27. package/dist/app/use-projection-detail-layout.js +23 -0
  28. package/dist/app/use-projection-list-actions.d.ts +31 -0
  29. package/dist/app/use-projection-list-actions.js +73 -0
  30. package/dist/app/use-projection-list-data.d.ts +20 -0
  31. package/dist/app/use-projection-list-data.js +26 -0
  32. package/dist/app/use-projection-list-definitions.d.ts +11 -0
  33. package/dist/app/use-projection-list-definitions.js +20 -0
  34. package/dist/app/use-projection-list-query.d.ts +28 -0
  35. package/dist/app/use-projection-list-query.js +83 -0
  36. package/dist/components/render-edit-banners.d.ts +12 -0
  37. package/dist/components/render-edit-banners.js +90 -0
  38. package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
  39. package/dist/components/render-edit-draft-save-helpers.js +42 -0
  40. package/dist/components/render-edit-field-grid.d.ts +4 -0
  41. package/dist/components/render-edit-field-grid.js +40 -0
  42. package/dist/components/render-edit-fields-section.d.ts +4 -0
  43. package/dist/components/render-edit-fields-section.js +76 -0
  44. package/dist/components/render-edit-form-actions.d.ts +4 -0
  45. package/dist/components/render-edit-form-actions.js +66 -0
  46. package/dist/components/render-edit-header-actions.d.ts +11 -0
  47. package/dist/components/render-edit-header-actions.js +120 -0
  48. package/dist/components/render-edit-layout-flags.d.ts +29 -0
  49. package/dist/components/render-edit-layout-flags.js +106 -0
  50. package/dist/components/render-edit-layout.d.ts +4 -0
  51. package/dist/components/render-edit-layout.js +79 -0
  52. package/dist/components/render-edit-render-model.d.ts +63 -0
  53. package/dist/components/render-edit-render-model.js +14 -0
  54. package/dist/components/render-edit-section-kinds.d.ts +4 -0
  55. package/dist/components/render-edit-section-kinds.js +115 -0
  56. package/dist/components/render-edit-submit-result.d.ts +8 -0
  57. package/dist/components/render-edit-submit-result.js +77 -0
  58. package/dist/components/render-edit-submit.d.ts +7 -0
  59. package/dist/components/render-edit-submit.js +113 -0
  60. package/dist/components/render-edit-titles.d.ts +17 -0
  61. package/dist/components/render-edit-titles.js +99 -0
  62. package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
  63. package/dist/components/render-edit-wizard-navigation.js +115 -0
  64. package/dist/components/render-edit.d.ts +2 -2
  65. package/dist/components/render-edit.js +63 -1128
  66. package/dist/components/render-field-choice-inputs.d.ts +7 -0
  67. package/dist/components/render-field-choice-inputs.js +52 -0
  68. package/dist/components/render-field-control.d.ts +12 -0
  69. package/dist/components/render-field-control.js +93 -0
  70. package/dist/components/render-field-display-text.d.ts +4 -0
  71. package/dist/components/render-field-display-text.js +48 -0
  72. package/dist/components/render-field-fill-action.d.ts +13 -0
  73. package/dist/components/render-field-fill-action.js +63 -0
  74. package/dist/components/render-field-input-shared.d.ts +39 -0
  75. package/dist/components/render-field-input-shared.js +20 -0
  76. package/dist/components/render-field-input.d.ts +3 -0
  77. package/dist/components/render-field-input.js +34 -0
  78. package/dist/components/render-field-meta.d.ts +6 -0
  79. package/dist/components/render-field-meta.js +51 -0
  80. package/dist/components/render-field-read-only.d.ts +11 -0
  81. package/dist/components/render-field-read-only.js +75 -0
  82. package/dist/components/render-field-reference.d.ts +15 -0
  83. package/dist/components/render-field-reference.js +103 -0
  84. package/dist/components/render-field-scalar-inputs.d.ts +10 -0
  85. package/dist/components/render-field-scalar-inputs.js +35 -0
  86. package/dist/components/render-field-text-inputs.d.ts +3 -0
  87. package/dist/components/render-field-text-inputs.js +34 -0
  88. package/dist/components/render-field-values.d.ts +18 -0
  89. package/dist/components/render-field-values.js +111 -0
  90. package/dist/components/render-field-write-only.d.ts +9 -0
  91. package/dist/components/render-field-write-only.js +29 -0
  92. package/dist/components/render-field.d.ts +2 -2
  93. package/dist/components/render-field.js +39 -670
  94. package/dist/components/render-list-header-slot.d.ts +5 -0
  95. package/dist/components/render-list-header-slot.js +30 -0
  96. package/dist/components/render-list-reference-bridge.d.ts +3 -0
  97. package/dist/components/render-list-reference-bridge.js +25 -0
  98. package/dist/components/render-list-reference-columns.d.ts +25 -0
  99. package/dist/components/render-list-reference-columns.js +137 -0
  100. package/dist/components/render-list-table-props.d.ts +52 -0
  101. package/dist/components/render-list-table-props.js +53 -0
  102. package/dist/components/render-list-toolbar-model.d.ts +42 -0
  103. package/dist/components/render-list-toolbar-model.js +82 -0
  104. package/dist/components/render-list-toolbar.d.ts +38 -0
  105. package/dist/components/render-list-toolbar.js +40 -0
  106. package/dist/components/render-list-types.d.ts +113 -0
  107. package/dist/components/render-list-types.js +1 -0
  108. package/dist/components/render-list.d.ts +4 -113
  109. package/dist/components/render-list.js +91 -307
  110. package/dist/components/use-reference-input-state.d.ts +20 -0
  111. package/dist/components/use-reference-input-state.js +85 -0
  112. package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
  113. package/dist/components/use-render-edit-controlled-sync.js +26 -0
  114. package/dist/components/use-render-edit-draft-save.d.ts +29 -0
  115. package/dist/components/use-render-edit-draft-save.js +141 -0
  116. package/dist/components/use-render-edit-form.d.ts +10 -0
  117. package/dist/components/use-render-edit-form.js +101 -0
  118. package/dist/components/use-render-edit-sections.d.ts +15 -0
  119. package/dist/components/use-render-edit-sections.js +100 -0
  120. package/dist/components/use-render-edit-state.d.ts +65 -0
  121. package/dist/components/use-render-edit-state.js +158 -0
  122. package/dist/components/use-render-edit-steps.d.ts +13 -0
  123. package/dist/components/use-render-edit-steps.js +52 -0
  124. package/dist/components/use-render-list-columns.d.ts +18 -0
  125. package/dist/components/use-render-list-columns.js +41 -0
  126. package/dist/components/use-render-list-search.d.ts +4 -0
  127. package/dist/components/use-render-list-search.js +50 -0
  128. package/dist/primitives.d.ts +1 -1
  129. package/dist/web/app/dashboard-body.d.ts +1 -1
  130. package/dist/web/app/dashboard-body.js +42 -246
  131. package/dist/web/app/dashboard-header.d.ts +34 -0
  132. package/dist/web/app/dashboard-header.js +58 -0
  133. package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
  134. package/dist/web/app/dashboard-screen-controls.js +42 -0
  135. package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
  136. package/dist/web/app/dashboard-screen-tiles.js +42 -0
  137. package/dist/web/app/dashboard-visibility.d.ts +42 -0
  138. package/dist/web/app/dashboard-visibility.js +102 -0
  139. package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
  140. package/dist/web/app/use-dashboard-screen-state.js +69 -0
  141. package/dist/web/primitives/data-table/actions-column.js +6 -5
  142. package/dist/web/primitives/data-table/card-body.d.ts +6 -0
  143. package/dist/web/primitives/data-table/card-body.js +32 -0
  144. package/dist/web/primitives/data-table/card-row.d.ts +5 -0
  145. package/dist/web/primitives/data-table/card-row.js +31 -0
  146. package/dist/web/primitives/data-table/card-view.d.ts +3 -0
  147. package/dist/web/primitives/data-table/card-view.js +13 -0
  148. package/dist/web/primitives/data-table/column-resize-handle.js +3 -1
  149. package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
  150. package/dist/web/primitives/data-table/data-cell-props.js +8 -0
  151. package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
  152. package/dist/web/primitives/data-table/row-render-parts.js +35 -0
  153. package/dist/web/primitives/data-table/table-body.d.ts +3 -0
  154. package/dist/web/primitives/data-table/table-body.js +32 -0
  155. package/dist/web/primitives/data-table/table-content.d.ts +3 -0
  156. package/dist/web/primitives/data-table/table-content.js +41 -0
  157. package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
  158. package/dist/web/primitives/data-table/table-data-cell.js +25 -0
  159. package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
  160. package/dist/web/primitives/data-table/table-header-row.js +21 -0
  161. package/dist/web/primitives/data-table/table-view.d.ts +3 -0
  162. package/dist/web/primitives/data-table/table-view.js +25 -0
  163. package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
  164. package/dist/web/primitives/data-table/toolbar.js +53 -0
  165. package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
  166. package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
  167. package/dist/web/primitives/data-table.d.ts +3 -4
  168. package/dist/web/primitives/data-table.js +32 -252
  169. package/dist/web/primitives/file-value-list.js +1 -1
  170. package/dist/web/primitives/form-footer-parts.d.ts +25 -0
  171. package/dist/web/primitives/form-footer-parts.js +55 -0
  172. package/dist/web/primitives/form.js +33 -43
  173. package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
  174. package/dist/web/primitives/input-choice-kinds.js +67 -0
  175. package/dist/web/primitives/input-file-kinds.d.ts +3 -0
  176. package/dist/web/primitives/input-file-kinds.js +6 -0
  177. package/dist/web/primitives/input-number-kinds.d.ts +4 -0
  178. package/dist/web/primitives/input-number-kinds.js +16 -0
  179. package/dist/web/primitives/input-optional-props.d.ts +20 -0
  180. package/dist/web/primitives/input-optional-props.js +24 -0
  181. package/dist/web/primitives/input-select-presentation.d.ts +13 -0
  182. package/dist/web/primitives/input-select-presentation.js +19 -0
  183. package/dist/web/primitives/input-shared.d.ts +22 -0
  184. package/dist/web/primitives/input-shared.js +10 -0
  185. package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
  186. package/dist/web/primitives/input-temporal-kinds.js +10 -0
  187. package/dist/web/primitives/input-text-kinds.d.ts +7 -0
  188. package/dist/web/primitives/input-text-kinds.js +33 -0
  189. package/dist/web/primitives/input.js +35 -161
  190. package/dist/web/widgets/chart-data.d.ts +35 -0
  191. package/dist/web/widgets/chart-data.js +37 -0
  192. package/dist/web/widgets/chart-markers.d.ts +26 -0
  193. package/dist/web/widgets/chart-markers.js +64 -0
  194. package/dist/web/widgets/chart-parts.d.ts +20 -0
  195. package/dist/web/widgets/chart-parts.js +15 -0
  196. package/dist/web/widgets/charts.d.ts +5 -78
  197. package/dist/web/widgets/charts.js +7 -485
  198. package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
  199. package/dist/web/widgets/drawer-panel-class.js +40 -0
  200. package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
  201. package/dist/web/widgets/drawer-resize-controls.js +12 -0
  202. package/dist/web/widgets/drawer-sizing.d.ts +19 -0
  203. package/dist/web/widgets/drawer-sizing.js +60 -0
  204. package/dist/web/widgets/drawer.js +17 -116
  205. package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
  206. package/dist/web/widgets/stacked-area-brush.js +91 -0
  207. package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
  208. package/dist/web/widgets/stacked-area-chart.js +45 -0
  209. package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
  210. package/dist/web/widgets/stacked-area-chrome.js +77 -0
  211. package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
  212. package/dist/web/widgets/stacked-area-geometry.js +83 -0
  213. package/dist/web/widgets/stacked-area-model.d.ts +11 -0
  214. package/dist/web/widgets/stacked-area-model.js +1 -0
  215. package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
  216. package/dist/web/widgets/stacked-area-plot.js +51 -0
  217. package/dist/web/widgets/stacked-area-props.d.ts +83 -0
  218. package/dist/web/widgets/stacked-area-props.js +16 -0
  219. package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
  220. package/dist/web/widgets/stacked-area-svg.js +38 -0
  221. package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
  222. package/dist/web/widgets/stacked-area-tooltip.js +86 -0
  223. package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
  224. package/dist/web/widgets/use-drawer-resize.js +52 -0
  225. package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
  226. package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
  227. package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
  228. package/dist/web/widgets/use-stacked-area-window.js +76 -0
  229. package/package.json +9 -9
  230. 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;