@gjsify/react-native 0.43.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.
Files changed (68) hide show
  1. package/README.md +127 -31
  2. package/lib/esm/apis/alert.js +1 -0
  3. package/lib/esm/apis/appearance.js +1 -0
  4. package/lib/esm/apis/color-scheme.js +1 -1
  5. package/lib/esm/apis/dimensions.js +1 -0
  6. package/lib/esm/apis/display.js +1 -0
  7. package/lib/esm/apis/index.js +1 -1
  8. package/lib/esm/apis/keyboard.js +1 -0
  9. package/lib/esm/components.js +1 -1
  10. package/lib/esm/generated/own-exports.js +1 -0
  11. package/lib/esm/generated/unsupported-exports.js +1 -1
  12. package/lib/esm/generated/unsupported-router-exports.js +1 -0
  13. package/lib/esm/hooks.js +1 -1
  14. package/lib/esm/index.js +1 -1
  15. package/lib/esm/lists/components.js +1 -0
  16. package/lib/esm/lists/controller.js +1 -0
  17. package/lib/esm/parent-context.js +1 -0
  18. package/lib/esm/press.js +1 -0
  19. package/lib/esm/primitives/intents.js +1 -1
  20. package/lib/esm/primitives/resolve.js +1 -1
  21. package/lib/esm/primitives/table.js +1 -1
  22. package/lib/esm/router/errors.js +1 -0
  23. package/lib/esm/router/index.js +1 -0
  24. package/lib/esm/router/navigation.js +1 -0
  25. package/lib/esm/router/root.js +1 -0
  26. package/lib/esm/router/routes.js +1 -0
  27. package/lib/esm/router/screens.js +1 -0
  28. package/lib/esm/router/stack.js +1 -0
  29. package/lib/esm/router/tabs.js +1 -0
  30. package/lib/esm/solid/index.js +1 -1
  31. package/lib/esm/stylesheet.js +1 -0
  32. package/lib/esm/support-table.js +1 -1
  33. package/lib/types/apis/alert.d.ts +46 -0
  34. package/lib/types/apis/appearance.d.ts +28 -0
  35. package/lib/types/apis/color-scheme.d.ts +21 -0
  36. package/lib/types/apis/dimensions.d.ts +42 -0
  37. package/lib/types/apis/display.d.ts +87 -0
  38. package/lib/types/apis/index.d.ts +10 -1
  39. package/lib/types/apis/keyboard.d.ts +22 -0
  40. package/lib/types/components.d.ts +166 -10
  41. package/lib/types/generated/own-exports.d.ts +1 -0
  42. package/lib/types/generated/unsupported-exports.d.ts +0 -19
  43. package/lib/types/generated/unsupported-router-exports.d.ts +17 -0
  44. package/lib/types/hooks.d.ts +16 -0
  45. package/lib/types/index.d.ts +10 -6
  46. package/lib/types/lists/components.d.ts +102 -0
  47. package/lib/types/lists/controller.d.ts +45 -0
  48. package/lib/types/lists/lists.spec.d.ts +2 -0
  49. package/lib/types/parent-context.d.ts +15 -0
  50. package/lib/types/press.d.ts +27 -0
  51. package/lib/types/primitives/index.d.ts +2 -2
  52. package/lib/types/primitives/resolve.d.ts +36 -0
  53. package/lib/types/primitives/table.d.ts +79 -2
  54. package/lib/types/router/errors.d.ts +37 -0
  55. package/lib/types/router/index.d.ts +10 -0
  56. package/lib/types/router/navigation.d.ts +72 -0
  57. package/lib/types/router/root.d.ts +24 -0
  58. package/lib/types/router/router.spec.d.ts +2 -0
  59. package/lib/types/router/routes.d.ts +90 -0
  60. package/lib/types/router/routes.spec.d.ts +2 -0
  61. package/lib/types/router/screens.d.ts +48 -0
  62. package/lib/types/router/stack.d.ts +118 -0
  63. package/lib/types/router/tabs.d.ts +36 -0
  64. package/lib/types/solid/index.d.ts +9 -4
  65. package/lib/types/stylesheet.d.ts +75 -0
  66. package/lib/types/stylesheet.spec.d.ts +2 -0
  67. package/lib/types/support-table.d.ts +36 -2
  68. package/package.json +19 -5
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";import{RouterError as e}from"./errors.js";const t=[`.tsx`,`.ts`,`.jsx`,`.js`,`.mts`,`.mjs`];function parseSegment(t,n){if(t.startsWith(`(`)){if(!t.endsWith(`)`))throw new e(`unknown-convention`,n,`the segment "${t}" opens a (group) and never closes it. A group directory is spelled \`(name)\` and contributes no URL segment`);let r=t.slice(1,-1);if(r.includes(`,`))throw new e(`shared-group-unsupported`,n,`"${t}" is a shared group — expo-router lets one directory belong to several navigators at once, and this layer answers for one navigator per directory. Split it into \`(`+r.split(`,`)[0]?.trim()+")` and a sibling, or drop the parentheses to make it an ordinary directory");if(r===``)throw new e(`unknown-convention`,n,'the segment "()" is an empty group. A group directory is spelled `(name)`');return{kind:`group`,name:r}}if(t.startsWith(`[`)){if(!t.endsWith(`]`))throw new e(`unknown-convention`,n,`the segment "${t}" opens a [param] and never closes it. A dynamic segment is spelled \`[name]\``);let r=t.slice(1,-1);if(r.startsWith(`...`))throw r.slice(3)===``?new e(`param-without-name`,n,`the segment "${t}" is a catch-all with no name. Name it — \`[...rest]\` — although this layer does not answer for catch-alls yet either`):new e(`deep-dynamic-unsupported`,n,`"${t}" is a catch-all segment. React Navigation's path config has no multi-segment wildcard that also carries its parts as a param, so honouring it would mean matching the URL in one place and splitting it in another. Use one \`[param]\` per segment, or \`+not-found\` for the fallback`);if(r.trim()===``)throw new e(`param-without-name`,n,`the segment "${t}" is a dynamic segment with no name, so nothing could read its value. Write \`[id]\` and read it with useLocalSearchParams().id`);if(r!==r.trim()||/[^A-Za-z0-9_$]/.test(r))throw new e(`unknown-convention`,n,`the segment "${t}" names a param "${r}", which is not usable as a key — a param name is letters, digits, \`_\` and \`\$\`, because useLocalSearchParams() hands it back as a property`);return{kind:`param`,name:r}}if(t.startsWith(`+`))throw new e(`unknown-convention`,n,`the segment "${t}" uses expo-router's \`+\` prefix, and \`+not-found\` is the only one this layer answers for. \`+html\`, \`+native-intent\` and the rest describe a web or a mobile host that a GTK window does not have`);if(t.startsWith(`_`))throw new e(`unknown-convention`,n,`the segment "${t}" is \`_\`-prefixed, and \`_layout\` is the only one this layer answers for. expo-router treats other \`_\` names as private and silently leaves them out of the tree; a file that is not a route belongs outside the routes directory, where nothing has to guess`);if(t===``||t===`.`||t===`..`)throw new e(`unknown-convention`,n,`"${n}" has an empty or relative path segment. A context key is a plain relative path`);return{kind:`static`,name:t}}function parseFile(n){let r=n.contextKey;if(r.startsWith(`/`)||r.includes(`\\`))throw new e(`bad-manifest`,r,"a context key is a path RELATIVE to the routes directory, with `/` separators. An absolute path or a backslash means the manifest was built against the wrong root");let i=t.find(e=>r.endsWith(e));if(i===void 0)throw new e(`unknown-convention`,r,`a route file ends in one of ${t.join(`, `)} — this one does not, so it is not a route. The routes directory holds routes: move it somewhere else, and nothing has to guess whether it was meant to be a screen`);let a=r.slice(0,-i.length).split(`/`),o=a[a.length-1],s=a.slice(0,-1),c=o===`_layout`,l=o===`+not-found`,u=s.map(e=>parseSegment(e,r)),d=c?null:l?{kind:`static`,name:`+not-found`}:parseSegment(o,r);return{contextKey:r,module:n.module,dir:u,leaf:d,isLayout:c,isNotFound:l}}function patternOf(e,t){if(t)return`*`;switch(e.kind){case`group`:return null;case`static`:return e.name===`index`?null:e.name;case`param`:return`:${e.name}`}}function patternFor(e,t){let n=[],r=[];for(let[i,a]of e.entries()){let o=patternOf(a,t&&i===e.length-1);o!==null&&n.push(o),a.kind===`param`&&r.push(a.name)}return{path:n.join(`/`),params:r}}const joinPattern=(e,t)=>t.notFound?t.path:[e,t.path].filter(e=>e!==``).join(`/`),nameFor=e=>e.map(e=>{switch(e.kind){case`group`:return`(${e.name})`;case`param`:return`[${e.name}]`;case`static`:return e.name}}).join(`/`),dirKey=e=>nameFor(e);function buildRouteTree(t){if(!Array.isArray(t))throw new e(`bad-manifest`,``,"the route manifest is not an array. A bundler plugin emits `[{ contextKey, module }, …]`; see `rnRouteManifestPlugin` in @gjsify/rolldown-plugin-gjsify");if(t.length===0)throw new e(`bad-manifest`,``,"the route manifest is empty, so there is nothing to route to. Point the plugin at the directory that holds your route files, or add an `index.tsx` to the one it is pointed at");for(let n of t)if(typeof n!=`object`||!n||typeof n.contextKey!=`string`)throw new e(`bad-manifest`,JSON.stringify(n??null),"a route manifest entry needs a string `contextKey` and a `module`");let n=t.map(parseFile),r=new Map;for(let t of n){if(!t.isLayout)continue;let n=dirKey(t.dir),i=r.get(n);if(i!==void 0)throw new e(`duplicate-route`,`${i.contextKey} and ${t.contextKey}`,`both are the \`_layout\` of "${n===``?`.`:n}", and a directory has one navigator. Delete one`);r.set(n,t)}let ownerOf=e=>{for(let t=e.length;t>0;t--){let n=dirKey(e.slice(0,t));if(r.has(n))return{owner:n,rest:e.slice(t)}}return{owner:``,rest:e}},i=new Map,a=r.get(``),o={key:``,node:{kind:`layout`,name:``,path:``,params:[],contextKey:a?.contextKey??null,module:a?.module??null,children:[],notFound:!1},children:[]};i.set(``,o);let s=[...r.entries()].filter(([e])=>e!==``).sort((e,t)=>e[0].split(`/`).length-t[0].split(`/`).length);for(let[e,t]of s){let{owner:n,rest:r}=ownerOf(t.dir.slice(0,-1)),a=t.dir[t.dir.length-1],o=[...r,a],{path:s,params:c}=patternFor(o,!1),l={key:e,node:{kind:`layout`,name:nameFor(o),path:s,params:c,contextKey:t.contextKey,module:t.module,children:[],notFound:!1},children:[]};i.set(e,l),i.get(n).children.push(l)}for(let e of n){if(e.isLayout)continue;let{owner:t,rest:n}=ownerOf(e.dir),r=[...n,e.leaf],{path:a,params:o}=patternFor(r,e.isNotFound);i.get(t).children.push({key:`${t}::${nameFor(r)}`,node:{kind:`screen`,name:nameFor(r),path:a,params:o,contextKey:e.contextKey,module:e.module,children:[],notFound:e.isNotFound},children:[]})}for(let[t,n]of r)if(t!==``&&i.get(t).children.length===0)throw new e(`layout-without-routes`,n.contextKey,`is the navigator for a directory that holds no routes, so it would render an empty navigator. Add a route file beside it, or delete it and let the parent navigator hold the routes`);let rank=e=>e.notFound?2:e.name===`index`?0:1,finish=e=>{let t=e.children.map(finish).sort((e,t)=>rank(e)-rank(t)||e.name.localeCompare(t.name));return{...e.node,children:t}},c=finish(o),l=new Map,walk=(t,n)=>{for(let r of t.children){let t=joinPattern(n,r);if(r.kind===`screen`){let n=l.get(t);if(n!==void 0)throw new e(`duplicate-route`,`${n} and ${r.contextKey}`,`both resolve to "/${t}", and a URL addresses one screen. A (group) contributes no segment, so two files in different groups collide unless one of the groups has its own \`_layout\``);l.set(t,r.contextKey)}else walk(r,t)}};return walk(c,``),c}function pathConfigOf(e,t=``){let n={};for(let r of e.children){let e=joinPattern(t,r);n[r.name]=r.kind===`screen`?{path:e}:pathConfigOf(r,e)}let r=e.children[0]?.name;return r===void 0?{screens:n}:{initialRouteName:r,screens:n}}function screenUrls(e,t=``){return e.children.flatMap(e=>{let n=joinPattern(t,e);return e.kind===`screen`?[`/${n}`]:screenUrls(e,n)})}export{buildRouteTree,pathConfigOf,screenUrls};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";import{RouterError as e}from"./errors.js";import{Children as t,createContext as n,createElement as r,isValidElement as i,useContext as a,useMemo as o}from"react";import{createNavigatorFactory as s}from"@react-navigation/core";const c=n(null),provideRouteNode=(e,t)=>r(c.Provider,{value:e},t);function useRouteNode(t){let n=a(c);if(n===null)throw new e(`no-route-node`,`<${t}>`,"was rendered outside the router, so it has no directory to take its screens from. A navigator belongs in a `_layout` file under the routes directory, and the tree needs a <RouterRoot manifest={…}> above it");return n}function defaultExportOf(t){let n=t.module;if(typeof n!=`object`||!n)throw new e(`bad-route-module`,t.contextKey??t.name,'has no module. Every manifest entry needs the evaluated module of its route file — a plugin emits `import * as R from "./route"` and passes `R`');let r=n.default;if(typeof r!=`function`&&(typeof r!=`object`||!r)){let r=Object.keys(n).filter(e=>e!=="__esModule");throw new e(`bad-route-module`,t.contextKey??t.name,`exports no default component. A route file default-exports the component for its screen; this one exports ${r.length===0?`nothing`:r.join(`, `)}. Without the check React would render \`undefined\` as an empty page and say nothing`)}return r}const l=new WeakMap;function componentFor(e){let t=l.get(e);if(t!==void 0)return t;let n=defaultExportOf(e),i=e.kind===`screen`?n:t=>provideRouteNode(e,r(n,t));return i.displayName=`Route(${e.contextKey??e.name})`,l.set(e,i),i}function screenOptionsFrom(n,r,a,o){let s=new Map;for(let c of t.toArray(o)){if(!i(c)||c.type!==r){let t=i(c)?`a <${typeof c.type==`string`?c.type:c.type.displayName??c.type.name??`component`}>`:`${typeof c} ${JSON.stringify(c)}`;throw new e(`not-a-screen-child`,`<${n}>`,`was given ${t} as a child. A navigator's children are its screens, and its screens come from the files in the directory its \`_layout\` owns — the only child it reads is <${n}.Screen name="…" options={…}/>, which configures one of them. Put content in a route file`)}let t=c.props;if(typeof t.name!=`string`||t.name===``)throw new e(`not-a-screen-child`,`<${n}.Screen>`,"needs a `name` — the route file's path relative to this navigator, without its extension: `index`, `settings`, `detail/[id]`");let o=t.options??{};for(let r of Object.keys(o))if(!a.includes(r))throw new e(`unknown-screen-option`,`<${n}.Screen name="${t.name}">`,`sets "${r}", which this navigator has no GTK answer for. It answers ${a.join(`, `)}. An option that is accepted and ignored is invisible until someone looks at the window`);s.set(t.name,o)}return s}function useSynthesisedScreens(e,t,n){return o(()=>e.children.map(e=>r(t,{key:e.name,name:e.name,component:componentFor(e),options:n.get(e.name)??{}})),[e,t,e.children.map(e=>`${e.name}:${JSON.stringify(n.get(e.name)??{})}`).join(`|`)])}function navigationPair(e){return s(e)()}export{componentFor,navigationPair,provideRouteNode,screenOptionsFrom,useRouteNode,useSynthesisedScreens};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";import{RouterError as e}from"./errors.js";import{navigationPair as t,screenOptionsFrom as n,useRouteNode as r,useSynthesisedScreens as i}from"./screens.js";import{createElement as a,useCallback as o,useLayoutEffect as s,useMemo as c,useRef as l,useState as u}from"react";import{StackActions as d,StackRouter as f,useNavigationBuilder as p}from"@react-navigation/core";const m=[`title`,`headerShown`,`animation`];function widgetStack(e){let t=e.get_navigation_stack(),n=t.get_n_items(),r=[];for(let e=0;e<n;e++){let n=t.get_item(e),i=n===null?null:n.get_tag();if(i===null)break;r.push(i)}return r}const same=(e,t)=>e.length===t.length&&e.every((e,n)=>t[n]===e),isStrictPrefix=(e,t)=>e.length<t.length&&e.every((e,n)=>t[n]===e);function applyStack(e,t,n,r){if(same(t,n))return`none`;let i=n[n.length-1];if(i!==void 0&&isStrictPrefix(n,t))return e.set_animate_transitions(r(t[t.length-1])),e.pop_to_tag(i),`pop`;if(i!==void 0&&isStrictPrefix(t,n)){let a=n.slice(0,-1);return same(a,t)||e.replace_with_tags([...a]),e.set_animate_transitions(r(i)),e.push_by_tag(i),`push`}return e.replace_with_tags([...n]),`replace`}const initialTracking=(e,t)=>({order:[...e],focused:t,closing:{}}),sameMembers=(e,t)=>e.length===t.length&&e.every(e=>t.includes(e)),neededKeys=(e,t)=>[...t,...Object.keys(e.closing).filter(e=>!t.includes(e))],needsAdvance=(e,t,n)=>!sameMembers(e.order,neededKeys(e,t))||e.focused!==n;function advanceTracking(e,t,n,r){let i={};for(let[n,r]of Object.entries(e.closing))t.includes(n)||(i[n]=r);let a=e.focused;if(a!==void 0&&!t.includes(a)){let e=r[a];e!==void 0&&(i[a]=e)}let o=[...t,...Object.keys(i).filter(e=>!t.includes(e))];return{order:[...e.order.filter(e=>o.includes(e)),...o.filter(t=>!e.order.includes(t))],focused:n,closing:i}}function releaseFrom(e,t){return e.closing[t]===void 0?e:{order:e.order.filter(e=>e!==t),focused:e.focused,closing:Object.fromEntries(Object.entries(e.closing).filter(([e])=>e!==t))}}function coalesce(e){let t=!1;return()=>{t||(t=!0,Promise.resolve().then(()=>{t=!1,e()}))}}const focusedKeyOf=e=>e.routes[e.index]?.key;function useStackPages(e,t){let n=e.routes.map(e=>e.key),r=focusedKeyOf(e),i=l(t),a=l(initialTracking(n,r)),[,c]=u(0);needsAdvance(a.current,n,r)&&(a.current=advanceTracking(a.current,n,r,i.current)),s(()=>{i.current=t});let d=o(e=>{let t=releaseFrom(a.current,e);t!==a.current&&(a.current=t,c(e=>e+1))},[]);return{pages:a.current.order.map(e=>t[e]??a.current.closing[e]).filter(e=>e!==void 0),release:d}}function usePageBindings(e){let t=l(new Map),n=l(new Map);return{bindingsFor:o(r=>{let i=n.current.get(r);if(i!==void 0)return i;let a={ref:e=>{e==null?t.current.delete(r):t.current.set(r,e)},onHidden:()=>e(r)};return n.current.set(r,a),a},[e]),widgets:t.current}}function pageElement(e,t){let{route:n,options:r}=e,i=r.headerShown===!1?e.render():a(`AdwToolbarView`,null,a(`AdwHeaderBar`,{slot:`top`}),e.render());return a(`AdwNavigationPage`,{key:n.key,tag:n.key,title:r.title??n.name,"on:hidden":t.onHidden,ref:t.ref},i)}function StackView(e){let{state:t,descriptors:n,navigation:r,NavigationContent:i}=p(f,e),u=l(null),{pages:m,release:h}=useStackPages(t,n),{bindingsFor:g,widgets:_}=usePageBindings(h),v=l(new Map);for(let e of m)v.current.set(e.route.key,e.options);let y=o(e=>e===void 0?!0:v.current.get(e)?.animation!==`none`,[]),b=o(()=>{let e=u.current;if(e===null)return;let t=r.getState(),n=widgetStack(e),i=t.routes.map(e=>e.key);isStrictPrefix(n,i)&&r.dispatch({...d.pop(i.length-n.length),target:t.key});let a=r.getState();applyStack(e,widgetStack(e),a.routes.map(e=>e.key),y)},[r,y]),x=c(()=>coalesce(b),[b]),S=l(!0);return s(()=>{let e=u.current;if(e===null)return;let n=t.routes.map(e=>e.key);if(S.current){S.current=!1,e.set_animate_transitions(!1),same(widgetStack(e),n)||e.replace_with_tags([...n]),e.set_animate_transitions(!0);return}applyStack(e,widgetStack(e),n,y)}),s(()=>{for(let e of m){let n=e.route.key;if(t.routes.some(e=>e.key===n))continue;let r=_.get(n);r!==void 0&&!r.get_mapped()&&h(n)}}),a(i,null,a(`AdwNavigationView`,{ref:u,popOnEscape:!0,"on:popped":x},...m.map(e=>pageElement(e,g(e.route.key)))))}StackView.displayName=`GjsifyStackView`;const h=t(StackView);function StackScreen(t){throw new e(`not-a-screen-child`,`<Stack.Screen>`,"was rendered instead of being read. It is a declaration: `<Stack>` collects its `name` and `options` and never renders it, so it only belongs as a direct child of `<Stack>`")}StackScreen.displayName=`Stack.Screen`;function Stack(e){let t=r(`Stack`),o=n(`Stack`,StackScreen,m,e.children),s=i(t,h.Screen,o),c=e.screenOptions===void 0?{}:{screenOptions:e.screenOptions};return a(h.Navigator,c,...s)}Stack.Screen=StackScreen;export{Stack,advanceTracking,applyStack,coalesce,initialTracking,neededKeys,needsAdvance,releaseFrom};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";import{RouterError as e}from"./errors.js";import{navigationPair as t,screenOptionsFrom as n,useRouteNode as r,useSynthesisedScreens as i}from"./screens.js";import{createElement as a,useCallback as o,useLayoutEffect as s,useRef as c}from"react";import{TabActions as l,TabRouter as u,useNavigationBuilder as d}from"@react-navigation/core";const f=[`title`];function TabsView(t){let{state:n,descriptors:r,navigation:i,NavigationContent:f}=d(u,t),p=c(null),m=c(null),h=n.routes[n.index]?.key;s(()=>{let e=m.current,t=p.current;e!==null&&t!==null&&e.get_stack()!==t&&e.set_stack(t)}),s(()=>{let e=p.current;e===null||h===void 0||e.get_visible_child_name()!==h&&e.set_visible_child_name(h)});let g=o(()=>{let t=p.current;if(t===null)return;let n=t.get_visible_child_name();if(n===null)return;let r=i.getState();if(n===r.routes[r.index]?.key)return;let a=r.routes.find(e=>e.key===n);if(a===void 0)throw new e(`no-route-node`,`<Tabs> page "${n}"`,`became visible and belongs to no route in this navigator. Every page is added under its route key, so this means something outside the router added a child to the Adw.ViewStack`);if(i.emit({type:`tabPress`,target:a.key,canPreventDefault:!0}).defaultPrevented){t.set_visible_child_name(r.routes[r.index]?.key??n);return}i.dispatch({...l.jumpTo(a.name,a.params),target:r.key})},[i]),_=n.routes.map(t=>{let n=r[t.key];if(n===void 0)throw new e(`no-route-node`,`<Tabs> route "${t.name}"`,`has no descriptor. React Navigation builds one per screen, so this is a route the navigator never received a <Screen> for`);return a(`AdwBin`,{key:t.key,layout:{name:t.key,title:n.options.title??t.name}},n.render())}),v=a(`AdwViewStack`,{ref:p,"on:notify::visible-child-name":g},..._);return t.headerShown===!1?a(f,null,v):a(f,null,a(`AdwToolbarView`,null,a(`AdwHeaderBar`,{slot:`top`},a(`AdwViewSwitcher`,{slot:`title`,ref:m,policy:`wide`})),v))}TabsView.displayName=`GjsifyTabsView`;const p=t(TabsView);function TabsScreen(t){throw new e(`not-a-screen-child`,`<Tabs.Screen>`,"was rendered instead of being read. It is a declaration: `<Tabs>` collects its `name` and `options` and never renders it, so it only belongs as a direct child of `<Tabs>`")}TabsScreen.displayName=`Tabs.Screen`;function Tabs(e){let t=r(`Tabs`),o=n(`Tabs`,TabsScreen,f,e.children),s=i(t,p.Screen,o);return a(p.Navigator,{...e.screenOptions===void 0?{}:{screenOptions:e.screenOptions},...e.headerShown===void 0?{}:{headerShown:e.headerShown}},...s)}Tabs.Screen=TabsScreen;export{Tabs};
@@ -1 +1 @@
1
- import"../_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"../primitives/errors.js";import{resolvePrimitive as t}from"../primitives/resolve.js";import{styleConfig as n}from"../style-config.js";import{createContext as r,createMemo as i,createSignal as a,onMount as o,untrack as s,useContext as c}from"solid-js";import{createComponent as l,createElement as u,effect as d,insert as f,insertNode as p,mount as m,setProp as h,widgetOf as g}from"@gjsify/gtk-host/solid";const _=r(()=>null),v=new WeakSet,y=new WeakMap,b={absolute:1,count:1,text:!1},accessorName=e=>e.replace(/-([a-z0-9])/g,(e,t)=>t.toUpperCase()),sameContext=(e,t)=>JSON.stringify(e)===JSON.stringify(t),sameValue=(e,t)=>Array.isArray(e)&&Array.isArray(t)?e.length===t.length&&e.every((e,n)=>e===t[n]):e===t;function element(e,r){let l=r,m=n(),x=c(_),[S,C]=a(null),w=i(()=>{let n=x();return t(e,l,{tokens:m.tokens,sheet:m.sheet,...n===null?{}:{parent:n},children:S()??b})}),T=s(w).childContext.overlay,E=i(()=>({...w().childContext,overlay:T}),void 0,{equals:sameContext}),D=childrenUnder(l,E);assertOwnChildren(e,D(),E),C(factsOf(e,D()));let O=s(w),k=u(O.node.tag),A=O.content===null?null:u(O.content.tag);O.slot!==null&&v.add(k),y.set(k,x);for(let[e,t]of Object.entries(dispatchers(O.events,l,k)))h(k,e,t);let j={},M={};d(()=>{let t=w();assertStableShape(e,O,t),writeProps(k,j,{...x()?.props,...t.node.props,...t.slot===null?{}:{slot:t.slot},...classProp(t.node)}),A!==null&&t.content!==null&&writeProps(A,M,{...t.content.props,...classProp(t.content)})});let N=l.ref;typeof N==`function`&&o(()=>N(g(k))),A!==null&&p(k,A);let P=A??k;if(O.absoluteSlot===null)f(P,D);else{f(P,()=>split(D()).ordinary);for(let e of split(s(D)).slotted)p(k,e)}return k}function childrenUnder(e,t){let n=_.Provider;return l(n,{value:t,get children(){return e.children}})}function assertOwnChildren(t,n,r){for(let i of Array.isArray(n)?n:[n]){if(typeof i!=`object`||!i)continue;let n=y.get(i);if(!(n===void 0||n===r))throw new e(t,`children`,"contains an element that was built BEFORE this one, so it resolved outside this element's context: it never learned this parent's orientation or its inherited alignment, and the loss is silent — `flex-1` stays unresolved and `items-center` never reaches it. Pass children lazily, `get children() { return … }`, which is what every Solid JSX compiler emits and the reason Solid's context works at all")}}function factsOf(t,n){let r=(Array.isArray(n)?n:[n]).filter(e=>e!=null&&typeof e!=`boolean`),i=0,a=!1;for(let n of r)if(typeof n==`string`||typeof n==`number`)a=!0;else if(typeof n==`function`)throw new e(t,`children`,'was given a function that takes an argument. Children-as-a-function-of-`{ pressed }` is not implemented (ADR 0032 § 7) — the press state is the GTK CSS `:active` pseudo-class, so write `className="active:opacity-70"` and GTK animates it with no reactive update at all. A zero-argument accessor is Solid\'s own dynamic child and never reaches this refusal');else typeof n==`object`&&n&&v.has(n)&&i++;return{absolute:i,count:r.length,text:a}}function split(e){let t=Array.isArray(e)?e:[e],n=[],r=[];for(let e of t)typeof e==`object`&&e&&v.has(e)?r.push(e):n.push(e);return{ordinary:n,slotted:r}}function dispatchers(e,t,n){let r={};for(let i of e)r[`on:${i.signal}`]=()=>{let e=t[i.prop];if(typeof e==`function`){if(i.read===null){e();return}e(g(n)[accessorName(i.read)])}};return r}function writeProps(e,t,n){for(let r of Object.keys(t))r in n||(h(e,r,void 0),delete t[r]);for(let[r,i]of Object.entries(n))sameValue(t[r],i)||(h(e,r,i),t[r]=i)}const classProp=e=>e.cssClasses.length===0?{}:{"css-classes":[...e.cssClasses]};function assertStableShape(t,n,r){let shape=e=>[e.node.tag,e.content?.tag??`-`,e.events.map(e=>`${e.prop}>${e.signal}`).join(`,`)].join(`|`),i=shape(n),a=shape(r);if(i!==a)throw new e(t,`reactive update`,`would change the element from ${i} to ${a}, and a Solid element is created once — there is no reconciler pass that could swap the widget. Split the branches into two elements under a <Show>, where each one is created for its own shape`)}function View(e){return element(`View`,e)}function Text(e){return element(`Text`,e)}function Pressable(e){return element(`Pressable`,e)}function ScrollView(e){return element(`ScrollView`,e)}function ActivityIndicator(e){return element(`ActivityIndicator`,e)}function TextInput(e){return element(`TextInput`,e)}function Switch(e){return element(`Switch`,e)}export{ActivityIndicator,Pressable,ScrollView,Switch,Text,TextInput,View,m as mount};
1
+ import"../_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"../primitives/errors.js";import{resolvePrimitive as t}from"../primitives/resolve.js";import{styleConfig as n}from"../style-config.js";import{createContext as r,createMemo as i,createSignal as a,onMount as o,untrack as s,useContext as c}from"solid-js";import{createComponent as l,createElement as u,effect as d,insert as f,insertNode as p,mount as m,setProp as h,widgetOf as g}from"@gjsify/gtk-host/solid";const _=r(()=>null),v=new WeakSet,y=new WeakMap,b={absolute:1,count:1,text:!1},accessorName=e=>e.replace(/-([a-z0-9])/g,(e,t)=>t.toUpperCase()),sameContext=(e,t)=>JSON.stringify(e)===JSON.stringify(t),sameValue=(e,t)=>Array.isArray(e)&&Array.isArray(t)?e.length===t.length&&e.every((e,n)=>e===t[n]):e===t;function element(e,r){let l=r,m=n(),x=c(_),[S,C]=a(null),w=i(()=>{let n=x();return t(e,l,{tokens:m.tokens,sheet:m.sheet,...n===null?{}:{parent:n},children:S()??b})}),T=s(w).childContext.overlay,E=i(()=>({...w().childContext,overlay:T}),void 0,{equals:sameContext}),D=childrenUnder(l,E);assertOwnChildren(e,D(),E),C(factsOf(e,D()));let O=s(w),k=u(O.node.tag),A=O.content===null?null:u(O.content.tag);O.slot!==null&&v.add(k),y.set(k,x);for(let[e,t]of Object.entries(dispatchers(O.events,l,k)))h(k,e,t);let j={},M={};d(()=>{let t=w();assertStableShape(e,O,t),writeProps(k,j,{...x()?.props,...t.node.props,...t.slot===null?{}:{slot:t.slot},...classProp(t.node)}),A!==null&&t.content!==null&&writeProps(A,M,{...t.content.props,...classProp(t.content)})});let N=l.ref;typeof N==`function`&&o(()=>N(g(k))),A!==null&&p(k,A);let P=A??k;if(O.absoluteSlot===null)f(P,D);else{f(P,()=>split(D()).ordinary);for(let e of split(s(D)).slotted)p(k,e)}return k}function childrenUnder(e,t){let n=_.Provider;return l(n,{value:t,get children(){return e.children}})}function assertOwnChildren(t,n,r){for(let i of Array.isArray(n)?n:[n]){if(typeof i!=`object`||!i)continue;let n=y.get(i);if(!(n===void 0||n===r))throw new e(t,`children`,"contains an element that was built BEFORE this one, so it resolved outside this element's context: it never learned this parent's orientation or its inherited alignment, and the loss is silent — `flex-1` stays unresolved and `items-center` never reaches it. Pass children lazily, `get children() { return … }`, which is what every Solid JSX compiler emits and the reason Solid's context works at all")}}function factsOf(t,n){let r=(Array.isArray(n)?n:[n]).filter(e=>e!=null&&typeof e!=`boolean`),i=0,a=!1;for(let n of r)if(typeof n==`string`||typeof n==`number`)a=!0;else if(typeof n==`function`)throw new e(t,`children`,"was given a function that takes an argument. Children-as-a-function-of-`{ pressed }` is implemented in the REACT binding and not in this one, and the difference is Solid's own: a component body runs once, so the press state would have to arrive as a SIGNAL the child reads rather than as a value the parent re-renders with — a different design, not a port of the React one. Write `className=\"active:opacity-70\"`, which is the GTK CSS `:active` pseudo-class (ADR 0032 § 7) and what nearly every use of the function form is asking for. A zero-argument accessor is Solid's own dynamic child and never reaches this refusal");else typeof n==`object`&&n&&v.has(n)&&i++;return{absolute:i,count:r.length,text:a}}function split(e){let t=Array.isArray(e)?e:[e],n=[],r=[];for(let e of t)typeof e==`object`&&e&&v.has(e)?r.push(e):n.push(e);return{ordinary:n,slotted:r}}function dispatchers(e,t,n){let r={};for(let i of e)r[`on:${i.signal}`]=()=>{let e=t[i.prop];if(typeof e==`function`){if(i.read===null){e();return}e(g(n)[accessorName(i.read)])}};return r}function writeProps(e,t,n){for(let r of Object.keys(t))r in n||(h(e,r,void 0),delete t[r]);for(let[r,i]of Object.entries(n))sameValue(t[r],i)||(h(e,r,i),t[r]=i)}const classProp=e=>e.cssClasses.length===0?{}:{"css-classes":[...e.cssClasses]};function assertStableShape(t,n,r){let shape=e=>[e.node.tag,e.content?.tag??`-`,e.events.map(e=>`${e.prop}>${e.signal}`).join(`,`)].join(`|`),i=shape(n),a=shape(r);if(i!==a)throw new e(t,`reactive update`,`would change the element from ${i} to ${a}, and a Solid element is created once — there is no reconciler pass that could swap the widget. Split the branches into two elements under a <Show>, where each one is created for its own shape`)}function View(e){return element(`View`,e)}function Text(e){return element(`Text`,e)}function Pressable(e){return element(`Pressable`,e)}function ScrollView(e){return element(`ScrollView`,e)}function ActivityIndicator(e){return element(`ActivityIndicator`,e)}function TextInput(e){return element(`TextInput`,e)}function Switch(e){return element(`Switch`,e)}export{ActivityIndicator,Pressable,ScrollView,Switch,Text,TextInput,View,m as mount};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{PrimitiveError as e}from"./primitives/errors.js";import{hairlineWidth as t}from"./apis/display.js";import{flattenStyle as n}from"./primitives/style.js";const r={position:`absolute`,top:0,right:0,bottom:0,left:0},i={create(e){return e},flatten(e){return n(e)},compose(e,t){return e==null||e===!1?t:t==null||t===!1?e:[e,t]},get hairlineWidth(){return t()},absoluteFill:r,absoluteFillObject:r,setStyleAttributePreprocessor(){throw new e(`StyleSheet`,`setStyleAttributePreprocessor`,"installs a process-wide transform on every style value, which this layer answers with a scoped one: `configureStyle({ tokens })` is where a project’s own values enter the vocabulary (ADR 0032 § 3). A global preprocessor beside it would be a second source of truth that no element could opt out of")}};export{i as StyleSheet,r as absoluteFillObject};
@@ -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 P2.`,limits:[`children-as-a-function-of-{ pressed } is a named refusal, at build time through the prop type and at runtime through a throw (ADR 0032 § 7). Write active:* instead — GTK animates :active itself with no re-render.`,`onPressIn/onPressOut are refused for the same reason: the press state never reaches the reconciler.`,`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.10: 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:`planned`,tier:`P1`,reason:`create/flatten/hairlineWidth/absoluteFill. Style objects go through the same partition as classes.`},FlatList:{status:`planned`,tier:`P2`,gtk:`Gtk.ListView + Gio.ListStore`,reason:`GTK virtualises for real, so this fits better here than it does on the web.`},SectionList:{status:`planned`,tier:`P2`,gtk:`Gtk.ListView + a section model`,reason:`Sections map onto GTK section models.`},VirtualizedList:{status:`planned`,tier:`P2`,gtk:`Gtk.ListView`,reason:`Its public surface is wide and mostly not worth honouring literally; the useful subset backs FlatList.`},VirtualizedSectionList:{status:`planned`,tier:`P2`,gtk:`Gtk.ListView`,reason:`The section-shaped sibling of VirtualizedList.`},Image:{status:`planned`,tier:`P2`,gtk:`Gtk.Picture / Gdk.Texture`,reason:`resizeMode becomes content-fit.`},ImageBackground:{status:`planned`,tier:`P2`,gtk:`Gtk.Picture in a Gtk.Overlay`,reason:`A picture with children over it.`},TouchableOpacity:{status:`planned`,tier:`P2`,gtk:`Gtk.Button (flat)`,reason:`The same machinery as Pressable, and nearly free once it exists.`},TouchableHighlight:{status:`planned`,tier:`P2`,gtk:`Gtk.Button (flat)`,reason:`As TouchableOpacity, with a different pressed style.`},TouchableWithoutFeedback:{status:`planned`,tier:`P2`,gtk:`Gtk.GestureClick`,reason:`A gesture controller on the child, with no button chrome.`},Button:{status:`planned`,tier:`P2`,gtk:`Gtk.Button`,reason:`The one component whose React Native styling story is "you cannot", which GTK agrees with.`},Dimensions:{status:`planned`,tier:`P2`,gtk:`Gdk.Surface`,reason:`Window size, not screen size — a desktop app is not full-screen.`},useWindowDimensions:{status:`planned`,tier:`P2`,gtk:`Gdk.Surface`,reason:`The hook form of Dimensions, re-rendering on resize.`},Alert:{status:`planned`,tier:`P2`,gtk:`Adw.AlertDialog`,reason:`Direct counterpart.`},Appearance:{status:`planned`,tier:`P2`,gtk:`Adw.StyleManager`,reason:`The imperative sibling of useColorScheme.`},SafeAreaView:{status:`no-desktop-meaning`,tier:`P2`,reason:`Insets are zero on a desktop window, but it has to exist to be imported.`},StatusBar:{status:`no-desktop-meaning`,tier:`P2`,reason:`A desktop window has no status bar to configure.`},KeyboardAvoidingView:{status:`no-desktop-meaning`,tier:`P2`,reason:`No on-screen keyboard eats a desktop window layout.`},Keyboard:{status:`no-desktop-meaning`,tier:`P2`,reason:`Its events are on-screen-keyboard events, which do not occur.`},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. Maps to the navigation view’s own back, so it lands with routing.`},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=Object.keys(e),n=new Set([`supported`,`partial`]),isImportable=t=>{let r=e[t];return r!==void 0&&n.has(r.status)};function explainUnsupported(t){let n=e[t];if(n===void 0)return`@gjsify/react-native: "${t}" 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 r=n.tier?` (tier ${n.tier})`:``,i=n.gtk?` The GTK counterpart is ${n.gtk}.`:``;switch(n.status){case`supported`:case`partial`:return`@gjsify/react-native: "${t}" is available.`;case`planned`:return`@gjsify/react-native: "${t}" is not implemented yet${r}. ${n.reason}${i}`;case`refused`:return`@gjsify/react-native: "${t}" will not be implemented. ${n.reason}`;case`no-desktop-meaning`:return`@gjsify/react-native: "${t}" has no meaning on a desktop window${r}. ${n.reason}`;case`not-reachable`:return`@gjsify/react-native: "${t}" cannot be implemented in this build chain. ${n.reason}`}}export{t 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,46 @@
1
+ /** React Native's own button descriptor. */
2
+ export interface AlertButton {
3
+ readonly text?: string;
4
+ readonly onPress?: () => void;
5
+ readonly style?: 'default' | 'cancel' | 'destructive';
6
+ /** Carried for parity and unused: a `Gtk.Entry` inside a dialog is `Alert.prompt`, which is iOS-only. */
7
+ readonly isPreferred?: boolean;
8
+ }
9
+ export interface AlertOptions {
10
+ /** Whether tapping outside dismisses it — Adwaita's `can-close`, which is the same idea. */
11
+ readonly cancelable?: boolean;
12
+ readonly onDismiss?: () => void;
13
+ }
14
+ export declare const Alert: {
15
+ /**
16
+ * Present a dialog with up to as many buttons as were given.
17
+ *
18
+ * `buttons` follows React Native's own defaulting: none given means one OK button,
19
+ * because a dialog with no way out is not a thing to ship. Beyond that the mapping
20
+ * is one response per button, in order, and the response IDs are positional
21
+ * (`response-0`, …) rather than derived from the label — two buttons labelled the
22
+ * same would otherwise collide into one response, silently losing a callback.
23
+ *
24
+ * THE FIRST NON-CANCEL BUTTON BECOMES THE DEFAULT RESPONSE, which is Adwaita's
25
+ * convention (Return activates it) and has no counterpart in React Native's model,
26
+ * where a phone has no Return key. Stated here because it is a decision, not a
27
+ * mapping.
28
+ *
29
+ * `onDismiss` is wired to `Adw.Dialog::closed` and not to `response`: measured,
30
+ * `Adw.AlertDialog` emits `response` and `Adw.Dialog` emits `closed` and
31
+ * `close-attempt`, and a dialog dismissed with Escape produces the close response
32
+ * — so `closed` is the one signal that fires however it went away.
33
+ */
34
+ readonly alert: (title: string, message?: string, buttons?: readonly AlertButton[], options?: AlertOptions) => void;
35
+ /**
36
+ * Refused by name: it is iOS-only, and the reason is not the platform.
37
+ *
38
+ * `Alert.prompt` puts a text field in the dialog. `Adw.AlertDialog` has an
39
+ * `extra-child` property that would hold a `Gtk.Entry`, so the widget half exists
40
+ * — what does not exist is React Native's contract for it: `prompt` has four
41
+ * overloads, a `keyboardType`, a login/password variant, and callbacks whose
42
+ * arity depends on the type. Building a subset of that would answer some calls and
43
+ * silently mis-answer others. Present your own dialog with an `extra-child`.
44
+ */
45
+ readonly prompt: () => never;
46
+ };
@@ -0,0 +1,28 @@
1
+ import { type ColorSchemeName } from './color-scheme.js';
2
+ import type { EventSubscription } from '../event-emitter.js';
3
+ /** React Native's own payload for a scheme change. */
4
+ export interface AppearancePreferences {
5
+ readonly colorScheme: ColorSchemeName | null;
6
+ }
7
+ export declare const Appearance: {
8
+ /**
9
+ * What the user is looking at. Never `null` here.
10
+ *
11
+ * React Native returns `null` for "the platform does not report one", which a
12
+ * desktop always does: `Adw.StyleManager:dark` is a boolean, so the answer is one
13
+ * of the two. Ported code that branches on `null` simply never takes that branch,
14
+ * which is the same thing it does on a phone that answers.
15
+ */
16
+ readonly getColorScheme: () => ColorSchemeName;
17
+ /** Ask for light, dark, or `null` to follow the desktop again. */
18
+ readonly setColorScheme: (scheme: ColorSchemeName | null) => void;
19
+ /**
20
+ * Subscribe to scheme changes, imperatively.
21
+ *
22
+ * The same subscription `useColorScheme` uses, so a component and an imperative
23
+ * caller cannot see different schemes. The disposer disconnects the GObject
24
+ * handler rather than setting a flag: GJS blocks JS callbacks during GC, so a
25
+ * handler left connected stays connected for the life of the process.
26
+ */
27
+ readonly addChangeListener: (listener: (preferences: AppearancePreferences) => void) => EventSubscription;
28
+ };
@@ -2,6 +2,27 @@
2
2
  export type ColorSchemeName = 'light' | 'dark';
3
3
  /** What the user is looking at right now. */
4
4
  export declare const currentColorScheme: () => ColorSchemeName;
5
+ /**
6
+ * Ask for a scheme, or hand the choice back to the desktop.
7
+ *
8
+ * The WRITE side of the same object, and the one place `Adw.StyleManager:color-scheme`
9
+ * is the right property rather than `dark`: `color-scheme` is what the APPLICATION
10
+ * asked for, which is exactly what `Appearance.setColorScheme` sets, while `dark` is
11
+ * read-only and is what the user ends up looking at.
12
+ *
13
+ * MEASURED on libadwaita 1.9.3: `Adw.ColorScheme` is DEFAULT=0, FORCE_LIGHT=1,
14
+ * PREFER_LIGHT=2, PREFER_DARK=3, FORCE_DARK=4. React Native's three values are
15
+ * `'light'`, `'dark'` and `null` (follow the system), so the FORCE members are the
16
+ * mapping and the PREFER members are not: `PREFER_DARK` means "dark unless the
17
+ * desktop insists otherwise", which is a fourth state React Native cannot express and
18
+ * would make `setColorScheme('dark')` a request the desktop could refuse.
19
+ *
20
+ * The two are round-trip verifiable and the spec does it: setting FORCE_LIGHT made
21
+ * `dark` false and raised `notify::dark`, and FORCE_DARK made it true again.
22
+ */
23
+ export declare function setColorSchemePreference(scheme: ColorSchemeName | null): void;
24
+ /** What the application has ASKED for, which is not what it is looking at. */
25
+ export declare const requestedColorScheme: () => ColorSchemeName | null;
5
26
  /**
6
27
  * Call `listener` whenever the scheme changes; returns the unsubscribe.
7
28
  *
@@ -0,0 +1,42 @@
1
+ import { type DisplayMetrics } from './display.js';
2
+ import type { EventSubscription } from '../event-emitter.js';
3
+ /** React Native's own two dimension keys. */
4
+ export type DimensionKey = 'window' | 'screen';
5
+ export declare const Dimensions: {
6
+ /**
7
+ * The size of the window, or of the monitor it is on.
8
+ *
9
+ * A named throw for an unknown key rather than `undefined`: React Native's own
10
+ * `get` throws too, and the shape of the failure matters here — the caller is
11
+ * about to read `.width` off the answer.
12
+ */
13
+ readonly get: (dimension: DimensionKey) => DisplayMetrics;
14
+ /**
15
+ * Re-render on resize, imperatively.
16
+ *
17
+ * React Native's own event name is `'change'` and its payload carries BOTH
18
+ * dimensions, which is why the listener is handed both rather than the one that
19
+ * moved: a window dragged to another monitor changes `screen` without changing
20
+ * `window`, and a subscriber that only saw the window's number would miss it.
21
+ *
22
+ * `screen` is read defensively — a display with no monitors is a headless session
23
+ * and `screenMetrics` refuses it by name — because a resize notification must not
24
+ * be lost to a question the subscriber did not ask. `window` is not: this callback
25
+ * only fires while a window exists.
26
+ */
27
+ readonly addEventListener: (type: "change", listener: (metrics: DimensionsPayload) => void) => EventSubscription;
28
+ /**
29
+ * Refused by name: React Native's native side calls it to PUBLISH the numbers.
30
+ *
31
+ * It is not an application API — `Dimensions.set` is how the bridge tells
32
+ * JavaScript what the device measured. There is no bridge here; GTK is asked
33
+ * directly, every time, so there is nothing to store and a stored value would be
34
+ * a second answer that could disagree with the window.
35
+ */
36
+ readonly set: () => never;
37
+ };
38
+ /** What a `'change'` listener receives — React Native's own payload shape. */
39
+ export interface DimensionsPayload {
40
+ readonly window: DisplayMetrics;
41
+ readonly screen: DisplayMetrics | null;
42
+ }
@@ -0,0 +1,87 @@
1
+ /** React Native's own shape, field for field. */
2
+ export interface DisplayMetrics {
3
+ readonly width: number;
4
+ readonly height: number;
5
+ readonly scale: number;
6
+ readonly fontScale: number;
7
+ }
8
+ /**
9
+ * One device pixel, in the logical pixels GTK CSS is written in.
10
+ *
11
+ * React Native's `hairlineWidth` is "the thinnest line the platform can draw", which
12
+ * is one device pixel; GTK CSS lengths are logical pixels, so that is `1 / scale`.
13
+ *
14
+ * IT IS A FUNCTION AND NOT A CONSTANT, because real code reads
15
+ * `StyleSheet.hairlineWidth` at module scope and `Gdk.Display.get_default()` is null
16
+ * until `Gtk.init()` (measured). Before then this answers 1 — one logical pixel,
17
+ * which IS one device pixel on an unscaled display and is the only value available;
18
+ * the same read after the application starts answers from the display. A cached
19
+ * module-level constant would freeze the pre-init answer for the life of the process.
20
+ *
21
+ * THE SMALLEST SCALE, NOT THE LARGEST, and this is the one judgement in the file.
22
+ * GTK4 removed the primary-monitor concept (measured), so with no window there is no
23
+ * principled "main screen" to read — and a global constant has to be right on every
24
+ * monitor the window may be moved to. `1 / max(scale)` is one device pixel on the
25
+ * finest monitor and SUB-pixel on a coarser one, where GTK snaps it and it can
26
+ * disappear; `1 / min(scale)` is one device pixel on the coarsest and at most a
27
+ * device pixel too thick elsewhere. A line that is slightly too thick is a worse
28
+ * hairline; a line that is not drawn is not a hairline at all.
29
+ */
30
+ export declare function hairlineWidth(): number;
31
+ /**
32
+ * The application window's size — React Native's `Dimensions.get('window')`.
33
+ *
34
+ * The ALLOCATION (`Gtk.Widget.get_width/get_height`), not the surface: measured, a
35
+ * 640×480 window has a 668×509 surface because the surface carries the CSD shadow,
36
+ * and React Native's number is the window's.
37
+ *
38
+ * Before the window has been allocated the allocation is 0×0 (measured), and then
39
+ * `default-width`/`default-height` are reported — which is the size the window is
40
+ * about to have, readable from the moment it is constructed. A zero would be a lie
41
+ * that divides.
42
+ */
43
+ export declare function windowMetrics(): DisplayMetrics;
44
+ /**
45
+ * The monitor's size — React Native's `Dimensions.get('screen')`.
46
+ *
47
+ * Implemented rather than refused, because `screen` genuinely IS the monitor and
48
+ * asking for it is a different question from asking for the window. What ADR 0032
49
+ * decides is that the WINDOW is what `Dimensions.get('window')` reports; a desktop
50
+ * application is not full-screen, so answering the window's question with the
51
+ * monitor's number would be wrong in the ordinary case rather than the rare one.
52
+ *
53
+ * The monitor is the one the window is on when there is a surface to ask about
54
+ * (`get_monitor_at_surface`), and the first one otherwise — GTK4 has no primary
55
+ * monitor to prefer (measured).
56
+ */
57
+ export declare function screenMetrics(): DisplayMetrics;
58
+ /**
59
+ * Call `listener` whenever the window's size changes; returns the unsubscribe.
60
+ *
61
+ * THE NOTIFIER AND THE VALUE ARE DIFFERENT OBJECTS, and that is measured rather than
62
+ * chosen. `Gtk.Widget` installs no width or height property and emits no
63
+ * size-allocate signal — it reports its allocation through
64
+ * `Gtk.Widget.vfunc_size_allocate`, a subclass override, which is the same wall
65
+ * `<View onLayout>` is refused against. `Gdk.Surface` DOES install `width` and
66
+ * `height` (read-only, measured) and notifies on them — measured: two
67
+ * `notify::width` emissions while a window was being mapped. So the surface says
68
+ * WHEN and the window says WHAT.
69
+ *
70
+ * PRECONDITION NOT MEASURED: whether the window's allocation is already updated
71
+ * inside that handler. A compositor-driven resize cannot be triggered from a probe —
72
+ * `set_default_size` on a mapped window is a no-op (measured: the window stayed
73
+ * 640×480 while `default-width` became 800) — so the ordering is stated as unknown
74
+ * rather than guessed. If GTK allocates after the notification, a resize is reported
75
+ * from the value read at the NEXT notification.
76
+ *
77
+ * A window with no surface yet is subscribed through its own `realize`, because that
78
+ * is when the surface appears (measured: null before, present after presenting).
79
+ * Every connection is disconnected by the disposer rather than left to a flag: GJS
80
+ * blocks JS callbacks during GC, so a handler that is not disconnected stays
81
+ * connected for the life of the process.
82
+ */
83
+ export declare function onWindowMetricsChange(listener: () => void): () => void;
84
+ /** {@link windowMetrics}, with a stable identity while the numbers are unchanged. */
85
+ export declare function windowMetricsSnapshot(): DisplayMetrics;
86
+ /** Test seam: forget the cached snapshot, so the next read is a fresh object. */
87
+ export declare function resetWindowMetricsCache(): void;
@@ -1,5 +1,14 @@
1
- export { currentColorScheme, onColorSchemeChange } from './color-scheme.js';
1
+ export { Alert } from './alert.js';
2
+ export type { AlertButton, AlertOptions } from './alert.js';
3
+ export { Appearance } from './appearance.js';
4
+ export type { AppearancePreferences } from './appearance.js';
5
+ export { currentColorScheme, onColorSchemeChange, requestedColorScheme, setColorSchemePreference, } from './color-scheme.js';
2
6
  export type { ColorSchemeName } from './color-scheme.js';
7
+ export { Dimensions } from './dimensions.js';
8
+ export type { DimensionKey, DimensionsPayload } from './dimensions.js';
9
+ export { hairlineWidth, onWindowMetricsChange, resetWindowMetricsCache, screenMetrics, windowMetrics, windowMetricsSnapshot, } from './display.js';
10
+ export type { DisplayMetrics } from './display.js';
11
+ export { Keyboard } from './keyboard.js';
3
12
  export { Linking } from './linking.js';
4
13
  export { Platform, resetPlatformCache } from './platform.js';
5
14
  export type { PlatformOS, PlatformSelectSpec } from './platform.js';
@@ -0,0 +1,22 @@
1
+ export declare const Keyboard: {
2
+ /**
3
+ * Refused by name. See the header: a listener that never fires is the silent drop.
4
+ */
5
+ readonly addListener: (eventName?: string) => never;
6
+ /** A declared no-op: nothing is shown, so nothing has to be hidden. */
7
+ readonly dismiss: () => void;
8
+ /** A declared no-op: `addListener` refuses, so there is nothing registered to remove. */
9
+ readonly removeAllListeners: () => void;
10
+ /** False, always — React Native's own answer for a keyboard that is not up. */
11
+ readonly isVisible: () => boolean;
12
+ /** `undefined`, which is React Native's own answer while the keyboard is hidden. */
13
+ readonly metrics: () => undefined;
14
+ /**
15
+ * Refused by name: it schedules a `LayoutAnimation`, which is tier P3.
16
+ *
17
+ * Not a keyboard question at all — it asks the animated layout subsystem to
18
+ * animate the next commit. `LayoutAnimation` is `planned` for the same reason
19
+ * `Animated` is: it is a subsystem, and doing it badly is worse than not doing it.
20
+ */
21
+ readonly scheduleLayoutAnimation: () => never;
22
+ };