@joist/element 4.0.0-next.3 → 4.0.0-next.30
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 +102 -15
- package/package.json +7 -9
- package/src/lib/attr.test.ts +138 -97
- package/src/lib/attr.ts +16 -32
- package/src/lib/element.test.ts +101 -70
- package/src/lib/element.ts +89 -40
- package/src/lib/lifecycle.test.ts +31 -0
- package/src/lib/lifecycle.ts +9 -0
- package/src/lib/listen.test.ts +88 -0
- package/src/lib/listen.ts +26 -9
- package/src/lib/metadata.ts +18 -7
- package/src/lib/query.test.ts +28 -0
- package/src/lib/query.ts +31 -0
- package/src/lib/result.ts +1 -21
- package/src/lib/tags.ts +27 -12
- package/src/lib/template.test.ts +123 -0
- package/src/lib/template.ts +118 -0
- package/src/lib.ts +2 -1
- package/target/lib/attr.d.ts +2 -1
- package/target/lib/attr.js +15 -24
- package/target/lib/attr.js.map +1 -1
- package/target/lib/attr.test.js +333 -252
- package/target/lib/attr.test.js.map +1 -1
- package/target/lib/element.d.ts +24 -11
- package/target/lib/element.js +66 -28
- package/target/lib/element.js.map +1 -1
- package/target/lib/element.test.js +158 -176
- 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/listen.d.ts +2 -2
- package/target/lib/listen.js +18 -3
- package/target/lib/listen.js.map +1 -1
- package/target/lib/listen.test.d.ts +1 -0
- package/target/lib/listen.test.js +159 -0
- package/target/lib/listen.test.js.map +1 -0
- package/target/lib/metadata.d.ts +17 -10
- package/target/lib/metadata.js +5 -2
- package/target/lib/metadata.js.map +1 -1
- package/target/lib/query.d.ts +9 -0
- package/target/lib/query.js +19 -0
- package/target/lib/query.js.map +1 -0
- package/target/lib/query.test.d.ts +1 -0
- package/target/lib/query.test.js +41 -0
- package/target/lib/query.test.js.map +1 -0
- package/target/lib/result.d.ts +1 -10
- package/target/lib/result.js +1 -22
- package/target/lib/result.js.map +1 -1
- package/target/lib/tags.d.ts +10 -8
- package/target/lib/tags.js +18 -24
- package/target/lib/tags.js.map +1 -1
- package/target/lib/template.d.ts +11 -0
- package/target/lib/template.js +87 -0
- package/target/lib/template.js.map +1 -0
- package/target/lib/template.test.d.ts +1 -0
- package/target/lib/template.test.js +91 -0
- package/target/lib/template.test.js.map +1 -0
- package/target/lib.d.ts +2 -1
- package/target/lib.js +2 -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/src/lib/element.test.ts
CHANGED
|
@@ -1,86 +1,117 @@
|
|
|
1
|
-
import { expect,
|
|
1
|
+
import { expect, assert } 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
|
-
accessor value1 = 'hello'; // no attribute
|
|
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
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
@attr()
|
|
16
|
+
accessor value2 = 0; // number
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
@attr()
|
|
19
|
+
accessor value3 = true; // boolean
|
|
20
|
+
|
|
21
|
+
@attr({ reflect: false })
|
|
22
|
+
accessor value4 = 'foo';
|
|
23
|
+
}
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
const el = new MyElement();
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
expect(el.getAttribute('value2')).to.equal('0');
|
|
27
|
-
expect(el.getAttribute('value3')).to.equal('');
|
|
28
|
-
});
|
|
27
|
+
document.body.append(el);
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class MyElement extends HTMLElement {
|
|
35
|
-
@attr()
|
|
36
|
-
accessor value1 = 'hello'; // no attribute
|
|
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);
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
el.remove();
|
|
35
|
+
});
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
// TODO: Figure out test
|
|
38
|
+
// it('should register attributes', async () => {
|
|
39
|
+
// @element({
|
|
40
|
+
// tagName: 'element-2'
|
|
41
|
+
// })
|
|
42
|
+
// class MyElement extends HTMLElement {
|
|
43
|
+
// @attr()
|
|
44
|
+
// accessor value1 = 'hello'; // no attribute
|
|
45
|
+
|
|
46
|
+
// @attr()
|
|
47
|
+
// accessor value2 = 0; // number
|
|
48
|
+
|
|
49
|
+
// @attr()
|
|
50
|
+
// accessor value3 = true; // boolean
|
|
51
|
+
|
|
52
|
+
// @attr({ observed: false }) // should be filtered out
|
|
53
|
+
// accessor value4 = 'hello world';
|
|
54
|
+
// }
|
|
55
|
+
|
|
56
|
+
// expect(Reflect.get(MyElement, 'observedAttributes')).to.deep.equal([
|
|
57
|
+
// 'value1',
|
|
58
|
+
// 'value2',
|
|
59
|
+
// 'value3'
|
|
60
|
+
// ]);
|
|
61
|
+
// });
|
|
62
|
+
|
|
63
|
+
it('should attach shadow root when the shadow property exists', async () => {
|
|
64
|
+
@element({
|
|
65
|
+
tagName: 'element-3',
|
|
66
|
+
shadowDom: []
|
|
67
|
+
})
|
|
68
|
+
class MyElement extends HTMLElement {}
|
|
69
|
+
|
|
70
|
+
const el = new MyElement();
|
|
71
|
+
|
|
72
|
+
expect(el.shadowRoot).to.be.instanceOf(ShadowRoot);
|
|
73
|
+
});
|
|
43
74
|
|
|
44
|
-
|
|
45
|
-
|
|
75
|
+
it('should apply html and css', async () => {
|
|
76
|
+
@element({
|
|
77
|
+
tagName: 'element-4',
|
|
78
|
+
shadowDom: [
|
|
79
|
+
css`
|
|
80
|
+
:host {
|
|
81
|
+
display: contents;
|
|
82
|
+
}
|
|
83
|
+
`,
|
|
84
|
+
html`<slot></slot>`,
|
|
85
|
+
{
|
|
86
|
+
apply(el) {
|
|
87
|
+
const div = document.createElement('div');
|
|
88
|
+
div.innerHTML = 'hello world';
|
|
89
|
+
|
|
90
|
+
el.append(div);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
})
|
|
95
|
+
class MyElement extends HTMLElement {}
|
|
96
|
+
|
|
97
|
+
const el = new MyElement();
|
|
98
|
+
|
|
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
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should the correct shadow dom mode', async () => {
|
|
105
|
+
@element({
|
|
106
|
+
tagName: 'element-5',
|
|
107
|
+
shadowDom: [],
|
|
108
|
+
shadowDomOpts: {
|
|
109
|
+
mode: 'closed'
|
|
46
110
|
}
|
|
111
|
+
})
|
|
112
|
+
class MyElement extends HTMLElement {}
|
|
113
|
+
|
|
114
|
+
const el = new MyElement();
|
|
47
115
|
|
|
48
|
-
|
|
49
|
-
'value1',
|
|
50
|
-
'value2',
|
|
51
|
-
'value3'
|
|
52
|
-
]);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should attach shadow root when the shadow property exists', async () => {
|
|
56
|
-
@element({
|
|
57
|
-
tagName: 'element-3',
|
|
58
|
-
shadow: []
|
|
59
|
-
})
|
|
60
|
-
class MyElement extends HTMLElement {}
|
|
61
|
-
|
|
62
|
-
const el = new MyElement();
|
|
63
|
-
|
|
64
|
-
expect(el.shadowRoot).to.be.instanceOf(ShadowRoot);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should apply html and css', async () => {
|
|
68
|
-
@element({
|
|
69
|
-
tagName: 'element-4',
|
|
70
|
-
shadow: [
|
|
71
|
-
css`
|
|
72
|
-
:host {
|
|
73
|
-
display: contents;
|
|
74
|
-
}
|
|
75
|
-
`,
|
|
76
|
-
html`<slot></slot>`
|
|
77
|
-
]
|
|
78
|
-
})
|
|
79
|
-
class MyElement extends HTMLElement {}
|
|
80
|
-
|
|
81
|
-
const el = new MyElement();
|
|
82
|
-
|
|
83
|
-
expect(el.shadowRoot!.adoptedStyleSheets.length).to.equal(1);
|
|
84
|
-
expect(el.shadowRoot!.innerHTML).to.equal(`<slot></slot>`);
|
|
85
|
-
});
|
|
116
|
+
assert.equal(el.shadowRoot, null);
|
|
86
117
|
});
|
package/src/lib/element.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AttrMetadata, metadataStore } from './metadata.js';
|
|
2
2
|
import { ShadowResult } from './result.js';
|
|
3
3
|
|
|
4
|
-
export interface ElementOpts
|
|
4
|
+
export interface ElementOpts {
|
|
5
5
|
tagName?: string;
|
|
6
|
-
|
|
6
|
+
shadowDom?: ShadowResult[];
|
|
7
|
+
shadowDomOpts?: ShadowRootInit;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
interface ElementConstructor {
|
|
11
|
+
new (...args: any[]): HTMLElement;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function element<T extends ElementConstructor>(opts?: ElementOpts) {
|
|
15
|
+
return function elementDecorator(Base: T, ctx: ClassDecoratorContext<T>) {
|
|
14
16
|
const meta = metadataStore.read(ctx.metadata);
|
|
15
17
|
|
|
16
|
-
ctx.addInitializer(function (
|
|
18
|
+
ctx.addInitializer(function () {
|
|
17
19
|
if (opts?.tagName) {
|
|
18
20
|
if (!customElements.get(opts.tagName)) {
|
|
19
21
|
customElements.define(opts.tagName, this);
|
|
@@ -21,56 +23,103 @@ export function element<
|
|
|
21
23
|
}
|
|
22
24
|
});
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const def = {
|
|
27
|
+
[Base.name]: class extends Base {
|
|
28
|
+
static observedAttributes: string[] = [];
|
|
29
|
+
|
|
30
|
+
static {
|
|
31
|
+
for (let [attrName] of meta.attrs) {
|
|
32
|
+
this.observedAttributes.push(attrName);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(...args: any[]) {
|
|
37
|
+
super(...args);
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
if (opts?.shadowDom) {
|
|
40
|
+
if (!this.shadowRoot) {
|
|
41
|
+
this.attachShadow(opts.shadowDomOpts ?? { mode: 'open' });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (let res of opts.shadowDom) {
|
|
45
|
+
res.apply(this);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
49
|
+
for (let { event, cb, selector } of meta.listeners) {
|
|
50
|
+
const root = selector(this);
|
|
34
51
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
res(this as unknown as Instance);
|
|
52
|
+
if (root) {
|
|
53
|
+
root.addEventListener(event, cb.bind(this));
|
|
38
54
|
} else {
|
|
39
|
-
|
|
55
|
+
throw new Error(`could not add listener to ${root}`);
|
|
40
56
|
}
|
|
41
57
|
}
|
|
58
|
+
|
|
59
|
+
for (let cb of meta.onReady) {
|
|
60
|
+
cb.call(this);
|
|
61
|
+
}
|
|
42
62
|
}
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
connectedCallback() {
|
|
65
|
+
if (this.isConnected) {
|
|
66
|
+
reflectAttributeValues(this, meta.attrs);
|
|
67
|
+
|
|
68
|
+
if (super.connectedCallback) {
|
|
69
|
+
super.connectedCallback();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
46
72
|
}
|
|
47
|
-
}
|
|
48
73
|
|
|
49
|
-
|
|
50
|
-
|
|
74
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
|
75
|
+
const attr = meta.attrs.get(name);
|
|
76
|
+
|
|
77
|
+
if (attr) {
|
|
78
|
+
if (oldValue !== newValue) {
|
|
79
|
+
const ogValue = attr.getPropValue.call(this);
|
|
51
80
|
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
if (newValue === '') {
|
|
82
|
+
// treat as boolean
|
|
83
|
+
attr.setPropValue.call(this, true);
|
|
84
|
+
} else if (typeof ogValue === 'number') {
|
|
85
|
+
// treat as number
|
|
86
|
+
attr.setPropValue.call(this, Number(newValue));
|
|
87
|
+
} else {
|
|
88
|
+
// treat as string
|
|
89
|
+
attr.setPropValue.call(this, newValue);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (attr.observe) {
|
|
94
|
+
if (super.attributeChangedCallback) {
|
|
95
|
+
super.attributeChangedCallback(name, oldValue, newValue);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
54
99
|
}
|
|
55
100
|
}
|
|
56
101
|
};
|
|
102
|
+
|
|
103
|
+
return def[Base.name];
|
|
57
104
|
};
|
|
58
105
|
}
|
|
59
106
|
|
|
60
|
-
function reflectAttributeValues(el:
|
|
61
|
-
for (let {
|
|
62
|
-
|
|
107
|
+
function reflectAttributeValues<T extends HTMLElement>(el: T, attrs: AttrMetadata) {
|
|
108
|
+
for (let [attrName, { getPropValue, reflect }] of attrs) {
|
|
109
|
+
if (reflect) {
|
|
110
|
+
const value = getPropValue.call(el);
|
|
63
111
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
112
|
+
// reflect values back to attributes
|
|
113
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
114
|
+
if (typeof value === 'boolean') {
|
|
115
|
+
if (value === true) {
|
|
116
|
+
// set boolean attribute
|
|
117
|
+
el.setAttribute(attrName, '');
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
// set key/value attribute
|
|
121
|
+
el.setAttribute(attrName, String(value));
|
|
70
122
|
}
|
|
71
|
-
} else {
|
|
72
|
-
// set key/value attribute
|
|
73
|
-
el.setAttribute(attrName, String(value));
|
|
74
123
|
}
|
|
75
124
|
}
|
|
76
125
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { assert } from 'chai';
|
|
2
|
+
import { element } from './element.js';
|
|
3
|
+
import { ready } from './lifecycle.js';
|
|
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
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { assert } from 'chai';
|
|
2
|
+
|
|
3
|
+
import { element } from './element.js';
|
|
4
|
+
import { listen } from './listen.js';
|
|
5
|
+
|
|
6
|
+
describe('@listen()', () => {
|
|
7
|
+
it('should add listener to an outer HTMLElement', (done) => {
|
|
8
|
+
@element({
|
|
9
|
+
tagName: 'listener-1'
|
|
10
|
+
})
|
|
11
|
+
class MyElement extends HTMLElement {
|
|
12
|
+
@listen('click')
|
|
13
|
+
onClick(e: Event) {
|
|
14
|
+
assert.equal(e.type, 'click');
|
|
15
|
+
|
|
16
|
+
done();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const el = new MyElement();
|
|
21
|
+
|
|
22
|
+
el.dispatchEvent(new Event('click'));
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should add listener to the shadow root if available', (done) => {
|
|
26
|
+
@element({
|
|
27
|
+
tagName: 'listener-2',
|
|
28
|
+
shadowDom: []
|
|
29
|
+
})
|
|
30
|
+
class MyElement extends HTMLElement {
|
|
31
|
+
@listen('click')
|
|
32
|
+
onClick(e: Event) {
|
|
33
|
+
assert.equal(e.type, 'click');
|
|
34
|
+
|
|
35
|
+
done();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const el = new MyElement();
|
|
40
|
+
|
|
41
|
+
el.shadowRoot!.dispatchEvent(new Event('click'));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should restrict argument to an event or an event subtype', (done) => {
|
|
45
|
+
class CustomEvent extends Event {
|
|
46
|
+
test = 'Hello World';
|
|
47
|
+
|
|
48
|
+
constructor() {
|
|
49
|
+
super('customevent');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@element({
|
|
54
|
+
tagName: 'listener-3'
|
|
55
|
+
})
|
|
56
|
+
class MyElement extends HTMLElement {
|
|
57
|
+
@listen('customevent')
|
|
58
|
+
onClick(e: CustomEvent) {
|
|
59
|
+
assert.equal(e.type, 'customevent');
|
|
60
|
+
|
|
61
|
+
done();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const el = new MyElement();
|
|
66
|
+
|
|
67
|
+
el.dispatchEvent(new CustomEvent());
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should respect a provided selector function', (done) => {
|
|
71
|
+
@element({
|
|
72
|
+
tagName: 'listener-4',
|
|
73
|
+
shadowDom: []
|
|
74
|
+
})
|
|
75
|
+
class MyElement extends HTMLElement {
|
|
76
|
+
@listen('click', (host) => host)
|
|
77
|
+
onClick(e: Event) {
|
|
78
|
+
assert.equal(e.type, 'click');
|
|
79
|
+
|
|
80
|
+
done();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const el = new MyElement();
|
|
85
|
+
|
|
86
|
+
el.dispatchEvent(new Event('click'));
|
|
87
|
+
});
|
|
88
|
+
});
|
package/src/lib/listen.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ListenerSelector, metadataStore } from './metadata.js';
|
|
2
2
|
|
|
3
|
-
export function listen<This extends HTMLElement>(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
) {
|
|
8
|
-
const metadata = metadataStore.read(ctx.metadata);
|
|
3
|
+
export function listen<This extends HTMLElement>(
|
|
4
|
+
event: string,
|
|
5
|
+
selector?: ListenerSelector<This> | string
|
|
6
|
+
) {
|
|
7
|
+
return function listenDecorator(value: (e: any) => void, ctx: ClassMethodDecoratorContext<This>) {
|
|
8
|
+
const metadata = metadataStore.read<This>(ctx.metadata);
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
let selectorInternal: ListenerSelector<This> = (el) => el.shadowRoot ?? el;
|
|
11
|
+
|
|
12
|
+
if (selector) {
|
|
13
|
+
if (typeof selector === 'string') {
|
|
14
|
+
selectorInternal = (el: This) => {
|
|
15
|
+
if (el.shadowRoot) {
|
|
16
|
+
return el.shadowRoot.querySelector(selector);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return el.querySelector(selector);
|
|
20
|
+
};
|
|
21
|
+
} else {
|
|
22
|
+
selectorInternal = selector;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
metadata.listeners.push({
|
|
27
|
+
event,
|
|
11
28
|
cb: value,
|
|
12
|
-
|
|
29
|
+
selector: selectorInternal
|
|
13
30
|
});
|
|
14
31
|
};
|
|
15
32
|
}
|
package/src/lib/metadata.ts
CHANGED
|
@@ -2,19 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
export interface AttrDef {
|
|
4
4
|
propName: string | symbol;
|
|
5
|
-
attrName: string;
|
|
6
5
|
observe: boolean;
|
|
6
|
+
reflect: boolean;
|
|
7
|
+
getPropValue: Function;
|
|
8
|
+
setPropValue: Function;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
export type
|
|
11
|
+
export type ListenerSelector<T> = (el: T) => Element | ShadowRoot | null;
|
|
10
12
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
export interface Listener<T> {
|
|
14
|
+
event: string;
|
|
15
|
+
cb: (e: Event) => void;
|
|
16
|
+
selector: ListenerSelector<T>;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
|
-
export class
|
|
17
|
-
|
|
19
|
+
export class AttrMetadata extends Map<string, AttrDef> {}
|
|
20
|
+
|
|
21
|
+
export class ElementMetadata<T> {
|
|
22
|
+
attrs = new AttrMetadata();
|
|
23
|
+
listeners: Listener<T>[] = [];
|
|
24
|
+
onReady = new Set<Function>();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class MetadataStore extends WeakMap<object, ElementMetadata<unknown>> {
|
|
28
|
+
read<T>(value: object): ElementMetadata<T> {
|
|
18
29
|
if (!this.has(value)) {
|
|
19
30
|
this.set(value, new ElementMetadata());
|
|
20
31
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
shadowDom: [
|
|
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
|
+
});
|
package/src/lib/query.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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) {
|
|
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
|
+
return res;
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/lib/result.ts
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
1
|
export interface ShadowResult {
|
|
2
|
-
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export abstract class JoistShadowResult implements ShadowResult {
|
|
6
|
-
strings;
|
|
7
|
-
values;
|
|
8
|
-
|
|
9
|
-
constructor(raw: TemplateStringsArray, ...values: any[]) {
|
|
10
|
-
this.strings = raw;
|
|
11
|
-
this.values = values;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
run(el: HTMLElement) {
|
|
15
|
-
if (!el.shadowRoot) {
|
|
16
|
-
throw new Error('ShadowResult has not been applied');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
this.setup(el.shadowRoot);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
abstract setup(root: ShadowRoot): void;
|
|
2
|
+
apply(el: Element): void;
|
|
23
3
|
}
|