@z-cloud/virtual-browser
Version:
249 lines (248 loc) • 8.35 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { BasicVirtualizer, debounce } from "@z-cloud/virtual-vanilla";
import { getElementOffsetTop } from "./utils/index.js";
const eventListenerOptions = {
passive: true
};
const getScrollElementRect = (element) => {
const isElement = element instanceof HTMLElement;
const width = isElement ? element.offsetWidth : element.innerWidth;
const height = isElement ? element.offsetHeight : element.innerHeight;
return { width, height };
};
class BrowserVirtualizer extends BasicVirtualizer {
constructor(options) {
super();
__publicField(this, "scrollElement", null);
__publicField(this, "targetWindow", null);
__publicField(this, "elementObserver", null);
__publicField(this, "dynamicElementsCache", /* @__PURE__ */ new Map());
__publicField(this, "scrollToIndexTimeoutId", null);
__publicField(this, "elementMounted", (element) => {
if (!element) {
this.dynamicElementsCache.forEach((ele, key) => {
var _a;
if (!ele.isConnected) {
(_a = this.elementObserver) == null ? void 0 : _a.unobserve(ele);
this.dynamicElementsCache.delete(key);
}
});
return;
}
this.handleElementSizeChange(
element,
this.options.horizontal ? element.offsetWidth : element.offsetHeight
);
});
__publicField(this, "resetScrolling", debounce(() => {
this.scrolling = false;
this.notify();
}, 160));
this.setOptions(options);
}
init(scrollElement) {
if (!scrollElement || this.scrollElement === scrollElement) {
return;
}
this.scrollElement = scrollElement;
if (this.scrollElement instanceof HTMLElement) {
this.targetWindow = this.scrollElement.ownerDocument.defaultView;
this.unsubscribes.push(this.observeElementRect());
} else {
this.targetWindow = this.scrollElement.window;
this.unsubscribes.push(this.observeWindowRect());
}
this.unsubscribes.push(this.addScrollEventListener());
this.initElementObserver();
this.scrollToOffset(
typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset
);
this.notify();
}
observeElementRect() {
const scrollElement = this.scrollElement;
const targetWindow = this.targetWindow;
if (!targetWindow || !globalThis.ResizeObserver) {
return;
}
this.setScrollElementRect(getScrollElementRect(scrollElement));
const observe = new ResizeObserver((entries) => {
var _a;
for (const entry of entries) {
const boxSize = (_a = entry.borderBoxSize) == null ? void 0 : _a[0];
if (boxSize) {
this.setScrollElementRect({
width: boxSize.inlineSize,
height: boxSize.blockSize
});
return;
}
}
this.setScrollElementRect(getScrollElementRect(scrollElement));
});
observe.observe(scrollElement, { box: "border-box" });
return () => {
observe.unobserve(scrollElement);
};
}
observeWindowRect() {
const targetWindow = this.targetWindow;
if (!targetWindow) {
return;
}
const handleResize = () => {
this.setScrollElementRect(getScrollElementRect(targetWindow));
};
handleResize();
targetWindow.addEventListener("resize", handleResize);
return () => {
targetWindow.removeEventListener("resize", handleResize);
};
}
initElementObserver() {
if (!this.targetWindow || !globalThis.ResizeObserver) {
return;
}
this.elementObserver = new ResizeObserver((entries) => {
var _a;
for (const entry of entries) {
const boxSize = (_a = entry.borderBoxSize) == null ? void 0 : _a[0];
if (boxSize) {
this.handleElementSizeChange(
entry.target,
boxSize[this.options.horizontal ? "inlineSize" : "blockSize"]
);
}
}
});
}
resizeItemSize(index, size) {
const item = this.items[index];
if (!item) {
return;
}
const delta = size - item.size;
if (Math.abs(delta) >= 1) {
const offset = this.getScrollOffset();
if (item.start < offset) {
this.scrollTo(offset + delta);
}
this.dynamicSizeCache.set(index, size);
this.pendingDynamicSizeIndexes.push(index);
this.scrolling = false;
this.clearFnMemo(["geItemsWithtMemo", "notifyWithMemo"]);
this.notify();
}
}
handleElementSizeChange(element, size) {
var _a, _b;
const index = Number.parseInt(element.dataset.index ?? "-1", 10);
const item = this.getItems()[index];
if (!item) {
return;
}
const key = item.key;
const prevElement = this.dynamicElementsCache.get(key);
if (prevElement !== element) {
if (prevElement) {
(_a = this.elementObserver) == null ? void 0 : _a.unobserve(prevElement);
}
(_b = this.elementObserver) == null ? void 0 : _b.observe(element, { box: "border-box" });
this.dynamicElementsCache.set(item.key, element);
}
if (element.isConnected) {
this.resizeItemSize(index, size);
}
}
/**
* 添加滚动监听
*/
addScrollEventListener() {
const scrollElement = this.scrollElement;
if (!this.targetWindow || !scrollElement) {
return;
}
const scrollHandler = () => {
let offset = 0;
const { horizontal } = this.options;
if (scrollElement instanceof HTMLElement) {
offset = horizontal ? scrollElement["scrollLeft"] : scrollElement["scrollTop"];
} else {
offset = horizontal ? scrollElement.scrollX : scrollElement.scrollY;
}
this.scrollOffset = offset;
this.scrolling = true;
this.resetScrolling(this.targetWindow);
this.notify();
};
scrollElement.addEventListener("scroll", scrollHandler, eventListenerOptions);
return () => {
scrollElement.removeEventListener("scroll", scrollHandler);
};
}
scrollTo(offset, behavior = "instant") {
var _a;
(_a = this.scrollElement) == null ? void 0 : _a.scrollTo({
[this.options.horizontal ? "left" : "top"]: offset,
behavior
});
}
scrollToOffset(offset, behavior) {
this.cancelScrollToIndex();
this.scrollTo(offset, behavior);
}
cancelScrollToIndex() {
var _a;
if (this.scrollToIndexTimeoutId !== null) {
(_a = this.targetWindow) == null ? void 0 : _a.clearTimeout(this.scrollToIndexTimeoutId);
this.scrollToIndexTimeoutId = null;
}
}
scrollToIndex(index, { align, behavior } = {}) {
this.cancelScrollToIndex();
const safeIndex = Math.max(0, Math.min(index, this.options.count - 1));
const offset = this.getOffsetForIndex(safeIndex, align);
if (!offset) {
return;
}
this.scrollTo(offset, this.dynamicMode() ? "instant" : behavior);
if (this.dynamicMode() && this.targetWindow) {
this.scrollToIndexTimeoutId = this.targetWindow.setTimeout(() => {
this.scrollToIndexTimeoutId = null;
if (this.dynamicElementsCache.has(this.options.getItemKey(safeIndex))) {
const calcOffset = this.getOffsetForIndex(safeIndex, align);
if (!calcOffset) {
return;
}
const currentScrollOffset = this.getScrollOffset();
if (Math.abs(calcOffset - currentScrollOffset) > 1) {
this.scrollToIndex(safeIndex, { align, behavior: "instant" });
}
} else {
this.scrollToIndex(safeIndex, { align, behavior: "instant" });
}
});
}
}
clean() {
var _a;
this.unsubscribes = this.unsubscribes.filter((unsub) => {
if (typeof unsub === "function") {
unsub();
}
return false;
});
(_a = this.elementObserver) == null ? void 0 : _a.disconnect();
this.elementObserver = null;
this.dynamicSizeCache.clear();
this.pendingDynamicSizeIndexes = [];
this.scrollElement = null;
this.targetWindow = null;
}
}
export {
BrowserVirtualizer,
getElementOffsetTop
};