@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,85 +0,0 @@
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,181 +0,0 @@
1
- import type { CSSProperties, MaybeRef, TransitionGroupProps } from 'vue'
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'
8
- import type { NotificationsProps, NotificationsRef } from '../Notifications'
9
- import { computed, onMounted, shallowRef, unref, watch } from 'vue'
10
- import Notifications from '../Notifications'
11
-
12
- const defaultGetContainer = () => document.body
13
-
14
- type OptionalConfig = Partial<NotificationListConfig>
15
-
16
- export interface NotificationConfig {
17
- prefixCls?: string
18
- /** Customize container. It will repeat call which means you should return same container element. */
19
- getContainer?: () => HTMLElement | ShadowRoot
20
- motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
21
- closable?: ClosableType
22
- maxCount?: number
23
- duration?: number | false | null
24
- showProgress?: boolean
25
- pauseOnHover?: boolean
26
- placement?: Placement
27
- classNames?: NotificationClassNames
28
- styles?: NotificationStyles
29
- components?: ComponentsType
30
- /** @private. Config for notification holder style. Safe to remove if refactor */
31
- className?: (placement: Placement) => string
32
- /** @private. Config for notification holder style. Safe to remove if refactor */
33
- style?: (placement: Placement) => CSSProperties
34
- /** @private Trigger when all the notification closed. */
35
- onAllRemoved?: VoidFunction
36
- stack?: StackConfig
37
- /** @private Slot for style in Notifications */
38
- renderNotifications?: NotificationsProps['renderNotifications']
39
- }
40
-
41
- export interface NotificationAPI {
42
- open: (config: OptionalConfig) => void
43
- close: (key: Key) => void
44
- destroy: () => void
45
- }
46
-
47
- interface OpenTask {
48
- type: 'open'
49
- config: NotificationListConfig
50
- }
51
-
52
- interface CloseTask {
53
- type: 'close'
54
- key: Key
55
- }
56
-
57
- interface DestroyTask {
58
- type: 'destroy'
59
- }
60
-
61
- type Task = OpenTask | CloseTask | DestroyTask
62
-
63
- let uniqueKey = 0
64
-
65
- function mergeConfig<T>(...objList: Partial<T>[]): T {
66
- const clone: any = {}
67
- objList.forEach((obj: any) => {
68
- if (obj) {
69
- Object.keys(obj).forEach((key) => {
70
- const val = obj[key]
71
- if (val !== undefined) {
72
- clone[key] = val
73
- }
74
- })
75
- }
76
- })
77
- return clone
78
- }
79
-
80
- export default function useNotification(
81
- rootConfig: MaybeRef<NotificationConfig> = {},
82
- ): [NotificationAPI, () => VueNode] {
83
- const configRef = computed(() => unref(rootConfig) || {})
84
- const container = shallowRef<HTMLElement | ShadowRoot>()
85
- const notificationRef = shallowRef<NotificationsRef>()
86
-
87
- const shareConfig = computed(() => {
88
- const {
89
- getContainer,
90
- motion,
91
- prefixCls,
92
- maxCount,
93
- className,
94
- style,
95
- onAllRemoved,
96
- stack,
97
- renderNotifications,
98
- pauseOnHover,
99
- classNames,
100
- styles,
101
- components,
102
- ...restConfig
103
- } = configRef.value
104
- return restConfig
105
- })
106
-
107
- const resolveContainer = () => {
108
- const getContainer = configRef.value.getContainer || defaultGetContainer
109
- return getContainer()
110
- }
111
-
112
- const contextHolder = () => (
113
- <Notifications
114
- container={container.value}
115
- ref={notificationRef}
116
- prefixCls={configRef.value.prefixCls}
117
- motion={configRef.value.motion}
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}
123
- className={configRef.value.className}
124
- style={configRef.value.style}
125
- onAllRemoved={configRef.value.onAllRemoved}
126
- stack={configRef.value.stack}
127
- renderNotifications={configRef.value.renderNotifications}
128
- />
129
- )
130
-
131
- const taskQueue = shallowRef<Task[]>([])
132
-
133
- const api: NotificationAPI = {
134
- open(config) {
135
- const mergedConfig = mergeConfig<NotificationListConfig>(shareConfig.value as any, config)
136
- if (mergedConfig.key === null || mergedConfig.key === undefined) {
137
- mergedConfig.key = `vc-notification-${uniqueKey}`
138
- uniqueKey += 1
139
- }
140
- taskQueue.value = [...taskQueue.value, { type: 'open', config: mergedConfig }]
141
- },
142
- close(key) {
143
- taskQueue.value = [...taskQueue.value, { type: 'close', key }]
144
- },
145
- destroy() {
146
- taskQueue.value = [...taskQueue.value, { type: 'destroy' }]
147
- },
148
- }
149
-
150
- onMounted(() => {
151
- container.value = resolveContainer()
152
- })
153
- watch(
154
- () => configRef.value.getContainer,
155
- () => {
156
- container.value = resolveContainer()
157
- },
158
- )
159
- watch(taskQueue, () => {
160
- if (notificationRef.value && taskQueue.value.length) {
161
- taskQueue.value.forEach((task) => {
162
- switch (task.type) {
163
- case 'open':
164
- notificationRef.value?.open(task.config)
165
- break
166
- case 'close':
167
- notificationRef.value?.close(task.key)
168
- break
169
- case 'destroy':
170
- notificationRef.value?.destroy()
171
- break
172
- default:
173
- break
174
- }
175
- })
176
- taskQueue.value = []
177
- }
178
- })
179
-
180
- return [api, contextHolder]
181
- }
@@ -1,36 +0,0 @@
1
- import type { ComputedRef, MaybeRef, ToRefs } from 'vue'
2
- import type { StackConfig } from '../interface'
3
- import { computed, reactive, toRefs, unref, watchEffect } from 'vue'
4
-
5
- const DEFAULT_OFFSET = 8
6
- const DEFAULT_THRESHOLD = 3
7
-
8
- type StackParams = Exclude<StackConfig, boolean>
9
-
10
- type UseStack = (
11
- config?: MaybeRef<StackConfig | undefined>,
12
- ) => [ComputedRef<boolean>, ToRefs<StackParams>]
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
- */
19
- const useStack: UseStack = (config) => {
20
- const result: StackParams = reactive({
21
- offset: DEFAULT_OFFSET,
22
- threshold: DEFAULT_THRESHOLD,
23
- })
24
-
25
- watchEffect(() => {
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
30
- }
31
- })
32
-
33
- return [computed(() => !!unref(config)), toRefs(result)]
34
- }
35
-
36
- export default useStack
package/src/index.ts DELETED
@@ -1,50 +0,0 @@
1
- import useNotification from './hooks/useNotification'
2
- import Notification from './Notification'
3
- import NotificationList from './NotificationList'
4
- import { useNotificationContext, useNotificationProvider } from './NotificationProvider'
5
- import Progress from './Progress'
6
-
7
- import type { NotificationAPI, NotificationConfig } from './hooks/useNotification'
8
- import type {
9
- ComponentsType,
10
- NotificationClassNames as NoticeClassNames,
11
- NotificationProps,
12
- NotificationStyles as NoticeStyles,
13
- } from './Notification'
14
- import type {
15
- NotificationClassNames,
16
- NotificationListProps,
17
- NotificationStyles,
18
- Placement,
19
- } from './NotificationList'
20
- import type { NotificationProgressProps } from './Progress'
21
- import type { Key, NotificationListConfig, StackConfig } from './interface'
22
- import type { ClosableType, ParsedClosableConfig } from './hooks/useClosable'
23
-
24
- export {
25
- Notification,
26
- NotificationList,
27
- Progress,
28
- useNotification,
29
- useNotificationContext,
30
- useNotificationProvider,
31
- }
32
-
33
- export type {
34
- ClosableType,
35
- ComponentsType,
36
- Key,
37
- NoticeClassNames,
38
- NoticeStyles,
39
- NotificationAPI,
40
- NotificationClassNames,
41
- NotificationConfig,
42
- NotificationListConfig,
43
- NotificationListProps,
44
- NotificationProgressProps,
45
- NotificationProps,
46
- NotificationStyles,
47
- ParsedClosableConfig,
48
- Placement,
49
- StackConfig,
50
- }
package/src/interface.ts DELETED
@@ -1,45 +0,0 @@
1
- import type { ClosableType } from './hooks/useClosable'
2
- import type {
3
- ComponentsType,
4
- NotificationProps,
5
- } from './Notification'
6
-
7
- export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'
8
-
9
- export type Key = string | number
10
-
11
- export type StackConfig
12
- = | boolean
13
- | {
14
- /**
15
- * When the notice count exceeds this threshold, notices will be stacked.
16
- * @default 3
17
- */
18
- threshold?: number
19
- /**
20
- * Vertical offset applied between stacked notices.
21
- * @default 8
22
- */
23
- offset?: number
24
- }
25
-
26
- /**
27
- * Configuration accepted by the public `api.open` call.
28
- * Mirrors rc-notification@2.0 NotificationListConfig.
29
- */
30
- export interface NotificationListConfig extends Omit<NotificationProps, 'prefixCls'> {
31
- key: Key
32
- placement?: Placement
33
- times?: number
34
- }
35
-
36
- export type Placements = Partial<Record<Placement, NotificationListConfig[]>>
37
-
38
- export type InnerOpenConfig = NotificationListConfig & { times?: number }
39
-
40
- // Re-export common surfaces consumers used to import from interface.ts directly.
41
- export type {
42
- ClosableType,
43
- ComponentsType,
44
- NotificationProps,
45
- }
@@ -1,70 +0,0 @@
1
- import { mount } from '@vue/test-utils'
2
- import { nextTick } from 'vue'
3
- import { afterEach, describe, expect, it, vi } from 'vitest'
4
- import Notifications from '../src/Notifications'
5
-
6
- describe('notification', () => {
7
- afterEach(() => {
8
- document.body.innerHTML = ''
9
- })
10
-
11
- it('calls close callbacks once when notice close button is clicked', async () => {
12
- const onClose = vi.fn()
13
- const closableOnClose = vi.fn()
14
- const wrapper = mount(Notifications, {
15
- props: {
16
- container: document.body,
17
- },
18
- attachTo: document.body,
19
- })
20
-
21
- wrapper.vm.open({
22
- key: 'notice',
23
- title: 'Notice',
24
- duration: false,
25
- closable: {
26
- closeIcon: 'x',
27
- onClose: closableOnClose,
28
- },
29
- onClose,
30
- })
31
-
32
- await nextTick()
33
- await nextTick()
34
- await document.querySelector<HTMLButtonElement>('.vc-notification-notice-close')!.click()
35
- await nextTick()
36
-
37
- expect(closableOnClose).toHaveBeenCalledTimes(1)
38
- expect(onClose).toHaveBeenCalledTimes(1)
39
- })
40
-
41
- it('does not call notice close callbacks when closed by api', async () => {
42
- const onClose = vi.fn()
43
- const closableOnClose = vi.fn()
44
- const wrapper = mount(Notifications, {
45
- props: {
46
- container: document.body,
47
- },
48
- attachTo: document.body,
49
- })
50
-
51
- wrapper.vm.open({
52
- key: 'notice',
53
- title: 'Notice',
54
- duration: false,
55
- closable: {
56
- closeIcon: 'x',
57
- onClose: closableOnClose,
58
- },
59
- onClose,
60
- })
61
-
62
- await nextTick()
63
- await nextTick()
64
- wrapper.vm.close('notice')
65
- await nextTick()
66
-
67
- expect(closableOnClose).not.toHaveBeenCalled()
68
- expect(onClose).not.toHaveBeenCalled()
69
- })
70
- })
package/tsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "extends": ["../../tsconfig.base.json"],
3
- "compilerOptions": {
4
-
5
- },
6
- "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"]
7
- }
package/vite.config.ts DELETED
@@ -1,19 +0,0 @@
1
- import type { UserConfig } from 'vite'
2
- import { defineConfig, mergeConfig } from 'vite'
3
- import { buildCommon, resolveBuildEntries } from '../../scripts/build.common'
4
-
5
- const packageRoot = new URL('.', import.meta.url)
6
- const entry = resolveBuildEntries(packageRoot, ['src/**/*.ts', 'src/**/*.tsx', '!src/**/*.test.ts', '!src/**/tests'])
7
-
8
- export default defineConfig({
9
- ...mergeConfig(buildCommon({
10
- packageRoot,
11
- external: ['vue', 'classnames', /^@v-c\/util/],
12
- }), {
13
- build: {
14
- lib: {
15
- entry,
16
- },
17
- },
18
- } as UserConfig),
19
- })
package/vitest.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineProject, mergeConfig } from 'vitest/config'
2
- import configShared from '../../vitest.config'
3
-
4
- export default mergeConfig(
5
- configShared,
6
- defineProject({
7
- test: {
8
- environment: 'jsdom',
9
- },
10
- }),
11
- )