251 lines
7.4 KiB
JavaScript
251 lines
7.4 KiB
JavaScript
import { useCallback, useRef, useState } from 'react';
|
|
|
|
const DEFAULT_INITIAL_SELECTION = [];
|
|
|
|
export const useDocumentSelection = ({
|
|
resolveDocumentRowKey,
|
|
resolveFolderRowKey,
|
|
isDocumentRowKey,
|
|
isFolderRowKey,
|
|
getRowId,
|
|
initialSelection = DEFAULT_INITIAL_SELECTION,
|
|
}) => {
|
|
const [selectedRowKeys, setSelectedRowKeys] = useState(initialSelection);
|
|
const [selectionOrder, setSelectionOrder] = useState(initialSelection);
|
|
const selectionOrderRef = useRef(initialSelection);
|
|
const selectionAnchorRef = useRef(null);
|
|
const selectionInitializedRef = useRef(false);
|
|
const [focusedDocumentId, setFocusedDocumentId] = useState(null);
|
|
const [focusedRowKey, setFocusedRowKey] = useState(null);
|
|
|
|
const visibleRowKeySetRef = useRef(new Set());
|
|
const navigableRowKeysRef = useRef([]);
|
|
|
|
const configureSelectionEnvironment = useCallback(({
|
|
visibleRowKeySet,
|
|
navigableRowKeys,
|
|
}) => {
|
|
if (visibleRowKeySet) {
|
|
visibleRowKeySetRef.current = visibleRowKeySet;
|
|
}
|
|
if (Array.isArray(navigableRowKeys)) {
|
|
navigableRowKeysRef.current = navigableRowKeys;
|
|
}
|
|
}, []);
|
|
|
|
const updateSelectionOrder = useCallback((nextSelection, interactedKeys = []) => {
|
|
const nextSet = new Set(nextSelection);
|
|
const previousOrder = selectionOrderRef.current.filter((id) => nextSet.has(id));
|
|
const interacted = (interactedKeys || []).filter((id, index, array) => array.indexOf(id) === index);
|
|
|
|
const base = previousOrder.filter((id) => !interacted.includes(id));
|
|
const result = [...base];
|
|
|
|
interacted.forEach((id) => {
|
|
if (nextSet.has(id) && !result.includes(id)) {
|
|
result.push(id);
|
|
}
|
|
});
|
|
|
|
nextSelection.forEach((id) => {
|
|
if (!result.includes(id)) {
|
|
result.push(id);
|
|
}
|
|
});
|
|
|
|
if (
|
|
result.length !== selectionOrderRef.current.length
|
|
|| result.some((id, index) => selectionOrderRef.current[index] !== id)
|
|
) {
|
|
selectionOrderRef.current = result;
|
|
setSelectionOrder(result);
|
|
} else {
|
|
selectionOrderRef.current = result;
|
|
}
|
|
}, []);
|
|
|
|
const applySelection = useCallback(
|
|
(rowKeys, { anchor, interactedKeys = [] } = {}) => {
|
|
const visibleRowKeySet = visibleRowKeySetRef.current;
|
|
const unique = [];
|
|
|
|
(rowKeys || []).forEach((key) => {
|
|
if (!key) return;
|
|
let canonicalKey = null;
|
|
if (visibleRowKeySet.has(key)) {
|
|
canonicalKey = key;
|
|
} else if (isDocumentRowKey(key)) {
|
|
const id = getRowId(key);
|
|
canonicalKey = id ? resolveDocumentRowKey(id) : null;
|
|
} else if (isFolderRowKey(key)) {
|
|
const id = getRowId(key);
|
|
canonicalKey = id ? resolveFolderRowKey(id) : null;
|
|
}
|
|
|
|
if (!canonicalKey || !visibleRowKeySet.has(canonicalKey)) {
|
|
return;
|
|
}
|
|
|
|
if (!unique.includes(canonicalKey)) {
|
|
unique.push(canonicalKey);
|
|
}
|
|
});
|
|
|
|
let resolvedAnchor = anchor;
|
|
if (resolvedAnchor && !unique.includes(resolvedAnchor)) {
|
|
resolvedAnchor = null;
|
|
}
|
|
|
|
setSelectedRowKeys(unique);
|
|
updateSelectionOrder(unique, interactedKeys);
|
|
|
|
const nextFocusedDocumentId = (() => {
|
|
if (focusedDocumentId) {
|
|
const focusKey = resolveDocumentRowKey(focusedDocumentId);
|
|
if (focusKey && unique.includes(focusKey)) {
|
|
return focusedDocumentId;
|
|
}
|
|
}
|
|
|
|
if (resolvedAnchor && isDocumentRowKey(resolvedAnchor)) {
|
|
return getRowId(resolvedAnchor) || null;
|
|
}
|
|
|
|
const lastDocKey = [...unique].reverse().find(isDocumentRowKey);
|
|
return lastDocKey ? getRowId(lastDocKey) || null : null;
|
|
})();
|
|
|
|
setFocusedDocumentId(nextFocusedDocumentId);
|
|
|
|
if (resolvedAnchor) {
|
|
selectionAnchorRef.current = resolvedAnchor;
|
|
} else if (!unique.length) {
|
|
selectionAnchorRef.current = null;
|
|
} else if (!selectionAnchorRef.current || !unique.includes(selectionAnchorRef.current)) {
|
|
selectionAnchorRef.current = unique[unique.length - 1];
|
|
}
|
|
|
|
return { selection: unique, focusKey: selectionAnchorRef.current };
|
|
},
|
|
[
|
|
focusedDocumentId,
|
|
getRowId,
|
|
isDocumentRowKey,
|
|
isFolderRowKey,
|
|
resolveDocumentRowKey,
|
|
resolveFolderRowKey,
|
|
updateSelectionOrder,
|
|
],
|
|
);
|
|
|
|
const clearSelection = useCallback(() => {
|
|
setFocusedRowKey(null);
|
|
applySelection([], { anchor: null, interactedKeys: [] });
|
|
}, [applySelection]);
|
|
|
|
const handleRowSelection = useCallback(
|
|
(rowKey, event) => {
|
|
const visibleRowKeySet = visibleRowKeySetRef.current;
|
|
const navigableRowKeys = navigableRowKeysRef.current;
|
|
if (!rowKey || !visibleRowKeySet.has(rowKey)) {
|
|
return;
|
|
}
|
|
|
|
setFocusedRowKey(rowKey);
|
|
|
|
const shiftKey = Boolean(event?.shiftKey);
|
|
const metaKey = Boolean(event?.metaKey);
|
|
const ctrlKey = Boolean(event?.ctrlKey);
|
|
const additive = metaKey || ctrlKey;
|
|
|
|
if (shiftKey) {
|
|
event?.preventDefault?.();
|
|
}
|
|
|
|
let anchorKey = selectionAnchorRef.current;
|
|
if (!anchorKey && shiftKey && selectedRowKeys.length) {
|
|
anchorKey = selectedRowKeys[selectedRowKeys.length - 1];
|
|
}
|
|
if (!anchorKey) {
|
|
anchorKey = rowKey;
|
|
}
|
|
|
|
let nextKeys = [];
|
|
let interactedKeys = [];
|
|
|
|
if (shiftKey && anchorKey) {
|
|
const anchorIndex = navigableRowKeys.indexOf(anchorKey);
|
|
const targetIndex = navigableRowKeys.indexOf(rowKey);
|
|
if (anchorIndex !== -1 && targetIndex !== -1) {
|
|
const [start, end] = anchorIndex <= targetIndex
|
|
? [anchorIndex, targetIndex]
|
|
: [targetIndex, anchorIndex];
|
|
const range = navigableRowKeys.slice(start, end + 1);
|
|
nextKeys = range;
|
|
|
|
const previousSet = new Set(selectedRowKeys);
|
|
interactedKeys = range.filter((key) => key === rowKey || !previousSet.has(key));
|
|
if (!interactedKeys.includes(rowKey)) {
|
|
interactedKeys.push(rowKey);
|
|
}
|
|
} else {
|
|
nextKeys = [rowKey];
|
|
interactedKeys = [rowKey];
|
|
}
|
|
} else if (additive) {
|
|
if (selectedRowKeys.includes(rowKey)) {
|
|
nextKeys = selectedRowKeys.filter((key) => key !== rowKey);
|
|
interactedKeys = [];
|
|
} else {
|
|
nextKeys = [...selectedRowKeys, rowKey];
|
|
interactedKeys = [rowKey];
|
|
}
|
|
anchorKey = rowKey;
|
|
} else {
|
|
nextKeys = [rowKey];
|
|
interactedKeys = [rowKey];
|
|
anchorKey = rowKey;
|
|
}
|
|
|
|
applySelection(nextKeys, { anchor: anchorKey, interactedKeys });
|
|
},
|
|
[applySelection, selectedRowKeys],
|
|
);
|
|
|
|
const promoteSelectionOrder = useCallback(
|
|
(docId) => {
|
|
if (!docId) return;
|
|
const rowKey = resolveDocumentRowKey(docId);
|
|
if (!rowKey) return;
|
|
|
|
if (!selectedRowKeys.includes(rowKey)) {
|
|
return;
|
|
}
|
|
|
|
updateSelectionOrder(selectedRowKeys, [rowKey]);
|
|
},
|
|
[resolveDocumentRowKey, selectedRowKeys, updateSelectionOrder],
|
|
);
|
|
|
|
return {
|
|
selectedRowKeys,
|
|
setSelectedRowKeys,
|
|
selectionOrder,
|
|
setSelectionOrder,
|
|
selectionOrderRef,
|
|
selectionAnchorRef,
|
|
selectionInitializedRef,
|
|
focusedDocumentId,
|
|
setFocusedDocumentId,
|
|
focusedRowKey,
|
|
setFocusedRowKey,
|
|
applySelection,
|
|
clearSelection,
|
|
handleRowSelection,
|
|
promoteSelectionOrder,
|
|
configureSelectionEnvironment,
|
|
};
|
|
};
|
|
|
|
export default useDocumentSelection;
|