@alfalab/core-components-base-modal 5.2.1 → 5.3.0
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/Component.js +1 -1
- package/esm/Component.js +1 -1
- package/esm/index.css +13 -13
- package/index.css +13 -13
- package/modern/Component.js +1 -1
- package/modern/index.css +13 -13
- package/package.json +5 -5
- package/src/Component.tsx +577 -0
- package/src/index.module.css +60 -0
- package/src/index.ts +2 -0
- package/src/matches-polyfill.ts +19 -0
- package/src/utils.ts +128 -0
package/Component.js
CHANGED
|
@@ -23,7 +23,7 @@ var FocusLock__default = /*#__PURE__*/_interopDefaultCompat(FocusLock);
|
|
|
23
23
|
var mergeRefs__default = /*#__PURE__*/_interopDefaultCompat(mergeRefs);
|
|
24
24
|
var cn__default = /*#__PURE__*/_interopDefaultCompat(cn);
|
|
25
25
|
|
|
26
|
-
var styles = {"component":"base-
|
|
26
|
+
var styles = {"component":"base-modal__component_mx6kj","wrapper":"base-modal__wrapper_mx6kj","content":"base-modal__content_mx6kj","hidden":"base-modal__hidden_mx6kj","backdrop":"base-modal__backdrop_mx6kj","appear":"base-modal__appear_mx6kj","enter":"base-modal__enter_mx6kj","appearActive":"base-modal__appearActive_mx6kj","enterActive":"base-modal__enterActive_mx6kj","exit":"base-modal__exit_mx6kj","exitActive":"base-modal__exitActive_mx6kj","exitDone":"base-modal__exitDone_mx6kj"};
|
|
27
27
|
require('./index.css')
|
|
28
28
|
|
|
29
29
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
package/esm/Component.js
CHANGED
|
@@ -12,7 +12,7 @@ import { isScrolledToTop, isScrolledToBottom, handleContainer, restoreContainerS
|
|
|
12
12
|
import './matches-polyfill.js';
|
|
13
13
|
import '@alfalab/core-components-global-store/esm';
|
|
14
14
|
|
|
15
|
-
var styles = {"component":"base-
|
|
15
|
+
var styles = {"component":"base-modal__component_mx6kj","wrapper":"base-modal__wrapper_mx6kj","content":"base-modal__content_mx6kj","hidden":"base-modal__hidden_mx6kj","backdrop":"base-modal__backdrop_mx6kj","appear":"base-modal__appear_mx6kj","enter":"base-modal__enter_mx6kj","appearActive":"base-modal__appearActive_mx6kj","enterActive":"base-modal__enterActive_mx6kj","exit":"base-modal__exit_mx6kj","exitActive":"base-modal__exitActive_mx6kj","exitDone":"base-modal__exitDone_mx6kj"};
|
|
16
16
|
require('./index.css')
|
|
17
17
|
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
package/esm/index.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* hash:
|
|
1
|
+
/* hash: vww7l */
|
|
2
2
|
:root {
|
|
3
3
|
} /* deprecated */ :root {
|
|
4
4
|
--color-light-bg-primary: #fff; /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
} :root {
|
|
16
16
|
} :root {
|
|
17
17
|
} :root {
|
|
18
|
-
} .base-
|
|
18
|
+
} .base-modal__component_mx6kj {
|
|
19
19
|
position: relative;
|
|
20
20
|
box-sizing: border-box;
|
|
21
21
|
background: var(--color-light-bg-primary);
|
|
22
22
|
margin: auto;
|
|
23
23
|
flex-shrink: 0;
|
|
24
|
-
} .base-
|
|
24
|
+
} .base-modal__wrapper_mx6kj {
|
|
25
25
|
position: fixed;
|
|
26
26
|
top: 0;
|
|
27
27
|
left: 0;
|
|
@@ -33,27 +33,27 @@
|
|
|
33
33
|
flex-direction: column;
|
|
34
34
|
align-items: center;
|
|
35
35
|
outline: 0;
|
|
36
|
-
} .base-
|
|
36
|
+
} .base-modal__content_mx6kj {
|
|
37
37
|
width: 100%;
|
|
38
38
|
height: 100%;
|
|
39
39
|
display: flex;
|
|
40
40
|
flex-direction: column;
|
|
41
41
|
flex: 1;
|
|
42
|
-
} .base-
|
|
42
|
+
} .base-modal__hidden_mx6kj {
|
|
43
43
|
display: none;
|
|
44
|
-
} .base-
|
|
44
|
+
} .base-modal__backdrop_mx6kj {
|
|
45
45
|
z-index: 0;
|
|
46
|
-
} .base-
|
|
47
|
-
.base-
|
|
46
|
+
} .base-modal__appear_mx6kj,
|
|
47
|
+
.base-modal__enter_mx6kj {
|
|
48
48
|
opacity: 0;
|
|
49
|
-
} .base-
|
|
50
|
-
.base-
|
|
49
|
+
} .base-modal__appearActive_mx6kj,
|
|
50
|
+
.base-modal__enterActive_mx6kj {
|
|
51
51
|
opacity: 1;
|
|
52
52
|
transition: opacity 200ms ease-in;
|
|
53
|
-
} .base-
|
|
53
|
+
} .base-modal__exit_mx6kj {
|
|
54
54
|
opacity: 1;
|
|
55
|
-
} .base-
|
|
56
|
-
.base-
|
|
55
|
+
} .base-modal__exitActive_mx6kj,
|
|
56
|
+
.base-modal__exitDone_mx6kj {
|
|
57
57
|
opacity: 0;
|
|
58
58
|
transition: opacity 200ms ease-out;
|
|
59
59
|
}
|
package/index.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* hash:
|
|
1
|
+
/* hash: vww7l */
|
|
2
2
|
:root {
|
|
3
3
|
} /* deprecated */ :root {
|
|
4
4
|
--color-light-bg-primary: #fff; /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
} :root {
|
|
16
16
|
} :root {
|
|
17
17
|
} :root {
|
|
18
|
-
} .base-
|
|
18
|
+
} .base-modal__component_mx6kj {
|
|
19
19
|
position: relative;
|
|
20
20
|
box-sizing: border-box;
|
|
21
21
|
background: var(--color-light-bg-primary);
|
|
22
22
|
margin: auto;
|
|
23
23
|
flex-shrink: 0;
|
|
24
|
-
} .base-
|
|
24
|
+
} .base-modal__wrapper_mx6kj {
|
|
25
25
|
position: fixed;
|
|
26
26
|
top: 0;
|
|
27
27
|
left: 0;
|
|
@@ -33,27 +33,27 @@
|
|
|
33
33
|
flex-direction: column;
|
|
34
34
|
align-items: center;
|
|
35
35
|
outline: 0;
|
|
36
|
-
} .base-
|
|
36
|
+
} .base-modal__content_mx6kj {
|
|
37
37
|
width: 100%;
|
|
38
38
|
height: 100%;
|
|
39
39
|
display: flex;
|
|
40
40
|
flex-direction: column;
|
|
41
41
|
flex: 1;
|
|
42
|
-
} .base-
|
|
42
|
+
} .base-modal__hidden_mx6kj {
|
|
43
43
|
display: none;
|
|
44
|
-
} .base-
|
|
44
|
+
} .base-modal__backdrop_mx6kj {
|
|
45
45
|
z-index: 0;
|
|
46
|
-
} .base-
|
|
47
|
-
.base-
|
|
46
|
+
} .base-modal__appear_mx6kj,
|
|
47
|
+
.base-modal__enter_mx6kj {
|
|
48
48
|
opacity: 0;
|
|
49
|
-
} .base-
|
|
50
|
-
.base-
|
|
49
|
+
} .base-modal__appearActive_mx6kj,
|
|
50
|
+
.base-modal__enterActive_mx6kj {
|
|
51
51
|
opacity: 1;
|
|
52
52
|
transition: opacity 200ms ease-in;
|
|
53
|
-
} .base-
|
|
53
|
+
} .base-modal__exit_mx6kj {
|
|
54
54
|
opacity: 1;
|
|
55
|
-
} .base-
|
|
56
|
-
.base-
|
|
55
|
+
} .base-modal__exitActive_mx6kj,
|
|
56
|
+
.base-modal__exitDone_mx6kj {
|
|
57
57
|
opacity: 0;
|
|
58
58
|
transition: opacity 200ms ease-out;
|
|
59
59
|
}
|
package/modern/Component.js
CHANGED
|
@@ -11,7 +11,7 @@ import { isScrolledToTop, isScrolledToBottom, handleContainer, restoreContainerS
|
|
|
11
11
|
import './matches-polyfill.js';
|
|
12
12
|
import '@alfalab/core-components-global-store/modern';
|
|
13
13
|
|
|
14
|
-
const styles = {"component":"base-
|
|
14
|
+
const styles = {"component":"base-modal__component_mx6kj","wrapper":"base-modal__wrapper_mx6kj","content":"base-modal__content_mx6kj","hidden":"base-modal__hidden_mx6kj","backdrop":"base-modal__backdrop_mx6kj","appear":"base-modal__appear_mx6kj","enter":"base-modal__enter_mx6kj","appearActive":"base-modal__appearActive_mx6kj","enterActive":"base-modal__enterActive_mx6kj","exit":"base-modal__exit_mx6kj","exitActive":"base-modal__exitActive_mx6kj","exitDone":"base-modal__exitDone_mx6kj"};
|
|
15
15
|
require('./index.css')
|
|
16
16
|
|
|
17
17
|
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
package/modern/index.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* hash:
|
|
1
|
+
/* hash: vww7l */
|
|
2
2
|
:root {
|
|
3
3
|
} /* deprecated */ :root {
|
|
4
4
|
--color-light-bg-primary: #fff; /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */ /* deprecated */
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
} :root {
|
|
16
16
|
} :root {
|
|
17
17
|
} :root {
|
|
18
|
-
} .base-
|
|
18
|
+
} .base-modal__component_mx6kj {
|
|
19
19
|
position: relative;
|
|
20
20
|
box-sizing: border-box;
|
|
21
21
|
background: var(--color-light-bg-primary);
|
|
22
22
|
margin: auto;
|
|
23
23
|
flex-shrink: 0;
|
|
24
|
-
} .base-
|
|
24
|
+
} .base-modal__wrapper_mx6kj {
|
|
25
25
|
position: fixed;
|
|
26
26
|
top: 0;
|
|
27
27
|
left: 0;
|
|
@@ -33,27 +33,27 @@
|
|
|
33
33
|
flex-direction: column;
|
|
34
34
|
align-items: center;
|
|
35
35
|
outline: 0;
|
|
36
|
-
} .base-
|
|
36
|
+
} .base-modal__content_mx6kj {
|
|
37
37
|
width: 100%;
|
|
38
38
|
height: 100%;
|
|
39
39
|
display: flex;
|
|
40
40
|
flex-direction: column;
|
|
41
41
|
flex: 1;
|
|
42
|
-
} .base-
|
|
42
|
+
} .base-modal__hidden_mx6kj {
|
|
43
43
|
display: none;
|
|
44
|
-
} .base-
|
|
44
|
+
} .base-modal__backdrop_mx6kj {
|
|
45
45
|
z-index: 0;
|
|
46
|
-
} .base-
|
|
47
|
-
.base-
|
|
46
|
+
} .base-modal__appear_mx6kj,
|
|
47
|
+
.base-modal__enter_mx6kj {
|
|
48
48
|
opacity: 0;
|
|
49
|
-
} .base-
|
|
50
|
-
.base-
|
|
49
|
+
} .base-modal__appearActive_mx6kj,
|
|
50
|
+
.base-modal__enterActive_mx6kj {
|
|
51
51
|
opacity: 1;
|
|
52
52
|
transition: opacity 200ms ease-in;
|
|
53
|
-
} .base-
|
|
53
|
+
} .base-modal__exit_mx6kj {
|
|
54
54
|
opacity: 1;
|
|
55
|
-
} .base-
|
|
56
|
-
.base-
|
|
55
|
+
} .base-modal__exitActive_mx6kj,
|
|
56
|
+
.base-modal__exitDone_mx6kj {
|
|
57
57
|
opacity: 0;
|
|
58
58
|
transition: opacity 200ms ease-out;
|
|
59
59
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfalab/core-components-base-modal",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "BaseModal component",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
"directory": "dist"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@alfalab/core-components-backdrop": "^3.0
|
|
15
|
-
"@alfalab/core-components-global-store": "^2.0
|
|
16
|
-
"@alfalab/core-components-portal": "^3.
|
|
17
|
-
"@alfalab/core-components-stack": "^4.0
|
|
14
|
+
"@alfalab/core-components-backdrop": "^3.1.0",
|
|
15
|
+
"@alfalab/core-components-global-store": "^2.1.0",
|
|
16
|
+
"@alfalab/core-components-portal": "^3.2.0",
|
|
17
|
+
"@alfalab/core-components-stack": "^4.1.0",
|
|
18
18
|
"@juggle/resize-observer": "^3.3.1",
|
|
19
19
|
"classnames": "^2.3.1",
|
|
20
20
|
"react-focus-lock": "^2.9.3",
|
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
|
2
|
+
import React, {
|
|
3
|
+
FC,
|
|
4
|
+
forwardRef,
|
|
5
|
+
KeyboardEvent,
|
|
6
|
+
MouseEvent,
|
|
7
|
+
MutableRefObject,
|
|
8
|
+
ReactNode,
|
|
9
|
+
Ref,
|
|
10
|
+
useCallback,
|
|
11
|
+
useEffect,
|
|
12
|
+
useMemo,
|
|
13
|
+
useRef,
|
|
14
|
+
useState,
|
|
15
|
+
} from 'react';
|
|
16
|
+
import FocusLock from 'react-focus-lock';
|
|
17
|
+
import mergeRefs from 'react-merge-refs';
|
|
18
|
+
import { CSSTransition } from 'react-transition-group';
|
|
19
|
+
import { TransitionProps } from 'react-transition-group/Transition';
|
|
20
|
+
import { ResizeObserver as ResizeObserverPolyfill } from '@juggle/resize-observer';
|
|
21
|
+
import cn from 'classnames';
|
|
22
|
+
|
|
23
|
+
import { Backdrop as DefaultBackdrop, BackdropProps } from '@alfalab/core-components-backdrop';
|
|
24
|
+
import { Portal, PortalProps } from '@alfalab/core-components-portal';
|
|
25
|
+
import { Stack, stackingOrder } from '@alfalab/core-components-stack';
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
getScrollbarSize,
|
|
29
|
+
handleContainer,
|
|
30
|
+
hasScrollbar,
|
|
31
|
+
isScrolledToBottom,
|
|
32
|
+
isScrolledToTop,
|
|
33
|
+
restoreContainerStyles,
|
|
34
|
+
} from './utils';
|
|
35
|
+
|
|
36
|
+
import styles from './index.module.css';
|
|
37
|
+
|
|
38
|
+
// TODO Без полифила крашится FocusLock в IE11. Выпилить в будущем!!!.
|
|
39
|
+
import './matches-polyfill';
|
|
40
|
+
|
|
41
|
+
export type BaseModalProps = {
|
|
42
|
+
/**
|
|
43
|
+
* Контент
|
|
44
|
+
*/
|
|
45
|
+
children?: ReactNode;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Компонент бэкдропа
|
|
49
|
+
*/
|
|
50
|
+
Backdrop?: FC<BackdropProps>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Свойства для Бэкдропа
|
|
54
|
+
*/
|
|
55
|
+
backdropProps?: Partial<BackdropProps> & Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Нода, компонент или функция возвращающая их
|
|
59
|
+
*
|
|
60
|
+
* Контейнер к которому будут добавляться порталы
|
|
61
|
+
*/
|
|
62
|
+
container?: PortalProps['getPortalContainer'];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Отключает автоматический перевод фокуса на модалку при открытии
|
|
66
|
+
* @default false
|
|
67
|
+
*/
|
|
68
|
+
disableAutoFocus?: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Отключает ловушку фокуса
|
|
72
|
+
* @default false
|
|
73
|
+
*/
|
|
74
|
+
disableFocusLock?: boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Отключает восстановление фокуса на предыдущем элементе после закрытия модалки
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
80
|
+
disableRestoreFocus?: boolean;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Отключает вызов `callback` при нажатии Escape
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
disableEscapeKeyDown?: boolean;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Отключает вызов `callback` при клике на бэкдроп
|
|
90
|
+
* @default false
|
|
91
|
+
*/
|
|
92
|
+
disableBackdropClick?: boolean;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Отключает блокировку скролла при открытии модального окна
|
|
96
|
+
* @default false
|
|
97
|
+
*/
|
|
98
|
+
disableBlockingScroll?: boolean;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Содержимое модалки всегда в DOM
|
|
102
|
+
* @default false
|
|
103
|
+
*/
|
|
104
|
+
keepMounted?: boolean;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Управление видимостью модалки
|
|
108
|
+
*/
|
|
109
|
+
open: boolean;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Дополнительный класс
|
|
113
|
+
*/
|
|
114
|
+
className?: string;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Дополнительный класс
|
|
118
|
+
*/
|
|
119
|
+
contentClassName?: string;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Дополнительный класс для обертки (Modal)
|
|
123
|
+
*/
|
|
124
|
+
wrapperClassName?: string;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Обработчик скролла контента
|
|
128
|
+
*/
|
|
129
|
+
scrollHandler?: 'wrapper' | 'content' | MutableRefObject<HTMLDivElement | null>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Пропсы для анимации (CSSTransition)
|
|
133
|
+
*/
|
|
134
|
+
transitionProps?: Partial<TransitionProps>;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Рендерить ли в контейнер через портал.
|
|
138
|
+
* @default true
|
|
139
|
+
*/
|
|
140
|
+
usePortal?: boolean;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Обработчик события нажатия на бэкдроп
|
|
144
|
+
*/
|
|
145
|
+
onBackdropClick?: (event: MouseEvent) => void;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Обработчик события нажатия на Escape
|
|
149
|
+
*
|
|
150
|
+
* Если `disableEscapeKeyDown` - false и модальное окно в фокусе
|
|
151
|
+
*/
|
|
152
|
+
onEscapeKeyDown?: (event: KeyboardEvent) => void;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Обработчик закрытия
|
|
156
|
+
*/
|
|
157
|
+
onClose?: (
|
|
158
|
+
event: MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>,
|
|
159
|
+
reason?: 'backdropClick' | 'escapeKeyDown' | 'closerClick',
|
|
160
|
+
) => void;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Обработчик события onEntered компонента Transition
|
|
164
|
+
*/
|
|
165
|
+
onMount?: () => void;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Обработчик события onExited компонента Transition
|
|
169
|
+
*/
|
|
170
|
+
onUnmount?: () => void;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Идентификатор для систем автоматизированного тестирования
|
|
174
|
+
*/
|
|
175
|
+
dataTestId?: string;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* z-index компонента
|
|
179
|
+
*/
|
|
180
|
+
zIndex?: number;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Реф, который должен быть установлен компонентной области
|
|
184
|
+
*/
|
|
185
|
+
componentRef?: MutableRefObject<HTMLDivElement | null>;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export type BaseModalContext = {
|
|
189
|
+
parentRef: React.RefObject<HTMLDivElement>;
|
|
190
|
+
componentRef: React.RefObject<HTMLDivElement>;
|
|
191
|
+
hasFooter?: boolean;
|
|
192
|
+
hasHeader?: boolean;
|
|
193
|
+
hasScroll?: boolean;
|
|
194
|
+
headerHighlighted?: boolean;
|
|
195
|
+
footerHighlighted?: boolean;
|
|
196
|
+
headerOffset?: number;
|
|
197
|
+
setHeaderOffset: (offset: number) => void;
|
|
198
|
+
contentRef: Ref<HTMLElement>;
|
|
199
|
+
setHasHeader: (exists: boolean) => void;
|
|
200
|
+
setHasFooter: (exists: boolean) => void;
|
|
201
|
+
onClose: Required<BaseModalProps>['onClose'];
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
|
205
|
+
export const BaseModalContext = React.createContext<BaseModalContext>({
|
|
206
|
+
parentRef: { current: null },
|
|
207
|
+
componentRef: { current: null },
|
|
208
|
+
hasFooter: false,
|
|
209
|
+
hasHeader: false,
|
|
210
|
+
hasScroll: false,
|
|
211
|
+
headerHighlighted: false,
|
|
212
|
+
footerHighlighted: false,
|
|
213
|
+
headerOffset: 0,
|
|
214
|
+
setHeaderOffset: () => null,
|
|
215
|
+
contentRef: () => null,
|
|
216
|
+
setHasHeader: () => null,
|
|
217
|
+
setHasFooter: () => null,
|
|
218
|
+
onClose: () => null,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
export const BaseModal = forwardRef<HTMLDivElement, BaseModalProps>(
|
|
222
|
+
(
|
|
223
|
+
{
|
|
224
|
+
open,
|
|
225
|
+
container,
|
|
226
|
+
children,
|
|
227
|
+
scrollHandler = 'wrapper',
|
|
228
|
+
Backdrop = DefaultBackdrop,
|
|
229
|
+
backdropProps = {},
|
|
230
|
+
transitionProps = {},
|
|
231
|
+
disableBackdropClick,
|
|
232
|
+
disableAutoFocus = false,
|
|
233
|
+
disableFocusLock = false,
|
|
234
|
+
disableEscapeKeyDown = false,
|
|
235
|
+
disableRestoreFocus = false,
|
|
236
|
+
disableBlockingScroll = false,
|
|
237
|
+
keepMounted = false,
|
|
238
|
+
className,
|
|
239
|
+
contentClassName,
|
|
240
|
+
wrapperClassName,
|
|
241
|
+
onBackdropClick,
|
|
242
|
+
onClose,
|
|
243
|
+
onEscapeKeyDown,
|
|
244
|
+
onMount,
|
|
245
|
+
onUnmount,
|
|
246
|
+
dataTestId,
|
|
247
|
+
zIndex = stackingOrder.MODAL,
|
|
248
|
+
componentRef = null,
|
|
249
|
+
usePortal = true,
|
|
250
|
+
},
|
|
251
|
+
ref,
|
|
252
|
+
) => {
|
|
253
|
+
const [exited, setExited] = useState<boolean | null>(null);
|
|
254
|
+
const [hasScroll, setHasScroll] = useState(false);
|
|
255
|
+
const [hasHeader, setHasHeader] = useState(false);
|
|
256
|
+
const [hasFooter, setHasFooter] = useState(false);
|
|
257
|
+
const [headerHighlighted, setHeaderHighlighted] = useState(false);
|
|
258
|
+
const [footerHighlighted, setFooterHighlighted] = useState(false);
|
|
259
|
+
const [headerOffset, setHeaderOffset] = useState(0);
|
|
260
|
+
|
|
261
|
+
const componentNodeRef = useRef<HTMLDivElement>(null);
|
|
262
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
263
|
+
const scrollableNodeRef = useRef<HTMLDivElement | null>(null);
|
|
264
|
+
const contentNodeRef = useRef<HTMLDivElement | null>(null);
|
|
265
|
+
const restoreContainerStylesRef = useRef<null | (() => void)>(null);
|
|
266
|
+
const mouseDownTarget = useRef<HTMLElement>();
|
|
267
|
+
const resizeObserverRef = useRef<ResizeObserver>();
|
|
268
|
+
|
|
269
|
+
const checkToHasScrollBar = () => {
|
|
270
|
+
if (scrollableNodeRef.current) {
|
|
271
|
+
const scrollExists = hasScrollbar(scrollableNodeRef.current);
|
|
272
|
+
|
|
273
|
+
setFooterHighlighted(scrollExists);
|
|
274
|
+
setHasScroll(scrollExists);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const isExited = exited || exited === null;
|
|
279
|
+
const shouldRender = keepMounted || open || !isExited;
|
|
280
|
+
|
|
281
|
+
const getContainer = useCallback(
|
|
282
|
+
() => (container ? container() : document.body) as HTMLElement,
|
|
283
|
+
[container],
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const addResizeHandle = useCallback(() => {
|
|
287
|
+
if (!resizeObserverRef.current) return;
|
|
288
|
+
|
|
289
|
+
if (scrollableNodeRef.current) {
|
|
290
|
+
resizeObserverRef.current.observe(scrollableNodeRef.current);
|
|
291
|
+
}
|
|
292
|
+
if (contentNodeRef.current) {
|
|
293
|
+
resizeObserverRef.current.observe(contentNodeRef.current);
|
|
294
|
+
}
|
|
295
|
+
}, []);
|
|
296
|
+
|
|
297
|
+
const removeResizeHandle = useCallback(() => resizeObserverRef.current?.disconnect(), []);
|
|
298
|
+
|
|
299
|
+
const contentRef = useCallback((node: HTMLDivElement) => {
|
|
300
|
+
if (node !== null) {
|
|
301
|
+
contentNodeRef.current = node;
|
|
302
|
+
if (resizeObserverRef.current) {
|
|
303
|
+
resizeObserverRef.current.observe(node);
|
|
304
|
+
}
|
|
305
|
+
checkToHasScrollBar();
|
|
306
|
+
}
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
309
|
+
const handleScroll = useCallback(() => {
|
|
310
|
+
if (!scrollableNodeRef.current || !componentNodeRef.current) return;
|
|
311
|
+
|
|
312
|
+
if (hasHeader) {
|
|
313
|
+
setHeaderHighlighted(
|
|
314
|
+
!isScrolledToTop(scrollableNodeRef.current) &&
|
|
315
|
+
componentNodeRef.current.getBoundingClientRect().top - headerOffset <= 0,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (hasFooter) {
|
|
320
|
+
setFooterHighlighted(
|
|
321
|
+
!isScrolledToBottom(scrollableNodeRef.current) &&
|
|
322
|
+
componentNodeRef.current.getBoundingClientRect().bottom >=
|
|
323
|
+
window.innerHeight,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}, [hasFooter, hasHeader, headerOffset]);
|
|
327
|
+
|
|
328
|
+
const handleClose = useCallback<Required<BaseModalProps>['onClose']>(
|
|
329
|
+
(event, reason) => {
|
|
330
|
+
if (onClose) {
|
|
331
|
+
onClose(event, reason);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (reason === 'backdropClick' && onBackdropClick) {
|
|
335
|
+
onBackdropClick(event as MouseEvent);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (reason === 'escapeKeyDown' && onEscapeKeyDown) {
|
|
339
|
+
onEscapeKeyDown(event as KeyboardEvent);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return null;
|
|
343
|
+
},
|
|
344
|
+
[onBackdropClick, onClose, onEscapeKeyDown],
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const handleBackdropMouseDown = (event: MouseEvent<HTMLElement>) => {
|
|
348
|
+
let clickedOnScrollbar = false;
|
|
349
|
+
const clientWidth = (event.target as HTMLElement)?.clientWidth;
|
|
350
|
+
|
|
351
|
+
if (event.clientX && clientWidth) {
|
|
352
|
+
// Устанавливаем смещение для абсолютно спозиционированного скроллбара в OSX в 17px.
|
|
353
|
+
const offset = getScrollbarSize() === 0 ? 17 : 0;
|
|
354
|
+
|
|
355
|
+
clickedOnScrollbar = event.clientX + offset > clientWidth;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (!disableBackdropClick && !clickedOnScrollbar) {
|
|
359
|
+
mouseDownTarget.current = event.target as HTMLElement;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const handleBackdropMouseUp = (event: MouseEvent<HTMLElement>) => {
|
|
364
|
+
if (
|
|
365
|
+
!disableBackdropClick &&
|
|
366
|
+
event.target === wrapperRef.current &&
|
|
367
|
+
mouseDownTarget.current === wrapperRef.current
|
|
368
|
+
) {
|
|
369
|
+
handleClose(event, 'backdropClick');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
mouseDownTarget.current = undefined;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const handleKeyDown = useCallback(
|
|
376
|
+
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
377
|
+
/*
|
|
378
|
+
* Чтобы сохранить дефолтное поведение элементов и событий форм,
|
|
379
|
+
* обработчик не устанавливает event.preventDefault()
|
|
380
|
+
*/
|
|
381
|
+
if (event.key !== 'Escape') {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Если есть обработчик escape на body
|
|
386
|
+
event.stopPropagation();
|
|
387
|
+
|
|
388
|
+
if (!disableEscapeKeyDown && handleClose) {
|
|
389
|
+
handleClose(event, 'escapeKeyDown');
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
[disableEscapeKeyDown, handleClose],
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
const getScrollHandler = useCallback(() => {
|
|
396
|
+
if (scrollHandler === 'wrapper') return wrapperRef.current;
|
|
397
|
+
if (scrollHandler === 'content') return componentNodeRef.current;
|
|
398
|
+
|
|
399
|
+
return scrollHandler.current || wrapperRef.current;
|
|
400
|
+
}, [scrollHandler]);
|
|
401
|
+
|
|
402
|
+
const handleEntered: Required<TransitionProps>['onEntered'] = useCallback(
|
|
403
|
+
(node, isAppearing) => {
|
|
404
|
+
scrollableNodeRef.current = getScrollHandler();
|
|
405
|
+
|
|
406
|
+
addResizeHandle();
|
|
407
|
+
|
|
408
|
+
if (scrollableNodeRef.current) {
|
|
409
|
+
scrollableNodeRef.current.addEventListener('scroll', handleScroll);
|
|
410
|
+
handleScroll();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (transitionProps.onEntered) {
|
|
414
|
+
transitionProps.onEntered(node, isAppearing);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (onMount) onMount();
|
|
418
|
+
},
|
|
419
|
+
[addResizeHandle, getScrollHandler, handleScroll, onMount, transitionProps],
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const handleExited: Required<TransitionProps>['onExited'] = useCallback(
|
|
423
|
+
(node) => {
|
|
424
|
+
removeResizeHandle();
|
|
425
|
+
|
|
426
|
+
setExited(true);
|
|
427
|
+
|
|
428
|
+
if (scrollableNodeRef.current) {
|
|
429
|
+
scrollableNodeRef.current.removeEventListener('scroll', handleScroll);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (transitionProps.onExited) {
|
|
433
|
+
transitionProps.onExited(node);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (onUnmount) onUnmount();
|
|
437
|
+
|
|
438
|
+
if (restoreContainerStylesRef.current) {
|
|
439
|
+
restoreContainerStylesRef.current();
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
[handleScroll, onUnmount, removeResizeHandle, transitionProps],
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
useEffect(() => {
|
|
446
|
+
if (open && isExited) {
|
|
447
|
+
if (!disableBlockingScroll) {
|
|
448
|
+
const el = getContainer();
|
|
449
|
+
|
|
450
|
+
handleContainer(el);
|
|
451
|
+
|
|
452
|
+
restoreContainerStylesRef.current = () => {
|
|
453
|
+
restoreContainerStylesRef.current = null;
|
|
454
|
+
restoreContainerStyles(el);
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
setExited(false);
|
|
459
|
+
}
|
|
460
|
+
}, [getContainer, open, disableBlockingScroll, isExited]);
|
|
461
|
+
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
const ResizeObserver = window.ResizeObserver || ResizeObserverPolyfill;
|
|
464
|
+
|
|
465
|
+
resizeObserverRef.current = new ResizeObserver(checkToHasScrollBar);
|
|
466
|
+
|
|
467
|
+
return () => {
|
|
468
|
+
if (restoreContainerStylesRef.current) {
|
|
469
|
+
restoreContainerStylesRef.current();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (resizeObserverRef.current) {
|
|
473
|
+
resizeObserverRef.current.disconnect();
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}, []);
|
|
477
|
+
|
|
478
|
+
const contextValue = useMemo<BaseModalContext>(
|
|
479
|
+
() => ({
|
|
480
|
+
parentRef: wrapperRef,
|
|
481
|
+
componentRef: componentNodeRef,
|
|
482
|
+
hasHeader,
|
|
483
|
+
hasFooter,
|
|
484
|
+
hasScroll,
|
|
485
|
+
headerHighlighted,
|
|
486
|
+
footerHighlighted,
|
|
487
|
+
headerOffset,
|
|
488
|
+
setHeaderOffset,
|
|
489
|
+
contentRef,
|
|
490
|
+
setHasHeader,
|
|
491
|
+
setHasFooter,
|
|
492
|
+
onClose: handleClose,
|
|
493
|
+
}),
|
|
494
|
+
[
|
|
495
|
+
contentRef,
|
|
496
|
+
hasHeader,
|
|
497
|
+
hasFooter,
|
|
498
|
+
hasScroll,
|
|
499
|
+
headerHighlighted,
|
|
500
|
+
footerHighlighted,
|
|
501
|
+
headerOffset,
|
|
502
|
+
setHeaderOffset,
|
|
503
|
+
handleClose,
|
|
504
|
+
],
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
const renderContent = () => (
|
|
508
|
+
<Stack value={zIndex}>
|
|
509
|
+
{(computedZIndex) => (
|
|
510
|
+
<BaseModalContext.Provider value={contextValue}>
|
|
511
|
+
<FocusLock
|
|
512
|
+
autoFocus={!disableAutoFocus}
|
|
513
|
+
disabled={disableFocusLock || !open}
|
|
514
|
+
returnFocus={!disableRestoreFocus}
|
|
515
|
+
>
|
|
516
|
+
{Backdrop && (
|
|
517
|
+
<Backdrop
|
|
518
|
+
{...backdropProps}
|
|
519
|
+
className={cn(backdropProps.className, styles.backdrop)}
|
|
520
|
+
open={open}
|
|
521
|
+
style={{
|
|
522
|
+
zIndex: computedZIndex,
|
|
523
|
+
}}
|
|
524
|
+
/>
|
|
525
|
+
)}
|
|
526
|
+
<div
|
|
527
|
+
role='dialog'
|
|
528
|
+
className={cn(styles.wrapper, wrapperClassName, {
|
|
529
|
+
[styles.hidden]: !open && isExited,
|
|
530
|
+
})}
|
|
531
|
+
ref={mergeRefs([ref, wrapperRef])}
|
|
532
|
+
onKeyDown={handleKeyDown}
|
|
533
|
+
onMouseDown={handleBackdropMouseDown}
|
|
534
|
+
onMouseUp={handleBackdropMouseUp}
|
|
535
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
536
|
+
tabIndex={0}
|
|
537
|
+
data-test-id={dataTestId}
|
|
538
|
+
style={{
|
|
539
|
+
zIndex: computedZIndex,
|
|
540
|
+
}}
|
|
541
|
+
>
|
|
542
|
+
<CSSTransition
|
|
543
|
+
appear={true}
|
|
544
|
+
timeout={200}
|
|
545
|
+
classNames={styles}
|
|
546
|
+
{...transitionProps}
|
|
547
|
+
in={open}
|
|
548
|
+
onEntered={handleEntered}
|
|
549
|
+
onExited={handleExited}
|
|
550
|
+
>
|
|
551
|
+
<div
|
|
552
|
+
className={cn(styles.component, className)}
|
|
553
|
+
ref={mergeRefs([componentRef, componentNodeRef])}
|
|
554
|
+
>
|
|
555
|
+
<div className={cn(styles.content, contentClassName)}>
|
|
556
|
+
{children}
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
</CSSTransition>
|
|
560
|
+
</div>
|
|
561
|
+
</FocusLock>
|
|
562
|
+
</BaseModalContext.Provider>
|
|
563
|
+
)}
|
|
564
|
+
</Stack>
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
if (!shouldRender) return null;
|
|
568
|
+
|
|
569
|
+
return usePortal ? (
|
|
570
|
+
<Portal getPortalContainer={container} immediateMount={true}>
|
|
571
|
+
{renderContent()}
|
|
572
|
+
</Portal>
|
|
573
|
+
) : (
|
|
574
|
+
renderContent()
|
|
575
|
+
);
|
|
576
|
+
},
|
|
577
|
+
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
@import '@alfalab/core-components-themes/src/default.css';
|
|
2
|
+
|
|
3
|
+
.component {
|
|
4
|
+
position: relative;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
background: var(--color-light-bg-primary);
|
|
7
|
+
margin: auto;
|
|
8
|
+
flex-shrink: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.wrapper {
|
|
12
|
+
position: fixed;
|
|
13
|
+
top: 0;
|
|
14
|
+
left: 0;
|
|
15
|
+
right: 0;
|
|
16
|
+
bottom: 0;
|
|
17
|
+
|
|
18
|
+
overflow: auto;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
outline: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.content {
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
flex: 1;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.hidden {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.backdrop {
|
|
38
|
+
z-index: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.appear,
|
|
42
|
+
.enter {
|
|
43
|
+
opacity: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.appearActive,
|
|
47
|
+
.enterActive {
|
|
48
|
+
opacity: 1;
|
|
49
|
+
transition: opacity 200ms ease-in;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.exit {
|
|
53
|
+
opacity: 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.exitActive,
|
|
57
|
+
.exitDone {
|
|
58
|
+
opacity: 0;
|
|
59
|
+
transition: opacity 200ms ease-out;
|
|
60
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
|
|
4
|
+
if (typeof window !== 'undefined') {
|
|
5
|
+
if (Element && !Element.prototype.matches) {
|
|
6
|
+
Element.prototype.matches =
|
|
7
|
+
Element.prototype.matchesSelector ||
|
|
8
|
+
Element.prototype.mozMatchesSelector ||
|
|
9
|
+
Element.prototype.msMatchesSelector ||
|
|
10
|
+
Element.prototype.oMatchesSelector ||
|
|
11
|
+
Element.prototype.webkitMatchesSelector ||
|
|
12
|
+
function (s) {
|
|
13
|
+
const matches = (this.document || this.ownerDocument).querySelectorAll(s);
|
|
14
|
+
let i = matches.length;
|
|
15
|
+
while (--i >= 0 && matches.item(i) !== this) {}
|
|
16
|
+
return i > -1;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { getModalStore, SavedStyle } from '@alfalab/core-components-global-store';
|
|
2
|
+
|
|
3
|
+
export function isScrolledToTop(target: HTMLElement) {
|
|
4
|
+
return target.scrollTop <= 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isScrolledToBottom(target: HTMLElement) {
|
|
8
|
+
return target.scrollHeight - target.offsetHeight <= target.scrollTop;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function hasScrollbar(target: HTMLElement) {
|
|
12
|
+
return target.scrollHeight > target.clientHeight;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getScrollbarSize = (() => {
|
|
16
|
+
let cachedSize: number;
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
if (cachedSize !== undefined) return cachedSize;
|
|
20
|
+
|
|
21
|
+
const scrollDiv = document.createElement('div');
|
|
22
|
+
|
|
23
|
+
scrollDiv.style.width = '99px';
|
|
24
|
+
scrollDiv.style.height = '99px';
|
|
25
|
+
scrollDiv.style.position = 'absolute';
|
|
26
|
+
scrollDiv.style.top = '-9999px';
|
|
27
|
+
scrollDiv.style.overflow = 'scroll';
|
|
28
|
+
|
|
29
|
+
document.body.appendChild(scrollDiv);
|
|
30
|
+
const scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
|
|
31
|
+
|
|
32
|
+
document.body.removeChild(scrollDiv);
|
|
33
|
+
|
|
34
|
+
cachedSize = scrollbarSize;
|
|
35
|
+
|
|
36
|
+
return scrollbarSize;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
|
|
40
|
+
const isOverflowing = (container: Element) => {
|
|
41
|
+
if (document.body === container) {
|
|
42
|
+
return window.innerWidth > document.documentElement.clientWidth;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return container.scrollHeight > container.clientHeight;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const getPaddingRight = (node: Element) =>
|
|
49
|
+
parseInt(window.getComputedStyle(node).paddingRight, 10) || 0;
|
|
50
|
+
|
|
51
|
+
export const restoreContainerStyles = (container: HTMLElement) => {
|
|
52
|
+
const modalRestoreStyles = getModalStore().getRestoreStyles();
|
|
53
|
+
|
|
54
|
+
const index = modalRestoreStyles.findIndex((s) => s.container === container);
|
|
55
|
+
const existingStyles = modalRestoreStyles[index];
|
|
56
|
+
|
|
57
|
+
if (!existingStyles) return;
|
|
58
|
+
|
|
59
|
+
existingStyles.modals -= 1;
|
|
60
|
+
|
|
61
|
+
if (existingStyles.modals <= 0) {
|
|
62
|
+
modalRestoreStyles.splice(index, 1);
|
|
63
|
+
|
|
64
|
+
existingStyles.styles.forEach(({ value, el, key }) => {
|
|
65
|
+
if (value) {
|
|
66
|
+
el.style.setProperty(key, value);
|
|
67
|
+
} else {
|
|
68
|
+
el.style.removeProperty(key);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const handleContainer = (container?: HTMLElement) => {
|
|
75
|
+
if (!container) return;
|
|
76
|
+
|
|
77
|
+
const modalRestoreStyles = getModalStore().getRestoreStyles();
|
|
78
|
+
|
|
79
|
+
const existingStyles = modalRestoreStyles.find((s) => s.container === container);
|
|
80
|
+
|
|
81
|
+
if (existingStyles) {
|
|
82
|
+
existingStyles.modals += 1;
|
|
83
|
+
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const containerStyles: SavedStyle[] = [];
|
|
88
|
+
|
|
89
|
+
if (isOverflowing(container)) {
|
|
90
|
+
// Вычисляет размер до применения `overflow hidden` для избежания скачков
|
|
91
|
+
const scrollbarSize = getScrollbarSize();
|
|
92
|
+
|
|
93
|
+
containerStyles.push({
|
|
94
|
+
value: container.style.paddingRight,
|
|
95
|
+
key: 'padding-right',
|
|
96
|
+
el: container,
|
|
97
|
+
});
|
|
98
|
+
// Вычисляем стили, чтобы получить реальный `padding` c шириной сколлбара
|
|
99
|
+
// eslint-disable-next-line no-param-reassign
|
|
100
|
+
container.style.paddingRight = `${getPaddingRight(container) + scrollbarSize}px`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const parent = container.parentElement;
|
|
104
|
+
const scrollContainer =
|
|
105
|
+
// TODO: заменить на optional chaining
|
|
106
|
+
parent &&
|
|
107
|
+
parent.nodeName === 'HTML' &&
|
|
108
|
+
window.getComputedStyle(parent).overflowY === 'scroll'
|
|
109
|
+
? parent
|
|
110
|
+
: container;
|
|
111
|
+
|
|
112
|
+
// Блокируем скролл даже если отсутствует скроллбар
|
|
113
|
+
if (scrollContainer.style.overflow !== 'hidden') {
|
|
114
|
+
containerStyles.push({
|
|
115
|
+
value: scrollContainer.style.overflow,
|
|
116
|
+
key: 'overflow',
|
|
117
|
+
el: scrollContainer,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
scrollContainer.style.overflow = 'hidden';
|
|
122
|
+
|
|
123
|
+
modalRestoreStyles.push({
|
|
124
|
+
container,
|
|
125
|
+
modals: 1,
|
|
126
|
+
styles: containerStyles,
|
|
127
|
+
});
|
|
128
|
+
};
|