@joist/element 4.0.0-next.41 → 4.0.0-next.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/attr-changed.test.ts +11 -11
- package/src/lib/attr-changed.ts +3 -3
- package/src/lib/attr.test.ts +52 -51
- package/src/lib/attr.ts +8 -8
- package/src/lib/element.test.ts +29 -29
- package/src/lib/element.ts +34 -26
- package/src/lib/lifecycle.test.ts +10 -10
- package/src/lib/lifecycle.ts +5 -2
- package/src/lib/listen.test.ts +27 -27
- package/src/lib/listen.ts +5 -5
- package/src/lib/metadata.ts +8 -6
- package/src/lib/query.test.ts +12 -12
- package/src/lib/query.ts +16 -6
- package/src/lib/tags.ts +7 -4
- package/src/lib/template.test.ts +35 -35
- package/src/lib/template.ts +27 -15
- package/src/lib.ts +7 -7
- package/target/lib/attr-changed.d.ts +2 -1
- package/target/lib/attr-changed.js +1 -1
- package/target/lib/attr-changed.js.map +1 -1
- package/target/lib/attr-changed.test.js +11 -11
- package/target/lib/attr.js +7 -7
- package/target/lib/attr.test.js +58 -57
- package/target/lib/attr.test.js.map +1 -1
- package/target/lib/element.d.ts +1 -1
- package/target/lib/element.js +24 -22
- package/target/lib/element.js.map +1 -1
- package/target/lib/element.test.js +29 -29
- package/target/lib/element.test.js.map +1 -1
- package/target/lib/lifecycle.d.ts +1 -1
- package/target/lib/lifecycle.js +1 -1
- package/target/lib/lifecycle.js.map +1 -1
- package/target/lib/lifecycle.test.js +10 -10
- package/target/lib/lifecycle.test.js.map +1 -1
- package/target/lib/listen.d.ts +1 -1
- package/target/lib/listen.js +3 -3
- package/target/lib/listen.js.map +1 -1
- package/target/lib/listen.test.js +27 -27
- package/target/lib/listen.test.js.map +1 -1
- package/target/lib/metadata.d.ts +5 -4
- package/target/lib/metadata.js +1 -1
- package/target/lib/metadata.js.map +1 -1
- package/target/lib/query.js +1 -1
- package/target/lib/query.js.map +1 -1
- package/target/lib/query.test.js +12 -12
- package/target/lib/tags.d.ts +1 -1
- package/target/lib/tags.js +6 -3
- package/target/lib/tags.js.map +1 -1
- package/target/lib/template.js +14 -12
- package/target/lib/template.js.map +1 -1
- package/target/lib/template.test.js +31 -31
- package/target/lib/template.test.js.map +1 -1
- package/target/lib.d.ts +7 -7
- package/target/lib.js +7 -7
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { assert } from
|
|
1
|
+
import { assert } from "chai";
|
|
2
2
|
|
|
3
|
-
import { attrChanged } from
|
|
4
|
-
import { attr } from
|
|
5
|
-
import { element } from
|
|
3
|
+
import { attrChanged } from "./attr-changed.js";
|
|
4
|
+
import { attr } from "./attr.js";
|
|
5
|
+
import { element } from "./element.js";
|
|
6
6
|
|
|
7
|
-
it(
|
|
7
|
+
it("should call specific attrbute callback", () => {
|
|
8
8
|
let args: string[] = [];
|
|
9
9
|
|
|
10
10
|
@element({
|
|
11
|
-
tagName:
|
|
11
|
+
tagName: "attr-changed-1",
|
|
12
12
|
})
|
|
13
13
|
class MyElement extends HTMLElement {
|
|
14
14
|
@attr()
|
|
15
|
-
accessor test =
|
|
15
|
+
accessor test = "hello";
|
|
16
16
|
|
|
17
|
-
@attrChanged(
|
|
17
|
+
@attrChanged("test")
|
|
18
18
|
onTestChanged(oldValue: string, newValue: string) {
|
|
19
19
|
args = [oldValue, newValue];
|
|
20
20
|
}
|
|
@@ -24,11 +24,11 @@ it('should call specific attrbute callback', () => {
|
|
|
24
24
|
|
|
25
25
|
document.body.append(el);
|
|
26
26
|
|
|
27
|
-
assert.deepEqual(args, [null,
|
|
27
|
+
assert.deepEqual(args, [null, "hello"]);
|
|
28
28
|
|
|
29
|
-
el.setAttribute(
|
|
29
|
+
el.setAttribute("test", "world");
|
|
30
30
|
|
|
31
|
-
assert.deepEqual(args, [
|
|
31
|
+
assert.deepEqual(args, ["hello", "world"]);
|
|
32
32
|
|
|
33
33
|
el.remove();
|
|
34
34
|
});
|
package/src/lib/attr-changed.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { metadataStore } from
|
|
1
|
+
import { type AttrChangedCallback, metadataStore } from "./metadata.js";
|
|
2
2
|
|
|
3
3
|
export function attrChanged(name: string) {
|
|
4
4
|
return function attrChangedDecorator<This extends HTMLElement>(
|
|
5
|
-
cb:
|
|
6
|
-
ctx: ClassMethodDecoratorContext<This
|
|
5
|
+
cb: AttrChangedCallback,
|
|
6
|
+
ctx: ClassMethodDecoratorContext<This>,
|
|
7
7
|
): void {
|
|
8
8
|
const meta = metadataStore.read(ctx.metadata);
|
|
9
9
|
const val = meta.attrChanges.get(name) ?? new Set();
|
package/src/lib/attr.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { expect } from
|
|
1
|
+
import { expect } from "chai";
|
|
2
2
|
|
|
3
|
-
import { attr } from
|
|
4
|
-
import { element } from
|
|
3
|
+
import { attr } from "./attr.js";
|
|
4
|
+
import { element } from "./element.js";
|
|
5
5
|
|
|
6
|
-
it(
|
|
6
|
+
it("should read and parse the correct values", () => {
|
|
7
7
|
@element({
|
|
8
|
-
tagName:
|
|
8
|
+
tagName: "attr-test-1",
|
|
9
9
|
})
|
|
10
10
|
class MyElement extends HTMLElement {
|
|
11
11
|
@attr()
|
|
@@ -18,29 +18,29 @@ it('should read and parse the correct values', () => {
|
|
|
18
18
|
accessor value3 = false; // boolean
|
|
19
19
|
|
|
20
20
|
@attr()
|
|
21
|
-
accessor value4 =
|
|
21
|
+
accessor value4 = "hello"; // string
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const container = document.createElement(
|
|
24
|
+
const container = document.createElement("div");
|
|
25
25
|
container.innerHTML = /*html*/ `
|
|
26
26
|
<attr-test-1 value2="2" value3 value4="world"></attr-test-1>
|
|
27
27
|
`;
|
|
28
28
|
|
|
29
29
|
document.body.append(container);
|
|
30
30
|
|
|
31
|
-
const el = document.querySelector(
|
|
31
|
+
const el = document.querySelector("attr-test-1") as MyElement;
|
|
32
32
|
|
|
33
33
|
expect(el.value1).to.equal(100);
|
|
34
34
|
expect(el.value2).to.equal(2);
|
|
35
35
|
expect(el.value3).to.equal(true);
|
|
36
|
-
expect(el.value4).to.equal(
|
|
36
|
+
expect(el.value4).to.equal("world");
|
|
37
37
|
|
|
38
38
|
container.remove();
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it(
|
|
41
|
+
it("should not write falsy props to attributes", async () => {
|
|
42
42
|
@element({
|
|
43
|
-
tagName:
|
|
43
|
+
tagName: "attr-test-2",
|
|
44
44
|
})
|
|
45
45
|
class MyElement extends HTMLElement {
|
|
46
46
|
@attr()
|
|
@@ -50,23 +50,23 @@ it('should not write falsy props to attributes', async () => {
|
|
|
50
50
|
accessor value2 = null;
|
|
51
51
|
|
|
52
52
|
@attr()
|
|
53
|
-
accessor value3 =
|
|
53
|
+
accessor value3 = "";
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const el = new MyElement();
|
|
57
57
|
|
|
58
|
-
expect(el.hasAttribute(
|
|
59
|
-
expect(el.hasAttribute(
|
|
60
|
-
expect(el.hasAttribute(
|
|
58
|
+
expect(el.hasAttribute("value1")).to.be.false;
|
|
59
|
+
expect(el.hasAttribute("value2")).to.be.false;
|
|
60
|
+
expect(el.hasAttribute("value3")).to.be.false;
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
it(
|
|
63
|
+
it("should update attributes when props are changed", async () => {
|
|
64
64
|
@element({
|
|
65
|
-
tagName:
|
|
65
|
+
tagName: "attr-test-3",
|
|
66
66
|
})
|
|
67
67
|
class MyElement extends HTMLElement {
|
|
68
68
|
@attr()
|
|
69
|
-
accessor value1 =
|
|
69
|
+
accessor value1 = "hello"; // no attribute
|
|
70
70
|
|
|
71
71
|
@attr()
|
|
72
72
|
accessor value2 = 0; // number
|
|
@@ -80,29 +80,30 @@ it('should update attributes when props are changed', async () => {
|
|
|
80
80
|
|
|
81
81
|
const el = new MyElement();
|
|
82
82
|
|
|
83
|
-
el.value1 =
|
|
83
|
+
el.value1 = "world";
|
|
84
84
|
el.value2 = 100;
|
|
85
85
|
el.value3 = false;
|
|
86
86
|
el.value4 = true;
|
|
87
87
|
|
|
88
|
-
expect(el.getAttribute(
|
|
89
|
-
expect(el.getAttribute(
|
|
90
|
-
expect(el.hasAttribute(
|
|
91
|
-
expect(el.hasAttribute(
|
|
88
|
+
expect(el.getAttribute("value1")).to.equal("world");
|
|
89
|
+
expect(el.getAttribute("value2")).to.equal("100");
|
|
90
|
+
expect(el.hasAttribute("value3")).to.be.false;
|
|
91
|
+
expect(el.hasAttribute("value4")).to.be.true;
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
it(
|
|
95
|
-
const
|
|
94
|
+
it("should normalize attribute names", async () => {
|
|
95
|
+
const value2 = "Value 2";
|
|
96
|
+
const value3 = Symbol("Value from SYMBOL");
|
|
96
97
|
|
|
97
98
|
@element({
|
|
98
|
-
tagName:
|
|
99
|
+
tagName: "attr-test-4",
|
|
99
100
|
})
|
|
100
101
|
class MyElement extends HTMLElement {
|
|
101
102
|
@attr()
|
|
102
|
-
accessor Value1 =
|
|
103
|
+
accessor Value1 = "hello";
|
|
103
104
|
|
|
104
105
|
@attr()
|
|
105
|
-
accessor [
|
|
106
|
+
accessor [value2] = 0;
|
|
106
107
|
|
|
107
108
|
@attr()
|
|
108
109
|
accessor [value3] = true;
|
|
@@ -113,20 +114,20 @@ it('should normalize attribute names', async () => {
|
|
|
113
114
|
document.body.append(el);
|
|
114
115
|
|
|
115
116
|
expect([...el.attributes].map((attr) => attr.name)).to.deep.equal([
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
"value1",
|
|
118
|
+
"value-2",
|
|
119
|
+
"value-from-symbol",
|
|
119
120
|
]);
|
|
120
121
|
|
|
121
122
|
el.remove();
|
|
122
123
|
});
|
|
123
124
|
|
|
124
|
-
it(
|
|
125
|
+
it("should throw an error for symbols with no description", async () => {
|
|
125
126
|
expect(() => {
|
|
126
127
|
const value = Symbol();
|
|
127
128
|
|
|
128
129
|
@element({
|
|
129
|
-
tagName:
|
|
130
|
+
tagName: "attr-test-4",
|
|
130
131
|
})
|
|
131
132
|
class MyElement extends HTMLElement {
|
|
132
133
|
@attr()
|
|
@@ -134,58 +135,58 @@ it('should throw an error for symbols with no description', async () => {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
new MyElement();
|
|
137
|
-
}).to.throw(
|
|
138
|
+
}).to.throw("Cannot handle Symbol property without description");
|
|
138
139
|
});
|
|
139
140
|
|
|
140
|
-
it(
|
|
141
|
+
it("should not reflect property to attribute", async () => {
|
|
141
142
|
@element({
|
|
142
|
-
tagName:
|
|
143
|
+
tagName: "attr-test-5",
|
|
143
144
|
})
|
|
144
145
|
class MyElement extends HTMLElement {
|
|
145
146
|
@attr({ reflect: false })
|
|
146
|
-
accessor value =
|
|
147
|
+
accessor value = "foo";
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
const el = new MyElement();
|
|
150
|
-
el.value =
|
|
151
|
+
el.value = "bar";
|
|
151
152
|
|
|
152
|
-
expect(el.value).to.equal(
|
|
153
|
+
expect(el.value).to.equal("bar");
|
|
153
154
|
|
|
154
|
-
expect(el.hasAttribute(
|
|
155
|
+
expect(el.hasAttribute("value")).to.be.false;
|
|
155
156
|
});
|
|
156
157
|
|
|
157
|
-
it(
|
|
158
|
+
it("non reflective attributes should still read new attribute values", async () => {
|
|
158
159
|
@element({
|
|
159
|
-
tagName:
|
|
160
|
+
tagName: "attr-test-6",
|
|
160
161
|
})
|
|
161
162
|
class MyElement extends HTMLElement {
|
|
162
163
|
@attr({ reflect: false })
|
|
163
|
-
accessor value =
|
|
164
|
+
accessor value = "foo";
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
const el = new MyElement();
|
|
167
|
-
el.setAttribute(
|
|
168
|
+
el.setAttribute("value", "bar");
|
|
168
169
|
|
|
169
|
-
expect(el.value).to.equal(
|
|
170
|
+
expect(el.value).to.equal("bar");
|
|
170
171
|
});
|
|
171
172
|
|
|
172
|
-
it(
|
|
173
|
+
it("should allow a manually defined attribute name", async () => {
|
|
173
174
|
@element({
|
|
174
|
-
tagName:
|
|
175
|
+
tagName: "attr-test-7",
|
|
175
176
|
})
|
|
176
177
|
class MyElement extends HTMLElement {
|
|
177
178
|
@attr({
|
|
178
|
-
name:
|
|
179
|
+
name: "aria-label",
|
|
179
180
|
})
|
|
180
|
-
accessor value =
|
|
181
|
+
accessor value = "";
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
const el = new MyElement();
|
|
184
|
-
el.setAttribute(
|
|
185
|
+
el.setAttribute("aria-label", "TEST");
|
|
185
186
|
|
|
186
187
|
document.body.append(el);
|
|
187
188
|
|
|
188
|
-
expect(el.value).to.equal(
|
|
189
|
+
expect(el.value).to.equal("TEST");
|
|
189
190
|
|
|
190
191
|
el.remove();
|
|
191
192
|
});
|
package/src/lib/attr.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { metadataStore } from
|
|
1
|
+
import { metadataStore } from "./metadata.js";
|
|
2
2
|
|
|
3
3
|
export interface AttrOpts {
|
|
4
4
|
name?: string;
|
|
@@ -9,7 +9,7 @@ export interface AttrOpts {
|
|
|
9
9
|
export function attr(opts?: AttrOpts) {
|
|
10
10
|
return function attrDecorator<This extends HTMLElement>(
|
|
11
11
|
{ get, set }: ClassAccessorDecoratorTarget<This, unknown>,
|
|
12
|
-
ctx: ClassAccessorDecoratorContext<This
|
|
12
|
+
ctx: ClassAccessorDecoratorContext<This>,
|
|
13
13
|
): ClassAccessorDecoratorResult<This, any> {
|
|
14
14
|
const attrName = opts?.name ?? parseAttrName(ctx.name);
|
|
15
15
|
const meta = metadataStore.read<This>(ctx.metadata);
|
|
@@ -20,7 +20,7 @@ export function attr(opts?: AttrOpts) {
|
|
|
20
20
|
observe: opts?.observed ?? true,
|
|
21
21
|
reflect,
|
|
22
22
|
getPropValue: get,
|
|
23
|
-
setPropValue: set
|
|
23
|
+
setPropValue: set,
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
return {
|
|
@@ -28,7 +28,7 @@ export function attr(opts?: AttrOpts) {
|
|
|
28
28
|
if (reflect) {
|
|
29
29
|
if (value === true) {
|
|
30
30
|
if (!this.hasAttribute(attrName)) {
|
|
31
|
-
this.setAttribute(attrName,
|
|
31
|
+
this.setAttribute(attrName, "");
|
|
32
32
|
}
|
|
33
33
|
} else if (value === false) {
|
|
34
34
|
if (this.hasAttribute(attrName)) {
|
|
@@ -44,7 +44,7 @@ export function attr(opts?: AttrOpts) {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
set.call(this, value);
|
|
47
|
-
}
|
|
47
|
+
},
|
|
48
48
|
};
|
|
49
49
|
};
|
|
50
50
|
}
|
|
@@ -52,15 +52,15 @@ export function attr(opts?: AttrOpts) {
|
|
|
52
52
|
function parseAttrName(val: string | symbol): string {
|
|
53
53
|
let value: string;
|
|
54
54
|
|
|
55
|
-
if (typeof val ===
|
|
55
|
+
if (typeof val === "symbol") {
|
|
56
56
|
if (val.description) {
|
|
57
57
|
value = val.description;
|
|
58
58
|
} else {
|
|
59
|
-
throw new Error(
|
|
59
|
+
throw new Error("Cannot handle Symbol property without description");
|
|
60
60
|
}
|
|
61
61
|
} else {
|
|
62
62
|
value = val;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
return value.toLowerCase().replaceAll(
|
|
65
|
+
return value.toLowerCase().replaceAll(" ", "-");
|
|
66
66
|
}
|
package/src/lib/element.test.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assert, expect } from "chai";
|
|
2
2
|
|
|
3
|
-
import { attr } from
|
|
4
|
-
import { element } from
|
|
5
|
-
import { css, html } from
|
|
3
|
+
import { attr } from "./attr.js";
|
|
4
|
+
import { element } from "./element.js";
|
|
5
|
+
import { css, html } from "./tags.js";
|
|
6
6
|
|
|
7
|
-
it(
|
|
7
|
+
it("should write default value to attribute", async () => {
|
|
8
8
|
@element({
|
|
9
|
-
tagName:
|
|
9
|
+
tagName: "element-1",
|
|
10
10
|
})
|
|
11
11
|
class MyElement extends HTMLElement {
|
|
12
12
|
@attr()
|
|
13
|
-
accessor value1 =
|
|
13
|
+
accessor value1 = "hello"; // no attribute
|
|
14
14
|
|
|
15
15
|
@attr()
|
|
16
16
|
accessor value2 = 0; // number
|
|
@@ -19,17 +19,17 @@ it('should write default value to attribute', async () => {
|
|
|
19
19
|
accessor value3 = true; // boolean
|
|
20
20
|
|
|
21
21
|
@attr({ reflect: false })
|
|
22
|
-
accessor value4 =
|
|
22
|
+
accessor value4 = "foo";
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const el = new MyElement();
|
|
26
26
|
|
|
27
27
|
document.body.append(el);
|
|
28
28
|
|
|
29
|
-
expect(el.getAttribute(
|
|
30
|
-
expect(el.getAttribute(
|
|
31
|
-
expect(el.getAttribute(
|
|
32
|
-
expect(el.getAttribute(
|
|
29
|
+
expect(el.getAttribute("value1")).to.equal("hello");
|
|
30
|
+
expect(el.getAttribute("value2")).to.equal("0");
|
|
31
|
+
expect(el.getAttribute("value3")).to.equal("");
|
|
32
|
+
expect(el.getAttribute("value4")).to.equal(null);
|
|
33
33
|
|
|
34
34
|
el.remove();
|
|
35
35
|
});
|
|
@@ -60,10 +60,10 @@ it('should write default value to attribute', async () => {
|
|
|
60
60
|
// ]);
|
|
61
61
|
// });
|
|
62
62
|
|
|
63
|
-
it(
|
|
63
|
+
it("should attach shadow root when the shadow property exists", async () => {
|
|
64
64
|
@element({
|
|
65
|
-
tagName:
|
|
66
|
-
shadowDom: []
|
|
65
|
+
tagName: "element-3",
|
|
66
|
+
shadowDom: [],
|
|
67
67
|
})
|
|
68
68
|
class MyElement extends HTMLElement {}
|
|
69
69
|
|
|
@@ -72,9 +72,9 @@ it('should attach shadow root when the shadow property exists', async () => {
|
|
|
72
72
|
expect(el.shadowRoot).to.be.instanceOf(ShadowRoot);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
it(
|
|
75
|
+
it("should apply html and css", async () => {
|
|
76
76
|
@element({
|
|
77
|
-
tagName:
|
|
77
|
+
tagName: "element-4",
|
|
78
78
|
shadowDom: [
|
|
79
79
|
css`
|
|
80
80
|
:host {
|
|
@@ -84,30 +84,30 @@ it('should apply html and css', async () => {
|
|
|
84
84
|
html`<slot></slot>`,
|
|
85
85
|
{
|
|
86
86
|
apply(el) {
|
|
87
|
-
const div = document.createElement(
|
|
88
|
-
div.innerHTML =
|
|
87
|
+
const div = document.createElement("div");
|
|
88
|
+
div.innerHTML = "hello world";
|
|
89
89
|
|
|
90
90
|
el.append(div);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
]
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
94
|
})
|
|
95
95
|
class MyElement extends HTMLElement {}
|
|
96
96
|
|
|
97
97
|
const el = new MyElement();
|
|
98
98
|
|
|
99
|
-
expect(el.shadowRoot
|
|
100
|
-
expect(el.shadowRoot
|
|
101
|
-
expect(el.innerHTML).to.equal(
|
|
99
|
+
expect(el.shadowRoot?.adoptedStyleSheets.length).to.equal(1);
|
|
100
|
+
expect(el.shadowRoot?.innerHTML).to.equal("<slot></slot>");
|
|
101
|
+
expect(el.innerHTML).to.equal("<div>hello world</div>");
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
it(
|
|
104
|
+
it("should the correct shadow dom mode", async () => {
|
|
105
105
|
@element({
|
|
106
|
-
tagName:
|
|
106
|
+
tagName: "element-5",
|
|
107
107
|
shadowDom: [],
|
|
108
108
|
shadowDomOpts: {
|
|
109
|
-
mode:
|
|
110
|
-
}
|
|
109
|
+
mode: "closed",
|
|
110
|
+
},
|
|
111
111
|
})
|
|
112
112
|
class MyElement extends HTMLElement {}
|
|
113
113
|
|
package/src/lib/element.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AttrMetadata, metadataStore } from
|
|
2
|
-
import { ShadowResult } from
|
|
1
|
+
import { type AttrMetadata, metadataStore } from "./metadata.js";
|
|
2
|
+
import type { ShadowResult } from "./result.js";
|
|
3
3
|
|
|
4
4
|
export interface ElementOpts {
|
|
5
5
|
tagName?: string;
|
|
@@ -27,27 +27,31 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
|
|
|
27
27
|
[Base.name]: class extends Base {
|
|
28
28
|
static observedAttributes: string[] = Array.from(meta.attrs.keys());
|
|
29
29
|
|
|
30
|
-
#
|
|
30
|
+
#abortController: AbortController | null = null;
|
|
31
31
|
|
|
32
32
|
constructor(...args: any[]) {
|
|
33
33
|
super(...args);
|
|
34
34
|
|
|
35
35
|
if (opts?.shadowDom) {
|
|
36
36
|
if (!this.shadowRoot) {
|
|
37
|
-
this.attachShadow(opts.shadowDomOpts ?? { mode:
|
|
37
|
+
this.attachShadow(opts.shadowDomOpts ?? { mode: "open" });
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
for (
|
|
40
|
+
for (const res of opts.shadowDom) {
|
|
41
41
|
res.apply(this);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
for (
|
|
45
|
+
for (const cb of meta.onReady) {
|
|
46
46
|
cb.call(this);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
attributeChangedCallback(
|
|
50
|
+
attributeChangedCallback(
|
|
51
|
+
name: string,
|
|
52
|
+
oldValue: string,
|
|
53
|
+
newValue: string,
|
|
54
|
+
) {
|
|
51
55
|
const attr = meta.attrs.get(name);
|
|
52
56
|
const cbs = meta.attrChanges.get(name);
|
|
53
57
|
|
|
@@ -55,10 +59,10 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
|
|
|
55
59
|
if (oldValue !== newValue) {
|
|
56
60
|
const ogValue = attr.getPropValue.call(this);
|
|
57
61
|
|
|
58
|
-
if (newValue ===
|
|
62
|
+
if (newValue === "") {
|
|
59
63
|
// treat as boolean
|
|
60
64
|
attr.setPropValue.call(this, true);
|
|
61
|
-
} else if (typeof ogValue ===
|
|
65
|
+
} else if (typeof ogValue === "number") {
|
|
62
66
|
// treat as number
|
|
63
67
|
attr.setPropValue.call(this, Number(newValue));
|
|
64
68
|
} else {
|
|
@@ -68,29 +72,29 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
|
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
if (cbs) {
|
|
71
|
-
for (
|
|
75
|
+
for (const cb of cbs) {
|
|
72
76
|
cb.call(this, oldValue, newValue);
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
if (
|
|
77
|
-
super.attributeChangedCallback
|
|
80
|
+
if (attr.observe) {
|
|
81
|
+
if (super.attributeChangedCallback) {
|
|
82
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
83
|
+
}
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
connectedCallback() {
|
|
83
89
|
if (this.isConnected) {
|
|
84
|
-
for (
|
|
90
|
+
for (const { event, cb, selector } of meta.listeners) {
|
|
85
91
|
const root = selector(this);
|
|
86
92
|
|
|
87
93
|
if (root) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
root.addEventListener(event, thisCb);
|
|
94
|
+
this.#abortController = new AbortController();
|
|
91
95
|
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
root.addEventListener(event, cb.bind(this), {
|
|
97
|
+
signal: this.#abortController.signal,
|
|
94
98
|
});
|
|
95
99
|
} else {
|
|
96
100
|
throw new Error(`could not add listener to ${root}`);
|
|
@@ -106,33 +110,37 @@ export function element<T extends ElementConstructor>(opts?: ElementOpts) {
|
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
disconnectedCallback(): void {
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
if (this.#abortController) {
|
|
114
|
+
this.#abortController.abort();
|
|
115
|
+
this.#abortController = null;
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
if (super.disconnectedCallback) {
|
|
114
119
|
super.disconnectedCallback();
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
|
-
}
|
|
122
|
+
},
|
|
118
123
|
};
|
|
119
124
|
|
|
120
125
|
return def[Base.name];
|
|
121
126
|
};
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
function reflectAttributeValues<T extends HTMLElement>(
|
|
125
|
-
|
|
129
|
+
function reflectAttributeValues<T extends HTMLElement>(
|
|
130
|
+
el: T,
|
|
131
|
+
attrs: AttrMetadata,
|
|
132
|
+
) {
|
|
133
|
+
for (const [attrName, { getPropValue, reflect }] of attrs) {
|
|
126
134
|
if (reflect) {
|
|
127
135
|
const value = getPropValue.call(el);
|
|
128
136
|
|
|
129
137
|
// reflect values back to attributes
|
|
130
|
-
if (value !== null && value !== undefined && value !==
|
|
131
|
-
if (typeof value ===
|
|
138
|
+
if (value !== null && value !== undefined && value !== "") {
|
|
139
|
+
if (typeof value === "boolean") {
|
|
132
140
|
if (value === true) {
|
|
133
141
|
// set boolean attribute
|
|
134
142
|
if (!el.hasAttribute(attrName)) {
|
|
135
|
-
el.setAttribute(attrName,
|
|
143
|
+
el.setAttribute(attrName, "");
|
|
136
144
|
}
|
|
137
145
|
}
|
|
138
146
|
} else {
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { assert } from
|
|
2
|
-
import { element } from
|
|
3
|
-
import { ready } from
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
import { element } from "./element.js";
|
|
3
|
+
import { ready } from "./lifecycle.js";
|
|
4
4
|
|
|
5
|
-
it(
|
|
5
|
+
it("should call all callbacks when template is ready", () => {
|
|
6
6
|
@element({
|
|
7
|
-
tagName:
|
|
7
|
+
tagName: "template-ready-1",
|
|
8
8
|
})
|
|
9
9
|
class MyElement extends HTMLElement {
|
|
10
10
|
callCount: Record<string, number> = {};
|
|
11
11
|
|
|
12
12
|
@ready()
|
|
13
13
|
onTemplateReady1() {
|
|
14
|
-
this.callCount
|
|
15
|
-
this.callCount
|
|
14
|
+
this.callCount.onTemplateReady1 ??= 0;
|
|
15
|
+
this.callCount.onTemplateReady1++;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
@ready()
|
|
19
19
|
onTemplateReady2() {
|
|
20
|
-
this.callCount
|
|
21
|
-
this.callCount
|
|
20
|
+
this.callCount.onTemplateReady2 ??= 0;
|
|
21
|
+
this.callCount.onTemplateReady2++;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -26,6 +26,6 @@ it('should call all callbacks when template is ready', () => {
|
|
|
26
26
|
|
|
27
27
|
assert.deepEqual(el.callCount, {
|
|
28
28
|
onTemplateReady1: 1,
|
|
29
|
-
onTemplateReady2: 1
|
|
29
|
+
onTemplateReady2: 1,
|
|
30
30
|
});
|
|
31
31
|
});
|
package/src/lib/lifecycle.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { metadataStore } from
|
|
1
|
+
import { metadataStore } from "./metadata.js";
|
|
2
2
|
|
|
3
3
|
export function ready() {
|
|
4
|
-
return function readyDecorator(
|
|
4
|
+
return function readyDecorator(
|
|
5
|
+
val: () => void,
|
|
6
|
+
ctx: ClassMethodDecoratorContext,
|
|
7
|
+
): void {
|
|
5
8
|
const metadata = metadataStore.read(ctx.metadata);
|
|
6
9
|
|
|
7
10
|
metadata.onReady.add(val);
|