UNPKG

@v4fire/client

Version:

V4Fire client core library

360 lines (300 loc) • 9.97 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * [[include:core/async/README.md]] * @packageDocumentation */ import SyncPromise from 'core/promise/sync'; import Super, { AsyncCbOptions, ClearOptionsId, isAsyncOptions } from '@v4fire/core/core/async'; import { namespaces, NamespacesDictionary } from 'core/async/const'; import type { AsyncRequestAnimationFrameOptions, AsyncAnimationFrameOptions, AsyncDnDOptions, DnDEventOptions, AsyncCb, AnimationFrameCb } from 'core/async/interface'; export * from '@v4fire/core/core/async'; export * from 'core/async/const'; export * from 'core/async/helpers'; export * from 'core/async/interface'; export default class Async<CTX extends object = Async<any>> extends Super<CTX> { static override namespaces: NamespacesDictionary = namespaces; static override linkNames: NamespacesDictionary = namespaces; /** * Wrapper for requestAnimationFrame * * @param cb - callback function * @param [element] - link for the element */ requestAnimationFrame<T = unknown>(cb: AnimationFrameCb<T, CTX>, element?: Element): Nullable<number>; /** * Wrapper for requestAnimationFrame * * @param cb - callback function * @param opts - additional options for the operation */ requestAnimationFrame<T = unknown>( cb: AnimationFrameCb<T, CTX>, opts: AsyncRequestAnimationFrameOptions<CTX> ): Nullable<number>; requestAnimationFrame<T>( cb: AnimationFrameCb<T, CTX>, p?: Element | AsyncRequestAnimationFrameOptions<CTX> ): Nullable<number> { if (Object.isPlainObject(p)) { return this.registerTask({ ...p, name: this.namespaces.animationFrame, obj: cb, clearFn: cancelAnimationFrame, wrapper: requestAnimationFrame, linkByWrapper: true, args: p.element }); } return this.registerTask({ name: this.namespaces.animationFrame, obj: cb, clearFn: cancelAnimationFrame, wrapper: requestAnimationFrame, linkByWrapper: true, args: p }); } /** * Wrapper for cancelAnimationFrame * * @alias * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) */ cancelAnimationFrame(id?: number): this; /** * Clears the specified "requestAnimationFrame" timer or a group of timers * @param opts - options for the operation */ cancelAnimationFrame(opts: ClearOptionsId<number>): this; cancelAnimationFrame(task?: number | ClearOptionsId<number>): this { return this.clearAnimationFrame(Object.cast(task)); } /** * Wrapper for cancelAnimationFrame * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) */ clearAnimationFrame(id?: number): this; /** * Clears the specified "requestAnimationFrame" timer or a group of timers * @param opts - options for the operation */ clearAnimationFrame(opts: ClearOptionsId<number>): this; clearAnimationFrame(task?: number | ClearOptionsId<number>): this { return this .cancelTask(task, this.namespaces.animationFrame) .cancelTask(task, this.namespaces.animationFramePromise); } /** * Mutes the specified "requestAnimationFrame" timer * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) */ muteAnimationFrame(id?: number): this; /** * Mutes the specified "requestAnimationFrame" timer or a group of timers * @param opts - options for the operation */ muteAnimationFrame(opts: ClearOptionsId<number>): this; muteAnimationFrame(task?: number | ClearOptionsId<number>): this { return this.markTask('muted', task, this.namespaces.animationFrame); } /** * Unmutes the specified "requestAnimationFrame" timer * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) */ unmuteAnimationFrame(id?: number): this; /** * Unmutes the specified "requestAnimationFrame" timer or a group of timers * @param opts - options for the operation */ unmuteAnimationFrame(opts: ClearOptionsId<number>): this; unmuteAnimationFrame(task?: number | ClearOptionsId<number>): this { return this.markTask('!muted', task, this.namespaces.animationFrame); } /** * Suspends the specified "requestAnimationFrame" timer * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) */ suspendAnimationFrame(id?: number): this; /** * Suspends the specified "requestAnimationFrame" timer or a group of timers * @param opts - options for the operation */ suspendAnimationFrame(opts: ClearOptionsId<number>): this; suspendAnimationFrame(task?: number | ClearOptionsId<number>): this { return this.markTask('paused', task, this.namespaces.animationFrame); } /** * Unsuspends the specified "requestAnimationFrame" timer * @param [id] - operation id (if not specified, then the operation will be applied for all registered tasks) */ unsuspendAnimationFrame(id?: number): this; /** * Unsuspends the specified "requestAnimationFrame" timer or a group of timers * @param opts - options for the operation */ unsuspendAnimationFrame(opts: ClearOptionsId<number>): this; unsuspendAnimationFrame(task?: number | ClearOptionsId<number>): this { return this.markTask('!paused', task, this.namespaces.animationFrame); } /** * Returns a promise that will be resolved on the next animation frame request * @param [element] - link for the element */ animationFrame(element?: Element): SyncPromise<number>; /** * Returns a promise that will be resolved on the next animation frame request * @param opts - options for the operation */ animationFrame(opts: AsyncAnimationFrameOptions): SyncPromise<number>; animationFrame(p?: Element | AsyncAnimationFrameOptions): SyncPromise<number> { return new SyncPromise((resolve, reject) => { if (Object.isPlainObject(p)) { return this.requestAnimationFrame(resolve, { ...p, promise: true, element: p.element, onClear: <AsyncCb<CTX>>this.onPromiseClear(resolve, reject) }); } return this.requestAnimationFrame(resolve, { promise: true, element: p, onClear: <AsyncCb<CTX>>this.onPromiseClear(resolve, reject) }); }); } /** * Adds Drag&Drop listeners to the specified element * * @param el * @param [useCapture] */ dnd(el: Element, useCapture?: boolean): Nullable<string>; /** * Adds Drag&Drop listeners to the specified element * * @param el * @param opts - options for the operation */ dnd<T = unknown>(el: Element, opts: AsyncDnDOptions<T, CTX>): Nullable<string>; dnd<T>(el: Element, opts?: boolean | AsyncDnDOptions<T, CTX>): Nullable<string> { let useCapture, p: AsyncDnDOptions<T, CTX> & AsyncCbOptions<CTX>; if (isAsyncOptions<AsyncDnDOptions<T, CTX>>(opts)) { useCapture = opts.options?.capture; p = opts; } else { useCapture = opts; p = {}; } p.group = p.group ?? `dnd:${Math.random()}`; if (this.locked) { return null; } const clearHandlers = Array.concat([], p.onClear); p.onClear = clearHandlers; function dragStartClear(this: unknown, ...args: unknown[]): void { for (let i = 0; i < clearHandlers.length; i++) { clearHandlers[i].call(this, ...args, 'dragstart'); } } function dragClear(this: unknown, ...args: unknown[]): void { for (let i = 0; i < clearHandlers.length; i++) { clearHandlers[i].call(this, ...args, 'drag'); } } function dragEndClear(this: unknown, ...args: unknown[]): void { for (let i = 0; i < clearHandlers.length; i++) { clearHandlers[i].call(this, ...args, 'dragend'); } } const dragStartUseCapture = !p.onDragStart || Object.isSimpleFunction(p.onDragStart) ? useCapture : Boolean(p.onDragStart.capture); const dragUseCapture = !p.onDrag || Object.isSimpleFunction(p.onDrag) ? useCapture : Boolean(p.onDrag.capture); const dragEndUseCapture = !p.onDragEnd || Object.isSimpleFunction(p.onDragEnd) ? useCapture : Boolean(p.onDragEnd.capture); const that = this, asyncOpts = {join: p.join, label: p.label, group: p.group}; function dragStart(this: CTX, e: Event): void { e.preventDefault(); let res; if (p.onDragStart) { if (Object.isFunction(p.onDragStart)) { res = p.onDragStart.call(this, e, el); } else if (Object.isPlainObject(p.onDragStart)) { res = (<DnDEventOptions>p.onDragStart).handler.call(this, e, el); } } const drag = (e) => { e.preventDefault(); if (res !== false) { if (Object.isFunction(p.onDrag)) { res = p.onDrag.call(this, e, el); } else if (Object.isPlainObject(p.onDrag)) { res = (<DnDEventOptions>p.onDrag).handler.call(this, e, el); } } }; const links: object[] = []; { const e = ['mousemove', 'touchmove']; for (let i = 0; i < e.length; i++) { const link = that.on(document, e[i], drag, {...asyncOpts, onClear: dragClear}, dragUseCapture); if (link) { links.push(link); } } } const dragEnd = (e) => { e.preventDefault(); if (res !== false) { if (Object.isFunction(p.onDragEnd)) { res = p.onDragEnd.call(this, e, el); } else if (Object.isPlainObject(p.onDragEnd)) { res = (<DnDEventOptions>p.onDragEnd).handler.call(this, e, el); } } for (let i = 0; i < links.length; i++) { that.off(links[i]); } }; { const e = ['mouseup', 'touchend']; for (let i = 0; i < e.length; i++) { const link = that.on(document, e[i], dragEnd, {...asyncOpts, onClear: dragEndClear}, dragEndUseCapture); if (link) { links.push(link); } } } } this.on<Event>(el, 'mousedown touchstart', dragStart, {...asyncOpts, onClear: dragStartClear}, dragStartUseCapture); return p.group; } }