@prose-reader/core 1.15.0 → 1.17.0

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.
@@ -2127,7 +2127,15 @@
2127
2127
  });
2128
2128
  const reader = next(options);
2129
2129
  const getStyle = () => `
2130
- ${``}
2130
+ ${/*
2131
+ Ideally, we would like to use !important but it could break publisher specific
2132
+ cases.
2133
+ Also right now we do not apply it to * since it would also break publisher
2134
+ more specific scaling down the tree.
2135
+
2136
+ body *:not([class^="mjx-"]) {
2137
+ */
2138
+ ``}
2131
2139
  body {
2132
2140
  ${settingsSubject$.value.fontScale !== 1 ? `font-size: ${settingsSubject$.value.fontScale}em !important;` : ``}
2133
2141
  ${settingsSubject$.value.lineHeight !== `publisher` ? `line-height: ${settingsSubject$.value.lineHeight} !important;` : ``}
@@ -2169,6 +2177,9 @@
2169
2177
  );
2170
2178
  return {
2171
2179
  ...reader,
2180
+ /**
2181
+ * Absorb current enhancer settings and passthrough the rest to reader
2182
+ */
2172
2183
  setSettings: (settings) => {
2173
2184
  const {
2174
2185
  fontJustification: fontJustification2 = settingsSubject$.value.fontJustification,
@@ -2182,6 +2193,9 @@
2182
2193
  }
2183
2194
  reader.setSettings(passthroughSettings);
2184
2195
  },
2196
+ /**
2197
+ * Combine reader settings with enhancer settings
2198
+ */
2185
2199
  settings$
2186
2200
  };
2187
2201
  };
@@ -2449,6 +2463,7 @@
2449
2463
  };
2450
2464
  };
2451
2465
  const createReport = (namespace) => ({
2466
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2452
2467
  log: (...data) => {
2453
2468
  if (window.__PROSE_READER_DEBUG) {
2454
2469
  if (namespace)
@@ -2457,6 +2472,7 @@
2457
2472
  console.log(wrap(ROOT_NAMESPACE), ...data);
2458
2473
  }
2459
2474
  },
2475
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2460
2476
  warn: (...data) => {
2461
2477
  if (window.__PROSE_READER_DEBUG) {
2462
2478
  if (namespace)
@@ -2465,9 +2481,22 @@
2465
2481
  console.warn(wrap(ROOT_NAMESPACE), ...data);
2466
2482
  }
2467
2483
  },
2484
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2468
2485
  error: (...data) => {
2469
2486
  console.error(...data);
2470
2487
  },
2488
+ // time: (label?: string | undefined) => {
2489
+ // if (window.__PROSE_READER_DEBUG) {
2490
+ // // eslint-disable-next-line no-console
2491
+ // console.time(`[prose-reader] [metric] ${label}`);
2492
+ // }
2493
+ // },
2494
+ // timeEnd: (label?: string | undefined) => {
2495
+ // if (window.__PROSE_READER_DEBUG) {
2496
+ // // eslint-disable-next-line no-console
2497
+ // console.timeEnd(`[prose-reader] [metric] ${label}`);
2498
+ // }
2499
+ // },
2471
2500
  time,
2472
2501
  logMetric: (performanceEntry, targetDuration = 0) => {
2473
2502
  if (window.__PROSE_READER_DEBUG) {
@@ -2481,6 +2510,7 @@
2481
2510
  }
2482
2511
  }
2483
2512
  },
2513
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2484
2514
  measurePerformance: (name, targetDuration = 10, functionToMeasure, { disable } = {}) => {
2485
2515
  if (disable || !window.__PROSE_READER_DEBUG)
2486
2516
  return functionToMeasure;
@@ -2675,15 +2705,37 @@
2675
2705
  beginPageIndexInChapter: paginationInfo.beginPageIndex,
2676
2706
  beginNumberOfPagesInChapter: paginationInfo.beginNumberOfPages,
2677
2707
  beginChapterInfo: beginItem ? chaptersInfo[beginItem.item.id] : void 0,
2708
+ // chapterIndex: number;
2709
+ // pages: number;
2710
+ // pageIndexInBook: number;
2711
+ // pageIndexInChapter: number;
2712
+ // pagesOfChapter: number;
2713
+ // pagePercentageInChapter: number;
2714
+ // offsetPercentageInChapter: number;
2715
+ // domIndex: number;
2716
+ // charOffset: number;
2717
+ // serializeString?: string;
2678
2718
  beginSpineItemIndex: paginationInfo.beginSpineItemIndex,
2719
+ // spineItemPath: beginItem?.item.path,
2720
+ // spineItemId: beginItem?.item.id,
2679
2721
  beginCfi: paginationInfo.beginCfi,
2680
2722
  beginSpineItemReadingDirection: beginItem == null ? void 0 : beginItem.getReadingDirection(),
2681
2723
  endChapterInfo: endItem ? chaptersInfo[endItem.item.id] : void 0,
2682
2724
  endPageIndexInChapter: paginationInfo.endPageIndex,
2683
2725
  endNumberOfPagesInChapter: paginationInfo.endNumberOfPages,
2684
2726
  endSpineItemIndex: paginationInfo.endSpineItemIndex,
2727
+ // spineItemPath: endItem?.item.path,
2728
+ // spineItemId: endItem?.item.id,
2685
2729
  endSpineItemReadingDirection: endItem == null ? void 0 : endItem.getReadingDirection(),
2686
2730
  endCfi: paginationInfo.endCfi,
2731
+ // end: ReadingLocation;
2732
+ // spineItemReadingDirection: focusedSpineItem?.getReadingDirection(),
2733
+ /**
2734
+ * This percentage is based of the weight (kb) of every items and the number of pages.
2735
+ * It is not accurate but gives a general good idea of the overall progress.
2736
+ * It is recommended to use this progress only for reflow books. For pre-paginated books
2737
+ * the number of pages and current index can be used instead since 1 page = 1 chapter.
2738
+ */
2687
2739
  percentageEstimateOfBook: endItem ? reader.progression.getPercentageEstimate(
2688
2740
  context,
2689
2741
  paginationInfo.endSpineItemIndex ?? 0,
@@ -2693,6 +2745,11 @@
2693
2745
  endItem
2694
2746
  ) : 0,
2695
2747
  isUsingSpread: context.shouldDisplaySpread()
2748
+ // chaptersOfBook: number;
2749
+ // chapter: string;
2750
+ // hasNextChapter: (reader.spine.spineItemIndex || 0) < (manifest.readingOrder.length - 1),
2751
+ // hasPreviousChapter: (reader.spine.spineItemIndex || 0) < (manifest.readingOrder.length - 1),
2752
+ // numberOfSpineItems: context.getManifest()?.readingOrder.length,
2696
2753
  };
2697
2754
  };
2698
2755
  const getSpineItemNumberOfPages = (spineItem) => {
@@ -2732,6 +2789,9 @@
2732
2789
  const numberOfPagesPerItems = getNumberOfPagesPerItems();
2733
2790
  return {
2734
2791
  numberOfPagesPerItems,
2792
+ /**
2793
+ * This may be not accurate for reflowable due to dynamic load / unload.
2794
+ */
2735
2795
  numberOfTotalPages: numberOfPagesPerItems.reduce((acc, numberOfPagesForItem) => acc + numberOfPagesForItem, 0)
2736
2796
  };
2737
2797
  }),
@@ -2822,7 +2882,11 @@
2822
2882
  }
2823
2883
  ${(foundTheme == null ? void 0 : foundTheme.foregroundColor) ? `
