Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

forest

Package Overview
Dependencies
Maintainers
5
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

forest

UI engine for web

  • 0.21.2
  • latest
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
6.7K
increased by9%
Maintainers
5
Weekly downloads
 
Created
Source

forest

UI engine for web

Usage

import {createStore, createEvent, sample} from 'effector'
import {using, spec, h} from 'forest'

using(document.body, () => {
  const {change, submit, $fields} = formModel()

  h('section', () => {
    spec({style: {width: '15em'}})

    h('form', () => {
      spec({
        handler: {
          config: {prevent: true},
          on: {submit},
        },
        style: {
          display: 'flex',
          flexDirection: 'column',
        },
      })

      h('input', {
        attr: {placeholder: 'Username'},
        handler: {input: change('username')},
      })

      h('input', {
        attr: {type: 'password', placeholder: 'Password'},
        classList: ['w-full', 'py-2', 'px-4'],
        handler: {input: change('password')},
      })

      h('button', {
        text: 'Submit',
        attr: {
          disabled: $fields.map(
            fields => !(fields.username && fields.password),
          ),
        },
      })
    })

    h('section', () => {
      spec({style: {marginTop: '1em'}})
      h('div', {text: 'Reactive form debug:'})
      h('pre', {text: $fields.map(stringify)})
    })
  })
})

function formModel() {
  const changed = createEvent()
  const submit = createEvent()

  const $fields = createStore({}).on(changed, (fields, {name, value}) => ({
    ...fields,
    [name]: value,
  }))

  const change = name => changed.prepend(e => ({name, value: e.target.value}))

  sample({
    source: $fields,
    clock: submit,
    fn: stringify,
  }).watch(alert)

  return {change, submit, $fields}
}

function stringify(values) {
  return JSON.stringify(values, null, 2)
}

Try it

API

using

Start an application from given root dom node. Can accept forked Scope. Set hydrate: true to reuse root html content (useful for ssr)

function using(root: DOMElement, fn: () => void): void

function using(
  root: DOMElement,
  config: {
    fn: () => void
    hydrate?: boolean
    scope?: Scope
  },
): void

h

Declare single dom element.

function h(tag: string, fn: () => void): void

function h(
  tag: string,
  config: {
    attr?: PropertyMap
    style?: PropertyMap
    styleVar?: PropertyMap
    classList?: ClassListMap | ClassListArray
    data?: PropertyMap
    text?: Property | Property[]
    visible?: Store<boolean>
    handler?:
      | {[domEvent: string]: Event<any>}
      | {
          config: {
            passive?: boolean
            capture?: boolean
            prevent?: boolean
            stop?: boolean
          }
          on: {[domEvent: string]: Event<any>}
        }
    fn?: () => void
  },
): void

See also: PropertyMap, Property

Config fields:

  • attr: add HTML attributes, e.g. class or input's value. {value: createStore('initial')} will become "value"="initial"

  • style: add inline styles. All style objects will be merged to single style html attribute. Object fields in camel case will be converted to dash-style, e.g. {borderRadius: '3px'} will become "style"="border-radius: 3px".

  • styleVar: add css variables to inline styles. {themeColor: createStore('red')} will become "style"="--themeColor: red"

  • classList: add class names to class attribute. {active: true} will become "class"="active" , ['active', 'disabled'] will become "class"="active disabled" and so on with Store support.

  • data: add data attributes. Object fields in camel case will be converted to dash-style, e.g. {buttonType: 'outline'} will become "data-button-type"="outline" and might be queried in css in this way:

[data-button-type='outline'] {
}
  • text: add text to element as property or array of properties

  • visible: node will be presented in dom tree while store value is true. Useful for conditional rendering

  • handler: add event handlers to dom node. In cases when preventDefault or stopPropagation is needed, extended form with config object can be used

const click = createEvent<MouseEvent>()

h('button', {
  text: 'Click me',
  handler: {click},
})

h('a', {
  text: 'Click me',
  handler: {
    config: {prevent: true},
    on: {click},
  },
})

Handler config fields:

  • fn: add children to given element by nesting api methods calls

spec

Add new properties to dom element. Designed to call from h callbacks and has the same fields as in h(tag, config). Can be called as many times as needed

function spec(config: {
  attr?: PropertyMap
  style?: PropertyMap
  styleVar?: PropertyMap
  classList?: ClassListMap | ClassListArray
  data?: PropertyMap
  text?: Property | Property[]
  visible?: Store<boolean>
  handler?:
    | {[domEvent: string]: Event<any>}
    | {
        config: {
          passive?: boolean
          capture?: boolean
          prevent?: boolean
          stop?: boolean
        }
        on: {[domEvent: string]: Event<any>}
      }
}): void
classList

