closures 0.7.1 → 0.7.3

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/dist/closures.js CHANGED
@@ -1,759 +1,571 @@
1
- let EMPTY_OBJECT = {},
2
- REF_SINGLE = 1, // ref with a single dom node
3
- REF_ARRAY = 4, // ref with an array od nodes
4
- REF_PARENT = 8, // ref with a child ref
5
- SVG_NS = "http://www.w3.org/2000/svg",
6
- DOM_PROPS_DIRECTIVES = {
7
- selected: propDirective("selected"),
8
- checked: propDirective("checked"),
9
- value: propDirective("value"),
10
- innerHTML: propDirective("innerHTML"),
11
- },
1
+ let NIL = void 0,
2
+ noop = _ => {},
3
+ propDirective = prop => ({
4
+ mount(el, value) {
5
+ el[prop] = value;
6
+ },
7
+ patch(el, newValue, oldValue) {
8
+ if (newValue !== oldValue) el[prop] = newValue;
9
+ },
10
+ unmount(el) {
11
+ el[prop] = NIL;
12
+ }
13
+ }),
12
14
  DEFAULT_ENV = {
13
- isSvg: false,
14
- directives: DOM_PROPS_DIRECTIVES,
15
+ isSVG: false,
16
+ redraw: noop,
17
+ directives: {
18
+ selected: propDirective('selected'),
19
+ checked: propDirective('checked'),
20
+ value: propDirective('value'),
21
+ innerHTML: propDirective('innerHTML')
22
+ }
15
23
  },
16
24
  ON_REMOVES = [],
17
- MOUNTING = [],
18
- XLINK_NS = "http://www.w3.org/1999/xlink",
19
- NS_ATTRS = { show: XLINK_NS, actuate: XLINK_NS, href: XLINK_NS },
25
+ CLOSURE_TO_FN = new WeakMap(),
26
+ ON_CREATE_KEY = 'oncreate',
20
27
  NUM = 1,
28
+ PARENT_DOM_KEY = '$_REF',
29
+ EMPTY_OBJECT = {},
30
+ SVG_NS = 'http://www.w3.org/2000/svg',
31
+ XLINK_NS = 'http://www.w3.org/1999/xlink',
32
+ NS_ATTRS = { show: XLINK_NS, actuate: XLINK_NS, href: XLINK_NS },
33
+ REF_SINGLE = 1, // ref with single dom node
21
34
  VTYPE_ELEMENT = 1,
35
+ REF_ARRAY = 4, // ref with array of nodes
36
+ REF_PARENT = 8, // ref with a child ref
22
37
  VTYPE_FUNCTION = 2,
23
- VTYPE_COMPONENT = 4,
24
- CLOSURE_TO_CMP = new WeakMap(),
25
- CMP_TO_CLOSURE = new WeakMap(),
26
- CHILDS = new WeakMap(),
27
- ID = _ => (NUM++).toString(),
28
- noop = _ => {},
38
+ RETAIN_KEY = '=',
39
+ generateClosureId = _ => NUM++,
29
40
  isFn = x => typeof x === 'function',
30
41
  isStr = x => typeof x === 'string',
31
42
  isObj = x => x !== null && typeof x === 'object',
32
43
  isArr = x => Array.isArray(x),
33
- isEmpty = c => c === null || c === false || c === undefined || (isArr(c) && c.length === 0),
44
+ toJson = v => JSON.stringify(v),
45
+ isEmpty = c => c === null || c === false || c === NIL || (isArr(c) && c.length === 0) || (c && c._t === RETAIN_KEY),
34
46
  isNonEmptyArray = c => isArr(c) && c.length > 0,
35
- isLeaf = c => isStr(c) || typeof c === "number",
47
+ isLeaf = c => isStr(c) || typeof c === 'number',
36
48
  isElement = c => c && c.vtype === VTYPE_ELEMENT,
37
- isRenderFunction = c => c && c.vtype === VTYPE_FUNCTION,
38
- isComponent = c => c && c.vtype === VTYPE_COMPONENT,
39
- isValidComponentType = c => c && isFn(c.mount);
40
-
41
- export const m = h;
49
+ isRenderFunction = c => c && c.vtype === VTYPE_FUNCTION;
50
+
51
+ let getDomNode = (ref) => {
52
+ let type = ref.type;
53
+ if (type === REF_SINGLE) return ref.node;
54
+ if (type === REF_PARENT) return getDomNode(ref.childRef);
55
+ if (type === REF_ARRAY) return getDomNode(ref.children[0]);
56
+ throw Error('Unknown ref type ' + toJson(ref));
57
+ };
58
+
59
+ let getNextSibling = ref => {
60
+ let type = ref.type;
61
+ if (type === REF_SINGLE) return ref.node.nextSibling;
62
+ if (type === REF_PARENT) return getNextSibling(ref.childRef);
63
+ if (type === REF_ARRAY) return getNextSibling(ref.children[ref.children.length - 1]);
64
+ throw Error('Unknown ref type ' + toJson(ref));
65
+ };
66
+
67
+ let insertDom = (parent, ref, nextSibling) => {
68
+ let type = ref.type;
69
+ if (type === REF_SINGLE) parent.insertBefore(ref.node, nextSibling);
70
+ else if (type === REF_PARENT) insertDom(parent, ref.childRef, nextSibling);
71
+ else if (type === REF_ARRAY)
72
+ for (let i = 0; i < ref.children.length; i++)
73
+ insertDom(parent, ref.children[i], nextSibling);
74
+ else throw Error('Unknown ref type ' + toJson(ref));
75
+ };
76
+
77
+ let removeDom = (parent, ref) => {
78
+ let type = ref.type;
79
+ if (type === REF_SINGLE) parent.removeChild(ref.node);
80
+ else if (type === REF_PARENT) removeDom(parent, ref.childRef);
81
+ else if (type === REF_ARRAY)
82
+ for (let i = 0; i < ref.children.length; i++)
83
+ removeDom(parent, ref.children[i]);
84
+ else throw Error('Unknown ref type ' + toJson(ref));
85
+ };
86
+
87
+ let replaceDom = (parent, newRef, oldRef) => {
88
+ insertDom(parent, newRef, getDomNode(oldRef));
89
+ removeDom(parent, oldRef);
90
+ };
91
+
92
+ let setDomAttribute = (el, attr, value, isSVG) => {
93
+ if (attr === 'className') attr = 'class';
94
+ if (value === true) el.setAttribute(attr, '');
95
+ else if (value === false) el.removeAttribute(attr);
96
+ else (isSVG && NS_ATTRS[attr])
97
+ ? el.setAttributeNS(NS_ATTRS[attr], attr, value)
98
+ : el.setAttribute(attr, value);
99
+ };
100
+
101
+ let mountAttributes = (el, props, env) => {
102
+ for (let key in props) {
103
+ if (key === 'key' || key === 'children' || key === ON_CREATE_KEY || key in env.directives) continue;
104
+ else if (key.startsWith('on'))
105
+ el[key.toLowerCase()] = ev => { props[key](ev); !env.manualRedraw && env.redraw(); };
106
+ else setDomAttribute(el, key, props[key], env.isSVG);
107
+ }
108
+ };
42
109
 
