@automattic/jetpack-components 0.71.0 → 0.72.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 +17 -1
- package/build/components/action-button/index.d.ts +10 -10
- package/build/components/action-button/index.js +1 -1
- package/build/components/action-popover/types.d.ts +0 -1
- package/build/components/admin-page/types.d.ts +0 -1
- package/build/components/admin-section/types.d.ts +0 -1
- package/build/components/boost-score-graph/uplot-line-chart.js +2 -2
- package/build/components/dot-pager/index.d.ts +14 -0
- package/build/components/dot-pager/index.js +52 -0
- package/build/components/icon-tooltip/index.js +1 -1
- package/build/components/icon-tooltip/types.d.ts +0 -1
- package/build/components/layout/types.d.ts +0 -1
- package/build/components/number-control/index.js +1 -1
- package/build/components/popover/index.d.ts +0 -1
- package/build/components/pricing-table/types.d.ts +0 -1
- package/build/components/product-offer/types.d.ts +0 -1
- package/build/components/product-price/types.d.ts +0 -1
- package/build/components/radio-control/index.d.ts +0 -1
- package/build/components/spinner/index.d.ts +3 -3
- package/build/components/spinner/index.js +1 -1
- package/build/components/split-button/types.d.ts +0 -1
- package/build/components/stat-card/types.d.ts +0 -1
- package/build/components/status/index.d.ts +0 -1
- package/build/components/swipeable/index.d.ts +12 -0
- package/build/components/swipeable/index.js +293 -0
- package/build/components/terms-of-service/index.d.ts +0 -1
- package/build/components/theme-provider/types.d.ts +0 -1
- package/build/components/toggle-control/index.d.ts +0 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/components/action-button/index.jsx +1 -1
- package/components/dot-pager/README.md +20 -0
- package/components/dot-pager/index.tsx +147 -0
- package/components/dot-pager/style.scss +80 -0
- package/components/number-control/index.jsx +3 -1
- package/components/swipeable/README.md +34 -0
- package/components/swipeable/index.tsx +425 -0
- package/components/swipeable/style.scss +34 -0
- package/index.ts +1 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
### This is a list detailing changes for the Jetpack RNA Components package releases.
|
|
4
4
|
|
|
5
|
+
## [0.72.1] - 2025-04-01
|
|
6
|
+
### Changed
|
|
7
|
+
- Update package dependencies. [#42762]
|
|
8
|
+
|
|
9
|
+
## [0.72.0] - 2025-03-31
|
|
10
|
+
### Added
|
|
11
|
+
- Add dot-page component. [#42625]
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Implement a continuous loop in testimonial slider. [#42781]
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Components: Prevent deprecation notices by adding `__next40pxDefaultSize` to controls. [#42677]
|
|
18
|
+
|
|
5
19
|
## [0.71.0] - 2025-03-26
|
|
6
20
|
### Removed
|
|
7
|
-
-
|
|
21
|
+
- Remove threat components. [#41654]
|
|
8
22
|
|
|
9
23
|
### Fixed
|
|
10
24
|
- Split Button: Increase compatibility of components, preventing console warnings. [#42627]
|
|
@@ -1333,6 +1347,8 @@
|
|
|
1333
1347
|
### Changed
|
|
1334
1348
|
- Update node version requirement to 14.16.1
|
|
1335
1349
|
|
|
1350
|
+
[0.72.1]: https://github.com/Automattic/jetpack-components/compare/0.72.0...0.72.1
|
|
1351
|
+
[0.72.0]: https://github.com/Automattic/jetpack-components/compare/0.71.0...0.72.0
|
|
1336
1352
|
[0.71.0]: https://github.com/Automattic/jetpack-components/compare/0.70.1...0.71.0
|
|
1337
1353
|
[0.70.1]: https://github.com/Automattic/jetpack-components/compare/0.70.0...0.70.1
|
|
1338
1354
|
[0.70.0]: https://github.com/Automattic/jetpack-components/compare/0.69.1...0.70.0
|
|
@@ -7,19 +7,19 @@ export default ActionButton;
|
|
|
7
7
|
* It is useful to async actions when the user has to wait the result of a request or process.
|
|
8
8
|
*
|
|
9
9
|
* @param {object} props - The properties.
|
|
10
|
-
* @return {React.
|
|
10
|
+
* @return {React.ReactNode} The `ActionButton` component.
|
|
11
11
|
*/
|
|
12
|
-
declare function ActionButton(props: object): React.
|
|
12
|
+
declare function ActionButton(props: object): React.ReactNode;
|
|
13
13
|
declare namespace ActionButton {
|
|
14
14
|
namespace propTypes {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
let label: any;
|
|
16
|
+
let onClick: any;
|
|
17
|
+
let isLoading: any;
|
|
18
|
+
let isDisabled: any;
|
|
19
|
+
let displayError: any;
|
|
20
|
+
let errorMessage: any;
|
|
21
|
+
let variant: any;
|
|
22
|
+
let isExternalLink: any;
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
import React from 'react';
|
|
@@ -20,7 +20,7 @@ import styles from './style.module.scss';
|
|
|
20
20
|
* It is useful to async actions when the user has to wait the result of a request or process.
|
|
21
21
|
*
|
|
22
22
|
* @param {object} props - The properties.
|
|
23
|
-
* @return {React.
|
|
23
|
+
* @return {React.ReactNode} The `ActionButton` component.
|
|
24
24
|
*/
|
|
25
25
|
const ActionButton = props => {
|
|
26
26
|
const { label, onClick, isLoading = false, loadingText, isDisabled, displayError = false, errorMessage = __('An error occurred. Please try again.', 'jetpack-components'), variant = 'primary', isExternalLink = false, customClass, } = props;
|
|
@@ -33,7 +33,7 @@ function createSerieInfo(label, score) {
|
|
|
33
33
|
gradient.addColorStop(0, getColor(score, '44'));
|
|
34
34
|
gradient.addColorStop(1, getColor(score, '11'));
|
|
35
35
|
return gradient;
|
|
36
|
-
},
|
|
36
|
+
}, // use the gradient as fill for the series
|
|
37
37
|
width: 2,
|
|
38
38
|
paths: (u, seriesIdx, idx0, idx1) => {
|
|
39
39
|
return spline?.()(u, seriesIdx, idx0, idx1) || null;
|
|
@@ -115,7 +115,7 @@ export default function UplotLineChart({ range, periods, annotations = [] }) {
|
|
|
115
115
|
space: 100,
|
|
116
116
|
size: 30,
|
|
117
117
|
grid: {
|
|
118
|
-
stroke: 'rgba(220, 220, 222, 0.5)',
|
|
118
|
+
stroke: 'rgba(220, 220, 222, 0.5)', // #DCDCDE with 0.5 opacity
|
|
119
119
|
width: 1,
|
|
120
120
|
},
|
|
121
121
|
ticks: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import './style.scss';
|
|
3
|
+
type DotPagerProps = {
|
|
4
|
+
hasDynamicHeight?: boolean;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
onPageSelected?: (index: number) => void;
|
|
8
|
+
isClickEnabled?: boolean;
|
|
9
|
+
rotateTime?: number;
|
|
10
|
+
tracksPrefix?: string;
|
|
11
|
+
tracksFn?: (eventName: string, data?: Record<string, unknown>) => void;
|
|
12
|
+
};
|
|
13
|
+
declare const DotPager: ({ hasDynamicHeight, children, className, onPageSelected, isClickEnabled, rotateTime, tracksPrefix, tracksFn, ...props }: DotPagerProps) => import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export default DotPager;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '@wordpress/components';
|
|
3
|
+
import { __, sprintf } from '@wordpress/i18n';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { Children, useState, useEffect, useCallback, useMemo } from 'react';
|
|
6
|
+
import { Swipeable } from '../swipeable/index.js';
|
|
7
|
+
import './style.scss';
|
|
8
|
+
const Controls = ({ currentPage, numberOfPages, setCurrentPage, tracksPrefix, tracksFn, }) => {
|
|
9
|
+
// Create a map of memoized handlers for each page
|
|
10
|
+
const pageHandlers = useMemo(() => Array.from({ length: numberOfPages }, (_, page) => () => {
|
|
11
|
+
tracksFn(tracksPrefix + '_dot_click', {
|
|
12
|
+
current_page: currentPage,
|
|
13
|
+
destination_page: page,
|
|
14
|
+
});
|
|
15
|
+
setCurrentPage(page);
|
|
16
|
+
}), [numberOfPages, currentPage, tracksFn, tracksPrefix, setCurrentPage]);
|
|
17
|
+
if (numberOfPages < 2) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return (_jsx("ul", { className: "dot-pager__controls", "aria-label": __('Pager controls', 'jetpack-components'), children: Array.from({ length: numberOfPages }, (_, page) => (_jsx("li", { "aria-current": page === currentPage ? 'page' : undefined, children: _jsx(Button, { className: clsx('dot-pager__control-choose-page', {
|
|
21
|
+
'dot-pager__control-current': page === currentPage,
|
|
22
|
+
}), disabled: page === currentPage, "aria-label": sprintf(
|
|
23
|
+
/* translators: %1$d: current page number, %2$d: total number of pages */
|
|
24
|
+
__('Page %1$d of %2$d', 'jetpack-components'), page + 1, numberOfPages), onClick: pageHandlers[page] }, page.toString()) }, `page-${page}`))) }));
|
|
25
|
+
};
|
|
26
|
+
const DotPager = ({ hasDynamicHeight = false, children, className = '', onPageSelected, isClickEnabled = false, rotateTime = 0, tracksPrefix = '', tracksFn = () => { }, ...props }) => {
|
|
27
|
+
const normalizedChildren = Children.toArray(children).filter(Boolean);
|
|
28
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
29
|
+
const [isPaused, setIsPaused] = useState(false);
|
|
30
|
+
const numPages = Children.count(normalizedChildren);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (rotateTime > 0 && numPages > 1 && !isPaused) {
|
|
33
|
+
const timerId = setTimeout(() => {
|
|
34
|
+
// Add 1 to numPages to account for the clones
|
|
35
|
+
setCurrentPage((currentPage + 1) % (numPages + 1));
|
|
36
|
+
}, rotateTime * 1000);
|
|
37
|
+
return () => clearTimeout(timerId);
|
|
38
|
+
}
|
|
39
|
+
}, [currentPage, numPages, rotateTime, isPaused]);
|
|
40
|
+
const handleSelectPage = useCallback((index) => {
|
|
41
|
+
setCurrentPage(index);
|
|
42
|
+
onPageSelected?.(index);
|
|
43
|
+
}, [onPageSelected]);
|
|
44
|
+
const handleMouseEnter = useCallback(() => {
|
|
45
|
+
setIsPaused(true);
|
|
46
|
+
}, []);
|
|
47
|
+
const handleMouseLeave = useCallback(() => {
|
|
48
|
+
setIsPaused(false);
|
|
49
|
+
}, []);
|
|
50
|
+
return (_jsxs("div", { className: clsx('dot-pager', className), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, ...props, children: [_jsx(Controls, { currentPage: currentPage, numberOfPages: numPages, setCurrentPage: handleSelectPage, tracksPrefix: tracksPrefix, tracksFn: tracksFn }), _jsx(Swipeable, { hasDynamicHeight: hasDynamicHeight, onPageSelect: handleSelectPage, currentPage: currentPage, pageClassName: "dot-pager__page", containerClassName: "dot-pager__pages", isClickEnabled: isClickEnabled, children: normalizedChildren })] }));
|
|
51
|
+
};
|
|
52
|
+
export default DotPager;
|
|
@@ -39,7 +39,7 @@ const IconTooltip = ({ className = '', iconClassName = '', placement = 'bottom-e
|
|
|
39
39
|
noArrow: false,
|
|
40
40
|
resize: false,
|
|
41
41
|
flip: false,
|
|
42
|
-
offset,
|
|
42
|
+
offset, // The distance (in px) between the anchor and the popover.
|
|
43
43
|
focusOnMount: 'container',
|
|
44
44
|
onClose: hideTooltip,
|
|
45
45
|
className: 'icon-tooltip-container',
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="react" resolution-mode="require"/>
|
|
2
1
|
export type Placement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end';
|
|
3
2
|
export type Position = 'top left' | 'top center' | 'top right' | 'bottom left' | 'bottom center' | 'bottom right';
|
|
4
3
|
export type IconTooltipProps = {
|
|
@@ -11,6 +11,6 @@ TextControl, } from '@wordpress/components';
|
|
|
11
11
|
*/
|
|
12
12
|
const NumberControl = ExperimentalNumberControl ||
|
|
13
13
|
function CustomNumberControl(props) {
|
|
14
|
-
return _jsx(TextControl, { type: "number", inputMode: "numeric", ...props });
|
|
14
|
+
return (_jsx(TextControl, { type: "number", inputMode: "numeric", ...props, __next40pxDefaultSize: true }));
|
|
15
15
|
};
|
|
16
16
|
export default NumberControl;
|
|
@@ -6,8 +6,8 @@ declare function Spinner({ color, className, size }: {
|
|
|
6
6
|
}): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
declare namespace Spinner {
|
|
8
8
|
namespace propTypes {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
let color: any;
|
|
10
|
+
let className: any;
|
|
11
|
+
let size: any;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -7,7 +7,7 @@ const Spinner = ({ color = '#FFFFFF', className = '', size = 20 }) => {
|
|
|
7
7
|
const styleOuter = {
|
|
8
8
|
width: size,
|
|
9
9
|
height: size,
|
|
10
|
-
fontSize: size,
|
|
10
|
+
fontSize: size, // allows border-width to be specified in em units
|
|
11
11
|
borderTopColor: color,
|
|
12
12
|
};
|
|
13
13
|
const styleInner = {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import './style.scss';
|
|
2
|
+
export declare const Swipeable: ({ hasDynamicHeight, children, currentPage, onPageSelect, pageClassName, containerClassName, isClickEnabled, ...otherProps }: {
|
|
3
|
+
[x: string]: any;
|
|
4
|
+
hasDynamicHeight?: boolean;
|
|
5
|
+
children: any;
|
|
6
|
+
currentPage?: number;
|
|
7
|
+
onPageSelect: any;
|
|
8
|
+
pageClassName: any;
|
|
9
|
+
containerClassName: any;
|
|
10
|
+
isClickEnabled: any;
|
|
11
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export default Swipeable;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { Children, useState, useLayoutEffect, useRef, useCallback, useEffect } from 'react';
|
|
4
|
+
import './style.scss';
|
|
5
|
+
const OFFSET_THRESHOLD_PERCENTAGE = 0.35; // Percentage of width to travel before we trigger the slider to move to the desired slide.
|
|
6
|
+
const VELOCITY_THRESHOLD = 0.2; // Speed of drag above, before we trigger the slider to move to the desired slide.
|
|
7
|
+
const VERTICAL_THRESHOLD_ANGLE = 55;
|
|
8
|
+
const TRANSITION_DURATION = '300ms';
|
|
9
|
+
/**
|
|
10
|
+
* Custom hook to observe and handle resize events on a DOM element.
|
|
11
|
+
*
|
|
12
|
+
* @return {[React.Dispatch<React.SetStateAction<HTMLElement | null>>, ResizeObserverEntry | null]} Tuple containing setter and entry
|
|
13
|
+
*/
|
|
14
|
+
function useResizeObserver() {
|
|
15
|
+
const [observerEntry, setObserverEntry] = useState(null);
|
|
16
|
+
const [node, setNode] = useState(null);
|
|
17
|
+
const observer = useRef(null);
|
|
18
|
+
const disconnect = useCallback(() => observer.current?.disconnect(), []);
|
|
19
|
+
const observe = useCallback(() => {
|
|
20
|
+
observer.current = new ResizeObserver(([entry]) => setObserverEntry(entry));
|
|
21
|
+
if (node) {
|
|
22
|
+
observer.current.observe(node);
|
|
23
|
+
}
|
|
24
|
+
}, [node]);
|
|
25
|
+
useLayoutEffect(() => {
|
|
26
|
+
observe();
|
|
27
|
+
return () => disconnect();
|
|
28
|
+
}, [disconnect, observe]);
|
|
29
|
+
return [setNode, observerEntry];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Gets the drag position and timestamp from a mouse, touch, or pointer event.
|
|
33
|
+
*
|
|
34
|
+
* @param {Event} event - The event object from the drag/touch/pointer interaction
|
|
35
|
+
* @return {object} Object containing x, y coordinates and timestamp of the event
|
|
36
|
+
*/
|
|
37
|
+
function getDragPositionAndTime(event) {
|
|
38
|
+
const { timeStamp } = event;
|
|
39
|
+
if (Object.prototype.hasOwnProperty.call(event, 'clientX')) {
|
|
40
|
+
return { x: event.clientX, y: event.clientY, timeStamp };
|
|
41
|
+
}
|
|
42
|
+
if (event.targetTouches[0]) {
|
|
43
|
+
return {
|
|
44
|
+
x: event.targetTouches[0].clientX,
|
|
45
|
+
y: event.targetTouches[0].clientY,
|
|
46
|
+
timeStamp,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const touch = event.changedTouches[0];
|
|
50
|
+
return { x: touch.clientX, y: touch.clientY, timeStamp };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Calculates the total width needed for all pages in the swipeable component.
|
|
54
|
+
*
|
|
55
|
+
* @param {number} pageWidth - The width of a single page
|
|
56
|
+
* @param {number} numPages - The total number of pages
|
|
57
|
+
* @return {number|null} The total width of all pages or null if pageWidth is not available
|
|
58
|
+
*/
|
|
59
|
+
function getPagesWidth(pageWidth, numPages) {
|
|
60
|
+
if (!pageWidth) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return pageWidth * numPages;
|
|
64
|
+
}
|
|
65
|
+
export const Swipeable = ({ hasDynamicHeight = false, children, currentPage = 0, onPageSelect, pageClassName, containerClassName, isClickEnabled, ...otherProps }) => {
|
|
66
|
+
const prevPageRef = useRef(currentPage);
|
|
67
|
+
const [swipeableArea, setSwipeableArea] = useState(null);
|
|
68
|
+
// TODO: Needs to be added RTL support
|
|
69
|
+
const isRtl = false;
|
|
70
|
+
const [resizeObserverRef, entry] = useResizeObserver();
|
|
71
|
+
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
72
|
+
const [pagesStyle, setPagesStyle] = useState({
|
|
73
|
+
transitionDuration: TRANSITION_DURATION,
|
|
74
|
+
});
|
|
75
|
+
const [dragData, setDragData] = useState(null);
|
|
76
|
+
const pagesRef = useRef(null);
|
|
77
|
+
const numPages = Children.count(children);
|
|
78
|
+
const containerWidth = entry?.contentRect?.width;
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
let timeoutId;
|
|
81
|
+
if ((currentPage === 0 && prevPageRef.current === numPages) ||
|
|
82
|
+
(currentPage === numPages - 1 && prevPageRef.current === -1)) {
|
|
83
|
+
// We are in a real slide after being transitioned from a clone
|
|
84
|
+
// we need to set again the transitionDuration to TRANSITION_DURATION
|
|
85
|
+
// But we need to wait a little bit to avoid enabling it before
|
|
86
|
+
// we moved to the real slide
|
|
87
|
+
timeoutId = setTimeout(() => {
|
|
88
|
+
setPagesStyle(prev => ({ ...prev, transitionDuration: TRANSITION_DURATION }));
|
|
89
|
+
}, 500);
|
|
90
|
+
}
|
|
91
|
+
else if (currentPage === numPages || currentPage < 0) {
|
|
92
|
+
// In a clone slide. Start the transition to the real slide
|
|
93
|
+
setIsTransitioning(true);
|
|
94
|
+
}
|
|
95
|
+
prevPageRef.current = currentPage;
|
|
96
|
+
return () => {
|
|
97
|
+
if (timeoutId) {
|
|
98
|
+
clearTimeout(timeoutId);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}, [currentPage, numPages]);
|
|
102
|
+
const getOffset = useCallback(index => {
|
|
103
|
+
// Adjust offset to account for the cloned element at the beginning
|
|
104
|
+
const adjustedIndex = index + 1;
|
|
105
|
+
const offset = containerWidth * adjustedIndex;
|
|
106
|
+
return isRtl ? offset : -offset;
|
|
107
|
+
}, [isRtl, containerWidth]);
|
|
108
|
+
const updateEnabled = hasDynamicHeight && numPages > 1;
|
|
109
|
+
// Generate a property that denotes the order of the cards, in order to recalculate height whenever the card order changes.
|
|
110
|
+
const childrenOrder = children.reduce((acc, child) => acc + child.key, '');
|
|
111
|
+
useLayoutEffect(() => {
|
|
112
|
+
if (!updateEnabled) {
|
|
113
|
+
// This is a fix for a bug when you have >1 pages and it update the component to just one but the height is still
|
|
114
|
+
// Related to https://github.com/Automattic/dotcom-forge/issues/2033
|
|
115
|
+
if (pagesStyle?.height) {
|
|
116
|
+
setPagesStyle({ ...pagesStyle, height: undefined });
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const targetHeight = pagesRef.current?.querySelector('.is-current')
|
|
121
|
+
?.offsetHeight;
|
|
122
|
+
if (targetHeight && pagesStyle?.height !== targetHeight) {
|
|
123
|
+
setPagesStyle({ ...pagesStyle, height: targetHeight });
|
|
124
|
+
}
|
|
125
|
+
}, [pagesRef, currentPage, pagesStyle, updateEnabled, containerWidth, childrenOrder]);
|
|
126
|
+
const resetDragData = useCallback(() => {
|
|
127
|
+
setPagesStyle(prev => ({
|
|
128
|
+
...prev,
|
|
129
|
+
transitionDuration: TRANSITION_DURATION,
|
|
130
|
+
}));
|
|
131
|
+
setDragData(null);
|
|
132
|
+
}, [setPagesStyle, setDragData]);
|
|
133
|
+
const handleDragStart = useCallback(event => {
|
|
134
|
+
const position = getDragPositionAndTime(event);
|
|
135
|
+
setSwipeableArea(pagesRef.current?.getBoundingClientRect());
|
|
136
|
+
setDragData({ start: position });
|
|
137
|
+
setPagesStyle(prev => ({ ...prev, transitionDuration: `0ms` })); // Set transition Duration to 0 for smooth dragging.
|
|
138
|
+
}, [setPagesStyle, setDragData]);
|
|
139
|
+
const hasSwipedToNextPage = useCallback(delta => (isRtl ? delta > 0 : delta < 0), [isRtl]);
|
|
140
|
+
const hasSwipedToPreviousPage = useCallback(delta => (isRtl ? delta < 0 : delta > 0), [isRtl]);
|
|
141
|
+
const handleTransitionEnd = useCallback(() => {
|
|
142
|
+
if (!isTransitioning) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
setIsTransitioning(false);
|
|
146
|
+
// If we're on the clone slides, jump to the corresponding real slide
|
|
147
|
+
// We set the transitionDuration to 0ms to make invisible the
|
|
148
|
+
// change from the clone to the real slide
|
|
149
|
+
if (currentPage >= numPages) {
|
|
150
|
+
setPagesStyle(prev => ({ ...prev, transitionDuration: '0ms' }));
|
|
151
|
+
onPageSelect(0);
|
|
152
|
+
}
|
|
153
|
+
else if (currentPage < 0) {
|
|
154
|
+
setPagesStyle(prev => ({ ...prev, transitionDuration: '0ms' }));
|
|
155
|
+
onPageSelect(numPages - 1);
|
|
156
|
+
}
|
|
157
|
+
}, [currentPage, numPages, onPageSelect, isTransitioning]);
|
|
158
|
+
const handleDragEnd = useCallback(event => {
|
|
159
|
+
if (!dragData) {
|
|
160
|
+
return; // End early if we are not dragging any more.
|
|
161
|
+
}
|
|
162
|
+
let dragPosition = getDragPositionAndTime(event);
|
|
163
|
+
if (dragPosition.x === 0) {
|
|
164
|
+
dragPosition = dragData.last;
|
|
165
|
+
}
|
|
166
|
+
const delta = dragPosition.x - dragData.start.x;
|
|
167
|
+
const absoluteDelta = Math.abs(delta);
|
|
168
|
+
const velocity = absoluteDelta / (dragPosition.timeStamp - dragData.start.timeStamp);
|
|
169
|
+
const verticalAbsoluteDelta = Math.abs(dragPosition.y - dragData.start.y);
|
|
170
|
+
const angle = (Math.atan2(verticalAbsoluteDelta, absoluteDelta) * 180) / Math.PI;
|
|
171
|
+
// Is click or tap?
|
|
172
|
+
if (velocity === 0 && isClickEnabled) {
|
|
173
|
+
onPageSelect((currentPage + 1) % numPages);
|
|
174
|
+
resetDragData();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Is vertical scroll detected?
|
|
178
|
+
if (angle > VERTICAL_THRESHOLD_ANGLE) {
|
|
179
|
+
resetDragData();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const hasMetThreshold = absoluteDelta > OFFSET_THRESHOLD_PERCENTAGE * containerWidth ||
|
|
183
|
+
velocity > VELOCITY_THRESHOLD;
|
|
184
|
+
let newIndex = currentPage;
|
|
185
|
+
if (hasMetThreshold) {
|
|
186
|
+
if (hasSwipedToNextPage(delta)) {
|
|
187
|
+
newIndex = currentPage + 1;
|
|
188
|
+
if (newIndex >= numPages) {
|
|
189
|
+
setIsTransitioning(true);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (hasSwipedToPreviousPage(delta)) {
|
|
193
|
+
newIndex = currentPage - 1;
|
|
194
|
+
if (newIndex < 0) {
|
|
195
|
+
setIsTransitioning(true);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
setPagesStyle(prev => ({
|
|
200
|
+
...prev,
|
|
201
|
+
transform: `translate3d(${getOffset(newIndex)}px, 0px, 0px)`,
|
|
202
|
+
transitionDuration: TRANSITION_DURATION,
|
|
203
|
+
}));
|
|
204
|
+
onPageSelect(newIndex);
|
|
205
|
+
setDragData(null);
|
|
206
|
+
}, [
|
|
207
|
+
currentPage,
|
|
208
|
+
dragData,
|
|
209
|
+
hasSwipedToNextPage,
|
|
210
|
+
hasSwipedToPreviousPage,
|
|
211
|
+
numPages,
|
|
212
|
+
onPageSelect,
|
|
213
|
+
setPagesStyle,
|
|
214
|
+
containerWidth,
|
|
215
|
+
isClickEnabled,
|
|
216
|
+
resetDragData,
|
|
217
|
+
getOffset,
|
|
218
|
+
]);
|
|
219
|
+
const handleDrag = useCallback(event => {
|
|
220
|
+
if (!dragData) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const dragPosition = getDragPositionAndTime(event);
|
|
224
|
+
const delta = dragPosition.x - dragData.start.x;
|
|
225
|
+
const absoluteDelta = Math.abs(delta);
|
|
226
|
+
const offset = getOffset(currentPage) + delta;
|
|
227
|
+
setDragData({ ...dragData, last: dragPosition });
|
|
228
|
+
// The user needs to swipe horizontally more then 2 px in order for the canvase to be dragging.
|
|
229
|
+
// We do this so that the user can scroll vertically smother.
|
|
230
|
+
if (absoluteDelta < 3) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
setPagesStyle(prev => ({
|
|
234
|
+
...prev,
|
|
235
|
+
transform: `translate3d(${offset}px, 0px, 0px)`,
|
|
236
|
+
transitionDuration: '0ms',
|
|
237
|
+
}));
|
|
238
|
+
if (!swipeableArea) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// Did the user swipe out of the swipeable area?
|
|
242
|
+
if (dragPosition.x < swipeableArea.left ||
|
|
243
|
+
dragPosition.x > swipeableArea.right ||
|
|
244
|
+
dragPosition.y > swipeableArea.bottom ||
|
|
245
|
+
dragPosition.y < swipeableArea.top) {
|
|
246
|
+
handleDragEnd(event);
|
|
247
|
+
}
|
|
248
|
+
}, [dragData, getOffset, currentPage, swipeableArea, handleDragEnd]);
|
|
249
|
+
const getTouchEvents = useCallback(() => {
|
|
250
|
+
if ('onpointerup' in document) {
|
|
251
|
+
return {
|
|
252
|
+
onPointerDown: handleDragStart,
|
|
253
|
+
onPointerMove: handleDrag,
|
|
254
|
+
onPointerUp: handleDragEnd,
|
|
255
|
+
onPointerLeave: handleDragEnd,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if ('ondragend' in document) {
|
|
259
|
+
return {
|
|
260
|
+
onDragStart: handleDragStart,
|
|
261
|
+
onDrag: handleDrag,
|
|
262
|
+
onDragEnd: handleDragEnd,
|
|
263
|
+
onDragExit: handleDragEnd,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if ('ontouchend' in document) {
|
|
267
|
+
return {
|
|
268
|
+
onTouchStart: handleDragStart,
|
|
269
|
+
onTouchMove: handleDrag,
|
|
270
|
+
onTouchEnd: handleDragEnd,
|
|
271
|
+
onTouchCancel: handleDragEnd,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}, [handleDragStart, handleDrag, handleDragEnd]);
|
|
276
|
+
const offset = getOffset(currentPage);
|
|
277
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { ...getTouchEvents(), className: "swipeable__container", ref: pagesRef, ...otherProps, children: _jsxs("div", { className: clsx('swipeable__pages', containerClassName), style: {
|
|
278
|
+
...pagesStyle,
|
|
279
|
+
width: getPagesWidth(containerWidth, numPages + 2),
|
|
280
|
+
transform: `translate3d(${offset}px, 0px, 0px)`,
|
|
281
|
+
}, onTransitionEnd: handleTransitionEnd, children: [_jsx("div", { style: { width: `${containerWidth}px` }, className: clsx('swipeable__page', pageClassName, {
|
|
282
|
+
'is-clone': true,
|
|
283
|
+
'is-prev': currentPage === 0,
|
|
284
|
+
}), children: Children.toArray(children)[numPages - 1] }, `clone-prev-${numPages - 1}`), Children.map(children, (child, index) => (_jsx("div", { style: { width: `${containerWidth}px` }, className: clsx('swipeable__page', pageClassName, {
|
|
285
|
+
'is-current': index === currentPage,
|
|
286
|
+
'is-prev': index < currentPage,
|
|
287
|
+
'is-next': index > currentPage,
|
|
288
|
+
}), "data-testid": `swipeable-page-${index + 1}`, children: child }, `page-${index}`))), _jsx("div", { style: { width: `${containerWidth}px` }, className: clsx('swipeable__page', pageClassName, {
|
|
289
|
+
'is-clone': true,
|
|
290
|
+
'is-next': currentPage === numPages - 1,
|
|
291
|
+
}), children: Children.toArray(children)[0] }, `clone-next-0`)] }) }), _jsx("div", { ref: resizeObserverRef, className: "swipeable__resize-observer" })] }));
|
|
292
|
+
};
|
|
293
|
+
export default Swipeable;
|
package/build/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export { default as BoostScoreGraph } from './components/boost-score-graph/index
|
|
|
45
45
|
export { default as ProductPrice } from './components/product-price/index.js';
|
|
46
46
|
export { default as ProductOffer, IconsCard } from './components/product-offer/index.js';
|
|
47
47
|
export { default as Dialog } from './components/dialog/index.js';
|
|
48
|
+
export { default as DotPager } from './components/dot-pager/index.js';
|
|
48
49
|
export { default as DonutMeter } from './components/donut-meter/index.js';
|
|
49
50
|
export { default as RecordMeterBar } from './components/record-meter-bar/index.js';
|
|
50
51
|
export { default as ContextualUpgradeTrigger } from './components/contextual-upgrade-trigger/index.js';
|
package/build/index.js
CHANGED
|
@@ -59,6 +59,7 @@ export { default as BoostScoreGraph } from './components/boost-score-graph/index
|
|
|
59
59
|
export { default as ProductPrice } from './components/product-price/index.js';
|
|
60
60
|
export { default as ProductOffer, IconsCard } from './components/product-offer/index.js';
|
|
61
61
|
export { default as Dialog } from './components/dialog/index.js';
|
|
62
|
+
export { default as DotPager } from './components/dot-pager/index.js';
|
|
62
63
|
export { default as DonutMeter } from './components/donut-meter/index.js';
|
|
63
64
|
export { default as RecordMeterBar } from './components/record-meter-bar/index.js';
|
|
64
65
|
export { default as ContextualUpgradeTrigger } from './components/contextual-upgrade-trigger/index.js';
|
|
@@ -20,7 +20,7 @@ import styles from './style.module.scss';
|
|
|
20
20
|
* It is useful to async actions when the user has to wait the result of a request or process.
|
|
21
21
|
*
|
|
22
22
|
* @param {object} props - The properties.
|
|
23
|
-
* @return {React.
|
|
23
|
+
* @return {React.ReactNode} The `ActionButton` component.
|
|
24
24
|
*/
|
|
25
25
|
const ActionButton = props => {
|
|
26
26
|
const {
|