Repository: ant-design/ant-design-mobile
Pull request #6324 (2023-08-16 15:44:44)

chore: feature merge master

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) Report
Patch coverage: **`78.72%`** and project coverage change: **`+0.52%`** :tada:
> Comparison is base [(`ac7c2be`)](https://app.codecov.io/gh/ant-design/ant-design-mobile/commit/ac7c2be981625668e042eb26eb142d22350e4a2a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) 91.06% compared to head [(`1e853fa`)](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) 91.58%.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           feature    #6324      +/-   ##
===========================================
+ Coverage    91.06%   91.58%   +0.52%     
===========================================
  Files          307      308       +1     
  Lines         6600     6609       +9     
  Branches      1645     1629      -16     
===========================================
+ Hits          6010     6053      +43     
+ Misses         544      521      -23     
+ Partials        46       35      -11     
```


| [Files Changed](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) | Coverage Δ | |
|---|---|---|
| [src/components/input/input.tsx](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2NvbXBvbmVudHMvaW5wdXQvaW5wdXQudHN4) | `97.46% <ø> (ø)` | |
| [src/components/rate/rate.tsx](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2NvbXBvbmVudHMvcmF0ZS9yYXRlLnRzeA==) | `100.00% <ø> (ø)` | |
| [src/components/tag/tag.tsx](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2NvbXBvbmVudHMvdGFnL3RhZy50c3g=) | `100.00% <ø> (ø)` | |
| [src/locales/base.ts](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2xvY2FsZXMvYmFzZS50cw==) | `100.00% <ø> (ø)` | |
| [src/locales/da-DK.ts](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2xvY2FsZXMvZGEtREsudHM=) | `100.00% <ø> (ø)` | |
| [src/locales/es-ES.ts](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2xvY2FsZXMvZXMtRVMudHM=) | `100.00% <ø> (ø)` | |
| [src/locales/fa-IR.ts](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2xvY2FsZXMvZmEtSVIudHM=) | `100.00% <ø> (ø)` | |
| [src/locales/fr-FR.ts](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2xvY2FsZXMvZnItRlIudHM=) | `100.00% <ø> (ø)` | |
| [src/locales/id-ID.ts](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2xvY2FsZXMvaWQtSUQudHM=) | `100.00% <ø> (ø)` | |
| [src/locales/it-IT.ts](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2xvY2FsZXMvaXQtSVQudHM=) | `100.00% <ø> (ø)` | |
| ... and [24 more](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) | |

... and [2 files with indirect coverage changes](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/ant-design/ant-design-mobile/pull/6324?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).

github-actions[bot]: PR preview has been successfully built and deployed to https://antd-mobile-preview-pr-6324.surge.sh

Patch:
diff --git a/package.json b/package.json
index 34a9c0ff0..7195ebde5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "antd-mobile",
-  "version": "5.31.1",
+  "version": "5.32.0",
   "scripts": {
     "start": "dumi dev",
     "build": "gulp",
diff --git a/src/components/action-sheet/action-sheet.less b/src/components/action-sheet/action-sheet.less
index 8a69de440..ad5df60cf 100644
--- a/src/components/action-sheet/action-sheet.less
+++ b/src/components/action-sheet/action-sheet.less
@@ -31,7 +31,7 @@
     background-color: var(--adm-color-background);
     padding: 16px;
     &:active {
-      background-color: #eeeeee;
+      background-color: var(--adm-color-border);
     }
     &-disabled {
       cursor: not-allowed;
diff --git a/src/components/button/button.less b/src/components/button/button.less
index f85502791..9dae61476 100644
--- a/src/components/button/button.less
+++ b/src/components/button/button.less
@@ -42,8 +42,9 @@
     );
     width: 100%;
     height: 100%;
-    background-color: #000;
-    border: var(--border-width) var(--border-style) #000;
+    background-color: var(--adm-color-text-dark-solid);
+    border: var(--border-width) var(--border-style)
+      var(--adm-color-text-dark-solid);
     border-radius: var(--border-radius);
     opacity: 0;
     content: ' ';
diff --git a/src/components/calendar/calendar.tsx b/src/components/calendar/calendar.tsx
index 63e5a707d..641267eae 100644
--- a/src/components/calendar/calendar.tsx
+++ b/src/components/calendar/calendar.tsx
@@ -15,6 +15,7 @@ import { useConfig } from '../config-provider'
 import isoWeek from 'dayjs/plugin/isoWeek'
 import { useUpdateEffect } from 'ahooks'
 import { usePropsValue } from '../../utils/use-props-value'
+import { replaceMessage } from '../../utils/replace-message'
 import {
   convertValueToRange,
   convertPageToDayjs,
@@ -171,10 +172,10 @@ export const Calendar = forwardRef<CalendarRef, CalendarProps>((p, ref) => {
         {props.prevMonthButton}
       </a>
       <div className={`${classPrefix}-title`}>
-        {locale.Calendar.renderYearAndMonth(
-          current.year(),
-          current.month() + 1
-        )}
+        {replaceMessage(locale.Calendar.yearAndMonth, {
+          year: current.year().toString(),
+          month: (current.month() + 1).toString(),
+        })}
       </div>
       <a
         className={classNames(
diff --git a/src/components/center-popup/center-popup.tsx b/src/components/center-popup/center-popup.tsx
index c4d8a88a0..38a597b3a 100644
--- a/src/components/center-popup/center-popup.tsx
+++ b/src/components/center-popup/center-popup.tsx
@@ -17,6 +17,8 @@ import {
   PopupBaseProps,
 } from '../popup/popup-base-props'
 
+const classPrefix = 'adm-center-popup'
+
 export type CenterPopupProps = PopupBaseProps &
   PropsWithChildren<{
     // These props currently are only used internally. They are not exported to users:
@@ -73,7 +75,7 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
 
   const body = (
     <div
-      className={classNames('adm-center-popup-body', props.bodyClassName)}
+      className={classNames(`${classPrefix}-body`, props.bodyClassName)}
       style={props.bodyStyle}
     >
       {props.children}
@@ -85,7 +87,7 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
     withNativeProps(
       props,
       <div
-        className='adm-center-popup'
+        className={classPrefix}
         style={{
           display: active ? undefined : 'none',
           pointerEvents: active ? undefined : 'none',
@@ -103,13 +105,13 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
               }
             }}
             style={props.maskStyle}
-            className={classNames('adm-center-popup-mask', props.maskClassName)}
+            className={classNames(`${classPrefix}-mask`, props.maskClassName)}
             disableBodyScroll={false}
             stopPropagation={props.stopPropagation}
           />
         )}
         <div
-          className='adm-center-popup-wrap'
+          className={`${classPrefix}-wrap`}
           role={props.role}
           aria-label={props['aria-label']}
         >
@@ -117,7 +119,7 @@ export const CenterPopup: FC<CenterPopupProps> = p => {
             {props.showCloseButton && (
               <a
                 className={classNames(
-                  'adm-center-popup-close',
+                  `${classPrefix}-close`,
                   'adm-plain-anchor'
                 )}
                 onClick={() => {
diff --git a/src/components/checkbox/index.en.md b/src/components/checkbox/index.en.md
index 4b50d679b..08cca2553 100644
--- a/src/components/checkbox/index.en.md
+++ b/src/components/checkbox/index.en.md
@@ -28,7 +28,7 @@ type CheckboxValue = string | number
 | block | Whether to render as a block-level element | `boolean` | `false` |
 | checked | To specify whether selected or not currently | `boolean` | `false` |
 | defaultChecked | Selected or not initially | `boolean` | `false` |
-| disabled | Failure state | `boolean` | `false` |
+| disabled | Disabled state | `boolean` | `false` |
 | icon | The customized `icon` | `(checked: boolean, indeterminate: boolean) => React.ReactNode` | - |
 | id | The id of the input element, often used in conjunction with the label | `string` | - |
 | indeterminate | To set the `indeterminate` state, only responsible for style control | `boolean` | `false` |
diff --git a/src/components/checkbox/index.zh.md b/src/components/checkbox/index.zh.md
index 8278e1345..5358738a7 100644
--- a/src/components/checkbox/index.zh.md
+++ b/src/components/checkbox/index.zh.md
@@ -28,7 +28,7 @@ type CheckboxValue = string | number
 | block | 是否渲染为块级元素 | `boolean` | `false` |
 | checked | 指定当前是否选中 | `boolean` | `false` |
 | defaultChecked | 初始是否选中 | `boolean` | `false` |
-| disabled | 失效状态 | `boolean` | `false` |
+| disabled | 禁用状态 | `boolean` | `false` |
 | icon | 自定义 `icon` 图标 | `(checked: boolean, indeterminate: boolean) => React.ReactNode` | - |
 | id | `input` 元素的 `id`，常用来配合 `label` 使用 | `string` | - |
 | indeterminate | 设置 `indeterminate` 状态，只负责样式控制 | `boolean` | `false` |
diff --git a/src/components/collapse/collapse.tsx b/src/components/collapse/collapse.tsx
index 8e1901574..569cb3dc4 100644
--- a/src/components/collapse/collapse.tsx
+++ b/src/components/collapse/collapse.tsx
@@ -130,30 +130,44 @@ export const Collapse: FC<CollapseProps> = props => {
     panels.push(child)
   })
 
-  const [activeKey, setActiveKey] = usePropsValue<string[]>(
-    props.accordion
-      ? {
-          value:
-            props.activeKey === undefined
-              ? undefined
-              : props.activeKey === null
-              ? []
-              : [props.activeKey],
-          defaultValue:
-            props.defaultActiveKey === undefined ||
-            props.defaultActiveKey === null
-              ? []
-              : [props.defaultActiveKey],
-          onChange: v => {
-            props.onChange?.(v[0] ?? null)
-          },
-        }
-      : {
-          value: props.activeKey,
-          defaultValue: props.defaultActiveKey ?? [],
-          onChange: props.onChange,
-        }
-  )
+  const handlePropsValue = () => {
+    if (!props.accordion) {
+      return {
+        value: props.activeKey,
+        defaultValue: props.defaultActiveKey ?? [],
+        onChange: props.onChange,
+      }
+    }
+
+    const initValue: {
+      value?: string[]
+      defaultValue: string[]
+      onChange: (v: string[]) => void
+    } = {
+      value: [],
+      defaultValue: [],
+      onChange: v => {
+        props.onChange?.(v[0] ?? null)
+      },
+    }
+
+    if (props.activeKey === undefined) {
+      initValue.value = undefined
+    } else if (props.activeKey !== null) {
+      initValue.value = [props.activeKey]
+    }
+
+    if (
+      ![null, undefined].includes(props.defaultActiveKey as null | undefined)
+    ) {
+      initValue.defaultValue = [props.defaultActiveKey as string]
+    }
+
+    return initValue
+  }
+
+  const [activeKey, setActiveKey] = usePropsValue<string[]>(handlePropsValue())
+
   const activeKeyList =
     activeKey === null ? [] : Array.isArray(activeKey) ? activeKey : [activeKey]
 
diff --git a/src/components/ellipsis/ellipsis.tsx b/src/components/ellipsis/ellipsis.tsx
index a1c98b970..84153347a 100644
--- a/src/components/ellipsis/ellipsis.tsx
+++ b/src/components/ellipsis/ellipsis.tsx
@@ -195,34 +195,32 @@ export const Ellipsis: FC<EllipsisProps> = p => {
   ])
 
   const expandActionElement =
-    exceeded && props.expandText
-      ? withStopPropagation(
-          props.stopPropagationForActionButtons,
-          <a
-            ref={expandElRef}
-            onClick={() => {
-              setExpanded(true)
-            }}
-          >
-            {props.expandText}
-          </a>
-        )
-      : null
+    !!props.expandText &&
+    withStopPropagation(
+      props.stopPropagationForActionButtons,
+      <a
+        ref={expandElRef}
+        onClick={() => {
+          setExpanded(true)
+        }}
+      >
+        {props.expandText}
+      </a>
+    )
 
   const collapseActionElement =
-    exceeded && props.collapseText
-      ? withStopPropagation(
-          props.stopPropagationForActionButtons,
-          <a
-            ref={collapseElRef}
-            onClick={() => {
-              setExpanded(false)
-            }}
-          >
-            {props.collapseText}
-          </a>
-        )
-      : null
+    !!props.collapseText &&
+    withStopPropagation(
+      props.stopPropagationForActionButtons,
+      <a
+        ref={collapseElRef}
+        onClick={() => {
+          setExpanded(false)
+        }}
+      >
+        {props.collapseText}
+      </a>
+    )
 
   const renderContent = () => {
     if (!exceeded) return props.content
diff --git a/src/components/error-block/create-error-block.tsx b/src/components/error-block/create-error-block.tsx
index 20ad1e528..0a7bab652 100644
--- a/src/components/error-block/create-error-block.tsx
+++ b/src/components/error-block/create-error-block.tsx
@@ -31,9 +31,10 @@ export function createErrorBlock(imageRecord: ImageRecord) {
     const props = mergeProps(defaultProps, p)
     const { locale } = useConfig()
     const contentPack = locale.ErrorBlock[props.status]
-    const des =
+    const desc =
       'description' in props ? props.description : contentPack.description
     const title = 'title' in props ? props.title : contentPack.title
+
     const image: ReactNode = props.image ?? imageRecord[props.status]
     const imageNode =
       typeof image === 'string' ? (
@@ -51,11 +52,12 @@ export function createErrorBlock(imageRecord: ImageRecord) {
       >
         <div className={`${classPrefix}-image`}>{imageNode}</div>
         <div className={`${classPrefix}-description`}>
-          {title && (
+          {![undefined, null].includes(title as null) && (
             <div className={`${classPrefix}-description-title`}>{title}</div>
           )}
-          {des && (
-            <div className={`${classPrefix}-description-subtitle`}>{des}</div>
+
+          {![undefined, null].includes(desc as null) && (
+            <div className={`${classPrefix}-description-subtitle`}>{desc}</div>
           )}
         </div>
 
diff --git a/src/components/error-block/error-block.less b/src/components/error-block/error-block.less
index dc3061c22..315d181bb 100644
--- a/src/components/error-block/error-block.less
+++ b/src/components/error-block/error-block.less
@@ -26,7 +26,7 @@
 
   &-description {
     font-size: var(--adm-font-size-4);
-    color: #999;
+    color: var(--adm-color-weak);
     line-height: 1.4;
     margin-top: 12px;
     &-title {
diff --git a/src/components/floating-panel/floating-panel.tsx b/src/components/floating-panel/floating-panel.tsx
index 7a15fa598..5d2f37b96 100644
--- a/src/components/floating-panel/floating-panel.tsx
+++ b/src/components/floating-panel/floating-panel.tsx
@@ -9,6 +9,8 @@ import { mergeProps } from '../../utils/with-default-props'
 import { useLockScroll } from '../../utils/use-lock-scroll'
 import { useMemoizedFn } from 'ahooks'
 
+const classPrefix = 'adm-floating-panel'
+
 export type FloatingPanelRef = {
   setHeight: (
     height: number,
@@ -83,7 +85,7 @@ export const FloatingPanel = forwardRef<FloatingPanelRef, FloatingPanelProps>(
         setPulling(pullingRef.current)
         if (!pullingRef.current) return
         const { event } = state
-        if (event.cancelable) {
+        if (event.cancelable && supportsPassive) {
           event.preventDefault()
         }
         event.stopPropagation()
@@ -104,9 +106,7 @@ export const FloatingPanel = forwardRef<FloatingPanelRef, FloatingPanelProps>(
         from: () => [0, y.get()],
         pointer: { touch: true },
         target: elementRef,
-        eventOptions: supportsPassive
-          ? { passive: false }
-          : (false as unknown as AddEventListenerOptions),
+        eventOptions: supportsPassive ? { passive: false } : undefined,
       }
     )
 
@@ -134,22 +134,22 @@ export const FloatingPanel = forwardRef<FloatingPanelRef, FloatingPanelProps>(
       props,
       <animated.div
         ref={elementRef}
-        className='adm-floating-panel'
+        className={classPrefix}
         style={{
           height: Math.round(maxHeight),
           translateY: y.to(y => `calc(100% + (${Math.round(y)}px))`),
         }}
       >
         <div
-          className='adm-floating-panel-mask'
+          className={`${classPrefix}-mask`}
           style={{
             display: pulling ? 'block' : 'none',
           }}
         />
-        <div className='adm-floating-panel-header' ref={headerRef}>
-          <div className='adm-floating-panel-bar' />
+        <div className={`${classPrefix}-header`} ref={headerRef}>
+          <div className={`${classPrefix}-bar`} />
         </div>
-        <div className='adm-floating-panel-content' ref={contentRef}>
+        <div className={`${classPrefix}-content`} ref={contentRef}>
           {props.children}
         </div>
       </animated.div>
diff --git a/src/components/form/demos/demo-subscribe.tsx b/src/components/form/demos/demo-subscribe.tsx
index bed1d8be1..59395ef96 100644
--- a/src/components/form/demos/demo-subscribe.tsx
+++ b/src/components/form/demos/demo-subscribe.tsx
@@ -45,22 +45,20 @@ export default function () {
       </Form.Item>
 
       <Form.Subscribe to={['loginMethod']}>
-        {({ loginMethod }) => {
-          return (
-            <>
-              {loginMethod === 'mobile' && (
-                <Form.Item name='account' label='手机号'>
-                  <Input placeholder='请输入手机号' />
-                </Form.Item>
-              )}
-              {loginMethod === 'email' && (
-                <Form.Item name='account' label='邮箱'>
-                  <Input placeholder='请输入邮箱' />
-                </Form.Item>
-              )}
-            </>
-          )
-        }}
+        {({ loginMethod }) => (
+          <>
+            {loginMethod === 'mobile' && (
+              <Form.Item name='account' label='手机号'>
+                <Input placeholder='请输入手机号' />
+              </Form.Item>
+            )}
+            {loginMethod === 'email' && (
+              <Form.Item name='account' label='邮箱'>
+                <Input placeholder='请输入邮箱' />
+              </Form.Item>
+            )}
+          </>
+        )}
       </Form.Subscribe>
     </Form>
   )
diff --git a/src/components/form/demos/demo2.tsx b/src/components/form/demos/demo2.tsx
index 47212bd9d..9486760b2 100644
--- a/src/components/form/demos/demo2.tsx
+++ b/src/components/form/demos/demo2.tsx
@@ -52,13 +52,9 @@ export default () => {
         </Form.Item>
         <Form.Item
           label='表单联动-字段B'
-          shouldUpdate={(prevValues, curValues) => {
-            return prevValues.b !== curValues.b
-          }}
+          shouldUpdate={(prevValues, curValues) => prevValues.b !== curValues.b}
         >
-          {({ getFieldValue }) => {
-            return JSON.stringify(getFieldValue('b'))
-          }}
+          {({ getFieldValue }) => JSON.stringify(getFieldValue('b'))}
         </Form.Item>
         <DatePickerInputItem />
       </Form>
diff --git a/src/components/form/demos/demo4.tsx b/src/components/form/demos/demo4.tsx
index 4d3e76679..402f43429 100644
--- a/src/components/form/demos/demo4.tsx
+++ b/src/components/form/demos/demo4.tsx
@@ -5,7 +5,7 @@ import type { PickerValue } from 'antd-mobile/es/components/picker'
 import { DownOutline } from 'antd-mobile-icons'
 
 interface MobileValue {
-  preValue: string
+  preValue: string | number
   realValue: string
 }
 
diff --git a/src/components/form/form-item.tsx b/src/components/form/form-item.tsx
index 26993dd24..3d21e3b03 100644
--- a/src/components/form/form-item.tsx
+++ b/src/components/form/form-item.tsx
@@ -3,14 +3,14 @@ import type { FC, ReactNode, MutableRefObject } from 'react'
 import classNames from 'classnames'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
 import { Field, FormInstance } from 'rc-field-form'
-import type { FieldProps } from 'rc-field-form/lib/Field'
 import FieldContext from 'rc-field-form/lib/FieldContext'
+import type { FieldProps } from 'rc-field-form/lib/Field'
 import type { Meta, InternalNamePath } from 'rc-field-form/lib/interface'
+import type { FormLayout } from './index'
 import { devWarning } from '../../utils/dev-log'
 import { FormContext, NoStyleItemContext } from './context'
 import { toArray, isSafeSetRefComponent } from './utils'
 import List, { ListItemProps } from '../list'
-import type { FormLayout } from './index'
 import Popover from '../popover'
 import { QuestionCircleOutline } from 'antd-mobile-icons'
 import { useConfig } from '../config-provider'
@@ -96,7 +96,6 @@ type FormItemLayoutProps = Pick<
 
 const FormItemLayout: FC<FormItemLayoutProps> = props => {
   const {
-    className,
     style,
     extra,
     label,
@@ -150,7 +149,7 @@ const FormItemLayout: FC<FormItemLayoutProps> = props => {
     }
   })()
 
-  const labelElement = label ? (
+  const labelElement = !!label && (
     <label className={`${classPrefix}-label`} htmlFor={htmlFor}>
       {label}
       {requiredMark}
@@ -167,34 +166,33 @@ const FormItemLayout: FC<FormItemLayoutProps> = props => {
         </Popover>
       )}
     </label>
-  ) : null
-
-  const description =
-    props.description || hasFeedback ? (
-      <>
-        {props.description}
-        {hasFeedback && (
-          <>
-            {props.errors.map((error, index) => (
-              <div
-                key={`error-${index}`}
-                className={`${classPrefix}-feedback-error`}
-              >
-                {error}
-              </div>
-            ))}
-            {props.warnings.map((warning, index) => (
-              <div
-                key={`warning-${index}`}
-                className={`${classPrefix}-feedback-warning`}
-              >
-                {warning}
-              </div>
-            ))}
-          </>
-        )}
-      </>
-    ) : null
+  )
+
+  const description = (!!props.description || hasFeedback) && (
+    <>
+      {props.description}
+      {hasFeedback && (
+        <>
+          {props.errors.map((error, index) => (
+            <div
+              key={`error-${index}`}
+              className={`${classPrefix}-feedback-error`}
+            >
+              {error}
+            </div>
+          ))}
+          {props.warnings.map((warning, index) => (
+            <div
+              key={`warning-${index}`}
+              className={`${classPrefix}-feedback-warning`}
+            >
+              {warning}
+            </div>
+          ))}
+        </>
+      )}
+    </>
+  )
 
   return withNativeProps(
     props,
@@ -204,15 +202,10 @@ const FormItemLayout: FC<FormItemLayoutProps> = props => {
       prefix={layout === 'horizontal' && labelElement}
       extra={extra}
       description={description}
-      className={classNames(
-        classPrefix,
-        className,
-        `${classPrefix}-${layout}`,
-        {
-          [`${classPrefix}-hidden`]: hidden,
-          [`${classPrefix}-has-error`]: props.errors.length,
-        }
-      )}
+      className={classNames(classPrefix, `${classPrefix}-${layout}`, {
+        [`${classPrefix}-hidden`]: hidden,
+        [`${classPrefix}-has-error`]: props.errors.length,
+      })}
       disabled={disabled}
       onClick={props.onClick}
       clickable={props.clickable}
@@ -235,7 +228,6 @@ const FormItemLayout: FC<FormItemLayoutProps> = props => {
 export const FormItem: FC<FormItemProps> = props => {
   const {
     // 样式相关
-    className,
     style,
     // FormItem 相关
     label,
@@ -331,7 +323,6 @@ export const FormItem: FC<FormItemProps> = props => {
     return withNativeProps(
       props,
       <FormItemLayout
-        className={className}
         style={style}
         label={label}
         extra={extra}
diff --git a/src/components/form/form.less b/src/components/form/form.less
index 1a9259631..9adaf92ad 100644
--- a/src/components/form/form.less
+++ b/src/components/form/form.less
@@ -29,5 +29,5 @@
 
 .adm-form-list-operation {
   text-align: center;
-  color: #1677ff;
+  color: var(--adm-color-primary);
 }
diff --git a/src/components/image-uploader/image-uploader.less b/src/components/image-uploader/image-uploader.less
index 50743d28d..dfaba83e8 100644
--- a/src/components/image-uploader/image-uploader.less
+++ b/src/components/image-uploader/image-uploader.less
@@ -86,7 +86,7 @@
       display: block;
 
       &-icon {
-        color: #999999;
+        color: var(--adm-color-weak);
         font-size: 32px;
       }
     }
diff --git a/src/components/input/input.tsx b/src/components/input/input.tsx
index 0ab19f3d0..94d8dfdc7 100644
--- a/src/components/input/input.tsx
+++ b/src/components/input/input.tsx
@@ -42,13 +42,13 @@ export type InputProps = Pick<
   | 'onClick'
   | 'step'
   | 'id'
+  | 'placeholder'
+  | 'readOnly'
+  | 'disabled'
 > & {
   value?: string
   defaultValue?: string
   onChange?: (val: string) => void
-  placeholder?: string
-  disabled?: boolean
-  readOnly?: boolean
   clearable?: boolean
   onlyShowClearWhenFocus?: boolean
   onClear?: () => void
diff --git a/src/components/notice-bar/notice-bar.less b/src/components/notice-bar/notice-bar.less
index bb2082e37..e096b66d4 100644
--- a/src/components/notice-bar/notice-bar.less
+++ b/src/components/notice-bar/notice-bar.less
@@ -2,7 +2,7 @@
 
 .@{class-prefix-notice-bar} {
   --background-color: #ababab;
-  --border-color: #999999;
+  --border-color: var(--adm-color-weak);
   --text-color: var(--adm-color-text-light-solid);
   --font-size: var(--adm-font-size-7);
   --icon-font-size: var(--adm-font-size-10);
@@ -38,7 +38,7 @@
   &.@{class-prefix-notice-bar}-info {
     --background-color: #d0e4ff;
     --border-color: #bcd8ff;
-    --text-color: #1677ff;
+    --text-color: var(--adm-color-primary);
   }
 
   & .@{class-prefix-notice-bar}-left {
diff --git a/src/components/number-keyboard/number-keyboard.less b/src/components/number-keyboard/number-keyboard.less
index d23bfb2b1..e6733338b 100644
--- a/src/components/number-keyboard/number-keyboard.less
+++ b/src/components/number-keyboard/number-keyboard.less
@@ -83,9 +83,9 @@
       left: 50%;
       width: 100%;
       height: 100%;
-      background-color: #000;
+      background-color: var(--adm-color-text-dark-solid);
       border: inherit;
-      border-color: #000;
+      border-color: var(--adm-color-text-dark-solid);
       border-radius: inherit;
       transform: translate(-50%, -50%);
       opacity: 0;
diff --git a/src/components/picker-view/demos/demo1.tsx b/src/components/picker-view/demos/demo1.tsx
index bf29028c9..a29740c67 100644
--- a/src/components/picker-view/demos/demo1.tsx
+++ b/src/components/picker-view/demos/demo1.tsx
@@ -1,10 +1,11 @@
 import React, { useState } from 'react'
 import { PickerView } from 'antd-mobile'
+import type { PickerValue } from 'antd-mobile/es/components/picker-view'
 import { DemoBlock } from 'demos'
 import { basicColumns } from './columns-data'
 
 export default () => {
-  const [value, setValue] = useState<(string | null)[]>(['Mon', 'am'])
+  const [value, setValue] = useState<PickerValue[]>(['Mon', 'am'])
 
   return (
     <>
diff --git a/src/components/picker-view/wheel.tsx b/src/components/picker-view/wheel.tsx
index 225420fd3..9283ec423 100644
--- a/src/components/picker-view/wheel.tsx
+++ b/src/components/picker-view/wheel.tsx
@@ -86,7 +86,7 @@ export const Wheel = memo<Props>(
       onSelect(item.value)
     }
 
-    const handleDrag = (
+    const handleGestureState = (
       state:
         | (Omit<FullGestureState<'wheel'>, 'event'> & {
             event: EventTypes['wheel']
@@ -94,21 +94,44 @@ export const Wheel = memo<Props>(
         | (Omit<FullGestureState<'drag'>, 'event'> & {
             event: EventTypes['drag']
           })
+    ) => {
+      const {
+        direction: [, direction],
+        distance: [, distance],
+        velocity: [, velocity],
+        offset: [, offset],
+        last,
+      } = state
+      return {
+        direction,
+        distance,
+        velocity,
+        offset,
+        last,
+      }
+    }
+
+    const handleDrag = (
+      state: Omit<FullGestureState<'drag'>, 'event'> & {
+        event: EventTypes['drag']
+      }
     ) => {
       draggingRef.current = true
       const min = -((column.length - 1) * itemHeight.current)
       const max = 0
-      if (state.last) {
+      const { direction, last, velocity, offset } = handleGestureState(state)
+
+      if (last) {
         draggingRef.current = false
-        const position =
-          state.offset[1] + state.velocity[1] * state.direction[1] * 50
-        const targetIndex =
-          min < max
-            ? -Math.round(bound(position, min, max) / itemHeight.current)
-            : 0
+
+        const position = offset + velocity * direction * 50
+        const boundNum = bound(position, min, max)
+        const targetIndex = -Math.round(boundNum / itemHeight.current)
+
         scrollSelect(targetIndex)
       } else {
-        const position = state.offset[1]
+        const position = offset
+
         api.start({
           y: rubberbandIfOutOfBounds(
             position,
@@ -120,7 +143,41 @@ export const Wheel = memo<Props>(
         })
       }
     }
+    const handleWheel = (
+      state: Omit<FullGestureState<'wheel'>, 'event'> & {
+        event: EventTypes['wheel']
+      }
+    ) => {
+      draggingRef.current = true
+      const min = -((column.length - 1) * itemHeight.current)
+      const max = 0
+      const { direction, last, velocity, distance } = handleGestureState(state)
+      const whellDir = -direction // 取反
+      const scrollY = y.get()
+
+      if (last) {
+        draggingRef.current = false
+
+        const speed = velocity * whellDir * 50
+        const position = scrollY + distance * whellDir + speed
+        const boundNum = bound(position, min, max)
+        const targetIndex = -Math.round(boundNum / itemHeight.current)
 
+        scrollSelect(targetIndex)
+      } else {
+        const position = scrollY + distance * whellDir
+
+        api.start({
+          y: rubberbandIfOutOfBounds(
+            position,
+            min,
+            max,
+            itemHeight.current * 50,
+            0.2
+          ),
+        })
+      }
+    }
     useDrag(
       state => {
         state.event.stopPropagation()
@@ -138,16 +195,14 @@ export const Wheel = memo<Props>(
     useWheel(
       state => {
         state.event.stopPropagation()
-        handleDrag(state)
+        handleWheel(state)
       },
       {
+        target: props.mouseWheel ? rootRef : undefined,
         axis: 'y',
         from: () => [0, y.get()],
         preventDefault: true,
-        target: props.mouseWheel ? rootRef : undefined,
-        eventOptions: supportsPassive
-          ? { passive: false }
-          : (false as unknown as AddEventListenerOptions),
+        eventOptions: supportsPassive ? { passive: false } : undefined,
       }
     )
 
@@ -163,9 +218,9 @@ export const Wheel = memo<Props>(
       const previous = column[previousIndex]
       const next = column[nextIndex]
       return (
-        <div className='adm-picker-view-column-accessible'>
+        <div className={`${classPrefix}-column-accessible`}>
           <div
-            className='adm-picker-view-column-accessible-current'
+            className={`${classPrefix}-column-accessible-current`}
             role='button'
             aria-label={
               current ? `当前选择的是：${current.label}` : '当前未选择'
@@ -174,7 +229,7 @@ export const Wheel = memo<Props>(
             -
           </div>
           <div
-            className='adm-picker-view-column-accessible-button'
+            className={`${classPrefix}-column-accessible-button`}
             onClick={() => {
               if (!previous) return
               scrollSelect(previousIndex)
@@ -187,7 +242,7 @@ export const Wheel = memo<Props>(
             -
           </div>
           <div
-            className='adm-picker-view-column-accessible-button'
+            className={`${classPrefix}-column-accessible-button`}
             onClick={() => {
               if (!next) return
               scrollSelect(nextIndex)
@@ -246,9 +301,8 @@ export const Wheel = memo<Props>(
     if (prev.onSelect !== next.onSelect) return false
     if (prev.renderLabel !== next.renderLabel) return false
     if (prev.mouseWheel !== next.mouseWheel) return false
-    if (!isEqual(prev.column, next.column)) {
-      return false
-    }
+    if (!isEqual(prev.column, next.column)) return false
+
     return true
   }
 )
diff --git a/src/components/picker/index.zh.md b/src/components/picker/index.zh.md
index 4b1b100d1..79876746c 100644
--- a/src/components/picker/index.zh.md
+++ b/src/components/picker/index.zh.md
@@ -155,7 +155,7 @@ type PickerDate = Date & {
 | 属性 | 说明 | 类型 | 默认值 | 版本 |
 | --- | --- | --- | --- | --- |
 | children | 所选项的渲染函数 | `(value: PickerDate, actions: PickerActions) => ReactNode` | - |
-| defaultValue | 选中值 | `PickerDate` | - |
+| defaultValue | 默认选中值 | `PickerDate` | - |
 | destroyOnClose | 不可见时是否销毁 `DOM` 结构 | `boolean` | `false` |
 | filter | 过滤可供选择的时间 | `DatePickerFilter` | - |
 | forceRender | 强制渲染内容 | `boolean` | `false` |
diff --git a/src/components/popover/index.en.md b/src/components/popover/index.en.md
index b9cb1c3e4..54fcd6145 100644
--- a/src/components/popover/index.en.md
+++ b/src/components/popover/index.en.md
@@ -22,7 +22,7 @@ Navigation applicable to functions can only be evoked by the icons on the naviga
 
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
-| children | The element that triggered the popover | `React.ReactElement` | - |
+| children | The element that triggered the Popover | `React.ReactElement` | - |
 | content | The content of the Popover | `React.ReactNode` | - |
 | defaultVisible | Whether to show or hide by default | `boolean` | `false` |
 | destroyOnHide | When hiding, whether to destroy the content of `tooltip` | `boolean` | `false` |
diff --git a/src/components/popover/index.zh.md b/src/components/popover/index.zh.md
index d28aa5e41..b09e4b0a4 100644
--- a/src/components/popover/index.zh.md
+++ b/src/components/popover/index.zh.md
@@ -22,7 +22,7 @@
 
 | 属性 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| children | 触发 `popover` 的元素 | `React.ReactElement` | - |
+| children | 触发 `Popover` 的元素 | `React.ReactElement` | - |
 | content | 弹出内容 | `React.ReactNode` | - |
 | defaultVisible | 默认是否显隐 | `boolean` | `false` |
 | destroyOnHide | 隐藏时，是否销毁 `tooltip` 内容 | `boolean` | `false` |
diff --git a/src/components/popover/popover-menu.less b/src/components/popover/popover-menu.less
index 00b920ace..f3971b5b2 100644
--- a/src/components/popover/popover-menu.less
+++ b/src/components/popover/popover-menu.less
@@ -1,5 +1,5 @@
 .adm-popover-menu {
-  --border-color: #eeeeee;
+  --border-color: var(--adm-color-border);
 
   &.adm-popover {
     --content-padding: 0;
@@ -52,7 +52,7 @@
 
 .adm-popover.adm-popover-dark {
   &.adm-popover-menu {
-    --border-color: #333333;
+    --border-color: var(--adm-color-text);
     --background: rgba(0, 0, 0, 0.9);
   }
 }
diff --git a/src/components/progress-bar/index.zh.md b/src/components/progress-bar/index.zh.md
index 02320286d..abcd14a5d 100644
--- a/src/components/progress-bar/index.zh.md
+++ b/src/components/progress-bar/index.zh.md
@@ -18,7 +18,7 @@
 
 | 参数 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
-| percent | 百分比 | `number` | `0` |
+| percent | 进度条百分比 | `number` | `0` |
 | rounded | 是否圆角 | `boolean` | `true` |
 | text | 是否显示进度文字，支持自定义渲染内容 | `boolean \| ReactNode \| ((percent: number) => ReactNode)` | `false` |
 
diff --git a/src/components/progress-bar/progress-bar.less b/src/components/progress-bar/progress-bar.less
index 00c493b77..fcaf30c9a 100644
--- a/src/components/progress-bar/progress-bar.less
+++ b/src/components/progress-bar/progress-bar.less
@@ -23,7 +23,7 @@
     flex: none;
     width: calc(var(--text-width) + 8px);
     padding-left: 8px;
-    color: #999999;
+    color: var(--adm-color-weak);
   }
 }
 
diff --git a/src/components/progress-circle/index.zh.md b/src/components/progress-circle/index.zh.md
index 7e9b6e27e..1996ed9d7 100644
--- a/src/components/progress-circle/index.zh.md
+++ b/src/components/progress-circle/index.zh.md
@@ -14,10 +14,10 @@
 
 ### 属性
 
-| 参数     | 说明       | 类型              | 默认值 |
-| -------- | ---------- | ----------------- | ------ |
-| children | 自定义信息 | `React.ReactNode` | -      |
-| percent  | 百分比     | `number`          | `0`    |
+| 参数     | 说明         | 类型              | 默认值 |
+| -------- | ------------ | ----------------- | ------ |
+| children | 自定义信息   | `React.ReactNode` | -      |
+| percent  | 进度圈百分比 | `number`          | `0`    |
 
 ### CSS 变量
 
diff --git a/src/components/pull-to-refresh/pull-to-refresh.tsx b/src/components/pull-to-refresh/pull-to-refresh.tsx
index f9ade9962..dd34aa3f9 100644
--- a/src/components/pull-to-refresh/pull-to-refresh.tsx
+++ b/src/components/pull-to-refresh/pull-to-refresh.tsx
@@ -162,9 +162,7 @@ export const PullToRefresh: FC<PullToRefreshProps> = p => {
       axis: 'y',
       target: elementRef,
       enabled: !props.disabled,
-      eventOptions: supportsPassive
-        ? { passive: false }
-        : (false as unknown as AddEventListenerOptions),
+      eventOptions: supportsPassive ? { passive: false } : undefined,
     }
   )
 
diff --git a/src/components/radio/index.zh.md b/src/components/radio/index.zh.md
index b9d448e89..93996260f 100644
--- a/src/components/radio/index.zh.md
+++ b/src/components/radio/index.zh.md
@@ -27,7 +27,7 @@ type RadioValue = string | number
 | block | 是否渲染为块级元素 | `boolean` | `false` |
 | checked | 指定当前是否选中 | `boolean` | `false` |
 | defaultChecked | 初始是否选中 | `boolean` | `false` |
-| disabled | 失效状态 | `boolean` | `false` |
+| disabled | 禁用状态 | `boolean` | `false` |
 | icon | 自定义 `icon` 图标 | `(checked: boolean) => React.ReactNode` | - |
 | id | `input` 元素的 `id`，常用来配合 `label` 使用 | `string` | - |
 | onChange | 变化时回调函数 | `(val: boolean) => void` | - |
diff --git a/src/components/rate/rate.tsx b/src/components/rate/rate.tsx
index 4da3b7302..668a03a2a 100644
--- a/src/components/rate/rate.tsx
+++ b/src/components/rate/rate.tsx
@@ -19,7 +19,12 @@ export type RateProps = {
   readOnly?: boolean
   value?: number
   onChange?: (value: number) => void
-} & NativeProps<'--star-size' | '--active-color' | '--inactive-color'>
+} & NativeProps<
+  | '--star-size'
+  | '--active-color'
+  | '--inactive-color'
+  | '--inactive-color-half'
+>
 
 const defaultProps = {
   count: 5,
diff --git a/src/components/stepper/index.en.md b/src/components/stepper/index.en.md
index fa958229d..51dd23898 100644
--- a/src/components/stepper/index.en.md
+++ b/src/components/stepper/index.en.md
@@ -32,7 +32,7 @@ It is suitable for inputting and adjusting the current value within a certain ra
 | onChange | Callback when value is changed | `(value: number \| null) => void` | - |
 | onFocus | Triggered when the input get focus | `(e: React.FocusEvent<HTMLInputElement>) => void` | - |
 | parser | Parse input text into number which should work with `formatter` | `(text: string) => number` | - | 5.26.0 |
-| step | Change the number of steps each time, it can be a decimal | `number` | `1` |
+| step | The value to increase or decrease each time, can be a decimal | `number` | `1` |
 | stringMode | Set value as string to support high precision decimals. Will set `defaultValue`,`value`, `min`, `max`, `onChange` to `string` type | `boolean` | `false` | 5.27.0 |
 | value | Current number, controlled value | `number \| null` | - |
 
diff --git a/src/components/stepper/index.zh.md b/src/components/stepper/index.zh.md
index 1af978fe2..77832e9a8 100644
--- a/src/components/stepper/index.zh.md
+++ b/src/components/stepper/index.zh.md
@@ -32,7 +32,7 @@
 | onChange | 变化时的回调 | `(value: number \| null) => void` | - |
 | onFocus | 输入框获得焦点时触发 | `(e: React.FocusEvent<HTMLInputElement>) => void` | - |
 | parser | 将输入解析为对应数字，一般配合 `formatter` 使用 | `(text: string) => number` | - | 5.26.0 |
-| step | 每次改变步数，可以为小数 | `number` | `1` |
+| step | 每次增加或减少的值，可以为小数 | `number` | `1` |
 | stringMode | 字符值模式，开启后支持高精度小数。开启后 `defaultValue`、`value`、`min`、`max`、`onChange` 等都将转换为 `string` 类型 | `boolean` | `false` | 5.27.0 |
 | value | 当前数，受控值 | `number \| null` | - |
 
diff --git a/src/components/steps/steps.tsx b/src/components/steps/steps.tsx
index 914f4bf8a..c672bc3a2 100644
--- a/src/components/steps/steps.tsx
+++ b/src/components/steps/steps.tsx
@@ -1,7 +1,7 @@
 import React from 'react'
 import type { FC, ReactNode } from 'react'
 import classNames from 'classnames'
-import { StepProps } from './step'
+import type { StepProps } from './step'
 import { mergeProps } from '../../utils/with-default-props'
 import { NativeProps, withNativeProps } from '../../utils/native-props'
 
@@ -37,19 +37,19 @@ export const Steps: FC<StepsProps> = p => {
     props,
     <div className={classString}>
       {React.Children.map(props.children, (child, index) => {
-        if (!React.isValidElement(child)) {
+        if (!React.isValidElement<StepProps>(child)) {
           return child
         }
-        const props = child.props as StepProps
-        let status = props.status || 'wait'
+        const childProps = child.props
+        let status = childProps.status || 'wait'
 
         if (index < current) {
-          status = props.status || 'finish'
+          status = childProps.status || 'finish'
         } else if (index === current) {
-          status = props.status || 'process'
+          status = childProps.status || 'process'
         }
 
-        const icon = props.icon ?? defaultIcon
+        const icon = childProps.icon ?? defaultIcon
 
         return React.cloneElement(child, {
           status,
diff --git a/src/components/swipe-action/index.en.md b/src/components/swipe-action/index.en.md
index d0a73019a..ede2be1e0 100644
--- a/src/components/swipe-action/index.en.md
+++ b/src/components/swipe-action/index.en.md
@@ -19,10 +19,10 @@ Swipe to reveal hidden function menus.
 | closeOnAction | Whether to return to the position automatically when the operation button is clicked | `boolean` | `true` |
 | closeOnTouchOutside | Whether to return to the position automatically when other areas is clicked | `boolean` | `true` |
 | leftActions | List of operation buttons on the left | `Action[]` | `[]` |
-| onAction | Triggered when operation button is clicked | `(action: Action, e: React.MouseEvent) => void` | - |
 | rightActions | List of operation buttons on the right | `Action[]` | `[]` |
+| onAction | Triggered when operation button is clicked | `(action: Action, e: React.MouseEvent) => void` | - |
 | stopPropagation | Stop the propagation of some events. | `PropagationEvent[]` | `[]` |
-| onActionsReveal | Triggered when the operation button appears completely | (side: 'left' \| 'right') => void | - |
+| onActionsReveal | Triggered when the operation button appears completely | `(side: 'left' \| 'right') => void` | - |
 
 ### Action
 
diff --git a/src/components/swipe-action/index.zh.md b/src/components/swipe-action/index.zh.md
index 44896474f..b9dbcc08b 100644
--- a/src/components/swipe-action/index.zh.md
+++ b/src/components/swipe-action/index.zh.md
@@ -19,10 +19,10 @@
 | closeOnAction | 是否在点击操作按钮时自动归位 | `boolean` | `true` |
 | closeOnTouchOutside | 是否在点击其他区域时自动归位 | `boolean` | `true` |
 | leftActions | 左侧的操作按钮列表 | `Action[]` | `[]` |
-| onAction | 点击操作按钮时触发 | `(action: Action, e: React.MouseEvent) => void` | - |
 | rightActions | 右侧的操作按钮列表 | `Action[]` | `[]` |
+| onAction | 点击操作按钮时触发 | `(action: Action, e: React.MouseEvent) => void` | - |
 | stopPropagation | 阻止某些事件的冒泡 | `PropagationEven[]` | `[]` |
-| onActionsReveal | 按钮完全出现时触发 | (side: 'left' \| 'right') => void | - |
+| onActionsReveal | 按钮完全出现时触发 | `(side: 'left' \| 'right') => void` | - |
 
 ### Action
 
diff --git a/src/components/swipe-action/swipe-action.tsx b/src/components/swipe-action/swipe-action.tsx
index f3d78f368..d519c3db7 100644
--- a/src/components/swipe-action/swipe-action.tsx
+++ b/src/components/swipe-action/swipe-action.tsx
@@ -19,9 +19,11 @@ import {
 
 const classPrefix = `adm-swipe-action`
 
+type SideType = 'left' | 'right'
+
 export type SwipeActionRef = {
   close: () => void
-  show: (side?: 'left' | 'right') => void
+  show: (side?: SideType) => void
 }
 
 type ActionColor =
@@ -47,7 +49,7 @@ export type SwipeActionProps = {
   closeOnAction?: boolean
   children: ReactNode
   stopPropagation?: PropagationEvent[]
-  onActionsReveal?: (side: 'left' | 'right') => void
+  onActionsReveal?: (side: SideType) => void
 } & NativeProps<'--background'>
 
 const defaultProps = {
@@ -156,7 +158,7 @@ export const SwipeAction = forwardRef<SwipeActionRef, SwipeActionProps>(
     }
 
     useImperativeHandle(ref, () => ({
-      show: (side: 'left' | 'right' = 'right') => {
+      show: (side: SideType = 'right') => {
         if (side === 'right') {
           api.start({
             x: -getRightWidth(),
diff --git a/src/components/switch/index.en.md b/src/components/switch/index.en.md
index 46a8fd86e..e4f5b3e5a 100644
--- a/src/components/switch/index.en.md
+++ b/src/components/switch/index.en.md
@@ -19,7 +19,7 @@ switch selector.
 
 | Name | Description | Type | Default |
 | --- | --- | --- | --- |
-| beforeChange | Execute before change | `(val: boolean) => Promise<void>` | - |
+| beforeChange（deprecated） | Execute before change （Deprecated, recommend using the `onChange` attribute.） | `(val: boolean) => Promise<void>` | - |
 | checked | Specify whether it is currently opened | `boolean` | `false` |
 | checkedText | Selected text | `ReactNode` | - |
 | defaultChecked | Whether to open initially | `boolean` | `false` |
diff --git a/src/components/tag/tag.tsx b/src/components/tag/tag.tsx
index 01226a9eb..098b2a08b 100644
--- a/src/components/tag/tag.tsx
+++ b/src/components/tag/tag.tsx
@@ -7,7 +7,7 @@ import classNames from 'classnames'
 const classPrefix = `adm-tag`
 
 const colorRecord: Record<string, string> = {
-  default: '#666666',
+  default: 'var(--adm-color-text-secondary, #666666)',
   primary: 'var(--adm-color-primary, #1677ff)',
   success: 'var(--adm-color-success, #00b578)',
   warning: 'var(--adm-color-warning, #ff8f1f)',
diff --git a/src/components/text-area/text-area.less b/src/components/text-area/text-area.less
index c2c10679f..048f80f54 100644
--- a/src/components/text-area/text-area.less
+++ b/src/components/text-area/text-area.less
@@ -5,7 +5,7 @@
   --disabled-color: var(--adm-color-weak);
   --text-align: left;
   --count-text-align: right;
-
+  position: relative;
   width: 100%;
   max-width: 100%;
   max-height: 100%;
diff --git a/src/components/text-area/text-area.tsx b/src/components/text-area/text-area.tsx
index dcc3e47df..b799f4001 100644
--- a/src/components/text-area/text-area.tsx
+++ b/src/components/text-area/text-area.tsx
@@ -135,13 +135,23 @@ export const TextArea = forwardRef<TextAreaRef, TextAreaProps>(
       )
     }
 
+    let rows = props.rows
+    if (typeof autoSize === 'object') {
+      if (autoSize.maxRows && rows > autoSize.maxRows) {
+        rows = autoSize.maxRows
+      }
+      if (autoSize.minRows && rows < autoSize.minRows) {
+        rows = autoSize.minRows
+      }
+    }
+
     return withNativeProps(
       props,
       <div className={classPrefix}>
         <textarea
           ref={nativeTextAreaRef}
           className={`${classPrefix}-element`}
-          rows={props.rows}
+          rows={rows}
           value={value}
           placeholder={props.placeholder}
           onChange={e => {
@@ -180,7 +190,7 @@ export const TextArea = forwardRef<TextAreaRef, TextAreaProps>(
             ref={hiddenTextAreaRef}
             className={`${classPrefix}-element ${classPrefix}-element-hidden`}
             value={value}
-            rows={props.rows}
+            rows={rows}
             aria-hidden
             readOnly
           />
diff --git a/src/components/toast/toast.less b/src/components/toast/toast.less
index 417497f27..881634c87 100644
--- a/src/components/toast/toast.less
+++ b/src/components/toast/toast.less
@@ -7,13 +7,13 @@
     left: 0;
     width: 100%;
     height: 100%;
+    text-align: center;
   }
   .@{class-prefix-toast}-main {
     display: inline-block;
     position: relative;
     top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
+    transform: translateY(-50%);
     width: auto;
     max-width: 204px;
     max-height: 70%;
diff --git a/src/components/virtual-input/virtual-input.tsx b/src/components/virtual-input/virtual-input.tsx
index b5eb60295..f25498e8a 100644
--- a/src/components/virtual-input/virtual-input.tsx
+++ b/src/components/virtual-input/virtual-input.tsx
@@ -151,7 +151,7 @@ export const VirtualInput = forwardRef<VirtualInputRef, VirtualInputProps>(
             <CloseCircleFill />
           </div>
         )}
-        {!value && (
+        {[undefined, null, ''].includes(value) && (
           <div className={`${classPrefix}-placeholder`}>
             {props.placeholder}
           </div>
diff --git a/src/locales/base.ts b/src/locales/base.ts
index dc22f84b2..fae8c4039 100644
--- a/src/locales/base.ts
+++ b/src/locales/base.ts
@@ -10,7 +10,7 @@ export const base = {
   },
   Calendar: {
     markItems: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
-    renderYearAndMonth: (year: number, month: number) => `${year}/${month}`,
+    yearAndMonth: '${year}/${month}',
   },
   Cascader: {
     placeholder: 'Selecting',
diff --git a/src/locales/da-DK.ts b/src/locales/da-DK.ts
index 79683c193..6e928f04c 100644
--- a/src/locales/da-DK.ts
+++ b/src/locales/da-DK.ts
@@ -13,7 +13,7 @@ const daDK = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['ma', 'ti', 'on', 'to', 'fr', 'lø', 'sø'],
-    renderYearAndMonth: (year: number, month: number) => `${month}.${year}`,
+    yearAndMonth: '${month}.${year}',
   },
   Cascader: {
     placeholder: 'Vælger',
diff --git a/src/locales/es-ES.ts b/src/locales/es-ES.ts
index fa75a67fe..b7fc56b8b 100644
--- a/src/locales/es-ES.ts
+++ b/src/locales/es-ES.ts
@@ -12,7 +12,7 @@ const esES = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab', 'Dom'],
-    renderYearAndMonth: (year: number, month: number) => `${year}/${month}`,
+    yearAndMonth: '${year}/${month}',
   },
   Cascader: {
     placeholder: 'Seleccionando',
diff --git a/src/locales/fa-IR.ts b/src/locales/fa-IR.ts
index a703ed6d0..a79899abe 100644
--- a/src/locales/fa-IR.ts
+++ b/src/locales/fa-IR.ts
@@ -19,7 +19,7 @@ const faIR = mergeLocale(base, {
       'پنج‌نشبه',
       'جمعه',
     ],
-    renderYearAndMonth: (year: number, month: number) => `${year}/${month}`,
+    yearAndMonth: '${year}/${month}',
   },
   Cascader: {
     placeholder: 'انتخاب کنید',
diff --git a/src/locales/fr-FR.ts b/src/locales/fr-FR.ts
index ce84d14ac..becc24823 100644
--- a/src/locales/fr-FR.ts
+++ b/src/locales/fr-FR.ts
@@ -12,8 +12,7 @@ const frFR = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept'],
-    renderYearAndMonth: (year: number, month: number) =>
-      `${year}Année ${month}Mois`,
+    yearAndMonth: '${year}Année ${month}Mois',
   },
   Cascader: {
     placeholder: 'Veuillez sélectionner',
diff --git a/src/locales/id-ID.ts b/src/locales/id-ID.ts
index b18f3fe7a..545fd7a70 100644
--- a/src/locales/id-ID.ts
+++ b/src/locales/id-ID.ts
@@ -12,8 +12,7 @@ const idID = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'],
-    renderYearAndMonth: (year: number, month: number) =>
-      `${year} Tahun ${month} Bulan`,
+    yearAndMonth: '${year} Tahun ${month} Bulan',
   },
   Cascader: {
     placeholder: 'Silahkan Pilih',
diff --git a/src/locales/it-IT.ts b/src/locales/it-IT.ts
index cb2927995..e3a303dc1 100644
--- a/src/locales/it-IT.ts
+++ b/src/locales/it-IT.ts
@@ -13,7 +13,7 @@ const itIT = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['lun', 'mar', 'mer', 'gio', 'ven', 'sab', 'dom'],
-    renderYearAndMonth: (year: number, month: number) => `${month}/${year}`,
+    yearAndMonth: '${month}/${year}',
   },
   Cascader: {
     placeholder: 'Si prega di selezionare',
diff --git a/src/locales/ja-JP.ts b/src/locales/ja-JP.ts
index 096698a54..564d729d4 100644
--- a/src/locales/ja-JP.ts
+++ b/src/locales/ja-JP.ts
@@ -12,7 +12,7 @@ const jaJP = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['月', '火', '水', '木', '金', '土', '日'],
-    renderYearAndMonth: (year: number, month: number) => ` ${year}年${month}月`,
+    yearAndMonth: '${year}年${month}月',
   },
   Cascader: {
     placeholder: '選択下さい',
diff --git a/src/locales/kk-KZ.ts b/src/locales/kk-KZ.ts
index f73ffb0bc..638a56bfd 100644
--- a/src/locales/kk-KZ.ts
+++ b/src/locales/kk-KZ.ts
@@ -12,8 +12,7 @@ const kkKZ = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['Дү', 'Се', 'Сә', 'Бе', 'Жұ', 'Се', 'Же'],
-    renderYearAndMonth: (year: number, month: number) =>
-      `${year}жыл${month}-ші ай`,
+    yearAndMonth: '${year}жыл${month}-ші ай',
   },
   Cascader: {
     placeholder: 'Таңдаңыз',
diff --git a/src/locales/ko-KR.ts b/src/locales/ko-KR.ts
index 02cecf823..7434fab17 100644
--- a/src/locales/ko-KR.ts
+++ b/src/locales/ko-KR.ts
@@ -13,7 +13,7 @@ const koKR = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['월', '화', '수', '목', '금', '토', '일'],
-    renderYearAndMonth: (year: number, month: number) => `${year}년 ${month}월`,
+    yearAndMonth: '${year}년 ${month}월',
   },
   Cascader: {
     placeholder: '선택됨',
@@ -132,7 +132,6 @@ const koKR = mergeLocale(base, {
   Switch: {
     name: '스위치',
   },
-
 })
 
 export default koKR
diff --git a/src/locales/nb-NO.ts b/src/locales/nb-NO.ts
index 27c54cf6b..5c2f21334 100644
--- a/src/locales/nb-NO.ts
+++ b/src/locales/nb-NO.ts
@@ -13,7 +13,7 @@ const nbNO = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør', 'Søn'],
-    renderYearAndMonth: (year: number, month: number) => `${month}.${year}`,
+    yearAndMonth: '${month}.${year}',
   },
   Cascader: {
     placeholder: 'Velg',
diff --git a/src/locales/nl-NL.ts b/src/locales/nl-NL.ts
index 9cc5fb9c7..5783129c3 100644
--- a/src/locales/nl-NL.ts
+++ b/src/locales/nl-NL.ts
@@ -13,7 +13,7 @@ const nlNL = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za', 'Zo'],
-    renderYearAndMonth: (year: number, month: number) => `${month}-${year}`,
+    yearAndMonth: '${month}-${year}',
   },
   Cascader: {
     placeholder: 'Selecteren',
diff --git a/src/locales/ru-RU.ts b/src/locales/ru-RU.ts
index 37d736257..8baac0119 100644
--- a/src/locales/ru-RU.ts
+++ b/src/locales/ru-RU.ts
@@ -13,7 +13,7 @@ const ruRU = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
-    renderYearAndMonth: (year: number, month: number) => `${year}/${month}`,
+    yearAndMonth: '${year}/${month}',
   },
   Cascader: {
     placeholder: 'Выбор',
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
index 2721cffb4..560030413 100644
--- a/src/locales/zh-CN.ts
+++ b/src/locales/zh-CN.ts
@@ -13,7 +13,7 @@ const zhCN = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['一', '二', '三', '四', '五', '六', '日'],
-    renderYearAndMonth: (year: number, month: number) => `${year}年${month}月`,
+    yearAndMonth: '${year}年${month}月',
   },
   Cascader: {
     placeholder: '请选择',
diff --git a/src/locales/zh-HK.ts b/src/locales/zh-HK.ts
index 430fd5be1..6a2fdf808 100644
--- a/src/locales/zh-HK.ts
+++ b/src/locales/zh-HK.ts
@@ -13,7 +13,7 @@ const zhHK = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['一', '二', '三', '四', '五', '六', '日'],
-    renderYearAndMonth: (year: number, month: number) => `${year}年${month}月`,
+    yearAndMonth: '${year}年${month}月',
   },
   Cascader: {
     placeholder: '請選擇',
diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
index 36f9b74fa..01ab77e09 100644
--- a/src/locales/zh-TW.ts
+++ b/src/locales/zh-TW.ts
@@ -13,7 +13,7 @@ const zhTW = mergeLocale(base, {
   },
   Calendar: {
     markItems: ['一', '二', '三', '四', '五', '六', '日'],
-    renderYearAndMonth: (year: number, month: number) => `${year}年${month}月`,
+    yearAndMonth: '${year}年${month}月',
   },
   Cascader: {
     placeholder: '請選擇',
diff --git a/src/utils/replace-message.ts b/src/utils/replace-message.ts
new file mode 100644
index 000000000..1b2298def
--- /dev/null
+++ b/src/utils/replace-message.ts
@@ -0,0 +1,10 @@
+// 移植自 field-form https://github.com/react-component/field-form/blob/master/src/utils/validateUtil.ts#L21
+export function replaceMessage(
+  template: string,
+  kv: Record<string, string>
+): string {
+  return template.replace(/\$\{\w+\}/g, (str: string) => {
+    const key = str.slice(2, -1)
+    return kv[key]
+  })
+}
diff --git a/src/utils/use-lock-scroll.ts b/src/utils/use-lock-scroll.ts
index 9c5e95ea1..755a13882 100644
--- a/src/utils/use-lock-scroll.ts
+++ b/src/utils/use-lock-scroll.ts
@@ -51,11 +51,12 @@ export function useLockScroll(
     }
 
     const { scrollHeight, offsetHeight, scrollTop } = el
+    const { height } = el.getBoundingClientRect()
     let status = '11'
 
     if (scrollTop === 0) {
       status = offsetHeight >= scrollHeight ? '00' : '01'
-    } else if (scrollTop + offsetHeight >= scrollHeight) {
+    } else if (scrollHeight <= Math.round(height + scrollTop)) {
       status = '10'
     }
 
@@ -64,7 +65,8 @@ export function useLockScroll(
       touch.isVertical() &&
       !(parseInt(status, 2) & parseInt(direction, 2))
     ) {
-      if (event.cancelable) {
+      if (event.cancelable && supportsPassive) {
+        // https://github.com/ant-design/ant-design-mobile/issues/6282
         event.preventDefault()
       }
     }


Test patch:
diff --git a/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap b/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap
index da49dbf8c..2d97539be 100644
--- a/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap
+++ b/src/components/config-provider/tests/__snapshots__/config-provider.test.tsx.snap
@@ -8618,7 +8618,7 @@ exports[`ConfigProvider should display the text as ja-JP 1`] = `
       <div
         class="adm-calendar-title"
       >
-         2022年3月
+        2022年3月
       </div>
       <a
         class="adm-calendar-arrow-button adm-calendar-arrow-button-right adm-calendar-arrow-button-right-month"
diff --git a/src/components/error-block/tests/error-block.test.tsx b/src/components/error-block/tests/error-block.test.tsx
index 350e7f32b..c59376e20 100644
--- a/src/components/error-block/tests/error-block.test.tsx
+++ b/src/components/error-block/tests/error-block.test.tsx
@@ -60,4 +60,24 @@ describe('ErrorBlock', () => {
 
     expect(container).toMatchSnapshot()
   })
+  test('renders when description and title', async () => {
+    const title = 0
+    const description = 0
+
+    const { container } = render(
+      <ErrorBlock title={title} description={description} />
+    )
+
+    const titleElement = container.querySelector(
+      `.${classPrefix}-description-title`
+    )
+    expect(titleElement).toBeInTheDocument()
+    expect(titleElement?.textContent).toBe(title + '')
+
+    const descElement = container.querySelector(
+      `.${classPrefix}-description-subtitle`
+    )
+    expect(descElement).toBeInTheDocument()
+    expect(descElement?.textContent).toBe(description + '')
+  })
 })
