Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/gvergnaud/evolui

A tiny reactive user interface library, built on top of RxJs.
https://github.com/gvergnaud/evolui

observable reactive-programming rxjs ui virtual-dom

Last synced: 3 months ago
JSON representation

A tiny reactive user interface library, built on top of RxJs.

Lists

README

        

evolui


A tiny reactive user interface library.


CircleCI status
npm version

## Features

* **Async** — evolui magically understands `Observables` and `Promises`. Just put them where they need to be displayed and, when they update, your UI will be refreshed for you.
* **Virtual DOM** — evolui has a fast virtual DOM diffing algorithm and do the less work possible by only updating the closest node from the values that changed.
* **Components** — You can build large applications by splitting its complexity inside encapsulated and predictable components.
* **Tiny** — The API surface is very small and the whole library is only `4kB` gziped.

## Install

```
npm install evolui rxjs
```

## Examples

* All examples [Demo](https://7yv1494p9x.codesandbox.io/) — [see code](https://codesandbox.io/s/github/gvergnaud/evolui/tree/master/example)
* Simple Animation [Demo](https://72wkn61x21.codesandbox.io/) — [see code](https://codesandbox.io/s/72wkn61x21)
* Complex Animation [Demo](https://31z431n4m.codesandbox.io/) — [see code](https://codesandbox.io/s/31z431n4m)
* Animated Pinterest Like Grid [Demo](https://wqyl0xmo47.codesandbox.io/) — [see code](https://codesandbox.io/s/wqyl0xmo47)

**To jump to the code, visite the [`example`](https://github.com/gvergnaud/evolui/tree/master/example) folder.**

## Getting Started

### Promises

```js
import html, { render } from 'evolui'
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

const promise = delay(1000).then(() => 'World!')

render(
html`


Hello, ${promise}


`,
document.querySelector('#mount')
)
```

![Promise demo](https://github.com/gvergnaud/evolui/blob/media/gifs/evolui-1.gif?raw=true)

### Observables

```js
import html, { render } from 'evolui'
import { interval } from 'rxjs'
import { take, map } from 'rxjs/operators'

const text$ = interval(1000).pipe(
take(4),
map(index => ['.', '..', '...', 'World!'][index])
)

render(
html`


Hello, ${text$}


`,
document.querySelector('#mount')
)
```

![Observable demo](https://github.com/gvergnaud/evolui/blob/media/gifs/evolui-2.gif?raw=true)

## Simple App

```js
import html, { render } from 'evolui'
import { createState } from 'evolui/extra'

const Counter = () => {
const state = createState({ count: 0 })

return html`


count: ${state.count}
state.count.set(c => c - 1)}>-

state.count.set(c => c + 1)}>+

`
}

render(html`<${Counter} />`, document.querySelector('#mount'))
```

## Concept

The main goal of evolui is to make dealing with observables as easy as dealing with regular values.

Observables are a great way to represent values that change over time. The hard part though is combining them. This is where evolui comes in handy. It understands **any** combination of `Array`s, `Promise`s and `Observable`s, so you never have to worry about the way you should combine them before putting them inside your template.

```js
import html from 'evolui'
import { from } from 'rxjs'
import { startWith } from 'rxjs/operators'

const getCharacterName = id =>
fetch(`https://swapi.co/api/people/${id}`)
.then(res => res.json())
.then(character => character.name)

html`


${'' /* this will return an array of observables. */}
${'' /* Don't panic! evolui understands that as well */}
${[1, 2, 3].map(
id => html`


${from(getCharacterName(id)).pipe(startWith('Loading...'))}


`
)}

`
```

![list demo](https://github.com/gvergnaud/evolui/blob/media/gifs/evolui-3.gif?raw=true)

## Components

Evolui lets you organize your code in components.

Components are defined as a simple function of `Observable Props -> Observable VirtualDOM`:

```js
import html, { render } from 'evolui'
import { createState } from 'evolui/extra'
import { map } from 'rxjs/operators'

const Button = props$ =>
props$.pipe(
map(
({ text, onClick }) => html`

${text}

`
)
)

const App = () => {
const state = createState({ count: 0 })

return html`


<${Button}
text="-"
onClick=${() => state.count.set(c => c - 1)}
/>

count: ${state.count}

<${Button}
text="+"
onClick=${() => state.count.set(c => c + 1)}
/>


`
}

render(html`<${App} />`, document.querySelector('#mount'))
```

### children

Components can have children 👍

```js
import html, { render } from 'evolui'
import { map } from 'rxjs/operators'

const CrazyLayout = map(({ children }) => html`

${children}
`)

render(
html`
<${CrazyLayout}>

I'm the content


${CrazyLayout}>
`,
document.querySelector('#mount')
)
```

## Extra

### Animations

`evolui/extra` exports a **spring** animation helper called `ease`.

```typescript
ease: (stiffness: number, damping: number, id: string?) => Observable => Observable
```

You just have to pipe any of your observables to `ease(, )` to make it animated.

If you are interested in using this feature separately, check out [`rx-ease`](https://github.com/gvergnaud/rx-ease)

```js
import html, { render } from 'evolui'
import { ease } from 'evolui/extra'
import { fromEvent } from 'rxjs'
import { map, startWith } from 'rxjs/operators'

const stiffness = 120
const damping = 20

const style$ = fromEvent(window, 'click').pipe(
map(() => ({ x: e.clientX, y: e.clientY })),
startWith({ x: 0, y: 0 }),
ease({
x: [stiffness, damping],
y: [stiffness, damping],
}),
map({ x, y }) => ({
transform: `translate(${x}px,${y}px)`
})
)

render(
html`


`,
document.querySelector('#mount')
)
```

![animation demo](https://raw.githubusercontent.com/gvergnaud/evolui/c445de8161c151c24d84d0ad61af0a6185f0d62d/dot-animation.gif)

For single values, you can pass the `stiffness` and `damping` directly

```js
import html, { render } from 'evolui'
import { ease } from 'evolui/extra'
import { interval } from 'rxjs'
import { map } from 'rxjs/operators'

const width$ = interval(1000).pipe(
map(i => i * 50),
ease(120, 20)
)

render(
html`


`,
document.querySelector('#mount')
)
```

## API

#### text :: TemplateLiteral -> Observable String

```js
import { text } from 'evolui'

const style$ = text`
position: absolute;
transform: translate(${x$}px, ${y$}px);
`
```

#### html :: TemplateLiteral -> Observable VirtualDOM

```js
import html from 'evolui'

const App = () => html`


`
```

#### render :: Observable VirtualDOM -> DOMNode -> ()

```js
import { render } from 'evolui'

render(html`<${App} />`, document.querySelector('#mount'))
```

#### ease :: (Number, Number) -> Observable Number -> Observable Number

```js
import { ease } from 'evolui/extra'
import { interval } from 'rxjs'

interval(1000).pipe(
ease(120, 20),
subscribe(x => console.log(x)) // every values will be interpolated
)
```

#### createState :: Object -> State

Create an object of mutable reactive values.

Each key on your initial state will be transformed into a stream, with a special `set` method on it.
`set` can take either a value or a mapper function.

```js
import html, { render } from 'evolui'
import { createState } from 'evolui/extra'

const state = createState({ count: 0 })

console.log(state.count)
// => Observable.of(0)

const reset = () => state.count.set(0)
const add1 = () => state.count.set(c => c + 1)

render(
html`


count: ${state.count}
reset
+

`,
document.querySelector('#mount')
)
```

#### all :: [Observable a] -> Observable [a]

```js
import { all } from 'evolui/extra'

const z$ = all([x$, y$]).map(([x, y]) => x + y)
```

### Lifecycle

* **mount** — after the element as been rendered
* **update** — after the dom element as been update
* **unmount** — before the dom element is removed from the dom

```js
html`


`
```

## Contributing

If you find this interesting and you want to contribute, don't hesitate to open an issue or to reach me out on twitter [@GabrielVergnaud](https://twitter.com/GabrielVergnaud)!