Minimal reactive component view library.
# app
# view source
example/app.tsx
/* eslint-disable @typescript-eslint/ban-types */
/** @jsxImportSource minimal-view */
// import { Class } from 'everyday-types'
import { web, view, render, element, on, queue, enableDebug } from 'minimal-view'
// enableDebug(5000)
const drawFns = new Set<any>()
function drawSchedule(fn: any) {
drawFns.add(fn)
anim()
}
function drawRemoveSchedule(fn: any) {
drawFns.delete(fn)
}
function animCall(fn: any) { fn() }
const anim = queue.raf(function animRaf() {
if (drawFns.size) {
anim()
const fns = [minimal-view.drawFns]
drawFns.clear()
fns.forEach(animCall)
}
})
const Button = web('btn', view(
class props {
state!: 'active' | 'inactive'
isActive!: boolean
onToggle!: () => void
children?: JSX.Element
}, class local {
more?: string
}, ({ $, fx }) => {
$.css = /*css*/`
&([state=active]) {
button {
background: teal;
}
}
&([state=inactive]) {
button {
background: grey;
}
}`
fx(({ onToggle, isActive, children }) => {
// console.log('fire', onToggle, isActive)
$.view = <>
<button onclick={onToggle}>
{children}
{isActive ? 'off' : 'on'}
</button>
</>
})
}))
const Wave = web('wave', view(
class props {
width = 200
height = 100
}, class local {
canvas?: HTMLCanvasElement
ctx?: CanvasRenderingContext2D | null
running = true
}, (({ $, fx, fn, refs }) => {
$.css = /*css*/`
canvas {
background: #000;
display: flex;
image-rendering: pixelated;
}`
const pr = window.devicePixelRatio
let t = 0
let stop = () => { }
const draw = fn(({ canvas, ctx, width, height }) => {
stop = () => drawRemoveSchedule(drawTick)
function drawTick() {
drawSchedule(drawTick)
// draw()
ctx.imageSmoothingEnabled = false
ctx.fillStyle = '#333'
ctx.drawImage(canvas, -1, 0, width, height)
ctx.fillRect(canvas.width - pr, 0, pr, height)
ctx.fillStyle = '#aaa'
t += 0.025
ctx.fillRect(width - pr, (height - pr) * (Math.sin(t) * 0.5 + 0.5), pr, pr)
}
return drawTick
})
fx(({ canvas }) => {
$.ctx = canvas.getContext('2d')
})
fx.raf(({ ctx, width, height }) => {
ctx.fillStyle = '#333'
ctx.fillRect(0, 0, width, height)
draw()
})
fx(({ width, height }) => {
$.view = <canvas ref={refs.canvas} width={width} height={height}
onclick={() => {
if ($.running) {
stop()
$.running = false
} else {
draw()
$.running = true
}
}}
/>
})
})))
const App = web('app', view(
class props {
numberOfItems = 1
}, class local {
host = element
isActive = true
items: any[] = []
itemsView: JSX.Element = false
scale = 1
}, ({ $, fx }) => {
$.css = /*css*/`
& {
display: flex;
flex-flow: row wrap;
background: brown;
padding: 10px;
gap: 10px;
transition: transform 100ms ease-out;
}
canvas {
background: #000;
display: block;
}`
fx.raf(({ host, scale }) => {
host.style.transform = `scale(${scale})`
})
fx(() =>
on(window, 'wheel').not.passive.prevent.stop.raf((ev) => {
$.scale = Math.max(0.01, $.scale + ev.deltaY * 0.001)
})
)
fx(({ numberOfItems }) => {
$.itemsView = Array.from({ length: numberOfItems }, () =>
<Wave width={200} height={100} />
)
})
fx(({ itemsView }) => {
$.view = <>
{itemsView}
</>
})
}))
// let isActive = true
render(<>
<style>{/*css*/`
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
`}</style>
<App numberOfItems={1} />
</>, document.body)
# new
# view source
example/new.tsx
/** @jsxImportSource minimal-view */
import { view } from 'minimal-view'
const V = view('x',
class props {
yo = 123
},
class local { },
function actions({ $, fns, fn }) {
return fns(new class actions {
foo = () => {
}
})
},
function effects({ $, fx, deps, refs }) {
fx(() => {
$.foo()
$.view = 'hey'
})
}
)
# reactive
# view source
example/reactive.ts
import { reactive } from 'minimal-view/reactive'
const X = reactive('x',
class props {
foo!: string
},
class local {
a = 1
b = 2
},
function actions({ $, fns, fn }) {
return fns(new class actions {
add = fn(({ a, b }) => () => {
return a + b
})
})
},
function effects({ $, fx }) {
}
)
const x = X({ foo: 'yes' })
# web
# view source
example/web.tsx
/** @jsxImportSource minimal-view */
// import { Class } from 'everyday-types'
import { effect } from 'minimal-reactive'
import { view, render } from 'minimal-view'
// let i = 0
effect.debug = (changed, fn, minimal-view.stackErr) => {
console.groupCollapsed(minimal-view.changed)
console.log((fn as any).source)
stackErr.forEach(err => console.warn(err))
console.groupEnd()
// if (i++ > 100) effect.stop()
}
// let inc = 0
const Button = view(class props {
isActive!: boolean
onToggle!: () => void
}, class local {
more?: string
}, ({ $, fx }) => {
fx(({ onToggle, isActive }) => {
// console.log('fire', onToggle, isActive)
$.view =
<button onclick={onToggle}>{
isActive ? 'off' : 'on'
}</button>
})
// return ({ isActive, onToggle }) => {
// if (isActive) onToggle()
// }
})
// setTimeout(() => {
// effect.stop()
// }, 50)
// Button(props: { color: string }) {
// hook.$ ??= use(props, class local {
// btn?: HTMLButtonElement
// view: JSX.Element
// })
// const $ = hook.$
// let someScoped = 0
// // const id = inc++
// $.effect(({ color }) => {
// // console.log('COLOR IS', color)
// // console.log('I AM', id)
// $.view = `${color} scoped: ${someScoped++}`
// // queueMicrotask(() => {
// // })
// return () => {
// console.warn('color disposed')
// }
// })
// return <button ref={$.refs.btn}>{$.view}</button>
// }
// function App() {
// const $ = use(class local {
// color = 'pink'
// text = 'hello'
// counter = 0
// late?: boolean
// some?: Stateful<{
// prop?: boolean,
// deep?: Stateful<{ other?: number }>
// }>
// status?: JSX.Element
// })
// .reduce(
// ({ counter }) => ({
// total: counter
// }))
// .reduce(
// ({ color, text }) => ({
// greeting: color + text
// }))
// .pick(
// ({ some }) => ({
// prop: some.prop,
// deepOther: some.deep.other
// }))
// $.effect(({ counter, deepOther }) => {
// if (counter % 2 === 1 + deepOther) {
// $.late = true
// $.color = ['red', 'green', 'blue'][Math.random() * 3 | 0]
// } else {
// $.late = false
// }
// return () => {
// console.log('disposed?', counter)
// }
// })
// $.effect(({ greeting, late }) => {
// $.status = greeting + `${late ? <h1>LATE</h1> : 'early'}`
// })
// $.effect(({ text, color, counter }) => {
// $.view =
// <div onclick={() => {
// $.text = 'yo'
// $.counter++
// }}>
// hello world {color} {text} {counter}
// <Button color={color} />
// </div>
// })
// return <>
// {$.view}
// {$.status}
// </>
// }
const App = view(
class props {
skata = 1
}, class local {
isActive = true
items: any[] = []
itemsView: JSX.Element = false
}, ({ $, fn, fx }) => {
const onToggle = fn(
({ isActive }) =>
() => {
$.isActive = !isActive
}
)
const addItem = fn(
({ isActive }) =>
isActive
? (item: any) => { $.items = [minimal-view.$.items, item] }
: () => { console.log('not active') }
)
fx(({ items }) => {
$.itemsView = items.map((item) => <i>{item}<br /></i>)
})
fx(({ $, itemsView, isActive }) => {
// $.view = 'hi'
$.view = <>
<Button
isActive={isActive}
onToggle={onToggle}
/>
<button onclick={() => addItem(Math.random())}>add item</button>
{itemsView}
</>
})
return ($) => {
$.skata
}
})
// let isActive = true
render(<App skata={2} />, document.body)
# State
# CustomElement
# Context
# abort
(fn)
# chain
(rest)
# dispatch
(el, nameOrEvent, detail, init)
# init
CustomEventInit<any>
dispatch<T extends EventTarget, K>(el, nameOrEvent, detail, init) =>
- any
# dispatchBind
(el)
dispatchBind<T extends EventTarget>(el) =>
- Fluent<, Required<DispatchOptions>>
# enableDebug
(maxUpdates, maxUpdatesWithinMs)
# event
(fn)
# on
(el)
# onAll
(target, listener, args)
# queue
(queueFn)
# reactive
(name, defaultProps, ctor, actions, fn)
reactive<TName, TProps, TLocal, TActions, TEffect>(name, defaultProps, ctor, actions, fn) =>
# render
(n)
# view
(name, defaultProps, local, actions, fn)
# web
(fn, options, parent)
# parent
= HTMLElement
{}
web<TName, TProps, TLocal>(fn, options, parent) =>
- WebElement<TName, TProps, TLocal> [
"Web"
]- argtor by stagas – Extracts destructured argument names from a function.
- event-toolkit by stagas – Toolkit for DOM events.
- everyday-types by stagas – Everyday utility types
- everyday-utils by stagas – Everyday utilities
- html-vdom by stagas – JSX virtual DOM using standard HTML
- minimal-reactive by stagas – Smallest possible implementation of reactive programming, effects and dependencies.
- nested-css by stagas – compile nested css rules
- proxy-toolkit by stagas – Proxy toolkit.
- to-fluent by stagas – Convert a function with a settings object to fluent API.
All contributions are welcome!