-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
182 lines (181 loc) · 6.07 KB
/
index.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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// A simple, portable webcomponent on the go. Refer to docs for more info https://github.com/teamdunno/elemxx#readme
/**
* @author teamdunno <https://github.com/teamdunno>
* @version 0.5.3
* @license MIT
*/
/**
* Detach reference from existing object (only function that returns the same reference)
*
* @param val The object
* @returns New reference from the object
* @throws TypeError on worse case (if `typeof value` dosent provide anything, or forcefully put the value as `function`)
*/
export function detachRef<T extends unknown>(val: T): T {
switch (typeof val) {
case "function": {
return val as T
}
case "string": {
return val.valueOf() as T
}
case "number": {
return val.valueOf() as T
}
case "bigint": {
return val.valueOf() as T
}
case "boolean": {
return val.valueOf() as T
}
case "symbol": {
return val.valueOf() as T
}
case "undefined": {
return undefined as T
}
case "object": {
switch (Array.isArray(val)) {
case true: {
return [...val as unknown[]] as T
}
default: {
if (val === null) {
return null as T
}
return { ...val }
}
}
}
default: {
throw new TypeError("elemxx detachRef: typeof keyword dosent provide anything to detach from")
}
}
}
/** `Track` interace. Used in {@link Elemxx.track} */
export interface Track<T> {
/** The value */
value: T;
/**
* Listen only on object changes
* @param func The function that wants to listen to
*/
watch(func: (value: T) => void): void;
/**
* Trigger once and listen on object changes
* @param func The function that wants to listen to
*/
observe(func: (value: T) => void): void;
/**
* Remove event by function reference
* @param func The function that wants to remove from
*/
remove(func: (value: T) => void): void;
/** Remove all events */
removeAll(): void;
}
/** A simple, portable webcomponent on the go */
export class Elemxx extends HTMLElement {
/** CSS string for the elem. It would be appended to DOM if set */
static css?: string = undefined;
/** Attribute list. If defined, {@link Track} will be added alongside the name to the {@link Elemxx.attrs}, and not cleaned on unmounted */
static attrList?: string[] = undefined;
/** Detect if element was mounted */
protected mounted: boolean = false;
/** use {@link Elemxx.attrs} insead */
public static observedAttributes = this.attrList;
/** Attributes that are defined in {@link Elemxx.attrList}. Not cleaned when unmounted unlike normal trackers on {@link Elemxx.track} */
public readonly attrs: Record<string, Track<string | null>> = {}
private _EXX_TRACKERS: Track<unknown>[] = []
/** Run this function on mounted */
onMount() { };
/** Run this function on unmounted */
onUnmount() { };
constructor() {
super();
}
/** use {@link Elemxx.attrs} instead */
public attributeChangedCallback(k: string, _: string | null, n: string | null) {
if (typeof this.attrs[k] === "undefined") return
this.attrs[k].value = n
}
/**
* ⚠️ **Note**: This is different than `this.attrs`
*
* Shorthand for `Object.values(this.attrs)`
*/
protected eachAttrs(): Track<string | null>[] {
return Object.values(this.attrs)
}
/**
* Track the changes of value
*
* @param value The value
* @param keep (default: `false`) Prevent removal of events when elem was unmounted
* @returns [{@link Track}] object
*/
protected track<T>(value: T, keep: boolean = false): Track<T> {
let evs: ((value: T) => void)[] = []
let v: T = value;
const t: Track<T> = {
get value() {
return v
},
set value(newValue: T) {
v = newValue
if (evs.length > 0) for (let i = 0; i < evs.length; i++) evs[i](value)
},
watch: function (func: (value: T) => void): void {
evs.push(func)
},
observe: function (func: (value: T) => void): void {
func(v)
evs.push(func)
},
remove: function (func: (value: T) => void): void {
evs = evs.filter((t) => t !== func)
},
removeAll: function (): void {
evs = []
}
}
if (!keep) {
this._EXX_TRACKERS.push(t)
}
return t
}
/** use {@link Elemxx.onMount} instead */
public connectedCallback() {
// https://stackoverflow.com/a/73551405/22147523
// weird solution, but it works
const proto = Object.fromEntries(Object.entries(this.constructor));
if (proto.attrList && proto.attrList.length > 0) {
const attrList = proto.attrList as string[]
for (let i = 0; i < attrList.length; i++) {
const name = attrList[i]
const obj = this.attrs[name]
const value = this.getAttribute(name)
if (typeof obj === "object") obj.value = value;
else this.attrs[name] = this.track(value, true);
}
}
if (proto.css) {
proto.css = proto.css.replace(/:me/g, this.localName)
const stychild = document.createElement("style")
stychild.innerHTML = proto.css
this.appendChild(stychild)
}
this.onMount()
}
/** use {@link Elemxx.onUnmount} instead */
public disconnectedCallback() {
this.mounted = false
if (this._EXX_TRACKERS.length > 0) {
for (let i = 0; i < this._EXX_TRACKERS.length; i++) {
this._EXX_TRACKERS[i].removeAll()
}
}
this.onUnmount()
}
}
// done