@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,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 {
|
|
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, }: {
|