@v-c/notification 2.0.0-beta.1 → 2.0.0-rc.2

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.
Files changed (51) hide show
  1. package/dist/Notification.d.ts +7 -234
  2. package/dist/Notification.js +199 -135
  3. package/dist/NotificationList/Content.d.ts +3 -80
  4. package/dist/NotificationList/Content.js +33 -47
  5. package/dist/NotificationList/index.d.ts +7 -128
  6. package/dist/NotificationList/index.js +139 -122
  7. package/dist/NotificationProvider.d.ts +1 -20
  8. package/dist/NotificationProvider.js +2 -13
  9. package/dist/Notifications.d.ts +10 -132
  10. package/dist/Notifications.js +123 -108
  11. package/dist/Progress.d.ts +3 -3
  12. package/dist/Progress.js +24 -11
  13. package/dist/hooks/useClosable.d.ts +6 -11
  14. package/dist/hooks/useClosable.js +7 -6
  15. package/dist/hooks/useListPosition/index.d.ts +14 -13
  16. package/dist/hooks/useListPosition/index.js +16 -23
  17. package/dist/hooks/useListPosition/useSizes.d.ts +3 -6
  18. package/dist/hooks/useListPosition/useSizes.js +12 -10
  19. package/dist/hooks/useNoticeTimer.d.ts +5 -4
  20. package/dist/hooks/useNoticeTimer.js +33 -41
  21. package/dist/hooks/useNotification.d.ts +25 -7
  22. package/dist/hooks/useNotification.js +20 -25
  23. package/dist/hooks/useStack.d.ts +9 -8
  24. package/dist/hooks/useStack.js +17 -11
  25. package/dist/index.d.ts +9 -8
  26. package/dist/index.js +2 -2
  27. package/dist/interface.d.ts +30 -0
  28. package/dist/interface.js +0 -0
  29. package/docs/context.vue +1 -1
  30. package/docs/hooks.vue +4 -4
  31. package/docs/index.less +143 -62
  32. package/docs/maxCount.vue +1 -1
  33. package/docs/showProgress.vue +2 -2
  34. package/docs/stack.vue +1 -1
  35. package/package.json +2 -3
  36. package/src/Notification.tsx +128 -165
  37. package/src/NotificationList/Content.tsx +36 -54
  38. package/src/NotificationList/index.tsx +131 -135
  39. package/src/NotificationProvider.tsx +3 -23
  40. package/src/Notifications.tsx +62 -84
  41. package/src/Progress.tsx +19 -15
  42. package/src/hooks/useClosable.ts +15 -16
  43. package/src/hooks/useListPosition/index.ts +24 -40
  44. package/src/hooks/useListPosition/useSizes.ts +16 -15
  45. package/src/hooks/useNoticeTimer.ts +34 -45
  46. package/src/hooks/useNotification.tsx +71 -43
  47. package/src/hooks/useStack.ts +20 -24
  48. package/src/index.ts +28 -13
  49. package/src/interface.ts +45 -0
  50. package/vitest.config.ts +1 -3
  51. package/tests/index.spec.tsx +0 -200
@@ -1,9 +1,9 @@
1
+ import type { Component, CSSProperties, HTMLAttributes } from 'vue'
1
2
  import type { VueNode } from '@v-c/util/dist/type'
2
- import type { Component, CSSProperties, PropType } from 'vue'
3
3
  import type { ClosableType } from './hooks/useClosable'
4
4
  import type { NotificationProgressProps } from './Progress'
5
- import { classNames as clsx } from '@v-c/util'
6
- import { computed, defineComponent, shallowRef, toRef, watch } from 'vue'
5
+ import { clsx } from '@v-c/util'
6
+ import { computed, defineComponent, ref, shallowRef, watch } from 'vue'
7
7
  import useClosable from './hooks/useClosable'
8
8
  import useNoticeTimer from './hooks/useNoticeTimer'
9
9
  import DefaultProgress from './Progress'
