smartphoto 2.1.4 → 2.1.7

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 CHANGED
@@ -70,6 +70,14 @@ document.addEventListener('DOMContentLoaded',function(){
70
70
  </script>
71
71
  ```
72
72
 
73
+ When SmartPhoto is constructed with a CSS selector string (as above), clicks are handled via a single delegated listener, so elements added to the page **after** construction (e.g. by Ajax/infinite scroll) are picked up automatically just by clicking them — no need to call `addItem()`/`addNewItem()` manually. Right before a photo is opened, SmartPhoto also reconciles that photo's group against the current DOM: newly appended matching elements are added, and elements that have since been removed from the DOM are dropped from the group (remaining indices are recalculated). This auto-detection only applies to the selector-string form; when a `NodeList`/`Element[]` is passed (or in data source mode below), add/remove items explicitly via `addItem()`/`addNewItem()`.
74
+
75
+ A few things to keep in mind:
76
+ - Reconciliation only happens **right before a photo is opened** (via a click, `show()`, or hash restoration) — not continuously. If an element disappears from the DOM while the viewer is already open and you `next()`/`prev()` through the same session, that removal isn't reflected until the viewer is opened again.
77
+ - It does not support replacing an entire container's `innerHTML` (which recreates existing elements too, as brand-new DOM nodes) — that produces duplicate items, since the old and new elements aren't recognized as the same one. Only *appending*/*removing* individual elements is supported; if you regenerate the whole container, call `destroy()` and construct a new instance instead.
78
+ - Changing `data-group` on an element that has already been opened/registered has no effect (the group is fixed at first registration). Changing it before the element is first interacted with is picked up correctly.
79
+ - If multiple instances are built with overlapping selectors, avoid relying on distinguishing exactly which instance handles a click for elements that could match either.
80
+
73
81
  ### Programmatic usage (data source mode)
74
82
 
75
83
  Instead of scanning `<a>` elements in the page, you can pass an array of slide objects directly (inspired by [yet-another-react-lightbox](https://yet-another-react-lightbox.com/)). This is useful when your images come from an API or a JS-rendered list.
@@ -211,7 +219,7 @@ Slide fields:
211
219
  <tr>
212
220
  <td>animationSpeed</td>
213
221
  <td>animation speed (ms) when switching/opening/closing images</td>
214
- <td>300</td>
222
+ <td>450</td>
215
223
  </tr>
216
224
  <tr>
217
225
  <td>forceInterval</td>
@@ -337,7 +345,7 @@ photo.on('zoomout',function(){
337
345
  </tr>
338
346
  <tr>
339
347
  <td><code>addNewItem(element)</code></td>
340
- <td>register a new <code>&lt;a&gt;</code> thumbnail element (HTML mode)</td>
348
+ <td>register a new <code>&lt;a&gt;</code> thumbnail element (HTML mode). Only needed when constructed with a <code>NodeList</code>/<code>Element[]</code> or otherwise not using a selector string — with a selector string, elements added later are auto-detected on click (see above)</td>
341
349
  </tr>
342
350
  <tr>
343
351
  <td><code>show(indexOrId?, options?)</code></td>
@@ -353,7 +361,7 @@ photo.on('zoomout',function(){
353
361
  </tr>
354
362
  <tr>
355
363
  <td><code>addItem(slideOrElement)</code></td>
356
- <td>add a new item. Accepts a slide object (data source mode) or an <code>Element</code> (HTML mode, same as <code>addNewItem</code>)</td>
364
+ <td>add a new item. Accepts a slide object (data source mode, where explicit registration is always required) or an <code>Element</code> (HTML mode, same caveat as <code>addNewItem</code>)</td>
357
365
  </tr>
358
366
  <tr>
359
367
  <td><code>currentIndex</code></td>
@@ -372,7 +380,7 @@ photo.on('zoomout',function(){
372
380
  <tr>
373
381
  <td>--smartphoto-animation-speed</td>
374
382
  <td>animation speed when switching/opening/closing images. Overridden per-instance by the <code>animationSpeed</code> JS option</td>
375
- <td>300ms</td>
383
+ <td>450ms</td>
376
384
  </tr>
377
385
  <tr>
378
386
  <td>--smartphoto-animation-function</td>
@@ -395,7 +403,7 @@ Set these on `.smartphoto` (or `:root`) to override the defaults, no rebuild req
395
403
 
396
404
  ```css
397
405
  .smartphoto {
398
- --smartphoto-animation-speed: 500ms;
406
+ --smartphoto-animation-speed: 450ms;
399
407
  --smartphoto-animation-function: ease-in-out;
400
408
  --smartphoto-backdrop-color: rgba(0, 0, 0, 0.9);
401
409
  --smartphoto-header-color: rgba(0, 0, 0, 0.4);
@@ -110,15 +110,26 @@
110
110
  * 対象に加えることで、opacity のフェードが完了するまでその適用を1フレーム分
111
111
  * 遅らせ、閉じる瞬間に「ぱっと」消えず滑らかにフェードアウトするようにする */
112
112
  transition:
113
- opacity var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out),
114
- display var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out) allow-discrete,
115
- overlay var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out) allow-discrete;
113
+ opacity var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out),
114
+ display var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out) allow-discrete,
115
+ overlay var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out) allow-discrete;
116
116
  }
117
117
 
118
118
  .smartphoto:not([open]) {
119
119
  opacity: 0;
120
120
  }
121
121
 
