@arrai-innovations/reactive-helpers 10.1.0 → 10.3.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.
@@ -1,7 +1,7 @@
1
1
  version: 2.1
2
2
  orbs:
3
3
  eslint: arrai/eslint@7.4.0
4
- prettier: arrai/prettier@5.1.1
4
+ prettier: arrai/prettier@5.4.0
5
5
  npm: arrai/npm@2.4.1
6
6
  github: arrai/github@1.1.0
7
7
  workflows:
@@ -75,14 +75,12 @@ workflows:
75
75
  - prettier/code_style:
76
76
  name: prettier
77
77
  context: arrai-global
78
- version: 2.6.2
79
78
  filters:
80
79
  branches:
81
80
  only:
82
81
  - main
83
82
  - prettier/code_style:
84
83
  name: prettier-no-badge
85
- version: 2.6.2
86
84
  create_badges: false
87
85
  filters:
88
86
  branches:
package/docs.md CHANGED
@@ -494,11 +494,11 @@ It tracks the objects in the list, and their added order.
494
494
  causing the list to be re-fetched as needed and listening for updates to the list.
495
495
 
496
496
  **Kind**: global function
497
- **Returns**: ListSubscription
497
+ **Returns**: [`ListSubscription`] - - the list subscription
498
498
 
499
- | Param | Type |
500
- | ------- | --------------------------- |
501
- | options | [`ListSubscriptionOptions`] |
499
+ | Param | Type | Description |
500
+ | ------- | --------------------------- | --------------------------------------------------- |
501
+ | options | [`ListSubscriptionOptions`] | the configuration options for the list subscription |
502
502
 
503
503
  ## useObjectInstances(instanceArgs)
504
504
 
@@ -832,6 +832,7 @@ A CSS object or a space-separated list of CSS classes.
832
832
  [`cssclasseswithrefs`]: #cssclasseswithrefs
833
833
  [`listinstance`]: #listinstance
834
834
  [`listinstanceoptions`]: #listinstanceoptions
835
+ [`listsubscription`]: #listsubscription
835
836
  [`listsubscriptionoptions`]: #listsubscriptionoptions
836
837
  [`objectinstanceinstance`]: #objectinstanceinstance
837
838
  [`listinstancestate`]: #listinstancestate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrai-innovations/reactive-helpers",
3
- "version": "10.1.0",
3
+ "version": "10.3.0",
4
4
  "description": "VueJS 3 utility composition functions to help manipulate objects and lists.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -2,12 +2,16 @@ import { nextTick } from "vue";
2
2
  import { deepUnref } from "vue-deepunref";
3
3
 
