react-native-gtkx 0.2.0-alpha.1 → 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.
Files changed (144) hide show
  1. package/README.md +11 -2
  2. package/dist/adw/widgets.generated.d.ts +4 -4
  3. package/dist/adw/widgets.generated.js +51 -51
  4. package/dist/adw/widgets.generated.js.map +1 -1
  5. package/dist/animated/create-animated.d.ts +2 -0
  6. package/dist/animated/create-animated.js +2 -0
  7. package/dist/animated/create-animated.js.map +1 -1
  8. package/dist/animated/index.d.ts +2 -0
  9. package/dist/animated/index.js +1 -0
  10. package/dist/animated/index.js.map +1 -1
  11. package/dist/animated/value-xy.d.ts +41 -0
  12. package/dist/animated/value-xy.js +90 -0
  13. package/dist/animated/value-xy.js.map +1 -0
  14. package/dist/animated/value.d.ts +9 -0
  15. package/dist/animated/value.js +30 -2
  16. package/dist/animated/value.js.map +1 -1
  17. package/dist/common/icon.d.ts +23 -0
  18. package/dist/common/icon.js +14 -0
  19. package/dist/common/icon.js.map +1 -0
  20. package/dist/common/index.d.ts +2 -0
  21. package/dist/common/index.js +2 -0
  22. package/dist/common/index.js.map +1 -1
  23. package/dist/common/list.d.ts +82 -0
  24. package/dist/common/list.js +166 -0
  25. package/dist/common/list.js.map +1 -0
  26. package/dist/common/widget.d.ts +1 -22
  27. package/dist/common/widget.js +75 -8
  28. package/dist/common/widget.js.map +1 -1
  29. package/dist/components/animated.d.ts +4 -2
  30. package/dist/components/animated.js +3 -1
  31. package/dist/components/animated.js.map +1 -1
  32. package/dist/components/app-registry.d.ts +26 -0
  33. package/dist/components/app-registry.js.map +1 -1
  34. package/dist/components/host-node.d.ts +7 -0
  35. package/dist/components/host-node.js +26 -1
  36. package/dist/components/host-node.js.map +1 -1
  37. package/dist/components/index.d.ts +6 -2
  38. package/dist/components/index.js +2 -1
  39. package/dist/components/index.js.map +1 -1
  40. package/dist/components/measure.d.ts +13 -0
  41. package/dist/components/measure.js +51 -0
  42. package/dist/components/measure.js.map +1 -0
  43. package/dist/components/modal.d.ts +1 -1
  44. package/dist/components/press-event.d.ts +28 -0
  45. package/dist/components/press-event.js +46 -0
  46. package/dist/components/press-event.js.map +1 -0
  47. package/dist/components/pressable.d.ts +2 -6
  48. package/dist/components/pressable.js +14 -8
  49. package/dist/components/pressable.js.map +1 -1
  50. package/dist/components/scroll-view.d.ts +4 -0
  51. package/dist/components/scroll-view.js +8 -2
  52. package/dist/components/scroll-view.js.map +1 -1
  53. package/dist/components/text-input.js +1 -1
  54. package/dist/components/text-input.js.map +1 -1
  55. package/dist/components/text.js +25 -1
  56. package/dist/components/text.js.map +1 -1
  57. package/dist/components/use-layout-child.js +17 -14
  58. package/dist/components/use-layout-child.js.map +1 -1
  59. package/dist/components/view.d.ts +9 -4
  60. package/dist/components/view.js +8 -2
  61. package/dist/components/view.js.map +1 -1
  62. package/dist/contracts.d.ts +19 -3
  63. package/dist/contracts.js.map +1 -1
  64. package/dist/gtk/index.d.ts +2 -1
  65. package/dist/gtk/index.js +8 -1
  66. package/dist/gtk/index.js.map +1 -1
  67. package/dist/gtk/widgets.generated.d.ts +14 -14
  68. package/dist/gtk/widgets.generated.js +101 -102
  69. package/dist/gtk/widgets.generated.js.map +1 -1
  70. package/dist/gtk/window-actions.d.ts +77 -0
  71. package/dist/gtk/window-actions.js +193 -0
  72. package/dist/gtk/window-actions.js.map +1 -0
  73. package/dist/gtkx/bridge/geometry.d.ts +15 -0
  74. package/dist/gtkx/bridge/geometry.js +38 -0
  75. package/dist/gtkx/bridge/geometry.js.map +1 -0
  76. package/dist/gtkx/bridge/index.d.ts +2 -0
  77. package/dist/gtkx/bridge/index.js +7 -2
  78. package/dist/gtkx/bridge/index.js.map +1 -1
  79. package/dist/gtkx/bridge/slot-portal.d.ts +12 -0
  80. package/dist/gtkx/bridge/slot-portal.js +17 -0
  81. package/dist/gtkx/bridge/slot-portal.js.map +1 -0
  82. package/dist/gtkx/bridge/svg-node.js +10 -54
  83. package/dist/gtkx/bridge/svg-node.js.map +1 -1
  84. package/dist/gtkx/bridge/use-signal.js +2 -2
  85. package/dist/gtkx/bridge/use-signal.js.map +1 -1
  86. package/dist/index.d.ts +2 -2
  87. package/dist/index.js +1 -1
  88. package/dist/index.js.map +1 -1
  89. package/dist/mcp/data/generated.d.ts +99 -77
  90. package/dist/mcp/data/generated.js +107 -76
  91. package/dist/mcp/data/generated.js.map +1 -1
  92. package/dist/mcp/server.js +1 -1
  93. package/dist/mcp/server.js.map +1 -1
  94. package/dist/navigation/sidebar.d.ts +31 -1
  95. package/dist/navigation/sidebar.js +12 -2
  96. package/dist/navigation/sidebar.js.map +1 -1
  97. package/dist/responder/system.d.ts +27 -0
  98. package/dist/responder/system.js +184 -0
  99. package/dist/responder/system.js.map +1 -0
  100. package/dist/responder/touch-history.d.ts +10 -0
  101. package/dist/responder/touch-history.js +78 -0
  102. package/dist/responder/touch-history.js.map +1 -0
  103. package/dist/responder/types.d.ts +63 -0
  104. package/dist/responder/types.js +29 -0
  105. package/dist/responder/types.js.map +1 -0
  106. package/dist/responder/use-responder.d.ts +12 -0
  107. package/dist/responder/use-responder.js +98 -0
  108. package/dist/responder/use-responder.js.map +1 -0
  109. package/dist/runner/host-dev.js +3 -1
  110. package/dist/runner/host-dev.js.map +1 -1
  111. package/dist/runner/host.js +5 -1
  112. package/dist/runner/host.js.map +1 -1
  113. package/dist/runner/index.js +1 -1
  114. package/dist/runner/index.js.map +1 -1
  115. package/dist/sea/gtkx-config-module.js +1 -1
  116. package/dist/sea/gtkx-config-module.js.map +1 -1
  117. package/dist/style/box-shadow.d.ts +7 -0
  118. package/dist/style/box-shadow.js +197 -0
  119. package/dist/style/box-shadow.js.map +1 -0
  120. package/dist/style/index.d.ts +2 -0
  121. package/dist/style/index.js +2 -0
  122. package/dist/style/index.js.map +1 -1
  123. package/dist/style/split-style.d.ts +5 -2
  124. package/dist/style/split-style.js +31 -3
  125. package/dist/style/split-style.js.map +1 -1
  126. package/dist/style/text-decoration.d.ts +15 -0
  127. package/dist/style/text-decoration.js +26 -0
  128. package/dist/style/text-decoration.js.map +1 -0
  129. package/dist/style/visual-css.js +37 -1
  130. package/dist/style/visual-css.js.map +1 -1
  131. package/dist/svg/gradient-geometry.js +2 -5
  132. package/dist/svg/gradient-geometry.js.map +1 -1
  133. package/dist/testing/index.js.map +1 -1
  134. package/dist/vendor/react-native/pan-responder.d.ts +102 -0
  135. package/dist/vendor/react-native/pan-responder.js +196 -0
  136. package/dist/vendor/react-native/pan-responder.js.map +1 -0
  137. package/dist/vendor/react-native/touch-history-math.d.ts +18 -0
  138. package/dist/vendor/react-native/touch-history-math.js +102 -0
  139. package/dist/vendor/react-native/touch-history-math.js.map +1 -0
  140. package/dist/vite/index.js +1 -1
  141. package/dist/vite/index.js.map +1 -1
  142. package/dist/vitest/index.d.ts +1 -1
  143. package/dist/vitest/index.js.map +1 -1
  144. package/package.json +9 -9
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from "react";
2
+ import type { StyleProp } from "../contracts";
3
+ export type IconProps = {
4
+ /** Icon-theme name, e.g. `"starred-symbolic"`. Symbolic icons take their
5
+ * colour from the surrounding label colour. */
6
+ name: string;
7
+ /** Square size in RN units. Defaults to 16, the size Adwaita uses for icons
8
+ * inside list rows and flat buttons. */
9
+ size?: number;
10
+ /** React Native style. Sizing here wins over `size`. */
11
+ style?: StyleProp;
12
+ testID?: string;
13
+ };
14
+ /**
15
+ * ```tsx
16
+ * <Icon name="starred-symbolic" size={16} />
17
+ * ```
18
+ *
19
+ * Sized through `pixel-size` as well as through Yoga: GTK would otherwise pick
20
+ * the nearest theme size and paint at that, so a 16-unit box could hold a
21
+ * 24px icon scaled down.
22
+ */
23
+ export declare const Icon: ({ name, size, style, testID, }: IconProps) => ReactNode;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { GtkImage } from "../gtkx/bridge/index";
3
+ import { Widget } from "./widget";
4
+ /**
5
+ * ```tsx
6
+ * <Icon name="starred-symbolic" size={16} />
7
+ * ```
8
+ *
9
+ * Sized through `pixel-size` as well as through Yoga: GTK would otherwise pick
10
+ * the nearest theme size and paint at that, so a 16-unit box could hold a
11
+ * 24px icon scaled down.
12
+ */
13
+ export const Icon = ({ name, size = 16, style, testID, }) => (_jsx(Widget, { testID: testID, style: [{ width: size, height: size }, style], children: _jsx(GtkImage, { iconName: name, pixelSize: size }) }));
14
+ //# sourceMappingURL=icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon.js","sourceRoot":"","sources":["../../src/common/icon.tsx"],"names":[],"mappings":";AAeA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAcjC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EACnB,IAAI,EACJ,IAAI,GAAG,EAAE,EACT,KAAK,EACL,MAAM,GACI,EAAa,EAAE,CAAC,CAC1B,KAAC,MAAM,IACL,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,KAAK,CAAC,YAE7C,KAAC,QAAQ,IACP,QAAQ,EAAE,IAAI,EACd,SAAS,EAAE,IAAI,GACf,GACK,CACV,CAAA","sourcesContent":["// A named icon from the desktop icon theme, in React Native layout.\n//\n// WHY this is not `Image`. RN's `Image` takes a file path or a URI, because on\n// iOS and Android an icon is an asset the app ships. On the Linux desktop an\n// icon is a NAME resolved against the current icon theme at paint time —\n// `user-trash-symbolic` recolours itself with the label colour, follows the\n// user's theme, and has no file the app could point at. There is nothing in\n// RN's `Image` contract that can express that, and pretending a name is a URI\n// would break `onError`, `resizeMode` and the loading states along with it.\n//\n// So this is the same shape RN apps already reach for on every platform —\n// react-native-vector-icons' `<Icon name size color />` — with the desktop\n// icon theme behind it instead of a bundled font.\nimport type { ReactNode } from \"react\"\nimport type { StyleProp } from \"../contracts\"\nimport { GtkImage } from \"../gtkx/bridge/index\"\nimport { Widget } from \"./widget\"\n\nexport type IconProps = {\n /** Icon-theme name, e.g. `\"starred-symbolic\"`. Symbolic icons take their\n * colour from the surrounding label colour. */\n name: string\n /** Square size in RN units. Defaults to 16, the size Adwaita uses for icons\n * inside list rows and flat buttons. */\n size?: number\n /** React Native style. Sizing here wins over `size`. */\n style?: StyleProp\n testID?: string\n}\n\n/**\n * ```tsx\n * <Icon name=\"starred-symbolic\" size={16} />\n * ```\n *\n * Sized through `pixel-size` as well as through Yoga: GTK would otherwise pick\n * the nearest theme size and paint at that, so a 16-unit box could hold a\n * 24px icon scaled down.\n */\nexport const Icon = ({\n name,\n size = 16,\n style,\n testID,\n}: IconProps): ReactNode => (\n <Widget\n testID={testID}\n style={[{ width: size, height: size }, style]}\n >\n <GtkImage\n iconName={name}\n pixelSize={size}\n />\n </Widget>\n)\n"]}
@@ -1,3 +1,5 @@
1
1
  export { NavigationStack, NavigationStackPage, type NavigationStackPageProps, type NavigationStackProps, } from "./navigation-stack";
