From ba7b5c5d4d6b567f991bc0ac8d3ac237f44bfe8b Mon Sep 17 00:00:00 2001 From: Masquerade Circus Date: Sun, 26 Jan 2020 16:50:37 -0600 Subject: [PATCH] fix(main): fix replace error when the old node has been deleted or moved --- .size-snapshot.json | 6 +++--- CHANGELOG.md | 19 +++++++------------ dist/valyrian.min.js | 2 +- lib/index.js | 2 +- plugins/utils/dom.js | 19 +++++++++++++++---- test/keyed_test.js | 31 ++++++++++++------------------- 6 files changed, 39 insertions(+), 40 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index a1b1248..0334e65 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,7 +1,7 @@ { "dist/valyrian.min.js": { - "bundled": 11980, - "minified": 4906, - "gzipped": 2001 + "bundled": 11997, + "minified": 4920, + "gzipped": 2005 } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3650cd8..f92cf5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,31 +1,26 @@ # [4.0.0](https://github.com/Masquerade-Circus/valyrian.js/compare/3.3.4...4.0.0) (2020-01-26) - ### Bug Fixes -* **main:** handle TextVnode as newVnode ([f29cec3](https://github.com/Masquerade-Circus/valyrian.js/commit/f29cec3c47310c2f4aa8719f5fd0659f52f8388d)) -* **main (directives):** fix use of v-if with v-for trigger an error ([dec8522](https://github.com/Masquerade-Circus/valyrian.js/commit/dec8522ec4328c7321d526fa77fb6bd308081541)) -* **main (keyed lists):** fix error replaced vnode with undefined node and updating with defined node ([782dbe7](https://github.com/Masquerade-Circus/valyrian.js/commit/782dbe77856965df3f18602ff4dd713ac622d949)) - +- **main:** handle TextVnode as newVnode ([f29cec3](https://github.com/Masquerade-Circus/valyrian.js/commit/f29cec3c47310c2f4aa8719f5fd0659f52f8388d)) +- **main (directives):** fix use of v-if with v-for trigger an error ([dec8522](https://github.com/Masquerade-Circus/valyrian.js/commit/dec8522ec4328c7321d526fa77fb6bd308081541)) +- **main (keyed lists):** fix error replaced vnode with undefined node and updating with defined node ([782dbe7](https://github.com/Masquerade-Circus/valyrian.js/commit/782dbe77856965df3f18602ff4dd713ac622d949)) ### Features -* **main:** add a reserved model property to pass data to the vnodes ([496f3ce](https://github.com/Masquerade-Circus/valyrian.js/commit/496f3ce15d84b187cdaa897d415a77138120ae55)) - +- **main:** add a reserved model property to pass data to the vnodes ([496f3ce](https://github.com/Masquerade-Circus/valyrian.js/commit/496f3ce15d84b187cdaa897d415a77138120ae55)) ### improvement -* **main:** rename lifecycle methods to write more easily in object props ([25b1eba](https://github.com/Masquerade-Circus/valyrian.js/commit/25b1eba52a34a54bcfdbf1e8304c0e7ad0de65b7)) - +- **main:** rename lifecycle methods to write more easily in object props ([25b1eba](https://github.com/Masquerade-Circus/valyrian.js/commit/25b1eba52a34a54bcfdbf1e8304c0e7ad0de65b7)) ### Performance Improvements -* **main:** increase performance for all new changes ([b4b1a0d](https://github.com/Masquerade-Circus/valyrian.js/commit/b4b1a0d440ba8d699827b5a155a2d877c69d6c47)) - +- **main:** increase performance for all new changes ([b4b1a0d](https://github.com/Masquerade-Circus/valyrian.js/commit/b4b1a0d440ba8d699827b5a155a2d877c69d6c47)) ### BREAKING CHANGES -* **main:** Lifecycle methods renamed. +- **main:** Lifecycle methods renamed. ## [3.3.4](https://github.com/Masquerade-Circus/valyrian.js/compare/v3.3.3...v3.3.4) (2020-01-24) diff --git a/dist/valyrian.min.js b/dist/valyrian.min.js index 4936c85..c53eec6 100644 --- a/dist/valyrian.min.js +++ b/dist/valyrian.min.js @@ -1 +1 @@ -!function(){"use strict";let e,o,n,t=void 0;function d(e,o,n){this.props=o||{},this.children=n,this.name=e}function r(e){this.dom=e}r.prototype={props:{},children:[]};let i=new r;function l(e,o){return o?document.createElementNS("http://www.w3.org/2000/svg",e):document.createElement(e)}function s(e,o,...n){return new d(e,o,n)}s.isNode="undefined"==typeof window,s.dom2vnode=e=>{if(3===e.nodeType)return new r(e);if(1===e.nodeType){let o={};[].forEach.call(e.attributes,e=>o[e.nodeName]=e.nodeValue);let n=new d(e.nodeName,o,[]);n.dom=e;for(let o=0,t=e.childNodes.length;o{let o=l("div");return o.innerHTML=e.trim(),[].map.call(o.childNodes,e=>s.dom2vnode(e))};let p=new Map;s.usePlugin=(e,o)=>!p.has(e)&&p.set(e,!0)&&e(s,o),s.reservedWords={key:!0,"v-list":!0,"v-noop":!0,oncreate:!0,onbeforeupdate:!0,onupdate:!0,onremove:!0,model:!0};let c={};function m(e){let o=e.target,n=`__on${e.type}`;for(;o;){if(o[n])return o[n](e),void(e.defaultPrevented||s.update());o=o.parentNode}}function a(e,o,n){if(e instanceof d){if("onremove"===o)for(let o=0,n=e.children.length;o{if(e in o.props){let t=o.props[e];s.reservedWords[e]?"function"==typeof s.reservedWords[e]&&s.reservedWords[e](t,o,n):"function"==typeof t?(c[e=`__${e}`]||(document.addEventListener(e.slice(4),m),c[e]=!0),o.dom[e]=t):e in o.dom&&!o.isSVG?o.dom[e]!==t&&(o.dom[e]=t):t!==n.props[e]&&o.dom.setAttribute(e,t)}};let g=e=>e.map(e=>e instanceof d?e.props.key:"");function N(e,o,n,t){n.dom?(o.dom=n.dom,o.props["v-noop"]||!1===a(o,"onbeforeupdate",n)?(o.children=n.children,h(o.dom,e,t)):(f(o,n),u(o,n),h(o.dom,e,t),a(o,s.isMounted?"onupdate":"oncreate",n),V(o,n))):(o.dom=l(o.name,o.isSVG),u(o,i),h(o.dom,e,t),a(o,"oncreate"),V(o,i))}let y=[];s.onCleanup=e=>{let o=s.current.parentVnode;o.onCleanup||(o.onCleanup=[]),o.onCleanup.push(e),-1===y.indexOf(o)&&y.push(o)},s.current={parentVnode:t,oldParentVnode:t,component:t};let w=Array.isArray;function V(e,o){let n=w(e.children)?e.children:[e.children],p=o.children;s.current.parentVnode=e,s.current.oldParentVnode=o;for(let o=0;o=o.length?t:l;-1!==n?(p[n].processed=!0,N(e.dom,c,p[n],d)):N(e.dom,c,i,d)}}let l=p.length;for(;l--;)p[l]&&!p[l].processed&&v(p[l])}else{let o=p.length,t=n.length;for(;o-- >t;)v(p[o]);for(o=0;o{if(e)return n&&(!function(){for(let e=y.length;e--;)for(let o of y[e].onCleanup)o();y=[]}(),o=e,e=new d(e.name,e.props,s(n,t,...r)),e.dom=o.dom,e.isSVG="svg"===e.name,V(e,o),s.isMounted=!0),s.isNode&&e.dom.innerHTML},s.mount=(o,t,d,...r)=>{let i=s.isNode?l("div"):"string"==typeof o?document.querySelectorAll(o)[0]:o;return e=s.dom2vnode(i),n=t,s.update(d,...r)},s.unmount=()=>{n=()=>"";let e=s.update();return n=t,s.isMounted=!1,e},s.directive=(e,o)=>!s.reservedWords[e]&&(s.reservedWords[e]=o),s.directive("v-for",(e,o)=>o.children=e.map(o.children[0]));let C=e=>(o,n,t)=>{if(e?o:!o){let e=document.createTextNode("");t.dom&&t.dom.parentNode&&(a(t,"onremove"),t.dom.parentNode.replaceChild(e,t.dom)),n.name="",n.children=[],n.props={},n.dom=e}};s.directive("v-if",C(!1)),s.directive("v-unless",C(!0)),s.directive("v-show",(e,o)=>o.dom.style.display=e?"":"none"),s.directive("v-class",(e,o)=>{for(let n in e)o.dom.classList.toggle(n,e[n])}),(s.isNode?global:window).v=s}(); \ No newline at end of file +!function(){"use strict";let e,o,n,t=void 0;function d(e,o,n){this.props=o||{},this.children=n,this.name=e}function r(e){this.dom=e}r.prototype={props:{},children:[]};let i=new r;function l(e,o){return o?document.createElementNS("http://www.w3.org/2000/svg",e):document.createElement(e)}function s(e,o,...n){return new d(e,o,n)}s.isNode="undefined"==typeof window,s.dom2vnode=e=>{if(3===e.nodeType)return new r(e);if(1===e.nodeType){let o={};[].forEach.call(e.attributes,e=>o[e.nodeName]=e.nodeValue);let n=new d(e.nodeName,o,[]);n.dom=e;for(let o=0,t=e.childNodes.length;o{let o=l("div");return o.innerHTML=e.trim(),[].map.call(o.childNodes,e=>s.dom2vnode(e))};let p=new Map;s.usePlugin=(e,o)=>!p.has(e)&&p.set(e,!0)&&e(s,o),s.reservedWords={key:!0,"v-list":!0,"v-noop":!0,oncreate:!0,onbeforeupdate:!0,onupdate:!0,onremove:!0,model:!0};let c={};function m(e){let o=e.target,n=`__on${e.type}`;for(;o;){if(o[n])return o[n](e),void(e.defaultPrevented||s.update());o=o.parentNode}}function a(e,o,n){if(e instanceof d){if("onremove"===o)for(let o=0,n=e.children.length;o{if(e in o.props){let t=o.props[e];s.reservedWords[e]?"function"==typeof s.reservedWords[e]&&s.reservedWords[e](t,o,n):"function"==typeof t?(c[e=`__${e}`]||(document.addEventListener(e.slice(4),m),c[e]=!0),o.dom[e]=t):e in o.dom&&!o.isSVG?o.dom[e]!==t&&(o.dom[e]=t):t!==n.props[e]&&o.dom.setAttribute(e,t)}};let g=e=>e.map(e=>e instanceof d?e.props.key:"");function N(e,o,n,t){n.dom?(o.dom=n.dom,o.props["v-noop"]||!1===a(o,"onbeforeupdate",n)?(o.children=n.children,h(o.dom,e,t)):(f(o,n),u(o,n),h(o.dom,e,t),a(o,s.isMounted?"onupdate":"oncreate",n),V(o,n))):(o.dom=l(o.name,o.isSVG),u(o,i),h(o.dom,e,t),a(o,"oncreate"),V(o,i))}let y=[];s.onCleanup=e=>{let o=s.current.parentVnode;o.onCleanup||(o.onCleanup=[]),o.onCleanup.push(e),-1===y.indexOf(o)&&y.push(o)},s.current={parentVnode:t,oldParentVnode:t,component:t};let w=Array.isArray;function V(e,o){let n=w(e.children)?e.children:[e.children],p=o.children;s.current.parentVnode=e,s.current.oldParentVnode=o;for(let o=0;o=o.length?t:l;-1!==n?(p[n].processed=!0,N(e.dom,c,p[n],d)):N(e.dom,c,i,d)}}let l=p.length;for(;l--;)p[l]&&!p[l].processed&&v(p[l])}else{let o=p.length,t=n.length;for(;o-- >t;)v(p[o]);for(o=0;o{if(e)return n&&(!function(){for(let e=y.length;e--;)for(let o of y[e].onCleanup)o();y=[]}(),o=e,e=new d(e.name,e.props,s(n,t,...r)),e.dom=o.dom,e.isSVG="svg"===e.name,V(e,o),s.isMounted=!0),s.isNode&&e.dom.innerHTML},s.mount=(o,t,d,...r)=>{let i=s.isNode?l("div"):"string"==typeof o?document.querySelectorAll(o)[0]:o;return e=s.dom2vnode(i),n=t,s.update(d,...r)},s.unmount=()=>{n=()=>"";let e=s.update();return n=t,s.isMounted=!1,e},s.directive=(e,o)=>!s.reservedWords[e]&&(s.reservedWords[e]=o),s.directive("v-for",(e,o)=>o.children=e.map(o.children[0]));let C=e=>(o,n,t)=>{if(e?o:!o){let e=document.createTextNode("");t.dom&&t.dom.parentNode&&(a(t,"onremove"),t.dom.parentNode.replaceChild(e,t.dom)),n.name="",n.children=[],n.props={},n.dom=e}};s.directive("v-if",C(!1)),s.directive("v-unless",C(!0)),s.directive("v-show",(e,o)=>o.dom.style.display=e?"":"none"),s.directive("v-class",(e,o)=>{for(let n in e)o.dom.classList.toggle(n,e[n])}),(s.isNode?global:window).v=s}(); \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 7dabf63..ff0a93f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -330,7 +330,7 @@ function patch(parentNode, oldParentNode) { lifecycleCall(oldNode, onremove); newNode.dom = createElement(newNode.name, newNode.isSVG); updateProps(newNode, emptyNode); - parentNode.dom.replaceChild(newNode.dom, oldNode.dom); + parentNode.dom.replaceChild(newNode.dom, parentNode.dom.childNodes[i]); lifecycleCall(newNode, oncreate); patch(newNode, emptyNode); } diff --git a/plugins/utils/dom.js b/plugins/utils/dom.js index dfd5049..f7a2ea5 100644 --- a/plugins/utils/dom.js +++ b/plugins/utils/dom.js @@ -73,6 +73,14 @@ function createAttributeFilter(name) { return o => toLower(o.nodeName) === toLower(name); } +function isChildNode(node, child) { + let index = node.childNodes ? findWhere(node.childNodes, child, true, true) : -1; + if (index === -1) { + throw new Error('The node is not child of this element'); + } + return true; +} + let selfClosingTags = [ 'area', 'base', @@ -142,19 +150,22 @@ class Node { return child; } replaceChild(child, ref) { - if (ref.parentNode === this) { + if (isChildNode(this, ref)) { this.insertBefore(child, ref); ref.remove(); return ref; } } removeChild(child) { - splice(this.childNodes, child, false, true); - return child; + if (isChildNode(this, child)) { + child.remove(); + return child; + } } remove() { if (this.parentNode) { - this.parentNode.removeChild(this); + splice(this.parentNode.childNodes, this, false, true); + this.parentNode = null; } } } diff --git a/test/keyed_test.js b/test/keyed_test.js index 3c98ca0..3f38aa7 100644 --- a/test/keyed_test.js +++ b/test/keyed_test.js @@ -18,11 +18,16 @@ describe('Keyed lists', () => { {name: 'Replaced with undefined', set: [1, 3, 2, , 5, 4]}, {name: 'Added, remove and replaced with undefined', set: [6, 7, 8, 9,, 10]} ]; - let beforeString = '
    '; - for (let key of set) { - beforeString += '
  • ' + key + '
  • '; + + function getString(set) { + let str = '
      '; + for (let key of set) { + str += key ? `
    • ${key}
    • ` : ''; + } + str += '
    '; + return str; } - beforeString += '
'; + let beforeString = getString(set); tests.forEach(test => { it('Keyed list: ' + test.name, () => { @@ -37,11 +42,7 @@ describe('Keyed lists', () => { keys = [...test.set]; let after = v.update(); - let afterString = '
    '; - for (let key of test.set) { - afterString += key ? `
  • ${key}
  • ` : ''; - } - afterString += '
'; + let afterString = getString(test.set); expect(before).toEqual(beforeString); expect(after).toEqual(afterString); @@ -61,20 +62,12 @@ describe('Keyed lists', () => { keys = [6, 7, 8, 9,, 10]; let after = v.update(); - let afterString = '
    '; - for (let key of keys) { - afterString += key ? `
  • ${key}
  • ` : ''; - } - afterString += '
'; + let afterString = getString(keys); keys = [1, 2, 3, 4, 5]; let afterUpdate = v.update(); - let afterUpdateString = '
    '; - for (let key of set) { - afterUpdateString += key ? `
  • ${key}
  • ` : ''; - } - afterUpdateString += '
'; + let afterUpdateString = getString(keys); expect(before).toEqual(beforeString); expect(after).toEqual(afterString);