Repository navigation
Expand file tree
/
Copy pathdecorators.ts
More file actions
129 lines (113 loc) · 4.02 KB
/
Copy pathdecorators.ts
File metadata and controls
129 lines (113 loc) · 4.02 KB
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
Copyright (c) 2022 Alethea Katherine Flowers.
Published under the standard MIT License.
Full text available at: https://opensource.org/licenses/MIT
*/
export function attribute<Type = unknown, TypeHint = unknown>(options: {
type: TypeHint;
converter?: AttributeConverter<Type, TypeHint>;
on_change?: (old_value: Type | null, new_value: Type | null) => void;
}) {
const to =
options.converter?.to_attribute ??
default_attribute_converter.to_attribute;
const from =
options.converter?.from_attribute ??
default_attribute_converter.from_attribute;
return (target: object, propertyKey: string | symbol): void => {
const attributeKey = (propertyKey as string).replace("_", "-");
let running_on_change = false;
Object.defineProperty(target, propertyKey, {
enumerable: true,
configurable: true,
get() {
return from(this.getAttribute(attributeKey), options.type);
},
set(value: Type) {
const old = this[propertyKey];
const converted = to(value, options.type);
if (converted === null) {
this.removeAttribute(attributeKey);
} else {
this.setAttribute(attributeKey, converted);
}
if (!running_on_change) {
running_on_change = true;
options.on_change?.(old, value);
running_on_change = false;
}
},
});
};
}
interface AttributeConverter<Type = unknown, TypeHint = unknown> {
to_attribute(value: Type, type?: TypeHint): unknown;
from_attribute(value: string | null, type?: TypeHint): Type;
}
const default_attribute_converter = {
to_attribute(value: unknown, type?: unknown): string | null {
if (value === null) {
return value;
}
switch (type) {
case Boolean:
return value ? "" : null;
case String:
return value as string;
case Number:
return `${value}`;
default:
throw new Error(
`Cannot convert type "${type}" and value "${value} to attribute`,
);
}
},
from_attribute(value: string | null, type?: unknown): unknown {
switch (type) {
case Boolean:
return value !== null;
case String:
return value;
case Number:
return value === null ? null : Number(value);
default:
throw new Error(
`Cannot convert type "${type}" and value "${value} to attribute`,
);
}
},
};
export function query(selector: string, cache?: boolean) {
return (target: object, propertyKey: string | symbol): void => {
const cache_key =
typeof propertyKey === "symbol" ? Symbol() : `__${propertyKey}`;
Object.defineProperty(target, propertyKey, {
enumerable: true,
configurable: true,
get() {
const this_as_record = this as unknown as {
[key: string | symbol]: Element | null;
};
if (cache && this_as_record[cache_key] !== undefined) {
return this_as_record[cache_key];
}
const result = this.renderRoot?.querySelector(selector) ?? null;
if (cache && result) {
this_as_record[cache_key] = result;
}
return result;
},
});
};
}
export function query_all(selector: string) {
return (target: object, propertyKey: string | symbol): void => {
Object.defineProperty(target, propertyKey, {
enumerable: true,
configurable: true,
get() {
return this.renderRoot?.querySelectorAll(selector) ?? [];
},
});
};
}