Tracking de l'application VApp (IHM du jeu)
This commit is contained in:
238
VApp/node_modules/vuetify/lib/composables/virtual.mjs
generated
vendored
Normal file
238
VApp/node_modules/vuetify/lib/composables/virtual.mjs
generated
vendored
Normal file
@ -0,0 +1,238 @@
|
||||
// Composables
|
||||
import { useDisplay } from "./display.mjs";
|
||||
import { useResizeObserver } from "./resizeObserver.mjs"; // Utilities
|
||||
import { computed, nextTick, onScopeDispose, ref, shallowRef, watch, watchEffect } from 'vue';
|
||||
import { clamp, debounce, IN_BROWSER, propsFactory } from "../util/index.mjs"; // Types
|
||||
const UP = -1;
|
||||
const DOWN = 1;
|
||||
|
||||
/** Determines how large each batch of items should be */
|
||||
const BUFFER_PX = 100;
|
||||
export const makeVirtualProps = propsFactory({
|
||||
itemHeight: {
|
||||
type: [Number, String],
|
||||
default: null
|
||||
},
|
||||
height: [Number, String]
|
||||
}, 'virtual');
|
||||
export function useVirtual(props, items) {
|
||||
const display = useDisplay();
|
||||
const itemHeight = shallowRef(0);
|
||||
watchEffect(() => {
|
||||
itemHeight.value = parseFloat(props.itemHeight || 0);
|
||||
});
|
||||
const first = shallowRef(0);
|
||||
const last = shallowRef(Math.ceil(
|
||||
// Assume 16px items filling the entire screen height if
|
||||
// not provided. This is probably incorrect but it minimises
|
||||
// the chance of ending up with empty space at the bottom.
|
||||
// The default value is set here to avoid poisoning getSize()
|
||||
(parseInt(props.height) || display.height.value) / (itemHeight.value || 16)) || 1);
|
||||
const paddingTop = shallowRef(0);
|
||||
const paddingBottom = shallowRef(0);
|
||||
|
||||
/** The scrollable element */
|
||||
const containerRef = ref();
|
||||
/** An element marking the top of the scrollable area,
|
||||
* used to add an offset if there's padding or other elements above the virtual list */
|
||||
const markerRef = ref();
|
||||
/** markerRef's offsetTop, lazily evaluated */
|
||||
let markerOffset = 0;
|
||||
const {
|
||||
resizeRef,
|
||||
contentRect
|
||||
} = useResizeObserver();
|
||||
watchEffect(() => {
|
||||
resizeRef.value = containerRef.value;
|
||||
});
|
||||
const viewportHeight = computed(() => {
|
||||
return containerRef.value === document.documentElement ? display.height.value : contentRect.value?.height || parseInt(props.height) || 0;
|
||||
});
|
||||
/** All static elements have been rendered and we have an assumed item height */
|
||||
const hasInitialRender = computed(() => {
|
||||
return !!(containerRef.value && markerRef.value && viewportHeight.value && itemHeight.value);
|
||||
});
|
||||
let sizes = Array.from({
|
||||
length: items.value.length
|
||||
});
|
||||
let offsets = Array.from({
|
||||
length: items.value.length
|
||||
});
|
||||
const updateTime = shallowRef(0);
|
||||
let targetScrollIndex = -1;
|
||||
function getSize(index) {
|
||||
return sizes[index] || itemHeight.value;
|
||||
}
|
||||
const updateOffsets = debounce(() => {
|
||||
const start = performance.now();
|
||||
offsets[0] = 0;
|
||||
const length = items.value.length;
|
||||
for (let i = 1; i <= length - 1; i++) {
|
||||
offsets[i] = (offsets[i - 1] || 0) + getSize(i - 1);
|
||||
}
|
||||
updateTime.value = Math.max(updateTime.value, performance.now() - start);
|
||||
}, updateTime);
|
||||
const unwatch = watch(hasInitialRender, v => {
|
||||
if (!v) return;
|
||||
// First render is complete, update offsets and visible
|
||||
// items in case our assumed item height was incorrect
|
||||
|
||||
unwatch();
|
||||
markerOffset = markerRef.value.offsetTop;
|
||||
updateOffsets.immediate();
|
||||
calculateVisibleItems();
|
||||
if (!~targetScrollIndex) return;
|
||||
nextTick(() => {
|
||||
IN_BROWSER && window.requestAnimationFrame(() => {
|
||||
scrollToIndex(targetScrollIndex);
|
||||
targetScrollIndex = -1;
|
||||
});
|
||||
});
|
||||
});
|
||||
watch(viewportHeight, (val, oldVal) => {
|
||||
oldVal && calculateVisibleItems();
|
||||
});
|
||||
onScopeDispose(() => {
|
||||
updateOffsets.clear();
|
||||
});
|
||||
function handleItemResize(index, height) {
|
||||
const prevHeight = sizes[index];
|
||||
const prevMinHeight = itemHeight.value;
|
||||
itemHeight.value = prevMinHeight ? Math.min(itemHeight.value, height) : height;
|
||||
if (prevHeight !== height || prevMinHeight !== itemHeight.value) {
|
||||
sizes[index] = height;
|
||||
updateOffsets();
|
||||
}
|
||||
}
|
||||
function calculateOffset(index) {
|
||||
index = clamp(index, 0, items.value.length - 1);
|
||||
return offsets[index] || 0;
|
||||
}
|
||||
function calculateIndex(scrollTop) {
|
||||
return binaryClosest(offsets, scrollTop);
|
||||
}
|
||||
let lastScrollTop = 0;
|
||||
let scrollVelocity = 0;
|
||||
let lastScrollTime = 0;
|
||||
function handleScroll() {
|
||||
if (!containerRef.value || !markerRef.value) return;
|
||||
const scrollTop = containerRef.value.scrollTop;
|
||||
const scrollTime = performance.now();
|
||||
const scrollDeltaT = scrollTime - lastScrollTime;
|
||||
if (scrollDeltaT > 500) {
|
||||
scrollVelocity = Math.sign(scrollTop - lastScrollTop);
|
||||
|
||||
// Not super important, only update at the
|
||||
// start of a scroll sequence to avoid reflows
|
||||
markerOffset = markerRef.value.offsetTop;
|
||||
} else {
|
||||
scrollVelocity = scrollTop - lastScrollTop;
|
||||
}
|
||||
lastScrollTop = scrollTop;
|
||||
lastScrollTime = scrollTime;
|
||||
calculateVisibleItems();
|
||||
}
|
||||
function handleScrollend() {
|
||||
if (!containerRef.value || !markerRef.value) return;
|
||||
scrollVelocity = 0;
|
||||
lastScrollTime = 0;
|
||||
calculateVisibleItems();
|
||||
}
|
||||
let raf = -1;
|
||||
function calculateVisibleItems() {
|
||||
cancelAnimationFrame(raf);
|
||||
raf = requestAnimationFrame(_calculateVisibleItems);
|
||||
}
|
||||
function _calculateVisibleItems() {
|
||||
if (!containerRef.value || !viewportHeight.value) return;
|
||||
const scrollTop = lastScrollTop - markerOffset;
|
||||
const direction = Math.sign(scrollVelocity);
|
||||
const startPx = Math.max(0, scrollTop - BUFFER_PX);
|
||||
const start = clamp(calculateIndex(startPx), 0, items.value.length);
|
||||
const endPx = scrollTop + viewportHeight.value + BUFFER_PX;
|
||||
const end = clamp(calculateIndex(endPx) + 1, start + 1, items.value.length);
|
||||
if (
|
||||
// Only update the side we're scrolling towards,
|
||||
// the other side will be updated incidentally
|
||||
(direction !== UP || start < first.value) && (direction !== DOWN || end > last.value)) {
|
||||
const topOverflow = calculateOffset(first.value) - calculateOffset(start);
|
||||
const bottomOverflow = calculateOffset(end) - calculateOffset(last.value);
|
||||
const bufferOverflow = Math.max(topOverflow, bottomOverflow);
|
||||
if (bufferOverflow > BUFFER_PX) {
|
||||
first.value = start;
|
||||
last.value = end;
|
||||
} else {
|
||||
// Only update the side that's reached its limit if there's still buffer left
|
||||
if (start <= 0) first.value = start;
|
||||
if (end >= items.value.length) last.value = end;
|
||||
}
|
||||
}
|
||||
paddingTop.value = calculateOffset(first.value);
|
||||
paddingBottom.value = calculateOffset(items.value.length) - calculateOffset(last.value);
|
||||
}
|
||||
function scrollToIndex(index) {
|
||||
const offset = calculateOffset(index);
|
||||
if (!containerRef.value || index && !offset) {
|
||||
targetScrollIndex = index;
|
||||
} else {
|
||||
containerRef.value.scrollTop = offset;
|
||||
}
|
||||
}
|
||||
const computedItems = computed(() => {
|
||||
return items.value.slice(first.value, last.value).map((item, index) => ({
|
||||
raw: item,
|
||||
index: index + first.value
|
||||
}));
|
||||
});
|
||||
watch(items, () => {
|
||||
sizes = Array.from({
|
||||
length: items.value.length
|
||||
});
|
||||
offsets = Array.from({
|
||||
length: items.value.length
|
||||
});
|
||||
updateOffsets.immediate();
|
||||
calculateVisibleItems();
|
||||
}, {
|
||||
deep: true
|
||||
});
|
||||
return {
|
||||
containerRef,
|
||||
markerRef,
|
||||
computedItems,
|
||||
paddingTop,
|
||||
paddingBottom,
|
||||
scrollToIndex,
|
||||
handleScroll,
|
||||
handleScrollend,
|
||||
handleItemResize
|
||||
};
|
||||
}
|
||||
|
||||
// https://gist.github.com/robertleeplummerjr/1cc657191d34ecd0a324
|
||||
function binaryClosest(arr, val) {
|
||||
let high = arr.length - 1;
|
||||
let low = 0;
|
||||
let mid = 0;
|
||||
let item = null;
|
||||
let target = -1;
|
||||
if (arr[high] < val) {
|
||||
return high;
|
||||
}
|
||||
while (low <= high) {
|
||||
mid = low + high >> 1;
|
||||
item = arr[mid];
|
||||
if (item > val) {
|
||||
high = mid - 1;
|
||||
} else if (item < val) {
|
||||
target = mid;
|
||||
low = mid + 1;
|
||||
} else if (item === val) {
|
||||
return mid;
|
||||
} else {
|
||||
return low;
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
//# sourceMappingURL=virtual.mjs.map
|
Reference in New Issue
Block a user