@skyscanner/backpack-web 41.5.0 → 41.7.0-beta-layout
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.
|
@@ -19,7 +19,14 @@
|
|
|
19
19
|
import { useEffect, useState } from 'react';
|
|
20
20
|
const useMediaQuery = (query, matchSSR = false) => {
|
|
21
21
|
const isClient = typeof window !== 'undefined' && !!window.matchMedia;
|
|
22
|
-
const [matches, setMatches] = useState(
|
|
22
|
+
const [matches, setMatches] = useState(() => {
|
|
23
|
+
// When matchSSR=true: use matchSSR value to match server-rendered HTML
|
|
24
|
+
// This prevents hydration errors when User-Agent (server) != viewport size (client)
|
|
25
|
+
if (!isClient || matchSSR) {
|
|
26
|
+
return matchSSR;
|
|
27
|
+
}
|
|
28
|
+
return window.matchMedia(query).matches;
|
|
29
|
+
});
|
|
23
30
|
useEffect(() => {
|
|
24
31
|
if (isClient) {
|
|
25
32
|
const media = window.matchMedia(query);
|
|
@@ -15,13 +15,22 @@
|
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
|
-
import { forwardRef, useRef, useEffect } from 'react';
|
|
18
|
+
import { forwardRef, useRef, useEffect, useCallback } from 'react';
|
|
19
19
|
import { useComposedRefs } from '@radix-ui/react-compose-refs';
|
|
20
20
|
import * as Slider from '@radix-ui/react-slider';
|
|
21
21
|
import { cssModules, isRTL, setNativeValue } from "../../bpk-react-utils";
|
|
22
22
|
import STYLES from "./BpkSlider.module.css";
|
|
23
23
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
24
24
|
const getClassName = cssModules(STYLES);
|
|
25
|
+
|
|
26
|
+
// Pure utility function to normalize slider values for callbacks
|
|
27
|
+
// Returns single number for single-thumb slider, array for range slider
|
|
28
|
+
const processSliderValues = (sliderValues, callback) => {
|
|
29
|
+
const val = sliderValues.length === 1 ? sliderValues[0] : sliderValues;
|
|
30
|
+
if (callback) {
|
|
31
|
+
callback(val);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
25
34
|
const BpkSlider = ({
|
|
26
35
|
ariaLabel,
|
|
27
36
|
ariaValuetext,
|
|
@@ -37,19 +46,72 @@ const BpkSlider = ({
|
|
|
37
46
|
}) => {
|
|
38
47
|
const invert = isRTL();
|
|
39
48
|
const currentValue = Array.isArray(value) ? value : [value];
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
|
|
50
|
+
// Track the latest value and callback for the Chrome workaround
|
|
51
|
+
// Using refs to avoid re-registering document event listeners when these change
|
|
52
|
+
const latestValueRef = useRef(currentValue);
|
|
53
|
+
const onAfterChangeRef = useRef(onAfterChange);
|
|
54
|
+
const isDraggingRef = useRef(false);
|
|
55
|
+
const hasCommittedRef = useRef(false);
|
|
56
|
+
// Store cleanup function to prevent memory leaks if component unmounts during drag
|
|
57
|
+
const cleanupRef = useRef(null);
|
|
58
|
+
|
|
59
|
+
// Keep refs updated
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
latestValueRef.current = currentValue;
|
|
62
|
+
}, [currentValue]);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
onAfterChangeRef.current = onAfterChange;
|
|
65
|
+
}, [onAfterChange]);
|
|
66
|
+
|
|
67
|
+
// Cleanup on unmount to prevent memory leaks
|
|
68
|
+
useEffect(() => () => {
|
|
69
|
+
if (cleanupRef.current) {
|
|
70
|
+
cleanupRef.current();
|
|
44
71
|
}
|
|
45
|
-
};
|
|
72
|
+
}, []);
|
|
46
73
|
const thumbRefs = [useRef(null), useRef(null)];
|
|
47
|
-
const handleOnChange = sliderValues => {
|
|
74
|
+
const handleOnChange = useCallback(sliderValues => {
|
|
75
|
+
latestValueRef.current = sliderValues;
|
|
48
76
|
processSliderValues(sliderValues, onChange);
|
|
49
|
-
};
|
|
50
|
-
const handleOnAfterChange = sliderValues => {
|
|
77
|
+
}, [onChange]);
|
|
78
|
+
const handleOnAfterChange = useCallback(sliderValues => {
|
|
79
|
+
hasCommittedRef.current = true;
|
|
80
|
+
isDraggingRef.current = false;
|
|
51
81
|
processSliderValues(sliderValues, onAfterChange);
|
|
52
|
-
};
|
|
82
|
+
}, [onAfterChange]);
|
|
83
|
+
|
|
84
|
+
// Chrome workaround: Listen for pointerup/pointercancel on document as safety net
|
|
85
|
+
// This ensures onAfterChange fires even when Radix's onValueCommit doesn't
|
|
86
|
+
// See: https://github.com/radix-ui/primitives/issues/1760
|
|
87
|
+
const handlePointerDown = useCallback(() => {
|
|
88
|
+
// Clean up any previous listener still hanging around (edge case)
|
|
89
|
+
if (cleanupRef.current) {
|
|
90
|
+
cleanupRef.current();
|
|
91
|
+
}
|
|
92
|
+
isDraggingRef.current = true;
|
|
93
|
+
hasCommittedRef.current = false;
|
|
94
|
+
const handlePointerEnd = () => {
|
|
95
|
+
document.removeEventListener('pointerup', handlePointerEnd);
|
|
96
|
+
document.removeEventListener('pointercancel', handlePointerEnd);
|
|
97
|
+
cleanupRef.current = null;
|
|
98
|
+
|
|
99
|
+
// Use requestAnimationFrame to defer the check, allowing Radix's onValueCommit
|
|
100
|
+
// to fire first and set hasCommittedRef.current = true. This prevents the race
|
|
101
|
+
// condition where both handlers could fire onAfterChange for the same interaction.
|
|
102
|
+
requestAnimationFrame(() => {
|
|
103
|
+
if (isDraggingRef.current && !hasCommittedRef.current && onAfterChangeRef.current) {
|
|
104
|
+
// Radix didn't fire onValueCommit, so we fire it manually
|
|
105
|
+
processSliderValues(latestValueRef.current, onAfterChangeRef.current);
|
|
106
|
+
}
|
|
107
|
+
isDraggingRef.current = false;
|
|
108
|
+
hasCommittedRef.current = false;
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
cleanupRef.current = handlePointerEnd;
|
|
112
|
+
document.addEventListener('pointerup', handlePointerEnd);
|
|
113
|
+
document.addEventListener('pointercancel', handlePointerEnd);
|
|
114
|
+
}, []);
|
|
53
115
|
return /*#__PURE__*/_jsxs(Slider.Root, {
|
|
54
116
|
className: getClassName('bpk-slider'),
|
|
55
117
|
defaultValue: currentValue,
|
|
@@ -58,6 +120,7 @@ const BpkSlider = ({
|
|
|
58
120
|
step: step || 1,
|
|
59
121
|
onValueChange: handleOnChange,
|
|
60
122
|
onValueCommit: handleOnAfterChange,
|
|
123
|
+
onPointerDown: handlePointerDown,
|
|
61
124
|
inverted: invert,
|
|
62
125
|
minStepsBetweenThumbs: minDistance,
|
|
63
126
|
...rest,
|