@internetarchive/bookreader 5.0.0-114 → 5.0.0-115

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 (35) hide show
  1. package/BookReader/474.js.map +1 -1
  2. package/BookReader/BookReader.css +27 -10
  3. package/BookReader/BookReader.js +1 -1
  4. package/BookReader/BookReader.js.map +1 -1
  5. package/BookReader/ia-bookreader-bundle.js +20 -38
  6. package/BookReader/ia-bookreader-bundle.js.map +1 -1
  7. package/BookReader/plugins/plugin.archive_analytics.js.map +1 -1
  8. package/BookReader/plugins/plugin.autoplay.js.map +1 -1
  9. package/BookReader/plugins/plugin.chapters.js.map +1 -1
  10. package/BookReader/plugins/plugin.experiments.js.map +1 -1
  11. package/BookReader/plugins/plugin.iframe.js +1 -1
  12. package/BookReader/plugins/plugin.iframe.js.map +1 -1
  13. package/BookReader/plugins/plugin.iiif.js.map +1 -1
  14. package/BookReader/plugins/plugin.resume.js.map +1 -1
  15. package/BookReader/plugins/plugin.search.js +1 -1
  16. package/BookReader/plugins/plugin.search.js.map +1 -1
  17. package/BookReader/plugins/plugin.text_selection.js +1 -1
  18. package/BookReader/plugins/plugin.text_selection.js.map +1 -1
  19. package/BookReader/plugins/plugin.translate.js +1 -1
  20. package/BookReader/plugins/plugin.translate.js.map +1 -1
  21. package/BookReader/plugins/plugin.tts.js +1 -1
  22. package/BookReader/plugins/plugin.tts.js.map +1 -1
  23. package/BookReader/plugins/plugin.url.js.map +1 -1
  24. package/BookReader/plugins/plugin.vendor-fullscreen.js.map +1 -1
  25. package/BookReader/plugins/translator-worker.js.map +1 -1
  26. package/BookReader/webcomponents-bundle.js.map +1 -1
  27. package/package.json +37 -31
  28. package/src/BookReader/utils/SelectionObserver.js +4 -2
  29. package/src/BookReader.js +2 -1
  30. package/src/css/_TextSelection.scss +25 -10
  31. package/src/plugins/plugin.iiif.js +7 -1
  32. package/src/plugins/tts/WebTTSEngine.js +2 -2
  33. package/src/plugins/tts/utils.js +0 -9
  34. package/src/util/TextSelectionManager.js +78 -27
  35. package/src/util/browserSniffing.js +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/bookreader",
3
- "version": "5.0.0-114",
3
+ "version": "5.0.0-115",
4
4
  "description": "The Internet Archive BookReader.",
5
5
  "type": "module",
