open-index/open-npm huggingface dataset:
npm package: @kcuf-hook/use-click-away
Description: React hook - useClickAway.
Author: Jianchun Wang
Latest version: 0.0.3

# @kcuf-hook/use-click-away

## Installation

```shell
npm install @kcuf-hook/use-click-away


## Usage

```tsx
import {
  ReactElement
} from 'react';

import useClickAway from '@kcuf-hook/use-click-away';

export default function SomeComponent(): ReactElement {
  const refClickAway = useClickAway(() => {
    console.info('clicked away');
  });
  
  return <div ref={refClickAway} />;
}
```
npm package: @kcuf-hook/use-controllable
Description: React hook - useControllable.
Author: Jianchun Wang
Latest version: 0.2.0

# @kcuf-hook/use-controllable

「可受控」组件 HOOK，跟值及回调在组件的 props 中的名称没有关系。

```ts
import useControllable, {
  useControllableSoftTrim,
  useControllableUnprotected
} from '@kcuf-hook/use-controllable';
```

* `useControllable` 受控组件 hook，适用任何类型，一般不会得到 `undefined` 值
* `useControllableSoftTrim` 受控且软 trim 组件 hook，仅针对 `string` 类型
* `useControllableSoftTrim` 受控组件 hook，适用任何类型，得到的值有可能是 `undefined`

## Why

往往我们在实现受控组件的时候，需要考虑的量有：

* `state.xx`
* `props.xx`
* `props.defaulXx`
* `props.onXxChange`

Hook `useControllable` 可以把这些变数进行优雅整合，避免写可受控组件的场景下遇到的不可受控（比如 `props` 变化未反应到界面的问题）和代码冗余问题。

注意：Hook `useControllable` 跟需要受控的值在 props 中的名称无关，也可以实现 `visible` 的受控：

* `state.visible`
* `props.visible`
* `props.defaulVisible`
* `props.onVisibleChange`

另外，对于字符串类型的输入，在输入的时候直接 `trim` 是非常不好的体验，因为那会让用户输入不了任何形式的空白字符，所以这里提供了 `useControllableSoftTrim` 来解决此类问题。

## 利用 `useControllable` 实现一个「可受控」Input 组件

```tsx
import {
  InputHTMLAttributes,
  ChangeEvent,
  ReactElement,
  useCallback
} from 'react';

import useControllable from '@kcuf-hook/use-controllable';

interface IInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange'> {
  value?: string;
  defaultValue?: string;
  onChange?(value: string, e: ChangeEvent<HTMLInputElement>): void;
}

function Input({
  value,
  defaultValue,
  onChange,
  ...props
}: IInputProps): ReactElement {
  const [controllableValue, setControllableValue] = useControllable('FINAL_DEFAULT', value, defaultValue, onChange);
  const handleOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    setControllableValue(e.target.value, e);
  }, [setControllableValue]);
  
  return <input {...{
    ...props,
    type: 'text',
    value: controllableValue,
    onChange: handleOnChange
  }} />;
}
```

## 利用 `useControllableSoftTrim` 实现软 trim

```tsx
import {
  InputHTMLAttributes,
  ChangeEvent,
  ReactElement,
  useCallback
} from 'react';

import {
  useControllableSoftTrim
} from '@kcuf-hook/use-controllable';

interface IInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange'> {
  trim?: boolean;
  value?: string;
  defaultValue?: string;
  onChange?(value: string, e: ChangeEvent<HTMLInputElement>): void;
}

function Input({
  trim,
  value,
  defaultValue,
  onChange,
  ...props
}: IInputProps): ReactElement {
  const [controllableValue, setControllableValue] = useControllableSoftTrim(trim, value, defaultValue, onChange);
  const handleOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    setControllableValue(e.target.value, e);
  }, [setControllableValue]);
  
  return <input {...{
    ...props,
    type: 'text',
    value: controllableValue,
    onChange: handleOnChange
  }} />;
}
```

这样，在敲空格的时候，是不会触发 `onChange` 的。
npm package: @kcuf-hook/use-fullscreen
Description: React hook - useFullscreen/useFullscreenRef.
Author: Jianchun Wang
Latest version: 0.1.1

# @kcuf-hook/use-fullscreen

A hook to use [Fullscreen API](https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API) in an easy way.

## API

* `useFullscreen(target?: HTMLElement): UseFullscreenResult`
* `useFullscreenRef(): UseFullscreenRefResult`

## Types

* `UseFullscreenResult`
* `UseFullscreenRefResult`

## Examples

### useFullscreen()

If you want fullscreen the full document, use `useFullscreen` without parameter.

```tsx
import {
  ReactElement
} from 'react';

import useFullscreen from '@kcuf-hook/use-fullscreen';

export default function SomeComponent(): ReactElement {
  const {
    fullscreen,
    toggle
  } = useFullscreen();
  
  return <>
    <button onClick={toggle}>{fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}</button>
    <div>...</div>
  </>;
}
```

### useFullscreen(element)

If you want fullscreen part of the UI, but the DOM ref is needed somewhere, or you want to put ref and the hook in different modules, use `useFullscreen`.

```tsx
import {
  ReactElement
} from 'react';

import useFullscreen from '@kcuf-hook/use-fullscreen';

export default function SomeComponent(): ReactElement {
  const domElement = useDomElement(); // a custom hook to get the real DOM element from another module
  const {
    fullscreen,
    toggle
  } = useFullscreen(domElement);
  
  return <button onClick={toggle}>{fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}</button>;
}
```

### useFullscreenRef()

If you want fullscreen part of the UI, and are putting all code inside one module, and the target has no other ref purpose, use `useFullscreenRef`.

```tsx
import {
  ReactElement
} from 'react';

import {
  useFullscreenRef
} from '@kcuf-hook/use-fullscreen';

export default function SomeComponent(): ReactElement {
  const [ref, {
    fullscreen,
    toggle
  }] = useFullscreenRef();
  
  return <>
    <div ref={ref}>...</div>
    <button onClick={toggle}>{fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}</button>
  </>;
}
```
npm package: @kcuf-hook/use-is-unmounted
Description: React hook - useIsUnmounted.
Author: Jianchun Wang
Latest version: 0.0.1

# @kcuf-hook/use-is-unmounted

## Usage

```tsx
import {
  ReactElement,
  useState,
  useEffect,
  useCallback
} from 'react';

// i would recommend placing all your data calls in a folder, one api per file
import dataSome, { // dataSomeList is some async method returning Promise<IDataSome>
  IDataSome
} from ':/data/some';
import dataOther, { // dataSomeList is some async method returning Promise<IDataSome>
  IDataOther
} from ':/data/other';

import useIsUnmounted from '@kcuf-hook/use-is-unmounted';

