@michaelyagi/shoji 0.1.0-alpha.8 → 0.1.0-beta.2
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 +3 -3
- package/dist/esm/bodyScrollLock-CC5PJxJp.js +96 -0
- package/dist/esm/bodyScrollLock-CC5PJxJp.js.map +1 -0
- package/dist/esm/core/FocusTrap.d.ts +15 -0
- package/dist/esm/core/Gallery.d.ts +442 -20
- package/dist/esm/core/GestureController.d.ts +55 -6
- package/dist/esm/core/SlideManager.d.ts +37 -9
- package/dist/esm/core/bodyScrollLock.d.ts +2 -0
- package/dist/esm/core/dom.d.ts +14 -0
- package/dist/esm/core/icons.d.ts +1 -1
- package/dist/esm/core/index.js +1234 -212
- package/dist/esm/core/index.js.map +1 -1
- package/dist/esm/core/plugin.d.ts +12 -1
- package/dist/esm/core/scan.d.ts +5 -4
- package/dist/esm/core/types.d.ts +69 -26
- package/dist/esm/core/zoomTransition.d.ts +77 -7
- package/dist/esm/gestures/GestureEngine.d.ts +11 -0
- package/dist/esm/index.css +416 -65
- package/dist/esm/index.js +1 -1
- package/dist/esm/index2.css +36 -0
- package/dist/esm/plugins/activeThumbnail/index.js +28 -17
- package/dist/esm/plugins/activeThumbnail/index.js.map +1 -1
- package/dist/esm/plugins/autoplay/index.d.ts +10 -0
- package/dist/esm/plugins/autoplay/index.js +124 -16
- package/dist/esm/plugins/autoplay/index.js.map +1 -1
- package/dist/esm/plugins/fullscreen/index.js +5 -2
- package/dist/esm/plugins/fullscreen/index.js.map +1 -1
- package/dist/esm/plugins/layout/index.js +22 -4
- package/dist/esm/plugins/layout/index.js.map +1 -1
- package/dist/esm/plugins/rotateFlip/index.js +43 -9
- package/dist/esm/plugins/rotateFlip/index.js.map +1 -1
- package/dist/esm/plugins/video/index.d.ts +5 -5
- package/dist/esm/plugins/video/index.js +123 -2
- package/dist/esm/plugins/video/index.js.map +1 -1
- package/dist/esm/plugins/video/vimeo.d.ts +51 -0
- package/dist/esm/plugins/video/youtube.d.ts +6 -3
- package/dist/esm/plugins/zoom/index.js +186 -34
- package/dist/esm/plugins/zoom/index.js.map +1 -1
- package/dist/esm/plugins/zoom/zoomMath.d.ts +126 -8
- package/dist/esm/transitions/SlideTransition.d.ts +2 -1
- package/dist/esm/transitions/presets.d.ts +1 -1
- package/dist/esm/{zoomTransition-bbKHpVpA.js → zoomTransition-BbnYTxY-.js} +43 -22
- package/dist/esm/zoomTransition-BbnYTxY-.js.map +1 -0
- package/dist/shoji.css +452 -65
- package/dist/shoji.js +1877 -300
- package/dist/shoji.js.map +1 -1
- package/dist/shoji.min.css +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
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Shoji
|
|
2
2
|
|
|
3
3
|
[](https://github.com/MichaelYagi/shoji/actions/workflows/ci.yml)
|
|
4
|
-
[](https://www.npmjs.com/package/@michaelyagi/shoji)
|
|
5
5
|
|
|
6
6
|
A zero-dependency, plugin-first TypeScript lightbox/gallery. Drop in two `<script>`/`<link>` tags, or install from npm — everything beyond the minimal lightbox, including every official plugin, is opt-in.
|
|
7
7
|
|
|
8
|
-
> **Status:
|
|
8
|
+
> **Status: beta.** The API is still settling — expect breaking changes between `0.x` releases.
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
@@ -28,7 +28,7 @@ A zero-dependency, plugin-first TypeScript lightbox/gallery. Drop in two `<scrip
|
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npm install @michaelyagi/shoji@
|
|
31
|
+
npm install @michaelyagi/shoji@beta
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
```js
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
let lockCount = 0;
|
|
2
|
+
let savedHtmlOverflow = "";
|
|
3
|
+
let savedHtmlPaddingRight = "";
|
|
4
|
+
let savedHtmlPaddingLeft = "";
|
|
5
|
+
let savedScrollX = 0;
|
|
6
|
+
let savedScrollY = 0;
|
|
7
|
+
let styleObserver = null;
|
|
8
|
+
let intentionalScrollOccurred = false;
|
|
9
|
+
const LIGHTBOX_SELECTOR = ".shoji-outer";
|
|
10
|
+
function markIntentionalScroll() {
|
|
11
|
+
intentionalScrollOccurred = true;
|
|
12
|
+
}
|
|
13
|
+
function isRtl() {
|
|
14
|
+
return getComputedStyle(document.documentElement).direction === "rtl";
|
|
15
|
+
}
|
|
16
|
+
function hasScrollableAncestor(node) {
|
|
17
|
+
let el = node instanceof Element ? node : (node == null ? void 0 : node.parentElement) ?? null;
|
|
18
|
+
while (el && el !== document.documentElement) {
|
|
19
|
+
const style = getComputedStyle(el);
|
|
20
|
+
if ((style.overflowY === "auto" || style.overflowY === "scroll") && el.scrollHeight > el.clientHeight) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
el = el.parentElement;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
let touchAllowsScrollThrough = false;
|
|
28
|
+
function onTouchStart(event) {
|
|
29
|
+
const target = event.target;
|
|
30
|
+
const insideLightbox = target instanceof Element && target.closest(LIGHTBOX_SELECTOR) !== null;
|
|
31
|
+
touchAllowsScrollThrough = !insideLightbox && hasScrollableAncestor(target);
|
|
32
|
+
}
|
|
33
|
+
function onTouchMove(event) {
|
|
34
|
+
const target = event.target;
|
|
35
|
+
const insideLightbox = target instanceof Element && target.closest(LIGHTBOX_SELECTOR) !== null;
|
|
36
|
+
if (!insideLightbox && !touchAllowsScrollThrough) event.preventDefault();
|
|
37
|
+
}
|
|
38
|
+
function onStyleMutation() {
|
|
39
|
+
if (lockCount === 0) return;
|
|
40
|
+
const html = document.documentElement;
|
|
41
|
+
if (getComputedStyle(html).overflow !== "hidden") {
|
|
42
|
+
html.style.overflow = "hidden";
|
|
43
|
+
styleObserver == null ? void 0 : styleObserver.takeRecords();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function lockBodyScroll() {
|
|
47
|
+
if (lockCount === 0) {
|
|
48
|
+
savedScrollX = window.scrollX;
|
|
49
|
+
savedScrollY = window.scrollY;
|
|
50
|
+
intentionalScrollOccurred = false;
|
|
51
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
52
|
+
const rtl = isRtl();
|
|
53
|
+
savedHtmlPaddingRight = document.documentElement.style.paddingRight;
|
|
54
|
+
savedHtmlPaddingLeft = document.documentElement.style.paddingLeft;
|
|
55
|
+
if (scrollbarWidth > 0) {
|
|
56
|
+
if (rtl) {
|
|
57
|
+
const currentPaddingLeft = parseFloat(getComputedStyle(document.documentElement).paddingLeft) || 0;
|
|
58
|
+
document.documentElement.style.paddingLeft = `${currentPaddingLeft + scrollbarWidth}px`;
|
|
59
|
+
} else {
|
|
60
|
+
const currentPaddingRight = parseFloat(getComputedStyle(document.documentElement).paddingRight) || 0;
|
|
61
|
+
document.documentElement.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
savedHtmlOverflow = document.documentElement.style.overflow;
|
|
65
|
+
document.documentElement.style.overflow = "hidden";
|
|
66
|
+
document.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
67
|
+
document.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
68
|
+
styleObserver = new MutationObserver(onStyleMutation);
|
|
69
|
+
styleObserver.observe(document.documentElement, {
|
|
70
|
+
attributes: true,
|
|
71
|
+
attributeFilter: ["style"]
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
lockCount++;
|
|
75
|
+
}
|
|
76
|
+
function unlockBodyScroll() {
|
|
77
|
+
lockCount = Math.max(0, lockCount - 1);
|
|
78
|
+
if (lockCount === 0) {
|
|
79
|
+
styleObserver == null ? void 0 : styleObserver.disconnect();
|
|
80
|
+
styleObserver = null;
|
|
81
|
+
document.removeEventListener("touchstart", onTouchStart);
|
|
82
|
+
document.removeEventListener("touchmove", onTouchMove);
|
|
83
|
+
document.documentElement.style.overflow = savedHtmlOverflow;
|
|
84
|
+
document.documentElement.style.paddingRight = savedHtmlPaddingRight;
|
|
85
|
+
document.documentElement.style.paddingLeft = savedHtmlPaddingLeft;
|
|
86
|
+
if (!intentionalScrollOccurred) {
|
|
87
|
+
window.scrollTo({ left: savedScrollX, top: savedScrollY, behavior: "instant" });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
lockBodyScroll as l,
|
|
93
|
+
markIntentionalScroll as m,
|
|
94
|
+
unlockBodyScroll as u
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=bodyScrollLock-CC5PJxJp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bodyScrollLock-CC5PJxJp.js","sources":["../../src/core/bodyScrollLock.ts"],"sourcesContent":["/** DESIGN.md §2.6a — reference-counted page scroll lock. */\nlet lockCount = 0;\nlet savedHtmlOverflow = '';\nlet savedHtmlPaddingRight = '';\nlet savedHtmlPaddingLeft = '';\nlet savedScrollX = 0;\nlet savedScrollY = 0;\nlet styleObserver: MutationObserver | null = null;\n// Set by markIntentionalScroll() (ActiveThumbnail's own scrollIntoView while\n// locked, DESIGN.md §2.6a/§4.2) — a real bug/regression: unlockBodyScroll()\n// unconditionally restoring savedScrollX/Y (below) was meant to undo\n// *unrelated* code hijacking scroll during the lock (a router, a stray\n// \"scroll to top\" button), but it just as unconditionally undid Shoji's own\n// legitimate background-page scrolling too, making ActiveThumbnail's\n// scrollIntoView a complete no-op the instant the lightbox that triggers it\n// is also what's holding the lock — every navigation while open, always.\n// Once anything marks an intentional scroll during this lock session, skip\n// the restore entirely rather than try to track a smooth-scroll animation's\n// eventual resting position (no reliable completion signal across browsers)\n// — same \"leave it where legitimate code put it\" reasoning, just without\n// pinpointing the exact pixel.\nlet intentionalScrollOccurred = false;\n\nconst LIGHTBOX_SELECTOR = '.shoji-outer';\n\n/** Call after intentionally scrolling something on the host page while the lock is active (e.g. ActiveThumbnail's own `scrollIntoView`) — tells `unlockBodyScroll()` not to undo it. */\nexport function markIntentionalScroll(): void {\n intentionalScrollOccurred = true;\n}\n\nfunction isRtl(): boolean {\n return getComputedStyle(document.documentElement).direction === 'rtl';\n}\n\n/**\n * Walks up from `node` looking for a real scrollable container — lets\n * `onTouchMove` below allow touch-scrolling through to something like a\n * host-app modal/sidebar opened on top of the lightbox, instead of\n * blocking it just because it's outside `.shoji-outer`. Stops at\n * `document.documentElement` deliberately — that's the exact background\n * scroll this lock exists to block, not a container to exempt from it.\n */\nfunction hasScrollableAncestor(node: Node | null): boolean {\n let el = node instanceof Element ? node : (node?.parentElement ?? null);\n while (el && el !== document.documentElement) {\n const style = getComputedStyle(el);\n if (\n (style.overflowY === 'auto' || style.overflowY === 'scroll') &&\n el.scrollHeight > el.clientHeight\n ) {\n return true;\n }\n el = el.parentElement;\n }\n return false;\n}\n\n// Computed once per gesture, in onTouchStart below, not recomputed on every\n// onTouchMove — a touch's `event.target` stays fixed to the original\n// touchstart target for the whole gesture regardless of where the finger\n// moves (Touch Events spec), so re-walking the ancestor chain and calling\n// getComputedStyle again on every move would just repeat the same, not-free\n// answer for no benefit (CLAUDE.md: no forced synchronous layout in hot\n// paths like gestures/scroll).\nlet touchAllowsScrollThrough = false;\n\nfunction onTouchStart(event: TouchEvent): void {\n const target = event.target;\n const insideLightbox = target instanceof Element && target.closest(LIGHTBOX_SELECTOR) !== null;\n touchAllowsScrollThrough = !insideLightbox && hasScrollableAncestor(target as Node | null);\n}\n\n/**\n * A real gap: `overflow: hidden` on `<html>` doesn't reliably block iOS\n * Safari's own touch-driven rubber-band/bounce scroll in every case — a\n * well-known limitation of this technique across the ecosystem. The common\n * workaround (flipping `body` to `position: fixed` while locked) has its\n * own real tradeoffs: a full reflow, plus manual scroll-position capture/\n * restore that's a frequent source of bugs in other libraries (the exact\n * class of bug this whole lock has already had twice — see the other real\n * bugs in this file's history). Lighter alternative: block the browser's\n * default only for a touchmove that didn't start inside a lightbox,\n * leaving Shoji's own in-dialog gesture handling (which already manages\n * its own `preventDefault`, see `GestureEngine.ts`) completely untouched.\n * Not a passive listener — `preventDefault` is genuinely required here to\n * suppress the browser's native scroll, not just observe it.\n *\n * Also untouched: a touch that started on a genuinely scrollable ancestor\n * outside the lightbox (`touchAllowsScrollThrough`, set in `onTouchStart`\n * above) — a real bug, reported from real usage: this used to block\n * touch-scrolling in *any* host-app UI outside `.shoji-outer`, including\n * something like a Bootstrap modal or sidebar opened on top of the\n * lightbox, not just the page body behind it.\n */\nfunction onTouchMove(event: TouchEvent): void {\n const target = event.target;\n const insideLightbox = target instanceof Element && target.closest(LIGHTBOX_SELECTOR) !== null;\n if (!insideLightbox && !touchAllowsScrollThrough) event.preventDefault();\n}\n\n/**\n * A real gap: this lock is reference-counted against other `Gallery`\n * instances, but has no way to know about unrelated code on the host page\n * — another modal/dropdown library that also sets\n * `document.documentElement.style.overflow`, then clears it back to `''`\n * on its own close, would silently undo this lock mid-lightbox with\n * neither library aware of the other. Watching the one attribute this\n * lock itself owns is the only way to detect and correct that reactively.\n * `takeRecords()` discards the mutation record our own corrective write\n * below just queued — otherwise the observer would fire again for a\n * change it caused itself, forever.\n */\nfunction onStyleMutation(): void {\n if (lockCount === 0) return;\n const html = document.documentElement;\n if (getComputedStyle(html).overflow !== 'hidden') {\n html.style.overflow = 'hidden';\n styleObserver?.takeRecords();\n }\n}\n\nexport function lockBodyScroll(): void {\n if (lockCount === 0) {\n // A real gap: `overflow: hidden` blocks wheel/touch/keyboard-driven\n // scrolling, but not a programmatic `window.scrollTo()`/`scrollTop`\n // write — confirmed directly. If the host's own code scrolls the\n // window while the lightbox is open (a router restoring scroll\n // position, an unrelated \"scroll to top\" button), the page ends up\n // somewhere different than where the viewer left it once the lock\n // releases. Restored unconditionally on unlock below — a scroll\n // *lock* implies the background stays frozen in place for the whole\n // time the lightbox is open, not just protected from direct input.\n savedScrollX = window.scrollX;\n savedScrollY = window.scrollY;\n intentionalScrollOccurred = false;\n\n // A real bug: hiding overflow below reclaims the scrollbar's own\n // gutter, widening <html>'s content box by however many px the\n // scrollbar was — on a host page whose content spans the full viewport\n // width, that reflow is visible as a shift right when the lightbox\n // opens/closes (and reverses on close). Measured *before* overflow is\n // hidden (clientWidth only grows once the gutter is actually reclaimed,\n // so measuring after would always read 0) and only compensated for when\n // a real scrollbar was actually there — a page that never had one gets\n // no padding at all, nothing to compensate for.\n //\n // Two rejected approaches, both real bugs of their own: padding-right\n // on `document.body` compensates the wrong element — a page whose body\n // is narrower than the viewport (a centered, `max-width`-capped layout,\n // this docs site included) never touches the scrollbar's gutter in the\n // first place, so padding body just shrinks its own content box by an\n // *extra* scrollbar-width's worth, a new, self-inflicted reflow.\n // `scrollbar-gutter: stable` compensates the right element (`<html>`,\n // the same one the width is measured on) but paints its own visible,\n // if non-interactive, scrollbar-track styling for as long as the lock\n // is active — trading a brief shift for a permanent scrollbar-shaped\n // strip sitting over the page the whole time the lightbox is open,\n // which reads as more wrong, not less. Padding is genuinely invisible\n // — the same blank space a wider margin would be — and, applied to\n // `<html>` (not `body`), compensates the actual element the scrollbar's\n // gutter belongs to either way.\n const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;\n const rtl = isRtl();\n savedHtmlPaddingRight = document.documentElement.style.paddingRight;\n savedHtmlPaddingLeft = document.documentElement.style.paddingLeft;\n if (scrollbarWidth > 0) {\n // A real gap: a classic scrollbar renders on the *left* on a\n // `direction: rtl` page, not the right — compensating the wrong\n // side would reintroduce the exact reflow this exists to prevent,\n // just mirrored. Checked at lock time, not baked in as a fixed\n // assumption.\n if (rtl) {\n const currentPaddingLeft =\n parseFloat(getComputedStyle(document.documentElement).paddingLeft) || 0;\n document.documentElement.style.paddingLeft = `${currentPaddingLeft + scrollbarWidth}px`;\n } else {\n const currentPaddingRight =\n parseFloat(getComputedStyle(document.documentElement).paddingRight) || 0;\n document.documentElement.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`;\n }\n }\n\n // A real bug: `document.body.style.overflow = 'hidden'` (this function's\n // original, and until now only, scroll-blocking mechanism) was still\n // set here alongside <html>'s own below — redundant, and not harmless:\n // any value of `overflow` other than `visible` makes an element\n // establish a new block-formatting context, which blocks top-margin\n // collapsing between it and its first child. Confirmed directly via\n // real-browser instrumentation: with body's overflow locked, an h1\n // immediately inside it rendered `bodyMarginTop + h1's own default\n // margin-top` below the viewport top (its margin no longer collapsing\n // into body's own); the instant `unlockBodyScroll()` restored body's\n // overflow, collapsing resumed and the h1 snapped back up by its own\n // margin's worth — reading as the page shifting upward right as the\n // lightbox closes, on *any* page where the first child's margin would\n // otherwise collapse with body's (essentially any page without padding/\n // border on body itself — not a corner case). <html>'s own overflow:\n // hidden below is sufficient on its own to block user-driven scrolling\n // (wheel/touch/keyboard) — confirmed directly — since it's the real\n // scrolling element in standards mode; body's lock was never adding\n // independent protection, only this side effect.\n //\n // <html>'s own overflow, not just body's — mobile viewport-widening\n // bug, see DESIGN.md §2.6a. Both axes, not just overflow-x: setting\n // only one non-'visible' axis forces the browser to silently promote\n // the other from 'visible' to 'auto' (mixing hidden+visible isn't\n // allowed per spec), which revealed a real scrollbar that wasn't\n // there before.\n savedHtmlOverflow = document.documentElement.style.overflow;\n document.documentElement.style.overflow = 'hidden';\n\n // passive: true — onTouchStart only ever reads/caches, never calls\n // preventDefault(), unlike onTouchMove below.\n document.addEventListener('touchstart', onTouchStart, { passive: true });\n document.addEventListener('touchmove', onTouchMove, { passive: false });\n\n // Starts observing only after the writes above have already landed, so\n // it never reacts to its own initial setup — only to a later, external\n // change.\n styleObserver = new MutationObserver(onStyleMutation);\n styleObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['style'],\n });\n }\n lockCount++;\n}\n\nexport function unlockBodyScroll(): void {\n lockCount = Math.max(0, lockCount - 1);\n if (lockCount === 0) {\n styleObserver?.disconnect();\n styleObserver = null;\n document.removeEventListener('touchstart', onTouchStart);\n document.removeEventListener('touchmove', onTouchMove);\n\n document.documentElement.style.overflow = savedHtmlOverflow;\n document.documentElement.style.paddingRight = savedHtmlPaddingRight;\n document.documentElement.style.paddingLeft = savedHtmlPaddingLeft;\n\n if (!intentionalScrollOccurred) {\n window.scrollTo({ left: savedScrollX, top: savedScrollY, behavior: 'instant' });\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -5,5 +5,20 @@ export declare class FocusTrap {
|
|
|
5
5
|
private readonly onKeydown;
|
|
6
6
|
private getFocusable;
|
|
7
7
|
activate(container: HTMLElement): void;
|
|
8
|
+
/**
|
|
9
|
+
* DESIGN.md §2.3a — narrows (or widens back) which subtree Tab cycles
|
|
10
|
+
* within, without touching `previouslyFocused`/the listener registration
|
|
11
|
+
* — a caption modal nested inside the already-trapped dialog needs Tab
|
|
12
|
+
* confined to just itself while open, then back to the whole dialog on
|
|
13
|
+
* close, but must NOT trigger `activate()`'s own focus-capture (that's
|
|
14
|
+
* reserved for the real open/close boundary; re-running it mid-session
|
|
15
|
+
* would capture the wrong "previously focused" element and restore focus
|
|
16
|
+
* to it too early). `getFocusable()` reads `this.container` fresh on
|
|
17
|
+
* every keydown, so simply reassigning it is enough — no need for a
|
|
18
|
+
* second, independent `FocusTrap` instance, which would double-handle
|
|
19
|
+
* every Tab keypress (two capture-phase `document` listeners, each
|
|
20
|
+
* computing its own overlapping focusable list).
|
|
21
|
+
*/
|
|
22
|
+
retarget(container: HTMLElement): void;
|
|
8
23
|
deactivate(): void;
|
|
9
24
|
}
|