@canlooks/can-ui 0.0.120 → 0.0.122

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 (31) hide show
  1. package/dist/cjs/components/dateTimePicker/dateTimePicker.style.js +1 -0
  2. package/dist/cjs/components/gallery/gallery.d.ts +3 -1
  3. package/dist/cjs/components/gallery/gallery.js +112 -49
  4. package/dist/cjs/components/gallery/gallery.style.js +7 -11
  5. package/dist/cjs/components/gallery/imageItem.d.ts +5 -2
  6. package/dist/cjs/components/gallery/imageItem.js +6 -4
  7. package/dist/cjs/components/gallery/index.d.ts +1 -0
  8. package/dist/cjs/components/gallery/index.js +1 -0
  9. package/dist/cjs/components/pinchable/pinchable.d.ts +7 -1
  10. package/dist/cjs/components/pinchable/pinchable.js +21 -9
  11. package/dist/cjs/components/pinchable/pinchable.style.js +8 -2
  12. package/dist/cjs/utils/bezier.d.ts +4 -3
  13. package/dist/cjs/utils/bezier.js +1 -1
  14. package/dist/cjs/utils/dnd.d.ts +9 -0
  15. package/dist/cjs/utils/dnd.js +16 -0
  16. package/dist/esm/components/dateTimePicker/dateTimePicker.style.js +1 -0
  17. package/dist/esm/components/gallery/gallery.d.ts +3 -1
  18. package/dist/esm/components/gallery/gallery.js +113 -50
  19. package/dist/esm/components/gallery/gallery.style.js +7 -11
  20. package/dist/esm/components/gallery/imageItem.d.ts +5 -2
  21. package/dist/esm/components/gallery/imageItem.js +7 -5
  22. package/dist/esm/components/gallery/index.d.ts +1 -0
  23. package/dist/esm/components/gallery/index.js +1 -0
  24. package/dist/esm/components/pinchable/pinchable.d.ts +7 -1
  25. package/dist/esm/components/pinchable/pinchable.js +22 -10
  26. package/dist/esm/components/pinchable/pinchable.style.js +8 -2
  27. package/dist/esm/utils/bezier.d.ts +4 -3
  28. package/dist/esm/utils/bezier.js +1 -1
  29. package/dist/esm/utils/dnd.d.ts +9 -0
  30. package/dist/esm/utils/dnd.js +15 -0
  31. package/package.json +1 -1
