Skip to content

Commit

Permalink
feat: add useFocusWithin (#1403)
Browse files Browse the repository at this point in the history
* feat: add useFocusWithin

* chore: useFocusWithin change onChange params name
  • Loading branch information
brickspert committed Dec 23, 2021
1 parent 98cd86b commit 2a0cc59
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 0 deletions.
1 change: 1 addition & 0 deletions config/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const menus = [
'useResponsive',
'useScroll',
'useSize',
'useFocusWithin',
],
},
{
Expand Down
2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import useEventListener from './useEventListener';
import useEventTarget from './useEventTarget';
import useExternal from './useExternal';
import useFavicon from './useFavicon';
import useFocusWithin from './useFocusWithin';
import useFullscreen from './useFullscreen';
import useFusionTable from './useFusionTable';
import useGetState from './useGetState';
Expand Down Expand Up @@ -142,4 +143,5 @@ export {
useInfiniteScroll,
useGetState,
clearCache,
useFocusWithin,
};
43 changes: 43 additions & 0 deletions packages/hooks/src/useFocusWithin/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* title: Basic usage
* desc: Use ref to set area that needs monitoring. The focus can be switched by click the outside with the mouse, or using keys such as `tab` on the keyboard.
*
* title.zh-CN: 基础用法
* desc.zh-CN: 使用 ref 设置需要监听的区域。可以通过鼠标点击外部区域,或者使用键盘的 `tab` 等按键来切换焦点。
*/

import React, { useRef } from 'react';
import { useFocusWithin } from 'ahooks';
import { message } from 'antd';

export default () => {
const ref = useRef();
const isFocusWithin = useFocusWithin(ref, {
onFocus: () => {
message.info('focus');
},
onBlur: () => {
message.info('blur');
},
});
return (
<div>
<div
ref={ref}
style={{
padding: 16,
backgroundColor: isFocusWithin ? 'red' : '',
border: '1px solid gray',
}}
>
<label style={{ display: 'block' }}>
First Name: <input />
</label>
<label style={{ display: 'block', marginTop: 16 }}>
Last Name: <input />
</label>
</div>
<p>isFocusWithin: {JSON.stringify(isFocusWithin)}</p>
</div>
);
};
35 changes: 35 additions & 0 deletions packages/hooks/src/useFocusWithin/demo/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* title: Pass in DOM element
* desc: Pass in a function that returns the DOM element.
*
* title.zh-CN: 传入 DOM 元素
* desc.zh-CN: 传入 function 并返回一个 dom 元素。
*/

import { useFocusWithin } from 'ahooks';
import React from 'react';

export default () => {
const isFocusWithin = useFocusWithin(() => document.getElementById('focus-area'));

return (
<div>
<div
id="focus-area"
style={{
padding: 16,
backgroundColor: isFocusWithin ? 'red' : '',
border: '1px solid gray',
}}
>
<label style={{ display: 'block' }}>
First Name: <input />
</label>
<label style={{ display: 'block', marginTop: 16 }}>
Last Name: <input />
</label>
</div>
<p>isFocusWithin: {JSON.stringify(isFocusWithin)}</p>
</div>
);
};
52 changes: 52 additions & 0 deletions packages/hooks/src/useFocusWithin/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
nav:
path: /hooks
---

# useFocusWithin

Monitor whether the current focus is within a certain area, Same as css attribute [:focus-within](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within).

## Examples

### Default usage

<code src="./demo/demo1.tsx" />

### Pass in DOM element

<code src="./demo/demo2.tsx" />

## API

```typescript
const isFocusWithin = useFocusWithin(
target,
{
onFocus,
onBlur,
onChange
}
);
```

### Params

| Property | Description | Type | Default |
|----------|--------------------|-------------------------------------------------------------|---------|
| target | DOM element or ref | `() => Element` \| `Element` \| `MutableRefObject<Element>` | - |
| options | More config | `Options` | - |

### Options

| Property | Description | Type | Default |
|----------|-----------------------------------------|------------------------------|---------|
| onFocus | Callback to be executed on focus | `(e: FocusEvent) => void` | - |
| onBlur | Callback to be executed on blur | `(e: FocusEvent) => void` | - |
| onChange | Callback to be executed on focus change | `(isFocusWithin: boolean) => void` | - |

### Result

| Property | Description | Type |
|---------------|------------------------------------------|-----------|
| isFocusWithin | Whether the focus is in the current area | `boolean` |
45 changes: 45 additions & 0 deletions packages/hooks/src/useFocusWithin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState } from 'react';
import useEventListener from '../useEventListener';
import type { BasicTarget } from '../utils/domTarget';

export interface Options {
onFocus?: (e: FocusEvent) => void;
onBlur?: (e: FocusEvent) => void;
onChange?: (isFocusWithin: boolean) => void;
}

export default function useFocusWithin(target: BasicTarget, options?: Options) {
const [isFocusWithin, setIsFocusWithin] = useState(false);
const { onFocus, onBlur, onChange } = options || {};

useEventListener(
'focusin',
(e: FocusEvent) => {
if (!isFocusWithin) {
onFocus?.(e);
onChange?.(true);
setIsFocusWithin(true);
}
},
{
target,
},
);

useEventListener(
'focusout',
(e: FocusEvent) => {
// @ts-ignore
if (isFocusWithin && !e.currentTarget?.contains?.(e.relatedTarget)) {
onBlur?.(e);
onChange?.(false);
setIsFocusWithin(false);
}
},
{
target,
},
);

return isFocusWithin;
}
52 changes: 52 additions & 0 deletions packages/hooks/src/useFocusWithin/index.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
nav:
path: /hooks
---

# useFocusWithin

监听当前焦点是否在某个区域之内,同 css 属性 [:focus-within](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within)

## 代码演示

### 基础用法

<code src="./demo/demo1.tsx" />

### 传入 DOM 元素

<code src="./demo/demo2.tsx" />

## API

```typescript
const isFocusWithin = useFocusWithin(
target,
{
onFocus,
onBlur,
onChange
}
);
```

### Params

| 参数 | 说明 | 类型 | 默认值 |
|---------|-----------------------|-------------------------------------------------------------|--------|
| target | DOM 节点或者 Ref 对象 | `() => Element` \| `Element` \| `MutableRefObject<Element>` | - |
| options | 额外的配置项 | `Options` | - |

### Options

| 参数 | 说明 | 类型 | 默认值 |
|----------|----------------|------------------------------|--------|
| onFocus | 获取焦点时触发 | `(e: FocusEvent) => void` | - |
| onBlur | 失去焦点时触发 | `(e: FocusEvent) => void` | - |
| onChange | 焦点变化时触发 | `(isFocusWithin: boolean) => void` | - |

### Result

| 参数 | 说明 | 类型 |
|---------------|--------------------|-----------|
| isFocusWithin | 焦点是否在当前区域 | `boolean` |

0 comments on commit 2a0cc59

Please sign in to comment.