122
+ /* ブラウザ標準の dialog:not([open]) { display: none } は、Bootstrap4 系の
123
+ * reboot.css 等が互換性維持のために `dialog { display: block; }` のような
124
+ * 要素セレクタ単体のルールを読み込んでいる場合、後勝ちで上書きされてしまう。
125
+ * 上書きされると閉じている dialog(position:fixed; inset:0)が画面全体に
126
+ * 残ったままクリックを吸収し続けるため、クラスセレクタを含めて詳細度を上げた
127
+ * フォールバックを明示しておく(要素セレクタ単体より詳細度が高いため、
128
+ * 読み込み順に関係なく確実に勝てる) */
129
+ dialog.smartphoto:not([open]) {
130
+ display: none !important;
131
+ }
132
+
122
133
  .smartphoto::backdrop {
123
134
  background-color: var(--smartphoto-backdrop-color, rgba(0, 0, 0, 1));
124
135
  }
@@ -127,11 +138,11 @@
127
138
  * ルート要素(html)の子として扱われ、dialog に設定した --smartphoto-animation-speed を
128
139
  * 継承しない。そのため JS 側では document.documentElement にも同じ変数を設定しており、
129
140
  * ここではその値を参照する。指定がなければブラウザ既定(実装依存, 概ね0.25s前後で
130
- * 速すぎるとの声があった)の代わりに 0.3s を使う */
141
+ * 速すぎるとの声があった)の代わりに 0.45s を使う */
131
142
  ::view-transition-group(smartphoto-hero),
132
143
  ::view-transition-old(smartphoto-hero),
133
144
  ::view-transition-new(smartphoto-hero) {
134
- animation-duration: var(--smartphoto-animation-speed, 0.3s);
145
+ animation-duration: var(--smartphoto-animation-speed, 0.45s);
135
146
  animation-timing-function: var(--smartphoto-animation-function, ease-out);
136
147
  }
137
148
 
@@ -213,7 +224,7 @@
213
224
  -moz-user-select: none;
214
225
  -ms-user-select: none;
215
226
  user-select: none;
216
- transition: transform var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
227
+ transition: transform var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
217
228
 
218
229
  -webkit-user-drag: none;
219
230
  }
@@ -229,7 +240,7 @@
229
240
  }
230
241
 
231
242
  .smartphoto-img-elasticmove {
232
- transition: transform var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
243
+ transition: transform var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
233
244
  }
234
245
 
235
246
  .smartphoto-img-wrap {
@@ -237,13 +248,13 @@
237
248
  opacity: 1;
238
249
  /* Pointer Events(ピンチ/ドラッグ)がブラウザ既定のスクロール・ズームと衝突しないようにする */
239
250
  touch-action: none;
240
- -webkit-transition: opacity var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
241
- -moz-transition: opacity var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
242
- -ms-transition: opacity var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
243
- -o-transition: opacity var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
244
- transition: opacity var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
251
+ -webkit-transition: opacity var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
252
+ -moz-transition: opacity var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
253
+ -ms-transition: opacity var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
254
+ -o-transition: opacity var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
255
+ transition: opacity var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
245
256
  animation-name: smartphoto-img-wrap;
246
- animation-duration: var(--smartphoto-animation-speed, 0.3s);
257
+ animation-duration: var(--smartphoto-animation-speed, 0.45s);
247
258
  animation-timing-function: var(--smartphoto-animation-function, ease-out);
248
259
  }
249
260
 
@@ -265,7 +276,7 @@
265
276
  left: 0;
266
277
  opacity: 1;
267
278
  animation-name: smartphoto-appear;
268
- animation-duration: var(--smartphoto-animation-speed, 0.3s);
279
+ animation-duration: var(--smartphoto-animation-speed, 0.45s);
269
280
  animation-timing-function: var(--smartphoto-animation-function, ease-out);
270
281
  }
271
282
 
@@ -282,7 +293,7 @@
282
293
  height: 30px;
283
294
  margin-top: -20px;
284
295
  box-sizing: content-box;
285
- animation-duration: var(--smartphoto-animation-speed, 0.3s);
296
+ animation-duration: var(--smartphoto-animation-speed, 0.45s);
286
297
  animation-timing-function: var(--smartphoto-animation-function, ease-out);
287
298
  animation-name: smartphoto-appear;
288
299
  }
@@ -341,7 +352,7 @@
341
352
  width: 100%;
342
353
  opacity: 1;
343
354
  animation-name: smartphoto-appear;
344
- animation-duration: var(--smartphoto-animation-speed, 0.3s);
355
+ animation-duration: var(--smartphoto-animation-speed, 0.45s);
345
356
  animation-timing-function: var(--smartphoto-animation-function, ease-out);
346
357
  }
347
358
 
@@ -417,7 +428,7 @@
417
428
  left: 0;
418
429
  width: 100%;
419
430
  height: 100%;
420
- transition: all var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
431
+ transition: all var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
421
432
  }
422
433
 
423
434
  .smartphoto-list li:focus {
@@ -485,7 +496,7 @@
485
496
  z-index: 100;
486
497
  top: 0;
487
498
  left: 0;
488
- transition: all var(--smartphoto-animation-speed, 0.3s) var(--smartphoto-animation-function, ease-out);
499
+ transition: all var(--smartphoto-animation-speed, 0.45s) var(--smartphoto-animation-function, ease-out);
489
500
  }
490
501
 
