@pallasoft/ps-mobile-components 1.5.0 → 1.6.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/dist/components/ps-infinite-list/index.d.ts +2 -0
- package/dist/components/ps-infinite-list/index.js +2 -0
- package/dist/components/ps-infinite-list/ps-infinite-list-footer.d.ts +2 -0
- package/dist/components/ps-infinite-list/ps-infinite-list-footer.js +5 -0
- package/dist/components/ps-infinite-list/ps-infinite-list-item.d.ts +2 -0
- package/dist/components/ps-infinite-list/ps-infinite-list-item.js +8 -0
- package/dist/components/ps-infinite-list/ps-infinite-list-props.d.ts +34 -0
- package/dist/components/ps-infinite-list/ps-infinite-list-props.js +1 -0
- package/dist/components/ps-infinite-list/ps-infinite-list.d.ts +2 -0
- package/dist/components/ps-infinite-list/ps-infinite-list.js +39 -0
- package/dist/constants/ps-infinite-config.d.ts +6 -0
- package/dist/constants/ps-infinite-config.js +6 -0
- package/dist/hooks/use-ps-infinite-data.d.ts +3 -0
- package/dist/hooks/use-ps-infinite-data.js +192 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/interfaces/ps-infinite-data-options.d.ts +10 -0
- package/dist/interfaces/ps-infinite-data-options.js +1 -0
- package/dist/interfaces/ps-infinite-data-result.d.ts +12 -0
- package/dist/interfaces/ps-infinite-data-result.js +1 -0
- package/dist/interfaces/ps-infinite-data-state.d.ts +8 -0
- package/dist/interfaces/ps-infinite-data-state.js +1 -0
- package/dist/interfaces/ps-infinite-data-update.d.ts +1 -0
- package/dist/interfaces/ps-infinite-data-update.js +1 -0
- package/dist/interfaces/ps-infinite-data-write.d.ts +6 -0
- package/dist/interfaces/ps-infinite-data-write.js +1 -0
- package/dist/interfaces/ps-infinite-page.d.ts +4 -0
- package/dist/interfaces/ps-infinite-page.js +1 -0
- package/dist/services/ps-infinite-data-service.d.ts +8 -0
- package/dist/services/ps-infinite-data-service.js +22 -0
- package/dist/services/ps-infinite-list-service.d.ts +10 -0
- package/dist/services/ps-infinite-list-service.js +36 -0
- package/package.json +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { PsLayoutConfig } from '../../constants/ps-layout-config';
|
|
4
|
+
import { PsSpinner } from '../ps-spinner/ps-spinner';
|
|
5
|
+
export const PsInfiniteListFooter = ({ isLoadingMore }) => isLoadingMore ? (_jsx(View, { style: { alignItems: 'center', paddingVertical: PsLayoutConfig.gutter }, testID: 'ps-infinite-list-loading-more', children: _jsx(PsSpinner, {}) })) : null;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
|
|
4
|
+
import { PsInfiniteListService } from '../../services/ps-infinite-list-service';
|
|
5
|
+
export const PsInfiniteListItem = ({ children, count, index }) => {
|
|
6
|
+
const theme = usePsMobileTheme();
|
|
7
|
+
return (_jsxs(View, { style: PsInfiniteListService.itemFrame(theme, index, count), children: [index === 0 ? null : _jsx(View, { style: PsInfiniteListService.divider(theme) }), children] }));
|
|
8
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import type { PsListVariant } from '../../services/ps-list-service';
|
|
4
|
+
import type { PsListSkeletonLayout } from '../ps-list/ps-list-props';
|
|
5
|
+
export interface PsInfiniteSection<T> {
|
|
6
|
+
data: T[];
|
|
7
|
+
footer?: string;
|
|
8
|
+
key: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PsInfiniteListProps<T> {
|
|
12
|
+
empty?: ReactNode;
|
|
13
|
+
hasMore: boolean;
|
|
14
|
+
header?: ReactNode;
|
|
15
|
+
isLoading?: boolean;
|
|
16
|
+
isLoadingMore?: boolean;
|
|
17
|
+
items?: T[];
|
|
18
|
+
keyExtractor: (item: T) => string;
|
|
19
|
+
onEndReached: () => void;
|
|
20
|
+
onRefresh?: () => Promise<void>;
|
|
21
|
+
renderItem: (item: T) => ReactElement;
|
|
22
|
+
sections?: PsInfiniteSection<T>[];
|
|
23
|
+
skeleton?: PsListSkeletonLayout;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
variant?: PsListVariant;
|
|
26
|
+
}
|
|
27
|
+
export interface PsInfiniteListFooterProps {
|
|
28
|
+
isLoadingMore: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface PsInfiniteListItemProps {
|
|
31
|
+
children: ReactElement;
|
|
32
|
+
count: number;
|
|
33
|
+
index: number;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { PsInfiniteListProps } from './ps-infinite-list-props';
|
|
2
|
+
export declare const PsInfiniteList: <T>({ empty, hasMore, header, isLoading, isLoadingMore, items, keyExtractor, onEndReached, onRefresh, renderItem, sections, skeleton, style, variant, }: PsInfiniteListProps<T>) => import("react").JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Platform, RefreshControl, SectionList, View } from 'react-native';
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
4
|
+
import { PsInfiniteConfig } from '../../constants/ps-infinite-config';
|
|
5
|
+
import { PsLayoutConfig } from '../../constants/ps-layout-config';
|
|
6
|
+
import { PsSelectionBarConfig } from '../../constants/ps-selection-bar-config';
|
|
7
|
+
import { PsScrollContext } from '../../contexts/ps-scroll-context';
|
|
8
|
+
import { usePsMobileTheme } from '../../hooks/use-ps-mobile-theme';
|
|
9
|
+
import { usePsRefresh } from '../../hooks/use-ps-refresh';
|
|
10
|
+
import { usePsScrollHost } from '../../hooks/use-ps-scroll-host';
|
|
11
|
+
import { usePsTabBar } from '../../hooks/use-ps-tab-bar';
|
|
12
|
+
import { usePsTopInset } from '../../hooks/use-ps-top-inset';
|
|
13
|
+
import { PsInfiniteListService } from '../../services/ps-infinite-list-service';
|
|
14
|
+
import { PsListService } from '../../services/ps-list-service';
|
|
15
|
+
import { PsScreenService } from '../../services/ps-screen-service';
|
|
16
|
+
import { PsSkeletonList } from '../ps-skeleton/ps-skeleton-list';
|
|
17
|
+
import { PsText } from '../ps-text/ps-text';
|
|
18
|
+
import { PsInfiniteListFooter } from './ps-infinite-list-footer';
|
|
19
|
+
import { PsInfiniteListItem } from './ps-infinite-list-item';
|
|
20
|
+
export const PsInfiniteList = ({ empty, hasMore, header, isLoading = false, isLoadingMore = false, items, keyExtractor, onEndReached, onRefresh, renderItem, sections, skeleton, style, variant = 'grouped', }) => {
|
|
21
|
+
const theme = usePsMobileTheme();
|
|
22
|
+
const insets = useSafeAreaInsets();
|
|
23
|
+
const topInset = usePsTopInset();
|
|
24
|
+
const { hasTabBar, isHidden: isTabBarHidden } = usePsTabBar();
|
|
25
|
+
const { isRefreshing, refresh } = usePsRefresh(onRefresh);
|
|
26
|
+
const host = usePsScrollHost();
|
|
27
|
+
const shown = isLoading ? [] : PsInfiniteListService.sections(items, sections);
|
|
28
|
+
return (_jsxs(View, { style: { flex: 1 }, children: [_jsx(PsScrollContext, { value: host.context, children: _jsx(SectionList, { ListEmptyComponent: isLoading ? _jsx(PsSkeletonList, { layout: skeleton }) : _jsx(_Fragment, { children: empty }), ListFooterComponent: _jsx(PsInfiniteListFooter, { isLoadingMore: isLoadingMore }), ListHeaderComponent: header === undefined ? undefined : (_jsx(View, { style: { marginBottom: PsLayoutConfig.listGap }, children: header })), contentContainerStyle: PsScreenService.content({
|
|
29
|
+
bottom: PsScreenService.bottomInset(insets.bottom, hasTabBar, isTabBarHidden),
|
|
30
|
+
top: topInset,
|
|
31
|
+
}, Platform.OS === 'ios', PsListService.hasGutter(variant), 0, isTabBarHidden ? PsSelectionBarConfig.height : 0), contentInsetAdjustmentBehavior: 'automatic', initialNumToRender: PsInfiniteConfig.initialNumToRender, keyExtractor: keyExtractor, keyboardDismissMode: 'interactive', keyboardShouldPersistTaps: 'handled', maxToRenderPerBatch: PsInfiniteConfig.maxToRenderPerBatch, onContentSizeChange: host.onContentSizeChange, onEndReached: () => {
|
|
32
|
+
if (PsInfiniteListService.shouldLoadMore(hasMore, isLoading, isLoadingMore))
|
|
33
|
+
onEndReached();
|
|
34
|
+
}, onEndReachedThreshold: PsInfiniteConfig.endReachedThreshold, onLayout: host.onLayout, onScroll: host.onScroll, refreshControl: onRefresh === undefined ? undefined : (_jsx(RefreshControl, { colors: [theme.primaryText], onRefresh: () => void refresh(), progressBackgroundColor: theme.surface, refreshing: isRefreshing, tintColor: theme.primary })), renderItem: ({ index, item, section }) => (_jsx(PsInfiniteListItem, { count: section.data.length, index: index, children: renderItem(item) })), renderSectionFooter: ({ section }) => (_jsx(View, { style: {
|
|
35
|
+
gap: PsLayoutConfig.listSectionGap,
|
|
36
|
+
paddingBottom: section === shown[shown.length - 1] ? 0 : PsLayoutConfig.listGap,
|
|
37
|
+
paddingTop: section.footer === undefined ? 0 : PsLayoutConfig.listSectionGap,
|
|
38
|
+
}, children: section.footer === undefined ? null : (_jsx(PsText, { tone: 'muted', variant: 'caption', children: section.footer })) })), renderSectionHeader: ({ section }) => section.title === undefined ? null : (_jsx(View, { style: { paddingBottom: PsLayoutConfig.listSectionGap }, children: _jsx(PsText, { accessibilityRole: 'header', tone: 'muted', variant: 'label', children: section.title }) })), scrollEnabled: !host.isLocked, scrollEventThrottle: PsLayoutConfig.scrollEventThrottle, sections: shown, showsVerticalScrollIndicator: false, stickySectionHeadersEnabled: false, style: [{ backgroundColor: theme.background, flex: 1 }, style], testID: 'ps-infinite-list', windowSize: PsInfiniteConfig.windowSize }) }), header === undefined ? null : (_jsx(View, { pointerEvents: 'none', style: [PsScreenService.statusBarScrim(topInset), { backgroundColor: theme.background }], testID: 'ps-infinite-list-status-bar-scrim' }))] }));
|
|
39
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PsInfiniteDataOptions } from '../interfaces/ps-infinite-data-options';
|
|
2
|
+
import type { PsInfiniteDataResult } from '../interfaces/ps-infinite-data-result';
|
|
3
|
+
export declare const usePsInfiniteData: <T>({ getKey, isEnabled, key, loadPage, onSettled, onWriteFailed, refreshKey, }: PsInfiniteDataOptions<T>) => PsInfiniteDataResult<T>;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { usePsChangeQueue, usePsOptimisticRows } from '@pallasoft/ps-utilities/react';
|
|
3
|
+
import { PsInfiniteDataService } from '../services/ps-infinite-data-service';
|
|
4
|
+
export const usePsInfiniteData = ({ getKey, isEnabled = true, key, loadPage, onSettled, onWriteFailed, refreshKey, }) => {
|
|
5
|
+
const [state, setState] = useState(() => PsInfiniteDataService.initial());
|
|
6
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
7
|
+
const isSavingRef = useRef(false);
|
|
8
|
+
const submittedRef = useRef(0);
|
|
9
|
+
const stateRef = useRef(state);
|
|
10
|
+
const committedRef = useRef([]);
|
|
11
|
+
const versionRef = useRef(0);
|
|
12
|
+
const latestRef = useRef({ getKey, loadPage, onSettled, onWriteFailed });
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
latestRef.current = { getKey, loadPage, onSettled, onWriteFailed };
|
|
15
|
+
});
|
|
16
|
+
const commit = useCallback((next) => {
|
|
17
|
+
stateRef.current = next(stateRef.current);
|
|
18
|
+
setState(stateRef.current);
|
|
19
|
+
}, []);
|
|
20
|
+
const setSaving = useCallback((value) => {
|
|
21
|
+
isSavingRef.current = value;
|
|
22
|
+
setIsSaving(value);
|
|
23
|
+
}, []);
|
|
24
|
+
const fetchFirst = useCallback(async (isSilent) => {
|
|
25
|
+
versionRef.current += 1;
|
|
26
|
+
const version = versionRef.current;
|
|
27
|
+
if (!isSilent)
|
|
28
|
+
commit(() => PsInfiniteDataService.initial());
|
|
29
|
+
try {
|
|
30
|
+
const page = await latestRef.current.loadPage(null);
|
|
31
|
+
if (version !== versionRef.current)
|
|
32
|
+
return;
|
|
33
|
+
commit(() => ({
|
|
34
|
+
error: false,
|
|
35
|
+
isLoading: false,
|
|
36
|
+
isLoadingMore: false,
|
|
37
|
+
items: page.items,
|
|
38
|
+
nextCursor: page.nextCursor,
|
|
39
|
+
pages: 1,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
if (version !== versionRef.current)
|
|
44
|
+
return;
|
|
45
|
+
commit((current) => ({ ...current, error: true, isLoading: false, isLoadingMore: false }));
|
|
46
|
+
}
|
|
47
|
+
}, [commit]);
|
|
48
|
+
const reloadLoaded = useCallback(async () => {
|
|
49
|
+
versionRef.current += 1;
|
|
50
|
+
const version = versionRef.current;
|
|
51
|
+
const target = Math.max(stateRef.current.pages, 1);
|
|
52
|
+
try {
|
|
53
|
+
let items = [];
|
|
54
|
+
let cursor = null;
|
|
55
|
+
let pages = 0;
|
|
56
|
+
while (pages < target) {
|
|
57
|
+
const page = await latestRef.current.loadPage(cursor);
|
|
58
|
+
if (version !== versionRef.current)
|
|
59
|
+
return;
|
|
60
|
+
items = PsInfiniteDataService.merge(items, page.items, latestRef.current.getKey);
|
|
61
|
+
cursor = page.nextCursor;
|
|
62
|
+
pages += 1;
|
|
63
|
+
if (cursor === null)
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
commit(() => ({
|
|
67
|
+
error: false,
|
|
68
|
+
isLoading: false,
|
|
69
|
+
isLoadingMore: false,
|
|
70
|
+
items,
|
|
71
|
+
nextCursor: cursor,
|
|
72
|
+
pages,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
if (version !== versionRef.current)
|
|
77
|
+
return;
|
|
78
|
+
commit((current) => ({ ...current, error: true }));
|
|
79
|
+
}
|
|
80
|
+
}, [commit]);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
committedRef.current = [];
|
|
83
|
+
setSaving(false);
|
|
84
|
+
if (!isEnabled) {
|
|
85
|
+
versionRef.current += 1;
|
|
86
|
+
commit(() => PsInfiniteDataService.initial());
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
void fetchFirst(false);
|
|
90
|
+
return () => {
|
|
91
|
+
versionRef.current += 1;
|
|
92
|
+
};
|
|
93
|
+
}, [key, isEnabled, fetchFirst, commit, setSaving]);
|
|
94
|
+
const refreshRef = useRef(refreshKey);
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (refreshRef.current === refreshKey)
|
|
97
|
+
return;
|
|
98
|
+
refreshRef.current = refreshKey;
|
|
99
|
+
if (isEnabled && !isSavingRef.current && !stateRef.current.isLoading)
|
|
100
|
+
void reloadLoaded();
|
|
101
|
+
}, [isEnabled, refreshKey, reloadLoaded]);
|
|
102
|
+
const loadMore = useCallback(async () => {
|
|
103
|
+
const current = stateRef.current;
|
|
104
|
+
if (current.isLoading || current.isLoadingMore || current.nextCursor === null)
|
|
105
|
+
return;
|
|
106
|
+
const version = versionRef.current;
|
|
107
|
+
const cursor = current.nextCursor;
|
|
108
|
+
commit((value) => ({ ...value, isLoadingMore: true }));
|
|
109
|
+
try {
|
|
110
|
+
const page = await latestRef.current.loadPage(cursor);
|
|
111
|
+
if (version !== versionRef.current)
|
|
112
|
+
return;
|
|
113
|
+
commit((value) => ({
|
|
114
|
+
...value,
|
|
115
|
+
error: false,
|
|
116
|
+
isLoadingMore: false,
|
|
117
|
+
items: PsInfiniteDataService.merge(value.items, page.items, latestRef.current.getKey),
|
|
118
|
+
nextCursor: page.nextCursor,
|
|
119
|
+
pages: value.pages + 1,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
if (version !== versionRef.current)
|
|
124
|
+
return;
|
|
125
|
+
commit((value) => ({ ...value, error: true, isLoadingMore: false }));
|
|
126
|
+
}
|
|
127
|
+
}, [commit]);
|
|
128
|
+
const refresh = useCallback(() => fetchFirst(true), [fetchFirst]);
|
|
129
|
+
const apply = useCallback((rows, updates) => PsInfiniteDataService.apply(rows, updates), []);
|
|
130
|
+
const optimistic = usePsOptimisticRows({
|
|
131
|
+
apply,
|
|
132
|
+
isSaving,
|
|
133
|
+
rows: state.items,
|
|
134
|
+
});
|
|
135
|
+
const rememberRef = useRef(optimistic.remember);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
rememberRef.current = optimistic.remember;
|
|
138
|
+
});
|
|
139
|
+
const send = useCallback(async (writes) => {
|
|
140
|
+
for (const item of writes) {
|
|
141
|
+
await item.request();
|
|
142
|
+
committedRef.current = [...committedRef.current, item];
|
|
143
|
+
item.resolve(true);
|
|
144
|
+
}
|
|
145
|
+
}, []);
|
|
146
|
+
const onFailed = useCallback((failed, dropped) => {
|
|
147
|
+
for (const item of [...failed, ...dropped]) {
|
|
148
|
+
optimistic.forgetLast();
|
|
149
|
+
item.resolve(false);
|
|
150
|
+
}
|
|
151
|
+
if (latestRef.current.onWriteFailed !== undefined)
|
|
152
|
+
latestRef.current.onWriteFailed();
|
|
153
|
+
}, [optimistic.forgetLast]);
|
|
154
|
+
const onDrained = useCallback(() => {
|
|
155
|
+
const through = submittedRef.current;
|
|
156
|
+
const settled = committedRef.current;
|
|
157
|
+
committedRef.current = [];
|
|
158
|
+
void (async () => {
|
|
159
|
+
const settle = latestRef.current.onSettled;
|
|
160
|
+
if (settle !== undefined)
|
|
161
|
+
await settle().catch(() => undefined);
|
|
162
|
+
if (settled.length > 0)
|
|
163
|
+
commit((current) => ({
|
|
164
|
+
...current,
|
|
165
|
+
items: PsInfiniteDataService.apply(current.items, settled.map((item) => item.update)),
|
|
166
|
+
}));
|
|
167
|
+
if (submittedRef.current === through)
|
|
168
|
+
setSaving(false);
|
|
169
|
+
})();
|
|
170
|
+
}, [commit, setSaving]);
|
|
171
|
+
const queue = usePsChangeQueue({ onDrained, onFailed, send });
|
|
172
|
+
const { enqueue } = queue;
|
|
173
|
+
const write = useCallback((update, request) => {
|
|
174
|
+
if (stateRef.current.isLoading)
|
|
175
|
+
return Promise.resolve(false);
|
|
176
|
+
submittedRef.current += 1;
|
|
177
|
+
setSaving(true);
|
|
178
|
+
rememberRef.current(update);
|
|
179
|
+
return new Promise((resolve) => enqueue({ request, resolve, update }));
|
|
180
|
+
}, [enqueue, setSaving]);
|
|
181
|
+
return useMemo(() => ({
|
|
182
|
+
committedItems: state.items,
|
|
183
|
+
error: state.error,
|
|
184
|
+
hasMore: PsInfiniteDataService.hasMore(state),
|
|
185
|
+
isLoading: state.isLoading,
|
|
186
|
+
isLoadingMore: state.isLoadingMore,
|
|
187
|
+
items: optimistic.rows,
|
|
188
|
+
loadMore,
|
|
189
|
+
refresh,
|
|
190
|
+
write,
|
|
191
|
+
}), [loadMore, optimistic.rows, refresh, state, write]);
|
|
192
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -174,3 +174,9 @@ export * from './interfaces/ps-barcode-scan';
|
|
|
174
174
|
export * from './interfaces/ps-barcode-scanner-labels';
|
|
175
175
|
export * from './interfaces/use-ps-barcode-scanner-result';
|
|
176
176
|
export { PsBarcodeScannerService } from './services/ps-barcode-scanner-service';
|
|
177
|
+
export * from './components/ps-infinite-list';
|
|
178
|
+
export * from './hooks/use-ps-infinite-data';
|
|
179
|
+
export * from './interfaces/ps-infinite-data-options';
|
|
180
|
+
export * from './interfaces/ps-infinite-data-result';
|
|
181
|
+
export * from './interfaces/ps-infinite-data-update';
|
|
182
|
+
export * from './interfaces/ps-infinite-page';
|
package/dist/index.js
CHANGED
|
@@ -169,3 +169,9 @@ export * from './interfaces/ps-barcode-scan';
|
|
|
169
169
|
export * from './interfaces/ps-barcode-scanner-labels';
|
|
170
170
|
export * from './interfaces/use-ps-barcode-scanner-result';
|
|
171
171
|
export { PsBarcodeScannerService } from './services/ps-barcode-scanner-service';
|
|
172
|
+
export * from './components/ps-infinite-list';
|
|
173
|
+
export * from './hooks/use-ps-infinite-data';
|
|
174
|
+
export * from './interfaces/ps-infinite-data-options';
|
|
175
|
+
export * from './interfaces/ps-infinite-data-result';
|
|
176
|
+
export * from './interfaces/ps-infinite-data-update';
|
|
177
|
+
export * from './interfaces/ps-infinite-page';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PsInfinitePage } from './ps-infinite-page';
|
|
2
|
+
export interface PsInfiniteDataOptions<T> {
|
|
3
|
+
getKey: (item: T) => string;
|
|
4
|
+
isEnabled?: boolean;
|
|
5
|
+
key: string;
|
|
6
|
+
loadPage: (cursor: string | null) => Promise<PsInfinitePage<T>>;
|
|
7
|
+
onSettled?: () => Promise<unknown>;
|
|
8
|
+
onWriteFailed?: () => void;
|
|
9
|
+
refreshKey?: number | string;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PsInfiniteDataUpdate } from './ps-infinite-data-update';
|
|
2
|
+
export interface PsInfiniteDataResult<T> {
|
|
3
|
+
committedItems: T[];
|
|
4
|
+
error: boolean;
|
|
5
|
+
hasMore: boolean;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
isLoadingMore: boolean;
|
|
8
|
+
items: T[];
|
|
9
|
+
loadMore: () => Promise<void>;
|
|
10
|
+
refresh: () => Promise<void>;
|
|
11
|
+
write: (update: PsInfiniteDataUpdate<T>, request: () => Promise<unknown>) => Promise<boolean>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type PsInfiniteDataUpdate<T> = (items: T[]) => T[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PsInfiniteDataState } from '../interfaces/ps-infinite-data-state';
|
|
2
|
+
import type { PsInfiniteDataUpdate } from '../interfaces/ps-infinite-data-update';
|
|
3
|
+
export declare class PsInfiniteDataService {
|
|
4
|
+
static initial<T>(): PsInfiniteDataState<T>;
|
|
5
|
+
static merge<T>(existing: T[], incoming: T[], getKey: (item: T) => string): T[];
|
|
6
|
+
static apply<T>(rows: T[], updates: PsInfiniteDataUpdate<T>[]): T[];
|
|
7
|
+
static hasMore<T>(state: PsInfiniteDataState<T>): boolean;
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class PsInfiniteDataService {
|
|
2
|
+
static initial() {
|
|
3
|
+
return {
|
|
4
|
+
error: false,
|
|
5
|
+
isLoading: true,
|
|
6
|
+
isLoadingMore: false,
|
|
7
|
+
items: [],
|
|
8
|
+
nextCursor: null,
|
|
9
|
+
pages: 0,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
static merge(existing, incoming, getKey) {
|
|
13
|
+
const keys = new Set(existing.map(getKey));
|
|
14
|
+
return [...existing, ...incoming.filter((item) => !keys.has(getKey(item)))];
|
|
15
|
+
}
|
|
16
|
+
static apply(rows, updates) {
|
|
17
|
+
return updates.reduce((current, update) => update(current), rows);
|
|
18
|
+
}
|
|
19
|
+
static hasMore(state) {
|
|
20
|
+
return state.nextCursor !== null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import type { PsInfiniteSection } from '../components/ps-infinite-list/ps-infinite-list-props';
|
|
3
|
+
import type { PsMobileTheme } from '../interfaces/ps-mobile-theme';
|
|
4
|
+
export declare class PsInfiniteListService {
|
|
5
|
+
static readonly itemsKey = "ps-infinite-items";
|
|
6
|
+
static sections<T>(items: T[] | undefined, sections: PsInfiniteSection<T>[] | undefined): PsInfiniteSection<T>[];
|
|
7
|
+
static shouldLoadMore(hasMore: boolean, isLoading: boolean, isLoadingMore: boolean): boolean;
|
|
8
|
+
static itemFrame(theme: PsMobileTheme, index: number, count: number): ViewStyle;
|
|
9
|
+
static divider(theme: PsMobileTheme): ViewStyle;
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PsListConfig } from '../constants/ps-list-config';
|
|
2
|
+
import { PsMobileControlConfig } from '../constants/ps-mobile-control-config';
|
|
3
|
+
export class PsInfiniteListService {
|
|
4
|
+
static sections(items, sections) {
|
|
5
|
+
if (sections !== undefined)
|
|
6
|
+
return sections.filter((section) => section.data.length > 0);
|
|
7
|
+
return items === undefined || items.length === 0
|
|
8
|
+
? []
|
|
9
|
+
: [{ data: items, key: PsInfiniteListService.itemsKey }];
|
|
10
|
+
}
|
|
11
|
+
static shouldLoadMore(hasMore, isLoading, isLoadingMore) {
|
|
12
|
+
return hasMore && !isLoading && !isLoadingMore;
|
|
13
|
+
}
|
|
14
|
+
static itemFrame(theme, index, count) {
|
|
15
|
+
const isFirst = index === 0;
|
|
16
|
+
const isLast = index === count - 1;
|
|
17
|
+
const radius = PsMobileControlConfig.cardRadius;
|
|
18
|
+
return {
|
|
19
|
+
backgroundColor: theme.surface,
|
|
20
|
+
borderBottomLeftRadius: isLast ? radius : 0,
|
|
21
|
+
borderBottomRightRadius: isLast ? radius : 0,
|
|
22
|
+
borderBottomWidth: isLast ? 1 : 0,
|
|
23
|
+
borderColor: theme.line,
|
|
24
|
+
borderLeftWidth: 1,
|
|
25
|
+
borderRightWidth: 1,
|
|
26
|
+
borderTopLeftRadius: isFirst ? radius : 0,
|
|
27
|
+
borderTopRightRadius: isFirst ? radius : 0,
|
|
28
|
+
borderTopWidth: isFirst ? 1 : 0,
|
|
29
|
+
overflow: 'hidden',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
static divider(theme) {
|
|
33
|
+
return { backgroundColor: theme.line, height: 1, marginLeft: PsListConfig.dividerInset };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
PsInfiniteListService.itemsKey = 'ps-infinite-items';
|
package/package.json
CHANGED