enforce data order in list selection

This commit is contained in:
jeffvli
2025-11-14 13:00:21 -08:00
parent da691fa978
commit af00110973
2 changed files with 52 additions and 20 deletions
@@ -8,7 +8,7 @@ import {
* Action creators for item grid state management * Action creators for item grid state management
* These can be reused across different components and contexts * These can be reused across different components and contexts
*/ */
export const itemGridActions = { export const itemListActions = {
clearAll: (): ItemListAction => ({ clearAll: (): ItemListAction => ({
type: 'CLEAR_ALL', type: 'CLEAR_ALL',
}), }),
@@ -71,7 +71,7 @@ export const itemGridActions = {
* Selector functions for item grid state * Selector functions for item grid state
* These can be reused to extract specific data from state * These can be reused to extract specific data from state
*/ */
export const itemGridSelectors = { export const itemListSelectors = {
getDragging: (state: ItemListState): unknown[] => { getDragging: (state: ItemListState): unknown[] => {
return Array.from(state.draggingItems.values()); return Array.from(state.draggingItems.values());
}, },
@@ -179,8 +179,8 @@ export const itemListUtils = {
return rowId ? currentState.expanded.has(rowId) : false; return rowId ? currentState.expanded.has(rowId) : false;
}); });
return allExpanded return allExpanded
? itemGridActions.clearExpanded() ? itemListActions.clearExpanded()
: itemGridActions.setExpanded(items, extractRowId); : itemListActions.setExpanded(items, extractRowId);
}, },
/** /**
@@ -196,7 +196,7 @@ export const itemListUtils = {
return rowId ? currentState.selected.has(rowId) : false; return rowId ? currentState.selected.has(rowId) : false;
}); });
return allSelected return allSelected
? itemGridActions.clearSelected() ? itemListActions.clearSelected()
: itemGridActions.setSelected(items, extractRowId); : itemListActions.setSelected(items, extractRowId);
}, },
}; };
@@ -1,8 +1,36 @@
import { useCallback, useMemo, useReducer } from 'react'; import { useCallback, useMemo, useReducer } from 'react';
import { itemGridSelectors } from '/@/renderer/components/item-list/helpers/item-list-reducer-utils'; import { itemListSelectors } from '/@/renderer/components/item-list/helpers/item-list-reducer-utils';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
const sortByDataOrder = <T>(
items: T[],
data: unknown[],
extractRowId: (item: unknown) => string | undefined,
isIdArray: boolean,
): T[] => {
const rowIdToIndex = new Map<string, number>();
// Create a map of rowId to index in the data array
data.forEach((item, index) => {
if (item && typeof item === 'object') {
const itemRowId = extractRowId(item);
if (itemRowId) {
rowIdToIndex.set(itemRowId, index);
}
}
});
// Sort items by their index in the data array (create new array to avoid mutation)
return [...items].sort((a, b) => {
const rowIdA = isIdArray ? (a as string) : extractRowId(a as unknown);
const rowIdB = isIdArray ? (b as string) : extractRowId(b as unknown);
const indexA = rowIdA ? (rowIdToIndex.get(rowIdA) ?? Infinity) : Infinity;
const indexB = rowIdB ? (rowIdToIndex.get(rowIdB) ?? Infinity) : Infinity;
return indexA - indexB;
});
};
export type ItemListAction = export type ItemListAction =
| { | {
extractRowId: (item: unknown) => string | undefined; extractRowId: (item: unknown) => string | undefined;
@@ -330,29 +358,31 @@ export const useItemListState = (
const isExpanded = useCallback( const isExpanded = useCallback(
(rowId: string) => { (rowId: string) => {
return itemGridSelectors.isExpanded(state, rowId); return itemListSelectors.isExpanded(state, rowId);
}, },
[state], [state],
); );
const isSelected = useCallback( const isSelected = useCallback(
(rowId: string) => { (rowId: string) => {
return itemGridSelectors.isSelected(state, rowId); return itemListSelectors.isSelected(state, rowId);
}, },
[state], [state],
); );
const getExpanded = useCallback(() => { const getExpanded = useCallback(() => {
return itemGridSelectors.getExpanded(state); return itemListSelectors.getExpanded(state);
}, [state]); }, [state]);
const getDragging = useCallback(() => { const getDragging = useCallback(() => {
return itemGridSelectors.getDragging(state); return itemListSelectors.getDragging(state);
}, [state]); }, [state]);
const getSelected = useCallback(() => { const getSelected = useCallback(() => {
return itemGridSelectors.getSelected(state); const selectedItems = itemListSelectors.getSelected(state);
}, [state]); const data = getDataFn ? getDataFn() : [];
return sortByDataOrder(selectedItems, data, extractRowIdFn, false);
}, [state, getDataFn, extractRowIdFn]);
const getDraggingIds = useCallback(() => { const getDraggingIds = useCallback(() => {
return Array.from(state.dragging); return Array.from(state.dragging);
@@ -363,8 +393,10 @@ export const useItemListState = (
}, [state.expanded]); }, [state.expanded]);
const getSelectedIds = useCallback(() => { const getSelectedIds = useCallback(() => {
return Array.from(state.selected); const selectedIds = Array.from(state.selected);
}, [state.selected]); const data = getDataFn ? getDataFn() : [];
return sortByDataOrder(selectedIds, data, extractRowIdFn, true);
}, [state.selected, getDataFn, extractRowIdFn]);
const clearExpanded = useCallback(() => { const clearExpanded = useCallback(() => {
dispatch({ type: 'CLEAR_EXPANDED' }); dispatch({ type: 'CLEAR_EXPANDED' });
@@ -383,24 +415,24 @@ export const useItemListState = (
}, []); }, []);
const getVersion = useCallback(() => { const getVersion = useCallback(() => {
return itemGridSelectors.getVersion(state); return itemListSelectors.getVersion(state);
}, [state]); }, [state]);
const hasExpanded = useCallback(() => { const hasExpanded = useCallback(() => {
return itemGridSelectors.hasAnyExpanded(state); return itemListSelectors.hasAnyExpanded(state);
}, [state]); }, [state]);
const hasDragging = useCallback(() => { const hasDragging = useCallback(() => {
return itemGridSelectors.hasAnyDragging(state); return itemListSelectors.hasAnyDragging(state);
}, [state]); }, [state]);
const hasSelected = useCallback(() => { const hasSelected = useCallback(() => {
return itemGridSelectors.hasAnySelected(state); return itemListSelectors.hasAnySelected(state);
}, [state]); }, [state]);
const isDragging = useCallback( const isDragging = useCallback(
(rowId: string) => { (rowId: string) => {
return itemGridSelectors.isDragging(state, rowId); return itemListSelectors.isDragging(state, rowId);
}, },
[state], [state],
); );