export default function SomeComponent(): ReactElement {
  const isUnmounted = useIsUnmounted();
  const [stateDataSome, setStateDataSome] = useState<IDataSome | null>(null);
  const [stateErrorSome, setStateErrorSome] = useState<Error | null>(null);
  const [stateDataOther, setStateDataOther] = useState<IDataOther | null>(null);
  const [stateErrorOther, setStateErrorOther] = useState<Error | null>(null);
  
  // use in effect directly
  useEffect(() => {
    dataSome().then(data => {
      if (isUnmounted()) {
        return;
      }
      
      setStateDataSome(data);
    }, (err: Error) => {
      if (isUnmounted()) {
        return;
      }
      
      setStateErrorSome(err);
    });
  }, [isUnmounted]);
  // or use as a callback
  const handleGetOther = useCallback(() => {
    dataOther().then(data => {
      if (isUnmounted()) {
        return;
      }
      
      setStateDataOther(data);
    }, (err: Error) => {
      if (isUnmounted()) {
        return;
      }
      
      setStateErrorOther(err);
    });
  }, [isUnmounted]);
  
  return stateDataSome || stateErrorSome ? <div>
    <ResultSome data={stateDataSome} error={stateErrorSome} />
    {stateDataOther || stateErrorOther ? <ResultOther data={stateDataOther} error={stateErrorOther} /> : null}
    <Button onClick={handleGetOther}>get other</Button>
  </div> : <Loading />;
};
```

## 参考

* [use-async-effect]
* [react-is-mounted-hook]

### 为什么不用 [use-async-effect]

[use-async-effect] 仅适用于 effect，且每个 effect 都会去创建对应的逻辑。这是我之前的选择，但当我期望创建一个事件处理函数，在事件处理函数中做异步操作的时候就发现完全不行。

### 为什么不用 [react-is-mounted-hook]

嗯，我一开始是用它，版本 `1.0.3`，但它连最基本的 `useCallback` 都没用，而且它的例子中 `useEffect` 的 `deps` 并没有把 `isMounted` 列入，而我在写代码的时候列入了，结果陷入了死循环。
虽然它有个 [PR](https://github.com/hupe1980/react-is-mounted-hook/pull/1) 在进行中，就是修复这个问题的，不过我等不了了。

### 为什么偏偏叫 isUnmounted 而不是 isMounted

个人觉得是这个 `hook` 着重点是判断组件被「卸载」，所以.. 而且这样有助于编写符合简单条件优先 `return` 原则的代码（如以上的例子）。

[use-async-effect]: https://www.npmjs.com/package/use-async-effect
[react-is-mounted-hook]: https://www.npmjs.com/package/react-is-mounted-hook
npm package: @kcuf-hook/use-route-query
Description: React hook - useRouteQuery.
Author: Jianchun Wang
Latest version: 0.2.0

# @kcuf-hook/use-route-query

将页面或组件所需状态记录在 URL 参数 `_` 上（默认为 `_`，可自定义）。

## Features

* 参数修改，不会影响到 `SearchParams` 里的其他参数
* 可自定义 `key`，这样，页面上可以有多个组件同时修改自己的参数，而不会相互影响
* 使用 JSON5 + Base64 进行编解码，是参数更小且保证类型正确
* 默认参数不会被写入 URL，进一步精简 URL 参数

## How to Use

第一步：定义 `useQuery`，给定类型和默认值：

```ts
import useRouteQuery, {
  UseRouteQueryResult
} from '@kcuf-hook/use-route-query';

interface IQuery {
  p: number;
  ps: number;
  q: string;
}

const DEFAULTS: IQuery = {
  p: 1,
  ps: 10,
  q: ''
};

export default function useQuery(): UseRouteQueryResult<IQuery> {
  return useRouteQuery(DEFAULTS);
}
```

第二步：使用自己的 `useQuery`：

```tsx
import useQuery from 'path/to/use-query'

function ThePagination(): UseRouteQueryResult<IQuery> {
  const [{
    p
  }, updateQuery] = useQuery();
  
  return <Pagination {...{
    // ...
    page: p,
    pageSize: ps,
    onChange: (page, pageSize) => updateQuery({
      p: page,
      ps: pageSize
    })
  }} />;
}

function TheSearch(): UseRouteQueryResult<IQuery> {
  const [{
    q
  }, updateQuery] = useQuery();
  
  return <Search {...{
    defaultValue: q,
    onChange: keyword => updateQuery({
      q: keyword,
      p: 1
    }) // 默认在当前路由下，若期望跳到新的路由，可以带上第二个参数 `pathname`
  }} />;
}
```
npm package: @kcuf-ui/error-boundary
Description: The kcuf-ui ErrorBoundary component.
Author: Jianchun Wang
Latest version: 0.2.0

# @kcuf-ui/error-boundary

An `ErrorBoundary` component to display detailed error info for developers.

## Props

* `children: ReactNode;`
* `fallback?: ReactNode | ((error: unknown, errorInfo?: ErrorInfo) => ReactNode);`
* `resetSignal?: unknown;`
* `onErrorCaught?(error: unknown, errorInfo: ErrorInfo): void;`

## FAQ

### When wrapping react-router component, the route won't refresh after one has failed, even after route has changed?

Use the route path as `resetSignal` prop, see the example below:

```tsx
import {
  ReactElement
} from 'react';
import {
  Outlet,
  useLocation
} from 'react-router';

import ErrorBoundary from '@kcuf-ui/error-boundary';

export default function Main(): ReactElement {
  const {
    pathname
  } = useLocation();
  
  return <main>
    <ErrorBoundary resetSignal={pathname}>
      <Outlet />
    </ErrorBoundary>
  </main>;
}

```
npm package: @kcuf-ui/icon-base
Description: An IconBase to be filled with types, and IconFont/WebFont helper.
Author: Jianchun Wang
Latest version: 0.2.3

# @kcuf-ui/icon-base

> <https://www.iconfont.cn> 辅助，用于注入 iconfont 或 webfont 并返回对应的 font 名，是写组件的好帮手，同时提供了 `IconBase` 以助理快速写组件。

## 使用 injectIconFont

### 定义 Icon 组件

你需要去 <https://www.iconfont.cn> 新建或选择一个 iconfont 项目，它会有一个固定不变的 `id` 和跟当前内容有关的 `hash`，并且可能有 Base64，都可以拿来，比如：

![Code Example](https://img.alicdn.com/imgextra/i3/O1CN01xQMgKp1syThmNp0Db_!!6000000005835-2-tps-1920-1048.png)

定义组件（参考 `@kcuf-ui/icon` 的实现，核心代码 50 行左右）：

```tsx
import {
  ReactElement
} from 'react';
import styled from 'styled-components';

import IconBase, {
  IconProps,
  injectIconFont
} from '@kcuf-ui/icon-base';

// 为 demo 完整，直接写这里，实际场景建议提出去
const ICON_TYPE_MAPPING = {
  loading: 'e62e',
  close: 'e612'
};

type TIconType = keyof typeof ICON_TYPE_MAPPING;

interface IIconProps extends IconProps<TIconType> {}

// https://at.alicdn.com/t/c/font_4720928_yvtln3fv7v.css
const fontFamily = injectIconFont('4720928', 'yvtln3fv7v', { // 每次更新只需更新文件中的 hash 值
  pathExtra: '/c'
});

function getIconCode(type: TIconType) {
  return `\\${ICON_TYPE_MAPPING[type] || 'e600'}`;
}

function getIconColor(type: TIconType): string | null {
  ...
}

export default function Icon(props: IIconProps): ReactElement {
  return <IconBase<TIconType> {...{
    rotating: props.type === 'loading',
    ...props,
    fontFamily,
    getIconCode,
    getIconColor
  }} />;
}

export type {
  TIconType as IconType,
  IIconProps as IconProps
};
```

### 使用 Icon 组件

```tsx
import Icon from '...'; // 应用内部的或你把它发布成包

// OK
<Icon type="loading" />
<Icon type="close" />
// TS 报错
<Icon type="load" />
<Icon type="x" />
```

### 使用 WebFont 字体

WebFont 可以让你的页面展现一些设计师希望的「美妙」字体（尤其是中文字体）。

众所周知，中文字体包不像西方字体那样，它是很大的，不可能为了视觉上的美观而让浏览器加载全量的中文字体包。

iconfont 网站提供了 [webfont](https://www.iconfont.cn/webfont) 的功能，你可以根据设计师给的文案去上边「定制」你要的字体。

假设，设计师要展示这样的文案：

![WebFont Example](https://img.alicdn.com/imgextra/i1/O1CN01KKpw4f1mkqDbsPutl_!!6000000004993-2-tps-852-82.png)

### 定义 WebFont 组件

你这么做：

![Copy Id](https://img.alicdn.com/imgextra/i2/O1CN01Ml8SDd238ggwXhb9E_!!6000000007211-2-tps-905-556.png)

```tsx
import {
  ReactElement
} from 'react';
import styled from 'styled-components';

import {
  injectWebFont
} from '@kcuf-ui/icon-base';

const fontFamily = injectWebFont('kygag0sd8g');

