ps99-api 2.6.5 → 2.7.1
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.
|
@@ -13,6 +13,7 @@ import { FixedSizeGrid, FixedSizeList } from "./ReactWindowMock";
|
|
|
13
13
|
import AutoSizer from "./AutoSizer";
|
|
14
14
|
import { useScrollPersistence } from "../context/ScrollContext";
|
|
15
15
|
import { useCollapsibleHeader } from '../hooks/useCollapsibleHeader';
|
|
16
|
+
import { usePullToRefresh } from '../hooks/usePullToRefresh';
|
|
16
17
|
import { formatGigantix } from "../utils/gigantix";
|
|
17
18
|
|
|
18
19
|
// const FixedSizeGrid = Grid;
|
|
@@ -491,6 +492,20 @@ const CollectionConfigIndex: React.FC<CollectionConfigIndexProps> = () => {
|
|
|
491
492
|
disabled: isShortContent // Disable collapsing if content is short
|
|
492
493
|
});
|
|
493
494
|
|
|
495
|
+
// Pull To Refresh Logic
|
|
496
|
+
const { isRefreshing, pullDistance, onTouchStart, onTouchMove, onTouchEnd, updateScrollTop, isDragging } = usePullToRefresh({
|
|
497
|
+
onRefresh: async () => {
|
|
498
|
+
// Simple reload to fetch new data
|
|
499
|
+
window.location.reload();
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// Update scroll top for PTR
|
|
504
|
+
const onScroll = (scrollInfo: { scrollOffset?: number, scrollTop?: number, scrollHeight?: number, clientHeight?: number }) => {
|
|
505
|
+
handleScroll(scrollInfo);
|
|
506
|
+
updateScrollTop(scrollInfo.scrollTop ?? scrollInfo.scrollOffset ?? 0);
|
|
507
|
+
};
|
|
508
|
+
|
|
494
509
|
// Loading State
|
|
495
510
|
if (loading) {
|
|
496
511
|
return (
|
|
@@ -656,6 +671,32 @@ const CollectionConfigIndex: React.FC<CollectionConfigIndexProps> = () => {
|
|
|
656
671
|
position: 'relative' // Context for absolute header
|
|
657
672
|
}}
|
|
658
673
|
>
|
|
674
|
+
{/* Pull To Refresh Indicator */}
|
|
675
|
+
{(pullDistance > 0 || isRefreshing) && (
|
|
676
|
+
<div style={{
|
|
677
|
+
position: 'absolute',
|
|
678
|
+
top: showHeader ? headerHeight + 5 : 0, // Adjust based on header
|
|
679
|
+
left: 0,
|
|
680
|
+
right: 0,
|
|
681
|
+
height: isRefreshing ? 60 : pullDistance,
|
|
682
|
+
display: 'flex',
|
|
683
|
+
alignItems: 'center',
|
|
684
|
+
justifyContent: 'center',
|
|
685
|
+
overflow: 'hidden',
|
|
686
|
+
overflow: 'hidden',
|
|
687
|
+
backgroundColor: '#f5f5f5',
|
|
688
|
+
zIndex: 5,
|
|
689
|
+
transition: isDragging ? 'none' : 'height 0.3s ease'
|
|
690
|
+
}}>
|
|
691
|
+
{isRefreshing ? (
|
|
692
|
+
<div className="spinner" style={{ width: 24, height: 24, border: '3px solid #ccc', borderTopColor: '#333', borderRadius: '50%' }}></div>
|
|
693
|
+
) : (
|
|
694
|
+
<span style={{ opacity: Math.min(pullDistance / 60, 1), transform: `rotate(${pullDistance * 2}deg)` }}>
|
|
695
|
+
⬇️
|
|
696
|
+
</span>
|
|
697
|
+
)}
|
|
698
|
+
</div>
|
|
699
|
+
)}
|
|
659
700
|
{/* Header Bar inside Window */}
|
|
660
701
|
<div
|
|
661
702
|
ref={headerRef}
|
|
@@ -828,8 +869,11 @@ const CollectionConfigIndex: React.FC<CollectionConfigIndexProps> = () => {
|
|
|
828
869
|
itemSize={80} // List view row height
|
|
829
870
|
width={width}
|
|
830
871
|
initialScrollOffset={initialScrollOffset}
|
|
831
|
-
onScroll={
|
|
872
|
+
onScroll={onScroll} // Use wrapped handler
|
|
832
873
|
bottomPadding={120} // Account for Android footer/nav
|
|
874
|
+
onTouchStart={onTouchStart}
|
|
875
|
+
onTouchMove={onTouchMove}
|
|
876
|
+
onTouchEnd={onTouchEnd}
|
|
833
877
|
itemData={{
|
|
834
878
|
items: finalItems,
|
|
835
879
|
navigate,
|
|
@@ -861,9 +905,12 @@ const CollectionConfigIndex: React.FC<CollectionConfigIndexProps> = () => {
|
|
|
861
905
|
rowHeight={220}
|
|
862
906
|
width={width}
|
|
863
907
|
initialScrollOffset={initialScrollOffset}
|
|
864
|
-
onScroll={
|
|
908
|
+
onScroll={onScroll} // Use wrapped handler
|
|
865
909
|
style={{ overflowX: "hidden" }}
|
|
866
910
|
bottomPadding={120} // Account for Android footer/nav
|
|
911
|
+
onTouchStart={onTouchStart}
|
|
912
|
+
onTouchMove={onTouchMove}
|
|
913
|
+
onTouchEnd={onTouchEnd}
|
|
867
914
|
itemData={{
|
|
868
915
|
items: finalItems,
|
|
869
916
|
columnCount: colCount,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useRef, useEffect } from 'react';
|
|
2
2
|
|
|
3
|
-
export const FixedSizeList = ({ children, itemCount, itemSize, height, width, onScroll, initialScrollOffset, itemData, bottomPadding = 0 }: any) => {
|
|
3
|
+
export const FixedSizeList = ({ children, itemCount, itemSize, height, width, onScroll, initialScrollOffset, itemData, bottomPadding = 0, onTouchStart, onTouchMove, onTouchEnd }: any) => {
|
|
4
4
|
const items = [];
|
|
5
5
|
for (let i = 0; i < itemCount; i++) {
|
|
6
6
|
items.push(
|
|
@@ -27,6 +27,9 @@ export const FixedSizeList = ({ children, itemCount, itemSize, height, width, on
|
|
|
27
27
|
scrollHeight: e.currentTarget.scrollHeight,
|
|
28
28
|
clientHeight: e.currentTarget.clientHeight
|
|
29
29
|
})}
|
|
30
|
+
onTouchStart={onTouchStart}
|
|
31
|
+
onTouchMove={onTouchMove}
|
|
32
|
+
onTouchEnd={onTouchEnd}
|
|
30
33
|
>
|
|
31
34
|
{items}
|
|
32
35
|
{bottomPadding > 0 && <div style={{ height: bottomPadding }} />}
|
|
@@ -34,7 +37,7 @@ export const FixedSizeList = ({ children, itemCount, itemSize, height, width, on
|
|
|
34
37
|
);
|
|
35
38
|
};
|
|
36
39
|
|
|
37
|
-
export const FixedSizeGrid = ({ children, columnCount, rowCount, columnWidth, rowHeight, height, width, onScroll, initialScrollOffset, itemData, style, bottomPadding = 0 }: any) => {
|
|
40
|
+
export const FixedSizeGrid = ({ children, columnCount, rowCount, columnWidth, rowHeight, height, width, onScroll, initialScrollOffset, itemData, style, bottomPadding = 0, onTouchStart, onTouchMove, onTouchEnd }: any) => {
|
|
38
41
|
const items = [];
|
|
39
42
|
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
40
43
|
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
|
|
@@ -73,6 +76,9 @@ export const FixedSizeGrid = ({ children, columnCount, rowCount, columnWidth, ro
|
|
|
73
76
|
scrollHeight: e.currentTarget.scrollHeight,
|
|
74
77
|
clientHeight: e.currentTarget.clientHeight
|
|
75
78
|
})}
|
|
79
|
+
onTouchStart={onTouchStart}
|
|
80
|
+
onTouchMove={onTouchMove}
|
|
81
|
+
onTouchEnd={onTouchEnd}
|
|
76
82
|
>
|
|
77
83
|
<div style={{ height: rowCount * rowHeight + bottomPadding, width: columnWidth * columnCount }}>
|
|
78
84
|
{items}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
interface PullToRefreshOptions {
|
|
4
|
+
onRefresh: () => Promise<void> | void;
|
|
5
|
+
threshold?: number; // px to pull down to trigger
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const usePullToRefresh = ({ onRefresh, threshold = 80 }: PullToRefreshOptions) => {
|
|
9
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
10
|
+
const [pullDistance, setPullDistance] = useState(0);
|
|
11
|
+
const startY = useRef<number>(0);
|
|
12
|
+
const isDragging = useRef(false);
|
|
13
|
+
|
|
14
|
+
// We need to know the scroll position to only allow pull when at top
|
|
15
|
+
const scrollTopRef = useRef(0);
|
|
16
|
+
|
|
17
|
+
const onTouchStart = (e: React.TouchEvent) => {
|
|
18
|
+
if (scrollTopRef.current === 0) {
|
|
19
|
+
startY.current = e.touches[0].clientY;
|
|
20
|
+
isDragging.current = true;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const onTouchMove = (e: React.TouchEvent) => {
|
|
25
|
+
if (!isDragging.current) return;
|
|
26
|
+
if (scrollTopRef.current > 0) {
|
|
27
|
+
isDragging.current = false;
|
|
28
|
+
setPullDistance(0);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const currentY = e.touches[0].clientY;
|
|
33
|
+
const diff = currentY - startY.current;
|
|
34
|
+
|
|
35
|
+
if (diff > 0) {
|
|
36
|
+
// Resistance effect
|
|
37
|
+
setPullDistance(Math.min(diff * 0.5, threshold * 1.5));
|
|
38
|
+
// Prevent native scroll if we are pulling down?
|
|
39
|
+
// Might interfere with normal scrolling if not careful.
|
|
40
|
+
// But since scrollTop is 0, pulling down usually does nothing in overflow:auto unless overscroll-behavior is set.
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const onTouchEnd = async () => {
|
|
45
|
+
if (!isDragging.current) return;
|
|
46
|
+
isDragging.current = false;
|
|
47
|
+
|
|
48
|
+
if (pullDistance > threshold) {
|
|
49
|
+
setIsRefreshing(true);
|
|
50
|
+
setPullDistance(threshold); // Snap to threshold
|
|
51
|
+
try {
|
|
52
|
+
await onRefresh();
|
|
53
|
+
} finally {
|
|
54
|
+
setIsRefreshing(false);
|
|
55
|
+
setPullDistance(0);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
setPullDistance(0);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const updateScrollTop = (val: number) => {
|
|
63
|
+
scrollTopRef.current = val;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Expose boolean for UI
|
|
67
|
+
const [isDraggingState, setIsDraggingState] = useState(false);
|
|
68
|
+
|
|
69
|
+
// Sync ref to state for UI (optional, or just use state)
|
|
70
|
+
// Actually, let's just use state for the critical UI part if needed,
|
|
71
|
+
// but typically we want ref for perf on touchmove.
|
|
72
|
+
// Let's just return a getter or similar?
|
|
73
|
+
// Simplest: just don't use isDragging for the transition logic in the UI, or use pullDistance === 0 check.
|
|
74
|
+
|
|
75
|
+
// Correction: In proper PTR, you want no transition during drag, but transition on release.
|
|
76
|
+
// So we need to know if we are dragging.
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
isRefreshing,
|
|
80
|
+
pullDistance,
|
|
81
|
+
onTouchStart: (e: React.TouchEvent) => { onTouchStart(e); setIsDraggingState(true); },
|
|
82
|
+
onTouchMove,
|
|
83
|
+
onTouchEnd: async () => { setIsDraggingState(false); await onTouchEnd(); },
|
|
84
|
+
updateScrollTop,
|
|
85
|
+
isDragging: isDraggingState
|
|
86
|
+
};
|
|
87
|
+
};
|