491
502
  .smartphoto-sr-only {
@@ -1 +1 @@
1
- @keyframes smartphoto{0%{opacity:0}to{opacity:1}}@keyframes smartphoto-img-wrap{0%{opacity:0}to{opacity:1}}@keyframes smartphoto-inner{0%{transform:translateY(100px)}to{transform:translate(0)}}@keyframes smartphoto-loader{0%{opacity:.4;transform:rotate(0deg)}50%{opacity:1;transform:rotate(180deg)}to{opacity:.4;transform:rotate(1turn)}}@keyframes smartphoto-appear{0%{opacity:0}to{opacity:1}}@keyframes smartphoto-hide{0%{opacity:1}to{opacity:0}}:root:has(dialog.smartphoto[open]){overflow:hidden}.smartphoto{border:none;background:transparent;padding:0;margin:0;outline:none;position:fixed;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden;height:var(--smartphoto-vh,100dvh);max-height:var(--smartphoto-vh,100dvh);background-color:var(--smartphoto-backdrop-color,#000);opacity:1;font-family:sans-serif;cursor:pointer;transition:opacity var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out),display var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out) allow-discrete,overlay var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out) allow-discrete}.smartphoto:not([open]){opacity:0}.smartphoto::backdrop{background-color:var(--smartphoto-backdrop-color,#000)}::view-transition-group(smartphoto-hero),::view-transition-new(smartphoto-hero),::view-transition-old(smartphoto-hero){animation-duration:var(--smartphoto-animation-speed,.3s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-close{opacity:0}.smartphoto-count{display:inline-block;color:#fff;font-size:16px}.smartphoto-header{display:block;box-sizing:border-box;position:fixed;z-index:102;top:0;left:0;width:100%;height:50px;padding:15px;background-color:var(--smartphoto-header-color,rgba(0,0,0,.2))}.smartphoto-content{display:block;position:absolute;top:0;left:0;width:100%;height:100%;touch-action:none}.smartphoto-dismiss{display:block;position:absolute;top:15px;right:10px;width:20px;height:20px;padding:0;border:none;background-color:transparent;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIGRhdGEtbmFtZT0i44Os44Kk44Ok44O8IDEiIHZpZXdCb3g9IjAgMCAyODM0LjY1IDI4MzQuNjUiPjx0aXRsZT5pY29uPC90aXRsZT48cGF0aCBkPSJNMTU3Ni40MiAxNDA2Ljc2IDI3ODQgMTk5LjE5YTU2Ljg2IDU2Ljg2IDAgMCAwIDAtODAuMThsLTc4LjkyLTc4LjkyYTU2Ljg2IDU2Ljg2IDAgMCAwLTgwLjE4IDBMMTQxNy4zMiAxMjQ3LjY2IDIwOS43NSA0MC4wOWE1Ni44NiA1Ni44NiAwIDAgMC04MC4xOCAwTDUwLjY1IDExOWE1Ni44NiA1Ni44NiAwIDAgMCAwIDgwLjE4bDEyMDcuNTggMTIwNy41OEw1MC42NSAyNjE0LjM0YTU2Ljg2IDU2Ljg2IDAgMCAwIDAgODAuMThsNzguOTIgNzguOTJhNTYuODYgNTYuODYgMCAwIDAgODAuMTggMGwxMjA3LjU3LTEyMDcuNThMMjYyNC45IDI3NzMuNDRhNTYuODYgNTYuODYgMCAwIDAgODAuMTggMGw3OC45Mi03OC45MmE1Ni44NiA1Ni44NiAwIDAgMCAwLTgwLjE4WiIvPjwvc3ZnPg==);text-shadow:0 1px 0 #fff;color:#fff;font-size:30px;text-decoration:none;cursor:pointer;line-height:1}.smartphoto-body{z-index:102;margin:0 auto}.smartphoto-body,.smartphoto-inner{position:relative;width:100%;height:100%}.smartphoto-inner{vertical-align:top}.smartphoto-img{display:none;max-width:none;width:auto;height:auto;cursor:zoom-in;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;transition:transform var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out);-webkit-user-drag:none}.smartphoto-img.active{display:block}.smartphoto-img-onmove{cursor:grab;cursor:-webkit-grab;transition:none}.smartphoto-img-elasticmove{transition:transform var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out)}.smartphoto-img-wrap{display:inline-block;opacity:1;touch-action:none;-webkit-transition:opacity var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out);-moz-transition:opacity var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out);-ms-transition:opacity var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out);-o-transition:opacity var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out);transition:opacity var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out);animation-name:smartphoto-img-wrap;animation-duration:var(--smartphoto-animation-speed,.3s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-img-left{transform:translateX(150%)!important}.smartphoto-img-right{transform:translateX(-150%)!important}.smartphoto-arrows{list-style-type:none;margin:0;padding:0;position:relative;z-index:1002;top:50%;left:0;opacity:1;animation-name:smartphoto-appear;animation-duration:var(--smartphoto-animation-speed,.3s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-arrows[aria-hidden=true]{animation-name:smartphoto-hide;display:none}.smartphoto-arrows li{display:block;position:absolute;top:50%;width:30px;height:30px;margin-top:-20px;box-sizing:content-box;animation-duration:var(--smartphoto-animation-speed,.3s);animation-timing-function:var(--smartphoto-animation-function,ease-out);animation-name:smartphoto-appear}.smartphoto-arrows li:focus{outline:none}.smartphoto-arrows [aria-hidden=true]{animation-name:smartphoto-hide;display:none}.smartphoto-arrows button{appearance:none;display:block;width:100%;height:100%;margin:0;padding:0;border:none;background-color:transparent;cursor:pointer}.smartphoto-arrow-right{right:0;padding:5px 0;background-color:rgba(0,0,0,.5)}.smartphoto-arrow-right button{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIGRhdGEtbmFtZT0i44Os44Kk44Ok44O8IDEiIHZpZXdCb3g9IjAgMCAyODM0LjY1IDI4MzQuNjUiPjx0aXRsZT5pY29uPC90aXRsZT48cGF0aCBkPSJNMTgzNy44OCAxNDE3LjMyIDY0My41OSAyMjNhNzIuMjEgNzIuMjEgMCAwIDEgMC0xMDEuODJMNzQzLjgyIDIxYTcyLjIxIDcyLjIxIDAgMCAxIDEwMS44MiAwbDEyNDUuMTkgMTI0NS4xOSAxMDAuMjMgMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAxIDAgMTAxLjgyTDg0NS42NCAyODEzLjY1YTcyLjIxIDcyLjIxIDAgMCAxLTEwMS44MiAwbC0xMDAuMjMtMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAxIDAtMTAxLjgyWiIvPjwvc3ZnPg==)}.smartphoto-arrow-left{left:0;padding:5px 0;background-color:rgba(0,0,0,.5)}.smartphoto-arrow-left button{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIGRhdGEtbmFtZT0i44Os44Kk44Ok44O8IDEiIHZpZXdCb3g9IjAgMCAyODM0LjY1IDI4MzQuNjUiPjx0aXRsZT5pY29uPC90aXRsZT48cGF0aCBkPSJNOTk2Ljc3IDE0MTcuMzIgMjE5MS4wNiAyMjNhNzIuMjEgNzIuMjEgMCAwIDAgMC0xMDEuODJMMjA5MC44MyAyMUE3Mi4yMSA3Mi4yMSAwIDAgMCAxOTg5IDIxTDc0My44MiAxMjY2LjE5bC0xMDAuMjMgMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAwIDAgMTAxLjgyTDE5ODkgMjgxMy42NWE3Mi4yMSA3Mi4yMSAwIDAgMCAxMDEuODIgMGwxMDAuMjMtMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAwIDAtMTAxLjgyWiIvPjwvc3ZnPg==)}.smartPhotoArrowHideIcon{display:none}.smartphoto-nav{position:absolute;bottom:0;left:0;width:100%;opacity:1;animation-name:smartphoto-appear;animation-duration:var(--smartphoto-animation-speed,.3s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-nav[aria-hidden=true]{animation-name:smartphoto-hide;display:none}.smartphoto-nav ul{display:block;overflow-x:auto;list-style:none;margin:0;padding:0;text-align:center;white-space:nowrap;-webkit-overflow-scrolling:touch}.smartphoto-nav li{display:inline-block;overflow:hidden;width:50px;height:50px}.smartphoto-nav button{appearance:none;display:block;width:100%;height:100%;margin:0;padding:0;border:none;background-color:#fff;background-position:50%;background-size:cover;opacity:.5;cursor:pointer}.smartphoto-nav button:focus{opacity:.8}.smartphoto-nav button.current{opacity:1}.smartphoto-nav img{width:auto;height:100%}.smartphoto-list{list-style-type:none;position:absolute;z-index:101;top:0;left:0;margin:0;padding:0;white-space:nowrap}.smartphoto-list li{display:block;position:absolute;top:0;left:0;width:100%;height:100%;transition:all var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out)}.smartphoto-list li:focus{outline:none}.smartphoto-list-onmove{transition:all .3s var(--smartphoto-animation-function,ease-out)}.smartphoto-caption{overflow:hidden;box-sizing:border-box;position:absolute;top:0;left:0;width:100%;height:50px;padding:0 50px;color:#fff;font-size:12px;text-align:center;line-height:50px;white-space:nowrap;text-overflow:ellipsis;margin:0;font-weight:400}.smartphoto-caption:focus{outline:none}.smartphoto-loader-wrap{display:block;position:relative;z-index:103;width:0;height:0;transform:translate(50vw,50vh);transform:translate(50vw,50dvh);transform:translate(50vw,calc(var(--smartphoto-vh, 100dvh)/2))}.smartphoto-loader{position:absolute;z-index:101;top:0;left:0;width:30px;height:30px;margin-top:-25px;margin-left:-25px;border:8px solid #17cddd;border-right-color:transparent;border-radius:50%;animation:smartphoto-loader .5s linear infinite}.smartphoto-img-clone{position:fixed;z-index:100;top:0;left:0;transition:all var(--smartphoto-animation-speed,.3s) var(--smartphoto-animation-function,ease-out)}.smartphoto-sr-only{overflow:hidden;position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;clip:rect(0,0,0,0)}
1
+ @keyframes smartphoto{0%{opacity:0}to{opacity:1}}@keyframes smartphoto-img-wrap{0%{opacity:0}to{opacity:1}}@keyframes smartphoto-inner{0%{transform:translateY(100px)}to{transform:translate(0)}}@keyframes smartphoto-loader{0%{opacity:.4;transform:rotate(0deg)}50%{opacity:1;transform:rotate(180deg)}to{opacity:.4;transform:rotate(1turn)}}@keyframes smartphoto-appear{0%{opacity:0}to{opacity:1}}@keyframes smartphoto-hide{0%{opacity:1}to{opacity:0}}:root:has(dialog.smartphoto[open]){overflow:hidden}.smartphoto{border:none;background:transparent;padding:0;margin:0;outline:none;position:fixed;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden;height:var(--smartphoto-vh,100dvh);max-height:var(--smartphoto-vh,100dvh);background-color:var(--smartphoto-backdrop-color,#000);opacity:1;font-family:sans-serif;cursor:pointer;transition:opacity var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out),display var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out) allow-discrete,overlay var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out) allow-discrete}.smartphoto:not([open]){opacity:0}dialog.smartphoto:not([open]){display:none!important}.smartphoto::backdrop{background-color:var(--smartphoto-backdrop-color,#000)}::view-transition-group(smartphoto-hero),::view-transition-new(smartphoto-hero),::view-transition-old(smartphoto-hero){animation-duration:var(--smartphoto-animation-speed,.45s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-close{opacity:0}.smartphoto-count{display:inline-block;color:#fff;font-size:16px}.smartphoto-header{display:block;box-sizing:border-box;position:fixed;z-index:102;top:0;left:0;width:100%;height:50px;padding:15px;background-color:var(--smartphoto-header-color,rgba(0,0,0,.2))}.smartphoto-content{display:block;position:absolute;top:0;left:0;width:100%;height:100%;touch-action:none}.smartphoto-dismiss{display:block;position:absolute;top:15px;right:10px;width:20px;height:20px;padding:0;border:none;background-color:transparent;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIGRhdGEtbmFtZT0i44Os44Kk44Ok44O8IDEiIHZpZXdCb3g9IjAgMCAyODM0LjY1IDI4MzQuNjUiPjx0aXRsZT5pY29uPC90aXRsZT48cGF0aCBkPSJNMTU3Ni40MiAxNDA2Ljc2IDI3ODQgMTk5LjE5YTU2Ljg2IDU2Ljg2IDAgMCAwIDAtODAuMThsLTc4LjkyLTc4LjkyYTU2Ljg2IDU2Ljg2IDAgMCAwLTgwLjE4IDBMMTQxNy4zMiAxMjQ3LjY2IDIwOS43NSA0MC4wOWE1Ni44NiA1Ni44NiAwIDAgMC04MC4xOCAwTDUwLjY1IDExOWE1Ni44NiA1Ni44NiAwIDAgMCAwIDgwLjE4bDEyMDcuNTggMTIwNy41OEw1MC42NSAyNjE0LjM0YTU2Ljg2IDU2Ljg2IDAgMCAwIDAgODAuMThsNzguOTIgNzguOTJhNTYuODYgNTYuODYgMCAwIDAgODAuMTggMGwxMjA3LjU3LTEyMDcuNThMMjYyNC45IDI3NzMuNDRhNTYuODYgNTYuODYgMCAwIDAgODAuMTggMGw3OC45Mi03OC45MmE1Ni44NiA1Ni44NiAwIDAgMCAwLTgwLjE4WiIvPjwvc3ZnPg==);text-shadow:0 1px 0 #fff;color:#fff;font-size:30px;text-decoration:none;cursor:pointer;line-height:1}.smartphoto-body{z-index:102;margin:0 auto}.smartphoto-body,.smartphoto-inner{position:relative;width:100%;height:100%}.smartphoto-inner{vertical-align:top}.smartphoto-img{display:none;max-width:none;width:auto;height:auto;cursor:zoom-in;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;transition:transform var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out);-webkit-user-drag:none}.smartphoto-img.active{display:block}.smartphoto-img-onmove{cursor:grab;cursor:-webkit-grab;transition:none}.smartphoto-img-elasticmove{transition:transform var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out)}.smartphoto-img-wrap{display:inline-block;opacity:1;touch-action:none;-webkit-transition:opacity var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out);-moz-transition:opacity var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out);-ms-transition:opacity var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out);-o-transition:opacity var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out);transition:opacity var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out);animation-name:smartphoto-img-wrap;animation-duration:var(--smartphoto-animation-speed,.45s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-img-left{transform:translateX(150%)!important}.smartphoto-img-right{transform:translateX(-150%)!important}.smartphoto-arrows{list-style-type:none;margin:0;padding:0;position:relative;z-index:1002;top:50%;left:0;opacity:1;animation-name:smartphoto-appear;animation-duration:var(--smartphoto-animation-speed,.45s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-arrows[aria-hidden=true]{animation-name:smartphoto-hide;display:none}.smartphoto-arrows li{display:block;position:absolute;top:50%;width:30px;height:30px;margin-top:-20px;box-sizing:content-box;animation-duration:var(--smartphoto-animation-speed,.45s);animation-timing-function:var(--smartphoto-animation-function,ease-out);animation-name:smartphoto-appear}.smartphoto-arrows li:focus{outline:none}.smartphoto-arrows [aria-hidden=true]{animation-name:smartphoto-hide;display:none}.smartphoto-arrows button{appearance:none;display:block;width:100%;height:100%;margin:0;padding:0;border:none;background-color:transparent;cursor:pointer}.smartphoto-arrow-right{right:0;padding:5px 0;background-color:rgba(0,0,0,.5)}.smartphoto-arrow-right button{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIGRhdGEtbmFtZT0i44Os44Kk44Ok44O8IDEiIHZpZXdCb3g9IjAgMCAyODM0LjY1IDI4MzQuNjUiPjx0aXRsZT5pY29uPC90aXRsZT48cGF0aCBkPSJNMTgzNy44OCAxNDE3LjMyIDY0My41OSAyMjNhNzIuMjEgNzIuMjEgMCAwIDEgMC0xMDEuODJMNzQzLjgyIDIxYTcyLjIxIDcyLjIxIDAgMCAxIDEwMS44MiAwbDEyNDUuMTkgMTI0NS4xOSAxMDAuMjMgMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAxIDAgMTAxLjgyTDg0NS42NCAyODEzLjY1YTcyLjIxIDcyLjIxIDAgMCAxLTEwMS44MiAwbC0xMDAuMjMtMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAxIDAtMTAxLjgyWiIvPjwvc3ZnPg==)}.smartphoto-arrow-left{left:0;padding:5px 0;background-color:rgba(0,0,0,.5)}.smartphoto-arrow-left button{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIGRhdGEtbmFtZT0i44Os44Kk44Ok44O8IDEiIHZpZXdCb3g9IjAgMCAyODM0LjY1IDI4MzQuNjUiPjx0aXRsZT5pY29uPC90aXRsZT48cGF0aCBkPSJNOTk2Ljc3IDE0MTcuMzIgMjE5MS4wNiAyMjNhNzIuMjEgNzIuMjEgMCAwIDAgMC0xMDEuODJMMjA5MC44MyAyMUE3Mi4yMSA3Mi4yMSAwIDAgMCAxOTg5IDIxTDc0My44MiAxMjY2LjE5bC0xMDAuMjMgMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAwIDAgMTAxLjgyTDE5ODkgMjgxMy42NWE3Mi4yMSA3Mi4yMSAwIDAgMCAxMDEuODIgMGwxMDAuMjMtMTAwLjIzYTcyLjIxIDcyLjIxIDAgMCAwIDAtMTAxLjgyWiIvPjwvc3ZnPg==)}.smartPhotoArrowHideIcon{display:none}.smartphoto-nav{position:absolute;bottom:0;left:0;width:100%;opacity:1;animation-name:smartphoto-appear;animation-duration:var(--smartphoto-animation-speed,.45s);animation-timing-function:var(--smartphoto-animation-function,ease-out)}.smartphoto-nav[aria-hidden=true]{animation-name:smartphoto-hide;display:none}.smartphoto-nav ul{display:block;overflow-x:auto;list-style:none;margin:0;padding:0;text-align:center;white-space:nowrap;-webkit-overflow-scrolling:touch}.smartphoto-nav li{display:inline-block;overflow:hidden;width:50px;height:50px}.smartphoto-nav button{appearance:none;display:block;width:100%;height:100%;margin:0;padding:0;border:none;background-color:#fff;background-position:50%;background-size:cover;opacity:.5;cursor:pointer}.smartphoto-nav button:focus{opacity:.8}.smartphoto-nav button.current{opacity:1}.smartphoto-nav img{width:auto;height:100%}.smartphoto-list{list-style-type:none;position:absolute;z-index:101;top:0;left:0;margin:0;padding:0;white-space:nowrap}.smartphoto-list li{display:block;position:absolute;top:0;left:0;width:100%;height:100%;transition:all var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out)}.smartphoto-list li:focus{outline:none}.smartphoto-list-onmove{transition:all .3s var(--smartphoto-animation-function,ease-out)}.smartphoto-caption{overflow:hidden;box-sizing:border-box;position:absolute;top:0;left:0;width:100%;height:50px;padding:0 50px;color:#fff;font-size:12px;text-align:center;line-height:50px;white-space:nowrap;text-overflow:ellipsis;margin:0;font-weight:400}.smartphoto-caption:focus{outline:none}.smartphoto-loader-wrap{display:block;position:relative;z-index:103;width:0;height:0;transform:translate(50vw,50vh);transform:translate(50vw,50dvh);transform:translate(50vw,calc(var(--smartphoto-vh, 100dvh)/2))}.smartphoto-loader{position:absolute;z-index:101;top:0;left:0;width:30px;height:30px;margin-top:-25px;margin-left:-25px;border:8px solid #17cddd;border-right-color:transparent;border-radius:50%;animation:smartphoto-loader .5s linear infinite}.smartphoto-img-clone{position:fixed;z-index:100;top:0;left:0;transition:all var(--smartphoto-animation-speed,.45s) var(--smartphoto-animation-function,ease-out)}.smartphoto-sr-only{overflow:hidden;position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;clip:rect(0,0,0,0)}
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * SmartPhoto v2.1.4
2
+ * SmartPhoto v2.1.7
3
3
  * (c) appleple
4
4
  * Released under the MIT License.
5
5
  */
@@ -114,7 +114,7 @@
114
114
  loadOffset: 2,
115
115
  resizeStyle: "fit",
116
116
  lazyAttribute: "data-src",
117
- animationSpeed: 300
117
+ animationSpeed: 450
118
118
  };
119
119
  function deepFreeze(obj) {
120
120
  Object.keys(obj).forEach((key) => {
@@ -128,7 +128,11 @@
128
128
  function createState(settings) {
129
129
  return {
130
130
  options: deepFreeze(
131
- extend({}, defaults, settings)
131
+ extend(
132
+ {},
133
+ defaults,
134
+ settings
135
+ )
132
136
  ),
133
137
  viewer: {
134
138
  isOpen: false,
@@ -793,6 +797,7 @@
793
797
  const dialog = document.createElement("dialog");
794
798
  dialog.className = classNames.smartPhoto;
795
799
  dialog.setAttribute("aria-labelledby", `smartphoto-${id}-title`);
800
+ dialog.style.display = "none";
796
801
  dialog.style.setProperty(
797
802
  "--smartphoto-animation-speed",
798
803
  `${options.animationSpeed}ms`
@@ -1139,6 +1144,44 @@
1139
1144
  this.timeouts = [];
1140
1145
  this.loadAllFired = /* @__PURE__ */ new Set();
1141
1146
  this.syncedGroupId = null;
1147
+ // 文字列セレクタで構築された場合のみ設定される。Ajax等で後から追加された
1148
+ // 要素をクリック時に動的検出するための再スキャン起点(§discoverGroupElements)
1149
+ this.rootSelector = null;
1150
+ // クリックされた要素(またはその祖先)から登録済み Item を逆引きするための
1151
+ // キャッシュ。documentへのイベントデリゲーションで祖先チェーンを辿る際に使う
1152
+ this.itemsByElement = /* @__PURE__ */ new Map();
1153
+ // サムネイルクリックはこの1本の document デリゲーションリスナーだけで処理する
1154
+ // (個別バインドは廃止)。同じクリックイベントを複数の SmartPhoto インスタンスが
1155
+ // 二重処理しないよう、処理済みマーカーをイベント自体に立てて後続インスタンスに
1156
+ // 早期returnさせる(セレクタが重複するインスタンスが同時に存在するケース)
1157
+ this.handleDocumentClick = (e) => {
1158
+ if (!(e.target instanceof Element)) {
1159
+ return;
1160
+ }
1161
+ const marker = e;
1162
+ if (marker.__smartphotoClaimed) {
1163
+ return;
1164
+ }
1165
+ let matched = this.findRegisteredAncestor(e.target);
1166
+ if (!matched && this.rootSelector) {
1167
+ matched = e.target.closest(this.rootSelector);
1168
+ }
1169
+ if (!matched) {
1170
+ return;
1171
+ }
1172
+ e.preventDefault();
1173
+ marker.__smartphotoClaimed = true;
1174
+ if (!this.itemsByElement.has(matched) && this.rootSelector) {
1175
+ const groupId = groupIdFromElement(matched);
1176
+ if (this.resyncGroupFromDom(groupId)) {
1177
+ this.syncCurrentGroupView();
1178
+ }
1179
+ }
1180
+ const item = this.itemsByElement.get(matched);
1181
+ if (item) {
1182
+ this.openPhoto(item, matched);
1183
+ }
1184
+ };
1142
1185
  // ---- 内部: window イベント ----
1143
1186
  this.updateViewportHeight = () => {
1144
1187
  this.view.refs.dialog.style.setProperty(
@@ -1150,6 +1193,7 @@
1150
1193
  if (!currentItems(this.state)) {
1151
1194
  return;
1152
1195
  }
1196
+ this.updateViewportHeight();
1153
1197
  this.resetTranslateCurrent();
1154
1198
  this.setPosByCurrentIndex();
1155
1199
  this.setSizeByScreen();
@@ -1172,6 +1216,7 @@
1172
1216
  if (!currentItems(this.state)) {
1173
1217
  return;
1174
1218
  }
1219
+ this.updateViewportHeight();
1175
1220
  this.resetTranslateCurrent();
1176
1221
  this.setPosByCurrentIndex();
1177
1222
  this.setHashByCurrentIndex();
@@ -1182,6 +1227,7 @@
1182
1227
  const poll = (time) => {
1183
1228
  this.scheduleTimeout(() => {
1184
1229
  if (prevWidth !== getWindowWidth()) {
1230
+ this.updateViewportHeight();
1185
1231
  this.resetTranslateCurrent();
1186
1232
  this.setPosByCurrentIndex();
1187
1233
  this.setHashByCurrentIndex();
@@ -1194,6 +1240,7 @@
1194
1240
  };
1195
1241
  poll(0);
1196
1242
  };
1243
+ this.rootSelector = typeof source === "string" ? source : null;
1197
1244
  this.state = createState(settings != null ? settings : {});
1198
1245
  this.view = createView(
1199
1246
  { id: this.id, options: this.state.options },
@@ -1215,15 +1262,14 @@
1215
1262
  },
1216
1263
  { signal: this.abortController.signal }
1217
1264
  );
1265
+ document.addEventListener("click", this.handleDocumentClick, {
1266
+ signal: this.abortController.signal
1267
+ });
1218
1268
  this.ingestSource(source);
1219
1269
  this.syncCurrentGroupView();
1220
1270
  const restored = this.restoreFromHash();
1221
1271
  if (restored) {
1222
- if (restored.element) {
1223
- triggerEvent(restored.element, "click");
1224
- } else {
1225
- this.openPhoto(restored, null);
1226
- }
1272
+ this.openPhoto(restored, restored.element);
1227
1273
  }
1228
1274
  this.updateViewportHeight();
1229
1275
  if (window.visualViewport) {
@@ -1268,6 +1314,7 @@
1268
1314
  clearTimeout(id);
1269
1315
  });
1270
1316
  this.timeouts = [];
1317
+ this.itemsByElement.clear();
1271
1318
  this.gestures.detach();
1272
1319
  this.view.destroy();
1273
1320
  }
@@ -1382,7 +1429,7 @@
1382
1429
  this.gotoSlide(this.state.viewer.prev);
1383
1430
  }
1384
1431
  addItem(slideOrElement) {
1385
- const item = slideOrElement instanceof Element ? this.addElementItem(slideOrElement) : this.addSlideItem(slideOrElement);
1432
+ const item = slideOrElement instanceof HTMLElement ? this.addElementItem(slideOrElement) : this.addSlideItem(slideOrElement);
1386
1433
  this.syncCurrentGroupView();
1387
1434
  return item;
1388
1435
  }
@@ -1414,7 +1461,7 @@
1414
1461
  );
1415
1462
  addItemToGroup(this.state, item);
1416
1463
  this.loadAllFired.delete(groupId);
1417
- this.bindThumbnailClick(element, item);
1464
+ this.itemsByElement.set(element, item);
1418
1465
  return item;
1419
1466
  }
1420
1467
  addSlideItem(slide) {
@@ -1426,15 +1473,73 @@
1426
1473
  this.loadAllFired.delete(groupId);
1427
1474
  return item;
1428
1475
  }
1429
- bindThumbnailClick(element, item) {
1430
- element.addEventListener(
1431
- "click",
1432
- (e) => {
1433
- e.preventDefault();
1434
- this.openPhoto(item, element);
1435
- },
1436
- { signal: this.abortController.signal }
1437
- );
1476
+ // クリックされた要素(またはその祖先。<a><img></a> img がクリックされる
1477
+ // ケースを含む)から、登録済み Item を持つ要素まで祖先チェーンを遡って探す
1478
+ findRegisteredAncestor(target) {
1479
+ let node = target;
1480
+ while (node) {
1481
+ if (this.itemsByElement.has(node)) {
1482
+ return node;
1483
+ }
1484
+ node = node.parentElement;
1485
+ }
1486
+ return null;
1487
+ }
1488
+ // rootSelector(文字列セレクタで構築した場合のみ設定される)を再スキャンし、
1489
+ // 指定グループを現在のDOM状態に合わせて丸ごと再構築する(追加・削除・並び順の
1490
+ // 変化を一度に反映)。既知要素は既存の Item オブジェクトをそのまま再利用する
1491
+ // ため loaded/width/height は保持され、DOM から消えた要素だけ itemsByElement
1492
+ // からも除去する。index/translateX は resetTranslate() で再計算する。
1493
+ // 呼び出し元(openPhoto)がこの結果を使い、変化があった場合のみ view を
1494
+ // 再同期する(§ダイアログを開く瞬間に整合を取る。開いている間の next()/prev()
1495
+ // では呼ばれないため、この間の追加/削除は次に開き直すまで反映されない)
1496
+ resyncGroupFromDom(groupId) {
1497
+ var _a;
1498
+ if (!this.rootSelector) {
1499
+ return false;
1500
+ }
1501
+ const domElements = Array.from(
1502
+ document.querySelectorAll(this.rootSelector)
1503
+ ).filter((el) => groupIdFromElement(el) === groupId);
1504
+ const domElementSet = new Set(domElements);
1505
+ const previous = (_a = this.state.groups.get(groupId)) != null ? _a : [];
1506
+ const previousByElement = /* @__PURE__ */ new Map();
1507
+ previous.forEach((it) => {
1508
+ if (it.element) {
1509
+ previousByElement.set(it.element, it);
1510
+ }
1511
+ });
1512
+ let changed = domElements.length !== previous.length;
1513
+ const rebuilt = domElements.map((el, index) => {
1514
+ const existing = previousByElement.get(el);
1515
+ if (existing) {
1516
+ if (existing.index !== index) {
1517
+ changed = true;
1518
+ existing.index = index;
1519
+ }
1520
+ return existing;
1521
+ }
1522
+ changed = true;
1523
+ const item = itemFromElement(
1524
+ el,
1525
+ this.state.options,
1526
+ index,
1527
+ getWindowWidth()
1528
+ );
1529
+ this.itemsByElement.set(el, item);
1530
+ return item;
1531
+ });
1532
+ previous.forEach((it) => {
1533
+ if (it.element && !domElementSet.has(it.element)) {
1534
+ this.itemsByElement.delete(it.element);
1535
+ }
1536
+ });
1537
+ resetTranslate(rebuilt, getWindowWidth());
1538
+ this.state.groups.set(groupId, rebuilt);
1539
+ if (changed) {
1540
+ this.loadAllFired.delete(groupId);
1541
+ }
1542
+ return changed;
1438
1543
  }
1439
1544
  syncCurrentGroupView() {
1440
1545
  const items = currentItems(this.state);
@@ -1503,10 +1608,12 @@
1503
1608
  syncDialog() {
1504
1609
  const { dialog, caption } = this.view.refs;
1505
1610
  if (this.state.viewer.isOpen && !dialog.open) {
1611
+ dialog.style.removeProperty("display");
1506
1612
  dialog.showModal();
1507
1613
  caption.focus();
1508
1614
  } else if (!this.state.viewer.isOpen && dialog.open) {
1509
1615
  dialog.close();
1616
+ dialog.style.display = "none";
1510
1617
  }
1511
1618
  }
1512
1619
  commit() {
@@ -1692,10 +1799,11 @@
1692
1799
  });
1693
1800
  }
1694
1801
  openPhoto(item, trigger) {
1802
+ const groupChanged = this.rootSelector ? this.resyncGroupFromDom(item.groupId) : false;
1695
1803
  this.lastTriggerElement = trigger;
1696
1804
  this.state.viewer.currentGroup = item.groupId;
1697
1805
  this.state.viewer.currentIndex = item.index;
1698
- if (this.syncedGroupId !== item.groupId) {
1806
+ if (this.syncedGroupId !== item.groupId || groupChanged) {
1699
1807
  this.syncCurrentGroupView();
1700
1808
  }
1701
1809
  this.setHashByCurrentIndex();
@@ -1787,6 +1895,7 @@
1787
1895
  this.state.viewer.photoPosX = 0;
1788
1896
  this.state.viewer.photoPosY = 0;
1789
1897
  this.state.viewer.onMove = true;
1898
+ this.updateViewportHeight();
1790
1899
  this.setPosByCurrentIndex();
1791
1900
  this.setHashByCurrentIndex();
1792
1901
  this.setSizeByScreen();