Skip to content

v1.3.0

Latest

Choose a tag to compare

@eugeniodepalo eugeniodepalo released this 21 Aug 14:12
· 1 commit to main since this release
2b6de1f

Read the announcement: GTKX 1.3: Introducing @gtkx/animated.

New Features

  • @gtkx/animated brings React Spring to GTKuseSpring, useSprings, useTrail, useTransition, useChain and useSpringValue, the Spring, Trail and Transition components, and the animated(Component) wrapper with its animated.GtkLabel shorthand drive real GObject properties. The wrapper writes each frame straight onto the widget through its ref, so a running spring does not re-render the component, while a prop with no native setter, such as the accessible* props, falls back to a React render per frame. Frames come from the GTK frame clock, and useReducedMotion reads GTK's gtk-enable-animations and gtk-interface-reduced-motion settings.
  • A style prop on every widget — Every element that renders a Gtk.Widget takes a style object of CSS declarations, the way React DOM spells them. GTK4 has no inline styles, so the object compiles to a single rule in a Gtk.CssProvider that belongs to that widget alone, registered one step above STYLE_PROVIDER_PRIORITY_APPLICATION so style outranks anything a class in cssClasses sets. Changing the object rewrites that one rule, so the cost does not grow with the number of distinct values a component cycles through; setting the prop to undefined or null removes it again. A key starting with & nests a block under a selector derived from it, so "&:hover" styles the hover state, and a bare number gets px where CSS expects a length. The prop is typed as a curated list of the paint and typography properties GTK4 actually understands rather than the whole web set, so style={{ display: "flex" }} fails to compile instead of becoming a runtime warning — layout stays in the widget's own props. Style and StyleProperties are new public types.
  • Springs drive style@gtkx/animated writes style per frame the way it writes a GObject property, through the widget's ref and without re-rendering the component, which is what makes a color, a border-radius or a box-shadow animatable at all, none of which GTK exposes as a property. A spring can be the whole prop, style={level.to(declarations)}, or sit on a single declaration, style={{ color: styles.color }}, so the object a spring hook returns can be handed to style as it is, the way React Spring is written for the DOM. Only style is read that way: a spring nested inside any other object-valued prop is left alone.
  • Cairo ships as @gtkx/cairo — Contexts, surfaces, patterns, regions, matrices, font faces, font options, scaled fonts and devices are real classes. Surface, Pattern and FontFace are abstract, and instances arrive as the most specific class the package models, so surface instanceof ImageSurface and ctx.getSource() instanceof LinearPattern narrow, while kinds with no dedicated subclass — a solid or surface pattern, a group target — come back as the base class.
  • registerClass declares signals — A signals option creates GObject signals on the new type, each naming its paramTypes and optional returnType as numeric GTypes or as classes carrying one, its flags, and an accumulator of first-wins or true-handled. connect, emit, on, once, off and useSignal take the declared names, with progress_changed and progress-changed reaching the same signal, and emit converts each argument into a GValue of the declared type. SignalSpec and SignalGType are new public types.
  • GValue parameters take a plain JavaScript value — Every parameter the callee only reads is typed GObject.Value | JsValue and accepts the JavaScript value itself, with the GType inferred: string to gchararray, boolean to gboolean, a whole in-range number to gint, any other number to gdouble, bigint to gint64 or guint64, an array of strings to GStrv, a wrapper to the GType it carries, and null to a NULL gpointer. Arguments of an emitted signal infer the same way, and passing a GObject.Value built by hand still works wherever inference would guess something else. A signal handler still receives a real GObject.Value.
  • GType parameters take a class — Every parameter that takes a GType accepts the class registered under it alongside the numeric bigint, so Gio.ListStore.new(Gtk.Label) and GObject.typeName(Gtk.Label) work, as do signal arguments declared as GTypes. A class that never went through registerClass is rejected rather than resolving to its parent's type. Only the input direction widens: return values, out parameters and handler arguments still hand back the numeric GType.
  • Abstract registered typesabstract: true registers the type with G_TYPE_FLAG_ABSTRACT, the way the flag marks a C type. Registered subclasses instantiate as usual and inherit its vfunc overrides, while constructing the class itself throws, from JavaScript and from a native caller alike.
  • classInit and cssName on registerClassregisterClass takes a classInit hook and a cssName, and every generated *Class GTypeStruct wrapper is now paired with its class. GObject.ObjectClass.peek and Gtk.WidgetClass.peek hand back any type's class struct outside a classInit hook, so GObject.ObjectClass.peek(Gtk.Label).findProperty("label") works, backed by the new peekTypeClass and registerClassStruct runtime exports.
  • Property overrides with paramSpecOverrideparamSpecOverride(name, source) redeclares a property a parent class or an implemented interface already carries, the way g_param_spec_override does in C, giving the subclass its own storage and notify emission while the value type, flags and default stay the ones the overridden spec declares. The source is a wrapper class, an interface, or a raw GType, and the call throws when it declares no property under that name. newParamSpecOverride is the matching @gtkx/runtime export.
  • ParamSpec introspection — Every GObject.ParamSpec carries readonly name, nick, blurb, flags, valueType and ownerType getters, whether it comes from a paramSpec* constructor, a notify handler, or findProperty and listProperties. flags is the ParamFlags bitfield the spec was created with, valueType the GType of the values the property holds, and ownerType the GType the spec is installed on, TYPE_INVALID until it is installed on one. getParamSpecFlags, getParamSpecValueType and getParamSpecOwnerType back them from @gtkx/runtime.
  • fromVariant takes a variant alone and unpacks recursivelyfromVariant no longer requires a type string: it accepts a GLib.Variant on its own, and an options object with recursive: true unwraps every nested variant into the value it holds, all the way down. toVariant accepts Uint8Array or number[] for a byte array either way. FromVariantOptions, RecursiveFromVariantOptions, RecursiveVariantValue, VariantInput and ByteArray are new exported types.
  • Bare @gtkx/jsx imports — The generated JSX store gains an index module, so import { GtkLabel } from "@gtkx/jsx" reaches every namespace's components without the per-namespace subpath. The undeclared-library check in the CLI resolves and diagnoses the bare import, with its own message when the store has no index module.
  • v2ValueReturns, v2FinishResults and v2InoutReturns future flags — The future config block takes three new flags, all off by default and unconditional in 2.0. v2ValueReturns makes the bindings whose return or caller-allocated out parameter is a GValueGtk.DropTarget.getValue, Gtk.ConstantExpression.getValue, Gdk.Clipboard.readValueAsync, Gtk.Builder.valueFromStringType, Gtk.TreeModel.getValue and a handful more — hand back what the value holds, typed unknown. v2FinishResults drops the always-true leading success boolean from promisified async methods, so Gio.File.loadContentsAsync resolves to [number[], string | null], or [Uint8Array, string | null] with v2ByteArrays also enabled, and a call left with a single out parameter, such as replaceContentsAsync, resolves to that value directly. v2InoutReturns stops repeating a caller-allocated inout record in a method's result, because the callee mutates the instance you passed and the returned entry was always the same object you already hold, so Gsk.Path.getNext(point) becomes boolean rather than [boolean, PathPoint] and Pango.Matrix.transformRectangle(rect) becomes void; primitive inout parameters, which cannot be mutated in place, stay in the result either way. Flip them and run tsc: every site that needs attention is a type error.
  • New @gtkx/runtime exports — The GValue marshalling set fromValue, toValueHandle, tryToValueHandle, ValueMarshalError and JsValue is public, alongside coerceObjectProperty, registerWrapperClassResolver with its WrapperClassResolver type, matchRegex and matchAllRegex, which keep a regex subject's bytes alive alongside its MatchInfo, and trimFinish.