2824
2884
  body * {
2825
- ${``}
2885
+ ${/*
2886
+ Ideally, we would like to use !important but it could break publisher specific
2887
+ cases
2888
+ */
2889
+ ``}
2826
2890
  color: ${foundTheme.foregroundColor};
2827
2891
  }
2828
2892
  ` : ``}
@@ -3420,6 +3484,8 @@
3420
3484
  take(1)
3421
3485
  );
3422
3486
  const unload$ = unloadSubject$.asObservable().pipe(
3487
+ // @todo remove iframe when viewport is free
3488
+ // @todo use takeUntil(load$) when it's the case to cancel
3423
3489
  withLatestFrom(frameElementSubject$),
3424
3490
  filter(([_, frame]) => !!frame),
3425
3491
  map(([, frame]) => {
@@ -3440,13 +3506,16 @@
3440
3506
  const load$ = loadSubject$.asObservable().pipe(
3441
3507
  withLatestFrom(isLoadedSubject$),
3442
3508
  filter(([_, isLoaded]) => !isLoaded),
3509
+ // let's ignore later load as long as the first one still runs
3443
3510
  exhaustMap(() => {
3444
3511
  return createFrame$().pipe(
3445
3512
  mergeMap((frame) => waitForViewportFree$.pipe(map(() => frame))),
3446
3513
  mergeMap((frame) => {
3447
3514
  parent.appendChild(frame);
3448
3515
  frameElementSubject$.next(frame);
3449
- if (!fetchResource && item.href.startsWith(window.location.origin) && (item.mediaType && [`application/xhtml+xml`, `application/xml`, `text/html`, `text/xml`].includes(item.mediaType) || !item.mediaType && ITEM_EXTENSION_VALID_FOR_FRAME_SRC.some((extension) => item.href.endsWith(extension)))) {
3516
+ if (!fetchResource && item.href.startsWith(window.location.origin) && // we have an encoding and it's a valid html
3517
+ (item.mediaType && [`application/xhtml+xml`, `application/xml`, `text/html`, `text/xml`].includes(item.mediaType) || // no encoding ? then try to detect html
3518
+ !item.mediaType && ITEM_EXTENSION_VALID_FOR_FRAME_SRC.some((extension) => item.href.endsWith(extension)))) {
3450
3519
  frame == null ? void 0 : frame.setAttribute(`src`, item.href);
3451
3520
  return rxjs.of(frame);
3452
3521
  } else {
@@ -3502,6 +3571,7 @@
3502
3571
  })
3503
3572
  );
3504
3573
  }),
3574
+ // we stop loading as soon as unload is requested
3505
3575
  takeUntil(unloadSubject$)
3506
3576
  );
3507
3577
  }),
@@ -3628,6 +3698,12 @@
3628
3698
  getHtmlFromResource,
3629
3699
  load,
3630
3700
  unload,
3701
+ /**
3702
+ * Upward layout is used when the parent wants to manipulate the iframe without triggering
3703
+ * `layout` event. This is a particular case needed for iframe because the parent can layout following
3704
+ * an iframe `layout` event. Because the parent `layout` may change some of iframe properties we do not
3705
+ * want the iframe to trigger a new `layout` even and have infinite loop.
3706
+ */
3631
3707
  staticLayout: (size) => {
3632
3708
  const frame = frameElement$.getValue();
3633
3709
  if (frame) {
@@ -3638,6 +3714,8 @@
3638
3714
  }
3639
3715
  }
3640
3716
  },
3717
+ // @todo block access, only public API to manipulate / get information (in order to memo / optimize)
3718
+ // manipulate() with cb and return boolean whether re-layout or not
3641
3719
  getManipulableFrame,
3642
3720
  getReadingDirection: () => {
3643
3721
  var _a;
@@ -3659,6 +3737,10 @@
3659
3737
  loaded$,
3660
3738
  ready$,
3661
3739
  isReady$: isReadySubject$.asObservable(),
3740
+ /**
3741
+ * This is used as upstream layout change. This event is being listened to by upper app
3742
+ * in order to layout again and adjust every element based on the new content.
3743
+ */
3662
3744
  contentLayoutChange$
3663
3745
  }
3664
3746
  };
@@ -3937,6 +4019,8 @@
3937
4019
  const rect = containerElement.getBoundingClientRect();
3938
4020
  const normalizedValues = {
3939
4021
  ...rect,
4022
+ // we want to round to first decimal because it's possible to have half pixel
4023
+ // however browser engine can also gives back x.yyyy based on their precision
3940
4024
  width: Math.round(rect.width * 10) / 10,
3941
4025
  height: Math.round(rect.height * 10) / 10
3942
4026
  };
@@ -4016,6 +4100,8 @@
4016
4100
  return {
4017
4101
  columnHeight,
4018
4102
  columnWidth,
4103
+ // horizontalMargin,
4104
+ // verticalMargin,
4019
4105
  width
4020
4106
  };
4021
4107
  };
@@ -4333,22 +4419,37 @@
4333
4419
  justify-content: ${spreadPosition === `left` ? `flex-end` : spreadPosition === `right` ? `flex-start` : `center`};
4334
4420
  ` : ``}
