Comparing version 0.7.4 to 1.0.1
211
package.json
{ | ||
"name": "snabbdom", | ||
"version": "0.7.4", | ||
"version": "1.0.1", | ||
"description": "A virtual DOM library with focus on simplicity, modularity, powerful features and performance.", | ||
"main": "snabbdom.js", | ||
"module": "es/snabbdom.js", | ||
"typings": "snabbdom.d.ts", | ||
"type": "module", | ||
"exports": { | ||
"./init": "./build/package/init.js", | ||
"./h": "./build/package/h.js", | ||
"./helpers/attachto": "./build/package/helpers/attachto.js", | ||
"./hooks": "./build/package/hooks.js", | ||
"./htmldomapi": "./build/package/htmldomapi.js", | ||
"./is": "./build/package/is.js", | ||
"./jsx": "./build/package/jsx.js", | ||
"./modules/attributes": "./build/package/modules/attributes.js", | ||
"./modules/class": "./build/package/modules/class.js", | ||
"./modules/dataset": "./build/package/modules/dataset.js", | ||
"./modules/eventlisteners": "./build/package/modules/eventlisteners.js", | ||
"./modules/hero": "./build/package/modules/hero.js", | ||
"./modules/module": "./build/package/modules/module.js", | ||
"./modules/props": "./build/package/modules/props.js", | ||
"./modules/style": "./build/package/modules/style.js", | ||
"./thunk": "./build/package/thunk.js", | ||
"./tovnode": "./build/package/tovnode.js", | ||
"./vnode": "./build/package/vnode.js" | ||
}, | ||
"directories": { | ||
@@ -13,32 +31,65 @@ "example": "examples", | ||
"devDependencies": { | ||
"benchmark": "^2.1.4", | ||
"browserify": "^14.4.0", | ||
"fake-raf": "1.0.1", | ||
"gulp": "^3.9.1", | ||
"gulp-clean": "^0.3.2", | ||
"gulp-rename": "^1.2.2", | ||
"gulp-sourcemaps": "^2.6.0", | ||
"gulp-uglify": "^3.0.0", | ||
"husky": "^3.0.5", | ||
"karma": "^3.0.0", | ||
"karma-browserstack-launcher": "^1.3.0", | ||
"karma-chrome-launcher": "^2.2.0", | ||
"karma-firefox-launcher": "^1.2.0", | ||
"karma-mocha": "^1.3.0", | ||
"karma-typescript": "^3.0.13", | ||
"knuth-shuffle": "^1.0.1", | ||
"mocha": "^5.2.0", | ||
"typescript": "^3.0.3", | ||
"xyz": "2.1.0" | ||
"@babel/core": "7.10.2", | ||
"@babel/preset-env": "7.10.2", | ||
"@commitlint/cli": "8.3.5", | ||
"@commitlint/travis-cli": "9.0.1", | ||
"@types/chai": "4.2.11", | ||
"@types/faker": "4.1.12", | ||
"@types/lodash.shuffle": "4.2.6", | ||
"@types/mathjs": "6.0.5", | ||
"@types/mocha": "7.0.2", | ||
"@typescript-eslint/eslint-plugin": "3.3.0", | ||
"babel-loader": "8.1.0", | ||
"benchmark": "2.1.4", | ||
"chai": "4.2.0", | ||
"chalk": "4.1.0", | ||
"core-js": "3.6.5", | ||
"cross-env": "7.0.2", | ||
"editorconfig-checker": "3.1.0", | ||
"eslint": "7.2.0", | ||
"eslint-config-standard-with-typescript": "18.0.2", | ||
"eslint-plugin-import": "2.21.2", | ||
"eslint-plugin-markdown": "2.0.0-alpha.0", | ||
"eslint-plugin-node": "11.1.0", | ||
"eslint-plugin-promise": "4.2.1", | ||
"eslint-plugin-standard": "4.0.1", | ||
"faker": "4.1.0", | ||
"husky": "4.2.5", | ||
"is-path-inside": "3.0.2", | ||
"karma": "5.1.0", | ||
"karma-browserstack-launcher": "1.6.0", | ||
"karma-chrome-launcher": "3.1.0", | ||
"karma-firefox-launcher": "1.3.0", | ||
"karma-mocha": "2.0.1", | ||
"karma-mocha-reporter": "2.2.5", | ||
"karma-webpack": "4.0.2", | ||
"latest-snabbdom-release": "npm:snabbdom@0.7.4", | ||
"lodash.shuffle": "4.2.0", | ||
"mathjs": "7.0.1", | ||
"mocha": "8.0.1", | ||
"npm-run-all": "4.1.5", | ||
"p-map-series": "2.1.0", | ||
"p-reduce": "2.1.0", | ||
"remark-cli": "8.0.0", | ||
"remark-toc": "7.0.0", | ||
"standard-version": "8.0.0", | ||
"ts-transform-import-path-rewrite": "0.2.1", | ||
"tsconfigs": "5.0.0", | ||
"tty-table": "4.1.3", | ||
"ttypescript": "1.5.10", | ||
"typescript": "3.9.5", | ||
"webpack": "4.43.0" | ||
}, | ||
"scripts": { | ||
"pretest": "npm run compile", | ||
"test": "karma start", | ||
"compile": "npm run compile-es && npm run compile-commonjs", | ||
"compile-es": "tsc --outDir es --module es6 --moduleResolution node", | ||
"compile-commonjs": "tsc --outDir ./", | ||
"prepublish": "npm run compile", | ||
"release-major": "xyz --repo git@github.com:paldepind/snabbdom.git --increment major", | ||
"release-minor": "xyz --repo git@github.com:paldepind/snabbdom.git --increment minor", | ||
"release-patch": "xyz --repo git@github.com:paldepind/snabbdom.git --increment patch" | ||
"docs": "remark . --output", | ||
"check-clean": "git diff --exit-code", | ||
"lint:js": "eslint --ext .ts,.tsx,.cjs,.md,.mjs --ignore-path .gitignore .", | ||
"lint:editorconfig": "editorconfig-checker", | ||
"lint": "run-s lint:editorconfig lint:js", | ||
"unit": "cross-env FILES_PATTERN=\"build/test/unit/**/*.js\" karma start karma.conf.cjs", | ||
"benchmark": "cross-env FILES_PATTERN=\"build/test/benchmark/**/*.js\" karma start karma.conf.cjs --concurrency=1", | ||
"make-release-commit": "standard-version", | ||
"test": "run-s lint compile unit", | ||
"compile": "ttsc --build src/test/tsconfig.json", | ||
"prepublishOnly": "npm run compile" | ||
}, | ||
@@ -61,8 +112,100 @@ "repository": { | ||
}, | ||
"remarkConfig": { | ||
"plugins": [ | ||
[ | ||
"toc", | ||
{ | ||
"tight": true | ||
} | ||
] | ||
], | ||
"settings": { | ||
"listItemIndent": "1", | ||
"bullet": "*", | ||
"tablePipeAlign": false | ||
} | ||
}, | ||
"homepage": "https://github.com/paldepind/snabbdom#readme", | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "npm test" | ||
"commit-msg": "commitlint --config commitlint.config.json -E HUSKY_GIT_PARAMS", | ||
"pre-commit": "run-s docs check-clean test" | ||
} | ||
} | ||
}, | ||
"files": [ | ||
"/build/package/h.d.ts", | ||
"/build/package/h.js", | ||
"/build/package/h.js.map", | ||
"/build/package/helpers/attachto.d.ts", | ||
"/build/package/helpers/attachto.js", | ||
"/build/package/helpers/attachto.js.map", | ||
"/build/package/hooks.d.ts", | ||
"/build/package/hooks.js", | ||
"/build/package/hooks.js.map", | ||
"/build/package/htmldomapi.d.ts", | ||
"/build/package/htmldomapi.js", | ||
"/build/package/htmldomapi.js.map", | ||
"/build/package/init.d.ts", | ||
"/build/package/init.js", | ||
"/build/package/init.js.map", | ||
"/build/package/is.d.ts", | ||
"/build/package/is.js", | ||
"/build/package/is.js.map", | ||
"/build/package/jsx-global.d.ts", | ||
"/build/package/jsx.d.ts", | ||
"/build/package/jsx.js", | ||
"/build/package/jsx.js.map", | ||
"/build/package/modules/attributes.d.ts", | ||
"/build/package/modules/attributes.js", | ||
"/build/package/modules/attributes.js.map", | ||
"/build/package/modules/class.d.ts", | ||
"/build/package/modules/class.js", | ||
"/build/package/modules/class.js.map", | ||
"/build/package/modules/dataset.d.ts", | ||
"/build/package/modules/dataset.js", | ||
"/build/package/modules/dataset.js.map", | ||
"/build/package/modules/eventlisteners.d.ts", | ||
"/build/package/modules/eventlisteners.js", | ||
"/build/package/modules/eventlisteners.js.map", | ||
"/build/package/modules/hero.d.ts", | ||
"/build/package/modules/hero.js", | ||
"/build/package/modules/hero.js.map", | ||
"/build/package/modules/module.d.ts", | ||
"/build/package/modules/module.js", | ||
"/build/package/modules/module.js.map", | ||
"/build/package/modules/props.d.ts", | ||
"/build/package/modules/props.js", | ||
"/build/package/modules/props.js.map", | ||
"/build/package/modules/style.d.ts", | ||
"/build/package/modules/style.js", | ||
"/build/package/modules/style.js.map", | ||
"/build/package/thunk.d.ts", | ||
"/build/package/thunk.js", | ||
"/build/package/thunk.js.map", | ||
"/build/package/tovnode.d.ts", | ||
"/build/package/tovnode.js", | ||
"/build/package/tovnode.js.map", | ||
"/build/package/vnode.d.ts", | ||
"/build/package/vnode.js", | ||
"/build/package/vnode.js.map", | ||
"/src/package/h.ts", | ||
"/src/package/helpers/attachto.ts", | ||
"/src/package/hooks.ts", | ||
"/src/package/htmldomapi.ts", | ||
"/src/package/init.ts", | ||
"/src/package/is.ts", | ||
"/src/package/jsx-global.ts", | ||
"/src/package/jsx.ts", | ||
"/src/package/modules/attributes.ts", | ||
"/src/package/modules/class.ts", | ||
"/src/package/modules/dataset.ts", | ||
"/src/package/modules/eventlisteners.ts", | ||
"/src/package/modules/hero.ts", | ||
"/src/package/modules/module.ts", | ||
"/src/package/modules/props.ts", | ||
"/src/package/modules/style.ts", | ||
"/src/package/thunk.ts", | ||
"/src/package/tovnode.ts", | ||
"/src/package/vnode.ts" | ||
] | ||
} |
546
README.md
@@ -1,2 +0,2 @@ | ||
<img src="logo.png" width="356px"> | ||
<img alt="Snabbdom" src="logo.png" width="356px"> | ||
@@ -6,2 +6,4 @@ A virtual DOM library with focus on simplicity, modularity, powerful features | ||
* * * | ||
[![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) | ||
@@ -16,16 +18,4 @@ [![Build Status](https://travis-ci.org/snabbdom/snabbdom.svg?branch=master)](https://travis-ci.org/snabbdom/snabbdom) | ||
## Table of contents | ||
## Introduction | ||
* [Introduction](#introduction) | ||
* [Features](#features) | ||
* [Inline example](#inline-example) | ||
* [Examples](#examples) | ||
* [Core documentation](#core-documentation) | ||
* [Modules documentation](#modules-documentation) | ||
* [Helpers](#helpers) | ||
* [Virtual Node documentation](#virtual-node) | ||
* [Structuring applications](#structuring-applications) | ||
## Why | ||
Virtual DOM is awesome. It allows us to express our application's view | ||
@@ -36,4 +26,2 @@ as a function of its state. But existing solutions were way way too | ||
## Introduction | ||
Snabbdom consists of an extremely simple, performant and extensible | ||
@@ -57,6 +45,5 @@ core that is only ≈ 200 SLOC. It offers a modular architecture with | ||
to hook into any part of the diff and patch process. | ||
* Splendid performance. Snabbdom is among the fastest virtual DOM libraries | ||
in the [Virtual DOM Benchmark](http://vdom-benchmark.github.io/vdom-benchmark/). | ||
* Splendid performance. Snabbdom is among the fastest virtual DOM libraries. | ||
* Patch function with a function signature equivalent to a reduce/scan | ||
function. Allows for easier integration with a FRP library. | ||
function. Allows for easier integration with a FRP library. | ||
* Features in modules | ||
@@ -75,37 +62,39 @@ * `h` function for easily creating virtual DOM nodes. | ||
## Inline example | ||
## Example | ||
```javascript | ||
var snabbdom = require('snabbdom'); | ||
var patch = snabbdom.init([ // Init patch function with chosen modules | ||
require('snabbdom/modules/class').default, // makes it easy to toggle classes | ||
require('snabbdom/modules/props').default, // for setting properties on DOM elements | ||
require('snabbdom/modules/style').default, // handles styling on elements with support for animations | ||
require('snabbdom/modules/eventlisteners').default, // attaches event listeners | ||
]); | ||
var h = require('snabbdom/h').default; // helper function for creating vnodes | ||
```mjs | ||
import { init } from 'snabbdom/init' | ||
import { classModule } from 'snabbdom/modules/class' | ||
import { propsModule } from 'snabbdom/modules/props' | ||
import { styleModule } from 'snabbdom/modules/style' | ||
import { eventListenersModule } from 'snabbdom/modules/eventlisteners' | ||
import { h } from 'snabbdom/h' // helper function for creating vnodes | ||
var container = document.getElementById('container'); | ||
var patch = init([ // Init patch function with chosen modules | ||
classModule, // makes it easy to toggle classes | ||
propsModule, // for setting properties on DOM elements | ||
styleModule, // handles styling on elements with support for animations | ||
eventListenersModule, // attaches event listeners | ||
]) | ||
var vnode = h('div#container.two.classes', {on: {click: someFn}}, [ | ||
h('span', {style: {fontWeight: 'bold'}}, 'This is bold'), | ||
var container = document.getElementById('container') | ||
var vnode = h('div#container.two.classes', { on: { click: someFn } }, [ | ||
h('span', { style: { fontWeight: 'bold' } }, 'This is bold'), | ||
' and this is just normal text', | ||
h('a', {props: {href: '/foo'}}, 'I\'ll take you places!') | ||
]); | ||
h('a', { props: { href: '/foo' } }, 'I\'ll take you places!') | ||
]) | ||
// Patch into empty DOM element – this modifies the DOM as a side effect | ||
patch(container, vnode); | ||
patch(container, vnode) | ||
var newVnode = h('div#container.two.classes', {on: {click: anotherEventHandler}}, [ | ||
h('span', {style: {fontWeight: 'normal', fontStyle: 'italic'}}, 'This is now italic type'), | ||
var newVnode = h('div#container.two.classes', { on: { click: anotherEventHandler } }, [ | ||
h('span', { style: { fontWeight: 'normal', fontStyle: 'italic' } }, 'This is now italic type'), | ||
' and this is still just normal text', | ||
h('a', {props: {href: '/bar'}}, 'I\'ll take you places!') | ||
]); | ||
h('a', { props: { href: '/bar' } }, 'I\'ll take you places!') | ||
]) | ||
// Second `patch` invocation | ||
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state | ||
// to unmount from the DOM and clean up, simply pass null | ||
patch(newVnode, null) | ||
patch(vnode, newVnode) // Snabbdom efficiently updates the old view to the new state | ||
``` | ||
## Examples | ||
## More examples | ||
@@ -116,2 +105,45 @@ * [Animated reordering of elements](http://snabbdom.github.io/snabbdom/examples/reorder-animation/) | ||
* * * | ||
## Table of contents | ||
* [Core documentation](#core-documentation) | ||
* [`init`](#init) | ||
* [`patch`](#patch) | ||
* [Unmounting](#unmounting) | ||
* [`snabbdom/h`](#snabbdomh) | ||
* [`snabbdom/tovnode`](#snabbdomtovnode) | ||
* [Hooks](#hooks) | ||
* [Overview](#overview) | ||
* [Usage](#usage) | ||
* [The `init` hook](#the-init-hook) | ||
* [The `insert` hook](#the-insert-hook) | ||
* [The `remove` hook](#the-remove-hook) | ||
* [The `destroy` hook](#the-destroy-hook) | ||
* [Creating modules](#creating-modules) | ||
* [Modules documentation](#modules-documentation) | ||
* [The class module](#the-class-module) | ||
* [The props module](#the-props-module) | ||
* [The attributes module](#the-attributes-module) | ||
* [The dataset module](#the-dataset-module) | ||
* [The style module](#the-style-module) | ||
* [Custom properties (CSS variables)](#custom-properties-css-variables) | ||
* [Delayed properties](#delayed-properties) | ||
* [Set properties on `remove`](#set-properties-on-remove) | ||
* [Set properties on `destroy`](#set-properties-on-destroy) | ||
* [Eventlisteners module](#eventlisteners-module) | ||
* [SVG](#svg) | ||
* [Classes in SVG Elements](#classes-in-svg-elements) | ||
* [Thunks](#thunks) | ||
* [Virtual Node](#virtual-node) | ||
* [sel : String](#sel--string) | ||
* [data : Object](#data--object) | ||
* [children : Array<vnode>](#children--arrayvnode) | ||
* [text : string](#text--string) | ||
* [elm : Element](#elm--element) | ||
* [key : string | number](#key--string--number) | ||
* [Structuring applications](#structuring-applications) | ||
* [Common errors](#common-errors) | ||
* [Opportunity for community feedback](#opportunity-for-community-feedback) | ||
## Core documentation | ||
@@ -123,13 +155,13 @@ | ||
### `snabbdom.init` | ||
### `init` | ||
The core exposes only one single function `snabbdom.init`. This `init` | ||
The core exposes only one single function `init`. This `init` | ||
takes a list of modules and returns a `patch` function that uses the | ||
specified set of modules. | ||
```javascript | ||
var patch = snabbdom.init([ | ||
require('snabbdom/modules/class').default, | ||
require('snabbdom/modules/style').default, | ||
]); | ||
```mjs | ||
import { classModule } from 'snabbdom/modules/class' | ||
import { styleModule } from 'snabbdom/modules/style' | ||
var patch = init([classModule, styleModule]) | ||
``` | ||
@@ -154,6 +186,16 @@ | ||
```javascript | ||
patch(oldVnode, newVnode); | ||
```mjs | ||
patch(oldVnode, newVnode) | ||
``` | ||
#### Unmounting | ||
While there is no API specifically for removing a VNode tree from its mount point element, one way of almost achieving this is providing a comment VNode as the second argument to `patch`, such as: | ||
```mjs | ||
patch(oldVnode, h('!', { hooks: { post: () => { /* patch complete */ } } })) | ||
``` | ||
Of course, then there is still a single comment node at the mount point. | ||
### `snabbdom/h` | ||
@@ -165,8 +207,9 @@ | ||
```javascript | ||
var h = require('snabbdom/h').default; | ||
var vnode = h('div', {style: {color: '#000'}}, [ | ||
```mjs | ||
import { h } from 'snabbdom/h' | ||
var vnode = h('div', { style: { color: '#000' } }, [ | ||
h('h1', 'Headline'), | ||
h('p', 'A paragraph'), | ||
]); | ||
]) | ||
``` | ||
@@ -179,17 +222,22 @@ | ||
```javascript | ||
var snabbdom = require('snabbdom') | ||
var patch = snabbdom.init([ // Init patch function with chosen modules | ||
require('snabbdom/modules/class').default, // makes it easy to toggle classes | ||
require('snabbdom/modules/props').default, // for setting properties on DOM elements | ||
require('snabbdom/modules/style').default, // handles styling on elements with support for animations | ||
require('snabbdom/modules/eventlisteners').default, // attaches event listeners | ||
]); | ||
var h = require('snabbdom/h').default; // helper function for creating vnodes | ||
var toVNode = require('snabbdom/tovnode').default; | ||
```mjs | ||
import { init } from 'snabbdom/init' | ||
import { classModule } from 'snabbdom/modules/class' | ||
import { propsModule } from 'snabbdom/modules/props' | ||
import { styleModule } from 'snabbdom/modules/style' | ||
import { eventListenersModule } from 'snabbdom/modules/eventlisteners' | ||
import { h } from 'snabbdom/h' // helper function for creating vnodes | ||
import { toVNode } from 'snabbdom/tovnode' | ||
var newVNode = h('div', {style: {color: '#000'}}, [ | ||
var patch = init([ // Init patch function with chosen modules | ||
classModule, // makes it easy to toggle classes | ||
propsModule, // for setting properties on DOM elements | ||
styleModule, // handles styling on elements with support for animations | ||
eventListenersModule, // attaches event listeners | ||
]) | ||
var newVNode = h('div', { style: { color: '#000' } }, [ | ||
h('h1', 'Headline'), | ||
h('p', 'A paragraph'), | ||
]); | ||
]) | ||
@@ -208,14 +256,14 @@ patch(toVNode(document.querySelector('.container')), newVNode) | ||
| Name | Triggered when | Arguments to callback | | ||
| ----------- | -------------- | ----------------------- | | ||
| `pre` | the patch process begins | none | | ||
| `init` | a vnode has been added | `vnode` | | ||
| `create` | a DOM element has been created based on a vnode | `emptyVnode, vnode` | | ||
| `insert` | an element has been inserted into the DOM | `vnode` | | ||
| `prepatch` | an element is about to be patched | `oldVnode, vnode` | | ||
| `update` | an element is being updated | `oldVnode, vnode` | | ||
| `postpatch` | an element has been patched | `oldVnode, vnode` | | ||
| `destroy` | an element is directly or indirectly being removed | `vnode` | | ||
| `remove` | an element is directly being removed from the DOM | `vnode, removeCallback` | | ||
| `post` | the patch process is done | none | | ||
| Name | Triggered when | Arguments to callback | | ||
| - | - | - | | ||
| `pre` | the patch process begins | none | | ||
| `init` | a vnode has been added | `vnode` | | ||
| `create` | a DOM element has been created based on a vnode | `emptyVnode, vnode` | | ||
| `insert` | an element has been inserted into the DOM | `vnode` | | ||
| `prepatch` | an element is about to be patched | `oldVnode, vnode` | | ||
| `update` | an element is being updated | `oldVnode, vnode` | | ||
| `postpatch` | an element has been patched | `oldVnode, vnode` | | ||
| `destroy` | an element is directly or indirectly being removed | `vnode` | | ||
| `remove` | an element is directly being removed from the DOM | `vnode, removeCallback` | | ||
| `post` | the patch process is done | none | | ||
@@ -234,9 +282,9 @@ The following hooks are available for modules: `pre`, `create`, | ||
```javascript | ||
```mjs | ||
h('div.row', { | ||
key: movie.rank, | ||
hook: { | ||
insert: (vnode) => { movie.elmHeight = vnode.elm.offsetHeight; } | ||
insert: (vnode) => { movie.elmHeight = vnode.elm.offsetHeight } | ||
} | ||
}); | ||
}) | ||
``` | ||
@@ -281,7 +329,7 @@ | ||
```js | ||
var vnode1 = h('div', [h('div', [h('span', 'Hello')])]); | ||
var vnode2 = h('div', []); | ||
patch(container, vnode1); | ||
patch(vnode1, vnode2); | ||
```mjs | ||
var vnode1 = h('div', [h('div', [h('span', 'Hello')])]) | ||
var vnode2 = h('div', []) | ||
patch(container, vnode1) | ||
patch(vnode1, vnode2) | ||
``` | ||
@@ -302,11 +350,11 @@ | ||
```javascript | ||
```mjs | ||
var myModule = { | ||
create: function(oldVnode, vnode) { | ||
create: function (oldVnode, vnode) { | ||
// invoked whenever a new virtual node is created | ||
}, | ||
update: function(oldVnode, vnode) { | ||
update: function (oldVnode, vnode) { | ||
// invoked whenever a virtual node is updated | ||
} | ||
}; | ||
} | ||
``` | ||
@@ -329,4 +377,4 @@ | ||
```javascript | ||
h('a', {class: {active: true, selected: false}}, 'Toggle'); | ||
```mjs | ||
h('a', { class: { active: true, selected: false } }, 'Toggle') | ||
``` | ||
@@ -338,6 +386,14 @@ | ||
```javascript | ||
h('a', {props: {href: '/foo'}}, 'Go to Foo'); | ||
```mjs | ||
h('a', { props: { href: '/foo' } }, 'Go to Foo') | ||
``` | ||
Properties can only be set. Not removed. Even though browsers allow addition and | ||
deletion of custom properties, deletion will not be attempted by this module. | ||
This makes sense, because native DOM properties cannot be removed. And | ||
if you are using custom properties for storing values or referencing | ||
objects on the DOM, then please consider using | ||
[data-\* attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) | ||
instead. Perhaps via [the dataset module](#the-dataset-module). | ||
### The attributes module | ||
@@ -347,4 +403,4 @@ | ||
```javascript | ||
h('a', {attrs: {href: '/foo'}}, 'Go to Foo'); | ||
```mjs | ||
h('a', { attrs: { href: '/foo' } }, 'Go to Foo') | ||
``` | ||
@@ -371,4 +427,4 @@ | ||
```javascript | ||
h('button', {dataset: {action: 'reset'}}, 'Reset'); | ||
```mjs | ||
h('button', { dataset: { action: 'reset' } }, 'Reset') | ||
``` | ||
@@ -381,6 +437,6 @@ | ||
```javascript | ||
```mjs | ||
h('span', { | ||
style: {border: '1px solid #bada55', color: '#c0ffee', fontWeight: 'bold'} | ||
}, 'Say my name, and every colour illuminates'); | ||
style: { border: '1px solid #bada55', color: '#c0ffee', fontWeight: 'bold' } | ||
}, 'Say my name, and every colour illuminates') | ||
``` | ||
@@ -392,6 +448,6 @@ | ||
```javascript | ||
```mjs | ||
h('div', { | ||
style: {position: shouldFollow ? 'fixed' : ''} | ||
}, 'I, I follow, I follow you'); | ||
style: { position: shouldFollow ? 'fixed' : '' } | ||
}, 'I, I follow, I follow you') | ||
``` | ||
@@ -404,6 +460,6 @@ | ||
```javascript | ||
```mjs | ||
h('div', { | ||
style: {'--warnColor': 'yellow'} | ||
}, 'Warning'); | ||
style: { '--warnColor': 'yellow' } | ||
}, 'Warning') | ||
``` | ||
@@ -416,6 +472,6 @@ | ||
```javascript | ||
```mjs | ||
h('span', { | ||
style: {opacity: '0', transition: 'opacity 1s', delayed: {opacity: '1'}} | ||
}, 'Imma fade right in!'); | ||
style: { opacity: '0', transition: 'opacity 1s', delayed: { opacity: '1' } } | ||
}, 'Imma fade right in!') | ||
``` | ||
@@ -432,7 +488,10 @@ | ||
```javascript | ||
```mjs | ||
h('span', { | ||
style: {opacity: '1', transition: 'opacity 1s', | ||
remove: {opacity: '0'}} | ||
}, 'It\'s better to fade out than to burn away'); | ||
style: { | ||
opacity: '1', | ||
transition: 'opacity 1s', | ||
remove: { opacity: '0' } | ||
} | ||
}, 'It\'s better to fade out than to burn away') | ||
``` | ||
@@ -444,7 +503,10 @@ | ||
```javascript | ||
```mjs | ||
h('span', { | ||
style: {opacity: '1', transition: 'opacity 1s', | ||
destroy: {opacity: '0'}} | ||
}, 'It\'s better to fade out than to burn away'); | ||
style: { | ||
opacity: '1', | ||
transition: 'opacity 1s', | ||
destroy: { opacity: '0' } | ||
} | ||
}, 'It\'s better to fade out than to burn away') | ||
``` | ||
@@ -462,5 +524,7 @@ | ||
```javascript | ||
function clickHandler(ev) { console.log('got clicked'); } | ||
h('div', {on: {click: clickHandler}}); | ||
```mjs | ||
function clickHandler (ev) { | ||
console.log('got clicked') | ||
} | ||
h('div', { on: { click: clickHandler } }) | ||
``` | ||
@@ -481,17 +545,24 @@ | ||
```javascript | ||
function clickHandler(number) { console.log('button ' + number + ' was clicked!'); } | ||
```mjs | ||
function clickHandler (number) { | ||
console.log('button ' + number + ' was clicked!') | ||
} | ||
h('div', [ | ||
h('a', {on: {click: [clickHandler, 1]}}), | ||
h('a', {on: {click: [clickHandler, 2]}}), | ||
h('a', {on: {click: [clickHandler, 3]}}), | ||
]); | ||
h('a', { on: { click: [clickHandler, 1] } }), | ||
h('a', { on: { click: [clickHandler, 2] } }), | ||
h('a', { on: { click: [clickHandler, 3] } }), | ||
]) | ||
``` | ||
Each handler is called not only with the given arguments but also with the current event and vnode appended to the argument list. It also supports using multiple listeners per event by specifying an array of handlers: | ||
```javascript | ||
stopPropagation = function(ev) { ev.stopPropagation() } | ||
sendValue = function(func, ev, vnode) { func(vnode.elm.value) } | ||
h('a', { on:{ click:[[sendValue, console.log], stopPropagation] } }); | ||
```mjs | ||
stopPropagation = function (ev) { | ||
ev.stopPropagation() | ||
} | ||
sendValue = function (func, ev, vnode) { | ||
func(vnode.elm.value) | ||
} | ||
h('a', { on: { click: [[sendValue, console.log], stopPropagation] } }) | ||
``` | ||
@@ -510,15 +581,21 @@ | ||
```javascript | ||
```mjs | ||
// Does not work | ||
var sharedHandler = { | ||
change: function(e){ console.log('you chose: ' + e.target.value); } | ||
}; | ||
change: function (e) { console.log('you chose: ' + e.target.value) } | ||
} | ||
h('div', [ | ||
h('input', {props: {type: 'radio', name: 'test', value: '0'}, | ||
on: sharedHandler}), | ||
h('input', {props: {type: 'radio', name: 'test', value: '1'}, | ||
on: sharedHandler}), | ||
h('input', {props: {type: 'radio', name: 'test', value: '2'}, | ||
on: sharedHandler}) | ||
]); | ||
h('input', { | ||
props: { type: 'radio', name: 'test', value: '0' }, | ||
on: sharedHandler | ||
}), | ||
h('input', { | ||
props: { type: 'radio', name: 'test', value: '1' }, | ||
on: sharedHandler | ||
}), | ||
h('input', { | ||
props: { type: 'radio', name: 'test', value: '2' }, | ||
on: sharedHandler | ||
}) | ||
]) | ||
``` | ||
@@ -529,19 +606,25 @@ | ||
```javascript | ||
```mjs | ||
// Works | ||
var sharedHandler = function(e){ console.log('you chose: ' + e.target.value); }; | ||
var sharedHandler = function (e) { | ||
console.log('you chose: ' + e.target.value) | ||
} | ||
h('div', [ | ||
h('input', {props: {type: 'radio', name: 'test', value: '0'}, | ||
on: {change: sharedHandler}}), | ||
h('input', {props: {type: 'radio', name: 'test', value: '1'}, | ||
on: {change: sharedHandler}}), | ||
h('input', {props: {type: 'radio', name: 'test', value: '2'}, | ||
on: {change: sharedHandler}}) | ||
]); | ||
h('input', { | ||
props: { type: 'radio', name: 'test', value: '0' }, | ||
on: { change: sharedHandler } | ||
}), | ||
h('input', { | ||
props: { type: 'radio', name: 'test', value: '1' }, | ||
on: { change: sharedHandler } | ||
}), | ||
h('input', { | ||
props: { type: 'radio', name: 'test', value: '2' }, | ||
on: { change: sharedHandler } | ||
}) | ||
]) | ||
``` | ||
## Helpers | ||
## SVG | ||
### SVG | ||
SVG just works when using the `h` function for creating virtual | ||
@@ -551,8 +634,8 @@ nodes. SVG elements are automatically created with the appropriate | ||
```javascript | ||
```mjs | ||
var vnode = h('div', [ | ||
h('svg', {attrs: {width: 100, height: 100}}, [ | ||
h('circle', {attrs: {cx: 50, cy: 50, r: 40, stroke: 'green', 'stroke-width': 4, fill: 'yellow'}}) | ||
h('svg', { attrs: { width: 100, height: 100 } }, [ | ||
h('circle', { attrs: { cx: 50, cy: 50, r: 40, stroke: 'green', 'stroke-width': 4, fill: 'yellow' } }) | ||
]) | ||
]); | ||
]) | ||
``` | ||
@@ -562,28 +645,10 @@ | ||
#### Using Classes in SVG Elements | ||
### Classes in SVG Elements | ||
Certain browsers (like IE <=11) [do not support `classList` property in SVG elements](http://caniuse.com/#feat=classlist). | ||
Hence, the _class_ module (which uses `classList` property internally) will not work for these browsers. | ||
Certain browsers (like IE <=11) [do not support `classList` property in SVG elements](http://caniuse.com/#feat=classlist). | ||
Because the _class_ module internally uses `classList`, it will not work in this case unless you use a [classList polyfill](https://www.npmjs.com/package/classlist-polyfill). | ||
(If you don't want to use a polyfill, you can use the `class` attribute with the _attributes_ module). | ||
The classes in selectors for SVG elements work fine from version 0.6.7. | ||
## Thunks | ||
You can add dynamic classes to SVG elements for these cases by using the _attributes_ module and an Array as shown below: | ||
```js | ||
h('svg', [ | ||
h('text.underline', { // 'underline' is a selector class, remain unchanged between renders. | ||
attrs: { | ||
// 'active' and 'red' are dynamic classes, they can change between renders | ||
// so we need to put them in the class attribute. | ||
// (Normally we'd use the classModule, but it doesn't work inside SVG) | ||
class: [isActive && "active", isColored && "red"].filter(Boolean).join(" ") | ||
} | ||
}, | ||
'Hello World' | ||
) | ||
]) | ||
``` | ||
### Thunks | ||
The `thunk` function takes a selector, a key for identifying a thunk, | ||
@@ -596,2 +661,4 @@ a function that returns a vnode and a variable amount of state | ||
The `renderFn` is invoked only if the `renderFn` is changed or `[stateArguments]` array length or it's elements are changed. | ||
The `key` is optional. It should be supplied when the `selector` is | ||
@@ -606,5 +673,5 @@ not unique among the thunks siblings. This ensures that the thunk is | ||
```js | ||
function numberView(n) { | ||
return h('div', 'Number is: ' + n); | ||
```mjs | ||
function numberView (n) { | ||
return h('div', 'Number is: ' + n) | ||
} | ||
@@ -618,5 +685,5 @@ ``` | ||
```js | ||
function render(state) { | ||
return thunk('num', numberView, [state.number]); | ||
```mjs | ||
function render (state) { | ||
return thunk('num', numberView, [state.number]) | ||
} | ||
@@ -636,12 +703,14 @@ ``` | ||
## Virtual Node | ||
**Properties** | ||
- [sel](#sel--string) | ||
- [data](#data--object) | ||
- [children](#children--array) | ||
- [text](#text--string) | ||
- [elm](#elm--element) | ||
- [key](#key--string--number) | ||
#### sel : String | ||
* [sel](#sel--string) | ||
* [data](#data--object) | ||
* [children](#children--array) | ||
* [text](#text--string) | ||
* [elm](#elm--element) | ||
* [key](#key--string--number) | ||
### sel : String | ||
The `.sel` property of a virtual node is the CSS selector passed to | ||
@@ -652,3 +721,3 @@ [`h()`](#snabbdomh) during creation. For example: `h('div#container', | ||
#### data : Object | ||
### data : Object | ||
@@ -663,12 +732,14 @@ The `.data` property of a virtual node is the place to add information | ||
For example `h('div', {props: {className: 'container'}}, [...])` will produce a virtual node with | ||
```js | ||
{ | ||
"props": { | ||
className: "container" | ||
```mjs | ||
({ | ||
props: { | ||
className: 'container' | ||
} | ||
} | ||
}) | ||
``` | ||
as its `.data` object. | ||
#### children : Array<vnode> | ||
### children : Array<vnode> | ||
@@ -683,12 +754,12 @@ The `.children` property of a virtual node is the third (optional) | ||
```js | ||
```mjs | ||
[ | ||
{ | ||
sel: 'h1', | ||
data: {}, | ||
children: undefined, | ||
text: 'Hello, World', | ||
elm: Element, | ||
key: undefined, | ||
} | ||
{ | ||
sel: 'h1', | ||
data: {}, | ||
children: undefined, | ||
text: 'Hello, World', | ||
elm: Element, | ||
key: undefined, | ||
} | ||
] | ||
@@ -699,3 +770,3 @@ ``` | ||
#### text : string | ||
### text : string | ||
@@ -709,3 +780,3 @@ The `.text` property is created when a virtual node is created with | ||
#### elm : Element | ||
### elm : Element | ||
@@ -717,3 +788,3 @@ The `.elm` property of a virtual node is a pointer to the real DOM | ||
#### key : string | number | ||
### key : string | number | ||
@@ -729,6 +800,7 @@ The `.key` property is created when a key is provided inside of your | ||
If provided, the `.key` property must be unique among sibling elements. | ||
For example: `h('div', {key: 1}, [])` will create a virtual node | ||
object with a `.key` property with the value of `1`. | ||
## Structuring applications | ||
@@ -745,7 +817,7 @@ | ||
* [Cycle.js](https://cycle.js.org/) – | ||
"A functional and reactive JavaScript framework for cleaner code" | ||
uses Snabbdom | ||
"A functional and reactive JavaScript framework for cleaner code" | ||
uses Snabbdom | ||
* [Vue.js](http://vuejs.org/) use a fork of snabbdom. | ||
* [scheme-todomvc](https://github.com/amirouche/scheme-todomvc/) build | ||
redux-like architecture on top of snabbdom bindings. | ||
redux-like architecture on top of snabbdom bindings. | ||
* [kaiju](https://github.com/AlexGalays/kaiju) - | ||
@@ -767,2 +839,3 @@ Stateful components and observables on top of snabbdom | ||
* [Pureact](https://github.com/irony/pureact) - "65 lines implementation of React incl Redux and hooks with only one dependency - Snabbdom" | ||
* [Snabberb](https://github.com/tobymao/snabberb) - A minimalistic Ruby framework using [Opal](https://github.com/opal/opal) and Snabbdom for building reactive views. | ||
@@ -774,9 +847,11 @@ Be sure to share it if you're building an application in another way | ||
``` | ||
```text | ||
Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node': | ||
The node before which the new node is to be inserted is not a child of this node. | ||
``` | ||
The reason for this error is reusing of vnodes between patches (see code example), snabbdom stores actual dom nodes inside the virtual dom nodes passed to it as performance improvement, so reusing nodes between patches is not supported. | ||
```js | ||
var sharedNode = h('div', {}, 'Selected'); | ||
```mjs | ||
var sharedNode = h('div', {}, 'Selected') | ||
var vnode1 = h('div', [ | ||
@@ -786,3 +861,3 @@ h('div', {}, ['One']), | ||
h('div', {}, [sharedNode]), | ||
]); | ||
]) | ||
var vnode2 = h('div', [ | ||
@@ -792,8 +867,10 @@ h('div', {}, ['One']), | ||
h('div', {}, ['Three']), | ||
]); | ||
patch(container, vnode1); | ||
patch(vnode1, vnode2); | ||
]) | ||
patch(container, vnode1) | ||
patch(vnode1, vnode2) | ||
``` | ||
You can fix this issue by creating a shallow copy of the object (here with object spread syntax): | ||
```js | ||
```mjs | ||
var vnode2 = h('div', [ | ||
@@ -803,7 +880,9 @@ h('div', {}, ['One']), | ||
h('div', {}, ['Three']), | ||
]); | ||
]) | ||
``` | ||
Another solution would be to wrap shared vnodes in a factory function: | ||
```js | ||
var sharedNode = () => h('div', {}, 'Selected'); | ||
```mjs | ||
var sharedNode = () => h('div', {}, 'Selected') | ||
var vnode1 = h('div', [ | ||
@@ -813,3 +892,8 @@ h('div', {}, ['One']), | ||
h('div', {}, [sharedNode()]), | ||
]); | ||
]) | ||
``` | ||
## Opportunity for community feedback | ||
Pull requests that the community may care to provide feedback on should be | ||
merged after such opportunity of a few days was provided. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
852
0
Yes
179243
50
78
2542