@0m0g1/griot 0.1.12 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0m0g1/griot",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "A self-contained block-based rich text editor and renderer built for historical document authoring.",
5
5
  "type": "module",
6
6
  "main": "./src/Griot.js",
@@ -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 (built once per open() call) ────────────────────────────
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 = new Lightbox();
314
+ export const lightbox = (typeof globalThis !== 'undefined' && globalThis[GLOBAL_KEY])
315
+ ? globalThis[GLOBAL_KEY]
316
+ : new Lightbox();