virtual-dom-2.1.1/000077500000000000000000000000001256011152500137545ustar00rootroot00000000000000virtual-dom-2.1.1/.editorconfig000066400000000000000000000003171256011152500164320ustar00rootroot00000000000000# editorconfig.org root = true [*] indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false virtual-dom-2.1.1/.gitignore000066400000000000000000000002441256011152500157440ustar00rootroot00000000000000.idea/ .DS_Store .monitor .*.swp .nodemonignore releases *.log *.err fleet.json public/browserify bin/*.json .bin build compile .lock-wscript coverage node_modules virtual-dom-2.1.1/.jshintrc000066400000000000000000000015421256011152500156030ustar00rootroot00000000000000{ "asi": true, "bitwise": false, "camelcase": true, "curly": false, "eqeqeq": true, "eqnull": true, "forin": false, "immed": true, "indent": 4, "latedef": "nofunc", "newcap": false, "noarg": true, "nonew": true, "plusplus": false, "proto": true, "quotmark": false, "regexp": false, "undef": true, "unused": true, "strict": false, "trailing": true, "noempty": true, "maxdepth": 5, "maxparams": 5, "globals": { "console": true, "Buffer": true, "setTimeout": true, "clearTimeout": true, "setInterval": true, "clearInterval": true, "require": false, "module": false, "exports": true, "global": false, "process": true, "__dirname": false, "__filename": false } } virtual-dom-2.1.1/.travis.yml000066400000000000000000000010551256011152500160660ustar00rootroot00000000000000language: node_js node_js: - '0.10' - '0.12' before_install: - npm install npm -g before_script: - npm install - npm install istanbul coveralls script: npm run travis-test env: global: - secure: JTqjyeAoQmKLVH9SzSeIWy02DcY9WAkfIU/w8YotxIsELWbPyZ6seYBEhHiB+JYJ9UeuUgyYhZ144SPm6K8o5w+merMD7zMglr95a/8CJnjMBdPK2/0Rjckz9OcANao5k+Qm+QBBu3keo6dRtKX25g6WrKwOYu8OLz5cradzhLs= - secure: OBCtZZfM5LZyT+fT84Kb7N0PP1JdTQQ3SymZD5Ac+JYK30gCG6UO/o713QjlI5yVG7qwdFlPQOYF2VVf7yw6dUvf6pKj51427b9nOir8Hb9XjWlpo/770dKxlvrNxtQ3kkygAJTU8Fe7Jbk30zJef/Be17C07viiCZs4sHsiZC8= virtual-dom-2.1.1/.zuul.yml000066400000000000000000000006171256011152500155600ustar00rootroot00000000000000ui: tape concurrency: 1 browsers: - name: chrome version: [26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, latest] - name: ie version: 6..latest - name: firefox version: 3..latest - name: iphone version: 4.3..latest - name: ipad version: 4.3..latest - name: android version: 4.0..latest - name: opera version: 11..latest - name: safari version: 5..latest virtual-dom-2.1.1/CHANGELOG.md000066400000000000000000000036261256011152500155740ustar00rootroot00000000000000# Release Notes ## v2.0.0 Provides fundamental fixes and tests for reordering of keyed nodes. Everyone is encouraged to upgrade to v2. The main caveat of this upgrade is that it changes the patch format to encode all of the move data, instead of previously providing the mapping and expecting patch to work out which moves are required. While this might limit options for reordering, on the grounds of performance and debugging it made more sense. This is considered a breaking change for that reason. However, for those of you who only consume `create`, `diff`, `patch` and `h`, this will not affect your usage, and upgrading is recommended due to the bugs this new version fixes. ## v1.3.0 - Add optimization to AttributHook to prevent resetting attributes where new hook instances are used but their values remain the same. - Extend the interface of unhook to take the next value from diff. - Fix bug where hook is called on unhook-only hooks. - Code refactor: diffProps broken out into it's own file ## v1.2.0 - Correctly sets SVG attributes that are namespaced using a (fixed) attribute hook. - Add CSS animation notes from github issue (css-animations.md) - A hook with an `unhook` method and no `hook` method is now considered a valid hook. - Fixes issue where unhook was not called when a hook property is replaced with a new value - Fixes dist script update - Update README to note that an instance of `dom-delegator` is required to use the `ev-*` properties. ## v1.1.0 - Element reordering - Updates the way in which elements are reordered to increase performance in common use cases. - Adds additional SVG display attributes. # v1.0.0 - Sensible versioning begins # v0.0.24 - Fix destroy ordering - Fixes a bug where widgets cannot be replaced by vnodes due to a bug in the order of destroy patches. # v0.0.23 - Release notes begin virtual-dom-2.1.1/LICENSE000066400000000000000000000020361256011152500147620ustar00rootroot00000000000000Copyright (c) 2014 Matt-Esch. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. virtual-dom-2.1.1/README.md000066400000000000000000000165171256011152500152450ustar00rootroot00000000000000# virtual-dom A JavaScript [DOM model](#dom-model) supporting [element creation](#element-creation), [diff computation](#diff-computation) and [patch operations](#patch-operations) for efficient re-rendering [![build status][1]][2] [![NPM version][3]][4] [![Coverage Status][5]][6] [![Davis Dependency status][7]][8] [![experimental](http://hughsk.github.io/stability-badges/dist/experimental.svg)](http://github.com/hughsk/stability-badges) [![Sauce Test Status](https://saucelabs.com/browser-matrix/mattesch.svg)](https://saucelabs.com/u/mattesch) ## Motivation Manual DOM manipulation is messy and keeping track of the previous DOM state is hard. A solution to this problem is to write your code as if you were recreating the entire DOM whenever state changes. Of course, if you actually recreated the entire DOM every time your application state changed, your app would be very slow and your input fields would lose focus. `virtual-dom` is a collection of modules designed to provide a declarative way of representing the DOM for your app. So instead of updating the DOM when your application state changes, you simply create a virtual tree or `VTree`, which looks like the DOM state that you want. `virtual-dom` will then figure out how to make the DOM look like this efficiently without recreating all of the DOM nodes. `virtual-dom` allows you to update a view whenever state changes by creating a full `VTree` of the view and then patching the DOM efficiently to look exactly as you described it. This results in keeping manual DOM manipulation and previous state tracking out of your application code, promoting clean and maintainable rendering logic for web applications. ## Example ```javascript var h = require('virtual-dom/h'); var diff = require('virtual-dom/diff'); var patch = require('virtual-dom/patch'); var createElement = require('virtual-dom/create-element'); // 1: Create a function that declares what the DOM should look like function render(count) { return h('div', { style: { textAlign: 'center', lineHeight: (100 + count) + 'px', border: '1px solid red', width: (100 + count) + 'px', height: (100 + count) + 'px' } }, [String(count)]); } // 2: Initialise the document var count = 0; // We need some app data. Here we just store a count. var tree = render(count); // We need an initial tree var rootNode = createElement(tree); // Create an initial root DOM node ... document.body.appendChild(rootNode); // ... and it should be in the document // 3: Wire up the update logic setInterval(function () { count++; var newTree = render(count); var patches = diff(tree, newTree); rootNode = patch(rootNode, patches); tree = newTree; }, 1000); ``` [View on RequireBin](http://requirebin.com/?gist=5492847b9a9025e64bab) ## Documentation You can find the documentation for the seperate components in their READMEs - For `create-element.js` see the [vdom README](vdom/README.md) - For `diff.js` see the [vtree README](vtree/README.md) - For `h.js` see the [virtual-hyperscript README](virtual-hyperscript/README.md) - For `patch.js` see the [vdom README](vdom/README.md) For information about the type signatures of these modules feel free to read the [javascript signature definition](docs.jsig) ## DOM model `virtual-dom` exposes a set of objects designed for representing DOM nodes. A "Document Object Model Model" might seem like a strange term, but it is exactly that. It's a native JavaScript tree structure that represents a native DOM node tree. We call this a **VTree** We can create a VTree using the objects directly in a verbose manner, or we can use the more terse virtual-hyperscript. ### Example - creating a VTree using the objects directly ```javascript var VNode = require('virtual-dom/vnode/vnode'); var VText = require('virtual-dom/vnode/vtext'); function render(data) { return new VNode('div', { className: "greeting" }, [ new VText("Hello " + String(data.name)) ]); } module.exports = render; ``` ### Example - creating a VTree using virtual-hyperscript ```javascript var h = require('virtual-dom/h'); function render(data) { return h('.greeting', ['Hello ' + data.name]); } module.exports = render; ``` The DOM model is designed to be efficient to create and read from. The reason why we don't just create a real DOM tree is that creating DOM nodes and reading the node properties is an expensive operation which is what we are trying to avoid. Reading some DOM node properties even causes side effects, so recreating the entire DOM structure with real DOM nodes simply isn't suitable for high performance rendering and it is not easy to reason about either. A `VTree` is designed to be equivalent to an immutable data structure. While it's not actually immutable, you can reuse the nodes in multiple places and the functions we have exposed that take VTrees as arguments never mutate the trees. We could freeze the objects in the model but don't for efficiency. (The benefits of an immutable-equivalent data structure will be documented in vtree or blog post at some point) ## Element creation ```haskell createElement(tree:VTree) -> DOMNode ``` Given that we have created a `VTree`, we need some way to translate this into a real DOM tree of some sort. This is provided by `create-element.js`. When rendering for the first time we would pass a complete `VTree` to create-element function to create the equivalent DOM node. ## Diff computation ```haskell diff(previous:VTree, current:VTree) -> PatchObject ``` The primary motivation behind virtual-dom is to allow us to write code independent of previous state. So when our application state changes we will generate a new `VTree`. The `diff` function creates a set of DOM patches that, based on the difference between the previous `VTree` and the current `VTree`, will update the previous DOM tree to match the new `VTree`. ## Patch operations ```haskell patch(rootNode:DOMNode, patches:PatchObject) -> DOMNode newRootNode ``` Once we have computed the set of patches required to apply to the DOM, we need a function that can apply those patches. This is provided by the `patch` function. Given a DOM root node and a set of DOM patches, the `patch` function will update the DOM. After applying the patches to the DOM, the DOM should look like the new `VTree`. ## Original motivation virtual-dom is heavily inspired by the inner workings of React by facebook. This project originated as a gist of ideas, which [we have linked to provide some background context](https://gist.github.com/Raynos/8414846). ## Tools * [html2hscript](https://github.com/twilson63/html2hscript) - Parse HTML into hyperscript * [html2hscript.herokuapp.com](http://html2hscript.herokuapp.com/) - Online Tool that converts html snippets to hyperscript * [html2hyperscript](https://github.com/unframework/html2hyperscript) - Original commandline utility to convert legacy HTML markup into hyperscript [1]: https://secure.travis-ci.org/Matt-Esch/virtual-dom.svg [2]: https://travis-ci.org/Matt-Esch/virtual-dom [3]: https://badge.fury.io/js/virtual-dom.svg [4]: https://badge.fury.io/js/virtual-dom [5]: http://img.shields.io/coveralls/Matt-Esch/virtual-dom.svg [6]: https://coveralls.io/r/Matt-Esch/virtual-dom [7]: https://david-dm.org/Matt-Esch/virtual-dom.svg [8]: https://david-dm.org/Matt-Esch/virtual-dom virtual-dom-2.1.1/create-element.js000066400000000000000000000001301256011152500171760ustar00rootroot00000000000000var createElement = require("./vdom/create-element.js") module.exports = createElement virtual-dom-2.1.1/diff.js000066400000000000000000000000751256011152500152240ustar00rootroot00000000000000var diff = require("./vtree/diff.js") module.exports = diff virtual-dom-2.1.1/docs.jsig000066400000000000000000000110341256011152500155610ustar00rootroot00000000000000-- A VHook is an object with a hook and unhook method. -- A VHook can be used to define your own apply property logic. -- -- When a hook is found in the VProperties object in either -- `patch()` or `createElement()`, instead of setting -- or unsetting the property we will invoke `hook()` or -- `unhook()` respectively so you can define what setting -- and unsetting a property means. type VHook : { hook: (node: DOMElement, propertyName: String) => void, unhook: (node: DOMElement, propertyName: String) => void } type VPropertyName : String type VPropertyValue : String | Boolean | Number -- A VProperties is a data structure that represents the -- set of properties that can be attached to a virtual node -- -- Properties can be many things. The simplest case is a string -- property name and string or bool or number property value -- -- You can also have hooks in your properties. You give a -- string hook name and a hook value. The hook will then -- be invoked as part of `patch()` and `createElement()` -- which allows you to set custom properties. -- -- Next up, attributes and style are treated slightly -- differently. These keys are expected to have objects -- of string keys and string values and will have the -- correct `patch()` and `createElement()` logic for -- setting attributes and styles. -- -- Finally properties can also have nested arbitrary objects -- of some key name to some value that is an object of -- string to bool or number or string. type VProperties : Object & Object & Object<"attributes", Object> & Object<"style", Object> & Object -- A VNode is a data structure that represents a virtual node -- in a virtual tree. -- -- A virtual node consists of a tagName, a set of properties -- and a list of children. It also has meta data attached, -- namely a key and a namespace. type VNode : { tagName: String, properties: VProperties children: Array, key: String | undefined, namespace: String | null } -- A VText is a data structure representing a text node in -- a virtual tree. type VText : { text: String, type: "VirtualText" } -- A Widget is a custom data structure for representing an -- object in a virtual tree. -- -- A Widget allows you to fully specify the semantics of -- intitialization of the object, updating of the object -- and destruction of the object. -- -- A Widget should generally be used for custom, performance -- critical code. type Widget : { type: "Widget", init: () => DOMElement, update: (previous: Widget, domNode: DOMElement) => void, destroy: (node: DOMElement) => void } -- A Thunk is a custom data structure for representing an -- unevaluated node in a virtual tree. -- -- A Thunk allows you to overwrite the semantics of `diff()` -- by implementing your own render method that takes the -- previous node in the previous virtual tree. -- -- Inside `render()` you can check whether the previous node -- is conceptually the same as the current node and return -- `previous.vnode` instead of re-computing and recreating -- an equivelant vnode. -- -- This allows you to implement caching in the virtual tree -- and has significant performance improvements type Thunk : { type: "Thunk", vnode: VTree, render: (previous: VTree | null) => VTree } -- A VTree is the union of all the types of nodes that can -- exist in a virtual tree. type VTree : VText | VNode | Widget | Thunk -- A VPatch represents a patch object that is returned from -- `diff()`. This patch object can be applied to an -- existing DOM element tree. type VPatch := { type: Number, vNode: VNode, patch: Any, type: 'VirtualPatch' } virtual-dom/create-element : (vnode: VTree, opts: { document?: DOMDocument, warn?: Boolean }) => DOMElement | DOMTextNode virtual-dom/diff : (left: VTree, right: VTree) => Array virtual-dom/h : ( tagSelector: String, properties?: { key: String, namespace: String } & VProperties, children?: Array | Vtree | textContent: String ) => VNode virtual-dom/patch : ( rootNode: DOMElement, patches: Array ) => newRootNode: DOMElement virtual-dom-2.1.1/docs/000077500000000000000000000000001256011152500147045ustar00rootroot00000000000000virtual-dom-2.1.1/docs/README.md000066400000000000000000000035071256011152500161700ustar00rootroot00000000000000# virtual-dom documentation This documentation is aimed at people who would like to work with virtual-dom directly, or gain a deeper understanding of how their virtual-dom based framework works. If you would rather be working at a higher level, you may find the [mercury framework](https://github.com/Raynos/mercury) a better place to start. ## Overview virtual-dom consists of four main parts: [vtree](https://github.com/Matt-Esch/virtual-dom/tree/master/vtree) - responsible for diffing two virtual representations DOM nodes [vdom](https://github.com/Matt-Esch/virtual-dom/tree/master/vdom) - responsible for taking the [patch](https://github.com/Matt-Esch/virtual-dom/blob/master/vdom/patch.js) genereated by [vtree/diff](https://github.com/Matt-Esch/virtual-dom/blob/master/vtree/diff.js) and using it to modify the rendered DOM [vnode](https://github.com/Matt-Esch/virtual-dom/tree/master/vnode) - virtual representation of dom elements [virtual-hyperscript](https://github.com/Matt-Esch/virtual-dom/tree/master/virtual-hyperscript) - an interface for generating VNodes from simple data structures Newcomers should start by reading the VNode and VText documentation, as virtual nodes are central to the operation of virtual-dom. Hooks, Thunks, and Widgets are more advanced features, and you will find both documentation of their interfaces and several examples on their respective pages. ## Contents [VNode](vnode.md) - A representation of a DOM element [VText](vtext.md) - A representation of a text node [Hooks](hooks.md) - The mechanism for executing functions after a new node has been created [Thunk](thunk.md) - The mechanism for taking control of diffing a specific DOM sub-tree [Widget](widget.md) - The mechanism for taking control of node patching: DOM Element creation, updating, and removal. [CSS animations](css-animations.md) virtual-dom-2.1.1/docs/css-animations.md000066400000000000000000000055301256011152500201610ustar00rootroot00000000000000# CSS animations Based on a discusison in [question](https://github.com/Matt-Esch/virtual-dom/issues/104#issuecomment-68611995). You should be activating the CSS transitions using hooks and nextTick. Here is a basic example of inserting an element through transition: ```javascript Item.render = function(state) { return h('li', { 'class' : new ItemInsertHook('item'), }, [ h('div.text', state.text), h('button', { 'ev-click' : mercury.event(...) }, 'Remove or something...'), ]); } function ItemInsertHook(value) { this.value = value; } ItemInsertHook.prototype.hook = function(elem, propName) { // Here we want to see if this is a newly created dom element // or an element that was inserted before and is revisited. // The way to check for that in this case is see if the element is // attached to the dom. // Newly created element will not be attached to the dom when hook is executed. if (!document.body.contains(elem)) { elem.setAttribute(propName, this.value + ' inserting'); nextTick(function () { elem.setAttribute(propName, this.value + ''); }.bind(this)) } } //Elswhere at the top level of application: function renderItemsList(state) { return h('ul#item-list', [ state.items.map(function(item) {return Item.render(item);}) ]); } ``` And css: ```css li.item.inserting { opacity : 0.01; } li.item { transition: opacity 0.2s ease-in-out; } li.item { opacity : 0.99; } ``` See full example on requirebin: http://requirebin.com/?gist=250e6e59aa40d5ff0fcc In a more complex case it may be necessary to encode animation state in the model. You should know exactly which nodes you wish to animate based on your data, and you should use that data to add a transition hook based on next tick. You don't have to do animations with JS, prefer CSS transitions, but you do need to model your expectations properly in your data model and apply transitions to the nodes using that data. Generic transitions that rely on the way in which the DOM is mutated isn't going to work consistently. For example, if you want an inserted transition, you might add a wasInserted boolean flag to your model. On rendering that item, if wasInserted is true, you add an animation hook which, on next tick, adds a css class like .inserted. You code your CSS transitions against this class. On next tick your hook will add the class and the transition will happen. Further to that you will want to clear the wasInserted flag, probably also on next tick. There are tons of these flag situations which should not trigger re-render. I think mercury needs to add something like this as a primitive type but that's outside the scope of virtual-dom. But as you can see, the animation works because you have recorded the state and necessity for the transition, and did not rely simply on the insertion of the node to trigger that animation. virtual-dom-2.1.1/docs/hooks.md000066400000000000000000000036431256011152500163570ustar00rootroot00000000000000# Hooks Hooks are functions that execute after turning a VNode into an Element. They are set by passing a VNode any property key with an object that has a function called hook that has not been directly assigned. The simplest way to ensure that a function isn't directly assigned is for it to be a prototype on an object. Will Work ```javascript var Hook = function(){} Hook.prototype.hook = function(node, propertyName, previousValue) { console.log("Hello, World") } createElement(h('div', { "my-hook": new Hook() })) ``` Won't Work ```javascript var hook = { hook: function(node, propertyName, previousvalue) { console.log("Hello, World") } } createElement(h('div', { "my-hook": hook })) ``` ## Arguments Your hook function will be given the following arguments **node** The Element generated from the VNode. ```javascript var Hook = function(){} Hook.prototype.hook = function(node, propertyName, previousValue) { console.log("type: ", node.constructor.name) } createElement(h('div', { "my-hook": new Hook() })) // logs "type: HTMLDivElement" ``` **propertyName** String, key of the property this hook was assigned from. ```javascript var Hook = function(){} Hook.prototype.hook = function(node, propertyName, previousValue) { console.log("name: " + propertyName) } createElement(h('div', { "my-hook": new Hook() })) // logs "name: my-hook" ``` **previousValue** *(optional)* If this node is having just its properties changed during a patch, it will receive the value that was previously assigned to the key. Otherwise, this argument will be undefined. ## Other Examples [virtual-hyperscript](https://github.com/Matt-Esch/virtual-dom/tree/master/virtual-hyperscript) uses hooks for several things, including setting up events and returning focus to input elements after a render. You can view these hooks in the [virtual-hyperscript/hooks](https://github.com/Matt-Esch/virtual-dom/tree/master/virtual-hyperscript/hooks) folder. virtual-dom-2.1.1/docs/thunk.md000066400000000000000000000132221256011152500163570ustar00rootroot00000000000000# Thunk Thunks allow the user to take control of the diff'ing process for a specific dom tree, usually to avoid doing calculations you know are unneeded, such as diff'ing a tree you know hasn't changed. ## Thunk Interface A Thunk needs to be an object with two keys **type** must be the string "Thunk" **render** Function that returns a VNode, Widget, or VText. ```javascript // Boilerplate Thunk var Thunk = function (){} Thunk.prototype.type = "Thunk" Thunk.prototype.render = function(previous){} ``` ## Render Method Arguments When diff is run, the render method is passed a single argument. **previous** The previous VNode, Thunk, Widget, or VText that the Thunk is being diffed against ## The Special "vnode" Property When `render` is called by `diff`, it will create a cache of whatever it returns in the key `vnode`. When implementing the Thunk interface, don't define a `vnode` key, as it will be overwritten! You should use the `vnode` property when render is provided with the `previous` argument, and you'd like to return the cached copy to prevent a Thunk from re-rendering. We give an example of this in the ConstantlyThunk implementation below. ## Simple Example Here we implement a simple Thunk called ConstantlyThunk. Any instance of ConstantlyThunk that gets diffed with another instance of ConstantlyThunk will return the value of the previous ConstantlyThunk, preventing a patch from being generated. ```javascript // Only the first instance of this Thunk will be shown. // Once its been rendered, any other instances of ConstantlyThunk that // diff with it will return a reference to the cached value that is automatically // assigned to the "vnode" property var ConstantlyThunk = function(greeting){ this.greeting = greeting } ConstantlyThunk.prototype.type = "Thunk" ConstantlyThunk.prototype.render = function(previous) { if (previous && previous.vnode) { return previous.vnode } else { return h('div', ["Constantly "+ this.greeting]) } } Thunk1 = new ConstantlyThunk("Thunk!") Thunk2 = new ConstantlyThunk("I won't be rendered!") thunkElem = createElement(Thunk1) document.body.appendChild(thunkElem) // No new patches are generated patches = diff(Thunk1, Thunk2) // Nothing will happen patch(thunkElem, patches) ``` ## Full Example Here we implement GenericThunk, a simplified version of Raynos' [immutable-thunk](https://github.com/Raynos/vdom-thunk/blob/master/immutable-thunk.js) It takes a rendering function, a comparison function, and a state. When it's being diffed vs. another instance of GenericThunk, it will use the comparison function to look at the new state and old state, and decide if it's ok to update. ```javascript var diff = require("virtual-dom").diff var patch = require("virtual-dom").patch var h = require("virtual-dom").h var createElement = require("virtual-dom").create // Our GenericThunk will take 3 arguments // renderFn is the function that will generate the VNode // cmpFn is the function that will be used to compare state to see if an update is necessary. // returns true if the update should re-render, and false if it should use the previous render // state is a value that holds the information cmpFn will use to decide whether we should // update the Thunk or not var GenericThunk = function(renderFn, cmpFn, state) { this.renderFn = renderFn this.cmpFn = cmpFn this.state = state } GenericThunk.prototype.type = "Thunk" GenericThunk.prototype.render = function(previous) { // The first time the Thunk renders, there will be no previous state var previousState = previous ? previous.state : null // We run the comparison function to see if the state has changed enough // for us to re-render. If it returns truthy, then we call the render // function to give us a new VNode if ((!previousState || !this.state) || this.cmpFn(previousState, this.state)) { return this.renderFn(previous, this) } else { // vnode will be set automatically when a thunk has been created // it contains the VNode, VText, Thunk, or Widget generated by // our render function. return previous.vnode } } // The function we'll pass to GenericThunk to see if the color has changed // We return a true value if the colors are different var titleCompare = function(previousState, currentState) { return previousState.color !== currentState.color } // The function that builds our title when we detect that // the color has changed var titleRender = function(previousThunk, currentThunk) { var currentColor = currentThunk.state.color return h("h1", { style: {color: currentColor}}, ["Hello, I'm a title colored " + currentColor]) } var GreenColoredThunk = new GenericThunk(titleRender, titleCompare, { color: "green"}) var BlueColoredThunk = new GenericThunk(titleRender, titleCompare, { color: "blue"}) var currentNode = GreenColoredThunk var rootNode = createElement(currentNode) // A simple function to diff your thunks, and patch the dom var update = function(nextNode) { var patches = diff(currentNode, nextNode) rootNode = patch(rootNode, patches) currentNode = nextNode } document.body.appendChild(rootNode) // We schedule a couple updates // Our first update will see that our color hasn't changed, and will stop comparing at that point, // instead returning a reference to GreenColoredThunk.vnode setTimeout(function() { update(new GenericThunk(titleRender, titleCompare, { color: "green" })) }, 1000) // In our second update, BlueColoredThunk will see that state.color has changed, // and will return a new VNode, generating a patch setTimeout(function() { update(BlueColoredThunk) }, 2000) ``` ## Other Resources Raynos has created a library for making Thunks at [vdom-thunk](https://github.com/Raynos/vdom-thunk). virtual-dom-2.1.1/docs/vnode.md000066400000000000000000000141711256011152500163450ustar00rootroot00000000000000# VNode A VNode is a representation of a dom node. Most end users will probably have their VNodes generated through [virtual-hyperscript](https://github.com/Matt-Esch/virtual-dom/tree/master/virtual-hyperscript), but understanding the VNode interface can be useful. In virtual-dom, VNodes are turned from virtual nodes into real nodes with the createElement function. You can read the code at [vdom/create-element](https://github.com/Matt-Esch/virtual-dom/blob/master/vdom/create-element.js) ## Arguments **tagName** A string, e.g. 'div', 'span', 'article' **properties** *(optional)* An object that maps property names to their values, e.g. `{ id: "foo", className: "bar" }` **children** *(optional)* An array of any combination of VNodes, VText, Widgets, or Thunks **key** *(optional)* An identifying string used to differentiate this node from others. Used internally by vtree/diff to do node reordering. **namespace** *(optional*) A string specifying the namespace uri to associate with the element. VNodes created with a namespace will use [Document#createElementNS](https://developer.mozilla.org/en-US/docs/Web/API/document.createElementNS) ### A full example ```javascript var VNode = require("virtual-dom").VNode var createElement = require("virtual-dom").create var Hook = function(){} Hook.prototype.hook = function(elem, key, previousValue) { console.log("Hello from " + elem.id + "!\nMy key was: " + key) } var tagName = "div" var style = "width: 100px; height: 100px; background-color: #FF0000;" var attributes = {"class": "red box", style: style } var key = "my-unique-red-box" var namespace = "http://www.w3.org/1999/xhtml" var properties = { attributes: attributes, id: "my-red-box", "a hook can have any property key": new Hook() } var childNode = new VNode("div", {id: "red-box-child"}) RedBoxNode = new VNode(tagName, properties, [childNode], key, namespace) RedBoxElem = createElement(RedBoxNode) document.body.appendChild(RedBoxElem) // Will result in html that looks like //
//
//
``` ### properties Keys in properties have several special cases. #### properties.attributes Everything in the `properties.attributes` object will be set on the rendered dom node using `Element#setAttribute(key, value)`, and removed using `Element#removeAttribute`. Refer to [MDN HTML Attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes) for available attributes. #### Hook Objects Any key whose value is an object with an inherited key called "hook" is considered a hook. Hooks are used to run functions at render time. Refer to the [hook documentation](hooks.md) for more information. #### Other Objects Any key in `properties` that is an object, but whose value isn't a hook and isn't in the attributes key, will set the rendered elements property to the given object. ```javascript createElement(new VNode('div', { style: { width: "100px", height: "100px"}})) // When added to the dom, the resulting element will look like
``` #### Other Values Most attributes can be set using properties. During element creation, keys and values in the `properties.attributes` get set using `Element#setAttribute(key, value)`, whereas keys other than `attributes` present in properties get set using `element[key] = value`. ```javascript foo = new VNode('div', { id: 'foo'}) // will render the same as foo = new VNode('div', { attributes: { id: 'foo' }}) ``` This can, however, cause some unexpected behavior, particularly if you are unfamiliar with the differences between setting an element property directly vs. setting it using an attribute. Refer to [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) and [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) for some common property behaviors. #### properties.style vs properties.attributes.style `properties.style` expects an object, while `properties.attributes.style` expects a string. ```javascript // using attributes var redBox = new VNode('div', { attributes: { style: "width: 100px; height: 100px; background-color: #FF0000;" }}) // using a property var anotherRedBox = new VNode('div', { style: { width: "100px", height: "100px", backgroundColor: "#FF0000" }}) ``` When using `properties.style`, if the styles have changed since the last render and are being updated, keys that are not in the new style definition will be set to an empty string. This differs from the normal behavior, which sets old object keys to undefined. #### properties.className vs properties.attributes.class Set the class attribute value using `className` key in properties, and the `class` key in attributes. ```javascript var redBox = new VNode('div', { className: "red box" }) // will render the same as var anotherRedBox = new VNode('div', { attributes: { "class": "red box" }}) ``` #### Custom attributes (data-\*) Custom attributes won't be rendered if set directly in properties. Set them in properties.attributes. ```javascript var doThis = new VNode('div', { attributes: { "data-example": "I will render" }}) var notThis = new VNode('div', { "data-example": "I will not render" }) ``` Alternately, you can use the [dataset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement.dataset) property to set data-\* attributes. ```javascript new VNode('div', { attributes: { "data-greeting": "Hello" }}) new VNode('div', { dataset: { greeting: "Hello" }}) // Will both generate
``` #### ARIA attributes Like custom attributes, ARIA attributes are also set in `properties.attributes`. ```javascript var ariaExample = new VNode('div', { attributes: { "aria-checked": "true" }}) ``` Unlike data-\* attributes, they cannot be set directly via properties. #### properties.value, properties.defaultValue, and properties.attributes.value If an input element is reset, its value will be returned to its value set in `properties.attributes.value`. If you've set the value using `properties.value`, this will not happen. However, you may set `properties.defaultValue` to get the desired result. virtual-dom-2.1.1/docs/vtext.md000066400000000000000000000027261256011152500164070ustar00rootroot00000000000000# VText VText is a representation of a Text node. In virtual-dom, a VText is turned into an actual text node with the `createElement` function. You can read the code at [vdom/create-element](https://github.com/Matt-Esch/virtual-dom/blob/master/vdom/create-element.js) `createElement` turns a VText into a Text node using [document#createTextNode](https://developer.mozilla.org/en-US/docs/Web/API/document.createTextNode). ## Full Example ```javascript var createElement = require("virtual-dom").create var VNode = require("virtual-dom/vnode/vnode") var VText = require("virtual-dom/vnode/vtext") var myText = new VText("Hello, World") // Pass our VText as a child of our VNode var myNode = new VNode("div", { id: "my-node" }, [myText]) var myElem = createElement(myNode) document.body.appendChild(myElem) // Will result in a dom string that looks like
Hello, World
``` ## Arguments **text** The string you would like the text node to contain. ## HTML Injection `document#createTextNode` will defend against HTML injection. You could use the innerHTML property, but it will most likely break virtual dom. ```javascript escapedText = new VText('Example') escapedNode = new VNode('div', null, [escapedText]) // Will enter the dom as
<span>Example</span>
unescapedNode = new VNode('div', { innerHTML: "Example" }) // Will enter the dom as
Example
// You should probably never do this ``` virtual-dom-2.1.1/docs/widget.md000066400000000000000000000055271256011152500165220ustar00rootroot00000000000000# Widget Widgets are used to take control of the patching process, allowing the user to create stateful components, control sub-tree rendering, and hook into element removal. ## Widget Interface **type** Must be the string "Widget" **init** The function called when the widget is being created. Should return a DOM Element. **update** The function called when the widget is being updated. **destroy** The function called when the widget is being removed from the dom. ```javascript // Boilerplate widget var Widget = function (){} Widget.prototype.type = "Widget" Widget.prototype.init = function(){} Widget.prototype.update = function(previous, domNode){} Widget.prototype.destroy = function(domNode){} ``` ### Update Arguments The arguments passed to `Widget#update` **previous** The previous Widget **domNode** The previous DOM Element associated with this widget ### Destroy Argument The argument passed to `Widget#destroy` **domNode** The HTMLElement associated with the widget that will be removed ## Full Example This example demonstrates one way to pass local component state and use `init`, `update`, and `destroy` to create a widget that counts each time it tries to update, only showing the odd numbers. ```javascript var diff = require("virtual-dom").diff var patch = require("virtual-dom").patch var h = require("virtual-dom").h var createElement = require("virtual-dom").create var OddCounterWidget = function() {} OddCounterWidget.prototype.type = "Widget" OddCounterWidget.prototype.count = 1 OddCounterWidget.prototype.init = function() { // With widgets, you can use any method you would like to generate the DOM Elements. // We could get the same result using: // return createElement(h("div", "Count is: " + this.count)) var divElem = document.createElement("div") var textElem = document.createTextNode("Count is: " + this.count) divElem.appendChild(textElem) return divElem } OddCounterWidget.prototype.update = function(previous, domNode) { this.count = previous.count + 1 // Only re-render if the current count is odd if (this.count % 2) { // Returning a new element from widget#update // will replace the previous node return this.init() } return null } OddCounterWidget.prototype.destroy = function(domNode) { // While you can do any cleanup you would like here, // we don't really have to do anything in this case. // Instead, we'll log the current count console.log(this.count) } var myCounter = new OddCounterWidget() var currentNode = myCounter var rootNode = createElement(currentNode) // A simple function to diff your widgets, and patch the dom var update = function(nextNode) { var patches = diff(currentNode, nextNode) rootNode = patch(rootNode, patches) currentNode = nextNode } document.body.appendChild(rootNode) setInterval(function(){ update(new OddCounterWidget()) }, 1000) ``` virtual-dom-2.1.1/h.js000066400000000000000000000001061256011152500145360ustar00rootroot00000000000000var h = require("./virtual-hyperscript/index.js") module.exports = h virtual-dom-2.1.1/index.js000066400000000000000000000005401256011152500154200ustar00rootroot00000000000000var diff = require("./diff.js") var patch = require("./patch.js") var h = require("./h.js") var create = require("./create-element.js") var VNode = require('./vnode/vnode.js') var VText = require('./vnode/vtext.js') module.exports = { diff: diff, patch: patch, h: h, create: create, VNode: VNode, VText: VText } virtual-dom-2.1.1/package.json000066400000000000000000000046141256011152500162470ustar00rootroot00000000000000{ "name": "virtual-dom", "version": "2.1.1", "description": "A batched diff-based DOM rendering strategy", "keywords": [ "virtual", "dom", "vdom", "vtree", "diff", "patch", "browser" ], "author": "Matt-Esch ", "repository": "git://github.com/Matt-Esch/virtual-dom.git", "main": "index", "homepage": "https://github.com/Matt-Esch/virtual-dom", "contributors": [ { "name": "Matt-Esch" } ], "bugs": { "url": "https://github.com/Matt-Esch/virtual-dom/issues", "email": "matt@mattesch.info" }, "dependencies": { "browser-split": "0.0.1", "error": "^4.3.0", "ev-store": "^7.0.0", "global": "^4.3.0", "is-object": "^1.0.1", "next-tick": "^0.2.2", "x-is-array": "0.1.0", "x-is-string": "0.1.0" }, "devDependencies": { "browserify": "^9.0.7", "istanbul": "^0.3.13", "min-document": "^2.14.0", "opn": "^1.0.1", "run-browser": "^2.0.2", "tap-dot": "^1.0.0", "tap-spec": "^3.0.0", "tape": "^4.0.0", "zuul": "^2.1.1" }, "license": "MIT", "scripts": { "test": "node ./test/index.js | tap-spec", "dot": "node ./test/index.js | tap-dot", "start": "node ./index.js", "cover": "istanbul cover --report html --print detail ./test/index.js", "view-cover": "istanbul report html && opn ./coverage/index.html", "browser": "run-browser test/index.js", "phantom": "run-browser test/index.js -b | tap-spec", "dist": "browserify --standalone virtual-dom index.js > dist/virtual-dom.js", "travis-test": "npm run phantom && npm run cover && istanbul report lcov && ((cat coverage/lcov.info | coveralls) || exit 0)", "release": "npm run release-patch", "release-patch": "git checkout master && npm version patch && git push origin master --tags && npm publish", "release-minor": "git checkout master && npm version minor && git push origin master --tags && npm publish", "release-major": "git checkout master && npm version major && git push origin master --tags && npm publish" }, "testling": { "files": "test/*.js", "browsers": [ "ie/8..latest", "firefox/17..latest", "firefox/nightly", "chrome/22..latest", "chrome/canary", "opera/12..latest", "opera/next", "safari/5.1..latest", "ipad/6.0..latest", "iphone/6.0..latest", "android-browser/4.2..latest" ] } } virtual-dom-2.1.1/patch.js000066400000000000000000000000771256011152500154150ustar00rootroot00000000000000var patch = require("./vdom/patch.js") module.exports = patch virtual-dom-2.1.1/test/000077500000000000000000000000001256011152500147335ustar00rootroot00000000000000virtual-dom-2.1.1/test/attributes.js000066400000000000000000000035341256011152500174640ustar00rootroot00000000000000var test = require("tape") var h = require("../h.js") var createElement = require("../create-element.js") var diff = require("../diff.js") var patch = require("../patch.js") test("attributes can be set", function (assert) { var leftTree = h("div") var rightTree = h("div",{ attributes: { src: "test.jpg" } }) var rootNode = createElement(leftTree) var patches = diff(leftTree, rightTree) var newRootNode = patch(rootNode, patches) assert.equal(newRootNode.getAttribute("src"), "test.jpg") assert.end() }) test("individual attributes can be unset", function (assert) { var leftTree = h("div", { attributes: { a: "1", b: "2", c: "3" } }) var rightTree = h("div", { attributes: { a: "1", c: "3" } }) var rootNode = createElement(leftTree) var patches = diff(leftTree, rightTree) var newRootNode = patch(rootNode, patches) assert.equal(newRootNode, rootNode) assert.equal(newRootNode.getAttribute("a"), "1") assert.ok(newRootNode.getAttribute("b") == null) assert.equal(newRootNode.getAttribute("c"), "3") assert.end() }) test("attributes can be completely unset", function (assert) { var leftTree = h("div", { attributes: { a: "1", b: "2", c: "3" } }) var rightTree = h("div") var rootNode = createElement(leftTree) var patches = diff(leftTree, rightTree) var newRootNode = patch(rootNode, patches) assert.equal(newRootNode, rootNode) assert.ok(newRootNode.getAttribute("a") == null) assert.ok(newRootNode.getAttribute("b") == null) assert.ok(newRootNode.getAttribute("c") == null) assert.end() }) virtual-dom-2.1.1/test/hook.js000066400000000000000000000323371256011152500162410ustar00rootroot00000000000000var test = require("tape") var h = require("../h.js") var Node = require("../vnode/vnode.js") var create = require("../create-element.js") var diff = require("../diff.js") var patch = require("../patch.js") var patchCount = require("./lib/patch-count.js") test("Stateful hooks are added to a hooks object on a node", function (assert) { function StatefulHook() {} StatefulHook.prototype.hook = function () {} StatefulHook.prototype.unhook = function () {} var statefulValue = new StatefulHook() function StatelessHook() {} StatelessHook.prototype.hook = function () {} var statelessValue = new StatelessHook() var node = new Node("div", { stateful: statefulValue, stateless: statelessValue, value: "not a hook" }, [], null, null) assert.equal(node.hooks.stateful, statefulValue) assert.equal(node.hooks.stateless, undefined) assert.equal(node.descendantHooks, false) assert.end() }) test("Node child stateless hooks are not identified", function (assert) { function Prop() {} Prop.prototype.hook = function () {} var propValue = new Prop() var node = new Node("div", { "id": propValue, "value": "not a hook" }, [], undefined, undefined) var parentNode = new Node("div", { "id": "not a hook" }, [node], undefined, undefined) assert.equal(node.hooks, undefined) assert.equal(parentNode.hooks, undefined) assert.notOk(parentNode.descendantHooks) assert.end() }) test("Node child stateful hooks are identified", function (assert) { function Prop() {} Prop.prototype.hook = function () {} Prop.prototype.unhook = function () {} var propValue = new Prop() var node = new Node("div", { "id": propValue, "value": "not a hook" }, [], undefined, undefined) var parentNode = new Node("div", { "id": "not a hook" }, [node], undefined, undefined) assert.equal(node.hooks.id, propValue) assert.equal(parentNode.hooks, undefined) assert.ok(parentNode.descendantHooks) assert.end() }) test("hooks get called in render", function (assert) { var counter = 0 var vtree = h("div", { "some-key": hook(function (elem, prop) { counter++ assert.equal(prop, "some-key") assert.equal(elem.tagName, "DIV") elem.className = "bar" }) }) var elem = create(vtree) assert.equal(elem.className, "bar") assert.equal(counter, 1) assert.end() }) test("functions are not hooks in render", function (assert) { var counter = 0 var fakeHook = function () { counter++ } var vtree = h("div", { "someProp": fakeHook }) var elem = create(vtree) assert.equal(elem.someProp, fakeHook) assert.equal(counter, 0) assert.end() }) test("hooks get called in patch", function (assert) { var counter = 0 var prev = h("div") var curr = h("div", { "some-key": hook(function (elem, prop) { counter++ assert.equal(prop, "some-key") assert.equal(elem.tagName, "DIV") elem.className = "bar" }) }) var elem = createAndPatch(prev, curr) assert.equal(elem.className, "bar") assert.equal(counter, 1) assert.end() }) test("hooks are called with DOM node, property name, and previous/next value", function (assert) { function Hook(name) { this.name = name this.hookArgs = [] this.unhookArgs = [] } Hook.prototype.hook = function() { this.hookArgs.push([].slice.call(arguments, 0)) } Hook.prototype.unhook = function() { this.unhookArgs.push([].slice.call(arguments, 0)) } var hook1 = new Hook('hook1') var hook2 = new Hook('hook2') var first = h("div", { id: 'first', hook: hook1 }) var second = h("div", { id: 'second', hook: hook2 }) var third = h("div") var elem = create(first) assert.equal(hook1.hookArgs.length, 1) assert.deepEqual(hook1.hookArgs[0], [elem, 'hook', undefined]) assert.equal(hook1.unhookArgs.length, 0) var patches = diff(first, second) elem = patch(elem, patches) assert.equal(hook2.hookArgs.length, 1) assert.deepEqual(hook2.hookArgs[0], [elem, 'hook', hook1]) assert.equal(hook1.unhookArgs.length, 1) assert.deepEqual(hook1.unhookArgs[0], [elem, 'hook', hook2]) patches = diff(second, third) elem = patch(elem, patches) assert.equal(hook2.hookArgs.length, 1) assert.equal(hook2.unhookArgs.length, 1) assert.deepEqual(hook2.unhookArgs[0], [elem, 'hook', undefined]) assert.end() }) test("functions are not hooks in render", function (assert) { var counter = 0 var fakeHook = function () { counter++ } var prev = h("div") var curr = h("div", { someProp: fakeHook }) var elem = createAndPatch(prev, curr) assert.equal(elem.someProp, fakeHook) assert.equal(counter, 0) assert.end() }) test("two different hooks", function (assert) { var counters = { a: 0, b: 0 } var prev = h("div", { propA: hook(function () { counters.a++ }) }) var curr = h("div", { propB: hook(function () { counters.b++ }) }) var elem = createAndPatch(prev, curr) assert.equal(elem.propA, undefined) assert.equal(elem.propB, null) assert.equal(counters.a, 1) assert.equal(counters.b, 1) assert.end() }) test("two hooks on same property", function (assert) { var counters = { a: 0, b: 0 } var prev = h("div", { propA: hook(function () { counters.a++ }) }) var curr = h("div", { propA: hook(function () { counters.b++ }) }) var elem = createAndPatch(prev, curr) assert.equal(elem.propA, undefined) assert.equal(counters.a, 1) assert.equal(counters.b, 1) assert.end() }) test("two hooks of same interface", function (assert) { function Hook(key) { this.key = key } Hook.prototype.hook = function () { counters[this.key]++ } var counters = { a: 0, b: 0 } var prev = h("div", { propA: new Hook("a") }) var curr = h("div", { propA: new Hook("b") }) var elem = createAndPatch(prev, curr) assert.equal(elem.propA, undefined) assert.equal(counters.a, 1, "counters.a") assert.equal(counters.b, 1, "counters.b") assert.end() }) test("hooks are not called on trivial diff", function (assert) { var counters = { a: 0, b: 0, c: 0 } var vnode = h("div", { test: hook(function () { counters.a++ }) }, [ h("div", { test: hook(function () { counters.b++ }) }), h("div", { test: hook(function () { counters.c++ }) }) ]) var rootNode = create(vnode) assert.equal(counters.a, 1, "counters.a") assert.equal(counters.b, 1, "counters.b") assert.equal(counters.c, 1, "counters.c") var patches = diff(vnode, vnode) assert.equal(patchCount(patches), 0) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(counters.a, 1, "counters.a patch") assert.equal(counters.b, 1, "counters.b patch") assert.equal(counters.c, 1, "counters.c patch") assert.end() }) test("property-replacing diff calls unhook", function (assert) { unhookCallCount = 0 function zhook(x) { this.x = x } zhook.prototype.hook = function () { return null } zhook.prototype.unhook = function () { unhookCallCount += 1 } hooker = new zhook('ONE') hooker2 = new zhook('TWO') var firstTree = h("div", {roothook: hooker}) var secondTree = h("div", {roothook: hooker2}) var thirdTree = h("span") var rootNode = create(firstTree) var firstPatches = diff(firstTree, secondTree) rootNode = patch(rootNode, firstPatches) var secondPatches = diff(secondTree, thirdTree) rootNode = patch(rootNode, secondPatches) assert.strictEqual(unhookCallCount, 2, "Missing unhook calls") assert.end() }) test("unhook-only hook is a valid hook", function (assert) { var unhookCalled = false; function UnhookHook() {} UnhookHook.prototype.unhook = function() { unhookCalled = true } var hook = new UnhookHook() var firstTree = h('div', {hook: hook}) var secondTree = h('div', {}) var rootNode = create(firstTree) assert.notOk(unhookCalled) var patches = diff(firstTree, secondTree) rootNode = patch(rootNode, patches) assert.ok(unhookCalled) assert.end() }) test("all hooks are unhooked", function (assert) { var hookCounts = {} var unhookCounts = {} function Hook(value) { if (!(this instanceof Hook)) { return new Hook(value) } this.value = value } Hook.prototype.hook = function hook() { var key = this.value if (key in hookCounts) { hookCounts[key]++ } else { hookCounts[key] = 1; } } Hook.prototype.unhook = function unhook() { var key = this.value; if (key in unhookCounts) { unhookCounts[key]++ } else { unhookCounts[key] = 1; } } var rootHook = Hook("rootHook") var childHookA = Hook("childHookA") var childHookB = Hook("childHookB") var childHookC = Hook("childHookC") var thunkyRootHook = Hook("thunkyRootHook") var thunkyChildHookA = Hook("thunkyChildHookA") var thunkyChildHookB = Hook("thunkyChildHookB") var thunkyChildHookC = Hook("thunkyChildHookC") function Thunky() {} Thunky.prototype.render = function () { return h("div", { rootHook: thunkyRootHook }, [ h("div", { childHook: thunkyChildHookA }), h("div", { childHook: thunkyChildHookB }), h("div", { childHook: thunkyChildHookC }) ]) } Thunky.prototype.type = "Thunk" var firstTree = h("div", { rootHook: rootHook }, [ h("div", { childHook: childHookA }), h("div", { childHook: childHookB }, [ new Thunky() ]), h("div", { childHook: childHookC }) ]) var secondTree = h("div", { rootHook: rootHook }, [ h("div", { childHook: childHookA }), h("div", { childHook: childHookB }, [ new Thunky() ]), h("div", { childHook: childHookC }) ]) var thirdTree = h('span') var rootNode = create(firstTree) assertHooked(); var firstPatches = diff(firstTree, secondTree) assertHooked(); assert.strictEqual(patchCount(firstPatches), 0, "No patches for identical") rootNode = patch(rootNode, firstPatches) assertHooked(); var secondPatches = diff(secondTree, thirdTree) assertHooked(); // Expect 1 root patch, 3 unhook patches and a thunk patch assert.strictEqual(patchCount(secondPatches), 5, "Expect unhook patches") rootNode = patch(rootNode, secondPatches) assertUnhooked() assert.end() function assertHooked() { assert.strictEqual(hookCounts.rootHook, 1) assert.strictEqual(hookCounts.childHookA, 1) assert.strictEqual(hookCounts.childHookB, 1) assert.strictEqual(hookCounts.childHookC, 1) assert.strictEqual(hookCounts.thunkyRootHook, 1) assert.strictEqual(hookCounts.thunkyChildHookA, 1) assert.strictEqual(hookCounts.thunkyChildHookB, 1) assert.strictEqual(hookCounts.thunkyChildHookC, 1) assert.strictEqual(unhookCounts.rootHook, undefined) assert.strictEqual(unhookCounts.childHookA, undefined) assert.strictEqual(unhookCounts.childHookB, undefined) assert.strictEqual(unhookCounts.childHookC, undefined) assert.strictEqual(unhookCounts.thunkyRootHook, undefined) assert.strictEqual(unhookCounts.thunkyChildHookA, undefined) assert.strictEqual(unhookCounts.thunkyChildHookB, undefined) assert.strictEqual(unhookCounts.thunkyChildHookC, undefined) } function assertUnhooked() { assert.strictEqual(hookCounts.rootHook, 1) assert.strictEqual(hookCounts.childHookA, 1) assert.strictEqual(hookCounts.childHookB, 1) assert.strictEqual(hookCounts.childHookC, 1) assert.strictEqual(hookCounts.thunkyRootHook, 1) assert.strictEqual(hookCounts.thunkyChildHookA, 1) assert.strictEqual(hookCounts.thunkyChildHookB, 1) assert.strictEqual(hookCounts.thunkyChildHookC, 1) assert.strictEqual(unhookCounts.rootHook, 1) assert.strictEqual(unhookCounts.childHookA, 1) assert.strictEqual(unhookCounts.childHookB, 1) assert.strictEqual(unhookCounts.childHookC, 1) assert.strictEqual(unhookCounts.thunkyRootHook, 1) assert.strictEqual(unhookCounts.thunkyChildHookA, 1) assert.strictEqual(unhookCounts.thunkyChildHookB, 1) assert.strictEqual(unhookCounts.thunkyChildHookC, 1) } }) function createAndPatch(prev, curr) { var elem = create(prev) var patches = diff(prev, curr) elem = patch(elem, patches) return elem } function Type(fn) { this.fn = fn } Type.prototype.hook = function () { this.fn.apply(this, arguments) } function hook(fn) { return new Type(fn) } virtual-dom-2.1.1/test/index.js000066400000000000000000000005331256011152500164010ustar00rootroot00000000000000require("./main.js") require("./hook.js") require("./nested-properties.js") require("./undefined-properties.js") require("./keys.js") require("./thunk.js") require("./style.js") require("./attributes") require("./non-string.js") require("../vdom/test/") require("../vtree/test/") require("../virtual-hyperscript/test/") require("../vnode/test/") virtual-dom-2.1.1/test/keys.js000066400000000000000000000533041256011152500162510ustar00rootroot00000000000000var test = require("tape") var h = require("../h.js") var diff = require("../diff.js") var patch = require("../patch.js") var render = require("../create-element.js") var patchCount = require("./lib/patch-count.js") var assertEqualDom = require("./lib/assert-equal-dom.js") var nodesFromArray = require("./lib/nodes-from-array.js") var assertChildNodesFromArray = require("./lib/assert-childNodes-from-array.js") var VPatch = require("../vnode/vpatch.js") test("keys get reordered", function (assert) { var leftNode = nodesFromArray(["1", "2", "3", "4", "test", "6", "good", "7"]) var rightNode = nodesFromArray(["7", "4", "3", "2", "6", "test", "good", "1"]) var rootNode = render(leftNode) var childNodes = [] for (var i = 0; i < rootNode.childNodes.length; i++) { childNodes.push(rootNode.childNodes[i]) } var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 1) assertReorderEquals(assert, patches, { removes: [ {from: 0, key: '1'}, {from: 0, key: '2'}, {from: 1, key: '4'}, {from: 2, key: '6'}, {from: 3, key: '7'} ], inserts: [ {to: 0, key: '7'}, {to: 1, key: '4'}, {to: 3, key: '2'}, {to: 4, key: '6'}, {to: 7, key: '1'} ] }) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, rootNode.childNodes.length) assert.equal(newRoot.childNodes[7], childNodes[0]) assert.equal(newRoot.childNodes[3], childNodes[1]) assert.equal(newRoot.childNodes[2], childNodes[2]) assert.equal(newRoot.childNodes[1], childNodes[3]) assert.equal(newRoot.childNodes[5], childNodes[4]) assert.equal(newRoot.childNodes[4], childNodes[5]) assert.equal(newRoot.childNodes[6], childNodes[6]) assert.equal(newRoot.childNodes[0], childNodes[7]) assert.end() }) test("mix keys without keys", function (assert) { var leftNode = h("div", [ h("div", { key: 1 }), h("div"), h("div"), h("div"), h("div"), h("div"), h("div"), h("div") ]) var rightNode = h("div", [ h("div"), h("div"), h("div"), h("div"), h("div"), h("div"), h("div"), h("div", { key: 1 }) ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 1) assertReorderEquals(assert, patches, { removes: [{from: 0, key: '1'}], inserts: [{to: 7, key: '1'}] }) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, rootNode.childNodes.length) assert.equal(newRoot.childNodes[0], childNodes[1]) assert.equal(newRoot.childNodes[1], childNodes[2]) assert.equal(newRoot.childNodes[2], childNodes[3]) assert.equal(newRoot.childNodes[3], childNodes[4]) assert.equal(newRoot.childNodes[4], childNodes[5]) assert.equal(newRoot.childNodes[5], childNodes[6]) assert.equal(newRoot.childNodes[6], childNodes[7]) assert.equal(newRoot.childNodes[7], childNodes[0]) assert.end() }) test("avoid unnecessary reordering", function (assert) { var leftNode = h("div", [ h("div"), h("div", { key: 1 }), h("div") ]) var rightNode = h("div", [ h("div"), h("div", { key: 1 }), h("div") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 0) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes[0], childNodes[0]) assert.equal(newRoot.childNodes[1], childNodes[1]) assert.equal(newRoot.childNodes[2], childNodes[2]) assert.end() }) test("missing key gets replaced", function (assert) { var leftNode = h("div", [ h("div", { key: 1 }), h("div"), h("div"), h("div"), h("div"), h("div"), h("div"), h("div") ]) var rightNode = h("div", [ h("div"), h("div"), h("div"), h("div"), h("div"), h("div"), h("div"), h("div") ]) var rootNode = render(leftNode) var childNodes = [] for (var i = 0; i < rootNode.childNodes.length; i++) { childNodes.push(rootNode.childNodes[i]) } var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 1) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, rootNode.childNodes.length) assert.notEqual(newRoot.childNodes[0], childNodes[0]) assert.equal(newRoot.childNodes[1], childNodes[1]) assert.equal(newRoot.childNodes[2], childNodes[2]) assert.equal(newRoot.childNodes[3], childNodes[3]) assert.equal(newRoot.childNodes[4], childNodes[4]) assert.equal(newRoot.childNodes[5], childNodes[5]) assert.equal(newRoot.childNodes[6], childNodes[6]) assert.equal(newRoot.childNodes[7], childNodes[7]) assert.end() }) test("widgets can be keyed", function (assert) { function DivWidget(key, state) { this.key = key this.state = state } DivWidget.prototype.init = function () { return render(h("div", this.state)) } DivWidget.prototype.update = function (rootNode, prev) { if (this.state !== prev.state) { return render(h("div", this.state)) } } DivWidget.prototype.type = "Widget" var leftNode = h("div", [ new DivWidget("1", "a"), new DivWidget("2", "b"), new DivWidget("3", "c") ]) var rightNode = h("div", [ new DivWidget("3", "c"), new DivWidget("2", "b"), new DivWidget("1", "a") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 4) // 1 reorder and 3 update patches var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, rootNode.childNodes.length) assertEqualDom(assert, newRoot.childNodes[0], childNodes[2]) assertEqualDom(assert, newRoot.childNodes[1], childNodes[1]) assertEqualDom(assert, newRoot.childNodes[2], childNodes[0]) assert.end() }) test("delete key at the start", function (assert) { var leftNode = h("div", [ h("div", { key: "a" }, "a"), h("div", { key: "b" }, "b"), h("div", { key: "c" }, "c") ]) var rightNode = h("div", [ h("div", { key: "b" }, "b"), h("div", { key: "c" }, "c") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) // just a remove patch assert.equal(patchCount(patches), 1) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, 2) assert.equal(newRoot.childNodes[0], childNodes[1]) assert.equal(newRoot.childNodes[1], childNodes[2]) assert.end() }) test("add key to start", function (assert) { var leftNode = h("div", [ h("div", { key: "b" }, "b"), h("div", { key: "c" }, "c") ]) var rightNode = h("div", [ h("div", { key: "a" }, "a"), h("div", { key: "b" }, "b"), h("div", { key: "c" }, "c") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 1) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, 3) assert.equal(newRoot.childNodes[1], childNodes[0]) assert.equal(newRoot.childNodes[2], childNodes[1]) assert.end() }) test("delete key at the end", function (assert) { var leftNode = h("div", [ h("div", { key: "a" }, "a"), h("div", { key: "b" }, "b"), h("div", { key: "c" }, "c") ]) var rightNode = h("div", [ h("div", { key: "a" }, "a"), h("div", { key: "b" }, "b") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) // just a remove patch assert.equal(patchCount(patches), 1) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, 2) assert.equal(newRoot.childNodes[0], childNodes[0]) assert.equal(newRoot.childNodes[1], childNodes[1]) assert.end() }) test("add key to end", function (assert) { var leftNode = h("div", [ h("div", { key: "a" }, "a"), h("div", { key: "b" }, "b") ]) var rightNode = h("div", [ h("div", { key: "a" }, "a"), h("div", { key: "b" }, "b"), h("div", { key: "c" }, "c") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 1) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, 3) assert.equal(newRoot.childNodes[0], childNodes[0]) assert.equal(newRoot.childNodes[1], childNodes[1]) assert.end() }) test("add to end and delete from center & reverse", function (assert) { var leftNode = h("div", [ h("div", { key: "a", id: "a" }, "a"), h("div", { key: "b", id: "b" }, "b"), h("div", { key: "c", id: "c" }, "c"), h("div", { key: "d", id: "d" }, "d") ]) var rightNode = h("div", [ h("div", { key: "e", id: "e" }, "e"), h("div", { key: "d", id: "d" }, "d"), h("div", { key: "c", id: "c" }, "c"), h("div", { key: "a", id: "a" }, "a") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 2) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, 4) assert.equal(newRoot.childNodes[1], childNodes[3]) assert.equal(newRoot.childNodes[2], childNodes[2]) assert.equal(newRoot.childNodes[3], childNodes[0]) assert.end() }) test("add to front and remove", function (assert) { var leftNode = h("ul", [ h("li", { key: "c" }, "c"), h("li", { key: "d" }, "d") ]) var rightNode = h("ul", [ h("li", { key: "a" }, "a"), h("li", { key: "b" }, "b"), h("li", { key: "c" }, "c"), h("li", { key: "e" }, "e") ]) var rootNode = render(leftNode) var childNodes = childNodesArray(rootNode) var patches = diff(leftNode, rightNode) var newRoot = patch(rootNode, patches) assert.equal(newRoot, rootNode) assert.equal(newRoot.childNodes.length, 4) assert.equal(newRoot.childNodes[2], childNodes[0]) assert.end() }) test("adding multiple widgets", function (assert) { function FooWidget(foo) { this.foo = foo this.counter = 0 this.key = foo } FooWidget.prototype.init = function () { return render(h("div", String(this.foo))) } FooWidget.prototype.update = function (prev, elem) { this.counter = prev.counter + 1 elem.textContent = this.foo + this.counter } FooWidget.prototype.type = "Widget" var firstTree = h("div", []) var rootNode = render(firstTree) assert.equal(rootNode.tagName, "DIV") var secondTree = h("div", [ new FooWidget("foo") ]) rootNode = patch(rootNode, diff(firstTree, secondTree)) assert.equal(rootNode.tagName, "DIV") assert.equal(rootNode.childNodes.length, 1) assert.equal(rootNode.childNodes[0].tagName, "DIV") assert.equal(rootNode.childNodes[0].childNodes[0].data, "foo") var thirdTree = h("div", [ new FooWidget("foo"), new FooWidget("bar") ]) rootNode = patch(rootNode, diff(secondTree, thirdTree)) assert.equal(rootNode.tagName, "DIV") assert.equal(rootNode.childNodes.length, 2) assert.end() }) var itemHelpers = { item: function (key) { key = key.toString() return h('div', { key: key, id: key }, ["" + key]) }, container: function (children) { return h('div', children) }, itemsInContainer: function () { return { from: function (start) { return { to: function (end) { function withPredicate(predicate) { var items = [] for (var i = start; i <= end; i++) { if (!predicate(i)) continue items.push(itemHelpers.item(i)) } return itemHelpers.container(items) } return { by: function (increment) { return withPredicate(function (i) { return (i - start) % increment === 0 }) }, withPredicate: withPredicate } } } } } }, expectTextOfChild: function (assert, rootNode, childNo, text) { assert.equal(rootNode.childNodes[childNo].id, text) } } test('3 elements in a container, insert an element after each', function (assert) { var threeItems = itemHelpers.itemsInContainer().from(0).to(4).by(2) var sixItems = itemHelpers.itemsInContainer().from(0).to(5).by(1) var rootNode = render(threeItems) rootNode = patch(rootNode, diff(threeItems, sixItems)) for (var i = 0; i <= 5; i++) { itemHelpers.expectTextOfChild(assert, rootNode, i, i.toString()) } assert.end() }) test('10 elements in a container, remove every second element', function(assert) { var fiveItems = itemHelpers.itemsInContainer().from(0).to(9).by(2) var tenItems = itemHelpers.itemsInContainer().from(0).to(9).by(1) var rootNode = render(tenItems) var patches = diff(tenItems, fiveItems) // 5 remove patches only assert.equal(patchCount(patches), 5) rootNode = patch(rootNode, patches) for (var i = 0; i < 5; i++) { itemHelpers.expectTextOfChild(assert, rootNode, i, (i * 2).toString()) } assert.end() }) test('3 elements in a container, add 3 elements after each', function (assert) { var first = itemHelpers.itemsInContainer().from(0).to(11).by(4) var second = itemHelpers.itemsInContainer().from(0).to(11).by(1) // Assert indices before assert.strictEqual(first.children.length, 3) var rootNode = render(first) for (var i = 0; i < 3; i++) { itemHelpers.expectTextOfChild(assert, rootNode, i, (4*i).toString()) } // Assert indices after assert.strictEqual(second.children.length, 12) var newRoot = patch(rootNode, diff(first, second)) for (var j = 0; j < 12; j++) { itemHelpers.expectTextOfChild(assert, newRoot, j, j.toString()) } assert.end() }) test('10 in container, add 1 after every 2nd element', function (assert) { function skipEveryThird(i) { return i % 3 === 0 || i % 3 === 1 } var first = itemHelpers .itemsInContainer() .from(0) .to(14) .withPredicate(skipEveryThird) var second = itemHelpers.itemsInContainer().from(0).to(14).by(1) // Assert indices before assert.strictEqual(first.children.length, 10) var rootNode = render(first) var expectedIndices = [0, 1, 3, 4, 6, 7, 9, 10, 12, 13] for (var i = 0; i < 10; i++) { itemHelpers.expectTextOfChild( assert, rootNode, i, expectedIndices[i].toString() ) } // Assert indices after assert.strictEqual(second.children.length, 15) var patches = diff(first, second) var newRoot = patch(rootNode, patches) for (var j = 0; j < 15; j++) { itemHelpers.expectTextOfChild(assert, newRoot, j, j.toString()) } assert.end() }) test('move a single element to the end', function (assert) { var start = nodesFromArray([0, 5, 1, 2, 3, 4]) var end = nodesFromArray([0, 1, 2, 3, 4, 5]) var patches = diff(start, end) assertReorderEquals(assert, patches, { removes: [{key: '5', from: 1}], inserts: [{key: '5', to: 5}] }) assert.end() }) test('move a single element to a later position', function (assert) { var start = nodesFromArray([0, 4, 1, 2, 3, 5]) var end = nodesFromArray([0, 1, 2, 3, 4, 5]) var patches = diff(start, end) assertReorderEquals(assert, patches, { removes: [{ key: '4', from: 1 }], inserts: [{ key: '4', to: 4 }] }) assert.end() }) test('remove a single element from early in the list', function (assert) { var start = nodesFromArray([0, 1, 2, 3, 4]) var end = nodesFromArray([0, 2, 3, 4]) var patches = diff(start, end) var reorderPatch = getReorderPatch(patches) assert.strictEqual(reorderPatch, null) assert.end() }) test('move an element to a position after a removed element', function (assert) { var start = nodesFromArray([0, 1, 2, 3, 4, 5]) var end = nodesFromArray([0, 2, 3, 5, 4]) var patches = diff(start, end) assertReorderEquals(assert, patches, { removes: [ {from: 1, key: null}, {from: 4, key: '5'} ], inserts: [{to: 3, key: '5'}] }) assert.end() }) test('mixed keys move from i>0 to iclicked 1337 times
') assert.end() }) virtual-dom-2.1.1/test/sort.js000066400000000000000000000130171256011152500162620ustar00rootroot00000000000000// // This tests the performance of vtree/diff. // // This test in it's current configuration will test every array permutation of // N numbers from 0, 2, ... N for N = 1 up to and including N = 9. // // Furthermore, arrays for N 10, 20, 30, ... 1000 are tested. Since the number // of permutations is a factorial problem, we can't test all permutations for // these. We generate 100 arrays instead and randomly shuffle them for testing. // // Each test is timed in a benchmark version of the test which removes the // peripheral operations, and each test is also re-run against a validation // version, which checks to ensure that the arrays are actually sorted. // // Essentially this test proves that the sorting algorithm for N 0-based // consecutive integers works for ALL permutations up to length 9. // var PERMUTATION_START = 1; var PERMUTATION_END = 9; var SAMPLE_START = 10; var SAMPLE_END = 1000; var SAMPLE_COUNT = 100; var SAMPLE_INTERVAL = 10; var nodesFromArray = require('./lib/nodes-from-array.js'); var assertChildNodesFromArray = require('./lib/assert-childNodes-from-array.js'); var diff = require('../vtree/diff'); var render = require('../create-element.js'); var patch = require('../patch.js'); var assert = require('assert'); var document = require('global/document'); var testlingOutput = document.getElementById('__testling_output'); if (testlingOutput) { testlingOutput.parentNode.removeChild(testlingOutput); } runTest(); // validateOnly(); // benchmarkOnly(); function runTest() { forEachPermutation(PERMUTATION_START, PERMUTATION_END, testArrays); forEachSample(SAMPLE_START, SAMPLE_END, SAMPLE_COUNT, SAMPLE_INTERVAL, testArrays); } function testArrays(arrays) { runSort(arrays); runBench(arrays); } function validateOnly() { forEachPermutation(PERMUTATION_START, PERMUTATION_END, runSort); forEachSample(SAMPLE_START, SAMPLE_END, SAMPLE_COUNT, SAMPLE_INTERVAL, runSort); } function benchmarkOnly() { forEachPermutation(PERMUTATION_START, PERMUTATION_END, runBench); forEachSample(SAMPLE_START, SAMPLE_END, SAMPLE_COUNT, SAMPLE_INTERVAL, runBench); } function runBench(permutations) { var count = permutations.length; var arrayLength = permutations[0].goal.length; console.log('Benchmarking sort for length ', arrayLength); var startTime = Date.now(); for (var i = 0; i < count; i++) { var item = permutations[i]; var goal = nodesFromArray(item.goal); var shuffled = nodesFromArray(item.shuffled); var rootNode = render(shuffled); document.body.appendChild(rootNode); var reflow = rootNode.offsetWidth; var patches = diff(shuffled, goal); patch(rootNode, patches); reflow = rootNode.offsetWidth; document.body.removeChild(rootNode); } var totalTime = Date.now() - startTime; var average = totalTime / count >> 0 console.log('All (' + count + ') arrays sorted in', totalTime, 'ms'); console.log('An array of length', arrayLength, 'sorts in', average, 'ms'); } function runSort(permutations) { var count = permutations.length; console.log('Testing sort for length ', permutations[0].goal.length); for (var i = 0; i < count; i++) { var item = permutations[i]; var goal = nodesFromArray(item.goal); var shuffled = nodesFromArray(item.shuffled); var rootNode = render(shuffled); var patches = diff(shuffled, goal); patch(rootNode, patches); assertChildNodesFromArray(assert, item.goal, rootNode.childNodes); } console.log('All permutations sorted correctly'); } function forEachPermutation(start, end, run) { for (var arrayLength = start; arrayLength <= end; arrayLength++) { var array = createArray(arrayLength); console.log('Generating test permutations for length', arrayLength); var permutations = permutator(array); run(permutations); } } function permutator(inputArr) { var results = []; function permute(arr, memo) { memo = memo || []; var cur; for (var i = 0; i < arr.length; i++) { cur = arr.splice(i, 1); if (arr.length === 0) { results.push({ goal: inputArr, shuffled: memo.concat(cur) }); } permute(arr.slice(), memo.concat(cur)); arr.splice(i, 0, cur[0]); } return results; } return permute(inputArr); } function forEachSample(start, end, count, interval, run) { console.log(arguments); for (var i = start; i <= end; i += interval) { var samples = new Array(count); console.log("Generating", count, "sample arrays of length", i); for (var j = 0; j < count; j++) { var goal = createArray(i); samples[j] = { goal: goal, shuffled: shuffle(goal) }; } run(samples); } } function createArray(arrayLength) { var array = new Array(arrayLength); for (var numberToAdd = 0; numberToAdd < arrayLength; numberToAdd++) { array[numberToAdd] = numberToAdd; } return array; } function shuffle(array) { var currentIndex = array.length; var temporaryValue; var randomIndex; while (0 !== currentIndex) { randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } virtual-dom-2.1.1/test/style.js000066400000000000000000000021171256011152500164320ustar00rootroot00000000000000var test = require("tape") var document = require("global/document") var h = require("../h") var diff = require("../diff") var patch = require("../patch") var render = require("../create-element") var patchCount = require("./lib/patch-count") test("style patches correctly", function (assert) { var leftNode = h("div", { style: { border: "1px solid #000" } }) var rightNode = h("div", { style: { padding: "5px" } }) var patches = diff(leftNode, rightNode) assert.equal(patchCount(patches), 1); var rootNode = render(leftNode) assert.equal(rootNode.style.border, style("border", "1px solid #000")) var newRoot = patch(rootNode, patches) assert.equal(rootNode, newRoot) assert.equal(newRoot.style.padding, style("padding", "5px")) assert.equal(newRoot.style.border, style("border", "")) assert.end() }) function style(name, setValue) { var div = document.createElement("div") div.style[name] = setValue return div.style[name] } virtual-dom-2.1.1/test/thunk.js000066400000000000000000000034061256011152500164250ustar00rootroot00000000000000var test = require("tape") var isThunk = require("../vnode/is-thunk") var isVNode = require("../vnode/is-vnode") var VNode = require("../vnode/vnode") var diff = require("../diff.js") var patchCount = require("./lib/patch-count.js") function Thunk(tagName) { this.tagName = tagName } Thunk.prototype.render = function () { return new VNode(this.tagName) } Thunk.prototype.type = "Thunk" test("is thunk", function (assert) { var notThunk = {} var thunkLiteral = { type: "Thunk", render: function () {} } assert.notOk(isThunk(notThunk)) assert.ok(isThunk(thunkLiteral)) assert.ok(isThunk(new Thunk("div"))) assert.end() }) test("null or undefined previous renders thunk", function (assert) { var n = new Thunk("first") var u = new Thunk("second") var nullPatches = diff(null, n) var undefPatches = diff(undefined, u) assert.ok(isVNode(n.vnode)) assert.ok(isVNode(u.vnode)) assert.equal(n.vnode.tagName, "first") assert.equal(u.vnode.tagName, "second") assert.equal(patchCount(nullPatches), 1) assert.equal(patchCount(undefPatches), 1) assert.end() }) test("previous thunk passed to render", function (assert) { var renderCount = 0 var previousThunk = new Thunk("div") var nextThunk = { type: "Thunk", render: function (previous) { renderCount++ assert.equal(previous, previousThunk) return new VNode("test") } } var patches = diff(previousThunk, nextThunk) assert.equal(renderCount, 1) assert.equal(patchCount(patches), 1) assert.ok(isVNode(nextThunk.vnode)) assert.equal(nextThunk.vnode.tagName, "test") assert.end() }) virtual-dom-2.1.1/test/undefined-properties.js000066400000000000000000000076331256011152500214350ustar00rootroot00000000000000var test = require("tape") var isObject = require("is-object") var h = require("../h.js") var diff = require("../diff.js") var patch = require("../patch.js") var render = require("../create-element.js") test("undefined props are not set in create-element", function (assert) { var node = h("div", { special: undefined }) var rootNode = render(node) assert.ok(!("special" in rootNode)) assert.end() }) test("undefined removes all previous styles", function (assert) { var leftNode = h("div", { style: { display: "none", border: "1px solid #000" } }) var rightNode = h("div", { style: undefined }) var rootNode = createAndPatch(leftNode, rightNode) assert.equal(rootNode.style.display, style("display", "")) assert.equal(rootNode.style.border, style("border", "")) assert.end(); }) test("undefined style removes individual styles", function (assert) { var leftNode = h("div", { "style": { "display": "none" }}) var rightNode = h("div", { "style": undefined }) var rootNode = createAndPatch(leftNode, rightNode) assert.equal(rootNode.style.display, style("display", "")) assert.end() }) test("undefined ignored for hooks", function (assert) { function CheckNodeBeforeSet(value) { this.value = value } CheckNodeBeforeSet.prototype.hook = function (rootNode, propName) { var value = this.value if (value !== rootNode[propName]) { rootNode[propName] = value } } var leftNode = h("input", { value: new CheckNodeBeforeSet("hello") }) var rightNode = h("input", { value: undefined }) var rootNode = render(leftNode) assert.equal(rootNode.value, "hello") var newRoot = patch(rootNode, diff(leftNode, rightNode)) assert.equal(newRoot.value, "hello") assert.end() }) test("undefined nulls other complex types", function (assert) { var leftNode = h("input", { special: {} }) var rightNode = h("input", { special: null }) var rootNode = render(leftNode) assert.ok(isObject(rootNode.special)) var newRoot = patch(rootNode, diff(leftNode, rightNode)) assert.equal(newRoot.special, null) assert.end() }) test("null not ignored for value", function (assert) { var leftNode = h("input", { value: "hello" }) var rightNode = h("input", { value: null }) var rootNode = createAndPatch(leftNode, rightNode) assert.equal(rootNode.value, property("input", "value", null)) assert.end() }) test("null not ignored for objects", function (assert) { var leftNode = h("div", { "test": { "complex": "object" }}) var rightNode = h("div", { "test": null }) var rootNode = createAndPatch(leftNode, rightNode) assert.equal(rootNode.test, null) assert.end() }) test("null not ignored for hooks", function (assert) { function CheckNodeBeforeSet(value) { this.value = value } CheckNodeBeforeSet.prototype.hook = function (rootNode, propName) { var value = this.value if (value !== rootNode[propName]) { rootNode.value = value } } var leftNode = h("input", { value: new CheckNodeBeforeSet("hello") }) var rightNode = h("input", { value: null }) var rootNode = render(leftNode) assert.equal(rootNode.value, "hello") var newRoot = patch(rootNode, diff(leftNode, rightNode)) assert.equal(newRoot.value, property("input", "value", null)) assert.end() }) function createAndPatch(prev, curr) { var elem = render(prev) var patches = diff(prev, curr) return patch(elem, patches) } // Safely translates style values using the DOM in the browser function style(name, value) { var node = render(h()) node.style[name] = value return node.style[name] } // Safely transaltes node property using the DOM in the browser function property(tag, prop, value) { var node = render(h(tag)) node[prop] = value return node[prop] } virtual-dom-2.1.1/vdom/000077500000000000000000000000001256011152500147215ustar00rootroot00000000000000virtual-dom-2.1.1/vdom/README.md000066400000000000000000000015601256011152500162020ustar00rootroot00000000000000# vdom A DOM render and patch algorithm for vtree ## Motivation Given a `vtree` structure representing a DOM structure, we would like to either render the structure to a DOM node using `vdom/create-element` or we would like to update the DOM using the results of `vtree/diff` by patching the DOM with `vdom/patch` ## Example ```js var h = require("virtual-dom/h") var diff = require("virtual-dom/diff") var createElement = require("virtual-dom/create-element") var patch = require("virtual-dom/patch") var leftNode = h("div") var rightNode = h("text") // Render the left node to a DOM node var rootNode = createElement(leftNode) document.body.appendChild(rootNode) // Update the DOM with the results of a diff var patches = diff(leftNode, rightNode) patch(rootNode, patches) ``` ## Installation `npm install virtual-dom` ## Contributors - Matt Esch ## MIT Licenced virtual-dom-2.1.1/vdom/apply-properties.js000066400000000000000000000054241256011152500206030ustar00rootroot00000000000000var isObject = require("is-object") var isHook = require("../vnode/is-vhook.js") module.exports = applyProperties function applyProperties(node, props, previous) { for (var propName in props) { var propValue = props[propName] if (propValue === undefined) { removeProperty(node, propName, propValue, previous); } else if (isHook(propValue)) { removeProperty(node, propName, propValue, previous) if (propValue.hook) { propValue.hook(node, propName, previous ? previous[propName] : undefined) } } else { if (isObject(propValue)) { patchObject(node, props, previous, propName, propValue); } else { node[propName] = propValue } } } } function removeProperty(node, propName, propValue, previous) { if (previous) { var previousValue = previous[propName] if (!isHook(previousValue)) { if (propName === "attributes") { for (var attrName in previousValue) { node.removeAttribute(attrName) } } else if (propName === "style") { for (var i in previousValue) { node.style[i] = "" } } else if (typeof previousValue === "string") { node[propName] = "" } else { node[propName] = null } } else if (previousValue.unhook) { previousValue.unhook(node, propName, propValue) } } } function patchObject(node, props, previous, propName, propValue) { var previousValue = previous ? previous[propName] : undefined // Set attributes if (propName === "attributes") { for (var attrName in propValue) { var attrValue = propValue[attrName] if (attrValue === undefined) { node.removeAttribute(attrName) } else { node.setAttribute(attrName, attrValue) } } return } if(previousValue && isObject(previousValue) && getPrototype(previousValue) !== getPrototype(propValue)) { node[propName] = propValue return } if (!isObject(node[propName])) { node[propName] = {} } var replacer = propName === "style" ? "" : undefined for (var k in propValue) { var value = propValue[k] node[propName][k] = (value === undefined) ? replacer : value } } function getPrototype(value) { if (Object.getPrototypeOf) { return Object.getPrototypeOf(value) } else if (value.__proto__) { return value.__proto__ } else if (value.constructor) { return value.constructor.prototype } } virtual-dom-2.1.1/vdom/create-element.js000066400000000000000000000023251256011152500201530ustar00rootroot00000000000000var document = require("global/document") var applyProperties = require("./apply-properties") var isVNode = require("../vnode/is-vnode.js") var isVText = require("../vnode/is-vtext.js") var isWidget = require("../vnode/is-widget.js") var handleThunk = require("../vnode/handle-thunk.js") module.exports = createElement function createElement(vnode, opts) { var doc = opts ? opts.document || document : document var warn = opts ? opts.warn : null vnode = handleThunk(vnode).a if (isWidget(vnode)) { return vnode.init() } else if (isVText(vnode)) { return doc.createTextNode(vnode.text) } else if (!isVNode(vnode)) { if (warn) { warn("Item is not a valid virtual dom node", vnode) } return null } var node = (vnode.namespace === null) ? doc.createElement(vnode.tagName) : doc.createElementNS(vnode.namespace, vnode.tagName) var props = vnode.properties applyProperties(node, props) var children = vnode.children for (var i = 0; i < children.length; i++) { var childNode = createElement(children[i], opts) if (childNode) { node.appendChild(childNode) } } return node } virtual-dom-2.1.1/vdom/dom-index.js000066400000000000000000000043151256011152500171460ustar00rootroot00000000000000// Maps a virtual DOM tree onto a real DOM tree in an efficient manner. // We don't want to read all of the DOM nodes in the tree so we use // the in-order tree indexing to eliminate recursion down certain branches. // We only recurse into a DOM node if we know that it contains a child of // interest. var noChild = {} module.exports = domIndex function domIndex(rootNode, tree, indices, nodes) { if (!indices || indices.length === 0) { return {} } else { indices.sort(ascending) return recurse(rootNode, tree, indices, nodes, 0) } } function recurse(rootNode, tree, indices, nodes, rootIndex) { nodes = nodes || {} if (rootNode) { if (indexInRange(indices, rootIndex, rootIndex)) { nodes[rootIndex] = rootNode } var vChildren = tree.children if (vChildren) { var childNodes = rootNode.childNodes for (var i = 0; i < tree.children.length; i++) { rootIndex += 1 var vChild = vChildren[i] || noChild var nextIndex = rootIndex + (vChild.count || 0) // skip recursion down the tree if there are no nodes down here if (indexInRange(indices, rootIndex, nextIndex)) { recurse(childNodes[i], vChild, indices, nodes, rootIndex) } rootIndex = nextIndex } } } return nodes } // Binary search for an index in the interval [left, right] function indexInRange(indices, left, right) { if (indices.length === 0) { return false } var minIndex = 0 var maxIndex = indices.length - 1 var currentIndex var currentItem while (minIndex <= maxIndex) { currentIndex = ((maxIndex + minIndex) / 2) >> 0 currentItem = indices[currentIndex] if (minIndex === maxIndex) { return currentItem >= left && currentItem <= right } else if (currentItem < left) { minIndex = currentIndex + 1 } else if (currentItem > right) { maxIndex = currentIndex - 1 } else { return true } } return false; } function ascending(a, b) { return a > b ? 1 : -1 } virtual-dom-2.1.1/vdom/patch-op.js000066400000000000000000000076041256011152500170010ustar00rootroot00000000000000var applyProperties = require("./apply-properties") var isWidget = require("../vnode/is-widget.js") var VPatch = require("../vnode/vpatch.js") var updateWidget = require("./update-widget") module.exports = applyPatch function applyPatch(vpatch, domNode, renderOptions) { var type = vpatch.type var vNode = vpatch.vNode var patch = vpatch.patch switch (type) { case VPatch.REMOVE: return removeNode(domNode, vNode) case VPatch.INSERT: return insertNode(domNode, patch, renderOptions) case VPatch.VTEXT: return stringPatch(domNode, vNode, patch, renderOptions) case VPatch.WIDGET: return widgetPatch(domNode, vNode, patch, renderOptions) case VPatch.VNODE: return vNodePatch(domNode, vNode, patch, renderOptions) case VPatch.ORDER: reorderChildren(domNode, patch) return domNode case VPatch.PROPS: applyProperties(domNode, patch, vNode.properties) return domNode case VPatch.THUNK: return replaceRoot(domNode, renderOptions.patch(domNode, patch, renderOptions)) default: return domNode } } function removeNode(domNode, vNode) { var parentNode = domNode.parentNode if (parentNode) { parentNode.removeChild(domNode) } destroyWidget(domNode, vNode); return null } function insertNode(parentNode, vNode, renderOptions) { var newNode = renderOptions.render(vNode, renderOptions) if (parentNode) { parentNode.appendChild(newNode) } return parentNode } function stringPatch(domNode, leftVNode, vText, renderOptions) { var newNode if (domNode.nodeType === 3) { domNode.replaceData(0, domNode.length, vText.text) newNode = domNode } else { var parentNode = domNode.parentNode newNode = renderOptions.render(vText, renderOptions) if (parentNode && newNode !== domNode) { parentNode.replaceChild(newNode, domNode) } } return newNode } function widgetPatch(domNode, leftVNode, widget, renderOptions) { var updating = updateWidget(leftVNode, widget) var newNode if (updating) { newNode = widget.update(leftVNode, domNode) || domNode } else { newNode = renderOptions.render(widget, renderOptions) } var parentNode = domNode.parentNode if (parentNode && newNode !== domNode) { parentNode.replaceChild(newNode, domNode) } if (!updating) { destroyWidget(domNode, leftVNode) } return newNode } function vNodePatch(domNode, leftVNode, vNode, renderOptions) { var parentNode = domNode.parentNode var newNode = renderOptions.render(vNode, renderOptions) if (parentNode && newNode !== domNode) { parentNode.replaceChild(newNode, domNode) } return newNode } function destroyWidget(domNode, w) { if (typeof w.destroy === "function" && isWidget(w)) { w.destroy(domNode) } } function reorderChildren(domNode, moves) { var childNodes = domNode.childNodes var keyMap = {} var node var remove var insert for (var i = 0; i < moves.removes.length; i++) { remove = moves.removes[i] node = childNodes[remove.from] if (remove.key) { keyMap[remove.key] = node } domNode.removeChild(node) } var length = childNodes.length for (var j = 0; j < moves.inserts.length; j++) { insert = moves.inserts[j] node = keyMap[insert.key] // this is the weirdest bug i've ever seen in webkit domNode.insertBefore(node, insert.to >= length++ ? null : childNodes[insert.to]) } } function replaceRoot(oldRoot, newRoot) { if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) { oldRoot.parentNode.replaceChild(newRoot, oldRoot) } return newRoot; } virtual-dom-2.1.1/vdom/patch.js000066400000000000000000000036421256011152500163630ustar00rootroot00000000000000var document = require("global/document") var isArray = require("x-is-array") var render = require("./create-element") var domIndex = require("./dom-index") var patchOp = require("./patch-op") module.exports = patch function patch(rootNode, patches, renderOptions) { renderOptions = renderOptions || {} renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch ? renderOptions.patch : patchRecursive renderOptions.render = renderOptions.render || render return renderOptions.patch(rootNode, patches, renderOptions) } function patchRecursive(rootNode, patches, renderOptions) { var indices = patchIndices(patches) if (indices.length === 0) { return rootNode } var index = domIndex(rootNode, patches.a, indices) var ownerDocument = rootNode.ownerDocument if (!renderOptions.document && ownerDocument !== document) { renderOptions.document = ownerDocument } for (var i = 0; i < indices.length; i++) { var nodeIndex = indices[i] rootNode = applyPatch(rootNode, index[nodeIndex], patches[nodeIndex], renderOptions) } return rootNode } function applyPatch(rootNode, domNode, patchList, renderOptions) { if (!domNode) { return rootNode } var newNode if (isArray(patchList)) { for (var i = 0; i < patchList.length; i++) { newNode = patchOp(patchList[i], domNode, renderOptions) if (domNode === rootNode) { rootNode = newNode } } } else { newNode = patchOp(patchList, domNode, renderOptions) if (domNode === rootNode) { rootNode = newNode } } return rootNode } function patchIndices(patches) { var indices = [] for (var key in patches) { if (key !== "a") { indices.push(Number(key)) } } return indices } virtual-dom-2.1.1/vdom/test/000077500000000000000000000000001256011152500157005ustar00rootroot00000000000000virtual-dom-2.1.1/vdom/test/dom-index.js000066400000000000000000000037531256011152500201320ustar00rootroot00000000000000var test = require("tape") var VNode = require("../../vnode/vnode") var VText = require("../../vnode/vtext") var diff = require("../../vtree/diff") var createElement = require("../create-element") var patch = require("../patch") test("indexing over thunk root", function (assert) { var leftThunk = { type: "Thunk", render: function () { return new VNode("div", { className:"test" }, [new VText("Left")]) } } var rightThunk = { type: "Thunk", render: function () { return new VNode("div", { className: "test" }, [new VText("Right")]) } } var root = createElement(leftThunk) var patches = diff(leftThunk, rightThunk) var newRoot = patch(root, patches) assert.equal(newRoot.childNodes[0].data, "Right") assert.end() }) test("indexing over thunk child", function (assert) { var leftNode = new VNode("div", { className: "parent-node" }, [ new VNode("div"), new VText("test"), { type: "Thunk", render: function () { return new VNode("div", { className:"test" }, [new VText("Left")]) } }, new VNode("div"), new VText("test") ]) var rightNode = new VNode("div", { className: "parent-node" }, [ new VNode("div"), new VText("test"), { type: "Thunk", render: function () { return new VNode("div", { className:"test" }, [new VText("Right")]) } }, new VNode("div"), new VText("test") ]) var root = createElement(leftNode) var patches = diff(leftNode, rightNode) patch(root, patches) assert.equal(root.childNodes[2].childNodes[0].data, "Right") assert.end() }) virtual-dom-2.1.1/vdom/test/index.js000066400000000000000000000001141256011152500173410ustar00rootroot00000000000000require("./dom-index") require("./patch-index") require("./patch-op-index") virtual-dom-2.1.1/vdom/test/patch-index.js000066400000000000000000000016331256011152500204450ustar00rootroot00000000000000var test = require("tape") var VNode = require("../../vnode/vnode") var VText = require("../../vnode/vtext") var diff = require("../../vtree/diff") var createElement = require("../create-element") var patch = require("../patch") test("overrided patch function is correctly used and received correct options", function (assert) { function patchCustom(rootNode, patches, renderOptions) { return { rootNode: rootNode, patches: patches, renderOptions: renderOptions } } function createElementCustom(vnode) {} var rootNode = new VNode("div") var patches = {} var renderOptions = { patch: patchCustom, render: createElementCustom } var result = patch(rootNode, patches, renderOptions) assert.equal(result.rootNode, rootNode) assert.equal(result.patches, patches) assert.equal(result.renderOptions, renderOptions) assert.end() })virtual-dom-2.1.1/vdom/test/patch-op-index.js000066400000000000000000000037551256011152500210700ustar00rootroot00000000000000var test = require("tape") var VNode = require("../../vnode/vnode") var VText = require("../../vnode/vtext") var diff = require("../../vtree/diff") var document = require("global/document") var createElement = require("../create-element") var patch = require("../patch") var createElementCustom = function(vnode) { var created = createElement(vnode) created.customCreation = true return created } function assertPachedNodeIsMarked(leftNode, rightNode, assert) { var root = createElementCustom(leftNode) var patches = diff(leftNode, rightNode) var newRoot = patch(root, patches, { render: createElementCustom }) assert.equal(newRoot.childNodes[0].customCreation, true) assert.end() } test("overrided createElement is used on node insertion", function (assert) { var leftNode = new VNode("div") var rightNode = new VNode("div", {}, [new VNode("div")]) assertPachedNodeIsMarked(leftNode, rightNode, assert) }) test("overrided createElement is used for patching vnodes", function (assert) { var leftNode = new VNode("div", {}, [new VNode("div")]) var rightNode = new VNode("div", {}, [new VNode("span")]) assertPachedNodeIsMarked(leftNode, rightNode, assert) }) test("overrided createElement is used for patching text nodes", function (assert) { var leftNode = new VNode("div", {}, [new VNode("div")]) var rightNode = new VNode("div", {}, [new VText("hello")]) assertPachedNodeIsMarked(leftNode, rightNode, assert) }) test("overrided createElement is used for patching widget nodes", function (assert) { var Widget = function (){} Widget.prototype.type = "Widget" Widget.prototype.init = function(){ return document.createElement("div") } Widget.prototype.update = function(previous, domNode){ return null } Widget.prototype.destroy = function(domNode){} var leftNode = new VNode("div", {}, [new VNode("div")]) var rightNode = new VNode("div", {}, [new Widget()]) assertPachedNodeIsMarked(leftNode, rightNode, assert) }) virtual-dom-2.1.1/vdom/update-widget.js000066400000000000000000000004711256011152500200240ustar00rootroot00000000000000var isWidget = require("../vnode/is-widget.js") module.exports = updateWidget function updateWidget(a, b) { if (isWidget(a) && isWidget(b)) { if ("name" in a && "name" in b) { return a.id === b.id } else { return a.init === b.init } } return false } virtual-dom-2.1.1/virtual-hyperscript/000077500000000000000000000000001256011152500200145ustar00rootroot00000000000000virtual-dom-2.1.1/virtual-hyperscript/README.md000066400000000000000000000040051256011152500212720ustar00rootroot00000000000000# virtual-hyperscript A DSL for creating virtual trees ## Example ```js var h = require('virtual-dom/h') var tree = h('div.foo#some-id', [ h('span', 'some text'), h('input', { type: 'text', value: 'foo' }) ]) ``` ## Docs See [hyperscript](https://github.com/dominictarr/hyperscript) which has the same interface. Except `virtual-hyperscript` returns a virtual DOM tree instead of a DOM element. ### `h(selector, properties, children)` `h()` takes a selector, an optional properties object and an optional array of children or a child that is a string. If you pass it a selector like `span.foo.bar#some-id` it will parse the selector and change the `id` and `className` properties of the `properties` object. If you pass it an array of `children` it will have child nodes, normally ou want to create children with `h()`. If you pass it a string it will create an array containing a single child node that is a text element. ### Special properties in `h()` #### `key` If you call `h` with `h('div', { key: someKey })` it will set a key on the return `VNode`. This `key` is not a normal DOM property but is a virtual-dom optimization hint. It basically tells virtual-dom to re-order DOM nodes instead of mutating them. #### `namespace` If you call `h` with `h('div', { namespace: "http://www.w3.org/2000/svg" })` it will set the namespace on the returned `VNode`. This `namespace` is not a normal DOM property, instead it will cause `vdom` to create a DOM element with a namespace. #### `ev-*` **Note:** You must create an instance of `dom-delegator` for `ev-*` to work. If you call `h` with `h('div', { ev-click: function (ev) { } })` it will store the event handler on the dom element. It will not set a property `'ev-foo'` on the DOM element. This means that `dom-delegator` will recognise the event handler on that element and correctly call your handler when an a click event happens. ## Installation `npm install virtual-dom` ## Contributors - Raynos - Matt Esch ## MIT Licenced virtual-dom-2.1.1/virtual-hyperscript/hooks/000077500000000000000000000000001256011152500211375ustar00rootroot00000000000000virtual-dom-2.1.1/virtual-hyperscript/hooks/attribute-hook.js000066400000000000000000000016551256011152500244450ustar00rootroot00000000000000'use strict'; module.exports = AttributeHook; function AttributeHook(namespace, value) { if (!(this instanceof AttributeHook)) { return new AttributeHook(namespace, value); } this.namespace = namespace; this.value = value; } AttributeHook.prototype.hook = function (node, prop, prev) { if (prev && prev.type === 'AttributeHook' && prev.value === this.value && prev.namespace === this.namespace) { return; } node.setAttributeNS(this.namespace, prop, this.value); }; AttributeHook.prototype.unhook = function (node, prop, next) { if (next && next.type === 'AttributeHook' && next.namespace === this.namespace) { return; } var colonPosition = prop.indexOf(':'); var localName = colonPosition > -1 ? prop.substr(colonPosition + 1) : prop; node.removeAttributeNS(this.namespace, localName); }; AttributeHook.prototype.type = 'AttributeHook'; virtual-dom-2.1.1/virtual-hyperscript/hooks/ev-hook.js000066400000000000000000000010241256011152500230420ustar00rootroot00000000000000'use strict'; var EvStore = require('ev-store'); module.exports = EvHook; function EvHook(value) { if (!(this instanceof EvHook)) { return new EvHook(value); } this.value = value; } EvHook.prototype.hook = function (node, propertyName) { var es = EvStore(node); var propName = propertyName.substr(3); es[propName] = this.value; }; EvHook.prototype.unhook = function(node, propertyName) { var es = EvStore(node); var propName = propertyName.substr(3); es[propName] = undefined; }; virtual-dom-2.1.1/virtual-hyperscript/hooks/focus-hook.js000066400000000000000000000006561256011152500235610ustar00rootroot00000000000000'use strict'; var document = require("global/document"); var nextTick = require("next-tick"); module.exports = MutableFocusHook; function MutableFocusHook() { if (!(this instanceof MutableFocusHook)) { return new MutableFocusHook(); } } MutableFocusHook.prototype.hook = function (node) { nextTick(function () { if (document.activeElement !== node) { node.focus(); } }); }; virtual-dom-2.1.1/virtual-hyperscript/hooks/soft-set-hook.js000066400000000000000000000005331256011152500242000ustar00rootroot00000000000000'use strict'; module.exports = SoftSetHook; function SoftSetHook(value) { if (!(this instanceof SoftSetHook)) { return new SoftSetHook(value); } this.value = value; } SoftSetHook.prototype.hook = function (node, propertyName) { if (node[propertyName] !== this.value) { node[propertyName] = this.value; } }; virtual-dom-2.1.1/virtual-hyperscript/index.js000066400000000000000000000067211256011152500214670ustar00rootroot00000000000000'use strict'; var isArray = require('x-is-array'); var VNode = require('../vnode/vnode.js'); var VText = require('../vnode/vtext.js'); var isVNode = require('../vnode/is-vnode'); var isVText = require('../vnode/is-vtext'); var isWidget = require('../vnode/is-widget'); var isHook = require('../vnode/is-vhook'); var isVThunk = require('../vnode/is-thunk'); var parseTag = require('./parse-tag.js'); var softSetHook = require('./hooks/soft-set-hook.js'); var evHook = require('./hooks/ev-hook.js'); module.exports = h; function h(tagName, properties, children) { var childNodes = []; var tag, props, key, namespace; if (!children && isChildren(properties)) { children = properties; props = {}; } props = props || properties || {}; tag = parseTag(tagName, props); // support keys if (props.hasOwnProperty('key')) { key = props.key; props.key = undefined; } // support namespace if (props.hasOwnProperty('namespace')) { namespace = props.namespace; props.namespace = undefined; } // fix cursor bug if (tag === 'INPUT' && !namespace && props.hasOwnProperty('value') && props.value !== undefined && !isHook(props.value) ) { props.value = softSetHook(props.value); } transformProperties(props); if (children !== undefined && children !== null) { addChild(children, childNodes, tag, props); } return new VNode(tag, props, childNodes, key, namespace); } function addChild(c, childNodes, tag, props) { if (typeof c === 'string') { childNodes.push(new VText(c)); } else if (typeof c === 'number') { childNodes.push(new VText(String(c))); } else if (isChild(c)) { childNodes.push(c); } else if (isArray(c)) { for (var i = 0; i < c.length; i++) { addChild(c[i], childNodes, tag, props); } } else if (c === null || c === undefined) { return; } else { throw UnexpectedVirtualElement({ foreignObject: c, parentVnode: { tagName: tag, properties: props } }); } } function transformProperties(props) { for (var propName in props) { if (props.hasOwnProperty(propName)) { var value = props[propName]; if (isHook(value)) { continue; } if (propName.substr(0, 3) === 'ev-') { // add ev-foo support props[propName] = evHook(value); } } } } function isChild(x) { return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x); } function isChildren(x) { return typeof x === 'string' || isArray(x) || isChild(x); } function UnexpectedVirtualElement(data) { var err = new Error(); err.type = 'virtual-hyperscript.unexpected.virtual-element'; err.message = 'Unexpected virtual child passed to h().\n' + 'Expected a VNode / Vthunk / VWidget / string but:\n' + 'got:\n' + errorString(data.foreignObject) + '.\n' + 'The parent vnode is:\n' + errorString(data.parentVnode) '\n' + 'Suggested fix: change your `h(..., [ ... ])` callsite.'; err.foreignObject = data.foreignObject; err.parentVnode = data.parentVnode; return err; } function errorString(obj) { try { return JSON.stringify(obj, null, ' '); } catch (e) { return String(obj); } } virtual-dom-2.1.1/virtual-hyperscript/parse-tag.js000066400000000000000000000021671256011152500222430ustar00rootroot00000000000000'use strict'; var split = require('browser-split'); var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/; var notClassId = /^\.|#/; module.exports = parseTag; function parseTag(tag, props) { if (!tag) { return 'DIV'; } var noId = !(props.hasOwnProperty('id')); var tagParts = split(tag, classIdSplit); var tagName = null; if (notClassId.test(tagParts[1])) { tagName = 'DIV'; } var classes, part, type, i; for (i = 0; i < tagParts.length; i++) { part = tagParts[i]; if (!part) { continue; } type = part.charAt(0); if (!tagName) { tagName = part; } else if (type === '.') { classes = classes || []; classes.push(part.substring(1, part.length)); } else if (type === '#' && noId) { props.id = part.substring(1, part.length); } } if (classes) { if (props.className) { classes.push(props.className); } props.className = classes.join(' '); } return props.namespace ? tagName : tagName.toUpperCase(); } virtual-dom-2.1.1/virtual-hyperscript/svg-attribute-namespace.js000066400000000000000000000257541256011152500251210ustar00rootroot00000000000000'use strict'; var DEFAULT_NAMESPACE = null; var EV_NAMESPACE = 'http://www.w3.org/2001/xml-events'; var XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink'; var XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace'; // http://www.w3.org/TR/SVGTiny12/attributeTable.html // http://www.w3.org/TR/SVG/attindex.html var SVG_PROPERTIES = { 'about': DEFAULT_NAMESPACE, 'accent-height': DEFAULT_NAMESPACE, 'accumulate': DEFAULT_NAMESPACE, 'additive': DEFAULT_NAMESPACE, 'alignment-baseline': DEFAULT_NAMESPACE, 'alphabetic': DEFAULT_NAMESPACE, 'amplitude': DEFAULT_NAMESPACE, 'arabic-form': DEFAULT_NAMESPACE, 'ascent': DEFAULT_NAMESPACE, 'attributeName': DEFAULT_NAMESPACE, 'attributeType': DEFAULT_NAMESPACE, 'azimuth': DEFAULT_NAMESPACE, 'bandwidth': DEFAULT_NAMESPACE, 'baseFrequency': DEFAULT_NAMESPACE, 'baseProfile': DEFAULT_NAMESPACE, 'baseline-shift': DEFAULT_NAMESPACE, 'bbox': DEFAULT_NAMESPACE, 'begin': DEFAULT_NAMESPACE, 'bias': DEFAULT_NAMESPACE, 'by': DEFAULT_NAMESPACE, 'calcMode': DEFAULT_NAMESPACE, 'cap-height': DEFAULT_NAMESPACE, 'class': DEFAULT_NAMESPACE, 'clip': DEFAULT_NAMESPACE, 'clip-path': DEFAULT_NAMESPACE, 'clip-rule': DEFAULT_NAMESPACE, 'clipPathUnits': DEFAULT_NAMESPACE, 'color': DEFAULT_NAMESPACE, 'color-interpolation': DEFAULT_NAMESPACE, 'color-interpolation-filters': DEFAULT_NAMESPACE, 'color-profile': DEFAULT_NAMESPACE, 'color-rendering': DEFAULT_NAMESPACE, 'content': DEFAULT_NAMESPACE, 'contentScriptType': DEFAULT_NAMESPACE, 'contentStyleType': DEFAULT_NAMESPACE, 'cursor': DEFAULT_NAMESPACE, 'cx': DEFAULT_NAMESPACE, 'cy': DEFAULT_NAMESPACE, 'd': DEFAULT_NAMESPACE, 'datatype': DEFAULT_NAMESPACE, 'defaultAction': DEFAULT_NAMESPACE, 'descent': DEFAULT_NAMESPACE, 'diffuseConstant': DEFAULT_NAMESPACE, 'direction': DEFAULT_NAMESPACE, 'display': DEFAULT_NAMESPACE, 'divisor': DEFAULT_NAMESPACE, 'dominant-baseline': DEFAULT_NAMESPACE, 'dur': DEFAULT_NAMESPACE, 'dx': DEFAULT_NAMESPACE, 'dy': DEFAULT_NAMESPACE, 'edgeMode': DEFAULT_NAMESPACE, 'editable': DEFAULT_NAMESPACE, 'elevation': DEFAULT_NAMESPACE, 'enable-background': DEFAULT_NAMESPACE, 'end': DEFAULT_NAMESPACE, 'ev:event': EV_NAMESPACE, 'event': DEFAULT_NAMESPACE, 'exponent': DEFAULT_NAMESPACE, 'externalResourcesRequired': DEFAULT_NAMESPACE, 'fill': DEFAULT_NAMESPACE, 'fill-opacity': DEFAULT_NAMESPACE, 'fill-rule': DEFAULT_NAMESPACE, 'filter': DEFAULT_NAMESPACE, 'filterRes': DEFAULT_NAMESPACE, 'filterUnits': DEFAULT_NAMESPACE, 'flood-color': DEFAULT_NAMESPACE, 'flood-opacity': DEFAULT_NAMESPACE, 'focusHighlight': DEFAULT_NAMESPACE, 'focusable': DEFAULT_NAMESPACE, 'font-family': DEFAULT_NAMESPACE, 'font-size': DEFAULT_NAMESPACE, 'font-size-adjust': DEFAULT_NAMESPACE, 'font-stretch': DEFAULT_NAMESPACE, 'font-style': DEFAULT_NAMESPACE, 'font-variant': DEFAULT_NAMESPACE, 'font-weight': DEFAULT_NAMESPACE, 'format': DEFAULT_NAMESPACE, 'from': DEFAULT_NAMESPACE, 'fx': DEFAULT_NAMESPACE, 'fy': DEFAULT_NAMESPACE, 'g1': DEFAULT_NAMESPACE, 'g2': DEFAULT_NAMESPACE, 'glyph-name': DEFAULT_NAMESPACE, 'glyph-orientation-horizontal': DEFAULT_NAMESPACE, 'glyph-orientation-vertical': DEFAULT_NAMESPACE, 'glyphRef': DEFAULT_NAMESPACE, 'gradientTransform': DEFAULT_NAMESPACE, 'gradientUnits': DEFAULT_NAMESPACE, 'handler': DEFAULT_NAMESPACE, 'hanging': DEFAULT_NAMESPACE, 'height': DEFAULT_NAMESPACE, 'horiz-adv-x': DEFAULT_NAMESPACE, 'horiz-origin-x': DEFAULT_NAMESPACE, 'horiz-origin-y': DEFAULT_NAMESPACE, 'id': DEFAULT_NAMESPACE, 'ideographic': DEFAULT_NAMESPACE, 'image-rendering': DEFAULT_NAMESPACE, 'in': DEFAULT_NAMESPACE, 'in2': DEFAULT_NAMESPACE, 'initialVisibility': DEFAULT_NAMESPACE, 'intercept': DEFAULT_NAMESPACE, 'k': DEFAULT_NAMESPACE, 'k1': DEFAULT_NAMESPACE, 'k2': DEFAULT_NAMESPACE, 'k3': DEFAULT_NAMESPACE, 'k4': DEFAULT_NAMESPACE, 'kernelMatrix': DEFAULT_NAMESPACE, 'kernelUnitLength': DEFAULT_NAMESPACE, 'kerning': DEFAULT_NAMESPACE, 'keyPoints': DEFAULT_NAMESPACE, 'keySplines': DEFAULT_NAMESPACE, 'keyTimes': DEFAULT_NAMESPACE, 'lang': DEFAULT_NAMESPACE, 'lengthAdjust': DEFAULT_NAMESPACE, 'letter-spacing': DEFAULT_NAMESPACE, 'lighting-color': DEFAULT_NAMESPACE, 'limitingConeAngle': DEFAULT_NAMESPACE, 'local': DEFAULT_NAMESPACE, 'marker-end': DEFAULT_NAMESPACE, 'marker-mid': DEFAULT_NAMESPACE, 'marker-start': DEFAULT_NAMESPACE, 'markerHeight': DEFAULT_NAMESPACE, 'markerUnits': DEFAULT_NAMESPACE, 'markerWidth': DEFAULT_NAMESPACE, 'mask': DEFAULT_NAMESPACE, 'maskContentUnits': DEFAULT_NAMESPACE, 'maskUnits': DEFAULT_NAMESPACE, 'mathematical': DEFAULT_NAMESPACE, 'max': DEFAULT_NAMESPACE, 'media': DEFAULT_NAMESPACE, 'mediaCharacterEncoding': DEFAULT_NAMESPACE, 'mediaContentEncodings': DEFAULT_NAMESPACE, 'mediaSize': DEFAULT_NAMESPACE, 'mediaTime': DEFAULT_NAMESPACE, 'method': DEFAULT_NAMESPACE, 'min': DEFAULT_NAMESPACE, 'mode': DEFAULT_NAMESPACE, 'name': DEFAULT_NAMESPACE, 'nav-down': DEFAULT_NAMESPACE, 'nav-down-left': DEFAULT_NAMESPACE, 'nav-down-right': DEFAULT_NAMESPACE, 'nav-left': DEFAULT_NAMESPACE, 'nav-next': DEFAULT_NAMESPACE, 'nav-prev': DEFAULT_NAMESPACE, 'nav-right': DEFAULT_NAMESPACE, 'nav-up': DEFAULT_NAMESPACE, 'nav-up-left': DEFAULT_NAMESPACE, 'nav-up-right': DEFAULT_NAMESPACE, 'numOctaves': DEFAULT_NAMESPACE, 'observer': DEFAULT_NAMESPACE, 'offset': DEFAULT_NAMESPACE, 'opacity': DEFAULT_NAMESPACE, 'operator': DEFAULT_NAMESPACE, 'order': DEFAULT_NAMESPACE, 'orient': DEFAULT_NAMESPACE, 'orientation': DEFAULT_NAMESPACE, 'origin': DEFAULT_NAMESPACE, 'overflow': DEFAULT_NAMESPACE, 'overlay': DEFAULT_NAMESPACE, 'overline-position': DEFAULT_NAMESPACE, 'overline-thickness': DEFAULT_NAMESPACE, 'panose-1': DEFAULT_NAMESPACE, 'path': DEFAULT_NAMESPACE, 'pathLength': DEFAULT_NAMESPACE, 'patternContentUnits': DEFAULT_NAMESPACE, 'patternTransform': DEFAULT_NAMESPACE, 'patternUnits': DEFAULT_NAMESPACE, 'phase': DEFAULT_NAMESPACE, 'playbackOrder': DEFAULT_NAMESPACE, 'pointer-events': DEFAULT_NAMESPACE, 'points': DEFAULT_NAMESPACE, 'pointsAtX': DEFAULT_NAMESPACE, 'pointsAtY': DEFAULT_NAMESPACE, 'pointsAtZ': DEFAULT_NAMESPACE, 'preserveAlpha': DEFAULT_NAMESPACE, 'preserveAspectRatio': DEFAULT_NAMESPACE, 'primitiveUnits': DEFAULT_NAMESPACE, 'propagate': DEFAULT_NAMESPACE, 'property': DEFAULT_NAMESPACE, 'r': DEFAULT_NAMESPACE, 'radius': DEFAULT_NAMESPACE, 'refX': DEFAULT_NAMESPACE, 'refY': DEFAULT_NAMESPACE, 'rel': DEFAULT_NAMESPACE, 'rendering-intent': DEFAULT_NAMESPACE, 'repeatCount': DEFAULT_NAMESPACE, 'repeatDur': DEFAULT_NAMESPACE, 'requiredExtensions': DEFAULT_NAMESPACE, 'requiredFeatures': DEFAULT_NAMESPACE, 'requiredFonts': DEFAULT_NAMESPACE, 'requiredFormats': DEFAULT_NAMESPACE, 'resource': DEFAULT_NAMESPACE, 'restart': DEFAULT_NAMESPACE, 'result': DEFAULT_NAMESPACE, 'rev': DEFAULT_NAMESPACE, 'role': DEFAULT_NAMESPACE, 'rotate': DEFAULT_NAMESPACE, 'rx': DEFAULT_NAMESPACE, 'ry': DEFAULT_NAMESPACE, 'scale': DEFAULT_NAMESPACE, 'seed': DEFAULT_NAMESPACE, 'shape-rendering': DEFAULT_NAMESPACE, 'slope': DEFAULT_NAMESPACE, 'snapshotTime': DEFAULT_NAMESPACE, 'spacing': DEFAULT_NAMESPACE, 'specularConstant': DEFAULT_NAMESPACE, 'specularExponent': DEFAULT_NAMESPACE, 'spreadMethod': DEFAULT_NAMESPACE, 'startOffset': DEFAULT_NAMESPACE, 'stdDeviation': DEFAULT_NAMESPACE, 'stemh': DEFAULT_NAMESPACE, 'stemv': DEFAULT_NAMESPACE, 'stitchTiles': DEFAULT_NAMESPACE, 'stop-color': DEFAULT_NAMESPACE, 'stop-opacity': DEFAULT_NAMESPACE, 'strikethrough-position': DEFAULT_NAMESPACE, 'strikethrough-thickness': DEFAULT_NAMESPACE, 'string': DEFAULT_NAMESPACE, 'stroke': DEFAULT_NAMESPACE, 'stroke-dasharray': DEFAULT_NAMESPACE, 'stroke-dashoffset': DEFAULT_NAMESPACE, 'stroke-linecap': DEFAULT_NAMESPACE, 'stroke-linejoin': DEFAULT_NAMESPACE, 'stroke-miterlimit': DEFAULT_NAMESPACE, 'stroke-opacity': DEFAULT_NAMESPACE, 'stroke-width': DEFAULT_NAMESPACE, 'surfaceScale': DEFAULT_NAMESPACE, 'syncBehavior': DEFAULT_NAMESPACE, 'syncBehaviorDefault': DEFAULT_NAMESPACE, 'syncMaster': DEFAULT_NAMESPACE, 'syncTolerance': DEFAULT_NAMESPACE, 'syncToleranceDefault': DEFAULT_NAMESPACE, 'systemLanguage': DEFAULT_NAMESPACE, 'tableValues': DEFAULT_NAMESPACE, 'target': DEFAULT_NAMESPACE, 'targetX': DEFAULT_NAMESPACE, 'targetY': DEFAULT_NAMESPACE, 'text-anchor': DEFAULT_NAMESPACE, 'text-decoration': DEFAULT_NAMESPACE, 'text-rendering': DEFAULT_NAMESPACE, 'textLength': DEFAULT_NAMESPACE, 'timelineBegin': DEFAULT_NAMESPACE, 'title': DEFAULT_NAMESPACE, 'to': DEFAULT_NAMESPACE, 'transform': DEFAULT_NAMESPACE, 'transformBehavior': DEFAULT_NAMESPACE, 'type': DEFAULT_NAMESPACE, 'typeof': DEFAULT_NAMESPACE, 'u1': DEFAULT_NAMESPACE, 'u2': DEFAULT_NAMESPACE, 'underline-position': DEFAULT_NAMESPACE, 'underline-thickness': DEFAULT_NAMESPACE, 'unicode': DEFAULT_NAMESPACE, 'unicode-bidi': DEFAULT_NAMESPACE, 'unicode-range': DEFAULT_NAMESPACE, 'units-per-em': DEFAULT_NAMESPACE, 'v-alphabetic': DEFAULT_NAMESPACE, 'v-hanging': DEFAULT_NAMESPACE, 'v-ideographic': DEFAULT_NAMESPACE, 'v-mathematical': DEFAULT_NAMESPACE, 'values': DEFAULT_NAMESPACE, 'version': DEFAULT_NAMESPACE, 'vert-adv-y': DEFAULT_NAMESPACE, 'vert-origin-x': DEFAULT_NAMESPACE, 'vert-origin-y': DEFAULT_NAMESPACE, 'viewBox': DEFAULT_NAMESPACE, 'viewTarget': DEFAULT_NAMESPACE, 'visibility': DEFAULT_NAMESPACE, 'width': DEFAULT_NAMESPACE, 'widths': DEFAULT_NAMESPACE, 'word-spacing': DEFAULT_NAMESPACE, 'writing-mode': DEFAULT_NAMESPACE, 'x': DEFAULT_NAMESPACE, 'x-height': DEFAULT_NAMESPACE, 'x1': DEFAULT_NAMESPACE, 'x2': DEFAULT_NAMESPACE, 'xChannelSelector': DEFAULT_NAMESPACE, 'xlink:actuate': XLINK_NAMESPACE, 'xlink:arcrole': XLINK_NAMESPACE, 'xlink:href': XLINK_NAMESPACE, 'xlink:role': XLINK_NAMESPACE, 'xlink:show': XLINK_NAMESPACE, 'xlink:title': XLINK_NAMESPACE, 'xlink:type': XLINK_NAMESPACE, 'xml:base': XML_NAMESPACE, 'xml:id': XML_NAMESPACE, 'xml:lang': XML_NAMESPACE, 'xml:space': XML_NAMESPACE, 'y': DEFAULT_NAMESPACE, 'y1': DEFAULT_NAMESPACE, 'y2': DEFAULT_NAMESPACE, 'yChannelSelector': DEFAULT_NAMESPACE, 'z': DEFAULT_NAMESPACE, 'zoomAndPan': DEFAULT_NAMESPACE }; module.exports = SVGAttributeNamespace; function SVGAttributeNamespace(value) { if (SVG_PROPERTIES.hasOwnProperty(value)) { return SVG_PROPERTIES[value]; } } virtual-dom-2.1.1/virtual-hyperscript/svg.js000066400000000000000000000026411256011152500211540ustar00rootroot00000000000000'use strict'; var isArray = require('x-is-array'); var h = require('./index.js'); var SVGAttributeNamespace = require('./svg-attribute-namespace'); var attributeHook = require('./hooks/attribute-hook'); var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; module.exports = svg; function svg(tagName, properties, children) { if (!children && isChildren(properties)) { children = properties; properties = {}; } properties = properties || {}; // set namespace for svg properties.namespace = SVG_NAMESPACE; var attributes = properties.attributes || (properties.attributes = {}); for (var key in properties) { if (!properties.hasOwnProperty(key)) { continue; } var namespace = SVGAttributeNamespace(key); if (namespace === undefined) { // not a svg attribute continue; } var value = properties[key]; if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean' ) { continue; } if (namespace !== null) { // namespaced attribute properties[key] = attributeHook(namespace, value); continue; } attributes[key] = value properties[key] = undefined } return h(tagName, properties, children); } function isChildren(x) { return typeof x === 'string' || isArray(x); } virtual-dom-2.1.1/virtual-hyperscript/test/000077500000000000000000000000001256011152500207735ustar00rootroot00000000000000virtual-dom-2.1.1/virtual-hyperscript/test/attribute-hook.js000066400000000000000000000107671256011152500243050ustar00rootroot00000000000000var test = require("tape") var doc = require("global/document") var attributeHook = require("../hooks/attribute-hook.js") var h = require("../index.js") var createElement = require("../../vdom/create-element") var patch = require("../../vdom/patch") var diff = require("../../vtree/diff") test("sets and removes namespaced attribute", function (assert) { var namespace = 'http://ns.com/my' var hook1 = attributeHook(namespace, 'first value') var hook2 = attributeHook(namespace, 'first value') var hook3 = attributeHook(namespace, 'second value') var first = h('div', {'myns:myattr': hook1}) var second = h('div', {'myns:myattr': hook2}) var third = h('div', {'myns:myattr': hook3}) var fourth = h('div', {}) var elem = createElement(first) assert.equal(elem.getAttributeNS(namespace, 'myattr'), 'first value') var patches = diff(first, second) patch(elem, patches) // The value shouldn't change. assert.equal(elem.getAttributeNS(namespace, 'myattr'), 'first value') patches = diff(second, third) patch(elem, patches) assert.equal(elem.getAttributeNS(namespace, 'myattr'), 'second value') patches = diff(third, fourth) patch(elem, patches) assert.equal(elem.getAttributeNS(namespace, 'myattr'), blankAttributeNS()) assert.end() }) test("sets the attribute if previous value was not an AttributeHook", function (assert) { var namespace = 'http://ns.com/my' var OtherHook = function(namespace, value) { this.namespace = namespace this.value = value } OtherHook.prototype.hook = function() {} var hook1 = new OtherHook(namespace, 'the value') var hook2 = attributeHook(namespace, 'the value') var first = h('div', {'myns:myattr': hook1}) var second = h('div', {'myns:myattr': hook2}) var elem = createElement(first) assert.equal(elem.getAttributeNS(namespace, 'myattr'), blankAttributeNS()) patches = diff(first, second) patch(elem, patches) assert.equal(elem.getAttributeNS(namespace, 'myattr'), 'the value') assert.end() }) test("sets the attribute if previous value uses a different namespace", function (assert) { var namespace = 'http://ns.com/my' var hook1 = attributeHook('http://other.ns/', 'the value') var hook2 = attributeHook(namespace, 'the value') var first = h('div', {'myns:myattr': hook1}) var second = h('div', {'myns:myattr': hook2}) var elem = createElement(first) assert.equal(elem.getAttributeNS(namespace, 'myattr'), blankAttributeNS()) patches = diff(first, second) patch(elem, patches) assert.equal(elem.getAttributeNS(namespace, 'myattr'), 'the value') assert.end() }) test("removes the attribute if next value is not an AttributeHook", function (assert) { var namespace = 'http://ns.com/my' var OtherHook = function(namespace, value) { this.namespace = namespace this.value = value } OtherHook.prototype.hook = function() {} var hook1 = attributeHook(namespace, 'the value') var hook2 = new OtherHook(namespace, 'the value') var first = h('div', {'myns:myattr': hook1}) var second = h('div', {'myns:myattr': hook2}) var elem = createElement(first) assert.equal(elem.getAttributeNS(namespace, 'myattr'), 'the value') patches = diff(first, second) patch(elem, patches) assert.equal(elem.getAttributeNS(namespace, 'myattr'), blankAttributeNS()) assert.end() }) test("removes the attribute if next value uses a different namespace", function (assert) { var namespace = 'http://ns.com/my' var hook1 = attributeHook(namespace, 'the value') var hook2 = attributeHook('http://other.ns/', 'the value') var first = h('div', {'myns:myattr': hook1}) var second = h('div', {'myns:myattr': hook2}) var elem = createElement(first) assert.equal(elem.getAttributeNS(namespace, 'myattr'), 'the value') patches = diff(first, second) patch(elem, patches) assert.equal(elem.getAttributeNS(namespace, 'myattr'), blankAttributeNS()) assert.end() }) function blankAttributeNS() { // Most browsers conform to the latest version of the DOM spec, // which requires `getAttributeNS` to return `null` when the attribute // doesn't exist, but some browsers (including phantomjs) implement the // old version of the spec and return an empty string instead, see: // https://developer.mozilla.org/en-US/docs/Web/API/element.getAttributeNS#Return_value var div = doc.createElement("div") return div.getAttributeNS(null, "foo") } virtual-dom-2.1.1/virtual-hyperscript/test/ev-hook.js000066400000000000000000000012731256011152500227040ustar00rootroot00000000000000var test = require("tape") var EvStore = require("ev-store") var h = require("../index.js") var createElement = require("../../vdom/create-element") var patch = require("../../vdom/patch") var diff = require("../../vtree/diff") test("h with events", function (assert) { function one() {} var left = h(".foo", { "ev-click": one }) var right = h(".bar", {}) var elem = createElement(left) var ds1 = EvStore(elem) assert.ok(ds1) assert.equal(ds1.click, one) var patches = diff(left, right) patch(elem, patches) var ds2 = EvStore(elem) assert.ok(ds2) assert.equal(ds1, ds2) assert.equal(ds2.click, undefined) assert.end() }) virtual-dom-2.1.1/virtual-hyperscript/test/h.js000066400000000000000000000070731256011152500215670ustar00rootroot00000000000000var test = require("tape") var EvStore = require('ev-store') var h = require("../index") test("h is a function", function (assert) { assert.equal(typeof h, "function") assert.end() }) test("h returns a vnode", function (assert) { assert.equal(h("div").tagName, "DIV") assert.end() }) test("h defaults tagName to uppercase", function (assert) { assert.equal(h("").tagName, "DIV") assert.equal(h("div").tagName, "DIV") assert.end() }) test("h preserves tagName case if namespace is given", function (assert) { assert.equal(h("test", { namespace: "http://www.w3.org/XML/1998/namespace" }).tagName, "test") assert.end() }) test("h has props", function (assert) { assert.equal(h("div", { foo: "bar" }).properties.foo, "bar") assert.end() }) test("h with text", function (assert) { var node = h("div", "text") assert.equal(node.children[0].text, "text") assert.end() }) test("h with key", function (assert) { var node = h("div", { key: "bar" }) assert.equal(node.key, "bar") assert.end() }) test("h with ev-", function (assert) { var node = h("div", { "ev-foo": "bar" }) assert.ok(node.properties["ev-foo"]) var hook = node.properties["ev-foo"] var elem = {} hook.hook(elem, "ev-foo") assert.equal(EvStore(elem).foo, "bar") assert.end() }) test("input.value soft hook", function (assert) { var node = h("input", { value: "text" }) assert.equal(typeof node.properties.value, "object") var elem = {} node.properties.value.hook(elem, "value") assert.equal(elem.value, "text") assert.end() }) test("h with child", function (assert) { var node = h("div", h("span")) assert.equal(node.children[0].tagName, "SPAN") assert.end() }) test("h with children", function (assert) { var node = h("div", [h("span")]) assert.equal(node.children[0].tagName, "SPAN") assert.end() }) test("h with null", function (assert) { var node = h("div", null) var node2 = h("div", [null]) assert.equal(node.children.length, 0) assert.equal(node2.children.length, 0) assert.end() }) test("h with undefined", function (assert) { var node = h("div", undefined) var node2 = h("div", [undefined]) assert.equal(node.children.length, 0) assert.equal(node2.children.length, 0) assert.end() }) test("h with foreign object", function (assert) { var errorSingleChild try { h("div", null, { foreign: "object" }) } catch (e) { errorSingleChild = e } var errorChildren try { h("div", [{ foreign: "object" }]) } catch (e) { errorChildren = e } assert.ok(errorSingleChild); assert.ok(/Unexpected virtual child/.test(errorSingleChild.message)) assert.ok(errorChildren); assert.ok(/Unexpected virtual child/.test(errorChildren.message)) assert.end() }) test("h with class", function (assert) { var node = h(".foo") assert.equal(node.properties.className, "foo") assert.end() }) test("h with id", function (assert) { var node = h("#foo") assert.equal(node.properties.id, "foo") assert.end() }) test("h with empty string", function (assert) { var node = h("") assert.equal(node.tagName, "DIV") assert.end() }) test("h with two classes", function (assert) { var node = h(".foo", { className: "bar" }) assert.equal(node.properties.className, "foo bar") assert.end() }) test("h with two ids", function (assert) { var node = h("#foo", { id: "bar" }) assert.equal(node.properties.id, "bar") assert.end() }) virtual-dom-2.1.1/virtual-hyperscript/test/index.js000066400000000000000000000001351256011152500224370ustar00rootroot00000000000000require("./h.js") require("./svg.js") require("./ev-hook.js") require("./attribute-hook.js") virtual-dom-2.1.1/virtual-hyperscript/test/svg.js000066400000000000000000000041311256011152500221270ustar00rootroot00000000000000var test = require("tape") var doc = require("global/document") var svg = require("../svg") var attributeHook = require("../hooks/attribute-hook") test("svg returns a vnode", function (assert) { assert.equal(svg("circle").tagName, "circle") assert.equal(svg("circle").namespace, "http://www.w3.org/2000/svg") assert.end() }) test("svg with text", function (assert) { var node = svg("circle", "dat text") assert.equal(node.children[0].text, "dat text") assert.end() }) test("svg with properties", function (assert) { var node = svg("circle", { width: "40px" }) assert.strictEqual(node.properties.attributes.width, "40px") assert.end() }) test("svg properties are set", function (assert) { var node = svg("circle.test", { style: { border: "1px solid #000" }, width: "40px" }) assert.strictEqual(node.properties.attributes.width, "40px") assert.strictEqual(node.properties.width, undefined) assert.strictEqual( node.properties.style.border, safeStyle("boder", "1px solid #000") ) assert.end() }) test("namespaced attributes are set with correct namespace", function(assert) { var node = svg("image", { "xlink:href": "http://example.com/image.png", "xml:space": "preserve", }) assert.strictEqual(node.properties.attributes["xlink:href"], undefined) assert.strictEqual(node.hooks["xlink:href"].constructor, attributeHook) assert.strictEqual(node.hooks["xlink:href"].value, "http://example.com/image.png") assert.strictEqual(node.hooks["xlink:href"].namespace, "http://www.w3.org/1999/xlink") assert.strictEqual(node.properties.attributes["xml:space"], undefined) assert.strictEqual(node.hooks["xml:space"].constructor, attributeHook) assert.strictEqual(node.hooks["xml:space"].value, "preserve") assert.strictEqual(node.hooks["xml:space"].namespace, "http://www.w3.org/XML/1998/namespace") assert.end() }) function safeStyle(property, value) { var div = doc.createElement("div") div.style[property] = value return div.style[property] } virtual-dom-2.1.1/vnode/000077500000000000000000000000001256011152500150675ustar00rootroot00000000000000virtual-dom-2.1.1/vnode/handle-thunk.js000066400000000000000000000015021256011152500200050ustar00rootroot00000000000000var isVNode = require("./is-vnode") var isVText = require("./is-vtext") var isWidget = require("./is-widget") var isThunk = require("./is-thunk") module.exports = handleThunk function handleThunk(a, b) { var renderedA = a var renderedB = b if (isThunk(b)) { renderedB = renderThunk(b, a) } if (isThunk(a)) { renderedA = renderThunk(a, null) } return { a: renderedA, b: renderedB } } function renderThunk(thunk, previous) { var renderedThunk = thunk.vnode if (!renderedThunk) { renderedThunk = thunk.vnode = thunk.render(previous) } if (!(isVNode(renderedThunk) || isVText(renderedThunk) || isWidget(renderedThunk))) { throw new Error("thunk did not return a valid node"); } return renderedThunk } virtual-dom-2.1.1/vnode/is-thunk.js000066400000000000000000000001321256011152500171630ustar00rootroot00000000000000module.exports = isThunk function isThunk(t) { return t && t.type === "Thunk" } virtual-dom-2.1.1/vnode/is-vhook.js000066400000000000000000000003341256011152500171640ustar00rootroot00000000000000module.exports = isHook function isHook(hook) { return hook && (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") || typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook")) } virtual-dom-2.1.1/vnode/is-vnode.js000066400000000000000000000002441256011152500171510ustar00rootroot00000000000000var version = require("./version") module.exports = isVirtualNode function isVirtualNode(x) { return x && x.type === "VirtualNode" && x.version === version } virtual-dom-2.1.1/vnode/is-vtext.js000066400000000000000000000002441256011152500172100ustar00rootroot00000000000000var version = require("./version") module.exports = isVirtualText function isVirtualText(x) { return x && x.type === "VirtualText" && x.version === version } virtual-dom-2.1.1/vnode/is-widget.js000066400000000000000000000001301256011152500173130ustar00rootroot00000000000000module.exports = isWidget function isWidget(w) { return w && w.type === "Widget" } virtual-dom-2.1.1/vnode/test/000077500000000000000000000000001256011152500160465ustar00rootroot00000000000000virtual-dom-2.1.1/vnode/test/handle-thunk.js000066400000000000000000000071741256011152500207770ustar00rootroot00000000000000var test = require("tape") var handleThunk = require("../handle-thunk") var VNode = require("../vnode") var VText = require("../vtext") test("render a new thunk to vnode", function (assert) { var aNode = { render: function (previous) { assert.error("Render should not be called for cached thunk") }, type: "Thunk" } aNode.vnode = new VNode("div") var renderedBNode = new VNode("div") var bNode = { render: function (previous) { assert.equal(previous, aNode) return renderedBNode }, type: "Thunk" } var result = handleThunk(aNode, bNode) assert.equal(result.a, aNode.vnode) assert.equal(result.b, renderedBNode) assert.equal(bNode.vnode, renderedBNode) assert.end() }) test("render a new thunk to vtext", function (assert) { var aNode = { render: function (previous) { assert.error("Render should not be called for cached thunk") }, type: "Thunk" } aNode.vnode = new VNode("div") var renderedBNode = new VText("text") var bNode = { render: function (previous) { assert.equal(previous, aNode) return renderedBNode }, type: "Thunk" } var result = handleThunk(aNode, bNode) assert.equal(result.a, aNode.vnode) assert.equal(result.b, renderedBNode) assert.equal(bNode.vnode, renderedBNode) assert.end() }) test("render a new thunk to a widget", function (assert) { var aNode = { render: function (previous) { assert.error("Render should not be called for cached thunk") }, type: "Thunk" } aNode.vnode = new VNode("div") var renderedBNode = { type: "Widget" } var bNode = { render: function (previous) { assert.equal(previous, aNode) return renderedBNode }, type: "Thunk" } var result = handleThunk(aNode, bNode) assert.equal(result.a, aNode.vnode) assert.equal(result.b, renderedBNode) assert.equal(bNode.vnode, renderedBNode) assert.end() }) test("render current thunk to a thunk throws exception", function (assert) { var aNode = { render: function (previous) { assert.error("Render should not be called for cached thunk") }, type: "Thunk" } aNode.vnode = new VNode("div") var bNode = { render: function (previous) { assert.equal(previous, aNode) return { type: "Thunk" } }, type: "Thunk" } var result try { handleThunk(aNode, bNode) } catch (e) { result = e } assert.equal(result.message, "thunk did not return a valid node") assert.end() }) test("render previous thunk to a thunk throws exception", function (assert) { var aNode = { render: function (previous) { assert.equal(previous, null) return { type: "Thunk" } }, type: "Thunk" } var renderedBNode = new VNode("div") var bNode = { render: function (previous) { assert.equal(previous, aNode) return renderedBNode }, type: "Thunk" } var result try { handleThunk(aNode, bNode) } catch (e) { result = e } assert.equal(result.message, "thunk did not return a valid node") assert.end() }) test("normal nodes are returned", function (assert) { var aNode = new VNode('div') var bNode = new VNode('div') var result = handleThunk(aNode, bNode) assert.equal(result.a, aNode) assert.equal(result.b, bNode) assert.end() }) virtual-dom-2.1.1/vnode/test/index.js000066400000000000000000000000351256011152500175110ustar00rootroot00000000000000require('./handle-thunk.js') virtual-dom-2.1.1/vnode/version.js000066400000000000000000000000251256011152500171070ustar00rootroot00000000000000module.exports = "2" virtual-dom-2.1.1/vnode/vnode.js000066400000000000000000000040311256011152500165360ustar00rootroot00000000000000var version = require("./version") var isVNode = require("./is-vnode") var isWidget = require("./is-widget") var isThunk = require("./is-thunk") var isVHook = require("./is-vhook") module.exports = VirtualNode var noProperties = {} var noChildren = [] function VirtualNode(tagName, properties, children, key, namespace) { this.tagName = tagName this.properties = properties || noProperties this.children = children || noChildren this.key = key != null ? String(key) : undefined this.namespace = (typeof namespace === "string") ? namespace : null var count = (children && children.length) || 0 var descendants = 0 var hasWidgets = false var hasThunks = false var descendantHooks = false var hooks for (var propName in properties) { if (properties.hasOwnProperty(propName)) { var property = properties[propName] if (isVHook(property) && property.unhook) { if (!hooks) { hooks = {} } hooks[propName] = property } } } for (var i = 0; i < count; i++) { var child = children[i] if (isVNode(child)) { descendants += child.count || 0 if (!hasWidgets && child.hasWidgets) { hasWidgets = true } if (!hasThunks && child.hasThunks) { hasThunks = true } if (!descendantHooks && (child.hooks || child.descendantHooks)) { descendantHooks = true } } else if (!hasWidgets && isWidget(child)) { if (typeof child.destroy === "function") { hasWidgets = true } } else if (!hasThunks && isThunk(child)) { hasThunks = true; } } this.count = count + descendants this.hasWidgets = hasWidgets this.hasThunks = hasThunks this.hooks = hooks this.descendantHooks = descendantHooks } VirtualNode.prototype.version = version VirtualNode.prototype.type = "VirtualNode" virtual-dom-2.1.1/vnode/vpatch.js000066400000000000000000000007451256011152500167200ustar00rootroot00000000000000var version = require("./version") VirtualPatch.NONE = 0 VirtualPatch.VTEXT = 1 VirtualPatch.VNODE = 2 VirtualPatch.WIDGET = 3 VirtualPatch.PROPS = 4 VirtualPatch.ORDER = 5 VirtualPatch.INSERT = 6 VirtualPatch.REMOVE = 7 VirtualPatch.THUNK = 8 module.exports = VirtualPatch function VirtualPatch(type, vNode, patch) { this.type = Number(type) this.vNode = vNode this.patch = patch } VirtualPatch.prototype.version = version VirtualPatch.prototype.type = "VirtualPatch" virtual-dom-2.1.1/vnode/vtext.js000066400000000000000000000003221256011152500165740ustar00rootroot00000000000000var version = require("./version") module.exports = VirtualText function VirtualText(text) { this.text = String(text) } VirtualText.prototype.version = version VirtualText.prototype.type = "VirtualText" virtual-dom-2.1.1/vtree/000077500000000000000000000000001256011152500151015ustar00rootroot00000000000000virtual-dom-2.1.1/vtree/README.md000066400000000000000000000012071256011152500163600ustar00rootroot00000000000000# vtree A realtime tree diffing algorithm ## Motivation `vtree` currently exists as part of `virtual-dom`. It is used for imitating diff operations between two `vnode` structures that imitate the structure of the active DOM node structure in the browser. ## Example ```js var h = require("virtual-dom/h") var diff = require("virtual-dom/diff") var leftNode = h("div") var rightNode = h("text") var patches = diff(leftNode, rightNode) /* -> { a: leftNode, 0: vpatch(rightNode) // a replace operation for the first node } */ ``` ## Installation `npm install virtual-dom` ## Contributors - Matt Esch ## MIT Licenced virtual-dom-2.1.1/vtree/diff-props.js000066400000000000000000000026071256011152500175150ustar00rootroot00000000000000var isObject = require("is-object") var isHook = require("../vnode/is-vhook") module.exports = diffProps function diffProps(a, b) { var diff for (var aKey in a) { if (!(aKey in b)) { diff = diff || {} diff[aKey] = undefined } var aValue = a[aKey] var bValue = b[aKey] if (aValue === bValue) { continue } else if (isObject(aValue) && isObject(bValue)) { if (getPrototype(bValue) !== getPrototype(aValue)) { diff = diff || {} diff[aKey] = bValue } else if (isHook(bValue)) { diff = diff || {} diff[aKey] = bValue } else { var objectDiff = diffProps(aValue, bValue) if (objectDiff) { diff = diff || {} diff[aKey] = objectDiff } } } else { diff = diff || {} diff[aKey] = bValue } } for (var bKey in b) { if (!(bKey in a)) { diff = diff || {} diff[bKey] = b[bKey] } } return diff } function getPrototype(value) { if (Object.getPrototypeOf) { return Object.getPrototypeOf(value) } else if (value.__proto__) { return value.__proto__ } else if (value.constructor) { return value.constructor.prototype } } virtual-dom-2.1.1/vtree/diff.js000066400000000000000000000272431256011152500163570ustar00rootroot00000000000000var isArray = require("x-is-array") var VPatch = require("../vnode/vpatch") var isVNode = require("../vnode/is-vnode") var isVText = require("../vnode/is-vtext") var isWidget = require("../vnode/is-widget") var isThunk = require("../vnode/is-thunk") var handleThunk = require("../vnode/handle-thunk") var diffProps = require("./diff-props") module.exports = diff function diff(a, b) { var patch = { a: a } walk(a, b, patch, 0) return patch } function walk(a, b, patch, index) { if (a === b) { return } var apply = patch[index] var applyClear = false if (isThunk(a) || isThunk(b)) { thunks(a, b, patch, index) } else if (b == null) { // If a is a widget we will add a remove patch for it // Otherwise any child widgets/hooks must be destroyed. // This prevents adding two remove patches for a widget. if (!isWidget(a)) { clearState(a, patch, index) apply = patch[index] } apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b)) } else if (isVNode(b)) { if (isVNode(a)) { if (a.tagName === b.tagName && a.namespace === b.namespace && a.key === b.key) { var propsPatch = diffProps(a.properties, b.properties) if (propsPatch) { apply = appendPatch(apply, new VPatch(VPatch.PROPS, a, propsPatch)) } apply = diffChildren(a, b, patch, apply, index) } else { apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) applyClear = true } } else { apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) applyClear = true } } else if (isVText(b)) { if (!isVText(a)) { apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) applyClear = true } else if (a.text !== b.text) { apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) } } else if (isWidget(b)) { if (!isWidget(a)) { applyClear = true } apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b)) } if (apply) { patch[index] = apply } if (applyClear) { clearState(a, patch, index) } } function diffChildren(a, b, patch, apply, index) { var aChildren = a.children var orderedSet = reorder(aChildren, b.children) var bChildren = orderedSet.children var aLen = aChildren.length var bLen = bChildren.length var len = aLen > bLen ? aLen : bLen for (var i = 0; i < len; i++) { var leftNode = aChildren[i] var rightNode = bChildren[i] index += 1 if (!leftNode) { if (rightNode) { // Excess nodes in b need to be added apply = appendPatch(apply, new VPatch(VPatch.INSERT, null, rightNode)) } } else { walk(leftNode, rightNode, patch, index) } if (isVNode(leftNode) && leftNode.count) { index += leftNode.count } } if (orderedSet.moves) { // Reorder nodes last apply = appendPatch(apply, new VPatch( VPatch.ORDER, a, orderedSet.moves )) } return apply } function clearState(vNode, patch, index) { // TODO: Make this a single walk, not two unhook(vNode, patch, index) destroyWidgets(vNode, patch, index) } // Patch records for all destroyed widgets must be added because we need // a DOM node reference for the destroy function function destroyWidgets(vNode, patch, index) { if (isWidget(vNode)) { if (typeof vNode.destroy === "function") { patch[index] = appendPatch( patch[index], new VPatch(VPatch.REMOVE, vNode, null) ) } } else if (isVNode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) { var children = vNode.children var len = children.length for (var i = 0; i < len; i++) { var child = children[i] index += 1 destroyWidgets(child, patch, index) if (isVNode(child) && child.count) { index += child.count } } } else if (isThunk(vNode)) { thunks(vNode, null, patch, index) } } // Create a sub-patch for thunks function thunks(a, b, patch, index) { var nodes = handleThunk(a, b) var thunkPatch = diff(nodes.a, nodes.b) if (hasPatches(thunkPatch)) { patch[index] = new VPatch(VPatch.THUNK, null, thunkPatch) } } function hasPatches(patch) { for (var index in patch) { if (index !== "a") { return true } } return false } // Execute hooks when two nodes are identical function unhook(vNode, patch, index) { if (isVNode(vNode)) { if (vNode.hooks) { patch[index] = appendPatch( patch[index], new VPatch( VPatch.PROPS, vNode, undefinedKeys(vNode.hooks) ) ) } if (vNode.descendantHooks || vNode.hasThunks) { var children = vNode.children var len = children.length for (var i = 0; i < len; i++) { var child = children[i] index += 1 unhook(child, patch, index) if (isVNode(child) && child.count) { index += child.count } } } } else if (isThunk(vNode)) { thunks(vNode, null, patch, index) } } function undefinedKeys(obj) { var result = {} for (var key in obj) { result[key] = undefined } return result } // List diff, naive left to right reordering function reorder(aChildren, bChildren) { // O(M) time, O(M) memory var bChildIndex = keyIndex(bChildren) var bKeys = bChildIndex.keys var bFree = bChildIndex.free if (bFree.length === bChildren.length) { return { children: bChildren, moves: null } } // O(N) time, O(N) memory var aChildIndex = keyIndex(aChildren) var aKeys = aChildIndex.keys var aFree = aChildIndex.free if (aFree.length === aChildren.length) { return { children: bChildren, moves: null } } // O(MAX(N, M)) memory var newChildren = [] var freeIndex = 0 var freeCount = bFree.length var deletedItems = 0 // Iterate through a and match a node in b // O(N) time, for (var i = 0 ; i < aChildren.length; i++) { var aItem = aChildren[i] var itemIndex if (aItem.key) { if (bKeys.hasOwnProperty(aItem.key)) { // Match up the old keys itemIndex = bKeys[aItem.key] newChildren.push(bChildren[itemIndex]) } else { // Remove old keyed items itemIndex = i - deletedItems++ newChildren.push(null) } } else { // Match the item in a with the next free item in b if (freeIndex < freeCount) { itemIndex = bFree[freeIndex++] newChildren.push(bChildren[itemIndex]) } else { // There are no free items in b to match with // the free items in a, so the extra free nodes // are deleted. itemIndex = i - deletedItems++ newChildren.push(null) } } } var lastFreeIndex = freeIndex >= bFree.length ? bChildren.length : bFree[freeIndex] // Iterate through b and append any new keys // O(M) time for (var j = 0; j < bChildren.length; j++) { var newItem = bChildren[j] if (newItem.key) { if (!aKeys.hasOwnProperty(newItem.key)) { // Add any new keyed items // We are adding new items to the end and then sorting them // in place. In future we should insert new items in place. newChildren.push(newItem) } } else if (j >= lastFreeIndex) { // Add any leftover non-keyed items newChildren.push(newItem) } } var simulate = newChildren.slice() var simulateIndex = 0 var removes = [] var inserts = [] var simulateItem for (var k = 0; k < bChildren.length;) { var wantedItem = bChildren[k] simulateItem = simulate[simulateIndex] // remove items while (simulateItem === null && simulate.length) { removes.push(remove(simulate, simulateIndex, null)) simulateItem = simulate[simulateIndex] } if (!simulateItem || simulateItem.key !== wantedItem.key) { // if we need a key in this position... if (wantedItem.key) { if (simulateItem && simulateItem.key) { // if an insert doesn't put this key in place, it needs to move if (bKeys[simulateItem.key] !== k + 1) { removes.push(remove(simulate, simulateIndex, simulateItem.key)) simulateItem = simulate[simulateIndex] // if the remove didn't put the wanted item in place, we need to insert it if (!simulateItem || simulateItem.key !== wantedItem.key) { inserts.push({key: wantedItem.key, to: k}) } // items are matching, so skip ahead else { simulateIndex++ } } else { inserts.push({key: wantedItem.key, to: k}) } } else { inserts.push({key: wantedItem.key, to: k}) } k++ } // a key in simulate has no matching wanted key, remove it else if (simulateItem && simulateItem.key) { removes.push(remove(simulate, simulateIndex, simulateItem.key)) } } else { simulateIndex++ k++ } } // remove all the remaining nodes from simulate while(simulateIndex < simulate.length) { simulateItem = simulate[simulateIndex] removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key)) } // If the only moves we have are deletes then we can just // let the delete patch remove these items. if (removes.length === deletedItems && !inserts.length) { return { children: newChildren, moves: null } } return { children: newChildren, moves: { removes: removes, inserts: inserts } } } function remove(arr, index, key) { arr.splice(index, 1) return { from: index, key: key } } function keyIndex(children) { var keys = {} var free = [] var length = children.length for (var i = 0; i < length; i++) { var child = children[i] if (child.key) { keys[child.key] = i } else { free.push(i) } } return { keys: keys, // A hash of key name to index free: free // An array of unkeyed item indices } } function appendPatch(apply, patch) { if (apply) { if (isArray(apply)) { apply.push(patch) } else { apply = [apply, patch] } return apply } else { return patch } } virtual-dom-2.1.1/vtree/test/000077500000000000000000000000001256011152500160605ustar00rootroot00000000000000virtual-dom-2.1.1/vtree/test/diff-props.js000066400000000000000000000007511256011152500204720ustar00rootroot00000000000000var test = require("tape") var diffProps = require("../diff-props") test("add attributes to empty attributes", function (assert) { var propsA = { attributes : {} } var propsB = { attributes : { class : "standard", "e-text" : "custom" } } var diff = diffProps(propsA,propsB) assert.equal(diff.attributes.class, "standard") assert.equal(diff.attributes["e-text"], "custom") assert.end() }) virtual-dom-2.1.1/vtree/test/index.js000066400000000000000000000000301256011152500175160ustar00rootroot00000000000000require("./diff-props")