@mui/x-data-grid 7.0.0 → 7.1.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/CHANGELOG.md +71 -0
- package/DataGrid/DataGrid.js +5 -0
- package/DataGrid/useDataGridProps.js +1 -0
- package/components/columnHeaders/GridColumnHeaderItem.js +3 -1
- package/components/columnHeaders/GridGenericColumnHeaderItem.js +3 -1
- package/components/toolbar/GridToolbarQuickFilter.js +17 -2
- package/hooks/features/dimensions/useGridDimensions.d.ts +1 -1
- package/hooks/features/dimensions/useGridDimensions.js +3 -2
- package/hooks/features/editing/useGridRowEditing.js +13 -4
- package/index.js +1 -1
- package/models/props/DataGridProps.d.ts +5 -0
- package/modern/DataGrid/DataGrid.js +5 -0
- package/modern/DataGrid/useDataGridProps.js +1 -0
- package/modern/components/columnHeaders/GridColumnHeaderItem.js +3 -1
- package/modern/components/columnHeaders/GridGenericColumnHeaderItem.js +3 -1
- package/modern/components/toolbar/GridToolbarQuickFilter.js +17 -2
- package/modern/hooks/features/dimensions/useGridDimensions.js +3 -2
- package/modern/hooks/features/editing/useGridRowEditing.js +13 -4
- package/modern/index.js +1 -1
- package/modern/utils/throttle.js +19 -0
- package/node/DataGrid/DataGrid.js +5 -0
- package/node/DataGrid/useDataGridProps.js +1 -0
- package/node/components/columnHeaders/GridColumnHeaderItem.js +3 -1
- package/node/components/columnHeaders/GridGenericColumnHeaderItem.js +3 -1
- package/node/components/toolbar/GridToolbarQuickFilter.js +17 -2
- package/node/hooks/features/dimensions/useGridDimensions.js +2 -1
- package/node/hooks/features/editing/useGridRowEditing.js +13 -4
- package/node/index.js +1 -1
- package/node/utils/throttle.js +25 -0
- package/package.json +1 -1
- package/utils/throttle.d.ts +4 -0
- package/utils/throttle.js +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,77 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## 7.1.0
|
|
7
|
+
|
|
8
|
+
_Mar 28, 2024_
|
|
9
|
+
|
|
10
|
+
We'd like to offer a big thanks to the 10 contributors who made this release possible. Here are some highlights ✨:
|
|
11
|
+
|
|
12
|
+
- 🚀 Add `resizeThrottleMs` prop (#12556) @romgrk
|
|
13
|
+
- 🌍 Improve Chinese (Hong Kong) (zh-HK) and Italian (it-IT) locale on the Pickers
|
|
14
|
+
- 🐞 Bugfixes
|
|
15
|
+
- 📚 Documentation improvements
|
|
16
|
+
|
|
17
|
+
### Data Grid
|
|
18
|
+
|
|
19
|
+
#### `@mui/x-data-grid@7.1.0`
|
|
20
|
+
|
|
21
|
+
- [DataGrid] Add `resizeThrottleMs` prop (#12556) @romgrk
|
|
22
|
+
- [DataGrid] Do not publish `rowEditStop` event if row has fields with errors (#11383) @cherniavskii
|
|
23
|
+
- [DataGrid] Fix bug in suspense (#12553) @romgrk
|
|
24
|
+
- [DataGrid] Fix missing class name in the `GridToolbarQuickFilter` component (#12484) @jhawkins11
|
|
25
|
+
|
|
26
|
+
#### `@mui/x-data-grid-pro@7.1.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
27
|
+
|
|
28
|
+
Same changes as in `@mui/x-data-grid@7.1.0`.
|
|
29
|
+
|
|
30
|
+
#### `@mui/x-data-grid-premium@7.1.0` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
31
|
+
|
|
32
|
+
Same changes as in `@mui/x-data-grid-pro@7.1.0`.
|
|
33
|
+
|
|
34
|
+
### Date and Time Pickers
|
|
35
|
+
|
|
36
|
+
#### `@mui/x-date-pickers@7.1.0`
|
|
37
|
+
|
|
38
|
+
- [fields] Fix placeholder override (#12589) @flaviendelangle
|
|
39
|
+
- [l10n] Improve Chinese (Hong Kong) (zh-HK) locale (#12547) @samchiu90
|
|
40
|
+
- [l10n] Improve Italian (it-IT) locale (#12549) @antomanc
|
|
41
|
+
- [pickers] Prepare compatibility with `@mui/zero-runtime` (stop using `ownerState` in `styled`) (#12003) @flaviendelangle
|
|
42
|
+
|
|
43
|
+
#### `@mui/x-date-pickers-pro@7.1.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
44
|
+
|
|
45
|
+
Same changes as in `@mui/x-date-pickers@7.1.0`, plus:
|
|
46
|
+
|
|
47
|
+
- [DateRangePicker] Fix selection behavior with single input field when `readOnly` (#12593) @LukasTy
|
|
48
|
+
|
|
49
|
+
### Charts
|
|
50
|
+
|
|
51
|
+
#### `@mui/x-charts@7.1.0`
|
|
52
|
+
|
|
53
|
+
- [charts] Fix tooltip causing crash on data change (#12571) @Rishi556
|
|
54
|
+
|
|
55
|
+
### Tree View
|
|
56
|
+
|
|
57
|
+
#### `@mui/x-tree-view@7.1.0`
|
|
58
|
+
|
|
59
|
+
- [TreeView] Do not use outdated version of the state to compute new label first char in `RichTreeView` (#12512) @flaviendelangle
|
|
60
|
+
|
|
61
|
+
### Docs
|
|
62
|
+
|
|
63
|
+
- [docs] Add example to add a second icon next to the field's opening button (#12524) @flaviendelangle
|
|
64
|
+
- [docs] Add missing note to Data Grid migration guide (#12557) @romgrk
|
|
65
|
+
- [docs] Fix Charts title for SEO (#12545) @oliviertassinari
|
|
66
|
+
- [docs] Fix small typo (#12558) @diogoparente
|
|
67
|
+
- [docs] Improve codemod related documentation (#12582) @MBilalShafi
|
|
68
|
+
- [docs] Reduce noise in migration docs side navigation (#12552) @cherniavskii
|
|
69
|
+
- [docs] Sync static images from core repository (#12525) @LukasTy
|
|
70
|
+
|
|
71
|
+
### Core
|
|
72
|
+
|
|
73
|
+
- [core] Fix `l10n` script on Windows (#12550) @LukasTy
|
|
74
|
+
- [core] Include `DateTimeRangePicker` tag in `releaseChangelog` (#12526) @LukasTy
|
|
75
|
+
- [core] Upgrade monorepo (#12536) @cherniavskii
|
|
76
|
+
|
|
6
77
|
## v7.0.0
|
|
7
78
|
|
|
8
79
|
_Mar 22, 2024_
|
package/DataGrid/DataGrid.js
CHANGED
|
@@ -608,6 +608,11 @@ DataGridRaw.propTypes = {
|
|
|
608
608
|
* @returns {Promise<R> | R} The final values to update the row.
|
|
609
609
|
*/
|
|
610
610
|
processRowUpdate: PropTypes.func,
|
|
611
|
+
/**
|
|
612
|
+
* The milliseconds throttle delay for resizing the grid.
|
|
613
|
+
* @default 60
|
|
614
|
+
*/
|
|
615
|
+
resizeThrottleMs: PropTypes.number,
|
|
611
616
|
/**
|
|
612
617
|
* Row region in pixels to render before/after the viewport
|
|
613
618
|
* @default 150
|
|
@@ -153,7 +153,9 @@ function GridColumnHeaderItem(props) {
|
|
|
153
153
|
const focusableElement = headerCellRef.current.querySelector('[tabindex="0"]');
|
|
154
154
|
const elementToFocus = focusableElement || headerCellRef.current;
|
|
155
155
|
elementToFocus?.focus();
|
|
156
|
-
apiRef.current.columnHeadersContainerRef
|
|
156
|
+
if (apiRef.current.columnHeadersContainerRef?.current) {
|
|
157
|
+
apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0;
|
|
158
|
+
}
|
|
157
159
|
}
|
|
158
160
|
}, [apiRef, hasFocus]);
|
|
159
161
|
const headerClassName = typeof colDef.headerClassName === 'function' ? colDef.headerClassName({
|
|
@@ -56,7 +56,9 @@ const GridGenericColumnHeaderItem = /*#__PURE__*/React.forwardRef(function GridG
|
|
|
56
56
|
const focusableElement = headerCellRef.current.querySelector('[tabindex="0"]');
|
|
57
57
|
const elementToFocus = focusableElement || headerCellRef.current;
|
|
58
58
|
elementToFocus?.focus();
|
|
59
|
-
apiRef.current.columnHeadersContainerRef
|
|
59
|
+
if (apiRef.current.columnHeadersContainerRef?.current) {
|
|
60
|
+
apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0;
|
|
61
|
+
}
|
|
60
62
|
}
|
|
61
63
|
}, [apiRef, hasFocus]);
|
|
62
64
|
return /*#__PURE__*/_jsxs("div", _extends({
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
|
3
|
-
const _excluded = ["quickFilterParser", "quickFilterFormatter", "debounceMs"];
|
|
3
|
+
const _excluded = ["quickFilterParser", "quickFilterFormatter", "debounceMs", "className"];
|
|
4
4
|
import * as React from 'react';
|
|
5
|
+
import clsx from 'clsx';
|
|
5
6
|
import PropTypes from 'prop-types';
|
|
6
7
|
import TextField from '@mui/material/TextField';
|
|
7
8
|
import { styled } from '@mui/material/styles';
|
|
8
9
|
import { unstable_debounce as debounce } from '@mui/utils';
|
|
10
|
+
import composeClasses from '@mui/utils/composeClasses';
|
|
11
|
+
import { getDataGridUtilityClass } from '../../constants';
|
|
9
12
|
import { useGridApiContext } from '../../hooks/utils/useGridApiContext';
|
|
10
13
|
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
|
|
11
14
|
import { useGridSelector } from '../../hooks/utils/useGridSelector';
|
|
12
15
|
import { gridQuickFilterValuesSelector } from '../../hooks/features/filter';
|
|
13
16
|
import { isDeepEqual } from '../../utils/utils';
|
|
14
17
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
+
const useUtilityClasses = ownerState => {
|
|
19
|
+
const {
|
|
20
|
+
classes
|
|
21
|
+
} = ownerState;
|
|
22
|
+
const slots = {
|
|
23
|
+
root: ['toolbarQuickFilter']
|
|
24
|
+
};
|
|
25
|
+
return composeClasses(slots, getDataGridUtilityClass, classes);
|
|
26
|
+
};
|
|
15
27
|
const GridToolbarQuickFilterRoot = styled(TextField, {
|
|
16
28
|
name: 'MuiDataGrid',
|
|
17
29
|
slot: 'ToolbarQuickFilter',
|
|
@@ -47,11 +59,13 @@ const defaultSearchValueFormatter = values => values.join(' ');
|
|
|
47
59
|
function GridToolbarQuickFilter(props) {
|
|
48
60
|
const apiRef = useGridApiContext();
|
|
49
61
|
const rootProps = useGridRootProps();
|
|
62
|
+
const classes = useUtilityClasses(rootProps);
|
|
50
63
|
const quickFilterValues = useGridSelector(apiRef, gridQuickFilterValuesSelector);
|
|
51
64
|
const {
|
|
52
65
|
quickFilterParser = defaultSearchValueParser,
|
|
53
66
|
quickFilterFormatter = defaultSearchValueFormatter,
|
|
54
|
-
debounceMs = rootProps.filterDebounceMs
|
|
67
|
+
debounceMs = rootProps.filterDebounceMs,
|
|
68
|
+
className
|
|
55
69
|
} = props,
|
|
56
70
|
other = _objectWithoutPropertiesLoose(props, _excluded);
|
|
57
71
|
const [searchValue, setSearchValue] = React.useState(() => quickFilterFormatter(quickFilterValues ?? []));
|
|
@@ -86,6 +100,7 @@ function GridToolbarQuickFilter(props) {
|
|
|
86
100
|
variant: "standard",
|
|
87
101
|
value: searchValue,
|
|
88
102
|
onChange: handleSearchValueChange,
|
|
103
|
+
className: clsx(className, classes.root),
|
|
89
104
|
placeholder: apiRef.current.getLocaleText('toolbarQuickFilterPlaceholder'),
|
|
90
105
|
"aria-label": apiRef.current.getLocaleText('toolbarQuickFilterLabel'),
|
|
91
106
|
type: "search"
|
|
@@ -3,7 +3,7 @@ import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
|
|
|
3
3
|
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
|
|
4
4
|
import { GridDimensions } from './gridDimensionsApi';
|
|
5
5
|
import { GridStateInitializer } from '../../utils/useGridInitializeState';
|
|
6
|
-
type RootProps = Pick<DataGridProcessedProps, 'onResize' | 'scrollbarSize' | 'pagination' | 'paginationMode' | 'autoHeight' | 'getRowHeight' | 'rowHeight' | 'columnHeaderHeight'>;
|
|
6
|
+
type RootProps = Pick<DataGridProcessedProps, 'onResize' | 'scrollbarSize' | 'pagination' | 'paginationMode' | 'autoHeight' | 'getRowHeight' | 'rowHeight' | 'resizeThrottleMs' | 'columnHeaderHeight'>;
|
|
7
7
|
export type GridDimensionsState = GridDimensions;
|
|
8
8
|
export declare const dimensionsStateInitializer: GridStateInitializer<RootProps>;
|
|
9
9
|
export declare function useGridDimensions(apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: RootProps): void;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { unstable_ownerDocument as ownerDocument, unstable_useEnhancedEffect as useEnhancedEffect, unstable_useEventCallback as useEventCallback, unstable_ownerWindow as ownerWindow } from '@mui/utils';
|
|
4
4
|
import { useGridApiEventHandler, useGridApiOptionHandler } from '../../utils/useGridApiEventHandler';
|
|
5
5
|
import { useGridApiMethod } from '../../utils/useGridApiMethod';
|
|
6
|
+
import { throttle } from '../../../utils/throttle';
|
|
6
7
|
import { useGridLogger } from '../../utils/useGridLogger';
|
|
7
8
|
import { gridColumnsTotalWidthSelector, gridVisiblePinnedColumnDefinitionsSelector } from '../columns';
|
|
8
9
|
import { gridDimensionsSelector } from './gridDimensionsSelectors';
|
|
@@ -57,7 +58,7 @@ export function useGridDimensions(apiRef, props) {
|
|
|
57
58
|
const leftPinnedWidth = pinnedColumns.left.reduce((w, col) => w + col.computedWidth, 0);
|
|
58
59
|
const rightPinnedWidth = pinnedColumns.right.reduce((w, col) => w + col.computedWidth, 0);
|
|
59
60
|
const [savedSize, setSavedSize] = React.useState();
|
|
60
|
-
const debouncedSetSavedSize = React.useMemo(() =>
|
|
61
|
+
const debouncedSetSavedSize = React.useMemo(() => throttle(setSavedSize, props.resizeThrottleMs), [props.resizeThrottleMs]);
|
|
61
62
|
const previousSize = React.useRef();
|
|
62
63
|
const getRootDimensions = () => apiRef.current.state.dimensions;
|
|
63
64
|
const setDimensions = useEventCallback(dimensions => {
|
|
@@ -45,6 +45,10 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
45
45
|
throw new Error(`MUI X: The row with id=${id} is not in ${mode} mode.`);
|
|
46
46
|
}
|
|
47
47
|
}, [apiRef]);
|
|
48
|
+
const hasFieldsWithErrors = React.useCallback(rowId => {
|
|
49
|
+
const editingState = gridEditRowsStateSelector(apiRef.current.state);
|
|
50
|
+
return Object.values(editingState[rowId]).some(fieldProps => fieldProps.error);
|
|
51
|
+
}, [apiRef]);
|
|
48
52
|
const handleCellDoubleClick = React.useCallback((params, event) => {
|
|
49
53
|
if (!params.isEditable) {
|
|
50
54
|
return;
|
|
@@ -86,6 +90,9 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
86
90
|
if (apiRef.current.getRowMode(params.id) === GridRowModes.View) {
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
93
|
+
if (hasFieldsWithErrors(params.id)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
89
96
|
const rowParams = apiRef.current.getRowParams(params.id);
|
|
90
97
|
const newParams = _extends({}, rowParams, {
|
|
91
98
|
field: params.field,
|
|
@@ -94,7 +101,7 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
94
101
|
apiRef.current.publishEvent('rowEditStop', newParams, event);
|
|
95
102
|
}
|
|
96
103
|
});
|
|
97
|
-
}, [apiRef]);
|
|
104
|
+
}, [apiRef, hasFieldsWithErrors]);
|
|
98
105
|
React.useEffect(() => {
|
|
99
106
|
return () => {
|
|
100
107
|
clearTimeout(focusTimeout.current);
|
|
@@ -140,6 +147,9 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
if (reason) {
|
|
150
|
+
if (reason !== GridRowEditStopReasons.escapeKeyDown && hasFieldsWithErrors(params.id)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
143
153
|
const newParams = _extends({}, apiRef.current.getRowParams(params.id), {
|
|
144
154
|
reason,
|
|
145
155
|
field: params.field
|
|
@@ -174,7 +184,7 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
174
184
|
apiRef.current.publishEvent('rowEditStart', newParams, event);
|
|
175
185
|
}
|
|
176
186
|
}
|
|
177
|
-
}, [apiRef]);
|
|
187
|
+
}, [apiRef, hasFieldsWithErrors]);
|
|
178
188
|
const handleRowEditStart = React.useCallback(params => {
|
|
179
189
|
const {
|
|
180
190
|
id,
|
|
@@ -358,8 +368,7 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
358
368
|
prevRowModesModel.current[id].mode = GridRowModes.Edit;
|
|
359
369
|
return;
|
|
360
370
|
}
|
|
361
|
-
|
|
362
|
-
if (hasSomeFieldWithError) {
|
|
371
|
+
if (hasFieldsWithErrors(id)) {
|
|
363
372
|
prevRowModesModel.current[id].mode = GridRowModes.Edit;
|
|
364
373
|
// Revert the mode in the rowModesModel prop back to "edit"
|
|
365
374
|
updateRowInRowModesModel(id, {
|
package/index.js
CHANGED
|
@@ -112,6 +112,11 @@ export interface DataGridPropsWithDefaultValues<R extends GridValidRowModel = an
|
|
|
112
112
|
* @default true
|
|
113
113
|
*/
|
|
114
114
|
rowSelection: boolean;
|
|
115
|
+
/**
|
|
116
|
+
* The milliseconds throttle delay for resizing the grid.
|
|
117
|
+
* @default 60
|
|
118
|
+
*/
|
|
119
|
+
resizeThrottleMs: number;
|
|
115
120
|
/**
|
|
116
121
|
* If `true`, column filters are disabled.
|
|
117
122
|
* @default false
|
|
@@ -608,6 +608,11 @@ DataGridRaw.propTypes = {
|
|
|
608
608
|
* @returns {Promise<R> | R} The final values to update the row.
|
|
609
609
|
*/
|
|
610
610
|
processRowUpdate: PropTypes.func,
|
|
611
|
+
/**
|
|
612
|
+
* The milliseconds throttle delay for resizing the grid.
|
|
613
|
+
* @default 60
|
|
614
|
+
*/
|
|
615
|
+
resizeThrottleMs: PropTypes.number,
|
|
611
616
|
/**
|
|
612
617
|
* Row region in pixels to render before/after the viewport
|
|
613
618
|
* @default 150
|
|
@@ -153,7 +153,9 @@ function GridColumnHeaderItem(props) {
|
|
|
153
153
|
const focusableElement = headerCellRef.current.querySelector('[tabindex="0"]');
|
|
154
154
|
const elementToFocus = focusableElement || headerCellRef.current;
|
|
155
155
|
elementToFocus?.focus();
|
|
156
|
-
apiRef.current.columnHeadersContainerRef
|
|
156
|
+
if (apiRef.current.columnHeadersContainerRef?.current) {
|
|
157
|
+
apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0;
|
|
158
|
+
}
|
|
157
159
|
}
|
|
158
160
|
}, [apiRef, hasFocus]);
|
|
159
161
|
const headerClassName = typeof colDef.headerClassName === 'function' ? colDef.headerClassName({
|
|
@@ -56,7 +56,9 @@ const GridGenericColumnHeaderItem = /*#__PURE__*/React.forwardRef(function GridG
|
|
|
56
56
|
const focusableElement = headerCellRef.current.querySelector('[tabindex="0"]');
|
|
57
57
|
const elementToFocus = focusableElement || headerCellRef.current;
|
|
58
58
|
elementToFocus?.focus();
|
|
59
|
-
apiRef.current.columnHeadersContainerRef
|
|
59
|
+
if (apiRef.current.columnHeadersContainerRef?.current) {
|
|
60
|
+
apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0;
|
|
61
|
+
}
|
|
60
62
|
}
|
|
61
63
|
}, [apiRef, hasFocus]);
|
|
62
64
|
return /*#__PURE__*/_jsxs("div", _extends({
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
|
3
|
-
const _excluded = ["quickFilterParser", "quickFilterFormatter", "debounceMs"];
|
|
3
|
+
const _excluded = ["quickFilterParser", "quickFilterFormatter", "debounceMs", "className"];
|
|
4
4
|
import * as React from 'react';
|
|
5
|
+
import clsx from 'clsx';
|
|
5
6
|
import PropTypes from 'prop-types';
|
|
6
7
|
import TextField from '@mui/material/TextField';
|
|
7
8
|
import { styled } from '@mui/material/styles';
|
|
8
9
|
import { unstable_debounce as debounce } from '@mui/utils';
|
|
10
|
+
import composeClasses from '@mui/utils/composeClasses';
|
|
11
|
+
import { getDataGridUtilityClass } from '../../constants';
|
|
9
12
|
import { useGridApiContext } from '../../hooks/utils/useGridApiContext';
|
|
10
13
|
import { useGridRootProps } from '../../hooks/utils/useGridRootProps';
|
|
11
14
|
import { useGridSelector } from '../../hooks/utils/useGridSelector';
|
|
12
15
|
import { gridQuickFilterValuesSelector } from '../../hooks/features/filter';
|
|
13
16
|
import { isDeepEqual } from '../../utils/utils';
|
|
14
17
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
+
const useUtilityClasses = ownerState => {
|
|
19
|
+
const {
|
|
20
|
+
classes
|
|
21
|
+
} = ownerState;
|
|
22
|
+
const slots = {
|
|
23
|
+
root: ['toolbarQuickFilter']
|
|
24
|
+
};
|
|
25
|
+
return composeClasses(slots, getDataGridUtilityClass, classes);
|
|
26
|
+
};
|
|
15
27
|
const GridToolbarQuickFilterRoot = styled(TextField, {
|
|
16
28
|
name: 'MuiDataGrid',
|
|
17
29
|
slot: 'ToolbarQuickFilter',
|
|
@@ -47,11 +59,13 @@ const defaultSearchValueFormatter = values => values.join(' ');
|
|
|
47
59
|
function GridToolbarQuickFilter(props) {
|
|
48
60
|
const apiRef = useGridApiContext();
|
|
49
61
|
const rootProps = useGridRootProps();
|
|
62
|
+
const classes = useUtilityClasses(rootProps);
|
|
50
63
|
const quickFilterValues = useGridSelector(apiRef, gridQuickFilterValuesSelector);
|
|
51
64
|
const {
|
|
52
65
|
quickFilterParser = defaultSearchValueParser,
|
|
53
66
|
quickFilterFormatter = defaultSearchValueFormatter,
|
|
54
|
-
debounceMs = rootProps.filterDebounceMs
|
|
67
|
+
debounceMs = rootProps.filterDebounceMs,
|
|
68
|
+
className
|
|
55
69
|
} = props,
|
|
56
70
|
other = _objectWithoutPropertiesLoose(props, _excluded);
|
|
57
71
|
const [searchValue, setSearchValue] = React.useState(() => quickFilterFormatter(quickFilterValues ?? []));
|
|
@@ -86,6 +100,7 @@ function GridToolbarQuickFilter(props) {
|
|
|
86
100
|
variant: "standard",
|
|
87
101
|
value: searchValue,
|
|
88
102
|
onChange: handleSearchValueChange,
|
|
103
|
+
className: clsx(className, classes.root),
|
|
89
104
|
placeholder: apiRef.current.getLocaleText('toolbarQuickFilterPlaceholder'),
|
|
90
105
|
"aria-label": apiRef.current.getLocaleText('toolbarQuickFilterLabel'),
|
|
91
106
|
type: "search"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { unstable_ownerDocument as ownerDocument, unstable_useEnhancedEffect as useEnhancedEffect, unstable_useEventCallback as useEventCallback, unstable_ownerWindow as ownerWindow } from '@mui/utils';
|
|
4
4
|
import { useGridApiEventHandler, useGridApiOptionHandler } from '../../utils/useGridApiEventHandler';
|
|
5
5
|
import { useGridApiMethod } from '../../utils/useGridApiMethod';
|
|
6
|
+
import { throttle } from '../../../utils/throttle';
|
|
6
7
|
import { useGridLogger } from '../../utils/useGridLogger';
|
|
7
8
|
import { gridColumnsTotalWidthSelector, gridVisiblePinnedColumnDefinitionsSelector } from '../columns';
|
|
8
9
|
import { gridDimensionsSelector } from './gridDimensionsSelectors';
|
|
@@ -57,7 +58,7 @@ export function useGridDimensions(apiRef, props) {
|
|
|
57
58
|
const leftPinnedWidth = pinnedColumns.left.reduce((w, col) => w + col.computedWidth, 0);
|
|
58
59
|
const rightPinnedWidth = pinnedColumns.right.reduce((w, col) => w + col.computedWidth, 0);
|
|
59
60
|
const [savedSize, setSavedSize] = React.useState();
|
|
60
|
-
const debouncedSetSavedSize = React.useMemo(() =>
|
|
61
|
+
const debouncedSetSavedSize = React.useMemo(() => throttle(setSavedSize, props.resizeThrottleMs), [props.resizeThrottleMs]);
|
|
61
62
|
const previousSize = React.useRef();
|
|
62
63
|
const getRootDimensions = () => apiRef.current.state.dimensions;
|
|
63
64
|
const setDimensions = useEventCallback(dimensions => {
|
|
@@ -45,6 +45,10 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
45
45
|
throw new Error(`MUI X: The row with id=${id} is not in ${mode} mode.`);
|
|
46
46
|
}
|
|
47
47
|
}, [apiRef]);
|
|
48
|
+
const hasFieldsWithErrors = React.useCallback(rowId => {
|
|
49
|
+
const editingState = gridEditRowsStateSelector(apiRef.current.state);
|
|
50
|
+
return Object.values(editingState[rowId]).some(fieldProps => fieldProps.error);
|
|
51
|
+
}, [apiRef]);
|
|
48
52
|
const handleCellDoubleClick = React.useCallback((params, event) => {
|
|
49
53
|
if (!params.isEditable) {
|
|
50
54
|
return;
|
|
@@ -86,6 +90,9 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
86
90
|
if (apiRef.current.getRowMode(params.id) === GridRowModes.View) {
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
93
|
+
if (hasFieldsWithErrors(params.id)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
89
96
|
const rowParams = apiRef.current.getRowParams(params.id);
|
|
90
97
|
const newParams = _extends({}, rowParams, {
|
|
91
98
|
field: params.field,
|
|
@@ -94,7 +101,7 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
94
101
|
apiRef.current.publishEvent('rowEditStop', newParams, event);
|
|
95
102
|
}
|
|
96
103
|
});
|
|
97
|
-
}, [apiRef]);
|
|
104
|
+
}, [apiRef, hasFieldsWithErrors]);
|
|
98
105
|
React.useEffect(() => {
|
|
99
106
|
return () => {
|
|
100
107
|
clearTimeout(focusTimeout.current);
|
|
@@ -140,6 +147,9 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
if (reason) {
|
|
150
|
+
if (reason !== GridRowEditStopReasons.escapeKeyDown && hasFieldsWithErrors(params.id)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
143
153
|
const newParams = _extends({}, apiRef.current.getRowParams(params.id), {
|
|
144
154
|
reason,
|
|
145
155
|
field: params.field
|
|
@@ -174,7 +184,7 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
174
184
|
apiRef.current.publishEvent('rowEditStart', newParams, event);
|
|
175
185
|
}
|
|
176
186
|
}
|
|
177
|
-
}, [apiRef]);
|
|
187
|
+
}, [apiRef, hasFieldsWithErrors]);
|
|
178
188
|
const handleRowEditStart = React.useCallback(params => {
|
|
179
189
|
const {
|
|
180
190
|
id,
|
|
@@ -358,8 +368,7 @@ export const useGridRowEditing = (apiRef, props) => {
|
|
|
358
368
|
prevRowModesModel.current[id].mode = GridRowModes.Edit;
|
|
359
369
|
return;
|
|
360
370
|
}
|
|
361
|
-
|
|
362
|
-
if (hasSomeFieldWithError) {
|
|
371
|
+
if (hasFieldsWithErrors(id)) {
|
|
363
372
|
prevRowModesModel.current[id].mode = GridRowModes.Edit;
|
|
364
373
|
// Revert the mode in the rowModesModel prop back to "edit"
|
|
365
374
|
updateRowInRowModesModel(id, {
|
package/modern/index.js
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function throttle(func, wait = 166) {
|
|
2
|
+
let timeout;
|
|
3
|
+
let lastArgs;
|
|
4
|
+
const later = () => {
|
|
5
|
+
timeout = undefined;
|
|
6
|
+
func(...lastArgs);
|
|
7
|
+
};
|
|
8
|
+
function throttled(...args) {
|
|
9
|
+
lastArgs = args;
|
|
10
|
+
if (timeout === undefined) {
|
|
11
|
+
timeout = setTimeout(later, wait);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
throttled.clear = () => {
|
|
15
|
+
clearTimeout(timeout);
|
|
16
|
+
timeout = undefined;
|
|
17
|
+
};
|
|
18
|
+
return throttled;
|
|
19
|
+
}
|
|
@@ -615,6 +615,11 @@ DataGridRaw.propTypes = {
|
|
|
615
615
|
* @returns {Promise<R> | R} The final values to update the row.
|
|
616
616
|
*/
|
|
617
617
|
processRowUpdate: _propTypes.default.func,
|
|
618
|
+
/**
|
|
619
|
+
* The milliseconds throttle delay for resizing the grid.
|
|
620
|
+
* @default 60
|
|
621
|
+
*/
|
|
622
|
+
resizeThrottleMs: _propTypes.default.number,
|
|
618
623
|
/**
|
|
619
624
|
* Row region in pixels to render before/after the viewport
|
|
620
625
|
* @default 150
|
|
@@ -63,6 +63,7 @@ const DATA_GRID_PROPS_DEFAULT_VALUES = exports.DATA_GRID_PROPS_DEFAULT_VALUES =
|
|
|
63
63
|
pagination: false,
|
|
64
64
|
paginationMode: 'client',
|
|
65
65
|
rowHeight: 52,
|
|
66
|
+
resizeThrottleMs: 60,
|
|
66
67
|
pageSizeOptions: [25, 50, 100],
|
|
67
68
|
rowSpacingType: 'margin',
|
|
68
69
|
showCellVerticalBorder: false,
|
|
@@ -161,7 +161,9 @@ function GridColumnHeaderItem(props) {
|
|
|
161
161
|
const focusableElement = headerCellRef.current.querySelector('[tabindex="0"]');
|
|
162
162
|
const elementToFocus = focusableElement || headerCellRef.current;
|
|
163
163
|
elementToFocus?.focus();
|
|
164
|
-
apiRef.current.columnHeadersContainerRef
|
|
164
|
+
if (apiRef.current.columnHeadersContainerRef?.current) {
|
|
165
|
+
apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0;
|
|
166
|
+
}
|
|
165
167
|
}
|
|
166
168
|
}, [apiRef, hasFocus]);
|
|
167
169
|
const headerClassName = typeof colDef.headerClassName === 'function' ? colDef.headerClassName({
|
|
@@ -64,7 +64,9 @@ const GridGenericColumnHeaderItem = exports.GridGenericColumnHeaderItem = /*#__P
|
|
|
64
64
|
const focusableElement = headerCellRef.current.querySelector('[tabindex="0"]');
|
|
65
65
|
const elementToFocus = focusableElement || headerCellRef.current;
|
|
66
66
|
elementToFocus?.focus();
|
|
67
|
-
apiRef.current.columnHeadersContainerRef
|
|
67
|
+
if (apiRef.current.columnHeadersContainerRef?.current) {
|
|
68
|
+
apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0;
|
|
69
|
+
}
|
|
68
70
|
}
|
|
69
71
|
}, [apiRef, hasFocus]);
|
|
70
72
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", (0, _extends2.default)({
|
|
@@ -8,19 +8,31 @@ exports.GridToolbarQuickFilter = GridToolbarQuickFilter;
|
|
|
8
8
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
9
9
|
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
|
|
10
10
|
var React = _interopRequireWildcard(require("react"));
|
|
11
|
+
var _clsx = _interopRequireDefault(require("clsx"));
|
|
11
12
|
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
12
13
|
var _TextField = _interopRequireDefault(require("@mui/material/TextField"));
|
|
13
14
|
var _styles = require("@mui/material/styles");
|
|
14
15
|
var _utils = require("@mui/utils");
|
|
16
|
+
var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
|
|
17
|
+
var _constants = require("../../constants");
|
|
15
18
|
var _useGridApiContext = require("../../hooks/utils/useGridApiContext");
|
|
16
19
|
var _useGridRootProps = require("../../hooks/utils/useGridRootProps");
|
|
17
20
|
var _useGridSelector = require("../../hooks/utils/useGridSelector");
|
|
18
21
|
var _filter = require("../../hooks/features/filter");
|
|
19
22
|
var _utils2 = require("../../utils/utils");
|
|
20
23
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
21
|
-
const _excluded = ["quickFilterParser", "quickFilterFormatter", "debounceMs"];
|
|
24
|
+
const _excluded = ["quickFilterParser", "quickFilterFormatter", "debounceMs", "className"];
|
|
22
25
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
23
26
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
27
|
+
const useUtilityClasses = ownerState => {
|
|
28
|
+
const {
|
|
29
|
+
classes
|
|
30
|
+
} = ownerState;
|
|
31
|
+
const slots = {
|
|
32
|
+
root: ['toolbarQuickFilter']
|
|
33
|
+
};
|
|
34
|
+
return (0, _composeClasses.default)(slots, _constants.getDataGridUtilityClass, classes);
|
|
35
|
+
};
|
|
24
36
|
const GridToolbarQuickFilterRoot = (0, _styles.styled)(_TextField.default, {
|
|
25
37
|
name: 'MuiDataGrid',
|
|
26
38
|
slot: 'ToolbarQuickFilter',
|
|
@@ -56,11 +68,13 @@ const defaultSearchValueFormatter = values => values.join(' ');
|
|
|
56
68
|
function GridToolbarQuickFilter(props) {
|
|
57
69
|
const apiRef = (0, _useGridApiContext.useGridApiContext)();
|
|
58
70
|
const rootProps = (0, _useGridRootProps.useGridRootProps)();
|
|
71
|
+
const classes = useUtilityClasses(rootProps);
|
|
59
72
|
const quickFilterValues = (0, _useGridSelector.useGridSelector)(apiRef, _filter.gridQuickFilterValuesSelector);
|
|
60
73
|
const {
|
|
61
74
|
quickFilterParser = defaultSearchValueParser,
|
|
62
75
|
quickFilterFormatter = defaultSearchValueFormatter,
|
|
63
|
-
debounceMs = rootProps.filterDebounceMs
|
|
76
|
+
debounceMs = rootProps.filterDebounceMs,
|
|
77
|
+
className
|
|
64
78
|
} = props,
|
|
65
79
|
other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded);
|
|
66
80
|
const [searchValue, setSearchValue] = React.useState(() => quickFilterFormatter(quickFilterValues ?? []));
|
|
@@ -95,6 +109,7 @@ function GridToolbarQuickFilter(props) {
|
|
|
95
109
|
variant: "standard",
|
|
96
110
|
value: searchValue,
|
|
97
111
|
onChange: handleSearchValueChange,
|
|
112
|
+
className: (0, _clsx.default)(className, classes.root),
|
|
98
113
|
placeholder: apiRef.current.getLocaleText('toolbarQuickFilterPlaceholder'),
|
|
99
114
|
"aria-label": apiRef.current.getLocaleText('toolbarQuickFilterLabel'),
|
|
100
115
|
type: "search"
|
|
@@ -11,6 +11,7 @@ var React = _interopRequireWildcard(require("react"));
|
|
|
11
11
|
var _utils = require("@mui/utils");
|
|
12
12
|
var _useGridApiEventHandler = require("../../utils/useGridApiEventHandler");
|
|
13
13
|
var _useGridApiMethod = require("../../utils/useGridApiMethod");
|
|
14
|
+
var _throttle = require("../../../utils/throttle");
|
|
14
15
|
var _useGridLogger = require("../../utils/useGridLogger");
|
|
15
16
|
var _columns = require("../columns");
|
|
16
17
|
var _gridDimensionsSelectors = require("./gridDimensionsSelectors");
|
|
@@ -68,7 +69,7 @@ function useGridDimensions(apiRef, props) {
|
|
|
68
69
|
const leftPinnedWidth = pinnedColumns.left.reduce((w, col) => w + col.computedWidth, 0);
|
|
69
70
|
const rightPinnedWidth = pinnedColumns.right.reduce((w, col) => w + col.computedWidth, 0);
|
|
70
71
|
const [savedSize, setSavedSize] = React.useState();
|
|
71
|
-
const debouncedSetSavedSize = React.useMemo(() => (0,
|
|
72
|
+
const debouncedSetSavedSize = React.useMemo(() => (0, _throttle.throttle)(setSavedSize, props.resizeThrottleMs), [props.resizeThrottleMs]);
|
|
72
73
|
const previousSize = React.useRef();
|
|
73
74
|
const getRootDimensions = () => apiRef.current.state.dimensions;
|
|
74
75
|
const setDimensions = (0, _utils.unstable_useEventCallback)(dimensions => {
|
|
@@ -54,6 +54,10 @@ const useGridRowEditing = (apiRef, props) => {
|
|
|
54
54
|
throw new Error(`MUI X: The row with id=${id} is not in ${mode} mode.`);
|
|
55
55
|
}
|
|
56
56
|
}, [apiRef]);
|
|
57
|
+
const hasFieldsWithErrors = React.useCallback(rowId => {
|
|
58
|
+
const editingState = (0, _gridEditingSelectors.gridEditRowsStateSelector)(apiRef.current.state);
|
|
59
|
+
return Object.values(editingState[rowId]).some(fieldProps => fieldProps.error);
|
|
60
|
+
}, [apiRef]);
|
|
57
61
|
const handleCellDoubleClick = React.useCallback((params, event) => {
|
|
58
62
|
if (!params.isEditable) {
|
|
59
63
|
return;
|
|
@@ -95,6 +99,9 @@ const useGridRowEditing = (apiRef, props) => {
|
|
|
95
99
|
if (apiRef.current.getRowMode(params.id) === _gridEditRowModel.GridRowModes.View) {
|
|
96
100
|
return;
|
|
97
101
|
}
|
|
102
|
+
if (hasFieldsWithErrors(params.id)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
98
105
|
const rowParams = apiRef.current.getRowParams(params.id);
|
|
99
106
|
const newParams = (0, _extends2.default)({}, rowParams, {
|
|
100
107
|
field: params.field,
|
|
@@ -103,7 +110,7 @@ const useGridRowEditing = (apiRef, props) => {
|
|
|
103
110
|
apiRef.current.publishEvent('rowEditStop', newParams, event);
|
|
104
111
|
}
|
|
105
112
|
});
|
|
106
|
-
}, [apiRef]);
|
|
113
|
+
}, [apiRef, hasFieldsWithErrors]);
|
|
107
114
|
React.useEffect(() => {
|
|
108
115
|
return () => {
|
|
109
116
|
clearTimeout(focusTimeout.current);
|
|
@@ -149,6 +156,9 @@ const useGridRowEditing = (apiRef, props) => {
|
|
|
149
156
|
}
|
|
150
157
|
}
|
|
151
158
|
if (reason) {
|
|
159
|
+
if (reason !== _gridRowParams.GridRowEditStopReasons.escapeKeyDown && hasFieldsWithErrors(params.id)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
152
162
|
const newParams = (0, _extends2.default)({}, apiRef.current.getRowParams(params.id), {
|
|
153
163
|
reason,
|
|
154
164
|
field: params.field
|
|
@@ -183,7 +193,7 @@ const useGridRowEditing = (apiRef, props) => {
|
|
|
183
193
|
apiRef.current.publishEvent('rowEditStart', newParams, event);
|
|
184
194
|
}
|
|
185
195
|
}
|
|
186
|
-
}, [apiRef]);
|
|
196
|
+
}, [apiRef, hasFieldsWithErrors]);
|
|
187
197
|
const handleRowEditStart = React.useCallback(params => {
|
|
188
198
|
const {
|
|
189
199
|
id,
|
|
@@ -367,8 +377,7 @@ const useGridRowEditing = (apiRef, props) => {
|
|
|
367
377
|
prevRowModesModel.current[id].mode = _gridEditRowModel.GridRowModes.Edit;
|
|
368
378
|
return;
|
|
369
379
|
}
|
|
370
|
-
|
|
371
|
-
if (hasSomeFieldWithError) {
|
|
380
|
+
if (hasFieldsWithErrors(id)) {
|
|
372
381
|
prevRowModesModel.current[id].mode = _gridEditRowModel.GridRowModes.Edit;
|
|
373
382
|
// Revert the mode in the rowModesModel prop back to "edit"
|
|
374
383
|
updateRowInRowModesModel(id, {
|
package/node/index.js
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.throttle = throttle;
|
|
7
|
+
function throttle(func, wait = 166) {
|
|
8
|
+
let timeout;
|
|
9
|
+
let lastArgs;
|
|
10
|
+
const later = () => {
|
|
11
|
+
timeout = undefined;
|
|
12
|
+
func(...lastArgs);
|
|
13
|
+
};
|
|
14
|
+
function throttled(...args) {
|
|
15
|
+
lastArgs = args;
|
|
16
|
+
if (timeout === undefined) {
|
|
17
|
+
timeout = setTimeout(later, wait);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
throttled.clear = () => {
|
|
21
|
+
clearTimeout(timeout);
|
|
22
|
+
timeout = undefined;
|
|
23
|
+
};
|
|
24
|
+
return throttled;
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function throttle(func, wait = 166) {
|
|
2
|
+
let timeout;
|
|
3
|
+
let lastArgs;
|
|
4
|
+
const later = () => {
|
|
5
|
+
timeout = undefined;
|
|
6
|
+
func(...lastArgs);
|
|
7
|
+
};
|
|
8
|
+
function throttled(...args) {
|
|
9
|
+
lastArgs = args;
|
|
10
|
+
if (timeout === undefined) {
|
|
11
|
+
timeout = setTimeout(later, wait);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
throttled.clear = () => {
|
|
15
|
+
clearTimeout(timeout);
|
|
16
|
+
timeout = undefined;
|
|
17
|
+
};
|
|
18
|
+
return throttled;
|
|
19
|
+
}
|