6
6
  "files": [
@@ -32,21 +32,21 @@
32
32
  "private": false,
33
33
  "dependencies": {
34
34
  "@internetarchive/bergamot-translator": "^0.4.9-ia.1",
35
- "@internetarchive/ia-activity-indicator": "0.0.6",
36
- "@internetarchive/ia-item-navigator": "2.2.2",
37
- "@internetarchive/icon-bookmark": "^1.4.0",
38
- "@internetarchive/icon-close": "1.4.0",
39
- "@internetarchive/icon-dl": "^1.4.0",
40
- "@internetarchive/icon-edit-pencil": "^1.4.1",
41
- "@internetarchive/icon-ia-logo": "1.4.0",
42
- "@internetarchive/icon-magnify-minus": "^1.4.0",
43
- "@internetarchive/icon-magnify-plus": "^1.4.0",
44
- "@internetarchive/icon-search": "^1.4.0",
45
- "@internetarchive/icon-share": "1.4.0",
46
- "@internetarchive/icon-toc": "^1.4.0",
47
- "@internetarchive/icon-visual-adjustment": "^1.4.0",
48
- "@internetarchive/modal-manager": "2.0.5",
49
- "@internetarchive/shared-resize-observer": "^0.2.0",
35
+ "@internetarchive/ia-activity-indicator": "1.0.0",
36
+ "@internetarchive/ia-item-navigator": "2.2.3",
37
+ "@internetarchive/icon-bookmark": "1.4.1",
38
+ "@internetarchive/icon-close": "1.4.1",
39
+ "@internetarchive/icon-dl": "1.4.1",
40
+ "@internetarchive/icon-edit-pencil": "1.4.1",
41
+ "@internetarchive/icon-ia-logo": "1.4.1",
42
+ "@internetarchive/icon-magnify-minus": "1.4.1",
43
+ "@internetarchive/icon-magnify-plus": "1.4.1",
44
+ "@internetarchive/icon-search": "1.4.1",
45
+ "@internetarchive/icon-share": "1.4.1",
46
+ "@internetarchive/icon-toc": "1.4.1",
47
+ "@internetarchive/icon-visual-adjustment": "1.4.1",
48
+ "@internetarchive/modal-manager": "2.0.6",
49
+ "@internetarchive/shared-resize-observer": "0.2.0",
50
50
  "interactjs": "^1.10.18",
51
51
  "iso-language-codes": "2.0.0",
52
52
  "jquery": "3.6.1",
@@ -58,36 +58,36 @@
58
58
  "soundmanager2": "2.97.20170602"
59
59
  },
60
60
  "devDependencies": {
61
- "@babel/core": "7.29.0",
62
- "@babel/eslint-parser": "7.28.6",
61
+ "@babel/core": "7.29.7",
62
+ "@babel/eslint-parser": "7.29.7",
63
63
  "@babel/plugin-proposal-class-properties": "7.18.6",
64
- "@babel/plugin-proposal-decorators": "7.29.0",
65
- "@babel/preset-env": "7.29.2",
66
- "@iiif/presentation-2": "^1.0.4",
67
- "@iiif/presentation-3": "^2.1.3",
64
+ "@babel/plugin-proposal-decorators": "7.29.7",
65
+ "@babel/preset-env": "7.29.7",
66
+ "@iiif/presentation-2": "1.0.4",
67
+ "@iiif/presentation-3": "2.2.3",
68
68
  "@open-wc/testing-helpers": "3.0.1",
69
69
  "@types/jest": "30.0.0",
70
70
  "@webcomponents/webcomponentsjs": "^2.6.0",
71
71
  "babel-loader": "10.1.1",
72
- "concurrently": "9.0.1",
72
+ "concurrently": "10.0.3",
73
73
  "core-js": "3.49.0",
74
- "cpx2": "8.0.0",
74
+ "cpx2": "9.0.0",
75
75
  "eslint": "7.32.0",
76
76
  "eslint-plugin-no-jquery": "^2.7.0",
77
77
  "eslint-plugin-testcafe": "0.2.1",
78
78
  "http-server": "14.1.1",
79
79
  "hypothesis": "^1.1627.0",
80
- "jest": "30.3.0",
81
- "jest-environment-jsdom": "30.3.0",
80
+ "jest": "30.4.2",
81
+ "jest-environment-jsdom": "30.4.1",
82
82
  "live-server": "1.2.2",
83
83
  "regenerator-runtime": "0.14.1",
84
- "sass": "1.98.0",
85
- "sinon": "21.0.3",
84
+ "sass": "1.100.0",
85
+ "sinon": "22.0.0",
86
86
  "svgo": "4.0.1",
87
87
  "testcafe": "3.7.4",
88
88
  "testcafe-browser-provider-browserstack": "1.15.2",
89
- "webpack": "5.105.4",
90
- "webpack-cli": "7.0.2"
89
+ "webpack": "5.107.2",
90
+ "webpack-cli": "7.0.3"
91
91
  },
92
92
  "jest": {
93
93
  "testEnvironment": "jsdom",
@@ -133,7 +133,13 @@
133
133
  "test:e2e:dev": "npx testcafe --live --dev",
134
134
  "DOCS:update:test-deps": "If CI succeeds, these should be good to update",
135
135
  "update:test-deps": "npm i @babel/eslint-parser@latest @open-wc/testing-helpers@latest @types/jest@latest eslint@7 eslint-plugin-testcafe@latest jest@latest jest-environment-jsdom@latest sinon@latest testcafe@latest",
136
+ "update:test-deps:test": "npm run lint && npm run test && npm run test:e2e",
136
137
  "DOCS:update:build-deps": "These can cause strange changes, so do an npm run build + check file size (git diff --stat), and check the site is as expected",
137
- "update:build-deps": "npm i @babel/core@latest @babel/preset-env@latest @babel/plugin-proposal-class-properties@latest @babel/plugin-proposal-decorators@latest babel-loader@latest core-js@latest regenerator-runtime@latest sass@latest svgo@latest webpack@latest webpack-cli@latest"
138
+ "update:build-deps": "npm i @babel/core@latest @babel/preset-env@latest @babel/plugin-proposal-class-properties@latest @babel/plugin-proposal-decorators@latest babel-loader@latest core-js@latest regenerator-runtime@latest sass@latest svgo@latest webpack@latest webpack-cli@latest",
139
+ "update:build-deps:test": "npm run build",
140
+ "DOCS:update:dev-deps": "Packages used for development",
141
+ "update:dev-deps": "npm i @iiif/presentation-2@latest @iiif/presentation-3@latest concurrently@latest cpx2@latest http-server@latest live-server@latest",
142
+ "DOCS:update:ia-deps": "Packages by @internetarchive",
143
+ "update:ia-deps": "npm i @internetarchive/ia-activity-indicator@latest @internetarchive/ia-item-navigator@latest @internetarchive/icon-bookmark@latest @internetarchive/icon-close@latest @internetarchive/icon-dl@latest @internetarchive/icon-edit-pencil@latest @internetarchive/icon-ia-logo@latest @internetarchive/icon-magnify-minus@latest @internetarchive/icon-magnify-plus@latest @internetarchive/icon-search@latest @internetarchive/icon-share@latest @internetarchive/icon-toc@latest @internetarchive/icon-visual-adjustment@latest @internetarchive/modal-manager@latest @internetarchive/shared-resize-observer@latest"
138
144
  }
139
145
  }
