diff --git a/package-lock.json b/package-lock.json
index 2a6b94e7..18c2b1fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14891,7 +14891,7 @@
},
"packages/core": {
"name": "@reatom/core",
- "version": "3.9.1",
+ "version": "3.9.2",
"license": "MIT"
},
"packages/core-v1": {
@@ -14952,7 +14952,7 @@
},
"packages/effects": {
"name": "@reatom/effects",
- "version": "3.10.2",
+ "version": "3.11.0",
"license": "MIT",
"dependencies": {
"@reatom/core": "^3.2.0",
@@ -15015,17 +15015,17 @@
},
"packages/framework": {
"name": "@reatom/framework",
- "version": "3.4.56",
+ "version": "3.4.57",
"license": "MIT",
"dependencies": {
"@reatom/async": "^3.16.4",
- "@reatom/core": "^3.9.1",
- "@reatom/effects": "^3.10.2",
+ "@reatom/core": "^3.9.2",
+ "@reatom/effects": "^3.11.0",
"@reatom/hooks": "^3.6.0",
"@reatom/lens": "^3.11.6",
"@reatom/logger": "^3.8.4",
"@reatom/primitives": "^3.7.3",
- "@reatom/utils": "^3.11.0"
+ "@reatom/utils": "^3.11.1"
}
},
"packages/hooks": {
@@ -15156,7 +15156,7 @@
},
"packages/npm-react": {
"name": "@reatom/npm-react",
- "version": "3.10.3",
+ "version": "3.10.4",
"license": "MIT",
"dependencies": {
"@reatom/core": "^3.5.0",
@@ -15283,7 +15283,7 @@
},
"packages/testing": {
"name": "@reatom/testing",
- "version": "3.4.7",
+ "version": "3.4.8",
"license": "MIT",
"dependencies": {
"@reatom/core": "^3.5.0",
@@ -15326,7 +15326,7 @@
},
"packages/utils": {
"name": "@reatom/utils",
- "version": "3.11.0",
+ "version": "3.11.1",
"license": "MIT"
},
"packages/web": {
diff --git a/packages/framework/package.json b/packages/framework/package.json
index 999cd1a8..4e44b50e 100644
--- a/packages/framework/package.json
+++ b/packages/framework/package.json
@@ -1,6 +1,6 @@
{
"name": "@reatom/framework",
- "version": "3.4.56",
+ "version": "3.4.57",
"private": false,
"sideEffects": false,
"description": "Reatom for framework",
@@ -19,13 +19,13 @@
},
"dependencies": {
"@reatom/async": "^3.16.4",
- "@reatom/core": "^3.9.1",
- "@reatom/effects": "^3.10.2",
+ "@reatom/core": "^3.9.2",
+ "@reatom/effects": "^3.11.0",
"@reatom/hooks": "^3.6.0",
"@reatom/lens": "^3.11.6",
"@reatom/logger": "^3.8.4",
"@reatom/primitives": "^3.7.3",
- "@reatom/utils": "^3.11.0"
+ "@reatom/utils": "^3.11.1"
},
"author": "artalar",
"license": "MIT",
diff --git a/packages/jsx/src/index.test.tsx b/packages/jsx/src/index.test.tsx
index 5c65dbf1..b1aa2933 100644
--- a/packages/jsx/src/index.test.tsx
+++ b/packages/jsx/src/index.test.tsx
@@ -77,16 +77,16 @@ it('children updates', setup((ctx, h, hf, mount, parent) => {
mount(parent, element)
- assert.is(element.childNodes.length, 3)
- assert.is(element.childNodes[1]?.textContent, 'foo')
- assert.is(element.childNodes[2], a)
+ assert.is(element.childNodes.length, 5)
+ assert.is(element.childNodes[2]?.textContent, 'foo')
+ assert.is(element.childNodes[4], a)
val(ctx, 'bar')
- assert.is(element.childNodes[1]?.textContent, 'bar')
+ assert.is(element.childNodes[2]?.textContent, 'bar')
- assert.is(element.childNodes[2], a)
+ assert.is(element.childNodes[4], a)
route(ctx, 'b')
- assert.is(element.childNodes[2], b)
+ assert.is(element.childNodes[4], b)
}))
it('dynamic children', setup((ctx, h, hf, mount, parent) => {
@@ -96,14 +96,14 @@ it('dynamic children', setup((ctx, h, hf, mount, parent) => {
mount(parent, element)
- assert.is(element.childNodes.length, 1)
+ assert.is(element.childNodes.length, 2)
children(ctx,
Hello, world!
)
- assert.is(element.childNodes[0]?.textContent, 'Hello, world!')
+ assert.is(element.childNodes[1]?.textContent, 'Hello, world!')
const inner = inner
children(ctx, {inner}
)
- assert.is(element.childNodes[0]?.childNodes[0], inner)
+ assert.is(element.childNodes[1]?.childNodes[0], inner)
const before = atom('before', 'before')
const after = atom('after', 'after')
@@ -159,25 +159,24 @@ it('fragment as child', setup((ctx, h, hf, mount, parent) => {
it('array children', setup((ctx, h, hf, mount, parent) => {
const n = atom(1)
- const list = atom((ctx) => Array.from({ length: ctx.spy(n) }, (_, i) => {i + 1}))
-
- assert.throws(() => {
- mount(
- parent,
-
- {list /* expected TS error */ as any}
-
-
,
- )
- })
+ const list = atom((ctx) => (<>
+ {...Array.from({ length: ctx.spy(n) }, (_, i) => {i + 1})}
+ >))
- const element =
+ const element = (
+
+ )
- assert.is(element.childNodes.length, 1)
+ mount(parent, element)
+
+ assert.is(element.childNodes.length, 3)
assert.is(element.textContent, '1')
n(ctx, 2)
- assert.is(element.childNodes.length, 2)
+ assert.is(element.childNodes.length, 4)
assert.is(element.textContent, '12')
}))
@@ -288,7 +287,7 @@ it('render HTMLElement atom', setup((ctx, h, hf, mount, parent) => {
const element = {htmlAtom}
- assert.is(element.innerHTML, 'div
')
+ assert.is(element.innerHTML, 'div
')
}))
it('render SVGElement atom', setup((ctx, h, hf, mount, parent) => {
@@ -296,7 +295,7 @@ it('render SVGElement atom', setup((ctx, h, hf, mount, parent) => {
const element = {svgAtom}
- assert.is(element.innerHTML, '')
+ assert.is(element.innerHTML, '')
}))
it('custom component', setup((ctx, h, hf, mount, parent) => {
@@ -529,3 +528,22 @@ it('style object update', setup((ctx, h, hf, mount, parent) => {
assert.is(component.getAttribute('style'), 'left: 0px; bottom: 0px;')
}))
+
+it('render updated atom elements', setup((ctx, h, hf, mount, parent) => {
+ const name = 'child'
+ const target = ``
+ const childAtom = atom(<>
+ div
+ p
+ >, name)
+
+ const element = {childAtom}
+ assert.is(element.innerHTML, `${target}div>div
p
`)
+
+ childAtom(ctx, span)
+ assert.is(element.innerHTML, `${target}span>span
`)
+
+ childAtom(ctx, 'text')
+ assert.is(element.innerHTML, `${target}text
`)
+}))
+
diff --git a/packages/jsx/src/index.ts b/packages/jsx/src/index.ts
index 97d42d1e..d44b7db1 100644
--- a/packages/jsx/src/index.ts
+++ b/packages/jsx/src/index.ts
@@ -19,8 +19,8 @@ type DomApis = Pick<
const isSkipped = (value: unknown): value is boolean | '' | null | undefined =>
typeof value === 'boolean' || value === '' || value == null
-let unsubscribesMap = new WeakMap>()
-let unlink = (parent: JSX.Element, un: Unsubscribe) => {
+let unsubscribesMap = new WeakMap>()
+let unlink = (parent: Node, un: Unsubscribe) => {
// check the connection in the next tick
// to give the user (programmer) an ability
// to put the created element in the dom
@@ -140,8 +140,39 @@ export const reatomJsx = (ctx: Ctx, DOM: DomApis = globalThis.window) => {
}
}
+ const walkAtom = (ctx: Ctx, parent: JSX.Element, anAtom: Atom) => {
+ const fragment = DOM.document.createDocumentFragment()
+ const target = DOM.document.createComment(anAtom.__reatom.name ?? '')
+ fragment.append(target)
+
+ let childNodes: ChildNode[] = []
+ const un = ctx.subscribe(anAtom, (v): void => {
+ childNodes.forEach((node) => node.remove())
+
+ if (v instanceof DOM.Node) {
+ childNodes = v instanceof DOM.DocumentFragment ? [...v.childNodes] : [v as ChildNode]
+ target.after(v)
+ } else if (isSkipped(v)) {
+ childNodes = []
+ } else {
+ const node = DOM.document.createTextNode(String(v))
+ childNodes = [node]
+ target.after(node)
+ }
+ })
+
+ if (!unsubscribesMap.get(target)) unsubscribesMap.set(target, [])
+ unlink(target, un)
+
+ parent.append(fragment)
+ }
+
let h = (tag: any, props: Rec, ...children: any[]) => {
- if (tag === hf) return children
+ if (tag === hf) {
+ const fragment = DOM.document.createDocumentFragment()
+ fragment.append(...children)
+ return fragment
+ }
props ??= {}
@@ -209,6 +240,9 @@ export const reatomJsx = (ctx: Ctx, DOM: DomApis = globalThis.window) => {
}
}
+ /**
+ * @todo Explore adding elements to a DocumentFragment before adding them to a Document.
+ */
let walk = (child: JSX.DOMAttributes['children']) => {
if (Array.isArray(child)) {
for (let i = 0; i < child.length; i++) walk(child[i])
@@ -216,49 +250,9 @@ export const reatomJsx = (ctx: Ctx, DOM: DomApis = globalThis.window) => {
if (isLinkedListAtom(child)) {
walkLinkedList(ctx, element, child)
} else if (isAtom(child)) {
- let innerChild = DOM.document.createTextNode('') as ChildNode | DocumentFragment
- let error: any
- var un: undefined | Unsubscribe = ctx.subscribe(child, (v): void => {
- try {
- if (un && !innerChild.isConnected && innerChild instanceof DOM.DocumentFragment === false) {
- un()
- } else {
- throwReatomError(
- Array.isArray(v) && children.length > 1,
- 'array children with other children are not supported',
- )
-
- if (v instanceof DOM.Element) {
- let list = unsubscribesMap.get(v)
- if (!list) unsubscribesMap.set(v, (list = []))
-
- if (un) element.replaceChild(v, innerChild)
- innerChild = v
- } else if (Array.isArray(v)) {
- if (un) element.replaceChildren(...v)
- else {
- const fragment = new DOM.DocumentFragment()
- v.forEach((el) => fragment.append(el))
- innerChild = fragment
- }
- } else {
- // TODO more tests
- innerChild.textContent = isSkipped(v) ? '' : String(v)
- }
- }
- } catch (e) {
- error = e
- }
- })
- if (error) throw error
- unlink(element, un)
- element.appendChild(innerChild)
+ walkAtom(ctx, element, child)
} else if (!isSkipped(child)) {
- element.appendChild(
- isObject(child) && 'nodeType' in child
- ? (child as JSX.Element)
- : DOM.document.createTextNode(String(child)),
- )
+ element.append(child as Node | string)
}
}
}
@@ -270,7 +264,10 @@ export const reatomJsx = (ctx: Ctx, DOM: DomApis = globalThis.window) => {
return element
}
- /** Fragment */
+ /**
+ * Fragment.
+ * @todo Describe a function as a component.
+ */
let hf = () => {}
let mount = (target: Element, child: Element) => {
@@ -283,7 +280,7 @@ export const reatomJsx = (ctx: Ctx, DOM: DomApis = globalThis.window) => {
* @see https://stackoverflow.com/a/64551276
* @note A custom NodeFilter function slows down performance by 1.5 times.
*/
- const walker = DOM.document.createTreeWalker(removedNode, 1)
+ const walker = DOM.document.createTreeWalker(removedNode, 1 | 128)
do {
const node = walker.currentNode as Element
diff --git a/packages/jsx/src/jsx.d.ts b/packages/jsx/src/jsx.d.ts
index 52315a62..32de477e 100644
--- a/packages/jsx/src/jsx.d.ts
+++ b/packages/jsx/src/jsx.d.ts
@@ -19,6 +19,7 @@ type ElementsAttributesAtomMaybe> = {
export namespace JSX {
type Element = HTMLElement | SVGElement
+ /** @todo Try replacing `Node | Element` with `ChildNode`. */
type ElementPrimitiveChildren = Node | Element | (string & {}) | number | boolean | null | undefined
type ElementChildren =