@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.
Files changed (56) hide show
  1. package/dist/Notification.d.ts +59 -0
  2. package/dist/Notification.js +301 -0
  3. package/dist/NotificationList/Content.d.ts +11 -0
  4. package/dist/NotificationList/Content.js +60 -0
  5. package/dist/NotificationList/index.d.ts +35 -0
  6. package/dist/NotificationList/index.js +224 -0
  7. package/dist/NotificationProvider.js +3 -1
  8. package/dist/Notifications.d.ts +9 -3
  9. package/dist/Notifications.js +45 -21
  10. package/dist/Progress.d.ts +8 -0
  11. package/dist/Progress.js +31 -0
  12. package/dist/hooks/useClosable.d.ts +17 -0
  13. package/dist/hooks/useClosable.js +34 -0
  14. package/dist/hooks/useListPosition/index.d.ts +18 -0
  15. package/dist/hooks/useListPosition/index.js +41 -0
  16. package/dist/hooks/useListPosition/useSizes.d.ts +10 -0
  17. package/dist/hooks/useListPosition/useSizes.js +31 -0
  18. package/dist/hooks/useNoticeTimer.d.ts +7 -0
  19. package/dist/hooks/useNoticeTimer.js +63 -0
  20. package/dist/hooks/useNotification.d.ts +10 -8
  21. package/dist/hooks/useNotification.js +10 -4
  22. package/dist/hooks/useStack.d.ts +5 -0
  23. package/dist/hooks/useStack.js +13 -10
  24. package/dist/index.d.ts +9 -6
  25. package/dist/index.js +5 -3
  26. package/dist/interface.d.ts +18 -43
  27. package/package.json +3 -3
  28. package/src/Notification.tsx +326 -0
  29. package/src/NotificationList/Content.tsx +66 -0
  30. package/src/NotificationList/index.tsx +282 -0
  31. package/src/Notifications.tsx +58 -64
  32. package/src/Progress.tsx +27 -0
  33. package/src/hooks/useClosable.ts +53 -0
  34. package/src/hooks/useListPosition/index.ts +69 -0
  35. package/src/hooks/useListPosition/useSizes.ts +43 -0
  36. package/src/hooks/useNoticeTimer.ts +85 -0
  37. package/src/hooks/useNotification.tsx +30 -28
  38. package/src/hooks/useStack.ts +12 -8
  39. package/src/index.ts +47 -6
  40. package/src/interface.ts +28 -44
  41. package/vite.config.ts +4 -3
  42. package/dist/Notice.cjs +0 -235
  43. package/dist/Notice.d.ts +0 -15
  44. package/dist/Notice.js +0 -227
  45. package/dist/NoticeList.cjs +0 -170
  46. package/dist/NoticeList.d.ts +0 -13
  47. package/dist/NoticeList.js +0 -164
  48. package/dist/NotificationProvider.cjs +0 -14
  49. package/dist/Notifications.cjs +0 -146
  50. package/dist/_virtual/rolldown_runtime.cjs +0 -21
  51. package/dist/hooks/useNotification.cjs +0 -93
  52. package/dist/hooks/useStack.cjs +0 -27
  53. package/dist/index.cjs +0 -7
  54. package/dist/interface.cjs +0 -1
  55. package/src/Notice.tsx +0 -212
  56. package/src/NoticeList.tsx +0 -219