diff --git a/src/components/passcode-input/tests/passcode-input.test.tsx b/src/components/passcode-input/tests/passcode-input.test.tsx
index ccd8e6602..4562541f3 100644
--- a/src/components/passcode-input/tests/passcode-input.test.tsx
+++ b/src/components/passcode-input/tests/passcode-input.test.tsx
@@ -66,6 +66,9 @@ describe('PasscodeInput', () => {
   })
 
   test('native keyboard should be work', async () => {
+    const originScrollIntoView = window.HTMLElement.prototype.scrollIntoView
+    window.HTMLElement.prototype.scrollIntoView = function () {}
+
     render(<PasscodeInput plain />)
     const input = screen.getByRole('button', { name: '密码输入框' })
     fireEvent.focus(input)
@@ -73,6 +76,8 @@ describe('PasscodeInput', () => {
       await userEvent.keyboard('abc')
     })
     expect(input).toHaveTextContent('abc')
+
+    window.HTMLElement.prototype.scrollIntoView = originScrollIntoView
   })
 
   test('event callbacks should be called', async () => {
diff --git a/src/components/tag/tests/__snapshots__/tag.test.tsx.snap b/src/components/tag/tests/__snapshots__/tag.test.tsx.snap
index bc9f4173a..99992b8e8 100644
--- a/src/components/tag/tests/__snapshots__/tag.test.tsx.snap
+++ b/src/components/tag/tests/__snapshots__/tag.test.tsx.snap
@@ -21,13 +21,13 @@ exports[`Tag renders with fill 1`] = `
 <div>
   <span
     class="adm-tag"
-    style="--border-color: #666666; --text-color: #666666; --background-color: transparent;"
+    style="--border-color: var(--adm-color-text-secondary, #666666); --text-color: var(--adm-color-text-secondary, #666666); --background-color: transparent;"
   >
     outline
   </span>
   <span
     class="adm-tag"
-    style="--border-color: #666666; --text-color: #ffffff; --background-color: #666666;"
+    style="--border-color: var(--adm-color-text-secondary, #666666); --text-color: #ffffff; --background-color: var(--adm-color-text-secondary, #666666);"
   >
     solid
   </span>
diff --git a/src/components/text-area/tests/text-area.test.tsx b/src/components/text-area/tests/text-area.test.tsx
index 6f3d4dfc8..d5aba7ff6 100644
--- a/src/components/text-area/tests/text-area.test.tsx
+++ b/src/components/text-area/tests/text-area.test.tsx
@@ -130,4 +130,16 @@ describe('TextArea', () => {
     expect(textarea).toHaveAttribute('rows', '1')
     expect(hiddenTextarea).toHaveAttribute('rows', '1')
   })
+
+  test('rows should be the minRows when rows < minRows', () => {
+    const { getByRole } = render(<TextArea autoSize={{ minRows: 3 }} />)
+    const textarea = getByRole('textbox')
+    expect(textarea).toHaveAttribute('rows', '3')
+  })
+
+  test('rows should be the maxRows when rows > maxRows', () => {
+    const { getByRole } = render(<TextArea autoSize={{ maxRows: 1 }} />)
+    const textarea = getByRole('textbox')
+    expect(textarea).toHaveAttribute('rows', '1')
+  })
 })
diff --git a/src/components/virtual-input/tests/virtual-input.test.tsx b/src/components/virtual-input/tests/virtual-input.test.tsx
index 5e12b3567..0cd11daab 100644
--- a/src/components/virtual-input/tests/virtual-input.test.tsx
+++ b/src/components/virtual-input/tests/virtual-input.test.tsx
@@ -108,4 +108,9 @@ describe('VirtualInput', () => {
       ).not.toBeVisible()
     })
   })
+
+  test('placeholder should not be shown when value is `0`', () => {
+    render(<VirtualInput value={0 as any} placeholder='placeholder' />)
+    expect(screen.queryByText('placeholder')).toBeNull()
+  })
 })
diff --git a/src/utils/tests/use-lock-scroll.test.tsx b/src/utils/tests/use-lock-scroll.test.tsx
new file mode 100644
index 000000000..f37e487a1
--- /dev/null
+++ b/src/utils/tests/use-lock-scroll.test.tsx
@@ -0,0 +1,185 @@
+import React, { useRef } from 'react'
+import { render, fireEvent, createEvent, screen } from 'testing'
+import { useLockScroll } from '../use-lock-scroll'
+
+describe('useLockScroll', () => {
+  let TestComponent: React.FC<{
+    scrollParams: boolean | 'strict'
+    handleTouch?: () => void
+  }>
+
+  beforeEach(() => {
+    TestComponent = (props: {
+      scrollParams: boolean | 'strict'
+      handleTouch?: () => void
+    }) => {
+      const divRef = useRef<HTMLDivElement>(null)
+
+      useLockScroll(divRef, props.scrollParams)
+
+      return (
+        <div
+          ref={divRef}
+          data-testid='lock'
+          style={{
+            height: 200,
+            overflow: 'scroll',
+            cursor: 'grab',
+            touchAction: 'none',
+          }}
+          onTouchMove={() => props.handleTouch && props.handleTouch()}
+        >
+          {new Array(10).fill({}).map((_, i) => (
+            <h1 key={i} style={{ height: 25 }}>
+              Test component {i}
+            </h1>
+          ))}
+        </div>
+      )
+    }
+  })
+
+  afterEach(() => {
+    TestComponent = null as any
+  })
+
+  test('use preventDefault when event listener is treated as as passive', () => {
+    const handleTouch = jest.fn()
+    const TestComponent = () => {
+      const divRef = useRef<HTMLDivElement>(null)
+
+      useLockScroll(divRef, true)
+
+      return (
+        <div ref={divRef} onTouchMove={handleTouch}>
+          div
+        </div>
+      )
+    }
+
+    render(<TestComponent />)
+
+    const el = screen.getByText('div')
+    const fn = jest.fn()
+
+    const event = createEvent.touchMove(el, {
+      touches: [{ clientX: 0, clientY: 400 }],
+    })
+    Object.defineProperty(event, 'preventDefault', {
+      value: fn,
+    })
+    fireEvent(el, event)
+
+    expect(fn).toBeCalled()
+  })
+
+  test('Scroll To Bottom', async () => {
+    const { getByTestId } = render(<TestComponent scrollParams={true} />)
+
+    const testEl = getByTestId('lock')
+
+    jest.spyOn(testEl, 'scrollHeight', 'get').mockImplementation(() => 200)
+
+    const scrollTop = jest.spyOn(testEl, 'scrollTop', 'get')
+    scrollTop.mockImplementationOnce(() => 150)
+
+    jest.spyOn(testEl, 'getBoundingClientRect').mockImplementation(() => ({
+      height: 20,
+      top: 0,
+      left: 0,
+      x: 0,
+      y: 0,
+      bottom: 0,
+      right: 0,
+      width: 0,
+      toJSON: () => {},
+    }))
+
+    fireEvent.touchStart(testEl, {
+      touches: [{ clientX: 0, clientY: 100 }],
+    })
+
+    const triggerTruthy = fireEvent.touchMove(testEl, {
+      touches: [{ clientX: 0, clientY: 20 }],
+    })
+    // 滚动事件正常触发
+    expect(triggerTruthy).toBeTruthy()
+
+    // 滚动高度到 180
+    scrollTop.mockImplementationOnce(() => 180)
+
+    const triggerFalsy = fireEvent.touchMove(testEl, {
+      touches: [{ clientX: 0, clientY: 10 }],
+    })
+
+    // 滚动事件被取消
+    expect(triggerFalsy).toBeFalsy()
+  })
+
+  test('Scroll To Top', async () => {
+    const { getByTestId } = render(<TestComponent scrollParams={true} />)
+
+    const testEl = getByTestId('lock')
+
+    jest.spyOn(testEl, 'scrollHeight', 'get').mockImplementation(() => 200)
+
+    const scrollTop = jest.spyOn(testEl, 'scrollTop', 'get')
+    scrollTop.mockImplementationOnce(() => 150)
+
+    jest.spyOn(testEl, 'getBoundingClientRect').mockImplementation(() => ({
+      height: 20,
+      top: 0,
+      left: 0,
+      x: 0,
+      y: 0,
+      bottom: 0,
+      right: 0,
+      width: 0,
+      toJSON: () => {},
+    }))
+
+    fireEvent.touchStart(testEl, {
+      touches: [{ clientX: 0, clientY: 100 }],
+    })
+
+    const triggerTruthy = fireEvent.touchMove(testEl, {
+      touches: [{ clientX: 0, clientY: 120 }],
+    })
+    // 滚动事件正常触发
+    expect(triggerTruthy).toBeTruthy()
+
+    // 滚动高度到顶部
+    scrollTop.mockImplementationOnce(() => 0)
+
+    const triggerFalsy = fireEvent.touchMove(testEl, {
+      touches: [{ clientX: 0, clientY: 200 }],
+    })
+
+    // 滚动事件被取消
+    expect(triggerFalsy).toBeFalsy()
+  })
+
+  test('Scroll With Strict Params', async () => {
+    const { getByTestId } = render(<TestComponent scrollParams='strict' />)
+
+    const testEl = getByTestId('lock')
+
+    jest
+      .spyOn(document.body, 'clientHeight', 'get')
+      .mockImplementation(() => 20)
+    jest
+      .spyOn(document.body, 'scrollHeight', 'get')
+      .mockImplementation(() => 30)
+
+    fireEvent.touchStart(testEl, {
+      touches: [{ clientX: 0, clientY: 100 }],
+    })
+
+    const cancelTrigger = fireEvent.touchMove(testEl, {
+      touches: [{ clientX: 0, clientY: 200 }],
+    })
+
+    // 事件被取消
+    expect(cancelTrigger).toBeFalsy()
+  })
+})