43
- export const onRemove = f => ON_REMOVES.push(f);
110
+ let patchAttributes = (el, newProps, oldProps, env) => {
111
+ let key;
112
+ for (key in newProps) {
113
+ if (key === 'key' || key === 'children' || key === ON_CREATE_KEY || key in env.directives)
114
+ continue;
44
115
 
45
- export function h(__tag, ...children) {
46
- let props = children[0];
47
- if (props && isObj(props) && !isArr(props) && !(props.__tag || props.props))
48
- children.shift();
49
- else props = EMPTY_OBJECT;
116
+ let oldValue = oldProps[key],
117
+ newValue = newProps[key];
118
+
119
+ if (oldValue !== newValue)
120
+ key.startsWith('on')
121
+ ? el[key.toLowerCase()] = ev => { newValue(ev); !env.manualRedraw && env.redraw(); }
122
+ : setDomAttribute(el, key, newValue, env.isSVG);
123
+ }
50
124
 
51
- props =
52
- children.length > 1
53
- ? Object.assign({}, props, { children })
54
- : children.length === 1
55
- ? Object.assign({}, props, { children: children[0] })
56
- : props;
125
+ for (key in oldProps) {
126
+ if (key === 'key' || key === 'children' || key in newProps || key in env.directives)
127
+ continue;
57
128
 
58
- // parse tag
59
- if (isStr(__tag)) {
60
- let idx = __tag.indexOf('.');
61
- if (~idx) {
62
- let className = __tag.slice(idx + 1).replace(/\./g, ' ')
63
- props.class = props.class ? className + ' ' + String(props.class) : className;
64
- __tag = __tag.slice(0, idx);
65
- }
66
- __tag = __tag ?? 'div';
129
+ if (key.startsWith('on')) el[key.toLowerCase()] = NIL;
130
+ else el.removeAttribute(key);
67
131
  }
132
+ };
133
+
134
+ let mountDirectives = (el, props, env) => {
135
+ for (let key in props)
136
+ if (key in env.directives)
137
+ env.directives[key].mount(el, props[key]);
138
+ };
139
+
140
+ let patchDirectives = (el, newProps, oldProps, env) => {
141
+ let key;
142
+ for (key in newProps)
143
+ if (key in env.directives)
144
+ env.directives[key].patch(el, newProps[key], oldProps[key]);
145
+
146
+ for (key in oldProps)
147
+ if (key in env.directives && !(key in newProps))
148
+ env.directives[key].unmount(el, oldProps[key]);
149
+ };
68
150
 
69
- return jsx(__tag, props, props.key);
70
- }
151
+ let unmountDirectives = (el, props, env) => {
152
+ for (let key in props)
153
+ if (key in env.directives)
154
+ env.directives[key].unmount(el, props[key]);
155
+ };
71
156
 
72
- export function jsx(__tag, props, key) {
73
- if (key !== key) throw new Error("Invalid NaN key");
74
- let vtype =
75
- isStr(__tag)
76
- ? VTYPE_ELEMENT
77
- : isValidComponentType(__tag)
78
- ? VTYPE_COMPONENT
79
- : isFn(__tag)
80
- ? VTYPE_FUNCTION
81
- : undefined;
82
- if (vtype === undefined) throw new Error("Invalid VNode type");
83
- return {
84
- vtype,
85
- __tag,
86
- key,
87
- props,
88
- };
89
- }
157
+ let mount = (vnode, env, closureId, closure, onRemove = noop) => {
158
+ let baseRef = { closureId, closure, onRemove };
90
159
 
91
- export const Fragment = props => props.children;
160
+ if (isEmpty(vnode))
161
+ return { ...baseRef, type: REF_SINGLE, node: document.createComment('NULL') };
92
162
 
93
- export function render(vnode, parentDomNode, options = {}) {
94
- let rootRef = parentDomNode.$$PETIT_DOM_REF;
95
- let env = Object.assign({}, DEFAULT_ENV);
96
- Object.assign(env.directives, options.directives);
97
- if (rootRef == null) {
98
- let ref = mount(vnode, env);
99
- parentDomNode.$$PETIT_DOM_REF = { ref, vnode };
100
- parentDomNode.textContent = "";
101
- insertDom(parentDomNode, ref, null);
102
- } else {
103
- rootRef.ref = patchInPlace(
104
- parentDomNode,
105
- vnode,
106
- rootRef.vnode,
107
- rootRef.ref,
108
- env
109
- );
110
- rootRef.vnode = vnode;
111
- }
112
- }
163
+ if (isLeaf(vnode))
164
+ return { ...baseRef, type: REF_SINGLE, node: document.createTextNode(vnode) };
113
165
 
114
- // non exports
115
- class Renderer {
116
- constructor(props, env) {
117
- this.props = props;
118
- this._STATE_ = {
119
- env,
120
- vnode: null,
121
- parentDomNode: null,
122
- ref: mount(null)
123
- };
124
- this.render = this.render.bind(this);
125
- }
166
+ if (isElement(vnode)) {
167
+ let node,
168
+ { _t, props } = vnode;
126
169
 
127
- setProps(props) {
128
- this.oldProps = this.props;
129
- this.props = props;
130
- }
170
+ if (_t === 'svg' && !env.isSVG)
171
+ env = { ...env, isSVG: true };
131
172
 
132
- render(vnode) {
133
- let state = this._STATE_;
134
- let oldVNode = state.vnode;
135
- state.vnode = vnode;
136
- if (state.parentDomNode === null) {
137
- let parentNode = getParentNode(state.ref);
138
- if (parentNode === null) {
139
- state.ref = mount(vnode, state.env);
140
- return;
141
- } else {
142
- state.parentDomNode = parentNode;
143
- }
144
- }
145
- // here we are sure state.parentDOMNode is defined
146
- state.ref = patchInPlace(
147
- state.parentDomNode,
148
- vnode,
149
- oldVNode,
150
- state.ref,
151
- state.env
152
- );
153
- }
154
- }
155
-
156
- function mount(vnode, env = DEFAULT_ENV) {
157
- if (isEmpty(vnode)) {
158
- return {
159
- type: REF_SINGLE,
160
- node: document.createComment("NULL"),
161
- };
162
- } else if (isLeaf(vnode)) {
163
- return {
164
- type: REF_SINGLE,
165
- node: document.createTextNode(vnode),
166
- };
167
- } else if (isElement(vnode)) {
168
- let node;
169
- let { __tag, props } = vnode;
170
- if (__tag === "svg" && !env.isSvg) {
171
- env = Object.assign({}, env, { isSVG: true });
172
- }
173
- // TODO : {is} for custom elements
174
- if (!env.isSVG) {
175
- node = document.createElement(__tag);
176
- } else {
177
- node = document.createElementNS(SVG_NS, __tag);
178
- }
173
+ if (!env.isSVG) node = document.createElement(_t);
174
+ else node = document.createElementNS(SVG_NS, _t);
179
175
 
180
- // element lifecycle hooks
181
- if (isFn(props.oncreate)) props.oncreate(node);
176
+ isFn(props[ON_CREATE_KEY]) && props[ON_CREATE_KEY](node);
182
177
 
183
178
  mountAttributes(node, props, env);
184
- let childrenRef =
185
- props.children == null ? props.children : mount(props.children, env);
186
- /**
187
- * We need to insert content before setting interactive props
188
- * that rely on children been present (e.g select)
189
- */
190
- if (childrenRef != null) insertDom(node, childrenRef);
179
+
180
+ let children = props.children === NIL
181
+ ? props.children
182
+ : mount(props.children, env);
183
+
184
+ if (children !== NIL) insertDom(node, children);
191
185
  mountDirectives(node, props, env);
186
+
192
187
  return {
188
+ ...baseRef,
193
189
  type: REF_SINGLE,
194
190
  node,
195
- children: childrenRef,
196
- };
197
- } else if (isNonEmptyArray(vnode)) {
191
+ children
192
+ }
193
+ }
194
+
195
+ if (isNonEmptyArray(vnode)) {
196
+ let i = 0, children = [];
197
+ for (; i < vnode.length; i++)
198
+ children.push(mount(vnode[i], env));
199
+
198
200
  return {
201
+ ...baseRef,
199
202
  type: REF_ARRAY,
200
- children: vnode.map((child) => mount(child, env)),
203
+ children
201
204
  };
202
- } else if (isRenderFunction(vnode)) {
203
- let childVNode = vnode.__tag(vnode.props);
205
+ }
204
206
 
205
- if (isFn(childVNode)) {
206
- let cmp,
207
- view = childVNode,
208
- id = ID(),
209
- cmps = CLOSURE_TO_CMP.get(vnode.__tag) ?? {};
210
-
211
- vnode.id = id;
212
- cmp = toClosureCmp(vnode, view)
213
-
214
- cmps[id] = cmp;
215
- CLOSURE_TO_CMP.set(vnode.__tag, cmps);
216
- CMP_TO_CLOSURE.set(cmp, vnode.__tag);
217
- return mount(cmp);
218
- }
207
+ if (isRenderFunction(vnode)) {
208
+ let childVnode = vnode._t(vnode.props); // if this is a closure component, this will be "oninit"
219
209
 
220
- let childRef = mount(childVNode, env);
221
- return {
222
- type: REF_PARENT,
223
- childRef,
224
- childState: childVNode,
225
- };
226
- } else if (isComponent(vnode)) {
227
- let renderer = new Renderer(vnode.props, env);
228
-
229
- let parentCmp;
230
- if (parentCmp = MOUNTING[MOUNTING.length - 1]) {
231
- let child_closure = CMP_TO_CLOSURE.get(vnode),
232
- childs = CHILDS.get(parentCmp) || [],
233
- idx = childs.length;
234
-
235
- // attach method to remove self from parent
236
- vnode.__tag.removeFromParent = _ => childs.splice(idx, 1);
237
-
238
- childs.push({
239
- id: vnode.id,
240
- vtype: VTYPE_FUNCTION,
241
- __tag: child_closure
242
- });
243
-
244
- CHILDS.set(parentCmp, childs);
210
+ if (isFn(childVnode)) {
211
+ // closure component
212
+ let id = generateClosureId(),
213
+ fnMap = CLOSURE_TO_FN.get(vnode._t) || new Map(),
214
+ onRemove = ON_REMOVES.pop() || noop;
215
+
216
+ fnMap.set(id, childVnode); // save renderFn
217
+ CLOSURE_TO_FN.set(vnode._t, fnMap); // closure -> Map(id -> renderFn)
218
+
219
+ let closure = vnode._t;
220
+ vnode._t = childVnode;
221
+ return mount(vnode, env, id, closure, onRemove);
245
222
  }
246
223
 
247
- vnode.__tag.mount(renderer);
248
224
  return {
225
+ ...baseRef,
249
226
  type: REF_PARENT,
250
- childRef: renderer._STATE_.ref,
251
- childState: renderer,
227
+ childRef: mount(childVnode, env),
228
+ childState: childVnode
252
229
  };
253
- } else if (vnode instanceof Node) {
254
- return {
255
- type: REF_SINGLE,
256
- node: vnode,
257
- };
258
- }
259
- if (vnode === undefined) {
260
- throw new Error("mount: vnode is undefined!");
261
230
  }
262
231
 
263
- throw new Error("mount: Invalid Vnode!");
264
- }
265
-
266
- function patch(
267
- parentDomNode,
268
- newVNode,
269
- oldVNode,
270
- ref,
271
- env = DEFAULT_ENV
272
- ) {
273
- if (isObj(oldVNode) && isObj(newVNode))
274
- newVNode.id = oldVNode.id;
275
-
276
- if (oldVNode === newVNode) {
277
- return ref;
278
- } else if (isEmpty(newVNode) && isEmpty(oldVNode)) {
279
- return ref;
280
- } else if (isLeaf(newVNode) && isLeaf(oldVNode)) {
281
- ref.node.nodeValue = newVNode;
282
- return ref;
283
- } else if (
284
- isElement(newVNode) &&
285
- isElement(oldVNode) &&
286
- newVNode.__tag === oldVNode.__tag
287
- ) {
288
- if (newVNode.__tag === "svg" && !env.isSvg) {
289
- env = Object.assign({}, env, { isSVG: true });
290
- }
291
- patchAttributes(ref.node, newVNode.props, oldVNode.props, env);
292
- let oldChildren = oldVNode.props.children;
293
- let newChildren = newVNode.props.children;
294
- if (oldChildren == null) {
295
- if (newChildren != null) {
296
- ref.children = mount(newChildren, env);
297
- insertDom(ref.node, ref.children);
298
- }
299
- } else {
300
- if (newChildren == null) {
301
- ref.node.textContent = "";
302
- unmount(oldChildren, ref.children, env);
303
- ref.children = null;
304
- } else {
305
- ref.children = patchInPlace(
306
- ref.node,
307
- newChildren,
308
- oldChildren,
309
- ref.children,
310
- env
311
- );
312
- }
313
- }
314
- patchDirectives(ref.node, newVNode.props, oldVNode.props, env);
315
- return ref;
316
- } else if (isNonEmptyArray(newVNode) && isNonEmptyArray(oldVNode)) {
317
- patchChildren(parentDomNode, newVNode, oldVNode, ref, env);
318
- return ref;
319
- } else if (
320
- isRenderFunction(newVNode) &&
321
- isRenderFunction(oldVNode) &&
322
- newVNode.__tag === oldVNode.__tag
323
- ) {
324
- let renderFn = newVNode.__tag;
325
- let shouldUpdate =
326
- renderFn.shouldUpdate != null
327
- ? renderFn.shouldUpdate(oldVNode.props, newVNode.props)
328
- : defaultShouldUpdate(oldVNode.props, newVNode.props);
329
- if (shouldUpdate) {
330
- let cmp,
331
- id = oldVNode.id,
332
- cmps = CLOSURE_TO_CMP.get(renderFn);
333
-
334
- if (cmps && id && (cmp = cmps[id])) {
335
- return patch(
336
- parentDomNode,
337
- { ...cmp, props: newVNode.props },
338
- cmp,
339
- ref
340
- );
341
- }
232
+ if (vnode instanceof Node)
233
+ return { ...baseRef, type: REF_SINGLE, node: vnode };
234
+
235
+ if (vnode === NIL)
236
+ throw Error('mount: vnode is undefined');
342
237
 
343
- let childVNode = renderFn(newVNode.props);
344
- let childRef = patch(
345
- parentDomNode,
346
- childVNode,
347
- ref.childState,
348
- ref.childRef,
349
- env
350
- );
351
- // We need to return a new ref in order for parent patches to
352
- // properly replace changing DOM nodes
353
- if (childRef !== ref.childRef) {
354
- return {
355
- type: REF_PARENT,
356
- childRef,
357
- childState: childVNode,
358
- };
359
- } else {
360
- ref.childState = childVNode;
361
- return ref;
362
- }
363
- } else {
364
- return ref;
365
- }
366
- } else if (
367
- isComponent(newVNode) &&
368
- isComponent(oldVNode) &&
369
- newVNode.__tag === oldVNode.__tag
370
- ) {
371
- let renderer = ref.childState;
372
- let state = renderer._STATE_;
373
- state.env = env;
374
- state.parentNode = parentDomNode;
375
- renderer.setProps(newVNode.props);
376
- newVNode.__tag.patch(renderer);
377
- if (ref.childRef !== state.ref) {
378
- return {
379
- type: REF_PARENT,
380
- childRef: state.ref,
381
- childState: renderer,
382
- };
383
- } else {
384
- return ref;
385
- }
386
- } else if (newVNode instanceof Node && oldVNode instanceof Node) {
387
- ref.node = newVNode;
388
- return ref;
389
- } else {
390
- return mount(newVNode, env);
391
- }
392
- }
238
+ throw Error('mount: Invalid vnode');
239
+ };
393
240
 
394
- /**
395
- * Execute any compoenent specific unmount code
396
- */
397
- function unmount(vnode, ref, env) {
398
- // if (vnode instanceof Node || isEmpty(vnode) || isLeaf(vnode)) return;
241
+ let unmount = (vnode, ref, env) => {
399
242
  if (isElement(vnode)) {
400
243
  unmountDirectives(ref.node, vnode.props, env);
401
- if (vnode.props.children != null)
244
+ if (vnode.props.children !== NIL)
402
245
  unmount(vnode.props.children, ref.children, env);
403
246
  } else if (isNonEmptyArray(vnode)) {
404
- vnode.forEach((childVNode, index) =>
405
- unmount(childVNode, ref.children[index], env)
406
- );
247
+ for (let i = 0; i < vnode.length; i++)
248
+ unmount(vnode[i], ref.children[i], env);
407
249
  } else if (isRenderFunction(vnode)) {
408
- let cmp, cmps = CLOSURE_TO_CMP.get(vnode.__tag);
409
- if (cmps && vnode.id && (cmp = cmps[vnode.id])) {
410
- delete cmps[vnode.id];
411
- CMP_TO_CLOSURE.delete(cmp);
412
- return unmount(cmp, ref, env);
250
+ let closure = ref.closure,
251
+ closureId = ref.closureId,
252
+ onRemove = ref.onRemove,
253
+ fns = CLOSURE_TO_FN.get(closure);
254
+
255
+ if (fns && closureId && fns.get(closureId)) {
256
+ fns.delete(closureId);
257
+ !fns.size && CLOSURE_TO_FN.delete(closure);
258
+ onRemove();
413
259
  }
414
260
 
415
261
  unmount(ref.childState, ref.childRef, env);
416
- } else if (isComponent(vnode)) {
417
- vnode.__tag.unmount(ref.childState);
418
262
  }
419
- }
263
+ };
264
+
265
+ let patchInPlace = (parentDomNode, newVnode, oldVnode, ref, env) => {
266
+ let newRef = patch(parentDomNode, newVnode, oldVnode, ref, env);
420
267
 
421
- function patchInPlace(parentDomNode, newVNode, oldVNode, ref, env) {
422
- let newRef = patch(parentDomNode, newVNode, oldVNode, ref, env);
423
268
  if (newRef !== ref) {
424
269
  replaceDom(parentDomNode, newRef, ref);
425
- unmount(oldVNode, ref, env);
270
+ unmount(oldVnode, ref, env);
426
271
  }
272
+
427
273
  return newRef;
428
- }
429
-
430
- function patchChildren(parentDomNode, newChildren, oldchildren, ref, env) {
431
- // We need to retreive the next sibling before the old children
432
- // get eventually removed from the current DOM document
433
- let nextNode = getNextSibling(ref);
434
- let children = Array(newChildren.length);
435
- let refChildren = ref.children;
436
- let newStart = 0,
274
+ };
275
+
276
+ let patchChildren = (parentDomNode, newChildren, oldChildren, ref, env) => {
277
+ // we need to retrieve the next sibling before the old children get removed from the DOM
278
+ let i, idx, oldVnode, newVnode, oldRef, newRef, refMap, beforeNode,
279
+ nextNode = getNextSibling(ref),
280
+ children = Array(newChildren.length),
281
+ refChildren = ref.children,
282
+ newStart = 0,
437
283
  oldStart = 0,
438
284
  newEnd = newChildren.length - 1,
439
- oldEnd = oldchildren.length - 1;
440
- let oldVNode, newVNode, oldRef, newRef, refMap;
441
-
285
+ oldEnd = oldChildren.length - 1;
286
+
442
287
  while (newStart <= newEnd && oldStart <= oldEnd) {
443
- if (refChildren[oldStart] === null) {
288
+ if (refChildren[oldStart] === NIL) {
444
289
  oldStart++;
445
290
  continue;
446
291
  }
447
- if (refChildren[oldEnd] === null) {
292
+
293
+ if (refChildren[oldEnd] === NIL) {
448
294
  oldEnd--;
449
295
  continue;
450
296
  }
451
-
452
- oldVNode = oldchildren[oldStart];
453
- newVNode = newChildren[newStart];
454
- if (newVNode.key === oldVNode.key) {
297
+
298
+ oldVnode = oldChildren[oldStart];
299
+ newVnode = newChildren[newStart];
300
+
301
+ if (oldVnode && newVnode && newVnode.key === oldVnode.key) {
455
302
  oldRef = refChildren[oldStart];
456
303
  newRef = children[newStart] = patchInPlace(
457
304
  parentDomNode,
458
- newVNode,
459
- oldVNode,
305
+ newVnode,
306
+ oldVnode,
460
307
  oldRef,
461
308
  env
462
309
  );
310
+
463
311
  newStart++;
464
312
  oldStart++;
465
313
  continue;
466
314
  }
467
-
468
- oldVNode = oldchildren[oldEnd];
469
- newVNode = newChildren[newEnd];
470
- if (newVNode.key === oldVNode.key) {
315
+
316
+ oldVnode = oldChildren[oldEnd];
317
+ newVnode = newChildren[newEnd];
318
+
319
+ if (oldVnode && newVnode && newVnode.key === oldVnode.key) {
471
320
  oldRef = refChildren[oldEnd];
472
321
  newRef = children[newEnd] = patchInPlace(
473
322
  parentDomNode,
474
- newVNode,
475
- oldVNode,
323
+ newVnode,
324
+ oldVnode,
476
325
  oldRef,
477
326
  env
478
327
  );
328
+
479
329
  newEnd--;
480
330
  oldEnd--;
481
331
  continue;
482
332
  }
483
-
484
- if (refMap == null) {
485
- refMap = {};
486
- for (let i = oldStart; i <= oldEnd; i++) {
487
- oldVNode = oldchildren[i];
488
- if (oldVNode.key != null) {
489
- refMap[oldVNode.key] = i;
490
- }
333
+
334
+ if (refMap === NIL)
335
+ for (i = oldStart, refMap = {}; i <= oldEnd; i++) {
336
+ oldVnode = oldChildren[i];
337
+ if (oldVnode && oldVnode.key !== NIL)
338
+ refMap[oldVnode.key] = i;
491
339
  }
492
- }
340
+
341
+ newVnode = newChildren[newStart];
342
+ idx = newVnode && newVnode.key !== NIL ? refMap[newVnode.key] : NIL;
493
343
 
494
- newVNode = newChildren[newStart];
495
- let idx = newVNode.key != null ? refMap[newVNode.key] : null;
496
- if (idx != null) {
497
- oldVNode = oldchildren[idx];
344
+ if (idx !== NIL) {
345
+ oldVnode = oldChildren[idx];
498
346
  oldRef = refChildren[idx];
499
347
  newRef = children[newStart] = patch(
500
348
  parentDomNode,
501
- newVNode,
502
- oldVNode,
349
+ newVnode,
350
+ oldVnode,
503
351
  oldRef,
504
352
  env
505
353
  );
354
+
506
355
  insertDom(parentDomNode, newRef, getDomNode(refChildren[oldStart]));
356
+
507
357
  if (newRef !== oldRef) {
508
358
  removeDom(parentDomNode, oldRef);
509
- unmount(oldVNode, oldRef, env);
359
+ unmount(oldVnode, oldRef, env);
510
360
  }
511
- refChildren[idx] = null;
361
+
362
+ refChildren[idx] = NIL;
512
363
  } else {
513
- newRef = children[newStart] = mount(newVNode, env);
364
+ newRef = children[newStart] = mount(newVnode, env);
514
365
  insertDom(parentDomNode, newRef, getDomNode(refChildren[oldStart]));
515
366
  }
367
+
516
368
  newStart++;
517
369
  }
518
-
519
- let beforeNode =
520
- newEnd < newChildren.length - 1
521
- ? getDomNode(children[newEnd + 1])
522
- : nextNode;
370
+
371
+ beforeNode = newEnd < newChildren.length - 1
372
+ ? getDomNode(children[newEnd + 1])
373
+ : nextNode;
374
+
523
375
  while (newStart <= newEnd) {
524
- let newRef = mount(newChildren[newStart], env);
376
+ newRef = mount(newChildren[newStart], env);
525
377
  children[newStart] = newRef;
526
378
  insertDom(parentDomNode, newRef, beforeNode);
527
379
  newStart++;
528
380
  }
381
+
529
382
  while (oldStart <= oldEnd) {
530
383
  oldRef = refChildren[oldStart];
531
- if (oldRef != null) {
384
+
385
+ if (oldRef !== NIL) {
532
386
  removeDom(parentDomNode, oldRef);
533
- unmount(oldchildren[oldStart], oldRef, env);
387
+ unmount(oldChildren[oldStart], oldRef, env);
534
388
  }
389
+
535
390
  oldStart++;
536
391
  }
392
+
537
393
  ref.children = children;
538
- }
394
+ };
539
395
 
540
- function defaultShouldUpdate(p1, p2) {
541
- if (p1 === p2) return false;
542
- for (let key in p2) {
543
- if (p1[key] !== p2[key]) return true;
544
- }
545
- return false;
546
- }
396
+ let patch = (parentDomNode, newVnode, oldVnode, ref, env = { ...DEFAULT_ENV }) => {
397
+ if (newVnode && newVnode._t === RETAIN_KEY) return ref;
547
398
 
548
- function propDirective(prop) {
549
- return {
550
- mount(element, value) {``
551
- element[prop] = value;
552
- },
553
- patch(element, newValue, oldValue) {
554
- if (newValue !== oldValue) {
555
- element[prop] = newValue;
556
- }
557
- },
558
- unmount(element, _) {
559
- element[prop] = null;
560
- },
561
- };
562
- }
399
+ let closure, closureId;
563
400
 
564
- function getDomNode(ref) {
565
- if (ref.type === REF_SINGLE) {
566
- return ref.node;
567
- } else if (ref.type === REF_ARRAY) {
568
- return getDomNode(ref.children[0]);
569
- } else if (ref.type === REF_PARENT) {
570
- return getDomNode(ref.childRef);
401
+ if (isObj(ref)) {
402
+ closure = ref.closure;
403
+ closureId = ref.closureId;
571
404
  }
572
- throw new Error("Unkown ref type " + JSON.stringify(ref));
573
- }
574
405
 
575
- function getParentNode(ref) {
576
- if (ref.type === REF_SINGLE) {
577
- return ref.node.parentNode;
578
- } else if (ref.type === REF_ARRAY) {
579
- return getParentNode(ref.children[0]);
580
- } else if (ref.type === REF_PARENT) {
581
- return getParentNode(ref.childRef);
582
- }
583
- throw new Error("Unkown ref type " + ref);
584
- }
406
+ if (isRenderFunction(newVnode) && isRenderFunction(oldVnode) && (newVnode._t === oldVnode._t || (closure && closureId))) {
407
+ // x is either a renderFn or a closure
408
+ let x = newVnode._t,
409
+ fn,
410
+ fns = CLOSURE_TO_FN.get(closure);
585
411
 
586
- function getNextSibling(ref) {
587
- if (ref.type === REF_SINGLE) {
588
- return ref.node.nextSibling;
589
- } else if (ref.type === REF_ARRAY) {
590
- return getNextSibling(ref.children[ref.children.length - 1]);
591
- } else if (ref.type === REF_PARENT) {
592
- return getNextSibling(ref.childRef);
593
- }
594
- throw new Error("Unkown ref type " + JSON.stringify(ref));
595
- }
412
+ if (fns && closureId && (fn = fns.get(closureId)))
413
+ x = fn;
596
414
 
597
- function insertDom(parent, ref, nextSibling) {
598
- if (ref.type === REF_SINGLE) {
599
- parent.insertBefore(ref.node, nextSibling);
600
- } else if (ref.type === REF_ARRAY) {
601
- ref.children.forEach((ch) => {
602
- insertDom(parent, ch, nextSibling);
603
- });
604
- } else if (ref.type === REF_PARENT) {
605
- insertDom(parent, ref.childRef, nextSibling);
606
- } else {
607
- throw new Error("Unkown ref type " + JSON.stringify(ref));
608
- }
609
- }
415
+ let childVnode = x(newVnode.props),
416
+ childRef = patch(
417
+ parentDomNode,
418
+ childVnode,
419
+ ref.childState,
420
+ ref.childRef,
421
+ env
422
+ );
610
423
 
611
- function removeDom(parent, ref) {
612
- if (ref.type === REF_SINGLE) {
613
- parent.removeChild(ref.node);
614
- } else if (ref.type === REF_ARRAY) {
615
- ref.children.forEach((ch) => {
616
- removeDom(parent, ch);
617
- });
618
- } else if (ref.type === REF_PARENT) {
619
- removeDom(parent, ref.childRef);
620
- } else {
621
- throw new Error("Unkown ref type " + ref);
424
+ // we need to return a new ref in order for parent patches to properly replace changing DOM nodes
425
+ if (childRef !== ref.childRef)
426
+ return {
427
+ type: REF_PARENT,
428
+ childRef,
429
+ childState: childVnode
430
+ };
431
+ else {
432
+ ref.childState = childVnode;
433
+ return ref;
434
+ }
622
435
  }
623
- }
624
436
 
625
- function replaceDom(parent, newRef, oldRef) {
626
- insertDom(parent, newRef, getDomNode(oldRef));
627
- removeDom(parent, oldRef);
628
- }
437
+ if (oldVnode === newVnode || (isEmpty(newVnode) && isEmpty(oldVnode)))
438
+ return ref;
629
439
 
630
- function mountDirectives(domElement, props, env) {
631
- for (let key in props) {
632
- if (key in env.directives) {
633
- env.directives[key].mount(domElement, props[key]);
634
- }
440
+ if (isLeaf(newVnode) && isLeaf(oldVnode)) {
441
+ // leafs just need to have node value updated
442
+ ref.node.nodeValue = newVnode;
443
+ return ref;
635
444
  }
636
- }
637
445
 
638
- function patchDirectives(domElement, newProps, oldProps, env) {
639
- for (let key in newProps) {
640
- if (key in env.directives) {
641
- env.directives[key].patch(domElement, newProps[key], oldProps[key]);
642
- }
643
- }
644
- for (let key in oldProps) {
645
- if (key in env.directives && !(key in newProps)) {
646
- env.directives[key].unmount(domElement, oldProps[key]);
647
- }
648
- }
649
- }
446
+ if (isElement(newVnode) && isElement(oldVnode) && newVnode._t === oldVnode._t) {
447
+ // make sure env has isSVG flag set to true
448
+ if (newVnode._t === 'svg' && !env.isSVG)
449
+ env = { ...env, isSVG: true };
650
450
 
651
- function unmountDirectives(domElement, props, env) {
652
- for (let key in props) {
653
- if (key in env.directives) {
654
- env.directives[key].unmount(domElement, props[key]);
655
- }
656
- }
657
- }
451
+ // update the node DOM attributes with the new props
452
+ patchAttributes(ref.node, newVnode.props, oldVnode.props, env);
658
453
 
659
- function mountAttributes(domElement, props, env) {
660
- for (var key in props) {
661
- if (key === "key" || key === "children" || key in env.directives) continue;
662
- if (key.startsWith("on")) {
663
- let cmp = MOUNTING[MOUNTING.length - 1];
664
- domElement[key.toLowerCase()] = cmp ? cmp.__tag.event(props[key]) : props[key];
665
- } else {
666
- setDOMAttribute(domElement, key, props[key], env.isSVG);
667
- }
668
- }
669
- }
454
+ let oldChildren = oldVnode.props.children,
455
+ newChildren = newVnode.props.children;
670
456
 
671
- function patchAttributes(domElement, newProps, oldProps, env) {
672
- for (var key in newProps) {
673
- if (key === "key" || key === "children" || key in env.directives) continue;
674
- var oldValue = oldProps[key];
675
- var newValue = newProps[key];
676
- if (oldValue !== newValue) {
677
- if (key.startsWith("on")) {
678
- let cmp = MOUNTING[MOUNTING.length - 1];
679
- domElement[key.toLowerCase()] = cmp ? cmp.__tag.event(newValue) : newValue;
457
+ if (oldChildren === NIL) {
458
+ if (newChildren !== NIL) {
459
+ ref.children = mount(newChildren, env);
460
+ insertDom(ref.node, ref.children);
461
+ }
462
+ } else {
463
+ if (newChildren === NIL) {
464
+ ref.node.textContent = '';
465
+ unmount(oldChildren, ref.children, env);
466
+ ref.children = NIL;
680
467
  } else {
681
- setDOMAttribute(domElement, key, newValue, env.isSVG);
468
+ ref.children = patchInPlace(
469
+ ref.node,
470
+ newChildren,
471
+ oldChildren,
472
+ ref.children,
473
+ env
474
+ );
682
475
  }
683
476
  }
477
+
478
+ patchDirectives(ref.node, newVnode.props, oldVnode.props, env);
479
+ return ref;
684
480
  }
685
- for (key in oldProps) {
686
- if (
687
- key === "key" ||
688
- key === "children" ||
689
- key in env.directives ||
690
- key in newProps
691
- )
692
- continue;
693
- if (key.startsWith("on")) {
694
- domElement[key.toLowerCase()] = null;
695
- } else {
696
- domElement.removeAttribute(key);
697
- }
481
+
482
+ if (isNonEmptyArray(newVnode) && isNonEmptyArray(oldVnode)) {
483
+ patchChildren(parentDomNode, newVnode, oldVnode, ref, env);
484
+ return ref;
698
485
  }
699
- }
700
486
 
701
- function setDOMAttribute(el, attr, value, isSVG) {
702
- if (value === true) {
703
- el.setAttribute(attr, "");
704
- } else if (value === false) {
705
- el.removeAttribute(attr);
706
- } else {
707
- var namespace = isSVG ? NS_ATTRS[attr] : undefined;
708
- if (namespace !== undefined) {
709
- el.setAttributeNS(namespace, attr, value);
710
- } else {
711
- el.setAttribute(attr, value);
712
- }
487
+ if (newVnode instanceof Node && oldVnode instanceof Node) {
488
+ ref.node = newVnode;
489
+ return ref;
713
490
  }
714
- }
715
491
 
716
- function toClosureCmp(vnode, view) {
717
- let onRemove = ON_REMOVES.pop() ?? noop,
718
- cmp = {
719
- key: vnode.key,
720
- id: vnode.id,
721
- vtype: VTYPE_COMPONENT,
722
- props: vnode.props || {},
723
- __tag: {
724
- view,
725
- removeFromParent: noop,
726
- event: noop,
727
- mount: render,
728
- patch: render,
729
- unmount: _ => {
730
- onRemove();
731
- let childs;
732
- if (childs = CHILDS.get(cmp)) {
733
- for (let i = 0; i < childs.length; i++) {
734
- unmount(childs[i], EMPTY_OBJECT);
735
- }
736
-
737
- CHILDS.delete(cmp);
738
- }
739
-
740
- cmp.__tag.removeFromParent();
741
- }
742
- }
743
- };
744
-
745
- function render(ctx) {
746
- cmp.__tag.event = fn => ev => {
747
- fn(ev);
748
- MOUNTING.push(cmp);
749
- ctx.render(view({ ...ctx.props }));
750
- MOUNTING.pop();
751
- };
492
+ return mount(newVnode, env);
493
+ };
494
+
495
+ export function h(_t, ...children) {
496
+ let idx, props = children[0];
497
+ if (props && isObj(props) && !isArr(props) && !(props._t || props.props))
498
+ children.shift();
499
+ else props = EMPTY_OBJECT;
752
500
 
753
- MOUNTING.push(cmp);
754
- ctx.render(view({ ...ctx.props }));
755
- MOUNTING.pop();
501
+ props =
502
+ children.length > 1
503
+ ? { ...props, children }
504
+ : children.length === 1
505
+ ? { ...props, children: children[0] }
506
+ : props;
507
+
508
+ // inline class parsing
509
+ if (isStr(_t) && ~(idx = _t.indexOf('.'))) {
510
+ let className = _t.slice(idx + 1).replace(/\./g, ' ')
511
+ props.class = props.class ? className + ' ' + String(props.class) : className;
512
+ _t = _t.slice(0, idx);
756
513
  }
757
514
 
758
- return cmp;
515
+ if (props.key !== props.key) throw new Error("Invalid NaN key");
516
+
517
+ let vtype =
518
+ isStr(_t)
519
+ ? VTYPE_ELEMENT
520
+ : _t && isFn(_t.mount)
521
+ ? VTYPE_COMPONENT
522
+ : isFn(_t)
523
+ ? VTYPE_FUNCTION
524
+ : NIL;
525
+
526
+ if (vtype === NIL) throw new Error("Invalid VNode type");
527
+
528
+ // returns a vnode
529
+ return {
530
+ vtype, // (number) VTYPE_ELEMENT | VTYPE_COMPONENT | VTYPE_FUNCTION
531
+ _t, // (string | object | function)
532
+ key: props.key, // string
533
+ props // object
534
+ };
759
535
  }
536
+
537
+ h.retain = _ => h(RETAIN_KEY);
538
+
539
+ export const m = h;
540
+
541
+ export const Fragment = props => props.children;
542
+
543
+ export const onRemove = fn => ON_REMOVES.push(fn);
544
+
545
+ export function app(vnode, parentDomNode, opts = {}) {
546
+ let ref,
547
+ env = { ...DEFAULT_ENV, manualRedraw: opts.manualRedraw },
548
+ rootRef = parentDomNode[PARENT_DOM_KEY];
549
+
550
+ env.directives = { ...env.directives, ...(opts.directives || {}) };
551
+
552
+ if (rootRef !== NIL)
553
+ throw Error('App already mounted on this node');
554
+
555
+ ref = mount(vnode, env);
556
+ rootRef = parentDomNode[PARENT_DOM_KEY] = { ref, vnode };
557
+ parentDomNode.textContent = '';
558
+ insertDom(parentDomNode, ref, NIL);
559
+
560
+ return env.redraw = (newVnode = vnode) => {
561
+ rootRef.ref = patchInPlace(
562
+ parentDomNode,
563
+ newVnode,
564
+ rootRef.vnode,
565
+ rootRef.ref,
566
+ env
567
+ );
568
+
569
+ rootRef.vnode = newVnode;
570
+ };
571
+ }