48 changed files with 135 additions and 610 deletions
@ -1,56 +0,0 @@ |
|||
import { ref, watchEffect, Ref } from 'vue'; |
|||
|
|||
/** |
|||
* Handle overlapping async evaluations |
|||
* |
|||
* @param cancelCallback The provided callback is invoked when a re-evaluation of the computed value is triggered before the previous one finished |
|||
*/ |
|||
export type AsyncComputedOnCancel = (cancelCallback: () => void) => void; |
|||
|
|||
/** |
|||
* A two-item tuple with the first item being a ref to the computed value and the second item holding a boolean ref, indicating whether the async computed value is currently (re-)evaluated |
|||
*/ |
|||
export type AsyncComputedResult<T> = [Ref<T>, Ref<boolean>]; |
|||
|
|||
/** |
|||
* Create an asynchronous computed dependency |
|||
* |
|||
* @param evaluationCallback The promise-returning callback which generates the computed value |
|||
* @param defaultValue A default value, used until the first evaluation finishes |
|||
*/ |
|||
export function asyncComputed<T>( |
|||
evaluationCallback: (onCancel: AsyncComputedOnCancel) => T | Promise<T>, |
|||
defaultValue?: T |
|||
): AsyncComputedResult<T> { |
|||
let counter = 0; |
|||
const current = ref(defaultValue) as Ref<T>; |
|||
const evaluating = ref<boolean>(false); |
|||
|
|||
watchEffect(async (onInvalidate: Fn) => { |
|||
counter++; |
|||
const counterAtBeginning = counter; |
|||
let hasFinished = false; |
|||
|
|||
try { |
|||
// Defer initial setting of `evaluating` ref
|
|||
// to avoid having it as a dependency
|
|||
Promise.resolve().then(() => { |
|||
evaluating.value = true; |
|||
}); |
|||
|
|||
const result = await evaluationCallback((cancelCallback) => { |
|||
onInvalidate(() => { |
|||
evaluating.value = false; |
|||
if (!hasFinished) cancelCallback(); |
|||
}); |
|||
}); |
|||
|
|||
if (counterAtBeginning === counter) current.value = result; |
|||
} finally { |
|||
evaluating.value = false; |
|||
hasFinished = true; |
|||
} |
|||
}); |
|||
|
|||
return [current, evaluating]; |
|||
} |
|||
@ -1,16 +0,0 @@ |
|||
import { ref } from 'vue'; |
|||
|
|||
export function useCounter(initialValue = 0) { |
|||
const count = ref(initialValue); |
|||
|
|||
const inc = (delta = 1) => (count.value += delta); |
|||
const dec = (delta = 1) => (count.value -= delta); |
|||
const get = () => count.value; |
|||
const set = (val: number) => (count.value = val); |
|||
const reset = (val = initialValue) => { |
|||
initialValue = val; |
|||
return set(val); |
|||
}; |
|||
|
|||
return { count, inc, dec, get, set, reset }; |
|||
} |
|||
@ -1,23 +0,0 @@ |
|||
import { isFunction } from '/@/utils/is'; |
|||
import { Ref, watch } from 'vue'; |
|||
|
|||
import { useTimeoutRef } from '/@/hooks/core/useTimeoutRef'; |
|||
|
|||
type TimeoutFnResult = [Fn<void>, Fn<void>, Ref<boolean>]; |
|||
|
|||
export function useTimeout(handle: Fn<any>, wait: number): TimeoutFnResult { |
|||
if (!isFunction(handle)) { |
|||
throw new Error('handle is not Function!'); |
|||
} |
|||
|
|||
const [readyRef, clear, runAgain] = useTimeoutRef(wait); |
|||
|
|||
watch( |
|||
readyRef, |
|||
(maturity) => { |
|||
maturity && handle(); |
|||
}, |
|||
{ immediate: false } |
|||
); |
|||
return [clear, runAgain, readyRef]; |
|||
} |
|||
@ -1,24 +0,0 @@ |
|||
import { Ref, ref } from 'vue'; |
|||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; |
|||
export type TimeoutResult = [Ref<boolean>, Fn<void>, Fn<void>]; |
|||
export function useTimeoutRef(wait: number): TimeoutResult { |
|||
const readyRef = ref(false); |
|||
|
|||
let timer: ReturnType<typeof setTimeout> | undefined; |
|||
function clear(): void { |
|||
readyRef.value = false; |
|||
timer && window.clearTimeout(timer); |
|||
} |
|||
function openTimer(): void { |
|||
clear(); |
|||
timer = setTimeout(() => { |
|||
readyRef.value = true; |
|||
}, wait); |
|||
} |
|||
|
|||
openTimer(); |
|||
|
|||
tryOnUnmounted(clear); |
|||
|
|||
return [readyRef, clear, openTimer]; |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; |
|||
import EventHub from '/@/utils/eventHub'; |
|||
const eventHub = new EventHub(); |
|||
export function useEventHub(): EventHub { |
|||
tryOnUnmounted(() => { |
|||
eventHub.clear(); |
|||
}); |
|||
|
|||
return eventHub; |
|||
} |
|||
@ -1,48 +0,0 @@ |
|||
import { Ref, watchEffect, ref } from 'vue'; |
|||
|
|||
interface IntersectionObserverProps { |
|||
target: Ref<Element | null | undefined>; |
|||
root?: Ref<Element | null | undefined>; |
|||
onIntersect: IntersectionObserverCallback; |
|||
rootMargin?: string; |
|||
threshold?: number; |
|||
} |
|||
|
|||
export function useIntersectionObserver({ |
|||
target, |
|||
root, |
|||
onIntersect, |
|||
rootMargin = '0px', |
|||
threshold = 0.1, |
|||
}: IntersectionObserverProps) { |
|||
let cleanup = () => {}; |
|||
const observer: Ref<Nullable<IntersectionObserver>> = ref(null); |
|||
const stopEffect = watchEffect(() => { |
|||
cleanup(); |
|||
|
|||
observer.value = new IntersectionObserver(onIntersect, { |
|||
root: root ? root.value : null, |
|||
rootMargin, |
|||
threshold, |
|||
}); |
|||
|
|||
const current = target.value; |
|||
|
|||
current && observer.value.observe(current); |
|||
|
|||
cleanup = () => { |
|||
if (observer.value) { |
|||
observer.value.disconnect(); |
|||
target.value && observer.value.unobserve(target.value); |
|||
} |
|||
}; |
|||
}); |
|||
|
|||
return { |
|||
observer, |
|||
stop: () => { |
|||
cleanup(); |
|||
stopEffect(); |
|||
}, |
|||
}; |
|||
} |
|||
@ -1,35 +0,0 @@ |
|||
import { ref } from 'vue'; |
|||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; |
|||
|
|||
function getTimestamp() { |
|||
return +Date.now(); |
|||
} |
|||
|
|||
export function useNow() { |
|||
const now = ref(getTimestamp()); |
|||
let started = false; |
|||
|
|||
const update = () => { |
|||
requestAnimationFrame(() => { |
|||
now.value = getTimestamp(); |
|||
if (started) update(); |
|||
}); |
|||
}; |
|||
|
|||
const start = () => { |
|||
if (!started) { |
|||
started = true; |
|||
update(); |
|||
} |
|||
}; |
|||
|
|||
const stop = () => { |
|||
started = false; |
|||
}; |
|||
|
|||
start(); |
|||
|
|||
tryOnUnmounted(stop); |
|||
|
|||
return now; |
|||
} |
|||
@ -1,95 +0,0 @@ |
|||
import { isServer } from '/@/utils/is'; |
|||
import { onUnmounted, getCurrentInstance } from 'vue'; |
|||
let lastTime = 0; |
|||
const prefixes = 'webkit moz ms o'.split(' '); // Each browser prefix
|
|||
|
|||
let requestAnimationFrame: any; |
|||
let cancelAnimationFrame: any; |
|||
|
|||
/* eslint-disable-next-line */ |
|||
const NO_LOOP = () => {}; |
|||
|
|||
const getWindowFrame = (name: string) => { |
|||
return name as any; |
|||
}; |
|||
if (isServer) { |
|||
requestAnimationFrame = cancelAnimationFrame = NO_LOOP; |
|||
} else { |
|||
requestAnimationFrame = window.requestAnimationFrame; |
|||
cancelAnimationFrame = window.cancelAnimationFrame; |
|||
let prefix; |
|||
for (let i = 0; i < prefixes.length; i++) { |
|||
if (requestAnimationFrame && cancelAnimationFrame) { |
|||
break; |
|||
} |
|||
prefix = prefixes[i]; |
|||
requestAnimationFrame = |
|||
requestAnimationFrame || window[getWindowFrame(prefix + 'RequestAnimationFrame')]; |
|||
cancelAnimationFrame = |
|||
cancelAnimationFrame || |
|||
window[getWindowFrame(prefix + 'CancelAnimationFrame')] || |
|||
window[getWindowFrame(prefix + 'CancelRequestAnimationFrame')]; |
|||
} |
|||
|
|||
// If the current browser does not support requestAnimationFrame and cancelAnimationFrame, it will fall back to setTimeout
|
|||
if (!requestAnimationFrame || !cancelAnimationFrame) { |
|||
requestAnimationFrame = function (callback: Fn) { |
|||
const currTime = new Date().getTime(); |
|||
const timeToCall = Math.max(0, 16 - (currTime - lastTime)); |
|||
const id = window.setTimeout(() => { |
|||
/* eslint-disable-next-line */ |
|||
callback(currTime + timeToCall); |
|||
}, timeToCall); |
|||
lastTime = currTime + timeToCall; |
|||
return id; |
|||
}; |
|||
|
|||
cancelAnimationFrame = function (id: number) { |
|||
window.clearTimeout(id); |
|||
}; |
|||
} |
|||
} |
|||
export function useRaf() { |
|||
// if (getCurrentInstance()) {
|
|||
// onUnmounted(() => {
|
|||
// cancelAnimationFrame();
|
|||
// });
|
|||
// }
|
|||
return { requestAnimationFrame, cancelAnimationFrame }; |
|||
} |
|||
|
|||
export function useRafFn(fn: (...arg: any) => any, options: { immediate?: boolean } = {}) { |
|||
const { immediate = false } = options; |
|||
let started = false; |
|||
let id: ReturnType<typeof window.requestAnimationFrame>; |
|||
|
|||
function loop() { |
|||
if (!started) return; |
|||
fn(); |
|||
id = requestAnimationFrame(loop); |
|||
} |
|||
|
|||
function start() { |
|||
if (!started) { |
|||
started = true; |
|||
loop(); |
|||
} |
|||
} |
|||
|
|||
function stop() { |
|||
started = false; |
|||
} |
|||
|
|||
if (immediate) { |
|||
start(); |
|||
} |
|||
|
|||
if (getCurrentInstance()) { |
|||
onUnmounted(() => { |
|||
cancelAnimationFrame(id); |
|||
stop(); |
|||
}); |
|||
} |
|||
|
|||
return { stop, start }; |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
import { reactive } from 'vue'; |
|||
|
|||
export function createGlobalState<T extends object>(factory: () => T) { |
|||
let state: T; |
|||
|
|||
return () => { |
|||
if (!state) { |
|||
state = reactive(factory()) as T; |
|||
} |
|||
return state; |
|||
}; |
|||
} |
|||
@ -1,40 +0,0 @@ |
|||
// import { ref, Ref, isRef, watch } from '@vue/runtime-dom';
|
|||
|
|||
// TODO 打开注释会造成热更新失效,待排查
|
|||
// export default function useCssVar(prop: string, refEl?: Ref<HTMLElement | null>) {
|
|||
// const refVar = ref('');
|
|||
// let el: HTMLElement = document.documentElement;
|
|||
|
|||
// if (isRef(refEl)) {
|
|||
// watch(
|
|||
// refEl,
|
|||
// () => {
|
|||
// if (refEl.value) {
|
|||
// el = refEl.value as HTMLElement;
|
|||
// refVar.value = getComputedStyle(el).getPropertyValue(prop);
|
|||
// }
|
|||
// },
|
|||
// { immediate: true }
|
|||
// );
|
|||
// } else {
|
|||
// refVar.value = getComputedStyle(el).getPropertyValue(prop);
|
|||
// }
|
|||
|
|||
// watch(
|
|||
// refVar,
|
|||
// (val) => {
|
|||
// el && el.style.setProperty(prop, val);
|
|||
// },
|
|||
// { immediate: true }
|
|||
// );
|
|||
|
|||
// return refVar;
|
|||
// }
|
|||
|
|||
export function getCssVar(prop: string, dom = document.documentElement) { |
|||
return getComputedStyle(dom).getPropertyValue(prop); |
|||
} |
|||
|
|||
export function setCssVar(prop: string, val: any, dom = document.documentElement) { |
|||
dom.style.setProperty(prop, val); |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
import { createStorage } from '/@/utils/storage'; |
|||
|
|||
export function useLocalStorage() { |
|||
return createStorage(localStorage); |
|||
} |
|||
@ -1,53 +0,0 @@ |
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { ref, watch } from 'vue'; |
|||
import { tryOnMounted, tryOnUnmounted } from '/@/utils/helper/vueHelper'; |
|||
|
|||
import { isBoolean } from '/@/utils/is'; |
|||
|
|||
const ON_LINE = 'online'; |
|||
const OFF_LINE = 'offline'; |
|||
export function useNetWork({ |
|||
onLineFn, |
|||
offLineFn, |
|||
}: { |
|||
onLineFn?: () => void; |
|||
offLineFn?: () => void; |
|||
}) { |
|||
const onLineRef = ref(navigator.onLine); |
|||
|
|||
// Disconnect time
|
|||
const offlineAt: Ref<number | undefined> = ref(undefined); |
|||
|
|||
watch( |
|||
() => onLineRef.value, |
|||
(onLine, oldValue): void => { |
|||
if (isBoolean(oldValue) && !oldValue && onLine) { |
|||
onLineFn && onLineFn(); |
|||
} else if (isBoolean(onLine) && !onLine && oldValue) { |
|||
// Network to no network
|
|||
offlineAt.value = Date.now(); |
|||
offLineFn && offLineFn(); |
|||
} |
|||
}, |
|||
{ |
|||
immediate: false, |
|||
} |
|||
); |
|||
|
|||
const handler = (e: Event) => { |
|||
const { type } = e; |
|||
onLineRef.value = type === ON_LINE; |
|||
}; |
|||
tryOnMounted(() => { |
|||
window.addEventListener(ON_LINE, handler); |
|||
window.addEventListener(OFF_LINE, handler); |
|||
}); |
|||
tryOnUnmounted(() => { |
|||
window.removeEventListener(ON_LINE, handler); |
|||
window.removeEventListener(OFF_LINE, handler); |
|||
}); |
|||
return { |
|||
onLineRef, |
|||
}; |
|||
} |
|||
@ -1,5 +0,0 @@ |
|||
import { createStorage } from '/@/utils/storage'; |
|||
|
|||
export function useSessionStorage() { |
|||
return createStorage(sessionStorage); |
|||
} |
|||
@ -1,32 +0,0 @@ |
|||
import type { Ref } from 'vue'; |
|||
|
|||
import { ref, watch } from 'vue'; |
|||
import { tryOnUnmounted } from '/@/utils/helper/vueHelper'; |
|||
import { isString } from '/@/utils/is'; |
|||
|
|||
export function useTitle(overrideTitle: string | null = null): Ref<string | null> { |
|||
const title = ref<string | null>(isString(overrideTitle) ? overrideTitle : document.title); |
|||
const observer = new MutationObserver((m) => { |
|||
title.value = m[0].target.textContent; |
|||
}); |
|||
|
|||
watch( |
|||
title, |
|||
(t, o) => { |
|||
if (isString(t) && t !== o) { |
|||
document.title = t; |
|||
} |
|||
}, |
|||
{ |
|||
immediate: true, |
|||
flush: 'sync', |
|||
} |
|||
); |
|||
|
|||
const titleElement = document.querySelector('title')!; |
|||
observer.observe(titleElement, { childList: true }); |
|||
tryOnUnmounted(() => { |
|||
observer.disconnect(); |
|||
}); |
|||
return title; |
|||
} |
|||
@ -1,20 +0,0 @@ |
|||
import { ref, onUnmounted, computed } from '@vue/runtime-dom'; |
|||
import { isDef } from '/@/utils/is'; |
|||
|
|||
export default function useVisibilityState() { |
|||
const refVisibility = ref(true); |
|||
|
|||
if (isDef(document) && isDef(document.visibilityState)) { |
|||
const setVisibility = () => { |
|||
refVisibility.value = document.visibilityState === 'visible'; |
|||
}; |
|||
|
|||
document.addEventListener('visibilitychange', setVisibility, false); |
|||
|
|||
onUnmounted(() => { |
|||
document.removeEventListener('visibilitychange', setVisibility); |
|||
}); |
|||
} |
|||
|
|||
return computed(() => refVisibility.value); |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
import { isServer } from '/@/utils/is'; |
|||
let lastTime = 0; |
|||
const prefixes = 'webkit moz ms o'.split(' '); |
|||
|
|||
let requestAnimationFrame: typeof window.requestAnimationFrame; |
|||
let cancelAnimationFrame: typeof window.cancelAnimationFrame; |
|||
(() => { |
|||
const NO_LOOP: any = () => {}; |
|||
const getWindowFrame = (name: string) => { |
|||
return name as any; |
|||
}; |
|||
if (isServer) { |
|||
requestAnimationFrame = cancelAnimationFrame = NO_LOOP; |
|||
} else { |
|||
requestAnimationFrame = window.requestAnimationFrame; |
|||
cancelAnimationFrame = window.cancelAnimationFrame; |
|||
let prefix; |
|||
for (let i = 0; i < prefixes.length; i++) { |
|||
if (requestAnimationFrame && cancelAnimationFrame) { |
|||
break; |
|||
} |
|||
prefix = prefixes[i]; |
|||
requestAnimationFrame = |
|||
requestAnimationFrame || window[getWindowFrame(prefix + 'RequestAnimationFrame')]; |
|||
cancelAnimationFrame = |
|||
cancelAnimationFrame || |
|||
window[getWindowFrame(prefix + 'CancelAnimationFrame')] || |
|||
window[getWindowFrame(prefix + 'CancelRequestAnimationFrame')]; |
|||
} |
|||
|
|||
// If the current browser does not support requestAnimationFrame and cancelAnimationFrame, it will fall back to setTimeout
|
|||
if (!requestAnimationFrame || !cancelAnimationFrame) { |
|||
requestAnimationFrame = function (callback: Fn) { |
|||
const currTime = new Date().getTime(); |
|||
const timeToCall = Math.max(0, 16 - (currTime - lastTime)); |
|||
const id = window.setTimeout(() => { |
|||
/* eslint-disable-next-line */ |
|||
callback(currTime + timeToCall); |
|||
}, timeToCall); |
|||
lastTime = currTime + timeToCall; |
|||
return id; |
|||
}; |
|||
|
|||
cancelAnimationFrame = function (id: number) { |
|||
window.clearTimeout(id); |
|||
}; |
|||
} |
|||
} |
|||
})(); |
|||
|
|||
export { requestAnimationFrame, cancelAnimationFrame }; |
|||
Loading…
Reference in new issue