@firecms/core 3.0.0-alpha.21 → 3.0.0-alpha.25

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 (41) hide show
  1. package/dist/components/TextareaAutosize.d.ts +2 -0
  2. package/dist/components/index.d.ts +2 -0
  3. package/dist/core/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +8 -1
  4. package/dist/core/components/EntityCollectionTable/useEntityCollectionTableController.d.ts +1 -1
  5. package/dist/core/components/VirtualTable/VirtualTableHeader.d.ts +3 -0
  6. package/dist/core/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  7. package/dist/core/components/VirtualTable/VirtualTableProps.d.ts +14 -0
  8. package/dist/core/components/VirtualTable/types.d.ts +2 -0
  9. package/dist/core/form_field_configs.d.ts +1 -1
  10. package/dist/core/util/make_properties_editable.d.ts +2 -1
  11. package/dist/core/util/property_utils.d.ts +2 -1
  12. package/dist/index.es.js +7886 -7752
  13. package/dist/index.es.js.map +1 -1
  14. package/dist/index.umd.js +105 -105
  15. package/dist/index.umd.js.map +1 -1
  16. package/dist/types/collections.d.ts +1 -0
  17. package/dist/types/plugins.d.ts +22 -0
  18. package/package.json +137 -136
  19. package/src/components/BooleanSwitch.tsx +1 -1
  20. package/src/components/BooleanSwitchWithLabel.tsx +2 -2
  21. package/src/components/CircularProgress.tsx +1 -1
  22. package/src/components/TextField.tsx +5 -6
  23. package/src/components/TextareaAutosize.tsx +8 -4
  24. package/src/components/index.tsx +2 -0
  25. package/src/core/components/EntityCollectionTable/EntityCollectionTable.tsx +18 -7
  26. package/src/core/components/EntityCollectionTable/EntityCollectionTableProps.tsx +13 -1
  27. package/src/core/components/EntityCollectionTable/internal/PropertyTableCell.tsx +4 -2
  28. package/src/core/components/EntityCollectionTable/useEntityCollectionTableController.tsx +1 -1
  29. package/src/core/components/EntityCollectionView/EntityCollectionView.tsx +110 -4
  30. package/src/core/components/VirtualTable/VirtualTable.tsx +19 -7
  31. package/src/core/components/VirtualTable/VirtualTableHeader.tsx +32 -32
  32. package/src/core/components/VirtualTable/VirtualTableHeaderRow.tsx +7 -2
  33. package/src/core/components/VirtualTable/VirtualTableProps.tsx +14 -0
  34. package/src/core/components/VirtualTable/VirtualTableRow.tsx +1 -1
  35. package/src/core/components/VirtualTable/types.tsx +2 -0
  36. package/src/core/util/make_properties_editable.ts +18 -1
  37. package/src/core/util/property_utils.tsx +6 -1
  38. package/src/form/components/LabelWithIcon.tsx +2 -3
  39. package/src/form/field_bindings/TextFieldBinding.tsx +1 -2
  40. package/src/types/collections.ts +2 -0
  41. package/src/types/plugins.tsx +24 -1
@@ -395,6 +395,7 @@ export type TableController<M extends Record<string, any> = any> = {
395
395
  checkFilterCombination?: (filterValues: FilterValues<any>, sortBy?: [string, "asc" | "desc"]) => boolean;
396
396
  popupCell?: SelectedCellProps<M>;
397
397
  setPopupCell?: (popupCell?: SelectedCellProps<M>) => void;
398
+ onAddColumn?: (column: string) => void;
398
399
  };
399
400
  export type SelectedCellProps<M extends Record<string, any>> = {
400
401
  propertyKey: Extract<keyof M, string>;
@@ -84,6 +84,28 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any> = {
84
84
  children: React.ReactNode;
85
85
  };
86
86
  };
87
+ collectionView?: {
88
+ /**
89
+ * Use this method to inject widgets to the entity collections header
90
+ * @param props
91
+ */
92
+ HeaderAction?: React.ComponentType<{
93
+ property: ResolvedProperty;
94
+ propertyKey: string;
95
+ fullPath: string;
96
+ parentPathSegments: string[];
97
+ onHover: boolean;
98
+ }>;
99
+ /**
100
+ * If you add this callback to your plugin, an add button will be added to the collection table.
101
+ * TODO: Only the first plugin that defines this callback will be used, at the moment.
102
+ */
103
+ AddColumnComponent?: React.ComponentType<{
104
+ fullPath: string;
105
+ parentPathSegments: string[];
106
+ collection: EntityCollection;
107
+ }>;
108
+ };
87
109
  };
