@arrai-innovations/reactive-helpers 10.4.2 → 11.0.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/.circleci/config.yml +3 -3
- package/README.md +120 -143
- package/config/listCrud.js +0 -1
- package/docs.md +178 -34
- package/package.json +3 -1
- package/tests/unit/use/listFilter.spec.js +195 -167
- package/tests/unit/use/listSearch.spec.js +505 -0
- package/tests/unit/use/listSort.spec.js +0 -1
- package/tests/unit/use/listSubscription.spec.js +91 -108
- package/tests/unit/use/search.spec.js +217 -36
- package/tests/unit/utils/assignReactiveObject.spec.js +0 -1
- package/tests/unit/{use → utils}/watches.spec.js +27 -3
- package/use/cancellableIntent.js +8 -3
- package/use/index.js +2 -0
- package/use/list.js +41 -14
- package/use/listCalculated.js +22 -19
- package/use/listFilter.js +128 -154
- package/use/listInstance.js +0 -28
- package/use/listKeys.js +94 -0
- package/use/listRelated.js +22 -14
- package/use/listSearch.js +358 -0
- package/use/listSort.js +30 -42
- package/use/listSubscription.js +3 -13
- package/use/search.js +154 -64
- package/utils/assignReactiveObject.js +58 -14
- package/utils/index.js +1 -0
- package/utils/keyDiff.js +13 -7
- package/utils/relatedCalculatedHelpers.js +17 -0
- package/utils/watches.js +14 -11
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import { difference, loadingCombine, assignReactiveObject, keyDiff } from "../utils/index.js";
|
|
2
|
+
import { getObjectRelatedCalculatedByKey } from "../utils/relatedCalculatedHelpers.js";
|
|
3
|
+
import {
|
|
4
|
+
listInstanceStateKeys,
|
|
5
|
+
listSubscriptionStateKeys,
|
|
6
|
+
listRelatedStateKeys,
|
|
7
|
+
listCalculatedStateKeys,
|
|
8
|
+
listSortStateKeys,
|
|
9
|
+
listFilterStateKeys,
|
|
10
|
+
listSearchStateKeys,
|
|
11
|
+
} from "./listKeys.js";
|
|
12
|
+
import { useSearch } from "./search.js";
|
|
13
|
+
import get from "lodash-es/get.js";
|
|
14
|
+
import isEqual from "lodash-es/isEqual.js";
|
|
15
|
+
import { reactive, effectScope, toRef, computed, watch, unref, onScopeDispose } from "vue";
|
|
16
|
+
import { deepUnref } from "vue-deepunref";
|
|
17
|
+
|
|
18
|
+
const parentStateKeys = difference(
|
|
19
|
+
new Set([
|
|
20
|
+
...listInstanceStateKeys,
|
|
21
|
+
...listSubscriptionStateKeys,
|
|
22
|
+
...listRelatedStateKeys,
|
|
23
|
+
...listCalculatedStateKeys,
|
|
24
|
+
...listSortStateKeys,
|
|
25
|
+
...listFilterStateKeys,
|
|
26
|
+
]),
|
|
27
|
+
new Set(listSearchStateKeys)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/* eslint-disable jsdoc/check-types */
|
|
31
|
+
// types valid for jsdoc-to-markdown, which uses the strict jsdoc.app. Object shorthand syntax doesn't work.
|
|
32
|
+
/**
|
|
33
|
+
* A Vue composition function that creates multiple list instances, and returns them as an object.
|
|
34
|
+
* @param {Object.<string, ListSearchInstanceOptions>} listInstanceArgs - each desired list instance options, keyed by an instance name.
|
|
35
|
+
* @returns {Object.<string, ListSearchInstance>} - each list instance, keyed by the instance name.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/* eslint-enable jsdoc/check-types */
|
|
39
|
+
export function useListSearches(args, instances) {
|
|
40
|
+
const searches = {};
|
|
41
|
+
for (const [key, value] of Object.entries(args)) {
|
|
42
|
+
searches[key] = useListSearch({ parentState: instances[key].state, ...value });
|
|
43
|
+
}
|
|
44
|
+
return searches;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} ListSearchProps
|
|
49
|
+
* @property {array} textSearchRules - rules for what to search for. Keys are the keys to search for, values are functions that take the object and return the value to search for.
|
|
50
|
+
* @property {string} textSearchValue - the value to search for.
|
|
51
|
+
* @property {object} customDocumentOptions - FlexSearch.Document options
|
|
52
|
+
* @property {object} customSearchOptions - FlexSearch.Search options
|
|
53
|
+
* @property {object} [customSearchOptions.limit=1000] - FlexSearch.Search options
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {object} ListSearchInstance
|
|
58
|
+
* @property {object} state - the state
|
|
59
|
+
* @property {SearchInstance} textSearchIndex - the text search index
|
|
60
|
+
* @property {object} effectScope - a Vue effect scope
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {object} ListSearchInstanceOptions
|
|
65
|
+
* @property {object} parentState - the list being filtered
|
|
66
|
+
* @property {ListSearchProps} props - reactive properties
|
|
67
|
+
* @property {number} [throttle=500] - throttle wait time
|
|
68
|
+
* @property {boolean} [showAllWhenEmpty=true] - whether to show all items when the search is empty
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Text filter for list items. This will not be performant for large lists, as each item will be watched.
|
|
73
|
+
* However, the results will reactively update.
|
|
74
|
+
* @param {ListSearchInstanceOptions} options - the arguments
|
|
75
|
+
* @returns {ListSearchInstance} - the instance
|
|
76
|
+
*/
|
|
77
|
+
export function useListSearch({ parentState, props, throttle = 500, showAllWhenEmpty = true }) {
|
|
78
|
+
if (!parentState) {
|
|
79
|
+
throw new Error("parentState is required");
|
|
80
|
+
}
|
|
81
|
+
const state = reactive({
|
|
82
|
+
objects: {},
|
|
83
|
+
objectsInOrder: [],
|
|
84
|
+
order: [],
|
|
85
|
+
textSearchRules: toRef(props, "textSearchRules"),
|
|
86
|
+
textSearchValue: toRef(props, "textSearchValue"),
|
|
87
|
+
objectIndexes: {},
|
|
88
|
+
updateSearchIndexesRunning: undefined,
|
|
89
|
+
customDocumentOptions: toRef(props, "customDocumentOptions"),
|
|
90
|
+
customSearchOptions: toRef(props, "customSearchOptions"),
|
|
91
|
+
searched: undefined,
|
|
92
|
+
running: undefined,
|
|
93
|
+
newSearchComputeds: undefined,
|
|
94
|
+
});
|
|
95
|
+
if (!state.customDocumentOptions) {
|
|
96
|
+
state.customDocumentOptions = {};
|
|
97
|
+
}
|
|
98
|
+
if (!state.customSearchOptions) {
|
|
99
|
+
state.customSearchOptions = {};
|
|
100
|
+
}
|
|
101
|
+
const textSearchIndexProps = reactive({
|
|
102
|
+
customDocumentOptions: computed(() => {
|
|
103
|
+
const options = {
|
|
104
|
+
tokenize: "forward",
|
|
105
|
+
minlength: 2,
|
|
106
|
+
...state.customDocumentOptions, // todo: not sure if this is ok inside a computed
|
|
107
|
+
};
|
|
108
|
+
if (!options.document) {
|
|
109
|
+
options.document = {
|
|
110
|
+
id: "id",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
options.document.index = state.textSearchRules;
|
|
114
|
+
return options;
|
|
115
|
+
}),
|
|
116
|
+
customSearchOptions: computed(() => ({
|
|
117
|
+
...state.customSearchOptions, // todo: not sure if this is ok inside a computed
|
|
118
|
+
limit: state.customSearchOptions.limit ?? 1000,
|
|
119
|
+
})),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const es = effectScope();
|
|
123
|
+
|
|
124
|
+
let textSearchIndex;
|
|
125
|
+
|
|
126
|
+
const objectEffectScopes = {};
|
|
127
|
+
const objectComputeds = {};
|
|
128
|
+
|
|
129
|
+
const previousTextSearchRules = [];
|
|
130
|
+
const previousObjectIndexes = {};
|
|
131
|
+
|
|
132
|
+
const doPassthrough = (cleanComputed = false) => {
|
|
133
|
+
// pass through the objects if there are no rules.
|
|
134
|
+
assignReactiveObject(state.objects, showAllWhenEmpty ? parentState.objects : {});
|
|
135
|
+
if (!cleanComputed) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// if there were indexes or computeds, there is no point in keeping them.
|
|
139
|
+
for (const objectKey of Object.keys(objectEffectScopes)) {
|
|
140
|
+
objectEffectScopes[objectKey].stop();
|
|
141
|
+
}
|
|
142
|
+
assignReactiveObject(objectEffectScopes, {});
|
|
143
|
+
assignReactiveObject(objectComputeds, {});
|
|
144
|
+
assignReactiveObject(state.objectIndexes, {});
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const makeComputeds = () => {
|
|
148
|
+
if (!state.textSearchRules?.length) {
|
|
149
|
+
doPassthrough(true);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const {
|
|
153
|
+
addedKeys: addedObjectIds,
|
|
154
|
+
removedKeys: removedObjectIds,
|
|
155
|
+
sameKeys: sameObjectIds,
|
|
156
|
+
} = keyDiff(Object.keys(parentState.objects), Object.keys(state.objects));
|
|
157
|
+
const { addedKeys: addedTextSearchRules, removedKeys: removedTextSearchRules } = keyDiff(
|
|
158
|
+
state.textSearchRules,
|
|
159
|
+
previousTextSearchRules
|
|
160
|
+
);
|
|
161
|
+
for (const removedObjectId of removedObjectIds) {
|
|
162
|
+
delete state.objectIndexes[removedObjectId];
|
|
163
|
+
// the effect scope will be stopped when the object is removed.
|
|
164
|
+
delete objectComputeds[removedObjectId];
|
|
165
|
+
if (objectEffectScopes[removedObjectId]) {
|
|
166
|
+
objectEffectScopes[removedObjectId].stop();
|
|
167
|
+
delete objectEffectScopes[removedObjectId];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const addedObjectId of addedObjectIds) {
|
|
171
|
+
state.objectIndexes[addedObjectId] = { id: addedObjectId };
|
|
172
|
+
objectComputeds[addedObjectId] = {};
|
|
173
|
+
objectEffectScopes[addedObjectId] = effectScope();
|
|
174
|
+
const objectEffectScope = objectEffectScopes[addedObjectId];
|
|
175
|
+
const objectRef = toRef(parentState.objects, addedObjectId);
|
|
176
|
+
const relatedRef = parentState.relatedObjects
|
|
177
|
+
? toRef(parentState.relatedObjects, addedObjectId)
|
|
178
|
+
: undefined;
|
|
179
|
+
const calculatedRef = parentState.calculatedObjects
|
|
180
|
+
? toRef(parentState.calculatedObjects, addedObjectId)
|
|
181
|
+
: undefined;
|
|
182
|
+
objectEffectScope.run(() => {
|
|
183
|
+
for (const rule of state.textSearchRules || []) {
|
|
184
|
+
const [obj, key] = getObjectRelatedCalculatedByKey(objectRef, relatedRef, calculatedRef, rule);
|
|
185
|
+
state.newSearchComputeds = true;
|
|
186
|
+
state.objectIndexes[addedObjectId][rule] = objectComputeds[addedObjectId][rule] = computed(() => {
|
|
187
|
+
return get(unref(obj), key);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
for (const sameObjectId of sameObjectIds) {
|
|
193
|
+
const objectEffectScope = objectEffectScopes[sameObjectId];
|
|
194
|
+
const objectRef = toRef(parentState.objects, sameObjectId);
|
|
195
|
+
const relatedRef = parentState.relatedObjects ? toRef(parentState.relatedObjects, sameObjectId) : undefined;
|
|
196
|
+
const calculatedRef = parentState.calculatedObjects
|
|
197
|
+
? toRef(parentState.calculatedObjects, sameObjectId)
|
|
198
|
+
: undefined;
|
|
199
|
+
for (const key of removedTextSearchRules) {
|
|
200
|
+
delete state.objectIndexes[sameObjectId][key];
|
|
201
|
+
// stop a computed earlier than the effect scope
|
|
202
|
+
objectComputeds[sameObjectId][key].effect.stop();
|
|
203
|
+
delete objectComputeds[sameObjectId][key];
|
|
204
|
+
}
|
|
205
|
+
objectEffectScope.run(() => {
|
|
206
|
+
for (const rule of addedTextSearchRules) {
|
|
207
|
+
const [obj, key] = getObjectRelatedCalculatedByKey(objectRef, relatedRef, calculatedRef, rule);
|
|
208
|
+
state.newSearchComputeds = true;
|
|
209
|
+
state.objectIndexes[sameObjectId][rule] = objectComputeds[sameObjectId][rule] = computed(() => {
|
|
210
|
+
return get(unref(obj), key);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
previousTextSearchRules.length = 0;
|
|
216
|
+
if (state.textSearchRules?.length) {
|
|
217
|
+
previousTextSearchRules.push(...state.textSearchRules);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const updateSearchIndexes = async () => {
|
|
222
|
+
if (state.updateSearchIndexesRunning === undefined) {
|
|
223
|
+
state.updateSearchIndexesRunning = 0;
|
|
224
|
+
}
|
|
225
|
+
state.updateSearchIndexesRunning++;
|
|
226
|
+
try {
|
|
227
|
+
const { addedKeys, removedKeys, sameKeys } = keyDiff(
|
|
228
|
+
Object.keys(state.objectIndexes),
|
|
229
|
+
Object.keys(previousObjectIndexes)
|
|
230
|
+
);
|
|
231
|
+
const promises = [];
|
|
232
|
+
for (const removedKey of removedKeys) {
|
|
233
|
+
promises.push(textSearchIndex.removeIndex(removedKey));
|
|
234
|
+
delete previousObjectIndexes[removedKey];
|
|
235
|
+
}
|
|
236
|
+
for (const addedKey of addedKeys) {
|
|
237
|
+
promises.push(textSearchIndex.addIndex(state.objectIndexes[addedKey]));
|
|
238
|
+
previousObjectIndexes[addedKey] = deepUnref(state.objectIndexes[addedKey]);
|
|
239
|
+
}
|
|
240
|
+
for (const sameKey of sameKeys) {
|
|
241
|
+
if (!isEqual(previousObjectIndexes[sameKey], state.objectIndexes[sameKey])) {
|
|
242
|
+
promises.push(textSearchIndex.updateIndex(state.objectIndexes[sameKey]));
|
|
243
|
+
previousObjectIndexes[sameKey] = deepUnref(state.objectIndexes[sameKey]);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (promises.length) {
|
|
247
|
+
await Promise.all(promises);
|
|
248
|
+
}
|
|
249
|
+
} finally {
|
|
250
|
+
if (state.newSearchComputeds) {
|
|
251
|
+
state.newSearchComputeds = false;
|
|
252
|
+
}
|
|
253
|
+
state.updateSearchIndexesRunning--;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const updateObjectsForResults = () => {
|
|
258
|
+
if (!state.textSearchRules?.length || !state.textSearchValue?.length) {
|
|
259
|
+
doPassthrough();
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
assignReactiveObject(
|
|
263
|
+
state.objects,
|
|
264
|
+
Object.fromEntries(
|
|
265
|
+
Object.entries(textSearchIndex.state.results)
|
|
266
|
+
.filter(([, value]) => !!value)
|
|
267
|
+
.map(([id]) => [id, toRef(parentState.objects, id)])
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const updateOrder = () => {
|
|
273
|
+
assignReactiveObject(
|
|
274
|
+
state.objectsInOrder,
|
|
275
|
+
parentState.order.filter((id) => !!state.objects[id]).map((id) => toRef(state.objects, id))
|
|
276
|
+
);
|
|
277
|
+
assignReactiveObject(
|
|
278
|
+
state.order,
|
|
279
|
+
parentState.order.filter((id) => !!state.objects[id])
|
|
280
|
+
);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
let firstIndexWasCleared = false;
|
|
284
|
+
|
|
285
|
+
const indexWasCleared = async () => {
|
|
286
|
+
// skip the first time, preventing clearing the index after makeComputeds already ran.
|
|
287
|
+
if (firstIndexWasCleared) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
firstIndexWasCleared = true;
|
|
291
|
+
assignReactiveObject(previousObjectIndexes, {});
|
|
292
|
+
await makeComputeds();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
let watchesRunning;
|
|
296
|
+
|
|
297
|
+
es.run(() => {
|
|
298
|
+
for (const key of parentStateKeys) {
|
|
299
|
+
state[key] = toRef(parentState, key);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
textSearchIndex = useSearch({
|
|
303
|
+
props: textSearchIndexProps,
|
|
304
|
+
throttle,
|
|
305
|
+
});
|
|
306
|
+
textSearchIndex.state.search = toRef(state, "textSearchValue");
|
|
307
|
+
textSearchIndex.events.addEventListener("newIndex", indexWasCleared);
|
|
308
|
+
state.searched = toRef(() => textSearchIndex.state.searched);
|
|
309
|
+
state.running = computed(() => {
|
|
310
|
+
return loadingCombine(parentState.running, state.newSearchComputeds, textSearchIndex.state.running);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
watch([() => Object.keys(parentState.objects), toRef(state.textSearchRules)], makeComputeds, {
|
|
314
|
+
immediate: true,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
watch(
|
|
318
|
+
toRef(state, "objectIndexes"),
|
|
319
|
+
() => {
|
|
320
|
+
updateSearchIndexes();
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
deep: true,
|
|
324
|
+
immediate: true,
|
|
325
|
+
}
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
watch(
|
|
329
|
+
[
|
|
330
|
+
toRef(state, "textSearchValue"),
|
|
331
|
+
() => Object.keys(textSearchIndex.state.results),
|
|
332
|
+
toRef(textSearchIndex.state, "running"),
|
|
333
|
+
],
|
|
334
|
+
updateObjectsForResults,
|
|
335
|
+
{
|
|
336
|
+
immediate: true,
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
watch(toRef(parentState, "order"), updateOrder, {
|
|
341
|
+
immediate: true,
|
|
342
|
+
deep: true,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
onScopeDispose(() => {
|
|
346
|
+
for (const objectKey of Object.keys(objectEffectScopes)) {
|
|
347
|
+
objectEffectScopes[objectKey].stop();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
state,
|
|
354
|
+
effectScope: es,
|
|
355
|
+
textSearchIndex,
|
|
356
|
+
watchesRunning,
|
|
357
|
+
};
|
|
358
|
+
}
|
package/use/listSort.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { assignReactiveObject, keyDiff, loadingCombine } from "../utils/index.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { assignReactiveObject, keyDiff, loadingCombine, difference } from "../utils/index.js";
|
|
2
|
+
import {
|
|
3
|
+
listSortStateKeys,
|
|
4
|
+
listFilterStateKeys,
|
|
5
|
+
listRelatedStateKeys,
|
|
6
|
+
listCalculatedStateKeys,
|
|
7
|
+
listSubscriptionStateKeys,
|
|
8
|
+
listInstanceStateKeys,
|
|
9
|
+
listSearchStateKeys,
|
|
10
|
+
} from "./listKeys.js";
|
|
7
11
|
import { useWatchesRunning } from "./watchesRunning.js";
|
|
8
12
|
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
9
13
|
import get from "lodash-es/get.js";
|
|
@@ -16,21 +20,6 @@ import throttle from "lodash-es/throttle.js";
|
|
|
16
20
|
import zip from "lodash-es/zip.js";
|
|
17
21
|
import { effectScope, reactive, toRef, unref, watch, computed } from "vue";
|
|
18
22
|
|
|
19
|
-
export const listSortStateKeys = [
|
|
20
|
-
"orderByRules",
|
|
21
|
-
// "order",
|
|
22
|
-
// "objectsInOrder",
|
|
23
|
-
"sortCriteria",
|
|
24
|
-
"sortCriteriaEffectScopes",
|
|
25
|
-
"orderByDesc",
|
|
26
|
-
"sortCriteriaWatchRunning",
|
|
27
|
-
"sortWatchRunning",
|
|
28
|
-
"outstandingEffects",
|
|
29
|
-
// "running",
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
export const listSortFunctions = [];
|
|
33
|
-
|
|
34
23
|
const collator = new Intl.Collator(undefined, { numeric: true });
|
|
35
24
|
|
|
36
25
|
const defaultSortThrottleWait = Symbol("defaultSortThrottleWait");
|
|
@@ -39,6 +28,18 @@ const defaultOptions = {
|
|
|
39
28
|
sortThrottleWait: 100,
|
|
40
29
|
};
|
|
41
30
|
|
|
31
|
+
const parentStateKeys = difference(
|
|
32
|
+
new Set([
|
|
33
|
+
...listInstanceStateKeys,
|
|
34
|
+
...listSubscriptionStateKeys,
|
|
35
|
+
...listRelatedStateKeys,
|
|
36
|
+
...listCalculatedStateKeys,
|
|
37
|
+
...listFilterStateKeys,
|
|
38
|
+
...listSearchStateKeys,
|
|
39
|
+
]),
|
|
40
|
+
new Set(listSortStateKeys)
|
|
41
|
+
);
|
|
42
|
+
|
|
42
43
|
export function setListSortDefaultOptions({ sortThrottleWait }) {
|
|
43
44
|
defaultOptions.sortThrottleWait = sortThrottleWait;
|
|
44
45
|
}
|
|
@@ -55,12 +56,14 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
55
56
|
if (sortThrottleWait === defaultSortThrottleWait) {
|
|
56
57
|
sortThrottleWait = defaultOptions.sortThrottleWait;
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
const sortCriteriaEffectScopes = {};
|
|
61
|
+
|
|
58
62
|
const state = reactive({
|
|
59
63
|
orderByRules,
|
|
60
64
|
order: [],
|
|
61
65
|
objectsInOrder: [],
|
|
62
66
|
sortCriteria: {},
|
|
63
|
-
sortCriteriaEffectScopes: {},
|
|
64
67
|
orderByDesc: [],
|
|
65
68
|
sortCriteriaWatchRunning: false,
|
|
66
69
|
sortWatchRunning: false,
|
|
@@ -69,16 +72,16 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
69
72
|
const es = effectScope();
|
|
70
73
|
|
|
71
74
|
function removeSortCriteria(removedKey) {
|
|
72
|
-
const oldScope =
|
|
75
|
+
const oldScope = sortCriteriaEffectScopes[removedKey];
|
|
73
76
|
if (oldScope) {
|
|
74
77
|
oldScope.stop();
|
|
75
|
-
delete
|
|
78
|
+
delete sortCriteriaEffectScopes[removedKey];
|
|
76
79
|
}
|
|
77
80
|
delete state.sortCriteria[removedKey];
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
function addSortCriteria(object, relatedObject, calculatedObject, key) {
|
|
81
|
-
const oldScope =
|
|
84
|
+
const oldScope = sortCriteriaEffectScopes[key];
|
|
82
85
|
if (oldScope) {
|
|
83
86
|
oldScope.stop();
|
|
84
87
|
}
|
|
@@ -123,7 +126,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
123
126
|
}
|
|
124
127
|
);
|
|
125
128
|
});
|
|
126
|
-
|
|
129
|
+
sortCriteriaEffectScopes[key] = newScope;
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
function sortCriteriaWatch() {
|
|
@@ -220,22 +223,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
220
223
|
let watchesRunning = null;
|
|
221
224
|
|
|
222
225
|
es.run(() => {
|
|
223
|
-
for (const key of
|
|
224
|
-
if (["order", "objectsInOrder"].includes(key)) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
state[key] = toRef(parentState, key);
|
|
228
|
-
}
|
|
229
|
-
for (const key of listSubscriptionStateKeys) {
|
|
230
|
-
state[key] = toRef(parentState, key);
|
|
231
|
-
}
|
|
232
|
-
for (const key of listRelatedStateKeys) {
|
|
233
|
-
state[key] = toRef(parentState, key);
|
|
234
|
-
}
|
|
235
|
-
for (const key of listCalculatedStateKeys) {
|
|
236
|
-
state[key] = toRef(parentState, key);
|
|
237
|
-
}
|
|
238
|
-
for (const key of listFilterStateKeys) {
|
|
226
|
+
for (const key of parentStateKeys) {
|
|
239
227
|
state[key] = toRef(parentState, key);
|
|
240
228
|
}
|
|
241
229
|
// this watch must come first or be immediate.
|
package/use/listSubscription.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { loadingCombine } from "../utils/loadingCombine.js";
|
|
2
2
|
import { useCancellableIntent } from "./cancellableIntent.js";
|
|
3
|
-
import {
|
|
3
|
+
import { useListInstance } from "./listInstance.js";
|
|
4
|
+
import { listInstanceStateKeys } from "./listKeys.js";
|
|
4
5
|
import inspect from "browser-util-inspect";
|
|
5
6
|
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
6
7
|
import isEmpty from "lodash-es/isEmpty.js";
|
|
@@ -14,17 +15,6 @@ export class ListSubscriptionError extends Error {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
export const listSubscriptionStateKeys = [
|
|
18
|
-
"subscriptionLoading",
|
|
19
|
-
"subscriptionErrored",
|
|
20
|
-
"subscriptionError",
|
|
21
|
-
"intendToList",
|
|
22
|
-
"intendToSubscribe",
|
|
23
|
-
"subscribed",
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
export const listSubscriptionFunctions = ["subscribe", "unsubscribe", "clearError"];
|
|
27
|
-
|
|
28
18
|
/**
|
|
29
19
|
* The configuration options used to create a list subscription.
|
|
30
20
|
* @typedef {object} ListSubscriptionOptions
|
|
@@ -190,7 +180,7 @@ export function useListSubscription({ listInstance, props, functions, keepOldPag
|
|
|
190
180
|
listInstance.deleteListObject(id);
|
|
191
181
|
} catch (err) {
|
|
192
182
|
if (err.name === "ListError" && err.code === "missing-object") {
|
|
193
|
-
console.warn(`deleteFromSubscription: delete for id not in objects (${id}).`);
|
|
183
|
+
console.warn(`deleteFromSubscription: delete for id not in objects (${inspect(id)}).`);
|
|
194
184
|
return;
|
|
195
185
|
}
|
|
196
186
|
throw err;
|