// 如果有其他的样式要求，也可以在这里加，或者在它的容器上加也行
const ScMengziKongzi = styled.span`
  font-family: ${fontFamily}, sans-serif;
`;

export default function MengziKongzi(): ReactElement {
  return <ScMengziKongzi>孔子曰：中午不睡，下午崩溃！孟子曰：孔子说的对！</ScMengziKongzi>;
}
```

### 使用 WebFont 组件

```tsx
import MengziKongzi from 'path/to/mengzi-kongzi'; // 一般在引用内部，没有意义发布成包

// 任何需要此组件的地方
<MengziKongzi />
```
npm package: @kcuf-ui/rc-icon-base
Description: An IconBase to be filled with types, and IconFont/WebFont helper.
Author: Jianchun Wang
Latest version: 0.1.9

# @kcuf-ui/rc-icon-base

> <https://www.iconfont.cn> 辅助，用于注入 iconfont 或 webfont 并返回对应的 font 名，是写组件的好帮手，同时提供了 `IconBase` 以助理快速写组件。

## 使用 injectIconFont

### 定义 Icon 组件

你需要去 <https://www.iconfont.cn> 新建或选择一个 iconfont 项目，它会有一个固定不变的 `id` 和跟当前内容有关的 `hash`，并且可能有 Base64，都可以拿来，比如：

![Code Example](https://img.alicdn.com/imgextra/i3/O1CN01xQMgKp1syThmNp0Db_!!6000000005835-2-tps-1920-1048.png)

定义组件（参考 `@kcuf-ui/rc-icon` 的实现，核心代码 50 行左右）：

```tsx
import {
  ReactElement,
  forwardRef
} from 'react';
import styled from 'styled-components';

import IconBase, {
  IconRef,
  IconProps,
  injectIconFont
} from '@kcuf-ui/rc-icon-base';

// 为 demo 完整，直接写这里，实际场景建议提出去
const ICON_TYPE_MAPPING = {
  loading: 'e62e',
  close: 'e612'
};

type TIconType = keyof typeof ICON_TYPE_MAPPING;

interface IIconProps extends IconProps<TIconType> {}

// https://at.alicdn.com/t/c/font_4720928_yvtln3fv7v.css
const fontFamily = injectIconFont('4720928', 'yvtln3fv7v', { // 每次更新只需更新文件中的 hash 值
  pathExtra: '/c'
});

function getIconCode(type: TIconType) {
  return `\\${ICON_TYPE_MAPPING[type] || 'e600'}`;
}

function getIconColor(type: TIconType): string | null {
  ...
}

/**
 * ConsoleBase 项目自用的图标组件
 */
function Icon({
  type,
  ...props
}: IIconProps, ref: IconRef): ReactElement {
  return <IconBase<TIconType> {...{
    ref,
    type,
    rotating: type === 'loading',
    ...props,
    fontFamily,
    getIconCode,
    getIconColor
  }} />;
}

export default forwardRef(Icon);

export type {
  TIconType as IconType,
  IIconProps as IconProps
};
```

### 使用 Icon 组件

```tsx
import Icon from '...'; // 应用内部的或你把它发布成包

// OK
<Icon type="loading" />
<Icon type="close" />
// TS 报错
<Icon type="load" />
<Icon type="x" />
```

### 使用 WebFont 字体

WebFont 可以让你的页面展现一些设计师希望的「美妙」字体（尤其是中文字体）。

众所周知，中文字体包不像西方字体那样，它是很大的，不可能为了视觉上的美观而让浏览器加载全量的中文字体包。

iconfont 网站提供了 [webfont](https://www.iconfont.cn/webfont) 的功能，你可以根据设计师给的文案去上边「定制」你要的字体。

假设，设计师要展示这样的文案：

![WebFont Example](https://img.alicdn.com/imgextra/i1/O1CN01KKpw4f1mkqDbsPutl_!!6000000004993-2-tps-852-82.png)

### 定义 WebFont 组件

你这么做：

![Copy Id](https://img.alicdn.com/imgextra/i2/O1CN01Ml8SDd238ggwXhb9E_!!6000000007211-2-tps-905-556.png)

```tsx
import {
  ReactElement
} from 'react';
import styled from 'styled-components';

import {
  injectWebFont
} from '@kcuf-ui/rc-icon-base';

const fontFamily = injectWebFont('kygag0sd8g');

// 如果有其他的样式要求，也可以在这里加，或者在它的容器上加也行
const ScMengziKongzi = styled.span`
  font-family: ${fontFamily}, sans-serif;
`;

export default function MengziKongzi(): ReactElement {
  return <ScMengziKongzi>孔子曰：中午不睡，下午崩溃！孟子曰：孔子说的对！</ScMengziKongzi>;
}
```

### 使用 WebFont 组件

```tsx
import React from 'react';

import MengziKongzi from 'path/to/mengzi-kongzi'; // 一般在引用内部，没有意义发布成包

// 任何需要此组件的地方
<MengziKongzi />
```
npm package: @kcuf-ui/rc-transition
Description: A transition manager for react component.
Author: Jianchun Wang
Latest version: 0.0.3

# @kcuf-ui/rc-transition

A ~~fork~~ rewritten of `https://www.npmjs.com/package/react-transition-group` using TS and hooks.

It's NOT designed to be a drop-in replacement of `react-transition-group`, however some APIs are learned from it.

## Why