4
4
  describe("use/listCalculated", () => {
5
- let useListInstance, useListCalculated;
5
+ let useListInstance, useListCalculated, useListRelated, AwaitNot;
6
6
  beforeEach(async () => {
7
7
  const listInstanceModule = await import("../../../use/listInstance");
8
8
  useListInstance = listInstanceModule.useListInstance;
9
9
  const listCalculatedModule = await import("../../../use/listCalculated");
10
10
  useListCalculated = listCalculatedModule.useListCalculated;
11
+ const listRelatedModule = await import("../../../use/listRelated");
12
+ useListRelated = listRelatedModule.useListRelated;
13
+ const watchesModule = await import("../../../utils/watches.js");
14
+ AwaitNot = watchesModule.AwaitNot;
11
15
  });
12
16
  it("should return a list of calculated items", async () => {
13
17
  const mainListInstance = useListInstance({ props: {} });
@@ -71,4 +75,60 @@ describe("use/listCalculated", () => {
71
75
  },
72
76
  });
73
77
  });
78
+ it("should allow calculated objects to return results based on related objects", async () => {
79
+ const mainListInstance = useListInstance({ props: {} });
80
+ const relatedListInstance = useListInstance({ props: {} });
81
+ mainListInstance.addListObject({
82
+ id: "1",
83
+ name: "main",
84
+ related_items: ["2", "3"],
85
+ related_id: "4",
86
+ calculated_items: ["2", "3"],
87
+ calculated_id: "4",
88
+ });
89
+ relatedListInstance.addListObject({
90
+ id: "2",
91
+ name: "related1",
92
+ });
93
+ relatedListInstance.addListObject({
94
+ id: "3",
95
+ name: "related2",
96
+ });
97
+ relatedListInstance.addListObject({
98
+ id: "4",
99
+ name: "related3",
100
+ });
101
+ const listRelated = useListRelated({
102
+ parentState: mainListInstance.state,
103
+ relatedObjectsRules: {
104
+ relatedItems: {
105
+ objects: relatedListInstance.state.objects,
106
+ pkKey: "related_items",
107
+ },
108
+ relatedItem: {
109
+ objects: relatedListInstance.state.objects,
110
+ pkKey: "related_id",
111
+ },
112
+ },
113
+ });
114
+ const listCalculated = useListCalculated({
115
+ parentState: listRelated.state,
116
+ calculatedObjectsRules: {
117
+ calculatedItems: (obj, relatedObj) => relatedObj.relatedItems?.map((x) => x.name + "-modified"),
118
+ calculatedItem: (obj, relatedObj) => relatedObj.relatedItem?.name + "-modified",
119
+ },
120
+ });
121
+ const anr = new AwaitNot({
122
+ obj: listCalculated.state,
123
+ prop: "running",
124
+ });
125
+ anr.start();
126
+ await anr.promise;
127
+ expect(deepUnref(listCalculated.state.calculatedObjects)).toEqual({
128
+ 1: {
129
+ calculatedItems: ["related1-modified", "related2-modified"],
130
+ calculatedItem: "related3-modified",
131
+ },
132
+ });
133
+ });
74
134
  });
@@ -1,14 +1,15 @@
1
- // import { useListInstance, useListRelated } from "../../../use/listRelated.js";
2
1
  import { nextTick } from "vue";
3
2
  import { deepUnref } from "vue-deepunref";
4
3
 
