Coder Social home page Coder Social logo

vanjs-org / van Goto Github PK

View Code? Open in Web Editor NEW
3.4K 31.0 77.0 2.79 MB

🍦 VanJS: World's smallest reactive UI framework. Incredibly Powerful, Insanely Small - Everyone can build a useful UI app in an hour.

Home Page: https://vanjs.org

License: MIT License

JavaScript 84.20% Shell 0.38% HTML 0.68% TypeScript 14.74%
ui-framework vanilla-dom-manipulation vanilla-javascript vanilla-js data-binding dom dom-manipulation minimalist grab-n-go reactive-ui

van's People

Contributors

allcontributors[bot] avatar atmos4 avatar b-rad-c avatar caputdraconis050630 avatar cqh963852 avatar duffscs avatar ebraminio avatar eford36 avatar efpage avatar hunter-gu avatar onsclom avatar tamo avatar tao-vanjs avatar tolluset avatar yahia-berashish avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

van's Issues

Updating UI

Hy Tao,

Thank you for your awesome framework.
I really love it and hope It will have the sucess it deserves.

I have a question about state.

How could I update the UI when passing an object to a state ? :

ObjectState

const {button, div} = van.tags

// Create a new State object with init value 1
const counter = van.state({
  count : 0
})

const viewcounter = div(counter.val.count)

const incrementBtn = button({onclick: () => ++counter.val.count}, "Increment")


function App () {
  const dom = div
  return dom (
    viewcounter,
    incrementBtn
  )
}

van.add(document.body, App())

I have managed this way, can you tell me if there is something wrong ? , thank you

StateObject

import van from 'https://vanjs.org/code/van-0.11.9.min.js'

const { div, button, input, "m-c": MC} = van.tags;

const counter = van.state({
  point : 89,
  name: "Hal"
})


function incCounter (s) {
  counter.val = {...counter.val, point : ++s.point }
}

function App (stateval) {
  let s = stateval
  const dom = div
  const view = dom (
     div(s.name),
     input({placeholder: "input..."}),
     div(s.point),
     button({onclick : () => incCounter(s)}, "inc")
  )
  return view
}

van.add(document.body, van.bind(counter, (stateval) => App(stateval)))

Regards

TS Typings improvement

Hey!

Below added type Val (for convenience of typing arguments) and exposed PropsWithKnownKeys for anyone who would want to explore and expand sort of "custom tags" using typescript.

Currently:

const Button = (text: StateView<string> | string, color: StateView<string> | string) =>
  button({style: () => `background-color: ${van.val(color)};`, onclick: ()=> console.log("HELLO")}, van.val(text))

After adding and exposing Val type:

const Button = (text: Val<string>, color: Val<string>) =>
  button({style: () => `background-color: ${van.val(color)};`, onclick: ()=> console.log("HELLO")}, van.val(text))

van.d.ts

export interface State<T> {
  val: T
  readonly oldVal: T
}

// Defining readonly view of State<T> for covariance.
// Basically we want StateView<string> to implement StateView<string | number>
export type StateView<T> = Readonly<State<T>>

export type Val<T> = State<T> | T

export type Primitive = string | number | boolean | bigint

export type PropValue = Primitive | ((e: any) => void) | null

export type PropValueOrDerived = PropValue | StateView<PropValue> | (() => PropValue)

export type Props = Record<string, PropValueOrDerived> & { class?: PropValueOrDerived }

export type PropsWithKnownKeys<ElementType> = Partial<{[K in keyof ElementType]: PropValueOrDerived}>

export type ValidChildDomValue = Primitive | Node | null | undefined

export type BindingFunc = ((dom?: Node) => ValidChildDomValue) | ((dom?: Element) => Element)

export type ChildDom = ValidChildDomValue | StateView<Primitive | null | undefined> | BindingFunc | readonly ChildDom[]

export type TagFunc<Result> = (first?: Props & PropsWithKnownKeys<Result> | ChildDom, ...rest: readonly ChildDom[]) => Result

type Tags = Readonly<Record<string, TagFunc<Element>>> & {
  [K in keyof HTMLElementTagNameMap]: TagFunc<HTMLElementTagNameMap[K]>
}

