@arrai-innovations/reactive-helpers 18.0.2 → 19.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/README.md +7 -1
- package/config/commonCrud.js +75 -0
- package/config/listCrud.js +57 -56
- package/config/objectCrud.js +110 -77
- package/index.js +6 -1
- package/package.json +3 -2
- package/types/config/commonCrud.d.ts +19 -0
- package/types/config/commonCrud.d.ts.map +1 -0
- package/types/config/listCrud.d.ts +44 -39
- package/types/config/listCrud.d.ts.map +1 -1
- package/types/config/objectCrud.d.ts +102 -40
- package/types/config/objectCrud.d.ts.map +1 -1
- package/types/index.d.ts +6 -1
- package/types/use/cancellableIntent.d.ts +2 -13
- package/types/use/cancellableIntent.d.ts.map +1 -1
- package/types/use/listInstance.d.ts +10 -10
- package/types/use/listInstance.d.ts.map +1 -1
- package/types/use/loadingError.d.ts +30 -6
- package/types/use/loadingError.d.ts.map +1 -1
- package/types/use/objectInstance.d.ts +83 -31
- package/types/use/objectInstance.d.ts.map +1 -1
- package/types/use/objectSubscription.d.ts +33 -29
- package/types/use/objectSubscription.d.ts.map +1 -1
- package/types/use/proxyLoadingError.d.ts +8 -23
- package/types/use/proxyLoadingError.d.ts.map +1 -1
- package/types/utils/cancellableFetch.d.ts +12 -0
- package/types/utils/cancellableFetch.d.ts.map +1 -0
- package/types/utils/cancellablePromise.d.ts +40 -0
- package/types/utils/cancellablePromise.d.ts.map +1 -0
- package/types/utils/deepUnref.d.ts +26 -0
- package/types/utils/deepUnref.d.ts.map +1 -0
- package/types/utils/refIfReactive.d.ts +2 -0
- package/types/utils/refIfReactive.d.ts.map +1 -0
- package/types/utils/unwrapNested.d.ts +2 -0
- package/types/utils/unwrapNested.d.ts.map +1 -0
- package/use/cancellableIntent.js +8 -13
- package/use/listInstance.js +67 -68
- package/use/listSearch.js +1 -1
- package/use/loadingError.js +30 -6
- package/use/objectInstance.js +228 -128
- package/use/objectSubscription.js +81 -82
- package/use/proxyLoadingError.js +14 -27
- package/use/search.js +1 -1
- package/utils/cancellableFetch.js +22 -0
- package/utils/cancellablePromise.js +70 -0
- package/utils/classes.js +5 -5
- package/utils/deepUnref.js +28 -0
- package/utils/refIfReactive.js +18 -0
- package/utils/unwrapNested.js +25 -0
|
@@ -2,8 +2,9 @@ import { assignReactiveObject } from "../utils/assignReactiveObject.js";
|
|
|
2
2
|
import { loadingCombine } from "../utils/loadingCombine.js";
|
|
3
3
|
import { useCancellableIntent } from "./cancellableIntent.js";
|
|
4
4
|
import { useLoadingError } from "./loadingError.js";
|
|
5
|
-
import { useObjectInstance
|
|
6
|
-
import { computed, effectScope, reactive, toRef } from "vue";
|
|
5
|
+
import { useObjectInstance } from "./objectInstance.js";
|
|
6
|
+
import { computed, effectScope, reactive, ref, toRef, toRefs } from "vue";
|
|
7
|
+
import { CancellablePromise } from "../utils/cancellablePromise.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* A composition function for managing object subscriptions, including subscription status, errors, and reactivity.
|
|
@@ -49,38 +50,39 @@ export const objectSubscriptionFunctions = [
|
|
|
49
50
|
* The raw state of the object subscription.
|
|
50
51
|
*
|
|
51
52
|
* @typedef {object} ObjectSubscriptionRawState
|
|
52
|
-
* @property {
|
|
53
|
-
* @property {
|
|
54
|
-
* @property {
|
|
55
|
-
* @property {boolean} intendToRetrieve - Whether the object intends to retrieve.
|
|
56
|
-
* @property {boolean} intendToSubscribe - Whether the object intends to subscribe.
|
|
57
|
-
* @property {boolean} subscribed - Whether the object is subscribed.
|
|
53
|
+
* @property {import('./loadingError.js').LoadingReadonlyRef} subscriptionLoading - Whether the subscription is loading.
|
|
54
|
+
* @property {import('./loadingError.js').ErroredReadonlyRef} subscriptionErrored - Whether the subscription has errored.
|
|
55
|
+
* @property {import('./loadingError.js').ErrorReadonlyRef} subscriptionError - The error that occurred.
|
|
56
|
+
* @property {import('vue').Ref<boolean>} intendToRetrieve - Whether the object intends to retrieve.
|
|
57
|
+
* @property {import('vue').Ref<boolean>} intendToSubscribe - Whether the object intends to subscribe.
|
|
58
|
+
* @property {import('vue').Ref<boolean|undefined>} subscribed - Whether the object is subscribed.
|
|
58
59
|
*/
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
62
|
* The state of the object subscription, including both subscription and object instance states.
|
|
62
63
|
*
|
|
63
|
-
* @typedef {import('vue').
|
|
64
|
+
* @typedef {import('vue').Reactive<
|
|
64
65
|
* ObjectSubscriptionRawState &
|
|
65
66
|
* import('./objectInstance.js').ObjectInstanceRawState
|
|
66
67
|
* >} ObjectSubscriptionState
|
|
67
68
|
*/
|
|
68
69
|
|
|
69
|
-
/* eslint-disable jsdoc/valid-types -- the subscribe function signature is valid typescript that irks eslint-plugin-jsdoc */
|
|
70
70
|
/**
|
|
71
71
|
* Functions available for object subscription management.
|
|
72
72
|
*
|
|
73
73
|
* @typedef {object} ObjectSubscriptionFunctions
|
|
74
|
-
* @property {(
|
|
75
|
-
*
|
|
76
|
-
*
|
|
74
|
+
* @property {(
|
|
75
|
+
* (options?: { retrieve?: boolean }) => boolean
|
|
76
|
+
* )} subscribe - Subscribes to updates from an object, managing subscription state and handling errors internally.
|
|
77
|
+
* Ensures that only one active subscription can exist at a time to prevent duplicate calls. Returns a promise that
|
|
78
|
+
* resolves to true if the subscription was successful, and false if it failed.
|
|
77
79
|
* @property {() => boolean} unsubscribe - Unsubscribes from the object, resetting related state flags. Returns
|
|
78
80
|
* true if the object was unsubscribed, and false if it was not subscribed.
|
|
79
|
-
* @property {(data: import('./objectInstance.js').
|
|
81
|
+
* @property {(data: import('./objectInstance.js').ExistingCrudObject) => void} updateFromSubscription - Update the
|
|
82
|
+
* object from a subscription.
|
|
80
83
|
* @property {() => void} deleteFromSubscription - Delete the object from a subscription.
|
|
81
84
|
* @property {() => void} clearError - Clears any errors related to the subscription, and resets the loading state.
|
|
82
85
|
*/
|
|
83
|
-
/* eslint-enable jsdoc/valid-types */
|
|
84
86
|
|
|
85
87
|
/**
|
|
86
88
|
* Properties of the object subscription.
|
|
@@ -103,8 +105,8 @@ export const objectSubscriptionFunctions = [
|
|
|
103
105
|
* Raw props for the object subscription.
|
|
104
106
|
*
|
|
105
107
|
* @typedef {object} ObjectSubscriptionRawProps
|
|
106
|
-
* @property {boolean}
|
|
107
|
-
* @property {boolean}
|
|
108
|
+
* @property {boolean|undefined} intendToRetrieve - Whether the object intends to retrieve.
|
|
109
|
+
* @property {boolean|undefined} intendToSubscribe - Whether the object intends to subscribe.
|
|
108
110
|
*/
|
|
109
111
|
|
|
110
112
|
/**
|
|
@@ -204,33 +206,42 @@ export function useObjectSubscription({ objectInstance, props, functions }) {
|
|
|
204
206
|
);
|
|
205
207
|
}
|
|
206
208
|
}
|
|
209
|
+
|
|
210
|
+
const es = effectScope();
|
|
207
211
|
const parentState = objectInstance.state;
|
|
208
212
|
const loadingError = useLoadingError();
|
|
213
|
+
const {
|
|
214
|
+
crud: parentCrud,
|
|
215
|
+
pk: parentPk,
|
|
216
|
+
pkKey: parentPkKey,
|
|
217
|
+
retrieveArgs: parentRetrieveArgs,
|
|
218
|
+
object: parentObject,
|
|
219
|
+
deleted: parentDeleted,
|
|
220
|
+
} = toRefs(parentState);
|
|
209
221
|
/** @type {ObjectSubscriptionState} */
|
|
210
|
-
// @ts-ignore - we're going to assign all the keys later, and in the effect scope
|
|
211
222
|
const state = reactive({
|
|
223
|
+
crud: parentCrud,
|
|
224
|
+
pk: parentPk,
|
|
225
|
+
pkKey: parentPkKey,
|
|
226
|
+
retrieveArgs: parentRetrieveArgs,
|
|
227
|
+
object: parentObject,
|
|
228
|
+
deleted: parentDeleted,
|
|
212
229
|
subscriptionLoading: loadingError.loading,
|
|
213
230
|
subscriptionErrored: loadingError.errored,
|
|
214
231
|
subscriptionError: loadingError.error,
|
|
215
|
-
subscribed: undefined,
|
|
216
|
-
intendToSubscribe: false,
|
|
217
|
-
intendToRetrieve: false,
|
|
232
|
+
subscribed: ref(undefined),
|
|
233
|
+
intendToSubscribe: "intendToSubscribe" in props ? toRef(props, "intendToSubscribe") : ref(false),
|
|
234
|
+
intendToRetrieve: "intendToRetrieve" in props ? toRef(props, "intendToRetrieve") : ref(false),
|
|
235
|
+
loading: es.run(() => computed(() => loadingCombine(parentState.loading, state.subscriptionLoading))),
|
|
236
|
+
errored: es.run(() => computed(() => parentState.errored || state.subscriptionErrored)),
|
|
237
|
+
error: es.run(() => computed(() => parentState.error || state.subscriptionError)),
|
|
218
238
|
});
|
|
219
|
-
if ("intendToRetrieve" in props) {
|
|
220
|
-
// @ts-ignore - passing Ref<boolean> to boolean in an UnwrapNestedRefs is fine
|
|
221
|
-
state.intendToRetrieve = toRef(props, "intendToRetrieve");
|
|
222
|
-
}
|
|
223
|
-
if ("intendToSubscribe" in props) {
|
|
224
|
-
// @ts-ignore - passing Ref<boolean> to boolean in an UnwrapNestedRefs is fine
|
|
225
|
-
state.intendToSubscribe = toRef(props, "intendToSubscribe");
|
|
226
|
-
}
|
|
227
|
-
|
|
228
239
|
/** @type {import('./cancellableIntent.js').CancellableIntent} */
|
|
229
240
|
let subscribeIntent;
|
|
230
241
|
/** @type {import('./cancellableIntent.js').CancellableIntent} */
|
|
231
242
|
let retrieveIntent;
|
|
232
243
|
|
|
233
|
-
function updateFromSubscription(data) {
|
|
244
|
+
function updateFromSubscription(/** @type {import('./objectInstance.js').ExistingCrudObject} */ data) {
|
|
234
245
|
assignReactiveObject(parentState.object, data);
|
|
235
246
|
}
|
|
236
247
|
|
|
@@ -257,55 +268,56 @@ export function useObjectSubscription({ objectInstance, props, functions }) {
|
|
|
257
268
|
function subscribe() {
|
|
258
269
|
// this function cannot be async, or the resulting promise will lose its .cancel() method
|
|
259
270
|
if (state.subscribed) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
);
|
|
271
|
+
// we throw because we want devs to see this error in the console
|
|
272
|
+
// state.error should be for user facing errors, or unknown errors
|
|
273
|
+
throw new ObjectSubscriptionError("already subscribed or subscribing.", "already-subscribed");
|
|
263
274
|
}
|
|
264
275
|
loadingError.clearError();
|
|
265
276
|
loadingError.setLoading();
|
|
266
|
-
|
|
267
|
-
|
|
277
|
+
const isCancelled = ref(false);
|
|
278
|
+
/** @type {import('../config/objectCrud.js').CrudSubscribeCallback} */
|
|
279
|
+
const subscribeCallback = (data, action) => {
|
|
280
|
+
if (action === "delete") {
|
|
281
|
+
objectInstance.deleteFromSubscription();
|
|
282
|
+
} else {
|
|
283
|
+
objectInstance.updateFromSubscription(data);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const subscribePromise = parentState.crud.subscribe({
|
|
268
287
|
crudArgs: parentState.crud.args,
|
|
269
288
|
pk: parentState.pk,
|
|
270
289
|
pkKey: parentState.pkKey,
|
|
271
290
|
retrieveArgs: state.retrieveArgs,
|
|
272
|
-
callback:
|
|
273
|
-
|
|
274
|
-
objectInstance.deleteFromSubscription();
|
|
275
|
-
} else {
|
|
276
|
-
objectInstance.updateFromSubscription(data);
|
|
277
|
-
}
|
|
278
|
-
},
|
|
291
|
+
callback: subscribeCallback,
|
|
292
|
+
isCancelled,
|
|
279
293
|
});
|
|
280
|
-
let cancelSubscription = async () => {
|
|
281
|
-
let cancelPromise = subscribePromise.cancel();
|
|
294
|
+
let cancelSubscription = async (/** @type {any} */ reason) => {
|
|
295
|
+
let cancelPromise = subscribePromise.cancel(reason);
|
|
282
296
|
cancelSubscription = null;
|
|
283
297
|
state.subscribed = false;
|
|
284
298
|
return cancelPromise;
|
|
285
299
|
};
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
catchPromise.cancel = cancelSubscription;
|
|
308
|
-
return catchPromise;
|
|
300
|
+
return CancellablePromise(
|
|
301
|
+
subscribePromise
|
|
302
|
+
.then(() => {
|
|
303
|
+
state.subscribed = true;
|
|
304
|
+
return true;
|
|
305
|
+
})
|
|
306
|
+
.catch((/** @type {Error} */ error) => {
|
|
307
|
+
loadingError.setError(error);
|
|
308
|
+
if (cancelSubscription) {
|
|
309
|
+
// noinspection JSIgnoredPromiseFromCall
|
|
310
|
+
cancelSubscription("Subscription error cancellation");
|
|
311
|
+
cancelSubscription = null;
|
|
312
|
+
state.subscribed = false;
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
})
|
|
316
|
+
.finally(() => {
|
|
317
|
+
loadingError.clearLoading();
|
|
318
|
+
}),
|
|
319
|
+
cancelSubscription
|
|
320
|
+
);
|
|
309
321
|
}
|
|
310
322
|
|
|
311
323
|
function publicUnsubscribe() {
|
|
@@ -326,20 +338,7 @@ export function useObjectSubscription({ objectInstance, props, functions }) {
|
|
|
326
338
|
objectInstance.clearError();
|
|
327
339
|
}
|
|
328
340
|
|
|
329
|
-
const es = effectScope();
|
|
330
|
-
|
|
331
341
|
es.run(() => {
|
|
332
|
-
// @ts-ignore - loadingCombine returns a boolean|undefined, our state loading is a boolean|undefined... should be fine
|
|
333
|
-
state.loading = computed(() => loadingCombine(parentState.loading, state.subscriptionLoading));
|
|
334
|
-
// @ts-ignore - the computed value is a boolean, our state errored is a boolean... should be fine
|
|
335
|
-
state.errored = computed(() => parentState.errored || state.subscriptionErrored);
|
|
336
|
-
// @ts-ignore - the computed value is an Error|undefined, our state error is an Error|undefined... should be fine
|
|
337
|
-
state.error = computed(() => parentState.error || state.subscriptionError);
|
|
338
|
-
|
|
339
|
-
for (const key of objectInstanceStateKeys.filter((key) => !["loading", "errored", "error"].includes(key))) {
|
|
340
|
-
state[key] = toRef(parentState, key);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
342
|
subscribeIntent = useCancellableIntent({
|
|
344
343
|
awaitableWithCancel: subscribe,
|
|
345
344
|
watchArguments: reactive({
|
|
@@ -359,7 +358,7 @@ export function useObjectSubscription({ objectInstance, props, functions }) {
|
|
|
359
358
|
pkKey: toRef(parentState, "pkKey"),
|
|
360
359
|
retrieveArgs: toRef(parentState, "retrieveArgs"),
|
|
361
360
|
}),
|
|
362
|
-
// delay triggering a retrieve until the last retrieve has finished/cancelled
|
|
361
|
+
// delay triggering a retrieve until the last retrieve has finished/cancelled.
|
|
363
362
|
// cancel can still be triggered
|
|
364
363
|
guardArguments: reactive({
|
|
365
364
|
loading: toRef(state, "loading"),
|
package/use/proxyLoadingError.js
CHANGED
|
@@ -1,51 +1,38 @@
|
|
|
1
1
|
import { computed, readonly, unref } from "vue";
|
|
2
2
|
import { loadingCombine } from "../utils/loadingCombine.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {(
|
|
6
|
-
* import('./loadingError.js').LoadingErrorStatus[]
|
|
7
|
-
* )} WatchableLoadingErrorsRaw
|
|
8
|
-
*/
|
|
3
|
+
import identity from "lodash-es/identity.js";
|
|
9
4
|
|
|
10
5
|
/**
|
|
11
6
|
* A watchable collection of loading errors.
|
|
12
7
|
*
|
|
13
8
|
* @typedef {(
|
|
14
|
-
* import('vue').UnwrapNestedRefs<
|
|
15
|
-
* import('vue').Ref<
|
|
16
|
-
*
|
|
17
|
-
* )}
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* The instance of useProxyLoadingError.
|
|
22
|
-
*
|
|
23
|
-
* @typedef {import('./loadingError.js').LoadingErrorStatus} ProxyLoadingError
|
|
9
|
+
* import('vue').UnwrapNestedRefs<import('./loadingError.js').LoadingErrorStatus> |
|
|
10
|
+
* import('vue').Ref<import('./loadingError.js').LoadingErrorStatus> |
|
|
11
|
+
* import('./loadingError.js').LoadingErrorStatus
|
|
12
|
+
* )} WatchableLoadingError
|
|
24
13
|
*/
|
|
25
14
|
|
|
26
15
|
/**
|
|
27
16
|
* A composable function for managing aggregated loading and error states across multiple sources.
|
|
28
17
|
*
|
|
29
|
-
* @param {
|
|
30
|
-
* @returns {
|
|
18
|
+
* @param {WatchableLoadingError[]} loadingErrors - A collection of loading error statuses to monitor and aggregate.
|
|
19
|
+
* @returns {import('./loadingError.js').LoadingErrorStatus} An object containing aggregated reactive fields and actions for loading and error states.
|
|
31
20
|
*/
|
|
32
21
|
export function useProxyLoadingError(loadingErrors) {
|
|
33
|
-
/** @type {import("vue").ComputedRef<boolean|undefined>} */
|
|
34
22
|
const loading = computed(() =>
|
|
35
|
-
loadingCombine(...unref(loadingErrors).map((loadingError) => unref(loadingError.loading)))
|
|
23
|
+
loadingCombine(...unref(loadingErrors).map((loadingError) => unref(unref(loadingError).loading)))
|
|
36
24
|
);
|
|
37
|
-
/** @type {import("vue").ComputedRef<Error|null>} */
|
|
38
25
|
const error = computed(
|
|
39
26
|
() =>
|
|
27
|
+
/** @type {Error|null} */
|
|
40
28
|
unref(loadingErrors)
|
|
41
|
-
.map((loadingError) => unref(loadingError.error))
|
|
42
|
-
.find(
|
|
29
|
+
.map((loadingError) => unref(unref(loadingError).error))
|
|
30
|
+
.find(identity) || null
|
|
43
31
|
);
|
|
44
|
-
/** @type {import("vue").ComputedRef<boolean>} */
|
|
45
32
|
const errored = computed(() =>
|
|
46
33
|
unref(loadingErrors)
|
|
47
|
-
.map((loadingError) => unref(loadingError.errored))
|
|
48
|
-
.some(
|
|
34
|
+
.map((loadingError) => unref(unref(loadingError).errored))
|
|
35
|
+
.some(identity)
|
|
49
36
|
);
|
|
50
37
|
|
|
51
38
|
return {
|
|
@@ -53,7 +40,7 @@ export function useProxyLoadingError(loadingErrors) {
|
|
|
53
40
|
error: readonly(error),
|
|
54
41
|
errored: readonly(errored),
|
|
55
42
|
clearError: () => {
|
|
56
|
-
unref(loadingErrors).forEach((loadingError) => loadingError.clearError());
|
|
43
|
+
unref(loadingErrors).forEach((loadingError) => unref(loadingError).clearError());
|
|
57
44
|
},
|
|
58
45
|
};
|
|
59
46
|
}
|
package/use/search.js
CHANGED
|
@@ -4,7 +4,7 @@ import FlexSearch from "flexsearch";
|
|
|
4
4
|
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
5
5
|
import isEqual from "lodash-es/isEqual.js";
|
|
6
6
|
import { effectScope, reactive, toRef, watch, computed } from "vue";
|
|
7
|
-
import { deepUnref } from "
|
|
7
|
+
import { deepUnref } from "../utils/deepUnref.js";
|
|
8
8
|
|
|
9
9
|
/* minimize new Set() allocations */
|
|
10
10
|
const unionReduce = (accumulator, currentValue) => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CancellablePromise } from "./cancellablePromise.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A wrapper around fetch that adds cancellation via AbortController and returns a CancellablePromise.
|
|
5
|
+
*
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {RequestInfo} input - The URL or Request object to fetch.
|
|
8
|
+
* @param {RequestInit} init - The options for the fetch request.
|
|
9
|
+
* @param {(response: Response) => Promise<T>} transform - A function to transform the response.
|
|
10
|
+
* @returns {CancellablePromise<T>} A cancellable promise that resolves to the transformed response.
|
|
11
|
+
*/
|
|
12
|
+
export function cancellableFetch(input, init, transform) {
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
const signal = controller.signal;
|
|
15
|
+
|
|
16
|
+
const basePromise = fetch(input, { ...init, signal }).then(transform);
|
|
17
|
+
|
|
18
|
+
return CancellablePromise(basePromise, async (/** @type {any} */ reason) => {
|
|
19
|
+
controller.abort(reason);
|
|
20
|
+
await basePromise.catch(() => {});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Promise that can be cancelled.
|
|
3
|
+
*
|
|
4
|
+
* @template T
|
|
5
|
+
* @typedef {Promise<T> & { cancel: (reason?: any) => Promise<void> | void }} CancellablePromise
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A possibly cancellable promise.
|
|
10
|
+
*
|
|
11
|
+
* @template T
|
|
12
|
+
* @typedef {Promise<T> & { cancel?: (reason?: any) => Promise<void> | void }} MaybeCancellablePromise
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a cancellable promise, mostly for easy of type checking.
|
|
17
|
+
*
|
|
18
|
+
* @template T
|
|
19
|
+
* @param {Promise<T>} promise - The promise to be cancellable.
|
|
20
|
+
* @param {() => (Promise<void>|void)} cancel - The function to cancel the promise.
|
|
21
|
+
* @returns {CancellablePromise<T>} The cancellable promise.
|
|
22
|
+
*/
|
|
23
|
+
export const CancellablePromise = (promise, cancel) => {
|
|
24
|
+
const cancellablePromise = /** @type {CancellablePromise<T>} */ (promise);
|
|
25
|
+
cancellablePromise.cancel = cancel;
|
|
26
|
+
return cancellablePromise;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a rejected 'cancellable' promise.
|
|
31
|
+
*
|
|
32
|
+
* @param {any} reason - The reason for the rejection.
|
|
33
|
+
* @returns {MaybeCancellablePromise<never>} A rejected 'cancellable' promise.
|
|
34
|
+
*/
|
|
35
|
+
CancellablePromise.reject = (reason) => {
|
|
36
|
+
return Promise.reject(reason);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates a resolved 'cancellable' promise.
|
|
41
|
+
*
|
|
42
|
+
* @template T
|
|
43
|
+
* @param {T} value - The value to resolve the promise with.
|
|
44
|
+
* @returns {MaybeCancellablePromise<T>} A resolved 'cancellable' promise.
|
|
45
|
+
*/
|
|
46
|
+
CancellablePromise.resolve = (value) => {
|
|
47
|
+
return Promise.resolve(value);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wraps a promise and optionally adds a cancel method if provided.
|
|
52
|
+
*
|
|
53
|
+
* @template T
|
|
54
|
+
* @param {Promise<T>} inner - The inner promise to wrap.
|
|
55
|
+
* @param {(() => Promise<void> | void)=} cancel - Optional cancel function.
|
|
56
|
+
* @returns {MaybeCancellablePromise<T>} The wrapped promise with an optional cancel method.
|
|
57
|
+
*/
|
|
58
|
+
export function wrapMaybeCancellable(inner, cancel) {
|
|
59
|
+
let resolve, reject;
|
|
60
|
+
/** @type {MaybeCancellablePromise<T>} */
|
|
61
|
+
const wrapped = new Promise((res, rej) => {
|
|
62
|
+
resolve = res;
|
|
63
|
+
reject = rej;
|
|
64
|
+
});
|
|
65
|
+
if (cancel) {
|
|
66
|
+
wrapped.cancel = cancel;
|
|
67
|
+
}
|
|
68
|
+
inner.then(resolve).catch(reject);
|
|
69
|
+
return wrapped;
|
|
70
|
+
}
|
package/utils/classes.js
CHANGED
|
@@ -122,12 +122,12 @@ export const objectifyClasses = (...classes) => {
|
|
|
122
122
|
* @returns {CombinedClasses} - The normalized form of the CSS classes, either as a string or an object.
|
|
123
123
|
*/
|
|
124
124
|
export const combineClasses = (...classes) => {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
const filteredClasses = deepUnrefFlatten(classes).flat(Infinity).filter(identity);
|
|
126
|
+
if (filteredClasses.every(isString)) {
|
|
127
|
+
return filteredClasses.join(" ");
|
|
128
|
+
}
|
|
129
129
|
const hasObjects = filteredClasses.some(isObject);
|
|
130
|
-
if (
|
|
130
|
+
if (!hasObjects) {
|
|
131
131
|
return stringifyClasses(...filteredClasses);
|
|
132
132
|
}
|
|
133
133
|
const result = objectifyClasses(...filteredClasses);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { deepUnref as _deepUnref } from "vue-deepunref";
|
|
2
|
+
|
|
3
|
+
/* eslint-disable jsdoc/valid-types */
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Recursively unwraps refs from a nested object, array, or primitive.
|
|
7
|
+
*
|
|
8
|
+
* @template T
|
|
9
|
+
* @typedef {T extends import('vue').Ref<infer U>
|
|
10
|
+
* ? DeepUnwrap<U>
|
|
11
|
+
* : T extends Array<infer V>
|
|
12
|
+
* ? Array<DeepUnwrap<V>>
|
|
13
|
+
* : T extends object
|
|
14
|
+
* ? { [K in keyof T]: DeepUnwrap<T[K]> }
|
|
15
|
+
* : T
|
|
16
|
+
* } DeepUnwrap
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/* eslint-enable jsdoc/valid-types */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Safe, recursively-typed deep unref.
|
|
23
|
+
*
|
|
24
|
+
* @template T
|
|
25
|
+
* @param {T} val - The value to deeply unwrap.
|
|
26
|
+
* @returns {DeepUnwrap<T>} - The deeply unwrapped value.
|
|
27
|
+
*/
|
|
28
|
+
export const deepUnref = _deepUnref;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isReactive, toRef, unref } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns a ref to a property if the source is reactive, otherwise returns the unrefed value.
|
|
5
|
+
*
|
|
6
|
+
* @template T
|
|
7
|
+
* @template {keyof T} K
|
|
8
|
+
* @param {T & object | undefined | null} source - The source object.
|
|
9
|
+
* @param {K} property - The property to access.
|
|
10
|
+
* @param {T[K]} [defaultValue] - The default value to use if source or property is missing.
|
|
11
|
+
* @returns {import('vue').Ref<T[K]> | T[K] | undefined} The ref to the property if the source is reactive, otherwise the unrefed value.
|
|
12
|
+
*/
|
|
13
|
+
export const refIfReactive = (source, property, defaultValue) => {
|
|
14
|
+
if (source && isReactive(source)) {
|
|
15
|
+
return toRef(source, property);
|
|
16
|
+
}
|
|
17
|
+
return unref(source?.[property]) ?? defaultValue;
|
|
18
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isProxy, isReactive, isRef, toRaw, unref } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unwraps Vue refs and proxies to get the raw value.
|
|
5
|
+
*
|
|
6
|
+
* @template T
|
|
7
|
+
* @param {T} arg - The argument to unwrap.
|
|
8
|
+
* @returns {import('vue').UnwrapRef<T>} The unwrapped value.
|
|
9
|
+
*/
|
|
10
|
+
export const unwrapNested = (arg) => {
|
|
11
|
+
/** @type {unknown} */
|
|
12
|
+
let key = arg;
|
|
13
|
+
let proxied = isProxy(arg) || isReactive(arg);
|
|
14
|
+
let refed = isRef(arg);
|
|
15
|
+
while (proxied || refed) {
|
|
16
|
+
if (proxied) {
|
|
17
|
+
key = toRaw(key);
|
|
18
|
+
} else if (refed) {
|
|
19
|
+
key = unref(key);
|
|
20
|
+
}
|
|
21
|
+
proxied = isProxy(key);
|
|
22
|
+
refed = isRef(key);
|
|
23
|
+
}
|
|
24
|
+
return /** @type {import('vue').UnwrapRef<T>} */ (key);
|
|
25
|
+
};
|