UNPKG

@v4fire/client

Version:

V4Fire client core library

290 lines (270 loc) • 6.74 kB
/*! * V4Fire Client Core * https://github.com/V4Fire/Client * * Released under the MIT license * https://github.com/V4Fire/Client/blob/master/LICENSE */ /** * [[include:core/component/decorators/README.md]] * @packageDocumentation */ import { paramsFactory } from 'core/component/decorators/base'; import type { InitFieldFn, DecoratorProp, DecoratorSystem, DecoratorField, DecoratorComponentAccessor, DecoratorMethod, DecoratorHook, DecoratorFieldWatcher, DecoratorMethodWatcher } from 'core/component/decorators/interface'; export * from 'core/component/decorators/base'; export * from 'core/component/decorators/interface'; /** * Marks a class property as a component prop * * @decorator * * @example * ```typescript * @component() * class bExample extends iBlock { * @prop(Number) * bla: number = 0; * * @prop({type: Number, required: false}) * baz?: number; * * @prop({type: Number, default: () => Math.random()}) * bar!: number; * } * ``` */ export const prop = paramsFactory< CanArray<Function | FunctionConstructor> | ObjectConstructor | DecoratorProp >('props', (p) => { if (Object.isFunction(p) || Object.isArray(p)) { return {type: p}; } return p; }); /** * Marks a class property as a component field. * In a regular component mutation of field properties force component re-rendering. * * @decorator * * @example * ```typescript * @component() * class bExample extends iBlock { * @field() * bla: number = 0; * * @field(() => Math.random()) * baz?: number; * } * ``` */ export const field = paramsFactory<InitFieldFn | DecoratorField>('fields', (p) => { if (Object.isFunction(p)) { return {init: p}; } return p; }); /** * Marks a class property as a system field. * Mutations of system properties never force component re-rendering. * * @decorator * * @example * ```typescript * @component() * class bExample extends iBlock { * @system() * bla: number = 0; * * @system(() => Math.random()) * baz?: number; * } * ``` */ export const system = paramsFactory<InitFieldFn | DecoratorSystem>('systemFields', (p) => { if (Object.isFunction(p)) { return {init: p}; } return p; }); /** * Attaches extra meta information to a component computed field or accessor * * @decorator * * @example * ```typescript * @component() * class bExample extends iBlock { * @computed({cache: true}) * get foo() { * return 42; * } * } * ``` */ export const computed = paramsFactory<DecoratorComponentAccessor>(null); /** * The universal decorator for a component property/accessor/method * * @decorator * * @example * ```typescript * @component() * class bExample extends iBlock { * @p({cache: true}) * get foo() { * return 42; * } * } * ``` */ export const p = paramsFactory< DecoratorProp | DecoratorField | DecoratorMethod | DecoratorComponentAccessor >(null); /** * Attaches a hook listener to a component method. * It means, that when a component is switched to the specified hook/s, the method will be invoked. * * @decorator * @example * ```typescript * @component() * class Foo extends iBlock { * @hook('mounted') * onMounted() { * * } * } * ``` */ export const hook = paramsFactory<DecoratorHook>(null, (hook) => ({hook})); /** * Attaches a watcher of a component property/event to a component method or property. * * When you watch for some property changes, the handler function can take the second argument * that refers to the old value of a property. If the object that watching is non-primitive, * the old value will be cloned from the original old value to avoid the problem when we have two * links to the one object. * * ```typescript * @component() * class Foo extends iBlock { * @field() * list: Dictionary[] = []; * * @watch('list') * onListChange(value: Dictionary[], oldValue: Dictionary[]): void { * // true * console.log(value !== oldValue); * console.log(value[0] !== oldValue[0]); * } * * // When you don't declare the second argument in a watcher, * // the previous value isn't cloned * @watch('list') * onListChangeWithoutCloning(value: Dictionary[]): void { * // true * console.log(value === arguments[1]); * console.log(value[0] === oldValue[0]); * } * * // When you watch a property in a deep and declare the second argument * // in a watcher, the previous value is cloned deeply * @watch({path: 'list', deep: true}) * onListChangeWithDeepCloning(value: Dictionary[], oldValue: Dictionary[]): void { * // true * console.log(value !== oldValue); * console.log(value[0] !== oldValue[0]); * } * * created() { * this.list.push({}); * this.list[0].foo = 1; * } * } * ``` * * To listen an event you need to use the special delimiter ":" within a path. * Also, you can specify an event emitter to listen by writing a link before ":". * For instance: * * 1. `':onChange'` - a component will listen own event "onChange"; * 2. `'localEmitter:onChange'` - a component will listen an event "onChange" from "localEmitter"; * 3. `'$parent.localEmitter:onChange'` - a component will listen an event "onChange" from "$parent.localEmitter"; * 4. `'document:scroll'` - a component will listen an event "scroll" from "window.document". * * A link to the event emitter is taken from component properties or from the global object. * The empty link '' is a link to a component itself. * * Also, if you listen an event, you can manage when start to listen the event by using special characters at the * beginning of a path string: * * 1. `'!'` - start to listen an event on the "beforeCreate" hook, for example: `'!rootEmitter:reset'`; * 2. `'?'` - start to listen an event on the "mounted" hook, for example: `'?$el:click'`. * * By default, all events start to listen on the "created" hook. * * @decorator * * @example * ```typescript * @component() * class bExample extends iBlock { * @field() * foo: Dictionary = {bla: 0}; * * // Watch for changes of "foo" * @watch('foo') * watcher1() { * * } * * // Deep watch for changes of "foo" * @watch({path: 'foo', deep: true}}) * watcher2() { * * } * * // Watch for changes of "foo.bla" * @watch('foo.bla') * watcher3() { * * } * * // Listen "onChange" event of a component * @watch(':onChange') * watcher3() { * * } * * // Listen "onChange" event of a component parentEmitter * @watch('parentEmitter:onChange') * watcher4() { * * } * } * ``` */ export const watch = paramsFactory< DecoratorFieldWatcher | DecoratorMethodWatcher >(null, (watch) => ({watch}));