@@ -19,6 +19,7 @@ exports.style = (0, utils_1.defineCss)(({ text, spacing }) => (0, react_1.css) `
19
19
  .${exports.classes.container} {
20
20
  display: flex;
21
21
  align-items: center;
22
+ gap: ${spacing[1]}px;
22
23
 
23
24
  .${exports.classes.placeholder} {
24
25
  flex: 1;
@@ -15,6 +15,8 @@ export interface ImagePreviewProps extends ModalProps {
15
15
  showClose?: boolean;
16
16
  /** 自定义渲染控制按钮 */
17
17
  renderControl?: ReactNode;
18
+ /** 拖拽至最边缘时,是否允许弹性溢出,默认为`true` */
19
+ allowEdgeBounce?: boolean;
18
20
  /** 元素弹性移动距离,默认为24 */
19
21
  bounceElementTranslate?: number;
20
22
  /** 手指弹性拖拽距离,默认为240 */
@@ -22,4 +24,4 @@ export interface ImagePreviewProps extends ModalProps {
22
24
  /** 滑动生效的速度,默认为450 (px/s) */
23
25
  effectiveSpeed?: number;
24
26
  }
25
- export declare const Gallery: import("react").MemoExoticComponent<({ src, defaultIndex, index, onIndexChange, defaultOpen, open, onOpenChange, showRotation, showZoom, showClose, renderControl, bounceElementTranslate, bounceDragDistance, effectiveSpeed, ...props }: ImagePreviewProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
27
+ export declare const Gallery: import("react").MemoExoticComponent<({ src, defaultIndex, index, onIndexChange, defaultOpen, open, onOpenChange, showRotation, showZoom, showClose, renderControl, allowEdgeBounce, bounceElementTranslate, bounceDragDistance, effectiveSpeed, ...props }: ImagePreviewProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
@@ -24,104 +24,162 @@ const commonControlProps = {
24
24
  size: 'large',
25
25
  color: 'text'
26
26
  };
27
- const bounceBezier = (0, utils_1.cubicBezier)(0, 0, 0, 1);
28
- exports.Gallery = (0, react_1.memo)(({ src, defaultIndex = 0, index, onIndexChange, defaultOpen = false, open, onOpenChange, showRotation = true, showZoom = true, showClose = true, renderControl, bounceElementTranslate = 24, bounceDragDistance = 240, effectiveSpeed = 450, ...props }) => {
27
+ const DOUBLE_CLICK_DELAY = 300;
28
+ exports.Gallery = (0, react_1.memo)(({ src, defaultIndex = 0, index, onIndexChange, defaultOpen = false, open, onOpenChange, showRotation = true, showZoom = true, showClose = true, renderControl, allowEdgeBounce = true, bounceElementTranslate = 24, bounceDragDistance = 240, effectiveSpeed = 450, ...props }) => {
29
29
  const srcArr = (0, utils_1.useSync)((0, utils_1.toArray)(src || []));
30
30
  const [innerOpen, setInnerOpen] = (0, utils_1.useControlled)(defaultOpen, open, onOpenChange);
31
31
  const close = () => {
32
32
  setInnerOpen(false);
33
33
  };
34
34
  const [innerIndex, setInnerIndex] = (0, utils_1.useControlled)(defaultIndex, index, onIndexChange);
35
+ const maskRef = (0, react_1.useRef)(null);
35
36
  const wrapperRef = (0, react_1.useRef)(null);
36
37
  const imageItemRefs = (0, react_1.useRef)([]);
37
38
  imageItemRefs.current = [];
39
+ const imgRefs = (0, react_1.useRef)([]);
40
+ imgRefs.current = [];
38
41
  /**
39
42
  * --------------------------------------------------------------
40
43
  * 左右滚动翻页
41
44
  */
45
+ const dragDirection = (0, react_1.useRef)(void 0);
42
46
  const draggableHandles = (0, utils_1.useDraggable)({
43
47
  onDragStart() {
44
- wrapperRef.current.dataset.transition = 'drag';
48
+ maskRef.current.style.transition = 'none';
45
49
  return {
46
50
  isFit: imageItemRefs.current[innerIndex.current].isFit(),
47
51
  startLeft: -innerIndex.current * wrapperRef.current.offsetWidth
48
52
  };
49
53
  },
50
- onDrag({ diff: [dx], data: { isFit: { left, right }, startLeft } }) {
51
- if ((!left && dx > 0) || (!right && dx < 0)) {
54
+ onDrag({ diff: [dx, dy], data: { isFit: { left, right, top, bottom }, startLeft } }) {
55
+ if ((!left && dx > 0)
56
+ || (!right && dx < 0)
57
+ || (!top && dy > 0)
58
+ || (!bottom && dy < 0)) {
59
+ // 图片超出边缘时,无需触发Gallery的滑动翻页,此时拖拽效果由Pinchable处理
52
60
  return;
53
61
  }
54
- const min = -wrapperRef.current.offsetWidth * (srcArr.current.length - 1);
55
- const max = 0;
56
- let newLeft = (0, utils_1.range)(startLeft + dx, min - bounceDragDistance, max + bounceDragDistance);
57
- if (newLeft < min) {
58
- newLeft = min - bounceBezier(-(newLeft - min) / bounceDragDistance) * bounceElementTranslate;
62
+ if (!dragDirection.current) {
63
+ if (dx <= -5 || 5 <= dx) {
64
+ dragDirection.current = 'horizontal';
65
+ resetVerticalDrag();
66
+ }
67
+ if (dy <= -5 || 5 <= dy) {
68
+ dragDirection.current = 'vertical';
69
+ }
59
70
  }
60
- else if (newLeft > max) {
61
- newLeft = max + bounceBezier((newLeft - max) / bounceDragDistance) * bounceElementTranslate;
71
+ if (dragDirection.current !== 'vertical') {
72
+ // 处理横向拖动
73
+ const min = -wrapperRef.current.offsetWidth * (srcArr.current.length - 1);
74
+ const max = 0;
75
+ const newLeft = (0, utils_1.edgeBounce)(startLeft + dx, { min, max, allowEdgeBounce, bounceElementTranslate, bounceDragDistance });
76
+ wrapperRef.current.style.left = newLeft + 'px';
62
77
  }
63
- wrapperRef.current.style.left = newLeft + 'px';
64
- },
65
- onDragEnd({ diff: [dx], speed: [speedX], data: { isFit: { left, right } } }) {
66
- wrapperRef.current.dataset.transition = '';
67
- if (!dx) {
68
- return;
78
+ if (dragDirection.current !== 'horizontal') {
79
+ // 处理纵向拖动
80
+ const rate = 1 - Math.abs(dy) / 2 / wrapperRef.current.offsetHeight;
81
+ imageItemRefs.current[innerIndex.current].style.transform = `translateY(${dy}px) scale(${rate})`;
82
+ maskRef.current.style.opacity = rate.toString();
69
83
  }
70
- if ((!left && dx > 0) || (!right && dx < 0)) {
84
+ },
85
+ onDragEnd({ diff: [dx, dy], speed: [speedX, speedY], data: { isFit: { left, right, top, bottom } } }) {
86
+ if (!dx && !dy) {
71
87
  return;
72
88
  }
73
- alowSlideTransition();
74
- const reset = () => {
75
- wrapperRef.current.style.left = -innerIndex.current * wrapperRef.current.offsetWidth + 'px';
76
- };
77
- const goPrev = () => {
78
- innerIndex.current === 0
79
- ? reset()
80
- : goPrevLoop();
81
- };
82
- const goNext = () => {
83
- innerIndex.current === srcArr.current.length - 1
84
- ? reset()
85
- : goNextLoop();
86
- };
87
- if (effectiveSpeed && speedX * 1000 >= effectiveSpeed) {
88
- dx > 0 ? goPrev() : goNext();
89
+ resetVerticalDrag(true);
90
+ if ((!left && dx > 0)
91
+ || (!right && dx < 0)
92
+ || (!top && dy > 0)
93
+ || (!bottom && dy < 0)) {
94
+ // 图片超出边缘时,无需触发Gallery的滑动翻页,此时拖拽效果由Pinchable处理
89
95
  return;
90
96
  }
91
- const halfWidth = wrapperRef.current.offsetWidth / 2;
92
- if (dx > halfWidth) {
93
- goPrev();
94
- }
95
- else if (dx < -halfWidth) {
96
- goNext();
97
+ if (dragDirection.current === 'vertical') {
98
+ if ((effectiveSpeed && speedY * 1000 >= effectiveSpeed)
99
+ || Math.abs(dy) > wrapperRef.current.offsetHeight / 2) {
100
+ close();
101
+ }
97
102
  }
98
103
  else {
99
- reset();
104
+ const goPrev = () => {
105
+ innerIndex.current === 0
106
+ ? resetHorizontalDrag()
107
+ : goPrevLoop();
108
+ };
109
+ const goNext = () => {
110
+ innerIndex.current === srcArr.current.length - 1
111
+ ? resetHorizontalDrag()
112
+ : goNextLoop();
113
+ };
114
+ // 满足速度要求
115
+ if (effectiveSpeed && speedX * 1000 >= effectiveSpeed) {
116
+ dx > 0 ? goPrev() : goNext();
117
+ return;
118
+ }
119
+ // 拖拽距离达到一半
120
+ const halfWidth = wrapperRef.current.offsetWidth / 2;
121
+ if (dx >= halfWidth) {
122
+ goPrev();
123
+ }
124
+ else if (dx <= -halfWidth) {
125
+ goNext();
126
+ }
127
+ else {
128
+ resetHorizontalDrag();
129
+ }
100
130
  }
131
+ dragDirection.current = void 0;
101
132
  },
102
133
  onClick: () => {
103
- doubleClicked.current = false;
104
134
  setTimeout(() => {
105
135
  !doubleClicked.current && close();
106
- }, 250);
136
+ }, DOUBLE_CLICK_DELAY);
107
137
  }
108
138
  });
139
+ const resetHorizontalDrag = () => {
140
+ allowSlideTransition('bounce');
141
+ wrapperRef.current.style.left = -innerIndex.current * wrapperRef.current.offsetWidth + 'px';
142
+ };
143
+ const resetVerticalDrag = (transition = false) => {
144
+ const imageItem = imageItemRefs.current[innerIndex.current];
145
+ const mask = maskRef.current;
146
+ if (transition) {
147
+ imageItem.dataset.transition = 'true';
148
+ // mask的css定义了transition,只需去掉style.transition即可实现过渡
149
+ mask.style.transition = '';
150
+ }
151
+ imageItem.style.transform = '';
152
+ mask.style.opacity = '';
153
+ };
109
154
  const doubleClicked = (0, react_1.useRef)(false);
110
155
  const doubleClickHandler = () => {
111
156
  doubleClicked.current = true;
157
+ setTimeout(() => doubleClicked.current = false, DOUBLE_CLICK_DELAY);
158
+ };
159
+ const allowSlideTransition = (transitionType = 'true') => {
160
+ if (wrapperRef.current) {
161
+ wrapperRef.current.dataset.transition = transitionType;
162
+ }
112
163
  };
113
- const alowSlideTransition = () => {
114
- wrapperRef.current && (wrapperRef.current.dataset.transition = 'set');
164
+ const clearTransition = () => {
165
+ if (wrapperRef.current) {
166
+ wrapperRef.current.dataset.transition = '';
167
+ }
168
+ const imageItem = imageItemRefs.current[innerIndex.current];
169
+ if (imageItem) {
170
+ imageItem.dataset.transition = '';
171
+ }
172
+ maskRef.current.style.transition = '';
115
173
  };
116
174
  const goPrevLoop = () => {
117
- alowSlideTransition();
175
+ allowSlideTransition();
118
176
  setInnerIndex(o => {
119
177
  imageItemRefs.current[o].reset();
120
178
  return (o + srcArr.current.length - 1) % srcArr.current.length;
121
179
  });
122
180
  };
123
181
  const goNextLoop = () => {
124
- alowSlideTransition();
182
+ allowSlideTransition();
125
183
  setInnerIndex(o => {
126
184
  imageItemRefs.current[o].reset();
127
185
  return (o + 1) % srcArr.current.length;
@@ -153,13 +211,18 @@ exports.Gallery = (0, react_1.memo)(({ src, defaultIndex = 0, index, onIndexChan
153
211
  };
154
212
  return ((0, jsx_runtime_1.jsx)(modal_1.Modal, { ...props, css: gallery_style_1.style, className: (0, utils_1.clsx)(gallery_style_1.classes.root, props.className), open: innerOpen.current, onClosed: resetAll, maskProps: {
155
213
  ...props.maskProps,
214
+ ref: (0, utils_1.cloneRef)(maskRef, props.maskProps?.ref),
156
215
  children: ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: gallery_style_1.classes.control, children: [renderControl, showRotation &&
157
216
  (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { title: "\u65CB\u8F6C-90\u00B0", children: (0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: rotateLeft, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faArrowRotateLeft_1.faArrowRotateLeft }) }) }), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { title: "\u65CB\u8F6C90\u00B0", children: (0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: rotateRight, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faArrowRotateRight_1.faArrowRotateRight }) }) })] }), showZoom &&
158
217
  (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { title: "\u7F29\u5C0F", children: (0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: zoomOut, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faMagnifyingGlassMinus_1.faMagnifyingGlassMinus }) }) }), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { title: "\u653E\u5927", children: (0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: zoomIn, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faMagnifyingGlassPlus_1.faMagnifyingGlassPlus }) }) }), (0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { title: "\u9002\u5E94\u5C4F\u5E55", children: (0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: reset, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faExpand_1.faExpand }) }) })] }), showClose &&
159
218
  (0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: close, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faXmark_1.faXmark }) })] }), srcArr.current.length > 1 &&
160
219
  (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: gallery_style_1.classes.swap, children: [(0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: goPrevLoop, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faAngleLeft_1.faAngleLeft }) }), (0, jsx_runtime_1.jsx)(button_1.Button, { ...commonControlProps, onClick: goNextLoop, children: (0, jsx_runtime_1.jsx)(icon_1.Icon, { icon: faAngleRight_1.faAngleRight }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: gallery_style_1.classes.counter, children: [innerIndex.current + 1, " / ", srcArr.current.length] })] })] }))
161
220
  }, children: srcArr.current.length > 0 &&
162
- (0, jsx_runtime_1.jsx)("div", { className: gallery_style_1.classes.galleryContainer, ...draggableHandles, onDoubleClick: doubleClickHandler, children: (0, jsx_runtime_1.jsx)("div", { ref: wrapperRef, className: gallery_style_1.classes.galleryWrapper, style: { left: -innerIndex.current * 100 + '%' }, onTransitionEnd: e => e.currentTarget.dataset.transition = '', children: srcArr.current.map((src, i) => (0, jsx_runtime_1.jsx)(imageItem_1.ImageItem, { ref: r => {
221
+ (0, jsx_runtime_1.jsx)("div", { className: gallery_style_1.classes.galleryContainer, ...draggableHandles, onDoubleClick: doubleClickHandler, children: (0, jsx_runtime_1.jsx)("div", { ref: wrapperRef, className: gallery_style_1.classes.galleryWrapper, style: { left: -innerIndex.current * 100 + '%' }, onTransitionEnd: clearTransition, children: srcArr.current.map((src, i) => (0, jsx_runtime_1.jsx)(imageItem_1.ImageItem, { ref: r => {
163
222
  r && imageItemRefs.current.push(r);
223
+ }, imgProps: {
224
+ ref: r => {
225
+ r && imgRefs.current.push(r);
226
+ }
164
227
  }, style: { left: i * 100 + '%' }, src: src }, i)) }) }) }));
165
228
  });
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.style = exports.classes = void 0;
4
4
  const react_1 = require("@emotion/react");
5
5
  const utils_1 = require("../../utils");
6
- exports.classes = (0, utils_1.defineInnerClasses)('image-preview', [
6
+ exports.classes = (0, utils_1.defineInnerClasses)('gallery', [
7
7
  'button',
8
8
  'control',
9
9
  'swap',
@@ -82,14 +82,14 @@ exports.style = (0, utils_1.defineCss)(({ spacing, easing, breakpoints }) => (0,
82
82
  &, .${exports.classes.imageItem} {
83
83
  position: absolute;
84
84
  top: 0;
85
- }
86
85
 
87
- &[data-transition=set] {
88
- transition: left .5s ${easing.bounce} 0s;
89
- }
86
+ &[data-transition=true] {
87
+ transition: all .3s ${easing.easeOut};
88
+ }
90
89
 
91
- &[data-transition=drag] {
92
- transition: left .1s linear;
90
+ &[data-transition=bounce] {
91
+ transition: all .25s ${easing.bounce};
92
+ }
93
93
  }
94
94
 
95
95
  .${exports.classes.imageItem} {
@@ -108,10 +108,6 @@ exports.style = (0, utils_1.defineCss)(({ spacing, easing, breakpoints }) => (0,
108
108
  }
109
109
 
110
110
  .${exports.classes.galleryContainer} .${exports.classes.galleryWrapper} {
111
- &[data-transition=true] {
112
- transition: left .4s ${easing.ease};
113
- }
114
-
115
111
  .${exports.classes.imageItem} {
116
112
  padding: 0;
117
113
  }
@@ -1,7 +1,9 @@
1
- import { Ref } from 'react';
1
+ import { JSX, Ref } from 'react';
2
2
  import { PinchableProps, PinchableRef } from '../pinchable';
3
3
  export interface ImageItemRef extends PinchableRef {
4
4
  isFit(): {
5
+ top: boolean;
6
+ bottom: boolean;
5
7
  left: boolean;
6
8
  right: boolean;
7
9
  };
@@ -9,6 +11,7 @@ export interface ImageItemRef extends PinchableRef {
9
11
  interface ImageItemProps extends PinchableProps {
10
12
  ref?: Ref<ImageItemRef>;
11
13
  src?: string;
14
+ imgProps?: JSX.IntrinsicElements['img'];
12
15
  }
13
- export declare function ImageItem({ ref, src, ...props }: ImageItemProps): import("@emotion/react/jsx-runtime").JSX.Element;
16
+ export declare function ImageItem({ ref, src, imgProps, ...props }: ImageItemProps): import("@emotion/react/jsx-runtime").JSX.Element;
14
17
  export {};
@@ -6,13 +6,15 @@ const gallery_style_1 = require("./gallery.style");
6
6
  const react_1 = require("react");
7
7
  const utils_1 = require("../../utils");
8
8
  const pinchable_1 = require("../pinchable");
9
- function ImageItem({ ref, src, ...props }) {
9
+ function ImageItem({ ref, src, imgProps, ...props }) {
10
10
  (0, react_1.useImperativeHandle)(ref, () => {
11
11
  if (pinchableRef.current) {
12
12
  pinchableRef.current.isFit = () => {
13
- const { x: pinchableX, width: pinchableWidth } = pinchableRef.current.getBoundingClientRect();
14
- const { x: imgX, width: imgWidth } = imgRef.current.getBoundingClientRect();
13
+ const { x: pinchableX, y: pinchableY, width: pinchableWidth, height: pinchableHeight } = pinchableRef.current.getBoundingClientRect();
14
+ const { x: imgX, y: imgY, width: imgWidth, height: imgHeight } = imgRef.current.getBoundingClientRect();
15
15
  return {
16
+ top: imgY >= pinchableY,
17
+ bottom: imgY + imgHeight <= pinchableY + pinchableHeight,
16
18
  left: imgX >= pinchableX,
17
19
  right: imgX + imgWidth <= pinchableX + pinchableWidth
18
20
  };
@@ -47,5 +49,5 @@ function ImageItem({ ref, src, ...props }) {
47
49
  resizeObserver.disconnect();
48
50
  };
49
51
  }, []);
50
- return ((0, jsx_runtime_1.jsx)(pinchable_1.Pinchable, { ...props, ref: pinchableRef, className: (0, utils_1.clsx)(gallery_style_1.classes.imageItem, props.className), children: (0, jsx_runtime_1.jsx)("img", { ref: imgRef, className: gallery_style_1.classes.image, src: src, draggable: false, alt: "" }) }));
52
+ return ((0, jsx_runtime_1.jsx)(pinchable_1.Pinchable, { ...props, ref: pinchableRef, className: (0, utils_1.clsx)(gallery_style_1.classes.imageItem, props.className), allowEdgeBounce: false, children: (0, jsx_runtime_1.jsx)("img", { draggable: false, alt: "", src: src, ...imgProps, ref: (0, utils_1.cloneRef)(imgRef, imgProps?.ref), className: (0, utils_1.clsx)(gallery_style_1.classes.image, imgProps?.className) }) }));
51
53
  }
@@ -1 +1,2 @@
1
1
  export * from './gallery';
2
+ export * from './imageItem';
@@ -2,3 +2,4 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  tslib_1.__exportStar(require("./gallery"), exports);
5
+ tslib_1.__exportStar(require("./imageItem"), exports);
@@ -22,11 +22,17 @@ type PinchableOwnProps = {
22
22
  translate?: [number, number];
23
23
  onTranslateChange?(translate: [number, number]): void;
24
24
  /** 平移限制,`translate`非受控模式下有效 */
25
- translateLimit?: (targetX: number, targetY: number, scale: number) => [number, number];
25
+ translateLimit?: (targetX: number, targetY: number, scale: number, rotateDeg: number) => [number, number];
26
26
  defaultRotate?: number;
27
27
  rotate?: number;
28
28
  onRotateChange?(rotate: number): void;
29
29
  children?: ReactElement;
30
+ /** 拖拽至最边缘时,是否允许弹性溢出,默认为`true` */
31
+ allowEdgeBounce?: boolean;
32
+ /** 元素弹性移动距离,默认为24 */
33
+ bounceElementTranslate?: number;
34
+ /** 手指弹性拖拽距离,默认为240 */
35
+ bounceDragDistance?: number;
30
36
  };
31
37
  export type PinchableProps<C extends ElementType = 'div'> = OverridableProps<PinchableOwnProps, C>;
32
38
  export declare const Pinchable: <C extends ElementType = "div">(props: PinchableProps<C>) => ReactElement;
@@ -5,7 +5,7 @@ const jsx_runtime_1 = require("@emotion/react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const utils_1 = require("../../utils");
7
7
  const pinchable_style_1 = require("./pinchable.style");
8
- exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defaultScale = 1, scale, onScaleChange, scaleLimit = [1, 6], defaultTranslate = [0, 0], translate, onTranslateChange, translateLimit, defaultRotate = 0, rotate, onRotateChange, children, ...props }) => {
8
+ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defaultScale = 1, scale, onScaleChange, scaleLimit = [1, 6], defaultTranslate = [0, 0], translate, onTranslateChange, translateLimit, defaultRotate = 0, rotate, onRotateChange, children, allowEdgeBounce = true, bounceElementTranslate = 24, bounceDragDistance = 240, ...props }) => {
9
9
  (0, react_1.useImperativeHandle)(ref, () => {
10
10
  if (wrapperRef.current) {
11
11
  wrapperRef.current.zoomIn = () => {
@@ -20,7 +20,7 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
20
20
  rotateFn(innerRotate.current - 90);
21
21
  };
22
22
  wrapperRef.current.rotateRight = () => {
23
- rotateFn(innerRotate.current - 90);
23
+ rotateFn(innerRotate.current + 90);
24
24
  };
25
25
  wrapperRef.current.reset = resetAll;
26
26
  }
@@ -41,6 +41,12 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
41
41
  onDrag(info, e) {
42
42
  gestureOptions?.onDrag?.(info, e);
43
43
  const { diff: [dx, dy], data: [startX, startY] } = info;
44
+ setInnerTranslate(translateLimitFn(startX + dx, startY + dy, void 0, void 0, true));
45
+ },
46
+ onDragEnd(info) {
47
+ gestureOptions?.onDragEnd?.(info);
48
+ const { diff: [dx, dy], data: [startX, startY] } = info;
49
+ allowTransition('bounce');
44
50
  setInnerTranslate(translateLimitFn(startX + dx, startY + dy));
45
51
  },
46
52
  onPinchStart(info) {
@@ -78,9 +84,9 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
78
84
  if (!children) {
79
85
  return;
80
86
  }
81
- const translateLimitFn = (x, y, scale = innerScale.current, deg = innerRotate.current) => {
87
+ const translateLimitFn = (x, y, scale = innerScale.current, deg = innerRotate.current, bounce = false) => {
82
88
  if (translateLimit) {
83
- return translateLimit(x, y, scale);
89
+ return translateLimit(x, y, scale, deg);
84
90
  }
85
91
  const { clientWidth: wrapperWidth, clientHeight: wrapperHeight } = wrapperRef.current;
86
92
  const { offsetWidth, offsetHeight } = contentRef.current;
@@ -89,14 +95,20 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
89
95
  const contentHeight = (isRotated ? offsetWidth : offsetHeight) * scale;
90
96
  const limitX = contentWidth > wrapperWidth ? (contentWidth - wrapperWidth) / 2 : 0;
91
97
  const limitY = contentHeight > wrapperHeight ? (contentHeight - wrapperHeight) / 2 : 0;
98
+ if (!bounce) {
99
+ return [
100
+ (0, utils_1.range)(x, -limitX, limitX),
101
+ (0, utils_1.range)(y, -limitY, limitY)
102
+ ];
103
+ }
92
104
  return [
93
- (0, utils_1.range)(x, -limitX, limitX),
94
- (0, utils_1.range)(y, -limitY, limitY)
105
+ (0, utils_1.edgeBounce)(x, { min: -limitX, max: limitX, allowEdgeBounce, bounceElementTranslate, bounceDragDistance }),
106
+ (0, utils_1.edgeBounce)(y, { min: -limitY, max: limitY, allowEdgeBounce, bounceElementTranslate, bounceDragDistance })
95
107
  ];
96
108
  };
97
109
  const childrenProps = children.props;
98
- const allowTransition = () => {
99
- contentRef.current && (contentRef.current.dataset.transition = 'true');
110
+ const allowTransition = (transitionType = 'true') => {
111
+ contentRef.current && (contentRef.current.dataset.transition = transitionType);
100
112
  };
101
113
  const zoomFn = (targetScale, originX, originY) => {
102
114
  allowTransition();
@@ -150,7 +162,7 @@ exports.Pinchable = (({ component: Component = 'div', ref, gestureOptions, defau
150
162
  : zoomFn(innerScale.current * 1.2, e.clientX, e.clientY);
151
163
  };
152
164
  const onTransitionEnd = (e) => {
153
- e.currentTarget.dataset.transition = 'false';
165
+ e.currentTarget.dataset.transition = '';
154
166
  setInnerRotate(innerRotate.current % 360);
155
167
  };
156
168
  return ((0, jsx_runtime_1.jsx)(Component, { ...props, ref: wrapperRef, css: pinchable_style_1.style, className: (0, utils_1.clsx)(pinchable_style_1.classes.root, props.className), ...pinchableHandles, children: (0, react_1.cloneElement)(children, {
@@ -16,8 +16,14 @@ exports.style = (0, utils_1.defineCss)(({ easing }) => (0, react_1.css) `
16
16
  touch-action: none;
17
17
  }
