@v4fire/client
Version:
V4Fire client core library
290 lines (270 loc) • 6.74 kB
text/typescript
/*!
* 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}));