Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VNode.children should allow more range. #17

Open
fabiancook opened this issue Feb 27, 2022 · 6 comments
Open

VNode.children should allow more range. #17

fabiancook opened this issue Feb 27, 2022 · 6 comments

Comments

@fabiancook
Copy link
Contributor

🚀 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.

@fabiancook
Copy link
Contributor Author

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.

@fabiancook
Copy link
Contributor Author

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")

@fabiancook
Copy link
Contributor Author

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.

@fabiancook
Copy link
Contributor Author

fabiancook commented Mar 30, 2022

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

@fabiancook
Copy link
Contributor Author

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.

@fabiancook
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant