@tsed/react-formio 3.0.0-rc.12 → 3.0.0-rc.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/all.js +2 -0
  2. package/dist/all.js.map +1 -1
  3. package/dist/chunks/_baseGet.js +102 -0
  4. package/dist/chunks/_baseGet.js.map +1 -0
  5. package/dist/chunks/_baseSlice.js +12 -65
  6. package/dist/chunks/_baseSlice.js.map +1 -1
  7. package/dist/chunks/camelCase.js +1 -1
  8. package/dist/chunks/get.js +16 -0
  9. package/dist/chunks/get.js.map +1 -0
  10. package/dist/chunks/index2.js +19746 -22285
  11. package/dist/chunks/index2.js.map +1 -1
  12. package/dist/chunks/moment.js +2535 -0
  13. package/dist/chunks/moment.js.map +1 -0
  14. package/dist/chunks/omit.js +173 -268
  15. package/dist/chunks/omit.js.map +1 -1
  16. package/dist/chunks/toString.js +56 -0
  17. package/dist/chunks/toString.js.map +1 -0
  18. package/dist/interfaces/index.d.ts +0 -1
  19. package/dist/molecules/forms/select/Select.interface.d.ts +0 -4
  20. package/dist/molecules/table/all.js +2 -0
  21. package/dist/molecules/table/all.js.map +1 -1
  22. package/dist/molecules/table/components/DefaultBooleanCell.d.ts +2 -0
  23. package/dist/molecules/table/components/DefaultBooleanCell.js +12 -0
  24. package/dist/molecules/table/components/DefaultBooleanCell.js.map +1 -0
  25. package/dist/molecules/table/components/DefaultCell.d.ts +2 -5
  26. package/dist/molecules/table/components/DefaultCell.js +8 -6
  27. package/dist/molecules/table/components/DefaultCell.js.map +1 -1
  28. package/dist/molecules/table/components/DefaultDateCell.d.ts +2 -0
  29. package/dist/molecules/table/components/DefaultDateCell.js +16 -0
  30. package/dist/molecules/table/components/DefaultDateCell.js.map +1 -0
  31. package/dist/molecules/table/components/DefaultFilter.d.ts +5 -7
  32. package/dist/molecules/table/components/DefaultFilter.js +8 -8
  33. package/dist/molecules/table/components/DefaultFilter.js.map +1 -1
  34. package/dist/molecules/table/filters/SelectFilter.js +8 -7
  35. package/dist/molecules/table/filters/SelectFilter.js.map +1 -1
  36. package/dist/molecules/table/interfaces/extends.d.ts +3 -0
  37. package/dist/molecules/table/utils/mapFormToColumns.js +38 -27
  38. package/dist/molecules/table/utils/mapFormToColumns.js.map +1 -1
  39. package/dist/organisms/form/actions/FormAction.js +5 -5
  40. package/dist/organisms/table/forms/components/FormsCell.js +1 -1
  41. package/package.json +3 -3
  42. package/src/all.ts +2 -0
  43. package/src/interfaces/index.ts +0 -1
  44. package/src/molecules/forms/select/Select.interface.ts +0 -4
  45. package/src/molecules/table/all.ts +2 -0
  46. package/src/molecules/table/components/DefaultBooleanCell.spec.tsx +42 -0
  47. package/src/molecules/table/components/DefaultBooleanCell.tsx +11 -0
  48. package/src/molecules/table/components/DefaultCell.spec.tsx +32 -0
  49. package/src/molecules/table/components/DefaultCell.tsx +8 -9
  50. package/src/molecules/table/components/DefaultDateCell.spec.tsx +43 -0
  51. package/src/molecules/table/components/DefaultDateCell.tsx +23 -0
  52. package/src/molecules/table/components/DefaultFilter.tsx +10 -7
  53. package/src/molecules/table/filters/Filters.d.ts +1 -0
  54. package/src/molecules/table/filters/SelectFilter.tsx +1 -0
  55. package/src/molecules/table/interfaces/extends.ts +3 -0
  56. package/src/molecules/table/utils/mapFormToColumns.spec.tsx +55 -0
  57. package/src/molecules/table/utils/mapFormToColumns.tsx +27 -22
  58. package/src/organisms/form/Form.stories.tsx +7 -2
  59. package/dist/interfaces/QueryOptions.d.ts +0 -23
  60. package/dist/interfaces/QueryOptions.js +0 -2
  61. package/dist/interfaces/QueryOptions.js.map +0 -1
  62. package/src/interfaces/QueryOptions.ts +0 -24