88
110
  /**
89
111
  * Props passed to the {@link FireCMSPlugin.homePage.CollectionActions} method.
package/package.json CHANGED
@@ -1,139 +1,140 @@
1
1
  {
2
- "name": "@firecms/core",
3
- "version": "3.0.0-alpha.21",
4
- "description": "Awesome Firebase/Firestore-based headless open-source CMS",
5
- "funding": {
6
- "url": "https://github.com/sponsors/firecmsco"
2
+ "name": "@firecms/core",
3
+ "version": "3.0.0-alpha.25",
4
+ "description": "Awesome Firebase/Firestore-based headless open-source CMS",
5
+ "funding": {
6
+ "url": "https://github.com/sponsors/firecmsco"
7
+ },
8
+ "author": "FireCMS",
9
+ "license": "MIT",
10
+ "repository": "git@github.com:FireCMSco/firecms.git",
11
+ "main": "./dist/index.umd.js",
12
+ "module": "./dist/index.es.js",
13
+ "types": "./dist/index.d.ts",
14
+ "source": "src/index.ts",
15
+ "engines": {
16
+ "node": ">=14"
17
+ },
18
+ "exports": {
19
+ ".": {
20
+ "import": "./dist/index.es.js",
21
+ "require": "./dist/index.umd.js",
22
+ "types": "./dist/index.d.ts"
7
23
  },
8
- "author": "FireCMS",
9
- "license": "MIT",
10
- "repository": "git@github.com:FireCMSco/firecms.git",
11
- "main": "./dist/index.umd.js",
12
- "module": "./dist/index.es.js",
13
- "types": "./dist/index.d.ts",
14
- "source": "src/index.ts",
15
- "engines": {
16
- "node": ">=14"
17
- },
18
- "exports": {
19
- ".": {
20
- "import": "./dist/index.es.js",
21
- "require": "./dist/index.umd.js",
22
- "types": "./dist/index.d.ts"
23
- },
24
- "./package.json": "./package.json"
25
- },
26
- "keywords": [
27
- "firebase",
28
- "cms",
29
- "admin",
30
- "admin panel",
31
- "firebase panel",
32
- "firestore",
33
- "headless",
34
- "headless cms",
35
- "content manager"
36
- ],
37
- "scripts": {
38
- "watch": "vite build --watch",
39
- "build": "vite build && tsc --emitDeclarationOnly",
40
- "prepublishOnly": "run-s build",
41
- "createTag": "PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]') && git tag v$PACKAGE_VERSION && git push --tags",
42
- "test:lint": "eslint \"src/**\" --quiet",
43
- "test": "jest",
44
- "generateIcons": "ts-node --esm src/icons/generateIcons.ts"
45
- },
46
- "dependencies": {
47
- "@date-io/date-fns": "^2.17.0",
48
- "@fontsource/ibm-plex-mono": "^5.0.8",
49
- "@fontsource/roboto": "^5.0.8",
50
- "@hello-pangea/dnd": "^16.3.0",
51
- "@material-design-icons/font": "^0.14.13",
52
- "@radix-ui/react-checkbox": "^1.0.4",
53
- "@radix-ui/react-collapsible": "^1.0.3",
54
- "@radix-ui/react-dialog": "^1.0.5",
55
- "@radix-ui/react-dropdown-menu": "^2.0.6",
56
- "@radix-ui/react-popover": "^1.0.7",
57
- "@radix-ui/react-portal": "^1.0.4",
58
- "@radix-ui/react-scroll-area": "^1.0.5",
59
- "@radix-ui/react-select": "^1.2.2",
60
- "@radix-ui/react-switch": "^1.0.3",
61
- "@radix-ui/react-tabs": "^1.0.4",
62
- "@radix-ui/react-tooltip": "^1.0.7",
63
- "cmdk": "^0.2.0",
64
- "date-fns": "^2.30.0",
65
- "formik": "^2.4.5",
66
- "history": "^5.3.0",
67
- "js-search": "^2.0.1",
68
- "markdown-it": "^13.0.2",
69
- "notistack": "^3.0.1",
70
- "object-hash": "^3.0.0",
71
- "react-datepicker": "^4.21.0",
72
- "react-dropzone": "^14.2.3",
73
- "react-fast-compare": "^3.2.2",
74
- "react-image-file-resizer": "^0.4.8",
75
- "react-markdown-editor-lite": "^1.3.4",
76
- "react-transition-group": "^4.4.5",
77
- "react-use-measure": "^2.1.1",
78
- "react-window": "^1.8.9",
79
- "tailwind-merge": "^1.14.0",
80
- "typeface-rubik": "^1.1.13",
81
- "yup": "^0.32.11"
82
- },
83
- "peerDependencies": {
84
- "algoliasearch": "^4.13.0",
85
- "firebase": "^10.4.0",
86
- "react": "^18.2.0",
87
- "react-dom": "^18.2.0",
88
- "react-router": "^6.2.0",
89
- "react-router-dom": "^6.2.0"
90
- },
91
- "eslintConfig": {
92
- "extends": [
93
- "react-app",
94
- "react-app/jest"
95
- ]
96
- },
97
- "devDependencies": {
98
- "@jest/globals": "^29.7.0",
99
- "@testing-library/jest-dom": "^5.17.0",
100
- "@testing-library/react": "^14.0.0",
101
- "@testing-library/user-event": "^14.5.1",
102
- "@types/jest": "^29.5.6",
103
- "@types/node": "^18.18.7",
104
- "@types/object-hash": "^3.0.5",
105
- "@types/react": "^18.2.33",
106
- "@types/react-dom": "^18.2.14",
107
- "@types/react-measure": "^2.0.10",
108
- "@typescript-eslint/eslint-plugin": "^5.62.0",
109
- "@typescript-eslint/parser": "^5.62.0",
110
- "@vitejs/plugin-react": "^4.1.0",
111
- "algoliasearch": "^4.20.0",
112
- "cross-env": "^7.0.3",
113
- "eslint": "^8.52.0",
114
- "eslint-config-standard": "^17.1.0",
115
- "eslint-plugin-import": "^2.29.0",
116
- "eslint-plugin-n": "^15.7.0",
117
- "eslint-plugin-promise": "^6.1.1",
118
- "eslint-plugin-react": "^7.33.2",
119
- "eslint-plugin-react-hooks": "^4.6.0",
120
- "firebase": "^10.5.2",
121
- "jest": "^29.7.0",
122
- "npm-run-all": "^4.1.5",
123
- "react-router": "^6.17.0",
124
- "react-router-dom": "^6.17.0",
125
- "ts-jest": "^29.1.1",
126
- "ts-node": "^10.9.1",
127
- "tsd": "^0.28.1",
128
- "typescript": "^5.2.2",
129
- "vite": "^4.5.0"
130
- },
131
- "files": [
132
- "dist",
133
- "src"
134
- ],
135
- "gitHead": "4a18047fa8fd4ddaa157ec2d415fcab89cf34d65",
136
- "publishConfig": {
137
- "access": "public"
138
- }
24
+ "./package.json": "./package.json"
25
+ },
26
+ "keywords": [
27
+ "firebase",
28
+ "cms",
29
+ "admin",
30
+ "admin panel",
31
+ "firebase panel",
32
+ "firestore",
33
+ "headless",
34
+ "headless cms",
35
+ "content manager"
36
+ ],
37
+ "scripts": {
38
+ "watch": "vite build --watch",
39
+ "build": "vite build && tsc --emitDeclarationOnly",
40
+ "prepublishOnly": "run-s build",
41
+ "createTag": "PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]') && git tag v$PACKAGE_VERSION && git push --tags",
42
+ "test:lint": "eslint \"src/**\" --quiet",
43
+ "test": "jest",
44
+ "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f",
45
+ "generateIcons": "ts-node --esm src/icons/generateIcons.ts"
46
+ },
47
+ "dependencies": {
48
+ "@date-io/date-fns": "^2.17.0",
49
+ "@fontsource/ibm-plex-mono": "^5.0.8",
50
+ "@fontsource/roboto": "^5.0.8",
51
+ "@hello-pangea/dnd": "^16.3.0",
52
+ "@material-design-icons/font": "^0.14.13",
53
+ "@radix-ui/react-checkbox": "^1.0.4",
54
+ "@radix-ui/react-collapsible": "^1.0.3",
55
+ "@radix-ui/react-dialog": "^1.0.5",
56
+ "@radix-ui/react-dropdown-menu": "^2.0.6",
57
+ "@radix-ui/react-popover": "^1.0.7",
58
+ "@radix-ui/react-portal": "^1.0.4",
59
+ "@radix-ui/react-scroll-area": "^1.0.5",
60
+ "@radix-ui/react-select": "^1.2.2",
61
+ "@radix-ui/react-switch": "^1.0.3",
62
+ "@radix-ui/react-tabs": "^1.0.4",
63
+ "@radix-ui/react-tooltip": "^1.0.7",
64
+ "cmdk": "^0.2.0",
65
+ "date-fns": "^2.30.0",
66
+ "formik": "^2.4.5",
67
+ "history": "^5.3.0",
68
+ "js-search": "^2.0.1",
69
+ "markdown-it": "^13.0.2",
70
+ "notistack": "^3.0.1",
71
+ "object-hash": "^3.0.0",
72
+ "react-datepicker": "^4.21.0",
73
+ "react-dropzone": "^14.2.3",
74
+ "react-fast-compare": "^3.2.2",
75
+ "react-image-file-resizer": "^0.4.8",
76
+ "react-markdown-editor-lite": "^1.3.4",
77
+ "react-transition-group": "^4.4.5",
78
+ "react-use-measure": "^2.1.1",
79
+ "react-window": "^1.8.9",
80
+ "tailwind-merge": "^1.14.0",
81
+ "typeface-rubik": "^1.1.13",
82
+ "yup": "^0.32.11"
83
+ },
84
+ "peerDependencies": {
85
+ "algoliasearch": "^4.13.0",
86
+ "firebase": "^10.4.0",
87
+ "react": "^18.2.0",
88
+ "react-dom": "^18.2.0",
89
+ "react-router": "^6.2.0",
90
+ "react-router-dom": "^6.2.0"
91
+ },
92
+ "eslintConfig": {
93
+ "extends": [
94
+ "react-app",
95
+ "react-app/jest"
96
+ ]
97
+ },
98
+ "devDependencies": {
99
+ "@jest/globals": "^29.7.0",
100
+ "@testing-library/jest-dom": "^5.17.0",
101
+ "@testing-library/react": "^14.0.0",
102
+ "@testing-library/user-event": "^14.5.1",
103
+ "@types/jest": "^29.5.6",
104
+ "@types/node": "^18.18.7",
105
+ "@types/object-hash": "^3.0.5",
106
+ "@types/react": "^18.2.33",
107
+ "@types/react-dom": "^18.2.14",
108
+ "@types/react-measure": "^2.0.10",
109
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
110
+ "@typescript-eslint/parser": "^5.62.0",
111
+ "@vitejs/plugin-react": "^4.1.0",
112
+ "algoliasearch": "^4.20.0",
113
+ "cross-env": "^7.0.3",
114
+ "eslint": "^8.52.0",
115
+ "eslint-config-standard": "^17.1.0",
116
+ "eslint-plugin-import": "^2.29.0",
117
+ "eslint-plugin-n": "^15.7.0",
118
+ "eslint-plugin-promise": "^6.1.1",
119
+ "eslint-plugin-react": "^7.33.2",
120
+ "eslint-plugin-react-hooks": "^4.6.0",
121
+ "firebase": "^10.5.2",
122
+ "jest": "^29.7.0",
123
+ "npm-run-all": "^4.1.5",
124
+ "react-router": "^6.17.0",
125
+ "react-router-dom": "^6.17.0",
126
+ "ts-jest": "^29.1.1",
127
+ "ts-node": "^10.9.1",
128
+ "tsd": "^0.28.1",
129
+ "typescript": "^5.2.2",
130
+ "vite": "^4.5.0"
131
+ },
132
+ "files": [
133
+ "dist",
134
+ "src"
135
+ ],
136
+ "gitHead": "8bce80c65696930df7a8003b0b5d0310f595d46d",
137
+ "publishConfig": {
138
+ "access": "public"
139
+ }
139
140
  }
@@ -27,7 +27,7 @@ export const BooleanSwitch = React.forwardRef(function BooleanSwitch({
27
27
  ref={ref}
28
28
  tabIndex={disabled ? -1 : undefined}
29
29
  onClick={disabled
30
- ? undefined
30
+ ? (e) => e.preventDefault()
31
31
  : (e) => {
32
32
  e.preventDefault();
33
33
  if (allowIndeterminate) {
@@ -54,12 +54,12 @@ export const BooleanSwitchWithLabel = function BooleanSwitchWithLabel({
54
54
  onBlur={onBlur}
55
55
  tabIndex={-1}
56
56
  className={cn(
57
- !invisible && fieldBackgroundMixin,
57
+ !invisible && fieldBackgroundMixin,
58
58
  !invisible && (disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin),
59
59
  disabled ? "cursor-default" : "cursor-pointer",
60
60
  "rounded-md relative max-w-full justify-between w-full box-border relative inline-flex items-center",
61
61
  !invisible && focus && !disabled ? focusedClasses : "",
62
- error ? "text-red-500 dark:text-red-600" : (focus ? "text-primary" : (!disabled ? "text-text-primary dark:text-text-primary-dark" : "text-text-secondary dark:text-text-secondary-dark")),
62
+ error ? "text-red-500 dark:text-red-600" : (focus && !disabled ? "text-primary" : (!disabled ? "text-text-primary dark:text-text-primary-dark" : "text-text-secondary dark:text-text-secondary-dark")),
63
63
  size === "small" ? "min-h-[40px]" : "min-h-[64px]",
64
64
  size === "small" ? "pl-2" : "pl-4",
65
65
  size === "small" ? "pr-4" : "pr-6",
@@ -13,7 +13,7 @@ export function CircularProgress({
13
13
 
14
14
  let sizeClasses = "";
15
15
  if (size === "small") {
16
- sizeClasses = "w-4 h-4 m-1";
16
+ sizeClasses = "w-4 h-4";
17
17
  } else if (size === "medium") {
18
18
  sizeClasses = "w-8 h-8 m-1";
19
19
  } else {
@@ -89,8 +89,7 @@ export function TextField<T extends string | number>({
89
89
  ? <TextareaAutosize
90
90
  {...inputProps as any}
91
91
  ref={inputRef}
92
-
93
- placeholder={placeholder}
92
+ placeholder={focused || hasValue || !label ? placeholder : undefined}
94
93
  autoFocus={autoFocus}
95
94
  rows={rows}
96
95
  // onFocus={() => setFocused(true)}
@@ -119,10 +118,10 @@ export function TextField<T extends string | number>({
119
118
  label ? (size === "medium" ? "pt-[28px] pb-2" : "pt-4 pb-2") : "py-2",
120
119
  focused ? "text-text-primary dark:text-text-primary-dark" : "",
121
120
  endAdornment ? "pr-10" : "pr-3",
122
- inputClassName,
123
- disabled && "border border-transparent outline-none opacity-50 dark:opacity-50 text-gray-800 dark:text-gray-200"
121
+ disabled && "border border-transparent outline-none opacity-50 dark:opacity-50 text-gray-800 dark:text-gray-200",
122
+ inputClassName
124
123
  )}
125
- placeholder={placeholder}
124
+ placeholder={focused || hasValue || !label ? placeholder : undefined}
126
125
  autoFocus={autoFocus}
127
126
  onFocus={() => setFocused(true)}
128
127
  onBlur={() => setFocused(false)}
@@ -150,7 +149,7 @@ export function TextField<T extends string | number>({
150
149
  className={cn(
151
150
  "pointer-events-none absolute",
152
151
  size === "medium" ? "top-1" : "-top-1",
153
- !error ? (focused ? "text-primary" : "text-text-secondary dark:text-text-secondary-dark") : "text-red-500 dark:text-red-600",
152
+ !error ? (focused ? "text-primary dark:text-primary" : "text-text-secondary dark:text-text-secondary-dark") : "text-red-500 dark:text-red-600",
154
153
  disabled ? "opacity-50" : "")}
155
154
  shrink={hasValue || focused}
156
155
  >
@@ -2,6 +2,7 @@ import * as React from "react";
2
2
  import { useLayoutEffect } from "react";
3
3
  import * as ReactDOM from "react-dom";
4
4
  import { debounce } from "../core/util/debounce";
5
+ import { cn } from "./util/cn";
5
6
 
6
7
  type State = {
7
8
  outerHeightStyle: number;
@@ -102,6 +103,7 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
102
103
  getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop);
103
104
  const border =
104
105
  getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth);
106
+ const minHeight = getStyleValue(computedStyle.minHeight);
105
107
 
106
108
  // The height of the inner content
107
109
  const innerHeight = sizeReferenceElement.scrollHeight;
@@ -119,10 +121,11 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
119
121
  if (maxRows) {
120
122
  outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
121
123
  }
122
- outerHeight = Math.max(outerHeight, singleRowHeight);
124
+ outerHeight = Math.max(outerHeight, singleRowHeight, minHeight);
123
125
 
124
126
  // Take the box sizing into account for applying this value as a style.
125
- const outerHeightStyle = outerHeight + (boxSizing === "border-box" ? padding + border : 0);
127
+ const outerHeightStyle = outerHeight + (!props.ignoreBoxSizing && boxSizing === "border-box" ? padding + border : 0);
128
+
126
129
  const overflow = Math.abs(outerHeight - innerHeight) <= 1;
127
130
 
128
131
  return {
@@ -274,7 +277,7 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
274
277
  />
275
278
  <textarea
276
279
  aria-hidden
277
- className={props.shadowClassName}
280
+ className={cn(props.className, props.shadowClassName)}
278
281
  readOnly
279
282
  ref={shadowRef}
280
283
  tabIndex={-1}
@@ -327,8 +330,9 @@ export type TextareaAutosizeProps = Omit<React.InputHTMLAttributes<HTMLTextAreaE
327
330
  onResize?: (state: State) => void;
328
331
 
329
332
  autoFocus?: boolean;
330
- }
331
333
 
334
+ ignoreBoxSizing?: boolean;
335
+ }
332
336
 
333
337
  function useForkRef<Instance>(
334
338
  ...refs: Array<React.Ref<Instance> | undefined>
@@ -36,3 +36,5 @@ export * from "./Tooltip";
36
36
  export * from "./Typography";
37
37
  export * from "./Tabs";
38
38
  export * from "./Table";
39
+ export * from "./Popover";
40
+ export * from "./Badge";
@@ -5,6 +5,7 @@ import {
5
5
  AdditionalFieldDelegate,
6
6
  CollectionSize,
7
7
  Entity,
8
+ EntityCollection,
8
9
  FilterValues,
9
10
  FireCMSContext,
10
11
  ResolvedProperty,
@@ -106,7 +107,10 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
106
107
  },
107
108
  filterable = true,
108
109
  sortable = true,
109
- endAdornment
110
+ endAdornment,
111
+ AddColumnComponent,
112
+ AdditionalHeaderWidget,
113
+ additionalIDHeaderWidget
110
114
  }: EntityCollectionTableProps<M>) {
111
115
 
112
116
  const largeLayout = useLargeLayout();
@@ -268,7 +272,7 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
268
272
 
269
273
  }, [additionalFieldsMap, size, selectedEntityIds]);
270
274
 
271
- const allColumns: VirtualTableColumn[] = useMemo(() => {
275
+ const collectionColumns: VirtualTableColumn[] = useMemo(() => {
272
276
  const columnsResult: VirtualTableColumn[] = Object.entries<ResolvedProperty>(properties)
273
277
  .flatMap(([key, property]) => getColumnKeysForProperty(property, key))
274
278
  .map(({
@@ -291,7 +295,10 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
291
295
  custom: {
292
296
  resolvedProperty: property,
293
297
  disabled
294
- }
298
+ },
299
+ AdditionalHeaderWidget: ({ onHover }) => AdditionalHeaderWidget
300
+ ? <AdditionalHeaderWidget property={property} propertyKey={key} onHover={onHover}/>
301
+ : undefined
295
302
  };
296
303
  });
297
304
 
@@ -315,16 +322,18 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
315
322
  title: "ID",
316
323
  resizable: false,
317
324
  frozen: largeLayout,
318
- headerAlign: "center"
325
+ headerAlign: "center",
326
+ align: "center",
327
+ AdditionalHeaderWidget: () => additionalIDHeaderWidget
319
328
  }), [largeLayout])
320
329
 
321
330
  const columns: VirtualTableColumn[] = useMemo(() => [
322
331
  idColumn,
323
332
  ...displayedColumnIds
324
333
  .map((p) => {
325
- return allColumns.find(c => c.key === p.key);
334
+ return collectionColumns.find(c => c.key === p.key);
326
335
  }).filter(Boolean) as VirtualTableColumn[]
327
- ], [allColumns, displayedColumnIds, idColumn]);
336
+ ], [collectionColumns, displayedColumnIds, idColumn]);
328
337
 
329
338
  const cellRenderer = useCallback((props: CellRendererParams<any>) => {
330
339
  const column = props.column;
@@ -366,7 +375,7 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
366
375
  onValueChange,
367
376
  size,
368
377
  selectedCell,
369
- selectedEntityIds,
378
+ selectedEntityIds
370
379
  }}
371
380
  >
372
381
 
@@ -408,6 +417,7 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
408
417
  }, [selectedEntityIds])}
409
418
  className="flex-grow"
410
419
  endAdornment={endAdornment}
420
+ AddColumnComponent={AddColumnComponent}
411
421
  />
412
422
 
413
423
  </div>
@@ -495,3 +505,4 @@ function filterableProperty(property: ResolvedProperty, partOfArray = false): bo
495
505
  }
496
506
  return ["string", "number", "boolean", "date", "reference", "array"].includes(property.dataType);
497
507
  }
508
+
@@ -5,7 +5,9 @@ import {
5
5
  Entity,
6
6
  FilterValues,
7
7
  ResolvedProperties,
8
- SelectionController, TableController,
8
+ ResolvedProperty,
9
+ SelectionController,
10
+ TableController,
9
11
  User
10
12
  } from "../../../types";
11
13
  import { OnCellValueChange, UniqueFieldValidator } from "./types";
@@ -117,6 +119,16 @@ export type EntityCollectionTableProps<M extends Record<string, any>,
117
119
 
118
120
  endAdornment?: React.ReactNode;
119
121
 
122
+ AdditionalHeaderWidget?: (props: {
123
+ property: ResolvedProperty,
124
+ propertyKey: string,
125
+ onHover: boolean,
126
+ }) => React.ReactNode;
127
+
128
+ AddColumnComponent?: React.ComponentType;
129
+
130
+ additionalIDHeaderWidget?: React.ReactNode;
131
+
120
132
  }
121
133
 
122
134
  export type GetPropertyForProps<M extends Record<string, any>> = {
@@ -128,11 +128,14 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any, any>>(
128
128
  );
129
129
 
130
130
  const saveValues = (value: any) => {
131
+ if (equal(value, internalValueRef.current))
132
+ return;
131
133
  setSaved(false);
132
134
  validation
133
135
  .validate(value)
134
136
  .then(() => {
135
137
  setError(undefined);
138
+ internalValueRef.current = value;
136
139
  if (onValueChange) {
137
140
  onValueChange({
138
141
  value,
@@ -167,7 +170,6 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any, any>>(
167
170
  } else {
168
171
  updatedValue = newValue;
169
172
  }
170
- internalValueRef.current = updatedValue;
171
173
  setInternalValue(updatedValue);
172
174
  saveValues(updatedValue);
173
175
  };
@@ -234,7 +236,7 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any, any>>(
234
236
  propertyKey={propertyKey}
235
237
  property={property}
236
238
  entity={entity}
237
- value={value}
239
+ value={internalValue}
238
240
  size={getPreviewSizeFrom(size)}
239
241
  />
240
242
  </EntityTableCell>;
@@ -33,7 +33,7 @@ export function useEntityCollectionTableController<M extends Record<string, any>
33
33
  collection,
34
34
  entitiesDisplayedFirst,
35
35
  lastDeleteTimestamp,
36
- forceFilter: forceFilterFromProps
36
+ forceFilter: forceFilterFromProps,
37
37
  }: EntityCollectionTableControllerProps<M>)
38
38
  : TableController<M> {
39
39