@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,7 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { RenderInputArgs, SharedInputProps } from "./render-field-input-shared.ts";
|
|
3
|
+
export declare function renderMultiSelectInput(args: RenderInputArgs, shared: SharedInputProps): ReactNode;
|
|
4
|
+
export declare function renderSelectInput({ field, Input, onChange }: RenderInputArgs, { common }: SharedInputProps): ReactNode;
|
|
5
|
+
export declare function renderFileInput({ field, Input, onChange }: RenderInputArgs, { common }: SharedInputProps): ReactNode;
|
|
6
|
+
export declare function renderFileListInput({ field, id, hasError, onChange }: RenderInputArgs): ReactNode;
|
|
7
|
+
export declare function renderUnsupportedInput({ field, id, Banner, Text, JsonView, t, }: RenderInputArgs): ReactNode;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AvailabilityOptionsMultiSelect, staticSelectOptions, } from "./availability-options-select.js";
|
|
3
|
+
import { FileListField } from "./file-list-field.js";
|
|
4
|
+
import { MultiSelectCheckboxes } from "./multi-select-checkboxes.js";
|
|
5
|
+
import { stringArrayOf, stringValue, withPinnedValues } from "./render-field-values.js";
|
|
6
|
+
function renderMultiSelectControl({ field, id, Input, onChange }, { common }, options, arrayValue) {
|
|
7
|
+
return field.display === "checkboxes" ? (_jsx(MultiSelectCheckboxes, { field: field, id: id, options: options, value: arrayValue, onChange: (v) => onChange(v) })) : (_jsx(Input, { kind: "combobox", ...common, multiple: true, value: arrayValue, onChange: (v) => onChange(withPinnedValues(v, options, arrayValue)), options: options }));
|
|
8
|
+
}
|
|
9
|
+
export function renderMultiSelectInput(args, shared) {
|
|
10
|
+
const { field } = args;
|
|
11
|
+
const arrayValue = stringArrayOf(field.value);
|
|
12
|
+
const renderControl = (options) => renderMultiSelectControl(args, shared, options, arrayValue);
|
|
13
|
+
if (field.selectOptionsAvailabilityQuery !== undefined && field.readOnly !== true) {
|
|
14
|
+
return (_jsx(AvailabilityOptionsMultiSelect, { field: field, query: field.selectOptionsAvailabilityQuery.query, initialValues: arrayValue, children: renderControl }));
|
|
15
|
+
}
|
|
16
|
+
return renderControl(staticSelectOptions(field));
|
|
17
|
+
}
|
|
18
|
+
export function renderSelectInput({ field, Input, onChange }, { common }) {
|
|
19
|
+
// Translated option labels come from the ViewModel builder
|
|
20
|
+
// (computeEditViewModel, convention key
|
|
21
|
+
// `<feature>:entity:<entity>:field:<field>:option:<value>`).
|
|
22
|
+
// If no translations are registered, fall back to the raw value as the
|
|
23
|
+
// label, which ComboboxInput then shows unchanged.
|
|
24
|
+
const selectOptions = field.optionLabels !== undefined ? staticSelectOptions(field) : (field.options ?? []);
|
|
25
|
+
// `display` is shared with multiSelect's "checkboxes", which is not a
|
|
26
|
+
// select presentation — narrow to the two a select field can request.
|
|
27
|
+
const display = field.display === "radio" || field.display === "dropdown" ? field.display : undefined;
|
|
28
|
+
return (_jsx(Input, { kind: "select", ...common, value: stringValue(field.value), onChange: (v) => onChange(v), options: selectOptions, ...(display !== undefined && { display }) }));
|
|
29
|
+
}
|
|
30
|
+
export function renderFileInput({ field, Input, onChange }, { common }) {
|
|
31
|
+
const kind = field.type === "image" ? "image" : "file";
|
|
32
|
+
const fileId = typeof field.value === "string" && field.value !== "" ? field.value : null;
|
|
33
|
+
return (_jsx(Input, { kind: kind, ...common, value: fileId, onChange: (v) => onChange(v), ...(field.accept !== undefined && { accept: field.accept }), ...(field.maxSize !== undefined && { maxSize: field.maxSize }), ...(field.entityType !== undefined && { entityType: field.entityType }), ...(field.uploadActionForm !== undefined && {
|
|
34
|
+
uploadActionForm: field.uploadActionForm,
|
|
35
|
+
}), ...(field.entityId !== undefined && { entityId: field.entityId }), ...(field.fieldName !== undefined && { fieldName: field.fieldName }), ...(field.imageVariant !== undefined && { imageVariant: field.imageVariant }), ...(field.capture !== undefined && { capture: field.capture }) }));
|
|
36
|
+
}
|
|
37
|
+
export function renderFileListInput({ field, id, hasError, onChange }) {
|
|
38
|
+
return (_jsx(FileListField, { field: field, id: id, kind: field.type === "images" ? "image" : "file", hasError: hasError, onChange: (ids) => onChange(ids) }));
|
|
39
|
+
}
|
|
40
|
+
// embedded (without embeddedListCells — that's embeddedList, which has
|
|
41
|
+
// had its own EmbeddedListField widget) and jsonb carry
|
|
42
|
+
// arbitrary objects. Without a dedicated widget these must NOT fall
|
|
43
|
+
// through to a text input: stringValue() turns them into
|
|
44
|
+
// "[object Object]" and saving that overwrites the real data with the
|
|
45
|
+
// mangled string. A `required: true` on either is caught loudly
|
|
46
|
+
// at boot (validateNoWidgetRequiredField in the framework package)
|
|
47
|
+
// instead of silently failing here.
|
|
48
|
+
export function renderUnsupportedInput({ field, id, Banner, Text, JsonView, t, }) {
|
|
49
|
+
const hasValue = field.value !== undefined && field.value !== null && field.value !== "";
|
|
50
|
+
return (_jsxs(Banner, { id: id, variant: "info", children: [t("kumiko.field.unsupported"), hasValue &&
|
|
51
|
+
(JsonView !== undefined ? (_jsx(JsonView, { value: field.value, testId: `field-value-${field.field}` })) : (_jsx(Text, { variant: "code", children: JSON.stringify(field.value) })))] }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { EditFieldViewModel, FieldIssue } from "../headless/index.ts";
|
|
3
|
+
import type { RenderInputArgs } from "./render-field-input-shared.ts";
|
|
4
|
+
export type ControlContext = RenderInputArgs & {
|
|
5
|
+
readonly readOnlyText: boolean;
|
|
6
|
+
readonly featureName: string | undefined;
|
|
7
|
+
readonly allIssues: Readonly<Record<string, readonly FieldIssue[]>> | undefined;
|
|
8
|
+
readonly changed: boolean | undefined;
|
|
9
|
+
readonly fillDisabled: boolean | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare function listPrintsOwnIssues(field: EditFieldViewModel): boolean;
|
|
12
|
+
export declare function renderControl(ctx: ControlContext): ReactNode;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { AvailabilityOptionsSelect } from "./availability-options-select.js";
|
|
3
|
+
import { EmbeddedListField } from "./embedded-list-field.js";
|
|
4
|
+
import { QueryOptionsSelect } from "./query-options-select.js";
|
|
5
|
+
import { FillActionControl } from "./render-field-fill-action.js";
|
|
6
|
+
import { renderInput } from "./render-field-input.js";
|
|
7
|
+
import { isComplexFieldType, isFileFieldType } from "./render-field-meta.js";
|
|
8
|
+
import { FieldRendererOutput, readOnlyDisplayText } from "./render-field-read-only.js";
|
|
9
|
+
import { ReadOnlyReferenceValue, ReferenceInput } from "./render-field-reference.js";
|
|
10
|
+
import { WriteOnlyTextInput } from "./render-field-write-only.js";
|
|
11
|
+
function rowOf({ field, row }) {
|
|
12
|
+
return row ?? { [field.field]: field.value };
|
|
13
|
+
}
|
|
14
|
+
// The list input prints its own list-level issues (including the empty state),
|
|
15
|
+
// so the Field wrapper must not print them a second time.
|
|
16
|
+
export function listPrintsOwnIssues(field) {
|
|
17
|
+
return (field.type === "embedded" &&
|
|
18
|
+
field.embeddedListCells !== undefined &&
|
|
19
|
+
!(field.readOnly && field.renderer !== undefined));
|
|
20
|
+
}
|
|
21
|
+
// An author-declared renderer always wins over the input widget, same as
|
|
22
|
+
// the list-column path (render-list.tsx).
|
|
23
|
+
const fieldRendererRule = ({ field, appLocale, row }) => {
|
|
24
|
+
if (!field.readOnly || field.renderer === undefined)
|
|
25
|
+
return undefined;
|
|
26
|
+
return (_jsx(FieldRendererOutput, { field: field, renderer: field.renderer, appLocale: appLocale, ...(row !== undefined && { row }) }));
|
|
27
|
+
};
|
|
28
|
+
const embeddedListRule = ({ field, id, onChange, allIssues, featureName }) => {
|
|
29
|
+
if (field.type !== "embedded" || field.embeddedListCells === undefined)
|
|
30
|
+
return undefined;
|
|
31
|
+
return (_jsx(EmbeddedListField, { field: field, id: id, onChange: onChange, allIssues: allIssues ?? {}, featureName: featureName ?? "" }));
|
|
32
|
+
};
|
|
33
|
+
// The reference field renders its own component because it uses useQuery()
|
|
34
|
+
// for the live lookup and must therefore be mounted as a React component,
|
|
35
|
+
// not called as a pure render function.
|
|
36
|
+
const referenceRule = ({ field, id, hasError, onChange, Input, featureName, readOnlyText, }) => {
|
|
37
|
+
if (field.type !== "reference")
|
|
38
|
+
return undefined;
|
|
39
|
+
if (readOnlyText) {
|
|
40
|
+
return _jsx(ReadOnlyReferenceValue, { field: field, featureName: featureName ?? "" });
|
|
41
|
+
}
|
|
42
|
+
return (_jsx(ReferenceInput, { field: field, id: id, hasError: hasError, onChange: onChange, Input: Input, featureName: featureName ?? "" }));
|
|
43
|
+
};
|
|
44
|
+
const selectQueryRule = (ctx) => {
|
|
45
|
+
const { field, id, hasError, onChange, readOnlyText } = ctx;
|
|
46
|
+
const source = field.selectOptionsQuery;
|
|
47
|
+
if (field.type !== "select" || source === undefined || readOnlyText)
|
|
48
|
+
return undefined;
|
|
49
|
+
return (_jsx(QueryOptionsSelect, { field: field, query: source.query, payload: source.payload, row: rowOf(ctx), id: id, hasError: hasError, onChange: onChange }));
|
|
50
|
+
};
|
|
51
|
+
const selectAvailabilityRule = (ctx) => {
|
|
52
|
+
const { field, id, hasError, onChange, readOnlyText } = ctx;
|
|
53
|
+
const source = field.selectOptionsAvailabilityQuery;
|
|
54
|
+
if (field.type !== "select" || source === undefined || readOnlyText)
|
|
55
|
+
return undefined;
|
|
56
|
+
return (_jsx(AvailabilityOptionsSelect, { field: field, query: source.query, payload: source.payload, row: rowOf(ctx), id: id, hasError: hasError, onChange: onChange }));
|
|
57
|
+
};
|
|
58
|
+
const readOnlyTextRule = ({ field, Text, appLocale, t, readOnlyText }) => {
|
|
59
|
+
if (!readOnlyText || isComplexFieldType(field.type) || isFileFieldType(field.type)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return (_jsx(Text, { testId: `field-value-${field.field}`, children: readOnlyDisplayText(field, appLocale, t) }));
|
|
63
|
+
};
|
|
64
|
+
const writeOnlyRule = ({ field, id, hasError, onChange, changed, readOnlyText }) => {
|
|
65
|
+
if (field.type !== "text" || field.writeOnly !== true || readOnlyText)
|
|
66
|
+
return undefined;
|
|
67
|
+
return (_jsx(WriteOnlyTextInput, { field: field, id: id, hasError: hasError, onChange: onChange, changed: changed === true }));
|
|
68
|
+
};
|
|
69
|
+
const fillActionRule = (ctx) => {
|
|
70
|
+
const { field, onChange, fillDisabled, Banner } = ctx;
|
|
71
|
+
const fillAction = field.fillAction;
|
|
72
|
+
if (fillAction === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return (_jsx(FillActionControl, { field: field, fillAction: fillAction, row: rowOf(ctx), onChange: onChange, disabled: fillDisabled === true, Banner: Banner, renderControl: (fill) => renderInput({ ...ctx, fill }) }));
|
|
75
|
+
};
|
|
76
|
+
const CONTROL_RULES = [
|
|
77
|
+
fieldRendererRule,
|
|
78
|
+
embeddedListRule,
|
|
79
|
+
referenceRule,
|
|
80
|
+
selectQueryRule,
|
|
81
|
+
selectAvailabilityRule,
|
|
82
|
+
readOnlyTextRule,
|
|
83
|
+
writeOnlyRule,
|
|
84
|
+
fillActionRule,
|
|
85
|
+
];
|
|
86
|
+
export function renderControl(ctx) {
|
|
87
|
+
for (const rule of CONTROL_RULES) {
|
|
88
|
+
const control = rule(ctx);
|
|
89
|
+
if (control !== undefined)
|
|
90
|
+
return control;
|
|
91
|
+
}
|
|
92
|
+
return renderInput(ctx);
|
|
93
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type EditFieldViewModel } from "../headless/index.ts";
|
|
2
|
+
export type DisplayText = (field: EditFieldViewModel, value: unknown, appLocale: string) => string;
|
|
3
|
+
export declare const DISPLAY_TEXT_BY_TYPE: ReadonlyMap<string, DisplayText>;
|
|
4
|
+
export declare const defaultDisplayText: DisplayText;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { applyFormatSpec, currencyDecimals, EMPTY_VALUE_PLACEHOLDER, } from "../headless/index.js";
|
|
2
|
+
import { locatedValue, moneyMinorValue, numberValue, resolveMoneyCurrency, stringValue, } from "./render-field-values.js";
|
|
3
|
+
const booleanText = (_field, value) => applyFormatSpec({ format: "boolean" }, value);
|
|
4
|
+
const dateText = (field, value, appLocale) => applyFormatSpec({ format: "date", locale: field.dateLocale ?? appLocale }, value);
|
|
5
|
+
const timestampText = (field, value, appLocale) => applyFormatSpec({ format: "timestamp", locale: field.dateLocale ?? appLocale }, value);
|
|
6
|
+
const locatedTimestampText = (field, value, appLocale) => {
|
|
7
|
+
const located = locatedValue(value);
|
|
8
|
+
if (located === "" || located.at === "")
|
|
9
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
10
|
+
return timestampText(field, located.utc ?? located.at, appLocale);
|
|
11
|
+
};
|
|
12
|
+
const numberText = (field, value, appLocale) => {
|
|
13
|
+
const n = numberValue(value);
|
|
14
|
+
return n === ""
|
|
15
|
+
? EMPTY_VALUE_PLACEHOLDER
|
|
16
|
+
: new Intl.NumberFormat(appLocale, { useGrouping: field.grouping ?? true }).format(n);
|
|
17
|
+
};
|
|
18
|
+
const moneyText = (field, value, appLocale) => {
|
|
19
|
+
const currency = resolveMoneyCurrency(value, field.currency);
|
|
20
|
+
const minor = moneyMinorValue(value, currency);
|
|
21
|
+
if (minor === "")
|
|
22
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
23
|
+
const major = minor / 10 ** currencyDecimals(currency);
|
|
24
|
+
return new Intl.NumberFormat(appLocale, { style: "currency", currency }).format(major);
|
|
25
|
+
};
|
|
26
|
+
const selectText = (field, value) => {
|
|
27
|
+
const labels = field.optionLabels;
|
|
28
|
+
const values = Array.isArray(value) ? value : [value];
|
|
29
|
+
if (values.length === 0)
|
|
30
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
31
|
+
return values
|
|
32
|
+
.map((v) => (typeof v === "string" ? (labels?.[v] ?? v) : stringValue(v)))
|
|
33
|
+
.join(", ");
|
|
34
|
+
};
|
|
35
|
+
// A Map (not an object) so prototype keys like "constructor" never match.
|
|
36
|
+
export const DISPLAY_TEXT_BY_TYPE = new Map([
|
|
37
|
+
["boolean", booleanText],
|
|
38
|
+
["date", dateText],
|
|
39
|
+
["timestamp", timestampText],
|
|
40
|
+
["locatedTimestamp", locatedTimestampText],
|
|
41
|
+
["number", numberText],
|
|
42
|
+
["bigInt", numberText],
|
|
43
|
+
["decimal", numberText],
|
|
44
|
+
["money", moneyText],
|
|
45
|
+
["select", selectText],
|
|
46
|
+
["multiSelect", selectText],
|
|
47
|
+
]);
|
|
48
|
+
export const defaultDisplayText = (_field, value) => stringValue(value);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FieldFillAction } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import type { EditFieldViewModel } from "../headless/index.ts";
|
|
4
|
+
import type { InputFillAction, usePrimitives } from "../primitives.tsx";
|
|
5
|
+
export declare function FillActionControl({ field, fillAction, row, onChange, disabled, Banner, renderControl, }: {
|
|
6
|
+
readonly field: EditFieldViewModel;
|
|
7
|
+
readonly fillAction: FieldFillAction;
|
|
8
|
+
readonly row: Readonly<Record<string, unknown>>;
|
|
9
|
+
readonly onChange: (value: unknown) => void;
|
|
10
|
+
readonly disabled: boolean;
|
|
11
|
+
readonly Banner: ReturnType<typeof usePrimitives>["Banner"];
|
|
12
|
+
readonly renderControl: (fill: InputFillAction) => ReactNode;
|
|
13
|
+
}): ReactNode;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { evalRowExtractor } from "../app/row-actions.js";
|
|
5
|
+
import { useOptionalDispatcher } from "../context/dispatcher-context.js";
|
|
6
|
+
import { useTranslation } from "../i18n.js";
|
|
7
|
+
const NUMERIC_FIELD_TYPES = new Set(["number", "bigInt", "decimal"]);
|
|
8
|
+
// A text field takes a number as its string form; a numeric field takes only a
|
|
9
|
+
// number — a string would be sent as a wrong-typed value.
|
|
10
|
+
function fillValueFor(fieldType, result) {
|
|
11
|
+
if (NUMERIC_FIELD_TYPES.has(fieldType))
|
|
12
|
+
return typeof result === "number" ? result : undefined;
|
|
13
|
+
if (typeof result === "string")
|
|
14
|
+
return result;
|
|
15
|
+
return typeof result === "number" ? String(result) : undefined;
|
|
16
|
+
}
|
|
17
|
+
// Owns the query state of a fill button. A separate component because RenderField
|
|
18
|
+
// returns early for hidden fields and the dispatcher hook must not be conditional.
|
|
19
|
+
export function FillActionControl({ field, fillAction, row, onChange, disabled, Banner, renderControl, }) {
|
|
20
|
+
const dispatcher = useOptionalDispatcher();
|
|
21
|
+
const t = useTranslation();
|
|
22
|
+
const [busy, setBusy] = useState(false);
|
|
23
|
+
const [failed, setFailed] = useState(false);
|
|
24
|
+
// A newer click or an unmount cancels the in-flight query so a late answer
|
|
25
|
+
// cannot overwrite a value the user has since typed.
|
|
26
|
+
const activeRun = useRef(null);
|
|
27
|
+
useEffect(() => () => activeRun.current?.abort(), []);
|
|
28
|
+
const run = async () => {
|
|
29
|
+
if (dispatcher === undefined) {
|
|
30
|
+
setFailed(true);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
activeRun.current?.abort();
|
|
34
|
+
const ctrl = new AbortController();
|
|
35
|
+
activeRun.current = ctrl;
|
|
36
|
+
setBusy(true);
|
|
37
|
+
setFailed(false);
|
|
38
|
+
const payload = fillAction.params === undefined ? {} : evalRowExtractor(fillAction.params, row);
|
|
39
|
+
const result = await dispatcher.query(fillAction.query, payload, {
|
|
40
|
+
signal: ctrl.signal,
|
|
41
|
+
});
|
|
42
|
+
if (ctrl.signal.aborted)
|
|
43
|
+
return;
|
|
44
|
+
setBusy(false);
|
|
45
|
+
if (!result.isSuccess) {
|
|
46
|
+
setFailed(true);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const value = fillValueFor(field.type, propertyOf(result.data, fillAction.field));
|
|
50
|
+
if (value === undefined) {
|
|
51
|
+
setFailed(true);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
onChange(value);
|
|
55
|
+
};
|
|
56
|
+
return (_jsxs(_Fragment, { children: [renderControl({
|
|
57
|
+
label: fillAction.label,
|
|
58
|
+
onPress: () => void run(),
|
|
59
|
+
busy,
|
|
60
|
+
disabled,
|
|
61
|
+
testId: `field-fill-${field.field}`,
|
|
62
|
+
}), failed && (_jsx(Banner, { variant: "error", testId: `field-fill-error-${field.field}`, children: t("kumiko.field.fillAction.failed") }))] }));
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { EditFieldViewModel } from "../headless/index.ts";
|
|
3
|
+
import type { useTranslation } from "../i18n.tsx";
|
|
4
|
+
import type { InputFillAction, usePrimitives } from "../primitives.tsx";
|
|
5
|
+
type Primitives = ReturnType<typeof usePrimitives>;
|
|
6
|
+
export type RenderInputArgs = {
|
|
7
|
+
readonly field: EditFieldViewModel;
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly hasError: boolean;
|
|
10
|
+
readonly onChange: (value: unknown) => void;
|
|
11
|
+
readonly Input: Primitives["Input"];
|
|
12
|
+
readonly appLocale: string;
|
|
13
|
+
readonly Banner: Primitives["Banner"];
|
|
14
|
+
readonly Text: Primitives["Text"];
|
|
15
|
+
readonly JsonView: Primitives["JsonView"];
|
|
16
|
+
readonly t: ReturnType<typeof useTranslation>;
|
|
17
|
+
readonly row?: Readonly<Record<string, unknown>>;
|
|
18
|
+
readonly fill?: InputFillAction;
|
|
19
|
+
};
|
|
20
|
+
export type SharedInputProps = {
|
|
21
|
+
readonly common: {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly disabled: boolean;
|
|
25
|
+
readonly required: boolean;
|
|
26
|
+
readonly hasError: boolean;
|
|
27
|
+
};
|
|
28
|
+
readonly placeholderProp: {
|
|
29
|
+
readonly placeholder?: string;
|
|
30
|
+
};
|
|
31
|
+
readonly affixProps: {
|
|
32
|
+
readonly prefix?: string;
|
|
33
|
+
readonly suffix?: string;
|
|
34
|
+
readonly fill?: InputFillAction;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type InputRenderer = (args: RenderInputArgs, shared: SharedInputProps) => ReactNode;
|
|
38
|
+
export declare function sharedInputProps({ field, id, hasError, row, fill, }: RenderInputArgs): SharedInputProps;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { resolveAffix } from "./render-field-values.js";
|
|
2
|
+
export function sharedInputProps({ field, id, hasError, row, fill, }) {
|
|
3
|
+
const prefix = resolveAffix(field.prefix, row);
|
|
4
|
+
const suffix = resolveAffix(field.suffix, row);
|
|
5
|
+
return {
|
|
6
|
+
common: {
|
|
7
|
+
id,
|
|
8
|
+
name: field.field,
|
|
9
|
+
disabled: field.readOnly,
|
|
10
|
+
required: field.required,
|
|
11
|
+
hasError,
|
|
12
|
+
},
|
|
13
|
+
placeholderProp: field.placeholder !== undefined ? { placeholder: field.placeholder } : {},
|
|
14
|
+
affixProps: {
|
|
15
|
+
...(prefix !== undefined && { prefix }),
|
|
16
|
+
...(suffix !== undefined && { suffix }),
|
|
17
|
+
...(fill !== undefined && { fill }),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { renderFileInput, renderFileListInput, renderMultiSelectInput, renderSelectInput, renderUnsupportedInput, } from "./render-field-choice-inputs.js";
|
|
2
|
+
import { sharedInputProps, } from "./render-field-input-shared.js";
|
|
3
|
+
import { renderBooleanInput, renderDateInput, renderDecimalInput, renderLocatedTimestampInput, renderMoneyInput, renderNumberInput, renderTimestampInput, renderTzInput, } from "./render-field-scalar-inputs.js";
|
|
4
|
+
import { renderFallbackInput } from "./render-field-text-inputs.js";
|
|
5
|
+
// A Map, not an object: `field.type` is data, and a bare object lookup would
|
|
6
|
+
// resolve inherited keys like "constructor".
|
|
7
|
+
const INPUT_RENDERERS = new Map([
|
|
8
|
+
["number", renderNumberInput],
|
|
9
|
+
["bigInt", renderNumberInput],
|
|
10
|
+
["decimal", renderDecimalInput],
|
|
11
|
+
["tz", renderTzInput],
|
|
12
|
+
["multiSelect", renderMultiSelectInput],
|
|
13
|
+
["money", renderMoneyInput],
|
|
14
|
+
["boolean", renderBooleanInput],
|
|
15
|
+
["date", renderDateInput],
|
|
16
|
+
["timestamp", renderTimestampInput],
|
|
17
|
+
["locatedTimestamp", renderLocatedTimestampInput],
|
|
18
|
+
["select", renderSelectInput],
|
|
19
|
+
["file", renderFileInput],
|
|
20
|
+
["image", renderFileInput],
|
|
21
|
+
["files", renderFileListInput],
|
|
22
|
+
["images", renderFileListInput],
|
|
23
|
+
["embedded", renderUnsupportedInput],
|
|
24
|
+
["jsonb", renderUnsupportedInput],
|
|
25
|
+
]);
|
|
26
|
+
// Dispatches field.type → Input-kind. Select threads options through
|
|
27
|
+
// from the EditFieldViewModel (computeEditViewModel pulls them from
|
|
28
|
+
// SelectFieldDef.options). Structural types without a widget (embedded,
|
|
29
|
+
// jsonb, multiSelect) render read-only instead of a data-destroying text
|
|
30
|
+
// fallback; truly unknown scalar types still fall back to text.
|
|
31
|
+
export function renderInput(args) {
|
|
32
|
+
const render = INPUT_RENDERERS.get(args.field.type) ?? renderFallbackInput;
|
|
33
|
+
return render(args, sharedInputProps(args));
|
|
34
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FieldIconKey } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { EditFieldViewModel } from "../headless/index.ts";
|
|
3
|
+
export declare function inputId(field: EditFieldViewModel): string;
|
|
4
|
+
export declare function isComplexFieldType(type: string): boolean;
|
|
5
|
+
export declare function isFileFieldType(type: string): boolean;
|
|
6
|
+
export declare function resolveFieldIcon(field: EditFieldViewModel): FieldIconKey | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { toKebab } from "../app/qn.js";
|
|
2
|
+
export function inputId(field) {
|
|
3
|
+
return `kumiko-edit-${field.field}`;
|
|
4
|
+
}
|
|
5
|
+
// Field types whose read display already isn't a boxed "disabled input" look
|
|
6
|
+
// (embedded/jsonb render as an info Banner) — `readOnlyDisplayText` doesn't
|
|
7
|
+
// cover these, they keep going through `renderInput`'s existing Banner branch
|
|
8
|
+
// in text-display mode too.
|
|
9
|
+
export function isComplexFieldType(type) {
|
|
10
|
+
return type === "embedded" || type === "jsonb";
|
|
11
|
+
}
|
|
12
|
+
// Read-only file fields keep their (disabled) widget: it shows a thumbnail or
|
|
13
|
+
// the file name, where plain text could only show the FileRef UUID.
|
|
14
|
+
export function isFileFieldType(type) {
|
|
15
|
+
return type === "file" || type === "image" || type === "files" || type === "images";
|
|
16
|
+
}
|
|
17
|
+
// Mirrors ACTION_ICON_BY_ID (kumiko-screen.tsx) for fields: only names with
|
|
18
|
+
// an unambiguous matching FieldIconKey are listed here, everything else
|
|
19
|
+
// stays iconless rather than guessing.
|
|
20
|
+
const FIELD_ICON_BY_NAME = {
|
|
21
|
+
email: "mail",
|
|
22
|
+
phone: "phone",
|
|
23
|
+
tel: "phone",
|
|
24
|
+
mobile: "phone",
|
|
25
|
+
url: "link",
|
|
26
|
+
website: "link",
|
|
27
|
+
link: "link",
|
|
28
|
+
password: "lock",
|
|
29
|
+
search: "search",
|
|
30
|
+
city: "map-pin",
|
|
31
|
+
address: "map-pin",
|
|
32
|
+
street: "map-pin",
|
|
33
|
+
user: "user",
|
|
34
|
+
owner: "user",
|
|
35
|
+
assignee: "user",
|
|
36
|
+
};
|
|
37
|
+
function kebabLastSegment(value) {
|
|
38
|
+
const kebab = toKebab(value);
|
|
39
|
+
const lastDash = kebab.lastIndexOf("-");
|
|
40
|
+
return lastDash === -1 ? kebab : kebab.slice(lastDash + 1);
|
|
41
|
+
}
|
|
42
|
+
// Derives a prefix icon when the field declares none: field.icon wins, then
|
|
43
|
+
// the field name. Deliberately no type-based fallback — a hash in front of a
|
|
44
|
+
// number field restates what the field already shows and reads as noise.
|
|
45
|
+
export function resolveFieldIcon(field) {
|
|
46
|
+
if (field.icon !== undefined)
|
|
47
|
+
return field.icon;
|
|
48
|
+
const byName = FIELD_ICON_BY_NAME[field.field.toLowerCase()] ??
|
|
49
|
+
FIELD_ICON_BY_NAME[kebabLastSegment(field.field)];
|
|
50
|
+
return byName;
|
|
51
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FieldRenderer } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { type EditFieldViewModel } from "../headless/index.ts";
|
|
4
|
+
import { useTranslation } from "../i18n.tsx";
|
|
5
|
+
export declare function FieldRendererOutput({ field, renderer, row, appLocale, }: {
|
|
6
|
+
readonly field: EditFieldViewModel;
|
|
7
|
+
readonly renderer: FieldRenderer;
|
|
8
|
+
readonly row?: Readonly<Record<string, unknown>>;
|
|
9
|
+
readonly appLocale: string;
|
|
10
|
+
}): ReactNode;
|
|
11
|
+
export declare function readOnlyDisplayText(field: EditFieldViewModel, appLocale: string, t: ReturnType<typeof useTranslation>): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { isFormatSpec } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
3
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
4
|
+
import { useColumnRenderer } from "../app/column-renderers.js";
|
|
5
|
+
import { extensionSectionName } from "../app/extension-sections.js";
|
|
6
|
+
import { rendererWarn } from "../dev-console.js";
|
|
7
|
+
import { fileIdsOf } from "../file-ids-of.js";
|
|
8
|
+
import { applyFormatSpec, EMPTY_VALUE_PLACEHOLDER, } from "../headless/index.js";
|
|
9
|
+
import { useTranslation } from "../i18n.js";
|
|
10
|
+
import { usePrimitives } from "../primitives.js";
|
|
11
|
+
import { DISPLAY_TEXT_BY_TYPE, defaultDisplayText } from "./render-field-display-text.js";
|
|
12
|
+
import { stringValue } from "./render-field-values.js";
|
|
13
|
+
// Doesn't resolve the `string` (cross-feature QN) FieldRenderer variant — the
|
|
14
|
+
// list-column path (DataTableCell) doesn't either, so this stays in parity.
|
|
15
|
+
export function FieldRendererOutput({ field, renderer, row, appLocale, }) {
|
|
16
|
+
const { Text, JsonView, FileValueList } = usePrimitives();
|
|
17
|
+
const t = useTranslation();
|
|
18
|
+
const valueTestId = `field-value-${field.field}`;
|
|
19
|
+
const componentName = !isFormatSpec(renderer) && typeof renderer === "object" && renderer !== null
|
|
20
|
+
? extensionSectionName(renderer)
|
|
21
|
+
: undefined;
|
|
22
|
+
const Component = useColumnRenderer(componentName);
|
|
23
|
+
if (isFormatSpec(renderer)) {
|
|
24
|
+
// format:"json" wants the raw value (JsonView stringifies + highlights
|
|
25
|
+
// itself) — applyFormatSpec's already-indented string is only the
|
|
26
|
+
// fallback for primitives-providers without JsonView.
|
|
27
|
+
if (renderer.format === "json" && JsonView !== undefined) {
|
|
28
|
+
return _jsx(JsonView, { value: field.value, indent: renderer.indent, testId: valueTestId });
|
|
29
|
+
}
|
|
30
|
+
if (renderer.format === "file" && FileValueList !== undefined) {
|
|
31
|
+
const fileIds = fileIdsOf(field.value);
|
|
32
|
+
if (fileIds.length === 0)
|
|
33
|
+
return _jsx(Text, { testId: valueTestId, children: EMPTY_VALUE_PLACEHOLDER });
|
|
34
|
+
return (_jsx(FileValueList, { fileIds: fileIds, previewOpenByDefault: renderer.previewOpenByDefault === true, testId: valueTestId }));
|
|
35
|
+
}
|
|
36
|
+
// App locale as default when the FormatSpec declares none of its own —
|
|
37
|
+
// otherwise locale-sensitive formats (timestamp/date/number/decimal/
|
|
38
|
+
// bigInt/unit) fell back to Intl's runtime default instead of the app
|
|
39
|
+
// language chosen via LocaleProvider. Prefer renderer.locale
|
|
40
|
+
// when set; coalesce undefined (spread override) back to appLocale.
|
|
41
|
+
return (_jsx(Text, { testId: valueTestId, children: applyFormatSpec({
|
|
42
|
+
...renderer,
|
|
43
|
+
locale: stringPropertyOf(renderer, "locale") ?? appLocale,
|
|
44
|
+
}, field.value, t) }));
|
|
45
|
+
}
|
|
46
|
+
if (componentName !== undefined) {
|
|
47
|
+
if (Component !== undefined) {
|
|
48
|
+
return (_jsx(Component, { value: field.value, row: row ?? { [field.field]: field.value }, column: { field: field.field } }));
|
|
49
|
+
}
|
|
50
|
+
rendererWarn(`[kumiko] fieldRenderer "${componentName}" not registered`);
|
|
51
|
+
}
|
|
52
|
+
return _jsx(Text, { testId: valueTestId, children: stringValue(field.value) });
|
|
53
|
+
}
|
|
54
|
+
function stringPropertyOf(value, key) {
|
|
55
|
+
const property = propertyOf(value, key);
|
|
56
|
+
return typeof property === "string" ? property : undefined;
|
|
57
|
+
}
|
|
58
|
+
// Read-only text for a `field.readOnly` field without its own `renderer` —
|
|
59
|
+
// per-type formatting that reuses the same value-shaping helpers as the
|
|
60
|
+
// editable widgets above, so a text value and its would-be Input widget stay
|
|
61
|
+
// derived from the identical parse. `type: "reference"` isn't
|
|
62
|
+
// covered here — it needs a live label lookup, see ReadOnlyReferenceValue.
|
|
63
|
+
// `isComplexFieldType` types aren't covered either — callers keep those on
|
|
64
|
+
// `renderInput`'s existing Banner fallback.
|
|
65
|
+
export function readOnlyDisplayText(field, appLocale, t) {
|
|
66
|
+
const { type, value } = field;
|
|
67
|
+
if (value === undefined || value === null || value === "")
|
|
68
|
+
return EMPTY_VALUE_PLACEHOLDER;
|
|
69
|
+
if (type === "text" && field.writeOnly === true)
|
|
70
|
+
return t("kumiko.field.writeOnly.set");
|
|
71
|
+
if (type === "text" && field.format === "password")
|
|
72
|
+
return "••••••••";
|
|
73
|
+
const toText = DISPLAY_TEXT_BY_TYPE.get(type) ?? defaultDisplayText;
|
|
74
|
+
return toText(field, value, appLocale);
|
|
75
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type EditFieldViewModel } from "../headless/index.ts";
|
|
3
|
+
import { usePrimitives } from "../primitives.tsx";
|
|
4
|
+
export declare function ReferenceInput({ field, id, hasError, onChange, Input, featureName, }: {
|
|
5
|
+
readonly field: EditFieldViewModel;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly hasError: boolean;
|
|
8
|
+
readonly onChange: (value: unknown) => void;
|
|
9
|
+
readonly Input: ReturnType<typeof usePrimitives>["Input"];
|
|
10
|
+
readonly featureName: string;
|
|
11
|
+
}): ReactNode;
|
|
12
|
+
export declare function ReadOnlyReferenceValue({ field, featureName, }: {
|
|
13
|
+
readonly field: EditFieldViewModel;
|
|
14
|
+
readonly featureName: string;
|
|
15
|
+
}): ReactNode;
|