JSX
Syntactic sugar for creating objects declaratively.
This is not React
Gnim shares many concepts with UI rendering libraries like React, Solid, and Svelte, but it is its own solution: it is not React.
JSX Element
A valid JSX component must either be a function that returns a GnimNode or a class that inherits from GObject.Object.
type FC = (props: any) => GnimNode
type CC = new (props: any) => GObject.Object
interface ConstructorNode {
type: string | FC | CC
props: Record<string, unknown>
}
type GnimNode =
| ConstructorNode
| GObject.Object
| Iterable<GnimNode>
| Accessor<GnimNode>
| string
| number
| bigint
| boolean
| null
| undefinedWhen two object types have a parent-child relationship, they can be composed naturally using JSX syntax. For example, this applies to types like Gtk.EventController:
<Gtk.Box>
<Gtk.GestureClick onPressed={() => print("clicked")} />
</Gtk.Box>Accessor children should be primitive types only
Due to how instantiation order works you should only pass primitive types as children using Accessors. If you capture a JSX expression in an Accessor and try to pass it as children it will break the scoping mechanism and contexts are lost.
let str: Accessor<string>
function Comp() {
return (
<Gtk.Button>
{str((s) => (
<Gtk.Label label={s} />
))}
<With value={str}>{(s) => <Gtk.Label label={s} />}</With>
</Gtk.Button>
)
}Class Components
When defining custom components, choosing between using classes vs. functions is mostly down to preference. There are cases when you will have to subclass, however you will mostly be using class components from libraries such as Gtk, and defining function components for custom components.
Constructor function
By default, classes are instantiated with the new keyword and initial values are passed in. In cases where you need to use a static constructor function instead, you can specify it with construct.
<Gtk.DropDown
construct={() => Gtk.DropDown.new_from_strings(["item1", "item2"])}
/>The construct property can also be given an existing instance. It can be used in combination with the render function to use JSX in subclasses.
@register
class MyWidget extends Gtk.Widget {
constructor() {
super()
render(() => <MyWidget construct={this}>Hello</MyWidget>)
}
}Type string
Under the hood, to build the widget tree Gnim uses the Gtk.Buildable interface, which lets you specify a slot to specify the type the child is meant to be.
<Gtk.CenterBox>
<Gtk.Box slot="start" />
<Gtk.Box slot="center" />
<Gtk.Box slot="end" />
</Gtk.CenterBox>NOTE
This is specific to Gtk renderers and is unavailable when using Clutter.
Signal handlers
Signal handlers can be defined with an on prefix, and notify:: signal handlers can be defined with an onNotify prefix.
<Gtk.Revealer
onNotifyChildRevealed={(self) => print(self, "child-revealed")}
onDestroy={(self) => print(self, "destroyed")}
/>Ref
It is possible to define an arbitrary function to do something with the instance imperatively. It is run after properties are set, signals are connected, and children are appended, but before the instance is appended to parents.
<Gtk.Stack ref={(self) => print(self, "is about to be returned")} />The most common use case is to acquire a reference to the widget in the scope of the function.
function MyWidget() {
let box: Gtk.Box
function someHandler() {
console.log(box)
}
return <Gtk.Box ref={(self) => (box = self)} />
}Another common use case is to initialize relations between widgets in the tree.
function MyWidget() {
let win: Gtk.Window
let searchbar: Gtk.SearchBar
effect(() => {
searchbar.set_key_capture_widget(win)
})
return (
<Gtk.Window ref={(self) => (win = self)}>
<Gtk.SearchBar ref={(self) => (searchbar = self)}>
<Gtk.SearchEntry />
</Gtk.SearchBar>
</Gtk.Window>
)
}Bindings
Properties can be set as a static value. Alternatively, they can be passed an Accessor, in which case whenever its value changes, it will be reflected on the widget.
const [revealed, setRevealed] = createState(false)
return (
<Gtk.Button onClicked={() => setRevealed((v) => !v)}>
<Gtk.Revealer revealChild={revealed}>
<Gtk.Label label="content" />
</Gtk.Revealer>
</Gtk.Button>
)Inline CSS
There is an additional css property available on Class components that inherit from Gtk.Widget. It is mostly meant to be used as a debugging tool, e.g. with css="border: 1px solid red;".
<Gtk.Button css="border: 1px solid red;" />Class names
The class property is available on Class components that inherit from Gtk.Widget. It is an alternative to Gtk4 cssClasses(Gtk3 does not have a property for class names) property which can take class names in various forms.
const name: string | Accessor<string> | string[] | Accessor<string[]>
return (
<Gtk.Button
class="class1 class2"
class={name}
class={["class1 class2", name]}
/>
)Function Components
Function components don't have internally managed properties, they are all handled in user code.
TIP
In Gnim, props have to be explicitly declared as reactive due to GObjects having possible construct-only properties that cannot be mutated after instantiation.
import { prop, MaybeAccessor } from "gnim"
function Counter(props: {
count?: MaybeAccessor<number>
onClicked?: () => void
children?: GnimNode
}) {
const count = prop(props.count, 0)
return (
<Gtk.Button onClicked={props.onClicked}>
<Gtk.Box>
<Gtk.Label label={count.as(String)} />
{props.children}
</Gtk.Box>
</Gtk.Button>
)
}Control flow
Dynamic rendering
When you want to render based on a value, you can use the <With> component.
let value: Accessor<{ member: string } | null>
return (
<With value={value}>
{(value) => value && <Gtk.Label label={value.member} />}
</With>
)TIP
In a lot of cases it is better to always render the component and set its visible property instead.
const member = computed(() => value()?.member || "")
const shouldShow = computed(() => member() !== "")
return <Label visible={shouldShow} label={member} />List rendering
The <For> component lets you render based on an array dynamically. Each time the array changes, it is compared with its previous state. Widgets for new items are inserted, while widgets associated with removed items are removed.
let list: Accessor<Iterable<T>>
return (
<For each={list}>
{(item: T, index: Accessor<number>) => (
<Gtk.Label label={index((i) => `${i}. ${item}`)} />
)}
</For>
)Fragment
A <Fragment> often used via <>...</> syntax, lets you group elements without a wrapper widget.
<>
<FirstChild />
<SecondChild />
</>Portal
Renders children into a different mount point in the widget tree, breaking out of the normal parent-child hierarchy.
Example:
<Portal mount={app}>
<Gtk.Window />
</Portal>Intrinsic Elements
Intrinsic elements are globally available components, which in web frameworks are usually HTMLElements such as <div> <span> <p>. There are no intrinsic elements by default but custom renderers may define them.