Repository: ant-design/ant-design-mobile
Pull request #5906 (2022-12-30 09:05:23)

feat(Swiper):add swiper stopPropagation
add swiper stopPropagation

github-actions[bot]: PR preview has been successfully built and deployed to https://antd-mobile-preview-pr-5906.surge.sh
codecov[bot]: # [Codecov](https://codecov.io/gh/ant-design/ant-design-mobile/pull/5906?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) Report
Base: **90.35**% // Head: **90.31**% // Decreases project coverage by **`-0.03%`** :warning:
> Coverage data is based on head [(`0f422e4`)](https://codecov.io/gh/ant-design/ant-design-mobile/pull/5906?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) compared to base [(`73aad49`)](https://codecov.io/gh/ant-design/ant-design-mobile/commit/73aad498c66a239330cb2ce2c4da3639e054146a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).
> Patch coverage: 50.00% of modified lines in pull request are covered.

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


```diff
@@             Coverage Diff             @@
##           feature    #5906      +/-   ##
===========================================
- Coverage    90.35%   90.31%   -0.04%     
===========================================
  Files          299      299              
  Lines         6488     6494       +6     
  Branches      1612     1612              
===========================================
+ Hits          5862     5865       +3     
- Misses         575      578       +3     
  Partials        51       51              
```


| [Impacted Files](https://codecov.io/gh/ant-design/ant-design-mobile/pull/5906?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design) | Coverage Δ | |
|---|---|---|
| [src/components/swiper/swiper.tsx](https://codecov.io/gh/ant-design/ant-design-mobile/pull/5906?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design#diff-c3JjL2NvbXBvbmVudHMvc3dpcGVyL3N3aXBlci50c3g=) | `88.09% <50.00%> (-1.42%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/ant-design/ant-design-mobile/pull/5906?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ant-design).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](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).


Patch:
diff --git a/src/components/swiper/index.en.md b/src/components/swiper/index.en.md
index df5a8605a..4d9287e34 100644
--- a/src/components/swiper/index.en.md
+++ b/src/components/swiper/index.en.md
@@ -40,8 +40,8 @@ When you use a vertical Swiper, be sure to set its height through the `--height`
 
 ### Props
 
-| Name | Description | Type | Default |
-| --- | --- | --- | --- |
+| Name | Description | Type | Default | Version |
+| --- | --- | --- | --- | --- |
 | allowTouchMove | Whether to allow gesture sliding | `boolean` | `true` |
 | autoplay | Whether to switch automatically | `boolean` | `false` |
 | autoplayInterval | Automatic switching interval, the unit is `ms` | `number` | `3000` |
@@ -55,6 +55,11 @@ When you use a vertical Swiper, be sure to set its height through the `--height`
 | slideSize | The slide width in percentage | `number` | `100` |
 | stuckAtBoundary | Whether to stuck at boundary in order to prevent white spaces. Only available when `loop` is `false` and `slideWidth` < 100. | `boolean` | `true` |
 | trackOffset | The track offset in percentage | `number` | `0` |
+| stopPropagation | Stop the propagation of some events. | `PropagationEvent[]` | `[]` | 5.28.0 |
+
+```ts
+type PropagationEvent = 'mouseup' | 'mousemove' | 'mousedown'
+```
 
 ### CSS Variables
 
diff --git a/src/components/swiper/index.zh.md b/src/components/swiper/index.zh.md
index eb6d55160..20d7d97ef 100644
--- a/src/components/swiper/index.zh.md
+++ b/src/components/swiper/index.zh.md
@@ -38,8 +38,8 @@
 
 ### 属性
 
-| 属性 | 说明 | 类型 | 默认值 |
-| --- | --- | --- | --- |
+| 属性 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
 | allowTouchMove | 是否允许手势滑动 | `boolean` | `true` |
 | autoplay | 是否自动切换 | `boolean` | `false` |
 | autoplayInterval | 自动切换的间隔，单位为 `ms` | `number` | `3000` |
@@ -53,6 +53,11 @@
 | slideSize | 滑块的宽度百分比 | `number` | `100` |
 | stuckAtBoundary | 是否在边界两边卡住，避免出现空白，仅在非 `loop` 模式且 `slideSize` < 100 时生效 | `boolean` | `true` |
 | trackOffset | 滑块轨道整体的偏移量百分比 | `number` | `0` |
+| stopPropagation | 阻止某些事件的冒泡 | `PropagationEvent[]` | `[]` | 5.28.0 |
+
+```ts
+type PropagationEvent = 'mouseup' | 'mousemove' | 'mousedown'
+```
 
 ### CSS 变量
 
diff --git a/src/components/swiper/swiper.tsx b/src/components/swiper/swiper.tsx
index 4464d521d..dd7e10763 100644
--- a/src/components/swiper/swiper.tsx
+++ b/src/components/swiper/swiper.tsx
@@ -21,9 +21,22 @@ import { staged } from 'staged-components'
 import { useRefState } from '../../utils/use-ref-state'
 import { bound } from '../../utils/bound'
 import { useIsomorphicLayoutEffect, useUpdateEffect } from 'ahooks'
+import { mergeFuncProps } from '../../utils/with-func-props'
 
 const classPrefix = `adm-swiper`
 
+const eventToPropRecord = {
+  'mousedown': 'onMouseDown',
+  'mousemove': 'onMouseMove',
+  'mouseup': 'onMouseUp',
+} as const
+
+type ValuesToUnion<T, K extends keyof T = keyof T> = K extends keyof T
+  ? T[K]
+  : never
+
+type PropagationEvent = keyof typeof eventToPropRecord
+
 export type SwiperRef = {
   swipeTo: (index: number) => void
   swipeNext: () => void
@@ -44,6 +57,7 @@ export type SwiperProps = {
   trackOffset?: number
   stuckAtBoundary?: boolean
   rubberband?: boolean
+  stopPropagation?: PropagationEvent[]
   children?: ReactElement | ReactElement[]
 } & NativeProps<'--height' | '--width' | '--border-radius' | '--track-padding'>
 
@@ -58,6 +72,7 @@ const defaultProps = {
   trackOffset: 0,
   stuckAtBoundary: true,
   rubberband: true,
+  stopPropagation: [] as PropagationEvent[],
 }
 
 let currentUid: undefined | {}
@@ -316,6 +331,19 @@ export const Swiper = forwardRef<SwiperRef, SwiperProps>(
         '--track-offset': `${props.trackOffset}%`,
       }
 
+      const dragProps = { ...(props.allowTouchMove ? bind() : {}) }
+      const stopPropagationProps: Partial<
+        Record<ValuesToUnion<typeof eventToPropRecord>, any>
+      > = {}
+      for (const key of props.stopPropagation) {
+        const prop = eventToPropRecord[key]
+        stopPropagationProps[prop] = function (e: Event) {
+          e.stopPropagation()
+        }
+      }
+
+      const mergedProps = mergeFuncProps(dragProps, stopPropagationProps)
+
       return withNativeProps(
         props,
         <div
@@ -336,7 +364,7 @@ export const Swiper = forwardRef<SwiperRef, SwiperProps>(
               }
               forceCancelDrag()
             }}
-            {...(props.allowTouchMove ? bind() : {})}
+            {...mergedProps}
           >
             {renderTrackInner()}
           </div>
diff --git a/src/utils/with-func-props.ts b/src/utils/with-func-props.ts
new file mode 100644
index 000000000..720a0abc3
--- /dev/null
+++ b/src/utils/with-func-props.ts
@@ -0,0 +1,29 @@
+type Merge<T, P> = {
+  [K in keyof T & keyof P]: P[K] | T[K]
+}
+
+export function mergeFuncProps<
+  T extends Record<string, any>,
+  P extends Record<string, any>
+>(p1: T, p2: P): Merge<T, P> {
+  const p1Keys = Object.keys(p1)
+  const p2Keys = Object.keys(p2)
+  const keys = new Set([...p1Keys, ...p2Keys])
+  const res: any = {}
+
+  keys.forEach(key => {
+    const p1Value = p1[key]
+    const p2Value = p2[key]
+
+    if (typeof p1Value === 'function' && typeof p2Value === 'function') {
+      res[key] = function (...args: any[]) {
+        p1Value(...args)
+        p2Value(...args)
+      }
+    } else {
+      res[key] = p1Value || p2Value
+    }
+  })
+
+  return res
+}


Test patch:
diff --git a/src/components/swiper/tests/swiper.test.tsx b/src/components/swiper/tests/swiper.test.tsx
index 76759aa08..844b3c486 100644
--- a/src/components/swiper/tests/swiper.test.tsx
+++ b/src/components/swiper/tests/swiper.test.tsx
@@ -291,4 +291,37 @@ describe('Swiper', () => {
 
     jest.useRealTimers()
   })
+
+  test('stop propagation should be work', () => {
+    const onMouseDown = jest.fn()
+    const onMouseMove = jest.fn()
+    const onMouseUp = jest.fn()
+    render(
+      <div
+        onMouseDown={onMouseDown}
+        onMouseMove={onMouseMove}
+        onMouseUp={onMouseUp}
+      >
+        <Swiper stopPropagation={['mousedown', 'mousemove', 'mouseup']}>
+          {items}
+        </Swiper>
+      </div>
+    )
+
+    const el = $$(`.${classPrefix}-track`)[0]
+    mockDrag(el, [
+      { clientX: 300, clientY: 0 },
+      {
+        clientX: 200,
+        clientY: 25,
+      },
+      {
+        clientX: 100,
+        clientY: 30,
+      },
+    ])
+    expect(onMouseDown).not.toBeCalled()
+    expect(onMouseMove).not.toBeCalled()
+    expect(onMouseUp).not.toBeCalled()
+  })
 })