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

@innet/dom

v0.15.0-alpha.4

Published

Tools to build Web Site

Downloads

97

Readme

  @innet/dom

 

NPM downloads changelog license

Abstract

This is an innet tool, that helps to create frontend-side application.

Here you can find JSX components, state-management, portals, context, slots, routing and more.

Based on innet.

stars watchers

Install

Use innetjs to start innet-dom app development.

npx innetjs init my-app -t fe

change my-app to work folder name

Go into my-app and check README.md

Handler

Use dom handler to start an application.

Clear src folder and create index.ts inside.

import innet from 'innet'
import dom from '@innet/dom'

import app from './app'

innet(app, dom)

JSX

You can use xml-like syntax to create and append elements into the DOM. More information about JSX here.

Create app.tsx in src folder.

export default (
  <h1>
    Hello World!
  </h1>
)

Everything, that you provide as the first argument of innet function with the dom handler, will fall into the body DOM-element.

portal

If you want to put your content into another element (not body), use portal element.

For example, you can change index.html from public folder.

<!doctype html>
<html lang="en">
<head ... >
<body>
  <div id="app"></div>
  <!-- add this ^ -->
</body>
</html>

And change app.tsx

const app = document.getElementById('app')

export default (
  <portal parent={app}>
    <h1>
      Hello World!
    </h1>
  </portal>
)

You can use portal everywhere inside the app.

Change app.tsx

const app = document.getElementById('app')
const myElement = document.createElement('div')

export default (
  <portal parent={app}>
    <h1>
      Hello World!
    </h1>
    <portal parent={myElement}>
      This is content of myElement
    </portal>
  </portal>
)

myElement should contain This is content of myElement and app should contain the next code.

<h1>
  Hello World!
</h1>

State Management

Usually, state management is available only inside a component.

With innet you can fully exclude component approach, but state management still to be available.

The state management based on watch-state

To bind state and content, use State, Cache or a function as the content.

Turn back index.html and change app.tsx

import { State } from 'watch-state'

const count = new State(0)

const increase = () => {
  count.value++
}

export default (
  <>
    <h1>
      Count: {count}
    </h1>
    <button onclick={increase}>
      Click Me
    </button>
  </>
)

To bind a state and a prop use State, Cache or a function as a value of the prop.

Change app.tsx

import { State } from 'watch-state'

const darkMode = new State(false)

const handleChange = (e: Event) => {
  darkMode.value = e.target.checked
}

export default (
  <div class={() => darkMode.value ? 'dark' : 'light'}>
    <h1>
      Hello World!
    </h1>
    <label>
      <input
        type="checkbox"
        onchange={handleChange}
      />
      Dark Mode
    </label>
  </div>
)

Components

Component is a function. You can use it as JSX element.

Create Content.tsx

export const Content = () => (
  <h1>
    Hello World!
  </h1>
)

Change app.tsx

import { Content } from './Content'

export default (
  <Content />
)

props

Any component gets an argument props.
If props have not provided the argument equals undefined else you get an object that contains the props.

Change Content.tsx

export function Content ({ color }) {
  return (
    <h1 style={{ color }}>
      Hello World!
    </h1>
  )
}

Then you should use the color prop outside.

Change app.tsx

import { Content } from './Content'

export default (
  <Content color='red' />
)

Hooks

You can use hooks inside a component. Sync hooks should be used before await, async hooks should be used as the first await.

export async function Content (props1) {
  const sync1 = useSyncHook1()
  const sync2 = useSyncHook2()
  
  const [
    async1,
    async2,
  ] = await Promise.all([
    useAsyncHook1(),
    useAsyncHook2(),
  ])
  
  // other
}

useProps

You can get props with useProps hook.

import { useProps } from '@innet/jsx'

export function Content (props1) {
  const props2 = useProps()

  return (
    <h1>
      {props1 === props2 ? 'same' : 'different'}
    </h1>
  )
}

useChildren

To get children elements you can take useChildren.

Change Content.tsx

