@joist/element 4.0.0-next.1 → 4.0.0-next.3
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 +20 -23
- package/package.json +1 -1
- package/src/lib/attr.test.ts +85 -20
- package/src/lib/attr.ts +61 -34
- package/src/lib/element.test.ts +70 -8
- package/src/lib/element.ts +61 -39
- package/src/lib/listen.ts +10 -4
- package/src/lib/metadata.ts +10 -3
- package/src/lib/result.ts +12 -16
- package/src/lib/tags.ts +9 -66
- package/src/lib.ts +1 -3
- package/target/lib/attr.d.ts +4 -1
- package/target/lib/attr.js +49 -28
- package/target/lib/attr.js.map +1 -1
- package/target/lib/attr.test.js +148 -24
- package/target/lib/attr.test.js.map +1 -1
- package/target/lib/element.d.ts +6 -1
- package/target/lib/element.js +47 -31
- package/target/lib/element.js.map +1 -1
- package/target/lib/element.test.js +131 -17
- package/target/lib/element.test.js.map +1 -1
- package/target/lib/listen.d.ts +2 -1
- package/target/lib/listen.js +6 -3
- package/target/lib/listen.js.map +1 -1
- package/target/lib/metadata.d.ts +11 -3
- package/target/lib/metadata.js +0 -1
- package/target/lib/metadata.js.map +1 -1
- package/target/lib/result.d.ts +6 -3
- package/target/lib/result.js +7 -4
- package/target/lib/result.js.map +1 -1
- package/target/lib/tags.d.ts +5 -17
- package/target/lib/tags.js +5 -11
- package/target/lib/tags.js.map +1 -1
- package/target/lib.d.ts +1 -3
- package/target/lib.js +1 -3
- package/target/lib.js.map +1 -1
- package/src/lib/shadow.test.ts +0 -40
- package/src/lib/shadow.ts +0 -16
- package/src/lib/tag-name.test.ts +0 -13
- package/src/lib/tag-name.ts +0 -10
- package/target/lib/shadow.d.ts +0 -2
- package/target/lib/shadow.js +0 -10
- package/target/lib/shadow.js.map +0 -1
- package/target/lib/shadow.test.d.ts +0 -1
- package/target/lib/shadow.test.js +0 -69
- package/target/lib/shadow.test.js.map +0 -1
- package/target/lib/tag-name.d.ts +0 -1
- package/target/lib/tag-name.js +0 -6
- package/target/lib/tag-name.js.map +0 -1
- package/target/lib/tag-name.test.d.ts +0 -1
- package/target/lib/tag-name.test.js +0 -36
- package/target/lib/tag-name.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,31 +11,28 @@ npm i @joist/element
|
|
|
11
11
|
#### Example:
|
|
12
12
|
|
|
13
13
|
```TS
|
|
14
|
-
import { tagName,
|
|
15
|
-
|
|
16
|
-
@element
|
|
14
|
+
import { tagName, css, html, attr, listen, element } from '@joist/element';
|
|
15
|
+
|
|
16
|
+
@element({
|
|
17
|
+
tagName: 'my-element',
|
|
18
|
+
shadow: [
|
|
19
|
+
css`
|
|
20
|
+
:host {
|
|
21
|
+
display: block;
|
|
22
|
+
color: red;
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
25
|
+
html`
|
|
26
|
+
<slot></slot>
|
|
27
|
+
`
|
|
28
|
+
]
|
|
29
|
+
})
|
|
17
30
|
export class MyElement extends HTMLElement {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// apply styles to shadow dom
|
|
22
|
-
@shadow styles = css`
|
|
23
|
-
:host {
|
|
24
|
-
display: block;
|
|
25
|
-
color: red;
|
|
26
|
-
}
|
|
27
|
-
`;
|
|
28
|
-
|
|
29
|
-
// apply html to shadow dom
|
|
30
|
-
@shadow template = html`
|
|
31
|
-
<slot></slot>
|
|
32
|
-
`;
|
|
33
|
-
|
|
34
|
-
// define attributes
|
|
35
|
-
@attr accessor value = 0;
|
|
31
|
+
@attr()
|
|
32
|
+
accessor value = 0;
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
@listen('click')
|
|
35
|
+
onClick() {
|
|
39
36
|
console.log('clicked!')
|
|
40
37
|
}
|
|
41
38
|
}
|
package/package.json
CHANGED
package/src/lib/attr.test.ts
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { expect, fixture, html } from '@open-wc/testing';
|
|
2
2
|
|
|
3
3
|
import { attr } from './attr.js';
|
|
4
|
+
import { element } from './element.js';
|
|
4
5
|
|
|
5
6
|
describe('@attr()', () => {
|
|
6
7
|
it('should read and parse the correct values', async () => {
|
|
8
|
+
@element({
|
|
9
|
+
tagName: 'attr-test-1'
|
|
10
|
+
})
|
|
7
11
|
class MyElement extends HTMLElement {
|
|
8
|
-
@attr
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@attr
|
|
12
|
-
|
|
12
|
+
@attr()
|
|
13
|
+
accessor value1 = 100; // no attribute
|
|
14
|
+
|
|
15
|
+
@attr()
|
|
16
|
+
accessor value2 = 0; // number
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
@attr()
|
|
19
|
+
accessor value3 = false; // boolean
|
|
20
|
+
|
|
21
|
+
@attr()
|
|
22
|
+
accessor value4 = 'hello'; // string
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
const el = await fixture<MyElement>(
|
|
17
|
-
html`<attr-test-
|
|
26
|
+
html`<attr-test-1 value2="2" value3 value4="world"></attr-test-1>`
|
|
18
27
|
);
|
|
19
28
|
|
|
20
29
|
expect(el.value1).to.equal(100);
|
|
@@ -24,15 +33,21 @@ describe('@attr()', () => {
|
|
|
24
33
|
});
|
|
25
34
|
|
|
26
35
|
it('should not write falsy props to attributes', async () => {
|
|
36
|
+
@element({
|
|
37
|
+
tagName: 'attr-test-2'
|
|
38
|
+
})
|
|
27
39
|
class MyElement extends HTMLElement {
|
|
28
|
-
@attr
|
|
29
|
-
|
|
30
|
-
@attr accessor value3 = '';
|
|
31
|
-
}
|
|
40
|
+
@attr()
|
|
41
|
+
accessor value1 = undefined;
|
|
32
42
|
|
|
33
|
-
|
|
43
|
+
@attr()
|
|
44
|
+
accessor value2 = null;
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
@attr()
|
|
47
|
+
accessor value3 = '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const el = await fixture<MyElement>(html`<attr-test-2></attr-test-2>`);
|
|
36
51
|
|
|
37
52
|
expect(el.hasAttribute('value1')).to.be.false;
|
|
38
53
|
expect(el.hasAttribute('value2')).to.be.false;
|
|
@@ -40,16 +55,24 @@ describe('@attr()', () => {
|
|
|
40
55
|
});
|
|
41
56
|
|
|
42
57
|
it('should update attributes when props are changed', async () => {
|
|
58
|
+
@element({
|
|
59
|
+
tagName: 'attr-test-3'
|
|
60
|
+
})
|
|
43
61
|
class MyElement extends HTMLElement {
|
|
44
|
-
@attr
|
|
45
|
-
|
|
46
|
-
@attr accessor value3 = true; // boolean
|
|
47
|
-
@attr accessor value4 = false; // boolean
|
|
48
|
-
}
|
|
62
|
+
@attr()
|
|
63
|
+
accessor value1 = 'hello'; // no attribute
|
|
49
64
|
|
|
50
|
-
|
|
65
|
+
@attr()
|
|
66
|
+
accessor value2 = 0; // number
|
|
51
67
|
|
|
52
|
-
|
|
68
|
+
@attr()
|
|
69
|
+
accessor value3 = true; // boolean
|
|
70
|
+
|
|
71
|
+
@attr()
|
|
72
|
+
accessor value4 = false; // boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const el = await fixture<MyElement>(html`<attr-test-3></attr-test-3>`);
|
|
53
76
|
|
|
54
77
|
el.value1 = 'world';
|
|
55
78
|
el.value2 = 100;
|
|
@@ -61,4 +84,46 @@ describe('@attr()', () => {
|
|
|
61
84
|
expect(el.hasAttribute('value3')).to.be.false;
|
|
62
85
|
expect(el.hasAttribute('value4')).to.be.true;
|
|
63
86
|
});
|
|
87
|
+
|
|
88
|
+
it('should normalize attribute names', async () => {
|
|
89
|
+
const value3 = Symbol('Value from SYMBOL');
|
|
90
|
+
|
|
91
|
+
@element({
|
|
92
|
+
tagName: 'attr-test-4'
|
|
93
|
+
})
|
|
94
|
+
class MyElement extends HTMLElement {
|
|
95
|
+
@attr()
|
|
96
|
+
accessor Value1 = 'hello';
|
|
97
|
+
|
|
98
|
+
@attr()
|
|
99
|
+
accessor ['Value 2'] = 0;
|
|
100
|
+
|
|
101
|
+
@attr()
|
|
102
|
+
accessor [value3] = true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const el = await fixture<MyElement>(html`<attr-test-4></attr-test-4>`);
|
|
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
|
+
});
|
|
64
129
|
});
|
package/src/lib/attr.ts
CHANGED
|
@@ -1,46 +1,73 @@
|
|
|
1
1
|
import { metadataStore } from './metadata.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
this.setAttribute(name, '');
|
|
15
|
-
} else if (value === false) {
|
|
16
|
-
this.removeAttribute(name);
|
|
17
|
-
} else {
|
|
18
|
-
this.setAttribute(name, String(value));
|
|
19
|
-
}
|
|
3
|
+
export interface AttrOpts {
|
|
4
|
+
observe?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function attr(opts?: AttrOpts) {
|
|
8
|
+
return function attrDecorator<This extends HTMLElement>(
|
|
9
|
+
{ get, set }: ClassAccessorDecoratorTarget<This, unknown>,
|
|
10
|
+
ctx: ClassAccessorDecoratorContext<This>
|
|
11
|
+
): ClassAccessorDecoratorResult<This, any> {
|
|
12
|
+
const attrName = parseAttrName(ctx.name);
|
|
13
|
+
const meta = metadataStore.read(ctx.metadata);
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
meta.attrs.push({
|
|
16
|
+
propName: ctx.name,
|
|
17
|
+
attrName,
|
|
18
|
+
observe: opts?.observe ?? true
|
|
19
|
+
});
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
21
|
+
return {
|
|
22
|
+
set(value: unknown) {
|
|
23
|
+
if (value === true) {
|
|
24
|
+
this.setAttribute(attrName, '');
|
|
25
|
+
} else if (value === false) {
|
|
26
|
+
this.removeAttribute(attrName);
|
|
27
|
+
} else {
|
|
28
|
+
this.setAttribute(attrName, String(value));
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
set.call(this, value);
|
|
32
|
+
},
|
|
33
|
+
get() {
|
|
34
|
+
const ogValue = get.call(this);
|
|
35
|
+
const attr = this.getAttribute(attrName);
|
|
36
|
+
|
|
37
|
+
if (attr !== null) {
|
|
38
|
+
// treat as boolean
|
|
39
|
+
if (attr === '') {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// treat as number
|
|
44
|
+
if (typeof ogValue === 'number') {
|
|
45
|
+
return Number(attr);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// treat as string
|
|
49
|
+
return attr;
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
//
|
|
39
|
-
return
|
|
52
|
+
// no readable value return original
|
|
53
|
+
return ogValue;
|
|
40
54
|
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseAttrName(val: string | symbol): string {
|
|
60
|
+
let value: string;
|
|
41
61
|
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
if (typeof val === 'symbol') {
|
|
63
|
+
if (val.description) {
|
|
64
|
+
value = val.description;
|
|
65
|
+
} else {
|
|
66
|
+
throw new Error('Cannot handle Symbol property without description');
|
|
44
67
|
}
|
|
45
|
-
}
|
|
68
|
+
} else {
|
|
69
|
+
value = val;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return value.toLowerCase().replaceAll(' ', '-');
|
|
46
73
|
}
|
package/src/lib/element.test.ts
CHANGED
|
@@ -1,24 +1,86 @@
|
|
|
1
|
-
import { expect, fixture, html } from '@open-wc/testing';
|
|
1
|
+
import { expect, fixture, html as litHtml } from '@open-wc/testing';
|
|
2
2
|
|
|
3
3
|
import { attr } from './attr.js';
|
|
4
4
|
import { element } from './element.js';
|
|
5
|
-
import {
|
|
5
|
+
import { css, html } from './tags.js';
|
|
6
6
|
|
|
7
7
|
describe('@element()', () => {
|
|
8
8
|
it('should write default value to attribute', async () => {
|
|
9
|
-
@element
|
|
9
|
+
@element({
|
|
10
|
+
tagName: 'element-1'
|
|
11
|
+
})
|
|
10
12
|
class MyElement extends HTMLElement {
|
|
11
|
-
@
|
|
13
|
+
@attr()
|
|
14
|
+
accessor value1 = 'hello'; // no attribute
|
|
12
15
|
|
|
13
|
-
@attr
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
@attr()
|
|
17
|
+
accessor value2 = 0; // number
|
|
18
|
+
|
|
19
|
+
@attr()
|
|
20
|
+
accessor value3 = true; // boolean
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
const el = await fixture<MyElement>(
|
|
23
|
+
const el = await fixture<MyElement>(litHtml`<element-1></element-1>`);
|
|
19
24
|
|
|
20
25
|
expect(el.getAttribute('value1')).to.equal('hello');
|
|
21
26
|
expect(el.getAttribute('value2')).to.equal('0');
|
|
22
27
|
expect(el.getAttribute('value3')).to.equal('');
|
|
23
28
|
});
|
|
29
|
+
|
|
30
|
+
it('should register attributes', async () => {
|
|
31
|
+
@element({
|
|
32
|
+
tagName: 'element-2'
|
|
33
|
+
})
|
|
34
|
+
class MyElement extends HTMLElement {
|
|
35
|
+
@attr()
|
|
36
|
+
accessor value1 = 'hello'; // no attribute
|
|
37
|
+
|
|
38
|
+
@attr()
|
|
39
|
+
accessor value2 = 0; // number
|
|
40
|
+
|
|
41
|
+
@attr()
|
|
42
|
+
accessor value3 = true; // boolean
|
|
43
|
+
|
|
44
|
+
@attr({ observe: false }) // should be filtered out
|
|
45
|
+
accessor value4 = 'hello world';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
expect(Reflect.get(MyElement, 'observedAttributes')).to.deep.equal([
|
|
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
|
+
});
|
|
24
86
|
});
|
package/src/lib/element.ts
CHANGED
|
@@ -1,55 +1,77 @@
|
|
|
1
|
-
import { metadataStore } from './metadata.js';
|
|
1
|
+
import { AttrDef, metadataStore } from './metadata.js';
|
|
2
|
+
import { ShadowResult } from './result.js';
|
|
2
3
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const meta = metadataStore.read(ctx.metadata);
|
|
4
|
+
export interface ElementOpts<T> {
|
|
5
|
+
tagName?: string;
|
|
6
|
+
shadow?: Array<ShadowResult | ((el: T) => void)>;
|
|
7
|
+
}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export function element<
|
|
10
|
+
Target extends CustomElementConstructor,
|
|
11
|
+
Instance extends InstanceType<Target>
|
|
12
|
+
>(opts?: ElementOpts<Instance>) {
|
|
13
|
+
return function elementDecorator(Base: Target, ctx: ClassDecoratorContext<Target>) {
|
|
14
|
+
const meta = metadataStore.read(ctx.metadata);
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
ctx.addInitializer(function (this: Target) {
|
|
17
|
+
if (opts?.tagName) {
|
|
18
|
+
if (!customElements.get(opts.tagName)) {
|
|
19
|
+
customElements.define(opts.tagName, this);
|
|
20
|
+
}
|
|
15
21
|
}
|
|
16
|
-
}
|
|
17
|
-
});
|
|
22
|
+
});
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
super(...args);
|
|
24
|
+
return class JoistElement extends Base {
|
|
25
|
+
static observedAttributes = meta.attrs
|
|
26
|
+
.filter(({ observe }) => observe) // filter out attributes that are not to be observed
|
|
27
|
+
.map(({ attrName }) => attrName);
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
constructor(...args: any[]) {
|
|
30
|
+
super(...args);
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
}
|
|
32
|
+
if (opts?.shadow) {
|
|
33
|
+
this.attachShadow({ mode: 'open' });
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (value !== null && value !== undefined && value !== '') {
|
|
38
|
-
if (typeof value === 'boolean') {
|
|
39
|
-
if (value === true) {
|
|
40
|
-
// set boolean attribute
|
|
41
|
-
this.setAttribute(attr, '');
|
|
35
|
+
for (let res of opts.shadow) {
|
|
36
|
+
if (typeof res === 'function') {
|
|
37
|
+
res(this as unknown as Instance);
|
|
38
|
+
} else {
|
|
39
|
+
res.run(this);
|
|
42
40
|
}
|
|
43
|
-
} else {
|
|
44
|
-
// set key/value attribute
|
|
45
|
-
this.setAttribute(attr, String(value));
|
|
46
41
|
}
|
|
47
42
|
}
|
|
43
|
+
|
|
44
|
+
for (let [event, { cb, root }] of meta.listeners) {
|
|
45
|
+
root(this).addEventListener(event, cb.bind(this));
|
|
46
|
+
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
connectedCallback() {
|
|
50
|
+
reflectAttributeValues(this, meta.attrs);
|
|
51
|
+
|
|
52
|
+
if (super.connectedCallback) {
|
|
53
|
+
super.connectedCallback();
|
|
54
|
+
}
|
|
52
55
|
}
|
|
53
|
-
}
|
|
56
|
+
};
|
|
54
57
|
};
|
|
55
58
|
}
|
|
59
|
+
|
|
60
|
+
function reflectAttributeValues(el: HTMLElement, attrs: AttrDef[]) {
|
|
61
|
+
for (let { propName, attrName } of attrs) {
|
|
62
|
+
const value = Reflect.get(el, propName);
|
|
63
|
+
|
|
64
|
+
// reflect values back to attributes
|
|
65
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
66
|
+
if (typeof value === 'boolean') {
|
|
67
|
+
if (value === true) {
|
|
68
|
+
// set boolean attribute
|
|
69
|
+
el.setAttribute(attrName, '');
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// set key/value attribute
|
|
73
|
+
el.setAttribute(attrName, String(value));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/lib/listen.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { metadataStore } from './metadata.js';
|
|
1
|
+
import { ListenerRootSelector, metadataStore } from './metadata.js';
|
|
2
2
|
|
|
3
|
-
export function listen<This extends HTMLElement>(event: string) {
|
|
4
|
-
return
|
|
3
|
+
export function listen<This extends HTMLElement>(event: string, root?: ListenerRootSelector) {
|
|
4
|
+
return function listenDecorator(
|
|
5
|
+
value: (e: Event) => void,
|
|
6
|
+
ctx: ClassMethodDecoratorContext<This>
|
|
7
|
+
) {
|
|
5
8
|
const metadata = metadataStore.read(ctx.metadata);
|
|
6
9
|
|
|
7
|
-
metadata.listeners.set(event,
|
|
10
|
+
metadata.listeners.set(event, {
|
|
11
|
+
cb: value,
|
|
12
|
+
root: root ?? ((el: HTMLElement) => el.shadowRoot ?? el)
|
|
13
|
+
});
|
|
8
14
|
};
|
|
9
15
|
}
|
package/src/lib/metadata.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
(Symbol as any).metadata ??= Symbol('Symbol.metadata');
|
|
2
2
|
|
|
3
|
+
export interface AttrDef {
|
|
4
|
+
propName: string | symbol;
|
|
5
|
+
attrName: string;
|
|
6
|
+
observe: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ListenerRootSelector = (el: HTMLElement) => HTMLElement | ShadowRoot;
|
|
10
|
+
|
|
3
11
|
export class ElementMetadata {
|
|
4
|
-
attrs:
|
|
5
|
-
|
|
6
|
-
listeners = new Map<string, (e: Event) => void>();
|
|
12
|
+
attrs: AttrDef[] = [];
|
|
13
|
+
listeners = new Map<string, { cb: (e: Event) => void; root: ListenerRootSelector }>();
|
|
7
14
|
}
|
|
8
15
|
|
|
9
16
|
export class MetadataStore extends WeakMap<object, ElementMetadata> {
|
package/src/lib/result.ts
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
#shadow: ShadowRoot | undefined = undefined;
|
|
6
|
-
|
|
7
|
-
get shadow() {
|
|
8
|
-
if (!this.#shadow) {
|
|
9
|
-
throw new Error('ShadowResult has not been applied');
|
|
10
|
-
}
|
|
1
|
+
export interface ShadowResult {
|
|
2
|
+
run(el: HTMLElement): void;
|
|
3
|
+
}
|
|
11
4
|
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
export abstract class JoistShadowResult implements ShadowResult {
|
|
6
|
+
strings;
|
|
7
|
+
values;
|
|
14
8
|
|
|
15
9
|
constructor(raw: TemplateStringsArray, ...values: any[]) {
|
|
16
10
|
this.strings = raw;
|
|
17
11
|
this.values = values;
|
|
18
12
|
}
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
run(el: HTMLElement) {
|
|
15
|
+
if (!el.shadowRoot) {
|
|
16
|
+
throw new Error('ShadowResult has not been applied');
|
|
17
|
+
}
|
|
22
18
|
|
|
23
|
-
this.
|
|
19
|
+
this.setup(el.shadowRoot);
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
abstract
|
|
22
|
+
abstract setup(root: ShadowRoot): void;
|
|
27
23
|
}
|