@michaelyagi/shoji 0.1.0-beta.5 → 0.1.0-beta.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
@@ -3,9 +3,9 @@
3
3
  [![CI](https://github.com/MichaelYagi/shoji/actions/workflows/ci.yml/badge.svg)](https://github.com/MichaelYagi/shoji/actions/workflows/ci.yml)
4
4
  [![npm (beta)](https://img.shields.io/npm/v/%40michaelyagi%2Fshoji/beta.svg)](https://www.npmjs.com/package/@michaelyagi/shoji)
5
5
 
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.
6
+ > **AI-authored.** 100% of the code was written by Claude (Anthropic); I shaped the architecture, scope, and every decision, and did all the testing.
7
7
 
8
- > **Status: beta.** The API is still settlingexpect breaking changes between `0.x` releases.
8
+ 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.
9
9
 
10
10
  ## Features
11
11
 
@@ -14,7 +14,7 @@ A zero-dependency, plugin-first TypeScript lightbox/gallery. Drop in two `<scrip
14
14
  - **Plugin system** — zoom (pinch/pan/wheel), fullscreen, rotate/flip, autoplay/slideshow, active-thumbnail sync, YouTube video embeds, and a grid/masonry/justified layout engine, each independently opt-in.
15
15
  - **Accessibility is not a plugin** — keyboard nav, focus trapping, ARIA roles, and screen-reader announcements live in core.
16
16
  - **Themeable via CSS custom properties** — no hardcoded colors/sizes in JS.
17
- - **Single-file distribution** — `dist/shoji.js` + `dist/shoji.css` (core + every official plugin, self-registering) for zero-setup `<script>` tag use, plus tree-shakable per-plugin ESM entries for bundler users.
17
+ - **Three distributions** — single-file (`dist/shoji.js` + `dist/shoji.css`, core + every plugin, self-registering) for zero-setup `<script>` tag use; core + individual plugin files (`dist/core/shoji-core.(min.)js` + `dist/plugins/{name}.(min.)js`) for `<script>`-tag consumers who want to pick exactly which plugins ship; and tree-shakable per-plugin ESM entries for bundler users.
18
18
  - Size-budgeted and CI-enforced: core ≤ 21 kB, each plugin ≤ 8 kB, full bundle ≤ 90 kB (all min+gzip).
19
19
 
20
20
  ## Install
@@ -534,8 +534,16 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
534
534
  const translateY = to.top + to.height / 2 - (from.top + from.height / 2);
535
535
  return `translate3d(${translateX}px, ${translateY}px, 0) scale3d(${scale}, ${scale}, 1)`;
536
536
  }
537
+ function effectiveOriginBox(origin) {
538
+ const img = origin.querySelector("img");
539
+ if (img) {
540
+ const rect = img.getBoundingClientRect();
541
+ if (rect.width > 0 && rect.height > 0) return rect;
542
+ }
543
+ return origin.getBoundingClientRect();
544
+ }
537
545
  function computeTransform(origin, target, aspectRatio, naturalSize) {
538
- const originRect = origin.getBoundingClientRect();
546
+ const originRect = effectiveOriginBox(origin);
539
547
  const targetRect = effectiveTargetBox(target, aspectRatio, naturalSize);
540
548
  if (targetRect.width === 0 || targetRect.height === 0 || originRect.width === 0 || originRect.height === 0) {
541
549
  return null;
@@ -1894,6 +1902,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1894
1902
  __publicField(this, "captureGestureStartState", () => {
1895
1903
  this.controlsHiddenAtGestureStart = this.autoHidden;
1896
1904
  });
1905
+ /**
1906
+ * A real mobile bug (DESIGN.md §2.6a): after scrolling far enough to
1907
+ * collapse a mobile browser's own URL bar, `window.innerHeight` grows
1908
+ * but `.shoji-outer`'s painted size didn't follow, off-centering the
1909
+ * photo. `visualViewport.height` reflects the true visible area;
1910
+ * `window`'s `resize` is the fallback where it's unsupported.
1911
+ */
1912
+ __publicField(this, "syncViewportHeight", () => {
1913
+ var _a;
1914
+ if (!this.dom) return;
1915
+ const height = ((_a = window.visualViewport) == null ? void 0 : _a.height) ?? window.innerHeight;
1916
+ this.dom.outer.style.height = `${height}px`;
1917
+ });
1897
1918
  __publicField(this, "onKeydown", (event) => {
1898
1919
  if (document.activeElement instanceof HTMLVideoElement && event.key !== "Escape") return;
1899
1920
  this.onActivity();
@@ -2931,6 +2952,10 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
2931
2952
  naturalSize && !((_a = this.itemList[index]) == null ? void 0 : _a.video) ? this.resolveOpenPlaceholderSrc(this.itemList[index], origin) : void 0
2932
2953
  );
2933
2954
  this.dom.outer.classList.add("shoji-open");
2955
+ this.syncViewportHeight();
2956
+ if (window.visualViewport)
2957
+ window.visualViewport.addEventListener("resize", this.syncViewportHeight);
2958
+ else window.addEventListener("resize", this.syncViewportHeight);
2934
2959
  document.addEventListener("keydown", this.onKeydown);
2935
2960
  this.focusTrap.activate(this.dom.dialog);
2936
2961
  this.scheduleAutoHide();
@@ -3155,8 +3180,12 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
3155
3180
  this.opened = false;
3156
3181
  unlockBodyScroll();
3157
3182
  document.removeEventListener("keydown", this.onKeydown);
3183
+ if (window.visualViewport)
3184
+ window.visualViewport.removeEventListener("resize", this.syncViewportHeight);
3185
+ else window.removeEventListener("resize", this.syncViewportHeight);
3158
3186
  this.focusTrap.deactivate();
3159
3187
  (_a = this.dom) == null ? void 0 : _a.outer.classList.remove("shoji-open");
3188
+ if (this.dom) this.dom.outer.style.height = "";
3160
3189
  if (this.dom) this.dom.backdrop.style.opacity = "";
3161
3190
  if (this.autoHideTimer !== null) {
3162
3191
  clearTimeout(this.autoHideTimer);