@via-profit/ui-kit 0.6.0 → 0.8.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.
- package/CHANGELOG.md +25 -0
- package/Modal/Drawer/DrawerInner.d.ts +6 -0
- package/Modal/Drawer/DrawerInner.js +91 -2
- package/Modal/Drawer/index.d.ts +8 -0
- package/Modal/Drawer/index.js +3 -3
- package/Modal/Drawer/useSwipeToClose.d.ts +30 -0
- package/Modal/Drawer/useSwipeToClose.js +238 -0
- package/Swiper/Swiper.d.ts +68 -0
- package/Swiper/Swiper.js +268 -135
- package/Swiper/SwiperSlide.js +3 -0
- package/Swiper/SwiperSlidesRenderer.d.ts +4 -0
- package/Swiper/SwiperSlidesRenderer.js +2 -1
- package/Swiper/SwiperTrack.d.ts +20 -0
- package/Swiper/SwiperTrack.js +28 -5
- package/Swiper/SwiperWrapper.d.ts +3 -0
- package/Swiper/SwiperWrapper.js +5 -4
- package/esm/Modal/Drawer/DrawerInner.js +91 -2
- package/esm/Modal/Drawer/index.js +3 -3
- package/esm/Modal/Drawer/useSwipeToClose.js +233 -0
- package/esm/Swiper/Swiper.js +268 -135
- package/esm/Swiper/SwiperSlide.js +3 -0
- package/esm/Swiper/SwiperSlidesRenderer.js +2 -1
- package/esm/Swiper/SwiperTrack.js +28 -5
- package/esm/Swiper/SwiperWrapper.js +5 -4
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
Все заметные изменения пакета `@via-profit/ui-kit`. Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/), версии следуют [семантическому версионированию](https://semver.org/lang/ru/).
|
|
4
4
|
|
|
5
|
+
## 0.8.0 — 2026-10-13
|
|
6
|
+
|
|
7
|
+
Drawer: закрытие свайпом.
|
|
8
|
+
|
|
9
|
+
### Добавлено
|
|
10
|
+
|
|
11
|
+
- **Modal**: свойство `swipeToClose` у `drawer` — панель закрывается свайпом к своему краю. Она следует за пальцем, короткий свайп возвращает её на место, прокручиваемое содержимое сначала прокручивается. На стороне, обращённой к странице, появляется полоска-ручка, за которую панель перетаскивается мышью.
|
|
12
|
+
|
|
13
|
+
## 0.7.0 — 2026-10-09
|
|
14
|
+
|
|
15
|
+
Swiper: дробный `slidesPerView` и `centered`, вертикальная прокрутка, затухание и бегущая лента.
|
|
16
|
+
|
|
17
|
+
### Добавлено
|
|
18
|
+
|
|
19
|
+
- **Swiper**: дробный `slidesPerView` (например, `1.2` — слайд и часть соседнего) и `centered` — текущий слайд посередине, соседи видны по краям;
|
|
20
|
+
- **Swiper**: `direction="vertical"` — вертикальная прокрутка, стрелки ↑/↓;
|
|
21
|
+
- **Swiper**: `effect="fade"` — смена слайдов затуханием;
|
|
22
|
+
- **Swiper**: `speed` — длительность смены слайда;
|
|
23
|
+
- **Swiper**: `autoScroll` — бегущая лента с постоянной скоростью в пикселях в секунду. Останавливается при наведении и при включённом в системе уменьшении движения.
|
|
24
|
+
|
|
25
|
+
### Исправлено
|
|
26
|
+
|
|
27
|
+
- **Swiper**: ширину слайда задаёт только `slidesPerView`. Раньше слайд с широким содержимым растягивался, и лента сдвигалась;
|
|
28
|
+
- **Swiper**: если слайдов меньше, чем нужно для бесконечной прокрутки, она выключается целиком. Раньше в консоль выводилась ошибка, но индексы считались с копиями слайдов, которых не было.
|
|
29
|
+
|
|
5
30
|
## 0.6.0 — 2026-10-07
|
|
6
31
|
|
|
7
32
|
Поддержка React 19. Тема снова задаёт только токены. Оформление компонентов в теме убрано: оно делало внутренние части компонентов публичным контрактом и добавляло работы при каждой отрисовке.
|
|
@@ -13,6 +13,12 @@ export type DrawerInnerProps = ModalInnerProps & React.RefAttributes<HTMLDivElem
|
|
|
13
13
|
* **Default:** `bottom`
|
|
14
14
|
*/
|
|
15
15
|
readonly anchor: AnchorVariant;
|
|
16
|
+
/**
|
|
17
|
+
* Close the drawer by a swipe towards its edge\
|
|
18
|
+
* \
|
|
19
|
+
* **Default:** `false`
|
|
20
|
+
*/
|
|
21
|
+
readonly swipeToClose?: boolean;
|
|
16
22
|
};
|
|
17
23
|
declare const _default: React.ForwardRefExoticComponent<Omit<DrawerInnerProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
18
24
|
export default _default;
|
|
@@ -18,6 +18,84 @@ const react_1 = __importDefault(require("react"));
|
|
|
18
18
|
const styled_1 = __importDefault(require("@emotion/styled"));
|
|
19
19
|
const react_2 = require("@emotion/react");
|
|
20
20
|
const ModalInner_1 = __importDefault(require("../BaseModal/ModalInner"));
|
|
21
|
+
const useSwipeToClose_1 = __importDefault(require("./useSwipeToClose"));
|
|
22
|
+
/**
|
|
23
|
+
* The grip on the side facing the page: shows that the drawer can be swiped
|
|
24
|
+
* and lets a mouse drag it (elsewhere the mouse selects the text)
|
|
25
|
+
*/
|
|
26
|
+
const DrawerHandle = styled_1.default.div `
|
|
27
|
+
position: absolute;
|
|
28
|
+
z-index: 1;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
cursor: grab;
|
|
33
|
+
touch-action: none;
|
|
34
|
+
|
|
35
|
+
&:active {
|
|
36
|
+
cursor: grabbing;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&::before {
|
|
40
|
+
content: '';
|
|
41
|
+
border-radius: 1em;
|
|
42
|
+
background-color: ${({ theme }) => theme.color.textPrimary.alpha(0.24).toString()};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
${({ $anchor }) => {
|
|
46
|
+
switch ($anchor) {
|
|
47
|
+
case 'top':
|
|
48
|
+
return (0, react_2.css) `
|
|
49
|
+
bottom: 0;
|
|
50
|
+
left: 50%;
|
|
51
|
+
transform: translateX(-50%);
|
|
52
|
+
width: 4em;
|
|
53
|
+
height: 1.25em;
|
|
54
|
+
&::before {
|
|
55
|
+
width: 2.5em;
|
|
56
|
+
height: 0.25em;
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
case 'left':
|
|
60
|
+
return (0, react_2.css) `
|
|
61
|
+
right: 0;
|
|
62
|
+
top: 50%;
|
|
63
|
+
transform: translateY(-50%);
|
|
64
|
+
width: 1.25em;
|
|
65
|
+
height: 4em;
|
|
66
|
+
&::before {
|
|
67
|
+
width: 0.25em;
|
|
68
|
+
height: 2.5em;
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
case 'right':
|
|
72
|
+
return (0, react_2.css) `
|
|
73
|
+
left: 0;
|
|
74
|
+
top: 50%;
|
|
75
|
+
transform: translateY(-50%);
|
|
76
|
+
width: 1.25em;
|
|
77
|
+
height: 4em;
|
|
78
|
+
&::before {
|
|
79
|
+
width: 0.25em;
|
|
80
|
+
height: 2.5em;
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
case 'bottom':
|
|
84
|
+
default:
|
|
85
|
+
return (0, react_2.css) `
|
|
86
|
+
top: 0;
|
|
87
|
+
left: 50%;
|
|
88
|
+
transform: translateX(-50%);
|
|
89
|
+
width: 4em;
|
|
90
|
+
height: 1.25em;
|
|
91
|
+
&::before {
|
|
92
|
+
width: 2.5em;
|
|
93
|
+
height: 0.25em;
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
`;
|
|
21
99
|
const StyledDrawerBottomInner = (0, styled_1.default)(ModalInner_1.default) `
|
|
22
100
|
position: fixed;
|
|
23
101
|
margin: 0;
|
|
@@ -131,7 +209,16 @@ const StyledDrawerLeftInner = (0, styled_1.default)(ModalInner_1.default) `
|
|
|
131
209
|
`}
|
|
132
210
|
`;
|
|
133
211
|
const DrawerInner = (props, ref) => {
|
|
134
|
-
const { children, isOpen, anchor, onRequestClose, dialogID } = props, nativeProps = __rest(props, ["children", "isOpen", "anchor", "onRequestClose", "dialogID"]);
|
|
212
|
+
const { children, isOpen, anchor, onRequestClose, dialogID, swipeToClose } = props, nativeProps = __rest(props, ["children", "isOpen", "anchor", "onRequestClose", "dialogID", "swipeToClose"]);
|
|
213
|
+
const elementRef = react_1.default.useRef(null);
|
|
214
|
+
react_1.default.useImperativeHandle(ref, () => elementRef.current);
|
|
215
|
+
const swipeHandlers = (0, useSwipeToClose_1.default)({
|
|
216
|
+
elementRef,
|
|
217
|
+
enabled: Boolean(swipeToClose),
|
|
218
|
+
isOpen,
|
|
219
|
+
anchor,
|
|
220
|
+
onClose: onRequestClose,
|
|
221
|
+
});
|
|
135
222
|
const Element = react_1.default.useMemo(() => {
|
|
136
223
|
switch (anchor) {
|
|
137
224
|
case 'top':
|
|
@@ -145,6 +232,8 @@ const DrawerInner = (props, ref) => {
|
|
|
145
232
|
return StyledDrawerBottomInner;
|
|
146
233
|
}
|
|
147
234
|
}, [anchor]);
|
|
148
|
-
return (react_1.default.createElement(Element, Object.assign({ role: "dialog", "aria-modal": "true", isOpen: isOpen, id: `${dialogID}-description`, onRequestClose: onRequestClose }, nativeProps, { ref:
|
|
235
|
+
return (react_1.default.createElement(Element, Object.assign({ role: "dialog", "aria-modal": "true", isOpen: isOpen, id: `${dialogID}-description`, onRequestClose: onRequestClose }, nativeProps, swipeHandlers, { ref: elementRef }),
|
|
236
|
+
swipeToClose && react_1.default.createElement(DrawerHandle, { "$anchor": anchor, "data-drawer-handle": true, "aria-hidden": "true" }),
|
|
237
|
+
children));
|
|
149
238
|
};
|
|
150
239
|
exports.default = react_1.default.forwardRef(DrawerInner);
|
package/Modal/Drawer/index.d.ts
CHANGED
|
@@ -41,6 +41,14 @@ export interface DrawerProps extends Omit<BaseModalProps, 'overrides'> {
|
|
|
41
41
|
* **Default**: `'Close'`
|
|
42
42
|
*/
|
|
43
43
|
readonly closeButtonLabel?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Close the drawer by a swipe towards its edge: down for `bottom`, left for `left` and so on.
|
|
46
|
+
* A short swipe returns the drawer back. The content scrolls first, the drawer follows
|
|
47
|
+
* the finger when the content is at its edge. A mouse drags the drawer by the handle\
|
|
48
|
+
* \
|
|
49
|
+
* **Default:** `false`
|
|
50
|
+
*/
|
|
51
|
+
readonly swipeToClose?: boolean;
|
|
44
52
|
/**
|
|
45
53
|
* Overridable components map
|
|
46
54
|
*/
|
package/Modal/Drawer/index.js
CHANGED
|
@@ -57,7 +57,7 @@ const DrawerFooter_1 = __importDefault(require("./DrawerFooter"));
|
|
|
57
57
|
const ModalOverlay_1 = __importDefault(require("../BaseModal/ModalOverlay"));
|
|
58
58
|
const useDialogID_1 = __importDefault(require("../BaseModal/useDialogID"));
|
|
59
59
|
const Drawer = props => {
|
|
60
|
-
const { children, onRequestClose, showCloseButton, header, closeButtonLabel, anchor, toolbar, footer, overrides } = props, otherProps = __rest(props, ["children", "onRequestClose", "showCloseButton", "header", "closeButtonLabel", "anchor", "toolbar", "footer", "overrides"]);
|
|
60
|
+
const { children, onRequestClose, showCloseButton, header, closeButtonLabel, anchor, toolbar, footer, overrides, swipeToClose } = props, otherProps = __rest(props, ["children", "onRequestClose", "showCloseButton", "header", "closeButtonLabel", "anchor", "toolbar", "footer", "overrides", "swipeToClose"]);
|
|
61
61
|
const hasFooter = React.useMemo(() => typeof footer !== 'undefined' && footer !== null, [footer]);
|
|
62
62
|
const hasHeader = React.useMemo(() => typeof header !== 'undefined' ||
|
|
63
63
|
typeof toolbar !== 'undefined' ||
|
|
@@ -67,8 +67,8 @@ const Drawer = props => {
|
|
|
67
67
|
// Created once: a component created during the render is a new type on every render,
|
|
68
68
|
// so React would remount the whole drawer content (inputs lose the focus and the state)
|
|
69
69
|
const Inner = React.useMemo(() => React.forwardRef(function Inner(innerProps, ref) {
|
|
70
|
-
return (React.createElement(DrawerInner_1.default, Object.assign({ anchor: anchor, dialogID: dialogID, "aria-labelledby": hasTitle ? `${dialogID}-title` : undefined, ref: ref }, innerProps)));
|
|
71
|
-
}), [anchor, dialogID, hasTitle]);
|
|
70
|
+
return (React.createElement(DrawerInner_1.default, Object.assign({ anchor: anchor, dialogID: dialogID, swipeToClose: swipeToClose, "aria-labelledby": hasTitle ? `${dialogID}-title` : undefined, ref: ref }, innerProps)));
|
|
71
|
+
}), [anchor, dialogID, hasTitle, swipeToClose]);
|
|
72
72
|
const overridesMap = React.useMemo(() => ({
|
|
73
73
|
Container: (overrides === null || overrides === void 0 ? void 0 : overrides.Container) || DrawerContainer_1.default,
|
|
74
74
|
Header: (overrides === null || overrides === void 0 ? void 0 : overrides.Header) || DrawerHeader_1.default,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AnchorVariant } from './DrawerInner';
|
|
3
|
+
export type UseSwipeToCloseOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* The drawer element: it follows the finger
|
|
6
|
+
*/
|
|
7
|
+
readonly elementRef: React.RefObject<HTMLDivElement | null>;
|
|
8
|
+
readonly enabled: boolean;
|
|
9
|
+
readonly isOpen: boolean;
|
|
10
|
+
readonly anchor: AnchorVariant;
|
|
11
|
+
readonly onClose: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Closes the drawer by a swipe towards its edge.
|
|
15
|
+
*
|
|
16
|
+
* The pointer events move the drawer. A touch would be taken by the browser for the scroll
|
|
17
|
+
* (and the pointer cancelled), so the native `touchmove` is prevented while the drawer is dragged
|
|
18
|
+
*/
|
|
19
|
+
declare const useSwipeToClose: (options: UseSwipeToCloseOptions) => {
|
|
20
|
+
onPointerDown?: undefined;
|
|
21
|
+
onPointerMove?: undefined;
|
|
22
|
+
onPointerUp?: undefined;
|
|
23
|
+
onPointerCancel?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
onPointerDown: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
26
|
+
onPointerMove: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
27
|
+
onPointerUp: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
28
|
+
onPointerCancel: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
29
|
+
};
|
|
30
|
+
export default useSwipeToClose;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const react_1 = __importDefault(require("react"));
|
|
7
|
+
// The finger moves this far before the gesture is recognized as a drag or a scroll
|
|
8
|
+
const SLOP = 6;
|
|
9
|
+
// The drawer is closed when dragged by this part of its size or thrown faster than this
|
|
10
|
+
const CLOSE_RATIO = 0.25;
|
|
11
|
+
const CLOSE_VELOCITY = 0.5; // px per ms
|
|
12
|
+
const RETURN_TRANSITION = 'transform 240ms ease-out';
|
|
13
|
+
const initialState = {
|
|
14
|
+
mode: 'idle',
|
|
15
|
+
pointerId: -1,
|
|
16
|
+
startX: 0,
|
|
17
|
+
startY: 0,
|
|
18
|
+
offset: 0,
|
|
19
|
+
lastOffset: 0,
|
|
20
|
+
lastTime: 0,
|
|
21
|
+
velocity: 0,
|
|
22
|
+
};
|
|
23
|
+
const isVertical = (anchor) => anchor === 'bottom' || anchor === 'top';
|
|
24
|
+
/**
|
|
25
|
+
* The movement of the finger towards the edge of the drawer: positive closes it
|
|
26
|
+
*/
|
|
27
|
+
const getClosingDelta = (anchor, dx, dy) => {
|
|
28
|
+
switch (anchor) {
|
|
29
|
+
case 'top':
|
|
30
|
+
return -dy;
|
|
31
|
+
case 'left':
|
|
32
|
+
return -dx;
|
|
33
|
+
case 'right':
|
|
34
|
+
return dx;
|
|
35
|
+
case 'bottom':
|
|
36
|
+
default:
|
|
37
|
+
return dy;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const getTransform = (anchor, offset) => {
|
|
41
|
+
switch (anchor) {
|
|
42
|
+
case 'top':
|
|
43
|
+
return `translateY(${-offset}px)`;
|
|
44
|
+
case 'left':
|
|
45
|
+
return `translateX(${-offset}px)`;
|
|
46
|
+
case 'right':
|
|
47
|
+
return `translateX(${offset}px)`;
|
|
48
|
+
case 'bottom':
|
|
49
|
+
default:
|
|
50
|
+
return `translateY(${offset}px)`;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Whether an element between the target and the drawer can scroll the way the finger moves.
|
|
55
|
+
* Then the finger scrolls the content: the drawer is dragged only when the content is at its edge
|
|
56
|
+
*/
|
|
57
|
+
const canScrollWithGesture = (target, root, anchor) => {
|
|
58
|
+
let element = target instanceof Element ? target : null;
|
|
59
|
+
while (element && element !== root) {
|
|
60
|
+
if (element instanceof HTMLElement) {
|
|
61
|
+
const style = window.getComputedStyle(element);
|
|
62
|
+
const overflow = isVertical(anchor) ? style.overflowY : style.overflowX;
|
|
63
|
+
const scrollable = overflow === 'auto' || overflow === 'scroll';
|
|
64
|
+
if (scrollable) {
|
|
65
|
+
const { scrollTop, scrollLeft, scrollHeight, scrollWidth, clientHeight, clientWidth } = element;
|
|
66
|
+
switch (anchor) {
|
|
67
|
+
case 'bottom':
|
|
68
|
+
if (scrollTop > 0) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case 'top':
|
|
73
|
+
if (scrollTop + clientHeight < scrollHeight - 1) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
case 'left':
|
|
78
|
+
if (scrollLeft + clientWidth < scrollWidth - 1) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case 'right':
|
|
83
|
+
if (scrollLeft > 0) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
element = element.parentElement;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Closes the drawer by a swipe towards its edge.
|
|
98
|
+
*
|
|
99
|
+
* The pointer events move the drawer. A touch would be taken by the browser for the scroll
|
|
100
|
+
* (and the pointer cancelled), so the native `touchmove` is prevented while the drawer is dragged
|
|
101
|
+
*/
|
|
102
|
+
const useSwipeToClose = (options) => {
|
|
103
|
+
const { elementRef, enabled, isOpen, anchor, onClose } = options;
|
|
104
|
+
const stateRef = react_1.default.useRef(Object.assign({}, initialState));
|
|
105
|
+
const setOffset = react_1.default.useCallback((offset, transition) => {
|
|
106
|
+
const element = elementRef.current;
|
|
107
|
+
if (element) {
|
|
108
|
+
element.style.transition = transition;
|
|
109
|
+
element.style.transform = offset > 0 ? getTransform(anchor, offset) : '';
|
|
110
|
+
}
|
|
111
|
+
}, [anchor, elementRef]);
|
|
112
|
+
const restore = react_1.default.useCallback(() => {
|
|
113
|
+
const element = elementRef.current;
|
|
114
|
+
if (element) {
|
|
115
|
+
element.style.transition = '';
|
|
116
|
+
element.style.transform = '';
|
|
117
|
+
}
|
|
118
|
+
}, [elementRef]);
|
|
119
|
+
// A drawer closed by a swipe keeps its offset while it slides out: reset it for the next opening
|
|
120
|
+
react_1.default.useEffect(() => {
|
|
121
|
+
if (isOpen) {
|
|
122
|
+
stateRef.current = Object.assign({}, initialState);
|
|
123
|
+
restore();
|
|
124
|
+
}
|
|
125
|
+
}, [isOpen, restore]);
|
|
126
|
+
/**
|
|
127
|
+
* Decides on the first noticeable movement whether it drags the drawer or scrolls the content
|
|
128
|
+
*/
|
|
129
|
+
const update = react_1.default.useCallback((clientX, clientY, target) => {
|
|
130
|
+
const state = stateRef.current;
|
|
131
|
+
const element = elementRef.current;
|
|
132
|
+
if (!element || (state.mode !== 'pending' && state.mode !== 'drag')) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const dx = clientX - state.startX;
|
|
136
|
+
const dy = clientY - state.startY;
|
|
137
|
+
const closing = getClosingDelta(anchor, dx, dy);
|
|
138
|
+
if (state.mode === 'pending') {
|
|
139
|
+
const main = isVertical(anchor) ? Math.abs(dy) : Math.abs(dx);
|
|
140
|
+
const cross = isVertical(anchor) ? Math.abs(dx) : Math.abs(dy);
|
|
141
|
+
if (main < SLOP && cross < SLOP) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const isDrag = closing > 0 && main >= cross && !canScrollWithGesture(target, element, anchor);
|
|
145
|
+
state.mode = isDrag ? 'drag' : 'scroll';
|
|
146
|
+
if (!isDrag) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// The drag starts from here, without a jump by the slop
|
|
150
|
+
state.startX = clientX;
|
|
151
|
+
state.startY = clientY;
|
|
152
|
+
state.lastTime = performance.now();
|
|
153
|
+
}
|
|
154
|
+
const offset = Math.max(0, getClosingDelta(anchor, clientX - state.startX, clientY - state.startY));
|
|
155
|
+
const now = performance.now();
|
|
156
|
+
const elapsed = now - state.lastTime;
|
|
157
|
+
if (elapsed > 0) {
|
|
158
|
+
state.velocity = (offset - state.lastOffset) / elapsed;
|
|
159
|
+
}
|
|
160
|
+
state.lastOffset = offset;
|
|
161
|
+
state.lastTime = now;
|
|
162
|
+
state.offset = offset;
|
|
163
|
+
setOffset(offset, 'none');
|
|
164
|
+
}, [anchor, elementRef, setOffset]);
|
|
165
|
+
// A non-passive listener: only it may stop the browser from scrolling under the drag
|
|
166
|
+
react_1.default.useEffect(() => {
|
|
167
|
+
const element = elementRef.current;
|
|
168
|
+
if (!enabled || !element) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const handleTouchMove = (event) => {
|
|
172
|
+
const touch = event.touches[0];
|
|
173
|
+
if (touch) {
|
|
174
|
+
update(touch.clientX, touch.clientY, event.target);
|
|
175
|
+
}
|
|
176
|
+
if (stateRef.current.mode === 'drag' && event.cancelable) {
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
element.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
181
|
+
return () => {
|
|
182
|
+
element.removeEventListener('touchmove', handleTouchMove);
|
|
183
|
+
};
|
|
184
|
+
}, [enabled, elementRef, update]);
|
|
185
|
+
const onPointerDown = react_1.default.useCallback((event) => {
|
|
186
|
+
if (!event.isPrimary || event.button !== 0) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
// A mouse drags only by the handle: everywhere else it selects the text
|
|
190
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
191
|
+
if (event.pointerType === 'mouse') {
|
|
192
|
+
if (!(target === null || target === void 0 ? void 0 : target.closest('[data-drawer-handle]'))) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
event.preventDefault();
|
|
196
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
197
|
+
}
|
|
198
|
+
stateRef.current = Object.assign(Object.assign({}, initialState), { mode: 'pending', pointerId: event.pointerId, startX: event.clientX, startY: event.clientY, lastTime: performance.now() });
|
|
199
|
+
}, []);
|
|
200
|
+
const onPointerMove = react_1.default.useCallback((event) => {
|
|
201
|
+
if (event.pointerId === stateRef.current.pointerId) {
|
|
202
|
+
update(event.clientX, event.clientY, event.target);
|
|
203
|
+
}
|
|
204
|
+
}, [update]);
|
|
205
|
+
const finish = react_1.default.useCallback((event, cancelled) => {
|
|
206
|
+
const state = stateRef.current;
|
|
207
|
+
if (event.pointerId !== state.pointerId) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const wasDragged = state.mode === 'drag';
|
|
211
|
+
const element = elementRef.current;
|
|
212
|
+
const size = element ? (isVertical(anchor) ? element.offsetHeight : element.offsetWidth) : 0;
|
|
213
|
+
const shouldClose = wasDragged &&
|
|
214
|
+
!cancelled &&
|
|
215
|
+
(state.offset > size * CLOSE_RATIO || state.velocity > CLOSE_VELOCITY);
|
|
216
|
+
stateRef.current = Object.assign({}, initialState);
|
|
217
|
+
if (!wasDragged) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (shouldClose) {
|
|
221
|
+
// The offset is kept: the drawer slides out from where the finger left it.
|
|
222
|
+
// The own transitions of the drawer are back, the drag turned them off
|
|
223
|
+
if (element) {
|
|
224
|
+
element.style.transition = '';
|
|
225
|
+
}
|
|
226
|
+
onClose(event);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
setOffset(0, RETURN_TRANSITION);
|
|
230
|
+
}, [anchor, elementRef, onClose, setOffset]);
|
|
231
|
+
const onPointerUp = react_1.default.useCallback((event) => finish(event, false), [finish]);
|
|
232
|
+
const onPointerCancel = react_1.default.useCallback((event) => finish(event, true), [finish]);
|
|
233
|
+
if (!enabled) {
|
|
234
|
+
return {};
|
|
235
|
+
}
|
|
236
|
+
return { onPointerDown, onPointerMove, onPointerUp, onPointerCancel };
|
|
237
|
+
};
|
|
238
|
+
exports.default = useSwipeToClose;
|
package/Swiper/Swiper.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface SwiperOverrides {
|
|
|
20
20
|
readonly Wrapper?: React.ComponentType<SwiperWrapperProps & React.RefAttributes<HTMLDivElement>>;
|
|
21
21
|
readonly Track?: React.ComponentType<SwiperTrackProps & React.RefAttributes<HTMLDivElement>>;
|
|
22
22
|
}
|
|
23
|
+
export type SwiperDirection = 'horizontal' | 'vertical';
|
|
24
|
+
export type SwiperEffect = 'slide' | 'fade';
|
|
23
25
|
export type SwiperProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
24
26
|
readonly dragThreshold?: number;
|
|
25
27
|
readonly snap?: boolean;
|
|
@@ -32,7 +34,40 @@ export type SwiperProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>
|
|
|
32
34
|
readonly pauseOnHover?: boolean;
|
|
33
35
|
readonly threshold?: number;
|
|
34
36
|
readonly resistance?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* How many slides are visible at once. A fractional value shows a part of the next slide,
|
|
39
|
+
* for example `1.2`\
|
|
40
|
+
* **Default:** `1`
|
|
41
|
+
*/
|
|
35
42
|
readonly slidesPerView?: number;
|
|
43
|
+
/**
|
|
44
|
+
* The current slide is in the middle of the view, the neighbours are partially visible on both sides.
|
|
45
|
+
* Makes sense with a fractional `slidesPerView`\
|
|
46
|
+
* **Default:** `false`
|
|
47
|
+
*/
|
|
48
|
+
readonly centered?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* The direction of the scroll. The vertical swiper needs a height: set it on the swiper\
|
|
51
|
+
* **Default:** `'horizontal'`
|
|
52
|
+
*/
|
|
53
|
+
readonly direction?: SwiperDirection;
|
|
54
|
+
/**
|
|
55
|
+
* `slide` moves the track, `fade` cross-fades the slides lying on top of each other.
|
|
56
|
+
* With `fade` the slides are always one per view\
|
|
57
|
+
* **Default:** `'slide'`
|
|
58
|
+
*/
|
|
59
|
+
readonly effect?: SwiperEffect;
|
|
60
|
+
/**
|
|
61
|
+
* The duration of the slide change in milliseconds\
|
|
62
|
+
* **Default:** `300`
|
|
63
|
+
*/
|
|
64
|
+
readonly speed?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Continuous scroll (ticker) in pixels per second, for example the logos of the clients.
|
|
67
|
+
* The track moves without stops in the infinite loop, the drag and the keyboard are off.
|
|
68
|
+
* The scroll pauses with `pauseOnHover`, by the API and when the user asked the system to reduce motion
|
|
69
|
+
*/
|
|
70
|
+
readonly autoScroll?: number;
|
|
36
71
|
/**
|
|
37
72
|
* Arrow keys switch the slides when the swiper is focused\
|
|
38
73
|
* **Default:** `true`
|
|
@@ -61,7 +96,40 @@ export declare const Swiper: React.ForwardRefExoticComponent<Omit<React.HTMLAttr
|
|
|
61
96
|
readonly pauseOnHover?: boolean;
|
|
62
97
|
readonly threshold?: number;
|
|
63
98
|
readonly resistance?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* How many slides are visible at once. A fractional value shows a part of the next slide,
|
|
101
|
+
* for example `1.2`\
|
|
102
|
+
* **Default:** `1`
|
|
103
|
+
*/
|
|
64
104
|
readonly slidesPerView?: number;
|
|
105
|
+
/**
|
|
106
|
+
* The current slide is in the middle of the view, the neighbours are partially visible on both sides.
|
|
107
|
+
* Makes sense with a fractional `slidesPerView`\
|
|
108
|
+
* **Default:** `false`
|
|
109
|
+
*/
|
|
110
|
+
readonly centered?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The direction of the scroll. The vertical swiper needs a height: set it on the swiper\
|
|
113
|
+
* **Default:** `'horizontal'`
|
|
114
|
+
*/
|
|
115
|
+
readonly direction?: SwiperDirection;
|
|
116
|
+
/**
|
|
117
|
+
* `slide` moves the track, `fade` cross-fades the slides lying on top of each other.
|
|
118
|
+
* With `fade` the slides are always one per view\
|
|
119
|
+
* **Default:** `'slide'`
|
|
120
|
+
*/
|
|
121
|
+
readonly effect?: SwiperEffect;
|
|
122
|
+
/**
|
|
123
|
+
* The duration of the slide change in milliseconds\
|
|
124
|
+
* **Default:** `300`
|
|
125
|
+
*/
|
|
126
|
+
readonly speed?: number;
|
|
127
|
+
/**
|
|
128
|
+
* Continuous scroll (ticker) in pixels per second, for example the logos of the clients.
|
|
129
|
+
* The track moves without stops in the infinite loop, the drag and the keyboard are off.
|
|
130
|
+
* The scroll pauses with `pauseOnHover`, by the API and when the user asked the system to reduce motion
|
|
131
|
+
*/
|
|
132
|
+
readonly autoScroll?: number;
|
|
65
133
|
/**
|
|
66
134
|
* Arrow keys switch the slides when the swiper is focused\
|
|
67
135
|
* **Default:** `true`
|