@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,81 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolveRecordTitle } from "../components/render-edit-helpers.js";
3
+ import { requestLeave } from "../leave-guard.js";
4
+ import { HeaderActionsBar } from "./header-actions-bar.js";
5
+ import { buildMetricItems, renderActionErrorBanner, renderBandSubtitle, renderHeaderActionsContent, renderHeaderSubtitleSlot, renderHeaderTitleSlot, renderMetricsBlock, renderStatusBadge, } from "./projection-detail-header.js";
6
+ import { buildDetailTabItems } from "./projection-detail-header-model.js";
7
+ export function deriveDetailRegionPieces(model) {
8
+ const { screen, record, primitives, actionError, hasHeaderActions } = model;
9
+ // Grouped into the head Card alongside title/status/metrics:
10
+ // these are actions on the record the head shows, not on whichever tab is
11
+ // open, so they must stay in place across tab switches instead of
12
+ // trailing the active tab's content in the card footer.
13
+ const headerActionsContent = hasHeaderActions && renderHeaderActionsContent(model);
14
+ // Rendered as RenderEdit's headerRegion (not as JSX siblings before it) so
15
+ // this shares the same page padding/width as the record's edit card below
16
+ // it, instead of sitting flush against the screen edge (fw record-screen
17
+ // header polish).
18
+ const recordTitle = screen.recordTitleField !== undefined
19
+ ? resolveRecordTitle(record[screen.recordTitleField])
20
+ : undefined;
21
+ const headerTitleSlot = renderHeaderTitleSlot(model);
22
+ const headerSubtitleSlot = renderHeaderSubtitleSlot(model);
23
+ const metricItems = buildMetricItems(model);
24
+ const usesPageHeaderSlot = model.pageHeaderSlotAvailable && primitives.PageHeader !== undefined;
25
+ const pageHeaderCompact = model.pageHeaderCompactContext && usesPageHeaderSlot;
26
+ const statusBadge = renderStatusBadge(model);
27
+ const bandSubtitle = renderBandSubtitle(model, statusBadge, headerSubtitleSlot, pageHeaderCompact);
28
+ const actionErrorBanner = actionError !== null && renderActionErrorBanner(model, actionError);
29
+ return {
30
+ PageHeader: usesPageHeaderSlot ? primitives.PageHeader : undefined,
31
+ pageHeaderCompact,
32
+ recordTitle,
33
+ headerTitleSlot,
34
+ headerSubtitleSlot,
35
+ headerActionsContent,
36
+ statusBadge,
37
+ bandSubtitle,
38
+ metricItems,
39
+ actionErrorBanner,
40
+ };
41
+ }
42
+ function renderPageHeaderBranch(model, pieces, headerSlot, PageHeader) {
43
+ const { screen, record, primitives, setActionError, hasHeaderActions, hasMetrics } = model;
44
+ const { Grid, Button, Dialog, ActionOverflowMenu, MetricBand } = primitives;
45
+ const { pageHeaderCompact, recordTitle, statusBadge, bandSubtitle, metricItems } = pieces;
46
+ const header = screen.header;
47
+ return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { ...(header !== undefined && { title: String(record[header.title] ?? "") }), ...(header === undefined && recordTitle !== undefined && { title: recordTitle }), ...(!pageHeaderCompact && statusBadge !== undefined && { status: statusBadge }), ...(hasHeaderActions && {
48
+ actions: (_jsx(Grid, { columns: "auto", testId: "kumiko-screen-projection-detail-actions", children: _jsx(HeaderActionsBar, { actions: model.headerActionsList, Button: Button, Dialog: Dialog, ActionOverflowMenu: ActionOverflowMenu, onError: setActionError, collapseAfterPrimary: true, compact: pageHeaderCompact, PageHeader: PageHeader, ...(screen.primaryAction !== undefined && {
49
+ preferredPrimaryId: screen.primaryAction,
50
+ }) }) })),
51
+ }) }), MetricBand === undefined && bandSubtitle, MetricBand === undefined && headerSlot, (hasMetrics || bandSubtitle !== undefined || headerSlot !== undefined) &&
52
+ (MetricBand !== undefined || hasMetrics) &&
53
+ renderMetricsBlock(model, metricItems, bandSubtitle, MetricBand !== undefined ? headerSlot : undefined), pieces.actionErrorBanner] }));
54
+ }
55
+ function renderHeaderCardBranch(model, pieces, headerSlot) {
56
+ const { primitives, hasHeaderActions, hasMetrics, hasHeaderCard } = model;
57
+ const { Card } = primitives;
58
+ const { recordTitle, headerTitleSlot, headerSubtitleSlot, headerActionsContent } = pieces;
59
+ return ((hasHeaderCard || headerSlot !== undefined || recordTitle !== undefined) && (_jsxs(Card, { slots: {
60
+ ...(headerTitleSlot !== undefined && { title: headerTitleSlot }),
61
+ ...(headerTitleSlot === undefined && recordTitle !== undefined && { title: recordTitle }),
62
+ ...(headerSubtitleSlot !== undefined && { subtitle: headerSubtitleSlot }),
63
+ ...(headerSlot !== undefined && { headerContent: headerSlot }),
64
+ ...(hasHeaderActions && { headerActions: headerActionsContent }),
65
+ }, children: [hasMetrics && renderMetricsBlock(model, pieces.metricItems), pieces.actionErrorBanner] })));
66
+ }
67
+ function renderDetailTabs(model, activeSection, Tabs) {
68
+ const { screen, record, effectiveTranslate, nav } = model;
69
+ return (_jsx(Tabs, { testId: "kumiko-screen-projection-detail-tabs", items: buildDetailTabItems(screen.layout.sections, record, effectiveTranslate), activeId: activeSection.id ?? "", onSelect: (id) => requestLeave(() => nav.setSearchParams({ tab: id })) }));
70
+ }
71
+ export function renderDetailHeaderContent(model, pieces, headerSlot) {
72
+ const { hasTabs, activeSection } = model;
73
+ const { PageHeader } = pieces;
74
+ const { Tabs } = model.primitives;
75
+ return (_jsxs(_Fragment, { children: [PageHeader !== undefined
76
+ ? renderPageHeaderBranch(model, pieces, headerSlot, PageHeader)
77
+ : renderHeaderCardBranch(model, pieces, headerSlot), hasTabs &&
78
+ activeSection !== undefined &&
79
+ Tabs !== undefined &&
80
+ renderDetailTabs(model, activeSection, Tabs)] }));
81
+ }
@@ -0,0 +1,29 @@
1
+ import type { ProjectionDetailScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { type Dispatch, type ReactNode, type SetStateAction } from "react";
3
+ import type { RenderEditAction } from "../components/render-edit.tsx";
4
+ import type { Translate } from "../headless/index.ts";
5
+ import type { usePrimitives } from "../primitives.tsx";
6
+ import { type ErrorNotice } from "./error-reference.tsx";
7
+ import type { FeatureSchema } from "./feature-schema.ts";
8
+ import type { NavApi } from "./nav.tsx";
9
+ import type { ReturnHost } from "./return-to.tsx";
10
+ export type DetailHeaderModel = {
11
+ readonly screen: ProjectionDetailScreenDefinition;
12
+ readonly record: Readonly<Record<string, unknown>>;
13
+ readonly primitives: ReturnType<typeof usePrimitives>;
14
+ readonly effectiveTranslate: Translate;
15
+ readonly nav: NavApi;
16
+ readonly appFeatures: readonly FeatureSchema[];
17
+ readonly userRoles: readonly string[] | undefined;
18
+ readonly host: ReturnHost | undefined;
19
+ readonly headerActionsList: readonly RenderEditAction[];
20
+ readonly setActionError: Dispatch<SetStateAction<ErrorNotice | null>>;
21
+ };
22
+ export declare function renderHeaderActionsContent(model: DetailHeaderModel): ReactNode;
23
+ export declare function renderHeaderTitleSlot(model: DetailHeaderModel): ReactNode;
24
+ export declare function renderHeaderSubtitleSlot(model: DetailHeaderModel): ReactNode;
25
+ export declare function buildMetricItems(model: DetailHeaderModel): ReactNode[] | undefined;
26
+ export declare function renderMetricsBlock(model: DetailHeaderModel, metricItems: ReactNode[] | undefined, subtitle?: ReactNode, lead?: ReactNode): ReactNode;
27
+ export declare function renderStatusBadge(model: DetailHeaderModel): ReactNode;
28
+ export declare function renderBandSubtitle(model: DetailHeaderModel, statusBadge: ReactNode, headerSubtitleSlot: ReactNode, pageHeaderCompact: boolean): ReactNode;
29
+ export declare function renderActionErrorBanner(model: DetailHeaderModel, actionError: ErrorNotice): ReactNode;
@@ -0,0 +1,84 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment } from "react";
3
+ import { ErrorReference } from "./error-reference.js";
4
+ import { HeaderActionsBar } from "./header-actions-bar.js";
5
+ import { headerStatusTone, resolveSubtitleHref } from "./projection-detail-header-model.js";
6
+ import { buildSubtitleParts, runMetricNavigate } from "./projection-detail-navigation.js";
7
+ import { navigateTargetAllows } from "./screen-access.js";
8
+ export function renderHeaderActionsContent(model) {
9
+ const { screen, primitives, headerActionsList, setActionError } = model;
10
+ const { Grid, Button, Dialog, ActionOverflowMenu } = primitives;
11
+ return (_jsx(Grid, { columns: "auto", testId: "kumiko-screen-projection-detail-actions", children: _jsx(HeaderActionsBar, { actions: headerActionsList, Button: Button, Dialog: Dialog, ActionOverflowMenu: ActionOverflowMenu, onError: setActionError, ...(screen.primaryAction !== undefined && { preferredPrimaryId: screen.primaryAction }) }) }));
12
+ }
13
+ // Plain Text, not Heading — the Card title slot already renders as its own
14
+ // heading element (h3); nesting another heading (h1) inside it is invalid HTML.
15
+ export function renderHeaderTitleSlot(model) {
16
+ const { screen, record, primitives } = model;
17
+ const { Grid, Text, StatusBadge } = primitives;
18
+ const header = screen.header;
19
+ return header !== undefined ? (header.status !== undefined ? (_jsxs(Grid, { columns: "auto", children: [_jsx(Text, { testId: "kumiko-screen-projection-detail-title", children: String(record[header.title] ?? "") }), StatusBadge !== undefined ? (_jsx(StatusBadge, { value: String(record[header.status] ?? ""), tone: headerStatusTone(header, record), testId: "kumiko-screen-projection-detail-status" })) : (_jsx(Text, { testId: "kumiko-screen-projection-detail-status", children: String(record[header.status] ?? "") }))] })) : (_jsx(Text, { testId: "kumiko-screen-projection-detail-title", children: String(record[header.title] ?? "") }))) : undefined;
20
+ }
21
+ function renderSubtitleParts(model, subtitleParts) {
22
+ const { Text, Link } = model.primitives;
23
+ return (_jsx(Text, { testId: "kumiko-screen-projection-detail-subtitle", children: subtitleParts.map((part, index) => (_jsxs(Fragment, { children: [index > 0 && (_jsx(Text, { variant: "muted", decorative: true, children: " · " })), part.href !== undefined ? (_jsx(Link, { href: part.href, ...(part.onPress !== undefined && { onPress: part.onPress }), className: "underline", testId: `kumiko-screen-projection-detail-subtitle-${part.field}`, children: part.text })) : (_jsx(Text, { testId: `kumiko-screen-projection-detail-subtitle-${part.field}`, children: part.text }))] }, part.field))) }));
24
+ }
25
+ function renderSubtitleField(model, subtitleField) {
26
+ const { screen, record, primitives } = model;
27
+ const { Text, Link } = primitives;
28
+ const subtitleHref = resolveSubtitleHref(screen.header, record);
29
+ return subtitleHref !== undefined ? (_jsx(Link, { href: subtitleHref, target: "_blank", testId: "kumiko-screen-projection-detail-subtitle", children: String(record[subtitleField] ?? "") })) : (_jsx(Text, { variant: "muted", testId: "kumiko-screen-projection-detail-subtitle", children: String(record[subtitleField] ?? "") }));
30
+ }
31
+ export function renderHeaderSubtitleSlot(model) {
32
+ const { screen, record, nav, appFeatures, userRoles, host } = model;
33
+ const header = screen.header;
34
+ const subtitleParts = header !== undefined && typeof header.subtitle === "object"
35
+ ? buildSubtitleParts(header.subtitle, record, nav, appFeatures, userRoles, host)
36
+ : undefined;
37
+ return subtitleParts !== undefined
38
+ ? subtitleParts.length > 0
39
+ ? renderSubtitleParts(model, subtitleParts)
40
+ : undefined
41
+ : typeof header?.subtitle === "string"
42
+ ? renderSubtitleField(model, header.subtitle)
43
+ : undefined;
44
+ }
45
+ // slots.header shares the header card with the actions (one card, actions
46
+ // top right) instead of rendering as an unframed block above it.
47
+ export function buildMetricItems(model) {
48
+ const { screen, record, primitives, effectiveTranslate, nav, appFeatures, userRoles, host } = model;
49
+ const { Metric, GridCell, Text } = primitives;
50
+ return screen.metrics?.map((metric) => {
51
+ const field = metric.field;
52
+ const label = effectiveTranslate(metric.label);
53
+ const value = String(record[field] ?? "");
54
+ const testId = `kumiko-screen-projection-detail-metric-${field}`;
55
+ const navigate = metric.navigate;
56
+ const onPress = navigate !== undefined && navigateTargetAllows(navigate, appFeatures, userRoles)
57
+ ? () => runMetricNavigate(nav, navigate, record, host)
58
+ : undefined;
59
+ return Metric !== undefined ? (_jsx(Metric, { label: label, value: value, testId: testId, ...(onPress !== undefined && { onPress }) }, field)) : (_jsxs(GridCell, { children: [_jsx(Text, { variant: "small", testId: `${testId}-label`, children: label }), _jsx(Text, { testId: `${testId}-value`, children: value })] }, field));
60
+ });
61
+ }
62
+ // Metrics are a dl band when the primitive exists; the old Grid stays as the fallback.
63
+ export function renderMetricsBlock(model, metricItems, subtitle, lead) {
64
+ const { screen, primitives } = model;
65
+ const { MetricBand, Grid } = primitives;
66
+ return MetricBand !== undefined ? (_jsx(MetricBand, { testId: "kumiko-screen-projection-detail-metrics", subtitle: subtitle, ...(lead !== undefined && { lead }), children: metricItems })) : (_jsx(Grid, { columns: screen.metrics?.length ?? 1, testId: "kumiko-screen-projection-detail-metrics", children: metricItems }));
67
+ }
68
+ // The phone shell header clips a long status mid-word, so on phones the badge
69
+ // sits in the body instead of the shell header slot.
70
+ export function renderStatusBadge(model) {
71
+ const { screen, record, primitives } = model;
72
+ const { StatusBadge } = primitives;
73
+ const header = screen.header;
74
+ return header?.status !== undefined && StatusBadge !== undefined ? (_jsx(StatusBadge, { value: String(record[header.status] ?? ""), tone: headerStatusTone(header, record), testId: "kumiko-screen-projection-detail-status" })) : undefined;
75
+ }
76
+ // Lives in the band's subtitle row so it shares the metrics' horizontal inset.
77
+ export function renderBandSubtitle(model, statusBadge, headerSubtitleSlot, pageHeaderCompact) {
78
+ const { Grid } = model.primitives;
79
+ return pageHeaderCompact && statusBadge !== undefined ? (_jsxs(_Fragment, { children: [_jsx(Grid, { columns: "auto", testId: "kumiko-screen-projection-detail-status-row", children: statusBadge }), headerSubtitleSlot] })) : (headerSubtitleSlot);
80
+ }
81
+ export function renderActionErrorBanner(model, actionError) {
82
+ const { Banner } = model.primitives;
83
+ return (_jsxs(Banner, { variant: "error", testId: "render-edit-action-error", children: [actionError.message, _jsx(ErrorReference, { requestId: actionError.requestId })] }));
84
+ }
@@ -0,0 +1,13 @@
1
+ import type { MetricNavigate, RecordHeaderSubtitlePart } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { FeatureSchema } from "./feature-schema.ts";
3
+ import type { NavApi } from "./nav.tsx";
4
+ import type { ReturnHost } from "./return-to.tsx";
5
+ export declare function runMetricNavigate(nav: NavApi, navigate: MetricNavigate, record: Readonly<Record<string, unknown>>, host: ReturnHost | undefined): void;
6
+ export declare function isAbsoluteHttpUrl(value: string): boolean;
7
+ export type SubtitlePartView = {
8
+ readonly field: string;
9
+ readonly text: string;
10
+ readonly href?: string;
11
+ readonly onPress?: () => void;
12
+ };
13
+ export declare function buildSubtitleParts(parts: readonly RecordHeaderSubtitlePart[], record: Readonly<Record<string, unknown>>, nav: NavApi, appFeatures: readonly FeatureSchema[], userRoles: readonly string[] | undefined, host: ReturnHost | undefined): SubtitlePartView[];
@@ -0,0 +1,91 @@
1
+ import { isSafeHref } from "../headless/index.js";
2
+ import { requestLeave } from "../leave-guard.js";
3
+ import { runProjectionRowNavigate } from "./row-actions.js";
4
+ import { navigateTargetAllows } from "./screen-access.js";
5
+ // Reuses runProjectionRowNavigate (row-actions.ts) instead of re-deriving
6
+ // navigate execution — a metric click has the same entity/screen/params
7
+ // shape as a RowActionNavigate, minus the fields (id, label, style) that
8
+ // function never reads.
9
+ export function runMetricNavigate(nav, navigate, record, host) {
10
+ // tab alone (no screen/entity) stays on the current record and just
11
+ // activates that tab — no route change, so runProjectionRowNavigate
12
+ // (which always navigates) doesn't apply here.
13
+ if (navigate.screen === undefined && navigate.entity === undefined) {
14
+ const { tab } = navigate;
15
+ if (tab !== undefined)
16
+ requestLeave(() => nav.setSearchParams({ tab }));
17
+ // skip: tab-only metric navigation never leaves the record
18
+ return;
19
+ }
20
+ const base = { kind: "navigate", id: "metric-navigate", label: "" };
21
+ const action = navigate.entity !== undefined
22
+ ? {
23
+ ...base,
24
+ entity: navigate.entity,
25
+ ...(navigate.entityId !== undefined && { entityId: navigate.entityId }),
26
+ ...(navigate.params !== undefined && { params: navigate.params }),
27
+ ...(navigate.tab !== undefined && { tab: navigate.tab }),
28
+ }
29
+ : navigate.screen !== undefined
30
+ ? {
31
+ ...base,
32
+ screen: navigate.screen,
33
+ ...(navigate.entityId !== undefined && { entityId: navigate.entityId }),
34
+ ...(navigate.params !== undefined && { params: navigate.params }),
35
+ ...(navigate.tab !== undefined && { tab: navigate.tab }),
36
+ }
37
+ : undefined;
38
+ // skip: a navigate without entity or screen target has nothing to open
39
+ if (action === undefined)
40
+ return;
41
+ runProjectionRowNavigate(nav, action, { id: "", values: record }, host);
42
+ }
43
+ // Absolute http(s) check for RecordHeaderSpec.subtitleHref — deliberately
44
+ // stricter than isSafeHref (which also allows relative paths and mailto:),
45
+ // since a relative/mailto value here should render as plain text, not a
46
+ // "_blank" external link.
47
+ export function isAbsoluteHttpUrl(value) {
48
+ return /^https?:\/\//i.test(value) && isSafeHref(value);
49
+ }
50
+ // hrefFor throws for an entity without a detailFor screen; outside
51
+ // createKumikoApp the feature list is empty, so navigateTargetAllows can't
52
+ // rule that out beforehand. The NavApi owns target resolution, so asking it
53
+ // and falling back to plain text covers both cases.
54
+ function hrefForNavigate(nav, navigate, record) {
55
+ const entityId = navigate.entityId !== undefined ? String(record[navigate.entityId] ?? "") : undefined;
56
+ try {
57
+ if (navigate.entity !== undefined) {
58
+ return entityId === undefined || entityId === ""
59
+ ? undefined
60
+ : nav.hrefFor({ entity: navigate.entity, id: entityId });
61
+ }
62
+ if (navigate.screen !== undefined) {
63
+ return nav.hrefFor({
64
+ screenId: navigate.screen,
65
+ ...(entityId !== undefined && entityId !== "" && { entityId }),
66
+ });
67
+ }
68
+ }
69
+ catch {
70
+ return undefined;
71
+ }
72
+ return undefined;
73
+ }
74
+ export function buildSubtitleParts(parts, record, nav, appFeatures, userRoles, host) {
75
+ return parts.flatMap((part) => {
76
+ const field = typeof part === "string" ? part : part.field;
77
+ const text = String(record[field] ?? "");
78
+ // skip: empty subtitle parts render nothing
79
+ if (text === "")
80
+ return [];
81
+ const navigate = typeof part === "string" ? undefined : part.navigate;
82
+ if (navigate === undefined || !navigateTargetAllows(navigate, appFeatures, userRoles)) {
83
+ return [{ field, text }];
84
+ }
85
+ const href = hrefForNavigate(nav, navigate, record);
86
+ // skip: unresolvable targets fall back to plain text
87
+ if (href === undefined)
88
+ return [{ field, text }];
89
+ return [{ field, text, href, onPress: () => runMetricNavigate(nav, navigate, record, host) }];
90
+ });
91
+ }
@@ -0,0 +1,22 @@
1
+ import type { ProjectionDetailScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { ReactNode } from "react";
3
+ import type { Translate } from "../headless/index.ts";
4
+ import type { usePrimitives } from "../primitives.tsx";
5
+ import type { FeatureSchema } from "./feature-schema.ts";
6
+ import type { NavApi } from "./nav.tsx";
7
+ import { QueryErrorBanner } from "./query-state-banners.tsx";
8
+ import { type useReturnTarget } from "./return-to.tsx";
9
+ type DetailScreen = ProjectionDetailScreenDefinition & {
10
+ readonly detailFor?: string;
11
+ };
12
+ export declare function renderMissingIdBanner(primitives: Pick<ReturnType<typeof usePrimitives>, "Banner" | "Text">, screen: DetailScreen): ReactNode;
13
+ export declare function renderDetailQueryError(error: Parameters<typeof QueryErrorBanner>[0]["error"], translate: Translate, onRetry: () => Promise<void>): ReactNode;
14
+ type NotFoundModel = {
15
+ readonly schema: FeatureSchema;
16
+ readonly screen: DetailScreen;
17
+ readonly nav: NavApi;
18
+ readonly returnTarget: ReturnType<typeof useReturnTarget> | undefined;
19
+ readonly effectiveTranslate: Translate;
20
+ };
21
+ export declare function renderRecordNotFound(model: NotFoundModel): ReactNode;
22
+ export {};
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { lastSegment } from "./qn.js";
3
+ import { QueryErrorBanner } from "./query-state-banners.js";
4
+ import { navigateToReturnOr } from "./return-to.js";
5
+ import { findEntityListScreen, RecordNotFoundState } from "./screen-shared.js";
6
+ export function renderMissingIdBanner(primitives, screen) {
7
+ const { Banner, Text } = primitives;
8
+ return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-projection-detail-missing-id", children: ["Screen ", _jsx(Text, { variant: "code", children: screen.id }), " (projectionDetail) needs a row id in the path \u2014 open it from a row action."] }));
9
+ }
10
+ export function renderDetailQueryError(error, translate, onRetry) {
11
+ return _jsx(QueryErrorBanner, { error: error, translate: translate, onRetry: onRetry });
12
+ }
13
+ export function renderRecordNotFound(model) {
14
+ const { schema, screen, nav, returnTarget, effectiveTranslate } = model;
15
+ const backListScreen = screen.listScreenId ??
16
+ (screen.detailFor !== undefined
17
+ ? findEntityListScreen(schema, screen.detailFor)?.id
18
+ : undefined);
19
+ return (_jsx(RecordNotFoundState, { translate: effectiveTranslate, ...((returnTarget !== undefined || backListScreen !== undefined) && {
20
+ onBack: () => navigateToReturnOr(nav, returnTarget, () => {
21
+ if (backListScreen !== undefined) {
22
+ nav.navigate({ screenId: lastSegment(backListScreen) });
23
+ }
24
+ }),
25
+ }) }));
26
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Translate } from "../headless/index.ts";
3
+ import { DrawerHost, type useDrawerAction } from "./action-form-body.tsx";
4
+ import type { FeatureSchema } from "./feature-schema.ts";
5
+ export type ProjectionDrawerModel = {
6
+ readonly schema: FeatureSchema;
7
+ readonly translate: Translate | undefined;
8
+ readonly userRoles: Parameters<typeof DrawerHost>[0]["userRoles"];
9
+ readonly drawer: Pick<ReturnType<typeof useDrawerAction>, "drawerAction" | "drawerScreen" | "drawerInitialValues" | "closeDrawer">;
10
+ readonly refreshRows: () => Promise<void>;
11
+ };
12
+ export declare function renderProjectionDrawerHost(model: ProjectionDrawerModel): ReactNode;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DrawerHost } from "./action-form-body.js";
3
+ export function renderProjectionDrawerHost(model) {
4
+ const { schema, translate, userRoles, drawer, refreshRows } = model;
5
+ const { drawerAction, drawerScreen, drawerInitialValues, closeDrawer } = drawer;
6
+ return (_jsx(DrawerHost, { schema: schema, drawerAction: drawerAction, drawerScreen: drawerScreen, ...(drawerInitialValues !== undefined && { drawerInitialValues }), userRoles: userRoles, ...(translate !== undefined && { translate }), onClose: closeDrawer, onSuccess: () => {
7
+ closeDrawer();
8
+ void refreshRows();
9
+ } }));
10
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ProjectionListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
- import { type ReactNode } from "react";
2
+ import type { ReactNode } from "react";
3
3
  import type { ListRowViewModel, Translate } from "../headless/index.ts";
4
4
  import type { FeatureSchema } from "./feature-schema.ts";
5
5
  export declare function ProjectionListBody({ schema, screen, translate, onRowClick, }: {