@almadar/ui 2.12.6 → 2.13.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.
- package/dist/{chunk-TSETXL2E.js → chunk-2XXSUIOK.js} +3 -0
- package/dist/{chunk-WGJIL4YR.js → chunk-3CP74CBL.js} +525 -525
- package/dist/{chunk-4ZBSL37D.js → chunk-MNMASFYN.js} +212 -212
- package/dist/{chunk-GTIAVPI5.js → chunk-X2ZDE63F.js} +1 -1
- package/dist/{chunk-AX45OCIB.js → chunk-XF7GGUTH.js} +3 -3
- package/dist/{chunk-DKQN5FVU.js → chunk-YLKXEXBP.js} +24 -24
- package/dist/{chunk-HJJIE4K5.js → chunk-YYCP5CD7.js} +28 -13
- package/dist/components/index.d.ts +10 -10
- package/dist/components/index.js +9 -9
- package/dist/context/index.js +2 -2
- package/dist/hooks/index.d.ts +61 -61
- package/dist/hooks/index.js +3 -3
- package/dist/lib/index.d.ts +174 -174
- package/dist/lib/index.js +1 -1
- package/dist/locales/index.js +1 -1
- package/dist/providers/index.js +5 -5
- package/dist/runtime/index.js +6 -6
- package/package.json +1 -1
- package/themes/arctic.css +6 -6
- package/themes/copper.css +2 -2
- package/themes/ember.css +6 -6
- package/themes/forest.css +2 -2
- package/themes/lavender.css +2 -2
- package/themes/midnight.css +2 -2
- package/themes/minimalist.css +7 -7
- package/themes/neon.css +6 -6
- package/themes/ocean.css +2 -2
- package/themes/rose.css +2 -2
- package/themes/sand.css +6 -6
- package/themes/slate.css +6 -6
- package/themes/sunset.css +2 -2
- package/dist/{cn-C_ATNPvi.d.ts → parseContentSegments-BZrQRvgK.d.ts} +144 -144
|
@@ -1,183 +1,397 @@
|
|
|
1
1
|
import { useEventBus } from './chunk-YXZM3WCF.js';
|
|
2
|
-
import { en_default } from './chunk-
|
|
3
|
-
import React2, { createContext,
|
|
2
|
+
import { en_default } from './chunk-2XXSUIOK.js';
|
|
3
|
+
import React2, { createContext, useState, useCallback, useEffect, useContext, useMemo, useRef } from 'react';
|
|
4
4
|
import { jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
var SelectionContext = createContext(null);
|
|
7
|
+
var defaultCompareEntities = (a, b) => {
|
|
8
|
+
if (a === b) return true;
|
|
9
|
+
if (!a || !b) return false;
|
|
10
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
11
|
+
const aId = a.id;
|
|
12
|
+
const bId = b.id;
|
|
13
|
+
return aId !== void 0 && aId === bId;
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
};
|
|
17
|
+
function SelectionProvider({
|
|
18
|
+
children,
|
|
19
|
+
debug = false,
|
|
20
|
+
compareEntities = defaultCompareEntities
|
|
21
|
+
}) {
|
|
22
|
+
const eventBus = useEventBus();
|
|
23
|
+
const [selected, setSelectedState] = useState(null);
|
|
24
|
+
const setSelected = useCallback(
|
|
25
|
+
(entity) => {
|
|
26
|
+
setSelectedState(entity);
|
|
27
|
+
if (debug) {
|
|
28
|
+
console.log("[SelectionProvider] Selection set:", entity);
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
[debug]
|
|
32
|
+
);
|
|
33
|
+
const clearSelection = useCallback(() => {
|
|
34
|
+
setSelectedState(null);
|
|
35
|
+
if (debug) {
|
|
36
|
+
console.log("[SelectionProvider] Selection cleared");
|
|
37
|
+
}
|
|
38
|
+
}, [debug]);
|
|
39
|
+
const isSelected = useCallback(
|
|
40
|
+
(entity) => {
|
|
41
|
+
return compareEntities(selected, entity);
|
|
42
|
+
},
|
|
43
|
+
[selected, compareEntities]
|
|
44
|
+
);
|
|
15
45
|
useEffect(() => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (!node) return;
|
|
23
|
-
observerRef.current = new IntersectionObserver(
|
|
24
|
-
(entries) => {
|
|
25
|
-
const entry = entries[0];
|
|
26
|
-
if (entry.isIntersecting && hasMoreRef.current && !isLoadingRef.current) {
|
|
27
|
-
callbackRef.current();
|
|
46
|
+
const handleSelect = (event) => {
|
|
47
|
+
const row = event.payload?.row;
|
|
48
|
+
if (row) {
|
|
49
|
+
setSelected(row);
|
|
50
|
+
if (debug) {
|
|
51
|
+
console.log(`[SelectionProvider] ${event.type} received:`, row);
|
|
28
52
|
}
|
|
29
|
-
},
|
|
30
|
-
{ rootMargin }
|
|
31
|
-
);
|
|
32
|
-
observerRef.current.observe(node);
|
|
33
|
-
}, [rootMargin]);
|
|
34
|
-
return { sentinelRef };
|
|
35
|
-
}
|
|
36
|
-
var { $meta: _meta, ...coreMessages } = en_default;
|
|
37
|
-
var coreLocale = coreMessages;
|
|
38
|
-
var I18nContext = createContext({
|
|
39
|
-
locale: "en",
|
|
40
|
-
direction: "ltr",
|
|
41
|
-
t: (key) => coreLocale[key] ?? key
|
|
42
|
-
// core locale fallback
|
|
43
|
-
});
|
|
44
|
-
I18nContext.displayName = "I18nContext";
|
|
45
|
-
var I18nProvider = I18nContext.Provider;
|
|
46
|
-
function useTranslate() {
|
|
47
|
-
return useContext(I18nContext);
|
|
48
|
-
}
|
|
49
|
-
function createTranslate(messages) {
|
|
50
|
-
return (key, params) => {
|
|
51
|
-
let msg = messages[key] ?? coreLocale[key] ?? key;
|
|
52
|
-
if (params) {
|
|
53
|
-
for (const [k, v] of Object.entries(params)) {
|
|
54
|
-
msg = msg.split(`{{${k}}}`).join(String(v));
|
|
55
53
|
}
|
|
56
|
-
}
|
|
57
|
-
|
|
54
|
+
};
|
|
55
|
+
const handleDeselect = (event) => {
|
|
56
|
+
clearSelection();
|
|
57
|
+
if (debug) {
|
|
58
|
+
console.log(`[SelectionProvider] ${event.type} received - clearing selection`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const unsubView = eventBus.on("UI:VIEW", handleSelect);
|
|
62
|
+
const unsubSelect = eventBus.on("UI:SELECT", handleSelect);
|
|
63
|
+
const unsubClose = eventBus.on("UI:CLOSE", handleDeselect);
|
|
64
|
+
const unsubDeselect = eventBus.on("UI:DESELECT", handleDeselect);
|
|
65
|
+
const unsubCancel = eventBus.on("UI:CANCEL", handleDeselect);
|
|
66
|
+
return () => {
|
|
67
|
+
unsubView();
|
|
68
|
+
unsubSelect();
|
|
69
|
+
unsubClose();
|
|
70
|
+
unsubDeselect();
|
|
71
|
+
unsubCancel();
|
|
72
|
+
};
|
|
73
|
+
}, [eventBus, setSelected, clearSelection, debug]);
|
|
74
|
+
const contextValue = {
|
|
75
|
+
selected,
|
|
76
|
+
setSelected,
|
|
77
|
+
clearSelection,
|
|
78
|
+
isSelected
|
|
58
79
|
};
|
|
80
|
+
return /* @__PURE__ */ jsx(SelectionContext.Provider, { value: contextValue, children });
|
|
59
81
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!
|
|
63
|
-
|
|
64
|
-
search: "",
|
|
65
|
-
filters: {},
|
|
66
|
-
sortField: void 0,
|
|
67
|
-
sortDirection: void 0,
|
|
68
|
-
listeners: /* @__PURE__ */ new Set()
|
|
69
|
-
});
|
|
82
|
+
function useSelection() {
|
|
83
|
+
const context = useContext(SelectionContext);
|
|
84
|
+
if (!context) {
|
|
85
|
+
throw new Error("useSelection must be used within a SelectionProvider");
|
|
70
86
|
}
|
|
71
|
-
return
|
|
87
|
+
return context;
|
|
72
88
|
}
|
|
73
|
-
function
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
const store = useMemo(() => getOrCreateStore(query), [query]);
|
|
79
|
-
useMemo(() => {
|
|
80
|
-
const listener = () => forceUpdate({});
|
|
81
|
-
store.listeners.add(listener);
|
|
82
|
-
return () => {
|
|
83
|
-
store.listeners.delete(listener);
|
|
84
|
-
};
|
|
85
|
-
}, [store]);
|
|
86
|
-
const notifyListeners = useCallback(() => {
|
|
87
|
-
store.listeners.forEach((listener) => listener());
|
|
88
|
-
}, [store]);
|
|
89
|
-
const setSearch = useCallback((value) => {
|
|
90
|
-
store.search = value;
|
|
91
|
-
notifyListeners();
|
|
92
|
-
}, [store, notifyListeners]);
|
|
93
|
-
const setFilter = useCallback((key, value) => {
|
|
94
|
-
store.filters = { ...store.filters, [key]: value };
|
|
95
|
-
notifyListeners();
|
|
96
|
-
}, [store, notifyListeners]);
|
|
97
|
-
const clearFilters = useCallback(() => {
|
|
98
|
-
store.filters = {};
|
|
99
|
-
store.search = "";
|
|
100
|
-
notifyListeners();
|
|
101
|
-
}, [store, notifyListeners]);
|
|
102
|
-
const setSort = useCallback((field, direction) => {
|
|
103
|
-
store.sortField = field;
|
|
104
|
-
store.sortDirection = direction;
|
|
105
|
-
notifyListeners();
|
|
106
|
-
}, [store, notifyListeners]);
|
|
107
|
-
return {
|
|
108
|
-
search: store.search,
|
|
109
|
-
setSearch,
|
|
110
|
-
filters: store.filters,
|
|
111
|
-
setFilter,
|
|
112
|
-
clearFilters,
|
|
113
|
-
sortField: store.sortField,
|
|
114
|
-
sortDirection: store.sortDirection,
|
|
115
|
-
setSort
|
|
116
|
-
};
|
|
89
|
+
function useSelectionOptional() {
|
|
90
|
+
const context = useContext(SelectionContext);
|
|
91
|
+
return context;
|
|
117
92
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
93
|
+
var EntityDataContext = createContext(null);
|
|
94
|
+
function EntityDataProvider({
|
|
95
|
+
adapter,
|
|
96
|
+
children
|
|
97
|
+
}) {
|
|
98
|
+
return React2.createElement(
|
|
99
|
+
EntityDataContext.Provider,
|
|
100
|
+
{ value: adapter },
|
|
101
|
+
children
|
|
102
|
+
);
|
|
125
103
|
}
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
const timerRef = useRef(null);
|
|
129
|
-
const startPos = useRef({ x: 0, y: 0 });
|
|
130
|
-
const isPressedRef = useRef(false);
|
|
131
|
-
const firedRef = useRef(false);
|
|
132
|
-
const cancel = useCallback(() => {
|
|
133
|
-
if (timerRef.current) {
|
|
134
|
-
clearTimeout(timerRef.current);
|
|
135
|
-
timerRef.current = null;
|
|
136
|
-
}
|
|
137
|
-
isPressedRef.current = false;
|
|
138
|
-
}, []);
|
|
139
|
-
const onPointerDown = useCallback((e) => {
|
|
140
|
-
firedRef.current = false;
|
|
141
|
-
startPos.current = { x: e.clientX, y: e.clientY };
|
|
142
|
-
isPressedRef.current = true;
|
|
143
|
-
timerRef.current = setTimeout(() => {
|
|
144
|
-
firedRef.current = true;
|
|
145
|
-
isPressedRef.current = false;
|
|
146
|
-
onLongPress();
|
|
147
|
-
}, duration);
|
|
148
|
-
}, [duration, onLongPress]);
|
|
149
|
-
const onPointerMove = useCallback((e) => {
|
|
150
|
-
if (!isPressedRef.current) return;
|
|
151
|
-
const dx = e.clientX - startPos.current.x;
|
|
152
|
-
const dy = e.clientY - startPos.current.y;
|
|
153
|
-
if (Math.sqrt(dx * dx + dy * dy) > moveThreshold) {
|
|
154
|
-
cancel();
|
|
155
|
-
}
|
|
156
|
-
}, [moveThreshold, cancel]);
|
|
157
|
-
const onPointerUp = useCallback(() => {
|
|
158
|
-
cancel();
|
|
159
|
-
}, [cancel]);
|
|
160
|
-
const onPointerCancel = useCallback(() => {
|
|
161
|
-
cancel();
|
|
162
|
-
}, [cancel]);
|
|
163
|
-
return {
|
|
164
|
-
onPointerDown,
|
|
165
|
-
onPointerMove,
|
|
166
|
-
onPointerUp,
|
|
167
|
-
onPointerCancel,
|
|
168
|
-
isPressed: isPressedRef.current
|
|
169
|
-
};
|
|
104
|
+
function useEntityDataAdapter() {
|
|
105
|
+
return useContext(EntityDataContext);
|
|
170
106
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
const
|
|
107
|
+
var entityDataKeys = {
|
|
108
|
+
all: ["entities"],
|
|
109
|
+
lists: () => [...entityDataKeys.all, "list"],
|
|
110
|
+
list: (entity, filters) => [...entityDataKeys.lists(), entity, filters],
|
|
111
|
+
details: () => [...entityDataKeys.all, "detail"],
|
|
112
|
+
detail: (entity, id) => [...entityDataKeys.details(), entity, id]
|
|
113
|
+
};
|
|
114
|
+
function useEntityList(entity, options = {}) {
|
|
115
|
+
const { skip = false } = options;
|
|
116
|
+
const adapter = useContext(EntityDataContext);
|
|
117
|
+
const adapterData = useMemo(() => {
|
|
118
|
+
if (!adapter || !entity || skip) return [];
|
|
119
|
+
return adapter.getData(entity);
|
|
120
|
+
}, [adapter, entity, skip, adapter?.isLoading]);
|
|
121
|
+
const [stubData, setStubData] = useState([]);
|
|
122
|
+
const [stubLoading, setStubLoading] = useState(!skip && !!entity && !adapter);
|
|
123
|
+
const [stubError, setStubError] = useState(null);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (adapter || skip || !entity) {
|
|
126
|
+
setStubLoading(false);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
setStubLoading(true);
|
|
130
|
+
const t = setTimeout(() => {
|
|
131
|
+
setStubData([]);
|
|
132
|
+
setStubLoading(false);
|
|
133
|
+
}, 100);
|
|
134
|
+
return () => clearTimeout(t);
|
|
135
|
+
}, [entity, skip, adapter]);
|
|
136
|
+
if (adapter) {
|
|
137
|
+
return {
|
|
138
|
+
data: adapterData,
|
|
139
|
+
isLoading: adapter.isLoading,
|
|
140
|
+
error: adapter.error ? new Error(adapter.error) : null,
|
|
141
|
+
refetch: () => {
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return { data: stubData, isLoading: stubLoading, error: stubError, refetch: () => {
|
|
146
|
+
} };
|
|
147
|
+
}
|
|
148
|
+
function useEntity(entity, id) {
|
|
149
|
+
const adapter = useContext(EntityDataContext);
|
|
150
|
+
const adapterData = useMemo(() => {
|
|
151
|
+
if (!adapter || !entity || !id) return null;
|
|
152
|
+
return adapter.getById(entity, id) ?? null;
|
|
153
|
+
}, [adapter, entity, id, adapter?.isLoading]);
|
|
154
|
+
const [stubData, setStubData] = useState(null);
|
|
155
|
+
const [stubLoading, setStubLoading] = useState(!!entity && !!id && !adapter);
|
|
156
|
+
const [stubError, setStubError] = useState(null);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (adapter || !entity || !id) {
|
|
159
|
+
setStubLoading(false);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
setStubLoading(true);
|
|
163
|
+
const t = setTimeout(() => {
|
|
164
|
+
setStubData(null);
|
|
165
|
+
setStubLoading(false);
|
|
166
|
+
}, 100);
|
|
167
|
+
return () => clearTimeout(t);
|
|
168
|
+
}, [entity, id, adapter]);
|
|
169
|
+
if (adapter) {
|
|
170
|
+
return {
|
|
171
|
+
data: adapterData,
|
|
172
|
+
isLoading: adapter.isLoading,
|
|
173
|
+
error: adapter.error ? new Error(adapter.error) : null
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return { data: stubData, isLoading: stubLoading, error: stubError };
|
|
177
|
+
}
|
|
178
|
+
function useEntityDetail(entity, id) {
|
|
179
|
+
const result = useEntity(entity, id);
|
|
180
|
+
return { ...result, refetch: () => {
|
|
181
|
+
} };
|
|
182
|
+
}
|
|
183
|
+
var suspenseCache = /* @__PURE__ */ new Map();
|
|
184
|
+
function getSuspenseCacheKey(entity, type, id) {
|
|
185
|
+
return id ? `${type}:${entity}:${id}` : `${type}:${entity}`;
|
|
186
|
+
}
|
|
187
|
+
function useEntityListSuspense(entity) {
|
|
188
|
+
const adapter = useContext(EntityDataContext);
|
|
189
|
+
if (adapter) {
|
|
190
|
+
if (adapter.isLoading) {
|
|
191
|
+
const cacheKey2 = getSuspenseCacheKey(entity, "list");
|
|
192
|
+
let entry2 = suspenseCache.get(cacheKey2);
|
|
193
|
+
if (!entry2 || entry2.status === "resolved") {
|
|
194
|
+
let resolve;
|
|
195
|
+
const promise = new Promise((r) => {
|
|
196
|
+
resolve = r;
|
|
197
|
+
});
|
|
198
|
+
entry2 = { promise, status: "pending" };
|
|
199
|
+
suspenseCache.set(cacheKey2, entry2);
|
|
200
|
+
const check = setInterval(() => {
|
|
201
|
+
if (!adapter.isLoading) {
|
|
202
|
+
clearInterval(check);
|
|
203
|
+
entry2.status = "resolved";
|
|
204
|
+
resolve();
|
|
205
|
+
}
|
|
206
|
+
}, 50);
|
|
207
|
+
}
|
|
208
|
+
if (entry2.status === "pending") {
|
|
209
|
+
throw entry2.promise;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (adapter.error) {
|
|
213
|
+
throw new Error(adapter.error);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
data: adapter.getData(entity),
|
|
217
|
+
refetch: () => {
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
const cacheKey = getSuspenseCacheKey(entity, "list");
|
|
222
|
+
let entry = suspenseCache.get(cacheKey);
|
|
223
|
+
if (!entry) {
|
|
224
|
+
let resolve;
|
|
225
|
+
const promise = new Promise((r) => {
|
|
226
|
+
resolve = r;
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
entry.status = "resolved";
|
|
229
|
+
resolve();
|
|
230
|
+
}, 100);
|
|
231
|
+
});
|
|
232
|
+
entry = { promise, status: "pending" };
|
|
233
|
+
suspenseCache.set(cacheKey, entry);
|
|
234
|
+
}
|
|
235
|
+
if (entry.status === "pending") {
|
|
236
|
+
throw entry.promise;
|
|
237
|
+
}
|
|
238
|
+
return { data: [], refetch: () => {
|
|
239
|
+
} };
|
|
240
|
+
}
|
|
241
|
+
function useEntitySuspense(entity, id) {
|
|
242
|
+
const adapter = useContext(EntityDataContext);
|
|
243
|
+
if (adapter) {
|
|
244
|
+
if (adapter.isLoading) {
|
|
245
|
+
const cacheKey2 = getSuspenseCacheKey(entity, "detail", id);
|
|
246
|
+
let entry2 = suspenseCache.get(cacheKey2);
|
|
247
|
+
if (!entry2 || entry2.status === "resolved") {
|
|
248
|
+
let resolve;
|
|
249
|
+
const promise = new Promise((r) => {
|
|
250
|
+
resolve = r;
|
|
251
|
+
});
|
|
252
|
+
entry2 = { promise, status: "pending" };
|
|
253
|
+
suspenseCache.set(cacheKey2, entry2);
|
|
254
|
+
const check = setInterval(() => {
|
|
255
|
+
if (!adapter.isLoading) {
|
|
256
|
+
clearInterval(check);
|
|
257
|
+
entry2.status = "resolved";
|
|
258
|
+
resolve();
|
|
259
|
+
}
|
|
260
|
+
}, 50);
|
|
261
|
+
}
|
|
262
|
+
if (entry2.status === "pending") {
|
|
263
|
+
throw entry2.promise;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (adapter.error) {
|
|
267
|
+
throw new Error(adapter.error);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
data: adapter.getById(entity, id) ?? null,
|
|
271
|
+
refetch: () => {
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const cacheKey = getSuspenseCacheKey(entity, "detail", id);
|
|
276
|
+
let entry = suspenseCache.get(cacheKey);
|
|
277
|
+
if (!entry) {
|
|
278
|
+
let resolve;
|
|
279
|
+
const promise = new Promise((r) => {
|
|
280
|
+
resolve = r;
|
|
281
|
+
setTimeout(() => {
|
|
282
|
+
entry.status = "resolved";
|
|
283
|
+
resolve();
|
|
284
|
+
}, 100);
|
|
285
|
+
});
|
|
286
|
+
entry = { promise, status: "pending" };
|
|
287
|
+
suspenseCache.set(cacheKey, entry);
|
|
288
|
+
}
|
|
289
|
+
if (entry.status === "pending") {
|
|
290
|
+
throw entry.promise;
|
|
291
|
+
}
|
|
292
|
+
return { data: null, refetch: () => {
|
|
293
|
+
} };
|
|
294
|
+
}
|
|
295
|
+
var queryStores = /* @__PURE__ */ new Map();
|
|
296
|
+
function getOrCreateStore(query) {
|
|
297
|
+
if (!queryStores.has(query)) {
|
|
298
|
+
queryStores.set(query, {
|
|
299
|
+
search: "",
|
|
300
|
+
filters: {},
|
|
301
|
+
sortField: void 0,
|
|
302
|
+
sortDirection: void 0,
|
|
303
|
+
listeners: /* @__PURE__ */ new Set()
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return queryStores.get(query);
|
|
307
|
+
}
|
|
308
|
+
function useQuerySingleton(query) {
|
|
309
|
+
const [, forceUpdate] = useState({});
|
|
310
|
+
if (!query) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const store = useMemo(() => getOrCreateStore(query), [query]);
|
|
314
|
+
useMemo(() => {
|
|
315
|
+
const listener = () => forceUpdate({});
|
|
316
|
+
store.listeners.add(listener);
|
|
317
|
+
return () => {
|
|
318
|
+
store.listeners.delete(listener);
|
|
319
|
+
};
|
|
320
|
+
}, [store]);
|
|
321
|
+
const notifyListeners = useCallback(() => {
|
|
322
|
+
store.listeners.forEach((listener) => listener());
|
|
323
|
+
}, [store]);
|
|
324
|
+
const setSearch = useCallback((value) => {
|
|
325
|
+
store.search = value;
|
|
326
|
+
notifyListeners();
|
|
327
|
+
}, [store, notifyListeners]);
|
|
328
|
+
const setFilter = useCallback((key, value) => {
|
|
329
|
+
store.filters = { ...store.filters, [key]: value };
|
|
330
|
+
notifyListeners();
|
|
331
|
+
}, [store, notifyListeners]);
|
|
332
|
+
const clearFilters = useCallback(() => {
|
|
333
|
+
store.filters = {};
|
|
334
|
+
store.search = "";
|
|
335
|
+
notifyListeners();
|
|
336
|
+
}, [store, notifyListeners]);
|
|
337
|
+
const setSort = useCallback((field, direction) => {
|
|
338
|
+
store.sortField = field;
|
|
339
|
+
store.sortDirection = direction;
|
|
340
|
+
notifyListeners();
|
|
341
|
+
}, [store, notifyListeners]);
|
|
342
|
+
return {
|
|
343
|
+
search: store.search,
|
|
344
|
+
setSearch,
|
|
345
|
+
filters: store.filters,
|
|
346
|
+
setFilter,
|
|
347
|
+
clearFilters,
|
|
348
|
+
sortField: store.sortField,
|
|
349
|
+
sortDirection: store.sortDirection,
|
|
350
|
+
setSort
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function parseQueryBinding(binding) {
|
|
354
|
+
const cleaned = binding.startsWith("@") ? binding.slice(1) : binding;
|
|
355
|
+
const parts = cleaned.split(".");
|
|
356
|
+
return {
|
|
357
|
+
query: parts[0],
|
|
358
|
+
field: parts.length > 1 ? parts.slice(1).join(".") : void 0
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
var { $meta: _meta, ...coreMessages } = en_default;
|
|
362
|
+
var coreLocale = coreMessages;
|
|
363
|
+
var I18nContext = createContext({
|
|
364
|
+
locale: "en",
|
|
365
|
+
direction: "ltr",
|
|
366
|
+
t: (key) => coreLocale[key] ?? key
|
|
367
|
+
// core locale fallback
|
|
368
|
+
});
|
|
369
|
+
I18nContext.displayName = "I18nContext";
|
|
370
|
+
var I18nProvider = I18nContext.Provider;
|
|
371
|
+
function useTranslate() {
|
|
372
|
+
return useContext(I18nContext);
|
|
373
|
+
}
|
|
374
|
+
function createTranslate(messages) {
|
|
375
|
+
return (key, params) => {
|
|
376
|
+
let msg = messages[key] ?? coreLocale[key] ?? key;
|
|
377
|
+
if (params) {
|
|
378
|
+
for (const [k, v] of Object.entries(params)) {
|
|
379
|
+
msg = msg.split(`{{${k}}}`).join(String(v));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return msg;
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function useSwipeGesture(callbacks, options = {}) {
|
|
386
|
+
const { threshold = 50, velocityThreshold = 0.3, preventDefault = false } = options;
|
|
387
|
+
const startX = useRef(0);
|
|
388
|
+
const startY = useRef(0);
|
|
389
|
+
const startTime = useRef(0);
|
|
390
|
+
const currentX = useRef(0);
|
|
391
|
+
const tracking = useRef(false);
|
|
392
|
+
const offsetXRef = useRef(0);
|
|
393
|
+
const isSwipingRef = useRef(false);
|
|
394
|
+
const onPointerDown = useCallback((e) => {
|
|
181
395
|
startX.current = e.clientX;
|
|
182
396
|
startY.current = e.clientY;
|
|
183
397
|
currentX.current = e.clientX;
|
|
@@ -230,6 +444,51 @@ function useSwipeGesture(callbacks, options = {}) {
|
|
|
230
444
|
isSwiping: isSwipingRef.current
|
|
231
445
|
};
|
|
232
446
|
}
|
|
447
|
+
function useLongPress(onLongPress, options = {}) {
|
|
448
|
+
const { duration = 500, moveThreshold = 10 } = options;
|
|
449
|
+
const timerRef = useRef(null);
|
|
450
|
+
const startPos = useRef({ x: 0, y: 0 });
|
|
451
|
+
const isPressedRef = useRef(false);
|
|
452
|
+
const firedRef = useRef(false);
|
|
453
|
+
const cancel = useCallback(() => {
|
|
454
|
+
if (timerRef.current) {
|
|
455
|
+
clearTimeout(timerRef.current);
|
|
456
|
+
timerRef.current = null;
|
|
457
|
+
}
|
|
458
|
+
isPressedRef.current = false;
|
|
459
|
+
}, []);
|
|
460
|
+
const onPointerDown = useCallback((e) => {
|
|
461
|
+
firedRef.current = false;
|
|
462
|
+
startPos.current = { x: e.clientX, y: e.clientY };
|
|
463
|
+
isPressedRef.current = true;
|
|
464
|
+
timerRef.current = setTimeout(() => {
|
|
465
|
+
firedRef.current = true;
|
|
466
|
+
isPressedRef.current = false;
|
|
467
|
+
onLongPress();
|
|
468
|
+
}, duration);
|
|
469
|
+
}, [duration, onLongPress]);
|
|
470
|
+
const onPointerMove = useCallback((e) => {
|
|
471
|
+
if (!isPressedRef.current) return;
|
|
472
|
+
const dx = e.clientX - startPos.current.x;
|
|
473
|
+
const dy = e.clientY - startPos.current.y;
|
|
474
|
+
if (Math.sqrt(dx * dx + dy * dy) > moveThreshold) {
|
|
475
|
+
cancel();
|
|
476
|
+
}
|
|
477
|
+
}, [moveThreshold, cancel]);
|
|
478
|
+
const onPointerUp = useCallback(() => {
|
|
479
|
+
cancel();
|
|
480
|
+
}, [cancel]);
|
|
481
|
+
const onPointerCancel = useCallback(() => {
|
|
482
|
+
cancel();
|
|
483
|
+
}, [cancel]);
|
|
484
|
+
return {
|
|
485
|
+
onPointerDown,
|
|
486
|
+
onPointerMove,
|
|
487
|
+
onPointerUp,
|
|
488
|
+
onPointerCancel,
|
|
489
|
+
isPressed: isPressedRef.current
|
|
490
|
+
};
|
|
491
|
+
}
|
|
233
492
|
function useDragReorder(initialItems, onReorder) {
|
|
234
493
|
const [items, setItems] = useState(initialItems);
|
|
235
494
|
const [dragIndex, setDragIndex] = useState(-1);
|
|
@@ -279,373 +538,114 @@ function useDragReorder(initialItems, onReorder) {
|
|
|
279
538
|
role: "button"
|
|
280
539
|
}), [handleDragStart, dragIndex]);
|
|
281
540
|
const getItemProps = useCallback((index) => ({
|
|
282
|
-
onPointerMove: handleDragMove(index),
|
|
283
|
-
onPointerUp: handleDragEnd,
|
|
284
|
-
"aria-dropeffect": "move",
|
|
285
|
-
"data-drag-index": String(index),
|
|
286
|
-
style: {
|
|
287
|
-
opacity: dragIndex === index ? 0.5 : 1,
|
|
288
|
-
transition: isDragging ? "transform 150ms ease" : void 0,
|
|
289
|
-
transform: isDragging && dragOverIndex >= 0 ? index === dragIndex ? "scale(1.02)" : index > dragIndex && index <= dragOverIndex ? "translateY(-100%)" : index < dragIndex && index >= dragOverIndex ? "translateY(100%)" : void 0 : void 0
|
|
290
|
-
}
|
|
291
|
-
}), [handleDragMove, handleDragEnd, dragIndex, dragOverIndex, isDragging]);
|
|
292
|
-
return {
|
|
293
|
-
items,
|
|
294
|
-
dragIndex,
|
|
295
|
-
dragOverIndex,
|
|
296
|
-
isDragging,
|
|
297
|
-
getDragHandleProps,
|
|
298
|
-
getItemProps
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
function usePullToRefresh(onRefresh, options = {}) {
|
|
302
|
-
const { threshold = 60, maxPull = 120 } = options;
|
|
303
|
-
const [pullDistance, setPullDistance] = useState(0);
|
|
304
|
-
const [isPulling, setIsPulling] = useState(false);
|
|
305
|
-
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
306
|
-
const startY = useRef(0);
|
|
307
|
-
const scrollTopRef = useRef(0);
|
|
308
|
-
const onTouchStart = useCallback((e) => {
|
|
309
|
-
const container = e.currentTarget;
|
|
310
|
-
scrollTopRef.current = container.scrollTop;
|
|
311
|
-
if (scrollTopRef.current <= 0) {
|
|
312
|
-
startY.current = e.touches[0].clientY;
|
|
313
|
-
setIsPulling(true);
|
|
314
|
-
}
|
|
315
|
-
}, []);
|
|
316
|
-
const onTouchMove = useCallback((e) => {
|
|
317
|
-
if (!isPulling || isRefreshing) return;
|
|
318
|
-
const container = e.currentTarget;
|
|
319
|
-
if (container.scrollTop > 0) {
|
|
320
|
-
setPullDistance(0);
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const dy = e.touches[0].clientY - startY.current;
|
|
324
|
-
if (dy > 0) {
|
|
325
|
-
const distance = Math.min(dy * 0.5, maxPull);
|
|
326
|
-
setPullDistance(distance);
|
|
327
|
-
}
|
|
328
|
-
}, [isPulling, isRefreshing, maxPull]);
|
|
329
|
-
const onTouchEnd = useCallback(() => {
|
|
330
|
-
if (!isPulling) return;
|
|
331
|
-
setIsPulling(false);
|
|
332
|
-
if (pullDistance >= threshold && !isRefreshing) {
|
|
333
|
-
setIsRefreshing(true);
|
|
334
|
-
setPullDistance(threshold);
|
|
335
|
-
onRefresh();
|
|
336
|
-
} else {
|
|
337
|
-
setPullDistance(0);
|
|
338
|
-
}
|
|
339
|
-
}, [isPulling, pullDistance, threshold, isRefreshing, onRefresh]);
|
|
340
|
-
const endRefresh = useCallback(() => {
|
|
341
|
-
setIsRefreshing(false);
|
|
342
|
-
setPullDistance(0);
|
|
343
|
-
}, []);
|
|
344
|
-
const containerProps = {
|
|
345
|
-
onTouchStart,
|
|
346
|
-
onTouchMove,
|
|
347
|
-
onTouchEnd,
|
|
348
|
-
style: {
|
|
349
|
-
transform: pullDistance > 0 ? `translateY(${pullDistance}px)` : void 0,
|
|
350
|
-
transition: isPulling ? "none" : "transform 300ms ease-out"
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
return {
|
|
354
|
-
pullDistance,
|
|
355
|
-
isPulling,
|
|
356
|
-
isRefreshing,
|
|
357
|
-
containerProps,
|
|
358
|
-
endRefresh
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
var SelectionContext = createContext(null);
|
|
362
|
-
var defaultCompareEntities = (a, b) => {
|
|
363
|
-
if (a === b) return true;
|
|
364
|
-
if (!a || !b) return false;
|
|
365
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
366
|
-
const aId = a.id;
|
|
367
|
-
const bId = b.id;
|
|
368
|
-
return aId !== void 0 && aId === bId;
|
|
369
|
-
}
|
|
370
|
-
return false;
|
|
371
|
-
};
|
|
372
|
-
function SelectionProvider({
|
|
373
|
-
children,
|
|
374
|
-
debug = false,
|
|
375
|
-
compareEntities = defaultCompareEntities
|
|
376
|
-
}) {
|
|
377
|
-
const eventBus = useEventBus();
|
|
378
|
-
const [selected, setSelectedState] = useState(null);
|
|
379
|
-
const setSelected = useCallback(
|
|
380
|
-
(entity) => {
|
|
381
|
-
setSelectedState(entity);
|
|
382
|
-
if (debug) {
|
|
383
|
-
console.log("[SelectionProvider] Selection set:", entity);
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
[debug]
|
|
387
|
-
);
|
|
388
|
-
const clearSelection = useCallback(() => {
|
|
389
|
-
setSelectedState(null);
|
|
390
|
-
if (debug) {
|
|
391
|
-
console.log("[SelectionProvider] Selection cleared");
|
|
392
|
-
}
|
|
393
|
-
}, [debug]);
|
|
394
|
-
const isSelected = useCallback(
|
|
395
|
-
(entity) => {
|
|
396
|
-
return compareEntities(selected, entity);
|
|
397
|
-
},
|
|
398
|
-
[selected, compareEntities]
|
|
399
|
-
);
|
|
400
|
-
useEffect(() => {
|
|
401
|
-
const handleSelect = (event) => {
|
|
402
|
-
const row = event.payload?.row;
|
|
403
|
-
if (row) {
|
|
404
|
-
setSelected(row);
|
|
405
|
-
if (debug) {
|
|
406
|
-
console.log(`[SelectionProvider] ${event.type} received:`, row);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
const handleDeselect = (event) => {
|
|
411
|
-
clearSelection();
|
|
412
|
-
if (debug) {
|
|
413
|
-
console.log(`[SelectionProvider] ${event.type} received - clearing selection`);
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
const unsubView = eventBus.on("UI:VIEW", handleSelect);
|
|
417
|
-
const unsubSelect = eventBus.on("UI:SELECT", handleSelect);
|
|
418
|
-
const unsubClose = eventBus.on("UI:CLOSE", handleDeselect);
|
|
419
|
-
const unsubDeselect = eventBus.on("UI:DESELECT", handleDeselect);
|
|
420
|
-
const unsubCancel = eventBus.on("UI:CANCEL", handleDeselect);
|
|
421
|
-
return () => {
|
|
422
|
-
unsubView();
|
|
423
|
-
unsubSelect();
|
|
424
|
-
unsubClose();
|
|
425
|
-
unsubDeselect();
|
|
426
|
-
unsubCancel();
|
|
427
|
-
};
|
|
428
|
-
}, [eventBus, setSelected, clearSelection, debug]);
|
|
429
|
-
const contextValue = {
|
|
430
|
-
selected,
|
|
431
|
-
setSelected,
|
|
432
|
-
clearSelection,
|
|
433
|
-
isSelected
|
|
434
|
-
};
|
|
435
|
-
return /* @__PURE__ */ jsx(SelectionContext.Provider, { value: contextValue, children });
|
|
436
|
-
}
|
|
437
|
-
function useSelection() {
|
|
438
|
-
const context = useContext(SelectionContext);
|
|
439
|
-
if (!context) {
|
|
440
|
-
throw new Error("useSelection must be used within a SelectionProvider");
|
|
441
|
-
}
|
|
442
|
-
return context;
|
|
443
|
-
}
|
|
444
|
-
function useSelectionOptional() {
|
|
445
|
-
const context = useContext(SelectionContext);
|
|
446
|
-
return context;
|
|
447
|
-
}
|
|
448
|
-
var EntityDataContext = createContext(null);
|
|
449
|
-
function EntityDataProvider({
|
|
450
|
-
adapter,
|
|
451
|
-
children
|
|
452
|
-
}) {
|
|
453
|
-
return React2.createElement(
|
|
454
|
-
EntityDataContext.Provider,
|
|
455
|
-
{ value: adapter },
|
|
456
|
-
children
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
function useEntityDataAdapter() {
|
|
460
|
-
return useContext(EntityDataContext);
|
|
461
|
-
}
|
|
462
|
-
var entityDataKeys = {
|
|
463
|
-
all: ["entities"],
|
|
464
|
-
lists: () => [...entityDataKeys.all, "list"],
|
|
465
|
-
list: (entity, filters) => [...entityDataKeys.lists(), entity, filters],
|
|
466
|
-
details: () => [...entityDataKeys.all, "detail"],
|
|
467
|
-
detail: (entity, id) => [...entityDataKeys.details(), entity, id]
|
|
468
|
-
};
|
|
469
|
-
function useEntityList(entity, options = {}) {
|
|
470
|
-
const { skip = false } = options;
|
|
471
|
-
const adapter = useContext(EntityDataContext);
|
|
472
|
-
const adapterData = useMemo(() => {
|
|
473
|
-
if (!adapter || !entity || skip) return [];
|
|
474
|
-
return adapter.getData(entity);
|
|
475
|
-
}, [adapter, entity, skip, adapter?.isLoading]);
|
|
476
|
-
const [stubData, setStubData] = useState([]);
|
|
477
|
-
const [stubLoading, setStubLoading] = useState(!skip && !!entity && !adapter);
|
|
478
|
-
const [stubError, setStubError] = useState(null);
|
|
479
|
-
useEffect(() => {
|
|
480
|
-
if (adapter || skip || !entity) {
|
|
481
|
-
setStubLoading(false);
|
|
482
|
-
return;
|
|
541
|
+
onPointerMove: handleDragMove(index),
|
|
542
|
+
onPointerUp: handleDragEnd,
|
|
543
|
+
"aria-dropeffect": "move",
|
|
544
|
+
"data-drag-index": String(index),
|
|
545
|
+
style: {
|
|
546
|
+
opacity: dragIndex === index ? 0.5 : 1,
|
|
547
|
+
transition: isDragging ? "transform 150ms ease" : void 0,
|
|
548
|
+
transform: isDragging && dragOverIndex >= 0 ? index === dragIndex ? "scale(1.02)" : index > dragIndex && index <= dragOverIndex ? "translateY(-100%)" : index < dragIndex && index >= dragOverIndex ? "translateY(100%)" : void 0 : void 0
|
|
483
549
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
data: adapterData,
|
|
494
|
-
isLoading: adapter.isLoading,
|
|
495
|
-
error: adapter.error ? new Error(adapter.error) : null,
|
|
496
|
-
refetch: () => {
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
return { data: stubData, isLoading: stubLoading, error: stubError, refetch: () => {
|
|
501
|
-
} };
|
|
550
|
+
}), [handleDragMove, handleDragEnd, dragIndex, dragOverIndex, isDragging]);
|
|
551
|
+
return {
|
|
552
|
+
items,
|
|
553
|
+
dragIndex,
|
|
554
|
+
dragOverIndex,
|
|
555
|
+
isDragging,
|
|
556
|
+
getDragHandleProps,
|
|
557
|
+
getItemProps
|
|
558
|
+
};
|
|
502
559
|
}
|
|
503
|
-
function
|
|
504
|
-
const
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
560
|
+
function useInfiniteScroll(onLoadMore, options = {}) {
|
|
561
|
+
const { rootMargin = "200px", hasMore = true, isLoading = false } = options;
|
|
562
|
+
const observerRef = useRef(null);
|
|
563
|
+
const callbackRef = useRef(onLoadMore);
|
|
564
|
+
callbackRef.current = onLoadMore;
|
|
565
|
+
const hasMoreRef = useRef(hasMore);
|
|
566
|
+
hasMoreRef.current = hasMore;
|
|
567
|
+
const isLoadingRef = useRef(isLoading);
|
|
568
|
+
isLoadingRef.current = isLoading;
|
|
512
569
|
useEffect(() => {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
setStubLoading(true);
|
|
518
|
-
const t = setTimeout(() => {
|
|
519
|
-
setStubData(null);
|
|
520
|
-
setStubLoading(false);
|
|
521
|
-
}, 100);
|
|
522
|
-
return () => clearTimeout(t);
|
|
523
|
-
}, [entity, id, adapter]);
|
|
524
|
-
if (adapter) {
|
|
525
|
-
return {
|
|
526
|
-
data: adapterData,
|
|
527
|
-
isLoading: adapter.isLoading,
|
|
528
|
-
error: adapter.error ? new Error(adapter.error) : null
|
|
570
|
+
return () => {
|
|
571
|
+
observerRef.current?.disconnect();
|
|
529
572
|
};
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
573
|
+
}, []);
|
|
574
|
+
const sentinelRef = useCallback((node) => {
|
|
575
|
+
observerRef.current?.disconnect();
|
|
576
|
+
if (!node) return;
|
|
577
|
+
observerRef.current = new IntersectionObserver(
|
|
578
|
+
(entries) => {
|
|
579
|
+
const entry = entries[0];
|
|
580
|
+
if (entry.isIntersecting && hasMoreRef.current && !isLoadingRef.current) {
|
|
581
|
+
callbackRef.current();
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
{ rootMargin }
|
|
585
|
+
);
|
|
586
|
+
observerRef.current.observe(node);
|
|
587
|
+
}, [rootMargin]);
|
|
588
|
+
return { sentinelRef };
|
|
541
589
|
}
|
|
542
|
-
function
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const check = setInterval(() => {
|
|
556
|
-
if (!adapter.isLoading) {
|
|
557
|
-
clearInterval(check);
|
|
558
|
-
entry2.status = "resolved";
|
|
559
|
-
resolve();
|
|
560
|
-
}
|
|
561
|
-
}, 50);
|
|
562
|
-
}
|
|
563
|
-
if (entry2.status === "pending") {
|
|
564
|
-
throw entry2.promise;
|
|
565
|
-
}
|
|
590
|
+
function usePullToRefresh(onRefresh, options = {}) {
|
|
591
|
+
const { threshold = 60, maxPull = 120 } = options;
|
|
592
|
+
const [pullDistance, setPullDistance] = useState(0);
|
|
593
|
+
const [isPulling, setIsPulling] = useState(false);
|
|
594
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
595
|
+
const startY = useRef(0);
|
|
596
|
+
const scrollTopRef = useRef(0);
|
|
597
|
+
const onTouchStart = useCallback((e) => {
|
|
598
|
+
const container = e.currentTarget;
|
|
599
|
+
scrollTopRef.current = container.scrollTop;
|
|
600
|
+
if (scrollTopRef.current <= 0) {
|
|
601
|
+
startY.current = e.touches[0].clientY;
|
|
602
|
+
setIsPulling(true);
|
|
566
603
|
}
|
|
567
|
-
|
|
568
|
-
|
|
604
|
+
}, []);
|
|
605
|
+
const onTouchMove = useCallback((e) => {
|
|
606
|
+
if (!isPulling || isRefreshing) return;
|
|
607
|
+
const container = e.currentTarget;
|
|
608
|
+
if (container.scrollTop > 0) {
|
|
609
|
+
setPullDistance(0);
|
|
610
|
+
return;
|
|
569
611
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
-
const cacheKey = getSuspenseCacheKey(entity, "list");
|
|
577
|
-
let entry = suspenseCache.get(cacheKey);
|
|
578
|
-
if (!entry) {
|
|
579
|
-
let resolve;
|
|
580
|
-
const promise = new Promise((r) => {
|
|
581
|
-
resolve = r;
|
|
582
|
-
setTimeout(() => {
|
|
583
|
-
entry.status = "resolved";
|
|
584
|
-
resolve();
|
|
585
|
-
}, 100);
|
|
586
|
-
});
|
|
587
|
-
entry = { promise, status: "pending" };
|
|
588
|
-
suspenseCache.set(cacheKey, entry);
|
|
589
|
-
}
|
|
590
|
-
if (entry.status === "pending") {
|
|
591
|
-
throw entry.promise;
|
|
592
|
-
}
|
|
593
|
-
return { data: [], refetch: () => {
|
|
594
|
-
} };
|
|
595
|
-
}
|
|
596
|
-
function useEntitySuspense(entity, id) {
|
|
597
|
-
const adapter = useContext(EntityDataContext);
|
|
598
|
-
if (adapter) {
|
|
599
|
-
if (adapter.isLoading) {
|
|
600
|
-
const cacheKey2 = getSuspenseCacheKey(entity, "detail", id);
|
|
601
|
-
let entry2 = suspenseCache.get(cacheKey2);
|
|
602
|
-
if (!entry2 || entry2.status === "resolved") {
|
|
603
|
-
let resolve;
|
|
604
|
-
const promise = new Promise((r) => {
|
|
605
|
-
resolve = r;
|
|
606
|
-
});
|
|
607
|
-
entry2 = { promise, status: "pending" };
|
|
608
|
-
suspenseCache.set(cacheKey2, entry2);
|
|
609
|
-
const check = setInterval(() => {
|
|
610
|
-
if (!adapter.isLoading) {
|
|
611
|
-
clearInterval(check);
|
|
612
|
-
entry2.status = "resolved";
|
|
613
|
-
resolve();
|
|
614
|
-
}
|
|
615
|
-
}, 50);
|
|
616
|
-
}
|
|
617
|
-
if (entry2.status === "pending") {
|
|
618
|
-
throw entry2.promise;
|
|
619
|
-
}
|
|
612
|
+
const dy = e.touches[0].clientY - startY.current;
|
|
613
|
+
if (dy > 0) {
|
|
614
|
+
const distance = Math.min(dy * 0.5, maxPull);
|
|
615
|
+
setPullDistance(distance);
|
|
620
616
|
}
|
|
621
|
-
|
|
622
|
-
|
|
617
|
+
}, [isPulling, isRefreshing, maxPull]);
|
|
618
|
+
const onTouchEnd = useCallback(() => {
|
|
619
|
+
if (!isPulling) return;
|
|
620
|
+
setIsPulling(false);
|
|
621
|
+
if (pullDistance >= threshold && !isRefreshing) {
|
|
622
|
+
setIsRefreshing(true);
|
|
623
|
+
setPullDistance(threshold);
|
|
624
|
+
onRefresh();
|
|
625
|
+
} else {
|
|
626
|
+
setPullDistance(0);
|
|
623
627
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
throw entry.promise;
|
|
646
|
-
}
|
|
647
|
-
return { data: null, refetch: () => {
|
|
648
|
-
} };
|
|
628
|
+
}, [isPulling, pullDistance, threshold, isRefreshing, onRefresh]);
|
|
629
|
+
const endRefresh = useCallback(() => {
|
|
630
|
+
setIsRefreshing(false);
|
|
631
|
+
setPullDistance(0);
|
|
632
|
+
}, []);
|
|
633
|
+
const containerProps = {
|
|
634
|
+
onTouchStart,
|
|
635
|
+
onTouchMove,
|
|
636
|
+
onTouchEnd,
|
|
637
|
+
style: {
|
|
638
|
+
transform: pullDistance > 0 ? `translateY(${pullDistance}px)` : void 0,
|
|
639
|
+
transition: isPulling ? "none" : "transform 300ms ease-out"
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
return {
|
|
643
|
+
pullDistance,
|
|
644
|
+
isPulling,
|
|
645
|
+
isRefreshing,
|
|
646
|
+
containerProps,
|
|
647
|
+
endRefresh
|
|
648
|
+
};
|
|
649
649
|
}
|
|
650
650
|
|
|
651
651
|
export { EntityDataProvider, I18nProvider, SelectionContext, SelectionProvider, createTranslate, entityDataKeys, parseQueryBinding, useDragReorder, useEntity, useEntityDataAdapter, useEntityDetail, useEntityList, useEntityListSuspense, useEntitySuspense, useInfiniteScroll, useLongPress, usePullToRefresh, useQuerySingleton, useSelection, useSelectionOptional, useSwipeGesture, useTranslate };
|