@gjsify/react-native 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,17 +8,15 @@ export type ComponentProvider = () => ComponentType<Record<string, unknown>>;
8
8
  * `runApplication`'s parameters: React Native's `initialProps` plus the window
9
9
  * shape, over the WHOLE option set of the application shell this layer runs on.
10
10
  *
11
- * `Omit<AdwaitaAppOptions, 'createWindow'>` rather than a hand-copied list of the
12
- * fields worth forwarding, and that is the load-bearing part. The shell owns
13
- * `devtools`, `about`, `onStartup`, `quitAction`, `flags` and `css`, each wired at
14
- * the lifecycle moment only the shell knows — `installDevtools` in particular MUST
15
- * run in `startup`, because the bus connection and object path exist only after the
16
- * application has registered, which is inside `runAdwaitaApp` and unreachable from
17
- * a consumer's entry file. A forwarding list would have to grow every time the shell
18
- * gains an option, and the field nobody remembered to add is exactly the one a
19
- * consumer needs: `devtools` was that field, and its absence is why an application
20
- * on this layer could not be driven from outside (#1455). `createWindow` is the one
21
- * option this layer answers itself — it renders the registered component.
11
+ * `Omit<AdwaitaAppOptions, 'createWindow'>` rather than a hand-copied list of the fields worth
12
+ * forwarding, and that is the load-bearing part. The shell owns `devtools`, `about`, `onStartup`,
13
+ * `quitAction`, `flags` and `css`, each wired at the lifecycle moment only the shell knows —
14
+ * `installDevtools` in particular MUST run in `startup`, because the bus connection and object path
15
+ * exist only after the application has registered, which is inside `runAdwaitaApp` and unreachable
16
+ * from a consumer's entry file. A forwarding list would have to grow every time the shell gains an
17
+ * option, and the field nobody remembered to add is exactly the one a consumer needs: `devtools`
18
+ * was that field, and its absence is why an application on this layer could not be driven from
19
+ * outside (#1455). `createWindow` is the one option this layer answers itself.
22
20
  */
23
21
  export interface RunApplicationOptions extends Omit<AdwaitaAppOptions, 'createWindow'> {
24
22
  /**
@@ -45,36 +43,29 @@ export declare const windowChromeChecks: () => number;
45
43
  /**
46
44
  * Everything `runApplication` hands the application shell, as a VALUE.
47
45
  *
48
- * A named function with a spec rather than an object literal inline, because the
49
- * defect it prevents is invisible at the call site: the previous shape listed
50
- * `applicationId` and `css` and dropped every other field, which reads as complete
51
- * and silently swallowed `devtools` — the option a consumer needs to be able to
52
- * drive their own application at all (#1455). `app-registry.spec.ts` asserts that
53
- * NOTHING is dropped, whatever it is, so re-introducing a field list fails for any
54
- * option rather than only for the ones a test happened to name.
46
+ * A named function with a spec rather than an object literal inline, because the defect it
47
+ * prevents is invisible at the call site: an inline shape that listed `applicationId` and `css` and
48
+ * dropped every other field reads as complete and silently swallowed `devtools` (#1455).
49
+ * `app-registry.spec.ts` asserts that NOTHING is dropped, whatever it is.
55
50
  *
56
- * `createWindow` last: it is the one option this layer answers itself, and a
57
- * consumer's `createWindow` must not be able to replace the React root.
51
+ * `createWindow` last: it is the one option this layer answers itself, and a consumer's
52
+ * `createWindow` must not be able to replace the React root.
58
53
  */
59
54
  export declare function toShellOptions(options: RunApplicationOptions, createWindow: (app: Adw.Application) => Gtk.Window): AdwaitaAppOptions;
60
55
  /**
61
56
  * Build the window, mount the registered component into it, publish the chrome.
62
57
  *
63
- * A NAMED FUNCTION AND NOT THE CLOSURE IT WAS, for the reason `window-chrome.ts` gives
64
- * one function earlier: the vectors have to measure THIS composition, and a spec that
65
- * rebuilt it by hand passes while the shipping shell drifts. That is #1549 as measured
66
- * — deleting `provideWindowChrome(chrome, …)` from the render call below left `oxfmt`
67
- * clean, `oxlint` at 0, `tsc` at 0 and the whole `@gjsify/react-native` suite green,
68
- * ten hand-written window-chrome vectors included, while the running application drew
69
- * two mapped `AdwHeaderBar`s: two sets of window controls, one dead close button
70
- * (#1460).
58
+ * A NAMED FUNCTION AND NOT THE CLOSURE IT WAS, for the reason `window-chrome.ts` gives one
59
+ * function earlier: the vectors have to measure THIS composition, and a spec that rebuilt it by hand
60
+ * passes while the shipping shell drifts. That is #1549 as measured — deleting
61
+ * `provideWindowChrome(chrome, …)` from the render call below left `oxfmt` clean, `oxlint` at 0,
62
+ * `tsc` at 0 and the whole `@gjsify/react-native` suite green, ten hand-written window-chrome
63
+ * vectors included, while the running application drew two mapped `AdwHeaderBar`s (#1460).
71
64
  *
72
- * The seam is HERE and not one call further out because `runApplication` cannot be
73
- * entered from a spec at all: it runs a `Gio.Application`, and a nested
74
- * `g_application_run` inside the test runner's own main loop never returns — measured,
75
- * `g_application_run: assertion '!application->priv->must_quit_now' failed` and a
76
- * timed-out case. Everything past this function is the shell's, and `toShellOptions`
77
- * already holds the hand-over to it.
65
+ * The seam is HERE and not one call further out because `runApplication` cannot be entered from a
66
+ * spec at all: it runs a `Gio.Application`, and a nested `g_application_run` inside the test
67
+ * runner's own main loop never returns — measured, `g_application_run: assertion
68
+ * '!application->priv->must_quit_now' failed` and a timed-out case.
78
69
  */
79
70
  export declare function mountApplicationRoot(app: Adw.Application, appKey: string, options: RunApplicationOptions, provider: ComponentProvider): Gtk.Window;
80
71
  /**
@@ -73,24 +73,21 @@ export interface RefusedRoute {
73
73
  /**
74
74
  * A prop the LAYER answers by calling a method when a signal fires.
75
75
  *
76
- * `accessibilityLiveRegion` is the whole of it, and the shape is forced by the
77
- * mismatch between the two platforms' models. React Native's prop is a DECLARATION
78
- * — "when this element's content changes, tell the user" — and the platform's
79
- * accessibility layer watches for the change. GTK has no such declaration: since
80
- * 4.14 it has `gtk_accessible_announce(self, message, priority)` (MEASURED on
81
- * gtk 4.22.4 — the method is on the `Gtk.Accessible` interface, so every widget has
82
- * it, and it takes a `GtkAccessibleAnnouncementPriority` of LOW/MEDIUM/HIGH), which
83
- * is an IMPERATIVE call: the caller supplies both the moment and the message.
76
+ * `accessibilityLiveRegion` is the whole of it, and the shape is forced by the mismatch between
77
+ * the two platforms' models. React Native's prop is a DECLARATION — "when this element's
78
+ * content changes, tell the user" — and the platform's accessibility layer watches for the
79
+ * change. GTK has no such declaration: since 4.14 it has `gtk_accessible_announce(self,
80
+ * message, priority)` (MEASURED on gtk 4.22.4 — on the `Gtk.Accessible` interface, so every
81
+ * widget has it), which is an IMPERATIVE call: the caller supplies both the moment and the
82
+ * message.
84
83
  *
85
- * So a live region can only be built where this layer knows both. It knows both on
86
- * exactly one primitive — `Text`, whose content IS one widget property, so
87
- * `notify::label` is the moment and `label` is the message. Everywhere else the
88
- * content is a SUBTREE and GTK emits nothing when one changes, which is why the
89
- * common route refuses the prop by name rather than this route answering it.
84
+ * So a live region can only be built where this layer knows both. It knows both on exactly one
85
+ * primitive — `Text`, whose content IS one widget property, so `notify::label` is the moment
86
+ * and `label` is the message. Everywhere else the content is a SUBTREE and GTK emits nothing
87
+ * when one changes, which is why the common route refuses the prop by name.
90
88
  *
91
- * Like {@link FileRoute} and {@link GestureRoute}, the table holds the whole of the
92
- * DECISION and none of the call: the priority is a `gi://Gtk` enum member and
93
- * nothing under `primitives/` imports `gi://`.
89
+ * Like {@link FileRoute} and {@link GestureRoute}, the table holds the whole of the DECISION
90
+ * and none of the call: nothing under `primitives/` imports `gi://`.
94
91
  */
95
92
  export interface AnnounceRoute {
96
93
  readonly to: 'announce';
@@ -124,14 +121,13 @@ export interface IgnoredRoute {
124
121
  /**
125
122
  * A prop whose value becomes a `Gio.File` — and the reason it is its own kind.
126
123
  *
127
- * `Gtk.Picture:file` takes a `Gio.File`, an OBJECT, and building one needs
128
- * `gi://Gio`. Nothing under `primitives/` imports `gi://` (that is what makes L2
129
- * testable without a display and reusable by a binding that never loads GTK), so the
130
- * table cannot produce the value — only the DECISION about it. So L2 does the whole
131
- * of the decision, which is where every refusal lives (`http:` has no synchronous
132
- * loader, a `require()` id has no asset registry, an array is a device-scale
133
- * picker), and hands the framework layer a `{ kind, value }` pair it turns into one
134
- * call: `Gio.File.new_for_path` or `Gio.File.new_for_uri`.
124
+ * `Gtk.Picture:file` takes a `Gio.File`, an OBJECT, and building one needs `gi://Gio`.
125
+ * Nothing under `primitives/` imports `gi://` (that is what makes L2 testable without a display
126
+ * and reusable by a binding that never loads GTK), so the table cannot produce the value —
127
+ * only the DECISION about it. So L2 does the whole of the decision, which is where every
128
+ * refusal lives (`http:` has no synchronous loader, a `require()` id has no asset registry, an
129
+ * array is a device-scale picker), and hands the framework layer a `{ kind, value }` pair it
130
+ * turns into one call.
135
131
  */
136
132
  export interface FileRoute {
137
133
  readonly to: 'file';
@@ -143,13 +139,12 @@ export interface FileRoute {
143
139
  /**
144
140
  * A prop bound through a gesture CONTROLLER rather than a signal on the widget.
145
141
  *
146
- * `TouchableWithoutFeedback` is the one primitive that needs it: it has no button
147
- * chrome, so its widget is a `Gtk.Box`, and a box emits no `clicked` (measured —
148
- * `Gtk.Button`'s two signals are `activate` and `clicked`, and a box has neither).
149
- * The desktop answer is a `Gtk.GestureClick` added to the widget, whose `released`
150
- * signal is the press completing. A controller is `add_controller(new
151
- * Gtk.GestureClick())` — a constructed OBJECT, so the same rule as {@link FileRoute}
152
- * applies: the table names the signal, the framework layer builds the controller.
142
+ * `TouchableWithoutFeedback` is the one primitive that needs it: it has no button chrome, so
143
+ * its widget is a `Gtk.Box`, and a box emits no `clicked` (measured — `Gtk.Button`'s two
144
+ * signals are `activate` and `clicked`, and a box has neither). The desktop answer is a
145
+ * `Gtk.GestureClick` added to the widget, whose `released` signal is the press completing. A
146
+ * controller is a constructed OBJECT, so the same rule as {@link FileRoute} applies: the table
147
+ * names the signal, the framework layer builds the controller.
153
148
  */
154
149
  export interface GestureRoute {
155
150
  readonly to: 'gesture';
@@ -8,12 +8,11 @@ export interface TabScreenOptions {
8
8
  /**
9
9
  * The switcher button's icon, as an icon-theme name (`go-home-symbolic`).
10
10
  *
11
- * NOT OPTIONAL DECORATION, which is why it is here rather than left to the
12
- * application. `Adw.ViewSwitcher` reserves the icon whether or not one is set —
13
- * MEASURED, the same five tabs measure 317/647 px with icons and without — and a
14
- * page with no `icon-name` draws the icon theme's missing-image glyph in the space
15
- * it reserved. So the choice was never "icons or no icons"; it was "your icon or a
16
- * broken one".
11
+ * NOT OPTIONAL DECORATION, which is why it is here rather than left to the application.
12
+ * `Adw.ViewSwitcher` reserves the icon whether or not one is set — MEASURED, the same five
13
+ * tabs measure 317/647 px with icons and without — and a page with no `icon-name` draws the
14
+ * icon theme's missing-image glyph in the space it reserved. So the choice was never "icons or
15
+ * no icons"; it was "your icon or a broken one".
17
16
  *
18
17
  * A name the theme does not carry draws that same glyph, and nothing says so.
19
18
  * `Gtk.IconTheme.has_icon()` is the check worth running when picking one.
@@ -23,33 +22,25 @@ export interface TabScreenOptions {
23
22
  /**
24
23
  * Put the stack on the focused page, or leave it alone because that page is not there yet.
25
24
  *
26
- * THE PRESENCE CHECK IS THE TERMINATING CONDITION, and the reason it has to be explicit is
27
- * that the obvious guard is a bet rather than a test. Writing
28
- *
29
- * if (stack.get_visible_child_name() !== focused) stack.set_visible_child_name(focused);
30
- *
31
- * reads as "set it unless it is already set", and it terminates only when the set TAKES.
25
+ * THE PRESENCE CHECK IS THE TERMINATING CONDITION, and the obvious guard is a bet rather than a
26
+ * test. Writing `if (stack.get_visible_child_name() !== focused) stack.set_visible_child_name(
27
+ * focused)` reads as "set it unless it is already set", and it terminates only when the set TAKES.
32
28
  * MEASURED on libadwaita 1.9.3: `set_visible_child_name` with a name the stack does not hold
33
- * changes nothing, emits NO `notify::visible-child-name`, and prints
34
- * `Adwaita-WARNING: Child name '…' not found in AdwViewStack`. So `get_visible_child_name()`
35
- * still answers the old page, the comparison is still unequal, and the effect — which has no
36
- * dependency array on purpose, because a page can materialise on a render where `focused` did
37
- * not change — repeats the failing call on every render for as long as the mismatch lasts.
29
+ * changes nothing, emits NO `notify::visible-child-name`, and prints `Adwaita-WARNING: Child name
30
+ * '…' not found in AdwViewStack`. So the getter still answers the old page, the comparison is
31
+ * still unequal, and the effect — which has no dependency array on purpose, because a page can
32
+ * materialise on a render where `focused` did not change — repeats the failing call on every
33
+ * render for as long as the mismatch lasts.
38
34
  *
39
- * A focused route whose page React has not committed yet is an ORDINARY intermediate state,
40
- * not a fault: the effect runs after the commit that added the OTHER pages, and the missing
41
- * one arrives on a later commit. So the answer is to do nothing and let the next render try,
42
- * which is what "not there yet" deserves — and one warning per render for a normal state is
43
- * how a log stops being read.
35
+ * A focused route whose page React has not committed yet is an ORDINARY intermediate state, not a
36
+ * fault, so the answer is to do nothing and let the next render try — one warning per render for a
37
+ * normal state is how a log stops being read.
44
38
  *
45
- * AND THE SET HAS A SECOND WAY TO DO NOTHING, which is why presence alone is not the whole
46
- * gate. `adw_view_stack_set_visible_child_name` ends on
47
- * `if (gtk_widget_get_visible (page->widget)) set_visible_child (...)`, so a page that is
48
- * there but HIDDEN changes nothing, emits no notify and — unlike the missing name — prints
49
- * NOTHING AT ALL (measured). That is the same non-terminating state by the quieter road: the
50
- * caller would be told the stack shows `focused` while it shows the other page, and no log
51
- * anywhere would disagree. Both no-op paths are therefore asked about BEFORE the write, which
52
- * is also what keeps each one attributable to its own failing assertion.
39
+ * AND THE SET HAS A SECOND WAY TO DO NOTHING, which is why presence alone is not the whole gate.
40
+ * `adw_view_stack_set_visible_child_name` ends on `if (gtk_widget_get_visible (page->widget))
41
+ * set_visible_child (...)`, so a page that is there but HIDDEN changes nothing, emits no notify and
42
+ * — unlike the missing name — prints NOTHING AT ALL (measured). That is the same non-terminating
43
+ * state by the quieter road. Both no-op paths are therefore asked about BEFORE the write.
53
44
  *
54
45
  * @returns whether the stack now shows `focused`.
55
46
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/react-native",
3
- "version": "0.53.0",
3
+ "version": "0.55.0",
4
4
  "description": "The React Native view vocabulary rendered onto GTK4/Adwaita, over @gjsify/gtk-host",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -120,8 +120,8 @@
120
120
  "react-native"
121
121
  ],
122
122
  "dependencies": {
123
- "@gjsify/adwaita-app": "^0.53.0",
124
- "@gjsify/gtk-host": "^0.53.0"
123
+ "@gjsify/adwaita-app": "^0.55.0",
124
+ "@gjsify/gtk-host": "^0.55.0"
125
125
  },
126
126
  "peerDependencies": {
127
127
  "@react-navigation/core": "^7.21.13",
@@ -152,9 +152,9 @@
152
152
  "@girs/gdk-4.0": "5.4.0",
153
153
  "@girs/gjs": "5.4.0",
154
154
  "@girs/gtk-4.0": "5.4.0",
155
- "@gjsify/cli": "^0.53.0",
156
- "@gjsify/rolldown-plugin-gjsify": "^0.53.0",
157
- "@gjsify/unit": "^0.53.0",
155
+ "@gjsify/cli": "^0.55.0",
156
+ "@gjsify/rolldown-plugin-gjsify": "^0.55.0",
157
+ "@gjsify/unit": "^0.55.0",
158
158
  "@react-navigation/core": "^7.21.13",
159
159
  "@react-navigation/routers": "^7.6.4",
160
160
  "@types/node": "^25.9.2",