@atlaskit/react-select 2.3.0 → 2.4.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.
Files changed (27) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/cjs/components/menu.js +5 -225
  3. package/dist/cjs/emotion/components/internal/index.js +34 -0
  4. package/dist/cjs/emotion/components/internal/scroll-manager.js +59 -0
  5. package/dist/cjs/emotion/components/internal/use-scroll-capture.js +132 -0
  6. package/dist/cjs/emotion/components/internal/use-scroll-lock.js +149 -0
  7. package/dist/es2019/components/menu.js +4 -224
  8. package/dist/es2019/emotion/components/internal/index.js +4 -0
  9. package/dist/es2019/emotion/components/internal/scroll-manager.js +51 -0
  10. package/dist/es2019/emotion/components/internal/use-scroll-capture.js +128 -0
  11. package/dist/es2019/emotion/components/internal/use-scroll-lock.js +143 -0
  12. package/dist/esm/components/menu.js +6 -228
  13. package/dist/esm/emotion/components/internal/index.js +4 -0
  14. package/dist/esm/emotion/components/internal/scroll-manager.js +51 -0
  15. package/dist/esm/emotion/components/internal/use-scroll-capture.js +126 -0
  16. package/dist/esm/emotion/components/internal/use-scroll-lock.js +143 -0
  17. package/dist/types/components/menu.d.ts +1 -1
  18. package/dist/types/emotion/components/internal/index.d.ts +4 -0
  19. package/dist/types/emotion/components/internal/scroll-manager.d.ts +17 -0
  20. package/dist/types/emotion/components/internal/use-scroll-capture.d.ts +12 -0
  21. package/dist/types/emotion/components/internal/use-scroll-lock.d.ts +9 -0
  22. package/dist/types-ts4.5/components/menu.d.ts +1 -1
  23. package/dist/types-ts4.5/emotion/components/internal/index.d.ts +4 -0
  24. package/dist/types-ts4.5/emotion/components/internal/scroll-manager.d.ts +17 -0
  25. package/dist/types-ts4.5/emotion/components/internal/use-scroll-capture.d.ts +12 -0
  26. package/dist/types-ts4.5/emotion/components/internal/use-scroll-lock.d.ts +9 -0
  27. package/package.json +2 -2
@@ -1,237 +1,17 @@
1
1
  /* eslint-disable @repo/internal/react/no-unsafe-spread-props */
2
- import React, { createContext, useContext, useRef, useState } from '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
@@ -0,0 +1,4 @@
1
+ export { default as A11yText } from './a11y-text';
2
+ export { default as DummyInput } from './dummy-input';
3
+ export { default as ScrollManager } from './scroll-manager';
4
+ export { default as RequiredInput } from './required-input';
@@ -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
+ }