package/src/Notice.tsx DELETED
@@ -1,212 +0,0 @@
1
- import type { CSSProperties } from 'vue'
2
- import type { Key, NoticeConfig } from './interface.ts'
3
- import { classNames } from '@v-c/util'
4
- import KeyCode from '@v-c/util/dist/KeyCode'
5
- import pickAttrs from '@v-c/util/dist/pickAttrs'
6
- import { computed, defineComponent, shallowRef, watch } from 'vue'
7
-
8
- export interface NoticeProps extends Omit<NoticeConfig, 'onClose'> {
9
- prefixCls: string
10
- eventKey: Key
11
- onClick?: (event: Event) => void
12
- onNoticeClose?: (key: Key) => void
13
- hovering?: boolean
14
- props?: Record<string, any>
15
- }
16
-
17
- const defaults = {
18
- duration: 4.5,
19
- pauseOnHover: true,
20
- closeIcon: 'x',
21
- } as const
22
-
23
- const Notify = defineComponent<NoticeProps & { times?: number }>((props, { attrs }) => {
24
- const hovering = shallowRef(false)
25
- const percent = shallowRef(0)
26
- const spentTime = shallowRef(0)
27
-
28
- const mergedHovering = computed(() => props.hovering || hovering.value)
29
- const mergedDuration = computed(() => {
30
- if (typeof props.duration === 'number') {
31
- return props.duration
32
- }
33
- if (props.duration === undefined) {
34
- return defaults.duration
35
- }
36
- return 0
37
- })
38
- const mergedPauseOnHover = computed(() =>
39
- props.pauseOnHover === undefined ? defaults.pauseOnHover : props.pauseOnHover,
40
- )
41
- const mergedShowProgress = computed(() => mergedDuration.value > 0 && props.showProgress)
42
- const mergedCloseIcon = computed(() => props.closeIcon ?? defaults.closeIcon)
43
-
44
- // ======================== Close =========================
45
- const onInternalClose = () => {
46
- props.onNoticeClose?.(props.eventKey)
47
- }
48
-
49
- const onCloseKeyDown = (e: KeyboardEvent) => {
50
- if (e.key === 'Enter' || e.code === 'Enter' || e.keyCode === KeyCode.ENTER) {
51
- onInternalClose()
52
- }
53
- }
54
-
55
- // ======================== Timing ========================
56
- watch([
57
- () => props.times,
58
- mergedDuration,
59
- mergedHovering,
60
- ], (_n, _, onCleanup) => {
61
- const duration = mergedDuration.value
62
- const hoveringValue = mergedHovering.value
63
- const pauseOnHover = mergedPauseOnHover.value
64
- if (!hoveringValue && duration > 0) {
65
- const start = Date.now() - spentTime.value
66
- const timeoutId = window.setTimeout(() => {
67
- onInternalClose()
68
- }, duration * 1000 - spentTime.value)
69
-
70
- onCleanup(() => {
71
- if (pauseOnHover) {
72
- clearTimeout(timeoutId)
73
- }
74
- spentTime.value = Date.now() - start
75
- })
76
- }
77
- }, {
78
- immediate: true,
79
- })
80
-
81
- // ===================== Progress Bar =====================
82
- watch([
83
- () => props.times,
84
- mergedDuration,
85
- spentTime,
86
- mergedHovering,
87
- mergedShowProgress,
88
- ], (_n, _, onCleanup) => {
89
- const hoveringValue = mergedHovering.value
90
- const showProgress = mergedShowProgress.value
91
- const pauseOnHover = mergedPauseOnHover.value
92
- const duration = mergedDuration.value
93
- const baseSpentTime = spentTime.value
94
-
95
- if (!hoveringValue && showProgress && (pauseOnHover || baseSpentTime === 0)) {
96
- const start = performance.now()
97
- let animationFrame = 0
98
-
99
- const calculate = () => {
100
- cancelAnimationFrame(animationFrame)
101
- animationFrame = requestAnimationFrame((timestamp) => {
102
- const runtime = timestamp + baseSpentTime - start
103
- const progress = Math.min(runtime / (duration * 1000), 1)
104
- percent.value = progress * 100
105
- if (progress < 1) {
106
- calculate()
107
- }
108
- })
109
- }
110
-
111
- calculate()
112
-
113
- onCleanup(() => {
114
- if (pauseOnHover) {
115
- cancelAnimationFrame(animationFrame)
116
- }
117
- })
118
- }
119
- }, {
120
- immediate: true,
121
- })
122
-
123
- return () => {
124
- const {
125
- closable,
126
- prefixCls,
127
- props: divProps,
128
- onClick,
129
- content,
130
- className,
131
- style,
132
- } = props
133
-
134
- // ======================== Closable ========================
135
- const closableConfig
136
- = typeof closable === 'object' && closable !== null
137
- ? closable
138
- : closable
139
- ? { closeIcon: mergedCloseIcon.value }
140
- : {}
141
- const ariaProps = pickAttrs(closableConfig, true)
142
-
143
- // ======================== Progress ========================
144
- const safePercent = percent.value <= 0 ? 0 : percent.value > 100 ? 100 : percent.value
145
- const validPercent = 100 - safePercent
146
-
147
- // ======================== Render ========================
148
- const noticePrefixCls = `${prefixCls}-notice`
149
-
150
- const mergedStyle: CSSProperties = {
151
- ...(typeof divProps?.style === 'object' && divProps?.style ? divProps.style : {}),
152
- ...(typeof (attrs as any).style === 'object' && (attrs as any).style ? (attrs as any).style : {}),
153
- ...(typeof style === 'object' && style ? style : {}),
154
- }
155
-
156
- return (
157
- <div
158
- {...divProps}
159
- class={
160
- classNames(
161
- noticePrefixCls,
162
- className,
163
- (attrs as any).class,
164
- {
165
- [`${noticePrefixCls}-closable`]: !!closable,
166
- },
167
- )
168
- }
169
- style={mergedStyle}
170
- onMouseenter={(e: MouseEvent) => {
171
- hovering.value = true
172
- divProps?.onMouseEnter?.(e)
173
- }}
174
- onMouseleave={(e: MouseEvent) => {
175
- hovering.value = false
176
- divProps?.onMouseLeave?.(e)
177
- }}
178
- onClick={onClick}
179
- >
180
- {/* Content */}
181
- <div class={`${noticePrefixCls}-content`}>{content}</div>
182
-
183
- {/* Close Icon */}
184
- {closable && (
185
- <button
186
- type="button"
187
- class={`${noticePrefixCls}-close`}
188
- onKeydown={onCloseKeyDown}
189
- aria-label="Close"
190
- {...ariaProps}
191
- onClick={(e) => {
192
- e.preventDefault()
193
- e.stopPropagation()
194
- onInternalClose()
195
- }}
196
- >
197
- {closableConfig.closeIcon ?? mergedCloseIcon.value}
198
- </button>
199
- )}
200
-
201
- {/* Progress Bar */}
202
- {mergedShowProgress.value && (
203
- <progress class={`${noticePrefixCls}-progress`} max="100" value={validPercent}>
204
- {`${validPercent}%`}
205
- </progress>
206
- )}
207
- </div>
208
- )
209
- }
210
- })
211
-
212
- export default Notify
@@ -1,219 +0,0 @@
1
- import type { CSSProperties, TransitionGroupProps } from 'vue'
2
- import type { InnerOpenConfig, Key, NoticeConfig, OpenConfig, Placement, StackConfig } from './interface.ts'
3
- import { classNames as clsx } from '@v-c/util'
4
- import { getTransitionGroupProps } from '@v-c/util/dist/utils/transition'
5
- import { unrefElement } from '@v-c/util/dist/vueuse/unref-element'
6
- import { computed, defineComponent, reactive, ref, shallowRef, toRef, TransitionGroup, watch, watchEffect } from 'vue'
7
- import useStack from './hooks/useStack.ts'
8
- import Notice from './Notice.tsx'
9
- import { useNotificationContext } from './NotificationProvider.tsx'
10
-
11
- export interface NoticeListProps {
12
- configList?: OpenConfig[]
13
- placement?: Placement
14
- prefixCls?: string
15
- motion?: TransitionGroupProps | ((placement: Placement) => TransitionGroupProps)
16
- stack?: StackConfig
17
-
18
- // Events
19
- onAllNoticeRemoved?: (placement: Placement) => void
20
- onNoticeClose?: (key: Key) => void
21
- }
22
-
23
- const NoticeList = defineComponent<NoticeListProps>((props, { attrs }) => {
24
- const ctx = useNotificationContext()
25
- const dictRef = reactive<Record<string, HTMLDivElement | undefined>>({})
26
- const keys = computed(() =>
27
- (props.configList ?? []).map(config => ({
28
- config,
29
- key: String(config.key),
30
- })),
31
- )
32
- const latestNotice = shallowRef<HTMLDivElement | null>(null)
33
- const hoverKeys = ref<string[]>([])
34
-
35
- const stackConfig = toRef(props, 'stack')
36
- const [stackEnabled, stackOptions] = useStack(stackConfig)
37
- const stackActive = computed(() => stackEnabled.value || stackConfig.value === false)
38
- const expanded = computed(() => {
39
- if (!stackActive.value) {
40
- return false
41
- }
42
- if (!stackEnabled.value) {
43
- return true
44
- }
45
- return hoverKeys.value.length > 0 || keys.value.length <= stackOptions.threshold!.value!
46
- })
47
- const placementMotion = computed(() => {
48
- if (typeof props.motion === 'function') {
49
- return props.placement ? props.motion(props.placement) : undefined
50
- }
51
- return props.motion
52
- })
53
-
54
- // Clean hover key
55
- watch([hoverKeys, keys, stackEnabled], () => {
56
- if (stackEnabled.value && hoverKeys.value.length > 1) {
57
- hoverKeys.value = hoverKeys.value.filter(key =>
58
- keys.value.some(({ key: dataKey }) => key === dataKey),
59
- )
60
- }
61
- })
62
- watch(stackEnabled, (enabled) => {
63
- if (!enabled && hoverKeys.value.length) {
64
- hoverKeys.value = []
65
- }
66
- })
67
-
68
- // Sync latest notice after DOM updates so collapsed stack uses accurate height
69
- watchEffect(
70
- () => {
71
- if (!stackActive.value) {
72
- latestNotice.value = null
73
- return
74
- }
75
-
76
- const lastKey = keys.value[keys.value.length - 1]?.key
77
- latestNotice.value = lastKey ? dictRef[lastKey] ?? null : null
78
- },
79
- { flush: 'post' },
80
- )
81
-
82
- const checkAllClosed = () => {
83
- if (!props.placement) {
84
- return
85
- }
86
- if (keys.value.length === 0) {
87
- props.onAllNoticeRemoved?.(props.placement)
88
- }
89
- }
90
-
91
- return () => {
92
- const { prefixCls = '', placement = 'topRight', onNoticeClose } = props
93
-
94
- const renderNotify = () =>
95
- keys.value.map(({ config }, motionIndex) => {
96
- const { key, times } = config as InnerOpenConfig
97
- const strKey = String(key)
98
- const {
99
- className: configClassName,
100
- style: configStyle,
101
- classNames: configClassNames,
102
- styles: configStyles,
103
- ...restConfig
104
- } = config as NoticeConfig
105
- const dataIndex = keys.value.findIndex(item => item.key === strKey)
106
- const stackStyle: CSSProperties = {}
107
-
108
- if (stackActive.value) {
109
- const index = keys.value.length - 1 - (dataIndex > -1 ? dataIndex : motionIndex - 1)
110
- const transformX = placement === 'top' || placement === 'bottom' ? '-50%' : '0'
111
- if (index > 0) {
112
- stackStyle.height = expanded.value
113
- ? dictRef[strKey]?.offsetHeight
114
- : latestNotice.value?.offsetHeight
115
- if (stackStyle.height && typeof stackStyle.height === 'number') {
116
- stackStyle.height = `${stackStyle.height}px`
117
- }
118
- let verticalOffset = 0
119
- for (let i = 0; i < index; i += 1) {
120
- const targetKey = keys.value[keys.value.length - 1 - i]?.key
121
- const node = targetKey ? dictRef[targetKey] : null
122
- verticalOffset += (node?.offsetHeight ?? 0) + stackOptions.gap!.value!
123
- }
124
-
125
- const transformY
126
- = (expanded.value ? verticalOffset : index * stackOptions.offset!.value!)
127
- * (placement.startsWith('top') ? 1 : -1)
128
- const currentWidth = dictRef[strKey]?.offsetWidth
129
- const latestWidth = latestNotice.value?.offsetWidth
130
- const scaleX
131
- = !expanded.value && latestWidth && currentWidth
132
- ? (latestWidth - stackOptions.offset!.value! * 2 * (index < 3 ? index : 3))
133
- / currentWidth
134
- : 1
135
- stackStyle.transform = `translate3d(${transformX}, ${transformY}px, 0) scaleX(${scaleX})`
136
- }
137
- else {
138
- stackStyle.transform = `translate3d(${transformX}, 0, 0)`
139
- }
140
- }
141
- return (
142
- <div
143
- key={strKey}
144
- class={clsx(`${prefixCls}-notice-wrapper`, configClassNames?.wrapper)}
145
- style={{
146
- ...stackStyle,
147
- ...configStyles?.wrapper,
148
- }}
149
- onMouseenter={() => {
150
- if (!stackEnabled.value) {
151
- return
152
- }
153
- hoverKeys.value = hoverKeys.value.includes(strKey)
154
- ? hoverKeys.value
155
- : [...hoverKeys.value, strKey]
156
- }}
157
- onMouseleave={() => {
158
- if (!stackEnabled.value) {
159
- return
160
- }
161
- hoverKeys.value = hoverKeys.value.filter(k => k !== strKey)
162
- }}
163
- >
164
- <Notice
165
- {...restConfig as any}
166
- ref={(el) => {
167
- const element = unrefElement<HTMLDivElement>(el as any) ?? undefined
168
- if (dataIndex > -1) {
169
- dictRef[strKey] = element
170
- }
171
- else {
172
- delete dictRef[strKey]
173
- }
174
- }}
175
- prefixCls={prefixCls}
176
- classNames={configClassNames}
177
- styles={configStyles}
178
- class={clsx(configClassName, (ctx.value as any)?.classNames?.notice)}
179
- style={configStyle}
180
- times={times}
181
- eventKey={key}
182
- onNoticeClose={onNoticeClose}
183
- hovering={stackEnabled.value && hoverKeys.value.length > 0}
184
- />
185
- </div>
186
- )
187
- })
188
- let motionGroupProps: TransitionGroupProps = {}
189
- if (placementMotion.value) {
190
- motionGroupProps = getTransitionGroupProps(placementMotion.value.name!, placementMotion.value)
191
- }
192
- return (
193
- <TransitionGroup
194
- key={placement}
195
- tag="div"
196
- appear
197
- {...{
198
- class: clsx(
199
- prefixCls,
200
- `${prefixCls}-${placement}`,
201
- (ctx.value as any)?.classNames?.list,
202
- (attrs as any).class,
203
- {
204
- [`${prefixCls}-stack-expanded`]: expanded.value,
205
- [`${prefixCls}-stack`]: stackActive.value,
206
- },
207
- ),
208
- style: (attrs as any).style,
209
- }}
210
- {...motionGroupProps}
211
- onAfterLeave={checkAllClosed}
212
- >
213
- {renderNotify()}
214
- </TransitionGroup>
215
- )
216
- }
217
- })
218
-
219
- export default NoticeList