@@ -39,7 +39,7 @@ export interface ComponentsType {
39
39
  export interface NotificationProps {
40
40
  // Style
41
41
  prefixCls: string
42
- class?: string
42
+ className?: string
43
43
  style?: CSSProperties
44
44
  classNames?: NotificationClassNames
45
45
  styles?: NotificationStyles
@@ -55,7 +55,7 @@ export interface NotificationProps {
55
55
  offset?: number
56
56
  notificationIndex?: number
57
57
  stackInThreshold?: boolean
58
- props?: Record<string, any>
58
+ props?: HTMLAttributes & Record<string, any>
59
59
 
60
60
  // Behavior
61
61
  duration?: number | false | null
@@ -65,198 +65,160 @@ export interface NotificationProps {
65
65
  pauseOnHover?: boolean
66
66
 
67
67
  // Function
68
- onClick?: (event: MouseEvent) => void
69
- onMouseEnter?: (event: MouseEvent) => void
70
- onMouseLeave?: (event: MouseEvent) => void
68
+ onClick?: (e: MouseEvent) => void
69
+ onMouseEnter?: (e: MouseEvent) => void
70
+ onMouseLeave?: (e: MouseEvent) => void
71
71
  /** @deprecated Please use `closable.onClose` instead. */
72
72
  onClose?: () => void
73
73
  }
74
74
 
75
- const Notification = defineComponent({
76
- name: 'Notification',
77
- inheritAttrs: false,
78
- props: {
79
- prefixCls: { type: String, required: true },
80
- class: { type: String, default: undefined },
81
- style: { type: Object as PropType<CSSProperties>, default: undefined },
82
- classNames: { type: Object as PropType<NotificationClassNames>, default: undefined },
83
- styles: { type: Object as PropType<NotificationStyles>, default: undefined },
84
- components: { type: Object as PropType<ComponentsType>, default: undefined },
85
-
86
- title: { type: null as any, default: undefined },
87
- description: { type: null as any, default: undefined },
88
- icon: { type: null as any, default: undefined },
89
- actions: { type: null as any, default: undefined },
90
- role: { type: String, default: undefined },
91
- closable: { type: [Boolean, Object, null] as PropType<ClosableType>, default: undefined },
92
- offset: { type: Number, default: undefined },
93
- notificationIndex: { type: Number, default: undefined },
94
- stackInThreshold: { type: Boolean, default: false },
95
- props: { type: Object as PropType<Record<string, any>>, default: undefined },
96
-
97
- duration: { type: [Number, Boolean] as PropType<number | false | null>, default: 4.5 },
98
- showProgress: { type: Boolean, default: false },
99
- times: { type: Number, default: undefined },
100
- hovering: { type: Boolean, default: false },
101
- pauseOnHover: { type: Boolean, default: true },
102
-
103
- onClick: { type: Function as PropType<(event: MouseEvent) => void>, default: undefined },
104
- onMouseEnter: { type: Function as PropType<(event: MouseEvent) => void>, default: undefined },
105
- onMouseLeave: { type: Function as PropType<(event: MouseEvent) => void>, default: undefined },
106
- onClose: { type: Function as PropType<() => void>, default: undefined },
107
- },
108
- setup(props, { attrs, expose }) {
109
- const noticeRef = shallowRef<HTMLDivElement | null>(null)
110
- expose({
111
- get nativeElement() {
112
- return noticeRef.value
113
- },
114
- })
75
+ interface NoticeStyle extends CSSProperties {
76
+ '--notification-index'?: number
77
+ '--notification-y'?: string
78
+ }
115
79
 
116
- const noticePrefixCls = computed(() => `${props.prefixCls}-notice`)
117
- const localHovering = shallowRef(false)
80
+ const Notification = defineComponent<NotificationProps>(
81
+ (props, { attrs, expose }) => {
82
+ const nodeRef = ref<HTMLDivElement | null>(null)
118
83
  const percent = shallowRef(0)
84
+ const hovering = shallowRef(false)
85
+
86
+ expose({
87
+ nativeElement: nodeRef,
88
+ })
119
89
 
120
90
  // ========================= Close ==========================
121
- const [mergedClosable, closableConfig, closeBtnAriaProps] = useClosable(
122
- toRef(props, 'closable'),
123
- )
91
+ const closableRef = computed(() => props.closable)
92
+ const [mergedClosable, closableConfig, closeBtnAriaProps] = useClosable(closableRef)
93
+
124
94
  const onInternalClose = () => {
125
95
  closableConfig.value.onClose?.()
126
96
  props.onClose?.()
127
97
  }
128
98
 
129
99
  // ======================== Duration ========================
100
+ const mergedDuration = computed(() => {
101
+ if (props.duration === undefined) {
102
+ return 4.5
103
+ }
104
+ return props.duration
105
+ })
130
106
  const [onResume, onPause] = useNoticeTimer(
131
- toRef(props, 'duration'),
107
+ mergedDuration,
132
108
  onInternalClose,
133
- (ptg) => {
134
- percent.value = ptg
109
+ (next) => {
110
+ percent.value = next
135
111
  },
136
112
  )
137
113
 
114
+ // Sync pause/resume to forced and local hover.
138
115
  watch(
139
116
  [
140
- () => props.pauseOnHover,
141
117
  () => props.hovering,
142
- localHovering,
118
+ hovering,
119
+ () => props.pauseOnHover,
143
120
  ],
144
- ([pauseOnHover, forcedHovering, hovering]) => {
121
+ () => {
122
+ const pauseOnHover = props.pauseOnHover ?? true
145
123
  if (!pauseOnHover) {
146
124
  return
147
125
  }
148
- if (forcedHovering) {
126
+ if (props.hovering) {
149
127
  onPause()
150
128
  }
151
- else if (!hovering) {
129
+ else if (!hovering.value) {
152
130
  onResume()
153
131
  }
154
132
  },
155
133
  { immediate: true },
156
134
  )
157
135
 
158
- // ========================= Hover ==========================
159
- function onInternalMouseEnter(event: MouseEvent) {
160
- localHovering.value = true
161
- if (props.pauseOnHover) {
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)) {
162
155
  onPause()
163
156
  }
164
- props.onMouseEnter?.(event)
157
+ props.onMouseEnter?.(e)
158
+ ;(props.props?.onMouseenter as any)?.(e)
165
159
  }
166
160
 
167
- function onInternalMouseLeave(event: MouseEvent) {
168
- localHovering.value = false
169
- if (props.pauseOnHover && !props.hovering) {
161
+ const onInternalMouseLeave = (e: MouseEvent) => {
162
+ hovering.value = false
163
+ const pauseOnHover = props.pauseOnHover ?? true
164
+ if (pauseOnHover && !props.hovering) {
170
165
  onResume()
171
166
  }
172
- props.onMouseLeave?.(event)
167
+ props.onMouseLeave?.(e)
168
+ ;(props.props?.onMouseleave as any)?.(e)
173
169
  }
174
170
 
175
- function onInternalCloseClick(event: MouseEvent) {
176
- event.preventDefault()
177
- event.stopPropagation()
171
+ const onInternalCloseClick = (e: MouseEvent) => {
172
+ e.preventDefault()
173
+ e.stopPropagation()
178
174
  onInternalClose()
179
175
  }
180
176
 
181
- // ======================== Position ========================
182
- let lastOffset = props.offset
183
- let lastIndex = props.notificationIndex
184
- watch(
185
- () => props.offset,
186
- (val) => {
187
- if (val !== undefined)
188
- lastOffset = val
189
- },
190
- { immediate: true },
191
- )
192
- watch(
193
- () => props.notificationIndex,
194
- (val) => {
195
- if (val !== undefined)
196
- lastIndex = val
197
- },
198
- { immediate: true },
199
- )
200
-
201
177
  return () => {
202
178
  const {
179
+ prefixCls,
180
+ className,
181
+ style,
182
+ classNames: ncs,
183
+ styles: nss,
184
+ components,
203
185
  title,
204
186
  description,
205
187
  icon,
206
188
  actions,
207
189
  role,
190
+ stackInThreshold,
191
+ props: rootProps,
208
192
  showProgress,
209
193
  duration,
210
- classNames,
211
- styles,
212
- components,
213
- class: className,
214
- style,
215
- props: rootProps,
216
- onClick,
217
194
  } = props
218
195
 
219
- const validPercent = 100 - Math.min(Math.max(percent.value * 100, 0), 100)
220
- const ProgressComponent = (components?.progress || DefaultProgress) as any
196
+ const noticePrefixCls = `${prefixCls}-notice`
221
197
 
222
198
  // ======================== Content =========================
223
- const titleNode
224
- = title !== undefined && title !== null
225
- ? (
226
- <div
227
- class={clsx(`${noticePrefixCls.value}-title`, classNames?.title)}
228
- style={styles?.title}
229
- >
230
- {title}
231
- </div>
232
- )
233
- : null
234
-
235
- const descNode
236
- = description !== undefined && description !== null
237
- ? (
238
- <div
239
- class={clsx(
240
- `${noticePrefixCls.value}-description`,
241
- classNames?.description,
242
- )}
243
- style={styles?.description}
244
- >
245
- {description}
246
- </div>
247
- )
248
- : null
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
249
214
 
250
215
  const hasTitle = titleNode !== null
251
216
  const hasDescription = descNode !== null
252
- let contentNode: VueNode = null
253
217
 
218
+ let contentNode: VueNode = null
254
219
  if (hasTitle && hasDescription) {
255
220
  contentNode = (
256
- <div
257
- class={clsx(`${noticePrefixCls.value}-section`, classNames?.section)}
258
- style={styles?.section}
259
- >
221
+ <div class={clsx(`${noticePrefixCls}-section`, ncs?.section)} style={nss?.section}>
260
222
  {titleNode}
261
223
  {descNode}
262
224
  </div>
@@ -268,14 +230,8 @@ const Notification = defineComponent({
268
230
 
269
231
  if (icon !== undefined && icon !== null) {
270
232
  contentNode = (
271
- <div
272
- class={clsx(`${noticePrefixCls.value}-wrapper`, classNames?.wrapper)}
273
- style={styles?.wrapper}
274
- >
275
- <div
276
- class={clsx(`${noticePrefixCls.value}-icon`, classNames?.icon)}
277
- style={styles?.icon}
278
- >
233
+ <div class={clsx(`${noticePrefixCls}-wrapper`, ncs?.wrapper)} style={nss?.wrapper}>
234
+ <div class={clsx(`${noticePrefixCls}-icon`, ncs?.icon)} style={nss?.icon}>
279
235
  {icon}
280
236
  </div>
281
237
  {contentNode}
@@ -285,49 +241,51 @@ const Notification = defineComponent({
285
241
 
286
242
  const actionsNode = actions
287
243
  ? (
288
- <div
289
- class={clsx(`${noticePrefixCls.value}-actions`, classNames?.actions)}
290
- style={styles?.actions}
291
- >
244
+ <div class={clsx(`${noticePrefixCls}-actions`, ncs?.actions)} style={nss?.actions}>
292
245
  {actions}
293
246
  </div>
294
247
  )
295
248
  : null
296
249
 
297
250
  // ========================= Render =========================
298
- const mergedOffset = props.offset ?? lastOffset
299
- const mergedIndex = props.notificationIndex ?? lastIndex ?? 0
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
300
256
 
301
- const mergedStyle: CSSProperties & Record<string, any> = {
257
+ const mergedStyle: NoticeStyle = {
302
258
  '--notification-index': mergedIndex,
303
- ...styles?.root,
259
+ ...nss?.root,
304
260
  ...style,
305
261
  }
306
-
307
262
  if (mergedOffset !== undefined) {
308
263
  mergedStyle['--notification-y'] = `${mergedOffset}px`
309
264
  }
310
265
 
311
- const mergedRole = role ?? rootProps?.role ?? 'alert'
266
+ const mergedRole = role ?? (rootProps as any)?.role ?? 'alert'
312
267
 
313
268
  return (
314
269
  <div
315
270
  {...rootProps}
316
- ref={noticeRef as any}
271
+ ref={nodeRef}
317
272
  role={mergedRole}
318
273
  data-notification-index={mergedIndex}
319
274
  class={clsx(
320
- noticePrefixCls.value,
275
+ noticePrefixCls,
321
276
  className,
322
277
  (attrs as any).class,
323
- classNames?.root,
278
+ ncs?.root,
324
279
  {
325
- [`${noticePrefixCls.value}-closable`]: mergedClosable.value,
326
- [`${noticePrefixCls.value}-stack-in-threshold`]: props.stackInThreshold,
280
+ [`${noticePrefixCls}-closable`]: mergedClosable.value,
281
+ [`${noticePrefixCls}-stack-in-threshold`]: stackInThreshold,
327
282
  },
328
283
  )}
329
- style={mergedStyle}
330
- onClick={onClick}
284
+ style={{
285
+ ...mergedStyle,
286
+ ...((attrs as any).style as CSSProperties | undefined),
287
+ }}
288
+ onClick={props.onClick}
331
289
  onMouseenter={onInternalMouseEnter}
332
290
  onMouseleave={onInternalMouseLeave}
333
291
  >
@@ -336,11 +294,10 @@ const Notification = defineComponent({
336
294
 
337
295
  {mergedClosable.value && (
338
296
  <button
339
- type="button"
340
- class={clsx(`${noticePrefixCls.value}-close`, classNames?.close)}
297
+ class={clsx(`${noticePrefixCls}-close`, ncs?.close)}
341
298
  aria-label="Close"
342
299
  {...closeBtnAriaProps.value}
343
- style={styles?.close}
300
+ style={nss?.close}
344
301
  onClick={onInternalCloseClick}
345
302
  >
346
303
  {closableConfig.value.closeIcon}
@@ -349,15 +306,21 @@ const Notification = defineComponent({
349
306
 
350
307
  {showProgress && typeof duration === 'number' && duration > 0 && (
351
308
  <ProgressComponent
352
- class={clsx(`${noticePrefixCls.value}-progress`, classNames?.progress)}
353
- percent={validPercent}
354
- style={styles?.progress}
309
+ {...{
310
+ className: clsx(`${noticePrefixCls}-progress`, ncs?.progress),
311
+ percent: validPercent,
312
+ style: nss?.progress,
313
+ } as NotificationProgressProps}
355
314
  />
356
315
  )}
357
316
  </div>
358
317
  )
359
318
  }
360
319
  },
361
- })
320
+ {
321
+ name: 'Notification',
322
+ inheritAttrs: false,
323
+ },
324
+ )
362
325
 
363
326
  export default Notification
@@ -1,84 +1,66 @@
1
- import type { CSSProperties, PropType, TransitionGroupProps } from 'vue'
2
- import { classNames as clsx } from '@v-c/util'
3
- import { defineComponent, h, shallowRef, TransitionGroup } from 'vue'
1
+ import type { CSSProperties } from 'vue'
2
+ import { clsx } from '@v-c/util'
3
+ import { defineComponent, ref } from 'vue'
4
4
 
5
5
  export interface ContentProps {
6
6
  listPrefixCls: string
7
7
  height: number
8
8
  topNoticeHeight?: number
9
9
  topNoticeWidth?: number
10
- class?: string
10
+ className?: string
11
11
  style?: CSSProperties
12
- motionProps?: TransitionGroupProps
13
- onAfterLeave?: () => void
14
12
  }
15
13
 
16
- const Content = defineComponent({
17
- name: 'NotificationListContent',
18
- inheritAttrs: false,
19
- props: {
20
- listPrefixCls: { type: String, required: true },
21
- height: { type: Number, required: true },
22
- topNoticeHeight: { type: Number, default: 0 },
23
- topNoticeWidth: { type: Number, default: 0 },
24
- class: { type: String, default: undefined },
25
- style: { type: Object as PropType<CSSProperties>, default: undefined },
26
- motionProps: { type: Object as PropType<TransitionGroupProps>, default: undefined },
27
- onAfterLeave: { type: Function as PropType<() => void>, default: undefined },
28
- },
29
- setup(props, { slots, expose }) {
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)
30
22
  let prevHeight = props.height
31
- const innerRef = shallowRef<HTMLElement | null>(null)
23
+
32
24
  expose({
33
- get nativeElement() {
34
- return innerRef.value
35
- },
25
+ nativeElement: contentRef,
36
26
  })
37
27
 
38
28
  return () => {
39
29
  const {
40
30
  listPrefixCls,
41
31
  height,
42
- topNoticeHeight,
43
- topNoticeWidth,
44
- class: className,
32
+ topNoticeHeight = 0,
33
+ topNoticeWidth = 0,
34
+ className,
45
35
  style,
46
- motionProps,
47
- onAfterLeave,
48
36
  } = props
49
- const heightStatus: 'increase' | 'decrease' = height < prevHeight ? 'decrease' : 'increase'
37
+
38
+ const heightStatus = height < prevHeight ? 'decrease' : 'increase'
50
39
  prevHeight = height
51
40
 
52
41
  const contentPrefixCls = `${listPrefixCls}-content`
53
- const contentStyle: CSSProperties & Record<string, any> = {
42
+ const contentStyle: ContentStyle = {
54
43
  ...style,
55
- 'height': `${height}px`,
56
- '--top-notificiation-height': `${topNoticeHeight ?? 0}px`,
57
- '--top-notificiation-width': `${topNoticeWidth ?? 0}px`,
44
+ height,
45
+ '--top-notificiation-height': `${topNoticeHeight}px`,
46
+ '--top-notificiation-width': `${topNoticeWidth}px`,
58
47
  }
59
- const contentClass = clsx(
60
- contentPrefixCls,
61
- `${contentPrefixCls}-${heightStatus}`,
62
- className,
63
- )
64
48
 
65
- return h(
66
- TransitionGroup,
67
- {
68
- tag: 'div',
69
- appear: true,
70
- ...motionProps,
71
- ref: (el: any) => {
72
- innerRef.value = (el?.$el ?? el) as HTMLElement | null
73
- },
74
- class: contentClass,
75
- style: contentStyle,
76
- onAfterLeave,
77
- },
78
- () => slots.default?.(),
49
+ return (
50
+ <div
51
+ ref={contentRef}
52
+ class={clsx(contentPrefixCls, `${contentPrefixCls}-${heightStatus}`, className)}
53
+ style={contentStyle}
54
+ >
55
+ {slots.default?.()}
56
+ </div>
79
57
  )
80
58
  }
81
59
  },
82
- })
60
+ {
61
+ name: 'NotificationListContent',
62
+ inheritAttrs: false,
63
+ },
64
+ )
83
65
 
84
66
  export default Content