Skip to content

Commit cc9264a

Browse files
committed
feat: implement Hono JSX wrapper
1 parent cb25e30 commit cc9264a

File tree

9 files changed

+475
-1
lines changed

9 files changed

+475
-1
lines changed

packages/frameworks/hono-jsx/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"@zag-js/utils": "workspace:*"
3232
},
3333
"devDependencies": {
34-
"hono": "4.8.5"
34+
"hono": "4.8.5",
35+
"clean-package": "2.2.0"
3536
},
3637
"peerDependencies": {
3738
"hono": ">=4.8.0"
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { Bindable, BindableParams } from "@zag-js/core"
2+
import { identity, isFunction } from "@zag-js/utils"
3+
import { useEffect, useRef, useState } from "hono/jsx"
4+
import { flushSync } from "hono/jsx/dom"
5+
import { useSafeLayoutEffect } from "./use-layout-effect"
6+
7+
export function useBindable<T>(props: () => BindableParams<T>): Bindable<T> {
8+
const initial = props().value ?? props().defaultValue
9+
10+
const eq = props().isEqual ?? Object.is
11+
12+
const [initialValue] = useState(initial)
13+
const [value, setValue] = useState(initialValue)
14+
15+
const controlled = props().value !== undefined
16+
17+
const valueRef = useRef(value)
18+
valueRef.current = controlled ? props().value : value
19+
20+
const prevValue = useRef(valueRef.current)
21+
useSafeLayoutEffect(() => {
22+
prevValue.current = valueRef.current
23+
}, [value, props().value])
24+
25+
const setFn = (value: T | ((prev: T) => T)) => {
26+
const prev = prevValue.current === null ? undefined : prevValue.current
27+
const next = isFunction(value) ? value(prev as T) : value
28+
29+
if (props().debug) {
30+
console.log(`[bindable > ${props().debug}] setValue`, { next, prev })
31+
}
32+
33+
if (!controlled) setValue(next)
34+
if (!eq(next, prev)) {
35+
props().onChange?.(next, prev)
36+
}
37+
}
38+
39+
function get(): T {
40+
return (controlled ? props().value : value) as T
41+
}
42+
43+
return {
44+
initial: initialValue,
45+
ref: valueRef,
46+
get,
47+
set(value: T | ((prev: T) => T)) {
48+
const exec = props().sync ? flushSync : identity
49+
exec(() => setFn(value))
50+
},
51+
invoke(nextValue: T, prevValue: T) {
52+
props().onChange?.(nextValue, prevValue)
53+
},
54+
hash(value: T) {
55+
return props().hash?.(value) ?? String(value)
56+
},
57+
}
58+
}
59+
60+
useBindable.cleanup = (fn: VoidFunction) => {
61+
useEffect(() => fn, [])
62+
}
63+
64+
useBindable.ref = <T>(defaultValue: T) => {
65+
const value = useRef(defaultValue)
66+
return {
67+
get: () => value.current as T,
68+
set: (next: T) => {
69+
value.current = next
70+
},
71+
}
72+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { mergeProps } from "@zag-js/core"
2+
export * from "./machine"
3+
export * from "./normalize-props"
4+
export * from "./portal"

0 commit comments

Comments
 (0)