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
- Host: GitHub
- URL: https://github.com/d8corp/innet-dom
- Owner: d8corp
- License: mit
- Created: 2022-03-24T20:14:46.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2023-06-25T10:56:06.000Z (about 3 years ago)
- Last Synced: 2025-03-14T11:03:53.221Z (over 1 year ago)
- Language: JavaScript
- Size: 2.59 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# @innet/dom
[](https://www.npmjs.com/package/@innet/dom)
[](https://www.npmtrends.com/@innet/dom)
[](https://changelogs.xyz/@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).
[](https://github.com/d8corp/innet-dom/stargazers)
[](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 (
-
{name}
{names.map(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 = () => (
)
```
### 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 = () => (
)
```
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 = () => (
)
```
### 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 = () => (
)
```
### 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 = () => (
)
```
### 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 = () => (
)
```
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.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).
[](https://github.com/d8corp/innet-dom/issues)