@internetarchive/bookreader 5.0.0-6-14 → 5.0.0-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (271) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +72 -10
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +241 -140
  6. package/BookReader/BookReader.js +1 -1
  7. package/BookReader/BookReader.js.LICENSE.txt +24 -20
  8. package/BookReader/BookReader.js.map +1 -1
  9. package/BookReader/ia-bookreader-bundle.js +1519 -0
  10. package/BookReader/ia-bookreader-bundle.js.LICENSE.txt +17 -0
  11. package/BookReader/ia-bookreader-bundle.js.map +1 -0
  12. package/BookReader/icons/close-circle-dark.svg +1 -0
  13. package/BookReader/icons/magnify-minus.svg +1 -1
  14. package/BookReader/icons/magnify-plus.svg +1 -1
  15. package/BookReader/icons/pause.svg +1 -1
  16. package/BookReader/icons/playback-speed.svg +1 -1
  17. package/BookReader/icons/read-aloud.svg +1 -1
  18. package/BookReader/icons/voice.svg +1 -0
  19. package/BookReader/images/BRicons.svg +2 -2
  20. package/BookReader/images/books_graphic.svg +1 -1
  21. package/BookReader/images/icon_book.svg +1 -1
  22. package/BookReader/images/icon_gear.svg +1 -1
  23. package/BookReader/images/icon_info.svg +1 -1
  24. package/BookReader/images/icon_playback-rate.svg +1 -1
  25. package/BookReader/images/icon_search_button.svg +1 -1
  26. package/BookReader/images/icon_share.svg +1 -1
  27. package/BookReader/images/icon_speaker.svg +1 -1
  28. package/BookReader/images/icon_speaker_open.svg +1 -1
  29. package/BookReader/images/marker_chap-off.svg +1 -1
  30. package/BookReader/images/marker_chap-on.svg +1 -1
  31. package/BookReader/images/marker_srch-on.svg +1 -1
  32. package/BookReader/jquery-3.js +2 -0
  33. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  34. package/BookReader/plugins/plugin.archive_analytics.js +1 -1
  35. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  36. package/BookReader/plugins/plugin.autoplay.js +1 -1
  37. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  38. package/BookReader/plugins/plugin.chapters.js +1 -1
  39. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  40. package/BookReader/plugins/plugin.iframe.js +1 -1
  41. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  42. package/BookReader/plugins/plugin.mobile_nav.js +1 -1
  43. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  44. package/BookReader/plugins/plugin.resume.js +1 -1
  45. package/BookReader/plugins/plugin.resume.js.map +1 -1
  46. package/BookReader/plugins/plugin.search.js +1 -1
  47. package/BookReader/plugins/plugin.search.js.map +1 -1
  48. package/BookReader/plugins/plugin.text_selection.js +1 -1
  49. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  50. package/BookReader/plugins/plugin.tts.js +1 -1
  51. package/BookReader/plugins/plugin.tts.js.map +1 -1
  52. package/BookReader/plugins/plugin.url.js +1 -1
  53. package/BookReader/plugins/plugin.url.js.map +1 -1
  54. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -1
  55. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  56. package/BookReader/webcomponents-bundle.js +3 -0
  57. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  58. package/BookReader/webcomponents-bundle.js.map +1 -0
  59. package/BookReaderDemo/BookReaderDemo.css +14 -1
  60. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  61. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  62. package/BookReaderDemo/IADemoBr.js +147 -0
  63. package/BookReaderDemo/demo-advanced.html +2 -2
  64. package/BookReaderDemo/demo-autoplay.html +2 -1
  65. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  66. package/BookReaderDemo/demo-fullscreen-mobile.html +2 -1
  67. package/BookReaderDemo/demo-fullscreen.html +2 -1
  68. package/BookReaderDemo/demo-iiif.html +2 -1
  69. package/BookReaderDemo/demo-internetarchive.html +84 -17
  70. package/BookReaderDemo/demo-multiple.html +2 -1
  71. package/BookReaderDemo/demo-preview-pages.html +2 -1
  72. package/BookReaderDemo/demo-simple.html +2 -1
  73. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -1
  74. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  75. package/BookReaderDemo/immersion-1up.html +2 -1
  76. package/BookReaderDemo/immersion-mode.html +2 -1
  77. package/BookReaderDemo/toggle_controls.html +2 -1
  78. package/BookReaderDemo/view_mode.html +2 -1
  79. package/BookReaderDemo/viewmode-cycle.html +2 -3
  80. package/CHANGELOG.md +244 -0
  81. package/README.md +14 -1
  82. package/babel.config.js +19 -0
  83. package/codecov.yml +6 -0
  84. package/index.html +3 -0
  85. package/jsconfig.json +19 -0
  86. package/netlify.toml +5 -0
  87. package/package.json +70 -59
  88. package/renovate.json +52 -0
  89. package/scripts/preversion.js +4 -1
  90. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  91. package/src/BookNavigator/assets/button-base.js +9 -2
  92. package/src/BookNavigator/assets/ia-logo.js +17 -0
  93. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  94. package/src/BookNavigator/assets/icon_close.js +1 -1
  95. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  96. package/src/BookNavigator/assets/icon_sort_desc.js +5 -0
  97. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  98. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  99. package/src/BookNavigator/book-navigator.js +585 -0
  100. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  101. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  102. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  103. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  104. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  105. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  106. package/src/BookNavigator/delete-modal-actions.js +1 -1
  107. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  108. package/src/BookNavigator/downloads/downloads.js +41 -25
  109. package/src/BookNavigator/search/search-provider.js +80 -28
  110. package/src/BookNavigator/search/search-results.js +34 -25
  111. package/src/BookNavigator/sharing.js +27 -0
  112. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  113. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  114. package/src/BookNavigator/volumes/volumes-provider.js +111 -0
  115. package/src/BookNavigator/volumes/volumes.js +188 -0
  116. package/src/BookReader/BookModel.js +59 -30
  117. package/src/BookReader/DebugConsole.js +3 -3
  118. package/src/BookReader/DragScrollable.js +233 -0
  119. package/src/BookReader/Mode1Up.js +56 -351
  120. package/src/BookReader/Mode1UpLit.js +391 -0
  121. package/src/BookReader/Mode2Up.js +73 -1318
  122. package/src/BookReader/Mode2UpLit.js +781 -0
  123. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  124. package/src/BookReader/ModeSmoothZoom.js +211 -0
  125. package/src/BookReader/ModeThumb.js +17 -11
  126. package/src/BookReader/Navbar/Navbar.js +10 -36
  127. package/src/BookReader/PageContainer.js +69 -6
  128. package/src/BookReader/ReduceSet.js +1 -1
  129. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  130. package/src/BookReader/events.js +2 -0
  131. package/src/BookReader/options.js +24 -2
  132. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  133. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  134. package/src/BookReader/utils.js +108 -13
  135. package/src/BookReader.js +481 -828
  136. package/src/assets/icons/close-circle-dark.svg +1 -0
  137. package/src/assets/icons/magnify-minus.svg +3 -7
  138. package/src/assets/icons/magnify-plus.svg +3 -7
  139. package/src/assets/icons/voice.svg +1 -0
  140. package/src/css/_BRBookmarks.scss +1 -1
  141. package/src/css/_BRComponent.scss +1 -1
  142. package/src/css/_BRmain.scss +33 -0
  143. package/src/css/_BRnav.scss +4 -26
  144. package/src/css/_BRpages.scss +147 -40
  145. package/src/css/_BRsearch.scss +25 -11
  146. package/src/css/_TextSelection.scss +16 -17
  147. package/src/css/_colorbox.scss +2 -2
  148. package/src/css/_controls.scss +17 -5
  149. package/src/css/_icons.scss +7 -1
  150. package/src/ia-bookreader/ia-bookreader.js +224 -0
  151. package/src/plugins/plugin.archive_analytics.js +3 -3
  152. package/src/plugins/plugin.autoplay.js +4 -9
  153. package/src/plugins/plugin.chapters.js +28 -35
  154. package/src/plugins/plugin.mobile_nav.js +11 -10
  155. package/src/plugins/plugin.resume.js +3 -3
  156. package/src/plugins/plugin.text_selection.js +32 -41
  157. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  158. package/src/plugins/search/plugin.search.js +179 -103
  159. package/src/plugins/search/view.js +59 -44
  160. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  161. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  162. package/src/plugins/tts/PageChunk.js +15 -21
  163. package/src/plugins/tts/PageChunkIterator.js +8 -12
  164. package/src/plugins/tts/WebTTSEngine.js +87 -71
  165. package/src/plugins/tts/plugin.tts.js +94 -125
  166. package/src/plugins/tts/utils.js +0 -25
  167. package/src/plugins/url/UrlPlugin.js +193 -0
  168. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  169. package/src/util/docCookies.js +21 -2
  170. package/tests/e2e/README.md +37 -0
  171. package/tests/e2e/autoplay.test.js +2 -2
  172. package/tests/e2e/base.test.js +7 -7
  173. package/tests/e2e/helpers/base.js +28 -23
  174. package/tests/e2e/helpers/debug.js +1 -1
  175. package/tests/e2e/helpers/desktopSearch.js +14 -13
  176. package/tests/e2e/helpers/mobileSearch.js +3 -3
  177. package/tests/e2e/helpers/params.js +17 -0
  178. package/tests/e2e/helpers/rightToLeft.js +4 -10
  179. package/tests/e2e/models/Navigation.js +13 -4
  180. package/tests/e2e/rightToLeft.test.js +4 -5
  181. package/tests/e2e/viewmode.test.js +40 -33
  182. package/tests/jest/BookNavigator/book-navigator.test.js +658 -0
  183. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  184. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  185. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  186. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  187. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  188. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  189. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  190. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +104 -60
  191. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  192. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  193. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  194. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  195. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +59 -14
  196. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  197. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  198. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  199. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  200. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  201. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  202. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  203. package/tests/jest/BookReader/ModeSmoothZoom.test.js +175 -0
  204. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  205. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  206. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  207. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  208. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  209. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  210. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  211. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  212. package/tests/jest/BookReader/utils.test.js +217 -0
  213. package/tests/jest/BookReader.keyboard.test.js +190 -0
  214. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  215. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  216. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  217. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  218. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +10 -11
  219. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  220. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  221. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  222. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  223. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  224. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +57 -47
  225. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +35 -6
  226. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  227. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  228. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  229. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  230. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  231. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  232. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  233. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  234. package/tests/jest/setup.js +3 -0
  235. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  236. package/tests/jest/util/docCookies.test.js +24 -0
  237. package/tests/{util → jest/util}/strings.test.js +1 -1
  238. package/tests/{utils.js → jest/utils.js} +38 -0
  239. package/webpack.config.js +11 -5
  240. package/.babelrc +0 -12
  241. package/.dependabot/config.yml +0 -6
  242. package/.testcaferc.json +0 -5
  243. package/BookReader/bookreader-component-bundle.js +0 -1450
  244. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  245. package/BookReader/bookreader-component-bundle.js.map +0 -1
  246. package/BookReader/jquery-1.10.1.js +0 -2
  247. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  248. package/BookReader/plugins/plugin.menu_toggle.js +0 -2
  249. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  250. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  251. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  252. package/karma.conf.js +0 -23
  253. package/src/BookNavigator/BookModel.js +0 -14
  254. package/src/BookNavigator/BookNavigator.js +0 -438
  255. package/src/BookNavigator/assets/book-loader.js +0 -27
  256. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  257. package/src/BookNavigator/search/a-search-result.js +0 -55
  258. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  259. package/src/ItemNavigator/ItemNavigator.js +0 -372
  260. package/src/ItemNavigator/providers/sharing.js +0 -29
  261. package/src/dragscrollable-br.js +0 -261
  262. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  263. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  264. package/tests/BookReader/Mode1Up.test.js +0 -164
  265. package/tests/BookReader/Mode2Up.test.js +0 -247
  266. package/tests/BookReader/utils.test.js +0 -109
  267. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  268. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  269. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  270. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  271. package/tests/util/docCookies.test.js +0 -15
