@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/src/Notifications.tsx
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import type { VueNode } from '@v-c/util/dist/type'
|
|
2
1
|
import type { CSSProperties, TransitionGroupProps } from 'vue'
|
|
3
|
-
import type {
|
|
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'
|
|
4
5
|
import { defineComponent, shallowRef, Teleport, watch } from 'vue'
|
|
5
|
-
import
|
|
6
|
+
import NotificationList, {
|
|
7
|
+
type NotificationClassNames,
|
|
8
|
+
type NotificationStyles,
|
|
9
|
+
} from './NotificationList'
|
|
6
10
|
|
|
7
11
|
export interface NotificationsProps {
|
|
8
12
|
prefixCls?: string
|
|
9
13
|
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
10
14
|
container?: HTMLElement | ShadowRoot
|
|
11
15
|
maxCount?: number
|
|
16
|
+
pauseOnHover?: boolean
|
|
17
|
+
classNames?: NotificationClassNames
|
|
18
|
+
styles?: NotificationStyles
|
|
19
|
+
components?: ComponentsType
|
|
12
20
|
className?: (placement: Placement) => string
|
|
13
21
|
style?: (placement: Placement) => CSSProperties
|
|
14
22
|
onAllRemoved?: VoidFunction
|
|
@@ -20,7 +28,7 @@ export interface NotificationsProps {
|
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
export interface NotificationsRef {
|
|
23
|
-
open: (config:
|
|
31
|
+
open: (config: NotificationListConfig) => void
|
|
24
32
|
close: (key: Key) => void
|
|
25
33
|
destroy: () => void
|
|
26
34
|
}
|
|
@@ -31,30 +39,25 @@ const defaults = {
|
|
|
31
39
|
|
|
32
40
|
const Notifications = defineComponent<NotificationsProps>(
|
|
33
41
|
(props = defaults, { expose }) => {
|
|
34
|
-
const configList = shallowRef<
|
|
35
|
-
|
|
42
|
+
const configList = shallowRef<NotificationListConfig[]>([])
|
|
43
|
+
|
|
36
44
|
const onNoticeClose = (key: Key) => {
|
|
37
|
-
// Trigger close event
|
|
38
45
|
const config = configList.value.find(item => item.key === key)
|
|
39
46
|
const closable = config?.closable
|
|
40
|
-
const closableObj = closable && typeof closable === 'object' ? closable :
|
|
41
|
-
closableObj
|
|
47
|
+
const closableObj = closable && typeof closable === 'object' ? closable : null
|
|
48
|
+
closableObj?.onClose?.()
|
|
42
49
|
config?.onClose?.()
|
|
43
50
|
configList.value = configList.value.filter(item => item.key !== key)
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
// ========================= Refs =========================
|
|
47
53
|
expose({
|
|
48
|
-
open: (config:
|
|
54
|
+
open: (config: NotificationListConfig) => {
|
|
49
55
|
const list = configList.value
|
|
50
|
-
let clone = [...
|
|
51
|
-
// Replace if exist
|
|
56
|
+
let clone = [...list]
|
|
52
57
|
const index = clone.findIndex(item => item.key === config.key)
|
|
53
|
-
const innerConfig: InnerOpenConfig = {
|
|
54
|
-
...config,
|
|
55
|
-
}
|
|
58
|
+
const innerConfig: InnerOpenConfig = { ...config }
|
|
56
59
|
if (index >= 0) {
|
|
57
|
-
innerConfig.times = ((list[index] as InnerOpenConfig)?.times
|
|
60
|
+
innerConfig.times = ((list[index] as InnerOpenConfig)?.times ?? 0) + 1
|
|
58
61
|
clone[index] = innerConfig
|
|
59
62
|
}
|
|
60
63
|
else {
|
|
@@ -73,31 +76,22 @@ const Notifications = defineComponent<NotificationsProps>(
|
|
|
73
76
|
},
|
|
74
77
|
})
|
|
75
78
|
|
|
76
|
-
// ====================== Placements ======================
|
|
77
|
-
|
|
78
79
|
const placements = shallowRef<Placements>({})
|
|
79
80
|
|
|
80
|
-
watch(
|
|
81
|
-
|
|
82
|
-
() => {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const placement = _placement as Placement
|
|
94
|
-
nextPlacements[placement] = nextPlacements[placement] || []
|
|
95
|
-
})
|
|
96
|
-
placements.value = nextPlacements
|
|
97
|
-
},
|
|
98
|
-
)
|
|
81
|
+
watch(configList, () => {
|
|
82
|
+
const next: Placements = {}
|
|
83
|
+
configList.value.forEach((config) => {
|
|
84
|
+
const placement = (config.placement ?? 'topRight') as Placement
|
|
85
|
+
next[placement] = next[placement] || []
|
|
86
|
+
next[placement]!.push(config)
|
|
87
|
+
})
|
|
88
|
+
// Keep existing placements so empty lists can finish leave motion.
|
|
89
|
+
Object.keys(placements.value).forEach((placement) => {
|
|
90
|
+
next[placement as Placement] = next[placement as Placement] || []
|
|
91
|
+
})
|
|
92
|
+
placements.value = next
|
|
93
|
+
})
|
|
99
94
|
|
|
100
|
-
// Clean up container if all notices fade out
|
|
101
95
|
const onAllNoticeRemoved = (placement: Placement) => {
|
|
102
96
|
const clone = { ...placements.value }
|
|
103
97
|
const list = clone[placement] || []
|
|
@@ -107,50 +101,50 @@ const Notifications = defineComponent<NotificationsProps>(
|
|
|
107
101
|
placements.value = clone
|
|
108
102
|
}
|
|
109
103
|
|
|
110
|
-
// Effect tell that placements is empty now
|
|
111
104
|
const emptyRef = shallowRef(false)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
props?.onAllRemoved?.()
|
|
122
|
-
emptyRef.value = false
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
)
|
|
105
|
+
watch(placements, () => {
|
|
106
|
+
if (Object.keys(placements.value).length > 0) {
|
|
107
|
+
emptyRef.value = true
|
|
108
|
+
}
|
|
109
|
+
else if (emptyRef.value) {
|
|
110
|
+
props?.onAllRemoved?.()
|
|
111
|
+
emptyRef.value = false
|
|
112
|
+
}
|
|
113
|
+
})
|
|
126
114
|
|
|
127
115
|
return () => {
|
|
128
116
|
const { container } = props
|
|
129
|
-
const prefixCls = props.prefixCls ?? defaults.prefixCls
|
|
130
|
-
// ======================== Render ========================
|
|
117
|
+
const prefixCls = props.prefixCls ?? defaults.prefixCls!
|
|
131
118
|
if (!container) {
|
|
132
119
|
return null
|
|
133
120
|
}
|
|
134
121
|
|
|
135
122
|
return (
|
|
136
123
|
<Teleport to={container}>
|
|
137
|
-
{Object.keys(placements.value).map((
|
|
138
|
-
const
|
|
124
|
+
{Object.keys(placements.value).map((rawPlacement) => {
|
|
125
|
+
const placement = rawPlacement as Placement
|
|
126
|
+
const placementConfigList = placements.value[placement]
|
|
139
127
|
const list = (
|
|
140
|
-
<
|
|
128
|
+
<NotificationList
|
|
141
129
|
key={placement}
|
|
142
130
|
configList={placementConfigList}
|
|
143
|
-
placement={placement
|
|
131
|
+
placement={placement}
|
|
144
132
|
prefixCls={prefixCls}
|
|
145
|
-
|
|
146
|
-
|
|
133
|
+
pauseOnHover={props.pauseOnHover}
|
|
134
|
+
classNames={props.classNames}
|
|
135
|
+
styles={props.styles}
|
|
136
|
+
components={props.components}
|
|
137
|
+
className={props.className?.(placement)}
|
|
138
|
+
style={props.style?.(placement)}
|
|
147
139
|
motion={props.motion}
|
|
148
140
|
stack={props.stack}
|
|
149
|
-
|
|
141
|
+
onAllRemoved={onAllNoticeRemoved}
|
|
150
142
|
onNoticeClose={onNoticeClose}
|
|
151
143
|
/>
|
|
152
144
|
)
|
|
153
|
-
return props.renderNotifications
|
|
145
|
+
return props.renderNotifications
|
|
146
|
+
? props.renderNotifications(list, { prefixCls, key: placement })
|
|
147
|
+
: list
|
|
154
148
|
})}
|
|
155
149
|
</Teleport>
|
|
156
150
|
)
|
package/src/Progress.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ComputedRef } from 'vue'
|
|
2
|
+
import { onScopeDispose, shallowRef, watch } from 'vue'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Runs the notice auto-close timer and reports progress updates via rAF.
|
|
6
|
+
* Returns controls to pause and resume the timer. Mirrors rc-notification@2.0
|
|
7
|
+
* useNoticeTimer.
|
|
8
|
+
*/
|
|
9
|
+
export default function useNoticeTimer(
|
|
10
|
+
duration: ComputedRef<number | false | null | undefined>,
|
|
11
|
+
onClose: () => void,
|
|
12
|
+
onUpdate: (percent: number) => void,
|
|
13
|
+
): [() => void, () => void] {
|
|
14
|
+
const durationMs = shallowRef(0)
|
|
15
|
+
const walking = shallowRef(false)
|
|
16
|
+
let passTime = 0
|
|
17
|
+
let lastRafTime: number | null = null
|
|
18
|
+
let rafId: number | null = null
|
|
19
|
+
|
|
20
|
+
const syncPassTime = () => {
|
|
21
|
+
const now = Date.now()
|
|
22
|
+
if (lastRafTime !== null) {
|
|
23
|
+
passTime += now - lastRafTime
|
|
24
|
+
}
|
|
25
|
+
lastRafTime = now
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cancelStep = () => {
|
|
29
|
+
if (rafId !== null) {
|
|
30
|
+
cancelAnimationFrame(rafId)
|
|
31
|
+
rafId = null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const onPause = () => {
|
|
36
|
+
syncPassTime()
|
|
37
|
+
walking.value = false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const onResume = () => {
|
|
41
|
+
if (durationMs.value > 0) {
|
|
42
|
+
lastRafTime = Date.now()
|
|
43
|
+
walking.value = true
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
onUpdate(0)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Reset accumulated passTime whenever duration changes.
|
|
51
|
+
watch(duration, () => {
|
|
52
|
+
const next = typeof duration.value === 'number' ? duration.value : 0
|
|
53
|
+
durationMs.value = Math.max(next, 0) * 1000
|
|
54
|
+
passTime = 0
|
|
55
|
+
walking.value = durationMs.value > 0
|
|
56
|
+
}, { immediate: true })
|
|
57
|
+
|
|
58
|
+
// Drive the rAF loop while walking is true.
|
|
59
|
+
watch(walking, (isWalking) => {
|
|
60
|
+
cancelStep()
|
|
61
|
+
if (!isWalking) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
lastRafTime = Date.now()
|
|
65
|
+
|
|
66
|
+
const step = () => {
|
|
67
|
+
syncPassTime()
|
|
68
|
+
if (passTime >= durationMs.value) {
|
|
69
|
+
onUpdate(1)
|
|
70
|
+
onClose()
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
onUpdate(Math.min(passTime / durationMs.value, 1))
|
|
74
|
+
rafId = requestAnimationFrame(step)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
step()
|
|
78
|
+
}, { immediate: true })
|
|
79
|
+
|
|
80
|
+
onScopeDispose(() => {
|
|
81
|
+
cancelStep()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return [onResume, onPause]
|
|
85
|
+
}
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
-
import type { VueNode } from '@v-c/util/dist/type'
|
|
2
1
|
import type { CSSProperties, MaybeRef, TransitionGroupProps } from 'vue'
|
|
3
|
-
import type {
|
|
2
|
+
import type { VueNode } from '@v-c/util/dist/type'
|
|
3
|
+
import type { ClosableType, Key, NotificationListConfig, Placement, StackConfig } from '../interface'
|
|
4
|
+
import type {
|
|
5
|
+
ComponentsType,
|
|
6
|
+
} from '../Notification'
|
|
7
|
+
import type { NotificationClassNames, NotificationStyles } from '../NotificationList'
|
|
4
8
|
import type { NotificationsProps, NotificationsRef } from '../Notifications'
|
|
5
9
|
import { computed, onMounted, shallowRef, unref, watch } from 'vue'
|
|
6
10
|
import Notifications from '../Notifications'
|
|
7
11
|
|
|
8
12
|
const defaultGetContainer = () => document.body
|
|
9
13
|
|
|
10
|
-
type OptionalConfig = Partial<
|
|
14
|
+
type OptionalConfig = Partial<NotificationListConfig>
|
|
11
15
|
|
|
12
16
|
export interface NotificationConfig {
|
|
13
17
|
prefixCls?: string
|
|
14
18
|
/** Customize container. It will repeat call which means you should return same container element. */
|
|
15
19
|
getContainer?: () => HTMLElement | ShadowRoot
|
|
16
20
|
motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
|
|
17
|
-
|
|
18
|
-
closable?:
|
|
19
|
-
| boolean
|
|
20
|
-
| ({ closeIcon?: VueNode, onClose?: VoidFunction } & Record<string, any>)
|
|
21
|
+
closable?: ClosableType
|
|
21
22
|
maxCount?: number
|
|
22
23
|
duration?: number | false | null
|
|
23
24
|
showProgress?: boolean
|
|
24
25
|
pauseOnHover?: boolean
|
|
26
|
+
placement?: Placement
|
|
27
|
+
classNames?: NotificationClassNames
|
|
28
|
+
styles?: NotificationStyles
|
|
29
|
+
components?: ComponentsType
|
|
25
30
|
/** @private. Config for notification holder style. Safe to remove if refactor */
|
|
26
31
|
className?: (placement: Placement) => string
|
|
27
32
|
/** @private. Config for notification holder style. Safe to remove if refactor */
|
|
@@ -41,7 +46,7 @@ export interface NotificationAPI {
|
|
|
41
46
|
|
|
42
47
|
interface OpenTask {
|
|
43
48
|
type: 'open'
|
|
44
|
-
config:
|
|
49
|
+
config: NotificationListConfig
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
interface CloseTask {
|
|
@@ -59,26 +64,24 @@ let uniqueKey = 0
|
|
|
59
64
|
|
|
60
65
|
function mergeConfig<T>(...objList: Partial<T>[]): T {
|
|
61
66
|
const clone: any = {}
|
|
62
|
-
|
|
63
67
|
objList.forEach((obj: any) => {
|
|
64
68
|
if (obj) {
|
|
65
69
|
Object.keys(obj).forEach((key) => {
|
|
66
70
|
const val = obj[key]
|
|
67
|
-
|
|
68
71
|
if (val !== undefined) {
|
|
69
72
|
clone[key] = val
|
|
70
73
|
}
|
|
71
74
|
})
|
|
72
75
|
}
|
|
73
76
|
})
|
|
74
|
-
|
|
75
77
|
return clone
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
export default function useNotification(
|
|
80
|
+
export default function useNotification(
|
|
81
|
+
rootConfig: MaybeRef<NotificationConfig> = {},
|
|
82
|
+
): [NotificationAPI, () => VueNode] {
|
|
79
83
|
const configRef = computed(() => unref(rootConfig) || {})
|
|
80
84
|
const container = shallowRef<HTMLElement | ShadowRoot>()
|
|
81
|
-
|
|
82
85
|
const notificationRef = shallowRef<NotificationsRef>()
|
|
83
86
|
|
|
84
87
|
const shareConfig = computed(() => {
|
|
@@ -92,6 +95,10 @@ export default function useNotification(rootConfig: MaybeRef<NotificationConfig>
|
|
|
92
95
|
onAllRemoved,
|
|
93
96
|
stack,
|
|
94
97
|
renderNotifications,
|
|
98
|
+
pauseOnHover,
|
|
99
|
+
classNames,
|
|
100
|
+
styles,
|
|
101
|
+
components,
|
|
95
102
|
...restConfig
|
|
96
103
|
} = configRef.value
|
|
97
104
|
return restConfig
|
|
@@ -109,6 +116,10 @@ export default function useNotification(rootConfig: MaybeRef<NotificationConfig>
|
|
|
109
116
|
prefixCls={configRef.value.prefixCls}
|
|
110
117
|
motion={configRef.value.motion}
|
|
111
118
|
maxCount={configRef.value.maxCount}
|
|
119
|
+
pauseOnHover={configRef.value.pauseOnHover}
|
|
120
|
+
classNames={configRef.value.classNames}
|
|
121
|
+
styles={configRef.value.styles}
|
|
122
|
+
components={configRef.value.components}
|
|
112
123
|
className={configRef.value.className}
|
|
113
124
|
style={configRef.value.style}
|
|
114
125
|
onAllRemoved={configRef.value.onAllRemoved}
|
|
@@ -119,16 +130,13 @@ export default function useNotification(rootConfig: MaybeRef<NotificationConfig>
|
|
|
119
130
|
|
|
120
131
|
const taskQueue = shallowRef<Task[]>([])
|
|
121
132
|
|
|
122
|
-
// ========================= Refs =========================
|
|
123
|
-
|
|
124
133
|
const api: NotificationAPI = {
|
|
125
134
|
open(config) {
|
|
126
|
-
const mergedConfig = mergeConfig(shareConfig.value, config)
|
|
135
|
+
const mergedConfig = mergeConfig<NotificationListConfig>(shareConfig.value as any, config)
|
|
127
136
|
if (mergedConfig.key === null || mergedConfig.key === undefined) {
|
|
128
137
|
mergedConfig.key = `vc-notification-${uniqueKey}`
|
|
129
138
|
uniqueKey += 1
|
|
130
139
|
}
|
|
131
|
-
|
|
132
140
|
taskQueue.value = [...taskQueue.value, { type: 'open', config: mergedConfig }]
|
|
133
141
|
},
|
|
134
142
|
close(key) {
|
|
@@ -139,14 +147,9 @@ export default function useNotification(rootConfig: MaybeRef<NotificationConfig>
|
|
|
139
147
|
},
|
|
140
148
|
}
|
|
141
149
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
onMounted(
|
|
146
|
-
() => {
|
|
147
|
-
container.value = resolveContainer()
|
|
148
|
-
},
|
|
149
|
-
)
|
|
150
|
+
onMounted(() => {
|
|
151
|
+
container.value = resolveContainer()
|
|
152
|
+
})
|
|
150
153
|
watch(
|
|
151
154
|
() => configRef.value.getContainer,
|
|
152
155
|
() => {
|
|
@@ -170,10 +173,9 @@ export default function useNotification(rootConfig: MaybeRef<NotificationConfig>
|
|
|
170
173
|
break
|
|
171
174
|
}
|
|
172
175
|
})
|
|
173
|
-
taskQueue.value =
|
|
176
|
+
taskQueue.value = []
|
|
174
177
|
}
|
|
175
178
|
})
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
return [api, contextHolder] as [NotificationAPI, () => VueNode]
|
|
180
|
+
return [api, contextHolder]
|
|
179
181
|
}
|
package/src/hooks/useStack.ts
CHANGED
|
@@ -4,25 +4,29 @@ import { computed, reactive, toRefs, unref, watchEffect } from 'vue'
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_OFFSET = 8
|
|
6
6
|
const DEFAULT_THRESHOLD = 3
|
|
7
|
-
const DEFAULT_GAP = 16
|
|
8
7
|
|
|
9
8
|
type StackParams = Exclude<StackConfig, boolean>
|
|
10
9
|
|
|
11
|
-
type UseStack = (
|
|
10
|
+
type UseStack = (
|
|
11
|
+
config?: MaybeRef<StackConfig | undefined>,
|
|
12
|
+
) => [ComputedRef<boolean>, ToRefs<StackParams>]
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Resolves the stack setting into an enabled flag and normalized stack params.
|
|
16
|
+
* Mirrors rc-notification@2.0 useStack. The `gap` config is no longer surfaced
|
|
17
|
+
* here — gap is now read from the list-content CSS `gap`/`row-gap`.
|
|
18
|
+
*/
|
|
13
19
|
const useStack: UseStack = (config) => {
|
|
14
20
|
const result: StackParams = reactive({
|
|
15
21
|
offset: DEFAULT_OFFSET,
|
|
16
22
|
threshold: DEFAULT_THRESHOLD,
|
|
17
|
-
gap: DEFAULT_GAP,
|
|
18
23
|
})
|
|
19
24
|
|
|
20
25
|
watchEffect(() => {
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
result.offset =
|
|
24
|
-
result.threshold =
|
|
25
|
-
result.gap = _config.gap ?? DEFAULT_GAP
|
|
26
|
+
const value = unref(config)
|
|
27
|
+
if (value && typeof value === 'object') {
|
|
28
|
+
result.offset = value.offset ?? DEFAULT_OFFSET
|
|
29
|
+
result.threshold = value.threshold ?? DEFAULT_THRESHOLD
|
|
26
30
|
}
|
|
27
31
|
})
|
|
28
32
|
|