()
+
+ function init() {
+ effectScope += 1
+ cachedValue = value.peek()
+ dispose = value.subscribe(() => {
+ const v = value.peek()
+ if (!equals(cachedValue, v)) {
+ cachedValue = v
+ Array.from(subscribers).forEach((cb) => cb())
+ }
+ })
+ effectScope -= 1
+ }
+
+ function subscribe(callback: Callback): DisposeFn {
+ if (subscribers.size === 0) {
+ init()
+ }
+
+ subscribers.add(callback)
+
+ return () => {
+ subscribers.delete(callback)
+ if (subscribers.size === 0) {
+ dispose()
+ cachedValue = nil
+ }
+ }
+ }
+
+ function get(): T {
+ if (cachedValue !== nil) return cachedValue
+ return value.peek()
+ }
+
+ return new Accessor(get, subscribe)
+}
+
+type PropKey = Exclude, "$signals">
+
+/**
+ * Create an {@link Accessor} on a {@link GObject.Object}'s registered property.
*
- * @param object The `GObject.Object` to create the `Accessor` on.
+ * @param object The {@link GObject.Object} to create the {@link Accessor} on.
* @param property One of its registered properties.
*/
export function createBinding(
object: T,
- property: Extract,
+ property: PropKey
,
): Accessor
-// TODO: support nested bindings
-// export function createBinding<
-// T extends GObject.Object,
-// P1 extends keyof T,
-// P2 extends keyof NonNullable,
-// >(
-// object: T,
-// property1: Extract,
-// property2: Extract,
-// ): Accessor[P2]>
+export function createBinding<
+ T extends GObject.Object,
+ P1 extends keyof T,
+ P2 extends keyof NonNullable,
+>(
+ object: T,
+ property1: PropKey,
+ property2: PropKey,
+): Accessor[P2] | null : NonNullable[P2]>
+
+export function createBinding<
+ T extends GObject.Object,
+ P1 extends keyof T,
+ P2 extends keyof NonNullable,
+ P3 extends keyof NonNullable[P2]>,
+>(
+ object: T,
+ property1: PropKey,
+ property2: PropKey,
+ property3: PropKey,
+): Accessor<
+ null extends T[P1]
+ ? NonNullable[P2]>[P3] | null
+ : null extends NonNullable[P2]
+ ? NonNullable[P2]>[P3] | null
+ : NonNullable[P2]>[P3]
+>
+
+export function createBinding<
+ T extends GObject.Object,
+ P1 extends keyof T,
+ P2 extends keyof NonNullable,
+ P3 extends keyof NonNullable[P2]>,
+ P4 extends keyof NonNullable[P2]>[P3]>,
+>(
+ object: T,
+ property1: PropKey,
+ property2: PropKey,
+ property3: PropKey,
+ property4: PropKey,
+): Accessor<
+ null extends T[P1]
+ ? NonNullable[P2]>[P3]>[P4] | null
+ : null extends NonNullable[P2]
+ ? NonNullable[P2]>[P3]>[P4] | null
+ : null extends NonNullable[P2]>[P3]
+ ? NonNullable[P2]>[P3]>[P4] | null
+ : NonNullable[P2]>[P3]>[P4]
+>
/**
- * Create an `Accessor` on a `Gio.Settings`'s `key`.
+ * Create an {@link Accessor} on a {@link Gio.Settings}'s key.
* Values are recursively unpacked.
*
- * @deprecated prefer using {@link createSettings}.
- * @param object The `Gio.Settings` to create the `Accessor` on.
- * @param key The settings key
+ * @deprecated Use {@link createSettings}.
+ * @param object The {@link Gio.Settings} to create the {@link Accessor} on.
+ * @param key The settings key.
*/
export function createBinding(settings: Gio.Settings, key: string): Accessor
-export function createBinding(object: GObject.Object | Gio.Settings, key: string): Accessor {
- const prop = kebabify(key) as keyof typeof object
-
- const subscribe: SubscribeFunction = (callback) => {
- const sig = object instanceof Gio.Settings ? "changed" : "notify"
- const id = connect.call(object, `${sig}::${prop}`, () => callback())
- return () => disconnect.call(object, id)
- }
+export function createBinding(
+ object: GObject.Object | Gio.Settings,
+ key: string,
+ ...props: string[]
+): Accessor {
+ if (props.length === 0) {
+ const prop = kebabify(key) as keyof typeof object
- const get = (): T => {
- if (object instanceof Gio.Settings) {
- return object.get_value(key).recursiveUnpack() as T
+ function subscribe(callback: Callback): DisposeFn {
+ const sig = object instanceof Gio.Settings ? "changed" : "notify"
+ const id = connect.call(object, `${sig}::${prop}`, () => callback())
+ return () => disconnect.call(object, id)
}
- if (object instanceof GObject.Object) {
- const getter = `get_${prop.replaceAll("-", "_")}` as keyof typeof object
+ function get(): T {
+ if (object instanceof Gio.Settings) {
+ return object.get_value(key).recursiveUnpack() as T
+ }
+
+ if (object instanceof GObject.Object) {
+ const getter = `get_${prop.replaceAll("-", "_")}` as keyof typeof object
- if (getter in object && typeof object[getter] === "function") {
- return (object[getter] as () => unknown)() as T
+ if (getter in object && typeof object[getter] === "function") {
+ return (object[getter] as () => unknown)() as T
+ }
+
+ if (prop in object) return object[prop] as T
+ if (key in object) return object[key as keyof typeof object] as T
}
- if (prop in object) return object[prop] as T
- if (key in object) return object[key as keyof typeof object] as T
+ throw Error(`cannot get property "${key}" on "${object}"`)
}
- throw Error(`cannot get property "${key}" on "${object}"`)
+ return new Accessor(get, subscribe)
}
- return new Accessor(get, subscribe)
+ return createComputed(() => {
+ let v = createBinding(object as any, key)()
+ for (const prop of props) {
+ if (prop) v = v !== null ? createBinding(v, prop)() : null
+ }
+ return v
+ })
}
-type ConnectionHandler<
+type ConnectionCallback<
O extends GObject.Object,
S extends keyof O["$signals"],
T,
@@ -393,20 +615,72 @@ type ConnectionHandler<
: never
: never
+type ConnectionHandler<
+ T,
+ O extends GObject.Object = GObject.Object,
+ S extends keyof O["$signals"] = any,
+> = [O, S, ConnectionCallback]
+
/**
- * Create an `Accessor` which sets up a list of `GObject.Object` signal connections.
+ * Create an {@link Accessor} which sets up a list of {@link GObject.Object} signal connections.
*
* ```ts Example
* const value: Accessor = createConnection(
* "initial value",
* [obj1, "sig-name", (...args) => "str"],
- * [obj2, "sig-name", (...args) => "str"]
+ * [obj2, "sig-name", (...args) => "str"],
* )
* ```
*
- * @param init The initial value
- * @param signals A list of `GObject.Object`, signal name and callback pairs to connect.
+ * @param init The initial value.
+ * @param handler A {@link GObject.Object}, signal name and callback pairs to connect.
*/
+export function createConnection(
+ init: T,
+ handler: ConnectionHandler,
+): Accessor
+
+export function createConnection<
+ T,
+ O1 extends GObject.Object,
+ S1 extends keyof O1["$signals"],
+ O2 extends GObject.Object,
+ S2 extends keyof O2["$signals"],
+>(init: T, h1: ConnectionHandler, h2: ConnectionHandler): Accessor
+
+export function createConnection<
+ T,
+ O1 extends GObject.Object,
+ S1 extends keyof O1["$signals"],
+ O2 extends GObject.Object,
+ S2 extends keyof O2["$signals"],
+ O3 extends GObject.Object,
+ S3 extends keyof O3["$signals"],
+>(
+ init: T,
+ h1: ConnectionHandler,
+ h2: ConnectionHandler,
+ h3: ConnectionHandler,
+): Accessor
+
+export function createConnection<
+ T,
+ O1 extends GObject.Object,
+ S1 extends keyof O1["$signals"],
+ O2 extends GObject.Object,
+ S2 extends keyof O2["$signals"],
+ O3 extends GObject.Object,
+ S3 extends keyof O3["$signals"],
+ O4 extends GObject.Object,
+ S4 extends keyof O4["$signals"],
+>(
+ init: T,
+ h1: ConnectionHandler,
+ h2: ConnectionHandler,
+ h3: ConnectionHandler,
+ h4: ConnectionHandler,
+): Accessor
+
export function createConnection<
T,
O1 extends GObject.Object,
@@ -419,38 +693,28 @@ export function createConnection<
S4 extends keyof O4["$signals"],
O5 extends GObject.Object,
S5 extends keyof O5["$signals"],
- O6 extends GObject.Object,
- S6 extends keyof O6["$signals"],
- O7 extends GObject.Object,
- S7 extends keyof O7["$signals"],
- O8 extends GObject.Object,
- S8 extends keyof O8["$signals"],
- O9 extends GObject.Object,
- S9 extends keyof O9["$signals"],
>(
init: T,
- h1: [O1, S1, ConnectionHandler],
- h2?: [O2, S2, ConnectionHandler],
- h3?: [O3, S3, ConnectionHandler],
- h4?: [O4, S4, ConnectionHandler],
- h5?: [O5, S5, ConnectionHandler],
- h6?: [O6, S6, ConnectionHandler],
- h7?: [O7, S7, ConnectionHandler],
- h8?: [O8, S8, ConnectionHandler],
- h9?: [O9, S9, ConnectionHandler],
-) {
- let value = init
- let dispose: Array
- const subscribers = new Set()
- const signals = [h1, h2, h3, h4, h5, h6, h7, h8, h9].filter((h) => h !== undefined)
+ h1: ConnectionHandler,
+ h2: ConnectionHandler,
+ h3: ConnectionHandler,
+ h4: ConnectionHandler,
+ h5: ConnectionHandler,
+): Accessor
+
+export function createConnection(init: T, ...handlers: ConnectionHandler[]) {
+ let currentValue = init
+ let dispose: Array
+
+ const subscribers = new Set()
- const subscribe: SubscribeFunction = (callback) => {
+ function subscribe(callback: Callback): DisposeFn {
if (subscribers.size === 0) {
- dispose = signals.map(([object, signal, callback]) => {
- const id = connect.call(object, signal as string, (_, ...args) => {
- const newValue = callback(...args, value)
- if (value !== newValue) {
- value = newValue
+ dispose = handlers.map(([object, signal, callback]) => {
+ const id = connect.call(object, signal, (_, ...args) => {
+ const newValue = callback(...args, currentValue)
+ if (!Object.is(currentValue, newValue)) {
+ currentValue = newValue
Array.from(subscribers).forEach((cb) => cb())
}
})
@@ -470,11 +734,15 @@ export function createConnection<
}
}
- return new Accessor(() => value, subscribe)
+ function get(): T {
+ return currentValue
+ }
+
+ return new Accessor(get, subscribe)
}
/**
- * Create a signal from a provier function.
+ * Create a reactive value from a provier function.
* The provider is called when the first subscriber appears and the returned dispose
* function from the provider will be called when the number of subscribers drop to zero.
*
@@ -490,19 +758,16 @@ export function createConnection<
* @param init The initial value
* @param producer The producer function which should return a cleanup function
*/
-export function createExternal(
- init: T,
- producer: (set: Setter) => DisposeFunction,
-): Accessor {
+export function createExternal(init: T, producer: (set: Setter) => DisposeFn): Accessor {
let currentValue = init
- let dispose: DisposeFunction
- const subscribers = new Set()
+ let dispose: DisposeFn
+ const subscribers = new Set()
- const subscribe: SubscribeFunction = (callback) => {
+ function subscribe(callback: Callback): DisposeFn {
if (subscribers.size === 0) {
dispose = producer((v: unknown) => {
const newValue: T = typeof v === "function" ? v(currentValue) : v
- if (newValue !== currentValue) {
+ if (!Object.is(newValue, currentValue)) {
currentValue = newValue
Array.from(subscribers).forEach((cb) => cb())
}
@@ -522,7 +787,6 @@ export function createExternal(
return new Accessor(() => currentValue, subscribe)
}
-/** @experimental */
type Settings> = {
[K in keyof T as Uncapitalize>]: Accessor>
} & {
@@ -530,8 +794,6 @@ type Settings> = {
}
/**
- * @experimental
- *
* Wrap a {@link Gio.Settings} into a collection of setters and accessors.
*
* Example:
@@ -557,7 +819,7 @@ export function createSettings>(
keys: T,
): Settings {
return Object.fromEntries(
- Object.entries(keys).flatMap(([key, type]) => [
+ Object.entries(keys).flatMap<[any, any]>(([key, type]) => [
[
camelify(key),
new Accessor(