import { useChildren } from '@innet/jsx'

export function Content ({ color }) {
  const children = useChildren()

  return (
    <h1 style={{ color }}>
      {children}
    </h1>
  )
}

Then you can use the children outside.

Change app.tsx

import { Content } from './Content'

export default (
  <Content color='red'>
    Hello World!
  </Content>
)

Return

A component awaits a return:

  • string, number - render as text node
    const Test1 = () => 123
    const Test2 = () => '123'
  • null, undefined, boolean, symbol - ignore
    const Test1 = () => null
    const Test2 = () => {}
    const Test3 = () => true
    const Test4 = () => Symbol()
  • DOM Element - put in the DOM
    const Test = () => document.createElement('div')
  • JSX Fragment, array - render content
    const Test1 = () => <>content</>
    const Test2 = () => ['content']
  • JSX Element - put in the DOM
    const Test1 = () => <div>content</div>
    const Test2 = () => <br />
  • JSX Plugin - run plugin
    const Test1 = () => <portal parent={app}>content</portal>
    const Test2 = () => <slot>content</slot>
  • function - observable children
    const state = new State()
    const Test1 = () => () => state.value
    const Test2 = () => state
    const Test3 = () => <>{() => state.value}</>

Life Cycle

Each component renders only once!

There are 3 steps of life cycle:

  • render (DOM elements are not created)
  • mounted (DOM elements are created)
  • destroy (elements will be removed from the DOM)

Because of a component renders only once you can have effects right inside the component function.

import { State } from 'watch-state'

function Content () {
  const state = new State()

  fetch('...')
    .then(e => e.json())
    .then(data => {
      state.value = data.text
    })

  return (
    <div>
      {state}
    </div>
  )
}

Async Component

Innet supports async components, you can simplify previous code.

async function Content () {
  const { text } = await fetch('...').then(e => e.json())

  return <div>{text}</div>
}

innetjs helps to make code splitting.

async function Content () {
  const { Test } = await import('./Test')

  return (
    <div>
      <Test />
    </div>
  )
}

Test.tsx

export const Test = () => (
  <div>
    Test success!
  </div>
)

While it's loading nothing can be shown. If you want to show something, use Generic Async Component.

Generic Async Component

Just add a star and use yield instead of return

async function * Content () {
  yield 'Loading...'

  const { text } = await fetch('...').then(e => e.json())

  yield <div>{text}</div>
}

Generic Component

It can be useful when you want to do something after a content deployed.

function * Content () {
  yield (
    <div id='test'>
      Hello World!
    </div>
  )

  colsole.log(document.getElementById('test'))
}

You can use queueMicrotask instead of a generic component, but there are a small difference:

queueMicrotask runs after whole content is available and generic component runs right after the content of the component rendered.

function * A () {
  queueMicrotask(() => {
    console.log(
      'queueMicrotask A',
      document.getElementById('a'),
      document.getElementById('b'),
    )
  })

  yield <span id='a' />

  console.log(
    'generic A',
    document.getElementById('a'),
    document.getElementById('b'),
  )
}

function * B () {
  queueMicrotask(() => {
    console.log(
      'queueMicrotask B',
      document.getElementById('a'),
      document.getElementById('b'),
    )
  })

  yield <span id='b' />

  console.log(
    'generic B',
    document.getElementById('a'),
    document.getElementById('b'),
  )
}

function Content () {
  return (
    <>
      <A />
      <B />
    </>
  )
}

You get the next output:

generic A <span id="a"></span> null
generic B <span id="a"></span> <span id="b"></span>
queueMicrotask A <span id="a"></span> <span id="b"></span>
queueMicrotask B <span id="a"></span> <span id="b"></span>

Ref

Ref helps to get an HTML element.

import { Ref } from '@innet/dom'

function * Content () {
  const wrapper = new Ref<HTMLDivElement>()
  
  yield (
    <div ref={wrapper}>
      Hello World!
    </div>
  )

  colsole.log(wrapper.value)
}

onDestroy

