@@ -17,18 +17,19 @@ import (
1717
1818type Component [P any ] func (props P ) * VDomElem
1919
20+ // Key returns the key property of the VDomElem as a string.
21+ // Returns an empty string if the key is not set, otherwise converts the key value to string using fmt.Sprint.
2022func (e * VDomElem ) Key () string {
2123 keyVal , ok := e .Props [KeyPropKey ]
2224 if ! ok {
2325 return ""
2426 }
25- keyStr , ok := keyVal .(string )
26- if ok {
27- return keyStr
28- }
29- return ""
27+ return fmt .Sprint (keyVal )
3028}
3129
30+ // WithKey sets the key property of the VDomElem and returns the element.
31+ // This is particularly useful for defined components since their prop types won't include keys.
32+ // Returns nil if the element is nil, otherwise returns the same element for chaining.
3233func (e * VDomElem ) WithKey (key string ) * VDomElem {
3334 if e == nil {
3435 return nil
@@ -40,26 +41,67 @@ func (e *VDomElem) WithKey(key string) *VDomElem {
4041 return e
4142}
4243
43- func TextElem (text string ) VDomElem {
44+ func textElem (text string ) VDomElem {
4445 return VDomElem {Tag : TextTag , Text : text }
4546}
4647
48+ func partToClasses (class any ) []string {
49+ if class == nil {
50+ return nil
51+ }
52+ switch c := class .(type ) {
53+ case string :
54+ if c != "" {
55+ return []string {c }
56+ }
57+ case []string :
58+ var parts []string
59+ for _ , s := range c {
60+ if s != "" {
61+ parts = append (parts , s )
62+ }
63+ }
64+ return parts
65+ case map [string ]bool :
66+ var parts []string
67+ for k , v := range c {
68+ if v && k != "" {
69+ parts = append (parts , k )
70+ }
71+ }
72+ return parts
73+ case []any :
74+ var parts []string
75+ for _ , item := range c {
76+ parts = append (parts , partToClasses (item )... )
77+ }
78+ return parts
79+ }
80+ return nil
81+ }
82+
83+ // Classes combines multiple class values into a single space-separated string.
84+ // Similar to the JavaScript clsx library, it accepts:
85+ // - strings: added directly if non-empty
86+ // - nil: ignored (useful for vdom.If() statements)
87+ // - []string: all non-empty strings are added
88+ // - map[string]bool: keys with true values are added
89+ // - []any: recursively processed
90+ //
91+ // Returns a space-separated string of all valid class names.
4792func Classes (classes ... any ) string {
4893 var parts []string
4994 for _ , class := range classes {
50- switch c := class .(type ) {
51- case nil :
52- continue
53- case string :
54- if c != "" {
55- parts = append (parts , c )
56- }
57- }
58- // Ignore any other types
95+ parts = append (parts , partToClasses (class )... )
5996 }
6097 return strings .Join (parts , " " )
6198}
6299
100+ // H creates a VDomElem with the specified tag, properties, and children.
101+ // This is the primary function for creating virtual DOM elements.
102+ // Children can be strings, VDomElems, *VDomElem, slices, booleans, numeric types,
103+ // or other types which are converted to strings using fmt.Sprint.
104+ // nil children are allowed and removed from the final list.
63105func H (tag string , props map [string ]any , children ... any ) * VDomElem {
64106 rtn := & VDomElem {Tag : tag , Props : props }
65107 if len (children ) > 0 {
@@ -71,20 +113,27 @@ func H(tag string, props map[string]any, children ...any) *VDomElem {
71113 return rtn
72114}
73115
116+ // If returns the provided part if the condition is true, otherwise returns nil.
117+ // This is useful for conditional rendering in VDOM children lists, props, and style attributes.
74118func If (cond bool , part any ) any {
75119 if cond {
76120 return part
77121 }
78122 return nil
79123}
80124
125+ // IfElse returns part if the condition is true, otherwise returns elsePart.
126+ // This provides ternary-like conditional logic for VDOM children, props, and attributes.
127+ // Accepts mixed types - part and elsePart don't need to be the same type, which is especially useful for children.
81128func IfElse (cond bool , part any , elsePart any ) any {
82129 if cond {
83130 return part
84131 }
85132 return elsePart
86133}
87134
135+ // Ternary returns trueRtn if the condition is true, otherwise returns falseRtn.
136+ // Unlike IfElse, this enforces type safety by requiring both return values to be the same type T.
88137func Ternary [T any ](cond bool , trueRtn T , falseRtn T ) T {
89138 if cond {
90139 return trueRtn
@@ -93,6 +142,8 @@ func Ternary[T any](cond bool, trueRtn T, falseRtn T) T {
93142 }
94143}
95144
145+ // ForEach applies a function to each item in a slice and returns a slice of results.
146+ // The function receives the item and its index, and can return any type for flexible VDOM generation.
96147func ForEach [T any ](items []T , fn func (T , int ) any ) []any {
97148 elems := make ([]any , len (items ))
98149 for idx , item := range items {
@@ -102,14 +153,11 @@ func ForEach[T any](items []T, fn func(T, int) any) []any {
102153 return elems
103154}
104155
105- func Props (props any ) map [string ]any {
106- m , err := util .StructToMap (props )
107- if err != nil {
108- return nil
109- }
110- return m
111- }
112-
156+ // UseState is the tsunami analog to React's useState hook.
157+ // It provides persistent state management within a VDOM component, returning the current
158+ // state value, a setter function, and an updater function.
159+ // Setting a new value causes a re-render of the component.
160+ // This hook must be called within a component context.
113161func UseState [T any ](ctx context.Context , initialVal T ) (T , func (T ), func (func (T ) T )) {
114162 rc := vdomctx .GetRenderContext (ctx )
115163 if rc == nil {
@@ -178,18 +226,35 @@ func useAtom[T any](ctx context.Context, hookName string, atomName string) (T, f
178226 return atomVal , typedSetVal , typedSetFuncVal
179227}
180228
229+ // UseSharedAtom provides access to a shared atom state across components.
230+ // It returns the current atom value, a setter function, and an updater function.
231+ // Setting a new value causes a re-render of any component using this atom.
232+ // This hook must be called within a component context.
181233func UseSharedAtom [T any ](ctx context.Context , atomName string ) (T , func (T ), func (func (T ) T )) {
182234 return useAtom [T ](ctx , "UseSharedAtom" , "$shared." + atomName )
183235}
184236
237+ // UseConfig provides access to config values (atom names are global across the app).
238+ // It returns the current config value, a setter function, and an updater function.
239+ // Setting a new value causes a re-render of all components using this config atom.
240+ // This hook must be called within a component context.
185241func UseConfig [T any ](ctx context.Context , atomName string ) (T , func (T ), func (func (T ) T )) {
186242 return useAtom [T ](ctx , "UseConfig" , "$config." + atomName )
187243}
188244
245+ // UseData provides access to data values (atom names are global across the app).
246+ // It returns the current data value, a setter function, and an updater function.
247+ // Setting a new value causes a re-render of all components using this data atom.
248+ // This hook must be called within a component context.
189249func UseData [T any ](ctx context.Context , atomName string ) (T , func (T ), func (func (T ) T )) {
190250 return useAtom [T ](ctx , "UseData" , "$data." + atomName )
191251}
192252
253+ // UseVDomRef provides a reference to a DOM element in the VDOM tree.
254+ // It returns a VDomRef that can be attached to elements for direct DOM access.
255+ // The ref will not be current on the first render - refs are set and become
256+ // current after client-side mounting.
257+ // This hook must be called within a component context.
193258func UseVDomRef (ctx context.Context ) * VDomRef {
194259 rc := vdomctx .GetRenderContext (ctx )
195260 val := rc .UseVDomRef (ctx )
@@ -200,6 +265,10 @@ func UseVDomRef(ctx context.Context) *VDomRef {
200265 return refVal
201266}
202267
268+ // UseRef is the tsunami analog to React's useRef hook.
269+ // It provides a mutable ref object that persists across re-renders.
270+ // Unlike UseVDomRef, this is not tied to DOM elements but holds arbitrary values.
271+ // This hook must be called within a component context.
203272func UseRef [T any ](ctx context.Context , val T ) * VDomSimpleRef [T ] {
204273 rc := vdomctx .GetRenderContext (ctx )
205274 refVal := rc .UseRef (ctx , & VDomSimpleRef [T ]{Current : val })
@@ -210,6 +279,10 @@ func UseRef[T any](ctx context.Context, val T) *VDomSimpleRef[T] {
210279 return typedRef
211280}
212281
282+ // UseId returns the underlying component's unique identifier (UUID).
283+ // The ID persists across re-renders but is recreated when the component
284+ // is recreated, following React component lifecycle.
285+ // This hook must be called within a component context.
213286func UseId (ctx context.Context ) string {
214287 rc := vdomctx .GetRenderContext (ctx )
215288 if rc == nil {
@@ -218,6 +291,8 @@ func UseId(ctx context.Context) string {
218291 return rc .UseId (ctx )
219292}
220293
294+ // UseRenderTs returns the timestamp of the current render.
295+ // This hook must be called within a component context.
221296func UseRenderTs (ctx context.Context ) int64 {
222297 rc := vdomctx .GetRenderContext (ctx )
223298 if rc == nil {
@@ -226,6 +301,10 @@ func UseRenderTs(ctx context.Context) int64 {
226301 return rc .UseRenderTs (ctx )
227302}
228303
304+ // UseResync returns whether the current render is a resync operation.
305+ // Resyncs happen on initial app loads or full refreshes, as opposed to
306+ // incremental renders which happen otherwise.
307+ // This hook must be called within a component context.
229308func UseResync (ctx context.Context ) bool {
230309 rc := vdomctx .GetRenderContext (ctx )
231310 if rc == nil {
@@ -234,6 +313,11 @@ func UseResync(ctx context.Context) bool {
234313 return rc .UseResync (ctx )
235314}
236315
316+ // UseEffect is the tsunami analog to React's useEffect hook.
317+ // It queues effects to run after the render cycle completes.
318+ // The function can return a cleanup function that runs before the next effect
319+ // or when the component unmounts. Dependencies use shallow comparison, just like React.
320+ // This hook must be called within a component context.
237321func UseEffect (ctx context.Context , fn func () func (), deps []any ) {
238322 // note UseEffect never actually runs anything, it just queues the effect to run later
239323 rc := vdomctx .GetRenderContext (ctx )
@@ -243,6 +327,8 @@ func UseEffect(ctx context.Context, fn func() func(), deps []any) {
243327 rc .UseEffect (ctx , fn , deps )
244328}
245329
330+ // UseSetAppTitle sets the application title for the current component.
331+ // This hook must be called within a component context.
246332func UseSetAppTitle (ctx context.Context , title string ) {
247333 rc := vdomctx .GetRenderContext (ctx )
248334 if rc == nil {
@@ -251,6 +337,10 @@ func UseSetAppTitle(ctx context.Context, title string) {
251337 rc .UseSetAppTitle (ctx , title )
252338}
253339
340+ // QueueRefOp queues a reference operation to be executed on the DOM element.
341+ // Operations include actions like "focus", "scrollIntoView", etc.
342+ // If the ref is nil or not current, the operation is ignored.
343+ // This function must be called within a component context.
254344func QueueRefOp (ctx context.Context , ref * VDomRef , op VDomRefOperation ) {
255345 if ref == nil || ! ref .HasCurrent {
256346 return
@@ -265,17 +355,22 @@ func QueueRefOp(ctx context.Context, ref *VDomRef, op VDomRefOperation) {
265355 vc .QueueRefOp (ctx , op )
266356}
267357
358+ // PartToElems converts various types into VDomElem slices for use in VDOM children.
359+ // It handles strings, booleans, VDomElems, *VDomElem, slices, and other types
360+ // by converting them to appropriate VDomElem representations.
361+ // nil values are ignored and removed from the final slice.
362+ // This is primarily an internal function and not typically called directly by application code.
268363func PartToElems (part any ) []VDomElem {
269364 if part == nil {
270365 return nil
271366 }
272367 switch partTyped := part .(type ) {
273368 case string :
274- return []VDomElem {TextElem (partTyped )}
369+ return []VDomElem {textElem (partTyped )}
275370 case bool :
276371 // matches react
277372 if partTyped {
278- return []VDomElem {TextElem ("true" )}
373+ return []VDomElem {textElem ("true" )}
279374 }
280375 return nil
281376 case VDomElem :
@@ -294,6 +389,6 @@ func PartToElems(part any) []VDomElem {
294389 }
295390 return rtn
296391 }
297- return []VDomElem {TextElem (fmt .Sprint (part ))}
392+ return []VDomElem {textElem (fmt .Sprint (part ))}
298393 }
299394}
0 commit comments