leafthrough 0.1.0 → 0.1.1

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
@@ -166,7 +166,7 @@ The magazine fills the element you give it and keeps its proportions, centred.
166
166
  | `motion` | `MotionOptions` | | See below. |
167
167
  | `interaction` | `InteractionOptions` | | See below. |
168
168
  | `maxPixelRatio` | `number` | `2` | The canvas is also capped at about 15 megapixels. |
169
- | `preload` | `number` | `6` | Pages either side of the visible ones to load ahead. Pages twice as far away unload. `Infinity` keeps all. |
169
+ | `preload` | `number` | `6` | Pages either side of the visible ones (mid-turn and rolled-up ones included) to load ahead. Pages twice as far away unload. `Infinity` keeps all. |
170
170
  | `plain` | `boolean` | `false` | Flat pages without 3D. Used by itself when the browser can't run WebGL. |
171
171
  | `label` | `string` | `'Magazine'` | Accessible name. |
172
172
  | `formatPageLabel` | `(detail) => string` | `"Pages 14–15 of 28"` | What screen readers hear when pages change. |
@@ -274,6 +274,8 @@ pages: [
274
274
  the reader as they turn or jump. Pages that haven't arrived show blank paper.
275
275
  - **Memory.** Pages within `preload` of the visible ones load; pages more than twice that
276
276
  far away unload, freeing their GPU memory, and load again when the reader comes back.
277
+ Visible means anything on screen: pages still turning in a quick riffle, and in `single`
278
+ the read pages rolled up behind the magazine, until they settle under the pile.
277
279
  A texture you pass in yourself stays loaded, since it's yours.
278
280
  - **Failures.** A page that fails stays blank paper and is reported through `onError`.
279
281
  `retry()` loads failed pages again, and so does the browser coming back online.
@@ -98,6 +98,8 @@ export declare class LeafThrough extends EventTarget {
98
98
  private pagesKey;
99
99
  private preloadRadius;
100
100
  private loadPages;
101
+ private inView;
102
+ private followReader;
101
103
  private resetReady;
102
104
  private pageLoaded;
103
105
  private fail;
@@ -580,6 +580,9 @@ var PlainEngine = class {
580
580
  get turning() {
581
581
  return false;
582
582
  }
583
+ pagesInView() {
584
+ return this.spread ? { from: Math.max(2 * this.current - 1, 0), to: 2 * this.current } : { from: this.current, to: this.current };
585
+ }
583
586
  // closed, only the cover side can be clicked
584
587
  get activeSide() {
585
588
  if (!this.spread) return "both";
@@ -893,6 +896,8 @@ var SingleEngine = class {
893
896
  fallDuration;
894
897
  abort = new AbortController();
895
898
  current;
899
+ turned = 0;
900
+ // sum of every sheet's progress, as of the last update
896
901
  flights = [];
897
902
  drag = null;
898
903
  // the pointer that started the gesture; other pointers are ignored until it lifts
@@ -964,6 +969,22 @@ var SingleEngine = class {
964
969
  return this.flights.length > 0 || this.drag !== null;
965
970
  }
966
971
  activeSide = "both";
972
+ pagesInView() {
973
+ const settled = Math.floor(this.paced(this.turned) - 1 - this.fallDelay - this.fallDuration);
974
+ let from = clamp(settled + 1, 0, Math.max(this.current - 1, 0));
975
+ let to = Math.max(Math.min(this.current, this.count - 1), this.pileTop()?.index ?? 0);
976
+ const turning = this.flights.map((flight) => flight.sheet);
977
+ if (this.drag) turning.push(this.drag.sheet);
978
+ for (const sheet of turning) {
979
+ from = Math.min(from, sheet.index);
980
+ to = Math.max(to, sheet.index);
981
+ }
982
+ return { from, to: Math.max(from, to) };
983
+ }
984
+ // the page on top of the pile, the one a turning page reveals
985
+ pileTop() {
986
+ return this.sheets.find((sheet) => sheet.index >= this.current && !this.busy(sheet));
987
+ }
967
988
  setPageTexture(page, texture) {
968
989
  const sheet = this.sheets[page];
969
990
  if (!sheet) return;
@@ -1313,16 +1334,18 @@ var SingleEngine = class {
1313
1334
  if (!moving && !this.dirty) return false;
1314
1335
  let turned = 0;
1315
1336
  for (const sheet of this.sheets) turned += sheet.progress;
1337
+ this.turned = turned;
1316
1338
  const edge = this.pileEdge(turned);
1317
- const top = this.sheets.find((sheet) => sheet.index >= this.current && !this.busy(sheet));
1339
+ const top = this.pileTop();
1318
1340
  for (const sheet of this.sheets) {
1319
1341
  this.applyUniforms(sheet, turned, edge);
1320
1342
  sheet.uniforms.uTrim.value.set(sheet === top || this.busy(sheet) ? 0 : 1, edge.bottom, edge.top);
1321
1343
  }
1322
1344
  const closing = this.closing(turned) > 0;
1345
+ const pileFrom = Math.max(this.current, top?.index ?? this.current);
1323
1346
  for (const sheet of this.sheets) {
1324
1347
  const i = sheet.index;
1325
- sheet.mesh.visible = closing || i <= this.current + PILE.renderDepth || this.busy(sheet);
1348
+ sheet.mesh.visible = closing || i <= pileFrom + PILE.renderDepth || this.busy(sheet);
1326
1349
  layerSheet(sheet.mesh, i < this.current ? this.current - 1 - i : i - this.current, this.count, this.busy(sheet));
1327
1350
  }
1328
1351
  const read = smooth(this.count > 1 ? this.paced(turned) / (this.count - 1) : 0) * (1 - this.shutting(turned));
@@ -1671,6 +1694,16 @@ var SpreadEngine = class {
1671
1694
  get turning() {
1672
1695
  return this.flights.length > 0 || this.drag !== null;
1673
1696
  }
1697
+ pagesInView() {
1698
+ let from = Math.max(2 * this.current - 1, 0);
1699
+ let to = 2 * this.current;
1700
+ for (const sheet of this.sheets) {
1701
+ if (sheet.progress <= 0 || sheet.progress >= 1) continue;
1702
+ from = Math.min(from, 2 * sheet.index);
1703
+ to = Math.max(to, 2 * sheet.index + 1);
1704
+ }
1705
+ return { from, to };
1706
+ }
1674
1707
  // closed, only the cover side can be grabbed
1675
1708
  get activeSide() {
1676
1709
  const busy = this.flights.length > 0 || this.drag !== null || this.hold !== null;
@@ -2099,9 +2132,10 @@ var PageLoader = class {
2099
2132
  failed = /* @__PURE__ */ new Set();
2100
2133
  // `first` pages load before the rest; the rest go nearest first
2101
2134
  first = [];
2102
- // the visible pages
2135
+ // the pages in view, and the one being read, which loads first among them
2103
2136
  from = 0;
2104
2137
  to = 0;
2138
+ around = 0;
2105
2139
  // whether the magazine loops, putting the cover right after the last page
2106
2140
  wrap = false;
2107
2141
  waiting = /* @__PURE__ */ new Set();
@@ -2122,11 +2156,15 @@ var PageLoader = class {
2122
2156
  if (!this.waiting.size) this.settleFirst();
2123
2157
  return ready;
2124
2158
  }
2125
- /** Follows the reader: loads around the visible pages and unloads pages left far behind. */
2126
- focus(from, to = from) {
2159
+ /**
2160
+ * Follows the reader: loads around the pages in view (`from`–`to`), the page being read
2161
+ * (`around`) first, and unloads pages left far behind.
2162
+ */
2163
+ focus(from, to = from, around = from) {
2127
2164
  if (this.disposed) return;
2128
2165
  this.from = from;
2129
2166
  this.to = to;
2167
+ this.around = around;
2130
2168
  const keep = 2 * this.radius;
2131
2169
  for (const [i, request] of this.inFlight) {
2132
2170
  if (this.distance(i) <= keep) continue;
@@ -2145,11 +2183,11 @@ var PageLoader = class {
2145
2183
  setWrap(wrap) {
2146
2184
  if (wrap === this.wrap) return;
2147
2185
  this.wrap = wrap;
2148
- this.focus(this.from, this.to);
2186
+ this.focus(this.from, this.to, this.around);
2149
2187
  }
2150
2188
  setRadius(radius) {
2151
2189
  this.radius = radius;
2152
- this.focus(this.from, this.to);
2190
+ this.focus(this.from, this.to, this.around);
2153
2191
  }
2154
2192
  /** Loads the pages that failed again. */
2155
2193
  retry() {
@@ -2175,7 +2213,7 @@ var PageLoader = class {
2175
2213
  const wanted = (i) => !this.textures[i] && !this.inFlight.has(i) && !this.failed.has(i) && this.distance(i) <= this.radius;
2176
2214
  const queue = [
2177
2215
  ...this.first.filter((i) => this.waiting.has(i)),
2178
- ...this.sources.map((_, i) => i).sort((a, b) => this.distance(a) - this.distance(b))
2216
+ ...this.sources.map((_, i) => i).sort((a, b) => this.distance(a) - this.distance(b) || Math.abs(a - this.around) - Math.abs(b - this.around))
2179
2217
  ];
2180
2218
  for (const page of queue) {
2181
2219
  if (this.inFlight.size >= CONCURRENCY) return;
@@ -2707,6 +2745,7 @@ var LeafThrough = class extends EventTarget {
2707
2745
  onError: (page, error) => this.fail(error, page)
2708
2746
  });
2709
2747
  this.loader = loader;
2748
+ this.inView = "";
2710
2749
  this.resetReady();
2711
2750
  const first = [around - 1, around, around + 1, around + 2];
2712
2751
  const visible = this.pagesAt(this.pageToPosition(around, layout), layout);
@@ -2716,6 +2755,17 @@ var LeafThrough = class extends EventTarget {
2716
2755
  this.needsRender = true;
2717
2756
  });
2718
2757
  }
2758
+ // loads around what's on screen, pages mid-turn included, the page being read first
2759
+ inView = "";
2760
+ followReader(engine, layout) {
2761
+ if (!this.loader) return;
2762
+ const { from, to } = engine.pagesInView();
2763
+ const around = this.pagesAt(engine.position, layout)[0] ?? to;
2764
+ const key = `${from}:${to}:${around}`;
2765
+ if (key === this.inView) return;
2766
+ this.inView = key;
2767
+ this.loader.focus(from, to, around);
2768
+ }
2719
2769
  resetReady() {
2720
2770
  this.ready = false;
2721
2771
  this.element.dataset.state = "loading";
@@ -2759,8 +2809,6 @@ var LeafThrough = class extends EventTarget {
2759
2809
  const first = !this.lastDetailKey;
2760
2810
  this.lastDetailKey = key;
2761
2811
  this.live.textContent = (this.options.formatPageLabel ?? defaultPageLabel)(detail);
2762
- const { visiblePages: visible } = detail;
2763
- this.loader?.focus(visible[0] ?? detail.page, visible[visible.length - 1] ?? detail.page);
2764
2812
  if (first) return;
2765
2813
  this.options.onPageChange?.(detail);
2766
2814
  this.dispatchEvent(new CustomEvent("pagechange", { detail }));
@@ -2867,6 +2915,7 @@ var LeafThrough = class extends EventTarget {
2867
2915
  if (!view) return;
2868
2916
  this.stepRiffle(now, view.engine);
2869
2917
  const moved = view.engine.update(now);
2918
+ this.followReader(view.engine, view.settings.layout);
2870
2919
  if (view.engine.activeSide !== this.hitSide) this.placeHitArea();
2871
2920
  if (moved || this.needsRender) {
2872
2921
  if (this.renderer && view.scene && view.camera) {
@@ -2926,4 +2975,4 @@ export {
2926
2975
  defaultPageLabel,
2927
2976
  LeafThrough
2928
2977
  };
2929
- //# sourceMappingURL=chunk-U2OBPSZN.js.map
2978
+ //# sourceMappingURL=chunk-DQL4R32K.js.map