1. `react-transition-group` has not been updated for over 2 years (version 4.4.5 till 2025/01/03)
2. `react-transition-group` is written in JS
3. `react-transition-group` depends on `prop-types` which TS react coders might hate
4. `react-transition-group` is in class component format
5. `react-transition-group` uses deprecated [findDOMNode](https://legacy.reactjs.org/docs/react-dom.html) which can only be used in class component
6. `react-transition-group` cannot start its own dev environment now, a bunch of errors

## How to Use

### Using Render Props

```tsx
import Transition from '@kcuf-ui/rc-transition';

const transitionStyles = {
  entering: {
    opacity: 1
  },
  entered: {
    opacity: 1
  },
  exiting: {
    opacity: 0
  },
  exited: {
    opacity: 0
  }
};

<Transition {...props}>
  {(status: 'entering' | 'entered' | 'exiting' | 'exited') => <Child style={transitionStyles[status]} />}
</Transition>
```

### Using `data-transition`

```tsx
import styled from 'styled-components';

import Transition from '@kcuf-ui/rc-transition';

const ScChild = styled.div`
  &[data-transition='entering'] {
    opacity: 1;
  }
  
  &[data-transition='entered'] {
    opacity: 1;
  }
  
  &[data-transition='exiting'] {
    opacity: 0;
    text-decoration: line-through;
  }
  
  &[data-transition='exited'] {
    opacity: 0;
  }
`;

<Transition {...props}>
  <ScChild />
</Transition>
```

### Using `useTransitionStatus` hook

```tsx
import {
  ReactElement
} from 'react';
import styled from 'styled-components';

import Transition, {
  useTransitionStatus
} from '@kcuf-ui/rc-transition';

function Child(): ReactElement {
  const transitionStatus = useTransitionStatus(); // however you would like to use it
  
  return <div>...</div>;
}

<Transition {...props}>
  <Child />
</Transition>
```

## APIs

### Exports

* Removed
  + `CSSTransition` → `Transition` + `data-transition` is enough
* Renamed
  + `SwitchTransition` → `TransitionSwitch` TODO
  + `ReplaceTransition` → `TransitionReplace` TODO

### TransitionProps

| prop | Description |
| :---: | :---: |
| `nodeRef` | Needed when `mountOnEnter \|\| unmountOnExit` to force reflow. |
| `in` | Same as `react-transition-grpup`. |
| `durationEnter?: number` | Default 400ms |
| `durationExit?: number` | Default to `durationEnter` |
| `onEnter()` | No parameters. |
| `onEntered()` | No parameters. |
| `onExit()` | No parameters. |
| `onExited()` | No parameters. |

These props are removed:

* ~~`onEntering()`~~
* ~~`onExiting()`~~
* ~~`appear`~~ → There is no `appear` now, treated the same as `enter`.
* ~~`enter`~~ → `durationEnter: 0`
* ~~`exit`~~ → `durationExit: 0`
* ~~`timeout?: number \| { apear?: number; enter?: number; exit: number; }`~~ → `durationEnter?: number` + `durationExit?: number`
* ~~`addEventListener`~~
npm package: @kcuf-ui/theme-toggle
Description: Theme toggle SVG components, with fine animation.
Author: Jianchun Wang
Latest version: 0.2.0

# @kcuf-ui/theme-toggle

> A fork and re-written (using styled-components) of the wonderful [@theme-toggles/react](https://www.npmjs.com/package/@theme-toggles/react).

## Why

1. Don't want to manually include external CSS
2. Don't want the `button` wrap and the `as` prop does NOT work as expected, which makes it impossible to put it under my styled `button` (yields `validateDOMNesting` warning)

In other words, I just want the SVG elements.
npm package: @kcuf/asset-loader
Description: Load JS/CSS/Image with ease and type safe.
Author: Jianchun Wang
Latest version: 0.0.1

# @kcuf/asset-loader

Asset loaders for JS/CSS/Image.

* `loadJs(url: string, options?: LoadJsOptions): Promise<void>;`
* `loadJsT<O extends object, K extends keyof O>(url: string, globalName: K, options?: LoadJsOptions): Promise<O[K]>;`
* `loadCss(url: string): void;`
* `loadImage(url: string): Promise<HTMLImageElement>;`
* `loadAssets(urls: string[]): void;`

## Features

* 有并发保护的 `loadJs`
* 类型安全的 `loadJsT`
npm package: @kcuf/base64
Description: Base64 url-safe encoding/decoding.
Author: Jianchun Wang
Latest version: 0.1.0

# @kcuf/base64

Base64 url-safe encode/decode.

## Why

The two Base64 related functions are now widely available in modern browsers:

* [btoa](https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa) for encoding
* [atob](https://developer.mozilla.org/en-US/docs/Web/API/Window/atob) for decoding

However, `btoa` does not support Unicode now, any of these will break:

* `btoa('中文')`
* `btoa('💥')`

That's why this package is used.

* 不支持 unicode，汉字和 Emoji 等会报错
  - FF → `DOMException: String contains an invalid character`
  - CH → `DOMException: Failed to execute 'atob' on 'Window': The string to be decoded contains characters outside of the Latin1 range.`
  - SF → `InvalidCharacterError: The string contains invalid characters.`

* 这两个方法有些奇怪，可以把它从 window 上脱离出来调用，但不能把它挂到某对象下面调用，否则会报错
  - FF → `TypeError: 'atob' called on an object that does not implement interface Window`
  - CH → `TypeError: Illegal invocation`
  - SF → `TypeError: Can only call Window.btoa on instances of Window`

## How to Use

### encode

```ts
import {
  encode
} from '@kcuf/base64';

encode(stringToEncode);
encode(stringToEncode, true); // url-safe
```

### decode

```ts
import {
  decode
} from '@kcuf/base64';

decode(stringEncoded);
```
npm package: @kcuf/canvas-marking
Description: A canvas marking library.
Author: Jianchun Wang
Latest version: 0.7.8

# @kcuf/canvas-marking

基于 `<canvas>` 的图片标注。

## How to Use

1. 准备一个 **空** 的容器节点 `dom`
2. `new CanvasMarking(dom, options)`

CanvasMarking 会在容器内部创建一个绝对定位并占满容器的 DOM，已实现大小自适应，因此会为容器添加样式 `position: relative; overflow: hidden;`。

之所以内部使用绝对定位，而非采用 `height: 100%` 是因为后者只有在容器有可计算的高度时（即在样式中明确定义 `height`）才能够生效。

推荐开发者为容器使用 Flex 布局 `flex: 1`，使其能够占满某个区域并自适应，除此之外，尽量不要附加其他样式。

## 参考

* https://github.com/wanglin2/markjs
npm package: @kcuf/canvas-marking-react-headless
Description: The React headless wrapping of @kcuf/canvas-marking.
Author: Jianchun Wang
Latest version: 0.4.8

# @kcuf/canvas-marking-react-headless

The React headless wrapper for `@kcuf/canvas-marking`, which `@kcuf/canvas-marking-react` is built upon。
npm package: @kcuf/click-hijacker
Description: Hijack click event like JQ delegate.
Author: Jianchun Wang
Latest version: 0.2.1

# @kcuf/click-hijacker

全局或局部的点击事件劫持器

## INSTALL

```bash
tnpm i -S  @kcuf/click-hijacker
```

```typescript
import hijackClick, {
  hijackClickInDom
} from '@kcuf/click-hijacker';

// Globally
hijackClick(hijacker);

// In a specific DOM element
hijackClickInDom(container, hijacker);
```

以上 `hijacker` 类型如下：

```ts
interface ClickHijacker<T = boolean> {
  /**
   * 判定条件，返回「真」即表示劫持成功，返回值将作为 `callback` 的参数
   */
  condition(el: HTMLElement, e: MouseEvent): T | void;
  /**
   * 劫持操作
   */
  callback?(result: T, el: HTMLElement): void;
  /**
   * 劫持后是否 `preventDefault`，默认对链接 `true`
   */
  shouldPreventDefault?: boolean;
  /**
   * 劫持后是否 `stopPropagation`，默认 `false`
   */
  shouldStopPropagation?: boolean;
}
```
npm package: @kcuf/data-normalizer
Description: Data normalizer helper to make all frontend data sane.
Author: Jianchun Wang
Latest version: 0.1.1
Deprecated: use @kcuf/helper-data

# @/pkg/data-normalizer

将千奇百怪、随心所欲、虚头巴脑的后端数据中的字段、类型进行标准化的帮助类型及方法。

不具业务向，只做通用的逻辑，业务代码可以扩展这个包。
npm package: @kcuf/demo-rc
Description: React components for demo purpose only.
Author: Jianchun Wang
Latest version: 0.6.1

# @kcuf/demo-rc

> 切莫用于生产代码，写 Demo 的时候用。

写 demo 时专用的一些基础元素，带简单的样式，为了比如用 storybook 写 demo 的时候好看和方便。

## Components

* 特殊作用
  - `MinimalNormalize` normalize 样式，建议每个 storybook demo 都加上
  - `PackageInfo` 显示包名和版本等信息
* Typography - 一些原生 DOM 的直接替代品及扩展
  - `H1`
  - `H2`
  - `H3`
  - `H4`
  - `H5`
  - `H6`
  - `P`
  - `Hr`
  - `Blockquote`
  - `Em`
  - `Strong`
  - `Code`
  - `Kbd`
  - `HtmlText` - 将字符串以 HTML 的形式展示，方便需要展示一些带 HTML 的内容的场景
  - `RainbowText` - 彩虹字体
* Feedback
  - `Alert` 提示信息
* Form
  - `InputText`
  - `InputTextarea`
  - `InputNumber`
  - `InputRange`
  - `InputColor`
  - `InputSwitch`
  - `Button`
  - `Form`
* Data Display
  - `List`
  - `Table`
  - `Codemirror`
  - `CodeViewer`
  - `CodeViewerJson5`
  - `PromiseViewer`
* Placeholder
  - `LongArticle` 可用来撑高
npm package: @kcuf/fetch-sse
Description: Fetch SSE.
Author: Jianchun Wang
Latest version: 0.0.5

# @kcuf/fetch-sse

> `EventSource` 能力的 `fetch` 实现 - 不是 Polyfill

🎈 为什么写这个，而为什么不用 `EventSource` 或 `EventSourcePolyfill`？

1. 原生 `EventSource` 只有一个 `withCredentials` 参数，不接收 `headers`
2. `Polyfill` 有个会循环启动的 BUG
3. `Polyfill` 版本代码臃肿，有较多过时的逻辑

## 参考

* [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
* [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
* [EventSourcePolyfill](https://github.com/Yaffle/EventSource)
npm package: @kcuf/fetcher
Description: Fetcher with more reasonable interceptors.
Author: Jianchun Wang
Latest version: 0.2.10

# @kcuf/fetcher

## Base

* `@kcuf/fetcher-fetch`
* `@kcuf/fetcher-json`

## Interceptors

* [x] `@kcuf/fetcher-interceptor-biz` 剥离业务数据，抛出业务错误
* [x] `@kcuf/fetcher-interceptor-cache-local` 本地缓存
* [x] `@kcuf/fetcher-interceptor-merging` 相同接口同一时间调用合并
* [ ]  `@kcuf/fetcher-interceptor-retry` 重试 n 次
* [x]  `@kcuf/fetcher-interceptor-sls` SLS 日志
* [x]  `@kcuf/fetcher-interceptor-login` 登录，若多个请求同时需要登录，不会唤起多个

## FAQ

### 为什么在 Response 的 `headers` 里获取不到某 header？

自定义 Header 需要显式地暴露，后端需要添加 [Access-Control-Expose-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Expose-Headers)。
npm package: @kcuf/fetcher-fetch
Description: An improved fetch with timeout.
Author: Jianchun Wang
Latest version: 0.2.0

# @kcuf/fetcher-fetch

> `@kcuf/fetcher` 底层 fetch 封装

原生 fetch 特点：

1. 不支持 `timeout`（这里模拟掉了）
2. HTTP 错误状态（400、500 等），仍然 resolve
3. 默认不携带 cookie，必须 `{ credentials: 'include' }`
4. 不支持 progress 事件，因此上传需要感知进度的还得用 XHR
5. 当 `body` 和 `headers` 类型有耦合
   - `body` 为 `URLSearchParams` | `FormData` | `Blob`，`headers` 中不可有 `'Content-Type'`
   - `headers` `Content-Type` 为 `'application/json'`，用 `JSON.stringify` 处理 `body`
   - `headers` `Content-Type` 为 `'application/x-www-form-urlencoded'`，用 `qs` 或 `URLSearchParams` 处理 `body`
npm package: @kcuf/fetcher-interceptor-cache-local
Description: Fetcher interceptor cache local in memory.
Author: Jianchun Wang
Latest version: 0.2.7

# @kcuf/fetcher-interceptor-cache-local

> 本地内存缓存

在页面生命周期内，能够保证仅真实调一次接口，优先级 `20`，必须在 `Biz` 之后，因为 biz 结果的处理影响缓存的数据。
npm package: @kcuf/fetcher-interceptor-delay
Description: Fetcher interceptor that delays the request for testing purpose.
Author: Jianchun Wang
Latest version: 0.1.0

# @kcuf/fetcher-interceptor-delay

## How to Use

```ts
import interceptDelay from '@kcuf/fetcher-interceptor-delay';

interceptDelay(fetcher);
```

Then, when you test some page for loading effect, open the developer tool console, and execute `__FETCHER_DELAY = 1000`, this will delay every fetcher call for 1000ms.
npm package: @kcuf/fetcher-interceptor-login
Description: Fetcher interceptor seamless login and redo request.
Author: Jianchun Wang
Latest version: 0.3.1

# @kcuf/fetcher-interceptor-login

无感登录是很常见的需求。

利用该拦截器，可以对需要登录的特定错误进行登录后重新发起请求，实现丝滑的登录流程。

## 前提

拦截器内部使用 `error.code` 判断是否需要拦截。

## 使用

```ts
import interceptLogin from '@kcuf/fetcher-interceptor-login';

import needLogin from './need-login';
import doLogin from './do-login';

interceptLogin(fetcher, {
  needLogin,
  doLogin,
  headerKeys
});
```

由于业务的特殊性，你需要自定义 `needLogin`、`doLogin` 以及可选的 `headerKeys`。

### needLogin

```ts
function needLogin(code: string, error: FetcherError): boolean;
```

### doLogin

```ts
function doLogin(): Promise<void>;
```

这是一个无参的返回 `Promise<void>` 的方法，一般来说对弹窗登录的 `Promise` 封装。

### headerKeys

> 使用 Cookie 的情况不需要。

注意：如果登录用的 **不是** Cookie，而用的是 Header 的话，需要在重试请求前将原请求 `config.headers` 中对应的值清掉。

这是由于 Fetcher 拦截器放的 header、params、body 等优先级不能比传入值高，而前一次由拦截器混入的 `headers` 在重试的二次 请求中，地位变成了传入值。

解决的办法是，使用 fetcher 提供的帮助方法 `deleteConfigHeaders` 在二次调用前清除相应的 header（可能有多个），这里提供了简便的配置属性 `headerKeys`。

## FAQ

### ❓登录窗怎么实现？

由于需要的是 `Promise`，你需要使用命令式的弹窗，而不应该是组件式。比如 `@kcuf/rc-dialog` 的命令式弹窗就很强。

### ❓登录窗登录成功，需要做什么？

登录成功，一般是设置 Cookie 或者 `localStorage` 存取用户登录相关的信息。如果是 Cookie 的话，一般不需要在做什么，因为 `fetch` 会默认带 Cookie 的，
如果记录到 `localStorage` 的，你需要写一个请求公共信息的拦截器（使用 `fetcher.interceptRequest`）。

### ❓多个接口同时报错要登录，会不会同时出现多个登录窗？

不会。内部对 `doLogin` 做了单例包裹，不会有这样的事情发生，只要登录未完成，就会在当前的登录队列中增加回调，而不会多弹窗。

### ❓登录窗可否取消登录？

可以，但取消登录后，错误就会变性，`error.name` 会变，`error.code` 不变，并且此次接口调用最终为失败。

建议在用户完成登录前，不允许用户关闭或取消。
npm package: @kcuf/fetcher-interceptor-merging
Description: Fetcher interceptor merging, which can prevent multiple same requests at the same time.
Author: Jianchun Wang
Latest version: 0.2.6

# @kcuf/fetcher-interceptor-merging

> 合并同一时间内的相同请求，不会请求多次，优先级默认 `30`，必须在 `CacheLocal` 之后，因为 `CacheLocal` 有类似的逻辑，且 cache 会优先于 merging。
npm package: @kcuf/fetcher-jsonp
Description: The jsonp implementation, can be used alone.
Author: Jianchun Wang
Latest version: 0.2.2

# @kcuf/fetcher-jsonp

> `@kcuf/fetcher` 底层 JSONP 实现

JSONP 特点：

1. 天然跨域
2. 只能 GET
3. 不支持 timeout（这里模拟了）
4. 不能用 Header
npm package: @kcuf/fetcher-xhr
Description: An xhr wrapping to mimic similar behavior of fetch.
Author: Jianchun Wang
Latest version: 0.1.3

# @kcuf/fetcher-xhr

> `@kcuf/fetcher` 底层 xhr 封装

只有上传，且需要感知进度的时候，需要用到它。

原生 XHR 特点：

1. 不是 `Promise`（这里封装了）
2. 支持 timeout
3. 支持 progress
4. 支持 abort，但需要得到 xhr 对象，这里用 `AbortController` 做了封装
npm package: @kcuf/geometry-basic
Description: Some basic geometry utility functions.
Author: Jianchun Wang
Latest version: 0.5.3

# @/pkg/helper-geometry

> 几何算法辅助

参考：

| lib | 说明 |
| --- | --- |
| [@turf/turf](https://turfjs.org) [npm](https://www.npmjs.com/package/@turf/turf) [git](https://github.com/Turfjs/turf) | A modular geospatial engine written in JavaScript |
| [@mathigon/euclid](https://mathigon.io/euclid) [npm](https://www.npmjs.com/package/@mathigon/euclid) [git](https://github.com/mathigon/euclid.js) | A Typescript library for 2D geometry |
| **2d-geometry** [npm](https://www.npmjs.com/package/2d-geometry) [git](https://github.com/romgrk/2d-geometry) | A fork of flatten-js focused on performance, ergonomics and Typescript |
| [flatten-js](https://alexbol99.github.io/flatten-js/index.html) [npm](https://www.npmjs.com/package/flatten-js) [git](https://github.com/alexbol99/flatten-js) | A Javascript library for 2d geometry |
| **@markroland/path-helper** [npm](https://www.npmjs.com/package/@markroland/path-helper) [git](https://github.com/markroland/path-helper) | A variety of methods to assist with performing operations on multi-point paths (polylines) |

## 各模块说明

保证 `src/helper` 下各模块依赖关系为下方依赖上方，避免循环依赖：

* `base` 基础，获取形状原信息
* `angle` 角
* `comparison` 比较
* `relation` 关系
* `transform` 变化（选择、移动等）
* `mutation` 修改
* `slicing` 切割
* `justify` 矫正
* `misc` 杂项
npm package: @kcuf/helper-data
Description: Data normalizer helpers and loading stuff to make all frontend data processing sane.
Author: Jianchun Wang
Latest version: 0.6.0

# @kcuf/helper-data

将千奇百怪、随心所欲、虚头巴脑的后端数据中的字段、类型进行标准化的帮助类型及方法。

不具业务向，只做通用的逻辑，业务代码可以扩展这个包。
npm package: @kcuf/helper-loading
Description: Loading stuff helpers.
Author: Jianchun Wang
Latest version: 0.6.0

# @kcuf/helper-loading

将数据、错误及加载状态标准化成一个对象的形式

## Helper

* `function createWithLoading<T>(data?: T | null, loading = ELoadingStatus.IDLE, error?: Error | null): IWithLoading<T>`

## Enum

* `LoadingStatus`

## Type

* `WithLoading<T>`
npm package: @kcuf/helper-rc-model
Description: Common helpers for headless component to deal with daily state updating.
Author: Jianchun Wang
Latest version: 0.2.0

# @kcuf/helper-rc-model

React Headless Model 中很多类似的逻辑实际上都是模式代码，利用这里提供的帮助方法，可以减小代码冗余提高开发效率。

## 帮助类型

* `ListChangeReason`

## 帮助方法

* `afterListChange<T>()`

## Hook

TODO
npm package: @kcuf/helper-tree
Description: The tree data helper.
Author: Jianchun Wang
Latest version: 0.2.2

# @kcuf/helper-tree

树形数据工具箱：

* `treeBuild`
* `treeFlatten`
* `treeTraverse`
* `treeFilter`
* `treeTrim`
* `treeMap`
* `treeDisable`
npm package: @kcuf/keymap
Description: Yet another keystroke helper.
Author: Jianchun Wang
Latest version: 0.2.0

# @kcuf/keymap

## 基础比较

| Package | Version | Size | Download | Star | TS | Demo | Test |
| --- | --- | --- | --- | --- | --- | --- | --- |
| [tinykeys] | 3.0.0/2024 | 1.48kB | 41k/Week | 3.8k | ✅ | [demo](https://jamiebuilds.github.io/tinykeys) | - |
| [hotkeys-js] | 3.13.7/2024 | 41.2kB | 363k/Week | 6.7k | ✍ | [demo](https://wangchujiang.com/hotkeys-js) | Jest |
| [keymaster] | 1.6.2/2014 | 8.58kB | 22k/Week | 6.5k | DT |  | - |
| [mousetrap] | 1.6.5/2019 | 33.9kB | 400k/Week | 11.7k | DT |  | Mocha |
| [shortcutjs] | 1.6.0/2017 | 12.9kB | 6/Week | 28 | ✅ |  | Jest ★ |
| [hotkeys] | 1.0.0/2014 | 15.1kB | 150/Week | 3 | JS |  |  |

其中 hotkeys OO 思想过重，用起来非常麻烦，后续不做计较。

## 功能比较

| - | [tinykeys] | [hotkeys-js] | [keymaster] | [mousetrap] | [shortcutjs] | `@kcuf/keymap` |
| --- | --- | --- | --- | --- | --- | --- |
| 批量绑定 | ✅ 对象 | ✅ 逗号 | ✅ 逗号 | ✅ 数组 | ✅ 对象数组 | ✅ 对象 |
| 支持序列（Combo） | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ |
| 安全解绑 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| 暂停（pause/resume） | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
| preventDefault | ❌ | `return false` | `return false` | `return false` | `options.preventDefault` | `return 'prevent'` |
| stopPropagation | ❌ | `return false` | `return false` | `return false` | ❌ | `return 'stop'` |
| 触发条件（某些场景下不触发） | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ `options.ignore(e: KeyboardEvent)` |
| 便捷绑定（`? *` 等） | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ |
| 代码触发（trigger） | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ |
| 符号 Modifiers（⇧⌥⌃⌘） | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ |

## 符号

| 符号 | `key` | `code` | ~~`keyCode`~~ |
| --- | --- | --- | --- |
| ⌃ | Control | ControlLeft / ControlRight | 17 |
| ⌥ | Alt | AltLeft / AltRight | 18 |
| ⇧ | Shift | ShiftLeft / ShiftRight | 16 |
| ⌘ | Meta | MetaLeft / MetaRight | 224 |
| ↑ | ArrowUp | ArrowUp | 38 |
| ↓ | ArrowDown | ArrowDown | 40 |
| ← | ArrowLeft | ArrowLeft | 37 |
| → | ArrowRight | ArrowRight | 39 |
| ⏎ / ↵ / ↩ | Enter | Enter | 13 |
| ␣ | Space | Space | 32 |
| ⎋ | Escape | Escape | 27 |
| ⇥ | Tab | Tab | 9 |
| ⇞ | PageUp | PageUp | ? |
| ⇟ | PageDown | PageDown | ? |
| ⌫ | Backspace | Backspace | 8 |
| ⌦ | Delete | Delete | 46 |

## FAQ

### ❓ 能否 `stopPropagation` 和 `preventDefault`？

可以。由绑定方法的返回值决定：

* `'stop'` → `e.stopPropagation()`
* `'prevent'` → `e.preventDefault()`
* `false` → `e.stopPropagation()` + `e.preventDefault()`
* 其他 → 什么都不做

### ❓能否在回调中拿到事件对象？

不能，不需要，也不安全。

### ❓ Mac 下 F11 无响应？

系统快捷键权限更高，接管不了。

### ❓ `Ctrl++` 不行？

不行。换 `Ctrl+=`。

### ❓ Combo 下使用 `+`？

可以。比如设置 `C + +`，可以在按键序列 `c` → `⇧=` → `⇧=` 时触发。

[tinykeys]: https://www.npmjs.com/package/tinykeys
[hotkeys-js]: https://www.npmjs.com/package/hotkeys-js
[keymaster]: https://www.npmjs.com/package/keymaster
[mousetrap]: https://www.npmjs.com/package/mousetrap
[shortcutjs]: https://www.npmjs.com/package/shortcutjs
[hotkeys]: https://www.npmjs.com/package/hotkeys
npm package: @kcuf/markdownlint-config
Description: Shareable markdownlint config.
Author: Jianchun Wang
Latest version: 0.0.2

# @kcuf/markdownlint-config

A shareable markdown lint config, which can be used with `markdownlint-cli` or `markdownlint-cli2`.

## Inherit

`@kcuf/markdownlint-config` ← _BASE_

## Install & Setup

```bash
pnpm add -D markdownlint-cli2 @kcuf/markdownlint-config
```

In your `.markdownlint.yaml`:

```yml
extends: "@kcuf/markdownlint-config"
```

## Usage

### Terminal

### With `lint-staged` (Recommended 💎)

```json
{
  "*.md": "markdownlint-cli2"
}
```

```bash
npx markdownlint-cli2 ./README.md
```

### npm-scripts

```bash
npm pkg set scripts.lint:md="markdownlint-cli2 **/*.md #node_modules"
```
npm package: @kcuf/md5
Description: Md5 url-safe encoding/decoding.
Author: Jianchun Wang
Latest version: 0.0.2

# @kcuf/md5

An MD5 encrypting util.

## APIs

```ts
import md5, {
  md5String,
  md5Uint8Array,
  md5Buffer,
  md5Blob,
  md5Json
} from '@kcuf/md5';
```

| API | Description |
| --- | --- |
| `md5(input: unknown): string` | `input` can be `string`, `Buffer`, `Uint8Array` and anything else (which may not be hashed correctly though). |
| `md5String(input: string): string` | `input` is string |
| `md5Buffer(input: Buffer \| ArrayBuffer): string` | `input` is Buffer like object |
| `md5Blob(blob: Blob): Promise<string>` | `input` is a Blob object, note that it is async |
| `md5Json(o: unknown): string` | Generate MD5 on plain object, the JSON is stringify-ed in ordered manner, so the composing order is regardless  |
| `md5Unit8Array(uint8Array: Uint8Array): string` | The underneath md5 function |
npm package: @kcuf/mere-dom
Description: A minimal DOM util collection.
Author: Jianchun Wang
Latest version: 0.0.4

# @kcuf/mere-dom

一个极简的 DOM 帮助工具，类似 `dom-helpers`，不考虑做大。

> 🤔 请优先考虑非 DOM 操作的方式。

虽然现今前端开发，基本上不需要考虑 DOM 操作，但有的时候还是避免不了。
npm package: @kcuf/messenger
Description: A simple and type safe post-message enhancement.
Author: Jianchun Wang
Latest version: 0.0.2

# @kcuf/messenger

* 🧱 优雅且类型安全的 `postMessage`
* 😎 可以传递 `Error` 对象
* 🎯 可以转成 `Promise`
* 🪁 可以跨 Window 发送事件

## 兼容性

`postMessage` 的 [兼容性](https://caniuse.com/#search=postMessage) 已经很好了，我们不做无谓判断。

## INSTALL

```shell
pnpm add @kcuf/messenger
```

## Usage

```ts
import messenger from '@kcuf/messenger';

messenger.emit<P>(type, payload); // 这里 P 是 `payload` 的类型定义
messenger.on<P>(type, payload => {
  // ...
});
messenger.once<P>(type, payload => {
  // ...
});

messenger.emitPromise<P>(type, payload);
messenger.onPromise<P>(type, payload => {
  // ...
});
```

## APIs

* `messenger.emit(type, payload?)` 相当于 `postMessage({ type, payload })`
* `messenger.emitPromise(type, payload?)` 也是 `postMessage`，返回 `Promise`，必须要有 `onPromise` 来承接，否则此 Promise 将永远无法结束
* `messenger.on(type, fn)` 相当于 `addEventListener('message' ...` 并判断 `e.data.type === type`
* `messenger.once(type, fn)` 相当于 `on`，调用后自动解绑
* `messenger.onPromise(type, fn)` 相当于 `on`，调用后自动解绑

## FAQ

### 如何向其他窗口（比如父 `parent` 或 `top`，甚至某个 `iframe` 的窗口）进行 `postMessage`？

给 `emit` / `emitPromise` 传第三个参数：

```ts
messenger.emit(type, payload, {
  targetWindow: 'parent' // 或 'top' 或其他 Window 对象
})
```

### 如何解绑？

之所以没有类似 `off` 之类的 API，是因为 `on`、`once`、`onPromise` 这几个方法的返回就是一个无参的解绑函数。

使用 hook 的例子

```tsx
import {
  useEffect
} from 'react';

import messenger from '@kcuf/messenger';

function useEffectOnMessengerXx(): void {
  useEffect(() => messenger.on(_MESSAGE_TYPE_ENUM_, () => {
    // ... do sth
  }), []);
}
```

### 必须 `@kcuf/messenger#emit` 联合 `@kcuf/messenger#on` 使用吗？

并不是。

你还是可以用原生的 `addEventListener('messenger', ...)` 来监听，唯一的区别就是你要自己去判断 `e.data.type` 等。

同样的，`@kcuf/messenger#on` 也可以接收裸写的 `postMessage({ type, data })`。只不过，你裸写 `postMessage` 很可能漏掉 `targetOrigin`。

### `type` 可否为空字符串？

不可。
npm package: @kcuf/npm-package-json-lint-config
Description: Shareable package.json lint config.
Author: Jianchun Wang
Latest version: 0.0.1

# @kcuf/npm-package-json-lint-config

## Install & Setup

```bash
pnpm add -D npm-package-json-lint @kcuf/npm-package-json-lint-config
```

In your `.npmpackagejsonlintrc.json`:

```json
{
  "extends": "@kcuf/npm-package-json-lint-config"
}
```

## Usage

### Terminal

```bash
npx npmPkgJsonLint ./package.json
```

### With `lint-staged`

```json
{
  "lint-staged": {
    "package.json": "npmPkgJsonLint"
  }
}
```

## Links

* [package.json](https://docs.npmjs.com/cli/v10/configuring-npm/package-json)
* [Shareable config](https://npmpackagejsonlint.org/docs/configuration#shareable-config)
* [Rules](https://npmpackagejsonlint.org/docs/rules)
npm package: @kcuf/rc-icon-base
Description: An IconBase to be filled with types, and IconFont/WebFont helper.
Author: Jianchun Wang
Latest version: 0.3.4

# @kcuf-ui/rc-icon-base

> <https://www.iconfont.cn> 辅助，用于注入 iconfont 或 webfont 并返回对应的 font 名，是写组件的好帮手，同时提供了 `IconBase` 以助理快速写组件。

## 使用 injectIconFont

### 定义 Icon 组件

你需要去 <https://www.iconfont.cn> 新建或选择一个 iconfont 项目，它会有一个固定不变的 `id` 和跟当前内容有关的 `hash`，并且可能有 Base64，都可以拿来，比如：

![Code Example](https://img.alicdn.com/imgextra/i3/O1CN01xQMgKp1syThmNp0Db_!!6000000005835-2-tps-1920-1048.png)

定义组件（参考 `@kcuf-ui/rc-icon` 的实现，核心代码 50 行左右）：

```tsx
import {
  ReactElement,
  forwardRef
} from 'react';
import styled from 'styled-components';

import IconBase, {
  IconRef,
  IconProps,
  injectIconFont
} from '@kcuf-ui/rc-icon-base';

// 为 demo 完整，直接写这里，实际场景建议提出去
const ICON_TYPE_MAPPING = {
  loading: 'e62e',
  close: 'e612'
};

type TIconType = keyof typeof ICON_TYPE_MAPPING;

interface IIconProps extends IconProps<TIconType> {}

// https://at.alicdn.com/t/c/font_4720928_yvtln3fv7v.css
const fontFamily = injectIconFont('4720928', 'yvtln3fv7v', { // 每次更新只需更新文件中的 hash 值
  pathExtra: '/c'
});

function getIconCode(type: TIconType) {
  return `\\${ICON_TYPE_MAPPING[type] || 'e600'}`;
}

function getIconColor(type: TIconType): string | null {
  ...
}

/**
 * ConsoleBase 项目自用的图标组件
 */
function Icon({
  type,
  ...props
}: IIconProps, ref: IconRef): ReactElement {
  return <IconBase<TIconType> {...{
    ref,
    type,
    rotating: type === 'loading',
    ...props,
    fontFamily,
    getIconCode,
    getIconColor
  }} />;
}

export default forwardRef(Icon);

export type {
  TIconType as IconType,
  IIconProps as IconProps
};
```

### 使用 Icon 组件

```tsx
import Icon from '...'; // 应用内部的或你把它发布成包

// OK
<Icon type="loading" />
<Icon type="close" />
// TS 报错
<Icon type="load" />
<Icon type="x" />
```

### 使用 WebFont 字体

WebFont 可以让你的页面展现一些设计师希望的「美妙」字体（尤其是中文字体）。

众所周知，中文字体包不像西方字体那样，它是很大的，不可能为了视觉上的美观而让浏览器加载全量的中文字体包。

iconfont 网站提供了 [webfont](https://www.iconfont.cn/webfont) 的功能，你可以根据设计师给的文案去上边「定制」你要的字体。

假设，设计师要展示这样的文案：

![WebFont Example](https://img.alicdn.com/imgextra/i1/O1CN01KKpw4f1mkqDbsPutl_!!6000000004993-2-tps-852-82.png)

### 定义 WebFont 组件

你这么做：

![Copy Id](https://img.alicdn.com/imgextra/i2/O1CN01Ml8SDd238ggwXhb9E_!!6000000007211-2-tps-905-556.png)

```tsx
import {
  ReactElement
} from 'react';
import styled from 'styled-components';

import {
  injectWebFont
} from '@kcuf-ui/rc-icon-base';

const fontFamily = injectWebFont('kygag0sd8g');

// 如果有其他的样式要求，也可以在这里加，或者在它的容器上加也行
const ScMengziKongzi = styled.span`
  font-family: ${fontFamily}, sans-serif;
`;

export default function MengziKongzi(): ReactElement {
  return <ScMengziKongzi>孔子曰：中午不睡，下午崩溃！孟子曰：孔子说的对！</ScMengziKongzi>;
}
```

### 使用 WebFont 组件

```tsx
import React from 'react';

import MengziKongzi from 'path/to/mengzi-kongzi'; // 一般在引用内部，没有意义发布成包

// 任何需要此组件的地方
<MengziKongzi />
```
npm package: @kcuf/rc-keyboard-mac
Description: A mac book keyboard react component.
Author: Jianchun Wang
Latest version: 0.3.2

# @kcuf/rc-keyboard-mac

![@kcuf/rc-keyboard-mac](https://img.picui.cn/free/2024/10/31/672333c84d470.jpg)

Yet another MacKeyboard React component, inspired by `@uiw/react-mac-keyboard` with some improvements and modifications:

1. Listen to document keydown events and reflect status by default
2. Can control active keys and modifiers
3. Use `styled-components` as CSS solution, reduced CSS size by using data attributes selector instead of `nth` selector, and reduced hard coded sizing in CSS
4. Can tell Left or Right Control/Option/Shift/Meta, and the difference between Backspace and Delete
5. Displays event details in the space bar by default
6. Ditched deprecated [keyCode](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode) for [code](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code)
7. An `onKeyPress` event which can reflect Shift state

## How to Use

```tsx
import Keyboard from '@kcuf/rc-keyboard-mac';

// default
<Keyboard />
```

...that easy.

## Demos

[![Edit @kcuf/rc-keyboard-mac](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/x4294z)

* 其他
  - [掘金](https://code.juejin.cn/pen/7431469243382071359)

## Props

All props are optional.

```ts
interface KeyboardProps extends HTMLAttributes<HTMLDivElement>, Partial<IKeyboardInfo> {
  /**
   * By default, the component will listen to keydown event on document, and refect the status in the keyboard.
   * 
   * 是否监听键盘事件，默认 `true`，如果 `false`，可以通过 `codes` 和 `capsLock` 进行控制。
   */
  listen?: boolean;
  /**
   * Controllable active codes, ref https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code.
   * 
   * 受控的 `activeCodes`
   */
  activeCodes?: EKeyboardCode[];
  /**
   * Whether Control / Alt / Shift / Meta / Fn can stay pressed before next click.
   * 
   * 是否对 Control / Alt / Shift / Meta / Fn 等键记录按下状态。
   */
  activeModifiers?: boolean | IKeyboardModifiers;
  /**
   * Wheter (default true) to display current keydown details in the space key, the info will disappear automatically after 3s.
   * 
   * 将键盘事件的信息显示在空格键上，3s 后自动消失，默认 `true`。
   * 
   * 注意，Mac 键盘在 CapsLock 的时候，按住 Shift 不会返回小写的 `key`，这里也不作纠正，如实反应。
   */
  detailsInSpace?: boolean;
  /**
   * Callback when click a key in the UI, can reflect whether Shift is on.
   * 
   * 点击按钮的回调，当 `capsLock` 受控时，能够根据 `Shift` 正确返回大小写的 `key` 值，但不会根据 `Alt` 做出反应。
   */
  onKeyPress?(key: string, code: EKeyboardCode): void;
  /**
   * With `props.activeModifiers` in object format, can make it controllable.
   * 
   * 配合 `props.activeModifiers` 的对象写法，可以实现受控。
   */
  onActiveModifiersChange?(activeModifiers: IKeyboardModifiers): void;
}
```

## Known Issues

* `Fn` and `F11` cannot be listened
npm package: @kcuf/react-hook-controllable
Description: React hook - useControllable.
Author: Jianchun Wang
Latest version: 0.1.2

# @kcuf/react-hook-controllable

「可受控」组件 HOOK，跟值及回调在组件的 props 中的名称没有关系。

```ts
import useControllable, {
  useControllableSoftTrim,
  useControllableUnprotected
} from '@kcuf/react-hook-controllable';
```

* `useControllable` 受控组件 hook，适用任何类型，一般不会得到 `undefined` 值
* `useControllableSoftTrim` 受控且软 trim 组件 hook，仅针对 `string` 类型
* `useControllableSoftTrim` 受控组件 hook，适用任何类型，得到的值有可能是 `undefined`

## Why

往往我们在实现受控组件的时候，需要考虑的量有：

* `state.xx`
* `props.xx`
* `props.defaulXx`
* `props.onXxChange`

Hook `useControllable` 可以把这些变数进行优雅整合，避免写可受控组件的场景下遇到的不可受控（比如 `props` 变化未反应到界面的问题）和代码冗余问题。

注意：Hook `useControllable` 跟需要受控的值在 props 中的名称无关，也可以实现 `visible` 的受控：

* `state.visible`
* `props.visible`
* `props.defaulVisible`
* `props.onVisibleChange`

另外，对于字符串类型的输入，在输入的时候直接 `trim` 是非常不好的体验，因为那会让用户输入不了任何形式的空白字符，所以这里提供了 `useControllableSoftTrim` 来解决此类问题。

## 利用 `useControllable` 实现一个「可受控」Input 组件

```tsx
import {
  InputHTMLAttributes,
  ChangeEvent,
  ReactElement,
  useCallback
} from 'react';

import useControllable from '@kcuf/react-hook-controllable';

interface IInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange'> {
  value?: string;
  defaultValue?: string;
  onChange?(value: string, e: ChangeEvent<HTMLInputElement>): void;
}

function Input({
  value,
  defaultValue,
  onChange,
  ...props
}: IInputProps): ReactElement {
  const [controllableValue, setControllableValue] = useControllable('FINAL_DEFAULT', value, defaultValue, onChange);
  const handleOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    setControllableValue(e.target.value, e);
  }, [setControllableValue]);
  
  return <input {...{
    ...props,
    type: 'text',
    value: controllableValue,
    onChange: handleOnChange
  }} />;
}
```

## 利用 `useControllableSoftTrim` 实现软 trim

```tsx
import {
  InputHTMLAttributes,
  ChangeEvent,
  ReactElement,
  useCallback
} from 'react';

import {
  useControllableSoftTrim
} from '@kcuf/react-hook-controllable';

interface IInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'defaultValue' | 'onChange'> {
  trim?: boolean;
  value?: string;
  defaultValue?: string;
  onChange?(value: string, e: ChangeEvent<HTMLInputElement>): void;
}

function Input({
  trim,
  value,
  defaultValue,
  onChange,
  ...props
}: IInputProps): ReactElement {
  const [controllableValue, setControllableValue] = useControllableSoftTrim(trim, value, defaultValue, onChange);
  const handleOnChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    setControllableValue(e.target.value, e);
  }, [setControllableValue]);
  
  return <input {...{
    ...props,
    type: 'text',
    value: controllableValue,
    onChange: handleOnChange
  }} />;
}
```

这样，在敲空格的时候，是不会触发 `onChange` 的。