1.0.0-alpha.1 • Published 7 months ago

nanoviews v1.0.0-alpha.1

Weekly downloads
-
License
MIT
Repository
github
Last release
7 months ago

nanoviews

ESM-only package NPM version Dependencies status Install size Build status Coverage status

A small Direct DOM library for creating user interfaces.

  • Small. Between 3.4 and 6 kB (minified and brotlied). Zero external dependencies*.
  • Direct DOM. Less CPU and memory usage compared to Virtual DOM.
  • Designed for best Tree-Shaking: only the code you use is included in your bundle.
  • TypeScript-first.
import { signal } from 'nanoviews/stores'
import { div, a, img, h1, button, p, mount } from 'nanoviews'

function App() {
  const $counter = signal(0)

  return div()(
    a({ href: 'https://vitejs.dev', target: '_blank' })(
      img({ src: './vite.svg', class: 'logo', alt: 'Vite logo' })
    ),
    a({ href: 'https://github.com/TrigenSoftware/nanoviews', target: '_blank' })(
      img({ src: './nanoviews.svg', class: 'logo nanoviews', alt: 'Nanoviews logo' })
    ),
    h1()('Vite + Nanoviews'),
    div({ class: 'card' })(
      button({
        onClick() {
          $counter.set($counter.get() + 1)
        }
      })('count is ', $counter)
    ),
    p({ class: 'read-the-docs' })('Click on the Vite and Nanoviews logos to learn more')
  )
}

mount(App, document.querySelector('#app'))

Install

pnpm add -D nanoviews
# or
npm i -D nanoviews
# or
yarn add -D nanoviews

Reactivity

Nanoviews is using Kida under the hood for reactivity. This library is inspired by Nano Stores and was build specially for Nanoviews.

import { signal } from 'nanoviews/stores' // or import { signal } from 'kida'
import { fragment, input, p } from 'nanoviews'

const $text = signal('')

fragment(
  input({
    onInput(event) {
      $text.set(event.target.value)
    }
  }),
  p()($text)
)

Basic markup

Nanoviews provides a set of methods for creating HTML elements with the specified attributes and children. Every method creates a "block" that represents a DOM node or another block(s).

Child can be an another element, primitive value (string, number, boolean, null or undefined), store with primitive or array of children. Attributes also can be a primitive value or store.

import { signal } from 'nanoviews/stores'
import { ul, li } from 'nanoviews'

const $boolean = signal(true)

ul({ class: 'list' })(
  li()('String value'),
  li()('Number value', 42),
  li()('Boolean value', $boolean)
)

mount

mount is a method that mounts the component to the specified container.

import { signal } from 'nanoviews/stores'
import { div, h1, p, mount } from 'nanoviews'

function App() {
  return div()(
    h1()('Nanoviews App'),
    p()('Hello World!')
  )
}

mount(App, document.querySelector('#app'))

Special methods

text$

text$ is a method that creates text node block with the specified value or store.

import { signal } from 'nanoviews/stores'
import { text$, effect$ } from 'nanoviews'

function TickTak() {
  const $tick = signal(0)

  effect$(() => {
    const id = setInterval(() => {
      $tick.set($tick.get() + 1)
    }, 1000)

    return () => clearInterval(id)
  })

  return text$($tick)
}

fragment

fragment is a method that creates a fragment block with the specified children.

import { signal } from 'nanoviews/stores'
import { fragment, effect$ } from 'nanoviews'

function TickTak() {
  const $tick = signal(0)

  effect$(() => {
    const id = setInterval(() => {
      $tick.set($tick.get() + 1)
    }, 1000)

    return () => clearInterval(id)
  })

  return fragment('Tick tak: ', $tick)
}

dangerouslySetInnerHTML

dangerouslySetInnerHTML is a method that sets the inner HTML of the element block. It is used for inserting HTML from a source that may not be trusted.

import { div, dangerouslySetInnerHTML } from 'nanoviews'

