@internetarchive/bookreader 5.0.0-6-multiple-files → 5.0.0-60

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.
Files changed (303) hide show
  1. package/.eslintrc.js +17 -15
  2. package/.github/workflows/node.js.yml +77 -6
  3. package/.github/workflows/npm-publish.yml +6 -20
  4. package/.testcaferc.js +10 -0
  5. package/BookReader/BookReader.css +241 -377
  6. package/BookReader/BookReader.js +2 -21564
  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/1up.svg +1 -12
  13. package/BookReader/icons/2up.svg +1 -15
  14. package/BookReader/icons/advance.svg +3 -26
  15. package/BookReader/icons/chevron-right.svg +1 -1
  16. package/BookReader/icons/close-circle-dark.svg +1 -0
  17. package/BookReader/icons/close-circle.svg +1 -1
  18. package/BookReader/icons/fullscreen.svg +1 -17
  19. package/BookReader/icons/fullscreen_exit.svg +1 -17
  20. package/BookReader/icons/hamburger.svg +1 -15
  21. package/BookReader/icons/left-arrow.svg +1 -12
  22. package/BookReader/icons/magnify-minus.svg +1 -16
  23. package/BookReader/icons/magnify-plus.svg +1 -17
  24. package/BookReader/icons/magnify.svg +1 -15
  25. package/BookReader/icons/pause.svg +1 -23
  26. package/BookReader/icons/play.svg +1 -22
  27. package/BookReader/icons/playback-speed.svg +1 -34
  28. package/BookReader/icons/read-aloud.svg +1 -22
  29. package/BookReader/icons/review.svg +3 -22
  30. package/BookReader/icons/thumbnails.svg +1 -17
  31. package/BookReader/icons/voice.svg +1 -0
  32. package/BookReader/icons/volume-full.svg +1 -22
  33. package/BookReader/images/BRicons.svg +5 -94
  34. package/BookReader/images/books_graphic.svg +1 -177
  35. package/BookReader/images/icon_book.svg +1 -12
  36. package/BookReader/images/icon_bookmark.svg +1 -12
  37. package/BookReader/images/icon_gear.svg +1 -14
  38. package/BookReader/images/icon_hamburger.svg +1 -20
  39. package/BookReader/images/icon_home.svg +1 -21
  40. package/BookReader/images/icon_info.svg +1 -11
  41. package/BookReader/images/icon_one_page.svg +1 -8
  42. package/BookReader/images/icon_pause.svg +1 -1
  43. package/BookReader/images/icon_play.svg +1 -1
  44. package/BookReader/images/icon_playback-rate.svg +1 -15
  45. package/BookReader/images/icon_search_button.svg +1 -8
  46. package/BookReader/images/icon_share.svg +1 -9
  47. package/BookReader/images/icon_skip-ahead.svg +1 -6
  48. package/BookReader/images/icon_skip-back.svg +2 -13
  49. package/BookReader/images/icon_speaker.svg +1 -18
  50. package/BookReader/images/icon_speaker_open.svg +1 -10
  51. package/BookReader/images/icon_thumbnails.svg +1 -12
  52. package/BookReader/images/icon_toc.svg +1 -5
  53. package/BookReader/images/icon_two_pages.svg +1 -9
  54. package/BookReader/images/marker_chap-off.svg +1 -11
  55. package/BookReader/images/marker_chap-on.svg +1 -11
  56. package/BookReader/images/marker_srch-on.svg +1 -11
  57. package/BookReader/jquery-3.js +2 -0
  58. package/BookReader/jquery-3.js.LICENSE.txt +24 -0
  59. package/BookReader/plugins/plugin.archive_analytics.js +1 -172
  60. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  61. package/BookReader/plugins/plugin.autoplay.js +1 -165
  62. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  63. package/BookReader/plugins/plugin.chapters.js +1 -304
  64. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  65. package/BookReader/plugins/plugin.iframe.js +1 -74
  66. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  67. package/BookReader/plugins/plugin.mobile_nav.js +1 -334
  68. package/BookReader/plugins/plugin.mobile_nav.js.map +1 -1
  69. package/BookReader/plugins/plugin.resume.js +1 -368
  70. package/BookReader/plugins/plugin.resume.js.map +1 -1
  71. package/BookReader/plugins/plugin.search.js +1 -1420
  72. package/BookReader/plugins/plugin.search.js.map +1 -1
  73. package/BookReader/plugins/plugin.text_selection.js +1 -1080
  74. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  75. package/BookReader/plugins/plugin.tts.js +2 -9193
  76. package/BookReader/plugins/plugin.tts.js.map +1 -1
  77. package/BookReader/plugins/plugin.url.js +1 -269
  78. package/BookReader/plugins/plugin.url.js.map +1 -1
  79. package/BookReader/plugins/plugin.vendor-fullscreen.js +1 -379
  80. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  81. package/BookReader/webcomponents-bundle.js +3 -0
  82. package/BookReader/webcomponents-bundle.js.LICENSE.txt +9 -0
  83. package/BookReader/webcomponents-bundle.js.map +1 -0
  84. package/BookReaderDemo/BookReaderDemo.css +14 -1
  85. package/BookReaderDemo/BookReaderJSAutoplay.js +4 -1
  86. package/BookReaderDemo/BookReaderJSSimple.js +1 -0
  87. package/BookReaderDemo/IADemoBr.js +147 -0
  88. package/BookReaderDemo/demo-advanced.html +2 -2
  89. package/BookReaderDemo/demo-autoplay.html +2 -1
  90. package/BookReaderDemo/demo-embed-iframe-src.html +2 -1
  91. package/BookReaderDemo/demo-fullscreen-mobile.html +2 -1
  92. package/BookReaderDemo/demo-fullscreen.html +2 -1
  93. package/BookReaderDemo/demo-iiif.html +2 -1
  94. package/BookReaderDemo/demo-internetarchive.html +84 -17
  95. package/BookReaderDemo/demo-multiple.html +2 -1
  96. package/BookReaderDemo/demo-preview-pages.html +2 -1
  97. package/BookReaderDemo/demo-simple.html +2 -1
  98. package/BookReaderDemo/demo-vendor-fullscreen.html +2 -1
  99. package/BookReaderDemo/ia-multiple-volumes-manifest.js +170 -0
  100. package/BookReaderDemo/immersion-1up.html +2 -1
  101. package/BookReaderDemo/immersion-mode.html +2 -1
  102. package/BookReaderDemo/toggle_controls.html +2 -1
  103. package/BookReaderDemo/view_mode.html +2 -1
  104. package/BookReaderDemo/viewmode-cycle.html +2 -3
  105. package/CHANGELOG.md +246 -0
  106. package/README.md +14 -1
  107. package/babel.config.js +19 -0
  108. package/codecov.yml +6 -0
  109. package/index.html +3 -0
  110. package/jsconfig.json +19 -0
  111. package/netlify.toml +5 -0
  112. package/package.json +70 -59
  113. package/renovate.json +52 -0
  114. package/scripts/preversion.js +4 -1
  115. package/src/BookNavigator/assets/bookmark-colors.js +1 -1
  116. package/src/BookNavigator/assets/button-base.js +9 -2
  117. package/src/BookNavigator/assets/ia-logo.js +17 -0
  118. package/src/BookNavigator/assets/icon_checkmark.js +1 -1
  119. package/src/BookNavigator/assets/icon_close.js +1 -1
  120. package/src/BookNavigator/assets/icon_sort_asc.js +5 -0
  121. package/src/BookNavigator/assets/{icon_sort_ascending.js → icon_sort_desc.js} +2 -2
  122. package/src/BookNavigator/assets/icon_sort_neutral.js +5 -0
  123. package/src/BookNavigator/assets/icon_volumes.js +11 -0
  124. package/src/BookNavigator/book-navigator.js +585 -0
  125. package/src/BookNavigator/bookmarks/bookmark-button.js +3 -2
  126. package/src/BookNavigator/bookmarks/bookmark-edit.js +3 -4
  127. package/src/BookNavigator/bookmarks/bookmarks-list.js +2 -3
  128. package/src/BookNavigator/bookmarks/bookmarks-loginCTA.js +3 -8
  129. package/src/BookNavigator/bookmarks/bookmarks-provider.js +27 -17
  130. package/src/BookNavigator/bookmarks/ia-bookmarks.js +116 -67
  131. package/src/BookNavigator/delete-modal-actions.js +1 -1
  132. package/src/BookNavigator/downloads/downloads-provider.js +36 -21
  133. package/src/BookNavigator/downloads/downloads.js +41 -25
  134. package/src/BookNavigator/search/search-provider.js +80 -28
  135. package/src/BookNavigator/search/search-results.js +34 -25
  136. package/src/BookNavigator/sharing.js +27 -0
  137. package/src/BookNavigator/visual-adjustments/visual-adjustments-provider.js +11 -10
  138. package/src/BookNavigator/visual-adjustments/visual-adjustments.js +3 -3
  139. package/src/BookNavigator/volumes/volumes-provider.js +88 -53
  140. package/src/BookNavigator/volumes/volumes.js +41 -14
  141. package/src/BookReader/BookModel.js +59 -30
  142. package/src/BookReader/DebugConsole.js +3 -3
  143. package/src/BookReader/DragScrollable.js +233 -0
  144. package/src/BookReader/Mode1Up.js +56 -351
  145. package/src/BookReader/Mode1UpLit.js +391 -0
  146. package/src/BookReader/Mode2Up.js +73 -1318
  147. package/src/BookReader/Mode2UpLit.js +781 -0
  148. package/src/BookReader/ModeCoordinateSpace.js +29 -0
  149. package/src/BookReader/ModeSmoothZoom.js +211 -0
  150. package/src/BookReader/ModeThumb.js +17 -11
  151. package/src/BookReader/Navbar/Navbar.js +10 -36
  152. package/src/BookReader/PageContainer.js +69 -6
  153. package/src/BookReader/ReduceSet.js +1 -1
  154. package/src/BookReader/Toolbar/Toolbar.js +10 -37
  155. package/src/BookReader/events.js +2 -0
  156. package/src/BookReader/options.js +24 -2
  157. package/src/BookReader/utils/HTMLDimensionsCacher.js +44 -0
  158. package/src/BookReader/utils/ScrollClassAdder.js +31 -0
  159. package/src/BookReader/utils.js +108 -13
  160. package/src/BookReader.js +480 -825
  161. package/src/assets/icons/close-circle-dark.svg +1 -0
  162. package/src/assets/icons/magnify-minus.svg +3 -7
  163. package/src/assets/icons/magnify-plus.svg +3 -7
  164. package/src/assets/icons/voice.svg +1 -0
  165. package/src/css/BookReader.scss +0 -12
  166. package/src/css/_BRBookmarks.scss +1 -1
  167. package/src/css/_BRComponent.scss +1 -1
  168. package/src/css/_BRmain.scss +33 -24
  169. package/src/css/_BRnav.scss +4 -26
  170. package/src/css/_BRpages.scss +147 -40
  171. package/src/css/_BRsearch.scss +25 -216
  172. package/src/css/_TextSelection.scss +16 -17
  173. package/src/css/_colorbox.scss +2 -2
  174. package/src/css/_controls.scss +17 -5
  175. package/src/css/_icons.scss +7 -1
  176. package/src/ia-bookreader/ia-bookreader.js +224 -0
  177. package/src/plugins/plugin.archive_analytics.js +3 -3
  178. package/src/plugins/plugin.autoplay.js +4 -9
  179. package/src/plugins/plugin.chapters.js +28 -35
  180. package/src/plugins/plugin.mobile_nav.js +11 -10
  181. package/src/plugins/plugin.resume.js +3 -3
  182. package/src/plugins/plugin.text_selection.js +32 -41
  183. package/src/plugins/plugin.vendor-fullscreen.js +4 -4
  184. package/src/plugins/search/plugin.search.js +179 -116
  185. package/src/plugins/search/view.js +63 -179
  186. package/src/plugins/tts/AbstractTTSEngine.js +46 -37
  187. package/src/plugins/tts/FestivalTTSEngine.js +13 -14
  188. package/src/plugins/tts/PageChunk.js +15 -21
  189. package/src/plugins/tts/PageChunkIterator.js +8 -12
  190. package/src/plugins/tts/WebTTSEngine.js +87 -71
  191. package/src/plugins/tts/plugin.tts.js +94 -125
  192. package/src/plugins/tts/utils.js +0 -25
  193. package/src/plugins/url/UrlPlugin.js +193 -0
  194. package/src/plugins/{plugin.url.js → url/plugin.url.js} +45 -16
  195. package/src/util/docCookies.js +21 -2
  196. package/tests/e2e/README.md +37 -0
  197. package/tests/e2e/autoplay.test.js +2 -2
  198. package/tests/e2e/base.test.js +7 -7
  199. package/tests/e2e/helpers/base.js +28 -23
  200. package/tests/e2e/helpers/debug.js +1 -1
  201. package/tests/e2e/helpers/desktopSearch.js +14 -13
  202. package/tests/e2e/helpers/mobileSearch.js +3 -3
  203. package/tests/e2e/helpers/params.js +17 -0
  204. package/tests/e2e/helpers/rightToLeft.js +4 -10
  205. package/tests/e2e/models/Navigation.js +13 -4
  206. package/tests/e2e/rightToLeft.test.js +4 -5
  207. package/tests/e2e/viewmode.test.js +40 -33
  208. package/tests/jest/BookNavigator/book-navigator.test.js +658 -0
  209. package/tests/jest/BookNavigator/bookmarks/bookmark-button.test.js +43 -0
  210. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmark-edit.test.js +25 -26
  211. package/tests/{karma → jest}/BookNavigator/bookmarks/bookmarks-list.test.js +41 -42
  212. package/tests/jest/BookNavigator/bookmarks/ia-bookmarks.test.js +45 -0
  213. package/tests/jest/BookNavigator/downloads/downloads-provider.test.js +67 -0
  214. package/tests/jest/BookNavigator/downloads/downloads.test.js +53 -0
  215. package/tests/jest/BookNavigator/search/search-provider.test.js +167 -0
  216. package/tests/{karma/BookNavigator → jest/BookNavigator/search}/search-results.test.js +104 -60
  217. package/tests/jest/BookNavigator/sharing/sharing-provider.test.js +49 -0
  218. package/tests/jest/BookNavigator/visual-adjustments.test.js +200 -0
  219. package/tests/jest/BookNavigator/volumes/volumes-provider.test.js +184 -0
  220. package/tests/jest/BookNavigator/volumes/volumes.test.js +97 -0
  221. package/tests/{BookReader → jest/BookReader}/BookModel.test.js +59 -14
  222. package/tests/jest/BookReader/BookReaderPublicFunctions.test.js +193 -0
  223. package/tests/{BookReader → jest/BookReader}/DebugConsole.test.js +1 -1
  224. package/tests/{BookReader → jest/BookReader}/ImageCache.test.js +4 -4
  225. package/tests/jest/BookReader/Mode1UpLit.test.js +73 -0
  226. package/tests/jest/BookReader/Mode2Up.test.js +98 -0
  227. package/tests/jest/BookReader/Mode2UpLit.test.js +190 -0
  228. package/tests/jest/BookReader/ModeCoordinateSpace.test.js +16 -0
  229. package/tests/jest/BookReader/ModeSmoothZoom.test.js +175 -0
  230. package/tests/jest/BookReader/ModeThumb.test.js +71 -0
  231. package/tests/{BookReader → jest/BookReader}/Navbar/Navbar.test.js +10 -10
  232. package/tests/{BookReader → jest/BookReader}/PageContainer.test.js +88 -6
  233. package/tests/{BookReader → jest/BookReader}/ReduceSet.test.js +1 -1
  234. package/tests/{BookReader → jest/BookReader}/Toolbar/Toolbar.test.js +2 -2
  235. package/tests/jest/BookReader/utils/HTMLDimensionsCacher.test.js +59 -0
  236. package/tests/jest/BookReader/utils/ScrollClassAdder.test.js +49 -0
  237. package/tests/{BookReader → jest/BookReader}/utils/classes.test.js +1 -1
  238. package/tests/jest/BookReader/utils.test.js +217 -0
  239. package/tests/jest/BookReader.keyboard.test.js +190 -0
  240. package/tests/{BookReader.options.test.js → jest/BookReader.options.test.js} +9 -1
  241. package/tests/{BookReader.test.js → jest/BookReader.test.js} +26 -37
  242. package/tests/{plugins → jest/plugins}/plugin.archive_analytics.test.js +2 -2
  243. package/tests/{plugins → jest/plugins}/plugin.autoplay.test.js +4 -4
  244. package/tests/{plugins → jest/plugins}/plugin.chapters.test.js +10 -11
  245. package/tests/{plugins → jest/plugins}/plugin.iframe.test.js +2 -2
  246. package/tests/{plugins → jest/plugins}/plugin.mobile_nav.test.js +5 -5
  247. package/tests/{plugins → jest/plugins}/plugin.resume.test.js +3 -3
  248. package/tests/{plugins → jest/plugins}/plugin.text_selection.test.js +39 -47
  249. package/tests/{plugins → jest/plugins}/plugin.vendor-fullscreen.test.js +2 -2
  250. package/tests/{plugins → jest/plugins}/search/plugin.search.test.js +63 -47
  251. package/tests/{plugins → jest/plugins}/search/plugin.search.view.test.js +35 -6
  252. package/tests/{plugins → jest/plugins}/tts/AbstractTTSEngine.test.js +9 -9
  253. package/tests/{plugins → jest/plugins}/tts/FestivalTTSEngine.test.js +4 -4
  254. package/tests/{plugins → jest/plugins}/tts/PageChunk.test.js +1 -1
  255. package/tests/{plugins → jest/plugins}/tts/PageChunkIterator.test.js +3 -3
  256. package/tests/{plugins → jest/plugins}/tts/WebTTSEngine.test.js +47 -1
  257. package/tests/{plugins → jest/plugins}/tts/utils.test.js +1 -60
  258. package/tests/jest/plugins/url/UrlPlugin.test.js +190 -0
  259. package/tests/{plugins → jest/plugins/url}/plugin.url.test.js +53 -14
  260. package/tests/jest/setup.js +3 -0
  261. package/tests/{util → jest/util}/browserSniffing.test.js +1 -1
  262. package/tests/jest/util/docCookies.test.js +24 -0
  263. package/tests/{util → jest/util}/strings.test.js +1 -1
  264. package/tests/{utils.js → jest/utils.js} +38 -0
  265. package/webpack.config.js +11 -5
  266. package/.babelrc +0 -12
  267. package/.dependabot/config.yml +0 -6
  268. package/.testcaferc.json +0 -5
  269. package/BookReader/bookreader-component-bundle.js +0 -14312
  270. package/BookReader/bookreader-component-bundle.js.LICENSE.txt +0 -38
  271. package/BookReader/bookreader-component-bundle.js.map +0 -1
  272. package/BookReader/icons/sort-ascending.svg +0 -1
  273. package/BookReader/icons/sort-descending.svg +0 -1
  274. package/BookReader/jquery-1.10.1.js +0 -108
  275. package/BookReader/jquery-1.10.1.js.LICENSE.txt +0 -24
  276. package/BookReader/plugins/plugin.menu_toggle.js +0 -369
  277. package/BookReader/plugins/plugin.menu_toggle.js.map +0 -1
  278. package/BookReaderDemo/bookreader-template-bundle.js +0 -7178
  279. package/BookReaderDemo/demo-plugin-menu-toggle.html +0 -34
  280. package/karma.conf.js +0 -23
  281. package/src/BookNavigator/BookModel.js +0 -14
  282. package/src/BookNavigator/BookNavigator.js +0 -452
  283. package/src/BookNavigator/assets/book-loader.js +0 -27
  284. package/src/BookNavigator/assets/icon_sort_descending.js +0 -5
  285. package/src/BookNavigator/br-fullscreen-mgr.js +0 -83
  286. package/src/BookNavigator/search/a-search-result.js +0 -55
  287. package/src/BookReaderComponent/BookReaderComponent.js +0 -112
  288. package/src/ItemNavigator/ItemNavigator.js +0 -372
  289. package/src/ItemNavigator/providers/sharing.js +0 -29
  290. package/src/assets/icons/sort-ascending.svg +0 -1
  291. package/src/assets/icons/sort-descending.svg +0 -1
  292. package/src/dragscrollable-br.js +0 -261
  293. package/src/plugins/menu_toggle/plugin.menu_toggle.js +0 -324
  294. package/tests/BookReader/BookReaderPublicFunctions.test.js +0 -171
  295. package/tests/BookReader/Mode1Up.test.js +0 -164
  296. package/tests/BookReader/Mode2Up.test.js +0 -247
  297. package/tests/BookReader/utils.test.js +0 -109
  298. package/tests/e2e/ia-production/ia-prod-base.js +0 -17
  299. package/tests/karma/BookNavigator/book-navigator.test.js +0 -132
  300. package/tests/karma/BookNavigator/visual-adjustments.test.js +0 -201
  301. package/tests/karma/BookNavigator/volumes.test.js +0 -101
  302. package/tests/plugins/menu_toggle/plugin.menu_toggle.test.js +0 -68
  303. 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
+ }