@v-c/notification 2.0.0 → 2.0.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.
@@ -1,326 +0,0 @@
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
@@ -1,76 +0,0 @@
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
- // Force height to a string so Vue's style patcher always re-applies the
43
- // unit. Passing a plain number (e.g. 0 → 216) was getting silently
44
- // skipped during patch — the CSS variables on the same vnode style
45
- // were applied, but `height: 216` was not patched onto the DOM until
46
- // a manual `inst.update()` was invoked.
47
- const contentStyle: ContentStyle = {
48
- ...style,
49
- height: `${height}px`,
50
- '--top-notificiation-height': `${topNoticeHeight}px`,
51
- '--top-notificiation-width': `${topNoticeWidth}px`,
52
- }
53
-
54
- return (
55
- <div
56
- ref={contentRef}
57
- class={clsx(contentPrefixCls, `${contentPrefixCls}-${heightStatus}`, className)}
58
- style={contentStyle}
59
- >
60
- {slots.default?.()}
61
- </div>
62
- )
63
- }
64
- },
65
- {
66
- name: 'NotificationListContent',
67
- inheritAttrs: false,
68
- // Declare runtime props so Vue tracks reactive reads inside the render
69
- // closure. Without this, `props.height` in `const { height } = props`
70
- // does not subscribe to updates, so the rendered `height: 0px` stays
71
- // stale even after position recomputes with measured node sizes.
72
- props: ['listPrefixCls', 'height', 'topNoticeHeight', 'topNoticeWidth', 'className', 'style'] as any,
73
- },
74
- )
75
-
76
- export default Content