@aglyn/plugins-mui 1.0.0-beta.235 → 1.0.0-beta.237
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/package.json +7 -7
- package/src/lib/components/image-lightbox-items.d.ts +53 -0
- package/src/lib/components/image-lightbox-items.js +113 -0
- package/src/lib/components/image-lightbox-items.js.map +1 -0
- package/src/lib/components/image-lightbox.d.ts +35 -0
- package/src/lib/components/image-lightbox.js +303 -0
- package/src/lib/components/image-lightbox.js.map +1 -0
- package/src/lib/components/image-list.d.ts +9 -1
- package/src/lib/components/image-list.js +83 -10
- package/src/lib/components/image-list.js.map +1 -1
- package/src/lib/components/image.d.ts +17 -1
- package/src/lib/components/image.js +129 -7
- package/src/lib/components/image.js.map +1 -1
- package/src/lib/components/video-lightbox.d.ts +6 -0
- package/src/lib/components/video-lightbox.js +56 -77
- package/src/lib/components/video-lightbox.js.map +1 -1
- package/src/lib/components/video.d.ts +2 -1
- package/src/lib/components/video.js +9 -2
- package/src/lib/components/video.js.map +1 -1
- package/src/lib/utils/lightbox-attributes.d.ts +33 -0
- package/src/lib/utils/lightbox-attributes.js +56 -0
- package/src/lib/utils/lightbox-attributes.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-mui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.237",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.237",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.237",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.237",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.237",
|
|
32
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.237",
|
|
33
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.237",
|
|
34
34
|
"@swc/helpers": "0.5.23",
|
|
35
35
|
"mobx": "^7.0.5",
|
|
36
36
|
"mobx-react-lite": "^5.1.0"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/** Marks an `<img>` that opens in a lightbox; its value is the gallery name. */
|
|
18
|
+
export declare const LIGHTBOX_ATTRIBUTE = "data-aglyn-lightbox";
|
|
19
|
+
/** The caption a picture shows in the lightbox. */
|
|
20
|
+
export declare const LIGHTBOX_CAPTION_ATTRIBUTE = "data-aglyn-lightbox-caption";
|
|
21
|
+
export interface LightboxPicture {
|
|
22
|
+
src: string;
|
|
23
|
+
srcSet?: string;
|
|
24
|
+
alt: string;
|
|
25
|
+
caption?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface LightboxGallery {
|
|
28
|
+
pictures: LightboxPicture[];
|
|
29
|
+
index: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Opens the gallery the trigger belongs to. Provided by an Image List whose
|
|
33
|
+
* lightbox is on, so every Image inside it opens the list's gallery rather
|
|
34
|
+
* than one of its own.
|
|
35
|
+
*/
|
|
36
|
+
export interface ImageLightboxGalleryApi {
|
|
37
|
+
open: (trigger: HTMLElement) => void;
|
|
38
|
+
}
|
|
39
|
+
export declare const ImageLightboxGalleryContext: import("react").Context<ImageLightboxGalleryApi>;
|
|
40
|
+
/** One `<img>` as a lightbox picture, or nothing for one with no source. */
|
|
41
|
+
export declare function pictureOf(img: Element): LightboxPicture | null;
|
|
42
|
+
/**
|
|
43
|
+
* The gallery an Image List opens from one of its pictures: every picture in
|
|
44
|
+
* the list, starting at the one pressed.
|
|
45
|
+
*/
|
|
46
|
+
export declare function listGalleryOf(list: Element, trigger: Element): LightboxGallery;
|
|
47
|
+
/**
|
|
48
|
+
* The gallery an Image opens on its own: itself, or — when it names a
|
|
49
|
+
* gallery — every picture on the page with the same name.
|
|
50
|
+
*/
|
|
51
|
+
export declare function imageGalleryOf(img: Element): LightboxGallery;
|
|
52
|
+
/** The next index, wrapping at both ends. */
|
|
53
|
+
export declare function stepIndex(index: number, step: number, count: number): number;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ /**
|
|
18
|
+
* What a picture lightbox shows, read off the page (AGL-3717).
|
|
19
|
+
*
|
|
20
|
+
* No React and no MUI here: the Image and the Image List import this on every
|
|
21
|
+
* page that places them, and the dialog itself (`image-lightbox.tsx`) loads
|
|
22
|
+
* only when a visitor opens it.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the pictures are read from the DOM
|
|
25
|
+
*
|
|
26
|
+
* A gallery is "the pictures in this list" or "the pictures that share this
|
|
27
|
+
* gallery name", and the page already holds exactly that: every Image that
|
|
28
|
+
* takes part stamps {@link LIGHTBOX_ATTRIBUTE} on its `<img>`, with its
|
|
29
|
+
* caption beside it. Reading them when the visitor clicks — rather than
|
|
30
|
+
* registering each Image with its list in React state — gives the order the
|
|
31
|
+
* visitor sees (a list's tiles, a page's sections), costs nothing until the
|
|
32
|
+
* click, and works whatever wraps the pictures: a tile, a card, a grid cell
|
|
33
|
+
* or a reusable component.
|
|
34
|
+
*
|
|
35
|
+
* The lightbox asks for the picture by the URL and `srcset` the Image already
|
|
36
|
+
* rendered, with a `sizes` of the whole screen, so the browser picks the
|
|
37
|
+
* full-size variant the media CDN already holds for it (AGL-175). Nothing new
|
|
38
|
+
* is resolved here, and no host is reached that the page did not reach.
|
|
39
|
+
*/ import { createContext } from "react";
|
|
40
|
+
/** Marks an `<img>` that opens in a lightbox; its value is the gallery name. */ export const LIGHTBOX_ATTRIBUTE = 'data-aglyn-lightbox';
|
|
41
|
+
/** The caption a picture shows in the lightbox. */ export const LIGHTBOX_CAPTION_ATTRIBUTE = 'data-aglyn-lightbox-caption';
|
|
42
|
+
export const ImageLightboxGalleryContext = createContext(null);
|
|
43
|
+
/** The caption of an Image List tile, when the picture names none itself. */ function tileCaption(img) {
|
|
44
|
+
var _img_closest_querySelector_textContent, _img_closest_querySelector, _img_closest;
|
|
45
|
+
const title = (_img_closest = img.closest('li')) == null ? void 0 : (_img_closest_querySelector = _img_closest.querySelector('.MuiImageListItemBar-title')) == null ? void 0 : (_img_closest_querySelector_textContent = _img_closest_querySelector.textContent) == null ? void 0 : _img_closest_querySelector_textContent.trim();
|
|
46
|
+
return title || undefined;
|
|
47
|
+
}
|
|
48
|
+
/** One `<img>` as a lightbox picture, or nothing for one with no source. */ export function pictureOf(img) {
|
|
49
|
+
var _img_getAttribute;
|
|
50
|
+
var _img_getAttribute1, _img_getAttribute2, _img_getAttribute3;
|
|
51
|
+
const src = (_img_getAttribute1 = img.getAttribute('src')) == null ? void 0 : _img_getAttribute1.trim();
|
|
52
|
+
if (!src) return null;
|
|
53
|
+
const srcSet = ((_img_getAttribute2 = img.getAttribute('srcset')) == null ? void 0 : _img_getAttribute2.trim()) || undefined;
|
|
54
|
+
const caption = ((_img_getAttribute3 = img.getAttribute(LIGHTBOX_CAPTION_ATTRIBUTE)) == null ? void 0 : _img_getAttribute3.trim()) || tileCaption(img);
|
|
55
|
+
return _extends({
|
|
56
|
+
src
|
|
57
|
+
}, srcSet ? {
|
|
58
|
+
srcSet
|
|
59
|
+
} : {}, {
|
|
60
|
+
alt: (_img_getAttribute = img.getAttribute('alt')) != null ? _img_getAttribute : ''
|
|
61
|
+
}, caption ? {
|
|
62
|
+
caption
|
|
63
|
+
} : {});
|
|
64
|
+
}
|
|
65
|
+
/** The pictures under `root` that take part, in document order. */ function picturesUnder(root, trigger, selector) {
|
|
66
|
+
const images = Array.from(root.querySelectorAll(selector));
|
|
67
|
+
const pictures = [];
|
|
68
|
+
let index = 0;
|
|
69
|
+
for (const img of images){
|
|
70
|
+
const picture = pictureOf(img);
|
|
71
|
+
if (!picture) continue;
|
|
72
|
+
if (img === trigger || img.contains(trigger) || trigger.contains(img)) {
|
|
73
|
+
index = pictures.length;
|
|
74
|
+
}
|
|
75
|
+
pictures.push(picture);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
pictures,
|
|
79
|
+
index
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The gallery an Image List opens from one of its pictures: every picture in
|
|
84
|
+
* the list, starting at the one pressed.
|
|
85
|
+
*/ export function listGalleryOf(list, trigger) {
|
|
86
|
+
return picturesUnder(list, trigger, `img[${LIGHTBOX_ATTRIBUTE}]`);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The gallery an Image opens on its own: itself, or — when it names a
|
|
90
|
+
* gallery — every picture on the page with the same name.
|
|
91
|
+
*/ export function imageGalleryOf(img) {
|
|
92
|
+
var _img_getAttribute;
|
|
93
|
+
const name = (_img_getAttribute = img.getAttribute(LIGHTBOX_ATTRIBUTE)) != null ? _img_getAttribute : '';
|
|
94
|
+
if (name) {
|
|
95
|
+
const doc = img.ownerDocument;
|
|
96
|
+
const escaped = typeof CSS !== 'undefined' && typeof CSS.escape === 'function' ? CSS.escape(name) : name.replace(/["\\]/g, '\\$&');
|
|
97
|
+
const gallery = picturesUnder(doc, img, `img[${LIGHTBOX_ATTRIBUTE}="${escaped}"]`);
|
|
98
|
+
if (gallery.pictures.length) return gallery;
|
|
99
|
+
}
|
|
100
|
+
const picture = pictureOf(img);
|
|
101
|
+
return {
|
|
102
|
+
pictures: picture ? [
|
|
103
|
+
picture
|
|
104
|
+
] : [],
|
|
105
|
+
index: 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** The next index, wrapping at both ends. */ export function stepIndex(index, step, count) {
|
|
109
|
+
if (count <= 0) return 0;
|
|
110
|
+
return ((index + step) % count + count) % count;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=image-lightbox-items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/image-lightbox-items.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * What a picture lightbox shows, read off the page (AGL-3717).\n *\n * No React and no MUI here: the Image and the Image List import this on every\n * page that places them, and the dialog itself (`image-lightbox.tsx`) loads\n * only when a visitor opens it.\n *\n * ## Why the pictures are read from the DOM\n *\n * A gallery is \"the pictures in this list\" or \"the pictures that share this\n * gallery name\", and the page already holds exactly that: every Image that\n * takes part stamps {@link LIGHTBOX_ATTRIBUTE} on its `<img>`, with its\n * caption beside it. Reading them when the visitor clicks — rather than\n * registering each Image with its list in React state — gives the order the\n * visitor sees (a list's tiles, a page's sections), costs nothing until the\n * click, and works whatever wraps the pictures: a tile, a card, a grid cell\n * or a reusable component.\n *\n * The lightbox asks for the picture by the URL and `srcset` the Image already\n * rendered, with a `sizes` of the whole screen, so the browser picks the\n * full-size variant the media CDN already holds for it (AGL-175). Nothing new\n * is resolved here, and no host is reached that the page did not reach.\n */\n\nimport { createContext } from 'react'\n\n/** Marks an `<img>` that opens in a lightbox; its value is the gallery name. */\nexport const LIGHTBOX_ATTRIBUTE = 'data-aglyn-lightbox'\n/** The caption a picture shows in the lightbox. */\nexport const LIGHTBOX_CAPTION_ATTRIBUTE = 'data-aglyn-lightbox-caption'\n\nexport interface LightboxPicture {\n src: string\n srcSet?: string\n alt: string\n caption?: string\n}\n\nexport interface LightboxGallery {\n pictures: LightboxPicture[]\n index: number\n}\n\n/**\n * Opens the gallery the trigger belongs to. Provided by an Image List whose\n * lightbox is on, so every Image inside it opens the list's gallery rather\n * than one of its own.\n */\nexport interface ImageLightboxGalleryApi {\n open: (trigger: HTMLElement) => void\n}\n\nexport const ImageLightboxGalleryContext =\n createContext<ImageLightboxGalleryApi | null>(null)\n\n/** The caption of an Image List tile, when the picture names none itself. */\nfunction tileCaption(img: Element): string | undefined {\n const title = img\n .closest('li')\n ?.querySelector('.MuiImageListItemBar-title')\n ?.textContent?.trim()\n return title || undefined\n}\n\n/** One `<img>` as a lightbox picture, or nothing for one with no source. */\nexport function pictureOf(img: Element): LightboxPicture | null {\n const src = img.getAttribute('src')?.trim()\n if (!src) return null\n const srcSet = img.getAttribute('srcset')?.trim() || undefined\n const caption =\n img.getAttribute(LIGHTBOX_CAPTION_ATTRIBUTE)?.trim() || tileCaption(img)\n return {\n src,\n ...(srcSet ? { srcSet } : {}),\n alt: img.getAttribute('alt') ?? '',\n ...(caption ? { caption } : {}),\n }\n}\n\n/** The pictures under `root` that take part, in document order. */\nfunction picturesUnder(\n root: ParentNode,\n trigger: Element,\n selector: string,\n): LightboxGallery {\n const images = Array.from(root.querySelectorAll(selector))\n const pictures: LightboxPicture[] = []\n let index = 0\n for (const img of images) {\n const picture = pictureOf(img)\n if (!picture) continue\n if (img === trigger || img.contains(trigger) || trigger.contains(img)) {\n index = pictures.length\n }\n pictures.push(picture)\n }\n return { pictures, index }\n}\n\n/**\n * The gallery an Image List opens from one of its pictures: every picture in\n * the list, starting at the one pressed.\n */\nexport function listGalleryOf(\n list: Element,\n trigger: Element,\n): LightboxGallery {\n return picturesUnder(list, trigger, `img[${LIGHTBOX_ATTRIBUTE}]`)\n}\n\n/**\n * The gallery an Image opens on its own: itself, or — when it names a\n * gallery — every picture on the page with the same name.\n */\nexport function imageGalleryOf(img: Element): LightboxGallery {\n const name = img.getAttribute(LIGHTBOX_ATTRIBUTE) ?? ''\n if (name) {\n const doc = img.ownerDocument\n const escaped =\n typeof CSS !== 'undefined' && typeof CSS.escape === 'function'\n ? CSS.escape(name)\n : name.replace(/[\"\\\\]/g, '\\\\$&')\n const gallery = picturesUnder(doc, img, `img[${LIGHTBOX_ATTRIBUTE}=\"${escaped}\"]`)\n if (gallery.pictures.length) return gallery\n }\n const picture = pictureOf(img)\n return { pictures: picture ? [picture] : [], index: 0 }\n}\n\n/** The next index, wrapping at both ends. */\nexport function stepIndex(index: number, step: number, count: number): number {\n if (count <= 0) return 0\n return (((index + step) % count) + count) % count\n}\n"],"names":["createContext","LIGHTBOX_ATTRIBUTE","LIGHTBOX_CAPTION_ATTRIBUTE","ImageLightboxGalleryContext","tileCaption","img","title","closest","querySelector","textContent","trim","undefined","pictureOf","src","getAttribute","srcSet","caption","alt","picturesUnder","root","trigger","selector","images","Array","from","querySelectorAll","pictures","index","picture","contains","length","push","listGalleryOf","list","imageGalleryOf","name","doc","ownerDocument","escaped","CSS","escape","replace","gallery","stepIndex","step","count"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { LightboxAppearance } from '@aglyn/shared-ui-jsx/components/lightbox/lightbox-appearance';
|
|
18
|
+
import { type LightboxGallery } from './image-lightbox-items';
|
|
19
|
+
/** How far a finger has to travel sideways before it counts as a swipe. */
|
|
20
|
+
export declare const SWIPE_THRESHOLD_PX = 40;
|
|
21
|
+
export interface ImageLightboxProps {
|
|
22
|
+
open: boolean;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
gallery: LightboxGallery;
|
|
25
|
+
appearance?: LightboxAppearance;
|
|
26
|
+
/** A strip of thumbnails under the picture, for a gallery of two or more. */
|
|
27
|
+
thumbnails?: boolean;
|
|
28
|
+
/** The dialog's name; the gallery's own name or "Gallery". */
|
|
29
|
+
label?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function ImageLightbox(props: ImageLightboxProps): import("react").JSX.Element;
|
|
32
|
+
export declare namespace ImageLightbox {
|
|
33
|
+
var displayName: string;
|
|
34
|
+
}
|
|
35
|
+
export default ImageLightbox;
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The picture lightbox (AGL-3717): one picture full size, or a gallery of
|
|
18
|
+
* them with previous and next, a counter, captions and an optional strip of
|
|
19
|
+
* thumbnails.
|
|
20
|
+
*
|
|
21
|
+
* It is the shared lightbox shell — the Video's dialog, lifted into
|
|
22
|
+
* `@aglyn/shared-ui-jsx/components/lightbox/lightbox-dialog` — with pictures
|
|
23
|
+
* in it, and it is reached the way the Video's is: `image.tsx` and
|
|
24
|
+
* `image-list.tsx` load this module through `lazy(() => import())` the first
|
|
25
|
+
* time a visitor goes near a picture that opens, so a page that only shows
|
|
26
|
+
* pictures never downloads the Dialog stack (AGL-1290).
|
|
27
|
+
*
|
|
28
|
+
* Nothing in it renders while closed. The full-size picture is requested when
|
|
29
|
+
* the dialog opens and not before; it is the one image here that loads
|
|
30
|
+
* eagerly, because the visitor has just asked to see it.
|
|
31
|
+
*/ 'use client';
|
|
32
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
33
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
34
|
+
import * as Aglyn from "@aglyn/aglyn";
|
|
35
|
+
import { mdiChevronLeft, mdiChevronRight } from "@aglyn/shared-data-mdi";
|
|
36
|
+
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
37
|
+
import { LightboxDialog } from "@aglyn/shared-ui-jsx/components/lightbox/lightbox-dialog";
|
|
38
|
+
import Box from "@mui/material/Box";
|
|
39
|
+
import IconButton from "@mui/material/IconButton";
|
|
40
|
+
import { alpha } from "@mui/material/styles";
|
|
41
|
+
import Typography from "@mui/material/Typography";
|
|
42
|
+
import { useEffect, useRef, useState } from "react";
|
|
43
|
+
import { stepIndex } from "./image-lightbox-items.js";
|
|
44
|
+
/** How far a finger has to travel sideways before it counts as a swipe. */ export const SWIPE_THRESHOLD_PX = 40;
|
|
45
|
+
const navButtonSx = {
|
|
46
|
+
position: 'absolute',
|
|
47
|
+
top: '50%',
|
|
48
|
+
transform: 'translateY(-50%)',
|
|
49
|
+
zIndex: 1,
|
|
50
|
+
color: 'common.white',
|
|
51
|
+
bgcolor: (theme)=>alpha(theme.palette.common.black, 0.45),
|
|
52
|
+
'&:hover': {
|
|
53
|
+
bgcolor: (theme)=>alpha(theme.palette.common.black, 0.65)
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
export function ImageLightbox(props) {
|
|
57
|
+
var _ref;
|
|
58
|
+
const { open, onClose, gallery, appearance, thumbnails, label } = props;
|
|
59
|
+
const { pictures } = gallery;
|
|
60
|
+
const count = pictures.length;
|
|
61
|
+
const [index, setIndex] = useState(gallery.index);
|
|
62
|
+
// A new opening starts at the picture pressed, not where the last one left off.
|
|
63
|
+
useEffect(()=>{
|
|
64
|
+
if (open) setIndex(gallery.index);
|
|
65
|
+
}, [
|
|
66
|
+
open,
|
|
67
|
+
gallery
|
|
68
|
+
]);
|
|
69
|
+
const swipeStart = useRef(null);
|
|
70
|
+
const current = pictures[Math.min(index, Math.max(count - 1, 0))];
|
|
71
|
+
if (!current) return null;
|
|
72
|
+
const many = count > 1;
|
|
73
|
+
const go = (step)=>setIndex((value)=>stepIndex(value, step, count));
|
|
74
|
+
const handleKeyDown = (event)=>{
|
|
75
|
+
if (!many) return;
|
|
76
|
+
if (event.key === 'ArrowRight') go(1);
|
|
77
|
+
else if (event.key === 'ArrowLeft') go(-1);
|
|
78
|
+
else if (event.key === 'Home') setIndex(0);
|
|
79
|
+
else if (event.key === 'End') setIndex(count - 1);
|
|
80
|
+
else return;
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
};
|
|
83
|
+
const handlePointerDown = (event)=>{
|
|
84
|
+
swipeStart.current = {
|
|
85
|
+
x: event.clientX,
|
|
86
|
+
y: event.clientY
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
const handlePointerUp = (event)=>{
|
|
90
|
+
const start = swipeStart.current;
|
|
91
|
+
swipeStart.current = null;
|
|
92
|
+
if (!start || !many) return;
|
|
93
|
+
const dx = event.clientX - start.x;
|
|
94
|
+
const dy = event.clientY - start.y;
|
|
95
|
+
// Sideways and far enough: a scroll or a tap is neither.
|
|
96
|
+
if (Math.abs(dx) < SWIPE_THRESHOLD_PX || Math.abs(dx) < Math.abs(dy)) return;
|
|
97
|
+
go(dx < 0 ? 1 : -1);
|
|
98
|
+
};
|
|
99
|
+
const placement = (_ref = appearance == null ? void 0 : appearance.captionPlacement) != null ? _ref : 'below';
|
|
100
|
+
const caption = placement !== 'hidden' ? current.caption : undefined;
|
|
101
|
+
const ownCloseLook = Boolean((appearance == null ? void 0 : appearance.closeStyle) && appearance.closeStyle !== 'icon');
|
|
102
|
+
return /*#__PURE__*/ _jsxs(LightboxDialog, {
|
|
103
|
+
open: open,
|
|
104
|
+
onClose: onClose,
|
|
105
|
+
label: label || (many ? 'Gallery' : current.alt || 'Picture'),
|
|
106
|
+
closeLabel: many ? 'Close gallery' : 'Close picture',
|
|
107
|
+
appearance: appearance,
|
|
108
|
+
onKeyDown: handleKeyDown,
|
|
109
|
+
maxWidth: "lg",
|
|
110
|
+
// The pictures float on the backdrop: no sheet behind them.
|
|
111
|
+
paperSx: {
|
|
112
|
+
// `background`, not a colour alone: it also clears the overlay
|
|
113
|
+
// gradient a dark theme lays on every paper.
|
|
114
|
+
background: 'transparent',
|
|
115
|
+
boxShadow: 'none',
|
|
116
|
+
overflow: 'visible',
|
|
117
|
+
alignItems: 'center'
|
|
118
|
+
},
|
|
119
|
+
closeButtonSx: ownCloseLook ? undefined : {
|
|
120
|
+
color: 'common.white',
|
|
121
|
+
bgcolor: (theme)=>alpha(theme.palette.common.black, 0.45)
|
|
122
|
+
},
|
|
123
|
+
children: [
|
|
124
|
+
/*#__PURE__*/ _jsxs(Box, {
|
|
125
|
+
component: "figure",
|
|
126
|
+
sx: {
|
|
127
|
+
position: 'relative',
|
|
128
|
+
m: 0,
|
|
129
|
+
display: 'flex',
|
|
130
|
+
flexDirection: 'column',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
minHeight: 0,
|
|
133
|
+
// A swipe moves between pictures, never the page behind them.
|
|
134
|
+
touchAction: 'pan-y'
|
|
135
|
+
},
|
|
136
|
+
onPointerDown: handlePointerDown,
|
|
137
|
+
onPointerUp: handlePointerUp,
|
|
138
|
+
children: [
|
|
139
|
+
/*#__PURE__*/ _jsxs(Box, {
|
|
140
|
+
sx: {
|
|
141
|
+
position: 'relative',
|
|
142
|
+
maxWidth: '100%'
|
|
143
|
+
},
|
|
144
|
+
children: [
|
|
145
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
146
|
+
component: "img",
|
|
147
|
+
src: current.src,
|
|
148
|
+
srcSet: current.srcSet,
|
|
149
|
+
// The whole screen is the slot, so the browser takes the
|
|
150
|
+
// full-size variant the media CDN already holds.
|
|
151
|
+
sizes: "100vw",
|
|
152
|
+
alt: current.alt,
|
|
153
|
+
// Requested only now, because the visitor asked for it.
|
|
154
|
+
loading: "eager",
|
|
155
|
+
draggable: false,
|
|
156
|
+
sx: {
|
|
157
|
+
display: 'block',
|
|
158
|
+
maxWidth: '100%',
|
|
159
|
+
maxHeight: thumbnails && many ? 'calc(100vh - 220px)' : 'calc(100vh - 140px)',
|
|
160
|
+
width: 'auto',
|
|
161
|
+
height: 'auto',
|
|
162
|
+
objectFit: 'contain',
|
|
163
|
+
userSelect: 'none',
|
|
164
|
+
borderRadius: (appearance == null ? void 0 : appearance.radius) !== undefined ? `${appearance.radius}px` : undefined
|
|
165
|
+
}
|
|
166
|
+
}, current.src),
|
|
167
|
+
caption && placement === 'overlay' ? /*#__PURE__*/ _jsx(Typography, {
|
|
168
|
+
component: "figcaption",
|
|
169
|
+
variant: "body2",
|
|
170
|
+
sx: {
|
|
171
|
+
position: 'absolute',
|
|
172
|
+
left: 0,
|
|
173
|
+
right: 0,
|
|
174
|
+
bottom: 0,
|
|
175
|
+
px: 2,
|
|
176
|
+
py: 1.5,
|
|
177
|
+
color: 'common.white',
|
|
178
|
+
background: (theme)=>`linear-gradient(transparent, ${alpha(theme.palette.common.black, 0.7)})`
|
|
179
|
+
},
|
|
180
|
+
children: caption
|
|
181
|
+
}) : null
|
|
182
|
+
]
|
|
183
|
+
}),
|
|
184
|
+
many ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
185
|
+
children: [
|
|
186
|
+
/*#__PURE__*/ _jsx(IconButton, {
|
|
187
|
+
"aria-label": "Previous picture",
|
|
188
|
+
onClick: ()=>go(-1),
|
|
189
|
+
sx: [
|
|
190
|
+
navButtonSx,
|
|
191
|
+
{
|
|
192
|
+
left: {
|
|
193
|
+
xs: 4,
|
|
194
|
+
sm: -56
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
],
|
|
198
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
199
|
+
path: mdiChevronLeft.path
|
|
200
|
+
})
|
|
201
|
+
}),
|
|
202
|
+
/*#__PURE__*/ _jsx(IconButton, {
|
|
203
|
+
"aria-label": "Next picture",
|
|
204
|
+
onClick: ()=>go(1),
|
|
205
|
+
sx: [
|
|
206
|
+
navButtonSx,
|
|
207
|
+
{
|
|
208
|
+
right: {
|
|
209
|
+
xs: 4,
|
|
210
|
+
sm: -56
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
215
|
+
path: mdiChevronRight.path
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
]
|
|
219
|
+
}) : null,
|
|
220
|
+
caption && placement === 'below' || many ? /*#__PURE__*/ _jsxs(Box, {
|
|
221
|
+
sx: {
|
|
222
|
+
display: 'flex',
|
|
223
|
+
gap: 2,
|
|
224
|
+
alignItems: 'baseline',
|
|
225
|
+
justifyContent: 'space-between',
|
|
226
|
+
width: '100%',
|
|
227
|
+
pt: 1.5,
|
|
228
|
+
color: 'common.white'
|
|
229
|
+
},
|
|
230
|
+
children: [
|
|
231
|
+
caption && placement === 'below' ? /*#__PURE__*/ _jsx(Typography, {
|
|
232
|
+
component: "figcaption",
|
|
233
|
+
variant: "body2",
|
|
234
|
+
children: caption
|
|
235
|
+
}) : /*#__PURE__*/ _jsx("span", {}),
|
|
236
|
+
many ? /*#__PURE__*/ _jsx(Typography, {
|
|
237
|
+
variant: "body2",
|
|
238
|
+
"aria-live": "polite",
|
|
239
|
+
"aria-label": `Picture ${index + 1} of ${count}`,
|
|
240
|
+
sx: {
|
|
241
|
+
whiteSpace: 'nowrap',
|
|
242
|
+
opacity: 0.85
|
|
243
|
+
},
|
|
244
|
+
children: `${index + 1} / ${count}`
|
|
245
|
+
}) : null
|
|
246
|
+
]
|
|
247
|
+
}) : null
|
|
248
|
+
]
|
|
249
|
+
}),
|
|
250
|
+
thumbnails && many ? /*#__PURE__*/ _jsx(Box, {
|
|
251
|
+
role: "group",
|
|
252
|
+
"aria-label": "Pictures",
|
|
253
|
+
sx: {
|
|
254
|
+
display: 'flex',
|
|
255
|
+
gap: 1,
|
|
256
|
+
pt: 1.5,
|
|
257
|
+
overflowX: 'auto',
|
|
258
|
+
maxWidth: '100%',
|
|
259
|
+
justifyContent: 'safe center'
|
|
260
|
+
},
|
|
261
|
+
children: pictures.map((picture, position)=>/*#__PURE__*/ _jsx(Box, {
|
|
262
|
+
component: "button",
|
|
263
|
+
type: "button",
|
|
264
|
+
onClick: ()=>setIndex(position),
|
|
265
|
+
"aria-label": `Show picture ${position + 1}${picture.alt ? `: ${picture.alt}` : ''}`,
|
|
266
|
+
"aria-current": position === index ? 'true' : undefined,
|
|
267
|
+
sx: {
|
|
268
|
+
flex: '0 0 auto',
|
|
269
|
+
p: 0,
|
|
270
|
+
border: 2,
|
|
271
|
+
borderColor: position === index ? 'common.white' : 'transparent',
|
|
272
|
+
borderRadius: 1,
|
|
273
|
+
bgcolor: 'transparent',
|
|
274
|
+
cursor: 'pointer',
|
|
275
|
+
opacity: position === index ? 1 : 0.6,
|
|
276
|
+
'&:focus-visible': {
|
|
277
|
+
outline: '2px solid',
|
|
278
|
+
outlineColor: 'common.white'
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
children: /*#__PURE__*/ _jsx(Box, _extends({
|
|
282
|
+
component: "img",
|
|
283
|
+
src: picture.src,
|
|
284
|
+
srcSet: picture.srcSet,
|
|
285
|
+
sizes: "64px",
|
|
286
|
+
alt: "",
|
|
287
|
+
sx: {
|
|
288
|
+
display: 'block',
|
|
289
|
+
width: 64,
|
|
290
|
+
height: 48,
|
|
291
|
+
objectFit: 'cover',
|
|
292
|
+
borderRadius: 0.5
|
|
293
|
+
}
|
|
294
|
+
}, Aglyn.DEFERRED_IMAGE_ATTRIBUTES))
|
|
295
|
+
}, `${picture.src}-${position}`))
|
|
296
|
+
}) : null
|
|
297
|
+
]
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
ImageLightbox.displayName = 'ImageLightbox';
|
|
301
|
+
export default ImageLightbox;
|
|
302
|
+
|
|
303
|
+
//# sourceMappingURL=image-lightbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/mui/src/lib/components/image-lightbox.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The picture lightbox (AGL-3717): one picture full size, or a gallery of\n * them with previous and next, a counter, captions and an optional strip of\n * thumbnails.\n *\n * It is the shared lightbox shell — the Video's dialog, lifted into\n * `@aglyn/shared-ui-jsx/components/lightbox/lightbox-dialog` — with pictures\n * in it, and it is reached the way the Video's is: `image.tsx` and\n * `image-list.tsx` load this module through `lazy(() => import())` the first\n * time a visitor goes near a picture that opens, so a page that only shows\n * pictures never downloads the Dialog stack (AGL-1290).\n *\n * Nothing in it renders while closed. The full-size picture is requested when\n * the dialog opens and not before; it is the one image here that loads\n * eagerly, because the visitor has just asked to see it.\n */\n'use client'\n\nimport * as Aglyn from '@aglyn/aglyn'\nimport { mdiChevronLeft, mdiChevronRight } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport type { LightboxAppearance } from '@aglyn/shared-ui-jsx/components/lightbox/lightbox-appearance'\nimport { LightboxDialog } from '@aglyn/shared-ui-jsx/components/lightbox/lightbox-dialog'\nimport Box from '@mui/material/Box'\nimport IconButton from '@mui/material/IconButton'\nimport { alpha, type Theme } from '@mui/material/styles'\nimport Typography from '@mui/material/Typography'\nimport { type KeyboardEvent, type PointerEvent, useEffect, useRef, useState } from 'react'\nimport { type LightboxGallery, stepIndex } from './image-lightbox-items'\n\n/** How far a finger has to travel sideways before it counts as a swipe. */\nexport const SWIPE_THRESHOLD_PX = 40\n\nexport interface ImageLightboxProps {\n open: boolean\n onClose: () => void\n gallery: LightboxGallery\n appearance?: LightboxAppearance\n /** A strip of thumbnails under the picture, for a gallery of two or more. */\n thumbnails?: boolean\n /** The dialog's name; the gallery's own name or \"Gallery\". */\n label?: string\n}\n\nconst navButtonSx = {\n position: 'absolute',\n top: '50%',\n transform: 'translateY(-50%)',\n zIndex: 1,\n color: 'common.white',\n bgcolor: (theme: Theme) => alpha(theme.palette.common.black, 0.45),\n '&:hover': { bgcolor: (theme: Theme) => alpha(theme.palette.common.black, 0.65) },\n} as const\n\nexport function ImageLightbox(props: ImageLightboxProps) {\n const { open, onClose, gallery, appearance, thumbnails, label } = props\n const { pictures } = gallery\n const count = pictures.length\n const [index, setIndex] = useState(gallery.index)\n // A new opening starts at the picture pressed, not where the last one left off.\n useEffect(() => {\n if (open) setIndex(gallery.index)\n }, [open, gallery])\n const swipeStart = useRef<{ x: number; y: number } | null>(null)\n const current = pictures[Math.min(index, Math.max(count - 1, 0))]\n if (!current) return null\n const many = count > 1\n const go = (step: number) => setIndex((value) => stepIndex(value, step, count))\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (!many) return\n if (event.key === 'ArrowRight') go(1)\n else if (event.key === 'ArrowLeft') go(-1)\n else if (event.key === 'Home') setIndex(0)\n else if (event.key === 'End') setIndex(count - 1)\n else return\n event.preventDefault()\n }\n const handlePointerDown = (event: PointerEvent<HTMLElement>) => {\n swipeStart.current = { x: event.clientX, y: event.clientY }\n }\n const handlePointerUp = (event: PointerEvent<HTMLElement>) => {\n const start = swipeStart.current\n swipeStart.current = null\n if (!start || !many) return\n const dx = event.clientX - start.x\n const dy = event.clientY - start.y\n // Sideways and far enough: a scroll or a tap is neither.\n if (Math.abs(dx) < SWIPE_THRESHOLD_PX || Math.abs(dx) < Math.abs(dy)) return\n go(dx < 0 ? 1 : -1)\n }\n const placement = appearance?.captionPlacement ?? 'below'\n const caption = placement !== 'hidden' ? current.caption : undefined\n const ownCloseLook = Boolean(appearance?.closeStyle && appearance.closeStyle !== 'icon')\n return (\n <LightboxDialog\n open={open}\n onClose={onClose}\n label={label || (many ? 'Gallery' : current.alt || 'Picture')}\n closeLabel={many ? 'Close gallery' : 'Close picture'}\n appearance={appearance}\n onKeyDown={handleKeyDown}\n maxWidth=\"lg\"\n // The pictures float on the backdrop: no sheet behind them.\n paperSx={{\n // `background`, not a colour alone: it also clears the overlay\n // gradient a dark theme lays on every paper.\n background: 'transparent',\n boxShadow: 'none',\n overflow: 'visible',\n alignItems: 'center',\n }}\n closeButtonSx={\n ownCloseLook\n ? undefined\n : { color: 'common.white', bgcolor: (theme: Theme) => alpha(theme.palette.common.black, 0.45) }\n }\n >\n <Box\n component=\"figure\"\n sx={{\n position: 'relative',\n m: 0,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n minHeight: 0,\n // A swipe moves between pictures, never the page behind them.\n touchAction: 'pan-y',\n }}\n onPointerDown={handlePointerDown}\n onPointerUp={handlePointerUp}\n >\n <Box sx={{ position: 'relative', maxWidth: '100%' }}>\n <Box\n component=\"img\"\n key={current.src}\n src={current.src}\n srcSet={current.srcSet}\n // The whole screen is the slot, so the browser takes the\n // full-size variant the media CDN already holds.\n sizes=\"100vw\"\n alt={current.alt}\n // Requested only now, because the visitor asked for it.\n loading=\"eager\"\n draggable={false}\n sx={{\n display: 'block',\n maxWidth: '100%',\n maxHeight: thumbnails && many ? 'calc(100vh - 220px)' : 'calc(100vh - 140px)',\n width: 'auto',\n height: 'auto',\n objectFit: 'contain',\n userSelect: 'none',\n borderRadius: appearance?.radius !== undefined ? `${appearance.radius}px` : undefined,\n }}\n />\n {caption && placement === 'overlay' ? (\n <Typography\n component=\"figcaption\"\n variant=\"body2\"\n sx={{\n position: 'absolute',\n left: 0,\n right: 0,\n bottom: 0,\n px: 2,\n py: 1.5,\n color: 'common.white',\n background: (theme: Theme) =>\n `linear-gradient(transparent, ${alpha(theme.palette.common.black, 0.7)})`,\n }}\n >\n {caption}\n </Typography>\n ) : null}\n </Box>\n {many ? (\n <>\n <IconButton\n aria-label=\"Previous picture\"\n onClick={() => go(-1)}\n sx={[navButtonSx, { left: { xs: 4, sm: -56 } }]}\n >\n <MdiIcon path={mdiChevronLeft.path} />\n </IconButton>\n <IconButton\n aria-label=\"Next picture\"\n onClick={() => go(1)}\n sx={[navButtonSx, { right: { xs: 4, sm: -56 } }]}\n >\n <MdiIcon path={mdiChevronRight.path} />\n </IconButton>\n </>\n ) : null}\n {(caption && placement === 'below') || many ? (\n <Box\n sx={{\n display: 'flex',\n gap: 2,\n alignItems: 'baseline',\n justifyContent: 'space-between',\n width: '100%',\n pt: 1.5,\n color: 'common.white',\n }}\n >\n {caption && placement === 'below' ? (\n <Typography component=\"figcaption\" variant=\"body2\">\n {caption}\n </Typography>\n ) : (\n <span />\n )}\n {many ? (\n <Typography\n variant=\"body2\"\n aria-live=\"polite\"\n aria-label={`Picture ${index + 1} of ${count}`}\n sx={{ whiteSpace: 'nowrap', opacity: 0.85 }}\n >\n {`${index + 1} / ${count}`}\n </Typography>\n ) : null}\n </Box>\n ) : null}\n </Box>\n {thumbnails && many ? (\n <Box\n role=\"group\"\n aria-label=\"Pictures\"\n sx={{\n display: 'flex',\n gap: 1,\n pt: 1.5,\n overflowX: 'auto',\n maxWidth: '100%',\n justifyContent: 'safe center',\n }}\n >\n {pictures.map((picture, position) => (\n <Box\n key={`${picture.src}-${position}`}\n component=\"button\"\n type=\"button\"\n onClick={() => setIndex(position)}\n aria-label={`Show picture ${position + 1}${picture.alt ? `: ${picture.alt}` : ''}`}\n aria-current={position === index ? 'true' : undefined}\n sx={{\n flex: '0 0 auto',\n p: 0,\n border: 2,\n borderColor: position === index ? 'common.white' : 'transparent',\n borderRadius: 1,\n bgcolor: 'transparent',\n cursor: 'pointer',\n opacity: position === index ? 1 : 0.6,\n '&:focus-visible': { outline: '2px solid', outlineColor: 'common.white' },\n }}\n >\n <Box\n component=\"img\"\n src={picture.src}\n srcSet={picture.srcSet}\n sizes=\"64px\"\n alt=\"\"\n sx={{ display: 'block', width: 64, height: 48, objectFit: 'cover', borderRadius: 0.5 }}\n {...Aglyn.DEFERRED_IMAGE_ATTRIBUTES}\n />\n </Box>\n ))}\n </Box>\n ) : null}\n </LightboxDialog>\n )\n}\nImageLightbox.displayName = 'ImageLightbox'\n\nexport default ImageLightbox\n"],"names":["Aglyn","mdiChevronLeft","mdiChevronRight","MdiIcon","LightboxDialog","Box","IconButton","alpha","Typography","useEffect","useRef","useState","stepIndex","SWIPE_THRESHOLD_PX","navButtonSx","position","top","transform","zIndex","color","bgcolor","theme","palette","common","black","ImageLightbox","props","open","onClose","gallery","appearance","thumbnails","label","pictures","count","length","index","setIndex","swipeStart","current","Math","min","max","many","go","step","value","handleKeyDown","event","key","preventDefault","handlePointerDown","x","clientX","y","clientY","handlePointerUp","start","dx","dy","abs","placement","captionPlacement","caption","undefined","ownCloseLook","Boolean","closeStyle","alt","closeLabel","onKeyDown","maxWidth","paperSx","background","boxShadow","overflow","alignItems","closeButtonSx","component","sx","m","display","flexDirection","minHeight","touchAction","onPointerDown","onPointerUp","src","srcSet","sizes","loading","draggable","maxHeight","width","height","objectFit","userSelect","borderRadius","radius","variant","left","right","bottom","px","py","aria-label","onClick","xs","sm","path","gap","justifyContent","pt","span","aria-live","whiteSpace","opacity","role","overflowX","map","picture","type","aria-current","flex","p","border","borderColor","cursor","outline","outlineColor","DEFERRED_IMAGE_ATTRIBUTES","displayName"],"mappings":"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"}
|
|
@@ -15,12 +15,20 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import * as Aglyn from '@aglyn/aglyn';
|
|
18
|
+
import { type LightboxAppearanceProps } from '@aglyn/shared-ui-jsx/components/lightbox/lightbox-appearance';
|
|
18
19
|
import { type ReactNode } from 'react';
|
|
19
20
|
import { toCount } from '../utils/to-count';
|
|
20
21
|
export declare const IMAGE_LIST_ID: Aglyn.ComponentId;
|
|
21
22
|
export declare const IMAGE_LIST_ITEM_ID: Aglyn.ComponentId;
|
|
22
23
|
export type ImageListVariant = 'standard' | 'quilted' | 'masonry' | 'woven';
|
|
23
|
-
export interface ImageListElementProps {
|
|
24
|
+
export interface ImageListElementProps extends LightboxAppearanceProps {
|
|
25
|
+
/**
|
|
26
|
+
* Opens a gallery lightbox when a visitor presses any picture in the list
|
|
27
|
+
* (AGL-3717): previous and next, swipe, arrow keys, a counter, captions.
|
|
28
|
+
*/
|
|
29
|
+
lightbox?: boolean | string;
|
|
30
|
+
/** A strip of thumbnails under the gallery's picture. */
|
|
31
|
+
lightboxThumbnails?: boolean | string;
|
|
24
32
|
cols?: number | string;
|
|
25
33
|
gap?: number | string;
|
|
26
34
|
/** Row height in px, or blank for `auto`. */
|