@atlaskit/react-select 2.3.0 → 2.4.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/CHANGELOG.md +22 -0
- package/dist/cjs/compiled/components/group.js +1 -1
- package/dist/cjs/components/menu.js +5 -225
- package/dist/cjs/emotion/components/group.js +1 -1
- package/dist/cjs/emotion/components/internal/index.js +34 -0
- package/dist/cjs/emotion/components/internal/scroll-manager.js +59 -0
- package/dist/cjs/emotion/components/internal/use-scroll-capture.js +132 -0
- package/dist/cjs/emotion/components/internal/use-scroll-lock.js +149 -0
- package/dist/es2019/compiled/components/group.js +1 -1
- package/dist/es2019/components/menu.js +4 -224
- package/dist/es2019/emotion/components/group.js +1 -1
- package/dist/es2019/emotion/components/internal/index.js +4 -0
- package/dist/es2019/emotion/components/internal/scroll-manager.js +51 -0
- package/dist/es2019/emotion/components/internal/use-scroll-capture.js +128 -0
- package/dist/es2019/emotion/components/internal/use-scroll-lock.js +143 -0
- package/dist/esm/compiled/components/group.js +1 -1
- package/dist/esm/components/menu.js +6 -228
- package/dist/esm/emotion/components/group.js +1 -1
- package/dist/esm/emotion/components/internal/index.js +4 -0
- package/dist/esm/emotion/components/internal/scroll-manager.js +51 -0
- package/dist/esm/emotion/components/internal/use-scroll-capture.js +126 -0
- package/dist/esm/emotion/components/internal/use-scroll-lock.js +143 -0
- package/dist/types/components/menu.d.ts +1 -1
- package/dist/types/emotion/components/internal/index.d.ts +4 -0
- package/dist/types/emotion/components/internal/scroll-manager.d.ts +17 -0
- package/dist/types/emotion/components/internal/use-scroll-capture.d.ts +12 -0
- package/dist/types/emotion/components/internal/use-scroll-lock.d.ts +9 -0
- package/dist/types-ts4.5/components/menu.d.ts +1 -1
- package/dist/types-ts4.5/emotion/components/internal/index.d.ts +4 -0
- package/dist/types-ts4.5/emotion/components/internal/scroll-manager.d.ts +17 -0
- package/dist/types-ts4.5/emotion/components/internal/use-scroll-capture.d.ts +12 -0
- package/dist/types-ts4.5/emotion/components/internal/use-scroll-lock.d.ts +9 -0
- package/package.json +3 -3
|
@@ -34,7 +34,7 @@ const Group = props => {
|
|
|
34
34
|
}, innerProps, {
|
|
35
35
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop, @atlaskit/ui-styling-standard/local-cx-xcss, @compiled/local-cx-xcss
|
|
36
36
|
className: ax([styles.root, cx(className, xcss, innerProps === null || innerProps === void 0 ? void 0 : innerProps.className)])
|
|
37
|
-
}), /*#__PURE__*/React.createElement(Heading, _extends({}, headingProps, {
|
|
37
|
+
}), label && /*#__PURE__*/React.createElement(Heading, _extends({}, headingProps, {
|
|
38
38
|
selectProps: selectProps,
|
|
39
39
|
getStyles: getStyles,
|
|
40
40
|
getClassNames: getClassNames,
|
|
@@ -1,237 +1,17 @@
|
|
|
1
1
|
/* eslint-disable @repo/internal/react/no-unsafe-spread-props */
|
|
2
|
-
import React
|
|
3
|
-
import useLayoutEffect from 'use-isomorphic-layout-effect';
|
|
2
|
+
import React from 'react';
|
|
4
3
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
5
|
-
import Compiled, { LoadingMessage as CompiledLoadingMessage, menuCSS as compiledMenuCSS, MenuList as CompiledMenuList, menuListCSS as compiledMenuListCSS, MenuPortal as CompiledMenuPortal, menuPortalCSS as compiledMenuPortalCSS, NoOptionsMessage as CompiledNoOptionsMessage } from '../compiled/components/menu';
|
|
6
|
-
import Emotion, { LoadingMessage as EmotionLoadingMessage, menuCSS as emotionMenuCSS, MenuList as EmotionMenuList, menuListCSS as emotionMenuListCSS, MenuPortal as EmotionMenuPortal, menuPortalCSS as emotionMenuPortalCSS, NoOptionsMessage as EmotionNoOptionsMessage } from '../emotion/components/menu';
|
|
7
|
-
import { animatedScrollTo, getScrollParent, getScrollTop, normalizedHeight, scrollTo } from '../utils';
|
|
8
|
-
|
|
9
|
-
// ==============================
|
|
10
|
-
// Menu
|
|
11
|
-
// ==============================
|
|
12
|
-
|
|
13
|
-
// Get Menu Placement
|
|
14
|
-
// ------------------------------
|
|
15
|
-
|
|
16
|
-
function getMenuPlacement({
|
|
17
|
-
maxHeight: preferredMaxHeight,
|
|
18
|
-
menuEl,
|
|
19
|
-
minHeight,
|
|
20
|
-
placement: preferredPlacement,
|
|
21
|
-
shouldScroll,
|
|
22
|
-
isFixedPosition,
|
|
23
|
-
controlHeight
|
|
24
|
-
}) {
|
|
25
|
-
const scrollParent = getScrollParent(menuEl);
|
|
26
|
-
const defaultState = {
|
|
27
|
-
placement: 'bottom',
|
|
28
|
-
maxHeight: preferredMaxHeight
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// something went wrong, return default state
|
|
32
|
-
if (!menuEl || !menuEl.offsetParent) {
|
|
33
|
-
return defaultState;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// we can't trust `scrollParent.scrollHeight` --> it may increase when
|
|
37
|
-
// the menu is rendered
|
|
38
|
-
const {
|
|
39
|
-
height: scrollHeight,
|
|
40
|
-
top: scrollParentTop
|
|
41
|
-
} = scrollParent.getBoundingClientRect();
|
|
42
|
-
const {
|
|
43
|
-
bottom: menuBottom,
|
|
44
|
-
height: menuHeight,
|
|
45
|
-
top: menuTop
|
|
46
|
-
} = menuEl.getBoundingClientRect();
|
|
47
|
-
const {
|
|
48
|
-
top: containerTop
|
|
49
|
-
} = menuEl.offsetParent.getBoundingClientRect();
|
|
50
|
-
const viewHeight = isFixedPosition ? window.innerHeight : normalizedHeight(scrollParent);
|
|
51
|
-
const scrollTop = getScrollTop(scrollParent);
|
|
52
|
-
// use menuTop - scrollParentTop for the actual top space of menu in the scroll container
|
|
53
|
-
const menuTopFromParent = fg('design-system-select-fix-placement') ? menuTop - scrollParentTop : menuTop;
|
|
54
|
-
const marginBottom = parseInt(getComputedStyle(menuEl).marginBottom, 10);
|
|
55
|
-
const marginTop = parseInt(getComputedStyle(menuEl).marginTop, 10);
|
|
56
|
-
const viewSpaceAbove = containerTop - marginTop;
|
|
57
|
-
const viewSpaceBelow = viewHeight - menuTopFromParent;
|
|
58
|
-
const scrollSpaceAbove = viewSpaceAbove + scrollTop;
|
|
59
|
-
const scrollSpaceBelow = scrollHeight - scrollTop - menuTopFromParent;
|
|
60
|
-
const scrollDown = menuBottom - viewHeight + scrollTop + marginBottom;
|
|
61
|
-
const scrollUp = scrollTop + menuTop - marginTop;
|
|
62
|
-
const scrollDuration = 160;
|
|
63
|
-
switch (preferredPlacement) {
|
|
64
|
-
case 'auto':
|
|
65
|
-
case 'bottom':
|
|
66
|
-
// 1: the menu will fit, do nothing
|
|
67
|
-
if (viewSpaceBelow >= menuHeight) {
|
|
68
|
-
return {
|
|
69
|
-
placement: 'bottom',
|
|
70
|
-
maxHeight: preferredMaxHeight
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// 2: the menu will fit, if scrolled
|
|
75
|
-
if (scrollSpaceBelow >= menuHeight && !isFixedPosition) {
|
|
76
|
-
if (shouldScroll) {
|
|
77
|
-
animatedScrollTo(scrollParent, scrollDown, scrollDuration);
|
|
78
|
-
}
|
|
79
|
-
return {
|
|
80
|
-
placement: 'bottom',
|
|
81
|
-
maxHeight: preferredMaxHeight
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 3: the menu will fit, if constrained
|
|
86
|
-
if (!isFixedPosition && scrollSpaceBelow >= minHeight || isFixedPosition && viewSpaceBelow >= minHeight) {
|
|
87
|
-
if (shouldScroll) {
|
|
88
|
-
animatedScrollTo(scrollParent, scrollDown, scrollDuration);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// we want to provide as much of the menu as possible to the user,
|
|
92
|
-
// so give them whatever is available below rather than the minHeight.
|
|
93
|
-
const constrainedHeight = isFixedPosition ? viewSpaceBelow - marginBottom : scrollSpaceBelow - marginBottom;
|
|
94
|
-
return {
|
|
95
|
-
placement: 'bottom',
|
|
96
|
-
maxHeight: constrainedHeight
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 4. Forked beviour when there isn't enough space below
|
|
101
|
-
|
|
102
|
-
// AUTO: flip the menu, render above
|
|
103
|
-
if (preferredPlacement === 'auto' || isFixedPosition) {
|
|
104
|
-
// may need to be constrained after flipping
|
|
105
|
-
let constrainedHeight = preferredMaxHeight;
|
|
106
|
-
const spaceAbove = isFixedPosition ? viewSpaceAbove : scrollSpaceAbove;
|
|
107
|
-
if (spaceAbove >= minHeight) {
|
|
108
|
-
constrainedHeight = Math.min(spaceAbove - marginBottom - controlHeight, preferredMaxHeight);
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
placement: 'top',
|
|
112
|
-
maxHeight: constrainedHeight
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// BOTTOM: allow browser to increase scrollable area and immediately set scroll
|
|
117
|
-
if (preferredPlacement === 'bottom') {
|
|
118
|
-
if (shouldScroll) {
|
|
119
|
-
scrollTo(scrollParent, scrollDown);
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
placement: 'bottom',
|
|
123
|
-
maxHeight: preferredMaxHeight
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
case 'top':
|
|
128
|
-
// 1: the menu will fit, do nothing
|
|
129
|
-
if (viewSpaceAbove >= menuHeight) {
|
|
130
|
-
return {
|
|
131
|
-
placement: 'top',
|
|
132
|
-
maxHeight: preferredMaxHeight
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// 2: the menu will fit, if scrolled
|
|
137
|
-
if (scrollSpaceAbove >= menuHeight && !isFixedPosition) {
|
|
138
|
-
if (shouldScroll) {
|
|
139
|
-
animatedScrollTo(scrollParent, scrollUp, scrollDuration);
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
placement: 'top',
|
|
143
|
-
maxHeight: preferredMaxHeight
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 3: the menu will fit, if constrained
|
|
148
|
-
if (!isFixedPosition && scrollSpaceAbove >= minHeight || isFixedPosition && viewSpaceAbove >= minHeight) {
|
|
149
|
-
let constrainedHeight = preferredMaxHeight;
|
|
150
|
-
|
|
151
|
-
// we want to provide as much of the menu as possible to the user,
|
|
152
|
-
// so give them whatever is available below rather than the minHeight.
|
|
153
|
-
if (!isFixedPosition && scrollSpaceAbove >= minHeight || isFixedPosition && viewSpaceAbove >= minHeight) {
|
|
154
|
-
constrainedHeight = isFixedPosition ? viewSpaceAbove - marginTop : scrollSpaceAbove - marginTop;
|
|
155
|
-
}
|
|
156
|
-
if (shouldScroll) {
|
|
157
|
-
animatedScrollTo(scrollParent, scrollUp, scrollDuration);
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
placement: 'top',
|
|
161
|
-
maxHeight: constrainedHeight
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 4. not enough space, the browser WILL NOT increase scrollable area when
|
|
166
|
-
// absolutely positioned element rendered above the viewport (only below).
|
|
167
|
-
// Flip the menu, render below
|
|
168
|
-
return {
|
|
169
|
-
placement: 'bottom',
|
|
170
|
-
maxHeight: preferredMaxHeight
|
|
171
|
-
};
|
|
172
|
-
default:
|
|
173
|
-
throw new Error(`Invalid placement provided "${preferredPlacement}".`);
|
|
174
|
-
}
|
|
175
|
-
return defaultState;
|
|
176
|
-
}
|
|
4
|
+
import Compiled, { LoadingMessage as CompiledLoadingMessage, menuCSS as compiledMenuCSS, MenuList as CompiledMenuList, menuListCSS as compiledMenuListCSS, MenuPlacer as CompiledMenuPlacer, MenuPortal as CompiledMenuPortal, menuPortalCSS as compiledMenuPortalCSS, NoOptionsMessage as CompiledNoOptionsMessage } from '../compiled/components/menu';
|
|
5
|
+
import Emotion, { LoadingMessage as EmotionLoadingMessage, menuCSS as emotionMenuCSS, MenuList as EmotionMenuList, menuListCSS as emotionMenuListCSS, MenuPlacer as EmotionMenuPlacer, MenuPortal as EmotionMenuPortal, menuPortalCSS as emotionMenuPortalCSS, NoOptionsMessage as EmotionNoOptionsMessage } from '../emotion/components/menu';
|
|
177
6
|
|
|
178
7
|
// Menu Component
|
|
179
8
|
// ------------------------------
|
|
180
9
|
|
|
181
|
-
const coercePlacement = p => p === 'auto' ? 'bottom' : p;
|
|
182
10
|
export const menuCSS = props => fg('compiled-react-select') ? compiledMenuCSS() : emotionMenuCSS(props);
|
|
183
|
-
const PortalPlacementContext = /*#__PURE__*/createContext(null);
|
|
184
11
|
|
|
185
12
|
// NOTE: internal only
|
|
186
13
|
// eslint-disable-next-line @repo/internal/react/require-jsdoc
|
|
187
|
-
export const MenuPlacer = props =>
|
|
188
|
-
const {
|
|
189
|
-
children,
|
|
190
|
-
minMenuHeight,
|
|
191
|
-
maxMenuHeight,
|
|
192
|
-
menuPlacement,
|
|
193
|
-
menuPosition,
|
|
194
|
-
menuShouldScrollIntoView
|
|
195
|
-
} = props;
|
|
196
|
-
const {
|
|
197
|
-
setPortalPlacement
|
|
198
|
-
} = useContext(PortalPlacementContext) || {};
|
|
199
|
-
const ref = useRef(null);
|
|
200
|
-
const [maxHeight, setMaxHeight] = useState(maxMenuHeight);
|
|
201
|
-
const [placement, setPlacement] = useState(null);
|
|
202
|
-
// The minimum height of the control
|
|
203
|
-
const controlHeight = 38;
|
|
204
|
-
useLayoutEffect(() => {
|
|
205
|
-
const menuEl = ref.current;
|
|
206
|
-
if (!menuEl) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// DO NOT scroll if position is fixed
|
|
211
|
-
const isFixedPosition = menuPosition === 'fixed';
|
|
212
|
-
const shouldScroll = menuShouldScrollIntoView && !isFixedPosition;
|
|
213
|
-
const state = getMenuPlacement({
|
|
214
|
-
maxHeight: maxMenuHeight,
|
|
215
|
-
menuEl,
|
|
216
|
-
minHeight: minMenuHeight,
|
|
217
|
-
placement: menuPlacement,
|
|
218
|
-
shouldScroll,
|
|
219
|
-
isFixedPosition,
|
|
220
|
-
controlHeight
|
|
221
|
-
});
|
|
222
|
-
setMaxHeight(state.maxHeight);
|
|
223
|
-
setPlacement(state.placement);
|
|
224
|
-
setPortalPlacement === null || setPortalPlacement === void 0 ? void 0 : setPortalPlacement(state.placement);
|
|
225
|
-
}, [maxMenuHeight, menuPlacement, menuPosition, menuShouldScrollIntoView, minMenuHeight, setPortalPlacement, controlHeight]);
|
|
226
|
-
return children({
|
|
227
|
-
ref,
|
|
228
|
-
placerProps: {
|
|
229
|
-
...props,
|
|
230
|
-
placement: placement || coercePlacement(menuPlacement),
|
|
231
|
-
maxHeight
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
};
|
|
14
|
+
export const MenuPlacer = props => fg('compiled-react-select') ? /*#__PURE__*/React.createElement(CompiledMenuPlacer, props) : /*#__PURE__*/React.createElement(EmotionMenuPlacer, props);
|
|
235
15
|
const Menu = props => fg('compiled-react-select') ? /*#__PURE__*/React.createElement(Compiled, props) : /*#__PURE__*/React.createElement(Emotion, props);
|
|
236
16
|
|
|
237
17
|
// eslint-disable-next-line @repo/internal/react/require-jsdoc
|
|
@@ -25,7 +25,7 @@ const Group = props => {
|
|
|
25
25
|
} = props;
|
|
26
26
|
return jsx("div", _extends({}, getStyleProps(props, 'group', {
|
|
27
27
|
group: true
|
|
28
|
-
}), innerProps), jsx(Heading, _extends({}, headingProps, {
|
|
28
|
+
}), innerProps), label && jsx(Heading, _extends({}, headingProps, {
|
|
29
29
|
selectProps: selectProps,
|
|
30
30
|
getStyles: getStyles,
|
|
31
31
|
getClassNames: getClassNames,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
import { Fragment } from 'react';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled
|
|
8
|
+
import { css, jsx } from '@emotion/react';
|
|
9
|
+
import useScrollCapture from './use-scroll-capture';
|
|
10
|
+
import useScrollLock from './use-scroll-lock';
|
|
11
|
+
const styles = css({
|
|
12
|
+
position: 'fixed',
|
|
13
|
+
insetBlockEnd: 0,
|
|
14
|
+
insetBlockStart: 0,
|
|
15
|
+
insetInlineEnd: 0,
|
|
16
|
+
insetInlineStart: 0
|
|
17
|
+
});
|
|
18
|
+
const blurSelectInput = event => {
|
|
19
|
+
const element = event.target;
|
|
20
|
+
return element.ownerDocument.activeElement && element.ownerDocument.activeElement.blur();
|
|
21
|
+
};
|
|
22
|
+
export default function ScrollManager({
|
|
23
|
+
children,
|
|
24
|
+
lockEnabled,
|
|
25
|
+
captureEnabled = true,
|
|
26
|
+
onBottomArrive,
|
|
27
|
+
onBottomLeave,
|
|
28
|
+
onTopArrive,
|
|
29
|
+
onTopLeave
|
|
30
|
+
}) {
|
|
31
|
+
const setScrollCaptureTarget = useScrollCapture({
|
|
32
|
+
isEnabled: captureEnabled,
|
|
33
|
+
onBottomArrive,
|
|
34
|
+
onBottomLeave,
|
|
35
|
+
onTopArrive,
|
|
36
|
+
onTopLeave
|
|
37
|
+
});
|
|
38
|
+
const setScrollLockTarget = useScrollLock({
|
|
39
|
+
isEnabled: lockEnabled
|
|
40
|
+
});
|
|
41
|
+
const targetRef = element => {
|
|
42
|
+
setScrollCaptureTarget(element);
|
|
43
|
+
setScrollLockTarget(element);
|
|
44
|
+
};
|
|
45
|
+
return jsx(Fragment, null, lockEnabled &&
|
|
46
|
+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
|
47
|
+
jsx("div", {
|
|
48
|
+
onClick: blurSelectInput,
|
|
49
|
+
css: styles
|
|
50
|
+
}), children(targetRef));
|
|
51
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { supportsPassiveEvents } from '../../../utils';
|
|
3
|
+
const cancelScroll = event => {
|
|
4
|
+
if (event.cancelable) {
|
|
5
|
+
event.preventDefault();
|
|
6
|
+
}
|
|
7
|
+
event.stopPropagation();
|
|
8
|
+
};
|
|
9
|
+
// TODO: Fill in the hook {description}.
|
|
10
|
+
/**
|
|
11
|
+
* {description}.
|
|
12
|
+
*/
|
|
13
|
+
export default function useScrollCapture({
|
|
14
|
+
isEnabled,
|
|
15
|
+
onBottomArrive,
|
|
16
|
+
onBottomLeave,
|
|
17
|
+
onTopArrive,
|
|
18
|
+
onTopLeave
|
|
19
|
+
}) {
|
|
20
|
+
const isBottom = useRef(false);
|
|
21
|
+
const isTop = useRef(false);
|
|
22
|
+
const touchStart = useRef(0);
|
|
23
|
+
const scrollTarget = useRef(null);
|
|
24
|
+
const handleEventDelta = useCallback((event, delta) => {
|
|
25
|
+
if (scrollTarget.current === null) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const {
|
|
29
|
+
scrollTop,
|
|
30
|
+
scrollHeight,
|
|
31
|
+
clientHeight
|
|
32
|
+
} = scrollTarget.current;
|
|
33
|
+
const target = scrollTarget.current;
|
|
34
|
+
const isDeltaPositive = delta > 0;
|
|
35
|
+
const availableScroll = scrollHeight - clientHeight - scrollTop;
|
|
36
|
+
let shouldCancelScroll = false;
|
|
37
|
+
|
|
38
|
+
// reset bottom/top flags
|
|
39
|
+
if (availableScroll > delta && isBottom.current) {
|
|
40
|
+
if (onBottomLeave) {
|
|
41
|
+
onBottomLeave(event);
|
|
42
|
+
}
|
|
43
|
+
isBottom.current = false;
|
|
44
|
+
}
|
|
45
|
+
if (isDeltaPositive && isTop.current) {
|
|
46
|
+
if (onTopLeave) {
|
|
47
|
+
onTopLeave(event);
|
|
48
|
+
}
|
|
49
|
+
isTop.current = false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// bottom limit
|
|
53
|
+
if (isDeltaPositive && delta > availableScroll) {
|
|
54
|
+
if (onBottomArrive && !isBottom.current) {
|
|
55
|
+
onBottomArrive(event);
|
|
56
|
+
}
|
|
57
|
+
target.scrollTop = scrollHeight;
|
|
58
|
+
shouldCancelScroll = true;
|
|
59
|
+
isBottom.current = true;
|
|
60
|
+
|
|
61
|
+
// top limit
|
|
62
|
+
} else if (!isDeltaPositive && -delta > scrollTop) {
|
|
63
|
+
if (onTopArrive && !isTop.current) {
|
|
64
|
+
onTopArrive(event);
|
|
65
|
+
}
|
|
66
|
+
target.scrollTop = 0;
|
|
67
|
+
shouldCancelScroll = true;
|
|
68
|
+
isTop.current = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// cancel scroll
|
|
72
|
+
if (shouldCancelScroll) {
|
|
73
|
+
cancelScroll(event);
|
|
74
|
+
}
|
|
75
|
+
}, [onBottomArrive, onBottomLeave, onTopArrive, onTopLeave]);
|
|
76
|
+
const onWheel = useCallback(event => {
|
|
77
|
+
handleEventDelta(event, event.deltaY);
|
|
78
|
+
}, [handleEventDelta]);
|
|
79
|
+
const onTouchStart = useCallback(event => {
|
|
80
|
+
// set touch start so we can calculate touchmove delta
|
|
81
|
+
touchStart.current = event.changedTouches[0].clientY;
|
|
82
|
+
}, []);
|
|
83
|
+
const onTouchMove = useCallback(event => {
|
|
84
|
+
const deltaY = touchStart.current - event.changedTouches[0].clientY;
|
|
85
|
+
handleEventDelta(event, deltaY);
|
|
86
|
+
}, [handleEventDelta]);
|
|
87
|
+
const startListening = useCallback(el => {
|
|
88
|
+
// bail early if no element is available to attach to
|
|
89
|
+
if (!el) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const notPassive = supportsPassiveEvents ? {
|
|
93
|
+
passive: false
|
|
94
|
+
} : false;
|
|
95
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
96
|
+
el.addEventListener('wheel', onWheel, notPassive);
|
|
97
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
98
|
+
el.addEventListener('touchstart', onTouchStart, notPassive);
|
|
99
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
100
|
+
el.addEventListener('touchmove', onTouchMove, notPassive);
|
|
101
|
+
}, [onTouchMove, onTouchStart, onWheel]);
|
|
102
|
+
const stopListening = useCallback(el => {
|
|
103
|
+
// bail early if no element is available to detach from
|
|
104
|
+
if (!el) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
109
|
+
el.removeEventListener('wheel', onWheel, false);
|
|
110
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
111
|
+
el.removeEventListener('touchstart', onTouchStart, false);
|
|
112
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
113
|
+
el.removeEventListener('touchmove', onTouchMove, false);
|
|
114
|
+
}, [onTouchMove, onTouchStart, onWheel]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!isEnabled) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const element = scrollTarget.current;
|
|
120
|
+
startListening(element);
|
|
121
|
+
return () => {
|
|
122
|
+
stopListening(element);
|
|
123
|
+
};
|
|
124
|
+
}, [isEnabled, startListening, stopListening]);
|
|
125
|
+
return element => {
|
|
126
|
+
scrollTarget.current = element;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
const STYLE_KEYS = ['boxSizing', 'height', 'overflow', 'paddingRight', 'position'];
|
|
3
|
+
const LOCK_STYLES = {
|
|
4
|
+
boxSizing: 'border-box',
|
|
5
|
+
// account for possible declaration `width: 100%;` on body
|
|
6
|
+
overflow: 'hidden',
|
|
7
|
+
position: 'relative',
|
|
8
|
+
height: '100%'
|
|
9
|
+
};
|
|
10
|
+
function preventTouchMove(e) {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
}
|
|
13
|
+
function allowTouchMove(e) {
|
|
14
|
+
e.stopPropagation();
|
|
15
|
+
}
|
|
16
|
+
function preventInertiaScroll() {
|
|
17
|
+
const top = this.scrollTop;
|
|
18
|
+
const totalScroll = this.scrollHeight;
|
|
19
|
+
const currentScroll = top + this.offsetHeight;
|
|
20
|
+
if (top === 0) {
|
|
21
|
+
this.scrollTop = 1;
|
|
22
|
+
} else if (currentScroll === totalScroll) {
|
|
23
|
+
this.scrollTop = top - 1;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// `ontouchstart` check works on most browsers
|
|
28
|
+
// `maxTouchPoints` works on IE10/11 and Surface
|
|
29
|
+
function isTouchDevice() {
|
|
30
|
+
// eslint-disable-next-line compat/compat
|
|
31
|
+
return 'ontouchstart' in window || navigator.maxTouchPoints;
|
|
32
|
+
}
|
|
33
|
+
const canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
34
|
+
let activeScrollLocks = 0;
|
|
35
|
+
const listenerOptions = {
|
|
36
|
+
capture: false,
|
|
37
|
+
passive: false
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// TODO: Fill in the hook {description}.
|
|
41
|
+
/**
|
|
42
|
+
* {description}.
|
|
43
|
+
*/
|
|
44
|
+
export default function useScrollLock({
|
|
45
|
+
isEnabled,
|
|
46
|
+
accountForScrollbars = true
|
|
47
|
+
}) {
|
|
48
|
+
const originalStyles = useRef({});
|
|
49
|
+
const scrollTarget = useRef(null);
|
|
50
|
+
const addScrollLock = useCallback(touchScrollTarget => {
|
|
51
|
+
if (!canUseDOM) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const target = document.body;
|
|
55
|
+
const targetStyle = target && target.style;
|
|
56
|
+
if (accountForScrollbars) {
|
|
57
|
+
// store any styles already applied to the body
|
|
58
|
+
STYLE_KEYS.forEach(key => {
|
|
59
|
+
const val = targetStyle && targetStyle[key];
|
|
60
|
+
originalStyles.current[key] = val;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// apply the lock styles and padding if this is the first scroll lock
|
|
65
|
+
if (accountForScrollbars && activeScrollLocks < 1) {
|
|
66
|
+
const currentPadding = parseInt(originalStyles.current.paddingRight, 10) || 0;
|
|
67
|
+
const clientWidth = document.body ? document.body.clientWidth : 0;
|
|
68
|
+
const adjustedPadding = window.innerWidth - clientWidth + currentPadding || 0;
|
|
69
|
+
Object.keys(LOCK_STYLES).forEach(key => {
|
|
70
|
+
const val = LOCK_STYLES[key];
|
|
71
|
+
if (targetStyle) {
|
|
72
|
+
targetStyle[key] = val;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
if (targetStyle) {
|
|
76
|
+
targetStyle.paddingRight = `${adjustedPadding}px`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// account for touch devices
|
|
81
|
+
if (target && isTouchDevice()) {
|
|
82
|
+
// Mobile Safari ignores { overflow: hidden } declaration on the body.
|
|
83
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
84
|
+
target.addEventListener('touchmove', preventTouchMove, listenerOptions);
|
|
85
|
+
|
|
86
|
+
// Allow scroll on provided target
|
|
87
|
+
if (touchScrollTarget) {
|
|
88
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
89
|
+
touchScrollTarget.addEventListener('touchstart', preventInertiaScroll, listenerOptions);
|
|
90
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
91
|
+
touchScrollTarget.addEventListener('touchmove', allowTouchMove, listenerOptions);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// increment active scroll locks
|
|
96
|
+
activeScrollLocks += 1;
|
|
97
|
+
}, [accountForScrollbars]);
|
|
98
|
+
const removeScrollLock = useCallback(touchScrollTarget => {
|
|
99
|
+
if (!canUseDOM) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const target = document.body;
|
|
103
|
+
const targetStyle = target && target.style;
|
|
104
|
+
|
|
105
|
+
// safely decrement active scroll locks
|
|
106
|
+
activeScrollLocks = Math.max(activeScrollLocks - 1, 0);
|
|
107
|
+
|
|
108
|
+
// reapply original body styles, if any
|
|
109
|
+
if (accountForScrollbars && activeScrollLocks < 1) {
|
|
110
|
+
STYLE_KEYS.forEach(key => {
|
|
111
|
+
const val = originalStyles.current[key];
|
|
112
|
+
if (targetStyle) {
|
|
113
|
+
targetStyle[key] = val;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// remove touch listeners
|
|
119
|
+
if (target && isTouchDevice()) {
|
|
120
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
121
|
+
target.removeEventListener('touchmove', preventTouchMove, listenerOptions);
|
|
122
|
+
if (touchScrollTarget) {
|
|
123
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
124
|
+
touchScrollTarget.removeEventListener('touchstart', preventInertiaScroll, listenerOptions);
|
|
125
|
+
// eslint-disable-next-line @repo/internal/dom-events/no-unsafe-event-listeners
|
|
126
|
+
touchScrollTarget.removeEventListener('touchmove', allowTouchMove, listenerOptions);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}, [accountForScrollbars]);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!isEnabled) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const element = scrollTarget.current;
|
|
135
|
+
addScrollLock(element);
|
|
136
|
+
return () => {
|
|
137
|
+
removeScrollLock(element);
|
|
138
|
+
};
|
|
139
|
+
}, [isEnabled, addScrollLock, removeScrollLock]);
|
|
140
|
+
return element => {
|
|
141
|
+
scrollTarget.current = element;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -35,7 +35,7 @@ var Group = function Group(props) {
|
|
|
35
35
|
}, innerProps, {
|
|
36
36
|
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop, @atlaskit/ui-styling-standard/local-cx-xcss, @compiled/local-cx-xcss
|
|
37
37
|
className: ax([styles.root, cx(className, xcss, innerProps === null || innerProps === void 0 ? void 0 : innerProps.className)])
|
|
38
|
-
}), /*#__PURE__*/React.createElement(Heading, _extends({}, headingProps, {
|
|
38
|
+
}), label && /*#__PURE__*/React.createElement(Heading, _extends({}, headingProps, {
|
|
39
39
|
selectProps: selectProps,
|
|
40
40
|
getStyles: getStyles,
|
|
41
41
|
getClassNames: getClassNames,
|