@@ -0,0 +1,781 @@
1
+ // @ts-check
2
+ import { customElement, property, query } from 'lit/decorators.js';
3
+ import {LitElement, html} from 'lit';
4
+ import { styleMap } from 'lit/directives/style-map.js';
5
+ import { ModeSmoothZoom } from './ModeSmoothZoom';
6
+ import { arrChanged, promisifyEvent } from './utils';
7
+ import { HTMLDimensionsCacher } from "./utils/HTMLDimensionsCacher";
8
+ import { PageModel } from './BookModel';
9
+ import { ModeCoordinateSpace } from './ModeCoordinateSpace';
10
+ /** @typedef {import('./BookModel').BookModel} BookModel */
11
+ /** @typedef {import('./BookModel').PageIndex} PageIndex */
12
+ /** @typedef {import('./ModeSmoothZoom').SmoothZoomable} SmoothZoomable */
13
+ /** @typedef {import('./PageContainer').PageContainer} PageContainer */
14
+ /** @typedef {import('../BookReader').default} BookReader */
15
+
16
+ // I _have_ to make this globally public, otherwise it won't let me call
17
+ // its constructor :/
18
+ /** @implements {SmoothZoomable} */
19
+ @customElement('br-mode-2up')
20
+ export class Mode2UpLit extends LitElement {
21
+ /****************************************/
22
+ /************** PROPERTIES **************/
23
+ /****************************************/
24
+
25
+ /** @type {BookReader} */
26
+ br;
27
+
28
+ /************** BOOK-RELATED PROPERTIES **************/
29
+
30
+ /** @type {BookModel} */
31
+ @property({ type: Object })
32
+ book;
33
+
34
+ /************** SCALE-RELATED PROPERTIES **************/
35
+
36
+ /** @type {ModeCoordinateSpace} Manage conversion between coordinates */
37
+ coordSpace = new ModeCoordinateSpace(this);
38
+
39
+ @property({ type: Number })
40
+ scale = 1;
41
+
42
+ initialScale = 1;
43
+
44
+ /** Position (in unit-less, [0, 1] coordinates) in client to scale around */
45
+ @property({ type: Object })
46
+ scaleCenter = { x: 0.5, y: 0.5 };
47
+
48
+ /** @type {import('./options').AutoFitValues} */
49
+ @property({ type: String })
50
+ autoFit = 'auto';
51
+
52
+ /************** VIRTUAL-FLIPPING PROPERTIES **************/
53
+
54
+ /** ms for flip animation */
55
+ flipSpeed = 400;
56
+
57
+ @query('.br-mode-2up__leafs--flipping') $flippingEdges;
58
+
59
+ /** @type {PageModel[]} */
60
+ @property({ type: Array, hasChanged: arrChanged })
61
+ visiblePages = [];
62
+
63
+ /** @type {PageModel | null} */
64
+ get pageLeft() {
65
+ return this.visiblePages.find(p => p.pageSide == 'L');
66
+ }
67
+
68
+ /** @type {PageModel | null} */
69
+ get pageRight() {
70
+ return this.visiblePages.find(p => p.pageSide == 'R');
71
+ }
72
+
73
+ /** @type {PageModel[]} */
74
+ @property({ type: Array })
75
+ renderedPages = [];
76
+
77
+ /** @type {Record<PageIndex, PageContainer>} position in inches */
78
+ pageContainerCache = {};
79
+
80
+ /** @type {{ direction: 'left' | 'right', pagesFlipping: [PageIndex, PageIndex], pagesFlippingCount: number }} */
81
+ activeFlip = null;
82
+
83
+ /** @private cache this value */
84
+ _leftCoverWidth = 0;
85
+
86
+ /************** DOM-RELATED PROPERTIES **************/
87
+
88
+ /** @type {HTMLElement} */
89
+ get $container() { return this; }
90
+
91
+ /** @type {HTMLElement} */
92
+ get $visibleWorld() { return this.$book; }
93
+
94
+ /** @type {HTMLElement} */
95
+ @query('.br-mode-2up__book')
96
+ $book;
97
+
98
+ get positions() {
99
+ return this.computePositions(this.pageLeft, this.pageRight);
100
+ }
101
+
102
+ /** @param {PageModel} page */
103
+ computePageHeight(page) {
104
+ return this.book.getMedianPageSizeInches().height;
105
+ }
106
+
107
+ /** @param {PageModel} page */
108
+ computePageWidth(page) {
109
+ return page.widthInches * this.computePageHeight(page) / page.heightInches;
110
+ }
111
+
112
+ /**
113
+ * @param {PageModel | null} pageLeft
114
+ * @param {PageModel | null} pageRight
115
+ */
116
+ computePositions(pageLeft, pageRight) {
117
+ const computePageWidth = this.computePageWidth.bind(this);
118
+ const numLeafs = this.book.getNumLeafs();
119
+ const movingPagesWidth = this.activeFlip ? Math.ceil(this.activeFlip.pagesFlippingCount / 2) * this.PAGE_THICKNESS_IN : 0;
120
+ const leftPagesCount = this.book.pageProgression == 'lr' ? (pageLeft?.index ?? 0) : (!pageLeft ? 0 : numLeafs - pageLeft.index);
121
+
122
+ // Everything is relative to the gutter
123
+ const gutter = this._leftCoverWidth + leftPagesCount * this.PAGE_THICKNESS_IN;
124
+
125
+ const pageLeftEnd = gutter;
126
+ const pageLeftWidth = !pageLeft ? computePageWidth(pageRight.right) : computePageWidth(pageLeft);
127
+ const pageLeftStart = gutter - pageLeftWidth;
128
+
129
+ const leafEdgesLeftEnd = pageLeftStart; // leafEdgesLeftStart + leafEdgesLeftMainWidth + leafEdgesLeftMovingWidth;
130
+ const leafEdgesLeftMovingWidth = this.activeFlip?.direction != 'left' ? 0 : movingPagesWidth;
131
+ const leafEdgesLeftMainWidth = Math.ceil(leftPagesCount / 2) * this.PAGE_THICKNESS_IN - leafEdgesLeftMovingWidth;
132
+ const leafEdgesLeftFullWidth = leafEdgesLeftMovingWidth + leafEdgesLeftMainWidth;
133
+ const leafEdgesLeftMovingStart = leafEdgesLeftEnd - leafEdgesLeftMovingWidth;
134
+ const leafEdgesLeftStart = leafEdgesLeftMovingStart - leafEdgesLeftMainWidth;
135
+
136
+ const pageRightStart = gutter;
137
+ const pageRightWidth = !pageRight ? 0 : computePageWidth(pageRight);
138
+ const pageRightEnd = pageRightStart + pageRightWidth;
139
+
140
+ const rightPagesCount = this.book.pageProgression == 'lr' ? (!pageRight ? 0 : numLeafs - pageRight.index) : (pageRight?.index ?? 0);
141
+ const leafEdgesRightStart = pageRightEnd;
142
+ const leafEdgesRightMovingWidth = this.activeFlip?.direction != 'right' ? 0 : movingPagesWidth;
143
+ const leafEdgesRightMainStart = leafEdgesRightStart + leafEdgesRightMovingWidth;
144
+ const leafEdgesRightMainWidth = Math.ceil(rightPagesCount / 2) * this.PAGE_THICKNESS_IN - leafEdgesRightMovingWidth;
145
+ const leafEdgesRightEnd = leafEdgesRightStart + leafEdgesRightMainWidth + leafEdgesRightMovingWidth;
146
+ const leafEdgesRightFullWidth = leafEdgesRightMovingWidth + leafEdgesRightMainWidth;
147
+
148
+ const spreadWidth = pageRightEnd - pageLeftStart;
149
+ const bookWidth = leafEdgesRightEnd - leafEdgesLeftStart;
150
+ return {
151
+ leafEdgesLeftStart,
152
+ leafEdgesLeftMainWidth,
153
+ leafEdgesLeftMovingStart,
154
+ leafEdgesLeftMovingWidth,
155
+ leafEdgesLeftEnd,
156
+ leafEdgesLeftFullWidth,
157
+
158
+ pageLeftStart,
159
+ pageLeftWidth,
160
+ pageLeftEnd,
161
+
162
+ gutter,
163
+
164
+ pageRightStart,
165
+ pageRightWidth,
166
+ pageRightEnd,
167
+
168
+ leafEdgesRightStart,
169
+ leafEdgesRightMovingWidth,
170
+ leafEdgesRightMainStart,
171
+ leafEdgesRightMainWidth,
172
+ leafEdgesRightEnd,
173
+ leafEdgesRightFullWidth,
174
+
175
+ spreadWidth,
176
+ bookWidth,
177
+ };
178
+ }
179
+
180
+ /** @type {HTMLDimensionsCacher} Cache things like clientWidth to reduce repaints */
181
+ htmlDimensionsCacher = new HTMLDimensionsCacher(this);
182
+
183
+ smoothZoomer = new ModeSmoothZoom(this);
184
+
185
+ /************** CONSTANT PROPERTIES **************/
186
+
187
+ /** How much to zoom when zoom button pressed */
188
+ ZOOM_FACTOR = 1.1;
189
+
190
+ /** How thick a page is in the real world, as an estimate for the leafs */
191
+ PAGE_THICKNESS_IN = 0.002;
192
+
193
+ /****************************************/
194
+ /************** PUBLIC API **************/
195
+ /****************************************/
196
+
197
+ /************** MAIN PUBLIC METHODS **************/
198
+
199
+ /**
200
+ * @param {PageIndex} index
201
+ * TODO Remove smooth option from everywhere.
202
+ */
203
+ async jumpToIndex(index, { smooth = true } = {}) {
204
+ await this.flipAnimation(index, { animate: smooth });
205
+ }
206
+
207
+ zoomIn() {
208
+ this.scale *= this.ZOOM_FACTOR;
209
+ }
210
+
211
+ zoomOut() {
212
+ this.scale *= 1 / this.ZOOM_FACTOR;
213
+ }
214
+
215
+ /********************************************/
216
+ /************** INTERNAL STUFF **************/
217
+ /********************************************/
218
+
219
+ /************** LIFE CYCLE **************/
220
+
221
+ /**
222
+ * @param {BookModel} book
223
+ * @param {BookReader} br
224
+ */
225
+ constructor(book, br) {
226
+ super();
227
+ this.book = book;
228
+
229
+ /** @type {BookReader} */
230
+ this.br = br;
231
+ }
232
+
233
+ /** @override */
234
+ firstUpdated(changedProps) {
235
+ super.firstUpdated(changedProps);
236
+ this.htmlDimensionsCacher.updateClientSizes();
237
+ this.smoothZoomer.attach();
238
+ }
239
+
240
+ /** @override */
241
+ connectedCallback() {
242
+ super.connectedCallback();
243
+ this.htmlDimensionsCacher.attachResizeListener();
244
+ this.smoothZoomer.attach();
245
+ }
246
+
247
+ /** @override */
248
+ disconnectedCallback() {
249
+ this.htmlDimensionsCacher.detachResizeListener();
250
+ this.smoothZoomer.detach();
251
+ super.disconnectedCallback();
252
+ }
253
+
254
+ /** @override */
255
+ updated(changedProps) {
256
+ // this.X is the new value
257
+ // changedProps.get('X') is the old value
258
+ if (changedProps.has('book')) {
259
+ this._leftCoverWidth = this.computePageWidth(this.book.getPage(this.book.pageProgression == 'lr' ? 0 : -1));
260
+ }
261
+ if (changedProps.has('visiblePages')) {
262
+ this.renderedPages = this.computeRenderedPages();
263
+ this.br.displayedIndices = this.visiblePages.map(p => p.index);
264
+ this.br.updateFirstIndex(this.br.displayedIndices[0]);
265
+ this.br._components.navbar.updateNavIndexThrottled();
266
+ }
267
+ if (changedProps.has('autoFit')) {
268
+ if (this.autoFit != 'none') {
269
+ this.resizeViaAutofit();
270
+ }
271
+ }
272
+ if (changedProps.has('scale')) {
273
+ const oldVal = changedProps.get('scale');
274
+ this.recenter();
275
+ this.smoothZoomer.updateViewportOnZoom(this.scale, oldVal);
276
+ }
277
+ }
278
+
279
+ /************** LIT CONFIGS **************/
280
+
281
+ /** @override */
282
+ createRenderRoot() {
283
+ // Disable shadow DOM; that would require a huge rejiggering of CSS
284
+ return this;
285
+ }
286
+
287
+ /************** RENDERING **************/
288
+
289
+ /** @override */
290
+ render() {
291
+ return html`
292
+ <div class="br-mode-2up__book" @mouseup=${this.handlePageClick}>
293
+ ${this.renderLeafEdges('left')}
294
+ ${this.renderedPages.map(p => this.renderPage(p))}
295
+ ${this.renderLeafEdges('right')}
296
+ </div>`;
297
+ }
298
+
299
+ /** @param {PageModel} page */
300
+ createPageContainer = (page) => {
301
+ return this.pageContainerCache[page.index] || (
302
+ this.pageContainerCache[page.index] = (
303
+ // @ts-ignore I know it's protected, TS! But Mode2Up and BookReader are friends.
304
+ this.br._createPageContainer(page.index)
305
+ )
306
+ );
307
+ }
308
+
309
+ /**
310
+ * @param {PageIndex} startIndex
311
+ */
312
+ initFirstRender(startIndex) {
313
+ const page = this.book.getPage(startIndex);
314
+ const spread = page.spread;
315
+ this.visiblePages = (
316
+ this.book.pageProgression == 'lr' ? [spread.left, spread.right] : [spread.right, spread.left]
317
+ ).filter(p => p);
318
+ this.htmlDimensionsCacher.updateClientSizes();
319
+ this.resizeViaAutofit(page);
320
+ this.initialScale = this.scale;
321
+ }
322
+
323
+ /** @param {PageModel} page */
324
+ renderPage = (page) => {
325
+ const wToR = this.coordSpace.worldUnitsToRenderedPixels;
326
+ const wToV = this.coordSpace.worldUnitsToVisiblePixels;
327
+
328
+ const width = wToR(this.computePageWidth(page));
329
+ const height = wToR(this.computePageHeight(page));
330
+ const isVisible = this.visiblePages.map(p => p.index).includes(page.index);
331
+ const positions = this.computePositions(page.spread.left, page.spread.right);
332
+
333
+ const pageContainerEl = this.createPageContainer(page)
334
+ .update({
335
+ dimensions: {
336
+ width,
337
+ height,
338
+ top: 0,
339
+ left: wToR(page.pageSide == 'L' ? positions.pageLeftStart : positions.pageLeftEnd),
340
+ },
341
+ reduce: page.width / wToV(this.computePageWidth(page)),
342
+ }).$container[0];
343
+
344
+ pageContainerEl.classList.toggle('BRpage-visible', isVisible);
345
+ return pageContainerEl;
346
+ }
347
+
348
+ /**
349
+ * @param {'left' | 'right'} side
350
+ * Renders the current leaf edges, as well as any "moving" leaf edges,
351
+ * i.e. leaf edges that are currently being flipped. Uses a custom element
352
+ * to render br-leaf-edges.
353
+ **/
354
+ renderLeafEdges = (side) => {
355
+ if (!this.visiblePages.length) return html``;
356
+ const fullWidthIn = side == 'left' ? this.positions.leafEdgesLeftFullWidth : this.positions.leafEdgesRightFullWidth;
357
+ if (!fullWidthIn) return html``;
358
+
359
+ const wToR = this.coordSpace.worldUnitsToRenderedPixels;
360
+ const height = wToR(this.computePageHeight(this.visiblePages[0]));
361
+ const hasMovingPages = this.activeFlip?.direction == side;
362
+
363
+ const leftmostPage = this.book.getPage(this.book.pageProgression == 'lr' ? 0 : this.book.getNumLeafs() - 1);
364
+ const rightmostPage = this.book.getPage(this.book.pageProgression == 'lr' ? this.book.getNumLeafs() - 1 : 0);
365
+ const numPagesFlipping = hasMovingPages ? this.activeFlip.pagesFlippingCount : 0;
366
+ const range = side == 'left' ?
367
+ [leftmostPage.index, this.pageLeft.goLeft(numPagesFlipping).index] :
368
+ [this.pageRight.goRight(numPagesFlipping).index, rightmostPage.index];
369
+
370
+ const mainEdgesStyle = {
371
+ width: `${wToR(side == 'left' ? this.positions.leafEdgesLeftMainWidth : this.positions.leafEdgesRightMainWidth)}px`,
372
+ height: `${height}px`,
373
+ left: `${wToR(side == 'left' ? this.positions.leafEdgesLeftStart : this.positions.leafEdgesRightMainStart)}px`,
374
+ };
375
+ const mainEdges = html`
376
+ <br-leaf-edges
377
+ leftIndex=${range[0]}
378
+ rightIndex=${range[1]}
379
+ .book=${this.book}
380
+ .pageClickHandler=${(index) => this.br.jumpToIndex(index)}
381
+ side=${side}
382
+ class="br-mode-2up__leafs br-mode-2up__leafs--${side}"
383
+ style=${styleMap(mainEdgesStyle)}
384
+ ></br-leaf-edges>
385
+ `;
386
+
387
+ if (hasMovingPages) {
388
+ const width = wToR(side == 'left' ? this.positions.leafEdgesLeftMovingWidth : this.positions.leafEdgesRightMovingWidth);
389
+ const style = {
390
+ width: `${width}px`,
391
+ height: `${height}px`,
392
+ left: `${wToR(side == 'left' ? this.positions.leafEdgesLeftMovingStart : this.positions.leafEdgesRightStart)}px`,
393
+ pointerEvents: 'none',
394
+ transformOrigin: `${wToR(side == 'left' ? this.positions.pageLeftWidth : -this.positions.pageRightWidth) + width / 2}px 0`,
395
+ };
396
+
397
+ const movingEdges = html`
398
+ <br-leaf-edges
399
+ leftIndex=${this.activeFlip.pagesFlipping[0]}
400
+ rightIndex=${this.activeFlip.pagesFlipping[1]}
401
+ .book=${this.book}
402
+ side=${side}
403
+ class="br-mode-2up__leafs br-mode-2up__leafs--${side} br-mode-2up__leafs--flipping"
404
+ style=${styleMap(style)}
405
+ ></br-leaf-edges>
406
+ `;
407
+
408
+ return side == 'left' ? html`${mainEdges}${movingEdges}` : html`${movingEdges}${mainEdges}`;
409
+ } else {
410
+ return mainEdges;
411
+ }
412
+ }
413
+
414
+ resizeViaAutofit(page = this.visiblePages[0]) {
415
+ this.scale = this.computeScale(page, this.autoFit);
416
+ }
417
+
418
+ recenter(page = this.visiblePages[0]) {
419
+ const translate = this.computeTranslate(page, this.scale);
420
+ this.$book.style.transform = `translateX(${translate.x}px) translateY(${translate.y}px) scale(${this.scale})`;
421
+ }
422
+
423
+ /**
424
+ * @returns {PageModel[]}
425
+ */
426
+ computeRenderedPages() {
427
+ // Also render 2 pages before/after
428
+ // @ts-ignore TS doesn't understand the filtering out of null values
429
+ return [
430
+ this.visiblePages[0]?.prev?.prev,
431
+ this.visiblePages[0]?.prev,
432
+ ...this.visiblePages,
433
+ this.visiblePages[this.visiblePages.length - 1]?.next,
434
+ this.visiblePages[this.visiblePages.length - 1]?.next?.next,
435
+ ]
436
+ .filter(p => p)
437
+ // Never render more than 10 pages! Usually means something is wrong
438
+ .slice(0, 10);
439
+ }
440
+
441
+ /**
442
+ * @param {PageModel} page
443
+ * @param {import('./options').AutoFitValues} autoFit
444
+ */
445
+ computeScale(page, autoFit) {
446
+ if (!page) return 1;
447
+ const spread = page.spread;
448
+ // Default to real size if it fits, otherwise default to full height
449
+ const bookWidth = this.computePositions(spread.left, spread.right).bookWidth;
450
+ const bookHeight = this.computePageHeight(spread.left || spread.right);
451
+ const BOOK_PADDING_PX = 10;
452
+ const curScale = this.scale;
453
+ this.scale = 1; // Need this temporarily
454
+ const widthScale = this.coordSpace.renderedPixelsToWorldUnits(this.htmlDimensionsCacher.clientWidth - 2 * BOOK_PADDING_PX) / bookWidth;
455
+ const heightScale = this.coordSpace.renderedPixelsToWorldUnits(this.htmlDimensionsCacher.clientHeight - 2 * BOOK_PADDING_PX) / bookHeight;
456
+ this.scale = curScale;
457
+ const realScale = 1;
458
+
459
+ let scale = realScale;
460
+ if (autoFit == 'width') {
461
+ scale = Math.min(widthScale, 1);
462
+ } else if (autoFit == 'height') {
463
+ scale = Math.min(heightScale, 1);
464
+ } else if (autoFit == 'auto') {
465
+ scale = Math.min(widthScale, heightScale, 1);
466
+ } else if (autoFit == 'none') {
467
+ scale = this.scale;
468
+ } else {
469
+ // Should be impossible
470
+ throw new Error(`Invalid autoFit value: ${autoFit}`);
471
+ }
472
+
473
+ return scale;
474
+ }
475
+
476
+ /**
477
+ * @param {PageModel} page
478
+ * @param {number} scale
479
+ * @returns {{x: number, y: number}}
480
+ */
481
+ computeTranslate(page, scale = this.scale) {
482
+ if (!page) return { x: 0, y: 0 };
483
+ const spread = page.spread;
484
+ // Default to real size if it fits, otherwise default to full height
485
+ const positions = this.computePositions(spread.left, spread.right);
486
+ const bookWidth = positions.bookWidth;
487
+ const bookHeight = this.computePageHeight(spread.left || spread.right);
488
+ const visibleBookWidth = this.coordSpace.worldUnitsToRenderedPixels(bookWidth) * scale;
489
+ const visibleBookHeight = this.coordSpace.worldUnitsToRenderedPixels(bookHeight) * scale;
490
+ const leftOffset = this.coordSpace.worldUnitsToRenderedPixels(-positions.leafEdgesLeftStart) * scale;
491
+ const translateX = (this.htmlDimensionsCacher.clientWidth - visibleBookWidth) / 2 + leftOffset;
492
+ const translateY = (this.htmlDimensionsCacher.clientHeight - visibleBookHeight) / 2;
493
+ return { x: Math.max(leftOffset, translateX), y: Math.max(0, translateY) };
494
+ }
495
+
496
+ /************** VIRTUAL FLIPPING LOGIC **************/
497
+
498
+ /**
499
+ * @param {'left' | 'right' | 'next' | 'prev' | PageIndex | PageModel | {left: PageModel | null, right: PageModel | null}} nextSpread
500
+ */
501
+ async flipAnimation(nextSpread, { animate = true } = {}) {
502
+ const curSpread = (this.pageLeft || this.pageRight)?.spread;
503
+ if (!curSpread) {
504
+ // Nothings been actually rendered yet! Will be corrected during initFirstRender
505
+ return;
506
+ }
507
+
508
+ nextSpread = this.parseNextSpread(nextSpread);
509
+ if (this.activeFlip || !nextSpread) return;
510
+
511
+ const progression = this.book.pageProgression;
512
+ const curLeftIndex = curSpread.left?.index ?? (progression == 'lr' ? -1 : this.book.getNumLeafs());
513
+ const nextLeftIndex = nextSpread.left?.index ?? (progression == 'lr' ? -1 : this.book.getNumLeafs());
514
+ if (curLeftIndex == nextLeftIndex) return;
515
+
516
+ const renderedIndices = this.renderedPages.map(p => p.index);
517
+ /** @type {PageContainer[]} */
518
+ const nextPageContainers = [];
519
+ for (const page of [nextSpread.left, nextSpread.right]) {
520
+ if (!page) continue;
521
+ nextPageContainers.push(this.createPageContainer(page));
522
+ if (!renderedIndices.includes(page.index)) {
523
+ this.renderedPages.push(page);
524
+ }
525
+ }
526
+
527
+ const curTranslate = this.computeTranslate(curSpread.left || curSpread.right, this.scale);
528
+ const idealNextTranslate = this.computeTranslate(nextSpread.left || nextSpread.right, this.scale);
529
+ const translateDiff = Math.sqrt((idealNextTranslate.x - curTranslate.x) ** 2 + (idealNextTranslate.y - curTranslate.y) ** 2);
530
+ let nextTranslate = `translate(${idealNextTranslate.x}px, ${idealNextTranslate.y}px)`;
531
+ if (translateDiff < 50) {
532
+ const activeTranslate = this.$book.style.transform.match(/translate\([^)]+\)/)?.[0];
533
+ if (activeTranslate) {
534
+ nextTranslate = activeTranslate;
535
+ }
536
+ }
537
+ const newTransform = `${nextTranslate} scale(${this.scale})`;
538
+
539
+ if (animate && 'animate' in Element.prototype) {
540
+ // This table is used to determine the direction of the flip animation:
541
+ // | < | >
542
+ // lr | L | R
543
+ // rl | R | L
544
+ const direction = progression == 'lr' ? (nextLeftIndex > curLeftIndex ? 'right' : 'left') : (nextLeftIndex > curLeftIndex ? 'left' : 'right');
545
+
546
+ this.activeFlip = {
547
+ direction,
548
+ pagesFlipping: [curLeftIndex, nextLeftIndex],
549
+ pagesFlippingCount: Math.abs(nextLeftIndex - curLeftIndex),
550
+ };
551
+
552
+ this.classList.add(`br-mode-2up--flipping-${direction}`);
553
+ this.classList.add(`BRpageFlipping`);
554
+
555
+ // Wait for lit update cycle to finish so that entering pages are rendered
556
+ this.requestUpdate();
557
+ await this.updateComplete;
558
+
559
+ this.visiblePages
560
+ .map(p => this.pageContainerCache[p.index].$container)
561
+ .forEach($c => $c.addClass('BRpage-exiting'));
562
+
563
+ nextPageContainers.forEach(c => c.$container.addClass('BRpage-entering'));
564
+
565
+ /** @type {KeyframeAnimationOptions} */
566
+ const animationStyle = {
567
+ duration: this.flipSpeed + this.activeFlip.pagesFlippingCount,
568
+ easing: 'ease-in',
569
+ fill: 'none',
570
+ };
571
+
572
+ const bookCenteringAnimation = this.$book.animate([
573
+ { transform: newTransform },
574
+ ], animationStyle);
575
+
576
+ const edgeTranslationAnimation = this.$flippingEdges.animate([
577
+ { transform: `rotateY(0deg)` },
578
+ {
579
+ transform: direction == 'left' ? `rotateY(-180deg)` : `rotateY(180deg)`,
580
+ },
581
+ ], animationStyle);
582
+
583
+ const exitingPageAnimation = direction == 'left' ?
584
+ this.querySelector('.BRpage-exiting[data-side=L]').animate([
585
+ { transform: `rotateY(0deg)` },
586
+ { transform: `rotateY(180deg)` },
587
+ ], animationStyle) : this.querySelector('.BRpage-exiting[data-side=R]').animate([
588
+ { transform: `rotateY(0deg)` },
589
+ { transform: `rotateY(-180deg)` },
590
+ ], animationStyle);
591
+
592
+ const enteringPageAnimation = direction == 'left' ?
593
+ this.querySelector('.BRpage-entering[data-side=R]').animate([
594
+ { transform: `rotateY(-180deg)` },
595
+ { transform: `rotateY(0deg)` },
596
+ ], animationStyle) : this.querySelector('.BRpage-entering[data-side=L]').animate([
597
+ { transform: `rotateY(180deg)` },
598
+ { transform: `rotateY(0deg)` },
599
+ ], animationStyle);
600
+
601
+ bookCenteringAnimation.play();
602
+ edgeTranslationAnimation.play();
603
+ exitingPageAnimation.play();
604
+ enteringPageAnimation.play();
605
+
606
+ nextPageContainers.forEach(c => c.$container.addClass('BRpage-visible'));
607
+
608
+ await Promise.race([
609
+ promisifyEvent(bookCenteringAnimation, 'finish'),
610
+ promisifyEvent(edgeTranslationAnimation, 'finish'),
611
+ promisifyEvent(exitingPageAnimation, 'finish'),
612
+ promisifyEvent(enteringPageAnimation, 'finish'),
613
+ ]);
614
+
615
+ this.classList.remove(`br-mode-2up--flipping-${direction}`);
616
+ this.classList.remove(`BRpageFlipping`);
617
+
618
+ this.visiblePages
619
+ .map(p => this.pageContainerCache[p.index].$container)
620
+ .forEach($c => $c.removeClass('BRpage-exiting BRpage-visible'));
621
+ nextPageContainers.forEach(c => c.$container.removeClass('BRpage-entering'));
622
+ this.activeFlip = null;
623
+ }
624
+
625
+ this.$book.style.transform = newTransform;
626
+ this.visiblePages = (
627
+ progression == 'lr' ? [nextSpread.left, nextSpread.right] : [nextSpread.right, nextSpread.left]
628
+ ).filter(x => x);
629
+ }
630
+
631
+ /**
632
+ * @param {'left' | 'right' | 'next' | 'prev' | PageIndex | PageModel | {left: PageModel | null, right: PageModel | null}} nextSpread
633
+ * @returns {{left: PageModel | null, right: PageModel | null}}
634
+ */
635
+ parseNextSpread(nextSpread) {
636
+ if (nextSpread == 'next') {
637
+ nextSpread = this.book.pageProgression == 'lr' ? 'right' : 'left';
638
+ } else if (nextSpread == 'prev') {
639
+ nextSpread = this.book.pageProgression == 'lr' ? 'left' : 'right';
640
+ }
641
+
642
+ const curSpread = (this.pageLeft || this.pageRight).spread;
643
+
644
+ if (nextSpread == 'left') {
645
+ nextSpread = curSpread.left?.findLeft({ combineConsecutiveUnviewables: true })?.spread;
646
+ } else if (nextSpread == 'right') {
647
+ nextSpread = curSpread.right?.findRight({ combineConsecutiveUnviewables: true })?.spread;
648
+ }
649
+
650
+ if (typeof(nextSpread) == 'number') {
651
+ nextSpread = this.book.getPage(nextSpread).spread;
652
+ }
653
+
654
+ if (nextSpread instanceof PageModel) {
655
+ nextSpread = nextSpread.spread;
656
+ }
657
+
658
+ return nextSpread;
659
+ }
660
+
661
+ /************** INPUT HANDLERS **************/
662
+
663
+ /**
664
+ * @param {MouseEvent} ev
665
+ */
666
+ handlePageClick = (ev) => {
667
+ // right click
668
+ if (ev.which == 3 && this.br.protected) {
669
+ return false;
670
+ }
671
+
672
+ if (ev.which != 1) return;
673
+
674
+ const $page = $(ev.target).closest('.BRpagecontainer');
675
+ if (!$page.length) return;
676
+ if ($page.data('side') == 'L') {
677
+ this.flipAnimation('left');
678
+ } else if ($page.data('side') == 'R') {
679
+ this.flipAnimation('right');
680
+ }
681
+ }
682
+ }
683
+
684
+ @customElement('br-leaf-edges')
685
+ export class LeafEdges extends LitElement {
686
+ @property({ type: Number }) leftIndex = 0;
687
+ @property({ type: Number }) rightIndex = 0;
688
+ /** @type {'left' | 'right'} */
689
+ @property({ type: String }) side = 'left';
690
+
691
+ /** @type {BookModel} */
692
+ @property({attribute: false})
693
+ book = null;
694
+
695
+ /** @type {(index: PageIndex) => void} */
696
+ @property({attribute: false, type: Function})
697
+ pageClickHandler = null;
698
+
699
+ @query('.br-leaf-edges__bar') $hoverBar;
700
+ @query('.br-leaf-edges__label') $hoverLabel;
701
+
702
+ get pageWidthPercent() {
703
+ return 100 * 1 / (this.rightIndex - this.leftIndex + 1);
704
+ }
705
+
706
+ render() {
707
+ return html`
708
+ <div
709
+ class="br-leaf-edges__bar"
710
+ style="${styleMap({width: `${this.pageWidthPercent}%`})}"
711
+ ></div>
712
+ <div class="br-leaf-edges__label">Page</div>`;
713
+ }
714
+
715
+ connectedCallback() {
716
+ super.connectedCallback();
717
+ this.addEventListener('mouseenter', this.onMouseEnter);
718
+ this.addEventListener('mouseleave', this.onMouseLeave);
719
+ this.addEventListener('click', this.onClick);
720
+ }
721
+ disconnectedCallback() {
722
+ super.disconnectedCallback();
723
+ this.addEventListener('mouseenter', this.onMouseEnter);
724
+ this.removeEventListener('mousemove', this.onMouseMove);
725
+ this.removeEventListener('mouseleave', this.onMouseLeave);
726
+ }
727
+
728
+ /** @override */
729
+ createRenderRoot() {
730
+ // Disable shadow DOM; that would require a huge rejiggering of CSS
731
+ return this;
732
+ }
733
+
734
+ /**
735
+ * @param {MouseEvent} e
736
+ */
737
+ onMouseEnter = (e) => {
738
+ this.addEventListener('mousemove', this.onMouseMove);
739
+ this.$hoverBar.style.display = 'block';
740
+ this.$hoverLabel.style.display = 'block';
741
+ }
742
+
743
+ /**
744
+ * @param {MouseEvent} e
745
+ */
746
+ onMouseMove = (e) => {
747
+ this.$hoverBar.style.left = `${e.offsetX}px`;
748
+ if (this.side == 'right') {
749
+ this.$hoverLabel.style.left = `${e.offsetX}px`;
750
+ } else {
751
+ this.$hoverLabel.style.right = `${this.offsetWidth - e.offsetX}px`;
752
+ }
753
+ this.$hoverLabel.style.top = `${e.offsetY}px`;
754
+ const index = this.mouseEventToPageIndex(e);
755
+ this.$hoverLabel.textContent = this.book.getPageName(index);
756
+ }
757
+
758
+ /**
759
+ * @param {MouseEvent} e
760
+ */
761
+ onMouseLeave = (e) => {
762
+ this.removeEventListener('mousemove', this.onMouseMove);
763
+ this.$hoverBar.style.display = 'none';
764
+ this.$hoverLabel.style.display = 'none';
765
+ }
766
+
767
+ /**
768
+ * @param {MouseEvent} e
769
+ */
770
+ onClick = (e) => {
771
+ this.pageClickHandler(this.mouseEventToPageIndex(e));
772
+ }
773
+
774
+ /**
775
+ * @param {MouseEvent} e
776
+ * @returns {PageIndex}
777
+ */
778
+ mouseEventToPageIndex(e) {
779
+ return Math.floor(this.leftIndex + (e.offsetX / this.offsetWidth) * (this.rightIndex - this.leftIndex + 1));
780
+ }
781
+ }