@gtkx/forms 0.0.1

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.
Files changed (42) hide show
  1. package/README.md +174 -0
  2. package/dist/combo-row.d.ts +7 -0
  3. package/dist/combo-row.d.ts.map +1 -0
  4. package/dist/combo-row.js +27 -0
  5. package/dist/combo-row.js.map +1 -0
  6. package/dist/entry-row.d.ts +7 -0
  7. package/dist/entry-row.d.ts.map +1 -0
  8. package/dist/entry-row.js +31 -0
  9. package/dist/entry-row.js.map +1 -0
  10. package/dist/index.d.ts +9 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +7 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/internal/field.d.ts +31 -0
  15. package/dist/internal/field.d.ts.map +1 -0
  16. package/dist/internal/field.js +56 -0
  17. package/dist/internal/field.js.map +1 -0
  18. package/dist/password-entry-row.d.ts +7 -0
  19. package/dist/password-entry-row.d.ts.map +1 -0
  20. package/dist/password-entry-row.js +31 -0
  21. package/dist/password-entry-row.js.map +1 -0
  22. package/dist/spin-row.d.ts +7 -0
  23. package/dist/spin-row.d.ts.map +1 -0
  24. package/dist/spin-row.js +31 -0
  25. package/dist/spin-row.js.map +1 -0
  26. package/dist/switch-row.d.ts +7 -0
  27. package/dist/switch-row.d.ts.map +1 -0
  28. package/dist/switch-row.js +26 -0
  29. package/dist/switch-row.js.map +1 -0
  30. package/dist/types.d.ts +19 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +2 -0
  33. package/dist/types.js.map +1 -0
  34. package/package.json +71 -0
  35. package/src/combo-row.tsx +49 -0
  36. package/src/entry-row.tsx +52 -0
  37. package/src/index.ts +67 -0
  38. package/src/internal/field.tsx +121 -0
  39. package/src/password-entry-row.tsx +52 -0
  40. package/src/spin-row.tsx +52 -0
  41. package/src/switch-row.tsx +45 -0
  42. package/src/types.ts +73 -0
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/gtkx-org/gtkx/main/logo.svg" alt="GTKX" width="100" />
3
+ </p>
4
+
5
+ <h1 align="center">GTKX</h1>
6
+
7
+ <p align="center">
8
+ The React framework for Linux, built on GTK.<br />
9
+ Write native GTK4 desktop applications with React and TypeScript.
10
+ Real GTK widgets, the GNOME stack, and standard web tooling.
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/create-gtkx"><img src="https://img.shields.io/npm/v/create-gtkx?color=cb3837&logo=npm&label=create-gtkx" alt="npm version" /></a>
15
+ <a href="https://www.npmjs.com/package/create-gtkx"><img src="https://img.shields.io/npm/dm/create-gtkx?color=cb3837&logo=npm&label=downloads" alt="npm downloads" /></a>
16
+ <img src="https://img.shields.io/badge/node-%E2%89%A524-339933?logo=node.js&logoColor=white" alt="Node >= 24" />
17
+ <a href="https://github.com/gtkx-org/gtkx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MPL--2.0-blue.svg" alt="License: MPL-2.0" /></a>
18
+ <a href="https://github.com/gtkx-org/gtkx/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/gtkx-org/gtkx/ci.yml?branch=main&logo=github&label=CI" alt="CI status" /></a>
19
+ <img src="https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white" alt="TypeScript" />
20
+ </p>
21
+
22
+ <p align="center">
23
+ <a href="https://gtkx.dev">Homepage</a> &middot;
24
+ <a href="https://gtkx.dev/guide/getting-started">Documentation</a> &middot;
25
+ <a href="https://github.com/gtkx-org/gtkx/tree/main/examples">Examples</a> &middot;
26
+ <a href="https://github.com/gtkx-org/gtkx/blob/main/CONTRIBUTING.md">Contributing</a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ <p align="center">
32
+ <img src="https://raw.githubusercontent.com/gtkx-org/gtkx/main/examples/tutorial/assets/screenshot.png" alt="The Tasks app: an Adwaita window with a sidebar of smart views and colored lists on the left, and a boxed task list on the right." />
33
+ </p>
34
+
35
+ <p align="center">
36
+ <em>The Tasks app you build in the <a href="https://gtkx.dev/tutorial/">tutorial</a>.</em>
37
+ </p>
38
+
39
+ ## Demo
40
+
41
+ The intrinsic elements in this snippet render GTK4 widgets, and ordinary React hooks and events drive them:
42
+
43
+ ```tsx
44
+ import * as Gtk from "@gtkx/gi/gtk";
45
+ import { GtkApplication, GtkApplicationWindow, GtkBox, GtkButton, GtkLabel } from "@gtkx/jsx/gtk";
46
+ import { createRoot, quit } from "@gtkx/react";
47
+ import { useState } from "react";
48
+
49
+ const Counter = () => {
50
+ const [count, setCount] = useState(0);
51
+
52
+ return (
53
+ <GtkApplicationWindow
54
+ title="Hello GTKX"
55
+ defaultWidth={400}
56
+ defaultHeight={300}
57
+ onCloseRequest={quit}
58
+ >
59
+ <GtkBox
60
+ orientation={Gtk.Orientation.VERTICAL}
61
+ spacing={20}
62
+ marginTop={40}
63
+ marginBottom={40}
64
+ marginStart={40}
65
+ marginEnd={40}
66
+ valign={Gtk.Align.CENTER}
67
+ halign={Gtk.Align.CENTER}
68
+ >
69
+ <GtkLabel cssClasses={["title-1"]}>Welcome to GTKX!</GtkLabel>
70
+ <GtkLabel cssClasses={["title-2"]}>{`Count: ${count}`}</GtkLabel>
71
+ <GtkButton
72
+ label="Increment"
73
+ onClicked={() => setCount((c) => c + 1)}
74
+ cssClasses={["suggested-action", "pill"]}
75
+ />
76
+ </GtkBox>
77
+ </GtkApplicationWindow>
78
+ );
79
+ };
80
+
81
+ const App = () => (
82
+ <GtkApplication>
83
+ <Counter />
84
+ </GtkApplication>
85
+ );
86
+
87
+ createRoot().render(<App />);
88
+ ```
89
+
90
+ This is the [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world) example, with `app.tsx` and `index.tsx` combined into a single snippet. `@gtkx/gi` and `@gtkx/jsx` are per-project bindings generated by the CLI, not packages you install from npm.
91
+
92
+ ## Why GTKX
93
+
94
+ ### A declarative layer for the GNOME stack
95
+
96
+ GTK4 is mature, and GtkBuilder XML can lay out a static interface, but nothing re-renders that interface when your application state changes, and nothing hot-reloads it as you work. GTKX adds that missing layer, and the tooling around it, on top of the stack you already know:
97
+
98
+ - a React reconciler that exposes every GObject as a JSX element,
99
+ - a CLI for scaffolding, development, and production builds,
100
+ - a dev server with Fast Refresh that patches your running UI in place,
101
+ - CSS-in-JS styling, React Hook Form-powered Adwaita controls, React Spring animations, React Navigation stack, tab, drawer, and split view navigators, and high-level list, grid, and dialog components,
102
+ - a Testing Library-style API for querying and driving your widgets in tests,
103
+ - and a Model Context Protocol (MCP) server that exposes your live app to AI agents.
104
+
105
+ ### The full GNOME API surface
106
+
107
+ React Native and similar frameworks hide the native toolkit so one API can run everywhere. GTKX exposes it: GTK4, Adwaita, and any other GObject-Introspection library on your system. Linux-only by design.
108
+
109
+ ### Why Node.js, and why generated bindings
110
+
111
+ GTKX runs on Node.js, which puts native modules, the npm ecosystem, and the tooling built for Node.js APIs within reach. GJS is GNOME's own runtime, built on SpiderMonkey rather than V8; node-gtk runs on Node.js but is lightly maintained, on the older nan/V8 ABI rather than N-API, and still centered on GTK3. The [Why GTKX guide](https://gtkx.dev/guide/why-gtkx) covers what running on Node.js means day to day.
112
+
113
+ GTKX generates the TypeScript types and the native FFI calls from the same GObject-Introspection data, so the types cannot drift from the calls they back. Codegen covers the whole GTK4 and Adwaita surface.
114
+
115
+ At runtime, the native Rust core calls straight into the system GTK4, Adwaita, and GLib libraries through libffi, without loading libgirepository at all.
116
+
117
+ ## Quick start
118
+
119
+ GTKX is Linux-only and needs Node.js 24 or later. See [Requirements](#requirements).
120
+
121
+ Scaffold a new app with the `create-gtkx` initializer:
122
+
123
+ ```sh
124
+ npm create gtkx
125
+ ```
126
+
127
+ The same command works with other package managers: `pnpm create gtkx` or `yarn create gtkx`.
128
+
129
+ Then run your new app:
130
+
131
+ ```sh
132
+ cd my-app
133
+ npm run dev
134
+ ```
135
+
136
+ To go further, follow the [tutorial](https://gtkx.dev/tutorial/).
137
+
138
+ ## Documentation
139
+
140
+ The documentation at **[gtkx.dev](https://gtkx.dev)** includes a step-by-step tutorial that builds a complete GNOME app, from scaffolding to packaging and shipping, plus guides and a full API reference.
141
+
142
+ **[Read the docs &rarr;](https://gtkx.dev/guide/why-gtkx)**
143
+
144
+ ## Requirements
145
+
146
+ GTKX is Linux-only. You need:
147
+
148
+ - Linux with the GTK4 (4.20 or later) and GLib development libraries, plus Adwaita (1.8 or later) once your project binds `Adw-1`
149
+ - Node.js 24 or later
150
+
151
+ The `@gtkx/native` addon ships prebuilt for x64 and arm64 glibc Linux; other targets need to build it from the GTKX repository, which requires a Rust toolchain.
152
+
153
+ ## Examples
154
+
155
+ Explore the [example apps](https://github.com/gtkx-org/gtkx/tree/main/examples):
156
+
157
+ - [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world): the counter above.
158
+ - [`gtk-demo`](https://github.com/gtkx-org/gtkx/tree/main/examples/gtk-demo): a React port of the official GTK4 widget showcase, covering lists, dialogs, gestures, CSS, and OpenGL.
159
+ - [`browser`](https://github.com/gtkx-org/gtkx/tree/main/examples/browser): a WebKitWebView-based web browser.
160
+ - [`animations`](https://github.com/gtkx-org/gtkx/tree/main/examples/animations): a tour of `@gtkx/animated`, React Spring animations driven by the GTK frame clock.
161
+ - [`navigation`](https://github.com/gtkx-org/gtkx/tree/main/examples/navigation): a tour of `@gtkx/navigation`, React Navigation's stack, tab, and drawer navigators rendered with libadwaita.
162
+ - [`tutorial`](https://github.com/gtkx-org/gtkx/tree/main/examples/tutorial): the Tasks app the documentation builds.
163
+
164
+ ## Status
165
+
166
+ GTKX is stable and ready for production use.
167
+
168
+ ## Contributing
169
+
170
+ Contributions are welcome. See [CONTRIBUTING.md](https://github.com/gtkx-org/gtkx/blob/main/CONTRIBUTING.md), the [Code of Conduct](https://github.com/gtkx-org/gtkx/blob/main/CODE_OF_CONDUCT.md), and the [security policy](https://github.com/gtkx-org/gtkx/blob/main/SECURITY.md). Building the repo needs Node.js 24 or later, pnpm, and a Rust toolchain.
171
+
172
+ ## License
173
+
174
+ GTKX is licensed under [MPL-2.0](https://github.com/gtkx-org/gtkx/blob/main/LICENSE).
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from "react";
2
+ import { type FieldValues } from "react-hook-form";
3
+ import type { ComboRowProps, FormFieldPath } from "./types.js";
4
+ /** Renders an ID-based Adwaita combo row controlled by React Hook Form. */
5
+ declare function ComboRow<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>, TTransformedValues = TFieldValues, TItem = unknown, TSection = unknown>(props: ComboRowProps<TFieldValues, TName, TTransformedValues, TItem, TSection>): ReactNode;
6
+ export { ComboRow };
7
+ //# sourceMappingURL=combo-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-row.d.ts","sourceRoot":"","sources":["../src/combo-row.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG/D,2EAA2E;AAC3E,iBAAS,QAAQ,CACb,YAAY,SAAS,WAAW,GAAG,WAAW,EAC9C,KAAK,SAAS,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,EACvF,kBAAkB,GAAG,YAAY,EACjC,KAAK,GAAG,OAAO,EACf,QAAQ,GAAG,OAAO,EACpB,KAAK,EAAE,aAAa,CAAC,YAAY,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,QAAQ,CAAC,GAAG,SAAS,CAgC3F;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ComboRow as BaseComboRow } from "@gtkx/components/adw";
3
+ import { useController } from "react-hook-form";
4
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
5
+ /** Renders an ID-based Adwaita combo row controlled by React Hook Form. */
6
+ function ComboRow(props) {
7
+ const { field, fieldState } = useController(props);
8
+ const rowProps = widgetProps(props);
9
+ const binding = useFieldWidget({
10
+ controllerRef: field.ref,
11
+ forwardedRef: rowProps.ref,
12
+ controllers: rowProps.controllers,
13
+ cssClasses: rowProps.cssClasses,
14
+ sensitive: rowProps.sensitive,
15
+ tooltipText: rowProps.tooltipText,
16
+ disabled: field.disabled,
17
+ isInvalid: fieldState.invalid,
18
+ errorMessage: fieldState.error?.message,
19
+ }, field.onBlur);
20
+ const selectedId = typeof field.value === "string" ? field.value : null;
21
+ return (_jsx(BaseComboRow, { ...rowProps, ...binding, selectedId: selectedId, onSelectionChanged: (id) => {
22
+ field.onChange(id);
23
+ rowProps.onSelectionChanged?.(id);
24
+ } }));
25
+ }
26
+ export { ComboRow };
27
+ //# sourceMappingURL=combo-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-row.js","sourceRoot":"","sources":["../src/combo-row.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAoB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElE,2EAA2E;AAC3E,SAAS,QAAQ,CAMf,KAA8E;IAC5E,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,aAAa,CAA0C,KAAK,CAAC,CAAC;IAC5F,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,cAAc,CAC1B;QACI,aAAa,EAAE,KAAK,CAAC,GAAG;QACxB,YAAY,EAAE,QAAQ,CAAC,GAAG;QAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,UAAU,CAAC,OAAO;QAC7B,YAAY,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO;KAC1C,EACD,KAAK,CAAC,MAAM,CACf,CAAC;IAEF,MAAM,UAAU,GAAG,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAExE,OAAO,CACH,KAAC,YAAY,OACL,QAAQ,KACR,OAAO,EACX,UAAU,EAAE,UAAU,EACtB,kBAAkB,EAAE,CAAC,EAAE,EAAE,EAAE;YACvB,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACnB,QAAQ,CAAC,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC;QACtC,CAAC,GACH,CACL,CAAC;AACN,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC","sourcesContent":["import type * as Adw from \"@gtkx/gi/adw\";\nimport type { ReactNode } from \"react\";\nimport { ComboRow as BaseComboRow } from \"@gtkx/components/adw\";\nimport { type FieldValues, useController } from \"react-hook-form\";\nimport type { ComboRowProps, FormFieldPath } from \"./types.js\";\nimport { useFieldWidget, widgetProps } from \"./internal/field.js\";\n\n/** Renders an ID-based Adwaita combo row controlled by React Hook Form. */\nfunction ComboRow<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,\n TTransformedValues = TFieldValues,\n TItem = unknown,\n TSection = unknown,\n>(props: ComboRowProps<TFieldValues, TName, TTransformedValues, TItem, TSection>): ReactNode {\n const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);\n const rowProps = widgetProps(props);\n\n const binding = useFieldWidget<Adw.ComboRow>(\n {\n controllerRef: field.ref,\n forwardedRef: rowProps.ref,\n controllers: rowProps.controllers,\n cssClasses: rowProps.cssClasses,\n sensitive: rowProps.sensitive,\n tooltipText: rowProps.tooltipText,\n disabled: field.disabled,\n isInvalid: fieldState.invalid,\n errorMessage: fieldState.error?.message,\n },\n field.onBlur,\n );\n\n const selectedId = typeof field.value === \"string\" ? field.value : null;\n\n return (\n <BaseComboRow\n {...rowProps}\n {...binding}\n selectedId={selectedId}\n onSelectionChanged={(id) => {\n field.onChange(id);\n rowProps.onSelectionChanged?.(id);\n }}\n />\n );\n}\n\nexport { ComboRow };\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from "react";
2
+ import { type FieldValues } from "react-hook-form";
3
+ import type { EntryRowProps, FormFieldPath } from "./types.js";
4
+ /** Renders an `Adw.EntryRow` controlled by React Hook Form. */
5
+ declare function EntryRow<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>, TTransformedValues = TFieldValues>(props: EntryRowProps<TFieldValues, TName, TTransformedValues>): ReactNode;
6
+ export { EntryRow };
7
+ //# sourceMappingURL=entry-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-row.d.ts","sourceRoot":"","sources":["../src/entry-row.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAO/D,+DAA+D;AAC/D,iBAAS,QAAQ,CACb,YAAY,SAAS,WAAW,GAAG,WAAW,EAC9C,KAAK,SAAS,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,EACvF,kBAAkB,GAAG,YAAY,EACnC,KAAK,EAAE,aAAa,CAAC,YAAY,EAAE,KAAK,EAAE,kBAAkB,CAAC,GAAG,SAAS,CAiC1E;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AdwEntryRow } from "@gtkx/jsx/adw";
3
+ import { useController } from "react-hook-form";
4
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
5
+ const selectText = (row) => {
6
+ row.selectRegion(0, -1);
7
+ };
8
+ /** Renders an `Adw.EntryRow` controlled by React Hook Form. */
9
+ function EntryRow(props) {
10
+ const { field, fieldState } = useController(props);
11
+ const rowProps = widgetProps(props);
12
+ const binding = useFieldWidget({
13
+ controllerRef: field.ref,
14
+ forwardedRef: rowProps.ref,
15
+ controllers: rowProps.controllers,
16
+ cssClasses: rowProps.cssClasses,
17
+ sensitive: rowProps.sensitive,
18
+ tooltipText: rowProps.tooltipText,
19
+ disabled: field.disabled,
20
+ isInvalid: fieldState.invalid,
21
+ errorMessage: fieldState.error?.message,
22
+ select: selectText,
23
+ }, field.onBlur);
24
+ const value = typeof field.value === "string" ? field.value : "";
25
+ return (_jsx(AdwEntryRow, { ...rowProps, ...binding, text: value, onNotifyText: (text, row) => {
26
+ field.onChange(text ?? "");
27
+ rowProps.onNotifyText?.(text, row);
28
+ } }));
29
+ }
30
+ export { EntryRow };
31
+ //# sourceMappingURL=entry-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry-row.js","sourceRoot":"","sources":["../src/entry-row.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAoB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElE,MAAM,UAAU,GAAG,CAAC,GAAiB,EAAQ,EAAE;IAC3C,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,+DAA+D;AAC/D,SAAS,QAAQ,CAIf,KAA6D;IAC3D,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,aAAa,CAA0C,KAAK,CAAC,CAAC;IAC5F,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,cAAc,CAC1B;QACI,aAAa,EAAE,KAAK,CAAC,GAAG;QACxB,YAAY,EAAE,QAAQ,CAAC,GAAG;QAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,UAAU,CAAC,OAAO;QAC7B,YAAY,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO;QACvC,MAAM,EAAE,UAAU;KACrB,EACD,KAAK,CAAC,MAAM,CACf,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjE,OAAO,CACH,KAAC,WAAW,OACJ,QAAQ,KACR,OAAO,EACX,IAAI,EAAE,KAAK,EACX,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YACxB,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YAC3B,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvC,CAAC,GACH,CACL,CAAC;AACN,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC","sourcesContent":["import type * as Adw from \"@gtkx/gi/adw\";\nimport type { ReactNode } from \"react\";\nimport { AdwEntryRow } from \"@gtkx/jsx/adw\";\nimport { type FieldValues, useController } from \"react-hook-form\";\nimport type { EntryRowProps, FormFieldPath } from \"./types.js\";\nimport { useFieldWidget, widgetProps } from \"./internal/field.js\";\n\nconst selectText = (row: Adw.EntryRow): void => {\n row.selectRegion(0, -1);\n};\n\n/** Renders an `Adw.EntryRow` controlled by React Hook Form. */\nfunction EntryRow<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,\n TTransformedValues = TFieldValues,\n>(props: EntryRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {\n const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);\n const rowProps = widgetProps(props);\n\n const binding = useFieldWidget<Adw.EntryRow>(\n {\n controllerRef: field.ref,\n forwardedRef: rowProps.ref,\n controllers: rowProps.controllers,\n cssClasses: rowProps.cssClasses,\n sensitive: rowProps.sensitive,\n tooltipText: rowProps.tooltipText,\n disabled: field.disabled,\n isInvalid: fieldState.invalid,\n errorMessage: fieldState.error?.message,\n select: selectText,\n },\n field.onBlur,\n );\n\n const value = typeof field.value === \"string\" ? field.value : \"\";\n\n return (\n <AdwEntryRow\n {...rowProps}\n {...binding}\n text={value}\n onNotifyText={(text, row) => {\n field.onChange(text ?? \"\");\n rowProps.onNotifyText?.(text, row);\n }}\n />\n );\n}\n\nexport { EntryRow };\n"]}
@@ -0,0 +1,9 @@
1
+ export { ComboRow } from "./combo-row.js";
2
+ export { EntryRow } from "./entry-row.js";
3
+ export { PasswordEntryRow } from "./password-entry-row.js";
4
+ export { SpinRow } from "./spin-row.js";
5
+ export { SwitchRow } from "./switch-row.js";
6
+ export type { ComboRowProps, EntryRowProps, FormFieldPath, FormFieldProps, PasswordEntryRowProps, SpinRowProps, SwitchRowProps, } from "./types.js";
7
+ export { Controller, FormProvider, useController, useFieldArray, useForm, useFormContext, useFormState, useWatch, } from "react-hook-form";
8
+ export type { ArrayPath, Control, ControllerFieldState, ControllerProps, ControllerRenderProps, DeepPartial, DefaultValues, ErrorOption, FieldArrayPath, FieldArrayWithId, FieldError, FieldErrors, FieldPath, FieldPathByValue, FieldPathValue, FieldPathValues, FieldValues, FormProviderProps, KeepStateOptions, Path, PathValue, RegisterOptions, Resolver, ResolverOptions, ResolverResult, SetFocusOptions, SetValueConfig, SubmitErrorHandler, SubmitHandler, UseControllerProps, UseControllerReturn, UseFieldArrayProps, UseFieldArrayReturn, UseFormProps, UseFormReturn, UseFormStateProps, UseFormStateReturn, UseWatchProps, Validate, ValidationRule, WatchObserver, } from "react-hook-form";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EACR,aAAa,EACb,aAAa,EACb,aAAa,EACb,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,cAAc,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACH,UAAU,EACV,YAAY,EACZ,aAAa,EACb,aAAa,EACb,OAAO,EACP,cAAc,EACd,YAAY,EACZ,QAAQ,GACX,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACR,SAAS,EACT,OAAO,EACP,oBAAoB,EACpB,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,IAAI,EACJ,SAAS,EACT,eAAe,EACf,QAAQ,EACR,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,QAAQ,EACR,cAAc,EACd,aAAa,GAChB,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { ComboRow } from "./combo-row.js";
2
+ export { EntryRow } from "./entry-row.js";
3
+ export { PasswordEntryRow } from "./password-entry-row.js";
4
+ export { SpinRow } from "./spin-row.js";
5
+ export { SwitchRow } from "./switch-row.js";
6
+ export { Controller, FormProvider, useController, useFieldArray, useForm, useFormContext, useFormState, useWatch, } from "react-hook-form";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAU5C,OAAO,EACH,UAAU,EACV,YAAY,EACZ,aAAa,EACb,aAAa,EACb,OAAO,EACP,cAAc,EACd,YAAY,EACZ,QAAQ,GACX,MAAM,iBAAiB,CAAC","sourcesContent":["export { ComboRow } from \"./combo-row.js\";\nexport { EntryRow } from \"./entry-row.js\";\nexport { PasswordEntryRow } from \"./password-entry-row.js\";\nexport { SpinRow } from \"./spin-row.js\";\nexport { SwitchRow } from \"./switch-row.js\";\nexport type {\n ComboRowProps,\n EntryRowProps,\n FormFieldPath,\n FormFieldProps,\n PasswordEntryRowProps,\n SpinRowProps,\n SwitchRowProps,\n} from \"./types.js\";\nexport {\n Controller,\n FormProvider,\n useController,\n useFieldArray,\n useForm,\n useFormContext,\n useFormState,\n useWatch,\n} from \"react-hook-form\";\nexport type {\n ArrayPath,\n Control,\n ControllerFieldState,\n ControllerProps,\n ControllerRenderProps,\n DeepPartial,\n DefaultValues,\n ErrorOption,\n FieldArrayPath,\n FieldArrayWithId,\n FieldError,\n FieldErrors,\n FieldPath,\n FieldPathByValue,\n FieldPathValue,\n FieldPathValues,\n FieldValues,\n FormProviderProps,\n KeepStateOptions,\n Path,\n PathValue,\n RegisterOptions,\n Resolver,\n ResolverOptions,\n ResolverResult,\n SetFocusOptions,\n SetValueConfig,\n SubmitErrorHandler,\n SubmitHandler,\n UseControllerProps,\n UseControllerReturn,\n UseFieldArrayProps,\n UseFieldArrayReturn,\n UseFormProps,\n UseFormReturn,\n UseFormStateProps,\n UseFormStateReturn,\n UseWatchProps,\n Validate,\n ValidationRule,\n WatchObserver,\n} from \"react-hook-form\";\n"]}
@@ -0,0 +1,31 @@
1
+ import type { RefCallBack } from "react-hook-form";
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
+ import { type ReactElement, type ReactNode, type Ref, type RefCallback } from "react";
4
+ type FormFieldPropName = "control" | "defaultValue" | "disabled" | "exact" | "name" | "rules" | "shouldUnregister";
5
+ type FieldWidget = Gtk.Widget & {
6
+ grabFocus: () => boolean;
7
+ };
8
+ type FieldWidgetOptions<Widget extends FieldWidget> = {
9
+ controllerRef: RefCallBack;
10
+ forwardedRef: Ref<Widget> | undefined;
11
+ controllers: ReactNode | null | undefined;
12
+ cssClasses: string[] | null | undefined;
13
+ sensitive: boolean | null | undefined;
14
+ tooltipText: string | null | undefined;
15
+ disabled: boolean | undefined;
16
+ isInvalid: boolean;
17
+ errorMessage: string | undefined;
18
+ select?: ((widget: Widget) => void) | undefined;
19
+ };
20
+ type FieldWidgetBinding<Widget extends FieldWidget> = {
21
+ ref: RefCallback<Widget>;
22
+ controllers: ReactElement;
23
+ cssClasses: string[] | null | undefined;
24
+ sensitive: boolean | null | undefined;
25
+ tooltipText: string | null | undefined;
26
+ accessibleInvalid: Gtk.AccessibleInvalidState;
27
+ };
28
+ declare const useFieldWidget: <Widget extends FieldWidget>(options: FieldWidgetOptions<Widget>, onBlur: () => void) => FieldWidgetBinding<Widget>;
29
+ declare const widgetProps: <Props extends Partial<Record<FormFieldPropName, unknown>>>(props: Props) => Omit<Props, FormFieldPropName>;
30
+ export { useFieldWidget, widgetProps };
31
+ //# sourceMappingURL=field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../src/internal/field.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAGpC,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAE,KAAK,WAAW,EAAe,MAAM,OAAO,CAAC;AAEnG,KAAK,iBAAiB,GAChB,SAAS,GACX,cAAc,GACd,UAAU,GACV,OAAO,GACP,MAAM,GACN,OAAO,GACP,kBAAkB,CAAC;AAEvB,KAAK,WAAW,GAAG,GAAG,CAAC,MAAM,GAAG;IAC5B,SAAS,EAAE,MAAM,OAAO,CAAC;CAC5B,CAAC;AAEF,KAAK,kBAAkB,CAAC,MAAM,SAAS,WAAW,IAAI;IAClD,aAAa,EAAE,WAAW,CAAC;IAC3B,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,SAAS,GAAG,IAAI,GAAG,SAAS,CAAC;IAC1C,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,SAAS,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,CAAC,MAAM,SAAS,WAAW,IAAI;IAClD,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACzB,WAAW,EAAE,YAAY,CAAC;IAC1B,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,SAAS,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,iBAAiB,EAAE,GAAG,CAAC,sBAAsB,CAAC;CACjD,CAAC;AA4DF,QAAA,MAAM,cAAc,GAAI,MAAM,SAAS,WAAW,EAC9C,SAAS,kBAAkB,CAAC,MAAM,CAAC,EACnC,QAAQ,MAAM,IAAI,KACnB,kBAAkB,CAAC,MAAM,CAY1B,CAAC;AAEH,QAAA,MAAM,WAAW,GAAI,KAAK,SAAS,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC,EAC1E,OAAO,KAAK,KACb,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAuC,CAAC;AAExE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
+ import { GtkEventControllerFocus } from "@gtkx/jsx/gtk";
4
+ import { omit } from "@gtkx/utils";
5
+ import { useCallback } from "react";
6
+ const FORM_FIELD_PROP_NAMES = [
7
+ "control",
8
+ "defaultValue",
9
+ "disabled",
10
+ "exact",
11
+ "name",
12
+ "rules",
13
+ "shouldUnregister",
14
+ ];
15
+ const setRef = (ref, widget) => {
16
+ if (typeof ref === "function") {
17
+ ref(widget);
18
+ }
19
+ else if (ref !== undefined && ref !== null) {
20
+ ref.current = widget;
21
+ }
22
+ };
23
+ const withErrorClass = (cssClasses, isInvalid) => {
24
+ if (!isInvalid || cssClasses?.includes("error") === true) {
25
+ return cssClasses;
26
+ }
27
+ return [...(cssClasses ?? []), "error"];
28
+ };
29
+ const useFieldWidgetRef = ({ controllerRef, forwardedRef, select, }) => useCallback((widget) => {
30
+ setRef(forwardedRef, widget);
31
+ if (widget === null) {
32
+ controllerRef(null);
33
+ return;
34
+ }
35
+ controllerRef({
36
+ focus: () => {
37
+ widget.grabFocus();
38
+ },
39
+ ...(select !== undefined && {
40
+ select: () => {
41
+ select(widget);
42
+ },
43
+ }),
44
+ });
45
+ }, [controllerRef, forwardedRef, select]);
46
+ const useFieldWidget = (options, onBlur) => ({
47
+ ref: useFieldWidgetRef(options),
48
+ controllers: (_jsxs(_Fragment, { children: [options.controllers, _jsx(GtkEventControllerFocus, { onLeave: onBlur })] })),
49
+ cssClasses: withErrorClass(options.cssClasses, options.isInvalid),
50
+ sensitive: options.disabled === true ? false : options.sensitive,
51
+ tooltipText: options.isInvalid && options.errorMessage !== undefined ? options.errorMessage : options.tooltipText,
52
+ accessibleInvalid: options.isInvalid ? Gtk.AccessibleInvalidState.TRUE : Gtk.AccessibleInvalidState.FALSE,
53
+ });
54
+ const widgetProps = (props) => omit(props, FORM_FIELD_PROP_NAMES);
55
+ export { useFieldWidget, widgetProps };
56
+ //# sourceMappingURL=field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.js","sourceRoot":"","sources":["../../src/internal/field.tsx"],"names":[],"mappings":"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","sourcesContent":["import type { RefCallBack } from \"react-hook-form\";\nimport * as Gtk from \"@gtkx/gi/gtk\";\nimport { GtkEventControllerFocus } from \"@gtkx/jsx/gtk\";\nimport { omit } from \"@gtkx/utils\";\nimport { type ReactElement, type ReactNode, type Ref, type RefCallback, useCallback } from \"react\";\n\ntype FormFieldPropName =\n | \"control\" |\n \"defaultValue\" |\n \"disabled\" |\n \"exact\" |\n \"name\" |\n \"rules\" |\n \"shouldUnregister\";\n\ntype FieldWidget = Gtk.Widget & {\n grabFocus: () => boolean;\n};\n\ntype FieldWidgetOptions<Widget extends FieldWidget> = {\n controllerRef: RefCallBack;\n forwardedRef: Ref<Widget> | undefined;\n controllers: ReactNode | null | undefined;\n cssClasses: string[] | null | undefined;\n sensitive: boolean | null | undefined;\n tooltipText: string | null | undefined;\n disabled: boolean | undefined;\n isInvalid: boolean;\n errorMessage: string | undefined;\n select?: ((widget: Widget) => void) | undefined;\n};\n\ntype FieldWidgetBinding<Widget extends FieldWidget> = {\n ref: RefCallback<Widget>;\n controllers: ReactElement;\n cssClasses: string[] | null | undefined;\n sensitive: boolean | null | undefined;\n tooltipText: string | null | undefined;\n accessibleInvalid: Gtk.AccessibleInvalidState;\n};\n\nconst FORM_FIELD_PROP_NAMES: FormFieldPropName[] = [\n \"control\",\n \"defaultValue\",\n \"disabled\",\n \"exact\",\n \"name\",\n \"rules\",\n \"shouldUnregister\",\n];\n\nconst setRef = <Widget,>(ref: Ref<Widget> | undefined, widget: Widget | null): void => {\n if (typeof ref === \"function\") {\n ref(widget);\n } else if (ref !== undefined && ref !== null) {\n ref.current = widget;\n }\n};\n\nconst withErrorClass = (\n cssClasses: string[] | null | undefined,\n isInvalid: boolean,\n): string[] | null | undefined => {\n if (!isInvalid || cssClasses?.includes(\"error\") === true) {\n return cssClasses;\n }\n\n return [...(cssClasses ?? []), \"error\"];\n};\n\nconst useFieldWidgetRef = <Widget extends FieldWidget>({\n controllerRef,\n forwardedRef,\n select,\n}: Pick<FieldWidgetOptions<Widget>, \"controllerRef\" | \"forwardedRef\" | \"select\">): RefCallback<Widget> =>\n useCallback(\n (widget) => {\n setRef(forwardedRef, widget);\n\n if (widget === null) {\n controllerRef(null);\n\n return;\n }\n\n controllerRef({\n focus: () => {\n widget.grabFocus();\n },\n ...(select !== undefined && {\n select: () => {\n select(widget);\n },\n }),\n });\n },\n [controllerRef, forwardedRef, select],\n );\n\nconst useFieldWidget = <Widget extends FieldWidget>(\n options: FieldWidgetOptions<Widget>,\n onBlur: () => void,\n): FieldWidgetBinding<Widget> => ({\n ref: useFieldWidgetRef(options),\n controllers: (\n <>\n {options.controllers}\n <GtkEventControllerFocus onLeave={onBlur} />\n </>\n ),\n cssClasses: withErrorClass(options.cssClasses, options.isInvalid),\n sensitive: options.disabled === true ? false : options.sensitive,\n tooltipText: options.isInvalid && options.errorMessage !== undefined ? options.errorMessage : options.tooltipText,\n accessibleInvalid: options.isInvalid ? Gtk.AccessibleInvalidState.TRUE : Gtk.AccessibleInvalidState.FALSE,\n});\n\nconst widgetProps = <Props extends Partial<Record<FormFieldPropName, unknown>>>(\n props: Props,\n): Omit<Props, FormFieldPropName> => omit(props, FORM_FIELD_PROP_NAMES);\n\nexport { useFieldWidget, widgetProps };\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from "react";
2
+ import { type FieldValues } from "react-hook-form";
3
+ import type { FormFieldPath, PasswordEntryRowProps } from "./types.js";
4
+ /** Renders an `Adw.PasswordEntryRow` controlled by React Hook Form. */
5
+ declare function PasswordEntryRow<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>, TTransformedValues = TFieldValues>(props: PasswordEntryRowProps<TFieldValues, TName, TTransformedValues>): ReactNode;
6
+ export { PasswordEntryRow };
7
+ //# sourceMappingURL=password-entry-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-entry-row.d.ts","sourceRoot":"","sources":["../src/password-entry-row.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAOvE,uEAAuE;AACvE,iBAAS,gBAAgB,CACrB,YAAY,SAAS,WAAW,GAAG,WAAW,EAC9C,KAAK,SAAS,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,EACvF,kBAAkB,GAAG,YAAY,EACnC,KAAK,EAAE,qBAAqB,CAAC,YAAY,EAAE,KAAK,EAAE,kBAAkB,CAAC,GAAG,SAAS,CAiClF;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AdwPasswordEntryRow } from "@gtkx/jsx/adw";
3
+ import { useController } from "react-hook-form";
4
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
5
+ const selectText = (row) => {
6
+ row.selectRegion(0, -1);
7
+ };
8
+ /** Renders an `Adw.PasswordEntryRow` controlled by React Hook Form. */
9
+ function PasswordEntryRow(props) {
10
+ const { field, fieldState } = useController(props);
11
+ const rowProps = widgetProps(props);
12
+ const binding = useFieldWidget({
13
+ controllerRef: field.ref,
14
+ forwardedRef: rowProps.ref,
15
+ controllers: rowProps.controllers,
16
+ cssClasses: rowProps.cssClasses,
17
+ sensitive: rowProps.sensitive,
18
+ tooltipText: rowProps.tooltipText,
19
+ disabled: field.disabled,
20
+ isInvalid: fieldState.invalid,
21
+ errorMessage: fieldState.error?.message,
22
+ select: selectText,
23
+ }, field.onBlur);
24
+ const value = typeof field.value === "string" ? field.value : "";
25
+ return (_jsx(AdwPasswordEntryRow, { ...rowProps, ...binding, text: value, onNotifyText: (text, row) => {
26
+ field.onChange(text ?? "");
27
+ rowProps.onNotifyText?.(text, row);
28
+ } }));
29
+ }
30
+ export { PasswordEntryRow };
31
+ //# sourceMappingURL=password-entry-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password-entry-row.js","sourceRoot":"","sources":["../src/password-entry-row.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAoB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElE,MAAM,UAAU,GAAG,CAAC,GAAyB,EAAQ,EAAE;IACnD,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,uEAAuE;AACvE,SAAS,gBAAgB,CAIvB,KAAqE;IACnE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,aAAa,CAA0C,KAAK,CAAC,CAAC;IAC5F,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,cAAc,CAC1B;QACI,aAAa,EAAE,KAAK,CAAC,GAAG;QACxB,YAAY,EAAE,QAAQ,CAAC,GAAG;QAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,UAAU,CAAC,OAAO;QAC7B,YAAY,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO;QACvC,MAAM,EAAE,UAAU;KACrB,EACD,KAAK,CAAC,MAAM,CACf,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAEjE,OAAO,CACH,KAAC,mBAAmB,OACZ,QAAQ,KACR,OAAO,EACX,IAAI,EAAE,KAAK,EACX,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;YACxB,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YAC3B,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACvC,CAAC,GACH,CACL,CAAC;AACN,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import type * as Adw from \"@gtkx/gi/adw\";\nimport type { ReactNode } from \"react\";\nimport { AdwPasswordEntryRow } from \"@gtkx/jsx/adw\";\nimport { type FieldValues, useController } from \"react-hook-form\";\nimport type { FormFieldPath, PasswordEntryRowProps } from \"./types.js\";\nimport { useFieldWidget, widgetProps } from \"./internal/field.js\";\n\nconst selectText = (row: Adw.PasswordEntryRow): void => {\n row.selectRegion(0, -1);\n};\n\n/** Renders an `Adw.PasswordEntryRow` controlled by React Hook Form. */\nfunction PasswordEntryRow<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,\n TTransformedValues = TFieldValues,\n>(props: PasswordEntryRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {\n const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);\n const rowProps = widgetProps(props);\n\n const binding = useFieldWidget<Adw.PasswordEntryRow>(\n {\n controllerRef: field.ref,\n forwardedRef: rowProps.ref,\n controllers: rowProps.controllers,\n cssClasses: rowProps.cssClasses,\n sensitive: rowProps.sensitive,\n tooltipText: rowProps.tooltipText,\n disabled: field.disabled,\n isInvalid: fieldState.invalid,\n errorMessage: fieldState.error?.message,\n select: selectText,\n },\n field.onBlur,\n );\n\n const value = typeof field.value === \"string\" ? field.value : \"\";\n\n return (\n <AdwPasswordEntryRow\n {...rowProps}\n {...binding}\n text={value}\n onNotifyText={(text, row) => {\n field.onChange(text ?? \"\");\n rowProps.onNotifyText?.(text, row);\n }}\n />\n );\n}\n\nexport { PasswordEntryRow };\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from "react";
2
+ import { type FieldValues } from "react-hook-form";
3
+ import type { FormFieldPath, SpinRowProps } from "./types.js";
4
+ /** Renders an `Adw.SpinRow` controlled by React Hook Form. */
5
+ declare function SpinRow<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, number> = FormFieldPath<TFieldValues, number>, TTransformedValues = TFieldValues>(props: SpinRowProps<TFieldValues, TName, TTransformedValues>): ReactNode;
6
+ export { SpinRow };
7
+ //# sourceMappingURL=spin-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spin-row.d.ts","sourceRoot":"","sources":["../src/spin-row.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAO9D,8DAA8D;AAC9D,iBAAS,OAAO,CACZ,YAAY,SAAS,WAAW,GAAG,WAAW,EAC9C,KAAK,SAAS,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,MAAM,CAAC,EACvF,kBAAkB,GAAG,YAAY,EACnC,KAAK,EAAE,YAAY,CAAC,YAAY,EAAE,KAAK,EAAE,kBAAkB,CAAC,GAAG,SAAS,CAiCzE;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AdwSpinRow } from "@gtkx/jsx/adw";
3
+ import { useController } from "react-hook-form";
4
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
5
+ const selectText = (row) => {
6
+ row.selectRegion(0, -1);
7
+ };
8
+ /** Renders an `Adw.SpinRow` controlled by React Hook Form. */
9
+ function SpinRow(props) {
10
+ const { field, fieldState } = useController(props);
11
+ const rowProps = widgetProps(props);
12
+ const binding = useFieldWidget({
13
+ controllerRef: field.ref,
14
+ forwardedRef: rowProps.ref,
15
+ controllers: rowProps.controllers,
16
+ cssClasses: rowProps.cssClasses,
17
+ sensitive: rowProps.sensitive,
18
+ tooltipText: rowProps.tooltipText,
19
+ disabled: field.disabled,
20
+ isInvalid: fieldState.invalid,
21
+ errorMessage: fieldState.error?.message,
22
+ select: selectText,
23
+ }, field.onBlur);
24
+ const value = typeof field.value === "number" ? field.value : 0;
25
+ return (_jsx(AdwSpinRow, { ...rowProps, ...binding, value: value, onNotifyValue: (nextValue, row) => {
26
+ field.onChange(nextValue ?? 0);
27
+ rowProps.onNotifyValue?.(nextValue, row);
28
+ } }));
29
+ }
30
+ export { SpinRow };
31
+ //# sourceMappingURL=spin-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spin-row.js","sourceRoot":"","sources":["../src/spin-row.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAoB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElE,MAAM,UAAU,GAAG,CAAC,GAAgB,EAAQ,EAAE;IAC1C,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,8DAA8D;AAC9D,SAAS,OAAO,CAId,KAA4D;IAC1D,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,aAAa,CAA0C,KAAK,CAAC,CAAC;IAC5F,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,cAAc,CAC1B;QACI,aAAa,EAAE,KAAK,CAAC,GAAG;QACxB,YAAY,EAAE,QAAQ,CAAC,GAAG;QAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,UAAU,CAAC,OAAO;QAC7B,YAAY,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO;QACvC,MAAM,EAAE,UAAU;KACrB,EACD,KAAK,CAAC,MAAM,CACf,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhE,OAAO,CACH,KAAC,UAAU,OACH,QAAQ,KACR,OAAO,EACX,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE;YAC9B,KAAK,CAAC,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;YAC/B,QAAQ,CAAC,aAAa,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC,GACH,CACL,CAAC;AACN,CAAC;AAED,OAAO,EAAE,OAAO,EAAE,CAAC","sourcesContent":["import type * as Adw from \"@gtkx/gi/adw\";\nimport type { ReactNode } from \"react\";\nimport { AdwSpinRow } from \"@gtkx/jsx/adw\";\nimport { type FieldValues, useController } from \"react-hook-form\";\nimport type { FormFieldPath, SpinRowProps } from \"./types.js\";\nimport { useFieldWidget, widgetProps } from \"./internal/field.js\";\n\nconst selectText = (row: Adw.SpinRow): void => {\n row.selectRegion(0, -1);\n};\n\n/** Renders an `Adw.SpinRow` controlled by React Hook Form. */\nfunction SpinRow<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, number> = FormFieldPath<TFieldValues, number>,\n TTransformedValues = TFieldValues,\n>(props: SpinRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {\n const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);\n const rowProps = widgetProps(props);\n\n const binding = useFieldWidget<Adw.SpinRow>(\n {\n controllerRef: field.ref,\n forwardedRef: rowProps.ref,\n controllers: rowProps.controllers,\n cssClasses: rowProps.cssClasses,\n sensitive: rowProps.sensitive,\n tooltipText: rowProps.tooltipText,\n disabled: field.disabled,\n isInvalid: fieldState.invalid,\n errorMessage: fieldState.error?.message,\n select: selectText,\n },\n field.onBlur,\n );\n\n const value = typeof field.value === \"number\" ? field.value : 0;\n\n return (\n <AdwSpinRow\n {...rowProps}\n {...binding}\n value={value}\n onNotifyValue={(nextValue, row) => {\n field.onChange(nextValue ?? 0);\n rowProps.onNotifyValue?.(nextValue, row);\n }}\n />\n );\n}\n\nexport { SpinRow };\n"]}
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from "react";
2
+ import { type FieldValues } from "react-hook-form";
3
+ import type { FormFieldPath, SwitchRowProps } from "./types.js";
4
+ /** Renders an `Adw.SwitchRow` controlled by React Hook Form. */
5
+ declare function SwitchRow<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, boolean> = FormFieldPath<TFieldValues, boolean>, TTransformedValues = TFieldValues>(props: SwitchRowProps<TFieldValues, TName, TTransformedValues>): ReactNode;
6
+ export { SwitchRow };
7
+ //# sourceMappingURL=switch-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch-row.d.ts","sourceRoot":"","sources":["../src/switch-row.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAE,KAAK,WAAW,EAAiB,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhE,gEAAgE;AAChE,iBAAS,SAAS,CACd,YAAY,SAAS,WAAW,GAAG,WAAW,EAC9C,KAAK,SAAS,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,GAAG,aAAa,CAAC,YAAY,EAAE,OAAO,CAAC,EACzF,kBAAkB,GAAG,YAAY,EACnC,KAAK,EAAE,cAAc,CAAC,YAAY,EAAE,KAAK,EAAE,kBAAkB,CAAC,GAAG,SAAS,CA8B3E;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { AdwSwitchRow } from "@gtkx/jsx/adw";
3
+ import { useController } from "react-hook-form";
4
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
5
+ /** Renders an `Adw.SwitchRow` controlled by React Hook Form. */
6
+ function SwitchRow(props) {
7
+ const { field, fieldState } = useController(props);
8
+ const rowProps = widgetProps(props);
9
+ const binding = useFieldWidget({
10
+ controllerRef: field.ref,
11
+ forwardedRef: rowProps.ref,
12
+ controllers: rowProps.controllers,
13
+ cssClasses: rowProps.cssClasses,
14
+ sensitive: rowProps.sensitive,
15
+ tooltipText: rowProps.tooltipText,
16
+ disabled: field.disabled,
17
+ isInvalid: fieldState.invalid,
18
+ errorMessage: fieldState.error?.message,
19
+ }, field.onBlur);
20
+ return (_jsx(AdwSwitchRow, { ...rowProps, ...binding, active: field.value === true, onNotifyActive: (active, row) => {
21
+ field.onChange(active === true);
22
+ rowProps.onNotifyActive?.(active, row);
23
+ } }));
24
+ }
25
+ export { SwitchRow };
26
+ //# sourceMappingURL=switch-row.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch-row.js","sourceRoot":"","sources":["../src/switch-row.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAoB,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAElE,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElE,gEAAgE;AAChE,SAAS,SAAS,CAIhB,KAA8D;IAC5D,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,aAAa,CAA0C,KAAK,CAAC,CAAC;IAC5F,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEpC,MAAM,OAAO,GAAG,cAAc,CAC1B;QACI,aAAa,EAAE,KAAK,CAAC,GAAG;QACxB,YAAY,EAAE,QAAQ,CAAC,GAAG;QAC1B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,UAAU,CAAC,OAAO;QAC7B,YAAY,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO;KAC1C,EACD,KAAK,CAAC,MAAM,CACf,CAAC;IAEF,OAAO,CACH,KAAC,YAAY,OACL,QAAQ,KACR,OAAO,EACX,MAAM,EAAE,KAAK,CAAC,KAAK,KAAK,IAAI,EAC5B,cAAc,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;YAC5B,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;YAChC,QAAQ,CAAC,cAAc,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC3C,CAAC,GACH,CACL,CAAC;AACN,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,CAAC","sourcesContent":["import type * as Adw from \"@gtkx/gi/adw\";\nimport type { ReactNode } from \"react\";\nimport { AdwSwitchRow } from \"@gtkx/jsx/adw\";\nimport { type FieldValues, useController } from \"react-hook-form\";\nimport type { FormFieldPath, SwitchRowProps } from \"./types.js\";\nimport { useFieldWidget, widgetProps } from \"./internal/field.js\";\n\n/** Renders an `Adw.SwitchRow` controlled by React Hook Form. */\nfunction SwitchRow<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, boolean> = FormFieldPath<TFieldValues, boolean>,\n TTransformedValues = TFieldValues,\n>(props: SwitchRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {\n const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);\n const rowProps = widgetProps(props);\n\n const binding = useFieldWidget<Adw.SwitchRow>(\n {\n controllerRef: field.ref,\n forwardedRef: rowProps.ref,\n controllers: rowProps.controllers,\n cssClasses: rowProps.cssClasses,\n sensitive: rowProps.sensitive,\n tooltipText: rowProps.tooltipText,\n disabled: field.disabled,\n isInvalid: fieldState.invalid,\n errorMessage: fieldState.error?.message,\n },\n field.onBlur,\n );\n\n return (\n <AdwSwitchRow\n {...rowProps}\n {...binding}\n active={field.value === true}\n onNotifyActive={(active, row) => {\n field.onChange(active === true);\n rowProps.onNotifyActive?.(active, row);\n }}\n />\n );\n}\n\nexport { SwitchRow };\n"]}
@@ -0,0 +1,19 @@
1
+ import type { ComboRowProps as BaseComboRowProps } from "@gtkx/components/adw";
2
+ import type { AdwEntryRowProps, AdwPasswordEntryRowProps, AdwSpinRowProps, AdwSwitchRowProps } from "@gtkx/jsx/adw";
3
+ import type { FieldPath, FieldPathByValue, FieldValues, UseControllerProps } from "react-hook-form";
4
+ /** A form field path whose value can be represented by a GTKX control. */
5
+ type FormFieldPath<TFieldValues extends FieldValues, TValue> = FieldPathByValue<TFieldValues, TValue | null | undefined>;
6
+ /** React Hook Form controller options shared by every GTKX form field. */
7
+ type FormFieldProps<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, TTransformedValues = TFieldValues> = UseControllerProps<TFieldValues, TName, TTransformedValues>;
8
+ /** Props for an Adwaita entry row controlled by React Hook Form. */
9
+ type EntryRowProps<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>, TTransformedValues = TFieldValues> = Omit<AdwEntryRowProps, "accessibleInvalid" | "text"> & FormFieldProps<TFieldValues, TName, TTransformedValues>;
10
+ /** Props for an Adwaita password entry row controlled by React Hook Form. */
11
+ type PasswordEntryRowProps<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>, TTransformedValues = TFieldValues> = Omit<AdwPasswordEntryRowProps, "accessibleInvalid" | "text"> & FormFieldProps<TFieldValues, TName, TTransformedValues>;
12
+ /** Props for an Adwaita switch row controlled by React Hook Form. */
13
+ type SwitchRowProps<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, boolean> = FormFieldPath<TFieldValues, boolean>, TTransformedValues = TFieldValues> = Omit<AdwSwitchRowProps, "accessibleInvalid" | "active"> & FormFieldProps<TFieldValues, TName, TTransformedValues>;
14
+ /** Props for an Adwaita spin row controlled by React Hook Form. */
15
+ type SpinRowProps<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, number> = FormFieldPath<TFieldValues, number>, TTransformedValues = TFieldValues> = Omit<AdwSpinRowProps, "accessibleInvalid" | "text" | "value"> & FormFieldProps<TFieldValues, TName, TTransformedValues>;
16
+ /** Props for an ID-based Adwaita combo row controlled by React Hook Form. */
17
+ type ComboRowProps<TFieldValues extends FieldValues = FieldValues, TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>, TTransformedValues = TFieldValues, TItem = unknown, TSection = unknown> = Omit<BaseComboRowProps<TItem, TSection>, "accessibleInvalid" | "selectedId"> & FormFieldProps<TFieldValues, TName, TTransformedValues>;
18
+ export { type ComboRowProps, type EntryRowProps, type FormFieldPath, type FormFieldProps, type PasswordEntryRowProps, type SpinRowProps, type SwitchRowProps, };
19
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAgEA,OAAO,EAQN,CAAC","sourcesContent":["import type { ComboRowProps as BaseComboRowProps } from \"@gtkx/components/adw\";\nimport type {\n AdwEntryRowProps,\n AdwPasswordEntryRowProps,\n AdwSpinRowProps,\n AdwSwitchRowProps,\n} from \"@gtkx/jsx/adw\";\nimport type { FieldPath, FieldPathByValue, FieldValues, UseControllerProps } from \"react-hook-form\";\n\n/** A form field path whose value can be represented by a GTKX control. */\ntype FormFieldPath<TFieldValues extends FieldValues, TValue> = FieldPathByValue<\n TFieldValues,\n TValue | null | undefined\n>;\n\n/** React Hook Form controller options shared by every GTKX form field. */\ntype FormFieldProps<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n TTransformedValues = TFieldValues,\n> = UseControllerProps<TFieldValues, TName, TTransformedValues>;\n\n/** Props for an Adwaita entry row controlled by React Hook Form. */\ntype EntryRowProps<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,\n TTransformedValues = TFieldValues,\n> = Omit<AdwEntryRowProps, \"accessibleInvalid\" | \"text\"> &\n FormFieldProps<TFieldValues, TName, TTransformedValues>;\n\n/** Props for an Adwaita password entry row controlled by React Hook Form. */\ntype PasswordEntryRowProps<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,\n TTransformedValues = TFieldValues,\n> = Omit<AdwPasswordEntryRowProps, \"accessibleInvalid\" | \"text\"> &\n FormFieldProps<TFieldValues, TName, TTransformedValues>;\n\n/** Props for an Adwaita switch row controlled by React Hook Form. */\ntype SwitchRowProps<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, boolean> = FormFieldPath<TFieldValues, boolean>,\n TTransformedValues = TFieldValues,\n> = Omit<AdwSwitchRowProps, \"accessibleInvalid\" | \"active\"> &\n FormFieldProps<TFieldValues, TName, TTransformedValues>;\n\n/** Props for an Adwaita spin row controlled by React Hook Form. */\ntype SpinRowProps<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, number> = FormFieldPath<TFieldValues, number>,\n TTransformedValues = TFieldValues,\n> = Omit<AdwSpinRowProps, \"accessibleInvalid\" | \"text\" | \"value\"> &\n FormFieldProps<TFieldValues, TName, TTransformedValues>;\n\n/** Props for an ID-based Adwaita combo row controlled by React Hook Form. */\ntype ComboRowProps<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,\n TTransformedValues = TFieldValues,\n TItem = unknown,\n TSection = unknown,\n> = Omit<BaseComboRowProps<TItem, TSection>, \"accessibleInvalid\" | \"selectedId\"> &\n FormFieldProps<TFieldValues, TName, TTransformedValues>;\n\nexport {\n type ComboRowProps,\n type EntryRowProps,\n type FormFieldPath,\n type FormFieldProps,\n type PasswordEntryRowProps,\n type SpinRowProps,\n type SwitchRowProps,\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@gtkx/forms",
3
+ "version": "0.0.1",
4
+ "description": "React Hook Form controls for building Adwaita forms.",
5
+ "keywords": [
6
+ "gtkx",
7
+ "gtk",
8
+ "gtk4",
9
+ "adwaita",
10
+ "react",
11
+ "react-hook-form",
12
+ "forms",
13
+ "linux",
14
+ "desktop"
15
+ ],
16
+ "homepage": "https://gtkx.dev",
17
+ "bugs": {
18
+ "url": "https://github.com/gtkx-org/gtkx/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/gtkx-org/gtkx.git",
23
+ "directory": "packages/forms"
24
+ },
25
+ "license": "MPL-2.0",
26
+ "author": "Eugenio Depalo <eugeniodepalo@gmail.com>",
27
+ "type": "module",
28
+ "exports": {
29
+ "./package.json": "./package.json",
30
+ ".": {
31
+ "source": "./src/index.ts",
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ }
35
+ },
36
+ "sideEffects": false,
37
+ "files": [
38
+ "dist",
39
+ "src"
40
+ ],
41
+ "scripts": {
42
+ "release": "tsx ../../scripts/release-package.ts"
43
+ },
44
+ "nx": {
45
+ "targets": {
46
+ "lint": {
47
+ "options": {
48
+ "cwd": ".",
49
+ "command": "eslint {projectRoot}"
50
+ }
51
+ }
52
+ }
53
+ },
54
+ "engines": {
55
+ "node": ">=24"
56
+ },
57
+ "dependencies": {
58
+ "@gtkx/components": "workspace:*",
59
+ "@gtkx/utils": "workspace:*",
60
+ "react-hook-form": "^7.86.0"
61
+ },
62
+ "peerDependencies": {
63
+ "react": "^19.2"
64
+ },
65
+ "devDependencies": {
66
+ "@gtkx/config": "workspace:*",
67
+ "@gtkx/testing": "workspace:*",
68
+ "@gtkx/vitest": "workspace:*",
69
+ "vitest": "catalog:"
70
+ }
71
+ }
@@ -0,0 +1,49 @@
1
+ import type * as Adw from "@gtkx/gi/adw";
2
+ import type { ReactNode } from "react";
3
+ import { ComboRow as BaseComboRow } from "@gtkx/components/adw";
4
+ import { type FieldValues, useController } from "react-hook-form";
5
+ import type { ComboRowProps, FormFieldPath } from "./types.js";
6
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
7
+
8
+ /** Renders an ID-based Adwaita combo row controlled by React Hook Form. */
9
+ function ComboRow<
10
+ TFieldValues extends FieldValues = FieldValues,
11
+ TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,
12
+ TTransformedValues = TFieldValues,
13
+ TItem = unknown,
14
+ TSection = unknown,
15
+ >(props: ComboRowProps<TFieldValues, TName, TTransformedValues, TItem, TSection>): ReactNode {
16
+ const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);
17
+ const rowProps = widgetProps(props);
18
+
19
+ const binding = useFieldWidget<Adw.ComboRow>(
20
+ {
21
+ controllerRef: field.ref,
22
+ forwardedRef: rowProps.ref,
23
+ controllers: rowProps.controllers,
24
+ cssClasses: rowProps.cssClasses,
25
+ sensitive: rowProps.sensitive,
26
+ tooltipText: rowProps.tooltipText,
27
+ disabled: field.disabled,
28
+ isInvalid: fieldState.invalid,
29
+ errorMessage: fieldState.error?.message,
30
+ },
31
+ field.onBlur,
32
+ );
33
+
34
+ const selectedId = typeof field.value === "string" ? field.value : null;
35
+
36
+ return (
37
+ <BaseComboRow
38
+ {...rowProps}
39
+ {...binding}
40
+ selectedId={selectedId}
41
+ onSelectionChanged={(id) => {
42
+ field.onChange(id);
43
+ rowProps.onSelectionChanged?.(id);
44
+ }}
45
+ />
46
+ );
47
+ }
48
+
49
+ export { ComboRow };
@@ -0,0 +1,52 @@
1
+ import type * as Adw from "@gtkx/gi/adw";
2
+ import type { ReactNode } from "react";
3
+ import { AdwEntryRow } from "@gtkx/jsx/adw";
4
+ import { type FieldValues, useController } from "react-hook-form";
5
+ import type { EntryRowProps, FormFieldPath } from "./types.js";
6
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
7
+
8
+ const selectText = (row: Adw.EntryRow): void => {
9
+ row.selectRegion(0, -1);
10
+ };
11
+
12
+ /** Renders an `Adw.EntryRow` controlled by React Hook Form. */
13
+ function EntryRow<
14
+ TFieldValues extends FieldValues = FieldValues,
15
+ TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,
16
+ TTransformedValues = TFieldValues,
17
+ >(props: EntryRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {
18
+ const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);
19
+ const rowProps = widgetProps(props);
20
+
21
+ const binding = useFieldWidget<Adw.EntryRow>(
22
+ {
23
+ controllerRef: field.ref,
24
+ forwardedRef: rowProps.ref,
25
+ controllers: rowProps.controllers,
26
+ cssClasses: rowProps.cssClasses,
27
+ sensitive: rowProps.sensitive,
28
+ tooltipText: rowProps.tooltipText,
29
+ disabled: field.disabled,
30
+ isInvalid: fieldState.invalid,
31
+ errorMessage: fieldState.error?.message,
32
+ select: selectText,
33
+ },
34
+ field.onBlur,
35
+ );
36
+
37
+ const value = typeof field.value === "string" ? field.value : "";
38
+
39
+ return (
40
+ <AdwEntryRow
41
+ {...rowProps}
42
+ {...binding}
43
+ text={value}
44
+ onNotifyText={(text, row) => {
45
+ field.onChange(text ?? "");
46
+ rowProps.onNotifyText?.(text, row);
47
+ }}
48
+ />
49
+ );
50
+ }
51
+
52
+ export { EntryRow };
package/src/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ export { ComboRow } from "./combo-row.js";
2
+ export { EntryRow } from "./entry-row.js";
3
+ export { PasswordEntryRow } from "./password-entry-row.js";
4
+ export { SpinRow } from "./spin-row.js";
5
+ export { SwitchRow } from "./switch-row.js";
6
+ export type {
7
+ ComboRowProps,
8
+ EntryRowProps,
9
+ FormFieldPath,
10
+ FormFieldProps,
11
+ PasswordEntryRowProps,
12
+ SpinRowProps,
13
+ SwitchRowProps,
14
+ } from "./types.js";
15
+ export {
16
+ Controller,
17
+ FormProvider,
18
+ useController,
19
+ useFieldArray,
20
+ useForm,
21
+ useFormContext,
22
+ useFormState,
23
+ useWatch,
24
+ } from "react-hook-form";
25
+ export type {
26
+ ArrayPath,
27
+ Control,
28
+ ControllerFieldState,
29
+ ControllerProps,
30
+ ControllerRenderProps,
31
+ DeepPartial,
32
+ DefaultValues,
33
+ ErrorOption,
34
+ FieldArrayPath,
35
+ FieldArrayWithId,
36
+ FieldError,
37
+ FieldErrors,
38
+ FieldPath,
39
+ FieldPathByValue,
40
+ FieldPathValue,
41
+ FieldPathValues,
42
+ FieldValues,
43
+ FormProviderProps,
44
+ KeepStateOptions,
45
+ Path,
46
+ PathValue,
47
+ RegisterOptions,
48
+ Resolver,
49
+ ResolverOptions,
50
+ ResolverResult,
51
+ SetFocusOptions,
52
+ SetValueConfig,
53
+ SubmitErrorHandler,
54
+ SubmitHandler,
55
+ UseControllerProps,
56
+ UseControllerReturn,
57
+ UseFieldArrayProps,
58
+ UseFieldArrayReturn,
59
+ UseFormProps,
60
+ UseFormReturn,
61
+ UseFormStateProps,
62
+ UseFormStateReturn,
63
+ UseWatchProps,
64
+ Validate,
65
+ ValidationRule,
66
+ WatchObserver,
67
+ } from "react-hook-form";
@@ -0,0 +1,121 @@
1
+ import type { RefCallBack } from "react-hook-form";
2
+ import * as Gtk from "@gtkx/gi/gtk";
3
+ import { GtkEventControllerFocus } from "@gtkx/jsx/gtk";
4
+ import { omit } from "@gtkx/utils";
5
+ import { type ReactElement, type ReactNode, type Ref, type RefCallback, useCallback } from "react";
6
+
7
+ type FormFieldPropName =
8
+ | "control" |
9
+ "defaultValue" |
10
+ "disabled" |
11
+ "exact" |
12
+ "name" |
13
+ "rules" |
14
+ "shouldUnregister";
15
+
16
+ type FieldWidget = Gtk.Widget & {
17
+ grabFocus: () => boolean;
18
+ };
19
+
20
+ type FieldWidgetOptions<Widget extends FieldWidget> = {
21
+ controllerRef: RefCallBack;
22
+ forwardedRef: Ref<Widget> | undefined;
23
+ controllers: ReactNode | null | undefined;
24
+ cssClasses: string[] | null | undefined;
25
+ sensitive: boolean | null | undefined;
26
+ tooltipText: string | null | undefined;
27
+ disabled: boolean | undefined;
28
+ isInvalid: boolean;
29
+ errorMessage: string | undefined;
30
+ select?: ((widget: Widget) => void) | undefined;
31
+ };
32
+
33
+ type FieldWidgetBinding<Widget extends FieldWidget> = {
34
+ ref: RefCallback<Widget>;
35
+ controllers: ReactElement;
36
+ cssClasses: string[] | null | undefined;
37
+ sensitive: boolean | null | undefined;
38
+ tooltipText: string | null | undefined;
39
+ accessibleInvalid: Gtk.AccessibleInvalidState;
40
+ };
41
+
42
+ const FORM_FIELD_PROP_NAMES: FormFieldPropName[] = [
43
+ "control",
44
+ "defaultValue",
45
+ "disabled",
46
+ "exact",
47
+ "name",
48
+ "rules",
49
+ "shouldUnregister",
50
+ ];
51
+
52
+ const setRef = <Widget,>(ref: Ref<Widget> | undefined, widget: Widget | null): void => {
53
+ if (typeof ref === "function") {
54
+ ref(widget);
55
+ } else if (ref !== undefined && ref !== null) {
56
+ ref.current = widget;
57
+ }
58
+ };
59
+
60
+ const withErrorClass = (
61
+ cssClasses: string[] | null | undefined,
62
+ isInvalid: boolean,
63
+ ): string[] | null | undefined => {
64
+ if (!isInvalid || cssClasses?.includes("error") === true) {
65
+ return cssClasses;
66
+ }
67
+
68
+ return [...(cssClasses ?? []), "error"];
69
+ };
70
+
71
+ const useFieldWidgetRef = <Widget extends FieldWidget>({
72
+ controllerRef,
73
+ forwardedRef,
74
+ select,
75
+ }: Pick<FieldWidgetOptions<Widget>, "controllerRef" | "forwardedRef" | "select">): RefCallback<Widget> =>
76
+ useCallback(
77
+ (widget) => {
78
+ setRef(forwardedRef, widget);
79
+
80
+ if (widget === null) {
81
+ controllerRef(null);
82
+
83
+ return;
84
+ }
85
+
86
+ controllerRef({
87
+ focus: () => {
88
+ widget.grabFocus();
89
+ },
90
+ ...(select !== undefined && {
91
+ select: () => {
92
+ select(widget);
93
+ },
94
+ }),
95
+ });
96
+ },
97
+ [controllerRef, forwardedRef, select],
98
+ );
99
+
100
+ const useFieldWidget = <Widget extends FieldWidget>(
101
+ options: FieldWidgetOptions<Widget>,
102
+ onBlur: () => void,
103
+ ): FieldWidgetBinding<Widget> => ({
104
+ ref: useFieldWidgetRef(options),
105
+ controllers: (
106
+ <>
107
+ {options.controllers}
108
+ <GtkEventControllerFocus onLeave={onBlur} />
109
+ </>
110
+ ),
111
+ cssClasses: withErrorClass(options.cssClasses, options.isInvalid),
112
+ sensitive: options.disabled === true ? false : options.sensitive,
113
+ tooltipText: options.isInvalid && options.errorMessage !== undefined ? options.errorMessage : options.tooltipText,
114
+ accessibleInvalid: options.isInvalid ? Gtk.AccessibleInvalidState.TRUE : Gtk.AccessibleInvalidState.FALSE,
115
+ });
116
+
117
+ const widgetProps = <Props extends Partial<Record<FormFieldPropName, unknown>>>(
118
+ props: Props,
119
+ ): Omit<Props, FormFieldPropName> => omit(props, FORM_FIELD_PROP_NAMES);
120
+
121
+ export { useFieldWidget, widgetProps };
@@ -0,0 +1,52 @@
1
+ import type * as Adw from "@gtkx/gi/adw";
2
+ import type { ReactNode } from "react";
3
+ import { AdwPasswordEntryRow } from "@gtkx/jsx/adw";
4
+ import { type FieldValues, useController } from "react-hook-form";
5
+ import type { FormFieldPath, PasswordEntryRowProps } from "./types.js";
6
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
7
+
8
+ const selectText = (row: Adw.PasswordEntryRow): void => {
9
+ row.selectRegion(0, -1);
10
+ };
11
+
12
+ /** Renders an `Adw.PasswordEntryRow` controlled by React Hook Form. */
13
+ function PasswordEntryRow<
14
+ TFieldValues extends FieldValues = FieldValues,
15
+ TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,
16
+ TTransformedValues = TFieldValues,
17
+ >(props: PasswordEntryRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {
18
+ const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);
19
+ const rowProps = widgetProps(props);
20
+
21
+ const binding = useFieldWidget<Adw.PasswordEntryRow>(
22
+ {
23
+ controllerRef: field.ref,
24
+ forwardedRef: rowProps.ref,
25
+ controllers: rowProps.controllers,
26
+ cssClasses: rowProps.cssClasses,
27
+ sensitive: rowProps.sensitive,
28
+ tooltipText: rowProps.tooltipText,
29
+ disabled: field.disabled,
30
+ isInvalid: fieldState.invalid,
31
+ errorMessage: fieldState.error?.message,
32
+ select: selectText,
33
+ },
34
+ field.onBlur,
35
+ );
36
+
37
+ const value = typeof field.value === "string" ? field.value : "";
38
+
39
+ return (
40
+ <AdwPasswordEntryRow
41
+ {...rowProps}
42
+ {...binding}
43
+ text={value}
44
+ onNotifyText={(text, row) => {
45
+ field.onChange(text ?? "");
46
+ rowProps.onNotifyText?.(text, row);
47
+ }}
48
+ />
49
+ );
50
+ }
51
+
52
+ export { PasswordEntryRow };
@@ -0,0 +1,52 @@
1
+ import type * as Adw from "@gtkx/gi/adw";
2
+ import type { ReactNode } from "react";
3
+ import { AdwSpinRow } from "@gtkx/jsx/adw";
4
+ import { type FieldValues, useController } from "react-hook-form";
5
+ import type { FormFieldPath, SpinRowProps } from "./types.js";
6
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
7
+
8
+ const selectText = (row: Adw.SpinRow): void => {
9
+ row.selectRegion(0, -1);
10
+ };
11
+
12
+ /** Renders an `Adw.SpinRow` controlled by React Hook Form. */
13
+ function SpinRow<
14
+ TFieldValues extends FieldValues = FieldValues,
15
+ TName extends FormFieldPath<TFieldValues, number> = FormFieldPath<TFieldValues, number>,
16
+ TTransformedValues = TFieldValues,
17
+ >(props: SpinRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {
18
+ const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);
19
+ const rowProps = widgetProps(props);
20
+
21
+ const binding = useFieldWidget<Adw.SpinRow>(
22
+ {
23
+ controllerRef: field.ref,
24
+ forwardedRef: rowProps.ref,
25
+ controllers: rowProps.controllers,
26
+ cssClasses: rowProps.cssClasses,
27
+ sensitive: rowProps.sensitive,
28
+ tooltipText: rowProps.tooltipText,
29
+ disabled: field.disabled,
30
+ isInvalid: fieldState.invalid,
31
+ errorMessage: fieldState.error?.message,
32
+ select: selectText,
33
+ },
34
+ field.onBlur,
35
+ );
36
+
37
+ const value = typeof field.value === "number" ? field.value : 0;
38
+
39
+ return (
40
+ <AdwSpinRow
41
+ {...rowProps}
42
+ {...binding}
43
+ value={value}
44
+ onNotifyValue={(nextValue, row) => {
45
+ field.onChange(nextValue ?? 0);
46
+ rowProps.onNotifyValue?.(nextValue, row);
47
+ }}
48
+ />
49
+ );
50
+ }
51
+
52
+ export { SpinRow };
@@ -0,0 +1,45 @@
1
+ import type * as Adw from "@gtkx/gi/adw";
2
+ import type { ReactNode } from "react";
3
+ import { AdwSwitchRow } from "@gtkx/jsx/adw";
4
+ import { type FieldValues, useController } from "react-hook-form";
5
+ import type { FormFieldPath, SwitchRowProps } from "./types.js";
6
+ import { useFieldWidget, widgetProps } from "./internal/field.js";
7
+
8
+ /** Renders an `Adw.SwitchRow` controlled by React Hook Form. */
9
+ function SwitchRow<
10
+ TFieldValues extends FieldValues = FieldValues,
11
+ TName extends FormFieldPath<TFieldValues, boolean> = FormFieldPath<TFieldValues, boolean>,
12
+ TTransformedValues = TFieldValues,
13
+ >(props: SwitchRowProps<TFieldValues, TName, TTransformedValues>): ReactNode {
14
+ const { field, fieldState } = useController<TFieldValues, TName, TTransformedValues>(props);
15
+ const rowProps = widgetProps(props);
16
+
17
+ const binding = useFieldWidget<Adw.SwitchRow>(
18
+ {
19
+ controllerRef: field.ref,
20
+ forwardedRef: rowProps.ref,
21
+ controllers: rowProps.controllers,
22
+ cssClasses: rowProps.cssClasses,
23
+ sensitive: rowProps.sensitive,
24
+ tooltipText: rowProps.tooltipText,
25
+ disabled: field.disabled,
26
+ isInvalid: fieldState.invalid,
27
+ errorMessage: fieldState.error?.message,
28
+ },
29
+ field.onBlur,
30
+ );
31
+
32
+ return (
33
+ <AdwSwitchRow
34
+ {...rowProps}
35
+ {...binding}
36
+ active={field.value === true}
37
+ onNotifyActive={(active, row) => {
38
+ field.onChange(active === true);
39
+ rowProps.onNotifyActive?.(active, row);
40
+ }}
41
+ />
42
+ );
43
+ }
44
+
45
+ export { SwitchRow };
package/src/types.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { ComboRowProps as BaseComboRowProps } from "@gtkx/components/adw";
2
+ import type {
3
+ AdwEntryRowProps,
4
+ AdwPasswordEntryRowProps,
5
+ AdwSpinRowProps,
6
+ AdwSwitchRowProps,
7
+ } from "@gtkx/jsx/adw";
8
+ import type { FieldPath, FieldPathByValue, FieldValues, UseControllerProps } from "react-hook-form";
9
+
10
+ /** A form field path whose value can be represented by a GTKX control. */
11
+ type FormFieldPath<TFieldValues extends FieldValues, TValue> = FieldPathByValue<
12
+ TFieldValues,
13
+ TValue | null | undefined
14
+ >;
15
+
16
+ /** React Hook Form controller options shared by every GTKX form field. */
17
+ type FormFieldProps<
18
+ TFieldValues extends FieldValues = FieldValues,
19
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
20
+ TTransformedValues = TFieldValues,
21
+ > = UseControllerProps<TFieldValues, TName, TTransformedValues>;
22
+
23
+ /** Props for an Adwaita entry row controlled by React Hook Form. */
24
+ type EntryRowProps<
25
+ TFieldValues extends FieldValues = FieldValues,
26
+ TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,
27
+ TTransformedValues = TFieldValues,
28
+ > = Omit<AdwEntryRowProps, "accessibleInvalid" | "text"> &
29
+ FormFieldProps<TFieldValues, TName, TTransformedValues>;
30
+
31
+ /** Props for an Adwaita password entry row controlled by React Hook Form. */
32
+ type PasswordEntryRowProps<
33
+ TFieldValues extends FieldValues = FieldValues,
34
+ TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,
35
+ TTransformedValues = TFieldValues,
36
+ > = Omit<AdwPasswordEntryRowProps, "accessibleInvalid" | "text"> &
37
+ FormFieldProps<TFieldValues, TName, TTransformedValues>;
38
+
39
+ /** Props for an Adwaita switch row controlled by React Hook Form. */
40
+ type SwitchRowProps<
41
+ TFieldValues extends FieldValues = FieldValues,
42
+ TName extends FormFieldPath<TFieldValues, boolean> = FormFieldPath<TFieldValues, boolean>,
43
+ TTransformedValues = TFieldValues,
44
+ > = Omit<AdwSwitchRowProps, "accessibleInvalid" | "active"> &
45
+ FormFieldProps<TFieldValues, TName, TTransformedValues>;
46
+
47
+ /** Props for an Adwaita spin row controlled by React Hook Form. */
48
+ type SpinRowProps<
49
+ TFieldValues extends FieldValues = FieldValues,
50
+ TName extends FormFieldPath<TFieldValues, number> = FormFieldPath<TFieldValues, number>,
51
+ TTransformedValues = TFieldValues,
52
+ > = Omit<AdwSpinRowProps, "accessibleInvalid" | "text" | "value"> &
53
+ FormFieldProps<TFieldValues, TName, TTransformedValues>;
54
+
55
+ /** Props for an ID-based Adwaita combo row controlled by React Hook Form. */
56
+ type ComboRowProps<
57
+ TFieldValues extends FieldValues = FieldValues,
58
+ TName extends FormFieldPath<TFieldValues, string> = FormFieldPath<TFieldValues, string>,
59
+ TTransformedValues = TFieldValues,
60
+ TItem = unknown,
61
+ TSection = unknown,
62
+ > = Omit<BaseComboRowProps<TItem, TSection>, "accessibleInvalid" | "selectedId"> &
63
+ FormFieldProps<TFieldValues, TName, TTransformedValues>;
64
+
65
+ export {
66
+ type ComboRowProps,
67
+ type EntryRowProps,
68
+ type FormFieldPath,
69
+ type FormFieldProps,
70
+ type PasswordEntryRowProps,
71
+ type SpinRowProps,
72
+ type SwitchRowProps,
73
+ };