@0m0g1/griot 0.1.11 → 0.1.13
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 +1 -1
- package/src/blocks/GalleryRenderer.js +3 -11
- package/src/blocks/Lightbox.js +22 -26
package/package.json
CHANGED
|
@@ -11,15 +11,7 @@
|
|
|
11
11
|
// layouts: 'grid' | 'masonry' | 'carousel' | 'strip'
|
|
12
12
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
let _lightbox = null;
|
|
16
|
-
function getLightbox() {
|
|
17
|
-
if (!_lightbox) {
|
|
18
|
-
// Dynamic require/import — works in both CJS and ESM bundler contexts
|
|
19
|
-
_lightbox = require('./Lightbox.js').lightbox;
|
|
20
|
-
}
|
|
21
|
-
return _lightbox;
|
|
22
|
-
}
|
|
14
|
+
import { lightbox } from './Lightbox.js';
|
|
23
15
|
|
|
24
16
|
const VALID_LAYOUTS = new Set(['grid', 'masonry', 'carousel', 'strip']);
|
|
25
17
|
|
|
@@ -105,7 +97,7 @@ function _itemEl(item, index, allItems) {
|
|
|
105
97
|
img.decoding = 'async';
|
|
106
98
|
img.draggable = false;
|
|
107
99
|
|
|
108
|
-
img.addEventListener('click', () =>
|
|
100
|
+
img.addEventListener('click', () => lightbox.open(allItems, index));
|
|
109
101
|
|
|
110
102
|
el.appendChild(img);
|
|
111
103
|
|
|
@@ -150,7 +142,7 @@ function _carousel(items, wrap) {
|
|
|
150
142
|
|
|
151
143
|
// Click on carousel image → open lightbox at CURRENT idx (not i, since user
|
|
152
144
|
// may have navigated away from the first image)
|
|
153
|
-
img.addEventListener('click', () =>
|
|
145
|
+
img.addEventListener('click', () => lightbox.open(items, idx));
|
|
154
146
|
|
|
155
147
|
slide.appendChild(img);
|
|
156
148
|
|
package/src/blocks/Lightbox.js
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
//
|
|
8
8
|
// items shape: { src?, url?, alt?, alt_text?, caption? }[]
|
|
9
9
|
// Keyboard: ← → Escape | Touch: swipe left/right | Click backdrop: close
|
|
10
|
+
//
|
|
11
|
+
// Fix: the singleton is stored on globalThis.__griot_lightbox so that even if
|
|
12
|
+
// this module is evaluated more than once (e.g. two different import paths
|
|
13
|
+
// resolving to separate webpack module cache entries — one via Griot.js facade,
|
|
14
|
+
// one via direct ./Lightbox.js import), all callers share the exact same
|
|
15
|
+
// instance. Without this, clicking outside one overlay closed it but left the
|
|
16
|
+
// second overlay (from the other instance) stuck on screen.
|
|
10
17
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
18
|
|
|
12
19
|
export class Lightbox {
|
|
@@ -41,7 +48,6 @@ export class Lightbox {
|
|
|
41
48
|
this._el.classList.remove('griot-lb--open');
|
|
42
49
|
document.body.style.overflow = '';
|
|
43
50
|
this._isOpen = false;
|
|
44
|
-
// Wait for CSS fade-out before hiding from layout
|
|
45
51
|
setTimeout(() => {
|
|
46
52
|
if (!this._isOpen && this._el) this._el.hidden = true;
|
|
47
53
|
}, 270);
|
|
@@ -57,20 +63,16 @@ export class Lightbox {
|
|
|
57
63
|
el.setAttribute('aria-modal', 'true');
|
|
58
64
|
el.setAttribute('aria-label', 'Image viewer');
|
|
59
65
|
|
|
60
|
-
// Backdrop click
|
|
61
66
|
el.addEventListener('click', e => { if (e.target === el) this.close(); });
|
|
62
67
|
|
|
63
|
-
// Close button
|
|
64
68
|
const close = _mkBtn('✕', 'griot-lb__close', 'Close');
|
|
65
69
|
close.addEventListener('click', () => this.close());
|
|
66
70
|
|
|
67
|
-
// Prev / Next
|
|
68
71
|
const prev = _mkBtn('‹', 'griot-lb__nav griot-lb__nav--prev', 'Previous image');
|
|
69
72
|
const next = _mkBtn('›', 'griot-lb__nav griot-lb__nav--next', 'Next image');
|
|
70
73
|
prev.addEventListener('click', e => { e.stopPropagation(); this._move(-1); });
|
|
71
74
|
next.addEventListener('click', e => { e.stopPropagation(); this._move(1); });
|
|
72
75
|
|
|
73
|
-
// Stage: image + caption
|
|
74
76
|
const stage = document.createElement('div');
|
|
75
77
|
stage.className = 'griot-lb__stage';
|
|
76
78
|
stage.addEventListener('click', e => e.stopPropagation());
|
|
@@ -85,17 +87,14 @@ export class Lightbox {
|
|
|
85
87
|
|
|
86
88
|
stage.append(img, cap);
|
|
87
89
|
|
|
88
|
-
// Counter
|
|
89
90
|
const ctr = document.createElement('div');
|
|
90
91
|
ctr.className = 'griot-lb__counter';
|
|
91
92
|
|
|
92
|
-
// Thumbnail strip (hidden until > 1 item)
|
|
93
93
|
const strip = document.createElement('div');
|
|
94
94
|
strip.className = 'griot-lb__strip';
|
|
95
95
|
|
|
96
96
|
el.append(close, prev, next, stage, ctr, strip);
|
|
97
97
|
|
|
98
|
-
// Touch swipe
|
|
99
98
|
el.addEventListener('touchstart', e => {
|
|
100
99
|
this._touchStartX = e.touches[0].clientX;
|
|
101
100
|
}, { passive: true });
|
|
@@ -114,7 +113,6 @@ export class Lightbox {
|
|
|
114
113
|
this._next = next;
|
|
115
114
|
this._strip = strip;
|
|
116
115
|
|
|
117
|
-
// Inject styles once
|
|
118
116
|
_injectStyles();
|
|
119
117
|
}
|
|
120
118
|
|
|
@@ -126,7 +124,6 @@ export class Lightbox {
|
|
|
126
124
|
document.body.style.overflow = 'hidden';
|
|
127
125
|
document.addEventListener('keydown', this._onKey);
|
|
128
126
|
|
|
129
|
-
// Double rAF ensures the hidden→visible transition actually runs
|
|
130
127
|
requestAnimationFrame(() =>
|
|
131
128
|
requestAnimationFrame(() => this._el.classList.add('griot-lb--open'))
|
|
132
129
|
);
|
|
@@ -153,7 +150,6 @@ export class Lightbox {
|
|
|
153
150
|
const alt = item.alt ?? item.alt_text ?? '';
|
|
154
151
|
const cap = item.caption ?? '';
|
|
155
152
|
|
|
156
|
-
// Fade-swap: fade out → preload → set src → fade in
|
|
157
153
|
this._img.style.opacity = '0';
|
|
158
154
|
this._img.alt = alt;
|
|
159
155
|
|
|
@@ -171,13 +167,12 @@ export class Lightbox {
|
|
|
171
167
|
this._next.hidden = single;
|
|
172
168
|
this._ctr.textContent = single ? '' : `${this._idx + 1} / ${this._items.length}`;
|
|
173
169
|
|
|
174
|
-
// Sync strip active thumb
|
|
175
170
|
this._strip.querySelectorAll('.griot-lb__thumb').forEach((th, i) => {
|
|
176
171
|
th.classList.toggle('is-active', i === this._idx);
|
|
177
172
|
});
|
|
178
173
|
}
|
|
179
174
|
|
|
180
|
-
// ── Thumbnail strip
|
|
175
|
+
// ── Thumbnail strip ─────────────────────────────────────────────────────────
|
|
181
176
|
|
|
182
177
|
_buildStrip() {
|
|
183
178
|
this._strip.innerHTML = '';
|
|
@@ -232,7 +227,6 @@ function _injectStyles() {
|
|
|
232
227
|
const s = document.createElement('style');
|
|
233
228
|
s.id = 'griot-lightbox-styles';
|
|
234
229
|
s.textContent = `
|
|
235
|
-
/* ── Lightbox overlay ───────────────────────────────────────────────────── */
|
|
236
230
|
.griot-lb {
|
|
237
231
|
position: fixed; inset: 0; z-index: 9000;
|
|
238
232
|
background: rgba(0,0,0,0);
|
|
@@ -242,8 +236,6 @@ function _injectStyles() {
|
|
|
242
236
|
overscroll-behavior: none;
|
|
243
237
|
}
|
|
244
238
|
.griot-lb--open { background: rgba(0,0,0,0.92); }
|
|
245
|
-
|
|
246
|
-
/* Stage */
|
|
247
239
|
.griot-lb__stage {
|
|
248
240
|
position: relative; display: flex; flex-direction: column;
|
|
249
241
|
align-items: center; max-width: 92vw; max-height: 80vh;
|
|
@@ -259,8 +251,6 @@ function _injectStyles() {
|
|
|
259
251
|
margin: 10px 0 0; text-align: center;
|
|
260
252
|
max-width: 70ch; line-height: 1.5;
|
|
261
253
|
}
|
|
262
|
-
|
|
263
|
-
/* Nav buttons */
|
|
264
254
|
.griot-lb__nav {
|
|
265
255
|
position: fixed; top: 50%; transform: translateY(-50%);
|
|
266
256
|
background: rgba(255,255,255,0.10); border: none;
|
|
@@ -272,8 +262,6 @@ function _injectStyles() {
|
|
|
272
262
|
.griot-lb__nav:hover { background: rgba(255,255,255,0.22); }
|
|
273
263
|
.griot-lb__nav--prev { left: 0; border-radius: 0 8px 8px 0; }
|
|
274
264
|
.griot-lb__nav--next { right: 0; border-radius: 8px 0 0 8px; }
|
|
275
|
-
|
|
276
|
-
/* Close */
|
|
277
265
|
.griot-lb__close {
|
|
278
266
|
position: fixed; top: 14px; right: 18px;
|
|
279
267
|
background: rgba(255,255,255,0.10); border: none;
|
|
@@ -283,15 +271,11 @@ function _injectStyles() {
|
|
|
283
271
|
transition: background 0.15s; z-index: 2;
|
|
284
272
|
}
|
|
285
273
|
.griot-lb__close:hover { background: rgba(255,255,255,0.22); }
|
|
286
|
-
|
|
287
|
-
/* Counter */
|
|
288
274
|
.griot-lb__counter {
|
|
289
275
|
position: fixed; top: 18px; left: 50%; transform: translateX(-50%);
|
|
290
276
|
font-size: 13px; color: #64748b; letter-spacing: 0.04em;
|
|
291
277
|
pointer-events: none;
|
|
292
278
|
}
|
|
293
|
-
|
|
294
|
-
/* Thumbnail strip */
|
|
295
279
|
.griot-lb__strip {
|
|
296
280
|
position: fixed; bottom: 14px; left: 50%; transform: translateX(-50%);
|
|
297
281
|
display: flex; gap: 6px; max-width: 90vw;
|
|
@@ -307,7 +291,6 @@ function _injectStyles() {
|
|
|
307
291
|
.griot-lb__thumb:hover { opacity: 0.85; }
|
|
308
292
|
.griot-lb__thumb.is-active { border-color: #6366f1; opacity: 1; }
|
|
309
293
|
.griot-lb__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
310
|
-
|
|
311
294
|
@media (max-width: 600px) {
|
|
312
295
|
.griot-lb__nav { width: 40px; height: 64px; font-size: 24px; }
|
|
313
296
|
.griot-lb__strip { display: none; }
|
|
@@ -316,5 +299,18 @@ function _injectStyles() {
|
|
|
316
299
|
document.head.appendChild(s);
|
|
317
300
|
}
|
|
318
301
|
|
|
302
|
+
// ── Singleton ─────────────────────────────────────────────────────────────────
|
|
303
|
+
// Store on globalThis so all module instances (regardless of import path or
|
|
304
|
+
// webpack chunk) share the exact same object. This prevents two overlays being
|
|
305
|
+
// appended to document.body when the module is evaluated more than once.
|
|
306
|
+
|
|
307
|
+
const GLOBAL_KEY = '__griot_lightbox__';
|
|
308
|
+
|
|
309
|
+
if (typeof globalThis !== 'undefined' && !globalThis[GLOBAL_KEY]) {
|
|
310
|
+
globalThis[GLOBAL_KEY] = new Lightbox();
|
|
311
|
+
}
|
|
312
|
+
|
|
319
313
|
/** Shared singleton — import and use directly everywhere. */
|
|
320
|
-
export const lightbox =
|
|
314
|
+
export const lightbox = (typeof globalThis !== 'undefined' && globalThis[GLOBAL_KEY])
|
|
315
|
+
? globalThis[GLOBAL_KEY]
|
|
316
|
+
: new Lightbox();
|