@@ -4,7 +4,7 @@ export class SelectionObserver {
4
4
  startedInSelector = false;
5
5
  /** @type {HTMLElement} */
6
6
  target = null;
7
- /** @type {Node} */
7
+ /** @type {Node | null} */
8
8
  lastKnownFocusNode = null;
9
9
 
10
10
  /**
@@ -30,6 +30,7 @@ export class SelectionObserver {
30
30
 
31
31
  _onSelectionChange = () => {
32
32
  const sel = window.getSelection();
33
+ if (!sel) return;
33
34
 
34
35
  if (!this.selecting && sel.toString()) {
35
36
  const target = $(sel.anchorNode).closest(this.selector)[0];
@@ -40,13 +41,14 @@ export class SelectionObserver {
40
41
  this.handler('started', this.target);
41
42
  }
42
43
 
43
- if (this.selecting && (this.lastKnownFocusNode != sel.focusNode || sel.toString() && !sel.isCollapsed)) {
44
+ if (this.selecting && this.lastKnownFocusNode != sel.focusNode && sel.toString() && !sel.isCollapsed) {
44
45
  this.lastKnownFocusNode = sel.focusNode;
45
46
  this.handler('changed', this.target);
46
47
  }
47
48
 
48
49
  if (this.selecting && (sel.isCollapsed || !sel.toString() || !$(sel.anchorNode).closest(this.selector)[0])) {
49
50
  this.selecting = false;
51
+ this.lastKnownFocusNode = null;
50
52
  this.handler('cleared', this.target);
51
53
  }
52
54
  };
package/src/BookReader.js CHANGED
@@ -151,7 +151,7 @@ BookReader.defaultOptions = DEFAULT_OPTIONS;
151
151
  * @type {BookReaderOptions}
152
152
  * This is here, just in case you need to absolutely override an option.
153
153
  */
154
- BookReader.optionOverrides = {};
154
+ BookReader.optionOverrides = BookReader.optionOverrides || {};
155
155
 
156
156
  /**
157
157
  * Setup
@@ -852,6 +852,7 @@ BookReader.prototype.setupKeyListeners = function () {
852
852
 
853
853
  // Ignore if modifiers are active.
854
854
  if (e.getModifierState('Control') ||
855
+ e.getModifierState('Shift') ||
855
856
  e.getModifierState('Alt') ||
856
857
  e.getModifierState('Meta') ||
857
858
  e.getModifierState('Win') /* hack for IE */) {
@@ -153,7 +153,16 @@
153
153
  color-scheme: dark;
154
154
  padding: 2px;
155
155
  overflow: hidden;
156
- transition: border-radius 0.2s;
156
+ opacity: 1;
157
+ transition: border-radius 0.2s, opacity 0.2s;
158
+
159
+ @starting-style {
160
+ opacity: 0;
161
+ }
162
+ }
163
+ .br-select-menu__root--scrolling {
164
+ opacity: 0;
165
+ pointer-events: none;
157
166
  }
