Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
✨ svg support
  • Loading branch information
Mortaro committed Sep 1, 2025
commit 73df30ccd507347b65b9e46a1dd39ee6cb7c728d
9 changes: 4 additions & 5 deletions client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { anchorableElement } from './anchorableNode'
import { generateCallback, generateSubject } from './events'
import { ref } from './ref'

export default function render(node, options) {
export default function render(node, isSvg = false) {
if (isFalse(node) || node.type === 'head') {
node.element = document.createComment('')
return node.element
Expand All @@ -16,9 +16,8 @@ export default function render(node, options) {
return node.element
}

const svg = (options && options.svg) || node.type === 'svg'

if (svg) {
isSvg = isSvg || node.type === 'svg'
if (isSvg) {
node.element = document.createElementNS('http://www.w3.org/2000/svg', node.type)
} else {
node.element = document.createElement(node.type)
Expand Down Expand Up @@ -58,7 +57,7 @@ export default function render(node, options) {

if (!node.attributes.html) {
for (let i = 0; i < node.children.length; i++) {
const child = render(node.children[i], { svg })
const child = render(node.children[i], isSvg)
node.element.appendChild(child)
}

Expand Down
14 changes: 8 additions & 6 deletions client/rerender.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,18 @@ function updateHeadChildren(currentChildren, nextChildren) {
}
}

function _rerender(current, next) {
function _rerender(current, next, isParentSvg = false) {
const selector = current.element
next.element = current.element

if (isFalse(current) && isFalse(next)) {
return
}

const isSvg = isParentSvg || next.type === 'svg'

if (current.type !== next.type) {
const nextSelector = render(next)
const nextSelector = render(next, isSvg)
selector.replaceWith(nextSelector)
return
}
Expand All @@ -132,22 +134,22 @@ function _rerender(current, next) {
const limit = Math.max(current.children.length, next.children.length)
if (next.children.length > current.children.length) {
for (let i = 0; i < current.children.length; i++) {
_rerender(current.children[i], next.children[i])
_rerender(current.children[i], next.children[i], isSvg)
}
for (let i = current.children.length; i < next.children.length; i++) {
const nextSelector = render(next.children[i])
const nextSelector = render(next.children[i], isSvg)
selector.appendChild(nextSelector)
}
} else if (current.children.length > next.children.length) {
for (let i = 0; i < next.children.length; i++) {
_rerender(current.children[i], next.children[i])
_rerender(current.children[i], next.children[i], isSvg)
}
for (let i = current.children.length - 1; i >= next.children.length; i--) {
selector.childNodes[i].remove()
}
} else {
for (let i = limit - 1; i > -1; i--) {
_rerender(current.children[i], next.children[i])
_rerender(current.children[i], next.children[i], isSvg)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion shared/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ export function isFunction(node) {
}

export function isText(node) {
return node.type === 'text'
return node.type === 'text' && node.attributes === undefined
}
2 changes: 2 additions & 0 deletions tests/src/Application.njs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import LazyComponentLoader from './LazyComponentLoader'
import NestedFolder from './nested/NestedFolder'
import ChildComponentWithoutServerFunctions from './ChildComponentWithoutServerFunctions'
import ObjectEventScope from './ObjectEventScope'
import SvgSupport from './SvgSupport.njs'
import './Application.css'

class Application extends Nullstack {
Expand Down Expand Up @@ -156,6 +157,7 @@ class Application extends Nullstack {
<LazyComponent route="/lazy-importer" prop="works" />
<ChildComponentWithoutServerFunctions route="/child-component-without-server-functions" />
<ObjectEventScope route="/object-event-scope" />
<SvgSupport route="/svg-support" />
<ErrorPage route="*" />
</body>
)
Expand Down
46 changes: 46 additions & 0 deletions tests/src/SvgSupport.njs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Nullstack from 'nullstack';

function Close({ size }) {
return (
<svg width={size} height={size} viewBox="0 0 482 482">
<path d="M124 124L358 358" stroke="#000" stroke-width="70.2055" stroke-linecap="round" stroke-linejoin="round" />
<path d="M358 124L124 358" stroke="#000" stroke-width="70.2055" stroke-linecap="round" stroke-linejoin="round" />
</svg>
)
}

function Hamburger({ size }) {
return (
<svg width={size} height={size} viewBox="0 0 482 482">
<path d="M92.5 150H386.5" stroke="#000" stroke-width="42" stroke-linecap="round" stroke-linejoin="round" />
<path d="M92.5 241H386.5" stroke="#000" stroke-width="42" stroke-linecap="round" stroke-linejoin="round" />
<path d="M92.5 332H386.5" stroke="#000" stroke-width="42" stroke-linecap="round" stroke-linejoin="round" />
</svg>
)
}

class SvgSupport extends Nullstack {

open = false
visible = false

render() {
return (
<div>
<svg viewBox="0 0 240 80" xmlns="http://www.w3.org/2000/svg">
<text x="20" y="35" class="small">I</text>
<text x="40" y="35" class="heavy">love</text>
<text x="55" y="55" class="small">my</text>
<text x="60" y="55" class="tiny">cat!</text>
</svg>
{this.open ? <Close size={30} /> : <Hamburger size={30} />}
<button onclick={{open: !this.open}}> toggle </button>
{this.visible && <Hamburger size={69} />}
<button onclick={{visible: !this.visible}}> show </button>
</div>
)
}

}

export default SvgSupport;
22 changes: 22 additions & 0 deletions tests/src/SvgSupport.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
describe('SvgSupport', () => {
beforeEach(async () => {
await page.goto('http://localhost:6969/svg-support')
await page.waitForSelector('[data-hydrated]')
})

test('svg can render text', async () => {
expect(true).toBeTruthy()
})

test('svg can add new paths while rerendering', async () => {
expect(true).toBeTruthy()
})

test('svg can render in short circuit statements', async () => {
expect(true).toBeTruthy()
})

test('svg can render in ternary statements', async () => {
expect(true).toBeTruthy()
})
})
Loading