@gjsify/adwaita-app 0.16.5

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.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @gjsify/adwaita-app
2
+
3
+ Native Adwaita application shell for GJS/GTK apps. It wires the boilerplate every
4
+ native Adwaita app repeats — the `runAsync` lifecycle, the opt-in
5
+ [`@gjsify/devtools`](../devtools) control plane, a startup CSS bootstrap, standard
6
+ `app.quit`/`app.about` actions, a data-driven `Adw.NavigationSplitView` nav shell,
7
+ an async-view mounter, and promise-based dialog/toast/file helpers.
8
+
9
+ It is **composition-first**: opt-in wiring for the parts that are pure
10
+ boilerplate, never a wrapper that hides `Adw`/`Gtk`. You still write your views as
11
+ plain `Gtk.Widget`s.
12
+
13
+ > GJS-only (`runtimes.gjs = polyfill`, everything else `none`). Tier 2.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ gjsify install @gjsify/adwaita-app
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ```ts
24
+ import Adw from '@girs/adw-1';
25
+ import { runAdwaitaApp, createNavShell, type NavItem } from '@gjsify/adwaita-app';
26
+
27
+ const NAV: NavItem[] = [
28
+ { id: 'overview', label: 'Overview', icon: 'go-home-symbolic' },
29
+ { id: 'reports', label: 'Reports', icon: 'x-office-spreadsheet-symbolic' },
30
+ ];
31
+
32
+ class MainWindow extends Adw.ApplicationWindow {
33
+ static { imports.gi.GObject.registerClass({ GTypeName: 'MyMainWindow' }, MainWindow); }
34
+ constructor(app: Adw.Application) {
35
+ super({ application: app, defaultWidth: 1000, defaultHeight: 700 });
36
+ const shell = createNavShell(this, {
37
+ items: NAV,
38
+ sidebarTitle: 'My App',
39
+ onSelect: (item) => shell.stack.set_visible_child_name(item.id),
40
+ });
41
+ shell.stack.add_named(buildOverview(), 'overview');
42
+ shell.stack.add_named(buildReports(), 'reports');
43
+ this.set_content(shell.widget);
44
+ shell.selectById('overview');
45
+ }
46
+ }
47
+
48
+ await runAdwaitaApp({
49
+ applicationId: 'org.example.App',
50
+ createWindow: (app) => new MainWindow(app),
51
+ css: '/* optional app CSS, loaded display-wide on startup */',
52
+ about: { applicationName: 'My App', version: '1.0.0', developerName: 'Me' },
53
+ // devtools omitted → gated on the GJSIFY_DEVTOOLS env var (safe in prod).
54
+ });
55
+ ```
56
+
57
+ `runAdwaitaApp` uses `Adw.Application.runAsync()` — **not** sync `run()` — so a
58
+ synchronous view load does not hang its spinner (GJS does not flush the
59
+ promise-job queue under `run()`).
60
+
61
+ ## API
62
+
63
+ ### Application
64
+
65
+ - `runAdwaitaApp(options): Promise<number>` — construct + run, resolve with the
66
+ exit code. `AdwaitaAppOptions`: `applicationId`, `createWindow`, optional
67
+ `flags`, `css`, `about` (`AboutInfo`), `quitAction` (default on, `<primary>q`),
68
+ `devtools` (`true` | `InstallDevtoolsOptions` | omitted = env-gated),
69
+ `onStartup`.
70
+ - `AdwaitaApp` — the configured `Adw.Application` subclass, if you need the
71
+ instance instead of `runAdwaitaApp`.
72
+
73
+ ### Navigation shell
74
+
75
+ - `createNavShell(window, options): NavShell` — builds the
76
+ `Adw.NavigationSplitView` (sidebar `Gtk.ListBox` + content `Gtk.Stack`) into
77
+ `window` (which owns the responsive `Adw.Breakpoint`, default `max-width: 720px`).
78
+ Returns `{ widget, stack, contentHeader, selectById, selectByIndex }`.
79
+ - `resolveInitialNavIndex(items, wantedId?)`, `findNavItem(items, id)` — pure
80
+ helpers (e.g. to turn a `${PREFIX}_VIEW` dev hook into a start index).
81
+
82
+ ### Async view mounting
83
+
84
+ - `LoadToken` + `loadIntoStack({ stack, token, load, fill, onError? })` — show a
85
+ loading page, run `load` (sync or async), then `fill` + show content — dropping
86
+ a result a newer reload superseded, and showing the error page on failure.
87
+
88
+ ### Interaction helpers
89
+
90
+ - `confirmDialog(parent, { heading, body?, confirmLabel?, cancelLabel?, destructive? }): Promise<boolean>`
91
+ - `errorDialog(parent, heading, body?): Promise<void>`
92
+ - `registerToastOverlay(overlay)` + `showToast(title, timeout?)`
93
+ - `pickFile(parent, { title?, filters? }): Promise<string | null>` /
94
+ `saveFile(parent, { title?, filters?, initialName? }): Promise<string | null>`
95
+
96
+ ### Dev hooks
97
+
98
+ - `readAppDevHooks({ prefix, env? }): { view?, file?, debug }` — the
99
+ `${PREFIX}_VIEW` / `${PREFIX}_FILE` / `${PREFIX}_DEBUG` env pattern (e.g.
100
+ `MYAPP_VIEW=reports myapp` to open straight to a view in dev).
101
+
102
+ ## See also
103
+
104
+ - [`@gjsify/devtools`](../devtools) — the DBus control plane wired in on startup.
105
+ - [`@gjsify/storybook`](../storybook) — the component browser this shell was
106
+ generalized from ([ADR 0009](../../../docs/adr/0009-native-adwaita-app-shell.md)).
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gdk-4.0";import n from"@girs/gio-2.0";import r from"@girs/gobject-2.0";import i from"@girs/gtk-4.0";import{installDevtools as a}from"@gjsify/devtools";var o=class AdwaitaApp extends e.Application{static{r.registerClass({GTypeName:`GjsifyAdwaitaApp`},AdwaitaApp)}constructor(e){super({application_id:e.applicationId,flags:e.flags??n.ApplicationFlags.DEFAULT_FLAGS}),this._window=null,this._options=e,this._initActions(),this.connect(`startup`,()=>this._onStartup()),this.connect(`activate`,()=>this._onActivate())}_initActions(){if(this._options.quitAction!==!1){let e=new n.SimpleAction({name:`quit`});e.connect(`activate`,()=>this.quit()),this.add_action(e),this.set_accels_for_action(`app.quit`,[`<primary>q`])}if(this._options.about){let e=new n.SimpleAction({name:`about`});e.connect(`activate`,()=>this._showAbout()),this.add_action(e)}}_onStartup(){this._options.css&&this._loadCss(this._options.css),this._installDevtools(),this._options.onStartup?.(this)}_loadCss(e){let n=t.Display.get_default();if(!n){console.error(`@gjsify/adwaita-app: no default display; CSS not applied`);return}let r=new i.CssProvider;r.load_from_string(e),i.StyleContext.add_provider_for_display(n,r,i.STYLE_PROVIDER_PRIORITY_APPLICATION)}_installDevtools(){let e=this._options.devtools;if(e===!1)return;let t=typeof e==`object`?e:{enabled:e||void 0};a(this,t)}_onActivate(){this._window||=this._options.createWindow(this),this._window.present()}_showAbout(){let t=this._options.about;if(!t)return;let n=new e.AboutDialog({application_name:t.applicationName,application_icon:t.applicationIcon??this._options.applicationId});t.version&&(n.version=t.version),t.developerName&&(n.developer_name=t.developerName),t.website&&(n.website=t.website),t.issueUrl&&(n.issue_url=t.issueUrl),t.license&&(n.license=t.license),t.comments&&(n.comments=t.comments),t.copyright&&(n.copyright=t.copyright),t.developers?.length&&n.set_developers(t.developers),n.present(this.get_active_window())}};async function runAdwaitaApp(e){return new o(e).runAsync([imports.system.programInvocationName,...ARGV])}r.type_ensure(o.$gtype);export{o as AdwaitaApp,runAdwaitaApp};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";function isTruthy(e){if(e==null||e===``)return!1;let t=e.toLowerCase();return t!==`0`&&t!==`false`&&t!==`no`}function readAppDevHooks(e){let t=e.env??globalThis.process?.env??{},n=e.prefix;return{view:t[`${n}_VIEW`]||void 0,file:t[`${n}_FILE`]||void 0,debug:isTruthy(t[`${n}_DEBUG`])}}export{readAppDevHooks};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";function confirmDialog(t,n){return new Promise(r=>{let i=new e.AlertDialog({heading:n.heading,body:n.body??``});i.add_response(`cancel`,n.cancelLabel??`Cancel`),i.add_response(`confirm`,n.confirmLabel??`OK`),i.set_response_appearance(`confirm`,n.destructive?e.ResponseAppearance.DESTRUCTIVE:e.ResponseAppearance.SUGGESTED),i.set_default_response(`confirm`),i.set_close_response(`cancel`),i.connect(`response`,(e,t)=>r(t===`confirm`)),i.present(t)})}function errorDialog(t,n,r){return new Promise(i=>{let a=new e.AlertDialog({heading:n,body:r??``});a.add_response(`ok`,`OK`),a.set_default_response(`ok`),a.set_close_response(`ok`),a.connect(`response`,()=>i()),a.present(t)})}export{confirmDialog,errorDialog};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/gio-2.0";import t from"@girs/gtk-4.0";function buildFilters(n){if(!n?.length)return;let r=new e.ListStore({itemType:t.FileFilter.$gtype});for(let e of n){let n=new t.FileFilter;n.set_name(e.name);for(let t of e.patterns??[])n.add_pattern(t);for(let t of e.mimeTypes??[])n.add_mime_type(t);r.append(n)}return r}function pickFile(e,n={}){return new Promise(r=>{let i=new t.FileDialog({title:n.title??`Open File`}),a=buildFilters(n.filters);a&&i.set_filters(a),i.open(e,null,(e,t)=>{try{r(i.open_finish(t)?.get_path()??null)}catch{r(null)}})})}function saveFile(e,n={}){return new Promise(r=>{let i=new t.FileDialog({title:n.title??`Save File`});n.initialName&&i.set_initial_name(n.initialName);let a=buildFilters(n.filters);a&&i.set_filters(a),i.save(e,null,(e,t)=>{try{r(i.save_finish(t)?.get_path()??null)}catch{r(null)}})})}export{pickFile,saveFile};
@@ -0,0 +1 @@
1
+ import{AdwaitaApp as e,runAdwaitaApp as t}from"./application.js";import{readAppDevHooks as n}from"./dev-hooks.js";import{confirmDialog as r,errorDialog as i}from"./dialogs.js";import{pickFile as a,saveFile as o}from"./file-dialog.js";import{createNavShell as s}from"./nav-shell.js";import{LoadToken as c,loadIntoStack as l}from"./view-loader.js";import{LoadingStack as u}from"./loading-stack.js";import{registerToastOverlay as d,showToast as f}from"./toast.js";import{findNavItem as p,resolveInitialNavIndex as m}from"./nav-model.js";export{e as AdwaitaApp,c as LoadToken,u as LoadingStack,r as confirmDialog,s as createNavShell,i as errorDialog,p as findNavItem,l as loadIntoStack,a as pickFile,n as readAppDevHooks,d as registerToastOverlay,m as resolveInitialNavIndex,t as runAdwaitaApp,o as saveFile,f as showToast};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gobject-2.0";import n from"@girs/gtk-4.0";var r=class LoadingStack extends n.Stack{static{t.registerClass({GTypeName:`GjsifyLoadingStack`},LoadingStack)}constructor(t={}){super(t);let r=new n.Box({orientation:n.Orientation.VERTICAL,halign:n.Align.CENTER,valign:n.Align.CENTER,hexpand:!0,vexpand:!0});r.append(new e.Spinner),this.add_named(r,`loading`),this._content=new e.Bin({hexpand:!0,vexpand:!0}),this.add_named(this._content,`content`),this._error=new e.StatusPage({iconName:`dialog-error-symbolic`,title:`Something went wrong`}),this.add_named(this._error,`error`),this.set_visible_child_name(`loading`)}setContent(e){this._content.set_child(e)}setError(e,t){this._error.set_title(e),this._error.set_description(t??``)}showLoading(){this.set_visible_child_name(`loading`)}showContent(){this.set_visible_child_name(`content`)}showError(){this.set_visible_child_name(`error`)}};t.type_ensure(r.$gtype);export{r as LoadingStack};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";function resolveInitialNavIndex(e,t){if(e.length===0)return-1;if(!t)return 0;let n=e.findIndex(e=>e.id===t);return n>=0?n:0}function findNavItem(e,t){return e.find(e=>e.id===t)}export{findNavItem,resolveInitialNavIndex};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gtk-4.0";function buildNavRow(e){let n=new t.ListBoxRow,r=new t.Box({orientation:t.Orientation.HORIZONTAL,spacing:12,marginTop:8,marginBottom:8,marginStart:12,marginEnd:12});e.icon&&r.append(new t.Image({iconName:e.icon}));let i=new t.Box({orientation:t.Orientation.VERTICAL,hexpand:!0});return i.append(new t.Label({label:e.label,xalign:0})),e.subtitle&&i.append(new t.Label({label:e.subtitle,xalign:0,cssClasses:[`dim-label`,`caption`]})),r.append(i),n.set_child(r),n}function createNavShell(n,r){let i=new e.NavigationSplitView,a=new t.Stack({transitionType:t.StackTransitionType.CROSSFADE,hexpand:!0,vexpand:!0}),o=new t.ListBox({cssClasses:[`navigation-sidebar`]}),s=new e.HeaderBar,c=new e.HeaderBar;r.sidebarTitle&&c.set_title_widget(new e.WindowTitle({title:r.sidebarTitle,subtitle:``})),r.sidebarHeaderStart&&c.pack_start(r.sidebarHeaderStart),r.sidebarHeaderEnd&&c.pack_end(r.sidebarHeaderEnd);let l=new e.ToolbarView;l.add_top_bar(c),l.set_content(o),i.set_sidebar(new e.NavigationPage({title:r.sidebarTitle??`Menu`,child:l}));let u=new e.ToolbarView;u.add_top_bar(s),u.set_content(a),i.set_content(new e.NavigationPage({title:r.sidebarTitle??``,child:u}));for(let e of r.items)o.append(buildNavRow(e));o.connect(`row-selected`,(e,t)=>{if(!t)return;let n=t.get_index(),a=r.items[n];a&&(r.onSelect(a,n),i.get_collapsed()&&i.set_show_content(!0))});let d=r.collapseWidth??720,f=e.BreakpointCondition.parse(`max-width: ${d}px`);if(f){let t=new e.Breakpoint({condition:f});t.add_setter(i,`collapsed`,!0),n.add_breakpoint(t)}let selectByIndex=e=>{let t=o.get_row_at_index(e);t&&o.select_row(t)};return{widget:i,stack:a,contentHeader:s,selectByIndex,selectById(e){let t=r.items.findIndex(t=>t.id===e);selectByIndex(t>=0?t:0)}}}export{createNavShell};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";let t=null;function registerToastOverlay(e){t=e}function showToast(n,r=3){if(!t){console.warn(`@gjsify/adwaita-app: showToast() before registerToastOverlay()`);return}t.add_toast(new e.Toast({title:n,timeout:r}))}export{registerToastOverlay,showToast};
File without changes
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";var LoadToken=class{constructor(){this._current=0}next(){return++this._current}get current(){return this._current}};function loadIntoStack(e){let t=e.loadingName??`loading`,n=e.contentName??`content`,r=e.errorName??`error`;e.stack.set_visible_child_name(t);let i=e.token.next();Promise.resolve().then(e.load).then(t=>{i===e.token.current&&(e.fill(t),e.stack.set_visible_child_name(n))}).catch(t=>{i===e.token.current&&(e.onError?.(t),e.stack.set_visible_child_name(r))})}export{LoadToken,loadIntoStack};
@@ -0,0 +1,56 @@
1
+ import Adw from '@girs/adw-1';
2
+ import Gio from '@girs/gio-2.0';
3
+ import Gtk from '@girs/gtk-4.0';
4
+ import { type InstallDevtoolsOptions } from '@gjsify/devtools';
5
+ import type { AboutInfo } from './types.js';
6
+ /** Options for {@link AdwaitaApp} / {@link runAdwaitaApp}. */
7
+ export interface AdwaitaAppOptions {
8
+ /** GApplication id, e.g. `org.example.App`. */
9
+ applicationId: string;
10
+ /** GApplication flags. Default `Gio.ApplicationFlags.DEFAULT_FLAGS`. */
11
+ flags?: Gio.ApplicationFlags;
12
+ /** Build the main window on first `activate`. Called once. */
13
+ createWindow: (app: Adw.Application) => Gtk.Window;
14
+ /** CSS string applied display-wide on `startup` (via `Gtk.CssProvider`). */
15
+ css?: string;
16
+ /** When set, wires an `app.about` action opening an `Adw.AboutDialog`. */
17
+ about?: AboutInfo;
18
+ /** Wire `app.quit` (`<primary>q`). Default `true`. */
19
+ quitAction?: boolean;
20
+ /**
21
+ * Devtools control plane: `true` force-enables, an object passes
22
+ * {@link InstallDevtoolsOptions} through, omitted leaves it gated on the
23
+ * `GJSIFY_DEVTOOLS` env var (safe in production either way).
24
+ */
25
+ devtools?: boolean | InstallDevtoolsOptions;
26
+ /** Extra work on `startup`, after CSS + devtools are wired. */
27
+ onStartup?: (app: Adw.Application) => void;
28
+ }
29
+ /**
30
+ * A ready-to-run `Adw.Application` configured from {@link AdwaitaAppOptions}.
31
+ * Prefer {@link runAdwaitaApp} unless you need the instance.
32
+ */
33
+ export declare class AdwaitaApp extends Adw.Application {
34
+ private readonly _options;
35
+ private _window;
36
+ constructor(options: AdwaitaAppOptions);
37
+ private _initActions;
38
+ private _onStartup;
39
+ private _loadCss;
40
+ private _installDevtools;
41
+ private _onActivate;
42
+ private _showAbout;
43
+ }
44
+ /**
45
+ * Construct and run an {@link AdwaitaApp}, resolving with its exit code when the
46
+ * last window closes. The whole per-project launcher collapses to:
47
+ *
48
+ * ```ts
49
+ * import { runAdwaitaApp } from '@gjsify/adwaita-app';
50
+ * await runAdwaitaApp({
51
+ * applicationId: 'org.example.App',
52
+ * createWindow: (app) => new MyWindow(app),
53
+ * });
54
+ * ```
55
+ */
56
+ export declare function runAdwaitaApp(options: AdwaitaAppOptions): Promise<number>;
@@ -0,0 +1,21 @@
1
+ /** Resolved dev-hook values. */
2
+ export interface AppDevHooks {
3
+ /** `${PREFIX}_VIEW` — open straight to this view id on startup. */
4
+ view?: string;
5
+ /** `${PREFIX}_FILE` — auto-load this file/document on startup. */
6
+ file?: string;
7
+ /** `${PREFIX}_DEBUG` — emit verbose load logging. */
8
+ debug: boolean;
9
+ }
10
+ /** Options for {@link readAppDevHooks}. */
11
+ export interface ReadAppDevHooksOptions {
12
+ /** Env-var prefix without a trailing underscore, e.g. `BH_APP` or `ER_APP`. */
13
+ prefix: string;
14
+ /** Env map to read from. Defaults to `globalThis.process?.env`. */
15
+ env?: Record<string, string | undefined>;
16
+ }
17
+ /**
18
+ * Read the `${prefix}_VIEW` / `${prefix}_FILE` / `${prefix}_DEBUG` dev hooks.
19
+ * Empty-string values are treated as unset.
20
+ */
21
+ export declare function readAppDevHooks(options: ReadAppDevHooksOptions): AppDevHooks;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import type Gtk from '@girs/gtk-4.0';
2
+ /** Options for {@link confirmDialog}. */
3
+ export interface ConfirmOptions {
4
+ /** Dialog heading. */
5
+ heading: string;
6
+ /** Body text under the heading. */
7
+ body?: string;
8
+ /** Confirm-button label. Default `OK`. */
9
+ confirmLabel?: string;
10
+ /** Cancel-button label. Default `Cancel`. */
11
+ cancelLabel?: string;
12
+ /** Style the confirm button as destructive (red) instead of suggested. */
13
+ destructive?: boolean;
14
+ }
15
+ /**
16
+ * Present a confirm/cancel `Adw.AlertDialog`; resolves `true` on confirm, `false`
17
+ * on cancel/dismiss. `parent` anchors the dialog (typically the window).
18
+ */
19
+ export declare function confirmDialog(parent: Gtk.Widget, options: ConfirmOptions): Promise<boolean>;
20
+ /** Present a single-button error `Adw.AlertDialog`; resolves when dismissed. */
21
+ export declare function errorDialog(parent: Gtk.Widget, heading: string, body?: string): Promise<void>;
@@ -0,0 +1,23 @@
1
+ import Gtk from '@girs/gtk-4.0';
2
+ /** A named file filter (glob patterns and/or MIME types). */
3
+ export interface FileFilterSpec {
4
+ /** Human-readable filter name. */
5
+ name: string;
6
+ /** Glob patterns, e.g. `*.json`. */
7
+ patterns?: string[];
8
+ /** MIME types, e.g. `application/pdf`. */
9
+ mimeTypes?: string[];
10
+ }
11
+ /** Options for {@link pickFile} / {@link saveFile}. */
12
+ export interface PickFileOptions {
13
+ /** Dialog title. */
14
+ title?: string;
15
+ /** File filters offered in the dialog. */
16
+ filters?: FileFilterSpec[];
17
+ /** Pre-filled file name (save dialogs). */
18
+ initialName?: string;
19
+ }
20
+ /** Open-file dialog; resolves the chosen path, or `null` if cancelled. */
21
+ export declare function pickFile(parent: Gtk.Window, options?: PickFileOptions): Promise<string | null>;
22
+ /** Save-file dialog; resolves the chosen path, or `null` if cancelled. */
23
+ export declare function saveFile(parent: Gtk.Window, options?: PickFileOptions): Promise<string | null>;
@@ -0,0 +1,16 @@
1
+ export { AdwaitaApp, runAdwaitaApp } from './application.js';
2
+ export type { AdwaitaAppOptions } from './application.js';
3
+ export { createNavShell } from './nav-shell.js';
4
+ export type { NavShell, NavShellOptions } from './nav-shell.js';
5
+ export { LoadToken, loadIntoStack } from './view-loader.js';
6
+ export type { LoadIntoStackOptions } from './view-loader.js';
7
+ export { LoadingStack } from './loading-stack.js';
8
+ export { confirmDialog, errorDialog } from './dialogs.js';
9
+ export type { ConfirmOptions } from './dialogs.js';
10
+ export { registerToastOverlay, showToast } from './toast.js';
11
+ export { pickFile, saveFile } from './file-dialog.js';
12
+ export type { FileFilterSpec, PickFileOptions } from './file-dialog.js';
13
+ export { readAppDevHooks } from './dev-hooks.js';
14
+ export type { AppDevHooks, ReadAppDevHooksOptions } from './dev-hooks.js';
15
+ export { findNavItem, resolveInitialNavIndex } from './nav-model.js';
16
+ export type { AboutInfo, NavItem } from './types.js';
@@ -0,0 +1,21 @@
1
+ import Gtk from '@girs/gtk-4.0';
2
+ /**
3
+ * A `Gtk.Stack` pre-wired with the three named pages {@link loadIntoStack}
4
+ * switches between: `loading` (a centered `Adw.Spinner`), `content` (a settable
5
+ * child), and `error` (an `Adw.StatusPage`). Starts on `loading`.
6
+ */
7
+ export declare class LoadingStack extends Gtk.Stack {
8
+ private readonly _content;
9
+ private readonly _error;
10
+ constructor(params?: Partial<Gtk.Stack.ConstructorProps>);
11
+ /** Set the widget shown on the `content` page. */
12
+ setContent(widget: Gtk.Widget): void;
13
+ /** Customize the `error` page's title (and optional description). */
14
+ setError(title: string, description?: string): void;
15
+ /** Show the loading spinner. */
16
+ showLoading(): void;
17
+ /** Show the content page. */
18
+ showContent(): void;
19
+ /** Show the error page (optionally setting its text first via {@link setError}). */
20
+ showError(): void;
21
+ }
@@ -0,0 +1,10 @@
1
+ import type { NavItem } from './types.js';
2
+ /**
3
+ * Resolve the sidebar row index to select on startup.
4
+ *
5
+ * @returns the index of the item whose id matches `wantedId`; `0` when there is
6
+ * no match or no `wantedId` but the list is non-empty; `-1` for an empty list.
7
+ */
8
+ export declare function resolveInitialNavIndex(items: readonly NavItem[], wantedId?: string): number;
9
+ /** Find a nav item by id, or `undefined`. */
10
+ export declare function findNavItem(items: readonly NavItem[], id: string): NavItem | undefined;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import Adw from '@girs/adw-1';
2
+ import Gtk from '@girs/gtk-4.0';
3
+ import type { NavItem } from './types.js';
4
+ /** Options for {@link createNavShell}. */
5
+ export interface NavShellOptions {
6
+ /** Sidebar rows + stack child ids, in order. */
7
+ items: readonly NavItem[];
8
+ /** Fired when the selected item changes (row-selected). */
9
+ onSelect: (item: NavItem, index: number) => void;
10
+ /** Sidebar header title. */
11
+ sidebarTitle?: string;
12
+ /** Widget packed at the start of the sidebar header (e.g. an open button). */
13
+ sidebarHeaderStart?: Gtk.Widget;
14
+ /** Widget packed at the end of the sidebar header (e.g. a menu button). */
15
+ sidebarHeaderEnd?: Gtk.Widget;
16
+ /** Collapse the split view below this width (px). Default `720`. */
17
+ collapseWidth?: number;
18
+ }
19
+ /** The pieces a consumer wires into its window + views. */
20
+ export interface NavShell {
21
+ /** Top-level widget — set as the window content. */
22
+ widget: Adw.NavigationSplitView;
23
+ /** Content stack — add view widgets via `add_named(view, item.id)`. */
24
+ stack: Gtk.Stack;
25
+ /** Content header bar — add title widgets / buttons. */
26
+ contentHeader: Adw.HeaderBar;
27
+ /** Select a nav item by its id (falls back to the first item). */
28
+ selectById(id: string): void;
29
+ /** Select a nav item by index. */
30
+ selectByIndex(index: number): void;
31
+ }
32
+ /**
33
+ * Build the nav shell into `window` (which owns the responsive breakpoint).
34
+ * Returns the split view to set as content plus the stack + content header to
35
+ * fill. Row selection drives `onSelect`; a collapsed shell reveals the content
36
+ * pane on selection.
37
+ */
38
+ export declare function createNavShell(window: Adw.ApplicationWindow, options: NavShellOptions): NavShell;
@@ -0,0 +1,10 @@
1
+ import Adw from '@girs/adw-1';
2
+ /** Register the active window's toast overlay. Call once, at window build. */
3
+ export declare function registerToastOverlay(toastOverlay: Adw.ToastOverlay): void;
4
+ /**
5
+ * Show a toast on the registered overlay. No-ops with a warning if
6
+ * {@link registerToastOverlay} has not been called yet.
7
+ *
8
+ * @param timeout auto-dismiss seconds; `0` = sticky. Default `3`.
9
+ */
10
+ export declare function showToast(title: string, timeout?: number): void;
@@ -0,0 +1,37 @@
1
+ /** A single entry in the navigation sidebar / view stack. */
2
+ export interface NavItem {
3
+ /** Stable id used as the `Gtk.Stack` child name and for `selectById`. */
4
+ id: string;
5
+ /** Sidebar row label. */
6
+ label: string;
7
+ /** Optional symbolic icon name for the sidebar row. */
8
+ icon?: string;
9
+ /** Optional dim caption under the label. */
10
+ subtitle?: string;
11
+ }
12
+ /**
13
+ * Fields for the standard `app.about` dialog. All optional except the name;
14
+ * omitted fields are simply not set on the `Adw.AboutDialog`.
15
+ */
16
+ export interface AboutInfo {
17
+ /** Application name shown at the top of the dialog. */
18
+ applicationName: string;
19
+ /** Version string (e.g. `1.2.0`). */
20
+ version?: string;
21
+ /** "Developer" / vendor line. */
22
+ developerName?: string;
23
+ /** Icon name; defaults to the application id. */
24
+ applicationIcon?: string;
25
+ /** Homepage URL. */
26
+ website?: string;
27
+ /** Issue-tracker URL. */
28
+ issueUrl?: string;
29
+ /** License text or SPDX id (free-form, shown verbatim). */
30
+ license?: string;
31
+ /** Developer credit lines. */
32
+ developers?: string[];
33
+ /** Copyright line. */
34
+ copyright?: string;
35
+ /** Extra description shown under the header. */
36
+ comments?: string;
37
+ }
@@ -0,0 +1,37 @@
1
+ import type Gtk from '@girs/gtk-4.0';
2
+ /**
3
+ * Monotonic counter guarding against stale async reloads: each load takes a
4
+ * `next()` ticket and only applies its result while it is still `current`.
5
+ */
6
+ export declare class LoadToken {
7
+ private _current;
8
+ /** Begin a new load; returns its ticket. */
9
+ next(): number;
10
+ /** The most recently issued ticket. */
11
+ get current(): number;
12
+ }
13
+ /** Options for {@link loadIntoStack}. */
14
+ export interface LoadIntoStackOptions<T> {
15
+ /** The `Gtk.Stack` whose visible child is swapped between the three pages. */
16
+ stack: Gtk.Stack;
17
+ /** Shared token so a later reload supersedes an in-flight one. */
18
+ token: LoadToken;
19
+ /** Produce the view data (sync or async). */
20
+ load: () => T | Promise<T>;
21
+ /** Populate the content page from the loaded data. */
22
+ fill: (data: T) => void;
23
+ /** Stack child name shown while loading. Default `loading`. */
24
+ loadingName?: string;
25
+ /** Stack child name shown on success. Default `content`. */
26
+ contentName?: string;
27
+ /** Stack child name shown on failure. Default `error`. */
28
+ errorName?: string;
29
+ /** Called with the error before the error page is shown. */
30
+ onError?: (error: unknown) => void;
31
+ }
32
+ /**
33
+ * Run `load`, then `fill` + show the content page — unless a newer reload has
34
+ * started (result dropped) or `load` throws (error page shown). Always shows the
35
+ * loading page first. Never rejects.
36
+ */
37
+ export declare function loadIntoStack<T>(options: LoadIntoStackOptions<T>): void;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@gjsify/adwaita-app",
3
+ "version": "0.16.5",
4
+ "description": "Native Adwaita application shell for GJS/GTK apps — Adw.Application lifecycle (runAsync + devtools + CSS), a data-driven NavigationSplitView nav shell, an async-view mounter, and promise dialog/toast/file helpers",
5
+ "type": "module",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "lib"
16
+ ],
17
+ "scripts": {
18
+ "clear": "rm -rf lib tmp tsconfig.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
19
+ "check": "gjsify tsc --noEmit",
20
+ "build": "gjsify run build:gjsify && gjsify run build:types",
21
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
22
+ "build:types": "gjsify tsc",
23
+ "build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
24
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
25
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
26
+ "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:gjs",
27
+ "test:gjs": "gjsify run test.gjs.mjs",
28
+ "test:node": "node test.node.mjs",
29
+ "storybook": "gjsify storybook",
30
+ "storybook:build": "gjsify storybook --build-only --out dist/storybook.gjs.mjs"
31
+ },
32
+ "keywords": [
33
+ "gjs",
34
+ "gtk",
35
+ "adwaita",
36
+ "application",
37
+ "shell",
38
+ "navigation",
39
+ "framework"
40
+ ],
41
+ "dependencies": {
42
+ "@girs/adw-1": "^4.1.0",
43
+ "@girs/gdk-4.0": "^4.1.0",
44
+ "@girs/gio-2.0": "^4.1.0",
45
+ "@girs/gjs": "^4.1.0",
46
+ "@girs/glib-2.0": "^4.1.0",
47
+ "@girs/gobject-2.0": "^4.1.0",
48
+ "@girs/gtk-4.0": "^4.1.0",
49
+ "@gjsify/devtools": "^0.16.5"
50
+ },
51
+ "devDependencies": {
52
+ "@gjsify/cli": "^0.16.5",
53
+ "@gjsify/storybook": "^0.16.5",
54
+ "@gjsify/unit": "^0.16.5",
55
+ "@types/node": "^25.9.2",
56
+ "typescript": "^6.0.3"
57
+ },
58
+ "gjsify": {
59
+ "runtimes": {
60
+ "gjs": "polyfill",
61
+ "node": "none",
62
+ "browser": "none",
63
+ "nativescript": "none"
64
+ },
65
+ "tier": 2,
66
+ "storybook": {
67
+ "applicationId": "org.gjsify.AdwaitaAppStorybook",
68
+ "title": "Adwaita App",
69
+ "stories": "stories",
70
+ "globals": "auto"
71
+ }
72
+ },
73
+ "license": "MIT",
74
+ "repository": {
75
+ "type": "git",
76
+ "url": "git+https://github.com/gjsify/gjsify.git",
77
+ "directory": "packages/framework/adwaita-app"
78
+ },
79
+ "bugs": {
80
+ "url": "https://github.com/gjsify/gjsify/issues"
81
+ },
82
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/adwaita-app#readme"
83
+ }