@tsed/react-formio 3.0.0-rc.2 → 3.0.0-rc.21
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/all.js +2 -0
- package/dist/all.js.map +1 -1
- package/dist/chunks/_baseGet.js +102 -0
- package/dist/chunks/_baseGet.js.map +1 -0
- package/dist/chunks/_baseSlice.js +12 -65
- package/dist/chunks/_baseSlice.js.map +1 -1
- package/dist/chunks/camelCase.js +1 -1
- package/dist/chunks/get.js +16 -0
- package/dist/chunks/get.js.map +1 -0
- package/dist/chunks/index2.js +19746 -22285
- package/dist/chunks/index2.js.map +1 -1
- package/dist/chunks/moment.js +2535 -0
- package/dist/chunks/moment.js.map +1 -0
- package/dist/chunks/omit.js +173 -268
- package/dist/chunks/omit.js.map +1 -1
- package/dist/chunks/toString.js +56 -0
- package/dist/chunks/toString.js.map +1 -0
- package/dist/interfaces/JSONRecord.d.ts +4 -0
- package/dist/interfaces/JSONRecord.js +2 -0
- package/dist/interfaces/JSONRecord.js.map +1 -0
- package/dist/interfaces/Operation.d.ts +3 -12
- package/dist/interfaces/SubmissionType.d.ts +4 -14
- package/dist/interfaces/index.d.ts +0 -1
- package/dist/molecules/forms/select/Select.interface.d.ts +0 -4
- package/dist/molecules/table/Table.d.ts +4 -12
- package/dist/molecules/table/Table.js +34 -33
- package/dist/molecules/table/Table.js.map +1 -1
- package/dist/molecules/table/all.js +2 -0
- package/dist/molecules/table/all.js.map +1 -1
- package/dist/molecules/table/components/DefaultBooleanCell.d.ts +2 -0
- package/dist/molecules/table/components/DefaultBooleanCell.js +12 -0
- package/dist/molecules/table/components/DefaultBooleanCell.js.map +1 -0
- package/dist/molecules/table/components/DefaultCell.d.ts +2 -5
- package/dist/molecules/table/components/DefaultCell.js +8 -6
- package/dist/molecules/table/components/DefaultCell.js.map +1 -1
- package/dist/molecules/table/components/DefaultCellOperations.d.ts +4 -11
- package/dist/molecules/table/components/DefaultCellOperations.js.map +1 -1
- package/dist/molecules/table/components/DefaultDateCell.d.ts +2 -0
- package/dist/molecules/table/components/DefaultDateCell.js +16 -0
- package/dist/molecules/table/components/DefaultDateCell.js.map +1 -0
- package/dist/molecules/table/components/DefaultFilter.d.ts +5 -7
- package/dist/molecules/table/components/DefaultFilter.js +8 -8
- package/dist/molecules/table/components/DefaultFilter.js.map +1 -1
- package/dist/molecules/table/components/DefaultOperationButton.d.ts +4 -11
- package/dist/molecules/table/components/DefaultOperationButton.js.map +1 -1
- package/dist/molecules/table/filters/Filters.d.ts +27 -0
- package/dist/molecules/table/filters/Filters.js +2 -0
- package/dist/molecules/table/filters/Filters.js.map +1 -0
- package/dist/molecules/table/filters/SelectFilter.js +22 -20
- package/dist/molecules/table/filters/SelectFilter.js.map +1 -1
- package/dist/molecules/table/filters/TextFieldFilter.js +16 -16
- package/dist/molecules/table/filters/TextFieldFilter.js.map +1 -1
- package/dist/molecules/table/hooks/useTable.d.ts +4 -11
- package/dist/molecules/table/hooks/useTable.js +14 -14
- package/dist/molecules/table/hooks/useTable.js.map +1 -1
- package/dist/molecules/table/hooks/useUniqValues.d.ts +4 -5
- package/dist/molecules/table/hooks/useUniqValues.js +23 -7
- package/dist/molecules/table/hooks/useUniqValues.js.map +1 -1
- package/dist/molecules/table/interfaces/extends.d.ts +3 -0
- package/dist/molecules/table/utils/mapFormToColumns.d.ts +5 -1
- package/dist/molecules/table/utils/mapFormToColumns.js +53 -26
- package/dist/molecules/table/utils/mapFormToColumns.js.map +1 -1
- package/dist/organisms/form/Form.d.ts +3 -11
- package/dist/organisms/form/Form.js.map +1 -1
- package/dist/organisms/form/actions/FormAction.js +5 -5
- package/dist/organisms/form/types.d.ts +1 -9
- package/dist/organisms/form/useForm.d.ts +4 -11
- package/dist/organisms/form/useForm.js.map +1 -1
- package/dist/organisms/table/forms/components/FormsCell.js +1 -1
- package/dist/organisms/table/submissions/SubmissionsTable.d.ts +4 -11
- package/dist/organisms/table/submissions/SubmissionsTable.js +2 -5
- package/dist/organisms/table/submissions/SubmissionsTable.js.map +1 -1
- package/dist/organisms/views/FormViews.d.ts +4 -11
- package/dist/organisms/views/FormViews.js.map +1 -1
- package/package.json +3 -3
- package/src/all.ts +2 -0
- package/src/interfaces/JSONRecord.ts +2 -0
- package/src/interfaces/Operation.ts +3 -6
- package/src/interfaces/SubmissionType.ts +4 -8
- package/src/interfaces/index.ts +0 -1
- package/src/molecules/forms/select/Select.interface.ts +0 -4
- package/src/molecules/table/Table.stories.tsx +101 -66
- package/src/molecules/table/Table.tsx +57 -56
- package/src/molecules/table/all.ts +2 -0
- package/src/molecules/table/components/DefaultBooleanCell.spec.tsx +42 -0
- package/src/molecules/table/components/DefaultBooleanCell.tsx +11 -0
- package/src/molecules/table/components/DefaultCell.spec.tsx +32 -0
- package/src/molecules/table/components/DefaultCell.tsx +8 -9
- package/src/molecules/table/components/DefaultCellOperations.tsx +4 -3
- package/src/molecules/table/components/DefaultDateCell.spec.tsx +43 -0
- package/src/molecules/table/components/DefaultDateCell.tsx +23 -0
- package/src/molecules/table/components/DefaultFilter.tsx +10 -7
- package/src/molecules/table/components/DefaultOperationButton.tsx +4 -4
- package/src/molecules/table/filters/{Filters.d.ts → Filters.ts} +7 -3
- package/src/molecules/table/filters/SelectFilter.tsx +5 -3
- package/src/molecules/table/filters/TextFieldFilter.tsx +5 -3
- package/src/molecules/table/hooks/useTable.tsx +8 -13
- package/src/molecules/table/hooks/useUniqValues.spec.tsx +82 -0
- package/src/molecules/table/hooks/useUniqValues.tsx +38 -6
- package/src/molecules/table/interfaces/extends.ts +3 -0
- package/src/molecules/table/utils/mapFormToColumns.spec.tsx +116 -0
- package/src/molecules/table/utils/mapFormToColumns.tsx +67 -27
- package/src/organisms/form/Form.stories.tsx +7 -2
- package/src/organisms/form/Form.tsx +3 -3
- package/src/organisms/form/types.ts +1 -6
- package/src/organisms/form/useForm.ts +6 -5
- package/src/organisms/table/submissions/SubmissionsTable.tsx +5 -10
- package/src/organisms/views/FormViews.tsx +6 -9
- package/dist/interfaces/QueryOptions.d.ts +0 -23
- package/dist/interfaces/QueryOptions.js +0 -2
- package/dist/interfaces/QueryOptions.js.map +0 -1
- package/dist/molecules/table/filters/Filters.d.js +0 -2
- package/dist/molecules/table/filters/Filters.d.js.map +0 -1
- package/src/interfaces/QueryOptions.ts +0 -24
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
import "../interfaces/extends";
|
|
2
2
|
|
|
3
|
-
import { Header } from "@tanstack/react-table";
|
|
3
|
+
import { Header, RowData } from "@tanstack/react-table";
|
|
4
4
|
import type { ComponentType } from "react";
|
|
5
5
|
|
|
6
6
|
import { getComponent, registerComponent } from "../../../registries/components";
|
|
7
7
|
|
|
8
|
-
export interface DefaultFilterProps<Data = any> {
|
|
9
|
-
header: Header<Data,
|
|
8
|
+
export interface DefaultFilterProps<Data extends RowData = any, TValue = unknown> {
|
|
9
|
+
header: Header<Data, TValue>;
|
|
10
10
|
i18n?: (f: string) => string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export interface FilterProps<Data = any, Opts = Record<string, unknown>> {
|
|
14
|
-
header: Header<Data, unknown>;
|
|
13
|
+
export interface FilterProps<Data extends RowData = any, Opts = Record<string, unknown>> extends DefaultFilterProps<Data> {
|
|
15
14
|
options: Opts;
|
|
16
|
-
i18n?: (f: string) => string;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
export function DefaultFilter<Data = any
|
|
17
|
+
export function DefaultFilter<Data extends RowData = any, TValue = unknown>(props: DefaultFilterProps<Data, TValue>) {
|
|
18
|
+
const { header, i18n } = props;
|
|
20
19
|
const {
|
|
21
20
|
filter = {
|
|
22
21
|
variant: "text"
|
|
@@ -32,6 +31,10 @@ export function DefaultFilter<Data = any>({ header, i18n }: DefaultFilterProps<D
|
|
|
32
31
|
return null;
|
|
33
32
|
}
|
|
34
33
|
|
|
34
|
+
if (filter.disabled) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
35
38
|
return (
|
|
36
39
|
<div className='table-cell-header__filter'>
|
|
37
40
|
<Filter header={header} options={filter} i18n={i18n} />
|
|
@@ -2,13 +2,13 @@ import { CellContext } from "@tanstack/react-table";
|
|
|
2
2
|
import cx from "classnames";
|
|
3
3
|
import { HTMLAttributes } from "react";
|
|
4
4
|
|
|
5
|
-
import { type CellMetadata,
|
|
5
|
+
import { type CellMetadata, Operation } from "../../../interfaces";
|
|
6
|
+
import type { JSONRecord } from "../../../interfaces/JSONRecord.js";
|
|
6
7
|
import { registerComponent } from "../../../registries/components";
|
|
7
8
|
import { iconClass } from "../../../utils/iconClass";
|
|
8
9
|
import { stopPropagationWrapper } from "../../../utils/stopPropagationWrapper";
|
|
9
10
|
|
|
10
|
-
export interface OperationButtonProps<Data extends
|
|
11
|
-
extends Omit<HTMLAttributes<HTMLButtonElement>, "onClick"> {
|
|
11
|
+
export interface OperationButtonProps<Data extends object = JSONRecord> extends Omit<HTMLAttributes<HTMLButtonElement>, "onClick"> {
|
|
12
12
|
operation: Operation<Data>;
|
|
13
13
|
info: CellContext<Data, unknown>;
|
|
14
14
|
metadata?: CellMetadata;
|
|
@@ -16,7 +16,7 @@ export interface OperationButtonProps<Data extends { [key: string]: JSON } = { [
|
|
|
16
16
|
i18n?: (i18n: string) => string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export function DefaultOperationButton<Data extends
|
|
19
|
+
export function DefaultOperationButton<Data extends object = JSONRecord>(props: OperationButtonProps<Data>) {
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
21
21
|
const { i18n = (f: string) => f, onClick, operation, info, ...extraProps } = props;
|
|
22
22
|
const { className = "btn", buttonSize = "xs", buttonType = "primary", buttonOutline, action, title = "", icon = "" } = operation;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RowData } from "@tanstack/react-table";
|
|
2
|
+
|
|
3
|
+
import type { SelectOptionProps, SelectProps } from "../../forms/select/Select.interface";
|
|
4
|
+
import type { FilterProps } from "../components/DefaultFilter";
|
|
2
5
|
|
|
3
6
|
export type FilterVariants = "text" | "range" | "select" | "boolean";
|
|
4
7
|
|
|
@@ -8,6 +11,7 @@ export interface FilterBaseOptions extends Record<string, unknown> {
|
|
|
8
11
|
|
|
9
12
|
export interface FilterTextOptions extends FilterBaseOptions {
|
|
10
13
|
variant: "text";
|
|
14
|
+
disabled?: boolean;
|
|
11
15
|
disableDatalist?: boolean;
|
|
12
16
|
}
|
|
13
17
|
|
|
@@ -18,9 +22,9 @@ export interface FilterRangeOptions extends FilterBaseOptions {
|
|
|
18
22
|
step?: number;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
export interface FilterSelectOptions extends FilterBaseOptions, SelectProps<string> {
|
|
25
|
+
export interface FilterSelectOptions<Data extends RowData = any> extends FilterBaseOptions, Omit<SelectProps<string>, "options"> {
|
|
22
26
|
variant: "select";
|
|
23
|
-
options?: SelectOptionProps<string>[];
|
|
27
|
+
options?: SelectOptionProps<string>[] | ((props: FilterProps<Data, FilterSelectOptions>) => SelectOptionProps<string>)[];
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export interface FilterBooleanOptions extends FilterBaseOptions {
|
|
@@ -8,7 +8,7 @@ import { FilterSelectOptions } from "./Filters";
|
|
|
8
8
|
export function SelectFilter<Data = any>({ header, options }: FilterProps<Data, FilterSelectOptions>) {
|
|
9
9
|
const Select = getComponent<typeof DefaultSelect>("Select");
|
|
10
10
|
const columnFilterValue = header.column.getFilterValue();
|
|
11
|
-
const uniqValues = useUniqValues<Data>({ header,
|
|
11
|
+
const uniqValues = useUniqValues<Data>({ header, options });
|
|
12
12
|
|
|
13
13
|
const opts =
|
|
14
14
|
options.layout === "choicesjs"
|
|
@@ -26,7 +26,7 @@ export function SelectFilter<Data = any>({ header, options }: FilterProps<Data,
|
|
|
26
26
|
value: ""
|
|
27
27
|
}
|
|
28
28
|
] as SelectOptionProps[]
|
|
29
|
-
).concat(
|
|
29
|
+
).concat(uniqValues);
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
32
|
<>
|
|
@@ -34,10 +34,11 @@ export function SelectFilter<Data = any>({ header, options }: FilterProps<Data,
|
|
|
34
34
|
size='small'
|
|
35
35
|
{...(options as any)}
|
|
36
36
|
{...opts}
|
|
37
|
+
autoComplete='off'
|
|
37
38
|
options={listOptions}
|
|
38
39
|
name={`filter_${header.column.id}`}
|
|
39
40
|
data-testid={`filter_${header.column.id}`}
|
|
40
|
-
value={columnFilterValue as string}
|
|
41
|
+
value={(columnFilterValue ?? "") as string}
|
|
41
42
|
onChange={(_, value) => header.column.setFilterValue(value)}
|
|
42
43
|
/>
|
|
43
44
|
</>
|
|
@@ -45,3 +46,4 @@ export function SelectFilter<Data = any>({ header, options }: FilterProps<Data,
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
registerComponent("Filter.select", SelectFilter);
|
|
49
|
+
registerComponent("Filter.boolean", SelectFilter);
|
|
@@ -7,15 +7,17 @@ import { FilterTextOptions } from "./Filters";
|
|
|
7
7
|
export function TextFieldFilter<Data = any>({ header, options }: FilterProps<Data, FilterTextOptions>) {
|
|
8
8
|
const InputText = getComponent<typeof DefaultInputText>("InputText");
|
|
9
9
|
const columnFilterValue = header.column.getFilterValue();
|
|
10
|
-
const uniqValues = useUniqValues<Data>({ header
|
|
10
|
+
const uniqValues = useUniqValues<Data>({ header });
|
|
11
11
|
const datalistId = `data_list_${header.column.id}`;
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<>
|
|
15
15
|
{!options.disableDatalist && (
|
|
16
16
|
<datalist id={datalistId}>
|
|
17
|
-
{uniqValues.map((
|
|
18
|
-
<option value={value} key={value}
|
|
17
|
+
{uniqValues.map((item) => (
|
|
18
|
+
<option value={item.value} key={item.value}>
|
|
19
|
+
{item.label || item.value}
|
|
20
|
+
</option>
|
|
19
21
|
))}
|
|
20
22
|
</datalist>
|
|
21
23
|
)}
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
import { useEffect } from "react";
|
|
14
14
|
|
|
15
15
|
import { useI18n } from "../../../hooks/useI18n.js";
|
|
16
|
-
import { type FormOptions,
|
|
16
|
+
import { type FormOptions, Operation } from "../../../interfaces";
|
|
17
|
+
import type { JSONRecord } from "../../../interfaces/JSONRecord.js";
|
|
17
18
|
import { getComponent } from "../../../registries/components";
|
|
18
19
|
import type { DefaultCellOperations } from "../components/DefaultCellOperations";
|
|
19
20
|
|
|
20
|
-
export interface UseTableProps<Data extends
|
|
21
|
-
extends Omit<TableOptions<Data>, "getCoreRowModel" | "onClick"> {
|
|
21
|
+
export interface UseTableProps<Data extends object = JSONRecord> extends Omit<TableOptions<Data>, "onClick" | "getCoreRowModel"> {
|
|
22
22
|
operations: Operation<Data>[];
|
|
23
23
|
metadata?: Record<string, unknown>;
|
|
24
24
|
i18n?: FormOptions["i18n"];
|
|
@@ -28,15 +28,10 @@ export interface UseTableProps<Data extends { [key: string]: JSON } = { [key: st
|
|
|
28
28
|
pageSizes?: number[];
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export function useTable<Data extends
|
|
31
|
+
export function useTable<Data extends object = JSONRecord>(props: UseTableProps<Data>) {
|
|
32
32
|
const Operations = getComponent<typeof DefaultCellOperations<Data>>("CellOperations");
|
|
33
33
|
const { t } = useI18n(props.i18n);
|
|
34
34
|
|
|
35
|
-
// const [pagination, setPagination] = useState({
|
|
36
|
-
// pageIndex: 0, //initial page index
|
|
37
|
-
// pageSize: 10 //default page size
|
|
38
|
-
// });
|
|
39
|
-
|
|
40
35
|
const operations = props.operations.length
|
|
41
36
|
? ([
|
|
42
37
|
{
|
|
@@ -54,10 +49,10 @@ export function useTable<Data extends { [key: string]: JSON } = { [key: string]:
|
|
|
54
49
|
columns: [...props.columns, ...operations],
|
|
55
50
|
getCoreRowModel: getCoreRowModel(),
|
|
56
51
|
getPaginationRowModel: getPaginationRowModel(),
|
|
57
|
-
getFilteredRowModel: !props.manualFiltering ? getFilteredRowModel() : undefined,
|
|
58
|
-
getSortedRowModel: !props.manualSorting ? getSortedRowModel() : undefined,
|
|
59
|
-
getFacetedRowModel: !props.manualFaceted ? getFacetedRowModel() : undefined, // client-side faceting
|
|
60
|
-
getFacetedUniqueValues: !props.manualFaceted ? getFacetedUniqueValues() : undefined // generate unique values for select filter/autocomplete
|
|
52
|
+
getFilteredRowModel: !props.manualFiltering ? props.getFilteredRowModel || getFilteredRowModel() : undefined,
|
|
53
|
+
getSortedRowModel: !props.manualSorting ? props.getSortedRowModel || getSortedRowModel() : undefined,
|
|
54
|
+
getFacetedRowModel: !props.manualFaceted ? props.getFacetedRowModel || getFacetedRowModel() : undefined, // client-side faceting
|
|
55
|
+
getFacetedUniqueValues: !props.manualFaceted ? props.getFacetedUniqueValues || getFacetedUniqueValues() : undefined // generate unique values for select filter/autocomplete
|
|
61
56
|
});
|
|
62
57
|
|
|
63
58
|
const { columnFilters, sorting, pagination, columnOrder, columnPinning, columnSizing, columnSizingInfo, columnVisibility, globalFilter } =
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
|
2
|
+
import { vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { useUniqValues } from "./useUniqValues";
|
|
5
|
+
|
|
6
|
+
function createHeader({ variant, labels, values = [] }: { variant?: string; labels?: Record<string, string>; values?: string[] }) {
|
|
7
|
+
return {
|
|
8
|
+
column: {
|
|
9
|
+
columnDef: {
|
|
10
|
+
meta: {
|
|
11
|
+
filter: {
|
|
12
|
+
variant
|
|
13
|
+
},
|
|
14
|
+
labels
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
getFacetedUniqueValues: () => new Map(values.map((value) => [value, 1]))
|
|
18
|
+
}
|
|
19
|
+
} as any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("useUniqValues", () => {
|
|
23
|
+
it("should return boolean options with the custom labels", () => {
|
|
24
|
+
const header = createHeader({
|
|
25
|
+
variant: "boolean",
|
|
26
|
+
labels: {
|
|
27
|
+
yes: "Enabled",
|
|
28
|
+
No: "Disabled"
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const { result } = renderHook(() => useUniqValues({ header }));
|
|
33
|
+
|
|
34
|
+
expect(result.current).toEqual([
|
|
35
|
+
{ label: "Enabled", value: "true" },
|
|
36
|
+
{ label: "Disabled", value: "false" }
|
|
37
|
+
]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should map faceted unique values to select options", () => {
|
|
41
|
+
const header = createHeader({
|
|
42
|
+
values: ["beta", "alpha"]
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const { result } = renderHook(() => useUniqValues({ header }));
|
|
46
|
+
|
|
47
|
+
expect(result.current).toEqual([
|
|
48
|
+
{ label: "alpha", value: "alpha" },
|
|
49
|
+
{ label: "beta", value: "beta" }
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should return provided select options", () => {
|
|
54
|
+
const header = createHeader({});
|
|
55
|
+
const options = [
|
|
56
|
+
{ label: "First", value: "first" },
|
|
57
|
+
{ label: "Second", value: "second" }
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const { result } = renderHook(() => useUniqValues({ header, options: { variant: "select", options } as any }));
|
|
61
|
+
|
|
62
|
+
expect(result.current).toEqual(options);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("should resolve select options from callback", () => {
|
|
66
|
+
const header = createHeader({});
|
|
67
|
+
const optionsFn = vi.fn().mockReturnValue([{ label: "From callback", value: "callback" }]);
|
|
68
|
+
|
|
69
|
+
const { result } = renderHook(() =>
|
|
70
|
+
useUniqValues({
|
|
71
|
+
header,
|
|
72
|
+
options: { variant: "select", options: optionsFn } as any
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(optionsFn).toHaveBeenCalledWith({
|
|
77
|
+
header,
|
|
78
|
+
options: { variant: "select", options: optionsFn }
|
|
79
|
+
});
|
|
80
|
+
expect(result.current).toEqual([{ label: "From callback", value: "callback" }]);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -1,10 +1,42 @@
|
|
|
1
|
-
import type { Header } from "@tanstack/react-table";
|
|
2
1
|
import { useMemo } from "react";
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import { SelectOptionProps } from "../../../molecules/forms/select/Select.interface";
|
|
4
|
+
import type { FilterProps } from "../components/DefaultFilter";
|
|
5
|
+
import { FilterSelectOptions } from "../filters/Filters";
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
)
|
|
7
|
+
export function useUniqValues<Data = any>({ header, options }: FilterProps<Data, FilterSelectOptions>): SelectOptionProps[] {
|
|
8
|
+
return useMemo(() => {
|
|
9
|
+
const providedOptions = options?.options;
|
|
10
|
+
|
|
11
|
+
if (providedOptions) {
|
|
12
|
+
if (typeof providedOptions === "function") {
|
|
13
|
+
return providedOptions({ header, options: options as FilterSelectOptions });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return providedOptions;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
switch (header.column.columnDef.meta?.filter?.variant) {
|
|
20
|
+
case "boolean":
|
|
21
|
+
return [
|
|
22
|
+
{ label: header.column.columnDef.meta?.labels?.["yes"] || "Yes", value: "true" },
|
|
23
|
+
{
|
|
24
|
+
label: header.column.columnDef.meta?.labels?.["no"] || header.column.columnDef.meta?.labels?.["No"] || "No",
|
|
25
|
+
value: "false"
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
default:
|
|
30
|
+
return Array.from(header.column.getFacetedUniqueValues().keys())
|
|
31
|
+
.flat()
|
|
32
|
+
.filter((value) => value !== undefined && value !== null)
|
|
33
|
+
.map((value) => String(value))
|
|
34
|
+
.sort()
|
|
35
|
+
.slice(0, 5000)
|
|
36
|
+
.map((value) => ({
|
|
37
|
+
label: value,
|
|
38
|
+
value
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
}, [header, options]);
|
|
10
42
|
}
|
|
@@ -7,6 +7,9 @@ declare module "@tanstack/react-table" {
|
|
|
7
7
|
//allows us to define custom properties for our columns
|
|
8
8
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
9
9
|
interface ColumnMeta<TData extends RowData, TValue> {
|
|
10
|
+
type?: "string" | "number" | "boolean" | "date" | string;
|
|
11
|
+
format?: string;
|
|
12
|
+
labels?: Record<string, string>;
|
|
10
13
|
filter?: FilterOptions;
|
|
11
14
|
sort?: string;
|
|
12
15
|
cellProps?: TdHTMLAttributes<HTMLTableCellElement>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { DefaultCellBoolean } from "../components/DefaultBooleanCell";
|
|
2
|
+
import { DefaultCell } from "../components/DefaultCell";
|
|
3
|
+
import { DefaultDateCell } from "../components/DefaultDateCell";
|
|
4
|
+
import { mapFormToColumns } from "./mapFormToColumns";
|
|
5
|
+
|
|
6
|
+
describe("mapFormToColumns", () => {
|
|
7
|
+
it("should use DefaultCellBoolean for mapped checkbox table columns", () => {
|
|
8
|
+
const form = {
|
|
9
|
+
components: [
|
|
10
|
+
{
|
|
11
|
+
type: "checkbox",
|
|
12
|
+
key: "enabled",
|
|
13
|
+
label: "Enabled:",
|
|
14
|
+
tableView: true
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
} as any;
|
|
18
|
+
|
|
19
|
+
const [column] = mapFormToColumns({ form: form }) as any[];
|
|
20
|
+
|
|
21
|
+
expect(column.accessorKey).toEqual("data.enabled");
|
|
22
|
+
expect(column.header).toEqual("Enabled");
|
|
23
|
+
expect(column.meta.type).toEqual("boolean");
|
|
24
|
+
expect(column.meta.filter.variant).toEqual("boolean");
|
|
25
|
+
expect(column.cell).toBe(DefaultCellBoolean);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should fallback to DefaultCell for kept columns without a cell renderer", () => {
|
|
29
|
+
const form = {
|
|
30
|
+
components: []
|
|
31
|
+
} as any;
|
|
32
|
+
|
|
33
|
+
const [column] = mapFormToColumns({ form: form, columns: [{ accessorKey: "data.other", header: "Other" } as any] }) as any[];
|
|
34
|
+
|
|
35
|
+
expect(column.accessorKey).toEqual("data.other");
|
|
36
|
+
expect(column.cell).toBe(DefaultCell);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should merge a kept column matched by component key", () => {
|
|
40
|
+
const form = {
|
|
41
|
+
components: [
|
|
42
|
+
{
|
|
43
|
+
type: "textfield",
|
|
44
|
+
key: "name",
|
|
45
|
+
label: "Name:",
|
|
46
|
+
tableView: true
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
} as any;
|
|
50
|
+
|
|
51
|
+
const [column] = mapFormToColumns({
|
|
52
|
+
form: form,
|
|
53
|
+
columns: [
|
|
54
|
+
{
|
|
55
|
+
id: "name",
|
|
56
|
+
header: "Custom name",
|
|
57
|
+
meta: {
|
|
58
|
+
order: 5,
|
|
59
|
+
filter: {
|
|
60
|
+
variant: "text"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} as any
|
|
64
|
+
]
|
|
65
|
+
}) as any[];
|
|
66
|
+
|
|
67
|
+
expect(column.accessorKey).toEqual("data.name");
|
|
68
|
+
expect(column.header).toEqual("Custom name");
|
|
69
|
+
expect(column.meta.order).toEqual(5);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should use DefaultDateCell for mapped datetime columns", () => {
|
|
73
|
+
const form = {
|
|
74
|
+
components: [
|
|
75
|
+
{
|
|
76
|
+
type: "datetime",
|
|
77
|
+
key: "createdAt",
|
|
78
|
+
label: "Created at:",
|
|
79
|
+
tableView: true
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
} as any;
|
|
83
|
+
|
|
84
|
+
const [column] = mapFormToColumns({ form: form }) as any[];
|
|
85
|
+
|
|
86
|
+
expect(column.accessorKey).toEqual("data.createdAt");
|
|
87
|
+
expect(column.meta.type).toEqual("date");
|
|
88
|
+
expect(column.cell).toBe(DefaultDateCell);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should dedupe columns when a kept column matches a mapped accessor key", () => {
|
|
92
|
+
const form = {
|
|
93
|
+
components: [
|
|
94
|
+
{
|
|
95
|
+
type: "textfield",
|
|
96
|
+
key: "email",
|
|
97
|
+
label: "Email:",
|
|
98
|
+
tableView: true
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
} as any;
|
|
102
|
+
|
|
103
|
+
const columns = mapFormToColumns({
|
|
104
|
+
form: form,
|
|
105
|
+
columns: [
|
|
106
|
+
{
|
|
107
|
+
accessorKey: "data.email",
|
|
108
|
+
header: "Email"
|
|
109
|
+
} as any
|
|
110
|
+
]
|
|
111
|
+
}) as any[];
|
|
112
|
+
|
|
113
|
+
expect(columns).toHaveLength(1);
|
|
114
|
+
expect(columns[0].accessorKey).toEqual("data.email");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "../interfaces/extends";
|
|
2
2
|
|
|
3
|
-
import { Components } from "@formio/js";
|
|
4
3
|
import { ColumnDef, ColumnDefResolved, createColumnHelper } from "@tanstack/react-table";
|
|
5
4
|
import cloneDeep from "lodash/cloneDeep";
|
|
6
5
|
import get from "lodash/get";
|
|
@@ -8,20 +7,46 @@ import get from "lodash/get";
|
|
|
8
7
|
import type { ComponentType, FormType } from "../../../interfaces";
|
|
9
8
|
import { getComponent } from "../../../registries/components";
|
|
10
9
|
import type { DefaultCell } from "../components/DefaultCell";
|
|
11
|
-
import type { FilterVariants } from "../filters/Filters
|
|
10
|
+
import type { FilterVariants } from "../filters/Filters";
|
|
12
11
|
|
|
13
|
-
const
|
|
12
|
+
const MAP_FILTER_TYPES: Record<string, FilterVariants> = {
|
|
14
13
|
number: "range",
|
|
15
14
|
currency: "range",
|
|
16
15
|
checkbox: "boolean"
|
|
17
|
-
};
|
|
16
|
+
} as const;
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
const MAP_TYPES = {
|
|
19
|
+
date: "date",
|
|
20
|
+
datetime: "date",
|
|
21
|
+
number: "number",
|
|
22
|
+
currency: "currency",
|
|
23
|
+
checkbox: "boolean"
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
function getColumnIdentity<Data>(column: ColumnDef<Data, any> | ColumnDefResolved<Data, any>) {
|
|
27
|
+
if ("id" in column && typeof column.id === "string") {
|
|
28
|
+
return column.id;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if ("accessorKey" in column && typeof column.accessorKey === "string") {
|
|
32
|
+
return column.accessorKey;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function mapFormToColumns<Data = any>({
|
|
39
|
+
form,
|
|
40
|
+
columns = [],
|
|
41
|
+
prefix = "data."
|
|
42
|
+
}: {
|
|
43
|
+
form: FormType;
|
|
44
|
+
columns?: ColumnDefResolved<Data, any>[];
|
|
45
|
+
prefix?: string;
|
|
46
|
+
}): ColumnDef<Data, any>[] {
|
|
20
47
|
const columnHelper = createColumnHelper<Data>();
|
|
21
48
|
const columnsToKeep = cloneDeep(columns);
|
|
22
49
|
|
|
23
|
-
const Cell = getComponent<typeof DefaultCell>("Cell");
|
|
24
|
-
|
|
25
50
|
const columnsFromComponents = form.components
|
|
26
51
|
.flatMap((component) => {
|
|
27
52
|
if (component.type === "tabs") {
|
|
@@ -33,10 +58,13 @@ export function mapFormToColumns<Data = any>(form: FormType, columns: ColumnDefR
|
|
|
33
58
|
.filter((component) => component?.tableView)
|
|
34
59
|
.map((c) => {
|
|
35
60
|
const component = c as ComponentType;
|
|
36
|
-
const
|
|
61
|
+
const componentColumnKey = `${prefix}${component.key}`;
|
|
62
|
+
const matchingKeys = new Set([component.key, componentColumnKey]);
|
|
37
63
|
|
|
38
|
-
const columnIndex = columnsToKeep.findIndex((
|
|
39
|
-
|
|
64
|
+
const columnIndex = columnsToKeep.findIndex((column) => {
|
|
65
|
+
const identity = getColumnIdentity(column);
|
|
66
|
+
|
|
67
|
+
return identity ? matchingKeys.has(identity) : false;
|
|
40
68
|
});
|
|
41
69
|
|
|
42
70
|
let column = columnsToKeep[columnIndex];
|
|
@@ -45,31 +73,43 @@ export function mapFormToColumns<Data = any>(form: FormType, columns: ColumnDefR
|
|
|
45
73
|
columnsToKeep.splice(columnIndex, 1);
|
|
46
74
|
}
|
|
47
75
|
|
|
48
|
-
return columnHelper.accessor(
|
|
76
|
+
return columnHelper.accessor(componentColumnKey as any, {
|
|
49
77
|
header: (component.label || component.title || component.key)?.replace(/:/, ""),
|
|
50
|
-
cell: (info) => {
|
|
51
|
-
return <Cell value={info.getValue() as Data} render={(value: Data) => cmp.asString(value)} />;
|
|
52
|
-
},
|
|
53
78
|
meta: {
|
|
54
|
-
|
|
79
|
+
type: (MAP_TYPES[component.type as keyof typeof MAP_TYPES] || component.type) as any,
|
|
80
|
+
filter: {
|
|
81
|
+
...column?.meta?.filter,
|
|
82
|
+
variant: MAP_FILTER_TYPES[component.type as keyof typeof MAP_FILTER_TYPES] || "text"
|
|
83
|
+
},
|
|
55
84
|
...(column?.meta || {})
|
|
56
85
|
},
|
|
57
86
|
...(column || {})
|
|
58
87
|
});
|
|
59
88
|
});
|
|
60
89
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
const dedupedColumns = [...columnsFromComponents, ...(columnsToKeep as any[])].reduce<ColumnDef<Data, any>[]>((acc, column) => {
|
|
91
|
+
const identity = getColumnIdentity(column);
|
|
92
|
+
|
|
93
|
+
if (identity && acc.some((existingColumn) => getColumnIdentity(existingColumn) === identity)) {
|
|
94
|
+
return acc;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
acc.push(column);
|
|
98
|
+
return acc;
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
const mergedColumns = dedupedColumns.map((column, index) => {
|
|
102
|
+
const Cell = getComponent<typeof DefaultCell>([`Cell.${column.id}`, `Cell.${column.meta?.type}`, "Cell"]);
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...column,
|
|
106
|
+
meta: {
|
|
107
|
+
...column?.meta,
|
|
108
|
+
order: get(column, "meta.order", index * 10)
|
|
109
|
+
},
|
|
110
|
+
cell: column.cell || Cell
|
|
111
|
+
};
|
|
112
|
+
});
|
|
73
113
|
|
|
74
114
|
return mergedColumns.sort((a, b) => (a.meta.order > b.meta.order ? 1 : -1)) as ColumnDef<Data, any>[];
|
|
75
115
|
}
|
|
@@ -260,12 +260,17 @@ export const WithSubmissionData: Story = {
|
|
|
260
260
|
*/
|
|
261
261
|
export const WithOnSubmit: Story = {
|
|
262
262
|
render(args) {
|
|
263
|
+
type OnSubmitData = {
|
|
264
|
+
firstName?: string;
|
|
265
|
+
[key: string]: unknown;
|
|
266
|
+
};
|
|
267
|
+
|
|
263
268
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
264
|
-
const [data, setData] = useState(() => args.submission!.data);
|
|
269
|
+
const [data, setData] = useState<OnSubmitData>(() => args.submission!.data as OnSubmitData);
|
|
265
270
|
|
|
266
271
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
267
272
|
useEffect(() => {
|
|
268
|
-
setData(args.submission!.data);
|
|
273
|
+
setData(args.submission!.data as OnSubmitData);
|
|
269
274
|
}, [args.submission]);
|
|
270
275
|
|
|
271
276
|
return (
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { JSONRecord } from "../../interfaces/JSONRecord.js";
|
|
2
2
|
import { useForm, UseFormProps } from "./useForm";
|
|
3
3
|
|
|
4
4
|
class CSSProperties {}
|
|
5
5
|
|
|
6
|
-
export interface FormProps<Data extends
|
|
6
|
+
export interface FormProps<Data extends object = JSONRecord> extends UseFormProps<Data> {
|
|
7
7
|
["data-testid"]?: string;
|
|
8
8
|
/**
|
|
9
9
|
*
|
|
@@ -13,7 +13,7 @@ export interface FormProps<Data extends { [key: string]: JSON } = { [key: string
|
|
|
13
13
|
style?: CSSProperties;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export function Form<Data extends
|
|
16
|
+
export function Form<Data extends object = JSONRecord>({
|
|
17
17
|
style,
|
|
18
18
|
className,
|
|
19
19
|
"data-testid": dataTestId = "formio-container",
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import { ComponentType
|
|
2
|
-
|
|
3
|
-
export interface FormPageChangeProps<Data extends { [key: string]: JSON } = { [key: string]: JSON }> {
|
|
4
|
-
page: number;
|
|
5
|
-
submission: SubmissionType<Data>;
|
|
6
|
-
}
|
|
1
|
+
import { ComponentType } from "../../interfaces";
|
|
7
2
|
|
|
8
3
|
export type FormCustomEvent = { type: string; event: string; component: ComponentType; data: any };
|