Private
Public Access
1
0

feat: Fluent UI Outlook Lite + connections mockup

This commit is contained in:
2026-04-14 18:52:25 +00:00
parent 1199eff6c3
commit dfa4010406
34820 changed files with 1003813 additions and 205 deletions

View File

@@ -0,0 +1,543 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "useVirtualizer_unstable", {
enumerable: true,
get: function() {
return useVirtualizer_unstable;
}
});
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
const _useIntersectionObserver = require("../../hooks/useIntersectionObserver");
const _Utilities = require("../../Utilities");
const _reactutilities = require("@fluentui/react-utilities");
const _reactdom = require("react-dom");
function useVirtualizer_unstable(props) {
'use no memo';
const { itemSize, numItems, virtualizerLength, children: renderChild, getItemSize, bufferItems = Math.round(virtualizerLength / 4.0), bufferSize = Math.floor(bufferItems / 2.0) * itemSize, axis = 'vertical', reversed = false, virtualizerContext, onRenderedFlaggedIndex, imperativeVirtualizerRef, containerSizeRef, scrollViewRef, enableScrollLoad, updateScrollPosition, gap = 0 } = props;
/* The context is optional, it's useful for injecting additional index logic, or performing uniform state updates*/ const _virtualizerContext = (0, _Utilities.useVirtualizerContextState_unstable)(virtualizerContext);
// We use this ref as a constant source to access the virtualizer's state imperatively
const actualIndexRef = _react.useRef(_virtualizerContext.contextIndex);
const flaggedIndex = _react.useRef(null);
const actualIndex = _virtualizerContext.contextIndex;
// Just in case our ref gets out of date vs the context during a re-render
if (_virtualizerContext.contextIndex !== actualIndexRef.current) {
actualIndexRef.current = _virtualizerContext.contextIndex;
}
const setActualIndex = _react.useCallback((index)=>{
actualIndexRef.current = index;
_virtualizerContext.setContextIndex(index);
}, [
_virtualizerContext
]);
// Store ref to before padding element
const beforeElementRef = _react.useRef(null);
// Store ref to before padding element
const afterElementRef = _react.useRef(null);
// We need to store an array to track dynamic sizes, we can use this to incrementally update changes
const childSizes = _react.useRef(new Array(getItemSize ? numItems : 0));
/* We keep track of the progressive sizing/placement down the list,
this helps us skip re-calculations unless children/size changes */ const childProgressiveSizes = _react.useRef(new Array(getItemSize ? numItems : 0));
if (virtualizerContext === null || virtualizerContext === void 0 ? void 0 : virtualizerContext.childProgressiveSizes) {
virtualizerContext.childProgressiveSizes.current = childProgressiveSizes.current;
}
// The internal tracking REF for child array (updates often).
const childArray = _react.useRef(new Array(virtualizerLength));
const populateSizeArrays = ()=>{
if (!getItemSize) {
// Static sizes, never mind!
return;
}
if (numItems !== childSizes.current.length) {
childSizes.current = new Array(numItems);
}
if (numItems !== childProgressiveSizes.current.length) {
childProgressiveSizes.current = new Array(numItems);
if (virtualizerContext === null || virtualizerContext === void 0 ? void 0 : virtualizerContext.childProgressiveSizes) {
virtualizerContext.childProgressiveSizes.current = childProgressiveSizes.current;
}
}
for(let index = 0; index < numItems; index++){
const _gap = index < numItems - 1 ? gap : 0;
childSizes.current[index] = getItemSize(index) + _gap;
if (index === 0) {
childProgressiveSizes.current[index] = childSizes.current[index];
} else {
childProgressiveSizes.current[index] = childProgressiveSizes.current[index - 1] + childSizes.current[index];
}
}
};
const [isScrolling, setIsScrolling] = _react.useState(false);
const [setScrollTimer, clearScrollTimer] = (0, _reactutilities.useTimeout)();
const scrollCounter = _react.useRef(0);
const initializeScrollingTimer = _react.useCallback(()=>{
if (!enableScrollLoad) {
// Disabled by default for reduction of render callbacks
setIsScrolling(false);
clearScrollTimer();
return;
}
/*
* This can be considered the 'velocity' required to start 'isScrolling'
* INIT_SCROLL_FLAG_REQ: Number of renders required to activate isScrolling
* INIT_SCROLL_FLAG_DELAY: Amount of time (ms) before current number of renders is reset
* - Maybe we should let users customize these in the future.
*/ const INIT_SCROLL_FLAG_REQ = 10;
const INIT_SCROLL_FLAG_DELAY = 100;
scrollCounter.current++;
if (scrollCounter.current >= INIT_SCROLL_FLAG_REQ) {
setIsScrolling(true);
}
clearScrollTimer();
setScrollTimer(()=>{
setIsScrolling(false);
scrollCounter.current = 0;
}, INIT_SCROLL_FLAG_DELAY);
}, [
clearScrollTimer,
setScrollTimer,
enableScrollLoad
]);
_react.useEffect(()=>{
initializeScrollingTimer();
}, [
actualIndex,
initializeScrollingTimer
]);
const updateChildRows = _react.useCallback((newIndex)=>{
if (numItems === 0) {
/* Nothing to virtualize */ return;
}
/*
We reset the array every time to ensure children are re-rendered
This function should only be called when update is nessecary
*/ childArray.current = new Array(virtualizerLength);
const _actualIndex = Math.max(newIndex, 0);
const end = Math.min(_actualIndex + virtualizerLength, numItems);
for(let i = _actualIndex; i < end; i++){
childArray.current[i - _actualIndex] = renderChild(i, isScrolling);
}
}, [
isScrolling,
numItems,
renderChild,
virtualizerLength
]);
const updateCurrentItemSizes = _react.useCallback((newIndex)=>{
if (!getItemSize) {
// Static sizes, not required.
return;
}
// We should always call our size function on index change (only for the items that will be rendered)
// This ensures we request the latest data for incoming items in case sizing has changed.
const endIndex = Math.min(newIndex + virtualizerLength, numItems);
const startIndex = Math.max(newIndex, 0);
let didUpdate = false;
for(let i = startIndex; i < endIndex; i++){
const _gap = i < numItems - 1 ? gap : 0;
const newSize = getItemSize(i) + _gap;
if (newSize !== childSizes.current[i]) {
childSizes.current[i] = newSize;
didUpdate = true;
}
}
if (didUpdate) {
// Update our progressive size array
for(let i = startIndex; i < numItems; i++){
const prevSize = i > 0 ? childProgressiveSizes.current[i - 1] : 0;
childProgressiveSizes.current[i] = prevSize + childSizes.current[i];
}
}
}, [
getItemSize,
numItems,
virtualizerLength,
gap
]);
const batchUpdateNewIndex = _react.useCallback((index)=>{
// Local updates
updateChildRows(index);
updateCurrentItemSizes(index);
// State setters
setActualIndex(index);
}, [
setActualIndex,
updateChildRows,
updateCurrentItemSizes
]);
const findIndexRecursive = _react.useCallback((scrollPos, lowIndex, highIndex)=>{
if (lowIndex > highIndex) {
// We shouldn't get here - but no-op the index if we do.
return actualIndex;
}
const midpoint = Math.floor((lowIndex + highIndex) / 2);
const iBefore = Math.max(midpoint - 1, 0);
const iAfter = Math.min(midpoint + 1, childProgressiveSizes.current.length - 1);
const indexValue = childProgressiveSizes.current[midpoint];
const afterIndexValue = childProgressiveSizes.current[iAfter];
const beforeIndexValue = childProgressiveSizes.current[iBefore];
if (scrollPos <= afterIndexValue && scrollPos >= beforeIndexValue) {
/* We've found our index - if we are exactly matching before/after index that's ok,
better to reduce checks if it's right on the boundary. */ return midpoint;
}
if (indexValue > scrollPos) {
return findIndexRecursive(scrollPos, lowIndex, midpoint - 1);
} else {
return findIndexRecursive(scrollPos, midpoint + 1, highIndex);
}
}, [
actualIndex
]);
const getIndexFromSizeArray = _react.useCallback((scrollPos)=>{
/* Quick searches our progressive height array */ if (scrollPos === 0 || childProgressiveSizes.current.length === 0 || scrollPos <= childProgressiveSizes.current[0]) {
// Check start
return 0;
}
if (scrollPos >= childProgressiveSizes.current[childProgressiveSizes.current.length - 1]) {
// Check end
return childProgressiveSizes.current.length - 1;
}
return findIndexRecursive(scrollPos, 0, childProgressiveSizes.current.length - 1);
}, [
findIndexRecursive
]);
const getIndexFromScrollPosition = _react.useCallback((scrollPos)=>{
if (!getItemSize) {
return Math.round(scrollPos / (itemSize + gap));
}
return getIndexFromSizeArray(scrollPos);
}, [
getIndexFromSizeArray,
getItemSize,
itemSize,
gap
]);
const calculateTotalSize = _react.useCallback(()=>{
if (!getItemSize) {
return (itemSize + gap) * numItems;
}
// Time for custom size calcs
return childProgressiveSizes.current[numItems - 1];
}, [
getItemSize,
itemSize,
numItems,
gap
]);
const calculateBefore = _react.useCallback(()=>{
const currentIndex = Math.min(actualIndex, numItems - 1);
if (!getItemSize) {
// The missing items from before virtualization starts height
return currentIndex * (itemSize + gap);
}
if (currentIndex <= 0) {
return 0;
}
// Time for custom size calcs
return childProgressiveSizes.current[currentIndex - 1];
}, [
actualIndex,
getItemSize,
itemSize,
numItems,
gap
]);
const calculateAfter = _react.useCallback(()=>{
if (numItems === 0 || actualIndex + virtualizerLength >= numItems) {
return 0;
}
const lastItemIndex = Math.min(actualIndex + virtualizerLength, numItems);
if (!getItemSize) {
// The missing items from after virtualization ends height
const remainingItems = numItems - lastItemIndex;
return remainingItems * (itemSize + gap) - gap;
}
// Time for custom size calcs
return childProgressiveSizes.current[numItems - 1] - childProgressiveSizes.current[lastItemIndex - 1];
}, [
actualIndex,
getItemSize,
itemSize,
numItems,
virtualizerLength,
gap
]);
// We store the number of items since last render, we will allow an update if the number of items changes
const previousNumItems = _react.useRef(numItems);
// Observe intersections of virtualized components
const { setObserverList } = (0, _useIntersectionObserver.useIntersectionObserver)(_react.useCallback((entries, observer)=>{
/* Sanity check - do we even need virtualization? */ if (virtualizerLength > numItems) {
if (actualIndex !== 0) {
batchUpdateNewIndex(0);
}
// No-op
return;
}
if (entries.length === 0) {
// No entries found, return.
return;
}
// Find the latest entry that is intersecting
const sortedEntries = entries.sort((entry1, entry2)=>entry2.time - entry1.time);
const latestEntry = sortedEntries.find((entry)=>{
return entry.isIntersecting;
});
if (!latestEntry) {
return;
}
// We have to be sure our item sizes are up to date with current indexed ref before calculation
// Check if we still need
updateCurrentItemSizes(actualIndexRef.current);
const calculateOverBuffer = ()=>{
/**
* We avoid using the scroll ref scrollTop, it may be incorrect
* as virtualization may exist within a scroll view with other elements
* The benefit of using IO is that we can detect relative scrolls,
* so any items can be placed around the virtualizer in the scroll view
*/ let measurementPos = 0;
if (latestEntry.target === afterElementRef.current) {
// Get after buffers position
measurementPos = calculateTotalSize() - calculateAfter();
// Get exact intersection position based on overflow size (how far into IO did we scroll?)
const overflowAmount = axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width;
// Add to original after position
measurementPos += overflowAmount;
// Ignore buffer size (IO offset)
measurementPos -= bufferSize;
var _containerSizeRef_current;
// we hit the after buffer and detected the end of view, we need to find the start index.
measurementPos -= (_containerSizeRef_current = containerSizeRef.current) !== null && _containerSizeRef_current !== void 0 ? _containerSizeRef_current : 0;
// Calculate how far past the window bounds we are (this will be zero if IO is within window)
const hOverflow = latestEntry.boundingClientRect.top - latestEntry.intersectionRect.top;
const hOverflowReversed = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom;
const wOverflow = latestEntry.boundingClientRect.left - latestEntry.intersectionRect.left;
const wOverflowReversed = latestEntry.boundingClientRect.right - latestEntry.intersectionRect.right;
const widthOverflow = reversed ? wOverflowReversed : wOverflow;
const heightOverflow = reversed ? hOverflowReversed : hOverflow;
const additionalOverflow = axis === 'vertical' ? heightOverflow : widthOverflow;
if (reversed) {
measurementPos += additionalOverflow;
} else {
measurementPos -= additionalOverflow;
}
} else if (latestEntry.target === beforeElementRef.current) {
// Get before buffers position
measurementPos = calculateBefore();
// Get exact intersection position based on overflow size (how far into window did we scroll IO?)
const overflowAmount = axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width;
// Minus from original before position
measurementPos -= overflowAmount;
// Ignore buffer size (IO offset)
measurementPos += bufferSize;
// Calculate how far past the window bounds we are (this will be zero if IO is within window)
const hOverflow = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom;
const hOverflowReversed = latestEntry.boundingClientRect.top - latestEntry.intersectionRect.top;
const wOverflow = latestEntry.boundingClientRect.right - latestEntry.intersectionRect.right;
const wOverflowReversed = latestEntry.boundingClientRect.left - latestEntry.intersectionRect.left;
const widthOverflow = reversed ? wOverflowReversed : wOverflow;
const heightOverflow = reversed ? hOverflowReversed : hOverflow;
const additionalOverflow = axis === 'vertical' ? heightOverflow : widthOverflow;
if (reversed) {
measurementPos += additionalOverflow;
} else {
measurementPos -= additionalOverflow;
}
}
return measurementPos;
};
// Get exact relative 'scrollTop' via IO values
const measurementPos = calculateOverBuffer();
const maxIndex = Math.max(numItems - virtualizerLength, 0);
const startIndex = getIndexFromScrollPosition(measurementPos) - bufferItems;
// Safety limits
const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex);
(0, _reactdom.flushSync)(()=>{
// Callback to allow measure functions to check virtualizer length
if (previousNumItems.current === numItems && newStartIndex + virtualizerLength >= numItems && actualIndex + virtualizerLength >= numItems) {
// We've already hit the end, no need to update state.
return;
}
// We should ensure we update virtualizer calculations if the length changes
previousNumItems.current = virtualizerLength;
updateScrollPosition === null || updateScrollPosition === void 0 ? void 0 : updateScrollPosition(measurementPos);
if (actualIndex !== newStartIndex) {
batchUpdateNewIndex(newStartIndex);
}
});
}, [
actualIndex,
virtualizerLength,
axis,
reversed,
numItems,
bufferSize,
bufferItems,
containerSizeRef,
updateScrollPosition,
batchUpdateNewIndex,
calculateAfter,
calculateBefore,
calculateTotalSize,
getIndexFromScrollPosition,
updateCurrentItemSizes
]), {
root: scrollViewRef ? scrollViewRef === null || scrollViewRef === void 0 ? void 0 : scrollViewRef.current : null,
rootMargin: '0px',
threshold: 0
});
const setBeforeRef = _react.useCallback((element)=>{
if (!element || beforeElementRef.current === element) {
return;
}
beforeElementRef.current = element;
const newList = [];
newList.push(beforeElementRef.current);
if (afterElementRef.current) {
newList.push(afterElementRef.current);
}
// Ensure we update array if before element changed
setObserverList(newList);
}, [
setObserverList
]);
const setAfterRef = _react.useCallback((element)=>{
if (!element || afterElementRef.current === element) {
return;
}
afterElementRef.current = element;
const newList = [];
if (beforeElementRef.current) {
newList.push(beforeElementRef.current);
}
newList.push(afterElementRef.current);
// Ensure we update array if after element changed
setObserverList(newList);
}, [
setObserverList
]);
// Initialize the size array before first render.
const hasInitialized = _react.useRef(false);
const initializeSizeArray = ()=>{
if (hasInitialized.current === false) {
hasInitialized.current = true;
populateSizeArrays();
}
};
_react.useImperativeHandle(imperativeVirtualizerRef, ()=>{
return {
progressiveSizes: childProgressiveSizes,
nodeSizes: childSizes,
setFlaggedIndex: (index)=>flaggedIndex.current = index,
currentIndex: actualIndexRef
};
}, [
childProgressiveSizes,
childSizes
]);
// Initialization on mount - update array index to 0 (ready state).
// Only fire on mount (no deps).
_react.useEffect(()=>{
if (actualIndex < 0) {
batchUpdateNewIndex(0);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
/*
* forceUpdate:
* We only want to trigger this when child render or scroll loading changes,
* it will force re-render all children elements
*/ const forceUpdate = _react.useReducer(()=>({}), {})[1];
// If the user passes in an updated renderChild function - update current children
_react.useEffect(()=>{
if (actualIndex >= 0) {
updateChildRows(actualIndex);
forceUpdate();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
renderChild,
isScrolling
]);
_react.useEffect(()=>{
// Ensure we repopulate if getItemSize callback changes
populateSizeArrays();
// We only run this effect on getItemSize change (recalc dynamic sizes)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
getItemSize,
gap
]);
// Effect to check flag index on updates
_react.useEffect(()=>{
if (!onRenderedFlaggedIndex || flaggedIndex.current === null) {
return;
}
if (actualIndex <= flaggedIndex.current && actualIndex + virtualizerLength >= flaggedIndex.current) {
onRenderedFlaggedIndex(flaggedIndex.current);
flaggedIndex.current = null;
}
}, [
actualIndex,
onRenderedFlaggedIndex,
virtualizerLength
]);
// Ensure we have run through and updated the whole size list array at least once.
initializeSizeArray();
if (getItemSize && (numItems !== childSizes.current.length || numItems !== childProgressiveSizes.current.length)) {
// Child length mismatch, repopulate size arrays.
populateSizeArrays();
}
// Ensure we recalc if virtualizer length changes
const maxCompare = Math.min(virtualizerLength, numItems);
if (childArray.current.length !== maxCompare && actualIndex + childArray.current.length < numItems) {
updateChildRows(actualIndex);
}
const isFullyInitialized = hasInitialized.current && actualIndex >= 0;
return {
components: {
before: 'div',
after: 'div',
beforeContainer: 'div',
afterContainer: 'div'
},
virtualizedChildren: childArray.current,
before: _reactutilities.slot.always(props.before, {
defaultProps: {
ref: setBeforeRef,
role: 'none'
},
elementType: 'div'
}),
after: _reactutilities.slot.always(props.after, {
defaultProps: {
ref: setAfterRef,
role: 'none'
},
elementType: 'div'
}),
beforeContainer: _reactutilities.slot.always(props.beforeContainer, {
defaultProps: {
role: 'none'
},
elementType: 'div'
}),
afterContainer: _reactutilities.slot.always(props.afterContainer, {
defaultProps: {
role: 'none'
},
elementType: 'div'
}),
beforeBufferHeight: isFullyInitialized ? calculateBefore() : 0,
afterBufferHeight: isFullyInitialized ? calculateAfter() : 0,
totalVirtualizerHeight: isFullyInitialized ? calculateTotalSize() : virtualizerLength * itemSize,
virtualizerStartIndex: actualIndex,
axis,
bufferSize,
reversed,
childSizes,
childProgressiveSizes
};
}