You can subscribe on destroy of a component by onDestroy from watch-state

Change Content.tsx

import { State, onDestroy } from 'watch-state'

export function Content() {
  const count = new State(0)
  // create a state

  const timer = setInterval(() => {
    count.value++
  }, 1000)
  // increase the state each second

  onDestroy(() => clearInterval(timer))
  // stop timer on destroy

  return () => count.value
  // return observable value
}

And change app.tsx

import { State } from 'watch-state'
import { Content } from './Content'

const show = new State(true)

const handleChange = (e: Event) => {
  show.value = e.target.checked
}

export default (
  <>
    <show when={show}>
      <Content />
    </show>
    <input
      type="checkbox"
      checked
      onchange={handleChange}
    />
  </>
)

Context

You can pass a value from a parent element through any children to the place you need.

Change Content.tsx

import { Context, useContext } from '@innet/dom'

export const color = new Context('blue')

export function Content () {
  const currentColor = useContext(color)

  return (
    <h1 style={{ color: currentColor }}>
      {children}
    </h1>
  )
}

And change app.tsx

import { Content, color } from './Content'

export default (
  <>
    <Content>
      Without context
    </Content>
    <context for={color} set='red'>
      <Content>
        With context
      </Content>
    </context>
  </>
)

show

You can use show element to show/hide content by state.

import { State } from 'watch-state'

const show = new State(true)

export default (
  <show when={show}>
    <button
      onclick={() => {
        show.value = false
      }}>
      Click Me
    </button>
  </show>
)

when can be: State | Cache | () => any | any

hide

You can use hide element to show/hide content by state.

import { State } from 'watch-state'

const isHidden = new State(false)

export default (
  <hide when={isHidden}>
    <button
      onclick={() => {
        isHidden.value = true
      }}>
      Click Me
    </button>
  </hide>
)

when can be: State | Cache | () => any | any

switch

You can use switch element to show a content by string state.

import { State } from 'watch-state'

const str = new State('')

const case1 = () => {
  str.value = 'case1'
}

const case2 = () => {
  str.value = 'case2'
}

export default (
  <switch of={str}>
    <slot name='case1'>
      Case 1
      <button
        onclick={case2}>
        Next
      </button>
    </slot>
    <slot name='case2'>
      Case 2
    </slot>
    Default content
    <button
      onclick={case1}>
      Next
    </button>
  </switch>
)

of can be: State<string | number> | Cache<string | number> | () => (string | number) | string | number

map

You can use map method of an array to put view on data.

const names = ['Mike', 'Alex', 'Dan']

export default (
  <ul>
    {names.map(name => (
      <li>
        {name}
      </li>
    ))}
  </ul>
)

It's ok for static data, but if you use a state, it's better to use map element.

import { State } from 'watch-state'
import { useMapValue, useMapIndex } from '@innet/dom'

const names = new State(['Mike', 'Alex', 'Dan'])

function User () {
  const name = useMapValue()
  const index = useMapIndex()
  
  return (
    <li>
      #{index}:
      {name}
    </li>
  )
}

export default (
  <ul>
    <map of={names}>
      <User />
    </map>
  </ul>
)

Use key property to improve DOM changes when you use an array of objects with some uniq field, like id.

import { State } from 'watch-state'

const names = new State([
  { id: 1, text: 'test1' },
  { id: 2, text: 'test2' },
  { id: 3, text: 'test3' },
])

function User () {
  const name = useMapValue()
  const index = useMapIndex()

  return (
    <li>
      #{index}:
      {name}
    </li>
  )
}

export default (
  <ul>
    <map of={names} key='id'>
      <User />
    </map>
  </ul>
)

slots

You can use slots to provide a couple of named child elements.

import { useChildren } from '@innet/jsx'

export const Content = () => (
  <slots from={useChildren()}>
    <div class='header'>
      <slot name='header'>
        default header
      </slot>
    </div>
    <div class='content'>
      <slot>
        default content
      </slot>
    </div>
    <div class='footer'>
      <slot name='footer'>
        default footer
      </slot>
    </div>
  </slots>
)
export default (
  <Content>
    <slot>Custom content</slot>
    <slot name='header'>
      Custom header
    </slot>
  </Content>
)

