Skip to content
generated from stagas/ts

Minimal reactive component view library.

License

Notifications You must be signed in to change notification settings

stagas/minimal-view

Repository files navigation

minimal-view

Minimal reactive component view library.

npm i minimal-view pnpm add minimal-view yarn add minimal-view

Examples

# 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}
            &nbsp;
            {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)

API

# String

    # At
    # Join

      _Join<T, D> extends inferred ? Cast<X, string> : never

    # Length
    # Replace

      _Replace<S, R, W> extends inferred ? Cast<X, string> : never

    # Split

      _Split<S, D> extends inferred ? Cast<X, string []> : never

# AbortOptions

    # constructor()
    # latest

      boolean

    # throw

      boolean

    # timeout

      number

# DispatchOptions

    # constructor()
    # bubbles

      boolean

    # cancelable

      boolean

    # composed

      boolean

# EffectOptions

    # constructor()
    # atomic

      boolean

    # concurrency

      number

    # debounce

      number

    # first

      boolean

    # hooks
    # keys

      string [] & {

      # source

        string

      }

    # last

      boolean

    # next

      boolean

    # once  =  bool

      boolean

    # raf

      boolean

    # task

      boolean

    # throttle

      number

    # time

      boolean

# EventOptions

    # constructor()
    # atomic

      boolean

    # capture

      boolean

    # concurrency

      number

    # debounce

      number

    # first

      boolean

    # hooks
    # immediate

      boolean

    # last

      boolean

    # next

      boolean

    # once

      boolean

    # passive

      boolean

    # prevent

      boolean

    # raf

      boolean

    # stop

      boolean

    # task

      boolean

    # throttle

      number

    # time

      boolean

# Fiber

    # constructor(data)

      # new Fiber()

        Fiber

        # data

          Partial<EventEmitter<any>>

    # listeners
    # emit(eventName, args)

      # eventName
      # args

        Parameters<{

        # error

          # (error)

            # error

              Error

            (error)  =>

              void

        # flushend

          # ()

            ()  =>

              void

        # flushstart

          # ()

            ()  =>

              void

        } [K]>

      emit<K>(eventName, args)  =>

    # get(obj)

      # obj

        object

      get(obj)  =>

    # off(eventName, callback)

      # eventName
      # callback

        {

        # error

          # (error)

            # error

              Error

            (error)  =>

              void

        # flushend

          # ()

            ()  =>

              void

        # flushstart

          # ()

            ()  =>

              void

        } [K]

      off<K>(eventName, callback)  =>

    # on(eventName, callback, options)

      # eventName
      # callback

        {

        # error

          # (error)

            # error

              Error

            (error)  =>

              void

        # flushend

          # ()

            ()  =>

              void

        # flushstart

          # ()

            ()  =>

              void

        } [K]

      # options

        EventEmitterOptions

      on<K>(eventName, callback, options)  =>

        Off

    # once(eventName, callback)

      # eventName
      # callback

        {

        # error

          # (error)

            # error

              Error

            (error)  =>

              void

        # flushend

          # ()

            ()  =>

              void

        # flushstart

          # ()

            ()  =>

              void

        } [K]

      once<K>(eventName, callback)  =>

        Off

# FnOptions

    # constructor()
    # atomic

      boolean

    # concurrency

      number

    # debounce

      number

    # first

      boolean

    # hooks
    # keys

      string [] & {

      # source

        string

      }

    # last

      boolean

    # next

      boolean

    # raf

      boolean

    # task

      boolean

    # throttle

      number

    # time

      boolean

# Lane

    # constructor()
    # complete

      boolean

    # effects  =  ...

      Set<Fn>

    # size
    # laneRun()

      laneRun()  =>

        void

# OnOptions

    # constructor()
    # atomic

      boolean

    # capture

      boolean

    # concurrency

      number

    # debounce

      number

    # first

      boolean

    # hooks
    # immediate

      boolean

    # last

      boolean

    # next

      boolean

    # once

      boolean

    # passive

      boolean

    # prevent

      boolean

    # raf

      boolean

    # stop

      boolean

    # task

      boolean

    # throttle

      number

    # time

      boolean