Breaking Changes

  • on<SignalName> methods become signal default handlersregisterClass installs every method matching /^on[A-Z]/ as the class-closure default handler for the kebab-cased signal it names, GJS-style, on every call, walking the whole prototype chain up to the generated wrapper class. This retroactively promotes helpers written before the feature existed: an onShow, onDestroy, onMap or onNotify method on a subclass now runs on every emission of that inherited signal. Audit every registered class for on-prefixed methods and rename any that were not meant to be handlers; a name matching no real signal is left alone.
  • Cairo moves into @gtkx/cairo, and @gtkx/gi/cairo is deprecated@gtkx/gi/cairo still resolves for all of 1.x and re-exports @gtkx/cairo, but a project that does not declare @gtkx/cairo gets the copy the code generator links into the generated store plus a per-run notice, because 2.0 stops doing that; install @gtkx/cairo and point imports at it. Constructors and create* factories check the cairo status and throw instead of handing back a broken object — new Context(finishedSurface), new ImageSurface(...), ImageSurface.create and createFromPng, Surface.createSimilar, createSimilarImage and createForRectangle, new RecordingSurface(...) and the Pattern.create* factories — so code that inspected status() afterwards must move to try/catch. Surface.createSimilarImage is typed as returning ImageSurface rather than Surface, and the *ConstructorProps types survive as deprecated Record<string, never> stubs removed in 2.0.
  • Unsafe C symbols are removed from the generated store — C symbols that double-free, thread-hop, or move raw pointers are marked non-introspectable, so the members vanish from the generated store and calling code stops compiling. Safe siblings remain for most of them — the typed setAttributeString, setAttributeUint32 and friends, addAction on the action group, asciiStrup, asciiStrdown, strstrLen, strrstr and internString, logSetHandler, logRemoveHandler and logWriterDefault, and GLib.Thread.self() and join — so move each call to those. The raw object data accessors have no replacement: keep per-instance data in JavaScript instead.
    • GObject.Object.prototype.getData, setData, setDataFull, stealData, getQdata, setQdata, setQdataFull, stealQdata, inherited by every generated class
    • GObject.ParamSpec.prototype.getQdata, setQdata, setQdataFull, stealQdata
    • GLib.Thread.new, tryNew and exit, plus the module-level GLib.threadNew, threadTryNew and threadExit
    • GLib.refStringNew, refStringNewLen, refStringNewIntern, refStringAcquire, refStringRelease, refStringLength
    • GLib.stpcpy, strcanon, strchomp, strchug, strdelimit, strdown, strup, strreverse
    • GLib.asciiDtostr, GLib.asciiFormatd, GLib.logSetWriterFunc
    • Gio.ActionMap.prototype.addActionEntries, and with it Gio.SimpleActionGroup, Gio.Application and Gtk.Application
    • Gio.FileInfo.prototype.setAttribute and Gio.File.prototype.setAttribute
  • Missing required arguments and invalid flag bits throw — A non-nullable argument that is omitted or passed as undefined throws instead of being marshalled as garbage, so GLib.Uri.escapeString("a b"), label.setText() and Gtk.acceleratorParse() all raise, while nullable arguments may still be omitted, skipped as undefined, or followed by extra trailing arguments. A flags argument is validated against the registered bits, so Gtk.acceleratorGetLabel(key, 1 << 15) or any value carrying an undefined bit throws where it previously passed through; zero and the union of every defined bit stay valid. Fix the call sites the exceptions point at.
  • Number props are fitted to the property instead of refused — A number headed for a whole-number property (gint, an enum, a flags field) is truncated toward zero and clamped to the ParamSpec range on the React reconciler path, in @gtkx/animated, and in the construct-property path of generated wrapper classes, so <GtkBox marginTop={12.7} /> and new Gtk.Label({ marginStart: 12.6, opacity: 1.5 }) succeed as 12, 12 and 1 where they previously threw. Imperative writes truncate the same way — label.marginStart = 12.7 and label.setProperty("margin-start", 12.7) land 12 — while a non-finite number or one outside the property's range is still refused. Code that relied on the old error to catch a bad computation must check the value itself.
  • @gtkx/testing installs the React act environment itselfregisterTestRuntimeHooks sets IS_REACT_ACT_ENVIRONMENT in a beforeAll and restores the previous value in an afterAll, and it runs on import, so every project using @gtkx/testing gets it. Test setups that set the global themselves can delete that boilerplate, since the hook saves and restores whatever it found. Projects that never set it are in an act environment for the first time, so imperative mutations made outside act() — calling a selection-model method, activateAction, setText, or a store action directly — now warn and must be wrapped in act(() => …).
  • registerClass validates the GType nametypeName, or the class name it falls back to, must match /^[A-Za-z_][A-Za-z0-9\-_+]{2,}$/, a letter or underscore followed by at least two more characters drawn from letters, digits, -, _ and +, and anything else raises a TypeError instead of reaching g_type_register_static and producing a broken type behind a g_critical. A two-character class name, or one carrying a $ or a dot, now fails at registration; pass an explicit typeName.
  • registerClass rejects Gio.AsyncInitable without vfuncInitAsync — Listing Gio.AsyncInitable in implements without a vfuncInitAsync anywhere on the class chain throws a TypeError, because the default init_async slot runs vfuncInit on a worker thread and crashes the process. The guard checks only interfaces the class actually adopts, so it is skipped when the parent type already implements Gio.AsyncInitable. Add a vfuncInitAsync override, or drop the interface from the list.
  • Plain-struct ownership transfers are dropped from the store — Callables that take or hand over ownership of a plain struct, one that is neither boxed nor carries a ref/unref pair, are refused because neither side can free it correctly, which removes GLib.PathBuf.prototype.free and freeToPath, GLib.OptionContext.prototype.free, GLib.StringChunk.prototype.free, GObject.ParamSpecPool.prototype.free, and GObject.enumCompleteTypeInfo and flagsCompleteTypeInfo. Use the non-consuming sibling where one exists — PathBuf.clearToPath() for freeToPath(), PathBuf.clear() and StringChunk.clear() for free() — and otherwise drop the call and let the wrapper's own lifetime handle it.
  • Callables with detached callback closure or destroy slots are dropped — A callable whose callback parameter has a closure or destroy index that is not immediately adjacent to it cannot be marshalled safely and is now refused, which removes GLib.Tree.newFull, GLib.Tree.prototype.traverse and JavaScriptCore.Value.prototype.objectDefinePropertyAccessor. Adjacent triples such as Gtk.CustomFilter.new and GLib.AsyncQueue.newFull still marshal; move to the sibling members that stay — GLib.Tree.prototype.foreach and destroy, GLib.Node.prototype.traverse, and JavaScriptCore.Value.prototype.objectDefinePropertyData.
  • UCS-4 conversions return arrays of characters — The return type of GLib.utf8ToUcs4, utf8ToUcs4Fast and utf16ToUcs4 is corrected from the scalar gunichar the GIR describes to the array of characters the C functions produce, so GLib.utf8ToUcs4("a💩b", -1n) yields [["a", "💩", "b"], 6n, 3n] where the binding previously handed back a single character. GLib.ucs4ToUtf8 accepts the array back, throwing on a surrogate, an out-of-range codepoint, a fractional element, or a multi-character string. Update any code that read the old scalar result.
  • Gio.File.replaceContentsBytesAsync is promisified — An override table supplies the finish function the GIR omits, so the binding changes from a void method taking a trailing AsyncReadyCallback to replaceContentsBytesAsync(contents, etag, makeBackup, flags, cancellable?): Promise<[boolean, string | null]>, or Promise<string | null> with future.v2FinishResults enabled. Drop the callback argument and await the call.
  • Record fields holding callbacks are typed bigint — A record field whose GIR type is a callback renders as bigint, a raw function pointer, in both the field accessor and the record's constructor props, matching what actually crosses the boundary. Reads and writes of such fields, GObject.TypeInfo.classInit and GLib.SourceFuncs.prepare among them, stop type-checking against a function and need a pointer value instead.
  • newObjectWithProperties returns the wrapper — The signature changes from (gtype, props, wrapper) => ExternalObject<Handle> to <T extends object>(gtype, props, wrapper: T) => T, because construction may find that the object already reached JavaScript and was wrapped — a Gtk.Window observed through getToplevels() firing items-changed — in which case the existing wrapper is returned so both references stay one object. Direct callers expecting a handle must call getHandle on the result.
  • v2ByteArrays also governs what fromVariant unpacks an ay to — A byte-array variant unpacks through getDataAsBytes().unrefToArray() rather than element by element, so with future.v2ByteArrays enabled fromVariant("ay", v) returns a Uint8Array instead of a number[], at both the type and the runtime level. Projects already on the flag must update any .push, .concat, Array.isArray or JSON.stringify use on that result; projects without it see no change. toVariant's value parameter widens to VariantInput<S>, accepting Uint8Array | number[], and throws when a byte array is packed from anything else.
  • Deprecated in 1.3, removed in 2.0Gdk.RGBA.create(css) gives way to new RGBA() plus parse(...) with its return value checked, so an unparseable string is rejected rather than becoming transparent black, or to new RGBA({ red, green, blue, alpha }); Graphene.Point.create(x, y) becomes new Point({ x, y }), Graphene.Rect.create(x, y, w, h) becomes new Rect() plus init(...), and Graphene.Size.create(w, h) becomes new Size({ width, height }). GObject.buildValue(gtype, populate) is deprecated too: pass the JavaScript value itself where a Value is expected, or build one with new Value() and init when the GType is one inference cannot name. All still work in 1.x and surface a deprecation in the editor.

Bug Fixes

  • A Gdk.Surface is destroyed before its last reference drops, so collecting one no longer crashes the app.
  • Structs, boxed types and fundamentals no longer leak or double-free when a binding transfers ownership across the boundary.
  • GLib.Regex match results no longer point at a freed subject string.
  • An object that reaches JavaScript mid-construction no longer ends up with a second, conflicting wrapper.
  • The same Gsk.RenderNode or Gtk.Expression always comes back as the same JavaScript object.
  • A vfunc slot handed a callback, such as Gio.AsyncInitable's init_async, receives a real callable instead of a raw pointer.
  • An error thrown from a vfunc or a callback reaches the C caller as a real GError instead of being swallowed.
  • GValues and properties holding a GByteArray read and write as bytes instead of failing.
  • Caller-allocated array results such as Graphene.Matrix.toFloat and Gdk.TextureDownloader are returned instead of dropped.
  • Tree-list selection keeps reporting the right row ids after expanding a branch shifts rows.
  • prefers-color-scheme, prefers-contrast and prefers-reduced-motion blocks in CSS templates apply instead of being silently ignored.
  • Selecting many rows at once no longer costs an FFI call per row.