dangerouslySetInnerHTML(
  div({ id: 'rendered-md' }),
  '<p>Some text</p>'
)

shadow

shadow is a method that attaches a shadow DOM to the specified element block.

import { div, shadow } from 'nanoviews'

shadow(
  div({ id: 'custom-element' }),
  {
    mode: 'open'
  }
)(
  'Nanoviews can shadow DOM!'
)

Effect attributes

Effect attributes are special attributes that can control element's behavior.

ref$

ref$ is an effect attribute that can provide a reference to the DOM node.

import { signal } from 'nanoviews/stores'
import { div, ref$ } from 'nanoviews'

const $ref = signal(null)

div({
  [ref$]: $ref
})(
  'Target element'
)

style$

style$ is an effect attribute that manages the style of the element.

import { signal } from 'nanoviews/stores'
import { button, style$ } from 'nanoviews'

const $color = signal('white')

button({
  [style$]: {
    color: $color,
    backgroundColor: 'black'
  }
})(
  'Click me'
)

autoFocus$

autoFocus$ is an effect attribute that sets the auto focus on the element.

import { input, autoFocus$ } from 'nanoviews'

input({
  type: 'text',
  [autoFocus$]: true
})

value$

value$ is an effect attribute that manages the value of text inputs.

import { signal } from 'nanoviews/stores'
import { textarea, value$ } from 'nanoviews'

const $review = signal('')

textarea({
  name: 'review',
  [value$]: $review
})(
  'Write your review here'
)

checked$

checked$ is an effect attribute that manages the checked state of checkboxes and radio buttons.

import { signal } from 'nanoviews/stores'
import { input, checked$, Indeterminate } from 'nanoviews'

const $checked = signal(false)

input({
  type: 'checkbox',
  [checked$]: $checked
})

Also you can manage indeterminate state of checkboxes:

$checked.set(Indeterminate)

selected$

selected$ is an effect attribute that manages the selected state of select's options.

import { signal } from 'nanoviews/stores'
import { select, option, selected$ } from 'nanoviews'

const $selected = signal('mid')

select({
  name: 'player-pos',
  [selected$]: $selected
})(
  option({
    value: 'carry'
  })('Yatoro'),
  option({
    value: 'mid',
  })('Larl'),
  option({
    value: 'offlane'
  })('Collapse'),
  option({
    value: 'support'
  })('Mira'),
  option({
    value: 'full-support',
  })('Miposhka'),
)

Multiple select:

const $selected = signal(['mid', 'carry'])

select({
  name: 'player-pos',
  [selected$]: $selected
})(
  option({
    value: 'carry'
  })('Yatoro'),
  option({
    value: 'mid',
  })('Larl'),
  option({
    value: 'offlane'
  })('Collapse'),
  option({
    value: 'support'
  })('Mira'),
  option({
    value: 'full-support',
  })('Miposhka'),
)

files$

files$ is an effect attribute that can provide the files of file inputs.

import { signal } from 'nanoviews/stores'
import { input, files$ } from 'nanoviews'

const $files = signal([])

input({
  type: 'file',
  [files$]: $files
})

Logic methods

effect$

effect$ is a method that add effects to the component.

import { div, effect$ } from 'nanoviews'

function App() {
  effect$(() => {
    console.log('Mounted')

    return () => {
      console.log('Unmounted')
    }
  })

  return div()('Hello, Nanoviews!')
}

observe$

observe$ is a method to observe stores on component mount.

import { div, observe$ } from 'nanoviews'

const $timeout = signal(1000)

function App() {
  let intervalId

  observe$((get) => {
    intervalId = setInterval(() => {
      console.log('Tick')
    }, get($timeout))

    return () => {
      clearInterval(intervalId)
    }
  })

  return div()('Hello, Nanoviews!')
}

if$

if$ is a method that can render different childs based on the condition.

import { signal } from 'nanoviews/stores'
import { if$, div, p } from 'nanoviews'

const $show = signal(false)

