@decky.fx/raven-tsx 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,12 @@
1
+ raven-tsx
2
+ Copyright 2026 Decky Fx
3
+
4
+ This package contains the Bun binding of Raven (src/vendor/raven.ts), written by
5
+ tools/bindgen in Raven, unchanged. Its native packages contain Raven's library.
6
+
7
+ Raven: https://github.com/oscar-soirey/Raven
8
+ Raven
9
+ Copyright 2026 Oscar Soirey
10
+
11
+ This product is licensed under the Apache License, Version 2.0 (see LICENSE).
12
+ It includes third-party components under their own licenses; see THIRD_PARTY.md.
package/RAVEN_VERSION ADDED
@@ -0,0 +1 @@
1
+ 899a268cb0bf11c771c18076d9375243dee47e43
package/README.md CHANGED
@@ -1,3 +1,395 @@
1
- # Temporary Holding Version
1
+ # raven-tsx
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Native desktop interfaces in TSX, for [Bun](https://bun.com), on the real widgets of [Raven](https://github.com/oscar-soirey/Raven). A core knows only Raven, and an adapter for a framework is built on it; React is the one there is today. No XML and no build step, as Bun compiles TSX itself.
4
+
5
+ ```tsx
6
+ import { useState } from "react";
7
+ import { Button, Label, runApp, VBox } from "@decky.fx/raven-tsx/react";
8
+
9
+ function App() {
10
+ const [count, setCount] = useState(0);
11
+ return (
12
+ <VBox Spacing={12} Slot={{ Padding: 24 }}>
13
+ <Label>Clicked {count} times</Label>
14
+ <Button Style="Primary" OnClicked={() => setCount(count + 1)}>Click me</Button>
15
+ </VBox>
16
+ );
17
+ }
18
+
19
+ await runApp(<App />, { Title: "Hello", Width: 480, Height: 240 });
20
+ ```
21
+
22
+ ![The studio example: a node graph and a timeline, in the Blueprint look](../../docs/screenshots/studio.png)
23
+
24
+ Every C example of Raven has a TSX port in [examples](../../examples), with screenshots in the [README of the repository](../../README.md).
25
+
26
+ ## Install
27
+
28
+ bun add @decky.fx/raven-tsx react react-reconciler
29
+
30
+ `react` and `react-reconciler` are optional peer dependencies: `@decky.fx/raven-tsx/react` needs both, and an application that uses only the core installs neither.
31
+
32
+ | Import | What it is |
33
+ |---|---|
34
+ | `@decky.fx/raven-tsx/react` | The components, `runApp`, the hooks and `registerTag`, for React, and everything of the core |
35
+ | `@decky.fx/raven-tsx` | The core, for code that is not a component: `defineTheme`, `loadTheme`, `raven`, and `onAction`, `onShortcut`, `onFrame`, which each return what stops them |
36
+
37
+ ```json
38
+ { "compilerOptions": { "jsx": "react-jsx", "types": ["bun", "react"] } }
39
+ ```
40
+
41
+ The package is TypeScript, so your compiler checks it with your settings (`skipLibCheck` skips only `.d.ts` files). It compiles with `strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes` (optional props take `undefined`, as React's do), `noPropertyAccessFromIndexSignature`, `noUnusedLocals`, `noUnusedParameters`, `noImplicitOverride`, `noImplicitReturns`, `noFallthroughCasesInSwitch`, `isolatedModules` and `verbatimModuleSyntax`. Not yet with `erasableSyntaxOnly`: Raven's own binding has `enum`s.
42
+
43
+ Raven's native library comes with it: there is a package per platform, and the package manager installs the one that fits. No install script runs, and nothing is compiled on your machine.
44
+
45
+ | Platform | Package |
46
+ |---|---|
47
+ | Linux x64, arm64 | `@decky.fx/raven-tsx-linux-x64`, `-linux-arm64` |
48
+ | macOS Apple Silicon, Intel | `@decky.fx/raven-tsx-darwin-arm64`, `-darwin-x64` |
49
+ | Windows x64 | `@decky.fx/raven-tsx-win32-x64` |
50
+
51
+ What the system must have: a Vulkan driver, and on Linux libX11. On macOS, version 14 or newer, Vulkan comes from MoltenVK: `brew install molten-vk`.
52
+
53
+ `RAVEN_LIBRARY` set to the path of a library has priority, to try a build of your own.
54
+
55
+ ## One executable
56
+
57
+ `bun build --compile` holds Bun, your code and the native library in one file:
58
+
59
+ bun build --compile src/index.tsx --outfile app
60
+
61
+ For another platform, install its package first:
62
+
63
+ bun install --os=win32 --cpu=x64
64
+ bun build --compile --target=bun-windows-x64 src/index.tsx --outfile app.exe
65
+
66
+ ## How props work
67
+
68
+ The components, with their props, are generated from Raven's own schema.
69
+
70
+ | XML | TSX |
71
+ |---|---|
72
+ | `Text="Hi"` | `Text="Hi"`, or `<Label>Hi</Label>` |
73
+ | `Width="120"` | `Width={120}` |
74
+ | `Slot.Padding="24"` | `Slot={{ Padding: 24 }}`, since JSX has no dots in names |
75
+ | `Items="a\|b"` | `Items={["a", "b"]}` |
76
+ | `OnClicked="name"` | `OnClicked={() => ...}` |
77
+
78
+ ## Events
79
+
80
+ Events with a value give it to the function, at each change. A `TextInput` gives its text in `OnChanged` and again in `OnCommitted` on Enter.
81
+
82
+ | Component | Event | Value |
83
+ |---|---|---|
84
+ | `TextInput` | `OnChanged`, `OnCommitted` | `string` |
85
+ | `Checkbox`, `Expander` | `OnChanged` | `boolean` |
86
+ | `Slider`, `SpinBox` | `OnChanged` | `number` |
87
+ | `ComboBox`, `TabView` | `OnChanged` | index |
88
+ | `ListView`, `TileView` | `OnChanged`, `OnActivated` | index |
89
+ | `TreeView` | `OnChanged`, `OnActivated` | handle of the item |
90
+ | `Table` | `OnChanged`, `OnActivated` | row |
91
+ | `Table` | `OnSort` | column, and whether it is descending |
92
+ | `DatePicker` | `OnChanged` | `"YYYY-MM-DD"` |
93
+ | `TimePicker` | `OnChanged` | `"HH:MM:SS"` |
94
+ | `Rating` | `OnChanged` | `number` |
95
+ | `Pagination` | `OnChanged` | page, from 0 |
96
+ | `Breadcrumb` | `OnClicked` | index |
97
+ | `TextEditor` | `OnChanged`, `OnCompletion` | its text, the start of the word |
98
+ | `TextEditor` | `OnZoom` | `number` |
99
+ | `ColorPicker` | `OnChanged` | `"#RRGGBB"`, or `"#RRGGBBAA"` when it is not opaque |
100
+ | `Label` | `OnLink` | the address of the link |
101
+ | `MenuItem` | `OnClicked` | whether it is checked now |
102
+ | `Island` | `OnChanged` | `boolean` |
103
+ | `Viewport`, `<Window>` | `OnResize` | width, height |
104
+ | `FileBrowser` | `OnChanged`, `OnAccepted` | path |
105
+ | `DockArea` | `OnChanged` | its layout, for `Layout` or `Rdock_area_load_layout` |
106
+ | `TreeView`, `TileView`, `NodeGraph`, `BlockEditor` | `OnContext` | what was right clicked, x, y |
107
+ | `Timeline` | `OnTime` | seconds |
108
+ | `Timeline` | `OnItemChanged`, `OnSelection`, `OnActivated`, `OnRemoved` | item |
109
+ | `NodeGraph` | `OnLinked`, `OnUnlinked`, `OnMoved`, `OnSelection`, `OnRemoved`, `OnActivated` | link or node |
110
+ | `BlockEditor` | `OnChanged`, `OnClicked` | block |
111
+ | any widget, `<Window>` | `OnDrop` | an `RDropEvent` |
112
+ | any widget | `OnEvent` | an `REvent`, before the widget sees it: the pointer over a `Custom`, for one |
113
+
114
+ Some events ask: `OnClose` of a `TabView` (index, page) and of a `DockArea` (panel), `OnConnect` of a `NodeGraph` (two pins), and `OnEvent` and `OnDrop`, where true means taken. Return true to let the thing happen. A tab or a panel that stays can then go by taking its element out, which is the way to do it: when Raven closes a panel by itself, it deletes a widget React still has, which does no harm but leaves the element with a `.widget` that is no longer valid. `Documents` on a `TabView` gives its tabs a close button.
115
+
116
+ Other events give nothing, and share the one action a widget has in Raven: give a widget one of them. `Timeline` and `NodeGraph` have an `OnChanged` of this kind, once per gesture.
117
+
118
+ Inputs are controlled, as in React DOM. With `Text={value}`, what the state says stays on screen if a handler refuses a change. An update made in an event is committed before the next event, as a click in React DOM. `ListView` and `ComboBox` keep their `Selected` when their `Items` change. A `|` in an item would split it, as `Items` is written that way in XML: for such items use `ItemText`. `FileBrowser`'s `Directory` and `Timeline`'s `Playing` are not held this way: the user changes them with no event that says so.
119
+
120
+ ## Windows
121
+
122
+ `<Window>` is an element like any other, anywhere in the tree: it opens with its element and closes with it. Without options, `runApp` lets the tree say what windows there are, and ends when the last one closes.
123
+
124
+ ```tsx
125
+ function App() {
126
+ const [help, setHelp] = useState(false);
127
+ return (
128
+ <>
129
+ <Window Title="Main" Width={640} Height={480}>
130
+ <Button OnClicked={() => setHelp(true)}>Help</Button>
131
+ </Window>
132
+ {help && <Window Title="Help" OnClose={() => setHelp(false)}><Label>...</Label></Window>}
133
+ </>
134
+ );
135
+ }
136
+
137
+ await runApp(<App />);
138
+ ```
139
+
140
+ A tree that never opens a window never ends: Raven runs until a window has existed, so that one can open later. An application that renders nothing at all waits for ever, and wakes about 60 times a second.
141
+
142
+ A splash screen is a window like the others, with `IsSplashScreen`: no frame, centered, above the other windows and out of the taskbar. Render the main window beside it and take the splash out when the main window is ready.
143
+
144
+ ```tsx
145
+ {loading && <Window IsSplashScreen Width={480} Height={280}><Label>Starting...</Label></Window>}
146
+ <Window Title="Main" Width={1024} Height={720}>...</Window>
147
+ ```
148
+
149
+ With `OnClose`, closing is a request: the window stays until the element is taken out. Without it, the user can close the window at any time. `Title`, `Theme`, `Width`, `Height`, `Backdrop`, `Opacity` and `ContinuousRedraw` can change on an open window. Other attributes only count when it is made.
150
+
151
+ What a `<Window>` holds directly fills it and follows its `Slot`, as the content of a window in C. Several such children lie on top of each other: put them in a `<VBox>` to stack them.
152
+
153
+ On macOS a `MenuBar` goes to the menu bar of the system; `Native={false}` keeps it in the window. An `<Island>` directly in a `<Window>` floats above the content at the top, as in XML. A `<Menu>` that no `MenuBar` or `Menu` holds is not shown until it is asked: its ref has `open(x, y)`, in the coordinates `OnContext` gives. A `<Separator>` in a `<Menu>` is a line of the menu.
154
+
155
+ ## Themes
156
+
157
+ A theme is a typed object, not a theme file: `defineTheme` checks it, and `loadTheme` gives it to Raven, which keeps it under its name. The colors and metrics take the names of Raven's schema, so a typo does not compile.
158
+
159
+ ```tsx
160
+ import { defineTheme, loadTheme, raven } from "@decky.fx/raven-tsx/react";
161
+
162
+ export const ocean = defineTheme({
163
+ Name: "Ocean",
164
+ Base: "Dark",
165
+ Accent: "#22D3EE",
166
+ Colors: { Window: "#0B1220", Panel: "#111A2C", Text: "#DCE7F5" },
167
+ Metrics: { ControlHeight: 32, Spacing: 8 },
168
+ Syntax: { Keyword: "#7DD3FC", String: "#86EFAC" },
169
+ Styles: [
170
+ { Widget: "Button", CornerRadius: 16 },
171
+ { Class: "danger", Accent: "#FB7185" },
172
+ { Widget: "Slider", Part: "Knob", State: "Hovered", Size: 16 },
173
+ ],
174
+ });
175
+
176
+ await runApp(<App />, options, { setup: () => raven.Rtheme_use(loadTheme(ocean)) });
177
+ ```
178
+
179
+ `Theme={ocean.Name}` gives it to a part of the window only. Everything a theme file has is there: `Look` or `Base`, `Motion`, `Material`, `Font`, `Wallpaper`, `Colors`, `Metrics`, `Glass`, `Syntax`, and `Styles` for a type, a class, a part and its states. A mistake Raven finds (a `Base` that names no theme) is reported by Raven, and the rest is used, as in a file. `themeXml(theme)` gives the text of the theme file, to save one.
180
+
181
+ ## Tags of your own
182
+
183
+ A component can be a tag Raven knows, built by Raven from XML as `<Button>` is: in a document made by code, in `Markup`, in a file of someone else. There is no XML declaration of it: the component is the only one.
184
+
185
+ ```tsx
186
+ await runApp(<App />, options, {
187
+ // (once Raven started: Raven refuses a tag before)
188
+ setup: () => {
189
+ registerTag("Card", Card, {
190
+ attributes: { Title: "string", Wide: "boolean", Count: "number" },
191
+ slots: ["footer"],
192
+ });
193
+ registerTag("Stat", Stat, { attributes: { Label: "string", Value: "string" } });
194
+ },
195
+ });
196
+ ```
197
+
198
+ ```xml
199
+ <Card Title="Sales" Wide="true">
200
+ <Stat Label="This week" Value="1 284"/>
201
+ <Into Name="footer"><Button Text="Details"/></Into>
202
+ </Card>
203
+ ```
204
+
205
+ `attributes` lists the attributes it takes, with the kind of their prop, checked against the component's props; the others stay with the widget (`Name`, `Width`, `Slot`...), and a value of the wrong kind is reported by Raven. The widgets of the children go where the component renders `children`, and those of `<Into Name="...">` where it renders the prop of that name, one of `slots`, with no box around them. Those it does not render are not shown, and stay alive until the element is destroyed. `Rwidget_set_attribute` renders it again, and destroying the widget unmounts it. Each element is a root of its own, so context does not reach it from around it. `registerTag` returns what unregisters the tag.
206
+
207
+ ## Going beyond props
208
+
209
+ `useShortcut(window, "Ctrl+S", save)` adds a shortcut to a window, given the ref of its `<Window>`. `useAction("scene.play", play)` names a function, for whatever fires actions by name; it receives the widget that fired it. An `On...` prop also takes such a name, or a built-in action: `OnClicked="app.quit"`.
210
+
211
+ `useFrame((seconds) => ...)` runs a function once per frame, before the layout, in any number of components. It also runs when nothing is happening, about 60 times a second: `runApp` wakes that often so that Bun's timers run beside Raven. `raven.Rset_frame_callback` goes in the same slot as one more function, so neither takes the place of the other, and the last one it was given is the only one kept alive.
212
+
213
+ `runApp(<App />, options, { appName, setup, allocator })`: `setup` runs after Raven starts and before the first render, for a look or a theme. `allocator` is what `Rset_allocator` takes, functions of C only.
214
+
215
+ `Theme` takes the name of a theme loaded with `loadTheme` (or `Rtheme_load`), and `Language` a language that was registered, beside those the types propose. A color or a metric goes back to the theme with `null`, without making the widget again: `Color={{ Text: null }}`. `FontStyle` on a `Label` takes a style of the font of the theme, and does not go with `Font`.
216
+
217
+ A function given directly to the C API runs outside what props get. `inEventScope(fn)` makes it the same: an exception stays on this side, and what it changes in React is committed before the next event.
218
+
219
+ `createPortal(children, widget)`, as in ReactDOM, renders into a widget given to Raven by hand, the content of a graph node for one, and context goes on through it. `tag<Props>("Name")` makes a component for a tag given to `Rxml_register_tag`.
220
+
221
+ Children added with `Rwidget_add_child` to a parent React also fills stay in their place, through inserts and when the parent is made again. They are yours to delete.
222
+
223
+ Web pages need `registerWebView` once, as the `setup` of `runApp`, which runs before the first render. They are apart from the binding, as a library can be built without them, and `webFunctions()` gives the functions of `raven_web.h`.
224
+
225
+ A `WebView` drives a browser of the Chromium family installed on the machine (Chrome, Chromium, Edge...), hidden, and shows its picture: there is none in the package. So show something else when there is no browser (`Rweb_is_available()` is 0), and when the library has no web pages: built with `-DRAVEN_BUILD_WEB=OFF`, `registerWebView` and `webFunctions()` throw a `RavenError`.
226
+
227
+ ```tsx
228
+ /** Whether web pages can be shown, known once `setup` ran. */
229
+ let pages = false;
230
+
231
+ function App(): ReactNode {
232
+ return pages ? <WebView Url="https://example.com" Slot={{ Fill: 1 }} /> : <Label>No browser to show the page in</Label>;
233
+ }
234
+
235
+ await runApp(<App />, { Width: 800, Height: 600 }, {
236
+ setup: () => {
237
+ try {
238
+ registerWebView();
239
+ pages = webFunctions().Rweb_is_available() !== 0;
240
+ } catch {
241
+ // (a library without web pages)
242
+ }
243
+ },
244
+ });
245
+ ```
246
+
247
+ The data of a few widgets is given as props. Rows of a `TreeView` have new handles each time `Items` changes. `Markup` holds the elements XML takes inside a widget, and a change to it makes the widget again:
248
+
249
+ ```tsx
250
+ <TreeView Items={[{ Text: "World", Expanded: true, Children: [{ Text: "Sky" }] }]} />
251
+ <Timeline Markup={`<Track Name="Camera"><Clip Start="0" Length="3"/></Track>`} />
252
+ <Image Texture={raven.Rtexture_create_from_pixels(pixels, width, height)} />
253
+ ```
254
+
255
+ A `Plot` takes its curves in `Series` and the lines the user drags in `DragLines`. The numbers are lists or `Float64Array`s, handed to Raven as they are: a new render sends only what changed, and a new array is what says that values changed. Streaming a million points with `Rplot_add_point` works too: leave `Y` out of that series.
256
+
257
+ ```tsx
258
+ <Plot
259
+ Title="Signal" XLabel="t (s)" Legend="TopLeft" YScale="SymLog"
260
+ Series={[{ Name: "wave", X: times, Y: values, Marker: "Circle" }, { Name: "mean", Y: means, Color: "#E5484D", Line: "Dashed" }]}
261
+ DragLines={[{ Axis: "Y", Value: limit, Label: "limit" }]}
262
+ OnChanged={(_line, value) => setLimit(value)}
263
+ OnViewChanged={(view) => console.log(view.X)}
264
+ />
265
+ ```
266
+
267
+ A `ref` gives a `WidgetInstance`, and `raven` is all of Raven's C API. Read `.widget` when you need it: the handle changes if the widget is made again.
268
+
269
+ ```tsx
270
+ const input = useRef<WidgetInstance>(null);
271
+ useEffect(() => input.current?.focus(), []);
272
+ <TextInput ref={input} />
273
+
274
+ // a TreeView is filled this way
275
+ raven.Rtree_view_add_item(tree.current.widget, raven.R_INVALID_ID, "World");
276
+ ```
277
+
278
+ A `Custom` draws with the `Rdraw_` functions. `OnPaint` runs while a frame is drawn, so it must not change state.
279
+
280
+ ```tsx
281
+ <Custom
282
+ OnMeasure={() => ({ Width: 400, Height: 200 })}
283
+ OnPaint={(painter, width, height) => raven.Rdraw_rect(painter, 0, 0, width, height, 8, 0.2, 0.3, 0.4, 1)}
284
+ />
285
+ ```
286
+
287
+ Long lists ask for their rows as they are shown, and a million rows cost nothing:
288
+
289
+ ```tsx
290
+ <ListView ItemCount={1_000_000} ItemText={(index) => `Row ${index}`} />
291
+ <TileView ItemCount={200} ItemFill={(index, item) => { item.text = names[index]; }} />
292
+ <Table Columns={["Name", "Age"]} RowCount={people.length} CellText={(row, column) => cell(people[row], column)} />
293
+ ```
294
+
295
+ A `NodeGraph` and a `Timeline` take one of Raven's looks, `Preset` "Auto" (the theme), "Modern", "Blueprint", "Blender" or "Neon", and their parts can be changed one by one: `LinkShape`, `PinShape`, `HeaderStyle`, `ClipStyle`, `KeyShape`, `PlayheadColor`... One type of pin, one node or one track takes its own with `Rnode_graph_set_type_style`, `Rnode_graph_set_node_style` and `Rtimeline_set_track_style`.
296
+
297
+ ```tsx
298
+ <NodeGraph Preset="Blueprint" LinkShape="Step" />
299
+ <Timeline Preset={look} ClipStyle="Outline" />
300
+ ```
301
+
302
+ Some things are done through a `ref`, as they are actions and not state. A `Toast` shows one message at a time; with `Queue="Sequence"` the next one waits its turn, with `Queue="Stack"` up to `MaxVisible` show at once.
303
+
304
+ ```tsx
305
+ raven.Rtoast_show(toast.current.widget, "Saved", 3);
306
+ raven.Rnotification_show("Backup", "It is finished", raven.R_NOTIFICATION_NORMAL);
307
+ ```
308
+
309
+ What belongs to the system and not to a window has no component: an icon in the tray (`Rtray_create`, `Rtray_set_menu`, `Rtray_set_clicked_callback`), the badge, progress and menu of the taskbar or the Dock (`Rtaskbar_set_badge`, `Rwindow_set_taskbar_progress`), picture in picture (`Rwindow_set_picture_in_picture`), and the inspector of a window, which lists its widgets and picks one with a click (`Rwindow_set_inspector`). Call them from an effect, and undo them in its cleanup.
310
+
311
+ An application with a tray icon lives on when its last window closes, as a tray application should: `runApp` returns only after `raven.Rquit(0)`, or the action `app.quit`. Give the icon a way to end it, a "Quit" item of its menu for one; the cleanup that destroys the icon still runs then. Make it only where the system shows it, `Rtray_is_supported()`: on Linux an icon is made even when no panel runs to show it, and the application would live on with nothing to click. Without one, closing the last window ends it, as usual.
312
+
313
+ ```tsx
314
+ useEffect(() => {
315
+ // (on Linux, a panel that shows tray icons must be running)
316
+ const tray = raven.Rtray_is_supported() ? raven.Rtray_create() : raven.R_INVALID_ID;
317
+ if (tray === raven.R_INVALID_ID) {
318
+ return; // (no tray: the application ends with its last window)
319
+ }
320
+ // Its picture, as RGBA pixels: without one, some panels show nothing to click.
321
+ // An orange square here; in practice, the icon of the application.
322
+ const pixels = new Uint8Array(32 * 32 * 4);
323
+ for (let index = 0; index < pixels.length; index += 4) {
324
+ pixels.set([0xe8, 0x59, 0x0c, 0xff], index);
325
+ }
326
+ raven.Rtray_set_icon(tray, pixels, 32, 32);
327
+ raven.Rtray_set_tooltip(tray, "Backup");
328
+ raven.Rtray_set_clicked_callback(tray, () => raven.Rquit(0));
329
+ return () => raven.Rtray_destroy(tray);
330
+ }, []);
331
+ ```
332
+
333
+ An `Image` shows an SVG file like any other: `<Image Source="logo.svg" />`.
334
+
335
+ ## What to expect
336
+
337
+ - Removing a prop makes the widget again, in its place, with the same children: Raven has no way to give an attribute its default back. What the widget held by itself (focus, typed text, scrolling) is lost. To avoid that, keep the prop and change its value.
338
+ - The designer of Raven does not edit TSX.
339
+ - Style parts and states (`Part`, `State`) only exist in themes: `Styles` of `defineTheme`, not props.
340
+
341
+ ## Working on this package
342
+
343
+ The repository is a Bun workspace, with this package in `packages/raven-tsx` and the examples in `examples`: `helloworld` tries everything, the others are the examples of Raven in TSX. It expects a Raven checkout two folders up, at `../../raven` from the root:
344
+
345
+ raven/ github.com/oscar-soirey/Raven
346
+ bun/raven-tsx/ this repository
347
+
348
+ Check out the commit of Raven this package is pinned to, build it, then make the native package of your platform from it (`native:local` refuses a library built from any other commit):
349
+
350
+ git -C raven checkout "$(cat bun/raven-tsx/packages/raven-tsx/RAVEN_VERSION)"
351
+ cmake -S raven -B raven/build-shared -DCMAKE_BUILD_TYPE=Release -DRAVEN_BUILD_SHARED=ON
352
+ cmake --build raven/build-shared --parallel
353
+ cd bun/raven-tsx/packages/raven-tsx
354
+ bun run native:local
355
+ bun install
356
+ bun test
357
+
358
+ Then, in `examples/helloworld`: `bun run dev`, or `bun run build`. Every other example runs with `bun run dev` in its folder.
359
+
360
+ | Task | Command |
361
+ |---|---|
362
+ | Regenerate `src/core/props.generated.ts` and `src/react/components.generated.ts` | `bun run generate` |
363
+ | Cross-build `raven.dll` on Linux with MinGW-w64, then `bun install` | `bun run native:windows` |
364
+ | Compile a Windows `.exe` of the example, after that | `bun run build:windows` in `examples/helloworld` |
365
+ | Where things stand, and what changed in Raven's API | `bun run raven:status [ref]` |
366
+ | Check that everything agrees with the pin | `bun run raven:check` |
367
+ | Move to another Raven, or to the latest | `bun run raven:update [ref]` |
368
+
369
+ `RAVEN_VERSION` says which Raven this is, a full commit or a tag, and nothing else does. `raven:update` is the way to change it: it moves the checkout, builds the libraries, packages one, and only then takes the binding, writes the pin and generates the components, since a newer library works with an older binding and the opposite stops every application at startup. It stops at the first step that fails, and can be run again.
370
+
371
+ Nothing in a library says which commit it is: `Rget_version` is the same for every commit between two releases. So `native:local` asks CMake which checkout the build folder belongs to, refuses it unless that checkout is at the pin with no changes, brings the build up to date, and writes `raven.json` beside the library. `--allow-mismatch` packages a library to try a change, and `raven:check` then says so. An application started with a library that lacks what the binding needs says so too, rather than naming one symbol.
372
+
373
+ `src/test/core/coverage.test.ts` fails when Raven adds a tag, an attribute or a callback nobody has decided about: wire it, or say there why not.
374
+
375
+ ### Drift
376
+
377
+ `drift.txt` records the git blob of each Raven file that raven-tsx copies or mirrors: the examples, `src/widgets`, `src/widget` and `src/xml`. It catches what an API diff cannot, such as a widget that behaves differently or an example that gained a feature.
378
+
379
+ | Task | Command |
380
+ |---|---|
381
+ | What differs from `drift.txt` at the pin | `bun run raven:drift` |
382
+ | Preview another commit before moving to it | `bun run raven:drift upstream/main` |
383
+ | The same, failing if anything differs | `bun run raven:drift --check` |
384
+ | Record a file or folder as checked, at the pin | `bun run raven:drift --accept examples/music` |
385
+ | Record everything as checked, at the pin | `bun run raven:drift --accept-all` |
386
+
387
+ `raven:status` and `raven:check` include it. A changed file is listed as `old..new`, and `git diff old new` in the Raven checkout shows the change. Accept a file only after you have looked at it: the diff of `drift.txt` in a commit is the record of what was reviewed.
388
+
389
+ Every push builds Raven at `RAVEN_VERSION` on each platform, and runs `raven:check` and the tests there.
390
+
391
+ Publishing is off until the repository variable `NPM_PUBLISH` is `true`; until then the workflow rehearses it with `npm pack --dry-run`. A new version is: set `version` in `package.json`, and push the tag `v<version>`. The packages of the platforms are published first, then this one.
392
+
393
+ npm can only trust a workflow for a package that exists, so the first version needs the secret `NPM_TOKEN`: a granular token that can write to the scope, with bypass 2FA on, as nobody is there to answer a prompt. Delete the secret after that release.
394
+
395
+ Later versions publish through OIDC, with provenance. Just before pushing the next tag, give each of the 6 packages a trusted publisher on npm (repository `deckyfx/raven-tsx`, workflow `release.yml`, environment `npm`, with `npm publish` allowed: by default it may only stage). Limit the GitHub environment `npm` to `v*` tags, so that a run of a branch cannot publish. A new publisher expires unless it publishes within 2 days; if that happens, delete it and make another.