@gjsify/adwaita-react-native 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.gtk.js +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/index.native.js +1 -0
- package/lib/esm/props.js +0 -0
- package/lib/esm/refuse.js +1 -0
- package/lib/esm/testing/react-native.js +1 -0
- package/lib/esm/widgets/bin.gtk.js +1 -0
- package/lib/esm/widgets/bin.js +1 -0
- package/lib/esm/widgets/bin.native.js +1 -0
- package/lib/esm/widgets/clamp.gtk.js +1 -0
- package/lib/esm/widgets/clamp.js +1 -0
- package/lib/esm/widgets/clamp.native.js +1 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.gtk.d.ts +3 -0
- package/lib/types/index.native.d.ts +3 -0
- package/lib/types/parity.spec.d.ts +71 -0
- package/lib/types/props.d.ts +19 -0
- package/lib/types/refuse.d.ts +9 -0
- package/lib/types/testing/react-native.d.ts +18 -0
- package/lib/types/testing/react-native.spec.d.ts +33 -0
- package/lib/types/widgets/bin.d.ts +10 -0
- package/lib/types/widgets/bin.gtk.d.ts +5 -0
- package/lib/types/widgets/bin.native.d.ts +5 -0
- package/lib/types/widgets/bin.native.spec.d.ts +3 -0
- package/lib/types/widgets/clamp.d.ts +12 -0
- package/lib/types/widgets/clamp.gtk.d.ts +5 -0
- package/lib/types/widgets/clamp.gtk.spec.d.ts +3 -0
- package/lib/types/widgets/clamp.native.d.ts +12 -0
- package/lib/types/widgets/clamp.native.spec.d.ts +3 -0
- package/package.json +101 -0
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @gjsify/adwaita-react-native
|
|
2
|
+
|
|
3
|
+
Adwaita widgets with **one API surface and two implementations**: the real `Adw.*`
|
|
4
|
+
widget on GTK4, React Native primitives on a phone.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
import { AdwClamp } from '@gjsify/adwaita-react-native';
|
|
8
|
+
|
|
9
|
+
<AdwClamp maximumSize={400}>{content}</AdwClamp>;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
On GTK that is `Adw.Clamp` through [`@gjsify/gtk-host`](../gtk-host). On Android and
|
|
13
|
+
iOS it is a `View` whose width and offset come from
|
|
14
|
+
[`@gjsify/adwaita-core`](../../web/adwaita-core)'s port of
|
|
15
|
+
`adw_clamp_layout_allocate` — libadwaita's own easing curve, not a `maxWidth`
|
|
16
|
+
approximation.
|
|
17
|
+
|
|
18
|
+
## What this is, honestly
|
|
19
|
+
|
|
20
|
+
**A walking skeleton, not a widget set.** Two widgets — `AdwBin` and `AdwClamp` —
|
|
21
|
+
carrying the whole vertical: both platform halves, the resolution mechanism, the
|
|
22
|
+
manifest declaration, a gate, suites on both sides, and a photograph of the GTK half.
|
|
23
|
+
The remaining widgets are repetition against a boundary that has been measured.
|
|
24
|
+
|
|
25
|
+
**The promise is the Adwaita design language on React Native, not "your app runs".**
|
|
26
|
+
Unlike [`@gjsify/react-native`](../react-native) — the opposite direction, which was
|
|
27
|
+
measured against a real production Expo application — this package has no third-party
|
|
28
|
+
codebase behind it. What it has is a shared arithmetic core and two renderers held to
|
|
29
|
+
the same numbers.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
Not on npm yet: this is a new name, and the first publish plus its Trusted Publisher
|
|
34
|
+
bootstrap is a maintainer action deliberately not attempted from CI or a feature branch
|
|
35
|
+
([the procedure](../../../docs/publishing.md)). Until then it is consumable from a
|
|
36
|
+
workspace checkout.
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
npm install @gjsify/adwaita-react-native
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`react` (>= 19.2) is a peer dependency. `react-native` (>= 0.87) is an **optional** peer:
|
|
43
|
+
a GTK-only consumer does not need it installed.
|
|
44
|
+
|
|
45
|
+
## Widgets
|
|
46
|
+
|
|
47
|
+
| widget | props | notes |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `AdwBin` | `children` | One child, no layout of its own |
|
|
50
|
+
| `AdwClamp` | `children`, `maximumSize` (600), `tighteningThreshold` (400) | Constrain a child's width and centre it, on libadwaita's easing curve |
|
|
51
|
+
|
|
52
|
+
Props are libadwaita's own names, camelCased — `maximumSize` is `AdwClamp:maximum-size`,
|
|
53
|
+
not a React Native `maxWidth` — so the property you look up in libadwaita's documentation
|
|
54
|
+
is the property you write. Defaults are libadwaita's.
|
|
55
|
+
|
|
56
|
+
## How the two halves are selected
|
|
57
|
+
|
|
58
|
+
Through the package's `exports` map and the `react-native` condition:
|
|
59
|
+
|
|
60
|
+
| condition | entry |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `react-native` | `lib/esm/index.native.js` |
|
|
63
|
+
| `default` | `lib/esm/index.gtk.js` |
|
|
64
|
+
| `exports` ignored, `module` read | `lib/esm/index.js` — refuses, by name |
|
|
65
|
+
|
|
66
|
+
**A stock React Native 0.87 application needs no configuration.** `metro-config` enables
|
|
67
|
+
package exports and `@react-native/metro-config` supplies the `react-native` condition.
|
|
68
|
+
|
|
69
|
+
Two things follow that are easy to undo by accident, so both are held by
|
|
70
|
+
`scripts/check-adwaita-rn-platform-split.mjs` rather than by convention — its header
|
|
71
|
+
carries the resolver measurements behind them:
|
|
72
|
+
|
|
73
|
+
- **The condition ORDER is part of the map.** `exports` answers with the first match and
|
|
74
|
+
`default` matches everything, so moving `default` ahead of `react-native` hands the GTK
|
|
75
|
+
build to every phone.
|
|
76
|
+
- **Every barrel names its platform files literally.** Nothing relies on a resolver
|
|
77
|
+
picking a `.native` sibling: that mechanism is real, and right for an *application*,
|
|
78
|
+
but it does not carry a published library whose shipped imports carry a `.js` extension.
|
|
79
|
+
|
|
80
|
+
### Consuming it on GTK
|
|
81
|
+
|
|
82
|
+
Nothing to configure on the phone. On GTK, point JSX at the host that has a GTK element
|
|
83
|
+
list, so a stray `<div>` is a type error instead of a tag that type-checks and renders
|
|
84
|
+
nothing:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"compilerOptions": {
|
|
89
|
+
"jsx": "react-jsx",
|
|
90
|
+
"jsxImportSource": "@gjsify/gtk-host/react"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## What is proven, and what is not
|
|
96
|
+
|
|
97
|
+
| claim | held by |
|
|
98
|
+
|---|---|
|
|
99
|
+
| both halves satisfy one declared surface | `tsc` — a platform module that renames, drops or invents a prop, or changes an arity, is a compile error |
|
|
100
|
+
| the `.native` half type-checks against **real** React Native | the `react-native` package's own types, never a subset |
|
|
101
|
+
| GTK: the widget is the real `Adw.*`, and it **renders** | the live GTK tree plus a GSK rasterisation, because GTK's failure mode is an empty window at exit 0 |
|
|
102
|
+
| React Native: which primitives, which props, which nesting | React's own reconciler over `react-test-renderer` |
|
|
103
|
+
| both halves agree on the number | two frames, one of them ON the easing curve: 1000 points at `maximumSize` 400 gives width 400 at offset 300, and 700 points at the default 600/400 gives **575 at offset 62**, where a `min()` would give 600 |
|
|
104
|
+
| both halves answer the same for a property value GObject cannot store | `normalizeClampSize` from `@gjsify/adwaita-core`, run by **both** halves — the same call `@gjsify/adwaita-web` and `@gjsify/adwaita-nativescript` make. `400.7` → 400, `NaN` → the default 600, `-5` → the range floor 0, asserted on both sides |
|
|
105
|
+
|
|
106
|
+
**Not proven: Yoga, and the device.** A `width` in a style object is an instruction to a
|
|
107
|
+
layout engine that no test here runs. The React Native half is type-checked and
|
|
108
|
+
tree-checked and has never been on a phone. That gap closes with an app on a real device,
|
|
109
|
+
and until then it is a gap, not a formality.
|
|
110
|
+
|
|
111
|
+
## Named divergences
|
|
112
|
+
|
|
113
|
+
These are places where the two halves cannot be made identical, written down rather
|
|
114
|
+
than smoothed over.
|
|
115
|
+
|
|
116
|
+
- **`AdwClamp` ignores the child's intrinsic minimum on React Native.** libadwaita's
|
|
117
|
+
clamp is a two-pass measure-then-allocate, and a child minimum wider than the clamp
|
|
118
|
+
RAISES all three thresholds — which is how such a child still gets its minimum
|
|
119
|
+
instead of being cut off. React Native has no such pass: `onLayout` reports a size
|
|
120
|
+
after layout and never the child's intrinsic minimum. So `childMin`/`childNat` are
|
|
121
|
+
passed as 0, and a child wider than the clamp is compressed here and is not on GTK.
|
|
122
|
+
- **`AdwClamp`'s `small`/`medium`/`large` size class is not carried.** libadwaita stamps
|
|
123
|
+
it on the child as a style class; React Native has no class system to stamp it into,
|
|
124
|
+
and inventing a styling seam is a decision this slice does not make.
|
|
125
|
+
- **One unclamped frame.** Before the first `onLayout` there is no available width, so
|
|
126
|
+
the child renders full-width for one frame.
|
|
127
|
+
- **A second child is kept on React Native and dropped on GTK, silently.** `Adw.Bin` and
|
|
128
|
+
`Adw.Clamp` are one-child widgets, and gtk-host's `single` child policy fills that slot
|
|
129
|
+
with `set_child`, so a second child EVICTS the first — with no throw, no host error and
|
|
130
|
+
no GLib message. A `View` has no such limit and renders both. Both behaviours are pinned
|
|
131
|
+
by a test on each side, so neither can move alone. Refusing more than one child on both
|
|
132
|
+
halves would close it and is a surface decision this slice does not make.
|
|
133
|
+
- **`Adw.ClampScrollable` has no counterpart and will not get one.** It binds its four
|
|
134
|
+
scroll properties onto its child, which is a GTK adoption concern; on React Native
|
|
135
|
+
scrolling belongs to the `ScrollView`, not to the clamp. This is an asymmetry, not a
|
|
136
|
+
missing widget.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AdwBin as e}from"./widgets/bin.gtk.js";import{AdwClamp as t}from"./widgets/clamp.gtk.js";export{e as AdwBin,t as AdwClamp};
|
package/lib/esm/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AdwBin as e}from"./widgets/bin.js";import{AdwClamp as t}from"./widgets/clamp.js";export{e as AdwBin,t as AdwClamp};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AdwBin as e}from"./widgets/bin.native.js";import{AdwClamp as t}from"./widgets/clamp.native.js";export{e as AdwBin,t as AdwClamp};
|
package/lib/esm/props.js
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";const e=`@gjsify/adwaita-react-native`;function refuseBaseModule(t){throw Error(`${e}: <${t}> was loaded from the BASE module, which has no implementation. The two real ones are selected by the package's \`exports\` map — the \`react-native\` condition picks the React Native build, anything else picks the GTK build. Reaching this module means the tool that resolved ${e} ignored export conditions. For Metro, that is \`unstable_enablePackageExports\` (on by default since metro-config 0.83) plus \`unstable_conditionNames: ['react-native']\` (set by @react-native/metro-config); for anything else, import the platform module directly.`)}export{refuseBaseModule};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{createElement as e}from"react";const t=`RCTView`,View=n=>e(t,n);export{t as RCT_VIEW,View};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{jsx as e}from"@gjsify/gtk-host/react/jsx-runtime";function AdwBin({children:t}){return e(`adw-bin`,{children:t})}export{AdwBin};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{refuseBaseModule as e}from"../refuse.js";function AdwBin(t){return e(`AdwBin`)}export{AdwBin};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{View as e}from"react-native";import{jsx as t}from"react/jsx-runtime";function AdwBin({children:n}){return t(e,{children:n})}export{AdwBin};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{jsx as e}from"@gjsify/gtk-host/react/jsx-runtime";import{ADW_CLAMP_DEFAULTS as t,normalizeClampSize as n}from"@gjsify/adwaita-core";const authoredSize=(e,t)=>e===void 0?void 0:n(e,t);function AdwClamp({children:n,maximumSize:r,tighteningThreshold:i}){return e(`adw-clamp`,{"maximum-size":authoredSize(r,t.maximumSize),"tightening-threshold":authoredSize(i,t.tighteningThreshold),children:n})}export{AdwClamp};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{refuseBaseModule as e}from"../refuse.js";function AdwClamp(t){return e(`AdwClamp`)}export{AdwClamp};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{ADW_CLAMP_DEFAULTS as e,clampAllocate as t,normalizeClampSize as n}from"@gjsify/adwaita-core";import{View as r}from"react-native";import{jsx as i}from"react/jsx-runtime";import{useCallback as a,useState as o}from"react";function AdwClamp({children:s,maximumSize:c,tighteningThreshold:l}){let[u,d]=o(null),f=a(e=>{d(e.nativeEvent.layout.width)},[]),p=u===null?null:t(u,{maximumSize:n(c,e.maximumSize),tighteningThreshold:n(l,e.tighteningThreshold),childMin:0,childNat:0});return i(r,{onLayout:f,style:{alignSelf:`stretch`},children:i(r,{style:p===null?void 0:{width:p.childSize,marginStart:p.offset},children:s})})}export{AdwClamp};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type * as BinBase from './widgets/bin.js';
|
|
2
|
+
import type * as BinGtk from './widgets/bin.gtk.js';
|
|
3
|
+
import type * as BinNative from './widgets/bin.native.js';
|
|
4
|
+
import type * as ClampBase from './widgets/clamp.js';
|
|
5
|
+
import type * as ClampGtk from './widgets/clamp.gtk.js';
|
|
6
|
+
import type * as ClampNative from './widgets/clamp.native.js';
|
|
7
|
+
/**
|
|
8
|
+
* `Assert<false>` is a constraint violation, and that is the whole trick.
|
|
9
|
+
*
|
|
10
|
+
* THE FIRST VERSION OF THIS FILE WAS INERT, and it failed in the way this repository
|
|
11
|
+
* pays for most often: a conditional type resolved to `never` exactly as designed, and
|
|
12
|
+
* the witness that was supposed to reject `never` used an `as` cast, which TypeScript
|
|
13
|
+
* permits from `{}`. Measured — making `AdwBin`'s React Native props require a field
|
|
14
|
+
* the base does not declare left the whole package at exit 0. So the assertion is a
|
|
15
|
+
* CONSTRAINT now: `Assert<T extends true>` cannot be satisfied by `false`, there is no
|
|
16
|
+
* cast in the expression, and the diagnostic names the alias, which names the widget
|
|
17
|
+
* and the platform.
|
|
18
|
+
*
|
|
19
|
+
* Exported: `testing/react-native.spec.ts` holds the double against React Native's own
|
|
20
|
+
* surface with the same trick, and a second copy of a one-line type is a second copy.
|
|
21
|
+
*/
|
|
22
|
+
export type Assert<T extends true> = T;
|
|
23
|
+
/**
|
|
24
|
+
* Are `A` and `B` the same type, without a union distributing on the way in?
|
|
25
|
+
*
|
|
26
|
+
* The tuple wrappers are load-bearing: a bare `A extends B` distributes over a union,
|
|
27
|
+
* and the two things this file compares — key sets — are unions.
|
|
28
|
+
*/
|
|
29
|
+
export type Identical<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
|
|
30
|
+
/**
|
|
31
|
+
* Do `A` and `B` accept exactly the same property NAMES?
|
|
32
|
+
*
|
|
33
|
+
* The half of a props contract that structural assignability cannot express when every
|
|
34
|
+
* property is optional. Used here per widget, and by `testing/react-native.spec.ts`
|
|
35
|
+
* against React Native's own `View`.
|
|
36
|
+
*/
|
|
37
|
+
export type SameKeys<A, B> = Identical<keyof A, keyof B>;
|
|
38
|
+
/** The props a component accepts, or `never` for anything that is not one. */
|
|
39
|
+
type PropsOf<Component> = Component extends (props: infer P) => unknown ? P : never;
|
|
40
|
+
/**
|
|
41
|
+
* Does `Platform` provide everything `Base` declares — the whole module surface by
|
|
42
|
+
* assignability, AND `Name`'s prop names exactly?
|
|
43
|
+
*
|
|
44
|
+
* The component is named rather than derived because a mapped type over `keyof Base`
|
|
45
|
+
* stays deferred when `Base` is a type parameter, and two deferred mapped types compare
|
|
46
|
+
* EQUAL: written that way the key-set half was inert, and the rename it was added for
|
|
47
|
+
* went on passing. A named component is also the better diagnostic, and `Name` is
|
|
48
|
+
* constrained to both modules, so a wrong one is an error here rather than a silent
|
|
49
|
+
* `never`.
|
|
50
|
+
*
|
|
51
|
+
* The assignability half stays: it is what catches a REQUIRED prop the base does not
|
|
52
|
+
* declare, a changed arity and a widened return, none of which a key set can see. A
|
|
53
|
+
* platform-only export beyond `Name` is deliberately fine — the surface promise is about
|
|
54
|
+
* the widgets, not about a helper one half happens to need.
|
|
55
|
+
*/
|
|
56
|
+
type SatisfiesBase<Platform, Base, Name extends keyof Base & keyof Platform> = Platform extends Base ? SameKeys<PropsOf<Platform[Name]>, PropsOf<Base[Name]>> : false;
|
|
57
|
+
export type BinGtkSatisfiesBase = Assert<SatisfiesBase<typeof BinGtk, typeof BinBase, 'AdwBin'>>;
|
|
58
|
+
export type BinNativeSatisfiesBase = Assert<SatisfiesBase<typeof BinNative, typeof BinBase, 'AdwBin'>>;
|
|
59
|
+
export type ClampGtkSatisfiesBase = Assert<SatisfiesBase<typeof ClampGtk, typeof ClampBase, 'AdwClamp'>>;
|
|
60
|
+
export type ClampNativeSatisfiesBase = Assert<SatisfiesBase<typeof ClampNative, typeof ClampBase, 'AdwClamp'>>;
|
|
61
|
+
/**
|
|
62
|
+
* The names above, as data, so the RUNTIME half can assert the set is complete.
|
|
63
|
+
*
|
|
64
|
+
* A type alias that is deleted takes its check with it and leaves no trace; this list
|
|
65
|
+
* is what a test can count, and `check-adwaita-rn-platform-split.mjs` holds it against
|
|
66
|
+
* the widgets on disk. Two derivations of one fact, and the weaker one is the one that
|
|
67
|
+
* would otherwise wave a missing widget through.
|
|
68
|
+
*/
|
|
69
|
+
export declare const PARITY_ASSERTIONS: readonly ["BinGtkSatisfiesBase", "BinNativeSatisfiesBase", "ClampGtkSatisfiesBase", "ClampNativeSatisfiesBase"];
|
|
70
|
+
declare const _default: () => Promise<void>;
|
|
71
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/** What every widget in this package accepts. */
|
|
3
|
+
export interface AdwWidgetProps {
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
/** `Adw.Bin` — a widget with one child and no layout of its own. */
|
|
7
|
+
export type AdwBinProps = AdwWidgetProps;
|
|
8
|
+
/**
|
|
9
|
+
* `Adw.Clamp` — constrain a child's size and centre it.
|
|
10
|
+
*
|
|
11
|
+
* The defaults named below are libadwaita's own; in code both halves read them from
|
|
12
|
+
* `@gjsify/adwaita-core`'s `ADW_CLAMP_DEFAULTS` rather than repeating them.
|
|
13
|
+
*/
|
|
14
|
+
export interface AdwClampProps extends AdwWidgetProps {
|
|
15
|
+
/** `maximum-size` — how wide the child may get. Default 600. */
|
|
16
|
+
maximumSize?: number;
|
|
17
|
+
/** `tightening-threshold` — where the eased tightening starts. Default 400. */
|
|
18
|
+
tighteningThreshold?: number;
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse, naming the component and how to reach a real one.
|
|
3
|
+
*
|
|
4
|
+
* Called from the component body rather than at module scope so that a bundler which
|
|
5
|
+
* merely REACHES the base module — a type-only import that survived, a barrel walked for
|
|
6
|
+
* its declarations — does not die at load. The failure is at the first render, which is
|
|
7
|
+
* still long before anything ships.
|
|
8
|
+
*/
|
|
9
|
+
export declare function refuseBaseModule(component: string): never;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { View as RealView } from 'react-native';
|
|
2
|
+
/**
|
|
3
|
+
* The host element name `View` renders as.
|
|
4
|
+
*
|
|
5
|
+
* `RCTView` is React Native's own name for the platform view — the type a real
|
|
6
|
+
* `react-test-renderer` tree carries under the real runtime — so a test asserting on it
|
|
7
|
+
* reads the same as one written against the real thing.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RCT_VIEW = "RCTView";
|
|
10
|
+
/**
|
|
11
|
+
* `react-native`'s `View`, as a host component.
|
|
12
|
+
*
|
|
13
|
+
* The `typeof RealView` annotation IS the contract: a double that grew a prop React
|
|
14
|
+
* Native does not have, or lost one it does, is a compile error here rather than a green
|
|
15
|
+
* test against a private fiction. `props` is widened because `createElement` is typed
|
|
16
|
+
* against React's intrinsic elements and knows nothing about React Native's.
|
|
17
|
+
*/
|
|
18
|
+
export declare const View: typeof RealView;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { View as RealView } from 'react-native';
|
|
2
|
+
import type { Assert, SameKeys } from '../parity.spec.js';
|
|
3
|
+
import { View } from './react-native.js';
|
|
4
|
+
/**
|
|
5
|
+
* The double accepts EXACTLY React Native's `View` props — same names, no more, no
|
|
6
|
+
* fewer.
|
|
7
|
+
*
|
|
8
|
+
* ON KEY SETS, AND WHY THE OBVIOUS VERSION DOES NOT WORK. The first attempt was
|
|
9
|
+
* `const CONTRACT: typeof RealView = View`, which reads like the whole contract and
|
|
10
|
+
* catches almost nothing: `ViewProps` is entirely OPTIONAL properties, and under
|
|
11
|
+
* structural typing any two all-optional object types are assignable to each other in
|
|
12
|
+
* both directions. Measured with that assertion ALONE in the file — replacing the
|
|
13
|
+
* annotation in `react-native.ts` with a hand-written interface carrying an invented
|
|
14
|
+
* `notOnTheRealOne?: number` produced no complaint from the contract at all. Comparing
|
|
15
|
+
* `keyof` is what bites, because a key set is a union of string literals and a union
|
|
16
|
+
* with an extra member is not assignable to one without it. Both directions were run
|
|
17
|
+
* against the version below: an invented prop and a dropped `onLayout` each fail on
|
|
18
|
+
* `DoublePropsMatchReactNative` and nowhere else. A third, added since: a prop whose
|
|
19
|
+
* TYPE changes while the key set does not (`onLayout?: string`) is invisible here and
|
|
20
|
+
* falls on `CONTRACT` below — the two assertions cover different halves and both are
|
|
21
|
+
* needed.
|
|
22
|
+
*
|
|
23
|
+
* The check is deliberately TAUTOLOGICAL while `react-native.ts` annotates `View` as
|
|
24
|
+
* `typeof RealView` — that is the point. It is the annotation this holds in place, and
|
|
25
|
+
* removing the annotation is the only way for the double to acquire a surface of its
|
|
26
|
+
* own.
|
|
27
|
+
*
|
|
28
|
+
* `SameKeys` comes from `parity.spec.ts`, which needs the same instrument for the same
|
|
29
|
+
* reason one level up: the package's own props are all-optional too.
|
|
30
|
+
*/
|
|
31
|
+
export type DoublePropsMatchReactNative = Assert<SameKeys<Parameters<typeof View>[0], Parameters<typeof RealView>[0]>>;
|
|
32
|
+
declare const _default: () => Promise<void>;
|
|
33
|
+
export default _default;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { AdwBinProps } from '../props.js';
|
|
3
|
+
/**
|
|
4
|
+
* A widget with one child.
|
|
5
|
+
*
|
|
6
|
+
* The declaration here is the CONTRACT both platform modules are held to by
|
|
7
|
+
* `parity.spec.ts`, so a platform module that renames a prop or changes an arity is a
|
|
8
|
+
* type error rather than a divergence somebody finds on one of the two devices.
|
|
9
|
+
*/
|
|
10
|
+
export declare function AdwBin(_props: AdwBinProps): ReactElement | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** @jsxImportSource @gjsify/gtk-host/react */
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
import type { AdwBinProps } from '../props.js';
|
|
4
|
+
/** {@link import('./bin.js').AdwBin} on GTK. */
|
|
5
|
+
export declare function AdwBin({ children }: AdwBinProps): ReactElement | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { AdwClampProps } from '../props.js';
|
|
3
|
+
/**
|
|
4
|
+
* Constrain a child's size and centre it.
|
|
5
|
+
*
|
|
6
|
+
* THE TWO IMPLEMENTATIONS SHARE THEIR ARITHMETIC AND NOTHING ELSE. On GTK the real
|
|
7
|
+
* `Adw.Clamp` computes the allocation in C; on React Native `@gjsify/adwaita-core`'s
|
|
8
|
+
* `clampAllocate` — a port of `adw_clamp_layout_allocate` — computes it in TypeScript.
|
|
9
|
+
* Both are libadwaita's easing curve, so the suites assert the same child size and the
|
|
10
|
+
* same centring offset on both sides rather than each side's own idea of "clamped".
|
|
11
|
+
*/
|
|
12
|
+
export declare function AdwClamp(_props: AdwClampProps): ReactElement | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** @jsxImportSource @gjsify/gtk-host/react */
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
import type { AdwClampProps } from '../props.js';
|
|
4
|
+
/** {@link import('./clamp.js').AdwClamp} on GTK. */
|
|
5
|
+
export declare function AdwClamp({ children, maximumSize, tighteningThreshold }: AdwClampProps): ReactElement | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { type ReactElement } from 'react';
|
|
3
|
+
import type { AdwClampProps } from '../props.js';
|
|
4
|
+
/**
|
|
5
|
+
* {@link import('./clamp.js').AdwClamp} on React Native.
|
|
6
|
+
*
|
|
7
|
+
* BEFORE THE FIRST LAYOUT there is no available width, so the child is rendered unclamped
|
|
8
|
+
* for exactly one frame. Rendering nothing instead would be worse than it sounds: the
|
|
9
|
+
* outer `View` is what `onLayout` fires on, so an empty first pass still costs the frame
|
|
10
|
+
* AND makes the child's own mount effects run a frame later than they do on GTK.
|
|
11
|
+
*/
|
|
12
|
+
export declare function AdwClamp({ children, maximumSize, tighteningThreshold }: AdwClampProps): ReactElement | null;
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/adwaita-react-native",
|
|
3
|
+
"version": "0.44.0",
|
|
4
|
+
"description": "Adwaita widgets with one API surface and two implementations — the real Adw.* widget on GTK4, React Native primitives on a phone",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"module": "lib/esm/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"react-native": "./lib/esm/index.native.js",
|
|
12
|
+
"default": "./lib/esm/index.gtk.js"
|
|
13
|
+
},
|
|
14
|
+
"./widgets/bin": {
|
|
15
|
+
"types": "./lib/types/widgets/bin.d.ts",
|
|
16
|
+
"react-native": "./lib/esm/widgets/bin.native.js",
|
|
17
|
+
"default": "./lib/esm/widgets/bin.gtk.js"
|
|
18
|
+
},
|
|
19
|
+
"./widgets/clamp": {
|
|
20
|
+
"types": "./lib/types/widgets/clamp.d.ts",
|
|
21
|
+
"react-native": "./lib/esm/widgets/clamp.native.js",
|
|
22
|
+
"default": "./lib/esm/widgets/clamp.gtk.js"
|
|
23
|
+
},
|
|
24
|
+
"./testing/react-native": {
|
|
25
|
+
"types": "./lib/types/testing/react-native.d.ts",
|
|
26
|
+
"default": "./lib/esm/testing/react-native.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"lib",
|
|
31
|
+
"!lib/types/**/*.spec.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"clear": "gjsify clear lib tmp tsconfig.tsbuildinfo test.gjs.mjs dist",
|
|
35
|
+
"check": "gjsify tsc --noEmit",
|
|
36
|
+
"build": "gjsify run build:gjsify && gjsify run build:types",
|
|
37
|
+
"build:gjsify": "gjsify build --library 'src/**/*.{ts,tsx,js}' --exclude 'src/**/*.spec.{mts,ts,tsx}' 'src/test.{mts,ts}' 'src/test.node.mts'",
|
|
38
|
+
"build:types": "gjsify tsc",
|
|
39
|
+
"build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
|
|
40
|
+
"build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs --define 'process.env.NODE_ENV=\"production\"' --exclude-globals navigator",
|
|
41
|
+
"build:test:node": "gjsify build src/test.node.mts --app node --outfile dist/test.node.mjs --exclude-globals navigator --alias react-native=@gjsify/adwaita-react-native/testing/react-native",
|
|
42
|
+
"test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:node && gjsify run test:gjs",
|
|
43
|
+
"test:gjs": "gjsify run test.gjs.mjs",
|
|
44
|
+
"test:node": "node dist/test.node.mjs"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"gjs",
|
|
48
|
+
"gtk",
|
|
49
|
+
"adwaita",
|
|
50
|
+
"react",
|
|
51
|
+
"react-native",
|
|
52
|
+
"design-system"
|
|
53
|
+
],
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@gjsify/adwaita-core": "^0.44.0",
|
|
56
|
+
"@gjsify/gtk-host": "^0.44.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"react": "^19.2.0",
|
|
60
|
+
"react-native": ">=0.87 <1"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"react-native": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@girs/adw-1": "^4.1.0",
|
|
69
|
+
"@girs/gjs": "^4.1.0",
|
|
70
|
+
"@girs/gtk-4.0": "^4.1.0",
|
|
71
|
+
"@gjsify/cli": "^0.44.0",
|
|
72
|
+
"@gjsify/unit": "^0.44.0",
|
|
73
|
+
"@types/node": "^25.9.2",
|
|
74
|
+
"@types/react": "^19.2.2",
|
|
75
|
+
"@types/react-test-renderer": "^19.1.0",
|
|
76
|
+
"react": "^19.2.0",
|
|
77
|
+
"react-native": ">=0.87 <1",
|
|
78
|
+
"react-test-renderer": "^19.2.8",
|
|
79
|
+
"typescript": "^6.0.3"
|
|
80
|
+
},
|
|
81
|
+
"gjsify": {
|
|
82
|
+
"runtimes": {
|
|
83
|
+
"gjs": "polyfill",
|
|
84
|
+
"node": "polyfill",
|
|
85
|
+
"browser": "none",
|
|
86
|
+
"nativescript": "none",
|
|
87
|
+
"react-native": "native"
|
|
88
|
+
},
|
|
89
|
+
"tier": 3
|
|
90
|
+
},
|
|
91
|
+
"license": "MIT",
|
|
92
|
+
"repository": {
|
|
93
|
+
"type": "git",
|
|
94
|
+
"url": "git+https://github.com/gjsify/gjsify.git",
|
|
95
|
+
"directory": "packages/framework/adwaita-react-native"
|
|
96
|
+
},
|
|
97
|
+
"bugs": {
|
|
98
|
+
"url": "https://github.com/gjsify/gjsify/issues"
|
|
99
|
+
},
|
|
100
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/adwaita-react-native#readme"
|
|
101
|
+
}
|