closures 0.7.1 → 0.7.2

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.cjs CHANGED
@@ -1,766 +1,597 @@
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 = '$$PETIT_DOM_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
+ };
42
91
 
43
- const onRemove = f => ON_REMOVES.push(f);
92
+ let setDomAttribute = (el, attr, value, isSVG) => {
93
+ if (value === true) el.setAttribute(attr, '');
94
+ else if (value === false) el.removeAttribute(attr);
95
+ else (isSVG && NS_ATTRS[attr])
96
+ ? el.setAttributeNS(NS_ATTRS[attr], attr, value)
97
+ : el.setAttribute(attr, value);
98
+ };
44
99
 
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;
100
+ let mountAttributes = (el, props, env) => {
101
+ for (let key in props) {
102
+ if (key === 'key' || key === 'children' || key === ON_CREATE_KEY || key in env.directives) continue;
103
+ else if (key.startsWith('on'))
104
+ el[key.toLowerCase()] = ev => { props[key](ev); !env.manualRedraw && env.redraw(); };
105
+ else setDomAttribute(el, key, props[key], env.isSVG);
106
+ }
107
+ };
50
108
 
51
- props =
52
- children.length > 1
53
- ? Object.assign({}, props, { children })
54
- : children.length === 1
55
- ? Object.assign({}, props, { children: children[0] })
56
- : props;
109
+ let patchAttributes = (el, newProps, oldProps, env) => {
110
+ let key;
111
+ for (key in newProps) {
112
+ if (key === 'key' || key === 'children' || key === ON_CREATE_KEY || key in env.directives)
113
+ continue;
57
114
 
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';
115
+ let oldValue = oldProps[key],
116
+ newValue = newProps[key];
117
+
118
+ if (oldValue !== newValue)
119
+ key.startsWith('on')
120
+ ? el[key.toLowerCase()] = ev => { newValue(ev); !env.manualRedraw && env.redraw(); }
121
+ : setDomAttribute(el, key, newValue, env.isSVG);
67
122
  }
68
123
 
69
- return jsx(__tag, props, props.key);
70
- }
124
+ for (key in oldProps) {
125
+ if (key === 'key' || key === 'children' || key in newProps || key in env.directives)
126
+ continue;
71
127
 
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
- }
128
+ if (key.startsWith('on')) el[key.toLowerCase()] = NIL;
129
+ else el.removeAttribute(key);
130
+ }
131
+ };
132
+
133
+ let mountDirectives = (el, props, env) => {
134
+ for (let key in props)
135
+ if (key in env.directives)
136
+ env.directives[key].mount(el, props[key]);
137
+ };
138
+
139
+ let patchDirectives = (el, newProps, oldProps, env) => {
140
+ let key;
141
+ for (key in newProps)
142
+ if (key in env.directives)
143
+ env.directives[key].patch(el, newProps[key], oldProps[key]);
144
+
145
+ for (key in oldProps)
146
+ if (key in env.directives && !(key in newProps))
147
+ env.directives[key].unmount(el, oldProps[key]);
148
+ };
90
149
 
91
- const Fragment = props => props.children;
150
+ let unmountDirectives = (el, props, env) => {
151
+ for (let key in props)
152
+ if (key in env.directives)
153
+ env.directives[key].unmount(el, props[key]);
154
+ };
92
155
 
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
- }
156
+ let mount = (vnode, env, closureId, closure, onRemove = noop) => {
157
+ let baseRef = { closureId, closure, onRemove };
113
158
 
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
- }
159
+ if (isEmpty(vnode))
160
+ return { ...baseRef, type: REF_SINGLE, node: document.createComment('NULL') };
126
161
 
127
- setProps(props) {
128
- this.oldProps = this.props;
129
- this.props = props;
130
- }
162
+ if (isLeaf(vnode))
163
+ return { ...baseRef, type: REF_SINGLE, node: document.createTextNode(vnode) };
131
164
 
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
- }
165
+ if (isElement(vnode)) {
166
+ let node,
167
+ { _t, props } = vnode;
155
168
 
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
- }
169
+ if (_t === 'svg' && !env.isSVG)
170
+ env = { ...env, isSVG: true };
179
171
 