export interface Van {
  readonly state: <T>(initVal: T) => State<T>
  readonly val: <T>(s: Val<T>) => T
  readonly oldVal: <T>(s: Val<T>) => T
  readonly derive: <T>(f: () => T) => State<T>
  readonly add: (dom: Element, ...children: readonly ChildDom[]) => Element
  readonly _: (f: () => PropValue) => () => PropValue
  readonly tags: Tags
  readonly tagsNS: (namespaceURI: string) => Readonly<Record<string, TagFunc<Element>>>
  readonly hydrate: <T extends Node>(dom: T, f: (dom: T) => T | null | undefined) => T
}

declare const van: Van

export default van

Proxy error using vanX.reactive() with lists of nested objects

I have an API that returns a list of objects, each of which has an inner object. This use case seems best handled using vanX with a combination of vanX.reactive, vanX.replace, and vanX.list to render a list of UI elements.

At the surface level, this appears to work fine if you don't access inner objects:

items = vanX.reactive(
  [
    {
      foo: 'bar',
      baz: {kind: 'dessert', amount: 'lots'}
    }
  ]
);

makeItemGood = (item) => van.tags.li(item.foo)
itemListGood = vanX.list(van.tags.ul, items, ({val: v}) => makeItemGood(v))
// yields: <ul><li>bar</li></ul>

However, the moment you attempt to access baz, you get the error:

TypeError: 'get' on proxy: property 'baz' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#<Object>' but got '#<Object>')

For example:

makeItemBad = (item) => van.tags.li(item.baz.kind)
itemListBad = vanX.list(van.tags.ul, items, ({val: v}) => makeItemBad(v))

As a work around I'm currently rewriting nested objects as JSON strings and then parsing them when it's time to make use of them.

Perhaps I've misunderstood something here, however? I don't actually need nested state management, but have struggled so far to get reactivity working using vanjs alone under this scenario.

Suggestion - readonly state proxy

This is more a dx issue, but I see a pattern frequently where I'd like to be able to bind to a state var, but prevent accidental writes to it because writes should be handled by a helper that adjusts multiple state variables or performs other side effects. It would be nice to have something like State.readonly() that returned a wrapped version where get stateVar.val worked but set stateVar.val threw an exception.

It might look something like this:

  "readonly"() {
    let _this=this
    return { 
      ..._this,
      set "val"(v) { throw new Error(`State is read-only`) },
    }
  }
export type State<T> = {
  val: T
  onnew(l: (val: T, oldVal: T) => void): void
  readonly(): State<T> & { readonly val:T}
}

Or, if you don't want to actually add that to the code, I can basically get what I need from TS type checking

export type ReadOnlyState<T> = Omit<State<T>, 'val'> & { readonly val: T }
export const toReadOnlyState = <T>(state: State<T>) => state as ReadOnlyState<T>

Custom Elements support

The concise but powerful API of VanJS would make it very good alternative to template literals for Web Components.

I've been evaluating it for this use case but there are a couple of advanced use cases that aren't supported that would be great to see.

  • Non primitive data types for properties (e.g. arrays, objects and functions)
  • Custom event handling

I've attempted at implementing the Custom Elements Everywhere tests:

Link to CodePen

If you consider this useful I'd happily create a PR to add these into the existing test suite, as well as assisting with any API changes.

Look forward to seeing how this library progresses.

SSR and Hydration Support for VanJS

Astro js support is a bit more complex one. But supporting Vite as bundle tool is really necessary nowadays.
I really like minimal frameworks like Van. But it's really hard to use them when you need SEO. Good SEO requires SSR, but SSR requires some kind of hydration.

SVG has a slightly different DOM API for element creation than ordinary DOM elements. Referred to as "namespace" elements in the spec (https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS)

SVG has a slightly different DOM API for element creation than ordinary DOM elements. Referred to as "namespace" elements in the spec (https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS)

I noticed while trying to add icons to an app using VanJS that the element in the devtools <svg width="100" ...><circle cx="50" ...></circle></svg> while visually identical to the namespace element didn't render on my page.

// SVG namespace element API
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
el.setAttribute("width", "100")
// etc.

Would it be possible to add support for SVG in VanJS without sacrificing the amazing developer experience?

Either switch to createElementNS for known namespace tags, e.g. path, p, circle etc. or allow users to opt-in somehow?

Originally posted by @andrewgryan in #27

Array state

I have such function to render to-do list. But it doesn't re-render after click on button.
As I understand, it's because I use .map with .val. So state doesn't track changes in there.
How to use .map with array state?

function todo() {
  const todos = van.state(["AAA", "BBB", "CCC"])

  return el.ul(
    todos.val.map((x) =>
      el.li(
        el.button(
          {
            onclick: () => {
              todos.val = todos.val.filter((value) => value != x)
            },
          },
          x
        )
      )
    )
  )
}