5
4
  describe("use/listRelated", () => {
6
- let useListInstance, useListRelated;
5
+ let useListInstance, useListRelated, AwaitNot;
7
6
  beforeEach(async () => {
8
7
  const listInstanceModule = await import("../../../use/listInstance");
9
8
  useListInstance = listInstanceModule.useListInstance;
10
9
  const listRelatedModule = await import("../../../use/listRelated");
11
10
  useListRelated = listRelatedModule.useListRelated;
11
+ const watchesModule = await import("../../../utils/watches.js");
12
+ AwaitNot = watchesModule.AwaitNot;
12
13
  });
13
14
  it("should return a list of related items", async () => {
14
15
  const mainListInstance = useListInstance({ props: {} });
@@ -78,4 +79,117 @@ describe("use/listRelated", () => {
78
79
  },
79
80
  });
80
81
  });
82
+ it('should allow related objects pkKey to be prefixed with "relatedItem." to reference previously related objects', async () => {
83
+ //
84
+ const mainListInstance = useListInstance({ props: {} });
85
+ const intermediateListInstance = useListInstance({ props: {} });
86
+ const relatedListInstance = useListInstance({ props: {} });
87
+ mainListInstance.addListObject({
88
+ id: "1",
89
+ name: "main",
90
+ intermediate_ids: ["2", "3"],
91
+ intermediate_id: "2",
92
+ });
93
+ intermediateListInstance.addListObject({
94
+ id: "2",
95
+ name: "intermediate1",
96
+ related_ids: ["4", "5"],
97
+ related_id: "4",
98
+ });
99
+ intermediateListInstance.addListObject({
100
+ id: "3",
101
+ name: "intermediate2",
102
+ related_ids: ["6", "7"],
103
+ related_id: "6",
104
+ });
105
+ relatedListInstance.addListObject({
106
+ id: "4",
107
+ name: "related1",
108
+ });
109
+ relatedListInstance.addListObject({
110
+ id: "5",
111
+ name: "related2",
112
+ });
113
+ relatedListInstance.addListObject({
114
+ id: "6",
115
+ name: "related3",
116
+ });
117
+ relatedListInstance.addListObject({
118
+ id: "7",
119
+ name: "related4",
120
+ });
121
+ const listRelated = useListRelated({
122
+ parentState: mainListInstance.state,
123
+ relatedObjectsRules: {
124
+ intermediateItems: {
125
+ objects: intermediateListInstance.state.objects,
126
+ pkKey: "intermediate_ids",
127
+ },
128
+ intermediateItem: {
129
+ objects: intermediateListInstance.state.objects,
130
+ pkKey: "intermediate_id",
131
+ },
132
+ relatedItems: {
133
+ objects: relatedListInstance.state.objects,
134
+ pkKey: "relatedItem.intermediateItems.related_ids",
135
+ },
136
+ relatedItem: {
137
+ objects: relatedListInstance.state.objects,
138
+ pkKey: "relatedItem.intermediateItem.related_id",
139
+ },
140
+ },
141
+ });
142
+ const anr = new AwaitNot({
143
+ obj: listRelated.state,
144
+ prop: "running",
145
+ });
146
+ anr.start();
147
+ await anr.promise;
148
+ expect(deepUnref(listRelated.state.relatedObjects)).toEqual({
149
+ 1: {
150
+ intermediateItems: [
151
+ {
152
+ id: "2",
153
+ name: "intermediate1",
154
+ related_ids: ["4", "5"],
155
+ related_id: "4",
156
+ },
157
+ {
158
+ id: "3",
159
+ name: "intermediate2",
160
+ related_ids: ["6", "7"],
161
+ related_id: "6",
162
+ },
163
+ ],
164
+ intermediateItem: {
165
+ id: "2",
166
+ name: "intermediate1",
167
+ related_ids: ["4", "5"],
168
+ related_id: "4",
169
+ },
170
+ relatedItems: [
171
+ {
172
+ id: "4",
173
+ name: "related1",
174
+ },
175
+ {
176
+ id: "5",
177
+ name: "related2",
178
+ },
179
+ {
180
+ id: "6",
181
+ name: "related3",
182
+ },
183
+ {
184
+ id: "7",
185
+ name: "related4",
186
+ },
187
+ ],
188
+ relatedItem: {
189
+ id: "4",
190
+ name: "related1",
191
+ },
192
+ },
193
+ });
194
+ });
81
195
  });
@@ -1,5 +1,4 @@
1
1
  import { doAwaitTimeout } from "../../../utils/index.js";
2
- import { nextTick } from "vue";
3
2
  import { deepUnref } from "vue-deepunref";
4
3
 
