1
This commit is contained in:
+9
@@ -0,0 +1,9 @@
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
# rc-image
|
||||
|
||||
React Image.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
[![NPM version][npm-image]][npm-url]
|
||||
[![npm download][download-image]][download-url]
|
||||
[![build status][github-actions-image]][github-actions-url]
|
||||
[![Codecov][codecov-image]][codecov-url]
|
||||
[![bundle size][bundlephobia-image]][bundlephobia-url]
|
||||
[![dumi][dumi-image]][dumi-url]
|
||||
|
||||
[npm-image]: http://img.shields.io/npm/v/rc-image.svg?style=flat-square
|
||||
[npm-url]: http://npmjs.org/package/rc-image
|
||||
[github-actions-image]: https://github.com/react-component/image/actions/workflows/react-component-ci.yml/badge.svg
|
||||
[github-actions-url]: https://github.com/react-component/image/actions/workflows/react-component-ci.yml
|
||||
[codecov-image]: https://img.shields.io/codecov/c/gh/react-component/image?style=flat-square
|
||||
[codecov-url]: https://codecov.io/gh/react-component/image
|
||||
[download-image]: https://img.shields.io/npm/dm/rc-image.svg?style=flat-square
|
||||
[download-url]: https://npmjs.org/package/rc-image
|
||||
[bundlephobia-url]: https://bundlephobia.com/result?p=rc-image
|
||||
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-image
|
||||
[dumi-url]: https://github.com/umijs/dumi
|
||||
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
|
||||
|
||||
## Feature
|
||||
|
||||
- [x] Placeholder
|
||||
- [x] Preview
|
||||
- [x] Rotate
|
||||
- [x] Zoom
|
||||
- [x] Flip
|
||||
- [x] Fallback
|
||||
- [x] Multiple Preview
|
||||
|
||||
## install
|
||||
|
||||
[](https://npmjs.org/package/rc-image)
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
```js
|
||||
import Image from '@rc-component/image';
|
||||
|
||||
export default () => (
|
||||
<Image src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" />
|
||||
);
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| preview | boolean \| [PreviewType](#PreviewType) | true | Whether to show preview |
|
||||
| prefixCls | string | rc-image | Classname prefix |
|
||||
| placeholder | boolean \| ReactElement | - | if `true` will set default placeholder or use `ReactElement` set customize placeholder |
|
||||
| fallback | string | - | Load failed src |
|
||||
| previewPrefixCls | string | rc-image-preview | Preview classname prefix |
|
||||
| onError | (event: Event) => void | - | Load failed callback |
|
||||
|
||||
### PreviewType
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| open | boolean | - | Whether the preview is open or not |
|
||||
| closeIcon | React.ReactNode | - | Custom close icon |
|
||||
| maskClosable | boolean | true | Whether to close preview on clicking mask |
|
||||
| src | string | - | Customize preview src |
|
||||
| movable | boolean | true | Enable drag |
|
||||
| scaleStep | number | 0.5 | The number to which the scale is increased or decreased |
|
||||
| minScale | number | 1 | Min scale |
|
||||
| maxScale | number | 50 | Max scale |
|
||||
| forceRender | boolean | - | Force render preview |
|
||||
| getContainer | string \| HTMLElement \| (() => HTMLElement) \| false | document.body | Return the mount node for preview |
|
||||
| imageRender | (originalNode: React.ReactElement, info: { transform: [TransformType](#TransformType) }) => React.ReactNode | - | Customize image |
|
||||
| actionsRender | (originalNode: React.ReactElement, info: Omit<[ToolbarRenderInfoType](#ToolbarRenderInfoType), 'current' \| 'total'>) => React.ReactNode | - | Customize toolbar |
|
||||
| onOpenChange | (open: boolean, prevVisible: boolean) => void | - | Callback when open is changed |
|
||||
| onTransform | { transform: [TransformType](#TransformType), action: [TransformAction](#TransformAction) } | - | Callback when transform is changed |
|
||||
|
||||
## Image.PreviewGroup
|
||||
|
||||
preview the merged src
|
||||
|
||||
```js
|
||||
import Image from '@rc-component/image';
|
||||
|
||||
export default () => (
|
||||
<Image.PreviewGroup>
|
||||
<Image src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" />
|
||||
<Image src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*P0S-QIRUbsUAAAAAAAAAAABkARQnAQ" />
|
||||
</Image.PreviewGroup>
|
||||
);
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| preview | boolean \| [PreviewGroupType](#PreviewGroupType) | true | Whether to show preview, <br> current: If Preview the show img index, default 0 |
|
||||
| previewPrefixCls | string | rc-image-preview | Preview classname prefix |
|
||||
| icons | { [iconKey]?: ReactNode } | - | Icons in the top operation bar, iconKey: 'rotateLeft' \| 'rotateRight' \| 'zoomIn' \| 'zoomOut' \| 'close' \| 'left' \| 'right' |
|
||||
| fallback | string | - | Load failed src |
|
||||
| items | (string \| { src: string, alt: string, crossOrigin: string, ... })[] | - | preview group |
|
||||
|
||||
### PreviewGroupType
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| open | boolean | - | Whether the preview is open or not |
|
||||
| movable | boolean | true | Enable drag |
|
||||
| current | number | - | Current index |
|
||||
| closeIcon | React.ReactNode | - | Custom close icon |
|
||||
| maskClosable | boolean | true | Whether to close preview on clicking mask |
|
||||
| scaleStep | number | 0.5 | The number to which the scale is increased or decreased |
|
||||
| minScale | number | 1 | Min scale |
|
||||
| maxScale | number | 50 | Max scale |
|
||||
| forceRender | boolean | - | Force render preview |
|
||||
| getContainer | string \| HTMLElement \| (() => HTMLElement) \| false | document.body | Return the mount node for preview |
|
||||
| countRender | (current: number, total: number) => ReactNode | - | Customize count |
|
||||
| imageRender | (originalNode: React.ReactElement, info: { transform: [TransformType](#TransformType), current: number }) => React.ReactNode | - | Customize image |
|
||||
| actionsRender | (originalNode: React.ReactElement, info: [ToolbarRenderInfoType](#ToolbarRenderInfoType)) => React.ReactNode | - | Customize toolbar |
|
||||
| onOpenChange | (open: boolean, prevVisible: boolean, current: number) => void | - | Callback when open is changed |
|
||||
| onTransform | { transform: [TransformType](#TransformType), action: [TransformAction](#TransformAction) } | - | Callback when transform is changed |
|
||||
|
||||
### TransformType
|
||||
|
||||
```typescript
|
||||
{
|
||||
x: number;
|
||||
y: number;
|
||||
rotate: number;
|
||||
scale: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### TransformAction
|
||||
|
||||
```typescript
|
||||
type TransformAction =
|
||||
| 'flipY'
|
||||
| 'flipX'
|
||||
| 'rotateLeft'
|
||||
| 'rotateRight'
|
||||
| 'zoomIn'
|
||||
| 'zoomOut'
|
||||
| 'close'
|
||||
| 'prev'
|
||||
| 'next'
|
||||
| 'wheel'
|
||||
| 'doubleClick'
|
||||
| 'move'
|
||||
| 'dragRebound';
|
||||
```
|
||||
|
||||
### ToolbarRenderInfoType
|
||||
|
||||
```typescript
|
||||
{
|
||||
icons: {
|
||||
prevIcon?: React.ReactNode;
|
||||
nextIcon?: React.ReactNode;
|
||||
flipYIcon: React.ReactNode;
|
||||
flipXIcon: React.ReactNode;
|
||||
rotateLeftIcon: React.ReactNode;
|
||||
rotateRightIcon: React.ReactNode;
|
||||
zoomOutIcon: React.ReactNode;
|
||||
zoomInIcon: React.ReactNode;
|
||||
};
|
||||
actions: {
|
||||
onActive?: (offset: number) => void;
|
||||
onFlipY: () => void;
|
||||
onFlipX: () => void;
|
||||
onRotateLeft: () => void;
|
||||
onRotateRight: () => void;
|
||||
onZoomOut: () => void;
|
||||
onZoomIn: () => void;
|
||||
onClose: () => void;
|
||||
onReset: () => void;
|
||||
};
|
||||
transform: {
|
||||
x: number;
|
||||
y: number;
|
||||
rotate: number;
|
||||
scale: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
},
|
||||
current: number;
|
||||
total: number;
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
http://localhost:8003/examples/
|
||||
|
||||
## Test Case
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
## Coverage
|
||||
|
||||
```
|
||||
npm run coverage
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
rc-image is released under the MIT license.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
<a href="https://openomy.app/github/react-component/image" target="_blank" style="display: block; width: 100%;" align="center">
|
||||
<img src="https://www.openomy.app/svg?repo=react-component/image&chart=bubble&latestMonth=24" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" />
|
||||
</a>
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
.rc-image-preview {
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
inset: 0;
|
||||
z-index: 1055;
|
||||
}
|
||||
.rc-image-preview-mask {
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
inset: 0;
|
||||
}
|
||||
.rc-image-preview-body {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
inset: 0;
|
||||
}
|
||||
.rc-image-preview-body > * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.rc-image-preview-img {
|
||||
max-width: 100%;
|
||||
max-height: 70%;
|
||||
}
|
||||
.rc-image-preview-close {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 0;
|
||||
border-radius: 99px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rc-image-preview-switch {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 0;
|
||||
padding: 0;
|
||||
border-radius: 9999px;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
.rc-image-preview-switch-disabled {
|
||||
cursor: default;
|
||||
opacity: 0.1;
|
||||
}
|
||||
.rc-image-preview-switch-prev {
|
||||
inset-inline-start: 0;
|
||||
}
|
||||
.rc-image-preview-switch-next {
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
.rc-image-preview-footer {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.rc-image-preview-progress {
|
||||
color: #fff;
|
||||
}
|
||||
.rc-image-preview-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 12px;
|
||||
}
|
||||
.rc-image-preview-actions-action {
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
}
|
||||
.rc-image-preview-actions-action-disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.rc-image-preview.fade {
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.rc-image-preview.fade-enter,
|
||||
.rc-image-preview.fade-appear {
|
||||
opacity: 0;
|
||||
}
|
||||
.rc-image-preview.fade-enter .rc-image-preview-body,
|
||||
.rc-image-preview.fade-appear .rc-image-preview-body {
|
||||
transform: scale(0);
|
||||
}
|
||||
.rc-image-preview.fade-enter-active,
|
||||
.rc-image-preview.fade-appear-active {
|
||||
opacity: 1;
|
||||
}
|
||||
.rc-image-preview.fade-enter-active .rc-image-preview-body,
|
||||
.rc-image-preview.fade-appear-active .rc-image-preview-body {
|
||||
transform: scale(1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.rc-image-preview.fade-leave {
|
||||
opacity: 1;
|
||||
}
|
||||
.rc-image-preview.fade-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
.rc-image {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.rc-image-img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rc-image-img-placeholder {
|
||||
background-color: #f3f3f3;
|
||||
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjhweCIgaGVpZ2h0PSIyMnB4IiB2aWV3Qm94PSIwIDAgMjggMjIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjIgKDc4MTgxKSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5pbWFnZS1maWxs5aSH5Lu9PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGcgaWQ9Iuafpeeci+WbvueJh+S8mOWMljQuMCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IuWKoOi9veWbvueJhyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU3Mi4wMDAwMDAsIC01MDYuMDAwMDAwKSI+CiAgICAgICAgICAgIDxnIGlkPSJpbWFnZS1maWxs5aSH5Lu9IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1NzAuMDAwMDAwLCA1MDEuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8cmVjdCBpZD0iUmVjdGFuZ2xlIiBmaWxsPSIjMDAwMDAwIiBvcGFjaXR5PSIwIiB4PSIwIiB5PSIwIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yOSw1IEwzLDUgQzIuNDQ2ODc1LDUgMiw1LjQ0Njg3NSAyLDYgTDIsMjYgQzIsMjYuNTUzMTI1IDIuNDQ2ODc1LDI3IDMsMjcgTDI5LDI3IEMyOS41NTMxMjUsMjcgMzAsMjYuNTUzMTI1IDMwLDI2IEwzMCw2IEMzMCw1LjQ0Njg3NSAyOS41NTMxMjUsNSAyOSw1IFogTTEwLjU2MjUsOS41IEMxMS42NjU2MjUsOS41IDEyLjU2MjUsMTAuMzk2ODc1IDEyLjU2MjUsMTEuNSBDMTIuNTYyNSwxMi42MDMxMjUgMTEuNjY1NjI1LDEzLjUgMTAuNTYyNSwxMy41IEM5LjQ1OTM3NSwxMy41IDguNTYyNSwxMi42MDMxMjUgOC41NjI1LDExLjUgQzguNTYyNSwxMC4zOTY4NzUgOS40NTkzNzUsOS41IDEwLjU2MjUsOS41IFogTTI2LjYyMTg3NSwyMy4xNTkzNzUgQzI2LjU3ODEyNSwyMy4xOTY4NzUgMjYuNTE4NzUsMjMuMjE4NzUgMjYuNDU5Mzc1LDIzLjIxODc1IEw1LjUzNzUsMjMuMjE4NzUgQzUuNCwyMy4yMTg3NSA1LjI4NzUsMjMuMTA2MjUgNS4yODc1LDIyLjk2ODc1IEM1LjI4NzUsMjIuOTA5Mzc1IDUuMzA5Mzc1LDIyLjg1MzEyNSA1LjM0Njg3NSwyMi44MDYyNSBMMTAuNjY4NzUsMTYuNDkzNzUgQzEwLjc1NjI1LDE2LjM4NzUgMTAuOTE1NjI1LDE2LjM3NSAxMS4wMjE4NzUsMTYuNDYyNSBDMTEuMDMxMjUsMTYuNDcxODc1IDExLjA0Mzc1LDE2LjQ4MTI1IDExLjA1MzEyNSwxNi40OTM3NSBMMTQuMTU5Mzc1LDIwLjE4MTI1IEwxOS4xLDE0LjMyMTg3NSBDMTkuMTg3NSwxNC4yMTU2MjUgMTkuMzQ2ODc1LDE0LjIwMzEyNSAxOS40NTMxMjUsMTQuMjkwNjI1IEMxOS40NjI1LDE0LjMgMTkuNDc1LDE0LjMwOTM3NSAxOS40ODQzNzUsMTQuMzIxODc1IEwyNi42NTkzNzUsMjIuODA5Mzc1IEMyNi43NDA2MjUsMjIuOTEyNSAyNi43MjgxMjUsMjMuMDcxODc1IDI2LjYyMTg3NSwyMy4xNTkzNzUgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjRThFOEU4Ij48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
.rc-image-cover {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.rc-image-cover-top {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: max-content;
|
||||
justify-content: flex-start;
|
||||
padding: 5px;
|
||||
}
|
||||
.rc-image-cover-bottom {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: max-content;
|
||||
justify-content: flex-end;
|
||||
padding: 5px;
|
||||
}
|
||||
.rc-image-cover-center {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.rc-image-placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
.rc-image-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.rc-image:hover .rc-image-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
@keyframes rcImageZoomIn {
|
||||
0% {
|
||||
transform: scale(0.2);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes rcImageZoomOut {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import * as React from 'react';
|
||||
import type { InternalPreviewConfig, PreviewSemanticName, ToolbarRenderInfoType } from './Preview';
|
||||
import PreviewGroup from './PreviewGroup';
|
||||
import type { TransformType } from './hooks/useImageTransform';
|
||||
export interface ImgInfo {
|
||||
url: string;
|
||||
alt: string;
|
||||
width: string | number;
|
||||
height: string | number;
|
||||
}
|
||||
export interface CoverConfig {
|
||||
coverNode?: React.ReactNode;
|
||||
placement?: 'top' | 'bottom' | 'center';
|
||||
}
|
||||
export interface PreviewConfig extends Omit<InternalPreviewConfig, 'countRender'> {
|
||||
cover?: React.ReactNode | CoverConfig;
|
||||
imageRender?: (originalNode: React.ReactElement, info: {
|
||||
transform: TransformType;
|
||||
image: ImgInfo;
|
||||
}) => React.ReactNode;
|
||||
actionsRender?: (originalNode: React.ReactElement, info: Omit<ToolbarRenderInfoType, 'current' | 'total'>) => React.ReactNode;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
export type SemanticName = 'root' | 'image' | 'cover';
|
||||
export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'placeholder' | 'onClick' | 'onKeyDown'> {
|
||||
prefixCls?: string;
|
||||
previewPrefixCls?: string;
|
||||
rootClassName?: string;
|
||||
classNames?: Partial<Record<SemanticName, string> & {
|
||||
popup?: Partial<Record<PreviewSemanticName, string>>;
|
||||
}>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties> & {
|
||||
popup?: Partial<Record<PreviewSemanticName, React.CSSProperties>>;
|
||||
}>;
|
||||
src?: string;
|
||||
placeholder?: React.ReactNode;
|
||||
fallback?: string;
|
||||
preview?: boolean | PreviewConfig;
|
||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
onError?: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
interface CompoundedComponent<P> extends React.FC<P> {
|
||||
PreviewGroup: typeof PreviewGroup;
|
||||
}
|
||||
declare const ImageInternal: CompoundedComponent<ImageProps>;
|
||||
export default ImageInternal;
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import useControlledState from "@rc-component/util/es/hooks/useControlledState";
|
||||
import { clsx } from 'clsx';
|
||||
import * as React from 'react';
|
||||
import { useContext, useMemo, useState } from 'react';
|
||||
import Preview from "./Preview";
|
||||
import PreviewGroup from "./PreviewGroup";
|
||||
import { COMMON_PROPS } from "./common";
|
||||
import { PreviewGroupContext } from "./context";
|
||||
import useRegisterImage from "./hooks/useRegisterImage";
|
||||
import useStatus from "./hooks/useStatus";
|
||||
const ImageInternal = props => {
|
||||
const {
|
||||
// Misc
|
||||
prefixCls = 'rc-image',
|
||||
previewPrefixCls = `${prefixCls}-preview`,
|
||||
// Style
|
||||
rootClassName,
|
||||
className,
|
||||
style,
|
||||
classNames = {},
|
||||
styles = {},
|
||||
width,
|
||||
height,
|
||||
// Image
|
||||
src: imgSrc,
|
||||
alt,
|
||||
placeholder,
|
||||
fallback,
|
||||
// Preview
|
||||
preview = true,
|
||||
// Events
|
||||
onClick,
|
||||
onError,
|
||||
onKeyDown,
|
||||
...otherProps
|
||||
} = props;
|
||||
const groupContext = useContext(PreviewGroupContext);
|
||||
|
||||
// ========================== Preview ===========================
|
||||
const canPreview = !!preview;
|
||||
const {
|
||||
src: previewSrc,
|
||||
open: previewOpen,
|
||||
onOpenChange: onPreviewOpenChange,
|
||||
cover,
|
||||
rootClassName: previewRootClassName,
|
||||
...restProps
|
||||
} = preview && typeof preview === 'object' ? preview : {};
|
||||
const coverPlacement = typeof cover === 'object' && cover.placement ? cover.placement || 'center' : 'center';
|
||||
const coverNode = typeof cover === 'object' && cover.coverNode ? cover.coverNode : cover;
|
||||
|
||||
// ============================ Open ============================
|
||||
const [isShowPreview, setShowPreview] = useControlledState(!!previewOpen, previewOpen);
|
||||
const [mousePosition, setMousePosition] = useState(null);
|
||||
const triggerPreviewOpen = nextOpen => {
|
||||
setShowPreview(nextOpen);
|
||||
onPreviewOpenChange?.(nextOpen);
|
||||
};
|
||||
const onPreviewClose = () => {
|
||||
triggerPreviewOpen(false);
|
||||
};
|
||||
|
||||
// ========================= ImageProps =========================
|
||||
const isCustomPlaceholder = placeholder && placeholder !== true;
|
||||
const src = previewSrc ?? imgSrc;
|
||||
const [getImgRef, srcAndOnload, status] = useStatus({
|
||||
src: imgSrc,
|
||||
isCustomPlaceholder,
|
||||
fallback
|
||||
});
|
||||
const imgCommonProps = useMemo(() => {
|
||||
const obj = {};
|
||||
COMMON_PROPS.forEach(prop => {
|
||||
if (props[prop] !== undefined) {
|
||||
obj[prop] = props[prop];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}, COMMON_PROPS.map(prop => props[prop]));
|
||||
|
||||
// ========================== Register ==========================
|
||||
const registerData = useMemo(() => ({
|
||||
...imgCommonProps,
|
||||
src
|
||||
}), [src, imgCommonProps]);
|
||||
const imageId = useRegisterImage(canPreview, registerData);
|
||||
|
||||
// ========================== Preview ===========================
|
||||
const onPreview = e => {
|
||||
const rect = e.target.getBoundingClientRect();
|
||||
const left = rect.x + rect.width / 2;
|
||||
const top = rect.y + rect.height / 2;
|
||||
if (groupContext) {
|
||||
groupContext.onPreview(imageId, src, left, top);
|
||||
} else {
|
||||
setMousePosition({
|
||||
x: left,
|
||||
y: top
|
||||
});
|
||||
triggerPreviewOpen(true);
|
||||
}
|
||||
onClick?.(e);
|
||||
};
|
||||
|
||||
// ======================= Keyboard Preview =====================
|
||||
const onPreviewKeyDown = event => {
|
||||
onKeyDown?.(event);
|
||||
if (!canPreview) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const left = rect.x + rect.width / 2;
|
||||
const top = rect.y + rect.height / 2;
|
||||
if (groupContext) {
|
||||
groupContext.onPreview(imageId, src, left, top);
|
||||
} else {
|
||||
setMousePosition({
|
||||
x: left,
|
||||
y: top
|
||||
});
|
||||
triggerPreviewOpen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// =========================== Render ===========================
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", _extends({}, otherProps, {
|
||||
className: clsx(prefixCls, rootClassName, classNames.root, {
|
||||
[`${prefixCls}-error`]: status === 'error'
|
||||
}),
|
||||
onClick: canPreview ? onPreview : onClick,
|
||||
role: canPreview ? 'button' : otherProps.role,
|
||||
tabIndex: canPreview && otherProps.tabIndex == null ? 0 : otherProps.tabIndex,
|
||||
"aria-label": canPreview ? otherProps['aria-label'] ?? alt : otherProps['aria-label'],
|
||||
onKeyDown: onPreviewKeyDown,
|
||||
style: {
|
||||
width,
|
||||
height,
|
||||
...styles.root
|
||||
}
|
||||
}), /*#__PURE__*/React.createElement("img", _extends({}, imgCommonProps, {
|
||||
className: clsx(`${prefixCls}-img`, {
|
||||
[`${prefixCls}-img-placeholder`]: placeholder === true
|
||||
}, classNames.image, className),
|
||||
style: {
|
||||
height,
|
||||
...styles.image,
|
||||
...style
|
||||
},
|
||||
ref: getImgRef
|
||||
}, srcAndOnload, {
|
||||
width: width,
|
||||
height: height,
|
||||
onError: onError
|
||||
})), status === 'loading' && /*#__PURE__*/React.createElement("div", {
|
||||
"aria-hidden": "true",
|
||||
className: `${prefixCls}-placeholder`
|
||||
}, placeholder), cover !== false && canPreview && /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-cover`, classNames.cover, `${prefixCls}-cover-${coverPlacement}`),
|
||||
style: {
|
||||
display: style?.display === 'none' ? 'none' : undefined,
|
||||
...styles.cover
|
||||
}
|
||||
}, coverNode)), !groupContext && canPreview && /*#__PURE__*/React.createElement(Preview, _extends({
|
||||
"aria-hidden": !isShowPreview,
|
||||
open: isShowPreview,
|
||||
prefixCls: previewPrefixCls,
|
||||
onClose: onPreviewClose,
|
||||
mousePosition: mousePosition,
|
||||
src: src,
|
||||
alt: alt,
|
||||
imageInfo: {
|
||||
width,
|
||||
height
|
||||
},
|
||||
fallback: fallback,
|
||||
imgCommonProps: imgCommonProps
|
||||
}, restProps, {
|
||||
classNames: classNames?.popup,
|
||||
styles: styles?.popup,
|
||||
rootClassName: clsx(previewRootClassName, rootClassName)
|
||||
})));
|
||||
};
|
||||
ImageInternal.PreviewGroup = PreviewGroup;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
ImageInternal.displayName = 'Image';
|
||||
}
|
||||
export default ImageInternal;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
export interface CloseBtnProps {
|
||||
prefixCls: string;
|
||||
icon?: React.ReactNode;
|
||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
export default function CloseBtn(props: CloseBtnProps): React.JSX.Element;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { clsx } from 'clsx';
|
||||
import * as React from 'react';
|
||||
export default function CloseBtn(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
icon,
|
||||
onClick,
|
||||
className,
|
||||
style
|
||||
} = props;
|
||||
return /*#__PURE__*/React.createElement("button", {
|
||||
className: clsx(`${prefixCls}-close`, className),
|
||||
style: style,
|
||||
onClick: onClick
|
||||
}, icon);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import type { Actions, PreviewProps } from '.';
|
||||
import type { ImgInfo } from '../Image';
|
||||
import type { TransformType } from '../hooks/useImageTransform';
|
||||
export type FooterSemanticName = 'footer' | 'actions';
|
||||
export interface FooterProps extends Actions {
|
||||
prefixCls: string;
|
||||
showProgress: boolean;
|
||||
countRender?: PreviewProps['countRender'];
|
||||
actionsRender?: PreviewProps['actionsRender'];
|
||||
current: number;
|
||||
count: number;
|
||||
showSwitch: boolean;
|
||||
icons: PreviewProps['icons'];
|
||||
scale: number;
|
||||
minScale: number;
|
||||
maxScale: number;
|
||||
image: ImgInfo;
|
||||
transform: TransformType;
|
||||
classNames: Partial<Record<FooterSemanticName, string>>;
|
||||
styles: Partial<Record<FooterSemanticName, React.CSSProperties>>;
|
||||
}
|
||||
export default function Footer(props: FooterProps): React.JSX.Element;
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
import { clsx } from 'clsx';
|
||||
import * as React from 'react';
|
||||
export default function Footer(props) {
|
||||
// 修改解构,添加缺失的属性,并提供默认值
|
||||
const {
|
||||
prefixCls,
|
||||
showProgress,
|
||||
current,
|
||||
count,
|
||||
showSwitch,
|
||||
// Style
|
||||
classNames,
|
||||
styles,
|
||||
// render
|
||||
icons,
|
||||
image,
|
||||
transform,
|
||||
countRender,
|
||||
actionsRender,
|
||||
// Scale
|
||||
scale,
|
||||
minScale,
|
||||
maxScale,
|
||||
// Actions
|
||||
onActive,
|
||||
onFlipY,
|
||||
onFlipX,
|
||||
onRotateLeft,
|
||||
onRotateRight,
|
||||
onZoomOut,
|
||||
onZoomIn,
|
||||
onClose,
|
||||
onReset
|
||||
} = props;
|
||||
const {
|
||||
left,
|
||||
right,
|
||||
prev,
|
||||
next,
|
||||
flipY,
|
||||
flipX,
|
||||
rotateLeft,
|
||||
rotateRight,
|
||||
zoomOut,
|
||||
zoomIn
|
||||
} = icons;
|
||||
|
||||
// ========================== Render ==========================
|
||||
// >>>>> Progress
|
||||
const progressNode = showProgress && /*#__PURE__*/React.createElement("div", {
|
||||
className: `${prefixCls}-progress`
|
||||
}, countRender ? countRender(current + 1, count) : /*#__PURE__*/React.createElement("bdi", null, `${current + 1} / ${count}`));
|
||||
|
||||
// >>>>> Actions
|
||||
const actionCls = `${prefixCls}-actions-action`;
|
||||
const renderOperation = ({
|
||||
type,
|
||||
disabled,
|
||||
onClick,
|
||||
icon
|
||||
}) => {
|
||||
return /*#__PURE__*/React.createElement("button", {
|
||||
type: "button",
|
||||
key: type,
|
||||
className: clsx(actionCls, `${actionCls}-${type}`, {
|
||||
[`${actionCls}-disabled`]: !!disabled
|
||||
}),
|
||||
onClick: onClick,
|
||||
disabled: !!disabled,
|
||||
"aria-label": type
|
||||
}, icon);
|
||||
};
|
||||
const switchPrevNode = showSwitch ? renderOperation({
|
||||
icon: prev ?? left,
|
||||
onClick: () => onActive(-1),
|
||||
type: 'prev',
|
||||
disabled: current === 0
|
||||
}) : undefined;
|
||||
const switchNextNode = showSwitch ? renderOperation({
|
||||
icon: next ?? right,
|
||||
onClick: () => onActive(1),
|
||||
type: 'next',
|
||||
disabled: current === count - 1
|
||||
}) : undefined;
|
||||
const flipYNode = renderOperation({
|
||||
icon: flipY,
|
||||
onClick: onFlipY,
|
||||
type: 'flipY'
|
||||
});
|
||||
const flipXNode = renderOperation({
|
||||
icon: flipX,
|
||||
onClick: onFlipX,
|
||||
type: 'flipX'
|
||||
});
|
||||
const rotateLeftNode = renderOperation({
|
||||
icon: rotateLeft,
|
||||
onClick: onRotateLeft,
|
||||
type: 'rotateLeft'
|
||||
});
|
||||
const rotateRightNode = renderOperation({
|
||||
icon: rotateRight,
|
||||
onClick: onRotateRight,
|
||||
type: 'rotateRight'
|
||||
});
|
||||
const zoomOutNode = renderOperation({
|
||||
icon: zoomOut,
|
||||
onClick: onZoomOut,
|
||||
type: 'zoomOut',
|
||||
disabled: scale <= minScale
|
||||
});
|
||||
const zoomInNode = renderOperation({
|
||||
icon: zoomIn,
|
||||
onClick: onZoomIn,
|
||||
type: 'zoomIn',
|
||||
disabled: scale === maxScale
|
||||
});
|
||||
const actionsNode = /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-actions`, classNames.actions),
|
||||
style: styles.actions
|
||||
}, flipYNode, flipXNode, rotateLeftNode, rotateRightNode, zoomOutNode, zoomInNode);
|
||||
|
||||
// >>>>> Render
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-footer`, classNames.footer),
|
||||
style: styles.footer
|
||||
}, progressNode, actionsRender ? actionsRender(actionsNode, {
|
||||
icons: {
|
||||
prevIcon: switchPrevNode,
|
||||
nextIcon: switchNextNode,
|
||||
flipYIcon: flipYNode,
|
||||
flipXIcon: flipXNode,
|
||||
rotateLeftIcon: rotateLeftNode,
|
||||
rotateRightIcon: rotateRightNode,
|
||||
zoomOutIcon: zoomOutNode,
|
||||
zoomInIcon: zoomInNode
|
||||
},
|
||||
actions: {
|
||||
onActive,
|
||||
onFlipY,
|
||||
onFlipX,
|
||||
onRotateLeft,
|
||||
onRotateRight,
|
||||
onZoomOut,
|
||||
onZoomIn,
|
||||
onReset,
|
||||
onClose
|
||||
},
|
||||
transform,
|
||||
current,
|
||||
total: count,
|
||||
image
|
||||
}) : actionsNode);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import type { OperationIcons } from '.';
|
||||
export interface PrevNextProps {
|
||||
prefixCls: string;
|
||||
onActive: (offset: number) => void;
|
||||
current: number;
|
||||
count: number;
|
||||
icons: OperationIcons;
|
||||
}
|
||||
export default function PrevNext(props: PrevNextProps): React.JSX.Element;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { clsx } from 'clsx';
|
||||
import * as React from 'react';
|
||||
export default function PrevNext(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
onActive,
|
||||
current,
|
||||
count,
|
||||
icons: {
|
||||
left,
|
||||
right,
|
||||
prev,
|
||||
next
|
||||
}
|
||||
} = props;
|
||||
const switchCls = `${prefixCls}-switch`;
|
||||
const prevDisabled = current === 0;
|
||||
const nextDisabled = current === count - 1;
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
||||
className: clsx(switchCls, `${switchCls}-prev`, {
|
||||
[`${switchCls}-disabled`]: prevDisabled
|
||||
}),
|
||||
onClick: () => onActive(-1),
|
||||
disabled: prevDisabled
|
||||
}, prev ?? left), /*#__PURE__*/React.createElement("button", {
|
||||
type: "button",
|
||||
className: clsx(switchCls, `${switchCls}-next`, {
|
||||
[`${switchCls}-disabled`]: nextDisabled
|
||||
}),
|
||||
onClick: () => onActive(1),
|
||||
disabled: nextDisabled
|
||||
}, next ?? right));
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { type PortalProps } from '@rc-component/portal';
|
||||
import React from 'react';
|
||||
import type { TransformAction, TransformType } from '../hooks/useImageTransform';
|
||||
import type { ImgInfo } from '../Image';
|
||||
import { type FooterSemanticName } from './Footer';
|
||||
export type PreviewSemanticName = 'root' | 'mask' | 'body' | 'close' | FooterSemanticName;
|
||||
export interface OperationIcons {
|
||||
rotateLeft?: React.ReactNode;
|
||||
rotateRight?: React.ReactNode;
|
||||
zoomIn?: React.ReactNode;
|
||||
zoomOut?: React.ReactNode;
|
||||
close?: React.ReactNode;
|
||||
prev?: React.ReactNode;
|
||||
next?: React.ReactNode;
|
||||
/** @deprecated Please use `prev` instead */
|
||||
left?: React.ReactNode;
|
||||
/** @deprecated Please use `next` instead */
|
||||
right?: React.ReactNode;
|
||||
flipX?: React.ReactNode;
|
||||
flipY?: React.ReactNode;
|
||||
}
|
||||
export interface Actions {
|
||||
onActive: (offset: number) => void;
|
||||
onFlipY: () => void;
|
||||
onFlipX: () => void;
|
||||
onRotateLeft: () => void;
|
||||
onRotateRight: () => void;
|
||||
onZoomOut: () => void;
|
||||
onZoomIn: () => void;
|
||||
onClose: () => void;
|
||||
onReset: () => void;
|
||||
}
|
||||
export type ToolbarRenderInfoType = {
|
||||
icons: {
|
||||
prevIcon?: React.ReactNode;
|
||||
nextIcon?: React.ReactNode;
|
||||
flipYIcon: React.ReactNode;
|
||||
flipXIcon: React.ReactNode;
|
||||
rotateLeftIcon: React.ReactNode;
|
||||
rotateRightIcon: React.ReactNode;
|
||||
zoomOutIcon: React.ReactNode;
|
||||
zoomInIcon: React.ReactNode;
|
||||
};
|
||||
actions: Actions;
|
||||
transform: TransformType;
|
||||
current: number;
|
||||
total: number;
|
||||
image: ImgInfo;
|
||||
};
|
||||
export interface InternalPreviewConfig {
|
||||
/** Better to use `classNames.root` instead */
|
||||
rootClassName?: string;
|
||||
src?: string;
|
||||
alt?: string;
|
||||
scaleStep?: number;
|
||||
minScale?: number;
|
||||
maxScale?: number;
|
||||
motionName?: string;
|
||||
open?: boolean;
|
||||
getContainer?: PortalProps['getContainer'];
|
||||
zIndex?: number;
|
||||
maskClosable?: boolean;
|
||||
afterOpenChange?: (open: boolean) => void;
|
||||
/** Whether to trap focus within the preview when open. Default is true. */
|
||||
focusTrap?: boolean;
|
||||
movable?: boolean;
|
||||
icons?: OperationIcons;
|
||||
closeIcon?: React.ReactNode;
|
||||
onTransform?: (info: {
|
||||
transform: TransformType;
|
||||
action: TransformAction;
|
||||
}) => void;
|
||||
countRender?: (current: number, total: number) => React.ReactNode;
|
||||
imageRender?: (originalNode: React.ReactElement, info: {
|
||||
transform: TransformType;
|
||||
current?: number;
|
||||
image: ImgInfo;
|
||||
}) => React.ReactNode;
|
||||
actionsRender?: (originalNode: React.ReactElement, info: ToolbarRenderInfoType) => React.ReactNode;
|
||||
}
|
||||
export interface PreviewProps extends InternalPreviewConfig {
|
||||
prefixCls: string;
|
||||
classNames?: Partial<Record<PreviewSemanticName, string>>;
|
||||
styles?: Partial<Record<PreviewSemanticName, React.CSSProperties>>;
|
||||
imageInfo?: {
|
||||
width: number | string;
|
||||
height: number | string;
|
||||
};
|
||||
fallback?: string;
|
||||
imgCommonProps?: React.ImgHTMLAttributes<HTMLImageElement>;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
current?: number;
|
||||
count?: number;
|
||||
onChange?: (current: number, prev: number) => void;
|
||||
onClose?: () => void;
|
||||
mousePosition: null | {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
declare const Preview: React.FC<PreviewProps>;
|
||||
export default Preview;
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import CSSMotion from '@rc-component/motion';
|
||||
import Portal from '@rc-component/portal';
|
||||
import { useEvent } from '@rc-component/util';
|
||||
import { useLockFocus } from "@rc-component/util/es/Dom/focus";
|
||||
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
|
||||
import KeyCode from "@rc-component/util/es/KeyCode";
|
||||
import { clsx } from 'clsx';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { PreviewGroupContext } from "../context";
|
||||
import useImageTransform from "../hooks/useImageTransform";
|
||||
import useMouseEvent from "../hooks/useMouseEvent";
|
||||
import useStatus from "../hooks/useStatus";
|
||||
import useTouchEvent from "../hooks/useTouchEvent";
|
||||
import { BASE_SCALE_RATIO } from "../previewConfig";
|
||||
import CloseBtn from "./CloseBtn";
|
||||
import Footer from "./Footer";
|
||||
import PrevNext from "./PrevNext";
|
||||
|
||||
// Note: if you want to add `action`,
|
||||
// pls contact @zombieJ or @thinkasany first.
|
||||
|
||||
const PreviewImage = ({
|
||||
fallback,
|
||||
src,
|
||||
imgRef,
|
||||
...props
|
||||
}) => {
|
||||
const [getImgRef, srcAndOnload] = useStatus({
|
||||
src,
|
||||
fallback
|
||||
});
|
||||
return /*#__PURE__*/React.createElement("img", _extends({
|
||||
ref: ref => {
|
||||
imgRef.current = ref;
|
||||
getImgRef(ref);
|
||||
}
|
||||
}, props, srcAndOnload));
|
||||
};
|
||||
const Preview = props => {
|
||||
const {
|
||||
prefixCls,
|
||||
rootClassName,
|
||||
src,
|
||||
alt,
|
||||
imageInfo,
|
||||
fallback,
|
||||
movable = true,
|
||||
onClose,
|
||||
open,
|
||||
afterOpenChange,
|
||||
maskClosable = true,
|
||||
icons = {},
|
||||
closeIcon,
|
||||
getContainer,
|
||||
current = 0,
|
||||
count = 1,
|
||||
countRender,
|
||||
scaleStep = 0.5,
|
||||
minScale = 1,
|
||||
maxScale = 50,
|
||||
motionName = 'fade',
|
||||
imageRender,
|
||||
imgCommonProps,
|
||||
actionsRender,
|
||||
onTransform,
|
||||
onChange,
|
||||
classNames = {},
|
||||
styles = {},
|
||||
mousePosition,
|
||||
zIndex,
|
||||
focusTrap = true
|
||||
} = props;
|
||||
const imgRef = useRef();
|
||||
const wrapperRef = useRef(null);
|
||||
const triggerRef = useRef(null);
|
||||
const groupContext = useContext(PreviewGroupContext);
|
||||
const showLeftOrRightSwitches = groupContext && count > 1;
|
||||
const showOperationsProgress = groupContext && count >= 1;
|
||||
|
||||
// ======================== Transform =========================
|
||||
const [enableTransition, setEnableTransition] = useState(true);
|
||||
const {
|
||||
transform,
|
||||
resetTransform,
|
||||
updateTransform,
|
||||
dispatchZoomChange
|
||||
} = useImageTransform(imgRef, minScale, maxScale, onTransform);
|
||||
const {
|
||||
isMoving,
|
||||
onMouseDown,
|
||||
onWheel
|
||||
} = useMouseEvent(imgRef, movable, open, scaleStep, transform, updateTransform, dispatchZoomChange);
|
||||
const {
|
||||
isTouching,
|
||||
onTouchStart,
|
||||
onTouchMove,
|
||||
onTouchEnd
|
||||
} = useTouchEvent(imgRef, movable, open, minScale, transform, updateTransform, dispatchZoomChange);
|
||||
const {
|
||||
rotate,
|
||||
scale
|
||||
} = transform;
|
||||
useEffect(() => {
|
||||
if (!enableTransition) {
|
||||
setEnableTransition(true);
|
||||
}
|
||||
}, [enableTransition]);
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
resetTransform('close');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// ========================== Image ===========================
|
||||
const onDoubleClick = event => {
|
||||
if (open) {
|
||||
if (scale !== 1) {
|
||||
updateTransform({
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1
|
||||
}, 'doubleClick');
|
||||
} else {
|
||||
dispatchZoomChange(BASE_SCALE_RATIO + scaleStep, 'doubleClick', event.clientX, event.clientY);
|
||||
}
|
||||
}
|
||||
};
|
||||
const imgNode = /*#__PURE__*/React.createElement(PreviewImage, _extends({}, imgCommonProps, {
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
imgRef: imgRef,
|
||||
className: `${prefixCls}-img`,
|
||||
alt: alt,
|
||||
style: {
|
||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0) scale3d(${transform.flipX ? '-' : ''}${scale}, ${transform.flipY ? '-' : ''}${scale}, 1) rotate(${rotate}deg)`,
|
||||
transitionDuration: (!enableTransition || isTouching) && '0s'
|
||||
},
|
||||
fallback: fallback,
|
||||
src: src,
|
||||
onWheel: onWheel,
|
||||
onMouseDown: onMouseDown,
|
||||
onDoubleClick: onDoubleClick,
|
||||
onTouchStart: onTouchStart,
|
||||
onTouchMove: onTouchMove,
|
||||
onTouchEnd: onTouchEnd,
|
||||
onTouchCancel: onTouchEnd
|
||||
}));
|
||||
const image = {
|
||||
url: src,
|
||||
alt,
|
||||
...imageInfo
|
||||
};
|
||||
|
||||
// ======================== Operation =========================
|
||||
// >>>>> Actions
|
||||
const onZoomIn = () => {
|
||||
dispatchZoomChange(BASE_SCALE_RATIO + scaleStep, 'zoomIn');
|
||||
};
|
||||
const onZoomOut = () => {
|
||||
dispatchZoomChange(BASE_SCALE_RATIO / (BASE_SCALE_RATIO + scaleStep), 'zoomOut');
|
||||
};
|
||||
const onRotateRight = () => {
|
||||
updateTransform({
|
||||
rotate: rotate + 90
|
||||
}, 'rotateRight');
|
||||
};
|
||||
const onRotateLeft = () => {
|
||||
updateTransform({
|
||||
rotate: rotate - 90
|
||||
}, 'rotateLeft');
|
||||
};
|
||||
const onFlipX = () => {
|
||||
updateTransform({
|
||||
flipX: !transform.flipX
|
||||
}, 'flipX');
|
||||
};
|
||||
const onFlipY = () => {
|
||||
updateTransform({
|
||||
flipY: !transform.flipY
|
||||
}, 'flipY');
|
||||
};
|
||||
const onReset = () => {
|
||||
resetTransform('reset');
|
||||
};
|
||||
const onActive = offset => {
|
||||
const nextCurrent = current + offset;
|
||||
if (nextCurrent >= 0 && nextCurrent <= count - 1) {
|
||||
setEnableTransition(false);
|
||||
resetTransform(offset < 0 ? 'prev' : 'next');
|
||||
onChange?.(nextCurrent, current);
|
||||
}
|
||||
};
|
||||
|
||||
// >>>>> Effect: Keyboard
|
||||
const onKeyDown = useEvent(event => {
|
||||
if (open) {
|
||||
const {
|
||||
keyCode
|
||||
} = event;
|
||||
if (showLeftOrRightSwitches) {
|
||||
if (keyCode === KeyCode.LEFT) {
|
||||
onActive(-1);
|
||||
} else if (keyCode === KeyCode.RIGHT) {
|
||||
onActive(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// ======================= Lock Scroll ========================
|
||||
const [lockScroll, setLockScroll] = useState(false);
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
setLockScroll(true);
|
||||
}
|
||||
}, [open]);
|
||||
const onVisibleChanged = nextVisible => {
|
||||
if (!nextVisible) {
|
||||
setLockScroll(false);
|
||||
|
||||
// Restore focus to the trigger element after leave animation
|
||||
triggerRef.current?.focus?.();
|
||||
triggerRef.current = null;
|
||||
}
|
||||
afterOpenChange?.(nextVisible);
|
||||
};
|
||||
|
||||
// ========================== Portal ==========================
|
||||
const [portalRender, setPortalRender] = useState(false);
|
||||
useLayoutEffect(() => {
|
||||
if (open) {
|
||||
setPortalRender(true);
|
||||
}
|
||||
}, [open]);
|
||||
const onEsc = ({
|
||||
top
|
||||
}) => {
|
||||
if (top) {
|
||||
onClose?.();
|
||||
}
|
||||
};
|
||||
|
||||
// =========================== Focus ============================
|
||||
useLayoutEffect(() => {
|
||||
if (open) {
|
||||
triggerRef.current = document.activeElement;
|
||||
}
|
||||
}, [open]);
|
||||
useLockFocus(focusTrap && open && portalRender, () => wrapperRef.current);
|
||||
|
||||
// ========================== Render ==========================
|
||||
const bodyStyle = {
|
||||
...styles.body
|
||||
};
|
||||
if (mousePosition) {
|
||||
bodyStyle.transformOrigin = `${mousePosition.x}px ${mousePosition.y}px`;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement(Portal, {
|
||||
open: portalRender && open,
|
||||
autoDestroy: false,
|
||||
getContainer: getContainer,
|
||||
autoLock: lockScroll,
|
||||
onEsc: onEsc
|
||||
}, /*#__PURE__*/React.createElement(CSSMotion, {
|
||||
motionName: motionName,
|
||||
visible: portalRender && open,
|
||||
motionAppear: true,
|
||||
motionEnter: true,
|
||||
motionLeave: true,
|
||||
onVisibleChanged: onVisibleChanged
|
||||
}, ({
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}) => {
|
||||
const mergedStyle = {
|
||||
...styles.root,
|
||||
...motionStyle
|
||||
};
|
||||
if (zIndex) {
|
||||
mergedStyle.zIndex = zIndex;
|
||||
}
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
ref: wrapperRef,
|
||||
className: clsx(prefixCls, rootClassName, classNames.root, motionClassName, {
|
||||
[`${prefixCls}-movable`]: movable,
|
||||
[`${prefixCls}-moving`]: isMoving
|
||||
}),
|
||||
style: mergedStyle,
|
||||
role: "dialog",
|
||||
"aria-modal": "true",
|
||||
"aria-label": alt,
|
||||
tabIndex: -1
|
||||
}, /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-mask`, classNames.mask),
|
||||
style: styles.mask,
|
||||
onClick: maskClosable ? onClose : undefined
|
||||
}), /*#__PURE__*/React.createElement("div", {
|
||||
className: clsx(`${prefixCls}-body`, classNames.body),
|
||||
style: bodyStyle
|
||||
}, imageRender ? imageRender(imgNode, {
|
||||
transform,
|
||||
image,
|
||||
...(groupContext ? {
|
||||
current
|
||||
} : {})
|
||||
}) : imgNode), closeIcon !== false && closeIcon !== null && /*#__PURE__*/React.createElement(CloseBtn, {
|
||||
prefixCls: prefixCls,
|
||||
icon: closeIcon === true ? icons.close : closeIcon || icons.close,
|
||||
onClick: onClose,
|
||||
className: classNames.close,
|
||||
style: styles.close
|
||||
}), showLeftOrRightSwitches && /*#__PURE__*/React.createElement(PrevNext, {
|
||||
prefixCls: prefixCls,
|
||||
current: current,
|
||||
count: count,
|
||||
icons: icons,
|
||||
onActive: onActive
|
||||
}), /*#__PURE__*/React.createElement(Footer, {
|
||||
prefixCls: prefixCls,
|
||||
showProgress: showOperationsProgress,
|
||||
current: current,
|
||||
count: count,
|
||||
showSwitch: showLeftOrRightSwitches
|
||||
// Style
|
||||
,
|
||||
classNames: classNames,
|
||||
styles: styles
|
||||
// Render
|
||||
,
|
||||
image: image,
|
||||
transform: transform,
|
||||
icons: icons,
|
||||
countRender: countRender,
|
||||
actionsRender: actionsRender
|
||||
// Scale
|
||||
,
|
||||
scale: scale,
|
||||
minScale: minScale,
|
||||
maxScale: maxScale
|
||||
// Actions
|
||||
,
|
||||
onActive: onActive,
|
||||
onFlipY: onFlipY,
|
||||
onFlipX: onFlipX,
|
||||
onRotateLeft: onRotateLeft,
|
||||
onRotateRight: onRotateRight,
|
||||
onZoomOut: onZoomOut,
|
||||
onZoomIn: onZoomIn,
|
||||
onClose: onClose,
|
||||
onReset: onReset
|
||||
}));
|
||||
}));
|
||||
};
|
||||
export default Preview;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import type { ImgInfo } from './Image';
|
||||
import type { InternalPreviewConfig, PreviewProps, PreviewSemanticName } from './Preview';
|
||||
import type { TransformType } from './hooks/useImageTransform';
|
||||
import type { ImageElementProps } from './interface';
|
||||
export interface GroupPreviewConfig extends InternalPreviewConfig {
|
||||
current?: number;
|
||||
imageRender?: (originalNode: React.ReactElement, info: {
|
||||
transform: TransformType;
|
||||
current: number;
|
||||
image: ImgInfo;
|
||||
}) => React.ReactNode;
|
||||
onOpenChange?: (value: boolean, info: {
|
||||
current: number;
|
||||
}) => void;
|
||||
onChange?: (current: number, prevCurrent: number) => void;
|
||||
}
|
||||
export interface PreviewGroupProps {
|
||||
previewPrefixCls?: string;
|
||||
classNames?: {
|
||||
popup?: Partial<Record<PreviewSemanticName, string>>;
|
||||
};
|
||||
styles?: {
|
||||
popup?: Partial<Record<PreviewSemanticName, React.CSSProperties>>;
|
||||
};
|
||||
icons?: PreviewProps['icons'];
|
||||
items?: (string | ImageElementProps)[];
|
||||
fallback?: string;
|
||||
preview?: boolean | GroupPreviewConfig;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
declare const Group: React.FC<PreviewGroupProps>;
|
||||
export default Group;
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
import useControlledState from "@rc-component/util/es/hooks/useControlledState";
|
||||
import useEvent from "@rc-component/util/es/hooks/useEvent";
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import Preview from "./Preview";
|
||||
import { PreviewGroupContext } from "./context";
|
||||
import usePreviewItems from "./hooks/usePreviewItems";
|
||||
const Group = ({
|
||||
previewPrefixCls = 'rc-image-preview',
|
||||
classNames,
|
||||
styles,
|
||||
children,
|
||||
icons = {},
|
||||
items,
|
||||
preview,
|
||||
fallback
|
||||
}) => {
|
||||
const {
|
||||
open: previewOpen,
|
||||
onOpenChange,
|
||||
current: currentIndex,
|
||||
onChange,
|
||||
...restProps
|
||||
} = preview && typeof preview === 'object' ? preview : {};
|
||||
|
||||
// ========================== Items ===========================
|
||||
const [mergedItems, register, fromItems] = usePreviewItems(items);
|
||||
|
||||
// ========================= Preview ==========================
|
||||
// >>> Index
|
||||
const [current, setCurrent] = useControlledState(0, currentIndex);
|
||||
const [keepOpenIndex, setKeepOpenIndex] = useState(false);
|
||||
|
||||
// >>> Image
|
||||
const {
|
||||
src,
|
||||
...imgCommonProps
|
||||
} = mergedItems[current]?.data || {};
|
||||
// >>> Visible
|
||||
const [isShowPreview, setShowPreview] = useControlledState(!!previewOpen, previewOpen);
|
||||
const triggerShowPreview = useEvent(next => {
|
||||
setShowPreview(next);
|
||||
if (next !== isShowPreview) {
|
||||
onOpenChange?.(next, {
|
||||
current
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// >>> Position
|
||||
const [mousePosition, setMousePosition] = useState(null);
|
||||
const onPreviewFromImage = React.useCallback((id, imageSrc, mouseX, mouseY) => {
|
||||
const index = fromItems ? mergedItems.findIndex(item => item.data.src === imageSrc) : mergedItems.findIndex(item => item.id === id);
|
||||
setCurrent(index < 0 ? 0 : index);
|
||||
triggerShowPreview(true);
|
||||
setMousePosition({
|
||||
x: mouseX,
|
||||
y: mouseY
|
||||
});
|
||||
setKeepOpenIndex(true);
|
||||
}, [mergedItems, fromItems]);
|
||||
|
||||
// Reset current when reopen
|
||||
React.useEffect(() => {
|
||||
if (isShowPreview) {
|
||||
if (!keepOpenIndex) {
|
||||
setCurrent(0);
|
||||
}
|
||||
} else {
|
||||
setKeepOpenIndex(false);
|
||||
}
|
||||
}, [isShowPreview]);
|
||||
|
||||
// ========================== Events ==========================
|
||||
const onInternalChange = (next, prev) => {
|
||||
setCurrent(next);
|
||||
onChange?.(next, prev);
|
||||
};
|
||||
const onPreviewClose = () => {
|
||||
triggerShowPreview(false);
|
||||
setMousePosition(null);
|
||||
};
|
||||
|
||||
// ========================= Context ==========================
|
||||
const previewGroupContext = React.useMemo(() => ({
|
||||
register,
|
||||
onPreview: onPreviewFromImage
|
||||
}), [register, onPreviewFromImage]);
|
||||
|
||||
// ========================== Render ==========================
|
||||
return /*#__PURE__*/React.createElement(PreviewGroupContext.Provider, {
|
||||
value: previewGroupContext
|
||||
}, children, /*#__PURE__*/React.createElement(Preview, _extends({
|
||||
"aria-hidden": !isShowPreview,
|
||||
open: isShowPreview,
|
||||
prefixCls: previewPrefixCls,
|
||||
onClose: onPreviewClose,
|
||||
mousePosition: mousePosition,
|
||||
imgCommonProps: imgCommonProps,
|
||||
src: src,
|
||||
fallback: fallback,
|
||||
icons: icons,
|
||||
current: current,
|
||||
count: mergedItems.length,
|
||||
onChange: onInternalChange
|
||||
}, restProps, {
|
||||
classNames: classNames?.popup,
|
||||
styles: styles?.popup
|
||||
})));
|
||||
};
|
||||
export default Group;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { ImageElementProps } from './interface';
|
||||
export declare const COMMON_PROPS: (keyof Omit<ImageElementProps, 'src'>)[];
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const COMMON_PROPS = ['crossOrigin', 'decoding', 'draggable', 'loading', 'referrerPolicy', 'sizes', 'srcSet', 'useMap', 'alt', 'fetchPriority'];
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import type { OnGroupPreview, RegisterImage } from './interface';
|
||||
export interface PreviewGroupContextProps {
|
||||
register: RegisterImage;
|
||||
onPreview: OnGroupPreview;
|
||||
}
|
||||
export declare const PreviewGroupContext: React.Context<PreviewGroupContextProps>;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import * as React from 'react';
|
||||
export const PreviewGroupContext = /*#__PURE__*/React.createContext(null);
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Fix positon x,y point when
|
||||
*
|
||||
* Ele width && height < client
|
||||
* - Back origin
|
||||
*
|
||||
* - Ele width | height > clientWidth | clientHeight
|
||||
* - left | top > 0 -> Back 0
|
||||
* - left | top + width | height < clientWidth | clientHeight -> Back left | top + width | height === clientWidth | clientHeight
|
||||
*
|
||||
* Regardless of other
|
||||
*/
|
||||
export default function getFixScaleEleTransPosition(width: number, height: number, left: number, top: number): null | {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
Generated
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
import { getClientSize } from "./util";
|
||||
function fixPoint(key, start, width, clientWidth) {
|
||||
const startAddWidth = start + width;
|
||||
const offsetStart = (width - clientWidth) / 2;
|
||||
if (width > clientWidth) {
|
||||
if (start > 0) {
|
||||
return {
|
||||
[key]: offsetStart
|
||||
};
|
||||
}
|
||||
if (start < 0 && startAddWidth < clientWidth) {
|
||||
return {
|
||||
[key]: -offsetStart
|
||||
};
|
||||
}
|
||||
} else if (start < 0 || startAddWidth > clientWidth) {
|
||||
return {
|
||||
[key]: start < 0 ? offsetStart : -offsetStart
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix positon x,y point when
|
||||
*
|
||||
* Ele width && height < client
|
||||
* - Back origin
|
||||
*
|
||||
* - Ele width | height > clientWidth | clientHeight
|
||||
* - left | top > 0 -> Back 0
|
||||
* - left | top + width | height < clientWidth | clientHeight -> Back left | top + width | height === clientWidth | clientHeight
|
||||
*
|
||||
* Regardless of other
|
||||
*/
|
||||
export default function getFixScaleEleTransPosition(width, height, left, top) {
|
||||
const {
|
||||
width: clientWidth,
|
||||
height: clientHeight
|
||||
} = getClientSize();
|
||||
let fixPos = null;
|
||||
if (width <= clientWidth && height <= clientHeight) {
|
||||
fixPos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
} else if (width > clientWidth || height > clientHeight) {
|
||||
fixPos = {
|
||||
...fixPoint('x', left, width, clientWidth),
|
||||
...fixPoint('y', top, height, clientHeight)
|
||||
};
|
||||
}
|
||||
return fixPos;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/// <reference types="react" />
|
||||
export type TransformType = {
|
||||
x: number;
|
||||
y: number;
|
||||
rotate: number;
|
||||
scale: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
};
|
||||
export type TransformAction = 'flipY' | 'flipX' | 'rotateLeft' | 'rotateRight' | 'zoomIn' | 'zoomOut' | 'close' | 'prev' | 'next' | 'wheel' | 'doubleClick' | 'move' | 'dragRebound' | 'touchZoom' | 'reset';
|
||||
export type UpdateTransformFunc = (newTransform: Partial<TransformType>, action: TransformAction) => void;
|
||||
export type DispatchZoomChangeFunc = (ratio: number, action: TransformAction, centerX?: number, centerY?: number, isTouch?: boolean) => void;
|
||||
export default function useImageTransform(imgRef: React.MutableRefObject<HTMLImageElement>, minScale: number, maxScale: number, onTransform: (info: {
|
||||
transform: TransformType;
|
||||
action: TransformAction;
|
||||
}) => void): {
|
||||
transform: {
|
||||
x: number;
|
||||
y: number;
|
||||
rotate: number;
|
||||
scale: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
};
|
||||
resetTransform: (action: TransformAction) => void;
|
||||
updateTransform: UpdateTransformFunc;
|
||||
dispatchZoomChange: DispatchZoomChangeFunc;
|
||||
};
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import { getClientSize } from "../util";
|
||||
import isEqual from "@rc-component/util/es/isEqual";
|
||||
import raf from "@rc-component/util/es/raf";
|
||||
import { useRef, useState } from 'react';
|
||||
const initialTransform = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotate: 0,
|
||||
scale: 1,
|
||||
flipX: false,
|
||||
flipY: false
|
||||
};
|
||||
export default function useImageTransform(imgRef, minScale, maxScale, onTransform) {
|
||||
const frame = useRef(null);
|
||||
const queue = useRef([]);
|
||||
const [transform, setTransform] = useState(initialTransform);
|
||||
const resetTransform = action => {
|
||||
setTransform(initialTransform);
|
||||
if (!isEqual(initialTransform, transform)) {
|
||||
onTransform?.({
|
||||
transform: initialTransform,
|
||||
action
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** Direct update transform */
|
||||
const updateTransform = (newTransform, action) => {
|
||||
if (frame.current === null) {
|
||||
queue.current = [];
|
||||
frame.current = raf(() => {
|
||||
setTransform(preState => {
|
||||
let memoState = preState;
|
||||
queue.current.forEach(queueState => {
|
||||
memoState = {
|
||||
...memoState,
|
||||
...queueState
|
||||
};
|
||||
});
|
||||
frame.current = null;
|
||||
onTransform?.({
|
||||
transform: memoState,
|
||||
action
|
||||
});
|
||||
return memoState;
|
||||
});
|
||||
});
|
||||
}
|
||||
queue.current.push({
|
||||
...transform,
|
||||
...newTransform
|
||||
});
|
||||
};
|
||||
|
||||
/** Scale according to the position of centerX and centerY */
|
||||
const dispatchZoomChange = (ratio, action, centerX, centerY, isTouch) => {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
offsetWidth,
|
||||
offsetHeight,
|
||||
offsetLeft,
|
||||
offsetTop
|
||||
} = imgRef.current;
|
||||
let newRatio = ratio;
|
||||
let newScale = transform.scale * ratio;
|
||||
if (newScale > maxScale) {
|
||||
newScale = maxScale;
|
||||
newRatio = maxScale / transform.scale;
|
||||
} else if (newScale < minScale) {
|
||||
// For mobile interactions, allow scaling down to the minimum scale.
|
||||
newScale = isTouch ? newScale : minScale;
|
||||
newRatio = newScale / transform.scale;
|
||||
}
|
||||
|
||||
/** Default center point scaling */
|
||||
const mergedCenterX = centerX ?? innerWidth / 2;
|
||||
const mergedCenterY = centerY ?? innerHeight / 2;
|
||||
const diffRatio = newRatio - 1;
|
||||
/** Deviation calculated from image size */
|
||||
const diffImgX = diffRatio * width * 0.5;
|
||||
const diffImgY = diffRatio * height * 0.5;
|
||||
/** The difference between the click position and the edge of the document */
|
||||
const diffOffsetLeft = diffRatio * (mergedCenterX - transform.x - offsetLeft);
|
||||
const diffOffsetTop = diffRatio * (mergedCenterY - transform.y - offsetTop);
|
||||
/** Final positioning */
|
||||
let newX = transform.x - (diffOffsetLeft - diffImgX);
|
||||
let newY = transform.y - (diffOffsetTop - diffImgY);
|
||||
|
||||
/**
|
||||
* When zooming the image
|
||||
* When the image size is smaller than the width and height of the window, the position is initialized
|
||||
*/
|
||||
if (ratio < 1 && newScale === 1) {
|
||||
const mergedWidth = offsetWidth * newScale;
|
||||
const mergedHeight = offsetHeight * newScale;
|
||||
const {
|
||||
width: clientWidth,
|
||||
height: clientHeight
|
||||
} = getClientSize();
|
||||
if (mergedWidth <= clientWidth && mergedHeight <= clientHeight) {
|
||||
newX = 0;
|
||||
newY = 0;
|
||||
}
|
||||
}
|
||||
updateTransform({
|
||||
x: newX,
|
||||
y: newY,
|
||||
scale: newScale
|
||||
}, action);
|
||||
};
|
||||
return {
|
||||
transform,
|
||||
resetTransform,
|
||||
updateTransform,
|
||||
dispatchZoomChange
|
||||
};
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import type React from 'react';
|
||||
import type { DispatchZoomChangeFunc, TransformType, UpdateTransformFunc } from './useImageTransform';
|
||||
export default function useMouseEvent(imgRef: React.MutableRefObject<HTMLImageElement>, movable: boolean, open: boolean, scaleStep: number, transform: TransformType, updateTransform: UpdateTransformFunc, dispatchZoomChange: DispatchZoomChangeFunc): {
|
||||
isMoving: boolean;
|
||||
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseMove: (event: MouseEvent) => void;
|
||||
onMouseUp: () => void;
|
||||
onWheel: (event: React.WheelEvent<HTMLImageElement>) => void;
|
||||
};
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
import { warning } from "@rc-component/util/es/warning";
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import getFixScaleEleTransPosition from "../getFixScaleEleTransPosition";
|
||||
import { BASE_SCALE_RATIO, WHEEL_MAX_SCALE_RATIO } from "../previewConfig";
|
||||
export default function useMouseEvent(imgRef, movable, open, scaleStep, transform, updateTransform, dispatchZoomChange) {
|
||||
const {
|
||||
rotate,
|
||||
scale,
|
||||
x,
|
||||
y
|
||||
} = transform;
|
||||
const [isMoving, setMoving] = useState(false);
|
||||
const startPositionInfo = useRef({
|
||||
diffX: 0,
|
||||
diffY: 0,
|
||||
transformX: 0,
|
||||
transformY: 0
|
||||
});
|
||||
const onMouseDown = event => {
|
||||
// Only allow main button
|
||||
if (!movable || event.button !== 0) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
startPositionInfo.current = {
|
||||
diffX: event.pageX - x,
|
||||
diffY: event.pageY - y,
|
||||
transformX: x,
|
||||
transformY: y
|
||||
};
|
||||
setMoving(true);
|
||||
};
|
||||
const onMouseMove = event => {
|
||||
if (open && isMoving) {
|
||||
updateTransform({
|
||||
x: event.pageX - startPositionInfo.current.diffX,
|
||||
y: event.pageY - startPositionInfo.current.diffY
|
||||
}, 'move');
|
||||
}
|
||||
};
|
||||
const onMouseUp = () => {
|
||||
if (open && isMoving) {
|
||||
setMoving(false);
|
||||
|
||||
/** No need to restore the position when the picture is not moved, So as not to interfere with the click */
|
||||
const {
|
||||
transformX,
|
||||
transformY
|
||||
} = startPositionInfo.current;
|
||||
const hasChangedPosition = x !== transformX && y !== transformY;
|
||||
if (!hasChangedPosition) return;
|
||||
const width = imgRef.current.offsetWidth * scale;
|
||||
const height = imgRef.current.offsetHeight * scale;
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const {
|
||||
left,
|
||||
top
|
||||
} = imgRef.current.getBoundingClientRect();
|
||||
const isRotate = rotate % 180 !== 0;
|
||||
const fixState = getFixScaleEleTransPosition(isRotate ? height : width, isRotate ? width : height, left, top);
|
||||
if (fixState) {
|
||||
updateTransform({
|
||||
...fixState
|
||||
}, 'dragRebound');
|
||||
}
|
||||
}
|
||||
};
|
||||
const onWheel = event => {
|
||||
if (!open || event.deltaY == 0) return;
|
||||
// Scale ratio depends on the deltaY size
|
||||
const scaleRatio = Math.abs(event.deltaY / 100);
|
||||
// Limit the maximum scale ratio
|
||||
const mergedScaleRatio = Math.min(scaleRatio, WHEEL_MAX_SCALE_RATIO);
|
||||
// Scale the ratio each time
|
||||
let ratio = BASE_SCALE_RATIO + mergedScaleRatio * scaleStep;
|
||||
if (event.deltaY > 0) {
|
||||
ratio = BASE_SCALE_RATIO / ratio;
|
||||
}
|
||||
dispatchZoomChange(ratio, 'wheel', event.clientX, event.clientY);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (movable) {
|
||||
window.addEventListener('mouseup', onMouseUp, false);
|
||||
window.addEventListener('mousemove', onMouseMove, false);
|
||||
try {
|
||||
// Resolve if in iframe lost event
|
||||
/* istanbul ignore next */
|
||||
if (window.top !== window.self) {
|
||||
window.top.addEventListener('mouseup', onMouseUp, false);
|
||||
window.top.addEventListener('mousemove', onMouseMove, false);
|
||||
}
|
||||
} catch (error) {
|
||||
/* istanbul ignore next */
|
||||
warning(false, `[rc-image] ${error}`);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('mouseup', onMouseUp);
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
window.top?.removeEventListener('mouseup', onMouseUp);
|
||||
window.top?.removeEventListener('mousemove', onMouseMove);
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
}, [open, isMoving, x, y, rotate, movable]);
|
||||
return {
|
||||
isMoving,
|
||||
onMouseDown,
|
||||
onMouseMove,
|
||||
onMouseUp,
|
||||
onWheel
|
||||
};
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import type { PreviewGroupProps } from '../PreviewGroup';
|
||||
import type { InternalItem, RegisterImage } from '../interface';
|
||||
export type Items = Omit<InternalItem, 'canPreview'>[];
|
||||
/**
|
||||
* Merge props provided `items` or context collected images
|
||||
*/
|
||||
export default function usePreviewItems(items?: PreviewGroupProps['items']): [items: Items, registerImage: RegisterImage, fromItems: boolean];
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import * as React from 'react';
|
||||
import { COMMON_PROPS } from "../common";
|
||||
/**
|
||||
* Merge props provided `items` or context collected images
|
||||
*/
|
||||
export default function usePreviewItems(items) {
|
||||
// Context collection image data
|
||||
const [images, setImages] = React.useState({});
|
||||
const registerImage = React.useCallback((id, data) => {
|
||||
setImages(imgs => ({
|
||||
...imgs,
|
||||
[id]: data
|
||||
}));
|
||||
return () => {
|
||||
setImages(imgs => {
|
||||
const cloneImgs = {
|
||||
...imgs
|
||||
};
|
||||
delete cloneImgs[id];
|
||||
return cloneImgs;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// items
|
||||
const mergedItems = React.useMemo(() => {
|
||||
// use `items` first
|
||||
if (items) {
|
||||
return items.map(item => {
|
||||
if (typeof item === 'string') {
|
||||
return {
|
||||
data: {
|
||||
src: item
|
||||
}
|
||||
};
|
||||
}
|
||||
const data = {};
|
||||
Object.keys(item).forEach(key => {
|
||||
if (['src', ...COMMON_PROPS].includes(key)) {
|
||||
data[key] = item[key];
|
||||
}
|
||||
});
|
||||
return {
|
||||
data
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// use registered images secondly
|
||||
return Object.keys(images).reduce((total, id) => {
|
||||
const {
|
||||
canPreview,
|
||||
data
|
||||
} = images[id];
|
||||
if (canPreview) {
|
||||
total.push({
|
||||
data,
|
||||
id
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}, []);
|
||||
}, [items, images]);
|
||||
return [mergedItems, registerImage, !!items];
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { ImageElementProps } from '../interface';
|
||||
export default function useRegisterImage(canPreview: boolean, data: ImageElementProps): string;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import * as React from 'react';
|
||||
import { PreviewGroupContext } from "../context";
|
||||
let uid = 0;
|
||||
export default function useRegisterImage(canPreview, data) {
|
||||
const [id] = React.useState(() => {
|
||||
uid += 1;
|
||||
return String(uid);
|
||||
});
|
||||
const groupContext = React.useContext(PreviewGroupContext);
|
||||
const registerData = {
|
||||
data,
|
||||
canPreview
|
||||
};
|
||||
|
||||
// Keep order start
|
||||
// Resolve https://github.com/ant-design/ant-design/issues/28881
|
||||
// Only need unRegister when component unMount
|
||||
React.useEffect(() => {
|
||||
if (groupContext) {
|
||||
return groupContext.register(id, registerData);
|
||||
}
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
if (groupContext) {
|
||||
groupContext.register(id, registerData);
|
||||
}
|
||||
}, [canPreview, data]);
|
||||
return id;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
type ImageStatus = 'normal' | 'error' | 'loading';
|
||||
export default function useStatus({ src, isCustomPlaceholder, fallback, }: {
|
||||
src: string;
|
||||
isCustomPlaceholder?: boolean;
|
||||
fallback?: string;
|
||||
}): readonly [(img?: HTMLImageElement) => void, {
|
||||
src: string;
|
||||
onLoad?: undefined;
|
||||
} | {
|
||||
onLoad: () => void;
|
||||
src: string;
|
||||
}, ImageStatus];
|
||||
export {};
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { isImageValid } from "../util";
|
||||
export default function useStatus({
|
||||
src,
|
||||
isCustomPlaceholder,
|
||||
fallback
|
||||
}) {
|
||||
const [status, setStatus] = useState(isCustomPlaceholder ? 'loading' : 'normal');
|
||||
const isLoaded = useRef(false);
|
||||
const isError = status === 'error';
|
||||
|
||||
// https://github.com/react-component/image/pull/187
|
||||
useEffect(() => {
|
||||
let isCurrentSrc = true;
|
||||
isImageValid(src).then(isValid => {
|
||||
// https://github.com/ant-design/ant-design/issues/44948
|
||||
// If src changes, the previous setStatus should not be triggered
|
||||
if (!isValid && isCurrentSrc) {
|
||||
setStatus('error');
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
isCurrentSrc = false;
|
||||
};
|
||||
}, [src]);
|
||||
useEffect(() => {
|
||||
if (isCustomPlaceholder && !isLoaded.current) {
|
||||
setStatus('loading');
|
||||
} else if (isError) {
|
||||
setStatus('normal');
|
||||
}
|
||||
}, [src]);
|
||||
const onLoad = () => {
|
||||
setStatus('normal');
|
||||
};
|
||||
const getImgRef = img => {
|
||||
isLoaded.current = false;
|
||||
if (status === 'loading' && img?.complete && (img.naturalWidth || img.naturalHeight)) {
|
||||
isLoaded.current = true;
|
||||
onLoad();
|
||||
}
|
||||
};
|
||||
const srcAndOnload = isError && fallback ? {
|
||||
src: fallback
|
||||
} : {
|
||||
onLoad,
|
||||
src
|
||||
};
|
||||
return [getImgRef, srcAndOnload, status];
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import type React from 'react';
|
||||
import type { DispatchZoomChangeFunc, TransformType, UpdateTransformFunc } from './useImageTransform';
|
||||
export default function useTouchEvent(imgRef: React.MutableRefObject<HTMLImageElement>, movable: boolean, open: boolean, minScale: number, transform: TransformType, updateTransform: UpdateTransformFunc, dispatchZoomChange: DispatchZoomChangeFunc): {
|
||||
isTouching: boolean;
|
||||
onTouchStart: (event: React.TouchEvent<HTMLImageElement>) => void;
|
||||
onTouchMove: (event: React.TouchEvent<HTMLImageElement>) => void;
|
||||
onTouchEnd: () => void;
|
||||
};
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import getFixScaleEleTransPosition from "../getFixScaleEleTransPosition";
|
||||
function getDistance(a, b) {
|
||||
const x = a.x - b.x;
|
||||
const y = a.y - b.y;
|
||||
return Math.hypot(x, y);
|
||||
}
|
||||
function getCenter(oldPoint1, oldPoint2, newPoint1, newPoint2) {
|
||||
// Calculate the distance each point has moved
|
||||
const distance1 = getDistance(oldPoint1, newPoint1);
|
||||
const distance2 = getDistance(oldPoint2, newPoint2);
|
||||
|
||||
// If both distances are 0, return the original points
|
||||
if (distance1 === 0 && distance2 === 0) {
|
||||
return [oldPoint1.x, oldPoint1.y];
|
||||
}
|
||||
|
||||
// Calculate the ratio of the distances
|
||||
const ratio = distance1 / (distance1 + distance2);
|
||||
|
||||
// Calculate the new center point based on the ratio
|
||||
const x = oldPoint1.x + ratio * (oldPoint2.x - oldPoint1.x);
|
||||
const y = oldPoint1.y + ratio * (oldPoint2.y - oldPoint1.y);
|
||||
return [x, y];
|
||||
}
|
||||
export default function useTouchEvent(imgRef, movable, open, minScale, transform, updateTransform, dispatchZoomChange) {
|
||||
const {
|
||||
rotate,
|
||||
scale,
|
||||
x,
|
||||
y
|
||||
} = transform;
|
||||
const [isTouching, setIsTouching] = useState(false);
|
||||
const touchPointInfo = useRef({
|
||||
point1: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
point2: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
eventType: 'none'
|
||||
});
|
||||
const updateTouchPointInfo = values => {
|
||||
touchPointInfo.current = {
|
||||
...touchPointInfo.current,
|
||||
...values
|
||||
};
|
||||
};
|
||||
const onTouchStart = event => {
|
||||
if (!movable) return;
|
||||
event.stopPropagation();
|
||||
setIsTouching(true);
|
||||
const {
|
||||
touches = []
|
||||
} = event;
|
||||
if (touches.length > 1) {
|
||||
// touch zoom
|
||||
updateTouchPointInfo({
|
||||
point1: {
|
||||
x: touches[0].clientX,
|
||||
y: touches[0].clientY
|
||||
},
|
||||
point2: {
|
||||
x: touches[1].clientX,
|
||||
y: touches[1].clientY
|
||||
},
|
||||
eventType: 'touchZoom'
|
||||
});
|
||||
} else {
|
||||
// touch move
|
||||
updateTouchPointInfo({
|
||||
point1: {
|
||||
x: touches[0].clientX - x,
|
||||
y: touches[0].clientY - y
|
||||
},
|
||||
eventType: 'move'
|
||||
});
|
||||
}
|
||||
};
|
||||
const onTouchMove = event => {
|
||||
const {
|
||||
touches = []
|
||||
} = event;
|
||||
const {
|
||||
point1,
|
||||
point2,
|
||||
eventType
|
||||
} = touchPointInfo.current;
|
||||
if (touches.length > 1 && eventType === 'touchZoom') {
|
||||
// touch zoom
|
||||
const newPoint1 = {
|
||||
x: touches[0].clientX,
|
||||
y: touches[0].clientY
|
||||
};
|
||||
const newPoint2 = {
|
||||
x: touches[1].clientX,
|
||||
y: touches[1].clientY
|
||||
};
|
||||
const [centerX, centerY] = getCenter(point1, point2, newPoint1, newPoint2);
|
||||
const ratio = getDistance(newPoint1, newPoint2) / getDistance(point1, point2);
|
||||
dispatchZoomChange(ratio, 'touchZoom', centerX, centerY, true);
|
||||
updateTouchPointInfo({
|
||||
point1: newPoint1,
|
||||
point2: newPoint2,
|
||||
eventType: 'touchZoom'
|
||||
});
|
||||
} else if (eventType === 'move') {
|
||||
// touch move
|
||||
updateTransform({
|
||||
x: touches[0].clientX - point1.x,
|
||||
y: touches[0].clientY - point1.y
|
||||
}, 'move');
|
||||
updateTouchPointInfo({
|
||||
eventType: 'move'
|
||||
});
|
||||
}
|
||||
};
|
||||
const onTouchEnd = () => {
|
||||
if (!open) return;
|
||||
if (isTouching) {
|
||||
setIsTouching(false);
|
||||
}
|
||||
updateTouchPointInfo({
|
||||
eventType: 'none'
|
||||
});
|
||||
if (minScale > scale) {
|
||||
/** When the scaling ratio is less than the minimum scaling ratio, reset the scaling ratio */
|
||||
return updateTransform({
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: minScale
|
||||
}, 'touchZoom');
|
||||
}
|
||||
const width = imgRef.current.offsetWidth * scale;
|
||||
const height = imgRef.current.offsetHeight * scale;
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const {
|
||||
left,
|
||||
top
|
||||
} = imgRef.current.getBoundingClientRect();
|
||||
const isRotate = rotate % 180 !== 0;
|
||||
const fixState = getFixScaleEleTransPosition(isRotate ? height : width, isRotate ? width : height, left, top);
|
||||
if (fixState) {
|
||||
updateTransform({
|
||||
...fixState
|
||||
}, 'dragRebound');
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const preventDefault = e => {
|
||||
e.preventDefault();
|
||||
};
|
||||
if (open && movable) {
|
||||
window.addEventListener('touchmove', preventDefault, {
|
||||
passive: false
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('touchmove', preventDefault);
|
||||
};
|
||||
}, [open, movable]);
|
||||
return {
|
||||
isTouching,
|
||||
onTouchStart,
|
||||
onTouchMove,
|
||||
onTouchEnd
|
||||
};
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import Image from './Image';
|
||||
export * from './Image';
|
||||
export default Image;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import Image from "./Image";
|
||||
export * from "./Image";
|
||||
export default Image;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/// <reference types="react" />
|
||||
/**
|
||||
* Used for PreviewGroup passed image data
|
||||
*/
|
||||
export type ImageElementProps = Pick<React.ImgHTMLAttributes<HTMLImageElement>, 'src' | 'crossOrigin' | 'decoding' | 'draggable' | 'loading' | 'referrerPolicy' | 'sizes' | 'srcSet' | 'useMap' | 'alt' | 'fetchPriority'>;
|
||||
export type PreviewImageElementProps = {
|
||||
data: ImageElementProps;
|
||||
canPreview: boolean;
|
||||
};
|
||||
export type InternalItem = PreviewImageElementProps & {
|
||||
id?: string;
|
||||
};
|
||||
export type RegisterImage = (id: string, data: PreviewImageElementProps) => VoidFunction;
|
||||
export type OnGroupPreview = (id: string, imageSrc: string, mouseX: number, mouseY: number) => void;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/** Scale the ratio base */
|
||||
export declare const BASE_SCALE_RATIO = 1;
|
||||
/** The maximum zoom ratio when the mouse zooms in, adjustable */
|
||||
export declare const WHEEL_MAX_SCALE_RATIO = 1;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/** Scale the ratio base */
|
||||
export const BASE_SCALE_RATIO = 1;
|
||||
/** The maximum zoom ratio when the mouse zooms in, adjustable */
|
||||
export const WHEEL_MAX_SCALE_RATIO = 1;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export declare function isImageValid(src: string): Promise<unknown>;
|
||||
export declare function getClientSize(): {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
export function isImageValid(src) {
|
||||
return new Promise(resolve => {
|
||||
if (!src) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const img = document.createElement('img');
|
||||
img.onerror = () => resolve(false);
|
||||
img.onload = () => resolve(true);
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
// ============================= Legacy =============================
|
||||
export function getClientSize() {
|
||||
const width = document.documentElement.clientWidth;
|
||||
const height = window.innerHeight || document.documentElement.clientHeight;
|
||||
return {
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import * as React from 'react';
|
||||
import type { InternalPreviewConfig, PreviewSemanticName, ToolbarRenderInfoType } from './Preview';
|
||||
import PreviewGroup from './PreviewGroup';
|
||||
import type { TransformType } from './hooks/useImageTransform';
|
||||
export interface ImgInfo {
|
||||
url: string;
|
||||
alt: string;
|
||||
width: string | number;
|
||||
height: string | number;
|
||||
}
|
||||
export interface CoverConfig {
|
||||
coverNode?: React.ReactNode;
|
||||
placement?: 'top' | 'bottom' | 'center';
|
||||
}
|
||||
export interface PreviewConfig extends Omit<InternalPreviewConfig, 'countRender'> {
|
||||
cover?: React.ReactNode | CoverConfig;
|
||||
imageRender?: (originalNode: React.ReactElement, info: {
|
||||
transform: TransformType;
|
||||
image: ImgInfo;
|
||||
}) => React.ReactNode;
|
||||
actionsRender?: (originalNode: React.ReactElement, info: Omit<ToolbarRenderInfoType, 'current' | 'total'>) => React.ReactNode;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
export type SemanticName = 'root' | 'image' | 'cover';
|
||||
export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'placeholder' | 'onClick' | 'onKeyDown'> {
|
||||
prefixCls?: string;
|
||||
previewPrefixCls?: string;
|
||||
rootClassName?: string;
|
||||
classNames?: Partial<Record<SemanticName, string> & {
|
||||
popup?: Partial<Record<PreviewSemanticName, string>>;
|
||||
}>;
|
||||
styles?: Partial<Record<SemanticName, React.CSSProperties> & {
|
||||
popup?: Partial<Record<PreviewSemanticName, React.CSSProperties>>;
|
||||
}>;
|
||||
src?: string;
|
||||
placeholder?: React.ReactNode;
|
||||
fallback?: string;
|
||||
preview?: boolean | PreviewConfig;
|
||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
onError?: (e: React.SyntheticEvent<HTMLImageElement, Event>) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
}
|
||||
interface CompoundedComponent<P> extends React.FC<P> {
|
||||
PreviewGroup: typeof PreviewGroup;
|
||||
}
|
||||
declare const ImageInternal: CompoundedComponent<ImageProps>;
|
||||
export default ImageInternal;
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState"));
|
||||
var _clsx = require("clsx");
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
var React = _react;
|
||||
var _Preview = _interopRequireDefault(require("./Preview"));
|
||||
var _PreviewGroup = _interopRequireDefault(require("./PreviewGroup"));
|
||||
var _common = require("./common");
|
||||
var _context = require("./context");
|
||||
var _useRegisterImage = _interopRequireDefault(require("./hooks/useRegisterImage"));
|
||||
var _useStatus = _interopRequireDefault(require("./hooks/useStatus"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
const ImageInternal = props => {
|
||||
const {
|
||||
// Misc
|
||||
prefixCls = 'rc-image',
|
||||
previewPrefixCls = `${prefixCls}-preview`,
|
||||
// Style
|
||||
rootClassName,
|
||||
className,
|
||||
style,
|
||||
classNames = {},
|
||||
styles = {},
|
||||
width,
|
||||
height,
|
||||
// Image
|
||||
src: imgSrc,
|
||||
alt,
|
||||
placeholder,
|
||||
fallback,
|
||||
// Preview
|
||||
preview = true,
|
||||
// Events
|
||||
onClick,
|
||||
onError,
|
||||
onKeyDown,
|
||||
...otherProps
|
||||
} = props;
|
||||
const groupContext = (0, _react.useContext)(_context.PreviewGroupContext);
|
||||
|
||||
// ========================== Preview ===========================
|
||||
const canPreview = !!preview;
|
||||
const {
|
||||
src: previewSrc,
|
||||
open: previewOpen,
|
||||
onOpenChange: onPreviewOpenChange,
|
||||
cover,
|
||||
rootClassName: previewRootClassName,
|
||||
...restProps
|
||||
} = preview && typeof preview === 'object' ? preview : {};
|
||||
const coverPlacement = typeof cover === 'object' && cover.placement ? cover.placement || 'center' : 'center';
|
||||
const coverNode = typeof cover === 'object' && cover.coverNode ? cover.coverNode : cover;
|
||||
|
||||
// ============================ Open ============================
|
||||
const [isShowPreview, setShowPreview] = (0, _useControlledState.default)(!!previewOpen, previewOpen);
|
||||
const [mousePosition, setMousePosition] = (0, _react.useState)(null);
|
||||
const triggerPreviewOpen = nextOpen => {
|
||||
setShowPreview(nextOpen);
|
||||
onPreviewOpenChange?.(nextOpen);
|
||||
};
|
||||
const onPreviewClose = () => {
|
||||
triggerPreviewOpen(false);
|
||||
};
|
||||
|
||||
// ========================= ImageProps =========================
|
||||
const isCustomPlaceholder = placeholder && placeholder !== true;
|
||||
const src = previewSrc ?? imgSrc;
|
||||
const [getImgRef, srcAndOnload, status] = (0, _useStatus.default)({
|
||||
src: imgSrc,
|
||||
isCustomPlaceholder,
|
||||
fallback
|
||||
});
|
||||
const imgCommonProps = (0, _react.useMemo)(() => {
|
||||
const obj = {};
|
||||
_common.COMMON_PROPS.forEach(prop => {
|
||||
if (props[prop] !== undefined) {
|
||||
obj[prop] = props[prop];
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}, _common.COMMON_PROPS.map(prop => props[prop]));
|
||||
|
||||
// ========================== Register ==========================
|
||||
const registerData = (0, _react.useMemo)(() => ({
|
||||
...imgCommonProps,
|
||||
src
|
||||
}), [src, imgCommonProps]);
|
||||
const imageId = (0, _useRegisterImage.default)(canPreview, registerData);
|
||||
|
||||
// ========================== Preview ===========================
|
||||
const onPreview = e => {
|
||||
const rect = e.target.getBoundingClientRect();
|
||||
const left = rect.x + rect.width / 2;
|
||||
const top = rect.y + rect.height / 2;
|
||||
if (groupContext) {
|
||||
groupContext.onPreview(imageId, src, left, top);
|
||||
} else {
|
||||
setMousePosition({
|
||||
x: left,
|
||||
y: top
|
||||
});
|
||||
triggerPreviewOpen(true);
|
||||
}
|
||||
onClick?.(e);
|
||||
};
|
||||
|
||||
// ======================= Keyboard Preview =====================
|
||||
const onPreviewKeyDown = event => {
|
||||
onKeyDown?.(event);
|
||||
if (!canPreview) {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
const rect = event.target.getBoundingClientRect();
|
||||
const left = rect.x + rect.width / 2;
|
||||
const top = rect.y + rect.height / 2;
|
||||
if (groupContext) {
|
||||
groupContext.onPreview(imageId, src, left, top);
|
||||
} else {
|
||||
setMousePosition({
|
||||
x: left,
|
||||
y: top
|
||||
});
|
||||
triggerPreviewOpen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// =========================== Render ===========================
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", _extends({}, otherProps, {
|
||||
className: (0, _clsx.clsx)(prefixCls, rootClassName, classNames.root, {
|
||||
[`${prefixCls}-error`]: status === 'error'
|
||||
}),
|
||||
onClick: canPreview ? onPreview : onClick,
|
||||
role: canPreview ? 'button' : otherProps.role,
|
||||
tabIndex: canPreview && otherProps.tabIndex == null ? 0 : otherProps.tabIndex,
|
||||
"aria-label": canPreview ? otherProps['aria-label'] ?? alt : otherProps['aria-label'],
|
||||
onKeyDown: onPreviewKeyDown,
|
||||
style: {
|
||||
width,
|
||||
height,
|
||||
...styles.root
|
||||
}
|
||||
}), /*#__PURE__*/React.createElement("img", _extends({}, imgCommonProps, {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-img`, {
|
||||
[`${prefixCls}-img-placeholder`]: placeholder === true
|
||||
}, classNames.image, className),
|
||||
style: {
|
||||
height,
|
||||
...styles.image,
|
||||
...style
|
||||
},
|
||||
ref: getImgRef
|
||||
}, srcAndOnload, {
|
||||
width: width,
|
||||
height: height,
|
||||
onError: onError
|
||||
})), status === 'loading' && /*#__PURE__*/React.createElement("div", {
|
||||
"aria-hidden": "true",
|
||||
className: `${prefixCls}-placeholder`
|
||||
}, placeholder), cover !== false && canPreview && /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-cover`, classNames.cover, `${prefixCls}-cover-${coverPlacement}`),
|
||||
style: {
|
||||
display: style?.display === 'none' ? 'none' : undefined,
|
||||
...styles.cover
|
||||
}
|
||||
}, coverNode)), !groupContext && canPreview && /*#__PURE__*/React.createElement(_Preview.default, _extends({
|
||||
"aria-hidden": !isShowPreview,
|
||||
open: isShowPreview,
|
||||
prefixCls: previewPrefixCls,
|
||||
onClose: onPreviewClose,
|
||||
mousePosition: mousePosition,
|
||||
src: src,
|
||||
alt: alt,
|
||||
imageInfo: {
|
||||
width,
|
||||
height
|
||||
},
|
||||
fallback: fallback,
|
||||
imgCommonProps: imgCommonProps
|
||||
}, restProps, {
|
||||
classNames: classNames?.popup,
|
||||
styles: styles?.popup,
|
||||
rootClassName: (0, _clsx.clsx)(previewRootClassName, rootClassName)
|
||||
})));
|
||||
};
|
||||
ImageInternal.PreviewGroup = _PreviewGroup.default;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
ImageInternal.displayName = 'Image';
|
||||
}
|
||||
var _default = exports.default = ImageInternal;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
export interface CloseBtnProps {
|
||||
prefixCls: string;
|
||||
icon?: React.ReactNode;
|
||||
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
export default function CloseBtn(props: CloseBtnProps): React.JSX.Element;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = CloseBtn;
|
||||
var _clsx = require("clsx");
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function CloseBtn(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
icon,
|
||||
onClick,
|
||||
className,
|
||||
style
|
||||
} = props;
|
||||
return /*#__PURE__*/React.createElement("button", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-close`, className),
|
||||
style: style,
|
||||
onClick: onClick
|
||||
}, icon);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import type { Actions, PreviewProps } from '.';
|
||||
import type { ImgInfo } from '../Image';
|
||||
import type { TransformType } from '../hooks/useImageTransform';
|
||||
export type FooterSemanticName = 'footer' | 'actions';
|
||||
export interface FooterProps extends Actions {
|
||||
prefixCls: string;
|
||||
showProgress: boolean;
|
||||
countRender?: PreviewProps['countRender'];
|
||||
actionsRender?: PreviewProps['actionsRender'];
|
||||
current: number;
|
||||
count: number;
|
||||
showSwitch: boolean;
|
||||
icons: PreviewProps['icons'];
|
||||
scale: number;
|
||||
minScale: number;
|
||||
maxScale: number;
|
||||
image: ImgInfo;
|
||||
transform: TransformType;
|
||||
classNames: Partial<Record<FooterSemanticName, string>>;
|
||||
styles: Partial<Record<FooterSemanticName, React.CSSProperties>>;
|
||||
}
|
||||
export default function Footer(props: FooterProps): React.JSX.Element;
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = Footer;
|
||||
var _clsx = require("clsx");
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function Footer(props) {
|
||||
// 修改解构,添加缺失的属性,并提供默认值
|
||||
const {
|
||||
prefixCls,
|
||||
showProgress,
|
||||
current,
|
||||
count,
|
||||
showSwitch,
|
||||
// Style
|
||||
classNames,
|
||||
styles,
|
||||
// render
|
||||
icons,
|
||||
image,
|
||||
transform,
|
||||
countRender,
|
||||
actionsRender,
|
||||
// Scale
|
||||
scale,
|
||||
minScale,
|
||||
maxScale,
|
||||
// Actions
|
||||
onActive,
|
||||
onFlipY,
|
||||
onFlipX,
|
||||
onRotateLeft,
|
||||
onRotateRight,
|
||||
onZoomOut,
|
||||
onZoomIn,
|
||||
onClose,
|
||||
onReset
|
||||
} = props;
|
||||
const {
|
||||
left,
|
||||
right,
|
||||
prev,
|
||||
next,
|
||||
flipY,
|
||||
flipX,
|
||||
rotateLeft,
|
||||
rotateRight,
|
||||
zoomOut,
|
||||
zoomIn
|
||||
} = icons;
|
||||
|
||||
// ========================== Render ==========================
|
||||
// >>>>> Progress
|
||||
const progressNode = showProgress && /*#__PURE__*/React.createElement("div", {
|
||||
className: `${prefixCls}-progress`
|
||||
}, countRender ? countRender(current + 1, count) : /*#__PURE__*/React.createElement("bdi", null, `${current + 1} / ${count}`));
|
||||
|
||||
// >>>>> Actions
|
||||
const actionCls = `${prefixCls}-actions-action`;
|
||||
const renderOperation = ({
|
||||
type,
|
||||
disabled,
|
||||
onClick,
|
||||
icon
|
||||
}) => {
|
||||
return /*#__PURE__*/React.createElement("button", {
|
||||
type: "button",
|
||||
key: type,
|
||||
className: (0, _clsx.clsx)(actionCls, `${actionCls}-${type}`, {
|
||||
[`${actionCls}-disabled`]: !!disabled
|
||||
}),
|
||||
onClick: onClick,
|
||||
disabled: !!disabled,
|
||||
"aria-label": type
|
||||
}, icon);
|
||||
};
|
||||
const switchPrevNode = showSwitch ? renderOperation({
|
||||
icon: prev ?? left,
|
||||
onClick: () => onActive(-1),
|
||||
type: 'prev',
|
||||
disabled: current === 0
|
||||
}) : undefined;
|
||||
const switchNextNode = showSwitch ? renderOperation({
|
||||
icon: next ?? right,
|
||||
onClick: () => onActive(1),
|
||||
type: 'next',
|
||||
disabled: current === count - 1
|
||||
}) : undefined;
|
||||
const flipYNode = renderOperation({
|
||||
icon: flipY,
|
||||
onClick: onFlipY,
|
||||
type: 'flipY'
|
||||
});
|
||||
const flipXNode = renderOperation({
|
||||
icon: flipX,
|
||||
onClick: onFlipX,
|
||||
type: 'flipX'
|
||||
});
|
||||
const rotateLeftNode = renderOperation({
|
||||
icon: rotateLeft,
|
||||
onClick: onRotateLeft,
|
||||
type: 'rotateLeft'
|
||||
});
|
||||
const rotateRightNode = renderOperation({
|
||||
icon: rotateRight,
|
||||
onClick: onRotateRight,
|
||||
type: 'rotateRight'
|
||||
});
|
||||
const zoomOutNode = renderOperation({
|
||||
icon: zoomOut,
|
||||
onClick: onZoomOut,
|
||||
type: 'zoomOut',
|
||||
disabled: scale <= minScale
|
||||
});
|
||||
const zoomInNode = renderOperation({
|
||||
icon: zoomIn,
|
||||
onClick: onZoomIn,
|
||||
type: 'zoomIn',
|
||||
disabled: scale === maxScale
|
||||
});
|
||||
const actionsNode = /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-actions`, classNames.actions),
|
||||
style: styles.actions
|
||||
}, flipYNode, flipXNode, rotateLeftNode, rotateRightNode, zoomOutNode, zoomInNode);
|
||||
|
||||
// >>>>> Render
|
||||
return /*#__PURE__*/React.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-footer`, classNames.footer),
|
||||
style: styles.footer
|
||||
}, progressNode, actionsRender ? actionsRender(actionsNode, {
|
||||
icons: {
|
||||
prevIcon: switchPrevNode,
|
||||
nextIcon: switchNextNode,
|
||||
flipYIcon: flipYNode,
|
||||
flipXIcon: flipXNode,
|
||||
rotateLeftIcon: rotateLeftNode,
|
||||
rotateRightIcon: rotateRightNode,
|
||||
zoomOutIcon: zoomOutNode,
|
||||
zoomInIcon: zoomInNode
|
||||
},
|
||||
actions: {
|
||||
onActive,
|
||||
onFlipY,
|
||||
onFlipX,
|
||||
onRotateLeft,
|
||||
onRotateRight,
|
||||
onZoomOut,
|
||||
onZoomIn,
|
||||
onReset,
|
||||
onClose
|
||||
},
|
||||
transform,
|
||||
current,
|
||||
total: count,
|
||||
image
|
||||
}) : actionsNode);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import type { OperationIcons } from '.';
|
||||
export interface PrevNextProps {
|
||||
prefixCls: string;
|
||||
onActive: (offset: number) => void;
|
||||
current: number;
|
||||
count: number;
|
||||
icons: OperationIcons;
|
||||
}
|
||||
export default function PrevNext(props: PrevNextProps): React.JSX.Element;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = PrevNext;
|
||||
var _clsx = require("clsx");
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function PrevNext(props) {
|
||||
const {
|
||||
prefixCls,
|
||||
onActive,
|
||||
current,
|
||||
count,
|
||||
icons: {
|
||||
left,
|
||||
right,
|
||||
prev,
|
||||
next
|
||||
}
|
||||
} = props;
|
||||
const switchCls = `${prefixCls}-switch`;
|
||||
const prevDisabled = current === 0;
|
||||
const nextDisabled = current === count - 1;
|
||||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
|
||||
className: (0, _clsx.clsx)(switchCls, `${switchCls}-prev`, {
|
||||
[`${switchCls}-disabled`]: prevDisabled
|
||||
}),
|
||||
onClick: () => onActive(-1),
|
||||
disabled: prevDisabled
|
||||
}, prev ?? left), /*#__PURE__*/React.createElement("button", {
|
||||
type: "button",
|
||||
className: (0, _clsx.clsx)(switchCls, `${switchCls}-next`, {
|
||||
[`${switchCls}-disabled`]: nextDisabled
|
||||
}),
|
||||
onClick: () => onActive(1),
|
||||
disabled: nextDisabled
|
||||
}, next ?? right));
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
import { type PortalProps } from '@rc-component/portal';
|
||||
import React from 'react';
|
||||
import type { TransformAction, TransformType } from '../hooks/useImageTransform';
|
||||
import type { ImgInfo } from '../Image';
|
||||
import { type FooterSemanticName } from './Footer';
|
||||
export type PreviewSemanticName = 'root' | 'mask' | 'body' | 'close' | FooterSemanticName;
|
||||
export interface OperationIcons {
|
||||
rotateLeft?: React.ReactNode;
|
||||
rotateRight?: React.ReactNode;
|
||||
zoomIn?: React.ReactNode;
|
||||
zoomOut?: React.ReactNode;
|
||||
close?: React.ReactNode;
|
||||
prev?: React.ReactNode;
|
||||
next?: React.ReactNode;
|
||||
/** @deprecated Please use `prev` instead */
|
||||
left?: React.ReactNode;
|
||||
/** @deprecated Please use `next` instead */
|
||||
right?: React.ReactNode;
|
||||
flipX?: React.ReactNode;
|
||||
flipY?: React.ReactNode;
|
||||
}
|
||||
export interface Actions {
|
||||
onActive: (offset: number) => void;
|
||||
onFlipY: () => void;
|
||||
onFlipX: () => void;
|
||||
onRotateLeft: () => void;
|
||||
onRotateRight: () => void;
|
||||
onZoomOut: () => void;
|
||||
onZoomIn: () => void;
|
||||
onClose: () => void;
|
||||
onReset: () => void;
|
||||
}
|
||||
export type ToolbarRenderInfoType = {
|
||||
icons: {
|
||||
prevIcon?: React.ReactNode;
|
||||
nextIcon?: React.ReactNode;
|
||||
flipYIcon: React.ReactNode;
|
||||
flipXIcon: React.ReactNode;
|
||||
rotateLeftIcon: React.ReactNode;
|
||||
rotateRightIcon: React.ReactNode;
|
||||
zoomOutIcon: React.ReactNode;
|
||||
zoomInIcon: React.ReactNode;
|
||||
};
|
||||
actions: Actions;
|
||||
transform: TransformType;
|
||||
current: number;
|
||||
total: number;
|
||||
image: ImgInfo;
|
||||
};
|
||||
export interface InternalPreviewConfig {
|
||||
/** Better to use `classNames.root` instead */
|
||||
rootClassName?: string;
|
||||
src?: string;
|
||||
alt?: string;
|
||||
scaleStep?: number;
|
||||
minScale?: number;
|
||||
maxScale?: number;
|
||||
motionName?: string;
|
||||
open?: boolean;
|
||||
getContainer?: PortalProps['getContainer'];
|
||||
zIndex?: number;
|
||||
maskClosable?: boolean;
|
||||
afterOpenChange?: (open: boolean) => void;
|
||||
/** Whether to trap focus within the preview when open. Default is true. */
|
||||
focusTrap?: boolean;
|
||||
movable?: boolean;
|
||||
icons?: OperationIcons;
|
||||
closeIcon?: React.ReactNode;
|
||||
onTransform?: (info: {
|
||||
transform: TransformType;
|
||||
action: TransformAction;
|
||||
}) => void;
|
||||
countRender?: (current: number, total: number) => React.ReactNode;
|
||||
imageRender?: (originalNode: React.ReactElement, info: {
|
||||
transform: TransformType;
|
||||
current?: number;
|
||||
image: ImgInfo;
|
||||
}) => React.ReactNode;
|
||||
actionsRender?: (originalNode: React.ReactElement, info: ToolbarRenderInfoType) => React.ReactNode;
|
||||
}
|
||||
export interface PreviewProps extends InternalPreviewConfig {
|
||||
prefixCls: string;
|
||||
classNames?: Partial<Record<PreviewSemanticName, string>>;
|
||||
styles?: Partial<Record<PreviewSemanticName, React.CSSProperties>>;
|
||||
imageInfo?: {
|
||||
width: number | string;
|
||||
height: number | string;
|
||||
};
|
||||
fallback?: string;
|
||||
imgCommonProps?: React.ImgHTMLAttributes<HTMLImageElement>;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
current?: number;
|
||||
count?: number;
|
||||
onChange?: (current: number, prev: number) => void;
|
||||
onClose?: () => void;
|
||||
mousePosition: null | {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
declare const Preview: React.FC<PreviewProps>;
|
||||
export default Preview;
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _motion = _interopRequireDefault(require("@rc-component/motion"));
|
||||
var _portal = _interopRequireDefault(require("@rc-component/portal"));
|
||||
var _util = require("@rc-component/util");
|
||||
var _focus = require("@rc-component/util/lib/Dom/focus");
|
||||
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
|
||||
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
|
||||
var _clsx = require("clsx");
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
var _context = require("../context");
|
||||
var _useImageTransform = _interopRequireDefault(require("../hooks/useImageTransform"));
|
||||
var _useMouseEvent = _interopRequireDefault(require("../hooks/useMouseEvent"));
|
||||
var _useStatus = _interopRequireDefault(require("../hooks/useStatus"));
|
||||
var _useTouchEvent = _interopRequireDefault(require("../hooks/useTouchEvent"));
|
||||
var _previewConfig = require("../previewConfig");
|
||||
var _CloseBtn = _interopRequireDefault(require("./CloseBtn"));
|
||||
var _Footer = _interopRequireDefault(require("./Footer"));
|
||||
var _PrevNext = _interopRequireDefault(require("./PrevNext"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
// Note: if you want to add `action`,
|
||||
// pls contact @zombieJ or @thinkasany first.
|
||||
|
||||
const PreviewImage = ({
|
||||
fallback,
|
||||
src,
|
||||
imgRef,
|
||||
...props
|
||||
}) => {
|
||||
const [getImgRef, srcAndOnload] = (0, _useStatus.default)({
|
||||
src,
|
||||
fallback
|
||||
});
|
||||
return /*#__PURE__*/_react.default.createElement("img", _extends({
|
||||
ref: ref => {
|
||||
imgRef.current = ref;
|
||||
getImgRef(ref);
|
||||
}
|
||||
}, props, srcAndOnload));
|
||||
};
|
||||
const Preview = props => {
|
||||
const {
|
||||
prefixCls,
|
||||
rootClassName,
|
||||
src,
|
||||
alt,
|
||||
imageInfo,
|
||||
fallback,
|
||||
movable = true,
|
||||
onClose,
|
||||
open,
|
||||
afterOpenChange,
|
||||
maskClosable = true,
|
||||
icons = {},
|
||||
closeIcon,
|
||||
getContainer,
|
||||
current = 0,
|
||||
count = 1,
|
||||
countRender,
|
||||
scaleStep = 0.5,
|
||||
minScale = 1,
|
||||
maxScale = 50,
|
||||
motionName = 'fade',
|
||||
imageRender,
|
||||
imgCommonProps,
|
||||
actionsRender,
|
||||
onTransform,
|
||||
onChange,
|
||||
classNames = {},
|
||||
styles = {},
|
||||
mousePosition,
|
||||
zIndex,
|
||||
focusTrap = true
|
||||
} = props;
|
||||
const imgRef = (0, _react.useRef)();
|
||||
const wrapperRef = (0, _react.useRef)(null);
|
||||
const triggerRef = (0, _react.useRef)(null);
|
||||
const groupContext = (0, _react.useContext)(_context.PreviewGroupContext);
|
||||
const showLeftOrRightSwitches = groupContext && count > 1;
|
||||
const showOperationsProgress = groupContext && count >= 1;
|
||||
|
||||
// ======================== Transform =========================
|
||||
const [enableTransition, setEnableTransition] = (0, _react.useState)(true);
|
||||
const {
|
||||
transform,
|
||||
resetTransform,
|
||||
updateTransform,
|
||||
dispatchZoomChange
|
||||
} = (0, _useImageTransform.default)(imgRef, minScale, maxScale, onTransform);
|
||||
const {
|
||||
isMoving,
|
||||
onMouseDown,
|
||||
onWheel
|
||||
} = (0, _useMouseEvent.default)(imgRef, movable, open, scaleStep, transform, updateTransform, dispatchZoomChange);
|
||||
const {
|
||||
isTouching,
|
||||
onTouchStart,
|
||||
onTouchMove,
|
||||
onTouchEnd
|
||||
} = (0, _useTouchEvent.default)(imgRef, movable, open, minScale, transform, updateTransform, dispatchZoomChange);
|
||||
const {
|
||||
rotate,
|
||||
scale
|
||||
} = transform;
|
||||
(0, _react.useEffect)(() => {
|
||||
if (!enableTransition) {
|
||||
setEnableTransition(true);
|
||||
}
|
||||
}, [enableTransition]);
|
||||
(0, _react.useEffect)(() => {
|
||||
if (!open) {
|
||||
resetTransform('close');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// ========================== Image ===========================
|
||||
const onDoubleClick = event => {
|
||||
if (open) {
|
||||
if (scale !== 1) {
|
||||
updateTransform({
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: 1
|
||||
}, 'doubleClick');
|
||||
} else {
|
||||
dispatchZoomChange(_previewConfig.BASE_SCALE_RATIO + scaleStep, 'doubleClick', event.clientX, event.clientY);
|
||||
}
|
||||
}
|
||||
};
|
||||
const imgNode = /*#__PURE__*/_react.default.createElement(PreviewImage, _extends({}, imgCommonProps, {
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
imgRef: imgRef,
|
||||
className: `${prefixCls}-img`,
|
||||
alt: alt,
|
||||
style: {
|
||||
transform: `translate3d(${transform.x}px, ${transform.y}px, 0) scale3d(${transform.flipX ? '-' : ''}${scale}, ${transform.flipY ? '-' : ''}${scale}, 1) rotate(${rotate}deg)`,
|
||||
transitionDuration: (!enableTransition || isTouching) && '0s'
|
||||
},
|
||||
fallback: fallback,
|
||||
src: src,
|
||||
onWheel: onWheel,
|
||||
onMouseDown: onMouseDown,
|
||||
onDoubleClick: onDoubleClick,
|
||||
onTouchStart: onTouchStart,
|
||||
onTouchMove: onTouchMove,
|
||||
onTouchEnd: onTouchEnd,
|
||||
onTouchCancel: onTouchEnd
|
||||
}));
|
||||
const image = {
|
||||
url: src,
|
||||
alt,
|
||||
...imageInfo
|
||||
};
|
||||
|
||||
// ======================== Operation =========================
|
||||
// >>>>> Actions
|
||||
const onZoomIn = () => {
|
||||
dispatchZoomChange(_previewConfig.BASE_SCALE_RATIO + scaleStep, 'zoomIn');
|
||||
};
|
||||
const onZoomOut = () => {
|
||||
dispatchZoomChange(_previewConfig.BASE_SCALE_RATIO / (_previewConfig.BASE_SCALE_RATIO + scaleStep), 'zoomOut');
|
||||
};
|
||||
const onRotateRight = () => {
|
||||
updateTransform({
|
||||
rotate: rotate + 90
|
||||
}, 'rotateRight');
|
||||
};
|
||||
const onRotateLeft = () => {
|
||||
updateTransform({
|
||||
rotate: rotate - 90
|
||||
}, 'rotateLeft');
|
||||
};
|
||||
const onFlipX = () => {
|
||||
updateTransform({
|
||||
flipX: !transform.flipX
|
||||
}, 'flipX');
|
||||
};
|
||||
const onFlipY = () => {
|
||||
updateTransform({
|
||||
flipY: !transform.flipY
|
||||
}, 'flipY');
|
||||
};
|
||||
const onReset = () => {
|
||||
resetTransform('reset');
|
||||
};
|
||||
const onActive = offset => {
|
||||
const nextCurrent = current + offset;
|
||||
if (nextCurrent >= 0 && nextCurrent <= count - 1) {
|
||||
setEnableTransition(false);
|
||||
resetTransform(offset < 0 ? 'prev' : 'next');
|
||||
onChange?.(nextCurrent, current);
|
||||
}
|
||||
};
|
||||
|
||||
// >>>>> Effect: Keyboard
|
||||
const onKeyDown = (0, _util.useEvent)(event => {
|
||||
if (open) {
|
||||
const {
|
||||
keyCode
|
||||
} = event;
|
||||
if (showLeftOrRightSwitches) {
|
||||
if (keyCode === _KeyCode.default.LEFT) {
|
||||
onActive(-1);
|
||||
} else if (keyCode === _KeyCode.default.RIGHT) {
|
||||
onActive(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
(0, _react.useEffect)(() => {
|
||||
if (open) {
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// ======================= Lock Scroll ========================
|
||||
const [lockScroll, setLockScroll] = (0, _react.useState)(false);
|
||||
_react.default.useEffect(() => {
|
||||
if (open) {
|
||||
setLockScroll(true);
|
||||
}
|
||||
}, [open]);
|
||||
const onVisibleChanged = nextVisible => {
|
||||
if (!nextVisible) {
|
||||
setLockScroll(false);
|
||||
|
||||
// Restore focus to the trigger element after leave animation
|
||||
triggerRef.current?.focus?.();
|
||||
triggerRef.current = null;
|
||||
}
|
||||
afterOpenChange?.(nextVisible);
|
||||
};
|
||||
|
||||
// ========================== Portal ==========================
|
||||
const [portalRender, setPortalRender] = (0, _react.useState)(false);
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (open) {
|
||||
setPortalRender(true);
|
||||
}
|
||||
}, [open]);
|
||||
const onEsc = ({
|
||||
top
|
||||
}) => {
|
||||
if (top) {
|
||||
onClose?.();
|
||||
}
|
||||
};
|
||||
|
||||
// =========================== Focus ============================
|
||||
(0, _useLayoutEffect.default)(() => {
|
||||
if (open) {
|
||||
triggerRef.current = document.activeElement;
|
||||
}
|
||||
}, [open]);
|
||||
(0, _focus.useLockFocus)(focusTrap && open && portalRender, () => wrapperRef.current);
|
||||
|
||||
// ========================== Render ==========================
|
||||
const bodyStyle = {
|
||||
...styles.body
|
||||
};
|
||||
if (mousePosition) {
|
||||
bodyStyle.transformOrigin = `${mousePosition.x}px ${mousePosition.y}px`;
|
||||
}
|
||||
return /*#__PURE__*/_react.default.createElement(_portal.default, {
|
||||
open: portalRender && open,
|
||||
autoDestroy: false,
|
||||
getContainer: getContainer,
|
||||
autoLock: lockScroll,
|
||||
onEsc: onEsc
|
||||
}, /*#__PURE__*/_react.default.createElement(_motion.default, {
|
||||
motionName: motionName,
|
||||
visible: portalRender && open,
|
||||
motionAppear: true,
|
||||
motionEnter: true,
|
||||
motionLeave: true,
|
||||
onVisibleChanged: onVisibleChanged
|
||||
}, ({
|
||||
className: motionClassName,
|
||||
style: motionStyle
|
||||
}) => {
|
||||
const mergedStyle = {
|
||||
...styles.root,
|
||||
...motionStyle
|
||||
};
|
||||
if (zIndex) {
|
||||
mergedStyle.zIndex = zIndex;
|
||||
}
|
||||
return /*#__PURE__*/_react.default.createElement("div", {
|
||||
ref: wrapperRef,
|
||||
className: (0, _clsx.clsx)(prefixCls, rootClassName, classNames.root, motionClassName, {
|
||||
[`${prefixCls}-movable`]: movable,
|
||||
[`${prefixCls}-moving`]: isMoving
|
||||
}),
|
||||
style: mergedStyle,
|
||||
role: "dialog",
|
||||
"aria-modal": "true",
|
||||
"aria-label": alt,
|
||||
tabIndex: -1
|
||||
}, /*#__PURE__*/_react.default.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-mask`, classNames.mask),
|
||||
style: styles.mask,
|
||||
onClick: maskClosable ? onClose : undefined
|
||||
}), /*#__PURE__*/_react.default.createElement("div", {
|
||||
className: (0, _clsx.clsx)(`${prefixCls}-body`, classNames.body),
|
||||
style: bodyStyle
|
||||
}, imageRender ? imageRender(imgNode, {
|
||||
transform,
|
||||
image,
|
||||
...(groupContext ? {
|
||||
current
|
||||
} : {})
|
||||
}) : imgNode), closeIcon !== false && closeIcon !== null && /*#__PURE__*/_react.default.createElement(_CloseBtn.default, {
|
||||
prefixCls: prefixCls,
|
||||
icon: closeIcon === true ? icons.close : closeIcon || icons.close,
|
||||
onClick: onClose,
|
||||
className: classNames.close,
|
||||
style: styles.close
|
||||
}), showLeftOrRightSwitches && /*#__PURE__*/_react.default.createElement(_PrevNext.default, {
|
||||
prefixCls: prefixCls,
|
||||
current: current,
|
||||
count: count,
|
||||
icons: icons,
|
||||
onActive: onActive
|
||||
}), /*#__PURE__*/_react.default.createElement(_Footer.default, {
|
||||
prefixCls: prefixCls,
|
||||
showProgress: showOperationsProgress,
|
||||
current: current,
|
||||
count: count,
|
||||
showSwitch: showLeftOrRightSwitches
|
||||
// Style
|
||||
,
|
||||
classNames: classNames,
|
||||
styles: styles
|
||||
// Render
|
||||
,
|
||||
image: image,
|
||||
transform: transform,
|
||||
icons: icons,
|
||||
countRender: countRender,
|
||||
actionsRender: actionsRender
|
||||
// Scale
|
||||
,
|
||||
scale: scale,
|
||||
minScale: minScale,
|
||||
maxScale: maxScale
|
||||
// Actions
|
||||
,
|
||||
onActive: onActive,
|
||||
onFlipY: onFlipY,
|
||||
onFlipX: onFlipX,
|
||||
onRotateLeft: onRotateLeft,
|
||||
onRotateRight: onRotateRight,
|
||||
onZoomOut: onZoomOut,
|
||||
onZoomIn: onZoomIn,
|
||||
onClose: onClose,
|
||||
onReset: onReset
|
||||
}));
|
||||
}));
|
||||
};
|
||||
var _default = exports.default = Preview;
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import * as React from 'react';
|
||||
import type { ImgInfo } from './Image';
|
||||
import type { InternalPreviewConfig, PreviewProps, PreviewSemanticName } from './Preview';
|
||||
import type { TransformType } from './hooks/useImageTransform';
|
||||
import type { ImageElementProps } from './interface';
|
||||
export interface GroupPreviewConfig extends InternalPreviewConfig {
|
||||
current?: number;
|
||||
imageRender?: (originalNode: React.ReactElement, info: {
|
||||
transform: TransformType;
|
||||
current: number;
|
||||
image: ImgInfo;
|
||||
}) => React.ReactNode;
|
||||
onOpenChange?: (value: boolean, info: {
|
||||
current: number;
|
||||
}) => void;
|
||||
onChange?: (current: number, prevCurrent: number) => void;
|
||||
}
|
||||
export interface PreviewGroupProps {
|
||||
previewPrefixCls?: string;
|
||||
classNames?: {
|
||||
popup?: Partial<Record<PreviewSemanticName, string>>;
|
||||
};
|
||||
styles?: {
|
||||
popup?: Partial<Record<PreviewSemanticName, React.CSSProperties>>;
|
||||
};
|
||||
icons?: PreviewProps['icons'];
|
||||
items?: (string | ImageElementProps)[];
|
||||
fallback?: string;
|
||||
preview?: boolean | GroupPreviewConfig;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
declare const Group: React.FC<PreviewGroupProps>;
|
||||
export default Group;
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = void 0;
|
||||
var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState"));
|
||||
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
|
||||
var _react = _interopRequireWildcard(require("react"));
|
||||
var React = _react;
|
||||
var _Preview = _interopRequireDefault(require("./Preview"));
|
||||
var _context = require("./context");
|
||||
var _usePreviewItems = _interopRequireDefault(require("./hooks/usePreviewItems"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
||||
const Group = ({
|
||||
previewPrefixCls = 'rc-image-preview',
|
||||
classNames,
|
||||
styles,
|
||||
children,
|
||||
icons = {},
|
||||
items,
|
||||
preview,
|
||||
fallback
|
||||
}) => {
|
||||
const {
|
||||
open: previewOpen,
|
||||
onOpenChange,
|
||||
current: currentIndex,
|
||||
onChange,
|
||||
...restProps
|
||||
} = preview && typeof preview === 'object' ? preview : {};
|
||||
|
||||
// ========================== Items ===========================
|
||||
const [mergedItems, register, fromItems] = (0, _usePreviewItems.default)(items);
|
||||
|
||||
// ========================= Preview ==========================
|
||||
// >>> Index
|
||||
const [current, setCurrent] = (0, _useControlledState.default)(0, currentIndex);
|
||||
const [keepOpenIndex, setKeepOpenIndex] = (0, _react.useState)(false);
|
||||
|
||||
// >>> Image
|
||||
const {
|
||||
src,
|
||||
...imgCommonProps
|
||||
} = mergedItems[current]?.data || {};
|
||||
// >>> Visible
|
||||
const [isShowPreview, setShowPreview] = (0, _useControlledState.default)(!!previewOpen, previewOpen);
|
||||
const triggerShowPreview = (0, _useEvent.default)(next => {
|
||||
setShowPreview(next);
|
||||
if (next !== isShowPreview) {
|
||||
onOpenChange?.(next, {
|
||||
current
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// >>> Position
|
||||
const [mousePosition, setMousePosition] = (0, _react.useState)(null);
|
||||
const onPreviewFromImage = React.useCallback((id, imageSrc, mouseX, mouseY) => {
|
||||
const index = fromItems ? mergedItems.findIndex(item => item.data.src === imageSrc) : mergedItems.findIndex(item => item.id === id);
|
||||
setCurrent(index < 0 ? 0 : index);
|
||||
triggerShowPreview(true);
|
||||
setMousePosition({
|
||||
x: mouseX,
|
||||
y: mouseY
|
||||
});
|
||||
setKeepOpenIndex(true);
|
||||
}, [mergedItems, fromItems]);
|
||||
|
||||
// Reset current when reopen
|
||||
React.useEffect(() => {
|
||||
if (isShowPreview) {
|
||||
if (!keepOpenIndex) {
|
||||
setCurrent(0);
|
||||
}
|
||||
} else {
|
||||
setKeepOpenIndex(false);
|
||||
}
|
||||
}, [isShowPreview]);
|
||||
|
||||
// ========================== Events ==========================
|
||||
const onInternalChange = (next, prev) => {
|
||||
setCurrent(next);
|
||||
onChange?.(next, prev);
|
||||
};
|
||||
const onPreviewClose = () => {
|
||||
triggerShowPreview(false);
|
||||
setMousePosition(null);
|
||||
};
|
||||
|
||||
// ========================= Context ==========================
|
||||
const previewGroupContext = React.useMemo(() => ({
|
||||
register,
|
||||
onPreview: onPreviewFromImage
|
||||
}), [register, onPreviewFromImage]);
|
||||
|
||||
// ========================== Render ==========================
|
||||
return /*#__PURE__*/React.createElement(_context.PreviewGroupContext.Provider, {
|
||||
value: previewGroupContext
|
||||
}, children, /*#__PURE__*/React.createElement(_Preview.default, _extends({
|
||||
"aria-hidden": !isShowPreview,
|
||||
open: isShowPreview,
|
||||
prefixCls: previewPrefixCls,
|
||||
onClose: onPreviewClose,
|
||||
mousePosition: mousePosition,
|
||||
imgCommonProps: imgCommonProps,
|
||||
src: src,
|
||||
fallback: fallback,
|
||||
icons: icons,
|
||||
current: current,
|
||||
count: mergedItems.length,
|
||||
onChange: onInternalChange
|
||||
}, restProps, {
|
||||
classNames: classNames?.popup,
|
||||
styles: styles?.popup
|
||||
})));
|
||||
};
|
||||
var _default = exports.default = Group;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { ImageElementProps } from './interface';
|
||||
export declare const COMMON_PROPS: (keyof Omit<ImageElementProps, 'src'>)[];
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.COMMON_PROPS = void 0;
|
||||
const COMMON_PROPS = exports.COMMON_PROPS = ['crossOrigin', 'decoding', 'draggable', 'loading', 'referrerPolicy', 'sizes', 'srcSet', 'useMap', 'alt', 'fetchPriority'];
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import type { OnGroupPreview, RegisterImage } from './interface';
|
||||
export interface PreviewGroupContextProps {
|
||||
register: RegisterImage;
|
||||
onPreview: OnGroupPreview;
|
||||
}
|
||||
export declare const PreviewGroupContext: React.Context<PreviewGroupContextProps>;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.PreviewGroupContext = void 0;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
const PreviewGroupContext = exports.PreviewGroupContext = /*#__PURE__*/React.createContext(null);
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Fix positon x,y point when
|
||||
*
|
||||
* Ele width && height < client
|
||||
* - Back origin
|
||||
*
|
||||
* - Ele width | height > clientWidth | clientHeight
|
||||
* - left | top > 0 -> Back 0
|
||||
* - left | top + width | height < clientWidth | clientHeight -> Back left | top + width | height === clientWidth | clientHeight
|
||||
*
|
||||
* Regardless of other
|
||||
*/
|
||||
export default function getFixScaleEleTransPosition(width: number, height: number, left: number, top: number): null | {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
Generated
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = getFixScaleEleTransPosition;
|
||||
var _util = require("./util");
|
||||
function fixPoint(key, start, width, clientWidth) {
|
||||
const startAddWidth = start + width;
|
||||
const offsetStart = (width - clientWidth) / 2;
|
||||
if (width > clientWidth) {
|
||||
if (start > 0) {
|
||||
return {
|
||||
[key]: offsetStart
|
||||
};
|
||||
}
|
||||
if (start < 0 && startAddWidth < clientWidth) {
|
||||
return {
|
||||
[key]: -offsetStart
|
||||
};
|
||||
}
|
||||
} else if (start < 0 || startAddWidth > clientWidth) {
|
||||
return {
|
||||
[key]: start < 0 ? offsetStart : -offsetStart
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix positon x,y point when
|
||||
*
|
||||
* Ele width && height < client
|
||||
* - Back origin
|
||||
*
|
||||
* - Ele width | height > clientWidth | clientHeight
|
||||
* - left | top > 0 -> Back 0
|
||||
* - left | top + width | height < clientWidth | clientHeight -> Back left | top + width | height === clientWidth | clientHeight
|
||||
*
|
||||
* Regardless of other
|
||||
*/
|
||||
function getFixScaleEleTransPosition(width, height, left, top) {
|
||||
const {
|
||||
width: clientWidth,
|
||||
height: clientHeight
|
||||
} = (0, _util.getClientSize)();
|
||||
let fixPos = null;
|
||||
if (width <= clientWidth && height <= clientHeight) {
|
||||
fixPos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
};
|
||||
} else if (width > clientWidth || height > clientHeight) {
|
||||
fixPos = {
|
||||
...fixPoint('x', left, width, clientWidth),
|
||||
...fixPoint('y', top, height, clientHeight)
|
||||
};
|
||||
}
|
||||
return fixPos;
|
||||
}
|
||||
Generated
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
/// <reference types="react" />
|
||||
export type TransformType = {
|
||||
x: number;
|
||||
y: number;
|
||||
rotate: number;
|
||||
scale: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
};
|
||||
export type TransformAction = 'flipY' | 'flipX' | 'rotateLeft' | 'rotateRight' | 'zoomIn' | 'zoomOut' | 'close' | 'prev' | 'next' | 'wheel' | 'doubleClick' | 'move' | 'dragRebound' | 'touchZoom' | 'reset';
|
||||
export type UpdateTransformFunc = (newTransform: Partial<TransformType>, action: TransformAction) => void;
|
||||
export type DispatchZoomChangeFunc = (ratio: number, action: TransformAction, centerX?: number, centerY?: number, isTouch?: boolean) => void;
|
||||
export default function useImageTransform(imgRef: React.MutableRefObject<HTMLImageElement>, minScale: number, maxScale: number, onTransform: (info: {
|
||||
transform: TransformType;
|
||||
action: TransformAction;
|
||||
}) => void): {
|
||||
transform: {
|
||||
x: number;
|
||||
y: number;
|
||||
rotate: number;
|
||||
scale: number;
|
||||
flipX: boolean;
|
||||
flipY: boolean;
|
||||
};
|
||||
resetTransform: (action: TransformAction) => void;
|
||||
updateTransform: UpdateTransformFunc;
|
||||
dispatchZoomChange: DispatchZoomChangeFunc;
|
||||
};
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useImageTransform;
|
||||
var _util = require("../util");
|
||||
var _isEqual = _interopRequireDefault(require("@rc-component/util/lib/isEqual"));
|
||||
var _raf = _interopRequireDefault(require("@rc-component/util/lib/raf"));
|
||||
var _react = require("react");
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
const initialTransform = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
rotate: 0,
|
||||
scale: 1,
|
||||
flipX: false,
|
||||
flipY: false
|
||||
};
|
||||
function useImageTransform(imgRef, minScale, maxScale, onTransform) {
|
||||
const frame = (0, _react.useRef)(null);
|
||||
const queue = (0, _react.useRef)([]);
|
||||
const [transform, setTransform] = (0, _react.useState)(initialTransform);
|
||||
const resetTransform = action => {
|
||||
setTransform(initialTransform);
|
||||
if (!(0, _isEqual.default)(initialTransform, transform)) {
|
||||
onTransform?.({
|
||||
transform: initialTransform,
|
||||
action
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** Direct update transform */
|
||||
const updateTransform = (newTransform, action) => {
|
||||
if (frame.current === null) {
|
||||
queue.current = [];
|
||||
frame.current = (0, _raf.default)(() => {
|
||||
setTransform(preState => {
|
||||
let memoState = preState;
|
||||
queue.current.forEach(queueState => {
|
||||
memoState = {
|
||||
...memoState,
|
||||
...queueState
|
||||
};
|
||||
});
|
||||
frame.current = null;
|
||||
onTransform?.({
|
||||
transform: memoState,
|
||||
action
|
||||
});
|
||||
return memoState;
|
||||
});
|
||||
});
|
||||
}
|
||||
queue.current.push({
|
||||
...transform,
|
||||
...newTransform
|
||||
});
|
||||
};
|
||||
|
||||
/** Scale according to the position of centerX and centerY */
|
||||
const dispatchZoomChange = (ratio, action, centerX, centerY, isTouch) => {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
offsetWidth,
|
||||
offsetHeight,
|
||||
offsetLeft,
|
||||
offsetTop
|
||||
} = imgRef.current;
|
||||
let newRatio = ratio;
|
||||
let newScale = transform.scale * ratio;
|
||||
if (newScale > maxScale) {
|
||||
newScale = maxScale;
|
||||
newRatio = maxScale / transform.scale;
|
||||
} else if (newScale < minScale) {
|
||||
// For mobile interactions, allow scaling down to the minimum scale.
|
||||
newScale = isTouch ? newScale : minScale;
|
||||
newRatio = newScale / transform.scale;
|
||||
}
|
||||
|
||||
/** Default center point scaling */
|
||||
const mergedCenterX = centerX ?? innerWidth / 2;
|
||||
const mergedCenterY = centerY ?? innerHeight / 2;
|
||||
const diffRatio = newRatio - 1;
|
||||
/** Deviation calculated from image size */
|
||||
const diffImgX = diffRatio * width * 0.5;
|
||||
const diffImgY = diffRatio * height * 0.5;
|
||||
/** The difference between the click position and the edge of the document */
|
||||
const diffOffsetLeft = diffRatio * (mergedCenterX - transform.x - offsetLeft);
|
||||
const diffOffsetTop = diffRatio * (mergedCenterY - transform.y - offsetTop);
|
||||
/** Final positioning */
|
||||
let newX = transform.x - (diffOffsetLeft - diffImgX);
|
||||
let newY = transform.y - (diffOffsetTop - diffImgY);
|
||||
|
||||
/**
|
||||
* When zooming the image
|
||||
* When the image size is smaller than the width and height of the window, the position is initialized
|
||||
*/
|
||||
if (ratio < 1 && newScale === 1) {
|
||||
const mergedWidth = offsetWidth * newScale;
|
||||
const mergedHeight = offsetHeight * newScale;
|
||||
const {
|
||||
width: clientWidth,
|
||||
height: clientHeight
|
||||
} = (0, _util.getClientSize)();
|
||||
if (mergedWidth <= clientWidth && mergedHeight <= clientHeight) {
|
||||
newX = 0;
|
||||
newY = 0;
|
||||
}
|
||||
}
|
||||
updateTransform({
|
||||
x: newX,
|
||||
y: newY,
|
||||
scale: newScale
|
||||
}, action);
|
||||
};
|
||||
return {
|
||||
transform,
|
||||
resetTransform,
|
||||
updateTransform,
|
||||
dispatchZoomChange
|
||||
};
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import type React from 'react';
|
||||
import type { DispatchZoomChangeFunc, TransformType, UpdateTransformFunc } from './useImageTransform';
|
||||
export default function useMouseEvent(imgRef: React.MutableRefObject<HTMLImageElement>, movable: boolean, open: boolean, scaleStep: number, transform: TransformType, updateTransform: UpdateTransformFunc, dispatchZoomChange: DispatchZoomChangeFunc): {
|
||||
isMoving: boolean;
|
||||
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseMove: (event: MouseEvent) => void;
|
||||
onMouseUp: () => void;
|
||||
onWheel: (event: React.WheelEvent<HTMLImageElement>) => void;
|
||||
};
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useMouseEvent;
|
||||
var _warning = require("@rc-component/util/lib/warning");
|
||||
var _react = require("react");
|
||||
var _getFixScaleEleTransPosition = _interopRequireDefault(require("../getFixScaleEleTransPosition"));
|
||||
var _previewConfig = require("../previewConfig");
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function useMouseEvent(imgRef, movable, open, scaleStep, transform, updateTransform, dispatchZoomChange) {
|
||||
const {
|
||||
rotate,
|
||||
scale,
|
||||
x,
|
||||
y
|
||||
} = transform;
|
||||
const [isMoving, setMoving] = (0, _react.useState)(false);
|
||||
const startPositionInfo = (0, _react.useRef)({
|
||||
diffX: 0,
|
||||
diffY: 0,
|
||||
transformX: 0,
|
||||
transformY: 0
|
||||
});
|
||||
const onMouseDown = event => {
|
||||
// Only allow main button
|
||||
if (!movable || event.button !== 0) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
startPositionInfo.current = {
|
||||
diffX: event.pageX - x,
|
||||
diffY: event.pageY - y,
|
||||
transformX: x,
|
||||
transformY: y
|
||||
};
|
||||
setMoving(true);
|
||||
};
|
||||
const onMouseMove = event => {
|
||||
if (open && isMoving) {
|
||||
updateTransform({
|
||||
x: event.pageX - startPositionInfo.current.diffX,
|
||||
y: event.pageY - startPositionInfo.current.diffY
|
||||
}, 'move');
|
||||
}
|
||||
};
|
||||
const onMouseUp = () => {
|
||||
if (open && isMoving) {
|
||||
setMoving(false);
|
||||
|
||||
/** No need to restore the position when the picture is not moved, So as not to interfere with the click */
|
||||
const {
|
||||
transformX,
|
||||
transformY
|
||||
} = startPositionInfo.current;
|
||||
const hasChangedPosition = x !== transformX && y !== transformY;
|
||||
if (!hasChangedPosition) return;
|
||||
const width = imgRef.current.offsetWidth * scale;
|
||||
const height = imgRef.current.offsetHeight * scale;
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const {
|
||||
left,
|
||||
top
|
||||
} = imgRef.current.getBoundingClientRect();
|
||||
const isRotate = rotate % 180 !== 0;
|
||||
const fixState = (0, _getFixScaleEleTransPosition.default)(isRotate ? height : width, isRotate ? width : height, left, top);
|
||||
if (fixState) {
|
||||
updateTransform({
|
||||
...fixState
|
||||
}, 'dragRebound');
|
||||
}
|
||||
}
|
||||
};
|
||||
const onWheel = event => {
|
||||
if (!open || event.deltaY == 0) return;
|
||||
// Scale ratio depends on the deltaY size
|
||||
const scaleRatio = Math.abs(event.deltaY / 100);
|
||||
// Limit the maximum scale ratio
|
||||
const mergedScaleRatio = Math.min(scaleRatio, _previewConfig.WHEEL_MAX_SCALE_RATIO);
|
||||
// Scale the ratio each time
|
||||
let ratio = _previewConfig.BASE_SCALE_RATIO + mergedScaleRatio * scaleStep;
|
||||
if (event.deltaY > 0) {
|
||||
ratio = _previewConfig.BASE_SCALE_RATIO / ratio;
|
||||
}
|
||||
dispatchZoomChange(ratio, 'wheel', event.clientX, event.clientY);
|
||||
};
|
||||
(0, _react.useEffect)(() => {
|
||||
if (movable) {
|
||||
window.addEventListener('mouseup', onMouseUp, false);
|
||||
window.addEventListener('mousemove', onMouseMove, false);
|
||||
try {
|
||||
// Resolve if in iframe lost event
|
||||
/* istanbul ignore next */
|
||||
if (window.top !== window.self) {
|
||||
window.top.addEventListener('mouseup', onMouseUp, false);
|
||||
window.top.addEventListener('mousemove', onMouseMove, false);
|
||||
}
|
||||
} catch (error) {
|
||||
/* istanbul ignore next */
|
||||
(0, _warning.warning)(false, `[rc-image] ${error}`);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('mouseup', onMouseUp);
|
||||
window.removeEventListener('mousemove', onMouseMove);
|
||||
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
window.top?.removeEventListener('mouseup', onMouseUp);
|
||||
window.top?.removeEventListener('mousemove', onMouseMove);
|
||||
} catch (error) {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
}, [open, isMoving, x, y, rotate, movable]);
|
||||
return {
|
||||
isMoving,
|
||||
onMouseDown,
|
||||
onMouseMove,
|
||||
onMouseUp,
|
||||
onWheel
|
||||
};
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import type { PreviewGroupProps } from '../PreviewGroup';
|
||||
import type { InternalItem, RegisterImage } from '../interface';
|
||||
export type Items = Omit<InternalItem, 'canPreview'>[];
|
||||
/**
|
||||
* Merge props provided `items` or context collected images
|
||||
*/
|
||||
export default function usePreviewItems(items?: PreviewGroupProps['items']): [items: Items, registerImage: RegisterImage, fromItems: boolean];
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = usePreviewItems;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _common = require("../common");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
/**
|
||||
* Merge props provided `items` or context collected images
|
||||
*/
|
||||
function usePreviewItems(items) {
|
||||
// Context collection image data
|
||||
const [images, setImages] = React.useState({});
|
||||
const registerImage = React.useCallback((id, data) => {
|
||||
setImages(imgs => ({
|
||||
...imgs,
|
||||
[id]: data
|
||||
}));
|
||||
return () => {
|
||||
setImages(imgs => {
|
||||
const cloneImgs = {
|
||||
...imgs
|
||||
};
|
||||
delete cloneImgs[id];
|
||||
return cloneImgs;
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
// items
|
||||
const mergedItems = React.useMemo(() => {
|
||||
// use `items` first
|
||||
if (items) {
|
||||
return items.map(item => {
|
||||
if (typeof item === 'string') {
|
||||
return {
|
||||
data: {
|
||||
src: item
|
||||
}
|
||||
};
|
||||
}
|
||||
const data = {};
|
||||
Object.keys(item).forEach(key => {
|
||||
if (['src', ..._common.COMMON_PROPS].includes(key)) {
|
||||
data[key] = item[key];
|
||||
}
|
||||
});
|
||||
return {
|
||||
data
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// use registered images secondly
|
||||
return Object.keys(images).reduce((total, id) => {
|
||||
const {
|
||||
canPreview,
|
||||
data
|
||||
} = images[id];
|
||||
if (canPreview) {
|
||||
total.push({
|
||||
data,
|
||||
id
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}, []);
|
||||
}, [items, images]);
|
||||
return [mergedItems, registerImage, !!items];
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import type { ImageElementProps } from '../interface';
|
||||
export default function useRegisterImage(canPreview: boolean, data: ImageElementProps): string;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useRegisterImage;
|
||||
var React = _interopRequireWildcard(require("react"));
|
||||
var _context = require("../context");
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
let uid = 0;
|
||||
function useRegisterImage(canPreview, data) {
|
||||
const [id] = React.useState(() => {
|
||||
uid += 1;
|
||||
return String(uid);
|
||||
});
|
||||
const groupContext = React.useContext(_context.PreviewGroupContext);
|
||||
const registerData = {
|
||||
data,
|
||||
canPreview
|
||||
};
|
||||
|
||||
// Keep order start
|
||||
// Resolve https://github.com/ant-design/ant-design/issues/28881
|
||||
// Only need unRegister when component unMount
|
||||
React.useEffect(() => {
|
||||
if (groupContext) {
|
||||
return groupContext.register(id, registerData);
|
||||
}
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
if (groupContext) {
|
||||
groupContext.register(id, registerData);
|
||||
}
|
||||
}, [canPreview, data]);
|
||||
return id;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
type ImageStatus = 'normal' | 'error' | 'loading';
|
||||
export default function useStatus({ src, isCustomPlaceholder, fallback, }: {
|
||||
src: string;
|
||||
isCustomPlaceholder?: boolean;
|
||||
fallback?: string;
|
||||
}): readonly [(img?: HTMLImageElement) => void, {
|
||||
src: string;
|
||||
onLoad?: undefined;
|
||||
} | {
|
||||
onLoad: () => void;
|
||||
src: string;
|
||||
}, ImageStatus];
|
||||
export {};
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useStatus;
|
||||
var _react = require("react");
|
||||
var _util = require("../util");
|
||||
function useStatus({
|
||||
src,
|
||||
isCustomPlaceholder,
|
||||
fallback
|
||||
}) {
|
||||
const [status, setStatus] = (0, _react.useState)(isCustomPlaceholder ? 'loading' : 'normal');
|
||||
const isLoaded = (0, _react.useRef)(false);
|
||||
const isError = status === 'error';
|
||||
|
||||
// https://github.com/react-component/image/pull/187
|
||||
(0, _react.useEffect)(() => {
|
||||
let isCurrentSrc = true;
|
||||
(0, _util.isImageValid)(src).then(isValid => {
|
||||
// https://github.com/ant-design/ant-design/issues/44948
|
||||
// If src changes, the previous setStatus should not be triggered
|
||||
if (!isValid && isCurrentSrc) {
|
||||
setStatus('error');
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
isCurrentSrc = false;
|
||||
};
|
||||
}, [src]);
|
||||
(0, _react.useEffect)(() => {
|
||||
if (isCustomPlaceholder && !isLoaded.current) {
|
||||
setStatus('loading');
|
||||
} else if (isError) {
|
||||
setStatus('normal');
|
||||
}
|
||||
}, [src]);
|
||||
const onLoad = () => {
|
||||
setStatus('normal');
|
||||
};
|
||||
const getImgRef = img => {
|
||||
isLoaded.current = false;
|
||||
if (status === 'loading' && img?.complete && (img.naturalWidth || img.naturalHeight)) {
|
||||
isLoaded.current = true;
|
||||
onLoad();
|
||||
}
|
||||
};
|
||||
const srcAndOnload = isError && fallback ? {
|
||||
src: fallback
|
||||
} : {
|
||||
onLoad,
|
||||
src
|
||||
};
|
||||
return [getImgRef, srcAndOnload, status];
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import type React from 'react';
|
||||
import type { DispatchZoomChangeFunc, TransformType, UpdateTransformFunc } from './useImageTransform';
|
||||
export default function useTouchEvent(imgRef: React.MutableRefObject<HTMLImageElement>, movable: boolean, open: boolean, minScale: number, transform: TransformType, updateTransform: UpdateTransformFunc, dispatchZoomChange: DispatchZoomChangeFunc): {
|
||||
isTouching: boolean;
|
||||
onTouchStart: (event: React.TouchEvent<HTMLImageElement>) => void;
|
||||
onTouchMove: (event: React.TouchEvent<HTMLImageElement>) => void;
|
||||
onTouchEnd: () => void;
|
||||
};
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.default = useTouchEvent;
|
||||
var _react = require("react");
|
||||
var _getFixScaleEleTransPosition = _interopRequireDefault(require("../getFixScaleEleTransPosition"));
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
function getDistance(a, b) {
|
||||
const x = a.x - b.x;
|
||||
const y = a.y - b.y;
|
||||
return Math.hypot(x, y);
|
||||
}
|
||||
function getCenter(oldPoint1, oldPoint2, newPoint1, newPoint2) {
|
||||
// Calculate the distance each point has moved
|
||||
const distance1 = getDistance(oldPoint1, newPoint1);
|
||||
const distance2 = getDistance(oldPoint2, newPoint2);
|
||||
|
||||
// If both distances are 0, return the original points
|
||||
if (distance1 === 0 && distance2 === 0) {
|
||||
return [oldPoint1.x, oldPoint1.y];
|
||||
}
|
||||
|
||||
// Calculate the ratio of the distances
|
||||
const ratio = distance1 / (distance1 + distance2);
|
||||
|
||||
// Calculate the new center point based on the ratio
|
||||
const x = oldPoint1.x + ratio * (oldPoint2.x - oldPoint1.x);
|
||||
const y = oldPoint1.y + ratio * (oldPoint2.y - oldPoint1.y);
|
||||
return [x, y];
|
||||
}
|
||||
function useTouchEvent(imgRef, movable, open, minScale, transform, updateTransform, dispatchZoomChange) {
|
||||
const {
|
||||
rotate,
|
||||
scale,
|
||||
x,
|
||||
y
|
||||
} = transform;
|
||||
const [isTouching, setIsTouching] = (0, _react.useState)(false);
|
||||
const touchPointInfo = (0, _react.useRef)({
|
||||
point1: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
point2: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
eventType: 'none'
|
||||
});
|
||||
const updateTouchPointInfo = values => {
|
||||
touchPointInfo.current = {
|
||||
...touchPointInfo.current,
|
||||
...values
|
||||
};
|
||||
};
|
||||
const onTouchStart = event => {
|
||||
if (!movable) return;
|
||||
event.stopPropagation();
|
||||
setIsTouching(true);
|
||||
const {
|
||||
touches = []
|
||||
} = event;
|
||||
if (touches.length > 1) {
|
||||
// touch zoom
|
||||
updateTouchPointInfo({
|
||||
point1: {
|
||||
x: touches[0].clientX,
|
||||
y: touches[0].clientY
|
||||
},
|
||||
point2: {
|
||||
x: touches[1].clientX,
|
||||
y: touches[1].clientY
|
||||
},
|
||||
eventType: 'touchZoom'
|
||||
});
|
||||
} else {
|
||||
// touch move
|
||||
updateTouchPointInfo({
|
||||
point1: {
|
||||
x: touches[0].clientX - x,
|
||||
y: touches[0].clientY - y
|
||||
},
|
||||
eventType: 'move'
|
||||
});
|
||||
}
|
||||
};
|
||||
const onTouchMove = event => {
|
||||
const {
|
||||
touches = []
|
||||
} = event;
|
||||
const {
|
||||
point1,
|
||||
point2,
|
||||
eventType
|
||||
} = touchPointInfo.current;
|
||||
if (touches.length > 1 && eventType === 'touchZoom') {
|
||||
// touch zoom
|
||||
const newPoint1 = {
|
||||
x: touches[0].clientX,
|
||||
y: touches[0].clientY
|
||||
};
|
||||
const newPoint2 = {
|
||||
x: touches[1].clientX,
|
||||
y: touches[1].clientY
|
||||
};
|
||||
const [centerX, centerY] = getCenter(point1, point2, newPoint1, newPoint2);
|
||||
const ratio = getDistance(newPoint1, newPoint2) / getDistance(point1, point2);
|
||||
dispatchZoomChange(ratio, 'touchZoom', centerX, centerY, true);
|
||||
updateTouchPointInfo({
|
||||
point1: newPoint1,
|
||||
point2: newPoint2,
|
||||
eventType: 'touchZoom'
|
||||
});
|
||||
} else if (eventType === 'move') {
|
||||
// touch move
|
||||
updateTransform({
|
||||
x: touches[0].clientX - point1.x,
|
||||
y: touches[0].clientY - point1.y
|
||||
}, 'move');
|
||||
updateTouchPointInfo({
|
||||
eventType: 'move'
|
||||
});
|
||||
}
|
||||
};
|
||||
const onTouchEnd = () => {
|
||||
if (!open) return;
|
||||
if (isTouching) {
|
||||
setIsTouching(false);
|
||||
}
|
||||
updateTouchPointInfo({
|
||||
eventType: 'none'
|
||||
});
|
||||
if (minScale > scale) {
|
||||
/** When the scaling ratio is less than the minimum scaling ratio, reset the scaling ratio */
|
||||
return updateTransform({
|
||||
x: 0,
|
||||
y: 0,
|
||||
scale: minScale
|
||||
}, 'touchZoom');
|
||||
}
|
||||
const width = imgRef.current.offsetWidth * scale;
|
||||
const height = imgRef.current.offsetHeight * scale;
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
const {
|
||||
left,
|
||||
top
|
||||
} = imgRef.current.getBoundingClientRect();
|
||||
const isRotate = rotate % 180 !== 0;
|
||||
const fixState = (0, _getFixScaleEleTransPosition.default)(isRotate ? height : width, isRotate ? width : height, left, top);
|
||||
if (fixState) {
|
||||
updateTransform({
|
||||
...fixState
|
||||
}, 'dragRebound');
|
||||
}
|
||||
};
|
||||
(0, _react.useEffect)(() => {
|
||||
const preventDefault = e => {
|
||||
e.preventDefault();
|
||||
};
|
||||
if (open && movable) {
|
||||
window.addEventListener('touchmove', preventDefault, {
|
||||
passive: false
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
window.removeEventListener('touchmove', preventDefault);
|
||||
};
|
||||
}, [open, movable]);
|
||||
return {
|
||||
isTouching,
|
||||
onTouchStart,
|
||||
onTouchMove,
|
||||
onTouchEnd
|
||||
};
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import Image from './Image';
|
||||
export * from './Image';
|
||||
export default Image;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
var _exportNames = {};
|
||||
exports.default = void 0;
|
||||
var _Image = _interopRequireWildcard(require("./Image"));
|
||||
Object.keys(_Image).forEach(function (key) {
|
||||
if (key === "default" || key === "__esModule") return;
|
||||
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
||||
if (key in exports && exports[key] === _Image[key]) return;
|
||||
Object.defineProperty(exports, key, {
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
return _Image[key];
|
||||
}
|
||||
});
|
||||
});
|
||||
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
||||
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
||||
var _default = exports.default = _Image.default;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/// <reference types="react" />
|
||||
/**
|
||||
* Used for PreviewGroup passed image data
|
||||
*/
|
||||
export type ImageElementProps = Pick<React.ImgHTMLAttributes<HTMLImageElement>, 'src' | 'crossOrigin' | 'decoding' | 'draggable' | 'loading' | 'referrerPolicy' | 'sizes' | 'srcSet' | 'useMap' | 'alt' | 'fetchPriority'>;
|
||||
export type PreviewImageElementProps = {
|
||||
data: ImageElementProps;
|
||||
canPreview: boolean;
|
||||
};
|
||||
export type InternalItem = PreviewImageElementProps & {
|
||||
id?: string;
|
||||
};
|
||||
export type RegisterImage = (id: string, data: PreviewImageElementProps) => VoidFunction;
|
||||
export type OnGroupPreview = (id: string, imageSrc: string, mouseX: number, mouseY: number) => void;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/** Scale the ratio base */
|
||||
export declare const BASE_SCALE_RATIO = 1;
|
||||
/** The maximum zoom ratio when the mouse zooms in, adjustable */
|
||||
export declare const WHEEL_MAX_SCALE_RATIO = 1;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.WHEEL_MAX_SCALE_RATIO = exports.BASE_SCALE_RATIO = void 0;
|
||||
/** Scale the ratio base */
|
||||
const BASE_SCALE_RATIO = exports.BASE_SCALE_RATIO = 1;
|
||||
/** The maximum zoom ratio when the mouse zooms in, adjustable */
|
||||
const WHEEL_MAX_SCALE_RATIO = exports.WHEEL_MAX_SCALE_RATIO = 1;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export declare function isImageValid(src: string): Promise<unknown>;
|
||||
export declare function getClientSize(): {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", {
|
||||
value: true
|
||||
});
|
||||
exports.getClientSize = getClientSize;
|
||||
exports.isImageValid = isImageValid;
|
||||
function isImageValid(src) {
|
||||
return new Promise(resolve => {
|
||||
if (!src) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
const img = document.createElement('img');
|
||||
img.onerror = () => resolve(false);
|
||||
img.onload = () => resolve(true);
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
// ============================= Legacy =============================
|
||||
function getClientSize() {
|
||||
const width = document.documentElement.clientWidth;
|
||||
const height = window.innerHeight || document.documentElement.clientHeight;
|
||||
return {
|
||||
width,
|
||||
height
|
||||
};
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "@rc-component/image",
|
||||
"version": "1.9.0",
|
||||
"description": "React easy to use image component",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-component",
|
||||
"react-image",
|
||||
"image"
|
||||
],
|
||||
"homepage": "http://github.com/react-component/image",
|
||||
"bugs": {
|
||||
"url": "http://github.com/react-component/image/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:react-component/image.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "./lib/index",
|
||||
"module": "./es/index",
|
||||
"types": "./lib/index.d.ts",
|
||||
"files": [
|
||||
"assets/*.css",
|
||||
"es",
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"compile": "father build && lessc assets/index.less assets/index.css",
|
||||
"coverage": "rc-test --coverage",
|
||||
"docs:build": "dumi build",
|
||||
"docs:deploy": "gh-pages -d docs-dist",
|
||||
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
|
||||
"now-build": "npm run docs:build",
|
||||
"prepublishOnly": "npm run compile && rc-np",
|
||||
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
|
||||
"start": "dumi dev",
|
||||
"test": "rc-test",
|
||||
"test:update": "rc-test -u",
|
||||
"tsc": "bunx tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rc-component/motion": "^1.0.0",
|
||||
"@rc-component/portal": "^2.1.2",
|
||||
"@rc-component/util": "^1.10.1",
|
||||
"clsx": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^5.0.1",
|
||||
"@rc-component/father-plugin": "^2.0.2",
|
||||
"@rc-component/np": "^1.0.0",
|
||||
"@rc-component/dialog": "^1.7.0",
|
||||
"@testing-library/jest-dom": "^6.4.0",
|
||||
"@testing-library/react": "^15.0.6",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@umijs/fabric": "^4.0.1",
|
||||
"dumi": "^2.1.4",
|
||||
"eslint": "^8.57.0",
|
||||
"father": "^4.0.0",
|
||||
"glob": "^11.0.3",
|
||||
"less": "^4.1.3",
|
||||
"rc-test": "^7.0.3",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.9.0",
|
||||
"react-dom": ">=16.9.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user