2
2
  export { HeaderSlotContent, IntrinsicContent, SlotContent, WidgetContent, type IntrinsicContentProps, type SlotContentProps, } from "./content";
3
3
  export { useWidgetLayout, Widget, wrapReactNative, type ReactNativeLayoutProps, type UseWidgetLayoutOptions, type WidgetProps, } from "./widget";
4
+ export { Icon, type IconProps } from "./icon";
5
+ export { List, ListRow, ListSeparator, rowPosition, type ListProps, type ListRowPosition, type ListRowProps, } from "./list";
@@ -16,4 +16,6 @@
16
16
  export { NavigationStack, NavigationStackPage, } from "./navigation-stack";
17
17
  export { HeaderSlotContent, IntrinsicContent, SlotContent, WidgetContent, } from "./content";
18
18
  export { useWidgetLayout, Widget, wrapReactNative, } from "./widget";
19
+ export { Icon } from "./icon";
20
+ export { List, ListRow, ListSeparator, rowPosition, } from "./list";
19
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,wEAAwE;AACxE,mCAAmC;AACnC,EAAE;AACF,mEAAmE;AACnE,uEAAuE;AACvE,iEAAiE;AACjE,uEAAuE;AACvE,kEAAkE;AAClE,wEAAwE;AACxE,EAAE;AACF,yCAAyC;AAEzC,OAAO,EACL,eAAe,EACf,mBAAmB,GAGpB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,aAAa,GAGd,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,eAAe,EACf,MAAM,EACN,eAAe,GAIhB,MAAM,UAAU,CAAA","sourcesContent":["// react-native-gtkx/common — the parts of the platform layer we wrote.\n//\n// The other two subpaths are widget bindings: react-native-gtkx/gtk and\n// react-native-gtkx/adw re-export what gtkx generates, name for name. This\n// one holds what has no upstream counterpart, which is why nothing here\n// carries a `Gtk` or `Adw` prefix.\n//\n// Two jobs live here. Getting React Native content INTO a GTK slot\n// (SlotContent, IntrinsicContent), and getting a GTK widget INTO React\n// Native layout (Widget, wrapReactNative, useWidgetLayout). Plus\n// NavigationStack, which is a declarative component over an imperative\n// widget rather than a binding of one — a different contract from\n// Adw.NavigationView, which is why it has its own name and lives apart.\n//\n// Nothing here imports react-navigation.\n\nexport {\n NavigationStack,\n NavigationStackPage,\n type NavigationStackPageProps,\n type NavigationStackProps,\n} from \"./navigation-stack\"\n\nexport {\n HeaderSlotContent,\n IntrinsicContent,\n SlotContent,\n WidgetContent,\n type IntrinsicContentProps,\n type SlotContentProps,\n} from \"./content\"\n\nexport {\n useWidgetLayout,\n Widget,\n wrapReactNative,\n type ReactNativeLayoutProps,\n type UseWidgetLayoutOptions,\n type WidgetProps,\n} from \"./widget\"\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,wEAAwE;AACxE,mCAAmC;AACnC,EAAE;AACF,mEAAmE;AACnE,uEAAuE;AACvE,iEAAiE;AACjE,uEAAuE;AACvE,kEAAkE;AAClE,wEAAwE;AACxE,EAAE;AACF,yCAAyC;AAEzC,OAAO,EACL,eAAe,EACf,mBAAmB,GAGpB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,WAAW,EACX,aAAa,GAGd,MAAM,WAAW,CAAA;AAElB,OAAO,EACL,eAAe,EACf,MAAM,EACN,eAAe,GAIhB,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAE,IAAI,EAAkB,MAAM,QAAQ,CAAA;AAE7C,OAAO,EACL,IAAI,EACJ,OAAO,EACP,aAAa,EACb,WAAW,GAIZ,MAAM,QAAQ,CAAA","sourcesContent":["// react-native-gtkx/common — the parts of the platform layer we wrote.\n//\n// The other two subpaths are widget bindings: react-native-gtkx/gtk and\n// react-native-gtkx/adw re-export what gtkx generates, name for name. This\n// one holds what has no upstream counterpart, which is why nothing here\n// carries a `Gtk` or `Adw` prefix.\n//\n// Two jobs live here. Getting React Native content INTO a GTK slot\n// (SlotContent, IntrinsicContent), and getting a GTK widget INTO React\n// Native layout (Widget, wrapReactNative, useWidgetLayout). Plus\n// NavigationStack, which is a declarative component over an imperative\n// widget rather than a binding of one — a different contract from\n// Adw.NavigationView, which is why it has its own name and lives apart.\n//\n// Nothing here imports react-navigation.\n\nexport {\n NavigationStack,\n NavigationStackPage,\n type NavigationStackPageProps,\n type NavigationStackProps,\n} from \"./navigation-stack\"\n\nexport {\n HeaderSlotContent,\n IntrinsicContent,\n SlotContent,\n WidgetContent,\n type IntrinsicContentProps,\n type SlotContentProps,\n} from \"./content\"\n\nexport {\n useWidgetLayout,\n Widget,\n wrapReactNative,\n type ReactNativeLayoutProps,\n type UseWidgetLayoutOptions,\n type WidgetProps,\n} from \"./widget\"\n\nexport { Icon, type IconProps } from \"./icon\"\n\nexport {\n List,\n ListRow,\n ListSeparator,\n rowPosition,\n type ListProps,\n type ListRowPosition,\n type ListRowProps,\n} from \"./list\"\n"]}
@@ -0,0 +1,82 @@
1
+ import { type ReactNode } from "react";
2
+ import type { StyleProp } from "../contracts";
3
+ /**
4
+ * Where a row sits in its list, which is what decides its corner radii and
5
+ * whether it draws a separator under itself.
6
+ *
7
+ * RN has no `:first-child`, so this is explicit. From a `FlatList` it is
8
+ * `rowPosition(index, data.length)`; from a static list it is the same
9
+ * expression over the array.
10
+ */
11
+ export type ListRowPosition = "first" | "middle" | "last" | "only";
12
+ /** The usual derivation, so no call site has to get the one-row case wrong. */
13
+ export declare const rowPosition: (index: number, count: number) => ListRowPosition;
14
+ export type ListProps = {
15
+ children?: ReactNode;
16
+ style?: StyleProp;
17
+ testID?: string;
18
+ };
19
+ /**
20
+ * The `.boxed-list` frame: a rounded, shadowed card. Put `ListRow`s in it
21
+ * directly, or a `FlatList` whose `renderItem` returns them.
22
+ *
23
+ * ```tsx
24
+ * <List>
25
+ * {tasks.map((task, index) => (
26
+ * <ListRow
27
+ * key={task.id}
28
+ * title={task.title}
29
+ * position={rowPosition(index, tasks.length)}
30
+ * onPress={() => open(task)}
31
+ * />
32
+ * ))}
33
+ * </List>
34
+ * ```
35
+ */
36
+ export declare const List: ({ children, style, testID }: ListProps) => ReactNode;
37
+ export type ListRowProps = {
38
+ /** Primary line. A string renders with the row's own typography; a node is
39
+ * rendered as given, for a title that needs its own styling. */
40
+ title?: ReactNode;
41
+ /** Second, dimmed line. Omitted rows are single-line, as `AdwActionRow`'s
42
+ * are. */
43
+ subtitle?: ReactNode;
44
+ /** Leading content — a checkbox, an icon, a colour swatch. */
45
+ prefix?: ReactNode;
46
+ /** Trailing content — buttons, a value, a chevron. */
47
+ suffix?: ReactNode;
48
+ /** Makes the row activatable: it gains the hover and press tints and calls
49
+ * this. Without it the row is inert, like a non-`activatable`
50
+ * `AdwActionRow`. */
51
+ onPress?: () => void;
52
+ /** Position in the list, for the corner radii and the separator. Defaults
53
+ * to `"middle"`, which is right for a row that is neither. */
54
+ position?: ListRowPosition;
55
+ /** Draw the hairline under this row. Default true for every position except
56
+ * the last — set it false when the list supplies its own separators (a
57
+ * `FlatList`'s `ItemSeparatorComponent`, say). */
58
+ separator?: boolean;
59
+ style?: StyleProp;
60
+ testID?: string;
61
+ };
62
+ /**
63
+ * One row of a {@link List} — `AdwActionRow`'s layout and states, built from
64
+ * `Pressable`, `View` and `Text`.
65
+ *
66
+ * Hover and press come from `Pressable`'s state callback, so they cost what
67
+ * they cost anywhere else in this platform: the hover path swaps a CSS class
68
+ * on the widget without a React render (see components/pressable.tsx).
69
+ *
70
+ * **Not yet:** keyboard navigation between rows and a focus ring.
71
+ * `GtkListBox` implements those as widget behaviour and RN has no focus model
72
+ * for `View` to hang them on — `Pressable`'s state is `{pressed, hovered}`
73
+ * with no `focused`. The ring itself is now drawable (`outlineWidth` and
74
+ * friends), so what is missing is the state, not the paint.
75
+ */
76
+ export declare const ListRow: ({ title, subtitle, prefix, suffix, onPress, position, separator, style, testID, }: ListRowProps) => ReactNode;
77
+ /**
78
+ * The hairline between two rows, for lists that draw their own separators —
79
+ * a `FlatList`'s `ItemSeparatorComponent` is exactly this, and using it means
80
+ * the rows themselves stay position-agnostic apart from their corners.
81
+ */
82
+ export declare const ListSeparator: () => ReactNode;
@@ -0,0 +1,166 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useColorScheme } from "../apis/index";
3
+ import { Pressable } from "../components/pressable";
4
+ import { Text } from "../components/text";
5
+ import { View } from "../components/view";
6
+ import { PlatformColor, StyleSheet } from "../style/index";
7
+ /** The usual derivation, so no call site has to get the one-row case wrong. */
8
+ export const rowPosition = (index, count) => {
9
+ const first = index === 0;
10
+ const last = index === count - 1;
11
+ if (first && last) {
12
+ return "only";
13
+ }
14
+ return first ? "first" : last ? "last" : "middle";
15
+ };
16
+ // libadwaita 1.9, /org/gnome/Adwaita/styles/gtk.css:
17
+ // list.boxed-list { background-color: var(--card-bg-color); border-radius: 12px;
18
+ // box-shadow: 0 0 0 1px RGB(0 0 6/3%),
19
+ // 0 1px 3px 1px RGB(0 0 6/7%),
20
+ // 0 2px 6px 2px RGB(0 0 6/3%); }
21
+ const RADIUS = 12;
22
+ const styles = StyleSheet.create({
23
+ list: {
24
+ backgroundColor: PlatformColor("card-bg-color"),
25
+ borderRadius: RADIUS,
26
+ // The frame is a shadow, not a border, and that distinction is load
27
+ // bearing: a border would consume the box and inset every row by a pixel,
28
+ // and no border draws the two soft drops underneath.
29
+ boxShadow: [
30
+ {
31
+ offsetX: 0,
32
+ offsetY: 0,
33
+ spreadDistance: 1,
34
+ color: "rgba(0, 0, 6, 0.03)",
35
+ },
36
+ {
37
+ offsetX: 0,
38
+ offsetY: 1,
39
+ blurRadius: 3,
40
+ spreadDistance: 1,
41
+ color: "rgba(0, 0, 6, 0.07)",
42
+ },
43
+ {
44
+ offsetX: 0,
45
+ offsetY: 2,
46
+ blurRadius: 6,
47
+ spreadDistance: 2,
48
+ color: "rgba(0, 0, 6, 0.03)",
49
+ },
50
+ ],
51
+ },
52
+ // `list > row { padding: 2px }` plus
53
+ // `row > box.header { margin: 0 12px; border-spacing: 6px; min-height: 50px }`
54
+ // — 2 + 50 + 2 + the 1px separator is the 55px row pitch measured off the
55
+ // widget's own screenshot.
56
+ row: {
57
+ flexDirection: "row",
58
+ alignItems: "center",
59
+ minHeight: 54,
60
+ paddingVertical: 2,
61
+ paddingHorizontal: 14,
62
+ gap: 6,
63
+ },
64
+ separatorUnder: {
65
+ borderBottomWidth: 1,
66
+ borderBottomColor: PlatformColor("card-shade-color"),
67
+ },
68
+ firstRow: { borderTopLeftRadius: RADIUS, borderTopRightRadius: RADIUS },
69
+ lastRow: {
70
+ borderBottomLeftRadius: RADIUS,
71
+ borderBottomRightRadius: RADIUS,
72
+ },
73
+ // `row > box.header > box.title { margin: 6px 0; border-spacing: 3px }`
74
+ titleBox: { flex: 1, marginVertical: 6, gap: 3 },
75
+ // The theme font is `Adwaita Sans 11`, i.e. 11pt = 14.67px, and Adwaita sets
76
+ // the subtitle to `font-size: smaller` — Pango's one-step-down, 1/1.2. RN's
77
+ // fontSize is numeric only (there is no `smaller`, no em, no inherited
78
+ // percentage), so the computed value is what an app can write. Documented
79
+ // in docs/api.md rather than hidden here.
80
+ title: { fontSize: 14.7 },
81
+ // `row label.subtitle { opacity: var(--dim-opacity) }`, 55% in the light
82
+ // Adwaita palette.
83
+ subtitle: { fontSize: 12.2, opacity: 0.55 },
84
+ separator: {
85
+ height: 1,
86
+ backgroundColor: PlatformColor("card-shade-color"),
87
+ },
88
+ });
89
+ // Adwaita tints the hover and press states with a fraction of the FOREGROUND
90
+ // colour (`color-mix(in srgb, currentColor 4%, transparent)`), so one literal
91
+ // cannot serve both themes and CSS colour functions are not part of RN's
92
+ // colour contract. Two literals plus the scheme is what an RN app writes on
93
+ // every platform, and it is what this does.
94
+ const TINTS = {
95
+ light: { hover: "rgba(0, 0, 0, 0.04)", press: "rgba(0, 0, 0, 0.08)" },
96
+ dark: {
97
+ hover: "rgba(255, 255, 255, 0.04)",
98
+ press: "rgba(255, 255, 255, 0.08)",
99
+ },
100
+ };
101
+ /**
102
+ * The `.boxed-list` frame: a rounded, shadowed card. Put `ListRow`s in it
103
+ * directly, or a `FlatList` whose `renderItem` returns them.
104
+ *
105
+ * ```tsx
106
+ * <List>
107
+ * {tasks.map((task, index) => (
108
+ * <ListRow
109
+ * key={task.id}
110
+ * title={task.title}
111
+ * position={rowPosition(index, tasks.length)}
112
+ * onPress={() => open(task)}
113
+ * />
114
+ * ))}
115
+ * </List>
116
+ * ```
117
+ */
118
+ export const List = ({ children, style, testID }) => (_jsx(View, { testID: testID, style: [styles.list, style], children: children }));
119
+ /**
120
+ * One row of a {@link List} — `AdwActionRow`'s layout and states, built from
121
+ * `Pressable`, `View` and `Text`.
122
+ *
123
+ * Hover and press come from `Pressable`'s state callback, so they cost what
124
+ * they cost anywhere else in this platform: the hover path swaps a CSS class
125
+ * on the widget without a React render (see components/pressable.tsx).
126
+ *
127
+ * **Not yet:** keyboard navigation between rows and a focus ring.
128
+ * `GtkListBox` implements those as widget behaviour and RN has no focus model
129
+ * for `View` to hang them on — `Pressable`'s state is `{pressed, hovered}`
130
+ * with no `focused`. The ring itself is now drawable (`outlineWidth` and
131
+ * friends), so what is missing is the state, not the paint.
132
+ */
133
+ export const ListRow = ({ title, subtitle, prefix, suffix, onPress, position = "middle", separator, style, testID, }) => {
134
+ const scheme = useColorScheme() === "dark" ? "dark" : "light";
135
+ const tints = TINTS[scheme];
136
+ const isFirst = position === "first" || position === "only";
137
+ const isLast = position === "last" || position === "only";
138
+ const drawSeparator = separator ?? !isLast;
139
+ const body = (_jsxs(_Fragment, { children: [prefix, _jsxs(View, { style: styles.titleBox, children: [typeof title === "string" ? (_jsx(Text, { style: styles.title, children: title })) : (title), typeof subtitle === "string" ? (_jsx(Text, { style: styles.subtitle, children: subtitle })) : (subtitle)] }), suffix] }));
140
+ const base = [
141
+ styles.row,
142
+ isFirst && styles.firstRow,
143
+ isLast && styles.lastRow,
144
+ drawSeparator && styles.separatorUnder,
145
+ style,
146
+ ];
147
+ if (!onPress) {
148
+ return (_jsx(View, { testID: testID, style: base, children: body }));
149
+ }
150
+ return (_jsx(Pressable, { testID: testID, onPress: onPress,
151
+ // The tint goes on the ROW, which is why the corner radii above have to
152
+ // be on the row too: a hovered first row must round its own top corners
153
+ // or the highlight paints square over the card's rounded ones.
154
+ style: ({ hovered, pressed }) => [
155
+ ...base,
156
+ hovered && { backgroundColor: tints.hover },
157
+ pressed && { backgroundColor: tints.press },
158
+ ], children: body }));
159
+ };
160
+ /**
161
+ * The hairline between two rows, for lists that draw their own separators —
162
+ * a `FlatList`'s `ItemSeparatorComponent` is exactly this, and using it means
163
+ * the rows themselves stay position-agnostic apart from their corners.
164
+ */
165
+ export const ListSeparator = () => _jsx(View, { style: styles.separator });
166
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/common/list.tsx"],"names":[],"mappings":"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","sourcesContent":["// Adwaita's boxed list, for code written in React Native.\n//\n// WHY this is here rather than in an app. `.boxed-list` is the single most\n// recognisable piece of GNOME chrome, and docs/research/react-native-first-showcase.md\n// measured what it actually is: a rounded, shadowed card whose rows carry\n// hairline separators, a hover tint and a press tint. Every one of those is a\n// style — there is no widget BEHAVIOUR in the look at all. So an app can build\n// it from `View` and `Pressable`, and `examples/tasks-nav` proves it.\n//\n// What an app should not have to do is rediscover the numbers. They come from\n// libadwaita's own compiled stylesheet, they are not obvious (the frame is a\n// three-part `box-shadow`, not a border; the first and last rows carry the\n// corner radii, not the container), and they move when libadwaita moves. That\n// is a platform's job, which is what this subpath is.\n//\n// Everything below is portable React Native underneath — no widget is created\n// that an app could not have created itself. Compare `Widget`/`SlotContent`\n// next door, which exist precisely because they CANNOT be written in RN.\nimport { type ReactNode } from \"react\"\nimport { useColorScheme } from \"../apis/index\"\nimport { Pressable } from \"../components/pressable\"\nimport { Text } from \"../components/text\"\nimport { View } from \"../components/view\"\nimport { PlatformColor, StyleSheet } from \"../style/index\"\nimport type { StyleProp } from \"../contracts\"\n\n/**\n * Where a row sits in its list, which is what decides its corner radii and\n * whether it draws a separator under itself.\n *\n * RN has no `:first-child`, so this is explicit. From a `FlatList` it is\n * `rowPosition(index, data.length)`; from a static list it is the same\n * expression over the array.\n */\nexport type ListRowPosition = \"first\" | \"middle\" | \"last\" | \"only\"\n\n/** The usual derivation, so no call site has to get the one-row case wrong. */\nexport const rowPosition = (index: number, count: number): ListRowPosition => {\n const first = index === 0\n const last = index === count - 1\n if (first && last) {\n return \"only\"\n }\n return first ? \"first\" : last ? \"last\" : \"middle\"\n}\n\n// libadwaita 1.9, /org/gnome/Adwaita/styles/gtk.css:\n// list.boxed-list { background-color: var(--card-bg-color); border-radius: 12px;\n// box-shadow: 0 0 0 1px RGB(0 0 6/3%),\n// 0 1px 3px 1px RGB(0 0 6/7%),\n// 0 2px 6px 2px RGB(0 0 6/3%); }\nconst RADIUS = 12\n\nconst styles = StyleSheet.create({\n list: {\n backgroundColor: PlatformColor(\"card-bg-color\"),\n borderRadius: RADIUS,\n // The frame is a shadow, not a border, and that distinction is load\n // bearing: a border would consume the box and inset every row by a pixel,\n // and no border draws the two soft drops underneath.\n boxShadow: [\n {\n offsetX: 0,\n offsetY: 0,\n spreadDistance: 1,\n color: \"rgba(0, 0, 6, 0.03)\",\n },\n {\n offsetX: 0,\n offsetY: 1,\n blurRadius: 3,\n spreadDistance: 1,\n color: \"rgba(0, 0, 6, 0.07)\",\n },\n {\n offsetX: 0,\n offsetY: 2,\n blurRadius: 6,\n spreadDistance: 2,\n color: \"rgba(0, 0, 6, 0.03)\",\n },\n ],\n },\n // `list > row { padding: 2px }` plus\n // `row > box.header { margin: 0 12px; border-spacing: 6px; min-height: 50px }`\n // — 2 + 50 + 2 + the 1px separator is the 55px row pitch measured off the\n // widget's own screenshot.\n row: {\n flexDirection: \"row\",\n alignItems: \"center\",\n minHeight: 54,\n paddingVertical: 2,\n paddingHorizontal: 14,\n gap: 6,\n },\n separatorUnder: {\n borderBottomWidth: 1,\n borderBottomColor: PlatformColor(\"card-shade-color\"),\n },\n firstRow: { borderTopLeftRadius: RADIUS, borderTopRightRadius: RADIUS },\n lastRow: {\n borderBottomLeftRadius: RADIUS,\n borderBottomRightRadius: RADIUS,\n },\n // `row > box.header > box.title { margin: 6px 0; border-spacing: 3px }`\n titleBox: { flex: 1, marginVertical: 6, gap: 3 },\n // The theme font is `Adwaita Sans 11`, i.e. 11pt = 14.67px, and Adwaita sets\n // the subtitle to `font-size: smaller` — Pango's one-step-down, 1/1.2. RN's\n // fontSize is numeric only (there is no `smaller`, no em, no inherited\n // percentage), so the computed value is what an app can write. Documented\n // in docs/api.md rather than hidden here.\n title: { fontSize: 14.7 },\n // `row label.subtitle { opacity: var(--dim-opacity) }`, 55% in the light\n // Adwaita palette.\n subtitle: { fontSize: 12.2, opacity: 0.55 },\n separator: {\n height: 1,\n backgroundColor: PlatformColor(\"card-shade-color\"),\n },\n})\n\n// Adwaita tints the hover and press states with a fraction of the FOREGROUND\n// colour (`color-mix(in srgb, currentColor 4%, transparent)`), so one literal\n// cannot serve both themes and CSS colour functions are not part of RN's\n// colour contract. Two literals plus the scheme is what an RN app writes on\n// every platform, and it is what this does.\nconst TINTS = {\n light: { hover: \"rgba(0, 0, 0, 0.04)\", press: \"rgba(0, 0, 0, 0.08)\" },\n dark: {\n hover: \"rgba(255, 255, 255, 0.04)\",\n press: \"rgba(255, 255, 255, 0.08)\",\n },\n} as const\n\nexport type ListProps = {\n children?: ReactNode\n style?: StyleProp\n testID?: string\n}\n\n/**\n * The `.boxed-list` frame: a rounded, shadowed card. Put `ListRow`s in it\n * directly, or a `FlatList` whose `renderItem` returns them.\n *\n * ```tsx\n * <List>\n * {tasks.map((task, index) => (\n * <ListRow\n * key={task.id}\n * title={task.title}\n * position={rowPosition(index, tasks.length)}\n * onPress={() => open(task)}\n * />\n * ))}\n * </List>\n * ```\n */\nexport const List = ({ children, style, testID }: ListProps): ReactNode => (\n <View\n testID={testID}\n style={[styles.list, style]}\n >\n {children}\n </View>\n)\n\nexport type ListRowProps = {\n /** Primary line. A string renders with the row's own typography; a node is\n * rendered as given, for a title that needs its own styling. */\n title?: ReactNode\n /** Second, dimmed line. Omitted rows are single-line, as `AdwActionRow`'s\n * are. */\n subtitle?: ReactNode\n /** Leading content — a checkbox, an icon, a colour swatch. */\n prefix?: ReactNode\n /** Trailing content — buttons, a value, a chevron. */\n suffix?: ReactNode\n /** Makes the row activatable: it gains the hover and press tints and calls\n * this. Without it the row is inert, like a non-`activatable`\n * `AdwActionRow`. */\n onPress?: () => void\n /** Position in the list, for the corner radii and the separator. Defaults\n * to `\"middle\"`, which is right for a row that is neither. */\n position?: ListRowPosition\n /** Draw the hairline under this row. Default true for every position except\n * the last — set it false when the list supplies its own separators (a\n * `FlatList`'s `ItemSeparatorComponent`, say). */\n separator?: boolean\n style?: StyleProp\n testID?: string\n}\n\n/**\n * One row of a {@link List} — `AdwActionRow`'s layout and states, built from\n * `Pressable`, `View` and `Text`.\n *\n * Hover and press come from `Pressable`'s state callback, so they cost what\n * they cost anywhere else in this platform: the hover path swaps a CSS class\n * on the widget without a React render (see components/pressable.tsx).\n *\n * **Not yet:** keyboard navigation between rows and a focus ring.\n * `GtkListBox` implements those as widget behaviour and RN has no focus model\n * for `View` to hang them on — `Pressable`'s state is `{pressed, hovered}`\n * with no `focused`. The ring itself is now drawable (`outlineWidth` and\n * friends), so what is missing is the state, not the paint.\n */\nexport const ListRow = ({\n title,\n subtitle,\n prefix,\n suffix,\n onPress,\n position = \"middle\",\n separator,\n style,\n testID,\n}: ListRowProps): ReactNode => {\n const scheme = useColorScheme() === \"dark\" ? \"dark\" : \"light\"\n const tints = TINTS[scheme]\n const isFirst = position === \"first\" || position === \"only\"\n const isLast = position === \"last\" || position === \"only\"\n const drawSeparator = separator ?? !isLast\n\n const body = (\n <>\n {prefix}\n <View style={styles.titleBox}>\n {typeof title === \"string\" ? (\n <Text style={styles.title}>{title}</Text>\n ) : (\n title\n )}\n {typeof subtitle === \"string\" ? (\n <Text style={styles.subtitle}>{subtitle}</Text>\n ) : (\n subtitle\n )}\n </View>\n {suffix}\n </>\n )\n\n const base = [\n styles.row,\n isFirst && styles.firstRow,\n isLast && styles.lastRow,\n drawSeparator && styles.separatorUnder,\n style,\n ]\n\n if (!onPress) {\n return (\n <View\n testID={testID}\n style={base}\n >\n {body}\n </View>\n )\n }\n\n return (\n <Pressable\n testID={testID}\n onPress={onPress}\n // The tint goes on the ROW, which is why the corner radii above have to\n // be on the row too: a hovered first row must round its own top corners\n // or the highlight paints square over the card's rounded ones.\n style={({ hovered, pressed }) => [\n ...base,\n hovered && { backgroundColor: tints.hover },\n pressed && { backgroundColor: tints.press },\n ]}\n >\n {body}\n </Pressable>\n )\n}\n\n/**\n * The hairline between two rows, for lists that draw their own separators —\n * a `FlatList`'s `ItemSeparatorComponent` is exactly this, and using it means\n * the rows themselves stay position-agnostic apart from their corners.\n */\nexport const ListSeparator = (): ReactNode => <View style={styles.separator} />\n"]}
@@ -59,25 +59,4 @@ export type ReactNativeLayoutProps = {
59
59
  style?: StyleProp;
60
60
  onLayout?: (event: LayoutEvent) => void;
61
61
  };