Question: updating attributes (eg. 'className') without re-generating whole sub-DOM

Considering the case of a tabbed-container component with say 3 tabs, only any 1 of them to be ever visible, and a van state var named activeIdx.

The requirement: when switching active-tab, the other tabs should just become hidden of course — but remain in the DOM in their current UI state (like scroll position in tab itself or some textarea). No re-generating DOM on hiding or showing, by adding/removing a custom CSS class (eg. 'active') via script on tab switch.

Right now, I'm doing like document.getElement('tab_' + i).classList.add/remove('active') in the van.derive handler for activeIdx. And it works.

My question is whether there are any hidden gotchas with this approach in a VanJS context or whether this is unproblematic =) I read somewhere a warning about custom DOM manipulations in your docs or threads some days ago but can't find it again right now.

Context-API support

Discussed in #152

Originally posted by yahia-berashish October 25, 2023
Hello, I tried to migrate React Context API to VanJS. The key steps:

  • create a class name and unique id for context provider, and store provide value
  • find the ancestor with class name by element.closest() when use context
  • get the unique id from dom element id, then get the provide value from store

Current drawback: it will always get the default context when component render, because the framework hasn't bind the reactive state and dom, and it's a little tricky to find the ancestor context provider.

Try it on sandbox: https://codesandbox.io/p/sandbox/vanjs-context-provider-poc-qsgdr8?file=%2Fsrc%2Fmain.ts%3A5%2C1

I want to know if there any recommendations about this.

state management? css?

how to manage state with vanjs, is this more of a SPA that can have client side routing, state etc?

also, an example with some well known css will be nice, e.g bootstrap 5.

Deeply nested state objects and arrays

Looking into a code and trying to wrap my head around a topic of dynamic deeply nested object and array structures. I'm pretty sure someone here thought it through and I don't have to rediscover things.

Lets say I have this example:

const items = vanX.reactive([])
return vanX.list(ul, items, v => SomeComponent(v))

and then I add item:

items.push({
    type: "some_sub_items",
    sub_items: []
})

can I use vanX.list on sub_items?

Can state not be an object?

Sorry if I missed it in docs.

<!doctype html>
<html>
<head>
</head>
<body>
<script type="module">
import * as van from 'https://vanjs.org/code/van-0.11.0.js'

const { button, span } = van.tags

class Counter {
  constructor() {
    this.state = van.state({
      amount: 0
    })
  }

  onIncrementButtonClickFactory(self) {
    return (event) => {
      console.log(event)
      self.state.amount += 1
    }
  }

  onDecrementButtonClickFactory(self) {
    return (event) => {
      console.log(event)
      self.state.amount -= 1
    }
  }

  render() {
    const incrementButtonOnClick = this.onIncrementButtonClickFactory(this)
    const derementButtonOnClick = this.onDecrementButtonClickFactory(this)
    const incrementButton = button({ onclick: incrementButtonOnClick }, "👍")
    const decrementButton = button({ onclick: derementButtonOnClick }, "👎")
    return span(`❤️ ${this.state.amount} `, incrementButton, decrementButton)
  }
}

window.addEventListener('load', function () {
  const node = document.body
  const counter = new Counter()
  van.add(node, counter.render())
})
</script>
</body>
</html>

