@tempots/native
Native iOS and Android rendering for the Tempo framework. Build cross-platform mobile apps using Tempo's declarative, signal-based rendering model with direct native view manipulation via a JSI bridge.
Installation
npm install @tempots/native
Overview
@tempots/native bridges the Tempo rendering model to native iOS and Android views through a JSI (JavaScript Interface) bridge. Instead of manipulating the DOM, it creates and manages native views (UIView on iOS, View on Android) while keeping the same declarative, signal-driven API that Tempo provides for the web.
Architecture
TypeScript (Tempo) <--> JSI Bridge <--> Native Views (iOS/Android)
Quick Start
import { renderNative, view, nativeStyle, nativeOn, prop } from '@tempots/native'
function Counter() {
const count = prop(0)
return view.View(
view.Text(
count.map(n => `Count: ${n}`),
nativeStyle.style({ fontSize: 48, textAlign: 'center', color: '#333' })
),
view.View(
view.View(
nativeOn.press(() => count.set(count.value - 1)),
view.Text('-'),
nativeStyle.style({ padding: 20, backgroundColor: '#ff6b6b' })
),
view.View(
nativeOn.press(() => count.set(count.value + 1)),
view.Text('+'),
nativeStyle.style({ padding: 20, backgroundColor: '#51cf66' })
),
nativeStyle.style({ flexDirection: 'row', justifyContent: 'center', gap: 20 })
),
nativeStyle.style({ flex: 1, justifyContent: 'center', alignItems: 'center' })
)
}
renderNative(Counter())
Native Views
Create native views using the view proxy object:
import { view } from '@tempots/native'
view.View(...) // Container view
view.Text('Hello') // Text display
view.Image(...) // Image display
view.ScrollView(...) // Scrollable container
view.TextInput(...) // Text input field
view.SafeAreaView(...) // Safe area container
view.FlatList(...) // Optimized list
view.Modal(...) // Modal overlay
view.Switch(...) // Toggle switch
view.MyCustomView(...) // Any custom native view type
Styles
Apply styles statically or reactively:
import { nativeStyle, applyStyle } from '@tempots/native'
import { prop } from '@tempots/core'
// Static styles
view.View(
nativeStyle.style({
flex: 1,
flexDirection: 'row',
backgroundColor: '#f0f0f0',
padding: 16,
borderRadius: 8,
})
)
// Reactive styles
const bg = prop('#f0f0f0')
view.View(
applyStyle(bg.map(color => ({ backgroundColor: color })))
)
Events
Handle native touch and input events:
import { nativeOn } from '@tempots/native'
view.View(
nativeOn.press((e) => console.log('Pressed at', e.pageX, e.pageY)),
nativeOn.longPress((e) => console.log('Long pressed')),
)
view.TextInput(
nativeOn.changeText((e) => console.log('Text:', e.text)),
nativeOn.submitEditing((e) => console.log('Submitted:', e.text)),
)
Shared Renderables
All standard Tempo renderables work with native contexts:
When/Unless- Conditional renderingForEach- List rendering from reactive arraysRepeat- Render N itemsOneOf/OneOfValue- Pattern matchingTask/Async- Async data loadingProvide/Use- Dependency injection
Navigation
In-memory navigation with a history stack:
import { createNavigator } from '@tempots/native'
type Route = 'home' | 'about' | 'settings'
const nav = createNavigator<Route>('home')
nav.navigate('about') // push "about" onto the stack
nav.back() // pop back to "home"
Lifecycle Signals
Track app-level state changes:
import { createAppStateSignal, createDimensionsSignal, createKeyboardSignal } from '@tempots/native'
const appState = createAppStateSignal(bridge) // "active" | "background" | "inactive"
const dims = createDimensionsSignal(bridge) // { width, height, scale, fontScale }
const keyboard = createKeyboardSignal(bridge) // { visible: boolean, height: number }
For more information, see the Tempo documentation.