@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,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { extensionSectionName, useExtensionSectionComponent } from "../app/extension-sections.js";
|
|
4
|
+
import { rendererWarn } from "../dev-console.js";
|
|
5
|
+
// Header slot in the list toolbar (toolbarEnd): `screen.slots.header` is
|
|
6
|
+
// a PlatformComponent (`{ react: { __component: "X" } }`) resolved through
|
|
7
|
+
// the same ExtensionSectionsProvider registry as entityEdit sections.
|
|
8
|
+
// entityId is null in the list context because there is no row; the component
|
|
9
|
+
// loads its own data (for example, a cap counter from a usage query). A
|
|
10
|
+
// separate component is required by the Rules of Hooks because
|
|
11
|
+
// useExtensionSectionComponent must not run conditionally in RenderList.
|
|
12
|
+
export function ListHeaderSlotMount({ screen, }) {
|
|
13
|
+
const header = screen.slots?.header;
|
|
14
|
+
const name = header !== undefined ? extensionSectionName(header) : undefined;
|
|
15
|
+
const Component = useExtensionSectionComponent(name);
|
|
16
|
+
// A declared-but-unregistered header slot renders nothing; warn so the author
|
|
17
|
+
// finds the typo / missing clientFeatures.extensionSectionComponents entry,
|
|
18
|
+
// mirroring the diagnostic banner in render-edit's ExtensionSectionMount
|
|
19
|
+
// (the toolbar has no room for a banner, so a dev-warn it is).
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (name !== undefined && Component === undefined) {
|
|
22
|
+
rendererWarn(`[kumiko] List header slot component "${name}" on screen "${screen.id}" is not registered in clientFeatures.extensionSectionComponents — the header slot renders nothing.`);
|
|
23
|
+
}
|
|
24
|
+
}, [name, Component, screen.id]);
|
|
25
|
+
if (Component === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
// screenId lets a header control drive this list's URL-filter state
|
|
28
|
+
// (useListUrlState) — e.g. a tag filter narrowing the list to an id-set.
|
|
29
|
+
return _jsx(Component, { entityName: screen.entity, entityId: null, screenId: screen.id });
|
|
30
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { PublishedReferenceLookup, ReferenceColumnSpec } from "./render-list-reference-columns.ts";
|
|
3
|
+
export declare function renderReferenceLookupBridges(referenceColumns: readonly ReferenceColumnSpec[], onMap: (field: string, lookup: PublishedReferenceLookup) => void): ReactNode;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { useReferenceLookup } from "../hooks/use-reference-lookup.js";
|
|
4
|
+
// Bridge component for each reference column. It is mounted once per
|
|
5
|
+
// column, calls useReferenceLookup unconditionally, and publishes the map
|
|
6
|
+
// to the parent through onMap. React manages mounting and unmounting when
|
|
7
|
+
// the schema changes through the key prop in the parent's map loop.
|
|
8
|
+
function ReferenceLookupBridge({ field, refEntity, labelField, featureName, onMap, }) {
|
|
9
|
+
const lookup = useReferenceLookup(featureName, refEntity, labelField);
|
|
10
|
+
// Use an effect rather than calling directly so setState runs outside the
|
|
11
|
+
// render path; otherwise React warns about updating one component while
|
|
12
|
+
// rendering another.
|
|
13
|
+
// Do not publish while loading or after a failed lookup: a missing map means
|
|
14
|
+
// "not resolved", whereas an empty published map means "nothing found".
|
|
15
|
+
// Only the latter may display "Unknown".
|
|
16
|
+
const resolved = !lookup.loading && !lookup.failed;
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (resolved)
|
|
19
|
+
onMap(field, { map: lookup.map, complete: lookup.complete });
|
|
20
|
+
}, [field, lookup.map, lookup.complete, resolved, onMap]);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
export function renderReferenceLookupBridges(referenceColumns, onMap) {
|
|
24
|
+
return referenceColumns.map((rc) => (_jsx(ReferenceLookupBridge, { field: rc.field, refEntity: rc.refEntity, labelField: rc.labelField, featureName: rc.refFeature, onMap: onMap }, rc.field)));
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { useReferenceLinkResolver } from "../app/reference-link.ts";
|
|
2
|
+
import type { ListColumnViewModel, Translate } from "../headless/index.ts";
|
|
3
|
+
import type { ReferenceLookupMap } from "../hooks/use-reference-lookup.ts";
|
|
4
|
+
export type PublishedReferenceLookup = {
|
|
5
|
+
readonly map: ReferenceLookupMap;
|
|
6
|
+
readonly complete: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type ReferenceColumnSpec = {
|
|
9
|
+
readonly field: string;
|
|
10
|
+
readonly refEntity: string;
|
|
11
|
+
readonly refFeature: string;
|
|
12
|
+
readonly labelField: string;
|
|
13
|
+
};
|
|
14
|
+
type ResolveReferenceLink = ReturnType<typeof useReferenceLinkResolver>;
|
|
15
|
+
export declare function selectReferenceColumns(columns: readonly ListColumnViewModel[], featureName: string): ReferenceColumnSpec[];
|
|
16
|
+
export type EnrichReferenceColumnsInput = {
|
|
17
|
+
readonly columns: readonly ListColumnViewModel[];
|
|
18
|
+
readonly referenceColumns: readonly ReferenceColumnSpec[];
|
|
19
|
+
readonly referenceLookups: Readonly<Record<string, PublishedReferenceLookup>>;
|
|
20
|
+
readonly resolveReferenceLink: ResolveReferenceLink;
|
|
21
|
+
readonly featureName: string;
|
|
22
|
+
readonly translate: Translate;
|
|
23
|
+
};
|
|
24
|
+
export declare function enrichReferenceColumns(input: EnrichReferenceColumnsInput): readonly ListColumnViewModel[];
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { UNRESOLVED_REFERENCE_LABEL_KEYS } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { EMPTY_VALUE_PLACEHOLDER } from "../headless/index.js";
|
|
3
|
+
function isPlainRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
// The server leaves a missing or unreadable target out of `_refs`, and a null
|
|
7
|
+
// entry means the same: both read as unresolved.
|
|
8
|
+
function eagerloadedRef(row, field) {
|
|
9
|
+
// skip: row carries no eager-loaded refs, so the lookup map decides
|
|
10
|
+
if (!isPlainRecord(row) || !isPlainRecord(row["_refs"]))
|
|
11
|
+
return undefined;
|
|
12
|
+
const ref = row["_refs"][field];
|
|
13
|
+
if (Array.isArray(ref))
|
|
14
|
+
return ref.filter(isPlainRecord);
|
|
15
|
+
return isPlainRecord(ref) ? ref : undefined;
|
|
16
|
+
}
|
|
17
|
+
export function selectReferenceColumns(columns, featureName) {
|
|
18
|
+
return columns
|
|
19
|
+
.filter((c) => c.type === "reference" && c.refEntity !== undefined)
|
|
20
|
+
.map((c) => ({
|
|
21
|
+
field: c.field,
|
|
22
|
+
refEntity: c.refEntity ?? "",
|
|
23
|
+
// refFeature comes from parseRefTarget in the ViewModel
|
|
24
|
+
// (default = the current featureName).
|
|
25
|
+
refFeature: c.refFeature ?? featureName,
|
|
26
|
+
labelField: c.refLabelField ?? "id",
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
// No raw id for entities that opt in: a placeholder while the lookup
|
|
30
|
+
// loads, the "unknown" label once the complete lookup found nothing. A
|
|
31
|
+
// capped lookup page cannot prove absence, so it keeps the raw id.
|
|
32
|
+
function unresolvedLabelFor(unknownKey, published, translate) {
|
|
33
|
+
return (id) => unknownKey === undefined
|
|
34
|
+
? id
|
|
35
|
+
: published === undefined
|
|
36
|
+
? EMPTY_VALUE_PLACEHOLDER
|
|
37
|
+
: published.complete
|
|
38
|
+
? translate(unknownKey)
|
|
39
|
+
: id;
|
|
40
|
+
}
|
|
41
|
+
// Only a row the user can see is linkable: resolved by the server
|
|
42
|
+
// eagerload or by the lookup query. A multi-reference links per entry via linkItemsFor.
|
|
43
|
+
function buildLinkFor(lookup) {
|
|
44
|
+
const { col, map, refEntity, resolveReferenceLink } = lookup;
|
|
45
|
+
return (value, row) => {
|
|
46
|
+
// skip: empty and multi-valued cells have no single target to link
|
|
47
|
+
if (value === null || value === undefined || value === "" || Array.isArray(value)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const id = String(value);
|
|
51
|
+
const eagerloaded = eagerloadedRef(row, col.field);
|
|
52
|
+
// The server leaves a missing or unreadable target out; null is treated the same.
|
|
53
|
+
const resolved = (typeof eagerloaded === "object" && eagerloaded !== null && !Array.isArray(eagerloaded)) ||
|
|
54
|
+
map?.has(id);
|
|
55
|
+
return resolved === true ? resolveReferenceLink(refEntity, id) : undefined;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildLinkItemsFor(lookup) {
|
|
59
|
+
const { col, map, refEntity, labelField, resolveReferenceLink, unresolvedLabel } = lookup;
|
|
60
|
+
return (value, row) => {
|
|
61
|
+
// skip: only a non-empty array is a multi-reference value
|
|
62
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
63
|
+
return undefined;
|
|
64
|
+
const eagerloaded = eagerloadedRef(row, col.field);
|
|
65
|
+
const eagerById = new Map(Array.isArray(eagerloaded) ? eagerloaded.map((r) => [String(r["id"]), r]) : []);
|
|
66
|
+
return value.map((v) => {
|
|
67
|
+
const id = String(v);
|
|
68
|
+
const target = eagerById.get(id);
|
|
69
|
+
const label = String(target?.[labelField] ?? map?.get(id) ?? unresolvedLabel(id));
|
|
70
|
+
// Only a row the user can see is linkable: server eagerload or lookup query.
|
|
71
|
+
const link = target !== undefined || map?.has(id) === true
|
|
72
|
+
? resolveReferenceLink(refEntity, id)
|
|
73
|
+
: undefined;
|
|
74
|
+
return { id, label, ...(link !== undefined && { link }) };
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function buildReferenceRenderer(lookup) {
|
|
79
|
+
const { col, map, labelField, unresolvedLabel } = lookup;
|
|
80
|
+
return (value, row) => {
|
|
81
|
+
// Server eager loading: when the server includes _refs, read the display
|
|
82
|
+
// value directly from the resolved row. This avoids a round trip
|
|
83
|
+
// through the bridge map and the limit:200 constraint. The
|
|
84
|
+
// EagerloadedRow type from @cosmicdrift/kumiko-framework/db defines
|
|
85
|
+
// the shape of _refs.
|
|
86
|
+
const resolved = eagerloadedRef(row, col.field);
|
|
87
|
+
if (Array.isArray(resolved) && resolved.length > 0) {
|
|
88
|
+
return resolved.map((r) => String(r[labelField] ?? r["id"] ?? "")).join(", ");
|
|
89
|
+
}
|
|
90
|
+
if (isPlainRecord(resolved)) {
|
|
91
|
+
return String(resolved[labelField] ?? resolved["id"] ?? "");
|
|
92
|
+
}
|
|
93
|
+
// Renderer-side fallback when server eager loading is not active.
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
if (value.length === 0)
|
|
96
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
97
|
+
return value.map((v) => map?.get(String(v)) ?? unresolvedLabel(String(v))).join(", ");
|
|
98
|
+
}
|
|
99
|
+
if (value === null || value === undefined || value === "")
|
|
100
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
101
|
+
const idStr = String(value);
|
|
102
|
+
return map?.get(idStr) ?? unresolvedLabel(idStr);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function enrichReferenceColumn(col, input) {
|
|
106
|
+
const { referenceLookups, resolveReferenceLink, featureName, translate } = input;
|
|
107
|
+
// skip: only reference columns are enriched
|
|
108
|
+
if (col.type !== "reference")
|
|
109
|
+
return col;
|
|
110
|
+
// skip: an author-declared renderer always takes precedence; the default
|
|
111
|
+
// is used only when none is set.
|
|
112
|
+
if (col.renderer !== undefined)
|
|
113
|
+
return col;
|
|
114
|
+
const published = referenceLookups[col.field];
|
|
115
|
+
const unknownKey = UNRESOLVED_REFERENCE_LABEL_KEYS[`${col.refFeature ?? featureName}:${col.refEntity ?? ""}`];
|
|
116
|
+
const lookup = {
|
|
117
|
+
col,
|
|
118
|
+
map: published?.map,
|
|
119
|
+
refEntity: col.refEntity ?? "",
|
|
120
|
+
labelField: col.refLabelField ?? "id",
|
|
121
|
+
resolveReferenceLink,
|
|
122
|
+
unresolvedLabel: unresolvedLabelFor(unknownKey, published, translate),
|
|
123
|
+
};
|
|
124
|
+
return {
|
|
125
|
+
...col,
|
|
126
|
+
renderer: buildReferenceRenderer(lookup),
|
|
127
|
+
linkFor: buildLinkFor(lookup),
|
|
128
|
+
linkItemsFor: buildLinkItemsFor(lookup),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
export function enrichReferenceColumns(input) {
|
|
132
|
+
const { columns, referenceColumns } = input;
|
|
133
|
+
// skip: without reference columns the view-model columns pass through untouched
|
|
134
|
+
if (referenceColumns.length === 0)
|
|
135
|
+
return columns;
|
|
136
|
+
return columns.map((col) => enrichReferenceColumn(col, input));
|
|
137
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { RenderListProps } from "./render-list-types.ts";
|
|
3
|
+
type Pick2<K extends keyof RenderListProps> = Pick<RenderListProps, K>;
|
|
4
|
+
export declare function tableChromeProps(p: Pick2<"onRowClick" | "description"> & {
|
|
5
|
+
readonly composedEmptyState: ReactNode;
|
|
6
|
+
readonly toolbarStart: ReactNode;
|
|
7
|
+
readonly toolbarEnd: ReactNode;
|
|
8
|
+
readonly itemNoun: ((count: number) => string) | undefined;
|
|
9
|
+
}): {
|
|
10
|
+
onRowClick?: ((row: import("../headless/index.ts").ListRowViewModel) => void) | undefined;
|
|
11
|
+
emptyState?: string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined;
|
|
12
|
+
toolbarStart?: string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined;
|
|
13
|
+
toolbarDescription?: string | undefined;
|
|
14
|
+
toolbarEnd?: string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined;
|
|
15
|
+
itemNoun?: ((count: number) => string) | undefined;
|
|
16
|
+
};
|
|
17
|
+
export declare function tableSortPagingProps(p: Pick2<"sort" | "onSortChange" | "pager" | "onReachEnd" | "loadingMore" | "hasMore">): {
|
|
18
|
+
sort?: import("../primitives.tsx").DataTableSort | null | undefined;
|
|
19
|
+
onSortChange?: ((next: import("../index.ts").ListSort | null) => void) | undefined;
|
|
20
|
+
pager?: {
|
|
21
|
+
readonly page: number;
|
|
22
|
+
readonly limit: number;
|
|
23
|
+
readonly total: number;
|
|
24
|
+
readonly onPageChange: (next: number) => void;
|
|
25
|
+
} | undefined;
|
|
26
|
+
onReachEnd?: (() => void) | undefined;
|
|
27
|
+
loadingMore?: boolean | undefined;
|
|
28
|
+
hasMore?: boolean | undefined;
|
|
29
|
+
};
|
|
30
|
+
export declare function tableRowProps(p: Pick2<"rowActions" | "rowActionMode" | "bulk" | "rowTone">): {
|
|
31
|
+
rowActions?: readonly import("../primitives.tsx").DataTableRowAction[] | undefined;
|
|
32
|
+
rowActionMode?: import("../primitives.tsx").DataTableRowActionMode | undefined;
|
|
33
|
+
bulk?: import("../data-table-bulk-types.ts").DataTableBulk | undefined;
|
|
34
|
+
rowTone?: ((row: import("../headless/index.ts").ListRowViewModel) => import("@cosmicdrift/kumiko-types/fields").SelectOptionTone | undefined) | undefined;
|
|
35
|
+
};
|
|
36
|
+
export declare function tableFilterProps(p: Pick2<"filterFacets" | "filterValues" | "onFilterChange" | "onFilterReset" | "dateRangeFacets" | "onDateRangeChange">): {
|
|
37
|
+
filterFacets?: readonly import("../data-table-facet-types.ts").DataTableFacet[] | undefined;
|
|
38
|
+
filterValues?: Readonly<Record<string, readonly string[]>> | undefined;
|
|
39
|
+
onFilterChange?: ((field: string, values: readonly string[]) => void) | undefined;
|
|
40
|
+
onFilterReset?: (() => void) | undefined;
|
|
41
|
+
dateRangeFacets?: readonly import("../data-table-facet-types.ts").DataTableDateRangeFacet[] | undefined;
|
|
42
|
+
onDateRangeChange?: ((field: string, bound: "from" | "to", value: string) => void) | undefined;
|
|
43
|
+
};
|
|
44
|
+
export declare function tableLayoutProps(p: Pick2<"chromeless" | "scrollBody" | "screenPadding" | "expandedRowIds" | "onToggleRowExpanded" | "renderExpandedRow">): {
|
|
45
|
+
chromeless?: boolean | undefined;
|
|
46
|
+
scrollBody?: boolean | undefined;
|
|
47
|
+
screenPadding?: boolean | undefined;
|
|
48
|
+
expandedRowIds?: ReadonlySet<string> | undefined;
|
|
49
|
+
onToggleRowExpanded?: ((rowId: string) => void) | undefined;
|
|
50
|
+
renderExpandedRow?: ((row: import("../headless/index.ts").ListRowViewModel) => ReactNode) | undefined;
|
|
51
|
+
};
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function tableChromeProps(p) {
|
|
2
|
+
const { onRowClick, composedEmptyState, toolbarStart, description, toolbarEnd, itemNoun } = p;
|
|
3
|
+
return {
|
|
4
|
+
...(onRowClick !== undefined && { onRowClick }),
|
|
5
|
+
...(composedEmptyState !== undefined && { emptyState: composedEmptyState }),
|
|
6
|
+
...(toolbarStart !== undefined && { toolbarStart }),
|
|
7
|
+
...(description !== undefined && { toolbarDescription: description }),
|
|
8
|
+
...(toolbarEnd !== undefined && { toolbarEnd }),
|
|
9
|
+
...(itemNoun !== undefined && { itemNoun }),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function tableSortPagingProps(p) {
|
|
13
|
+
const { sort, onSortChange, pager, onReachEnd, loadingMore, hasMore } = p;
|
|
14
|
+
return {
|
|
15
|
+
...(sort !== undefined && { sort }),
|
|
16
|
+
...(onSortChange !== undefined && { onSortChange }),
|
|
17
|
+
...(pager !== undefined && { pager }),
|
|
18
|
+
...(onReachEnd !== undefined && { onReachEnd }),
|
|
19
|
+
...(loadingMore !== undefined && { loadingMore }),
|
|
20
|
+
...(hasMore !== undefined && { hasMore }),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function tableRowProps(p) {
|
|
24
|
+
const { rowActions, rowActionMode, bulk, rowTone } = p;
|
|
25
|
+
return {
|
|
26
|
+
...(rowActions !== undefined && { rowActions }),
|
|
27
|
+
...(rowActionMode !== undefined && { rowActionMode }),
|
|
28
|
+
...(bulk !== undefined && { bulk }),
|
|
29
|
+
...(rowTone !== undefined && { rowTone }),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function tableFilterProps(p) {
|
|
33
|
+
const { filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, } = p;
|
|
34
|
+
return {
|
|
35
|
+
...(filterFacets !== undefined && { filterFacets }),
|
|
36
|
+
...(filterValues !== undefined && { filterValues }),
|
|
37
|
+
...(onFilterChange !== undefined && { onFilterChange }),
|
|
38
|
+
...(onFilterReset !== undefined && { onFilterReset }),
|
|
39
|
+
...(dateRangeFacets !== undefined && { dateRangeFacets }),
|
|
40
|
+
...(onDateRangeChange !== undefined && { onDateRangeChange }),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function tableLayoutProps(p) {
|
|
44
|
+
const { chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, } = p;
|
|
45
|
+
return {
|
|
46
|
+
...(chromeless !== undefined && { chromeless }),
|
|
47
|
+
...(scrollBody !== undefined && { scrollBody }),
|
|
48
|
+
...(screenPadding !== undefined && { screenPadding }),
|
|
49
|
+
...(expandedRowIds !== undefined && { expandedRowIds }),
|
|
50
|
+
...(onToggleRowExpanded !== undefined && { onToggleRowExpanded }),
|
|
51
|
+
...(renderExpandedRow !== undefined && { renderExpandedRow }),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EntityListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { Translate } from "../headless/index.ts";
|
|
3
|
+
import { type ActionMenuItemSpec } from "../primitives.tsx";
|
|
4
|
+
import type { ToolbarActionButton } from "./render-list-toolbar-action.tsx";
|
|
5
|
+
type ToolbarModelInput = {
|
|
6
|
+
readonly toolbarActions: readonly ToolbarActionButton[] | undefined;
|
|
7
|
+
readonly pageHeaderAvailable: boolean;
|
|
8
|
+
readonly pageHeaderSlotAvailable: boolean;
|
|
9
|
+
readonly pageHeaderCompact: boolean;
|
|
10
|
+
readonly createInPageHeader: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type ToolbarActionSplit = {
|
|
13
|
+
readonly toolbarIconOnly: boolean;
|
|
14
|
+
readonly promotedToolbarActions: readonly ToolbarActionButton[];
|
|
15
|
+
readonly promotedInOverflowMenu: boolean;
|
|
16
|
+
readonly toolbarActionsInToolbar: readonly ToolbarActionButton[];
|
|
17
|
+
readonly hasToolbarActionsInToolbar: boolean;
|
|
18
|
+
};
|
|
19
|
+
export declare function splitToolbarActions(input: ToolbarModelInput): ToolbarActionSplit;
|
|
20
|
+
type MenuItemInput = {
|
|
21
|
+
readonly promotedToolbarActions: readonly ToolbarActionButton[];
|
|
22
|
+
readonly promotedInOverflowMenu: boolean;
|
|
23
|
+
readonly menuActionBusyId: string | null;
|
|
24
|
+
readonly setMenuActionBusyId: (id: string | null) => void;
|
|
25
|
+
readonly setMenuActionError: (message: string | null) => void;
|
|
26
|
+
};
|
|
27
|
+
export declare function buildPromotedMenuItems(input: MenuItemInput): readonly ActionMenuItemSpec[];
|
|
28
|
+
type ListLabelInput = {
|
|
29
|
+
readonly screen: EntityListScreenDefinition;
|
|
30
|
+
readonly translate: Translate;
|
|
31
|
+
readonly createLabel: string | undefined;
|
|
32
|
+
readonly searchPlaceholder: string | undefined;
|
|
33
|
+
readonly itemNounKey: string | undefined;
|
|
34
|
+
readonly featureName: string;
|
|
35
|
+
};
|
|
36
|
+
export declare function resolveToolbarLabels(input: ListLabelInput): {
|
|
37
|
+
effectiveCreateLabel: string;
|
|
38
|
+
effectiveSearchPlaceholder: string;
|
|
39
|
+
};
|
|
40
|
+
export declare function resolveItemNoun(input: ListLabelInput): ((count: number) => string) | undefined;
|
|
41
|
+
export declare function resolveToolbarTitle(screen: EntityListScreenDefinition, translate: Translate): string;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { shouldRenderActionsIconOnly } from "../primitives.js";
|
|
2
|
+
import { needsActionConfirm } from "./render-edit-action-button.js";
|
|
3
|
+
// Icon-less actions are promoted too; on phones the call site falls back to a
|
|
4
|
+
// "plus" icon, on desktop the label carries the action.
|
|
5
|
+
function isHeaderPromotableToolbarAction(action) {
|
|
6
|
+
return action.style === "primary" && !needsActionConfirm(action);
|
|
7
|
+
}
|
|
8
|
+
export function splitToolbarActions(input) {
|
|
9
|
+
const { toolbarActions, pageHeaderAvailable, pageHeaderSlotAvailable, pageHeaderCompact, createInPageHeader, } = input;
|
|
10
|
+
const hasToolbarActions = toolbarActions !== undefined && toolbarActions.length > 0;
|
|
11
|
+
const toolbarIconOnly = hasToolbarActions && shouldRenderActionsIconOnly(toolbarActions);
|
|
12
|
+
// Every primary toolbar action takes the page-header slot, left of the create
|
|
13
|
+
// button (which stays the rightmost primary action): labelled on desktop, so
|
|
14
|
+
// projection lists look like entity lists. On phones an icon button, or, next to
|
|
15
|
+
// the create button, an entry in the header's overflow menu: the header has no
|
|
16
|
+
// room for a labelled action beside it.
|
|
17
|
+
const promotedToolbarActions = pageHeaderAvailable && pageHeaderSlotAvailable
|
|
18
|
+
? (toolbarActions?.filter(isHeaderPromotableToolbarAction) ?? [])
|
|
19
|
+
: [];
|
|
20
|
+
const promotedInOverflowMenu = pageHeaderCompact && createInPageHeader && promotedToolbarActions.length > 0;
|
|
21
|
+
const toolbarActionsInToolbar = toolbarActions?.filter((a) => !promotedToolbarActions.includes(a)) ?? [];
|
|
22
|
+
const hasToolbarActionsInToolbar = toolbarActionsInToolbar.length > 0;
|
|
23
|
+
return {
|
|
24
|
+
toolbarIconOnly,
|
|
25
|
+
promotedToolbarActions,
|
|
26
|
+
promotedInOverflowMenu,
|
|
27
|
+
toolbarActionsInToolbar,
|
|
28
|
+
hasToolbarActionsInToolbar,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function buildPromotedMenuItems(input) {
|
|
32
|
+
const { promotedToolbarActions, promotedInOverflowMenu, menuActionBusyId, setMenuActionBusyId, setMenuActionError, } = input;
|
|
33
|
+
return promotedInOverflowMenu
|
|
34
|
+
? promotedToolbarActions.map((a) => ({
|
|
35
|
+
id: a.id,
|
|
36
|
+
label: a.label,
|
|
37
|
+
...(a.icon !== undefined && { icon: a.icon }),
|
|
38
|
+
...(menuActionBusyId !== null && { disabled: true }),
|
|
39
|
+
testId: `render-list-toolbar-action-${a.id}`,
|
|
40
|
+
onSelect: () => {
|
|
41
|
+
setMenuActionBusyId(a.id);
|
|
42
|
+
setMenuActionError(null);
|
|
43
|
+
void (async () => {
|
|
44
|
+
try {
|
|
45
|
+
await a.onTrigger();
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
setMenuActionError(e instanceof Error ? e.message : String(e));
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
setMenuActionBusyId(null);
|
|
52
|
+
}
|
|
53
|
+
})();
|
|
54
|
+
},
|
|
55
|
+
}))
|
|
56
|
+
: [];
|
|
57
|
+
}
|
|
58
|
+
export function resolveToolbarLabels(input) {
|
|
59
|
+
const { screen, translate, createLabel, searchPlaceholder } = input;
|
|
60
|
+
// i18n defaults for toolbar and empty-state strings. The caller can
|
|
61
|
+
// override each one through a prop; otherwise the framework bundles provide
|
|
62
|
+
// them (kumiko.actions.create, kumiko.list.search-placeholder, …).
|
|
63
|
+
const effectiveCreateLabel = translate(createLabel ?? screen.createLabel ?? "kumiko.actions.create");
|
|
64
|
+
const effectiveSearchPlaceholder = translate(searchPlaceholder ?? screen.searchPlaceholder ?? "kumiko.list.search-placeholder");
|
|
65
|
+
return { effectiveCreateLabel, effectiveSearchPlaceholder };
|
|
66
|
+
}
|
|
67
|
+
export function resolveItemNoun(input) {
|
|
68
|
+
const { screen, translate, itemNounKey, featureName } = input;
|
|
69
|
+
const nounKey = itemNounKey ?? `${featureName}:entity:${screen.entity}:noun`;
|
|
70
|
+
return (itemNounKey !== undefined || !screen.entity.startsWith("__")) &&
|
|
71
|
+
translate(nounKey, { count: 2 }) !== nounKey
|
|
72
|
+
? (count) => translate(nounKey, { count })
|
|
73
|
+
: undefined;
|
|
74
|
+
}
|
|
75
|
+
// Title resolution uses the conventional i18n key `screen:<id>.title`.
|
|
76
|
+
// If the bundle knows the key, use its translated title; otherwise use
|
|
77
|
+
// screenId so the app developer can see that the translation is missing.
|
|
78
|
+
export function resolveToolbarTitle(screen, translate) {
|
|
79
|
+
const titleKey = `screen:${screen.id}.title`;
|
|
80
|
+
const resolvedTitle = translate(titleKey);
|
|
81
|
+
return resolvedTitle === titleKey ? screen.id : resolvedTitle;
|
|
82
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EntityListScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { Translate } from "../headless/index.ts";
|
|
4
|
+
import type { ActionMenuItemSpec, usePrimitives } from "../primitives.tsx";
|
|
5
|
+
import { type ListEmptyStateContent } from "./list-empty-state.tsx";
|
|
6
|
+
import type { ToolbarActionSplit } from "./render-list-toolbar-model.ts";
|
|
7
|
+
type Primitives = ReturnType<typeof usePrimitives>;
|
|
8
|
+
export type ListToolbarModel = {
|
|
9
|
+
readonly screen: EntityListScreenDefinition;
|
|
10
|
+
readonly Button: Primitives["Button"];
|
|
11
|
+
readonly Dialog: Primitives["Dialog"];
|
|
12
|
+
readonly Banner: Primitives["Banner"];
|
|
13
|
+
readonly onCreate: (() => void) | undefined;
|
|
14
|
+
readonly createInPageHeader: boolean;
|
|
15
|
+
readonly pageHeaderCompact: boolean;
|
|
16
|
+
readonly effectiveCreateLabel: string;
|
|
17
|
+
readonly split: ToolbarActionSplit;
|
|
18
|
+
};
|
|
19
|
+
export declare function renderCreateButton(model: ListToolbarModel): ReactNode;
|
|
20
|
+
export declare function renderHeaderActions(model: ListToolbarModel, createButton: ReactNode): ReactNode;
|
|
21
|
+
export declare function renderToolbarEnd(model: ListToolbarModel, createButton: ReactNode): ReactNode;
|
|
22
|
+
export declare function renderSearchInput(Input: Primitives["Input"], localQ: string, setLocalQ: (next: string) => void, placeholder: string): ReactNode;
|
|
23
|
+
type EmptyStateModel = {
|
|
24
|
+
readonly Button: Primitives["Button"];
|
|
25
|
+
readonly emptyState: ReactNode;
|
|
26
|
+
readonly onCreate: (() => void) | undefined;
|
|
27
|
+
readonly emptyContent: ListEmptyStateContent | undefined;
|
|
28
|
+
readonly narrowed: boolean;
|
|
29
|
+
readonly defaultFilterActive: boolean;
|
|
30
|
+
readonly onFilterReset: (() => void) | undefined;
|
|
31
|
+
readonly translate: Translate;
|
|
32
|
+
readonly createInPageHeader: boolean;
|
|
33
|
+
readonly effectiveCreateLabel: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function composeEmptyState(model: EmptyStateModel): ReactNode;
|
|
36
|
+
export declare function renderPageHeader(PageHeader: NonNullable<Primitives["PageHeader"]>, headerActions: ReactNode, promotedInOverflowMenu: boolean, promotedMenuItems: readonly ActionMenuItemSpec[]): ReactNode;
|
|
37
|
+
export declare function renderMenuActionError(Banner: Primitives["Banner"], menuActionError: string): ReactNode;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ListEmptyStateBody } from "./list-empty-state.js";
|
|
3
|
+
import { ListHeaderSlotMount } from "./render-list-header-slot.js";
|
|
4
|
+
import { ToolbarActionView } from "./render-list-toolbar-action.js";
|
|
5
|
+
export function renderCreateButton(model) {
|
|
6
|
+
const { Button, onCreate, createInPageHeader, pageHeaderCompact, effectiveCreateLabel } = model;
|
|
7
|
+
return onCreate !== undefined && createInPageHeader && pageHeaderCompact ? (_jsx(Button, { variant: "primary", icon: "plus", size: "icon", ariaLabel: effectiveCreateLabel, title: effectiveCreateLabel, onClick: onCreate, testId: "render-list-create" })) : onCreate !== undefined ? (_jsx(Button, { variant: "primary", icon: "plus", onClick: onCreate, testId: "render-list-create", className: "px-3.5", children: effectiveCreateLabel })) : undefined;
|
|
8
|
+
}
|
|
9
|
+
export function renderHeaderActions(model, createButton) {
|
|
10
|
+
const { Button, Dialog, Banner, createInPageHeader, pageHeaderCompact, split } = model;
|
|
11
|
+
const { promotedToolbarActions, promotedInOverflowMenu } = split;
|
|
12
|
+
return promotedToolbarActions.length > 0 && !promotedInOverflowMenu ? (_jsxs(_Fragment, { children: [promotedToolbarActions.map((a) => (_jsx(ToolbarActionView, { action: pageHeaderCompact ? { ...a, icon: a.icon ?? "plus" } : a, iconOnly: pageHeaderCompact, Button: Button, Dialog: Dialog, Banner: Banner }, a.id))), createInPageHeader && createButton] })) : createInPageHeader ? (createButton) : undefined;
|
|
13
|
+
}
|
|
14
|
+
export function renderToolbarEnd(model, createButton) {
|
|
15
|
+
const { screen, Button, Dialog, Banner, onCreate, createInPageHeader, split } = model;
|
|
16
|
+
const { toolbarActionsInToolbar, hasToolbarActionsInToolbar, toolbarIconOnly } = split;
|
|
17
|
+
const hasHeaderSlot = screen.slots?.header !== undefined;
|
|
18
|
+
return hasHeaderSlot ||
|
|
19
|
+
hasToolbarActionsInToolbar ||
|
|
20
|
+
(onCreate !== undefined && !createInPageHeader) ? (_jsxs(_Fragment, { children: [hasHeaderSlot && _jsx(ListHeaderSlotMount, { screen: screen }), hasToolbarActionsInToolbar &&
|
|
21
|
+
toolbarActionsInToolbar.map((a) => (_jsx(ToolbarActionView, { action: a, iconOnly: toolbarIconOnly, Button: Button, Dialog: Dialog, Banner: Banner }, a.id))), !createInPageHeader && createButton] })) : undefined;
|
|
22
|
+
}
|
|
23
|
+
export function renderSearchInput(Input, localQ, setLocalQ, placeholder) {
|
|
24
|
+
return (_jsx(Input, { kind: "text", id: "render-list-search", name: "search", value: localQ, onChange: setLocalQ, placeholder: placeholder }));
|
|
25
|
+
}
|
|
26
|
+
// Empty state: by default, show a heading, description, and optional CTA
|
|
27
|
+
// button. The caller can replace it completely through emptyState.
|
|
28
|
+
// Without onCreate or an override, DataTable uses its default
|
|
29
|
+
// (`kumiko.list.no-entries`).
|
|
30
|
+
export function composeEmptyState(model) {
|
|
31
|
+
const { Button, emptyState, onCreate, emptyContent, narrowed, defaultFilterActive, onFilterReset, translate, createInPageHeader, effectiveCreateLabel, } = model;
|
|
32
|
+
return (emptyState ??
|
|
33
|
+
(onCreate !== undefined || emptyContent !== undefined || narrowed || defaultFilterActive ? (_jsx(ListEmptyStateBody, { content: emptyContent, narrowed: narrowed, defaultFilterActive: defaultFilterActive, ...(onFilterReset !== undefined && { onShowAll: onFilterReset }), translate: translate, createButton: onCreate !== undefined && !narrowed && !defaultFilterActive && !createInPageHeader ? (_jsx(Button, { variant: "primary", icon: "plus", onClick: onCreate, testId: "render-list-empty-create", children: effectiveCreateLabel })) : undefined })) : undefined));
|
|
34
|
+
}
|
|
35
|
+
export function renderPageHeader(PageHeader, headerActions, promotedInOverflowMenu, promotedMenuItems) {
|
|
36
|
+
return (_jsx(PageHeader, { actions: headerActions, ...(promotedInOverflowMenu && { overflowItems: promotedMenuItems }) }));
|
|
37
|
+
}
|
|
38
|
+
export function renderMenuActionError(Banner, menuActionError) {
|
|
39
|
+
return (_jsx(Banner, { variant: "error", testId: "render-list-toolbar-action-error", children: menuActionError }));
|
|
40
|
+
}
|