62
- /**
63
- * Give a GTK component React Native layout, keeping every prop it already
64
- * has.
65
- *
66
- * ```tsx
67
- * const Button = wrapReactNative(GtkButton)
68
- *
69
- * <View style={{ flexDirection: "row", gap: 8 }}>
70
- * <Button style={{ flex: 1 }} label="Save" onClicked={save} />
71
- * </View>
72
- * ```
73
- *
74
- * The result is generic in the wrapped component's props, so `label`,
75
- * `onClicked` and everything else gtkx binds keep their types; `style` and
76
- * `onLayout` are added on top.
77
- *
78
- * **Outside React Native layout it steps aside.** The same widget is often
79
- * used in a pure GTK slot — a HeaderBar's `start`, a ToolbarView's `topBar` —
80
- * where there is no Yoga tree to join. The wrapper detects that and renders
81
- * the bare component, so one exported symbol works in both worlds.
82
- */
83
- export declare const wrapReactNative: <P extends object>(Component: ComponentType<P>) => ComponentType<P & ReactNativeLayoutProps>;
62
+ export declare const wrapReactNative: <P extends object>(Component: ComponentType<P>, widgetName?: string) => ComponentType<P & ReactNativeLayoutProps>;
@@ -11,8 +11,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
11
11
  //