180
- // element lifecycle hooks
181
- if (isFn(props.oncreate)) props.oncreate(node);
172
+ if (!env.isSVG) node = document.createElement(_t);
173
+ else node = document.createElementNS(SVG_NS, _t);
174
+
175
+ isFn(props[ON_CREATE_KEY]) && props[ON_CREATE_KEY](node);
182
176
 
183
177
  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);
178
+
179
+ let children = props.children === NIL
180
+ ? props.children
181
+ : mount(props.children, env);
182
+
183
+ if (children !== NIL) insertDom(node, children);
191
184
  mountDirectives(node, props, env);
185
+
192
186
  return {
187
+ ...baseRef,
193
188
  type: REF_SINGLE,
194
189
  node,
195
- children: childrenRef,
196
- };
197
- } else if (isNonEmptyArray(vnode)) {
190
+ children
191
+ }
192
+ }
193
+
194
+ if (isNonEmptyArray(vnode)) {
195
+ let i = 0, children = [];
196
+ for (; i < vnode.length; i++)
197
+ children.push(mount(vnode[i], env));
198
+
198
199
  return {
200
+ ...baseRef,
199
201
  type: REF_ARRAY,
200
- children: vnode.map((child) => mount(child, env)),
202
+ children
201
203
  };
202
- } else if (isRenderFunction(vnode)) {
203
- let childVNode = vnode.__tag(vnode.props);
204
+ }
204
205
 
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
- }
206
+ if (isRenderFunction(vnode)) {
207
+ let childVnode = vnode._t(vnode.props); // if this is a closure component, this will be "oninit"
219
208
 
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);
209
+ if (isFn(childVnode)) {
210
+ // closure component
211
+ let id = generateClosureId(),
212
+ fnMap = CLOSURE_TO_FN.get(vnode._t) || new Map(),
213
+ onRemove = ON_REMOVES.pop() || noop;
214
+
215
+ fnMap.set(id, childVnode); // save renderFn
216
+ CLOSURE_TO_FN.set(vnode._t, fnMap); // closure -> Map(id -> renderFn)
217
+
218
+ let closure = vnode._t;
219
+ vnode._t = childVnode;
220
+ return mount(vnode, env, id, closure, onRemove);
245
221
  }
246
222
 
247
- vnode.__tag.mount(renderer);
248
223
  return {
224
+ ...baseRef,
249
225
  type: REF_PARENT,
250
- childRef: renderer._STATE_.ref,
251
- childState: renderer,
226
+ childRef: mount(childVnode, env),
227
+ childState: childVnode
252
228
  };
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
229
  }
262
230
 
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
- }
231
+ if (vnode instanceof Node)
232
+ return { ...baseRef, type: REF_SINGLE, node: vnode };
233
+
234
+ if (vnode === NIL)
235
+ throw Error('mount: vnode is undefined');
342
236
 
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
- }
237
+ throw Error('mount: Invalid vnode');
238
+ };
393
239
 
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;
240
+ let unmount = (vnode, ref, env) => {
399
241
  if (isElement(vnode)) {
400
242
  unmountDirectives(ref.node, vnode.props, env);
401
- if (vnode.props.children != null)
243
+ if (vnode.props.children !== NIL)
402
244
  unmount(vnode.props.children, ref.children, env);
403
245
  } else if (isNonEmptyArray(vnode)) {
404
- vnode.forEach((childVNode, index) =>
405
- unmount(childVNode, ref.children[index], env)
406
- );
246
+ for (let i = 0; i < vnode.length; i++)
247
+ unmount(vnode[i], ref.children[i], env);
407
248
  } 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);
