mirror of
https://gitee.com/ant-design-vue/ant-design-vue.git
synced 2024-12-01 19:48:38 +08:00
feat: add vueuse feature
This commit is contained in:
parent
ac9d3e40c1
commit
7fa1fe428d
22
LICENSE
22
LICENSE
@ -44,3 +44,25 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019-PRESENT Anthony Fu<https://github.com/antfu>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
34
components/_util/hooks/_vueuse/_configurable.ts
Normal file
34
components/_util/hooks/_vueuse/_configurable.ts
Normal file
@ -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;
|
28
components/_util/hooks/_vueuse/is.ts
Normal file
28
components/_util/hooks/_vueuse/is.ts
Normal file
@ -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);
|
9
components/_util/hooks/_vueuse/resolveUnref.ts
Normal file
9
components/_util/hooks/_vueuse/resolveUnref.ts
Normal file
@ -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);
|
||||
}
|
15
components/_util/hooks/_vueuse/tryOnMounted.ts
Normal file
15
components/_util/hooks/_vueuse/tryOnMounted.ts
Normal file
@ -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);
|
||||
}
|
15
components/_util/hooks/_vueuse/tryOnScopeDispose.ts
Normal file
15
components/_util/hooks/_vueuse/tryOnScopeDispose.ts
Normal file
@ -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;
|
||||
}
|
142
components/_util/hooks/_vueuse/types.ts
Normal file
142
components/_util/hooks/_vueuse/types.ts
Normal file
@ -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;
|
||||
};
|
24
components/_util/hooks/_vueuse/unrefElement.ts
Normal file
24
components/_util/hooks/_vueuse/unrefElement.ts
Normal file
@ -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;
|
||||
}
|
65
components/_util/hooks/_vueuse/useElementSize.ts
Normal file
65
components/_util/hooks/_vueuse/useElementSize.ts
Normal file
@ -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>;
|
94
components/_util/hooks/_vueuse/useResizeObserver.ts
Normal file
94
components/_util/hooks/_vueuse/useResizeObserver.ts
Normal file
@ -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>;
|
14
components/_util/hooks/_vueuse/useSupported.ts
Normal file
14
components/_util/hooks/_vueuse/useSupported.ts
Normal file
@ -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
Block a user