@v-c/notification 1.0.0 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Notification.d.ts +59 -0
- package/dist/Notification.js +301 -0
- package/dist/NotificationList/Content.d.ts +11 -0
- package/dist/NotificationList/Content.js +60 -0
- package/dist/NotificationList/index.d.ts +35 -0
- package/dist/NotificationList/index.js +224 -0
- package/dist/NotificationProvider.js +3 -1
- package/dist/Notifications.d.ts +9 -3
- package/dist/Notifications.js +45 -21
- package/dist/Progress.d.ts +8 -0
- package/dist/Progress.js +31 -0
- package/dist/hooks/useClosable.d.ts +17 -0
- package/dist/hooks/useClosable.js +34 -0
- package/dist/hooks/useListPosition/index.d.ts +18 -0
- package/dist/hooks/useListPosition/index.js +41 -0
- package/dist/hooks/useListPosition/useSizes.d.ts +10 -0
- package/dist/hooks/useListPosition/useSizes.js +31 -0
- package/dist/hooks/useNoticeTimer.d.ts +7 -0
- package/dist/hooks/useNoticeTimer.js +63 -0
- package/dist/hooks/useNotification.d.ts +10 -8
- package/dist/hooks/useNotification.js +10 -4
- package/dist/hooks/useStack.d.ts +5 -0
- package/dist/hooks/useStack.js +13 -10
- package/dist/index.d.ts +9 -6
- package/dist/index.js +5 -3
- package/dist/interface.d.ts +18 -43
- package/package.json +3 -3
- package/src/Notification.tsx +326 -0
- package/src/NotificationList/Content.tsx +66 -0
- package/src/NotificationList/index.tsx +282 -0
- package/src/Notifications.tsx +58 -64
- package/src/Progress.tsx +27 -0
- package/src/hooks/useClosable.ts +53 -0
- package/src/hooks/useListPosition/index.ts +69 -0
- package/src/hooks/useListPosition/useSizes.ts +43 -0
- package/src/hooks/useNoticeTimer.ts +85 -0
- package/src/hooks/useNotification.tsx +30 -28
- package/src/hooks/useStack.ts +12 -8
- package/src/index.ts +47 -6
- package/src/interface.ts +28 -44
- package/vite.config.ts +4 -3
- package/dist/Notice.cjs +0 -235
- package/dist/Notice.d.ts +0 -15
- package/dist/Notice.js +0 -227
- package/dist/NoticeList.cjs +0 -170
- package/dist/NoticeList.d.ts +0 -13
- package/dist/NoticeList.js +0 -164
- package/dist/NotificationProvider.cjs +0 -14
- package/dist/Notifications.cjs +0 -146
- package/dist/_virtual/rolldown_runtime.cjs +0 -21
- package/dist/hooks/useNotification.cjs +0 -93
- package/dist/hooks/useStack.cjs +0 -27
- package/dist/index.cjs +0 -7
- package/dist/interface.cjs +0 -1
- package/src/Notice.tsx +0 -212
- package/src/NoticeList.tsx +0 -219
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v-c/notification",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0-rc.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js",
|
|
12
|
-
"
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
13
|
},
|
|
14
14
|
"./dist/*": "./dist/*",
|
|
15
15
|
"./package.json": "./package.json"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"vue": "^3.0.0"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@v-c/util": "^1.0.
|
|
22
|
+
"@v-c/util": "^1.0.19"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "vite build",
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type { Component, CSSProperties, HTMLAttributes } from 'vue'
|
|
2
|
+
import type { VueNode } from '@v-c/util/dist/type'
|
|
3
|
+
import type { ClosableType } from './hooks/useClosable'
|
|
4
|
+
import type { NotificationProgressProps } from './Progress'
|
|
5
|
+
import { clsx } from '@v-c/util'
|
|
6
|
+
import { computed, defineComponent, ref, shallowRef, watch } from 'vue'
|
|
7
|
+
import useClosable from './hooks/useClosable'
|
|
8
|
+
import useNoticeTimer from './hooks/useNoticeTimer'
|
|
9
|
+
import DefaultProgress from './Progress'
|
|
10
|
+
|
|
11
|
+
export interface NotificationClassNames {
|
|
12
|
+
wrapper?: string
|
|
13
|
+
root?: string
|
|
14
|
+
icon?: string
|
|
15
|
+
section?: string
|
|
16
|
+
title?: string
|
|
17
|
+
description?: string
|
|
18
|
+
actions?: string
|
|
19
|
+
close?: string
|
|
20
|
+
progress?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface NotificationStyles {
|
|
24
|
+
wrapper?: CSSProperties
|
|
25
|
+
root?: CSSProperties
|
|
26
|
+
icon?: CSSProperties
|
|
27
|
+
section?: CSSProperties
|
|
28
|
+
title?: CSSProperties
|
|
29
|
+
description?: CSSProperties
|
|
30
|
+
actions?: CSSProperties
|
|
31
|
+
close?: CSSProperties
|
|
32
|
+
progress?: CSSProperties
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ComponentsType {
|
|
36
|
+
progress?: Component<NotificationProgressProps>
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface NotificationProps {
|
|
40
|
+
// Style
|
|
41
|
+
prefixCls: string
|
|
42
|
+
className?: string
|
|
43
|
+
style?: CSSProperties
|
|
44
|
+
classNames?: NotificationClassNames
|
|
45
|
+
styles?: NotificationStyles
|
|
46
|
+
components?: ComponentsType
|
|
47
|
+
|
|
48
|
+
// UI
|
|
49
|
+
title?: VueNode
|
|
50
|
+
description?: VueNode
|
|
51
|
+
icon?: VueNode
|
|
52
|
+
actions?: VueNode
|
|
53
|
+
role?: string
|
|
54
|
+
closable?: ClosableType
|
|
55
|
+
offset?: number
|
|
56
|
+
notificationIndex?: number
|
|
57
|
+
stackInThreshold?: boolean
|
|
58
|
+
props?: HTMLAttributes & Record<string, any>
|
|
59
|
+
|
|
60
|
+
// Behavior
|
|
61
|
+
duration?: number | false | null
|
|
62
|
+
showProgress?: boolean
|
|
63
|
+
times?: number
|
|
64
|
+
hovering?: boolean
|
|
65
|
+
pauseOnHover?: boolean
|
|
66
|
+
|
|
67
|
+
// Function
|
|
68
|
+
onClick?: (e: MouseEvent) => void
|
|
69
|
+
onMouseEnter?: (e: MouseEvent) => void
|
|
70
|
+
onMouseLeave?: (e: MouseEvent) => void
|
|
71
|
+
/** @deprecated Please use `closable.onClose` instead. */
|
|
72
|
+
onClose?: () => void
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface NoticeStyle extends CSSProperties {
|
|
76
|
+
'--notification-index'?: number
|
|
77
|
+
'--notification-y'?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const Notification = defineComponent<NotificationProps>(
|
|
81
|
+
(props, { attrs, expose }) => {
|
|
82
|
+
const nodeRef = ref<HTMLDivElement | null>(null)
|
|
83
|
+
const percent = shallowRef(0)
|
|
84
|
+
const hovering = shallowRef(false)
|
|
85
|
+
|
|
86
|
+
expose({
|
|
87
|
+
nativeElement: nodeRef,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// ========================= Close ==========================
|
|
91
|
+
const closableRef = computed(() => props.closable)
|
|
92
|
+
const [mergedClosable, closableConfig, closeBtnAriaProps] = useClosable(closableRef)
|
|
93
|
+
|
|
94
|
+
const onInternalClose = () => {
|
|
95
|
+
closableConfig.value.onClose?.()
|
|
96
|
+
props.onClose?.()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ======================== Duration ========================
|
|
100
|
+
const mergedDuration = computed(() => {
|
|
101
|
+
if (props.duration === undefined) {
|
|
102
|
+
return 4.5
|
|
103
|
+
}
|
|
104
|
+
return props.duration
|
|
105
|
+
})
|
|
106
|
+
const [onResume, onPause] = useNoticeTimer(
|
|
107
|
+
mergedDuration,
|
|
108
|
+
onInternalClose,
|
|
109
|
+
(next) => {
|
|
110
|
+
percent.value = next
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
// Sync pause/resume to forced and local hover.
|
|
115
|
+
watch(
|
|
116
|
+
[
|
|
117
|
+
() => props.hovering,
|
|
118
|
+
hovering,
|
|
119
|
+
() => props.pauseOnHover,
|
|
120
|
+
],
|
|
121
|
+
() => {
|
|
122
|
+
const pauseOnHover = props.pauseOnHover ?? true
|
|
123
|
+
if (!pauseOnHover) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
if (props.hovering) {
|
|
127
|
+
onPause()
|
|
128
|
+
}
|
|
129
|
+
else if (!hovering.value) {
|
|
130
|
+
onResume()
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{ immediate: true },
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// Cache offset/notificationIndex so transitions still have a value
|
|
137
|
+
// even when the controlling list omits them temporarily.
|
|
138
|
+
const offsetRef = shallowRef(props.offset)
|
|
139
|
+
const indexRef = shallowRef(props.notificationIndex)
|
|
140
|
+
watch(() => props.offset, (next) => {
|
|
141
|
+
if (next !== undefined) {
|
|
142
|
+
offsetRef.value = next
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
watch(() => props.notificationIndex, (next) => {
|
|
146
|
+
if (next !== undefined) {
|
|
147
|
+
indexRef.value = next
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// ======================== Hover ==========================
|
|
152
|
+
const onInternalMouseEnter = (e: MouseEvent) => {
|
|
153
|
+
hovering.value = true
|
|
154
|
+
if ((props.pauseOnHover ?? true)) {
|
|
155
|
+
onPause()
|
|
156
|
+
}
|
|
157
|
+
props.onMouseEnter?.(e)
|
|
158
|
+
;(props.props?.onMouseenter as any)?.(e)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const onInternalMouseLeave = (e: MouseEvent) => {
|
|
162
|
+
hovering.value = false
|
|
163
|
+
const pauseOnHover = props.pauseOnHover ?? true
|
|
164
|
+
if (pauseOnHover && !props.hovering) {
|
|
165
|
+
onResume()
|
|
166
|
+
}
|
|
167
|
+
props.onMouseLeave?.(e)
|
|
168
|
+
;(props.props?.onMouseleave as any)?.(e)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const onInternalCloseClick = (e: MouseEvent) => {
|
|
172
|
+
e.preventDefault()
|
|
173
|
+
e.stopPropagation()
|
|
174
|
+
onInternalClose()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
const {
|
|
179
|
+
prefixCls,
|
|
180
|
+
className,
|
|
181
|
+
style,
|
|
182
|
+
classNames: ncs,
|
|
183
|
+
styles: nss,
|
|
184
|
+
components,
|
|
185
|
+
title,
|
|
186
|
+
description,
|
|
187
|
+
icon,
|
|
188
|
+
actions,
|
|
189
|
+
role,
|
|
190
|
+
stackInThreshold,
|
|
191
|
+
props: rootProps,
|
|
192
|
+
showProgress,
|
|
193
|
+
duration,
|
|
194
|
+
} = props
|
|
195
|
+
|
|
196
|
+
const noticePrefixCls = `${prefixCls}-notice`
|
|
197
|
+
|
|
198
|
+
// ======================== Content =========================
|
|
199
|
+
const titleNode = title !== undefined && title !== null
|
|
200
|
+
? (
|
|
201
|
+
<div class={clsx(`${noticePrefixCls}-title`, ncs?.title)} style={nss?.title}>
|
|
202
|
+
{title}
|
|
203
|
+
</div>
|
|
204
|
+
)
|
|
205
|
+
: null
|
|
206
|
+
|
|
207
|
+
const descNode = description !== undefined && description !== null
|
|
208
|
+
? (
|
|
209
|
+
<div class={clsx(`${noticePrefixCls}-description`, ncs?.description)} style={nss?.description}>
|
|
210
|
+
{description}
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
: null
|
|
214
|
+
|
|
215
|
+
const hasTitle = titleNode !== null
|
|
216
|
+
const hasDescription = descNode !== null
|
|
217
|
+
|
|
218
|
+
let contentNode: VueNode = null
|
|
219
|
+
if (hasTitle && hasDescription) {
|
|
220
|
+
contentNode = (
|
|
221
|
+
<div class={clsx(`${noticePrefixCls}-section`, ncs?.section)} style={nss?.section}>
|
|
222
|
+
{titleNode}
|
|
223
|
+
{descNode}
|
|
224
|
+
</div>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
contentNode = titleNode || descNode
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (icon !== undefined && icon !== null) {
|
|
232
|
+
contentNode = (
|
|
233
|
+
<div class={clsx(`${noticePrefixCls}-wrapper`, ncs?.wrapper)} style={nss?.wrapper}>
|
|
234
|
+
<div class={clsx(`${noticePrefixCls}-icon`, ncs?.icon)} style={nss?.icon}>
|
|
235
|
+
{icon}
|
|
236
|
+
</div>
|
|
237
|
+
{contentNode}
|
|
238
|
+
</div>
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const actionsNode = actions
|
|
243
|
+
? (
|
|
244
|
+
<div class={clsx(`${noticePrefixCls}-actions`, ncs?.actions)} style={nss?.actions}>
|
|
245
|
+
{actions}
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
: null
|
|
249
|
+
|
|
250
|
+
// ========================= Render =========================
|
|
251
|
+
const mergedOffset = props.offset ?? offsetRef.value
|
|
252
|
+
const mergedIndex = props.notificationIndex ?? indexRef.value ?? 0
|
|
253
|
+
const safePercent = Math.min(Math.max(percent.value * 100, 0), 100)
|
|
254
|
+
const validPercent = 100 - safePercent
|
|
255
|
+
const ProgressComponent = (components?.progress || DefaultProgress) as any
|
|
256
|
+
|
|
257
|
+
const mergedStyle: NoticeStyle = {
|
|
258
|
+
'--notification-index': mergedIndex,
|
|
259
|
+
...nss?.root,
|
|
260
|
+
...style,
|
|
261
|
+
}
|
|
262
|
+
if (mergedOffset !== undefined) {
|
|
263
|
+
mergedStyle['--notification-y'] = `${mergedOffset}px`
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const mergedRole = role ?? (rootProps as any)?.role ?? 'alert'
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<div
|
|
270
|
+
{...rootProps}
|
|
271
|
+
ref={nodeRef}
|
|
272
|
+
role={mergedRole}
|
|
273
|
+
data-notification-index={mergedIndex}
|
|
274
|
+
class={clsx(
|
|
275
|
+
noticePrefixCls,
|
|
276
|
+
className,
|
|
277
|
+
(attrs as any).class,
|
|
278
|
+
ncs?.root,
|
|
279
|
+
{
|
|
280
|
+
[`${noticePrefixCls}-closable`]: mergedClosable.value,
|
|
281
|
+
[`${noticePrefixCls}-stack-in-threshold`]: stackInThreshold,
|
|
282
|
+
},
|
|
283
|
+
)}
|
|
284
|
+
style={{
|
|
285
|
+
...mergedStyle,
|
|
286
|
+
...((attrs as any).style as CSSProperties | undefined),
|
|
287
|
+
}}
|
|
288
|
+
onClick={props.onClick}
|
|
289
|
+
onMouseenter={onInternalMouseEnter}
|
|
290
|
+
onMouseleave={onInternalMouseLeave}
|
|
291
|
+
>
|
|
292
|
+
{contentNode}
|
|
293
|
+
{actionsNode}
|
|
294
|
+
|
|
295
|
+
{mergedClosable.value && (
|
|
296
|
+
<button
|
|
297
|
+
class={clsx(`${noticePrefixCls}-close`, ncs?.close)}
|
|
298
|
+
aria-label="Close"
|
|
299
|
+
{...closeBtnAriaProps.value}
|
|
300
|
+
style={nss?.close}
|
|
301
|
+
onClick={onInternalCloseClick}
|
|
302
|
+
>
|
|
303
|
+
{closableConfig.value.closeIcon}
|
|
304
|
+
</button>
|
|
305
|
+
)}
|
|
306
|
+
|
|
307
|
+
{showProgress && typeof duration === 'number' && duration > 0 && (
|
|
308
|
+
<ProgressComponent
|
|
309
|
+
{...{
|
|
310
|
+
className: clsx(`${noticePrefixCls}-progress`, ncs?.progress),
|
|
311
|
+
percent: validPercent,
|
|
312
|
+
style: nss?.progress,
|
|
313
|
+
} as NotificationProgressProps}
|
|
314
|
+
/>
|
|
315
|
+
)}
|
|
316
|
+
</div>
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'Notification',
|
|
322
|
+
inheritAttrs: false,
|
|
323
|
+
},
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
export default Notification
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CSSProperties } from 'vue'
|
|
2
|
+
import { clsx } from '@v-c/util'
|
|
3
|
+
import { defineComponent, ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
export interface ContentProps {
|
|
6
|
+
listPrefixCls: string
|
|
7
|
+
height: number
|
|
8
|
+
topNoticeHeight?: number
|
|
9
|
+
topNoticeWidth?: number
|
|
10
|
+
className?: string
|
|
11
|
+
style?: CSSProperties
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ContentStyle extends CSSProperties {
|
|
15
|
+
'--top-notificiation-height': string
|
|
16
|
+
'--top-notificiation-width': string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Content = defineComponent<ContentProps>(
|
|
20
|
+
(props, { slots, expose }) => {
|
|
21
|
+
const contentRef = ref<HTMLDivElement | null>(null)
|
|
22
|
+
let prevHeight = props.height
|
|
23
|
+
|
|
24
|
+
expose({
|
|
25
|
+
nativeElement: contentRef,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
const {
|
|
30
|
+
listPrefixCls,
|
|
31
|
+
height,
|
|
32
|
+
topNoticeHeight = 0,
|
|
33
|
+
topNoticeWidth = 0,
|
|
34
|
+
className,
|
|
35
|
+
style,
|
|
36
|
+
} = props
|
|
37
|
+
|
|
38
|
+
const heightStatus = height < prevHeight ? 'decrease' : 'increase'
|
|
39
|
+
prevHeight = height
|
|
40
|
+
|
|
41
|
+
const contentPrefixCls = `${listPrefixCls}-content`
|
|
42
|
+
const contentStyle: ContentStyle = {
|
|
43
|
+
...style,
|
|
44
|
+
height,
|
|
45
|
+
'--top-notificiation-height': `${topNoticeHeight}px`,
|
|
46
|
+
'--top-notificiation-width': `${topNoticeWidth}px`,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
ref={contentRef}
|
|
52
|
+
class={clsx(contentPrefixCls, `${contentPrefixCls}-${heightStatus}`, className)}
|
|
53
|
+
style={contentStyle}
|
|
54
|
+
>
|
|
55
|
+
{slots.default?.()}
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'NotificationListContent',
|
|
62
|
+
inheritAttrs: false,
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
export default Content
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type { CSSProperties, TransitionGroupProps } from 'vue'
|
|
2
|
+
import type { Key, StackConfig } from '../interface'
|
|
3
|
+
import type {
|
|
4
|
+
ComponentsType,
|
|
5
|
+
NotificationClassNames as NoticeClassNames,
|
|
6
|
+
NotificationProps,
|
|
7
|
+
NotificationStyles as NoticeStyles,
|
|
8
|
+
} from '../Notification'
|
|
9
|
+
import { clsx } from '@v-c/util'
|
|
10
|
+
import { getTransitionGroupProps } from '@v-c/util/dist/utils/transition'
|
|
11
|
+
import { unrefElement } from '@v-c/util/dist/vueuse/unref-element'
|
|
12
|
+
import { computed, defineComponent, ref, shallowRef, toRef, TransitionGroup, watch, watchEffect } from 'vue'
|
|
13
|
+
import useListPosition from '../hooks/useListPosition'
|
|
14
|
+
import useStack from '../hooks/useStack'
|
|
15
|
+
import Notification from '../Notification'
|
|
16
|
+
import { useNotificationContext } from '../NotificationProvider'
|
|
17
|
+
import Content from './Content'
|
|
18
|
+
|
|
19
|
+
export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'
|
|
20
|
+
|
|
21
|
+
export type { StackConfig }
|
|
22
|
+
|
|
23
|
+
export interface NotificationListConfig extends Omit<NotificationProps, 'prefixCls'> {
|
|
24
|
+
key: Key
|
|
25
|
+
placement?: Placement
|
|
26
|
+
times?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface NotificationClassNames extends NoticeClassNames {
|
|
30
|
+
list?: string
|
|
31
|
+
listContent?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface NotificationStyles extends NoticeStyles {
|
|
35
|
+
list?: CSSProperties
|
|
36
|
+
listContent?: CSSProperties
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface NotificationListProps {
|
|
40
|
+
configList?: NotificationListConfig[]
|
|
41
|
+
prefixCls?: string
|
|
42
|
+
placement: Placement
|
|
43
|
+
pauseOnHover?: boolean
|
|
44
|
+
classNames?: NotificationClassNames
|
|
45
|
+
styles?: NotificationStyles
|
|
46
|
+
components?: ComponentsType
|
|
47
|
+
stack?: StackConfig
|
|
48
|
+
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
49
|
+
className?: string
|
|
50
|
+
style?: CSSProperties
|
|
51
|
+
onNoticeClose?: (key: Key) => void
|
|
52
|
+
onAllRemoved?: (placement: Placement) => void
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const noticeSlotKeys: (keyof NoticeClassNames)[] = [
|
|
56
|
+
'wrapper',
|
|
57
|
+
'root',
|
|
58
|
+
'icon',
|
|
59
|
+
'section',
|
|
60
|
+
'title',
|
|
61
|
+
'description',
|
|
62
|
+
'actions',
|
|
63
|
+
'close',
|
|
64
|
+
'progress',
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
function fillClassNames(
|
|
68
|
+
list: (NotificationClassNames | undefined)[],
|
|
69
|
+
): NotificationClassNames {
|
|
70
|
+
return noticeSlotKeys.reduce<NotificationClassNames>((merged, key) => {
|
|
71
|
+
merged[key] = clsx(...list.map(item => item?.[key]))
|
|
72
|
+
return merged
|
|
73
|
+
}, {})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fillStyles(
|
|
77
|
+
list: (NotificationStyles | undefined)[],
|
|
78
|
+
): NotificationStyles {
|
|
79
|
+
return noticeSlotKeys.reduce<NotificationStyles>((merged, key) => {
|
|
80
|
+
merged[key] = Object.assign({}, ...list.map(item => item?.[key]))
|
|
81
|
+
return merged
|
|
82
|
+
}, {})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getIndex(keys: { key: Key }[], key: Key): number | undefined {
|
|
86
|
+
const strKey = String(key)
|
|
87
|
+
const index = keys.findIndex(item => String(item.key) === strKey)
|
|
88
|
+
if (index === -1) {
|
|
89
|
+
return undefined
|
|
90
|
+
}
|
|
91
|
+
return keys.length - index - 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const NotificationList = defineComponent<NotificationListProps>(
|
|
95
|
+
(props, { attrs }) => {
|
|
96
|
+
const ctx = useNotificationContext()
|
|
97
|
+
|
|
98
|
+
const configList = computed(() => props.configList ?? [])
|
|
99
|
+
const keys = computed(() =>
|
|
100
|
+
configList.value.map(config => ({ ...config, key: String(config.key) as Key })),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// ====================== Stack State =======================
|
|
104
|
+
const stackConfig = toRef(props, 'stack')
|
|
105
|
+
const [stackEnabled, stackParams] = useStack(stackConfig)
|
|
106
|
+
const listHovering = shallowRef(false)
|
|
107
|
+
const expanded = computed(() =>
|
|
108
|
+
stackEnabled.value && (listHovering.value || keys.value.length <= (stackParams.threshold?.value ?? 0)),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const stackPosition = computed(() => {
|
|
112
|
+
if (!stackEnabled.value || expanded.value) {
|
|
113
|
+
return undefined
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
offset: stackParams.offset?.value,
|
|
117
|
+
threshold: stackParams.threshold?.value,
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// ====================== List Measure ======================
|
|
122
|
+
const gap = ref(0)
|
|
123
|
+
const contentRef = ref<{ nativeElement: { value: HTMLDivElement | null } } | null>(null)
|
|
124
|
+
const [position, setNodeSize] = useListPosition(keys as any, stackPosition as any, gap)
|
|
125
|
+
|
|
126
|
+
const hasConfigList = computed(() => !!configList.value.length)
|
|
127
|
+
watchEffect(() => {
|
|
128
|
+
if (!hasConfigList.value) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
const listNode = unrefElement<HTMLDivElement>(contentRef.value?.nativeElement as any)
|
|
132
|
+
if (!listNode) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
const { gap: cssGap, rowGap } = window.getComputedStyle(listNode)
|
|
136
|
+
const nextGap = Number.parseFloat(rowGap || cssGap) || 0
|
|
137
|
+
if (gap.value !== nextGap) {
|
|
138
|
+
gap.value = nextGap
|
|
139
|
+
}
|
|
140
|
+
}, { flush: 'post' })
|
|
141
|
+
|
|
142
|
+
// Notify when list becomes empty (after motion finished).
|
|
143
|
+
const checkAllClosed = () => {
|
|
144
|
+
if (configList.value.length === 0) {
|
|
145
|
+
props.onAllRemoved?.(props.placement)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Compute motion props per placement.
|
|
150
|
+
const placementMotion = computed(() => {
|
|
151
|
+
if (typeof props.motion === 'function') {
|
|
152
|
+
return props.placement ? props.motion(props.placement) : undefined
|
|
153
|
+
}
|
|
154
|
+
return props.motion
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Cleanup node sizes when items leave permanently.
|
|
158
|
+
watch(keys, (next, prev) => {
|
|
159
|
+
if (!prev) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
const nextKeySet = new Set(next.map(item => String(item.key)))
|
|
163
|
+
prev.forEach((item) => {
|
|
164
|
+
const key = String(item.key)
|
|
165
|
+
if (!nextKeySet.has(key)) {
|
|
166
|
+
setNodeSize(key, null)
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
return () => {
|
|
172
|
+
const {
|
|
173
|
+
prefixCls = 'vc-notification',
|
|
174
|
+
pauseOnHover,
|
|
175
|
+
classNames: ncs,
|
|
176
|
+
styles: nss,
|
|
177
|
+
components,
|
|
178
|
+
placement,
|
|
179
|
+
className,
|
|
180
|
+
style,
|
|
181
|
+
onNoticeClose,
|
|
182
|
+
} = props
|
|
183
|
+
|
|
184
|
+
const listPrefixCls = `${prefixCls}-list`
|
|
185
|
+
const positionResult = position.value
|
|
186
|
+
|
|
187
|
+
let motionGroupProps: TransitionGroupProps = {}
|
|
188
|
+
if (placementMotion.value) {
|
|
189
|
+
motionGroupProps = getTransitionGroupProps(placementMotion.value.name!, placementMotion.value)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const renderItems = () =>
|
|
193
|
+
keys.value.map((config) => {
|
|
194
|
+
const { key, placement: _itemPlacement, classNames: configClassNames, styles: configStyles, className: configClassName, style: configStyle, ...notificationConfig } = config
|
|
195
|
+
const strKey = String(key)
|
|
196
|
+
const notificationIndex = getIndex(keys.value, key)
|
|
197
|
+
const stackInThreshold
|
|
198
|
+
= stackEnabled.value && notificationIndex !== undefined && notificationIndex < (stackParams.threshold?.value ?? 0)
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<Notification
|
|
202
|
+
key={strKey}
|
|
203
|
+
{...notificationConfig}
|
|
204
|
+
ref={(el: any) => {
|
|
205
|
+
const node = unrefElement<HTMLDivElement>(el?.nativeElement as any)
|
|
206
|
+
setNodeSize(strKey, node ?? null)
|
|
207
|
+
}}
|
|
208
|
+
prefixCls={prefixCls}
|
|
209
|
+
class={clsx((ctx.value as any)?.classNames?.notice, configClassName)}
|
|
210
|
+
style={configStyle}
|
|
211
|
+
classNames={fillClassNames([ncs, configClassNames])}
|
|
212
|
+
styles={fillStyles([nss, configStyles])}
|
|
213
|
+
components={{
|
|
214
|
+
...components,
|
|
215
|
+
...(config as NotificationListConfig).components,
|
|
216
|
+
}}
|
|
217
|
+
hovering={stackEnabled.value && listHovering.value}
|
|
218
|
+
pauseOnHover={config.pauseOnHover ?? pauseOnHover}
|
|
219
|
+
offset={positionResult.notificationPosition.get(strKey)}
|
|
220
|
+
notificationIndex={notificationIndex}
|
|
221
|
+
stackInThreshold={stackInThreshold}
|
|
222
|
+
onClose={() => {
|
|
223
|
+
(config as NotificationListConfig).onClose?.()
|
|
224
|
+
onNoticeClose?.(key)
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
227
|
+
)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div
|
|
232
|
+
class={clsx(
|
|
233
|
+
prefixCls,
|
|
234
|
+
listPrefixCls,
|
|
235
|
+
`${prefixCls}-${placement}`,
|
|
236
|
+
(ctx.value as any)?.classNames?.list,
|
|
237
|
+
className,
|
|
238
|
+
ncs?.list,
|
|
239
|
+
(attrs as any).class,
|
|
240
|
+
{
|
|
241
|
+
[`${prefixCls}-stack`]: stackEnabled.value,
|
|
242
|
+
[`${prefixCls}-stack-expanded`]: expanded.value,
|
|
243
|
+
[`${listPrefixCls}-hovered`]: listHovering.value,
|
|
244
|
+
},
|
|
245
|
+
)}
|
|
246
|
+
onMouseenter={() => {
|
|
247
|
+
listHovering.value = true
|
|
248
|
+
}}
|
|
249
|
+
onMouseleave={() => {
|
|
250
|
+
listHovering.value = false
|
|
251
|
+
}}
|
|
252
|
+
style={{ ...nss?.list, ...style, ...((attrs as any).style ?? {}) } as CSSProperties}
|
|
253
|
+
>
|
|
254
|
+
<Content
|
|
255
|
+
ref={contentRef as any}
|
|
256
|
+
listPrefixCls={listPrefixCls}
|
|
257
|
+
height={positionResult.totalHeight}
|
|
258
|
+
topNoticeHeight={positionResult.topNoticeHeight}
|
|
259
|
+
topNoticeWidth={positionResult.topNoticeWidth}
|
|
260
|
+
className={ncs?.listContent}
|
|
261
|
+
style={nss?.listContent}
|
|
262
|
+
>
|
|
263
|
+
<TransitionGroup
|
|
264
|
+
tag={false as any}
|
|
265
|
+
appear
|
|
266
|
+
{...motionGroupProps}
|
|
267
|
+
onAfterLeave={checkAllClosed}
|
|
268
|
+
>
|
|
269
|
+
{renderItems()}
|
|
270
|
+
</TransitionGroup>
|
|
271
|
+
</Content>
|
|
272
|
+
</div>
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'NotificationList',
|
|
278
|
+
inheritAttrs: false,
|
|
279
|
+
},
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
export default NotificationList
|