249
+ let closure = ref.closure,
250
+ closureId = ref.closureId,
251
+ onRemove = ref.onRemove,
252
+ fns = CLOSURE_TO_FN.get(closure);
253
+
254
+ if (fns && closureId && fns.get(closureId)) {
255
+ fns.delete(closureId);
256
+ !fns.size && CLOSURE_TO_FN.delete(closure);
257
+ onRemove();
413
258
  }
414
259
 
415
260
  unmount(ref.childState, ref.childRef, env);
416
- } else if (isComponent(vnode)) {
417
- vnode.__tag.unmount(ref.childState);
418
261
  }
419
- }
262
+ };
263
+
264
+ let patchInPlace = (parentDomNode, newVnode, oldVnode, ref, env) => {
265
+ let newRef = patch(parentDomNode, newVnode, oldVnode, ref, env);
420
266
 
421
- function patchInPlace(parentDomNode, newVNode, oldVNode, ref, env) {
422
- let newRef = patch(parentDomNode, newVNode, oldVNode, ref, env);
423
267
  if (newRef !== ref) {
424
268
  replaceDom(parentDomNode, newRef, ref);
425
- unmount(oldVNode, ref, env);
269
+ unmount(oldVnode, ref, env);
426
270
  }
271
+
427
272
  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,
273
+ };
274
+
275
+ let patchChildren = (parentDomNode, newChildren, oldChildren, ref, env) => {
276
+ // we need to retrieve the next sibling before the old children get removed from the DOM
277
+ let i, idx, oldVnode, newVnode, oldRef, newRef, refMap, beforeNode,
278
+ nextNode = getNextSibling(ref),
279
+ children = Array(newChildren.length),
280
+ refChildren = ref.children,
281
+ newStart = 0,
437
282
  oldStart = 0,
438
283
  newEnd = newChildren.length - 1,
439
- oldEnd = oldchildren.length - 1;
440
- let oldVNode, newVNode, oldRef, newRef, refMap;
441
-
284
+ oldEnd = oldChildren.length - 1;
285
+
442
286
  while (newStart <= newEnd && oldStart <= oldEnd) {
443
- if (refChildren[oldStart] === null) {
287
+ if (refChildren[oldStart] === NIL) {
444
288
  oldStart++;
445
289
  continue;
446
290
  }
447
- if (refChildren[oldEnd] === null) {
291
+
292
+ if (refChildren[oldEnd] === NIL) {
448
293
  oldEnd--;
449
294
  continue;
450
295
  }
451
-
452
- oldVNode = oldchildren[oldStart];
453
- newVNode = newChildren[newStart];
454
- if (newVNode.key === oldVNode.key) {
296
+
297
+ oldVnode = oldChildren[oldStart];
298
+ newVnode = newChildren[newStart];
299
+
300
+ if (oldVnode && newVnode && newVnode.key === oldVnode.key) {
455
301
  oldRef = refChildren[oldStart];
456
302
  newRef = children[newStart] = patchInPlace(
457
303
  parentDomNode,
458
- newVNode,
459
- oldVNode,
304
+ newVnode,
305
+ oldVnode,
460
306
  oldRef,
461
307
  env
462
308
  );
309
+
463
310
  newStart++;
464
311
  oldStart++;
465
312
  continue;
466
313
  }
467
-
468
- oldVNode = oldchildren[oldEnd];
469
- newVNode = newChildren[newEnd];
470
- if (newVNode.key === oldVNode.key) {
314
+
315
+ oldVnode = oldChildren[oldEnd];
316
+ newVnode = newChildren[newEnd];
317
+
318
+ if (oldVnode && newVnode && newVnode.key === oldVnode.key) {
471
319
  oldRef = refChildren[oldEnd];
472
320
  newRef = children[newEnd] = patchInPlace(
473
321
  parentDomNode,
474
- newVNode,
475
- oldVNode,
322
+ newVnode,
323
+ oldVnode,
476
324
  oldRef,
477
325
  env
478
326
  );
327
+
479
328
  newEnd--;
480
329
  oldEnd--;
481
330
  continue;
482
331
  }
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
- }
332
+
333
+ if (refMap === NIL)
334
+ for (i = oldStart, refMap = {}; i <= oldEnd; i++) {
335
+ oldVnode = oldChildren[i];
336
+ if (oldVnode && oldVnode.key !== NIL)
337
+ refMap[oldVnode.key] = i;
491
338
  }
492
- }
339
+
340
+ newVnode = newChildren[newStart];
341
+ idx = newVnode && newVnode.key !== NIL ? refMap[newVnode.key] : NIL;
493
342
 
494
- newVNode = newChildren[newStart];
495
- let idx = newVNode.key != null ? refMap[newVNode.key] : null;
496
- if (idx != null) {
497
- oldVNode = oldchildren[idx];
343
+ if (idx !== NIL) {
344
+ oldVnode = oldChildren[idx];
498
345
  oldRef = refChildren[idx];
499
346
  newRef = children[newStart] = patch(
500
347
  parentDomNode,
501
- newVNode,
502
- oldVNode,
348
+ newVnode,
349
+ oldVnode,
503
350
  oldRef,
504
351
  env
505
352
  );
353
+
506
354
  insertDom(parentDomNode, newRef, getDomNode(refChildren[oldStart]));
355
+
507
356
  if (newRef !== oldRef) {
508
357
  removeDom(parentDomNode, oldRef);
509
- unmount(oldVNode, oldRef, env);
358
+ unmount(oldVnode, oldRef, env);
510
359
  }
511
- refChildren[idx] = null;
360
+
361
+ refChildren[idx] = NIL;
512
362
  } else {
513
- newRef = children[newStart] = mount(newVNode, env);
363
+ newRef = children[newStart] = mount(newVnode, env);
514
364
  insertDom(parentDomNode, newRef, getDomNode(refChildren[oldStart]));
515
365
  }
366
+
516
367
  newStart++;
517
368
  }
518
-
519
- let beforeNode =
520
- newEnd < newChildren.length - 1
521
- ? getDomNode(children[newEnd + 1])
522
- : nextNode;
369
+
370
+ beforeNode = newEnd < newChildren.length - 1
371
+ ? getDomNode(children[newEnd + 1])
372
+ : nextNode;
373
+
523
374
  while (newStart <= newEnd) {
524
- let newRef = mount(newChildren[newStart], env);
375
+ newRef = mount(newChildren[newStart], env);
525
376
  children[newStart] = newRef;
526
377
  insertDom(parentDomNode, newRef, beforeNode);
527
378
  newStart++;
528
379
  }
380
+
529
381
  while (oldStart <= oldEnd) {
530
382
  oldRef = refChildren[oldStart];
531
- if (oldRef != null) {
383
+
384
+ if (oldRef !== NIL) {
532
385
  removeDom(parentDomNode, oldRef);
533
- unmount(oldchildren[oldStart], oldRef, env);
386
+ unmount(oldChildren[oldStart], oldRef, env);
534
387
  }
388
+
535
389
  oldStart++;
536
390
  }
391
+
537
392
  ref.children = children;
538
- }
393
+ };
539
394
 
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
- }
395
+ let patch = (parentDomNode, newVnode, oldVnode, ref, env = { ...DEFAULT_ENV }) => {
396
+ if (newVnode && newVnode._t === RETAIN_KEY) return ref;
547
397
 
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
- }
398
+ let closure, closureId;
563
399
 
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);
400
+ if (isObj(ref)) {
401
+ closure = ref.closure;
402
+ closureId = ref.closureId;
571
403
  }
572
- throw new Error("Unkown ref type " + JSON.stringify(ref));
573
- }
574
404
 
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
- }
405
+ if (isRenderFunction(newVnode) && isRenderFunction(oldVnode) && (newVnode._t === oldVnode._t || (closure && closureId))) {
406
+ // x is either a renderFn or a closure
407
+ let x = newVnode._t,
408
+ fn,
409
+ fns = CLOSURE_TO_FN.get(closure);
585
410
 
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
- }
411
+ if (fns && closureId && (fn = fns.get(closureId)))
412
+ x = fn;
596
413
 
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
- }
414
+ let childVnode = x(newVnode.props),
415
+ childRef = patch(
416
+ parentDomNode,
417
+ childVnode,
418
+ ref.childState,
419
+ ref.childRef,
420
+ env
421
+ );
610
422
 
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);
423
+ // we need to return a new ref in order for parent patches to properly replace changing DOM nodes
424
+ if (childRef !== ref.childRef)
425
+ return {
426
+ type: REF_PARENT,
427
+ childRef,
428
+ childState: childVnode
429
+ };
430
+ else {
431
+ ref.childState = childVnode;
432
+ return ref;
433
+ }
622
434
  }
