parent
ac9d3e40c1
commit
7fa1fe428d
@ -0,0 +1,34 @@
|
|||||||
|
import { isClient } from './is';
|
||||||
|
|
||||||
|
export interface ConfigurableWindow {
|
||||||
|
/*
|
||||||
|
* Specify a custom `window` instance, e.g. working with iframes or in testing environments.
|
||||||
|
*/
|
||||||
|
window?: Window;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigurableDocument {
|
||||||
|
/*
|
||||||
|
* Specify a custom `document` instance, e.g. working with iframes or in testing environments.
|
||||||
|
*/
|
||||||
|
document?: Document;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigurableNavigator {
|
||||||
|
/*
|
||||||
|
* Specify a custom `navigator` instance, e.g. working with iframes or in testing environments.
|
||||||
|
*/
|
||||||
|
navigator?: Navigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigurableLocation {
|
||||||
|
/*
|
||||||
|
* Specify a custom `location` instance, e.g. working with iframes or in testing environments.
|
||||||
|
*/
|
||||||
|
location?: Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultWindow = /* #__PURE__ */ isClient ? window : undefined;
|
||||||
|
export const defaultDocument = /* #__PURE__ */ isClient ? window.document : undefined;
|
||||||
|
export const defaultNavigator = /* #__PURE__ */ isClient ? window.navigator : undefined;
|
||||||
|
export const defaultLocation = /* #__PURE__ */ isClient ? window.location : undefined;
|
@ -0,0 +1,28 @@
|
|||||||
|
export const isClient = typeof window !== 'undefined';
|
||||||
|
export const isDef = <T = any>(val?: T): val is T => typeof val !== 'undefined';
|
||||||
|
export const assert = (condition: boolean, ...infos: any[]) => {
|
||||||
|
if (!condition) console.warn(...infos);
|
||||||
|
};
|
||||||
|
const toString = Object.prototype.toString;
|
||||||
|
export const isBoolean = (val: any): val is boolean => typeof val === 'boolean';
|
||||||
|
export const isFunction = <T extends Function>(val: any): val is T => typeof val === 'function';
|
||||||
|
export const isNumber = (val: any): val is number => typeof val === 'number';
|
||||||
|
export const isString = (val: unknown): val is string => typeof val === 'string';
|
||||||
|
export const isObject = (val: any): val is object => toString.call(val) === '[object Object]';
|
||||||
|
export const isWindow = (val: any): val is Window =>
|
||||||
|
typeof window !== 'undefined' && toString.call(val) === '[object Window]';
|
||||||
|
export const now = () => Date.now();
|
||||||
|
export const timestamp = () => +Date.now();
|
||||||
|
export const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
|
||||||
|
export const noop = () => {};
|
||||||
|
export const rand = (min: number, max: number) => {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
};
|
||||||
|
export const isIOS =
|
||||||
|
/* #__PURE__ */ isClient &&
|
||||||
|
window?.navigator?.userAgent &&
|
||||||
|
/iP(ad|hone|od)/.test(window.navigator.userAgent);
|
||||||
|
export const hasOwn = <T extends object, K extends keyof T>(val: T, key: K): key is K =>
|
||||||
|
Object.prototype.hasOwnProperty.call(val, key);
|
@ -0,0 +1,9 @@
|
|||||||
|
import { unref } from 'vue';
|
||||||
|
import type { MaybeComputedRef } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of value/ref/getter.
|
||||||
|
*/
|
||||||
|
export function resolveUnref<T>(r: MaybeComputedRef<T>): T {
|
||||||
|
return typeof r === 'function' ? (r as any)() : unref(r);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { getCurrentInstance, nextTick, onMounted } from 'vue';
|
||||||
|
import type { Fn } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call onMounted() if it's inside a component lifecycle, if not, just call the function
|
||||||
|
*
|
||||||
|
* @param fn
|
||||||
|
* @param sync if set to false, it will run in the nextTick() of Vue
|
||||||
|
*/
|
||||||
|
export function tryOnMounted(fn: Fn, sync = true) {
|
||||||
|
if (getCurrentInstance()) onMounted(fn);
|
||||||
|
else if (sync) fn();
|
||||||
|
else nextTick(fn);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import { getCurrentScope, onScopeDispose } from 'vue';
|
||||||
|
import type { Fn } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call onScopeDispose() if it's inside a effect scope lifecycle, if not, do nothing
|
||||||
|
*
|
||||||
|
* @param fn
|
||||||
|
*/
|
||||||
|
export function tryOnScopeDispose(fn: Fn) {
|
||||||
|
if (getCurrentScope()) {
|
||||||
|
onScopeDispose(fn);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@ -0,0 +1,142 @@
|
|||||||
|
import type { ComputedRef, Ref, WatchOptions, WatchSource } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any function
|
||||||
|
*/
|
||||||
|
export type Fn = () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ref that allow to set null or undefined
|
||||||
|
*/
|
||||||
|
export type RemovableRef<T> = Omit<Ref<T>, 'value'> & {
|
||||||
|
get value(): T;
|
||||||
|
set value(value: T | null | undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `RemovableRef`
|
||||||
|
*/
|
||||||
|
export type RemoveableRef<T> = RemovableRef<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maybe it's a ref, or a plain value
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* type MaybeRef<T> = T | Ref<T>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type MaybeRef<T> = T | Ref<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maybe it's a ref, or a plain value, or a getter function
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* type MaybeComputedRef<T> = (() => T) | T | Ref<T> | ComputedRef<T>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type MaybeComputedRef<T> = MaybeReadonlyRef<T> | MaybeRef<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maybe it's a computed ref, or a getter function
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* type MaybeReadonlyRef<T> = (() => T) | ComputedRef<T>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type MaybeReadonlyRef<T> = (() => T) | ComputedRef<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make all the nested attributes of an object or array to MaybeRef<T>
|
||||||
|
*
|
||||||
|
* Good for accepting options that will be wrapped with `reactive` or `ref`
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* UnwrapRef<DeepMaybeRef<T>> === T
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export type DeepMaybeRef<T> = T extends Ref<infer V>
|
||||||
|
? MaybeRef<V>
|
||||||
|
: T extends Array<any> | object
|
||||||
|
? { [K in keyof T]: DeepMaybeRef<T[K]> }
|
||||||
|
: MaybeRef<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Infers the element type of an array
|
||||||
|
*/
|
||||||
|
export type ElementOf<T> = T extends (infer E)[] ? E : never;
|
||||||
|
|
||||||
|
export type ShallowUnwrapRef<T> = T extends Ref<infer P> ? P : T;
|
||||||
|
|
||||||
|
export type Awaitable<T> = Promise<T> | T;
|
||||||
|
|
||||||
|
export type ArgumentsType<T> = T extends (...args: infer U) => any ? U : never;
|
||||||
|
|
||||||
|
export interface Pausable {
|
||||||
|
/**
|
||||||
|
* A ref indicate whether a pausable instance is active
|
||||||
|
*/
|
||||||
|
isActive: Ref<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary pause the effect from executing
|
||||||
|
*/
|
||||||
|
pause: Fn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume the effects
|
||||||
|
*/
|
||||||
|
resume: Fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stoppable {
|
||||||
|
/**
|
||||||
|
* A ref indicate whether a stoppable instance is executing
|
||||||
|
*/
|
||||||
|
isPending: Ref<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the effect from executing
|
||||||
|
*/
|
||||||
|
stop: Fn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the effects
|
||||||
|
*/
|
||||||
|
start: Fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `Stoppable`
|
||||||
|
*/
|
||||||
|
export type Stopable = Stoppable;
|
||||||
|
|
||||||
|
export interface ConfigurableFlush {
|
||||||
|
/**
|
||||||
|
* Timing for monitoring changes, refer to WatchOptions for more details
|
||||||
|
*
|
||||||
|
* @default 'pre'
|
||||||
|
*/
|
||||||
|
flush?: WatchOptions['flush'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigurableFlushSync {
|
||||||
|
/**
|
||||||
|
* Timing for monitoring changes, refer to WatchOptions for more details.
|
||||||
|
* Unlike `watch()`, the default is set to `sync`
|
||||||
|
*
|
||||||
|
* @default 'sync'
|
||||||
|
*/
|
||||||
|
flush?: WatchOptions['flush'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal Types
|
||||||
|
export type MapSources<T> = {
|
||||||
|
[K in keyof T]: T[K] extends WatchSource<infer V> ? V : never;
|
||||||
|
};
|
||||||
|
export type MapOldSources<T, Immediate> = {
|
||||||
|
[K in keyof T]: T[K] extends WatchSource<infer V>
|
||||||
|
? Immediate extends true
|
||||||
|
? V | undefined
|
||||||
|
: V
|
||||||
|
: never;
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
import type { ComponentPublicInstance } from 'vue';
|
||||||
|
import type { MaybeComputedRef, MaybeRef } from './types';
|
||||||
|
import { resolveUnref } from './resolveUnref';
|
||||||
|
|
||||||
|
export type VueInstance = ComponentPublicInstance;
|
||||||
|
export type MaybeElementRef<T extends MaybeElement = MaybeElement> = MaybeRef<T>;
|
||||||
|
export type MaybeComputedElementRef<T extends MaybeElement = MaybeElement> = MaybeComputedRef<T>;
|
||||||
|
export type MaybeElement = HTMLElement | SVGElement | VueInstance | undefined | null;
|
||||||
|
|
||||||
|
export type UnRefElementReturn<T extends MaybeElement = MaybeElement> = T extends VueInstance
|
||||||
|
? Exclude<MaybeElement, VueInstance>
|
||||||
|
: T | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the dom element of a ref of element or Vue component instance
|
||||||
|
*
|
||||||
|
* @param elRef
|
||||||
|
*/
|
||||||
|
export function unrefElement<T extends MaybeElement>(
|
||||||
|
elRef: MaybeComputedElementRef<T>,
|
||||||
|
): UnRefElementReturn<T> {
|
||||||
|
const plain = resolveUnref(elRef);
|
||||||
|
return (plain as VueInstance)?.$el ?? plain;
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
import { ref, watch } from 'vue';
|
||||||
|
import type { MaybeComputedElementRef } from './unrefElement';
|
||||||
|
import type { UseResizeObserverOptions } from './useResizeObserver';
|
||||||
|
import { useResizeObserver } from './useResizeObserver';
|
||||||
|
import { unrefElement } from './unrefElement';
|
||||||
|
|
||||||
|
export interface ElementSize {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reactive size of an HTML element.
|
||||||
|
*
|
||||||
|
* @see https://vueuse.org/useElementSize
|
||||||
|
* @param target
|
||||||
|
* @param callback
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
export function useElementSize(
|
||||||
|
target: MaybeComputedElementRef,
|
||||||
|
initialSize: ElementSize = { width: 0, height: 0 },
|
||||||
|
options: UseResizeObserverOptions = {},
|
||||||
|
) {
|
||||||
|
const { box = 'content-box' } = options;
|
||||||
|
const width = ref(initialSize.width);
|
||||||
|
const height = ref(initialSize.height);
|
||||||
|
|
||||||
|
useResizeObserver(
|
||||||
|
target,
|
||||||
|
([entry]) => {
|
||||||
|
const boxSize =
|
||||||
|
box === 'border-box'
|
||||||
|
? entry.borderBoxSize
|
||||||
|
: box === 'content-box'
|
||||||
|
? entry.contentBoxSize
|
||||||
|
: entry.devicePixelContentBoxSize;
|
||||||
|
|
||||||
|
if (boxSize) {
|
||||||
|
width.value = boxSize.reduce((acc, { inlineSize }) => acc + inlineSize, 0);
|
||||||
|
height.value = boxSize.reduce((acc, { blockSize }) => acc + blockSize, 0);
|
||||||
|
} else {
|
||||||
|
// fallback
|
||||||
|
width.value = entry.contentRect.width;
|
||||||
|
height.value = entry.contentRect.height;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => unrefElement(target),
|
||||||
|
ele => {
|
||||||
|
width.value = ele ? initialSize.width : 0;
|
||||||
|
height.value = ele ? initialSize.height : 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UseElementSizeReturn = ReturnType<typeof useElementSize>;
|
@ -0,0 +1,94 @@
|
|||||||
|
import { tryOnScopeDispose } from './tryOnScopeDispose';
|
||||||
|
import { watch } from 'vue';
|
||||||
|
import type { MaybeComputedElementRef } from './unrefElement';
|
||||||
|
import { unrefElement } from './unrefElement';
|
||||||
|
import { useSupported } from './useSupported';
|
||||||
|
import type { ConfigurableWindow } from './_configurable';
|
||||||
|
import { defaultWindow } from './_configurable';
|
||||||
|
|
||||||
|
export interface ResizeObserverSize {
|
||||||
|
readonly inlineSize: number;
|
||||||
|
readonly blockSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResizeObserverEntry {
|
||||||
|
readonly target: Element;
|
||||||
|
readonly contentRect: DOMRectReadOnly;
|
||||||
|
readonly borderBoxSize?: ReadonlyArray<ResizeObserverSize>;
|
||||||
|
readonly contentBoxSize?: ReadonlyArray<ResizeObserverSize>;
|
||||||
|
readonly devicePixelContentBoxSize?: ReadonlyArray<ResizeObserverSize>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResizeObserverCallback = (
|
||||||
|
entries: ReadonlyArray<ResizeObserverEntry>,
|
||||||
|
observer: ResizeObserver,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export interface UseResizeObserverOptions extends ConfigurableWindow {
|
||||||
|
/**
|
||||||
|
* Sets which box model the observer will observe changes to. Possible values
|
||||||
|
* are `content-box` (the default), `border-box` and `device-pixel-content-box`.
|
||||||
|
*
|
||||||
|
* @default 'content-box'
|
||||||
|
*/
|
||||||
|
box?: ResizeObserverBoxOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare class ResizeObserver {
|
||||||
|
constructor(callback: ResizeObserverCallback);
|
||||||
|
disconnect(): void;
|
||||||
|
observe(target: Element, options?: UseResizeObserverOptions): void;
|
||||||
|
unobserve(target: Element): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reports changes to the dimensions of an Element's content or the border-box
|
||||||
|
*
|
||||||
|
* @see https://vueuse.org/useResizeObserver
|
||||||
|
* @param target
|
||||||
|
* @param callback
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
export function useResizeObserver(
|
||||||
|
target: MaybeComputedElementRef,
|
||||||
|
callback: ResizeObserverCallback,
|
||||||
|
options: UseResizeObserverOptions = {},
|
||||||
|
) {
|
||||||
|
const { window = defaultWindow, ...observerOptions } = options;
|
||||||
|
let observer: ResizeObserver | undefined;
|
||||||
|
const isSupported = useSupported(() => window && 'ResizeObserver' in window);
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
if (observer) {
|
||||||
|
observer.disconnect();
|
||||||
|
observer = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopWatch = watch(
|
||||||
|
() => unrefElement(target),
|
||||||
|
el => {
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
if (isSupported.value && window && el) {
|
||||||
|
observer = new ResizeObserver(callback);
|
||||||
|
observer!.observe(el, observerOptions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, flush: 'post' },
|
||||||
|
);
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
cleanup();
|
||||||
|
stopWatch();
|
||||||
|
};
|
||||||
|
|
||||||
|
tryOnScopeDispose(stop);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSupported,
|
||||||
|
stop,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UseResizeObserverReturn = ReturnType<typeof useResizeObserver>;
|
@ -0,0 +1,14 @@
|
|||||||
|
import { tryOnMounted } from './tryOnMounted';
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export function useSupported(callback: () => unknown, sync = false) {
|
||||||
|
const isSupported = ref() as Ref<boolean>;
|
||||||
|
|
||||||
|
const update = () => (isSupported.value = Boolean(callback()));
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
tryOnMounted(update, sync);
|
||||||
|
return isSupported;
|
||||||
|
}
|
Loading…
Reference in new issue