@webtides/element-js 0.3.2 → 0.4.2
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/CHANGELOG.md +35 -0
- package/README.md +2 -23
- package/dist/index.js +24 -4
- package/dist/index.js.map +2 -2
- package/package.json +21 -21
- package/src/BaseElement.js +28 -4
- package/src/renderer/vanilla/TemplateElement.js +52 -0
- package/src/renderer/vanilla/index.js +18 -0
- package/src/renderer/vanilla/util/html.js +78 -0
- package/src/renderer/vanilla/util/render.js +278 -0
- package/src/util/AttributeParser.js +18 -0
package/package.json
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webtides/element-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"module": "index.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"exports": {
|
|
7
|
-
"browser": "./dist/index.js",
|
|
8
|
-
"import": "./index.js",
|
|
9
|
-
"default": "./index.js"
|
|
10
|
-
},
|
|
11
6
|
"files": [
|
|
12
7
|
"/dist",
|
|
13
8
|
"/src",
|
|
14
9
|
"index.js"
|
|
15
10
|
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"browser": "./dist/index.js",
|
|
14
|
+
"import": "./index.js",
|
|
15
|
+
"default": "./index.js"
|
|
16
|
+
},
|
|
17
|
+
"./src/*": "./src/*.js",
|
|
18
|
+
"./src/util/*": "./src/util/*.js",
|
|
19
|
+
"./src/renderer/vanilla": "./src/renderer/vanilla/index.js",
|
|
20
|
+
"./src/renderer/vanilla/TemplateElement": "./src/renderer/vanilla/TemplateElement.js",
|
|
21
|
+
"./src/renderer/vanilla/util/*": "./src/renderer/vanilla/util/*.js"
|
|
22
|
+
},
|
|
16
23
|
"repository": "https://github.com/webtides/element-js.git",
|
|
17
24
|
"author": "@webtides",
|
|
18
25
|
"license": "MIT",
|
|
@@ -31,9 +38,8 @@
|
|
|
31
38
|
"lit-html": "^1.2.1"
|
|
32
39
|
},
|
|
33
40
|
"devDependencies": {
|
|
34
|
-
"@open-wc/
|
|
35
|
-
"@
|
|
36
|
-
"@open-wc/testing-karma": "^3.3.19",
|
|
41
|
+
"@open-wc/testing": "^3.0.3",
|
|
42
|
+
"@web/test-runner": "^0.13.26",
|
|
37
43
|
"babel-eslint": "^10.1.0",
|
|
38
44
|
"babel-plugin-module-resolver": "^4.0.0",
|
|
39
45
|
"deepmerge": "^4.2.2",
|
|
@@ -41,15 +47,9 @@
|
|
|
41
47
|
"eslint": "^7.0.0",
|
|
42
48
|
"eslint-config-prettier": "^6.11.0",
|
|
43
49
|
"eslint-plugin-prettier": "^3.1.3",
|
|
44
|
-
"husky": "^
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"prettier": "^2.0.5"
|
|
48
|
-
},
|
|
49
|
-
"husky": {
|
|
50
|
-
"hooks": {
|
|
51
|
-
"pre-commit": "lint-staged"
|
|
52
|
-
}
|
|
50
|
+
"husky": "^7.0.4",
|
|
51
|
+
"lint-staged": "^12.1.2",
|
|
52
|
+
"prettier": "^2.5.1"
|
|
53
53
|
},
|
|
54
54
|
"lint-staged": {
|
|
55
55
|
"*.{js,json}": [
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"scripts": {
|
|
61
61
|
"lint": "eslint src/**/*.js",
|
|
62
62
|
"prepare": "esbuild index.js --outfile=dist/index.js --bundle --format=esm --sourcemap --target=chrome79,firefox68,safari12,edge79",
|
|
63
|
-
"test": "
|
|
64
|
-
"test:coverage": "
|
|
65
|
-
"test:watch": "
|
|
63
|
+
"test": "web-test-runner test/**/*.test.js --node-resolve",
|
|
64
|
+
"test:coverage": "web-test-runner test/**/*.test.js --node-resolve --coverage",
|
|
65
|
+
"test:watch": "web-test-runner test/**/*.test.js --node-resolve --watch"
|
|
66
66
|
}
|
|
67
67
|
}
|
package/src/BaseElement.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseAttribute, isNaN, dashToCamel, camelToDash, isObjectLike } from './util/AttributeParser.js';
|
|
2
2
|
import { getClosestParentCustomElementNode, isOfSameNodeType } from './util/DOMHelper.js';
|
|
3
|
+
|
|
3
4
|
export { defineElement } from './util/defineElement';
|
|
4
5
|
export { toString } from './util/toString';
|
|
5
6
|
|
|
@@ -369,9 +370,16 @@ class BaseElement extends HTMLElement {
|
|
|
369
370
|
|
|
370
371
|
eventTargets.forEach((eventTarget) => {
|
|
371
372
|
Object.keys(events).forEach((eventName) => {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
373
|
+
const notation = events[eventName];
|
|
374
|
+
if (typeof notation === 'function') {
|
|
375
|
+
const callback = notation.bind(this);
|
|
376
|
+
eventTarget.addEventListener(eventName, callback);
|
|
377
|
+
this._registeredEvents.push({ eventTarget, eventName, callback });
|
|
378
|
+
} else if (typeof notation === 'object' && typeof notation.listener === 'function') {
|
|
379
|
+
const callback = notation.listener.bind(this);
|
|
380
|
+
eventTarget.addEventListener(eventName, callback, notation.options ?? {});
|
|
381
|
+
this._registeredEvents.push({ eventTarget, eventName, callback });
|
|
382
|
+
}
|
|
375
383
|
});
|
|
376
384
|
});
|
|
377
385
|
});
|
|
@@ -400,7 +408,23 @@ class BaseElement extends HTMLElement {
|
|
|
400
408
|
|
|
401
409
|
refsArray.forEach((refNode) => {
|
|
402
410
|
const refKey = refNode.getAttribute('ref');
|
|
403
|
-
|
|
411
|
+
const isListKey = refKey.endsWith('[]');
|
|
412
|
+
const cleanKey = isListKey ? refKey.slice(0, -2) : refKey;
|
|
413
|
+
const hasListRef = Array.isArray(refsMap[cleanKey]);
|
|
414
|
+
|
|
415
|
+
if (isListKey) {
|
|
416
|
+
if (!refsMap[cleanKey] || !hasListRef) {
|
|
417
|
+
// create or overwrite previously registered single ref
|
|
418
|
+
refsMap[cleanKey] = [];
|
|
419
|
+
}
|
|
420
|
+
refsMap[cleanKey].push(refNode);
|
|
421
|
+
} else if (!hasListRef) {
|
|
422
|
+
refsMap[refKey] = refNode;
|
|
423
|
+
} else {
|
|
424
|
+
console.warn(
|
|
425
|
+
`Did not register singular ref ${refKey}, ${refNode} as a ref list is already registered under the same name.`,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
404
428
|
});
|
|
405
429
|
|
|
406
430
|
this.$refs = refsMap;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { StyledElement } from '../../StyledElement';
|
|
2
|
+
import { html, spreadAttributes, unsafeHTML } from './util/html';
|
|
3
|
+
import { render } from './util/render';
|
|
4
|
+
export { i18n } from '../../util/i18n';
|
|
5
|
+
|
|
6
|
+
class TemplateElement extends StyledElement {
|
|
7
|
+
static _$templateElement$ = true;
|
|
8
|
+
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super({
|
|
11
|
+
deferUpdate: false,
|
|
12
|
+
shadowRender: false,
|
|
13
|
+
styles: [],
|
|
14
|
+
adoptGlobalStyles: true,
|
|
15
|
+
mutationObserverOptions: {
|
|
16
|
+
childList: false,
|
|
17
|
+
},
|
|
18
|
+
...options,
|
|
19
|
+
});
|
|
20
|
+
this._template = this._options.template;
|
|
21
|
+
|
|
22
|
+
if (this._options.shadowRender && !this.shadowRoot) {
|
|
23
|
+
this.attachShadow({ mode: 'open' });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
template() {
|
|
28
|
+
return html``;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
update(options) {
|
|
32
|
+
this.renderTemplate();
|
|
33
|
+
super.update(options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
renderTemplate() {
|
|
37
|
+
const template = this._template || this.template();
|
|
38
|
+
|
|
39
|
+
if (typeof template === 'string') {
|
|
40
|
+
// just a plain string literal. no fancy rendering required
|
|
41
|
+
this.getRoot().innerHTML = `${template}`;
|
|
42
|
+
} else {
|
|
43
|
+
render(template, this.getRoot());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getRoot() {
|
|
48
|
+
return this.shadowRoot !== null ? this.shadowRoot : this;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { TemplateElement, html, unsafeHTML, render, spreadAttributes };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseElement } from '../../BaseElement';
|
|
2
|
+
import { StyledElement } from '../../StyledElement';
|
|
3
|
+
import { TemplateElement, html, unsafeHTML, spreadAttributes } from './TemplateElement';
|
|
4
|
+
import { toString } from '../../util/toString';
|
|
5
|
+
import { defineElement } from '../../util/defineElement';
|
|
6
|
+
import { i18n } from '../../util/i18n';
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
BaseElement,
|
|
10
|
+
StyledElement,
|
|
11
|
+
TemplateElement,
|
|
12
|
+
html,
|
|
13
|
+
unsafeHTML,
|
|
14
|
+
spreadAttributes,
|
|
15
|
+
toString,
|
|
16
|
+
defineElement,
|
|
17
|
+
i18n,
|
|
18
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { camelToDash, decodeAttribute, encodeAttribute } from '../../../util/AttributeParser';
|
|
2
|
+
|
|
3
|
+
class Part {
|
|
4
|
+
constructor(value) {
|
|
5
|
+
this.value = value;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
toString() {
|
|
9
|
+
if (typeof this.value !== 'string') {
|
|
10
|
+
return encodeAttribute(JSON.stringify(this.value));
|
|
11
|
+
}
|
|
12
|
+
return encodeAttribute(this.value);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class TemplatePart extends Part {
|
|
17
|
+
$$templatePart = true;
|
|
18
|
+
|
|
19
|
+
constructor(value) {
|
|
20
|
+
super(value?.$$templatePart ? value.value : value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
toString() {
|
|
24
|
+
if (this.value === null || this.value === undefined) {
|
|
25
|
+
return '';
|
|
26
|
+
} else if (this.value === '') {
|
|
27
|
+
return '';
|
|
28
|
+
} else if (Array.isArray(this.value)) {
|
|
29
|
+
return this.value.map((row) => new TemplatePart(row).toString()).join('');
|
|
30
|
+
} else if (this.value?.$$templateLiteral) {
|
|
31
|
+
return this.value.toString();
|
|
32
|
+
} else if (typeof this.value === 'function') {
|
|
33
|
+
// A directive
|
|
34
|
+
return this.value(this);
|
|
35
|
+
}
|
|
36
|
+
return super.toString();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class TemplateLiteral {
|
|
41
|
+
$$templateLiteral = true;
|
|
42
|
+
|
|
43
|
+
constructor(strings, ...values) {
|
|
44
|
+
let result = [];
|
|
45
|
+
for (let i = 0; i < strings.length; i++) {
|
|
46
|
+
result.push(strings[i]);
|
|
47
|
+
|
|
48
|
+
if (i < values.length) {
|
|
49
|
+
result.push(new TemplatePart(values[i]));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.result = result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
toString() {
|
|
56
|
+
return `${this.result.join('')}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const html = function (strings, ...values) {
|
|
61
|
+
return new TemplateLiteral(strings, ...values);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const unsafeHTML = (string) => {
|
|
65
|
+
return () => `<!--$$plainly-set-inner-html-->${decodeAttribute(string)}`;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const spreadAttributes = (attributes) => {
|
|
69
|
+
return () => {
|
|
70
|
+
return Object.keys(attributes)
|
|
71
|
+
.map((key) => {
|
|
72
|
+
return `${camelToDash(key)}='${new Part(attributes[key])}'`;
|
|
73
|
+
})
|
|
74
|
+
.join(' ');
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export { html, unsafeHTML, spreadAttributes };
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const NODE_TYPE_COMMENT = 8;
|
|
2
|
+
const NODE_TYPE_TEXT = 3;
|
|
3
|
+
|
|
4
|
+
const _cachedTemplateElements = {};
|
|
5
|
+
|
|
6
|
+
const convertStringToHTML = (templateString) => {
|
|
7
|
+
const parser = new DOMParser();
|
|
8
|
+
const document = parser.parseFromString(`<main>${templateString}</main>`, 'text/html');
|
|
9
|
+
|
|
10
|
+
return document.querySelector('main');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create an array of the attributes on an element
|
|
15
|
+
* @param {NamedNodeMap} attributes The attributes on an element
|
|
16
|
+
* @return {Array} The attributes on an element as an array of key/value pairs
|
|
17
|
+
*/
|
|
18
|
+
const getAttributes = function (attributes) {
|
|
19
|
+
return Array.prototype.map.call(attributes, function (attribute) {
|
|
20
|
+
return {
|
|
21
|
+
attributeName: attribute.name,
|
|
22
|
+
value: attribute.value,
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a DOM Tree Map for an element
|
|
29
|
+
*
|
|
30
|
+
* @param {HTMLElement} element The element to map
|
|
31
|
+
* @param {Boolean} isSVG If true, node is within an SVG
|
|
32
|
+
* @return {{ domMap: Array, plainlySetInnerHTML?: boolean, innerHTML?: string }} A DOM tree map
|
|
33
|
+
*/
|
|
34
|
+
const createDOMMap = function (element, isChild = false, isSVG = false, type = 'template') {
|
|
35
|
+
const children = [...element.childNodes];
|
|
36
|
+
|
|
37
|
+
// If the list contains a comment which says "$$plainly-set-inner-html" it means
|
|
38
|
+
// that the all children of the element will be set as unsafe `innerHTML´
|
|
39
|
+
// which means that there won't be any diffing or fancy things
|
|
40
|
+
// but we will just mark it as a pure component and leave it be.
|
|
41
|
+
for (const child of children) {
|
|
42
|
+
if (child?.nodeType === NODE_TYPE_COMMENT && child.textContent === '$$plainly-set-inner-html') {
|
|
43
|
+
return {
|
|
44
|
+
domMap: [],
|
|
45
|
+
plainlySetInnerHTML: true,
|
|
46
|
+
innerHTML: element.innerHTML,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const domMap = children.map((node, index) => {
|
|
52
|
+
const details = {
|
|
53
|
+
content: node.childNodes && node.childNodes.length > 0 ? null : node.textContent,
|
|
54
|
+
attributes: node.nodeType !== 1 ? [] : getAttributes(node.attributes),
|
|
55
|
+
type:
|
|
56
|
+
node.nodeType === NODE_TYPE_TEXT
|
|
57
|
+
? 'text'
|
|
58
|
+
: node.nodeType === NODE_TYPE_COMMENT
|
|
59
|
+
? 'comment'
|
|
60
|
+
: node.tagName.toLowerCase(),
|
|
61
|
+
node: node,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
details.isSVG = isSVG || details.type === 'svg';
|
|
65
|
+
details.children =
|
|
66
|
+
isChild && isTemplateElement({ node }) ? { domMap: [] } : createDOMMap(node, true, details.isSVG);
|
|
67
|
+
return details;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
domMap,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const removeAttributes = function (element, attributes) {
|
|
76
|
+
attributes.forEach(function (attribute) {
|
|
77
|
+
element.removeAttribute(attribute.attributeName);
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Add attributes to an element
|
|
83
|
+
* @param {HTMLElement} element The element
|
|
84
|
+
* @param {[{ attributeName: string, value: string}]} attributes The attributes to add
|
|
85
|
+
*/
|
|
86
|
+
const addAttributes = function (element, attributes) {
|
|
87
|
+
attributes.forEach(function (attribute) {
|
|
88
|
+
if (attribute.attributeName.startsWith('.')) {
|
|
89
|
+
// Directly set the attribute on the element instance as property
|
|
90
|
+
// instead of setting the attribute.
|
|
91
|
+
element[attribute.attributeName.substring(1)] = attribute.value || '';
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
element.setAttribute(attribute.attributeName, attribute.value || '');
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Diff the attributes on an existing element versus the template
|
|
101
|
+
* @param {Object} template The new template
|
|
102
|
+
* @param {Object} existing The existing DOM node
|
|
103
|
+
*/
|
|
104
|
+
const diffAttributes = function (template, existing) {
|
|
105
|
+
// Get attributes to remove
|
|
106
|
+
const remove = existing.attributes.filter(function (attribute) {
|
|
107
|
+
const getAtt = template.attributes.find(function (newAtt) {
|
|
108
|
+
return attribute.attributeName === newAtt.attributeName;
|
|
109
|
+
});
|
|
110
|
+
return getAtt === undefined;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Get attributes to change
|
|
114
|
+
const change = template.attributes.filter(function (attribute) {
|
|
115
|
+
const getAtt = existing.attributes.find((existingAtt) => {
|
|
116
|
+
return attribute.attributeName === existingAtt.attributeName;
|
|
117
|
+
});
|
|
118
|
+
return getAtt === undefined || getAtt.value !== attribute.value;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Add/remove any required attributes
|
|
122
|
+
addAttributes(existing.node, change);
|
|
123
|
+
removeAttributes(existing.node, remove);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Make an HTML element
|
|
128
|
+
* @param {Object} element The element details
|
|
129
|
+
* @param {Object} templateResult
|
|
130
|
+
* @return {HTMLElement|false} The HTML element
|
|
131
|
+
*/
|
|
132
|
+
const makeElement = function (element, templateResult) {
|
|
133
|
+
// Create the element
|
|
134
|
+
let node;
|
|
135
|
+
if (element.type === 'text') {
|
|
136
|
+
node = document.createTextNode(element.content);
|
|
137
|
+
} else if (element.type === 'comment') {
|
|
138
|
+
node = document.createComment(element.content);
|
|
139
|
+
} else if (element.isSVG) {
|
|
140
|
+
node = document.createElementNS('http://www.w3.org/2000/svg', element.type);
|
|
141
|
+
} else {
|
|
142
|
+
node = document.createElement(element.type);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Add attributes
|
|
146
|
+
addAttributes(node, element.attributes);
|
|
147
|
+
|
|
148
|
+
// If the element has child nodes, create them
|
|
149
|
+
// Otherwise, add textContent
|
|
150
|
+
|
|
151
|
+
if (element.children.domMap.length > 0) {
|
|
152
|
+
diff(element.children, { domMap: [] }, node);
|
|
153
|
+
} else if (templateResult.plainlySetInnerHTML) {
|
|
154
|
+
node.innerHTML = templateResult.innerHTML;
|
|
155
|
+
} else if (element.type !== 'text') {
|
|
156
|
+
node.textContent = element.content;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return node;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const isTemplateElement = (element) => {
|
|
163
|
+
const tagName = element.node?.tagName?.toLowerCase() ?? false;
|
|
164
|
+
if (!tagName || !tagName.includes('-')) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (_cachedTemplateElements[tagName]) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const elementClass = customElements.get(tagName);
|
|
173
|
+
const isTemplateElement = elementClass && elementClass._$templateElement$ === true;
|
|
174
|
+
|
|
175
|
+
if (isTemplateElement) {
|
|
176
|
+
_cachedTemplateElements[tagName] = elementClass;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return isTemplateElement;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Diff the existing DOM node versus the template
|
|
184
|
+
* @param {{ domMap: Array, plainlySetInnerHTML?: boolean, innerHTML?: string }} templateResult A DOM tree map of the template content
|
|
185
|
+
* @param {{ domMap: array }} domResult A DOM tree map of the existing DOM node
|
|
186
|
+
* @param {HTMLElement} element The element to render content into
|
|
187
|
+
*/
|
|
188
|
+
const diff = function (templateResult, domResult, element) {
|
|
189
|
+
const templateMap = templateResult.domMap;
|
|
190
|
+
|
|
191
|
+
if (templateResult.plainlySetInnerHTML) {
|
|
192
|
+
element.innerHTML = templateResult.innerHTML;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const domMap = domResult.domMap;
|
|
197
|
+
|
|
198
|
+
// If extra elements in domMap, remove them
|
|
199
|
+
let count = domMap.length - templateMap.length;
|
|
200
|
+
if (count > 0) {
|
|
201
|
+
for (; count > 0; count--) {
|
|
202
|
+
domMap[domMap.length - count].node.parentNode.removeChild(domMap[domMap.length - count].node);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Diff each item in the templateMap
|
|
207
|
+
templateMap.forEach(function (node, index) {
|
|
208
|
+
// If element doesn't exist, create it
|
|
209
|
+
if (!domMap[index]) {
|
|
210
|
+
const newElement = makeElement(node, node.children);
|
|
211
|
+
element.appendChild(newElement);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// If element is not the same type, replace it with new element
|
|
216
|
+
if (templateMap[index].type !== domMap[index].type) {
|
|
217
|
+
domMap[index].node.parentNode.replaceChild(
|
|
218
|
+
makeElement(templateMap[index], node.children),
|
|
219
|
+
domMap[index].node,
|
|
220
|
+
);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// If attributes are different, update them
|
|
225
|
+
diffAttributes(templateMap[index], domMap[index]);
|
|
226
|
+
|
|
227
|
+
if (node.children.plainlySetInnerHTML && domMap[index]) {
|
|
228
|
+
domMap[index].innerHTML = node.children.innerHTML;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// If content is different, update it
|
|
233
|
+
if (templateMap[index].content !== domMap[index].content) {
|
|
234
|
+
if (!isTemplateElement(domMap[index])) {
|
|
235
|
+
// ignore custom elements.
|
|
236
|
+
domMap[index].node.textContent = templateMap[index].content;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// If target element should be empty, wipe it
|
|
241
|
+
if (domMap[index].children.domMap.length > 0 && node.children.domMap.length < 1) {
|
|
242
|
+
if (!isTemplateElement(domMap[index])) {
|
|
243
|
+
// ignore custom elements.
|
|
244
|
+
domMap[index].node.innerHTML = '';
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// If element is empty and shouldn't be, build it up
|
|
250
|
+
// This uses a document fragment to minimize reflows
|
|
251
|
+
if (domMap[index].children.domMap.length === 0 && node.children.domMap.length > 0) {
|
|
252
|
+
if (isTemplateElement(domMap[index])) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const fragment = document.createDocumentFragment();
|
|
257
|
+
diff(node.children, domMap[index].children, domMap[index].node);
|
|
258
|
+
element.appendChild(fragment);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// If there are existing child elements that need to be modified, diff them
|
|
263
|
+
if (node.children.domMap.length > 0) {
|
|
264
|
+
if (!isTemplateElement(node)) {
|
|
265
|
+
diff(node.children, domMap[index].children, domMap[index].node);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const render = (template, target) => {
|
|
272
|
+
const templateDOMMap = createDOMMap(convertStringToHTML(template), false, false, 'template');
|
|
273
|
+
const domMap = createDOMMap(target, false, false, 'dom');
|
|
274
|
+
|
|
275
|
+
diff(templateDOMMap, domMap, target);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export { render };
|
|
@@ -64,3 +64,21 @@ export function dashToCamel(string) {
|
|
|
64
64
|
export function camelToDash(string) {
|
|
65
65
|
return string.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
export function decodeAttribute(attribute) {
|
|
69
|
+
return `${attribute}`
|
|
70
|
+
.replace(/</g, '<')
|
|
71
|
+
.replace(/>/g, '>')
|
|
72
|
+
.replace(/"/g, '"')
|
|
73
|
+
.replace(/'/g, "'");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function encodeAttribute(attribute) {
|
|
77
|
+
return `${attribute}`
|
|
78
|
+
.replace(/'/g, ''')
|
|
79
|
+
.replace(/"/g, '"')
|
|
80
|
+
.replace(/</g, '<')
|
|
81
|
+
.replace(/>/g, '>')
|
|
82
|
+
.replace(/\r\n/g, '\n')
|
|
83
|
+
.replace(/[\r\n]/g, '\n');
|
|
84
|
+
}
|