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

https://github.com/d8corp/innet-dom

Tools to build Web Site
https://github.com/d8corp/innet-dom

Last synced: about 1 year ago
JSON representation

Tools to build Web Site

Awesome Lists containing this project

README

          


InnetJs logo by Mikhail Lysikov

#   @innet/dom

 

[![NPM](https://img.shields.io/npm/v/@innet/dom.svg)](https://www.npmjs.com/package/@innet/dom)
[![downloads](https://img.shields.io/npm/dm/@innet/dom.svg)](https://www.npmtrends.com/@innet/dom)
[![changelog](https://img.shields.io/badge/Changelog-⋮-brightgreen)](https://changelogs.xyz/@innet/dom)
[![license](https://img.shields.io/npm/l/@innet/dom)](https://github.com/d8corp/innet-dom/blob/main/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](https://www.npmjs.com/package/innet).

[![stars](https://img.shields.io/github/stars/d8corp/innet-dom?style=social)](https://github.com/d8corp/innet-dom/stargazers)
[![watchers](https://img.shields.io/github/watchers/d8corp/innet-dom?style=social)](https://github.com/d8corp/innet-dom/watchers)

## Install
Use [innetjs](https://www.npmjs.com/package/innetjs) to start `innet-dom` app development.

```shell
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.
```typescript
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](https://www.typescriptlang.org/docs/handbook/jsx.html).

Create `app.tsx` in `src` folder.
```typescript jsx
export default (


Hello World!


)
```

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.
```html


```

And change `app.tsx`

```typescript jsx
const app = document.getElementById('app')

export default (


Hello World!



)
```

You can use `portal` everywhere inside the app.

Change `app.tsx`
```typescript jsx
const app = document.getElementById('app')
const myElement = document.createElement('div')

export default (


Hello World!



This is content of myElement


)
```

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


Hello World!


```

## 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](https://github.com/d8corp/watch-state)

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

Turn back `index.html` and change `app.tsx`
```typescript jsx
import { State } from 'watch-state'

const count = new State(0)

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

export default (
<>


Count: {count}



Click Me

>
)
```

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

Change `app.tsx`
```typescript jsx
import { State } from 'watch-state'

const darkMode = new State(false)

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

export default (

darkMode.value ? 'dark' : 'light'}>


Hello World!




Dark Mode


)
```

## Components

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

Create `Content.tsx`
```typescript jsx
export const Content = () => (


Hello World!


)
```

Change `app.tsx`
```typescript jsx
import { Content } from './Content'

export default (

)
```

#### 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`
```typescript jsx
export function Content ({ color }) {
return (


Hello World!


)
}
```

Then you should use the `color` prop outside.

Change `app.tsx`
```typescript jsx
import { Content } from './Content'

export default (

)
```

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

```typescript jsx
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.

```typescript jsx
import { useProps } from '@innet/jsx'

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

return (


{props1 === props2 ? 'same' : 'different'}


)
}
```

#### useChildren
To get children elements you can take `useChildren`.

Change `Content.tsx`
```typescript jsx
import { useChildren } from '@innet/jsx'

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

return (


{children}


)
}
```

Then you can use the children outside.

Change `app.tsx`
```typescript jsx
import { Content } from './Content'

export default (

Hello World!

)
```

### Return

A component awaits a return:
- `string`, `number` - render as text node
```typescript jsx
const Test1 = () => 123
const Test2 = () => '123'
```
- `null`, `undefined`, `boolean`, `symbol` - ignore
```typescript jsx
const Test1 = () => null
const Test2 = () => {}
const Test3 = () => true
const Test4 = () => Symbol()
```
- DOM Element - put in the DOM
```typescript jsx
const Test = () => document.createElement('div')
```
- JSX Fragment, `array` - render content
```typescript jsx
const Test1 = () => <>content>
const Test2 = () => ['content']
```
- JSX Element - put in the DOM
```typescript jsx
const Test1 = () =>

content

const Test2 = () =>

```
- JSX Plugin - run plugin
```typescript jsx
const Test1 = () => content
const Test2 = () => content
```
- function - observable children
```typescript jsx
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.
```typescript jsx
import { State } from 'watch-state'

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

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

return (


{state}

)
}
```

### Async Component

Innet supports async components, you can simplify previous code.
```typescript jsx
async function Content () {
const { text } = await fetch('...').then(e => e.json())

return

{text}

}
```

[innetjs](https://www.npmjs.com/package/innetjs) helps to make code splitting.
```typescript jsx
async function Content () {
const { Test } = await import('./Test')

return (




)
}
```

`Test.tsx`
```typescript jsx
export const Test = () => (


Test success!

)
```

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`
```typescript jsx
async function * Content () {
yield 'Loading...'

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

yield

{text}

}
```

### Generic Component

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

```typescript jsx
function * Content () {
yield (


Hello World!

)

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.
```typescript jsx
function * A () {
queueMicrotask(() => {
console.log(
'queueMicrotask A',
document.getElementById('a'),
document.getElementById('b'),
)
})

yield

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

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

yield

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

function Content () {
return (
<>


>
)
}
```

You get the next output:

```
generic A null
generic B
queueMicrotask A
queueMicrotask B
```

## Ref

`Ref` helps to get an HTML element.
```typescript jsx
import { Ref } from '@innet/dom'

function * Content () {
const wrapper = new Ref()

yield (


Hello World!

)

colsole.log(wrapper.value)
}
```

## onDestroy

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

Change `Content.tsx`

```typescript jsx
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`
```typescript jsx
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 (
<>




>
)
```

## Context

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

Change `Content.tsx`
```typescript jsx
import { Context, useContext } from '@innet/dom'

export const color = new Context('blue')

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

return (


{children}


)
}
```

And change `app.tsx`
```typescript jsx
import { Content, color } from './Content'

export default (
<>

Without context



With context


>
)
```

## show

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

```typescript jsx
import { State } from 'watch-state'

const show = new State(true)

export default (

{
show.value = false
}}>
Click Me


)
```

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

## hide

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

```typescript jsx
import { State } from 'watch-state'

const isHidden = new State(false)

export default (

{
isHidden.value = true
}}>
Click Me


)
```

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

## switch

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

```typescript jsx
import { State } from 'watch-state'

const str = new State('')

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

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

export default (


Case 1

Next



Case 2

Default content

Next


)
```

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

## map

You can use `map` method of an array to put view on data.
```typescript jsx
const names = ['Mike', 'Alex', 'Dan']

export default (


    {names.map(name => (

  • {name}

  • ))}

)
```

It's ok for static data, but if you use a state, it's better to use `map` element.
```typescript jsx
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 (


  • #{index}:
    {name}

  • )
    }

    export default (






    )
    ```

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

    ```typescript jsx
    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 (


  • #{index}:
    {name}

  • )
    }

    export default (






    )
    ```

    ## slots

    You can use slots to provide a couple of named child elements.
    ```typescript jsx
    import { useChildren } from '@innet/jsx'

    export const Content = () => (



    default header




    default content




    )
    ```

    ```typescript jsx
    export default (

    Custom content

    Custom header


    )
    ```

    You get `Custom header`, `Custom content` and `default footer`

    ## useSlots

    `useSlots` is a way to get slots.
    ```typescript jsx
    import { useSlots } from '@innet/dom'

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

    return (
    <>


    {header}



    {content}




    >
    )
    }
    ```

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

    ```typescript jsx
    export default (


    Custom header

    Custom content

    )
    ```

    You can use a couple of slots with the same name.
    ```typescript jsx
    export default (


    Custom header1



    Custom header2

    Custom content

    )
    ```

    ## router
    You can render content by url.

    ```typescript jsx
    export const Content = () => (


    Home page


    Settings page

    Not Found

    )
    ```

    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
    ```typescript jsx
    export const Content = () => (


    Home page


    Settings page

    Not Found

    )
    ```

    When you use a router, that is inside a slot of another router, the route checks the next peace of url path.
    ```typescript jsx
    export const Content = () => (


    Home page




    Main Settings


    User Settings

    Settings


    Not Found

    )
    ```

    `/` - 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
    ```typescript jsx
    export const Content = () => (


    Login


    Logout


    )
    ```

    `?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`.
    ```typescript jsx
    const Test = () => {
    const route = useRoute()

    return () => route.value
    }

    export const Content = () => (


    Home page:


    Settings:

    Other:

    )
    ```

    `/` - 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](https://developer.mozilla.org/en-US/docs/Web/API/History_API).

    ```typescript jsx
    export const Content = () => (


    home
    test
    unknown
    modal



    Home Page


    Test Page

    404



    Test Modal




    )
    ```

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

    ```typescript jsx
    export const Content = () => (

    home

    )
    ```

    ### class
    You can add root or active link class

    ```typescript jsx
    const classes = {
    root: 'link',
    active: 'active',
    }

    export const Content = () => (



    home


    test


    )
    ```

    You can use all features from [html-classes](https://www.npmjs.com/package/html-classes) for the `class` prop.

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

    export const Content = () => (


    'dynamic-root'}>
    home


    test


    )
    ```

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

    ```typescript jsx
    const classes = { root: 'link', active: 'active' }

    export const Content = () => (



    home


    test


    )
    ```

    ### scroll
    You can use smooth scroll
    ```css
    body, html {
    scroll-behavior: smooth;
    }
    ```
    The property of `scroll` says should we scroll on click and how.

    > by default equals `before`

    ```typescript jsx
    export const Content = () => (



    home


    test


    test


    )
    ```

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

    ```typescript jsx
    export const Content = () => (



    home


    test


    )
    ```

    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.

    ```typescript jsx
    export function Content () {
    return (

    Works

    fine!


    )
    }
    ```

    ### useHidden
    You can react on removing of elements

    Change `Content.tsx`
    ```typescript jsx
    import { useHidden } from '@innet/dom'

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

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

    And change `app.tsx`
    ```typescript jsx
    import { State } from 'watch-state'

    const show = new State(true)

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

    export default () => show.value && (



    Hide


    )
    ```

    ### ref
    You can use `ref` to get the hidden state.

    Change `Content.tsx`
    ```typescript jsx
    export function Content () {
    const hidden = new Ref()

    return (

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

    )
    }
    ```

    And change `app.tsx`
    ```typescript jsx
    import { State } from 'watch-state'

    const show = new State(true)

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

    export default () => show.value && (
    <>


    Hide

    >
    )
    ```

    ## useParent

    You can get parent HTML element inside a component

    ```typescript jsx
    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](https://www.npmjs.com/package/html-classes) features on `class` prop.

    ```typescript jsx
    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 (

    styles.root}>
    styles.header}>
    header

    styles.content}>
    content


    )
    }
    ```

    Then you can use `class` prop to define classes.

    ```typescript jsx
    import { State } from 'watch-state'

    const show = new State(true)

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

    export default (
    <>
    show.value && 'show'
    ],
    }}
    />

    Hide

    >
    )
    ```

    ## Issues
    If you find a bug or have a suggestion, please file an issue on [GitHub](https://github.com/d8corp/innet-dom/issues).

    [![issues](https://img.shields.io/github/issues-raw/d8corp/innet-dom)](https://github.com/d8corp/innet-dom/issues)