@arrai-innovations/reactive-helpers 10.4.2 → 11.0.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/.circleci/config.yml +3 -3
- package/README.md +123 -146
- package/config/listCrud.js +0 -1
- package/docs.md +178 -34
- package/package.json +3 -1
- package/tests/unit/use/listFilter.spec.js +335 -165
- package/tests/unit/use/listInstance.spec.js +14 -1
- package/tests/unit/use/listSearch.spec.js +505 -0
- package/tests/unit/use/listSort.spec.js +17 -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 +43 -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 +146 -152
- package/use/listInstance.js +26 -31
- package/use/listKeys.js +99 -0
- package/use/listRelated.js +22 -14
- package/use/listSearch.js +357 -0
- package/use/listSort.js +46 -49
- package/use/listSubscription.js +3 -13
- package/use/search.js +154 -64
- package/use/watchesRunning.js +1 -1
- 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
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;
|
package/use/search.js
CHANGED
|
@@ -1,105 +1,195 @@
|
|
|
1
1
|
import { assignReactiveObject } from "../utils/assignReactiveObject.js";
|
|
2
|
+
import asyncThrottle from "@jcoreio/async-throttle";
|
|
2
3
|
import FlexSearch from "flexsearch";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { effectScope, reactive, toRef, watch } from "vue";
|
|
4
|
+
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
5
|
+
import isEqual from "lodash-es/isEqual.js";
|
|
6
|
+
import { effectScope, reactive, toRef, watch, computed } from "vue";
|
|
7
|
+
import { deepUnref } from "vue-deepunref";
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
/* minimize new Set() allocations */
|
|
10
|
+
const unionReduce = (accumulator, currentValue) => {
|
|
11
|
+
for (const elem of currentValue) {
|
|
12
|
+
accumulator.add(elem);
|
|
13
|
+
}
|
|
14
|
+
return accumulator;
|
|
9
15
|
};
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
17
|
+
/**
|
|
18
|
+
* FlexSearch.Document search options
|
|
19
|
+
* @typedef {object} SearchOptions
|
|
20
|
+
* @property {number} limit - limit of results
|
|
21
|
+
*/
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
// eslint-disable-next-line jsdoc/require-property
|
|
24
|
+
/**
|
|
25
|
+
* FlexSearch.Document options
|
|
26
|
+
* @typedef {object} DocumentOptions
|
|
27
|
+
*/
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {object} SearchState
|
|
31
|
+
* @property {string} search - the search string
|
|
32
|
+
* @property {object} results - the results, where the keys are the ids of the objects that match, and the values are true
|
|
33
|
+
* @property {boolean} searched - whether the search has been performed
|
|
34
|
+
* @property {boolean} searching - whether the search is currently running
|
|
35
|
+
*/
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {object} SearchInstance
|
|
39
|
+
* @property {SearchState} state - the state
|
|
40
|
+
* @property {function} addIndex - add an index
|
|
41
|
+
* @property {function} updateIndex - update an index
|
|
42
|
+
* @property {function} removeIndex - remove an index
|
|
43
|
+
* @property {function} clearIndex - clear the index
|
|
44
|
+
* @property {object} effectScope - a Vue effect scope
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
const defaultDocumentOptions = {
|
|
48
|
+
tokenize: "forward",
|
|
49
|
+
document: {
|
|
50
|
+
id: "id",
|
|
51
|
+
index: ["name"],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A reactive object for passing document options or search options to useSearch
|
|
57
|
+
* @typedef {object} SearchProps
|
|
58
|
+
* @property {DocumentOptions} customDocumentOptions - FlexSearch.Document options
|
|
59
|
+
* @property {SearchOptions} customSearchOptions - search options
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A reactive wrapper around FlexSearch.Index
|
|
64
|
+
* @param {object} options - options
|
|
65
|
+
* @param {SearchProps} options.props - props
|
|
66
|
+
* @param {number} [options.throttle] - throttle wait time
|
|
67
|
+
* @returns {SearchInstance} - the instance
|
|
68
|
+
*/
|
|
69
|
+
export function useSearch({ props, throttle = 500 }) {
|
|
70
|
+
let searchIndex;
|
|
71
|
+
const events = new EventTarget();
|
|
36
72
|
const state = reactive({
|
|
37
73
|
search: "",
|
|
38
74
|
results: {},
|
|
39
75
|
searched: false,
|
|
40
76
|
searching: false,
|
|
77
|
+
customDocumentOptions: toRef(props, "customDocumentOptions"),
|
|
78
|
+
customSearchOptions: toRef(props, "customSearchOptions"),
|
|
79
|
+
called: 0,
|
|
80
|
+
pending: 0,
|
|
81
|
+
running: computed(() => state.searching || state.pending > state.called),
|
|
41
82
|
});
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
83
|
+
const es = effectScope();
|
|
84
|
+
const addIndex = async (indexValue) => {
|
|
85
|
+
++state.pending;
|
|
86
|
+
const unrefedIndexValue = deepUnref(indexValue);
|
|
87
|
+
try {
|
|
88
|
+
await searchIndex.addAsync(unrefedIndexValue);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
++state.called;
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
47
93
|
throttledDoSearch();
|
|
48
94
|
};
|
|
49
|
-
const updateIndex = (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
95
|
+
const updateIndex = async (indexValue) => {
|
|
96
|
+
++state.pending;
|
|
97
|
+
try {
|
|
98
|
+
await searchIndex.updateAsync(deepUnref(indexValue));
|
|
99
|
+
} catch (e) {
|
|
100
|
+
++state.called;
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
54
103
|
throttledDoSearch();
|
|
55
104
|
};
|
|
56
|
-
const removeIndex = (id) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
105
|
+
const removeIndex = async (id) => {
|
|
106
|
+
++state.pending;
|
|
107
|
+
try {
|
|
108
|
+
await searchIndex.removeAsync(id);
|
|
109
|
+
} catch (e) {
|
|
110
|
+
++state.called;
|
|
111
|
+
throw e;
|
|
112
|
+
}
|
|
61
113
|
throttledDoSearch();
|
|
62
114
|
};
|
|
115
|
+
|
|
116
|
+
let previousCustomDocumentOptions = null;
|
|
117
|
+
|
|
118
|
+
const customDocumentOptionsWatchHandler = () => {
|
|
119
|
+
const unrefCustomDocumentOptions = deepUnref(state.customDocumentOptions);
|
|
120
|
+
if (!isEqual(previousCustomDocumentOptions, unrefCustomDocumentOptions)) {
|
|
121
|
+
clearIndex();
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const makeIndex = () => {
|
|
126
|
+
const unrefCustomDocumentOptions = deepUnref(state.customDocumentOptions) || cloneDeep(defaultDocumentOptions);
|
|
127
|
+
previousCustomDocumentOptions = unrefCustomDocumentOptions;
|
|
128
|
+
searchIndex = new FlexSearch.Document(unrefCustomDocumentOptions);
|
|
129
|
+
};
|
|
130
|
+
|
|
63
131
|
const clearIndex = () => {
|
|
64
|
-
|
|
132
|
+
makeIndex();
|
|
65
133
|
assignReactiveObject(state.results, {});
|
|
66
|
-
|
|
134
|
+
events.dispatchEvent(new Event("newIndex"));
|
|
67
135
|
};
|
|
68
136
|
|
|
69
137
|
async function doSearch() {
|
|
70
|
-
if (
|
|
138
|
+
if (
|
|
139
|
+
!state.search ||
|
|
140
|
+
(state.customDocumentOptions?.minlength && state.search.length < state.customDocumentOptions.minlength)
|
|
141
|
+
) {
|
|
71
142
|
assignReactiveObject(state.results, {});
|
|
72
143
|
state.searched = false;
|
|
73
|
-
|
|
144
|
+
// if there was nothing to do the first time, changing from undefined to false will still let
|
|
145
|
+
// users know that we are done.
|
|
146
|
+
} else {
|
|
147
|
+
const results = searchIndex.search(state.search, {
|
|
148
|
+
limit: state.customSearchOptions?.limit || 1000,
|
|
149
|
+
});
|
|
150
|
+
const resultsSet = results.map((r) => r.result).reduce(unionReduce, new Set());
|
|
151
|
+
if (!state.searched) {
|
|
152
|
+
state.searched = true;
|
|
153
|
+
}
|
|
154
|
+
assignReactiveObject(state.results, Object.fromEntries(Array.from(resultsSet).map((e) => [e, true])) || {});
|
|
155
|
+
}
|
|
156
|
+
if (state.pending - state.called <= 0) {
|
|
157
|
+
state.searching = false;
|
|
74
158
|
}
|
|
75
|
-
state.searching = true;
|
|
76
|
-
const results = await searchIndex.searchAsync(state.search, {
|
|
77
|
-
limit: mySearchOptions.limit,
|
|
78
|
-
});
|
|
79
|
-
state.searched = true;
|
|
80
|
-
assignReactiveObject(state.results, fromPairs(results.map((e) => [e, true])) || {});
|
|
81
|
-
state.searching = false;
|
|
82
159
|
}
|
|
83
160
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
161
|
+
const _throttledDoSearch = asyncThrottle(doSearch, throttle);
|
|
162
|
+
const throttledDoSearch = () => {
|
|
163
|
+
++state.called;
|
|
164
|
+
if (!state.searching) {
|
|
165
|
+
state.searching = true;
|
|
166
|
+
}
|
|
167
|
+
return _throttledDoSearch();
|
|
168
|
+
};
|
|
87
169
|
|
|
88
170
|
es.run(() => {
|
|
171
|
+
watch(toRef(state, "customDocumentOptions"), customDocumentOptionsWatchHandler, {
|
|
172
|
+
deep: true,
|
|
173
|
+
});
|
|
174
|
+
makeIndex();
|
|
89
175
|
watch(
|
|
90
176
|
toRef(state, "search"),
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
} else {
|
|
95
|
-
assignReactiveObject(state.results, {});
|
|
96
|
-
state.searched = false;
|
|
97
|
-
}
|
|
177
|
+
() => {
|
|
178
|
+
++state.pending;
|
|
179
|
+
throttledDoSearch();
|
|
98
180
|
},
|
|
99
181
|
{
|
|
100
182
|
immediate: true,
|
|
101
183
|
}
|
|
102
184
|
);
|
|
103
185
|
});
|
|
104
|
-
return {
|
|
186
|
+
return {
|
|
187
|
+
state,
|
|
188
|
+
addIndex,
|
|
189
|
+
updateIndex,
|
|
190
|
+
removeIndex,
|
|
191
|
+
clearIndex,
|
|
192
|
+
events,
|
|
193
|
+
effectScope: es,
|
|
194
|
+
};
|
|
105
195
|
}
|
package/use/watchesRunning.js
CHANGED
|
@@ -22,7 +22,7 @@ export function useWatchesRunning({ triggerRefs, watchSentinelRefs }) {
|
|
|
22
22
|
}
|
|
23
23
|
);
|
|
24
24
|
state.running = computed(() => {
|
|
25
|
-
const values = watchSentinelRefs.map((ref) => ref
|
|
25
|
+
const values = watchSentinelRefs.map((ref) => unref(ref));
|
|
26
26
|
return loadingCombine(values);
|
|
27
27
|
});
|
|
28
28
|
});
|
|
@@ -83,12 +83,12 @@ function validateTargetAndSource(target, source) {
|
|
|
83
83
|
* @returns {boolean} True if any keys were replaced, false otherwise.
|
|
84
84
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
85
85
|
*/
|
|
86
|
-
function reactiveReplaceKeys(target, source, keys, exclude
|
|
86
|
+
function reactiveReplaceKeys(target, source, keys, exclude) {
|
|
87
87
|
const targetIsReactive = isReactive(target);
|
|
88
88
|
const sourceIsReactive = isReactive(source);
|
|
89
89
|
let didAnything = false;
|
|
90
90
|
for (const key of keys) {
|
|
91
|
-
if (!exclude
|
|
91
|
+
if (!exclude?.includes(key)) {
|
|
92
92
|
if (targetIsReactive && sourceIsReactive) {
|
|
93
93
|
const targetPropRaw = unref(toRef(target, key));
|
|
94
94
|
// if they are object like we can see if the values are the same
|
|
@@ -122,7 +122,7 @@ function reactiveReplaceKeys(target, source, keys, exclude = []) {
|
|
|
122
122
|
* @returns {boolean} True if any keys were added, false otherwise.
|
|
123
123
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
124
124
|
*/
|
|
125
|
-
export function addReactiveObject(target, source, exclude
|
|
125
|
+
export function addReactiveObject(target, source, exclude, addedKeys = null) {
|
|
126
126
|
if (!addedKeys) {
|
|
127
127
|
if (target === source) {
|
|
128
128
|
return false;
|
|
@@ -144,7 +144,7 @@ export function addReactiveObject(target, source, exclude = [], addedKeys = null
|
|
|
144
144
|
* @returns {boolean} True if any keys were updated, false otherwise.
|
|
145
145
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
146
146
|
*/
|
|
147
|
-
export function updateReactiveObject(target, source, exclude
|
|
147
|
+
export function updateReactiveObject(target, source, exclude, sameKeys = null) {
|
|
148
148
|
if (!sameKeys) {
|
|
149
149
|
if (target === source) {
|
|
150
150
|
return false;
|
|
@@ -167,7 +167,7 @@ export function updateReactiveObject(target, source, exclude = [], sameKeys = nu
|
|
|
167
167
|
* keys will be calculated.
|
|
168
168
|
* @returns {boolean} True if any keys were added or updated, false otherwise.
|
|
169
169
|
*/
|
|
170
|
-
export function addOrUpdateReactiveObject(target, source, exclude
|
|
170
|
+
export function addOrUpdateReactiveObject(target, source, exclude, addedKeys = null, sameKeys = null) {
|
|
171
171
|
if (!addedKeys && !sameKeys) {
|
|
172
172
|
if (target === source) {
|
|
173
173
|
return false;
|
|
@@ -191,7 +191,7 @@ export function addOrUpdateReactiveObject(target, source, exclude = [], addedKey
|
|
|
191
191
|
* @returns {boolean} True if any keys were removed, false otherwise.
|
|
192
192
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
193
193
|
*/
|
|
194
|
-
export function trimReactiveObject(target, source, exclude
|
|
194
|
+
export function trimReactiveObject(target, source, exclude, removedKeys = null) {
|
|
195
195
|
if (!removedKeys) {
|
|
196
196
|
if (target === source) {
|
|
197
197
|
return false;
|
|
@@ -204,14 +204,14 @@ export function trimReactiveObject(target, source, exclude = [], removedKeys = n
|
|
|
204
204
|
if (targetIsArray) {
|
|
205
205
|
// Remove indices in reverse (descending) order to keep them stable
|
|
206
206
|
for (const removedKey of [...removedKeys].map((key) => parseInt(key, 10)).sort((a, b) => b - a)) {
|
|
207
|
-
if (!exclude
|
|
207
|
+
if (!exclude?.includes(removedKey)) {
|
|
208
208
|
target.splice(removedKey, 1);
|
|
209
209
|
didAnything = true;
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
} else {
|
|
213
213
|
for (const removedKey of removedKeys) {
|
|
214
|
-
if (!exclude
|
|
214
|
+
if (!exclude?.includes(removedKey)) {
|
|
215
215
|
delete target[removedKey];
|
|
216
216
|
didAnything = true;
|
|
217
217
|
}
|
|
@@ -220,6 +220,44 @@ export function trimReactiveObject(target, source, exclude = [], removedKeys = n
|
|
|
220
220
|
return didAnything;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
function checkIfReversed(target, source) {
|
|
224
|
+
if (target.length !== source.length || target.length === 0) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
let t = target.length - 1,
|
|
228
|
+
s = 0,
|
|
229
|
+
slen = source.length;
|
|
230
|
+
while (t >= 0 && s < slen) {
|
|
231
|
+
const targetValue = target[t];
|
|
232
|
+
const sourceValue = source[s];
|
|
233
|
+
if (targetValue === sourceValue) {
|
|
234
|
+
t--;
|
|
235
|
+
s++;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function assignReactiveArray(target, source) {
|
|
244
|
+
if (target === source) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
({ target, source } = validateTargetAndSource(target, source));
|
|
248
|
+
let didAnything = false;
|
|
249
|
+
if (checkIfReversed(target, source)) {
|
|
250
|
+
// reverse the target
|
|
251
|
+
target.reverse();
|
|
252
|
+
didAnything = true;
|
|
253
|
+
} else {
|
|
254
|
+
const { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
|
|
255
|
+
didAnything |= trimReactiveObject(target, source, null, removedKeys);
|
|
256
|
+
didAnything |= addOrUpdateReactiveObject(target, source, null, addedKeys, sameKeys);
|
|
257
|
+
}
|
|
258
|
+
return didAnything;
|
|
259
|
+
}
|
|
260
|
+
|
|
223
261
|
/**
|
|
224
262
|
* Change a target to match a source, where keys missing from the source are removed from the target,
|
|
225
263
|
* keys present in the source are added to the target, and keys present in both are updated in the target.
|
|
@@ -230,7 +268,13 @@ export function trimReactiveObject(target, source, exclude = [], removedKeys = n
|
|
|
230
268
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
231
269
|
* @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
|
|
232
270
|
*/
|
|
233
|
-
export function assignReactiveObject(target, source, exclude
|
|
271
|
+
export function assignReactiveObject(target, source, exclude) {
|
|
272
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
273
|
+
if (exclude?.length) {
|
|
274
|
+
console.warn(`assignReactiveObject: exclude doesn't make sense for array assignment`);
|
|
275
|
+
}
|
|
276
|
+
return assignReactiveArray(target, source);
|
|
277
|
+
}
|
|
234
278
|
if (target === source) {
|
|
235
279
|
return false;
|
|
236
280
|
}
|
|
@@ -277,7 +321,7 @@ function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn)
|
|
|
277
321
|
for (const key of keysForRecurse) {
|
|
278
322
|
// scope exclude for this next level, remove keys that don't start with the current path, trim keys that do to remove the current path
|
|
279
323
|
const nextLevelExclude = exclude
|
|
280
|
-
|
|
324
|
+
?.filter((excludeKey) => !excludeKey.startsWith(path))
|
|
281
325
|
.map((excludeKey) => excludeKey.replace(path, ""));
|
|
282
326
|
const nextPath = isArray(source[key]) ? `${path}[${key}]` : `${path}.${key}`;
|
|
283
327
|
fn(target[key], source[key], nextLevelExclude, nextPath);
|
|
@@ -298,7 +342,7 @@ function recursiveInner(target, source, exclude, addedKeys, sameKeys, path, fn)
|
|
|
298
342
|
* @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
|
|
299
343
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
300
344
|
*/
|
|
301
|
-
function assignReactiveObjectRecursive(target, source, exclude
|
|
345
|
+
function assignReactiveObjectRecursive(target, source, exclude, path = "") {
|
|
302
346
|
let { addedKeys, sameKeys, removedKeys } = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
|
|
303
347
|
let didAnything = false;
|
|
304
348
|
didAnything |= trimReactiveObject(target, source, exclude, removedKeys);
|
|
@@ -316,7 +360,7 @@ function assignReactiveObjectRecursive(target, source, exclude = [], path = "")
|
|
|
316
360
|
* @returns {boolean} True if any keys were added, updated, or removed, false otherwise.
|
|
317
361
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
318
362
|
*/
|
|
319
|
-
export function assignReactiveObjectDeep(target, source, exclude
|
|
363
|
+
export function assignReactiveObjectDeep(target, source, exclude) {
|
|
320
364
|
// exclude keys will need to be lodash get strings
|
|
321
365
|
if (target === source) {
|
|
322
366
|
return false;
|
|
@@ -337,7 +381,7 @@ export function assignReactiveObjectDeep(target, source, exclude = []) {
|
|
|
337
381
|
* @param {string} [path] The current path, used to rescope exclude for the next level.
|
|
338
382
|
* @returns {boolean} True if any keys were added or updated, false otherwise.
|
|
339
383
|
*/
|
|
340
|
-
function addOrUpdateReactiveObjectRecursive(target, source, exclude
|
|
384
|
+
function addOrUpdateReactiveObjectRecursive(target, source, exclude, path = "") {
|
|
341
385
|
let addedKeys,
|
|
342
386
|
sameKeys = keyDiff(Object.keys(source) || [], Object.keys(target) || []);
|
|
343
387
|
return recursiveInner(target, source, exclude, addedKeys, sameKeys, path, addOrUpdateReactiveObjectRecursive);
|
|
@@ -353,7 +397,7 @@ function addOrUpdateReactiveObjectRecursive(target, source, exclude = [], path =
|
|
|
353
397
|
* @returns {boolean} True if any keys were added or updated, false otherwise.
|
|
354
398
|
* @throws {AssignReactiveObjectError} If either target or source are not ultimately objects or arrays.
|
|
355
399
|
*/
|
|
356
|
-
export function addOrUpdateReactiveObjectDeep(target, source, exclude
|
|
400
|
+
export function addOrUpdateReactiveObjectDeep(target, source, exclude) {
|
|
357
401
|
// exclude keys will need to be lodash get strings
|
|
358
402
|
if (target === source) {
|
|
359
403
|
return false;
|
package/utils/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./getFakeId.js";
|
|
|
8
8
|
export * from "./keyDiff.js";
|
|
9
9
|
export * from "./lifecycleDebug.js";
|
|
10
10
|
export * from "./loadingCombine.js";
|
|
11
|
+
export * from "./relatedCalculatedHelpers.js";
|
|
11
12
|
export * from "./set.js";
|
|
12
13
|
export * from "./transformWalk.js";
|
|
13
14
|
export * from "./watches.js";
|
package/utils/keyDiff.js
CHANGED
|
@@ -18,23 +18,29 @@ import { difference, intersection } from "./set.js";
|
|
|
18
18
|
/**
|
|
19
19
|
* Result object of keyDiff and keyDiffDeep
|
|
20
20
|
* @typedef {object} KeyDiffResult
|
|
21
|
-
* @property {
|
|
22
|
-
* @property {
|
|
23
|
-
* @property {
|
|
21
|
+
* @property {Set} [sameKeys] - if sameKeys option is true, return keys that are the same
|
|
22
|
+
* @property {Set} [removedKeys] - if removedKeys option is true, return keys that are removed
|
|
23
|
+
* @property {Set} [addedKeys] - if addedKeys option is true, return keys that are added
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Calculate the difference between two arrays of keys, in terms of what keys
|
|
28
28
|
* are the same, what keys are removed, and what keys are added.
|
|
29
29
|
* @function keyDiff
|
|
30
|
-
* @param {string[]} newKeys - keys to consider as new
|
|
31
|
-
* @param {string[]} oldKeys - keys to consider as old
|
|
30
|
+
* @param {string[]|Set} newKeys - keys to consider as new
|
|
31
|
+
* @param {string[]|Set} oldKeys - keys to consider as old
|
|
32
32
|
* @param {KeyDiffOptions} [options] - which differences are returned
|
|
33
33
|
* @returns {KeyDiffResult} - the differences
|
|
34
34
|
*/
|
|
35
35
|
export function keyDiff(newKeys, oldKeys, { sameKeys = true, removedKeys = true, addedKeys = true } = {}) {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
let newKeysSet = newKeys;
|
|
37
|
+
let oldKeysSet = oldKeys;
|
|
38
|
+
if (!(newKeys instanceof Set)) {
|
|
39
|
+
newKeysSet = new Set(newKeys);
|
|
40
|
+
}
|
|
41
|
+
if (!(oldKeys instanceof Set)) {
|
|
42
|
+
oldKeysSet = new Set(oldKeys);
|
|
43
|
+
}
|
|
38
44
|
const returnValue = {};
|
|
39
45
|
if (sameKeys) {
|
|
40
46
|
returnValue.sameKeys = intersection(newKeysSet, oldKeysSet);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const relatedItemRegex = /^relatedItem\./;
|
|
2
|
+
export const calculatedItemRegex = /^calculatedItem\./;
|
|
3
|
+
|
|
4
|
+
export const getObjectRelatedCalculatedByKey = (obj, relatedObj, calculatedObj, key) => {
|
|
5
|
+
let getObj = obj,
|
|
6
|
+
getKey = key.replace(relatedItemRegex, () => {
|
|
7
|
+
getObj = relatedObj;
|
|
8
|
+
return "";
|
|
9
|
+
});
|
|
10
|
+
if (getKey === key) {
|
|
11
|
+
getKey = key.replace(calculatedItemRegex, () => {
|
|
12
|
+
getObj = calculatedObj;
|
|
13
|
+
return "";
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return [getObj, getKey];
|
|
17
|
+
};
|
package/utils/watches.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { watch } from "vue";
|
|
1
|
+
import { watch, toRef } from "vue";
|
|
2
2
|
|
|
3
3
|
// If you want an immediate watch, but you can't use immediate because you want to stop the watch in the function.
|
|
4
4
|
export class ImmediateStopWatch {
|
|
@@ -85,7 +85,7 @@ export function doAwaitTimeout(timeout) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
export class AwaitNot {
|
|
88
|
-
constructor({ obj, prop, couldAlreadyBeFalse = false, timeout = 1000 }) {
|
|
88
|
+
constructor({ obj, prop, ref, couldAlreadyBeFalse = false, timeout = 1000 }) {
|
|
89
89
|
if (timeout > 0) {
|
|
90
90
|
this.timeout = new AwaitTimeout({ timeout });
|
|
91
91
|
}
|
|
@@ -94,8 +94,11 @@ export class AwaitNot {
|
|
|
94
94
|
this.reject = reject;
|
|
95
95
|
});
|
|
96
96
|
this.couldAlreadyBeFalse = couldAlreadyBeFalse;
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
if (ref) {
|
|
98
|
+
this.ref = ref;
|
|
99
|
+
} else {
|
|
100
|
+
this.ref = toRef(() => obj[prop]);
|
|
101
|
+
}
|
|
99
102
|
this.trueISW = new ImmediateStopWatch();
|
|
100
103
|
this.falseISW = new ImmediateStopWatch();
|
|
101
104
|
// prebuild the exception for a more useful stack.
|
|
@@ -125,7 +128,7 @@ export class AwaitNot {
|
|
|
125
128
|
});
|
|
126
129
|
this.timeout.start();
|
|
127
130
|
}
|
|
128
|
-
if (this.
|
|
131
|
+
if (this.ref.value === false && this.couldAlreadyBeFalse) {
|
|
129
132
|
this.waitForTrue();
|
|
130
133
|
} else {
|
|
131
134
|
this.waitForFalse();
|
|
@@ -134,14 +137,14 @@ export class AwaitNot {
|
|
|
134
137
|
|
|
135
138
|
waitForTrue() {
|
|
136
139
|
this.trueISW.start(
|
|
137
|
-
|
|
140
|
+
this.ref,
|
|
138
141
|
(newValue) => {
|
|
139
142
|
if (newValue === true) {
|
|
140
143
|
this.stopTrue();
|
|
141
144
|
this.waitForFalse();
|
|
142
145
|
}
|
|
143
146
|
},
|
|
144
|
-
[this.
|
|
147
|
+
[this.ref.value]
|
|
145
148
|
);
|
|
146
149
|
}
|
|
147
150
|
|
|
@@ -154,7 +157,7 @@ export class AwaitNot {
|
|
|
154
157
|
|
|
155
158
|
waitForFalse() {
|
|
156
159
|
this.falseISW.start(
|
|
157
|
-
|
|
160
|
+
this.ref,
|
|
158
161
|
(newValue) => {
|
|
159
162
|
if (newValue === false) {
|
|
160
163
|
this.stop();
|
|
@@ -164,7 +167,7 @@ export class AwaitNot {
|
|
|
164
167
|
this.cleanPromise();
|
|
165
168
|
}
|
|
166
169
|
},
|
|
167
|
-
[this.
|
|
170
|
+
[this.ref.value]
|
|
168
171
|
);
|
|
169
172
|
}
|
|
170
173
|
|
|
@@ -193,8 +196,8 @@ export class AwaitNot {
|
|
|
193
196
|
}
|
|
194
197
|
}
|
|
195
198
|
|
|
196
|
-
export function doAwaitNot({ obj, prop, couldAlreadyBeFalse = true, timeout = 1000 }) {
|
|
197
|
-
const awaitNot = new AwaitNot({ obj, prop, couldAlreadyBeFalse, timeout });
|
|
199
|
+
export function doAwaitNot({ obj, prop, ref, couldAlreadyBeFalse = true, timeout = 1000 }) {
|
|
200
|
+
const awaitNot = new AwaitNot({ obj, prop, ref, couldAlreadyBeFalse, timeout });
|
|
198
201
|
awaitNot.start();
|
|
199
202
|
return awaitNot.promise;
|
|
200
203
|
}
|