p-elements-core 1.2.32-rc8 → 1.2.32

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.
Files changed (64) hide show
  1. package/.editorconfig +17 -17
  2. package/.gitlab-ci.yml +18 -18
  3. package/CHANGELOG.md +201 -201
  4. package/demo/sample.js +1 -1
  5. package/demo/screen.css +16 -16
  6. package/dist/p-elements-core-modern.js +1 -1
  7. package/dist/p-elements-core.js +1 -1
  8. package/docs/package-lock.json +6897 -6897
  9. package/docs/package.json +27 -27
  10. package/docs/src/404.md +8 -8
  11. package/docs/src/_data/demos/hello-world/hello-world.tsx +35 -35
  12. package/docs/src/_data/demos/hello-world/index.html +10 -10
  13. package/docs/src/_data/demos/hello-world/project.json +7 -7
  14. package/docs/src/_data/demos/timer/demo-timer.tsx +120 -120
  15. package/docs/src/_data/demos/timer/icons.tsx +62 -62
  16. package/docs/src/_data/demos/timer/index.html +12 -12
  17. package/docs/src/_data/demos/timer/project.json +8 -8
  18. package/docs/src/_data/global.js +13 -13
  19. package/docs/src/_data/helpers.js +19 -19
  20. package/docs/src/_includes/layouts/base.njk +30 -30
  21. package/docs/src/_includes/layouts/playground.njk +40 -40
  22. package/docs/src/_includes/partials/app-header.njk +8 -8
  23. package/docs/src/_includes/partials/head.njk +14 -14
  24. package/docs/src/_includes/partials/nav.njk +19 -19
  25. package/docs/src/_includes/partials/top-nav.njk +51 -51
  26. package/docs/src/documentation/custom-element.md +221 -221
  27. package/docs/src/documentation/decorators/bind.md +71 -71
  28. package/docs/src/documentation/decorators/custom-element-config.md +63 -63
  29. package/docs/src/documentation/decorators/property.md +83 -83
  30. package/docs/src/documentation/decorators/query.md +66 -66
  31. package/docs/src/documentation/decorators/render-property-on-set.md +60 -60
  32. package/docs/src/documentation/decorators.md +9 -9
  33. package/docs/src/documentation/reactive-properties.md +53 -53
  34. package/docs/src/index.d.ts +25 -25
  35. package/docs/src/index.md +3 -3
  36. package/docs/src/scripts/components/app-mode-switch/app-mode-switch.css +78 -78
  37. package/docs/src/scripts/components/app-mode-switch/app-mode-switch.tsx +166 -166
  38. package/docs/src/scripts/components/app-playground/app-playground.tsx +189 -189
  39. package/docs/tsconfig.json +22 -22
  40. package/index.html +10 -2
  41. package/package.json +1 -1
  42. package/readme.md +206 -206
  43. package/src/custom-element-controller.ts +31 -31
  44. package/src/custom-element.test.ts +906 -906
  45. package/src/custom-element.ts +3 -8
  46. package/src/decorators/bind.test.ts +163 -163
  47. package/src/decorators/bind.ts +46 -46
  48. package/src/decorators/custom-element-config.ts +17 -17
  49. package/src/decorators/property.test.ts +279 -279
  50. package/src/decorators/query.test.ts +146 -146
  51. package/src/decorators/query.ts +12 -12
  52. package/src/decorators/render-property-on-set.ts +3 -3
  53. package/src/helpers/css.ts +71 -71
  54. package/src/maquette/cache.ts +35 -35
  55. package/src/maquette/dom.ts +115 -115
  56. package/src/maquette/h.ts +100 -100
  57. package/src/maquette/index.ts +12 -12
  58. package/src/maquette/interfaces.ts +536 -536
  59. package/src/maquette/jsx.ts +61 -61
  60. package/src/maquette/mapping.ts +56 -56
  61. package/src/maquette/projection.ts +666 -666
  62. package/src/maquette/projector.ts +205 -205
  63. package/src/sample/mixin/highlight.tsx +33 -33
  64. package/src/sample/sample.tsx +98 -0
