@webtides/element-js 0.7.2 → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +668 -969
- package/dist/index.js.map +4 -4
- package/package.json +16 -20
- package/src/BaseElement.js +2 -43
- package/src/StyledElement.js +1 -9
- package/src/TemplateElement.js +3 -7
- package/src/dom-parts/AttributePart.js +160 -0
- package/src/dom-parts/ChildNodePart.js +343 -0
- package/src/dom-parts/NodePart.js +37 -0
- package/src/dom-parts/Part.js +12 -0
- package/src/dom-parts/TemplatePart.js +140 -0
- package/src/dom-parts/TemplateResult.js +331 -0
- package/src/{renderer/vanilla/util → dom-parts}/directives.js +17 -11
- package/src/dom-parts/html.js +12 -0
- package/src/dom-parts/render.js +10 -0
- package/src/util/AttributeParser.js +8 -8
- package/src/util/DOMHelper.js +213 -0
- package/src/util/Directive.js +22 -0
- package/src/util/Store.js +5 -6
- package/CHANGELOG.md +0 -137
- package/src/renderer/vanilla/TemplateElement.js +0 -50
- package/src/renderer/vanilla/index.js +0 -18
- package/src/renderer/vanilla/util/html.js +0 -65
- package/src/renderer/vanilla/util/render.js +0 -149
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webtides/element-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
4
|
"module": "index.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"files": [
|
|
7
8
|
"/dist",
|
|
8
9
|
"/src",
|
|
@@ -16,12 +17,7 @@
|
|
|
16
17
|
},
|
|
17
18
|
"./src/*": "./src/*.js",
|
|
18
19
|
"./src/util/*": "./src/util/*.js",
|
|
19
|
-
"./src/
|
|
20
|
-
"./src/renderer/vanilla/TemplateElement": "./src/renderer/vanilla/TemplateElement.js",
|
|
21
|
-
"./src/renderer/vanilla/util/*": "./src/renderer/vanilla/util/*.js",
|
|
22
|
-
"./src/renderer/vanilla/util/directives.js": "./src/renderer/vanilla/util/directives.js",
|
|
23
|
-
"./src/renderer/vanilla/util/html.js": "./src/renderer/vanilla/util/html.js",
|
|
24
|
-
"./src/renderer/vanilla/util/render.js": "./src/renderer/vanilla/util/render.js"
|
|
20
|
+
"./src/dom-parts/*": "./src/dom-parts/*.js"
|
|
25
21
|
},
|
|
26
22
|
"repository": "https://github.com/webtides/element-js.git",
|
|
27
23
|
"author": "@webtides",
|
|
@@ -37,23 +33,23 @@
|
|
|
37
33
|
"element-js",
|
|
38
34
|
"shadow-dom"
|
|
39
35
|
],
|
|
40
|
-
"dependencies": {
|
|
41
|
-
"lit-html": "^1.2.1"
|
|
42
|
-
},
|
|
36
|
+
"dependencies": {},
|
|
43
37
|
"devDependencies": {
|
|
44
|
-
"@
|
|
38
|
+
"@babel/eslint-parser": "^7.18.9",
|
|
39
|
+
"@open-wc/testing": "^3.1.6",
|
|
45
40
|
"@web/dev-server": "^0.1.30",
|
|
46
|
-
"@web/test-runner": "^0.
|
|
41
|
+
"@web/test-runner": "^0.14.0",
|
|
47
42
|
"babel-eslint": "^10.1.0",
|
|
48
43
|
"babel-plugin-module-resolver": "^5.0.0",
|
|
49
44
|
"deepmerge": "^4.2.2",
|
|
50
|
-
"esbuild": "^0.
|
|
51
|
-
"eslint": "
|
|
52
|
-
"eslint-config-prettier": "^
|
|
53
|
-
"eslint-plugin-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
45
|
+
"esbuild": "^0.15.5",
|
|
46
|
+
"eslint": "8.22.0",
|
|
47
|
+
"eslint-config-prettier": "^7.2.0",
|
|
48
|
+
"eslint-plugin-import": "^2.26.0",
|
|
49
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
50
|
+
"husky": "^8.0.1",
|
|
51
|
+
"lint-staged": "^13.0.3",
|
|
52
|
+
"prettier": "^2.7.1"
|
|
57
53
|
},
|
|
58
54
|
"lint-staged": {
|
|
59
55
|
"*.{js,json}": [
|
|
@@ -62,7 +58,7 @@
|
|
|
62
58
|
]
|
|
63
59
|
},
|
|
64
60
|
"scripts": {
|
|
65
|
-
"lint": "eslint src/**/*.js",
|
|
61
|
+
"lint": "eslint index.js 'src/**/*.js'",
|
|
66
62
|
"prepare": "esbuild index.js --outfile=dist/index.js --bundle --format=esm --sourcemap --target=chrome79,firefox68,safari12,edge79",
|
|
67
63
|
"test": "web-test-runner test/**/*.test.{html,js} --node-resolve",
|
|
68
64
|
"test:coverage": "web-test-runner test/**/*.test.{html,js} --node-resolve --coverage",
|
package/src/BaseElement.js
CHANGED
|
@@ -26,13 +26,6 @@ class BaseElement extends HTMLElement {
|
|
|
26
26
|
propertyOptions: {},
|
|
27
27
|
...options,
|
|
28
28
|
};
|
|
29
|
-
|
|
30
|
-
if (options.childListUpdate !== undefined && options.childListUpdate !== null) {
|
|
31
|
-
this._options.mutationObserverOptions.childList = options.childListUpdate;
|
|
32
|
-
console.warn(
|
|
33
|
-
`[${this.localName}] Using the "childListUpdate" option is deprecated and will be removed before 1.0! Please use the "mutationObserverOptions" dictionary instead. See the docs for more info`,
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
29
|
}
|
|
37
30
|
|
|
38
31
|
connectedCallback() {
|
|
@@ -42,9 +35,6 @@ class BaseElement extends HTMLElement {
|
|
|
42
35
|
// define all properties to "this"
|
|
43
36
|
this.defineProperties();
|
|
44
37
|
|
|
45
|
-
// define all computed properties to "this"
|
|
46
|
-
this.defineComputedProperties();
|
|
47
|
-
|
|
48
38
|
// define context
|
|
49
39
|
this.definePropertyInjection();
|
|
50
40
|
|
|
@@ -52,13 +42,14 @@ class BaseElement extends HTMLElement {
|
|
|
52
42
|
this.defineObserver();
|
|
53
43
|
|
|
54
44
|
if (this.hasAttribute('defer-update') || this._options.deferUpdate) {
|
|
55
|
-
// don't
|
|
45
|
+
// don't update/render, but register refs and events
|
|
56
46
|
this.registerEventsAndRefs();
|
|
57
47
|
|
|
58
48
|
this.triggerHook('connected');
|
|
59
49
|
} else {
|
|
60
50
|
this.requestUpdate({ notify: false }).then(() => {
|
|
61
51
|
this.triggerHook('connected');
|
|
52
|
+
this.triggerHook('afterUpdate');
|
|
62
53
|
});
|
|
63
54
|
}
|
|
64
55
|
}
|
|
@@ -388,11 +379,6 @@ class BaseElement extends HTMLElement {
|
|
|
388
379
|
});
|
|
389
380
|
}
|
|
390
381
|
|
|
391
|
-
// Deprecated
|
|
392
|
-
hooks() {
|
|
393
|
-
return {};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
382
|
// Connected lifecycle hook
|
|
397
383
|
connected() {}
|
|
398
384
|
|
|
@@ -407,14 +393,6 @@ class BaseElement extends HTMLElement {
|
|
|
407
393
|
|
|
408
394
|
// Triggers a lifecycle hook based on the name
|
|
409
395
|
triggerHook(name) {
|
|
410
|
-
if (this.hooks && name in this.hooks()) {
|
|
411
|
-
console.warn(
|
|
412
|
-
`[${this.localName}] Using the hooks() map for lifecycle hooks is deprecated! Please overwrite the existing lifecycle hook functions. See the docs for more info`,
|
|
413
|
-
);
|
|
414
|
-
this.hooks()[name]();
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
396
|
if (name in this) {
|
|
419
397
|
this[name]();
|
|
420
398
|
}
|
|
@@ -431,25 +409,6 @@ class BaseElement extends HTMLElement {
|
|
|
431
409
|
return {};
|
|
432
410
|
}
|
|
433
411
|
|
|
434
|
-
// Deprecated
|
|
435
|
-
computed() {
|
|
436
|
-
return {};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Deprecated
|
|
440
|
-
defineComputedProperties() {
|
|
441
|
-
Object.keys(this.computed()).forEach((prop) => {
|
|
442
|
-
if (!this.hasOwnProperty(prop)) {
|
|
443
|
-
console.warn(
|
|
444
|
-
`[${this.localName}] Using the computed() map for computed properties is deprecated! Please use regular JS getters and return the computed value. See the docs for more info`,
|
|
445
|
-
);
|
|
446
|
-
Object.defineProperty(this, prop, {
|
|
447
|
-
get: () => this.computed()[prop](),
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
412
|
/**
|
|
454
413
|
* Return a map of events that should be registered on the element.
|
|
455
414
|
* eg. { '.link': {
|
package/src/StyledElement.js
CHANGED
|
@@ -33,14 +33,6 @@ class StyledElement extends BaseElement {
|
|
|
33
33
|
if (supportsAdoptingStyleSheets() && this._options.shadowRender) {
|
|
34
34
|
// adopting does only make sense in shadow dom. Fall back to append for light elements
|
|
35
35
|
this.adoptStyleSheets();
|
|
36
|
-
} else if (this._options.shadowRender && window.ShadyCSS !== undefined) {
|
|
37
|
-
// if shadowRoot is polyfilled we use ShadyCSS to copy scoped styles to <head>
|
|
38
|
-
window.ShadyCSS.ScopingShim.prepareAdoptedCssText(this._styles, this.localName);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// if shadowRoot is polyfilled - scope element template
|
|
42
|
-
if (window.ShadyCSS !== undefined) {
|
|
43
|
-
window.ShadyCSS.styleElement(this);
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
38
|
|
|
@@ -52,7 +44,7 @@ class StyledElement extends BaseElement {
|
|
|
52
44
|
if (!supportsAdoptingStyleSheets() || this._options.shadowRender === false) {
|
|
53
45
|
// append stylesheets to template if not already adopted
|
|
54
46
|
const appendableStyles = [...this._styles];
|
|
55
|
-
if (this._options.shadowRender && this._options.adoptGlobalStyles
|
|
47
|
+
if (this._options.shadowRender && this._options.adoptGlobalStyles) {
|
|
56
48
|
appendableStyles.unshift(StyledElement.globalStyles?.textContent ?? '');
|
|
57
49
|
}
|
|
58
50
|
this.appendStyleSheets(appendableStyles);
|
package/src/TemplateElement.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StyledElement } from './StyledElement.js';
|
|
2
|
-
import { html } from '
|
|
3
|
-
import { render } from '
|
|
2
|
+
import { html } from './dom-parts/html.js';
|
|
3
|
+
import { render } from './dom-parts/render.js';
|
|
4
4
|
export { i18n } from './util/i18n.js';
|
|
5
5
|
|
|
6
6
|
class TemplateElement extends StyledElement {
|
|
@@ -35,11 +35,7 @@ class TemplateElement extends StyledElement {
|
|
|
35
35
|
// just a plain string literal. no lit-html required
|
|
36
36
|
this.getRoot().innerHTML = `${template}`;
|
|
37
37
|
} else {
|
|
38
|
-
|
|
39
|
-
render(html` ${template} `, this.getRoot(), {
|
|
40
|
-
scopeName: this.localName,
|
|
41
|
-
eventContext: this,
|
|
42
|
-
});
|
|
38
|
+
render(template, this.getRoot());
|
|
43
39
|
}
|
|
44
40
|
}
|
|
45
41
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Part } from './Part.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {Element} node
|
|
5
|
+
* @param {String} name
|
|
6
|
+
* @param {Boolean} oldValue
|
|
7
|
+
* @return {(function(*): void)|*}
|
|
8
|
+
*/
|
|
9
|
+
const processBooleanAttribute = (node, name, oldValue) => {
|
|
10
|
+
// TODO: It would be better if the ?boolean= attribute would not be there in the first place...
|
|
11
|
+
node.removeAttribute(`?${name}`);
|
|
12
|
+
return (newValue) => {
|
|
13
|
+
const value = !!newValue?.valueOf() && newValue !== 'false';
|
|
14
|
+
if (oldValue !== value) {
|
|
15
|
+
node.toggleAttribute(name, (oldValue = !!value));
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {Element} node
|
|
22
|
+
* @param {String} name
|
|
23
|
+
* @return {(function(*): void)|*}
|
|
24
|
+
*/
|
|
25
|
+
const processPropertyAttribute = (node, name) => {
|
|
26
|
+
// TODO: It would be better if the .property= attribute would not be there in the first place...
|
|
27
|
+
node.removeAttribute(`.${name}`);
|
|
28
|
+
return (value) => {
|
|
29
|
+
node[name] = value;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Element} node
|
|
35
|
+
* @param {String} name
|
|
36
|
+
* @return {(function(*): void)|*}
|
|
37
|
+
*/
|
|
38
|
+
const processEventAttribute = (node, name) => {
|
|
39
|
+
// TODO: It would be better if the event attribute would not be there in the first place...
|
|
40
|
+
node.removeAttribute(name);
|
|
41
|
+
|
|
42
|
+
let oldValue = undefined;
|
|
43
|
+
let type = name.startsWith('@') ? name.slice(1) : name.toLowerCase().slice(2);
|
|
44
|
+
|
|
45
|
+
return (newValue) => {
|
|
46
|
+
if (oldValue !== newValue) {
|
|
47
|
+
if (oldValue) node.removeEventListener(type, oldValue);
|
|
48
|
+
if ((oldValue = newValue)) node.addEventListener(type, oldValue);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {Element} node
|
|
55
|
+
* @param {String} name
|
|
56
|
+
* @return {(function(*): void)|*}
|
|
57
|
+
*/
|
|
58
|
+
const processAttribute = (node, name) => {
|
|
59
|
+
let oldValue,
|
|
60
|
+
orphan = true;
|
|
61
|
+
const attributeNode = globalThis.document?.createAttributeNS(null, name);
|
|
62
|
+
return (newValue) => {
|
|
63
|
+
const value = newValue?.valueOf();
|
|
64
|
+
if (oldValue !== value) {
|
|
65
|
+
if ((oldValue = value) == null) {
|
|
66
|
+
if (!orphan) {
|
|
67
|
+
node.removeAttributeNode(attributeNode);
|
|
68
|
+
orphan = true;
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
attributeNode.value = value;
|
|
72
|
+
if (orphan) {
|
|
73
|
+
node.setAttributeNodeNS(attributeNode);
|
|
74
|
+
orphan = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {Element} node
|
|
83
|
+
* @param {String} name
|
|
84
|
+
* @return {(function(*): void)|*}
|
|
85
|
+
*/
|
|
86
|
+
export const processAttributePart = (node, name) => {
|
|
87
|
+
// boolean attribute: ?boolean=${...}
|
|
88
|
+
if (name.startsWith('?')) {
|
|
89
|
+
return processBooleanAttribute(node, name.slice(1), false);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// property attribute: .property=${...}
|
|
93
|
+
if (name.startsWith('.')) {
|
|
94
|
+
return processPropertyAttribute(node, name.slice(1));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// event attribute: @event=${...} || "old school" event attribute: onevent=${...}
|
|
98
|
+
if (name.startsWith('@') || name.startsWith('on')) {
|
|
99
|
+
return processEventAttribute(node, name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// normal "string" attribute: attribute=${...}
|
|
103
|
+
return processAttribute(node, name);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* For updating a single attribute
|
|
108
|
+
*/
|
|
109
|
+
export class AttributePart extends Part {
|
|
110
|
+
name = undefined;
|
|
111
|
+
interpolations = 1;
|
|
112
|
+
values = [];
|
|
113
|
+
currentValueIndex = 0;
|
|
114
|
+
initialValue = undefined;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param {Node} node
|
|
118
|
+
* @param {String} name
|
|
119
|
+
* @param {String} initialValue
|
|
120
|
+
*/
|
|
121
|
+
constructor(node, name, initialValue) {
|
|
122
|
+
super();
|
|
123
|
+
this.name = name;
|
|
124
|
+
this.initialValue = initialValue;
|
|
125
|
+
this.processor = processAttributePart(node.nextElementSibling, this.name);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {Node} node
|
|
130
|
+
*/
|
|
131
|
+
addNode(node) {
|
|
132
|
+
this.interpolations++;
|
|
133
|
+
// TODO: what about the comment node? It will always be the first for now...
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param {string | number | bigint | boolean | undefined | symbol | null} value
|
|
138
|
+
*/
|
|
139
|
+
update(value) {
|
|
140
|
+
// If we only have one sole interpolation, we can just apply the update
|
|
141
|
+
if (this.initialValue === '\x03') {
|
|
142
|
+
this.processor(value);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Instead of applying the update immediately, we check if the part has multiple interpolations
|
|
147
|
+
// and store the values for each interpolation in a list
|
|
148
|
+
this.values[this.currentValueIndex++] = value;
|
|
149
|
+
|
|
150
|
+
// Only the last call to update (for the last interpolation) will actually trigger the update
|
|
151
|
+
// on the DOM processor. Here we can reset everything before the next round of updates
|
|
152
|
+
if (this.interpolations === this.currentValueIndex) {
|
|
153
|
+
this.currentValueIndex = 0;
|
|
154
|
+
let replaceIndex = 0;
|
|
155
|
+
// Note: this will coarse the values into strings, but it's probably ok since there can only be multiple values in string attributes?!
|
|
156
|
+
const parsedValue = this.initialValue.replace(/\x03/g, () => this.values[replaceIndex++]);
|
|
157
|
+
this.processor(parsedValue);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|