fui-material 2.8.22 → 2.8.23

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.
@@ -4925,6 +4925,21 @@ const FStatusBadge = ({
4925
4925
  /* @__PURE__ */ jsx("span", { children })
4926
4926
  ] });
4927
4927
  };
4928
+ function findOffsetIndex(offsets, targetOffset) {
4929
+ let low = 0;
4930
+ let high = offsets.length - 2;
4931
+ let result = 0;
4932
+ while (low <= high) {
4933
+ const mid = low + high >> 1;
4934
+ if (offsets[mid] <= targetOffset) {
4935
+ result = mid;
4936
+ low = mid + 1;
4937
+ } else {
4938
+ high = mid - 1;
4939
+ }
4940
+ }
4941
+ return result;
4942
+ }
4928
4943
  function FProgressiveList({
4929
4944
  items,
4930
4945
  chunkSize = 6,
@@ -4939,14 +4954,18 @@ function FProgressiveList({
4939
4954
  hasMore = false,
4940
4955
  isLoadingMore = false,
4941
4956
  loader: loader2,
4942
- threshold = 250
4957
+ threshold = 250,
4958
+ virtualize = false,
4959
+ overscan = 2
4943
4960
  }) {
4944
4961
  const totalCount = items.length;
4962
+ const containerRef = useRef(null);
4945
4963
  const [renderedCount, setRenderedCount] = useState(() => Math.min(chunkSize, totalCount));
4946
4964
  const sentinelRef = useRef(null);
4947
4965
  const prevFirstKeyRef = useRef(void 0);
4948
4966
  const prevTotalCountRef = useRef(totalCount);
4949
4967
  useEffect(() => {
4968
+ if (virtualize) return;
4950
4969
  const currentFirstKey = items.length > 0 ? keyExtractor(items[0], 0) : void 0;
4951
4970
  const isReset = currentFirstKey !== prevFirstKeyRef.current || totalCount < prevTotalCountRef.current;
4952
4971
  if (isReset) {
@@ -4954,17 +4973,119 @@ function FProgressiveList({
4954
4973
  }
4955
4974
  prevFirstKeyRef.current = currentFirstKey;
4956
4975
  prevTotalCountRef.current = totalCount;
4957
- }, [items, keyExtractor, chunkSize, totalCount]);
4976
+ }, [items, keyExtractor, chunkSize, totalCount, virtualize]);
4958
4977
  useEffect(() => {
4978
+ if (virtualize) return;
4959
4979
  if (renderedCount >= totalCount) return;
4960
4980
  const frameId = requestAnimationFrame(() => {
4961
4981
  setRenderedCount((prev) => Math.min(prev + chunkSize, totalCount));
4962
4982
  });
4963
4983
  return () => cancelAnimationFrame(frameId);
4964
- }, [renderedCount, totalCount, chunkSize]);
4984
+ }, [renderedCount, totalCount, chunkSize, virtualize]);
4985
+ const measuredHeights = useRef(/* @__PURE__ */ new Map());
4986
+ const [measureVersion, setMeasureVersion] = useState(0);
4987
+ const resizeObserverRef = useRef(null);
4988
+ useEffect(() => {
4989
+ if (!virtualize) return;
4990
+ const currentFirstKey = items.length > 0 ? keyExtractor(items[0], 0) : void 0;
4991
+ if (currentFirstKey !== prevFirstKeyRef.current || totalCount < prevTotalCountRef.current) {
4992
+ measuredHeights.current.clear();
4993
+ setMeasureVersion((v) => v + 1);
4994
+ }
4995
+ prevFirstKeyRef.current = currentFirstKey;
4996
+ prevTotalCountRef.current = totalCount;
4997
+ }, [items, keyExtractor, totalCount, virtualize]);
4998
+ useEffect(() => {
4999
+ if (!virtualize) return;
5000
+ const ro = new ResizeObserver((entries) => {
5001
+ var _a, _b;
5002
+ let changed = false;
5003
+ for (const entry of entries) {
5004
+ const indexAttr = entry.target.getAttribute("data-virtual-index");
5005
+ if (indexAttr !== null) {
5006
+ const index = Number(indexAttr);
5007
+ const height = ((_b = (_a = entry.borderBoxSize) == null ? void 0 : _a[0]) == null ? void 0 : _b.blockSize) ?? entry.contentRect.height;
5008
+ const prevHeight = measuredHeights.current.get(index);
5009
+ if (height > 0 && (!prevHeight || Math.abs(prevHeight - height) > 2)) {
5010
+ measuredHeights.current.set(index, height);
5011
+ changed = true;
5012
+ }
5013
+ }
5014
+ }
5015
+ if (changed) {
5016
+ setMeasureVersion((v) => v + 1);
5017
+ }
5018
+ });
5019
+ resizeObserverRef.current = ro;
5020
+ return () => ro.disconnect();
5021
+ }, [virtualize]);
5022
+ const offsets = useMemo(() => {
5023
+ if (!virtualize) return new Float64Array(0);
5024
+ const arr = new Float64Array(totalCount + 1);
5025
+ let sum = 0;
5026
+ for (let i = 0; i < totalCount; i++) {
5027
+ arr[i] = sum;
5028
+ const h = measuredHeights.current.get(i) ?? estimatedItemHeight;
5029
+ sum += h;
5030
+ }
5031
+ arr[totalCount] = sum;
5032
+ return arr;
5033
+ }, [totalCount, estimatedItemHeight, virtualize, measureVersion]);
5034
+ const [scrollState, setScrollState] = useState(() => ({
5035
+ scrollTop: 0,
5036
+ viewportHeight: typeof window !== "undefined" ? window.innerHeight : 800
5037
+ }));
5038
+ useEffect(() => {
5039
+ if (!virtualize) return;
5040
+ let rafId = null;
5041
+ const handleScroll = () => {
5042
+ if (rafId !== null) return;
5043
+ rafId = requestAnimationFrame(() => {
5044
+ rafId = null;
5045
+ if (!containerRef.current) return;
5046
+ const rect = containerRef.current.getBoundingClientRect();
5047
+ const scrollTop = Math.max(0, -rect.top);
5048
+ const viewportHeight = window.innerHeight;
5049
+ setScrollState((prev) => {
5050
+ if (Math.abs(prev.scrollTop - scrollTop) > 30 || prev.viewportHeight !== viewportHeight) {
5051
+ return { scrollTop, viewportHeight };
5052
+ }
5053
+ return prev;
5054
+ });
5055
+ });
5056
+ };
5057
+ window.addEventListener("scroll", handleScroll, { passive: true });
5058
+ window.addEventListener("resize", handleScroll, { passive: true });
5059
+ handleScroll();
5060
+ return () => {
5061
+ if (rafId !== null) cancelAnimationFrame(rafId);
5062
+ window.removeEventListener("scroll", handleScroll);
5063
+ window.removeEventListener("resize", handleScroll);
5064
+ };
5065
+ }, [virtualize]);
5066
+ const { startIndex, endIndex, topSpacerHeight, bottomSpacerHeight } = useMemo(() => {
5067
+ if (!virtualize || totalCount === 0 || offsets.length <= 1) {
5068
+ return { startIndex: 0, endIndex: 0, topSpacerHeight: 0, bottomSpacerHeight: 0 };
5069
+ }
5070
+ const visibleTop = scrollState.scrollTop;
5071
+ const visibleBottom = scrollState.scrollTop + scrollState.viewportHeight;
5072
+ const rawStartIndex = findOffsetIndex(offsets, visibleTop);
5073
+ const rawEndIndex = findOffsetIndex(offsets, visibleBottom);
5074
+ const sIdx = Math.max(0, rawStartIndex - overscan);
5075
+ const eIdx = Math.min(totalCount - 1, rawEndIndex + overscan);
5076
+ const topHeight = offsets[sIdx] || 0;
5077
+ const totalHeight = offsets[totalCount] || 0;
5078
+ const bottomHeight = Math.max(0, totalHeight - (offsets[eIdx + 1] || totalHeight));
5079
+ return {
5080
+ startIndex: sIdx,
5081
+ endIndex: eIdx,
5082
+ topSpacerHeight: topHeight,
5083
+ bottomSpacerHeight: bottomHeight
5084
+ };
5085
+ }, [virtualize, totalCount, offsets, scrollState, overscan]);
4965
5086
  useEffect(() => {
4966
5087
  if (!onLoadMore || !hasMore || isLoadingMore) return;
4967
- if (renderedCount < totalCount) return;
5088
+ if (!virtualize && renderedCount < totalCount) return;
4968
5089
  const sentinelEl = sentinelRef.current;
4969
5090
  if (!sentinelEl) return;
4970
5091
  const observer = new IntersectionObserver(
@@ -4980,26 +5101,56 @@ function FProgressiveList({
4980
5101
  }
4981
5102
  );
4982
5103
  observer.observe(sentinelEl);
4983
- return () => {
4984
- observer.disconnect();
4985
- };
4986
- }, [onLoadMore, hasMore, isLoadingMore, renderedCount, totalCount, threshold]);
4987
- const visibleItems = items.slice(0, renderedCount);
5104
+ return () => observer.disconnect();
5105
+ }, [onLoadMore, hasMore, isLoadingMore, renderedCount, totalCount, threshold, virtualize]);
5106
+ useEffect(() => {
5107
+ if (!virtualize || !onLoadMore || !hasMore || isLoadingMore) return;
5108
+ if (totalCount > 0 && endIndex >= totalCount - 1 - overscan) {
5109
+ onLoadMore();
5110
+ }
5111
+ }, [virtualize, endIndex, totalCount, overscan, onLoadMore, hasMore, isLoadingMore]);
5112
+ const setItemRef = useCallback((el) => {
5113
+ if (el && resizeObserverRef.current) {
5114
+ resizeObserverRef.current.observe(el);
5115
+ }
5116
+ }, []);
4988
5117
  const itemWrapperStyle = {
4989
- contentVisibility: "auto",
4990
- containIntrinsicSize: `0 ${estimatedItemHeight}px`,
5118
+ contentVisibility: virtualize ? void 0 : "auto",
5119
+ containIntrinsicSize: virtualize ? void 0 : `0 ${estimatedItemHeight}px`,
4991
5120
  marginBottom: 16
4992
5121
  };