if$($show)(
  () => div()('Hello, Nanoviews!')
)

const $toggle = signal(false)

if$($toggle)(
  () => p()('Toggle is true'),
  () => div()('Toggle is false')
)

switch$

switch$ is a method like if$ but with multiple conditions.

import { signal } from 'nanoviews/stores'
import { switch$, case$, default$, div, p } from 'nanoviews'

const $state = signal('loading')

switch$(state)(
  case$('loading', () => b()('Loading')),
  case$('error', () => b()('Error')),
  default$(() => 'Success')
)

for$

for$ is a method that can iterate over an array to render a list of blocks.

import { signal, record } from 'nanoviews/stores'
import { for$, ul, li } from 'nanoviews'

const $players = signal([
  { id: 0, name: 'chopper' },
  { id: 1, name: 'magixx' },
  { id: 2, name: 'zont1x' },
  { id: 3, name: 'donk' },
  { id: 4, name: 'sh1ro' },
  { id: 5, name: 'hally' }
])

ul()(
  for$($players, player => player.id)(
    $player => li()(
      record($player).name
    )
  )
)

children$

children$ is a method that creates block with optional children receiver.

import { div, children$ } from 'nanoviews'

function MyComponent(props) {
  return children$(children => div(props)(
    'My component children: ',
    children || 'empty'
  ))
}

MyComponent() // <div>My component children: empty</div>

MyComponent()('Hello, Nanoviews!') // <div>My component children: Hello, Nanoviews!</div>

slots$

slots$ is a method to receive slots and rest children.

import { main, header, footer, slot$, slots$ } from 'nanoviews'

function LayoutHeader(text) {
  return slot$(LayoutHeader, text)
}

function LayoutFooter(text) {
  return slot$(LayoutFooter, text)
}

function Layout() {
  return slots$(
    [LayoutHeader, LayoutFooter],
    (headerSlot, footerSlot, children) => main()(
      header()(headerSlot),
      children,
      footer()(footerSlot)
    )
  )
}

Layout()(
  LayoutHeader('Header content'),
  LayoutFooter('Footer content'),
  'Main content'
)

Slot's content can be anything, including functions, that can be used to render lists:

import { ul, li, b, slot$, slots$, for$ } from 'nanoviews'

function ListItem(renderItem) {
  return slot$(ListItem, renderItem)
}

function List(items) {
  return slots$(
    [ListItem],
    (listItemSlot) => ul()(
      for$(items)(
        item => li()(
          listItemSlot(item.name)
        )
      )
    )
  )
}

List([
  { id: 0, name: 'chopper' },
  { id: 1, name: 'magixx' },
  { id: 2, name: 'zont1x' },
  { id: 3, name: 'donk' },
  { id: 4, name: 'sh1ro' },
  { id: 5, name: 'hally' }
])(
  ListItem(name => b()('Player: ', name))
)

context$

context$ is a method that can provide a context to the children.

import { signal } from 'nanoviews/stores'
import { div, context$, provide, inject } from 'nanoviews'

function ThemeContext() {
  return signal('light') // default value
}

function MyComponent() {
  const $theme = inject(ThemeContext)

  return div()(
    'Current theme: ',
    $theme
  )
}

function App() {
  const $theme = signal('dark')

  return context$(
    [provide(ThemeContext, $theme)],
    () => MyComponent()
  )
}

App() // <div>Current theme: dark</div>

portal$

portal$ is a method that can render a block in a different place in the DOM.

import { div, portal$ } from 'nanoviews'

portal$(
  document.body,
  div()('I am in the body!')
)

throw$

throw$ is a helper to throw an error in expressions.

import { ul, children$, throw$ } from 'nanoviews'

function MyComponent() {
  return children$((children = throw$(new Error('Children are required'))) => ul()(children))
}
1.0.0-alpha.1

7 months ago

0.0.0-alpha.2

1 year ago

0.0.0-alpha.1

1 year ago

0.0.0-alpha.0

1 year ago