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