623
- }
624
435
 
625
- function replaceDom(parent, newRef, oldRef) {
626
- insertDom(parent, newRef, getDomNode(oldRef));
627
- removeDom(parent, oldRef);
628
- }
436
+ if (oldVnode === newVnode || (isEmpty(newVnode) && isEmpty(oldVnode)))
437
+ return ref;
629
438
 
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
- }
439
+ if (isLeaf(newVnode) && isLeaf(oldVnode)) {
440
+ // leafs just need to have node value updated
441
+ ref.node.nodeValue = newVnode;
442
+ return ref;
635
443
  }
636
- }
637
444
 
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
- }
445
+ if (isElement(newVnode) && isElement(oldVnode) && newVnode._t === oldVnode._t) {
446
+ // make sure env has isSVG flag set to true
447
+ if (newVnode._t === 'svg' && !env.isSVG)
448
+ env = { ...env, isSVG: true };
650
449
 
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
- }
450
+ // update the node DOM attributes with the new props
451
+ patchAttributes(ref.node, newVnode.props, oldVnode.props, env);
658
452
 
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
- }
453
+ let oldChildren = oldVnode.props.children,
454
+ newChildren = newVnode.props.children;
670
455
 
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;
456
+ if (oldChildren === NIL) {
457
+ if (newChildren !== NIL) {
458
+ ref.children = mount(newChildren, env);
459
+ insertDom(ref.node, ref.children);
460
+ }
461
+ } else {
462
+ if (newChildren === NIL) {
463
+ ref.node.textContent = '';
464
+ unmount(oldChildren, ref.children, env);
465
+ ref.children = NIL;
680
466
  } else {
681
- setDOMAttribute(domElement, key, newValue, env.isSVG);
467
+ ref.children = patchInPlace(
468
+ ref.node,
469
+ newChildren,
470
+ oldChildren,
471
+ ref.children,
472
+ env
473
+ );
682
474
  }
683
475
  }
