@michaelyagi/shoji 0.1.0-alpha.10 → 0.1.0-alpha.11
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/dist/esm/core/Gallery.d.ts +15 -1
- package/dist/esm/core/SlideManager.d.ts +27 -1
- package/dist/esm/core/index.js +81 -11
- package/dist/esm/core/index.js.map +1 -1
- package/dist/esm/core/types.d.ts +9 -0
- package/dist/esm/core/zoomTransition.d.ts +19 -3
- package/dist/esm/index.js +1 -1
- package/dist/esm/plugins/autoplay/index.js +31 -1
- package/dist/esm/plugins/autoplay/index.js.map +1 -1
- package/dist/esm/plugins/rotateFlip/index.js +1 -1
- package/dist/esm/plugins/zoom/index.js +1 -1
- package/dist/esm/{zoomTransition-bbKHpVpA.js → zoomTransition-2_HG7tyJ.js} +18 -12
- package/dist/esm/zoomTransition-2_HG7tyJ.js.map +1 -0
- package/dist/shoji.js +128 -23
- package/dist/shoji.js.map +1 -1
- package/dist/shoji.min.js +1 -1
- package/dist/shoji.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/esm/zoomTransition-bbKHpVpA.js.map +0 -1
|
@@ -50,6 +50,9 @@ export declare class Gallery {
|
|
|
50
50
|
private readonly onOuterClick;
|
|
51
51
|
/** DESIGN.md §2.8 — any interaction re-shows controls, restarts the idle clock. `autoHideDelay: 0` = "never show controls" — a no-op here. */
|
|
52
52
|
private readonly onActivity;
|
|
53
|
+
/** x */
|
|
54
|
+
private controlsHiddenAtGestureStart;
|
|
55
|
+
private readonly captureGestureStartState;
|
|
53
56
|
private readonly onKeydown;
|
|
54
57
|
constructor(target: HTMLElement | string, options?: GalleryOptions);
|
|
55
58
|
/** Everything the constructor does after `this.element` is resolved — shared with `reinit()` (§2.7). */
|
|
@@ -102,7 +105,8 @@ export declare class Gallery {
|
|
|
102
105
|
*/
|
|
103
106
|
getOriginElement(index: number): HTMLElement | null;
|
|
104
107
|
private scheduleAutoHide;
|
|
105
|
-
|
|
108
|
+
/** Forces the same fade §2.8's idle timer would eventually trigger — public so a plugin can hide controls on its own trigger. Same `isControlActive()` guard as the timer. */
|
|
109
|
+
hideControls(): void;
|
|
106
110
|
private showControls;
|
|
107
111
|
/**
|
|
108
112
|
* DESIGN.md §2.1 — `caption` is `string | HTMLElement | DangerousHtmlCaption`:
|
|
@@ -175,6 +179,16 @@ export declare class Gallery {
|
|
|
175
179
|
close(): void;
|
|
176
180
|
/** `item.width`/`height`, else origin's `naturalWidth`/`naturalHeight` (accurate when `item.thumb` is unset). Feeds `computeTransform`'s letterbox-aware sizing. */
|
|
177
181
|
private resolveAspectRatio;
|
|
182
|
+
/**
|
|
183
|
+
* `item.width`/`height` only — deliberately never a thumbnail's own
|
|
184
|
+
* `naturalWidth`/`naturalHeight` the way `resolveAspectRatio` above will:
|
|
185
|
+
* that's a fine stand-in for *shape*, but using it as the real photo's
|
|
186
|
+
* true pixel size would under-cap a genuinely large photo down to
|
|
187
|
+
* thumbnail resolution. `undefined` here just means "genuinely unknown,"
|
|
188
|
+
* not "assume small" — `zoomTransition.ts`'s `containedBox` already
|
|
189
|
+
* treats it that way (no cap applied at all).
|
|
190
|
+
*/
|
|
191
|
+
private resolveNaturalSize;
|
|
178
192
|
/**
|
|
179
193
|
* Idempotent on purpose: a pending zoom-out's `transitionend`/fallback
|
|
180
194
|
* timeout can still fire after `destroy()` has already force-finished the
|
|
@@ -66,7 +66,33 @@ export declare class SlideManager {
|
|
|
66
66
|
private moveIn;
|
|
67
67
|
/** Starts decoding `item` for `index`, only if nothing already is (see `pending`). Resolves by looking up whichever slot currently wants this index, not the one active when the decode started. */
|
|
68
68
|
private ensureImageDecoding;
|
|
69
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* DESIGN.md §2.3 — swaps the spinner for the placeholder once *it*
|
|
71
|
+
* decodes, not immediately: `item.thumb` is often just `item.src` again,
|
|
72
|
+
* so an undecoded placeholder can leave as long a blank gap as the
|
|
73
|
+
* spinner it replaces.
|
|
74
|
+
*
|
|
75
|
+
* A real bug: `.shoji-slide-open-placeholder`'s CSS unconditionally
|
|
76
|
+
* force-fills the frame (deliberate default: a real photo is usually
|
|
77
|
+
* bigger than the dialog). Wrong for a genuinely small photo with known
|
|
78
|
+
* `item.width`/`item.height` — same "grows too big, snaps down once real"
|
|
79
|
+
* symptom the zoom-in transition itself had (§2.3b), but that fix only
|
|
80
|
+
* governs the animated *transform*, not this placeholder's own CSS box
|
|
81
|
+
* once it settles. When `naturalSize` is known, sizes the placeholder
|
|
82
|
+
* explicitly instead, reusing `containedBox` (`zoomTransition.ts`) —
|
|
83
|
+
* unknown-size items keep the original force-fill guess.
|
|
84
|
+
*
|
|
85
|
+
* A second real bug in that same fix: measured `slot.media`'s own rect as
|
|
86
|
+
* "the container" — but `slot.media` is the exact element the zoom-in
|
|
87
|
+
* transition (§2.3b) animates a `scale()` transform on. `getBoundingClientRect()`
|
|
88
|
+
* reflects whatever transform is *currently* applied, and the placeholder's
|
|
89
|
+
* own decode typically resolves before that ~300ms animation settles — so
|
|
90
|
+
* this measured a still-small, mid-animation rect (often the origin
|
|
91
|
+
* thumbnail's own tiny size) instead of the real dialog size. `slot.root`
|
|
92
|
+
* (`.shoji-slide`) fixes it: same box, only ever `translateX`'d for
|
|
93
|
+
* pool-offset positioning, never scaled — a stable read regardless of
|
|
94
|
+
* what its child is mid-animation through.
|
|
95
|
+
*/
|
|
70
96
|
private revealOpenPlaceholder;
|
|
71
97
|
/** Native `<video controls>` — real playback, not just a poster. Not autoplayed/muted: a deliberate click-to-play, not a background loop. */
|
|
72
98
|
private renderVideo;
|
package/dist/esm/core/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
import { w as waitForTransitionEnd, z as zoomIn, a as zoomOut } from "../zoomTransition-
|
|
4
|
+
import { w as waitForTransitionEnd, c as containedBox, z as zoomIn, a as zoomOut } from "../zoomTransition-2_HG7tyJ.js";
|
|
5
5
|
let lockCount = 0;
|
|
6
6
|
let savedOverflow = "";
|
|
7
7
|
let savedHtmlOverflow = "";
|
|
@@ -785,7 +785,8 @@ class SlideManager {
|
|
|
785
785
|
releaseVideo(slot.media);
|
|
786
786
|
slot.media.replaceChildren(createSpinner());
|
|
787
787
|
if (index === centerIndex && openPlaceholderSrc) {
|
|
788
|
-
|
|
788
|
+
const naturalSize = item.width && item.height ? { width: item.width, height: item.height } : void 0;
|
|
789
|
+
this.revealOpenPlaceholder(openPlaceholderSrc, slot, index, naturalSize);
|
|
789
790
|
}
|
|
790
791
|
if (((_a = item.video) == null ? void 0 : _a.provider) === "html5") {
|
|
791
792
|
this.renderVideo(item, slot, index, onLoad);
|
|
@@ -854,14 +855,50 @@ class SlideManager {
|
|
|
854
855
|
img.addEventListener("error", reveal, { once: true });
|
|
855
856
|
}
|
|
856
857
|
}
|
|
857
|
-
/**
|
|
858
|
-
|
|
858
|
+
/**
|
|
859
|
+
* DESIGN.md §2.3 — swaps the spinner for the placeholder once *it*
|
|
860
|
+
* decodes, not immediately: `item.thumb` is often just `item.src` again,
|
|
861
|
+
* so an undecoded placeholder can leave as long a blank gap as the
|
|
862
|
+
* spinner it replaces.
|
|
863
|
+
*
|
|
864
|
+
* A real bug: `.shoji-slide-open-placeholder`'s CSS unconditionally
|
|
865
|
+
* force-fills the frame (deliberate default: a real photo is usually
|
|
866
|
+
* bigger than the dialog). Wrong for a genuinely small photo with known
|
|
867
|
+
* `item.width`/`item.height` — same "grows too big, snaps down once real"
|
|
868
|
+
* symptom the zoom-in transition itself had (§2.3b), but that fix only
|
|
869
|
+
* governs the animated *transform*, not this placeholder's own CSS box
|
|
870
|
+
* once it settles. When `naturalSize` is known, sizes the placeholder
|
|
871
|
+
* explicitly instead, reusing `containedBox` (`zoomTransition.ts`) —
|
|
872
|
+
* unknown-size items keep the original force-fill guess.
|
|
873
|
+
*
|
|
874
|
+
* A second real bug in that same fix: measured `slot.media`'s own rect as
|
|
875
|
+
* "the container" — but `slot.media` is the exact element the zoom-in
|
|
876
|
+
* transition (§2.3b) animates a `scale()` transform on. `getBoundingClientRect()`
|
|
877
|
+
* reflects whatever transform is *currently* applied, and the placeholder's
|
|
878
|
+
* own decode typically resolves before that ~300ms animation settles — so
|
|
879
|
+
* this measured a still-small, mid-animation rect (often the origin
|
|
880
|
+
* thumbnail's own tiny size) instead of the real dialog size. `slot.root`
|
|
881
|
+
* (`.shoji-slide`) fixes it: same box, only ever `translateX`'d for
|
|
882
|
+
* pool-offset positioning, never scaled — a stable read regardless of
|
|
883
|
+
* what its child is mid-animation through.
|
|
884
|
+
*/
|
|
885
|
+
revealOpenPlaceholder(src, slot, index, naturalSize) {
|
|
859
886
|
const img = createOpenPlaceholder(src);
|
|
860
887
|
const reveal = () => {
|
|
861
888
|
var _a;
|
|
862
889
|
if (slot.assignedIndex !== index || slot.ready) return;
|
|
863
890
|
(_a = slot.media.querySelector(".shoji-slide-spinner")) == null ? void 0 : _a.remove();
|
|
864
891
|
slot.media.appendChild(img);
|
|
892
|
+
if (naturalSize) {
|
|
893
|
+
const containerRect = slot.root.getBoundingClientRect();
|
|
894
|
+
const box = containedBox(
|
|
895
|
+
{ left: 0, top: 0, width: containerRect.width, height: containerRect.height },
|
|
896
|
+
naturalSize.width / naturalSize.height,
|
|
897
|
+
naturalSize
|
|
898
|
+
);
|
|
899
|
+
img.style.width = `${box.width}px`;
|
|
900
|
+
img.style.height = `${box.height}px`;
|
|
901
|
+
}
|
|
865
902
|
};
|
|
866
903
|
if (typeof img.decode === "function") {
|
|
867
904
|
img.decode().then(reveal, reveal);
|
|
@@ -1375,6 +1412,11 @@ class Gallery {
|
|
|
1375
1412
|
this.showControls();
|
|
1376
1413
|
this.scheduleAutoHide();
|
|
1377
1414
|
});
|
|
1415
|
+
/** x */
|
|
1416
|
+
__publicField(this, "controlsHiddenAtGestureStart", false);
|
|
1417
|
+
__publicField(this, "captureGestureStartState", () => {
|
|
1418
|
+
this.controlsHiddenAtGestureStart = this.autoHidden;
|
|
1419
|
+
});
|
|
1378
1420
|
__publicField(this, "onKeydown", (event) => {
|
|
1379
1421
|
if (document.activeElement instanceof HTMLVideoElement && event.key !== "Escape") return;
|
|
1380
1422
|
this.onActivity();
|
|
@@ -1506,6 +1548,7 @@ class Gallery {
|
|
|
1506
1548
|
});
|
|
1507
1549
|
dom.outer.addEventListener("click", this.onOuterClick);
|
|
1508
1550
|
dom.outer.addEventListener("pointermove", this.onActivity, { passive: true });
|
|
1551
|
+
dom.outer.addEventListener("pointerdown", this.captureGestureStartState, { passive: true });
|
|
1509
1552
|
dom.outer.addEventListener("pointerdown", this.onActivity, { passive: true });
|
|
1510
1553
|
dom.outer.addEventListener("touchstart", this.onActivity, { passive: true });
|
|
1511
1554
|
dom.outer.addEventListener("wheel", this.onActivity, { passive: true });
|
|
@@ -1545,7 +1588,11 @@ class Gallery {
|
|
|
1545
1588
|
}
|
|
1546
1589
|
},
|
|
1547
1590
|
{
|
|
1548
|
-
onTap: (x, y) => this.bus.emit("tap", {
|
|
1591
|
+
onTap: (x, y) => this.bus.emit("tap", {
|
|
1592
|
+
x,
|
|
1593
|
+
y,
|
|
1594
|
+
controlsWereHidden: this.controlsHiddenAtGestureStart
|
|
1595
|
+
}),
|
|
1549
1596
|
onDoubleTap: (x, y) => this.bus.emit("doubleTap", { x, y }),
|
|
1550
1597
|
onPinchStart: (centerX, centerY) => this.bus.emit("pinchStart", { centerX, centerY }),
|
|
1551
1598
|
onPinchMove: (scale, centerX, centerY) => this.bus.emit("pinchMove", { scale, centerX, centerY }),
|
|
@@ -1720,6 +1767,7 @@ class Gallery {
|
|
|
1720
1767
|
}
|
|
1721
1768
|
this.autoHideTimer = setTimeout(() => this.hideControls(), this.autoHideDelay);
|
|
1722
1769
|
}
|
|
1770
|
+
/** Forces the same fade §2.8's idle timer would eventually trigger — public so a plugin can hide controls on its own trigger. Same `isControlActive()` guard as the timer. */
|
|
1723
1771
|
hideControls() {
|
|
1724
1772
|
if (!this.dom || this.autoHidden || this.isControlActive()) return;
|
|
1725
1773
|
this.autoHidden = true;
|
|
@@ -1832,15 +1880,23 @@ class Gallery {
|
|
|
1832
1880
|
this.captionVisibleOnVideo = !!this.options.showVideoCaption;
|
|
1833
1881
|
lockBodyScroll();
|
|
1834
1882
|
const origin = this.getOriginElement(index);
|
|
1835
|
-
this.
|
|
1883
|
+
const naturalSize = this.resolveNaturalSize(index);
|
|
1884
|
+
this.renderCurrentSlide(
|
|
1885
|
+
naturalSize ? this.resolveOpenPlaceholderSrc(this.itemList[index], origin) : void 0
|
|
1886
|
+
);
|
|
1836
1887
|
this.dom.outer.classList.add("shoji-open");
|
|
1837
1888
|
document.addEventListener("keydown", this.onKeydown);
|
|
1838
1889
|
this.focusTrap.activate(this.dom.dialog);
|
|
1839
1890
|
this.scheduleAutoHide();
|
|
1840
1891
|
this.applyMobileControlsSetting();
|
|
1841
1892
|
const media = (_a = this.slides) == null ? void 0 : _a.getActiveMedia();
|
|
1842
|
-
if (media && origin) {
|
|
1843
|
-
zoomIn({
|
|
1893
|
+
if (media && origin && naturalSize) {
|
|
1894
|
+
zoomIn({
|
|
1895
|
+
origin,
|
|
1896
|
+
target: media,
|
|
1897
|
+
aspectRatio: this.resolveAspectRatio(index, origin),
|
|
1898
|
+
naturalSize
|
|
1899
|
+
});
|
|
1844
1900
|
}
|
|
1845
1901
|
this.bus.emit("open", { index });
|
|
1846
1902
|
this.bus.emit("afterOpen", { index });
|
|
@@ -1940,16 +1996,17 @@ class Gallery {
|
|
|
1940
1996
|
}
|
|
1941
1997
|
}
|
|
1942
1998
|
close() {
|
|
1943
|
-
var _a;
|
|
1999
|
+
var _a, _b;
|
|
1944
2000
|
if (this.destroyed || !this.opened || this.isClosing) return;
|
|
1945
2001
|
this.bus.emit("beforeClose", {});
|
|
1946
2002
|
this.isClosing = true;
|
|
1947
2003
|
const media = (_a = this.slides) == null ? void 0 : _a.getActiveMedia();
|
|
1948
2004
|
pauseMedia(media ?? null);
|
|
1949
2005
|
const origin = this.getOriginElement(this.activeIndex);
|
|
1950
|
-
|
|
2006
|
+
const naturalSize = this.resolveNaturalSize(this.activeIndex);
|
|
2007
|
+
if (media && origin && (((_b = this.slides) == null ? void 0 : _b.isActiveReady()) || naturalSize)) {
|
|
1951
2008
|
const aspectRatio = this.resolveAspectRatio(this.activeIndex, origin);
|
|
1952
|
-
zoomOut({ origin, target: media, aspectRatio }, () => this.finishClose());
|
|
2009
|
+
zoomOut({ origin, target: media, aspectRatio, naturalSize }, () => this.finishClose());
|
|
1953
2010
|
} else {
|
|
1954
2011
|
this.finishClose();
|
|
1955
2012
|
}
|
|
@@ -1964,6 +2021,19 @@ class Gallery {
|
|
|
1964
2021
|
}
|
|
1965
2022
|
return void 0;
|
|
1966
2023
|
}
|
|
2024
|
+
/**
|
|
2025
|
+
* `item.width`/`height` only — deliberately never a thumbnail's own
|
|
2026
|
+
* `naturalWidth`/`naturalHeight` the way `resolveAspectRatio` above will:
|
|
2027
|
+
* that's a fine stand-in for *shape*, but using it as the real photo's
|
|
2028
|
+
* true pixel size would under-cap a genuinely large photo down to
|
|
2029
|
+
* thumbnail resolution. `undefined` here just means "genuinely unknown,"
|
|
2030
|
+
* not "assume small" — `zoomTransition.ts`'s `containedBox` already
|
|
2031
|
+
* treats it that way (no cap applied at all).
|
|
2032
|
+
*/
|
|
2033
|
+
resolveNaturalSize(index) {
|
|
2034
|
+
const item = this.itemList[index];
|
|
2035
|
+
return (item == null ? void 0 : item.width) && item.height ? { width: item.width, height: item.height } : void 0;
|
|
2036
|
+
}
|
|
1967
2037
|
/**
|
|
1968
2038
|
* Idempotent on purpose: a pending zoom-out's `transitionend`/fallback
|
|
1969
2039
|
* timeout can still fire after `destroy()` has already force-finished the
|