@vanijs/vani 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +300 -21
- package/README.md +1 -0
- package/dist/lib/index.d.mts +3 -2
- package/dist/lib/index.mjs +2 -1
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/jsx-dev-runtime.d.mts +1 -1
- package/dist/lib/jsx-dev-runtime.mjs +1 -1
- package/dist/lib/jsx-runtime-BK1MMitM.d.mts +287 -0
- package/dist/lib/jsx-runtime-BUSOLzm1.mjs +2 -0
- package/dist/lib/jsx-runtime-BUSOLzm1.mjs.map +1 -0
- package/dist/lib/jsx-runtime.d.mts +1 -26
- package/dist/lib/jsx-runtime.mjs +1 -1
- package/package.json +67 -38
- package/dist/lib/runtime-Cx9SKHrc.d.mts +0 -190
- package/dist/lib/runtime-X8xzmXJz.mjs +0 -1
package/DOCS.md
CHANGED
|
@@ -226,6 +226,8 @@ export function MyReactComponent() {
|
|
|
226
226
|
- Optional fine-grained state with `signal()`, `derive()`, `effect()`, plus `text()`/`attr()`
|
|
227
227
|
helpers
|
|
228
228
|
- Async components with fallbacks
|
|
229
|
+
- `onMount(getNodes, parent)` gives access to the rendered DOM subtree without ref plumbing,
|
|
230
|
+
aligning with explicit updates and simplifying vanilla JS integrations
|
|
229
231
|
- `className` accepts string, array, and object forms for ergonomic composition
|
|
230
232
|
- ESM-first and designed to run in any modern environment
|
|
231
233
|
|
|
@@ -386,6 +388,237 @@ Each component owns a DOM range delimited by anchors:
|
|
|
386
388
|
|
|
387
389
|
Updates replace only the DOM between anchors.
|
|
388
390
|
|
|
391
|
+
### 3.1) Nested component hierarchies (isolated subtrees)
|
|
392
|
+
|
|
393
|
+
To build a nested tree, have a parent return child component instances as part of its render output.
|
|
394
|
+
Each component instance creates its own anchor range, so parent and child updates stay isolated.
|
|
395
|
+
|
|
396
|
+
#### How DOM isolation works
|
|
397
|
+
|
|
398
|
+
When you nest components, each component gets its own pair of `<!--vani:start-->` and
|
|
399
|
+
`<!--vani:end-->` comment anchors. The DOM structure for a parent containing a child looks like:
|
|
400
|
+
|
|
401
|
+
```html
|
|
402
|
+
<!--vani:start-->
|
|
403
|
+
<!-- Parent start -->
|
|
404
|
+
<div>
|
|
405
|
+
<div>Parent title</div>
|
|
406
|
+
<button>Rename parent</button>
|
|
407
|
+
<!--vani:start-->
|
|
408
|
+
<!-- Child start -->
|
|
409
|
+
<div>
|
|
410
|
+
Child clicks: 0
|
|
411
|
+
<button>Click child</button>
|
|
412
|
+
</div>
|
|
413
|
+
<!--vani:end-->
|
|
414
|
+
<!-- Child end -->
|
|
415
|
+
</div>
|
|
416
|
+
<!--vani:end-->
|
|
417
|
+
<!-- Parent end -->
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
When the parent updates, Vani replaces the DOM between the parent's anchors **but preserves the
|
|
421
|
+
child's anchors and their contents**. When the child updates, only the DOM between the child's
|
|
422
|
+
anchors is replaced. This anchor-based isolation is automatic—no special API is needed.
|
|
423
|
+
|
|
424
|
+
#### Update isolation rules
|
|
425
|
+
|
|
426
|
+
- **Parent update**: replaces parent’s subtree but leaves nested child anchor ranges intact.
|
|
427
|
+
- **Child update**: replaces only the child’s subtree; parent is unaffected.
|
|
428
|
+
- **Re-render with new props**: if the parent re-renders and returns the same child component with
|
|
429
|
+
new props, the child’s component instance is preserved (not recreated) and the child can read the
|
|
430
|
+
new props on its next `update()`.
|
|
431
|
+
|
|
432
|
+
#### Basic parent-child example
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
import { component, div, button, type Handle, type ComponentRef } from '@vanijs/vani'
|
|
436
|
+
|
|
437
|
+
const Child = component<{ label: string }>((props, handle: Handle) => {
|
|
438
|
+
let clicks = 0
|
|
439
|
+
return () =>
|
|
440
|
+
div(
|
|
441
|
+
`${props.label} clicks: ${clicks}`,
|
|
442
|
+
button(
|
|
443
|
+
{
|
|
444
|
+
onclick: () => {
|
|
445
|
+
clicks += 1
|
|
446
|
+
handle.update()
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
'Click child',
|
|
450
|
+
),
|
|
451
|
+
)
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
const Parent = component((_, handle: Handle) => {
|
|
455
|
+
let title = 'Parent'
|
|
456
|
+
const childRef: ComponentRef = { current: null }
|
|
457
|
+
|
|
458
|
+
const rename = () => {
|
|
459
|
+
title = title === 'Parent' ? 'Parent (renamed)' : 'Parent'
|
|
460
|
+
handle.update()
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return () =>
|
|
464
|
+
div(
|
|
465
|
+
div(`Title: ${title}`),
|
|
466
|
+
button({ onclick: rename }, 'Rename parent'),
|
|
467
|
+
// Nested component subtree (isolated updates)
|
|
468
|
+
Child({ ref: childRef, label: 'Child' }),
|
|
469
|
+
)
|
|
470
|
+
})
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
When you click "Rename parent", only the parent subtree updates. When you click "Click child", only
|
|
474
|
+
the child subtree updates.
|
|
475
|
+
|
|
476
|
+
#### Deeper nesting (grandparent → parent → child)
|
|
477
|
+
|
|
478
|
+
You can nest components to any depth. Each level maintains its own isolated subtree:
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
import { component, div, button, type Handle } from '@vanijs/vani'
|
|
482
|
+
|
|
483
|
+
const GrandChild = component<{ name: string }>((props, handle: Handle) => {
|
|
484
|
+
let value = 0
|
|
485
|
+
return () =>
|
|
486
|
+
div(
|
|
487
|
+
`GrandChild (${props.name}): ${value}`,
|
|
488
|
+
button(
|
|
489
|
+
{
|
|
490
|
+
onclick: () => {
|
|
491
|
+
value += 1
|
|
492
|
+
handle.update()
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
'+',
|
|
496
|
+
),
|
|
497
|
+
)
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const Child = component<{ id: number }>((props, handle: Handle) => {
|
|
501
|
+
let label = `Child #${props.id}`
|
|
502
|
+
return () =>
|
|
503
|
+
div(
|
|
504
|
+
div(label),
|
|
505
|
+
button(
|
|
506
|
+
{
|
|
507
|
+
onclick: () => {
|
|
508
|
+
label += '!'
|
|
509
|
+
handle.update()
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
'Edit label',
|
|
513
|
+
),
|
|
514
|
+
GrandChild({ name: `gc-${props.id}` }),
|
|
515
|
+
)
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
const Parent = component((_, handle: Handle) => {
|
|
519
|
+
let count = 2
|
|
520
|
+
return () =>
|
|
521
|
+
div(
|
|
522
|
+
div(`Parent has ${count} children`),
|
|
523
|
+
button(
|
|
524
|
+
{
|
|
525
|
+
onclick: () => {
|
|
526
|
+
count += 1
|
|
527
|
+
handle.update()
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
'Add child',
|
|
531
|
+
),
|
|
532
|
+
...Array.from({ length: count }, (_, i) => Child({ id: i })),
|
|
533
|
+
)
|
|
534
|
+
})
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Each `GrandChild` can update independently of its `Child`, and each `Child` can update independently
|
|
538
|
+
of the `Parent`. The anchor isolation means you can have deeply nested trees without cascading
|
|
539
|
+
re-renders.
|
|
540
|
+
|
|
541
|
+
#### Passing props and callbacks (prop drilling)
|
|
542
|
+
|
|
543
|
+
Props flow down explicitly. If a child needs data from an ancestor, pass it through props:
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
import { component, div, button, type Handle } from '@vanijs/vani'
|
|
547
|
+
|
|
548
|
+
const Display = component<{ value: number }>((props) => {
|
|
549
|
+
return () => div(`Current value: ${props.value}`)
|
|
550
|
+
})
|
|
551
|
+
|
|
552
|
+
const Controls = component<{ onIncrement: () => void }>((props) => {
|
|
553
|
+
return () => button({ onclick: props.onIncrement }, 'Increment')
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
const App = component((_, handle: Handle) => {
|
|
557
|
+
let count = 0
|
|
558
|
+
const increment = () => {
|
|
559
|
+
count += 1
|
|
560
|
+
handle.update()
|
|
561
|
+
}
|
|
562
|
+
return () => div(Display({ value: count }), Controls({ onIncrement: increment }))
|
|
563
|
+
})
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
When `increment` is called, the `App` re-renders. Because `Display` and `Controls` are nested
|
|
567
|
+
components with their own anchor ranges, their internal state (if any) is preserved. The new `value`
|
|
568
|
+
prop is available to `Display` on the next render.
|
|
569
|
+
|
|
570
|
+
#### Updating children from the parent via refs
|
|
571
|
+
|
|
572
|
+
If you need to trigger a child update without re-rendering the parent, store a `ComponentRef` and
|
|
573
|
+
call `update()` on it directly:
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
import { component, div, button, type Handle, type ComponentRef } from '@vanijs/vani'
|
|
577
|
+
|
|
578
|
+
const Counter = component<{ start: number }>((props, handle: Handle) => {
|
|
579
|
+
let count = props.start
|
|
580
|
+
return () =>
|
|
581
|
+
div(
|
|
582
|
+
`Count: ${count}`,
|
|
583
|
+
button(
|
|
584
|
+
{
|
|
585
|
+
onclick: () => {
|
|
586
|
+
count += 1
|
|
587
|
+
handle.update()
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
'+',
|
|
591
|
+
),
|
|
592
|
+
)
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
const Dashboard = component(() => {
|
|
596
|
+
const counterRef: ComponentRef = { current: null }
|
|
597
|
+
|
|
598
|
+
const resetCounter = () => {
|
|
599
|
+
// Update only the Counter subtree, not the Dashboard
|
|
600
|
+
counterRef.current?.update()
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return () =>
|
|
604
|
+
div(
|
|
605
|
+
Counter({ ref: counterRef, start: 0 }),
|
|
606
|
+
button({ onclick: resetCounter }, 'Refresh counter'),
|
|
607
|
+
)
|
|
608
|
+
})
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
Clicking "Refresh counter" calls `counterRef.current?.update()`, which re-renders only the `Counter`
|
|
612
|
+
subtree. The `Dashboard` itself does not re-render.
|
|
613
|
+
|
|
614
|
+
#### Summary
|
|
615
|
+
|
|
616
|
+
- Nesting is standard component composition—no special API.
|
|
617
|
+
- Each component instance owns an anchor-delimited DOM range.
|
|
618
|
+
- Updates are isolated: a component’s `handle.update()` only affects its own subtree.
|
|
619
|
+
- Props and callbacks flow down explicitly (prop drilling).
|
|
620
|
+
- Use `ComponentRef` to update children without re-rendering parents.
|
|
621
|
+
|
|
389
622
|
### 4) Lists and item-level updates
|
|
390
623
|
|
|
391
624
|
Lists scale well in Vani when each item is its own component. Every item owns a tiny subtree and can
|
|
@@ -496,7 +729,7 @@ const List = component((_, handle: Handle) => {
|
|
|
496
729
|
),
|
|
497
730
|
)
|
|
498
731
|
}
|
|
499
|
-
handle.
|
|
732
|
+
handle.onBeforeMount(() => {
|
|
500
733
|
queueMicrotask(renderRows)
|
|
501
734
|
})
|
|
502
735
|
|
|
@@ -855,7 +1088,7 @@ import { component, div, button, type Handle } from '@vanijs/vani'
|
|
|
855
1088
|
import { getState, setState, subscribe } from './store'
|
|
856
1089
|
|
|
857
1090
|
const Counter = component((_, handle: Handle) => {
|
|
858
|
-
handle.
|
|
1091
|
+
handle.onBeforeMount(() => subscribe(() => handle.update()))
|
|
859
1092
|
|
|
860
1093
|
return () => {
|
|
861
1094
|
const { count } = getState()
|
|
@@ -939,7 +1172,7 @@ by never mutating another module's state directly; instead call exported command
|
|
|
939
1172
|
scale, manual invalidation challenges include fan-out, over- or under-invalidating, update
|
|
940
1173
|
ordering/races, stale reads during transitions, lifecycle leaks, and lack of observability. Mitigate
|
|
941
1174
|
with explicit contracts, centralized command surfaces, predictable ordering, cleanup via
|
|
942
|
-
`handle.
|
|
1175
|
+
`handle.onBeforeMount()`, and lightweight logging around invalidation helpers.
|
|
943
1176
|
|
|
944
1177
|
Architecture sketch:
|
|
945
1178
|
|
|
@@ -967,8 +1200,8 @@ Each module owns its state and exposes a small API:
|
|
|
967
1200
|
|
|
968
1201
|
2. View adapters (bind handles)
|
|
969
1202
|
|
|
970
|
-
Views subscribe once via `handle.
|
|
971
|
-
This keeps invalidation scoped to the subtree that owns the handle.
|
|
1203
|
+
Views subscribe once via `handle.onBeforeMount()` and call `handle.update()` when their module
|
|
1204
|
+
notifies. This keeps invalidation scoped to the subtree that owns the handle.
|
|
972
1205
|
|
|
973
1206
|
3. Coordinator or message hub
|
|
974
1207
|
|
|
@@ -1037,7 +1270,7 @@ export const createUsersFeature = (): { api: UsersFeatureApi; View: Component }
|
|
|
1037
1270
|
}
|
|
1038
1271
|
|
|
1039
1272
|
const View = component((_, handle: Handle) => {
|
|
1040
|
-
handle.
|
|
1273
|
+
handle.onBeforeMount(() => api.subscribe(() => handle.update()))
|
|
1041
1274
|
return () => div(api.getUsers().map((user) => div(user.name)))
|
|
1042
1275
|
})
|
|
1043
1276
|
|
|
@@ -1109,7 +1342,7 @@ export const invalidateUserRow = (id: string) => {
|
|
|
1109
1342
|
}
|
|
1110
1343
|
|
|
1111
1344
|
export const UserRow = component<{ id: string; name: string }>((props, handle) => {
|
|
1112
|
-
handle.
|
|
1345
|
+
handle.onBeforeMount(() => bindUserRow(props.id, handle))
|
|
1113
1346
|
return () => div(props.name)
|
|
1114
1347
|
})
|
|
1115
1348
|
```
|
|
@@ -1158,7 +1391,7 @@ export const queueUpdate = (handle: Handle) => {
|
|
|
1158
1391
|
- Stale reads during transitions: if you defer with `startTransition()`, ensure that the render
|
|
1159
1392
|
reads the latest snapshot or that the transition captures the intended version.
|
|
1160
1393
|
- Lifecycle leaks: if a handle isn't unsubscribed, updates keep firing. Always return cleanup from
|
|
1161
|
-
`subscribe()` and bind it through `handle.
|
|
1394
|
+
`subscribe()` and bind it through `handle.onBeforeMount()`.
|
|
1162
1395
|
- Observability gaps: without implicit reactivity, you need traceability. Wrap invalidation helpers
|
|
1163
1396
|
to log or count updates per module and catch runaway loops early.
|
|
1164
1397
|
|
|
@@ -1178,7 +1411,7 @@ Creates a component factory. The `fn` receives `props` and a `handle`.
|
|
|
1178
1411
|
import { component, div, type Handle } from '@vanijs/vani'
|
|
1179
1412
|
|
|
1180
1413
|
const Card = component<{ title: string }>((props, handle: Handle) => {
|
|
1181
|
-
handle.
|
|
1414
|
+
handle.onBeforeMount(() => {
|
|
1182
1415
|
console.log('Mounted:', props.title)
|
|
1183
1416
|
})
|
|
1184
1417
|
|
|
@@ -1189,11 +1422,10 @@ const Card = component<{ title: string }>((props, handle: Handle) => {
|
|
|
1189
1422
|
Components can return other component instances directly:
|
|
1190
1423
|
|
|
1191
1424
|
```ts
|
|
1192
|
-
import { component } from '@vanijs/vani'
|
|
1193
|
-
import * as h from '@vanijs/vani'
|
|
1425
|
+
import { component, h1 } from '@vanijs/vani'
|
|
1194
1426
|
|
|
1195
1427
|
const Hero = component(() => {
|
|
1196
|
-
return () =>
|
|
1428
|
+
return () => h1('Hello')
|
|
1197
1429
|
})
|
|
1198
1430
|
|
|
1199
1431
|
const Page = component(() => {
|
|
@@ -1227,6 +1459,9 @@ const handles = hydrateToDOM(App(), root)
|
|
|
1227
1459
|
handles.forEach((handle) => handle.update())
|
|
1228
1460
|
```
|
|
1229
1461
|
|
|
1462
|
+
If hydration fails due to missing anchors or mismatched structure, Vani raises a `HydrationError`
|
|
1463
|
+
and `hydrateToDOM()` logs it. Other errors are rethrown so they surface immediately.
|
|
1464
|
+
|
|
1230
1465
|
### `renderToString(components)`
|
|
1231
1466
|
|
|
1232
1467
|
Server‑side render to HTML with anchors. Accepts a single component or an array of components.
|
|
@@ -1275,7 +1510,8 @@ div(span('Label'), input({ type: 'text' }), button({ onclick: () => {} }, 'Submi
|
|
|
1275
1510
|
### SVG icons (Lucide)
|
|
1276
1511
|
|
|
1277
1512
|
Use the Vite SVG plugin at `src/ecosystem/vite-plugin-vani-svg.ts` and import SVGs with `?vani`.
|
|
1278
|
-
This keeps the bundle small by only including the icons you actually import.
|
|
1513
|
+
This keeps the bundle small by only including the icons you actually import. Invalid SVG strings
|
|
1514
|
+
throw an error so failures stay obvious.
|
|
1279
1515
|
|
|
1280
1516
|
In your `vite.config.ts`:
|
|
1281
1517
|
|
|
@@ -1336,21 +1572,22 @@ const Parent = component(() => {
|
|
|
1336
1572
|
})
|
|
1337
1573
|
```
|
|
1338
1574
|
|
|
1339
|
-
### Cleanup and
|
|
1575
|
+
### Cleanup and lifecycle
|
|
1340
1576
|
|
|
1341
|
-
|
|
1577
|
+
The `onBeforeMount` hook runs once during component setup (before the first render) and can return a
|
|
1578
|
+
cleanup function.
|
|
1342
1579
|
|
|
1343
|
-
If you plan to use vani for a SSR/SSG application, you should use
|
|
1344
|
-
such as accessing the window object, accessing the DOM, etc.
|
|
1580
|
+
If you plan to use vani for a SSR/SSG application, you should use `onBeforeMount` to run client-only
|
|
1581
|
+
code such as accessing the window object, accessing the DOM, etc.
|
|
1345
1582
|
|
|
1346
|
-
|
|
1347
|
-
not re-run on every `handle.update()`; updates only call the render function.
|
|
1583
|
+
The `onBeforeMount` hook is very simple and runs once during component setup (the component function
|
|
1584
|
+
run). It does not re-run on every `handle.update()`; updates only call the render function.
|
|
1348
1585
|
|
|
1349
1586
|
```ts
|
|
1350
1587
|
import { component, div } from '@vanijs/vani'
|
|
1351
1588
|
|
|
1352
1589
|
const Timer = component((_, handle) => {
|
|
1353
|
-
handle.
|
|
1590
|
+
handle.onBeforeMount(() => {
|
|
1354
1591
|
const id = setInterval(() => console.log('tick'), 1000)
|
|
1355
1592
|
return () => clearInterval(id)
|
|
1356
1593
|
})
|
|
@@ -1358,6 +1595,48 @@ const Timer = component((_, handle) => {
|
|
|
1358
1595
|
})
|
|
1359
1596
|
```
|
|
1360
1597
|
|
|
1598
|
+
The `onMount` hook runs after the first render, once the component's nodes are in the DOM. It
|
|
1599
|
+
receives a lazy `getNodes()` function and the parent mount point.
|
|
1600
|
+
|
|
1601
|
+
Benefits:
|
|
1602
|
+
|
|
1603
|
+
- No need to set up refs ahead of time just to access nodes after render.
|
|
1604
|
+
- Easy to initialize external, vanilla JS libraries with all rendered nodes in one place.
|
|
1605
|
+
- `getNodes()` is lazy, so you only pay for DOM traversal if you actually need it.
|
|
1606
|
+
|
|
1607
|
+
```ts
|
|
1608
|
+
import { component, div } from '@vanijs/vani'
|
|
1609
|
+
|
|
1610
|
+
const Measure = component((_, handle) => {
|
|
1611
|
+
handle.onMount((getNodes, parent) => {
|
|
1612
|
+
const nodes = getNodes()
|
|
1613
|
+
const firstElement = nodes.find((node) => node instanceof HTMLElement)
|
|
1614
|
+
if (!firstElement) return
|
|
1615
|
+
|
|
1616
|
+
const rect = (firstElement as HTMLElement).getBoundingClientRect()
|
|
1617
|
+
console.log('Mounted in', parent, 'size', rect.width, rect.height)
|
|
1618
|
+
})
|
|
1619
|
+
|
|
1620
|
+
return () => div('Measured')
|
|
1621
|
+
})
|
|
1622
|
+
```
|
|
1623
|
+
|
|
1624
|
+
You can also register cleanup functions directly with `handle.onCleanup()`:
|
|
1625
|
+
|
|
1626
|
+
```ts
|
|
1627
|
+
import { component, div } from '@vanijs/vani'
|
|
1628
|
+
|
|
1629
|
+
const Subscription = component((_, handle) => {
|
|
1630
|
+
const unsubscribe = someStore.subscribe(() => handle.update())
|
|
1631
|
+
handle.onCleanup(unsubscribe)
|
|
1632
|
+
|
|
1633
|
+
return () => div('Subscribed')
|
|
1634
|
+
})
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
Both patterns are equivalent. Use `handle.onBeforeMount()` when the setup and cleanup are logically
|
|
1638
|
+
grouped, and `handle.onCleanup()` when you need to register cleanup separately from initialization.
|
|
1639
|
+
|
|
1361
1640
|
### Signals and DOM bindings (optional)
|
|
1362
1641
|
|
|
1363
1642
|
Signals are opt-in fine-grained state. They update only the DOM nodes bound to them.
|
|
@@ -1379,7 +1658,7 @@ const Counter = component(() => {
|
|
|
1379
1658
|
- `derive(fn)` returns a computed getter.
|
|
1380
1659
|
- `effect(fn)` re-runs when signals used inside `fn` change.
|
|
1381
1660
|
- `text(getter)` binds a text node to a signal.
|
|
1382
|
-
- `attr(el, name,
|
|
1661
|
+
- `attr(el, name, value)` binds an attribute/class to a signal getter or static value.
|
|
1383
1662
|
|
|
1384
1663
|
### Partial attribute updates
|
|
1385
1664
|
|
package/README.md
CHANGED
package/dist/lib/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as isComponentInstance, B as RenderMode, C as batch, D as el, E as createRoot, F as renderToDOM, G as setRenderMode, H as configureSignalDom, I as startTransition, L as withRenderMode, M as mount, N as reactive, O as fragment, P as renderKeyedChildren, R as AttrValue, S as VNode, T as component, U as getRenderMode, V as TextNode, W as getSignalDomAdapter, _ as Root, b as UpdateOptions, c as ComponentInstance, d as DomRef, f as ElementProps, g as RenderFn, h as HydrationError, j as isDevMode, k as hydrateToDOM, l as ComponentRef, m as HtmlProps, o as Component, p as Handle, s as ComponentInput, t as Fragment, u as DataAttribute, v as SSRNode, w as classNames, x as VChild, y as SvgProps, z as ClassName } from "./jsx-runtime-BK1MMitM.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/vani/html.d.ts
|
|
4
4
|
declare const div: (propsOrChild?: VChild | HtmlProps<"div">, ...children: VChild[]) => VNode;
|
|
@@ -110,4 +110,5 @@ type SvgRenderOptions = {
|
|
|
110
110
|
};
|
|
111
111
|
declare const renderSvgString: (svg: string, options?: SvgRenderOptions) => VNode;
|
|
112
112
|
//#endregion
|
|
113
|
-
export { ClassName, Component, ComponentInput, ComponentInstance, ComponentRef, DataAttribute, DomRef, ElementProps, Handle, HtmlProps,
|
|
113
|
+
export { AttrValue, ClassName, Component, ComponentInput, ComponentInstance, ComponentRef, DataAttribute, DomRef, ElementProps, Fragment, Handle, HtmlProps, HydrationError, RenderFn, RenderMode, Root, SSRNode, SvgProps, SvgRenderOptions, TextNode, UpdateOptions, VChild, VNode, a, abbr, article, aside, audio, batch, blockquote, br, button, caption, circle, cite, classNames, clipPath, code, col, colgroup, component, configureSignalDom, createRoot, datalist, dd, defs, details, dfn, div, dl, dt, el, ellipse, em, embed, fieldset, figcaption, figure, footer, form, fragment, g, getRenderMode, getSignalDomAdapter, h1, h2, h3, h4, h5, h6, header, hr, hydrateToDOM, iframe, img, input, isComponentInstance, isDevMode, kbd, label, legend, li, line, linearGradient, main, mark, mask, meter, mount, nav, noscript, ol, optgroup, option, output, p, path, pattern, picture, polygon, polyline, pre, progress, radialGradient, reactive, rect, renderKeyedChildren, renderSvgString, renderToDOM, renderToString, samp, script, section, select, setRenderMode, slot, small, source, span, startTransition, stop, strong, style, summary, svg, table, tbody, td, template, textarea, tfoot, th, thead, time, tr, ul, use, var_, video, withRenderMode };
|
|
114
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/lib/index.mjs
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{C as e,S as t,_ as n,a as r,b as i,c as a,d as o,f as s,g as c,h as ee,l as te,m as l,o as ne,p as u,s as re,t as ie,u as d,v as ae,w as oe,x as se,y as f}from"./jsx-runtime-BUSOLzm1.mjs";function p(e){return(t,...n)=>d(e,t,...n)}const m=p(`div`),h=p(`span`),g=p(`ul`),_=p(`li`),v=p(`ol`),y=p(`dl`),b=p(`dt`),x=p(`dd`),S=p(`main`),C=p(`header`),w=p(`footer`),T=p(`section`),E=p(`article`),D=p(`aside`),O=p(`nav`),k=p(`details`),A=p(`summary`),j=p(`a`),M=p(`button`),N=p(`input`),P=p(`output`),F=p(`textarea`),I=p(`select`),L=p(`option`),R=p(`optgroup`),z=p(`label`),B=p(`form`),V=p(`progress`),H=p(`meter`),U=p(`fieldset`),W=p(`legend`),G=p(`datalist`),K=p(`figure`),ce=p(`figcaption`),le=p(`img`),ue=p(`picture`),de=p(`source`),fe=p(`video`),pe=p(`audio`),me=p(`iframe`),he=p(`embed`),ge=p(`time`),_e=p(`mark`),ve=p(`p`),ye=p(`h1`),be=p(`h2`),xe=p(`h3`),Se=p(`h4`),Ce=p(`h5`),we=p(`h6`),Te=p(`code`),Ee=p(`pre`),De=p(`blockquote`),Oe=p(`var`),ke=p(`kbd`),Ae=p(`samp`),je=p(`cite`),Me=p(`dfn`),Ne=p(`abbr`),Pe=p(`small`),Fe=p(`strong`),Ie=p(`em`),Le=p(`br`),Re=p(`hr`),ze=p(`table`),Be=p(`caption`),Ve=p(`colgroup`),He=p(`col`),Ue=p(`tbody`),We=p(`thead`),Ge=p(`tfoot`),Ke=p(`tr`),qe=p(`td`),Je=p(`th`),Ye=p(`style`),Xe=p(`script`),Ze=p(`noscript`),Qe=p(`template`),$e=p(`slot`),et=p(`svg`),tt=p(`g`),nt=p(`path`),rt=p(`circle`),it=p(`rect`),at=p(`line`),ot=p(`polyline`),st=p(`polygon`),ct=p(`ellipse`),lt=p(`defs`),ut=p(`clipPath`),dt=p(`mask`),ft=p(`pattern`),pt=p(`linearGradient`),mt=p(`radialGradient`),ht=p(`stop`),gt=p(`use`);function _t(e){return Array.isArray(e)?e:[e]}const vt=new Set([`area`,`base`,`br`,`col`,`embed`,`hr`,`img`,`input`,`link`,`meta`,`param`,`source`,`track`,`wbr`]);function yt(){return{update(){},updateSync(){},dispose(){},onCleanup(){},onBeforeMount(){},onMount(){}}}function bt(e){return typeof e==`function`?{$$vani:`component`,component:e,props:{}}:e}function q(e){return e.replaceAll(`&`,`&`).replaceAll(`<`,`<`).replaceAll(`>`,`>`).replaceAll(`"`,`"`).replaceAll(`'`,`'`)}function xt(e){let t=[];for(let n of Object.keys(e)){let r=e[n];r==null||r===!1||n.startsWith(`on`)&&typeof r==`function`||(r===!0?t.push(n):t.push(`${n}="${q(String(r))}"`))}return t.length>0?` ${t.join(` `)}`:``}function J(e){if(e==null||e===!1)return{type:`fragment`,children:[]};if(typeof e==`string`||typeof e==`number`)return{type:`text`,text:String(e)};if(u(e))return{type:`component`,instance:e};if(typeof e==`object`&&`type`in e)return e;throw Error(`[vani] SSR received a DOM node. This is not supported.`)}async function St(e){let t=`<!--vani:start-->`,n=`<!--vani:end-->`;if(e.clientOnly){let r=e.props?.fallback;return r?`${t}${await Y(J(r()))}${n}`:`${t}${n}`}let r=e.component(e.props,yt());return`${t}${await Y(J((r instanceof Promise?await r:r)()))}${n}`}async function Y(e){switch(e.type){case`text`:return q(e.text);case`comment`:return`<!--${e.text}-->`;case`fragment`:return(await Promise.all(e.children.map(Y))).join(``);case`component`:return St(e.instance);case`element`:{let t=xt(e.props);if(vt.has(e.tag))return`<${e.tag}${t}>`;let n=(await Promise.all(e.children.map(Y))).join(``);return`<${e.tag}${t}>${n}</${e.tag}>`}}}async function X(e){return i(`ssr`,async()=>{if(t()!==`ssr`)throw Error(`[vani] renderToString failed to set SSR render mode.`);let n=_t(e).map(e=>({type:`component`,instance:bt(e)}));return(await Promise.all(n.map(Y))).join(``)})}const Z=new Map,Q=(e,t)=>{if(!t)return e;let n=`${e??``} ${t}`.trim();return n.length>0?n:void 0},Ct=(e,t)=>{if(!t)return;let n=t.size;if(n!=null&&(e.setAttribute(`width`,String(n)),e.setAttribute(`height`,String(n))),t.className){let n=Q(e.getAttribute(`class`)??void 0,t.className);n&&e.setAttribute(`class`,n)}if(t.attributes){for(let[n,r]of Object.entries(t.attributes))if(!(r==null||r===!1)){if(r===!0){e.setAttribute(n,``);continue}e.setAttribute(n,String(r))}}},wt=e=>{let t={},n=/([^\s=]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g,r;for(;r=n.exec(e);){let e=r[1],n=r[2]??r[3]??r[4];t[e]=n===void 0?!0:n}return t},Tt=e=>{let t=e.replaceAll(/<!--[\s\S]*?-->/g,``).trim(),n=/<\/?[^>]+>/g,r=[],i=null,a=0,o,s=e=>{let t=r.at(-1);t&&(t.type===`element`||t.type===`fragment`)&&t.children.push(e)};for(;o=n.exec(t);){let e=t.slice(a,o.index);e.trim().length>0&&s({type:`text`,text:e});let c=o[0];if(c.startsWith(`</`))r.pop();else{let e=c.endsWith(`/>`),t=c.slice(1,e?-2:-1).trim(),n=t.search(/\s/),a={type:`element`,tag:n===-1?t:t.slice(0,n),props:wt(n===-1?``:t.slice(n+1)),children:[]};i==null?i=a:s(a),e||r.push(a)}a=n.lastIndex}return i??{type:`fragment`,children:[]}},$=e=>e.type===`text`?{type:`text`,text:e.text}:e.type===`comment`?{type:`comment`,text:e.text}:e.type===`fragment`?{type:`fragment`,children:e.children.map($)}:e.type===`component`?{type:`component`,instance:e.instance}:{type:`element`,tag:e.tag,props:{...e.props},children:e.children.map($)},Et=(e,t)=>{if(!t||e.type!==`element`||e.tag!==`svg`)return;let n=e.props;if(t.size!=null&&(n.width=String(t.size),n.height=String(t.size)),t.className){let e=Q(n.class,t.className);e&&(n.class=e)}if(t.attributes){for(let[e,r]of Object.entries(t.attributes))if(!(r==null||r===!1)){if(r===!0){n[e]=!0;continue}n[e]=String(r)}}},Dt=(e,n)=>{if(t()===`ssr`){let t=$(Tt(e));return Et(t,n),t}let r=Z.get(e);if(!r){let t=new DOMParser().parseFromString(e,`image/svg+xml`);if(t.documentElement.nodeName===`parsererror`?t.documentElement:t.querySelector(`parsererror`))throw Error(`[vani] invalid SVG string`);r=t.documentElement,Z.set(e,r)}let i=r.cloneNode(!0);return Ct(i,n),i};export{ie as Fragment,r as HydrationError,j as a,Ne as abbr,E as article,D as aside,pe as audio,ne as batch,De as blockquote,Le as br,M as button,Be as caption,rt as circle,je as cite,re as classNames,ut as clipPath,Te as code,He as col,Ve as colgroup,a as component,se as configureSignalDom,te as createRoot,G as datalist,x as dd,lt as defs,k as details,Me as dfn,m as div,y as dl,b as dt,d as el,ct as ellipse,Ie as em,he as embed,U as fieldset,ce as figcaption,K as figure,w as footer,B as form,o as fragment,tt as g,t as getRenderMode,e as getSignalDomAdapter,ye as h1,be as h2,xe as h3,Se as h4,Ce as h5,we as h6,C as header,Re as hr,s as hydrateToDOM,me as iframe,le as img,N as input,u as isComponentInstance,l as isDevMode,ke as kbd,z as label,W as legend,_ as li,at as line,pt as linearGradient,S as main,_e as mark,dt as mask,H as meter,ee as mount,O as nav,Ze as noscript,v as ol,R as optgroup,L as option,P as output,ve as p,nt as path,ft as pattern,ue as picture,st as polygon,ot as polyline,Ee as pre,V as progress,mt as radialGradient,c as reactive,it as rect,n as renderKeyedChildren,Dt as renderSvgString,ae as renderToDOM,X as renderToString,Ae as samp,Xe as script,T as section,I as select,oe as setRenderMode,$e as slot,Pe as small,de as source,h as span,f as startTransition,ht as stop,Fe as strong,Ye as style,A as summary,et as svg,ze as table,Ue as tbody,qe as td,Qe as template,F as textarea,Ge as tfoot,Je as th,We as thead,ge as time,Ke as tr,g as ul,gt as use,Oe as var_,fe as video,i as withRenderMode};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vani/html.ts","../../src/vani/ssr.ts","../../src/vani/svg.ts"],"sourcesContent":["// ─────────────────────────────────────────────\n// HTML element helpers\n// ─────────────────────────────────────────────\n\nimport { el, type ElementProps, type VChild } from './runtime'\n\ntype ElementTagName = keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap\n\nfunction createElementFn<E extends ElementTagName>(tag: E) {\n return (propsOrChild?: ElementProps<E> | VChild | null, ...children: VChild[]) =>\n el(tag, propsOrChild, ...children)\n}\n\n// Basic and semantic elements\nexport const div = createElementFn('div')\nexport const span = createElementFn('span')\nexport const ul = createElementFn('ul')\nexport const li = createElementFn('li')\nexport const ol = createElementFn('ol')\nexport const dl = createElementFn('dl')\nexport const dt = createElementFn('dt')\nexport const dd = createElementFn('dd')\nexport const main = createElementFn('main')\nexport const header = createElementFn('header')\nexport const footer = createElementFn('footer')\nexport const section = createElementFn('section')\nexport const article = createElementFn('article')\nexport const aside = createElementFn('aside')\nexport const nav = createElementFn('nav')\n\n// Interactive elements\nexport const details = createElementFn('details')\nexport const summary = createElementFn('summary')\nexport const a = createElementFn('a')\nexport const button = createElementFn('button')\nexport const input = createElementFn('input')\nexport const output = createElementFn('output')\nexport const textarea = createElementFn('textarea')\nexport const select = createElementFn('select')\nexport const option = createElementFn('option')\nexport const optgroup = createElementFn('optgroup')\nexport const label = createElementFn('label')\nexport const form = createElementFn('form')\nexport const progress = createElementFn('progress')\nexport const meter = createElementFn('meter')\nexport const fieldset = createElementFn('fieldset')\nexport const legend = createElementFn('legend')\nexport const datalist = createElementFn('datalist')\n\n// Media elements\nexport const figure = createElementFn('figure')\nexport const figcaption = createElementFn('figcaption')\nexport const img = createElementFn('img')\nexport const picture = createElementFn('picture')\nexport const source = createElementFn('source')\nexport const video = createElementFn('video')\nexport const audio = createElementFn('audio')\nexport const iframe = createElementFn('iframe')\nexport const embed = createElementFn('embed')\n\n// Prose elements\nexport const time = createElementFn('time')\nexport const mark = createElementFn('mark')\nexport const p = createElementFn('p')\nexport const h1 = createElementFn('h1')\nexport const h2 = createElementFn('h2')\nexport const h3 = createElementFn('h3')\nexport const h4 = createElementFn('h4')\nexport const h5 = createElementFn('h5')\nexport const h6 = createElementFn('h6')\nexport const code = createElementFn('code')\nexport const pre = createElementFn('pre')\nexport const blockquote = createElementFn('blockquote')\nexport const var_ = createElementFn('var')\nexport const kbd = createElementFn('kbd')\nexport const samp = createElementFn('samp')\nexport const cite = createElementFn('cite')\nexport const dfn = createElementFn('dfn')\nexport const abbr = createElementFn('abbr')\nexport const small = createElementFn('small')\nexport const strong = createElementFn('strong')\nexport const em = createElementFn('em')\nexport const br = createElementFn('br')\nexport const hr = createElementFn('hr')\n\n// Tables\nexport const table = createElementFn('table')\nexport const caption = createElementFn('caption')\nexport const colgroup = createElementFn('colgroup')\nexport const col = createElementFn('col')\nexport const tbody = createElementFn('tbody')\nexport const thead = createElementFn('thead')\nexport const tfoot = createElementFn('tfoot')\nexport const tr = createElementFn('tr')\nexport const td = createElementFn('td')\nexport const th = createElementFn('th')\n\n// Scripting elements\nexport const style = createElementFn('style')\nexport const script = createElementFn('script')\nexport const noscript = createElementFn('noscript')\nexport const template = createElementFn('template')\nexport const slot = createElementFn('slot')\n\n// SVG elements\nexport const svg = createElementFn('svg')\nexport const g = createElementFn('g')\nexport const path = createElementFn('path')\nexport const circle = createElementFn('circle')\nexport const rect = createElementFn('rect')\nexport const line = createElementFn('line')\nexport const polyline = createElementFn('polyline')\nexport const polygon = createElementFn('polygon')\nexport const ellipse = createElementFn('ellipse')\nexport const defs = createElementFn('defs')\nexport const clipPath = createElementFn('clipPath')\nexport const mask = createElementFn('mask')\nexport const pattern = createElementFn('pattern')\nexport const linearGradient = createElementFn('linearGradient')\nexport const radialGradient = createElementFn('radialGradient')\nexport const stop = createElementFn('stop')\nexport const use = createElementFn('use')\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable unicorn/no-array-callback-reference */\n\nimport { getRenderMode } from './common'\nimport {\n isComponentInstance,\n withRenderMode,\n type Component,\n type ComponentInstance,\n type Handle,\n type RenderFn,\n type SSRNode,\n type VChild,\n} from './runtime'\n\ntype Renderable = Component<any> | ComponentInstance<any>\n\nfunction normalizeRenderables(input: Renderable | Renderable[]): Renderable[] {\n return Array.isArray(input) ? input : [input]\n}\n\nconst voidElements = new Set([\n 'area',\n 'base',\n 'br',\n 'col',\n 'embed',\n 'hr',\n 'img',\n 'input',\n 'link',\n 'meta',\n 'param',\n 'source',\n 'track',\n 'wbr',\n])\n\nfunction createSsrHandle(): Handle {\n return {\n update() {},\n updateSync() {},\n dispose() {},\n onCleanup() {},\n onBeforeMount() {},\n onMount() {},\n }\n}\n\nfunction normalizeComponent(comp: Renderable): ComponentInstance<any> {\n if (typeof comp === 'function') {\n return {\n $$vani: 'component',\n component: comp,\n props: {},\n }\n }\n return comp\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''')\n}\n\nfunction serializeProps(props: Record<string, any>): string {\n const parts: string[] = []\n for (const key of Object.keys(props)) {\n const value = props[key]\n if (value == null || value === false) continue\n if (key.startsWith('on') && typeof value === 'function') continue\n\n if (value === true) {\n parts.push(key)\n } else {\n parts.push(`${key}=\"${escapeHtml(String(value))}\"`)\n }\n }\n\n return parts.length > 0 ? ` ${parts.join(' ')}` : ''\n}\n\nfunction toSsrNode(node: VChild): SSRNode {\n if (node == null || node === false) {\n return { type: 'fragment', children: [] }\n }\n\n if (typeof node === 'string' || typeof node === 'number') {\n return { type: 'text', text: String(node) }\n }\n\n if (isComponentInstance(node)) {\n return { type: 'component', instance: node }\n }\n\n if (typeof node === 'object' && 'type' in node) {\n return node as SSRNode\n }\n\n throw new Error('[vani] SSR received a DOM node. This is not supported.')\n}\n\nasync function renderComponent(instance: ComponentInstance<any>): Promise<string> {\n const start = '<!--vani:start-->'\n const end = '<!--vani:end-->'\n\n if (instance.clientOnly) {\n const fallback = (instance.props as any)?.fallback as RenderFn | undefined\n if (!fallback) {\n return `${start}${end}`\n }\n const fallbackNode = toSsrNode(fallback())\n return `${start}${await serializeNode(fallbackNode)}${end}`\n }\n\n const result = instance.component(instance.props, createSsrHandle())\n const renderFn = result instanceof Promise ? await result : result\n const node = toSsrNode(renderFn())\n return `${start}${await serializeNode(node)}${end}`\n}\n\nasync function serializeNode(node: SSRNode): Promise<string> {\n switch (node.type) {\n case 'text':\n return escapeHtml(node.text)\n case 'comment':\n return `<!--${node.text}-->`\n case 'fragment':\n return (await Promise.all(node.children.map(serializeNode))).join('')\n case 'component':\n return renderComponent(node.instance)\n case 'element': {\n const attrs = serializeProps(node.props)\n if (voidElements.has(node.tag)) {\n return `<${node.tag}${attrs}>`\n }\n const children = (await Promise.all(node.children.map(serializeNode))).join('')\n return `<${node.tag}${attrs}>${children}</${node.tag}>`\n }\n }\n}\n\nexport async function renderToString(components: Renderable | Renderable[]): Promise<string> {\n return withRenderMode('ssr', async () => {\n if (getRenderMode() !== 'ssr') {\n throw new Error('[vani] renderToString failed to set SSR render mode.')\n }\n\n const normalized = normalizeRenderables(components)\n const nodes: SSRNode[] = normalized.map((component) => ({\n type: 'component',\n instance: normalizeComponent(component),\n }))\n\n const rendered = await Promise.all(nodes.map(serializeNode))\n return rendered.join('')\n })\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable unicorn/no-array-callback-reference */\n\nimport { getRenderMode } from './common'\nimport { type SSRNode, type SvgProps, type VNode } from './runtime'\n\nexport type SvgRenderOptions = {\n size?: number\n className?: string\n attributes?: SvgProps\n}\n\nconst svgStringCache = new Map<string, SVGSVGElement>()\n\nconst mergeClassValue = (base: string | undefined, extra: string | undefined) => {\n if (!extra) return base\n const merged = `${base ?? ''} ${extra}`.trim()\n return merged.length > 0 ? merged : undefined\n}\n\nconst applySvgOverridesToNode = (node: SVGSVGElement, options?: SvgRenderOptions) => {\n if (!options) return\n const size = options.size\n if (size != null) {\n node.setAttribute('width', String(size))\n node.setAttribute('height', String(size))\n }\n if (options.className) {\n const merged = mergeClassValue(node.getAttribute('class') ?? undefined, options.className)\n if (merged) {\n node.setAttribute('class', merged)\n }\n }\n if (options.attributes) {\n for (const [key, value] of Object.entries(options.attributes)) {\n if (value == null || value === false) continue\n if (value === true) {\n node.setAttribute(key, '')\n continue\n }\n node.setAttribute(key, String(value))\n }\n }\n}\n\nconst parseSvgAttributes = (input: string) => {\n const attrs: Record<string, string | boolean> = {}\n const attrRegex = /([^\\s=]+)(?:=(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'>]+)))?/g\n let match: RegExpExecArray | null\n while ((match = attrRegex.exec(input))) {\n const key = match[1]\n const value = match[2] ?? match[3] ?? match[4]\n attrs[key] = value === undefined ? true : value\n }\n return attrs\n}\n\nconst parseSvgToSsrNode = (svg: string): SSRNode => {\n const cleaned = svg.replaceAll(/<!--[\\s\\S]*?-->/g, '').trim()\n const tagRegex = /<\\/?[^>]+>/g\n const stack: Array<SSRNode> = []\n let root: SSRNode | null = null\n let lastIndex = 0\n let match: RegExpExecArray | null\n\n const appendChild = (child: SSRNode) => {\n const parent = stack.at(-1)\n if (parent && (parent.type === 'element' || parent.type === 'fragment')) {\n parent.children.push(child)\n }\n }\n\n while ((match = tagRegex.exec(cleaned))) {\n const text = cleaned.slice(lastIndex, match.index)\n if (text.trim().length > 0) {\n appendChild({ type: 'text', text })\n }\n\n const token = match[0]\n if (token.startsWith('</')) {\n stack.pop()\n } else {\n const selfClosing = token.endsWith('/>')\n const inside = token.slice(1, selfClosing ? -2 : -1).trim()\n const spaceIndex = inside.search(/\\s/)\n const tag = spaceIndex === -1 ? inside : inside.slice(0, spaceIndex)\n const attrString = spaceIndex === -1 ? '' : inside.slice(spaceIndex + 1)\n const props = parseSvgAttributes(attrString)\n const node: SSRNode = { type: 'element', tag, props, children: [] }\n\n if (root == null) {\n root = node\n } else {\n appendChild(node)\n }\n\n if (!selfClosing) {\n stack.push(node)\n }\n }\n\n lastIndex = tagRegex.lastIndex\n }\n\n return root ?? { type: 'fragment', children: [] }\n}\n\nconst cloneSsrNode = (node: SSRNode): SSRNode => {\n if (node.type === 'text') {\n return { type: 'text', text: node.text }\n }\n if (node.type === 'comment') {\n return { type: 'comment', text: node.text }\n }\n if (node.type === 'fragment') {\n return { type: 'fragment', children: node.children.map(cloneSsrNode) }\n }\n if (node.type === 'component') {\n return { type: 'component', instance: node.instance }\n }\n return {\n type: 'element',\n tag: node.tag,\n props: { ...node.props },\n children: node.children.map(cloneSsrNode),\n }\n}\n\nconst applySvgOverridesToSsr = (node: SSRNode, options?: SvgRenderOptions) => {\n if (!options || node.type !== 'element' || node.tag !== 'svg') return\n const props = node.props as Record<string, any>\n if (options.size != null) {\n props.width = String(options.size)\n props.height = String(options.size)\n }\n if (options.className) {\n const merged = mergeClassValue(props.class as string | undefined, options.className)\n if (merged) {\n props.class = merged\n }\n }\n if (options.attributes) {\n for (const [key, value] of Object.entries(options.attributes)) {\n if (value == null || value === false) continue\n if (value === true) {\n props[key] = true\n continue\n }\n props[key] = String(value)\n }\n }\n}\n\nexport const renderSvgString = (svg: string, options?: SvgRenderOptions): VNode => {\n if (getRenderMode() === 'ssr') {\n const parsed = cloneSsrNode(parseSvgToSsrNode(svg))\n applySvgOverridesToSsr(parsed, options)\n return parsed\n }\n\n let base = svgStringCache.get(svg)\n if (!base) {\n const doc = new DOMParser().parseFromString(svg, 'image/svg+xml')\n const parserError =\n doc.documentElement.nodeName === 'parsererror'\n ? doc.documentElement\n : doc.querySelector('parsererror')\n if (parserError) {\n throw new Error('[vani] invalid SVG string')\n }\n base = doc.documentElement as unknown as SVGSVGElement\n svgStringCache.set(svg, base)\n }\n const node = base.cloneNode(true) as SVGSVGElement\n applySvgOverridesToNode(node, options)\n return node\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as jsxs, i as jsxDEV, r as jsx, t as Fragment } from "./jsx-runtime-BK1MMitM.mjs";
|
|
2
2
|
export { Fragment, jsx, jsxDEV, jsxs };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import{i as e,n as t,r as n,t as r}from"./jsx-runtime-BUSOLzm1.mjs";export{r as Fragment,t as jsx,n as jsxDEV,e as jsxs};
|