diff --git a/desktop/flipper-plugin/src/state/datasource/DataSource.tsx b/desktop/flipper-plugin/src/state/datasource/DataSource.tsx
index ca5a8d605..e2a954f4d 100644
--- a/desktop/flipper-plugin/src/state/datasource/DataSource.tsx
+++ b/desktop/flipper-plugin/src/state/datasource/DataSource.tsx
@@ -299,6 +299,7 @@ export class DataSource<
setReversed(reverse: boolean) {
if (this.reverse !== reverse) {
this.reverse = reverse;
+ // TODO: not needed anymore
this.rebuildOutput();
}
}
diff --git a/desktop/flipper-plugin/src/ui/datatable/ColumnFilter.tsx b/desktop/flipper-plugin/src/ui/datatable/ColumnFilter.tsx
index 282832df7..0e52fdf85 100644
--- a/desktop/flipper-plugin/src/ui/datatable/ColumnFilter.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/ColumnFilter.tsx
@@ -29,6 +29,7 @@ export type ColumnFilterHandlers = {
onAddColumnFilter(columnId: string, value: string): void;
onRemoveColumnFilter(columnId: string, index: number): void;
onToggleColumnFilter(columnId: string, index: number): void;
+ onSetColumnFilterFromSelection(columnId: string): void;
};
export function FilterIcon({
@@ -98,6 +99,37 @@ export function FilterIcon({
No active filters
)}
+
+
+
+
+
+
);
diff --git a/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx b/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx
index 272013e06..e6462d380 100644
--- a/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/DataTable.tsx
@@ -26,11 +26,7 @@ import {useDataTableManager, TableManager} from './useDataTableManager';
import {TableSearch} from './TableSearch';
import styled from '@emotion/styled';
import {theme} from '../theme';
-import {
- tableContextMenuFactory,
- TableContextMenuContext,
-} from './TableContextMenu';
-import {useMemoize} from '../../utils/useMemoize';
+import {tableContextMenuFactory} from './TableContextMenu';
interface DataTableProps {
columns: DataTableColumn[];
@@ -93,9 +89,6 @@ export function DataTable(
selectItem,
selection,
addRangeToSelection,
- addColumnFilter,
- getSelectedItem,
- getSelectedItems,
} = tableManager;
const renderingConfig = useMemo>(() => {
@@ -236,13 +229,8 @@ export function DataTable(
// TODO: support customizing context menu
const contexMenu = props._testHeight
? undefined // don't render context menu in tests
- : // eslint-disable-next-line
- useMemoize(tableContextMenuFactory, [
- visibleColumns,
- addColumnFilter,
- getSelectedItem,
- getSelectedItems as any,
- ]);
+ : tableContextMenuFactory(tableManager);
+
return (
@@ -250,34 +238,34 @@ export function DataTable(
-
- >
- dataSource={dataSource}
- autoScroll={props.autoScroll}
- useFixedRowHeight={!usesWrapping}
- defaultRowHeight={DEFAULT_ROW_HEIGHT}
- context={renderingConfig}
- itemRenderer={itemRenderer}
- onKeyDown={onKeyDown}
- virtualizerRef={virtualizerRef}
- onRangeChange={onRangeChange}
- _testHeight={props._testHeight}
- />
-
+ >
+ dataSource={dataSource}
+ autoScroll={props.autoScroll}
+ useFixedRowHeight={!usesWrapping}
+ defaultRowHeight={DEFAULT_ROW_HEIGHT}
+ context={renderingConfig}
+ itemRenderer={itemRenderer}
+ onKeyDown={onKeyDown}
+ virtualizerRef={virtualizerRef}
+ onRangeChange={onRangeChange}
+ _testHeight={props._testHeight}
+ />
{range && {range}}
diff --git a/desktop/flipper-plugin/src/ui/datatable/TableContextMenu.tsx b/desktop/flipper-plugin/src/ui/datatable/TableContextMenu.tsx
index f7b5a3e38..ca1aad6e8 100644
--- a/desktop/flipper-plugin/src/ui/datatable/TableContextMenu.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/TableContextMenu.tsx
@@ -8,26 +8,16 @@
*/
import {CopyOutlined, FilterOutlined} from '@ant-design/icons';
-import {Menu} from 'antd';
-import {DataTableColumn} from './DataTable';
+import {Checkbox, Menu} from 'antd';
import {TableManager} from './useDataTableManager';
import React from 'react';
-import {createContext} from 'react';
import {normalizeCellValue} from './TableRow';
import {tryGetFlipperLibImplementation} from '../../plugin/FlipperLib';
+import {DataTableColumn} from './DataTable';
const {Item, SubMenu} = Menu;
-export const TableContextMenuContext = createContext<
- React.ReactElement | undefined
->(undefined);
-
-export function tableContextMenuFactory(
- visibleColumns: DataTableColumn[],
- addColumnFilter: TableManager['addColumnFilter'],
- _getSelection: () => T,
- getMultiSelection: () => T[],
-) {
+export function tableContextMenuFactory(tableManager: TableManager) {
const lib = tryGetFlipperLibImplementation();
if (!lib) {
return (
@@ -36,34 +26,32 @@ export function tableContextMenuFactory(
);
}
+ const hasSelection = tableManager.selection?.items.size > 0 ?? false;
return (
);
}
+
+function friendlyColumnTitle(column: DataTableColumn): string {
+ const name = column.title || column.key;
+ return name[0].toUpperCase() + name.substr(1);
+}
diff --git a/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx b/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx
index 7a466dc49..8224185a0 100644
--- a/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/TableHead.tsx
@@ -20,23 +20,37 @@ import React from 'react';
import {theme} from '../theme';
import type {DataTableColumn} from './DataTable';
-import {Checkbox, Dropdown, Menu, Typography} from 'antd';
-import {CaretDownFilled, CaretUpFilled, DownOutlined} from '@ant-design/icons';
+import {Typography} from 'antd';
+import {CaretDownFilled, CaretUpFilled} from '@ant-design/icons';
import {Layout} from '../Layout';
-import {Sorting, OnColumnResize} from './useDataTableManager';
+import {Sorting, OnColumnResize, SortDirection} from './useDataTableManager';
import {ColumnFilterHandlers, FilterIcon, HeaderButton} from './ColumnFilter';
const {Text} = Typography;
-function SortIcons({direction}: {direction?: 'up' | 'down'}) {
+function SortIcons({
+ direction,
+ onSort,
+}: {
+ direction?: SortDirection;
+ onSort(direction: SortDirection): void;
+}) {
return (
{
+ e.stopPropagation();
+ onSort(direction === 'up' ? undefined : 'up');
+ }}
className={
'ant-table-column-sorter-up ' + (direction === 'up' ? 'active' : '')
}
/>
{
+ e.stopPropagation();
+ onSort(direction === 'down' ? undefined : 'down');
+ }}
className={
'ant-table-column-sorter-down ' +
(direction === 'down' ? 'active' : '')
@@ -55,25 +69,41 @@ const SortIconsContainer = styled.span<{direction?: 'up' | 'down'}>(
position: 'relative',
left: 4,
top: -3,
+ cursor: 'pointer',
color: theme.disabledColor,
+ '.ant-table-column-sorter-up:hover, .ant-table-column-sorter-down:hover': {
+ color: theme.primaryColor,
+ },
}),
);
-const SettingsButton = styled(HeaderButton)({
- position: 'absolute',
- right: 0,
- top: 0,
-});
-
const TableHeaderColumnInteractive = styled(Interactive)({
overflow: 'hidden',
whiteSpace: 'nowrap',
width: '100%',
+ borderRight: `1px solid ${theme.dividerColor}`,
+ paddingRight: 4,
});
TableHeaderColumnInteractive.displayName =
'TableHead:TableHeaderColumnInteractive';
-const TableHeadContainer = styled.div<{horizontallyScrollable?: boolean}>({
+const TableHeadColumnContainer = styled.div<{
+ width: Width;
+}>((props) => ({
+ flexShrink: props.width === undefined ? 1 : 0,
+ flexGrow: props.width === undefined ? 1 : 0,
+ width: props.width === undefined ? '100%' : props.width,
+ paddingLeft: 4,
+ [`:hover ${SortIconsContainer}`]: {
+ visibility: 'visible',
+ },
+ [`&:hover ${HeaderButton}`]: {
+ visibility: 'visible !important' as any,
+ },
+}));
+TableHeadColumnContainer.displayName = 'TableHead:TableHeadColumnContainer';
+
+const TableHeadContainer = styled.div({
position: 'relative',
display: 'flex',
flexDirection: 'row',
@@ -84,25 +114,6 @@ const TableHeadContainer = styled.div<{horizontallyScrollable?: boolean}>({
});
TableHeadContainer.displayName = 'TableHead:TableHeadContainer';
-const TableHeadColumnContainer = styled.div<{
- width: Width;
-}>((props) => ({
- flexShrink: props.width === undefined ? 1 : 0,
- flexGrow: props.width === undefined ? 1 : 0,
- width: props.width === undefined ? '100%' : props.width,
- '&:last-of-type': {
- marginRight: 20, // space for settings button
- },
- [`:hover ${SortIconsContainer}`]: {
- visibility: 'visible',
- },
- [`&:hover ${HeaderButton}`]: {
- visibility: 'visible !important' as any,
- },
- padding: '0 4px',
-}));
-TableHeadColumnContainer.displayName = 'TableHead:TableHeadColumnContainer';
-
const RIGHT_RESIZABLE = {right: true};
function TableHeadColumn({
@@ -116,7 +127,7 @@ function TableHeadColumn({
column: DataTableColumn;
sorted: 'up' | 'down' | undefined;
isResizable: boolean;
- onSort: (id: string) => void;
+ onSort: (id: string, direction: SortDirection) => void;
sortOrder: undefined | Sorting;
onColumnResize: OnColumnResize;
} & ColumnFilterHandlers) {
@@ -150,11 +161,23 @@ function TableHeadColumn({
};
let children = (
-
- onSort(column.key)} role="button" tabIndex={0}>
+
+ {
+ e.stopPropagation();
+ onSort(
+ column.key,
+ sorted === 'up' ? undefined : sorted === 'down' ? 'up' : 'down',
+ );
+ }}
+ role="button"
+ tabIndex={0}>
{column.title ?? <> >}
-
+ onSort(column.key, dir)}
+ />
@@ -181,40 +204,15 @@ function TableHeadColumn({
}
export const TableHead = memo(function TableHead({
- columns,
visibleColumns,
...props
}: {
- columns: DataTableColumn[];
visibleColumns: DataTableColumn[];
onColumnResize: OnColumnResize;
- onColumnToggleVisibility: (key: string) => void;
onReset: () => void;
sorting: Sorting | undefined;
- onColumnSort: (key: string) => void;
+ onColumnSort: (key: string, direction: SortDirection) => void;
} & ColumnFilterHandlers) {
- const menu = (
-
- );
-
return (
{visibleColumns.map((column, i) => (
@@ -228,6 +226,7 @@ export const TableHead = memo(function TableHead({
onAddColumnFilter={props.onAddColumnFilter}
onRemoveColumnFilter={props.onRemoveColumnFilter}
onToggleColumnFilter={props.onToggleColumnFilter}
+ onSetColumnFilterFromSelection={props.onSetColumnFilterFromSelection}
sorted={
props.sorting?.key === column.key
? props.sorting!.direction
@@ -235,11 +234,6 @@ export const TableHead = memo(function TableHead({
}
/>
))}
-
-
-
-
-
);
});
diff --git a/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx b/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx
index f97bcb143..e2b0dfc4b 100644
--- a/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/TableRow.tsx
@@ -7,15 +7,12 @@
* @format
*/
-import React, {memo, useContext} from 'react';
+import React, {memo} from 'react';
import styled from '@emotion/styled';
import {theme} from 'flipper-plugin';
import type {RenderContext} from './DataTable';
import {Width} from '../../utils/widthUtils';
import {pad} from 'lodash';
-import {DownCircleFilled} from '@ant-design/icons';
-import {Dropdown} from 'antd';
-import {TableContextMenuContext} from './TableContextMenu';
// heuristic for row estimation, should match any future styling updates
export const DEFAULT_ROW_HEIGHT = 24;
@@ -85,8 +82,6 @@ const TableBodyColumnContainer = styled.div<{
}));
TableBodyColumnContainer.displayName = 'TableRow:TableBodyColumnContainer';
-const contextMenuTriggers = ['click' as const, 'contextMenu' as const];
-
type Props = {
config: RenderContext;
highlighted: boolean;
@@ -96,7 +91,6 @@ type Props = {
export const TableRow = memo(function TableRow(props: Props) {
const {config, highlighted, value: row} = props;
- const menu = useContext(TableContextMenuContext);
return (
);
})}
- {menu && highlighted && (
-
-
-
-
-
- )}
);
});
-function stopPropagation(e: React.MouseEvent) {
- e.stopPropagation();
-}
-
export function normalizeCellValue(value: any): string {
switch (typeof value) {
case 'boolean':
diff --git a/desktop/flipper-plugin/src/ui/datatable/TableSearch.tsx b/desktop/flipper-plugin/src/ui/datatable/TableSearch.tsx
index 752af41a3..d03a1b746 100644
--- a/desktop/flipper-plugin/src/ui/datatable/TableSearch.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/TableSearch.tsx
@@ -7,7 +7,8 @@
* @format
*/
-import {Input} from 'antd';
+import {MenuOutlined} from '@ant-design/icons';
+import {Button, Dropdown, Input} from 'antd';
import React, {memo} from 'react';
import {Layout} from '../Layout';
import {theme} from '../theme';
@@ -15,9 +16,12 @@ import {theme} from '../theme';
export const TableSearch = memo(function TableSearch({
onSearch,
extraActions,
+ contextMenu,
}: {
onSearch(value: string): void;
extraActions?: React.ReactElement;
+ hasSelection?: boolean;
+ contextMenu?: React.ReactElement;
}) {
return (
{extraActions}
+ {contextMenu && (
+
+
+
+ )}
);
});
diff --git a/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx b/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx
index c01023189..a3e7cce84 100644
--- a/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTable.node.tsx
@@ -195,7 +195,7 @@ test('sorting', async () => {
}
// sort asc
act(() => {
- ref.current?.sortColumn('title');
+ ref.current?.sortColumn('title', 'down');
});
{
const elem = await rendering.findAllByText(/item/);
@@ -208,7 +208,7 @@ test('sorting', async () => {
}
// sort desc
act(() => {
- ref.current?.sortColumn('title');
+ ref.current?.sortColumn('title', 'up');
});
{
const elem = await rendering.findAllByText(/item/);
@@ -219,9 +219,9 @@ test('sorting', async () => {
'item a',
]);
}
- // another click resets again
+ // reset sort
act(() => {
- ref.current?.sortColumn('title');
+ ref.current?.sortColumn('title', undefined);
});
{
const elem = await rendering.findAllByText(/item/);
diff --git a/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTableManager.node.tsx b/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTableManager.node.tsx
index 1f7052c5a..792a10083 100644
--- a/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTableManager.node.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/__tests__/DataTableManager.node.tsx
@@ -46,6 +46,34 @@ test('computeSetSelection', () => {
current: 5,
items: new Set([2, 3, 8, 9, 5, 6, 7]), // n.b. order is irrelevant
});
+
+ // single item existing selection
+ expect(
+ computeSetSelection(
+ {
+ current: 4,
+ items: new Set([4]),
+ },
+ 5,
+ ),
+ ).toEqual({
+ current: 5,
+ items: new Set([5]),
+ });
+
+ // single item existing selection, toggle item off
+ expect(
+ computeSetSelection(
+ {
+ current: 4,
+ items: new Set([4]),
+ },
+ 4,
+ ),
+ ).toEqual({
+ current: -1,
+ items: new Set(),
+ });
});
test('computeAddRangeToSelection', () => {
@@ -79,7 +107,7 @@ test('computeAddRangeToSelection', () => {
// invest selection - toggle off
expect(computeAddRangeToSelection(partialBase, 8, 8, true)).toEqual({
- current: 8, // note: this item is not part of the selection!
+ current: 9, // select the next thing
items: new Set([2, 3, 9]),
});
diff --git a/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx b/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx
index ed9d31758..dc8d356d2 100644
--- a/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx
+++ b/desktop/flipper-plugin/src/ui/datatable/useDataTableManager.tsx
@@ -17,9 +17,11 @@ import {useMemoize} from '../../utils/useMemoize';
export type OnColumnResize = (id: string, size: number | Percentage) => void;
export type Sorting = {
key: string;
- direction: 'up' | 'down';
+ direction: Exclude;
};
+export type SortDirection = 'up' | 'down' | undefined;
+
export type TableManager = ReturnType;
type Selection = {items: ReadonlySet; current: number};
@@ -53,6 +55,69 @@ export function useDataTableManager(
[columns],
);
+ /**
+ * Select an individual item, used by mouse clicks and keyboard navigation
+ * Set addToSelection if the current selection should be expanded to the given position,
+ * rather than replacing the current selection.
+ *
+ * The nextIndex can be used to compute the new selection by basing relatively to the current selection
+ */
+ const selectItem = useCallback(
+ (
+ nextIndex: number | ((currentIndex: number) => number),
+ addToSelection?: boolean,
+ ) => {
+ setSelection((base) =>
+ computeSetSelection(base, nextIndex, addToSelection),
+ );
+ },
+ [],
+ );
+
+ /**
+ * Adds a range of items to the current seleciton (if any)
+ */
+ const addRangeToSelection = useCallback(
+ (start: number, end: number, allowUnselect?: boolean) => {
+ setSelection((base) =>
+ computeAddRangeToSelection(base, start, end, allowUnselect),
+ );
+ },
+ [],
+ );
+
+ // N.B: we really want to have stable refs for these functions,
+ // to avoid that all context menus need re-render for every selection change,
+ // hence the selectionRef hack
+ const getSelectedItem = useCallback(() => {
+ return selectionRef.current.current < 0
+ ? undefined
+ : dataSource.getItem(selectionRef.current.current);
+ }, [dataSource]);
+
+ const getSelectedItems = useCallback(() => {
+ return [...selectionRef.current.items]
+ .sort()
+ .map((i) => dataSource.getItem(i))
+ .filter(Boolean) as any[];
+ }, [dataSource]);
+
+ useEffect(
+ function fireSelection() {
+ if (onSelect) {
+ const item = getSelectedItem();
+ const items = getSelectedItems();
+ onSelect(item, items);
+ }
+ },
+ // selection is intentionally a dep
+ [onSelect, selection, selection, getSelectedItem, getSelectedItems],
+ );
+
+ /**
+ * Filtering
+ */
+
const addColumnFilter = useCallback(
(columnId: string, value: string, disableOthers = false) => {
// TODO: fix typings
@@ -102,6 +167,22 @@ export function useDataTableManager(
);
}, []);
+ const setColumnFilterFromSelection = useCallback(
+ (columnId: string) => {
+ const items = getSelectedItems();
+ if (items.length) {
+ items.forEach((item, index) => {
+ addColumnFilter(
+ columnId,
+ item[columnId],
+ index === 0, // remove existing filters before adding the first
+ );
+ });
+ }
+ },
+ [getSelectedItems, addColumnFilter],
+ );
+
// filter is computed by useMemo to support adding column filters etc here in the future
const currentFilter = useMemoize(
computeDataTableFilter,
@@ -127,23 +208,22 @@ export function useDataTableManager(
}, []);
const sortColumn = useCallback(
- (key: string) => {
- if (sorting?.key === key) {
- if (sorting.direction === 'down') {
- setSorting({key, direction: 'up'});
- dataSource.setReversed(true);
- } else {
- setSorting(undefined);
- dataSource.setSortBy(undefined);
- dataSource.setReversed(false);
- }
- } else {
- setSorting({
- key,
- direction: 'down',
- });
- dataSource.setSortBy(key as any);
+ (key: string, direction: SortDirection) => {
+ if (direction === undefined) {
+ // remove sorting
+ setSorting(undefined);
+ dataSource.setSortBy(undefined);
dataSource.setReversed(false);
+ } else {
+ // update sorting
+ // TODO: make sure that setting both doesn't rebuild output twice!
+ if (!sorting || sorting.key !== key) {
+ dataSource.setSortBy(key as any);
+ }
+ if (!sorting || sorting.direction !== direction) {
+ dataSource.setReversed(direction === 'up');
+ }
+ setSorting({key, direction});
}
},
[dataSource, sorting],
@@ -166,65 +246,6 @@ export function useDataTableManager(
[currentFilter, dataSource],
);
- /**
- * Select an individual item, used by mouse clicks and keyboard navigation
- * Set addToSelection if the current selection should be expanded to the given position,
- * rather than replacing the current selection.
- *
- * The nextIndex can be used to compute the new selection by basing relatively to the current selection
- */
- const selectItem = useCallback(
- (
- nextIndex: number | ((currentIndex: number) => number),
- addToSelection?: boolean,
- ) => {
- setSelection((base) =>
- computeSetSelection(base, nextIndex, addToSelection),
- );
- },
- [],
- );
-
- /**
- * Adds a range of items to the current seleciton (if any)
- */
- const addRangeToSelection = useCallback(
- (start: number, end: number, allowUnselect?: boolean) => {
- setSelection((base) =>
- computeAddRangeToSelection(base, start, end, allowUnselect),
- );
- },
- [],
- );
-
- // N.B: we really want to have stable refs for these functions,
- // to avoid that all context menus need re-render for every selection change,
- // hence the selectionRef hack
- const getSelectedItem = useCallback(() => {
- return selectionRef.current.current < 0
- ? undefined
- : dataSource.getItem(selectionRef.current.current);
- }, [dataSource]);
-
- const getSelectedItems = useCallback(() => {
- return [...selectionRef.current.items]
- .sort()
- .map((i) => dataSource.getItem(i))
- .filter(Boolean);
- }, [dataSource]);
-
- useEffect(
- function fireSelection() {
- if (onSelect) {
- const item = getSelectedItem();
- const items = getSelectedItems();
- onSelect(item, items);
- }
- },
- // selection is intentionally a dep
- [onSelect, selection, selection, getSelectedItem, getSelectedItems],
- );
-
return {
/** The default columns, but normalized */
columns,
@@ -252,6 +273,7 @@ export function useDataTableManager(
addColumnFilter,
removeColumnFilter,
toggleColumnFilter,
+ setColumnFilterFromSelection,
};
}
@@ -310,6 +332,10 @@ export function computeSetSelection(
): Selection {
const newIndex =
typeof nextIndex === 'number' ? nextIndex : nextIndex(base.current);
+ // special case: toggle existing selection off
+ if (!addToSelection && base.items.size === 1 && base.current === newIndex) {
+ return emptySelection;
+ }
if (newIndex < 0) {
return emptySelection;
}
@@ -334,17 +360,18 @@ export function computeAddRangeToSelection(
end: number,
allowUnselect?: boolean,
): Selection {
- // special case: unselectiong a single existing item
+ // special case: unselectiong a single item with the selection
if (start === end && allowUnselect) {
if (base?.items.has(start)) {
const copy = new Set(base.items);
copy.delete(start);
- if (copy.size === 0) {
+ const current = [...copy];
+ if (current.length === 0) {
return emptySelection;
}
return {
items: copy,
- current: start,
+ current: current[current.length - 1], // back to the last selected one
};
}
// intentional fall-through