# QueueOptions

    # constructor()
    # atomic

      boolean

    # concurrency

      number

    # debounce

      number

    # first

      boolean

    # hooks
    # last

      boolean

    # next

      boolean

    # raf

      boolean

    # task

      boolean

    # throttle

      number

    # time

      boolean

# State
# Change

    # key

      string

    # next

      any

    # prev

      any

# CustomElement

    # attributeChangedCallback(name, oldValue, newValue)

      # name

        string

      # oldValue

        null | string

      # newValue

        null | string

      attributeChangedCallback(name, oldValue, newValue)  =>

        void

    # connectedCallback()

      connectedCallback()  =>

        void

    # disconnectedCallback()

      disconnectedCallback()  =>

        void

# EventHandler

    # this
    # event

      E & {

      # currentTarget
      # target

        Element

      }

    EventHandler(this, event)  =>

      any

# Hooks

    # after

      # ()

        ()  =>

          void

    # before

      # ()

        ()  =>

          void

# IDep

    # accessors

      {

      # get()

        get()  =>

          undefined | null | T

      # set(value)

        # value

        set(value)  =>

          boolean

      }

    # current

      undefined | null | T

    # stackErr

      Error

    # subs

      Set<any>

    # value

      undefined | null | T

    # trigger()

      trigger()  =>

        void

# SafeMap

    # get(v)

      # v

      get(v)  =>

    # has(v)

      # v

      has(v)  =>

        boolean

# SafeWeakMap

    # get(v)

      # v

      get(v)  =>

    # has(v)

      # v

      has(v)  =>

        boolean

# Any

    any

# AnyFn

    # (args)

      # args

        any []

      (args)  =>

        any

    | void | false

# Boxs
# Chunk

    N extends N ? number extends N ? T [] : _Chunk<T, N, [ ]> : never

# Class

    # (args)

      # new()

        T

        # args

          any []

# Component
# Context
# Ctor

    # (args)

      # new()

        unknown

        # args

          any []

# CustomElementConstructor
# Dep
# Deps

    [K in keyof T ]-?: NonNullable<T [K]>

# DetailOf
# Dispatch
# Effect
# EventKeys
# EventsOf
# Fn

    # (args)

      # args

      (args)  =>

# Func
# Fx
# Get
# Key

    number | string | symbol

# Keys
# Mixable

    {} & Omit<T, "constructor">

# Mutable

    [P in keyof T ]: T [P]

# Narrow

    K extends T ? K : never

# NarrowKey
# NonNull
# Null

    null | undefined | void

# ObjectFromList

    [K in T extends ReadonlyArray<inferred> ? U : never ]: V

# Off

    # ()

      ()  =>

        void

# OffFx

    # (reconnect, disconnect)

      # reconnect

        boolean

      # disconnect

        boolean

      (reconnect, disconnect)  =>

        any

# On
# OnEvent
# OnGetter
# Prefix

    template-literal

# Props

    [K in keyof T ]: NonNullable<T [K]>

# Reactive
# ReactiveFactory
# Refs

    [K in keyof T ]-?: NonNullable<T [K]> extends HTMLElement ? VRef<T [K]> : never

# SansOn
# StringKeys
# StringLiteral

    T extends string ? string extends T ? never : T : never

# StringOf
# Sub

    # (prevValue, nextValue)

      # prevValue

        T | null | undefined

      # nextValue

        T | null | undefined

      <T>(prevValue, nextValue)  =>

        void

# Target

    HTMLElement | SVGElement | Window | Document | ShadowRoot

# Task

    {

    # args

      any

    # fn

      Fn<any, any>

    # promise

      Promise<any>

    # reject

      Fn<any, any>

    # resolve

      Fn<any, any>

    # self

      any

    }

