summaryrefslogtreecommitdiff
path: root/lang
diff options
context:
space:
mode:
authorAylur <[email protected]>2024-11-24 00:04:07 +0000
committerAylur <[email protected]>2024-12-02 22:34:01 +0100
commit2f09ed83386b334f0dfb7f376b99739b15e49fc9 (patch)
tree4be7848780c4145310a5fa69c60661a61c69ca47 /lang
parent990f031507b21f8a18c0710016fb76b1f260afe8 (diff)
gjs gtk4 support
export jsx-runtime
Diffstat (limited to 'lang')
-rw-r--r--lang/gjs/eslint.config.mjs1
-rw-r--r--lang/gjs/meson.build6
-rw-r--r--lang/gjs/package.json8
-rw-r--r--lang/gjs/src/_app.ts9
-rw-r--r--lang/gjs/src/_astal.ts180
-rw-r--r--lang/gjs/src/binding.ts8
-rw-r--r--lang/gjs/src/file.ts6
-rw-r--r--lang/gjs/src/gobject.ts10
-rw-r--r--lang/gjs/src/gtk3/astalify.ts225
-rw-r--r--lang/gjs/src/gtk3/jsx-runtime.ts31
-rw-r--r--lang/gjs/src/gtk3/widget.ts20
-rw-r--r--lang/gjs/src/gtk4/app.ts6
-rw-r--r--lang/gjs/src/gtk4/astalify.ts227
-rw-r--r--lang/gjs/src/gtk4/index.ts4
-rw-r--r--lang/gjs/src/gtk4/jsx-runtime.ts69
-rw-r--r--lang/gjs/src/gtk4/widget.ts165
-rw-r--r--lang/gjs/src/index.ts4
-rw-r--r--lang/gjs/src/package.json22
-rw-r--r--lang/gjs/src/process.ts9
-rw-r--r--lang/gjs/src/variable.ts13
-rw-r--r--lang/gjs/tsconfig.json2
21 files changed, 772 insertions, 253 deletions
diff --git a/lang/gjs/eslint.config.mjs b/lang/gjs/eslint.config.mjs
index 05e49ee..5e32355 100644
--- a/lang/gjs/eslint.config.mjs
+++ b/lang/gjs/eslint.config.mjs
@@ -15,5 +15,6 @@ export default tseslint.config({
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@stylistic/new-parens": "off",
+ "@stylistic/brace-style": ["error", "1tbs", { allowSingleLine: true }],
},
})
diff --git a/lang/gjs/meson.build b/lang/gjs/meson.build
index 51496dc..402bc55 100644
--- a/lang/gjs/meson.build
+++ b/lang/gjs/meson.build
@@ -7,15 +7,17 @@ dependency('astal-3.0')
install_data(
[
+ 'src/_app.ts',
+ 'src/_astal.ts',
'src/binding.ts',
'src/file.ts',
'src/gobject.ts',
'src/index.ts',
+ 'src/overrides.ts',
'src/process.ts',
'src/time.ts',
'src/variable.ts',
- 'src/overrides.ts',
- 'src/_app.ts',
+ 'src/package.json',
],
install_dir: dest,
)
diff --git a/lang/gjs/package.json b/lang/gjs/package.json
index 43a7702..e3b7761 100644
--- a/lang/gjs/package.json
+++ b/lang/gjs/package.json
@@ -18,6 +18,12 @@
".": "./index.ts",
"./gtk3": "./src/gtk3/index.ts",
"./gtk4": "./src/gtk4/index.ts",
+ "./gtk3/app": "./src/gtk3/app.ts",
+ "./gtk4/app": "./src/gtk4/app.ts",
+ "./gtk3/widget": "./src/gtk3/widget.ts",
+ "./gtk4/widget": "./src/gtk4/widget.ts",
+ "./gtk3/jsx-runtime": "./src/gtk3/jsx-runtime.ts",
+ "./gtk4/jsx-runtime": "./src/gtk4/jsx-runtime.ts",
"./binding": "./src/binding.ts",
"./file": "./src/file.ts",
"./gobject": "./src/gobject.ts",
@@ -42,6 +48,6 @@
},
"scripts": {
"lint": "eslint . --fix",
- "types": "ts-for-gir generate -o @girs"
+ "types": "ts-for-gir generate -o @girs --ignoreVersionConflicts"
}
}
diff --git a/lang/gjs/src/_app.ts b/lang/gjs/src/_app.ts
index 3dadd04..46497c1 100644
--- a/lang/gjs/src/_app.ts
+++ b/lang/gjs/src/_app.ts
@@ -53,8 +53,7 @@ export function mkApp(App: App3 | App4) {
${body.includes(";") ? body : `return ${body};`}
})`)
fn()().then(res).catch(rej)
- }
- catch (error) {
+ } catch (error) {
rej(error)
}
})
@@ -69,8 +68,7 @@ export function mkApp(App: App3 | App4) {
IO.write_sock_finish(res),
)
})
- }
- else {
+ } else {
super.vfunc_request(msg, conn)
}
}
@@ -102,8 +100,7 @@ export function mkApp(App: App3 | App4) {
try {
app.acquire_socket()
- }
- catch (error) {
+ } catch (error) {
return client(msg => IO.send_message(app.instanceName, msg)!, ...programArgs)
}
diff --git a/lang/gjs/src/_astal.ts b/lang/gjs/src/_astal.ts
new file mode 100644
index 0000000..cfd8c9f
--- /dev/null
+++ b/lang/gjs/src/_astal.ts
@@ -0,0 +1,180 @@
+import Variable from "./variable.js"
+import { execAsync } from "./process.js"
+import Binding, { Connectable, kebabify, snakeify, Subscribable } from "./binding.js"
+
+export const noImplicitDestroy = Symbol("no no implicit destroy")
+export const setChildren = Symbol("children setter method")
+
+export function mergeBindings(array: any[]) {
+ function getValues(...args: any[]) {
+ let i = 0
+ return array.map(value => value instanceof Binding
+ ? args[i++]
+ : value,
+ )
+ }
+
+ const bindings = array.filter(i => i instanceof Binding)
+
+ if (bindings.length === 0)
+ return array
+
+ if (bindings.length === 1)
+ return bindings[0].as(getValues)
+
+ return Variable.derive(bindings, getValues)()
+}
+
+export function setProp(obj: any, prop: string, value: any) {
+ try {
+ const setter = `set_${snakeify(prop)}`
+ if (typeof obj[setter] === "function")
+ return obj[setter](value)
+
+ return (obj[prop] = value)
+ } catch (error) {
+ console.error(`could not set property "${prop}" on ${obj}:`, error)
+ }
+}
+
+export type BindableProps<T> = {
+ [K in keyof T]: Binding<T[K]> | T[K];
+}
+
+export function hook<Widget extends Connectable>(
+ widget: Widget,
+ object: Connectable | Subscribable,
+ signalOrCallback: string | ((self: Widget, ...args: any[]) => void),
+ callback?: (self: Widget, ...args: any[]) => void,
+) {
+ if (typeof object.connect === "function" && callback) {
+ const id = object.connect(signalOrCallback, (_: any, ...args: unknown[]) => {
+ callback(widget, ...args)
+ })
+ widget.connect("destroy", () => {
+ (object.disconnect as Connectable["disconnect"])(id)
+ })
+ } else if (typeof object.subscribe === "function" && typeof signalOrCallback === "function") {
+ const unsub = object.subscribe((...args: unknown[]) => {
+ signalOrCallback(widget, ...args)
+ })
+ widget.connect("destroy", unsub)
+ }
+}
+
+export function construct<Widget extends Connectable & { [setChildren]: (children: any[]) => void }>(widget: Widget, config: any) {
+ const { setup, child, children = [], ...props } = config
+
+ if (child)
+ children.unshift(child)
+
+ // collect bindings
+ const bindings: Array<[string, Binding<any>]> = Object
+ .keys(props)
+ .reduce((acc: any, prop) => {
+ if (props[prop] instanceof Binding) {
+ const binding = props[prop]
+ delete props[prop]
+ return [...acc, [prop, binding]]
+ }
+ return acc
+ }, [])
+
+ // collect signal handlers
+ const onHandlers: Array<[string, string | (() => unknown)]> = Object
+ .keys(props)
+ .reduce((acc: any, key) => {
+ if (key.startsWith("on")) {
+ const sig = kebabify(key).split("-").slice(1).join("-")
+ const handler = props[key]
+ delete props[key]
+ return [...acc, [sig, handler]]
+ }
+ return acc
+ }, [])
+
+ // set children
+ const mergedChildren = mergeBindings(children.flat(Infinity))
+ if (mergedChildren instanceof Binding) {
+ widget[setChildren](mergedChildren.get())
+ widget.connect("destroy", mergedChildren.subscribe((v) => {
+ widget[setChildren](v)
+ }))
+ } else {
+ if (mergedChildren.length > 0) {
+ widget[setChildren](mergedChildren)
+ }
+ }
+
+ // setup signal handlers
+ for (const [signal, callback] of onHandlers) {
+ const sig = signal.startsWith("notify")
+ ? signal.replace("-", "::")
+ : signal
+
+ if (typeof callback === "function") {
+ widget.connect(sig, callback)
+ } else {
+ widget.connect(sig, () => execAsync(callback)
+ .then(print).catch(console.error))
+ }
+ }
+
+ // setup bindings handlers
+ for (const [prop, binding] of bindings) {
+ if (prop === "child" || prop === "children") {
+ widget.connect("destroy", binding.subscribe((v: any) => {
+ widget[setChildren](v)
+ }))
+ }
+ widget.connect("destroy", binding.subscribe((v: any) => {
+ setProp(widget, prop, v)
+ }))
+ setProp(widget, prop, binding.get())
+ }
+
+ // filter undefined values
+ for (const [key, value] of Object.entries(props)) {
+ if (value === undefined) {
+ delete props[key]
+ }
+ }
+
+ Object.assign(widget, props)
+ setup?.(widget)
+ return widget
+}
+
+function isArrowFunction(func: any): func is (args: any) => any {
+ return !Object.hasOwn(func, "prototype")
+}
+
+export function jsx(
+ ctors: Record<string, { new(props: any): any } | ((props: any) => any)>,
+ ctor: string | ((props: any) => any) | { new(props: any): any },
+ { children, ...props }: any,
+) {
+ children ??= []
+
+ if (!Array.isArray(children))
+ children = [children]
+
+ children = children.filter(Boolean)
+
+ if (children.length === 1)
+ props.child = children[0]
+ else if (children.length > 1)
+ props.children = children
+
+ if (typeof ctor === "string") {
+ if (isArrowFunction(ctors[ctor]))
+ return ctors[ctor](props)
+
+ return new ctors[ctor](props)
+ }
+
+ if (isArrowFunction(ctor))
+ return ctor(props)
+
+ return new ctor(props)
+}
diff --git a/lang/gjs/src/binding.ts b/lang/gjs/src/binding.ts
index 95d905f..19a55cf 100644
--- a/lang/gjs/src/binding.ts
+++ b/lang/gjs/src/binding.ts
@@ -20,7 +20,7 @@ export interface Connectable {
[key: string]: any
}
-export default class Binding<Value> {
+export class Binding<Value> {
private transformFn = (v: any) => v
#emitter: Subscribable<Value> | Connectable
@@ -46,7 +46,7 @@ export default class Binding<Value> {
return `Binding<${this.#emitter}${this.#prop ? `, "${this.#prop}"` : ""}>`
}
- as<T>(fn: (v: Value) => T): Binding<T> {
+ as<T>(fn: (v: Value) => T | Binding<T>): Binding<T> {
const bind = new Binding(this.#emitter, this.#prop)
bind.transformFn = (v: Value) => fn(this.transformFn(v))
return bind as unknown as Binding<T>
@@ -72,8 +72,7 @@ export default class Binding<Value> {
return this.#emitter.subscribe(() => {
callback(this.get())
})
- }
- else if (typeof this.#emitter.connect === "function") {
+ } else if (typeof this.#emitter.connect === "function") {
const signal = `notify::${this.#prop}`
const id = this.#emitter.connect(signal, () => {
callback(this.get())
@@ -87,3 +86,4 @@ export default class Binding<Value> {
}
export const { bind } = Binding
+export default Binding
diff --git a/lang/gjs/src/file.ts b/lang/gjs/src/file.ts
index 6ad8be3..4220d9d 100644
--- a/lang/gjs/src/file.ts
+++ b/lang/gjs/src/file.ts
@@ -12,8 +12,7 @@ export function readFileAsync(path: string): Promise<string> {
Astal.read_file_async(path, (_, res) => {
try {
resolve(Astal.read_file_finish(res) || "")
- }
- catch (error) {
+ } catch (error) {
reject(error)
}
})
@@ -29,8 +28,7 @@ export function writeFileAsync(path: string, content: string): Promise<void> {
Astal.write_file_async(path, content, (_, res) => {
try {
resolve(Astal.write_file_finish(res))
- }
- catch (error) {
+ } catch (error) {
reject(error)
}
})
diff --git a/lang/gjs/src/gobject.ts b/lang/gjs/src/gobject.ts
index b744cfb..1d6b9a1 100644
--- a/lang/gjs/src/gobject.ts
+++ b/lang/gjs/src/gobject.ts
@@ -90,9 +90,7 @@ export function property(declaration: PropertyDeclaration = Object) {
})
target.constructor[meta].Properties[kebabify(prop)] = pspec(name, ParamFlags.READWRITE, declaration)
- }
-
- else {
+ } else {
let flags = 0
if (desc.get) flags |= ParamFlags.READABLE
if (desc.set) flags |= ParamFlags.WRITABLE
@@ -124,8 +122,7 @@ export function signal(
target.constructor[meta].Signals[name] = {
param_types: arr,
}
- }
- else {
+ } else {
target.constructor[meta].Signals[name] = declaration || {
param_types: [],
}
@@ -137,8 +134,7 @@ export function signal(
this.emit(name, ...args)
},
})
- }
- else {
+ } else {
const og: ((...args: any[]) => void) = desc.value
desc.value = function (...args: any[]) {
// @ts-expect-error not typed
diff --git a/lang/gjs/src/gtk3/astalify.ts b/lang/gjs/src/gtk3/astalify.ts
index 6973805..92ffb6f 100644
--- a/lang/gjs/src/gtk3/astalify.ts
+++ b/lang/gjs/src/gtk3/astalify.ts
@@ -1,45 +1,11 @@
+import { hook, noImplicitDestroy, setChildren, mergeBindings, type BindableProps, construct } from "../_astal.js"
import Astal from "gi://Astal?version=3.0"
import Gtk from "gi://Gtk?version=3.0"
import Gdk from "gi://Gdk?version=3.0"
import GObject from "gi://GObject"
-import { execAsync } from "../process.js"
-import Variable from "../variable.js"
-import Binding, { kebabify, snakeify, type Connectable, type Subscribable } from "../binding.js"
+import Binding, { type Connectable, type Subscribable } from "../binding.js"
-export function mergeBindings(array: any[]) {
- function getValues(...args: any[]) {
- let i = 0
- return array.map(value => value instanceof Binding
- ? args[i++]
- : value,
- )
- }
-
- const bindings = array.filter(i => i instanceof Binding)
-
- if (bindings.length === 0)
- return array
-
- if (bindings.length === 1)
- return bindings[0].as(getValues)
-
- return Variable.derive(bindings, getValues)()
-}
-
-function setProp(obj: any, prop: string, value: any) {
- try {
- // the setter method has to be used because
- // array like properties are not bound correctly as props
- const setter = `set_${snakeify(prop)}`
- if (typeof obj[setter] === "function")
- return obj[setter](value)
-
- return (obj[prop] = value)
- }
- catch (error) {
- console.error(`could not set property "${prop}" on ${obj}:`, error)
- }
-}
+export { BindableProps, mergeBindings }
export default function astalify<
C extends { new(...args: any[]): Gtk.Widget },
@@ -65,60 +31,44 @@ export default function astalify<
get_click_through(): boolean { return this.clickThrough }
set_click_through(clickThrough: boolean) { this.clickThrough = clickThrough }
- declare private __no_implicit_destroy: boolean
- get noImplicitDestroy(): boolean { return this.__no_implicit_destroy }
- set noImplicitDestroy(value: boolean) { this.__no_implicit_destroy = value }
+ declare private [noImplicitDestroy]: boolean
+ get noImplicitDestroy(): boolean { return this[noImplicitDestroy] }
+ set noImplicitDestroy(value: boolean) { this[noImplicitDestroy] = value }
+
+ protected getChildren(): Array<Gtk.Widget> {
+ if (this instanceof Gtk.Bin) {
+ return this.get_child() ? [this.get_child()!] : []
+ } else if (this instanceof Gtk.Container) {
+ return this.get_children()
+ }
+ return []
+ }
- _setChildren(children: Gtk.Widget[]) {
+ protected setChildren(children: any[]) {
children = children.flat(Infinity).map(ch => ch instanceof Gtk.Widget
? ch
: new Gtk.Label({ visible: true, label: String(ch) }))
- // remove
- if (this instanceof Gtk.Bin) {
- const ch = this.get_child()
- if (ch)
- this.remove(ch)
- if (ch && !children.includes(ch) && !this.noImplicitDestroy)
- ch?.destroy()
+ if (this instanceof Gtk.Container) {
+ for (const ch of children)
+ this.add(ch)
+ } else {
+ throw Error(`can not add children to ${this.constructor.name}`)
}
- else if (this instanceof Gtk.Container) {
- for (const ch of this.get_children()) {
+ }
+
+ [setChildren](children: any[]) {
+ // remove
+ if (this instanceof Gtk.Container) {
+ for (const ch of this.getChildren()) {
this.remove(ch)
if (!children.includes(ch) && !this.noImplicitDestroy)
ch?.destroy()
}
}
- // TODO: add more container types
- if (this instanceof Astal.Box) {
- this.set_children(children)
- }
-
- else if (this instanceof Astal.Stack) {
- this.set_children(children)
- }
-
- else if (this instanceof Astal.CenterBox) {
- this.startWidget = children[0]
- this.centerWidget = children[1]
- this.endWidget = children[2]
- }
-
- else if (this instanceof Astal.Overlay) {
- const [child, ...overlays] = children
- this.set_child(child)
- this.set_overlays(overlays)
- }
-
- else if (this instanceof Gtk.Container) {
- for (const ch of children)
- this.add(ch)
- }
-
- else {
- throw Error(`can not add children to ${this.constructor.name}, it is not a container widget`)
- }
+ // append
+ this.setChildren(children)
}
toggleClassName(cn: string, cond = true) {
@@ -139,96 +89,15 @@ export default function astalify<
signalOrCallback: string | ((self: this, ...args: any[]) => void),
callback?: (self: this, ...args: any[]) => void,
) {
- if (typeof object.connect === "function" && callback) {
- const id = object.connect(signalOrCallback, (_: any, ...args: unknown[]) => {
- callback(this, ...args)
- })
- this.connect("destroy", () => {
- (object.disconnect as Connectable["disconnect"])(id)
- })
- }
-
- else if (typeof object.subscribe === "function" && typeof signalOrCallback === "function") {
- const unsub = object.subscribe((...args: unknown[]) => {
- signalOrCallback(this, ...args)
- })
- this.connect("destroy", unsub)
- }
-
+ hook(this, object, signalOrCallback, callback)
return this
}
constructor(...params: any[]) {
super()
- const [config] = params
-
- const { setup, child, children = [], ...props } = config
+ const props = params[0] || {}
props.visible ??= true
-
- if (child)
- children.unshift(child)
-
- // collect bindings
- const bindings = Object.keys(props).reduce((acc: any, prop) => {
- if (props[prop] instanceof Binding) {
- const binding = props[prop]
- delete props[prop]
- return [...acc, [prop, binding]]
- }
- return acc
- }, [])
-
- // collect signal handlers
- const onHandlers = Object.keys(props).reduce((acc: any, key) => {
- if (key.startsWith("on")) {
- const sig = kebabify(key).split("-").slice(1).join("-")
- const handler = props[key]
- delete props[key]
- return [...acc, [sig, handler]]
- }
- return acc
- }, [])
-
- // set children
- const mergedChildren = mergeBindings(children.flat(Infinity))
- if (mergedChildren instanceof Binding) {
- this._setChildren(mergedChildren.get())
- this.connect("destroy", mergedChildren.subscribe((v) => {
- this._setChildren(v)
- }))
- }
- else {
- if (mergedChildren.length > 0) {
- this._setChildren(mergedChildren)
- }
- }
-
- // setup signal handlers
- for (const [signal, callback] of onHandlers) {
- if (typeof callback === "function") {
- this.connect(signal, callback)
- }
- else {
- this.connect(signal, () => execAsync(callback)
- .then(print).catch(console.error))
- }
- }
-
- // setup bindings handlers
- for (const [prop, binding] of bindings) {
- if (prop === "child" || prop === "children") {
- this.connect("destroy", binding.subscribe((v: any) => {
- this._setChildren(v)
- }))
- }
- this.connect("destroy", binding.subscribe((v: any) => {
- setProp(this, prop, v)
- }))
- setProp(this, prop, binding.get())
- }
-
- Object.assign(this, props)
- setup?.(this)
+ construct(this, props)
}
}
@@ -256,15 +125,13 @@ export default function astalify<
return Widget
}
-export type BindableProps<T> = {
- [K in keyof T]: Binding<T[K]> | T[K];
-}
-
type SigHandler<
W extends InstanceType<typeof Gtk.Widget>,
Args extends Array<unknown>,
> = ((self: W, ...args: Args) => unknown) | string | string[]
+export type BindableChild = Gtk.Widget | Binding<Gtk.Widget>
+
export type ConstructProps<
Self extends InstanceType<typeof Gtk.Widget>,
Props extends Gtk.Widget.ConstructorProps,
@@ -274,23 +141,21 @@ export type ConstructProps<
[S in keyof Signals]: SigHandler<Self, Signals[S]>
}> & Partial<{
[Key in `on${string}`]: SigHandler<Self, any[]>
-}> & BindableProps<Partial<Props> & {
+}> & BindableProps<Partial<Props & {
className?: string
css?: string
cursor?: string
clickThrough?: boolean
-}> & {
- onDestroy?: (self: Self) => unknown
- onDraw?: (self: Self) => unknown
- onKeyPressEvent?: (self: Self, event: Gdk.Event) => unknown
- onKeyReleaseEvent?: (self: Self, event: Gdk.Event) => unknown
- onButtonPressEvent?: (self: Self, event: Gdk.Event) => unknown
- onButtonReleaseEvent?: (self: Self, event: Gdk.Event) => unknown
- onRealize?: (self: Self) => unknown
- setup?: (self: Self) => void
-}
-
-export type BindableChild = Gtk.Widget | Binding<Gtk.Widget>
+}>> & Partial<{
+ onDestroy: (self: Self) => unknown
+ onDraw: (self: Self) => unknown
+ onKeyPressEvent: (self: Self, event: Gdk.Event) => unknown
+ onKeyReleaseEvent: (self: Self, event: Gdk.Event) => unknown
+ onButtonPressEvent: (self: Self, event: Gdk.Event) => unknown
+ onButtonReleaseEvent: (self: Self, event: Gdk.Event) => unknown
+ onRealize: (self: Self) => unknown
+ setup: (self: Self) => void
+}>
type Cursor =
| "default"
diff --git a/lang/gjs/src/gtk3/jsx-runtime.ts b/lang/gjs/src/gtk3/jsx-runtime.ts
index 9da4bb6..f37c695 100644
--- a/lang/gjs/src/gtk3/jsx-runtime.ts
+++ b/lang/gjs/src/gtk3/jsx-runtime.ts
@@ -1,11 +1,8 @@
import Gtk from "gi://Gtk?version=3.0"
-import { mergeBindings, type BindableChild } from "./astalify.js"
+import { type BindableChild } from "./astalify.js"
+import { mergeBindings, jsx as _jsx } from "../_astal.js"
import * as Widget from "./widget.js"
-function isArrowFunction(func: any): func is (args: any) => any {
- return !Object.hasOwn(func, "prototype")
-}
-
export function Fragment({ children = [], child }: {
child?: BindableChild
children?: Array<BindableChild>
@@ -16,29 +13,9 @@ export function Fragment({ children = [], child }: {
export function jsx(
ctor: keyof typeof ctors | typeof Gtk.Widget,
- { children, ...props }: any,
+ props: any,
) {
- children ??= []
-
- if (!Array.isArray(children))
- children = [children]
-
- children = children.filter(Boolean)
-
- if (children.length === 1)
- props.child = children[0]
- else if (children.length > 1)
- props.children = children
-
- if (typeof ctor === "string") {
- return new ctors[ctor](props)
- }
-
- if (isArrowFunction(ctor))
- return ctor(props)
-
- // @ts-expect-error can be class or function
- return new ctor(props)
+ return _jsx(ctors, ctor as any, props)
}
const ctors = {
diff --git a/lang/gjs/src/gtk3/widget.ts b/lang/gjs/src/gtk3/widget.ts
index b4e8497..759a4b1 100644
--- a/lang/gjs/src/gtk3/widget.ts
+++ b/lang/gjs/src/gtk3/widget.ts
@@ -4,6 +4,12 @@ import Gtk from "gi://Gtk?version=3.0"
import GObject from "gi://GObject"
import astalify, { type ConstructProps, type BindableChild } from "./astalify.js"
+function filter(children: any[]) {
+ return children.flat(Infinity).map(ch => ch instanceof Gtk.Widget
+ ? ch
+ : new Gtk.Label({ visible: true, label: String(ch) }))
+}
+
// Box
Object.defineProperty(Astal.Box.prototype, "children", {
get() { return this.get_children() },
@@ -14,6 +20,7 @@ export type BoxProps = ConstructProps<Box, Astal.Box.ConstructorProps>
export class Box extends astalify(Astal.Box) {
static { GObject.registerClass({ GTypeName: "Box" }, this) }
constructor(props?: BoxProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) }
+ protected setChildren(children: any[]): void { this.set_children(filter(children)) }
}
// Button
@@ -35,6 +42,12 @@ export type CenterBoxProps = ConstructProps<CenterBox, Astal.CenterBox.Construct
export class CenterBox extends astalify(Astal.CenterBox) {
static { GObject.registerClass({ GTypeName: "CenterBox" }, this) }
constructor(props?: CenterBoxProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) }
+ protected setChildren(children: any[]): void {
+ const ch = filter(children)
+ this.startWidget = ch[0] || new Gtk.Box
+ this.centerWidget = ch[1] || new Gtk.Box
+ this.endWidget = ch[2] || new Gtk.Box
+ }
}
// CircularProgress
@@ -91,6 +104,7 @@ export type LabelProps = ConstructProps<Label, Astal.Label.ConstructorProps>
export class Label extends astalify(Astal.Label) {
static { GObject.registerClass({ GTypeName: "Label" }, this) }
constructor(props?: LabelProps) { super(props as any) }
+ protected setChildren(children: any[]): void { this.label = String(children) }
}
// LevelBar
@@ -112,6 +126,11 @@ export type OverlayProps = ConstructProps<Overlay, Astal.Overlay.ConstructorProp
export class Overlay extends astalify(Astal.Overlay) {
static { GObject.registerClass({ GTypeName: "Overlay" }, this) }
constructor(props?: OverlayProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) }
+ protected setChildren(children: any[]): void {
+ const [child, ...overlays] = filter(children)
+ this.set_child(child)
+ this.set_overlays(overlays)
+ }
}
// Revealer
@@ -142,6 +161,7 @@ export type StackProps = ConstructProps<Stack, Astal.Stack.ConstructorProps>
export class Stack extends astalify(Astal.Stack) {
static { GObject.registerClass({ GTypeName: "Stack" }, this) }
constructor(props?: StackProps, ...children: Array<BindableChild>) { super({ children, ...props } as any) }
+ protected setChildren(children: any[]): void { this.set_children(filter(children)) }
}
// Switch
diff --git a/lang/gjs/src/gtk4/app.ts b/lang/gjs/src/gtk4/app.ts
index 1c51772..7906993 100644
--- a/lang/gjs/src/gtk4/app.ts
+++ b/lang/gjs/src/gtk4/app.ts
@@ -4,4 +4,10 @@ import { mkApp } from "../_app"
Gtk.init()
+// users might want to use Adwaita in which case it has to be initialized
+// it might be common pitfall to forget it because `App` is not `Adw.Application`
+await import("gi://Adw?version=1")
+ .then(({ default: Adw }) => Adw.init())
+ .catch(() => void 0)
+
export default mkApp(Astal.Application)
diff --git a/lang/gjs/src/gtk4/astalify.ts b/lang/gjs/src/gtk4/astalify.ts
index 6c8ea4d..644ac1a 100644
--- a/lang/gjs/src/gtk4/astalify.ts
+++ b/lang/gjs/src/gtk4/astalify.ts
@@ -1 +1,226 @@
-// TODO:
+import { noImplicitDestroy, setChildren, type BindableProps, construct } from "../_astal.js"
+import Gtk from "gi://Gtk?version=4.0"
+import Gdk from "gi://Gdk?version=4.0"
+import Binding from "../binding.js"
+
+export const type = Symbol("child type")
+const dummyBulder = new Gtk.Builder
+
+function _getChildren(widget: Gtk.Widget): Array<Gtk.Widget> {
+ if ("get_child" in widget && typeof widget.get_child == "function") {
+ return widget.get_child() ? [widget.get_child()] : []
+ }
+
+ const children: Array<Gtk.Widget> = []
+ let ch = widget.get_first_child()
+ while (ch !== null) {
+ children.push(ch)
+ ch = ch.get_next_sibling()
+ }
+ return children
+}
+
+function _setChildren(widget: Gtk.Widget, children: any[]) {
+ children = children.flat(Infinity).map(ch => ch instanceof Gtk.Widget
+ ? ch
+ : new Gtk.Label({ visible: true, label: String(ch) }))
+
+ for (const child of children) {
+ widget.vfunc_add_child(
+ dummyBulder,
+ child,
+ type in widget ? widget[type] as string : null,
+ )
+ }
+}
+
+type Config<T extends Gtk.Widget> = {
+ setChildren(widget: T, children: any[]): void
+ getChildren(widget: T): Array<Gtk.Widget>
+}
+
+export default function astalify<
+ Widget extends Gtk.Widget,
+ Props extends Gtk.Widget.ConstructorProps = Gtk.Widget.ConstructorProps,
+ Signals extends Record<`on${string}`, Array<unknown>> = Record<`on${string}`, any[]>,
+>(cls: { new(...args: any[]): Widget }, config: Partial<Config<Widget>> = {}) {
+ Object.assign(cls.prototype, {
+ [setChildren](children: any[]) {
+ const w = this as unknown as Widget
+ for (const child of (config.getChildren?.(w) || _getChildren(w))) {
+ if (child instanceof Gtk.Widget) {
+ child.unparent()
+ if (!children.includes(child) && noImplicitDestroy in this)
+ child.run_dispose()
+ }
+ }
+
+ if (config.setChildren) {
+ config.setChildren(w, children)
+ } else {
+ _setChildren(w, children)
+ }
+ },
+ })
+
+ return {
+ [cls.name]: (
+ props: ConstructProps<Widget, Props, Signals> = {},
+ ...children: any[]
+ ): Widget => {
+ const widget = new cls("cssName" in props ? { cssName: props.cssName } : {})
+
+ if ("cssName" in props) {
+ delete props.cssName
+ }
+
+ if (props.noImplicitDestroy) {
+ Object.assign(widget, { [noImplicitDestroy]: true })
+ delete props.noImplicitDestroy
+ }
+
+ if (props.type) {
+ Object.assign(widget, { [type]: props.type })
+ delete props.type
+ }
+
+ if (children.length > 0) {
+ Object.assign(props, { children })
+ }
+
+ return construct(widget as any, setupControllers(widget, props as any))
+ },
+ }[cls.name]
+}
+
+type SigHandler<
+ W extends InstanceType<typeof Gtk.Widget>,
+ Args extends Array<unknown>,
+> = ((self: W, ...args: Args) => unknown) | string | string[]
+
+export { BindableProps }
+export type BindableChild = Gtk.Widget | Binding<Gtk.Widget>
+
+export type ConstructProps<
+ Self extends InstanceType<typeof Gtk.Widget>,
+ Props extends Gtk.Widget.ConstructorProps,
+ Signals extends Record<`on${string}`, Array<unknown>> = Record<`on${string}`, any[]>,
+> = Partial<{
+ // @ts-expect-error can't assign to unknown, but it works as expected though
+ [S in keyof Signals]: SigHandler<Self, Signals[S]>
+}> & Partial<{
+ [Key in `on${string}`]: SigHandler<Self, any[]>
+}> & Partial<BindableProps<Omit<Props, "cssName" | "css_name">>> & {
+ noImplicitDestroy?: true
+ type?: string
+ cssName?: string
+} & EventController<Self> & {
+ onDestroy?: (self: Self) => unknown
+ setup?: (self: Self) => void
+}
+
+type EventController<Self extends Gtk.Widget> = {
+ onFocusEnter?: (self: Self) => void
+ onFocusLeave?: (self: Self) => void
+
+ onKeyPressed?: (self: Self, keyval: number, keycode: number, state: Gdk.ModifierType) => void
+ onKeyReleased?: (self: Self, keyval: number, keycode: number, state: Gdk.ModifierType) => void
+ onKeyModifier?: (self: Self, state: Gdk.ModifierType) => void
+
+ onLegacy?: (self: Self, event: Gdk.Event) => void
+ onButtonPressed?: (self: Self, state: Gdk.ButtonEvent) => void
+ onButtonReleased?: (self: Self, state: Gdk.ButtonEvent) => void
+
+ onHoverEnter?: (self: Self, x: number, y: number) => void
+ onHoverLeave?: (self: Self) => void
+ onMotion?: (self: Self, x: number, y: number) => void
+
+ onScroll?: (self: Self, dx: number, dy: number) => void
+ onScrollDecelerate?: (self: Self, vel_x: number, vel_y: number) => void
+}
+
+function setupControllers<T>(widget: Gtk.Widget, {
+ onFocusEnter,
+ onFocusLeave,
+ onKeyPressed,
+ onKeyReleased,
+ onKeyModifier,
+ onLegacy,
+ onButtonPressed,
+ onButtonReleased,
+ onHoverEnter,
+ onHoverLeave,
+ onMotion,
+ onScroll,
+ onScrollDecelerate,
+ ...props
+}: EventController<Gtk.Widget> & T) {
+ if (onFocusEnter || onFocusLeave) {
+ const focus = new Gtk.EventControllerFocus
+ widget.add_controller(focus)
+
+ if (onFocusEnter)
+ focus.connect("focus-enter", () => onFocusEnter(widget))
+
+ if (onFocusLeave)
+ focus.connect("focus-leave", () => onFocusLeave(widget))
+ }
+
+ if (onKeyPressed || onKeyReleased || onKeyModifier) {
+ const key = new Gtk.EventControllerKey
+ widget.add_controller(key)
+
+ if (onKeyPressed)
+ key.connect("key-pressed", (_, val, code, state) => onKeyPressed(widget, val, code, state))
+
+ if (onKeyReleased)
+ key.connect("key-released", (_, val, code, state) => onKeyReleased(widget, val, code, state))
+
+ if (onKeyModifier)
+ key.connect("modifiers", (_, state) => onKeyModifier(widget, state))
+ }
+
+ if (onLegacy || onButtonPressed || onButtonReleased) {
+ const legacy = new Gtk.EventControllerLegacy
+ widget.add_controller(legacy)
+
+ legacy.connect("event", (_, event) => {
+ if (event.get_event_type() === Gdk.EventType.BUTTON_PRESS) {
+ onButtonPressed?.(widget, event as Gdk.ButtonEvent)
+ }
+
+ if (event.get_event_type() === Gdk.EventType.BUTTON_RELEASE) {
+ onButtonReleased?.(widget, event as Gdk.ButtonEvent)
+ }
+
+ onLegacy?.(widget, event)
+ })
+ }
+
+ if (onMotion || onHoverEnter || onHoverLeave) {
+ const hover = new Gtk.EventControllerMotion
+ widget.add_controller(hover)
+
+ if (onHoverEnter)
+ hover.connect("enter", (_, x, y) => onHoverEnter(widget, x, y))
+
+ if (onHoverLeave)
+ hover.connect("leave", () => onHoverLeave(widget))
+
+ if (onMotion)
+ hover.connect("motion", (_, x, y) => onMotion(widget, x, y))
+ }
+
+ if (onScroll || onScrollDecelerate) {
+ const scroll = new Gtk.EventControllerScroll
+ widget.add_controller(scroll)
+
+ if (onScroll)
+ scroll.connect("scroll", (_, x, y) => onScroll(widget, x, y))
+
+ if (onScrollDecelerate)
+ scroll.connect("decelerate", (_, x, y) => onScrollDecelerate(widget, x, y))
+ }
+
+ return props
+}
diff --git a/lang/gjs/src/gtk4/index.ts b/lang/gjs/src/gtk4/index.ts
index 3b1f737..2cc6862 100644
--- a/lang/gjs/src/gtk4/index.ts
+++ b/lang/gjs/src/gtk4/index.ts
@@ -1,9 +1,9 @@
import Astal from "gi://Astal?version=4.0"
import Gtk from "gi://Gtk?version=4.0"
import Gdk from "gi://Gdk?version=4.0"
-// import astalify, { type ConstructProps } from "./astalify.js"
+import astalify, { type ConstructProps } from "./astalify.js"
export { Astal, Gtk, Gdk }
export { default as App } from "./app.js"
-// export { astalify, ConstructProps }
+export { astalify, ConstructProps }
// export * as Widget from "./widget.js"
diff --git a/lang/gjs/src/gtk4/jsx-runtime.ts b/lang/gjs/src/gtk4/jsx-runtime.ts
index 6c8ea4d..80a3e87 100644
--- a/lang/gjs/src/gtk4/jsx-runtime.ts
+++ b/lang/gjs/src/gtk4/jsx-runtime.ts
@@ -1 +1,68 @@
-// TODO:
+import Gtk from "gi://Gtk?version=4.0"
+import { type BindableChild } from "./astalify.js"
+import { mergeBindings, jsx as _jsx } from "../_astal.js"
+import * as Widget from "./widget.js"
+
+export function Fragment({ children = [], child }: {
+ child?: BindableChild
+ children?: Array<BindableChild>
+}) {
+ if (child) children.push(child)
+ return mergeBindings(children)
+}
+
+export function jsx(
+ ctor: keyof typeof ctors | typeof Gtk.Widget,
+ props: any,
+) {
+ return _jsx(ctors, ctor as any, props)
+}
+
+const ctors = {
+ box: Widget.Box,
+ button: Widget.Button,
+ centerbox: Widget.CenterBox,
+ // circularprogress: Widget.CircularProgress,
+ // drawingarea: Widget.DrawingArea,
+ entry: Widget.Entry,
+ image: Widget.Image,
+ label: Widget.Label,
+ levelbar: Widget.LevelBar,
+ overlay: Widget.Overlay,
+ revealer: Widget.Revealer,
+ slider: Widget.Slider,
+ stack: Widget.Stack,
+ switch: Widget.Switch,
+ window: Widget.Window,
+ menubutton: Widget.MenuButton,
+ popover: Widget.Popover,
+}
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace JSX {
+ type Element = Gtk.Widget
+ type ElementClass = Gtk.Widget
+ interface IntrinsicElements {
+ box: Widget.BoxProps
+ button: Widget.ButtonProps
+ centerbox: Widget.CenterBoxProps
+ // circularprogress: Widget.CircularProgressProps
+ // drawingarea: Widget.DrawingAreaProps
+ entry: Widget.EntryProps
+ image: Widget.ImageProps
+ label: Widget.LabelProps
+ levelbar: Widget.LevelBarProps
+ overlay: Widget.OverlayProps
+ revealer: Widget.RevealerProps
+ slider: Widget.SliderProps
+ stack: Widget.StackProps
+ switch: Widget.SwitchProps
+ window: Widget.WindowProps
+ menubutton: Widget.MenuButtonProps
+ popover: Widget.PopoverProps
+ }
+ }
+}
+
+export const jsxs = jsx
diff --git a/lang/gjs/src/gtk4/widget.ts b/lang/gjs/src/gtk4/widget.ts
index 6c8ea4d..10d4b9f 100644
--- a/lang/gjs/src/gtk4/widget.ts
+++ b/lang/gjs/src/gtk4/widget.ts
@@ -1 +1,164 @@
-// TODO:
+import Astal from "gi://Astal?version=4.0"
+import Gtk from "gi://Gtk?version=4.0"
+import astalify, { type, type ConstructProps } from "./astalify.js"
+
+function filter(children: any[]) {
+ return children.flat(Infinity).map(ch => ch instanceof Gtk.Widget
+ ? ch
+ : new Gtk.Label({ visible: true, label: String(ch) }))
+}
+
+// Box
+Object.defineProperty(Astal.Box.prototype, "children", {
+ get() { return this.get_children() },
+ set(v) { this.set_children(v) },
+})
+
+export type BoxProps = ConstructProps<Astal.Box, Astal.Box.ConstructorProps>
+export const Box = astalify<Astal.Box, Astal.Box.ConstructorProps>(Astal.Box, {
+ getChildren(self) { return self.get_children() },
+ setChildren(self, children) { return self.set_children(filter(children)) },
+})
+
+// Button
+export type ButtonProps = ConstructProps<Gtk.Button, Gtk.Button.ConstructorProps, {
+ onClicked: []
+}>
+export const Button = astalify<Gtk.Button, Gtk.Button.ConstructorProps>(Gtk.Button)
+
+// CenterBox
+export type CenterBoxProps = ConstructProps<Gtk.CenterBox, Gtk.CenterBox.ConstructorProps>
+export const CenterBox = astalify<Gtk.CenterBox, Gtk.CenterBox.ConstructorProps>(Gtk.CenterBox, {
+ getChildren(box) {
+ return [box.startWidget, box.centerWidget, box.endWidget]
+ },
+ setChildren(box, children) {
+ const ch = filter(children)
+ box.startWidget = ch[0] || new Gtk.Box
+ box.centerWidget = ch[1] || new Gtk.Box
+ box.endWidget = ch[2] || new Gtk.Box
+ },
+})
+
+// TODO: CircularProgress
+// TODO: DrawingArea
+
+// Entry
+type EntrySignals = {
+ onActivate: []
+ onNotifyText: []
+}
+
+export type EntryProps = ConstructProps<Gtk.Entry, Gtk.Entry.ConstructorProps, EntrySignals>
+export const Entry = astalify<Gtk.Entry, Gtk.Entry.ConstructorProps, EntrySignals>(Gtk.Entry, {
+ getChildren() { return [] },
+})
+
+// Image
+export type ImageProps = ConstructProps<Gtk.Image, Gtk.Image.ConstructorProps>
+export const Image = astalify<Gtk.Image, Gtk.Image.ConstructorProps>(Gtk.Image, {
+ getChildren() { return [] },
+})
+
+// Label
+export type LabelProps = ConstructProps<Gtk.Label, Gtk.Label.ConstructorProps>
+export const Label = astalify<Gtk.Label, Gtk.Label.ConstructorProps>(Gtk.Label, {
+ getChildren() { return [] },
+ setChildren(self, children) { self.label = String(children) },
+})
+
+// LevelBar
+export type LevelBarProps = ConstructProps<Gtk.LevelBar, Gtk.LevelBar.ConstructorProps>
+export const LevelBar = astalify<Gtk.LevelBar, Gtk.LevelBar.ConstructorProps>(Gtk.LevelBar, {
+ getChildren() { return [] },
+})
+
+// TODO: ListBox
+
+// Overlay
+export type OverlayProps = ConstructProps<Gtk.Overlay, Gtk.Overlay.ConstructorProps>
+export const Overlay = astalify<Gtk.Overlay, Gtk.Overlay.ConstructorProps>(Gtk.Overlay, {
+ getChildren(self) {
+ const children: Array<Gtk.Widget> = []
+ let ch = self.get_first_child()
+ while (ch !== null) {
+ children.push(ch)
+ ch = ch.get_next_sibling()
+ }
+
+ return children.filter(ch => ch !== self.child)
+ },
+ setChildren(self, children) {
+ for (const child of filter(children)) {
+ const types = type in child
+ ? (child[type] as string).split(/\s+/)
+ : []
+
+ if (types.includes("overlay")) {
+ self.add_overlay(child)
+ } else {
+ self.set_child(child)
+ }
+
+ self.set_measure_overlay(child, types.includes("measure"))
+ self.set_clip_overlay(child, types.includes("clip"))
+ }
+ },
+})
+
+// Revealer
+export type RevealerProps = ConstructProps<Gtk.Revealer, Gtk.Revealer.ConstructorProps>
+export const Revealer = astalify<Gtk.Revealer, Gtk.Revealer.ConstructorProps>(Gtk.Revealer)
+
+// Slider
+type SliderSignals = {
+ onChangeValue: []
+}
+
+export type SliderProps = ConstructProps<Astal.Slider, Astal.Slider.ConstructorProps, SliderSignals>
+export const Slider = astalify<Astal.Slider, Astal.Slider.ConstructorProps, SliderSignals>(Astal.Slider, {
+ getChildren() { return [] },
+})
+
+// Stack
+export type StackProps = ConstructProps<Gtk.Stack, Gtk.Stack.ConstructorProps>
+export const Stack = astalify<Gtk.Stack, Gtk.Stack.ConstructorProps>(Gtk.Stack, {
+ setChildren(self, children) {
+ for (const child of filter(children)) {
+ if (child.name != "" && child.name != null) {
+ self.add_named(child, child.name)
+ } else {
+ self.add_child(child)
+ }
+ }
+ },
+})
+
+// Switch
+export type SwitchProps = ConstructProps<Gtk.Switch, Gtk.Switch.ConstructorProps>
+export const Switch = astalify<Gtk.Switch, Gtk.Switch.ConstructorProps>(Gtk.Switch, {
+ getChildren() { return [] },
+})
+
+// Window
+export type WindowProps = ConstructProps<Astal.Window, Astal.Window.ConstructorProps>
+export const Window = astalify<Astal.Window, Astal.Window.ConstructorProps>(Astal.Window)
+
+// MenuButton
+export type MenuButtonProps = ConstructProps<Gtk.MenuButton, Gtk.MenuButton.ConstructorProps>
+export const MenuButton = astalify<Gtk.MenuButton, Gtk.MenuButton.ConstructorProps>(Gtk.MenuButton, {
+ getChildren(self) { return [self.popover, self.child] },
+ setChildren(self, children) {
+ for (const child of filter(children)) {
+ if (child instanceof Gtk.Popover) {
+ self.set_popover(child)
+ } else {
+ self.set_child(child)
+ }
+ }
+ },
+})
+
+// Popoper
+export type PopoverProps = ConstructProps<Gtk.Popover, Gtk.Popover.ConstructorProps>
+export const Popover = astalify<Gtk.Popover, Gtk.Popover.ConstructorProps>(Gtk.Popover)
diff --git a/lang/gjs/src/index.ts b/lang/gjs/src/index.ts
index 8fe8d01..f448af9 100644
--- a/lang/gjs/src/index.ts
+++ b/lang/gjs/src/index.ts
@@ -4,5 +4,5 @@ export * from "./process.js"
export * from "./time.js"
export * from "./file.js"
export * from "./gobject.js"
-export { bind, default as Binding } from "./binding.js"
-export { Variable } from "./variable.js"
+export { Binding, bind } from "./binding.js"
+export { Variable, derive } from "./variable.js"
diff --git a/lang/gjs/src/package.json b/lang/gjs/src/package.json
new file mode 100644
index 0000000..b792213
--- /dev/null
+++ b/lang/gjs/src/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "astal",
+ "type": "module",
+ "license": "LGPL-2.1",
+ "exports": {
+ ".": "./index.ts",
+ "./gtk3": "./gtk3/index.ts",
+ "./gtk4": "./gtk4/index.ts",
+ "./gtk3/app": "./gtk3/app.ts",
+ "./gtk4/app": "./gtk4/app.ts",
+ "./gtk3/widget": "./gtk3/widget.ts",
+ "./gtk4/widget": "./gtk4/widget.ts",
+ "./gtk3/jsx-runtime": "./gtk3/jsx-runtime.ts",
+ "./gtk4/jsx-runtime": "./gtk4/jsx-runtime.ts",
+ "./binding": "./binding.ts",
+ "./file": "./file.ts",
+ "./gobject": "./gobject.ts",
+ "./process": "./process.ts",
+ "./time": "./time.ts",
+ "./variable": "./variable.ts"
+ }
+}
diff --git a/lang/gjs/src/process.ts b/lang/gjs/src/process.ts
index c41adc1..6e3a4a9 100644
--- a/lang/gjs/src/process.ts
+++ b/lang/gjs/src/process.ts
@@ -50,18 +50,15 @@ export function execAsync(cmd: string | string[]): Promise<string> {
Astal.Process.exec_asyncv(cmd, (_, res) => {
try {
resolve(Astal.Process.exec_asyncv_finish(res))
- }
- catch (error) {
+ } catch (error) {
reject(error)
}
})
- }
- else {
+ } else {
Astal.Process.exec_async(cmd, (_, res) => {
try {
resolve(Astal.Process.exec_finish(res))
- }
- catch (error) {
+ } catch (error) {
reject(error)
}
})
diff --git a/lang/gjs/src/variable.ts b/lang/gjs/src/variable.ts
index 9b3d3d2..016d73a 100644
--- a/lang/gjs/src/variable.ts
+++ b/lang/gjs/src/variable.ts
@@ -60,13 +60,11 @@ class VariableWrapper<T> extends Function {
if (v instanceof Promise) {
v.then(v => this.set(v))
.catch(err => this.variable.emit("error", err))
- }
- else {
+ } else {
this.set(v)
}
})
- }
- else if (this.pollExec) {
+ } else if (this.pollExec) {
this._poll = interval(this.pollInterval, () => {
execAsync(this.pollExec!)
.then(v => this.set(this.pollTransform!(v, this.get())))
@@ -143,8 +141,7 @@ class VariableWrapper<T> extends Function {
if (typeof exec === "function") {
this.pollFn = exec
delete this.pollExec
- }
- else {
+ } else {
this.pollExec = exec
delete this.pollFn
}
@@ -188,8 +185,7 @@ class VariableWrapper<T> extends Function {
const id = o.connect(s, set)
this.onDropped(() => o.disconnect(id))
}
- }
- else {
+ } else {
if (typeof sigOrFn === "string") {
const id = objs.connect(sigOrFn, set)
this.onDropped(() => objs.disconnect(id))
@@ -227,4 +223,5 @@ export const Variable = new Proxy(VariableWrapper as any, {
new<T>(init: T): Variable<T>
}
+export const { derive } = Variable
export default Variable
diff --git a/lang/gjs/tsconfig.json b/lang/gjs/tsconfig.json
index 7a3a8c8..571c756 100644
--- a/lang/gjs/tsconfig.json
+++ b/lang/gjs/tsconfig.json
@@ -13,7 +13,7 @@
"@girs",
"src/*.ts",
// "src/gtk3/*",
- // "src/gtk4/*",
+ "src/gtk4/*",
"index.ts",
]
}