-
-
Notifications
You must be signed in to change notification settings - Fork 147
/
bindable.ts
108 lines (102 loc) · 3.66 KB
/
bindable.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { Constructable, PLATFORM, Reporter } from '@aurelia/kernel';
import { BindableSource, IBindableDescription } from '../definitions';
import { BindingMode } from '../flags';
/**
* Decorator: Specifies custom behavior for a bindable property.
* @param config The overrides
*/
export function bindable(config?: BindableSource): BindableDecorator;
/**
* Decorator: Specifies a bindable property on a class.
* @param prop The property name
*/
export function bindable(prop: string): ClassDecorator;
/**
* Decorator: Specifies a bindable property on a class.
* @param target The class
* @param prop The property name
*/
export function bindable<T extends InstanceType<Constructable & Partial<WithBindables>>>(target: T, prop: string): void;
export function bindable<T extends InstanceType<Constructable & Partial<WithBindables>>>(configOrTarget?: BindableSource | T, prop?: string): void | BindableDecorator | ClassDecorator {
let config: IBindableDescription;
const decorator = function decorate($target: T, $prop: string): void {
if (arguments.length > 1) {
// Non invocation:
// - @bindable
// Invocation with or w/o opts:
// - @bindable()
// - @bindable({...opts})
config.property = $prop;
}
Bindable.for($target.constructor as Partial<WithBindables>).add(config);
};
if (arguments.length > 1) {
// Non invocation:
// - @bindable
config = {};
decorator(configOrTarget as T, prop);
return;
} else if (typeof configOrTarget === 'string') {
// ClassDecorator
// - @bindable('bar')
// Direct call:
// - @bindable('bar')(Foo)
config = {};
return decorator as BindableDecorator;
}
// Invocation with or w/o opts:
// - @bindable()
// - @bindable({...opts})
config = (configOrTarget || {}) as IBindableDescription;
return decorator as BindableDecorator;
}
interface IFluentBindableBuilder {
add(config: IBindableDescription): IFluentBindableBuilder;
add(propertyName: string): IFluentBindableBuilder;
get(): Record<string, IBindableDescription>;
}
export const Bindable = {
for<T extends Partial<WithBindables>>(obj: T): IFluentBindableBuilder {
const builder: IFluentBindableBuilder = {
add(nameOrConfig: string | IBindableDescription): typeof builder {
let description: IBindableDescription;
if (nameOrConfig !== null && typeof nameOrConfig === 'object') {
description = nameOrConfig;
} else if (typeof nameOrConfig === 'string') {
description = {
property: nameOrConfig
};
}
const prop = description.property;
if (!prop) {
throw Reporter.error(0); // TODO: create error code (must provide a property name)
}
if (!description.attribute) {
description.attribute = PLATFORM.kebabCase(prop);
}
if (!description.callback) {
description.callback = `${prop}Changed`;
}
if (description.mode === undefined) {
description.mode = BindingMode.toView;
}
obj.bindables[prop] = description;
return this;
},
get(): Record<string, IBindableDescription> {
return obj.bindables as Record<string, IBindableDescription>;
}
};
if (obj.bindables === undefined) {
obj.bindables = {};
} else if (Array.isArray(obj.bindables)) {
const props = obj.bindables;
obj.bindables = {};
props.forEach(builder.add);
}
return builder;
}
};
export type WithBindables = { bindables: Record<string, IBindableDescription> | string[] };
export type BindableDecorator = <T extends InstanceType<Constructable & Partial<WithBindables>>>
(target: T, prop: string) => void;