Property classList has two forms, each optionally reactive:

  • object map
const $isEnabled = createStore(true)
spec({classList: {first: true, second: $isEnabled}})
  • array list

Be careful, each array item will be treated as a single class name, so it should not have a spaces.

const $class = createStore('active')
spec({classList: ['size-big', $class]})

If spec with classList called twice or more, all enabled classes will be merged in the order of appearance.
Also, classList will be merged with static class attribute:

h('div', {
  attr: {class: 'first second'},
  classList: ['third'],
  fn() {
    spec({classList: {fourth: true}})
  },
})

// => <div class="first second third fourth"></div>

list

Render array of items from store

function list<T>(source: Store<T[]>, fn: (config: {store: Store<T>, key: Store<number>}) => void): void

function list<T>(config: {
  source: Store<T[]>,
  key: string
  fields?: string[]
  fn: (config: {store: Store<T>, key: Store<any>, fields: Store<any>[]}) => void): void
}): void

Config fields:

  • source: store with an array of items
  • key: field name which value will be used as key for given item
  • fn: function which will be used as a template for every list item. Receive item value and item key as stores and fields as array of stores if provided. All fields are strongly typed and inferred from config definition
  • fields: array of item field names which will be passed to fn as array of separate stores. Useful to avoid store.map and remap calls

variant

Mount one of given cases by selecting a specific one by the current value of the key field of source store value. Type of store in cases functions will be inferred from a case type. Optional default case - __ (like in split)

function variant<T>(config: {
  source: Store<T>
  key: string
  cases: {
    [caseName: string]: ({store: Store<T>}) => void
  }
}): void

route

Generalized route is a combination of state and visibility status. fn content will be mounted until visible called with source value will return true. In case of store in visible field, content will be mounted while that store contain true. variant is shorthand for creating several routes at once

function route<T>(config: {
  source: Store<T>
  visible: ((value: T) => boolean) | Store<boolean>
  fn: (config: {store: Store<T>}) => void
}): void

text

Use template literals to add text to dom node. Accept any properties

function text(words: TemplateStringsArray, ...values: Property[]): void

Example

const $username = createStore('guest')

h('h1', () => {
  text`Hello ${$username}!`
})

rec

Provide support for recursive templates. Can be called outside from using calls

function rec<T>(config: {store: Store<T>}): (config: {store: Store<T>}) => void

block

Allow defining and validate template outside from using calls.

function block(config: {fn: () => void}): () => void

renderStatic

Method from forest/server to render given application to string. Can accept forked Scope, in which case fn children must be wrapped in block to ensure that all units are created before fork call

function renderStatic(fn: () => void): Promise<string>

function renderStatic(config: {scope?: Scope; fn: () => void}): Promise<string>

remap

Helper for retrieving value fields from single store. Shorthand for several store.map(val => val[fieldName]) calls. Infer types when used with either single key or with as const: const [id, name] = remap(user, ['id', 'name'] as const)

function remap<T>(store: Store<T>, keys: string[]): Store<any>[]

function remap<T>(store: Store<T>, key: string): Store<any>

val

Helper for joining properties to single string with template literals. If only plain values are passed, the method returns string

function val(words: TemplateStringsArray, ...values: Property[]): Store<string>

function val(words: TemplateStringsArray, ...values: PlainProperty[]): string

Example

const $store = createStore(10)
const a = 20

h('g', {
  attr: {
    transform: val`translate(${$store} ${a})`,
  },
})

Type terms

PlainProperty

Value types accepted by methods, which write values to dom properties. Strings are written as is, numbers are converted to strings, null and false mean no value (property deletion), true is used when the specific property value is not needed.

type PlainProperty = string | number | null | boolean

Property

In most cases dom properties can be wrapped in stores, thereby making result value dynamic

type Property = PlainProperty | Store<PlainProperty>

PropertyMap

Object with dom properties, possibly reactive

type PropertyMap = {[field: string]: Property}

ClassListMap

Object with class names as keys and boolean values, possibly reactive

type ClassListMap = {[className: string]: Store<boolean> | boolean}
spec({
  classList: {
    'class-name': true,
    'class-name-2': $booleanStore,
  },
})

ClassListArray

Array with class names, possibly reactive

type ClassListArray = Array<Store<string> | string>
spec({
  classList: ['class-name', $stringStore],
})

Keywords

FAQs

Package last updated on 31 Aug 2022

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc