smartphoto 2.1.4 → 2.1.6

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.
@@ -106,7 +106,7 @@ var defaults = {
106
106
  loadOffset: 2,
107
107
  resizeStyle: "fit",
108
108
  lazyAttribute: "data-src",
109
- animationSpeed: 300
109
+ animationSpeed: 450
110
110
  };
111
111
  function deepFreeze(obj) {
112
112
  Object.keys(obj).forEach((key) => {
@@ -120,7 +120,11 @@ function deepFreeze(obj) {
120
120
  function createState(settings) {
121
121
  return {
122
122
  options: deepFreeze(
123
- extend({}, defaults, settings)
123
+ extend(
124
+ {},
125
+ defaults,
126
+ settings
127
+ )
124
128
  ),
125
129
  viewer: {
126
130
  isOpen: false,
@@ -1120,6 +1124,44 @@ var SmartPhoto = class {
1120
1124
  this.timeouts = [];
1121
1125
  this.loadAllFired = /* @__PURE__ */ new Set();
1122
1126
  this.syncedGroupId = null;
1127
+ // 文字列セレクタで構築された場合のみ設定される。Ajax等で後から追加された
1128
+ // 要素をクリック時に動的検出するための再スキャン起点(§discoverGroupElements)
1129
+ this.rootSelector = null;
1130
+ // クリックされた要素(またはその祖先)から登録済み Item を逆引きするための
1131
+ // キャッシュ。documentへのイベントデリゲーションで祖先チェーンを辿る際に使う
1132
+ this.itemsByElement = /* @__PURE__ */ new Map();
1133
+ // サムネイルクリックはこの1本の document デリゲーションリスナーだけで処理する
1134
+ // (個別バインドは廃止)。同じクリックイベントを複数の SmartPhoto インスタンスが
1135
+ // 二重処理しないよう、処理済みマーカーをイベント自体に立てて後続インスタンスに
1136
+ // 早期returnさせる(セレクタが重複するインスタンスが同時に存在するケース)
1137
+ this.handleDocumentClick = (e) => {
1138
+ if (!(e.target instanceof Element)) {
1139
+ return;
1140
+ }
1141
+ const marker = e;
1142
+ if (marker.__smartphotoClaimed) {
1143
+ return;
1144
+ }
1145
+ let matched = this.findRegisteredAncestor(e.target);
1146
+ if (!matched && this.rootSelector) {
1147
+ matched = e.target.closest(this.rootSelector);
1148
+ }
1149
+ if (!matched) {
1150
+ return;
1151
+ }
1152
+ e.preventDefault();
1153
+ marker.__smartphotoClaimed = true;
1154
+ if (!this.itemsByElement.has(matched) && this.rootSelector) {
1155
+ const groupId = groupIdFromElement(matched);
1156
+ if (this.resyncGroupFromDom(groupId)) {
1157
+ this.syncCurrentGroupView();
1158
+ }
1159
+ }
1160
+ const item = this.itemsByElement.get(matched);
1161
+ if (item) {
1162
+ this.openPhoto(item, matched);
1163
+ }
1164
+ };
1123
1165
  // ---- 内部: window イベント ----
1124
1166
  this.updateViewportHeight = () => {
1125
1167
  this.view.refs.dialog.style.setProperty(
@@ -1131,6 +1173,7 @@ var SmartPhoto = class {
1131
1173
  if (!currentItems(this.state)) {
1132
1174
  return;
1133
1175
  }
1176
+ this.updateViewportHeight();
1134
1177
  this.resetTranslateCurrent();
1135
1178
  this.setPosByCurrentIndex();
1136
1179
  this.setSizeByScreen();
@@ -1153,6 +1196,7 @@ var SmartPhoto = class {
1153
1196
  if (!currentItems(this.state)) {
1154
1197
  return;
1155
1198
  }
1199
+ this.updateViewportHeight();
1156
1200
  this.resetTranslateCurrent();
1157
1201
  this.setPosByCurrentIndex();
1158
1202
  this.setHashByCurrentIndex();
@@ -1163,6 +1207,7 @@ var SmartPhoto = class {
1163
1207
  const poll = (time) => {
1164
1208
  this.scheduleTimeout(() => {
1165
1209
  if (prevWidth !== getWindowWidth()) {
1210
+ this.updateViewportHeight();
1166
1211
  this.resetTranslateCurrent();
1167
1212
  this.setPosByCurrentIndex();
1168
1213
  this.setHashByCurrentIndex();
@@ -1175,6 +1220,7 @@ var SmartPhoto = class {
1175
1220
  };
1176
1221
  poll(0);
1177
1222
  };
1223
+ this.rootSelector = typeof source === "string" ? source : null;
1178
1224
  this.state = createState(settings ?? {});
1179
1225
  this.view = createView(
1180
1226
  { id: this.id, options: this.state.options },
@@ -1196,15 +1242,14 @@ var SmartPhoto = class {
1196
1242
  },
1197
1243
  { signal: this.abortController.signal }
1198
1244
  );
1245
+ document.addEventListener("click", this.handleDocumentClick, {
1246
+ signal: this.abortController.signal
1247
+ });
1199
1248
  this.ingestSource(source);
1200
1249
  this.syncCurrentGroupView();
1201
1250
  const restored = this.restoreFromHash();
1202
1251
  if (restored) {
1203
- if (restored.element) {
1204
- triggerEvent(restored.element, "click");
1205
- } else {
1206
- this.openPhoto(restored, null);
1207
- }
1252
+ this.openPhoto(restored, restored.element);
1208
1253
  }
1209
1254
  this.updateViewportHeight();
1210
1255
  if (window.visualViewport) {
@@ -1249,6 +1294,7 @@ var SmartPhoto = class {
1249
1294
  clearTimeout(id);
1250
1295
  });
1251
1296
  this.timeouts = [];
1297
+ this.itemsByElement.clear();
1252
1298
  this.gestures.detach();
1253
1299
  this.view.destroy();
1254
1300
  }
@@ -1361,7 +1407,7 @@ var SmartPhoto = class {
1361
1407
  this.gotoSlide(this.state.viewer.prev);
1362
1408
  }
1363
1409
  addItem(slideOrElement) {
1364
- const item = slideOrElement instanceof Element ? this.addElementItem(slideOrElement) : this.addSlideItem(slideOrElement);
1410
+ const item = slideOrElement instanceof HTMLElement ? this.addElementItem(slideOrElement) : this.addSlideItem(slideOrElement);
1365
1411
  this.syncCurrentGroupView();
1366
1412
  return item;
1367
1413
  }
@@ -1392,7 +1438,7 @@ var SmartPhoto = class {
1392
1438
  );
1393
1439
  addItemToGroup(this.state, item);
1394
1440
  this.loadAllFired.delete(groupId);
1395
- this.bindThumbnailClick(element, item);
1441
+ this.itemsByElement.set(element, item);
1396
1442
  return item;
1397
1443
  }
1398
1444
  addSlideItem(slide) {
@@ -1403,15 +1449,72 @@ var SmartPhoto = class {
1403
1449
  this.loadAllFired.delete(groupId);
1404
1450
  return item;
1405
1451
  }
1406
- bindThumbnailClick(element, item) {
1407
- element.addEventListener(
1408
- "click",
1409
- (e) => {
1410
- e.preventDefault();
1411
- this.openPhoto(item, element);
1412
- },
1413
- { signal: this.abortController.signal }
1414
- );
1452
+ // クリックされた要素(またはその祖先。<a><img></a> の img がクリックされる
1453
+ // ケースを含む)から、登録済み Item を持つ要素まで祖先チェーンを遡って探す
1454
+ findRegisteredAncestor(target) {
1455
+ let node = target;
1456
+ while (node) {
1457
+ if (this.itemsByElement.has(node)) {
1458
+ return node;
1459
+ }
1460
+ node = node.parentElement;
1461
+ }
1462
+ return null;
1463
+ }
1464
+ // rootSelector(文字列セレクタで構築した場合のみ設定される)を再スキャンし、
1465
+ // 指定グループを現在のDOM状態に合わせて丸ごと再構築する(追加・削除・並び順の
1466
+ // 変化を一度に反映)。既知要素は既存の Item オブジェクトをそのまま再利用する
1467
+ // ため loaded/width/height は保持され、DOM から消えた要素だけ itemsByElement
1468
+ // からも除去する。index/translateX は resetTranslate() で再計算する。
1469
+ // 呼び出し元(openPhoto)がこの結果を使い、変化があった場合のみ view を
1470
+ // 再同期する(§ダイアログを開く瞬間に整合を取る。開いている間の next()/prev()
1471
+ // では呼ばれないため、この間の追加/削除は次に開き直すまで反映されない)
1472
+ resyncGroupFromDom(groupId) {
1473
+ if (!this.rootSelector) {
1474
+ return false;
1475
+ }
1476
+ const domElements = Array.from(
1477
+ document.querySelectorAll(this.rootSelector)
1478
+ ).filter((el) => groupIdFromElement(el) === groupId);
1479
+ const domElementSet = new Set(domElements);
1480
+ const previous = this.state.groups.get(groupId) ?? [];
1481
+ const previousByElement = /* @__PURE__ */ new Map();
1482
+ previous.forEach((it) => {
1483
+ if (it.element) {
1484
+ previousByElement.set(it.element, it);
1485
+ }
1486
+ });
1487
+ let changed = domElements.length !== previous.length;
1488
+ const rebuilt = domElements.map((el, index) => {
1489
+ const existing = previousByElement.get(el);
1490
+ if (existing) {
1491
+ if (existing.index !== index) {
1492
+ changed = true;
1493
+ existing.index = index;
1494
+ }
1495
+ return existing;
1496
+ }
1497
+ changed = true;
1498
+ const item = itemFromElement(
1499
+ el,
1500
+ this.state.options,
1501
+ index,
1502
+ getWindowWidth()
1503
+ );
1504
+ this.itemsByElement.set(el, item);
1505
+ return item;
1506
+ });
1507
+ previous.forEach((it) => {
1508
+ if (it.element && !domElementSet.has(it.element)) {
1509
+ this.itemsByElement.delete(it.element);
1510
+ }
1511
+ });
1512
+ resetTranslate(rebuilt, getWindowWidth());
1513
+ this.state.groups.set(groupId, rebuilt);
1514
+ if (changed) {
1515
+ this.loadAllFired.delete(groupId);
1516
+ }
1517
+ return changed;
1415
1518
  }
1416
1519
  syncCurrentGroupView() {
1417
1520
  const items = currentItems(this.state);
@@ -1665,10 +1768,11 @@ var SmartPhoto = class {
1665
1768
  });
1666
1769
  }
1667
1770
  openPhoto(item, trigger) {
1771
+ const groupChanged = this.rootSelector ? this.resyncGroupFromDom(item.groupId) : false;
1668
1772
  this.lastTriggerElement = trigger;
1669
1773
  this.state.viewer.currentGroup = item.groupId;
1670
1774
  this.state.viewer.currentIndex = item.index;
1671
- if (this.syncedGroupId !== item.groupId) {
1775
+ if (this.syncedGroupId !== item.groupId || groupChanged) {
1672
1776
  this.syncCurrentGroupView();
1673
1777
  }
1674
1778
  this.setHashByCurrentIndex();
@@ -1759,6 +1863,7 @@ var SmartPhoto = class {
1759
1863
  this.state.viewer.photoPosX = 0;
1760
1864
  this.state.viewer.photoPosY = 0;
1761
1865
  this.state.viewer.onMove = true;
1866
+ this.updateViewportHeight();
1762
1867
  this.setPosByCurrentIndex();
1763
1868
  this.setHashByCurrentIndex();
1764
1869
  this.setSizeByScreen();
@@ -1,4 +1,4 @@
1
- import type { Item, ItemId, Slide, SmartPhotoEvent, SmartPhotoSettings, SmartPhotoSource } from "./types";
1
+ import type { ItemId, SlideData, SmartPhotoEvent, SmartPhotoItem, SmartPhotoOptions, SmartPhotoSource } from "./types";
2
2
  interface ShowOptions {
3
3
  group?: string;
4
4
  trigger?: HTMLElement;
@@ -16,7 +16,9 @@ export default class SmartPhoto {
16
16
  private timeouts;
17
17
  private loadAllFired;
18
18
  private syncedGroupId;
19
- constructor(source: SmartPhotoSource, settings?: SmartPhotoSettings);
19
+ private rootSelector;
20
+ private readonly itemsByElement;
21
+ constructor(source: SmartPhotoSource, settings?: SmartPhotoOptions);
20
22
  on(event: SmartPhotoEvent, fn: (this: HTMLDialogElement, ev: Event) => void): void;
21
23
  destroy(): void;
22
24
  [Symbol.dispose](): void;
@@ -24,17 +26,19 @@ export default class SmartPhoto {
24
26
  hidePhoto(dir?: "top" | "bottom"): void;
25
27
  zoomPhoto(): void;
26
28
  zoomOutPhoto(): void;
27
- addNewItem(element: HTMLElement): Item;
29
+ addNewItem(element: HTMLElement): SmartPhotoItem;
28
30
  show(indexOrId?: ItemId, opts?: ShowOptions): void;
29
31
  hide(): void;
30
32
  next(): void;
31
33
  prev(): void;
32
- addItem(slideOrElement: Slide | HTMLElement): Item;
34
+ addItem(slideOrElement: SlideData | HTMLElement): SmartPhotoItem;
33
35
  get currentIndex(): number;
34
36
  private ingestSource;
35
37
  private addElementItem;
36
38
  private addSlideItem;
37
- private bindThumbnailClick;
39
+ private findRegisteredAncestor;
40
+ private resyncGroupFromDom;
41
+ private handleDocumentClick;
38
42
  private syncCurrentGroupView;
39
43
  private setHash;
40
44
  private setHashByCurrentIndex;
@@ -1,10 +1,10 @@
1
- import type { Bound, Item, Slide, SmartPhotoOptions, SmartPhotoSettings, State, ViewerState } from "./types";
2
- export declare const defaults: SmartPhotoOptions;
3
- export declare function createState(settings: SmartPhotoSettings): State;
1
+ import type { Bound, Item, ResolvedSmartPhotoOptions, SlideData, SmartPhotoOptions, State, ViewerState } from "./types";
2
+ export declare const defaults: ResolvedSmartPhotoOptions;
3
+ export declare function createState(settings: SmartPhotoOptions): State;
4
4
  export declare function groupIdFromElement(element: Element): string;
5
- export declare function groupIdFromSlide(slide: Slide): string;
6
- export declare function itemFromElement(element: Element, options: Pick<SmartPhotoOptions, "lazyAttribute">, index: number, winWidth: number): Item;
7
- export declare function itemFromSlide(slide: Slide, index: number, winWidth: number): Item;
5
+ export declare function groupIdFromSlide(slide: SlideData): string;
6
+ export declare function itemFromElement(element: Element, options: Pick<ResolvedSmartPhotoOptions, "lazyAttribute">, index: number, winWidth: number): Item;
7
+ export declare function itemFromSlide(slide: SlideData, index: number, winWidth: number): Item;
8
8
  export declare function addItemToGroup(state: State, item: Item): void;
9
9
  export declare function currentItems(state: State): Item[] | null;
10
10
  export declare function currentItem(state: State): Item | null;
@@ -31,7 +31,7 @@ export interface Messages {
31
31
  closeDialog: string;
32
32
  carouselLabel: string;
33
33
  }
34
- export interface SmartPhotoOptions {
34
+ export interface ResolvedSmartPhotoOptions {
35
35
  classNames: ClassNames;
36
36
  message: Messages;
37
37
  arrows: boolean;
@@ -53,29 +53,31 @@ export interface SmartPhotoOptions {
53
53
  lazyAttribute: string;
54
54
  animationSpeed: number;
55
55
  }
56
- export type SmartPhotoSettings = Partial<Omit<SmartPhotoOptions, "classNames" | "message">> & {
56
+ export type SmartPhotoOptions = Partial<Omit<ResolvedSmartPhotoOptions, "classNames" | "message">> & {
57
57
  classNames?: Partial<ClassNames>;
58
58
  message?: Partial<Messages>;
59
59
  };
60
60
  export type ItemId = string | number;
61
- export interface Item {
61
+ export interface SmartPhotoItem {
62
62
  src: string | null;
63
63
  thumb: string | null;
64
64
  caption: string | null;
65
65
  alt: string | null;
66
66
  groupId: string;
67
- translateX: number;
68
- translateY: number;
69
67
  index: number;
70
68
  width: number;
71
69
  height: number;
70
+ id: ItemId;
71
+ loaded: boolean;
72
+ element: HTMLElement | null;
73
+ }
74
+ export interface Item extends SmartPhotoItem {
75
+ translateX: number;
76
+ translateY: number;
72
77
  scale: number;
73
78
  x: number;
74
79
  y: number;
75
- id: ItemId;
76
- loaded: boolean;
77
80
  processed: boolean;
78
- element: HTMLElement | null;
79
81
  }
80
82
  export interface AppearEffect {
81
83
  width: number;
@@ -111,7 +113,7 @@ export interface ViewerState {
111
113
  showNextArrow: boolean;
112
114
  }
113
115
  export interface State {
114
- options: SmartPhotoOptions;
116
+ options: ResolvedSmartPhotoOptions;
115
117
  viewer: ViewerState;
116
118
  groups: Map<string, Item[]>;
117
119
  }
@@ -121,7 +123,7 @@ export interface Bound {
121
123
  maxX: number;
122
124
  maxY: number;
123
125
  }
124
- export interface Slide {
126
+ export interface SlideData {
125
127
  src: string;
126
128
  thumb?: string;
127
129
  caption?: string;
@@ -131,7 +133,7 @@ export interface Slide {
131
133
  width?: number;
132
134
  height?: number;
133
135
  }
134
- export type SmartPhotoSource = string | NodeListOf<Element> | Element[] | Slide[];
136
+ export type SmartPhotoSource = string | NodeListOf<Element> | Element[] | SlideData[];
135
137
  export type SmartPhotoEvent = "open" | "close" | "change" | "swipestart" | "swipeend" | "zoomin" | "zoomout" | "gesturestart" | "gestureend" | "loadall";
136
138
  export type SwipeEndResult = "prev" | "next" | "close-top" | "close-bottom" | "stay";
137
139
  export type PhotoDragEndResult = "prev" | "next" | "zoom-out" | null;
@@ -1,4 +1,4 @@
1
- import type { AppearEffect, Item, SmartPhotoOptions, State } from "./types";
1
+ import type { AppearEffect, Item, ResolvedSmartPhotoOptions, State } from "./types";
2
2
  interface SlideRefs {
3
3
  li: HTMLLIElement;
4
4
  loaderWrap: HTMLDivElement | null;
@@ -41,7 +41,7 @@ export interface View {
41
41
  }
42
42
  export declare function createView({ id, options }: {
43
43
  id: string;
44
- options: SmartPhotoOptions;
44
+ options: ResolvedSmartPhotoOptions;
45
45
  }, handlers: ViewHandlers, { signal }: {
46
46
  signal: AbortSignal;
47
47
  }): View;
@@ -1,2 +1,3 @@
1
1
  import SmartPhoto from "./core";
2
2
  export default SmartPhoto;
3
+ export type { ClassNames, ItemId, Messages, SlideData, SmartPhotoEvent, SmartPhotoItem, SmartPhotoOptions, SmartPhotoSource, } from "./core/types";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smartphoto",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "smartphoto",
5
5
  "homepage": "https://developer.a-blogcms.jp",
6
6
  "main": "./lib/smartphoto.js",