Skip to content

Commit 85ce864

Browse files
committed
Merge remote-tracking branch 'upstream/master' into map-zoom
2 parents eee89a2 + c273e8b commit 85ce864

17 files changed

Lines changed: 381 additions & 75 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import * as React from 'react';
2+
import {
3+
DataGridPremium,
4+
gridFocusCellSelector,
5+
gridSortedRowIdsSelector,
6+
useGridApiRef,
7+
} from '@mui/x-data-grid-premium';
8+
9+
import { useDemoData } from '@mui/x-data-grid-generator';
10+
import Button from '@mui/material/Button';
11+
import Paper from '@mui/material/Paper';
12+
import Stack from '@mui/material/Stack';
13+
import Typography from '@mui/material/Typography';
14+
15+
// Mirrors the priority used for the Ctrl+C shortcut: selected cells, then
16+
// selected rows, then the focused cell. It's built entirely from public APIs,
17+
// so it can be triggered from anywhere, including a button or a touch device.
18+
function getSelectionAsText(apiRef) {
19+
const api = apiRef.current;
20+
if (!api) {
21+
return '';
22+
}
23+
24+
const selectedCells = api.getSelectedCellsAsArray();
25+
if (selectedCells.length > 1) {
26+
const cellSelectionModel = api.getCellSelectionModel();
27+
const sortedRowIds = gridSortedRowIdsSelector({ current: api }).filter(
28+
(id) => cellSelectionModel[id],
29+
);
30+
return sortedRowIds
31+
.map((rowId) =>
32+
Object.keys(cellSelectionModel[rowId])
33+
.map((field) =>
34+
cellSelectionModel[rowId][field]
35+
? String(api.getCellParams(rowId, field).formattedValue)
36+
: '',
37+
)
38+
.join('\t'),
39+
)
40+
.join('\n');
41+
}
42+
43+
const selectedRowIds = api.getSelectedRows();
44+
if (selectedRowIds.size > 0) {
45+
return api.getDataAsCsv({
46+
includeHeaders: false,
47+
shouldAppendQuotes: false,
48+
escapeFormulas: false,
49+
});
50+
}
51+
52+
const focusedCell = gridFocusCellSelector({ current: api });
53+
if (focusedCell) {
54+
return String(
55+
api.getCellParams(focusedCell.id, focusedCell.field).formattedValue,
56+
);
57+
}
58+
59+
return '';
60+
}
61+
62+
export default function ClipboardCopyButton() {
63+
const apiRef = useGridApiRef();
64+
const { data } = useDemoData({
65+
dataSet: 'Commodity',
66+
rowLength: 10,
67+
maxColumns: 6,
68+
});
69+
const [copiedText, setCopiedText] = React.useState('');
70+
71+
const handleCopy = () => {
72+
const text = getSelectionAsText(apiRef);
73+
navigator.clipboard.writeText(text);
74+
setCopiedText(text);
75+
};
76+
77+
return (
78+
<Stack sx={{ width: '100%' }} spacing={1}>
79+
<Button
80+
sx={{ alignSelf: 'flex-start' }}
81+
variant="outlined"
82+
onClick={handleCopy}
83+
>
84+
Copy selection
85+
</Button>
86+
<div style={{ height: 400 }}>
87+
<DataGridPremium apiRef={apiRef} checkboxSelection cellSelection {...data} />
88+
</div>
89+
<Typography variant="body2" color="text.secondary">
90+
Clipboard content:
91+
</Typography>
92+
<Paper variant="outlined" sx={{ p: 1.5, minHeight: 48 }}>
93+
<Typography
94+
component="pre"
95+
variant="body2"
96+
sx={{
97+
m: 0,
98+
fontFamily: 'monospace',
99+
whiteSpace: 'pre-wrap',
100+
wordBreak: 'break-all',
101+
}}
102+
>
103+
{copiedText || (
104+
<Typography component="span" color="text.disabled" variant="body2">
105+
Nothing copied yet
106+
</Typography>
107+
)}
108+
</Typography>
109+
</Paper>
110+
</Stack>
111+
);
112+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import * as React from 'react';
2+
import {
3+
DataGridPremium,
4+
GridApiPremium,
5+
gridFocusCellSelector,
6+
gridSortedRowIdsSelector,
7+
useGridApiRef,
8+
} from '@mui/x-data-grid-premium';
9+
import type { RefObject } from '@mui/x-internals/types';
10+
import { useDemoData } from '@mui/x-data-grid-generator';
11+
import Button from '@mui/material/Button';
12+
import Paper from '@mui/material/Paper';
13+
import Stack from '@mui/material/Stack';
14+
import Typography from '@mui/material/Typography';
15+
16+
// Mirrors the priority used for the Ctrl+C shortcut: selected cells, then
17+
// selected rows, then the focused cell. It's built entirely from public APIs,
18+
// so it can be triggered from anywhere, including a button or a touch device.
19+
function getSelectionAsText(apiRef: RefObject<GridApiPremium | null>): string {
20+
const api = apiRef.current;
21+
if (!api) {
22+
return '';
23+
}
24+
25+
const selectedCells = api.getSelectedCellsAsArray();
26+
if (selectedCells.length > 1) {
27+
const cellSelectionModel = api.getCellSelectionModel();
28+
const sortedRowIds = gridSortedRowIdsSelector({ current: api }).filter(
29+
(id) => cellSelectionModel[id],
30+
);
31+
return sortedRowIds
32+
.map((rowId) =>
33+
Object.keys(cellSelectionModel[rowId])
34+
.map((field) =>
35+
cellSelectionModel[rowId][field]
36+
? String(api.getCellParams(rowId, field).formattedValue)
37+
: '',
38+
)
39+
.join('\t'),
40+
)
41+
.join('\n');
42+
}
43+
44+
const selectedRowIds = api.getSelectedRows();
45+
if (selectedRowIds.size > 0) {
46+
return api.getDataAsCsv({
47+
includeHeaders: false,
48+
shouldAppendQuotes: false,
49+
escapeFormulas: false,
50+
});
51+
}
52+
53+
const focusedCell = gridFocusCellSelector({ current: api });
54+
if (focusedCell) {
55+
return String(
56+
api.getCellParams(focusedCell.id, focusedCell.field).formattedValue,
57+
);
58+
}
59+
60+
return '';
61+
}
62+
63+
export default function ClipboardCopyButton() {
64+
const apiRef = useGridApiRef();
65+
const { data } = useDemoData({
66+
dataSet: 'Commodity',
67+
rowLength: 10,
68+
maxColumns: 6,
69+
});
70+
const [copiedText, setCopiedText] = React.useState('');
71+
72+
const handleCopy = () => {
73+
const text = getSelectionAsText(apiRef);
74+
navigator.clipboard.writeText(text);
75+
setCopiedText(text);
76+
};
77+
78+
return (
79+
<Stack sx={{ width: '100%' }} spacing={1}>
80+
<Button
81+
sx={{ alignSelf: 'flex-start' }}
82+
variant="outlined"
83+
onClick={handleCopy}
84+
>
85+
Copy selection
86+
</Button>
87+
<div style={{ height: 400 }}>
88+
<DataGridPremium apiRef={apiRef} checkboxSelection cellSelection {...data} />
89+
</div>
90+
<Typography variant="body2" color="text.secondary">
91+
Clipboard content:
92+
</Typography>
93+
<Paper variant="outlined" sx={{ p: 1.5, minHeight: 48 }}>
94+
<Typography
95+
component="pre"
96+
variant="body2"
97+
sx={{
98+
m: 0,
99+
fontFamily: 'monospace',
100+
whiteSpace: 'pre-wrap',
101+
wordBreak: 'break-all',
102+
}}
103+
>
104+
{copiedText || (
105+
<Typography component="span" color="text.disabled" variant="body2">
106+
Nothing copied yet
107+
</Typography>
108+
)}
109+
</Typography>
110+
</Paper>
111+
</Stack>
112+
);
113+
}

docs/data/data-grid/clipboard/clipboard.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ The priority of the data copied to the clipboard is the following, from highest
1515

1616
{{"demo": "ClipboardCopy.js", "bg": "inline", "defaultCodeOpen": false}}
1717

18+
### Triggering a copy without the keyboard shortcut
19+
20+
To let users copy the current selection from a button (or any other UI, such as a context menu), combine public APIs: `apiRef.current.getSelectedCellsAsArray()` and `getCellSelectionModel()` for cell selection, `apiRef.current.getDataAsCsv()` for row selection, and `gridFocusCellSelector` as a fallback for a single focused cell.
21+
22+
{{"demo": "ClipboardCopyButton.js", "bg": "inline"}}
23+
1824
## Clipboard paste [<span class="plan-premium"></span>](/x/introduction/licensing/#premium-plan 'Premium plan')
1925

2026
:::info

packages/x-charts/src/ChartsXAxis/ChartsGroupedXAxisTicks.tsx

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,7 @@ import { useAxisTicksProps } from './useAxisTicksProps';
99
import { useStore } from '../internals/store/useStore';
1010
import { selectorChartXAxisAutoSizeResults } from '../internals/plugins/featurePlugins/useChartCartesianAxis/useChartAxisAutoSize.selectors';
1111
import type { UseChartCartesianAxisSignature } from '../internals/plugins/featurePlugins/useChartCartesianAxis';
12-
13-
const DEFAULT_GROUPING_CONFIG = {
14-
tickSize: 6,
15-
};
16-
17-
const getGroupingConfig = (
18-
groups: AxisGroup[],
19-
groupIndex: number,
20-
tickSize: number | undefined,
21-
computedGroupTickSizes?: number[],
22-
) => {
23-
const config = groups[groupIndex] ?? ({} as AxisGroup);
24-
25-
const defaultTickSize = tickSize ?? DEFAULT_GROUPING_CONFIG.tickSize;
26-
const calculatedTickSize = defaultTickSize * groupIndex * 2 + defaultTickSize;
27-
28-
return {
29-
...DEFAULT_GROUPING_CONFIG,
30-
...config,
31-
tickSize: computedGroupTickSizes?.[groupIndex] ?? config.tickSize ?? calculatedTickSize,
32-
};
33-
};
12+
import { getGroupingConfig } from '../internals/getGroupingConfig';
3413

3514
interface ChartsGroupedXAxisProps extends ChartsXAxisProps {}
3615

packages/x-charts/src/ChartsYAxis/ChartsGroupedYAxisTicks.tsx

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,7 @@ import { useAxisTicksProps } from './useAxisTicksProps';
99
import { useStore } from '../internals/store/useStore';
1010
import { selectorChartYAxisAutoSizeResults } from '../internals/plugins/featurePlugins/useChartCartesianAxis/useChartAxisAutoSize.selectors';
1111
import type { UseChartCartesianAxisSignature } from '../internals/plugins/featurePlugins/useChartCartesianAxis';
12-
13-
const DEFAULT_GROUPING_CONFIG = {
14-
tickSize: 6,
15-
};
16-
17-
const getGroupingConfig = (
18-
groups: AxisGroup[],
19-
groupIndex: number,
20-
tickSize: number | undefined,
21-
computedGroupTickSizes?: number[],
22-
) => {
23-
const config = groups[groupIndex] ?? ({} as AxisGroup);
24-
25-
const defaultTickSize = tickSize ?? DEFAULT_GROUPING_CONFIG.tickSize;
26-
const calculatedTickSize = defaultTickSize * groupIndex * 2 + defaultTickSize;
27-
28-
return {
29-
...DEFAULT_GROUPING_CONFIG,
30-
...config,
31-
tickSize: computedGroupTickSizes?.[groupIndex] ?? config.tickSize ?? calculatedTickSize,
32-
};
33-
};
12+
import { getGroupingConfig } from '../internals/getGroupingConfig';
3413

3514
/**
3615
* @ignore - internal component.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import type { AxisGroup } from '../models/axis';
2+
3+
const DEFAULT_GROUPING_CONFIG = {
4+
tickSize: 6,
5+
};
6+
7+
export const getGroupingConfig = (
8+
groups: AxisGroup[],
9+
groupIndex: number,
10+
tickSize: number | undefined,
11+
computedGroupTickSizes?: number[],
12+
) => {
13+
const config = groups[groupIndex] ?? ({} as AxisGroup);
14+
15+
const defaultTickSize = tickSize ?? DEFAULT_GROUPING_CONFIG.tickSize;
16+
const calculatedTickSize = defaultTickSize * groupIndex * 2 + defaultTickSize;
17+
18+
return {
19+
...DEFAULT_GROUPING_CONFIG,
20+
...config,
21+
tickSize: computedGroupTickSizes?.[groupIndex] ?? config.tickSize ?? calculatedTickSize,
22+
};
23+
};

packages/x-date-pickers/src/DateCalendar/DayCalendar.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ function WrappedDay({
241241
currentMonthNumber: number;
242242
}) {
243243
const {
244+
currentMonth,
244245
disabled,
245246
disableHighlightToday,
246247
isMonthSwitchingAnimating,
@@ -294,20 +295,20 @@ function WrappedDay({
294295
});
295296

296297
const isFirstVisibleCell = React.useMemo(() => {
297-
const startOfMonth = adapter.startOfMonth(adapter.setMonth(day, currentMonthNumber));
298+
const startOfMonth = adapter.startOfMonth(currentMonth);
298299
if (!showDaysOutsideCurrentMonth) {
299300
return adapter.isSameDay(day, startOfMonth);
300301
}
301302
return adapter.isSameDay(day, adapter.startOfWeek(startOfMonth));
302-
}, [currentMonthNumber, day, showDaysOutsideCurrentMonth, adapter]);
303+
}, [currentMonth, day, showDaysOutsideCurrentMonth, adapter]);
303304

304305
const isLastVisibleCell = React.useMemo(() => {
305-
const endOfMonth = adapter.endOfMonth(adapter.setMonth(day, currentMonthNumber));
306+
const endOfMonth = adapter.endOfMonth(currentMonth);
306307
if (!showDaysOutsideCurrentMonth) {
307308
return adapter.isSameDay(day, endOfMonth);
308309
}
309310
return adapter.isSameDay(day, adapter.endOfWeek(endOfMonth));
310-
}, [currentMonthNumber, day, showDaysOutsideCurrentMonth, adapter]);
311+
}, [currentMonth, day, showDaysOutsideCurrentMonth, adapter]);
311312

312313
return (
313314
<Day

0 commit comments

Comments
 (0)