12
12
  // The visual half of the style (background, border, radius) is applied as a
13
13
  // GTK CSS class, the same mechanism View uses.
14
- import { useContext, useRef } from "react";
15
- import { HostNodeContext } from "../components/host-node";
14
+ import { isValidElement, useContext, useRef, } from "react";
15
+ import { HostNodeContext, SlotContext, } from "../components/host-node";
16
16
  import { useLayoutChild, } from "../components/use-layout-child";
17
17
  import { GtkBox } from "../gtkx/bridge/index";
18
18
  /**
@@ -92,7 +92,73 @@ export const useWidgetLayout = (ref, options = {}) => {
92
92
  * where there is no Yoga tree to join. The wrapper detects that and renders
93
93
  * the bare component, so one exported symbol works in both worlds.
94
94
  */
95
- export const wrapReactNative = (Component) => {
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";
96
162
  const InLayout = (props) => {
97
163
  const { style, onLayout, ...rest } = props;
98
164
  const boxRef = useRef(null);
@@ -117,6 +183,10 @@ export const wrapReactNative = (Component) => {
117
183
  hexpand: inner.hexpand ?? true, vexpand: inner.vexpand ?? true }) }));
118
184
  };
119
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);
120
190
  // Read the context directly rather than through useHostNode, which throws