I was trying to teach my friend who doesn't know programming how to use a really cool lightweight library like this while showing him basic concepts of HTML/JavaScript/ES6/frontend development. I couldn't discern from the docs how to make this work. this.state.amount is undefined(this.state.val.amount isn't but that feels wrong) and none of the events work.

vanX: "TypeError: Illegal invocation" when using JS-native `File` object instead of custom one

Hiya, I had a well-functioning vanx.Reactive<UpFile> (where type UpFile={name:string,type:string}) already fully working with vanx.list() and vanx.replace().

I had to directly swap out that little custom type with the browser/EcmaScript-native File (that's in urFileInput.files[]File also having those same 2 fields, and then some), and, with no other changes having been made to the code, now during replace() vanX throws like so:

van-1.2.3.js:17 TypeError: Illegal invocation
    at get (<anonymous>)
    at Object.get (van-x.js:29:11)
    at posts.js:76:65
    at van-x.js:57:13
    at runAndCaptureDeps (van-1.2.3.js:15:12)
    at bind (van-1.2.3.js:74:16)
    at add (van-1.2.3.js:94:32)
    at van-1.2.3.js:119:10
    at Module.replace (van-x.js:113:16)
    at onFilesAdded (posts.js:142:8)
runAndCaptureDeps @ van-1.2.3.js:17
bind @ van-1.2.3.js:74
add @ van-1.2.3.js:94
(anonymous) @ van-1.2.3.js:119
replace @ van-x.js:113
onFilesAdded @ posts.js:142
onchange @ posts.js:33

van-x.js:112 Uncaught TypeError: Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node'.
    at Module.replace (van-x.js:112:23)
    at onFilesAdded (posts.js:142:8)
    at HTMLInputElement.onchange (posts.js:33:90)
replace @ van-x.js:112
onFilesAdded @ posts.js:142
onchange @ posts.js:33

To prevent this, AFAICT I'd have to keep a separate File[] array and a my custom File-clone type and keep a vanx.Reactive<MyFileType> sync'd with the sibling File[] array. Doable, but enough of a situation to prompt the question whether you have any idea what causes this, whether there might be a nifty trick for the vanX lib user to employ for certain "browser-native/resource objects" where that would happen, etc. Eager to hear your thoughts =)

"state functions" dependencies

hi, back in v0.x van.bind() asked for dependency array, since v1.x it's handled automatically, which is convenient, but not always what you want. it might not be the best practice, but sometimes you want to trigger the "state function" when some state you're not reffering in the function changes.

example: graph nodes w/ linked siblings. i want to output list of selected node's siblings. when i change change time, selected node's siblings might dis/appear, so i want to update list upon this change.

i don't want to create new array of selected nodes just because of this case and only other thing i can think of is referring the value and not using it, but does not feel "right" :) and also might stop working in future as it's kinda hacky way

() => {
    state2.val;  <-- is not used
    return state1.val;
}

Incorrect attribute typing for van_jsx

This is another JSX type issue I haven't noticed in the previous issue.
The attributes used for the JSX elements in jsx-internal.d.ts are plain HTML attributes, which means that reactive states cannot be used as attributes = no reactive attributes.
This can be solved by creating a utility type to modify the attributes, something like this:

// modify attribute type
type VanAttrs<T> = {...}

// use utility type instead of HTMLAttributes
 export interface IntrinsicElements {
    a: VanAttrs<HTMLAnchorElement>;
    abbr: VanAttrs<HTMLElement>;
    address: VanAttrs<HTMLElement>;
    area: VanAttrs<HTMLAreaElement>;
    article: VanAttrs<HTMLElement>;
    ...
}

I will probably take a look at this issue later if it is still open

onRender and/or onReRender triggers

Would be nice to see examples of and an option to run some script upon rendering, for example, wiring bootstrap or materializeweb.com based component handlers.

Improve type definition

I'm an absolutely heavy user of typescript, and glad to see how lightweight a library van-js is, but I'd find some type definitions can be improved

For the function bind, I use a series of generics instead of overload to improve that

typescript playground

PS: tags didn't contain all known element types for now(e.g. section and some other semantic tags), I'm also trying to do something on it

bind() not picking up changes even when `onnew` does

I have a pattern like this:

const myStoreFactory = ()=> {
   const foo = state(false)
}

const myStore = myStoreFactory()

const myComponent = ()=>{
   const { foo } = myStore
   foo.onnew(console.log) // This works
   return bind(foo, foo=>{
      return div(foo ? `foo` : `no foo`)
   })
}

const toggle = ()=>{
   foo.val = !foo.val
   setTimeout(toggle, 1000)
}

In some cases, and I can't figure out exactly why, bind() is not picking up the changes even when onnew will fire. Any ideas?

tags.input: readonly vs disabled

I know this is a real edge-case but would be totally nice if one could pass to van.tags.input({},...) a readonly: false (typically, would be a terniary conditional rather than false const of course =) similar to disabled, without the readonly-ness becoming true in-the-browser (well, certainly Chromium).

Ie.: when we pass {disabled:false}, the input is not disabled, but passing {readonly:false} seems to make it readonly still.

I know full well that it's really a quirk of HTML and/or the browsers' DOM APIs rather than van's, of course ... and special-casing like that in a crisp codebase such as van might just be a no-go for you... but like I said, here's a "would be too neat from the van user's point-of-view" =) — feel free to close issue if out-of-scope or going against the van design philosophy or something.