5
4
  describe("use/useListSort", () => {
@@ -10,7 +9,8 @@ describe("use/useListSort", () => {
10
9
  useListInstances,
11
10
  useListSort,
12
11
  useListSorts,
13
- setListSortDefaultOptions;
12
+ setListSortDefaultOptions,
13
+ AwaitNot;
14
14
  const contactsResolved = [
15
15
  {
16
16
  id: 15,
@@ -58,10 +58,22 @@ describe("use/useListSort", () => {
58
58
  setListSortDefaultOptions({
59
59
  sortThrottleWait: 0,
60
60
  });
61
+ const importedUtils = await import("../../../utils");
62
+ AwaitNot = importedUtils.AwaitNot;
61
63
  });
62
64
 
63
65
  afterEach(() => vi.resetAllMocks());
64
66
 
67
+ const waitForListSort = async (listSort) => {
68
+ const anr = new AwaitNot({
69
+ obj: listSort.state,
70
+ prop: "running",
71
+ timeout: 2000,
72
+ });
73
+ anr.start();
74
+ await anr.promise;
75
+ };
76
+
65
77
  it("generates initial values from inputs", () => {
66
78
  const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
67
79
  expect(listSort.state.orderByRules).toEqual(orderByRules);
@@ -89,13 +101,14 @@ describe("use/useListSort", () => {
89
101
  listInstance.addListObject(contact);
90
102
  }
91
103
  const listSort = useListSort({ parentState: listInstance.state, orderByRules });
92
- await nextTick();
104
+ // sorts immediately
93
105
  expect(listSort.state.order).toEqual(testOrder1);
106
+ await waitForListSort(listSort);
94
107
  listInstance.addListObject(addObject);
95
- await nextTick();
108
+ await waitForListSort(listSort);
96
109
  expect(listSort.state.order).toEqual(testOrder2);
97
110
  listInstance.deleteListObject(12);
98
- await nextTick();
111
+ await waitForListSort(listSort);
99
112
  expect(listSort.state.order).toEqual(testOrder3);
100
113
  });
101
114
  });
@@ -115,17 +128,17 @@ describe("use/useListSort", () => {
115
128
  listSort.state.orderByRules.pop();
116
129
  listSort.state.orderByRules.push({ key: "lexical_name", desc: false, localeCompare: true });
117
130
  expect(listSort.state.order).toEqual(testOrder1);
118
- await nextTick();
131
+ await waitForListSort(listSort);
119
132
  expect(listSort.state.order).toEqual(testOrder2);
120
133
  listSort.state.orderByRules.pop();
121
134
  listSort.state.orderByRules.push({ key: "organization", desc: true, localeCompare: true });
122
- await nextTick();
135
+ await waitForListSort(listSort);
123
136
  expect(listSort.state.order).toEqual(testOrder3);
124
137
  listSort.state.orderByRules.pop();
125
- await nextTick();
138
+ await waitForListSort(listSort);
126
139
  expect(listSort.state.order).toEqual(testOrder4);
127
140
  listSort.state.orderByRules.push({ key: "organization", desc: false, localeCompare: false });
128
- await nextTick();
141
+ await waitForListSort(listSort);
129
142
  expect(listSort.state.order).toEqual(testOrder2);
130
143
  });
131
144
  });
@@ -212,20 +225,31 @@ describe("use/useListSort", () => {
212
225
  }
213
226
 
214
227
  const testOrder1 = [];
215
- const testOrder2 = ["35", "12", "15", "9"];
216
- const testOrder3 = ["35", "15", "9"];
228
+ const testOrder2 = ["12", "15", "9"];
229
+ const testOrder3 = ["35", "12", "15", "9"];
230
+ const testOrder4 = ["35", "15", "9"];
217
231
 
218
232
  const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
219
- await nextTick();
220
233
  expect(listSort.state.order).toEqual(testOrder1);
234
+
235
+ // wait for the original throttle to finish
236
+ await doAwaitTimeout(250);
237
+
238
+ expect(listSort.state.order).toEqual(testOrder2);
221
239
  listInstance.addListObject(addObject);
222
- await nextTick();
223
240
  expect(listSort.state.order).toEqual(testOrder2);
241
+ // trigger the leading edge of the throttle
242
+ await doAwaitTimeout(10);
243
+ expect(listSort.state.order).toEqual(testOrder3);
244
+ // trigger again before the 200ms throttle
224
245
  listInstance.deleteListObject(12);
225
- await nextTick();
226
- expect(listSort.state.order).toEqual(testOrder2);
227
- await doAwaitTimeout(200);
228
246
  expect(listSort.state.order).toEqual(testOrder3);
247
+ // this should trigger before the 200ms throttle
248
+ await doAwaitTimeout(100);
249
+ expect(listSort.state.order).toEqual(testOrder3);
250
+ // this should trigger after the 200ms throttle
251
+ await doAwaitTimeout(200);
252
+ expect(listSort.state.order).toEqual(testOrder4);
229
253
  });
230
254
  });
231
255
  });
@@ -87,7 +87,11 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
87
87
  calculatedObjectsEffectScopes[objectKey].run(() => {
88
88
  for (const addedRuleKey of addedRuleKeys) {
89
89
  calculatedObjectsObject[addedRuleKey] = computed(() =>
90
- state.calculatedObjectsRules?.[addedRuleKey]?.(unref(originalObject), unref(relatedObject))
90
+ state.calculatedObjectsRules?.[addedRuleKey]?.(
91
+ unref(originalObject),
92
+ unref(relatedObject),
93
+ calculatedObjectsObject
94
+ )
91
95
  );
92
96
  }
93
97
  });