# Unboxs

    [K in keyof T ]-?: NonNullable<T [K] ["value"]>

# Vals

    [K in keyof T ]: T [K] ["value"]

# ValueConstructor

    StringConstructor | NumberConstructor | BooleanConstructor

# ValuesOf

    T [keyof T]

# View

    # (props)

# ViewLocal

    {

    # __style

      string

    # css

      JSX.Element

    # view

      JSX.Element

    }

# ViewState
# WebElement
# _Chunk
# Classes  =  ...

    any []

# MouseButton

    {

    # Left

      number

    # Middle

      number

    # Right

      number

    }

# element

    HTMLElement | undefined

# fiber  =  ...
# Task(fn, self, args)

    # fn

      Fn<any, any>

    # self

      any

    # args

      any

    Task(fn, self, args)  =>

# abort(fn)

    # fn(signal)

      # signal

        AbortSignal

      fn(signal)  =>

    abort<P, R>(fn)  =>

      Fn<P, R extends Promise<any> ? R : Promise<R>>

# chain(rest)

    # rest

    chain(rest)  =>

      # ()

        ()  =>

          any

    # rest

    chain(rest)  =>

      # ()

        ()  =>

          any

# dep(value)

    # value

      null | T

    dep<T>(value)  =>

# deps(obj)

    # obj

    deps<T>(obj)  =>

# dispatch(el, nameOrEvent, detail, init)

    # el
    # nameOrEvent
    # detail
    # init

      CustomEventInit<any>

    dispatch<T extends  EventTarget, K>(el, nameOrEvent, detail, init)  =>

      any

# dispatchBind(el)

    # el

    dispatchBind<T extends  EventTarget>(el)  =>

      Fluent<
      # (nameOrEvent, detail, init)

        # nameOrEvent
        # detail
        # init

          CustomEventInit<any>

        <K>(nameOrEvent, detail, init)  =>

          any

      , Required<DispatchOptions>>

# effect(deps, fn)
# enableDebug(maxUpdates, maxUpdatesWithinMs)

    # maxUpdates  =  2000

      number

    # maxUpdatesWithinMs  =  10

      number

    enableDebug(maxUpdates, maxUpdatesWithinMs)  =>

      void

# event(fn)
# hook(args)

    # args

      any

    hook(args)  =>

      any

# on(el)

    # el

    on<T extends  EventTarget>(el)  =>

    # el
    # key

    on<T extends  EventTarget, K>(el, key)  =>

# onAll(target, listener, args)

    # target

      EventTarget

    # listener

      EventListener

    # args

      any []

    onAll(target, listener, args)  =>

      # ()

        ()  =>

          void

# part(fn)

    # fn(update)

      # update(view)

        # view

          VKid

        update(view)  =>

          void

      fn(update)  =>

        void

    part(fn)  =>

      # ()

        ()  =>

          VKid

# queue(queueFn)

    # queueFn

    queue<P, R>(queueFn)  =>

      Fn<P, R extends Promise<any> ? R : Promise<R>>

# reactive(name, defaultProps, ctor, actions, fn)
# render(n)

    # n

      VKid

    render(n)  =>

      DocumentFragment

    # n

      VKid

    # el
    # doc

      Doc

    # withNull

      boolean

    render<T extends  TargetEl>(n, el, doc, withNull)  =>

# taskGroup(other, tasks)

    # other
    # tasks

    taskGroup(other, tasks)  =>

      void

# taskRun(task, res)

    # task
    # res

      any

    taskRun(task, res)  =>

      any

# view(name, defaultProps, local, actions, fn)
# web(fn, options, parent)
# wrapEvent(options)
# wrapQueue(options)

    # options

    wrapQueue(options)  =>

      # (queueFn)

        # queueFn

        <P, R>(queueFn)  =>

          Fn<P, R extends Promise<any> ? R : Promise<R>>

Credits

Contributing

Fork or edit and submit a PR.

All contributions are welcome!

License

MIT © 2023 stagas