121
191
  // by design: here its absence is a supported case, not a mistake. The
122
192
  // same widget is often used in a pure GTK slot (a HeaderBar's `start`, a
@@ -125,16 +195,13 @@ export const wrapReactNative = (Component) => {
125
195
  if (!host) {
126
196
  // style/onLayout are meaningless without a Yoga tree — drop them rather
127
197
  // than forward them onto a GObject that has no such properties.
128
- const rest = { ...props };
198
+ const rest = { ...withSlots };
129
199
  delete rest.style;
130
200
  delete rest.onLayout;
131
201
  return _jsx(Component, { ...rest });
132
202
  }
133
- return _jsx(InLayout, { ...props });
203
+ return _jsx(InLayout, { ...withSlots });
134
204
  };
135
- const name = Component.displayName ??
136
- Component.name ??
137
- "Widget";
138
205
  InLayout.displayName = `wrapReactNative(${name}).InLayout`;
139
206
  Wrapped.displayName = `wrapReactNative(${name})`;
140
207
  return Wrapped;
@@ -1 +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 { useContext, useRef, type ComponentType, type ReactNode } from \"react\"\nimport { HostNodeContext } 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 */\nexport const wrapReactNative = <P extends object>(\n Component: ComponentType<P>,\n): ComponentType<P & ReactNativeLayoutProps> => {\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 // 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 = { ...props } as Partial<ReactNativeLayoutProps>\n delete rest.style\n delete rest.onLayout\n return <Component {...(rest as P)} />\n }\n return <InLayout {...props} />\n }\n\n const name =\n (Component as { displayName?: string; name?: string }).displayName ??\n (Component as { name?: string }).name ??\n \"Widget\"\n InLayout.displayName = `wrapReactNative(${name}).InLayout`\n Wrapped.displayName = `wrapReactNative(${name})`\n return Wrapped\n}\n"]}
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../../src/common/widget.tsx"],"names":[],"mappings":";AAAA,wEAAwE;AACxE,EAAE;AACF,4EAA4E;AAC5E,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAC9E,gDAAgD;AAChD,EAAE;AACF,4EAA4E;AAC5E,+CAA+C;AAC/C,OAAO,EACL,cAAc,EACd,UAAU,EACV,MAAM,GAGP,MAAM,OAAO,CAAA;AACd,OAAO,EACL,eAAe,EACf,WAAW,GAEZ,MAAM,yBAAyB,CAAA;AAChC,OAAO,EACL,cAAc,GAEf,MAAM,gCAAgC,CAAA;AAEvC,OAAO,EAAE,MAAM,EAAY,MAAM,sBAAsB,CAAA;AAYvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAe,EAAE,EAAE;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,yDAAyD;IACzD,MAAM,MAAM,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE;QAC1C,KAAK;QACL,QAAQ;QACR,sEAAsE;QACtE,6DAA6D;QAC7D,iBAAiB,EAAE,IAAI;KACxB,CAAC,CAAA;IAEF,OAAO,CACL,KAAC,MAAM,IACL,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,YAE5C,QAAQ,GACF,CACV,CAAA;AACH,CAAC,CAAA;AAOD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,GAAmC,EACnC,UAAkC,EAAE,EACrB,EAAE;IACjB,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE;QACvC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,iBAAiB,EAAE,IAAI;KACxB,CAAC,CAAA;IACF,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA;AASD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,oEAAoE;AACpE,gDAAgD;AAChD,EAAE;AACF,2EAA2E;AAC3E,0EAA0E;AAC1E,uEAAuE;AACvE,2EAA2E;AAC3E,qEAAqE;AACrE,6EAA6E;AAC7E,sEAAsE;AACtE,wEAAwE;AACxE,2EAA2E;AAC3E,yEAAyE;AACzE,kBAAkB;AAClB,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,2EAA2E;AAC3E,2EAA2E;AAC3E,wEAAwE;AACxE,2EAA2E;AAC3E,MAAM,YAAY,GAAG,CAAC,EACpB,QAAQ,EACR,QAAQ,GAIT,EAAE,EAAE,CAAC;AACJ,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AACxE,0EAA0E;AAC1E,2CAA2C;AAC3C,KAAC,WAAW,CAAC,QAAQ,IAAC,KAAK,EAAE,QAAQ,YACnC,KAAC,eAAe,CAAC,QAAQ,IAAC,KAAK,EAAE,IAAI,YAAG,QAAQ,GAA4B,GACvD,CACxB,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,KAAc,EAAW,EAAE,CAC/C,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;AAE7E,uEAAuE;AACvE,4EAA4E;AAC5E,4EAA4E;AAC5E,8BAA8B;AAC9B,MAAM,kBAAkB,GAAG,CAAmB,KAAQ,EAAE,MAAc,EAAK,EAAE;IAC3E,IAAI,OAA4C,CAAA;IAChD,KAAK,MAAM,GAAG,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,GAAG,KAAK,KAAK,EAAE,CAAC;YAClB,SAAQ;QACV,CAAC;QACD,MAAM,KAAK,GAAI,KAAiC,CAAC,GAAG,CAAC,CAAA;QACrD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,SAAQ;QACV,CAAC;QACD,OAAO,KAAK,EAAE,CAAA;QACd,OAAO,CAAC,GAAG,CAAC,GAAG;QACb,wEAAwE;QACxE,qDAAqD;QACrD,KAAC,YAAY,IACX,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,YAE1D,KAAkB,GACN,CAChB,CAAA;IACH,CAAC;IACD,6EAA6E;IAC7E,iEAAiE;IACjE,OAAO,OAAO,CAAC,CAAC,CAAE,EAAE,GAAG,KAAK,EAAE,GAAG,OAAO,EAAQ,CAAC,CAAC,CAAC,KAAK,CAAA;AAC1D,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,SAA2B;AAC3B,4EAA4E;AAC5E,sEAAsE;AACtE,mEAAmE;AACnE,UAAmB,EACwB,EAAE;IAC7C,MAAM,IAAI,GACR,UAAU;QACT,SAAqD,CAAC,WAAW;QACjE,SAA+B,CAAC,IAAI;QACrC,QAAQ,CAAA;IAEV,MAAM,QAAQ,GAAG,CAAC,KAAiC,EAAE,EAAE;QACrD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAA;QAC1C,MAAM,MAAM,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;QAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE;YAC1C,KAAK;YACL,QAAQ;YACR,qEAAqE;YACrE,yDAAyD;YACzD,iBAAiB,EAAE,IAAI;SACxB,CAAC,CAAA;QACF,MAAM,KAAK,GAAG,IAGb,CAAA;QACD,OAAO,CACL,KAAC,MAAM,IAAC,GAAG,EAAE,MAAM,YACjB,KAAC,SAAS,OACJ,KAAK;gBACT,mEAAmE;gBACnE,mEAAmE;gBACnE,yDAAyD;gBACzD,UAAU,EACR,QAAQ;oBACN,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC;oBACzC,CAAC,CAAC,KAAK,CAAC,UAAU;gBAEtB,oEAAoE;gBACpE,8DAA8D;gBAC9D,8CAA8C;gBAC9C,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI,EAC9B,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,IAAI,GAC9B,GACK,CACV,CAAA;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,CAAC,KAAiC,EAAE,EAAE;QACpD,0EAA0E;QAC1E,0EAA0E;QAC1E,kCAAkC;QAClC,MAAM,SAAS,GAAG,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;QACjD,0EAA0E;QAC1E,sEAAsE;QACtE,yEAAyE;QACzE,+DAA+D;QAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,eAAe,CAAC,CAAA;QACxC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,wEAAwE;YACxE,gEAAgE;YAChE,MAAM,IAAI,GAAG,EAAE,GAAG,SAAS,EAAqC,CAAA;YAChE,OAAO,IAAI,CAAC,KAAK,CAAA;YACjB,OAAO,IAAI,CAAC,QAAQ,CAAA;YACpB,OAAO,KAAC,SAAS,OAAM,IAAU,GAAI,CAAA;QACvC,CAAC;QACD,OAAO,KAAC,QAAQ,OAAK,SAAS,GAAI,CAAA;IACpC,CAAC,CAAA;IAED,QAAQ,CAAC,WAAW,GAAG,mBAAmB,IAAI,YAAY,CAAA;IAC1D,OAAO,CAAC,WAAW,GAAG,mBAAmB,IAAI,GAAG,CAAA;IAChD,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA","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,5 +1,6 @@
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";
4
5
  type AnimatedValue = number | string;
