feat(ImagePreview): add long-press event (#11252)

* feat(ImagePreview): add long-press event

* chore: upd

* chore: upd

* chore: upd
This commit is contained in:
neverland 2022-11-13 11:42:26 +08:00 committed by GitHub
parent 839bcd8928
commit 093db7b37e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 62 additions and 28 deletions

View File

@ -77,7 +77,7 @@ export default defineComponent({
props: imagePreviewProps, props: imagePreviewProps,
emits: ['scale', 'close', 'closed', 'change', 'update:show'], emits: ['scale', 'close', 'closed', 'change', 'longPress', 'update:show'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const swipeRef = ref<SwipeInstance>(); const swipeRef = ref<SwipeInstance>();
@ -146,7 +146,7 @@ export default defineComponent({
indicatorColor="white" indicatorColor="white"
onChange={setActive} onChange={setActive}
> >
{props.images.map((image) => ( {props.images.map((image, index) => (
<ImagePreviewItem <ImagePreviewItem
src={image} src={image}
show={props.show} show={props.show}
@ -157,6 +157,7 @@ export default defineComponent({
rootHeight={state.rootHeight} rootHeight={state.rootHeight}
onScale={emitScale} onScale={emitScale}
onClose={emitClose} onClose={emitClose}
onLongPress={() => emit('longPress', { index })}
v-slots={{ v-slots={{
image: slots.image, image: slots.image,
}} }}

View File

@ -14,6 +14,7 @@ import {
preventDefault, preventDefault,
createNamespace, createNamespace,
makeRequiredProp, makeRequiredProp,
LONG_PRESS_START_TIME,
type ComponentInstance, type ComponentInstance,
} from '../utils'; } from '../utils';
@ -45,7 +46,7 @@ export default defineComponent({
rootHeight: makeRequiredProp(Number), rootHeight: makeRequiredProp(Number),
}, },
emits: ['scale', 'close'], emits: ['scale', 'close', 'longPress'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const state = reactive({ const state = reactive({
@ -200,20 +201,23 @@ export default defineComponent({
const TAP_TIME = 250; const TAP_TIME = 250;
const TAP_OFFSET = 5; const TAP_OFFSET = 5;
if ( if (offsetX.value < TAP_OFFSET && offsetY.value < TAP_OFFSET) {
offsetX.value < TAP_OFFSET && // tap or double tap
offsetY.value < TAP_OFFSET && if (deltaTime < TAP_TIME) {
deltaTime < TAP_TIME if (doubleTapTimer) {
) { clearTimeout(doubleTapTimer);
if (doubleTapTimer) {
clearTimeout(doubleTapTimer);
doubleTapTimer = null;
toggleScale();
} else {
doubleTapTimer = setTimeout(() => {
emit('close');
doubleTapTimer = null; doubleTapTimer = null;
}, TAP_TIME); toggleScale();
} else {
doubleTapTimer = setTimeout(() => {
emit('close');
doubleTapTimer = null;
}, TAP_TIME);
}
}
// long press
else if (deltaTime > LONG_PRESS_START_TIME) {
emit('longPress');
} }
} }
}; };

View File

@ -235,11 +235,11 @@ Vant exports following ImagePreview utility functions:
| Event | Description | Arguments | | Event | Description | Arguments |
| --- | --- | --- | | --- | --- | --- |
| close | Emitted when closing ImagePreview | _value: { index, url }_ | | close | Emitted when closing ImagePreview | _{ index: number, url: string }_ |
| closed | Emitted when ImagePreview is closed | - | | closed | Emitted when ImagePreview is closed | - |
| change | Emitted when current image changed | _index: number_ | | change | Emitted when current image changed | _index: number_ |
| scale | Emitted when scaling current image | _value: ImagePreviewScaleEventParams_ | | scale | Emitted when scaling current image | _{ index: number, scale: number }_ |
| scale | Emitted when scaling current image | _value: ImagePreviewScaleEventParams_ | | long-press | Emitted when long press current image | _{ index: number }_ |
### Methods ### Methods

View File

@ -250,12 +250,13 @@ Vant 中导出了以下 ImagePreview 相关的辅助函数:
通过组件调用 `ImagePreview` 时,支持以下事件: 通过组件调用 `ImagePreview` 时,支持以下事件:
| 事件 | 说明 | 回调参数 | | 事件 | 说明 | 回调参数 |
| --- | --- | --- | | ---------- | ---------------------- | ---------------------------------- |
| close | 关闭时触发 | { index: 索引, url: 图片链接 } | | close | 关闭时触发 | _{ index: number, url: string }_ |
| closed | 关闭且且动画结束后触发 | - | | closed | 关闭且且动画结束后触发 | - |
| change | 切换当前图片时触发 | index: 当前图片的索引 | | change | 切换当前图片时触发 | _index: number_ |
| scale | 缩放当前图片时触发 | { index: 当前图片的索引, scale: 当前缩放的值 } | | scale | 缩放当前图片时触发 | _{ index: number, scale: number }_ |
| long-press | 长按当前图片时触发 | _{ index: number }_ |
### 方法 ### 方法

View File

@ -3,7 +3,9 @@ import {
later, later,
triggerDrag, triggerDrag,
mockGetBoundingClientRect, mockGetBoundingClientRect,
trigger,
} from '../../../test'; } from '../../../test';
import { LONG_PRESS_START_TIME } from '../../utils';
import ImagePreviewComponent from '../ImagePreview'; import ImagePreviewComponent from '../ImagePreview';
import { images, triggerZoom } from './shared'; import { images, triggerZoom } from './shared';
@ -287,3 +289,24 @@ test('should render image slot correctly 2', async () => {
expect(wrapper.html().includes('video')).toBeTruthy(); expect(wrapper.html().includes('video')).toBeTruthy();
}); });
test('should emit long-press event after long press', async () => {
const onLongPress = jest.fn();
const wrapper = mount(ImagePreviewComponent, {
props: {
images,
show: true,
onLongPress,
},
});
await later();
const swipe = wrapper.find('.van-swipe-item');
trigger(swipe, 'touchstart', 0, 0, { x: 0, y: 0 });
await later(LONG_PRESS_START_TIME + 100);
trigger(swipe, 'touchend', 0, 0, { touchList: [] });
expect(onLongPress).toHaveBeenLastCalledWith({
index: 0,
});
});

View File

@ -24,6 +24,7 @@ import {
callInterceptor, callInterceptor,
makeNumericProp, makeNumericProp,
HAPTICS_FEEDBACK, HAPTICS_FEEDBACK,
LONG_PRESS_START_TIME,
type Numeric, type Numeric,
} from '../utils'; } from '../utils';
@ -33,7 +34,6 @@ import { useCustomFieldValue } from '@vant/use';
const [name, bem] = createNamespace('stepper'); const [name, bem] = createNamespace('stepper');
const LONG_PRESS_INTERVAL = 200; const LONG_PRESS_INTERVAL = 200;
const LONG_PRESS_START_TIME = 600;
const isEqual = (value1?: Numeric, value2?: Numeric) => const isEqual = (value1?: Numeric, value2?: Numeric) =>
String(value1) === String(value2); String(value1) === String(value2);

View File

@ -1,6 +1,7 @@
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { Stepper } from '..'; import { Stepper } from '..';
import { mount, later } from '../../../test'; import { mount, later } from '../../../test';
import { LONG_PRESS_START_TIME } from '../../utils';
test('should disable buttons and input when using disabled prop', () => { test('should disable buttons and input when using disabled prop', () => {
const wrapper = mount(Stepper, { const wrapper = mount(Stepper, {
@ -126,9 +127,9 @@ test('should update value after long pressing', async () => {
expect(wrapper.emitted('update:modelValue')![0]).toEqual([2]); expect(wrapper.emitted('update:modelValue')![0]).toEqual([2]);
await plus.trigger('touchstart'); await plus.trigger('touchstart');
await later(1000); await later(LONG_PRESS_START_TIME + 500);
await plus.trigger('touchend'); await plus.trigger('touchend');
expect(wrapper.emitted('update:modelValue')).toEqual([[2], [3], [4]]); expect(wrapper.emitted('update:modelValue')).toEqual([[2], [3], [4], [5]]);
}); });
test('should allow to disable long press', async () => { test('should allow to disable long press', async () => {

View File

@ -12,3 +12,7 @@ export const BORDER_UNSET_TOP_BOTTOM = `${BORDER}-unset--top-bottom`;
export const HAPTICS_FEEDBACK = 'van-haptics-feedback'; export const HAPTICS_FEEDBACK = 'van-haptics-feedback';
export const FORM_KEY: InjectionKey<FormProvide> = Symbol('van-form'); export const FORM_KEY: InjectionKey<FormProvide> = Symbol('van-form');
// Same as the default value of iOS long press time
// https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/1616423-minimumpressduration
export const LONG_PRESS_START_TIME = 500;