@@ -0,0 +1,32 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { DefaultCell } from "./DefaultCell";
4
+
5
+ function createCellContext(value: unknown, rendered: unknown) {
6
+ return {
7
+ getValue: () => value,
8
+ renderValue: () => rendered
9
+ } as any;
10
+ }
11
+
12
+ describe("DefaultCell", () => {
13
+ it("should render an empty span when value is undefined", () => {
14
+ render(<DefaultCell {...createCellContext(undefined, undefined)} />);
15
+
16
+ expect(screen.getByText("", { selector: "span" })).toBeInTheDocument();
17
+ expect(screen.queryByText(/.+/)).not.toBeInTheDocument();
18
+ });
19
+
20
+ it("should render html when rendered value differs from raw value", () => {
21
+ render(<DefaultCell {...createCellContext("hello", "<strong>hello</strong>")} />);
22
+
23
+ expect(screen.getByText("hello", { selector: "strong" })).toBeInTheDocument();
24
+ });
25
+
26
+ it("should render text when rendered value matches raw value", () => {
27
+ render(<DefaultCell {...createCellContext("hello", "hello")} />);
28
+
29
+ expect(screen.getByText("hello", { selector: "span" })).toBeInTheDocument();
30
+ expect(screen.queryByText("hello", { selector: "strong" })).not.toBeInTheDocument();
31
+ });
32
+ });
@@ -1,22 +1,21 @@
1
- import { registerComponent } from "../../../registries/components";
1
+ import type { CellContext } from "@tanstack/react-table";
2
2
 
3
- export interface DefaultCellProps<Data = any> {
4
- value: Data;
5
- render?: (value: Data) => any;
6
- }
3
+ import { registerComponent } from "../../../registries/components";
7
4
 
