virtualstate/x

VNode.children should allow more range.

Opened this issue ยท 6 comments

๐Ÿš€ Feature Proposal

Update the type signature of VNode to

type VNodeChild = string | number | boolean | undefined | VNode | Iterable<VNodeChild>;

interface VNodeChildrenContainer {
  children: AsyncIterable<VNodeChild> | Iterable<VNodeChild>
}

This opens up compatibility, an introduces "scalar" values as immediately represented values with no abstraction.

I believe this would also reduce the requirement to understand why we might want to wrap these (which was only to maintain a type definition, which may be easier to do now!)

Motivation

Currently children is transformed to maintain a consistent reading pattern, if read functions are provided through #16 there is less of a reason to enforce that children must be wrapped nodes for no gain.

Example

const node = {
  children: [1, 2, "hello", { children: [ 3, 4, ] }, [ 5, 6 ] ]
}
const node = {
  children: {
    async *[Symbol.asyncIterator]() {
      yield 1;
      yield "hello";
      yield [1, 2, "hello", { children: [ 3, 4, ] }, [ 5, 6 ] ];
   } 
  }
}

These native representations should require zero transformation from the implementation of @virtualstate/fringe, and instead now can be directly read using read functions instead.

Read functions should include a function that creates a consistent node with a strict type, however it should not be needed in the majority of cases.

An example of where this is used is @virtualstate/kdl where the representation does not need to be modified to be used with a generic KDL query.

The input & output of the above, with no h function:

{
    source: "name",
    options: {
        attribute: "value",
        value: 1
    },
    children: [
        {
            type: "main",
            children: [
                {
                    $$type: "section",
                    props: {
                        id: "main-section"
                    },
                    children: {
                        async *[Symbol.asyncIterator]() {
                            yield [
                                {
                                    type: "h1",
                                    children: [
                                        "hello",
                                        "world"
                                    ]
                                },
                                "whats up"
                            ]
                        }
                    }
                }
            ]
        }
    ]
}
name attribute="value" value=1 {
  main {
    section "whats up" id="main-section" {
      h1 "hello" "world"
    }
  }
}

This tree could then be queried

section[prop(id) = "main-section"] h1

Result:

h1 "hello" "world"

To maintain complete consistency, the current h/f functions defined by @virtualstate/fringe would return directly an earlier mentioned generic node, with its enumerable keys set to the same as what fringe uses now, these keys are only visual, and should instead be shifted to symbols, e.g. Symbol.for(":jsx/children") & Symbol.for(":jsx/type")

https://github.com/virtualstate/focus is an implementation that supports any (within the defined keys, open a PR if you know
of another common key for an object property) h definition

allowing for

https://github.com/virtualstate/focus/blob/d9bec26b3a8c0dff4f9c4257c9bbf25574062113/src/tests/access.tsx#L15-L54

const multiTree = {
  source: "name",
  options: {
    attribute: "value",
    value: 1,
  },
  children: [
    {
      type: "main",
      children: [
        {
          $$type: "section",
          props: {
            id: "main-section",
          },
          children: {
            async *[Symbol.asyncIterator]() {
              yield [
                {
                  type: "h1",
                  children: ["hello", "world"],
                },
                "whats up",
              ];
            },
          },
        },
        <footer id="foot">Footer content</footer>,
        proxyH("test-proxy", { id: "test-proxy" }, "test"),
        staticH("test-static", { id: "test-static" }, "test"),
        staticH(
          async function* Component(props: unknown) {
            yield `component 1 ${JSON.stringify({ props })}`;
          },
          { id: "component" }
        ),
      ],
    },
  ],
};

This is generic jsx, the input defines how the output will look like.

This completely drops the concept of scalar in the focus implementation, however this can be mapped using an implementation of h that specifically maps these.

fringe will be:

import { h as f, ProxyContext, children as fChildren, isStaticChildNode } from "@virtualstate/focus";

/* some children implementation that maps scalar */
async function *children(node: unknown) {
  for await (const snapshot of fChildren(node)) {
    yield snapshot.map(source => isStaticChildNode(value) ? scalar(source): source)
  }
}

function scalar(value: unknown) {
  return f(value, { ...options, [ProxyContext]: { scalar() { return true } } });
}

export function h(source?: unknown, options?: Record<string | symbol, unknown>, ...children: unknown[]) {
  return f(source, { ...options, [ProxyContext]: { children } }, ...children);
}

Once this has been verified working, semver minor can happen with a full replacement, focus will become included into this repo

After minor, h exported by x will be from focus instead of fringe, this will then be a semver major.

Examples referencing scalars will be dropped

It may be beneficial for the options of ProxyContext to be able to define enumerable keys, allowing for scoping of the keys checked internally to focus, with x, this will be ignored.

ProxyContext also allows for instance + component, if instance is used, the returned value is used as the returned object from h, you can use symbols defined by focus to https://github.com/virtualstate/focus/blob/d9bec26b3a8c0dff4f9c4257c9bbf25574062113/src/access.ts#L40 reference specific jsx concepts in a more defined way, if component is used, globals can be used to create a communication bridge for implementations.