@@ -58,6 +58,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
58
58
  for (const objectKey of Object.keys(state.relatedObjects)) {
59
59
  const relatedObjectsObject = state.relatedObjects[objectKey];
60
60
  const originalObjectRef = toRef(parentState.objects, objectKey);
61
+ const relatedObjectRef = toRef(state.relatedObjects, objectKey);
61
62
  let removedRuleKeys, addedRuleKeys;
62
63
  if (!relatedObjectsRulesIsEmpty) {
63
64
  ({ removedKeys: removedRuleKeys, addedKeys: addedRuleKeys } = keyDiff(
@@ -86,12 +87,30 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
86
87
  const rulePkKey = state.relatedObjectsRules?.[addedRuleKey]?.pkKey || addedRuleKey;
87
88
  const ruleOrder = unref(state.relatedObjectsRules?.[addedRuleKey]?.order);
88
89
  if (!ruleObjects || !rulePkKey) {
89
- relatedObjectsObject[addedRuleKey] = undefined;
90
+ if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
91
+ relatedObjectsObject[addedRuleKey] = undefined;
92
+ }
90
93
  return;
91
94
  }
92
- let value = get(unref(originalObjectRef), rulePkKey);
95
+ let value;
96
+ if (rulePkKey.startsWith("relatedItem.")) {
97
+ value = get(unref(relatedObjectRef), rulePkKey.slice(12));
98
+ if (isUndefined(value)) {
99
+ // is the first level an array?
100
+ const firstLevelKey = rulePkKey.slice(12).split(".")[0];
101
+ const firstLevelItem = get(unref(relatedObjectRef), firstLevelKey);
102
+ if (isArray(firstLevelItem)) {
103
+ const restOfKey = rulePkKey.slice(12 + firstLevelKey.length + 1);
104
+ value = firstLevelItem.map((e) => get(e, restOfKey)).flat();
105
+ }
106
+ }
107
+ } else {
108
+ value = get(unref(originalObjectRef), rulePkKey);
109
+ }
93
110
  if (isUndefined(value)) {
94
- relatedObjectsObject[addedRuleKey] = undefined;
111
+ if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
112
+ relatedObjectsObject[addedRuleKey] = undefined;
113
+ }
95
114
  return;
96
115
  }
97
116
  if (isArray(value)) {
@@ -114,7 +133,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
114
133
  }
115
134
  };
116
135
  watch(
117
- [toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef],
136
+ [toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef, relatedObjectRef],
118
137
  relatedObjectsObjectWatchFn,
119
138
  {
120
139
  deep: true,
package/use/listSort.js CHANGED
@@ -1,18 +1,20 @@
1
- import { assignReactiveObject, keyDiff } from "../utils/index.js";
1
+ import { assignReactiveObject, keyDiff, loadingCombine } from "../utils/index.js";
2
2
  import { listCalculatedStateKeys } from "./listCalculated.js";
3
3
  import { listFilterStateKeys } from "./listFilter.js";
4
4
  import { listInstanceStateKeys } from "./listInstance.js";
5
5
  import { listRelatedStateKeys } from "./listRelated.js";
6
6
  import { listSubscriptionStateKeys } from "./listSubscription.js";
7
+ import { useWatchesRunning } from "./watchesRunning.js";
7
8
  import cloneDeep from "lodash-es/cloneDeep.js";
8
9
  import get from "lodash-es/get.js";
9
10
  import identity from "lodash-es/identity.js";
10
11
  import isEmpty from "lodash-es/isEmpty.js";
12
+ import isEqual from "lodash-es/isEqual.js";
11
13
  import isNull from "lodash-es/isNull.js";
12
14
  import isUndefined from "lodash-es/isUndefined.js";
13
15
  import throttle from "lodash-es/throttle.js";
14
16
  import zip from "lodash-es/zip.js";
15
- import { effectScope, reactive, toRef, unref, watch } from "vue";
17
+ import { effectScope, reactive, toRef, unref, watch, computed } from "vue";
16
18
 
17
19
  export const listSortStateKeys = [
18
20
  "orderByRules",
@@ -21,6 +23,10 @@ export const listSortStateKeys = [
21
23
  "sortCriteria",
22
24
  "sortCriteriaEffectScopes",
23
25
  "orderByDesc",
26
+ "sortCriteriaWatchRunning",
27
+ "sortWatchRunning",
28
+ "outstandingEffects",
29
+ // "running",
24
30
  ];
25
31
 
26
32
  export const listSortFunctions = [];
@@ -56,6 +62,9 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
56
62
  sortCriteria: {},
57
63
  sortCriteriaEffectScopes: {},
58
64
  orderByDesc: [],
65
+ sortCriteriaWatchRunning: false,
66
+ sortWatchRunning: false,
67
+ outstandingEffects: false,
59
68
  });
60
69
  const es = effectScope();
61
70
 
@@ -92,7 +101,13 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
92
101
  }
93
102
  newSearchCriteria.push(newItem);
94
103
  }