5
6
  type AnimatedNode = {
@@ -16,15 +17,16 @@ export type AnimatedViewStyle = Omit<FlatStyle, "opacity" | "transform"> & {
16
17
  opacity?: number | AnimatedNode;
17
18
  transform?: (TransformPart | AnimatedTransformPart)[];
18
19
  };
19
- export type AnimatedViewProps = {
20
+ export type AnimatedViewProps = ResponderProps & {
20
21
  style?: AnimatedViewStyle | AnimatedViewStyle[];
21
22
  children?: ReactNode;
22
23
  onLayout?: (event: LayoutEvent) => void;
23
24
  testID?: string;
24
25
  };
25
26
  export declare const Animated: {
26
- View: ({ style, children, onLayout, testID, }: AnimatedViewProps) => React.JSX.Element;
27
+ View: ({ style, children, onLayout, testID, ...responderProps }: AnimatedViewProps) => React.JSX.Element;
27
28
  Value: typeof import("../animated/value").AnimatedValue;
29
+ ValueXY: typeof import("../animated/value-xy").AnimatedValueXY;
28
30
  timing(value: import("../animated/value").AnimatedValue, config: import("../animated/types").TimingConfig): import("../animated/types").CompositeAnimation;
29
31
  spring(value: import("../animated/value").AnimatedValue, config: import("../animated/types").SpringConfig): import("../animated/types").CompositeAnimation;
30
32
  sequence(animations: import("../animated/types").CompositeAnimation[]): import("../animated/types").CompositeAnimation;
@@ -2,6 +2,7 @@ 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
7
  import { setStoredTransform } from "./rect-store";
7
8
  import { useLayoutChild, useRnContainer, } from "./use-layout-child";
@@ -84,7 +85,7 @@ const splitAnimated = (style) => {
84
85
  // widget (opacity) and to the rect store (the transform applied by the
85
86
  // parent's allocate), on top of the engine-committed base rect — the fast
86
87
  // path measured in the spike. Animation frames never touch Yoga.
87
- const AnimatedView = ({ style, children, onLayout, testID, }) => {
88
+ const AnimatedView = ({ style, children, onLayout, testID, ...responderProps }) => {
88
89
  const widgetRef = useRef(null);
89
90
  const { staticStyle, opacity, slots } = splitAnimated(style);
90
91
  const { host, node, cssClass } = useLayoutChild(widgetRef, {
@@ -92,6 +93,7 @@ const AnimatedView = ({ style, children, onLayout, testID, }) => {
92
93
  onLayout,
93
94
  });
94
95
  useRnContainer(widgetRef, node);
96
+ useResponder(widgetRef, responderProps);
95
97
  // The effect reads the slots of the render that (re)armed it; the values
96
98
  // inside are then owned by the listeners.
97
99
  const slotsRef = useRef(slots);