@botiverse/raft-ui 0.10.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/components/virtual-list/styles/virtual-list.css +64 -0
- package/dist/{index-CDHLWJtB.d.mts → index-CAdZkAtE.d.mts} +1 -1
- package/dist/index.d.mts +343 -3
- package/dist/index.mjs +2141 -2
- package/dist/style.css +49 -0
- package/dist/styles.css +1 -0
- package/dist/wip.d.mts +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { cn } from "./cn.mjs";
|
|
|
2
2
|
import { A as buttonVariants, C as DialogNav, D as DialogTitle, E as DialogSurface, F as resolveThemeScopeProps, I as useThemeScopeProps, L as useTheme, M as defaultNativeButton, N as useParentOverlayKind, O as DialogTrigger, P as withOverlayLayer, R as ThemeStateContext, S as DialogHeader, T as DialogPortal, _ as DialogBody, a as DrawerContent, b as DialogDescription, c as DrawerHeader, d as DrawerTitle, f as DrawerTrigger, g as Dialog, h as panelHeader, i as DrawerClose, j as Spinner, k as Button, l as DrawerOverlay, m as panelHeadEdges, n as DrawerActions, o as DrawerDescription, p as panelHeadActions, r as DrawerBody, s as DrawerFooter, t as Drawer, u as DrawerPortal, v as DialogClose, w as DialogOverlay, x as DialogFooter, y as DialogContent, z as tv } from "./drawer-yZD16Ybl.mjs";
|
|
3
3
|
import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
|
|
4
4
|
import { AlertTriangle, Bell, Check, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, Copy, FileText, Image, Info, Minus, Upload, X, Zap } from "lucide-react";
|
|
5
|
-
import { Children, Fragment, cloneElement, createContext, isValidElement, use, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
import { Children, Fragment, cloneElement, createContext, createElement, isValidElement, use, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { Button as Button$1 } from "@base-ui/react/button";
|
|
8
8
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
@@ -34,6 +34,7 @@ import { Toast } from "@base-ui/react/toast";
|
|
|
34
34
|
import { Tabs as Tabs$1 } from "@base-ui/react/tabs";
|
|
35
35
|
import { Separator as Separator$1 } from "@base-ui/react/separator";
|
|
36
36
|
import { Group, Panel as Panel$1, Separator as Separator$2 } from "react-resizable-panels";
|
|
37
|
+
import { flushSync } from "react-dom";
|
|
37
38
|
//#region src/components/alert-dialog/alert-dialog.tsx
|
|
38
39
|
const alertDialog = tv({
|
|
39
40
|
slots: {
|
|
@@ -14872,6 +14873,2144 @@ function TaskSectionEmpty({ className, render, ...props }) {
|
|
|
14872
14873
|
});
|
|
14873
14874
|
}
|
|
14874
14875
|
//#endregion
|
|
14876
|
+
//#region src/components/virtual-list/utils/constants.ts
|
|
14877
|
+
/**
|
|
14878
|
+
* Written on the root element of every measured item: maps the DOM to item keys (kept selections,
|
|
14879
|
+
* and what other code uses to find an item).
|
|
14880
|
+
*/
|
|
14881
|
+
const ITEM_KEY_ATTR = "data-virtual-list-item-key";
|
|
14882
|
+
/** The default of the `columns` and `items` props, and the empty kept-item lists. */
|
|
14883
|
+
const EMPTY_ARRAY = [];
|
|
14884
|
+
/**
|
|
14885
|
+
* The visible-items element lays the rows out in normal block flow, so their margins collapse as the
|
|
14886
|
+
* consumer's CSS says (between adjacent rows, and a row's children's through the row). It is a block
|
|
14887
|
+
* formatting context (`flow-root`), so the first row's top margin and the last row's bottom margin stay
|
|
14888
|
+
* inside it instead of collapsing through it (into the spacer, or with the footer), and it is the rows'
|
|
14889
|
+
* offsetParent (`position: relative`): the list measures where each row starts from layout
|
|
14890
|
+
* (`measureRowSpans`).
|
|
14891
|
+
*/
|
|
14892
|
+
const VIRTUAL_VISIBLE_ITEMS_STYLE = {
|
|
14893
|
+
position: "relative",
|
|
14894
|
+
display: "flow-root"
|
|
14895
|
+
};
|
|
14896
|
+
/**
|
|
14897
|
+
* With virtualization off, the visible-items element still carries the CSS variables, so sticky
|
|
14898
|
+
* children that use `calc(var(--virtual-list-transform-y))` keep working.
|
|
14899
|
+
*/
|
|
14900
|
+
const NON_VIRTUAL_VISIBLE_ITEMS_STYLE = {
|
|
14901
|
+
"--virtual-list-transform-y": "0px",
|
|
14902
|
+
"--virtual-list-ios-compensation-y": "0px",
|
|
14903
|
+
transform: "translateY(0px)",
|
|
14904
|
+
position: "relative",
|
|
14905
|
+
display: "flow-root"
|
|
14906
|
+
};
|
|
14907
|
+
/** ... within this window (ms). */
|
|
14908
|
+
const RENDER_THRASHING_WINDOW_MS = 1e3;
|
|
14909
|
+
//#endregion
|
|
14910
|
+
//#region src/components/virtual-list/utils/fenwick-tree.ts
|
|
14911
|
+
/**
|
|
14912
|
+
* Sizes of `l` items, each `estimated` plus a delta stored in the tree: `sum(i)` (the offset of item
|
|
14913
|
+
* `i`) is `i * estimated` plus the prefix sum of the deltas, in O(log n); `getBound(offset)` maps an
|
|
14914
|
+
* offset to an item index by a binary search over `sum()` (O(log² n)).
|
|
14915
|
+
*/
|
|
14916
|
+
var FenwickTree = class {
|
|
14917
|
+
l;
|
|
14918
|
+
estimated;
|
|
14919
|
+
ft;
|
|
14920
|
+
constructor(length, estimated) {
|
|
14921
|
+
this.l = length;
|
|
14922
|
+
this.estimated = estimated;
|
|
14923
|
+
const ft = Array(length + 1);
|
|
14924
|
+
for (let i = 0; i < length + 1; ++i) ft[i] = 0;
|
|
14925
|
+
this.ft = ft;
|
|
14926
|
+
}
|
|
14927
|
+
/** Adds `delta` to the size of item `index`. */
|
|
14928
|
+
add(index, delta) {
|
|
14929
|
+
if (delta === 0) return;
|
|
14930
|
+
const { l, ft } = this;
|
|
14931
|
+
index += 1;
|
|
14932
|
+
while (index <= l) {
|
|
14933
|
+
ft[index] += delta;
|
|
14934
|
+
index += index & -index;
|
|
14935
|
+
}
|
|
14936
|
+
}
|
|
14937
|
+
/** The size of item `index`. */
|
|
14938
|
+
get(index) {
|
|
14939
|
+
return this.sum(index + 1) - this.sum(index);
|
|
14940
|
+
}
|
|
14941
|
+
/** The total size of the first `count` items (all of them by default). */
|
|
14942
|
+
sum(count = this.l) {
|
|
14943
|
+
if (count <= 0) return 0;
|
|
14944
|
+
const { ft, estimated, l } = this;
|
|
14945
|
+
if (count > l) throw new Error("[FenwickTree.sum]: `i` is larger than length.");
|
|
14946
|
+
let sum = count * estimated;
|
|
14947
|
+
while (count > 0) {
|
|
14948
|
+
sum += ft[count];
|
|
14949
|
+
count -= count & -count;
|
|
14950
|
+
}
|
|
14951
|
+
return sum;
|
|
14952
|
+
}
|
|
14953
|
+
/** The largest count `c` with `sum(c) <= threshold`: the index of the item that contains `threshold`. */
|
|
14954
|
+
getBound(threshold) {
|
|
14955
|
+
let lo = 0;
|
|
14956
|
+
let hi = this.l;
|
|
14957
|
+
while (hi > lo) {
|
|
14958
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
14959
|
+
const midSum = this.sum(mid);
|
|
14960
|
+
if (midSum > threshold) {
|
|
14961
|
+
hi = mid;
|
|
14962
|
+
continue;
|
|
14963
|
+
}
|
|
14964
|
+
if (!(midSum < threshold)) return mid;
|
|
14965
|
+
if (lo === mid) {
|
|
14966
|
+
if (this.sum(lo + 1) <= threshold) return lo + 1;
|
|
14967
|
+
return mid;
|
|
14968
|
+
}
|
|
14969
|
+
lo = mid;
|
|
14970
|
+
}
|
|
14971
|
+
return lo;
|
|
14972
|
+
}
|
|
14973
|
+
};
|
|
14974
|
+
//#endregion
|
|
14975
|
+
//#region src/components/virtual-list/utils/items-anchor.ts
|
|
14976
|
+
/**
|
|
14977
|
+
* Finds the row at `top` (the view's top, in item coordinates: `scrollTop + compensation - paddingTop`)
|
|
14978
|
+
* in the previous items and returns it with where it is now. If that row is gone, the first row after
|
|
14979
|
+
* it that is still there is kept instead, at the distance from the view's top it had; `null` when no row
|
|
14980
|
+
* from the one at the top onwards is left (or there is nothing to anchor to). Rows `canAnchor` rejects are
|
|
14981
|
+
* passed over the same way (edge rows such as a list's leading boundary, which new rows go below).
|
|
14982
|
+
*/
|
|
14983
|
+
function findItemsAnchor({ prevItems, prevTree, prevPrependIndexes, nextItems, getKey, canAnchor, top }) {
|
|
14984
|
+
if (prevItems.length === 0 || nextItems.length === 0) return null;
|
|
14985
|
+
const nextIndexOfKey = /* @__PURE__ */ new Map();
|
|
14986
|
+
nextItems.forEach((item, index) => {
|
|
14987
|
+
nextIndexOfKey.set(getKey(item), index);
|
|
14988
|
+
});
|
|
14989
|
+
const topIndex = Math.min(Math.max(prevTree.getBound(top), 0), prevItems.length - 1);
|
|
14990
|
+
for (let index = topIndex; index < prevItems.length; ++index) {
|
|
14991
|
+
const item = prevItems[index];
|
|
14992
|
+
if (canAnchor !== void 0 && !canAnchor(item)) continue;
|
|
14993
|
+
const key = getKey(item);
|
|
14994
|
+
const nextIndex = nextIndexOfKey.get(key);
|
|
14995
|
+
if (nextIndex === void 0) continue;
|
|
14996
|
+
const prependIndexes = [];
|
|
14997
|
+
for (const prevIndex of prevPrependIndexes) {
|
|
14998
|
+
const item = prevItems[prevIndex];
|
|
14999
|
+
const moved = item === void 0 ? void 0 : nextIndexOfKey.get(getKey(item));
|
|
15000
|
+
if (moved !== void 0) prependIndexes.push(moved);
|
|
15001
|
+
}
|
|
15002
|
+
return {
|
|
15003
|
+
key,
|
|
15004
|
+
delta: top - prevTree.sum(index),
|
|
15005
|
+
index: nextIndex,
|
|
15006
|
+
prependIndexes
|
|
15007
|
+
};
|
|
15008
|
+
}
|
|
15009
|
+
return null;
|
|
15010
|
+
}
|
|
15011
|
+
/**
|
|
15012
|
+
* The range to render with the new items: from the anchor row, as many rows as `start..end` held (none if
|
|
15013
|
+
* it was empty), clamped to `itemCount` items. It starts at the anchor row, not at the new index of the old
|
|
15014
|
+
* start: the rows above the anchor are not mounted in this render. When the view's top is above the anchor
|
|
15015
|
+
* row (the row at the top was removed), the range update that follows mounts the rows above as rows
|
|
15016
|
+
* entering above the range, so their first measurement is compensated like when scrolling up.
|
|
15017
|
+
*/
|
|
15018
|
+
function getAnchoredRange(start, end, anchorIndex, itemCount) {
|
|
15019
|
+
if (end < start) return [anchorIndex, anchorIndex - 1];
|
|
15020
|
+
return [anchorIndex, Math.min(anchorIndex + (end - start), itemCount - 1)];
|
|
15021
|
+
}
|
|
15022
|
+
//#endregion
|
|
15023
|
+
//#region src/components/virtual-list/hooks/use-event-callback.ts
|
|
15024
|
+
/** A stable function that calls the `fn` of the latest render. */
|
|
15025
|
+
function useEventCallback(fn) {
|
|
15026
|
+
const fnRef = useRef(fn);
|
|
15027
|
+
fnRef.current = fn;
|
|
15028
|
+
return useCallback((...args) => fnRef.current(...args), []);
|
|
15029
|
+
}
|
|
15030
|
+
//#endregion
|
|
15031
|
+
//#region src/components/virtual-list/hooks/use-latest-ref.ts
|
|
15032
|
+
/** A ref whose `current` is the value of the latest render. */
|
|
15033
|
+
function useLatestRef(value) {
|
|
15034
|
+
const ref = useRef(value);
|
|
15035
|
+
ref.current = value;
|
|
15036
|
+
return ref;
|
|
15037
|
+
}
|
|
15038
|
+
//#endregion
|
|
15039
|
+
//#region src/components/virtual-list/hooks/use-layout-effect-with-previous.ts
|
|
15040
|
+
/**
|
|
15041
|
+
* `useLayoutEffect(() => fn(previousDeps), deps)`: the layout-effect form of useMemoWithPrevious;
|
|
15042
|
+
* `fn` may return a cleanup.
|
|
15043
|
+
*/
|
|
15044
|
+
function useLayoutEffectWithPrevious(fn, deps) {
|
|
15045
|
+
const previousRef = useRef(void 0);
|
|
15046
|
+
useLayoutEffect(() => {
|
|
15047
|
+
const cleanup = fn(previousRef.current);
|
|
15048
|
+
previousRef.current = deps;
|
|
15049
|
+
return cleanup;
|
|
15050
|
+
}, deps);
|
|
15051
|
+
}
|
|
15052
|
+
//#endregion
|
|
15053
|
+
//#region src/components/virtual-list/hooks/use-memo-with-previous.ts
|
|
15054
|
+
/**
|
|
15055
|
+
* `useMemo(() => fn(previousDeps), deps)`: runs `fn` while rendering whenever `deps` change, with the
|
|
15056
|
+
* deps of the last committed render (`undefined` before the first commit).
|
|
15057
|
+
*
|
|
15058
|
+
* The previous deps are recorded at commit, not inside the memo: React may call a memo's function
|
|
15059
|
+
* more than once for one render (StrictMode does on purpose) or throw a render away, and either
|
|
15060
|
+
* would otherwise pass this render's own deps as `previous`.
|
|
15061
|
+
*/
|
|
15062
|
+
function useMemoWithPrevious(fn, deps) {
|
|
15063
|
+
const previousRef = useRef(void 0);
|
|
15064
|
+
const previous = previousRef.current;
|
|
15065
|
+
useMemo(() => {
|
|
15066
|
+
fn(previous);
|
|
15067
|
+
}, deps);
|
|
15068
|
+
useLayoutEffect(() => {
|
|
15069
|
+
previousRef.current = deps;
|
|
15070
|
+
});
|
|
15071
|
+
}
|
|
15072
|
+
//#endregion
|
|
15073
|
+
//#region src/components/virtual-list/hooks/use-merged-ref.ts
|
|
15074
|
+
/**
|
|
15075
|
+
* Merges refs into one setter. Returns `[setRef, getRef]`: `setRef(el)` stores `el` and passes it to
|
|
15076
|
+
* every ref; `getRef()` reads the stored element. When a ref changes between renders (checked while
|
|
15077
|
+
* rendering), the old one is cleared and the new one gets the stored element at once. The setter
|
|
15078
|
+
* calls ref callbacks itself, so what they return is ignored.
|
|
15079
|
+
*/
|
|
15080
|
+
function useMergedRef(...refs) {
|
|
15081
|
+
const entries = useMemo(() => [], []);
|
|
15082
|
+
const elementRef = useRef(null);
|
|
15083
|
+
const getRef = useCallback(() => elementRef.current, []);
|
|
15084
|
+
const clearEntry = useCallback((entry) => {
|
|
15085
|
+
if (entry != null) {
|
|
15086
|
+
if (entry.type === "RefCallback") entry.ref(null);
|
|
15087
|
+
else entry.ref.current = null;
|
|
15088
|
+
}
|
|
15089
|
+
}, []);
|
|
15090
|
+
const applyStoredElement = useCallback((ref) => {
|
|
15091
|
+
if (typeof ref == "function") ref(getRef());
|
|
15092
|
+
else ref.current = getRef();
|
|
15093
|
+
}, [getRef]);
|
|
15094
|
+
const setRef = useCallback((el) => {
|
|
15095
|
+
elementRef.current = el;
|
|
15096
|
+
for (const entry of entries) if (entry == null) continue;
|
|
15097
|
+
else if (entry.type === "RefObject") entry.ref.current = el;
|
|
15098
|
+
else entry.ref(el);
|
|
15099
|
+
}, [entries]);
|
|
15100
|
+
for (let i = 0; i < refs.length; i++) {
|
|
15101
|
+
const ref = refs[i];
|
|
15102
|
+
const entry = entries[i];
|
|
15103
|
+
if (ref == null) {
|
|
15104
|
+
clearEntry(entry);
|
|
15105
|
+
entries[i] = null;
|
|
15106
|
+
} else {
|
|
15107
|
+
if (entry && entry.ref === ref) continue;
|
|
15108
|
+
if (typeof ref == "function") {
|
|
15109
|
+
if (entry != null) clearEntry(entry);
|
|
15110
|
+
entries[i] = {
|
|
15111
|
+
type: "RefCallback",
|
|
15112
|
+
ref
|
|
15113
|
+
};
|
|
15114
|
+
} else {
|
|
15115
|
+
if (entry != null) clearEntry(entry);
|
|
15116
|
+
entries[i] = {
|
|
15117
|
+
type: "RefObject",
|
|
15118
|
+
ref
|
|
15119
|
+
};
|
|
15120
|
+
}
|
|
15121
|
+
applyStoredElement(ref);
|
|
15122
|
+
}
|
|
15123
|
+
}
|
|
15124
|
+
for (let i = refs.length; i < entries.length; i++) {
|
|
15125
|
+
clearEntry(entries[i]);
|
|
15126
|
+
entries[i] = null;
|
|
15127
|
+
}
|
|
15128
|
+
return [setRef, getRef];
|
|
15129
|
+
}
|
|
15130
|
+
//#endregion
|
|
15131
|
+
//#region src/components/virtual-list/hooks/use-non-passive-wheel.ts
|
|
15132
|
+
/**
|
|
15133
|
+
* Adds a non-passive `wheel` listener (so it may call `preventDefault`) to the element `getTarget`
|
|
15134
|
+
* returns, in a layout effect, only while `enabled` (a non-passive listener can hold up scrolling, so
|
|
15135
|
+
* none is added when it would not be used).
|
|
15136
|
+
*/
|
|
15137
|
+
function useNonPassiveWheel(getTarget, listener, enabled) {
|
|
15138
|
+
useLayoutEffect(() => {
|
|
15139
|
+
if (!enabled) return;
|
|
15140
|
+
const target = getTarget();
|
|
15141
|
+
if (target != null) target.addEventListener("wheel", listener, { passive: false });
|
|
15142
|
+
return () => {
|
|
15143
|
+
if (target != null) target.removeEventListener("wheel", listener);
|
|
15144
|
+
};
|
|
15145
|
+
}, [
|
|
15146
|
+
getTarget,
|
|
15147
|
+
listener,
|
|
15148
|
+
enabled
|
|
15149
|
+
]);
|
|
15150
|
+
}
|
|
15151
|
+
//#endregion
|
|
15152
|
+
//#region src/components/virtual-list/ItemColumns.tsx
|
|
15153
|
+
const ColumnsContext = createContext(null);
|
|
15154
|
+
/**
|
|
15155
|
+
* Renders the item's columns in the current column range: `renderItemWithCols` once, else
|
|
15156
|
+
* `renderCol` for each column.
|
|
15157
|
+
*/
|
|
15158
|
+
function ItemColumns(props) {
|
|
15159
|
+
const { index, item } = props;
|
|
15160
|
+
const context = useContext(ColumnsContext);
|
|
15161
|
+
if (!context) return null;
|
|
15162
|
+
const { startIndex, endIndex, columns, renderCol, renderItemWithCols, getLeft } = context;
|
|
15163
|
+
if (renderItemWithCols != null) return renderItemWithCols({
|
|
15164
|
+
itemIndex: index,
|
|
15165
|
+
startColIndex: startIndex,
|
|
15166
|
+
endColIndex: endIndex,
|
|
15167
|
+
allColumns: columns,
|
|
15168
|
+
item,
|
|
15169
|
+
getLeft
|
|
15170
|
+
});
|
|
15171
|
+
if (renderCol != null) {
|
|
15172
|
+
const cols = [];
|
|
15173
|
+
for (let i = startIndex; i <= endIndex; ++i) {
|
|
15174
|
+
const column = columns[i];
|
|
15175
|
+
cols.push(renderCol({
|
|
15176
|
+
column,
|
|
15177
|
+
left: getLeft(i),
|
|
15178
|
+
item,
|
|
15179
|
+
key: i
|
|
15180
|
+
}));
|
|
15181
|
+
}
|
|
15182
|
+
return cols;
|
|
15183
|
+
}
|
|
15184
|
+
return null;
|
|
15185
|
+
}
|
|
15186
|
+
/**
|
|
15187
|
+
* An iPhone, iPad or iPod, or an iPad that reports itself as a Mac with touch points
|
|
15188
|
+
* (`navigator.platform` is deprecated, but it still tells iPadOS from macOS here).
|
|
15189
|
+
*/
|
|
15190
|
+
const isIOS = !!(typeof document !== "undefined" && typeof window !== "undefined") && (/iPad|iPhone|iPod/.test(navigator.platform) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1) && !window.MSStream;
|
|
15191
|
+
let coarsePointerCache;
|
|
15192
|
+
let devicePixelRatioCache;
|
|
15193
|
+
/** Whether the primary pointer is coarse (a touch screen); read once. */
|
|
15194
|
+
function isCoarsePointer() {
|
|
15195
|
+
if (coarsePointerCache === void 0) coarsePointerCache = "matchMedia" in window && window.matchMedia("(pointer:coarse)").matches;
|
|
15196
|
+
return coarsePointerCache;
|
|
15197
|
+
}
|
|
15198
|
+
/**
|
|
15199
|
+
* What wheel deltas are divided by: `devicePixelRatio` in Chromium (`"chrome" in window`), else 1;
|
|
15200
|
+
* read once.
|
|
15201
|
+
*/
|
|
15202
|
+
function getWheelDeltaScale() {
|
|
15203
|
+
if (typeof document === "undefined") return 1;
|
|
15204
|
+
if (devicePixelRatioCache === void 0) devicePixelRatioCache = "chrome" in window ? window.devicePixelRatio : 1;
|
|
15205
|
+
return devicePixelRatioCache;
|
|
15206
|
+
}
|
|
15207
|
+
/**
|
|
15208
|
+
* Makes a runner that runs a callback in the next animation frame. Each runner requests one frame at
|
|
15209
|
+
* a time: a callback given before it fires replaces the previous one. One per list, so lists never
|
|
15210
|
+
* replace each other's callbacks.
|
|
15211
|
+
*/
|
|
15212
|
+
function createLatestInNextFrame() {
|
|
15213
|
+
let nextFrameCallback;
|
|
15214
|
+
let nextFrameHandle;
|
|
15215
|
+
return (callback) => {
|
|
15216
|
+
nextFrameCallback = callback;
|
|
15217
|
+
if (nextFrameHandle === void 0) nextFrameHandle = requestAnimationFrame(() => {
|
|
15218
|
+
nextFrameHandle = void 0;
|
|
15219
|
+
nextFrameCallback?.();
|
|
15220
|
+
});
|
|
15221
|
+
};
|
|
15222
|
+
}
|
|
15223
|
+
//#endregion
|
|
15224
|
+
//#region src/components/virtual-list/utils/align.ts
|
|
15225
|
+
/**
|
|
15226
|
+
* The `scrollTop` that puts an item (its top `itemTop` in the list's content, its height `itemSize`) at
|
|
15227
|
+
* `align` in a view `viewportHeight` high, inside the margins: its top `scrollMarginTop` below the view's
|
|
15228
|
+
* top (`"start"`), its bottom `scrollMarginBottom` above the view's bottom (`"end"`), or its middle in the
|
|
15229
|
+
* middle of the area between them (`"center"`). Clamped to `0 .. maxScrollTop`.
|
|
15230
|
+
*/
|
|
15231
|
+
function getAlignedScrollTop({ itemTop, itemSize, viewportHeight, align, scrollMarginTop, scrollMarginBottom, maxScrollTop }) {
|
|
15232
|
+
let top;
|
|
15233
|
+
if (align === "start") top = itemTop - scrollMarginTop;
|
|
15234
|
+
else if (align === "end") top = itemTop + itemSize - viewportHeight + scrollMarginBottom;
|
|
15235
|
+
else {
|
|
15236
|
+
const area = viewportHeight - scrollMarginTop - scrollMarginBottom;
|
|
15237
|
+
top = itemTop + itemSize / 2 - scrollMarginTop - area / 2;
|
|
15238
|
+
}
|
|
15239
|
+
return Math.max(0, Math.min(top, Math.max(0, maxScrollTop)));
|
|
15240
|
+
}
|
|
15241
|
+
//#endregion
|
|
15242
|
+
//#region src/components/virtual-list/utils/measure.ts
|
|
15243
|
+
/**
|
|
15244
|
+
* Where each in-flow row starts in the visible-items element `container`, and where the rows end: in one
|
|
15245
|
+
* pass of reads, no writes, so the browser lays out at most once.
|
|
15246
|
+
*
|
|
15247
|
+
* `starts[i]` is the top of the border box of `rows[i]` (in document order), relative to the container's
|
|
15248
|
+
* padding edge; `end` is the container's inner height. The container is a block formatting context
|
|
15249
|
+
* (`display: flow-root`, see `constants.ts`) and `position: relative`, so it is the rows' offsetParent, the
|
|
15250
|
+
* first row's top margin and the last row's bottom margin stay inside it, and between two rows sits their
|
|
15251
|
+
* collapsed margin, whatever the consumer's CSS (a child's margins collapsing through a row included).
|
|
15252
|
+
*
|
|
15253
|
+
* Read from the boxes' rects (sub-pixel, so a span does not change with the rounding of its position), but
|
|
15254
|
+
* checked against the layout's own `offsetTop` / `clientHeight` (whole pixels, and blind to transforms):
|
|
15255
|
+
* where they disagree by more than a pixel, a transform is at work (the row's own, say an entrance
|
|
15256
|
+
* animation, or a scale on an ancestor, say a dialog zooming in) and the layout's value is used, since
|
|
15257
|
+
* the rows are laid out, and measured again on resize, without it.
|
|
15258
|
+
*
|
|
15259
|
+
* A row that is not laid out (`display: none`, so no offsetParent) takes no space: it starts where the
|
|
15260
|
+
* next one does.
|
|
15261
|
+
*/
|
|
15262
|
+
function measureRowStarts(container, rows) {
|
|
15263
|
+
const frame = readFrame(container);
|
|
15264
|
+
const starts = rows.map((row) => readRowStart(frame, row));
|
|
15265
|
+
let next = frame.end;
|
|
15266
|
+
for (let i = starts.length - 1; i >= 0; --i) next = starts[i] ??= next;
|
|
15267
|
+
return {
|
|
15268
|
+
starts,
|
|
15269
|
+
end: frame.end
|
|
15270
|
+
};
|
|
15271
|
+
}
|
|
15272
|
+
/** Where `row` starts in `container` (see `measureRowStarts`); 0 when it is not laid out. */
|
|
15273
|
+
function measureRowStart(container, row) {
|
|
15274
|
+
return readRowStart(readFrame(container), row) ?? 0;
|
|
15275
|
+
}
|
|
15276
|
+
function readFrame(container) {
|
|
15277
|
+
const rect = container.getBoundingClientRect();
|
|
15278
|
+
const layoutHeight = container.offsetHeight;
|
|
15279
|
+
const scaled = Math.abs(rect.height - layoutHeight) > 1;
|
|
15280
|
+
const borderY = layoutHeight - container.clientHeight;
|
|
15281
|
+
return {
|
|
15282
|
+
container,
|
|
15283
|
+
scaled,
|
|
15284
|
+
innerTop: rect.top + container.clientTop,
|
|
15285
|
+
end: scaled ? container.clientHeight : toLayoutUnit(rect.height - borderY)
|
|
15286
|
+
};
|
|
15287
|
+
}
|
|
15288
|
+
function readRowStart({ container, scaled, innerTop }, row) {
|
|
15289
|
+
if (row.offsetParent !== container) return null;
|
|
15290
|
+
const layoutTop = row.offsetTop;
|
|
15291
|
+
if (scaled) return layoutTop;
|
|
15292
|
+
const rectTop = toLayoutUnit(row.getBoundingClientRect().top - innerTop);
|
|
15293
|
+
return Math.abs(rectTop - layoutTop) > 1 ? layoutTop : rectTop;
|
|
15294
|
+
}
|
|
15295
|
+
/**
|
|
15296
|
+
* The space each of `rows` takes: from where it starts to where the next one starts, the last one to the
|
|
15297
|
+
* end of the rows (see `measureRowStarts`). So the collapsed margin between two rows belongs to the upper
|
|
15298
|
+
* one, and the spans add up to the rows' laid-out height from the first row's border box on. `firstStart`
|
|
15299
|
+
* is where the first row starts (its top margin, plus the margins of its first children that collapse
|
|
15300
|
+
* through it): space before the rows that no span holds (the list keeps it apart, see VirtualList's
|
|
15301
|
+
* `leadingMarginRef` and `applyLayoutStyles`).
|
|
15302
|
+
*/
|
|
15303
|
+
function measureRowSpans(container, rows) {
|
|
15304
|
+
const { starts, end } = measureRowStarts(container, rows);
|
|
15305
|
+
return {
|
|
15306
|
+
spans: starts.map((start, i) => (i + 1 < starts.length ? starts[i + 1] : end) - start),
|
|
15307
|
+
firstStart: starts[0] ?? 0
|
|
15308
|
+
};
|
|
15309
|
+
}
|
|
15310
|
+
/**
|
|
15311
|
+
* Rounded to 1/64px, the unit browsers lay out in: rect differences carry floating-point noise (with page
|
|
15312
|
+
* zoom), and a size that differs from the cached one by noise alone would count as a resize.
|
|
15313
|
+
*/
|
|
15314
|
+
function toLayoutUnit(value) {
|
|
15315
|
+
return Math.round(value * 64) / 64;
|
|
15316
|
+
}
|
|
15317
|
+
//#endregion
|
|
15318
|
+
//#region src/components/virtual-list/utils/range.ts
|
|
15319
|
+
/**
|
|
15320
|
+
* The items to render: from the item at `scrollTop - paddingTop` through as many items as fit in
|
|
15321
|
+
* `listHeight` at their minimum size, plus one (no overscan). Empty (`0..-1`) before the list has been
|
|
15322
|
+
* measured or when there are no items.
|
|
15323
|
+
*/
|
|
15324
|
+
function getVerticalRange({ fenwickTree, scrollTop, listHeight, itemMinSize, itemCount, paddingTop }) {
|
|
15325
|
+
if (listHeight === void 0 || itemCount === 0) return {
|
|
15326
|
+
startIndex: 0,
|
|
15327
|
+
endIndex: -1
|
|
15328
|
+
};
|
|
15329
|
+
const startIndex = Math.max(fenwickTree.getBound(scrollTop - paddingTop), 0);
|
|
15330
|
+
return {
|
|
15331
|
+
startIndex,
|
|
15332
|
+
endIndex: Math.min(startIndex + Math.ceil(listHeight / itemMinSize + 1), itemCount - 1)
|
|
15333
|
+
};
|
|
15334
|
+
}
|
|
15335
|
+
/** The columns to render: the ones in `scrollLeft .. scrollLeft + listWidth`, plus one on each side. */
|
|
15336
|
+
function getHorizontalRange({ xFenwickTree, scrollLeft, listWidth, columnCount }) {
|
|
15337
|
+
if (xFenwickTree === null || columnCount === 0) return {
|
|
15338
|
+
startIndex: 0,
|
|
15339
|
+
endIndex: 0
|
|
15340
|
+
};
|
|
15341
|
+
else return {
|
|
15342
|
+
startIndex: Math.max(xFenwickTree.getBound(scrollLeft) - 1, 0),
|
|
15343
|
+
endIndex: Math.min(xFenwickTree.getBound(scrollLeft + listWidth) + 1, columnCount - 1)
|
|
15344
|
+
};
|
|
15345
|
+
}
|
|
15346
|
+
/** Whether `a` and `b` have the same length and every key of `a` is in `b`. */
|
|
15347
|
+
function haveSameKeys(a, b) {
|
|
15348
|
+
if (a.length !== b.length) return false;
|
|
15349
|
+
for (const key of a) if (!b.includes(key)) return false;
|
|
15350
|
+
return true;
|
|
15351
|
+
}
|
|
15352
|
+
/** Whether `el` or one of its ancestors is hidden with an inline `display: none`. */
|
|
15353
|
+
function isHiddenByDisplayNone(el) {
|
|
15354
|
+
let current = el;
|
|
15355
|
+
while (current !== null) {
|
|
15356
|
+
if (current.style.display === "none") return true;
|
|
15357
|
+
current = current.parentElement;
|
|
15358
|
+
}
|
|
15359
|
+
return false;
|
|
15360
|
+
}
|
|
15361
|
+
function px(value) {
|
|
15362
|
+
if (value !== void 0) return `${value}px`;
|
|
15363
|
+
}
|
|
15364
|
+
//#endregion
|
|
15365
|
+
//#region src/components/virtual-list/resize-observer/resize-observer-delegate.ts
|
|
15366
|
+
/**
|
|
15367
|
+
* Observes elements with one ResizeObserver, created on the first `observe()`, and calls each
|
|
15368
|
+
* element's own callback. Entries whose target is detached (no `parentElement`) are ignored.
|
|
15369
|
+
* `runAfterEntries(fn)` (from an element's callback) runs `fn` once after every entry of the current
|
|
15370
|
+
* notification has been delivered: VirtualList measures all of a list's rows once per notification,
|
|
15371
|
+
* not once per row.
|
|
15372
|
+
*/
|
|
15373
|
+
var ResizeObserverDelegate = class {
|
|
15374
|
+
observer = void 0;
|
|
15375
|
+
callbacksMap = /* @__PURE__ */ new Map();
|
|
15376
|
+
observedDomSet = /* @__PURE__ */ new WeakSet();
|
|
15377
|
+
afterEntries = /* @__PURE__ */ new Set();
|
|
15378
|
+
/** The shared observer, created on first use; `null` without a native ResizeObserver. */
|
|
15379
|
+
ensureObserver() {
|
|
15380
|
+
if (this.observer === void 0) this.observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(this.callback.bind(this));
|
|
15381
|
+
return this.observer;
|
|
15382
|
+
}
|
|
15383
|
+
callback(entries) {
|
|
15384
|
+
for (const entry of entries) {
|
|
15385
|
+
if (!entry.target.parentElement) continue;
|
|
15386
|
+
const callback = this.callbacksMap.get(entry.target);
|
|
15387
|
+
if (callback) callback(entry);
|
|
15388
|
+
}
|
|
15389
|
+
const afterEntries = [...this.afterEntries];
|
|
15390
|
+
this.afterEntries.clear();
|
|
15391
|
+
for (const fn of afterEntries) fn();
|
|
15392
|
+
}
|
|
15393
|
+
runAfterEntries(fn) {
|
|
15394
|
+
this.afterEntries.add(fn);
|
|
15395
|
+
}
|
|
15396
|
+
observe(el, callback) {
|
|
15397
|
+
this.callbacksMap.set(el, callback);
|
|
15398
|
+
if (!this.observedDomSet.has(el)) {
|
|
15399
|
+
this.ensureObserver()?.observe(el);
|
|
15400
|
+
this.observedDomSet.add(el);
|
|
15401
|
+
}
|
|
15402
|
+
}
|
|
15403
|
+
unobserve(el) {
|
|
15404
|
+
this.callbacksMap.delete(el);
|
|
15405
|
+
if (this.observedDomSet.has(el)) {
|
|
15406
|
+
this.ensureObserver()?.unobserve(el);
|
|
15407
|
+
this.observedDomSet.delete(el);
|
|
15408
|
+
}
|
|
15409
|
+
}
|
|
15410
|
+
};
|
|
15411
|
+
let resizeObserverDelegate;
|
|
15412
|
+
/** The shared delegate, created on first use. */
|
|
15413
|
+
function getResizeObserverDelegate() {
|
|
15414
|
+
resizeObserverDelegate ||= new ResizeObserverDelegate();
|
|
15415
|
+
return resizeObserverDelegate;
|
|
15416
|
+
}
|
|
15417
|
+
//#endregion
|
|
15418
|
+
//#region src/components/virtual-list/resize-observer/ResizeObserverWrapper.tsx
|
|
15419
|
+
/**
|
|
15420
|
+
* Calls `children({ ref })` and observes the element `ref` receives. `onRefUpdate` runs synchronously
|
|
15421
|
+
* whenever the ref is set (VirtualList measures a new item there, before paint), and again in a
|
|
15422
|
+
* layout effect when `simulateRefUpdateTrigger` differs from its first value; `onResize` runs from the
|
|
15423
|
+
* ResizeObserver, with `isFirst` for its first call (again after re-enabling). `disabled` stops
|
|
15424
|
+
* observing; `onUnmounted` runs in a layout-effect cleanup.
|
|
15425
|
+
*/
|
|
15426
|
+
function ResizeObserverWrapper(props) {
|
|
15427
|
+
const { disabled, onResize, children, onRefUpdate, forwardedRef, simulateRefUpdateTrigger, onUnmounted } = props;
|
|
15428
|
+
const [setRef, getRef] = useMergedRef(forwardedRef);
|
|
15429
|
+
const isFirstResizeRef = useRef(true);
|
|
15430
|
+
const prevSimulateTriggerRef = useRef(simulateRefUpdateTrigger);
|
|
15431
|
+
const emitRefUpdate = useEventCallback(() => {
|
|
15432
|
+
onRefUpdate?.(getRef());
|
|
15433
|
+
});
|
|
15434
|
+
useLayoutEffect(() => {
|
|
15435
|
+
if (prevSimulateTriggerRef.current !== simulateRefUpdateTrigger) emitRefUpdate();
|
|
15436
|
+
}, [simulateRefUpdateTrigger, emitRefUpdate]);
|
|
15437
|
+
const handleResize = useEventCallback((entry) => {
|
|
15438
|
+
if (!disabled) onResize({
|
|
15439
|
+
entry,
|
|
15440
|
+
isFirst: isFirstResizeRef.current
|
|
15441
|
+
});
|
|
15442
|
+
isFirstResizeRef.current = false;
|
|
15443
|
+
});
|
|
15444
|
+
useLayoutEffect(() => {
|
|
15445
|
+
const el = getRef();
|
|
15446
|
+
if (el != null) {
|
|
15447
|
+
if (disabled) {
|
|
15448
|
+
getResizeObserverDelegate().unobserve(el);
|
|
15449
|
+
isFirstResizeRef.current = true;
|
|
15450
|
+
} else {
|
|
15451
|
+
getResizeObserverDelegate().observe(el, handleResize);
|
|
15452
|
+
return () => {
|
|
15453
|
+
getResizeObserverDelegate().unobserve(el);
|
|
15454
|
+
};
|
|
15455
|
+
}
|
|
15456
|
+
}
|
|
15457
|
+
}, [
|
|
15458
|
+
disabled,
|
|
15459
|
+
getRef,
|
|
15460
|
+
handleResize
|
|
15461
|
+
]);
|
|
15462
|
+
const emitUnmounted = useEventCallback(() => {
|
|
15463
|
+
onUnmounted?.();
|
|
15464
|
+
});
|
|
15465
|
+
useLayoutEffect(() => () => {
|
|
15466
|
+
emitUnmounted();
|
|
15467
|
+
}, [emitUnmounted]);
|
|
15468
|
+
return children({ ref: useEventCallback((el) => {
|
|
15469
|
+
const previous = getRef();
|
|
15470
|
+
setRef(el);
|
|
15471
|
+
onRefUpdate?.(el);
|
|
15472
|
+
if (disabled) {
|
|
15473
|
+
if (previous != null) getResizeObserverDelegate().unobserve(previous);
|
|
15474
|
+
} else if (el === null) {
|
|
15475
|
+
if (previous !== null) getResizeObserverDelegate().unobserve(previous);
|
|
15476
|
+
} else {
|
|
15477
|
+
if (previous !== null && previous !== el) getResizeObserverDelegate().unobserve(previous);
|
|
15478
|
+
getResizeObserverDelegate().observe(el, handleResize);
|
|
15479
|
+
}
|
|
15480
|
+
}) });
|
|
15481
|
+
}
|
|
15482
|
+
//#endregion
|
|
15483
|
+
//#region src/components/virtual-list/hooks/use-ios-momentum-compensation.ts
|
|
15484
|
+
/**
|
|
15485
|
+
* On iOS, writing `scrollTop` during momentum scrolling stops the fling. So while a touch scroll is
|
|
15486
|
+
* active, size changes above the view are accumulated and applied as `translateY(-compensation)` on
|
|
15487
|
+
* the items element; they are written to `scrollTop` (flushed) one second after the finger lifted, at
|
|
15488
|
+
* the top or bottom edge, or on the next touchstart.
|
|
15489
|
+
*/
|
|
15490
|
+
function useIosMomentumCompensation({ enabled, listElRef, itemsElRef, syncViewport, compensationYRef: iosCompYRef }) {
|
|
15491
|
+
const touchSessionActiveRef = useRef(false);
|
|
15492
|
+
const idleTimerRef = useRef(null);
|
|
15493
|
+
const scrollSuppressTimerRef = useRef(null);
|
|
15494
|
+
const forcedScrollTopRef = useRef(null);
|
|
15495
|
+
const touchEndedRef = useRef(true);
|
|
15496
|
+
const applyCompensationTransform = useEventCallback(() => {
|
|
15497
|
+
const itemsEl = itemsElRef.current;
|
|
15498
|
+
if (itemsEl) {
|
|
15499
|
+
const compensation = iosCompYRef.current;
|
|
15500
|
+
if (compensation === 0) itemsEl.style.transform = "";
|
|
15501
|
+
else itemsEl.style.transform = `translateY(${-compensation}px)`;
|
|
15502
|
+
}
|
|
15503
|
+
});
|
|
15504
|
+
const clearIdleTimer = useEventCallback(() => {
|
|
15505
|
+
if (idleTimerRef.current !== null) {
|
|
15506
|
+
clearTimeout(idleTimerRef.current);
|
|
15507
|
+
idleTimerRef.current = null;
|
|
15508
|
+
}
|
|
15509
|
+
});
|
|
15510
|
+
const startScrollSuppression = useEventCallback(() => {
|
|
15511
|
+
if (scrollSuppressTimerRef.current !== null) clearTimeout(scrollSuppressTimerRef.current);
|
|
15512
|
+
scrollSuppressTimerRef.current = setTimeout(() => {
|
|
15513
|
+
scrollSuppressTimerRef.current = null;
|
|
15514
|
+
forcedScrollTopRef.current = null;
|
|
15515
|
+
syncViewport();
|
|
15516
|
+
}, 200);
|
|
15517
|
+
});
|
|
15518
|
+
const flushCompensation = useEventCallback((reason) => {
|
|
15519
|
+
clearIdleTimer();
|
|
15520
|
+
const compensation = iosCompYRef.current;
|
|
15521
|
+
iosCompYRef.current = 0;
|
|
15522
|
+
touchSessionActiveRef.current = false;
|
|
15523
|
+
applyCompensationTransform();
|
|
15524
|
+
const listEl = listElRef.current;
|
|
15525
|
+
if (listEl && compensation !== 0) {
|
|
15526
|
+
const scrollTop = listEl.scrollTop + compensation;
|
|
15527
|
+
listEl.scrollTop = scrollTop;
|
|
15528
|
+
if (reason == "touchStart") {
|
|
15529
|
+
forcedScrollTopRef.current = scrollTop;
|
|
15530
|
+
startScrollSuppression();
|
|
15531
|
+
}
|
|
15532
|
+
}
|
|
15533
|
+
});
|
|
15534
|
+
const scheduleIdleFlush = useEventCallback(() => {
|
|
15535
|
+
clearIdleTimer();
|
|
15536
|
+
idleTimerRef.current = setTimeout(() => {
|
|
15537
|
+
idleTimerRef.current = null;
|
|
15538
|
+
if (touchEndedRef.current) flushCompensation("idleTimer");
|
|
15539
|
+
else scheduleIdleFlush();
|
|
15540
|
+
}, 1e3);
|
|
15541
|
+
});
|
|
15542
|
+
const handleTouchStart = useEventCallback(() => {
|
|
15543
|
+
touchEndedRef.current = false;
|
|
15544
|
+
if (touchSessionActiveRef.current) flushCompensation("touchStart");
|
|
15545
|
+
touchSessionActiveRef.current = true;
|
|
15546
|
+
scheduleIdleFlush();
|
|
15547
|
+
});
|
|
15548
|
+
const handleTouchEnd = useEventCallback(() => {
|
|
15549
|
+
touchEndedRef.current = true;
|
|
15550
|
+
});
|
|
15551
|
+
const handleScrollDuringTouch = useEventCallback(() => {
|
|
15552
|
+
if (touchSessionActiveRef.current) scheduleIdleFlush();
|
|
15553
|
+
});
|
|
15554
|
+
const clampCompensation = useEventCallback(() => {
|
|
15555
|
+
const listEl = listElRef.current;
|
|
15556
|
+
if (!listEl) return;
|
|
15557
|
+
const maxScrollTop = listEl.scrollHeight - listEl.clientHeight;
|
|
15558
|
+
iosCompYRef.current = Math.max(-listEl.scrollTop, Math.min(maxScrollTop - listEl.scrollTop, iosCompYRef.current));
|
|
15559
|
+
});
|
|
15560
|
+
const addCompensation = useEventCallback((deltaY) => {
|
|
15561
|
+
if (!enabled || !touchSessionActiveRef.current) return false;
|
|
15562
|
+
iosCompYRef.current += deltaY;
|
|
15563
|
+
clampCompensation();
|
|
15564
|
+
const listEl = listElRef.current;
|
|
15565
|
+
if (listEl) {
|
|
15566
|
+
if (listEl.scrollTop <= 0) {
|
|
15567
|
+
if (touchEndedRef.current) flushCompensation("scrollToTop");
|
|
15568
|
+
return true;
|
|
15569
|
+
}
|
|
15570
|
+
if (iosCompYRef.current > 0) {
|
|
15571
|
+
const maxScrollTop = listEl.scrollHeight - listEl.clientHeight;
|
|
15572
|
+
if (listEl.scrollTop + iosCompYRef.current >= maxScrollTop) {
|
|
15573
|
+
flushCompensation("overBottom");
|
|
15574
|
+
return true;
|
|
15575
|
+
}
|
|
15576
|
+
}
|
|
15577
|
+
}
|
|
15578
|
+
applyCompensationTransform();
|
|
15579
|
+
return true;
|
|
15580
|
+
});
|
|
15581
|
+
useLayoutEffect(() => {
|
|
15582
|
+
if (!enabled) return;
|
|
15583
|
+
const listEl = listElRef.current;
|
|
15584
|
+
if (!listEl) return;
|
|
15585
|
+
const itemsEl = itemsElRef.current;
|
|
15586
|
+
listEl.addEventListener("touchstart", handleTouchStart, { passive: true });
|
|
15587
|
+
listEl.addEventListener("touchend", handleTouchEnd, { passive: true });
|
|
15588
|
+
listEl.addEventListener("touchcancel", handleTouchEnd, { passive: true });
|
|
15589
|
+
listEl.addEventListener("scroll", handleScrollDuringTouch, { passive: true });
|
|
15590
|
+
return () => {
|
|
15591
|
+
listEl.removeEventListener("touchstart", handleTouchStart);
|
|
15592
|
+
listEl.removeEventListener("touchend", handleTouchEnd);
|
|
15593
|
+
listEl.removeEventListener("touchcancel", handleTouchEnd);
|
|
15594
|
+
listEl.removeEventListener("scroll", handleScrollDuringTouch);
|
|
15595
|
+
clearIdleTimer();
|
|
15596
|
+
if (scrollSuppressTimerRef.current !== null) {
|
|
15597
|
+
clearTimeout(scrollSuppressTimerRef.current);
|
|
15598
|
+
scrollSuppressTimerRef.current = null;
|
|
15599
|
+
forcedScrollTopRef.current = null;
|
|
15600
|
+
}
|
|
15601
|
+
if (iosCompYRef.current !== 0) {
|
|
15602
|
+
if (itemsEl) itemsEl.style.transform = "";
|
|
15603
|
+
listEl.scrollTop += iosCompYRef.current;
|
|
15604
|
+
iosCompYRef.current = 0;
|
|
15605
|
+
}
|
|
15606
|
+
touchSessionActiveRef.current = false;
|
|
15607
|
+
};
|
|
15608
|
+
}, [
|
|
15609
|
+
enabled,
|
|
15610
|
+
listElRef,
|
|
15611
|
+
itemsElRef,
|
|
15612
|
+
handleTouchStart,
|
|
15613
|
+
handleTouchEnd,
|
|
15614
|
+
handleScrollDuringTouch,
|
|
15615
|
+
clearIdleTimer,
|
|
15616
|
+
iosCompYRef
|
|
15617
|
+
]);
|
|
15618
|
+
return {
|
|
15619
|
+
addCompensation,
|
|
15620
|
+
isScrollSuppressedWithForceRestore: useEventCallback((scrollTop) => {
|
|
15621
|
+
const suppressed = scrollSuppressTimerRef.current !== null;
|
|
15622
|
+
if (suppressed && listElRef.current && forcedScrollTopRef.current !== null && scrollTop === 0) listElRef.current.scrollTop = forcedScrollTopRef.current;
|
|
15623
|
+
return suppressed;
|
|
15624
|
+
})
|
|
15625
|
+
};
|
|
15626
|
+
}
|
|
15627
|
+
//#endregion
|
|
15628
|
+
//#region src/components/virtual-list/hooks/use-keyboard-scroll.ts
|
|
15629
|
+
/**
|
|
15630
|
+
* PageUp / PageDown / Space (Shift+Space up) scroll by the list's height minus `pageBottomOcclusion`
|
|
15631
|
+
* (what covers the bottom of the list, e.g. an input box) minus 32 px; Home / End and Cmd+Up / Cmd+Down
|
|
15632
|
+
* call `scrollTo({ position: "top" | "bottom" })`. Only keys pressed on the list itself (the scroll
|
|
15633
|
+
* container has focus) scroll it: a key pressed in a row (a button, a checkbox, a text field, a segmented
|
|
15634
|
+
* control) is left to that control, and so is a key a handler has already taken (`defaultPrevented`).
|
|
15635
|
+
*/
|
|
15636
|
+
function useKeyboardScroll(scrollTo, { pageBottomOcclusion = 0 } = {}) {
|
|
15637
|
+
const occlusionRef = useRef(0);
|
|
15638
|
+
occlusionRef.current = pageBottomOcclusion;
|
|
15639
|
+
return { onKeyDown: useCallback((event) => {
|
|
15640
|
+
if (event.defaultPrevented || event.target !== event.currentTarget) return;
|
|
15641
|
+
if (event.key === "PageDown" || event.key === "PageUp" || event.key === " " || event.key === "Spacebar") {
|
|
15642
|
+
const page = Math.max(event.currentTarget.clientHeight - occlusionRef.current - 32, 0);
|
|
15643
|
+
if (page <= 0) return;
|
|
15644
|
+
event.preventDefault();
|
|
15645
|
+
event.currentTarget.scrollTop += event.key === "PageUp" || (event.key === " " || event.key === "Spacebar") && event.shiftKey ? -page : page;
|
|
15646
|
+
return;
|
|
15647
|
+
}
|
|
15648
|
+
let position = null;
|
|
15649
|
+
switch (event.key) {
|
|
15650
|
+
case "Home":
|
|
15651
|
+
position = "top";
|
|
15652
|
+
break;
|
|
15653
|
+
case "ArrowUp":
|
|
15654
|
+
if (event.metaKey) position = "top";
|
|
15655
|
+
break;
|
|
15656
|
+
case "End":
|
|
15657
|
+
position = "bottom";
|
|
15658
|
+
break;
|
|
15659
|
+
case "ArrowDown": if (event.metaKey) position = "bottom";
|
|
15660
|
+
}
|
|
15661
|
+
if (position) {
|
|
15662
|
+
event.preventDefault();
|
|
15663
|
+
scrollTo({ position });
|
|
15664
|
+
}
|
|
15665
|
+
}, [scrollTo]) };
|
|
15666
|
+
}
|
|
15667
|
+
//#endregion
|
|
15668
|
+
//#region src/components/virtual-list/hooks/use-non-virtual-content-resize.ts
|
|
15669
|
+
/**
|
|
15670
|
+
* While `nonVirtual`, observes the items element and calls `onContentResize` when it resizes (with
|
|
15671
|
+
* virtualization on, the list calls it from its own measurements).
|
|
15672
|
+
*/
|
|
15673
|
+
function useNonVirtualContentResize(itemsElRef, onContentResize, nonVirtual) {
|
|
15674
|
+
const notifyContentResize = useEventCallback(() => {
|
|
15675
|
+
onContentResize?.();
|
|
15676
|
+
});
|
|
15677
|
+
useLayoutEffect(() => {
|
|
15678
|
+
const itemsEl = itemsElRef.current;
|
|
15679
|
+
if (itemsEl && nonVirtual) {
|
|
15680
|
+
getResizeObserverDelegate().observe(itemsEl, notifyContentResize);
|
|
15681
|
+
return () => {
|
|
15682
|
+
getResizeObserverDelegate().unobserve(itemsEl);
|
|
15683
|
+
};
|
|
15684
|
+
}
|
|
15685
|
+
}, [
|
|
15686
|
+
itemsElRef,
|
|
15687
|
+
notifyContentResize,
|
|
15688
|
+
nonVirtual
|
|
15689
|
+
]);
|
|
15690
|
+
}
|
|
15691
|
+
//#endregion
|
|
15692
|
+
//#region src/components/virtual-list/hooks/use-scroll-anchor.ts
|
|
15693
|
+
/**
|
|
15694
|
+
* Finds the first child of `container` whose box crosses the top of `scroller` (a binary search over
|
|
15695
|
+
* `getBoundingClientRect()`) and saves it with its top in `anchorRef`, or null when there is none.
|
|
15696
|
+
*/
|
|
15697
|
+
function snapshotAnchorElementStateAndSaveToRef(anchorRef, scroller, container) {
|
|
15698
|
+
const children = Array.from(container.children);
|
|
15699
|
+
const scrollerTop = scroller.getBoundingClientRect().top;
|
|
15700
|
+
let lo = 0;
|
|
15701
|
+
let hi = children.length - 1;
|
|
15702
|
+
let found = -1;
|
|
15703
|
+
let foundTop = 0;
|
|
15704
|
+
while (lo <= hi) {
|
|
15705
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
15706
|
+
const rect = children[mid].getBoundingClientRect();
|
|
15707
|
+
if (rect.top <= scrollerTop && rect.bottom > scrollerTop) {
|
|
15708
|
+
found = mid;
|
|
15709
|
+
hi = mid - 1;
|
|
15710
|
+
foundTop = rect.top;
|
|
15711
|
+
} else lo = mid + 1;
|
|
15712
|
+
}
|
|
15713
|
+
if (found < 0) anchorRef.current = null;
|
|
15714
|
+
else {
|
|
15715
|
+
const element = children[found];
|
|
15716
|
+
if (element) anchorRef.current = {
|
|
15717
|
+
element,
|
|
15718
|
+
top: foundTop
|
|
15719
|
+
};
|
|
15720
|
+
else anchorRef.current = null;
|
|
15721
|
+
}
|
|
15722
|
+
}
|
|
15723
|
+
/**
|
|
15724
|
+
* `snapshotAnchorElementStateAndSaveToRef()` remembers the item at the top of the list;
|
|
15725
|
+
* `restoreScrollStateFromAnchor()` scrolls by how far it moved since; `clearAnchor()` forgets it.
|
|
15726
|
+
*/
|
|
15727
|
+
function useScrollAnchor(listElRef, getVisibleItemsEl) {
|
|
15728
|
+
const anchorRef = useRef(null);
|
|
15729
|
+
return {
|
|
15730
|
+
snapshotAnchorElementStateAndSaveToRef: useCallback(() => {
|
|
15731
|
+
const visibleItemsEl = getVisibleItemsEl();
|
|
15732
|
+
if (listElRef.current && visibleItemsEl) snapshotAnchorElementStateAndSaveToRef(anchorRef, listElRef.current, visibleItemsEl);
|
|
15733
|
+
}, [listElRef, getVisibleItemsEl]),
|
|
15734
|
+
restoreScrollStateFromAnchor: useCallback(() => {
|
|
15735
|
+
if (!anchorRef.current) return;
|
|
15736
|
+
const moved = anchorRef.current.element.getBoundingClientRect().top - anchorRef.current.top;
|
|
15737
|
+
listElRef.current?.scrollBy(0, moved);
|
|
15738
|
+
}, [listElRef]),
|
|
15739
|
+
clearAnchor: useCallback(() => {
|
|
15740
|
+
anchorRef.current = null;
|
|
15741
|
+
}, [])
|
|
15742
|
+
};
|
|
15743
|
+
}
|
|
15744
|
+
//#endregion
|
|
15745
|
+
//#region src/components/virtual-list/VirtualListContext.ts
|
|
15746
|
+
const VirtualListContext = createContext(null);
|
|
15747
|
+
//#endregion
|
|
15748
|
+
//#region src/components/virtual-list/VirtualList.tsx
|
|
15749
|
+
/**
|
|
15750
|
+
* The most range updates one aligned jump goes through before it stops (each one mounts and measures the
|
|
15751
|
+
* rows the previous position brought into view; it is usually stable after 2-3).
|
|
15752
|
+
*/
|
|
15753
|
+
const MAX_ALIGN_STEPS = 10;
|
|
15754
|
+
function VirtualList(props) {
|
|
15755
|
+
const { virtual = true, sessionId, showScrollbar = false, columns = EMPTY_ARRAY, renderCol, renderItemWithCols, items = EMPTY_ARRAY, itemMinSize, itemEstimatedSize, itemResizable = false, itemsStyle, visibleItemsTag = "div", visibleItemsProps, ignoreItemResize = false, onScroll, onWheel, onResize, defaultScrollKey, defaultScrollIndex, getKey, paddingTop = 0, paddingBottom = 0, children: renderItemChild, ItemComponent, empty, emptyAlign = "start", className, style, itemsClassName, footer, footerAfterBottomPadding, header, domRef, ref: _ref, onContentResize, onTransformChange, componentRef, printable, catchWheel = false, onRenderAnomaly, keyboardPageBottomOcclusion = 0, focusable = true, keepAnchorOnItemsChange = false, canAnchorItem, onKeyDown: onNativeKeyDown, ...nativeProps } = props;
|
|
15756
|
+
const listElRef = useRef(null);
|
|
15757
|
+
const [mergedListRefSetter] = useMergedRef(domRef, listElRef);
|
|
15758
|
+
const itemsElRef = useRef(null);
|
|
15759
|
+
const getVisibleItemsEl = useCallback(() => itemsElRef.current?.children[0] || null, []);
|
|
15760
|
+
const [[renderReason, renderCallback, renderVersion], setRenderState] = useState([
|
|
15761
|
+
null,
|
|
15762
|
+
null,
|
|
15763
|
+
0
|
|
15764
|
+
]);
|
|
15765
|
+
const viewportRef = useRef({
|
|
15766
|
+
listHeight: void 0,
|
|
15767
|
+
scrollTop: 0,
|
|
15768
|
+
scrollLeft: 0,
|
|
15769
|
+
listWidth: 0
|
|
15770
|
+
});
|
|
15771
|
+
const rangeRef = useRef({
|
|
15772
|
+
prependIndexes: [],
|
|
15773
|
+
verticalStartIndex: 0,
|
|
15774
|
+
verticalEndIndex: -1,
|
|
15775
|
+
horizontalStartIndex: 0,
|
|
15776
|
+
horizontalEndIndex: -1
|
|
15777
|
+
});
|
|
15778
|
+
/**
|
|
15779
|
+
* The rows kept mounted outside the range (absolutely positioned at their offsets): the rows holding
|
|
15780
|
+
* the text selection and the row holding focus (`focusedKeyRef`).
|
|
15781
|
+
*/
|
|
15782
|
+
const keptKeysRef = useRef([]);
|
|
15783
|
+
/** The key of the row that holds focus (`document.activeElement` is in it), tracked by focusin / focusout. */
|
|
15784
|
+
const focusedKeyRef = useRef(null);
|
|
15785
|
+
const keyToHeightOffsetRef = useRef(/* @__PURE__ */ new Map());
|
|
15786
|
+
const leadingMarginRef = useRef(0);
|
|
15787
|
+
/**
|
|
15788
|
+
* Where the first mounted row in flow starts in the visible-items element (its top margin there,
|
|
15789
|
+
* since nothing above it is mounted), as last written by `applyLayoutStyles`.
|
|
15790
|
+
*/
|
|
15791
|
+
const firstRowStartRef = useRef(0);
|
|
15792
|
+
const pendingScrollTaskRef = useRef(null);
|
|
15793
|
+
const pendingScrollTaskTimeoutRef = useRef(null);
|
|
15794
|
+
const pendingScrollTaskRafRef = useRef(null);
|
|
15795
|
+
const wheelScrollingRef = useRef(false);
|
|
15796
|
+
/** Clears `wheelScrollingRef` in the next frame; this list's own, so other lists cannot replace it. */
|
|
15797
|
+
const [runLatestInNextFrame] = useState(createLatestInNextFrame);
|
|
15798
|
+
/** The wheel is only taken over (catchWheel) where the pointer is not coarse. */
|
|
15799
|
+
const mayUseWheelRef = useRef(typeof document === "undefined" || !isCoarsePointer());
|
|
15800
|
+
const reportRenderAnomaly = useEventCallback((anomaly) => {
|
|
15801
|
+
onRenderAnomaly?.(anomaly);
|
|
15802
|
+
});
|
|
15803
|
+
const forcedRenderTimesRef = useRef([]);
|
|
15804
|
+
/**
|
|
15805
|
+
* The offset applied by the iOS compensation, created here (not in its hook) because
|
|
15806
|
+
* `keepAnchorOnItemsChange` reads it while rendering, before the hook is called.
|
|
15807
|
+
*/
|
|
15808
|
+
const compensationYRef = useRef(0);
|
|
15809
|
+
/** The items, Fenwick tree and session of the last commit (the tree is updated in place by measurements). */
|
|
15810
|
+
const committedLayoutRef = useRef(null);
|
|
15811
|
+
const itemsAnchor = useMemo(() => {
|
|
15812
|
+
const committed = committedLayoutRef.current;
|
|
15813
|
+
const listEl = listElRef.current;
|
|
15814
|
+
if (!keepAnchorOnItemsChange || !virtual || committed === null || listEl === null || committed.items === items || committed.sessionId !== sessionId) return null;
|
|
15815
|
+
return findItemsAnchor({
|
|
15816
|
+
prevItems: committed.items,
|
|
15817
|
+
prevTree: committed.tree,
|
|
15818
|
+
prevPrependIndexes: rangeRef.current.prependIndexes,
|
|
15819
|
+
nextItems: items,
|
|
15820
|
+
getKey,
|
|
15821
|
+
canAnchor: canAnchorItem,
|
|
15822
|
+
top: listEl.scrollTop + compensationYRef.current - paddingTop - leadingMarginRef.current
|
|
15823
|
+
});
|
|
15824
|
+
}, [items]);
|
|
15825
|
+
/** The anchor whose range has been committed to `rangeRef` (so it is applied once). */
|
|
15826
|
+
const appliedItemsAnchorRef = useRef(null);
|
|
15827
|
+
const pendingItemsAnchor = itemsAnchor !== null && itemsAnchor !== appliedItemsAnchorRef.current ? itemsAnchor : null;
|
|
15828
|
+
let renderedRange = rangeRef.current;
|
|
15829
|
+
if (pendingItemsAnchor !== null) {
|
|
15830
|
+
const [verticalStartIndex, verticalEndIndex] = getAnchoredRange(renderedRange.verticalStartIndex, renderedRange.verticalEndIndex, pendingItemsAnchor.index, items.length);
|
|
15831
|
+
renderedRange = {
|
|
15832
|
+
...renderedRange,
|
|
15833
|
+
prependIndexes: pendingItemsAnchor.prependIndexes,
|
|
15834
|
+
verticalStartIndex,
|
|
15835
|
+
verticalEndIndex
|
|
15836
|
+
};
|
|
15837
|
+
}
|
|
15838
|
+
const { prependIndexes, verticalStartIndex: startIndex, verticalEndIndex: endIndex, horizontalStartIndex: colStartIndex, horizontalEndIndex: colEndIndex } = renderedRange;
|
|
15839
|
+
const keyToIndexRef = useRef(void 0);
|
|
15840
|
+
const prevSessionIdRef = useRef(sessionId);
|
|
15841
|
+
const sessionChangedRef = useRef(false);
|
|
15842
|
+
const lastTransformSignatureRef = useRef(null);
|
|
15843
|
+
const clearPendingScrollTask = useCallback(() => {
|
|
15844
|
+
if (pendingScrollTaskTimeoutRef.current !== null) {
|
|
15845
|
+
window.clearTimeout(pendingScrollTaskTimeoutRef.current);
|
|
15846
|
+
pendingScrollTaskTimeoutRef.current = null;
|
|
15847
|
+
}
|
|
15848
|
+
if (pendingScrollTaskRafRef.current != null) {
|
|
15849
|
+
window.cancelAnimationFrame(pendingScrollTaskRafRef.current);
|
|
15850
|
+
pendingScrollTaskRafRef.current = null;
|
|
15851
|
+
}
|
|
15852
|
+
pendingScrollTaskRef.current = null;
|
|
15853
|
+
}, []);
|
|
15854
|
+
useMemo(() => {
|
|
15855
|
+
if (prevSessionIdRef.current === sessionId) return;
|
|
15856
|
+
prevSessionIdRef.current = sessionId;
|
|
15857
|
+
sessionChangedRef.current = true;
|
|
15858
|
+
clearPendingScrollTask();
|
|
15859
|
+
wheelScrollingRef.current = false;
|
|
15860
|
+
keptKeysRef.current = [];
|
|
15861
|
+
focusedKeyRef.current = null;
|
|
15862
|
+
keyToHeightOffsetRef.current.clear();
|
|
15863
|
+
leadingMarginRef.current = 0;
|
|
15864
|
+
keyToIndexRef.current = /* @__PURE__ */ new Map();
|
|
15865
|
+
rangeRef.current = {
|
|
15866
|
+
prependIndexes: [],
|
|
15867
|
+
verticalStartIndex: 0,
|
|
15868
|
+
verticalEndIndex: -1,
|
|
15869
|
+
horizontalStartIndex: 0,
|
|
15870
|
+
horizontalEndIndex: -1
|
|
15871
|
+
};
|
|
15872
|
+
const viewport = viewportRef.current;
|
|
15873
|
+
viewport.scrollTop = 0;
|
|
15874
|
+
viewport.scrollLeft = 0;
|
|
15875
|
+
const listEl = listElRef.current;
|
|
15876
|
+
if (listEl != null) {
|
|
15877
|
+
listEl.scrollTop = 0;
|
|
15878
|
+
listEl.scrollLeft = 0;
|
|
15879
|
+
}
|
|
15880
|
+
}, [sessionId, clearPendingScrollTask]);
|
|
15881
|
+
useMemo(() => {
|
|
15882
|
+
lastTransformSignatureRef.current = null;
|
|
15883
|
+
}, [sessionId]);
|
|
15884
|
+
useMemo(() => {
|
|
15885
|
+
const keyToIndex = /* @__PURE__ */ new Map();
|
|
15886
|
+
items.forEach((item, index) => {
|
|
15887
|
+
keyToIndex.set(getKey(item), index);
|
|
15888
|
+
});
|
|
15889
|
+
keyToIndexRef.current = keyToIndex;
|
|
15890
|
+
const keptKeys = [];
|
|
15891
|
+
keptKeysRef.current.forEach((key) => {
|
|
15892
|
+
if (keyToIndexRef.current.has(key)) keptKeys.push(key);
|
|
15893
|
+
});
|
|
15894
|
+
keptKeysRef.current = keptKeys;
|
|
15895
|
+
}, [
|
|
15896
|
+
sessionId,
|
|
15897
|
+
items,
|
|
15898
|
+
getKey
|
|
15899
|
+
]);
|
|
15900
|
+
const totalColumnsWidth = useMemo(() => {
|
|
15901
|
+
if (renderCol == null && renderItemWithCols == null || columns.length === 0) return;
|
|
15902
|
+
let width = 0;
|
|
15903
|
+
columns.forEach((column) => {
|
|
15904
|
+
width += column.width;
|
|
15905
|
+
});
|
|
15906
|
+
return width;
|
|
15907
|
+
}, [
|
|
15908
|
+
sessionId,
|
|
15909
|
+
renderCol,
|
|
15910
|
+
renderItemWithCols,
|
|
15911
|
+
columns
|
|
15912
|
+
]);
|
|
15913
|
+
/**
|
|
15914
|
+
* Rebuilt whenever the items or the session change: `itemEstimatedSize` for every item plus the
|
|
15915
|
+
* measured offsets cached by key, so measurements survive re-renders and reordering.
|
|
15916
|
+
*/
|
|
15917
|
+
const fenwickTree = useMemo(() => {
|
|
15918
|
+
if (!virtual) return new FenwickTree(0, 0);
|
|
15919
|
+
const tree = new FenwickTree(items.length, itemEstimatedSize);
|
|
15920
|
+
const heightOffsets = keyToHeightOffsetRef.current;
|
|
15921
|
+
items.forEach((item, index) => {
|
|
15922
|
+
const key = getKey(item);
|
|
15923
|
+
const offset = heightOffsets.get(key);
|
|
15924
|
+
if (offset !== void 0) tree.add(index, offset);
|
|
15925
|
+
});
|
|
15926
|
+
return tree;
|
|
15927
|
+
}, [
|
|
15928
|
+
sessionId,
|
|
15929
|
+
items,
|
|
15930
|
+
itemEstimatedSize,
|
|
15931
|
+
getKey,
|
|
15932
|
+
virtual
|
|
15933
|
+
]);
|
|
15934
|
+
const visibleItems = useMemo(() => {
|
|
15935
|
+
if (!virtual) return items;
|
|
15936
|
+
if (endIndex < startIndex) return [];
|
|
15937
|
+
const visible = [];
|
|
15938
|
+
const lastIndex = Math.min(endIndex, items.length - 1);
|
|
15939
|
+
for (let i = startIndex; i <= lastIndex; ++i) visible.push(items[i]);
|
|
15940
|
+
return visible;
|
|
15941
|
+
}, [
|
|
15942
|
+
sessionId,
|
|
15943
|
+
startIndex,
|
|
15944
|
+
endIndex,
|
|
15945
|
+
items,
|
|
15946
|
+
virtual
|
|
15947
|
+
]);
|
|
15948
|
+
const xFenwickTree = useMemo(() => {
|
|
15949
|
+
if (columns.length === 0) return null;
|
|
15950
|
+
const tree = new FenwickTree(columns.length, 0);
|
|
15951
|
+
columns.forEach((column, index) => {
|
|
15952
|
+
tree.add(index, column.width);
|
|
15953
|
+
});
|
|
15954
|
+
return tree;
|
|
15955
|
+
}, [sessionId, columns]);
|
|
15956
|
+
const getColumnLeft = useEventCallback((index) => xFenwickTree !== null ? xFenwickTree.sum(index) : 0);
|
|
15957
|
+
const columnsContextValue = useMemo(() => ({
|
|
15958
|
+
startIndex: colStartIndex,
|
|
15959
|
+
endIndex: colEndIndex,
|
|
15960
|
+
columns,
|
|
15961
|
+
renderCol,
|
|
15962
|
+
renderItemWithCols,
|
|
15963
|
+
getLeft: getColumnLeft
|
|
15964
|
+
}), [
|
|
15965
|
+
colStartIndex,
|
|
15966
|
+
colEndIndex,
|
|
15967
|
+
columns,
|
|
15968
|
+
renderCol,
|
|
15969
|
+
renderItemWithCols,
|
|
15970
|
+
getColumnLeft
|
|
15971
|
+
]);
|
|
15972
|
+
/** Where the rows start in the spacer's content: `paddingTop` plus the first row's top margin. */
|
|
15973
|
+
const getRowsTop = useEventCallback(() => paddingTop + leadingMarginRef.current);
|
|
15974
|
+
/**
|
|
15975
|
+
* The mounted rows in flow, in order: the measured elements of the rendered range. Kept rows (outside
|
|
15976
|
+
* the range, absolutely positioned) are left out: they take no space in the flow.
|
|
15977
|
+
*/
|
|
15978
|
+
const getFlowRows = useEventCallback(() => {
|
|
15979
|
+
const rows = [];
|
|
15980
|
+
const visibleItemsEl = getVisibleItemsEl();
|
|
15981
|
+
if (visibleItemsEl === null) return rows;
|
|
15982
|
+
for (const el of Array.from(visibleItemsEl.children)) {
|
|
15983
|
+
const key = el.getAttribute(ITEM_KEY_ATTR);
|
|
15984
|
+
const index = key === null ? void 0 : keyToIndexRef.current.get(key);
|
|
15985
|
+
if (key !== null && index !== void 0 && index >= startIndex && index <= endIndex && el instanceof HTMLElement) rows.push({
|
|
15986
|
+
el,
|
|
15987
|
+
key,
|
|
15988
|
+
index
|
|
15989
|
+
});
|
|
15990
|
+
}
|
|
15991
|
+
return rows;
|
|
15992
|
+
});
|
|
15993
|
+
/**
|
|
15994
|
+
* Writes the layout without a render: the spacer height (the total size plus the first row's top
|
|
15995
|
+
* margin; a min-height with itemResizable, so the real content may exceed it) and the visible items'
|
|
15996
|
+
* translateY, with the CSS variables sticky children read (`--virtual-list-transform-y`,
|
|
15997
|
+
* `--virtual-list-ios-compensation-y`).
|
|
15998
|
+
*
|
|
15999
|
+
* Unlike the reference implementation (DeepSeek web): the translateY puts the border box of the first
|
|
16000
|
+
* mounted row at its offset (`firstIndex`, or the first row in the DOM), not the element's top. The visible-items element is a
|
|
16001
|
+
* block formatting context, so the first mounted row's top margin is inside it, above the row, and
|
|
16002
|
+
* does not collapse with the row before it (not mounted): the element starts that margin higher. The
|
|
16003
|
+
* collapsed gap above that row is already in the size of the row before it (see `leadingMarginRef`),
|
|
16004
|
+
* so the rows sit exactly where the tree has them whichever row is first, and moving the window's top
|
|
16005
|
+
* edge moves no row. Read before anything is written, so it forces no extra layout.
|
|
16006
|
+
*/
|
|
16007
|
+
const applyLayoutStyles = useEventCallback((firstIndex) => {
|
|
16008
|
+
if (!virtual) return;
|
|
16009
|
+
let index = Math.min(firstIndex, items.length);
|
|
16010
|
+
let firstRowStart = 0;
|
|
16011
|
+
const itemsEl = itemsElRef.current;
|
|
16012
|
+
const visibleItemsEl = itemsEl?.children[0];
|
|
16013
|
+
const firstRow = itemResizable ? getFlowRows()[0] : void 0;
|
|
16014
|
+
if (firstRow !== void 0 && visibleItemsEl instanceof HTMLElement) {
|
|
16015
|
+
index = firstRow.index;
|
|
16016
|
+
firstRowStart = measureRowStart(visibleItemsEl, firstRow.el);
|
|
16017
|
+
}
|
|
16018
|
+
firstRowStartRef.current = firstRowStart;
|
|
16019
|
+
const leadingMargin = leadingMarginRef.current;
|
|
16020
|
+
if (itemsEl) {
|
|
16021
|
+
const height = px(fenwickTree.sum() + leadingMargin) || "";
|
|
16022
|
+
if (itemResizable) {
|
|
16023
|
+
itemsEl.style.height = "";
|
|
16024
|
+
itemsEl.style.minHeight = height;
|
|
16025
|
+
} else {
|
|
16026
|
+
const changed = itemsEl.style.height !== height || itemsEl.style.minHeight !== "";
|
|
16027
|
+
itemsEl.style.height = height;
|
|
16028
|
+
itemsEl.style.minHeight = "";
|
|
16029
|
+
if (changed) onContentResize?.();
|
|
16030
|
+
}
|
|
16031
|
+
}
|
|
16032
|
+
if (visibleItemsEl instanceof HTMLElement) {
|
|
16033
|
+
const offset = px(leadingMargin + fenwickTree.sum(index) - firstRowStart);
|
|
16034
|
+
visibleItemsEl.style.transform = `translateY(${offset})`;
|
|
16035
|
+
visibleItemsEl.style.setProperty("--virtual-list-transform-y", offset);
|
|
16036
|
+
const compensation = px(compensationYRef.current);
|
|
16037
|
+
visibleItemsEl.style.setProperty("--virtual-list-ios-compensation-y", compensation);
|
|
16038
|
+
const signature = `${index}|${offset}`;
|
|
16039
|
+
if (lastTransformSignatureRef.current !== signature) {
|
|
16040
|
+
lastTransformSignatureRef.current = signature;
|
|
16041
|
+
onTransformChange?.();
|
|
16042
|
+
}
|
|
16043
|
+
}
|
|
16044
|
+
});
|
|
16045
|
+
useLayoutEffect(() => {
|
|
16046
|
+
if (!virtual) {
|
|
16047
|
+
const itemsEl = itemsElRef.current;
|
|
16048
|
+
if (itemsEl) {
|
|
16049
|
+
itemsEl.style.height = "";
|
|
16050
|
+
itemsEl.style.minHeight = "";
|
|
16051
|
+
}
|
|
16052
|
+
}
|
|
16053
|
+
}, [virtual]);
|
|
16054
|
+
/**
|
|
16055
|
+
* The viewport sync. The range starts at the item at `scrollTop + compensation - paddingTop` and
|
|
16056
|
+
* holds as many items as fit at `itemMinSize`, plus one. Items holding the text selection, and the item
|
|
16057
|
+
* holding focus, stay
|
|
16058
|
+
* mounted ("kept"). When the new range is inside the old one, only the layout styles are written;
|
|
16059
|
+
* otherwise the items entering above the old start are recorded (`prependIndexes`) and the range is
|
|
16060
|
+
* rendered with flushSync, now or in a microtask (`deferRenderToMicrotask`). At 120 forced renders
|
|
16061
|
+
* within a second a "renderThrashing" anomaly is reported. `afterRender` runs after the layout styles
|
|
16062
|
+
* of the new range are applied.
|
|
16063
|
+
*/
|
|
16064
|
+
const updateRange = useEventCallback((reason, deferRenderToMicrotask, afterRender) => {
|
|
16065
|
+
if (!virtual) return;
|
|
16066
|
+
const listEl = listElRef.current;
|
|
16067
|
+
if (listEl == null) return;
|
|
16068
|
+
const viewport = viewportRef.current;
|
|
16069
|
+
if (viewport.listHeight === void 0) {
|
|
16070
|
+
applyLayoutStyles(startIndex);
|
|
16071
|
+
afterRender?.();
|
|
16072
|
+
return;
|
|
16073
|
+
}
|
|
16074
|
+
const scrollTop = listEl.scrollTop;
|
|
16075
|
+
const effectiveScrollTop = scrollTop + compensationYRef.current;
|
|
16076
|
+
const scrollLeft = listEl.scrollLeft;
|
|
16077
|
+
const range = rangeRef.current;
|
|
16078
|
+
const prevRange = {
|
|
16079
|
+
startIndex: range.verticalStartIndex,
|
|
16080
|
+
endIndex: range.verticalEndIndex
|
|
16081
|
+
};
|
|
16082
|
+
const nextRange = getVerticalRange({
|
|
16083
|
+
fenwickTree,
|
|
16084
|
+
scrollTop: effectiveScrollTop,
|
|
16085
|
+
listHeight: viewport.listHeight,
|
|
16086
|
+
itemMinSize,
|
|
16087
|
+
itemCount: items.length,
|
|
16088
|
+
paddingTop: getRowsTop()
|
|
16089
|
+
});
|
|
16090
|
+
const rangeChanged = prevRange.startIndex !== nextRange.startIndex || prevRange.endIndex !== nextRange.endIndex;
|
|
16091
|
+
const nextInsidePrev = nextRange.startIndex >= prevRange.startIndex && prevRange.endIndex >= nextRange.endIndex;
|
|
16092
|
+
const prevColRange = {
|
|
16093
|
+
startIndex: range.horizontalStartIndex,
|
|
16094
|
+
endIndex: range.horizontalEndIndex
|
|
16095
|
+
};
|
|
16096
|
+
const nextColRange = getHorizontalRange({
|
|
16097
|
+
xFenwickTree,
|
|
16098
|
+
scrollLeft,
|
|
16099
|
+
listWidth: viewport.listWidth,
|
|
16100
|
+
columnCount: columns.length
|
|
16101
|
+
});
|
|
16102
|
+
const colRangeChanged = prevColRange.startIndex !== nextColRange.startIndex || prevColRange.endIndex !== nextColRange.endIndex;
|
|
16103
|
+
viewport.scrollTop = scrollTop;
|
|
16104
|
+
viewport.scrollLeft = scrollLeft;
|
|
16105
|
+
let keptKeysChanged = false;
|
|
16106
|
+
if (typeof window !== "undefined" && reason !== "sessionChanged") {
|
|
16107
|
+
const { startIndex: nextStart, endIndex: nextEnd } = nextRange;
|
|
16108
|
+
const keptKeys = [];
|
|
16109
|
+
const keep = (key) => {
|
|
16110
|
+
const index = keyToIndexRef.current.get(key);
|
|
16111
|
+
if (index !== void 0 && (!(index >= nextStart) || !(index <= nextEnd)) && !keptKeys.includes(key)) keptKeys.push(key);
|
|
16112
|
+
};
|
|
16113
|
+
const focusedKey = focusedKeyRef.current;
|
|
16114
|
+
if (focusedKey !== null) {
|
|
16115
|
+
const active = document.activeElement;
|
|
16116
|
+
if (active !== null && listEl.contains(active)) keep(focusedKey);
|
|
16117
|
+
else focusedKeyRef.current = null;
|
|
16118
|
+
}
|
|
16119
|
+
const selection = window.getSelection();
|
|
16120
|
+
if (selection != null && !selection.isCollapsed && selection.rangeCount > 0) listEl.querySelectorAll(`[${ITEM_KEY_ATTR}]`).forEach((itemEl) => {
|
|
16121
|
+
if (!selection.containsNode(itemEl, true) && itemEl.getElementsByTagName("input").length !== 0) return;
|
|
16122
|
+
const key = itemEl.getAttribute(ITEM_KEY_ATTR);
|
|
16123
|
+
if (key !== null) keep(key);
|
|
16124
|
+
});
|
|
16125
|
+
if (!haveSameKeys(keptKeys, keptKeysRef.current)) {
|
|
16126
|
+
keptKeysRef.current = keptKeys;
|
|
16127
|
+
keptKeysChanged = true;
|
|
16128
|
+
}
|
|
16129
|
+
}
|
|
16130
|
+
if (reason !== "sessionChanged" && !keptKeysChanged && (!rangeChanged || nextInsidePrev) && !colRangeChanged) {
|
|
16131
|
+
if (nextInsidePrev) applyLayoutStyles(prevRange.startIndex);
|
|
16132
|
+
else applyLayoutStyles(nextRange.startIndex);
|
|
16133
|
+
afterRender?.();
|
|
16134
|
+
return;
|
|
16135
|
+
}
|
|
16136
|
+
if (reason !== "sessionChanged") {
|
|
16137
|
+
const entering = [];
|
|
16138
|
+
if (pendingScrollTaskRef.current?.type === void 0 && nextRange.endIndex >= prevRange.startIndex) for (let i = nextRange.startIndex; i < prevRange.startIndex; ++i) entering.push(i);
|
|
16139
|
+
range.prependIndexes = entering;
|
|
16140
|
+
} else range.prependIndexes = [];
|
|
16141
|
+
range.verticalStartIndex = nextRange.startIndex;
|
|
16142
|
+
range.verticalEndIndex = nextRange.endIndex;
|
|
16143
|
+
range.horizontalStartIndex = nextColRange.startIndex;
|
|
16144
|
+
range.horizontalEndIndex = nextColRange.endIndex;
|
|
16145
|
+
const now = Date.now();
|
|
16146
|
+
const renderTimes = forcedRenderTimesRef.current;
|
|
16147
|
+
renderTimes.push(now);
|
|
16148
|
+
const windowStart = now - RENDER_THRASHING_WINDOW_MS;
|
|
16149
|
+
while (renderTimes.length > 0 && renderTimes[0] < windowStart) renderTimes.shift();
|
|
16150
|
+
if (renderTimes.length >= 120) {
|
|
16151
|
+
reportRenderAnomaly({
|
|
16152
|
+
type: "renderThrashing",
|
|
16153
|
+
message: `${renderTimes.length} forced renders within ${RENDER_THRASHING_WINDOW_MS}ms`,
|
|
16154
|
+
payload: {
|
|
16155
|
+
renderCount: renderTimes.length,
|
|
16156
|
+
windowMs: RENDER_THRASHING_WINDOW_MS,
|
|
16157
|
+
triggerReason: reason,
|
|
16158
|
+
itemCount: items.length
|
|
16159
|
+
}
|
|
16160
|
+
});
|
|
16161
|
+
renderTimes.length = 0;
|
|
16162
|
+
}
|
|
16163
|
+
if (deferRenderToMicrotask) Promise.resolve().then(() => {
|
|
16164
|
+
flushSync(() => {
|
|
16165
|
+
setRenderState((state) => [
|
|
16166
|
+
reason,
|
|
16167
|
+
afterRender,
|
|
16168
|
+
state[2] + 1
|
|
16169
|
+
]);
|
|
16170
|
+
});
|
|
16171
|
+
});
|
|
16172
|
+
else flushSync(() => {
|
|
16173
|
+
setRenderState((state) => [
|
|
16174
|
+
reason,
|
|
16175
|
+
afterRender,
|
|
16176
|
+
state[2] + 1
|
|
16177
|
+
]);
|
|
16178
|
+
});
|
|
16179
|
+
});
|
|
16180
|
+
/**
|
|
16181
|
+
* A pending scroll task (to the top, the bottom, an index, or a custom task) stays armed for one
|
|
16182
|
+
* animation frame plus one task, so that item sizes measured right after the jump replay it instead
|
|
16183
|
+
* of being compensated.
|
|
16184
|
+
*/
|
|
16185
|
+
const schedulePendingScrollTaskExpiry = useCallback(() => {
|
|
16186
|
+
pendingScrollTaskRafRef.current = requestAnimationFrame(() => {
|
|
16187
|
+
pendingScrollTaskRafRef.current = null;
|
|
16188
|
+
pendingScrollTaskTimeoutRef.current = window.setTimeout(() => {
|
|
16189
|
+
pendingScrollTaskRef.current = null;
|
|
16190
|
+
pendingScrollTaskTimeoutRef.current = null;
|
|
16191
|
+
});
|
|
16192
|
+
});
|
|
16193
|
+
}, []);
|
|
16194
|
+
const scrollToPosition = useEventCallback((left, top, behavior) => {
|
|
16195
|
+
listElRef.current?.scrollTo({
|
|
16196
|
+
left,
|
|
16197
|
+
top,
|
|
16198
|
+
behavior
|
|
16199
|
+
});
|
|
16200
|
+
});
|
|
16201
|
+
/**
|
|
16202
|
+
* Scrolls to the offset of `index` in the Fenwick tree (an estimate for items not measured yet);
|
|
16203
|
+
* an "instant" jump is armed as the pending task, to be replayed after measurements. `ensureVisibleOnly`
|
|
16204
|
+
* (the `debounce` option) scrolls only as far as needed to show the item. With virtualization off the
|
|
16205
|
+
* offset is read from the DOM (`getBoundingClientRect` of the index-th child).
|
|
16206
|
+
*/
|
|
16207
|
+
const scrollToIndex = useEventCallback((index, behavior, ensureVisibleOnly, isReplay, scrollMarginTop = 0, scrollMarginBottom = 0) => {
|
|
16208
|
+
if (!virtual) {
|
|
16209
|
+
const listEl = listElRef.current;
|
|
16210
|
+
const itemsEl = itemsElRef.current;
|
|
16211
|
+
if (!itemsEl || !listEl) return;
|
|
16212
|
+
const visibleItemsEl = itemsEl.firstElementChild;
|
|
16213
|
+
if (!visibleItemsEl) return;
|
|
16214
|
+
const itemEl = visibleItemsEl.children.item(index);
|
|
16215
|
+
if (!(itemEl instanceof HTMLElement)) return;
|
|
16216
|
+
const listRect = listEl.getBoundingClientRect();
|
|
16217
|
+
const itemRect = itemEl.getBoundingClientRect();
|
|
16218
|
+
const itemTop = itemRect.top - listRect.top + listEl.scrollTop;
|
|
16219
|
+
if (ensureVisibleOnly) {
|
|
16220
|
+
const { scrollTop, offsetHeight } = listEl;
|
|
16221
|
+
const itemHeight = itemRect.height;
|
|
16222
|
+
if (itemTop > scrollTop) {
|
|
16223
|
+
if (!(itemTop + itemHeight <= scrollTop + offsetHeight)) listEl.scrollTo({
|
|
16224
|
+
left: 0,
|
|
16225
|
+
top: itemTop + itemHeight - offsetHeight + scrollMarginBottom,
|
|
16226
|
+
behavior
|
|
16227
|
+
});
|
|
16228
|
+
} else listEl.scrollTo({
|
|
16229
|
+
left: 0,
|
|
16230
|
+
top: itemTop - scrollMarginTop,
|
|
16231
|
+
behavior
|
|
16232
|
+
});
|
|
16233
|
+
} else listEl.scrollTo({
|
|
16234
|
+
left: 0,
|
|
16235
|
+
top: itemTop,
|
|
16236
|
+
behavior
|
|
16237
|
+
});
|
|
16238
|
+
return;
|
|
16239
|
+
}
|
|
16240
|
+
const targetTop = fenwickTree.sum(index) + getRowsTop();
|
|
16241
|
+
if (!isReplay) {
|
|
16242
|
+
clearPendingScrollTask();
|
|
16243
|
+
if (behavior === "instant") {
|
|
16244
|
+
pendingScrollTaskRef.current = {
|
|
16245
|
+
type: "index",
|
|
16246
|
+
index,
|
|
16247
|
+
debounce: ensureVisibleOnly
|
|
16248
|
+
};
|
|
16249
|
+
schedulePendingScrollTaskExpiry();
|
|
16250
|
+
}
|
|
16251
|
+
}
|
|
16252
|
+
if (ensureVisibleOnly) {
|
|
16253
|
+
const listEl = listElRef.current;
|
|
16254
|
+
if (listEl) {
|
|
16255
|
+
const { scrollTop, offsetHeight } = listEl;
|
|
16256
|
+
if (targetTop > scrollTop) {
|
|
16257
|
+
const itemSize = fenwickTree.get(index);
|
|
16258
|
+
if (!(targetTop + itemSize <= scrollTop + offsetHeight)) {
|
|
16259
|
+
listEl.scrollTo({
|
|
16260
|
+
left: 0,
|
|
16261
|
+
top: targetTop + itemSize - offsetHeight + scrollMarginBottom,
|
|
16262
|
+
behavior
|
|
16263
|
+
});
|
|
16264
|
+
updateRange("listScroll", true, null);
|
|
16265
|
+
}
|
|
16266
|
+
} else {
|
|
16267
|
+
listEl.scrollTo({
|
|
16268
|
+
left: 0,
|
|
16269
|
+
top: targetTop - scrollMarginTop,
|
|
16270
|
+
behavior
|
|
16271
|
+
});
|
|
16272
|
+
updateRange("listScroll", true, null);
|
|
16273
|
+
}
|
|
16274
|
+
}
|
|
16275
|
+
} else {
|
|
16276
|
+
listElRef.current?.scrollTo({
|
|
16277
|
+
left: 0,
|
|
16278
|
+
top: targetTop,
|
|
16279
|
+
behavior
|
|
16280
|
+
});
|
|
16281
|
+
updateRange("listScroll", true, null);
|
|
16282
|
+
}
|
|
16283
|
+
});
|
|
16284
|
+
/**
|
|
16285
|
+
* Measures the mounted rows in flow from layout, synchronously, and writes the sizes that changed into
|
|
16286
|
+
* the Fenwick tree and the key -> offset cache, and the first row's top margin when the first row is
|
|
16287
|
+
* mounted (`leadingMarginRef`). Reads everything first (`measureRowSpans`), then writes: no layout is
|
|
16288
|
+
* forced in between. O(mounted rows).
|
|
16289
|
+
*
|
|
16290
|
+
* Unlike the reference implementation (DeepSeek web): it measured each row's own `offsetHeight`,
|
|
16291
|
+
* which in block flow misses the margins between rows and a child's margins that collapse through a row, so the rows drifted from
|
|
16292
|
+
* their offsets. A row's size here is the space it takes in the flow: from where its border box starts
|
|
16293
|
+
* to where the next row's starts (the last mounted row: to the end of the rows), so collapsed margins
|
|
16294
|
+
* are counted once, in the row above them, and the sizes add up to the laid-out height. A row's size
|
|
16295
|
+
* depends on the next row's top margin, so every measurement measures the whole run, not the row that
|
|
16296
|
+
* resized. The last mounted row's bottom margin does not collapse with the next row (not mounted): its
|
|
16297
|
+
* size is provisional until the next row mounts, which corrects it without moving any mounted row
|
|
16298
|
+
* (they are laid out by the browser, positioned by the first row's offset alone). Kept rows are not
|
|
16299
|
+
* measured: absolutely positioned, they take no space in the flow, and keep the size they were
|
|
16300
|
+
* measured at in it.
|
|
16301
|
+
*/
|
|
16302
|
+
const measureFlowRun = useEventCallback(() => {
|
|
16303
|
+
const resizes = [];
|
|
16304
|
+
const visibleItemsEl = getVisibleItemsEl();
|
|
16305
|
+
if (!virtual || ignoreItemResize || !itemResizable || !(visibleItemsEl instanceof HTMLElement)) return {
|
|
16306
|
+
resizes,
|
|
16307
|
+
leadingMarginDelta: 0
|
|
16308
|
+
};
|
|
16309
|
+
const rows = getFlowRows();
|
|
16310
|
+
if (rows.length === 0) return {
|
|
16311
|
+
resizes,
|
|
16312
|
+
leadingMarginDelta: 0
|
|
16313
|
+
};
|
|
16314
|
+
const { spans, firstStart } = measureRowSpans(visibleItemsEl, rows.map((row) => row.el));
|
|
16315
|
+
let leadingMarginDelta = 0;
|
|
16316
|
+
if (rows[0].index === 0 && firstStart !== leadingMarginRef.current) {
|
|
16317
|
+
leadingMarginDelta = firstStart - leadingMarginRef.current;
|
|
16318
|
+
leadingMarginRef.current = firstStart;
|
|
16319
|
+
}
|
|
16320
|
+
rows.forEach(({ key, index }, i) => {
|
|
16321
|
+
const size = spans[i];
|
|
16322
|
+
const previousSize = fenwickTree.get(index);
|
|
16323
|
+
if (size === previousSize) return;
|
|
16324
|
+
if (size - itemEstimatedSize === 0) keyToHeightOffsetRef.current.delete(key);
|
|
16325
|
+
else keyToHeightOffsetRef.current.set(key, size - itemEstimatedSize);
|
|
16326
|
+
fenwickTree.add(index, size - previousSize);
|
|
16327
|
+
resizes.push({
|
|
16328
|
+
index,
|
|
16329
|
+
key,
|
|
16330
|
+
delta: size - previousSize
|
|
16331
|
+
});
|
|
16332
|
+
});
|
|
16333
|
+
return {
|
|
16334
|
+
resizes,
|
|
16335
|
+
leadingMarginDelta
|
|
16336
|
+
};
|
|
16337
|
+
});
|
|
16338
|
+
/**
|
|
16339
|
+
* Measures the mounted rows now (`measureFlowRun`) and applies the layout; returns whether any size
|
|
16340
|
+
* changed. Used by the aligned jump, which cannot wait for the batch (a microtask) or the
|
|
16341
|
+
* ResizeObserver (after layout, possibly a frame later) to learn the sizes.
|
|
16342
|
+
*/
|
|
16343
|
+
const measureMountedItems = useEventCallback(() => {
|
|
16344
|
+
const { resizes, leadingMarginDelta } = measureFlowRun();
|
|
16345
|
+
const changed = resizes.length !== 0 || leadingMarginDelta !== 0;
|
|
16346
|
+
if (changed) {
|
|
16347
|
+
applyLayoutStyles(startIndex);
|
|
16348
|
+
onContentResize?.();
|
|
16349
|
+
}
|
|
16350
|
+
return changed;
|
|
16351
|
+
});
|
|
16352
|
+
/**
|
|
16353
|
+
* Where item `index` is, in the list's scroll coordinates: read from the DOM when
|
|
16354
|
+
* it is mounted (exact), else its offset in the Fenwick tree (estimates for unmeasured items).
|
|
16355
|
+
*/
|
|
16356
|
+
const getItemExtent = useEventCallback((index) => {
|
|
16357
|
+
const listEl = listElRef.current;
|
|
16358
|
+
const item = items[index];
|
|
16359
|
+
const visibleItemsEl = getVisibleItemsEl();
|
|
16360
|
+
if (listEl && item !== void 0 && visibleItemsEl && itemResizable) {
|
|
16361
|
+
const key = getKey(item);
|
|
16362
|
+
for (const itemEl of Array.from(visibleItemsEl.children)) if (itemEl.getAttribute("data-virtual-list-item-key") === key) {
|
|
16363
|
+
const rect = itemEl.getBoundingClientRect();
|
|
16364
|
+
const listTop = listEl.getBoundingClientRect().top + listEl.clientTop;
|
|
16365
|
+
return {
|
|
16366
|
+
top: rect.top - listTop + listEl.scrollTop,
|
|
16367
|
+
size: rect.height,
|
|
16368
|
+
measured: true
|
|
16369
|
+
};
|
|
16370
|
+
}
|
|
16371
|
+
}
|
|
16372
|
+
return {
|
|
16373
|
+
top: fenwickTree.sum(index) + getRowsTop(),
|
|
16374
|
+
size: fenwickTree.get(index),
|
|
16375
|
+
measured: !itemResizable
|
|
16376
|
+
};
|
|
16377
|
+
});
|
|
16378
|
+
/** Bumped by every aligned jump: a chain of steps stops when a newer jump has started. */
|
|
16379
|
+
const alignRunRef = useRef(0);
|
|
16380
|
+
/**
|
|
16381
|
+
* One step of an aligned jump: measure the mounted items, work out the exact
|
|
16382
|
+
* `scrollTop` for the item from the DOM, set it, and update the range. If the range changes, the rows it
|
|
16383
|
+
* brings into view are rendered (in a microtask, still before the browser paints) and the next step runs
|
|
16384
|
+
* from the render callback (a layout effect) to measure them; the jump is done when a step finds the item
|
|
16385
|
+
* mounted, no size changed and `scrollTop` already right. Bounded by `MAX_ALIGN_STEPS`.
|
|
16386
|
+
*/
|
|
16387
|
+
const alignStep = useEventCallback((run, index, align, scrollMarginTop, scrollMarginBottom, remaining) => {
|
|
16388
|
+
const listEl = listElRef.current;
|
|
16389
|
+
if (listEl == null || run !== alignRunRef.current || index >= items.length) return;
|
|
16390
|
+
const sizesChanged = measureMountedItems();
|
|
16391
|
+
const { top: itemTop, size: itemSize, measured } = getItemExtent(index);
|
|
16392
|
+
const viewportHeight = listEl.clientHeight;
|
|
16393
|
+
const target = Math.round(getAlignedScrollTop({
|
|
16394
|
+
itemTop,
|
|
16395
|
+
itemSize,
|
|
16396
|
+
viewportHeight,
|
|
16397
|
+
align,
|
|
16398
|
+
scrollMarginTop,
|
|
16399
|
+
scrollMarginBottom,
|
|
16400
|
+
maxScrollTop: listEl.scrollHeight - viewportHeight
|
|
16401
|
+
}));
|
|
16402
|
+
const moved = Math.abs(listEl.scrollTop - target) >= .5;
|
|
16403
|
+
if (moved) listEl.scrollTo({
|
|
16404
|
+
left: listEl.scrollLeft,
|
|
16405
|
+
top: target,
|
|
16406
|
+
behavior: "instant"
|
|
16407
|
+
});
|
|
16408
|
+
viewportRef.current.scrollTop = listEl.scrollTop;
|
|
16409
|
+
if (!moved && !sizesChanged && measured || remaining <= 0) return;
|
|
16410
|
+
updateRange("listScroll", true, () => {
|
|
16411
|
+
alignStep(run, index, align, scrollMarginTop, scrollMarginBottom, remaining - 1);
|
|
16412
|
+
});
|
|
16413
|
+
});
|
|
16414
|
+
/**
|
|
16415
|
+
* `scrollTo({ index | key, align })`. Instant (or "auto"): the aligned steps above, armed as the pending
|
|
16416
|
+
* task like the other instant jumps (item sizes reported after the jump replay it, for one frame plus one
|
|
16417
|
+
* task). Smooth: one scroll to the position worked out from the current layout (estimates for rows not
|
|
16418
|
+
* measured yet), like the smooth index jump. Virtualization off: read from the DOM.
|
|
16419
|
+
*/
|
|
16420
|
+
const scrollToAligned = useEventCallback((index, align, behavior, isReplay, scrollMarginTop, scrollMarginBottom) => {
|
|
16421
|
+
const listEl = listElRef.current;
|
|
16422
|
+
if (listEl == null) return;
|
|
16423
|
+
if (!virtual) {
|
|
16424
|
+
const itemEl = getVisibleItemsEl()?.children.item(index);
|
|
16425
|
+
if (!itemEl) return;
|
|
16426
|
+
const rect = itemEl.getBoundingClientRect();
|
|
16427
|
+
const listTop = listEl.getBoundingClientRect().top + listEl.clientTop;
|
|
16428
|
+
const top = getAlignedScrollTop({
|
|
16429
|
+
itemTop: rect.top - listTop + listEl.scrollTop,
|
|
16430
|
+
itemSize: rect.height,
|
|
16431
|
+
viewportHeight: listEl.clientHeight,
|
|
16432
|
+
align,
|
|
16433
|
+
scrollMarginTop,
|
|
16434
|
+
scrollMarginBottom,
|
|
16435
|
+
maxScrollTop: listEl.scrollHeight - listEl.clientHeight
|
|
16436
|
+
});
|
|
16437
|
+
listEl.scrollTo({
|
|
16438
|
+
left: 0,
|
|
16439
|
+
top: Math.round(top),
|
|
16440
|
+
behavior
|
|
16441
|
+
});
|
|
16442
|
+
return;
|
|
16443
|
+
}
|
|
16444
|
+
const smooth = behavior === "smooth";
|
|
16445
|
+
if (!isReplay) {
|
|
16446
|
+
clearPendingScrollTask();
|
|
16447
|
+
if (!smooth) {
|
|
16448
|
+
pendingScrollTaskRef.current = {
|
|
16449
|
+
type: "align",
|
|
16450
|
+
index,
|
|
16451
|
+
align,
|
|
16452
|
+
scrollMarginTop,
|
|
16453
|
+
scrollMarginBottom
|
|
16454
|
+
};
|
|
16455
|
+
schedulePendingScrollTaskExpiry();
|
|
16456
|
+
}
|
|
16457
|
+
}
|
|
16458
|
+
const run = ++alignRunRef.current;
|
|
16459
|
+
if (smooth) {
|
|
16460
|
+
const { top: itemTop, size: itemSize } = getItemExtent(index);
|
|
16461
|
+
const top = getAlignedScrollTop({
|
|
16462
|
+
itemTop,
|
|
16463
|
+
itemSize,
|
|
16464
|
+
viewportHeight: listEl.clientHeight,
|
|
16465
|
+
align,
|
|
16466
|
+
scrollMarginTop,
|
|
16467
|
+
scrollMarginBottom,
|
|
16468
|
+
maxScrollTop: listEl.scrollHeight - listEl.clientHeight
|
|
16469
|
+
});
|
|
16470
|
+
listEl.scrollTo({
|
|
16471
|
+
left: 0,
|
|
16472
|
+
top: Math.round(top),
|
|
16473
|
+
behavior
|
|
16474
|
+
});
|
|
16475
|
+
updateRange("listScroll", true, null);
|
|
16476
|
+
return;
|
|
16477
|
+
}
|
|
16478
|
+
alignStep(run, index, align, scrollMarginTop, scrollMarginBottom, MAX_ALIGN_STEPS);
|
|
16479
|
+
});
|
|
16480
|
+
const scrollTo = useEventCallback((options, topIfNumber) => {
|
|
16481
|
+
if (typeof options == "number") {
|
|
16482
|
+
scrollToPosition(options, topIfNumber, "instant");
|
|
16483
|
+
return;
|
|
16484
|
+
}
|
|
16485
|
+
const { left, top, index, key, position, behavior = "instant", debounce = true, scrollMarginTop = 0, scrollMarginBottom = 0, align } = options;
|
|
16486
|
+
if (left !== void 0 || top !== void 0) scrollToPosition(left, top, behavior);
|
|
16487
|
+
else if (align !== void 0 && (index !== void 0 || key !== void 0)) {
|
|
16488
|
+
const alignIndex = index ?? keyToIndexRef.current.get(key);
|
|
16489
|
+
if (alignIndex !== void 0) scrollToAligned(alignIndex, align, behavior, false, scrollMarginTop, scrollMarginBottom);
|
|
16490
|
+
} else if (index !== void 0) scrollToIndex(index, behavior, debounce, false, scrollMarginTop, scrollMarginBottom);
|
|
16491
|
+
else if (key !== void 0) {
|
|
16492
|
+
const keyIndex = keyToIndexRef.current.get(key);
|
|
16493
|
+
if (keyIndex !== void 0) scrollToIndex(keyIndex, behavior, debounce, false, scrollMarginTop, scrollMarginBottom);
|
|
16494
|
+
} else if (position) {
|
|
16495
|
+
if (virtual) {
|
|
16496
|
+
clearPendingScrollTask();
|
|
16497
|
+
if (behavior === "instant") {
|
|
16498
|
+
pendingScrollTaskRef.current = { type: position };
|
|
16499
|
+
schedulePendingScrollTaskExpiry();
|
|
16500
|
+
}
|
|
16501
|
+
}
|
|
16502
|
+
if (position === "bottom") {
|
|
16503
|
+
scrollToPosition(0, listElRef.current?.scrollHeight || 2e9, behavior);
|
|
16504
|
+
if (virtual) updateRange("listScroll", true, null);
|
|
16505
|
+
} else if (position === "top") scrollToPosition(0, 0, behavior);
|
|
16506
|
+
}
|
|
16507
|
+
});
|
|
16508
|
+
const replayPendingScrollTask = useEventCallback(() => {
|
|
16509
|
+
const task = pendingScrollTaskRef.current;
|
|
16510
|
+
if (task) {
|
|
16511
|
+
if (task.type === "top") scrollToPosition(0, 0, "instant");
|
|
16512
|
+
else if (task.type === "bottom") scrollToPosition(0, listElRef.current?.scrollHeight || 2e9, "instant");
|
|
16513
|
+
else if (task.type === "index") scrollToIndex(task.index, "instant", task.debounce, true);
|
|
16514
|
+
else if (task.type === "align") scrollToAligned(task.index, task.align, "instant", true, task.scrollMarginTop, task.scrollMarginBottom);
|
|
16515
|
+
else if (task.type === "custom") task.task();
|
|
16516
|
+
}
|
|
16517
|
+
});
|
|
16518
|
+
/**
|
|
16519
|
+
* Runs `task` now (after a range update) and again after every batch of item resizes while it is
|
|
16520
|
+
* armed (one frame plus one task).
|
|
16521
|
+
*/
|
|
16522
|
+
const submitScrollAdjustingTask = useCallback((task) => {
|
|
16523
|
+
clearPendingScrollTask();
|
|
16524
|
+
pendingScrollTaskRef.current = {
|
|
16525
|
+
type: "custom",
|
|
16526
|
+
task
|
|
16527
|
+
};
|
|
16528
|
+
schedulePendingScrollTaskExpiry();
|
|
16529
|
+
updateRange("listScroll", true, replayPendingScrollTask);
|
|
16530
|
+
}, [
|
|
16531
|
+
replayPendingScrollTask,
|
|
16532
|
+
clearPendingScrollTask,
|
|
16533
|
+
schedulePendingScrollTaskExpiry,
|
|
16534
|
+
updateRange
|
|
16535
|
+
]);
|
|
16536
|
+
const syncViewport = useCallback(() => {
|
|
16537
|
+
updateRange("listScroll", false, null);
|
|
16538
|
+
}, [updateRange]);
|
|
16539
|
+
const restoreItemsAnchor = useEventCallback((anchor) => {
|
|
16540
|
+
appliedItemsAnchorRef.current = anchor;
|
|
16541
|
+
rangeRef.current = renderedRange;
|
|
16542
|
+
const listEl = listElRef.current;
|
|
16543
|
+
const index = keyToIndexRef.current.get(anchor.key);
|
|
16544
|
+
if (listEl == null || index === void 0 || pendingScrollTaskRef.current?.type !== void 0) return;
|
|
16545
|
+
applyLayoutStyles(startIndex);
|
|
16546
|
+
const diff = fenwickTree.sum(index) + anchor.delta + getRowsTop() - (listEl.scrollTop + compensationYRef.current);
|
|
16547
|
+
if (diff !== 0 && !addCompensation(diff)) listEl.scrollBy(0, diff);
|
|
16548
|
+
viewportRef.current.scrollTop = listEl.scrollTop;
|
|
16549
|
+
});
|
|
16550
|
+
useLayoutEffect(() => {
|
|
16551
|
+
if (pendingItemsAnchor !== null && !sessionChangedRef.current) restoreItemsAnchor(pendingItemsAnchor);
|
|
16552
|
+
if (sessionChangedRef.current) {
|
|
16553
|
+
updateRange("sessionChanged", true, replayPendingScrollTask);
|
|
16554
|
+
sessionChangedRef.current = false;
|
|
16555
|
+
} else if (itemResizable) {
|
|
16556
|
+
const heightBefore = listElRef.current?.offsetHeight;
|
|
16557
|
+
applyLayoutStyles(startIndex);
|
|
16558
|
+
if (heightBefore === listElRef.current?.offsetHeight) updateRange("itemsChanged", true, replayPendingScrollTask);
|
|
16559
|
+
} else updateRange("itemsChanged", true, replayPendingScrollTask);
|
|
16560
|
+
}, [
|
|
16561
|
+
sessionId,
|
|
16562
|
+
items,
|
|
16563
|
+
replayPendingScrollTask,
|
|
16564
|
+
updateRange,
|
|
16565
|
+
itemResizable,
|
|
16566
|
+
applyLayoutStyles
|
|
16567
|
+
]);
|
|
16568
|
+
useLayoutEffect(() => {
|
|
16569
|
+
committedLayoutRef.current = {
|
|
16570
|
+
items,
|
|
16571
|
+
tree: fenwickTree,
|
|
16572
|
+
sessionId
|
|
16573
|
+
};
|
|
16574
|
+
});
|
|
16575
|
+
useLayoutEffect(() => {
|
|
16576
|
+
applyLayoutStyles(startIndex);
|
|
16577
|
+
renderCallback?.();
|
|
16578
|
+
}, [
|
|
16579
|
+
renderReason,
|
|
16580
|
+
renderCallback,
|
|
16581
|
+
renderVersion,
|
|
16582
|
+
applyLayoutStyles
|
|
16583
|
+
]);
|
|
16584
|
+
const getOffsetTopOfKey = useEventCallback((key) => {
|
|
16585
|
+
const index = keyToIndexRef.current.get(key);
|
|
16586
|
+
if (index !== void 0) return fenwickTree.sum(index) + getRowsTop();
|
|
16587
|
+
});
|
|
16588
|
+
const refreshListWidth = useCallback(() => {
|
|
16589
|
+
const listEl = listElRef.current;
|
|
16590
|
+
if (listEl) listEl.style.setProperty("--virtual-list-width", `${listEl.offsetWidth}px`);
|
|
16591
|
+
}, [listElRef]);
|
|
16592
|
+
const imperativeApi = useMemo(() => ({
|
|
16593
|
+
listElRef,
|
|
16594
|
+
itemsElRef,
|
|
16595
|
+
scrollTo,
|
|
16596
|
+
syncViewport,
|
|
16597
|
+
getOffsetTopOfKey,
|
|
16598
|
+
submitScrollAdjustingTask,
|
|
16599
|
+
refreshListWidth
|
|
16600
|
+
}), [
|
|
16601
|
+
getOffsetTopOfKey,
|
|
16602
|
+
scrollTo,
|
|
16603
|
+
syncViewport,
|
|
16604
|
+
submitScrollAdjustingTask,
|
|
16605
|
+
refreshListWidth
|
|
16606
|
+
]);
|
|
16607
|
+
useImperativeHandle(componentRef, () => imperativeApi, [imperativeApi]);
|
|
16608
|
+
const itemResizeQueueRef = useRef([]);
|
|
16609
|
+
const enqueueItemResize = useEventCallback((entry) => {
|
|
16610
|
+
itemResizeQueueRef.current.push(entry);
|
|
16611
|
+
});
|
|
16612
|
+
const clearItemResizeQueue = useEventCallback(() => {
|
|
16613
|
+
itemResizeQueueRef.current = [];
|
|
16614
|
+
});
|
|
16615
|
+
const batchResizeScheduledRef = useRef(false);
|
|
16616
|
+
/**
|
|
16617
|
+
* Applies the queued item measurements (in place of the browser's scroll anchoring): the mounted rows
|
|
16618
|
+
* are measured (`measureFlowRun`: all of them, since a row's size depends on its neighbour) and each
|
|
16619
|
+
* size change goes into the Fenwick tree and the key -> offset cache. With no pending scroll task,
|
|
16620
|
+
* the size changes of items measured for the FIRST time among those that entered above the old start
|
|
16621
|
+
* of the range (`prependIndexes`), and of rows above the view, are scrolled away (`scrollBy`, or
|
|
16622
|
+
* through the iOS compensation during a touch scroll); with a pending task, the task is replayed.
|
|
16623
|
+
*/
|
|
16624
|
+
const handleItemBatchResize = useEventCallback(() => {
|
|
16625
|
+
if (!virtual) return;
|
|
16626
|
+
const queue = itemResizeQueueRef.current;
|
|
16627
|
+
if (ignoreItemResize) return;
|
|
16628
|
+
const listElBefore = listElRef.current;
|
|
16629
|
+
const viewTop = listElBefore == null ? 0 : listElBefore.scrollTop + compensationYRef.current - getRowsTop();
|
|
16630
|
+
const viewTopIndex = listElBefore == null ? 0 : fenwickTree.getBound(viewTop);
|
|
16631
|
+
const isFirstOfKey = /* @__PURE__ */ new Map();
|
|
16632
|
+
for (const { key, isFirst } of queue) if (!isFirstOfKey.has(key)) isFirstOfKey.set(key, isFirst);
|
|
16633
|
+
const { resizes, leadingMarginDelta } = measureFlowRun();
|
|
16634
|
+
applyLayoutStyles(startIndex);
|
|
16635
|
+
onContentResize?.();
|
|
16636
|
+
const listEl = listElRef.current;
|
|
16637
|
+
if (listEl != null) {
|
|
16638
|
+
if (pendingScrollTaskRef.current?.type === void 0) {
|
|
16639
|
+
const compensates = (index, key) => key !== void 0 && isFirstOfKey.get(key) === true && prependIndexes.includes(index) || index < viewTopIndex;
|
|
16640
|
+
let compensation = 0;
|
|
16641
|
+
for (const { index, key, delta } of resizes) if (compensates(index, key)) compensation += delta;
|
|
16642
|
+
const firstItem = items[0];
|
|
16643
|
+
if (leadingMarginDelta !== 0 && (viewTop > 0 || compensates(0, firstItem === void 0 ? void 0 : getKey(firstItem)))) compensation += leadingMarginDelta;
|
|
16644
|
+
if (compensation !== 0 && !addCompensation(compensation)) listEl.scrollBy(0, compensation);
|
|
16645
|
+
} else replayPendingScrollTask();
|
|
16646
|
+
const viewport = viewportRef.current;
|
|
16647
|
+
viewport.scrollTop = listEl.scrollTop;
|
|
16648
|
+
viewport.scrollLeft = listEl.scrollLeft;
|
|
16649
|
+
}
|
|
16650
|
+
clearItemResizeQueue();
|
|
16651
|
+
updateRange("batchItemResize", false, null);
|
|
16652
|
+
});
|
|
16653
|
+
/** Measurements of items that just mounted are applied together, in one microtask. */
|
|
16654
|
+
const scheduleItemBatchResize = useEventCallback(() => {
|
|
16655
|
+
if (!batchResizeScheduledRef.current) {
|
|
16656
|
+
Promise.resolve().then(() => {
|
|
16657
|
+
handleItemBatchResize();
|
|
16658
|
+
batchResizeScheduledRef.current = false;
|
|
16659
|
+
});
|
|
16660
|
+
batchResizeScheduledRef.current = true;
|
|
16661
|
+
}
|
|
16662
|
+
});
|
|
16663
|
+
/**
|
|
16664
|
+
* Ignores scroll events while a flush of the iOS compensation suppresses them; otherwise updates
|
|
16665
|
+
* the range (replaying a pending task) and calls `onScroll`.
|
|
16666
|
+
*/
|
|
16667
|
+
const handleListScroll = useEventCallback((event) => {
|
|
16668
|
+
const scrollTop = event.target.scrollTop;
|
|
16669
|
+
if (!isScrollSuppressedWithForceRestore(scrollTop)) {
|
|
16670
|
+
if (!wheelScrollingRef.current) updateRange("listScroll", false, pendingScrollTaskRef.current ? replayPendingScrollTask : null);
|
|
16671
|
+
onScroll?.(event.nativeEvent);
|
|
16672
|
+
}
|
|
16673
|
+
});
|
|
16674
|
+
/**
|
|
16675
|
+
* With `catchWheel` (and a fine pointer): scrolls by the wheel itself (`preventDefault`, then
|
|
16676
|
+
* `scrollTop +=`) and updates the range at once, so the rows are rendered in the same frame.
|
|
16677
|
+
*/
|
|
16678
|
+
const handleWheel = useEventCallback((wheelEvent) => {
|
|
16679
|
+
if (catchWheel) {
|
|
16680
|
+
if (mayUseWheelRef.current) {
|
|
16681
|
+
const listEl = listElRef.current;
|
|
16682
|
+
if (listEl != null) {
|
|
16683
|
+
const scale = getWheelDeltaScale();
|
|
16684
|
+
const deltaX = wheelEvent.deltaX / scale;
|
|
16685
|
+
const deltaY = wheelEvent.deltaY / scale;
|
|
16686
|
+
if (deltaX === 0 && (listEl.scrollTop === 0 && deltaY <= 0 || listEl.scrollTop + listEl.offsetHeight >= listEl.scrollHeight && deltaY >= 0)) return;
|
|
16687
|
+
wheelEvent.preventDefault();
|
|
16688
|
+
listEl.scrollTop += deltaY;
|
|
16689
|
+
listEl.scrollLeft += deltaX;
|
|
16690
|
+
syncViewport();
|
|
16691
|
+
wheelScrollingRef.current = true;
|
|
16692
|
+
runLatestInNextFrame(() => {
|
|
16693
|
+
wheelScrollingRef.current = false;
|
|
16694
|
+
});
|
|
16695
|
+
}
|
|
16696
|
+
}
|
|
16697
|
+
onWheel?.(wheelEvent);
|
|
16698
|
+
}
|
|
16699
|
+
});
|
|
16700
|
+
useNonPassiveWheel(useCallback(() => listElRef.current, []), handleWheel, catchWheel);
|
|
16701
|
+
const iosCompensationEnabled = isIOS && virtual;
|
|
16702
|
+
const { addCompensation, isScrollSuppressedWithForceRestore } = useIosMomentumCompensation({
|
|
16703
|
+
enabled: iosCompensationEnabled,
|
|
16704
|
+
listElRef,
|
|
16705
|
+
itemsElRef,
|
|
16706
|
+
syncViewport,
|
|
16707
|
+
compensationYRef
|
|
16708
|
+
});
|
|
16709
|
+
/**
|
|
16710
|
+
* The list's own resize: stores its height and width, sets `--virtual-list-width` and updates
|
|
16711
|
+
* the range. Skipped while the list is hidden with `display: none`.
|
|
16712
|
+
*/
|
|
16713
|
+
const handleListResize = useEventCallback((listEl, deferRender) => {
|
|
16714
|
+
if (isHiddenByDisplayNone(listEl)) return;
|
|
16715
|
+
const height = listEl.offsetHeight;
|
|
16716
|
+
const width = listEl.offsetWidth;
|
|
16717
|
+
const viewport = viewportRef.current;
|
|
16718
|
+
if (listElRef.current) listElRef.current.style.setProperty("--virtual-list-width", `${width}px`);
|
|
16719
|
+
if (renderCol == null && renderItemWithCols == null) {
|
|
16720
|
+
if (height === viewport.listHeight) return;
|
|
16721
|
+
} else if (height === viewport.listHeight && width === viewport.listWidth) return;
|
|
16722
|
+
viewport.listHeight = height;
|
|
16723
|
+
viewport.listWidth = width;
|
|
16724
|
+
updateRange("listResize", deferRender, replayPendingScrollTask);
|
|
16725
|
+
});
|
|
16726
|
+
const onListResizeObserved = useEventCallback(({ entry }) => {
|
|
16727
|
+
handleListResize(entry.target, false);
|
|
16728
|
+
onResize?.(entry);
|
|
16729
|
+
});
|
|
16730
|
+
const onListRefUpdated = useEventCallback((el) => {
|
|
16731
|
+
if (el) handleListResize(el, true);
|
|
16732
|
+
});
|
|
16733
|
+
/**
|
|
16734
|
+
* Tracks the row holding focus: focusin finds the row the target is in (the visible-items element's
|
|
16735
|
+
* child it is inside); focusout to outside the list releases it, and the range is updated so the row
|
|
16736
|
+
* unmounts if it is out of the range.
|
|
16737
|
+
*/
|
|
16738
|
+
const handleFocusIn = useEventCallback((event) => {
|
|
16739
|
+
const visibleItemsEl = getVisibleItemsEl();
|
|
16740
|
+
let el = event.target instanceof Element ? event.target : null;
|
|
16741
|
+
while (el !== null && el.parentElement !== visibleItemsEl) el = el.parentElement;
|
|
16742
|
+
focusedKeyRef.current = el?.getAttribute("data-virtual-list-item-key") ?? null;
|
|
16743
|
+
});
|
|
16744
|
+
const handleFocusOut = useEventCallback((event) => {
|
|
16745
|
+
const listEl = listElRef.current;
|
|
16746
|
+
const next = event.relatedTarget;
|
|
16747
|
+
if (listEl === null || next instanceof Node && listEl.contains(next)) return;
|
|
16748
|
+
if (focusedKeyRef.current !== null) {
|
|
16749
|
+
focusedKeyRef.current = null;
|
|
16750
|
+
updateRange("listScroll", true, null);
|
|
16751
|
+
}
|
|
16752
|
+
});
|
|
16753
|
+
useLayoutEffect(() => {
|
|
16754
|
+
const listEl = listElRef.current;
|
|
16755
|
+
if (listEl === null) return;
|
|
16756
|
+
listEl.addEventListener("focusin", handleFocusIn);
|
|
16757
|
+
listEl.addEventListener("focusout", handleFocusOut);
|
|
16758
|
+
return () => {
|
|
16759
|
+
listEl.removeEventListener("focusin", handleFocusIn);
|
|
16760
|
+
listEl.removeEventListener("focusout", handleFocusOut);
|
|
16761
|
+
};
|
|
16762
|
+
}, [handleFocusIn, handleFocusOut]);
|
|
16763
|
+
useLayoutEffect(() => {
|
|
16764
|
+
if (defaultScrollIndex != null) scrollTo({ index: defaultScrollIndex });
|
|
16765
|
+
else if (defaultScrollKey != null) scrollTo({ key: defaultScrollKey });
|
|
16766
|
+
}, [sessionId]);
|
|
16767
|
+
const itemsContainerStyle = useMemo(() => ({
|
|
16768
|
+
boxSizing: "content-box",
|
|
16769
|
+
width: px(totalColumnsWidth) ?? void 0,
|
|
16770
|
+
paddingTop: px(paddingTop) ?? void 0,
|
|
16771
|
+
...typeof itemsStyle == "object" ? itemsStyle : {}
|
|
16772
|
+
}), [
|
|
16773
|
+
totalColumnsWidth,
|
|
16774
|
+
paddingTop,
|
|
16775
|
+
itemsStyle
|
|
16776
|
+
]);
|
|
16777
|
+
const [keptItemsBefore, keptItemsAfter] = useMemo(() => {
|
|
16778
|
+
if (!virtual) return [EMPTY_ARRAY, EMPTY_ARRAY];
|
|
16779
|
+
const before = [];
|
|
16780
|
+
const after = [];
|
|
16781
|
+
keptKeysRef.current.forEach((key) => {
|
|
16782
|
+
const index = keyToIndexRef.current.get(key);
|
|
16783
|
+
if (index === void 0) return;
|
|
16784
|
+
const item = items[index];
|
|
16785
|
+
if (item !== void 0) {
|
|
16786
|
+
if (index < startIndex) before.push(item);
|
|
16787
|
+
else if (index > endIndex) after.push(item);
|
|
16788
|
+
}
|
|
16789
|
+
});
|
|
16790
|
+
return [before, after];
|
|
16791
|
+
}, [
|
|
16792
|
+
virtual,
|
|
16793
|
+
keptKeysRef.current,
|
|
16794
|
+
items,
|
|
16795
|
+
startIndex,
|
|
16796
|
+
endIndex
|
|
16797
|
+
]);
|
|
16798
|
+
/**
|
|
16799
|
+
* Renders one item. With `itemResizable` it is wrapped in a ResizeObserverWrapper: when the ref is
|
|
16800
|
+
* set the item gets `data-virtual-list-item-key` and is measured in the next microtask batch
|
|
16801
|
+
* (`isFirst`); later sizes come from the ResizeObserver. The React key is `sessionId:key`, so a new
|
|
16802
|
+
* session remounts every item.
|
|
16803
|
+
*/
|
|
16804
|
+
const renderItem = useCallback((item, absolute) => {
|
|
16805
|
+
const key = getKey(item);
|
|
16806
|
+
const index = keyToIndexRef.current.get(key);
|
|
16807
|
+
const reactKey = sessionId !== void 0 ? `${sessionId}:${key}` : key;
|
|
16808
|
+
const absoluteStyle = absolute ? {
|
|
16809
|
+
position: "absolute",
|
|
16810
|
+
top: px(fenwickTree.sum(index) - fenwickTree.sum(startIndex) + firstRowStartRef.current),
|
|
16811
|
+
left: 0,
|
|
16812
|
+
right: 0
|
|
16813
|
+
} : void 0;
|
|
16814
|
+
const colsNode = renderCol != null ? /* @__PURE__ */ jsx(ItemColumns, {
|
|
16815
|
+
index,
|
|
16816
|
+
item
|
|
16817
|
+
}) : void 0;
|
|
16818
|
+
const itemWithColsNode = renderItemWithCols != null ? /* @__PURE__ */ jsx(ItemColumns, {
|
|
16819
|
+
index,
|
|
16820
|
+
item
|
|
16821
|
+
}) : void 0;
|
|
16822
|
+
if (itemResizable) return /* @__PURE__ */ jsx(ResizeObserverWrapper, {
|
|
16823
|
+
simulateRefUpdateTrigger: sessionId,
|
|
16824
|
+
disabled: !virtual,
|
|
16825
|
+
onRefUpdate: (el) => {
|
|
16826
|
+
if (el) {
|
|
16827
|
+
el.setAttribute(ITEM_KEY_ATTR, key);
|
|
16828
|
+
if (!ignoreItemResize && virtual) {
|
|
16829
|
+
enqueueItemResize({
|
|
16830
|
+
key,
|
|
16831
|
+
target: el,
|
|
16832
|
+
isFirst: true
|
|
16833
|
+
});
|
|
16834
|
+
scheduleItemBatchResize();
|
|
16835
|
+
}
|
|
16836
|
+
}
|
|
16837
|
+
},
|
|
16838
|
+
onResize: ({ entry, isFirst }) => {
|
|
16839
|
+
if (!ignoreItemResize) {
|
|
16840
|
+
enqueueItemResize({
|
|
16841
|
+
key,
|
|
16842
|
+
target: entry.target,
|
|
16843
|
+
isFirst
|
|
16844
|
+
});
|
|
16845
|
+
getResizeObserverDelegate().runAfterEntries(handleItemBatchResize);
|
|
16846
|
+
}
|
|
16847
|
+
},
|
|
16848
|
+
children: ({ ref }) => {
|
|
16849
|
+
if (ItemComponent) return /* @__PURE__ */ jsx(ItemComponent, {
|
|
16850
|
+
domRef: ref,
|
|
16851
|
+
item,
|
|
16852
|
+
index,
|
|
16853
|
+
renderedCols: colsNode,
|
|
16854
|
+
renderedItemWithCols: itemWithColsNode,
|
|
16855
|
+
style: absoluteStyle
|
|
16856
|
+
});
|
|
16857
|
+
else if (renderItemChild == null) return;
|
|
16858
|
+
else return renderItemChild({
|
|
16859
|
+
key: void 0,
|
|
16860
|
+
domRef: ref,
|
|
16861
|
+
item,
|
|
16862
|
+
index,
|
|
16863
|
+
renderedCols: colsNode,
|
|
16864
|
+
renderedItemWithCols: itemWithColsNode,
|
|
16865
|
+
style: absoluteStyle
|
|
16866
|
+
});
|
|
16867
|
+
}
|
|
16868
|
+
}, reactKey);
|
|
16869
|
+
else if (ItemComponent) return /* @__PURE__ */ jsx(ItemComponent, {
|
|
16870
|
+
domRef: null,
|
|
16871
|
+
item,
|
|
16872
|
+
index,
|
|
16873
|
+
renderedCols: colsNode,
|
|
16874
|
+
renderedItemWithCols: itemWithColsNode,
|
|
16875
|
+
style: absoluteStyle
|
|
16876
|
+
}, reactKey);
|
|
16877
|
+
else if (renderItemChild == null) return;
|
|
16878
|
+
else return renderItemChild({
|
|
16879
|
+
key: reactKey,
|
|
16880
|
+
domRef: null,
|
|
16881
|
+
item,
|
|
16882
|
+
index,
|
|
16883
|
+
renderedCols: colsNode,
|
|
16884
|
+
renderedItemWithCols: itemWithColsNode,
|
|
16885
|
+
style: absoluteStyle
|
|
16886
|
+
});
|
|
16887
|
+
}, [
|
|
16888
|
+
virtual,
|
|
16889
|
+
getKey,
|
|
16890
|
+
fenwickTree,
|
|
16891
|
+
startIndex,
|
|
16892
|
+
renderCol,
|
|
16893
|
+
renderItemWithCols,
|
|
16894
|
+
itemResizable,
|
|
16895
|
+
sessionId,
|
|
16896
|
+
ignoreItemResize,
|
|
16897
|
+
enqueueItemResize,
|
|
16898
|
+
scheduleItemBatchResize,
|
|
16899
|
+
handleItemBatchResize,
|
|
16900
|
+
ItemComponent,
|
|
16901
|
+
renderItemChild
|
|
16902
|
+
]);
|
|
16903
|
+
const renderedVisibleItems = useMemo(() => visibleItems.map((item) => renderItem(item, false)), [renderItem, visibleItems]);
|
|
16904
|
+
const renderedKeptBefore = useMemo(() => keptItemsBefore.map((item) => renderItem(item, true)), [renderItem, keptItemsBefore]);
|
|
16905
|
+
const renderedKeptAfter = useMemo(() => keptItemsAfter.map((item) => renderItem(item, true)), [renderItem, keptItemsAfter]);
|
|
16906
|
+
useNonVirtualContentResize(itemsElRef, onContentResize, !virtual);
|
|
16907
|
+
const onContentResizeRef = useLatestRef(onContentResize);
|
|
16908
|
+
const hasItems = items.length !== 0;
|
|
16909
|
+
const footerNode = useMemo(() => typeof footer != "function" ? footer : /* @__PURE__ */ jsx(ResizeObserverWrapper, {
|
|
16910
|
+
onResize: () => {
|
|
16911
|
+
onContentResizeRef.current?.();
|
|
16912
|
+
},
|
|
16913
|
+
onUnmounted: () => {
|
|
16914
|
+
onContentResizeRef.current?.();
|
|
16915
|
+
},
|
|
16916
|
+
children: ({ ref }) => footer({ domRef: ref })
|
|
16917
|
+
}), [footer, onContentResizeRef]);
|
|
16918
|
+
const emptyNode = useMemo(() => typeof empty != "function" ? empty : /* @__PURE__ */ jsx(ResizeObserverWrapper, {
|
|
16919
|
+
onResize: () => {
|
|
16920
|
+
onContentResizeRef.current?.();
|
|
16921
|
+
},
|
|
16922
|
+
onUnmounted: () => {
|
|
16923
|
+
onContentResizeRef.current?.();
|
|
16924
|
+
},
|
|
16925
|
+
children: ({ ref }) => empty({ domRef: ref })
|
|
16926
|
+
}), [empty, onContentResizeRef]);
|
|
16927
|
+
const { snapshotAnchorElementStateAndSaveToRef, restoreScrollStateFromAnchor, clearAnchor } = useScrollAnchor(listElRef, getVisibleItemsEl);
|
|
16928
|
+
useMemoWithPrevious((previous) => {
|
|
16929
|
+
if (!previous || !itemResizable) return;
|
|
16930
|
+
const [wasVirtual, previousSessionId] = previous;
|
|
16931
|
+
if (previousSessionId === sessionId) {
|
|
16932
|
+
if (wasVirtual && !virtual) snapshotAnchorElementStateAndSaveToRef();
|
|
16933
|
+
else {
|
|
16934
|
+
const visibleItemsEl = getVisibleItemsEl();
|
|
16935
|
+
if (!(visibleItemsEl instanceof HTMLElement)) return;
|
|
16936
|
+
const rows = [];
|
|
16937
|
+
for (const el of Array.from(visibleItemsEl.children)) {
|
|
16938
|
+
const key = el.getAttribute(ITEM_KEY_ATTR);
|
|
16939
|
+
if (key !== null && el instanceof HTMLElement) rows.push({
|
|
16940
|
+
el,
|
|
16941
|
+
key
|
|
16942
|
+
});
|
|
16943
|
+
}
|
|
16944
|
+
const { spans, firstStart } = measureRowSpans(visibleItemsEl, rows.map((row) => row.el));
|
|
16945
|
+
if (rows[0] !== void 0 && keyToIndexRef.current.get(rows[0].key) === 0) leadingMarginRef.current = firstStart;
|
|
16946
|
+
rows.forEach(({ key }, i) => {
|
|
16947
|
+
const offset = spans[i] - itemEstimatedSize;
|
|
16948
|
+
if (offset === 0) keyToHeightOffsetRef.current.delete(key);
|
|
16949
|
+
else keyToHeightOffsetRef.current.set(key, offset);
|
|
16950
|
+
});
|
|
16951
|
+
}
|
|
16952
|
+
}
|
|
16953
|
+
}, [virtual, sessionId]);
|
|
16954
|
+
useLayoutEffectWithPrevious((previous) => {
|
|
16955
|
+
if (!itemResizable || !previous) return;
|
|
16956
|
+
const [wasVirtual, previousSessionId] = previous;
|
|
16957
|
+
if (previousSessionId === sessionId && wasVirtual && !virtual) {
|
|
16958
|
+
restoreScrollStateFromAnchor();
|
|
16959
|
+
clearAnchor();
|
|
16960
|
+
}
|
|
16961
|
+
}, [virtual, sessionId]);
|
|
16962
|
+
const { onKeyDown: onKeyboardScroll } = useKeyboardScroll(scrollTo, { pageBottomOcclusion: keyboardPageBottomOcclusion });
|
|
16963
|
+
/** The consumer's `onKeyDown` first: a key it takes (`preventDefault`) does not scroll the list. */
|
|
16964
|
+
const onKeyDown = useCallback((event) => {
|
|
16965
|
+
onNativeKeyDown?.(event);
|
|
16966
|
+
onKeyboardScroll(event);
|
|
16967
|
+
}, [onNativeKeyDown, onKeyboardScroll]);
|
|
16968
|
+
const emptyCentered = items.length === 0 && emptyAlign === "center";
|
|
16969
|
+
const rootClassName = cn("r-virtual-list", columns.length !== 0 && "r-virtual-list--x-scrollable", showScrollbar && "r-virtual-list--show-scrollbar", printable === true && "r-virtual-list--printable", emptyCentered && "r-virtual-list--empty-centered", iosCompensationEnabled && "r-virtual-list--ios-compensation-enabled", className);
|
|
16970
|
+
return /* @__PURE__ */ jsx(ResizeObserverWrapper, {
|
|
16971
|
+
forwardedRef: mergedListRefSetter,
|
|
16972
|
+
onRefUpdate: onListRefUpdated,
|
|
16973
|
+
onResize: onListResizeObserved,
|
|
16974
|
+
children: ({ ref }) => /* @__PURE__ */ jsxs("div", {
|
|
16975
|
+
...nativeProps,
|
|
16976
|
+
"data-slot": "virtual-list",
|
|
16977
|
+
className: rootClassName,
|
|
16978
|
+
style,
|
|
16979
|
+
onScroll: handleListScroll,
|
|
16980
|
+
ref,
|
|
16981
|
+
onKeyDown,
|
|
16982
|
+
tabIndex: focusable ? 0 : -1,
|
|
16983
|
+
children: [
|
|
16984
|
+
header,
|
|
16985
|
+
hasItems ? /* @__PURE__ */ jsx("div", {
|
|
16986
|
+
"data-slot": "virtual-list-items",
|
|
16987
|
+
ref: itemsElRef,
|
|
16988
|
+
className: itemsClassName,
|
|
16989
|
+
style: itemsContainerStyle,
|
|
16990
|
+
children: /* @__PURE__ */ jsx(VirtualListContext.Provider, {
|
|
16991
|
+
value: imperativeApi,
|
|
16992
|
+
children: /* @__PURE__ */ jsx(ColumnsContext.Provider, {
|
|
16993
|
+
value: columnsContextValue,
|
|
16994
|
+
children: createElement(visibleItemsTag, {
|
|
16995
|
+
"data-slot": "virtual-list-visible-items",
|
|
16996
|
+
style: virtual ? VIRTUAL_VISIBLE_ITEMS_STYLE : NON_VIRTUAL_VISIBLE_ITEMS_STYLE,
|
|
16997
|
+
...visibleItemsProps
|
|
16998
|
+
}, ...renderedKeptBefore, ...renderedVisibleItems, ...renderedKeptAfter)
|
|
16999
|
+
})
|
|
17000
|
+
})
|
|
17001
|
+
}, "items") : /* @__PURE__ */ jsx("div", {
|
|
17002
|
+
"data-slot": "virtual-list-empty",
|
|
17003
|
+
className: cn("r-virtual-list__empty", emptyCentered && "r-virtual-list__empty--center"),
|
|
17004
|
+
children: emptyNode
|
|
17005
|
+
}, "empty"),
|
|
17006
|
+
footerNode,
|
|
17007
|
+
paddingBottom ? /* @__PURE__ */ jsx("div", { style: { paddingBottom } }) : null,
|
|
17008
|
+
footerAfterBottomPadding
|
|
17009
|
+
]
|
|
17010
|
+
})
|
|
17011
|
+
});
|
|
17012
|
+
}
|
|
17013
|
+
//#endregion
|
|
14875
17014
|
//#region src/components/tasks/tasks-panel.recipe.ts
|
|
14876
17015
|
const tasksPanel = tv({ slots: {
|
|
14877
17016
|
root: ["flex min-h-0 min-w-0 flex-1 flex-col", "bg-layer-canvas text-foreground"],
|
|
@@ -16688,4 +18827,4 @@ function ThemeProvider({ mode, defaultMode = "light", onModeChange, storageKey,
|
|
|
16688
18827
|
});
|
|
16689
18828
|
}
|
|
16690
18829
|
//#endregion
|
|
16691
|
-
export { ActivityInboxControls, ActivityInboxLoadingMore, ActivityInboxPanel, ActivityInboxSelectionCount, ActivityInboxSelectionToolbar, ActivityInboxSidebar, ActivityInboxViewport, AlertDialog, AlertDialogAction, AlertDialogBody, AlertDialogCancel, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AppRailFooter, AppRailHeader, AppRailItem, AppRailItemAttention, AppRailItemBadge, AppRailItemIcon, AppRailItemIndicator, AppRailItemLabel, AppRailNav, AppRailRoot, AppShellMainSlot, AppShellOverlaySlot, AppShellPanelSlot, AppShellRailSlot, AppShellRoot, AppShellSidebarSlot, AppShellSidebarTrigger, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Banner, BannerAction, BannerDescription, BannerTitle, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardLeading, CardTitle, CardTrailing, ChatIcon, Checkbox, CheckboxIndicator, CodeBlock, CodeBlockAction, CodeBlockActions, CodeBlockBody, CodeBlockHeader, CodeBlockTitle, Combobox, ComboboxChip, ComboboxChipRemove, ComboboxChips, ComboboxClear, ComboboxCollection, ComboboxContent, ComboboxControl, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxHeader, ComboboxInput, ComboboxInputGroup, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxList, ComboboxPopup, ComboboxPortal, ComboboxRow, ComboboxSeparator, ComboboxTrigger, ComboboxTriggerCount, ComboboxTriggerIndicator, ComboboxValue, Composer, ComposerActions, ComposerAttachment, ComposerAttachmentBody, ComposerAttachmentFailedOverlay, ComposerAttachmentFile, ComposerAttachmentImage, ComposerAttachmentMeta, ComposerAttachmentRemove, ComposerAttachmentTitle, ComposerAttachmentUploadProgressBar, ComposerAttachmentUploadingOverlay, ComposerAttachments, ComposerAttachmentsScrollArea, ComposerDropOverlay, ComposerDropOverlayLabel, ComposerDropOverlayPrompt, ComposerIconButton, ComposerInput, ComposerMeta, ComposerRoot, ComposerSubmit, ComposerSuggestionAside, ComposerSuggestionContent, ComposerSuggestionGroup, ComposerSuggestionGroupLabel, ComposerSuggestionIcon, ComposerSuggestionList, ComposerSuggestionMeta, ComposerSuggestionOption, ComposerSuggestionTitle, ComposerToolbar, ComposerToolbarLabel, ContextMenu, ContextMenuCheckboxItem, ContextMenuCheckboxItemIndicator, ContextMenuContent, ContextMenuFooter, ContextMenuItem, ContextMenuItemLabel, ContextMenuLabel, ContextMenuPopup, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuRadioItemIndicator, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSubmenu, ContextMenuSubmenuTrigger, ContextMenuTrigger, ConversationPanelBody, ConversationPanelContent, ConversationPanelEmptyState, ConversationPanelEmptyStateBrand, ConversationPanelEmptyStateBrandMark, ConversationPanelFooter, ConversationPanelRoot, ConversationPanelTabs, CopyableCode, CopyableCodeAction, CopyableCodeRoot, DescriptionDetails, DescriptionItem, DescriptionList, DescriptionTerm, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogNav, DialogOverlay, DialogPortal, DialogSurface, DialogTitle, DialogTrigger, DirectMessageIcon, Drawer, DrawerActions, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuCheckboxItemIndicator, DropdownMenuContent, DropdownMenuFooter, DropdownMenuItem, DropdownMenuItemCount, DropdownMenuItemLabel, DropdownMenuLabel, DropdownMenuPopup, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuRadioItemIndicator, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSubmenu, DropdownMenuSubmenuTrigger, DropdownMenuTrigger, EmptyState, EmptyStateActions, EmptyStateContent, EmptyStateDescription, EmptyStateIcon, EmptyStateTitle, Field, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel, FieldValidity, FileAction, FileActions, FileContent, FileImage, FileMeta, FileMetaItem, FileName, FilePreview, FilePreviewBadge, FilePreviewCode, FilePreviewDocument, FilePreviewFile, FilePreviewMedia, FileRow, FileRowOpen, FileThumbnail, FilesList, FilesPanel, FilesViewport, FloatButton, FloatButtonGroup, FormSection, IconTile, InboxItem, InboxItemActions, InboxItemBody, InboxItemContent, InboxItemContext, InboxItemHeading, InboxItemHoverTarget, InboxItemLeading, InboxItemMarks, InboxItemMessage, InboxItemMetadata, InboxItemSelection, InboxItemSender, InboxItemSubtitle, InboxItemTimestamp, InboxItemTitle, InboxItemTrailing, InboxItemTrigger, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupInput, Kbd, KbdGroup, Label, LabelAsterisk, LabelOptional, LabelSub, LegacyTaskPanelActions, LegacyTaskPanelBody, LegacyTaskPanelField, LegacyTaskPanelFieldLabel, LegacyTaskPanelFieldText, LegacyTaskPanelFieldTitle, LegacyTaskPanelHeader, LegacyTaskPanelIcon, LegacyTaskPanelMeta, LegacyTaskPanelMetaLabel, LegacyTaskPanelMetaRow, LegacyTaskPanelMetaValue, LegacyTaskPanelRoot, LegacyTaskPanelSection, LegacyTaskPanelStatus, LegacyTaskPanelTitle, LegacyTaskPanelTitleMeta, Lightbox, LightboxActions, LightboxClose, LightboxContent, LightboxHeader, LightboxHeaderLabel, LightboxHeaderMeta, LightboxInfoActions, LightboxInfoBar, LightboxInfoDock, LightboxInfoTitle, LightboxMedia, LightboxNavigationButton, LightboxPortal, LightboxStage, LightboxThumbnail, LightboxThumbnailStrip, LightboxTitle, LightboxToolbar, LightboxTrigger, ListRow, ListRowBody, ListRowBottom, ListRowChevron, ListRowDescription, ListRowGroup, ListRowIcon, ListRowMeta, ListRowTitle, ListRowTop, ListRowValue, LiveAgentActivityBar, LiveAgentActivityBarAvatar, LiveAgentActivityBarContent, LiveAgentActivityBarRow, LiveAgentActivityBarStatus, LiveAgentActivityBarText, MarkdownBlockquote, MarkdownCell, MarkdownCheckbox, MarkdownHeaderCell, MarkdownHeading, MarkdownImage, MarkdownInlineCode, MarkdownLink, MarkdownList, MarkdownListItem, MarkdownMark, MarkdownOrderedList, MarkdownParagraph, MarkdownRule, MarkdownTable, MarkdownTableRow, MarkdownTableScroll, MessageActionCard, MessageActionCardActions, MessageActionCardActionsNote, MessageActionCardCollapseTrigger, MessageActionCardCompletion, MessageActionCardContent, MessageActionCardHeader, MessageActionCardHeaderDescription, MessageActionCardHeaderIcon, MessageActionCardHeaderStatus, MessageActionCardHeaderTitle, MessageActionCardHint, MessageActionCardHintLabel, MessageActionCardNotice, MessageActionCardProperties, MessageActionCardProperty, MessageActionCardPropertyLabel, MessageActionCardPropertyValue, MessageActionCardScope, MessageActionCardScopeDescription, MessageActionCardScopeList, MessageActionCardScopeListLabel, MessageActionCardScopeListViewport, MessageActionCardScopeName, MessageActionCardStatus, MessageAttachmentAction, MessageAttachmentCard, MessageAttachmentCommentSummary, MessageAttachmentDiffAddition, MessageAttachmentDiffDeletion, MessageAttachmentDiffSummary, MessageAttachmentGroup, MessageAttachmentMeta, MessageAttachmentMetaEnd, MessageAttachmentMetaStart, MessageAttachmentSummary, MessageAttachmentTitle, MessageEmbed, MessageEmbedContent, MessageEmbedFooter, MessageEmbedHeader, MessageForwardedBundle, MessageForwardedBundleAttachment, MessageForwardedBundleAttachmentMeta, MessageForwardedBundleAttachmentName, MessageForwardedBundleAttachments, MessageForwardedBundleFooter, MessageForwardedBundleFooterAction, MessageForwardedBundleGallery, MessageForwardedBundleGalleryTile, MessageForwardedBundleHeader, MessageForwardedBundleHeaderContent, MessageForwardedBundleHeaderCount, MessageForwardedBundleHeaderLabel, MessageForwardedBundleItem, MessageForwardedBundleItemAuthor, MessageForwardedBundleItemContent, MessageForwardedBundleItemMeta, MessageForwardedBundleItems, MessageForwardedBundleSource, MessageForwardedBundleSourceLabel, MessageIcon, MessageImageGallery, MessageImageGalleryAction, MessageImageGalleryItem, MessageImageGalleryMedia, MessageImageGalleryOverlay, MessageImageGalleryPreview, MessageImageGalleryRow, MessageItem, MessageItemAttachments, MessageItemAvatarSlot, MessageItemBadge, MessageItemBadgeSeparator, MessageItemBody, MessageItemContent, MessageItemContinuationTime, MessageItemEdited, MessageItemEmbedded, MessageItemFooter, MessageItemGutter, MessageItemHeader, MessageItemInlineStatus, MessageItemMeta, MessageItemReadReceipt, MessageItemSavedIndicator, MessageItemSearchMatch, MessageItemSender, MessageItemSenderBadges, MessageItemSenderButton, MessageItemSenderGroup, MessageItemTime, MessageItemToolbar, MessageItemToolbarButton, MessageItemTranslationAction, MessageItemTranslationFailedStatus, MessageItemTranslationOriginal, MessageItemTranslationOriginalLabel, MessageItemTranslationProgressTrack, MessageItemTranslationRetryAction, MessageItemTranslationStatus, MessageList, MessageListDateDivider, MessageListHistoryLimited, MessageListNotice, MessageMultiSelect, MessageMultiSelectCheckbox, MessageMultiSelectToolbar, MessageQuotedPreview, MessageQuotedPreviewAttachment, MessageQuotedPreviewAttachments, MessageQuotedPreviewAuthor, MessageQuotedPreviewAuthorMeta, MessageQuotedPreviewAuthorName, MessageQuotedPreviewChannel, MessageQuotedPreviewContent, MessageQuotedPreviewHeader, MessageQuotedPreviewLoading, MessageQuotedPreviewText, MessageQuotedPreviewThread, MessageQuotedPreviewTimestamp, MessageQuotedPreviewUnavailable, MessageReactionAddButton, MessageReactionChip, MessageReactionCount, MessageReactionErrorIndicator, MessageReactionGlyph, MessageReactionPickerContent, MessageReactionPickerStatic, MessageReactionQuickButton, MessageReactionQuickRow, MessageReactionTooltipContent, MessageReactionTooltipMeta, MessageReactionTooltipName, MessageReactionTooltipNameText, MessageReactionTooltipNames, MessageReactionTooltipSeparator, MessageReferenceChip, MessageReferenceIndicator, MessageReferenceLabel, MessageReferencePending, MessageReferenceText, MessageReferenceUnavailable, MessageReplies, MessageRepliesItem, MessageRepliesItemContent, MessageRepliesItemSender, MessageRepliesItemTime, MessageRepliesSummary, MessageRepliesSummaryItem, MessageTextSelectionToolbar, MessageTextSelectionToolbarButton, MessageTextSelectionToolbarSeparator, MessageVideoPreview, MessageVideoPreviewAction, MessageVideoPreviewFailureAction, MessageVideoPreviewFailureContent, MessageVideoPreviewFailureIcon, MessageVideoPreviewFailureTitle, MessageVideoPreviewFallback, MessageVideoPreviewMedia, MobileNavItem, MobileNavLabel, MobileNavRoot, NotificationCenter, NotificationCenterActionButton, NotificationCenterCount, NotificationCenterEmptyState, NotificationCenterHeader, NotificationCenterItem, NotificationCenterItemActions, NotificationCenterItemBody, NotificationCenterItemContent, NotificationCenterItemIcon, NotificationCenterItemRow, NotificationCenterItemTitle, NotificationCenterList, NotificationCenterPopup, NotificationCenterScroller, NotificationCenterTitle, NotificationCenterTrigger, OtterIndicator, Panel, PanelAction, PanelActions, PanelActivity, PanelBody, PanelFooter, PanelHeader, PanelHeaderContent, PanelHeaderIcon, PanelHeaderRoot, PanelHeading, PanelMeta, PanelNotice, PanelNoticeActions, PanelNoticeContent, PanelScrollContent, PanelScrollViewport, PanelSection, PanelSectionHeader, PanelStatus, PanelTitle, PanelToggleAction, PickerTriggerButton, Popover, PopoverArrow, PopoverBackdrop, PopoverClose, PopoverContent, PopoverDescription, PopoverHeader, PopoverPopup, PopoverPortal, PopoverSeparator, PopoverTitle, PopoverTrigger, PopoverViewport, PreviewCard, PreviewCardArrow, PreviewCardContent, PreviewCardDescription, PreviewCardHeader, PreviewCardPopup, PreviewCardPortal, PreviewCardSeparator, PreviewCardTitle, PreviewCardTrigger, ProfilePanel, ProfilePanelBody, ProfilePanelHeader, ProfilePanelHeaderContent, ProfilePanelSection, Progress, ProgressHeader, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, RadioGroup, RadioGroupIndicator, RadioGroupItem, RaftLogo, RaftLogoLockup, RaftWordmark, ResizableGroup, ResizableHandle, ResizablePanel, SINGLE_IMAGE_MAX_HEIGHT, SINGLE_IMAGE_MAX_WIDTH, ScrollArea, ScrollAreaContent, ScrollAreaCorner, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport, SearchEntityResult, SearchEntityResultActivity, SearchEntityResultContent, SearchEntityResultDescription, SearchEntityResultHeader, SearchEntityResultIcon, SearchEntityResultLeading, SearchEntityResultTitle, SearchMessageResult, SearchMessageResultBody, SearchMessageResultHeader, SearchMessageResultMatch, SearchMessageResultMeta, SearchMessageResultSender, SearchMessageResultThreadMeta, SearchMessageResultTimestamp, SearchResultsList, SearchResultsSection, SearchResultsSectionHeading, SearchResultsSummary, SearchShellFilters, SearchShellRoot, SearchShellViewport, SearchThreadMessageResult, SearchThreadResult, SearchThreadResultHeader, SearchThreadResultMessages, SearchThreadResultMeta, SearchThreadResultTimestamp, SearchThreadResultTitle, SearchTrigger, SearchTriggerLabel, SearchTriggerShortcut, SegmentedControl, SegmentedControlCount, SegmentedControlItem, SegmentedControlLabel, Select, SelectArrow, SelectContent, SelectGroup, SelectGroupLabel, SelectIcon, SelectItem, SelectItemDescription, SelectItemIndicator, SelectItemLeading, SelectItemText, SelectLabel, SelectList, SelectPopup, SelectPortal, SelectScrollDownArrow, SelectScrollUpArrow, SelectSeparator, SelectTrigger, SelectValue, Separator, SidebarBody, SidebarContent, SidebarControl, SidebarGroupLabel, SidebarHeader, SidebarHeaderTitle, SidebarItem, SidebarItemAside, SidebarItemChannelIcon, SidebarItemContent, SidebarItemCount, SidebarItemCountSeparator, SidebarItemIcon, SidebarItemIndicator, SidebarItemMeta, SidebarItemMetaIcon, SidebarItemStatus, SidebarItemSubtitle, SidebarItemThreadCount, SidebarItemTitle, SidebarList, SidebarLiveActivity, SidebarMenu, SidebarMenuAside, SidebarMenuCount, SidebarMenuIcon, SidebarMenuTitle, SidebarRoot, SidebarSection, SidebarSectionAttention, SidebarSectionChevron, SidebarSectionControl, SidebarSectionControls, SidebarSectionCount, SidebarSectionDisclosure, SidebarSectionEmpty, SidebarSectionHeader, SidebarSectionTitle, SidebarSectionTrailing, Skeleton, SortableComposerAttachment, SortableComposerAttachments, SortableComposerAttachmentsScroll, SortableTabsList, SortableTabsTab, Spinner, Status, Step, Steps, Switch, SwitchThumb, SystemMessage, SystemMessageContent, SystemMessageGroup, SystemMessageGroupChevron, SystemMessageGroupDisclosure, SystemMessageGroupHeader, SystemMessageGroupPanel, SystemMessageGroupSummary, SystemMessageTime, TASK_STATUS_IDS, Table, TableBody, TableCaption, TableCell, TableColgroup, TableColumn, TableHead, TableHeader, TableRow, Tabs, TabsLabel, TabsList, TabsPanel, TabsTab, TaskBoardColumn, TaskBoardColumnBadge, TaskBoardColumnChevron, TaskBoardColumnCount, TaskBoardColumnEmpty, TaskBoardColumnEmptyAllowed, TaskBoardColumnEmptyBlocked, TaskBoardColumnEmptyIdle, TaskBoardColumnHeading, TaskBoardColumnItems, TaskBoardColumnPanel, TaskBoardColumnTrigger, TaskBoardRoot, TaskCard, TaskCardBody, TaskCardChannel, TaskCardDescription, TaskCardLegacy, TaskCardMeta, TaskCardNumber, TaskCardRow, TaskCardStatus, TaskCardTitle, TaskChip, TaskClosedIcon, TaskDndProvider, TaskDoneIcon, TaskDragOverlay, TaskDraggable, TaskDropPlaceholder, TaskDropZone, TaskInProgressIcon, TaskInReviewIcon, TaskListRoot, TaskLoadingList, TaskLoadingRow, TaskSection, TaskSectionBadge, TaskSectionChevron, TaskSectionCount, TaskSectionEmpty, TaskSectionHeading, TaskSectionItems, TaskSectionPanel, TaskSectionTrigger, TaskStatusBadge, TaskStatusIcon, TaskTodoIcon, TasksPanelEmpty, TasksPanelRoot, TasksPanelToolbar, TasksPanelToolbarFilters, TasksPanelToolbarGroup, TasksPanelToolbarLabel, TasksPanelToolbarNewTask, TasksPanelToolbarView, TasksPanelViewport, Text, TextHeading, TextLink, TextMono, TextSans, Textarea, TextareaCounter, TextareaGroup, ThemeProvider, ThreadIcon, ThreadPanelActions, ThreadPanelBody, ThreadPanelContent, ThreadPanelFooter, ThreadPanelHeader, ThreadPanelOriginalMessage, ThreadPanelReplyCount, ThreadPanelReplySummary, ThreadPanelRoot, ThreadPanelSearch, ThreadPanelSearchCount, ThreadPanelSearchTrailing, ThreadPanelTitle, ThreadPanelTitleMeta, Timeline, TimelineContent, TimelineItem, TimelineTime, TimelineTitle, ToastAction, ToastActions, ToastArrow, ToastBody, ToastClose, ToastContent, ToastDescription, ToastIcon, ToastPortal, ToastPositioner, ToastProgress, ToastProvider, ToastRoot, ToastTitle, ToastViewport, ToggleGroup, ToggleGroupItem, ToggleGroupLabel, Tooltip, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, buildMessageImageGalleryRows, buttonVariants, classifyMessageImageAspect, cn, createMessageTextSelectionAnchor, createToastManager, getTextSelectionFocusRect, installInputModality, taskStatusSoft, taskStatusStrong, textLinkVariants, toast, useComposerSuggestions, useOrderedTabs, useTheme, useToastManager };
|
|
18830
|
+
export { ActivityInboxControls, ActivityInboxLoadingMore, ActivityInboxPanel, ActivityInboxSelectionCount, ActivityInboxSelectionToolbar, ActivityInboxSidebar, ActivityInboxViewport, AlertDialog, AlertDialogAction, AlertDialogBody, AlertDialogCancel, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, AppRailFooter, AppRailHeader, AppRailItem, AppRailItemAttention, AppRailItemBadge, AppRailItemIcon, AppRailItemIndicator, AppRailItemLabel, AppRailNav, AppRailRoot, AppShellMainSlot, AppShellOverlaySlot, AppShellPanelSlot, AppShellRailSlot, AppShellRoot, AppShellSidebarSlot, AppShellSidebarTrigger, Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage, Badge, Banner, BannerAction, BannerDescription, BannerTitle, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardLeading, CardTitle, CardTrailing, ChatIcon, Checkbox, CheckboxIndicator, CodeBlock, CodeBlockAction, CodeBlockActions, CodeBlockBody, CodeBlockHeader, CodeBlockTitle, Combobox, ComboboxChip, ComboboxChipRemove, ComboboxChips, ComboboxClear, ComboboxCollection, ComboboxContent, ComboboxControl, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxHeader, ComboboxInput, ComboboxInputGroup, ComboboxItem, ComboboxItemIndicator, ComboboxLabel, ComboboxList, ComboboxPopup, ComboboxPortal, ComboboxRow, ComboboxSeparator, ComboboxTrigger, ComboboxTriggerCount, ComboboxTriggerIndicator, ComboboxValue, Composer, ComposerActions, ComposerAttachment, ComposerAttachmentBody, ComposerAttachmentFailedOverlay, ComposerAttachmentFile, ComposerAttachmentImage, ComposerAttachmentMeta, ComposerAttachmentRemove, ComposerAttachmentTitle, ComposerAttachmentUploadProgressBar, ComposerAttachmentUploadingOverlay, ComposerAttachments, ComposerAttachmentsScrollArea, ComposerDropOverlay, ComposerDropOverlayLabel, ComposerDropOverlayPrompt, ComposerIconButton, ComposerInput, ComposerMeta, ComposerRoot, ComposerSubmit, ComposerSuggestionAside, ComposerSuggestionContent, ComposerSuggestionGroup, ComposerSuggestionGroupLabel, ComposerSuggestionIcon, ComposerSuggestionList, ComposerSuggestionMeta, ComposerSuggestionOption, ComposerSuggestionTitle, ComposerToolbar, ComposerToolbarLabel, ContextMenu, ContextMenuCheckboxItem, ContextMenuCheckboxItemIndicator, ContextMenuContent, ContextMenuFooter, ContextMenuItem, ContextMenuItemLabel, ContextMenuLabel, ContextMenuPopup, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuRadioItemIndicator, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSubmenu, ContextMenuSubmenuTrigger, ContextMenuTrigger, ConversationPanelBody, ConversationPanelContent, ConversationPanelEmptyState, ConversationPanelEmptyStateBrand, ConversationPanelEmptyStateBrandMark, ConversationPanelFooter, ConversationPanelRoot, ConversationPanelTabs, CopyableCode, CopyableCodeAction, CopyableCodeRoot, DescriptionDetails, DescriptionItem, DescriptionList, DescriptionTerm, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogNav, DialogOverlay, DialogPortal, DialogSurface, DialogTitle, DialogTrigger, DirectMessageIcon, Drawer, DrawerActions, DrawerBody, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuCheckboxItemIndicator, DropdownMenuContent, DropdownMenuFooter, DropdownMenuItem, DropdownMenuItemCount, DropdownMenuItemLabel, DropdownMenuLabel, DropdownMenuPopup, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuRadioItemIndicator, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSubmenu, DropdownMenuSubmenuTrigger, DropdownMenuTrigger, EmptyState, EmptyStateActions, EmptyStateContent, EmptyStateDescription, EmptyStateIcon, EmptyStateTitle, Field, FieldControl, FieldDescription, FieldError, FieldItem, FieldLabel, FieldValidity, FileAction, FileActions, FileContent, FileImage, FileMeta, FileMetaItem, FileName, FilePreview, FilePreviewBadge, FilePreviewCode, FilePreviewDocument, FilePreviewFile, FilePreviewMedia, FileRow, FileRowOpen, FileThumbnail, FilesList, FilesPanel, FilesViewport, FloatButton, FloatButtonGroup, FormSection, IconTile, InboxItem, InboxItemActions, InboxItemBody, InboxItemContent, InboxItemContext, InboxItemHeading, InboxItemHoverTarget, InboxItemLeading, InboxItemMarks, InboxItemMessage, InboxItemMetadata, InboxItemSelection, InboxItemSender, InboxItemSubtitle, InboxItemTimestamp, InboxItemTitle, InboxItemTrailing, InboxItemTrigger, InlineCode, Input, InputGroup, InputGroupAddon, InputGroupInput, Kbd, KbdGroup, Label, LabelAsterisk, LabelOptional, LabelSub, LegacyTaskPanelActions, LegacyTaskPanelBody, LegacyTaskPanelField, LegacyTaskPanelFieldLabel, LegacyTaskPanelFieldText, LegacyTaskPanelFieldTitle, LegacyTaskPanelHeader, LegacyTaskPanelIcon, LegacyTaskPanelMeta, LegacyTaskPanelMetaLabel, LegacyTaskPanelMetaRow, LegacyTaskPanelMetaValue, LegacyTaskPanelRoot, LegacyTaskPanelSection, LegacyTaskPanelStatus, LegacyTaskPanelTitle, LegacyTaskPanelTitleMeta, Lightbox, LightboxActions, LightboxClose, LightboxContent, LightboxHeader, LightboxHeaderLabel, LightboxHeaderMeta, LightboxInfoActions, LightboxInfoBar, LightboxInfoDock, LightboxInfoTitle, LightboxMedia, LightboxNavigationButton, LightboxPortal, LightboxStage, LightboxThumbnail, LightboxThumbnailStrip, LightboxTitle, LightboxToolbar, LightboxTrigger, ListRow, ListRowBody, ListRowBottom, ListRowChevron, ListRowDescription, ListRowGroup, ListRowIcon, ListRowMeta, ListRowTitle, ListRowTop, ListRowValue, LiveAgentActivityBar, LiveAgentActivityBarAvatar, LiveAgentActivityBarContent, LiveAgentActivityBarRow, LiveAgentActivityBarStatus, LiveAgentActivityBarText, MarkdownBlockquote, MarkdownCell, MarkdownCheckbox, MarkdownHeaderCell, MarkdownHeading, MarkdownImage, MarkdownInlineCode, MarkdownLink, MarkdownList, MarkdownListItem, MarkdownMark, MarkdownOrderedList, MarkdownParagraph, MarkdownRule, MarkdownTable, MarkdownTableRow, MarkdownTableScroll, MessageActionCard, MessageActionCardActions, MessageActionCardActionsNote, MessageActionCardCollapseTrigger, MessageActionCardCompletion, MessageActionCardContent, MessageActionCardHeader, MessageActionCardHeaderDescription, MessageActionCardHeaderIcon, MessageActionCardHeaderStatus, MessageActionCardHeaderTitle, MessageActionCardHint, MessageActionCardHintLabel, MessageActionCardNotice, MessageActionCardProperties, MessageActionCardProperty, MessageActionCardPropertyLabel, MessageActionCardPropertyValue, MessageActionCardScope, MessageActionCardScopeDescription, MessageActionCardScopeList, MessageActionCardScopeListLabel, MessageActionCardScopeListViewport, MessageActionCardScopeName, MessageActionCardStatus, MessageAttachmentAction, MessageAttachmentCard, MessageAttachmentCommentSummary, MessageAttachmentDiffAddition, MessageAttachmentDiffDeletion, MessageAttachmentDiffSummary, MessageAttachmentGroup, MessageAttachmentMeta, MessageAttachmentMetaEnd, MessageAttachmentMetaStart, MessageAttachmentSummary, MessageAttachmentTitle, MessageEmbed, MessageEmbedContent, MessageEmbedFooter, MessageEmbedHeader, MessageForwardedBundle, MessageForwardedBundleAttachment, MessageForwardedBundleAttachmentMeta, MessageForwardedBundleAttachmentName, MessageForwardedBundleAttachments, MessageForwardedBundleFooter, MessageForwardedBundleFooterAction, MessageForwardedBundleGallery, MessageForwardedBundleGalleryTile, MessageForwardedBundleHeader, MessageForwardedBundleHeaderContent, MessageForwardedBundleHeaderCount, MessageForwardedBundleHeaderLabel, MessageForwardedBundleItem, MessageForwardedBundleItemAuthor, MessageForwardedBundleItemContent, MessageForwardedBundleItemMeta, MessageForwardedBundleItems, MessageForwardedBundleSource, MessageForwardedBundleSourceLabel, MessageIcon, MessageImageGallery, MessageImageGalleryAction, MessageImageGalleryItem, MessageImageGalleryMedia, MessageImageGalleryOverlay, MessageImageGalleryPreview, MessageImageGalleryRow, MessageItem, MessageItemAttachments, MessageItemAvatarSlot, MessageItemBadge, MessageItemBadgeSeparator, MessageItemBody, MessageItemContent, MessageItemContinuationTime, MessageItemEdited, MessageItemEmbedded, MessageItemFooter, MessageItemGutter, MessageItemHeader, MessageItemInlineStatus, MessageItemMeta, MessageItemReadReceipt, MessageItemSavedIndicator, MessageItemSearchMatch, MessageItemSender, MessageItemSenderBadges, MessageItemSenderButton, MessageItemSenderGroup, MessageItemTime, MessageItemToolbar, MessageItemToolbarButton, MessageItemTranslationAction, MessageItemTranslationFailedStatus, MessageItemTranslationOriginal, MessageItemTranslationOriginalLabel, MessageItemTranslationProgressTrack, MessageItemTranslationRetryAction, MessageItemTranslationStatus, MessageList, MessageListDateDivider, MessageListHistoryLimited, MessageListNotice, MessageMultiSelect, MessageMultiSelectCheckbox, MessageMultiSelectToolbar, MessageQuotedPreview, MessageQuotedPreviewAttachment, MessageQuotedPreviewAttachments, MessageQuotedPreviewAuthor, MessageQuotedPreviewAuthorMeta, MessageQuotedPreviewAuthorName, MessageQuotedPreviewChannel, MessageQuotedPreviewContent, MessageQuotedPreviewHeader, MessageQuotedPreviewLoading, MessageQuotedPreviewText, MessageQuotedPreviewThread, MessageQuotedPreviewTimestamp, MessageQuotedPreviewUnavailable, MessageReactionAddButton, MessageReactionChip, MessageReactionCount, MessageReactionErrorIndicator, MessageReactionGlyph, MessageReactionPickerContent, MessageReactionPickerStatic, MessageReactionQuickButton, MessageReactionQuickRow, MessageReactionTooltipContent, MessageReactionTooltipMeta, MessageReactionTooltipName, MessageReactionTooltipNameText, MessageReactionTooltipNames, MessageReactionTooltipSeparator, MessageReferenceChip, MessageReferenceIndicator, MessageReferenceLabel, MessageReferencePending, MessageReferenceText, MessageReferenceUnavailable, MessageReplies, MessageRepliesItem, MessageRepliesItemContent, MessageRepliesItemSender, MessageRepliesItemTime, MessageRepliesSummary, MessageRepliesSummaryItem, MessageTextSelectionToolbar, MessageTextSelectionToolbarButton, MessageTextSelectionToolbarSeparator, MessageVideoPreview, MessageVideoPreviewAction, MessageVideoPreviewFailureAction, MessageVideoPreviewFailureContent, MessageVideoPreviewFailureIcon, MessageVideoPreviewFailureTitle, MessageVideoPreviewFallback, MessageVideoPreviewMedia, MobileNavItem, MobileNavLabel, MobileNavRoot, NotificationCenter, NotificationCenterActionButton, NotificationCenterCount, NotificationCenterEmptyState, NotificationCenterHeader, NotificationCenterItem, NotificationCenterItemActions, NotificationCenterItemBody, NotificationCenterItemContent, NotificationCenterItemIcon, NotificationCenterItemRow, NotificationCenterItemTitle, NotificationCenterList, NotificationCenterPopup, NotificationCenterScroller, NotificationCenterTitle, NotificationCenterTrigger, OtterIndicator, Panel, PanelAction, PanelActions, PanelActivity, PanelBody, PanelFooter, PanelHeader, PanelHeaderContent, PanelHeaderIcon, PanelHeaderRoot, PanelHeading, PanelMeta, PanelNotice, PanelNoticeActions, PanelNoticeContent, PanelScrollContent, PanelScrollViewport, PanelSection, PanelSectionHeader, PanelStatus, PanelTitle, PanelToggleAction, PickerTriggerButton, Popover, PopoverArrow, PopoverBackdrop, PopoverClose, PopoverContent, PopoverDescription, PopoverHeader, PopoverPopup, PopoverPortal, PopoverSeparator, PopoverTitle, PopoverTrigger, PopoverViewport, PreviewCard, PreviewCardArrow, PreviewCardContent, PreviewCardDescription, PreviewCardHeader, PreviewCardPopup, PreviewCardPortal, PreviewCardSeparator, PreviewCardTitle, PreviewCardTrigger, ProfilePanel, ProfilePanelBody, ProfilePanelHeader, ProfilePanelHeaderContent, ProfilePanelSection, Progress, ProgressHeader, ProgressIndicator, ProgressLabel, ProgressTrack, ProgressValue, RadioGroup, RadioGroupIndicator, RadioGroupItem, RaftLogo, RaftLogoLockup, RaftWordmark, ResizableGroup, ResizableHandle, ResizablePanel, SINGLE_IMAGE_MAX_HEIGHT, SINGLE_IMAGE_MAX_WIDTH, ScrollArea, ScrollAreaContent, ScrollAreaCorner, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport, SearchEntityResult, SearchEntityResultActivity, SearchEntityResultContent, SearchEntityResultDescription, SearchEntityResultHeader, SearchEntityResultIcon, SearchEntityResultLeading, SearchEntityResultTitle, SearchMessageResult, SearchMessageResultBody, SearchMessageResultHeader, SearchMessageResultMatch, SearchMessageResultMeta, SearchMessageResultSender, SearchMessageResultThreadMeta, SearchMessageResultTimestamp, SearchResultsList, SearchResultsSection, SearchResultsSectionHeading, SearchResultsSummary, SearchShellFilters, SearchShellRoot, SearchShellViewport, SearchThreadMessageResult, SearchThreadResult, SearchThreadResultHeader, SearchThreadResultMessages, SearchThreadResultMeta, SearchThreadResultTimestamp, SearchThreadResultTitle, SearchTrigger, SearchTriggerLabel, SearchTriggerShortcut, SegmentedControl, SegmentedControlCount, SegmentedControlItem, SegmentedControlLabel, Select, SelectArrow, SelectContent, SelectGroup, SelectGroupLabel, SelectIcon, SelectItem, SelectItemDescription, SelectItemIndicator, SelectItemLeading, SelectItemText, SelectLabel, SelectList, SelectPopup, SelectPortal, SelectScrollDownArrow, SelectScrollUpArrow, SelectSeparator, SelectTrigger, SelectValue, Separator, SidebarBody, SidebarContent, SidebarControl, SidebarGroupLabel, SidebarHeader, SidebarHeaderTitle, SidebarItem, SidebarItemAside, SidebarItemChannelIcon, SidebarItemContent, SidebarItemCount, SidebarItemCountSeparator, SidebarItemIcon, SidebarItemIndicator, SidebarItemMeta, SidebarItemMetaIcon, SidebarItemStatus, SidebarItemSubtitle, SidebarItemThreadCount, SidebarItemTitle, SidebarList, SidebarLiveActivity, SidebarMenu, SidebarMenuAside, SidebarMenuCount, SidebarMenuIcon, SidebarMenuTitle, SidebarRoot, SidebarSection, SidebarSectionAttention, SidebarSectionChevron, SidebarSectionControl, SidebarSectionControls, SidebarSectionCount, SidebarSectionDisclosure, SidebarSectionEmpty, SidebarSectionHeader, SidebarSectionTitle, SidebarSectionTrailing, Skeleton, SortableComposerAttachment, SortableComposerAttachments, SortableComposerAttachmentsScroll, SortableTabsList, SortableTabsTab, Spinner, Status, Step, Steps, Switch, SwitchThumb, SystemMessage, SystemMessageContent, SystemMessageGroup, SystemMessageGroupChevron, SystemMessageGroupDisclosure, SystemMessageGroupHeader, SystemMessageGroupPanel, SystemMessageGroupSummary, SystemMessageTime, TASK_STATUS_IDS, Table, TableBody, TableCaption, TableCell, TableColgroup, TableColumn, TableHead, TableHeader, TableRow, Tabs, TabsLabel, TabsList, TabsPanel, TabsTab, TaskBoardColumn, TaskBoardColumnBadge, TaskBoardColumnChevron, TaskBoardColumnCount, TaskBoardColumnEmpty, TaskBoardColumnEmptyAllowed, TaskBoardColumnEmptyBlocked, TaskBoardColumnEmptyIdle, TaskBoardColumnHeading, TaskBoardColumnItems, TaskBoardColumnPanel, TaskBoardColumnTrigger, TaskBoardRoot, TaskCard, TaskCardBody, TaskCardChannel, TaskCardDescription, TaskCardLegacy, TaskCardMeta, TaskCardNumber, TaskCardRow, TaskCardStatus, TaskCardTitle, TaskChip, TaskClosedIcon, TaskDndProvider, TaskDoneIcon, TaskDragOverlay, TaskDraggable, TaskDropPlaceholder, TaskDropZone, TaskInProgressIcon, TaskInReviewIcon, TaskListRoot, TaskLoadingList, TaskLoadingRow, TaskSection, TaskSectionBadge, TaskSectionChevron, TaskSectionCount, TaskSectionEmpty, TaskSectionHeading, TaskSectionItems, TaskSectionPanel, TaskSectionTrigger, TaskStatusBadge, TaskStatusIcon, TaskTodoIcon, TasksPanelEmpty, TasksPanelRoot, TasksPanelToolbar, TasksPanelToolbarFilters, TasksPanelToolbarGroup, TasksPanelToolbarLabel, TasksPanelToolbarNewTask, TasksPanelToolbarView, TasksPanelViewport, Text, TextHeading, TextLink, TextMono, TextSans, Textarea, TextareaCounter, TextareaGroup, ThemeProvider, ThreadIcon, ThreadPanelActions, ThreadPanelBody, ThreadPanelContent, ThreadPanelFooter, ThreadPanelHeader, ThreadPanelOriginalMessage, ThreadPanelReplyCount, ThreadPanelReplySummary, ThreadPanelRoot, ThreadPanelSearch, ThreadPanelSearchCount, ThreadPanelSearchTrailing, ThreadPanelTitle, ThreadPanelTitleMeta, Timeline, TimelineContent, TimelineItem, TimelineTime, TimelineTitle, ToastAction, ToastActions, ToastArrow, ToastBody, ToastClose, ToastContent, ToastDescription, ToastIcon, ToastPortal, ToastPositioner, ToastProgress, ToastProvider, ToastRoot, ToastTitle, ToastViewport, ToggleGroup, ToggleGroupItem, ToggleGroupLabel, Tooltip, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, VirtualList, buildMessageImageGalleryRows, buttonVariants, classifyMessageImageAspect, cn, createMessageTextSelectionAnchor, createToastManager, getTextSelectionFocusRect, installInputModality, taskStatusSoft, taskStatusStrong, textLinkVariants, toast, useComposerSuggestions, useOrderedTabs, useTheme, useToastManager };
|