476
+
477
+ patchDirectives(ref.node, newVnode.props, oldVnode.props, env);
478
+ return ref;
684
479
  }
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);
480
+
481
+ if (isNonEmptyArray(newVnode) && isNonEmptyArray(oldVnode)) {
482
+ patchChildren(parentDomNode, newVnode, oldVnode, ref, env);
483
+ return ref;
484
+ }
485
+
486
+ if (newVnode instanceof Node && oldVnode instanceof Node) {
487
+ ref.node = newVnode;
488
+ return ref;
489
+ }
490
+
491
+ return mount(newVnode, env);
492
+ };
493
+
494
+ function h(_t, ...children) {
495
+ let props = children[0];
496
+ if (props && isObj(props) && !isArr(props) && !(props._t || props.props))
497
+ children.shift();
498
+ else props = EMPTY_OBJECT;
499
+
500
+ props =
501
+ children.length > 1
502
+ ? { ...props, children }
503
+ : children.length === 1
504
+ ? { ...props, children: children[0] }
505
+ : props;
506
+
507
+ // parse tag
508
+ if (isStr(_t)) {
509
+ let idx = _t.indexOf('.');
510
+ if (~idx) {
511
+ let className = _t.slice(idx + 1).replace(/\./g, ' ')
512
+ props.class = props.class ? className + ' ' + String(props.class) : className;
513
+ _t = _t.slice(0, idx);
697
514
  }
515
+ _t = _t || 'div';
698
516
  }
517
+
518
+ if (props.key !== props.key) throw new Error("Invalid NaN key");
519
+
520
+ let vtype =
521
+ isStr(_t)
522
+ ? VTYPE_ELEMENT
523
+ : _t && isFn(_t.mount)
524
+ ? VTYPE_COMPONENT
525
+ : isFn(_t)
526
+ ? VTYPE_FUNCTION
527
+ : NIL;
528
+
529
+ if (vtype === NIL) throw new Error("Invalid VNode type");
530
+
531
+ // returns a vnode
532
+ return {
533
+ vtype, // (number) VTYPE_ELEMENT | VTYPE_COMPONENT | VTYPE_FUNCTION
534
+ _t, // (string | object | function)
535
+ key: props.key, // string
536
+ props // object
537
+ };
699
538
  }