Neat lib, glad it exists! Really happy to not have to go heavyweight overbloated React-and-the-likes and can stay vanilla, and appreciate the .d.ts too.

vanX: `Cannot read properties of null (reading 'Symbol()')` issue when `replace`ing an array of non-null objs with partially-null fields

Bear with me if I paste some slightly verbose (tho still trim & banal) code... the array's item type being reactive([])d here is this:

export type Post = {
	Id: number
	DtMade?: string
	DtMod?: string
	By: number // Id of User
	Files: string[]
	Md: string // text content of post
	Repl: number // Id of another Post, or 0
	To: number[] // Id of User(s)
}

(This is in a codegenerated-from-my-API .ts, no van let alone state fields here as you can see. Simple as it gets objs.)

This simplistic / prototypal component fails during replace as will be stacktrace-detailed right afterwards:

import van from '../../__yostatic/vanjs/van-1.2.3.debug.js'
import * as vanx from '../../__yostatic/vanjs/van-x.js'
import * as yo from '../yo-sdk.js'

const htm = van.tags

export type UiCtlPosts = {
    DOM: HTMLElement
    posts: vanx.Reactive<yo.Post[]>
    update: (_: yo.Post[]) => void
}

export function create(): UiCtlPosts {
    const me: UiCtlPosts = {
        DOM: htm.div({ 'class': 'haxsh-posts' }),
        posts: vanx.reactive([] as yo.Post[]),
        update: (posts) => update(me, posts),
    }
    van.add(me.DOM, vanx.list(htm.div, me.posts, (it) => {
        return htm.div({}, it.val.Md)
    }))
    return me
}

function update(me: UiCtlPosts, posts: yo.Post[]) {
    vanx.replace(me.posts, (_: yo.Post[]) => posts)
}

Now here's what happens on every update call — I have console-verified that posts is always filled with 123 non-null Post objects. No fields are ever null except occasionally To. And what is catched is this:

