fix: resolve lock_scroll.js 404 and optimize Trunk preloading
Some checks failed
Build MIPS Binary / build (push) Failing after 1m54s
Some checks failed
Build MIPS Binary / build (push) Failing after 1m54s
This commit is contained in:
@@ -20,13 +20,12 @@
|
|||||||
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
<link rel="apple-touch-icon" sizes="512x512" href="icon-512.png" />
|
||||||
|
|
||||||
<!-- Trunk Assets -->
|
<!-- Trunk Assets -->
|
||||||
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="0" data-no-preload />
|
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-no-preload />
|
||||||
<link data-trunk rel="css" href="public/tailwind.css" />
|
<link data-trunk rel="css" href="public/tailwind.css" />
|
||||||
<link data-trunk rel="copy-file" href="manifest.json" />
|
<link data-trunk rel="copy-file" href="manifest.json" />
|
||||||
<link data-trunk rel="copy-file" href="icon-192.png" />
|
<link data-trunk rel="copy-file" href="icon-192.png" />
|
||||||
<link data-trunk rel="copy-file" href="icon-512.png" />
|
<link data-trunk rel="copy-file" href="icon-512.png" />
|
||||||
<link data-trunk rel="copy-file" href="public/lock_scroll.js" />
|
<link data-trunk rel="copy-file" href="public/lock_scroll.js" />
|
||||||
<script src="/lock_scroll.js"></script>
|
|
||||||
<link data-trunk rel="copy-file" href="sw.js" />
|
<link data-trunk rel="copy-file" href="sw.js" />
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
|
|||||||
@@ -1,253 +0,0 @@
|
|||||||
/**
|
|
||||||
* Scroll Lock Utility
|
|
||||||
* Handles locking and unlocking scroll for both window and all scrollable containers
|
|
||||||
* Similar to react-remove-scroll but in vanilla JavaScript
|
|
||||||
*/
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
// Prevent multiple initializations
|
|
||||||
if (window.ScrollLock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ScrollLock {
|
|
||||||
constructor() {
|
|
||||||
this.locked = false;
|
|
||||||
this.scrollableElements = [];
|
|
||||||
this.scrollPositions = new Map();
|
|
||||||
this.originalStyles = new Map();
|
|
||||||
this.fixedElements = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find all scrollable elements in the DOM (optimized)
|
|
||||||
* Uses more targeted selectors instead of querying all elements
|
|
||||||
*/
|
|
||||||
findScrollableElements() {
|
|
||||||
const scrollables = [];
|
|
||||||
|
|
||||||
// More targeted query - only look for elements with overflow properties
|
|
||||||
const candidates = document.querySelectorAll(
|
|
||||||
'[style*="overflow"], [class*="overflow"], [class*="scroll"], main, aside, section, div',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Batch all style reads first to minimize reflows
|
|
||||||
const elementsToCheck = [];
|
|
||||||
for (const el of candidates) {
|
|
||||||
// Skip the element itself or if it's inside these containers
|
|
||||||
const dataName = el.getAttribute("data-name");
|
|
||||||
const isExcludedElement =
|
|
||||||
dataName === "ScrollArea" ||
|
|
||||||
dataName === "CommandList" ||
|
|
||||||
dataName === "SelectContent" ||
|
|
||||||
dataName === "MultiSelectContent" ||
|
|
||||||
dataName === "DropdownMenuContent" ||
|
|
||||||
dataName === "ContextMenuContent";
|
|
||||||
|
|
||||||
if (
|
|
||||||
el !== document.body &&
|
|
||||||
el !== document.documentElement &&
|
|
||||||
!isExcludedElement &&
|
|
||||||
!el.closest('[data-name="ScrollArea"]') &&
|
|
||||||
!el.closest('[data-name="CommandList"]') &&
|
|
||||||
!el.closest('[data-name="SelectContent"]') &&
|
|
||||||
!el.closest('[data-name="MultiSelectContent"]') &&
|
|
||||||
!el.closest('[data-name="DropdownMenuContent"]') &&
|
|
||||||
!el.closest('[data-name="ContextMenuContent"]')
|
|
||||||
) {
|
|
||||||
elementsToCheck.push(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now batch read all computed styles and dimensions
|
|
||||||
elementsToCheck.forEach((el) => {
|
|
||||||
const style = window.getComputedStyle(el);
|
|
||||||
const hasOverflow =
|
|
||||||
style.overflow === "auto" ||
|
|
||||||
style.overflow === "scroll" ||
|
|
||||||
style.overflowY === "auto" ||
|
|
||||||
style.overflowY === "scroll";
|
|
||||||
|
|
||||||
// Only check scrollHeight if overflow is set
|
|
||||||
if (hasOverflow && el.scrollHeight > el.clientHeight) {
|
|
||||||
scrollables.push(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return scrollables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lock scrolling on all scrollable elements (optimized)
|
|
||||||
* Batches all DOM reads before DOM writes to prevent forced reflows
|
|
||||||
*/
|
|
||||||
lock() {
|
|
||||||
if (this.locked) return;
|
|
||||||
|
|
||||||
this.locked = true;
|
|
||||||
|
|
||||||
// Find all scrollable elements
|
|
||||||
this.scrollableElements = this.findScrollableElements();
|
|
||||||
|
|
||||||
// ===== BATCH 1: READ PHASE - Read all layout properties first =====
|
|
||||||
const windowScrollY = window.scrollY;
|
|
||||||
const scrollbarWidth = window.innerWidth - document.body.clientWidth;
|
|
||||||
|
|
||||||
// Store window scroll position
|
|
||||||
this.scrollPositions.set("window", windowScrollY);
|
|
||||||
|
|
||||||
// Store original body styles
|
|
||||||
this.originalStyles.set("body", {
|
|
||||||
position: document.body.style.position,
|
|
||||||
top: document.body.style.top,
|
|
||||||
width: document.body.style.width,
|
|
||||||
overflow: document.body.style.overflow,
|
|
||||||
paddingRight: document.body.style.paddingRight,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Read all fixed-position elements and their padding (only if we have scrollbar)
|
|
||||||
if (scrollbarWidth > 0) {
|
|
||||||
// Use more targeted query for fixed elements
|
|
||||||
const fixedCandidates = document.querySelectorAll(
|
|
||||||
'[style*="fixed"], [class*="fixed"], header, nav, aside, [role="dialog"], [role="alertdialog"]',
|
|
||||||
);
|
|
||||||
|
|
||||||
this.fixedElements = Array.from(fixedCandidates).filter((el) => {
|
|
||||||
const style = window.getComputedStyle(el);
|
|
||||||
return (
|
|
||||||
style.position === "fixed" &&
|
|
||||||
!el.closest('[data-name="DropdownMenuContent"]') &&
|
|
||||||
!el.closest('[data-name="MultiSelectContent"]') &&
|
|
||||||
!el.closest('[data-name="ContextMenuContent"]')
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Batch read all padding values
|
|
||||||
this.fixedElements.forEach((el) => {
|
|
||||||
const computedStyle = window.getComputedStyle(el);
|
|
||||||
const currentPadding = Number.parseInt(computedStyle.paddingRight, 10) || 0;
|
|
||||||
|
|
||||||
this.originalStyles.set(el, {
|
|
||||||
paddingRight: el.style.paddingRight,
|
|
||||||
computedPadding: currentPadding,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read scrollable elements info
|
|
||||||
const scrollableInfo = this.scrollableElements.map((el) => {
|
|
||||||
const scrollTop = el.scrollTop;
|
|
||||||
const elementScrollbarWidth = el.offsetWidth - el.clientWidth;
|
|
||||||
const computedStyle = window.getComputedStyle(el);
|
|
||||||
const currentPadding = Number.parseInt(computedStyle.paddingRight, 10) || 0;
|
|
||||||
|
|
||||||
this.scrollPositions.set(el, scrollTop);
|
|
||||||
this.originalStyles.set(el, {
|
|
||||||
overflow: el.style.overflow,
|
|
||||||
overflowY: el.style.overflowY,
|
|
||||||
paddingRight: el.style.paddingRight,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { el, elementScrollbarWidth, currentPadding };
|
|
||||||
});
|
|
||||||
|
|
||||||
// ===== BATCH 2: WRITE PHASE - Apply all styles at once =====
|
|
||||||
|
|
||||||
// Apply body lock
|
|
||||||
document.body.style.position = "fixed";
|
|
||||||
document.body.style.top = `-${windowScrollY}px`;
|
|
||||||
document.body.style.width = "100%";
|
|
||||||
document.body.style.overflow = "hidden";
|
|
||||||
|
|
||||||
if (scrollbarWidth > 0) {
|
|
||||||
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
|
||||||
|
|
||||||
// Apply padding compensation to fixed elements
|
|
||||||
this.fixedElements.forEach((el) => {
|
|
||||||
const stored = this.originalStyles.get(el);
|
|
||||||
if (stored) {
|
|
||||||
el.style.paddingRight = `${stored.computedPadding + scrollbarWidth}px`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lock all scrollable containers
|
|
||||||
scrollableInfo.forEach(({ el, elementScrollbarWidth, currentPadding }) => {
|
|
||||||
el.style.overflow = "hidden";
|
|
||||||
|
|
||||||
if (elementScrollbarWidth > 0) {
|
|
||||||
el.style.paddingRight = `${currentPadding + elementScrollbarWidth}px`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unlock scrolling on all elements (optimized)
|
|
||||||
* @param {number} delay - Delay in milliseconds before unlocking (for animations)
|
|
||||||
*/
|
|
||||||
unlock(delay = 0) {
|
|
||||||
if (!this.locked) return;
|
|
||||||
|
|
||||||
const performUnlock = () => {
|
|
||||||
// Restore body scroll
|
|
||||||
const bodyStyles = this.originalStyles.get("body");
|
|
||||||
if (bodyStyles) {
|
|
||||||
document.body.style.position = bodyStyles.position;
|
|
||||||
document.body.style.top = bodyStyles.top;
|
|
||||||
document.body.style.width = bodyStyles.width;
|
|
||||||
document.body.style.overflow = bodyStyles.overflow;
|
|
||||||
document.body.style.paddingRight = bodyStyles.paddingRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore window scroll position
|
|
||||||
const windowScrollY = this.scrollPositions.get("window") || 0;
|
|
||||||
window.scrollTo(0, windowScrollY);
|
|
||||||
|
|
||||||
// Restore all scrollable containers
|
|
||||||
this.scrollableElements.forEach((el) => {
|
|
||||||
const originalStyles = this.originalStyles.get(el);
|
|
||||||
if (originalStyles) {
|
|
||||||
el.style.overflow = originalStyles.overflow;
|
|
||||||
el.style.overflowY = originalStyles.overflowY;
|
|
||||||
el.style.paddingRight = originalStyles.paddingRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore scroll position
|
|
||||||
const scrollPosition = this.scrollPositions.get(el) || 0;
|
|
||||||
el.scrollTop = scrollPosition;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restore fixed-position elements padding
|
|
||||||
this.fixedElements.forEach((el) => {
|
|
||||||
const styles = this.originalStyles.get(el);
|
|
||||||
if (styles && styles.paddingRight !== undefined) {
|
|
||||||
el.style.paddingRight = styles.paddingRight;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear storage
|
|
||||||
this.scrollableElements = [];
|
|
||||||
this.fixedElements = [];
|
|
||||||
this.scrollPositions.clear();
|
|
||||||
this.originalStyles.clear();
|
|
||||||
this.locked = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (delay > 0) {
|
|
||||||
setTimeout(performUnlock, delay);
|
|
||||||
} else {
|
|
||||||
performUnlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if scrolling is currently locked
|
|
||||||
*/
|
|
||||||
isLocked() {
|
|
||||||
return this.locked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export as singleton
|
|
||||||
window.ScrollLock = new ScrollLock();
|
|
||||||
})();
|
|
||||||
@@ -93,7 +93,7 @@ pub fn DialogContent(
|
|||||||
let backdrop_behavior = if close_on_backdrop_click { "auto" } else { "manual" };
|
let backdrop_behavior = if close_on_backdrop_click { "auto" } else { "manual" };
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<script src="/hooks/lock_scroll.js"></script>
|
<script src="/lock_scroll.js"></script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-name=backdrop_data_name
|
data-name=backdrop_data_name
|
||||||
|
|||||||
Reference in New Issue
Block a user