@simple-photo-gallery/common 1.0.5 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/client.d.ts +240 -0
- package/dist/client.js +433 -0
- package/dist/client.js.map +1 -0
- package/dist/gallery.cjs +32 -2
- package/dist/gallery.cjs.map +1 -1
- package/dist/gallery.d.cts +106 -6
- package/dist/gallery.d.ts +106 -6
- package/dist/gallery.js +32 -3
- package/dist/gallery.js.map +1 -1
- package/dist/styles/photoswipe/photoswipe.css +138 -0
- package/dist/theme.d.ts +442 -0
- package/dist/theme.js +387 -0
- package/dist/theme.js.map +1 -0
- package/package.json +25 -2
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @simple-photo-gallery/common
|
|
2
|
+
|
|
3
|
+
Shared utilities and types for Simple Photo Gallery themes and CLI.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @simple-photo-gallery/common
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Package Exports
|
|
12
|
+
|
|
13
|
+
- `.` - Gallery types and Zod validation schemas
|
|
14
|
+
- `./theme` - Theme utilities for data loading and resolution
|
|
15
|
+
- `./client` - Browser-side utilities (PhotoSwipe, blurhash, CSS)
|
|
16
|
+
- `./styles/photoswipe` - PhotoSwipe CSS bundle
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import PhotoSwipeLightbox from 'photoswipe/lightbox';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Decode a single blurhash and draw it to a canvas element.
|
|
5
|
+
*
|
|
6
|
+
* @param canvas - The canvas element with a data-blur-hash attribute
|
|
7
|
+
* @param width - The width to decode at (default: 32)
|
|
8
|
+
* @param height - The height to decode at (default: 32)
|
|
9
|
+
*/
|
|
10
|
+
declare function decodeBlurhashToCanvas(canvas: HTMLCanvasElement, width?: number, height?: number): void;
|
|
11
|
+
/**
|
|
12
|
+
* Decode and render all blurhash canvases on the page.
|
|
13
|
+
* Finds all canvas elements with data-blur-hash attribute and draws the decoded image.
|
|
14
|
+
*
|
|
15
|
+
* @param selector - CSS selector for canvas elements (default: 'canvas[data-blur-hash]')
|
|
16
|
+
* @param width - The width to decode at (default: 32)
|
|
17
|
+
* @param height - The height to decode at (default: 32)
|
|
18
|
+
*/
|
|
19
|
+
declare function decodeAllBlurhashes(selector?: string, width?: number, height?: number): void;
|
|
20
|
+
|
|
21
|
+
/** Options for the hero image fallback behavior */
|
|
22
|
+
interface HeroImageFallbackOptions {
|
|
23
|
+
/** CSS selector for the picture element (default: '#hero-bg-picture') */
|
|
24
|
+
pictureSelector?: string;
|
|
25
|
+
/** CSS selector for the img element within picture (default: 'img.hero__bg-img') */
|
|
26
|
+
imgSelector?: string;
|
|
27
|
+
/** CSS selector for the blurhash canvas element (default: 'canvas[data-blur-hash]') */
|
|
28
|
+
canvasSelector?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Initialize hero image fallback behavior.
|
|
32
|
+
* Handles:
|
|
33
|
+
* - Hiding blurhash canvas when image loads successfully
|
|
34
|
+
* - Removing source elements and retrying with fallback src on error
|
|
35
|
+
* - Keeping blurhash visible if final fallback also fails
|
|
36
|
+
*
|
|
37
|
+
* @param options - Configuration options for selectors
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import { initHeroImageFallback } from '@simple-photo-gallery/common/client';
|
|
42
|
+
*
|
|
43
|
+
* // Use default selectors
|
|
44
|
+
* initHeroImageFallback();
|
|
45
|
+
*
|
|
46
|
+
* // Or with custom selectors
|
|
47
|
+
* initHeroImageFallback({
|
|
48
|
+
* pictureSelector: '#my-hero-picture',
|
|
49
|
+
* imgSelector: 'img.my-hero-img',
|
|
50
|
+
* canvasSelector: 'canvas.my-blurhash',
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function initHeroImageFallback(options?: HeroImageFallbackOptions): void;
|
|
55
|
+
|
|
56
|
+
/** Options for the PhotoSwipe video plugin */
|
|
57
|
+
interface VideoPluginOptions {
|
|
58
|
+
/** HTML attributes to apply to video elements */
|
|
59
|
+
videoAttributes?: Record<string, string>;
|
|
60
|
+
/** Whether to autoplay videos when they become active */
|
|
61
|
+
autoplay?: boolean;
|
|
62
|
+
/** Pixels from bottom of video where drag is prevented (for video controls) */
|
|
63
|
+
preventDragOffset?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Options for creating a gallery lightbox */
|
|
67
|
+
interface GalleryLightboxOptions {
|
|
68
|
+
/** CSS selector for gallery container (default: '.gallery-grid') */
|
|
69
|
+
gallerySelector?: string;
|
|
70
|
+
/** CSS selector for gallery items within container (default: 'a') */
|
|
71
|
+
childrenSelector?: string;
|
|
72
|
+
/** Animation duration when opening in ms (default: 300) */
|
|
73
|
+
showAnimationDuration?: number;
|
|
74
|
+
/** Animation duration when closing in ms (default: 300) */
|
|
75
|
+
hideAnimationDuration?: number;
|
|
76
|
+
/** Enable mouse wheel zoom (default: true) */
|
|
77
|
+
wheelToZoom?: boolean;
|
|
78
|
+
/** Loop back to first image after last (default: false) */
|
|
79
|
+
loop?: boolean;
|
|
80
|
+
/** Background opacity 0-1 (default: 1) */
|
|
81
|
+
bgOpacity?: number;
|
|
82
|
+
/** Options for the video plugin */
|
|
83
|
+
videoPluginOptions?: VideoPluginOptions;
|
|
84
|
+
/** Enable slide-in animations when changing slides (default: true) */
|
|
85
|
+
slideAnimations?: boolean;
|
|
86
|
+
/** Enable custom caption UI from data-pswp-caption attribute (default: true) */
|
|
87
|
+
enableCaptions?: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Create a PhotoSwipe lightbox with sensible defaults and video support.
|
|
91
|
+
* Returns the lightbox instance for further customization before calling init().
|
|
92
|
+
*
|
|
93
|
+
* IMPORTANT: You must import the PhotoSwipe CSS files for the lightbox to work properly:
|
|
94
|
+
* - `import 'photoswipe/style.css'` - Base PhotoSwipe styles
|
|
95
|
+
* - `import '@simple-photo-gallery/common/styles/photoswipe'` - SPG enhancement styles
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* import { createGalleryLightbox } from '@simple-photo-gallery/common/client';
|
|
100
|
+
* import 'photoswipe/style.css';
|
|
101
|
+
* import '@simple-photo-gallery/common/styles/photoswipe';
|
|
102
|
+
*
|
|
103
|
+
* // Basic usage
|
|
104
|
+
* const lightbox = await createGalleryLightbox();
|
|
105
|
+
* lightbox.init();
|
|
106
|
+
*
|
|
107
|
+
* // With custom options
|
|
108
|
+
* const lightbox = await createGalleryLightbox({
|
|
109
|
+
* gallerySelector: '.my-gallery',
|
|
110
|
+
* loop: true,
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* // Add custom event handlers before init
|
|
114
|
+
* lightbox.on('change', () => console.log('slide changed'));
|
|
115
|
+
* lightbox.init();
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare function createGalleryLightbox(options?: GalleryLightboxOptions): Promise<PhotoSwipeLightbox>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* PhotoSwipe plugin that adds video support to the lightbox.
|
|
122
|
+
* Videos are automatically detected by the `data-pswp-type="video"` attribute
|
|
123
|
+
* on gallery links.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* import PhotoSwipe from 'photoswipe';
|
|
128
|
+
* import PhotoSwipeLightbox from 'photoswipe/lightbox';
|
|
129
|
+
* import { PhotoSwipeVideoPlugin } from '@simple-photo-gallery/common/client';
|
|
130
|
+
*
|
|
131
|
+
* const lightbox = new PhotoSwipeLightbox({
|
|
132
|
+
* gallery: '.gallery',
|
|
133
|
+
* children: 'a',
|
|
134
|
+
* pswpModule: PhotoSwipe,
|
|
135
|
+
* });
|
|
136
|
+
*
|
|
137
|
+
* new PhotoSwipeVideoPlugin(lightbox);
|
|
138
|
+
* lightbox.init();
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
declare class PhotoSwipeVideoPlugin {
|
|
142
|
+
constructor(lightbox: PhotoSwipeLightbox, options?: VideoPluginOptions);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Options for URL deep-linking functionality */
|
|
146
|
+
interface DeepLinkingOptions {
|
|
147
|
+
/** CSS selector for galleries (default: '.gallery-grid') */
|
|
148
|
+
gallerySelector?: string;
|
|
149
|
+
/** CSS selector for gallery items (default: 'a') */
|
|
150
|
+
itemSelector?: string;
|
|
151
|
+
/** URL parameter name for image ID (default: 'image') */
|
|
152
|
+
parameterName?: string;
|
|
153
|
+
/** Delay in ms before opening image on page load (default: 100) */
|
|
154
|
+
openDelay?: number;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Updates browser URL with current image ID.
|
|
158
|
+
* Uses History API to replace state without page reload.
|
|
159
|
+
*/
|
|
160
|
+
declare function updateGalleryURL(imageId: string | null, paramName?: string): void;
|
|
161
|
+
/**
|
|
162
|
+
* Extracts image ID from URL parameters.
|
|
163
|
+
*/
|
|
164
|
+
declare function getImageIdFromURL(paramName?: string): string | null;
|
|
165
|
+
/**
|
|
166
|
+
* Opens a specific image by ID within a gallery.
|
|
167
|
+
* Searches through all gallery items and opens matching image in lightbox.
|
|
168
|
+
*
|
|
169
|
+
* @returns true if image was found and opened, false otherwise
|
|
170
|
+
*/
|
|
171
|
+
declare function openImageById(lightbox: PhotoSwipeLightbox, imageId: string, options?: DeepLinkingOptions): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Sets up automatic URL updates when lightbox changes.
|
|
174
|
+
* Call after creating lightbox but before init().
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```typescript
|
|
178
|
+
* const lightbox = await createGalleryLightbox();
|
|
179
|
+
* setupDeepLinking(lightbox);
|
|
180
|
+
* lightbox.init();
|
|
181
|
+
* restoreFromURL(lightbox);
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare function setupDeepLinking(lightbox: PhotoSwipeLightbox, options?: DeepLinkingOptions): void;
|
|
185
|
+
/**
|
|
186
|
+
* Restores gallery state from URL on page load.
|
|
187
|
+
* Automatically opens image if specified in URL parameters.
|
|
188
|
+
* Call after lightbox.init().
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* const lightbox = await createGalleryLightbox();
|
|
193
|
+
* setupDeepLinking(lightbox);
|
|
194
|
+
* lightbox.init();
|
|
195
|
+
* restoreFromURL(lightbox);
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
declare function restoreFromURL(lightbox: PhotoSwipeLightbox, options?: DeepLinkingOptions): void;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* CSS utility functions for client-side theming and color manipulation.
|
|
202
|
+
* These utilities are browser-only and require DOM access.
|
|
203
|
+
*/
|
|
204
|
+
/**
|
|
205
|
+
* Normalizes hex color values to 6-digit format (e.g., #abc -> #aabbcc).
|
|
206
|
+
* Returns null if the hex value is invalid.
|
|
207
|
+
*
|
|
208
|
+
* @param hex - The hex color value to normalize (with or without #)
|
|
209
|
+
* @returns The normalized 6-digit hex color with # prefix, or null if invalid
|
|
210
|
+
*/
|
|
211
|
+
declare function normalizeHex(hex: string): string | null;
|
|
212
|
+
/**
|
|
213
|
+
* Parses and validates a color value.
|
|
214
|
+
* Supports CSS color names, hex values, rgb/rgba, and 'transparent'.
|
|
215
|
+
* Returns null if the color is invalid.
|
|
216
|
+
*
|
|
217
|
+
* @param colorParam - The color string to parse
|
|
218
|
+
* @returns The validated color string, or null if invalid
|
|
219
|
+
*/
|
|
220
|
+
declare function parseColor(colorParam: string | null): string | null;
|
|
221
|
+
/**
|
|
222
|
+
* Sets or removes a CSS custom property (variable) on an element.
|
|
223
|
+
* Removes the property if value is null.
|
|
224
|
+
*
|
|
225
|
+
* @param element - The HTML element to modify
|
|
226
|
+
* @param name - The CSS variable name (e.g., '--my-color')
|
|
227
|
+
* @param value - The value to set, or null to remove
|
|
228
|
+
*/
|
|
229
|
+
declare function setCSSVar(element: HTMLElement, name: string, value: string | null): void;
|
|
230
|
+
/**
|
|
231
|
+
* Derives a color with adjusted opacity from an existing color.
|
|
232
|
+
* Converts rgb to rgba if needed, or adjusts existing rgba opacity.
|
|
233
|
+
*
|
|
234
|
+
* @param color - The source color (rgb, rgba, or other CSS color)
|
|
235
|
+
* @param opacity - The target opacity (0-1)
|
|
236
|
+
* @returns The color with adjusted opacity, or original if not rgb/rgba
|
|
237
|
+
*/
|
|
238
|
+
declare function deriveOpacityColor(color: string, opacity: number): string;
|
|
239
|
+
|
|
240
|
+
export { type DeepLinkingOptions, type GalleryLightboxOptions, type HeroImageFallbackOptions, PhotoSwipeVideoPlugin, type VideoPluginOptions, createGalleryLightbox, decodeAllBlurhashes, decodeBlurhashToCanvas, deriveOpacityColor, getImageIdFromURL, initHeroImageFallback, normalizeHex, openImageById, parseColor, restoreFromURL, setCSSVar, setupDeepLinking, updateGalleryURL };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { decode } from 'blurhash';
|
|
2
|
+
|
|
3
|
+
// src/client/blurhash.ts
|
|
4
|
+
function decodeBlurhashToCanvas(canvas, width = 32, height = 32) {
|
|
5
|
+
const blurHashValue = canvas.dataset.blurHash;
|
|
6
|
+
if (!blurHashValue) return;
|
|
7
|
+
const pixels = decode(blurHashValue, width, height);
|
|
8
|
+
const ctx = canvas.getContext("2d");
|
|
9
|
+
if (pixels && ctx) {
|
|
10
|
+
const imageData = new ImageData(new Uint8ClampedArray(pixels), width, height);
|
|
11
|
+
ctx.putImageData(imageData, 0, 0);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function decodeAllBlurhashes(selector = "canvas[data-blur-hash]", width = 32, height = 32) {
|
|
15
|
+
const canvases = document.querySelectorAll(selector);
|
|
16
|
+
for (const canvas of canvases) {
|
|
17
|
+
decodeBlurhashToCanvas(canvas, width, height);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/client/hero-fallback.ts
|
|
22
|
+
function initHeroImageFallback(options = {}) {
|
|
23
|
+
const {
|
|
24
|
+
pictureSelector = "#hero-bg-picture",
|
|
25
|
+
imgSelector = "img.hero__bg-img",
|
|
26
|
+
canvasSelector = "canvas[data-blur-hash]"
|
|
27
|
+
} = options;
|
|
28
|
+
const picture = document.querySelector(pictureSelector);
|
|
29
|
+
const img = picture?.querySelector(imgSelector);
|
|
30
|
+
const canvas = document.querySelector(canvasSelector);
|
|
31
|
+
if (!img) return;
|
|
32
|
+
const fallbackSrc = img.getAttribute("src") || "";
|
|
33
|
+
let didFallback = false;
|
|
34
|
+
const hideBlurhash = () => {
|
|
35
|
+
if (canvas) {
|
|
36
|
+
canvas.style.display = "none";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const doFallback = () => {
|
|
40
|
+
if (didFallback) return;
|
|
41
|
+
didFallback = true;
|
|
42
|
+
if (picture) {
|
|
43
|
+
for (const sourceEl of picture.querySelectorAll("source")) {
|
|
44
|
+
sourceEl.remove();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const current = img.getAttribute("src") || "";
|
|
48
|
+
img.setAttribute("src", "");
|
|
49
|
+
img.setAttribute("src", fallbackSrc || current);
|
|
50
|
+
img.addEventListener(
|
|
51
|
+
"error",
|
|
52
|
+
() => {
|
|
53
|
+
},
|
|
54
|
+
{ once: true }
|
|
55
|
+
);
|
|
56
|
+
img.addEventListener("load", hideBlurhash, { once: true });
|
|
57
|
+
};
|
|
58
|
+
if (img.complete) {
|
|
59
|
+
if (img.naturalWidth === 0) {
|
|
60
|
+
doFallback();
|
|
61
|
+
} else {
|
|
62
|
+
hideBlurhash();
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
img.addEventListener("load", hideBlurhash, { once: true });
|
|
66
|
+
}
|
|
67
|
+
img.addEventListener("error", doFallback, { once: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/client/photoswipe/video-plugin.ts
|
|
71
|
+
var defaultOptions = {
|
|
72
|
+
videoAttributes: { controls: "", playsinline: "", preload: "auto" },
|
|
73
|
+
autoplay: true,
|
|
74
|
+
preventDragOffset: 40
|
|
75
|
+
};
|
|
76
|
+
function isVideoContent(content) {
|
|
77
|
+
return content && "data" in content && content.data && content.data.type === "video";
|
|
78
|
+
}
|
|
79
|
+
var VideoContentSetup = class {
|
|
80
|
+
constructor(lightbox, options) {
|
|
81
|
+
this.options = options;
|
|
82
|
+
this.initLightboxEvents(lightbox);
|
|
83
|
+
lightbox.on("init", () => {
|
|
84
|
+
if (lightbox.pswp) {
|
|
85
|
+
this.initPswpEvents(lightbox.pswp);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
initLightboxEvents(lightbox) {
|
|
90
|
+
lightbox.on("contentLoad", (data) => this.onContentLoad(data));
|
|
91
|
+
lightbox.on("contentDestroy", (data) => this.onContentDestroy(data));
|
|
92
|
+
lightbox.on("contentActivate", (data) => this.onContentActivate(data));
|
|
93
|
+
lightbox.on("contentDeactivate", (data) => this.onContentDeactivate(data));
|
|
94
|
+
lightbox.on("contentAppend", (data) => this.onContentAppend(data));
|
|
95
|
+
lightbox.on("contentResize", (data) => this.onContentResize(data));
|
|
96
|
+
lightbox.addFilter(
|
|
97
|
+
"isKeepingPlaceholder",
|
|
98
|
+
(value, ...args) => this.isKeepingPlaceholder(value, args[0])
|
|
99
|
+
);
|
|
100
|
+
lightbox.addFilter(
|
|
101
|
+
"isContentZoomable",
|
|
102
|
+
(value, ...args) => this.isContentZoomable(value, args[0])
|
|
103
|
+
);
|
|
104
|
+
lightbox.addFilter(
|
|
105
|
+
"useContentPlaceholder",
|
|
106
|
+
(value, ...args) => this.useContentPlaceholder(value, args[0])
|
|
107
|
+
);
|
|
108
|
+
lightbox.addFilter("domItemData", (value, ...args) => {
|
|
109
|
+
const itemData = value;
|
|
110
|
+
const linkEl = args[1];
|
|
111
|
+
if (itemData.type === "video" && linkEl) {
|
|
112
|
+
if (linkEl.dataset.pswpVideoSources) {
|
|
113
|
+
itemData.videoSources = JSON.parse(linkEl.dataset.pswpVideoSources);
|
|
114
|
+
} else if (linkEl.dataset.pswpVideoSrc) {
|
|
115
|
+
itemData.videoSrc = linkEl.dataset.pswpVideoSrc;
|
|
116
|
+
} else {
|
|
117
|
+
itemData.videoSrc = linkEl.href;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return itemData;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
initPswpEvents(pswp) {
|
|
124
|
+
pswp.on("pointerDown", (data) => {
|
|
125
|
+
const e = data;
|
|
126
|
+
const slide = pswp.currSlide;
|
|
127
|
+
if (slide && isVideoContent(slide) && this.options.preventDragOffset) {
|
|
128
|
+
const origEvent = e.originalEvent;
|
|
129
|
+
if (origEvent && origEvent.type === "pointerdown") {
|
|
130
|
+
const videoHeight = Math.ceil(slide.height * slide.currZoomLevel);
|
|
131
|
+
const verticalEnding = videoHeight + slide.bounds.center.y;
|
|
132
|
+
const pointerYPos = origEvent.pageY - pswp.offset.y;
|
|
133
|
+
if (pointerYPos > verticalEnding - this.options.preventDragOffset && pointerYPos < verticalEnding) {
|
|
134
|
+
e.preventDefault?.();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
pswp.on("appendHeavy", (data) => {
|
|
140
|
+
const e = data;
|
|
141
|
+
if (e.slide && isVideoContent(e.slide) && !e.slide.isActive) {
|
|
142
|
+
e.preventDefault?.();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
pswp.on("close", () => {
|
|
146
|
+
const slide = pswp.currSlide;
|
|
147
|
+
if (slide && isVideoContent(slide.content)) {
|
|
148
|
+
if (!pswp.options.showHideAnimationType || pswp.options.showHideAnimationType === "zoom") {
|
|
149
|
+
pswp.options.showHideAnimationType = "fade";
|
|
150
|
+
}
|
|
151
|
+
this.pauseVideo(slide.content);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
onContentDestroy({ content }) {
|
|
156
|
+
if (isVideoContent(content) && content._videoPosterImg) {
|
|
157
|
+
const handleLoad = () => {
|
|
158
|
+
if (content._videoPosterImg) {
|
|
159
|
+
content._videoPosterImg.removeEventListener("error", handleError);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const handleError = () => {
|
|
163
|
+
};
|
|
164
|
+
content._videoPosterImg.addEventListener("load", handleLoad);
|
|
165
|
+
content._videoPosterImg.addEventListener("error", handleError);
|
|
166
|
+
content._videoPosterImg = void 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
onContentResize(e) {
|
|
170
|
+
if (e.content && isVideoContent(e.content)) {
|
|
171
|
+
e.preventDefault?.();
|
|
172
|
+
const width = e.width;
|
|
173
|
+
const height = e.height;
|
|
174
|
+
const content = e.content;
|
|
175
|
+
if (content.element) {
|
|
176
|
+
content.element.style.width = width + "px";
|
|
177
|
+
content.element.style.height = height + "px";
|
|
178
|
+
}
|
|
179
|
+
if (content.slide && content.slide.placeholder) {
|
|
180
|
+
const placeholderElStyle = content.slide.placeholder.element.style;
|
|
181
|
+
placeholderElStyle.transform = "none";
|
|
182
|
+
placeholderElStyle.width = width + "px";
|
|
183
|
+
placeholderElStyle.height = height + "px";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
isKeepingPlaceholder(isZoomable, content) {
|
|
188
|
+
if (isVideoContent(content)) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return isZoomable;
|
|
192
|
+
}
|
|
193
|
+
isContentZoomable(isZoomable, content) {
|
|
194
|
+
if (isVideoContent(content)) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
return isZoomable;
|
|
198
|
+
}
|
|
199
|
+
onContentActivate({ content }) {
|
|
200
|
+
if (isVideoContent(content) && this.options.autoplay) {
|
|
201
|
+
this.playVideo(content);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
onContentDeactivate({ content }) {
|
|
205
|
+
if (isVideoContent(content)) {
|
|
206
|
+
this.pauseVideo(content);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
onContentAppend(e) {
|
|
210
|
+
if (e.content && isVideoContent(e.content)) {
|
|
211
|
+
e.preventDefault?.();
|
|
212
|
+
e.content.isAttached = true;
|
|
213
|
+
e.content.appendImage?.();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
onContentLoad(e) {
|
|
217
|
+
const content = e.content;
|
|
218
|
+
if (!isVideoContent(content)) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
e.preventDefault?.();
|
|
222
|
+
if (content.element) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
content.state = "loading";
|
|
226
|
+
content.type = "video";
|
|
227
|
+
content.element = document.createElement("video");
|
|
228
|
+
if (this.options.videoAttributes) {
|
|
229
|
+
for (const key in this.options.videoAttributes) {
|
|
230
|
+
content.element.setAttribute(key, this.options.videoAttributes[key] || "");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
content.element.setAttribute("poster", content.data.msrc || "");
|
|
234
|
+
this.preloadVideoPoster(content, content.data.msrc);
|
|
235
|
+
content.element.style.position = "absolute";
|
|
236
|
+
content.element.style.left = "0";
|
|
237
|
+
content.element.style.top = "0";
|
|
238
|
+
if (content.data.videoSources) {
|
|
239
|
+
for (const source of content.data.videoSources) {
|
|
240
|
+
const sourceEl = document.createElement("source");
|
|
241
|
+
sourceEl.src = source.src;
|
|
242
|
+
sourceEl.type = source.type;
|
|
243
|
+
content.element.append(sourceEl);
|
|
244
|
+
}
|
|
245
|
+
} else if (content.data.videoSrc) {
|
|
246
|
+
content.element.src = content.data.videoSrc;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
preloadVideoPoster(content, src) {
|
|
250
|
+
if (!content._videoPosterImg && src) {
|
|
251
|
+
content._videoPosterImg = new Image();
|
|
252
|
+
content._videoPosterImg.src = src;
|
|
253
|
+
if (content._videoPosterImg.complete) {
|
|
254
|
+
content.onLoaded?.();
|
|
255
|
+
} else {
|
|
256
|
+
content._videoPosterImg.addEventListener("load", () => {
|
|
257
|
+
content.onLoaded?.();
|
|
258
|
+
});
|
|
259
|
+
content._videoPosterImg.addEventListener("error", () => {
|
|
260
|
+
content.onLoaded?.();
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
playVideo(content) {
|
|
266
|
+
if (content.element) {
|
|
267
|
+
content.element.play();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
pauseVideo(content) {
|
|
271
|
+
if (content.element) {
|
|
272
|
+
content.element.pause();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
useContentPlaceholder(usePlaceholder, content) {
|
|
276
|
+
if (isVideoContent(content)) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
return usePlaceholder;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
var PhotoSwipeVideoPlugin = class {
|
|
283
|
+
constructor(lightbox, options = {}) {
|
|
284
|
+
new VideoContentSetup(lightbox, {
|
|
285
|
+
...defaultOptions,
|
|
286
|
+
...options
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/client/photoswipe/lightbox.ts
|
|
292
|
+
async function createGalleryLightbox(options = {}) {
|
|
293
|
+
const photoswipeModule = await import('photoswipe');
|
|
294
|
+
const PhotoSwipe = photoswipeModule.default;
|
|
295
|
+
const lightboxModule = await import('photoswipe/lightbox');
|
|
296
|
+
const PhotoSwipeLightboxModule = lightboxModule.default;
|
|
297
|
+
const lightbox = new PhotoSwipeLightboxModule({
|
|
298
|
+
gallery: options.gallerySelector ?? ".gallery-grid",
|
|
299
|
+
children: options.childrenSelector ?? "a",
|
|
300
|
+
pswpModule: PhotoSwipe,
|
|
301
|
+
showAnimationDuration: options.showAnimationDuration ?? 300,
|
|
302
|
+
hideAnimationDuration: options.hideAnimationDuration ?? 300,
|
|
303
|
+
wheelToZoom: options.wheelToZoom ?? true,
|
|
304
|
+
loop: options.loop ?? false,
|
|
305
|
+
bgOpacity: options.bgOpacity ?? 1
|
|
306
|
+
});
|
|
307
|
+
new PhotoSwipeVideoPlugin(lightbox, options.videoPluginOptions);
|
|
308
|
+
if (options.slideAnimations !== false) {
|
|
309
|
+
lightbox.on("contentDeactivate", ({ content }) => {
|
|
310
|
+
content.element?.classList.remove("pswp__img--in-viewport");
|
|
311
|
+
});
|
|
312
|
+
lightbox.on("contentActivate", ({ content }) => {
|
|
313
|
+
content.element?.classList.add("pswp__img--in-viewport");
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (options.enableCaptions !== false) {
|
|
317
|
+
lightbox.on("uiRegister", () => {
|
|
318
|
+
lightbox.pswp?.ui?.registerElement({
|
|
319
|
+
name: "custom-caption",
|
|
320
|
+
isButton: false,
|
|
321
|
+
className: "pswp__caption",
|
|
322
|
+
appendTo: "wrapper",
|
|
323
|
+
onInit: (el) => {
|
|
324
|
+
lightbox.pswp?.on("change", () => {
|
|
325
|
+
const currSlideElement = lightbox.pswp?.currSlide?.data.element;
|
|
326
|
+
if (currSlideElement) {
|
|
327
|
+
const caption = currSlideElement.dataset.pswpCaption;
|
|
328
|
+
el.innerHTML = caption || currSlideElement.querySelector("img")?.alt || "";
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return lightbox;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/client/photoswipe/deep-linking.ts
|
|
339
|
+
function updateGalleryURL(imageId, paramName = "image") {
|
|
340
|
+
const url = new URL(globalThis.location.href);
|
|
341
|
+
if (imageId) {
|
|
342
|
+
url.searchParams.set(paramName, imageId);
|
|
343
|
+
} else {
|
|
344
|
+
url.searchParams.delete(paramName);
|
|
345
|
+
}
|
|
346
|
+
globalThis.history.replaceState({}, "", url.toString());
|
|
347
|
+
}
|
|
348
|
+
function getImageIdFromURL(paramName = "image") {
|
|
349
|
+
const params = new URLSearchParams(globalThis.location.search);
|
|
350
|
+
return params.get(paramName);
|
|
351
|
+
}
|
|
352
|
+
function openImageById(lightbox, imageId, options = {}) {
|
|
353
|
+
const gallerySelector = options.gallerySelector ?? ".gallery-grid";
|
|
354
|
+
const itemSelector = options.itemSelector ?? "a";
|
|
355
|
+
const galleries = document.querySelectorAll(gallerySelector);
|
|
356
|
+
const allItems = [];
|
|
357
|
+
for (const gallery of galleries) {
|
|
358
|
+
const items = gallery.querySelectorAll(itemSelector);
|
|
359
|
+
allItems.push(...items);
|
|
360
|
+
}
|
|
361
|
+
for (const [i, item] of allItems.entries()) {
|
|
362
|
+
const itemId = item.dataset.imageId || "";
|
|
363
|
+
if (itemId === imageId) {
|
|
364
|
+
lightbox.loadAndOpen(i);
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
function setupDeepLinking(lightbox, options = {}) {
|
|
371
|
+
const paramName = options.parameterName ?? "image";
|
|
372
|
+
lightbox.on("change", () => {
|
|
373
|
+
if (lightbox.pswp) {
|
|
374
|
+
const currSlideElement = lightbox.pswp.currSlide?.data.element;
|
|
375
|
+
const imageId = currSlideElement?.dataset?.imageId ?? null;
|
|
376
|
+
updateGalleryURL(imageId, paramName);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
lightbox.on("close", () => {
|
|
380
|
+
updateGalleryURL(null, paramName);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
function restoreFromURL(lightbox, options = {}) {
|
|
384
|
+
const paramName = options.parameterName ?? "image";
|
|
385
|
+
const openDelay = options.openDelay ?? 100;
|
|
386
|
+
const imageIdFromURL = getImageIdFromURL(paramName);
|
|
387
|
+
if (imageIdFromURL) {
|
|
388
|
+
const open = () => {
|
|
389
|
+
setTimeout(() => openImageById(lightbox, imageIdFromURL, options), openDelay);
|
|
390
|
+
};
|
|
391
|
+
if (document.readyState === "loading") {
|
|
392
|
+
document.addEventListener("DOMContentLoaded", open);
|
|
393
|
+
} else {
|
|
394
|
+
open();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// src/client/css-utils.ts
|
|
400
|
+
function normalizeHex(hex) {
|
|
401
|
+
hex = hex.replace("#", "");
|
|
402
|
+
if (hex.length === 3) hex = [...hex].map((c) => c + c).join("");
|
|
403
|
+
return hex.length === 6 && /^[0-9A-Fa-f]{6}$/.test(hex) ? `#${hex}` : null;
|
|
404
|
+
}
|
|
405
|
+
function parseColor(colorParam) {
|
|
406
|
+
if (!colorParam) return null;
|
|
407
|
+
const normalized = colorParam.toLowerCase().trim();
|
|
408
|
+
if (normalized === "transparent") return "transparent";
|
|
409
|
+
const testEl = document.createElement("div");
|
|
410
|
+
testEl.style.color = normalized;
|
|
411
|
+
if (testEl.style.color) return normalized;
|
|
412
|
+
return normalizeHex(colorParam);
|
|
413
|
+
}
|
|
414
|
+
function setCSSVar(element, name, value) {
|
|
415
|
+
if (value) {
|
|
416
|
+
element.style.setProperty(name, value);
|
|
417
|
+
} else {
|
|
418
|
+
element.style.removeProperty(name);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function deriveOpacityColor(color, opacity) {
|
|
422
|
+
if (color.startsWith("rgba")) {
|
|
423
|
+
return color.replace(/,\s*[\d.]+\)$/, `, ${opacity})`);
|
|
424
|
+
}
|
|
425
|
+
if (color.startsWith("rgb")) {
|
|
426
|
+
return color.replace("rgb", "rgba").replace(")", `, ${opacity})`);
|
|
427
|
+
}
|
|
428
|
+
return color;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export { PhotoSwipeVideoPlugin, createGalleryLightbox, decodeAllBlurhashes, decodeBlurhashToCanvas, deriveOpacityColor, getImageIdFromURL, initHeroImageFallback, normalizeHex, openImageById, parseColor, restoreFromURL, setCSSVar, setupDeepLinking, updateGalleryURL };
|
|
432
|
+
//# sourceMappingURL=client.js.map
|
|
433
|
+
//# sourceMappingURL=client.js.map
|