700
539
 
701
- function setDOMAttribute(el, attr, value, isSVG) {
702
- if (value === true) {
703
- el.setAttribute(attr, "");
704
- } else if (value === false) {
705
- el.removeAttribute(attr);
540
+ h.retain = _ => h(RETAIN_KEY);
541
+
542
+ const m = h;
543
+
544
+ const Fragment = props => props.children;
545
+
546
+ const onRemove = fn => ON_REMOVES.push(fn);
547
+
548
+ function render(vnode, parentDomNode) {
549
+ let ref,
550
+ env = { ...DEFAULT_ENV, manualRedraw: 1 },
551
+ rootRef = parentDomNode[PARENT_DOM_KEY];
552
+ if (rootRef === NIL) {
553
+ ref = mount(vnode, env);
554
+ parentDomNode[PARENT_DOM_KEY] = { ref, vnode };
555
+ parentDomNode.textContent = '';
556
+ insertDom(parentDomNode, ref, NIL);
706
557
  } 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
- }
558
+ rootRef.ref = patchInPlace(
559
+ parentDomNode,
560
+ vnode,
561
+ rootRef.vnode,
562
+ rootRef.ref,
563
+ env
564
+ );
565
+ rootRef.vnode = vnode;
713
566
  }
714
567
  }
715
568
 
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
- };
569
+ function run(vnode, parentDomNode, opts = {}) {
570
+ let ref,
571
+ env = { ...DEFAULT_ENV, ...opts },
572
+ rootRef = parentDomNode[PARENT_DOM_KEY];
752
573
 
753
- MOUNTING.push(cmp);
754
- ctx.render(view({ ...ctx.props }));
755
- MOUNTING.pop();
756
- }
574
+ if (rootRef !== NIL)
575
+ throw Error('App already mounted on this node');
757
576
 
758
- return cmp;
759
- }
760
-
577
+ ref = mount(vnode, env);
578
+ rootRef = parentDomNode[PARENT_DOM_KEY] = { ref, vnode };
579
+ parentDomNode.textContent = '';
580
+ insertDom(parentDomNode, ref, NIL);
581
+
582
+ return env.redraw = _ => {
583
+ rootRef.ref = patchInPlace(
584
+ parentDomNode,
585
+ vnode,
586
+ rootRef.vnode,
587
+ rootRef.ref,
588
+ env
589
+ );
590
+ };
591
+ }
761
592
  exports.Fragment = Fragment;
762
593
  exports.h = h;
763
- exports.jsx = jsx;
764
594
  exports.m = m;
765
595
  exports.onRemove = onRemove;
766
- exports.render = render;
596
+ exports.render = render;
597
+ exports.run = run;