@gjsify/react-native 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/lib/esm/generated/own-exports.js +1 -0
- package/lib/esm/index.js +1 -1
- package/lib/esm/lists/components.js +1 -1
- package/lib/esm/lists/controller.js +1 -1
- package/lib/esm/primitives/intents.js +1 -1
- package/lib/esm/support-table.js +1 -1
- package/lib/types/generated/own-exports.d.ts +1 -0
- package/lib/types/index.d.ts +1 -1
- package/lib/types/lists/controller.d.ts +14 -58
- package/lib/types/support-table.d.ts +23 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -116,10 +116,10 @@ indistinguishable from a bug in the application, forever.
|
|
|
116
116
|
|
|
117
117
|
A few that are worth knowing before they surprise you:
|
|
118
118
|
|
|
119
|
-
- **`justify-between`** is refused. ADR 0032 § 6 maps it to `Gtk.CenterBox
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
a
|
|
119
|
+
- **`justify-between`** is refused. ADR 0032 § 6 maps it to `Gtk.CenterBox` — a
|
|
120
|
+
different WIDGET, not another property on this one, and WHICH widget it is depends
|
|
121
|
+
on the child count. This layer resolves properties for the widget it was handed
|
|
122
|
+
and has no children to count. Use a `flex-1` spacer or `gap-*`.
|
|
123
123
|
- **`active:`** is the only variant idiom that pays off, and it costs nothing: it
|
|
124
124
|
becomes a GTK CSS `:active` pseudo-class on the generated class, so GTK animates a
|
|
125
125
|
press with no re-render at all. A variant on a WIDGET property (`active:flex-1`) is
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=[`OWN_EXPORT_NAMES`,`PrimitiveError`,`SUPPORTED_NAMES`,`SUPPORT_TABLE`,`UnsupportedError`,`configureStyle`,`explainUnsupported`,`isImportable`,`isOwnExport`,`primitives`,`registerRootComponent`,`resetStyleConfig`,`styleConfig`];export{e as OWN_EXPORT_NAMES};
|
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"./primitives/errors.js";import{Alert as t}from"./apis/alert.js";import{Appearance as n}from"./apis/appearance.js";import{Dimensions as r}from"./apis/dimensions.js";import{Keyboard as i}from"./apis/keyboard.js";import{Linking as a}from"./apis/linking.js";import{Platform as o}from"./apis/platform.js";import{Share as s}from"./apis/share.js";import"./apis/index.js";import{SUPPORTED_NAMES as
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"./primitives/errors.js";import{Alert as t}from"./apis/alert.js";import{Appearance as n}from"./apis/appearance.js";import{Dimensions as r}from"./apis/dimensions.js";import{Keyboard as i}from"./apis/keyboard.js";import{Linking as a}from"./apis/linking.js";import{Platform as o}from"./apis/platform.js";import{Share as s}from"./apis/share.js";import"./apis/index.js";import{OWN_EXPORT_NAMES as c}from"./generated/own-exports.js";import{SUPPORTED_NAMES as l,SUPPORT_TABLE as u,explainUnsupported as d,isImportable as f,isOwnExport as p}from"./support-table.js";import{UnsupportedError as m}from"./unsupported.js";import{AppRegistry as h,registerRootComponent as g}from"./app-registry.js";import{configureStyle as _,resetStyleConfig as v,styleConfig as y}from"./style-config.js";import{ActivityIndicator as b,Button as x,Image as S,ImageBackground as C,KeyboardAvoidingView as w,Pressable as T,SafeAreaView as E,ScrollView as D,StatusBar as O,Switch as k,Text as A,TextInput as j,TouchableHighlight as M,TouchableOpacity as N,TouchableWithoutFeedback as P,View as F}from"./components.js";import{EventEmitter as I}from"./event-emitter.js";import{AccessibilityInfo as L,ActionSheetIOS as R,Animated as z,AppState as B,BackHandler as V,Clipboard as H,DevMenu as U,DevSettings as W,DeviceEventEmitter as G,DeviceInfo as K,DrawerLayoutAndroid as q,DynamicColorIOS as J,Easing as Y,I18nManager as X,InputAccessoryView as Z,InteractionManager as Q,LayoutAnimation as $,LogBox as ee,Modal as te,NativeAppEventEmitter as ne,NativeComponentRegistry as re,NativeDialogManagerAndroid as ie,NativeEventEmitter as ae,NativeModules as oe,Networking as se,PanResponder as ce,PermissionsAndroid as le,PixelRatio as ue,PlatformColor as de,ProgressBarAndroid as fe,PushNotificationIOS as pe,ReactNativeVersion as me,RefreshControl as he,RootTagContext as ge,Settings as _e,Systrace as ve,ToastAndroid as ye,Touchable as be,TouchableNativeFeedback as xe,TurboModuleRegistry as Se,UIManager as Ce,UTFSequence as we,Vibration as Te,VirtualViewMode as Ee,codegenNativeCommands as De,codegenNativeComponent as Oe,experimental_LayoutConformance as ke,findNodeHandle as Ae,processColor as je,registerCallableModule as Me,requireNativeComponent as Ne,unstable_NativeText as Pe,unstable_NativeView as Fe,unstable_TextAncestorContext as Ie,unstable_VirtualView as Le,useAnimatedColor as Re,useAnimatedValue as ze,useAnimatedValueXY as Be,usePressability as Ve}from"./generated/unsupported-exports.js";import{useColorScheme as He,useWindowDimensions as Ue}from"./hooks.js";import{FlatList as We,SectionList as Ge,VirtualizedList as Ke,VirtualizedSectionList as qe}from"./lists/components.js";import{StyleSheet as Je}from"./stylesheet.js";import{primitives_exports as Ye}from"./primitives/index.js";function unstable_batchedUpdates(e,t){return e(t)}export{L as AccessibilityInfo,R as ActionSheetIOS,b as ActivityIndicator,t as Alert,z as Animated,h as AppRegistry,B as AppState,n as Appearance,V as BackHandler,x as Button,H as Clipboard,U as DevMenu,W as DevSettings,G as DeviceEventEmitter,K as DeviceInfo,r as Dimensions,q as DrawerLayoutAndroid,J as DynamicColorIOS,Y as Easing,I as EventEmitter,We as FlatList,X as I18nManager,S as Image,C as ImageBackground,Z as InputAccessoryView,Q as InteractionManager,i as Keyboard,w as KeyboardAvoidingView,$ as LayoutAnimation,a as Linking,ee as LogBox,te as Modal,ne as NativeAppEventEmitter,re as NativeComponentRegistry,ie as NativeDialogManagerAndroid,ae as NativeEventEmitter,oe as NativeModules,se as Networking,c as OWN_EXPORT_NAMES,ce as PanResponder,le as PermissionsAndroid,ue as PixelRatio,o as Platform,de as PlatformColor,T as Pressable,e as PrimitiveError,fe as ProgressBarAndroid,pe as PushNotificationIOS,me as ReactNativeVersion,he as RefreshControl,ge as RootTagContext,l as SUPPORTED_NAMES,u as SUPPORT_TABLE,E as SafeAreaView,D as ScrollView,Ge as SectionList,_e as Settings,s as Share,O as StatusBar,Je as StyleSheet,k as Switch,ve as Systrace,A as Text,j as TextInput,ye as ToastAndroid,be as Touchable,M as TouchableHighlight,xe as TouchableNativeFeedback,N as TouchableOpacity,P as TouchableWithoutFeedback,Se as TurboModuleRegistry,Ce as UIManager,we as UTFSequence,m as UnsupportedError,Te as Vibration,F as View,Ee as VirtualViewMode,Ke as VirtualizedList,qe as VirtualizedSectionList,De as codegenNativeCommands,Oe as codegenNativeComponent,_ as configureStyle,ke as experimental_LayoutConformance,d as explainUnsupported,Ae as findNodeHandle,f as isImportable,p as isOwnExport,Ye as primitives,je as processColor,Me as registerCallableModule,g as registerRootComponent,Ne as requireNativeComponent,v as resetStyleConfig,y as styleConfig,Pe as unstable_NativeText,Fe as unstable_NativeView,Ie as unstable_TextAncestorContext,Le as unstable_VirtualView,unstable_batchedUpdates,Re as useAnimatedColor,ze as useAnimatedValue,Be as useAnimatedValueXY,He as useColorScheme,Ve as usePressability,Ue as useWindowDimensions};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"../primitives/errors.js";import{ParentProvider as t}from"../parent-context.js";import{nodeProps as n,usePlan as r}from"../components.js";import{
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"../primitives/errors.js";import{ParentProvider as t}from"../parent-context.js";import{nodeProps as n,usePlan as r}from"../components.js";import{createListController as i,onScrollNearEnd as a,rowKey as o}from"./controller.js";import{createElement as s,useEffect as c,useRef as l}from"react";const u="restyles the separator around a row, and a `Gtk.ListView` has no separator widget to restyle — `Gtk.ListView:show-separators` makes GTK draw the Adwaita line itself, between rows it owns. Give the row its own border instead (`border-b`), which is a real widget property you can change",d={highlight(){throw new e(`FlatList`,`separators.highlight`,u)},unhighlight(){throw new e(`FlatList`,`separators.unhighlight`,u)},updateProps(){throw new e(`FlatList`,`separators.updateProps`,u)}};function slot(e){return typeof e==`function`?s(e,{}):e??null}function useListFrame(e,t,n,o,s,u){let d=t,f=r(e,t),p=l(null),m=l(null),h=l(null),g=l(n);g.current=n,c(()=>{let e=p.current;if(s||e==null)return;let t=i(u===void 0?{}:{onRowError:u});return h.current=t,t.attach(e),t.setRows(g.current),()=>{h.current=null,t.dispose()}},[s]),c(()=>{h.current?.setRows(g.current)},[o,d.data,d.sections,d.extraData,d.renderItem]);let _=d.onEndReachedThreshold??.5,v=d.horizontal===!0?`horizontal`:`vertical`,y=d.onEndReached;return c(()=>{let e=m.current;if(!(y===void 0||e==null))return a(e,v,_,e=>y({distanceFromEnd:e}))},[y,v,_,s]),{rendered:f,listRef:e=>{p.current=e},scrollerRef:e=>{m.current=e}}}function renderFrame(r,i,a,o){let{rendered:c,listRef:l,scrollerRef:u}=r,{plan:d,inherited:f,extra:p,contentExtra:m,published:h}=c;if(d.content===null)throw new e(d.primitive,``,`has no content node, so there is no scroller to put the list in — the primitive table is wrong, not the call`);let wrap=(e,n)=>e===null?null:s(t,{value:h,key:n},e),g=a?wrap(slot(i.ListEmptyComponent),`empty`):s(d.content.tag,{...n(d.content,{},m),key:`scroller`,ref:u},s(`GtkListView`,{ref:l,...o?{orientation:`horizontal`}:{}}));return s(d.node.tag,n(d.node,f,p),wrap(slot(i.ListHeaderComponent),`header`),g,wrap(slot(i.ListFooterComponent),`footer`))}function flattenData(e){let t=e.data??null,n=e.getItemCount===void 0?t?.length??0:e.getItemCount(t),at=n=>e.getItem===void 0?t[n]:e.getItem(t,n),r=[];for(let t=0;t<n;t++){let n=o(at(t),t,e.keyExtractor);r.push({key:n,render:()=>renderOne(e.renderItem,at(t),t)})}return{rows:r,rowsKey:r.map(e=>e.key).join(`\0`)}}function flattenSections(e){let t=[];return(e.sections??[]).forEach((n,r)=>{let i=n.key??n.title??`section-${r}`;e.renderSectionHeader!==void 0&&t.push({key:`header:${i}`,render:()=>e.renderSectionHeader?.({section:n})??null}),n.data.forEach((n,r)=>{let a=o(n,r,e.keyExtractor);t.push({key:`${i}:${a}`,render:()=>renderOne(e.renderItem,n,r)})})}),{rows:t,rowsKey:t.map(e=>e.key).join(`\0`)}}function renderOne(t,n,r){if(t===void 0)throw new e(`FlatList`,`renderItem`,`was not given, so there is nothing to put in a row. React Native requires it too — and a list of empty rows is exactly the silent failure this layer refuses`);return t({item:n,index:r,separators:d})}function FlatList(e){let{rows:t,rowsKey:n}=flattenData(e),r=t.length===0&&e.ListEmptyComponent!==void 0;return renderFrame(useListFrame(`FlatList`,e,t,n,r,e.onRowError),e,r,e.horizontal===!0)}function VirtualizedList(e){let{rows:t,rowsKey:n}=flattenData(e),r=t.length===0&&e.ListEmptyComponent!==void 0;return renderFrame(useListFrame(`FlatList`,e,t,n,r,e.onRowError),e,r,e.horizontal===!0)}function SectionList(e){let{rows:t,rowsKey:n}=flattenSections(e),r=t.length===0&&e.ListEmptyComponent!==void 0;return renderFrame(useListFrame(`FlatList`,e,t,n,r,e.onRowError),e,r,e.horizontal===!0)}function VirtualizedSectionList(e){return SectionList(e)}export{FlatList,SectionList,VirtualizedList,VirtualizedSectionList};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../_virtual/_rolldown/runtime.js";import{ParentProvider as e}from"../parent-context.js";import
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{ParentProvider as e}from"../parent-context.js";import{createRoot as t}from"@gjsify/gtk-host/react";import{createElement as n}from"react";import{adopt as r,createElement as i,insert as a,widgetOf as o}from"@gjsify/gtk-host";import{ListController as s,onScrollNearEnd as c}from"@gjsify/gtk-host/list";const l={orientation:`vertical`,props:{},overlay:!1},RowBody=({row:e})=>e.render();function reactRows(s){let c=new Map,u=[],d=!1,schedule=()=>{d||(d=!0,queueMicrotask(()=>{d=!1;let e=[...c];c.clear();for(let[t,n]of e)t.render(n);let t=u.splice(0,u.length);for(let e of t)e.unmount()}))};return{mountRow(e){let n=i(`GtkBox`,{orientation:`vertical`});a(n,r(e));let c=s.onRowError;return t(o(n),c===void 0?{}:{onUncaughtError:c})},showRow(t,r){c.set(t,r===null?null:n(e,{value:l},n(RowBody,{row:r}))),schedule()},disposeRow(e){c.delete(e),u.push(e),schedule()}}}function createListController(e={}){return new s(reactRows(e))}function rowKey(e,t,n){if(n!==void 0)return n(e,t);if(typeof e==`object`&&e){let t=e;if(typeof t.key==`string`)return t.key;if(typeof t.id==`string`||typeof t.id==`number`)return String(t.id)}return String(t)}export{createListController,c as onScrollNearEnd,rowKey};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"../_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"./errors.js";const t={"flex-start":`start`,"flex-end":`end`,center:`center`,stretch:`fill`,baseline:`baseline`},n={"flex-start":`start`,"flex-end":`end`,center:`center`},r={left:{xalign:0,justify:`left`},center:{xalign:.5,justify:`center`},right:{xalign:1,justify:`right`},justify:{xalign:0,justify:`fill`}},cross=e=>e===`horizontal`?`valign`:`halign`,main=e=>e===`horizontal`?`halign`:`valign`;function resolveIntent(i){let{primitive:a,intent:o,orientation:s,widget:c,parent:l}=i,u={},d=[],f={},p={},m=null,h=l?.textAlign;if(o.alignChildren!==void 0){if(!c.box)throw new e(a,`items-*`,`aligns CHILDREN, and this primitive has none to align — the widget it becomes is not a box. Move the utility to the container`);f[cross(s)]=t[o.alignChildren]}if(o.distribute!==void 0){if(!c.box)throw new e(a,`justify-*`,`distributes CHILDREN along the main axis, and the widget this primitive becomes is not a box. Move the utility to the container`);let t=n[o.distribute];if(t===void 0)throw new e(a,`justify-${label(o.distribute)}`,"GTK's box has no main-axis justification, and ADR 0032 § 6's mapping for this one is `Gtk.CenterBox
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"./errors.js";const t={"flex-start":`start`,"flex-end":`end`,center:`center`,stretch:`fill`,baseline:`baseline`},n={"flex-start":`start`,"flex-end":`end`,center:`center`},r={left:{xalign:0,justify:`left`},center:{xalign:.5,justify:`center`},right:{xalign:1,justify:`right`},justify:{xalign:0,justify:`fill`}},cross=e=>e===`horizontal`?`valign`:`halign`,main=e=>e===`horizontal`?`halign`:`valign`;function resolveIntent(i){let{primitive:a,intent:o,orientation:s,widget:c,parent:l}=i,u={},d=[],f={},p={},m=null,h=l?.textAlign;if(o.alignChildren!==void 0){if(!c.box)throw new e(a,`items-*`,`aligns CHILDREN, and this primitive has none to align — the widget it becomes is not a box. Move the utility to the container`);f[cross(s)]=t[o.alignChildren]}if(o.distribute!==void 0){if(!c.box)throw new e(a,`justify-*`,`distributes CHILDREN along the main axis, and the widget this primitive becomes is not a box. Move the utility to the container`);let t=n[o.distribute];if(t===void 0)throw new e(a,`justify-${label(o.distribute)}`,"GTK's box has no main-axis justification, and ADR 0032 § 6's mapping for this one is `Gtk.CenterBox` — a different WIDGET, not another property on this one. The host's `slotted` policy used to be what blocked it, because it named a `remove` that gtk 4.22.4's `Gtk.CenterBox` does not install (it clears a slot with `set_center_widget(null)`); that block is gone, since `children.remove` is optional for a policy whose slots are all setters. What is missing is the CHILD COUNT — two or three children pick the widget, and this layer resolves properties for the widget it was handed, with no children to count. Spell the distribution with a spacer child (`flex-1`) or with `gap-*`");u[main(s)]=t}if(o.axisSpacing!==void 0){if(!c.box)throw new e(a,`gap-x-*/gap-y-*`,"is a `Gtk.Box:spacing`, and the widget this primitive becomes has none (measured, gtk-props.ts)");o.axisSpacing.axis===s&&(u.spacing=o.axisSpacing.pixels)}if(o.textAlign!==void 0&&(h=o.textAlign),h!==void 0&&c.alignsText){let e=r[h];u.xalign=e.xalign,u.justify=e.justify,h=void 0}if(o.expand!==void 0&&(l===void 0?p.expand=o.expand:u[l.orientation===`horizontal`?`hexpand`:`vexpand`]=!0),o.alignSelf!==void 0&&(l===void 0?p.alignSelf=o.alignSelf:u[cross(l.orientation)]=t[o.alignSelf]),o.overlay?.role===`child`)if(l===void 0)p.overlay=o.overlay;else if(l.overlay)m=`overlay`,placeAbsolute(a,o.overlay.edges,i.emittedProps,u,d);else throw new e(a,`absolute`,"positions this element on top of its parent, so the PARENT has to be a `Gtk.Overlay` — and it is not. A `View` becomes one as soon as one of its children is absolutely positioned, so the parent here is either not a `View` (a `ScrollView`, a `Pressable`, a `Text`) or the element is a root");return{props:u,css:d,childContext:{orientation:s,props:f,overlay:!1,...h===void 0?{}:{textAlign:h}},slot:m,remaining:p}}function placeAbsolute(t,n,r,i,a){let{top:o,right:s,bottom:c,left:l}=n;if((o!==void 0||c!==void 0)&&(i.valign=o!==void 0&&c!==void 0?`fill`:o===void 0?`end`:`start`,o!==void 0&&(i[`margin-top`]=o),c!==void 0&&(i[`margin-bottom`]=c)),!(l===void 0&&s===void 0)){if(r[`margin-start`]!==void 0||r[`margin-end`]!==void 0)throw new e(t,`absolute + ms-*/me-*`,"offsets a PHYSICAL edge (`left-*`/`right-*`/`inset-*`), which becomes GTK CSS, while `ms-*`/`me-*` become the widget properties `margin-start`/`margin-end`. GTK applies both and they ADD, where CSS would have let one win. Spell the horizontal margin one way");i.halign=l!==void 0&&s!==void 0?`fill`:l===void 0?`end`:`start`,l!==void 0&&a.push(`margin-left: ${l}px`),s!==void 0&&a.push(`margin-right: ${s}px`)}}const label=e=>e.replace(/^space-/,``);export{resolveIntent};
|
package/lib/esm/support-table.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";const e={View:{status:`partial`,tier:`P1`,gtk:`Gtk.Box, or Gtk.Overlay when a child is absolutely positioned`,reason:`The container primitive. Which widget it becomes depends on its children, not on the element.`,limits:[`An absolutely positioned child is placed by alignment plus a margin, because Gtk.Overlay positions an overlay child by its halign/valign and has no coordinate pair. An offset on only ONE edge of an axis therefore leaves the other axis at GTK’s default (fill), where React Native would keep the child at its static position — a position GTK has no way to express for an overlay child.`,`onLayout is refused: GTK reports allocation through Gtk.Widget.vfunc_size_allocate, a subclass override rather than a signal.`,`pointerEvents answers auto and none only. box-none and box-only split hit-testing between a widget and its subtree, and Gtk.Widget:can-target is one boolean for both.`]},Text:{status:`partial`,tier:`P1`,gtk:`Gtk.Label`,reason:`Wrapping is ON by default in React Native and OFF on a Gtk.Label, so the default is set explicitly.`,limits:[`A nested <Text> is refused by the host naming the tag: Gtk.Label takes no children (measured), so React Native’s inline-styling idiom has no counterpart. Use Pango markup through a ref, or separate labels.`,`ellipsizeMode="clip" is refused: PangoEllipsizeMode is NONE, START, MIDDLE, END (measured) and has no clip member, so honouring it would add an ellipsis the author asked not to have.`,`onPress is refused: a Gtk.Label emits no clicked signal (measured). Wrap it in a <Pressable>.`]},Pressable:{status:`partial`,tier:`P1`,gtk:`Gtk.Button (flat)`,reason:`Press state is a GTK CSS :active pseudo-class; children-as-a-function is implemented over the state flag, and costs nothing when it is unused.`,limits:[`children-as-a-function-of-{ pressed } reads the widget’s Gtk.StateFlags.ACTIVE through Gtk.Widget::state-flags-changed, because Gtk.Button installs no active property (measured). An element that does NOT use the function form subscribes to nothing — press styling written as active:* is CSS and never reaches the reconciler (ADR 0032 § 7), and pressWatchCount() is what holds the two apart.`,`onPressIn/onPressOut are refused: they report the press as an EVENT, and the state flag reports it as a state. Use the function form, or active:*.`,`onLongPress is refused: a long press is a Gtk.GestureLongPress controller added to the widget, not a property or a signal on it.`,`hitSlop is refused: GTK hit-tests the allocation and cannot grow it past the widget. Pad the button instead, which enlarges the real target.`]},ScrollView:{status:`partial`,tier:`P1`,gtk:`Gtk.ScrolledWindow + an implicit content box`,reason:`contentContainerStyle styles the inner box, which is a second styleable node.`,limits:[`onScroll and scrollEventThrottle are refused: GTK reports scroll position as notify::value on the Gtk.Adjustment behind hadjustment/vadjustment, an object rather than a signal on the scrolled window. Reach the adjustment through a ref.`,`scrollEnabled is refused: a Gtk.ScrolledWindow has no switch that stops scrolling, and setting both policies to never clamps the child as well as hiding the bars.`,`refreshControl is refused, matching RefreshControl’s own entry: GTK has no pull-to-refresh idiom.`,`Layout utilities that need a box (items-*, gap-*, justify-*) are refused on style and belong on contentContainerStyle — which is React Native’s own rule, not a GTK one.`]},ActivityIndicator:{status:`partial`,tier:`P1`,gtk:`Adw.Spinner`,reason:`Direct counterpart.`,limits:[`animating={false} hides the widget. MEASURED: Adw.Spinner installs 36 properties and every one is Gtk.Widget’s — there is nothing that stops it while it is on screen, so "not animating" is "not shown". That is also exactly what hidesWhenStopped asks for, which is why hidesWhenStopped is a declared no-op.`,`size="small"/"large" are 16 and 32 px — Gtk.IconSize’s own two steps — not React Native’s 20/36, which are Android dp and land between GTK’s steps. A numeric size is used verbatim.`]},TextInput:{status:`partial`,tier:`P1`,gtk:`Gtk.Entry / Gtk.TextView`,reason:`Single- versus multi-line is one prop in React Native and two different widgets in GTK.`,limits:[`multiline={true} is a Gtk.TextView, whose content lives in a Gtk.TextBuffer rather than in a property — MEASURED: 61 properties, no text. So value, defaultValue and onChangeText are refused by name on a multiline input, as are placeholder, maxLength and secureTextEntry, none of which Gtk.TextView installs. Single-line is complete.`,`onFocus/onBlur are refused: focus arrives through a Gtk.EventControllerFocus, a controller rather than a signal on the widget.`,`autoFocus is refused: Gtk.Widget.grab_focus() only works once the widget is mapped, which is a moment this layer does not own. Call it from a ref in an effect.`,`keyboardType maps onto Gtk.Entry:input-purpose; returnKeyType, autoCapitalize, autoCorrect and keyboardAppearance are declared no-ops, because they describe an on-screen keyboard.`]},Linking:{status:`partial`,tier:`P1`,gtk:`Gtk.UriLauncher`,reason:`openURL and canOpenURL only.`,limits:[`openURL asks can_launch FIRST and rejects when the answer is no. MEASURED: Gtk.UriLauncher.launch never calls back for a scheme with no handler (3 000 ms, no callback, exit 0), and a promise that never settles is worse than a rejection.`,`getInitialURL always resolves null. A desktop deep link arrives as Gio.Application::open AFTER startup, so there is no value to read before it — which is also what React Native returns for a normal launch.`,`addEventListener/addListener are refused: URL delivery is the application object’s own wiring (HANDLES_OPEN plus the open signal), above this layer.`,`openSettings and sendIntent are refused: a per-app settings page and an Android Intent have no desktop counterpart.`]},Switch:{status:`partial`,tier:`P1`,gtk:`Gtk.Switch`,reason:`Direct counterpart.`,limits:[`onValueChange binds notify::active, not state-set: state-set runs BEFORE the state changes and must return false to let the default handler proceed, and a handler that forgets makes the switch stick at exit 0.`,`trackColor and thumbColor are refused: Adwaita paints the switch from the theme accent and its track is a CSS subnode, not a property. Style it from the application stylesheet.`]},Platform:{status:`partial`,tier:`P1`,reason:`OS is "linux" | "macos" | "windows"; select() picks the default branch.`,limits:[`OS reports "linux", which is not a member of React Native’s own PlatformOSType. Reporting "macos" on a Linux desktop would be a lie select() cannot recover from, and "web" implies a DOM.`,`select() does NOT consult a native branch, for ADR 0032 § 9’s reason: a native branch is written for a React Native runtime, and handing it to a GTK build is a failure that surfaces in a window. A spec with neither this OS nor default is a named refusal rather than undefined.`,`Version throws by name: it is a mobile OS version and a desktop has no counterpart meaning the same thing.`,`constants throws by name: it is the native bridge’s constants object, and there is no bridge here.`]},Modal:{status:`planned`,tier:`P1`,gtk:`Adw.Dialog`,reason:`An Adw.Dialog cannot be an ordinary element. MEASURED on libadwaita 1.9.3: box.append(dialog) calls g_error() — SIGABRT and a core dump, not a catchable exception — but ONLY when the box is rooted in a window. A detached box accepts the append in silence, so a re-test on a bare box appears to disprove this and puts the primitive back. A dialog is PRESENTED against a parent, never parented by it, so this is a PORTAL and needs a host seam that does not exist yet.`},useColorScheme:{status:`supported`,tier:`P1`,gtk:`Adw.StyleManager.dark`,reason:`Follows the Adwaita colour scheme — the dark property, which is what the user is looking at, not color-scheme, which is what the application asked for.`},Share:{status:`partial`,tier:`P1`,gtk:`Gdk.Clipboard`,reason:`No desktop share sheet worth pretending about; copying the link is the honest mapping.`,limits:[`action is always sharedAction. dismissedAction means the user closed a share sheet without choosing, and nothing here asks the user anything — reporting a dismissal would be inventing an event. The constant is still exported, so a comparison against it resolves instead of failing to import.`,`activityType is always undefined: there is no activity to name.`,`Gtk.UriLauncher is NOT used here, although the planning entry named it. Launching a URI OPENS it in another application, which is a share sheet’s behavioural inverse — that call is Linking.openURL’s, and it is there.`]},AppRegistry:{status:`partial`,tier:`P1`,gtk:`Adw.Application + Adw.ApplicationWindow`,reason:`The entry point. Nothing renders without a window, so this is P1 despite being a shim.`,limits:[`runApplication takes gjsify options (applicationId is required) rather than React Native’s { rootTag, initialProps } — a phone host supplies the application identity, a desktop one IS the application.`,`unmountApplicationComponentAtRootTag throws: there is no root tag to address.`]},StyleSheet:{status:`partial`,tier:`P1`,gtk:`Gdk.Monitor.scale for hairlineWidth`,reason:`create/flatten/compose/hairlineWidth/absoluteFill. Style objects go through the same partition as classes (ADR 0032 § 4), which is why create can be identity.`,limits:[`create is IDENTITY, which is also what React Native’s own is now (it once registered styles and returned numeric ids). It cannot validate: half the vocabulary resolves against the parent (flex-1 is hexpand on a row and vexpand on a column), and create has no tree. The style object is validated where className is, at element resolution.`,`hairlineWidth is a GETTER, not a constant: Gdk.Display.get_default() is null before Gtk.init() (measured) and real code reads this at module scope, so before the display exists it answers 1. It takes the SMALLEST monitor scale, because GTK4 has no primary monitor (measured — get_primary_monitor is gone) and a hairline that is sub-pixel on a monitor can vanish, while one that is a device pixel too thick cannot.`,`setStyleAttributePreprocessor throws by name: it installs a process-wide transform on every style value, and configureStyle({ tokens }) is the scoped hook that already does that job.`]},FlatList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView + Gio.ListStore, owned by the component`,reason:`GTK virtualises for real. The list is NOT an ordinary element: a Gtk.ListView takes no children (measured — no append, add, insert, prepend, remove or set_child), so the component owns the view and drives the model from data, with React only inside the item factory.`,limits:[`data, renderItem, keyExtractor, ListEmptyComponent, ListHeaderComponent, ListFooterComponent, horizontal and onEndReached are answered. Everything else on React Native’s list surface is refused BY NAME in the primitive table, most of it because it configures React Native’s own virtualisation and Gtk.ListView does that job itself.`,`The header and footer are OUTSIDE the scroller, so they stay put while the rows scroll. Measured: putting them inside means wrapping the list in a Gtk.Box, which makes the scroller scroll a GtkViewport and allocates the list its whole content height (11 000 px for 500 rows) while it still realises only 205 of them — rows with no widget inside the allocated area.`,`A row’s React tree is rendered on a microtask, not inside the bind. GTK binds a row the moment the view is rooted in a window (measured), which under React is inside a commit or an effect — where a nested root cannot flush and the host refuses by name. A microtask drains before GTK’s next frame, so no row is ever painted empty.`,`A change to data replaces the model rows whose KEYS changed with one splice, which re-binds them; rows whose keys did not change are re-rendered in place instead, because GTK does not re-bind a row whose model object is unchanged (measured: items_changed over the same object does nothing).`,`onEndReached is bound to the scroller’s own Gtk.Adjustment (notify::value / upper / page-size) and fires once per arrival at the end. A list with nothing to scroll never fires, which is every list before it has been allocated.`,`renderItem’s separators.highlight/unhighlight/updateProps throw by name: a Gtk.ListView has no separator widget to restyle.`]},SectionList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView + one flattened Gio.ListStore`,reason:`The same component as FlatList, handed sections instead of data.`,limits:[`Every FlatList limit applies.`,`The sections are FLATTENED into one model with header ROWS, so a header scrolls with its section. GTK’s own sticky headers do exist — measured: Gtk.SectionModel is present, Gtk.FlattenListModel and every selection model implement it (Gio.ListStore and Gtk.StringList do not), Gtk.ListView.set_header_factory is there, and a flatten model over two stores answered get_section(0) = [0, 2] — and they are not used, because that route needs a second factory, a model of models, and Gtk.ListHeader’s start/end/n-items reconciled with React Native’s section object. stickySectionHeadersEnabled is therefore refused by name rather than silently false.`,`renderSectionFooter is not answered: a footer row would need the flattening to know where a section ends before the next one is read, which the header form does not.`]},VirtualizedList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView`,reason:`Its public surface is wide and mostly not worth honouring literally; the useful subset backs FlatList, and this name is that subset plus getItem/getItemCount.`,limits:[`Every FlatList limit applies.`,`getItem(data, index) and getItemCount(data) are answered, which is the one thing this name adds over FlatList. The virtualisation knobs — initialNumToRender, maxToRenderPerBatch, windowSize, updateCellsBatchingPeriod, removeClippedSubviews, getItemLayout — are refused by name: Gtk.ListView creates and recycles rows itself and installs no property that changes the batching (measured: 205 of 500 rows in a 400×300 window).`,`CellRendererComponent, debug, onLayout and the other internals of React Native’s own implementation are not answered at all — an unlisted prop is refused by name, which is the point.`]},VirtualizedSectionList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView`,reason:`The section-shaped sibling of VirtualizedList, which is SectionList here.`,limits:[`Every SectionList limit applies; it is the same component under React Native’s other name.`]},Image:{status:`partial`,tier:`P2`,gtk:`Gtk.Picture`,reason:`resizeMode becomes content-fit, and the default is inverted: React Native defaults to cover, a Gtk.Picture to contain (measured).`,limits:[`resizeMode answers cover, contain, stretch and center. repeat is refused by name: GtkContentFit is FILL, CONTAIN, COVER, SCALE_DOWN (measured) and has no tiling member — tiling is a Gdk.Paintable implementation, not a property of the widget that draws one.`,`source takes { uri } with a local path, a file: URI or a resource: URI. http:/https:/data: are refused by name (they need a fetch, a decoder and a cache — an async pipeline this layer does not own); a require() id is refused (it is an index into React Native’s asset registry, and ADR 0032 § 12 leaves the build chain to the consumer); an array is refused (it is the per-device-scale picker, and GTK scales one texture by the surface scale factor).`,`onLoad, onLoadStart, onLoadEnd, onError and onProgress are refused: Gtk.Picture emits NO signals at all (measured), and a file that does not exist leaves paintable null with no diagnostic — so a callback that never fires would hide exactly the case it exists to report.`,`blurRadius and tintColor are refused: a per-pixel effect is a Gsk render node, not a widget property.`]},ImageBackground:{status:`partial`,tier:`P2`,gtk:`Gtk.Picture in a Gtk.Overlay`,reason:`A picture with the children stacked over it. The picture is the overlay’s MAIN child, because a Gtk.Overlay paints every overlay child ABOVE it.`,limits:[`Every Image limit applies to source and resizeMode.`,`The element is sized by its PICTURE, not by its children — the opposite of React Native, where the image is absolutely positioned and contributes nothing. Measured: a Gtk.Overlay measures only its main child (a 9 px main child beside a 266 px overlay child gave the overlay 9 px), and gtk_overlay_set_measure_overlay() is a per-child METHOD rather than a property, so the host’s data-driven placement has no way to name it. Give the element a size, or let its parent stretch it (flex-1).`,`imageStyle styles the picture and style styles the container, as in React Native. imageRef is refused: this element’s ref is the Gtk.Overlay and overlay.get_child() is the picture.`]},TouchableOpacity:{status:`partial`,tier:`P2`,gtk:`Gtk.Button (flat)`,reason:`The same machinery as Pressable, written over it: one shared record of routes in the primitive table, one line of component.`,limits:[`activeOpacity is refused by name — write active:opacity-70. The pressed appearance is a GTK CSS :active declaration (ADR 0032 § 7), and honouring a raw number here would put a value into the styling path that did not come from the project’s token scale (ADR 0032 § 3).`,`Every Pressable limit applies: onPressIn/onPressOut, onLongPress, hitSlop and the press-delay props are refused by name.`]},TouchableHighlight:{status:`partial`,tier:`P2`,gtk:`Gtk.Button (flat)`,reason:`As TouchableOpacity. The pressed style is Adwaita’s own unless a variant says otherwise.`,limits:[`underlayColor is refused by name — write active:bg-<token>, for the same reason activeOpacity is: a raw colour would bypass the token scale that every other colour in this vocabulary comes from.`,`onShowUnderlay/onHideUnderlay are refused: there is no underlay widget to show.`,`Every TouchableOpacity limit applies.`]},TouchableWithoutFeedback:{status:`partial`,tier:`P2`,gtk:`Gtk.Box + Gtk.GestureClick`,reason:`No chrome, so no button: a vertical Gtk.Box like a View, with a Gtk.GestureClick added to it. Measured: Gtk.Button emits activate and clicked, a Gtk.Box emits neither, and Gtk.GestureClick emits pressed/released/stopped/unpaired-release.`,limits:[`It is a real box in the tree, not a transparent clone of its child — React Native clones the single child and attaches the handlers to it, and GTK has no way for a widget to be its own child. So it lays out like a View and takes one row of the parent’s box.`,`disabled becomes can-target: false rather than sensitive: false, because sensitive greys out every descendant of a wrapper that is supposed to have no appearance of its own.`,`onPress only. The rest of the Touchable surface is refused by name, as on TouchableOpacity.`]},Button:{status:`partial`,tier:`P2`,gtk:`Gtk.Button`,reason:`The one component whose React Native styling story is "you cannot", which GTK agrees with. title, onPress and disabled.`,limits:[`style and className are REFUSED by name, which is faithful: React Native’s Button takes neither. An Adwaita button is painted by the theme and by its own classes (suggested-action, destructive-action), which an application stylesheet sets. Use Pressable when you need to style it.`,`color is refused: it is the background on Android and the text colour on iOS — one prop, two meanings — and GTK’s answer to both is a class rather than a colour.`,`It takes no children: title writes Gtk.Button:label, and a text child would be a second authority for the same slot. React Native’s Button takes no children either.`]},Dimensions:{status:`partial`,tier:`P2`,gtk:`Gtk.Window allocation, Gdk.Monitor geometry`,reason:`get("window") is the window, not the screen — a desktop app is not full-screen, so the screen’s number would be wrong in the ordinary case. get("screen") is the monitor, because that is what it asks for.`,limits:[`get("window") throws by name when there is no window yet, which includes every read at module scope. There is no honest number before the application builds its window, and a zero divides. Read it from a component or an effect, or use useWindowDimensions.`,`The reported size is the window’s ALLOCATION, falling back to default-width/default-height before it has been allocated. It is deliberately NOT the Gdk.Surface size: measured, a 640×480 window has a 668×509 surface, which carries the client-side-decoration shadow.`,`The window is found through Gtk.Window.get_toplevels() (the mapped one first), so it answers for an application that built its own window as well as for one AppRegistry created.`,`fontScale comes from Gtk.Settings:gtk-xft-dpi / 1024 / 96 (measured: 98304, i.e. 1). set() throws by name: it is how React Native’s native side PUBLISHES metrics, and there is no bridge here.`]},useWindowDimensions:{status:`partial`,tier:`P2`,gtk:`Gdk.Surface notify::width/height, Gtk.Window allocation`,reason:`The hook form of Dimensions, through useSyncExternalStore so a resize between render and commit cannot tear.`,limits:[`The NOTIFIER and the VALUE are different objects, and it is measured rather than chosen: Gtk.Widget installs no width or height property and emits no size-allocate signal (the wall <View onLayout> is refused against), while Gdk.Surface installs read-only width and height and notifies on them. So the surface says when and the window says what.`,`PRECONDITION NOT MEASURED: whether the window’s allocation is already updated inside that handler. A compositor-driven resize cannot be triggered from a probe — set_default_size on a mapped window is a no-op (measured) — so this is recorded as unknown rather than guessed.`,`Every Dimensions limit applies to the value itself.`]},Alert:{status:`partial`,tier:`P2`,gtk:`Adw.AlertDialog`,reason:`Direct counterpart, and buildable where Modal is not: Alert is a FUNCTION CALL, so no element is ever inserted into a widget. Measured on libadwaita 1.9.3 — present(null) from a plain function, with no parent and no window, returned with no diagnostic.`,limits:[`destructive maps to Adw.ResponseAppearance.DESTRUCTIVE and cancel becomes the dialog’s CLOSE RESPONSE, which is stronger than an appearance: it is what Escape and the compositor’s close both produce. The first non-cancel button becomes the default response, which is Adwaita’s convention and has no React Native counterpart.`,`Response IDs are positional (response-0, …) rather than derived from the label, so two buttons with the same text do not collide into one response.`,`prompt throws by name: it is iOS-only, and its contract (four overloads, a login/password variant, a callback whose arity depends on the type) is what makes it a refusal rather than a port. Adw.AlertDialog:extra-child is there if you want to build it.`]},Appearance:{status:`supported`,tier:`P2`,gtk:`Adw.StyleManager`,reason:`The imperative sibling of useColorScheme, over the SAME reader — getColorScheme reads Adw.StyleManager:dark (what the user is looking at) and setColorScheme writes :color-scheme (what the application asked for), which is exactly the split React Native’s getter and setter have.`},SafeAreaView:{status:`partial`,tier:`P2`,gtk:`Gtk.Box`,reason:`The INSET has no desktop meaning; the layout does. It is a View in every other respect, and it has to be a real export to be imported.`,limits:[`The safe-area insets are always zero, because a desktop window has none. What is a no-op is the inset — not the box, not the column, not the children: a component that rendered nothing would be a screen that silently disappeared.`,`It is a View, so every View limit applies (the absolute-child overlay switch, onLayout refused, pointerEvents auto/none only).`]},StatusBar:{status:`partial`,tier:`P2`,reason:`A desktop window has no status bar to configure, and <StatusBar/> is in the first ten lines of most React Native screens — so it renders NOTHING and says so, rather than failing to import.`,limits:[`The component renders null. Its declarative props (barStyle, hidden, backgroundColor, translucent, animated) are accepted and do nothing, which is what a declared no-op is.`,`Every imperative static REFUSES by name: setBarStyle, setHidden, setBackgroundColor, setTranslucent, setNetworkActivityIndicatorVisible, pushStackEntry, popStackEntry, replaceStackEntry. A setter that appears to work is indistinguishable from one that does.`,`currentHeight throws by name rather than answering 0: code reads it straight into a layout (paddingTop: StatusBar.currentHeight), and a number here would inset every ported screen by a bar that is not there.`]},KeyboardAvoidingView:{status:`partial`,tier:`P2`,gtk:`Gtk.Box`,reason:`No on-screen keyboard eats a desktop window layout, so the AVOIDING is the no-op. React Native’s KeyboardAvoidingView is a View that changes its own height; what is left here is the View.`,limits:[`behavior, keyboardVerticalOffset and enabled are declared no-ops: they describe the avoidance, which is already off.`,`contentContainerStyle is refused by name: it styles the inner view React Native adds to do the avoiding, and there is no second node here. Put it on style.`,`It is a View, so every View limit applies.`]},Keyboard:{status:`partial`,tier:`P2`,reason:`Its events are on-screen-keyboard events, which do not occur — so the questions with a correct answer get it and the ones whose answer would have to be invented refuse.`,limits:[`addListener REFUSES by name, for every event. A subscription that resolves and then never fires is the silent drop this layer exists to remove: a keyboardDidShow handler that never runs looks like a bug in your own code, for ever.`,`dismiss() and removeAllListeners() are no-ops — nothing was shown, so nothing has to be hidden, and nothing is registered to remove.`,`isVisible() answers false and metrics() answers undefined, which are React Native’s own answers for a keyboard that is not up.`,`scheduleLayoutAnimation throws by name: it is LayoutAnimation’s subsystem (tier P3), not a keyboard behaviour.`]},RefreshControl:{status:`refused`,reason:`GTK has no pull-to-refresh idiom and should not grow one. Give the desktop build a refresh action instead.`},AppState:{status:`planned`,tier:`P3`,gtk:`Gtk.Application / Gdk.Surface state`,reason:`active/background from window focus and visibility.`},PixelRatio:{status:`planned`,tier:`P3`,gtk:`Gdk.Surface.scale-factor`,reason:`The scale factor of the surface the widget is on.`},PlatformColor:{status:`planned`,tier:`P3`,gtk:`Adwaita named colours`,reason:`Maps unusually well — GTK’s palette is exactly this idea.`},AccessibilityInfo:{status:`planned`,tier:`P3`,gtk:`Gtk.Accessible / AT-SPI`,reason:`The highest-value P3 entry: GTK’s accessibility model is strong and the props map onto it well.`},Animated:{status:`planned`,tier:`P3`,gtk:`Adw.TimedAnimation / Adw.SpringAnimation`,reason:`Genuinely mappable, but it is a subsystem rather than a component. Doing it badly is worse than not doing it.`},Easing:{status:`planned`,tier:`P3`,reason:`Pure maths; it lands with Animated or not at all.`},LayoutAnimation:{status:`planned`,tier:`P3`,reason:`Needs an animated layout pass, which is the same subsystem as Animated.`},InteractionManager:{status:`planned`,tier:`P3`,reason:`Deferring work until interactions settle; a main-loop idle source is the counterpart.`},useAnimatedValue:{status:`planned`,tier:`P3`,reason:`Part of the Animated subsystem.`},useAnimatedValueXY:{status:`planned`,tier:`P3`,reason:`Part of the Animated subsystem.`},useAnimatedColor:{status:`planned`,tier:`P3`,reason:`Part of the Animated subsystem.`},PanResponder:{status:`planned`,tier:`P3`,gtk:`Gtk.Gesture* controllers`,reason:`The controllers exist; the arbitration model is not React Native’s, so this is its own project.`},usePressability:{status:`planned`,tier:`P3`,reason:`The hook behind the Touchable family; it lands with the gesture work.`},DrawerLayoutAndroid:{status:`planned`,tier:`P3`,gtk:`Adw.OverlaySplitView`,reason:`The pattern survives on a desktop even though the component is Android-only.`},I18nManager:{status:`planned`,tier:`P3`,gtk:`Gtk.Widget.direction`,reason:`RTL is a widget-direction question on GTK.`},LogBox:{status:`no-desktop-meaning`,tier:`P3`,reason:`A development overlay for a phone; the console is the desktop equivalent.`},Systrace:{status:`no-desktop-meaning`,reason:`Android systrace has no desktop counterpart.`},Vibration:{status:`no-desktop-meaning`,reason:`A desktop machine does not vibrate.`},ToastAndroid:{status:`planned`,tier:`P3`,gtk:`Adw.Toast`,reason:`Android-only by name, and Adwaita has the exact widget.`},InputAccessoryView:{status:`no-desktop-meaning`,reason:`An iOS keyboard accessory bar; there is no keyboard to accessorise.`},TouchableNativeFeedback:{status:`planned`,tier:`P3`,gtk:`Gtk.Button`,reason:`Android-only ripple; shimmed to its portable sibling.`},ActionSheetIOS:{status:`planned`,tier:`P3`,gtk:`Adw.AlertDialog with responses`,reason:`iOS-only by name; the pattern is a dialog with choices.`},PushNotificationIOS:{status:`refused`,reason:`Platform notification plumbing. A desktop app uses Gio.Notification directly.`},PermissionsAndroid:{status:`refused`,reason:`Android runtime permissions have no desktop counterpart; portals are asked for at use time.`},Settings:{status:`refused`,reason:`An iOS user-defaults bridge. GSettings is the desktop answer and is not this API.`},Clipboard:{status:`planned`,tier:`P3`,gtk:`Gdk.Clipboard`,reason:`Deprecated upstream in favour of a community package, but trivial here.`},BackHandler:{status:`planned`,tier:`P3`,reason:'An Android hardware back button. Routing has landed and this did NOT arrive with it, which the earlier reason assumed it would: BackHandler INTERCEPTS a back press and consumes it, and MEASURED on libadwaita 1.9.3, Adw.NavigationView emits `popped` AFTER the fact and has no vetoable "about to pop" signal at all — its only prevention is Adw.NavigationPage:can-pop, a property rather than an event. The honest counterpart is usePreventRemove, which the routing layer does honour through its popped bridge; this name needs a key controller of its own on the window.'},DynamicColorIOS:{status:`planned`,tier:`P3`,reason:`A light/dark colour pair; the Adwaita scheme already provides the switch.`},processColor:{status:`planned`,tier:`P3`,reason:`Colour string to a platform value; a Gdk.RGBA on this side.`},UTFSequence:{status:`planned`,tier:`P3`,reason:`A table of unicode constants. Pure data, no platform in it.`},experimental_LayoutConformance:{status:`refused`,reason:`An experimental switch between two React Native layout implementations. Neither is used here.`},unstable_VirtualView:{status:`refused`,reason:`Unstable React Native internal.`},VirtualViewMode:{status:`refused`,reason:`Unstable React Native internal.`},unstable_NativeText:{status:`refused`,reason:`Unstable React Native internal.`},unstable_NativeView:{status:`refused`,reason:`Unstable React Native internal.`},unstable_TextAncestorContext:{status:`refused`,reason:`Unstable React Native internal.`},ReactNativeVersion:{status:`planned`,tier:`P3`,reason:`The version this layer targets, reported honestly rather than spoofed.`},DeviceInfo:{status:`refused`,reason:`A native module surface describing a phone.`},DevMenu:{status:`no-desktop-meaning`,reason:`The shake-to-open developer menu.`},DevSettings:{status:`no-desktop-meaning`,reason:`Development-client settings for a phone runtime.`},NativeModules:{status:`refused`,reason:`The native-module bridge. There is no bridge here; this layer renders in-process onto GTK.`},TurboModuleRegistry:{status:`refused`,reason:`The TurboModule lookup. Same reason as NativeModules.`},NativeComponentRegistry:{status:`refused`,reason:`Fabric component registration. The host owns widget creation.`},requireNativeComponent:{status:`refused`,reason:`Looks up a native view manager. Write a GTK widget descriptor instead.`},codegenNativeComponent:{status:`refused`,reason:`React Native codegen for Fabric components.`},codegenNativeCommands:{status:`refused`,reason:`React Native codegen for Fabric commands.`},UIManager:{status:`refused`,reason:`The legacy view-manager surface. The GTK host is the view manager here.`},findNodeHandle:{status:`refused`,reason:`Returns a native view tag. A ref here is the Gtk.Widget itself, which is more useful.`},registerCallableModule:{status:`refused`,reason:`Registers a module callable from the native side. There is no native side.`},Networking:{status:`refused`,reason:`React Native’s XHR internals. Use fetch, which gjsify provides.`},NativeDialogManagerAndroid:{status:`refused`,reason:`An Android dialog native module. Alert is the portable spelling.`},ProgressBarAndroid:{status:`planned`,tier:`P3`,gtk:`Gtk.ProgressBar`,reason:`Android-only by name; GTK has the widget.`},Touchable:{status:`refused`,reason:`The legacy mixin behind the Touchable family, not a public component.`},EventEmitter:{status:`supported`,reason:`Pure JavaScript; nothing in it touches a platform.`},NativeEventEmitter:{status:`planned`,tier:`P3`,reason:`It would construct and subscribe, but nothing native would ever emit into it — shipping that needs a decision, not a class.`},DeviceEventEmitter:{status:`planned`,tier:`P3`,reason:`The global emitter. Lands with NativeEventEmitter, and for the same reason.`},NativeAppEventEmitter:{status:`refused`,reason:`The legacy iOS app-event emitter.`},RootTagContext:{status:`refused`,reason:`A React Native surface identifier. This layer has one root per Adw window.`},unstable_batchedUpdates:{status:`supported`,reason:`React 19 batches automatically; this is the identity call it already is upstream.`}},t={router:{status:`partial`,tier:`P1`,gtk:`Adw.NavigationView (push/pop) via React Navigation’s StackActions`,reason:`push, back, replace and navigate — the four methods the measured application calls, 19 of the 27 calls being push.`,limits:[`push falls back to NAVIGATE when the navigator that owns the target is not mounted yet. StackActions.push is dispatched at ONE navigator and an href can name a screen several navigators down; for an unmounted navigator, arriving there IS the new entry, so the fallback is correct rather than approximate — but a push into a mounted navigator that is not a stack cannot add an entry, because PUSH is the stack router’s action and no other router answers it.`,`back() THROWS when there is nothing to go back to, where expo-router and React Navigation both return quietly. A quiet return is a back button that does nothing with no message, which is the silent drop this layer refuses everywhere else.`,`setParams, dismiss, dismissAll, dismissTo and canGoBack are not implemented. They are not in the measured surface, and each is a named refusal rather than an undefined property read.`,`An href is a path. expo-router also accepts an object form ({ pathname, params }); this layer takes the string, which is what all 27 measured calls pass.`]},useLocalSearchParams:{status:`partial`,tier:`P1`,reason:`The current route’s params — the [param] values and the query string — read through React Navigation’s own useRoute().`,limits:[`Values are strings, because a URL segment is one. A number or a boolean that arrived through a dispatched param is stringified; anything structural (an object, an array) is left out, and a [param] cannot produce one. expo-router additionally returns string[] for a repeated query key, which this layer does not.`,`React Navigation’s seven nesting keys — screen, params, initial, state, path, pop, merge — are filtered out, so a route cannot use those seven as param names.`]},usePathname:{status:`partial`,tier:`P1`,reason:`The current URL without its query string, from React Navigation’s getPathFromState over the published root state.`,limits:[`On the +not-found route it reports "/+not-found" rather than the URL that missed. getPathFromState builds a path from the STATE, and the state records which screen matched, not the text that reached it.`,`It answers "/" before the navigation container is ready. That is not a placeholder — nothing has been navigated to yet — but a component that renders on the first commit and compares against a literal will see it.`]},Stack:{status:`partial`,tier:`P1`,gtk:`Adw.NavigationView + Adw.NavigationPage`,reason:`The stack navigator: React declares which pages exist, the widget owns their order, and the route key is the tag that joins the two.`,limits:[`A screen answers title, headerShown and animation. Every other expo-router / React Navigation stack option is refused BY NAME at the point of declaration, because an option that is accepted and ignored is invisible until someone looks at the window.`,`The header is an Adw.HeaderBar inside an Adw.ToolbarView, which grows its own back button and reads the page’s own title. A custom header component is not supported; headerShown={false} drops the bar, and with it the back affordance.`,`animation is "default" or "none" — Adw.NavigationView has one transition and a switch for it (animate-transitions), not a set of named ones.`,`Screen preloading is not implemented. React Navigation’s preloadedRoutes would need pages added to the widget’s pool without entering the stack, which is expressible and untested here.`]},Tabs:{status:`partial`,tier:`P1`,gtk:`Adw.ViewStack + Adw.ViewSwitcher`,reason:`The tab navigator. The switcher is driven by the stack’s own page model, so a route file adds a button with no tab-bar bookkeeping.`,limits:[`A tab answers title only. tabBarIcon is refused: in React Native it is a COMPONENT and Adwaita paints a switcher button from Adw.ViewStackPage:icon-name, an icon NAME on a page object the add returns — reachable through a ref, not through this declaration.`,`Every tab is mounted. React Native’s tab navigator is lazy because a phone cannot afford five screens; an Adw.ViewStack page that is not the visible child is not realised, and laziness would buy a flash on first switch.`,`The switcher sits in the header bar with policy WIDE (MEASURED: the default is NARROW, the phone-shaped one). A narrow-window breakpoint is the application’s to declare — Adw.ViewSwitcherBar is not wired here.`,`tabBarBadge, tabBarPosition and the rest of the tab-bar vocabulary are refused by name for the same reason as tabBarIcon: they describe a bar this layer does not draw.`]},Link:{status:`planned`,tier:`P2`,gtk:`Gtk.Button (flat) or Gtk.LinkButton`,reason:"An href as an element rather than a call. Cheap once `router` exists, and the honest widget question — button or link — is worth measuring first."},Redirect:{status:`planned`,tier:`P2`,reason:`A declarative navigate-on-render. It needs a rule for what happens when it redirects during the first commit, which is the commit that must not be empty.`},Slot:{status:`planned`,tier:`P2`,gtk:`Adw.Bin`,reason:`A layout that renders its child route with no navigator around it. It is the one layout shape that has no widget of its own.`},useRouter:{status:`planned`,tier:`P2`,reason:"The hook form of `router`. Identical behaviour, and it exists so a test can substitute the object — which is worth having once there is something to substitute."},useSegments:{status:`planned`,tier:`P2`,reason:`The current route split into segments. Derivable from the published root state, and unused in the measured application.`},useGlobalSearchParams:{status:`planned`,tier:`P2`,reason:`The params of the focused route anywhere in the tree, rather than of the calling screen. It re-renders on every navigation by design, which is why expo-router documents preferring the local form.`},useNavigation:{status:`planned`,tier:`P2`,reason:`React Navigation’s own hook, re-exported by expo-router. It works already through @react-navigation/core; exporting it from here is a decision about what this surface promises, not a build.`},useFocusEffect:{status:`planned`,tier:`P2`,reason:`As useNavigation: it is core’s, and it works. The open question is the same one.`},useRootNavigationState:{status:`planned`,tier:`P3`,reason:"The root state as a hook. The store `usePathname` reads is already there; this is a second selector over it."},Drawer:{status:`planned`,tier:`P3`,gtk:`Adw.OverlaySplitView`,reason:`A sidebar navigator. The pattern survives on a desktop better than it does on a phone, and Adwaita has the exact widget — it is a third navigator, not a variation on these two.`},withLayoutContext:{status:`planned`,tier:`P3`,reason:"The escape hatch that lets a third-party navigator take its screens from the file tree. It is exactly `useRouteNode` plus screen synthesis, made public — worth doing once a third navigator exists to prove the shape."},Navigator:{status:`planned`,tier:`P3`,reason:`expo-router’s low-level navigator primitive. It belongs with withLayoutContext and for the same reason.`},ErrorBoundary:{status:`planned`,tier:`P3`,gtk:`Adw.StatusPage`,reason:`A per-route error screen. React 19 routes an uncaught error to the root’s handler and @gjsify/gtk-host/react rethrows it from render() — deliberately, so a refusal is not swallowed — and a boundary here has to be reconciled with that rather than bolted beside it.`},ExpoRoot:{status:`refused`,reason:"It takes a Metro `require.context`, which does not exist in this build chain. `RouterRoot` takes the manifest a bundler plugin emits instead — the same job, with the one input that is available."},useUnstableGlobalHref:{status:`refused`,reason:`Unstable by its own name, and it answers the question usePathname already answers for the only surface this layer has.`},useNavigationContainerRef:{status:`refused`,reason:"The container ref is this layer’s own (one process routes one tree), and handing it out would let a consumer dispatch around the refusals `router` exists to give."},SplashScreen:{status:`refused`,reason:`A native splash screen belongs to a phone launcher. A GTK application maps its window when it is ready, which is the desktop equivalent and is Gio.Application’s job, not the router’s.`}},n=Object.keys(e),r=Object.keys(t),i=new Set([`supported`,`partial`]),a=[[`@gjsify/react-native`,e],[`@gjsify/react-native/router`,t]],lookup=e=>{for(let[t,n]of a){let r=n[e];if(r!==void 0)return{module:t,entry:r}}},isImportable=e=>{let t=lookup(e);return t!==void 0&&i.has(t.entry.status)};function explainUnsupported(e){let t=lookup(e);if(t===void 0)return`@gjsify/react-native: "${e}" is not a React Native export this layer knows about. If the installed react-native really exports it, the support table is out of date — run scripts/check-rn-surface.mjs, which compares the two.`;let{module:n,entry:r}=t,i=r.tier?` (tier ${r.tier})`:``,a=r.gtk?` The GTK counterpart is ${r.gtk}.`:``;switch(r.status){case`supported`:case`partial`:return`${n}: "${e}" is available.`;case`planned`:return`${n}: "${e}" is not implemented yet${i}. ${r.reason}${a}`;case`refused`:return`${n}: "${e}" will not be implemented. ${r.reason}`;case`no-desktop-meaning`:return`${n}: "${e}" has no meaning on a desktop window${i}. ${r.reason}`;case`not-reachable`:return`${n}: "${e}" cannot be implemented in this build chain. ${r.reason}`}}export{r as ROUTER_NAMES,t as ROUTER_SUPPORT_TABLE,n as SUPPORTED_NAMES,e as SUPPORT_TABLE,explainUnsupported,isImportable};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{OWN_EXPORT_NAMES as e}from"./generated/own-exports.js";const t={View:{status:`partial`,tier:`P1`,gtk:`Gtk.Box, or Gtk.Overlay when a child is absolutely positioned`,reason:`The container primitive. Which widget it becomes depends on its children, not on the element.`,limits:[`An absolutely positioned child is placed by alignment plus a margin, because Gtk.Overlay positions an overlay child by its halign/valign and has no coordinate pair. An offset on only ONE edge of an axis therefore leaves the other axis at GTK’s default (fill), where React Native would keep the child at its static position — a position GTK has no way to express for an overlay child.`,`onLayout is refused: GTK reports allocation through Gtk.Widget.vfunc_size_allocate, a subclass override rather than a signal.`,`pointerEvents answers auto and none only. box-none and box-only split hit-testing between a widget and its subtree, and Gtk.Widget:can-target is one boolean for both.`]},Text:{status:`partial`,tier:`P1`,gtk:`Gtk.Label`,reason:`Wrapping is ON by default in React Native and OFF on a Gtk.Label, so the default is set explicitly.`,limits:[`A nested <Text> is refused by the host naming the tag: Gtk.Label takes no children (measured), so React Native’s inline-styling idiom has no counterpart. Use Pango markup through a ref, or separate labels.`,`ellipsizeMode="clip" is refused: PangoEllipsizeMode is NONE, START, MIDDLE, END (measured) and has no clip member, so honouring it would add an ellipsis the author asked not to have.`,`onPress is refused: a Gtk.Label emits no clicked signal (measured). Wrap it in a <Pressable>.`]},Pressable:{status:`partial`,tier:`P1`,gtk:`Gtk.Button (flat)`,reason:`Press state is a GTK CSS :active pseudo-class; children-as-a-function is implemented over the state flag, and costs nothing when it is unused.`,limits:[`children-as-a-function-of-{ pressed } reads the widget’s Gtk.StateFlags.ACTIVE through Gtk.Widget::state-flags-changed, because Gtk.Button installs no active property (measured). An element that does NOT use the function form subscribes to nothing — press styling written as active:* is CSS and never reaches the reconciler (ADR 0032 § 7), and pressWatchCount() is what holds the two apart.`,`onPressIn/onPressOut are refused: they report the press as an EVENT, and the state flag reports it as a state. Use the function form, or active:*.`,`onLongPress is refused: a long press is a Gtk.GestureLongPress controller added to the widget, not a property or a signal on it.`,`hitSlop is refused: GTK hit-tests the allocation and cannot grow it past the widget. Pad the button instead, which enlarges the real target.`]},ScrollView:{status:`partial`,tier:`P1`,gtk:`Gtk.ScrolledWindow + an implicit content box`,reason:`contentContainerStyle styles the inner box, which is a second styleable node.`,limits:[`onScroll and scrollEventThrottle are refused: GTK reports scroll position as notify::value on the Gtk.Adjustment behind hadjustment/vadjustment, an object rather than a signal on the scrolled window. Reach the adjustment through a ref.`,`scrollEnabled is refused: a Gtk.ScrolledWindow has no switch that stops scrolling, and setting both policies to never clamps the child as well as hiding the bars.`,`refreshControl is refused, matching RefreshControl’s own entry: GTK has no pull-to-refresh idiom.`,`Layout utilities that need a box (items-*, gap-*, justify-*) are refused on style and belong on contentContainerStyle — which is React Native’s own rule, not a GTK one.`]},ActivityIndicator:{status:`partial`,tier:`P1`,gtk:`Adw.Spinner`,reason:`Direct counterpart.`,limits:[`animating={false} hides the widget. MEASURED: Adw.Spinner installs 36 properties and every one is Gtk.Widget’s — there is nothing that stops it while it is on screen, so "not animating" is "not shown". That is also exactly what hidesWhenStopped asks for, which is why hidesWhenStopped is a declared no-op.`,`size="small"/"large" are 16 and 32 px — Gtk.IconSize’s own two steps — not React Native’s 20/36, which are Android dp and land between GTK’s steps. A numeric size is used verbatim.`]},TextInput:{status:`partial`,tier:`P1`,gtk:`Gtk.Entry / Gtk.TextView`,reason:`Single- versus multi-line is one prop in React Native and two different widgets in GTK.`,limits:[`multiline={true} is a Gtk.TextView, whose content lives in a Gtk.TextBuffer rather than in a property — MEASURED: 61 properties, no text. So value, defaultValue and onChangeText are refused by name on a multiline input, as are placeholder, maxLength and secureTextEntry, none of which Gtk.TextView installs. Single-line is complete.`,`onFocus/onBlur are refused: focus arrives through a Gtk.EventControllerFocus, a controller rather than a signal on the widget.`,`autoFocus is refused: Gtk.Widget.grab_focus() only works once the widget is mapped, which is a moment this layer does not own. Call it from a ref in an effect.`,`keyboardType maps onto Gtk.Entry:input-purpose; returnKeyType, autoCapitalize, autoCorrect and keyboardAppearance are declared no-ops, because they describe an on-screen keyboard.`]},Linking:{status:`partial`,tier:`P1`,gtk:`Gtk.UriLauncher`,reason:`openURL and canOpenURL only.`,limits:[`openURL asks can_launch FIRST and rejects when the answer is no. MEASURED: Gtk.UriLauncher.launch never calls back for a scheme with no handler (3 000 ms, no callback, exit 0), and a promise that never settles is worse than a rejection.`,`getInitialURL always resolves null. A desktop deep link arrives as Gio.Application::open AFTER startup, so there is no value to read before it — which is also what React Native returns for a normal launch.`,`addEventListener/addListener are refused: URL delivery is the application object’s own wiring (HANDLES_OPEN plus the open signal), above this layer.`,`openSettings and sendIntent are refused: a per-app settings page and an Android Intent have no desktop counterpart.`]},Switch:{status:`partial`,tier:`P1`,gtk:`Gtk.Switch`,reason:`Direct counterpart.`,limits:[`onValueChange binds notify::active, not state-set: state-set runs BEFORE the state changes and must return false to let the default handler proceed, and a handler that forgets makes the switch stick at exit 0.`,`trackColor and thumbColor are refused: Adwaita paints the switch from the theme accent and its track is a CSS subnode, not a property. Style it from the application stylesheet.`]},Platform:{status:`partial`,tier:`P1`,reason:`OS is "linux" | "macos" | "windows"; select() picks the default branch.`,limits:[`OS reports "linux", which is not a member of React Native’s own PlatformOSType. Reporting "macos" on a Linux desktop would be a lie select() cannot recover from, and "web" implies a DOM.`,`select() does NOT consult a native branch, for ADR 0032 § 9’s reason: a native branch is written for a React Native runtime, and handing it to a GTK build is a failure that surfaces in a window. A spec with neither this OS nor default is a named refusal rather than undefined.`,`Version throws by name: it is a mobile OS version and a desktop has no counterpart meaning the same thing.`,`constants throws by name: it is the native bridge’s constants object, and there is no bridge here.`]},Modal:{status:`planned`,tier:`P1`,gtk:`Adw.Dialog`,reason:`An Adw.Dialog cannot be an ordinary element. MEASURED on libadwaita 1.9.3: box.append(dialog) calls g_error() — SIGABRT and a core dump, not a catchable exception — but ONLY when the box is rooted in a window. A detached box accepts the append in silence, so a re-test on a bare box appears to disprove this and puts the primitive back. A dialog is PRESENTED against a parent, never parented by it, so this is a PORTAL and needs a host seam that does not exist yet.`},useColorScheme:{status:`supported`,tier:`P1`,gtk:`Adw.StyleManager.dark`,reason:`Follows the Adwaita colour scheme — the dark property, which is what the user is looking at, not color-scheme, which is what the application asked for.`},Share:{status:`partial`,tier:`P1`,gtk:`Gdk.Clipboard`,reason:`No desktop share sheet worth pretending about; copying the link is the honest mapping.`,limits:[`action is always sharedAction. dismissedAction means the user closed a share sheet without choosing, and nothing here asks the user anything — reporting a dismissal would be inventing an event. The constant is still exported, so a comparison against it resolves instead of failing to import.`,`activityType is always undefined: there is no activity to name.`,`Gtk.UriLauncher is NOT used here, although the planning entry named it. Launching a URI OPENS it in another application, which is a share sheet’s behavioural inverse — that call is Linking.openURL’s, and it is there.`]},AppRegistry:{status:`partial`,tier:`P1`,gtk:`Adw.Application + Adw.ApplicationWindow`,reason:`The entry point. Nothing renders without a window, so this is P1 despite being a shim.`,limits:[`runApplication takes gjsify options (applicationId is required) rather than React Native’s { rootTag, initialProps } — a phone host supplies the application identity, a desktop one IS the application.`,`unmountApplicationComponentAtRootTag throws: there is no root tag to address.`]},StyleSheet:{status:`partial`,tier:`P1`,gtk:`Gdk.Monitor.scale for hairlineWidth`,reason:`create/flatten/compose/hairlineWidth/absoluteFill. Style objects go through the same partition as classes (ADR 0032 § 4), which is why create can be identity.`,limits:[`create is IDENTITY, which is also what React Native’s own is now (it once registered styles and returned numeric ids). It cannot validate: half the vocabulary resolves against the parent (flex-1 is hexpand on a row and vexpand on a column), and create has no tree. The style object is validated where className is, at element resolution.`,`hairlineWidth is a GETTER, not a constant: Gdk.Display.get_default() is null before Gtk.init() (measured) and real code reads this at module scope, so before the display exists it answers 1. It takes the SMALLEST monitor scale, because GTK4 has no primary monitor (measured — get_primary_monitor is gone) and a hairline that is sub-pixel on a monitor can vanish, while one that is a device pixel too thick cannot.`,`setStyleAttributePreprocessor throws by name: it installs a process-wide transform on every style value, and configureStyle({ tokens }) is the scoped hook that already does that job.`]},FlatList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView + Gio.ListStore, owned by the component`,reason:`GTK virtualises for real. The list is NOT an ordinary element: a Gtk.ListView takes no children (measured — no append, add, insert, prepend, remove or set_child), so the component owns the view and drives the model from data, with React only inside the item factory.`,limits:[`data, renderItem, keyExtractor, ListEmptyComponent, ListHeaderComponent, ListFooterComponent, horizontal and onEndReached are answered. Everything else on React Native’s list surface is refused BY NAME in the primitive table, most of it because it configures React Native’s own virtualisation and Gtk.ListView does that job itself.`,`The header and footer are OUTSIDE the scroller, so they stay put while the rows scroll. Measured: putting them inside means wrapping the list in a Gtk.Box, which makes the scroller scroll a GtkViewport and allocates the list its whole content height (11 000 px for 500 rows) while it still realises only 205 of them — rows with no widget inside the allocated area.`,`A row’s React tree is rendered on a microtask, not inside the bind. GTK binds a row the moment the view is rooted in a window (measured), which under React is inside a commit or an effect — where a nested root cannot flush and the host refuses by name. A microtask drains before GTK’s next frame, so no row is ever painted empty.`,`A change to data replaces the model rows whose KEYS changed with one splice, which re-binds them; rows whose keys did not change are re-rendered in place instead, because GTK does not re-bind a row whose model object is unchanged (measured: items_changed over the same object does nothing).`,`onEndReached is bound to the scroller’s own Gtk.Adjustment (notify::value / upper / page-size) and fires once per arrival at the end. A list with nothing to scroll never fires, which is every list before it has been allocated.`,`renderItem’s separators.highlight/unhighlight/updateProps throw by name: a Gtk.ListView has no separator widget to restyle.`]},SectionList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView + one flattened Gio.ListStore`,reason:`The same component as FlatList, handed sections instead of data.`,limits:[`Every FlatList limit applies.`,`The sections are FLATTENED into one model with header ROWS, so a header scrolls with its section. GTK’s own sticky headers do exist — measured: Gtk.SectionModel is present, Gtk.FlattenListModel and every selection model implement it (Gio.ListStore and Gtk.StringList do not), Gtk.ListView.set_header_factory is there, and a flatten model over two stores answered get_section(0) = [0, 2] — and they are not used, because that route needs a second factory, a model of models, and Gtk.ListHeader’s start/end/n-items reconciled with React Native’s section object. stickySectionHeadersEnabled is therefore refused by name rather than silently false.`,`renderSectionFooter is not answered: a footer row would need the flattening to know where a section ends before the next one is read, which the header form does not.`]},VirtualizedList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView`,reason:`Its public surface is wide and mostly not worth honouring literally; the useful subset backs FlatList, and this name is that subset plus getItem/getItemCount.`,limits:[`Every FlatList limit applies.`,`getItem(data, index) and getItemCount(data) are answered, which is the one thing this name adds over FlatList. The virtualisation knobs — initialNumToRender, maxToRenderPerBatch, windowSize, updateCellsBatchingPeriod, removeClippedSubviews, getItemLayout — are refused by name: Gtk.ListView creates and recycles rows itself and installs no property that changes the batching (measured: 205 of 500 rows in a 400×300 window).`,`CellRendererComponent, debug, onLayout and the other internals of React Native’s own implementation are not answered at all — an unlisted prop is refused by name, which is the point.`]},VirtualizedSectionList:{status:`partial`,tier:`P2`,gtk:`Gtk.ListView`,reason:`The section-shaped sibling of VirtualizedList, which is SectionList here.`,limits:[`Every SectionList limit applies; it is the same component under React Native’s other name.`]},Image:{status:`partial`,tier:`P2`,gtk:`Gtk.Picture`,reason:`resizeMode becomes content-fit, and the default is inverted: React Native defaults to cover, a Gtk.Picture to contain (measured).`,limits:[`resizeMode answers cover, contain, stretch and center. repeat is refused by name: GtkContentFit is FILL, CONTAIN, COVER, SCALE_DOWN (measured) and has no tiling member — tiling is a Gdk.Paintable implementation, not a property of the widget that draws one.`,`source takes { uri } with a local path, a file: URI or a resource: URI. http:/https:/data: are refused by name (they need a fetch, a decoder and a cache — an async pipeline this layer does not own); a require() id is refused (it is an index into React Native’s asset registry, and ADR 0032 § 12 leaves the build chain to the consumer); an array is refused (it is the per-device-scale picker, and GTK scales one texture by the surface scale factor).`,`onLoad, onLoadStart, onLoadEnd, onError and onProgress are refused: Gtk.Picture emits NO signals at all (measured), and a file that does not exist leaves paintable null with no diagnostic — so a callback that never fires would hide exactly the case it exists to report.`,`blurRadius and tintColor are refused: a per-pixel effect is a Gsk render node, not a widget property.`]},ImageBackground:{status:`partial`,tier:`P2`,gtk:`Gtk.Picture in a Gtk.Overlay`,reason:`A picture with the children stacked over it. The picture is the overlay’s MAIN child, because a Gtk.Overlay paints every overlay child ABOVE it.`,limits:[`Every Image limit applies to source and resizeMode.`,`The element is sized by its PICTURE, not by its children — the opposite of React Native, where the image is absolutely positioned and contributes nothing. Measured: a Gtk.Overlay measures only its main child (a 9 px main child beside a 266 px overlay child gave the overlay 9 px), and gtk_overlay_set_measure_overlay() is a per-child METHOD rather than a property, so the host’s data-driven placement has no way to name it. Give the element a size, or let its parent stretch it (flex-1).`,`imageStyle styles the picture and style styles the container, as in React Native. imageRef is refused: this element’s ref is the Gtk.Overlay and overlay.get_child() is the picture.`]},TouchableOpacity:{status:`partial`,tier:`P2`,gtk:`Gtk.Button (flat)`,reason:`The same machinery as Pressable, written over it: one shared record of routes in the primitive table, one line of component.`,limits:[`activeOpacity is refused by name — write active:opacity-70. The pressed appearance is a GTK CSS :active declaration (ADR 0032 § 7), and honouring a raw number here would put a value into the styling path that did not come from the project’s token scale (ADR 0032 § 3).`,`Every Pressable limit applies: onPressIn/onPressOut, onLongPress, hitSlop and the press-delay props are refused by name.`]},TouchableHighlight:{status:`partial`,tier:`P2`,gtk:`Gtk.Button (flat)`,reason:`As TouchableOpacity. The pressed style is Adwaita’s own unless a variant says otherwise.`,limits:[`underlayColor is refused by name — write active:bg-<token>, for the same reason activeOpacity is: a raw colour would bypass the token scale that every other colour in this vocabulary comes from.`,`onShowUnderlay/onHideUnderlay are refused: there is no underlay widget to show.`,`Every TouchableOpacity limit applies.`]},TouchableWithoutFeedback:{status:`partial`,tier:`P2`,gtk:`Gtk.Box + Gtk.GestureClick`,reason:`No chrome, so no button: a vertical Gtk.Box like a View, with a Gtk.GestureClick added to it. Measured: Gtk.Button emits activate and clicked, a Gtk.Box emits neither, and Gtk.GestureClick emits pressed/released/stopped/unpaired-release.`,limits:[`It is a real box in the tree, not a transparent clone of its child — React Native clones the single child and attaches the handlers to it, and GTK has no way for a widget to be its own child. So it lays out like a View and takes one row of the parent’s box.`,`disabled becomes can-target: false rather than sensitive: false, because sensitive greys out every descendant of a wrapper that is supposed to have no appearance of its own.`,`onPress only. The rest of the Touchable surface is refused by name, as on TouchableOpacity.`]},Button:{status:`partial`,tier:`P2`,gtk:`Gtk.Button`,reason:`The one component whose React Native styling story is "you cannot", which GTK agrees with. title, onPress and disabled.`,limits:[`style and className are REFUSED by name, which is faithful: React Native’s Button takes neither. An Adwaita button is painted by the theme and by its own classes (suggested-action, destructive-action), which an application stylesheet sets. Use Pressable when you need to style it.`,`color is refused: it is the background on Android and the text colour on iOS — one prop, two meanings — and GTK’s answer to both is a class rather than a colour.`,`It takes no children: title writes Gtk.Button:label, and a text child would be a second authority for the same slot. React Native’s Button takes no children either.`]},Dimensions:{status:`partial`,tier:`P2`,gtk:`Gtk.Window allocation, Gdk.Monitor geometry`,reason:`get("window") is the window, not the screen — a desktop app is not full-screen, so the screen’s number would be wrong in the ordinary case. get("screen") is the monitor, because that is what it asks for.`,limits:[`get("window") throws by name when there is no window yet, which includes every read at module scope. There is no honest number before the application builds its window, and a zero divides. Read it from a component or an effect, or use useWindowDimensions.`,`The reported size is the window’s ALLOCATION, falling back to default-width/default-height before it has been allocated. It is deliberately NOT the Gdk.Surface size: measured, a 640×480 window has a 668×509 surface, which carries the client-side-decoration shadow.`,`The window is found through Gtk.Window.get_toplevels() (the mapped one first), so it answers for an application that built its own window as well as for one AppRegistry created.`,`fontScale comes from Gtk.Settings:gtk-xft-dpi / 1024 / 96 (measured: 98304, i.e. 1). set() throws by name: it is how React Native’s native side PUBLISHES metrics, and there is no bridge here.`]},useWindowDimensions:{status:`partial`,tier:`P2`,gtk:`Gdk.Surface notify::width/height, Gtk.Window allocation`,reason:`The hook form of Dimensions, through useSyncExternalStore so a resize between render and commit cannot tear.`,limits:[`The NOTIFIER and the VALUE are different objects, and it is measured rather than chosen: Gtk.Widget installs no width or height property and emits no size-allocate signal (the wall <View onLayout> is refused against), while Gdk.Surface installs read-only width and height and notifies on them. So the surface says when and the window says what.`,`PRECONDITION NOT MEASURED: whether the window’s allocation is already updated inside that handler. A compositor-driven resize cannot be triggered from a probe — set_default_size on a mapped window is a no-op (measured) — so this is recorded as unknown rather than guessed.`,`Every Dimensions limit applies to the value itself.`]},Alert:{status:`partial`,tier:`P2`,gtk:`Adw.AlertDialog`,reason:`Direct counterpart, and buildable where Modal is not: Alert is a FUNCTION CALL, so no element is ever inserted into a widget. Measured on libadwaita 1.9.3 — present(null) from a plain function, with no parent and no window, returned with no diagnostic.`,limits:[`destructive maps to Adw.ResponseAppearance.DESTRUCTIVE and cancel becomes the dialog’s CLOSE RESPONSE, which is stronger than an appearance: it is what Escape and the compositor’s close both produce. The first non-cancel button becomes the default response, which is Adwaita’s convention and has no React Native counterpart.`,`Response IDs are positional (response-0, …) rather than derived from the label, so two buttons with the same text do not collide into one response.`,`prompt throws by name: it is iOS-only, and its contract (four overloads, a login/password variant, a callback whose arity depends on the type) is what makes it a refusal rather than a port. Adw.AlertDialog:extra-child is there if you want to build it.`]},Appearance:{status:`supported`,tier:`P2`,gtk:`Adw.StyleManager`,reason:`The imperative sibling of useColorScheme, over the SAME reader — getColorScheme reads Adw.StyleManager:dark (what the user is looking at) and setColorScheme writes :color-scheme (what the application asked for), which is exactly the split React Native’s getter and setter have.`},SafeAreaView:{status:`partial`,tier:`P2`,gtk:`Gtk.Box`,reason:`The INSET has no desktop meaning; the layout does. It is a View in every other respect, and it has to be a real export to be imported.`,limits:[`The safe-area insets are always zero, because a desktop window has none. What is a no-op is the inset — not the box, not the column, not the children: a component that rendered nothing would be a screen that silently disappeared.`,`It is a View, so every View limit applies (the absolute-child overlay switch, onLayout refused, pointerEvents auto/none only).`]},StatusBar:{status:`partial`,tier:`P2`,reason:`A desktop window has no status bar to configure, and <StatusBar/> is in the first ten lines of most React Native screens — so it renders NOTHING and says so, rather than failing to import.`,limits:[`The component renders null. Its declarative props (barStyle, hidden, backgroundColor, translucent, animated) are accepted and do nothing, which is what a declared no-op is.`,`Every imperative static REFUSES by name: setBarStyle, setHidden, setBackgroundColor, setTranslucent, setNetworkActivityIndicatorVisible, pushStackEntry, popStackEntry, replaceStackEntry. A setter that appears to work is indistinguishable from one that does.`,`currentHeight throws by name rather than answering 0: code reads it straight into a layout (paddingTop: StatusBar.currentHeight), and a number here would inset every ported screen by a bar that is not there.`]},KeyboardAvoidingView:{status:`partial`,tier:`P2`,gtk:`Gtk.Box`,reason:`No on-screen keyboard eats a desktop window layout, so the AVOIDING is the no-op. React Native’s KeyboardAvoidingView is a View that changes its own height; what is left here is the View.`,limits:[`behavior, keyboardVerticalOffset and enabled are declared no-ops: they describe the avoidance, which is already off.`,`contentContainerStyle is refused by name: it styles the inner view React Native adds to do the avoiding, and there is no second node here. Put it on style.`,`It is a View, so every View limit applies.`]},Keyboard:{status:`partial`,tier:`P2`,reason:`Its events are on-screen-keyboard events, which do not occur — so the questions with a correct answer get it and the ones whose answer would have to be invented refuse.`,limits:[`addListener REFUSES by name, for every event. A subscription that resolves and then never fires is the silent drop this layer exists to remove: a keyboardDidShow handler that never runs looks like a bug in your own code, for ever.`,`dismiss() and removeAllListeners() are no-ops — nothing was shown, so nothing has to be hidden, and nothing is registered to remove.`,`isVisible() answers false and metrics() answers undefined, which are React Native’s own answers for a keyboard that is not up.`,`scheduleLayoutAnimation throws by name: it is LayoutAnimation’s subsystem (tier P3), not a keyboard behaviour.`]},RefreshControl:{status:`refused`,reason:`GTK has no pull-to-refresh idiom and should not grow one. Give the desktop build a refresh action instead.`},AppState:{status:`planned`,tier:`P3`,gtk:`Gtk.Application / Gdk.Surface state`,reason:`active/background from window focus and visibility.`},PixelRatio:{status:`planned`,tier:`P3`,gtk:`Gdk.Surface.scale-factor`,reason:`The scale factor of the surface the widget is on.`},PlatformColor:{status:`planned`,tier:`P3`,gtk:`Adwaita named colours`,reason:`Maps unusually well — GTK’s palette is exactly this idea.`},AccessibilityInfo:{status:`planned`,tier:`P3`,gtk:`Gtk.Accessible / AT-SPI`,reason:`The highest-value P3 entry: GTK’s accessibility model is strong and the props map onto it well.`},Animated:{status:`planned`,tier:`P3`,gtk:`Adw.TimedAnimation / Adw.SpringAnimation`,reason:`Genuinely mappable, but it is a subsystem rather than a component. Doing it badly is worse than not doing it.`},Easing:{status:`planned`,tier:`P3`,reason:`Pure maths; it lands with Animated or not at all.`},LayoutAnimation:{status:`planned`,tier:`P3`,reason:`Needs an animated layout pass, which is the same subsystem as Animated.`},InteractionManager:{status:`planned`,tier:`P3`,reason:`Deferring work until interactions settle; a main-loop idle source is the counterpart.`},useAnimatedValue:{status:`planned`,tier:`P3`,reason:`Part of the Animated subsystem.`},useAnimatedValueXY:{status:`planned`,tier:`P3`,reason:`Part of the Animated subsystem.`},useAnimatedColor:{status:`planned`,tier:`P3`,reason:`Part of the Animated subsystem.`},PanResponder:{status:`planned`,tier:`P3`,gtk:`Gtk.Gesture* controllers`,reason:`The controllers exist; the arbitration model is not React Native’s, so this is its own project.`},usePressability:{status:`planned`,tier:`P3`,reason:`The hook behind the Touchable family; it lands with the gesture work.`},DrawerLayoutAndroid:{status:`planned`,tier:`P3`,gtk:`Adw.OverlaySplitView`,reason:`The pattern survives on a desktop even though the component is Android-only.`},I18nManager:{status:`planned`,tier:`P3`,gtk:`Gtk.Widget.direction`,reason:`RTL is a widget-direction question on GTK.`},LogBox:{status:`no-desktop-meaning`,tier:`P3`,reason:`A development overlay for a phone; the console is the desktop equivalent.`},Systrace:{status:`no-desktop-meaning`,reason:`Android systrace has no desktop counterpart.`},Vibration:{status:`no-desktop-meaning`,reason:`A desktop machine does not vibrate.`},ToastAndroid:{status:`planned`,tier:`P3`,gtk:`Adw.Toast`,reason:`Android-only by name, and Adwaita has the exact widget.`},InputAccessoryView:{status:`no-desktop-meaning`,reason:`An iOS keyboard accessory bar; there is no keyboard to accessorise.`},TouchableNativeFeedback:{status:`planned`,tier:`P3`,gtk:`Gtk.Button`,reason:`Android-only ripple; shimmed to its portable sibling.`},ActionSheetIOS:{status:`planned`,tier:`P3`,gtk:`Adw.AlertDialog with responses`,reason:`iOS-only by name; the pattern is a dialog with choices.`},PushNotificationIOS:{status:`refused`,reason:`Platform notification plumbing. A desktop app uses Gio.Notification directly.`},PermissionsAndroid:{status:`refused`,reason:`Android runtime permissions have no desktop counterpart; portals are asked for at use time.`},Settings:{status:`refused`,reason:`An iOS user-defaults bridge. GSettings is the desktop answer and is not this API.`},Clipboard:{status:`planned`,tier:`P3`,gtk:`Gdk.Clipboard`,reason:`Deprecated upstream in favour of a community package, but trivial here.`},BackHandler:{status:`planned`,tier:`P3`,reason:'An Android hardware back button. Routing has landed and this did NOT arrive with it, which the earlier reason assumed it would: BackHandler INTERCEPTS a back press and consumes it, and MEASURED on libadwaita 1.9.3, Adw.NavigationView emits `popped` AFTER the fact and has no vetoable "about to pop" signal at all — its only prevention is Adw.NavigationPage:can-pop, a property rather than an event. The honest counterpart is usePreventRemove, which the routing layer does honour through its popped bridge; this name needs a key controller of its own on the window.'},DynamicColorIOS:{status:`planned`,tier:`P3`,reason:`A light/dark colour pair; the Adwaita scheme already provides the switch.`},processColor:{status:`planned`,tier:`P3`,reason:`Colour string to a platform value; a Gdk.RGBA on this side.`},UTFSequence:{status:`planned`,tier:`P3`,reason:`A table of unicode constants. Pure data, no platform in it.`},experimental_LayoutConformance:{status:`refused`,reason:`An experimental switch between two React Native layout implementations. Neither is used here.`},unstable_VirtualView:{status:`refused`,reason:`Unstable React Native internal.`},VirtualViewMode:{status:`refused`,reason:`Unstable React Native internal.`},unstable_NativeText:{status:`refused`,reason:`Unstable React Native internal.`},unstable_NativeView:{status:`refused`,reason:`Unstable React Native internal.`},unstable_TextAncestorContext:{status:`refused`,reason:`Unstable React Native internal.`},ReactNativeVersion:{status:`planned`,tier:`P3`,reason:`The version this layer targets, reported honestly rather than spoofed.`},DeviceInfo:{status:`refused`,reason:`A native module surface describing a phone.`},DevMenu:{status:`no-desktop-meaning`,reason:`The shake-to-open developer menu.`},DevSettings:{status:`no-desktop-meaning`,reason:`Development-client settings for a phone runtime.`},NativeModules:{status:`refused`,reason:`The native-module bridge. There is no bridge here; this layer renders in-process onto GTK.`},TurboModuleRegistry:{status:`refused`,reason:`The TurboModule lookup. Same reason as NativeModules.`},NativeComponentRegistry:{status:`refused`,reason:`Fabric component registration. The host owns widget creation.`},requireNativeComponent:{status:`refused`,reason:`Looks up a native view manager. Write a GTK widget descriptor instead.`},codegenNativeComponent:{status:`refused`,reason:`React Native codegen for Fabric components.`},codegenNativeCommands:{status:`refused`,reason:`React Native codegen for Fabric commands.`},UIManager:{status:`refused`,reason:`The legacy view-manager surface. The GTK host is the view manager here.`},findNodeHandle:{status:`refused`,reason:`Returns a native view tag. A ref here is the Gtk.Widget itself, which is more useful.`},registerCallableModule:{status:`refused`,reason:`Registers a module callable from the native side. There is no native side.`},Networking:{status:`refused`,reason:`React Native’s XHR internals. Use fetch, which gjsify provides.`},NativeDialogManagerAndroid:{status:`refused`,reason:`An Android dialog native module. Alert is the portable spelling.`},ProgressBarAndroid:{status:`planned`,tier:`P3`,gtk:`Gtk.ProgressBar`,reason:`Android-only by name; GTK has the widget.`},Touchable:{status:`refused`,reason:`The legacy mixin behind the Touchable family, not a public component.`},EventEmitter:{status:`supported`,reason:`Pure JavaScript; nothing in it touches a platform.`},NativeEventEmitter:{status:`planned`,tier:`P3`,reason:`It would construct and subscribe, but nothing native would ever emit into it — shipping that needs a decision, not a class.`},DeviceEventEmitter:{status:`planned`,tier:`P3`,reason:`The global emitter. Lands with NativeEventEmitter, and for the same reason.`},NativeAppEventEmitter:{status:`refused`,reason:`The legacy iOS app-event emitter.`},RootTagContext:{status:`refused`,reason:`A React Native surface identifier. This layer has one root per Adw window.`},unstable_batchedUpdates:{status:`supported`,reason:`React 19 batches automatically; this is the identity call it already is upstream.`}},n={router:{status:`partial`,tier:`P1`,gtk:`Adw.NavigationView (push/pop) via React Navigation’s StackActions`,reason:`push, back, replace and navigate — the four methods the measured application calls, 19 of the 27 calls being push.`,limits:[`push falls back to NAVIGATE when the navigator that owns the target is not mounted yet. StackActions.push is dispatched at ONE navigator and an href can name a screen several navigators down; for an unmounted navigator, arriving there IS the new entry, so the fallback is correct rather than approximate — but a push into a mounted navigator that is not a stack cannot add an entry, because PUSH is the stack router’s action and no other router answers it.`,`back() THROWS when there is nothing to go back to, where expo-router and React Navigation both return quietly. A quiet return is a back button that does nothing with no message, which is the silent drop this layer refuses everywhere else.`,`setParams, dismiss, dismissAll, dismissTo and canGoBack are not implemented. They are not in the measured surface, and each is a named refusal rather than an undefined property read.`,`An href is a path. expo-router also accepts an object form ({ pathname, params }); this layer takes the string, which is what all 27 measured calls pass.`]},useLocalSearchParams:{status:`partial`,tier:`P1`,reason:`The current route’s params — the [param] values and the query string — read through React Navigation’s own useRoute().`,limits:[`Values are strings, because a URL segment is one. A number or a boolean that arrived through a dispatched param is stringified; anything structural (an object, an array) is left out, and a [param] cannot produce one. expo-router additionally returns string[] for a repeated query key, which this layer does not.`,`React Navigation’s seven nesting keys — screen, params, initial, state, path, pop, merge — are filtered out, so a route cannot use those seven as param names.`]},usePathname:{status:`partial`,tier:`P1`,reason:`The current URL without its query string, from React Navigation’s getPathFromState over the published root state.`,limits:[`On the +not-found route it reports "/+not-found" rather than the URL that missed. getPathFromState builds a path from the STATE, and the state records which screen matched, not the text that reached it.`,`It answers "/" before the navigation container is ready. That is not a placeholder — nothing has been navigated to yet — but a component that renders on the first commit and compares against a literal will see it.`]},Stack:{status:`partial`,tier:`P1`,gtk:`Adw.NavigationView + Adw.NavigationPage`,reason:`The stack navigator: React declares which pages exist, the widget owns their order, and the route key is the tag that joins the two.`,limits:[`A screen answers title, headerShown and animation. Every other expo-router / React Navigation stack option is refused BY NAME at the point of declaration, because an option that is accepted and ignored is invisible until someone looks at the window.`,`The header is an Adw.HeaderBar inside an Adw.ToolbarView, which grows its own back button and reads the page’s own title. A custom header component is not supported; headerShown={false} drops the bar, and with it the back affordance.`,`animation is "default" or "none" — Adw.NavigationView has one transition and a switch for it (animate-transitions), not a set of named ones.`,`Screen preloading is not implemented. React Navigation’s preloadedRoutes would need pages added to the widget’s pool without entering the stack, which is expressible and untested here.`]},Tabs:{status:`partial`,tier:`P1`,gtk:`Adw.ViewStack + Adw.ViewSwitcher`,reason:`The tab navigator. The switcher is driven by the stack’s own page model, so a route file adds a button with no tab-bar bookkeeping.`,limits:[`A tab answers title only. tabBarIcon is refused: in React Native it is a COMPONENT and Adwaita paints a switcher button from Adw.ViewStackPage:icon-name, an icon NAME on a page object the add returns — reachable through a ref, not through this declaration.`,`Every tab is mounted. React Native’s tab navigator is lazy because a phone cannot afford five screens; an Adw.ViewStack page that is not the visible child is not realised, and laziness would buy a flash on first switch.`,`The switcher sits in the header bar with policy WIDE (MEASURED: the default is NARROW, the phone-shaped one). A narrow-window breakpoint is the application’s to declare — Adw.ViewSwitcherBar is not wired here.`,`tabBarBadge, tabBarPosition and the rest of the tab-bar vocabulary are refused by name for the same reason as tabBarIcon: they describe a bar this layer does not draw.`]},Link:{status:`planned`,tier:`P2`,gtk:`Gtk.Button (flat) or Gtk.LinkButton`,reason:"An href as an element rather than a call. Cheap once `router` exists, and the honest widget question — button or link — is worth measuring first."},Redirect:{status:`planned`,tier:`P2`,reason:`A declarative navigate-on-render. It needs a rule for what happens when it redirects during the first commit, which is the commit that must not be empty.`},Slot:{status:`planned`,tier:`P2`,gtk:`Adw.Bin`,reason:`A layout that renders its child route with no navigator around it. It is the one layout shape that has no widget of its own.`},useRouter:{status:`planned`,tier:`P2`,reason:"The hook form of `router`. Identical behaviour, and it exists so a test can substitute the object — which is worth having once there is something to substitute."},useSegments:{status:`planned`,tier:`P2`,reason:`The current route split into segments. Derivable from the published root state, and unused in the measured application.`},useGlobalSearchParams:{status:`planned`,tier:`P2`,reason:`The params of the focused route anywhere in the tree, rather than of the calling screen. It re-renders on every navigation by design, which is why expo-router documents preferring the local form.`},useNavigation:{status:`planned`,tier:`P2`,reason:`React Navigation’s own hook, re-exported by expo-router. It works already through @react-navigation/core; exporting it from here is a decision about what this surface promises, not a build.`},useFocusEffect:{status:`planned`,tier:`P2`,reason:`As useNavigation: it is core’s, and it works. The open question is the same one.`},useRootNavigationState:{status:`planned`,tier:`P3`,reason:"The root state as a hook. The store `usePathname` reads is already there; this is a second selector over it."},Drawer:{status:`planned`,tier:`P3`,gtk:`Adw.OverlaySplitView`,reason:`A sidebar navigator. The pattern survives on a desktop better than it does on a phone, and Adwaita has the exact widget — it is a third navigator, not a variation on these two.`},withLayoutContext:{status:`planned`,tier:`P3`,reason:"The escape hatch that lets a third-party navigator take its screens from the file tree. It is exactly `useRouteNode` plus screen synthesis, made public — worth doing once a third navigator exists to prove the shape."},Navigator:{status:`planned`,tier:`P3`,reason:`expo-router’s low-level navigator primitive. It belongs with withLayoutContext and for the same reason.`},ErrorBoundary:{status:`planned`,tier:`P3`,gtk:`Adw.StatusPage`,reason:`A per-route error screen. React 19 routes an uncaught error to the root’s handler and @gjsify/gtk-host/react rethrows it from render() — deliberately, so a refusal is not swallowed — and a boundary here has to be reconciled with that rather than bolted beside it.`},ExpoRoot:{status:`refused`,reason:"It takes a Metro `require.context`, which does not exist in this build chain. `RouterRoot` takes the manifest a bundler plugin emits instead — the same job, with the one input that is available."},useUnstableGlobalHref:{status:`refused`,reason:`Unstable by its own name, and it answers the question usePathname already answers for the only surface this layer has.`},useNavigationContainerRef:{status:`refused`,reason:"The container ref is this layer’s own (one process routes one tree), and handing it out would let a consumer dispatch around the refusals `router` exists to give."},SplashScreen:{status:`refused`,reason:`A native splash screen belongs to a phone launcher. A GTK application maps its window when it is ready, which is the desktop equivalent and is Gio.Application’s job, not the router’s.`}},r=Object.keys(t),i=Object.keys(n),a=new Set([`supported`,`partial`]),o=[[`@gjsify/react-native`,t],[`@gjsify/react-native/router`,n]],lookup=e=>{for(let[t,n]of o){let r=n[e];if(r!==void 0)return{module:t,entry:r}}},s=new Set(e),isOwnExport=e=>s.has(e),isImportable=e=>{let t=lookup(e);return t===void 0?isOwnExport(e):a.has(t.entry.status)};function explainUnsupported(e){let t=lookup(e);if(t===void 0)return isOwnExport(e)?`@gjsify/react-native: "${e}" is this layer's own export rather than a React Native name, and it is available.`:`@gjsify/react-native: "${e}" is not a React Native export this layer knows about. If the installed react-native really exports it, the support table is out of date — run scripts/check-rn-surface.mjs, which compares the two.`;let{module:n,entry:r}=t,i=r.tier?` (tier ${r.tier})`:``,a=r.gtk?` The GTK counterpart is ${r.gtk}.`:``;switch(r.status){case`supported`:case`partial`:return`${n}: "${e}" is available.`;case`planned`:return`${n}: "${e}" is not implemented yet${i}. ${r.reason}${a}`;case`refused`:return`${n}: "${e}" will not be implemented. ${r.reason}`;case`no-desktop-meaning`:return`${n}: "${e}" has no meaning on a desktop window${i}. ${r.reason}`;case`not-reachable`:return`${n}: "${e}" cannot be implemented in this build chain. ${r.reason}`}}export{e as OWN_EXPORT_NAMES,i as ROUTER_NAMES,n as ROUTER_SUPPORT_TABLE,r as SUPPORTED_NAMES,t as SUPPORT_TABLE,explainUnsupported,isImportable,isOwnExport};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const OWN_EXPORT_NAMES: readonly string[];
|
package/lib/types/index.d.ts
CHANGED
|
@@ -22,5 +22,5 @@ export { PrimitiveError } from './primitives/errors.js';
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function unstable_batchedUpdates<T, R>(callback: (argument: T) => R, argument: T): R;
|
|
24
24
|
export * from './generated/unsupported-exports.js';
|
|
25
|
-
export { SUPPORT_TABLE, SUPPORTED_NAMES, explainUnsupported, isImportable, type SupportEntry, type SupportStatus, type SupportTier, } from './support-table.js';
|
|
25
|
+
export { OWN_EXPORT_NAMES, SUPPORT_TABLE, SUPPORTED_NAMES, explainUnsupported, isImportable, isOwnExport, type SupportEntry, type SupportStatus, type SupportTier, } from './support-table.js';
|
|
26
26
|
export { UnsupportedError } from './unsupported.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ListController, onScrollNearEnd } from '@gjsify/gtk-host/list';
|
|
2
|
+
import { type ReactRoot } from '@gjsify/gtk-host/react';
|
|
2
3
|
import { type ReactNode } from 'react';
|
|
3
4
|
/** One row: an identity, and the React tree it renders to. */
|
|
4
5
|
export interface ListRow {
|
|
@@ -7,13 +8,9 @@ export interface ListRow {
|
|
|
7
8
|
/** Called on every bind, so it always reads the latest `data`. */
|
|
8
9
|
render(): ReactNode;
|
|
9
10
|
}
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* Constructed by a component, attached to a view the host created, disposed from the
|
|
14
|
-
* component's own cleanup. Nothing here is reactive: `setRows` is called with the
|
|
15
|
-
* current rows and works out whether GTK has to hear about it.
|
|
16
|
-
*/
|
|
11
|
+
/** The controller a list component owns: the shared core, wearing React's row sink. */
|
|
12
|
+
export type ReactListController = ListController<ListRow, ReactRoot>;
|
|
13
|
+
/** What a list component may tell its controller, beyond the rows themselves. */
|
|
17
14
|
export interface ListControllerOptions {
|
|
18
15
|
/**
|
|
19
16
|
* Where a row's own render error goes.
|
|
@@ -26,40 +23,8 @@ export interface ListControllerOptions {
|
|
|
26
23
|
*/
|
|
27
24
|
readonly onRowError?: (error: Error) => void;
|
|
28
25
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
constructor(options?: ListControllerOptions);
|
|
32
|
-
/**
|
|
33
|
-
* Give the view its model and factory.
|
|
34
|
-
*
|
|
35
|
-
* `Gtk.NoSelection` and not the store directly: `Gtk.ListView:model` is a
|
|
36
|
-
* `Gtk.SelectionModel`, and a plain `Gio.ListModel` is not one. `NoSelection` is
|
|
37
|
-
* the wrapper that says "no selection" — React Native's lists have no selection
|
|
38
|
-
* concept, and `Gtk.SingleSelection` would give rows a selected state nothing
|
|
39
|
-
* would ever clear.
|
|
40
|
-
*/
|
|
41
|
-
attach(view: Gtk.ListView): void;
|
|
42
|
-
/**
|
|
43
|
-
* The rows GTK should be showing.
|
|
44
|
-
*
|
|
45
|
-
* The model is only touched when the KEYS changed. When they did not, every row's
|
|
46
|
-
* tree is re-rendered instead — a row whose key is the same but whose data changed
|
|
47
|
-
* has to repaint, and measurement 4 says GTK will not re-bind it for us. That is
|
|
48
|
-
* strictly cheaper than a splice, which tears down every row's React root.
|
|
49
|
-
*/
|
|
50
|
-
setRows(rows: readonly ListRow[]): void;
|
|
51
|
-
/** How many rows GTK is currently holding a widget for. A spec seam and a leak detector. */
|
|
52
|
-
get liveRows(): number;
|
|
53
|
-
/**
|
|
54
|
-
* Unmount every row, disconnect every handler, and let go of the view.
|
|
55
|
-
*
|
|
56
|
-
* Called from the component's cleanup, which runs while JS callbacks still work —
|
|
57
|
-
* unlike GTK's own `teardown`, which fires during GC sweeping when the view is
|
|
58
|
-
* collected and is BLOCKED there (measured, see the header). Idempotent, because a
|
|
59
|
-
* component's cleanup and an explicit dispose both reach it.
|
|
60
|
-
*/
|
|
61
|
-
dispose(): void;
|
|
62
|
-
}
|
|
26
|
+
/** The list controller a React component owns. */
|
|
27
|
+
export declare function createListController(options?: ListControllerOptions): ReactListController;
|
|
63
28
|
/**
|
|
64
29
|
* `keyExtractor`, with React Native's own default.
|
|
65
30
|
*
|
|
@@ -69,21 +34,12 @@ export declare class ListController {
|
|
|
69
34
|
*/
|
|
70
35
|
export declare function rowKey(item: unknown, index: number, keyExtractor?: (item: unknown, index: number) => string): string;
|
|
71
36
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* `Gtk.Adjustment`, because that is where GTK keeps a scroll position: the scrolled
|
|
75
|
-
* window has no scroll signal of its own, and `notify::value` on the adjustment behind
|
|
76
|
-
* `hadjustment`/`vadjustment` is what `ScrollView`'s own `onScroll` refusal points at.
|
|
77
|
-
* MEASURED: a `Gtk.ScrolledWindow` hands out its adjustments with no window anywhere,
|
|
78
|
-
* and `set_value` on one raised `notify::value` — so this is drivable, and asserted, in
|
|
79
|
-
* a spec that presents nothing.
|
|
80
|
-
*
|
|
81
|
-
* `upper` and `page-size` are subscribed too, and not for completeness: `upper` grows
|
|
82
|
-
* when rows are added, which is the event that ARMS the next call — React Native fires
|
|
83
|
-
* `onEndReached` once per arrival at the end, and a list that grew has a new end.
|
|
37
|
+
* The scroll edge behind `onEndReached`, re-exported rather than reimplemented.
|
|
84
38
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
39
|
+
* It lived here, and it was never React's: `Gtk.Adjustment` arithmetic with no React in
|
|
40
|
+
* it and no toolkit value import. `@gjsify/gtk-host/list` owns it now, so a Vue or Solid
|
|
41
|
+
* list gets the same measured behaviour instead of re-deriving it — which is the failure
|
|
42
|
+
* this whole extraction exists to prevent. `components.ts` imports it from here, so the
|
|
43
|
+
* move costs no consumer a rewrite.
|
|
88
44
|
*/
|
|
89
|
-
export
|
|
45
|
+
export { onScrollNearEnd };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OWN_EXPORT_NAMES } from './generated/own-exports.js';
|
|
1
2
|
/** What this layer does about a React Native export. */
|
|
2
3
|
export type SupportStatus =
|
|
3
4
|
/** Implemented. */
|
|
@@ -37,6 +38,28 @@ export declare const ROUTER_SUPPORT_TABLE: Readonly<Record<string, SupportEntry>
|
|
|
37
38
|
export declare const SUPPORTED_NAMES: readonly string[];
|
|
38
39
|
/** Every `expo-router` name the routing surface declares a status for. */
|
|
39
40
|
export declare const ROUTER_NAMES: readonly string[];
|
|
41
|
+
/**
|
|
42
|
+
* Is `name` one of this layer's OWN exports rather than a React Native one?
|
|
43
|
+
*
|
|
44
|
+
* `configureStyle`, `resetStyleConfig` and `styleConfig` carry § 3's token scales,
|
|
45
|
+
* `primitives` is L2, and the table plus its readers are public. None is a React
|
|
46
|
+
* Native name, so none has — or should have — a table entry.
|
|
47
|
+
*/
|
|
48
|
+
export declare const isOwnExport: (name: string) => boolean;
|
|
49
|
+
export { OWN_EXPORT_NAMES };
|
|
50
|
+
/**
|
|
51
|
+
* May a build import `name` from this package?
|
|
52
|
+
*
|
|
53
|
+
* TWO POPULATIONS, and the tables answer FIRST. A name react-native exports is the
|
|
54
|
+
* tables' to judge, whatever else claims it; only a name no table has heard of falls
|
|
55
|
+
* through to the layer's own exports. That order is the safety property: the derived
|
|
56
|
+
* list can add names, never promote a `planned` one, so a mistake upstream of it
|
|
57
|
+
* cannot turn `import { Modal }` into a green build.
|
|
58
|
+
*
|
|
59
|
+
* A name in NEITHER population is still refused — which is the whole difference
|
|
60
|
+
* between this and "anything the table does not know is fine", the shape that would
|
|
61
|
+
* pass every typo.
|
|
62
|
+
*/
|
|
40
63
|
export declare const isImportable: (name: string) => boolean;
|
|
41
64
|
/**
|
|
42
65
|
* The sentence a build error and a runtime throw both print.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "The React Native view vocabulary rendered onto GTK4/Adwaita, over @gjsify/gtk-host",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"react-native"
|
|
51
51
|
],
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@gjsify/adwaita-app": "^0.
|
|
54
|
-
"@gjsify/gtk-host": "^0.
|
|
53
|
+
"@gjsify/adwaita-app": "^0.45.0",
|
|
54
|
+
"@gjsify/gtk-host": "^0.45.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@react-navigation/core": "^7.21.13",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@gjsify/cli": "^0.
|
|
81
|
+
"@gjsify/cli": "^0.45.0",
|
|
82
82
|
"@react-navigation/core": "^7.21.13",
|
|
83
83
|
"@react-navigation/routers": "^7.6.4",
|
|
84
|
-
"@gjsify/unit": "^0.
|
|
84
|
+
"@gjsify/unit": "^0.45.0",
|
|
85
85
|
"@girs/adw-1": "^4.1.0",
|
|
86
86
|
"@girs/gdk-4.0": "^4.1.0",
|
|
87
87
|
"@girs/gjs": "^4.1.0",
|