uix-image-magnify 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +266 -89
- package/dist/{ImageMagnify-DssaU7jS.d.cts → ImageMagnifyGallery-Bw_SkDZq.d.cts} +35 -18
- package/dist/{ImageMagnify-DssaU7jS.d.ts → ImageMagnifyGallery-Bw_SkDZq.d.ts} +35 -18
- package/dist/index.cjs +128 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -50
- package/dist/index.d.ts +3 -50
- package/dist/index.js +130 -75
- package/dist/index.js.map +1 -1
- package/dist/ssr/index.cjs +406 -4
- package/dist/ssr/index.cjs.map +1 -1
- package/dist/ssr/index.d.cts +11 -4
- package/dist/ssr/index.d.ts +11 -4
- package/dist/ssr/index.js +407 -7
- package/dist/ssr/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
**Fast, accessible, SSR-safe image magnifier and product gallery for React & Next.js.**
|
|
4
4
|
|
|
5
|
-
A lightweight, fully-typed React component set for zooming images with a lens, a side-by-side pane, inner pan-and-zoom, or a follow-style magnifier. Ships with a product-style gallery (thumbnails + arrows + counter),
|
|
5
|
+
A lightweight, fully-typed React component set for zooming images with a lens, a side-by-side pane, inner pan-and-zoom, or a follow-style magnifier. Ships with a product-style gallery (thumbnails + arrows + counter), headless hooks, and SSR-safe variants that prevent hydration mismatches in Next.js App Router, Remix, and any other SSR framework.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Single Image Magnifier](#single-image-magnifier)
|
|
15
|
+
- [Zoom modes](#zoom-modes)
|
|
16
|
+
- [Zoom triggers](#zoom-triggers)
|
|
17
|
+
- [Inner mode panning](#inner-mode-panning)
|
|
18
|
+
- [Wheel zoom](#wheel-zoom)
|
|
19
|
+
- [Responsive behaviour](#responsive-behaviour)
|
|
20
|
+
- [Product Gallery](#product-gallery)
|
|
21
|
+
- [Thumbnails](#thumbnails)
|
|
22
|
+
- [Controlled gallery](#controlled-gallery)
|
|
23
|
+
- [Custom arrows](#custom-arrows)
|
|
24
|
+
- [Disable features](#disable-features)
|
|
25
|
+
- [Side-by-side pane inside the gallery](#side-by-side-pane-inside-the-gallery)
|
|
26
|
+
- [SSR & Next.js](#ssr--nextjs)
|
|
27
|
+
- [Hooks](#hooks)
|
|
28
|
+
- [useImageSlider](#useimageslider)
|
|
29
|
+
- [useImageMagnify](#useimagemagnify)
|
|
30
|
+
- [Props Reference](#props-reference)
|
|
31
|
+
- [ImageMagnifyProps](#imagemagnifyprops)
|
|
32
|
+
- [ImageMagnifyGalleryProps](#imagemagnifygalleryprops)
|
|
33
|
+
- [Styling](#styling)
|
|
34
|
+
- [Accessibility](#accessibility)
|
|
35
|
+
- [TypeScript](#typescript)
|
|
36
|
+
- [Package Exports](#package-exports)
|
|
37
|
+
- [Browser Support](#browser-support)
|
|
38
|
+
- [License](#license)
|
|
6
39
|
|
|
7
40
|
---
|
|
8
41
|
|
|
@@ -11,13 +44,14 @@ A lightweight, fully-typed React component set for zooming images with a lens, a
|
|
|
11
44
|
- 🔍 **4 zoom modes** — `lens`, `side-by-side`, `inner`, `follow`
|
|
12
45
|
- 🖱️ **6 zoom triggers** — `hover`, `click`, `double-click`, `hold`, `wheel`, `none`
|
|
13
46
|
- 🖼️ **Product gallery** with thumbnails, arrows, counter, and loop
|
|
14
|
-
- ♿ **Accessible** — keyboard
|
|
47
|
+
- ♿ **Accessible** — keyboard navigation, ARIA roles, live region, `aria-current` thumbnails
|
|
15
48
|
- 🧠 **SSR-safe** — dedicated `/ssr` entry point eliminates hydration mismatches
|
|
16
49
|
- 📱 **Responsive** — zoom pane automatically moves below the image on mobile
|
|
17
50
|
- 🎨 **Customizable** — every part is styleable; arrows accept custom React nodes
|
|
18
51
|
- 🪝 **Headless hooks** — `useImageMagnify`, `useImageSlider`
|
|
19
52
|
- 🧩 **TypeScript-first** with strict prop types
|
|
20
53
|
- 🌲 **Tree-shakeable** — `sideEffects: false`, ESM + CJS builds
|
|
54
|
+
- ⚡ **Performance** — `requestAnimationFrame`-batched DOM writes; zero re-renders during zoom
|
|
21
55
|
|
|
22
56
|
---
|
|
23
57
|
|
|
@@ -33,6 +67,8 @@ pnpm add uix-image-magnify
|
|
|
33
67
|
|
|
34
68
|
`react` and `react-dom` (`>=17`) are peer dependencies. `swiper` is installed automatically as a dependency for the gallery.
|
|
35
69
|
|
|
70
|
+
**Requirements:** Node 18+ for the build toolchain (not for runtime). Modern evergreen browsers (see [Browser Support](#browser-support)).
|
|
71
|
+
|
|
36
72
|
---
|
|
37
73
|
|
|
38
74
|
## Quick Start
|
|
@@ -76,7 +112,7 @@ import { ImageMagnify } from "uix-image-magnify";
|
|
|
76
112
|
lensShape="circle"
|
|
77
113
|
lensSize={160}
|
|
78
114
|
lazy
|
|
79
|
-
|
|
115
|
+
/>;
|
|
80
116
|
```
|
|
81
117
|
|
|
82
118
|
### Zoom modes
|
|
@@ -88,27 +124,27 @@ import { ImageMagnify } from "uix-image-magnify";
|
|
|
88
124
|
| `inner` | The image itself scales and pans inside its own container. |
|
|
89
125
|
| `follow` | A lens follows the cursor with an optional offset. |
|
|
90
126
|
|
|
91
|
-
> **Mobile:** on `side-by-side` + `panePosition="bottom"`, the pane flows below the image instead of being absolutely positioned — no overlap, no clipping.
|
|
92
|
-
|
|
93
127
|
### Zoom triggers
|
|
94
128
|
|
|
95
|
-
| Trigger | Behavior
|
|
96
|
-
| -------------- |
|
|
97
|
-
| `hover` | Zooms while the pointer is over the image.
|
|
98
|
-
| `click` | Toggles zoom on click.
|
|
99
|
-
| `double-click` | Toggles zoom on double-click.
|
|
100
|
-
| `hold` | Zooms only while the pointer is pressed down.
|
|
101
|
-
| `wheel` | Scroll to zoom; supports `minZoomScale
|
|
102
|
-
| `none` | Disables interaction (renders a plain image).
|
|
129
|
+
| Trigger | Behavior |
|
|
130
|
+
| -------------- | --------------------------------------------------------- |
|
|
131
|
+
| `hover` | Zooms while the pointer is over the image. |
|
|
132
|
+
| `click` | Toggles zoom on click. |
|
|
133
|
+
| `double-click` | Toggles zoom on double-click. |
|
|
134
|
+
| `hold` | Zooms only while the pointer is pressed down. |
|
|
135
|
+
| `wheel` | Scroll to zoom; supports `minZoomScale` / `maxZoomScale`. |
|
|
136
|
+
| `none` | Disables interaction (renders a plain image). |
|
|
103
137
|
|
|
104
|
-
>
|
|
138
|
+
> **Touch devices:** interaction behaves as tap-to-toggle regardless of `zoomType` (except `none`).
|
|
139
|
+
>
|
|
140
|
+
> **Wheel mode:** on desktop, zoom is driven by the wheel event only — a plain click does not toggle zoom.
|
|
105
141
|
|
|
106
|
-
###
|
|
142
|
+
### Inner mode panning
|
|
107
143
|
|
|
108
144
|
When `mode="inner"`, set `dragMode`:
|
|
109
145
|
|
|
110
146
|
- `dragMode="follow"` (default) — the pixel under the cursor stays under the cursor.
|
|
111
|
-
- `dragMode="pan"` — drag to pan around the zoomed image.
|
|
147
|
+
- `dragMode="pan"` — drag to pan around the zoomed image. The image is **centered** the first time you zoom in.
|
|
112
148
|
|
|
113
149
|
```tsx
|
|
114
150
|
<ImageMagnify
|
|
@@ -135,11 +171,17 @@ When `mode="inner"`, set `dragMode`:
|
|
|
135
171
|
/>
|
|
136
172
|
```
|
|
137
173
|
|
|
174
|
+
### Responsive behaviour
|
|
175
|
+
|
|
176
|
+
- On screens `≤ 768px` wide, `width` is forced to `"100%"`.
|
|
177
|
+
- When `mode="side-by-side"` and the pane would render to the left/right on mobile, it automatically flows **below** the image instead — no overflow, no clipping.
|
|
178
|
+
- The `useMediaQuery` hook is SSR-safe: it returns `false` during SSR and the first client render, then updates before paint.
|
|
179
|
+
|
|
138
180
|
---
|
|
139
181
|
|
|
140
182
|
## Product Gallery
|
|
141
183
|
|
|
142
|
-
`ImageMagnifyGallery` combines a Swiper-powered slider with the magnifier. It supports thumbnails, arrows, a counter, loop, keyboard navigation, and adjacent-image preloading.
|
|
184
|
+
`ImageMagnifyGallery` combines a Swiper-powered main slider with the magnifier and a lightweight thumbnail strip. It supports thumbnails, arrows, a counter, loop, keyboard navigation, and adjacent-image preloading.
|
|
143
185
|
|
|
144
186
|
```tsx
|
|
145
187
|
import { ImageMagnifyGallery } from "uix-image-magnify";
|
|
@@ -162,6 +204,27 @@ const images = [
|
|
|
162
204
|
thumbnailSize={72}
|
|
163
205
|
thumbnailGap={10}
|
|
164
206
|
onIndexChange={(i) => console.log("active:", i)}
|
|
207
|
+
/>;
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Thumbnails
|
|
211
|
+
|
|
212
|
+
Thumbnails are rendered as a **plain scrollable flex strip** of `<button>` elements — not a nested Swiper. This is significantly faster (no second carousel instance, no duplicated magnifier instances) and gives you full control over styling.
|
|
213
|
+
|
|
214
|
+
- `thumbnailSize` — thumbnail width/height in px (default `80`).
|
|
215
|
+
- `thumbnailGap` — gap between thumbnails in px (default `8`).
|
|
216
|
+
- Clicking a thumbnail navigates the main slider.
|
|
217
|
+
- The active thumbnail receives `aria-current="true"` and merges `thumbnailActiveStyle` over `thumbnailStyle`.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
<ImageMagnifyGallery
|
|
221
|
+
images={images}
|
|
222
|
+
alt="Product"
|
|
223
|
+
thumbnailSize={64}
|
|
224
|
+
thumbnailGap={12}
|
|
225
|
+
thumbnailContainerStyle={{ justifyContent: "center" }}
|
|
226
|
+
thumbnailStyle={{ borderRadius: 8 }}
|
|
227
|
+
thumbnailActiveStyle={{ borderColor: "#0af" }}
|
|
165
228
|
/>
|
|
166
229
|
```
|
|
167
230
|
|
|
@@ -175,9 +238,11 @@ const [active, setActive] = useState(0);
|
|
|
175
238
|
alt="Product"
|
|
176
239
|
activeIndex={active}
|
|
177
240
|
onIndexChange={setActive}
|
|
178
|
-
|
|
241
|
+
/>;
|
|
179
242
|
```
|
|
180
243
|
|
|
244
|
+
When `activeIndex` is provided, the gallery becomes **controlled**: `onIndexChange` fires on every slide change, and external updates to `activeIndex` call `swiper.slideTo(...)`.
|
|
245
|
+
|
|
181
246
|
### Custom arrows
|
|
182
247
|
|
|
183
248
|
```tsx
|
|
@@ -191,6 +256,8 @@ const [active, setActive] = useState(0);
|
|
|
191
256
|
/>
|
|
192
257
|
```
|
|
193
258
|
|
|
259
|
+
Arrow buttons are automatically disabled (`disabled` + `aria-disabled`) at the ends when `loop={false}`.
|
|
260
|
+
|
|
194
261
|
### Disable features
|
|
195
262
|
|
|
196
263
|
```tsx
|
|
@@ -206,22 +273,61 @@ const [active, setActive] = useState(0);
|
|
|
206
273
|
/>
|
|
207
274
|
```
|
|
208
275
|
|
|
276
|
+
### Side-by-side pane inside the gallery
|
|
277
|
+
|
|
278
|
+
Swiper applies `overflow: hidden` to its wrapper, so a `side-by-side` pane positioned `left` or `right` of the image will be **clipped** by the slide boundary. Options:
|
|
279
|
+
|
|
280
|
+
1. Use `mode="lens"`, `mode="inner"`, or `mode="follow"` — these render inside the image bounds.
|
|
281
|
+
2. Use `mode="side-by-side"` with `panePosition="bottom"` and give the slide enough vertical space.
|
|
282
|
+
3. Pass `swiperStyle={{ overflow: "visible" }}` to let the pane escape the slide — note that adjacent slides will then be visible too.
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
// Option 1: use lens mode
|
|
286
|
+
<ImageMagnifyGallery images={images} alt="Product" mode="lens" />
|
|
287
|
+
|
|
288
|
+
// Option 2: bottom pane
|
|
289
|
+
<ImageMagnifyGallery
|
|
290
|
+
images={images}
|
|
291
|
+
alt="Product"
|
|
292
|
+
mode="side-by-side"
|
|
293
|
+
panePosition="bottom"
|
|
294
|
+
paneHeight={320}
|
|
295
|
+
/>
|
|
296
|
+
|
|
297
|
+
// Option 3: opt out of clipping
|
|
298
|
+
<ImageMagnifyGallery
|
|
299
|
+
images={images}
|
|
300
|
+
alt="Product"
|
|
301
|
+
mode="side-by-side"
|
|
302
|
+
panePosition="right"
|
|
303
|
+
swiperStyle={{ overflow: "visible" }}
|
|
304
|
+
/>
|
|
305
|
+
```
|
|
306
|
+
|
|
209
307
|
---
|
|
210
308
|
|
|
211
309
|
## SSR & Next.js
|
|
212
310
|
|
|
213
|
-
The magnifier touches `window`, `matchMedia`, and pointer events. To avoid hydration mismatches, this package ships **SSR-safe wrappers** under the `uix-image-magnify/ssr` entry point:
|
|
311
|
+
The magnifier touches `window`, `matchMedia`, `ResizeObserver`, and pointer events. To avoid hydration mismatches, this package ships **SSR-safe wrappers** under the `uix-image-magnify/ssr` entry point:
|
|
214
312
|
|
|
215
313
|
- `ImageMagnifySSR` — plain `<img>` on server + first client render, swaps to `<ImageMagnify>` after hydration.
|
|
216
314
|
- `ImageMagnifyGallerySSR` — same approach for the gallery.
|
|
217
315
|
|
|
316
|
+
Both non-SSR components are also re-exported from this entry point for convenience.
|
|
317
|
+
|
|
218
318
|
### Option 1 — Use the SSR wrappers (recommended)
|
|
219
319
|
|
|
220
320
|
```tsx
|
|
221
321
|
// app/products/[id]/page.tsx (Server Component-safe import)
|
|
222
322
|
import { ImageMagnifySSR } from "uix-image-magnify/ssr";
|
|
223
323
|
|
|
224
|
-
export default function ProductImage({
|
|
324
|
+
export default function ProductImage({
|
|
325
|
+
src,
|
|
326
|
+
alt,
|
|
327
|
+
}: {
|
|
328
|
+
src: string;
|
|
329
|
+
alt: string;
|
|
330
|
+
}) {
|
|
225
331
|
return (
|
|
226
332
|
<ImageMagnifySSR
|
|
227
333
|
src={src}
|
|
@@ -247,11 +353,11 @@ import dynamic from "next/dynamic";
|
|
|
247
353
|
|
|
248
354
|
const ImageMagnify = dynamic(
|
|
249
355
|
() => import("uix-image-magnify").then((m) => m.ImageMagnify),
|
|
250
|
-
{ ssr: false }
|
|
356
|
+
{ ssr: false },
|
|
251
357
|
);
|
|
252
358
|
```
|
|
253
359
|
|
|
254
|
-
Both approaches produce zero hydration warnings. Option 1 keeps the SSR-rendered `<img>` visible before hydration so users see the image immediately.
|
|
360
|
+
Both approaches produce zero hydration warnings. Option 1 keeps the SSR-rendered `<img>` visible before hydration so users see the image immediately (no layout shift, no blank frame).
|
|
255
361
|
|
|
256
362
|
### Remix / React Router
|
|
257
363
|
|
|
@@ -278,15 +384,26 @@ const { index, setIndex, prev, next, canPrev, canNext } = useImageSlider({
|
|
|
278
384
|
|
|
279
385
|
**Options**
|
|
280
386
|
|
|
281
|
-
| Option | Type | Default | Description
|
|
282
|
-
| --------------- | ------------------------- | ------- |
|
|
283
|
-
| `length` | `number` | — | Total items.
|
|
284
|
-
| `defaultIndex` | `number` | `0` | Initial index (uncontrolled).
|
|
285
|
-
| `index` | `number` | — | Controlled index.
|
|
286
|
-
| `onIndexChange` | `(index: number) => void` | — | Fired when the index changes.
|
|
287
|
-
| `loop` | `boolean` | `true` | Wrap around at the ends.
|
|
387
|
+
| Option | Type | Default | Description |
|
|
388
|
+
| --------------- | ------------------------- | ------- | ----------------------------- |
|
|
389
|
+
| `length` | `number` | — | Total items. |
|
|
390
|
+
| `defaultIndex` | `number` | `0` | Initial index (uncontrolled). |
|
|
391
|
+
| `index` | `number` | — | Controlled index. |
|
|
392
|
+
| `onIndexChange` | `(index: number) => void` | — | Fired when the index changes. |
|
|
393
|
+
| `loop` | `boolean` | `true` | Wrap around at the ends. |
|
|
394
|
+
|
|
395
|
+
**Returns**
|
|
288
396
|
|
|
289
|
-
|
|
397
|
+
| Field | Type | Description |
|
|
398
|
+
| ---------- | ------------------------ | ----------------------------------------------------------- |
|
|
399
|
+
| `index` | `number` | Current (clamped) index. |
|
|
400
|
+
| `canPrev` | `boolean` | Whether a previous item exists (always `true` when `loop`). |
|
|
401
|
+
| `canNext` | `boolean` | Whether a next item exists (always `true` when `loop`). |
|
|
402
|
+
| `setIndex` | `(next: number) => void` | Go to a specific index (wraps when `loop`). |
|
|
403
|
+
| `prev` | `() => void` | Go to previous item. |
|
|
404
|
+
| `next` | `() => void` | Go to next item. |
|
|
405
|
+
|
|
406
|
+
`defaultIndex` is clamped to `[0, length - 1]`.
|
|
290
407
|
|
|
291
408
|
### `useImageMagnify`
|
|
292
409
|
|
|
@@ -312,8 +429,47 @@ const {
|
|
|
312
429
|
zoomType: "hover",
|
|
313
430
|
zoomScale: 2.5,
|
|
314
431
|
});
|
|
432
|
+
|
|
433
|
+
return (
|
|
434
|
+
<div ref={containerRef} {...handlers} style={{ position: "relative" }}>
|
|
435
|
+
<img
|
|
436
|
+
ref={imageRef}
|
|
437
|
+
src="/thumb.jpg"
|
|
438
|
+
alt=""
|
|
439
|
+
style={{ display: "block", width: "100%" }}
|
|
440
|
+
/>
|
|
441
|
+
{isZoomed && (
|
|
442
|
+
<div
|
|
443
|
+
ref={paneRef}
|
|
444
|
+
style={{
|
|
445
|
+
position: "absolute",
|
|
446
|
+
left: "100%",
|
|
447
|
+
top: 0,
|
|
448
|
+
width: 400,
|
|
449
|
+
height: 400,
|
|
450
|
+
backgroundImage: bgImage ? `url("${bgImage}")` : undefined,
|
|
451
|
+
backgroundRepeat: "no-repeat",
|
|
452
|
+
}}
|
|
453
|
+
/>
|
|
454
|
+
)}
|
|
455
|
+
</div>
|
|
456
|
+
);
|
|
315
457
|
```
|
|
316
458
|
|
|
459
|
+
**Return value**
|
|
460
|
+
|
|
461
|
+
| Field | Type | Description |
|
|
462
|
+
| -------------- | ----------------------------- | ----------------------------------------------- |
|
|
463
|
+
| `isZoomed` | `boolean` | Whether zoom is currently active. |
|
|
464
|
+
| `zoomScale` | `number` | Current zoom scale. |
|
|
465
|
+
| `bgImage` | `string \| undefined` | URL to use as the zoomed background. |
|
|
466
|
+
| `containerRef` | `RefObject<HTMLDivElement>` | Attach to the wrapper element. |
|
|
467
|
+
| `imageRef` | `RefObject<HTMLImageElement>` | Attach to the `<img>`. |
|
|
468
|
+
| `lensRef` | `RefObject<HTMLDivElement>` | Attach to the lens element (lens/follow modes). |
|
|
469
|
+
| `paneRef` | `RefObject<HTMLDivElement>` | Attach to the side-by-side pane. |
|
|
470
|
+
| `cursorRef` | `RefObject<HTMLDivElement>` | Attach to the side-by-side cursor dot. |
|
|
471
|
+
| `handlers` | `object` | Spread on the container element. |
|
|
472
|
+
|
|
317
473
|
**Option highlights**
|
|
318
474
|
|
|
319
475
|
| Option | Type | Default |
|
|
@@ -345,57 +501,58 @@ const {
|
|
|
345
501
|
|
|
346
502
|
Extends `UseImageMagnifyOptions` **plus**:
|
|
347
503
|
|
|
348
|
-
| Prop | Type | Default | Description
|
|
349
|
-
| ---------------- | ------------------------------ | ------- |
|
|
350
|
-
| `src` | `string` | — | Regular image URL.
|
|
351
|
-
| `alt` | `string` | — | Accessible label.
|
|
352
|
-
| `width` | `number \| string` | — | Outer width. On mobile, forced to `100%`.
|
|
353
|
-
| `className` | `string` | — | Outer wrapper class.
|
|
354
|
-
| `style` | `CSSProperties` | — | Outer wrapper style.
|
|
355
|
-
| `imageClassName` | `string` | — | `<img>` class.
|
|
356
|
-
| `imageStyle` | `CSSProperties` | — | `<img>` style.
|
|
357
|
-
| `lensClassName` | `string` | — | Lens element class.
|
|
358
|
-
| `lensStyle` | `CSSProperties` | — | Lens element style.
|
|
359
|
-
| `paneClassName` | `string` | — | Side-by-side pane class.
|
|
360
|
-
| `paneStyle` | `CSSProperties` | — | Side-by-side pane style.
|
|
361
|
-
| `paneWidth` | `number \| string` | `400` | Pane width.
|
|
362
|
-
| `paneHeight` | `number \| string` | `400` | Pane height.
|
|
363
|
-
| `lazy` | `boolean` | — | Use `loading="lazy"
|
|
364
|
-
| `imageProps` | `ImgHTMLAttributes` | — | Extra props forwarded to the `<img>`. |
|
|
365
|
-
| `onLoad` | `ImgHTMLAttributes["onLoad"]` | — | Fired on image load.
|
|
366
|
-
| `onError` | `ImgHTMLAttributes["onError"]` | — | Fired on image error.
|
|
504
|
+
| Prop | Type | Default | Description |
|
|
505
|
+
| ---------------- | ------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
506
|
+
| `src` | `string` | — | Regular image URL. |
|
|
507
|
+
| `alt` | `string` | — | Accessible label. |
|
|
508
|
+
| `width` | `number \| string` | — | Outer width. On mobile, forced to `100%`. |
|
|
509
|
+
| `className` | `string` | — | Outer wrapper class. |
|
|
510
|
+
| `style` | `CSSProperties` | — | Outer wrapper style. |
|
|
511
|
+
| `imageClassName` | `string` | — | `<img>` class. |
|
|
512
|
+
| `imageStyle` | `CSSProperties` | — | `<img>` style. |
|
|
513
|
+
| `lensClassName` | `string` | — | Lens element class. |
|
|
514
|
+
| `lensStyle` | `CSSProperties` | — | Lens element style. |
|
|
515
|
+
| `paneClassName` | `string` | — | Side-by-side pane class. |
|
|
516
|
+
| `paneStyle` | `CSSProperties` | — | Side-by-side pane style. |
|
|
517
|
+
| `paneWidth` | `number \| string` | `400` | Pane width. |
|
|
518
|
+
| `paneHeight` | `number \| string` | `400` | Pane height. |
|
|
519
|
+
| `lazy` | `boolean` | — | Use `loading="lazy"` on the `<img>`. |
|
|
520
|
+
| `imageProps` | `ImgHTMLAttributes` | — | Extra props forwarded to the `<img>`. Excludes `src`, `alt`, `style`, `className`, `onLoad`, `onError` (use the dedicated props instead). |
|
|
521
|
+
| `onLoad` | `ImgHTMLAttributes["onLoad"]` | — | Fired on image load. |
|
|
522
|
+
| `onError` | `ImgHTMLAttributes["onError"]` | — | Fired on image error. |
|
|
367
523
|
|
|
368
524
|
### `ImageMagnifyGalleryProps`
|
|
369
525
|
|
|
370
526
|
Extends `ImageMagnifyProps` **minus** `src` / `onLoad` / `onError`, plus:
|
|
371
527
|
|
|
372
|
-
| Prop | Type | Default | Description
|
|
373
|
-
| ----------------------------- | ------------------------- | ------- |
|
|
374
|
-
| `images` | `string[]` | — | Image URLs.
|
|
375
|
-
| `alt` | `string` | — | Alt text (shared).
|
|
376
|
-
| `activeIndex` | `number` | — | Controlled index.
|
|
377
|
-
| `defaultIndex` | `number` | `0` | Initial index (uncontrolled).
|
|
378
|
-
| `onIndexChange` | `(index: number) => void` | — | Fired when the active slide changes.
|
|
379
|
-
| `showArrows` | `boolean` | `true` | Prev/next arrows.
|
|
380
|
-
| `showThumbnails` | `boolean` | `true` | Thumbnail strip.
|
|
381
|
-
| `showCounter` | `boolean` | `false` | "1 / N" counter overlay.
|
|
382
|
-
| `loop` | `boolean` | `true` | Infinite loop.
|
|
383
|
-
| `enableKeyboard` | `boolean` | `true` | Arrow-key navigation.
|
|
384
|
-
| `preloadAdjacent` | `boolean` | `true` | Preload next/prev images.
|
|
385
|
-
| `viewportClassName` | `string` | — | Main carousel viewport class.
|
|
386
|
-
| `viewportStyle` | `CSSProperties` | — | Main carousel viewport style.
|
|
387
|
-
| `
|
|
388
|
-
| `
|
|
389
|
-
| `
|
|
390
|
-
| `
|
|
391
|
-
| `
|
|
392
|
-
| `
|
|
393
|
-
| `
|
|
394
|
-
| `
|
|
395
|
-
| `
|
|
396
|
-
| `
|
|
397
|
-
| `
|
|
398
|
-
| `
|
|
528
|
+
| Prop | Type | Default | Description |
|
|
529
|
+
| ----------------------------- | ------------------------- | ------- | --------------------------------------------- |
|
|
530
|
+
| `images` | `string[]` | — | Image URLs. |
|
|
531
|
+
| `alt` | `string` | — | Alt text (shared; per-slide alt is appended). |
|
|
532
|
+
| `activeIndex` | `number` | — | Controlled index. |
|
|
533
|
+
| `defaultIndex` | `number` | `0` | Initial index (uncontrolled). |
|
|
534
|
+
| `onIndexChange` | `(index: number) => void` | — | Fired when the active slide changes. |
|
|
535
|
+
| `showArrows` | `boolean` | `true` | Prev/next arrows. |
|
|
536
|
+
| `showThumbnails` | `boolean` | `true` | Thumbnail strip. |
|
|
537
|
+
| `showCounter` | `boolean` | `false` | "1 / N" counter overlay. |
|
|
538
|
+
| `loop` | `boolean` | `true` | Infinite loop. |
|
|
539
|
+
| `enableKeyboard` | `boolean` | `true` | Arrow-key navigation. |
|
|
540
|
+
| `preloadAdjacent` | `boolean` | `true` | Preload next/prev images (respects `loop`). |
|
|
541
|
+
| `viewportClassName` | `string` | — | Main carousel viewport class. |
|
|
542
|
+
| `viewportStyle` | `CSSProperties` | — | Main carousel viewport style. |
|
|
543
|
+
| `swiperStyle` | `CSSProperties` | — | Style applied to the inner Swiper element. |
|
|
544
|
+
| `thumbnailContainerClassName` | `string` | — | Thumbnail strip class. |
|
|
545
|
+
| `thumbnailContainerStyle` | `CSSProperties` | — | Thumbnail strip style. |
|
|
546
|
+
| `thumbnailClassName` | `string` | — | Thumbnail button class. |
|
|
547
|
+
| `thumbnailActiveClassName` | `string` | — | Added to the active thumbnail button. |
|
|
548
|
+
| `thumbnailStyle` | `CSSProperties` | — | Thumbnail button style. |
|
|
549
|
+
| `thumbnailActiveStyle` | `CSSProperties` | — | Merged over `thumbnailStyle` when active. |
|
|
550
|
+
| `arrowClassName` | `string` | — | Arrow button class. |
|
|
551
|
+
| `arrowStyle` | `CSSProperties` | — | Arrow button style. |
|
|
552
|
+
| `thumbnailSize` | `number` | `80` | Thumbnail width/height (px). |
|
|
553
|
+
| `thumbnailGap` | `number` | `8` | Thumbnail gap (px). |
|
|
554
|
+
| `prevArrow` | `ReactNode` | — | Custom previous arrow. |
|
|
555
|
+
| `nextArrow` | `ReactNode` | — | Custom next arrow. |
|
|
399
556
|
|
|
400
557
|
---
|
|
401
558
|
|
|
@@ -441,27 +598,38 @@ The gallery ships with inline defaults for arrows and thumbnails that you can ov
|
|
|
441
598
|
alt="Product"
|
|
442
599
|
thumbnailSize={64}
|
|
443
600
|
thumbnailGap={12}
|
|
601
|
+
thumbnailContainerStyle={{ justifyContent: "center" }}
|
|
444
602
|
thumbnailStyle={{ borderRadius: 8 }}
|
|
445
603
|
thumbnailActiveStyle={{ borderColor: "#0af" }}
|
|
446
604
|
arrowStyle={{ background: "#111", color: "#fff" }}
|
|
447
605
|
/>
|
|
448
606
|
```
|
|
449
607
|
|
|
608
|
+
Because the gallery does not import any CSS from `swiper/css`, only the base Swiper runtime is used (no default arrow/thumbnail styles leak in). All visuals come from inline styles + your classes.
|
|
609
|
+
|
|
450
610
|
---
|
|
451
611
|
|
|
452
612
|
## Accessibility
|
|
453
613
|
|
|
454
614
|
- Root gallery uses `role="region"`, `aria-roledescription="carousel"`, `aria-label`.
|
|
455
|
-
- Each image container is `role="img"
|
|
615
|
+
- Each image container is `role="img"` with `aria-label={alt}`. It is focusable (`tabIndex={0}`) **only when interaction is enabled** (`zoomType !== "none"` and not `disabled`).
|
|
456
616
|
- Hidden `aria-live="polite"` region announces **"Image X of Y"** on slide change.
|
|
457
|
-
-
|
|
458
|
-
|
|
459
|
-
- **+ / = / -** — zoom in/out (when `zoomType="wheel"`).
|
|
460
|
-
- **Enter / Space** — toggle zoom.
|
|
461
|
-
- **Esc** — reset zoom.
|
|
617
|
+
- Thumbnails are `<button>` elements with `aria-label` and `aria-current="true"` for the active one; the strip is a `role="group"` labelled "Image thumbnails".
|
|
618
|
+
- Arrow buttons get `aria-disabled` and `disabled` at the ends when `loop={false}`.
|
|
462
619
|
- Disabled state exposes `aria-disabled`.
|
|
620
|
+
- Swiper's `A11y` module is enabled for the main slider.
|
|
463
621
|
- Custom arrows accept any `ReactNode`; default icons are `<svg aria-hidden="true">` with `aria-label` on the buttons.
|
|
464
622
|
|
|
623
|
+
### Keyboard controls
|
|
624
|
+
|
|
625
|
+
| Key | Action |
|
|
626
|
+
| ----------------- | ------------------------------------------------- |
|
|
627
|
+
| **← / → / ↑ / ↓** | Move the zoom pointer (image container). |
|
|
628
|
+
| **+ / = / -** | Zoom in/out (when `zoomType="wheel"`). |
|
|
629
|
+
| **Enter / Space** | Toggle zoom. |
|
|
630
|
+
| **Esc** | Reset zoom. |
|
|
631
|
+
| **← / →** | Navigate slides (gallery, when `enableKeyboard`). |
|
|
632
|
+
|
|
465
633
|
---
|
|
466
634
|
|
|
467
635
|
## TypeScript
|
|
@@ -487,10 +655,12 @@ import type {
|
|
|
487
655
|
|
|
488
656
|
## Package Exports
|
|
489
657
|
|
|
490
|
-
| Import path | Contents
|
|
491
|
-
| ----------------------- |
|
|
492
|
-
| `uix-image-magnify` | `ImageMagnify`, `ImageMagnifyGallery`, `useImageMagnify`, `useImageSlider` + all types
|
|
493
|
-
| `uix-image-magnify/ssr` | `ImageMagnifySSR`, `ImageMagnifyGallerySSR
|
|
658
|
+
| Import path | Contents |
|
|
659
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
660
|
+
| `uix-image-magnify` | `ImageMagnify`, `ImageMagnifyGallery`, `useImageMagnify`, `useImageSlider` + all types |
|
|
661
|
+
| `uix-image-magnify/ssr` | `ImageMagnifySSR`, `ImageMagnifyGallerySSR`, plus non-SSR re-exports (`ImageMagnify`, `ImageMagnifyGallery`) and their types |
|
|
662
|
+
|
|
663
|
+
Both entry points are dual-published (ESM + CJS) with bundled TypeScript declarations.
|
|
494
664
|
|
|
495
665
|
---
|
|
496
666
|
|
|
@@ -503,7 +673,7 @@ Modern evergreen browsers (Chrome, Edge, Firefox, Safari). Requires:
|
|
|
503
673
|
- Pointer Events
|
|
504
674
|
- CSS `transform` / `background-position`
|
|
505
675
|
|
|
506
|
-
SSR wrappers render plain `<img>` on the server for maximum compatibility.
|
|
676
|
+
SSR wrappers render plain `<img>` on the server for maximum compatibility. The library uses React `>=17` and has no other runtime peer dependencies besides `swiper`.
|
|
507
677
|
|
|
508
678
|
---
|
|
509
679
|
|
|
@@ -513,6 +683,13 @@ MIT © uix-image-magnify contributors
|
|
|
513
683
|
|
|
514
684
|
---
|
|
515
685
|
|
|
516
|
-
|
|
686
|
+
## Contributing
|
|
687
|
+
|
|
688
|
+
Issues and PRs are welcome. Before submitting, please run:
|
|
689
|
+
|
|
690
|
+
```bash
|
|
691
|
+
npm run typecheck
|
|
692
|
+
npm run build
|
|
693
|
+
```
|
|
517
694
|
|
|
518
|
-
|
|
695
|
+
Both must pass cleanly.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { CSSProperties, ImgHTMLAttributes } from 'react';
|
|
2
|
+
import { CSSProperties, ImgHTMLAttributes, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
type ZoomType = "hover" | "click" | "double-click" | "hold" | "wheel" | "none";
|
|
5
5
|
type PanePosition = "left" | "right" | "top" | "bottom";
|
|
@@ -7,35 +7,20 @@ type LensShape = "circle" | "square";
|
|
|
7
7
|
type MagnifyMode = "lens" | "side-by-side" | "inner" | "follow";
|
|
8
8
|
type DragMode = "follow" | "pan";
|
|
9
9
|
interface UseImageMagnifyOptions {
|
|
10
|
-
/** Base image URL (required when used standalone). */
|
|
11
10
|
src?: string;
|
|
12
|
-
/** High-resolution image used for the zoomed view. Defaults to `src`. */
|
|
13
11
|
zoomSrc?: string;
|
|
14
|
-
/** Magnification factor (2 = 200%). */
|
|
15
12
|
zoomScale?: number;
|
|
16
|
-
/** Minimum scale for wheel/keyboard zoom. Default `1.5`. */
|
|
17
13
|
minZoomScale?: number;
|
|
18
|
-
/** Maximum scale for wheel/keyboard zoom. Default `5`. */
|
|
19
14
|
maxZoomScale?: number;
|
|
20
|
-
/** Interaction that activates zoom. */
|
|
21
15
|
zoomType?: ZoomType;
|
|
22
|
-
/** How the zoomed content is displayed. */
|
|
23
16
|
mode?: MagnifyMode;
|
|
24
|
-
/** Drag interaction for `inner` mode. */
|
|
25
17
|
dragMode?: DragMode;
|
|
26
|
-
/** Where the side-by-side pane sits relative to the image. */
|
|
27
18
|
panePosition?: PanePosition;
|
|
28
|
-
/** Diameter / side length of the lens in px. */
|
|
29
19
|
lensSize?: number;
|
|
30
|
-
/** Lens shape. */
|
|
31
20
|
lensShape?: LensShape;
|
|
32
|
-
/** Offset (both axes, px) for `mode="follow"`. */
|
|
33
21
|
followOffset?: number;
|
|
34
|
-
/** Disable all interaction. */
|
|
35
22
|
disabled?: boolean;
|
|
36
|
-
/** Preload the high-res image to avoid a flash. */
|
|
37
23
|
zoomPreload?: boolean;
|
|
38
|
-
/** Reset zoom when the pointer leaves the container. */
|
|
39
24
|
resetOnLeave?: boolean;
|
|
40
25
|
onZoomStart?: () => void;
|
|
41
26
|
onZoomEnd?: () => void;
|
|
@@ -79,10 +64,42 @@ interface ImageMagnifyProps extends UseImageMagnifyOptions {
|
|
|
79
64
|
paneWidth?: number | string;
|
|
80
65
|
paneHeight?: number | string;
|
|
81
66
|
lazy?: boolean;
|
|
82
|
-
imageProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt" | "style">;
|
|
67
|
+
imageProps?: Omit<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt" | "style" | "className" | "onLoad" | "onError">;
|
|
83
68
|
onLoad?: ImgHTMLAttributes<HTMLImageElement>["onLoad"];
|
|
84
69
|
onError?: ImgHTMLAttributes<HTMLImageElement>["onError"];
|
|
85
70
|
}
|
|
86
71
|
declare const ImageMagnify: React.ForwardRefExoticComponent<ImageMagnifyProps & React.RefAttributes<HTMLDivElement>>;
|
|
87
72
|
|
|
88
|
-
|
|
73
|
+
interface ImageMagnifyGalleryProps extends Omit<ImageMagnifyProps, "src" | "onLoad" | "onError"> {
|
|
74
|
+
images: string[];
|
|
75
|
+
alt: string;
|
|
76
|
+
activeIndex?: number;
|
|
77
|
+
defaultIndex?: number;
|
|
78
|
+
onIndexChange?: (index: number) => void;
|
|
79
|
+
showArrows?: boolean;
|
|
80
|
+
showThumbnails?: boolean;
|
|
81
|
+
showCounter?: boolean;
|
|
82
|
+
loop?: boolean;
|
|
83
|
+
enableKeyboard?: boolean;
|
|
84
|
+
preloadAdjacent?: boolean;
|
|
85
|
+
className?: string;
|
|
86
|
+
style?: CSSProperties;
|
|
87
|
+
viewportClassName?: string;
|
|
88
|
+
viewportStyle?: CSSProperties;
|
|
89
|
+
swiperStyle?: CSSProperties;
|
|
90
|
+
thumbnailContainerClassName?: string;
|
|
91
|
+
thumbnailContainerStyle?: CSSProperties;
|
|
92
|
+
thumbnailClassName?: string;
|
|
93
|
+
thumbnailActiveClassName?: string;
|
|
94
|
+
thumbnailStyle?: CSSProperties;
|
|
95
|
+
thumbnailActiveStyle?: CSSProperties;
|
|
96
|
+
arrowClassName?: string;
|
|
97
|
+
arrowStyle?: CSSProperties;
|
|
98
|
+
thumbnailSize?: number;
|
|
99
|
+
thumbnailGap?: number;
|
|
100
|
+
prevArrow?: ReactNode;
|
|
101
|
+
nextArrow?: ReactNode;
|
|
102
|
+
}
|
|
103
|
+
declare const ImageMagnifyGallery: React.ForwardRefExoticComponent<ImageMagnifyGalleryProps & React.RefAttributes<HTMLDivElement>>;
|
|
104
|
+
|
|
105
|
+
export { type DragMode as D, ImageMagnify as I, type LensShape as L, type MagnifyMode as M, type PanePosition as P, type UseImageMagnifyOptions as U, type ZoomType as Z, ImageMagnifyGallery as a, type ImageMagnifyGalleryProps as b, type ImageMagnifyProps as c, useImageMagnify as u };
|