You get Custom header, Custom content and default footer

useSlots

useSlots is a way to get slots.

import { useSlots } from '@innet/dom'

export const Content = () => {
  const {
    '': content,
    header,
    footer
  } = useSlots()

  return (
    <>
      <show when={header}>
        <div class='header'>
          {header}
        </div>
      </show>
      <div class='content'>
        {content}
      </div>
      <show when={footer}>
        <div class='footer'>
          {footer}
        </div>
      </show>
    </>
  )
}

Any slots without name or with name equals empty string and any content outside slots collect into empty string slot.

export default (
  <Content>
    <slot name='header'>
      Custom header
    </slot>
    Custom content
  </Content>
)

You can use a couple of slots with the same name.

export default (
  <Content>
    <slot name='header'>
      Custom header1 <br />
    </slot>
    <slot name='header'>
      Custom header2
    </slot>
    Custom content
  </Content>
)

router

You can render content by url.

export const Content = () => (
  <router>
    <slot name='/'>
      Home page
    </slot>
    <slot name='settings'>
      Settings page
    </slot>
    Not Found
  </router>
)

There are strong matching by default, so you can see

/ - Home page
/settings - Settings page
/settings/test - Not Found
/any-other - Not Found

If you want to show Settings page on /settings/test, use ish prop on router element

export const Content = () => (
  <router ish>
    <slot name='/'>
      Home page
    </slot>
    <slot name='settings'>
      Settings page
    </slot>
    Not Found
  </router>
)

When you use a router, that is inside a slot of another router, the route checks the next peace of url path.

export const Content = () => (
  <router ish>
    <slot name='/'>
      Home page
    </slot>
    <slot name='settings'>
      <router>
        <slot name='main'>
          Main Settings
        </slot>
        <slot name='user'>
          User Settings
        </slot>
        Settings
      </router>
    </slot>
    Not Found
  </router>
)

/ - Home page
/settings - Settings
/settings/main - Main Settings
/settings/user - User Settings
/settings/any-other - Settings
/any-other - Not Found

You can use search prop to make router binds on query search params

export const Content = () => (
  <router search='modal'>
    <slot name='login'>
      Login
    </slot>
    <slot name='logout'>
      Logout
    </slot>
  </router>
)

?modal=login - Login
/settings?modal=logout - Logout
/settings?user=1&modal=logout - Logout
/any-other?any-params&modal=any-other - render nothing

useRoute

You can handle dynamic routes by useRoute.

const Test = () => {
  const route = useRoute()

  return () => route.value
}

export const Content = () => (
  <router ish>
    <slot name='/'>
      Home page: <Test />
    </slot>
    <slot name='settings'>
      Settings: <Test />
    </slot>
    Other: <Test />
  </router>
)

/ - Home page: /
/settings - Settings: /
/settings/test - Settings: test
/any-other - Other: any-other

a

The tag a has a couple of features.

rel="noopener noreferrer nofollow" and target="_blank" are default for external links.

href

If href starts from /, ? or # then the Link will use History API.

export const Content = () => (
  <div>
    <a href="/">home</a>
    <a href="/test">test</a>
    <a href="/home">unknown</a>
    <a href="?modal=test">modal</a>
    <div>
      <router>
        <slot name='/'>
          Home Page
        </slot>
        <slot name='test'>
          Test Page
        </slot>
        404
      </router>
      <router search='modal'>
        <slot name='test'>
          Test Modal
        </slot>
      </router>
    </div>
  </div>
)

replace

By default, it pushes to history, but you may use replace to replace current history state.

export const Content = () => (
  <a replace href="/">
    home
  </a>
)

class

You can add root or active link class

const classes = {
  root: 'link',
  active: 'active',
}