4993
- return /* @__PURE__ */ jsxs("div", { className, style: st, children: [
4994
- visibleItems.map((item, index) => /* @__PURE__ */ jsx(
4995
- "div",
4996
- {
4997
- style: itemWrapperStyle,
4998
- children: children(item, index)
4999
- },
5000
- keyExtractor(item, index)
5001
- )),
5002
- showProgress && renderProgress && renderedCount < totalCount && /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "8px 0" }, children: renderProgress(renderedCount, totalCount) }),
5122
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className, style: st, children: [
5123
+ virtualize ? (
5124
+ // --- РЕНДЕР С ВИРТУАЛЬНЫМ ОКНОМ ---
5125
+ /* @__PURE__ */ jsxs(Fragment, { children: [
5126
+ topSpacerHeight > 0 && /* @__PURE__ */ jsx("div", { style: { height: topSpacerHeight, width: "100%" }, "aria-hidden": "true" }),
5127
+ items.slice(startIndex, endIndex + 1).map((item, localIndex) => {
5128
+ const globalIndex = startIndex + localIndex;
5129
+ return /* @__PURE__ */ jsx(
5130
+ "div",
5131
+ {
5132
+ ref: setItemRef,
5133
+ "data-virtual-index": globalIndex,
5134
+ style: itemWrapperStyle,
5135
+ children: children(item, globalIndex)
5136
+ },
5137
+ keyExtractor(item, globalIndex)
5138
+ );
5139
+ }),
5140
+ bottomSpacerHeight > 0 && /* @__PURE__ */ jsx("div", { style: { height: bottomSpacerHeight, width: "100%" }, "aria-hidden": "true" })
5141
+ ] })
5142
+ ) : (
5143
+ // --- СТАНДАРТНЫЙ ПРОГРЕССИВНЫЙ РЕНДЕР ---
5144
+ items.slice(0, renderedCount).map((item, index) => /* @__PURE__ */ jsx(
5145
+ "div",
5146
+ {
5147
+ style: itemWrapperStyle,
5148
+ children: children(item, index)
5149
+ },
5150
+ keyExtractor(item, index)
5151
+ ))
5152
+ ),
5153
+ !virtualize && showProgress && renderProgress && renderedCount < totalCount && /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: "8px 0" }, children: renderProgress(renderedCount, totalCount) }),
5003
5154
  onLoadMore && hasMore && /* @__PURE__ */ jsx(
5004
5155
  "div",
5005
5156
  {
@@ -10104,9 +10255,11 @@ function useFManualPagination(options) {
10104
10255
  resetPage = true,
10105
10256
  dopUrl
10106
10257
  }) => {
10107
- const targetPage = resetPage ? 1 : page;
10258
+ const payloadObj = payload && typeof payload === "object" ? payload : void 0;
10259
+ const targetPage = payloadObj && typeof payloadObj.page === "number" ? payloadObj.page : resetPage ? 1 : page;
10260
+ const targetSize = payloadObj && typeof payloadObj.size === "number" ? payloadObj.size : pageSize || defaultPageSize || 20;
10108
10261
  if (resetPage) {
10109
- setPage(1);
10262
+ setPage(targetPage);
10110
10263
  if (isInfinite) {
10111
10264
  setAccumulatedData(void 0);
10112
10265
  }
@@ -10114,7 +10267,8 @@ function useFManualPagination(options) {
10114
10267
  setIsSubmitting(true);
10115
10268
  try {
10116
10269
  const rawResponse = await query.run({
10117
- newParams: { ...payload, page: targetPage, size: pageSize },
10270
+ newParams: { ...payload, page: targetPage, size: targetSize },
10271
+ newUrlParams: { page: targetPage, size: targetSize },
10118
10272
  dopUrl
10119
10273
  });
10120
10274
  const extracted = extractResult(rawResponse);
@@ -10125,13 +10279,14 @@ function useFManualPagination(options) {
10125
10279
  } finally {
10126
10280
  setIsSubmitting(false);
10127
10281
  }
10128
- }, [query, page, pageSize, isInfinite, extractResult]);
10282
+ }, [query, page, pageSize, defaultPageSize, isInfinite, extractResult]);
10129
10283
  const changePage = useCallback(async (newPage) => {
10130
10284
  if (isInfinite) {
10131
10285
  setIsFetchingMore(true);
10132
10286
  try {
10133
10287
  const rawResponse = await query.run({
10134
10288
  newParams: { page: newPage, size: pageSize },
10289
+ newUrlParams: { page: newPage, size: pageSize },
10135
10290
  mergeParams: true
10136
10291
  });
10137
10292
  const extracted = extractResult(rawResponse);
@@ -10145,6 +10300,7 @@ function useFManualPagination(options) {
10145
10300
  setPage(newPage);
10146
10301
  const rawResponse = await query.run({
10147
10302
  newParams: { page: newPage, size: pageSize },
10303
+ newUrlParams: { page: newPage, size: pageSize },
10148
10304
  mergeParams: true
10149
10305
  });
10150
10306
  return extractResult(rawResponse);