react-native-gtkx 0.2.0-alpha.2 → 0.3.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/aliases/index.d.ts +84 -0
- package/dist/aliases/index.js +208 -0
- package/dist/aliases/index.js.map +1 -0
- package/dist/animated/index.d.ts +2 -0
- package/dist/animated/index.js +5 -0
- package/dist/animated/index.js.map +1 -1
- package/dist/animated/types.d.ts +1 -0
- package/dist/animated/types.js.map +1 -1
- package/dist/animated/value-animation.js +17 -4
- package/dist/animated/value-animation.js.map +1 -1
- package/dist/apis/host.gtkx.js +9 -12
- package/dist/apis/host.gtkx.js.map +1 -1
- package/dist/apis/index.d.ts +4 -2
- package/dist/apis/index.js +2 -0
- package/dist/apis/index.js.map +1 -1
- package/dist/apis/keyboard.d.ts +60 -0
- package/dist/apis/keyboard.js +63 -0
- package/dist/apis/keyboard.js.map +1 -0
- package/dist/apis/log-box.d.ts +15 -0
- package/dist/apis/log-box.js +46 -0
- package/dist/apis/log-box.js.map +1 -0
- package/dist/apis/platform.d.ts +18 -1
- package/dist/apis/platform.js.map +1 -1
- package/dist/common/index.d.ts +0 -1
- package/dist/common/index.js +24 -1
- package/dist/common/index.js.map +1 -1
- package/dist/components/animated.d.ts +70 -6
- package/dist/components/animated.js +559 -46
- package/dist/components/animated.js.map +1 -1
- package/dist/components/driven-size.d.ts +27 -0
- package/dist/components/driven-size.js +85 -0
- package/dist/components/driven-size.js.map +1 -0
- package/dist/components/find-node-handle.d.ts +11 -0
- package/dist/components/find-node-handle.js +53 -0
- package/dist/components/find-node-handle.js.map +1 -0
- package/dist/components/flat-list.d.ts +3 -2
- package/dist/components/flat-list.js.map +1 -1
- package/dist/components/frame-scheduler.d.ts +16 -0
- package/dist/components/frame-scheduler.js +37 -0
- package/dist/components/frame-scheduler.js.map +1 -0
- package/dist/components/image.d.ts +6 -1
- package/dist/components/image.js +15 -4
- package/dist/components/image.js.map +1 -1
- package/dist/components/index.d.ts +7 -5
- package/dist/components/index.js +5 -3
- package/dist/components/index.js.map +1 -1
- package/dist/components/measure.d.ts +51 -1
- package/dist/components/measure.js +101 -2
- package/dist/components/measure.js.map +1 -1
- package/dist/components/pressable.d.ts +61 -1
- package/dist/components/pressable.js +151 -4
- package/dist/components/pressable.js.map +1 -1
- package/dist/components/rect-store.d.ts +58 -0
- package/dist/components/rect-store.js +124 -3
- package/dist/components/rect-store.js.map +1 -1
- package/dist/components/root.js +6 -1
- package/dist/components/root.js.map +1 -1
- package/dist/components/scroll-phase.d.ts +35 -0
- package/dist/components/scroll-phase.js +26 -0
- package/dist/components/scroll-phase.js.map +1 -0
- package/dist/components/scroll-view.d.ts +39 -3
- package/dist/components/scroll-view.js +378 -43
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/text-input.js +9 -25
- package/dist/components/text-input.js.map +1 -1
- package/dist/components/text.d.ts +6 -2
- package/dist/components/text.js +43 -8
- package/dist/components/text.js.map +1 -1
- package/dist/components/use-focus.d.ts +27 -0
- package/dist/components/use-focus.js +116 -0
- package/dist/components/use-focus.js.map +1 -0
- package/dist/components/use-layout-child.d.ts +13 -1
- package/dist/components/use-layout-child.js +188 -5
- package/dist/components/use-layout-child.js.map +1 -1
- package/dist/components/view.d.ts +5 -2
- package/dist/components/view.js +18 -13
- package/dist/components/view.js.map +1 -1
- package/dist/components/virtualized-list.d.ts +45 -5
- package/dist/components/virtualized-list.js +90 -24
- package/dist/components/virtualized-list.js.map +1 -1
- package/dist/components/wheel-scroll-session.d.ts +7 -0
- package/dist/components/wheel-scroll-session.js +53 -0
- package/dist/components/wheel-scroll-session.js.map +1 -0
- package/dist/components/widget-retention.d.ts +35 -0
- package/dist/components/widget-retention.js +169 -0
- package/dist/components/widget-retention.js.map +1 -0
- package/dist/contracts.d.ts +4 -0
- package/dist/contracts.js.map +1 -1
- package/dist/dnd/context.d.ts +69 -0
- package/dist/dnd/context.js +149 -0
- package/dist/dnd/context.js.map +1 -0
- package/dist/dnd/draggable.d.ts +59 -0
- package/dist/dnd/draggable.js +149 -0
- package/dist/dnd/draggable.js.map +1 -0
- package/dist/dnd/droppable.d.ts +24 -0
- package/dist/dnd/droppable.js +74 -0
- package/dist/dnd/droppable.js.map +1 -0
- package/dist/dnd/gtk-controllers.d.ts +39 -0
- package/dist/dnd/gtk-controllers.js +110 -0
- package/dist/dnd/gtk-controllers.js.map +1 -0
- package/dist/dnd/index.d.ts +6 -0
- package/dist/dnd/index.js +31 -0
- package/dist/dnd/index.js.map +1 -0
- package/dist/dnd/order.d.ts +7 -0
- package/dist/dnd/order.js +20 -0
- package/dist/dnd/order.js.map +1 -0
- package/dist/dnd/payload.d.ts +14 -0
- package/dist/dnd/payload.js +41 -0
- package/dist/dnd/payload.js.map +1 -0
- package/dist/dnd/sortable.d.ts +49 -0
- package/dist/dnd/sortable.js +307 -0
- package/dist/dnd/sortable.js.map +1 -0
- package/dist/dnd/types.d.ts +290 -0
- package/dist/dnd/types.js +25 -0
- package/dist/dnd/types.js.map +1 -0
- package/dist/gesture-handler-compat/builder.d.ts +251 -0
- package/dist/gesture-handler-compat/builder.js +513 -0
- package/dist/gesture-handler-compat/builder.js.map +1 -0
- package/dist/gesture-handler-compat/composition.d.ts +23 -0
- package/dist/gesture-handler-compat/composition.js +111 -0
- package/dist/gesture-handler-compat/composition.js.map +1 -0
- package/dist/gesture-handler-compat/deciders.d.ts +3 -0
- package/dist/gesture-handler-compat/deciders.js +47 -0
- package/dist/gesture-handler-compat/deciders.js.map +1 -0
- package/dist/gesture-handler-compat/detector-runtime.d.ts +44 -0
- package/dist/gesture-handler-compat/detector-runtime.js +374 -0
- package/dist/gesture-handler-compat/detector-runtime.js.map +1 -0
- package/dist/gesture-handler-compat/detector.d.ts +22 -0
- package/dist/gesture-handler-compat/detector.js +104 -0
- package/dist/gesture-handler-compat/detector.js.map +1 -0
- package/dist/gesture-handler-compat/fling.d.ts +24 -0
- package/dist/gesture-handler-compat/fling.js +123 -0
- package/dist/gesture-handler-compat/fling.js.map +1 -0
- package/dist/gesture-handler-compat/force-touch.d.ts +13 -0
- package/dist/gesture-handler-compat/force-touch.js +40 -0
- package/dist/gesture-handler-compat/force-touch.js.map +1 -0
- package/dist/gesture-handler-compat/hooks.d.ts +157 -0
- package/dist/gesture-handler-compat/hooks.js +268 -0
- package/dist/gesture-handler-compat/hooks.js.map +1 -0
- package/dist/gesture-handler-compat/hover.d.ts +2 -0
- package/dist/gesture-handler-compat/hover.js +22 -0
- package/dist/gesture-handler-compat/hover.js.map +1 -0
- package/dist/gesture-handler-compat/index.d.ts +98 -0
- package/dist/gesture-handler-compat/index.js +255 -0
- package/dist/gesture-handler-compat/index.js.map +1 -0
- package/dist/gesture-handler-compat/long-press.d.ts +6 -0
- package/dist/gesture-handler-compat/long-press.js +21 -0
- package/dist/gesture-handler-compat/long-press.js.map +1 -0
- package/dist/gesture-handler-compat/manual.d.ts +2 -0
- package/dist/gesture-handler-compat/manual.js +18 -0
- package/dist/gesture-handler-compat/manual.js.map +1 -0
- package/dist/gesture-handler-compat/native.d.ts +7 -0
- package/dist/gesture-handler-compat/native.js +34 -0
- package/dist/gesture-handler-compat/native.js.map +1 -0
- package/dist/gesture-handler-compat/orchestrator.d.ts +77 -0
- package/dist/gesture-handler-compat/orchestrator.js +284 -0
- package/dist/gesture-handler-compat/orchestrator.js.map +1 -0
- package/dist/gesture-handler-compat/pan.d.ts +23 -0
- package/dist/gesture-handler-compat/pan.js +116 -0
- package/dist/gesture-handler-compat/pan.js.map +1 -0
- package/dist/gesture-handler-compat/recognizer.d.ts +268 -0
- package/dist/gesture-handler-compat/recognizer.js +1056 -0
- package/dist/gesture-handler-compat/recognizer.js.map +1 -0
- package/dist/gesture-handler-compat/relations.d.ts +43 -0
- package/dist/gesture-handler-compat/relations.js +128 -0
- package/dist/gesture-handler-compat/relations.js.map +1 -0
- package/dist/gesture-handler-compat/tap.d.ts +6 -0
- package/dist/gesture-handler-compat/tap.js +29 -0
- package/dist/gesture-handler-compat/tap.js.map +1 -0
- package/dist/gesture-handler-compat/touchpad.d.ts +41 -0
- package/dist/gesture-handler-compat/touchpad.js +64 -0
- package/dist/gesture-handler-compat/touchpad.js.map +1 -0
- package/dist/gesture-handler-compat/types.d.ts +441 -0
- package/dist/gesture-handler-compat/types.js +167 -0
- package/dist/gesture-handler-compat/types.js.map +1 -0
- package/dist/gtk/controllers.d.ts +39 -0
- package/dist/gtk/controllers.js +92 -0
- package/dist/gtk/controllers.js.map +1 -0
- package/dist/gtk/index.d.ts +2 -1
- package/dist/gtk/index.js +7 -1
- package/dist/gtk/index.js.map +1 -1
- package/dist/gtkx/bridge/geometry.js +6 -4
- package/dist/gtkx/bridge/geometry.js.map +1 -1
- package/dist/gtkx/bridge/index.d.ts +5 -3
- package/dist/gtkx/bridge/index.js +6 -3
- package/dist/gtkx/bridge/index.js.map +1 -1
- package/dist/gtkx/bridge/slot-portal.js.map +1 -1
- package/dist/gtkx/bridge/use-signal.js +1 -1
- package/dist/gtkx/bridge/use-signal.js.map +1 -1
- package/dist/gtkx/bridge/view-box.d.ts +56 -0
- package/dist/gtkx/bridge/view-box.js +394 -9
- package/dist/gtkx/bridge/view-box.js.map +1 -1
- package/dist/gtkx/bridge/widget-css.d.ts +17 -0
- package/dist/gtkx/bridge/widget-css.js +72 -0
- package/dist/gtkx/bridge/widget-css.js.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layout/driven-size.d.ts +12 -0
- package/dist/layout/driven-size.js +70 -0
- package/dist/layout/driven-size.js.map +1 -0
- package/dist/layout/engine.d.ts +17 -1
- package/dist/layout/engine.js +3 -20
- package/dist/layout/engine.js.map +1 -1
- package/dist/layout/node.d.ts +13 -0
- package/dist/layout/node.js +14 -0
- package/dist/layout/node.js.map +1 -1
- package/dist/layout/yoga.d.ts +3 -2
- package/dist/layout/yoga.js +5 -2
- package/dist/layout/yoga.js.map +1 -1
- package/dist/mcp/data/generated.d.ts +257 -60
- package/dist/mcp/data/generated.js +306 -62
- package/dist/mcp/data/generated.js.map +1 -1
- package/dist/metro/index.d.ts +10 -0
- package/dist/metro/index.js +21 -12
- package/dist/metro/index.js.map +1 -1
- package/dist/navigation/sidebar.d.ts +16 -0
- package/dist/navigation/sidebar.js +65 -1
- package/dist/navigation/sidebar.js.map +1 -1
- package/dist/reanimated-compat/animated-ref.d.ts +32 -0
- package/dist/reanimated-compat/animated-ref.js +57 -0
- package/dist/reanimated-compat/animated-ref.js.map +1 -0
- package/dist/reanimated-compat/animation.d.ts +177 -0
- package/dist/reanimated-compat/animation.js +540 -0
- package/dist/reanimated-compat/animation.js.map +1 -0
- package/dist/reanimated-compat/color.d.ts +28 -0
- package/dist/reanimated-compat/color.js +249 -0
- package/dist/reanimated-compat/color.js.map +1 -0
- package/dist/reanimated-compat/decay.d.ts +39 -0
- package/dist/reanimated-compat/decay.js +96 -0
- package/dist/reanimated-compat/decay.js.map +1 -0
- package/dist/reanimated-compat/easing.d.ts +30 -0
- package/dist/reanimated-compat/easing.js +76 -0
- package/dist/reanimated-compat/easing.js.map +1 -0
- package/dist/reanimated-compat/hooks.d.ts +14 -0
- package/dist/reanimated-compat/hooks.js +217 -0
- package/dist/reanimated-compat/hooks.js.map +1 -0
- package/dist/reanimated-compat/index.d.ts +258 -0
- package/dist/reanimated-compat/index.js +464 -0
- package/dist/reanimated-compat/index.js.map +1 -0
- package/dist/reanimated-compat/interpolation.d.ts +18 -0
- package/dist/reanimated-compat/interpolation.js +122 -0
- package/dist/reanimated-compat/interpolation.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-config.d.ts +43 -0
- package/dist/reanimated-compat/layout-animation-config.js +78 -0
- package/dist/reanimated-compat/layout-animation-config.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-presets.d.ts +117 -0
- package/dist/reanimated-compat/layout-animation-presets.js +498 -0
- package/dist/reanimated-compat/layout-animation-presets.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-runtime.d.ts +21 -0
- package/dist/reanimated-compat/layout-animation-runtime.js +311 -0
- package/dist/reanimated-compat/layout-animation-runtime.js.map +1 -0
- package/dist/reanimated-compat/layout-animation-view.d.ts +27 -0
- package/dist/reanimated-compat/layout-animation-view.js +266 -0
- package/dist/reanimated-compat/layout-animation-view.js.map +1 -0
- package/dist/reanimated-compat/layout-animation.d.ts +171 -0
- package/dist/reanimated-compat/layout-animation.js +469 -0
- package/dist/reanimated-compat/layout-animation.js.map +1 -0
- package/dist/reanimated-compat/layout-transitions.d.ts +83 -0
- package/dist/reanimated-compat/layout-transitions.js +350 -0
- package/dist/reanimated-compat/layout-transitions.js.map +1 -0
- package/dist/reanimated-compat/mutable.d.ts +29 -0
- package/dist/reanimated-compat/mutable.js +139 -0
- package/dist/reanimated-compat/mutable.js.map +1 -0
- package/dist/reanimated-compat/props.d.ts +18 -0
- package/dist/reanimated-compat/props.js +111 -0
- package/dist/reanimated-compat/props.js.map +1 -0
- package/dist/reanimated-compat/scroll-handler.d.ts +119 -0
- package/dist/reanimated-compat/scroll-handler.js +274 -0
- package/dist/reanimated-compat/scroll-handler.js.map +1 -0
- package/dist/reanimated-compat/scroll-offset.d.ts +12 -0
- package/dist/reanimated-compat/scroll-offset.js +104 -0
- package/dist/reanimated-compat/scroll-offset.js.map +1 -0
- package/dist/reanimated-compat/style.d.ts +90 -0
- package/dist/reanimated-compat/style.js +452 -0
- package/dist/reanimated-compat/style.js.map +1 -0
- package/dist/reanimated-compat/test-timers.d.ts +28 -0
- package/dist/reanimated-compat/test-timers.js +136 -0
- package/dist/reanimated-compat/test-timers.js.map +1 -0
- package/dist/reanimated-compat/threads.d.ts +13 -0
- package/dist/reanimated-compat/threads.js +41 -0
- package/dist/reanimated-compat/threads.js.map +1 -0
- package/dist/reanimated-compat/tracking.d.ts +41 -0
- package/dist/reanimated-compat/tracking.js +106 -0
- package/dist/reanimated-compat/tracking.js.map +1 -0
- package/dist/reanimated-compat/updater-animations.d.ts +36 -0
- package/dist/reanimated-compat/updater-animations.js +273 -0
- package/dist/reanimated-compat/updater-animations.js.map +1 -0
- package/dist/responder/system.d.ts +80 -7
- package/dist/responder/system.js +143 -23
- package/dist/responder/system.js.map +1 -1
- package/dist/responder/use-responder.d.ts +11 -0
- package/dist/responder/use-responder.js +225 -6
- package/dist/responder/use-responder.js.map +1 -1
- package/dist/runner/host-dev.js +1 -1
- package/dist/runner/host-dev.js.map +1 -1
- package/dist/runner/host.js +1 -1
- package/dist/runner/host.js.map +1 -1
- package/dist/runner/index.js +3 -3
- package/dist/runner/index.js.map +1 -1
- package/dist/sea/gtkx-config-module.js +1 -1
- package/dist/sea/gtkx-config-module.js.map +1 -1
- package/dist/style/absolute-insets.d.ts +26 -0
- package/dist/style/absolute-insets.js +113 -0
- package/dist/style/absolute-insets.js.map +1 -0
- package/dist/style/animated-size.d.ts +25 -0
- package/dist/style/animated-size.js +225 -0
- package/dist/style/animated-size.js.map +1 -0
- package/dist/style/colors.d.ts +17 -0
- package/dist/style/colors.js +25 -6
- package/dist/style/colors.js.map +1 -1
- package/dist/style/imperative-css.d.ts +21 -0
- package/dist/style/imperative-css.js +63 -0
- package/dist/style/imperative-css.js.map +1 -0
- package/dist/style/index.d.ts +2 -1
- package/dist/style/index.js +2 -1
- package/dist/style/index.js.map +1 -1
- package/dist/style/split-style.js +5 -0
- package/dist/style/split-style.js.map +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/unsupported-export.d.ts +8 -0
- package/dist/unsupported-export.js +66 -0
- package/dist/unsupported-export.js.map +1 -0
- package/dist/vite/index.d.ts +21 -8
- package/dist/vite/index.js +127 -82
- package/dist/vite/index.js.map +1 -1
- package/dist/vitest/index.d.ts +2 -2
- package/dist/vitest/index.js.map +1 -1
- package/dist/worklets-compat/index.d.ts +4 -0
- package/dist/worklets-compat/index.js +28 -0
- package/dist/worklets-compat/index.js.map +1 -0
- package/dist/worklets-compat/surface.d.ts +110 -0
- package/dist/worklets-compat/surface.js +153 -0
- package/dist/worklets-compat/surface.js.map +1 -0
- package/package.json +25 -9
- package/types.d.ts +9 -0
- package/dist/common/list.d.ts +0 -82
- package/dist/common/list.js +0 -166
- package/dist/common/list.js.map +0 -1
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Focus, in the shape React Native already has for it.
|
|
2
|
+
//
|
|
3
|
+
// `docs/research/react-native-first-showcase.md` listed focus STATE as one
|
|
4
|
+
// of the three things no style closes: `outlineWidth` and friends made the
|
|
5
|
+
// ring drawable, but nothing told a `View` it was focused, so the ring could
|
|
6
|
+
// never be asked for. Nothing here is invented to fix that — RN has all of
|
|
7
|
+
// the surface already, on the platforms that have a focus model:
|
|
8
|
+
//
|
|
9
|
+
// - `focusable` is on RN's own `View` (Android, Windows);
|
|
10
|
+
// - `onFocus`/`onBlur` are on `View` in react-native-web and
|
|
11
|
+
// react-native-windows (RN core has them on TextInput and the touchables);
|
|
12
|
+
// - react-native-web's `Pressable` state callback is
|
|
13
|
+
// `{focused, hovered, pressed}` — this platform already added `hovered`
|
|
14
|
+
// to that callback for the same reason, so `focused` is the same move.
|
|
15
|
+
//
|
|
16
|
+
// The second half of the finding — "RN has no focus-traversal model on the
|
|
17
|
+
// desktop at all" — needs no model of ours either. `gtk_widget_set_focusable`
|
|
18
|
+
// puts the widget into GTK's focus chain, and GTK's directional keynav then
|
|
19
|
+
// moves focus between focusable siblings with Tab and the arrow keys. That
|
|
20
|
+
// is precisely the behaviour `GtkListBox` was supplying to `AdwActionRow`s,
|
|
21
|
+
// obtained by making the boxes focusable rather than by reimplementing it.
|
|
22
|
+
import { useLayoutEffect, useRef } from "react";
|
|
23
|
+
import { Gtk } from "../gtkx/bridge/index";
|
|
24
|
+
/**
|
|
25
|
+
* Reports GTK focus enter/leave as RN's `onFocus`/`onBlur`.
|
|
26
|
+
*
|
|
27
|
+
* Attached imperatively even though rc.3 has a declarative `controllers`
|
|
28
|
+
* slot: one wiring per widget, handlers read from a ref, so changing
|
|
29
|
+
* `onFocus`/`onBlur` never re-creates the controller mid-interaction — the
|
|
30
|
+
* same reasoning as `Pressable`'s gestures.
|
|
31
|
+
*/
|
|
32
|
+
export const useFocusController = (widgetRef, onFocus, onBlur) => {
|
|
33
|
+
// Written during render on purpose: the effect below runs once and must
|
|
34
|
+
// see the latest handlers, and there is no commit between this render and
|
|
35
|
+
// a focus signal that could deliver them any other way.
|
|
36
|
+
const handlers = useRef({ onFocus, onBlur });
|
|
37
|
+
handlers.current = { onFocus, onBlur };
|
|
38
|
+
useLayoutEffect(() => {
|
|
39
|
+
const widget = widgetRef.current;
|
|
40
|
+
if (!widget) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const focus = new Gtk.EventControllerFocus();
|
|
44
|
+
focus.on("enter", () => handlers.current.onFocus?.());
|
|
45
|
+
focus.on("leave", () => handlers.current.onBlur?.());
|
|
46
|
+
widget.addController(focus);
|
|
47
|
+
return () => {
|
|
48
|
+
widget.removeController(focus);
|
|
49
|
+
};
|
|
50
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
51
|
+
}, []);
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* RN's `focusable`: puts the widget into GTK's focus chain (or takes it
|
|
55
|
+
* out), which is what makes Tab and the arrow keys reach it.
|
|
56
|
+
*/
|
|
57
|
+
export const useFocusable = (widgetRef, focusable) => {
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
|
+
const widget = widgetRef.current;
|
|
60
|
+
if (!widget) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// `focusable`, NOT `can-focus`. The two read alike and are not: GTK4's
|
|
64
|
+
// `can-focus` governs the whole SUBTREE (it defaults to true, and
|
|
65
|
+
// clearing it would make every descendant unreachable too), while
|
|
66
|
+
// `focusable` is about this widget alone — which is what RN's prop
|
|
67
|
+
// means.
|
|
68
|
+
widget.setFocusable(focusable);
|
|
69
|
+
}, [widgetRef, focusable]);
|
|
70
|
+
};
|
|
71
|
+
// GDK keyvals for the two keys that activate a focused control everywhere
|
|
72
|
+
// RN runs (gdk/gdkkeysyms.h). Spelled out rather than imported: gtkx binds
|
|
73
|
+
// no keysym table, and three constants do not justify one.
|
|
74
|
+
const KEY_RETURN = 0xff_0d;
|
|
75
|
+
const KEY_KP_ENTER = 0xff_8d;
|
|
76
|
+
const KEY_SPACE = 0x20;
|
|
77
|
+
/**
|
|
78
|
+
* Enter/Space on a focused widget, reported through `onActivate`.
|
|
79
|
+
*
|
|
80
|
+
* Without this `focusable` would be decorative: a control you can Tab to
|
|
81
|
+
* and not operate is worse than one you cannot reach. Web and Android both
|
|
82
|
+
* fire a press for these two keys, so a `Pressable` here does too.
|
|
83
|
+
*
|
|
84
|
+
* Returns `true` from the GTK handler when it acted, so the key does not
|
|
85
|
+
* also travel on to a parent's own bindings.
|
|
86
|
+
*/
|
|
87
|
+
export const useActivateOnKey = (widgetRef, onActivate) => {
|
|
88
|
+
const handler = useRef(onActivate);
|
|
89
|
+
handler.current = onActivate;
|
|
90
|
+
useLayoutEffect(() => {
|
|
91
|
+
const widget = widgetRef.current;
|
|
92
|
+
if (!widget) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const keys = new Gtk.EventControllerKey();
|
|
96
|
+
keys.on("key-pressed", (keyval) => {
|
|
97
|
+
if (keyval !== KEY_RETURN &&
|
|
98
|
+
keyval !== KEY_KP_ENTER &&
|
|
99
|
+
keyval !== KEY_SPACE) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const activate = handler.current;
|
|
103
|
+
if (!activate) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
activate();
|
|
107
|
+
return true;
|
|
108
|
+
});
|
|
109
|
+
widget.addController(keys);
|
|
110
|
+
return () => {
|
|
111
|
+
widget.removeController(keys);
|
|
112
|
+
};
|
|
113
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
114
|
+
}, []);
|
|
115
|
+
};
|
|
116
|
+
//# sourceMappingURL=use-focus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-focus.js","sourceRoot":"","sources":["../../src/components/use-focus.ts"],"names":[],"mappings":"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","sourcesContent":["// Focus, in the shape React Native already has for it.\n//\n// `docs/research/react-native-first-showcase.md` listed focus STATE as one\n// of the three things no style closes: `outlineWidth` and friends made the\n// ring drawable, but nothing told a `View` it was focused, so the ring could\n// never be asked for. Nothing here is invented to fix that — RN has all of\n// the surface already, on the platforms that have a focus model:\n//\n// - `focusable` is on RN's own `View` (Android, Windows);\n// - `onFocus`/`onBlur` are on `View` in react-native-web and\n// react-native-windows (RN core has them on TextInput and the touchables);\n// - react-native-web's `Pressable` state callback is\n// `{focused, hovered, pressed}` — this platform already added `hovered`\n// to that callback for the same reason, so `focused` is the same move.\n//\n// The second half of the finding — \"RN has no focus-traversal model on the\n// desktop at all\" — needs no model of ours either. `gtk_widget_set_focusable`\n// puts the widget into GTK's focus chain, and GTK's directional keynav then\n// moves focus between focusable siblings with Tab and the arrow keys. That\n// is precisely the behaviour `GtkListBox` was supplying to `AdwActionRow`s,\n// obtained by making the boxes focusable rather than by reimplementing it.\nimport { useLayoutEffect, useRef, type RefObject } from \"react\"\nimport { Gtk } from \"../gtkx/bridge/index\"\n\n/**\n * Reports GTK focus enter/leave as RN's `onFocus`/`onBlur`.\n *\n * Attached imperatively even though rc.3 has a declarative `controllers`\n * slot: one wiring per widget, handlers read from a ref, so changing\n * `onFocus`/`onBlur` never re-creates the controller mid-interaction — the\n * same reasoning as `Pressable`'s gestures.\n */\nexport const useFocusController = (\n widgetRef: RefObject<Gtk.Widget | null>,\n onFocus?: () => void,\n onBlur?: () => void,\n): void => {\n // Written during render on purpose: the effect below runs once and must\n // see the latest handlers, and there is no commit between this render and\n // a focus signal that could deliver them any other way.\n const handlers = useRef({ onFocus, onBlur })\n handlers.current = { onFocus, onBlur }\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const focus = new Gtk.EventControllerFocus()\n focus.on(\"enter\", () => handlers.current.onFocus?.())\n focus.on(\"leave\", () => handlers.current.onBlur?.())\n widget.addController(focus)\n return () => {\n widget.removeController(focus)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n}\n\n/**\n * RN's `focusable`: puts the widget into GTK's focus chain (or takes it\n * out), which is what makes Tab and the arrow keys reach it.\n */\nexport const useFocusable = (\n widgetRef: RefObject<Gtk.Widget | null>,\n focusable: boolean,\n): void => {\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n // `focusable`, NOT `can-focus`. The two read alike and are not: GTK4's\n // `can-focus` governs the whole SUBTREE (it defaults to true, and\n // clearing it would make every descendant unreachable too), while\n // `focusable` is about this widget alone — which is what RN's prop\n // means.\n widget.setFocusable(focusable)\n }, [widgetRef, focusable])\n}\n\n// GDK keyvals for the two keys that activate a focused control everywhere\n// RN runs (gdk/gdkkeysyms.h). Spelled out rather than imported: gtkx binds\n// no keysym table, and three constants do not justify one.\nconst KEY_RETURN = 0xff_0d\nconst KEY_KP_ENTER = 0xff_8d\nconst KEY_SPACE = 0x20\n\n/**\n * Enter/Space on a focused widget, reported through `onActivate`.\n *\n * Without this `focusable` would be decorative: a control you can Tab to\n * and not operate is worse than one you cannot reach. Web and Android both\n * fire a press for these two keys, so a `Pressable` here does too.\n *\n * Returns `true` from the GTK handler when it acted, so the key does not\n * also travel on to a parent's own bindings.\n */\nexport const useActivateOnKey = (\n widgetRef: RefObject<Gtk.Widget | null>,\n onActivate?: () => void,\n): void => {\n const handler = useRef(onActivate)\n handler.current = onActivate\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n const keys = new Gtk.EventControllerKey()\n keys.on(\"key-pressed\", (keyval: number) => {\n if (\n keyval !== KEY_RETURN &&\n keyval !== KEY_KP_ENTER &&\n keyval !== KEY_SPACE\n ) {\n return false\n }\n const activate = handler.current\n if (!activate) {\n return false\n }\n activate()\n return true\n })\n widget.addController(keys)\n return () => {\n widget.removeController(keys)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import { type RefObject } from "react";
|
|
|
2
2
|
import type { LayoutNode } from "../layout/index";
|
|
3
3
|
import { StyleSheet } from "../style/index";
|
|
4
4
|
import type { LayoutStyle, MeasureFn, StyleProp, VisualStyle } from "../contracts";
|
|
5
|
-
import {
|
|
5
|
+
import { Gtk } from "../gtkx/bridge/index";
|
|
6
6
|
import { type HostNode } from "./host-node";
|
|
7
7
|
export type LayoutEvent = {
|
|
8
8
|
nativeEvent: {
|
|
@@ -20,6 +20,7 @@ export type LayoutChildOptions = {
|
|
|
20
20
|
measure?: MeasureFn;
|
|
21
21
|
measureFromWidget?: boolean;
|
|
22
22
|
extraLayout?: LayoutStyle;
|
|
23
|
+
alwaysClips?: boolean;
|
|
23
24
|
};
|
|
24
25
|
export type LayoutChild = {
|
|
25
26
|
host: HostNode;
|
|
@@ -28,6 +29,17 @@ export type LayoutChild = {
|
|
|
28
29
|
visual: VisualStyle;
|
|
29
30
|
cssClass: string | null;
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* The shadow-tree node laying out this widget, or undefined for a widget that
|
|
34
|
+
* is not a layout child of any container (a nested layout root, a raw GTK
|
|
35
|
+
* widget in a slot).
|
|
36
|
+
*
|
|
37
|
+
* The reverse of what `syncChildOrder` uses it for, and the same map: an
|
|
38
|
+
* animated size has to walk the driven node's DESCENDANTS, which it can only
|
|
39
|
+
* reach through the widget tree, and pairing each widget back to its node is
|
|
40
|
+
* what keeps that walk honest about which children belong to which container.
|
|
41
|
+
*/
|
|
42
|
+
export declare const nodeForWidget: (widget: Gtk.Widget) => LayoutNode | undefined;
|
|
31
43
|
export declare const useLayoutChild: (widgetRef: RefObject<Gtk.Widget | null>, options: LayoutChildOptions) => LayoutChild;
|
|
32
44
|
export type RnContainerOptions = {
|
|
33
45
|
measure?: (orientation: "horizontal" | "vertical", forSize: number) => number;
|
|
@@ -1,15 +1,96 @@
|
|
|
1
1
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { splitStyle, StyleSheet } from "../style/index";
|
|
3
3
|
import { defaultCssRegistry } from "../style/registry.gtkx";
|
|
4
|
-
import { allocateChild, attachRnLayout, detachRnLayout, measureWidget, queueAllocate, queueResize, } from "../gtkx/bridge/index";
|
|
4
|
+
import { allocateChild, attachRnLayout, cacheChildOrder, detachRnLayout, Gtk, invalidateZOrder, measureWidget, queueAllocate, queueResize, setZIndex, } from "../gtkx/bridge/index";
|
|
5
5
|
import { perfAddTime, perfBurst, perfCount, perfEnabled, perfNow, } from "../perf";
|
|
6
6
|
import { useHostNode } from "./host-node";
|
|
7
|
-
import { deferDuringAllocate, getStoredOffset, getStoredRect, setStoredRect, setStoredTransform, } from "./rect-store";
|
|
7
|
+
import { deferDuringAllocate, getStoredDrivenBox, getStoredOffset, getStoredRect, setStoredRect, setStoredTransform, } from "./rect-store";
|
|
8
|
+
import { deferUntilReleased } from "./widget-retention";
|
|
8
9
|
// Perf: sizeAllocate recurses synchronously into child containers, so a
|
|
9
10
|
// naive per-pass timer double-counts nested passes. Track depth and time
|
|
10
11
|
// only the outermost (top-level) allocate — that is the real wall time GTK
|
|
11
12
|
// spends in OUR layout code for the frame.
|
|
12
13
|
let perfAllocDepth = 0;
|
|
14
|
+
// RN's `overflow` onto GTK's. It is the one style that has to reach BOTH
|
|
15
|
+
// halves of the pipeline: Yoga reads it while measuring (see
|
|
16
|
+
// layout/apply-style.ts, which keeps setting it on the node), and the widget
|
|
17
|
+
// needs it to clip paint and picking — GTK4 CSS has no `overflow` property,
|
|
18
|
+
// so unlike every other visual style this one is a widget call rather than a
|
|
19
|
+
// declaration.
|
|
20
|
+
//
|
|
21
|
+
// `scroll` clips exactly like `hidden`, which is what RN itself does: a
|
|
22
|
+
// `View` is not made scrollable by a style on either platform (iOS sets
|
|
23
|
+
// `clipsToBounds` for any non-visible overflow, Android's ReactViewGroup
|
|
24
|
+
// clips for SCROLL and HIDDEN alike) — only a `ScrollView` scrolls. Mapping
|
|
25
|
+
// it to VISIBLE instead would be the silent lie this replaces.
|
|
26
|
+
const WIDGET_OVERFLOW = {
|
|
27
|
+
visible: Gtk.Overflow.VISIBLE,
|
|
28
|
+
hidden: Gtk.Overflow.HIDDEN,
|
|
29
|
+
scroll: Gtk.Overflow.HIDDEN,
|
|
30
|
+
};
|
|
31
|
+
// Which Yoga node lays out which widget. The reconciler talks in widgets and
|
|
32
|
+
// the shadow tree talks in nodes, and syncChildOrder below is the one place
|
|
33
|
+
// that has to translate between them.
|
|
34
|
+
const nodesByWidget = new WeakMap();
|
|
35
|
+
/**
|
|
36
|
+
* The shadow-tree node laying out this widget, or undefined for a widget that
|
|
37
|
+
* is not a layout child of any container (a nested layout root, a raw GTK
|
|
38
|
+
* widget in a slot).
|
|
39
|
+
*
|
|
40
|
+
* The reverse of what `syncChildOrder` uses it for, and the same map: an
|
|
41
|
+
* animated size has to walk the driven node's DESCENDANTS, which it can only
|
|
42
|
+
* reach through the widget tree, and pairing each widget back to its node is
|
|
43
|
+
* what keeps that walk honest about which children belong to which container.
|
|
44
|
+
*/
|
|
45
|
+
export const nodeForWidget = (widget) => nodesByWidget.get(widget);
|
|
46
|
+
/**
|
|
47
|
+
* Puts a container's shadow tree back into its widgets' order.
|
|
48
|
+
*
|
|
49
|
+
* WHY this is needed, and why insertion order alone was not enough. A child
|
|
50
|
+
* picks its Yoga index once, on mount, from where the reconciler put its
|
|
51
|
+
* widget — which is right for a child that APPEARS mid-list, and blind to a
|
|
52
|
+
* child that MOVES. React reorders keyed siblings by moving the existing
|
|
53
|
+
* fibers, so nothing mounts and nothing unmounts: the widgets end up in the
|
|
54
|
+
* new order and the Yoga nodes stay in the old one, and since the rects come
|
|
55
|
+
* from the nodes, the rows redraw exactly where they were. Every list that
|
|
56
|
+
* can be sorted, filtered into a different order, or dragged into one was
|
|
57
|
+
* silently affected — found by dragging a row in `examples/tasks-nav` after
|
|
58
|
+
* its rows were rewritten in React Native (a `GtkListBox` was doing its own
|
|
59
|
+
* layout before, so nothing had ever exercised this).
|
|
60
|
+
*
|
|
61
|
+
* Runs after every commit of the container, and does nothing at all unless
|
|
62
|
+
* the two orders actually disagree — one pointer comparison per child.
|
|
63
|
+
*/
|
|
64
|
+
const syncChildOrder = (parentWidget, parent) => {
|
|
65
|
+
const ordered = [];
|
|
66
|
+
let moved = false;
|
|
67
|
+
let child = parentWidget.getFirstChild();
|
|
68
|
+
while (child !== null) {
|
|
69
|
+
const node = nodesByWidget.get(child);
|
|
70
|
+
// Widgets that are not layout children of THIS container are skipped in
|
|
71
|
+
// both orders: a nested layout root, or a raw GTK widget a slot holds.
|
|
72
|
+
if (node !== undefined && node.parent === parent) {
|
|
73
|
+
if (parent.children[ordered.length] !== node) {
|
|
74
|
+
moved = true;
|
|
75
|
+
}
|
|
76
|
+
ordered.push(node);
|
|
77
|
+
}
|
|
78
|
+
child = child.getNextSibling();
|
|
79
|
+
}
|
|
80
|
+
// A disagreement in COUNT is not a reorder — it means a child is
|
|
81
|
+
// mid-mount, or something in this container lays itself out. Re-sorting
|
|
82
|
+
// against a partial view would be worse than leaving it alone.
|
|
83
|
+
if (!moved || ordered.length !== parent.children.length) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
for (let index = 0; index < ordered.length; index += 1) {
|
|
87
|
+
const node = ordered[index];
|
|
88
|
+
if (parent.children[index] !== node) {
|
|
89
|
+
parent.removeChild(node);
|
|
90
|
+
parent.insertChild(node, index);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
13
94
|
// Shared plumbing for every leaf/container component: owns one LayoutNode,
|
|
14
95
|
// keeps it in the parent's shadow tree for the component lifetime, applies
|
|
15
96
|
// the layout half of the style and commits Yoga rects into the rect store,
|
|
@@ -32,6 +113,43 @@ export const useLayoutChild = (widgetRef, options) => {
|
|
|
32
113
|
node.setStyle(JSON.parse(layoutKey));
|
|
33
114
|
}, [node, layoutKey]);
|
|
34
115
|
const cssClass = defaultCssRegistry.getClassName(visual);
|
|
116
|
+
// The GTK half of `overflow`. Yoga has had it since the beginning and
|
|
117
|
+
// nothing reached the widget, so the style was accepted and painted
|
|
118
|
+
// nothing — `overflow: "hidden"` on a container was a no-op. GTK clips to
|
|
119
|
+
// the CSS padding box, so `borderRadius` shapes the clip for free (a
|
|
120
|
+
// rounded-clip node rather than a rectangular one) and both paint and
|
|
121
|
+
// `gtk_widget_pick()` follow it.
|
|
122
|
+
const overflow = options.alwaysClips
|
|
123
|
+
? "hidden"
|
|
124
|
+
: (flat?.overflow ?? "visible");
|
|
125
|
+
useLayoutEffect(() => {
|
|
126
|
+
widgetRef.current?.setOverflow(WIDGET_OVERFLOW[overflow]);
|
|
127
|
+
}, [widgetRef, overflow]);
|
|
128
|
+
// RN's `zIndex`, which is neither a Yoga input nor a CSS declaration: the
|
|
129
|
+
// PARENT container sorts its snapshot and its picking by it
|
|
130
|
+
// (gtkx/bridge/view-box.ts). Applied unconditionally, as RN does — CSS
|
|
131
|
+
// needs a non-static `position` for `z-index` to take effect and RN does
|
|
132
|
+
// not, so nothing here consults `position`. `undefined` is 0, and negative
|
|
133
|
+
// values are legal (they paint below unraised siblings).
|
|
134
|
+
const zIndex = flat?.zIndex ?? 0;
|
|
135
|
+
useLayoutEffect(() => {
|
|
136
|
+
const widget = widgetRef.current;
|
|
137
|
+
if (!widget) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setZIndex(widget, zIndex);
|
|
141
|
+
return () => {
|
|
142
|
+
// Cleared before the widget leaves the tree, so the container that is
|
|
143
|
+
// losing it drops its cached order and the process-wide "anything
|
|
144
|
+
// raised at all" counter comes back down to zero.
|
|
145
|
+
setZIndex(widget, 0);
|
|
146
|
+
const parentWidget = host.widgetRef.current;
|
|
147
|
+
if (parentWidget) {
|
|
148
|
+
invalidateZOrder(parentWidget);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
152
|
+
}, [zIndex]);
|
|
35
153
|
// RN transforms are visual only: the widget keeps the box Yoga gave it, so
|
|
36
154
|
// a transform never touches the shadow tree — it only asks the parent
|
|
37
155
|
// container to run its allocate hook again. Keyed on the serialized array
|
|
@@ -59,6 +177,16 @@ export const useLayoutChild = (widgetRef, options) => {
|
|
|
59
177
|
const widget = widgetRef.current;
|
|
60
178
|
const parentWidget = host.widgetRef.current;
|
|
61
179
|
let index = parent.children.length;
|
|
180
|
+
if (widget) {
|
|
181
|
+
// Published so the container can put the shadow tree back into widget
|
|
182
|
+
// order after React has MOVED an existing child — see syncChildOrder.
|
|
183
|
+
// Outside the `parentWidget` guard below on purpose: on a first mount
|
|
184
|
+
// the PARENT's ref is not attached yet (React attaches host refs
|
|
185
|
+
// bottom-up, so a child's layout effect runs before its container's
|
|
186
|
+
// widget exists), and a registration skipped there would never happen
|
|
187
|
+
// again — the effect runs once.
|
|
188
|
+
nodesByWidget.set(widget, node);
|
|
189
|
+
}
|
|
62
190
|
if (widget && parentWidget) {
|
|
63
191
|
let position = 0;
|
|
64
192
|
let sibling = parentWidget.getFirstChild();
|
|
@@ -122,7 +250,19 @@ export const useLayoutChild = (widgetRef, options) => {
|
|
|
122
250
|
}
|
|
123
251
|
});
|
|
124
252
|
return () => {
|
|
253
|
+
// `widget`, not `widgetRef.current`: by cleanup time the ref may
|
|
254
|
+
// already have been detached, and the entry to drop is the one this
|
|
255
|
+
// effect registered.
|
|
256
|
+
if (widget) {
|
|
257
|
+
nodesByWidget.delete(widget);
|
|
258
|
+
}
|
|
125
259
|
node.setCommit(null);
|
|
260
|
+
// Removed from the shadow tree and freed even when the widget is being
|
|
261
|
+
// RETAINED for an exit animation (see ./widget-retention): the siblings
|
|
262
|
+
// must close the gap immediately — an exiting widget does not hold its
|
|
263
|
+
// space, it draws over the space closing behind it — and the rect its
|
|
264
|
+
// container keeps reporting afterwards is `lastRect`, a plain JS value
|
|
265
|
+
// that outlives the Yoga node it came from.
|
|
126
266
|
parent.removeChild(node);
|
|
127
267
|
node.free();
|
|
128
268
|
};
|
|
@@ -193,6 +333,22 @@ export const useLayoutChild = (widgetRef, options) => {
|
|
|
193
333
|
export const useRnContainer = (widgetRef, node, options) => {
|
|
194
334
|
const optionsRef = useRef(options);
|
|
195
335
|
optionsRef.current = options;
|
|
336
|
+
// Deliberately no dependency array: a MOVE mounts nothing and unmounts
|
|
337
|
+
// nothing, so there is no other commit this could key off. Children's
|
|
338
|
+
// layout effects run before their container's, so by the time this fires
|
|
339
|
+
// every child of this commit has registered its node. See syncChildOrder.
|
|
340
|
+
useLayoutEffect(() => {
|
|
341
|
+
const widget = widgetRef.current;
|
|
342
|
+
if (widget) {
|
|
343
|
+
syncChildOrder(widget, node);
|
|
344
|
+
// The paint order caches a child list, and a commit is the only thing
|
|
345
|
+
// that can change one without any zIndex being written — a mount, an
|
|
346
|
+
// unmount, or a MOVE. Dropping it here is a WeakMap delete on a
|
|
347
|
+
// container that has a raised child and nothing at all on one that does
|
|
348
|
+
// not (`invalidateZOrder` only redraws when there was a cache to drop).
|
|
349
|
+
invalidateZOrder(widget);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
196
352
|
useLayoutEffect(() => {
|
|
197
353
|
const widget = widgetRef.current;
|
|
198
354
|
if (!widget) {
|
|
@@ -204,8 +360,16 @@ export const useRnContainer = (widgetRef, node, options) => {
|
|
|
204
360
|
if (custom) {
|
|
205
361
|
return custom(orientation, forSize);
|
|
206
362
|
}
|
|
363
|
+
// A driven size wins over the committed one here as well as in the
|
|
364
|
+
// parent's allocate, so the container asks GTK for the box it is
|
|
365
|
+
// actually being given. Otherwise a size animated DOWNWARD would
|
|
366
|
+
// under-allocate a widget against its own stated minimum every frame.
|
|
207
367
|
const rect = node.getRect();
|
|
208
|
-
|
|
368
|
+
const driven = getStoredDrivenBox(widget);
|
|
369
|
+
const size = orientation === "horizontal"
|
|
370
|
+
? (driven?.width ?? rect?.width)
|
|
371
|
+
: (driven?.height ?? rect?.height);
|
|
372
|
+
return Math.round(size ?? 0);
|
|
209
373
|
},
|
|
210
374
|
allocate: (width, height) => {
|
|
211
375
|
const start = perfEnabled ? perfNow() : 0;
|
|
@@ -213,15 +377,28 @@ export const useRnContainer = (widgetRef, node, options) => {
|
|
|
213
377
|
optionsRef.current?.beforeAllocate?.(width, height);
|
|
214
378
|
let child = widget.getFirstChild();
|
|
215
379
|
let children = 0;
|
|
380
|
+
// Collected while this loop walks the sibling chain anyway, and handed
|
|
381
|
+
// to the paint pass, which would otherwise have to walk it again: each
|
|
382
|
+
// getNextSibling() mints a native wrapper and is the single most
|
|
383
|
+
// expensive thing a JS snapshot vfunc does (docs/research/z-index.md).
|
|
384
|
+
const order = [];
|
|
216
385
|
while (child) {
|
|
386
|
+
order.push(child);
|
|
217
387
|
const rect = getStoredRect(child);
|
|
218
388
|
if (rect) {
|
|
219
389
|
const offset = getStoredOffset(child);
|
|
220
|
-
|
|
390
|
+
// The engine's rect, with whatever an animation is currently
|
|
391
|
+
// driving laid over it field by field: the animated node
|
|
392
|
+
// overrides only the axis being driven, its descendants override
|
|
393
|
+
// the whole rect because re-laying the subtree out is what
|
|
394
|
+
// decides them. The transform composes on top either way.
|
|
395
|
+
const driven = offset.driven;
|
|
396
|
+
allocateChild(child, (driven?.x ?? rect.x) + offset.dx, (driven?.y ?? rect.y) + offset.dy, driven?.width ?? rect.width, driven?.height ?? rect.height, offset.matrix);
|
|
221
397
|
children += 1;
|
|
222
398
|
}
|
|
223
399
|
child = child.getNextSibling();
|
|
224
400
|
}
|
|
401
|
+
cacheChildOrder(widget, order);
|
|
225
402
|
perfAllocDepth -= 1;
|
|
226
403
|
if (perfEnabled) {
|
|
227
404
|
perfCount("gtk.allocPass");
|
|
@@ -235,7 +412,13 @@ export const useRnContainer = (widgetRef, node, options) => {
|
|
|
235
412
|
},
|
|
236
413
|
});
|
|
237
414
|
return () => {
|
|
238
|
-
|
|
415
|
+
// Deferred inside a retained subtree for the same reason the Yoga node
|
|
416
|
+
// is: without its RnGtkxLayout the container falls back to GtkBox's own
|
|
417
|
+
// layout and the children of a widget that is still visible re-stack
|
|
418
|
+
// themselves mid-animation.
|
|
419
|
+
if (!deferUntilReleased(widget, () => detachRnLayout(widget))) {
|
|
420
|
+
detachRnLayout(widget);
|
|
421
|
+
}
|
|
239
422
|
};
|
|
240
423
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
241
424
|
}, []);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-layout-child.js","sourceRoot":"","sources":["../../src/components/use-layout-child.ts"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type RefObject } from \"react\"\nimport type { LayoutNode } from \"../layout/index\"\nimport { splitStyle, StyleSheet } from \"../style/index\"\nimport { defaultCssRegistry } from \"../style/registry.gtkx\"\nimport type {\n LayoutStyle,\n MeasureFn,\n Rect,\n StyleProp,\n TransformPart,\n VisualStyle,\n} from \"../contracts\"\nimport {\n allocateChild,\n attachRnLayout,\n detachRnLayout,\n measureWidget,\n queueAllocate,\n queueResize,\n type Gtk,\n} from \"../gtkx/bridge/index\"\nimport {\n perfAddTime,\n perfBurst,\n perfCount,\n perfEnabled,\n perfNow,\n} from \"../perf\"\nimport { useHostNode, type HostNode } from \"./host-node\"\nimport {\n deferDuringAllocate,\n getStoredOffset,\n getStoredRect,\n setStoredRect,\n setStoredTransform,\n} from \"./rect-store\"\n\nexport type LayoutEvent = {\n nativeEvent: {\n layout: { x: number; y: number; width: number; height: number }\n }\n}\n\n// Perf: sizeAllocate recurses synchronously into child containers, so a\n// naive per-pass timer double-counts nested passes. Track depth and time\n// only the outermost (top-level) allocate — that is the real wall time GTK\n// spends in OUR layout code for the frame.\nlet perfAllocDepth = 0\n\nexport type LayoutChildOptions = {\n style: StyleProp | undefined\n onLayout: ((event: LayoutEvent) => void) | undefined\n measure?: MeasureFn\n // Leaves with intrinsic GTK sizes (Entry, Switch, Spinner) measure their\n // own widget so Yoga reserves exactly what the theme will draw.\n measureFromWidget?: boolean\n extraLayout?: LayoutStyle\n}\n\nexport type LayoutChild = {\n host: HostNode\n node: LayoutNode\n flat: ReturnType<typeof StyleSheet.flatten>\n visual: VisualStyle\n cssClass: string | null\n}\n\n// Shared plumbing for every leaf/container component: owns one LayoutNode,\n// keeps it in the parent's shadow tree for the component lifetime, applies\n// the layout half of the style and commits Yoga rects into the rect store,\n// where the parent container's RnGtkxLayout allocate() picks them up.\nexport const useLayoutChild = (\n widgetRef: RefObject<Gtk.Widget | null>,\n options: LayoutChildOptions,\n): LayoutChild => {\n const host = useHostNode()\n\n // useState's lazy initializer is guaranteed once-per-instance even under\n // the React Compiler — a lazy ref here got its creation memoized ACROSS\n // component instances, sharing one Yoga node and double-freeing it.\n const [node] = useState<LayoutNode>(() => host.engine.createNode())\n\n const flat = StyleSheet.flatten(options.style)\n const { layout, visual } = splitStyle(flat)\n const effectiveLayout = options.extraLayout\n ? { ...layout, ...options.extraLayout }\n : layout\n\n // Re-apply layout style only when it actually changed (keyed effect) —\n // setStyle dirties the whole tree.\n const layoutKey = JSON.stringify(effectiveLayout)\n useLayoutEffect(() => {\n node.setStyle(JSON.parse(layoutKey) as LayoutStyle)\n }, [node, layoutKey])\n\n const cssClass = defaultCssRegistry.getClassName(visual)\n\n // RN transforms are visual only: the widget keeps the box Yoga gave it, so\n // a transform never touches the shadow tree — it only asks the parent\n // container to run its allocate hook again. Keyed on the serialized array\n // so a re-render with the same transform costs nothing.\n // (Animated.View owns its own transform and strips it from the static\n // style before it gets here, so the two writers never race.)\n const transformKey = JSON.stringify(visual.transform ?? null)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n setStoredTransform(widget, JSON.parse(transformKey) as TransformPart[])\n const parentWidget = host.widgetRef.current\n if (parentWidget) {\n queueAllocate(parentWidget)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [transformKey])\n\n useLayoutEffect(() => {\n const parent = host.node\n // The reconciler has already attached our widget in JSX order, so its\n // sibling position is the correct Yoga index — this keeps the shadow tree\n // ordered even for mid-list mounts (conditional children, list inserts).\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n let index = parent.children.length\n if (widget && parentWidget) {\n let position = 0\n let sibling = parentWidget.getFirstChild()\n while (sibling !== null && sibling !== widget) {\n position += 1\n sibling = sibling.getNextSibling()\n }\n if (sibling === widget) {\n index = Math.min(position, parent.children.length)\n }\n }\n parent.insertChild(node, index)\n node.setCommit((rect: Rect) => {\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n if (!widget || !parentWidget) {\n return\n }\n const previous = getStoredRect(widget)\n // A commit does not imply a change. The engine may hand us the rect a\n // widget already has — nodes carrying a measure function are visited\n // even when Yoga did not move them — and queueing an allocation for\n // those makes GTK re-allocate and re-snapshot the container for nothing.\n //\n // `rect.skip` counts how often this guard actually fires and\n // `rect.change` how often it does not, because a guard nobody can see\n // firing is a guard nobody can attribute a measurement to: this one was\n // once credited with a large maximized-window win that a later, properly\n // geometry-controlled run could not reproduce (docs/research/\n // scroll-performance.md, round six). Keep it — it is correct and free —\n // but read the counter before crediting it with anything.\n const unchanged =\n previous !== undefined &&\n previous.x === rect.x &&\n previous.y === rect.y &&\n previous.width === rect.width &&\n previous.height === rect.height\n if (perfEnabled) {\n perfCount(unchanged ? \"rect.skip\" : \"rect.change\")\n }\n if (unchanged) {\n return\n }\n setStoredRect(widget, rect)\n const sizeChanged =\n !previous ||\n previous.width !== rect.width ||\n previous.height !== rect.height\n const queue = (): void => {\n if (sizeChanged) {\n // A size change invalidates this widget's cached measure (nested\n // managers, ScrolledWindow ranges) and re-allocates the ancestors.\n queueResize(widget)\n } else {\n // Pure move: the parent just needs another allocation pass. GTK\n // dedupes queued allocates, so per-child calls batch into one.\n queueAllocate(parentWidget)\n }\n }\n if (!deferDuringAllocate(widget, queue)) {\n queue()\n }\n })\n return () => {\n node.setCommit(null)\n parent.removeChild(node)\n node.free()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const onLayout = options.onLayout\n useLayoutEffect(() => {\n node.setOnLayout(\n onLayout\n ? (rect: Rect) => {\n onLayout({ nativeEvent: { layout: rect } })\n }\n : null,\n )\n }, [node, onLayout])\n\n // The explicit return type keeps the inner params contextually typed: with a\n // bare lazy initializer TS may match the outer arrow against MeasureFn\n // itself (useState accepts S | () => S) and drop the context.\n const [widgetMeasure] = useState<MeasureFn>(\n (): MeasureFn => (width, widthMode) => {\n const widget = widgetRef.current\n if (!widget) {\n return { width: 0, height: 0 }\n }\n const { minimum, natural } = measureWidget(widget, \"horizontal\")\n // Floor at the widget's own minimum width, not at 1 — see the same\n // clamp in text.tsx's measure for why: gtk_widget_measure() enforces\n // this floor internally regardless of what we request (warning while\n // it does), so anything below `minimum` was never actually honored.\n const used =\n widthMode === \"undefined\"\n ? natural\n : Math.min(natural, Math.max(minimum, Math.floor(width)))\n return {\n width: used,\n height: measureWidget(widget, \"vertical\", used).natural,\n }\n },\n )\n const measure =\n options.measure ?? (options.measureFromWidget ? widgetMeasure : undefined)\n useLayoutEffect(() => {\n if (!measure) {\n return\n }\n node.setMeasureFn(measure)\n // Widgets measured before their window is mapped report zero (no realized\n // theme context yet, unlike offscreen probes) and Yoga caches it —\n // invalidate once the widget actually maps.\n const widget = widgetRef.current\n const remeasure = (): void => {\n node.markDirty()\n }\n if (options.measureFromWidget && widget) {\n widget.on(\"map\", remeasure)\n }\n return () => {\n if (options.measureFromWidget && widget) {\n widget.off(\"map\", remeasure)\n }\n // Cleared by free() on unmount; explicit unset only when measure is\n // removed while mounted.\n node.setMeasureFn(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, measure])\n\n return { host, node, flat, visual, cssClass }\n}\n\nexport type RnContainerOptions = {\n // Overrides the default engine-rect measure (the window root reports 0 so\n // the window can shrink freely below the current content size; the\n // intrinsic root measures its content through Yoga, honoring forSize for\n // height-for-width).\n measure?: (orientation: \"horizontal\" | \"vertical\", forSize: number) => number\n // Runs before children are placed — the window root syncs the engine\n // viewport to the actual allocation here.\n beforeAllocate?: (width: number, height: number) => void\n}\n\n// Containers (Root, View, Animated.View, ScrollView content) drive their GTK\n// side with an RnGtkxLayout: measure() reports the engine rect of `node`\n// (minimum == natural — GTK minimums of children never leak upward), and\n// allocate() places every child widget at exactly its stored engine rect\n// plus the Animated offset.\nexport const useRnContainer = (\n widgetRef: RefObject<Gtk.Widget | null>,\n node: LayoutNode,\n options?: RnContainerOptions,\n): void => {\n const optionsRef = useRef<RnContainerOptions | undefined>(options)\n optionsRef.current = options\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n attachRnLayout(widget, {\n measure: (orientation, forSize) => {\n const custom = optionsRef.current?.measure\n if (custom) {\n return custom(orientation, forSize)\n }\n const rect = node.getRect()\n return Math.round(\n (orientation === \"horizontal\" ? rect?.width : rect?.height) ?? 0,\n )\n },\n allocate: (width, height) => {\n const start = perfEnabled ? perfNow() : 0\n perfAllocDepth += 1\n optionsRef.current?.beforeAllocate?.(width, height)\n let child = widget.getFirstChild()\n let children = 0\n while (child) {\n const rect = getStoredRect(child)\n if (rect) {\n const offset = getStoredOffset(child)\n allocateChild(\n child,\n rect.x + offset.dx,\n rect.y + offset.dy,\n rect.width,\n rect.height,\n offset.matrix,\n )\n children += 1\n }\n child = child.getNextSibling()\n }\n perfAllocDepth -= 1\n if (perfEnabled) {\n perfCount(\"gtk.allocPass\")\n perfCount(\"gtk.allocChild\", children)\n if (perfAllocDepth === 0) {\n const elapsed = perfNow() - start\n perfAddTime(\"gtk.allocTop\", elapsed)\n perfBurst(\"gtk.allocMsPerFrame\", elapsed)\n }\n }\n },\n })\n return () => {\n detachRnLayout(widget)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n}\n"]}
|
|
1
|
+
{"version":3,"file":"use-layout-child.js","sourceRoot":"","sources":["../../src/components/use-layout-child.ts"],"names":[],"mappings":"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","sourcesContent":["import { useLayoutEffect, useRef, useState, type RefObject } from \"react\"\nimport type { LayoutNode } from \"../layout/index\"\nimport { splitStyle, StyleSheet } from \"../style/index\"\nimport { defaultCssRegistry } from \"../style/registry.gtkx\"\nimport type {\n LayoutStyle,\n MeasureFn,\n Rect,\n StyleProp,\n TransformPart,\n VisualStyle,\n} from \"../contracts\"\nimport {\n allocateChild,\n attachRnLayout,\n cacheChildOrder,\n detachRnLayout,\n Gtk,\n invalidateZOrder,\n measureWidget,\n queueAllocate,\n queueResize,\n setZIndex,\n} from \"../gtkx/bridge/index\"\nimport {\n perfAddTime,\n perfBurst,\n perfCount,\n perfEnabled,\n perfNow,\n} from \"../perf\"\nimport { useHostNode, type HostNode } from \"./host-node\"\nimport {\n deferDuringAllocate,\n getStoredDrivenBox,\n getStoredOffset,\n getStoredRect,\n setStoredRect,\n setStoredTransform,\n} from \"./rect-store\"\nimport { deferUntilReleased } from \"./widget-retention\"\n\nexport type LayoutEvent = {\n nativeEvent: {\n layout: { x: number; y: number; width: number; height: number }\n }\n}\n\n// Perf: sizeAllocate recurses synchronously into child containers, so a\n// naive per-pass timer double-counts nested passes. Track depth and time\n// only the outermost (top-level) allocate — that is the real wall time GTK\n// spends in OUR layout code for the frame.\nlet perfAllocDepth = 0\n\n// RN's `overflow` onto GTK's. It is the one style that has to reach BOTH\n// halves of the pipeline: Yoga reads it while measuring (see\n// layout/apply-style.ts, which keeps setting it on the node), and the widget\n// needs it to clip paint and picking — GTK4 CSS has no `overflow` property,\n// so unlike every other visual style this one is a widget call rather than a\n// declaration.\n//\n// `scroll` clips exactly like `hidden`, which is what RN itself does: a\n// `View` is not made scrollable by a style on either platform (iOS sets\n// `clipsToBounds` for any non-visible overflow, Android's ReactViewGroup\n// clips for SCROLL and HIDDEN alike) — only a `ScrollView` scrolls. Mapping\n// it to VISIBLE instead would be the silent lie this replaces.\nconst WIDGET_OVERFLOW: Record<\n NonNullable<LayoutStyle[\"overflow\"]>,\n Gtk.Overflow\n> = {\n visible: Gtk.Overflow.VISIBLE,\n hidden: Gtk.Overflow.HIDDEN,\n scroll: Gtk.Overflow.HIDDEN,\n}\n\nexport type LayoutChildOptions = {\n style: StyleProp | undefined\n onLayout: ((event: LayoutEvent) => void) | undefined\n measure?: MeasureFn\n // Leaves with intrinsic GTK sizes (Entry, Switch, Spinner) measure their\n // own widget so Yoga reserves exactly what the theme will draw.\n measureFromWidget?: boolean\n extraLayout?: LayoutStyle\n // Widgets that clip whatever the style says: an under-allocated GtkLabel\n // paints its full text past its box (Text), and a scroll viewport clips by\n // contract (ScrollView). Both used to write the widget's overflow\n // themselves; one writer avoids the ordering bug where a style change\n // re-runs the generic effect and un-clips them.\n alwaysClips?: boolean\n}\n\nexport type LayoutChild = {\n host: HostNode\n node: LayoutNode\n flat: ReturnType<typeof StyleSheet.flatten>\n visual: VisualStyle\n cssClass: string | null\n}\n\n// Which Yoga node lays out which widget. The reconciler talks in widgets and\n// the shadow tree talks in nodes, and syncChildOrder below is the one place\n// that has to translate between them.\nconst nodesByWidget = new WeakMap<Gtk.Widget, LayoutNode>()\n\n/**\n * The shadow-tree node laying out this widget, or undefined for a widget that\n * is not a layout child of any container (a nested layout root, a raw GTK\n * widget in a slot).\n *\n * The reverse of what `syncChildOrder` uses it for, and the same map: an\n * animated size has to walk the driven node's DESCENDANTS, which it can only\n * reach through the widget tree, and pairing each widget back to its node is\n * what keeps that walk honest about which children belong to which container.\n */\nexport const nodeForWidget = (widget: Gtk.Widget): LayoutNode | undefined =>\n nodesByWidget.get(widget)\n\n/**\n * Puts a container's shadow tree back into its widgets' order.\n *\n * WHY this is needed, and why insertion order alone was not enough. A child\n * picks its Yoga index once, on mount, from where the reconciler put its\n * widget — which is right for a child that APPEARS mid-list, and blind to a\n * child that MOVES. React reorders keyed siblings by moving the existing\n * fibers, so nothing mounts and nothing unmounts: the widgets end up in the\n * new order and the Yoga nodes stay in the old one, and since the rects come\n * from the nodes, the rows redraw exactly where they were. Every list that\n * can be sorted, filtered into a different order, or dragged into one was\n * silently affected — found by dragging a row in `examples/tasks-nav` after\n * its rows were rewritten in React Native (a `GtkListBox` was doing its own\n * layout before, so nothing had ever exercised this).\n *\n * Runs after every commit of the container, and does nothing at all unless\n * the two orders actually disagree — one pointer comparison per child.\n */\nconst syncChildOrder = (parentWidget: Gtk.Widget, parent: LayoutNode): void => {\n const ordered: LayoutNode[] = []\n let moved = false\n let child = parentWidget.getFirstChild()\n while (child !== null) {\n const node = nodesByWidget.get(child)\n // Widgets that are not layout children of THIS container are skipped in\n // both orders: a nested layout root, or a raw GTK widget a slot holds.\n if (node !== undefined && node.parent === parent) {\n if (parent.children[ordered.length] !== node) {\n moved = true\n }\n ordered.push(node)\n }\n child = child.getNextSibling()\n }\n // A disagreement in COUNT is not a reorder — it means a child is\n // mid-mount, or something in this container lays itself out. Re-sorting\n // against a partial view would be worse than leaving it alone.\n if (!moved || ordered.length !== parent.children.length) {\n return\n }\n for (let index = 0; index < ordered.length; index += 1) {\n const node = ordered[index]!\n if (parent.children[index] !== node) {\n parent.removeChild(node)\n parent.insertChild(node, index)\n }\n }\n}\n\n// Shared plumbing for every leaf/container component: owns one LayoutNode,\n// keeps it in the parent's shadow tree for the component lifetime, applies\n// the layout half of the style and commits Yoga rects into the rect store,\n// where the parent container's RnGtkxLayout allocate() picks them up.\nexport const useLayoutChild = (\n widgetRef: RefObject<Gtk.Widget | null>,\n options: LayoutChildOptions,\n): LayoutChild => {\n const host = useHostNode()\n\n // useState's lazy initializer is guaranteed once-per-instance even under\n // the React Compiler — a lazy ref here got its creation memoized ACROSS\n // component instances, sharing one Yoga node and double-freeing it.\n const [node] = useState<LayoutNode>(() => host.engine.createNode())\n\n const flat = StyleSheet.flatten(options.style)\n const { layout, visual } = splitStyle(flat)\n const effectiveLayout = options.extraLayout\n ? { ...layout, ...options.extraLayout }\n : layout\n\n // Re-apply layout style only when it actually changed (keyed effect) —\n // setStyle dirties the whole tree.\n const layoutKey = JSON.stringify(effectiveLayout)\n useLayoutEffect(() => {\n node.setStyle(JSON.parse(layoutKey) as LayoutStyle)\n }, [node, layoutKey])\n\n const cssClass = defaultCssRegistry.getClassName(visual)\n\n // The GTK half of `overflow`. Yoga has had it since the beginning and\n // nothing reached the widget, so the style was accepted and painted\n // nothing — `overflow: \"hidden\"` on a container was a no-op. GTK clips to\n // the CSS padding box, so `borderRadius` shapes the clip for free (a\n // rounded-clip node rather than a rectangular one) and both paint and\n // `gtk_widget_pick()` follow it.\n const overflow = options.alwaysClips\n ? \"hidden\"\n : (flat?.overflow ?? \"visible\")\n useLayoutEffect(() => {\n widgetRef.current?.setOverflow(WIDGET_OVERFLOW[overflow])\n }, [widgetRef, overflow])\n\n // RN's `zIndex`, which is neither a Yoga input nor a CSS declaration: the\n // PARENT container sorts its snapshot and its picking by it\n // (gtkx/bridge/view-box.ts). Applied unconditionally, as RN does — CSS\n // needs a non-static `position` for `z-index` to take effect and RN does\n // not, so nothing here consults `position`. `undefined` is 0, and negative\n // values are legal (they paint below unraised siblings).\n const zIndex = flat?.zIndex ?? 0\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n setZIndex(widget, zIndex)\n return () => {\n // Cleared before the widget leaves the tree, so the container that is\n // losing it drops its cached order and the process-wide \"anything\n // raised at all\" counter comes back down to zero.\n setZIndex(widget, 0)\n const parentWidget = host.widgetRef.current\n if (parentWidget) {\n invalidateZOrder(parentWidget)\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [zIndex])\n\n // RN transforms are visual only: the widget keeps the box Yoga gave it, so\n // a transform never touches the shadow tree — it only asks the parent\n // container to run its allocate hook again. Keyed on the serialized array\n // so a re-render with the same transform costs nothing.\n // (Animated.View owns its own transform and strips it from the static\n // style before it gets here, so the two writers never race.)\n const transformKey = JSON.stringify(visual.transform ?? null)\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n setStoredTransform(widget, JSON.parse(transformKey) as TransformPart[])\n const parentWidget = host.widgetRef.current\n if (parentWidget) {\n queueAllocate(parentWidget)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [transformKey])\n\n useLayoutEffect(() => {\n const parent = host.node\n // The reconciler has already attached our widget in JSX order, so its\n // sibling position is the correct Yoga index — this keeps the shadow tree\n // ordered even for mid-list mounts (conditional children, list inserts).\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n let index = parent.children.length\n if (widget) {\n // Published so the container can put the shadow tree back into widget\n // order after React has MOVED an existing child — see syncChildOrder.\n // Outside the `parentWidget` guard below on purpose: on a first mount\n // the PARENT's ref is not attached yet (React attaches host refs\n // bottom-up, so a child's layout effect runs before its container's\n // widget exists), and a registration skipped there would never happen\n // again — the effect runs once.\n nodesByWidget.set(widget, node)\n }\n if (widget && parentWidget) {\n let position = 0\n let sibling = parentWidget.getFirstChild()\n while (sibling !== null && sibling !== widget) {\n position += 1\n sibling = sibling.getNextSibling()\n }\n if (sibling === widget) {\n index = Math.min(position, parent.children.length)\n }\n }\n parent.insertChild(node, index)\n node.setCommit((rect: Rect) => {\n const widget = widgetRef.current\n const parentWidget = host.widgetRef.current\n if (!widget || !parentWidget) {\n return\n }\n const previous = getStoredRect(widget)\n // A commit does not imply a change. The engine may hand us the rect a\n // widget already has — nodes carrying a measure function are visited\n // even when Yoga did not move them — and queueing an allocation for\n // those makes GTK re-allocate and re-snapshot the container for nothing.\n //\n // `rect.skip` counts how often this guard actually fires and\n // `rect.change` how often it does not, because a guard nobody can see\n // firing is a guard nobody can attribute a measurement to: this one was\n // once credited with a large maximized-window win that a later, properly\n // geometry-controlled run could not reproduce (docs/research/\n // scroll-performance.md, round six). Keep it — it is correct and free —\n // but read the counter before crediting it with anything.\n const unchanged =\n previous !== undefined &&\n previous.x === rect.x &&\n previous.y === rect.y &&\n previous.width === rect.width &&\n previous.height === rect.height\n if (perfEnabled) {\n perfCount(unchanged ? \"rect.skip\" : \"rect.change\")\n }\n if (unchanged) {\n return\n }\n setStoredRect(widget, rect)\n const sizeChanged =\n !previous ||\n previous.width !== rect.width ||\n previous.height !== rect.height\n const queue = (): void => {\n if (sizeChanged) {\n // A size change invalidates this widget's cached measure (nested\n // managers, ScrolledWindow ranges) and re-allocates the ancestors.\n queueResize(widget)\n } else {\n // Pure move: the parent just needs another allocation pass. GTK\n // dedupes queued allocates, so per-child calls batch into one.\n queueAllocate(parentWidget)\n }\n }\n if (!deferDuringAllocate(widget, queue)) {\n queue()\n }\n })\n return () => {\n // `widget`, not `widgetRef.current`: by cleanup time the ref may\n // already have been detached, and the entry to drop is the one this\n // effect registered.\n if (widget) {\n nodesByWidget.delete(widget)\n }\n node.setCommit(null)\n // Removed from the shadow tree and freed even when the widget is being\n // RETAINED for an exit animation (see ./widget-retention): the siblings\n // must close the gap immediately — an exiting widget does not hold its\n // space, it draws over the space closing behind it — and the rect its\n // container keeps reporting afterwards is `lastRect`, a plain JS value\n // that outlives the Yoga node it came from.\n parent.removeChild(node)\n node.free()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const onLayout = options.onLayout\n useLayoutEffect(() => {\n node.setOnLayout(\n onLayout\n ? (rect: Rect) => {\n onLayout({ nativeEvent: { layout: rect } })\n }\n : null,\n )\n }, [node, onLayout])\n\n // The explicit return type keeps the inner params contextually typed: with a\n // bare lazy initializer TS may match the outer arrow against MeasureFn\n // itself (useState accepts S | () => S) and drop the context.\n const [widgetMeasure] = useState<MeasureFn>(\n (): MeasureFn => (width, widthMode) => {\n const widget = widgetRef.current\n if (!widget) {\n return { width: 0, height: 0 }\n }\n const { minimum, natural } = measureWidget(widget, \"horizontal\")\n // Floor at the widget's own minimum width, not at 1 — see the same\n // clamp in text.tsx's measure for why: gtk_widget_measure() enforces\n // this floor internally regardless of what we request (warning while\n // it does), so anything below `minimum` was never actually honored.\n const used =\n widthMode === \"undefined\"\n ? natural\n : Math.min(natural, Math.max(minimum, Math.floor(width)))\n return {\n width: used,\n height: measureWidget(widget, \"vertical\", used).natural,\n }\n },\n )\n const measure =\n options.measure ?? (options.measureFromWidget ? widgetMeasure : undefined)\n useLayoutEffect(() => {\n if (!measure) {\n return\n }\n node.setMeasureFn(measure)\n // Widgets measured before their window is mapped report zero (no realized\n // theme context yet, unlike offscreen probes) and Yoga caches it —\n // invalidate once the widget actually maps.\n const widget = widgetRef.current\n const remeasure = (): void => {\n node.markDirty()\n }\n if (options.measureFromWidget && widget) {\n widget.on(\"map\", remeasure)\n }\n return () => {\n if (options.measureFromWidget && widget) {\n widget.off(\"map\", remeasure)\n }\n // Cleared by free() on unmount; explicit unset only when measure is\n // removed while mounted.\n node.setMeasureFn(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, measure])\n\n return { host, node, flat, visual, cssClass }\n}\n\nexport type RnContainerOptions = {\n // Overrides the default engine-rect measure (the window root reports 0 so\n // the window can shrink freely below the current content size; the\n // intrinsic root measures its content through Yoga, honoring forSize for\n // height-for-width).\n measure?: (orientation: \"horizontal\" | \"vertical\", forSize: number) => number\n // Runs before children are placed — the window root syncs the engine\n // viewport to the actual allocation here.\n beforeAllocate?: (width: number, height: number) => void\n}\n\n// Containers (Root, View, Animated.View, ScrollView content) drive their GTK\n// side with an RnGtkxLayout: measure() reports the engine rect of `node`\n// (minimum == natural — GTK minimums of children never leak upward), and\n// allocate() places every child widget at exactly its stored engine rect\n// plus the Animated offset.\nexport const useRnContainer = (\n widgetRef: RefObject<Gtk.Widget | null>,\n node: LayoutNode,\n options?: RnContainerOptions,\n): void => {\n const optionsRef = useRef<RnContainerOptions | undefined>(options)\n optionsRef.current = options\n\n // Deliberately no dependency array: a MOVE mounts nothing and unmounts\n // nothing, so there is no other commit this could key off. Children's\n // layout effects run before their container's, so by the time this fires\n // every child of this commit has registered its node. See syncChildOrder.\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (widget) {\n syncChildOrder(widget, node)\n // The paint order caches a child list, and a commit is the only thing\n // that can change one without any zIndex being written — a mount, an\n // unmount, or a MOVE. Dropping it here is a WeakMap delete on a\n // container that has a raised child and nothing at all on one that does\n // not (`invalidateZOrder` only redraws when there was a cache to drop).\n invalidateZOrder(widget)\n }\n })\n\n useLayoutEffect(() => {\n const widget = widgetRef.current\n if (!widget) {\n return\n }\n attachRnLayout(widget, {\n measure: (orientation, forSize) => {\n const custom = optionsRef.current?.measure\n if (custom) {\n return custom(orientation, forSize)\n }\n // A driven size wins over the committed one here as well as in the\n // parent's allocate, so the container asks GTK for the box it is\n // actually being given. Otherwise a size animated DOWNWARD would\n // under-allocate a widget against its own stated minimum every frame.\n const rect = node.getRect()\n const driven = getStoredDrivenBox(widget)\n const size =\n orientation === \"horizontal\"\n ? (driven?.width ?? rect?.width)\n : (driven?.height ?? rect?.height)\n return Math.round(size ?? 0)\n },\n allocate: (width, height) => {\n const start = perfEnabled ? perfNow() : 0\n perfAllocDepth += 1\n optionsRef.current?.beforeAllocate?.(width, height)\n let child = widget.getFirstChild()\n let children = 0\n // Collected while this loop walks the sibling chain anyway, and handed\n // to the paint pass, which would otherwise have to walk it again: each\n // getNextSibling() mints a native wrapper and is the single most\n // expensive thing a JS snapshot vfunc does (docs/research/z-index.md).\n const order: Gtk.Widget[] = []\n while (child) {\n order.push(child)\n const rect = getStoredRect(child)\n if (rect) {\n const offset = getStoredOffset(child)\n // The engine's rect, with whatever an animation is currently\n // driving laid over it field by field: the animated node\n // overrides only the axis being driven, its descendants override\n // the whole rect because re-laying the subtree out is what\n // decides them. The transform composes on top either way.\n const driven = offset.driven\n allocateChild(\n child,\n (driven?.x ?? rect.x) + offset.dx,\n (driven?.y ?? rect.y) + offset.dy,\n driven?.width ?? rect.width,\n driven?.height ?? rect.height,\n offset.matrix,\n )\n children += 1\n }\n child = child.getNextSibling()\n }\n cacheChildOrder(widget, order)\n perfAllocDepth -= 1\n if (perfEnabled) {\n perfCount(\"gtk.allocPass\")\n perfCount(\"gtk.allocChild\", children)\n if (perfAllocDepth === 0) {\n const elapsed = perfNow() - start\n perfAddTime(\"gtk.allocTop\", elapsed)\n perfBurst(\"gtk.allocMsPerFrame\", elapsed)\n }\n }\n },\n })\n return () => {\n // Deferred inside a retained subtree for the same reason the Yoga node\n // is: without its RnGtkxLayout the container falls back to GtkBox's own\n // layout and the children of a widget that is still visible re-stack\n // themselves mid-animation.\n if (!deferUntilReleased(widget, () => detachRnLayout(widget))) {\n detachRnLayout(widget)\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n}\n"]}
|
|
@@ -9,10 +9,13 @@ export type ViewProps = ResponderProps & {
|
|
|
9
9
|
style?: StyleProp;
|
|
10
10
|
pointerEvents?: PointerEventsValue;
|
|
11
11
|
onLayout?: (event: LayoutEvent) => void;
|
|
12
|
+
focusable?: boolean;
|
|
13
|
+
onFocus?: () => void;
|
|
14
|
+
onBlur?: () => void;
|
|
12
15
|
children?: ReactNode;
|
|
13
16
|
testID?: string;
|
|
14
17
|
ref?: Ref<ViewHandle>;
|
|
15
18
|
};
|
|
16
|
-
export declare const View: ({ style, pointerEvents, onLayout, children, testID, ref, ...responderProps }: ViewProps) => React.JSX.Element;
|
|
17
|
-
export declare const SafeAreaView: ({ style, pointerEvents, onLayout, children, testID, ref, ...responderProps }: ViewProps) => React.JSX.Element;
|
|
19
|
+
export declare const View: ({ style, pointerEvents, onLayout, focusable, onFocus, onBlur, children, testID, ref, ...responderProps }: ViewProps) => React.JSX.Element;
|
|
20
|
+
export declare const SafeAreaView: ({ style, pointerEvents, onLayout, focusable, onFocus, onBlur, children, testID, ref, ...responderProps }: ViewProps) => React.JSX.Element;
|
|
18
21
|
export declare const StatusBar: () => null;
|