{
  "message":"Cannot read properties of null (reading 'Symbol()')",
  "stack"  :"TypeError: Cannot read properties of null (reading 'Symbol()')
    at toState (http://localhost:5252/__yostatic/vanjs/van-x.js:11:21)
    at http://localhost:5252/__yostatic/vanjs/van-x.js:16:74
    at Array.map (<anonymous>)
    at reactive (http://localhost:5252/__yostatic/vanjs/van-x.js:16:54)
    at toState (http://localhost:5252/__yostatic/vanjs/van-x.js:11:72)
    at http://localhost:5252/__yostatic/vanjs/van-x.js:96:25
    at Array.map (<anonymous>)
    at Module.replace (http://localhost:5252/__yostatic/vanjs/van-x.js:94:38)
    at update (http://localhost:5252/__static/ui/posts.js:26:8)
    at Object.update (http://localhost:5252/__static/ui/posts.js:12:24)"
}

To circumvent this, I have found that this alternative update impl would be necessary instead:

function update(me: UiCtlPosts, posts: yo.Post[]) {
    vanx.replace(me.posts, (_: yo.Post[]) => posts.map(it => {
        if (!it.To)
            it.To = []
        return it
    }))
}

Imho shouldn't be necessary, right? Might have minified away or forgotten a ? in the still-new, otherwise funky and already-greatly-appreciated vanX perhaps =)

vanX: `TypeError: 'get' on proxy` and `TypeError: 'set' on proxy`, what am I doing wrong?

Here's my dummy .ts component:

import van from '../../__yostatic/vanjs/van-1.2.3.debug.js'
import * as vanx from '../../__yostatic/vanjs/van-x.js'
import * as yo from '../yo-sdk.js'

const htm = van.tags

export type UiCtlBuddies = {
    DOM: HTMLElement
    buddies: vanx.Reactive<yo.User[]> // User type def not included here, the inits below show it anyway
}

export function create(): UiCtlBuddies {
    const me: UiCtlBuddies = {
        DOM: htm.div({ 'class': 'buddies' }),
        buddies: vanx.reactive([
            { Id: 1234, Btw: "Btw 1234", Nick: "user1234", PicFileId: "", Auth: 1234 } as yo.User,
            { Id: 2345, Btw: "Btw 2345", Nick: "user2345", PicFileId: "", Auth: 2345 } as yo.User,
            { Id: 4321, Btw: "Btw 4321", Nick: "user4321", PicFileId: "", Auth: 4321 } as yo.User,
        ])
    }

    setInterval(() => {
        const idx = Math.floor(me.buddies.length * Math.random())
        const buddy = me.buddies[idx]
        buddy.Btw = new Date().toLocaleTimeString() // take this line out and there are no errors (because there are no updates=)
    }, 1000)

    van.add(me.DOM, vanx.list(htm.ul, me.buddies, (it) => {
        const buddy = it.val
        return htm.li({}, buddy.Nick, buddy.LastSeen, buddy.Btw)
    }))

    return me
}

Every second, it aims to pick a random User in buddies and set its Btw: string to current time.

With that, every second Chrome dev-tools console spits out 2 errors together, first:

Uncaught TypeError: 'set' on proxy: trap returned truish for property 'Btw' which exists in the proxy target as a non-configurable and non-writable data property with a different value
at buddies.js:20:15
(anonymous) @ buddies.js:20

and second:

van-1.2.3.js:17 TypeError: 'get' on proxy: property 'Btw' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'Btw 4321' but got '9:35:37 AM')
at buddies.js:24:57
at van-x.js:52:13
at van-1.2.3.debug.js:60:27
at runAndCaptureDeps (van-1.2.3.js:15:12)
at bind (van-1.2.3.js:74:16)
at updateDoms (van-1.2.3.js:128:20)

Any ideas what I'm doing wrong? I thought I'd basically followed the vanjs.org/x examples.. somewhere must have messed up there tho.

Btw: an array-of-strings instead of an array-of-objects works for successful automagic reactive update-and-refresh. But that surely can't be the solution given we're talking vanX here =)

Add a swap method

Hello,

I suppose the main idea of Vanjs is to create a single page application, but you can use it also for handle few parts of a single html page

It's possible to add a new method like van.swap or improve the van.add method for swap a element?
It's useful when you create a component with a css-in-js librairie and you need customize "the container" depending on the context.
it's useful also when you fetch a element and you want update the UI.

Example when I create a nav component with VanJs and Panda-css:

index.html

<body>
  <nav id="navigation"></nav>
</body>

index.js

import { css } from '../styled-system/css';
import van from 'vanjs-core';

const { nav, div } = van.tags;

const containerStyle = css({
  display: flex,
  border: 'solid'
});

const itemStyle = css({
  display: flex,
  border: 'solid'        
});

const Menu= ()=> nav({id:"navigation", class:containerStyle}, 
                     [div({class:itemStyle},"Home"), 
                      div({class:itemStyle},"About"), 
                      div({class:itemStyle},"Contact")])

van.add(document.querySelector("#navigation"), Menu())

here when I append, I have :

index.html

<body>
   <nav id="navigation">
      <nav id="navigation">
         <div>Home</div>
         <div>About</div>
         <div>Menu</div>
      </nav>
    </nav>
</body>

with swap:
index.js

van.swap(document.querySelector("#navigation"), Menu())

index.html

<body>
      <nav id="navigation">
         <div>Home</div>
         <div>About</div>
         <div>Menu</div>
      </nav>
</body>

off course I can do:

const Menu= ()=>  [div({class:itemStyle},"Home"), 
                   div({class:itemStyle},"About"), 
                   div({class:itemStyle},"Contact")]
                   
 van.add(document.querySelector("#navigation"), Menu())

But I loose my container style an I must handle two css files.

Allow lit-html-like templates as worthy, yet build-free JSX replacement

Discussed in #10

Originally posted by cloudspeech May 25, 2023
VanJS' HTML-markup-as-nested-JS-function-calls approach to templating is going to be a hard sell to the wider community, especially people that have seen React. They just want something like JSX.

Lit-html has a very nice syntax that comes very close to JSX, yet only uses browser-native means. See https://lit.dev/docs/templates/overview/.

I humbly suggest to not reinvent the wheel but provide an add-on to VanJS that supports unchanged lit-html template syntax (properties, attributes, events at minimum, perhaps also ref). This way the vanJS base size can be kept minimal for purists.

I expect the add-on's size to likewise be very small (using DOMParser).

What do you think?

TS2345 error when adding a DocumentFragment as a child node

I'm attempting to add raw html as a child node.

const Hello = () => div(
	p("👋Hello ", fragment_('<em>there</em>')),
	ul(
		li("🗺️World"),
		li(a({href: "https://vanjs.org/"}, "🍦VanJS")),
	),
)
van.add(document.body, Hello())
function fragment_(html:string) {
	const frag = document.createDocumentFragment()
	const temp = document.createElement('div')
	temp.innerHTML = html
	while (temp.firstChild) {
		frag.appendChild(temp.firstChild)
	}
	return frag
}

The code above results with an error:

TS2345: Argument of type  DocumentFragment  is not assignable to parameter of type  ChildDom<Element, Text> 

It works in js-fiddle using just javascript.

Example using multiple components together.

Would be good to see more examples using components together. Ideally something with a router based render, and async loading of components via import('./SomeComponent').then(({ SomeComponent }) => SomeComponent)

Real World Template

Is there a real world template that provides the below?

  • demo how SSR works
  • demo how interactivity is achieved with SSR, i.e. does this support hydration?
  • demo how components can be structured in individual files and imported to pages/routes

Thanks!

Feature - Ignore null or undefined for the rest parameters

I am trying this pattern, could the rest: ChildDom[] allow undefined or null, more like rest: (ChildDom|undefined|null)[]

export const Button = (props?: ButtonProps) => {
  const componentClasses = classNames(
    Classes.BUTTON,
    props?.active ? Classes.ACTIVE : null,
    props?.minimal ? Classes.MINIMAL : null,
    props?.outlined ? Classes.OUTLINED : null,
    props?.fill ? Classes.FILL : null,
    props?.small ? Classes.SMALL : null,
    props?.alignText ? AlignmentClassMap[props?.alignText] : null,
    props?.intent ? IntentClassMap[props?.intent] : null
  );
  const createText = () => {
    if (props?.text) {
      return span({ class: Classes.BUTTON_TEXT }, props.text);
    }
  };
  const createIcon = (icon?: IconName) => {
    if (icon) {
      return Icon({ icon });
    }
  };
  return button({ class: componentClasses }, createText(), createIcon());
};
Button.displayName = `${DISPLAYNAME_PREFIX}.Button`;

Otherwise I have to do this which is a bit uglier

export const Button = (props?: ButtonProps) => {
  const componentClasses = classNames(
    Classes.BUTTON,
    props?.active ? Classes.ACTIVE : null,
    props?.minimal ? Classes.MINIMAL : null,
    props?.outlined ? Classes.OUTLINED : null,
    props?.fill ? Classes.FILL : null,
    props?.small ? Classes.SMALL : null,
    props?.alignText ? AlignmentClassMap[props?.alignText] : null,
    props?.intent ? IntentClassMap[props?.intent] : null
  );
  const children: ChildDom[] = [];
  if (props?.text) {
    children.push(span({ class: Classes.BUTTON_TEXT }, props.text));
  }
  if (props?.icon) {
    children.push(Icon({ icon: props?.icon }));
  }
  return button({ class: componentClasses }, ...children);
};
Button.displayName = `${DISPLAYNAME_PREFIX}.Button`;

Sometimes components might return nothing due to logic inside so it would be useful to ignore undefined or null

Adding TypeScript support VanCone

Hello again.
I will be trying to open a PR to add TypeScript support, but I don't have enough information about how the vanjs-core works, I tried to get some things done with ChatGPT, but I think I still need help from @b-rad-c on how the code functions and what types are expected to be passed and result from the code.

No license file for Van

Could you please tell us what License for Vanjs?
It would be important for enterprise early adoption. Thanks!

DocumentFragment not Append to Dom

I am creating a todo list

const items = van.state<number[]>([1,2,3]);
let count = 3;

van.add(
  document.getElementById("app")!,
  van.tags.button(
    {
      onclick: () => {
        items.val = [...items.oldVal, count++];
      },
    },
    "add item",
    () => items.val.length,
  ),
 // todo list here
  () => {
    const parent = document.createDocumentFragment();

    items.val.forEach((item) => {
      const element = van.tags.div(item);
      parent?.append(element);
    });

    return parent;
  },
);

I want the todo items append to the app div. without a wrapper. So I tried DocumentFragment

This todo list will only be executed once and will not be executed a second time due to changes in items. May I ask why this is?

Example for use with a global/unified state machine

For Advanced Examples, it would be nice to see a way to use this against a state engine like Zustand, Jotai, Valtio, Redux or MobX. Since being able to integrate a larger shared state would be beneficial for use in larger application(s) development.

Remember local state

Hey,
I just wanted to try out VanJS by implementing a todo app.
While there are two examples - a procedural and a reactive one - I prefer the latter.

So here is a small snippet of my app:

const App = () => {
  const todos = van.state([{ task: "test", done: false }]);

  const onAddTodoClick = () => {
    todos.val = [...todos.val, { task: "new task", done: false }];
  };

  return van.tags.div(
    van.tags.button({ onClick: onAddTodoClick }, "+"),
    () => van.tags.ul(todos.val.map(todo => TodoItem({ todo })))
  );
};

const TodoItem = props => {
  const edit = van.state(false);

  const onEditClick = e => {
    e.stopPropagation();

    edit.val = true;
  };

  return () => van.tags.li(
    edit.val ? "edit" : props.todo.task,
    van.tags.button({ onClick: onEditClick }, "edit"),
  ); 
};

(this code is out of my head - can't test it right now)

Unfortunately if I click on an item to edit it and add a new item to the list, the list will be rerendered completely. So all items will be in the "normal" state again.

Asked in this thread, there is a procedural solution, but as a longtime react developer, I would like to know, if there is also a functional solution without saving the todo item state also in the app state.

TypeScript error when using State inside JSX element

When using van_jsx, I noticed that using a state as an element child will cause a TypeScript error:
Type 'State' is not assignable to type 'ComponentChild'.

The issue was solved very simply by adding the Sate type imported from vanjs-core to the ComponentChild type in type.d.ts:
Before:
export type ComponentChild =
| FunctionChild
| Element
| string
| number
| bigint
| boolean
| null
| undefined;

After - this solves the problem:
export type ComponentChild =
| FunctionChild
| Element
| string
| number
| bigint
| boolean
| null
| undefined
| State

Reference comparison

The code uses !== to compare old and new values.
The problem is, if at least one of the values is NaN the condition will always be true

NaN !== 0; // is true
NaN !== NaN; // is also true!

We can fix the problem by using Object.is:

Object.is('0', 0); // false
Object.is(0, -0); // false
Object.is(NaN, 0); // false
Object.is(NaN, NaN); // true

State change debounce and supression

For my understanding, state management in VanJS is event driven. The event is triggered by the state setter and prpagated to all event listeners:

s.listeners.forEach(l => l(v, curV))

For frequent updates this may cause too many calls. A common way to prevent this is a timeout, that prevents retriggering before the timeout has fired.

It may also be useful to have an option to globally disable all events (e.g. during page update or deletion) programmatically with frunctions like van.inhibitUpdate() and van.restoreUpdate().

expose isProps

There is this fragment in tagsNS:

let [props, ...children] = protoOf(args[0] ?? 0) === objProto ? args : [{}, ...args]

and for the sake of possibility of creating semi "custom tags" I would want to get exposed isProps to be able to detect if props are provided as first argument to my function:

let isProps = (o) => protoOf(o ?? 0) === objProto

and then line in tagsNS would look like that:

let [props, ...children] = isProps(args[0]) ? args : [{}, ...args]

If no one using isProps minifier should inline it back, so no size impact.

`van.bind` and state-derived properties support both state and non-state (static) objects

Usecase

For reusable components like that:

const Component = (props: ComponentProps) => ...

it would be desirable for ComponentProps to have shape like that:

interface ComponentProps {
  prop1: string | State<string>
  prop2: number | State<number>
}

That is, while composing a Component object, you can pass in either a static string object, or a state object for string for prop1, and either a static number object, or a state object for number for prop2. In other words, the custom component can either be built statically, or reactively (whose appearance is reactive to underlying state changes).

If the property value is used as the child DOM node or property value of the tag function, everything will work properly. Since both child DOM node and property value can accept both static and state objects. However, when the property value is used in van.bind or state-derived properties, things will be problematic, as both van.bind and state-derived properties can only accept state objects, not static objects as deps argument.

The example below illustrate the problematic situation:

const Button = (color: string | State<string>) => button(
  {style: {deps: [color], f: color => `background-color: ${color};`}},
  "Click me",
)

The code above works when color is a string-typed state object, but doesn't work if color is a string, as state-derived properties don't support static object as values of deps argument.

Solution

Both van.bind and state-derived properties should support both state and static objects as values of deps argument. When a static object is passed in, the behavior would be the same as if a state object whose val never changes.

Workaround before official solution is released

Before the official support is in place, VanJS users are encouraged to use the following workaround:

const stateProto = van.state(0).__proto__

const toState = v => v.__proto__ === stateProto ? v : van.state(v)

const Button = (color: string | State<string>) => button(
  {style: {deps: [toState(color)], f: color => `background-color: ${color};`}},
  "Click me",
)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.