export const Content = () => (
  <div>
    <a
      href="/"
      class='only-root'>
      home
    </a>
    <a
      href="/test"
      class={classes}>
      test
    </a>
  </div>
)

You can use all features from html-classes for the class prop.

const classes = {
  root: ['link1', 'link2', () => 'dynamic-class'],
  active: { active: true },
}

export const Content = () => (
  <div>
    <a
      href="/"
      class={() => 'dynamic-root'}>
      home
    </a>
    <a
      href="/test"
      class={classes}>
      test
    </a>
  </div>
)

exact

By default, active class appends if URL starts with href prop value, but use exact to compare exactly.

const classes = { root: 'link', active: 'active' }

export const Content = () => (
  <div>
    <a
      href="/"
      exact
      classes={classes}>
      home
    </a>
    <a
      href="/test"
      classes={classes}>
      test
    </a>
  </div>
)

scroll

You can use smooth scroll

body, html {
  scroll-behavior: smooth;
}

The property of scroll says should we scroll on click and how.

by default equals before

export const Content = () => (
  <div>
    <a href="/" scroll='before'>
      home
    </a>
    <a href="/test" scroll='after'>
      test
    </a>
    <a href="?modal" scroll='none'>
      test
    </a>
  </div>
)

scrollTo

If you want to scroll the page to custom position (by default it's up of the page) use scrollTo

export const Content = () => (
  <div>
    <a href="/" scrollTo={100}>
      home
    </a>
    <a href="/test" scrollTo='#root'>
      test
    </a>
  </div>
)

Use a string to scroll under an element relates to the CSS selector you provide or use -1 to stop scrolling.

delay

You can show and hide elements with delay.

export function Content () {
  return (
    <delay show={1000}>
      Works
      <delay show={1000}>
        fine!
      </delay>
    </delay>
  )
}

useHidden

You can react on removing of elements

Change Content.tsx

import { useHidden } from '@innet/dom'

export function Content () {
  const hidden = useHidden()

  return () => hidden.value ? 'hidden' : 'shown'
}

And change app.tsx

import { State } from 'watch-state'

const show = new State(true)

const handleClick = () => {
  show.value = false
}

export default () => show.value && (
  <delay hide={1000}>
    <Content />
    <button
      onclick={handleClick}>
      Hide
    </button>
  </delay>
)

ref

You can use ref to get the hidden state.

Change Content.tsx

export function Content () {
  const hidden = new Ref()

  return (
    <delay ref={hidden} hide={1000}>
      {() => hidden.value.value ? 'hidden' : 'shown'}
    </delay>
  )
}

And change app.tsx

import { State } from 'watch-state'

const show = new State(true)

const handleClick = () => {
  show.value = false
}

export default () => show.value && (
  <>
    <Content />
    <button
      onclick={handleClick}>
      Hide
    </button>
  </>
)

useParent

You can get parent HTML element inside a component

import { getParent } from '@innet/dom'

export function Content () {
  console.log(useParent())
}

style

You can style components with style function. The function returns useStyle hook. Use this hook inside a component to get html-classes features on class prop.

import { style, Style } from '@innet/dom'

import styles from './Content.scss'
// or you can use an object like
// { root: '...', header: '...', content: '...' }

const useContentStyles = style(styles)

export interface ContentProps extends Style {}

export function Content (props: ContentProps) {
  const styles = useContentStyles()

  return (
    <div class={() => styles.root}>
      <header class={() => styles.header}>
        header
      </header>
      <main class={() => styles.content}>
        content
      </main>
    </div>
  )
}

Then you can use class prop to define classes.

import { State } from 'watch-state'

const show = new State(true)

const handleClick = () => {
  show.value = !show.value
}

export default (
  <>
    <Content
      class={{
        root: 'root',
        header: ['header', 'another-class'],
        content: [
          'content',
          () => show.value && 'show'
        ],
      }}
    />
    <button
      onclick={handleClick}>
      Hide
    </button>
  </>
)

Issues

If you find a bug or have a suggestion, please file an issue on GitHub.

issues