Skip to content

Commit 517fbab

Browse files
Merge branch '25_1' into 25_1_ng_nested_fix
2 parents a15d846 + be3fa25 commit 517fbab

File tree

1 file changed

+220
-111
lines changed

1 file changed

+220
-111
lines changed

packages/devextreme/js/__internal/ui/m_notify.ts

Lines changed: 220 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,266 @@
1+
import type { dxElementWrapper } from '@js/core/renderer';
12
import $ from '@js/core/renderer';
2-
import { extend } from '@js/core/utils/extend';
33
import { isPlainObject, isString } from '@js/core/utils/type';
44
import { value as viewPort } from '@js/core/utils/view_port';
55
import { getWindow } from '@js/core/utils/window';
6+
import type {
7+
HiddenEvent,
8+
Properties as ToastProperties,
9+
ShowingEvent,
10+
} from '@js/ui/toast';
611
import Toast from '@js/ui/toast';
712

13+
type DefaultDirection = 'down-push' | 'up-push';
14+
15+
type NotifyType = 'info' | 'warning' | 'error' | 'success';
16+
17+
interface Dimensions {
18+
toastWidth: number;
19+
toastHeight: number;
20+
windowHeight: number;
21+
windowWidth: number;
22+
}
23+
24+
interface NotifyCoordinates {
25+
top?: number;
26+
bottom?: number;
27+
left?: number;
28+
right?: number;
29+
}
30+
31+
interface PositionStyles {
32+
top: number | string;
33+
bottom: number | string;
34+
left: number | string;
35+
right: number | string;
36+
}
37+
38+
type CoordinateFunction = (dimensions: Dimensions) => NotifyCoordinates;
39+
type PositionStylesFunction = (
40+
coordinates: NotifyCoordinates,
41+
dimensions: Dimensions,
42+
) => PositionStyles;
43+
844
const window = getWindow();
9-
let $notify = null;
10-
const $containers = {};
1145

12-
function notify(message, /* optional */ typeOrStack, displayTime) {
13-
const options = isPlainObject(message) ? message : { message };
14-
const stack = isPlainObject(typeOrStack) ? typeOrStack : undefined;
15-
const type = isPlainObject(typeOrStack) ? undefined : typeOrStack;
16-
const { onHidden: userOnHidden } = options;
46+
let $notify: dxElementWrapper | null = null;
1747

18-
if (stack?.position) {
19-
const { position } = stack;
20-
const direction = stack.direction || getDefaultDirection(position);
21-
const containerKey = isString(position)
22-
? position
23-
: `${position.top}-${position.left}-${position.bottom}-${position.right}`;
48+
const $containers: Record<string, dxElementWrapper> = {};
2449

25-
const { onShowing: userOnShowing } = options;
26-
const $container = getStackContainer(containerKey);
27-
setContainerClasses($container, direction);
50+
const COORDINATE_ALIASES: Record<string, NotifyCoordinates | CoordinateFunction> = {
51+
'top left': { top: 10, left: 10 },
52+
'top right': { top: 10, right: 10 },
53+
'bottom left': { bottom: 10, left: 10 },
54+
'bottom right': { bottom: 10, right: 10 },
55+
'top center': (dimensions: Dimensions): NotifyCoordinates => ({
56+
top: 10,
57+
left: Math.round(dimensions.windowWidth / 2 - dimensions.toastWidth / 2),
58+
}),
59+
'left center': (dimensions: Dimensions): NotifyCoordinates => ({
60+
top: Math.round(dimensions.windowHeight / 2 - dimensions.toastHeight / 2),
61+
left: 10,
62+
}),
63+
'right center': (dimensions: Dimensions): NotifyCoordinates => ({
64+
top: Math.round(dimensions.windowHeight / 2 - dimensions.toastHeight / 2),
65+
right: 10,
66+
}),
67+
center: (dimensions: Dimensions): NotifyCoordinates => ({
68+
top: Math.round(dimensions.windowHeight / 2 - dimensions.toastHeight / 2),
69+
left: Math.round(dimensions.windowWidth / 2 - dimensions.toastWidth / 2),
70+
}),
71+
'bottom center': (dimensions: Dimensions): NotifyCoordinates => ({
72+
bottom: 10,
73+
left: Math.round(dimensions.windowWidth / 2 - dimensions.toastWidth / 2),
74+
}),
75+
};
2876

29-
extend(options, {
30-
container: $container,
31-
_skipContentPositioning: true,
32-
onShowing(args) {
33-
setContainerStyles($container, direction, position);
34-
userOnShowing?.(args);
35-
},
36-
});
37-
}
77+
const POSITION_STYLES_MAP: Record<string, PositionStylesFunction> = {
78+
up: (coordinates: NotifyCoordinates, dimensions: Dimensions): PositionStyles => ({
79+
bottom: coordinates.bottom
80+
?? dimensions.windowHeight - dimensions.toastHeight - (coordinates.top ?? 0),
81+
top: '',
82+
left: coordinates.left ?? '',
83+
right: coordinates.right ?? '',
84+
}),
85+
down: (coordinates: NotifyCoordinates, dimensions: Dimensions): PositionStyles => ({
86+
top: coordinates.top
87+
?? dimensions.windowHeight - dimensions.toastHeight - (coordinates.bottom ?? 0),
88+
bottom: '',
89+
left: coordinates.left ?? '',
90+
right: coordinates.right ?? '',
91+
}),
92+
left: (coordinates: NotifyCoordinates, dimensions: Dimensions): PositionStyles => ({
93+
right: coordinates.right
94+
?? dimensions.windowWidth - dimensions.toastWidth - (coordinates.left ?? 0),
95+
left: '',
96+
top: coordinates.top ?? '',
97+
bottom: coordinates.bottom ?? '',
98+
}),
99+
right: (coordinates: NotifyCoordinates, dimensions: Dimensions): PositionStyles => ({
100+
left: coordinates.left
101+
?? dimensions.windowWidth - dimensions.toastWidth - (coordinates.right ?? 0),
102+
right: '',
103+
top: coordinates.top ?? '',
104+
bottom: coordinates.bottom ?? '',
105+
}),
106+
};
38107

39-
extend(options, {
40-
type,
41-
displayTime,
42-
onHidden(args) {
43-
$(args.element).remove();
44-
userOnHidden?.(args);
45-
},
46-
});
47-
// @ts-expect-error
48-
$notify = $('<div>').appendTo(viewPort());
49-
// @ts-expect-error
50-
new Toast($notify, options).show();
51-
}
108+
const getDefaultDirection = (position: string | NotifyCoordinates): DefaultDirection => {
109+
const condition = isString(position) && position.includes('top');
52110

53-
const getDefaultDirection = (position) => (isString(position) && position.includes('top') ? 'down-push' : 'up-push');
111+
return condition ? 'down-push' : 'up-push';
112+
};
54113