158
167
  .br-select-menu__root:hover {
159
168
  border-radius: 8px;
@@ -172,6 +181,7 @@
172
181
  align-items: center;
173
182
  border-radius: 20px;
174
183
  font-family: inherit;
184
+ color: inherit;
175
185
  border: 0;
176
186
  transition: background-color 0.2s, border-radius 0.2s;
177
187
  cursor: pointer;
@@ -191,18 +201,23 @@
191
201
  }
192
202
 
193
203
  .br-select-menu__label {
194
- width: 0px;
195
- opacity: 0;
196
- interpolate-size: allow-keywords;
197
- transition: width 0.2s;
204
+ margin-left: 4px;
198
205
  font-size: 12px;
199
206
  }
200
207
 
201
-
202
- .br-select-menu__root:hover .br-select-menu__label {
203
- width: auto;
204
- margin-left: 4px;
205
- opacity: 1;
208
+ @media (hover: hover) {
209
+ .br-select-menu__label {
210
+ width: 0px;
211
+ margin-left: 0;
212
+ opacity: 0;
213
+ interpolate-size: allow-keywords;
214
+ transition: width 0.2s;
215
+ }
216
+ .br-select-menu__root:hover .br-select-menu__label {
217
+ width: auto;
218
+ margin-left: 4px;
219
+ opacity: 1;
220
+ }
206
221
  }
207
222
 
208
223
  .BRtextLayer .BRhighlight {
@@ -3,13 +3,19 @@ import { BookReaderPlugin } from '../BookReaderPlugin.js';
3
3
 
4
4
  const BookReader = /** @type {typeof import('../BookReader').default} */(window.BookReader);
5
5
 
6
+ /**
7
+ * @extends BookReaderPlugin<IiifPlugin['options']>
8
+ */
6
9
  export class IiifPlugin extends BookReaderPlugin {
7
10
  options = {
8
11
  enabled: true,
9
- /** @type {import('@iiif/presentation-3').Manifest | import('@iiif/presentation-2').Manifest} */
12
+ /** @type {import('@iiif/presentation-3').Manifest | import('@iiif/presentation-2').Manifest | null} */
10
13
  manifest: null,
11
14
  }
12
15
 
16
+ /**
17
+ * @param {IiifPlugin['options']} options
18
+ */
13
19
  setup(options) {
14
20
  super.setup(options);
15
21
  this.manifest = this.options.manifest;
@@ -1,6 +1,6 @@
1
1
  /* global br */
2
- import { isChrome, isFirefox } from '../../util/browserSniffing.js';
3
- import { isAndroid, DEBUG_READ_ALOUD } from './utils.js';
2
+ import { isChrome, isFirefox, isAndroid } from '../../util/browserSniffing.js';
3
+ import { DEBUG_READ_ALOUD } from './utils.js';
4
4
  import { promisifyEvent, sleep } from '../../BookReader/utils.js';
5
5
  import AbstractTTSEngine from './AbstractTTSEngine.js';
6
6
  /** @typedef {import("./AbstractTTSEngine.js").PageChunk} PageChunk */
@@ -1,15 +1,6 @@
1
1
  // @ts-check
2
2
  import langs from 'iso-language-codes';
3
3
 
4
- /**
5
- * Checks whether the current browser is on android
6
- * @param {string} [userAgent]
7
- * @return {boolean}
8
- */
9
- export function isAndroid(userAgent = navigator.userAgent) {
10
- return /android/i.test(userAgent);
11
- }
12
-
13
4
  /** @type {{[lang: string]: string}} */
14
5
  // Handle odd one-off language pairs that might show up over time
15
6
  const specialLangs = {
@@ -1,11 +1,13 @@
1
1
  // @ts-check
2
2
  import { countWords } from './strings.js';
3
3
  import { SelectionObserver } from "../BookReader/utils/SelectionObserver.js";
4
+ import { clamp } from "../BookReader/utils.js";
4
5
  import { html, LitElement } from 'lit';
5
6
  import { customElement, property, query } from 'lit/decorators.js';
6
7
  import { ifDefined } from 'lit/directives/if-defined.js';
7
8
  import '@internetarchive/icon-share';
8
9
  import '@internetarchive/icon-edit-pencil/icon-edit-pencil.js';
10
+ import { isIOS, isAndroid } from './browserSniffing.js';
9
11
 
10
12
  const BR_HIGHLIGHTS_LOCAL_STORAGE_KEY = "BRhighlightStorage";
11
13
  const MAX_FULL_QUOTE_URL_CHARS = 80;
@@ -55,15 +57,12 @@ export class TextSelectionManager {
55
57
  // Set a class on the page to avoid hiding it when zooming/etc
56
58
  this.br.refs.$br.find('.BRpagecontainer--hasSelection').removeClass('BRpagecontainer--hasSelection');
57
59
  $(window.getSelection().anchorNode).closest('.BRpagecontainer').addClass('BRpagecontainer--hasSelection');
58
- this.showSelectMenu();
59
-
60
+ // Don't show while still doing selection
61
+ if (!this.mouseIsDown) this.showSelectMenu();
60
62
  }
61
63
 
62
64
  if (selectEvent == 'changed') {
63
- // hide the button as user changes their selection
64
- if (this.mouseIsDown) {
65
- this.hideSelectMenu();
66
- } else if (window.getSelection()?.toString()) {
65
+ if (!this.mouseIsDown && window.getSelection()?.toString()) {
67
66
  this.showSelectMenu();
68
67
  }
69
68
  }
@@ -165,7 +164,6 @@ export class TextSelectionManager {
165
164
  // blocking selection
166
165
  $(textLayer).on("mousedown.textSelectPluginHandler", (event) => {
167
166
  this.mouseIsDown = true;
168
- this.hideSelectMenu();
169
167
  if ($(event.target).is(this.selectionElement.join(", "))) {
170
168
  event.stopPropagation();
171
169
  }
@@ -173,7 +171,6 @@ export class TextSelectionManager {
173
171
 
174
172
  $(textLayer).on("mouseup.textSelectPluginHandler", (event) => {
175
173
  this.mouseIsDown = false;
176
- this.hideSelectMenu();
177
174
  textLayer.style.pointerEvents = "none";
178
175
  if (skipNextMouseup) {
179
176
  skipNextMouseup = false;
@@ -199,7 +196,6 @@ export class TextSelectionManager {
199
196
  if (event.which != 1) return;
200
197
  this.mouseIsDown = true;
201
198
  event.stopPropagation();
202
- this.hideSelectMenu();
203
199
  });
204
200
 
205
201
  // Prevent page flip on click
@@ -221,7 +217,11 @@ export class TextSelectionManager {
221
217
  document.body.append(this.selectMenu);
222
218
  }
223
219
 
224
- this.selectMenu.show();
220
+ if (this.selectMenu.open) {
221
+ this.selectMenu.repositionToSelection();
222
+ } else {
223
+ this.selectMenu.show();
224
+ }
225
225
  }
226
226
 
227
227
  hideSelectMenu() {
@@ -459,6 +459,9 @@ class BRSelectMenu extends LitElement {
459
459
  @query('#copy-link-option')
460
460
  copyLinkOption;
461
461
 
462
+ @property({type: Boolean, reflect: true})
463
+ open = false;
464
+
462
465
  @property({type: Boolean})
463
466
  copyLinkToHighlightEnabled = true;
464
467
 
@@ -479,6 +482,19 @@ class BRSelectMenu extends LitElement {
479
482
  return this;
480
483
  }
481
484
 
485
+ /** @type {number | null} */
486
+ _scrollTimeoutId = null;
487
+
488
+ _onScroll = () => {
489
+ this.classList.add('br-select-menu__root--scrolling');
490
+ if (this._scrollTimeoutId != null) clearTimeout(this._scrollTimeoutId);
491
+ this._scrollTimeoutId = window.setTimeout(() => {
492
+ this._scrollTimeoutId = null;
493
+ this.repositionToSelection();
494
+ this.classList.remove('br-select-menu__root--scrolling');
495
+ }, 150);
496
+ };
497
+
482
498
  /** @override */
483
499
  connectedCallback() {
484
500
  super.connectedCallback();
@@ -487,10 +503,22 @@ class BRSelectMenu extends LitElement {
487
503
  this.setAttribute('aria-orientation', 'vertical');
488
504
  }
489
505
 
506
+ /** @override */
507
+ disconnectedCallback() {
508
+ super.disconnectedCallback();
509
+ window.removeEventListener('scroll', this._onScroll, { capture: true });
510
+ if (this._scrollTimeoutId != null) {
511
+ clearTimeout(this._scrollTimeoutId);
512
+ this._scrollTimeoutId = null;
513
+ }
514
+ }
515
+
490
516
  renderCopyLinkToHighlightOption() {
517
+ // Mousedown needed to prevent selection from being cleared on iOS
491
518
  return html`
492
519
  <br-menu-option
493
520
  id="copy-link-option"
521
+ @mousedown=${/** @param {MouseEvent} e */ (e) => e.preventDefault()}
494
522
  @click=${this.handleCopyLinkToHighlight}
495
523
  icon="share"
496
524
  label="Copy Link to Highlight"
@@ -703,31 +731,54 @@ class BRSelectMenu extends LitElement {
703
731
  this.show();
704
732
  }
705
733
 
706
- show() {
734
+ repositionToSelection() {
735
+ const SCREEN_MARGIN = 10;
736
+ const currentSelection = /** @type {Selection} */ (window.getSelection());
737
+ const range = currentSelection.getRangeAt(0);
738
+ const selectionRect = range.getBoundingClientRect();
739
+
740
+ const isMobile = isIOS() || isAndroid();
741
+ // Leave room for mobile teardrop selection handles
742
+ const SELECTION_PADDING = isAndroid() ? 25 : isIOS() ? 15 : 10;
743
+ // On mobile, avoid the native OS text-selection bar which appears near the top of the screen
744
+ const OS_BAR_H = isMobile ? 60 : 0;
745
+ const idealTop = (selectionRect.top < OS_BAR_H
746
+ ? selectionRect.bottom + OS_BAR_H
747
+ : selectionRect.bottom) + SELECTION_PADDING;
748
+ const top = Math.max(SCREEN_MARGIN, idealTop) + window.scrollY;
749
+
750
+ const menuWidth = this.getBoundingClientRect().width;
751
+ // Excludes scrollbars
752
+ const windowWidth = document.documentElement.clientWidth;
753
+ const idealLeft = menuWidth > selectionRect.width
754
+ ? selectionRect.left + selectionRect.width / 2 - menuWidth / 2
755
+ : selectionRect.left;
756
+ const left = clamp(idealLeft, SCREEN_MARGIN, windowWidth - menuWidth - SCREEN_MARGIN) + window.scrollX;
757
+
758
+ this.style.top = `${top}px`;
759
+ this.style.left = `${left}px`;
760
+ }
761
+
762
+ async show() {
707
763
  if (this.br.plugins.translate?.userToggleTranslate) return;
708
- const currentSelection = window.getSelection();
709
- const start = currentSelection.anchorNode.parentElement;
710
- const end = currentSelection.focusNode.parentElement; // will always be a text node
711
- const height = 30;
712
- const width = 60;
713
- const startBoundingRect = start.getBoundingClientRect();
714
- const endBoundingRect = end.getBoundingClientRect();
715
- let hlButtonTop = startBoundingRect.top - height;
716
- let hlButtonLeft = startBoundingRect.left - width;
717
- if (currentSelection.direction == 'backward') {
718
- hlButtonTop = endBoundingRect.top - height;
719
- hlButtonLeft = endBoundingRect.left - width;
720
- }
721
- this.style.top = `${hlButtonTop}px`;
722
- this.style.left = `${hlButtonLeft}px`;
764
+
723
765
  this.style.zIndex = '1';
724
766
  this.style.position = 'absolute';
725
767
  this.style.display = 'block';
768
+ this.open = true;
769
+ this.classList.remove('br-select-menu__root--scrolling');
770
+ window.removeEventListener('scroll', this._onScroll, { capture: true });
771
+ window.addEventListener('scroll', this._onScroll, { capture: true, passive: true });
772
+ await this.updateComplete; // ensure Lit has rendered so getBoundingClientRect() returns real width
773
+ this.repositionToSelection();
726
774
  this.clearNodesForRemoval();
727
775
  }
728
776
 
729
- hide = () => {
777
+ hide() {
778
+ if (!this.open) return;
730
779
  this.style.display = 'none';
780
+ this.open = false;
781
+ window.removeEventListener('scroll', this._onScroll, { capture: true });
731
782
  this.clearNodesForRemoval();
732
783
  return;
733
784
  }
@@ -51,6 +51,15 @@ export function isIOS() {
51
51
  return 'ongesturestart' in window && navigator.maxTouchPoints > 0;
52
52
  }
53
53
 
54
+ /**
55
+ * Checks whether the current browser is on android
56
+ * @param {string} [userAgent]
57
+ * @return {boolean}
58
+ */
59
+ export function isAndroid(userAgent = navigator.userAgent) {
60
+ return /android/i.test(userAgent);
61
+ }
62
+
54
63
  /**
55
64
  * Checks whether the current browser is Samsung Internet
56
65
  * https://stackoverflow.com/a/40684162/2317712