npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nanoviews

v0.0.0-alpha.2

Published

A small Direct DOM library for creating user interfaces.

Downloads

16

Readme

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 2.71 and 4.77 kB (minified and brotlied). Zero 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 { atom } from 'nanostores'
import { div, a, img, h1, button, p, render } from 'nanoviews'

function App() {
  const $counter = atom(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')
  )
}

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

Install

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

Reactivity

Nanoviews is designed to be used with Nano Stores for reactivity. But Nano Stores is not a direct dependency of Nanoviews, so you can use any other reactive library that implements store interface.

import { atom } from 'nanostores'
import { fragment, input, p } from 'nanoviews'

const $text = atom('')

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 { atom } from 'nanostores'
import { ul, li } from 'nanoviews'

const $boolean = atom(true)

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

Special methods

text$

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

import { atom } from 'nanostores'
import { text$, effect$ } from 'nanoviews'

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

    return () => clearInterval(id)
  }

  return effect$(
    effect,
    text$($tick)
  )
}

fragment

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

import { atom } from 'nanostores'
import { fragment, effect$ } from 'nanoviews'

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

    return () => clearInterval(id)
  }

  return effect$(
    effect,
    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>'
)

attachShadow

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

import { div, attachShadow } from 'nanoviews'

attachShadow(
  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 { atom } from 'nanostores'
import { div, ref$ } from 'nanoviews'

const $ref = atom(null)

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

style$

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

import { atom } from 'nanostores'
import { button, style$ } from 'nanoviews'

const $color = atom('white')

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

classList$

classList$ is an effect attribute that manages class names of the element.

import { button, classList$, classIf$, classGet$ } from 'nanoviews'
import * as styles from './styles.css'

function MyButton({
  class: className,
  theme = 'primary',
  rounded = false
}) {
  return button({
    [classList$]: [className, 'myButton', classIf$(styles.rounded, rounded), classGet$(styles, theme)]
  })
}

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 { atom } from 'nanostores'
import { textarea, value$ } from 'nanoviews'

const $review = atom('')

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 { atom } from 'nanostores'
import { input, checked$, Indeterminate } from 'nanoviews'

const $checked = atom(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 { atom } from 'nanostores'
import { select, option, selected$ } from 'nanoviews'

const $selected = atom('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 = atom(['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 { atom } from 'nanostores'
import { input, files$ } from 'nanoviews'

const $files = atom([])

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

Logic methods

effect$

effect$ is a method that add effects to the element block.

import { div, effect$ } from 'nanoviews'

function App() {
  const effect = (element) => {
    console.log('Mounted', element.outerHTML) // Mounted <div>Hello, Nanoviews!</div>

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

  return effect$(
    effect, // or array of effects
    div()('Hello, Nanoviews!')
  )
}

decide$

decide$ is a method that can switch between different childs.

import { atom } from 'nanostores'
import { decide$, div, p } from 'nanoviews'

const $show = atom(false)

decide$($show, (show) => {
  if (show) {
    return div()('Hello, Nanoviews!')
  }
})

const $toggle = atom(false)

decide$($toggle, toggle => (
  toggle ? p()('Toggle is true') : div()('Toggle is false')
))

for$

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

import { atom } from 'nanostores'
import { for$, ul, li } from 'nanoviews'

const $players = atom([
  { 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()(player))
)

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 that should be used with children$ to receive slots.

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

const LayoutHeaderSlot = createSlot()
const LayoutFooterSlot = createSlot()

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

Layout()(
  LayoutHeaderSlot('Header content'),
  LayoutFooterSlot('Footer content'),
  'Main content'
)

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

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

const ListItemSlot = createSlot()

function List(items) {
  return children$(
    slots$(
      [ListItemSlot],
      (listItemSlot) => ul()(
        for$(items, (item) => item.id, (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' }
])(
  ListItemSlot((name) => b()('Player: ', name))
)

context$

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

import { atom } from 'nanostores'
import { div, context$, createContext } from 'nanoviews'

const [ThemeContext, getTheme] = createContext(atom('light')) // default value

function MyComponent() {
  const theme = getTheme()

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

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

  return context$(
    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!')
)

await$

await$ is a method that can handle and render promises.

import { atom } from 'nanostores'
import { i, b, await$, pending$, then$, catch$ } from 'nanoviews'

const $promise = atom(Promise.resolve('Hello, Nanoviews!'))

await$($promise)(
  pending$(() => i()('Loading...')),
  then$((value) => b()(value)),
  catch$((error) => String(error))
)

forAwait$

forAwait$ is a method that can handle and render async iterables.

import { ul, li, forAwait$, pending$, each$, then$, catch$ } from 'nanoviews'

ul()(
  forAwait$(fetchProducts())(
    pending$(() => li()('Loading...')),
    each$((product) => ProductView(product)),
    then$((count) => li()('Loaded products count: ', count)),
    catch$((error) => li()('Error: ', error))
  )
)

Also you can render list in reversed order:

import { main, div, forAwait$, pending$, each$, then$, catch$ } from 'nanoviews'

main()(
  forAwait$(fetchNewsFeed(), true)(
    pending$(() => div()('Loading...')),
    each$((product) => PostView(product)),
    then$((count) => div()('Loaded products count: ', count)),
    catch$((error) => div()('Error: ', error))
  )
)

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))
}