@webtides/element-js 1.1.6 → 1.1.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webtides/element-js",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "module": "index.js",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -3,6 +3,16 @@ import { Part } from './Part.js';
3
3
  import { TemplateResult } from './TemplateResult.js';
4
4
  import { TemplatePart } from './TemplatePart.js';
5
5
 
6
+ /**
7
+ * @param {Element} commentNode
8
+ * @param {Element[]} nodes
9
+ */
10
+ const insertNodesBetweenComments = (commentNode, nodes) => {
11
+ for (const node of nodes) {
12
+ commentNode.parentNode.insertBefore(node, commentNode);
13
+ }
14
+ };
15
+
6
16
  /**
7
17
  * @param {Element} commentNode
8
18
  */
@@ -58,11 +68,7 @@ const diffChildNodes = function (parentNode, domChildNodes, templateChildNodes,
58
68
 
59
69
  // If the DOM node doesn't exist, append/copy the template node
60
70
  if (domChildNode === undefined) {
61
- if (templateChildNode instanceof TemplatePart) {
62
- anchorNode.before(...templateChildNode.childNodes);
63
- } else if (Array.isArray(templateChildNode)) {
64
- anchorNode.before(...templateChildNode);
65
- } else if (typeof templateChildNode === 'object' && 'ELEMENT_NODE' in templateChildNode) {
71
+ if (typeof templateChildNode === 'object' && 'ELEMENT_NODE' in templateChildNode) {
66
72
  parentNode.insertBefore(templateChildNode, anchorNode);
67
73
  } else {
68
74
  // TODO: this might not be performant?! Can we maybe handle this in processDomNode?!
@@ -79,12 +85,12 @@ const diffChildNodes = function (parentNode, domChildNodes, templateChildNodes,
79
85
  parentNode.removeChild(domChildNode);
80
86
  } else if (Array.isArray(domChildNode) || domChildNode instanceof TemplatePart) {
81
87
  // remove childNodes
82
- const childNodes = Array.isArray(domChildNode) ? domChildNode : [...domChildNode.childNodes];
88
+ const childNodes = Array.isArray(domChildNode) ? domChildNode : domChildNode.childNodes;
83
89
  for (const childNode of childNodes) {
84
90
  childNode.remove();
85
91
  }
86
92
  } else {
87
- console.log('This should not happen... what do we do about this node?!', domChildNode);
93
+ console.log('This should not happen... what do we do about this node?!', { domChildNode });
88
94
  }
89
95
  continue;
90
96
  }
@@ -105,13 +111,12 @@ const diffChildNodes = function (parentNode, domChildNodes, templateChildNodes,
105
111
  if (parentNode.contains(domChildNode)) {
106
112
  parentNode.replaceChild(templateChildNode, domChildNode);
107
113
  } else {
108
- console.warn(
109
- 'This should not happen',
114
+ console.warn('This should not happen', {
110
115
  parentNode,
111
- parentNode.contains(domChildNode),
116
+ 'parentNode.contains(domChildNode)': parentNode.contains(domChildNode),
112
117
  templateChildNode,
113
118
  domChildNode,
114
- );
119
+ });
115
120
  }
116
121
  }
117
122
  return templateChildNodes;
@@ -123,7 +128,7 @@ const diffChildNodes = function (parentNode, domChildNodes, templateChildNodes,
123
128
  * @return {(function(*): void)|*}
124
129
  */
125
130
  export const processNodePart = (comment, initialValue) => {
126
- let nodes = initialValue instanceof TemplatePart ? [...initialValue.childNodes] : [];
131
+ let nodes = initialValue instanceof TemplatePart ? initialValue.childNodes : [];
127
132
  let oldValue = initialValue instanceof TemplatePart ? initialValue.strings : initialValue;
128
133
  // this is for string values to be inserted into the DOM. A cached TextNode will be used so that we don't have to constantly create new DOM nodes.
129
134
  let cachedTextNode = undefined;
@@ -166,7 +171,20 @@ export const processNodePart = (comment, initialValue) => {
166
171
  removeNodesBetweenComments(comment);
167
172
  nodes = [];
168
173
  } else {
169
- nodes = diffChildNodes(comment.parentNode, oldValue || [], newValue, comment);
174
+ // TODO: this will always diff the arrays although they might not have changed...
175
+ const unwrapArray = (nodes) => {
176
+ // we have to unwrap any complex objects inside the array so that we can diff arrays of childNodes
177
+ return nodes.flatMap((value) => {
178
+ if (value instanceof TemplatePart) {
179
+ return value.childNodes;
180
+ }
181
+ if (Array.isArray(value)) {
182
+ return unwrapArray(value);
183
+ }
184
+ return value;
185
+ });
186
+ };
187
+ nodes = diffChildNodes(comment.parentNode, nodes, unwrapArray(newValue), comment);
170
188
  }
171
189
  oldValue = newValue;
172
190
  break;
@@ -176,7 +194,7 @@ export const processNodePart = (comment, initialValue) => {
176
194
  // if the new value is a node or a fragment, and it's different from the live node, then it's diffed.
177
195
  // static strings have changed in tpl part
178
196
  oldValue = newValue.strings;
179
- nodes = diffChildNodes(comment.parentNode, nodes, [...newValue.childNodes], comment);
197
+ nodes = diffChildNodes(comment.parentNode, nodes, newValue.childNodes, comment);
180
198
  } else if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) {
181
199
  // DOM Node changed, needs diffing
182
200
  oldValue = newValue;
@@ -196,12 +214,6 @@ export const processNodePart = (comment, initialValue) => {
196
214
  * For updating a text node, a sequence of child nodes or an array of `any` values
197
215
  */
198
216
  export class ChildNodePart extends Part {
199
- /** @type {Node | undefined} */
200
- startNode = undefined;
201
-
202
- /** @type {Node | undefined} */
203
- endNode = undefined;
204
-
205
217
  // TODO: this is only for array values
206
218
  /** @type {Part[]} */
207
219
  parts = [];
@@ -225,6 +237,7 @@ export class ChildNodePart extends Part {
225
237
 
226
238
  super();
227
239
 
240
+ let endNode = undefined;
228
241
  let serverSideRendered = false;
229
242
  if (startNode) {
230
243
  startNode.__part = this; // add Part to comment node for debugging in the browser
@@ -237,7 +250,7 @@ export class ChildNodePart extends Part {
237
250
  childNode = childNode.nextSibling;
238
251
  }
239
252
 
240
- const endNode = childNode;
253
+ endNode = childNode;
241
254
  childNodes.push(endNode);
242
255
 
243
256
  // if not SSRed, childNodes will only ever have two comment nodes, the start and the end marker
@@ -246,15 +259,13 @@ export class ChildNodePart extends Part {
246
259
  }
247
260
 
248
261
  this.childNodes = childNodes;
249
- this.startNode = startNode;
250
- this.endNode = endNode;
251
262
  }
252
263
 
253
264
  // value can become array | TemplatePart | any
254
265
  const initialValue = this.parseValue(value);
255
266
 
256
- if (this.endNode) {
257
- this.processor = processNodePart(this.endNode, serverSideRendered ? initialValue : undefined);
267
+ if (endNode) {
268
+ this.processor = processNodePart(endNode, serverSideRendered ? initialValue : undefined);
258
269
  }
259
270
 
260
271
  if (!serverSideRendered) {
@@ -262,9 +273,9 @@ export class ChildNodePart extends Part {
262
273
  this.updateParts(value);
263
274
  }
264
275
 
265
- // We need a childNodes list that is NOT live so that we don't lose elements when they get removed from the dom and we can (re)add them back in later.
266
- this.childNodes = [...this.childNodes];
267
- this.processor?.(initialValue);
276
+ if (!(value instanceof TemplateResult)) {
277
+ this.processor?.(initialValue);
278
+ }
268
279
  }
269
280
  }
270
281
 
@@ -305,11 +316,19 @@ export class ChildNodePart extends Part {
305
316
  parseValue(value) {
306
317
  if (value instanceof TemplateResult) {
307
318
  if (!this.templatePart) {
308
- const templatePartCommentNodes = this.childNodes?.filter(
309
- (node) => node && node.nodeType === COMMENT_NODE && node.data === 'template-part',
310
- );
311
- const startNode = templatePartCommentNodes[0];
319
+ const startNode = Array.from(this.childNodes)
320
+ .filter((node) => node.nodeType === COMMENT_NODE)
321
+ .find((node) => node.data === 'template-part');
312
322
  this.templatePart = new TemplatePart(startNode, value);
323
+ const serverSideRendered = startNode !== undefined;
324
+ if (!serverSideRendered) {
325
+ // TODO: we should already have the "endNode" from the constructor...
326
+ const endNode = Array.from(this.childNodes)
327
+ .filter((node) => node.nodeType === COMMENT_NODE)
328
+ .find((node) => node.data.includes('/dom-part-'));
329
+ // INFO: this is similar to TemplateResult#237
330
+ insertNodesBetweenComments(endNode, this.templatePart.childNodes);
331
+ }
313
332
  }
314
333
  return this.templatePart;
315
334
  }
@@ -354,6 +373,7 @@ export class ChildNodePart extends Part {
354
373
  }
355
374
  parsedValues[index] = childNodePart;
356
375
  } else {
376
+ // TODO: what if the value from the function is a TemplatePart or an array?! Should we do this first thing?!
357
377
  parsedValues[index] = typeof value === 'function' ? value() : value;
358
378
  }
359
379
  }
@@ -33,6 +33,7 @@ export class TemplatePart extends Part {
33
33
 
34
34
  super();
35
35
 
36
+ let endNode = undefined;
36
37
  let serverSideRendered = false;
37
38
  if (startNode) {
38
39
  startNode.__part = this; // add Part to comment node for debugging in the browser
@@ -45,7 +46,7 @@ export class TemplatePart extends Part {
45
46
  childNode = childNode.nextSibling;
46
47
  }
47
48
 
48
- const endNode = childNode;
49
+ endNode = childNode;
49
50
  childNodes.push(endNode);
50
51
 
51
52
  // if not SSRed, childNodes will only ever have two comment nodes, the start and the end marker
@@ -54,23 +55,19 @@ export class TemplatePart extends Part {
54
55
  }
55
56
 
56
57
  this.childNodes = childNodes;
57
- this.startNode = startNode;
58
- this.endNode = endNode;
59
58
  }
60
59
 
61
60
  this.parseValue(value);
62
61
 
63
62
  if (!serverSideRendered) {
63
+ // TODO: this is causing double diffing because of the this.parseValue() before, ChildNode parts will be constructed + processed
64
64
  this.updateParts(value.values);
65
- // We need a childNodes list that is NOT live so that we don't loose elements when they get removed from the dom and we can (re)add them back in later.
66
- this.childNodes = [...this.childNodes];
67
65
  }
68
66
 
69
- this.startNode = this.childNodes[0];
70
- this.endNode = this.childNodes[this.childNodes.length - 1];
67
+ endNode = this.childNodes[this.childNodes.length - 1];
71
68
 
72
- if (this.endNode) {
73
- this.processor = processNodePart(this.endNode, this);
69
+ if (endNode) {
70
+ this.processor = processNodePart(endNode, this);
74
71
  }
75
72
  }
76
73
 
@@ -78,10 +75,11 @@ export class TemplatePart extends Part {
78
75
  * @param {TemplateResult} value
79
76
  */
80
77
  update(value) {
81
- this.parseValue(value);
78
+ const shouldUpdateDOM = this.parseValue(value);
79
+ if (shouldUpdateDOM) {
80
+ this.processor?.(this);
81
+ }
82
82
  this.updateParts(value.values);
83
- this.childNodes = [...this.childNodes];
84
- this.processor?.(this);
85
83
  }
86
84
 
87
85
  /**
@@ -95,6 +93,7 @@ export class TemplatePart extends Part {
95
93
 
96
94
  /**
97
95
  * @param {TemplateResult} templateResult
96
+ * @return {boolean} whether the part needs to be updated in the DOM
98
97
  */
99
98
  parseValue(templateResult) {
100
99
  if (this.strings !== templateResult.strings) {
@@ -134,8 +133,10 @@ export class TemplatePart extends Part {
134
133
  throw `cannot map part: ${part}`;
135
134
  });
136
135
  this.strings = templateResult.strings;
136
+ this.childNodes = [...this.childNodes];
137
+ return true;
137
138
  }
138
- return this.childNodes;
139
+ return false;
139
140
  }
140
141
 
141
142
  /**