closures 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
package/dist/closures.cjs CHANGED
@@ -1,766 +1,576 @@
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
- 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
- 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
- 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
- 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
- const Fragment = props => props.children;
160
+ if (isEmpty(vnode))
161
+ return { ...baseRef, type: REF_SINGLE, node: document.createComment('NULL') };
92
162
 
93
- 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
+ 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
  }
760
-
536
+
537
+ h.retain = _ => h(RETAIN_KEY);
538
+
539
+ const m = h;
540
+
541
+ const Fragment = props => props.children;
542
+
543
+ const onRemove = fn => ON_REMOVES.push(fn);
544
+
545
+ 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
+ }
761
572
  exports.Fragment = Fragment;
573
+ exports.app = app;
762
574
  exports.h = h;
763
- exports.jsx = jsx;
764
575
  exports.m = m;
765
- exports.onRemove = onRemove;
766
- exports.render = render;
576
+ exports.onRemove = onRemove;