Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/core/src/core.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import parseChildren from "./parseChildren"
import transforms, { R2WCType } from "./transforms"
import { toDashedCase } from "./utils"

Expand All @@ -7,6 +8,7 @@ type PropNames<Props> = Array<PropName<Props>>
export interface R2WCOptions<Props> {
shadow?: "open" | "closed"
props?: PropNames<Props> | Partial<Record<PropName<Props>, R2WCType>>
experimentalChildren?: boolean
}

export interface R2WCRenderer<Props, Context> {
Expand All @@ -21,6 +23,7 @@ export interface R2WCRenderer<Props, Context> {

export interface R2WCBaseProps {
container?: HTMLElement
children?: React.ReactNode
}

const renderSymbol = Symbol.for("r2wc.render")
Expand Down Expand Up @@ -98,6 +101,10 @@ export default function r2wc<Props extends R2WCBaseProps, Context>(
this[propsSymbol][prop] = transform.parse(value, attribute, this)
}
}

if (options.experimentalChildren) {
this[propsSymbol].children = parseChildren(this.childNodes)
}
}

connectedCallback() {
Expand Down
76 changes: 76 additions & 0 deletions packages/core/src/parseChildren.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from "react"

import { toCamelCase } from "./utils"

const ELEMENT_NODE = 1
const TEXT_NODE = 3
const COMMENT_NODE = 8

export default function parseChildren(
input: NodeListOf<ChildNode>,
): React.ReactNode[] {
const output: React.ReactNode[] = []

for (let index = 0; index < input.length; index++) {
const child = input[index]
output.push(parseChild(child, index))
}

return output
}

function parseChild(
input: ChildNode,
index: number,
): React.ReactNode | undefined {
if (input.nodeType === TEXT_NODE || input.nodeType === COMMENT_NODE) {
return input.nodeValue
}

if (input.nodeType === ELEMENT_NODE) {
const node = input as HTMLElement
const nodeName = node.localName
const children = parseChildren(node.childNodes)

const props: Record<string, unknown> = { key: index }
for (let { name, value } of node.attributes) {
if (name === "class") name = "class-name"
if (name === "for") name = "html-for"
if (name === "colspan") name = "colSpan"
if (name === "rowspan") name = "rowSpan"
if (nodeName === "input" && name === "value") name = "default-value"
if (nodeName === "input" && name === "checked") name = "default-checked"

if (!name.startsWith("data-")) name = toCamelCase(name)

if (name === "style") {
const input = value
.split(";")
.filter((value) => value.length > 0)
.map((value) =>
value
.trim()
.split(":")
.map((value) => value.trim()),
)

const styles = {}
for (const [key, value] of input) {
const camelKey = toCamelCase(key)

// @ts-ignore
styles[camelKey] = value
}

// @ts-ignore
value = styles
}

props[name] = value
}

return React.createElement(nodeName, props, ...children)
}

return undefined
}