chore: merge src and src-next
This commit is contained in:
@@ -23,13 +23,13 @@
|
||||
</demo-block>
|
||||
|
||||
<van-action-sheet
|
||||
v-model="show.basic"
|
||||
v-model:show="show.basic"
|
||||
:actions="simpleActions"
|
||||
@select="onSelect"
|
||||
/>
|
||||
|
||||
<van-action-sheet
|
||||
v-model="show.cancel"
|
||||
v-model:show="show.cancel"
|
||||
:actions="simpleActions"
|
||||
close-on-click-action
|
||||
:cancel-text="t('cancel')"
|
||||
@@ -37,7 +37,7 @@
|
||||
/>
|
||||
|
||||
<van-action-sheet
|
||||
v-model="show.description"
|
||||
v-model:show="show.description"
|
||||
:actions="actionsWithDescription"
|
||||
close-on-click-action
|
||||
:cancel-text="t('cancel')"
|
||||
@@ -45,13 +45,13 @@
|
||||
/>
|
||||
|
||||
<van-action-sheet
|
||||
v-model="show.status"
|
||||
v-model:show="show.status"
|
||||
close-on-click-action
|
||||
:actions="statusActions"
|
||||
:cancel-text="t('cancel')"
|
||||
/>
|
||||
|
||||
<van-action-sheet v-model="show.title" :title="t('title')">
|
||||
<van-action-sheet v-model:show="show.title" :title="t('title')">
|
||||
<div class="demo-action-sheet-content">{{ t('content') }}</div>
|
||||
</van-action-sheet>
|
||||
</demo-section>
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
|
||||
// Mixins
|
||||
import { popupMixinProps } from '../mixins/popup';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
import Popup from '../popup';
|
||||
import Loading from '../loading';
|
||||
|
||||
const [createComponent, bem] = createNamespace('action-sheet');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
...popupMixinProps,
|
||||
title: String,
|
||||
actions: Array,
|
||||
duration: [Number, String],
|
||||
cancelText: String,
|
||||
description: String,
|
||||
getContainer: [String, Function],
|
||||
closeOnPopstate: Boolean,
|
||||
closeOnClickAction: Boolean,
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'cross',
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: [
|
||||
'open',
|
||||
'close',
|
||||
'opened',
|
||||
'closed',
|
||||
'cancel',
|
||||
'select',
|
||||
'update:show',
|
||||
'click-overlay',
|
||||
],
|
||||
|
||||
setup(props, { slots, emit }) {
|
||||
function onCancel() {
|
||||
emit('update:show', false);
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
const createEmitter = (name) => () => emit(name);
|
||||
const listeners = {
|
||||
onOpen: createEmitter('open'),
|
||||
onClose: createEmitter('close'),
|
||||
onOpened: createEmitter('opened'),
|
||||
onClosed: createEmitter('closed'),
|
||||
'onClick-overlay': createEmitter('click-overlay'),
|
||||
'onUpdate:show': (show) => {
|
||||
emit('update:show', show);
|
||||
},
|
||||
};
|
||||
|
||||
return function () {
|
||||
const { title, cancelText } = props;
|
||||
|
||||
function Header() {
|
||||
if (title) {
|
||||
return (
|
||||
<div class={bem('header')}>
|
||||
{title}
|
||||
<Icon
|
||||
name={props.closeIcon}
|
||||
class={bem('close')}
|
||||
onClick={onCancel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Content() {
|
||||
if (slots.default) {
|
||||
return <div class={bem('content')}>{slots.default()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Option(item, index) {
|
||||
const { disabled, loading, callback } = item;
|
||||
|
||||
function onClickOption(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (disabled || loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(item);
|
||||
}
|
||||
|
||||
emit('select', item, index);
|
||||
|
||||
if (props.closeOnClickAction) {
|
||||
emit('update:show', false);
|
||||
}
|
||||
}
|
||||
|
||||
function OptionContent() {
|
||||
if (loading) {
|
||||
return <Loading size="20px" />;
|
||||
}
|
||||
|
||||
return [
|
||||
<span class={bem('name')}>{item.name}</span>,
|
||||
item.subname && <div class={bem('subname')}>{item.subname}</div>,
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={[bem('item', { disabled, loading }), item.className]}
|
||||
style={{ color: item.color }}
|
||||
onClick={onClickOption}
|
||||
>
|
||||
{OptionContent()}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelText() {
|
||||
if (cancelText) {
|
||||
return [
|
||||
<div class={bem('gap')} />,
|
||||
<button type="button" class={bem('cancel')} onClick={onCancel}>
|
||||
{cancelText}
|
||||
</button>,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const Description = props.description && (
|
||||
<div class={bem('description')}>{props.description}</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popup
|
||||
class={bem()}
|
||||
position="bottom"
|
||||
show={props.show}
|
||||
round={props.round}
|
||||
overlay={props.overlay}
|
||||
duration={props.duration}
|
||||
lazyRender={props.lazyRender}
|
||||
lockScroll={props.lockScroll}
|
||||
getContainer={props.getContainer}
|
||||
closeOnPopstate={props.closeOnPopstate}
|
||||
closeOnClickOverlay={props.closeOnClickOverlay}
|
||||
safeAreaInsetBottom={props.safeAreaInsetBottom}
|
||||
{...listeners}
|
||||
>
|
||||
{Header()}
|
||||
{Description}
|
||||
{props.actions && props.actions.map(Option)}
|
||||
{Content()}
|
||||
{CancelText()}
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,193 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
|
||||
// Mixins
|
||||
import { popupMixinProps } from '../mixins/popup';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
import Popup from '../popup';
|
||||
import Loading from '../loading';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
import { PopupMixinProps } from '../mixins/popup/type';
|
||||
|
||||
export type ActionSheetItem = {
|
||||
name: string;
|
||||
color?: string;
|
||||
subname?: string;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
callback?: (item: ActionSheetItem) => void;
|
||||
};
|
||||
|
||||
export type ActionSheetProps = PopupMixinProps & {
|
||||
round: boolean;
|
||||
title?: string;
|
||||
actions?: ActionSheetItem[];
|
||||
duration: number | string;
|
||||
closeIcon: string;
|
||||
cancelText?: string;
|
||||
description?: string;
|
||||
closeOnPopstate?: boolean;
|
||||
closeOnClickAction?: boolean;
|
||||
safeAreaInsetBottom?: boolean;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('action-sheet');
|
||||
|
||||
function ActionSheet(
|
||||
h: CreateElement,
|
||||
props: ActionSheetProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<ActionSheetProps>
|
||||
) {
|
||||
const { title, cancelText } = props;
|
||||
|
||||
function onCancel() {
|
||||
emit(ctx, 'input', false);
|
||||
emit(ctx, 'cancel');
|
||||
}
|
||||
|
||||
function Header() {
|
||||
if (title) {
|
||||
return (
|
||||
<div class={bem('header')}>
|
||||
{title}
|
||||
<Icon
|
||||
name={props.closeIcon}
|
||||
class={bem('close')}
|
||||
onClick={onCancel}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Content() {
|
||||
if (slots.default) {
|
||||
return <div class={bem('content')}>{slots.default()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Option(item: ActionSheetItem, index: number) {
|
||||
const { disabled, loading, callback } = item;
|
||||
|
||||
function onClickOption(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (disabled || loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(item);
|
||||
}
|
||||
|
||||
emit(ctx, 'select', item, index);
|
||||
|
||||
if (props.closeOnClickAction) {
|
||||
emit(ctx, 'input', false);
|
||||
}
|
||||
}
|
||||
|
||||
function OptionContent() {
|
||||
if (loading) {
|
||||
return <Loading size="20px" />;
|
||||
}
|
||||
|
||||
return [
|
||||
<span class={bem('name')}>{item.name}</span>,
|
||||
item.subname && <div class={bem('subname')}>{item.subname}</div>,
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
class={[bem('item', { disabled, loading }), item.className]}
|
||||
style={{ color: item.color }}
|
||||
onClick={onClickOption}
|
||||
>
|
||||
{OptionContent()}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelText() {
|
||||
if (cancelText) {
|
||||
return [
|
||||
<div class={bem('gap')} />,
|
||||
<button type="button" class={bem('cancel')} onClick={onCancel}>
|
||||
{cancelText}
|
||||
</button>,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const Description = props.description && (
|
||||
<div class={bem('description')}>{props.description}</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popup
|
||||
class={bem()}
|
||||
position="bottom"
|
||||
round={props.round}
|
||||
value={props.value}
|
||||
overlay={props.overlay}
|
||||
duration={props.duration}
|
||||
lazyRender={props.lazyRender}
|
||||
lockScroll={props.lockScroll}
|
||||
getContainer={props.getContainer}
|
||||
closeOnPopstate={props.closeOnPopstate}
|
||||
closeOnClickOverlay={props.closeOnClickOverlay}
|
||||
safeAreaInsetBottom={props.safeAreaInsetBottom}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{Header()}
|
||||
{Description}
|
||||
{props.actions && props.actions.map(Option)}
|
||||
{Content()}
|
||||
{CancelText()}
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
ActionSheet.props = {
|
||||
...popupMixinProps,
|
||||
title: String,
|
||||
actions: Array,
|
||||
duration: [Number, String],
|
||||
cancelText: String,
|
||||
description: String,
|
||||
getContainer: [String, Function],
|
||||
closeOnPopstate: Boolean,
|
||||
closeOnClickAction: Boolean,
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'cross',
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<ActionSheetProps>(ActionSheet);
|
||||
@@ -0,0 +1,149 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { BORDER_SURROUND, WHITE } from '../utils/constant';
|
||||
import { routeProps, route } from '../utils/router';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
import Loading from '../loading';
|
||||
|
||||
const [createComponent, bem] = createNamespace('button');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
...routeProps,
|
||||
text: String,
|
||||
icon: String,
|
||||
color: String,
|
||||
block: Boolean,
|
||||
plain: Boolean,
|
||||
round: Boolean,
|
||||
square: Boolean,
|
||||
loading: Boolean,
|
||||
hairline: Boolean,
|
||||
disabled: Boolean,
|
||||
iconPrefix: String,
|
||||
nativeType: String,
|
||||
loadingText: String,
|
||||
loadingType: String,
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'normal',
|
||||
},
|
||||
loadingSize: {
|
||||
type: String,
|
||||
default: '20px',
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['click', 'touchstart'],
|
||||
|
||||
methods: {
|
||||
onClick() {
|
||||
if (!this.loading && !this.disabled) {
|
||||
this.$emit('click', event);
|
||||
route(this.$router, this);
|
||||
}
|
||||
},
|
||||
|
||||
onTouchstart(event) {
|
||||
this.$emit('touchstart', event);
|
||||
},
|
||||
|
||||
genContent() {
|
||||
const Content = [];
|
||||
|
||||
if (this.loading) {
|
||||
Content.push(
|
||||
<Loading
|
||||
class={bem('loading')}
|
||||
size={this.loadingSize}
|
||||
type={this.loadingType}
|
||||
color="currentColor"
|
||||
/>
|
||||
);
|
||||
} else if (this.icon) {
|
||||
Content.push(
|
||||
<Icon
|
||||
name={this.icon}
|
||||
class={bem('icon')}
|
||||
classPrefix={this.iconPrefix}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let text;
|
||||
if (this.loading) {
|
||||
text = this.loadingText;
|
||||
} else {
|
||||
text = this.$slots.default ? this.$slots.default() : this.text;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
Content.push(<span class={bem('text')}>{text}</span>);
|
||||
}
|
||||
|
||||
return Content;
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const { tag, type, color, plain, disabled, loading, hairline } = this;
|
||||
|
||||
const style = {};
|
||||
|
||||
if (color) {
|
||||
style.color = plain ? color : WHITE;
|
||||
|
||||
if (!plain) {
|
||||
// Use background instead of backgroundColor to make linear-gradient work
|
||||
style.background = color;
|
||||
}
|
||||
|
||||
// hide border when color is linear-gradient
|
||||
if (color.indexOf('gradient') !== -1) {
|
||||
style.border = 0;
|
||||
} else {
|
||||
style.borderColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
const classes = [
|
||||
bem([
|
||||
type,
|
||||
this.size,
|
||||
{
|
||||
plain,
|
||||
loading,
|
||||
disabled,
|
||||
hairline,
|
||||
block: this.block,
|
||||
round: this.round,
|
||||
square: this.square,
|
||||
},
|
||||
]),
|
||||
{ [BORDER_SURROUND]: hairline },
|
||||
];
|
||||
|
||||
return (
|
||||
<tag
|
||||
style={style}
|
||||
class={classes}
|
||||
type={this.nativeType}
|
||||
disabled={disabled}
|
||||
onClick={this.onClick}
|
||||
onTouchstart={this.onTouchstart}
|
||||
>
|
||||
<div class={bem('content')}>{this.genContent()}</div>
|
||||
</tag>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1,191 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import { BORDER_SURROUND, WHITE } from '../utils/constant';
|
||||
import { routeProps, RouteProps, functionalRoute } from '../utils/router';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
import Loading, { LoadingType } from '../loading';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger';
|
||||
|
||||
export type ButtonSize = 'large' | 'normal' | 'small' | 'mini';
|
||||
|
||||
export type ButtonProps = RouteProps & {
|
||||
tag: keyof HTMLElementTagNameMap | string;
|
||||
type: ButtonType;
|
||||
size: ButtonSize;
|
||||
text?: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
block?: boolean;
|
||||
plain?: boolean;
|
||||
round?: boolean;
|
||||
square?: boolean;
|
||||
loading?: boolean;
|
||||
hairline?: boolean;
|
||||
disabled?: boolean;
|
||||
nativeType?: string;
|
||||
iconPrefix?: string;
|
||||
loadingSize: string;
|
||||
loadingType?: LoadingType;
|
||||
loadingText?: string;
|
||||
};
|
||||
|
||||
export type ButtonEvents = {
|
||||
onClick?(event: Event): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('button');
|
||||
|
||||
function Button(
|
||||
h: CreateElement,
|
||||
props: ButtonProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<ButtonProps>
|
||||
) {
|
||||
const {
|
||||
tag,
|
||||
icon,
|
||||
type,
|
||||
color,
|
||||
plain,
|
||||
disabled,
|
||||
loading,
|
||||
hairline,
|
||||
loadingText,
|
||||
} = props;
|
||||
|
||||
const style: Record<string, string | number> = {};
|
||||
|
||||
if (color) {
|
||||
style.color = plain ? color : WHITE;
|
||||
|
||||
if (!plain) {
|
||||
// Use background instead of backgroundColor to make linear-gradient work
|
||||
style.background = color;
|
||||
}
|
||||
|
||||
// hide border when color is linear-gradient
|
||||
if (color.indexOf('gradient') !== -1) {
|
||||
style.border = 0;
|
||||
} else {
|
||||
style.borderColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
function onClick(event: Event) {
|
||||
if (!loading && !disabled) {
|
||||
emit(ctx, 'click', event);
|
||||
functionalRoute(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchstart(event: TouchEvent) {
|
||||
emit(ctx, 'touchstart', event);
|
||||
}
|
||||
|
||||
const classes = [
|
||||
bem([
|
||||
type,
|
||||
props.size,
|
||||
{
|
||||
plain,
|
||||
loading,
|
||||
disabled,
|
||||
hairline,
|
||||
block: props.block,
|
||||
round: props.round,
|
||||
square: props.square,
|
||||
},
|
||||
]),
|
||||
{ [BORDER_SURROUND]: hairline },
|
||||
];
|
||||
|
||||
function Content() {
|
||||
const content = [];
|
||||
|
||||
if (loading) {
|
||||
content.push(
|
||||
<Loading
|
||||
class={bem('loading')}
|
||||
size={props.loadingSize}
|
||||
type={props.loadingType}
|
||||
color="currentColor"
|
||||
/>
|
||||
);
|
||||
} else if (icon) {
|
||||
content.push(
|
||||
<Icon name={icon} class={bem('icon')} classPrefix={props.iconPrefix} />
|
||||
);
|
||||
}
|
||||
|
||||
let text;
|
||||
if (loading) {
|
||||
text = loadingText;
|
||||
} else {
|
||||
text = slots.default ? slots.default() : props.text;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
content.push(<span class={bem('text')}>{text}</span>);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
return (
|
||||
<tag
|
||||
style={style}
|
||||
class={classes}
|
||||
type={props.nativeType}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
onTouchstart={onTouchstart}
|
||||
{...inherit(ctx)}
|
||||
>
|
||||
<div class={bem('content')}>{Content()}</div>
|
||||
</tag>
|
||||
);
|
||||
}
|
||||
|
||||
Button.props = {
|
||||
...routeProps,
|
||||
text: String,
|
||||
icon: String,
|
||||
color: String,
|
||||
block: Boolean,
|
||||
plain: Boolean,
|
||||
round: Boolean,
|
||||
square: Boolean,
|
||||
loading: Boolean,
|
||||
hairline: Boolean,
|
||||
disabled: Boolean,
|
||||
iconPrefix: String,
|
||||
nativeType: String,
|
||||
loadingText: String,
|
||||
loadingType: String,
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'normal',
|
||||
},
|
||||
loadingSize: {
|
||||
type: String,
|
||||
default: '20px',
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<ButtonProps, ButtonEvents>(Button);
|
||||
@@ -0,0 +1,38 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { BORDER_TOP_BOTTOM } from '../utils/constant';
|
||||
|
||||
const [createComponent, bem] = createNamespace('cell-group');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
title: String,
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { slots }) {
|
||||
return function () {
|
||||
const Group = (
|
||||
<div class={[bem(), { [BORDER_TOP_BOTTOM]: props.border }]}>
|
||||
{slots.default?.()}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (props.title || slots.title) {
|
||||
return (
|
||||
<>
|
||||
<div class={bem('title')} {...this.$attrs}>
|
||||
{slots.title ? slots.title() : props.title}
|
||||
</div>
|
||||
{Group}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return Group;
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,58 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
import { BORDER_TOP_BOTTOM } from '../utils/constant';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots, ScopedSlot } from '../utils/types';
|
||||
|
||||
export type CellGroupProps = {
|
||||
title?: string;
|
||||
border: boolean;
|
||||
};
|
||||
|
||||
export type CellGroupSlots = DefaultSlots & {
|
||||
title?: ScopedSlot;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('cell-group');
|
||||
|
||||
function CellGroup(
|
||||
h: CreateElement,
|
||||
props: CellGroupProps,
|
||||
slots: CellGroupSlots,
|
||||
ctx: RenderContext<CellGroupProps>
|
||||
) {
|
||||
const Group = (
|
||||
<div
|
||||
class={[bem(), { [BORDER_TOP_BOTTOM]: props.border }]}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{slots.default?.()}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (props.title || slots.title) {
|
||||
return (
|
||||
<div>
|
||||
<div class={bem('title')}>
|
||||
{slots.title ? slots.title() : props.title}
|
||||
</div>
|
||||
{Group}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return Group;
|
||||
}
|
||||
|
||||
CellGroup.props = {
|
||||
title: String,
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<CellGroupProps>(CellGroup);
|
||||
@@ -0,0 +1,133 @@
|
||||
// Utils
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { route, routeProps } from '../utils/router';
|
||||
import { cellProps } from './shared';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
|
||||
const [createComponent, bem] = createNamespace('cell');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
...cellProps,
|
||||
...routeProps,
|
||||
},
|
||||
|
||||
emits: ['click'],
|
||||
|
||||
setup(props, { slots, emit }) {
|
||||
return function () {
|
||||
const { icon, size, title, label, value, isLink } = props;
|
||||
const showTitle = slots.title || isDef(title);
|
||||
|
||||
function Label() {
|
||||
const showLabel = slots.label || isDef(label);
|
||||
|
||||
if (showLabel) {
|
||||
return (
|
||||
<div class={[bem('label'), props.labelClass]}>
|
||||
{slots.label ? slots.label() : label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Title() {
|
||||
if (showTitle) {
|
||||
return (
|
||||
<div
|
||||
class={[bem('title'), props.titleClass]}
|
||||
style={props.titleStyle}
|
||||
>
|
||||
{slots.title ? slots.title() : <span>{title}</span>}
|
||||
{Label()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Value() {
|
||||
const showValue = slots.default || isDef(value);
|
||||
|
||||
if (showValue) {
|
||||
return (
|
||||
<div
|
||||
class={[bem('value', { alone: !showTitle }), props.valueClass]}
|
||||
>
|
||||
{slots.default ? slots.default() : <span>{value}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function LeftIcon() {
|
||||
if (slots.icon) {
|
||||
return slots.icon();
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
return (
|
||||
<Icon
|
||||
class={bem('left-icon')}
|
||||
name={icon}
|
||||
classPrefix={props.iconPrefix}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function RightIcon() {
|
||||
const rightIconSlot = slots['right-icon'];
|
||||
|
||||
if (rightIconSlot) {
|
||||
return rightIconSlot();
|
||||
}
|
||||
|
||||
if (isLink) {
|
||||
const { arrowDirection } = props;
|
||||
|
||||
return (
|
||||
<Icon
|
||||
class={bem('right-icon')}
|
||||
name={arrowDirection ? `arrow-${arrowDirection}` : 'arrow'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const onClick = (event) => {
|
||||
emit('click', event);
|
||||
route(this.$router, this);
|
||||
};
|
||||
|
||||
const clickable = isLink || props.clickable;
|
||||
|
||||
const classes = {
|
||||
clickable,
|
||||
center: props.center,
|
||||
required: props.required,
|
||||
borderless: !props.border,
|
||||
};
|
||||
|
||||
if (size) {
|
||||
classes[size] = size;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={bem(classes)}
|
||||
role={clickable ? 'button' : null}
|
||||
tabindex={clickable ? 0 : null}
|
||||
onClick={onClick}
|
||||
>
|
||||
{LeftIcon()}
|
||||
{Title()}
|
||||
{Value()}
|
||||
{RightIcon()}
|
||||
{slots.extra?.()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,150 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import { routeProps, RouteProps, functionalRoute } from '../utils/router';
|
||||
import { cellProps, SharedCellProps } from './shared';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { ScopedSlot, DefaultSlots } from '../utils/types';
|
||||
import { Mods } from '../utils/create/bem';
|
||||
|
||||
export type CellProps = RouteProps & SharedCellProps;
|
||||
|
||||
export type CellSlots = DefaultSlots & {
|
||||
icon?: ScopedSlot;
|
||||
title?: ScopedSlot;
|
||||
label?: ScopedSlot;
|
||||
extra?: ScopedSlot;
|
||||
'right-icon'?: ScopedSlot;
|
||||
};
|
||||
|
||||
export type CellEvents = {
|
||||
onClick?(event: Event): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('cell');
|
||||
|
||||
function Cell(
|
||||
h: CreateElement,
|
||||
props: CellProps,
|
||||
slots: CellSlots,
|
||||
ctx: RenderContext<CellProps>
|
||||
) {
|
||||
const { icon, size, title, label, value, isLink } = props;
|
||||
const showTitle = slots.title || isDef(title);
|
||||
|
||||
function Label() {
|
||||
const showLabel = slots.label || isDef(label);
|
||||
|
||||
if (showLabel) {
|
||||
return (
|
||||
<div class={[bem('label'), props.labelClass]}>
|
||||
{slots.label ? slots.label() : label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Title() {
|
||||
if (showTitle) {
|
||||
return (
|
||||
<div class={[bem('title'), props.titleClass]} style={props.titleStyle}>
|
||||
{slots.title ? slots.title() : <span>{title}</span>}
|
||||
{Label()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Value() {
|
||||
const showValue = slots.default || isDef(value);
|
||||
|
||||
if (showValue) {
|
||||
return (
|
||||
<div class={[bem('value', { alone: !showTitle }), props.valueClass]}>
|
||||
{slots.default ? slots.default() : <span>{value}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function LeftIcon() {
|
||||
if (slots.icon) {
|
||||
return slots.icon();
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
return (
|
||||
<Icon
|
||||
class={bem('left-icon')}
|
||||
name={icon}
|
||||
classPrefix={props.iconPrefix}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function RightIcon() {
|
||||
const rightIconSlot = slots['right-icon'];
|
||||
|
||||
if (rightIconSlot) {
|
||||
return rightIconSlot();
|
||||
}
|
||||
|
||||
if (isLink) {
|
||||
const { arrowDirection } = props;
|
||||
|
||||
return (
|
||||
<Icon
|
||||
class={bem('right-icon')}
|
||||
name={arrowDirection ? `arrow-${arrowDirection}` : 'arrow'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function onClick(event: Event) {
|
||||
emit(ctx, 'click', event);
|
||||
functionalRoute(ctx);
|
||||
}
|
||||
|
||||
const clickable = isLink || props.clickable;
|
||||
|
||||
const classes: Mods = {
|
||||
clickable,
|
||||
center: props.center,
|
||||
required: props.required,
|
||||
borderless: !props.border,
|
||||
};
|
||||
|
||||
if (size) {
|
||||
classes[size] = size;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={bem(classes)}
|
||||
role={clickable ? 'button' : null}
|
||||
tabindex={clickable ? 0 : null}
|
||||
onClick={onClick}
|
||||
{...inherit(ctx)}
|
||||
>
|
||||
{LeftIcon()}
|
||||
{Title()}
|
||||
{Value()}
|
||||
{RightIcon()}
|
||||
{slots.extra?.()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Cell.props = {
|
||||
...cellProps,
|
||||
...routeProps,
|
||||
};
|
||||
|
||||
export default createComponent<CellProps, CellEvents, CellSlots>(Cell);
|
||||
@@ -14,7 +14,7 @@ Vue.use(Circle);
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-circle v-model="currentRate" :rate="30" :speed="100" :text="text" />
|
||||
<van-circle v-model:currentRate="currentRate" :rate="30" :speed="100" :text="text" />
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -36,7 +36,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
:stroke-width="60"
|
||||
text="Custom Width"
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
layer-color="#ebedf0"
|
||||
text="Custom Color"
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
:color="gradientColor"
|
||||
text="Gradient"
|
||||
@@ -83,7 +83,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
:clockwise="false"
|
||||
text="Counter Clockwise"
|
||||
@@ -94,7 +94,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
size="120px"
|
||||
text="Custom Size"
|
||||
@@ -107,7 +107,7 @@ export default {
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model | Current rate | _number_ | - |
|
||||
| v-model:currentRate | Current rate | _number_ | - |
|
||||
| rate | Target rate | _number \| string_ | `100` |
|
||||
| size | Circle size | _number \| string_ | `100px` |
|
||||
| color `v2.1.4` | Progress color, passing object to render gradient | _string \| object_ | `#1989fa` |
|
||||
|
||||
@@ -13,10 +13,15 @@ Vue.use(Circle);
|
||||
|
||||
### 基础用法
|
||||
|
||||
`rate`属性表示进度条的目标进度,`v-model`表示动画过程中的实时进度。当`rate`发生变化时,`v-model`会以`speed`的速度变化,直至达到`rate`设定的值。
|
||||
`rate`属性表示进度条的目标进度,`v-model:currentRate`表示动画过程中的实时进度。当`rate`发生变化时,`v-model:currentRate`会以`speed`的速度变化,直至达到`rate`设定的值。
|
||||
|
||||
```html
|
||||
<van-circle v-model="currentRate" :rate="30" :speed="100" :text="text" />
|
||||
<van-circle
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="30"
|
||||
:speed="100"
|
||||
:text="text"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -40,7 +45,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
:stroke-width="60"
|
||||
text="宽度定制"
|
||||
@@ -53,7 +58,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
layer-color="#ebedf0"
|
||||
text="颜色定制"
|
||||
@@ -66,7 +71,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
:color="gradientColor"
|
||||
text="渐变色"
|
||||
@@ -93,7 +98,7 @@ export default {
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
:clockwise="false"
|
||||
text="逆时针方向"
|
||||
@@ -105,7 +110,12 @@ export default {
|
||||
通过`size`属性设置圆环直径
|
||||
|
||||
```html
|
||||
<van-circle v-model="currentRate" :rate="rate" size="120px" text="大小定制" />
|
||||
<van-circle
|
||||
v-model:currentRate="currentRate"
|
||||
:rate="rate"
|
||||
size="120px"
|
||||
text="大小定制"
|
||||
/>
|
||||
```
|
||||
|
||||
## API
|
||||
@@ -114,7 +124,7 @@ export default {
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model | 当前进度 | _number_ | - |
|
||||
| v-model:currentRate | 当前进度 | _number_ | - |
|
||||
| rate | 目标进度 | _number \| string_ | `100` |
|
||||
| size | 圆环直径,默认单位为 `px` | _number \| string_ | `100px` |
|
||||
| color `v2.1.4` | 进度条颜色,传入对象格式可以定义渐变色 | _string \| object_ | `#1989fa` |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<demo-section>
|
||||
<demo-block :title="t('basicUsage')">
|
||||
<van-circle
|
||||
v-model="currentRate1"
|
||||
v-model:currentRate="currentRate1"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
:text="currentRate1.toFixed(0) + '%'"
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<demo-block :title="t('customStyle')">
|
||||
<van-circle
|
||||
v-model="currentRate3"
|
||||
v-model:currentRate="currentRate3"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
:stroke-width="60"
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
|
||||
<van-circle
|
||||
v-model="currentRate3"
|
||||
v-model:currentRate="currentRate3"
|
||||
color="#ee0a24"
|
||||
:rate="rate"
|
||||
layer-color="#ebedf0"
|
||||
@@ -28,7 +28,7 @@
|
||||
/>
|
||||
|
||||
<van-circle
|
||||
v-model="currentRate2"
|
||||
v-model:currentRate="currentRate2"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
:color="gradientColor"
|
||||
@@ -36,7 +36,7 @@
|
||||
/>
|
||||
|
||||
<van-circle
|
||||
v-model="currentRate4"
|
||||
v-model:currentRate="currentRate4"
|
||||
color="#07c160"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
@@ -46,7 +46,7 @@
|
||||
/>
|
||||
|
||||
<van-circle
|
||||
v-model="currentRate4"
|
||||
v-model:currentRate="currentRate4"
|
||||
color="#7232dd"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
|
||||
+10
-7
@@ -23,7 +23,7 @@ export default createComponent({
|
||||
props: {
|
||||
text: String,
|
||||
strokeLinecap: String,
|
||||
value: {
|
||||
currentRate: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
@@ -61,6 +61,8 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['update:currentRate'],
|
||||
|
||||
beforeCreate() {
|
||||
this.uid = `van-circle-gradient-${uid++}`;
|
||||
},
|
||||
@@ -83,7 +85,7 @@ export default createComponent({
|
||||
},
|
||||
|
||||
layerStyle() {
|
||||
const offset = (PERIMETER * this.value) / 100;
|
||||
const offset = (PERIMETER * this.currentRate) / 100;
|
||||
|
||||
return {
|
||||
stroke: `${this.color}`,
|
||||
@@ -130,7 +132,7 @@ export default createComponent({
|
||||
rate: {
|
||||
handler(rate) {
|
||||
this.startTime = Date.now();
|
||||
this.startRate = this.value;
|
||||
this.startRate = this.currentRate;
|
||||
this.endRate = format(rate);
|
||||
this.increase = this.endRate > this.startRate;
|
||||
this.duration = Math.abs(
|
||||
@@ -141,7 +143,7 @@ export default createComponent({
|
||||
cancelRaf(this.rafId);
|
||||
this.rafId = raf(this.animate);
|
||||
} else {
|
||||
this.$emit('input', this.endRate);
|
||||
this.$emit('update:currentRate', this.endRate);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
@@ -154,7 +156,7 @@ export default createComponent({
|
||||
const progress = Math.min((now - this.startTime) / this.duration, 1);
|
||||
const rate = progress * (this.endRate - this.startRate) + this.startRate;
|
||||
|
||||
this.$emit('input', format(parseFloat(rate.toFixed(1))));
|
||||
this.$emit('update:currentRate', format(parseFloat(rate.toFixed(1))));
|
||||
|
||||
if (this.increase ? rate < this.endRate : rate > this.endRate) {
|
||||
this.rafId = raf(this.animate);
|
||||
@@ -175,8 +177,9 @@ export default createComponent({
|
||||
stroke={this.gradient ? `url(#${this.uid})` : this.color}
|
||||
/>
|
||||
</svg>
|
||||
{this.slots() ||
|
||||
(this.text && <div class={bem('text')}>{this.text}</div>)}
|
||||
{this.$slots.default
|
||||
? this.$slots.default()
|
||||
: this.text && <div class={bem('text')}>{this.text}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
+3
-1
@@ -15,6 +15,8 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['click'],
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
const { index } = this;
|
||||
@@ -44,7 +46,7 @@ export default createComponent({
|
||||
class={bem({ [span]: span, [`offset-${offset}`]: offset })}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
{this.slots()}
|
||||
{this.$slots.default?.()}
|
||||
</this.tag>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -21,6 +21,8 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['change', 'finish'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
remain: 0,
|
||||
@@ -40,7 +42,9 @@ export default createComponent({
|
||||
watch: {
|
||||
time: {
|
||||
immediate: true,
|
||||
handler: 'reset',
|
||||
handler() {
|
||||
this.reset();
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -153,7 +157,9 @@ export default createComponent({
|
||||
render() {
|
||||
return (
|
||||
<div class={bem()}>
|
||||
{this.slots('default', this.timeData) || this.formattedTime}
|
||||
{this.$slots.default
|
||||
? this.$slots.default(this.timeData)
|
||||
: this.formattedTime}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
// import Vue from 'vue';
|
||||
import VanDialog from './Dialog';
|
||||
import { isServer } from '../utils';
|
||||
import { inBrowser } from '../utils';
|
||||
|
||||
let instance;
|
||||
|
||||
@@ -28,7 +28,7 @@ function initInstance() {
|
||||
|
||||
function Dialog(options) {
|
||||
/* istanbul ignore if */
|
||||
if (isServer) {
|
||||
if (!inBrowser) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createNamespace } from '../utils';
|
||||
|
||||
const [createComponent, bem] = createNamespace('divider');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
dashed: Boolean,
|
||||
hairline: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
contentPosition: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const Content = this.$slots.default?.();
|
||||
return (
|
||||
<div
|
||||
role="separator"
|
||||
class={bem({
|
||||
dashed: this.dashed,
|
||||
hairline: this.hairline,
|
||||
[`content-${this.contentPosition}`]: !!Content,
|
||||
})}
|
||||
>
|
||||
{Content}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type DividerProps = {
|
||||
dashed?: boolean;
|
||||
hairline: boolean;
|
||||
borderColor?: string;
|
||||
contentPosition: 'left' | 'center' | 'right';
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('divider');
|
||||
|
||||
function Divider(
|
||||
h: CreateElement,
|
||||
props: DividerProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
role="separator"
|
||||
style={{ borderColor: props.borderColor }}
|
||||
class={bem({
|
||||
dashed: props.dashed,
|
||||
hairline: props.hairline,
|
||||
[`content-${props.contentPosition}`]: slots.default,
|
||||
})}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{slots.default && slots.default()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Divider.props = {
|
||||
dashed: Boolean,
|
||||
hairline: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
contentPosition: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<DividerProps>(Divider);
|
||||
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
render() {
|
||||
const genStop = (color: string, offset: number, opacity?: number) => (
|
||||
const genStop = (color, offset, opacity) => (
|
||||
<stop stop-color={color} offset={`${offset}%`} stop-opacity={opacity} />
|
||||
);
|
||||
|
||||
+5
-3
@@ -16,7 +16,7 @@ export default createComponent({
|
||||
|
||||
methods: {
|
||||
genImageContent() {
|
||||
const slots = this.slots('image');
|
||||
const slots = this.$slots.image?.();
|
||||
|
||||
if (slots) {
|
||||
return slots;
|
||||
@@ -40,7 +40,9 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genDescription() {
|
||||
const description = this.slots('description') || this.description;
|
||||
const description = this.$slots.description
|
||||
? this.slot.description()
|
||||
: this.description;
|
||||
|
||||
if (description) {
|
||||
return <p class={bem('description')}>{description}</p>;
|
||||
@@ -48,7 +50,7 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genBottom() {
|
||||
const slot = this.slots();
|
||||
const slot = this.$slots.default?.();
|
||||
|
||||
if (slot) {
|
||||
return <div class={bem('bottom')}>{slot}</div>;
|
||||
|
||||
+11
-17
@@ -1,5 +1,5 @@
|
||||
// Utils
|
||||
import { createNamespace, addUnit, isDef } from '../utils';
|
||||
import { createNamespace, addUnit } from '../utils';
|
||||
import { BORDER } from '../utils/constant';
|
||||
import { route, routeProps } from '../utils/router';
|
||||
|
||||
@@ -21,10 +21,11 @@ export default createComponent({
|
||||
text: String,
|
||||
icon: String,
|
||||
iconPrefix: String,
|
||||
info: [Number, String],
|
||||
badge: [Number, String],
|
||||
},
|
||||
|
||||
emits: ['click'],
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
const { square, gutter, columnNum } = this.parent;
|
||||
@@ -70,14 +71,11 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genIcon() {
|
||||
const iconSlot = this.slots('icon');
|
||||
const info = isDef(this.badge) ? this.badge : this.info;
|
||||
|
||||
if (iconSlot) {
|
||||
if (this.$slots.icon) {
|
||||
return (
|
||||
<div class={bem('icon-wrapper')}>
|
||||
{iconSlot}
|
||||
<Info dot={this.dot} info={info} />
|
||||
{this.$slots.icon()}
|
||||
<Info dot={this.dot} info={this.badge} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -87,7 +85,7 @@ export default createComponent({
|
||||
<Icon
|
||||
name={this.icon}
|
||||
dot={this.dot}
|
||||
info={info}
|
||||
info={this.badge}
|
||||
size={this.parent.iconSize}
|
||||
class={bem('icon')}
|
||||
classPrefix={this.iconPrefix}
|
||||
@@ -97,10 +95,8 @@ export default createComponent({
|
||||
},
|
||||
|
||||
getText() {
|
||||
const textSlot = this.slots('text');
|
||||
|
||||
if (textSlot) {
|
||||
return textSlot;
|
||||
if (this.$slots.text) {
|
||||
return this.$slots.text();
|
||||
}
|
||||
|
||||
if (this.text) {
|
||||
@@ -109,10 +105,8 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genContent() {
|
||||
const slot = this.slots();
|
||||
|
||||
if (slot) {
|
||||
return slot;
|
||||
if (this.$slots.default) {
|
||||
return this.$slots.default();
|
||||
}
|
||||
|
||||
return [this.genIcon(), this.getText()];
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ export default createComponent({
|
||||
style={this.style}
|
||||
class={[bem(), { [BORDER_TOP]: this.border && !this.gutter }]}
|
||||
>
|
||||
{this.slots()}
|
||||
{this.$slots.default?.()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Ref } from 'vue';
|
||||
import { useGlobalEvent } from './use-global-event';
|
||||
|
||||
export type UseClickOutsideOpitons = {
|
||||
event: string;
|
||||
callback: EventListener;
|
||||
element: Ref<Element>;
|
||||
flag?: Ref<boolean>;
|
||||
};
|
||||
|
||||
export function useClickOutside(options: UseClickOutsideOpitons) {
|
||||
const { event = 'click', callback, element, flag } = options;
|
||||
|
||||
function onClick(event: Event) {
|
||||
if (!element.value.contains(event.target as Node)) {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
|
||||
useGlobalEvent(document, event, onClick, false, flag);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { on, off } from '../utils/dom/event';
|
||||
import {
|
||||
Ref,
|
||||
watch,
|
||||
onMounted,
|
||||
onActivated,
|
||||
onUnmounted,
|
||||
onDeactivated
|
||||
} from 'vue';
|
||||
|
||||
export function useGlobalEvent(
|
||||
target: EventTarget,
|
||||
event: string,
|
||||
handler: EventListener,
|
||||
passive = false,
|
||||
flag?: Ref<boolean>
|
||||
) {
|
||||
let binded: boolean;
|
||||
|
||||
function add() {
|
||||
if (binded || (flag && !flag.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
on(target, event, handler, passive);
|
||||
binded = true;
|
||||
}
|
||||
|
||||
function remove() {
|
||||
if (binded) {
|
||||
off(target, event, handler);
|
||||
binded = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
watch(() => {
|
||||
flag.value ? add() : remove();
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(add);
|
||||
onActivated(add);
|
||||
onUnmounted(remove);
|
||||
onDeactivated(remove);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useTouch } from './use-touch';
|
||||
import { getScroller } from '../utils/dom/scroll';
|
||||
import { on, off, preventDefault } from '../utils/dom/event';
|
||||
|
||||
let count = 0;
|
||||
const CLASSNAME = 'van-overflow-hidden';
|
||||
|
||||
export function useLockScroll(element: HTMLElement) {
|
||||
const { start, move, deltaY, direction } = useTouch();
|
||||
|
||||
function onTouchMove(event: TouchEvent) {
|
||||
move(event);
|
||||
|
||||
if (direction.value !== 'vertical') {
|
||||
return;
|
||||
}
|
||||
|
||||
let prevent = false;
|
||||
const up = deltaY.value < 0;
|
||||
const scroller = getScroller(event.target as HTMLElement, element);
|
||||
const { scrollTop, scrollHeight, offsetHeight } = scroller as HTMLElement;
|
||||
|
||||
if (scrollTop === 0) {
|
||||
prevent = up && offsetHeight < scrollHeight;
|
||||
} else if (scrollTop + offsetHeight >= scrollHeight) {
|
||||
prevent = !up;
|
||||
}
|
||||
|
||||
if (prevent) {
|
||||
preventDefault(event, true);
|
||||
}
|
||||
}
|
||||
|
||||
function lock() {
|
||||
if (!count) {
|
||||
document.body.classList.add(CLASSNAME);
|
||||
}
|
||||
|
||||
count++;
|
||||
on(document, 'touchstart', start);
|
||||
on(document, 'touchmove', onTouchMove);
|
||||
}
|
||||
|
||||
lock();
|
||||
|
||||
return function unlock() {
|
||||
count--;
|
||||
off(document, 'touchstart', start);
|
||||
off(document, 'touchmove', onTouchMove);
|
||||
|
||||
if (!count) {
|
||||
document.body.classList.remove(CLASSNAME);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const MIN_DISTANCE = 10;
|
||||
|
||||
function getDirection(x: number, y: number) {
|
||||
if (x > y && x > MIN_DISTANCE) {
|
||||
return 'horizontal';
|
||||
}
|
||||
|
||||
if (y > x && y > MIN_DISTANCE) {
|
||||
return 'vertical';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export function useTouch() {
|
||||
const startX = ref(0);
|
||||
const startY = ref(0);
|
||||
const deltaX = ref(0);
|
||||
const deltaY = ref(0);
|
||||
const offsetX = ref(0);
|
||||
const offsetY = ref(0);
|
||||
const direction = ref('');
|
||||
|
||||
function reset() {
|
||||
direction.value = '';
|
||||
deltaX.value = 0;
|
||||
deltaY.value = 0;
|
||||
offsetX.value = 0;
|
||||
offsetY.value = 0;
|
||||
}
|
||||
|
||||
function start(event: TouchEvent) {
|
||||
reset();
|
||||
startX.value = event.touches[0].clientX;
|
||||
startY.value = event.touches[0].clientY;
|
||||
}
|
||||
|
||||
function move(event: TouchEvent) {
|
||||
const touch = event.touches[0];
|
||||
deltaX.value = touch.clientX - startX.value;
|
||||
deltaY.value = touch.clientY - startY.value;
|
||||
offsetX.value = Math.abs(deltaX.value);
|
||||
offsetY.value = Math.abs(deltaY.value);
|
||||
|
||||
if (!direction.value) {
|
||||
direction.value = getDirection(offsetX.value, offsetY.value);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
move,
|
||||
start,
|
||||
startX,
|
||||
startY,
|
||||
deltaX,
|
||||
deltaY,
|
||||
offsetX,
|
||||
offsetY,
|
||||
direction
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Utils
|
||||
import { addUnit, createNamespace } from '../utils';
|
||||
|
||||
// Components
|
||||
import Info from '../info';
|
||||
|
||||
const [createComponent, bem] = createNamespace('icon');
|
||||
|
||||
function isImage(name) {
|
||||
return name ? name.indexOf('/') !== -1 : false;
|
||||
}
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
dot: Boolean,
|
||||
name: String,
|
||||
size: [Number, String],
|
||||
badge: [Number, String],
|
||||
color: String,
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'i',
|
||||
},
|
||||
classPrefix: {
|
||||
type: String,
|
||||
default: bem(),
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const { name } = this;
|
||||
const imageIcon = isImage(name);
|
||||
|
||||
return (
|
||||
<this.tag
|
||||
class={[
|
||||
this.classPrefix,
|
||||
imageIcon ? '' : `${this.classPrefix}-${name}`,
|
||||
]}
|
||||
style={{
|
||||
color: this.color,
|
||||
fontSize: addUnit(this.size),
|
||||
}}
|
||||
>
|
||||
{this.$slots.default?.()}
|
||||
{imageIcon && <img class={bem('image')} src={name} />}
|
||||
<Info
|
||||
dot={this.dot}
|
||||
info={this.badge}
|
||||
/>
|
||||
</this.tag>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace, addUnit, isDef } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
|
||||
// Components
|
||||
import Info from '../info';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type IconProps = {
|
||||
dot?: boolean;
|
||||
tag: keyof HTMLElementTagNameMap | string;
|
||||
name?: string;
|
||||
size?: string | number;
|
||||
info?: string | number;
|
||||
badge?: string | number;
|
||||
color?: string;
|
||||
classPrefix: string;
|
||||
};
|
||||
|
||||
export type IconEvents = {
|
||||
onClick?(event: Event): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('icon');
|
||||
|
||||
function isImage(name?: string): boolean {
|
||||
return name ? name.indexOf('/') !== -1 : false;
|
||||
}
|
||||
|
||||
// compatible with legacy usage, should be removed in next major version
|
||||
const LEGACY_MAP: Record<string, string> = {
|
||||
medel: 'medal',
|
||||
'medel-o': 'medal-o',
|
||||
};
|
||||
|
||||
function correctName(name?: string) {
|
||||
return (name && LEGACY_MAP[name]) || name;
|
||||
}
|
||||
|
||||
function Icon(
|
||||
h: CreateElement,
|
||||
props: IconProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<IconProps>
|
||||
) {
|
||||
const name = correctName(props.name);
|
||||
const imageIcon = isImage(name);
|
||||
|
||||
return (
|
||||
<props.tag
|
||||
class={[
|
||||
props.classPrefix,
|
||||
imageIcon ? '' : `${props.classPrefix}-${name}`,
|
||||
]}
|
||||
style={{
|
||||
color: props.color,
|
||||
fontSize: addUnit(props.size),
|
||||
}}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{slots.default && slots.default()}
|
||||
{imageIcon && <img class={bem('image')} src={name} />}
|
||||
<Info
|
||||
dot={props.dot}
|
||||
info={isDef(props.badge) ? props.badge : props.info}
|
||||
/>
|
||||
</props.tag>
|
||||
);
|
||||
}
|
||||
|
||||
Icon.props = {
|
||||
dot: Boolean,
|
||||
name: String,
|
||||
size: [Number, String],
|
||||
// @deprecated
|
||||
// should be removed in next major version
|
||||
info: [Number, String],
|
||||
badge: [Number, String],
|
||||
color: String,
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'i',
|
||||
},
|
||||
classPrefix: {
|
||||
type: String,
|
||||
default: bem(),
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<IconProps, IconEvents>(Icon);
|
||||
@@ -1,6 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
// import Vue from 'vue';
|
||||
import VueImagePreview from './ImagePreview';
|
||||
import { isServer } from '../utils';
|
||||
import { inBrowser } from '../utils';
|
||||
|
||||
let instance;
|
||||
|
||||
@@ -46,7 +46,7 @@ const initInstance = () => {
|
||||
|
||||
const ImagePreview = (images, startPosition = 0) => {
|
||||
/* istanbul ignore if */
|
||||
if (isServer) {
|
||||
if (!inBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+19
-11
@@ -31,6 +31,8 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['load', 'error', 'click'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
@@ -116,7 +118,9 @@ export default createComponent({
|
||||
if (this.loading && this.showLoading) {
|
||||
return (
|
||||
<div class={bem('loading')}>
|
||||
{this.slots('loading') || (
|
||||
{this.$slots.loading ? (
|
||||
this.$slots.loading()
|
||||
) : (
|
||||
<Icon name={this.loadingIcon} class={bem('loading-icon')} />
|
||||
)}
|
||||
</div>
|
||||
@@ -126,7 +130,9 @@ export default createComponent({
|
||||
if (this.error && this.showError) {
|
||||
return (
|
||||
<div class={bem('error')}>
|
||||
{this.slots('error') || (
|
||||
{this.$slots.error ? (
|
||||
this.$slots.error()
|
||||
) : (
|
||||
<Icon name={this.errorIcon} class={bem('error-icon')} />
|
||||
)}
|
||||
</div>
|
||||
@@ -153,14 +159,16 @@ export default createComponent({
|
||||
return <img ref="image" vLazy={this.src} {...imgData} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
src={this.src}
|
||||
onLoad={this.onLoad}
|
||||
onError={this.onError}
|
||||
{...imgData}
|
||||
/>
|
||||
);
|
||||
if (this.src) {
|
||||
return (
|
||||
<img
|
||||
src={this.src}
|
||||
onLoad={this.onLoad}
|
||||
onError={this.onError}
|
||||
{...imgData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -173,7 +181,7 @@ export default createComponent({
|
||||
>
|
||||
{this.genImage()}
|
||||
{this.genPlaceholder()}
|
||||
{this.slots()}
|
||||
{this.$slots.default?.()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// Utils
|
||||
import { isDef, createNamespace } from '../utils';
|
||||
|
||||
const [createComponent, bem] = createNamespace('info');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
dot: Boolean,
|
||||
info: [Number, String],
|
||||
},
|
||||
|
||||
render() {
|
||||
const { dot, info } = this;
|
||||
const showInfo = isDef(info) && info !== '';
|
||||
|
||||
if (!dot && !showInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
return <div class={bem({ dot })}>{dot ? '' : info}</div>;
|
||||
},
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type InfoProps = {
|
||||
dot?: boolean;
|
||||
info?: string | number;
|
||||
badge?: string | number;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('info');
|
||||
|
||||
function Info(
|
||||
h: CreateElement,
|
||||
props: InfoProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<InfoProps>
|
||||
) {
|
||||
const { dot, info } = props;
|
||||
const showInfo = isDef(info) && info !== '';
|
||||
|
||||
if (!dot && !showInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem({ dot })} {...inherit(ctx, true)}>
|
||||
{dot ? '' : props.info}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Info.props = {
|
||||
dot: Boolean,
|
||||
info: [Number, String],
|
||||
};
|
||||
|
||||
export default createComponent<InfoProps>(Info);
|
||||
@@ -0,0 +1,64 @@
|
||||
// Utils
|
||||
import { createNamespace, addUnit } from '../utils';
|
||||
|
||||
const [createComponent, bem] = createNamespace('loading');
|
||||
|
||||
const SpinIcon = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
SpinIcon.push(<i />);
|
||||
}
|
||||
|
||||
const CircularIcon = (
|
||||
<svg class={bem('circular')} viewBox="25 25 50 50">
|
||||
<circle cx="50" cy="50" r="20" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
color: String,
|
||||
size: [Number, String],
|
||||
vertical: Boolean,
|
||||
textSize: [Number, String],
|
||||
type: {
|
||||
type: String,
|
||||
default: 'circular',
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
genLoadingText() {
|
||||
if (this.$slots.default) {
|
||||
const style = this.textSize && {
|
||||
fontSize: addUnit(this.textSize),
|
||||
};
|
||||
|
||||
return (
|
||||
<span class={bem('text')} style={style}>
|
||||
{this.$slots.default()}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const { color, size, type, vertical } = this;
|
||||
|
||||
const style = { color };
|
||||
if (size) {
|
||||
const iconSize = addUnit(size);
|
||||
style.width = iconSize;
|
||||
style.height = iconSize;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem([type, { vertical }])}>
|
||||
<span class={bem('spinner', type)} style={style}>
|
||||
{type === 'spinner' ? SpinIcon : CircularIcon}
|
||||
</span>
|
||||
{this.genLoadingText()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace, addUnit } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type LoadingType = 'circular' | 'spinner';
|
||||
|
||||
export type LoadingProps = {
|
||||
type: LoadingType;
|
||||
size?: string | number;
|
||||
color: string;
|
||||
vertical?: boolean;
|
||||
textSize?: string | number;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('loading');
|
||||
|
||||
function LoadingIcon(h: CreateElement, props: LoadingProps) {
|
||||
if (props.type === 'spinner') {
|
||||
const Spin = [];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
Spin.push(<i />);
|
||||
}
|
||||
return Spin;
|
||||
}
|
||||
|
||||
return (
|
||||
<svg class={bem('circular')} viewBox="25 25 50 50">
|
||||
<circle cx="50" cy="50" r="20" fill="none" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function LoadingText(
|
||||
h: CreateElement,
|
||||
props: LoadingProps,
|
||||
slots: DefaultSlots
|
||||
) {
|
||||
if (slots.default) {
|
||||
const style = props.textSize && {
|
||||
fontSize: addUnit(props.textSize),
|
||||
};
|
||||
|
||||
return (
|
||||
<span class={bem('text')} style={style}>
|
||||
{slots.default()}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Loading(
|
||||
h: CreateElement,
|
||||
props: LoadingProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<LoadingProps>
|
||||
) {
|
||||
const { color, size, type } = props;
|
||||
|
||||
const style: { [key: string]: string } = { color };
|
||||
if (size) {
|
||||
const iconSize = addUnit(size) as string;
|
||||
style.width = iconSize;
|
||||
style.height = iconSize;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={bem([type, { vertical: props.vertical }])}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
<span class={bem('spinner', type)} style={style}>
|
||||
{LoadingIcon(h, props)}
|
||||
</span>
|
||||
{LoadingText(h, props, slots)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading.props = {
|
||||
color: String,
|
||||
size: [Number, String],
|
||||
vertical: Boolean,
|
||||
textSize: [Number, String],
|
||||
type: {
|
||||
type: String,
|
||||
default: 'circular',
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<LoadingProps>(Loading);
|
||||
+10
-19
@@ -1,34 +1,25 @@
|
||||
import Vue from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { deepAssign } from '../utils/deep-assign';
|
||||
import defaultMessages from './lang/zh-CN';
|
||||
|
||||
declare module 'vue' {
|
||||
interface VueConstructor {
|
||||
util: {
|
||||
defineReactive(obj: object, key: string, value: any): void;
|
||||
};
|
||||
}
|
||||
}
|
||||
type Messages = Record<string, Record<string, any>>;
|
||||
|
||||
const proto = Vue.prototype;
|
||||
const { defineReactive } = Vue.util;
|
||||
|
||||
defineReactive(proto, '$vantLang', 'zh-CN');
|
||||
defineReactive(proto, '$vantMessages', {
|
||||
const lang = ref('zh-CN');
|
||||
const messages = ref<Messages>({
|
||||
'zh-CN': defaultMessages,
|
||||
});
|
||||
|
||||
export default {
|
||||
messages() {
|
||||
return proto.$vantMessages[proto.$vantLang];
|
||||
return messages.value[lang.value];
|
||||
},
|
||||
|
||||
use(lang: string, messages?: object) {
|
||||
proto.$vantLang = lang;
|
||||
this.add({ [lang]: messages });
|
||||
use(newLang: string, newMessages?: object) {
|
||||
lang.value = newLang;
|
||||
this.add({ [newLang]: newMessages });
|
||||
},
|
||||
|
||||
add(messages = {}) {
|
||||
deepAssign(proto.$vantMessages, messages);
|
||||
add(newMessages = {}) {
|
||||
deepAssign(messages.value, newMessages);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Bind event when mounted or activated
|
||||
*/
|
||||
import { on, off } from '../utils/dom/event';
|
||||
|
||||
let uid = 0;
|
||||
|
||||
export function BindEventMixin(handler) {
|
||||
const key = `binded_${uid++}`;
|
||||
|
||||
function bind() {
|
||||
if (!this[key]) {
|
||||
handler.call(this, on, true);
|
||||
this[key] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function unbind() {
|
||||
if (this[key]) {
|
||||
handler.call(this, off, false);
|
||||
this[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mounted: bind,
|
||||
activated: bind,
|
||||
deactivated: unbind,
|
||||
beforeDestroy: unbind,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Bind event when mounted or activated
|
||||
*/
|
||||
import { on, off } from '../utils/dom/event';
|
||||
|
||||
type BindEventMixinThis = {
|
||||
binded: boolean;
|
||||
};
|
||||
|
||||
type BindEventHandler = (bind: Function, isBind: boolean) => void;
|
||||
|
||||
export function BindEventMixin(handler: BindEventHandler) {
|
||||
function bind(this: BindEventMixinThis) {
|
||||
if (!this.binded) {
|
||||
handler.call(this, on, true);
|
||||
this.binded = true;
|
||||
}
|
||||
}
|
||||
|
||||
function unbind(this: BindEventMixinThis) {
|
||||
if (this.binded) {
|
||||
handler.call(this, off, false);
|
||||
this.binded = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
mounted: bind,
|
||||
activated: bind,
|
||||
deactivated: unbind,
|
||||
beforeDestroy: unbind,
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import { OverlayConfig } from './overlay';
|
||||
|
||||
export type StackItem = {
|
||||
vm: any;
|
||||
overlay: any;
|
||||
config: OverlayConfig;
|
||||
};
|
||||
|
||||
export const context = {
|
||||
|
||||
+29
-43
@@ -1,11 +1,5 @@
|
||||
// Context
|
||||
import { context } from './context';
|
||||
import {
|
||||
openOverlay,
|
||||
closeOverlay,
|
||||
updateOverlay,
|
||||
removeOverlay,
|
||||
} from './overlay';
|
||||
|
||||
// Utils
|
||||
import { on, off, preventDefault } from '../../utils/dom/event';
|
||||
@@ -14,18 +8,19 @@ import { getScroller } from '../../utils/dom/scroll';
|
||||
|
||||
// Mixins
|
||||
import { TouchMixin } from '../touch';
|
||||
import { PortalMixin } from '../portal';
|
||||
import { CloseOnPopstateMixin } from '../close-on-popstate';
|
||||
|
||||
export const popupMixinProps = {
|
||||
// whether to show popup
|
||||
value: Boolean,
|
||||
show: Boolean,
|
||||
// whether to show overlay
|
||||
overlay: Boolean,
|
||||
// overlay custom style
|
||||
overlayStyle: Object,
|
||||
// overlay custom class name
|
||||
overlayClass: String,
|
||||
// teleport
|
||||
getContainer: [String, Function],
|
||||
// whether to close popup when click overlay
|
||||
closeOnClickOverlay: Boolean,
|
||||
// z-index
|
||||
@@ -44,23 +39,13 @@ export const popupMixinProps = {
|
||||
|
||||
export function PopupMixin(options = {}) {
|
||||
return {
|
||||
mixins: [
|
||||
TouchMixin,
|
||||
CloseOnPopstateMixin,
|
||||
PortalMixin({
|
||||
afterPortal() {
|
||||
if (this.overlay) {
|
||||
updateOverlay();
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
mixins: [TouchMixin, CloseOnPopstateMixin],
|
||||
|
||||
props: popupMixinProps,
|
||||
|
||||
data() {
|
||||
return {
|
||||
inited: this.value,
|
||||
inited: this.show,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -71,9 +56,9 @@ export function PopupMixin(options = {}) {
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
show(val) {
|
||||
const type = val ? 'open' : 'close';
|
||||
this.inited = this.inited || this.value;
|
||||
this.inited = this.inited || this.show;
|
||||
this[type]();
|
||||
|
||||
if (!options.skipToggleEvent) {
|
||||
@@ -85,7 +70,7 @@ export function PopupMixin(options = {}) {
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.value) {
|
||||
if (this.show) {
|
||||
this.open();
|
||||
}
|
||||
},
|
||||
@@ -93,23 +78,22 @@ export function PopupMixin(options = {}) {
|
||||
/* istanbul ignore next */
|
||||
activated() {
|
||||
if (this.shouldReopen) {
|
||||
this.$emit('input', true);
|
||||
this.$emit('update:show', true);
|
||||
this.shouldReopen = false;
|
||||
}
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.removeLock();
|
||||
removeOverlay(this);
|
||||
|
||||
if (this.getContainer) {
|
||||
removeNode(this.$el);
|
||||
removeNode(this.$refs.root);
|
||||
}
|
||||
},
|
||||
|
||||
/* istanbul ignore next */
|
||||
deactivated() {
|
||||
if (this.value) {
|
||||
if (this.show) {
|
||||
this.close();
|
||||
this.shouldReopen = true;
|
||||
}
|
||||
@@ -161,16 +145,15 @@ export function PopupMixin(options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
closeOverlay(this);
|
||||
this.opened = false;
|
||||
this.removeLock();
|
||||
this.$emit('input', false);
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
|
||||
onTouchMove(event) {
|
||||
this.touchMove(event);
|
||||
const direction = this.deltaY > 0 ? '10' : '01';
|
||||
const el = getScroller(event.target, this.$el);
|
||||
const el = getScroller(event.target, this.$refs.root);
|
||||
const { scrollHeight, offsetHeight, scrollTop } = el;
|
||||
let status = '11';
|
||||
|
||||
@@ -191,29 +174,32 @@ export function PopupMixin(options = {}) {
|
||||
}
|
||||
},
|
||||
|
||||
onClickOverlay() {
|
||||
this.$emit('click-overlay');
|
||||
|
||||
if (this.closeOnClickOverlay) {
|
||||
// TODO
|
||||
// if (this.onClickOverlay) {
|
||||
// this.onClickOverlay();
|
||||
// } else {
|
||||
// this.close();
|
||||
// }
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
|
||||
renderOverlay() {
|
||||
if (this.$isServer || !this.value) {
|
||||
if (this.$isServer || !this.show) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.updateZIndex(this.overlay ? 1 : 0);
|
||||
|
||||
if (this.overlay) {
|
||||
openOverlay(this, {
|
||||
zIndex: context.zIndex++,
|
||||
duration: this.duration,
|
||||
className: this.overlayClass,
|
||||
customStyle: this.overlayStyle,
|
||||
});
|
||||
} else {
|
||||
closeOverlay(this);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateZIndex(value = 0) {
|
||||
this.$el.style.zIndex = ++context.zIndex + value;
|
||||
this.$refs.root.style.zIndex = ++context.zIndex + value;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import Overlay from '../../overlay';
|
||||
import { context } from './context';
|
||||
import { mount } from '../../utils/functional';
|
||||
import { removeNode } from '../../utils/dom/node';
|
||||
|
||||
export type OverlayConfig = {
|
||||
zIndex?: number;
|
||||
className?: string;
|
||||
customStyle?: string | object[] | object;
|
||||
};
|
||||
|
||||
const defaultConfig: OverlayConfig = {
|
||||
className: '',
|
||||
customStyle: {},
|
||||
};
|
||||
|
||||
function mountOverlay(vm: any) {
|
||||
return mount(Overlay, {
|
||||
on: {
|
||||
// close popup when overlay clicked & closeOnClickOverlay is true
|
||||
click() {
|
||||
vm.$emit('click-overlay');
|
||||
|
||||
if (vm.closeOnClickOverlay) {
|
||||
if (vm.onClickOverlay) {
|
||||
vm.onClickOverlay();
|
||||
} else {
|
||||
vm.close();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function updateOverlay(vm: any): void {
|
||||
const item = context.find(vm);
|
||||
|
||||
if (item) {
|
||||
const el = vm.$el;
|
||||
const { config, overlay } = item;
|
||||
|
||||
if (el && el.parentNode) {
|
||||
el.parentNode.insertBefore(overlay.$el, el);
|
||||
}
|
||||
|
||||
Object.assign(overlay, defaultConfig, config, {
|
||||
show: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function openOverlay(vm: any, config: OverlayConfig): void {
|
||||
const item = context.find(vm);
|
||||
if (item) {
|
||||
item.config = config;
|
||||
} else {
|
||||
const overlay = mountOverlay(vm);
|
||||
context.stack.push({ vm, config, overlay });
|
||||
}
|
||||
|
||||
updateOverlay(vm);
|
||||
}
|
||||
|
||||
export function closeOverlay(vm: any): void {
|
||||
const item = context.find(vm);
|
||||
if (item) {
|
||||
item.overlay.show = false;
|
||||
}
|
||||
}
|
||||
|
||||
export function removeOverlay(vm: any) {
|
||||
const item = context.find(vm);
|
||||
if (item) {
|
||||
removeNode(item.overlay.$el);
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
function getElement(selector) {
|
||||
if (typeof selector === 'string') {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
return selector();
|
||||
}
|
||||
|
||||
export function PortalMixin({ ref, afterPortal }) {
|
||||
return {
|
||||
props: {
|
||||
getContainer: [String, Function],
|
||||
},
|
||||
|
||||
watch: {
|
||||
getContainer: 'portal',
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.getContainer) {
|
||||
this.portal();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
portal() {
|
||||
const { getContainer } = this;
|
||||
const el = ref ? this.$refs[ref] : this.$el;
|
||||
|
||||
let container;
|
||||
if (getContainer) {
|
||||
container = getElement(getContainer);
|
||||
} else if (this.$parent) {
|
||||
container = this.$parent.$el;
|
||||
}
|
||||
|
||||
if (container && container !== el.parentNode) {
|
||||
container.appendChild(el);
|
||||
}
|
||||
|
||||
if (afterPortal) {
|
||||
afterPortal.call(this);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
+3
-21
@@ -1,24 +1,16 @@
|
||||
import { sortChildren } from '../utils/vnodes';
|
||||
|
||||
export function ChildrenMixin(parent, options = {}) {
|
||||
const indexKey = options.indexKey || 'index';
|
||||
|
||||
return {
|
||||
inject: {
|
||||
[parent]: {
|
||||
// TODO: disableBindRelation
|
||||
parent: {
|
||||
from: parent,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
parent() {
|
||||
if (this.disableBindRelation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this[parent];
|
||||
},
|
||||
|
||||
[indexKey]() {
|
||||
this.bindRelation();
|
||||
|
||||
@@ -30,14 +22,6 @@ export function ChildrenMixin(parent, options = {}) {
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
disableBindRelation(val) {
|
||||
if (!val) {
|
||||
this.bindRelation();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.bindRelation();
|
||||
},
|
||||
@@ -58,8 +42,6 @@ export function ChildrenMixin(parent, options = {}) {
|
||||
|
||||
const children = [...this.parent.children, this];
|
||||
|
||||
sortChildren(children, this.parent);
|
||||
|
||||
this.parent.children = children;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,6 +22,8 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['click-left', 'click-right'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
height: null,
|
||||
@@ -36,7 +38,7 @@ export default createComponent({
|
||||
|
||||
methods: {
|
||||
genLeft() {
|
||||
const leftSlot = this.slots('left');
|
||||
const leftSlot = this.$slots.left?.();
|
||||
|
||||
if (leftSlot) {
|
||||
return leftSlot;
|
||||
@@ -49,7 +51,7 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genRight() {
|
||||
const rightSlot = this.slots('right');
|
||||
const rightSlot = this.$slots.right?.();
|
||||
|
||||
if (rightSlot) {
|
||||
return rightSlot;
|
||||
@@ -71,7 +73,7 @@ export default createComponent({
|
||||
{this.genLeft()}
|
||||
</div>
|
||||
<div class={[bem('title'), 'van-ellipsis']}>
|
||||
{this.slots('title') || this.title}
|
||||
{this.$slots.title ? this.$slots.title() : this.title}
|
||||
</div>
|
||||
<div class={bem('right')} onClick={this.onClickRight}>
|
||||
{this.genRight()}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Transition } from 'vue';
|
||||
import { createNamespace, isDef, noop } from '../utils';
|
||||
import { preventDefault } from '../utils/dom/event';
|
||||
|
||||
const [createComponent, bem] = createNamespace('overlay');
|
||||
|
||||
function preventTouchMove(event) {
|
||||
preventDefault(event, true);
|
||||
}
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
show: Boolean,
|
||||
zIndex: [Number, String],
|
||||
duration: [Number, String],
|
||||
className: null,
|
||||
customStyle: Object,
|
||||
lockScroll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { slots }) {
|
||||
return function () {
|
||||
const style = {
|
||||
zIndex: props.zIndex,
|
||||
...props.customStyle,
|
||||
};
|
||||
|
||||
if (isDef(props.duration)) {
|
||||
style.animationDuration = `${props.duration}s`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition name="van-fade">
|
||||
<div
|
||||
vShow={props.show}
|
||||
style={style}
|
||||
class={[bem(), props.className]}
|
||||
onTouchmove={props.lockScroll ? preventTouchMove : noop}
|
||||
>
|
||||
{slots.default?.()}
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace, isDef, noop } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
import { preventDefault } from '../utils/dom/event';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type OverlayProps = {
|
||||
show?: boolean;
|
||||
zIndex?: number | string;
|
||||
duration: number | string | null;
|
||||
className?: any;
|
||||
lockScroll?: boolean;
|
||||
customStyle?: object;
|
||||
};
|
||||
|
||||
export type OverlayEvents = {
|
||||
click(event: Event): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('overlay');
|
||||
|
||||
function preventTouchMove(event: TouchEvent) {
|
||||
preventDefault(event, true);
|
||||
}
|
||||
|
||||
function Overlay(
|
||||
h: CreateElement,
|
||||
props: OverlayProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<OverlayProps>
|
||||
) {
|
||||
const style: { [key: string]: any } = {
|
||||
zIndex: props.zIndex,
|
||||
...props.customStyle,
|
||||
};
|
||||
|
||||
if (isDef(props.duration)) {
|
||||
style.animationDuration = `${props.duration}s`;
|
||||
}
|
||||
|
||||
return (
|
||||
<transition name="van-fade">
|
||||
<div
|
||||
vShow={props.show}
|
||||
style={style}
|
||||
class={[bem(), props.className]}
|
||||
onTouchmove={props.lockScroll ? preventTouchMove : noop}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{slots.default?.()}
|
||||
</div>
|
||||
</transition>
|
||||
);
|
||||
}
|
||||
|
||||
Overlay.props = {
|
||||
show: Boolean,
|
||||
zIndex: [Number, String],
|
||||
duration: [Number, String],
|
||||
className: null as any,
|
||||
customStyle: Object,
|
||||
lockScroll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<OverlayProps, OverlayEvents>(Overlay);
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
<demo-block :title="t('title3')">
|
||||
<van-pagination
|
||||
force-ellipses
|
||||
v-model="currentPage3"
|
||||
force-ellipses
|
||||
:total-items="125"
|
||||
:show-page-size="3"
|
||||
:prev-text="t('prevText')"
|
||||
|
||||
+13
-9
@@ -16,7 +16,7 @@ export default createComponent({
|
||||
type: String,
|
||||
default: 'multi',
|
||||
},
|
||||
value: {
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
@@ -38,6 +38,8 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['change', 'update:modelValue'],
|
||||
|
||||
computed: {
|
||||
count() {
|
||||
const count =
|
||||
@@ -62,7 +64,7 @@ export default createComponent({
|
||||
// recompute if showPageSize
|
||||
if (isMaxSized) {
|
||||
// Current page is displayed in the middle of the visible ones
|
||||
startPage = Math.max(this.value - Math.floor(showPageSize / 2), 1);
|
||||
startPage = Math.max(this.modelValue - Math.floor(showPageSize / 2), 1);
|
||||
endPage = startPage + showPageSize - 1;
|
||||
|
||||
// Adjust if limit is exceeded
|
||||
@@ -74,7 +76,7 @@ export default createComponent({
|
||||
|
||||
// Add page number links
|
||||
for (let number = startPage; number <= endPage; number++) {
|
||||
const page = makePage(number, number, number === this.value);
|
||||
const page = makePage(number, number, number === this.modelValue);
|
||||
pages.push(page);
|
||||
}
|
||||
|
||||
@@ -96,9 +98,9 @@ export default createComponent({
|
||||
},
|
||||
|
||||
watch: {
|
||||
value: {
|
||||
modelValue: {
|
||||
handler(page) {
|
||||
this.select(page || this.value);
|
||||
this.select(page);
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
@@ -107,8 +109,8 @@ export default createComponent({
|
||||
methods: {
|
||||
select(page, emitChange) {
|
||||
page = Math.min(this.count, Math.max(1, page));
|
||||
if (this.value !== page) {
|
||||
this.$emit('input', page);
|
||||
if (this.modelValue !== page) {
|
||||
this.$emit('update:modelValue', page);
|
||||
|
||||
if (emitChange) {
|
||||
this.$emit('change', page);
|
||||
@@ -118,7 +120,7 @@ export default createComponent({
|
||||
},
|
||||
|
||||
render() {
|
||||
const { value } = this;
|
||||
const value = this.modelValue;
|
||||
const simple = this.mode !== 'multi';
|
||||
|
||||
const onSelect = (value) => () => {
|
||||
@@ -143,7 +145,9 @@ export default createComponent({
|
||||
))}
|
||||
{simple && (
|
||||
<li class={bem('page-desc')}>
|
||||
{this.slots('pageDesc') || `${value}/${this.count}`}
|
||||
{this.$slots.pageDesc
|
||||
? this.$slots.pageDesc()
|
||||
: `${value}/${this.count}`}
|
||||
</li>
|
||||
)}
|
||||
<li
|
||||
|
||||
+10
-10
@@ -15,7 +15,7 @@ Vue.use(Popup);
|
||||
|
||||
```html
|
||||
<van-cell is-link @click="showPopup">Show Popup</van-cell>
|
||||
<van-popup v-model="show">Content</van-popup>
|
||||
<van-popup v-model:show="show">Content</van-popup>
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -39,21 +39,21 @@ export default {
|
||||
Use `position` prop to set popup display position
|
||||
|
||||
```html
|
||||
<van-popup v-model="show" position="top" :style="{ height: '30%' }" />
|
||||
<van-popup v-model:show="show" position="top" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### Close Icon
|
||||
|
||||
```html
|
||||
<van-popup
|
||||
v-model="show"
|
||||
v-model:show="show"
|
||||
closeable
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<!-- Custom Icon -->
|
||||
<van-popup
|
||||
v-model="show"
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon="close"
|
||||
position="bottom"
|
||||
@@ -61,7 +61,7 @@ Use `position` prop to set popup display position
|
||||
/>
|
||||
<!-- Icon Position -->
|
||||
<van-popup
|
||||
v-model="show"
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon-position="top-left"
|
||||
position="bottom"
|
||||
@@ -72,7 +72,7 @@ Use `position` prop to set popup display position
|
||||
### Round Corner
|
||||
|
||||
```html
|
||||
<van-popup v-model="show" round position="bottom" :style="{ height: '30%' }" />
|
||||
<van-popup v-model:show="show" round position="bottom" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### Get Container
|
||||
@@ -81,13 +81,13 @@ Use `get-container` prop to specify mount location
|
||||
|
||||
```html
|
||||
<!-- mount to body -->
|
||||
<van-popup v-model="show" get-container="body" />
|
||||
<van-popup v-model:show="show" get-container="body" />
|
||||
|
||||
<!-- mount to #app -->
|
||||
<van-popup v-model="show" get-container="#app" />
|
||||
<van-popup v-model:show="show" get-container="#app" />
|
||||
|
||||
<!-- Specify the mount location by function -->
|
||||
<van-popup v-model="show" :get-container="getContainer" />
|
||||
<van-popup v-model:show="show" :get-container="getContainer" />
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model (value) | Whether to show popup | _boolean_ | `false` |
|
||||
| v-model:show | Whether to show popup | _boolean_ | `false` |
|
||||
| overlay | Whether to show overlay | _boolean_ | `true` |
|
||||
| position | Can be set to `top` `bottom` `right` `left` | _string_ | `center` |
|
||||
| overlay-class | Custom overlay class | _string_ | - |
|
||||
|
||||
+11
-11
@@ -17,11 +17,11 @@ Vue.use(Popup);
|
||||
|
||||
### 基础用法
|
||||
|
||||
通过`v-model`控制弹出层是否展示
|
||||
通过 `v-model:show` 控制弹出层是否展示
|
||||
|
||||
```html
|
||||
<van-cell is-link @click="showPopup">展示弹出层</van-cell>
|
||||
<van-popup v-model="show">内容</van-popup>
|
||||
<van-popup v-model:show="show">内容</van-popup>
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
通过`position`属性设置弹出位置,默认居中弹出,可以设置为`top`、`bottom`、`left`、`right`
|
||||
|
||||
```html
|
||||
<van-popup v-model="show" position="top" :style="{ height: '30%' }" />
|
||||
<van-popup v-model:show="show" position="top" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### 关闭图标
|
||||
@@ -54,14 +54,14 @@ export default {
|
||||
|
||||
```html
|
||||
<van-popup
|
||||
v-model="show"
|
||||
v-model:show="show"
|
||||
closeable
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<!-- 自定义图标 -->
|
||||
<van-popup
|
||||
v-model="show"
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon="close"
|
||||
position="bottom"
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
/>
|
||||
<!-- 图标位置 -->
|
||||
<van-popup
|
||||
v-model="show"
|
||||
v-model:show="show"
|
||||
closeable
|
||||
close-icon-position="top-left"
|
||||
position="bottom"
|
||||
@@ -82,7 +82,7 @@ export default {
|
||||
设置`round`属性后,弹窗会根据弹出位置添加不同的圆角样式
|
||||
|
||||
```html
|
||||
<van-popup v-model="show" round position="bottom" :style="{ height: '30%' }" />
|
||||
<van-popup v-model:show="show" round position="bottom" :style="{ height: '30%' }" />
|
||||
```
|
||||
|
||||
### 指定挂载位置
|
||||
@@ -91,13 +91,13 @@ export default {
|
||||
|
||||
```html
|
||||
<!-- 挂载到 body 节点下 -->
|
||||
<van-popup v-model="show" get-container="body" />
|
||||
<van-popup v-model:show="show" get-container="body" />
|
||||
|
||||
<!-- 挂载到 #app 节点下 -->
|
||||
<van-popup v-model="show" get-container="#app" />
|
||||
<van-popup v-model:show="show" get-container="#app" />
|
||||
|
||||
<!-- 通过函数指定挂载位置 -->
|
||||
<van-popup v-model="show" :get-container="getContainer" />
|
||||
<van-popup v-model:show="show" :get-container="getContainer" />
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -119,7 +119,7 @@ export default {
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model (value) | 是否显示弹出层 | _boolean_ | `false` |
|
||||
| v-model:show | 是否显示弹出层 | _boolean_ | `false` |
|
||||
| overlay | 是否显示遮罩层 | _boolean_ | `true` |
|
||||
| position | 弹出位置,可选值为 `top` `bottom` `right` `left` | _string_ | `center` |
|
||||
| overlay-class | 自定义遮罩层类名 | _string_ | - |
|
||||
|
||||
+14
-10
@@ -2,7 +2,7 @@
|
||||
<demo-section>
|
||||
<demo-block card :title="t('basicUsage')">
|
||||
<van-cell :title="t('buttonBasic')" is-link @click="showBasic = true" />
|
||||
<van-popup v-model="showBasic" :style="{ padding: '30px 50px' }">
|
||||
<van-popup v-model:show="showBasic" :style="{ padding: '30px 50px' }">
|
||||
{{ t('content') }}
|
||||
</van-popup>
|
||||
</demo-block>
|
||||
@@ -13,19 +13,23 @@
|
||||
<van-cell :title="t('buttonLeft')" is-link @click="showLeft = true" />
|
||||
<van-cell :title="t('buttonRight')" is-link @click="showRight = true" />
|
||||
|
||||
<van-popup v-model="showTop" position="top" :style="{ height: '30%' }" />
|
||||
<van-popup
|
||||
v-model="showBottom"
|
||||
v-model:show="showTop"
|
||||
position="top"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model:show="showBottom"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model="showLeft"
|
||||
v-model:show="showLeft"
|
||||
position="left"
|
||||
:style="{ width: '30%', height: '100%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model="showRight"
|
||||
v-model:show="showRight"
|
||||
position="right"
|
||||
:style="{ width: '30%', height: '100%' }"
|
||||
/>
|
||||
@@ -45,20 +49,20 @@
|
||||
/>
|
||||
|
||||
<van-popup
|
||||
v-model="showCloseIcon"
|
||||
v-model:show="showCloseIcon"
|
||||
closeable
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model="showCustomCloseIcon"
|
||||
v-model:show="showCustomCloseIcon"
|
||||
closeable
|
||||
close-icon="close"
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
/>
|
||||
<van-popup
|
||||
v-model="showCustomIconPosition"
|
||||
v-model:show="showCustomIconPosition"
|
||||
closeable
|
||||
close-icon-position="top-left"
|
||||
position="bottom"
|
||||
@@ -73,7 +77,7 @@
|
||||
@click="showRoundCorner = true"
|
||||
/>
|
||||
<van-popup
|
||||
v-model="showRoundCorner"
|
||||
v-model:show="showRoundCorner"
|
||||
round
|
||||
position="bottom"
|
||||
:style="{ height: '30%' }"
|
||||
@@ -87,7 +91,7 @@
|
||||
@click="showGetContainer = true"
|
||||
/>
|
||||
<van-popup
|
||||
v-model="showGetContainer"
|
||||
v-model:show="showGetContainer"
|
||||
get-container="body"
|
||||
:style="{ padding: '30px 50px' }"
|
||||
/>
|
||||
|
||||
+85
-43
@@ -1,12 +1,16 @@
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { Teleport, Transition } from 'vue';
|
||||
import { createNamespace, isDef, isFunction } from '../utils';
|
||||
import { PopupMixin } from '../mixins/popup';
|
||||
import Icon from '../icon';
|
||||
import Overlay from '../overlay';
|
||||
|
||||
const [createComponent, bem] = createNamespace('popup');
|
||||
|
||||
export default createComponent({
|
||||
mixins: [PopupMixin()],
|
||||
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
round: Boolean,
|
||||
duration: [Number, String],
|
||||
@@ -35,6 +39,16 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: [
|
||||
'open',
|
||||
'close',
|
||||
'click',
|
||||
'opened',
|
||||
'closed',
|
||||
'update:show',
|
||||
'click-overlay',
|
||||
],
|
||||
|
||||
beforeCreate() {
|
||||
const createEmitter = (eventName) => (event) =>
|
||||
this.$emit(eventName, event);
|
||||
@@ -44,52 +58,80 @@ export default createComponent({
|
||||
this.onClosed = createEmitter('closed');
|
||||
},
|
||||
|
||||
methods: {
|
||||
genOverlay() {
|
||||
if (this.overlay) {
|
||||
return <Overlay show={this.show} onClick={this.onClickOverlay} />;
|
||||
}
|
||||
},
|
||||
|
||||
genPopup() {
|
||||
const { round, position, duration } = this;
|
||||
const isCenter = position === 'center';
|
||||
|
||||
const transitionName =
|
||||
this.transition ||
|
||||
(isCenter ? 'van-fade' : `van-popup-slide-${position}`);
|
||||
|
||||
const style = {};
|
||||
if (isDef(duration)) {
|
||||
const key = isCenter ? 'animationDuration' : 'transitionDuration';
|
||||
style[key] = `${duration}s`;
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
name={transitionName}
|
||||
onAfterEnter={this.onOpened}
|
||||
onAfterLeave={this.onClosed}
|
||||
>
|
||||
{this.shouldRender ? (
|
||||
<div
|
||||
vShow={this.show}
|
||||
ref="root"
|
||||
style={style}
|
||||
class={bem({
|
||||
round,
|
||||
[position]: position,
|
||||
'safe-area-inset-bottom': this.safeAreaInsetBottom,
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
{...this.$attrs}
|
||||
>
|
||||
{this.$slots.default?.()}
|
||||
{this.closeable && (
|
||||
<Icon
|
||||
role="button"
|
||||
tabindex="0"
|
||||
name={this.closeIcon}
|
||||
class={bem('close-icon', this.closeIconPosition)}
|
||||
onClick={this.close}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</Transition>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
if (!this.shouldRender) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { round, position, duration } = this;
|
||||
const isCenter = position === 'center';
|
||||
|
||||
const transitionName =
|
||||
this.transition ||
|
||||
(isCenter ? 'van-fade' : `van-popup-slide-${position}`);
|
||||
|
||||
const style = {};
|
||||
if (isDef(duration)) {
|
||||
const key = isCenter ? 'animationDuration' : 'transitionDuration';
|
||||
style[key] = `${duration}s`;
|
||||
const { getContainer } = this;
|
||||
if (getContainer) {
|
||||
const to = isFunction(getContainer) ? getContainer() : getContainer;
|
||||
return (
|
||||
<Teleport to={to}>
|
||||
{this.genOverlay()}
|
||||
{this.genPopup()}
|
||||
</Teleport>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<transition
|
||||
name={transitionName}
|
||||
onAfterEnter={this.onOpened}
|
||||
onAfterLeave={this.onClosed}
|
||||
>
|
||||
<div
|
||||
vShow={this.value}
|
||||
style={style}
|
||||
class={bem({
|
||||
round,
|
||||
[position]: position,
|
||||
'safe-area-inset-bottom': this.safeAreaInsetBottom,
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
{this.slots()}
|
||||
{this.closeable && (
|
||||
<Icon
|
||||
role="button"
|
||||
tabindex="0"
|
||||
name={this.closeIcon}
|
||||
class={bem('close-icon', this.closeIconPosition)}
|
||||
onClick={this.close}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</transition>
|
||||
<>
|
||||
{this.genOverlay()}
|
||||
{this.genPopup()}
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -82,22 +82,22 @@
|
||||
transition-timing-function: ease-in;
|
||||
}
|
||||
|
||||
&-slide-top-enter,
|
||||
&-slide-top-enter-from,
|
||||
&-slide-top-leave-active {
|
||||
transform: translate3d(0, -100%, 0);
|
||||
}
|
||||
|
||||
&-slide-right-enter,
|
||||
&-slide-right-enter-from,
|
||||
&-slide-right-leave-active {
|
||||
transform: translate3d(100%, -50%, 0);
|
||||
}
|
||||
|
||||
&-slide-bottom-enter,
|
||||
&-slide-bottom-enter-from,
|
||||
&-slide-bottom-leave-active {
|
||||
transform: translate3d(0, 100%, 0);
|
||||
}
|
||||
|
||||
&-slide-left-enter,
|
||||
&-slide-left-enter-from,
|
||||
&-slide-left-leave-active {
|
||||
transform: translate3d(-100%, -50%, 0);
|
||||
}
|
||||
|
||||
+12
-9
@@ -36,7 +36,7 @@ export default createComponent({
|
||||
voidColor: String,
|
||||
iconPrefix: String,
|
||||
disabledColor: String,
|
||||
value: {
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
@@ -58,11 +58,15 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.itemRefs = [];
|
||||
},
|
||||
|
||||
computed: {
|
||||
list() {
|
||||
const list = [];
|
||||
for (let i = 1; i <= this.count; i++) {
|
||||
list.push(getRateStatus(this.value, i, this.allowHalf));
|
||||
list.push(getRateStatus(this.modelValue, i, this.allowHalf));
|
||||
}
|
||||
|
||||
return list;
|
||||
@@ -83,8 +87,8 @@ export default createComponent({
|
||||
|
||||
methods: {
|
||||
select(index) {
|
||||
if (!this.disabled && !this.readonly && index !== this.value) {
|
||||
this.$emit('input', index);
|
||||
if (!this.disabled && !this.readonly && index !== this.modelValue) {
|
||||
this.$emit('update:modelValue', index);
|
||||
this.$emit('change', index);
|
||||
}
|
||||
},
|
||||
@@ -96,9 +100,7 @@ export default createComponent({
|
||||
|
||||
this.touchStart(event);
|
||||
|
||||
const rects = this.$refs.items.map((item) =>
|
||||
item.getBoundingClientRect()
|
||||
);
|
||||
const rects = this.itemRefs.map((item) => item.getBoundingClientRect());
|
||||
const ranges = [];
|
||||
|
||||
rects.forEach((rect, index) => {
|
||||
@@ -161,9 +163,10 @@ export default createComponent({
|
||||
|
||||
return (
|
||||
<div
|
||||
ref="items"
|
||||
refInFor
|
||||
key={index}
|
||||
ref={(val) => {
|
||||
this.itemRefs[index] = val;
|
||||
}}
|
||||
role="radio"
|
||||
style={style}
|
||||
tabindex="0"
|
||||
|
||||
+3
-1
@@ -20,6 +20,8 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['click'],
|
||||
|
||||
computed: {
|
||||
spaces() {
|
||||
const gutter = Number(this.gutter);
|
||||
@@ -80,7 +82,7 @@ export default createComponent({
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
{this.slots()}
|
||||
{this.$slots.default?.()}
|
||||
</this.tag>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { createNamespace } from '../utils';
|
||||
import { ChildrenMixin } from '../mixins/relation';
|
||||
import { route, routeProps } from '../utils/router';
|
||||
import Info from '../info';
|
||||
@@ -8,10 +8,11 @@ const [createComponent, bem] = createNamespace('sidebar-item');
|
||||
export default createComponent({
|
||||
mixins: [ChildrenMixin('vanSidebar')],
|
||||
|
||||
emits: ['click'],
|
||||
|
||||
props: {
|
||||
...routeProps,
|
||||
dot: Boolean,
|
||||
info: [Number, String],
|
||||
badge: [Number, String],
|
||||
title: String,
|
||||
disabled: Boolean,
|
||||
@@ -19,7 +20,7 @@ export default createComponent({
|
||||
|
||||
computed: {
|
||||
select() {
|
||||
return this.index === +this.parent.activeKey;
|
||||
return this.index === +this.parent.modelValue;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,7 +31,7 @@ export default createComponent({
|
||||
}
|
||||
|
||||
this.$emit('click', this.index);
|
||||
this.parent.$emit('input', this.index);
|
||||
this.parent.$emit('update:modelValue', this.index);
|
||||
this.parent.setIndex(this.index);
|
||||
route(this.$router, this);
|
||||
},
|
||||
@@ -44,11 +45,7 @@ export default createComponent({
|
||||
>
|
||||
<div class={bem('text')}>
|
||||
{this.title}
|
||||
<Info
|
||||
dot={this.dot}
|
||||
info={isDef(this.badge) ? this.badge : this.info}
|
||||
class={bem('info')}
|
||||
/>
|
||||
<Info dot={this.dot} info={this.badge} class={bem('info')} />
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -6,12 +6,10 @@ const [createComponent, bem] = createNamespace('sidebar');
|
||||
export default createComponent({
|
||||
mixins: [ParentMixin('vanSidebar')],
|
||||
|
||||
model: {
|
||||
prop: 'activeKey',
|
||||
},
|
||||
emits: ['change', 'update:modelValue'],
|
||||
|
||||
props: {
|
||||
activeKey: {
|
||||
modelValue: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
@@ -19,13 +17,13 @@ export default createComponent({
|
||||
|
||||
data() {
|
||||
return {
|
||||
index: +this.activeKey,
|
||||
index: +this.modelValue,
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
activeKey() {
|
||||
this.setIndex(+this.activeKey);
|
||||
modelValue() {
|
||||
this.setIndex(+this.modelValue);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -39,6 +37,6 @@ export default createComponent({
|
||||
},
|
||||
|
||||
render() {
|
||||
return <div class={bem()}>{this.slots()}</div>;
|
||||
return <div class={bem()}>{this.$slots.default?.()}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { createNamespace, addUnit } from '../utils';
|
||||
|
||||
const [createComponent, bem] = createNamespace('skeleton');
|
||||
const DEFAULT_ROW_WIDTH = '100%';
|
||||
const DEFAULT_LAST_ROW_WIDTH = '60%';
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
title: Boolean,
|
||||
round: Boolean,
|
||||
avatar: Boolean,
|
||||
row: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
animate: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
avatarSize: {
|
||||
type: String,
|
||||
default: '32px',
|
||||
},
|
||||
avatarShape: {
|
||||
type: String,
|
||||
default: 'round',
|
||||
},
|
||||
titleWidth: {
|
||||
type: [Number, String],
|
||||
default: '40%',
|
||||
},
|
||||
rowWidth: {
|
||||
type: [Number, String, Array],
|
||||
default: DEFAULT_ROW_WIDTH,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { slots }) {
|
||||
return function () {
|
||||
if (!props.loading) {
|
||||
return slots.default && slots.default();
|
||||
}
|
||||
|
||||
function Title() {
|
||||
if (props.title) {
|
||||
return (
|
||||
<h3
|
||||
class={bem('title')}
|
||||
style={{ width: addUnit(props.titleWidth) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Rows() {
|
||||
const Rows = [];
|
||||
const { rowWidth } = props;
|
||||
|
||||
function getRowWidth(index) {
|
||||
if (rowWidth === DEFAULT_ROW_WIDTH && index === +props.row - 1) {
|
||||
return DEFAULT_LAST_ROW_WIDTH;
|
||||
}
|
||||
|
||||
if (Array.isArray(rowWidth)) {
|
||||
return rowWidth[index];
|
||||
}
|
||||
|
||||
return rowWidth;
|
||||
}
|
||||
|
||||
for (let i = 0; i < props.row; i++) {
|
||||
Rows.push(
|
||||
<div
|
||||
class={bem('row')}
|
||||
style={{ width: addUnit(getRowWidth(i)) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return Rows;
|
||||
}
|
||||
|
||||
function Avatar() {
|
||||
if (props.avatar) {
|
||||
const size = addUnit(props.avatarSize);
|
||||
return (
|
||||
<div
|
||||
class={bem('avatar', props.avatarShape)}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem({ animate: props.animate, round: props.round })}>
|
||||
{Avatar()}
|
||||
<div class={bem('content')}>
|
||||
{Title()}
|
||||
{Rows()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,129 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace, addUnit } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type SkeletonProps = {
|
||||
row: number | string;
|
||||
title?: boolean;
|
||||
round?: boolean;
|
||||
avatar?: boolean;
|
||||
loading: boolean;
|
||||
animate: boolean;
|
||||
avatarSize: string;
|
||||
avatarShape: 'square' | 'round';
|
||||
titleWidth: number | string;
|
||||
rowWidth: number | string | (number | string)[];
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('skeleton');
|
||||
const DEFAULT_ROW_WIDTH = '100%';
|
||||
const DEFAULT_LAST_ROW_WIDTH = '60%';
|
||||
|
||||
function Skeleton(
|
||||
h: CreateElement,
|
||||
props: SkeletonProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext
|
||||
) {
|
||||
if (!props.loading) {
|
||||
return slots.default && slots.default();
|
||||
}
|
||||
|
||||
function Title() {
|
||||
if (props.title) {
|
||||
return (
|
||||
<h3 class={bem('title')} style={{ width: addUnit(props.titleWidth) }} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Rows() {
|
||||
const Rows = [];
|
||||
const { rowWidth } = props;
|
||||
|
||||
function getRowWidth(index: number) {
|
||||
if (rowWidth === DEFAULT_ROW_WIDTH && index === +props.row - 1) {
|
||||
return DEFAULT_LAST_ROW_WIDTH;
|
||||
}
|
||||
|
||||
if (Array.isArray(rowWidth)) {
|
||||
return rowWidth[index];
|
||||
}
|
||||
|
||||
return rowWidth;
|
||||
}
|
||||
|
||||
for (let i = 0; i < props.row; i++) {
|
||||
Rows.push(
|
||||
<div class={bem('row')} style={{ width: addUnit(getRowWidth(i)) }} />
|
||||
);
|
||||
}
|
||||
|
||||
return Rows;
|
||||
}
|
||||
|
||||
function Avatar() {
|
||||
if (props.avatar) {
|
||||
const size = addUnit(props.avatarSize);
|
||||
return (
|
||||
<div
|
||||
class={bem('avatar', props.avatarShape)}
|
||||
style={{ width: size, height: size }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={bem({ animate: props.animate, round: props.round })}
|
||||
{...inherit(ctx)}
|
||||
>
|
||||
{Avatar()}
|
||||
<div class={bem('content')}>
|
||||
{Title()}
|
||||
{Rows()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Skeleton.props = {
|
||||
title: Boolean,
|
||||
round: Boolean,
|
||||
avatar: Boolean,
|
||||
row: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
animate: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
avatarSize: {
|
||||
type: String,
|
||||
default: '32px',
|
||||
},
|
||||
avatarShape: {
|
||||
type: String,
|
||||
default: 'round',
|
||||
},
|
||||
titleWidth: {
|
||||
type: [Number, String],
|
||||
default: '40%',
|
||||
},
|
||||
rowWidth: {
|
||||
type: [Number, String, Array],
|
||||
default: DEFAULT_ROW_WIDTH,
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<SkeletonProps>(Skeleton);
|
||||
+13
-9
@@ -27,12 +27,14 @@ export default createComponent({
|
||||
type: [Number, String],
|
||||
default: 1,
|
||||
},
|
||||
value: {
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['change', 'drag-end', 'drag-start', 'update:modelValue'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
dragStatus: '',
|
||||
@@ -57,7 +59,7 @@ export default createComponent({
|
||||
|
||||
created() {
|
||||
// format initial value
|
||||
this.updateValue(this.value);
|
||||
this.updateValue(this.modelValue);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
@@ -71,7 +73,7 @@ export default createComponent({
|
||||
}
|
||||
|
||||
this.touchStart(event);
|
||||
this.startValue = this.format(this.value);
|
||||
this.startValue = this.format(this.modelValue);
|
||||
this.dragStatus = 'start';
|
||||
},
|
||||
|
||||
@@ -122,15 +124,15 @@ export default createComponent({
|
||||
const total = this.vertical ? rect.height : rect.width;
|
||||
const value = +this.min + (delta / total) * this.range;
|
||||
|
||||
this.startValue = this.value;
|
||||
this.startValue = this.modelValue;
|
||||
this.updateValue(value, true);
|
||||
},
|
||||
|
||||
updateValue(value, end) {
|
||||
value = this.format(value);
|
||||
|
||||
if (value !== this.value) {
|
||||
this.$emit('input', value);
|
||||
if (value !== this.modelValue) {
|
||||
this.$emit('update:modelValue', value);
|
||||
}
|
||||
|
||||
if (end && value !== this.startValue) {
|
||||
@@ -157,7 +159,7 @@ export default createComponent({
|
||||
};
|
||||
|
||||
const barStyle = {
|
||||
[mainAxis]: `${((this.value - this.min) * 100) / this.range}%`,
|
||||
[mainAxis]: `${((this.modelValue - this.min) * 100) / this.range}%`,
|
||||
background: this.activeColor,
|
||||
};
|
||||
|
||||
@@ -177,12 +179,14 @@ export default createComponent({
|
||||
role="slider"
|
||||
tabindex={this.disabled ? -1 : 0}
|
||||
aria-valuemin={this.min}
|
||||
aria-valuenow={this.value}
|
||||
aria-valuenow={this.modelValue}
|
||||
aria-valuemax={this.max}
|
||||
aria-orientation={this.vertical ? 'vertical' : 'horizontal'}
|
||||
class={bem('button-wrapper')}
|
||||
>
|
||||
{this.slots('button') || (
|
||||
{this.$slots.button ? (
|
||||
this.$slots.button()
|
||||
) : (
|
||||
<div class={bem('button')} style={this.buttonStyle} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
+14
-14
@@ -44,22 +44,22 @@ export default createComponent({
|
||||
const { activeIcon, activeColor, inactiveIcon } = this.parent;
|
||||
|
||||
if (this.active) {
|
||||
return (
|
||||
this.slots('active-icon') || (
|
||||
<Icon
|
||||
class={bem('icon', 'active')}
|
||||
name={activeIcon}
|
||||
color={activeColor}
|
||||
/>
|
||||
)
|
||||
return this.$slots['active-icon'] ? (
|
||||
this.$slots['active-icon']()
|
||||
) : (
|
||||
<Icon
|
||||
class={bem('icon', 'active')}
|
||||
name={activeIcon}
|
||||
color={activeColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const inactiveIconSlot = this.slots('inactive-icon');
|
||||
|
||||
if (inactiveIcon || inactiveIconSlot) {
|
||||
return (
|
||||
inactiveIconSlot || <Icon class={bem('icon')} name={inactiveIcon} />
|
||||
if (inactiveIcon || this.$slots['inactive-icon']) {
|
||||
return this.$slots['inactive-icon'] ? (
|
||||
this.$slots['inactive-icon']()
|
||||
) : (
|
||||
<Icon class={bem('icon')} name={inactiveIcon} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export default createComponent({
|
||||
style={this.titleStyle}
|
||||
onClick={this.onClickStep}
|
||||
>
|
||||
{this.slots()}
|
||||
{this.$slots.default?.()}
|
||||
</div>
|
||||
<div class={bem('circle-container')} onClick={this.onClickStep}>
|
||||
{this.genCircle()}
|
||||
|
||||
+3
-1
@@ -24,10 +24,12 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['click-step'],
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class={bem([this.direction])}>
|
||||
<div class={bem('items')}>{this.slots()}</div>
|
||||
<div class={bem('items')}>{this.$slots.default?.()}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -195,7 +195,7 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genLeftPart() {
|
||||
const content = this.slots('left');
|
||||
const content = this.$slots.left?.();
|
||||
|
||||
if (content) {
|
||||
return (
|
||||
@@ -211,7 +211,7 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genRightPart() {
|
||||
const content = this.slots('right');
|
||||
const content = this.$slots.right?.();
|
||||
|
||||
if (content) {
|
||||
return (
|
||||
@@ -237,7 +237,7 @@ export default createComponent({
|
||||
<div class={bem()} onClick={this.getClickHandler('cell')}>
|
||||
<div class={bem('wrapper')} style={wrapperStyle}>
|
||||
{this.genLeftPart()}
|
||||
{this.slots()}
|
||||
{this.$slots.default?.()}
|
||||
{this.genRightPart()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
### Async Control
|
||||
|
||||
```html
|
||||
<van-switch :value="checked" @input="onInput" />
|
||||
<van-switch :model-value="checked" @update:model:value="onUpdateValue" />
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onInput(checked) {
|
||||
onUpdateValue(checked) {
|
||||
Dialog.confirm({
|
||||
title: 'Confirm',
|
||||
message: 'Are you sure to toggle switch?',
|
||||
|
||||
@@ -63,10 +63,10 @@ export default {
|
||||
|
||||
### 异步控制
|
||||
|
||||
需要异步控制开关时,可以使用`value`属性和`input`事件代替`v-model`,并在`input`事件回调函数中手动处理开关状态
|
||||
需要异步控制开关时,可以使用 `modelValue` 属性和 `update:model-value` 事件代替 `v-model`,并在事件回调函数中手动处理开关状态。
|
||||
|
||||
```html
|
||||
<van-switch :value="checked" @input="onInput" />
|
||||
<van-switch :model-value="checked" @update:model:value="onUpdateValue" />
|
||||
```
|
||||
|
||||
```js
|
||||
@@ -77,7 +77,7 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onInput(checked) {
|
||||
onUpdateValue(checked) {
|
||||
Dialog.confirm({
|
||||
title: '提醒',
|
||||
message: '是否切换开关?',
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('asyncControl')">
|
||||
<van-switch :value="checked4" @input="onInput" />
|
||||
<van-switch :model-value="checked4" @update:model-value="onInput" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('withCell')">
|
||||
<van-cell center :title="t('title')">
|
||||
<van-switch v-model="checked5" slot="right-icon" size="24" />
|
||||
<template #right-icon>
|
||||
<van-switch v-model="checked5" size="24" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
|
||||
+4
-2
@@ -15,9 +15,11 @@ export default createComponent({
|
||||
|
||||
props: switchProps,
|
||||
|
||||
emits: ['click', 'change', 'update:modelValue'],
|
||||
|
||||
computed: {
|
||||
checked() {
|
||||
return this.value === this.activeValue;
|
||||
return this.modelValue === this.activeValue;
|
||||
},
|
||||
|
||||
style() {
|
||||
@@ -34,7 +36,7 @@ export default createComponent({
|
||||
|
||||
if (!this.disabled && !this.loading) {
|
||||
const newValue = this.checked ? this.inactiveValue : this.activeValue;
|
||||
this.$emit('input', newValue);
|
||||
this.$emit('update:modelValue', newValue);
|
||||
this.$emit('change', newValue);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
export type SharedSwitchProps = {
|
||||
size?: string | number;
|
||||
value?: any;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
modelValue?: any;
|
||||
activeValue: any;
|
||||
inactiveValue: any;
|
||||
activeColor?: string;
|
||||
@@ -15,9 +15,9 @@ export type SharedSwitchProps = {
|
||||
|
||||
export const switchProps = {
|
||||
size: [Number, String],
|
||||
value: null as any,
|
||||
loading: Boolean,
|
||||
disabled: Boolean,
|
||||
modelValue: null as any,
|
||||
activeColor: String,
|
||||
inactiveColor: String,
|
||||
activeValue: {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { BORDER_SURROUND } from '../utils/constant';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
|
||||
const [createComponent, bem] = createNamespace('tag');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
size: String,
|
||||
mark: Boolean,
|
||||
color: String,
|
||||
plain: Boolean,
|
||||
round: Boolean,
|
||||
textColor: String,
|
||||
closeable: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['close'],
|
||||
|
||||
setup(props, { slots, emit }) {
|
||||
return function () {
|
||||
const { type, mark, plain, color, round, size } = props;
|
||||
|
||||
const key = plain ? 'color' : 'backgroundColor';
|
||||
const style = { [key]: color };
|
||||
|
||||
if (props.textColor) {
|
||||
style.color = props.textColor;
|
||||
}
|
||||
|
||||
const classes = { mark, plain, round };
|
||||
if (size) {
|
||||
classes[size] = size;
|
||||
}
|
||||
|
||||
const CloseIcon = props.closeable && (
|
||||
<Icon
|
||||
name="cross"
|
||||
class={bem('close')}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
emit('close');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<transition name={props.closeable ? 'van-fade' : null}>
|
||||
<span
|
||||
key="content"
|
||||
style={style}
|
||||
class={[bem([classes, type]), { [BORDER_SURROUND]: plain }]}
|
||||
>
|
||||
{slots.default?.()}
|
||||
{CloseIcon}
|
||||
</span>
|
||||
</transition>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,90 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace } from '../utils';
|
||||
import { inherit, emit } from '../utils/functional';
|
||||
import { BORDER_SURROUND } from '../utils/constant';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type TagType = 'default' | 'primary' | 'success' | 'danger';
|
||||
|
||||
export type TagSize = 'large' | 'medium';
|
||||
|
||||
export type TagProps = {
|
||||
type: TagType;
|
||||
size?: TagSize;
|
||||
mark?: boolean;
|
||||
color?: string;
|
||||
plain?: boolean;
|
||||
round?: boolean;
|
||||
textColor?: string;
|
||||
closeable?: boolean;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('tag');
|
||||
|
||||
function Tag(
|
||||
h: CreateElement,
|
||||
props: TagProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<TagProps>
|
||||
) {
|
||||
const { type, mark, plain, color, round, size } = props;
|
||||
|
||||
const key = plain ? 'color' : 'backgroundColor';
|
||||
const style = { [key]: color };
|
||||
|
||||
if (props.textColor) {
|
||||
style.color = props.textColor;
|
||||
}
|
||||
|
||||
const classes: { [key: string]: any } = { mark, plain, round };
|
||||
if (size) {
|
||||
classes[size] = size;
|
||||
}
|
||||
|
||||
const CloseIcon = props.closeable && (
|
||||
<Icon
|
||||
name="cross"
|
||||
class={bem('close')}
|
||||
onClick={(event: PointerEvent) => {
|
||||
event.stopPropagation();
|
||||
emit(ctx, 'close');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<transition name={props.closeable ? 'van-fade' : null}>
|
||||
<span
|
||||
key="content"
|
||||
style={style}
|
||||
class={[bem([classes, type]), { [BORDER_SURROUND]: plain }]}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{slots.default?.()}
|
||||
{CloseIcon}
|
||||
</span>
|
||||
</transition>
|
||||
);
|
||||
}
|
||||
|
||||
Tag.props = {
|
||||
size: String,
|
||||
mark: Boolean,
|
||||
color: String,
|
||||
plain: Boolean,
|
||||
round: Boolean,
|
||||
textColor: String,
|
||||
closeable: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<TagProps>(Tag);
|
||||
@@ -15,9 +15,9 @@ Vue.use(TreeSelect);
|
||||
|
||||
```html
|
||||
<van-tree-select
|
||||
v-model:active-id="activeId"
|
||||
v-model:main-active-index="activeIndex"
|
||||
:items="items"
|
||||
:active-id.sync="activeId"
|
||||
:main-active-index.sync="activeIndex"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -37,9 +37,9 @@ export default {
|
||||
|
||||
```html
|
||||
<van-tree-select
|
||||
v-model:active-id="activeIds"
|
||||
v-model:main-active-index="activeIndex"
|
||||
:items="items"
|
||||
:active-id.sync="activeIds"
|
||||
:main-active-index.sync="activeIndex"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
### Custom Content
|
||||
|
||||
```html
|
||||
<van-tree-select height="55vw" :items="items" :main-active-index.sync="active">
|
||||
<van-tree-select v-model:main-active-index="active" height="55vw" :items="items">
|
||||
<template #content>
|
||||
<van-image
|
||||
v-if="active === 0"
|
||||
@@ -87,9 +87,9 @@ export default {
|
||||
|
||||
```html
|
||||
<van-tree-select
|
||||
v-model:main-active-index="activeIndex"
|
||||
height="55vw"
|
||||
:items="items"
|
||||
:main-active-index.sync="activeIndex"
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ Vue.use(TreeSelect);
|
||||
|
||||
```html
|
||||
<van-tree-select
|
||||
v-model:active-id="activeId"
|
||||
v-model:main-active-index="activeIndex"
|
||||
:items="items"
|
||||
:active-id.sync="activeId"
|
||||
:main-active-index.sync="activeIndex"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -41,9 +41,9 @@ export default {
|
||||
|
||||
```html
|
||||
<van-tree-select
|
||||
v-model:active-id="activeIds"
|
||||
v-model:main-active-index="activeIndex"
|
||||
:items="items"
|
||||
:active-id.sync="activeIds"
|
||||
:main-active-index.sync="activeIndex"
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -64,7 +64,11 @@ export default {
|
||||
通过`content`插槽可以自定义右侧区域的内容
|
||||
|
||||
```html
|
||||
<van-tree-select height="55vw" :items="items" :main-active-index.sync="active">
|
||||
<van-tree-select
|
||||
v-model:main-active-index="active"
|
||||
height="55vw"
|
||||
:items="items"
|
||||
>
|
||||
<template #content>
|
||||
<van-image
|
||||
v-if="active === 0"
|
||||
@@ -95,9 +99,9 @@ export default {
|
||||
|
||||
```html
|
||||
<van-tree-select
|
||||
v-model:main-active-index="activeIndex"
|
||||
height="55vw"
|
||||
:items="items"
|
||||
:main-active-index.sync="activeIndex"
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
<demo-section>
|
||||
<demo-block :title="t('radioMode')">
|
||||
<van-tree-select
|
||||
v-model:active-id="activeId"
|
||||
v-model:main-active-index="activeIndex"
|
||||
:items="items"
|
||||
:active-id.sync="activeId"
|
||||
:main-active-index.sync="activeIndex"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('multipleMode')">
|
||||
<van-tree-select
|
||||
v-model:active-id="activeIds"
|
||||
v-model:main-active-index="activeIndex2"
|
||||
:items="items"
|
||||
:active-id.sync="activeIds"
|
||||
:main-active-index.sync="activeIndex2"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('customContent')">
|
||||
<van-tree-select
|
||||
v-model:main-active-index="activeIndex3"
|
||||
height="55vw"
|
||||
:items="simpleItems"
|
||||
:main-active-index.sync="activeIndex3"
|
||||
>
|
||||
<template slot="content">
|
||||
<van-image
|
||||
@@ -39,10 +39,10 @@
|
||||
|
||||
<demo-block :title="t('showBadge')">
|
||||
<van-tree-select
|
||||
v-model:active-id="activeId2"
|
||||
v-model:main-active-index="activeIndex4"
|
||||
height="55vw"
|
||||
:items="badgeItems"
|
||||
:active-id.sync="activeId2"
|
||||
:main-active-index.sync="activeIndex4"
|
||||
/>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// Utils
|
||||
import { createNamespace, addUnit, isDef } from '../utils';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
import Sidebar from '../sidebar';
|
||||
import SidebarItem from '../sidebar-item';
|
||||
|
||||
const [createComponent, bem] = createNamespace('tree-select');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: Infinity,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 300,
|
||||
},
|
||||
activeId: {
|
||||
type: [Number, String, Array],
|
||||
default: 0,
|
||||
},
|
||||
selectedIcon: {
|
||||
type: String,
|
||||
default: 'success',
|
||||
},
|
||||
mainActiveIndex: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
emits: [
|
||||
'click-nav',
|
||||
'click-item',
|
||||
'update:activeId',
|
||||
'update:mainActiveIndex',
|
||||
],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
return function () {
|
||||
const { items, height, activeId, selectedIcon, mainActiveIndex } = props;
|
||||
|
||||
const selectedItem = items[+mainActiveIndex] || {};
|
||||
const subItems = selectedItem.children || [];
|
||||
const isMultiple = Array.isArray(activeId);
|
||||
|
||||
function isActiveItem(id) {
|
||||
return isMultiple ? activeId.indexOf(id) !== -1 : activeId === id;
|
||||
}
|
||||
|
||||
const Navs = items.map((item) => (
|
||||
<SidebarItem
|
||||
dot={item.dot}
|
||||
info={isDef(item.badge) ? item.badge : item.info}
|
||||
title={item.text}
|
||||
disabled={item.disabled}
|
||||
class={[bem('nav-item'), item.className]}
|
||||
/>
|
||||
));
|
||||
|
||||
function Content() {
|
||||
if (slots.content) {
|
||||
return slots.content();
|
||||
}
|
||||
|
||||
return subItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
class={[
|
||||
'van-ellipsis',
|
||||
bem('item', {
|
||||
active: isActiveItem(item.id),
|
||||
disabled: item.disabled,
|
||||
}),
|
||||
]}
|
||||
onClick={() => {
|
||||
if (!item.disabled) {
|
||||
let newActiveId = item.id;
|
||||
if (isMultiple) {
|
||||
newActiveId = activeId.slice();
|
||||
|
||||
const index = newActiveId.indexOf(item.id);
|
||||
|
||||
if (index !== -1) {
|
||||
newActiveId.splice(index, 1);
|
||||
} else if (newActiveId.length < props.max) {
|
||||
newActiveId.push(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
emit('update:activeId', newActiveId);
|
||||
emit('click-item', item);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.text}
|
||||
{isActiveItem(item.id) && (
|
||||
<Icon name={selectedIcon} class={bem('selected')} />
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem()} style={{ height: addUnit(height) }}>
|
||||
<Sidebar
|
||||
class={bem('nav')}
|
||||
modelValue={mainActiveIndex}
|
||||
onChange={(index) => {
|
||||
emit('update:mainActiveIndex', index);
|
||||
emit('click-nav', index);
|
||||
}}
|
||||
>
|
||||
{Navs}
|
||||
</Sidebar>
|
||||
<div class={bem('content')}>{Content()}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -1,166 +0,0 @@
|
||||
// Utils
|
||||
import { createNamespace, addUnit, isDef } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
|
||||
// Components
|
||||
import Icon from '../icon';
|
||||
import Sidebar from '../sidebar';
|
||||
import SidebarItem from '../sidebar-item';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots, ScopedSlot } from '../utils/types';
|
||||
|
||||
export type TreeSelectItem = {
|
||||
text: string;
|
||||
dot?: boolean;
|
||||
info?: string | number;
|
||||
badge?: string | number;
|
||||
disabled?: boolean;
|
||||
className?: any;
|
||||
children: TreeSelectChildren[];
|
||||
};
|
||||
|
||||
export type TreeSelectChildren = {
|
||||
id: number;
|
||||
text: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type TreeSelectActiveId = number | string | (number | string)[];
|
||||
|
||||
export type TreeSelectProps = {
|
||||
max: number | string;
|
||||
height: number | string;
|
||||
items: TreeSelectItem[];
|
||||
activeId: TreeSelectActiveId;
|
||||
selectedIcon: string;
|
||||
mainActiveIndex: number | string;
|
||||
};
|
||||
|
||||
export type TreeSelectSlots = DefaultSlots & {
|
||||
content?: ScopedSlot;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('tree-select');
|
||||
|
||||
function TreeSelect(
|
||||
h: CreateElement,
|
||||
props: TreeSelectProps,
|
||||
slots: TreeSelectSlots,
|
||||
ctx: RenderContext<TreeSelectProps>
|
||||
) {
|
||||
const { items, height, activeId, selectedIcon, mainActiveIndex } = props;
|
||||
|
||||
const selectedItem: Partial<TreeSelectItem> = items[+mainActiveIndex] || {};
|
||||
const subItems = selectedItem.children || [];
|
||||
const isMultiple = Array.isArray(activeId);
|
||||
|
||||
function isActiveItem(id: number | string) {
|
||||
return isMultiple
|
||||
? (activeId as (number | string)[]).indexOf(id) !== -1
|
||||
: activeId === id;
|
||||
}
|
||||
|
||||
const Navs = items.map((item) => (
|
||||
<SidebarItem
|
||||
dot={item.dot}
|
||||
info={isDef(item.badge) ? item.badge : item.info}
|
||||
title={item.text}
|
||||
disabled={item.disabled}
|
||||
class={[bem('nav-item'), item.className]}
|
||||
/>
|
||||
));
|
||||
|
||||
function Content() {
|
||||
if (slots.content) {
|
||||
return slots.content();
|
||||
}
|
||||
|
||||
return subItems.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
class={[
|
||||
'van-ellipsis',
|
||||
bem('item', {
|
||||
active: isActiveItem(item.id),
|
||||
disabled: item.disabled,
|
||||
}),
|
||||
]}
|
||||
onClick={() => {
|
||||
if (!item.disabled) {
|
||||
let newActiveId: TreeSelectActiveId = item.id;
|
||||
if (isMultiple) {
|
||||
newActiveId = (activeId as (string | number)[]).slice();
|
||||
|
||||
const index = newActiveId.indexOf(item.id);
|
||||
|
||||
if (index !== -1) {
|
||||
newActiveId.splice(index, 1);
|
||||
} else if (newActiveId.length < props.max) {
|
||||
newActiveId.push(item.id);
|
||||
}
|
||||
}
|
||||
|
||||
emit(ctx, 'update:active-id', newActiveId);
|
||||
emit(ctx, 'click-item', item);
|
||||
// compatible with legacy usage, should be removed in next major version
|
||||
emit(ctx, 'itemclick', item);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.text}
|
||||
{isActiveItem(item.id) && (
|
||||
<Icon name={selectedIcon} class={bem('selected')} />
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem()} style={{ height: addUnit(height) }} {...inherit(ctx)}>
|
||||
<Sidebar
|
||||
class={bem('nav')}
|
||||
activeKey={mainActiveIndex}
|
||||
onChange={(index: number) => {
|
||||
emit(ctx, 'update:main-active-index', index);
|
||||
emit(ctx, 'click-nav', index);
|
||||
// compatible with legacy usage, should be removed in next major version
|
||||
emit(ctx, 'navclick', index);
|
||||
}}
|
||||
>
|
||||
{Navs}
|
||||
</Sidebar>
|
||||
<div class={bem('content')}>{Content()}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TreeSelect.props = {
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: Infinity,
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 300,
|
||||
},
|
||||
activeId: {
|
||||
type: [Number, String, Array],
|
||||
default: 0,
|
||||
},
|
||||
selectedIcon: {
|
||||
type: String,
|
||||
default: 'success',
|
||||
},
|
||||
mainActiveIndex: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export default createComponent<TreeSelectProps>(TreeSelect);
|
||||
@@ -1,86 +1,18 @@
|
||||
/**
|
||||
* Create a basic component with common options
|
||||
*/
|
||||
import '../../locale';
|
||||
import { isFunction } from '..';
|
||||
import { camelize } from '../format/string';
|
||||
import { SlotsMixin } from '../../mixins/slots';
|
||||
import Vue, {
|
||||
VNode,
|
||||
VueConstructor,
|
||||
ComponentOptions,
|
||||
RenderContext,
|
||||
} from 'vue';
|
||||
import { DefaultProps, FunctionComponent } from '../types';
|
||||
|
||||
export interface VantComponentOptions extends ComponentOptions<Vue> {
|
||||
functional?: boolean;
|
||||
install?: (Vue: VueConstructor) => void;
|
||||
}
|
||||
|
||||
export type TsxBaseProps<Slots> = {
|
||||
key: string | number;
|
||||
// hack for jsx prop spread
|
||||
props: any;
|
||||
class: any;
|
||||
style: string | object[] | object;
|
||||
scopedSlots: Slots;
|
||||
};
|
||||
|
||||
export type TsxComponent<Props, Events, Slots> = (
|
||||
props: Partial<Props & Events & TsxBaseProps<Slots>>
|
||||
) => VNode;
|
||||
|
||||
function install(this: ComponentOptions<Vue>, Vue: VueConstructor) {
|
||||
const { name } = this;
|
||||
Vue.component(name as string, this);
|
||||
Vue.component(camelize(`-${name}`), this);
|
||||
}
|
||||
|
||||
// unify slots & scopedSlots
|
||||
export function unifySlots(context: RenderContext) {
|
||||
// use data.scopedSlots in lower Vue version
|
||||
const scopedSlots = context.scopedSlots || context.data.scopedSlots || {};
|
||||
const slots = context.slots();
|
||||
|
||||
Object.keys(slots).forEach((key) => {
|
||||
if (!scopedSlots[key]) {
|
||||
scopedSlots[key] = () => slots[key];
|
||||
}
|
||||
});
|
||||
|
||||
return scopedSlots;
|
||||
}
|
||||
|
||||
// should be removed after Vue 3
|
||||
function transformFunctionComponent(
|
||||
pure: FunctionComponent
|
||||
): VantComponentOptions {
|
||||
return {
|
||||
functional: true,
|
||||
props: pure.props,
|
||||
model: pure.model,
|
||||
render: (h, context): any =>
|
||||
pure(h, context.props, unifySlots(context), context),
|
||||
};
|
||||
}
|
||||
// function install(this: any, Vue: VueConstructor) {
|
||||
// const { name } = this;
|
||||
// Vue.component(name as string, this);
|
||||
// Vue.component(camelize(`-${name}`), this);
|
||||
// }
|
||||
|
||||
export function createComponent(name: string) {
|
||||
return function <Props = DefaultProps, Events = {}, Slots = {}>(
|
||||
sfc: VantComponentOptions | FunctionComponent
|
||||
): TsxComponent<Props, Events, Slots> {
|
||||
if (isFunction(sfc)) {
|
||||
sfc = transformFunctionComponent(sfc);
|
||||
}
|
||||
|
||||
if (!sfc.functional) {
|
||||
sfc.mixins = sfc.mixins || [];
|
||||
sfc.mixins.push(SlotsMixin);
|
||||
}
|
||||
|
||||
return function (sfc: any) {
|
||||
sfc.name = name;
|
||||
sfc.install = install;
|
||||
// sfc.install = install;
|
||||
|
||||
return sfc as TsxComponent<Props, Events, Slots>;
|
||||
return sfc;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,5 +10,9 @@ type CreateNamespaceReturn = [
|
||||
|
||||
export function createNamespace(name: string): CreateNamespaceReturn {
|
||||
name = 'van-' + name;
|
||||
return [createComponent(name), createBEM(name), createI18N(name)];
|
||||
return [
|
||||
createComponent(name),
|
||||
createBEM(name),
|
||||
createI18N(name)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { isServer } from '..';
|
||||
import { inBrowser } from '..';
|
||||
import { EventHandler } from '../types';
|
||||
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
export let supportsPassive = false;
|
||||
|
||||
if (!isServer) {
|
||||
if (inBrowser) {
|
||||
try {
|
||||
const opts = {};
|
||||
Object.defineProperty(opts, 'passive', {
|
||||
@@ -25,7 +25,7 @@ export function on(
|
||||
handler: EventHandler,
|
||||
passive = false
|
||||
) {
|
||||
if (!isServer) {
|
||||
if (inBrowser) {
|
||||
target.addEventListener(
|
||||
event,
|
||||
handler,
|
||||
@@ -35,7 +35,7 @@ export function on(
|
||||
}
|
||||
|
||||
export function off(target: EventTarget, event: string, handler: EventHandler) {
|
||||
if (!isServer) {
|
||||
if (inBrowser) {
|
||||
target.removeEventListener(event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* requestAnimationFrame polyfill
|
||||
*/
|
||||
|
||||
import { isServer } from '..';
|
||||
import { inBrowser } from '..';
|
||||
|
||||
let prev = Date.now();
|
||||
|
||||
@@ -16,7 +16,7 @@ function fallback(fn: FrameRequestCallback): number {
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
const root = (isServer ? global : window) as Window;
|
||||
const root = (inBrowser ? window : global) as Window;
|
||||
|
||||
/* istanbul ignore next */
|
||||
const iRaf = root.requestAnimationFrame || fallback;
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import Vue, { RenderContext, VNodeData } from 'vue';
|
||||
import { ObjectIndex } from './types';
|
||||
|
||||
type Context = RenderContext & { data: VNodeData & ObjectIndex };
|
||||
|
||||
type InheritContext = Partial<VNodeData> & ObjectIndex;
|
||||
|
||||
const inheritKey = [
|
||||
'ref',
|
||||
'style',
|
||||
'class',
|
||||
'attrs',
|
||||
'nativeOn',
|
||||
'directives',
|
||||
'staticClass',
|
||||
'staticStyle',
|
||||
];
|
||||
|
||||
const mapInheritKey: ObjectIndex = { nativeOn: 'on' };
|
||||
|
||||
// inherit partial context, map nativeOn to on
|
||||
export function inherit(
|
||||
context: Context,
|
||||
inheritListeners?: boolean
|
||||
): InheritContext {
|
||||
const result = inheritKey.reduce((obj, key) => {
|
||||
if (context.data[key]) {
|
||||
obj[mapInheritKey[key] || key] = context.data[key];
|
||||
}
|
||||
return obj;
|
||||
}, {} as InheritContext);
|
||||
|
||||
if (inheritListeners) {
|
||||
result.on = result.on || {};
|
||||
Object.assign(result.on, context.data.on);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// emit event
|
||||
export function emit(context: Context, eventName: string, ...args: any[]) {
|
||||
const listeners = context.listeners[eventName];
|
||||
if (listeners) {
|
||||
if (Array.isArray(listeners)) {
|
||||
listeners.forEach((listener) => {
|
||||
listener(...args);
|
||||
});
|
||||
} else {
|
||||
listeners(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mount functional component
|
||||
export function mount(Component: any, data?: VNodeData) {
|
||||
const instance = new Vue({
|
||||
el: document.createElement('div'),
|
||||
props: Component.props,
|
||||
render(h) {
|
||||
return h(Component, {
|
||||
props: this.$props,
|
||||
...data,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
document.body.appendChild(instance.$el);
|
||||
|
||||
return instance;
|
||||
}
|
||||
+3
-6
@@ -1,14 +1,11 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export { createNamespace } from './create';
|
||||
export { addUnit } from './format/unit';
|
||||
|
||||
export const inBrowser = typeof window !== 'undefined';
|
||||
export const isServer: boolean = Vue.prototype.$isServer;
|
||||
export { createNamespace } from './create';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
export function noop() {}
|
||||
|
||||
export const inBrowser = typeof window !== 'undefined'
|
||||
|
||||
export function isDef(val: unknown): boolean {
|
||||
return val !== undefined && val !== null;
|
||||
}
|
||||
|
||||
+4
-9
@@ -2,12 +2,11 @@
|
||||
* Vue Router support
|
||||
*/
|
||||
|
||||
import { RenderContext } from 'vue/types';
|
||||
import VueRouter, { RawLocation } from 'vue-router/types';
|
||||
import type { Router, RouteLocation } from 'vue-router';
|
||||
|
||||
export type RouteConfig = {
|
||||
url?: string;
|
||||
to?: RawLocation;
|
||||
to?: RouteLocation;
|
||||
replace?: boolean;
|
||||
};
|
||||
|
||||
@@ -19,7 +18,7 @@ function isRedundantNavigation(err: Error) {
|
||||
);
|
||||
}
|
||||
|
||||
export function route(router: VueRouter, config: RouteConfig) {
|
||||
export function route(router: Router, config: RouteConfig) {
|
||||
const { to, url, replace } = config;
|
||||
if (to && router) {
|
||||
const promise = router[replace ? 'replace' : 'push'](to);
|
||||
@@ -37,14 +36,10 @@ export function route(router: VueRouter, config: RouteConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
export function functionalRoute(context: RenderContext) {
|
||||
route(context.parent && context.parent.$router, context.props);
|
||||
}
|
||||
|
||||
export type RouteProps = {
|
||||
url?: string;
|
||||
replace?: boolean;
|
||||
to?: RawLocation;
|
||||
to?: RouteLocation;
|
||||
};
|
||||
|
||||
export const routeProps = {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { isServer } from '..';
|
||||
import { inBrowser } from '..';
|
||||
|
||||
export function isAndroid(): boolean {
|
||||
/* istanbul ignore next */
|
||||
return isServer ? false : /android/.test(navigator.userAgent.toLowerCase());
|
||||
return inBrowser ? /android/.test(navigator.userAgent.toLowerCase()) : false;
|
||||
}
|
||||
|
||||
export function isIOS(): boolean {
|
||||
/* istanbul ignore next */
|
||||
return isServer
|
||||
? false
|
||||
: /ios|iphone|ipad|ipod/.test(navigator.userAgent.toLowerCase());
|
||||
return inBrowser
|
||||
? /ios|iphone|ipad|ipod/.test(navigator.userAgent.toLowerCase())
|
||||
: false;
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { VNode } from 'vue';
|
||||
|
||||
function flattenVNodes(vnodes: VNode[]) {
|
||||
const result: VNode[] = [];
|
||||
|
||||
function traverse(vnodes: VNode[]) {
|
||||
vnodes.forEach((vnode) => {
|
||||
result.push(vnode);
|
||||
|
||||
if (vnode.componentInstance) {
|
||||
traverse(vnode.componentInstance.$children.map((item) => item.$vnode));
|
||||
}
|
||||
|
||||
if (vnode.children) {
|
||||
traverse(vnode.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
traverse(vnodes);
|
||||
return result;
|
||||
}
|
||||
|
||||
// sort children instances by vnodes order
|
||||
export function sortChildren(children: Vue[], parent: Vue) {
|
||||
const { componentOptions } = parent.$vnode;
|
||||
if (!componentOptions || !componentOptions.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
const vnodes = flattenVNodes(componentOptions.children);
|
||||
children.sort((a, b) => vnodes.indexOf(a.$vnode) - vnodes.indexOf(b.$vnode));
|
||||
}
|
||||
Reference in New Issue
Block a user