@@ -1,666 +1,666 @@
1
- /**
2
- * Exports here are NOT re-exported to maquette
3
- */
4
- import { EventHandler, Projection, ProjectionOptions, VNode, VNodeProperties } from "./interfaces";
5
-
6
- const NAMESPACE_W3 = "http://www.w3.org/";
7
- const NAMESPACE_SVG = `${NAMESPACE_W3}2000/svg`;
8
- const NAMESPACE_XLINK = `${NAMESPACE_W3}1999/xlink`;
9
-
10
- const BOOLEAN_ATRRIBUTES = [
11
- "allowfullscreen",
12
- "async",
13
- "autofocus",
14
- "autoplay",
15
- "checked",
16
- "controls",
17
- "default",
18
- "defer",
19
- "disabled",
20
- "formnovalidate",
21
- "hidden",
22
- "ismap",
23
- "itemscope",
24
- "loop",
25
- "multiple",
26
- "muted",
27
- "nomodule",
28
- "novalidate",
29
- "open",
30
- "readonly",
31
- "required",
32
- "reversed",
33
- "selected",
34
- "scoped",
35
- "seamless",
36
- "typemustmatch",
37
- ];
38
-
39
-
40
- let emptyArray = <VNode[]>[];
41
-
42
- export let extend = <T extends object>(base: T, overrides: any): T => {
43
- let result = {} as any;
44
- Object.keys(base).forEach((key) => {
45
- result[key] = (base as any)[key];
46
- });
47
- if (overrides) {
48
- Object.keys(overrides).forEach((key) => {
49
- result[key] = overrides[key];
50
- });
51
- }
52
- return result;
53
- };
54
-
55
- let same = (vnode1: VNode, vnode2: VNode) => {
56
- if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {
57
- return false;
58
- }
59
- if (vnode1.properties && vnode2.properties) {
60
- if (vnode1.properties.key !== vnode2.properties.key) {
61
- return false;
62
- }
63
- return vnode1.properties.bind === vnode2.properties.bind;
64
- }
65
- return !vnode1.properties && !vnode2.properties;
66
- };
67
-
68
- let checkStyleValue = (styleValue: unknown) => {
69
- if (typeof styleValue !== "string") {
70
- throw new Error("Style values must be strings");
71
- }
72
- };
73
-
74
- let findIndexOfChild = (children: VNode[], sameAs: VNode, start: number) => {
75
- if (sameAs.vnodeSelector !== "") {
76
- // Never scan for text-nodes
77
- for (let i = start; i < children.length; i++) {
78
- if (same(children[i], sameAs)) {
79
- return i;
80
- }
81
- }
82
- }
83
- return -1;
84
- };
85
-
86
- let checkDistinguishable = (
87
- childNodes: VNode[],
88
- indexToCheck: number,
89
- parentVNode: VNode,
90
- operation: string
91
- ) => {
92
- let childNode = childNodes[indexToCheck];
93
- if (childNode.vnodeSelector === "") {
94
- return; // Text nodes need not be distinguishable
95
- }
96
- let properties = childNode.properties;
97
- let key = properties
98
- ? properties.key === undefined
99
- ? properties.bind
100
- : properties.key
101
- : undefined;
102
- if (!key) {
103
- // A key is just assumed to be unique
104
- for (let i = 0; i < childNodes.length; i++) {
105
- if (i !== indexToCheck) {
106
- let node = childNodes[i];
107
- if (same(node, childNode)) {
108
- throw {
109
- error: new Error(
110
- `${parentVNode.vnodeSelector} had a ${childNode.vnodeSelector} child ${
111
- operation === "added" ? operation : "removed"
112
- }, but there is now more than one. You must add unique key properties to make them distinguishable.`
113
- ),
114
- parentNode: parentVNode,
115
- childNode,
116
- };
117
- }
118
- }
119
- }
120
- }
121
- };
122
-
123
- let nodeAdded = (vNode: VNode) => {
124
- if (vNode.properties) {
125
- let enterAnimation = vNode.properties.enterAnimation;
126
- if (enterAnimation) {
127
- enterAnimation(vNode.domNode as Element, vNode.properties);
128
- }
129
- }
130
- };
131
-
132
- let removedNodes: VNode[] = [];
133
- let requestedIdleCallback = false;
134
-
135
- let visitRemovedNode = (node: VNode) => {
136
- (node.children || []).forEach(visitRemovedNode);
137
-
138
- if (node.properties && node.properties.afterRemoved) {
139
- node.properties.afterRemoved.apply(node.properties.bind || node.properties, [
140
- <Element>node.domNode,
141
- ]);
142
- }
143
- };
144
-
145
- let processPendingNodeRemovals = (): void => {
146
- requestedIdleCallback = false;
147
-
148
- removedNodes.forEach(visitRemovedNode);
149
- removedNodes.length = 0;
150
- };
151
-
152
- let scheduleNodeRemoval = (vNode: VNode): void => {
153
- removedNodes.push(vNode);
154
-
155
- if (!requestedIdleCallback) {
156
- requestedIdleCallback = true;
157
- if (typeof window !== "undefined" && "requestIdleCallback" in window) {
158
- window.requestIdleCallback(processPendingNodeRemovals, { timeout: 16 });
159
- } else {
160
- setTimeout(processPendingNodeRemovals, 16);
161
- }
162
- }
163
- };
164
-
165
- let nodeToRemove = (vNode: VNode) => {
166
- let domNode: Node = vNode.domNode!;
167
- if (vNode.properties) {
168
-
169
- if (vNode.properties.eventListeners) {
170
- if (Array.isArray(vNode.properties.eventListeners)) {
171
- vNode.properties.eventListeners.forEach((evListener) => {
172
- domNode.removeEventListener(evListener[0], evListener[1]);
173
- });
174
- }
175
- }
176
-
177
- let exitAnimation = vNode.properties.exitAnimation;
178
- if (exitAnimation) {
179
- (domNode as HTMLElement).style.pointerEvents = "none";
180
- let removeDomNode = () => {
181
- if (domNode.parentNode) {
182
- domNode.parentNode.removeChild(domNode);
183
- scheduleNodeRemoval(vNode);
184
- }
185
- };
186
- exitAnimation(domNode as Element, removeDomNode, vNode.properties);
187
- return;
188
- }
189
- }
190
- if (domNode.parentNode) {
191
- domNode.parentNode.removeChild(domNode);
192
- scheduleNodeRemoval(vNode);
193
- }
194
- };
195
-
196
- let setProperties = (
197
- domNode: Node,
198
- properties: VNodeProperties | undefined,
199
- projectionOptions: ProjectionOptions
200
- ) => {
201
- if (!properties) {
202
- return;
203
- }
204
- let eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
205
- let propNames = Object.keys(properties);
206
- let propCount = propNames.length;
207
- for (let i = 0; i < propCount; i++) {
208
- let propName = propNames[i];
209
- let propValue = properties[propName];
210
- if (propName === "className") {
211
- throw new Error('Property "className" is not supported, use "class".');
212
- } else if (propName === "class") {
213
- toggleClasses(domNode as HTMLElement, propValue as string, true);
214
- } else if (propName === "classes") {
215
- // object with string keys and boolean values
216
- let classNames = Object.keys(propValue);
217
- let classNameCount = classNames.length;
218
- for (let j = 0; j < classNameCount; j++) {
219
- let className = classNames[j];
220
- if (propValue[className]) {
221
- (domNode as Element).classList.add(className);
222
- }
223
- }
224
- } else if (propName === "styles") {
225
- // object with string keys and string (!) values
226
- let styleNames = Object.keys(propValue);
227
- let styleCount = styleNames.length;
228
- for (let j = 0; j < styleCount; j++) {
229
- let styleName = styleNames[j];
230
- let styleValue = propValue[styleName];
231
- if (styleValue) {
232
- checkStyleValue(styleValue);
233
- projectionOptions.styleApplyer!(<HTMLElement>domNode, styleName, styleValue);
234
- }
235
- }
236
- } else if (propName === "on" && propValue) {
237
- // object with string keys and function values
238
- for (let [key, handler] of Object.entries(properties.on!)) {
239
- let listener: EventHandler | undefined =
240
- typeof handler === "function" ? handler : handler.listener;
241
- if (eventHandlerInterceptor) {
242
- listener = eventHandlerInterceptor(key, listener, domNode, properties);
243
- }
244
- if (listener) {
245
- domNode.addEventListener(
246
- key,
247
- listener,
248
- typeof handler === "function" ? undefined : handler.options
249
- );
250
- }
251
- }
252
- } else if (propName !== "key" && propValue !== null && propValue !== undefined) {
253
- let type = typeof propValue;
254
- if (type === "function") {
255
- if (propName.lastIndexOf("on", 0) === 0) {
256
- // lastIndexOf(,0)===0 -> startsWith
257
- if (eventHandlerInterceptor) {
258
- propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
259
- }
260
- if (propName === "oninput") {
261
- (function () {
262
- // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
263
- let oldPropValue = propValue;
264
- propValue = function (this: HTMLElement, evt: Event) {
265
- oldPropValue.apply(this, [evt]);
266
- (evt.target as any)["oninput-value"] = (evt.target as HTMLInputElement).value; // may be HTMLTextAreaElement as well
267
- };
268
- })();
269
- }
270
- }
271
- (domNode as any)[propName] = propValue;
272
- } else if (projectionOptions.namespace === NAMESPACE_SVG) {
273
- if (propName === "href") {
274
- (domNode as Element).setAttributeNS(NAMESPACE_XLINK, propName, propValue);
275
- } else {
276
- // all SVG attributes are read-only in DOM, so...
277
- (domNode as Element).setAttribute(propName, propValue);
278
- }
279
- } else if (type === "string" && propName !== "value" && propName !== "innerHTML") {
280
- (domNode as Element).setAttribute(propName, propValue);
281
-
282
- } else {
283
- if (type === "string" && propName === "value" && (domNode as any)?.tagName.indexOf("-")) {
284
- // set attribute for custom elements
285
- (domNode as Element).setAttribute(propName, propValue);
286
- }
287
- (domNode as any)[propName] = propValue;
288
- }
289
- }
290
- }
291
- };
292
-
293
- let addChildren = (
294
- domNode: Node,
295
- children: VNode[] | undefined,
296
- projectionOptions: ProjectionOptions
297
- ) => {
298
- if (!children) {
299
- return;
300
- }
301
- for (let child of children) {
302
- createDom(child, domNode, undefined, projectionOptions);
303
- }
304
- };
305
-
306
- export let initPropertiesAndChildren = (
307
- domNode: Node,
308
- vnode: VNode,
309
- projectionOptions: ProjectionOptions
310
- ): void => {
311
- addChildren(domNode, vnode.children, projectionOptions); // children before properties, needed for value property of <select>.
312
- if (vnode.text) {
313
- domNode.textContent = vnode.text;
314
- }
315
- setProperties(domNode, vnode.properties, projectionOptions);
316
- if (vnode.properties && vnode.properties.afterCreate) {
317
- vnode.properties.afterCreate.apply(vnode.properties.bind || vnode.properties, [
318
- domNode as Element,
319
- projectionOptions,
320
- vnode.vnodeSelector,
321
- vnode.properties,
322
- vnode.children,
323
- ]);
324
- } else if (vnode.properties && vnode.properties.eventListeners) {
325
- if (Array.isArray(vnode.properties.eventListeners)) {
326
- vnode.properties.eventListeners.forEach((evListener: any) => {
327
- domNode.addEventListener(evListener[0], evListener[1], evListener[2]);
328
- });
329
- } else {
330
- throw new Error("eventListeners value must be an array");
331
- }
332
- }
333
- };
334
-
335
- export let createDom = (
336
- vnode: VNode,
337
- parentNode: Node,
338
- insertBefore: Node | null | undefined,
339
- projectionOptions: ProjectionOptions
340
- ): void => {
341
- let domNode: Node | undefined;
342
- let start = 0;
343
- let vnodeSelector = vnode.vnodeSelector;
344
- let doc = parentNode.ownerDocument!;
345
- if (vnodeSelector === "") {
346
- if (vnode.domNode) {
347
- vnode.domNode.nodeValue = vnode.text!;
348
- } else {
349
- domNode = vnode.domNode = doc.createTextNode(vnode.text!);
350
- if (insertBefore !== undefined) {
351
- parentNode.insertBefore(domNode, insertBefore);
352
- } else {
353
- parentNode.appendChild(domNode);
354
- }
355
- }
356
- } else {
357
- for (let i = 0; i <= vnodeSelector.length; ++i) {
358
- let c = vnodeSelector.charAt(i);
359
- if (i === vnodeSelector.length || c === "." || c === "#") {
360
- let type = vnodeSelector.charAt(start - 1);
361
- let found = vnodeSelector.slice(start, i);
362
- if (type === ".") {
363
- (domNode! as HTMLElement).classList.add(found);
364
- } else if (type === "#") {
365
- (domNode! as Element).id = found;
366
- } else {
367
- if (found === "svg") {
368
- projectionOptions = extend(projectionOptions, {
369
- namespace: NAMESPACE_SVG,
370
- });
371
- }
372
- if (projectionOptions.namespace !== undefined) {
373
- domNode = vnode.domNode = doc.createElementNS(projectionOptions.namespace, found);
374
- } else {
375
- domNode = vnode.domNode =
376
- vnode.domNode ||
377
- (vnode.properties?.is
378
- ? doc.createElement(found, { is: vnode.properties.is })
379
- : doc.createElement(found));
380
- if (found === "input" && vnode.properties && vnode.properties.type !== undefined) {
381
- // IE8 and older don't support setting input type after the DOM Node has been added to the document
382
- (domNode as Element).setAttribute("type", vnode.properties.type);
383
- }
384
- }
385
- if (insertBefore !== undefined) {
386
- parentNode.insertBefore(domNode, insertBefore);
387
- } else if (domNode.parentNode !== parentNode) {
388
- parentNode.appendChild(domNode);
389
- }
390
- }
391
- start = i + 1;
392
- }
393
- }
394
- initPropertiesAndChildren(domNode!, vnode, projectionOptions);
395
- }
396
- };
397
-
398
- let updateDom: (previous: VNode, vnode: VNode, projectionOptions: ProjectionOptions) => boolean;
399
-
400
- /**
401
- * Adds or removes classes from an Element
402
- * @param domNode the element
403
- * @param classes a string separated list of classes
404
- * @param on true means add classes, false means remove
405
- */
406
- let toggleClasses = (domNode: HTMLElement, classes: string | null | undefined, on: boolean) => {
407
- if (!classes) {
408
- return;
409
- }
410
- classes.split(" ").forEach((classToToggle) => {
411
- if (classToToggle) {
412
- domNode.classList.toggle(classToToggle, on);
413
- }
414
- });
415
- };
416
-
417
- let updateProperties = (
418
- domNode: Node,
419
- previousProperties: VNodeProperties | undefined,
420
- properties: VNodeProperties | undefined,
421
- projectionOptions: ProjectionOptions
422
- ) => {
423
- if (!properties) {
424
- return;
425
- }
426
- let propertiesUpdated = false;
427
- let propNames = Object.keys(properties);
428
- let propCount = propNames.length;
429
- for (let i = 0; i < propCount; i++) {
430
- let propName = propNames[i];
431
- // assuming that properties will be nullified instead of missing is by design
432
- let propValue = properties[propName];
433
- let previousValue = previousProperties![propName];
434
- if (propName === "class") {
435
- if (previousValue !== propValue) {
436
- toggleClasses(domNode as HTMLElement, previousValue, false);
437
- toggleClasses(domNode as HTMLElement, propValue, true);
438
- }
439
- } else if (propName === "classes") {
440
- let classList = (domNode as Element).classList;
441
- let classNames = Object.keys(propValue);
442
- let classNameCount = classNames.length;
443
- for (let j = 0; j < classNameCount; j++) {
444
- let className = classNames[j];
445
- let on = !!propValue[className];
446
- let previousOn = !!previousValue[className];
447
- if (on === previousOn) {
448
- continue;
449
- }
450
- propertiesUpdated = true;
451
- if (on) {
452
- classList.add(className);
453
- } else {
454
- classList.remove(className);
455
- }
456
- }
457
- } else if (propName === "styles") {
458
- let styleNames = Object.keys(propValue);
459
- let styleCount = styleNames.length;
460
- for (let j = 0; j < styleCount; j++) {
461
- let styleName = styleNames[j];
462
- let newStyleValue = propValue[styleName];
463
- let oldStyleValue = previousValue[styleName];
464
- if (newStyleValue === oldStyleValue) {
465
- continue;
466
- }
467
- propertiesUpdated = true;
468
- if (newStyleValue) {
469
- checkStyleValue(newStyleValue);
470
- projectionOptions.styleApplyer!(domNode as HTMLElement, styleName, newStyleValue);
471
- } else {
472
- projectionOptions.styleApplyer!(domNode as HTMLElement, styleName, "");
473
- }
474
- }
475
- } else {
476
- if (!propValue && typeof previousValue === "string") {
477
- if (BOOLEAN_ATRRIBUTES.indexOf(propName) < 0) {
478
- propValue = "";
479
- }
480
- }
481
- if (propName === "value") {
482
- // value can be manipulated by the user directly and using event.preventDefault() is not an option
483
- let domValue = (domNode as any)[propName];
484
-
485
- if (
486
- domValue !== propValue && // The 'value' in the DOM tree !== newValue
487
- ((domNode as any)["oninput-value"]
488
- ? domValue === (domNode as any)["oninput-value"] // If the last reported value to 'oninput' does not match domValue, do nothing and wait for oninput
489
- : propValue !== previousValue) // Only update the value if the vdom changed
490
- ) {
491
- // The edge cases are described in the tests
492
- (domNode as any)[propName] = propValue; // Reset the value, even if the virtual DOM did not change
493
- (domNode as any)["oninput-value"] = undefined;
494
- } // else do not update the domNode, otherwise the cursor position would be changed
495
- if (propValue !== previousValue) {
496
- propertiesUpdated = true;
497
- }
498
- } else if(BOOLEAN_ATRRIBUTES.indexOf(propName) >= 0) {
499
- if (typeof propValue === "string" || propValue === true) {
500
- (domNode as Element).setAttribute(propName, "");
501
- domNode[propName] = true;
502
- } else {
503
- (domNode as Element).removeAttribute(propName);
504
- domNode[propName] = false;
505
- }
506
- propertiesUpdated = true;
507
- } else if (propValue !== previousValue) {
508
- let type = typeof propValue;
509
- if (type !== "function" || !projectionOptions.eventHandlerInterceptor) {
510
- // Function updates are expected to be handled by the EventHandlerInterceptor
511
- if (projectionOptions.namespace === NAMESPACE_SVG) {
512
- if (propName === "href") {
513
- (domNode as Element).setAttributeNS(NAMESPACE_XLINK, propName, propValue);
514
- } else {
515
- // all SVG attributes are read-only in DOM, so...
516
- (domNode as Element).setAttribute(propName, propValue);
517
- }
518
- } else if (type === "string" && propName !== "innerHTML") {
519
- if (propName === "role" && propValue === "") {
520
- (domNode as any).removeAttribute(propName);
521
- } else {
522
- (domNode as Element).setAttribute(propName, propValue);
523
- }
524
- } else if ((domNode as any)[propName] !== propValue) {
525
- // Comparison is here for side-effects in Edge with scrollLeft and scrollTop
526
- (domNode as any)[propName] = propValue;
527
- }
528
-
529
- propertiesUpdated = true;
530
- }
531
- }
532
- }
533
- }
534
- return propertiesUpdated;
535
- };
536
-
537
- let updateChildren = (
538
- vnode: VNode,
539
- domNode: Node,
540
- oldChildren: VNode[] | undefined,
541
- newChildren: VNode[] | undefined,
542
- projectionOptions: ProjectionOptions
543
- ) => {
544
- if (oldChildren === newChildren) {
545
- return false;
546
- }
547
- oldChildren = oldChildren || emptyArray;
548
- newChildren = newChildren || emptyArray;
549
- let oldChildrenLength = oldChildren.length;
550
- let newChildrenLength = newChildren.length;
551
-
552
- let oldIndex = 0;
553
- let newIndex = 0;
554
- let i: number;
555
- let textUpdated = false;
556
- while (newIndex < newChildrenLength) {
557
- let oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;
558
- let newChild = newChildren[newIndex];
559
- if (oldChild !== undefined && same(oldChild, newChild)) {
560
- textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;
561
- oldIndex++;
562
- } else {
563
- let findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);
564
- if (findOldIndex >= 0) {
565
- // Remove preceding missing children
566
- for (i = oldIndex; i < findOldIndex; i++) {
567
- nodeToRemove(oldChildren[i]);
568
- checkDistinguishable(oldChildren, i, vnode, "removed");
569
- }
570
- textUpdated =
571
- updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;
572
- oldIndex = findOldIndex + 1;
573
- } else {
574
- // New child
575
- createDom(
576
- newChild,
577
- domNode,
578
- oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined,
579
- projectionOptions
580
- );
581
- nodeAdded(newChild);
582
- checkDistinguishable(newChildren, newIndex, vnode, "added");
583
- }
584
- }
585
- newIndex++;
586
- }
587
- if (oldChildrenLength > oldIndex) {
588
- // Remove child fragments
589
- for (i = oldIndex; i < oldChildrenLength; i++) {
590
- nodeToRemove(oldChildren[i]);
591
- checkDistinguishable(oldChildren, i, vnode, "removed");
592
- }
593
- }
594
- return textUpdated;
595
- };
596
-
597
- updateDom = (previous, vnode, projectionOptions) => {
598
- let domNode = previous.domNode!;
599
- let textUpdated = false;
600
- if (previous === vnode) {
601
- return false; // By contract, VNode objects may not be modified anymore after passing them to maquette
602
- }
603
- let updated = false;
604
- if (vnode.vnodeSelector === "") {
605
- if (vnode.text !== previous.text) {
606
- let newTextNode = domNode.ownerDocument!.createTextNode(vnode.text!);
607
- domNode.parentNode!.replaceChild(newTextNode, domNode);
608
- vnode.domNode = newTextNode;
609
- textUpdated = true;
610
- return textUpdated;
611
- }
612
- vnode.domNode = domNode;
613
- } else {
614
- if (vnode.vnodeSelector.lastIndexOf("svg", 0) === 0) {
615
- // lastIndexOf(needle,0)===0 means StartsWith
616
- projectionOptions = extend(projectionOptions, {
617
- namespace: NAMESPACE_SVG,
618
- });
619
- }
620
- if (previous.text !== vnode.text) {
621
- updated = true;
622
- if (vnode.text === undefined) {
623
- domNode.removeChild(domNode.firstChild!); // the only textnode presumably
624
- } else {
625
- domNode.textContent = vnode.text;
626
- }
627
- }
628
- vnode.domNode = domNode;
629
- updated =
630
- updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) ||
631
- updated;
632
- updated =
633
- updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) ||
634
- updated;
635
- if (vnode.properties && vnode.properties.afterUpdate) {
636
- vnode.properties.afterUpdate.apply(vnode.properties.bind || vnode.properties, [
637
- <Element>domNode,
638
- projectionOptions,
639
- vnode.vnodeSelector,
640
- vnode.properties,
641
- vnode.children,
642
- ]);
643
- }
644
- }
645
- if (updated && vnode.properties && vnode.properties.updateAnimation) {
646
- vnode.properties.updateAnimation(<Element>domNode, vnode.properties, previous.properties);
647
- }
648
- return textUpdated;
649
- };
650
-
651
- export let createProjection = (vnode: VNode, projectionOptions: ProjectionOptions): Projection => {
652
- return {
653
- getLastRender: () => vnode,
654
- update: (updatedVnode: VNode) => {
655
- if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {
656
- throw new Error(
657
- "The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)"
658
- );
659
- }
660
- let previousVNode = vnode;
661
- vnode = updatedVnode;
662
- updateDom(previousVNode, updatedVnode, projectionOptions);
663
- },
664
- domNode: <Element>vnode.domNode,
665
- };
666
- };
1
+ /**
2
+ * Exports here are NOT re-exported to maquette
3
+ */
4
+ import { EventHandler, Projection, ProjectionOptions, VNode, VNodeProperties } from "./interfaces";
5
+
6
+ const NAMESPACE_W3 = "http://www.w3.org/";
7
+ const NAMESPACE_SVG = `${NAMESPACE_W3}2000/svg`;
8
+ const NAMESPACE_XLINK = `${NAMESPACE_W3}1999/xlink`;
9
+
10
+ const BOOLEAN_ATRRIBUTES = [
11
+ "allowfullscreen",
12
+ "async",
13
+ "autofocus",
14
+ "autoplay",
15
+ "checked",
16
+ "controls",
17
+ "default",
18
+ "defer",
19
+ "disabled",
20
+ "formnovalidate",
21
+ "hidden",
22
+ "ismap",
23
+ "itemscope",
24
+ "loop",
25
+ "multiple",
26
+ "muted",
27
+ "nomodule",
28
+ "novalidate",
29
+ "open",
30
+ "readonly",
31
+ "required",
32
+ "reversed",
33
+ "selected",
34
+ "scoped",
35
+ "seamless",
36
+ "typemustmatch",
37
+ ];
38
+
39
+
40
+ let emptyArray = <VNode[]>[];
41
+
42
+ export let extend = <T extends object>(base: T, overrides: any): T => {
43
+ let result = {} as any;
44
+ Object.keys(base).forEach((key) => {
45
+ result[key] = (base as any)[key];
46
+ });
47
+ if (overrides) {
48
+ Object.keys(overrides).forEach((key) => {
49
+ result[key] = overrides[key];
50
+ });
51
+ }
52
+ return result;
53
+ };
54
+
55
+ let same = (vnode1: VNode, vnode2: VNode) => {
56
+ if (vnode1.vnodeSelector !== vnode2.vnodeSelector) {
57
+ return false;
58
+ }
59
+ if (vnode1.properties && vnode2.properties) {
60
+ if (vnode1.properties.key !== vnode2.properties.key) {
61
+ return false;
62
+ }
63
+ return vnode1.properties.bind === vnode2.properties.bind;
64
+ }
65
+ return !vnode1.properties && !vnode2.properties;
66
+ };
67
+
68
+ let checkStyleValue = (styleValue: unknown) => {
69
+ if (typeof styleValue !== "string") {
70
+ throw new Error("Style values must be strings");
71
+ }
72
+ };
73
+
74
+ let findIndexOfChild = (children: VNode[], sameAs: VNode, start: number) => {
75
+ if (sameAs.vnodeSelector !== "") {
76
+ // Never scan for text-nodes
77
+ for (let i = start; i < children.length; i++) {
78
+ if (same(children[i], sameAs)) {
79
+ return i;
80
+ }
81
+ }
82
+ }
83
+ return -1;
84
+ };
85
+
86
+ let checkDistinguishable = (
87
+ childNodes: VNode[],
88
+ indexToCheck: number,
89
+ parentVNode: VNode,
90
+ operation: string
91
+ ) => {
92
+ let childNode = childNodes[indexToCheck];
93
+ if (childNode.vnodeSelector === "") {
94
+ return; // Text nodes need not be distinguishable
95
+ }
96
+ let properties = childNode.properties;
97
+ let key = properties
98
+ ? properties.key === undefined
99
+ ? properties.bind
100
+ : properties.key
101
+ : undefined;
102
+ if (!key) {
103
+ // A key is just assumed to be unique
104
+ for (let i = 0; i < childNodes.length; i++) {
105
+ if (i !== indexToCheck) {
106
+ let node = childNodes[i];
107
+ if (same(node, childNode)) {
108
+ throw {
109
+ error: new Error(
110
+ `${parentVNode.vnodeSelector} had a ${childNode.vnodeSelector} child ${
111
+ operation === "added" ? operation : "removed"
112
+ }, but there is now more than one. You must add unique key properties to make them distinguishable.`
113
+ ),
114
+ parentNode: parentVNode,
115
+ childNode,
116
+ };
117
+ }
118
+ }
119
+ }
120
+ }
121
+ };
122
+
123
+ let nodeAdded = (vNode: VNode) => {
124
+ if (vNode.properties) {
125
+ let enterAnimation = vNode.properties.enterAnimation;
126
+ if (enterAnimation) {
127
+ enterAnimation(vNode.domNode as Element, vNode.properties);
128
+ }
129
+ }
130
+ };
131
+
132
+ let removedNodes: VNode[] = [];
133
+ let requestedIdleCallback = false;
134
+
135
+ let visitRemovedNode = (node: VNode) => {
136
+ (node.children || []).forEach(visitRemovedNode);
137
+
138
+ if (node.properties && node.properties.afterRemoved) {
139
+ node.properties.afterRemoved.apply(node.properties.bind || node.properties, [
140
+ <Element>node.domNode,
141
+ ]);
142
+ }
143
+ };
144
+
145
+ let processPendingNodeRemovals = (): void => {
146
+ requestedIdleCallback = false;
147
+
148
+ removedNodes.forEach(visitRemovedNode);
149
+ removedNodes.length = 0;
150
+ };
151
+
152
+ let scheduleNodeRemoval = (vNode: VNode): void => {
153
+ removedNodes.push(vNode);
154
+
155
+ if (!requestedIdleCallback) {
156
+ requestedIdleCallback = true;
157
+ if (typeof window !== "undefined" && "requestIdleCallback" in window) {
158
+ window.requestIdleCallback(processPendingNodeRemovals, { timeout: 16 });
159
+ } else {
160
+ setTimeout(processPendingNodeRemovals, 16);
161
+ }
162
+ }
163
+ };
164
+
165
+ let nodeToRemove = (vNode: VNode) => {
166
+ let domNode: Node = vNode.domNode!;
167
+ if (vNode.properties) {
168
+
169
+ if (vNode.properties.eventListeners) {
170
+ if (Array.isArray(vNode.properties.eventListeners)) {
171
+ vNode.properties.eventListeners.forEach((evListener) => {
172
+ domNode.removeEventListener(evListener[0], evListener[1]);
173
+ });
174
+ }
175
+ }
176
+
177
+ let exitAnimation = vNode.properties.exitAnimation;
178
+ if (exitAnimation) {
179
+ (domNode as HTMLElement).style.pointerEvents = "none";
180
+ let removeDomNode = () => {
181
+ if (domNode.parentNode) {
182
+ domNode.parentNode.removeChild(domNode);
183
+ scheduleNodeRemoval(vNode);
184
+ }
185
+ };
186
+ exitAnimation(domNode as Element, removeDomNode, vNode.properties);
187
+ return;
188
+ }
189
+ }
190
+ if (domNode.parentNode) {
191
+ domNode.parentNode.removeChild(domNode);
192
+ scheduleNodeRemoval(vNode);
193
+ }
194
+ };
195
+
196
+ let setProperties = (
197
+ domNode: Node,
198
+ properties: VNodeProperties | undefined,
199
+ projectionOptions: ProjectionOptions
200
+ ) => {
201
+ if (!properties) {
202
+ return;
203
+ }
204
+ let eventHandlerInterceptor = projectionOptions.eventHandlerInterceptor;
205
+ let propNames = Object.keys(properties);
206
+ let propCount = propNames.length;
207
+ for (let i = 0; i < propCount; i++) {
208
+ let propName = propNames[i];
209
+ let propValue = properties[propName];
210
+ if (propName === "className") {
211
+ throw new Error('Property "className" is not supported, use "class".');
212
+ } else if (propName === "class") {
213
+ toggleClasses(domNode as HTMLElement, propValue as string, true);
214
+ } else if (propName === "classes") {
215
+ // object with string keys and boolean values
216
+ let classNames = Object.keys(propValue);
217
+ let classNameCount = classNames.length;
218
+ for (let j = 0; j < classNameCount; j++) {
219
+ let className = classNames[j];
220
+ if (propValue[className]) {
221
+ (domNode as Element).classList.add(className);
222
+ }
223
+ }
224
+ } else if (propName === "styles") {
225
+ // object with string keys and string (!) values
226
+ let styleNames = Object.keys(propValue);
227
+ let styleCount = styleNames.length;
228
+ for (let j = 0; j < styleCount; j++) {
229
+ let styleName = styleNames[j];
230
+ let styleValue = propValue[styleName];
231
+ if (styleValue) {
232
+ checkStyleValue(styleValue);
233
+ projectionOptions.styleApplyer!(<HTMLElement>domNode, styleName, styleValue);
234
+ }
235
+ }
236
+ } else if (propName === "on" && propValue) {
237
+ // object with string keys and function values
238
+ for (let [key, handler] of Object.entries(properties.on!)) {
239
+ let listener: EventHandler | undefined =
240
+ typeof handler === "function" ? handler : handler.listener;
241
+ if (eventHandlerInterceptor) {
242
+ listener = eventHandlerInterceptor(key, listener, domNode, properties);
243
+ }
244
+ if (listener) {
245
+ domNode.addEventListener(
246
+ key,
247
+ listener,
248
+ typeof handler === "function" ? undefined : handler.options
249
+ );
250
+ }
251
+ }
252
+ } else if (propName !== "key" && propValue !== null && propValue !== undefined) {
253
+ let type = typeof propValue;
254
+ if (type === "function") {
255
+ if (propName.lastIndexOf("on", 0) === 0) {
256
+ // lastIndexOf(,0)===0 -> startsWith
257
+ if (eventHandlerInterceptor) {
258
+ propValue = eventHandlerInterceptor(propName, propValue, domNode, properties); // intercept eventhandlers
259
+ }
260
+ if (propName === "oninput") {
261
+ (function () {
262
+ // record the evt.target.value, because IE and Edge sometimes do a requestAnimationFrame between changing value and running oninput
263
+ let oldPropValue = propValue;
264
+ propValue = function (this: HTMLElement, evt: Event) {
265
+ oldPropValue.apply(this, [evt]);
266
+ (evt.target as any)["oninput-value"] = (evt.target as HTMLInputElement).value; // may be HTMLTextAreaElement as well
267
+ };
268
+ })();
269
+ }
270
+ }
271
+ (domNode as any)[propName] = propValue;
272
+ } else if (projectionOptions.namespace === NAMESPACE_SVG) {
273
+ if (propName === "href") {
274
+ (domNode as Element).setAttributeNS(NAMESPACE_XLINK, propName, propValue);
275
+ } else {
276
+ // all SVG attributes are read-only in DOM, so...
277
+ (domNode as Element).setAttribute(propName, propValue);
278
+ }
279
+ } else if (type === "string" && propName !== "value" && propName !== "innerHTML") {
280
+ (domNode as Element).setAttribute(propName, propValue);
281
+
282
+ } else {
283
+ if (type === "string" && propName === "value" && (domNode as any)?.tagName.indexOf("-")) {
284
+ // set attribute for custom elements
285
+ (domNode as Element).setAttribute(propName, propValue);
286
+ }
287
+ (domNode as any)[propName] = propValue;
288
+ }
289
+ }
290
+ }
291
+ };
292
+
293
+ let addChildren = (
294
+ domNode: Node,
295
+ children: VNode[] | undefined,
296
+ projectionOptions: ProjectionOptions
297
+ ) => {
298
+ if (!children) {
299
+ return;
300
+ }
301
+ for (let child of children) {
302
+ createDom(child, domNode, undefined, projectionOptions);
303
+ }
304
+ };
305
+
306
+ export let initPropertiesAndChildren = (
307
+ domNode: Node,
308
+ vnode: VNode,
309
+ projectionOptions: ProjectionOptions
310
+ ): void => {
311
+ addChildren(domNode, vnode.children, projectionOptions); // children before properties, needed for value property of <select>.
312
+ if (vnode.text) {
313
+ domNode.textContent = vnode.text;
314
+ }
315
+ setProperties(domNode, vnode.properties, projectionOptions);
316
+ if (vnode.properties && vnode.properties.afterCreate) {
317
+ vnode.properties.afterCreate.apply(vnode.properties.bind || vnode.properties, [
318
+ domNode as Element,
319
+ projectionOptions,
320
+ vnode.vnodeSelector,
321
+ vnode.properties,
322
+ vnode.children,
323
+ ]);
324
+ } else if (vnode.properties && vnode.properties.eventListeners) {
325
+ if (Array.isArray(vnode.properties.eventListeners)) {
326
+ vnode.properties.eventListeners.forEach((evListener: any) => {
327
+ domNode.addEventListener(evListener[0], evListener[1], evListener[2]);
328
+ });
329
+ } else {
330
+ throw new Error("eventListeners value must be an array");
331
+ }
332
+ }
333
+ };
334
+
335
+ export let createDom = (
336
+ vnode: VNode,
337
+ parentNode: Node,
338
+ insertBefore: Node | null | undefined,
339
+ projectionOptions: ProjectionOptions
340
+ ): void => {
341
+ let domNode: Node | undefined;
342
+ let start = 0;
343
+ let vnodeSelector = vnode.vnodeSelector;
344
+ let doc = parentNode.ownerDocument!;
345
+ if (vnodeSelector === "") {
346
+ if (vnode.domNode) {
347
+ vnode.domNode.nodeValue = vnode.text!;
348
+ } else {
349
+ domNode = vnode.domNode = doc.createTextNode(vnode.text!);
350
+ if (insertBefore !== undefined) {
351
+ parentNode.insertBefore(domNode, insertBefore);
352
+ } else {
353
+ parentNode.appendChild(domNode);
354
+ }
355
+ }
356
+ } else {
357
+ for (let i = 0; i <= vnodeSelector.length; ++i) {
358
+ let c = vnodeSelector.charAt(i);
359
+ if (i === vnodeSelector.length || c === "." || c === "#") {
360
+ let type = vnodeSelector.charAt(start - 1);
361
+ let found = vnodeSelector.slice(start, i);
362
+ if (type === ".") {
363
+ (domNode! as HTMLElement).classList.add(found);
364
+ } else if (type === "#") {
365
+ (domNode! as Element).id = found;
366
+ } else {
367
+ if (found === "svg") {
368
+ projectionOptions = extend(projectionOptions, {
369
+ namespace: NAMESPACE_SVG,
370
+ });
371
+ }
372
+ if (projectionOptions.namespace !== undefined) {
373
+ domNode = vnode.domNode = doc.createElementNS(projectionOptions.namespace, found);
374
+ } else {
375
+ domNode = vnode.domNode =
376
+ vnode.domNode ||
377
+ (vnode.properties?.is
378
+ ? doc.createElement(found, { is: vnode.properties.is })
379
+ : doc.createElement(found));
380
+ if (found === "input" && vnode.properties && vnode.properties.type !== undefined) {
381
+ // IE8 and older don't support setting input type after the DOM Node has been added to the document
382
+ (domNode as Element).setAttribute("type", vnode.properties.type);
383
+ }
384
+ }
385
+ if (insertBefore !== undefined) {
386
+ parentNode.insertBefore(domNode, insertBefore);
387
+ } else if (domNode.parentNode !== parentNode) {
388
+ parentNode.appendChild(domNode);
389
+ }
390
+ }
391
+ start = i + 1;
392
+ }
393
+ }
394
+ initPropertiesAndChildren(domNode!, vnode, projectionOptions);
395
+ }
396
+ };
397
+
398
+ let updateDom: (previous: VNode, vnode: VNode, projectionOptions: ProjectionOptions) => boolean;
399
+
400
+ /**
401
+ * Adds or removes classes from an Element
402
+ * @param domNode the element
403
+ * @param classes a string separated list of classes
404
+ * @param on true means add classes, false means remove
405
+ */
406
+ let toggleClasses = (domNode: HTMLElement, classes: string | null | undefined, on: boolean) => {
407
+ if (!classes) {
408
+ return;
409
+ }
410
+ classes.split(" ").forEach((classToToggle) => {
411
+ if (classToToggle) {
412
+ domNode.classList.toggle(classToToggle, on);
413
+ }
414
+ });
415
+ };
416
+
417
+ let updateProperties = (
418
+ domNode: Node,
419
+ previousProperties: VNodeProperties | undefined,
420
+ properties: VNodeProperties | undefined,
421
+ projectionOptions: ProjectionOptions
422
+ ) => {
423
+ if (!properties) {
424
+ return;
425
+ }
426
+ let propertiesUpdated = false;
427
+ let propNames = Object.keys(properties);
428
+ let propCount = propNames.length;
429
+ for (let i = 0; i < propCount; i++) {
430
+ let propName = propNames[i];
431
+ // assuming that properties will be nullified instead of missing is by design
432
+ let propValue = properties[propName];
433
+ let previousValue = previousProperties![propName];
434
+ if (propName === "class") {
435
+ if (previousValue !== propValue) {
436
+ toggleClasses(domNode as HTMLElement, previousValue, false);
437
+ toggleClasses(domNode as HTMLElement, propValue, true);
438
+ }
439
+ } else if (propName === "classes") {
440
+ let classList = (domNode as Element).classList;
441
+ let classNames = Object.keys(propValue);
442
+ let classNameCount = classNames.length;
443
+ for (let j = 0; j < classNameCount; j++) {
444
+ let className = classNames[j];
445
+ let on = !!propValue[className];
446
+ let previousOn = !!previousValue[className];
447
+ if (on === previousOn) {
448
+ continue;
449
+ }
450
+ propertiesUpdated = true;
451
+ if (on) {
452
+ classList.add(className);
453
+ } else {
454
+ classList.remove(className);
455
+ }
456
+ }
457
+ } else if (propName === "styles") {
458
+ let styleNames = Object.keys(propValue);
459
+ let styleCount = styleNames.length;
460
+ for (let j = 0; j < styleCount; j++) {
461
+ let styleName = styleNames[j];
462
+ let newStyleValue = propValue[styleName];
463
+ let oldStyleValue = previousValue[styleName];
464
+ if (newStyleValue === oldStyleValue) {
465
+ continue;
466
+ }
467
+ propertiesUpdated = true;
468
+ if (newStyleValue) {
469
+ checkStyleValue(newStyleValue);
470
+ projectionOptions.styleApplyer!(domNode as HTMLElement, styleName, newStyleValue);
471
+ } else {
472
+ projectionOptions.styleApplyer!(domNode as HTMLElement, styleName, "");
473
+ }
474
+ }
475
+ } else {
476
+ if (!propValue && typeof previousValue === "string") {
477
+ if (BOOLEAN_ATRRIBUTES.indexOf(propName) < 0) {
478
+ propValue = "";
479
+ }
480
+ }
481
+ if (propName === "value") {
482
+ // value can be manipulated by the user directly and using event.preventDefault() is not an option
483
+ let domValue = (domNode as any)[propName];
484
+
485
+ if (
486
+ domValue !== propValue && // The 'value' in the DOM tree !== newValue
487
+ ((domNode as any)["oninput-value"]
488
+ ? domValue === (domNode as any)["oninput-value"] // If the last reported value to 'oninput' does not match domValue, do nothing and wait for oninput
489
+ : propValue !== previousValue) // Only update the value if the vdom changed
490
+ ) {
491
+ // The edge cases are described in the tests
492
+ (domNode as any)[propName] = propValue; // Reset the value, even if the virtual DOM did not change
493
+ (domNode as any)["oninput-value"] = undefined;
494
+ } // else do not update the domNode, otherwise the cursor position would be changed
495
+ if (propValue !== previousValue) {
496
+ propertiesUpdated = true;
497
+ }
498
+ } else if(BOOLEAN_ATRRIBUTES.indexOf(propName) >= 0) {
499
+ if (typeof propValue === "string" || propValue === true) {
500
+ (domNode as Element).setAttribute(propName, "");
501
+ domNode[propName] = true;
502
+ } else {
503
+ (domNode as Element).removeAttribute(propName);
504
+ domNode[propName] = false;
505
+ }
506
+ propertiesUpdated = true;
507
+ } else if (propValue !== previousValue) {
508
+ let type = typeof propValue;
509
+ if (type !== "function" || !projectionOptions.eventHandlerInterceptor) {
510
+ // Function updates are expected to be handled by the EventHandlerInterceptor
511
+ if (projectionOptions.namespace === NAMESPACE_SVG) {
512
+ if (propName === "href") {
513
+ (domNode as Element).setAttributeNS(NAMESPACE_XLINK, propName, propValue);
514
+ } else {
515
+ // all SVG attributes are read-only in DOM, so...
516
+ (domNode as Element).setAttribute(propName, propValue);
517
+ }
518
+ } else if (type === "string" && propName !== "innerHTML") {
519
+ if (propName === "role" && propValue === "") {
520
+ (domNode as any).removeAttribute(propName);
521
+ } else {
522
+ (domNode as Element).setAttribute(propName, propValue);
523
+ }
524
+ } else if ((domNode as any)[propName] !== propValue) {
525
+ // Comparison is here for side-effects in Edge with scrollLeft and scrollTop
526
+ (domNode as any)[propName] = propValue;
527
+ }
528
+
529
+ propertiesUpdated = true;
530
+ }
531
+ }
532
+ }
533
+ }
534
+ return propertiesUpdated;
535
+ };
536
+
537
+ let updateChildren = (
538
+ vnode: VNode,
539
+ domNode: Node,
540
+ oldChildren: VNode[] | undefined,
541
+ newChildren: VNode[] | undefined,
542
+ projectionOptions: ProjectionOptions
543
+ ) => {
544
+ if (oldChildren === newChildren) {
545
+ return false;
546
+ }
547
+ oldChildren = oldChildren || emptyArray;
548
+ newChildren = newChildren || emptyArray;
549
+ let oldChildrenLength = oldChildren.length;
550
+ let newChildrenLength = newChildren.length;
551
+
552
+ let oldIndex = 0;
553
+ let newIndex = 0;
554
+ let i: number;
555
+ let textUpdated = false;
556
+ while (newIndex < newChildrenLength) {
557
+ let oldChild = oldIndex < oldChildrenLength ? oldChildren[oldIndex] : undefined;
558
+ let newChild = newChildren[newIndex];
559
+ if (oldChild !== undefined && same(oldChild, newChild)) {
560
+ textUpdated = updateDom(oldChild, newChild, projectionOptions) || textUpdated;
561
+ oldIndex++;
562
+ } else {
563
+ let findOldIndex = findIndexOfChild(oldChildren, newChild, oldIndex + 1);
564
+ if (findOldIndex >= 0) {
565
+ // Remove preceding missing children
566
+ for (i = oldIndex; i < findOldIndex; i++) {
567
+ nodeToRemove(oldChildren[i]);
568
+ checkDistinguishable(oldChildren, i, vnode, "removed");
569
+ }
570
+ textUpdated =
571
+ updateDom(oldChildren[findOldIndex], newChild, projectionOptions) || textUpdated;
572
+ oldIndex = findOldIndex + 1;
573
+ } else {
574
+ // New child
575
+ createDom(
576
+ newChild,
577
+ domNode,
578
+ oldIndex < oldChildrenLength ? oldChildren[oldIndex].domNode : undefined,
579
+ projectionOptions
580
+ );
581
+ nodeAdded(newChild);
582
+ checkDistinguishable(newChildren, newIndex, vnode, "added");
583
+ }
584
+ }
585
+ newIndex++;
586
+ }
587
+ if (oldChildrenLength > oldIndex) {
588
+ // Remove child fragments
589
+ for (i = oldIndex; i < oldChildrenLength; i++) {
590
+ nodeToRemove(oldChildren[i]);
591
+ checkDistinguishable(oldChildren, i, vnode, "removed");
592
+ }
593
+ }
594
+ return textUpdated;
595
+ };
596
+
597
+ updateDom = (previous, vnode, projectionOptions) => {
598
+ let domNode = previous.domNode!;
599
+ let textUpdated = false;
600
+ if (previous === vnode) {
601
+ return false; // By contract, VNode objects may not be modified anymore after passing them to maquette
602
+ }
603
+ let updated = false;
604
+ if (vnode.vnodeSelector === "") {
605
+ if (vnode.text !== previous.text) {
606
+ let newTextNode = domNode.ownerDocument!.createTextNode(vnode.text!);
607
+ domNode.parentNode!.replaceChild(newTextNode, domNode);
608
+ vnode.domNode = newTextNode;
609
+ textUpdated = true;
610
+ return textUpdated;
611
+ }
612
+ vnode.domNode = domNode;
613
+ } else {
614
+ if (vnode.vnodeSelector.lastIndexOf("svg", 0) === 0) {
615
+ // lastIndexOf(needle,0)===0 means StartsWith
616
+ projectionOptions = extend(projectionOptions, {
617
+ namespace: NAMESPACE_SVG,
618
+ });
619
+ }
620
+ if (previous.text !== vnode.text) {
621
+ updated = true;
622
+ if (vnode.text === undefined) {
623
+ domNode.removeChild(domNode.firstChild!); // the only textnode presumably
624
+ } else {
625
+ domNode.textContent = vnode.text;
626
+ }
627
+ }
628
+ vnode.domNode = domNode;
629
+ updated =
630
+ updateChildren(vnode, domNode, previous.children, vnode.children, projectionOptions) ||
631
+ updated;
632
+ updated =
633
+ updateProperties(domNode, previous.properties, vnode.properties, projectionOptions) ||
634
+ updated;
635
+ if (vnode.properties && vnode.properties.afterUpdate) {
636
+ vnode.properties.afterUpdate.apply(vnode.properties.bind || vnode.properties, [
637
+ <Element>domNode,
638
+ projectionOptions,
639
+ vnode.vnodeSelector,
640
+ vnode.properties,
641
+ vnode.children,
642
+ ]);
643
+ }
644
+ }
645
+ if (updated && vnode.properties && vnode.properties.updateAnimation) {
646
+ vnode.properties.updateAnimation(<Element>domNode, vnode.properties, previous.properties);
647
+ }
648
+ return textUpdated;
649
+ };
650
+
651
+ export let createProjection = (vnode: VNode, projectionOptions: ProjectionOptions): Projection => {
652
+ return {
653
+ getLastRender: () => vnode,
654
+ update: (updatedVnode: VNode) => {
655
+ if (vnode.vnodeSelector !== updatedVnode.vnodeSelector) {
656
+ throw new Error(
657
+ "The selector for the root VNode may not be changed. (consider using dom.merge and add one extra level to the virtual DOM)"
658
+ );
659
+ }
660
+ let previousVNode = vnode;
661
+ vnode = updatedVnode;
662
+ updateDom(previousVNode, updatedVnode, projectionOptions);
663
+ },
664
+ domNode: <Element>vnode.domNode,
665
+ };
666
+ };