vuecssuiant-designantdreactantantd-vueenterprisefrontendui-designvue-antdvue-antd-uivue3vuecomponent
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
6.9 KiB
230 lines
6.9 KiB
import type { CSSProperties } from 'vue'; |
|
import { watch, computed, defineComponent, ref, TransitionGroup } from 'vue'; |
|
import type { NoticeProps } from './Notice'; |
|
import Notice from './Notice'; |
|
import type { CSSMotionProps } from '../_util/transition'; |
|
import { getTransitionGroupProps } from '../_util/transition'; |
|
import type { Key, VueNode } from '../_util/type'; |
|
import classNames from '../_util/classNames'; |
|
import Portal from '../_util/Portal'; |
|
|
|
let seed = 0; |
|
const now = Date.now(); |
|
|
|
export function getUuid() { |
|
const id = seed; |
|
seed += 1; |
|
return `rcNotification_${now}_${id}`; |
|
} |
|
|
|
export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'; |
|
|
|
export interface OpenConfig extends NoticeProps { |
|
key: Key; |
|
placement?: Placement; |
|
content?: VueNode; |
|
duration?: number | null; |
|
} |
|
|
|
export type Placements = Partial<Record<Placement, OpenConfig[]>>; |
|
|
|
export interface NoticeContent extends Omit<NoticeProps, 'prefixCls' | 'noticeKey' | 'onClose'> { |
|
prefixCls?: string; |
|
key?: Key; |
|
updateMark?: string; |
|
content?: any; |
|
onClose?: () => void; |
|
style?: CSSProperties; |
|
class?: String; |
|
placement?: Placement; |
|
} |
|
|
|
export type NoticeFunc = (noticeProps: NoticeContent) => void; |
|
export type HolderReadyCallback = ( |
|
div: HTMLDivElement, |
|
noticeProps: NoticeProps & { key: Key }, |
|
) => void; |
|
|
|
export interface NotificationInstance { |
|
notice: NoticeFunc; |
|
removeNotice: (key: Key) => void; |
|
destroy: () => void; |
|
add: (noticeProps: NoticeContent) => void; |
|
component: Notification; |
|
} |
|
|
|
export interface HookNotificationProps { |
|
prefixCls?: string; |
|
transitionName?: string; |
|
animation?: string | CSSMotionProps | ((placement?: Placement) => CSSMotionProps); |
|
maxCount?: number; |
|
closeIcon?: any; |
|
hashId?: string; |
|
// Hook Notification |
|
remove: (key: Key) => void; |
|
notices: NotificationState; |
|
getStyles?: (placement?: Placement) => CSSProperties; |
|
getClassName?: (placement?: Placement) => string; |
|
onAllRemoved?: VoidFunction; |
|
getContainer?: () => HTMLElement; |
|
} |
|
|
|
type NotificationState = { |
|
notice: NoticeContent & { |
|
userPassKey?: Key; |
|
}; |
|
holderCallback?: HolderReadyCallback; |
|
}[]; |
|
|
|
const Notification = defineComponent<HookNotificationProps>({ |
|
name: 'HookNotification', |
|
inheritAttrs: false, |
|
props: [ |
|
'prefixCls', |
|
'transitionName', |
|
'animation', |
|
'maxCount', |
|
'closeIcon', |
|
'hashId', |
|
'remove', |
|
'notices', |
|
'getStyles', |
|
'getClassName', |
|
'onAllRemoved', |
|
'getContainer', |
|
] as any, |
|
setup(props, { attrs, slots }) { |
|
const hookRefs = new Map<Key, HTMLDivElement>(); |
|
const notices = computed(() => props.notices); |
|
const transitionProps = computed(() => { |
|
let name = props.transitionName; |
|
if (!name && props.animation) { |
|
switch (typeof props.animation) { |
|
case 'string': |
|
name = props.animation; |
|
break; |
|
case 'function': |
|
name = props.animation().name; |
|
break; |
|
case 'object': |
|
name = props.animation.name; |
|
break; |
|
default: |
|
name = `${props.prefixCls}-fade`; |
|
break; |
|
} |
|
} |
|
return getTransitionGroupProps(name); |
|
}); |
|
|
|
const remove = (key: Key) => props.remove(key); |
|
const placements = ref({} as Record<Placement, NotificationState>); |
|
watch(notices, () => { |
|
const nextPlacements = {} as any; |
|
// init placements with animation |
|
Object.keys(placements.value).forEach(placement => { |
|
nextPlacements[placement] = []; |
|
}); |
|
props.notices.forEach(config => { |
|
const { placement = 'topRight' } = config.notice; |
|
if (placement) { |
|
nextPlacements[placement] = nextPlacements[placement] || []; |
|
nextPlacements[placement].push(config); |
|
} |
|
}); |
|
placements.value = nextPlacements; |
|
}); |
|
|
|
const placementList = computed(() => Object.keys(placements.value) as Placement[]); |
|
|
|
return () => { |
|
const { prefixCls, closeIcon = slots.closeIcon?.({ prefixCls }) } = props; |
|
const noticeNodes = placementList.value.map(placement => { |
|
const noticesForPlacement = placements.value[placement]; |
|
const classes = props.getClassName?.(placement); |
|
const styles = props.getStyles?.(placement); |
|
const noticeNodesForPlacement = noticesForPlacement.map( |
|
({ notice, holderCallback }, index) => { |
|
const updateMark = index === notices.value.length - 1 ? notice.updateMark : undefined; |
|
const { key, userPassKey } = notice; |
|
const { content } = notice; |
|
const noticeProps = { |
|
prefixCls, |
|
closeIcon: typeof closeIcon === 'function' ? closeIcon({ prefixCls }) : closeIcon, |
|
...(notice as any), |
|
...notice.props, |
|
key, |
|
noticeKey: userPassKey || key, |
|
updateMark, |
|
onClose: (noticeKey: Key) => { |
|
remove(noticeKey); |
|
notice.onClose?.(); |
|
}, |
|
onClick: notice.onClick, |
|
}; |
|
|
|
if (holderCallback) { |
|
return ( |
|
<div |
|
key={key} |
|
class={`${prefixCls}-hook-holder`} |
|
ref={(div: HTMLDivElement) => { |
|
if (typeof key === 'undefined') { |
|
return; |
|
} |
|
if (div) { |
|
hookRefs.set(key, div); |
|
holderCallback(div, noticeProps); |
|
} else { |
|
hookRefs.delete(key); |
|
} |
|
}} |
|
/> |
|
); |
|
} |
|
|
|
return ( |
|
<Notice {...noticeProps} class={classNames(noticeProps.class, props.hashId)}> |
|
{typeof content === 'function' ? content({ prefixCls }) : content} |
|
</Notice> |
|
); |
|
}, |
|
); |
|
const className = { |
|
[prefixCls]: 1, |
|
[`${prefixCls}-${placement}`]: 1, |
|
[attrs.class as string]: !!attrs.class, |
|
[props.hashId]: true, |
|
[classes]: !!classes, |
|
}; |
|
function onAfterLeave() { |
|
if (noticesForPlacement.length > 0) { |
|
return; |
|
} |
|
Reflect.deleteProperty(placements.value, placement); |
|
props.onAllRemoved?.(); |
|
} |
|
return ( |
|
<div |
|
key={placement} |
|
class={className} |
|
style={ |
|
(attrs.style as CSSProperties) || |
|
styles || { |
|
top: '65px', |
|
left: '50%', |
|
} |
|
} |
|
> |
|
<TransitionGroup tag="div" {...transitionProps.value} onAfterLeave={onAfterLeave}> |
|
{noticeNodesForPlacement} |
|
</TransitionGroup> |
|
</div> |
|
); |
|
}); |
|
return <Portal getContainer={props.getContainer}>{noticeNodes}</Portal>; |
|
}; |
|
}, |
|
}); |
|
|
|
export default Notification;
|
|
|