4335
4421
  }
4336
- ${``}
4422
+ ${/*
4423
+ might be html * but it does mess up things like figure if so.
4424
+ check accessible_epub_3
4425
+ */
4426
+ ``}
4337
4427
  html, body {
4338
4428
  height: 100%;
4339
4429
  width: 100%;
4340
4430
  }
4341
- ${``}
4431
+ ${/*
4432
+ This one is important for preventing 100% img to resize above
4433
+ current width. Especially needed for cbz conversion
4434
+ */
4435
+ ``}
4342
4436
  html, body {
4343
4437
  -max-width: ${columnWidth}px !important;
4344
4438
  }
4345
- ${``}
4439
+ ${/*
4440
+ * @see https://hammerjs.github.io/touch-action/
4441
+ * It needs to be disabled when using free scroll
4442
+ */
4443
+ ``}
4346
4444
  html, body {
4347
4445
  ${enableTouch ? `
4348
4446
  touch-action: none
4349
4447
  ` : ``}
4350
4448
  }
4351
- ${``}
4449
+ ${/*
4450
+ prevent drag of image instead of touch on firefox
4451
+ */
4452
+ ``}
4352
4453
  img {
4353
4454
  user-select: none;
4354
4455
  -webkit-user-drag: none;
@@ -4356,9 +4457,18 @@
4356
4457
  -moz-user-drag: none;
4357
4458
  -o-user-drag: none;
4358
4459
  user-drag: none;
4359
- ${``}
4460
+ ${/*
4461
+ prevent weird overflow or margin. Try `block` if `flex` has weird behavior
4462
+ */
4463
+ ``}
4360
4464
  display: flex;
4361
- ${``}
4465
+ ${/*
4466
+ If the document does not have viewport, we cannot scale anything inside.
4467
+ This should never happens with a valid epub document however it will happens if
4468
+ we load .jpg, .png, etc directly in the iframe. This is expected, in this case we force
4469
+ the inner content to display correctly.
4470
+ */
4471
+ ``}
4362
4472
  ${!viewportDimensions ? `
4363
4473
  -width: 100%;
4364
4474
  max-width: 100%;
@@ -4489,7 +4599,10 @@
4489
4599
  height: 100%;
4490
4600
  margin: 0;
4491
4601
  }
4492
- ${``}
4602
+ ${/*
4603
+ * @see https://hammerjs.github.io/touch-action/
4604
+ */
4605
+ ``}
4493
4606
  html, body {
4494
4607
  touch-action: none;
4495
4608
  }
@@ -4497,7 +4610,10 @@
4497
4610
  };
4498
4611
  const buildStyleForReflowableImageOnly = ({ isScrollable, enableTouch }) => {
4499
4612
  return `
4500
- ${``}
4613
+ ${/*
4614
+ * @see https://hammerjs.github.io/touch-action/
4615
+ */
4616
+ ``}
4501
4617
  html, body {
4502
4618
  width: 100%;
4503
4619
  margin: 0;
@@ -4512,9 +4628,14 @@
4512
4628
  margin: 0;
4513
4629
  padding: 0;
4514
4630
  box-sizing: border-box;
4515
- ${``}
4631
+ ${// we make sure img spread on entire screen
4632
+ ``}
4516
4633
  width: 100%;
4517
- ${``}
4634
+ ${/**
4635
+ * line break issue
4636
+ * @see https://stackoverflow.com/questions/37869020/image-not-taking-up-the-full-height-of-container
4637
+ */
4638
+ ``}
4518
4639
  display: block;
4519
4640
  }
4520
4641
  ` : ``}
@@ -4529,13 +4650,28 @@
4529
4650
  parsererror {
4530
4651
  display: none !important;
4531
4652
  }
4532
- ${``}
4653
+ ${/*
4654
+ might be html * but it does mess up things like figure if so.
4655
+ check accessible_epub_3
4656
+ */
4657
+ ``}
4533
4658
  html, body {
4534
4659
  margin: 0;
4535
4660
  padding: 0 !important;
4536
4661
  -max-width: ${columnWidth}px !important;
4537
4662
  }
4538
- ${``}
4663
+ ${/*
4664
+ body {
4665
+ height: ${columnHeight}px !important;
4666
+ width: ${columnWidth}px !important;
4667
+ -margin-left: ${horizontalMargin}px !important;
4668
+ -margin-right: ${horizontalMargin}px !important;
4669
+ -margin: ${verticalMargin}px ${horizontalMargin}px !important;
4670
+ -padding-top: ${horizontalMargin}px !important;
4671
+ -padding-bottom: ${horizontalMargin}px !important;
4672
+ }
4673
+ */
4674
+ ``}
4539
4675
  body {
4540
4676
  padding: 0 !important;
4541
4677
  width: ${width}px !important;
@@ -4551,18 +4687,33 @@
4551
4687
  margin: 0;
4552
4688
  }
4553
4689
  body:focus-visible {
4554
- ${``}
4690
+ ${/*
4691
+ we make sure that there are no outline when we focus something inside the iframe
4692
+ */
4693
+ ``}
4555
4694
  outline: none;
4556
4695
  }
4557
- ${``}
4696
+ ${/*
4697
+ * @see https://hammerjs.github.io/touch-action/
4698
+ */
4699
+ ``}
4558
4700
  html, body {
4559
4701
  touch-action: none;
4560
4702
  }
4561
- ${``}
4703
+ ${/*
4704
+ this messes up hard, be careful with this
4705
+ */
4706
+ ``}
4562
4707
  * {
4563
4708
  -max-width: ${columnWidth}px !important;
4564
4709
  }
4565
- ${``}
4710
+ ${/*
4711
+ this is necessary to have a proper calculation when determining size
4712
+ of iframe content. If an img is using something like width:100% it would expand to
4713
+ the size of the original image and potentially gives back a wrong size (much larger)
4714
+ @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Columns/Handling_Overflow_in_Multicol
4715
+ */
4716
+ ``}
4566
4717
  img, video, audio, object, svg {
4567
4718
  max-width: 100%;
4568
4719
  max-width: ${columnWidth}px !important;
@@ -4581,7 +4732,17 @@
4581
4732
  box-sizing: border-box;
4582
4733
  d-max-width: ${columnWidth}px !important;
4583
4734
  }
4584
- ${``}
4735
+ ${/*
4736
+ img, video, audio, object, svg {
4737
+ max-height: ${columnHeight}px !important;
4738
+ box-sizing: border-box;
4739
+ object-fit: contain;
4740
+ -webkit-column-break-inside: avoid;
4741
+ page-break-inside: avoid;
4742
+ break-inside: avoid;
4743
+ }
4744
+ */
4745
+ ``}
4585
4746
  table {
4586
4747
  max-width: ${columnWidth}px !important;
4587
4748
  table-layout: fixed;
@@ -4778,7 +4939,11 @@
4778
4939
  this.isRange = false;
4779
4940
  this.opts = Object.assign(
4780
4941
  {
4942
+ // If CFI is a Simple Range, pretend it isn't
4943
+ // by parsing only the start of the range
4781
4944
  flattenRange: false,
4945
+ // Strip temporal, spatial, offset and textLocationAssertion
4946
+ // from places where they don't make sense
4782
4947
  stricter: true
4783
4948
  },
4784
4949
  opts || {}
@@ -4898,6 +5063,7 @@
4898
5063
  return cfi.get();
4899
5064
  }
4900
5065
  }
5066
+ // Takes two CFI paths and compares them
4901
5067
  static comparePath(a, b) {
4902
5068
  const max = Math.max(a.length, b.length);
4903
5069
  let i, cA, cB, diff;
@@ -4914,11 +5080,13 @@
4914
5080
  }
4915
5081
  return 0;
4916
5082
  }
5083
+ // Sort an array of CFI objects
4917
5084
  static sort(a) {
4918
5085
  a.sort((a2, b) => {
4919
5086
  return this.compare(a2, b);
4920
5087
  });
4921
5088
  }
5089
+ // Takes two CFI objects and compares them.
4922
5090
  static compare(a, b) {
4923
5091
  let oA = a.get();
4924
5092
  let oB = b.get();
@@ -4938,6 +5106,7 @@
4938
5106
  return this.comparePath(oA, oB);
4939
5107
  }
4940
5108
  }
5109
+ // Takes two parsed path parts (assuming path is split on '!') and compares them.
4941
5110
  static compareParts(a, b) {
4942
5111
  const max = Math.max(a.length, b.length);
4943
5112
  let i, cA, cB, diff;
@@ -4979,6 +5148,7 @@
4979
5148
  return str;
4980
5149
  }
4981
5150
  }
5151
+ // decode HTML/XML entities and compute length
4982
5152
  trueLength(dom, str) {
4983
5153
  return this.decodeEntities(dom, str).length;
4984
5154
  }
@@ -5244,6 +5414,9 @@
5244
5414
  throw new Error(`Missing child node index in CFI`);
5245
5415
  return { parsed: o, offset: i, newDoc: state === `!` };
5246
5416
  }
5417
+ // The CFI counts child nodes differently from the DOM
5418
+ // Retrieve the child of parentNode at the specified index
5419
+ // according to the CFI standard way of counting
5247
5420
  getChildNodeByCFIIndex(dom, parentNode, index, offset) {
5248
5421
  const children = parentNode.childNodes;
5249
5422
  if (!children.length)
@@ -5322,6 +5495,7 @@
5322
5495
  }
5323
5496
  return false;
5324
5497
  }
5498
+ // Use a Text Location Assertion to correct and offset
5325
5499
  correctOffset(dom, node, offset, assertion) {
5326
5500
  let curNode = node;
5327
5501
  let matchStr;
@@ -5419,6 +5593,15 @@
5419
5593
  }
5420
5594
  return o;
5421
5595
  }
5596
+ // Each part of a CFI (as separated by '!')
5597
+ // references a separate HTML/XHTML/XML document.
5598
+ // This function takes an index specifying the part
5599
+ // of the CFI and the appropriate Document or XMLDocument
5600
+ // that is referenced by the specified part of the CFI
5601
+ // and returns the URI for the document referenced by
5602
+ // the next part of the CFI
5603
+ // If the opt `ignoreIDs` is true then IDs
5604
+ // will not be used while resolving
5422
5605
  resolveURI(index, dom, opts) {
5423
5606
  opts = opts || {};
5424
5607
  if (index < 0 || index > this.parts.length - 2) {
@@ -5430,7 +5613,8 @@
5430
5613
  const o = this.resolveNode(index, subparts, dom, opts);
5431
5614
  let node = o.node;
5432
5615
  const tagName = node.tagName.toLowerCase();
5433
- if (tagName === `itemref` && node.parentNode.tagName.toLowerCase() === `spine`) {
5616
+ if (tagName === `itemref` && // @ts-ignore
5617
+ node.parentNode.tagName.toLowerCase() === `spine`) {
5434
5618
  const idref = node.getAttribute(`idref`);
5435
5619
  if (!idref)
5436
5620
  throw new Error(`Referenced node had not 'idref' attribute`);
@@ -5477,6 +5661,9 @@
5477
5661
  delete o.offset;
5478
5662
  return { ...lastPart, ...o };
5479
5663
  }
5664
+ // Takes the Document or XMLDocument for the final
5665
+ // document referenced by the CFI
5666
+ // and returns the node and offset into that node
5480
5667
  resolveLast(dom, opts) {
5481
5668
  opts = Object.assign(
5482
5669
  {
@@ -5734,7 +5921,22 @@
5734
5921
  spineItem: beginSpineItem,
5735
5922
  spineItemIndex: beginItemIndex,
5736
5923
  pageIndex: beginPageIndex,
5737
- cfi: (lastExpectedNavigation == null ? void 0 : lastExpectedNavigation.type) === `navigate-from-cfi` && spineItemToFocus === beginSpineItem ? lastExpectedNavigation.data : data.triggeredBy === `adjust` && context.getSettings().computedPageTurnMode === `controlled` ? pagination.getInfo().beginCfi : beginItemIndex !== pagination.getInfo().beginSpineItemIndex ? cfiLocator.getRootCfi(beginSpineItem) : cfiLocator.getRootCfi(beginSpineItem),
5924
+ /**
5925
+ * Because the start of a navigation may involve animations and interactions we don't resolve heavy CFI here.
5926
+ * We do want to have certain information correct in the pagination right after a navigation (same tick) but we just
5927
+ * defer heavy non vital stuff for later.
5928
+ * There are only 4 different cfi update at this stage:
5929
+ * - navigation comes from cfi, we simply affect the cfi to the pagination
5930
+ * - navigation comes from adjustment with controlled mode, we don't update the cfi, just pass the previous one
5931
+ * - navigation comes from adjustment with free mode, we will update with root cfi if needed because we could be on new page
5932
+ * - navigation is not from adjustment, this means we are on either new page or new reading item, we use light cfi with root (no dom lookup)
5933
+ *
5934
+ * The cfi is later adjusted with heavy dom lookup once the viewport is free.
5935
+ */
5936
+ cfi: (lastExpectedNavigation == null ? void 0 : lastExpectedNavigation.type) === `navigate-from-cfi` && spineItemToFocus === beginSpineItem ? lastExpectedNavigation.data : data.triggeredBy === `adjust` && context.getSettings().computedPageTurnMode === `controlled` ? pagination.getInfo().beginCfi : beginItemIndex !== pagination.getInfo().beginSpineItemIndex ? cfiLocator.getRootCfi(beginSpineItem) : (
5937
+ /* @todo check ? */
5938
+ cfiLocator.getRootCfi(beginSpineItem)
5939
+ ),
5738
5940
  options: {
5739
5941
  isAtEndOfChapter: false
5740
5942
  }
@@ -5743,7 +5945,10 @@
5743
5945
  spineItem: endSpineItem,
5744
5946
  spineItemIndex: endItemIndex,
5745
5947
  pageIndex: endPageIndex,
5746
- cfi: (lastExpectedNavigation == null ? void 0 : lastExpectedNavigation.type) === `navigate-from-cfi` && spineItemToFocus === endSpineItem ? lastExpectedNavigation.data : data.triggeredBy === `adjust` && context.getSettings().computedPageTurnMode === `controlled` ? pagination.getInfo().endCfi : endItemIndex !== pagination.getInfo().endSpineItemIndex ? cfiLocator.getRootCfi(endSpineItem) : cfiLocator.getRootCfi(endSpineItem),
5948
+ cfi: (lastExpectedNavigation == null ? void 0 : lastExpectedNavigation.type) === `navigate-from-cfi` && spineItemToFocus === endSpineItem ? lastExpectedNavigation.data : data.triggeredBy === `adjust` && context.getSettings().computedPageTurnMode === `controlled` ? pagination.getInfo().endCfi : endItemIndex !== pagination.getInfo().endSpineItemIndex ? cfiLocator.getRootCfi(endSpineItem) : (
5949
+ /* @todo check ? */
5950
+ cfiLocator.getRootCfi(endSpineItem)
5951
+ ),
5747
5952
  options: {
5748
5953
  isAtEndOfChapter: false
5749
5954
  }
@@ -5773,7 +5978,16 @@
5773
5978
  takeUntil(context.$.destroy$)
5774
5979
  ).subscribe();
5775
5980
  rxjs.merge(
5981
+ /**
5982
+ * We want to update content after navigation since we are at a different place.
5983
+ * We also wait for navigated items to be updated so that we have access to correct focus.
5984
+ * which is why we use this observer rather than `navigation$`.
5985
+ */
5776
5986
  itemUpdateOnNavigation$,
5987
+ /**
5988
+ * This one make sure we also listen for layout change and that we execute the code once the navigation
5989
+ * has been adjusted (whether it's needed or not).
5990
+ */
5777
5991
  navigationAdjusted$
5778
5992
  ).pipe(
5779
5993
  switchMap(() => {
@@ -5972,7 +6186,10 @@
5972
6186
  const isPrePaginated = ((_a = context.getManifest()) == null ? void 0 : _a.renditionLayout) === `pre-paginated`;
5973
6187
  const isUsingFreeScroll = context.getSettings().computedPageTurnMode === `scrollable`;
5974
6188
  orderedSpineItemsSubject$.value.forEach((orderedSpineItem, index) => {
5975
- const isBeforeFocusedWithPreload = index < leftIndex && !isPrePaginated && isUsingFreeScroll ? true : index < leftIndex - numberOfAdjacentSpineItemToPreLoad;
6189
+ const isBeforeFocusedWithPreload = (
6190
+ // we never want to preload anything before on free scroll on flow because it could offset the cursor
6191
+ index < leftIndex && !isPrePaginated && isUsingFreeScroll ? true : index < leftIndex - numberOfAdjacentSpineItemToPreLoad
6192
+ );
5976
6193
  const isAfterTailWithPreload = index > rightIndex + numberOfAdjacentSpineItemToPreLoad;
5977
6194
  if (!isBeforeFocusedWithPreload && !isAfterTailWithPreload) {
5978
6195
  orderedSpineItem.loadContent();
@@ -6178,7 +6395,8 @@
6178
6395
  var _a, _b, _c;
6179
6396
  const pageSize = context.getPageSize();
6180
6397
  const frame = (_b = (_a = spineItem.spineItemFrame) == null ? void 0 : _a.getManipulableFrame()) == null ? void 0 : _b.frame;
6181
- if (((_c = frame == null ? void 0 : frame.contentWindow) == null ? void 0 : _c.document) && frame.contentWindow.document.body !== null) {
6398
+ if (((_c = frame == null ? void 0 : frame.contentWindow) == null ? void 0 : _c.document) && // very important because it is being used by next functions
6399
+ frame.contentWindow.document.body !== null) {
6182
6400
  const { x: left, y: top } = getSpineItemPositionFromPageIndex(pageIndex, spineItem);
6183
6401
  const viewport = {
6184
6402
  left,
@@ -6748,8 +6966,14 @@
6748
6966
  chapterPageNavigation$,
6749
6967
  leftPageNavigation$,
6750
6968
  rightPageNavigation$,
6969
+ // for some reason after too much item ts complains
6751
6970
  rxjs.merge(cfiNavigation$, pageNavigation$)
6752
6971
  ).pipe(
6972
+ /**
6973
+ * Ideally when manually navigating we expect the navigation to be different from the previous one.
6974
+ * This is because manual navigation is not used with scroll where you can move within the same item. A manual
6975
+ * navigation would theoretically always move to different items.
6976
+ */
6753
6977
  withLatestFrom(currentNavigationSubject$),
6754
6978
  filter(([navigation, currentNavigation]) => navigator2.areNavigationDifferent(navigation, currentNavigation)),
6755
6979
  map(([navigation]) => navigation)
@@ -6924,6 +7148,9 @@
6924
7148
  }
6925
7149
  const { x, y } = element.getBoundingClientRect();
6926
7150
  const newValue = {
7151
+ // we want to round to first decimal because it's possible to have half pixel
7152
+ // however browser engine can also gives back x.yyyy based on their precision
7153
+ // @see https://stackoverflow.com/questions/13847053/difference-between-and-math-floor for ~~
6927
7154
  x: ~~(Math.abs(x) * 10) / 10,
6928
7155
  y: ~~(Math.abs(y) * 10) / 10
6929
7156
  };
@@ -7134,6 +7361,14 @@
7134
7361
  const animationDuration = currentEvent.animation === `snap` ? context.getSettings().computedSnapAnimationDuration : context.getSettings().computedPageTurnAnimationDuration;
7135
7362
  const pageTurnAnimation = currentEvent.animation === `snap` ? `slide` : context.getSettings().computedPageTurnAnimation;
7136
7363
  return rxjs.of(currentEvent).pipe(
7364
+ /**
7365
+ * @important
7366
+ * Optimization:
7367
+ * When the adjustment does not need animation it means we want to be there as fast as possible
7368
+ * One example is when we adjust position after layout. In this case we don't want to have flicker or see
7369
+ * anything for x ms while we effectively adjust. We want it to be immediate.
7370
+ * However when user is repeatedly turning page, we can improve smoothness by delaying a bit the adjustment
7371
+ */
7137
7372
  currentEvent.shouldAnimate ? delay(1, rxjs.animationFrameScheduler) : rxjs.identity,
7138
7373
  tap((data) => {
7139
7374
  const noAdjustmentNeeded = false;
@@ -7150,6 +7385,14 @@
7150
7385
  element.style.setProperty(`opacity`, `1`);
7151
7386
  }
7152
7387
  }),
7388
+ /**
7389
+ * @important
7390
+ * We always need to adjust the reading offset. Even if the current viewport value
7391
+ * is the same as the payload position. This is because an already running animation could
7392
+ * be active, meaning the viewport is still adjusting itself (after animation duration). So we
7393
+ * need to adjust to anchor to the payload position. This is because we use viewport computed position,
7394
+ * not the value set by `setProperty`
7395
+ */
7153
7396
  withLatestFrom(hooks$),
7154
7397
  tap(([data, hooks]) => {
7155
7398
  if (pageTurnAnimation !== `fade`) {
@@ -7188,6 +7431,12 @@
7188
7431
  map((states) => states.every((state) => state === `end`) ? `free` : `busy`),
7189
7432
  distinctUntilChanged(),
7190
7433
  shareReplay(1),
7434
+ /**
7435
+ * @important
7436
+ * Since state$ is being updated from navigation$ and other exported streams we need it to be
7437
+ * hot so it always have the correct value no matter when someone subscribe later.
7438
+ * We cannot wait for the cold stream to start after a navigation already happened for example.
7439
+ */
7191
7440
  makeItHot
7192
7441
  );
7193
7442
  const waitForViewportFree$ = state$.pipe(
@@ -7195,6 +7444,24 @@
7195
7444
  rxjs.take(1)
7196
7445
  );
7197
7446
  const navigationAdjustedAfterLayout$ = spine.$.layout$.pipe(
7447
+ /**
7448
+ * @important
7449
+ * Careful with using debounce / throttle here since it can decrease user experience
7450
+ * when layout happens it can means an item before the current one has been unloaded, at current code
7451
+ * we unload and size back each item to the screen so it will have the effect of flicker for user.
7452
+ * Consider this workflow:
7453
+ * - user navigate to page 2
7454
+ * - viewport move to item 2
7455
+ * - page 1 unload and goes back from 2000px to 500px
7456
+ * - layout triggered
7457
+ * - viewport is now on an item far after item 2 because item 1 shrink (PROBLEM)
7458
+ * - sometime after viewport is adjusted back to item 2.
7459
+ *
7460
+ * Two solution to fix this issue:
7461
+ * - maybe later try to implement a different strategy and never shrink back item unless they are loaded
7462
+ * - do not use debounce / throttle and navigate back to the item right on the same tick
7463
+ */
7464
+ // debounceTime(10, animationFrameScheduler),
7198
7465
  switchMap(
7199
7466
  () => waitForViewportFree$.pipe(
7200
7467
  switchMap(() => {
@@ -7264,10 +7531,18 @@
7264
7531
  if (context.isRTL()) {
7265
7532
  return {
7266
7533
  x: leftEnd - position.x - context.getPageSize().width,
7534
+ // y: (topEnd - position.y) - context.getPageSize().height,
7267
7535
  y: Math.max(0, position.y - topStart)
7268
7536
  };
7269
7537
  }
7270
7538
  return {
7539
+ /**
7540
+ * when using spread the item could be on the right and therefore will be negative
7541
+ * @example
7542
+ * 400 (position = viewport), page of 200
7543
+ * 400 - 600 = -200.
7544
+ * However we can assume we are at 0, because we in fact can see the beginning of the item
7545
+ */
7271
7546
  x: Math.max(0, position.x - leftStart),
7272
7547
  y: Math.max(0, position.y - topStart)
7273
7548
  };
@@ -7279,6 +7554,7 @@
7279
7554
  if (context.isRTL()) {
7280
7555
  return {
7281
7556
  x: leftEnd - spineItemPosition.x - context.getPageSize().width,
7557
+ // y: (topEnd - spineItemPosition.y) - context.getPageSize().height,
7282
7558
  y: topStart + spineItemPosition.y
7283
7559
  };
7284
7560
  }
@@ -7619,10 +7895,23 @@
7619
7895
  destroy,
7620
7896
  setSettings: context.setSettings,
7621
7897
  settings$: context.$.settings$,
7898
+ /**
7899
+ * @important
7900
+ * BehaviorSubject
7901
+ */
7622
7902
  pagination$: pagination.$.info$,
7623
7903
  $: {
7624
7904
  state$: stateSubject$.asObservable(),
7905
+ /**
7906
+ * Dispatched when the reader has loaded a book and is displayed a book.
7907
+ * Using navigation API and getting information about current content will
7908
+ * have an effect.
7909
+ * It can typically be used to hide a loading indicator.
7910
+ */
7625
7911
  ready$: readySubject$.asObservable(),
7912
+ /**
7913
+ * Dispatched when a change in selection happens
7914
+ */
7626
7915
  selection$: selectionSubject$.asObservable(),
7627
7916
  viewportState$: viewportNavigator.$.state$,
7628
7917
  layout$: spine.$.layout$,
@@ -7874,6 +8163,10 @@
7874
8163
  };
7875
8164
  return {
7876
8165
  ...reader,
8166
+ // $: {
8167
+ // ...reader.$,
8168
+ // errors$: merge(reader.$.errors$, errorsSubject$.asObservable())
8169
+ // },
7877
8170
  destroy,
7878
8171
  load
7879
8172
  };
@@ -8012,7 +8305,11 @@
8012
8305
  `prose-reader-accessibility`,
8013
8306
  `
8014
8307
  :focus-visible {
8015
- ${``}
8308
+ ${/*
8309
+ Some epubs remove the outline, this is not good practice since it reduce accessibility.
8310
+ We will try to restore it by force.
8311
+ */
8312
+ ``}
8016
8313
  outline: -webkit-focus-ring-color auto 1px;
8017
8314
  }
8018
8315
  `
@@ -8167,23 +8464,27 @@
8167
8464
  container.appendChild(detailsElement);
8168
8465
  return container;
8169
8466
  };
8170
- const createReaderWithEnhancers = loadingEnhancer(
8171
- webkitEnhancer(
8172
- fontsEnhancer(
8173
- linksEnhancer(
8174
- accessibilityEnhancer(
8175
- resourcesEnhancer(
8176
- utilsEnhancer(
8177
- layoutEnhancer(
8178
- zoomEnhancer(
8179
- mediaEnhancer(
8180
- chromeEnhancer(
8181
- navigationEnhancer(
8182
- themeEnhancer(
8183
- hotkeysEnhancer(
8184
- paginationEnhancer(
8185
- progressionEnhancer(
8186
- createReader
8467
+ const createReaderWithEnhancers = (
8468
+ //__
8469
+ loadingEnhancer(
8470
+ webkitEnhancer(
8471
+ fontsEnhancer(
8472
+ linksEnhancer(
8473
+ accessibilityEnhancer(
8474
+ resourcesEnhancer(
8475
+ utilsEnhancer(
8476
+ layoutEnhancer(
8477
+ zoomEnhancer(
8478
+ mediaEnhancer(
8479
+ chromeEnhancer(
8480
+ navigationEnhancer(
8481
+ themeEnhancer(
8482
+ hotkeysEnhancer(
8483
+ paginationEnhancer(
8484
+ progressionEnhancer(
8485
+ // __
8486
+ createReader
8487
+ )
8187
8488
  )
8188
8489
  )
8189
8490
  )