1
0
mirror of https://gitee.com/vant-contrib/vant.git synced 2025-04-06 03:57:59 +08:00

Merge branch 'dev' into next

This commit is contained in:
chenjiahan 2022-08-28 09:47:56 +08:00
commit cf5e7e6629
17 changed files with 303 additions and 135 deletions

@ -30,7 +30,7 @@
</script>
<% } %>
</head>
<body ontouchstart>
<body>
<div id="app"></div>
<script type="module" src="/desktop/main.js"></script>
</body>

@ -39,7 +39,7 @@
</script>
<% } %>
</head>
<body ontouchstart>
<body>
<div id="app"></div>
<script type="module" src="/mobile/main.js"></script>
</body>

@ -13,3 +13,8 @@ window.app = createApp(App)
setTimeout(() => {
window.app.mount('#app');
}, 0);
// https://stackoverflow.com/questions/3885018/active-pseudo-class-doesnt-work-in-mobile-safari/33681490#33681490
document.addEventListener('touchstart', () => {}, {
passive: true,
});

@ -212,3 +212,24 @@ import 'vant/es/image-preview/style';
```
> Tip: "Full Import" and "On-demand Import" should not be used at the same time, otherwise it will lead to problems such as code duplication and style overrides.
## With Frameworks
### Use Vant in Nuxt 3
When using Vant in Nuxt 3, you should add `/vant/` to the `build.transpile`:
```ts
import { defineNuxtConfig } from 'nuxt';
export default defineNuxtConfig({
experimental: {
externalVue: true,
},
});
```
Reference:
- [nuxt/framework#6761](https://github.com/nuxt/framework/issues/6761)
- [nuxt/framework#4084](https://github.com/nuxt/framework/issues/4084)

@ -217,3 +217,24 @@ import 'vant/es/image-preview/style';
```
> 提示:在单个项目中不应该同时使用「全量引入」和「按需引入」,否则会导致代码重复、样式错乱等问题。
## 在框架中使用
### 在 Nuxt 3 中使用
在 Nuxt 3 中使用 Vant 时,由于 Nuxt 3 框架本身的限制,需要在 `nuxt.config.ts` 中添加以下配置:
```ts
import { defineNuxtConfig } from 'nuxt';
export default defineNuxtConfig({
experimental: {
externalVue: true,
},
});
```
关于该问题的背景,可以参考以下 issue
- [nuxt/framework#6761](https://github.com/nuxt/framework/issues/6761)
- [nuxt/framework#4084](https://github.com/nuxt/framework/issues/4084)

@ -134,7 +134,7 @@ export default defineComponent({
};
const getInitialDate = (defaultDate = props.defaultDate) => {
const { type, minDate, maxDate } = props;
const { type, minDate, maxDate, allowSameDay } = props;
if (defaultDate === null) {
return defaultDate;
@ -149,9 +149,12 @@ export default defineComponent({
const start = limitDateRange(
defaultDate[0] || now,
minDate,
getPrevDay(maxDate)
allowSameDay ? maxDate : getPrevDay(maxDate)
);
const end = limitDateRange(
defaultDate[1] || now,
allowSameDay ? minDate : getNextDay(minDate)
);
const end = limitDateRange(defaultDate[1] || now, getNextDay(minDate));
return [start, end];
}

@ -270,3 +270,41 @@ test('should emit overRange when exceeded max range', async () => {
expect(onOverRange).toHaveBeenCalledTimes(1);
});
test('should allow default date to be minDate when using allowSameDay prop', () => {
const minDate = new Date(1800, 0, 1);
const maxDate = new Date(1800, 0, 29);
const wrapper = mount(Calendar, {
props: {
type: 'range',
poppable: false,
minDate,
maxDate,
defaultDate: [minDate, minDate],
lazyRender: false,
allowSameDay: true,
},
});
wrapper.find('.van-calendar__confirm').trigger('click');
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual([minDate, minDate]);
});
test('should allow default date to be maxDate when using allowSameDay prop', () => {
const minDate = new Date(1800, 0, 1);
const maxDate = new Date(1800, 0, 29);
const wrapper = mount(Calendar, {
props: {
type: 'range',
poppable: false,
minDate,
maxDate,
defaultDate: [maxDate, maxDate],
lazyRender: false,
allowSameDay: true,
},
});
wrapper.find('.van-calendar__confirm').trigger('click');
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual([maxDate, maxDate]);
});

@ -26,6 +26,7 @@ import {
makeStringProp,
makeNumericProp,
createNamespace,
type ComponentInstance,
} from '../utils';
import {
cutString,
@ -42,7 +43,11 @@ import {
import { cellSharedProps } from '../cell/Cell';
// Composables
import { CUSTOM_FIELD_INJECTION_KEY, useParent } from '@vant/use';
import {
useParent,
useEventListener,
CUSTOM_FIELD_INJECTION_KEY,
} from '@vant/use';
import { useId } from '../composables/use-id';
import { useExpose } from '../composables/use-expose';
@ -145,6 +150,7 @@ export default defineComponent({
});
const inputRef = ref<HTMLInputElement>();
const clearIconRef = ref<ComponentInstance>();
const customValue = ref<() => unknown>();
const { parent: form } = useParent(FORM_KEY);
@ -353,7 +359,7 @@ export default defineComponent({
const onClickRightIcon = (event: MouseEvent) =>
emit('clickRightIcon', event);
const onClear = (event: MouseEvent) => {
const onClear = (event: TouchEvent) => {
preventDefault(event);
emit('update:modelValue', '');
emit('clear', event);
@ -526,9 +532,9 @@ export default defineComponent({
{renderInput()}
{showClear.value && (
<Icon
ref={clearIconRef}
name={props.clearIcon}
class={bem('clear')}
onTouchstart={onClear}
/>
)}
{renderRightIcon()}
@ -568,6 +574,11 @@ export default defineComponent({
nextTick(adjustTextareaSize);
});
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchstart', onClear, {
target: computed(() => clearIconRef.value?.$el),
});
return () => {
const disabled = getProp('disabled');
const labelAlign = getProp('labelAlign');

@ -1,5 +1,6 @@
<script setup lang="ts">
import VanField from '..';
import VanForm from '../../form';
import VanCellGroup from '../../cell-group';
import { ref } from 'vue';
import { useTranslate } from '../../../docs/site';
@ -43,35 +44,39 @@ const password = ref('');
<template>
<demo-block :title="t('customType')">
<van-cell-group inset>
<van-field
v-model="text"
:label="t('text')"
:placeholder="t('textPlaceholder')"
/>
<van-field
v-model="phone"
type="tel"
:label="t('phone')"
:placeholder="t('phonePlaceholder')"
/>
<van-field
v-model="digit"
type="digit"
:label="t('digit')"
:placeholder="t('digitPlaceholder')"
/>
<van-field
v-model="number"
type="number"
:label="t('number')"
:placeholder="t('numberPlaceholder')"
/>
<van-field
v-model="password"
type="password"
:label="t('password')"
:placeholder="t('passwordPlaceholder')"
/>
<van-form>
<van-field
v-model="text"
:label="t('text')"
:placeholder="t('textPlaceholder')"
autocomplete="off"
/>
<van-field
v-model="phone"
type="tel"
:label="t('phone')"
:placeholder="t('phonePlaceholder')"
/>
<van-field
v-model="digit"
type="digit"
:label="t('digit')"
:placeholder="t('digitPlaceholder')"
/>
<van-field
v-model="number"
type="number"
:label="t('number')"
:placeholder="t('numberPlaceholder')"
/>
<van-field
v-model="password"
type="password"
:label="t('password')"
:placeholder="t('passwordPlaceholder')"
autocomplete="off"
/>
</van-form>
</van-cell-group>
</demo-block>
</template>

@ -26,103 +26,107 @@ exports[`should render demo and match snapshot 1`] = `
</div>
<div>
<div class="van-cell-group van-cell-group--inset">
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Text
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
id="van-field-input"
class="van-field__control"
placeholder="Text"
aria-labelledby="van-field-label"
<form class="van-form">
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Text
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
id="van-field-input"
class="van-field__control"
placeholder="Text"
autocomplete="off"
aria-labelledby="van-field-label"
>
</div>
</div>
</div>
</div>
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Phone
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="tel"
id="van-field-input"
class="van-field__control"
placeholder="Phone"
aria-labelledby="van-field-label"
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Phone
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="tel"
id="van-field-input"
class="van-field__control"
placeholder="Phone"
aria-labelledby="van-field-label"
>
</div>
</div>
</div>
</div>
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Digit
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="tel"
inputmode="numeric"
id="van-field-input"
class="van-field__control"
placeholder="Digit"
aria-labelledby="van-field-label"
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Digit
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="tel"
inputmode="numeric"
id="van-field-input"
class="van-field__control"
placeholder="Digit"
aria-labelledby="van-field-label"
>
</div>
</div>
</div>
</div>
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Number
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
inputmode="decimal"
id="van-field-input"
class="van-field__control"
placeholder="Number"
aria-labelledby="van-field-label"
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Number
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
inputmode="decimal"
id="van-field-input"
class="van-field__control"
placeholder="Number"
aria-labelledby="van-field-label"
>
</div>
</div>
</div>
</div>
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Password
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="password"
id="van-field-input"
class="van-field__control"
placeholder="Password"
aria-labelledby="van-field-label"
<div class="van-cell van-field">
<div class="van-cell__title van-field__label">
<label id="van-field-label"
for="van-field-input"
>
Password
</label>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="password"
id="van-field-input"
class="van-field__control"
placeholder="Password"
autocomplete="off"
aria-labelledby="van-field-label"
>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
<div>

@ -1,4 +1,5 @@
import {
ref,
watch,
computed,
reactive,
@ -13,10 +14,12 @@ import {
preventDefault,
createNamespace,
makeRequiredProp,
type ComponentInstance,
} from '../utils';
// Composables
import { useTouch } from '../composables/use-touch';
import { useEventListener } from '@vant/use';
// Components
import { Image } from '../image';
@ -57,6 +60,7 @@ export default defineComponent({
});
const touch = useTouch();
const swipeItem = ref<ComponentInstance>();
const vertical = computed(() => {
const { rootWidth, rootHeight } = props;
@ -271,6 +275,11 @@ export default defineComponent({
}
);
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchmove', onTouchMove, {
target: computed(() => swipeItem.value?.$el),
});
return () => {
const imageSlots = {
loading: () => <Loading type="spinner" />,
@ -278,9 +287,9 @@ export default defineComponent({
return (
<SwipeItem
ref={swipeItem}
class={bem('swipe-item')}
onTouchstartPassive={onTouchStart}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
>

@ -77,6 +77,7 @@ export default defineComponent({
setup(props, { emit, slots }) {
const root = ref<HTMLElement>();
const sidebar = ref<HTMLElement>();
const activeAnchor = ref<Numeric>('');
const touch = useTouch();
@ -272,11 +273,11 @@ export default defineComponent({
const renderSidebar = () => (
<div
ref={sidebar}
class={bem('sidebar')}
style={sidebarStyle.value}
onClick={onClickSidebar}
onTouchstartPassive={touch.start}
onTouchmove={onTouchMove}
>
{renderIndexes()}
</div>
@ -284,6 +285,11 @@ export default defineComponent({
useExpose({ scrollTo });
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchmove', onTouchMove, {
target: sidebar,
});
return () => (
<div ref={root} class={bem()}>
{props.teleport ? (

@ -1,12 +1,14 @@
import {
ref,
Transition,
defineComponent,
type PropType,
type CSSProperties,
type ExtractPropTypes,
} from 'vue';
// Utils
import {
noop,
isDef,
extend,
truthProp,
@ -16,6 +18,9 @@ import {
createNamespace,
getZIndexStyle,
} from '../utils';
// Composables
import { useEventListener } from '@vant/use';
import { useLazyRender } from '../composables/use-lazy-render';
const [name, bem] = createNamespace('overlay');
@ -38,10 +43,13 @@ export default defineComponent({
props: overlayProps,
setup(props, { slots }) {
const root = ref<HTMLElement>();
const lazyRender = useLazyRender(() => props.show || !props.lazyRender);
const preventTouchMove = (event: TouchEvent) => {
preventDefault(event, true);
const onTouchMove = (event: TouchEvent) => {
if (props.lockScroll) {
preventDefault(event, true);
}
};
const renderOverlay = lazyRender(() => {
@ -57,15 +65,20 @@ export default defineComponent({
return (
<div
v-show={props.show}
ref={root}
style={style}
class={[bem(), props.className]}
onTouchmove={props.lockScroll ? preventTouchMove : noop}
>
{slots.default?.()}
</div>
);
});
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchmove', onTouchMove, {
target: root,
});
return () => (
<Transition v-slots={{ default: renderOverlay }} name="van-fade" appear />
);

@ -13,7 +13,7 @@ import {
} from '../utils';
// Composables
import { useRect, useCustomFieldValue } from '@vant/use';
import { useRect, useCustomFieldValue, useEventListener } from '@vant/use';
import { useRefs } from '../composables/use-refs';
import { useTouch } from '../composables/use-touch';
@ -268,6 +268,11 @@ export default defineComponent({
useCustomFieldValue(() => props.modelValue);
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchmove', onTouchMove, {
target: groupRef,
});
return () => (
<div
ref={groupRef}
@ -280,7 +285,6 @@ export default defineComponent({
aria-disabled={props.disabled}
aria-readonly={props.readonly}
onTouchstartPassive={onTouchStart}
onTouchmove={onTouchMove}
>
{list.value.map(renderStar)}
</div>

@ -22,7 +22,7 @@ import {
} from '../utils';
// Composables
import { useRect, useCustomFieldValue } from '@vant/use';
import { useRect, useCustomFieldValue, useEventListener } from '@vant/use';
import { useTouch } from '../composables/use-touch';
const [name, bem] = createNamespace('slider');
@ -65,6 +65,7 @@ export default defineComponent({
let startValue: SliderValue;
const root = ref<HTMLElement>();
const slider = ref<HTMLElement>();
const dragStatus = ref<'start' | 'dragging' | ''>();
const touch = useTouch();
@ -290,6 +291,7 @@ export default defineComponent({
return (
<div
ref={slider}
role="slider"
class={getButtonClassName(index)}
tabindex={props.disabled ? undefined : 0}
@ -306,7 +308,6 @@ export default defineComponent({
}
onTouchStart(event);
}}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
onClick={stopPropagation}
@ -320,6 +321,11 @@ export default defineComponent({
updateValue(props.modelValue);
useCustomFieldValue(() => props.modelValue);
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchmove', onTouchMove, {
target: slider,
});
return () => (
<div
ref={root}

@ -21,7 +21,7 @@ import {
} from '../utils';
// Composables
import { useRect, useClickAway } from '@vant/use';
import { useRect, useClickAway, useEventListener } from '@vant/use';
import { useTouch } from '../composables/use-touch';
import { useExpose } from '../composables/use-expose';
@ -209,6 +209,11 @@ export default defineComponent({
useClickAway(root, () => onClick('outside'), { eventName: 'touchstart' });
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchmove', onTouchMove, {
target: root,
});
return () => {
const wrapperStyle = {
transform: `translate3d(${state.offset}px, 0, 0)`,
@ -221,7 +226,6 @@ export default defineComponent({
class={bem()}
onClick={getClickHandler('cell', lockClick)}
onTouchstartPassive={onTouchStart}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
>

@ -28,7 +28,12 @@ import {
} from '../utils';
// Composables
import { doubleRaf, useChildren, usePageVisibility } from '@vant/use';
import {
doubleRaf,
useChildren,
useEventListener,
usePageVisibility,
} from '@vant/use';
import { useTouch } from '../composables/use-touch';
import { useExpose } from '../composables/use-expose';
import { onPopupReopen } from '../composables/on-popup-reopen';
@ -66,6 +71,7 @@ export default defineComponent({
setup(props, { emit, slots }) {
const root = ref<HTMLElement>();
const track = ref<HTMLElement>();
const state = reactive<SwipeState>({
rect: null,
width: 0,
@ -301,8 +307,15 @@ export default defineComponent({
touch.move(event);
if (isCorrectDirection.value) {
preventDefault(event, props.stopPropagation);
move({ offset: delta.value });
const isEdgeTouch =
!props.loop &&
((state.active === 0 && delta.value > 0) ||
(state.active === count.value - 1 && delta.value < 0));
if (!isEdgeTouch) {
preventDefault(event, props.stopPropagation);
move({ offset: delta.value });
}
}
}
};
@ -435,13 +448,18 @@ export default defineComponent({
onDeactivated(stopAutoplay);
onBeforeUnmount(stopAutoplay);
// useEventListener will set passive to `false` to eliminate the warning of Chrome
useEventListener('touchmove', onTouchMove, {
target: track,
});
return () => (
<div ref={root} class={bem()}>
<div
ref={track}
style={trackStyle.value}
class={bem('track', { vertical: props.vertical })}
onTouchstartPassive={onTouchStart}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
>