8
- export function DefaultCell<Data = any>({ value, render = (f: any) => f }: DefaultCellProps<Data>): JSX.Element {
5
+ export function DefaultCell<Data = any>({ getValue, renderValue }: CellContext<Data, any>): JSX.Element {
6
+ const value = getValue();
9
7
  if (value === undefined) {
10
8
  return <span></span>;
11
9
  }
12
10
 
13
- const rendered = render(value);
11
+ const rendered = renderValue();
14
12
 
15
- if (value !== rendered) {
16
- return <div dangerouslySetInnerHTML={{ __html: rendered }} />;
13
+ if (rendered != null && value !== rendered) {
14
+ return <div dangerouslySetInnerHTML={{ __html: String(rendered) }} />;
17
15
  }
18
16
 
19
17
  return <span>{String(value)}</span>;
20
18
  }
21
19
 
22
20
  registerComponent("Cell", DefaultCell);
21
+ registerComponent("Cell.string", DefaultCell);
@@ -0,0 +1,43 @@
1
+ import { render, screen } from "@testing-library/react";
2
+
3
+ import { DefaultDateCell } from "./DefaultDateCell";
4
+
5
+ function createCellContext(value: string | undefined, format?: string) {
6
+ return {
7
+ getValue: () => value,
8
+ column: {
9
+ columnDef: {
10
+ meta: {
11
+ format
12
+ }
13
+ }
14
+ }
15
+ } as any;
16
+ }
17
+
18
+ describe("DefaultDateCell", () => {
19
+ it("should render an empty span when value is undefined", () => {
20
+ render(<DefaultDateCell {...createCellContext(undefined)} />);
21
+
22
+ expect(screen.getByText("", { selector: "span" })).toBeInTheDocument();
23
+ expect(screen.queryByText(/.+/)).not.toBeInTheDocument();
24
+ });
25
+
26
+ it("should render the formatted date with the default format", () => {
27
+ render(<DefaultDateCell {...createCellContext("2026-03-12T10:30:00.000Z")} />);
28
+
29
+ expect(screen.getByText("03/12/2026", { selector: "span" })).toBeInTheDocument();
30
+ });
31
+
32
+ it("should render the formatted date with a custom format", () => {
33
+ render(<DefaultDateCell {...createCellContext("2026-03-12T10:30:00.000Z", "YYYY-MM-DD HH:mm")} />);
34
+
35
+ expect(screen.getByText("2026-03-12 10:30", { selector: "span" })).toBeInTheDocument();
36
+ });
37
+
38
+ it("should fallback to the raw value when the date is invalid", () => {
39
+ render(<DefaultDateCell {...createCellContext("not-a-date")} />);
40
+
41
+ expect(screen.getByText("not-a-date", { selector: "span" })).toBeInTheDocument();
42
+ });
43
+ });
@@ -0,0 +1,23 @@
1
+ import { CellContext } from "@tanstack/react-table";
2
+ import moment from "moment";
3
+
4
+ import { registerComponent } from "../../../registries/components";
5
+
6
+ export function DefaultDateCell<Data extends object>({ getValue, column: { columnDef } }: CellContext<Data, string>) {
7
+ const value = getValue();
8
+
9
+ if (!value) {
10
+ return <span />;
11
+ }
12
+
13
+ const date = moment.parseZone(value, moment.ISO_8601, true);
14
+
15
+ if (!date.isValid()) {
16
+ return <span>{String(value)}</span>;
17
+ }
18
+
19
+ return <span>{date.format(columnDef.meta?.format || "L")}</span>;
20
+ }
21
+
22
+ registerComponent("Cell.date", DefaultDateCell);
23
+ registerComponent("Cell.datetime", DefaultDateCell);
@@ -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, unknown>;
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>({ header, i18n }: DefaultFilterProps<Data>) {
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} />
@@ -8,6 +8,7 @@ export interface FilterBaseOptions extends Record<string, unknown> {
8
8
 
9
9
  export interface FilterTextOptions extends FilterBaseOptions {
10
10
  variant: "text";
11
+ disabled?: boolean;
11
12
  disableDatalist?: boolean;
12
13
  }
13
14
 
@@ -34,6 +34,7 @@ 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}`}
@@ -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,55 @@
1
+ import { DefaultCell } from "../components/DefaultCell";
2
+ import { DefaultDateCell } from "../components/DefaultDateCell";
3
+ import { mapFormToColumns } from "./mapFormToColumns";
4
+
5
+ describe("mapFormToColumns", () => {
6
+ it("should use DefaultCell for mapped table columns", () => {
7
+ const form = {
8
+ components: [
9
+ {
10
+ type: "checkbox",
11
+ key: "enabled",
12
+ label: "Enabled:",
13
+ tableView: true
14
+ }
15
+ ]
16
+ } as any;
17
+
18
+ const [column] = mapFormToColumns(form) as any[];
19
+
20
+ expect(column.accessorKey).toEqual("data.enabled");
21
+ expect(column.header).toEqual("Enabled");
22
+ expect(column.meta.filter.variant).toEqual("boolean");
23
+ expect(column.cell).toBe(DefaultCell);
24
+ });
25
+
26
+ it("should fallback to DefaultCell for kept columns without a cell renderer", () => {
27
+ const form = {
28
+ components: []
29
+ } as any;
30
+
31
+ const [column] = mapFormToColumns(form, [{ accessorKey: "data.other", header: "Other" } as any]) as any[];
32
+
33
+ expect(column.accessorKey).toEqual("data.other");
34
+ expect(column.cell).toBe(DefaultCell);
35
+ });
36
+
37
+ it("should use DefaultDateCell for mapped datetime columns", () => {
38
+ const form = {
39
+ components: [
40
+ {
41
+ type: "datetime",
42
+ key: "createdAt",
43
+ label: "Created at:",
44
+ tableView: true
45
+ }
46
+ ]
47
+ } as any;
48
+
49
+ const [column] = mapFormToColumns(form) as any[];
50
+
51
+ expect(column.accessorKey).toEqual("data.createdAt");
52
+ expect(column.meta.type).toEqual("date");
53
+ expect(column.cell).toBe(DefaultDateCell);
54
+ });
55
+ });
@@ -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";
@@ -10,18 +9,24 @@ import { getComponent } from "../../../registries/components";
10
9
  import type { DefaultCell } from "../components/DefaultCell";
11
10
  import type { FilterVariants } from "../filters/Filters.js";
12
11
 
13
- const MAP_TYPES: Record<string, FilterVariants> = {
12
+ const MAP_FILTER_TYPES: Record<string, FilterVariants> = {
14
13
  number: "range",
15
14
  currency: "range",
16
15
  checkbox: "boolean"
17
- };
16
+ } as const;
17
+
18
+ const MAP_TYPES = {
19
+ date: "date",
20
+ datetime: "date",
21
+ number: "number",
22
+ currency: "currency",
23
+ checkbox: "boolean"
24
+ } as const;
18
25
 
19
26
  export function mapFormToColumns<Data = any>(form: FormType, columns: ColumnDefResolved<Data, any>[] = []): ColumnDef<Data, any>[] {
20
27
  const columnHelper = createColumnHelper<Data>();
21
28
  const columnsToKeep = cloneDeep(columns);
22
29
 
23
- const Cell = getComponent<typeof DefaultCell>("Cell");
24
-
25
30
  const columnsFromComponents = form.components
26
31
  .flatMap((component) => {
27
32
  if (component.type === "tabs") {
@@ -33,7 +38,6 @@ export function mapFormToColumns<Data = any>(form: FormType, columns: ColumnDefR
33
38
  .filter((component) => component?.tableView)
34
39
  .map((c) => {
35
40
  const component = c as ComponentType;
36
- const cmp: any = Components.create(component, {}, null);
37
41
 
38
42
  const columnIndex = columnsToKeep.findIndex(({ accessorKey }) => {
39
43
  return accessorKey === `data.${component.key}`;
@@ -47,29 +51,30 @@ export function mapFormToColumns<Data = any>(form: FormType, columns: ColumnDefR
47
51
 
48
52
  return columnHelper.accessor(`data.${component.key}` as any, {
49
53
  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
54
  meta: {
54
- filter: { variant: MAP_TYPES[component.type!] || "text" },
55
+ type: MAP_TYPES[component.type as keyof typeof MAP_TYPES] || component.type,
56
+ filter: {
57
+ ...column?.meta?.filter,
58
+ variant: MAP_FILTER_TYPES[component.type!] || "text"
59
+ },
55
60
  ...(column?.meta || {})
56
61
  },
57
62
  ...(column || {})
58
63
  });
59
64
  });
60
65
 
61
- const mergedColumns = columnsFromComponents.concat(columnsToKeep as any[]).map((column, index) => ({
62
- ...column,
63
- meta: {
64
- ...(column.meta || {}),
65
- order: get(column, "meta.order", index * 10)
66
- },
67
- cell:
68
- column.cell ||
69
- ((info) => {
70
- return <Cell value={info.getValue() as Data} render={(value: Data) => value} />;
71
- })
72
- }));
66
+ const mergedColumns = columnsFromComponents.concat(columnsToKeep as any[]).map((column, index) => {
67
+ const Cell = getComponent<typeof DefaultCell>([`Cell.${column.id}`, `Cell.${column.meta?.type}`, "Cell"]);
68
+
69
+ return {
70
+ ...column,
71
+ meta: {
72
+ ...column?.meta,
73
+ order: get(column, "meta.order", index * 10)
74
+ },
75
+ cell: column.cell || Cell
76
+ };
77
+ });
73
78
 
74
79
  return mergedColumns.sort((a, b) => (a.meta.order > b.meta.order ? 1 : -1)) as ColumnDef<Data, any>[];
75
80
  }
@@ -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,23 +0,0 @@
1
- import { ColumnIdentifier } from './ColumnIdentifier';
2
- export type QueryOptions = {
3
- /**
4
- * Current displayed page
5
- */
6
- pageIndex: any;
7
- /**
8
- * Pagination size
9
- */
10
- pageSize: any;
11
- /**
12
- * SortBy state
13
- */
14
- sortBy: ColumnIdentifier[];
15
- /**
16
- * Filters state
17
- */
18
- filters: ColumnIdentifier[];
19
- /**
20
- * Focused input filter (let the Table component retrieve focus state when we fill the filter)
21
- */
22
- filterId: string;
23
- };
@@ -1,2 +0,0 @@
1
-
2
- //# sourceMappingURL=QueryOptions.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"QueryOptions.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,24 +0,0 @@
1
- import { ColumnIdentifier } from "./ColumnIdentifier";
2
-
3
- export type QueryOptions = {
4
- /**
5
- * Current displayed page
6
- */
7
- pageIndex: any;
8
- /**
9
- * Pagination size
10
- */
11
- pageSize: any;
12
- /**
13
- * SortBy state
14
- */
15
- sortBy: ColumnIdentifier[];
16
- /**
17
- * Filters state
18
- */
19
- filters: ColumnIdentifier[];
20
- /**
21
- * Focused input filter (let the Table component retrieve focus state when we fill the filter)
22
- */
23
- filterId: string;
24
- };