@v-c/notification 1.0.0 → 2.0.0-beta.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 (67) hide show
  1. package/dist/Notification.d.ts +286 -0
  2. package/dist/Notification.js +237 -0
  3. package/dist/NotificationList/Content.d.ts +88 -0
  4. package/dist/NotificationList/Content.js +74 -0
  5. package/dist/NotificationList/index.d.ts +156 -0
  6. package/dist/NotificationList/index.js +204 -0
  7. package/dist/NotificationProvider.d.ts +20 -1
  8. package/dist/NotificationProvider.js +16 -3
  9. package/dist/Notifications.d.ts +136 -8
  10. package/dist/Notifications.js +118 -109
  11. package/dist/Progress.d.ts +8 -0
  12. package/dist/Progress.js +18 -0
  13. package/dist/hooks/useClosable.d.ts +22 -0
  14. package/dist/hooks/useClosable.js +33 -0
  15. package/dist/hooks/useListPosition/index.d.ts +17 -0
  16. package/dist/hooks/useListPosition/index.js +48 -0
  17. package/dist/hooks/useListPosition/useSizes.d.ts +13 -0
  18. package/dist/hooks/useListPosition/useSizes.js +29 -0
  19. package/dist/hooks/useNoticeTimer.d.ts +6 -0
  20. package/dist/hooks/useNoticeTimer.js +71 -0
  21. package/dist/hooks/useNotification.d.ts +8 -24
  22. package/dist/hooks/useNotification.js +33 -22
  23. package/dist/hooks/useStack.d.ts +8 -4
  24. package/dist/hooks/useStack.js +15 -18
  25. package/dist/index.d.ts +7 -5
  26. package/dist/index.js +5 -3
  27. package/docs/context.vue +1 -1
  28. package/docs/hooks.vue +4 -4
  29. package/docs/index.less +62 -143
  30. package/docs/maxCount.vue +1 -1
  31. package/docs/showProgress.vue +2 -2
  32. package/docs/stack.vue +1 -1
  33. package/package.json +5 -4
  34. package/src/Notification.tsx +363 -0
  35. package/src/NotificationList/Content.tsx +84 -0
  36. package/src/NotificationList/index.tsx +298 -0
  37. package/src/NotificationProvider.tsx +23 -3
  38. package/src/Notifications.tsx +103 -87
  39. package/src/Progress.tsx +23 -0
  40. package/src/hooks/useClosable.ts +54 -0
  41. package/src/hooks/useListPosition/index.ts +85 -0
  42. package/src/hooks/useListPosition/useSizes.ts +42 -0
  43. package/src/hooks/useNoticeTimer.ts +96 -0
  44. package/src/hooks/useNotification.tsx +54 -80
  45. package/src/hooks/useStack.ts +26 -18
  46. package/src/index.ts +31 -5
  47. package/tests/index.spec.tsx +200 -0
  48. package/vite.config.ts +4 -3
  49. package/vitest.config.ts +3 -1
  50. package/dist/Notice.cjs +0 -235
  51. package/dist/Notice.d.ts +0 -15
  52. package/dist/Notice.js +0 -227
  53. package/dist/NoticeList.cjs +0 -170
  54. package/dist/NoticeList.d.ts +0 -13
  55. package/dist/NoticeList.js +0 -164
  56. package/dist/NotificationProvider.cjs +0 -14
  57. package/dist/Notifications.cjs +0 -146
  58. package/dist/_virtual/rolldown_runtime.cjs +0 -21
  59. package/dist/hooks/useNotification.cjs +0 -93
  60. package/dist/hooks/useStack.cjs +0 -27
  61. package/dist/index.cjs +0 -7
  62. package/dist/interface.cjs +0 -1
  63. package/dist/interface.d.ts +0 -55
  64. package/dist/interface.js +0 -0
  65. package/src/Notice.tsx +0 -212
  66. package/src/NoticeList.tsx +0 -219
  67. package/src/interface.ts +0 -61
