@joist/element 4.0.0-next.3 → 4.0.0-next.5
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/README.md +2 -2
- package/package.json +4 -3
- package/src/lib/attr.test.ts +107 -98
- package/src/lib/attr.ts +2 -2
- package/src/lib/element.test.ts +91 -80
- package/src/lib/element.ts +15 -7
- package/src/lib/lifecycle.test.ts +31 -0
- package/src/lib/lifecycle.ts +9 -0
- package/src/lib/metadata.ts +1 -0
- package/src/lib/query.test.ts +53 -0
- package/src/lib/query.ts +37 -0
- package/src/lib/result.ts +2 -22
- package/src/lib/tags.ts +28 -13
- package/src/lib/template.test.ts +65 -0
- package/src/lib/template.ts +132 -0
- package/src/lib.ts +3 -1
- package/target/lib/attr.d.ts +1 -1
- package/target/lib/attr.js +1 -1
- package/target/lib/attr.js.map +1 -1
- package/target/lib/attr.test.js +258 -252
- package/target/lib/attr.test.js.map +1 -1
- package/target/lib/element.d.ts +2 -2
- package/target/lib/element.js +12 -5
- package/target/lib/element.js.map +1 -1
- package/target/lib/element.test.js +185 -177
- package/target/lib/element.test.js.map +1 -1
- package/target/lib/lifecycle.d.ts +1 -0
- package/target/lib/lifecycle.js +8 -0
- package/target/lib/lifecycle.js.map +1 -0
- package/target/lib/lifecycle.test.js +48 -0
- package/target/lib/lifecycle.test.js.map +1 -0
- package/target/lib/metadata.d.ts +1 -0
- package/target/lib/metadata.js +1 -0
- package/target/lib/metadata.js.map +1 -1
- package/target/lib/query.d.ts +9 -0
- package/target/lib/query.js +24 -0
- package/target/lib/query.js.map +1 -0
- package/target/lib/query.test.d.ts +1 -0
- package/target/lib/query.test.js +78 -0
- package/target/lib/query.test.js.map +1 -0
- package/target/lib/result.d.ts +2 -11
- package/target/lib/result.js +1 -22
- package/target/lib/result.js.map +1 -1
- package/target/lib/tags.d.ts +11 -9
- package/target/lib/tags.js +18 -24
- package/target/lib/tags.js.map +1 -1
- package/target/lib/template.d.ts +6 -0
- package/target/lib/template.js +91 -0
- package/target/lib/template.js.map +1 -0
- package/target/lib/template.test.d.ts +1 -0
- package/target/lib/template.test.js +47 -0
- package/target/lib/template.test.js.map +1 -0
- package/target/lib.d.ts +3 -1
- package/target/lib.js +3 -1
- package/target/lib.js.map +1 -1
- package/src/lib/tags.test.ts +0 -28
- package/target/lib/tags.test.js +0 -23
- package/target/lib/tags.test.js.map +0 -1
- /package/target/lib/{tags.test.d.ts → lifecycle.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm i @joist/element
|
|
|
11
11
|
#### Example:
|
|
12
12
|
|
|
13
13
|
```TS
|
|
14
|
-
import {
|
|
14
|
+
import { css, html, attr, listen, element } from '@joist/element';
|
|
15
15
|
|
|
16
16
|
@element({
|
|
17
17
|
tagName: 'my-element',
|
|
@@ -23,7 +23,7 @@ import { tagName, css, html, attr, listen, element } from '@joist/element';
|
|
|
23
23
|
}
|
|
24
24
|
`,
|
|
25
25
|
html`
|
|
26
|
-
|
|
26
|
+
<!--#:value-->
|
|
27
27
|
`
|
|
28
28
|
]
|
|
29
29
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joist/element",
|
|
3
|
-
"version": "4.0.0-next.
|
|
3
|
+
"version": "4.0.0-next.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./target/lib.js",
|
|
6
6
|
"module": "./target/lib.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"description": "Intelligently apply styles to WebComponents",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/
|
|
23
|
+
"url": "git+https://github.com/joist-framework/joist.git"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"TypeScript",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"author": "deebloo",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://github.com/
|
|
34
|
+
"url": "https://github.com/joist-framework/joist/issues"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"test": {
|
|
58
58
|
"command": "wtr --config wtr.config.mjs",
|
|
59
59
|
"files": [
|
|
60
|
+
"vitest.config.js",
|
|
60
61
|
"target/**"
|
|
61
62
|
],
|
|
62
63
|
"output": [],
|
package/src/lib/attr.test.ts
CHANGED
|
@@ -1,129 +1,138 @@
|
|
|
1
|
-
import { expect
|
|
1
|
+
import { expect } from 'chai';
|
|
2
2
|
|
|
3
3
|
import { attr } from './attr.js';
|
|
4
4
|
import { element } from './element.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
accessor value1 = 100; // no attribute
|
|
6
|
+
it('should read and parse the correct values', () => {
|
|
7
|
+
@element({
|
|
8
|
+
tagName: 'attr-test-1'
|
|
9
|
+
})
|
|
10
|
+
class MyElement extends HTMLElement {
|
|
11
|
+
@attr()
|
|
12
|
+
accessor value1 = 100; // no attribute
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
@attr()
|
|
15
|
+
accessor value2 = 0; // number
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
@attr()
|
|
18
|
+
accessor value3 = false; // boolean
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
@attr()
|
|
21
|
+
accessor value4 = 'hello'; // string
|
|
22
|
+
}
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const container = document.createElement('div');
|
|
25
|
+
container.innerHTML = /*html*/ `
|
|
26
|
+
<attr-test-1 value2="2" value3 value4="world"></attr-test-1>
|
|
27
|
+
`;
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
expect(el.value2).to.equal(2);
|
|
31
|
-
expect(el.value3).to.equal(true);
|
|
32
|
-
expect(el.value4).to.equal('world');
|
|
33
|
-
});
|
|
29
|
+
document.body.append(container);
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
@element({
|
|
37
|
-
tagName: 'attr-test-2'
|
|
38
|
-
})
|
|
39
|
-
class MyElement extends HTMLElement {
|
|
40
|
-
@attr()
|
|
41
|
-
accessor value1 = undefined;
|
|
31
|
+
const el = document.querySelector('attr-test-1') as MyElement;
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
expect(el.value1).to.equal(100);
|
|
34
|
+
expect(el.value2).to.equal(2);
|
|
35
|
+
expect(el.value3).to.equal(true);
|
|
36
|
+
expect(el.value4).to.equal('world');
|
|
45
37
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
38
|
+
container.remove();
|
|
39
|
+
});
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
it('should not write falsy props to attributes', async () => {
|
|
42
|
+
@element({
|
|
43
|
+
tagName: 'attr-test-2'
|
|
44
|
+
})
|
|
45
|
+
class MyElement extends HTMLElement {
|
|
46
|
+
@attr()
|
|
47
|
+
accessor value1 = undefined;
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
expect(el.hasAttribute('value3')).to.be.false;
|
|
55
|
-
});
|
|
49
|
+
@attr()
|
|
50
|
+
accessor value2 = null;
|
|
56
51
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
})
|
|
61
|
-
class MyElement extends HTMLElement {
|
|
62
|
-
@attr()
|
|
63
|
-
accessor value1 = 'hello'; // no attribute
|
|
52
|
+
@attr()
|
|
53
|
+
accessor value3 = '';
|
|
54
|
+
}
|
|
64
55
|
|
|
65
|
-
|
|
66
|
-
accessor value2 = 0; // number
|
|
56
|
+
const el = new MyElement();
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
});
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
it('should update attributes when props are changed', async () => {
|
|
64
|
+
@element({
|
|
65
|
+
tagName: 'attr-test-3'
|
|
66
|
+
})
|
|
67
|
+
class MyElement extends HTMLElement {
|
|
68
|
+
@attr()
|
|
69
|
+
accessor value1 = 'hello'; // no attribute
|
|
70
|
+
|
|
71
|
+
@attr()
|
|
72
|
+
accessor value2 = 0; // number
|
|
73
|
+
|
|
74
|
+
@attr()
|
|
75
|
+
accessor value3 = true; // boolean
|
|
76
|
+
|
|
77
|
+
@attr()
|
|
78
|
+
accessor value4 = false; // boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const el = new MyElement();
|
|
82
|
+
|
|
83
|
+
el.value1 = 'world';
|
|
84
|
+
el.value2 = 100;
|
|
85
|
+
el.value3 = false;
|
|
86
|
+
el.value4 = true;
|
|
87
|
+
|
|
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
|
+
});
|
|
74
93
|
|
|
75
|
-
|
|
94
|
+
it('should normalize attribute names', async () => {
|
|
95
|
+
const value3 = Symbol('Value from SYMBOL');
|
|
76
96
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
97
|
+
@element({
|
|
98
|
+
tagName: 'attr-test-4'
|
|
99
|
+
})
|
|
100
|
+
class MyElement extends HTMLElement {
|
|
101
|
+
@attr()
|
|
102
|
+
accessor Value1 = 'hello';
|
|
81
103
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expect(el.hasAttribute('value3')).to.be.false;
|
|
85
|
-
expect(el.hasAttribute('value4')).to.be.true;
|
|
86
|
-
});
|
|
104
|
+
@attr()
|
|
105
|
+
accessor ['Value 2'] = 0;
|
|
87
106
|
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
@attr()
|
|
108
|
+
accessor [value3] = true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const el = new MyElement();
|
|
112
|
+
|
|
113
|
+
document.body.append(el);
|
|
114
|
+
|
|
115
|
+
expect([...el.attributes].map((attr) => attr.name)).to.deep.equal([
|
|
116
|
+
'value1',
|
|
117
|
+
'value-2',
|
|
118
|
+
'value-from-symbol'
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
el.remove();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should throw an error for symbols with no description', async () => {
|
|
125
|
+
expect(() => {
|
|
126
|
+
const value = Symbol();
|
|
90
127
|
|
|
91
128
|
@element({
|
|
92
129
|
tagName: 'attr-test-4'
|
|
93
130
|
})
|
|
94
131
|
class MyElement extends HTMLElement {
|
|
95
132
|
@attr()
|
|
96
|
-
accessor
|
|
97
|
-
|
|
98
|
-
@attr()
|
|
99
|
-
accessor ['Value 2'] = 0;
|
|
100
|
-
|
|
101
|
-
@attr()
|
|
102
|
-
accessor [value3] = true;
|
|
133
|
+
accessor [value] = true;
|
|
103
134
|
}
|
|
104
135
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
expect([...el.attributes].map((attr) => attr.name)).to.deep.equal([
|
|
108
|
-
'value1',
|
|
109
|
-
'value-2',
|
|
110
|
-
'value-from-symbol'
|
|
111
|
-
]);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should throw an error for symbols with no description', async () => {
|
|
115
|
-
expect(() => {
|
|
116
|
-
const value = Symbol();
|
|
117
|
-
|
|
118
|
-
@element({
|
|
119
|
-
tagName: 'attr-test-4'
|
|
120
|
-
})
|
|
121
|
-
class MyElement extends HTMLElement {
|
|
122
|
-
@attr()
|
|
123
|
-
accessor [value] = true;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
new MyElement();
|
|
127
|
-
}).to.throw('Cannot handle Symbol property without description');
|
|
128
|
-
});
|
|
136
|
+
new MyElement();
|
|
137
|
+
}).to.throw('Cannot handle Symbol property without description');
|
|
129
138
|
});
|
package/src/lib/attr.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { metadataStore } from './metadata.js';
|
|
2
2
|
|
|
3
3
|
export interface AttrOpts {
|
|
4
|
-
|
|
4
|
+
observed?: boolean;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function attr(opts?: AttrOpts) {
|
|
@@ -15,7 +15,7 @@ export function attr(opts?: AttrOpts) {
|
|
|
15
15
|
meta.attrs.push({
|
|
16
16
|
propName: ctx.name,
|
|
17
17
|
attrName,
|
|
18
|
-
observe: opts?.
|
|
18
|
+
observe: opts?.observed ?? true
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
return {
|
package/src/lib/element.test.ts
CHANGED
|
@@ -1,86 +1,97 @@
|
|
|
1
|
-
import { expect
|
|
1
|
+
import { expect } from 'chai';
|
|
2
2
|
|
|
3
3
|
import { attr } from './attr.js';
|
|
4
4
|
import { element } from './element.js';
|
|
5
5
|
import { css, html } from './tags.js';
|
|
6
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
|
-
|
|
7
|
+
it('should write default value to attribute', async () => {
|
|
8
|
+
@element({
|
|
9
|
+
tagName: 'element-1'
|
|
10
|
+
})
|
|
11
|
+
class MyElement extends HTMLElement {
|
|
12
|
+
@attr()
|
|
13
|
+
accessor value1 = 'hello'; // no attribute
|
|
14
|
+
|
|
15
|
+
@attr()
|
|
16
|
+
accessor value2 = 0; // number
|
|
17
|
+
|
|
18
|
+
@attr()
|
|
19
|
+
accessor value3 = true; // boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const el = new MyElement();
|
|
23
|
+
|
|
24
|
+
document.body.append(el);
|
|
25
|
+
|
|
26
|
+
expect(el.getAttribute('value1')).to.equal('hello');
|
|
27
|
+
expect(el.getAttribute('value2')).to.equal('0');
|
|
28
|
+
expect(el.getAttribute('value3')).to.equal('');
|
|
29
|
+
|
|
30
|
+
el.remove();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should register attributes', async () => {
|
|
34
|
+
@element({
|
|
35
|
+
tagName: 'element-2'
|
|
36
|
+
})
|
|
37
|
+
class MyElement extends HTMLElement {
|
|
38
|
+
@attr()
|
|
39
|
+
accessor value1 = 'hello'; // no attribute
|
|
40
|
+
|
|
41
|
+
@attr()
|
|
42
|
+
accessor value2 = 0; // number
|
|
43
|
+
|
|
44
|
+
@attr()
|
|
45
|
+
accessor value3 = true; // boolean
|
|
46
|
+
|
|
47
|
+
@attr({ observed: false }) // should be filtered out
|
|
48
|
+
accessor value4 = 'hello world';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
expect(Reflect.get(MyElement, 'observedAttributes')).to.deep.equal([
|
|
52
|
+
'value1',
|
|
53
|
+
'value2',
|
|
54
|
+
'value3'
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should attach shadow root when the shadow property exists', async () => {
|
|
59
|
+
@element({
|
|
60
|
+
tagName: 'element-3',
|
|
61
|
+
shadow: []
|
|
62
|
+
})
|
|
63
|
+
class MyElement extends HTMLElement {}
|
|
64
|
+
|
|
65
|
+
const el = new MyElement();
|
|
66
|
+
|
|
67
|
+
expect(el.shadowRoot).to.be.instanceOf(ShadowRoot);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should apply html and css', async () => {
|
|
71
|
+
@element({
|
|
72
|
+
tagName: 'element-4',
|
|
73
|
+
shadow: [
|
|
74
|
+
css`
|
|
75
|
+
:host {
|
|
76
|
+
display: contents;
|
|
77
|
+
}
|
|
78
|
+
`,
|
|
79
|
+
html`<slot></slot>`,
|
|
80
|
+
{
|
|
81
|
+
apply(el) {
|
|
82
|
+
const div = document.createElement('div');
|
|
83
|
+
div.innerHTML = 'hello world';
|
|
84
|
+
|
|
85
|
+
el.append(div);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
})
|
|
90
|
+
class MyElement extends HTMLElement {}
|
|
91
|
+
|
|
92
|
+
const el = new MyElement();
|
|
93
|
+
|
|
94
|
+
expect(el.shadowRoot!.adoptedStyleSheets.length).to.equal(1);
|
|
95
|
+
expect(el.shadowRoot!.innerHTML).to.equal(`<slot></slot>`);
|
|
96
|
+
expect(el.innerHTML).to.equal(`<div>hello world</div>`);
|
|
86
97
|
});
|
package/src/lib/element.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AttrDef, metadataStore } from './metadata.js';
|
|
2
2
|
import { ShadowResult } from './result.js';
|
|
3
3
|
|
|
4
|
-
export interface ElementOpts<T> {
|
|
4
|
+
export interface ElementOpts<T extends HTMLElement> {
|
|
5
5
|
tagName?: string;
|
|
6
|
-
shadow?: Array<ShadowResult | ((el: T) => void)>;
|
|
6
|
+
shadow?: Array<ShadowResult<T> | ((el: T) => void)>;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export function element<
|
|
@@ -30,13 +30,15 @@ export function element<
|
|
|
30
30
|
super(...args);
|
|
31
31
|
|
|
32
32
|
if (opts?.shadow) {
|
|
33
|
-
this.
|
|
33
|
+
if (!this.shadowRoot) {
|
|
34
|
+
this.attachShadow({ mode: 'open' });
|
|
35
|
+
}
|
|
34
36
|
|
|
35
37
|
for (let res of opts.shadow) {
|
|
36
38
|
if (typeof res === 'function') {
|
|
37
39
|
res(this as unknown as Instance);
|
|
38
40
|
} else {
|
|
39
|
-
res.
|
|
41
|
+
res.apply(this as unknown as Instance);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -44,13 +46,19 @@ export function element<
|
|
|
44
46
|
for (let [event, { cb, root }] of meta.listeners) {
|
|
45
47
|
root(this).addEventListener(event, cb.bind(this));
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
for (let cb of meta.onReady) {
|
|
51
|
+
cb.call(this);
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
connectedCallback() {
|
|
50
|
-
|
|
56
|
+
if (this.isConnected) {
|
|
57
|
+
reflectAttributeValues(this, meta.attrs);
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
|
|
59
|
+
if (super.connectedCallback) {
|
|
60
|
+
super.connectedCallback();
|
|
61
|
+
}
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { assert } from 'chai';
|
|
2
|
+
import { element } from './element.js';
|
|
3
|
+
import { ready } from './lifecycle';
|
|
4
|
+
|
|
5
|
+
it('should call all callbacks when template is ready', () => {
|
|
6
|
+
@element({
|
|
7
|
+
tagName: 'template-ready-1'
|
|
8
|
+
})
|
|
9
|
+
class MyElement extends HTMLElement {
|
|
10
|
+
callCount: Record<string, number> = {};
|
|
11
|
+
|
|
12
|
+
@ready()
|
|
13
|
+
onTemplateReady1() {
|
|
14
|
+
this.callCount['onTemplateReady1'] ??= 0;
|
|
15
|
+
this.callCount['onTemplateReady1']++;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@ready()
|
|
19
|
+
onTemplateReady2() {
|
|
20
|
+
this.callCount['onTemplateReady2'] ??= 0;
|
|
21
|
+
this.callCount['onTemplateReady2']++;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const el = new MyElement();
|
|
26
|
+
|
|
27
|
+
assert.deepEqual(el.callCount, {
|
|
28
|
+
onTemplateReady1: 1,
|
|
29
|
+
onTemplateReady2: 1
|
|
30
|
+
});
|
|
31
|
+
});
|
package/src/lib/metadata.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type ListenerRootSelector = (el: HTMLElement) => HTMLElement | ShadowRoot
|
|
|
11
11
|
export class ElementMetadata {
|
|
12
12
|
attrs: AttrDef[] = [];
|
|
13
13
|
listeners = new Map<string, { cb: (e: Event) => void; root: ListenerRootSelector }>();
|
|
14
|
+
onReady = new Set<Function>();
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export class MetadataStore extends WeakMap<object, ElementMetadata> {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
|
|
3
|
+
import { element } from './element.js';
|
|
4
|
+
import { query } from './query.js';
|
|
5
|
+
import { html } from './tags.js';
|
|
6
|
+
|
|
7
|
+
it('should work', () => {
|
|
8
|
+
@element({
|
|
9
|
+
tagName: 'query-test-1',
|
|
10
|
+
shadow: [
|
|
11
|
+
html`
|
|
12
|
+
<form>
|
|
13
|
+
<input id="fname" name="fname" />
|
|
14
|
+
<input id="lname" name="lname" />
|
|
15
|
+
</form>
|
|
16
|
+
`
|
|
17
|
+
]
|
|
18
|
+
})
|
|
19
|
+
class MyElement extends HTMLElement {
|
|
20
|
+
fname = query<HTMLInputElement>('#fname');
|
|
21
|
+
lname = query<HTMLInputElement>('#lname');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const el = new MyElement();
|
|
25
|
+
|
|
26
|
+
expect(el.fname()).to.equal(el.shadowRoot?.querySelector('#fname'));
|
|
27
|
+
expect(el.lname()).to.equal(el.shadowRoot?.querySelector('#lname'));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should patch the selected item', () => {
|
|
31
|
+
@element({
|
|
32
|
+
tagName: 'query-test-2',
|
|
33
|
+
shadow: [
|
|
34
|
+
html`
|
|
35
|
+
<form>
|
|
36
|
+
<input id="fname" name="fname" />
|
|
37
|
+
<input id="lname" name="lname" />
|
|
38
|
+
</form>
|
|
39
|
+
`
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
class MyElement extends HTMLElement {
|
|
43
|
+
fname = query<HTMLInputElement>('#fname');
|
|
44
|
+
lname = query<HTMLInputElement>('#lname');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const el = new MyElement();
|
|
48
|
+
el.fname({ value: 'Foo' });
|
|
49
|
+
el.lname({ value: 'Bar' });
|
|
50
|
+
|
|
51
|
+
expect(el.shadowRoot?.querySelector<HTMLInputElement>('#fname')?.value).to.equal('Foo');
|
|
52
|
+
expect(el.shadowRoot?.querySelector<HTMLInputElement>('#lname')?.value).to.equal('Bar');
|
|
53
|
+
});
|
package/src/lib/query.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
type Tags = keyof HTMLElementTagNameMap;
|
|
2
|
+
type SVGTags = keyof SVGElementTagNameMap;
|
|
3
|
+
type MathTags = keyof MathMLElementTagNameMap;
|
|
4
|
+
|
|
5
|
+
type QueryResult<T> = (updates?: Partial<T>) => T;
|
|
6
|
+
|
|
7
|
+
export function query<K extends Tags>(selectors: K): QueryResult<HTMLElementTagNameMap[K]>;
|
|
8
|
+
export function query<K extends SVGTags>(selectors: K): QueryResult<SVGElementTagNameMap[K]>;
|
|
9
|
+
export function query<K extends MathTags>(selectors: K): QueryResult<MathMLElementTagNameMap[K]>;
|
|
10
|
+
export function query<E extends HTMLElement = HTMLElement>(selectors: string): QueryResult<E>;
|
|
11
|
+
export function query<K extends Tags>(query: K): QueryResult<HTMLElementTagNameMap[K]> {
|
|
12
|
+
let res: HTMLElementTagNameMap[K] | null = null;
|
|
13
|
+
|
|
14
|
+
return function (this: HTMLElement, updates?: Partial<HTMLElementTagNameMap[K]>) {
|
|
15
|
+
if (res) {
|
|
16
|
+
return res;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (this.shadowRoot) {
|
|
20
|
+
res = this.shadowRoot.querySelector<K>(query);
|
|
21
|
+
} else {
|
|
22
|
+
res = this.querySelector<K>(query);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!res) {
|
|
26
|
+
throw new Error('could not find element');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (updates) {
|
|
30
|
+
for (let update in updates) {
|
|
31
|
+
Reflect.set(res, update, updates[update]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return res;
|
|
36
|
+
};
|
|
37
|
+
}
|