Skip to content

Commit

Permalink
Merge branch 'master' into fix_useClickAway_dynamic_dom
Browse files Browse the repository at this point in the history
  • Loading branch information
liuyib authored May 6, 2024
2 parents d3fb08d + 6bec34c commit ab95053
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 23 deletions.
6 changes: 5 additions & 1 deletion packages/hooks/src/createUseStorageState/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ export function createUseStorageState(getStorage: () => Storage | undefined) {

const updateState = (value?: SetState<T>) => {
const currentState = isFunction(value) ? value(state) : value;
setState(currentState);

if (!listenStorageChange) {
setState(currentState);
}

try {
let newValue: string | null;
Expand Down Expand Up @@ -126,5 +129,6 @@ export function createUseStorageState(getStorage: () => Storage | undefined) {

return [state, useMemoizedFn(updateState)] as const;
}

return useStorageState;
}
21 changes: 21 additions & 0 deletions packages/hooks/src/useDynamicList/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import useDynamicList from '../index';

describe('useDynamicList', () => {
const setUp = (props: any): any => renderHook(() => useDynamicList(props));
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

afterEach(() => {
warnSpy.mockReset();
});

afterAll(() => {
warnSpy.mockRestore();
});

it('getKey should work', () => {
const hook = setUp([1, 2, 3]);
Expand Down Expand Up @@ -97,6 +106,18 @@ describe('useDynamicList', () => {
hook.result.current.remove(7);
});
expect(hook.result.current.list.length).toBe(7);

// batch remove
act(() => {
hook.result.current.batchRemove(1);
});
expect(warnSpy).toHaveBeenCalledWith(
'`indexes` parameter of `batchRemove` function expected to be an array, but got "number".',
);
act(() => {
hook.result.current.batchRemove([0, 1, 2]);
});
expect(hook.result.current.list.length).toBe(4);
});

it('same items should have different keys', () => {
Expand Down
22 changes: 20 additions & 2 deletions packages/hooks/src/useDynamicList/demo/demo1.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@

import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { useDynamicList } from 'ahooks';
import { Input } from 'antd';
import { Button, Input, Space } from 'antd';
import React from 'react';

export default () => {
const { list, remove, getKey, insert, replace } = useDynamicList(['David', 'Jack']);
const { list, remove, batchRemove, getKey, insert, replace } = useDynamicList(['David', 'Jack']);
const listIndexes = list.map((item, index) => index);

const Row = (index: number, item: any) => (
<div key={getKey(index)} style={{ marginBottom: 16 }}>
Expand Down Expand Up @@ -44,6 +45,23 @@ export default () => {
<>
{list.map((ele, index) => Row(index, ele))}

<Space style={{ marginBottom: 16 }}>
<Button
danger
disabled={list.length <= 1}
onClick={() => batchRemove(listIndexes.filter((index) => index % 2 === 0))}
>
Remove odd items
</Button>
<Button
danger
disabled={list.length <= 1}
onClick={() => batchRemove(listIndexes.filter((index) => index % 2 !== 0))}
>
Remove even items
</Button>
</Space>

<div>{JSON.stringify([list])}</div>
</>
);
Expand Down
33 changes: 33 additions & 0 deletions packages/hooks/src/useDynamicList/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback, useRef, useState } from 'react';
import isDev from '../utils/isDev';

const useDynamicList = <T>(initialList: T[] = []) => {
const counterRef = useRef(-1);
Expand Down Expand Up @@ -77,6 +78,37 @@ const useDynamicList = <T>(initialList: T[] = []) => {
});
}, []);

const batchRemove = useCallback((indexes: number[]) => {
if (!Array.isArray(indexes)) {
if (isDev) {
console.error(
`\`indexes\` parameter of \`batchRemove\` function expected to be an array, but got "${typeof indexes}".`,
);
}
return;
}
if (!indexes.length) {
return;
}

setList((prevList) => {
const newKeyList: number[] = [];
const newList = prevList.filter((item, index) => {
const shouldKeep = !indexes.includes(index);

if (shouldKeep) {
newKeyList.push(getKey(index));
}

return shouldKeep;
});

keyList.current = newKeyList;

return newList;
});
}, []);

const move = useCallback((oldIndex: number, newIndex: number) => {
if (oldIndex === newIndex) {
return;
Expand Down Expand Up @@ -150,6 +182,7 @@ const useDynamicList = <T>(initialList: T[] = []) => {
merge,
replace,
remove,
batchRemove,
getKey,
getIndex,
move,
Expand Down
7 changes: 2 additions & 5 deletions packages/hooks/src/useLocalStorageState/demo/demo4.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,12 @@ function Counter() {
listenStorageChange: true,
});

const add = () => setCount(count! + 1);
const clear = () => setCount();

return (
<div style={{ marginBottom: '8px' }}>
<button style={{ marginRight: '8px' }} onClick={add}>
<button style={{ marginRight: '8px' }} onClick={() => setCount(count! + 1)}>
count: {count}
</button>
<button onClick={clear}>Clear</button>
<button onClick={() => setCount()}>Clear</button>
</div>
);
}
42 changes: 42 additions & 0 deletions packages/hooks/src/useSelections/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { act, renderHook } from '@testing-library/react';
import { useState } from 'react';
import useSelections from '../index';
import type { Options } from '../index';

Expand Down Expand Up @@ -193,4 +194,45 @@ describe('useSelections', () => {
expect(result.current.selected).toEqual(_selected);
expect(result.current.isSelected(_selectedItem)).toBe(true);
});

it('clearAll should work correct', async () => {
const runCase = (data, newData, remainData) => {
const { result } = renderHook(() => {
const [list, setList] = useState(data);
const hook = useSelections(list, {
itemKey: 'id',
});

return { setList, hook };
});
const { setSelected, unSelectAll, clearAll } = result.current.hook;

act(() => {
setSelected(data);
});
expect(result.current.hook.selected).toEqual(data);
expect(result.current.hook.allSelected).toBe(true);

act(() => {
result.current.setList(newData);
});
expect(result.current.hook.allSelected).toBe(false);

act(() => {
unSelectAll();
});
expect(result.current.hook.selected).toEqual(remainData);

act(() => {
clearAll();
});
expect(result.current.hook.selected).toEqual([]);
expect(result.current.hook.allSelected).toEqual(false);
expect(result.current.hook.noneSelected).toBe(true);
expect(result.current.hook.partiallySelected).toBe(false);
};

runCase(_data, [3, 4, 5], [1, 2]);
runCase(_dataObj, [{ id: 3 }, { id: 4 }, { id: 5 }], [{ id: 1 }, { id: 2 }]);
});
});
11 changes: 10 additions & 1 deletion packages/hooks/src/useSelections/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ const result: Result = useSelections<T>(items: T[], options?: Options<T>);
const result: Result = useSelections<T>(items: T[], defaultSelected?: T[]);
```

### Params

<!-- prettier-ignore -->
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| items | Data items | `T[]` | - |
| options | Optional configuration | `Options` | - |

### Options

<!-- prettier-ignore -->
Expand All @@ -48,7 +56,7 @@ const result: Result = useSelections<T>(items: T[], defaultSelected?: T[]);

| Property | Description | Type |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| selected | Selected Items | `T[]` |
| selected | Selected items | `T[]` |
| allSelected | Is all items selected | `boolean` |
| noneSelected | Is no item selected | `boolean` |
| partiallySelected | Is partially items selected | `boolean` |
Expand All @@ -60,3 +68,4 @@ const result: Result = useSelections<T>(items: T[], defaultSelected?: T[]);
| selectAll | Select all items | `() => void` |
| unSelectAll | UnSelect all items | `() => void` |
| toggleAll | Toggle select all items | `() => void` |
| clearAll | Clear all selected (In general, `clearAll` is equivalent to `unSelectAll`. If the items is dynamic, `clearAll` will clear "all selected data", while `unSelectAll` will only clear "the currently selected data in the items") | `() => void` |
6 changes: 6 additions & 0 deletions packages/hooks/src/useSelections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export default function useSelections<T>(items: T[], options?: T[] | Options<T>)

const toggleAll = () => (allSelected ? unSelectAll() : selectAll());

const clearAll = () => {
selectedMap.clear();
setSelected([]);
};

return {
selected,
noneSelected,
Expand All @@ -106,6 +111,7 @@ export default function useSelections<T>(items: T[], options?: T[] | Options<T>)
toggle: useMemoizedFn(toggle),
selectAll: useMemoizedFn(selectAll),
unSelectAll: useMemoizedFn(unSelectAll),
clearAll: useMemoizedFn(clearAll),
toggleAll: useMemoizedFn(toggleAll),
} as const;
}
37 changes: 23 additions & 14 deletions packages/hooks/src/useSelections/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ const result: Result = useSelections<T>(items: T[], options?: Options<T>);
const result: Result = useSelections<T>(items: T[], defaultSelected?: T[]);
```

### Params

<!-- prettier-ignore -->
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| items | 元素列表 | `T[]` | - |
| options | 可选配置项 | `Options` | - |

### Options

<!-- prettier-ignore -->
Expand All @@ -46,17 +54,18 @@ const result: Result = useSelections<T>(items: T[], defaultSelected?: T[]);

### Result

| 参数 | 说明 | 类型 |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| selected | 已经选择的元素 | `T[]` |
| allSelected | 是否全选 | `boolean` |
| noneSelected | 是否一个都没有选择 | `boolean` |
| partiallySelected | 是否半选 | `boolean` |
| isSelected | 是否被选择 | `(value: T) => boolean` |
| setSelected | 选择多个元素。多次执行时,后面的返回值会覆盖前面的,因此如果希望合并多次操作的结果,需要手动处理:`setSelected((oldArray) => oldArray.concat(newArray))` | `(value: T[]) => void \| (value: (prevState: T[]) => T[]) => void` |
| select | 选择单个元素 | `(value: T) => void` |
| unSelect | 取消选择单个元素 | `(value: T) => void` |
| toggle | 反选单个元素 | `(value: T) => void` |
| selectAll | 选择全部元素 | `() => void` |
| unSelectAll | 取消选择全部元素 | `() => void` |
| toggleAll | 反选全部元素 | `() => void` |
| 参数 | 说明 | 类型 |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| selected | 已经选择的元素 | `T[]` |
| allSelected | 是否全选 | `boolean` |
| noneSelected | 是否一个都没有选择 | `boolean` |
| partiallySelected | 是否半选 | `boolean` |
| isSelected | 是否被选择 | `(value: T) => boolean` |
| setSelected | 选择多个元素。多次执行时,后面的返回值会覆盖前面的,因此如果希望合并多次操作的结果,需要手动处理:`setSelected((oldArray) => oldArray.concat(newArray))` | `(value: T[]) => void \| (value: (prevState: T[]) => T[]) => void` |
| select | 选择单个元素 | `(value: T) => void` |
| unSelect | 取消选择单个元素 | `(value: T) => void` |
| toggle | 反选单个元素 | `(value: T) => void` |
| selectAll | 选择全部元素 | `() => void` |
| unSelectAll | 取消选择全部元素 | `() => void` |
| toggleAll | 反选全部元素 | `() => void` |
| clearAll | 清除所有选中元素(一般情况下,`clearAll` 等价于 `unSelectAll`。如果元素列表是动态的,则 `clearAll` 会清除掉“所有选中过的元素”,而 `unSelectAll` 只会清除掉“当前元素列表里选中的元素”) | `() => void` |
1 change: 1 addition & 0 deletions packages/hooks/src/utils/getDocumentOrShadow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const checkIfAllInShadow = (targets: BasicTarget[]): boolean => {
const targetElement = getTargetElement(item);
if (!targetElement) return false;
if (targetElement.getRootNode() instanceof ShadowRoot) return true;
return false;
});
};

Expand Down

0 comments on commit ab95053

Please sign in to comment.