55-
const createStackContainer = (key) => {
114+
const createStackContainer = (key: string): dxElementWrapper => {
56115
const $container = $('<div>').appendTo(viewPort());
116+
57117
$containers[key] = $container;
58118

59119
return $container;
60120
};
61121

62-
const getStackContainer = (key) => {
122+
const getStackContainer = (key: string): dxElementWrapper => {
63123
const $container = $containers[key];
64124

65125
return $container || createStackContainer(key);
66126
};
67127

68-
const setContainerClasses = (container, direction) => {
128+
const setContainerClasses = (container: dxElementWrapper, direction: string): void => {
69129
const containerClasses = `dx-toast-stack dx-toast-stack-${direction}-direction`;
70130
container.removeAttr('class').addClass(containerClasses);
71131
};
72132

73-
const setContainerStyles = (container, direction, position) => {
74-
const { offsetWidth: toastWidth, offsetHeight: toastHeight } = container.children().first().get(0);
133+
const getNotifyCoordinatesByAlias = (alias: string, dimensions: Dimensions): NotifyCoordinates => {
134+
const coordinate = alias
135+
? COORDINATE_ALIASES[alias]
136+
: COORDINATE_ALIASES['bottom center'];
137+
138+
return typeof coordinate === 'function' ? coordinate(dimensions) : coordinate;
139+
};
140+
141+
const getPositionStylesByNotifyCoordinates = (
142+
direction: string,
143+
coordinates: NotifyCoordinates,
144+
dimensions: Dimensions,
145+
): PositionStyles => {
146+
const directionKey = direction.replace(/-push|-stack/g, '');
147+
const styleFunction = POSITION_STYLES_MAP[directionKey];
148+
149+
return styleFunction ? styleFunction(coordinates, dimensions) : {
150+
top: '',
151+
bottom: '',
152+
left: '',
153+
right: '',
154+
};
155+
};
156+
157+
const setContainerStyles = (
158+
container: dxElementWrapper,
159+
direction: string,
160+
position: string | NotifyCoordinates,
161+
): void => {
162+
const {
163+
// @ts-expect-error 'offsetWidth' does not exist on type 'Element'
164+
offsetWidth: toastWidth,
165+
// @ts-expect-error 'offsetHeight' does not exist on type 'Element'
166+
offsetHeight: toastHeight,
167+
} = container.children().first().get(0);
75168

76-
const dimensions = {
169+
const dimensions: Dimensions = {
77170
toastWidth,
78171
toastHeight,
79172
windowHeight: window.innerHeight,
80173
windowWidth: window.innerWidth,
81174
};
82175

83-
const coordinates = isString(position) ? getCoordinatesByAlias(position, dimensions) : position;
176+
const coordinates = isString(position)
177+
? getNotifyCoordinatesByAlias(position, dimensions)
178+
: position;
84179

85-
const styles = getPositionStylesByCoordinates(direction, coordinates, dimensions);
180+
const styles = getPositionStylesByNotifyCoordinates(direction, coordinates, dimensions);
86181

87182
container.css(styles);
88183
};
89184

90-
const getCoordinatesByAlias = (alias, {
91-
toastWidth, toastHeight, windowHeight, windowWidth,
92-
}) => {
93-
switch (alias) {
94-
case 'top left':
95-
return { top: 10, left: 10 };
96-
case 'top right':
97-
return { top: 10, right: 10 };
98-
case 'bottom left':
99-
return { bottom: 10, left: 10 };
100-
case 'bottom right':
101-
return { bottom: 10, right: 10 };
102-
case 'top center':
103-
return { top: 10, left: Math.round(windowWidth / 2 - toastWidth / 2) };
104-
case 'left center':
105-
return { top: Math.round(windowHeight / 2 - toastHeight / 2), left: 10 };
106-
case 'right center':
107-
return { top: Math.round(windowHeight / 2 - toastHeight / 2), right: 10 };
108-
case 'center':
109-
return {
110-
top: Math.round(windowHeight / 2 - toastHeight / 2),
111-
left: Math.round(windowWidth / 2 - toastWidth / 2),
112-
};
113-
case 'bottom center':
114-
default:
115-
return { bottom: 10, left: Math.round(windowWidth / 2 - toastWidth / 2) };
116-
}
117-
};
118-
// @ts-expect-error
119-
const getPositionStylesByCoordinates = (direction, coordinates, dimensions) => {
185+
const getToastOptions = (
186+
message: ToastProperties | string,
187+
typeOrStack?: NotifyType,
188+
displayTime?: number,
189+
): ToastProperties => {
190+
const userOptions = isPlainObject(message) ? message : { message };
191+
192+
const stack = isPlainObject(typeOrStack) ? typeOrStack : undefined;
193+
const type = isPlainObject(typeOrStack) ? undefined : typeOrStack;
194+
120195
const {
121-
toastWidth, toastHeight, windowHeight, windowWidth,
122-
} = dimensions;
123-
124-
// eslint-disable-next-line default-case
125-
switch (direction.replace(/-push|-stack/g, '')) {
126-
case 'up':
127-
return {
128-
bottom: coordinates.bottom ?? windowHeight - toastHeight - coordinates.top,
129-
top: '',
130-
left: coordinates.left ?? '',
131-
right: coordinates.right ?? '',
132-
};
133-
case 'down':
134-
return {
135-
top: coordinates.top ?? windowHeight - toastHeight - coordinates.bottom,
136-
bottom: '',
137-
left: coordinates.left ?? '',
138-
right: coordinates.right ?? '',
139-
};
140-
case 'left':
141-
return {
142-
right: coordinates.right ?? windowWidth - toastWidth - coordinates.left,
143-
left: '',
144-
top: coordinates.top ?? '',
145-
bottom: coordinates.bottom ?? '',
146-
};
147-
case 'right':
148-
return {
149-
left: coordinates.left ?? windowWidth - toastWidth - coordinates.right,
150-
right: '',
151-
top: coordinates.top ?? '',
152-
bottom: coordinates.bottom ?? '',
153-
};
196+
onHidden: userOnHidden,
197+
onShowing: userOnShowing,
198+
} = userOptions;
199+
200+
const defaultConfiguration: Partial<ToastProperties> = {
201+
onHidden: (e: HiddenEvent): void => {
202+
$(e.element).remove();
203+
userOnHidden?.(e);
204+
},
205+
};
206+
207+
if (type !== undefined) {
208+
defaultConfiguration.type = type;
209+
}
210+
211+
if (displayTime !== undefined) {
212+
defaultConfiguration.displayTime = displayTime;
213+
}
214+
215+
if (stack?.position) {
216+
const { position } = stack;
217+
218+
const direction = stack.direction || getDefaultDirection(position);
219+
220+
const containerKey = isString(position)
221+
? position
222+
: `${position.top}-${position.left}-${position.bottom}-${position.right}`;
223+
224+
const $container = getStackContainer(containerKey);
225+
226+
setContainerClasses($container, direction);
227+
228+
const options = {
229+
...userOptions,
230+
...defaultConfiguration,
231+
container: $container,
232+
_skipContentPositioning: true,
233+
onShowing: (e: ShowingEvent): void => {
234+
setContainerStyles($container, direction, position);
235+
userOnShowing?.(e);
236+
},
237+
};
238+
239+
return options;
154240
}
241+
242+
const options = {
243+
...userOptions,
244+
...defaultConfiguration,
245+
};
246+
247+
return options;
248+
};
249+
250+
const notify = (
251+
message: ToastProperties | string,
252+
typeOrStack?: NotifyType,
253+
displayTime?: number,
254+
): void => {
255+
const options = getToastOptions(message, typeOrStack, displayTime);
256+
257+
$notify = $('<div>').appendTo(viewPort());
258+
259+
// @ts-expect-error Toast constructor accepts jQuery element
260+
const toast = new Toast($notify, options);
261+
262+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
263+
toast.show();
155264
};
156265

157266
/// #DEBUG

0 commit comments

Comments
 (0)