`;
}
@@ -154,6 +156,7 @@
import { PreferencesProvider } from "/js/dataLayer/preferencesProvider.js";
import { IdentityResolver } from "/js/atproto.js";
import { Router } from "/js/router.js";
+ import { scrollLocks } from "/js/scrollLocks.js";
import { Api } from "/js/api.js";
import { auth } from "/js/auth.js";
import { NotificationService } from "/js/notificationService.js";
@@ -352,6 +355,9 @@
};
const router = new Router();
+
+ scrollLocks.setContainerProvider(() => router.currentPage);
+
router.addRoute(["/", "/intent/compose"], () => homeView, {
layoutOptions: { activeNavItem: "home" },
});
diff --git a/src/js/components/account-switcher-dialog.js b/src/js/components/account-switcher-dialog.js
index 098f9a7d..e31d50e9 100644
--- a/src/js/components/account-switcher-dialog.js
+++ b/src/js/components/account-switcher-dialog.js
@@ -1,6 +1,6 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { enableDragToDismiss } from "/js/utils.js";
import { auth, getLoginErrorMessage } from "/js/auth.js";
import { Signal, ReactiveStore, effect } from "/js/signals.js";
@@ -19,7 +19,7 @@ class AccountSwitcherDialog extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.state = new ReactiveStore("account-switcher-dialog");
this.state.$currentDid = new Signal.State(null);
this.state.$accounts = new Signal.State(null);
@@ -278,7 +278,7 @@ class AccountSwitcherDialog extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector("dialog");
dialog.showModal();
enableDragToDismiss(dialog, {
@@ -290,7 +290,8 @@ class AccountSwitcherDialog extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector("dialog");
if (dialog?.open) {
dialog.close();
diff --git a/src/js/components/add-to-lists-dialog.js b/src/js/components/add-to-lists-dialog.js
index 9c9bc087..17d6e2ad 100644
--- a/src/js/components/add-to-lists-dialog.js
+++ b/src/js/components/add-to-lists-dialog.js
@@ -1,6 +1,6 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { enableDragToDismiss } from "/js/utils.js";
import { Signal, SignalSet, ReactiveStore, effect } from "/js/signals.js";
import { isModerationList } from "/js/dataHelpers.js";
@@ -13,7 +13,7 @@ class AddToListsDialog extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.state = new ReactiveStore("add-to-lists-dialog");
this.state.$pendingByListUri = new SignalSet();
this.state.$loadError = new Signal.State(null);
@@ -230,7 +230,7 @@ class AddToListsDialog extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".add-to-lists-dialog");
dialog.showModal();
enableDragToDismiss(dialog, {
@@ -241,7 +241,8 @@ class AddToListsDialog extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".add-to-lists-dialog");
if (dialog?.open) {
dialog.close();
diff --git a/src/js/components/animated-sidebar.js b/src/js/components/animated-sidebar.js
index ac404f44..36375ac5 100644
--- a/src/js/components/animated-sidebar.js
+++ b/src/js/components/animated-sidebar.js
@@ -1,13 +1,13 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component, getChildrenFragment } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { isMobileViewport } from "/js/utils.js";
class AnimatedSidebar extends Component {
connectedCallback() {
if (!this._initialized) {
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.isOpen = false;
this._children = getChildrenFragment(this);
this.innerHTML = "";
@@ -59,7 +59,7 @@ class AnimatedSidebar extends Component {
return;
}
this.isOpen = true;
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
this.querySelector("dialog.sidebar").showModal();
}
@@ -68,7 +68,8 @@ class AnimatedSidebar extends Component {
return;
}
this.isOpen = false;
- this.scrollLock.unlock({ restoreScroll });
+ this.scrollLock?.release({ restoreScroll });
+ this.scrollLock = null;
const dialog = this.querySelector("dialog.sidebar");
if (dialog.hasAttribute("open")) {
dialog.close();
diff --git a/src/js/components/context-menu.js b/src/js/components/context-menu.js
index f015c220..0d64cfca 100644
--- a/src/js/components/context-menu.js
+++ b/src/js/components/context-menu.js
@@ -5,7 +5,7 @@ import {
isMobileViewport,
} from "/js/utils.js";
import { Component, getChildrenFragment } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { hapticsImpactLight } from "/js/haptics.js";
class ContextMenu extends Component {
@@ -14,7 +14,7 @@ class ContextMenu extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this._childNodes = [...this.childNodes];
this.innerHTML = "";
this.isOpen = false;
@@ -29,7 +29,8 @@ class ContextMenu extends Component {
disconnectedCallback() {
// If scroll is still prevented, restore it
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
if (this._observer) {
this._observer.disconnect();
}
@@ -85,7 +86,7 @@ class ContextMenu extends Component {
open(x, y) {
hapticsImpactLight();
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".context-menu");
dialog.showModal();
@@ -124,7 +125,8 @@ class ContextMenu extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".context-menu");
dialog.close();
this.isOpen = false;
diff --git a/src/js/components/drafts-dialog.js b/src/js/components/drafts-dialog.js
index de80bb0a..07567b53 100644
--- a/src/js/components/drafts-dialog.js
+++ b/src/js/components/drafts-dialog.js
@@ -1,6 +1,6 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { displayRelativeTime, enableDragToDismiss } from "/js/utils.js";
import { Signal, ReactiveStore, effect, untrack } from "/js/signals.js";
import { confirmModal } from "/js/modals/confirm.modal.js";
@@ -165,7 +165,7 @@ class DraftsDialog extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.state = new ReactiveStore("drafts-dialog");
this.state.$loadError = new Signal.State(false);
this.state.$isLoadingMore = new Signal.State(false);
@@ -323,7 +323,7 @@ class DraftsDialog extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector("dialog");
dialog.showModal();
enableDragToDismiss(dialog, {
@@ -335,7 +335,8 @@ class DraftsDialog extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector("dialog");
if (dialog?.open) {
dialog.close();
diff --git a/src/js/components/edit-profile-dialog.js b/src/js/components/edit-profile-dialog.js
index bffdd0ae..eee86c39 100644
--- a/src/js/components/edit-profile-dialog.js
+++ b/src/js/components/edit-profile-dialog.js
@@ -1,6 +1,6 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { avatarThumbnailUrl } from "/js/dataHelpers.js";
import {
classnames,
@@ -26,7 +26,7 @@ class EditProfileDialog extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this._displayName = "";
this._description = "";
this._currentAvatar = null;
@@ -475,7 +475,7 @@ class EditProfileDialog extends Component {
open() {
this._isOpen = true;
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".edit-profile-dialog");
if (dialog) {
dialog.showModal();
@@ -510,7 +510,8 @@ class EditProfileDialog extends Component {
close() {
this._isOpen = false;
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".edit-profile-dialog");
if (dialog) {
dialog.close();
diff --git a/src/js/components/emoji-picker-dialog.js b/src/js/components/emoji-picker-dialog.js
index fb7d0afd..ea668efb 100644
--- a/src/js/components/emoji-picker-dialog.js
+++ b/src/js/components/emoji-picker-dialog.js
@@ -1,5 +1,5 @@
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import "/js/lib/emoji-picker-element.js";
class EmojiPickerDialog extends Component {
@@ -7,7 +7,7 @@ class EmojiPickerDialog extends Component {
if (this._initialized) {
return;
}
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.isOpen = false;
this._initialized = true;
}
@@ -35,7 +35,7 @@ class EmojiPickerDialog extends Component {
});
document.body.appendChild(dialog);
dialog.showModal();
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
this.isOpen = true;
this._dialog = dialog;
this._picker = picker;
@@ -57,7 +57,8 @@ class EmojiPickerDialog extends Component {
this._dialog?.remove();
this._dialog = null;
this._anchor = null;
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
this.isOpen = false;
}
diff --git a/src/js/components/image-alt-text-dialog.js b/src/js/components/image-alt-text-dialog.js
index 7277b311..26274ffd 100644
--- a/src/js/components/image-alt-text-dialog.js
+++ b/src/js/components/image-alt-text-dialog.js
@@ -1,7 +1,7 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
import { classnames, graphemeCount, resetScrollOnBlur } from "/js/utils.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
class ImageAltTextDialog extends Component {
connectedCallback() {
@@ -9,7 +9,7 @@ class ImageAltTextDialog extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.innerHTML = "";
this.render();
this.initialized = true;
@@ -112,7 +112,7 @@ class ImageAltTextDialog extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".image-alt-text-dialog");
dialog.showModal();
this.querySelector(".image-alt-text-dialog-textarea")?.focus({
@@ -126,7 +126,8 @@ class ImageAltTextDialog extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".image-alt-text-dialog");
dialog.close();
this.dispatchEvent(new CustomEvent("alt-text-dialog-closed"));
diff --git a/src/js/components/new-chat-dialog.js b/src/js/components/new-chat-dialog.js
index 1a81c15e..994859ed 100644
--- a/src/js/components/new-chat-dialog.js
+++ b/src/js/components/new-chat-dialog.js
@@ -1,6 +1,6 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { enableDragToDismiss } from "/js/utils.js";
import { Signal, ReactiveStore, effect } from "/js/signals.js";
import { getDisplayName, MISSING_HANDLE } from "/js/dataHelpers.js";
@@ -144,7 +144,7 @@ class NewChatDialog extends Component {
}
this.dataLayer = this.dataLayer ?? null;
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.state = new ReactiveStore("new-chat-dialog");
this.state.$query = new Signal.State("");
this.innerHTML = "";
@@ -296,7 +296,7 @@ class NewChatDialog extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".new-chat-dialog");
dialog.showModal();
this.querySelector(".new-chat-search-input")?.focus({
@@ -310,7 +310,8 @@ class NewChatDialog extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".new-chat-dialog");
if (dialog?.open) {
dialog.close();
diff --git a/src/js/components/post-composer.js b/src/js/components/post-composer.js
index cef66b7f..70e2ca41 100644
--- a/src/js/components/post-composer.js
+++ b/src/js/components/post-composer.js
@@ -13,7 +13,7 @@ import {
} from "/js/utils.js";
import { externalLinkTemplate } from "/js/templates/externalLink.template.js";
import { confirmModal } from "/js/modals/confirm.modal.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { imageIconTemplate } from "/js/templates/icons/imageIcon.template.js";
import { emojiIconTemplate } from "/js/templates/icons/emojiIcon.template.js";
import { closeIconTemplate } from "/js/templates/icons/closeIcon.template.js";
@@ -359,7 +359,7 @@ class PostComposer extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.innerHTML = "";
this._draftId = null;
this._isDirty = false;
@@ -1234,7 +1234,7 @@ class PostComposer extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".post-composer");
dialog.showModal();
this.querySelector("rich-text-input")?.focus({ preventScroll: true });
@@ -1269,7 +1269,8 @@ class PostComposer extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".post-composer");
dialog.close();
this.dispatchEvent(new CustomEvent("post-composer-closed"));
diff --git a/src/js/components/post-notifications-dialog.js b/src/js/components/post-notifications-dialog.js
index 8f8fb7a9..15ffcf51 100644
--- a/src/js/components/post-notifications-dialog.js
+++ b/src/js/components/post-notifications-dialog.js
@@ -1,6 +1,6 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { enableDragToDismiss } from "/js/utils.js";
import "/js/components/toggle-switch.js";
import { closeIconTemplate } from "/js/templates/icons/closeIcon.template.js";
@@ -11,7 +11,7 @@ class PostNotificationsDialog extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this._postEnabled = this.activitySubscription?.post ?? false;
this._replyEnabled = this.activitySubscription?.reply ?? false;
this._isSaving = false;
@@ -146,7 +146,7 @@ class PostNotificationsDialog extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".post-notifications-dialog");
dialog.showModal();
@@ -158,7 +158,8 @@ class PostNotificationsDialog extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".post-notifications-dialog");
if (dialog?.open) {
dialog.close();
diff --git a/src/js/components/reactions-dialog.js b/src/js/components/reactions-dialog.js
index b8ff22fb..2c5a592a 100644
--- a/src/js/components/reactions-dialog.js
+++ b/src/js/components/reactions-dialog.js
@@ -1,7 +1,7 @@
import { Component } from "/js/components/component.js";
import { html, render } from "/js/lib/lit-html.js";
import { effect } from "/js/signals.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { enableDragToDismiss } from "/js/utils.js";
import { avatarTemplate } from "/js/templates/avatar.template.js";
import { getDisplayName, groupReactions } from "/js/dataHelpers.js";
@@ -13,7 +13,7 @@ class ReactionsDialog extends Component {
this._initialized = true;
this.setAttribute("data-dialog-wrapper", "");
this._activeFilter = "all";
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this._dispose = effect(() => {
this.render();
});
@@ -21,7 +21,7 @@ class ReactionsDialog extends Component {
const dialog = this.querySelector(".reactions-dialog");
if (dialog && !dialog.open) {
dialog.showModal();
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
enableDragToDismiss(dialog, {
onClose: () => this._close(),
scrollContainer: this.querySelector(".reactions-list"),
@@ -33,7 +33,8 @@ class ReactionsDialog extends Component {
disconnectedCallback() {
if (this._dispose) this._dispose();
- this.scrollLock?.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
}
_close() {
diff --git a/src/js/components/report-dialog.js b/src/js/components/report-dialog.js
index df533a2c..5320026d 100644
--- a/src/js/components/report-dialog.js
+++ b/src/js/components/report-dialog.js
@@ -1,6 +1,6 @@
import { html, render } from "/js/lib/lit-html.js";
import { Component } from "/js/components/component.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import {
enableDragToDismiss,
kebabCase,
@@ -632,7 +632,7 @@ class ReportDialog extends Component {
return;
}
this.setAttribute("data-dialog-wrapper", "");
- this.scrollLock = new ScrollLock(this);
+ this.scrollLock = null;
this.innerHTML = "";
this._stepIndex = 0;
this._selectedCategory = null;
@@ -808,7 +808,7 @@ class ReportDialog extends Component {
}
open() {
- this.scrollLock.lock();
+ this.scrollLock ??= scrollLocks.acquire({ target: this });
const dialog = this.querySelector(".report-dialog");
dialog.showModal();
@@ -823,7 +823,8 @@ class ReportDialog extends Component {
}
close() {
- this.scrollLock.unlock();
+ this.scrollLock?.release();
+ this.scrollLock = null;
const dialog = this.querySelector(".report-dialog");
dialog.close();
this.dispatchEvent(new CustomEvent("report-dialog-closed"));
diff --git a/src/js/modals/modal.js b/src/js/modals/modal.js
index cebaa141..f80b7dd4 100644
--- a/src/js/modals/modal.js
+++ b/src/js/modals/modal.js
@@ -1,5 +1,5 @@
import { render } from "/js/lib/lit-html.js";
-import { ScrollLock } from "/js/scrollLock.js";
+import { scrollLocks } from "/js/scrollLocks.js";
import { enableDragToDismiss } from "/js/utils.js";
export class Modal {
@@ -44,12 +44,12 @@ export class Modal {
dialog.setAttribute(key, value);
}
- const scrollLock = new ScrollLock(dialog);
+ let scrollLock = null;
let resolved = false;
const dismiss = (value) => {
if (resolved) return;
resolved = true;
- scrollLock.unlock();
+ scrollLock?.release();
dialog.close();
dialog.remove();
resolve(value);
@@ -72,7 +72,7 @@ export class Modal {
});
document.body.appendChild(dialog);
- scrollLock.lock();
+ scrollLock = scrollLocks.acquire({ target: dialog });
dialog.showModal();
if (this.dragToDismiss) {
diff --git a/src/js/preferences.js b/src/js/preferences.js
index 94122b17..83456171 100644
--- a/src/js/preferences.js
+++ b/src/js/preferences.js
@@ -491,7 +491,6 @@ export class Preferences {
return false;
}
- // Todo - memoize this?
postHasMutedWord(post) {
const text = post?.record?.text ?? null;
const facets = post?.record?.facets ?? null;
diff --git a/src/js/scrollLock.js b/src/js/scrollLocks.js
similarity index 63%
rename from src/js/scrollLock.js
rename to src/js/scrollLocks.js
index cae2d583..7511afb2 100644
--- a/src/js/scrollLock.js
+++ b/src/js/scrollLocks.js
@@ -90,61 +90,59 @@ function findScrollableAncestor(element) {
return null;
}
-// Locks are tracked as a stack of holders: the first holder locks the
-// page and only the last holder to unlock restores it.
-let __scrollLockHolders = [];
-let __lockedContainer = null;
+class ScrollLockManager {
+ #getContainer = null;
+ #leases = new Set();
+ #lockedContainer = null;
-export class ScrollLock {
- constructor(target) {
- this.target = target ?? null;
- this.locked = false;
- this._lockedAncestor = null;
- this._previousAncestorOverflow = "";
+ setContainerProvider(getContainer) {
+ this.#getContainer = getContainer;
}
- lock() {
- if (this.locked) {
- return;
- }
- const container = document.querySelector(".page-visible"); // todo find better way to get container
- if (!container) {
- console.warn(
- "ScrollLock: no .page-visible container found; skipping lock",
- );
- return;
- }
- if (__scrollLockHolders.length === 0) {
+ acquire({ target = null } = {}) {
+ let released = false;
+ let lockedAncestor = null;
+ let previousAncestorOverflow = "";
+ const release = ({ restoreScroll = true } = {}) => {
+ if (released) return;
+ released = true;
+
+ if (lockedAncestor) {
+ lockedAncestor.style.overflow = previousAncestorOverflow;
+ lockedAncestor = null;
+ previousAncestorOverflow = "";
+ }
+
+ this.#leases.delete(release);
+ if (this.#leases.size === 0 && this.#lockedContainer) {
+ unlockScroll(this.#lockedContainer, { restoreScroll });
+ this.#lockedContainer = null;
+ }
+ };
+
+ if (this.#leases.size === 0) {
+ const container = this.#getContainer?.();
+ if (!container) {
+ console.warn(
+ "ScrollLock: no current page container found; skipping lock",
+ );
+ return { release };
+ }
lockScroll(container);
- __lockedContainer = container;
+ this.#lockedContainer = container;
}
- __scrollLockHolders.push(this);
- // If target is passed, lock the nearest scrollable ancestor of that target in addition to the outer page
- const ancestor = this.target ? findScrollableAncestor(this.target) : null;
+
+ this.#leases.add(release);
+ // Also prevent scroll in the nearest scrollable ancestor of the trigger.
+ const ancestor = target ? findScrollableAncestor(target) : null;
if (ancestor) {
- this._lockedAncestor = ancestor;
- this._previousAncestorOverflow = ancestor.style.overflow;
+ lockedAncestor = ancestor;
+ previousAncestorOverflow = ancestor.style.overflow;
ancestor.style.overflow = "hidden";
}
- this.locked = true;
- }
- unlock({ restoreScroll = true } = {}) {
- if (!this.locked) {
- return;
- }
- if (this._lockedAncestor) {
- this._lockedAncestor.style.overflow = this._previousAncestorOverflow;
- this._lockedAncestor = null;
- this._previousAncestorOverflow = "";
- }
- __scrollLockHolders = __scrollLockHolders.filter(
- (holder) => holder !== this,
- );
- if (__scrollLockHolders.length === 0) {
- unlockScroll(__lockedContainer, { restoreScroll });
- __lockedContainer = null;
- }
- this.locked = false;
+ return { release };
}
}
+
+export const scrollLocks = new ScrollLockManager();
diff --git a/src/js/views/postThread.view.js b/src/js/views/postThread.view.js
index 6e284b65..fefc42ae 100644
--- a/src/js/views/postThread.view.js
+++ b/src/js/views/postThread.view.js
@@ -267,7 +267,6 @@ class PostThreadView extends View {
${replyChains.map((replyChain, i) =>
// there can be a lot of images in a reply chain, so lazy load them after the first few
- // TODO: infinite scroll for reply chains? or use v2 endpoint?
replyChainTemplate({
replyChain,
currentUser,
diff --git a/src/js/views/profile.view.js b/src/js/views/profile.view.js
index 2a3fa653..3ca18f8c 100644
--- a/src/js/views/profile.view.js
+++ b/src/js/views/profile.view.js
@@ -302,7 +302,9 @@ class ProfileView extends View {
This account has requested that users sign in to view their profile.