@joist/element 4.0.0-next.4 → 4.0.0-next.41
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-changed.test.ts +34 -0
- package/src/lib/attr-changed.ts +15 -0
- package/src/lib/attr.test.ts +159 -97
- package/src/lib/attr.ts +26 -33
- package/src/lib/element.test.ts +95 -71
- package/src/lib/element.ts +107 -51
- package/src/lib/lifecycle.test.ts +31 -0
- package/src/lib/lifecycle.ts +9 -0
- package/src/lib/listen.test.ts +104 -0
- package/src/lib/listen.ts +27 -7
- package/src/lib/metadata.ts +21 -8
- package/src/lib/query.test.ts +21 -47
- package/src/lib/query.ts +2 -8
- 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 +3 -2
- package/target/lib/attr-changed.d.ts +1 -0
- package/target/lib/attr-changed.js +10 -0
- package/target/lib/attr-changed.js.map +1 -0
- package/target/lib/attr-changed.test.d.ts +1 -0
- package/target/lib/attr-changed.test.js +54 -0
- package/target/lib/attr-changed.test.js.map +1 -0
- package/target/lib/attr.d.ts +3 -1
- package/target/lib/attr.js +23 -25
- package/target/lib/attr.js.map +1 -1
- package/target/lib/attr.test.js +373 -251
- package/target/lib/attr.test.js.map +1 -1
- package/target/lib/element.d.ts +8 -339
- package/target/lib/element.js +81 -39
- package/target/lib/element.js.map +1 -1
- package/target/lib/element.test.js +154 -178
- 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.d.ts +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 +167 -0
- package/target/lib/listen.test.js.map +1 -0
- package/target/lib/metadata.d.ts +20 -10
- package/target/lib/metadata.js +8 -2
- package/target/lib/metadata.js.map +1 -1
- package/target/lib/query.d.ts +1 -1
- package/target/lib/query.js +1 -6
- package/target/lib/query.js.map +1 -1
- package/target/lib/query.test.js +35 -74
- package/target/lib/query.test.js.map +1 -1
- package/target/lib/result.d.ts +1 -8
- package/target/lib/result.js +1 -14
- package/target/lib/result.js.map +1 -1
- package/target/lib/tags.d.ts +10 -6
- package/target/lib/tags.js +20 -11
- 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 +3 -2
- package/target/lib.js +3 -2
- package/target/lib.js.map +1 -1
package/src/lib/query.test.ts
CHANGED
|
@@ -1,54 +1,28 @@
|
|
|
1
|
-
import { expect } from '
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
|
|
2
3
|
import { element } from './element.js';
|
|
3
4
|
import { query } from './query.js';
|
|
4
5
|
import { html } from './tags.js';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
}
|
|
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
|
+
}
|
|
46
23
|
|
|
47
|
-
|
|
48
|
-
el.fname({ value: 'Foo' });
|
|
49
|
-
el.lname({ value: 'Bar' });
|
|
24
|
+
const el = new MyElement();
|
|
50
25
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
26
|
+
expect(el.fname()).to.equal(el.shadowRoot?.querySelector('#fname'));
|
|
27
|
+
expect(el.lname()).to.equal(el.shadowRoot?.querySelector('#lname'));
|
|
54
28
|
});
|
package/src/lib/query.ts
CHANGED
|
@@ -7,11 +7,11 @@ type QueryResult<T> = (updates?: Partial<T>) => T;
|
|
|
7
7
|
export function query<K extends Tags>(selectors: K): QueryResult<HTMLElementTagNameMap[K]>;
|
|
8
8
|
export function query<K extends SVGTags>(selectors: K): QueryResult<SVGElementTagNameMap[K]>;
|
|
9
9
|
export function query<K extends MathTags>(selectors: K): QueryResult<MathMLElementTagNameMap[K]>;
|
|
10
|
-
export function query<E extends
|
|
10
|
+
export function query<E extends HTMLElement = HTMLElement>(selectors: string): QueryResult<E>;
|
|
11
11
|
export function query<K extends Tags>(query: K): QueryResult<HTMLElementTagNameMap[K]> {
|
|
12
12
|
let res: HTMLElementTagNameMap[K] | null = null;
|
|
13
13
|
|
|
14
|
-
return function (this: HTMLElement
|
|
14
|
+
return function (this: HTMLElement) {
|
|
15
15
|
if (res) {
|
|
16
16
|
return res;
|
|
17
17
|
}
|
|
@@ -26,12 +26,6 @@ export function query<K extends Tags>(query: K): QueryResult<HTMLElementTagNameM
|
|
|
26
26
|
throw new Error('could not find element');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (updates) {
|
|
30
|
-
for (let update in updates) {
|
|
31
|
-
Reflect.set(res, update, updates[update]);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
29
|
return res;
|
|
36
30
|
};
|
|
37
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
|
}
|
package/src/lib/tags.ts
CHANGED
|
@@ -1,24 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ShadowResult } from './result.js';
|
|
2
2
|
|
|
3
|
-
export class HTMLResult extends
|
|
4
|
-
|
|
5
|
-
let template = document.createElement('template');
|
|
6
|
-
template.innerHTML = concat(this.strings);
|
|
3
|
+
export class HTMLResult<T extends HTMLElement> implements ShadowResult {
|
|
4
|
+
#template;
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
constructor(raw: TemplateStringsArray, ..._values: any[]) {
|
|
7
|
+
this.#template = document.createElement('template');
|
|
8
|
+
this.#template.innerHTML = concat(raw);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
apply(el: T): void {
|
|
12
|
+
if (el.shadowRoot) {
|
|
13
|
+
el.shadowRoot.append(this.#template.content.cloneNode(true));
|
|
14
|
+
}
|
|
9
15
|
}
|
|
10
16
|
}
|
|
11
17
|
|
|
12
|
-
export function html
|
|
18
|
+
export function html<T extends HTMLElement>(
|
|
19
|
+
strings: TemplateStringsArray,
|
|
20
|
+
...values: any[]
|
|
21
|
+
): HTMLResult<T> {
|
|
13
22
|
return new HTMLResult(strings, ...values);
|
|
14
23
|
}
|
|
15
24
|
|
|
16
|
-
export class CSSResult
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
export class CSSResult implements ShadowResult {
|
|
26
|
+
#sheet;
|
|
27
|
+
|
|
28
|
+
constructor(raw: TemplateStringsArray, ..._values: any[]) {
|
|
29
|
+
this.#sheet = new CSSStyleSheet();
|
|
30
|
+
this.#sheet.replaceSync(concat(raw));
|
|
31
|
+
}
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
apply(el: HTMLElement): void {
|
|
34
|
+
if (el.shadowRoot) {
|
|
35
|
+
el.shadowRoot.adoptedStyleSheets = [...el.shadowRoot.adoptedStyleSheets, this.#sheet];
|
|
36
|
+
}
|
|
22
37
|
}
|
|
23
38
|
}
|
|
24
39
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { assert } from 'chai';
|
|
2
|
+
|
|
3
|
+
import { template } from './template.js';
|
|
4
|
+
|
|
5
|
+
// Run all tests with both shadow and light dom
|
|
6
|
+
const TESTS = [
|
|
7
|
+
function bindableNodes(el: HTMLElement, root: HTMLElement | ShadowRoot) {
|
|
8
|
+
it(`should intialize bindable nodes ${root instanceof ShadowRoot ? '(ShadowDOM)' : '(LightDOM)'}`, () => {
|
|
9
|
+
el.title = 'Hello World';
|
|
10
|
+
el.ariaLabel = 'This is the label';
|
|
11
|
+
el.ariaDescription = 'This is the description';
|
|
12
|
+
|
|
13
|
+
root.innerHTML = /*html*/ `
|
|
14
|
+
<span #:bind="title"></span>
|
|
15
|
+
|
|
16
|
+
<ul>
|
|
17
|
+
<li #:bind="ariaLabel"></li>
|
|
18
|
+
<li #:bind="ariaDescription"></li>
|
|
19
|
+
</ul>
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
const render = template().bind(el);
|
|
23
|
+
|
|
24
|
+
render();
|
|
25
|
+
|
|
26
|
+
assert.equal(
|
|
27
|
+
root.innerHTML
|
|
28
|
+
.split('\n')
|
|
29
|
+
.map((res) => res.trim())
|
|
30
|
+
.join(''),
|
|
31
|
+
'<span #:bind="title">Hello World</span><ul><li #:bind="ariaLabel">This is the label</li><li #:bind="ariaDescription">This is the description</li></ul>'
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
function attributeNodes(el: HTMLElement, root: HTMLElement | ShadowRoot) {
|
|
36
|
+
it(`should intialize template attributes ${root instanceof ShadowRoot ? '(ShadowDOM)' : '(LightDOM)'}`, () => {
|
|
37
|
+
el.ariaLabel = 'This is the label';
|
|
38
|
+
el.ariaDescription = 'This is the description';
|
|
39
|
+
|
|
40
|
+
root.innerHTML = /*html*/ `
|
|
41
|
+
<ul #:aria-label="ariaLabel" #:aria-description="ariaDescription"></ul>
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const render = template().bind(el);
|
|
45
|
+
|
|
46
|
+
render();
|
|
47
|
+
|
|
48
|
+
assert.equal(
|
|
49
|
+
root.innerHTML
|
|
50
|
+
.split('\n')
|
|
51
|
+
.map((res) => res.trim())
|
|
52
|
+
.join(''),
|
|
53
|
+
'<ul #:aria-label="ariaLabel" #:aria-description="ariaDescription" aria-label="This is the label" aria-description="This is the description"></ul>'
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
function customGetter(el: HTMLElement, root: HTMLElement | ShadowRoot) {
|
|
58
|
+
it(`should use custom getter for values ${root instanceof ShadowRoot ? '(ShadowDOM)' : '(LightDOM)'}`, () => {
|
|
59
|
+
const data: Record<string, string> = {
|
|
60
|
+
title: 'Hello World',
|
|
61
|
+
ariaLabel: 'This is the label',
|
|
62
|
+
ariaDescription: 'This is the description'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
root.innerHTML = /*html*/ `
|
|
66
|
+
<span #:bind="title"></span>
|
|
67
|
+
|
|
68
|
+
<ul>
|
|
69
|
+
<li #:bind="ariaLabel"></li>
|
|
70
|
+
<li #:bind="ariaDescription"></li>
|
|
71
|
+
</ul>
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
const render = template({ value: (key) => data[key] }).bind(el);
|
|
75
|
+
|
|
76
|
+
render();
|
|
77
|
+
|
|
78
|
+
assert.equal(
|
|
79
|
+
root.innerHTML
|
|
80
|
+
.split('\n')
|
|
81
|
+
.map((res) => res.trim())
|
|
82
|
+
.join(''),
|
|
83
|
+
'<span #:bind="title">Hello World</span><ul><li #:bind="ariaLabel">This is the label</li><li #:bind="ariaDescription">This is the description</li></ul>'
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
function customPrefix(el: HTMLElement, root: HTMLElement | ShadowRoot) {
|
|
88
|
+
it(`should use custom getter for values ${root instanceof ShadowRoot ? '(ShadowDOM)' : '(LightDOM)'}`, () => {
|
|
89
|
+
el.title = 'Hello World';
|
|
90
|
+
el.ariaLabel = 'This is the label';
|
|
91
|
+
el.ariaDescription = 'This is the description';
|
|
92
|
+
|
|
93
|
+
root.innerHTML = /*html*/ `
|
|
94
|
+
<span x-bind="title"></span>
|
|
95
|
+
|
|
96
|
+
<ul x-aria-label="ariaLabel">
|
|
97
|
+
<li x-bind="ariaLabel"></li>
|
|
98
|
+
<li x-bind="ariaDescription"></li>
|
|
99
|
+
</ul>
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
const render = template({ tokenPrefix: 'x-' }).bind(el);
|
|
103
|
+
|
|
104
|
+
render();
|
|
105
|
+
|
|
106
|
+
assert.equal(
|
|
107
|
+
root.innerHTML
|
|
108
|
+
.split('\n')
|
|
109
|
+
.map((res) => res.trim())
|
|
110
|
+
.join(''),
|
|
111
|
+
'<span x-bind="title">Hello World</span><ul x-aria-label="ariaLabel" aria-label="This is the label"><li x-bind="ariaLabel">This is the label</li><li x-bind="ariaDescription">This is the description</li></ul>'
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
for (let test of TESTS) {
|
|
118
|
+
const lightEl = document.createElement('div');
|
|
119
|
+
test(lightEl, lightEl);
|
|
120
|
+
|
|
121
|
+
const shadowEl = document.createElement('div');
|
|
122
|
+
test(shadowEl, shadowEl.attachShadow({ mode: 'open' }));
|
|
123
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
type Updater = () => void;
|
|
2
|
+
class Updates extends Set<Updater> {}
|
|
3
|
+
type TemplateValueGetter = (key: string) => string;
|
|
4
|
+
|
|
5
|
+
export interface TemplateOpts {
|
|
6
|
+
value?: TemplateValueGetter;
|
|
7
|
+
tokenPrefix?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RenderOpts {
|
|
11
|
+
refresh?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function template({ tokenPrefix = '#:', value }: TemplateOpts = {}) {
|
|
15
|
+
// Track all nodes that can be updated and their associated property
|
|
16
|
+
let updates: Updates | null = null;
|
|
17
|
+
|
|
18
|
+
return function render<T extends HTMLElement>(this: T, opts?: RenderOpts): void {
|
|
19
|
+
if (!updates || opts?.refresh) {
|
|
20
|
+
updates = findUpdates(this, {
|
|
21
|
+
tokenPrefix,
|
|
22
|
+
value: value ?? ((key: string) => getTemplateValue(this, key))
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
for (let update of updates) {
|
|
26
|
+
update();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findUpdates(el: HTMLElement, opts: Required<TemplateOpts>): Updates {
|
|
33
|
+
const iterator = document.createTreeWalker(el.shadowRoot ?? el, NodeFilter.SHOW_ELEMENT);
|
|
34
|
+
const updates = new Updates();
|
|
35
|
+
|
|
36
|
+
while (iterator.nextNode()) {
|
|
37
|
+
const res = trackElement(iterator.currentNode, updates, opts);
|
|
38
|
+
|
|
39
|
+
if (res !== null) {
|
|
40
|
+
iterator.currentNode = res;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return updates;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* configures and tracks a given Node so that it can be updated in place later.
|
|
49
|
+
*/
|
|
50
|
+
function trackElement(node: Node, updates: Updates, opts: Required<TemplateOpts>): Node | null {
|
|
51
|
+
const element = node as Element;
|
|
52
|
+
const getter = opts.value;
|
|
53
|
+
const tokenPrefix = opts.tokenPrefix;
|
|
54
|
+
|
|
55
|
+
for (let attr of element.attributes) {
|
|
56
|
+
const nodeValue = attr.value.trim();
|
|
57
|
+
const realAttributeName = attr.name.replace(tokenPrefix, '');
|
|
58
|
+
|
|
59
|
+
let update: Updater | null = null;
|
|
60
|
+
|
|
61
|
+
if (attr.name.startsWith(`${tokenPrefix}bind`)) {
|
|
62
|
+
update = () => {
|
|
63
|
+
const value = getter(attr.value);
|
|
64
|
+
|
|
65
|
+
if (element.textContent !== value) {
|
|
66
|
+
element.textContent = getter(attr.value);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
} else if (attr.name.startsWith(tokenPrefix)) {
|
|
70
|
+
const isBooleanAttr = nodeValue.startsWith('!');
|
|
71
|
+
const isPositive = nodeValue.startsWith('!!');
|
|
72
|
+
const propertyKey = nodeValue.replaceAll('!', '');
|
|
73
|
+
|
|
74
|
+
if (isBooleanAttr) {
|
|
75
|
+
update = () => {
|
|
76
|
+
let value = isPositive ? !!getter(propertyKey) : !getter(propertyKey);
|
|
77
|
+
|
|
78
|
+
if (value) {
|
|
79
|
+
element.setAttribute(realAttributeName, '');
|
|
80
|
+
} else {
|
|
81
|
+
element.removeAttribute(realAttributeName);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
} else {
|
|
85
|
+
const realAttribute = document.createAttribute(realAttributeName);
|
|
86
|
+
element.setAttributeNode(realAttribute);
|
|
87
|
+
|
|
88
|
+
update = () => {
|
|
89
|
+
const value = getter(nodeValue);
|
|
90
|
+
|
|
91
|
+
if (realAttribute.value !== value) {
|
|
92
|
+
realAttribute.value = value;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (update) {
|
|
99
|
+
updates.add(update);
|
|
100
|
+
|
|
101
|
+
update();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getTemplateValue(obj: object, key: string): any {
|
|
109
|
+
const parsed = key.split('.');
|
|
110
|
+
|
|
111
|
+
let pointer: any = obj;
|
|
112
|
+
|
|
113
|
+
for (let part of parsed) {
|
|
114
|
+
pointer = pointer[part];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return pointer;
|
|
118
|
+
}
|
package/src/lib.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { JoistShadowResult as TemplateResult } from './lib/result.js';
|
|
2
1
|
export { css, html, HTMLResult, CSSResult } from './lib/tags.js';
|
|
3
2
|
export { attr } from './lib/attr.js';
|
|
4
3
|
export { listen } from './lib/listen.js';
|
|
5
|
-
export { element
|
|
4
|
+
export { element } from './lib/element.js';
|
|
6
5
|
export { query } from './lib/query.js';
|
|
6
|
+
export { ready } from './lib/lifecycle.js';
|
|
7
|
+
export { attrChanged } from './lib/attr-changed.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function attrChanged(name: string): <This extends HTMLElement>(cb: Function, ctx: ClassMethodDecoratorContext<This>) => void;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { metadataStore } from './metadata.js';
|
|
2
|
+
export function attrChanged(name) {
|
|
3
|
+
return function attrChangedDecorator(cb, ctx) {
|
|
4
|
+
const meta = metadataStore.read(ctx.metadata);
|
|
5
|
+
const val = meta.attrChanges.get(name) ?? new Set();
|
|
6
|
+
val.add(cb);
|
|
7
|
+
meta.attrChanges.set(name, val);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=attr-changed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attr-changed.js","sourceRoot":"","sources":["../../src/lib/attr-changed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,SAAS,oBAAoB,CAClC,EAAY,EACZ,GAAsC;QAEtC,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;QAEpD,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEZ,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { __esDecorate, __runInitializers } from "tslib";
|
|
2
|
+
import { assert } from 'chai';
|
|
3
|
+
import { attrChanged } from './attr-changed.js';
|
|
4
|
+
import { attr } from './attr.js';
|
|
5
|
+
import { element } from './element.js';
|
|
6
|
+
it('should call specific attrbute callback', () => {
|
|
7
|
+
let args = [];
|
|
8
|
+
let MyElement = (() => {
|
|
9
|
+
let _classDecorators = [element({
|
|
10
|
+
tagName: 'attr-changed-1'
|
|
11
|
+
})];
|
|
12
|
+
let _classDescriptor;
|
|
13
|
+
let _classExtraInitializers = [];
|
|
14
|
+
let _classThis;
|
|
15
|
+
let _classSuper = HTMLElement;
|
|
16
|
+
let _instanceExtraInitializers = [];
|
|
17
|
+
let _test_decorators;
|
|
18
|
+
let _test_initializers = [];
|
|
19
|
+
let _test_extraInitializers = [];
|
|
20
|
+
let _onTestChanged_decorators;
|
|
21
|
+
var MyElement = class extends _classSuper {
|
|
22
|
+
static { _classThis = this; }
|
|
23
|
+
static {
|
|
24
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
25
|
+
_test_decorators = [attr()];
|
|
26
|
+
_onTestChanged_decorators = [attrChanged('test')];
|
|
27
|
+
__esDecorate(this, null, _test_decorators, { kind: "accessor", name: "test", static: false, private: false, access: { has: obj => "test" in obj, get: obj => obj.test, set: (obj, value) => { obj.test = value; } }, metadata: _metadata }, _test_initializers, _test_extraInitializers);
|
|
28
|
+
__esDecorate(this, null, _onTestChanged_decorators, { kind: "method", name: "onTestChanged", static: false, private: false, access: { has: obj => "onTestChanged" in obj, get: obj => obj.onTestChanged }, metadata: _metadata }, null, _instanceExtraInitializers);
|
|
29
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
30
|
+
MyElement = _classThis = _classDescriptor.value;
|
|
31
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
32
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
33
|
+
}
|
|
34
|
+
#test_accessor_storage = (__runInitializers(this, _instanceExtraInitializers), __runInitializers(this, _test_initializers, 'hello'));
|
|
35
|
+
get test() { return this.#test_accessor_storage; }
|
|
36
|
+
set test(value) { this.#test_accessor_storage = value; }
|
|
37
|
+
onTestChanged(oldValue, newValue) {
|
|
38
|
+
args = [oldValue, newValue];
|
|
39
|
+
}
|
|
40
|
+
constructor() {
|
|
41
|
+
super(...arguments);
|
|
42
|
+
__runInitializers(this, _test_extraInitializers);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return MyElement = _classThis;
|
|
46
|
+
})();
|
|
47
|
+
const el = new MyElement();
|
|
48
|
+
document.body.append(el);
|
|
49
|
+
assert.deepEqual(args, [null, 'hello']);
|
|
50
|
+
el.setAttribute('test', 'world');
|
|
51
|
+
assert.deepEqual(args, ['hello', 'world']);
|
|
52
|
+
el.remove();
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=attr-changed.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attr-changed.test.js","sourceRoot":"","sources":["../../src/lib/attr-changed.test.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;IAChD,IAAI,IAAI,GAAa,EAAE,CAAC;QAKlB,SAAS;gCAHd,OAAO,CAAC;gBACP,OAAO,EAAE,gBAAgB;aAC1B,CAAC;;;;0BACsB,WAAW;;;;;;6BAAnB,SAAQ,WAAW;;;;oCAChC,IAAI,EAAE;6CAGN,WAAW,CAAC,MAAM,CAAC;gBAFpB,iKAAS,IAAI,6BAAJ,IAAI,mFAAW;gBAGxB,0LAAA,aAAa,6DAEZ;gBAPH,6KAQC;;;gBARK,uDAAS;;YAEb,0BAFI,mDAAS,8CAEG,OAAO,GAAC;YAAxB,IAAS,IAAI,0CAAW;YAAxB,IAAS,IAAI,gDAAW;YAGxB,aAAa,CAAC,QAAgB,EAAE,QAAgB;gBAC9C,IAAI,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC9B,CAAC;;;;;;;;IAGH,MAAM,EAAE,GAAG,IAAI,SAAS,EAAE,CAAC;IAE3B,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAEzB,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAExC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAE3C,EAAE,CAAC,MAAM,EAAE,CAAC;AACd,CAAC,CAAC,CAAC"}
|
package/target/lib/attr.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export interface AttrOpts {
|
|
2
|
-
|
|
2
|
+
name?: string;
|
|
3
|
+
observed?: boolean;
|
|
4
|
+
reflect?: boolean;
|
|
3
5
|
}
|
|
4
6
|
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,39 +1,37 @@
|
|
|
1
1
|
import { metadataStore } from './metadata.js';
|
|
2
2
|
export function attr(opts) {
|
|
3
3
|
return function attrDecorator({ get, set }, ctx) {
|
|
4
|
-
const attrName = parseAttrName(ctx.name);
|
|
4
|
+
const attrName = opts?.name ?? parseAttrName(ctx.name);
|
|
5
5
|
const meta = metadataStore.read(ctx.metadata);
|
|
6
|
-
|
|
6
|
+
const reflect = opts?.reflect ?? true;
|
|
7
|
+
meta.attrs.set(attrName, {
|
|
7
8
|
propName: ctx.name,
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
observe: opts?.observed ?? true,
|
|
10
|
+
reflect,
|
|
11
|
+
getPropValue: get,
|
|
12
|
+
setPropValue: set
|
|
10
13
|
});
|
|
11
14
|
return {
|
|
12
15
|
set(value) {
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
get() {
|
|
25
|
-
const ogValue = get.call(this);
|
|
26
|
-
const attr = this.getAttribute(attrName);
|
|
27
|
-
if (attr !== null) {
|
|
28
|
-
if (attr === '') {
|
|
29
|
-
return true;
|
|
16
|
+
if (reflect) {
|
|
17
|
+
if (value === true) {
|
|
18
|
+
if (!this.hasAttribute(attrName)) {
|
|
19
|
+
this.setAttribute(attrName, '');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (value === false) {
|
|
23
|
+
if (this.hasAttribute(attrName)) {
|
|
24
|
+
this.removeAttribute(attrName);
|
|
25
|
+
}
|
|
30
26
|
}
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
else {
|
|
28
|
+
const strValue = String(value);
|
|
29
|
+
if (this.getAttribute(attrName) !== strValue) {
|
|
30
|
+
this.setAttribute(attrName, strValue);
|
|
31
|
+
}
|
|
33
32
|
}
|
|
34
|
-
return attr;
|
|
35
33
|
}
|
|
36
|
-
|
|
34
|
+
set.call(this, value);
|
|
37
35
|
}
|
|
38
36
|
};
|
|
39
37
|
};
|
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;AAQ9C,MAAM,UAAU,IAAI,CAAC,IAAe;IAClC,OAAO,SAAS,aAAa,CAC3B,EAAE,GAAG,EAAE,GAAG,EAA+C,EACzD,GAAwC;QAExC,MAAM,QAAQ,GAAG,IAAI,EAAE,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAO,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC;QAEtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE;YACvB,QAAQ,EAAE,GAAG,CAAC,IAAI;YAClB,OAAO,EAAE,IAAI,EAAE,QAAQ,IAAI,IAAI;YAC/B,OAAO;YACP,YAAY,EAAE,GAAG;YACjB,YAAY,EAAE,GAAG;SAClB,CAAC,CAAC;QAEH,OAAO;YACL,GAAG,CAAC,KAAc;gBAChB,IAAI,OAAO,EAAE,CAAC;oBACZ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;wBACnB,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;4BACjC,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;wBAClC,CAAC;oBACH,CAAC;yBAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;wBAC3B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;4BAChC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;wBACjC,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;wBAE/B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,QAAQ,EAAE,CAAC;4BAC7C,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;wBACxC,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACxB,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"}
|