@v-c/notification 2.0.0 → 2.0.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.
- package/dist/NotificationList/index.d.ts +1 -1
- package/dist/NotificationList/index.js +7 -6
- package/dist/Notifications.js +1 -1
- package/package.json +12 -2
- package/bump.config.ts +0 -6
- package/docs/context.vue +0 -34
- package/docs/hooks.vue +0 -89
- package/docs/index.less +0 -265
- package/docs/maxCount.vue +0 -24
- package/docs/motion.ts +0 -33
- package/docs/notification.stories.vue +0 -31
- package/docs/showProgress.vue +0 -34
- package/docs/stack.vue +0 -39
- package/src/Notification.tsx +0 -326
- package/src/NotificationList/Content.tsx +0 -76
- package/src/NotificationList/index.tsx +0 -322
- package/src/NotificationProvider.tsx +0 -19
- package/src/Notifications.tsx +0 -153
- package/src/Progress.tsx +0 -27
- package/src/hooks/useClosable.ts +0 -53
- package/src/hooks/useListPosition/index.ts +0 -69
- package/src/hooks/useListPosition/useSizes.ts +0 -43
- package/src/hooks/useNoticeTimer.ts +0 -85
- package/src/hooks/useNotification.tsx +0 -181
- package/src/hooks/useStack.ts +0 -36
- package/src/index.ts +0 -50
- package/src/interface.ts +0 -45
- package/tests/notification.test.tsx +0 -70
- package/tsconfig.json +0 -7
- package/vite.config.ts +0 -19
- package/vitest.config.ts +0 -11
|
@@ -1,322 +0,0 @@
|
|
|
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 { unrefElement } from '@v-c/util/dist/vueuse/unref-element'
|
|
11
|
-
import { computed, defineComponent, ref, shallowRef, toRef, TransitionGroup, watch, watchEffect } from 'vue'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Map Vue's TransitionGroup enter/leave class hooks onto the rc-motion
|
|
15
|
-
* style class names that antdv-next 6.4.0 notification styles target
|
|
16
|
-
* (-enter-start / -enter-active / -leave-start / -leave-active).
|
|
17
|
-
*
|
|
18
|
-
* The shared @v-c/util getTransitionGroupProps puts -leave-active in the
|
|
19
|
-
* leaveActiveClass, which means the notice jumps straight to opacity:0
|
|
20
|
-
* on leave without animating. Wire each phase to the correct rc-motion
|
|
21
|
-
* suffix so the opacity/transform transition runs.
|
|
22
|
-
*/
|
|
23
|
-
function buildMotionGroupProps(name: string, override?: Partial<TransitionGroupProps>): TransitionGroupProps {
|
|
24
|
-
return {
|
|
25
|
-
name,
|
|
26
|
-
appear: true,
|
|
27
|
-
// ENTER: from = opacity 0 (-enter-start / -appear-start)
|
|
28
|
-
// to = opacity 1 (-enter-active / -appear-active)
|
|
29
|
-
enterFromClass: `${name} ${name}-enter ${name}-appear ${name}-enter-start ${name}-appear-start`,
|
|
30
|
-
enterActiveClass: `${name} ${name}-enter ${name}-appear`,
|
|
31
|
-
enterToClass: `${name} ${name}-enter ${name}-appear ${name}-enter-active ${name}-appear-active`,
|
|
32
|
-
// LEAVE: from = opacity 1 (-leave-start)
|
|
33
|
-
// to = opacity 0 (-leave-active)
|
|
34
|
-
leaveFromClass: `${name} ${name}-leave ${name}-leave-start`,
|
|
35
|
-
leaveActiveClass: `${name} ${name}-leave`,
|
|
36
|
-
leaveToClass: `${name} ${name}-leave ${name}-leave-active`,
|
|
37
|
-
moveClass: `${name} ${name}-move`,
|
|
38
|
-
...override,
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
import useListPosition from '../hooks/useListPosition'
|
|
42
|
-
import useStack from '../hooks/useStack'
|
|
43
|
-
import Notification from '../Notification'
|
|
44
|
-
import { useNotificationContext } from '../NotificationProvider'
|
|
45
|
-
import Content from './Content'
|
|
46
|
-
|
|
47
|
-
export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'
|
|
48
|
-
|
|
49
|
-
export type { StackConfig }
|
|
50
|
-
|
|
51
|
-
export interface NotificationListConfig extends Omit<NotificationProps, 'prefixCls'> {
|
|
52
|
-
key: Key
|
|
53
|
-
placement?: Placement
|
|
54
|
-
times?: number
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface NotificationClassNames extends NoticeClassNames {
|
|
58
|
-
list?: string
|
|
59
|
-
listContent?: string
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export interface NotificationStyles extends NoticeStyles {
|
|
63
|
-
list?: CSSProperties
|
|
64
|
-
listContent?: CSSProperties
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface NotificationListProps {
|
|
68
|
-
configList?: NotificationListConfig[]
|
|
69
|
-
prefixCls?: string
|
|
70
|
-
placement: Placement
|
|
71
|
-
pauseOnHover?: boolean
|
|
72
|
-
classNames?: NotificationClassNames
|
|
73
|
-
styles?: NotificationStyles
|
|
74
|
-
components?: ComponentsType
|
|
75
|
-
stack?: StackConfig
|
|
76
|
-
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
77
|
-
className?: string
|
|
78
|
-
style?: CSSProperties
|
|
79
|
-
onNoticeClose?: (key: Key) => void
|
|
80
|
-
onAllRemoved?: (placement: Placement) => void
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const noticeSlotKeys: (keyof NoticeClassNames)[] = [
|
|
84
|
-
'wrapper',
|
|
85
|
-
'root',
|
|
86
|
-
'icon',
|
|
87
|
-
'section',
|
|
88
|
-
'title',
|
|
89
|
-
'description',
|
|
90
|
-
'actions',
|
|
91
|
-
'close',
|
|
92
|
-
'progress',
|
|
93
|
-
]
|
|
94
|
-
|
|
95
|
-
function fillClassNames(
|
|
96
|
-
list: (NotificationClassNames | undefined)[],
|
|
97
|
-
): NotificationClassNames {
|
|
98
|
-
return noticeSlotKeys.reduce<NotificationClassNames>((merged, key) => {
|
|
99
|
-
merged[key] = clsx(...list.map(item => item?.[key]))
|
|
100
|
-
return merged
|
|
101
|
-
}, {})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function fillStyles(
|
|
105
|
-
list: (NotificationStyles | undefined)[],
|
|
106
|
-
): NotificationStyles {
|
|
107
|
-
return noticeSlotKeys.reduce<NotificationStyles>((merged, key) => {
|
|
108
|
-
merged[key] = Object.assign({}, ...list.map(item => item?.[key]))
|
|
109
|
-
return merged
|
|
110
|
-
}, {})
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function getIndex(keys: { key: Key }[], key: Key): number | undefined {
|
|
114
|
-
const strKey = String(key)
|
|
115
|
-
const index = keys.findIndex(item => String(item.key) === strKey)
|
|
116
|
-
if (index === -1) {
|
|
117
|
-
return undefined
|
|
118
|
-
}
|
|
119
|
-
return keys.length - index - 1
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const NotificationList = defineComponent<NotificationListProps>(
|
|
123
|
-
(props, { attrs }) => {
|
|
124
|
-
const ctx = useNotificationContext()
|
|
125
|
-
|
|
126
|
-
const configList = computed(() => props.configList ?? [])
|
|
127
|
-
const keys = computed(() =>
|
|
128
|
-
configList.value.map(config => ({ ...config, key: String(config.key) as Key })),
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
// ====================== Stack State =======================
|
|
132
|
-
const stackConfig = toRef(props, 'stack')
|
|
133
|
-
const [stackEnabled, stackParams] = useStack(stackConfig)
|
|
134
|
-
const listHovering = shallowRef(false)
|
|
135
|
-
const expanded = computed(() =>
|
|
136
|
-
stackEnabled.value && (listHovering.value || keys.value.length <= (stackParams.threshold?.value ?? 0)),
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
const stackPosition = computed(() => {
|
|
140
|
-
if (!stackEnabled.value || expanded.value) {
|
|
141
|
-
return undefined
|
|
142
|
-
}
|
|
143
|
-
return {
|
|
144
|
-
offset: stackParams.offset?.value,
|
|
145
|
-
threshold: stackParams.threshold?.value,
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
// ====================== List Measure ======================
|
|
150
|
-
const gap = ref(0)
|
|
151
|
-
const contentRef = ref<{ nativeElement: { value: HTMLDivElement | null } } | null>(null)
|
|
152
|
-
const [position, setNodeSize] = useListPosition(keys as any, stackPosition as any, gap)
|
|
153
|
-
|
|
154
|
-
const hasConfigList = computed(() => !!configList.value.length)
|
|
155
|
-
watchEffect(() => {
|
|
156
|
-
if (!hasConfigList.value) {
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
const listNode = unrefElement<HTMLDivElement>(contentRef.value?.nativeElement as any)
|
|
160
|
-
if (!listNode) {
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
const { gap: cssGap, rowGap } = window.getComputedStyle(listNode)
|
|
164
|
-
const nextGap = Number.parseFloat(rowGap || cssGap) || 0
|
|
165
|
-
if (gap.value !== nextGap) {
|
|
166
|
-
gap.value = nextGap
|
|
167
|
-
}
|
|
168
|
-
}, { flush: 'post' })
|
|
169
|
-
|
|
170
|
-
// Notify when list becomes empty (after motion finished).
|
|
171
|
-
const checkAllClosed = () => {
|
|
172
|
-
if (configList.value.length === 0) {
|
|
173
|
-
props.onAllRemoved?.(props.placement)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Compute motion props per placement.
|
|
178
|
-
const placementMotion = computed(() => {
|
|
179
|
-
if (typeof props.motion === 'function') {
|
|
180
|
-
return props.placement ? props.motion(props.placement) : undefined
|
|
181
|
-
}
|
|
182
|
-
return props.motion
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
// Cleanup node sizes when items leave permanently.
|
|
186
|
-
watch(keys, (next, prev) => {
|
|
187
|
-
if (!prev) {
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
const nextKeySet = new Set(next.map(item => String(item.key)))
|
|
191
|
-
prev.forEach((item) => {
|
|
192
|
-
const key = String(item.key)
|
|
193
|
-
if (!nextKeySet.has(key)) {
|
|
194
|
-
setNodeSize(key, null)
|
|
195
|
-
}
|
|
196
|
-
})
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
return () => {
|
|
200
|
-
const {
|
|
201
|
-
prefixCls = 'vc-notification',
|
|
202
|
-
pauseOnHover,
|
|
203
|
-
classNames: ncs,
|
|
204
|
-
styles: nss,
|
|
205
|
-
components,
|
|
206
|
-
placement,
|
|
207
|
-
className,
|
|
208
|
-
style,
|
|
209
|
-
onNoticeClose,
|
|
210
|
-
} = props
|
|
211
|
-
|
|
212
|
-
const listPrefixCls = `${prefixCls}-list`
|
|
213
|
-
const positionResult = position.value
|
|
214
|
-
|
|
215
|
-
let motionGroupProps: TransitionGroupProps = {}
|
|
216
|
-
if (placementMotion.value?.name) {
|
|
217
|
-
motionGroupProps = buildMotionGroupProps(placementMotion.value.name, placementMotion.value)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const renderItems = () =>
|
|
221
|
-
keys.value.map((config) => {
|
|
222
|
-
const {
|
|
223
|
-
key,
|
|
224
|
-
placement: _itemPlacement,
|
|
225
|
-
classNames: configClassNames,
|
|
226
|
-
styles: configStyles,
|
|
227
|
-
className: configClassName,
|
|
228
|
-
style: configStyle,
|
|
229
|
-
// Extract onClose so the spread below does not also bind it.
|
|
230
|
-
// Vue would otherwise merge our `onClose={...}` and the spread's
|
|
231
|
-
// `onClose` into an Array, breaking `props.onClose?.()` in the
|
|
232
|
-
// notice itself.
|
|
233
|
-
onClose: configOnClose,
|
|
234
|
-
...notificationConfig
|
|
235
|
-
} = config
|
|
236
|
-
const strKey = String(key)
|
|
237
|
-
const notificationIndex = getIndex(keys.value, key)
|
|
238
|
-
const stackInThreshold
|
|
239
|
-
= stackEnabled.value && notificationIndex !== undefined && notificationIndex < (stackParams.threshold?.value ?? 0)
|
|
240
|
-
|
|
241
|
-
return (
|
|
242
|
-
<Notification
|
|
243
|
-
key={strKey}
|
|
244
|
-
{...notificationConfig}
|
|
245
|
-
ref={(el: any) => {
|
|
246
|
-
const node = unrefElement<HTMLDivElement>(el?.nativeElement as any)
|
|
247
|
-
setNodeSize(strKey, node ?? null)
|
|
248
|
-
}}
|
|
249
|
-
prefixCls={prefixCls}
|
|
250
|
-
class={clsx((ctx.value as any)?.classNames?.notice, configClassName)}
|
|
251
|
-
style={configStyle}
|
|
252
|
-
classNames={fillClassNames([ncs, configClassNames])}
|
|
253
|
-
styles={fillStyles([nss, configStyles])}
|
|
254
|
-
components={{
|
|
255
|
-
...components,
|
|
256
|
-
...(config as NotificationListConfig).components,
|
|
257
|
-
}}
|
|
258
|
-
hovering={stackEnabled.value && listHovering.value}
|
|
259
|
-
pauseOnHover={config.pauseOnHover ?? pauseOnHover}
|
|
260
|
-
offset={positionResult.notificationPosition.get(strKey)}
|
|
261
|
-
notificationIndex={notificationIndex}
|
|
262
|
-
stackInThreshold={stackInThreshold}
|
|
263
|
-
onClose={() => {
|
|
264
|
-
configOnClose?.()
|
|
265
|
-
onNoticeClose?.(key)
|
|
266
|
-
}}
|
|
267
|
-
/>
|
|
268
|
-
)
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
return (
|
|
272
|
-
<div
|
|
273
|
-
class={clsx(
|
|
274
|
-
prefixCls,
|
|
275
|
-
listPrefixCls,
|
|
276
|
-
`${prefixCls}-${placement}`,
|
|
277
|
-
(ctx.value as any)?.classNames?.list,
|
|
278
|
-
className,
|
|
279
|
-
ncs?.list,
|
|
280
|
-
(attrs as any).class,
|
|
281
|
-
{
|
|
282
|
-
[`${prefixCls}-stack`]: stackEnabled.value,
|
|
283
|
-
[`${prefixCls}-stack-expanded`]: expanded.value,
|
|
284
|
-
[`${listPrefixCls}-hovered`]: listHovering.value,
|
|
285
|
-
},
|
|
286
|
-
)}
|
|
287
|
-
onMouseenter={() => {
|
|
288
|
-
listHovering.value = true
|
|
289
|
-
}}
|
|
290
|
-
onMouseleave={() => {
|
|
291
|
-
listHovering.value = false
|
|
292
|
-
}}
|
|
293
|
-
style={{ ...nss?.list, ...style, ...((attrs as any).style ?? {}) } as CSSProperties}
|
|
294
|
-
>
|
|
295
|
-
<Content
|
|
296
|
-
ref={contentRef as any}
|
|
297
|
-
listPrefixCls={listPrefixCls}
|
|
298
|
-
height={positionResult.totalHeight}
|
|
299
|
-
topNoticeHeight={positionResult.topNoticeHeight}
|
|
300
|
-
topNoticeWidth={positionResult.topNoticeWidth}
|
|
301
|
-
className={ncs?.listContent}
|
|
302
|
-
style={nss?.listContent}
|
|
303
|
-
>
|
|
304
|
-
<TransitionGroup
|
|
305
|
-
appear
|
|
306
|
-
{...motionGroupProps}
|
|
307
|
-
onAfterLeave={checkAllClosed}
|
|
308
|
-
>
|
|
309
|
-
{renderItems()}
|
|
310
|
-
</TransitionGroup>
|
|
311
|
-
</Content>
|
|
312
|
-
</div>
|
|
313
|
-
)
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
name: 'NotificationList',
|
|
318
|
-
inheritAttrs: false,
|
|
319
|
-
},
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
export default NotificationList
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
-
import { inject, provide, ref } from 'vue'
|
|
3
|
-
|
|
4
|
-
export interface NotificationContextProps {
|
|
5
|
-
classNames?: {
|
|
6
|
-
notice?: string
|
|
7
|
-
list?: string
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
export const NotificationContext: InjectionKey<Ref<NotificationContextProps>> = Symbol('NotificationContext')
|
|
11
|
-
|
|
12
|
-
export function useNotificationProvider(props: Ref<NotificationContextProps>) {
|
|
13
|
-
provide(NotificationContext, props)
|
|
14
|
-
return props
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useNotificationContext() {
|
|
18
|
-
return inject(NotificationContext, ref({}))
|
|
19
|
-
}
|
package/src/Notifications.tsx
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import type { CSSProperties, TransitionGroupProps } from 'vue'
|
|
2
|
-
import type { VueNode } from '@v-c/util/dist/type'
|
|
3
|
-
import type { InnerOpenConfig, Key, NotificationListConfig, Placement, Placements, StackConfig } from './interface'
|
|
4
|
-
import type { ComponentsType } from './Notification'
|
|
5
|
-
import { defineComponent, shallowRef, Teleport, watch } from 'vue'
|
|
6
|
-
import NotificationList, {
|
|
7
|
-
type NotificationClassNames,
|
|
8
|
-
type NotificationStyles,
|
|
9
|
-
} from './NotificationList'
|
|
10
|
-
|
|
11
|
-
export interface NotificationsProps {
|
|
12
|
-
prefixCls?: string
|
|
13
|
-
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
14
|
-
container?: HTMLElement | ShadowRoot
|
|
15
|
-
maxCount?: number
|
|
16
|
-
pauseOnHover?: boolean
|
|
17
|
-
classNames?: NotificationClassNames
|
|
18
|
-
styles?: NotificationStyles
|
|
19
|
-
components?: ComponentsType
|
|
20
|
-
className?: (placement: Placement) => string
|
|
21
|
-
style?: (placement: Placement) => CSSProperties
|
|
22
|
-
onAllRemoved?: VoidFunction
|
|
23
|
-
stack?: StackConfig
|
|
24
|
-
renderNotifications?: (
|
|
25
|
-
node: VueNode,
|
|
26
|
-
info: { prefixCls: string, key: Key },
|
|
27
|
-
) => VueNode
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface NotificationsRef {
|
|
31
|
-
open: (config: NotificationListConfig) => void
|
|
32
|
-
close: (key: Key) => void
|
|
33
|
-
destroy: () => void
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const defaults = {
|
|
37
|
-
prefixCls: 'vc-notification',
|
|
38
|
-
} as NotificationsProps
|
|
39
|
-
|
|
40
|
-
const Notifications = defineComponent<NotificationsProps>(
|
|
41
|
-
(props = defaults, { expose }) => {
|
|
42
|
-
const configList = shallowRef<NotificationListConfig[]>([])
|
|
43
|
-
|
|
44
|
-
const onNoticeClose = (key: Key) => {
|
|
45
|
-
configList.value = configList.value.filter(item => item.key !== key)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
expose({
|
|
49
|
-
open: (config: NotificationListConfig) => {
|
|
50
|
-
const list = configList.value
|
|
51
|
-
let clone = [...list]
|
|
52
|
-
const index = clone.findIndex(item => item.key === config.key)
|
|
53
|
-
const innerConfig: InnerOpenConfig = { ...config }
|
|
54
|
-
if (index >= 0) {
|
|
55
|
-
innerConfig.times = ((list[index] as InnerOpenConfig)?.times ?? 0) + 1
|
|
56
|
-
clone[index] = innerConfig
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
innerConfig.times = 0
|
|
60
|
-
clone.push(innerConfig)
|
|
61
|
-
}
|
|
62
|
-
const maxCount = props.maxCount ?? 0
|
|
63
|
-
if (maxCount > 0 && clone.length > maxCount) {
|
|
64
|
-
clone = clone.slice(-maxCount)
|
|
65
|
-
}
|
|
66
|
-
configList.value = clone
|
|
67
|
-
},
|
|
68
|
-
close: onNoticeClose,
|
|
69
|
-
destroy: () => {
|
|
70
|
-
configList.value = []
|
|
71
|
-
},
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
const placements = shallowRef<Placements>({})
|
|
75
|
-
|
|
76
|
-
watch(configList, () => {
|
|
77
|
-
const next: Placements = {}
|
|
78
|
-
configList.value.forEach((config) => {
|
|
79
|
-
const placement = (config.placement ?? 'topRight') as Placement
|
|
80
|
-
next[placement] = next[placement] || []
|
|
81
|
-
next[placement]!.push(config)
|
|
82
|
-
})
|
|
83
|
-
// Keep existing placements so empty lists can finish leave motion.
|
|
84
|
-
Object.keys(placements.value).forEach((placement) => {
|
|
85
|
-
next[placement as Placement] = next[placement as Placement] || []
|
|
86
|
-
})
|
|
87
|
-
placements.value = next
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
const onAllNoticeRemoved = (placement: Placement) => {
|
|
91
|
-
const clone = { ...placements.value }
|
|
92
|
-
const list = clone[placement] || []
|
|
93
|
-
if (!list.length) {
|
|
94
|
-
delete clone[placement]
|
|
95
|
-
}
|
|
96
|
-
placements.value = clone
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const emptyRef = shallowRef(false)
|
|
100
|
-
watch(placements, () => {
|
|
101
|
-
if (Object.keys(placements.value).length > 0) {
|
|
102
|
-
emptyRef.value = true
|
|
103
|
-
}
|
|
104
|
-
else if (emptyRef.value) {
|
|
105
|
-
props?.onAllRemoved?.()
|
|
106
|
-
emptyRef.value = false
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
return () => {
|
|
111
|
-
const { container } = props
|
|
112
|
-
const prefixCls = props.prefixCls ?? defaults.prefixCls!
|
|
113
|
-
if (!container) {
|
|
114
|
-
return null
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<Teleport to={container}>
|
|
119
|
-
{Object.keys(placements.value).map((rawPlacement) => {
|
|
120
|
-
const placement = rawPlacement as Placement
|
|
121
|
-
const placementConfigList = placements.value[placement]
|
|
122
|
-
const list = (
|
|
123
|
-
<NotificationList
|
|
124
|
-
key={placement}
|
|
125
|
-
configList={placementConfigList}
|
|
126
|
-
placement={placement}
|
|
127
|
-
prefixCls={prefixCls}
|
|
128
|
-
pauseOnHover={props.pauseOnHover}
|
|
129
|
-
classNames={props.classNames}
|
|
130
|
-
styles={props.styles}
|
|
131
|
-
components={props.components}
|
|
132
|
-
className={props.className?.(placement)}
|
|
133
|
-
style={props.style?.(placement)}
|
|
134
|
-
motion={props.motion}
|
|
135
|
-
stack={props.stack}
|
|
136
|
-
onAllRemoved={onAllNoticeRemoved}
|
|
137
|
-
onNoticeClose={onNoticeClose}
|
|
138
|
-
/>
|
|
139
|
-
)
|
|
140
|
-
return props.renderNotifications
|
|
141
|
-
? props.renderNotifications(list, { prefixCls, key: placement })
|
|
142
|
-
: list
|
|
143
|
-
})}
|
|
144
|
-
</Teleport>
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
name: 'Notifications',
|
|
150
|
-
},
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
export default Notifications
|
package/src/Progress.tsx
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { CSSProperties } from 'vue'
|
|
2
|
-
import { defineComponent } from 'vue'
|
|
3
|
-
|
|
4
|
-
export interface NotificationProgressProps {
|
|
5
|
-
className?: string
|
|
6
|
-
style?: CSSProperties
|
|
7
|
-
percent: number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const Progress = defineComponent<NotificationProgressProps>(
|
|
11
|
-
(props) => {
|
|
12
|
-
return () => (
|
|
13
|
-
<progress
|
|
14
|
-
class={props.className}
|
|
15
|
-
max="100"
|
|
16
|
-
value={props.percent}
|
|
17
|
-
style={props.style}
|
|
18
|
-
/>
|
|
19
|
-
)
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
name: 'NotificationProgress',
|
|
23
|
-
inheritAttrs: false,
|
|
24
|
-
},
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
export default Progress
|
package/src/hooks/useClosable.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { AriaAttributes, ComputedRef } from 'vue'
|
|
2
|
-
import type { VueNode } from '@v-c/util/dist/type'
|
|
3
|
-
import pickAttrs from '@v-c/util/dist/pickAttrs'
|
|
4
|
-
import { computed } from 'vue'
|
|
5
|
-
|
|
6
|
-
export type ClosableConfig = {
|
|
7
|
-
closeIcon?: VueNode
|
|
8
|
-
disabled?: boolean
|
|
9
|
-
onClose?: VoidFunction
|
|
10
|
-
} & AriaAttributes & Record<`data-${string}`, unknown>
|
|
11
|
-
|
|
12
|
-
export type ClosableType = boolean | ClosableConfig | null | undefined
|
|
13
|
-
|
|
14
|
-
export interface ParsedClosableConfig extends ClosableConfig {
|
|
15
|
-
closeIcon: VueNode
|
|
16
|
-
disabled: boolean
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Normalizes the closable option into a boolean flag, parsed config, and
|
|
21
|
-
* aria props for the close button. Mirrors rc-notification@2.0 useClosable.
|
|
22
|
-
*/
|
|
23
|
-
export default function useClosable(
|
|
24
|
-
closable: ComputedRef<ClosableType>,
|
|
25
|
-
): [ComputedRef<boolean>, ComputedRef<ParsedClosableConfig>, ComputedRef<Record<string, unknown>>] {
|
|
26
|
-
const closableObj = computed<ClosableConfig>(() => {
|
|
27
|
-
const value = closable.value
|
|
28
|
-
if (value === false) {
|
|
29
|
-
return { closeIcon: null, disabled: true }
|
|
30
|
-
}
|
|
31
|
-
if (typeof value === 'object' && value !== null) {
|
|
32
|
-
return value
|
|
33
|
-
}
|
|
34
|
-
return {}
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const closableConfig = computed<ParsedClosableConfig>(() => {
|
|
38
|
-
const obj = closableObj.value
|
|
39
|
-
return {
|
|
40
|
-
...obj,
|
|
41
|
-
closeIcon: 'closeIcon' in obj ? obj.closeIcon : '×',
|
|
42
|
-
disabled: obj.disabled ?? false,
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const closableAriaProps = computed(() => pickAttrs(closableConfig.value, true))
|
|
47
|
-
|
|
48
|
-
return [
|
|
49
|
-
computed(() => !!closable.value),
|
|
50
|
-
closableConfig,
|
|
51
|
-
closableAriaProps,
|
|
52
|
-
]
|
|
53
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { ComputedRef, Ref } from 'vue'
|
|
2
|
-
import type { Key } from '../../interface'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
|
-
import useSizes from './useSizes'
|
|
5
|
-
|
|
6
|
-
export interface ListPositionStackConfig {
|
|
7
|
-
threshold?: number
|
|
8
|
-
offset?: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Calculates each notification's position and the full list height.
|
|
13
|
-
* Mirrors rc-notification@2.0 useListPosition.
|
|
14
|
-
*/
|
|
15
|
-
export default function useListPosition(
|
|
16
|
-
configList: ComputedRef<{ key: Key }[]>,
|
|
17
|
-
stack: ComputedRef<ListPositionStackConfig | undefined>,
|
|
18
|
-
gap: Ref<number>,
|
|
19
|
-
) {
|
|
20
|
-
const [sizeMap, setNodeSize] = useSizes()
|
|
21
|
-
|
|
22
|
-
const result = computed(() => {
|
|
23
|
-
let offsetY = 0
|
|
24
|
-
let nextTotalHeight = 0
|
|
25
|
-
const stackParams = stack.value
|
|
26
|
-
const stackThreshold = stackParams?.threshold ?? 0
|
|
27
|
-
const stackOffset = stackParams?.offset ?? 0
|
|
28
|
-
const notificationPosition = new Map<string, number>()
|
|
29
|
-
let topNoticeHeight: number | undefined
|
|
30
|
-
let topNoticeWidth: number | undefined
|
|
31
|
-
|
|
32
|
-
configList.value
|
|
33
|
-
.slice()
|
|
34
|
-
.reverse()
|
|
35
|
-
.forEach((config, index) => {
|
|
36
|
-
// Walk from newest to oldest so each notice can be positioned after the ones below it.
|
|
37
|
-
const key = String(config.key)
|
|
38
|
-
const height = sizeMap.value[key]?.height ?? 0
|
|
39
|
-
const y = stackParams && index > 0 ? offsetY + stackOffset - height : offsetY
|
|
40
|
-
|
|
41
|
-
notificationPosition.set(key, y)
|
|
42
|
-
|
|
43
|
-
if (index === 0) {
|
|
44
|
-
topNoticeHeight = height
|
|
45
|
-
topNoticeWidth = sizeMap.value[key]?.width ?? 0
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!stackParams || index < stackThreshold) {
|
|
49
|
-
nextTotalHeight = Math.max(nextTotalHeight, y + height)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (stackParams) {
|
|
53
|
-
offsetY = y + height
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
offsetY += height + gap.value
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
notificationPosition,
|
|
62
|
-
totalHeight: nextTotalHeight,
|
|
63
|
-
topNoticeHeight,
|
|
64
|
-
topNoticeWidth,
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
return [result, setNodeSize] as const
|
|
69
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { shallowRef } from 'vue'
|
|
2
|
-
|
|
3
|
-
export interface NodeSize {
|
|
4
|
-
width: number
|
|
5
|
-
height: number
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type NodeSizeMap = Record<string, NodeSize>
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Stores measured node sizes by key and exposes a callback to update them.
|
|
12
|
-
* Mirrors rc-notification@2.0 useSizes.
|
|
13
|
-
*/
|
|
14
|
-
export default function useSizes() {
|
|
15
|
-
const sizeMap = shallowRef<NodeSizeMap>({})
|
|
16
|
-
|
|
17
|
-
const setNodeSize = (key: string, node: HTMLDivElement | null) => {
|
|
18
|
-
if (!node) {
|
|
19
|
-
if (!(key in sizeMap.value)) {
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
const next = { ...sizeMap.value }
|
|
23
|
-
delete next[key]
|
|
24
|
-
sizeMap.value = next
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const nextSize: NodeSize = {
|
|
29
|
-
width: node.offsetWidth,
|
|
30
|
-
height: node.offsetHeight,
|
|
31
|
-
}
|
|
32
|
-
const prev = sizeMap.value[key]
|
|
33
|
-
if (prev && prev.width === nextSize.width && prev.height === nextSize.height) {
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
sizeMap.value = {
|
|
37
|
-
...sizeMap.value,
|
|
38
|
-
[key]: nextSize,
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return [sizeMap, setNodeSize] as const
|
|
43
|
-
}
|