@internetarchive/bookreader 5.0.0-8 → 5.0.0-80

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