@@ -1,55 +0,0 @@
1
- import { VueNode } from '@v-c/util/dist/type';
2
- import { CSSProperties } from 'vue';
3
- export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight';
4
- type NoticeSemanticProps = 'wrapper';
5
- export type Key = string | number;
6
- export interface NoticeConfig {
7
- content?: VueNode;
8
- duration?: number | false | null;
9
- showProgress?: boolean;
10
- pauseOnHover?: boolean;
11
- closeIcon?: VueNode;
12
- closable?: boolean | ({
13
- closeIcon?: VueNode;
14
- onClose?: VoidFunction;
15
- } & Record<string, any>);
16
- className?: string;
17
- style?: CSSProperties;
18
- classNames?: {
19
- [key in NoticeSemanticProps]?: string;
20
- };
21
- styles?: {
22
- [key in NoticeSemanticProps]?: CSSProperties;
23
- };
24
- /** @private Internal usage. Do not override in your code */
25
- props?: Record<string, any>;
26
- onClose?: VoidFunction;
27
- onClick?: (event: Event) => void;
28
- }
29
- export interface OpenConfig extends NoticeConfig {
30
- key: Key;
31
- placement?: Placement;
32
- content?: VueNode;
33
- duration?: number | false | null;
34
- }
35
- export type InnerOpenConfig = OpenConfig & {
36
- times?: number;
37
- };
38
- export type Placements = Partial<Record<Placement, OpenConfig[]>>;
39
- export type StackConfig = boolean | {
40
- /**
41
- * When number is greater than threshold, notifications will be stacked together.
42
- * @default 3
43
- */
44
- threshold?: number;
45
- /**
46
- * Offset when notifications are stacked together.
47
- * @default 8
48
- */
49
- offset?: number;
50
- /**
51
- * Spacing between each notification when expanded.
52
- */
53
- gap?: number;
54
- };
55
- export {};
package/dist/interface.js DELETED
File without changes
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
package/src/interface.ts DELETED
@@ -1,61 +0,0 @@
1
- import type { VueNode } from '@v-c/util/dist/type'
2
- import type { CSSProperties } from 'vue'
3
-
4
- export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight'
5
-
6
- type NoticeSemanticProps = 'wrapper'
7
-
8
- export type Key = string | number
9
- export interface NoticeConfig {
10
- content?: VueNode
11
- duration?: number | false | null
12
- showProgress?: boolean
13
- pauseOnHover?: boolean
14
- closeIcon?: VueNode
15
- closable?:
16
- | boolean
17
- | ({ closeIcon?: VueNode, onClose?: VoidFunction } & Record<string, any>)
18
- className?: string
19
- style?: CSSProperties
20
- classNames?: {
21
- [key in NoticeSemanticProps]?: string;
22
- }
23
- styles?: {
24
- [key in NoticeSemanticProps]?: CSSProperties;
25
- }
26
- /** @private Internal usage. Do not override in your code */
27
- props?: Record<string, any>
28
-
29
- onClose?: VoidFunction
30
- onClick?: (event: Event) => void
31
- }
32
-
33
- export interface OpenConfig extends NoticeConfig {
34
- key: Key
35
- placement?: Placement
36
- content?: VueNode
37
- duration?: number | false | null
38
- }
39
-
40
- export type InnerOpenConfig = OpenConfig & { times?: number }
41
-
42
- export type Placements = Partial<Record<Placement, OpenConfig[]>>
43
-
44
- export type StackConfig
45
- = | boolean
46
- | {
47
- /**
48
- * When number is greater than threshold, notifications will be stacked together.
49
- * @default 3
50
- */
51
- threshold?: number
52
- /**
53
- * Offset when notifications are stacked together.
54
- * @default 8
55
- */
56
- offset?: number
57
- /**
58
- * Spacing between each notification when expanded.
59
- */
60
- gap?: number
61
- }