@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.
- package/dist/Notification.d.ts +286 -0
- package/dist/Notification.js +237 -0
- package/dist/NotificationList/Content.d.ts +88 -0
- package/dist/NotificationList/Content.js +74 -0
- package/dist/NotificationList/index.d.ts +156 -0
- package/dist/NotificationList/index.js +204 -0
- package/dist/NotificationProvider.d.ts +20 -1
- package/dist/NotificationProvider.js +16 -3
- package/dist/Notifications.d.ts +136 -8
- package/dist/Notifications.js +118 -109
- package/dist/Progress.d.ts +8 -0
- package/dist/Progress.js +18 -0
- package/dist/hooks/useClosable.d.ts +22 -0
- package/dist/hooks/useClosable.js +33 -0
- package/dist/hooks/useListPosition/index.d.ts +17 -0
- package/dist/hooks/useListPosition/index.js +48 -0
- package/dist/hooks/useListPosition/useSizes.d.ts +13 -0
- package/dist/hooks/useListPosition/useSizes.js +29 -0
- package/dist/hooks/useNoticeTimer.d.ts +6 -0
- package/dist/hooks/useNoticeTimer.js +71 -0
- package/dist/hooks/useNotification.d.ts +8 -24
- package/dist/hooks/useNotification.js +33 -22
- package/dist/hooks/useStack.d.ts +8 -4
- package/dist/hooks/useStack.js +15 -18
- package/dist/index.d.ts +7 -5
- package/dist/index.js +5 -3
- package/docs/context.vue +1 -1
- package/docs/hooks.vue +4 -4
- package/docs/index.less +62 -143
- package/docs/maxCount.vue +1 -1
- package/docs/showProgress.vue +2 -2
- package/docs/stack.vue +1 -1
- package/package.json +5 -4
- package/src/Notification.tsx +363 -0
- package/src/NotificationList/Content.tsx +84 -0
- package/src/NotificationList/index.tsx +298 -0
- package/src/NotificationProvider.tsx +23 -3
- package/src/Notifications.tsx +103 -87
- package/src/Progress.tsx +23 -0
- package/src/hooks/useClosable.ts +54 -0
- package/src/hooks/useListPosition/index.ts +85 -0
- package/src/hooks/useListPosition/useSizes.ts +42 -0
- package/src/hooks/useNoticeTimer.ts +96 -0
- package/src/hooks/useNotification.tsx +54 -80
- package/src/hooks/useStack.ts +26 -18
- package/src/index.ts +31 -5
- package/tests/index.spec.tsx +200 -0
- package/vite.config.ts +4 -3
- package/vitest.config.ts +3 -1
- 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/dist/interface.d.ts +0 -55
- package/dist/interface.js +0 -0
- package/src/Notice.tsx +0 -212
- package/src/NoticeList.tsx +0 -219
- package/src/interface.ts +0 -61
package/dist/Notifications.js
CHANGED
|
@@ -1,140 +1,149 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Teleport, createVNode, defineComponent, isVNode,
|
|
1
|
+
import NotificationList from "./NotificationList/index.js";
|
|
2
|
+
import { Teleport, createVNode, defineComponent, isVNode, shallowRef, watch } from "vue";
|
|
3
|
+
//#region src/Notifications.tsx
|
|
3
4
|
function _isSlot(s) {
|
|
4
5
|
return typeof s === "function" || Object.prototype.toString.call(s) === "[object Object]" && !isVNode(s);
|
|
5
6
|
}
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const config = configList.value.find((item) => item.key === key);
|
|
11
|
-
const closable = config?.closable;
|
|
12
|
-
(closable && typeof closable === "object" ? closable : {}).onClose?.();
|
|
13
|
-
config?.onClose?.();
|
|
14
|
-
configList.value = configList.value.filter((item) => item.key !== key);
|
|
15
|
-
};
|
|
16
|
-
expose({
|
|
17
|
-
open: (config) => {
|
|
18
|
-
const list = configList.value;
|
|
19
|
-
let clone = [...configList.value];
|
|
20
|
-
const index = clone.findIndex((item) => item.key === config.key);
|
|
21
|
-
const innerConfig = { ...config };
|
|
22
|
-
if (index >= 0) {
|
|
23
|
-
innerConfig.times = (list[index]?.times || 0) + 1;
|
|
24
|
-
clone[index] = innerConfig;
|
|
25
|
-
} else {
|
|
26
|
-
innerConfig.times = 0;
|
|
27
|
-
clone.push(innerConfig);
|
|
28
|
-
}
|
|
29
|
-
const maxCount = props.maxCount ?? 0;
|
|
30
|
-
if (maxCount > 0 && clone.length > maxCount) clone = clone.slice(-maxCount);
|
|
31
|
-
configList.value = clone;
|
|
32
|
-
},
|
|
33
|
-
close: onNoticeClose,
|
|
34
|
-
destroy: () => {
|
|
35
|
-
configList.value = [];
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
const placements = shallowRef({});
|
|
39
|
-
watch(configList, () => {
|
|
40
|
-
const nextPlacements = {};
|
|
41
|
-
configList.value.forEach((config) => {
|
|
42
|
-
const { placement = "topRight" } = config;
|
|
43
|
-
if (placement) {
|
|
44
|
-
nextPlacements[placement] = nextPlacements[placement] || [];
|
|
45
|
-
nextPlacements[placement].push(config);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
Object.keys(placements.value).forEach((_placement) => {
|
|
49
|
-
const placement = _placement;
|
|
50
|
-
nextPlacements[placement] = nextPlacements[placement] || [];
|
|
51
|
-
});
|
|
52
|
-
placements.value = nextPlacements;
|
|
53
|
-
});
|
|
54
|
-
const onAllNoticeRemoved = (placement) => {
|
|
55
|
-
const clone = { ...placements.value };
|
|
56
|
-
if (!(clone[placement] || []).length) delete clone[placement];
|
|
57
|
-
placements.value = clone;
|
|
58
|
-
};
|
|
59
|
-
const emptyRef = shallowRef(false);
|
|
60
|
-
watch(placements, () => {
|
|
61
|
-
if (Object.keys(placements.value).length > 0) emptyRef.value = true;
|
|
62
|
-
else if (emptyRef.value) {
|
|
63
|
-
props?.onAllRemoved?.();
|
|
64
|
-
emptyRef.value = false;
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
return () => {
|
|
68
|
-
let _slot;
|
|
69
|
-
const { container } = props;
|
|
70
|
-
const prefixCls = props.prefixCls ?? defaults.prefixCls ?? "";
|
|
71
|
-
if (!container) return null;
|
|
72
|
-
return createVNode(Teleport, { "to": container }, _isSlot(_slot = Object.keys(placements.value).map((placement) => {
|
|
73
|
-
const placementConfigList = placements.value[placement];
|
|
74
|
-
const list = createVNode(NoticeList_default, {
|
|
75
|
-
"key": placement,
|
|
76
|
-
"configList": placementConfigList,
|
|
77
|
-
"placement": placement,
|
|
78
|
-
"prefixCls": prefixCls,
|
|
79
|
-
"class": props.className?.(placement),
|
|
80
|
-
"style": props.style?.(placement),
|
|
81
|
-
"motion": props.motion,
|
|
82
|
-
"stack": props.stack,
|
|
83
|
-
"onAllNoticeRemoved": () => onAllNoticeRemoved(placement),
|
|
84
|
-
"onNoticeClose": onNoticeClose
|
|
85
|
-
}, null);
|
|
86
|
-
return props.renderNotifications ? props.renderNotifications(list, {
|
|
87
|
-
prefixCls,
|
|
88
|
-
key: placement
|
|
89
|
-
}) : list;
|
|
90
|
-
})) ? _slot : { default: () => [_slot] });
|
|
91
|
-
};
|
|
92
|
-
}, {
|
|
93
|
-
props: /* @__PURE__ */ mergeDefaults({
|
|
7
|
+
var Notifications = /* @__PURE__ */ defineComponent({
|
|
8
|
+
name: "Notifications",
|
|
9
|
+
inheritAttrs: false,
|
|
10
|
+
props: {
|
|
94
11
|
prefixCls: {
|
|
95
12
|
type: String,
|
|
96
|
-
|
|
97
|
-
default: void 0
|
|
13
|
+
default: "vc-notification"
|
|
98
14
|
},
|
|
99
|
-
|
|
100
|
-
type:
|
|
101
|
-
required: false,
|
|
15
|
+
classNames: {
|
|
16
|
+
type: Object,
|
|
102
17
|
default: void 0
|
|
103
18
|
},
|
|
104
|
-
|
|
105
|
-
|
|
19
|
+
styles: {
|
|
20
|
+
type: Object,
|
|
106
21
|
default: void 0
|
|
107
22
|
},
|
|
108
|
-
|
|
109
|
-
type:
|
|
110
|
-
required: false,
|
|
23
|
+
components: {
|
|
24
|
+
type: Object,
|
|
111
25
|
default: void 0
|
|
112
26
|
},
|
|
113
27
|
className: {
|
|
114
28
|
type: Function,
|
|
115
|
-
required: false,
|
|
116
29
|
default: void 0
|
|
117
30
|
},
|
|
118
31
|
style: {
|
|
119
32
|
type: Function,
|
|
120
|
-
required: false,
|
|
121
33
|
default: void 0
|
|
122
34
|
},
|
|
123
|
-
|
|
124
|
-
|
|
35
|
+
container: {
|
|
36
|
+
type: Object,
|
|
37
|
+
default: void 0
|
|
38
|
+
},
|
|
39
|
+
motion: {
|
|
40
|
+
type: [Object, Function],
|
|
41
|
+
default: void 0
|
|
42
|
+
},
|
|
43
|
+
maxCount: {
|
|
44
|
+
type: Number,
|
|
45
|
+
default: void 0
|
|
46
|
+
},
|
|
47
|
+
pauseOnHover: {
|
|
48
|
+
type: Boolean,
|
|
125
49
|
default: void 0
|
|
126
50
|
},
|
|
127
51
|
stack: {
|
|
128
52
|
type: [Boolean, Object],
|
|
129
|
-
|
|
53
|
+
default: void 0
|
|
54
|
+
},
|
|
55
|
+
onAllRemoved: {
|
|
56
|
+
type: Function,
|
|
130
57
|
default: void 0
|
|
131
58
|
},
|
|
132
59
|
renderNotifications: {
|
|
133
60
|
type: Function,
|
|
134
|
-
required: false,
|
|
135
61
|
default: void 0
|
|
136
62
|
}
|
|
137
|
-
},
|
|
138
|
-
|
|
63
|
+
},
|
|
64
|
+
setup(props, { expose }) {
|
|
65
|
+
const configList = shallowRef([]);
|
|
66
|
+
const placements = shallowRef({});
|
|
67
|
+
const emptyRef = shallowRef(false);
|
|
68
|
+
expose({
|
|
69
|
+
open: (config) => {
|
|
70
|
+
const list = configList.value;
|
|
71
|
+
let clone = [...list];
|
|
72
|
+
const index = clone.findIndex((item) => item.key === config.key);
|
|
73
|
+
const innerConfig = { ...config };
|
|
74
|
+
if (index >= 0) {
|
|
75
|
+
innerConfig.times = (list[index]?.times ?? 0) + 1;
|
|
76
|
+
clone[index] = innerConfig;
|
|
77
|
+
} else {
|
|
78
|
+
innerConfig.times = 0;
|
|
79
|
+
clone.push(innerConfig);
|
|
80
|
+
}
|
|
81
|
+
const maxCount = props.maxCount ?? 0;
|
|
82
|
+
if (maxCount > 0 && clone.length > maxCount) clone = clone.slice(-maxCount);
|
|
83
|
+
configList.value = clone;
|
|
84
|
+
},
|
|
85
|
+
close: (key) => {
|
|
86
|
+
configList.value = configList.value.filter((item) => item.key !== key);
|
|
87
|
+
},
|
|
88
|
+
destroy: () => {
|
|
89
|
+
configList.value = [];
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
watch(configList, () => {
|
|
93
|
+
const next = {};
|
|
94
|
+
configList.value.forEach((config) => {
|
|
95
|
+
const placement = config.placement ?? "topRight";
|
|
96
|
+
next[placement] = next[placement] || [];
|
|
97
|
+
next[placement].push(config);
|
|
98
|
+
});
|
|
99
|
+
Object.keys(placements.value).forEach((placement) => {
|
|
100
|
+
next[placement] = next[placement] || [];
|
|
101
|
+
});
|
|
102
|
+
placements.value = next;
|
|
103
|
+
}, { immediate: true });
|
|
104
|
+
function onAllNoticeRemoved(placement) {
|
|
105
|
+
const clone = { ...placements.value };
|
|
106
|
+
if (!(clone[placement] || []).length) delete clone[placement];
|
|
107
|
+
placements.value = clone;
|
|
108
|
+
}
|
|
109
|
+
watch(placements, () => {
|
|
110
|
+
if (Object.keys(placements.value).length > 0) emptyRef.value = true;
|
|
111
|
+
else if (emptyRef.value) {
|
|
112
|
+
props.onAllRemoved?.();
|
|
113
|
+
emptyRef.value = false;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return () => {
|
|
117
|
+
let _slot;
|
|
118
|
+
const { container, prefixCls = "vc-notification" } = props;
|
|
119
|
+
if (!container) return null;
|
|
120
|
+
const placementList = Object.keys(placements.value);
|
|
121
|
+
return createVNode(Teleport, { "to": container }, _isSlot(_slot = placementList.map((placement) => {
|
|
122
|
+
const list = createVNode(NotificationList, {
|
|
123
|
+
"key": placement,
|
|
124
|
+
"configList": placements.value[placement],
|
|
125
|
+
"placement": placement,
|
|
126
|
+
"prefixCls": prefixCls,
|
|
127
|
+
"pauseOnHover": props.pauseOnHover,
|
|
128
|
+
"classNames": props.classNames,
|
|
129
|
+
"styles": props.styles,
|
|
130
|
+
"components": props.components,
|
|
131
|
+
"class": props.className?.(placement),
|
|
132
|
+
"style": props.style?.(placement),
|
|
133
|
+
"motion": props.motion,
|
|
134
|
+
"stack": props.stack,
|
|
135
|
+
"onNoticeClose": (key) => {
|
|
136
|
+
configList.value = configList.value.filter((item) => item.key !== key);
|
|
137
|
+
},
|
|
138
|
+
"onAllRemoved": onAllNoticeRemoved
|
|
139
|
+
}, null);
|
|
140
|
+
return props.renderNotifications ? props.renderNotifications(list, {
|
|
141
|
+
prefixCls,
|
|
142
|
+
key: placement
|
|
143
|
+
}) : list;
|
|
144
|
+
})) ? _slot : { default: () => [_slot] });
|
|
145
|
+
};
|
|
146
|
+
}
|
|
139
147
|
});
|
|
140
|
-
|
|
148
|
+
//#endregion
|
|
149
|
+
export { Notifications as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CSSProperties, FunctionalComponent } from 'vue';
|
|
2
|
+
export interface NotificationProgressProps {
|
|
3
|
+
class?: string;
|
|
4
|
+
style?: CSSProperties;
|
|
5
|
+
percent: number;
|
|
6
|
+
}
|
|
7
|
+
declare const Progress: FunctionalComponent<NotificationProgressProps>;
|
|
8
|
+
export default Progress;
|
package/dist/Progress.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createVNode } from "vue";
|
|
2
|
+
//#region src/Progress.tsx
|
|
3
|
+
var Progress = (props) => {
|
|
4
|
+
return createVNode("progress", {
|
|
5
|
+
"class": props.class,
|
|
6
|
+
"max": "100",
|
|
7
|
+
"value": props.percent,
|
|
8
|
+
"style": props.style
|
|
9
|
+
}, null);
|
|
10
|
+
};
|
|
11
|
+
Progress.props = [
|
|
12
|
+
"class",
|
|
13
|
+
"style",
|
|
14
|
+
"percent"
|
|
15
|
+
];
|
|
16
|
+
Progress.inheritAttrs = false;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { Progress as default };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { VueNode } from '@v-c/util/dist/type';
|
|
2
|
+
import { ComputedRef, MaybeRef } from 'vue';
|
|
3
|
+
export type ClosableConfig = {
|
|
4
|
+
closeIcon?: VueNode;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
onClose?: VoidFunction;
|
|
7
|
+
} & Record<string, any>;
|
|
8
|
+
export type ClosableType = boolean | ClosableConfig | null | undefined;
|
|
9
|
+
export interface ParsedClosableConfig {
|
|
10
|
+
closeIcon: VueNode;
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
onClose?: VoidFunction;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Normalize the closable option into an enabled flag, parsed config, and aria props.
|
|
17
|
+
*/
|
|
18
|
+
export default function useClosable(closable: MaybeRef<ClosableType>): [
|
|
19
|
+
ComputedRef<boolean>,
|
|
20
|
+
ComputedRef<ParsedClosableConfig>,
|
|
21
|
+
ComputedRef<Record<string, any>>
|
|
22
|
+
];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { computed, unref } from "vue";
|
|
2
|
+
import pickAttrs from "@v-c/util/dist/pickAttrs";
|
|
3
|
+
//#region src/hooks/useClosable.ts
|
|
4
|
+
/**
|
|
5
|
+
* Normalize the closable option into an enabled flag, parsed config, and aria props.
|
|
6
|
+
*/
|
|
7
|
+
function useClosable(closable) {
|
|
8
|
+
const closableObj = computed(() => {
|
|
9
|
+
const value = unref(closable);
|
|
10
|
+
if (value === false) return {
|
|
11
|
+
closeIcon: null,
|
|
12
|
+
disabled: true
|
|
13
|
+
};
|
|
14
|
+
if (typeof value === "object" && value !== null) return value;
|
|
15
|
+
return {};
|
|
16
|
+
});
|
|
17
|
+
const enabled = computed(() => !!unref(closable));
|
|
18
|
+
const closableConfig = computed(() => {
|
|
19
|
+
const obj = closableObj.value;
|
|
20
|
+
return {
|
|
21
|
+
...obj,
|
|
22
|
+
closeIcon: "closeIcon" in obj ? obj.closeIcon : "×",
|
|
23
|
+
disabled: obj.disabled ?? false
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
return [
|
|
27
|
+
enabled,
|
|
28
|
+
closableConfig,
|
|
29
|
+
computed(() => pickAttrs(closableConfig.value, true))
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { useClosable as default };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ComputedRef, MaybeRef } from 'vue';
|
|
2
|
+
import { StackConfig } from '../useStack';
|
|
3
|
+
type Key = string | number | symbol;
|
|
4
|
+
export interface ConfigItem {
|
|
5
|
+
key: Key;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Calculates each notification's position and the full list height.
|
|
9
|
+
*/
|
|
10
|
+
export default function useListPosition(configList: MaybeRef<readonly ConfigItem[]>, stack: MaybeRef<StackConfig | undefined>, gap?: MaybeRef<number>): [
|
|
11
|
+
ComputedRef<Map<string, number>>,
|
|
12
|
+
(key: string, node: HTMLElement | null) => void,
|
|
13
|
+
ComputedRef<number>,
|
|
14
|
+
ComputedRef<number | undefined>,
|
|
15
|
+
ComputedRef<number | undefined>
|
|
16
|
+
];
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import useSizes from "./useSizes.js";
|
|
2
|
+
import { computed, unref } from "vue";
|
|
3
|
+
//#region src/hooks/useListPosition/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Calculates each notification's position and the full list height.
|
|
6
|
+
*/
|
|
7
|
+
function useListPosition(configList, stack, gap = 0) {
|
|
8
|
+
const [sizeMap, setNodeSize] = useSizes();
|
|
9
|
+
const result = computed(() => {
|
|
10
|
+
const list = unref(configList);
|
|
11
|
+
const stackValue = unref(stack);
|
|
12
|
+
const gapValue = unref(gap) ?? 0;
|
|
13
|
+
let offsetY = 0;
|
|
14
|
+
let nextTotalHeight = 0;
|
|
15
|
+
const stackThreshold = stackValue?.threshold ?? 0;
|
|
16
|
+
const positions = /* @__PURE__ */ new Map();
|
|
17
|
+
let topNoticeHeight;
|
|
18
|
+
let topNoticeWidth;
|
|
19
|
+
list.slice().reverse().forEach((config, index) => {
|
|
20
|
+
const key = String(config.key);
|
|
21
|
+
const height = sizeMap.value[key]?.height ?? 0;
|
|
22
|
+
const y = stackValue && index > 0 ? offsetY + (stackValue.offset ?? 0) - height : offsetY;
|
|
23
|
+
positions.set(key, y);
|
|
24
|
+
if (index === 0) {
|
|
25
|
+
topNoticeHeight = height;
|
|
26
|
+
topNoticeWidth = sizeMap.value[key]?.width ?? 0;
|
|
27
|
+
}
|
|
28
|
+
if (!stackValue || index < stackThreshold) nextTotalHeight = Math.max(nextTotalHeight, y + height);
|
|
29
|
+
if (stackValue) offsetY = y + height;
|
|
30
|
+
else offsetY += height + gapValue;
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
positions,
|
|
34
|
+
totalHeight: nextTotalHeight,
|
|
35
|
+
topNoticeHeight,
|
|
36
|
+
topNoticeWidth
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
return [
|
|
40
|
+
computed(() => result.value.positions),
|
|
41
|
+
setNodeSize,
|
|
42
|
+
computed(() => result.value.totalHeight),
|
|
43
|
+
computed(() => result.value.topNoticeHeight),
|
|
44
|
+
computed(() => result.value.topNoticeWidth)
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { useListPosition as default };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
export interface NodeSize {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}
|
|
6
|
+
export type NodeSizeMap = Record<string, NodeSize>;
|
|
7
|
+
/**
|
|
8
|
+
* Track measured node sizes by key. Returns the size map ref and a setter callback.
|
|
9
|
+
*/
|
|
10
|
+
export default function useSizes(): [
|
|
11
|
+
Ref<NodeSizeMap>,
|
|
12
|
+
(key: string, node: HTMLElement | null) => void
|
|
13
|
+
];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
//#region src/hooks/useListPosition/useSizes.ts
|
|
3
|
+
/**
|
|
4
|
+
* Track measured node sizes by key. Returns the size map ref and a setter callback.
|
|
5
|
+
*/
|
|
6
|
+
function useSizes() {
|
|
7
|
+
const sizeMap = ref({});
|
|
8
|
+
function setNodeSize(key, node) {
|
|
9
|
+
if (!node) {
|
|
10
|
+
if (!(key in sizeMap.value)) return;
|
|
11
|
+
const { [key]: _, ...rest } = sizeMap.value;
|
|
12
|
+
sizeMap.value = rest;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const next = {
|
|
16
|
+
width: node.offsetWidth,
|
|
17
|
+
height: node.offsetHeight
|
|
18
|
+
};
|
|
19
|
+
const prev = sizeMap.value[key];
|
|
20
|
+
if (prev && prev.width === next.width && prev.height === next.height) return;
|
|
21
|
+
sizeMap.value = {
|
|
22
|
+
...sizeMap.value,
|
|
23
|
+
[key]: next
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return [sizeMap, setNodeSize];
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { useSizes as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ComputedRef, MaybeRef } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Run the auto-close timer for a notice and report progress updates.
|
|
4
|
+
* Returns controls to pause and resume the timer.
|
|
5
|
+
*/
|
|
6
|
+
export default function useNoticeTimer(duration: MaybeRef<number | false | null | undefined>, onClose: () => void, onUpdate: (ptg: number) => void): [() => void, () => void, ComputedRef<number>];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { computed, onBeforeUnmount, shallowRef, unref, watch } from "vue";
|
|
2
|
+
import raf from "@v-c/util/dist/raf";
|
|
3
|
+
//#region src/hooks/useNoticeTimer.ts
|
|
4
|
+
/**
|
|
5
|
+
* Run the auto-close timer for a notice and report progress updates.
|
|
6
|
+
* Returns controls to pause and resume the timer.
|
|
7
|
+
*/
|
|
8
|
+
function useNoticeTimer(duration, onClose, onUpdate) {
|
|
9
|
+
const durationMs = computed(() => {
|
|
10
|
+
const value = unref(duration);
|
|
11
|
+
return Math.max(typeof value === "number" ? value : 0, 0) * 1e3;
|
|
12
|
+
});
|
|
13
|
+
const walking = shallowRef(durationMs.value > 0);
|
|
14
|
+
const passTime = shallowRef(0);
|
|
15
|
+
let lastRafTime = null;
|
|
16
|
+
let rafId = null;
|
|
17
|
+
function syncPassTime() {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
if (lastRafTime !== null) passTime.value += now - lastRafTime;
|
|
20
|
+
lastRafTime = now;
|
|
21
|
+
}
|
|
22
|
+
function cancelRaf() {
|
|
23
|
+
if (rafId !== null) {
|
|
24
|
+
raf.cancel(rafId);
|
|
25
|
+
rafId = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function onPause() {
|
|
29
|
+
syncPassTime();
|
|
30
|
+
walking.value = false;
|
|
31
|
+
}
|
|
32
|
+
function onResume() {
|
|
33
|
+
if (durationMs.value > 0) {
|
|
34
|
+
lastRafTime = Date.now();
|
|
35
|
+
walking.value = true;
|
|
36
|
+
} else onUpdate(0);
|
|
37
|
+
}
|
|
38
|
+
watch(durationMs, () => {
|
|
39
|
+
passTime.value = 0;
|
|
40
|
+
lastRafTime = null;
|
|
41
|
+
walking.value = durationMs.value > 0;
|
|
42
|
+
});
|
|
43
|
+
watch(walking, (isWalking) => {
|
|
44
|
+
cancelRaf();
|
|
45
|
+
if (!isWalking) return;
|
|
46
|
+
function step() {
|
|
47
|
+
syncPassTime();
|
|
48
|
+
if (passTime.value >= durationMs.value) {
|
|
49
|
+
onUpdate(1);
|
|
50
|
+
onClose();
|
|
51
|
+
} else {
|
|
52
|
+
onUpdate(Math.min(passTime.value / durationMs.value, 1));
|
|
53
|
+
rafId = raf(step);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
step();
|
|
57
|
+
}, { immediate: true });
|
|
58
|
+
onBeforeUnmount(() => {
|
|
59
|
+
cancelRaf();
|
|
60
|
+
});
|
|
61
|
+
return [
|
|
62
|
+
onResume,
|
|
63
|
+
onPause,
|
|
64
|
+
computed(() => {
|
|
65
|
+
if (durationMs.value <= 0) return 0;
|
|
66
|
+
return Math.min(passTime.value / durationMs.value, 1);
|
|
67
|
+
})
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { useNoticeTimer as default };
|
|
@@ -1,31 +1,15 @@
|
|
|
1
1
|
import { VueNode } from '@v-c/util/dist/type';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { MaybeRef } from 'vue';
|
|
3
|
+
import { NotificationListConfig, Placement } from '../NotificationList';
|
|
4
4
|
import { NotificationsProps } from '../Notifications';
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
type Key = string | number | symbol;
|
|
6
|
+
type OptionalConfig = Partial<NotificationListConfig>;
|
|
7
|
+
export interface NotificationConfig extends Omit<NotificationsProps, 'container'> {
|
|
8
|
+
placement?: Placement;
|
|
9
9
|
getContainer?: () => HTMLElement | ShadowRoot;
|
|
10
|
-
|
|
11
|
-
closeIcon?: VueNode;
|
|
12
|
-
closable?: boolean | ({
|
|
13
|
-
closeIcon?: VueNode;
|
|
14
|
-
onClose?: VoidFunction;
|
|
15
|
-
} & Record<string, any>);
|
|
16
|
-
maxCount?: number;
|
|
10
|
+
closable?: NotificationListConfig['closable'];
|
|
17
11
|
duration?: number | false | null;
|
|
18
|
-
showProgress?:
|
|
19
|
-
pauseOnHover?: boolean;
|
|
20
|
-
/** @private. Config for notification holder style. Safe to remove if refactor */
|
|
21
|
-
className?: (placement: Placement) => string;
|
|
22
|
-
/** @private. Config for notification holder style. Safe to remove if refactor */
|
|
23
|
-
style?: (placement: Placement) => CSSProperties;
|
|
24
|
-
/** @private Trigger when all the notification closed. */
|
|
25
|
-
onAllRemoved?: VoidFunction;
|
|
26
|
-
stack?: StackConfig;
|
|
27
|
-
/** @private Slot for style in Notifications */
|
|
28
|
-
renderNotifications?: NotificationsProps['renderNotifications'];
|
|
12
|
+
showProgress?: NotificationListConfig['showProgress'];
|
|
29
13
|
}
|
|
30
14
|
export interface NotificationAPI {
|
|
31
15
|
open: (config: OptionalConfig) => void;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Notifications from "../Notifications.js";
|
|
2
2
|
import { computed, createVNode, onMounted, shallowRef, unref, watch } from "vue";
|
|
3
|
+
//#region src/hooks/useNotification.tsx
|
|
3
4
|
var defaultGetContainer = () => document.body;
|
|
4
5
|
var uniqueKey = 0;
|
|
5
6
|
function mergeConfig(...objList) {
|
|
6
7
|
const clone = {};
|
|
7
8
|
objList.forEach((obj) => {
|
|
8
9
|
if (obj) Object.keys(obj).forEach((key) => {
|
|
9
|
-
const
|
|
10
|
-
if (
|
|
10
|
+
const value = obj[key];
|
|
11
|
+
if (value !== void 0) clone[key] = value;
|
|
11
12
|
});
|
|
12
13
|
});
|
|
13
14
|
return clone;
|
|
@@ -15,37 +16,46 @@ function mergeConfig(...objList) {
|
|
|
15
16
|
function useNotification(rootConfig = {}) {
|
|
16
17
|
const configRef = computed(() => unref(rootConfig) || {});
|
|
17
18
|
const container = shallowRef();
|
|
18
|
-
const
|
|
19
|
+
const notificationsRef = shallowRef();
|
|
20
|
+
const taskQueue = shallowRef([]);
|
|
19
21
|
const shareConfig = computed(() => {
|
|
20
|
-
const {
|
|
21
|
-
return
|
|
22
|
+
const { placement, closable, duration, showProgress } = configRef.value;
|
|
23
|
+
return {
|
|
24
|
+
placement,
|
|
25
|
+
closable,
|
|
26
|
+
duration,
|
|
27
|
+
showProgress
|
|
28
|
+
};
|
|
22
29
|
});
|
|
23
|
-
|
|
30
|
+
function resolveContainer() {
|
|
24
31
|
return (configRef.value.getContainer || defaultGetContainer)();
|
|
25
|
-
}
|
|
26
|
-
const contextHolder = () => createVNode(
|
|
32
|
+
}
|
|
33
|
+
const contextHolder = () => createVNode(Notifications, {
|
|
27
34
|
"container": container.value,
|
|
28
|
-
"ref":
|
|
35
|
+
"ref": notificationsRef,
|
|
29
36
|
"prefixCls": configRef.value.prefixCls,
|
|
30
37
|
"motion": configRef.value.motion,
|
|
31
38
|
"maxCount": configRef.value.maxCount,
|
|
39
|
+
"pauseOnHover": configRef.value.pauseOnHover,
|
|
40
|
+
"classNames": configRef.value.classNames,
|
|
41
|
+
"styles": configRef.value.styles,
|
|
42
|
+
"components": configRef.value.components,
|
|
32
43
|
"className": configRef.value.className,
|
|
33
44
|
"style": configRef.value.style,
|
|
34
45
|
"onAllRemoved": configRef.value.onAllRemoved,
|
|
35
46
|
"stack": configRef.value.stack,
|
|
36
47
|
"renderNotifications": configRef.value.renderNotifications
|
|
37
48
|
}, null);
|
|
38
|
-
const taskQueue = shallowRef([]);
|
|
39
49
|
const api = {
|
|
40
50
|
open(config) {
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
51
|
+
const merged = mergeConfig(shareConfig.value, config);
|
|
52
|
+
if (merged.key === null || merged.key === void 0) {
|
|
53
|
+
merged.key = `vc-notification-${uniqueKey}`;
|
|
44
54
|
uniqueKey += 1;
|
|
45
55
|
}
|
|
46
56
|
taskQueue.value = [...taskQueue.value, {
|
|
47
57
|
type: "open",
|
|
48
|
-
config:
|
|
58
|
+
config: merged
|
|
49
59
|
}];
|
|
50
60
|
},
|
|
51
61
|
close(key) {
|
|
@@ -65,24 +75,25 @@ function useNotification(rootConfig = {}) {
|
|
|
65
75
|
container.value = resolveContainer();
|
|
66
76
|
});
|
|
67
77
|
watch(taskQueue, () => {
|
|
68
|
-
if (
|
|
69
|
-
taskQueue.value
|
|
78
|
+
if (notificationsRef.value && taskQueue.value.length) {
|
|
79
|
+
const tasks = taskQueue.value;
|
|
80
|
+
tasks.forEach((task) => {
|
|
70
81
|
switch (task.type) {
|
|
71
82
|
case "open":
|
|
72
|
-
|
|
83
|
+
notificationsRef.value?.open(task.config);
|
|
73
84
|
break;
|
|
74
85
|
case "close":
|
|
75
|
-
|
|
86
|
+
notificationsRef.value?.close(task.key);
|
|
76
87
|
break;
|
|
77
88
|
case "destroy":
|
|
78
|
-
|
|
89
|
+
notificationsRef.value?.destroy();
|
|
79
90
|
break;
|
|
80
|
-
default: break;
|
|
81
91
|
}
|
|
82
92
|
});
|
|
83
|
-
taskQueue.value = taskQueue.value.filter((task) => !
|
|
93
|
+
taskQueue.value = taskQueue.value.filter((task) => !tasks.includes(task));
|
|
84
94
|
}
|
|
85
95
|
});
|
|
86
96
|
return [api, contextHolder];
|
|
87
97
|
}
|
|
98
|
+
//#endregion
|
|
88
99
|
export { useNotification as default };
|