104
+ if (isEqual(newSearchCriteria, state.sortCriteria[key])) {
105
+ return;
106
+ }
95
107
  assignReactiveObject(state.sortCriteria[key], newSearchCriteria);
108
+ if (!state.outstandingEffects) {
109
+ state.outstandingEffects = true;
110
+ }
96
111
  },
97
112
  {
98
113
  deep: true,
@@ -104,82 +119,96 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
104
119
  }
105
120
 
106
121
  function sortCriteriaWatch() {
107
- if (!state.orderByRules || !state.orderByRules.filter(identity).length) {
108
- if (!isEmpty(state.sortCriteria)) {
109
- for (const removedKey of Object.keys(state.sortCriteria)) {
110
- removeSortCriteria(removedKey);
122
+ try {
123
+ if (!state.orderByRules || !state.orderByRules.filter(identity).length) {
124
+ if (!isEmpty(state.sortCriteria)) {
125
+ for (const removedKey of Object.keys(state.sortCriteria)) {
126
+ removeSortCriteria(removedKey);
127
+ }
111
128
  }
129
+ assignReactiveObject(state.order, cloneDeep(parentState.order));
130
+ assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
131
+ return;
112
132
  }
113
- assignReactiveObject(state.order, cloneDeep(parentState.order));
114
- assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
115
- return;
116
- }
117
- const { removedKeys, addedKeys } = keyDiff(Object.keys(parentState.objects), Object.keys(state.sortCriteria));
118
- for (const removedKey of removedKeys) {
119
- removeSortCriteria(removedKey);
120
- }
121
-
122
- es.run(() => {
123
- for (const addedKey of addedKeys) {
124
- const object = toRef(() => parentState.objects[addedKey]);
125
- addSortCriteria(object, addedKey);
133
+ const { removedKeys, addedKeys } = keyDiff(
134
+ Object.keys(parentState.objects),
135
+ Object.keys(state.sortCriteria)
136
+ );
137
+ for (const removedKey of removedKeys) {
138
+ removeSortCriteria(removedKey);
126
139
  }
127
- });
128
- assignReactiveObject(
129
- state.orderByDesc,
130
- state.orderByRules.filter(identity).map((e) => e.desc || false)
131
- );
140
+
141
+ es.run(() => {
142
+ for (const addedKey of addedKeys) {
143
+ const object = toRef(() => parentState.objects[addedKey]);
144
+ addSortCriteria(object, addedKey);
145
+ }
146
+ });
147
+ assignReactiveObject(
148
+ state.orderByDesc,
149
+ state.orderByRules.filter(identity).map((e) => e.desc || false)
150
+ );
151
+ } finally {
152
+ state.sortCriteriaWatchRunning = false;
153
+ }
132
154
  }
133
155
 
134
156
  function sortWatch() {
135
- if (!state.orderByRules || !state.orderByRules.length) {
136
- assignReactiveObject(state.order, cloneDeep(parentState.order));
137
- assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
138
- return;
139
- }
157
+ try {
158
+ if (!state.orderByRules || !state.orderByRules.length) {
159
+ assignReactiveObject(state.order, cloneDeep(parentState.order));
160
+ assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
161
+ return;
162
+ }
140
163
 
141
- let idList = Object.keys(parentState.objects);
142
- idList.sort((xKey, yKey) => {
143
- const xCriteria = state.sortCriteria[xKey];
144
- const yCriteria = state.sortCriteria[yKey];
145
- for (let [x, y, orderByObj] of zip(xCriteria, yCriteria, state.orderByRules)) {
146
- if (!orderByObj) {
147
- continue;
148
- }
149
- if (orderByObj.desc) {
150
- [x, y] = [y, x];
151
- }
152
- const isUndefinedX = isUndefined(x) || isNull(x);
153
- const isUndefinedY = isUndefined(y) || isNull(y);
154
- if (isUndefinedX && isUndefinedY) {
155
- continue;
156
- } else if (isUndefinedX) {
157
- return -1;
158
- } else if (isUndefinedY) {
159
- return 1;
160
- }
161
- if (orderByObj.localeCompare) {
162
- const strComp = collator.compare(x, y);
163
- if (strComp) {
164
- return strComp;
164
+ let idList = Object.keys(parentState.objects);
165
+ idList.sort((xKey, yKey) => {
166
+ const xCriteria = state.sortCriteria[xKey];
167
+ const yCriteria = state.sortCriteria[yKey];
168
+ for (let [x, y, orderByObj] of zip(xCriteria, yCriteria, state.orderByRules)) {
169
+ if (!orderByObj) {
170
+ continue;
165
171
  }
166
- } else {
167
- if (x < y) {
168
- return -1;
172
+ if (orderByObj.desc) {
173
+ [x, y] = [y, x];
169
174
  }
170
- if (x > y) {
175
+ const isUndefinedX = isUndefined(x) || isNull(x);
176
+ const isUndefinedY = isUndefined(y) || isNull(y);
177
+ if (isUndefinedX && isUndefinedY) {
178
+ continue;
179
+ } else if (isUndefinedX) {
180
+ return -1;
181
+ } else if (isUndefinedY) {
171
182
  return 1;
172
183
  }
184
+ if (orderByObj.localeCompare) {
185
+ const strComp = collator.compare(x, y);
186
+ if (strComp) {
187
+ return strComp;
188
+ }
189
+ } else {
190
+ if (x < y) {
191
+ return -1;
192
+ }
193
+ if (x > y) {
194
+ return 1;
195
+ }
196
+ }
173
197
  }
174
- }
175
- return 0;
176
- });
177
- assignReactiveObject(state.order, idList);
178
- assignReactiveObject(state.objectsInOrder, idList.map((e) => parentState.objects[e]).filter(identity));
198
+ return 0;
199
+ });
200
+ assignReactiveObject(state.order, idList);
201
+ assignReactiveObject(state.objectsInOrder, idList.map((e) => parentState.objects[e]).filter(identity));
202
+ } finally {
203
+ state.sortWatchRunning = false;
204
+ state.outstandingEffects = false;
205
+ }
179
206
  }
180
207
 
181
208
  const throttledSortWatch = throttle(sortWatch, sortThrottleWait);
182
209
 
210
+ let watchesRunning = null;
211
+
183
212
  es.run(() => {
184
213
  for (const key of listInstanceStateKeys) {
185
214
  if (["order", "objectsInOrder"].includes(key)) {
@@ -209,11 +238,23 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
209
238
  deep: true,
210
239
  immediate: true,
211
240
  });
241
+ watchesRunning = useWatchesRunning({
242
+ triggerRefs: [
243
+ computed(() => (state.orderByRules && !isEmpty(state.orderByRules) ? parentState.loading : false)),
244
+ ],
245
+ watchSentinelRefs: [toRef(state, "sortCriteriaWatchRunning"), toRef(state, "sortWatchRunning")],
246
+ });
247
+
248
+ state.sortRunning = computed(() => loadingCombine(watchesRunning.state.running, state.outstandingEffects));
249
+ state.running = computed(() =>
250
+ loadingCombine(watchesRunning.state.running, state.outstandingEffects, parentState.running)
251
+ );
212
252
  });
213
253
 
214
254
  return {
215
255
  state,
216
256
  parentState,
217
257
  effectScope: es,
258
+ watchesRunning,
218
259
  };
219
260
  }
@@ -77,8 +77,8 @@ export function useListSubscriptions(args, listInstances = {}) {
77
77
  /**
78
78
  * `useListSubscription` creates a reactive object that manages a list of objects, as returned by `useListInstance`,
79
79
  * causing the list to be re-fetched as needed and listening for updates to the list.
80
- * @param {ListSubscriptionOptions} options
81
- * @returns ListSubscription
80
+ * @param {ListSubscriptionOptions} options - the configuration options for the list subscription
81
+ * @returns {ListSubscription} - the list subscription
82
82
  */
83
83
  export function useListSubscription({ listInstance, props, functions, keepOldPages = false }) {
84
84
  if (!listInstance && !props) {
package/utils/watches.js CHANGED
@@ -86,7 +86,9 @@ export function doAwaitTimeout(timeout) {
86
86
 
87
87
  export class AwaitNot {
88
88
  constructor({ obj, prop, couldAlreadyBeFalse = false, timeout = 1000 }) {
89
- this.timeout = new AwaitTimeout({ timeout });
89
+ if (timeout > 0) {
90
+ this.timeout = new AwaitTimeout({ timeout });
91
+ }
90
92
  this.promise = new Promise((resolve, reject) => {
91
93
  this.resolve = resolve;
92
94
  this.reject = reject;
@@ -97,22 +99,32 @@ export class AwaitNot {
97
99
  this.trueISW = new ImmediateStopWatch();
98
100
  this.falseISW = new ImmediateStopWatch();
99
101
  // prebuild the exception for a more useful stack.
100
- this.timeoutError = new AwaitNotError("Timeout", "timeout");
102
+ if (this.timeout) {
103
+ this.timeoutError = new AwaitNotError("Timeout", "timeout");
104
+ }
101
105
  }
102
106
 
103
107
  start() {
104
- this.timeout.promise
105
- .then(() => {
106
- this.stop();
107
- this.reject(this.timeoutError);
108
- })
109
- .catch((err) => {
110
- this.stop();
111
- if (!(err instanceof AwaitTimeoutError)) {
112
- this.reject(err);
113
- }
114
- });
115
- this.timeout.start();
108
+ if (this.timeout) {
109
+ this.timeout.promise
110
+ .then(() => {
111
+ if (this.reject) {
112
+ this.reject(this.timeoutError);
113
+ }
114
+ this.stop();
115
+ this.cleanPromise();
116
+ })
117
+ .catch((err) => {
118
+ if (!(err instanceof AwaitTimeoutError)) {
119
+ if (this.reject) {
120
+ this.reject(err);
121
+ }
122
+ this.cleanPromise();
123
+ }
124
+ this.stop();
125
+ });
126
+ this.timeout.start();
127
+ }
116
128
  if (this.obj[this.prop] === false && this.couldAlreadyBeFalse) {
117
129
  this.waitForTrue();
118
130
  } else {
@@ -146,7 +158,10 @@ export class AwaitNot {
146
158
  (newValue) => {
147
159
  if (newValue === false) {
148
160
  this.stop();
149
- this.resolve();
161
+ if (this.resolve) {
162
+ this.resolve();
163
+ }
164
+ this.cleanPromise();
150
165
  }
151
166
  },
152
167
  [this.obj[this.prop]]
@@ -161,10 +176,21 @@ export class AwaitNot {
161
176
  }
162
177
 
163
178
  stop() {
164
- this.timeout.stop();
179
+ if (this.timeout) {
180
+ this.timeout.stop();
181
+ }
165
182
  this.stopTrue();
166
183
  this.stopFalse();
167
184
  }
185
+
186
+ cleanPromise() {
187
+ if (this.resolve) {
188
+ delete this.resolve;
189
+ }
190
+ if (this.reject) {
191
+ delete this.reject;
192
+ }
193
+ }
168
194
  }
169
195
 
170
196
  export function doAwaitNot({ obj, prop, couldAlreadyBeFalse = true, timeout = 1000 }) {