18
18
 
19
- .${exports.classes.content}[data-transition=true] {
20
- transition: all .25s ${easing.easeOut};
19
+ .${exports.classes.content} {
20
+ &[data-transition=true] {
21
+ transition: all .25s ${easing.easeOut};
22
+ }
23
+
24
+ &[data-transition=bounce] {
25
+ transition: all .25s ${easing.bounce};
26
+ }
21
27
  }
22
28
  }
23
29
  `);
@@ -1,13 +1,14 @@
1
+ export type BezierFunc = (x: number) => number;
1
2
  /**
2
3
  * 创建一个Bezier曲线函数,默认从(0,0)到(1,1)
3
4
  * @param p1x
4
5
  * @param p1y
5
6
  * @param p2x
6
7
  * @param p2y
7
- * @returns {(x: number) => number} 返回一个函数,接收x返回y
8
+ * @returns {BezierFunc} 返回一个函数,接收x返回y
8
9
  */
9
- export declare function cubicBezier(p1x: number, p1y: number, p2x: number, p2y: number): (x: number) => number;
10
+ export declare function cubicBezier(p1x: number, p1y: number, p2x: number, p2y: number): BezierFunc;
10
11
  export declare namespace cubicBezier {
11
- var preset: (preset: Preset) => (x: number) => number;
12
+ var preset: (preset: Preset) => BezierFunc;
12
13
  }
13
14
  export type Preset = 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
@@ -7,7 +7,7 @@ exports.cubicBezier = cubicBezier;
7
7
  * @param p1y
8
8
  * @param p2x
9
9
  * @param p2y
10
- * @returns {(x: number) => number} 返回一个函数,接收x返回y
10
+ * @returns {BezierFunc} 返回一个函数,接收x返回y
11
11
  */
12
12
  function cubicBezier(p1x, p1y, p2x, p2y) {
13
13
  const ZERO_LIMIT = 1e-6;
@@ -1,6 +1,7 @@
1
1
  import { DragEndEvent } from '@dnd-kit/core';
2
2
  import { Id, Obj } from '../types';
3
3
  import { NodeType, SortInfo } from '../components/tree';
4
+ import { BezierFunc } from './bezier';
4
5
  /**
5
6
  * 默认提供给@dnd-kit的sensors属性
6
7
  */
@@ -19,3 +20,11 @@ export declare function onDndDragEnd<S extends Obj>(e: DragEndEvent, prevState:
19
20
  * @param primaryKey 索引用的主键,默认为`id`
20
21
  */
21
22
  export declare function onTreeNodeSort<N extends NodeType<V>, V extends Id = Id>(info: SortInfo<V>, treeNodes: N[], primaryKey?: string): N[];
23
+ export declare function edgeBounce(value: number, { allowEdgeBounce, min, max, bounceElementTranslate, bounceDragDistance, bezierFn }: {
24
+ allowEdgeBounce: boolean;
25
+ min: number;
26
+ max: number;
27
+ bounceElementTranslate: number;
28
+ bounceDragDistance: number;
29
+ bezierFn?: BezierFunc;
30
+ }): number;
@@ -3,8 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useDndSensors = useDndSensors;
4
4
  exports.onDndDragEnd = onDndDragEnd;
5
5
  exports.onTreeNodeSort = onTreeNodeSort;
6
+ exports.edgeBounce = edgeBounce;
6
7
  const core_1 = require("@dnd-kit/core");
7
8
  const sortable_1 = require("@dnd-kit/sortable");
9
+ const utils_1 = require("./utils");
10
+ const bezier_1 = require("./bezier");
8
11
  /**
9
12
  * 默认提供给@dnd-kit的sensors属性
10
13
  */
@@ -73,3 +76,16 @@ function onTreeNodeSort(info, treeNodes, primaryKey = 'id') {
73
76
  }
74
77
  return treeNodes;
75
78
  }
79
+ function edgeBounce(value, { allowEdgeBounce, min, max, bounceElementTranslate, bounceDragDistance, bezierFn = (0, bezier_1.cubicBezier)(0, 0, 0, 1) }) {
80
+ if (allowEdgeBounce && bounceElementTranslate && bounceDragDistance > 0) {
81
+ value = (0, utils_1.range)(value, min - bounceDragDistance, max + bounceDragDistance);
82
+ if (value < min) {
83
+ value = min - bezierFn((min - value) / bounceDragDistance) * bounceElementTranslate;
84
+ }
85
+ else if (value > max) {
86
+ value = max + bezierFn((value - max) / bounceDragDistance) * bounceElementTranslate;
87
+ }
88
+ return value;
89
+ }
90
+ return (0, utils_1.range)(value, min, max);
91
+ }
@@ -16,6 +16,7 @@ export const style = defineCss(({ text, spacing }) => css `
16
16
  .${classes.container} {
17
17
  display: flex;
18
18
  align-items: center;
19
+ gap: ${spacing[1]}px;
19
20
 
20
21
  .${classes.placeholder} {
21
22
  flex: 1;
@@ -15,6 +15,8 @@ export interface ImagePreviewProps extends ModalProps {
15
15
  showClose?: boolean;
16
16
  /** 自定义渲染控制按钮 */
17
17
  renderControl?: ReactNode;
18
+ /** 拖拽至最边缘时,是否允许弹性溢出,默认为`true` */
19
+ allowEdgeBounce?: boolean;
18
20
  /** 元素弹性移动距离,默认为24 */
19
21
  bounceElementTranslate?: number;
20
22
  /** 手指弹性拖拽距离,默认为240 */
@@ -22,4 +24,4 @@ export interface ImagePreviewProps extends ModalProps {
22
24
  /** 滑动生效的速度,默认为450 (px/s) */
23
25
  effectiveSpeed?: number;
24
26
  }
25
- export declare const Gallery: import("react").MemoExoticComponent<({ src, defaultIndex, index, onIndexChange, defaultOpen, open, onOpenChange, showRotation, showZoom, showClose, renderControl, bounceElementTranslate, bounceDragDistance, effectiveSpeed, ...props }: ImagePreviewProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
27
+ export declare const Gallery: import("react").MemoExoticComponent<({ src, defaultIndex, index, onIndexChange, defaultOpen, open, onOpenChange, showRotation, showZoom, showClose, renderControl, allowEdgeBounce, bounceElementTranslate, bounceDragDistance, effectiveSpeed, ...props }: ImagePreviewProps) => import("@emotion/react/jsx-runtime").JSX.Element>;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
2
2
  import { memo, useRef } from 'react';
3
3
  import { Modal } from '../modal';
4
- import { clsx, cubicBezier, range, toArray, useControlled, useDraggable, useSync } from '../../utils';
4
+ import { cloneRef, clsx, edgeBounce, toArray, useControlled, useDraggable, useSync } from '../../utils';
5
5
  import { classes, style } from './gallery.style';
6
6
  import { Button } from '../button';
7
7
  import { Tooltip } from '../tooltip';
@@ -21,104 +21,162 @@ const commonControlProps = {
21
21
  size: 'large',
22
22
  color: 'text'
23
23
  };
24
- const bounceBezier = cubicBezier(0, 0, 0, 1);
25
- export const Gallery = memo(({ src, defaultIndex = 0, index, onIndexChange, defaultOpen = false, open, onOpenChange, showRotation = true, showZoom = true, showClose = true, renderControl, bounceElementTranslate = 24, bounceDragDistance = 240, effectiveSpeed = 450, ...props }) => {
24
+ const DOUBLE_CLICK_DELAY = 300;
25
+ export const Gallery = memo(({ src, defaultIndex = 0, index, onIndexChange, defaultOpen = false, open, onOpenChange, showRotation = true, showZoom = true, showClose = true, renderControl, allowEdgeBounce = true, bounceElementTranslate = 24, bounceDragDistance = 240, effectiveSpeed = 450, ...props }) => {
26
26
  const srcArr = useSync(toArray(src || []));
27
27
  const [innerOpen, setInnerOpen] = useControlled(defaultOpen, open, onOpenChange);
28
28
  const close = () => {
29
29
  setInnerOpen(false);
30
30
  };
31
31
  const [innerIndex, setInnerIndex] = useControlled(defaultIndex, index, onIndexChange);
32
+ const maskRef = useRef(null);
32
33
  const wrapperRef = useRef(null);
33
34
  const imageItemRefs = useRef([]);
34
35
  imageItemRefs.current = [];
36
+ const imgRefs = useRef([]);
37
+ imgRefs.current = [];
35
38
  /**
36
39
  * --------------------------------------------------------------
37
40
  * 左右滚动翻页
38
41
  */
42
+ const dragDirection = useRef(void 0);
39
43
  const draggableHandles = useDraggable({
40
44
  onDragStart() {
41
- wrapperRef.current.dataset.transition = 'drag';
45
+ maskRef.current.style.transition = 'none';
42
46
  return {
43
47
  isFit: imageItemRefs.current[innerIndex.current].isFit(),
44
48
  startLeft: -innerIndex.current * wrapperRef.current.offsetWidth
45
49
  };
46
50
  },
47
- onDrag({ diff: [dx], data: { isFit: { left, right }, startLeft } }) {
48
- if ((!left && dx > 0) || (!right && dx < 0)) {
51
+ onDrag({ diff: [dx, dy], data: { isFit: { left, right, top, bottom }, startLeft } }) {
52
+ if ((!left && dx > 0)
53
+ || (!right && dx < 0)
54
+ || (!top && dy > 0)
55
+ || (!bottom && dy < 0)) {
56
+ // 图片超出边缘时,无需触发Gallery的滑动翻页,此时拖拽效果由Pinchable处理
49
57
  return;
50
58
  }
51
- const min = -wrapperRef.current.offsetWidth * (srcArr.current.length - 1);
52
- const max = 0;
53
- let newLeft = range(startLeft + dx, min - bounceDragDistance, max + bounceDragDistance);
54
- if (newLeft < min) {
55
- newLeft = min - bounceBezier(-(newLeft - min) / bounceDragDistance) * bounceElementTranslate;
59
+ if (!dragDirection.current) {
60
+ if (dx <= -5 || 5 <= dx) {
61
+ dragDirection.current = 'horizontal';
62
+ resetVerticalDrag();
63
+ }
64
+ if (dy <= -5 || 5 <= dy) {
65
+ dragDirection.current = 'vertical';
66
+ }
56
67
  }
57
- else if (newLeft > max) {
58
- newLeft = max + bounceBezier((newLeft - max) / bounceDragDistance) * bounceElementTranslate;
68
+ if (dragDirection.current !== 'vertical') {
69
+ // 处理横向拖动
70
+ const min = -wrapperRef.current.offsetWidth * (srcArr.current.length - 1);
71
+ const max = 0;
72
+ const newLeft = edgeBounce(startLeft + dx, { min, max, allowEdgeBounce, bounceElementTranslate, bounceDragDistance });
73
+ wrapperRef.current.style.left = newLeft + 'px';
59
74
  }
60
- wrapperRef.current.style.left = newLeft + 'px';
61
- },
62
- onDragEnd({ diff: [dx], speed: [speedX], data: { isFit: { left, right } } }) {
63
- wrapperRef.current.dataset.transition = '';
64
- if (!dx) {
65
- return;
75
+ if (dragDirection.current !== 'horizontal') {
76
+ // 处理纵向拖动
77
+ const rate = 1 - Math.abs(dy) / 2 / wrapperRef.current.offsetHeight;
78
+ imageItemRefs.current[innerIndex.current].style.transform = `translateY(${dy}px) scale(${rate})`;
79
+ maskRef.current.style.opacity = rate.toString();
66
80
  }
67
- if ((!left && dx > 0) || (!right && dx < 0)) {
81
+ },
82
+ onDragEnd({ diff: [dx, dy], speed: [speedX, speedY], data: { isFit: { left, right, top, bottom } } }) {
83
+ if (!dx && !dy) {
68
84
  return;
69
85
  }
70
- alowSlideTransition();
71
- const reset = () => {
72
- wrapperRef.current.style.left = -innerIndex.current * wrapperRef.current.offsetWidth + 'px';
73
- };
74
- const goPrev = () => {
75
- innerIndex.current === 0
76
- ? reset()
77
- : goPrevLoop();
78
- };
79
- const goNext = () => {
80
- innerIndex.current === srcArr.current.length - 1
81
- ? reset()
82
- : goNextLoop();
83
- };
84
- if (effectiveSpeed && speedX * 1000 >= effectiveSpeed) {
85
- dx > 0 ? goPrev() : goNext();
86
+ resetVerticalDrag(true);
87
+ if ((!left && dx > 0)
88
+ || (!right && dx < 0)
89
+ || (!top && dy > 0)
90
+ || (!bottom && dy < 0)) {
91
+ // 图片超出边缘时,无需触发Gallery的滑动翻页,此时拖拽效果由Pinchable处理
86
92
  return;
87
93
  }
88
- const halfWidth = wrapperRef.current.offsetWidth / 2;
89
- if (dx > halfWidth) {
90
- goPrev();
91
- }
92
- else if (dx < -halfWidth) {
93
- goNext();
94
+ if (dragDirection.current === 'vertical') {
95
+ if ((effectiveSpeed && speedY * 1000 >= effectiveSpeed)
96
+ || Math.abs(dy) > wrapperRef.current.offsetHeight / 2) {
97
+ close();
98
+ }
94
99
  }
95
100
  else {
96
- reset();
101
+ const goPrev = () => {
102
+ innerIndex.current === 0
103
+ ? resetHorizontalDrag()
104
+ : goPrevLoop();
105
+ };
106
+ const goNext = () => {
107
+ innerIndex.current === srcArr.current.length - 1
108
+ ? resetHorizontalDrag()
109
+ : goNextLoop();
110
+ };
111
+ // 满足速度要求
112
+ if (effectiveSpeed && speedX * 1000 >= effectiveSpeed) {
113
+ dx > 0 ? goPrev() : goNext();
114
+ return;
115
+ }
116
+ // 拖拽距离达到一半
117
+ const halfWidth = wrapperRef.current.offsetWidth / 2;
118
+ if (dx >= halfWidth) {
119
+ goPrev();
120
+ }
121
+ else if (dx <= -halfWidth) {
122
+ goNext();
123
+ }
124
+ else {
125
+ resetHorizontalDrag();
126
+ }
97
127
  }
128
+ dragDirection.current = void 0;
98
129
  },
99
130
  onClick: () => {
100
- doubleClicked.current = false;
101
131
  setTimeout(() => {
102
132
  !doubleClicked.current && close();
103
- }, 250);
133
+ }, DOUBLE_CLICK_DELAY);
104
134
  }
105
135
  });
136
+ const resetHorizontalDrag = () => {
137
+ allowSlideTransition('bounce');
138
+ wrapperRef.current.style.left = -innerIndex.current * wrapperRef.current.offsetWidth + 'px';
139
+ };
140
+ const resetVerticalDrag = (transition = false) => {
141
+ const imageItem = imageItemRefs.current[innerIndex.current];
142
+ const mask = maskRef.current;
143
+ if (transition) {
144
+ imageItem.dataset.transition = 'true';
145
+ // mask的css定义了transition,只需去掉style.transition即可实现过渡
146
+ mask.style.transition = '';
147
+ }
148
+ imageItem.style.transform = '';
149
+ mask.style.opacity = '';
150
+ };
106
151
  const doubleClicked = useRef(false);
107
152
  const doubleClickHandler = () => {
108
153
  doubleClicked.current = true;
154
+ setTimeout(() => doubleClicked.current = false, DOUBLE_CLICK_DELAY);
155
+ };
156
+ const allowSlideTransition = (transitionType = 'true') => {
157
+ if (wrapperRef.current) {
158
+ wrapperRef.current.dataset.transition = transitionType;
159
+ }
109
160
  };
110
- const alowSlideTransition = () => {
111
- wrapperRef.current && (wrapperRef.current.dataset.transition = 'set');
161
+ const clearTransition = () => {
162
+ if (wrapperRef.current) {
163
+ wrapperRef.current.dataset.transition = '';
164
+ }
165
+ const imageItem = imageItemRefs.current[innerIndex.current];
166
+ if (imageItem) {
167
+ imageItem.dataset.transition = '';
168
+ }
169
+ maskRef.current.style.transition = '';
112
170
  };
113
171
  const goPrevLoop = () => {
114
- alowSlideTransition();
172
+ allowSlideTransition();
115
173
  setInnerIndex(o => {
116
174
  imageItemRefs.current[o].reset();
117
175
  return (o + srcArr.current.length - 1) % srcArr.current.length;
118
176
  });
119
177
  };
120
178
  const goNextLoop = () => {
121
- alowSlideTransition();
179
+ allowSlideTransition();
122
180
  setInnerIndex(o => {
123
181
  imageItemRefs.current[o].reset();
124
182
  return (o + 1) % srcArr.current.length;
@@ -150,13 +208,18 @@ export const Gallery = memo(({ src, defaultIndex = 0, index, onIndexChange, defa
150
208
  };
151
209
  return (_jsx(Modal, { ...props, css: style, className: clsx(classes.root, props.className), open: innerOpen.current, onClosed: resetAll, maskProps: {
152
210
  ...props.maskProps,
211
+ ref: cloneRef(maskRef, props.maskProps?.ref),
153
212
  children: (_jsxs(_Fragment, { children: [_jsxs("div", { className: classes.control, children: [renderControl, showRotation &&
154
213
  _jsxs(_Fragment, { children: [_jsx(Tooltip, { title: "\u65CB\u8F6C-90\u00B0", children: _jsx(Button, { ...commonControlProps, onClick: rotateLeft, children: _jsx(Icon, { icon: faArrowRotateLeft }) }) }), _jsx(Tooltip, { title: "\u65CB\u8F6C90\u00B0", children: _jsx(Button, { ...commonControlProps, onClick: rotateRight, children: _jsx(Icon, { icon: faArrowRotateRight }) }) })] }), showZoom &&
155
214
  _jsxs(_Fragment, { children: [_jsx(Tooltip, { title: "\u7F29\u5C0F", children: _jsx(Button, { ...commonControlProps, onClick: zoomOut, children: _jsx(Icon, { icon: faMagnifyingGlassMinus }) }) }), _jsx(Tooltip, { title: "\u653E\u5927", children: _jsx(Button, { ...commonControlProps, onClick: zoomIn, children: _jsx(Icon, { icon: faMagnifyingGlassPlus }) }) }), _jsx(Tooltip, { title: "\u9002\u5E94\u5C4F\u5E55", children: _jsx(Button, { ...commonControlProps, onClick: reset, children: _jsx(Icon, { icon: faExpand }) }) })] }), showClose &&
156
215
  _jsx(Button, { ...commonControlProps, onClick: close, children: _jsx(Icon, { icon: faXmark }) })] }), srcArr.current.length > 1 &&
157
216
  _jsxs(_Fragment, { children: [_jsxs("div", { className: classes.swap, children: [_jsx(Button, { ...commonControlProps, onClick: goPrevLoop, children: _jsx(Icon, { icon: faAngleLeft }) }), _jsx(Button, { ...commonControlProps, onClick: goNextLoop, children: _jsx(Icon, { icon: faAngleRight }) })] }), _jsxs("div", { className: classes.counter, children: [innerIndex.current + 1, " / ", srcArr.current.length] })] })] }))
158
217
  }, children: srcArr.current.length > 0 &&
159
- _jsx("div", { className: classes.galleryContainer, ...draggableHandles, onDoubleClick: doubleClickHandler, children: _jsx("div", { ref: wrapperRef, className: classes.galleryWrapper, style: { left: -innerIndex.current * 100 + '%' }, onTransitionEnd: e => e.currentTarget.dataset.transition = '', children: srcArr.current.map((src, i) => _jsx(ImageItem, { ref: r => {
218
+ _jsx("div", { className: classes.galleryContainer, ...draggableHandles, onDoubleClick: doubleClickHandler, children: _jsx("div", { ref: wrapperRef, className: classes.galleryWrapper, style: { left: -innerIndex.current * 100 + '%' }, onTransitionEnd: clearTransition, children: srcArr.current.map((src, i) => _jsx(ImageItem, { ref: r => {
160
219
  r && imageItemRefs.current.push(r);
220
+ }, imgProps: {
221
+ ref: r => {
222
+ r && imgRefs.current.push(r);
223
+ }
161
224
  }, style: { left: i * 100 + '%' }, src: src }, i)) }) }) }));
162
225
  });
@@ -1,6 +1,6 @@
1
1
  import { css } from '@emotion/react';
2
2
  import { defineInnerClasses, defineCss } from '../../utils';
3
- export const classes = defineInnerClasses('image-preview', [
3
+ export const classes = defineInnerClasses('gallery', [
4
4
  'button',
5
5
  'control',
6
6
  'swap',
@@ -79,14 +79,14 @@ export const style = defineCss(({ spacing, easing, breakpoints }) => css `
79
79
  &, .${classes.imageItem} {
80
80
  position: absolute;
81
81
  top: 0;
82
- }
83
82
 
84
- &[data-transition=set] {
85
- transition: left .5s ${easing.bounce} 0s;
86
- }
83
+ &[data-transition=true] {
84
+ transition: all .3s ${easing.easeOut};
85
+ }
87
86
 
88
- &[data-transition=drag] {
89
- transition: left .1s linear;
87
+ &[data-transition=bounce] {
88
+ transition: all .25s ${easing.bounce};
89
+ }
90
90
  }
91
91
 
92
92
  .${classes.imageItem} {
@@ -105,10 +105,6 @@ export const style = defineCss(({ spacing, easing, breakpoints }) => css `
105
105
  }
106
106
 
107
107
  .${classes.galleryContainer} .${classes.galleryWrapper} {
108
- &[data-transition=true] {
109
- transition: left .4s ${easing.ease};
110
- }
111
-
112
108
  .${classes.imageItem} {
113
109
  padding: 0;
114
110
  }
@@ -1,7 +1,9 @@
1
- import { Ref } from 'react';
1
+ import { JSX, Ref } from 'react';
2
2
  import { PinchableProps, PinchableRef } from '../pinchable';
3
3
  export interface ImageItemRef extends PinchableRef {
4
4
  isFit(): {
5
+ top: boolean;
6
+ bottom: boolean;
5
7
  left: boolean;
6
8
  right: boolean;
7
9
  };
@@ -9,6 +11,7 @@ export interface ImageItemRef extends PinchableRef {
9
11
  interface ImageItemProps extends PinchableProps {
10
12
  ref?: Ref<ImageItemRef>;
11
13
  src?: string;
14
+ imgProps?: JSX.IntrinsicElements['img'];
12
15
  }
13
- export declare function ImageItem({ ref, src, ...props }: ImageItemProps): import("@emotion/react/jsx-runtime").JSX.Element;
16
+ export declare function ImageItem({ ref, src, imgProps, ...props }: ImageItemProps): import("@emotion/react/jsx-runtime").JSX.Element;
14
17
  export {};
@@ -1,15 +1,17 @@
1
1
  import { jsx as _jsx } from "@emotion/react/jsx-runtime";
2
2
  import { classes } from './gallery.style';
3
3
  import { useEffect, useImperativeHandle, useRef } from 'react';
4
- import { clsx } from '../../utils';
4
+ import { cloneRef, clsx } from '../../utils';
5
5
  import { Pinchable } from '../pinchable';
6
- export function ImageItem({ ref, src, ...props }) {
6
+ export function ImageItem({ ref, src, imgProps, ...props }) {
7
7
  useImperativeHandle(ref, () => {
8
8
  if (pinchableRef.current) {
9
9
  pinchableRef.current.isFit = () => {
10
- const { x: pinchableX, width: pinchableWidth } = pinchableRef.current.getBoundingClientRect();
11
- const { x: imgX, width: imgWidth } = imgRef.current.getBoundingClientRect();
10
+ const { x: pinchableX, y: pinchableY, width: pinchableWidth, height: pinchableHeight } = pinchableRef.current.getBoundingClientRect();
11
+ const { x: imgX, y: imgY, width: imgWidth, height: imgHeight } = imgRef.current.getBoundingClientRect();
12
12
  return {
13
+ top: imgY >= pinchableY,
14
+ bottom: imgY + imgHeight <= pinchableY + pinchableHeight,
13
15
  left: imgX >= pinchableX,
14
16
  right: imgX + imgWidth <= pinchableX + pinchableWidth
15
17
  };
@@ -44,5 +46,5 @@ export function ImageItem({ ref, src, ...props }) {
44
46
  resizeObserver.disconnect();
45
47
  };
46
48
  }, []);
47
- return (_jsx(Pinchable, { ...props, ref: pinchableRef, className: clsx(classes.imageItem, props.className), children: _jsx("img", { ref: imgRef, className: classes.image, src: src, draggable: false, alt: "" }) }));
49
+ return (_jsx(Pinchable, { ...props, ref: pinchableRef, className: clsx(classes.imageItem, props.className), allowEdgeBounce: false, children: _jsx("img", { draggable: false, alt: "", src: src, ...imgProps, ref: cloneRef(imgRef, imgProps?.ref), className: clsx(classes.image, imgProps?.className) }) }));
48
50
  }
@@ -1 +1,2 @@
1
1
  export * from './gallery';
2
+ export * from './imageItem';
@@ -1 +1,2 @@
1
1
  export * from './gallery';
2
+ export * from './imageItem';
@@ -22,11 +22,17 @@ type PinchableOwnProps = {
22
22
  translate?: [number, number];
23
23
  onTranslateChange?(translate: [number, number]): void;
24
24
  /** 平移限制,`translate`非受控模式下有效 */
25
- translateLimit?: (targetX: number, targetY: number, scale: number) => [number, number];
25
+ translateLimit?: (targetX: number, targetY: number, scale: number, rotateDeg: number) => [number, number];
26
26
  defaultRotate?: number;
27
27
  rotate?: number;
28
28
  onRotateChange?(rotate: number): void;
29
29
  children?: ReactElement;
30
+ /** 拖拽至最边缘时,是否允许弹性溢出,默认为`true` */
31
+ allowEdgeBounce?: boolean;
32
+ /** 元素弹性移动距离,默认为24 */
33
+ bounceElementTranslate?: number;
34
+ /** 手指弹性拖拽距离,默认为240 */
35
+ bounceDragDistance?: number;
30
36
  };
31
37
  export type PinchableProps<C extends ElementType = 'div'> = OverridableProps<PinchableOwnProps, C>;
32
38
  export declare const Pinchable: <C extends ElementType = "div">(props: PinchableProps<C>) => ReactElement;
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "@emotion/react/jsx-runtime";
2
2
  import { cloneElement, useImperativeHandle, useRef } from 'react';
3
- import { cloneRef, clsx, range, useControlled, useGesture } from '../../utils';
3
+ import { cloneRef, clsx, edgeBounce, range, useControlled, useGesture } from '../../utils';
4
4
  import { classes, style } from './pinchable.style';
5
- export const Pinchable = (({ component: Component = 'div', ref, gestureOptions, defaultScale = 1, scale, onScaleChange, scaleLimit = [1, 6], defaultTranslate = [0, 0], translate, onTranslateChange, translateLimit, defaultRotate = 0, rotate, onRotateChange, children, ...props }) => {
5
+ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions, defaultScale = 1, scale, onScaleChange, scaleLimit = [1, 6], defaultTranslate = [0, 0], translate, onTranslateChange, translateLimit, defaultRotate = 0, rotate, onRotateChange, children, allowEdgeBounce = true, bounceElementTranslate = 24, bounceDragDistance = 240, ...props }) => {
6
6
  useImperativeHandle(ref, () => {
7
7
  if (wrapperRef.current) {
8
8
  wrapperRef.current.zoomIn = () => {
@@ -17,7 +17,7 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
17
17
  rotateFn(innerRotate.current - 90);
18
18
  };
19
19
  wrapperRef.current.rotateRight = () => {
20
- rotateFn(innerRotate.current - 90);
20
+ rotateFn(innerRotate.current + 90);
21
21
  };
22
22
  wrapperRef.current.reset = resetAll;
23
23
  }
@@ -38,6 +38,12 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
38
38
  onDrag(info, e) {
39
39
  gestureOptions?.onDrag?.(info, e);
40
40
  const { diff: [dx, dy], data: [startX, startY] } = info;
41
+ setInnerTranslate(translateLimitFn(startX + dx, startY + dy, void 0, void 0, true));
42
+ },
43
+ onDragEnd(info) {
44
+ gestureOptions?.onDragEnd?.(info);
45
+ const { diff: [dx, dy], data: [startX, startY] } = info;
46
+ allowTransition('bounce');
41
47
  setInnerTranslate(translateLimitFn(startX + dx, startY + dy));
42
48
  },
43
49
  onPinchStart(info) {
@@ -75,9 +81,9 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
75
81
  if (!children) {
76
82
  return;
77
83
  }
78
- const translateLimitFn = (x, y, scale = innerScale.current, deg = innerRotate.current) => {
84
+ const translateLimitFn = (x, y, scale = innerScale.current, deg = innerRotate.current, bounce = false) => {
79
85
  if (translateLimit) {
80
- return translateLimit(x, y, scale);
86
+ return translateLimit(x, y, scale, deg);
81
87
  }
82
88
  const { clientWidth: wrapperWidth, clientHeight: wrapperHeight } = wrapperRef.current;
83
89
  const { offsetWidth, offsetHeight } = contentRef.current;
@@ -86,14 +92,20 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
86
92
  const contentHeight = (isRotated ? offsetWidth : offsetHeight) * scale;
87
93
  const limitX = contentWidth > wrapperWidth ? (contentWidth - wrapperWidth) / 2 : 0;
88
94
  const limitY = contentHeight > wrapperHeight ? (contentHeight - wrapperHeight) / 2 : 0;
95
+ if (!bounce) {
96
+ return [
97
+ range(x, -limitX, limitX),
98
+ range(y, -limitY, limitY)
99
+ ];
100
+ }
89
101
  return [
90
- range(x, -limitX, limitX),
91
- range(y, -limitY, limitY)
102
+ edgeBounce(x, { min: -limitX, max: limitX, allowEdgeBounce, bounceElementTranslate, bounceDragDistance }),
103
+ edgeBounce(y, { min: -limitY, max: limitY, allowEdgeBounce, bounceElementTranslate, bounceDragDistance })
92
104
  ];
93
105
  };
94
106
  const childrenProps = children.props;
95
- const allowTransition = () => {
96
- contentRef.current && (contentRef.current.dataset.transition = 'true');
107
+ const allowTransition = (transitionType = 'true') => {
108
+ contentRef.current && (contentRef.current.dataset.transition = transitionType);
97
109
  };
98
110
  const zoomFn = (targetScale, originX, originY) => {
99
111
  allowTransition();
@@ -147,7 +159,7 @@ export const Pinchable = (({ component: Component = 'div', ref, gestureOptions,
147
159
  : zoomFn(innerScale.current * 1.2, e.clientX, e.clientY);
148
160
  };
149
161
  const onTransitionEnd = (e) => {
150
- e.currentTarget.dataset.transition = 'false';
162
+ e.currentTarget.dataset.transition = '';
151
163
  setInnerRotate(innerRotate.current % 360);
152
164
  };
153
165
  return (_jsx(Component, { ...props, ref: wrapperRef, css: style, className: clsx(classes.root, props.className), ...pinchableHandles, children: cloneElement(children, {
@@ -13,8 +13,14 @@ export const style = defineCss(({ easing }) => css `
13
13
  touch-action: none;
14
14
  }
15
15
 
16
- .${classes.content}[data-transition=true] {
17
- transition: all .25s ${easing.easeOut};
16
+ .${classes.content} {
17
+ &[data-transition=true] {
18
+ transition: all .25s ${easing.easeOut};
19
+ }
20
+
21
+ &[data-transition=bounce] {
22
+ transition: all .25s ${easing.bounce};
23
+ }
18
24
  }
19
25
  }
20
26
  `);
@@ -1,13 +1,14 @@
1
+ export type BezierFunc = (x: number) => number;
1
2
  /**
2
3
  * 创建一个Bezier曲线函数,默认从(0,0)到(1,1)
3
4
  * @param p1x
4
5
  * @param p1y
5
6
  * @param p2x
6
7
  * @param p2y
7
- * @returns {(x: number) => number} 返回一个函数,接收x返回y
8
+ * @returns {BezierFunc} 返回一个函数,接收x返回y
8
9
  */
9
- export declare function cubicBezier(p1x: number, p1y: number, p2x: number, p2y: number): (x: number) => number;
10
+ export declare function cubicBezier(p1x: number, p1y: number, p2x: number, p2y: number): BezierFunc;
10
11
  export declare namespace cubicBezier {
11
- var preset: (preset: Preset) => (x: number) => number;
12
+ var preset: (preset: Preset) => BezierFunc;
12
13
  }
13
14
  export type Preset = 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
@@ -4,7 +4,7 @@
4
4
  * @param p1y
5
5
  * @param p2x
6
6
  * @param p2y
7
- * @returns {(x: number) => number} 返回一个函数,接收x返回y
7
+ * @returns {BezierFunc} 返回一个函数,接收x返回y
8
8
  */
9
9
  export function cubicBezier(p1x, p1y, p2x, p2y) {
10
10
  const ZERO_LIMIT = 1e-6;
@@ -1,6 +1,7 @@
1
1
  import { DragEndEvent } from '@dnd-kit/core';
2
2
  import { Id, Obj } from '../types';
3
3
  import { NodeType, SortInfo } from '../components/tree';
4
+ import { BezierFunc } from './bezier';
4
5
  /**
5
6
  * 默认提供给@dnd-kit的sensors属性
6
7
  */
@@ -19,3 +20,11 @@ export declare function onDndDragEnd<S extends Obj>(e: DragEndEvent, prevState:
19
20
  * @param primaryKey 索引用的主键,默认为`id`
20
21
  */
21
22
  export declare function onTreeNodeSort<N extends NodeType<V>, V extends Id = Id>(info: SortInfo<V>, treeNodes: N[], primaryKey?: string): N[];
23
+ export declare function edgeBounce(value: number, { allowEdgeBounce, min, max, bounceElementTranslate, bounceDragDistance, bezierFn }: {
24
+ allowEdgeBounce: boolean;
25
+ min: number;
26
+ max: number;
27
+ bounceElementTranslate: number;
28
+ bounceDragDistance: number;
29
+ bezierFn?: BezierFunc;
30
+ }): number;
@@ -1,5 +1,7 @@
1
1
  import { PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
2
2
  import { arrayMove } from '@dnd-kit/sortable';
3
+ import { range } from './utils';
4
+ import { cubicBezier } from './bezier';
3
5
  /**
4
6
  * 默认提供给@dnd-kit的sensors属性
5
7
  */
@@ -68,3 +70,16 @@ export function onTreeNodeSort(info, treeNodes, primaryKey = 'id') {
68
70
  }
69
71
  return treeNodes;
70
72
  }
73
+ export function edgeBounce(value, { allowEdgeBounce, min, max, bounceElementTranslate, bounceDragDistance, bezierFn = cubicBezier(0, 0, 0, 1) }) {
74
+ if (allowEdgeBounce && bounceElementTranslate && bounceDragDistance > 0) {
75
+ value = range(value, min - bounceDragDistance, max + bounceDragDistance);
76
+ if (value < min) {
77
+ value = min - bezierFn((min - value) / bounceDragDistance) * bounceElementTranslate;
78
+ }
79
+ else if (value > max) {
80
+ value = max + bezierFn((value - max) / bounceDragDistance) * bounceElementTranslate;
81
+ }
82
+ return value;
83
+ }
84
+ return range(value, min, max);
85
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canlooks/can-ui",
3
- "version": "0.0.120",
3
+ "version": "0.0.122",
4
4
  "author": "C.CanLiang <canlooks@gmail.com>",
5
5
  "description": "My ui framework",
6
6
  "license": "MIT",