UNPKG

@v4fire/client

Version:

V4Fire client core library

334 lines (276 loc) • 7.55 kB
/* eslint-disable @typescript-eslint/consistent-type-assertions */ /*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * [[include:super/i-block/modules/decorators/README.md]] * @packageDocumentation */ import type { ProxyCb } from 'core/async'; import { initEmitter, ModVal } from 'core/component'; import { p as pDecorator, prop as propDecorator, field as fieldDecorator, system as systemDecorator, watch as watchDecorator, DecoratorMethod, DecoratorComponentAccessor } from 'core/component/decorators'; import type iBlock from 'super/i-block/i-block'; import { statuses } from 'super/i-block/const'; import { waitCtxRgxp } from 'super/i-block/modules/decorators/const'; import type { DecoratorProp, DecoratorField, InitFieldFn, DecoratorFieldWatcher, DecoratorMethodWatcher, WaitStatuses, WaitDecoratorOptions, WaitOptions, DecoratorEventListenerMethod } from 'super/i-block/modules/decorators/interface'; export { hook, computed } from 'core/component/decorators'; export * from 'super/i-block/modules/decorators/interface'; /** * @see core/component/decorators/base.ts * @inheritDoc */ export const p = pDecorator as <CTX = iBlock, A = unknown, B = A>( params?: // @ts-ignore (unsafe cast) DecoratorProp<CTX, A, B> | DecoratorField<CTX, A, B> | DecoratorMethod<CTX, A, B> | DecoratorComponentAccessor ) => Function; /** * @see core/component/decorators/base.ts * @inheritDoc */ export const prop = propDecorator as <CTX = iBlock, A = unknown, B = A>( // @ts-ignore (unsafe cast) params?: CanArray<FunctionConstructor | Function> | ObjectConstructor | DecoratorProp<CTX, A, B> ) => Function; /** * @see core/component/decorators/base.ts * @inheritDoc */ export const field = fieldDecorator as <CTX = iBlock, A = unknown, B = A>( // @ts-ignore (unsafe cast) params?: InitFieldFn<CTX> | DecoratorField<CTX, A, B> ) => Function; /** * @see core/component/decorators/base.ts * @inheritDoc */ export const system = systemDecorator as <CTX = iBlock, A = unknown, B = A>( // @ts-ignore (unsafe cast) params?: InitFieldFn<CTX> | DecoratorField<CTX, A, B> ) => Function; /** * @see core/component/decorators/base.ts * @inheritDoc */ export const watch = watchDecorator as <CTX = iBlock, A = unknown, B = A>( // @ts-ignore (unsafe cast) params?: DecoratorFieldWatcher<CTX, A, B> | DecoratorMethodWatcher<CTX, A, B> ) => Function; /** * Decorates a method as a modifier handler * * @decorator * @param name - modifier name to listen * @param [value] - modifier value to listen * @param [method] - event method */ export function mod( name: string, value: ModVal = '*', method: DecoratorEventListenerMethod = 'on' ): Function { return (target, key, descriptor) => { initEmitter.once('bindConstructor', (componentName) => { initEmitter.once(`constructor.${componentName}`, ({meta}) => { meta.hooks.beforeCreate.push({ fn(this: iBlock['unsafe']): void { this.localEmitter[method](`block.mod.set.${name}.${value}`, descriptor.value.bind(this)); } }); }); }); }; } /** * Decorates a method as a remove modifier handler * * @decorator * @param name - modifier name to listen * @param [value] - modifier value to listen * @param [method] - event method */ export function removeMod( name: string, value: ModVal = '*', method: DecoratorEventListenerMethod = 'on' ): Function { return (target, key, descriptor) => { initEmitter.once('bindConstructor', (componentName) => { initEmitter.once(`constructor.${componentName}`, ({meta}) => { meta.hooks.beforeCreate.push({ fn(this: iBlock['unsafe']): void { this.localEmitter[method](`block.mod.remove.${name}.${value}`, descriptor.value.bind(this)); } }); }); }); }; } /** * Decorates a method to wait * * @see [[Async.wait]] * @decorator * * @param opts - additional options */ export function wait(opts: WaitDecoratorOptions): Function; /** * Wraps the specified function to wait a component status * * @see [[Async.wait]] * @param opts - additional options */ export function wait<F extends AnyFunction>( opts: WaitOptions<F> ): ProxyCb<Parameters<F>, CanPromise<ReturnType<F>>, iBlock>; /** * Decorates a method to wait the specified component status * * @see [[Async.wait]] * @decorator * * @param status * @param [opts] - additional options */ export function wait(status: WaitStatuses, opts?: WaitDecoratorOptions): Function; /** * Wraps the specified function to wait a component status * * @see [[Async.wait]] * @param status * @param fnOrOpts - function to wrap or additional options */ export function wait<F extends AnyFunction>( status: WaitStatuses, fnOrOpts: F | WaitOptions<F> ): ProxyCb<Parameters<F>, CanPromise<ReturnType<F>>, iBlock>; export function wait( statusOrOpts: WaitStatuses | WaitDecoratorOptions | WaitOptions, optsOrCb?: WaitDecoratorOptions | WaitOptions | Function ): Function { let status: WaitStatuses, opts: CanUndef<WaitDecoratorOptions | WaitOptions>, handler, ctx; if (Object.isFunction(optsOrCb)) { if (Object.isString(statusOrOpts)) { if (RegExp.test(waitCtxRgxp, statusOrOpts)) { ctx = RegExp.$1; status = statuses[RegExp.$2]; } else { status = statuses[statusOrOpts]; } } else { status = 0; if (Object.isPlainObject(statusOrOpts)) { opts = statusOrOpts; } } handler = optsOrCb; } else if (Object.isString(statusOrOpts)) { if (RegExp.test(waitCtxRgxp, statusOrOpts)) { ctx = RegExp.$1; status = statuses[RegExp.$2]; } else { status = statuses[statusOrOpts]; } if (Object.isPlainObject(optsOrCb)) { opts = <typeof opts>optsOrCb; handler = Object.get(opts, 'fn'); } } else { status = 0; if (Object.isPlainObject(statusOrOpts)) { opts = statusOrOpts; handler = Object.get(opts, 'fn'); } } opts ??= {}; let { join, label, group, defer } = opts; const isDecorator = !Object.isFunction(handler); function wrapper(this: iBlock['unsafe'], ...args: unknown[]): CanUndef<CanPromise<unknown>> { const getRoot = () => ctx != null ? this.field.get(ctx) : this, root = getRoot(); if (join === undefined) { join = handler.length > 0 ? 'replace' : true; } const {async: $a} = this, p = {join, label, group}; const exec = (ctx) => { const componentStatus = Number(statuses[this.componentStatus]); let res, init = false; if (componentStatus < 0 && status > componentStatus) { throw Object.assign(new Error('Component status watcher abort'), { type: 'abort' }); } if (this.isFlyweight || componentStatus >= status) { init = true; res = Object.isTruly(defer) ? $a.promise($a.nextTick().then(() => handler.apply(this, args)), p) : handler.apply(this, args); } if (!init) { res = $a.promisifyOnce(ctx, `componentStatus:${statuses[status]}`, { ...p, handler: () => handler.apply(this, args) }); } if (isDecorator && Object.isPromise(res)) { return res.catch(stderr); } return res; }; if (root != null) { return exec(root); } const res = $a.promise($a.wait(getRoot)).then(() => exec(getRoot())); if (isDecorator) { return res.catch(stderr); } return res; } if (isDecorator) { return ((target, key, descriptors) => { handler = descriptors.value; descriptors.value = wrapper; }); } return wrapper; }