@hh.ru/magritte-ui-modal 11.0.41 → 11.0.43
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/actionBarDefaults.d.ts +2 -2
- package/actionBarDefaults.js +3 -17
- package/actionBarDefaults.js.map +1 -1
- package/index.css +121 -121
- package/index.d.ts +5 -5
- package/index.js +1 -1
- package/index.mock.js +5 -5
- package/index.mock.js.map +1 -1
- package/internal/ModalBody.js +42 -24
- package/internal/ModalBody.js.map +1 -1
- package/internal/ModalContent.js +2 -2
- package/internal/ModalContent.js.map +1 -1
- package/internal/ModalInner.d.ts +1 -1
- package/internal/ModalInner.js +46 -26
- package/internal/ModalInner.js.map +1 -1
- package/internal/ModalSlotsContext.js +34 -52
- package/internal/ModalSlotsContext.js.map +1 -1
- package/internal/ModalStickyHeader.js +27 -23
- package/internal/ModalStickyHeader.js.map +1 -1
- package/internal/constants.js +33 -42
- package/internal/constants.js.map +1 -1
- package/internal/isTreeSelectorChild.js +0 -5
- package/internal/isTreeSelectorChild.js.map +1 -1
- package/internal/useModalOrder.js +69 -76
- package/internal/useModalOrder.js.map +1 -1
- package/internal/useModalTransition.js +71 -72
- package/internal/useModalTransition.js.map +1 -1
- package/internal/usePageHeaderScrollAnimation.js +42 -42
- package/internal/usePageHeaderScrollAnimation.js.map +1 -1
- package/modal-DKfr_WcM.js +5 -0
- package/modal-DKfr_WcM.js.map +1 -0
- package/package.json +30 -29
- package/public/Modal.d.ts +2 -2
- package/public/Modal.js +119 -51
- package/public/Modal.js.map +1 -1
- package/public/ModalMedia.js +26 -15
- package/public/ModalMedia.js.map +1 -1
- package/public/ModalWithImage.d.ts +2 -2
- package/public/ModalWithImage.js +11 -3
- package/public/ModalWithImage.js.map +1 -1
- package/withImageDefaults.d.ts +3 -3
- package/withImageDefaults.js +5 -18
- package/withImageDefaults.js.map +1 -1
- package/modal-Df_WVial.js +0 -5
- package/modal-Df_WVial.js.map +0 -1
package/internal/constants.js
CHANGED
|
@@ -1,52 +1,43 @@
|
|
|
1
1
|
import './../index.css';
|
|
2
2
|
import coreTokens from '@hh.ru/magritte-design-tokens/core-tokens';
|
|
3
|
-
import { s as styles } from '../modal-
|
|
3
|
+
import { s as styles } from '../modal-DKfr_WcM.js';
|
|
4
4
|
|
|
5
|
-
/** Отступ сверху под page-header (px) — дефолт для `position=left|right` без оверлея. */
|
|
6
5
|
const DEFAULT_PAGE_HEADER_PADDING = 72;
|
|
7
|
-
const noop = () =>
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
const EXIT_ANIMATION_DURATION = coreTokens['magritte-semantic-animation-ease-in-out-200'].duration;
|
|
11
|
-
/**
|
|
12
|
-
* Таймаут `CSSTransition` для анимации модала (мс).
|
|
13
|
-
* При `prefers-reduced-motion` анимация мгновенная (0) — согласовано с media-query в modal.less.
|
|
14
|
-
*/
|
|
6
|
+
const noop = () => void 0;
|
|
7
|
+
const ENTER_ANIMATION_DURATION = coreTokens["magritte-semantic-animation-ease-in-out-300"].duration;
|
|
8
|
+
const EXIT_ANIMATION_DURATION = coreTokens["magritte-semantic-animation-ease-in-out-200"].duration;
|
|
15
9
|
const getAnimationTimeout = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
return { enter: ENTER_ANIMATION_DURATION, exit: EXIT_ANIMATION_DURATION };
|
|
10
|
+
const prefersReducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
11
|
+
if (prefersReducedMotion) {
|
|
12
|
+
return { enter: 0, exit: 0 };
|
|
13
|
+
}
|
|
14
|
+
return { enter: ENTER_ANIMATION_DURATION, exit: EXIT_ANIMATION_DURATION };
|
|
23
15
|
};
|
|
24
|
-
/** Классы фаз `CSSTransition` по позиции модала. */
|
|
25
16
|
const CSS_CLASSES = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
17
|
+
center: {
|
|
18
|
+
appear: styles.animationEnterCenter,
|
|
19
|
+
appearActive: styles.animationEnterCenterActive,
|
|
20
|
+
enter: styles.animationEnterCenter,
|
|
21
|
+
enterActive: styles.animationEnterCenterActive,
|
|
22
|
+
exit: styles.animationExitCenter,
|
|
23
|
+
exitActive: styles.animationExitCenterActive
|
|
24
|
+
},
|
|
25
|
+
left: {
|
|
26
|
+
appear: styles.animationEnterLeft,
|
|
27
|
+
appearActive: styles.animationEnterLeftActive,
|
|
28
|
+
enter: styles.animationEnterLeft,
|
|
29
|
+
enterActive: styles.animationEnterLeftActive,
|
|
30
|
+
exit: styles.animationExitLeft,
|
|
31
|
+
exitActive: styles.animationExitLeftActive
|
|
32
|
+
},
|
|
33
|
+
right: {
|
|
34
|
+
appear: styles.animationEnterRight,
|
|
35
|
+
appearActive: styles.animationEnterRightActive,
|
|
36
|
+
enter: styles.animationEnterRight,
|
|
37
|
+
enterActive: styles.animationEnterRightActive,
|
|
38
|
+
exit: styles.animationExitRight,
|
|
39
|
+
exitActive: styles.animationExitRightActive
|
|
40
|
+
}
|
|
50
41
|
};
|
|
51
42
|
|
|
52
43
|
export { CSS_CLASSES, DEFAULT_PAGE_HEADER_PADDING, getAnimationTimeout, noop };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sources":["src/internal/constants.ts"],"sourcesContent":["import coreTokens from '@hh.ru/magritte-design-tokens/core-tokens';\n\nimport styles from './modal.less';\n\n/** Отступ сверху под page-header (px) — дефолт для `position=left|right` без оверлея. */\nexport const DEFAULT_PAGE_HEADER_PADDING = 72;\n\nexport const noop = (): undefined => undefined;\n\n/** Длительности анимации появления/исчезновения модала (мс) — из дизайн-токенов (см. modal.less). */\nconst ENTER_ANIMATION_DURATION = coreTokens['magritte-semantic-animation-ease-in-out-300'].duration;\nconst EXIT_ANIMATION_DURATION = coreTokens['magritte-semantic-animation-ease-in-out-200'].duration;\n\n/**\n * Таймаут `CSSTransition` для анимации модала (мс).\n * При `prefers-reduced-motion` анимация мгновенная (0) — согласовано с media-query в modal.less.\n */\nexport const getAnimationTimeout = (): { enter: number; exit: number } => {\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n if (prefersReducedMotion) {\n return { enter: 0, exit: 0 };\n }\n\n return { enter: ENTER_ANIMATION_DURATION, exit: EXIT_ANIMATION_DURATION };\n};\n\n/** Классы фаз `CSSTransition` по позиции модала. */\nexport const CSS_CLASSES = {\n center: {\n appear: styles.animationEnterCenter,\n appearActive: styles.animationEnterCenterActive,\n enter: styles.animationEnterCenter,\n enterActive: styles.animationEnterCenterActive,\n exit: styles.animationExitCenter,\n exitActive: styles.animationExitCenterActive,\n },\n left: {\n appear: styles.animationEnterLeft,\n appearActive: styles.animationEnterLeftActive,\n enter: styles.animationEnterLeft,\n enterActive: styles.animationEnterLeftActive,\n exit: styles.animationExitLeft,\n exitActive: styles.animationExitLeftActive,\n },\n right: {\n appear: styles.animationEnterRight,\n appearActive: styles.animationEnterRightActive,\n enter: styles.animationEnterRight,\n enterActive: styles.animationEnterRightActive,\n exit: styles.animationExitRight,\n exitActive: styles.animationExitRightActive,\n },\n};\n"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["src/internal/constants.ts"],"sourcesContent":["import coreTokens from '@hh.ru/magritte-design-tokens/core-tokens';\n\nimport styles from './modal.less';\n\n/** Отступ сверху под page-header (px) — дефолт для `position=left|right` без оверлея. */\nexport const DEFAULT_PAGE_HEADER_PADDING = 72;\n\nexport const noop = (): undefined => undefined;\n\n/** Длительности анимации появления/исчезновения модала (мс) — из дизайн-токенов (см. modal.less). */\nconst ENTER_ANIMATION_DURATION = coreTokens['magritte-semantic-animation-ease-in-out-300'].duration;\nconst EXIT_ANIMATION_DURATION = coreTokens['magritte-semantic-animation-ease-in-out-200'].duration;\n\n/**\n * Таймаут `CSSTransition` для анимации модала (мс).\n * При `prefers-reduced-motion` анимация мгновенная (0) — согласовано с media-query в modal.less.\n */\nexport const getAnimationTimeout = (): { enter: number; exit: number } => {\n const prefersReducedMotion =\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n\n if (prefersReducedMotion) {\n return { enter: 0, exit: 0 };\n }\n\n return { enter: ENTER_ANIMATION_DURATION, exit: EXIT_ANIMATION_DURATION };\n};\n\n/** Классы фаз `CSSTransition` по позиции модала. */\nexport const CSS_CLASSES = {\n center: {\n appear: styles.animationEnterCenter,\n appearActive: styles.animationEnterCenterActive,\n enter: styles.animationEnterCenter,\n enterActive: styles.animationEnterCenterActive,\n exit: styles.animationExitCenter,\n exitActive: styles.animationExitCenterActive,\n },\n left: {\n appear: styles.animationEnterLeft,\n appearActive: styles.animationEnterLeftActive,\n enter: styles.animationEnterLeft,\n enterActive: styles.animationEnterLeftActive,\n exit: styles.animationExitLeft,\n exitActive: styles.animationExitLeftActive,\n },\n right: {\n appear: styles.animationEnterRight,\n appearActive: styles.animationEnterRightActive,\n enter: styles.animationEnterRight,\n enterActive: styles.animationEnterRightActive,\n exit: styles.animationExitRight,\n exitActive: styles.animationExitRightActive,\n },\n};\n"],"names":[],"mappings":";;;AAKO,MAAM,2BAA8B,GAAA,GAAA;AAEpC,MAAM,OAAO,MAAiB,KAAA,EAAA;AAGrC,MAAM,wBAAA,GAA2B,UAAW,CAAA,6CAA6C,CAAE,CAAA,QAAA,CAAA;AAC3F,MAAM,uBAAA,GAA0B,UAAW,CAAA,6CAA6C,CAAE,CAAA,QAAA,CAAA;AAMnF,MAAM,sBAAsB,MAAuC;AACtE,EAAM,MAAA,oBAAA,GACF,OAAO,MAAA,KAAW,WAClB,IAAA,OAAO,MAAO,CAAA,UAAA,KAAe,UAC7B,IAAA,MAAA,CAAO,UAAW,CAAA,kCAAkC,CAAE,CAAA,OAAA,CAAA;AAE1D,EAAA,IAAI,oBAAsB,EAAA;AACtB,IAAA,OAAO,EAAE,KAAA,EAAO,CAAG,EAAA,IAAA,EAAM,CAAE,EAAA,CAAA;AAAA,GAC/B;AAEA,EAAA,OAAO,EAAE,KAAA,EAAO,wBAA0B,EAAA,IAAA,EAAM,uBAAwB,EAAA,CAAA;AAC5E,EAAA;AAGO,MAAM,WAAc,GAAA;AAAA,EACvB,MAAQ,EAAA;AAAA,IACJ,QAAQ,MAAO,CAAA,oBAAA;AAAA,IACf,cAAc,MAAO,CAAA,0BAAA;AAAA,IACrB,OAAO,MAAO,CAAA,oBAAA;AAAA,IACd,aAAa,MAAO,CAAA,0BAAA;AAAA,IACpB,MAAM,MAAO,CAAA,mBAAA;AAAA,IACb,YAAY,MAAO,CAAA,yBAAA;AAAA,GACvB;AAAA,EACA,IAAM,EAAA;AAAA,IACF,QAAQ,MAAO,CAAA,kBAAA;AAAA,IACf,cAAc,MAAO,CAAA,wBAAA;AAAA,IACrB,OAAO,MAAO,CAAA,kBAAA;AAAA,IACd,aAAa,MAAO,CAAA,wBAAA;AAAA,IACpB,MAAM,MAAO,CAAA,iBAAA;AAAA,IACb,YAAY,MAAO,CAAA,uBAAA;AAAA,GACvB;AAAA,EACA,KAAO,EAAA;AAAA,IACH,QAAQ,MAAO,CAAA,mBAAA;AAAA,IACf,cAAc,MAAO,CAAA,yBAAA;AAAA,IACrB,OAAO,MAAO,CAAA,mBAAA;AAAA,IACd,aAAa,MAAO,CAAA,yBAAA;AAAA,IACpB,MAAM,MAAO,CAAA,kBAAA;AAAA,IACb,YAAY,MAAO,CAAA,wBAAA;AAAA,GACvB;AACJ;;;;"}
|
|
@@ -2,11 +2,6 @@ import './../index.css';
|
|
|
2
2
|
import { Children } from 'react';
|
|
3
3
|
import { isValidTreeSelectorWrapper } from '@hh.ru/magritte-ui-tree-selector';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* `true`, если среди детей модала есть `TreeSelector`-обёртка. Единая точка детекции: её используют и
|
|
7
|
-
* `Modal` (форсит `height='full-screen'`), и `ModalContent` (рендерит особый контейнер) — проверка детей
|
|
8
|
-
* должна совпадать в обоих местах, иначе детекция разъедется.
|
|
9
|
-
*/
|
|
10
5
|
const isTreeSelectorChild = (children) => Children.toArray(children).some(isValidTreeSelectorWrapper);
|
|
11
6
|
|
|
12
7
|
export { isTreeSelectorChild };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isTreeSelectorChild.js","sources":["src/internal/isTreeSelectorChild.ts"],"sourcesContent":["import { Children, type ReactNode } from 'react';\n\nimport { isValidTreeSelectorWrapper } from '@hh.ru/magritte-ui-tree-selector';\n\n/**\n * `true`, если среди детей модала есть `TreeSelector`-обёртка. Единая точка детекции: её используют и\n * `Modal` (форсит `height='full-screen'`), и `ModalContent` (рендерит особый контейнер) — проверка детей\n * должна совпадать в обоих местах, иначе детекция разъедется.\n */\nexport const isTreeSelectorChild = (children: ReactNode): boolean =>\n Children.toArray(children).some(isValidTreeSelectorWrapper);\n"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"isTreeSelectorChild.js","sources":["src/internal/isTreeSelectorChild.ts"],"sourcesContent":["import { Children, type ReactNode } from 'react';\n\nimport { isValidTreeSelectorWrapper } from '@hh.ru/magritte-ui-tree-selector';\n\n/**\n * `true`, если среди детей модала есть `TreeSelector`-обёртка. Единая точка детекции: её используют и\n * `Modal` (форсит `height='full-screen'`), и `ModalContent` (рендерит особый контейнер) — проверка детей\n * должна совпадать в обоих местах, иначе детекция разъедется.\n */\nexport const isTreeSelectorChild = (children: ReactNode): boolean =>\n Children.toArray(children).some(isValidTreeSelectorWrapper);\n"],"names":[],"mappings":";;;AASa,MAAA,mBAAA,GAAsB,CAAC,QAChC,KAAA,QAAA,CAAS,QAAQ,QAAQ,CAAA,CAAE,KAAK,0BAA0B;;;;"}
|
|
@@ -2,90 +2,83 @@ import './../index.css';
|
|
|
2
2
|
import { useState, useLayoutEffect, useEffect } from 'react';
|
|
3
3
|
import { useDomStoredUniqueId, getValueFromDom, storeValueInDom } from '@hh.ru/magritte-common-dom-storage';
|
|
4
4
|
|
|
5
|
-
const STORAGE_NAME =
|
|
5
|
+
const STORAGE_NAME = "_MAGRITTE_MODAL_COUNTER_";
|
|
6
6
|
const getState = () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
const state = getValueFromDom(STORAGE_NAME);
|
|
8
|
+
if (state === void 0) {
|
|
9
|
+
return {
|
|
10
|
+
prevVisiblility: {},
|
|
11
|
+
positions: {},
|
|
12
|
+
subscribers: {},
|
|
13
|
+
count: 0
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return state;
|
|
17
17
|
};
|
|
18
18
|
const storeState = (state) => {
|
|
19
|
-
|
|
19
|
+
storeValueInDom(STORAGE_NAME, state);
|
|
20
20
|
};
|
|
21
21
|
const counterFactory = () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
},
|
|
59
|
-
};
|
|
22
|
+
return {
|
|
23
|
+
updateVisiblility: (id, visible) => {
|
|
24
|
+
const state = getState();
|
|
25
|
+
const prevVisible = !!state.prevVisiblility[id];
|
|
26
|
+
if (visible === prevVisible) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
state.prevVisiblility[id] = visible;
|
|
30
|
+
if (visible) {
|
|
31
|
+
state.count += 1;
|
|
32
|
+
state.positions[id] = state.count;
|
|
33
|
+
} else {
|
|
34
|
+
state.count -= 1;
|
|
35
|
+
const currentPosition = state.positions[id];
|
|
36
|
+
state.positions[id] = 0;
|
|
37
|
+
Object.entries(state.positions).forEach(([key, value]) => {
|
|
38
|
+
state.positions[key] = value > currentPosition ? value - 1 : value;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
storeState(state);
|
|
42
|
+
},
|
|
43
|
+
subscribe: (id, callback) => {
|
|
44
|
+
const state = getState();
|
|
45
|
+
state.subscribers[id] = callback;
|
|
46
|
+
storeState(state);
|
|
47
|
+
},
|
|
48
|
+
unsubscribe: (id) => {
|
|
49
|
+
const state = getState();
|
|
50
|
+
delete state.subscribers[id];
|
|
51
|
+
storeState(state);
|
|
52
|
+
},
|
|
53
|
+
notifyAll: () => {
|
|
54
|
+
const { subscribers, count, positions } = getState();
|
|
55
|
+
Object.entries(subscribers).forEach(([id, callback]) => callback({ position: positions[id], count }));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
60
58
|
};
|
|
61
59
|
const counter = counterFactory();
|
|
62
60
|
const useModalOrder = (visible, modalRef) => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
!modalRef.current?.contains(document.activeElement)) {
|
|
85
|
-
modalRef.current?.focus();
|
|
86
|
-
}
|
|
87
|
-
}, [position, modalRef]);
|
|
88
|
-
return [position.position, position.count];
|
|
61
|
+
const [position, setPosition] = useState({ position: 0, count: 0 });
|
|
62
|
+
const id = useDomStoredUniqueId();
|
|
63
|
+
useLayoutEffect(() => {
|
|
64
|
+
counter.updateVisiblility(id, visible);
|
|
65
|
+
counter.notifyAll();
|
|
66
|
+
}, [visible]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
counter.subscribe(id, setPosition);
|
|
69
|
+
counter.updateVisiblility(id, visible);
|
|
70
|
+
return () => {
|
|
71
|
+
counter.unsubscribe(id);
|
|
72
|
+
counter.updateVisiblility(id, false);
|
|
73
|
+
counter.notifyAll();
|
|
74
|
+
};
|
|
75
|
+
}, [id]);
|
|
76
|
+
useLayoutEffect(() => {
|
|
77
|
+
if (position.position === position.count && position.count > 0 && !modalRef.current?.contains(document.activeElement)) {
|
|
78
|
+
modalRef.current?.focus();
|
|
79
|
+
}
|
|
80
|
+
}, [position, modalRef]);
|
|
81
|
+
return [position.position, position.count];
|
|
89
82
|
};
|
|
90
83
|
|
|
91
84
|
export { useModalOrder };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModalOrder.js","sources":["src/internal/useModalOrder.ts"],"sourcesContent":["import { RefObject, useEffect, useLayoutEffect, useState } from 'react';\n\nimport { storeValueInDom, getValueFromDom, useDomStoredUniqueId } from '@hh.ru/magritte-common-dom-storage';\n\ntype NotifyCallbackArg = { position: number; count: number };\n\ntype Subscribers = Record<string, (position: NotifyCallbackArg) => void>;\ntype VisibilityMap = Record<string, boolean>;\ntype PositionMap = Record<string, number>;\n\ntype ModalCounterState = {\n prevVisiblility: VisibilityMap;\n positions: PositionMap;\n subscribers: Subscribers;\n count: number;\n};\n\nconst STORAGE_NAME = '_MAGRITTE_MODAL_COUNTER_';\n\nconst getState = (): ModalCounterState => {\n const state = getValueFromDom<ModalCounterState>(STORAGE_NAME);\n if (state === undefined) {\n return {\n prevVisiblility: {},\n positions: {},\n subscribers: {},\n count: 0,\n };\n }\n return state;\n};\n\nconst storeState = (state: ModalCounterState): void => {\n storeValueInDom(STORAGE_NAME, state);\n};\n\nconst counterFactory = () => {\n return {\n updateVisiblility: (id: string, visible: boolean) => {\n // eslint-disable-next-line prefer-const\n const state = getState();\n const prevVisible = !!state.prevVisiblility[id];\n if (visible === prevVisible) {\n return;\n }\n state.prevVisiblility[id] = visible;\n if (visible) {\n state.count += 1;\n state.positions[id] = state.count;\n } else {\n state.count -= 1;\n const currentPosition = state.positions[id];\n state.positions[id] = 0;\n Object.entries(state.positions).forEach(([key, value]) => {\n state.positions[key] = value > currentPosition ? value - 1 : value;\n });\n }\n storeState(state);\n },\n subscribe: (id: string, callback: (position: NotifyCallbackArg) => void) => {\n const state = getState();\n state.subscribers[id] = callback;\n storeState(state);\n },\n unsubscribe: (id: string) => {\n const state = getState();\n delete state.subscribers[id];\n storeState(state);\n },\n notifyAll: () => {\n const { subscribers, count, positions } = getState();\n Object.entries(subscribers).forEach(([id, callback]) => callback({ position: positions[id], count }));\n },\n };\n};\n\nconst counter = counterFactory();\n\nexport const useModalOrder = (visible: boolean, modalRef: RefObject<HTMLDivElement>): [number, number] => {\n const [position, setPosition] = useState<{ position: number; count: number }>({ position: 0, count: 0 });\n const id = useDomStoredUniqueId();\n\n useLayoutEffect(() => {\n counter.updateVisiblility(id, visible);\n counter.notifyAll();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [visible]);\n\n useEffect(() => {\n counter.subscribe(id, setPosition);\n counter.updateVisiblility(id, visible);\n return () => {\n counter.unsubscribe(id);\n counter.updateVisiblility(id, false);\n counter.notifyAll();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id]);\n\n useLayoutEffect(() => {\n // Если это самый \"верхний\" модал передаем туда фокус\n if (\n position.position === position.count &&\n position.count > 0 &&\n !modalRef.current?.contains(document.activeElement)\n ) {\n modalRef.current?.focus();\n }\n }, [position, modalRef]);\n\n return [position.position, position.count];\n};\n"],"names":[],"mappings":";;;AAiBA,MAAM,
|
|
1
|
+
{"version":3,"file":"useModalOrder.js","sources":["src/internal/useModalOrder.ts"],"sourcesContent":["import { RefObject, useEffect, useLayoutEffect, useState } from 'react';\n\nimport { storeValueInDom, getValueFromDom, useDomStoredUniqueId } from '@hh.ru/magritte-common-dom-storage';\n\ntype NotifyCallbackArg = { position: number; count: number };\n\ntype Subscribers = Record<string, (position: NotifyCallbackArg) => void>;\ntype VisibilityMap = Record<string, boolean>;\ntype PositionMap = Record<string, number>;\n\ntype ModalCounterState = {\n prevVisiblility: VisibilityMap;\n positions: PositionMap;\n subscribers: Subscribers;\n count: number;\n};\n\nconst STORAGE_NAME = '_MAGRITTE_MODAL_COUNTER_';\n\nconst getState = (): ModalCounterState => {\n const state = getValueFromDom<ModalCounterState>(STORAGE_NAME);\n if (state === undefined) {\n return {\n prevVisiblility: {},\n positions: {},\n subscribers: {},\n count: 0,\n };\n }\n return state;\n};\n\nconst storeState = (state: ModalCounterState): void => {\n storeValueInDom(STORAGE_NAME, state);\n};\n\nconst counterFactory = () => {\n return {\n updateVisiblility: (id: string, visible: boolean) => {\n // eslint-disable-next-line prefer-const\n const state = getState();\n const prevVisible = !!state.prevVisiblility[id];\n if (visible === prevVisible) {\n return;\n }\n state.prevVisiblility[id] = visible;\n if (visible) {\n state.count += 1;\n state.positions[id] = state.count;\n } else {\n state.count -= 1;\n const currentPosition = state.positions[id];\n state.positions[id] = 0;\n Object.entries(state.positions).forEach(([key, value]) => {\n state.positions[key] = value > currentPosition ? value - 1 : value;\n });\n }\n storeState(state);\n },\n subscribe: (id: string, callback: (position: NotifyCallbackArg) => void) => {\n const state = getState();\n state.subscribers[id] = callback;\n storeState(state);\n },\n unsubscribe: (id: string) => {\n const state = getState();\n delete state.subscribers[id];\n storeState(state);\n },\n notifyAll: () => {\n const { subscribers, count, positions } = getState();\n Object.entries(subscribers).forEach(([id, callback]) => callback({ position: positions[id], count }));\n },\n };\n};\n\nconst counter = counterFactory();\n\nexport const useModalOrder = (visible: boolean, modalRef: RefObject<HTMLDivElement>): [number, number] => {\n const [position, setPosition] = useState<{ position: number; count: number }>({ position: 0, count: 0 });\n const id = useDomStoredUniqueId();\n\n useLayoutEffect(() => {\n counter.updateVisiblility(id, visible);\n counter.notifyAll();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [visible]);\n\n useEffect(() => {\n counter.subscribe(id, setPosition);\n counter.updateVisiblility(id, visible);\n return () => {\n counter.unsubscribe(id);\n counter.updateVisiblility(id, false);\n counter.notifyAll();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [id]);\n\n useLayoutEffect(() => {\n // Если это самый \"верхний\" модал передаем туда фокус\n if (\n position.position === position.count &&\n position.count > 0 &&\n !modalRef.current?.contains(document.activeElement)\n ) {\n modalRef.current?.focus();\n }\n }, [position, modalRef]);\n\n return [position.position, position.count];\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -6,79 +6,78 @@ import { useModalOrder } from './useModalOrder.js';
|
|
|
6
6
|
import '@hh.ru/magritte-common-dom-storage';
|
|
7
7
|
|
|
8
8
|
const INVISIBLE_FOR_BREAKPOINTS = [Breakpoint.XS, Breakpoint.S];
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
enableScrollRef.current = undefined;
|
|
73
|
-
};
|
|
74
|
-
}, [visible, showOverlay]);
|
|
75
|
-
return {
|
|
76
|
-
onEntering,
|
|
77
|
-
onEntered,
|
|
78
|
-
onExit,
|
|
79
|
-
onExited,
|
|
80
|
-
isHiddenForBreakpoint: INVISIBLE_FOR_BREAKPOINTS.includes(breakpoint),
|
|
9
|
+
const useModalTransition = ({
|
|
10
|
+
visible,
|
|
11
|
+
isServerEnv,
|
|
12
|
+
showOverlay,
|
|
13
|
+
returnFocusOnClose,
|
|
14
|
+
onAppear,
|
|
15
|
+
onBeforeExit,
|
|
16
|
+
onAfterExit,
|
|
17
|
+
modalRef,
|
|
18
|
+
animationTimeoutExit
|
|
19
|
+
}) => {
|
|
20
|
+
const enableScrollRef = useRef();
|
|
21
|
+
const onAfterExitRef = useRef(onAfterExit);
|
|
22
|
+
const onBeforeExitRef = useRef(onBeforeExit);
|
|
23
|
+
onAfterExitRef.current = onAfterExit;
|
|
24
|
+
onBeforeExitRef.current = onBeforeExit;
|
|
25
|
+
const animationTimeoutExitRef = useRef(animationTimeoutExit);
|
|
26
|
+
animationTimeoutExitRef.current = animationTimeoutExit;
|
|
27
|
+
const activatorRef = useRef(null);
|
|
28
|
+
const onEntering = useCallback(() => {
|
|
29
|
+
if (returnFocusOnClose && document.activeElement instanceof HTMLElement) {
|
|
30
|
+
activatorRef.current = document.activeElement;
|
|
31
|
+
}
|
|
32
|
+
if (!enableScrollRef.current && showOverlay) {
|
|
33
|
+
enableScrollRef.current = disableScroll();
|
|
34
|
+
}
|
|
35
|
+
if (modalRef.current && !modalRef.current.contains(document.activeElement)) {
|
|
36
|
+
modalRef.current.focus();
|
|
37
|
+
}
|
|
38
|
+
}, [returnFocusOnClose, showOverlay, modalRef]);
|
|
39
|
+
const onEntered = useCallback(() => {
|
|
40
|
+
onAppear?.();
|
|
41
|
+
}, [onAppear]);
|
|
42
|
+
const onExit = useCallback(() => {
|
|
43
|
+
onBeforeExitRef.current?.();
|
|
44
|
+
}, []);
|
|
45
|
+
const onExited = useCallback(() => {
|
|
46
|
+
enableScrollRef.current?.();
|
|
47
|
+
enableScrollRef.current = void 0;
|
|
48
|
+
onAfterExitRef.current?.();
|
|
49
|
+
if (activatorRef.current !== null && document.contains(activatorRef.current)) {
|
|
50
|
+
activatorRef.current?.focus();
|
|
51
|
+
activatorRef.current = null;
|
|
52
|
+
}
|
|
53
|
+
}, []);
|
|
54
|
+
useModalOrder(!!visible && !isServerEnv, modalRef);
|
|
55
|
+
const { breakpoint } = useBreakpoint();
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!visible) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const currentBreakpointVisible = !INVISIBLE_FOR_BREAKPOINTS.includes(breakpoint);
|
|
61
|
+
if (!currentBreakpointVisible) {
|
|
62
|
+
onExit();
|
|
63
|
+
onExited();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
onEntering();
|
|
67
|
+
}, [breakpoint, onEntering, onExited, onExit]);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
return () => {
|
|
70
|
+
enableScrollRef.current?.(animationTimeoutExitRef.current);
|
|
71
|
+
enableScrollRef.current = void 0;
|
|
81
72
|
};
|
|
73
|
+
}, [visible, showOverlay]);
|
|
74
|
+
return {
|
|
75
|
+
onEntering,
|
|
76
|
+
onEntered,
|
|
77
|
+
onExit,
|
|
78
|
+
onExited,
|
|
79
|
+
isHiddenForBreakpoint: INVISIBLE_FOR_BREAKPOINTS.includes(breakpoint)
|
|
80
|
+
};
|
|
82
81
|
};
|
|
83
82
|
|
|
84
83
|
export { useModalTransition };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useModalTransition.js","sources":["src/internal/useModalTransition.ts"],"sourcesContent":["import { type RefObject, useCallback, useEffect, useRef } from 'react';\n\nimport { disableScroll, type TimeoutCallback } from '@hh.ru/magritte-common-modal-helper';\nimport { Breakpoint, useBreakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport { useModalOrder } from '@hh.ru/magritte-ui-modal/internal/useModalOrder';\n\nconst INVISIBLE_FOR_BREAKPOINTS = [Breakpoint.XS, Breakpoint.S];\n\ninterface UseModalTransitionParams {\n visible: boolean | undefined;\n isServerEnv: boolean;\n showOverlay: boolean;\n returnFocusOnClose: boolean | undefined;\n onAppear?: VoidFunction;\n onBeforeExit?: VoidFunction;\n onAfterExit?: VoidFunction;\n modalRef: RefObject<HTMLDivElement>;\n /** Длительность анимации закрытия (мс) — с ней разблокируется скролл при размонтировании. */\n animationTimeoutExit: number;\n}\n\ninterface UseModalTransitionResult {\n onEntering: VoidFunction;\n onEntered: VoidFunction;\n onExit: VoidFunction;\n onExited: VoidFunction;\n /** Текущий брейкпоинт входит в число тех, на которых модал не рендерится (XS/S). */\n isHiddenForBreakpoint: boolean;\n}\n\n/**\n * Жизненный цикл модала вокруг `CSSTransition`: блокировка скролла страницы на время показа, возврат\n * фокуса на активатор после закрытия, колбэки `onAppear/onBeforeExit/onAfterExit`, регистрация в стеке\n * модалов (`useModalOrder`) и обработка смены брейкпоинта (на XS/S модал скрывается). Возвращает\n * хендлеры для `CSSTransition` и флаг «скрыт на текущем брейкпоинте».\n */\nexport const useModalTransition = ({\n visible,\n isServerEnv,\n showOverlay,\n returnFocusOnClose,\n onAppear,\n onBeforeExit,\n onAfterExit,\n modalRef,\n animationTimeoutExit,\n}: UseModalTransitionParams): UseModalTransitionResult => {\n const enableScrollRef = useRef<TimeoutCallback>();\n const onAfterExitRef = useRef(onAfterExit);\n const onBeforeExitRef = useRef(onBeforeExit);\n onAfterExitRef.current = onAfterExit;\n onBeforeExitRef.current = onBeforeExit;\n const animationTimeoutExitRef = useRef(animationTimeoutExit);\n animationTimeoutExitRef.current = animationTimeoutExit;\n\n const activatorRef = useRef<HTMLElement | null>(null);\n\n const onEntering = useCallback(() => {\n if (returnFocusOnClose && document.activeElement instanceof HTMLElement) {\n activatorRef.current = document.activeElement;\n }\n if (!enableScrollRef.current && showOverlay) {\n enableScrollRef.current = disableScroll();\n }\n if (modalRef.current && !modalRef.current.contains(document.activeElement)) {\n modalRef.current.focus();\n }\n }, [returnFocusOnClose, showOverlay, modalRef]);\n\n const onEntered = useCallback(() => {\n onAppear?.();\n }, [onAppear]);\n\n const onExit = useCallback(() => {\n onBeforeExitRef.current?.();\n }, []);\n\n const onExited = useCallback(() => {\n // оставляем обработку скрола еще и тут\n // чтобы обработать историю со сменой breakpoint\n enableScrollRef.current?.();\n enableScrollRef.current = undefined;\n\n onAfterExitRef.current?.();\n\n if (activatorRef.current !== null && document.contains(activatorRef.current)) {\n activatorRef.current?.focus();\n activatorRef.current = null;\n }\n }, []);\n\n useModalOrder(!!visible && !isServerEnv, modalRef);\n\n const { breakpoint } = useBreakpoint();\n useEffect(() => {\n if (!visible) {\n return;\n }\n\n const currentBreakpointVisible = !INVISIBLE_FOR_BREAKPOINTS.includes(breakpoint);\n if (!currentBreakpointVisible) {\n onExit();\n onExited();\n return;\n }\n\n onEntering();\n // visible не должно быть в зависимостях, т.к. этот эффект обрабатывает только изменение брейкпоинта\n // visible обрабатывается CSSTransition\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [breakpoint, onEntering, onExited, onExit]);\n\n useEffect(() => {\n return () => {\n enableScrollRef.current?.(animationTimeoutExitRef.current);\n enableScrollRef.current = undefined;\n };\n }, [visible, showOverlay]);\n\n return {\n onEntering,\n onEntered,\n onExit,\n onExited,\n isHiddenForBreakpoint: INVISIBLE_FOR_BREAKPOINTS.includes(breakpoint),\n };\n};\n"],"names":[],"mappings":";;;;;;AAMA,MAAM,
|
|
1
|
+
{"version":3,"file":"useModalTransition.js","sources":["src/internal/useModalTransition.ts"],"sourcesContent":["import { type RefObject, useCallback, useEffect, useRef } from 'react';\n\nimport { disableScroll, type TimeoutCallback } from '@hh.ru/magritte-common-modal-helper';\nimport { Breakpoint, useBreakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport { useModalOrder } from '@hh.ru/magritte-ui-modal/internal/useModalOrder';\n\nconst INVISIBLE_FOR_BREAKPOINTS = [Breakpoint.XS, Breakpoint.S];\n\ninterface UseModalTransitionParams {\n visible: boolean | undefined;\n isServerEnv: boolean;\n showOverlay: boolean;\n returnFocusOnClose: boolean | undefined;\n onAppear?: VoidFunction;\n onBeforeExit?: VoidFunction;\n onAfterExit?: VoidFunction;\n modalRef: RefObject<HTMLDivElement>;\n /** Длительность анимации закрытия (мс) — с ней разблокируется скролл при размонтировании. */\n animationTimeoutExit: number;\n}\n\ninterface UseModalTransitionResult {\n onEntering: VoidFunction;\n onEntered: VoidFunction;\n onExit: VoidFunction;\n onExited: VoidFunction;\n /** Текущий брейкпоинт входит в число тех, на которых модал не рендерится (XS/S). */\n isHiddenForBreakpoint: boolean;\n}\n\n/**\n * Жизненный цикл модала вокруг `CSSTransition`: блокировка скролла страницы на время показа, возврат\n * фокуса на активатор после закрытия, колбэки `onAppear/onBeforeExit/onAfterExit`, регистрация в стеке\n * модалов (`useModalOrder`) и обработка смены брейкпоинта (на XS/S модал скрывается). Возвращает\n * хендлеры для `CSSTransition` и флаг «скрыт на текущем брейкпоинте».\n */\nexport const useModalTransition = ({\n visible,\n isServerEnv,\n showOverlay,\n returnFocusOnClose,\n onAppear,\n onBeforeExit,\n onAfterExit,\n modalRef,\n animationTimeoutExit,\n}: UseModalTransitionParams): UseModalTransitionResult => {\n const enableScrollRef = useRef<TimeoutCallback>();\n const onAfterExitRef = useRef(onAfterExit);\n const onBeforeExitRef = useRef(onBeforeExit);\n onAfterExitRef.current = onAfterExit;\n onBeforeExitRef.current = onBeforeExit;\n const animationTimeoutExitRef = useRef(animationTimeoutExit);\n animationTimeoutExitRef.current = animationTimeoutExit;\n\n const activatorRef = useRef<HTMLElement | null>(null);\n\n const onEntering = useCallback(() => {\n if (returnFocusOnClose && document.activeElement instanceof HTMLElement) {\n activatorRef.current = document.activeElement;\n }\n if (!enableScrollRef.current && showOverlay) {\n enableScrollRef.current = disableScroll();\n }\n if (modalRef.current && !modalRef.current.contains(document.activeElement)) {\n modalRef.current.focus();\n }\n }, [returnFocusOnClose, showOverlay, modalRef]);\n\n const onEntered = useCallback(() => {\n onAppear?.();\n }, [onAppear]);\n\n const onExit = useCallback(() => {\n onBeforeExitRef.current?.();\n }, []);\n\n const onExited = useCallback(() => {\n // оставляем обработку скрола еще и тут\n // чтобы обработать историю со сменой breakpoint\n enableScrollRef.current?.();\n enableScrollRef.current = undefined;\n\n onAfterExitRef.current?.();\n\n if (activatorRef.current !== null && document.contains(activatorRef.current)) {\n activatorRef.current?.focus();\n activatorRef.current = null;\n }\n }, []);\n\n useModalOrder(!!visible && !isServerEnv, modalRef);\n\n const { breakpoint } = useBreakpoint();\n useEffect(() => {\n if (!visible) {\n return;\n }\n\n const currentBreakpointVisible = !INVISIBLE_FOR_BREAKPOINTS.includes(breakpoint);\n if (!currentBreakpointVisible) {\n onExit();\n onExited();\n return;\n }\n\n onEntering();\n // visible не должно быть в зависимостях, т.к. этот эффект обрабатывает только изменение брейкпоинта\n // visible обрабатывается CSSTransition\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [breakpoint, onEntering, onExited, onExit]);\n\n useEffect(() => {\n return () => {\n enableScrollRef.current?.(animationTimeoutExitRef.current);\n enableScrollRef.current = undefined;\n };\n }, [visible, showOverlay]);\n\n return {\n onEntering,\n onEntered,\n onExit,\n onExited,\n isHiddenForBreakpoint: INVISIBLE_FOR_BREAKPOINTS.includes(breakpoint),\n };\n};\n"],"names":[],"mappings":"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"}
|