react-native-gtkx 0.1.0-alpha.4 → 0.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -9
- package/dist/adw/index.d.ts +3 -0
- package/dist/adw/index.js +41 -0
- package/dist/adw/index.js.map +1 -0
- package/dist/adw/widgets.generated.d.ts +63 -0
- package/dist/adw/widgets.generated.js +125 -0
- package/dist/adw/widgets.generated.js.map +1 -0
- package/dist/animated/create-animated.d.ts +2 -0
- package/dist/animated/create-animated.js +2 -0
- package/dist/animated/create-animated.js.map +1 -1
- package/dist/animated/index.d.ts +2 -0
- package/dist/animated/index.js +1 -0
- package/dist/animated/index.js.map +1 -1
- package/dist/animated/value-xy.d.ts +41 -0
- package/dist/animated/value-xy.js +90 -0
- package/dist/animated/value-xy.js.map +1 -0
- package/dist/animated/value.d.ts +9 -0
- package/dist/animated/value.js +30 -2
- package/dist/animated/value.js.map +1 -1
- package/dist/common/content.d.ts +69 -0
- package/dist/common/content.js +84 -0
- package/dist/common/content.js.map +1 -0
- package/dist/common/icon.d.ts +23 -0
- package/dist/common/icon.js +14 -0
- package/dist/common/icon.js.map +1 -0
- package/dist/common/index.d.ts +5 -0
- package/dist/common/index.js +21 -0
- package/dist/common/index.js.map +1 -0
- package/dist/common/list.d.ts +82 -0
- package/dist/common/list.js +166 -0
- package/dist/common/list.js.map +1 -0
- package/dist/common/navigation-stack.d.ts +84 -0
- package/dist/common/navigation-stack.js +313 -0
- package/dist/common/navigation-stack.js.map +1 -0
- package/dist/common/widget.d.ts +62 -0
- package/dist/common/widget.js +209 -0
- package/dist/common/widget.js.map +1 -0
- package/dist/components/animated.d.ts +7 -4
- package/dist/components/animated.js +88 -67
- package/dist/components/animated.js.map +1 -1
- package/dist/components/app-registry.d.ts +36 -1
- package/dist/components/app-registry.js +8 -3
- package/dist/components/app-registry.js.map +1 -1
- package/dist/components/host-node.d.ts +7 -0
- package/dist/components/host-node.js +26 -1
- package/dist/components/host-node.js.map +1 -1
- package/dist/components/index.d.ts +6 -2
- package/dist/components/index.js +2 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/measure.d.ts +13 -0
- package/dist/components/measure.js +51 -0
- package/dist/components/measure.js.map +1 -0
- package/dist/components/modal.d.ts +1 -1
- package/dist/components/press-event.d.ts +28 -0
- package/dist/components/press-event.js +46 -0
- package/dist/components/press-event.js.map +1 -0
- package/dist/components/pressable.d.ts +2 -6
- package/dist/components/pressable.js +119 -20
- package/dist/components/pressable.js.map +1 -1
- package/dist/components/rect-store.d.ts +11 -1
- package/dist/components/rect-store.js +27 -3
- package/dist/components/rect-store.js.map +1 -1
- package/dist/components/scroll-view.d.ts +4 -0
- package/dist/components/scroll-view.js +8 -2
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/svg/animated-support.d.ts +19 -0
- package/dist/components/svg/animated-support.js +64 -0
- package/dist/components/svg/animated-support.js.map +1 -0
- package/dist/components/svg/context.d.ts +8 -0
- package/dist/components/svg/context.js +15 -0
- package/dist/components/svg/context.js.map +1 -0
- package/dist/components/svg/gradients.d.ts +31 -0
- package/dist/components/svg/gradients.js +93 -0
- package/dist/components/svg/gradients.js.map +1 -0
- package/dist/components/svg/group.d.ts +8 -0
- package/dist/components/svg/group.js +30 -0
- package/dist/components/svg/group.js.map +1 -0
- package/dist/components/svg/index.d.ts +6 -0
- package/dist/components/svg/index.js +5 -0
- package/dist/components/svg/index.js.map +1 -0
- package/dist/components/svg/node.d.ts +7 -0
- package/dist/components/svg/node.js +7 -0
- package/dist/components/svg/node.js.map +1 -0
- package/dist/components/svg/paint.d.ts +24 -0
- package/dist/components/svg/paint.js +57 -0
- package/dist/components/svg/paint.js.map +1 -0
- package/dist/components/svg/shapes.d.ts +47 -0
- package/dist/components/svg/shapes.js +51 -0
- package/dist/components/svg/shapes.js.map +1 -0
- package/dist/components/svg/svg.d.ts +14 -0
- package/dist/components/svg/svg.js +48 -0
- package/dist/components/svg/svg.js.map +1 -0
- package/dist/components/svg/use-shape-node.d.ts +12 -0
- package/dist/components/svg/use-shape-node.js +52 -0
- package/dist/components/svg/use-shape-node.js.map +1 -0
- package/dist/components/text-input.js +1 -1
- package/dist/components/text-input.js.map +1 -1
- package/dist/components/text.js +36 -3
- package/dist/components/text.js.map +1 -1
- package/dist/components/use-layout-child.js +50 -4
- package/dist/components/use-layout-child.js.map +1 -1
- package/dist/components/view.d.ts +9 -4
- package/dist/components/view.js +8 -2
- package/dist/components/view.js.map +1 -1
- package/dist/contracts.d.ts +31 -4
- package/dist/contracts.js.map +1 -1
- package/dist/gtk/index.d.ts +7 -0
- package/dist/gtk/index.js +46 -0
- package/dist/gtk/index.js.map +1 -0
- package/dist/gtk/widgets.generated.d.ts +103 -0
- package/dist/gtk/widgets.generated.js +205 -0
- package/dist/gtk/widgets.generated.js.map +1 -0
- package/dist/gtk/window-actions.d.ts +77 -0
- package/dist/gtk/window-actions.js +193 -0
- package/dist/gtk/window-actions.js.map +1 -0
- package/dist/gtkx/bridge/geometry.d.ts +15 -0
- package/dist/gtkx/bridge/geometry.js +38 -0
- package/dist/gtkx/bridge/geometry.js.map +1 -0
- package/dist/gtkx/bridge/index.d.ts +11 -4
- package/dist/gtkx/bridge/index.js +47 -6
- package/dist/gtkx/bridge/index.js.map +1 -1
- package/dist/gtkx/bridge/layout-manager.d.ts +2 -1
- package/dist/gtkx/bridge/layout-manager.js +31 -9
- package/dist/gtkx/bridge/layout-manager.js.map +1 -1
- package/dist/gtkx/bridge/slot-portal.d.ts +12 -0
- package/dist/gtkx/bridge/slot-portal.js +17 -0
- package/dist/gtkx/bridge/slot-portal.js.map +1 -0
- package/dist/gtkx/bridge/svg-node.d.ts +79 -0
- package/dist/gtkx/bridge/svg-node.js +323 -0
- package/dist/gtkx/bridge/svg-node.js.map +1 -0
- package/dist/gtkx/bridge/use-signal.js +19 -9
- package/dist/gtkx/bridge/use-signal.js.map +1 -1
- package/dist/gtkx/bridge/widgets.generated.d.ts +2 -0
- package/dist/gtkx/bridge/widgets.generated.js +14 -0
- package/dist/gtkx/bridge/widgets.generated.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/bin.d.ts +2 -0
- package/dist/mcp/bin.js +17 -0
- package/dist/mcp/bin.js.map +1 -0
- package/dist/mcp/data/generated.d.ts +1304 -0
- package/dist/mcp/data/generated.js +1179 -0
- package/dist/mcp/data/generated.js.map +1 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/list.d.ts +4 -0
- package/dist/mcp/list.js +56 -0
- package/dist/mcp/list.js.map +1 -0
- package/dist/mcp/resolve.d.ts +55 -0
- package/dist/mcp/resolve.js +151 -0
- package/dist/mcp/resolve.js.map +1 -0
- package/dist/mcp/search.d.ts +13 -0
- package/dist/mcp/search.js +48 -0
- package/dist/mcp/search.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +135 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tool.d.ts +21 -0
- package/dist/mcp/tool.js +36 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/metro/index.js +10 -0
- package/dist/metro/index.js.map +1 -1
- package/dist/navigation/index.d.ts +50 -13
- package/dist/navigation/index.js +183 -148
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/option-warnings.js +9 -1
- package/dist/navigation/option-warnings.js.map +1 -1
- package/dist/navigation/sidebar.d.ts +207 -9
- package/dist/navigation/sidebar.js +294 -10
- package/dist/navigation/sidebar.js.map +1 -1
- package/dist/responder/system.d.ts +27 -0
- package/dist/responder/system.js +184 -0
- package/dist/responder/system.js.map +1 -0
- package/dist/responder/touch-history.d.ts +10 -0
- package/dist/responder/touch-history.js +78 -0
- package/dist/responder/touch-history.js.map +1 -0
- package/dist/responder/types.d.ts +63 -0
- package/dist/responder/types.js +29 -0
- package/dist/responder/types.js.map +1 -0
- package/dist/responder/use-responder.d.ts +12 -0
- package/dist/responder/use-responder.js +98 -0
- package/dist/responder/use-responder.js.map +1 -0
- package/dist/runner/host-dev.js +3 -1
- package/dist/runner/host-dev.js.map +1 -1
- package/dist/runner/host.js +5 -1
- package/dist/runner/host.js.map +1 -1
- package/dist/runner/index.d.ts +22 -2
- package/dist/runner/index.js +163 -13
- package/dist/runner/index.js.map +1 -1
- package/dist/sea/assemble.d.ts +29 -0
- package/dist/sea/assemble.js +129 -0
- package/dist/sea/assemble.js.map +1 -0
- package/dist/sea/bundle.d.ts +61 -0
- package/dist/sea/bundle.js +202 -0
- package/dist/sea/bundle.js.map +1 -0
- package/dist/sea/gtkx-config-module.d.ts +18 -0
- package/dist/sea/gtkx-config-module.js +67 -0
- package/dist/sea/gtkx-config-module.js.map +1 -0
- package/dist/sea/native-shim.d.ts +40 -0
- package/dist/sea/native-shim.js +121 -0
- package/dist/sea/native-shim.js.map +1 -0
- package/dist/sea/react-anchor-plugin.d.ts +2 -0
- package/dist/sea/react-anchor-plugin.js +30 -0
- package/dist/sea/react-anchor-plugin.js.map +1 -0
- package/dist/style/box-shadow.d.ts +7 -0
- package/dist/style/box-shadow.js +197 -0
- package/dist/style/box-shadow.js.map +1 -0
- package/dist/style/index.d.ts +3 -0
- package/dist/style/index.js +3 -0
- package/dist/style/index.js.map +1 -1
- package/dist/style/split-style.d.ts +5 -2
- package/dist/style/split-style.js +33 -5
- package/dist/style/split-style.js.map +1 -1
- package/dist/style/text-decoration.d.ts +15 -0
- package/dist/style/text-decoration.js +26 -0
- package/dist/style/text-decoration.js.map +1 -0
- package/dist/style/transform.d.ts +22 -0
- package/dist/style/transform.js +137 -0
- package/dist/style/transform.js.map +1 -0
- package/dist/style/visual-css.js +40 -3
- package/dist/style/visual-css.js.map +1 -1
- package/dist/svg/geometry.d.ts +19 -0
- package/dist/svg/geometry.js +80 -0
- package/dist/svg/geometry.js.map +1 -0
- package/dist/svg/gradient-geometry.d.ts +15 -0
- package/dist/svg/gradient-geometry.js +15 -0
- package/dist/svg/gradient-geometry.js.map +1 -0
- package/dist/svg/transform.d.ts +22 -0
- package/dist/svg/transform.js +74 -0
- package/dist/svg/transform.js.map +1 -0
- package/dist/svg/view-box.d.ts +34 -0
- package/dist/svg/view-box.js +108 -0
- package/dist/svg/view-box.js.map +1 -0
- package/dist/svg-compat/index.d.ts +1 -0
- package/dist/svg-compat/index.js +16 -0
- package/dist/svg-compat/index.js.map +1 -0
- package/dist/testing/index.d.ts +14 -0
- package/dist/testing/index.js +14 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/vendor/react-native/pan-responder.d.ts +102 -0
- package/dist/vendor/react-native/pan-responder.js +196 -0
- package/dist/vendor/react-native/pan-responder.js.map +1 -0
- package/dist/vendor/react-native/touch-history-math.d.ts +18 -0
- package/dist/vendor/react-native/touch-history-math.js +102 -0
- package/dist/vendor/react-native/touch-history-math.js.map +1 -0
- package/dist/vite/index.d.ts +7 -3
- package/dist/vite/index.js +16 -4
- package/dist/vite/index.js.map +1 -1
- package/dist/vitest/index.d.ts +52 -0
- package/dist/vitest/index.js +58 -0
- package/dist/vitest/index.js.map +1 -0
- package/dist/vitest/setup.d.ts +4 -0
- package/dist/vitest/setup.js +22 -0
- package/dist/vitest/setup.js.map +1 -0
- package/package.json +52 -13
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Making a raw GTK widget a first-class citizen of React Native layout.
|
|
3
|
+
//
|
|
4
|
+
// The widgets this subpath re-exports are real GTK widgets, and GTK widgets
|
|
5
|
+
// know nothing about Yoga. Dropped into a <View> as-is they would never be
|
|
6
|
+
// measured or positioned, which would make "we export everything" a hollow
|
|
7
|
+
// promise. `Widget` closes that gap: it gives the widget a Yoga node, applies
|
|
8
|
+
// the layout half of a React Native style to it, and — this is the part that
|
|
9
|
+
// matters — measures the widget itself, so it lands at the size the GTK theme
|
|
10
|
+
// actually wants instead of collapsing to zero.
|
|
11
|
+
//
|
|
12
|
+
// The visual half of the style (background, border, radius) is applied as a
|
|
13
|
+
// GTK CSS class, the same mechanism View uses.
|
|
14
|
+
import { isValidElement, useContext, useRef, } from "react";
|
|
15
|
+
import { HostNodeContext, SlotContext, } from "../components/host-node";
|
|
16
|
+
import { useLayoutChild, } from "../components/use-layout-child";
|
|
17
|
+
import { GtkBox } from "../gtkx/bridge/index";
|
|
18
|
+
/**
|
|
19
|
+
* Put any GTK widget into React Native layout.
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <View style={{ flexDirection: "row", gap: 8, padding: 12 }}>
|
|
23
|
+
* <Widget style={{ flex: 1 }}>
|
|
24
|
+
* <GtkEntry placeholderText="Search" />
|
|
25
|
+
* </Widget>
|
|
26
|
+
* <Widget>
|
|
27
|
+
* <GtkButton iconName="edit-find-symbolic" />
|
|
28
|
+
* </Widget>
|
|
29
|
+
* </View>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* The entry flexes, the button takes its natural size, both sit in the row
|
|
33
|
+
* exactly like React Native children — because to Yoga they now are.
|
|
34
|
+
*
|
|
35
|
+
* For the reverse direction (React Native content inside a GTK slot) see
|
|
36
|
+
* `SlotContent` and `IntrinsicContent`.
|
|
37
|
+
*/
|
|
38
|
+
export const Widget = ({ style, onLayout, children, testID }) => {
|
|
39
|
+
// The wrapper box IS the Yoga leaf, and it is measured through GTK, so the
|
|
40
|
+
// widget inside reports its natural size up to Yoga. Wrapping rather than
|
|
41
|
+
// cloning keeps the child's own ref and props untouched.
|
|
42
|
+
const boxRef = useRef(null);
|
|
43
|
+
const { cssClass } = useLayoutChild(boxRef, {
|
|
44
|
+
style,
|
|
45
|
+
onLayout,
|
|
46
|
+
// Without this a widget with no explicit size would measure as zero —
|
|
47
|
+
// the whole reason a naked GtkButton in a View looks broken.
|
|
48
|
+
measureFromWidget: true,
|
|
49
|
+
});
|
|
50
|
+
return (_jsx(GtkBox, { ref: boxRef, name: testID, cssClasses: cssClass ? [cssClass] : undefined, children: children }));
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The same thing as {@link Widget}, without the extra component — attach the
|
|
54
|
+
* ref yourself when you already hold one.
|
|
55
|
+
*
|
|
56
|
+
* ```tsx
|
|
57
|
+
* const ref = useRef<Gtk.Button | null>(null)
|
|
58
|
+
* useWidgetLayout(ref, { style: { width: 40, height: 40 } })
|
|
59
|
+
* return <GtkButton ref={ref} iconName="open-menu-symbolic" />
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* Returns the GTK CSS class produced by the visual half of the style, or
|
|
63
|
+
* null — pass it to the widget's `cssClasses` if you want backgrounds and
|
|
64
|
+
* borders from the style to apply.
|
|
65
|
+
*/
|
|
66
|
+
export const useWidgetLayout = (ref, options = {}) => {
|
|
67
|
+
const { cssClass } = useLayoutChild(ref, {
|
|
68
|
+
style: options.style,
|
|
69
|
+
onLayout: options.onLayout,
|
|
70
|
+
measureFromWidget: true,
|
|
71
|
+
});
|
|
72
|
+
return cssClass;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Give a GTK component React Native layout, keeping every prop it already
|
|
76
|
+
* has.
|
|
77
|
+
*
|
|
78
|
+
* ```tsx
|
|
79
|
+
* const Button = wrapReactNative(GtkButton)
|
|
80
|
+
*
|
|
81
|
+
* <View style={{ flexDirection: "row", gap: 8 }}>
|
|
82
|
+
* <Button style={{ flex: 1 }} label="Save" onClicked={save} />
|
|
83
|
+
* </View>
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* The result is generic in the wrapped component's props, so `label`,
|
|
87
|
+
* `onClicked` and everything else gtkx binds keep their types; `style` and
|
|
88
|
+
* `onLayout` are added on top.
|
|
89
|
+
*
|
|
90
|
+
* **Outside React Native layout it steps aside.** The same widget is often
|
|
91
|
+
* used in a pure GTK slot — a HeaderBar's `start`, a ToolbarView's `topBar` —
|
|
92
|
+
* where there is no Yoga tree to join. The wrapper detects that and renders
|
|
93
|
+
* the bare component, so one exported symbol works in both worlds.
|
|
94
|
+
*/
|
|
95
|
+
// A wrapped widget is a Yoga LEAF, so everything INSIDE it is GTK's
|
|
96
|
+
// territory — its children and its slots alike.
|
|
97
|
+
//
|
|
98
|
+
// A widget SLOT is a property that takes a widget (`titleWidget`, `sheet`,
|
|
99
|
+
// `sidebar`, `startChild`); a widget's content area is an ordinary child.
|
|
100
|
+
// The distinction is gtkx's, it moves between releases — rc.3 took the
|
|
101
|
+
// `content`/`child` props off single-child widgets and made that content a
|
|
102
|
+
// child — and it has never had anything to do with layout. Both keep
|
|
103
|
+
// rendering where they were written, so both keep seeing the enclosing React
|
|
104
|
+
// Native layout root even though GTK has parented them somewhere else
|
|
105
|
+
// entirely. Content then joins a Yoga tree whose viewport is the WINDOW
|
|
106
|
+
// while GTK gives it the widget's own rectangle: laid out against one box,
|
|
107
|
+
// drawn in another, and silently stealing space from a tree it was never
|
|
108
|
+
// meant to be in.
|
|
109
|
+
//
|
|
110
|
+
// So the boundary clears the layout root on the way in. The widgets a slot
|
|
111
|
+
// or a child usually holds want exactly that (it is what `WidgetContent`
|
|
112
|
+
// does by hand), and React Native content entering has to bring its own root
|
|
113
|
+
// — `SlotContent` to fill the area, `IntrinsicContent` to size the area to
|
|
114
|
+
// itself. Which of the two is right cannot be inferred, and measurement says
|
|
115
|
+
// so louder than argument: `AdwBottomSheet` alone FILLS in its content child
|
|
116
|
+
// but HUGS in both `sheet` (a bottom sheet rises to the height of its own
|
|
117
|
+
// contents) and `bottomBar`. One widget, three content areas, two answers,
|
|
118
|
+
// with nothing in the name or the GIR type to tell them apart — the answer
|
|
119
|
+
// lives in the widget's own layout code. The boundary is what makes NOT
|
|
120
|
+
// saying a readable error instead of a silent mislayout — see useHostNode.
|
|
121
|
+
const SlotBoundary = ({ location, children, }) => (
|
|
122
|
+
// Two providers, no widget: a context provider is invisible to the gtkx
|
|
123
|
+
// reconciler, so this is safe even on slots whose value is NOT a widget
|
|
124
|
+
// (`breakpoints`, `menuModel`, `buffer`, `adjustment` — the majority of
|
|
125
|
+
// element-valued props, in fact) — wrapping those in a real box would put
|
|
126
|
+
// a GtkBox where an AdwBreakpoint belongs.
|
|
127
|
+
_jsx(SlotContext.Provider, { value: location, children: _jsx(HostNodeContext.Provider, { value: null, children: children }) }));
|
|
128
|
+
const holdsElement = (value) => isValidElement(value) || (Array.isArray(value) && value.some(holdsElement));
|
|
129
|
+
// Puts every element-valued prop, and the children, behind a boundary.
|
|
130
|
+
// `ref` is never content; anything that holds no element (a string child, a
|
|
131
|
+
// number, an already-constructed GObject) is left exactly as it was, so the
|
|
132
|
+
// common widget pays nothing.
|
|
133
|
+
const withSlotBoundaries = (props, widget) => {
|
|
134
|
+
let bounded;
|
|
135
|
+
for (const key in props) {
|
|
136
|
+
if (key === "ref") {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const value = props[key];
|
|
140
|
+
if (!holdsElement(value)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
bounded ??= {};
|
|
144
|
+
bounded[key] = (
|
|
145
|
+
// Children are not a named slot: the error says "inside AdwBottomSheet"
|
|
146
|
+
// rather than naming a property that does not exist.
|
|
147
|
+
_jsx(SlotBoundary, { location: { widget, slot: key === "children" ? null : key }, children: value }));
|
|
148
|
+
}
|
|
149
|
+
// The common case is a widget with no element-valued prop at all: return the
|
|
150
|
+
// props object untouched rather than allocate a copy per render.
|
|
151
|
+
return bounded ? { ...props, ...bounded } : props;
|
|
152
|
+
};
|
|
153
|
+
export const wrapReactNative = (Component,
|
|
154
|
+
// The widget's name, for error messages and React devtools. gtkx builds its
|
|
155
|
+
// components from a factory, so they carry no name of their own — the
|
|
156
|
+
// generated widget surface passes the class name it already knows.
|
|
157
|
+
widgetName) => {
|
|
158
|
+
const name = widgetName ||
|
|
159
|
+
Component.displayName ||
|
|
160
|
+
Component.name ||
|
|
161
|
+
"Widget";
|
|
162
|
+
const InLayout = (props) => {
|
|
163
|
+
const { style, onLayout, ...rest } = props;
|
|
164
|
+
const boxRef = useRef(null);
|
|
165
|
+
const { cssClass } = useLayoutChild(boxRef, {
|
|
166
|
+
style,
|
|
167
|
+
onLayout,
|
|
168
|
+
// Without this a widget with no explicit size measures as zero — the
|
|
169
|
+
// whole reason a naked GtkButton in a View looks broken.
|
|
170
|
+
measureFromWidget: true,
|
|
171
|
+
});
|
|
172
|
+
const inner = rest;
|
|
173
|
+
return (_jsx(GtkBox, { ref: boxRef, children: _jsx(Component, { ...inner,
|
|
174
|
+
// The style's visual half lands on the WIDGET, not on the wrapper,
|
|
175
|
+
// so backgrounds and borders colour the button itself. This is the
|
|
176
|
+
// point: on this platform React Native style drives GTK.
|
|
177
|
+
cssClasses: cssClass
|
|
178
|
+
? [...(inner.cssClasses ?? []), cssClass]
|
|
179
|
+
: inner.cssClasses,
|
|
180
|
+
// Fill the rect Yoga computed, so `flex: 1` and explicit sizes mean
|
|
181
|
+
// what they mean everywhere else in React Native. An explicit
|
|
182
|
+
// hexpand/vexpand from the caller still wins.
|
|
183
|
+
hexpand: inner.hexpand ?? true, vexpand: inner.vexpand ?? true }) }));
|
|
184
|
+
};
|
|
185
|
+
const Wrapped = (props) => {
|
|
186
|
+
// Every slot this widget is given becomes GTK territory, in both branches
|
|
187
|
+
// below: whether the widget itself joined React Native layout has nothing
|
|
188
|
+
// to do with what its slots hold.
|
|
189
|
+
const withSlots = withSlotBoundaries(props, name);
|
|
190
|
+
// Read the context directly rather than through useHostNode, which throws
|
|
191
|
+
// by design: here its absence is a supported case, not a mistake. The
|
|
192
|
+
// same widget is often used in a pure GTK slot (a HeaderBar's `start`, a
|
|
193
|
+
// ToolbarView's `topBar`) where there is no Yoga tree to join.
|
|
194
|
+
const host = useContext(HostNodeContext);
|
|
195
|
+
if (!host) {
|
|
196
|
+
// style/onLayout are meaningless without a Yoga tree — drop them rather
|
|
197
|
+
// than forward them onto a GObject that has no such properties.
|
|
198
|
+
const rest = { ...withSlots };
|
|
199
|
+
delete rest.style;
|
|
200
|
+
delete rest.onLayout;
|
|
201
|
+
return _jsx(Component, { ...rest });
|
|
202
|
+
}
|
|
203
|
+
return _jsx(InLayout, { ...withSlots });
|
|
204
|
+
};
|
|
205
|
+
InLayout.displayName = `wrapReactNative(${name}).InLayout`;
|
|
206
|
+
Wrapped.displayName = `wrapReactNative(${name})`;
|
|
207
|
+
return Wrapped;
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=widget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/common/widget.tsx"],"names":[],"mappings":"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","sourcesContent":["// Making a raw GTK widget a first-class citizen of React Native layout.\n//\n// The widgets this subpath re-exports are real GTK widgets, and GTK widgets\n// know nothing about Yoga. Dropped into a <View> as-is they would never be\n// measured or positioned, which would make \"we export everything\" a hollow\n// promise. `Widget` closes that gap: it gives the widget a Yoga node, applies\n// the layout half of a React Native style to it, and — this is the part that\n// matters — measures the widget itself, so it lands at the size the GTK theme\n// actually wants instead of collapsing to zero.\n//\n// The visual half of the style (background, border, radius) is applied as a\n// GTK CSS class, the same mechanism View uses.\nimport {\n isValidElement,\n useContext,\n useRef,\n type ComponentType,\n type ReactNode,\n} from \"react\"\nimport {\n HostNodeContext,\n SlotContext,\n type SlotLocation,\n} from \"../components/host-node\"\nimport {\n useLayoutChild,\n type LayoutEvent,\n} from \"../components/use-layout-child\"\nimport type { StyleProp } from \"../contracts\"\nimport { GtkBox, type Gtk } from \"../gtkx/bridge/index\"\n\nexport type WidgetProps = {\n /** React Native style. The layout half drives Yoga, the visual half becomes\n * a GTK CSS class. Omit sizing and the widget's own natural size wins. */\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n /** The GTK widget(s) to lay out. Your own `ref` on them is untouched. */\n children: ReactNode\n testID?: string\n}\n\n/**\n * Put any GTK widget into React Native layout.\n *\n * ```tsx\n * <View style={{ flexDirection: \"row\", gap: 8, padding: 12 }}>\n * <Widget style={{ flex: 1 }}>\n * <GtkEntry placeholderText=\"Search\" />\n * </Widget>\n * <Widget>\n * <GtkButton iconName=\"edit-find-symbolic\" />\n * </Widget>\n * </View>\n * ```\n *\n * The entry flexes, the button takes its natural size, both sit in the row\n * exactly like React Native children — because to Yoga they now are.\n *\n * For the reverse direction (React Native content inside a GTK slot) see\n * `SlotContent` and `IntrinsicContent`.\n */\nexport const Widget = ({ style, onLayout, children, testID }: WidgetProps) => {\n // The wrapper box IS the Yoga leaf, and it is measured through GTK, so the\n // widget inside reports its natural size up to Yoga. Wrapping rather than\n // cloning keeps the child's own ref and props untouched.\n const boxRef = useRef<Gtk.Box | null>(null)\n const { cssClass } = useLayoutChild(boxRef, {\n style,\n onLayout,\n // Without this a widget with no explicit size would measure as zero —\n // the whole reason a naked GtkButton in a View looks broken.\n measureFromWidget: true,\n })\n\n return (\n <GtkBox\n ref={boxRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : undefined}\n >\n {children}\n </GtkBox>\n )\n}\n\nexport type UseWidgetLayoutOptions = {\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n}\n\n/**\n * The same thing as {@link Widget}, without the extra component — attach the\n * ref yourself when you already hold one.\n *\n * ```tsx\n * const ref = useRef<Gtk.Button | null>(null)\n * useWidgetLayout(ref, { style: { width: 40, height: 40 } })\n * return <GtkButton ref={ref} iconName=\"open-menu-symbolic\" />\n * ```\n *\n * Returns the GTK CSS class produced by the visual half of the style, or\n * null — pass it to the widget's `cssClasses` if you want backgrounds and\n * borders from the style to apply.\n */\nexport const useWidgetLayout = (\n ref: { current: Gtk.Widget | null },\n options: UseWidgetLayoutOptions = {},\n): string | null => {\n const { cssClass } = useLayoutChild(ref, {\n style: options.style,\n onLayout: options.onLayout,\n measureFromWidget: true,\n })\n return cssClass\n}\n\nexport type ReactNativeLayoutProps = {\n /** React Native style. The layout half drives Yoga, the visual half becomes\n * a GTK CSS class. Omit sizing and the widget's natural size wins. */\n style?: StyleProp\n onLayout?: (event: LayoutEvent) => void\n}\n\n/**\n * Give a GTK component React Native layout, keeping every prop it already\n * has.\n *\n * ```tsx\n * const Button = wrapReactNative(GtkButton)\n *\n * <View style={{ flexDirection: \"row\", gap: 8 }}>\n * <Button style={{ flex: 1 }} label=\"Save\" onClicked={save} />\n * </View>\n * ```\n *\n * The result is generic in the wrapped component's props, so `label`,\n * `onClicked` and everything else gtkx binds keep their types; `style` and\n * `onLayout` are added on top.\n *\n * **Outside React Native layout it steps aside.** The same widget is often\n * used in a pure GTK slot — a HeaderBar's `start`, a ToolbarView's `topBar` —\n * where there is no Yoga tree to join. The wrapper detects that and renders\n * the bare component, so one exported symbol works in both worlds.\n */\n// A wrapped widget is a Yoga LEAF, so everything INSIDE it is GTK's\n// territory — its children and its slots alike.\n//\n// A widget SLOT is a property that takes a widget (`titleWidget`, `sheet`,\n// `sidebar`, `startChild`); a widget's content area is an ordinary child.\n// The distinction is gtkx's, it moves between releases — rc.3 took the\n// `content`/`child` props off single-child widgets and made that content a\n// child — and it has never had anything to do with layout. Both keep\n// rendering where they were written, so both keep seeing the enclosing React\n// Native layout root even though GTK has parented them somewhere else\n// entirely. Content then joins a Yoga tree whose viewport is the WINDOW\n// while GTK gives it the widget's own rectangle: laid out against one box,\n// drawn in another, and silently stealing space from a tree it was never\n// meant to be in.\n//\n// So the boundary clears the layout root on the way in. The widgets a slot\n// or a child usually holds want exactly that (it is what `WidgetContent`\n// does by hand), and React Native content entering has to bring its own root\n// — `SlotContent` to fill the area, `IntrinsicContent` to size the area to\n// itself. Which of the two is right cannot be inferred, and measurement says\n// so louder than argument: `AdwBottomSheet` alone FILLS in its content child\n// but HUGS in both `sheet` (a bottom sheet rises to the height of its own\n// contents) and `bottomBar`. One widget, three content areas, two answers,\n// with nothing in the name or the GIR type to tell them apart — the answer\n// lives in the widget's own layout code. The boundary is what makes NOT\n// saying a readable error instead of a silent mislayout — see useHostNode.\nconst SlotBoundary = ({\n location,\n children,\n}: {\n location: SlotLocation\n children: ReactNode\n}) => (\n // Two providers, no widget: a context provider is invisible to the gtkx\n // reconciler, so this is safe even on slots whose value is NOT a widget\n // (`breakpoints`, `menuModel`, `buffer`, `adjustment` — the majority of\n // element-valued props, in fact) — wrapping those in a real box would put\n // a GtkBox where an AdwBreakpoint belongs.\n <SlotContext.Provider value={location}>\n <HostNodeContext.Provider value={null}>{children}</HostNodeContext.Provider>\n </SlotContext.Provider>\n)\n\nconst holdsElement = (value: unknown): boolean =>\n isValidElement(value) || (Array.isArray(value) && value.some(holdsElement))\n\n// Puts every element-valued prop, and the children, behind a boundary.\n// `ref` is never content; anything that holds no element (a string child, a\n// number, an already-constructed GObject) is left exactly as it was, so the\n// common widget pays nothing.\nconst withSlotBoundaries = <P extends object>(props: P, widget: string): P => {\n let bounded: Record<string, unknown> | undefined\n for (const key in props) {\n if (key === \"ref\") {\n continue\n }\n const value = (props as Record<string, unknown>)[key]\n if (!holdsElement(value)) {\n continue\n }\n bounded ??= {}\n bounded[key] = (\n // Children are not a named slot: the error says \"inside AdwBottomSheet\"\n // rather than naming a property that does not exist.\n <SlotBoundary\n location={{ widget, slot: key === \"children\" ? null : key }}\n >\n {value as ReactNode}\n </SlotBoundary>\n )\n }\n // The common case is a widget with no element-valued prop at all: return the\n // props object untouched rather than allocate a copy per render.\n return bounded ? ({ ...props, ...bounded } as P) : props\n}\n\nexport const wrapReactNative = <P extends object>(\n Component: ComponentType<P>,\n // The widget's name, for error messages and React devtools. gtkx builds its\n // components from a factory, so they carry no name of their own — the\n // generated widget surface passes the class name it already knows.\n widgetName?: string,\n): ComponentType<P & ReactNativeLayoutProps> => {\n const name =\n widgetName ||\n (Component as { displayName?: string; name?: string }).displayName ||\n (Component as { name?: string }).name ||\n \"Widget\"\n\n const InLayout = (props: P & ReactNativeLayoutProps) => {\n const { style, onLayout, ...rest } = props\n const boxRef = useRef<Gtk.Box | null>(null)\n const { cssClass } = useLayoutChild(boxRef, {\n style,\n onLayout,\n // Without this a widget with no explicit size measures as zero — the\n // whole reason a naked GtkButton in a View looks broken.\n measureFromWidget: true,\n })\n const inner = rest as P & { cssClasses?: string[] } & {\n hexpand?: boolean\n vexpand?: boolean\n }\n return (\n <GtkBox ref={boxRef}>\n <Component\n {...inner}\n // The style's visual half lands on the WIDGET, not on the wrapper,\n // so backgrounds and borders colour the button itself. This is the\n // point: on this platform React Native style drives GTK.\n cssClasses={\n cssClass\n ? [...(inner.cssClasses ?? []), cssClass]\n : inner.cssClasses\n }\n // Fill the rect Yoga computed, so `flex: 1` and explicit sizes mean\n // what they mean everywhere else in React Native. An explicit\n // hexpand/vexpand from the caller still wins.\n hexpand={inner.hexpand ?? true}\n vexpand={inner.vexpand ?? true}\n />\n </GtkBox>\n )\n }\n\n const Wrapped = (props: P & ReactNativeLayoutProps) => {\n // Every slot this widget is given becomes GTK territory, in both branches\n // below: whether the widget itself joined React Native layout has nothing\n // to do with what its slots hold.\n const withSlots = withSlotBoundaries(props, name)\n // Read the context directly rather than through useHostNode, which throws\n // by design: here its absence is a supported case, not a mistake. The\n // same widget is often used in a pure GTK slot (a HeaderBar's `start`, a\n // ToolbarView's `topBar`) where there is no Yoga tree to join.\n const host = useContext(HostNodeContext)\n if (!host) {\n // style/onLayout are meaningless without a Yoga tree — drop them rather\n // than forward them onto a GObject that has no such properties.\n const rest = { ...withSlots } as Partial<ReactNativeLayoutProps>\n delete rest.style\n delete rest.onLayout\n return <Component {...(rest as P)} />\n }\n return <InLayout {...withSlots} />\n }\n\n InLayout.displayName = `wrapReactNative(${name}).InLayout`\n Wrapped.displayName = `wrapReactNative(${name})`\n return Wrapped\n}\n"]}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { FlatStyle, TransformPart } from "../contracts";
|
|
3
|
+
import type { ResponderProps } from "../responder/types";
|
|
3
4
|
import { type LayoutEvent } from "./use-layout-child";
|
|
5
|
+
type AnimatedValue = number | string;
|
|
4
6
|
type AnimatedNode = {
|
|
5
7
|
addListener(callback: (state: {
|
|
6
|
-
value:
|
|
8
|
+
value: AnimatedValue;
|
|
7
9
|
}) => void): string;
|
|
8
10
|
removeListener(id: string): void;
|
|
9
|
-
__getValue():
|
|
11
|
+
__getValue(): AnimatedValue;
|
|
10
12
|
};
|
|
11
13
|
type AnimatedTransformPart = {
|
|
12
14
|
[key: string]: number | string | AnimatedNode;
|
|
@@ -15,15 +17,16 @@ export type AnimatedViewStyle = Omit<FlatStyle, "opacity" | "transform"> & {
|
|
|
15
17
|
opacity?: number | AnimatedNode;
|
|
16
18
|
transform?: (TransformPart | AnimatedTransformPart)[];
|
|
17
19
|
};
|
|
18
|
-
export type AnimatedViewProps = {
|
|
20
|
+
export type AnimatedViewProps = ResponderProps & {
|
|
19
21
|
style?: AnimatedViewStyle | AnimatedViewStyle[];
|
|
20
22
|
children?: ReactNode;
|
|
21
23
|
onLayout?: (event: LayoutEvent) => void;
|
|
22
24
|
testID?: string;
|
|
23
25
|
};
|
|
24
26
|
export declare const Animated: {
|
|
25
|
-
View: ({ style, children, onLayout, testID, }: AnimatedViewProps) => React.JSX.Element;
|
|
27
|
+
View: ({ style, children, onLayout, testID, ...responderProps }: AnimatedViewProps) => React.JSX.Element;
|
|
26
28
|
Value: typeof import("../animated/value").AnimatedValue;
|
|
29
|
+
ValueXY: typeof import("../animated/value-xy").AnimatedValueXY;
|
|
27
30
|
timing(value: import("../animated/value").AnimatedValue, config: import("../animated/types").TimingConfig): import("../animated/types").CompositeAnimation;
|
|
28
31
|
spring(value: import("../animated/value").AnimatedValue, config: import("../animated/types").SpringConfig): import("../animated/types").CompositeAnimation;
|
|
29
32
|
sequence(animations: import("../animated/types").CompositeAnimation[]): import("../animated/types").CompositeAnimation;
|
|
@@ -2,8 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useLayoutEffect, useRef } from "react";
|
|
3
3
|
import { createAnimated } from "../animated/index";
|
|
4
4
|
import { GLib, GtkBox, queueAllocate } from "../gtkx/bridge/index";
|
|
5
|
+
import { useResponder } from "../responder/use-responder";
|
|
5
6
|
import { HostNodeContext } from "./host-node";
|
|
6
|
-
import {
|
|
7
|
+
import { setStoredTransform } from "./rect-store";
|
|
7
8
|
import { useLayoutChild, useRnContainer, } from "./use-layout-child";
|
|
8
9
|
// ~60fps one-shot ticks off the GLib main loop. A frame-clock driver (per
|
|
9
10
|
// window) is a later optimization; timeouts keep the driver widget-free.
|
|
@@ -29,6 +30,19 @@ const isAnimatedNode = (value) => typeof value === "object" &&
|
|
|
29
30
|
value !== null &&
|
|
30
31
|
typeof value.addListener === "function" &&
|
|
31
32
|
typeof value.__getValue === "function";
|
|
33
|
+
// Stable per-node identity for the effect's dependency key: Animated nodes
|
|
34
|
+
// are objects, and the bindings must be rebuilt when the node behind a slot
|
|
35
|
+
// changes — not on every render that rebuilds an equal-looking array.
|
|
36
|
+
let nextNodeId = 1;
|
|
37
|
+
const nodeIds = new WeakMap();
|
|
38
|
+
const nodeId = (value) => {
|
|
39
|
+
let id = nodeIds.get(value);
|
|
40
|
+
if (id === undefined) {
|
|
41
|
+
id = nextNodeId++;
|
|
42
|
+
nodeIds.set(value, id);
|
|
43
|
+
}
|
|
44
|
+
return id;
|
|
45
|
+
};
|
|
32
46
|
const splitAnimated = (style) => {
|
|
33
47
|
const flat = {};
|
|
34
48
|
const collect = (entry) => {
|
|
@@ -43,105 +57,112 @@ const splitAnimated = (style) => {
|
|
|
43
57
|
};
|
|
44
58
|
collect(style);
|
|
45
59
|
let opacity = null;
|
|
46
|
-
|
|
47
|
-
let translateY = null;
|
|
60
|
+
const slots = [];
|
|
48
61
|
if (isAnimatedNode(flat.opacity)) {
|
|
49
62
|
opacity = flat.opacity;
|
|
50
63
|
delete flat.opacity;
|
|
51
64
|
}
|
|
52
65
|
if (Array.isArray(flat.transform)) {
|
|
53
|
-
const staticParts = [];
|
|
54
66
|
for (const part of flat.transform) {
|
|
55
67
|
const key = Object.keys(part)[0];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
translateX = value;
|
|
59
|
-
}
|
|
60
|
-
else if (key === "translateY" && isAnimatedNode(value)) {
|
|
61
|
-
translateY = value;
|
|
62
|
-
}
|
|
63
|
-
else if (!isAnimatedNode(value)) {
|
|
64
|
-
staticParts.push(part);
|
|
68
|
+
if (key === undefined) {
|
|
69
|
+
continue;
|
|
65
70
|
}
|
|
71
|
+
const value = part[key];
|
|
72
|
+
slots.push(isAnimatedNode(value)
|
|
73
|
+
? { key, node: value, value: value.__getValue() }
|
|
74
|
+
: { key, node: null, value: value });
|
|
66
75
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
76
|
+
// The whole array is owned here from now on, static entries included:
|
|
77
|
+
// leaving them in the style would make useLayoutChild write the same
|
|
78
|
+
// rect-store slot from the other side, and the two would overwrite
|
|
79
|
+
// each other every render.
|
|
80
|
+
delete flat.transform;
|
|
73
81
|
}
|
|
74
|
-
return { staticStyle: flat, opacity,
|
|
82
|
+
return { staticStyle: flat, opacity, slots };
|
|
75
83
|
};
|
|
76
84
|
// Animated values bypass React entirely: listeners write straight to the
|
|
77
|
-
// widget (opacity) and to the rect store (
|
|
85
|
+
// widget (opacity) and to the rect store (the transform applied by the
|
|
78
86
|
// parent's allocate), on top of the engine-committed base rect — the fast
|
|
79
87
|
// path measured in the spike. Animation frames never touch Yoga.
|
|
80
|
-
const AnimatedView = ({ style, children, onLayout, testID, }) => {
|
|
88
|
+
const AnimatedView = ({ style, children, onLayout, testID, ...responderProps }) => {
|
|
81
89
|
const widgetRef = useRef(null);
|
|
82
|
-
const { staticStyle, opacity,
|
|
90
|
+
const { staticStyle, opacity, slots } = splitAnimated(style);
|
|
83
91
|
const { host, node, cssClass } = useLayoutChild(widgetRef, {
|
|
84
92
|
style: staticStyle,
|
|
85
93
|
onLayout,
|
|
86
94
|
});
|
|
87
95
|
useRnContainer(widgetRef, node);
|
|
88
|
-
|
|
96
|
+
useResponder(widgetRef, responderProps);
|
|
97
|
+
// The effect reads the slots of the render that (re)armed it; the values
|
|
98
|
+
// inside are then owned by the listeners.
|
|
99
|
+
const slotsRef = useRef(slots);
|
|
100
|
+
slotsRef.current = slots;
|
|
101
|
+
// Rebind on a change of shape (which transforms, in which order), of the
|
|
102
|
+
// node behind an animated entry, or of a static entry's value — not on
|
|
103
|
+
// every render that rebuilds an equal-looking array.
|
|
104
|
+
const bindingKey = (opacity ? `opacity#${nodeId(opacity)}` : "") +
|
|
105
|
+
slots
|
|
106
|
+
.map((slot) => slot.node
|
|
107
|
+
? `|${slot.key}#${nodeId(slot.node)}`
|
|
108
|
+
: `|${slot.key}=${String(slot.value)}`)
|
|
109
|
+
.join("");
|
|
89
110
|
useLayoutEffect(() => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
const widget = widgetRef.current;
|
|
112
|
+
if (!widget) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// The live transform array, mutated in place: an Animated write must
|
|
116
|
+
// stay one numeric store plus one queued allocation — no React render,
|
|
117
|
+
// no Yoga pass, no allocation per frame beyond the composed matrix.
|
|
118
|
+
//
|
|
119
|
+
// RN transform semantics: this is paint-only. The result goes to the
|
|
120
|
+
// rect store unclamped — the parent's measure ignores children, so a
|
|
121
|
+
// transformed child draws past the boundary over its neighbors without
|
|
93
122
|
// moving a single ancestor.
|
|
94
|
-
const
|
|
95
|
-
|
|
123
|
+
const parts = slotsRef.current.map((slot) => ({ [slot.key]: slot.value }));
|
|
124
|
+
const flush = () => {
|
|
125
|
+
setStoredTransform(widget, parts);
|
|
96
126
|
const parentWidget = host.widgetRef.current;
|
|
97
|
-
if (
|
|
98
|
-
|
|
127
|
+
if (parentWidget) {
|
|
128
|
+
queueAllocate(parentWidget);
|
|
99
129
|
}
|
|
100
|
-
setStoredOffset(widget, offsets.current.x, offsets.current.y);
|
|
101
|
-
queueAllocate(parentWidget);
|
|
102
130
|
};
|
|
103
|
-
const
|
|
131
|
+
const subscriptions = [];
|
|
132
|
+
slotsRef.current.forEach((slot, index) => {
|
|
133
|
+
if (!slot.node) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const part = parts[index];
|
|
137
|
+
subscriptions.push({
|
|
138
|
+
node: slot.node,
|
|
139
|
+
id: slot.node.addListener(({ value }) => {
|
|
140
|
+
part[slot.key] = value;
|
|
141
|
+
flush();
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
});
|
|
104
145
|
if (opacity) {
|
|
105
|
-
|
|
146
|
+
const applyOpacity = (value) => {
|
|
147
|
+
const numeric = typeof value === "number" ? value : parseFloat(value);
|
|
148
|
+
widgetRef.current?.setOpacity(Math.min(1, Math.max(0, numeric)));
|
|
149
|
+
};
|
|
150
|
+
subscriptions.push({
|
|
106
151
|
node: opacity,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
},
|
|
152
|
+
id: opacity.addListener(({ value }) => {
|
|
153
|
+
applyOpacity(value);
|
|
154
|
+
}),
|
|
110
155
|
});
|
|
156
|
+
applyOpacity(opacity.__getValue());
|
|
111
157
|
}
|
|
112
|
-
|
|
113
|
-
bindings.push({
|
|
114
|
-
node: translateX,
|
|
115
|
-
apply: (value) => {
|
|
116
|
-
offsets.current.x = value;
|
|
117
|
-
applyTranslate();
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
if (translateY) {
|
|
122
|
-
bindings.push({
|
|
123
|
-
node: translateY,
|
|
124
|
-
apply: (value) => {
|
|
125
|
-
offsets.current.y = value;
|
|
126
|
-
applyTranslate();
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
const subscriptions = bindings.map((binding) => ({
|
|
131
|
-
binding,
|
|
132
|
-
id: binding.node.addListener(({ value }) => binding.apply(value)),
|
|
133
|
-
}));
|
|
134
|
-
for (const { binding } of subscriptions) {
|
|
135
|
-
binding.apply(binding.node.__getValue());
|
|
136
|
-
}
|
|
158
|
+
flush();
|
|
137
159
|
return () => {
|
|
138
|
-
for (const {
|
|
139
|
-
|
|
160
|
+
for (const { node: animated, id } of subscriptions) {
|
|
161
|
+
animated.removeListener(id);
|
|
140
162
|
}
|
|
141
163
|
};
|
|
142
|
-
// Re-bind when the animated node identities change.
|
|
143
164
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
144
|
-
}, [
|
|
165
|
+
}, [bindingKey, node]);
|
|
145
166
|
return (_jsx(GtkBox, { ref: widgetRef, name: testID, cssClasses: cssClass ? [cssClass] : [], children: _jsx(HostNodeContext.Provider, { value: { engine: host.engine, node, widgetRef }, children: children }) }));
|
|
146
167
|
};
|
|
147
168
|
export const Animated = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animated.js","sourceRoot":"","sources":["../../src/components/animated.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, type ReactNode } from \"react\"\nimport { createAnimated, type FrameScheduler } from \"../animated/index\"\nimport type { FlatStyle, StyleProp, TransformPart } from \"../contracts\"\nimport { GLib, GtkBox, queueAllocate, type Gtk } from \"../gtkx/bridge/index\"\nimport { HostNodeContext } from \"./host-node\"\nimport { setStoredOffset } from \"./rect-store\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\n// ~60fps one-shot ticks off the GLib main loop. A frame-clock driver (per\n// window) is a later optimization; timeouts keep the driver widget-free.\nconst glibScheduler: FrameScheduler = {\n schedule(callback) {\n let cancelled = false\n const id = GLib.timeoutAdd(GLib.PRIORITY_DEFAULT, 16, () => {\n if (!cancelled) {\n callback(Number(GLib.getMonotonicTime()) / 1000)\n }\n return false\n })\n return () => {\n if (!cancelled) {\n cancelled = true\n GLib.Source.remove(id)\n }\n }\n },\n}\n\nconst api = createAnimated(glibScheduler)\n\ntype AnimatedNode = {\n addListener(callback: (state: { value: number }) => void): string\n removeListener(id: string): void\n __getValue(): number\n}\n\nconst isAnimatedNode = (value: unknown): value is AnimatedNode =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AnimatedNode).addListener === \"function\" &&\n typeof (value as AnimatedNode).__getValue === \"function\"\n\ntype AnimatedTransformPart = { [key: string]: number | string | AnimatedNode }\n\n// Omit, not plain intersection: intersecting would AND the animated transform\n// with FlatStyle's numeric one, rejecting Animated.Value entries.\nexport type AnimatedViewStyle = Omit<FlatStyle, \"opacity\" | \"transform\"> & {\n opacity?: number | AnimatedNode\n transform?: (TransformPart | AnimatedTransformPart)[]\n}\n\ntype Binding = {\n node: AnimatedNode\n apply: (value: number) => void\n}\n\nexport type AnimatedViewProps = {\n style?: AnimatedViewStyle | AnimatedViewStyle[]\n children?: ReactNode\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\nconst splitAnimated = (\n style: AnimatedViewProps[\"style\"],\n): {\n staticStyle: StyleProp\n opacity: AnimatedNode | null\n translateX: AnimatedNode | null\n translateY: AnimatedNode | null\n} => {\n const flat: Record<string, unknown> = {}\n const collect = (entry: AnimatedViewProps[\"style\"]): void => {\n if (!entry) {\n return\n }\n if (Array.isArray(entry)) {\n entry.forEach(collect)\n return\n }\n Object.assign(flat, entry)\n }\n collect(style)\n\n let opacity: AnimatedNode | null = null\n let translateX: AnimatedNode | null = null\n let translateY: AnimatedNode | null = null\n\n if (isAnimatedNode(flat.opacity)) {\n opacity = flat.opacity\n delete flat.opacity\n }\n if (Array.isArray(flat.transform)) {\n const staticParts: TransformPart[] = []\n for (const part of flat.transform as AnimatedTransformPart[]) {\n const key = Object.keys(part)[0]\n const value = part[key!]\n if (key === \"translateX\" && isAnimatedNode(value)) {\n translateX = value\n } else if (key === \"translateY\" && isAnimatedNode(value)) {\n translateY = value\n } else if (!isAnimatedNode(value)) {\n staticParts.push(part as TransformPart)\n }\n }\n if (staticParts.length > 0) {\n flat.transform = staticParts\n } else {\n delete flat.transform\n }\n }\n\n return { staticStyle: flat as StyleProp, opacity, translateX, translateY }\n}\n\n// Animated values bypass React entirely: listeners write straight to the\n// widget (opacity) and to the rect store (translate offsets applied by the\n// parent's allocate), on top of the engine-committed base rect — the fast\n// path measured in the spike. Animation frames never touch Yoga.\nconst AnimatedView = ({\n style,\n children,\n onLayout,\n testID,\n}: AnimatedViewProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { staticStyle, opacity, translateX, translateY } = splitAnimated(style)\n\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style: staticStyle,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n\n const offsets = useRef({ x: 0, y: 0 })\n\n useLayoutEffect(() => {\n // RN transform semantics: translate is paint-only. The offset goes to\n // the rect store unclamped — the parent's measure ignores children, so an\n // out-of-bounds child draws past the boundary over its neighbors without\n // moving a single ancestor.\n const applyTranslate = (): void => {\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n if (!widget || !parentWidget) {\n return\n }\n setStoredOffset(widget, offsets.current.x, offsets.current.y)\n queueAllocate(parentWidget)\n }\n\n const bindings: Binding[] = []\n if (opacity) {\n bindings.push({\n node: opacity,\n apply: (value) => {\n widgetRef.current?.setOpacity(Math.min(1, Math.max(0, value)))\n },\n })\n }\n if (translateX) {\n bindings.push({\n node: translateX,\n apply: (value) => {\n offsets.current.x = value\n applyTranslate()\n },\n })\n }\n if (translateY) {\n bindings.push({\n node: translateY,\n apply: (value) => {\n offsets.current.y = value\n applyTranslate()\n },\n })\n }\n\n const subscriptions = bindings.map((binding) => ({\n binding,\n id: binding.node.addListener(({ value }) => binding.apply(value)),\n }))\n for (const { binding } of subscriptions) {\n binding.apply(binding.node.__getValue())\n }\n return () => {\n for (const { binding, id } of subscriptions) {\n binding.node.removeListener(id)\n }\n }\n // Re-bind when the animated node identities change.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [opacity, translateX, translateY, node])\n\n return (\n <GtkBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {children}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\nexport const Animated = {\n ...api,\n View: AnimatedView,\n}\n\nexport { Easing } from \"../animated/index\"\n"]}
|
|
1
|
+
{"version":3,"file":"animated.js","sourceRoot":"","sources":["../../src/components/animated.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, type ReactNode } from \"react\"\nimport { createAnimated, type FrameScheduler } from \"../animated/index\"\nimport type { FlatStyle, StyleProp, TransformPart } from \"../contracts\"\nimport { GLib, GtkBox, queueAllocate, type Gtk } from \"../gtkx/bridge/index\"\nimport type { ResponderProps } from \"../responder/types\"\nimport { useResponder } from \"../responder/use-responder\"\nimport { HostNodeContext } from \"./host-node\"\nimport { setStoredTransform } from \"./rect-store\"\nimport {\n useLayoutChild,\n useRnContainer,\n type LayoutEvent,\n} from \"./use-layout-child\"\n\n// ~60fps one-shot ticks off the GLib main loop. A frame-clock driver (per\n// window) is a later optimization; timeouts keep the driver widget-free.\nconst glibScheduler: FrameScheduler = {\n schedule(callback) {\n let cancelled = false\n const id = GLib.timeoutAdd(GLib.PRIORITY_DEFAULT, 16, () => {\n if (!cancelled) {\n callback(Number(GLib.getMonotonicTime()) / 1000)\n }\n return false\n })\n return () => {\n if (!cancelled) {\n cancelled = true\n GLib.Source.remove(id)\n }\n }\n },\n}\n\nconst api = createAnimated(glibScheduler)\n\n// Interpolations produce suffixed strings (\"45deg\"), so a driven value is\n// not necessarily a number — `rotate` is normally an interpolate() with a\n// deg outputRange.\ntype AnimatedValue = number | string\n\ntype AnimatedNode = {\n addListener(callback: (state: { value: AnimatedValue }) => void): string\n removeListener(id: string): void\n __getValue(): AnimatedValue\n}\n\nconst isAnimatedNode = (value: unknown): value is AnimatedNode =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as AnimatedNode).addListener === \"function\" &&\n typeof (value as AnimatedNode).__getValue === \"function\"\n\ntype AnimatedTransformPart = { [key: string]: number | string | AnimatedNode }\n\n// One entry of the style's transform array, kept in source order. The array\n// order IS the composition order in RN, so an animated entry updates its own\n// slot in place instead of being lifted out of the list — that is what makes\n// [{rotate}, {translateX}] compose differently from the reverse, as in RN.\ntype TransformSlot = {\n key: string\n node: AnimatedNode | null\n value: AnimatedValue\n}\n\n// Stable per-node identity for the effect's dependency key: Animated nodes\n// are objects, and the bindings must be rebuilt when the node behind a slot\n// changes — not on every render that rebuilds an equal-looking array.\nlet nextNodeId = 1\nconst nodeIds = new WeakMap<object, number>()\nconst nodeId = (value: object): number => {\n let id = nodeIds.get(value)\n if (id === undefined) {\n id = nextNodeId++\n nodeIds.set(value, id)\n }\n return id\n}\n\n// Omit, not plain intersection: intersecting would AND the animated transform\n// with FlatStyle's numeric one, rejecting Animated.Value entries.\nexport type AnimatedViewStyle = Omit<FlatStyle, \"opacity\" | \"transform\"> & {\n opacity?: number | AnimatedNode\n transform?: (TransformPart | AnimatedTransformPart)[]\n}\n\n// Animated.View is where a PanResponder drag lands in idiomatic RN — the\n// dragged box is animated by definition — so it takes the responder props\n// exactly as View does.\nexport type AnimatedViewProps = ResponderProps & {\n style?: AnimatedViewStyle | AnimatedViewStyle[]\n children?: ReactNode\n onLayout?: (event: LayoutEvent) => void\n testID?: string\n}\n\nconst splitAnimated = (\n style: AnimatedViewProps[\"style\"],\n): {\n staticStyle: StyleProp\n opacity: AnimatedNode | null\n slots: TransformSlot[]\n} => {\n const flat: Record<string, unknown> = {}\n const collect = (entry: AnimatedViewProps[\"style\"]): void => {\n if (!entry) {\n return\n }\n if (Array.isArray(entry)) {\n entry.forEach(collect)\n return\n }\n Object.assign(flat, entry)\n }\n collect(style)\n\n let opacity: AnimatedNode | null = null\n const slots: TransformSlot[] = []\n\n if (isAnimatedNode(flat.opacity)) {\n opacity = flat.opacity\n delete flat.opacity\n }\n if (Array.isArray(flat.transform)) {\n for (const part of flat.transform as AnimatedTransformPart[]) {\n const key = Object.keys(part)[0]\n if (key === undefined) {\n continue\n }\n const value = part[key]\n slots.push(\n isAnimatedNode(value)\n ? { key, node: value, value: value.__getValue() }\n : { key, node: null, value: value as AnimatedValue },\n )\n }\n // The whole array is owned here from now on, static entries included:\n // leaving them in the style would make useLayoutChild write the same\n // rect-store slot from the other side, and the two would overwrite\n // each other every render.\n delete flat.transform\n }\n\n return { staticStyle: flat as StyleProp, opacity, slots }\n}\n\n// Animated values bypass React entirely: listeners write straight to the\n// widget (opacity) and to the rect store (the transform applied by the\n// parent's allocate), on top of the engine-committed base rect — the fast\n// path measured in the spike. Animation frames never touch Yoga.\nconst AnimatedView = ({\n style,\n children,\n onLayout,\n testID,\n ...responderProps\n}: AnimatedViewProps) => {\n const widgetRef = useRef<Gtk.Box | null>(null)\n const { staticStyle, opacity, slots } = splitAnimated(style)\n\n const { host, node, cssClass } = useLayoutChild(widgetRef, {\n style: staticStyle,\n onLayout,\n })\n useRnContainer(widgetRef, node)\n useResponder(widgetRef, responderProps)\n\n // The effect reads the slots of the render that (re)armed it; the values\n // inside are then owned by the listeners.\n const slotsRef = useRef<TransformSlot[]>(slots)\n slotsRef.current = slots\n\n // Rebind on a change of shape (which transforms, in which order), of the\n // node behind an animated entry, or of a static entry's value — not on\n // every render that rebuilds an equal-looking array.\n const bindingKey =\n (opacity ? `opacity#${nodeId(opacity)}` : \"\") +\n slots\n .map((slot) =>\n slot.node\n ? `|${slot.key}#${nodeId(slot.node)}`\n : `|${slot.key}=${String(slot.value)}`,\n )\n .join(\"\")\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n\n // The live transform array, mutated in place: an Animated write must\n // stay one numeric store plus one queued allocation — no React render,\n // no Yoga pass, no allocation per frame beyond the composed matrix.\n //\n // RN transform semantics: this is paint-only. The result goes to the\n // rect store unclamped — the parent's measure ignores children, so a\n // transformed child draws past the boundary over its neighbors without\n // moving a single ancestor.\n const parts = slotsRef.current.map(\n (slot) => ({ [slot.key]: slot.value }) as Record<string, AnimatedValue>,\n )\n\n const flush = (): void => {\n setStoredTransform(widget, parts as unknown as TransformPart[])\n const parentWidget = host.widgetRef.current\n if (parentWidget) {\n queueAllocate(parentWidget)\n }\n }\n\n const subscriptions: { node: AnimatedNode; id: string }[] = []\n slotsRef.current.forEach((slot, index) => {\n if (!slot.node) {\n return\n }\n const part = parts[index]!\n subscriptions.push({\n node: slot.node,\n id: slot.node.addListener(({ value }) => {\n part[slot.key] = value\n flush()\n }),\n })\n })\n\n if (opacity) {\n const applyOpacity = (value: AnimatedValue): void => {\n const numeric = typeof value === \"number\" ? value : parseFloat(value)\n widgetRef.current?.setOpacity(Math.min(1, Math.max(0, numeric)))\n }\n subscriptions.push({\n node: opacity,\n id: opacity.addListener(({ value }) => {\n applyOpacity(value)\n }),\n })\n applyOpacity(opacity.__getValue())\n }\n\n flush()\n\n return () => {\n for (const { node: animated, id } of subscriptions) {\n animated.removeListener(id)\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [bindingKey, node])\n\n return (\n <GtkBox\n ref={widgetRef}\n name={testID}\n cssClasses={cssClass ? [cssClass] : []}\n >\n <HostNodeContext.Provider\n value={{ engine: host.engine, node, widgetRef }}\n >\n {children}\n </HostNodeContext.Provider>\n </GtkBox>\n )\n}\n\nexport const Animated = {\n ...api,\n View: AnimatedView,\n}\n\nexport { Easing } from \"../animated/index\"\n"]}
|
|
@@ -1,12 +1,47 @@
|
|
|
1
|
-
import { type ComponentType } from "react";
|
|
1
|
+
import { type ComponentType, type ReactNode } from "react";
|
|
2
2
|
type ComponentProvider = () => ComponentType<Record<string, unknown>>;
|
|
3
3
|
export declare const getActiveChrome: () => "system" | "content" | null;
|
|
4
|
+
export type ActionAccel = {
|
|
5
|
+
detailedActionName: string;
|
|
6
|
+
accels: string[];
|
|
7
|
+
};
|
|
4
8
|
export type RunApplicationParams = {
|
|
5
9
|
initialProps?: Record<string, unknown>;
|
|
6
10
|
title?: string;
|
|
7
11
|
width?: number;
|
|
8
12
|
height?: number;
|
|
9
13
|
chrome?: "system" | "content";
|
|
14
|
+
/**
|
|
15
|
+
* GSimpleAction elements registered on the GApplication itself
|
|
16
|
+
* ("app.<name>" — reachable from anywhere, including a Gio.Notification's
|
|
17
|
+
* action buttons, which can only ever target an app-level action).
|
|
18
|
+
*
|
|
19
|
+
* @deprecated Render `<ApplicationActions>` (react-native-gtkx/gtk) inside
|
|
20
|
+
* the app instead. Options render as SIBLINGS of the app tree, so no
|
|
21
|
+
* context of the app's can reach them and the set is fixed for the
|
|
22
|
+
* process's lifetime. Still supported; see docs/api.md.
|
|
23
|
+
*/
|
|
24
|
+
applicationActions?: ReactNode;
|
|
25
|
+
actionAccels?: ActionAccel[];
|
|
26
|
+
/**
|
|
27
|
+
* GSimpleAction elements registered on the window ("win.<name>" — what a
|
|
28
|
+
* HeaderBar button's actionName or a GMenu item usually targets).
|
|
29
|
+
*
|
|
30
|
+
* @deprecated Render `<WindowActions>` (react-native-gtkx/gtk) inside the
|
|
31
|
+
* app instead — it can read the app's context and registers/unregisters
|
|
32
|
+
* with the component that declares it. Still supported; see docs/api.md.
|
|
33
|
+
*/
|
|
34
|
+
windowActions?: ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* Event controllers attached to the window itself — a
|
|
37
|
+
* GtkShortcutController scoped to the whole window is the common case.
|
|
38
|
+
*
|
|
39
|
+
* @deprecated Render `<WindowControllers>` (react-native-gtkx/gtk) inside
|
|
40
|
+
* the app instead, for the same reasons as `windowActions`. Still
|
|
41
|
+
* supported; see docs/api.md.
|
|
42
|
+
*/
|
|
43
|
+
windowControllers?: ReactNode;
|
|
44
|
+
breakpoints?: ReactNode;
|
|
10
45
|
};
|
|
11
46
|
export declare const AppRegistry: {
|
|
12
47
|
registerComponent(appKey: string, provider: ComponentProvider): string;
|
|
@@ -70,9 +70,14 @@ export const AppRegistry = {
|
|
|
70
70
|
const height = params.height ?? 600;
|
|
71
71
|
const contentChrome = params.chrome === "content";
|
|
72
72
|
activeChrome = contentChrome ? "content" : "system";
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
if (process.env.NODE_ENV !== "production" &&
|
|
74
|
+
params.breakpoints &&
|
|
75
|
+
!contentChrome) {
|
|
76
|
+
console.warn('[react-native-gtkx] breakpoints was passed to runApplication without chrome: "content" — GtkApplicationWindow (the "system" chrome default) has no breakpoints concept, so it is ignored.');
|
|
77
|
+
}
|
|
78
|
+
const content = contentChrome ? (_jsx(ContentChrome, { App: App, initialProps: params.initialProps ?? {} })) : (_jsx(WindowContent, { App: App, initialProps: params.initialProps ?? {}, initialWidth: width, initialHeight: height }));
|
|
79
|
+
const AppWindow = () => contentChrome ? (_jsx(AdwApplicationWindow, { title: params.title ?? appKey, defaultWidth: width, defaultHeight: height, onCloseRequest: quit, actions: params.windowActions, controllers: params.windowControllers, breakpoints: params.breakpoints, children: content })) : (_jsx(GtkApplicationWindow, { title: params.title ?? appKey, defaultWidth: width, defaultHeight: height, onCloseRequest: quit, actions: params.windowActions, controllers: params.windowControllers, children: content }));
|
|
80
|
+
createRoot().render(_jsx(GtkApplication, { actions: params.applicationActions, actionAccels: params.actionAccels, children: _jsx(AppWindow, {}) }));
|
|
76
81
|
},
|
|
77
82
|
};
|
|
78
83
|
//# sourceMappingURL=app-registry.js.map
|