@joist/element 4.0.0-next.2 → 4.0.0-next.4
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 +17 -19
- package/package.json +1 -1
- package/src/lib/attr.test.ts +16 -0
- package/src/lib/attr.ts +12 -4
- package/src/lib/element.test.ts +45 -2
- package/src/lib/element.ts +63 -30
- package/src/lib/listen.ts +10 -4
- package/src/lib/metadata.ts +4 -1
- package/src/lib/query.test.ts +54 -0
- package/src/lib/query.ts +37 -0
- package/src/lib/result.ts +12 -16
- package/src/lib/tags.ts +9 -66
- package/src/lib.ts +3 -3
- package/target/lib/attr.d.ts +4 -1
- package/target/lib/attr.js +7 -3
- package/target/lib/attr.js.map +1 -1
- package/target/lib/attr.test.js +38 -0
- package/target/lib/attr.test.js.map +1 -1
- package/target/lib/element.d.ts +7 -2
- package/target/lib/element.js +46 -18
- package/target/lib/element.js.map +1 -1
- package/target/lib/element.test.js +76 -3
- 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 +6 -1
- 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.js +80 -0
- package/target/lib/query.test.js.map +1 -0
- package/target/lib/result.d.ts +6 -5
- package/target/lib/result.js +6 -11
- package/target/lib/result.js.map +1 -1
- package/target/lib/tags.d.ts +5 -19
- package/target/lib/tags.js +9 -30
- package/target/lib/tags.js.map +1 -1
- package/target/lib.d.ts +3 -3
- package/target/lib.js +3 -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/tags.test.ts +0 -28
- 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.js +0 -69
- package/target/lib/shadow.test.js.map +0 -1
- package/target/lib/tags.test.d.ts +0 -1
- package/target/lib/tags.test.js +0 -23
- package/target/lib/tags.test.js.map +0 -1
- /package/target/lib/{shadow.test.d.ts → query.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -11,30 +11,28 @@ npm i @joist/element
|
|
|
11
11
|
#### Example:
|
|
12
12
|
|
|
13
13
|
```TS
|
|
14
|
-
import { tagName,
|
|
14
|
+
import { tagName, css, html, attr, listen, element } from '@joist/element';
|
|
15
15
|
|
|
16
16
|
@element({
|
|
17
|
-
tagName: 'my-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
|
+
]
|
|
18
29
|
})
|
|
19
30
|
export class MyElement extends HTMLElement {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
:host {
|
|
23
|
-
display: block;
|
|
24
|
-
color: red;
|
|
25
|
-
}
|
|
26
|
-
`;
|
|
31
|
+
@attr()
|
|
32
|
+
accessor value = 0;
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<slot></slot>
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
// define attributes
|
|
34
|
-
@attr() accessor value = 0;
|
|
35
|
-
|
|
36
|
-
// listen for events
|
|
37
|
-
@listen('click') onClick() {
|
|
34
|
+
@listen('click')
|
|
35
|
+
onClick() {
|
|
38
36
|
console.log('clicked!')
|
|
39
37
|
}
|
|
40
38
|
}
|
package/package.json
CHANGED
package/src/lib/attr.test.ts
CHANGED
|
@@ -110,4 +110,20 @@ describe('@attr()', () => {
|
|
|
110
110
|
'value-from-symbol'
|
|
111
111
|
]);
|
|
112
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
|
+
});
|
|
113
129
|
});
|
package/src/lib/attr.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { metadataStore } from './metadata.js';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
3
|
+
export interface AttrOpts {
|
|
4
|
+
observe?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function attr(opts?: AttrOpts) {
|
|
8
|
+
return function attrDecorator<This extends HTMLElement>(
|
|
5
9
|
{ get, set }: ClassAccessorDecoratorTarget<This, unknown>,
|
|
6
10
|
ctx: ClassAccessorDecoratorContext<This>
|
|
7
|
-
): ClassAccessorDecoratorResult<This, any>
|
|
11
|
+
): ClassAccessorDecoratorResult<This, any> {
|
|
8
12
|
const attrName = parseAttrName(ctx.name);
|
|
9
13
|
const meta = metadataStore.read(ctx.metadata);
|
|
10
14
|
|
|
11
|
-
meta.attrs.push({
|
|
15
|
+
meta.attrs.push({
|
|
16
|
+
propName: ctx.name,
|
|
17
|
+
attrName,
|
|
18
|
+
observe: opts?.observe ?? true
|
|
19
|
+
});
|
|
12
20
|
|
|
13
21
|
return {
|
|
14
22
|
set(value: unknown) {
|
package/src/lib/element.test.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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 { css, html } from './tags.js';
|
|
5
6
|
|
|
6
7
|
describe('@element()', () => {
|
|
7
8
|
it('should write default value to attribute', async () => {
|
|
@@ -19,7 +20,7 @@ describe('@element()', () => {
|
|
|
19
20
|
accessor value3 = true; // boolean
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const el = await fixture<MyElement>(
|
|
23
|
+
const el = await fixture<MyElement>(litHtml`<element-1></element-1>`);
|
|
23
24
|
|
|
24
25
|
expect(el.getAttribute('value1')).to.equal('hello');
|
|
25
26
|
expect(el.getAttribute('value2')).to.equal('0');
|
|
@@ -39,6 +40,9 @@ describe('@element()', () => {
|
|
|
39
40
|
|
|
40
41
|
@attr()
|
|
41
42
|
accessor value3 = true; // boolean
|
|
43
|
+
|
|
44
|
+
@attr({ observe: false }) // should be filtered out
|
|
45
|
+
accessor value4 = 'hello world';
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
expect(Reflect.get(MyElement, 'observedAttributes')).to.deep.equal([
|
|
@@ -47,4 +51,43 @@ describe('@element()', () => {
|
|
|
47
51
|
'value3'
|
|
48
52
|
]);
|
|
49
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
|
+
(el) => {
|
|
78
|
+
const div = document.createElement('div');
|
|
79
|
+
div.innerHTML = 'hello world';
|
|
80
|
+
|
|
81
|
+
el.append(div);
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
})
|
|
85
|
+
class MyElement extends HTMLElement {}
|
|
86
|
+
|
|
87
|
+
const el = new MyElement();
|
|
88
|
+
|
|
89
|
+
expect(el.shadowRoot!.adoptedStyleSheets.length).to.equal(1);
|
|
90
|
+
expect(el.shadowRoot!.innerHTML).to.equal(`<slot></slot>`);
|
|
91
|
+
expect(el.innerHTML).to.equal(`<div>hello world</div>`);
|
|
92
|
+
});
|
|
50
93
|
});
|
package/src/lib/element.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { metadataStore } from './metadata.js';
|
|
1
|
+
import { AttrDef, metadataStore } from './metadata.js';
|
|
2
|
+
import { ShadowResult } from './result.js';
|
|
2
3
|
|
|
3
|
-
export interface ElementOpts {
|
|
4
|
+
export interface ElementOpts<T> {
|
|
4
5
|
tagName?: string;
|
|
6
|
+
shadow?: Array<ShadowResult | ((el: T) => void)>;
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export const LifeCycle = {
|
|
10
|
+
onInit: Symbol('onInit')
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function element<
|
|
14
|
+
Target extends CustomElementConstructor,
|
|
15
|
+
Instance extends InstanceType<Target>
|
|
16
|
+
>(opts?: ElementOpts<Instance>) {
|
|
17
|
+
return function elementDecorator(Base: Target, ctx: ClassDecoratorContext<Target>) {
|
|
12
18
|
const meta = metadataStore.read(ctx.metadata);
|
|
13
19
|
|
|
14
20
|
ctx.addInitializer(function (this: Target) {
|
|
@@ -20,41 +26,68 @@ export function element(opts?: ElementOpts) {
|
|
|
20
26
|
});
|
|
21
27
|
|
|
22
28
|
return class JoistElement extends Base {
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
static observedAttributes = meta.attrs
|
|
30
|
+
.filter(({ observe }) => observe) // filter out attributes that are not to be observed
|
|
31
|
+
.map(({ attrName }) => attrName);
|
|
25
32
|
|
|
26
33
|
constructor(...args: any[]) {
|
|
27
34
|
super(...args);
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
}
|
|
36
|
+
if (opts?.shadow) {
|
|
37
|
+
if (!this.shadowRoot) {
|
|
38
|
+
this.attachShadow({ mode: 'open' });
|
|
39
|
+
}
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// reflect values back to attributes
|
|
41
|
-
if (value !== null && value !== undefined && value !== '') {
|
|
42
|
-
if (typeof value === 'boolean') {
|
|
43
|
-
if (value === true) {
|
|
44
|
-
// set boolean attribute
|
|
45
|
-
this.setAttribute(attrName, '');
|
|
46
|
-
}
|
|
41
|
+
for (let res of opts.shadow) {
|
|
42
|
+
if (typeof res === 'function') {
|
|
43
|
+
res(this as unknown as Instance);
|
|
47
44
|
} else {
|
|
48
|
-
|
|
49
|
-
this.setAttribute(attrName, String(value));
|
|
45
|
+
res.run(this);
|
|
50
46
|
}
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
for (let [event, { cb, root }] of meta.listeners) {
|
|
51
|
+
root(this).addEventListener(event, cb.bind(this));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (LifeCycle.onInit in this) {
|
|
55
|
+
const onInit = Reflect.get(this, LifeCycle.onInit);
|
|
56
|
+
|
|
57
|
+
if (typeof onInit === 'function') {
|
|
58
|
+
onInit();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
connectedCallback() {
|
|
64
|
+
if (this.isConnected) {
|
|
65
|
+
reflectAttributeValues(this, meta.attrs);
|
|
66
|
+
|
|
67
|
+
if (super.connectedCallback) {
|
|
68
|
+
super.connectedCallback();
|
|
69
|
+
}
|
|
56
70
|
}
|
|
57
71
|
}
|
|
58
72
|
};
|
|
59
73
|
};
|
|
60
74
|
}
|
|
75
|
+
|
|
76
|
+
function reflectAttributeValues(el: HTMLElement, attrs: AttrDef[]) {
|
|
77
|
+
for (let { propName, attrName } of attrs) {
|
|
78
|
+
const value = Reflect.get(el, propName);
|
|
79
|
+
|
|
80
|
+
// reflect values back to attributes
|
|
81
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
82
|
+
if (typeof value === 'boolean') {
|
|
83
|
+
if (value === true) {
|
|
84
|
+
// set boolean attribute
|
|
85
|
+
el.setAttribute(attrName, '');
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
// set key/value attribute
|
|
89
|
+
el.setAttribute(attrName, String(value));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
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
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
export interface AttrDef {
|
|
4
4
|
propName: string | symbol;
|
|
5
5
|
attrName: string;
|
|
6
|
+
observe: boolean;
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
export type ListenerRootSelector = (el: HTMLElement) => HTMLElement | ShadowRoot;
|
|
10
|
+
|
|
8
11
|
export class ElementMetadata {
|
|
9
12
|
attrs: AttrDef[] = [];
|
|
10
|
-
listeners = new Map<string, (e: Event) => void>();
|
|
13
|
+
listeners = new Map<string, { cb: (e: Event) => void; root: ListenerRootSelector }>();
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export class MetadataStore extends WeakMap<object, ElementMetadata> {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { element } from './element.js';
|
|
3
|
+
import { query } from './query.js';
|
|
4
|
+
import { html } from './tags.js';
|
|
5
|
+
|
|
6
|
+
describe('query', () => {
|
|
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
|
+
});
|
|
54
|
+
});
|
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 Element = Element>(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
|
+
}
|
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
|
}
|
package/src/lib/tags.ts
CHANGED
|
@@ -1,57 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
* NOTE: TemplateStringsArray can be used to cache via a WeakMap.
|
|
3
|
-
*
|
|
4
|
-
* function html(strs: TemplateStringsArray) {
|
|
5
|
-
* return strs
|
|
6
|
-
* }
|
|
7
|
-
*
|
|
8
|
-
* class Foo {
|
|
9
|
-
* hello = html`world`;
|
|
10
|
-
* }
|
|
11
|
-
*
|
|
12
|
-
* // these will be the same instance of TemplateStringsArray
|
|
13
|
-
* new Foo().hello === new Foo().hello
|
|
14
|
-
*/
|
|
1
|
+
import { JoistShadowResult } from './result.js';
|
|
15
2
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
type MathTags = keyof MathMLElementTagNameMap;
|
|
21
|
-
|
|
22
|
-
export const htmlTemplateCache = new WeakMap<TemplateStringsArray, HTMLTemplateElement>();
|
|
23
|
-
|
|
24
|
-
export class HTMLResult extends ShadowResult {
|
|
25
|
-
query<K extends Tags>(selectors: K): HTMLElementTagNameMap[K] | null;
|
|
26
|
-
query<K extends SVGTags>(selectors: K): SVGElementTagNameMap[K] | null;
|
|
27
|
-
query<K extends MathTags>(selectors: K): MathMLElementTagNameMap[K] | null;
|
|
28
|
-
query<E extends Element = Element>(selectors: string): E | null;
|
|
29
|
-
query<K extends Tags>(query: K) {
|
|
30
|
-
return this.shadow.querySelector<K>(query);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
queryAll<K extends Tags>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
|
|
34
|
-
queryAll<K extends SVGTags>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
|
|
35
|
-
queryAll<K extends MathTags>(selectors: K): NodeListOf<MathMLElementTagNameMap[K]>;
|
|
36
|
-
queryAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
|
|
37
|
-
queryAll<K extends Tags>(query: K) {
|
|
38
|
-
return this.shadow.querySelectorAll<K>(query);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* THe HTMLTemplateElement itself will be cached but a new instance of the result returned
|
|
43
|
-
*/
|
|
44
|
-
apply(root: ShadowRoot): void {
|
|
45
|
-
let template: HTMLTemplateElement;
|
|
46
|
-
|
|
47
|
-
if (htmlTemplateCache.has(this.strings)) {
|
|
48
|
-
template = htmlTemplateCache.get(this.strings) as HTMLTemplateElement;
|
|
49
|
-
} else {
|
|
50
|
-
template = document.createElement('template');
|
|
51
|
-
|
|
52
|
-
template.innerHTML = concat(this.strings);
|
|
53
|
-
htmlTemplateCache.set(this.strings, template);
|
|
54
|
-
}
|
|
3
|
+
export class HTMLResult extends JoistShadowResult {
|
|
4
|
+
setup(root: ShadowRoot): void {
|
|
5
|
+
let template = document.createElement('template');
|
|
6
|
+
template.innerHTML = concat(this.strings);
|
|
55
7
|
|
|
56
8
|
root.append(template.content.cloneNode(true));
|
|
57
9
|
}
|
|
@@ -61,19 +13,10 @@ export function html(strings: TemplateStringsArray, ...values: any[]): HTMLResul
|
|
|
61
13
|
return new HTMLResult(strings, ...values);
|
|
62
14
|
}
|
|
63
15
|
|
|
64
|
-
export
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let sheet: CSSStyleSheet;
|
|
69
|
-
|
|
70
|
-
if (styleSheetCache.has(this.strings)) {
|
|
71
|
-
sheet = styleSheetCache.get(this.strings) as CSSStyleSheet;
|
|
72
|
-
} else {
|
|
73
|
-
sheet = new CSSStyleSheet();
|
|
74
|
-
|
|
75
|
-
sheet.replaceSync(concat(this.strings));
|
|
76
|
-
}
|
|
16
|
+
export class CSSResult extends JoistShadowResult {
|
|
17
|
+
setup(root: ShadowRoot): void {
|
|
18
|
+
let sheet = new CSSStyleSheet();
|
|
19
|
+
sheet.replaceSync(concat(this.strings));
|
|
77
20
|
|
|
78
21
|
root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
|
|
79
22
|
}
|
package/src/lib.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { JoistShadowResult as TemplateResult } from './lib/result.js';
|
|
2
2
|
export { css, html, HTMLResult, CSSResult } from './lib/tags.js';
|
|
3
|
-
export { shadow } from './lib/shadow.js';
|
|
4
3
|
export { attr } from './lib/attr.js';
|
|
5
4
|
export { listen } from './lib/listen.js';
|
|
6
|
-
export { element } from './lib/element.js';
|
|
5
|
+
export { element, LifeCycle } from './lib/element.js';
|
|
6
|
+
export { query } from './lib/query.js';
|
package/target/lib/attr.d.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface AttrOpts {
|
|
2
|
+
observe?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export declare function attr(opts?: AttrOpts): <This extends HTMLElement>({ get, set }: ClassAccessorDecoratorTarget<This, unknown>, ctx: ClassAccessorDecoratorContext<This>) => ClassAccessorDecoratorResult<This, any>;
|
package/target/lib/attr.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { metadataStore } from './metadata.js';
|
|
2
|
-
export function attr() {
|
|
3
|
-
return ({ get, set }, ctx)
|
|
2
|
+
export function attr(opts) {
|
|
3
|
+
return function attrDecorator({ get, set }, ctx) {
|
|
4
4
|
const attrName = parseAttrName(ctx.name);
|
|
5
5
|
const meta = metadataStore.read(ctx.metadata);
|
|
6
|
-
meta.attrs.push({
|
|
6
|
+
meta.attrs.push({
|
|
7
|
+
propName: ctx.name,
|
|
8
|
+
attrName,
|
|
9
|
+
observe: opts?.observe ?? true
|
|
10
|
+
});
|
|
7
11
|
return {
|
|
8
12
|
set(value) {
|
|
9
13
|
if (value === true) {
|
package/target/lib/attr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attr.js","sourceRoot":"","sources":["../../src/lib/attr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"attr.js","sourceRoot":"","sources":["../../src/lib/attr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAM9C,MAAM,UAAU,IAAI,CAAC,IAAe;IAClC,OAAO,SAAS,aAAa,CAC3B,EAAE,GAAG,EAAE,GAAG,EAA+C,EACzD,GAAwC;QAExC,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAE9C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YACd,QAAQ,EAAE,GAAG,CAAC,IAAI;YAClB,QAAQ;YACR,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,IAAI;SAC/B,CAAC,CAAC;QAEH,OAAO;YACL,GAAG,CAAC,KAAc;gBAChB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACnB,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;gBAClC,CAAC;qBAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;oBAC3B,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC7C,CAAC;gBAED,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACxB,CAAC;YACD,GAAG;gBACD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;gBAEzC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAElB,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;wBAChB,OAAO,IAAI,CAAC;oBACd,CAAC;oBAGD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;wBAChC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;oBACtB,CAAC;oBAGD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAGD,OAAO,OAAO,CAAC;YACjB,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,GAAoB;IACzC,IAAI,KAAa,CAAC;IAElB,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,GAAG,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC"}
|
package/target/lib/attr.test.js
CHANGED
|
@@ -242,5 +242,43 @@ describe('@attr()', () => {
|
|
|
242
242
|
'value-from-symbol'
|
|
243
243
|
]);
|
|
244
244
|
});
|
|
245
|
+
it('should throw an error for symbols with no description', async () => {
|
|
246
|
+
expect(() => {
|
|
247
|
+
const value = Symbol();
|
|
248
|
+
let MyElement = (() => {
|
|
249
|
+
var _a;
|
|
250
|
+
let _classDecorators = [element({
|
|
251
|
+
tagName: 'attr-test-4'
|
|
252
|
+
})];
|
|
253
|
+
let _classDescriptor;
|
|
254
|
+
let _classExtraInitializers = [];
|
|
255
|
+
let _classThis;
|
|
256
|
+
let _classSuper = HTMLElement;
|
|
257
|
+
let _member_decorators;
|
|
258
|
+
let _member_initializers = [];
|
|
259
|
+
let _member_extraInitializers = [];
|
|
260
|
+
var MyElement = class extends _classSuper {
|
|
261
|
+
static { _classThis = this; }
|
|
262
|
+
static {
|
|
263
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
264
|
+
__esDecorate(this, null, _member_decorators, { kind: "accessor", name: _a, static: false, private: false, access: { has: obj => _a in obj, get: obj => obj[_a], set: (obj, value) => { obj[_a] = value; } }, metadata: _metadata }, _member_initializers, _member_extraInitializers);
|
|
265
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
266
|
+
MyElement = _classThis = _classDescriptor.value;
|
|
267
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
268
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
269
|
+
}
|
|
270
|
+
#_a_accessor_storage = __runInitializers(this, _member_initializers, true);
|
|
271
|
+
get [(_member_decorators = [attr()], _a = __propKey(value))]() { return this.#_a_accessor_storage; }
|
|
272
|
+
set [_a](value) { this.#_a_accessor_storage = value; }
|
|
273
|
+
constructor() {
|
|
274
|
+
super(...arguments);
|
|
275
|
+
__runInitializers(this, _member_extraInitializers);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
return MyElement = _classThis;
|
|
279
|
+
})();
|
|
280
|
+
new MyElement();
|
|
281
|
+
}).to.throw('Cannot handle Symbol property without description');
|
|
282
|
+
});
|
|
245
283
|
});
|
|
246
284
|
//# sourceMappingURL=attr.test.js.map
|