Skip to content

Commit 1d6fe92

Browse files
authored
feat(components,native_modules): expand component and device APIs (#7)
1 parent c798d98 commit 1d6fe92

43 files changed

Lines changed: 5606 additions & 124 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ PythonNative is a cross-platform toolkit for building native Android and iOS app
3131
## Features
3232

3333
- **Declarative UI:** Describe *what* your UI should look like with element functions (`Text`, `Button`, `Column`, `Row`, etc.). PythonNative creates and updates native views automatically.
34+
- **Rich component library:** 25+ built-in components backed by real native widgets — `TextInput`, `Image` / `ImageBackground`, `ScrollView`, `FlatList` / `SectionList`, `Modal`, `Pressable` / `TouchableOpacity`, `Switch` / `Checkbox`, `Slider`, `SegmentedControl`, `Picker`, `DatePicker`, `ProgressBar` / `ActivityIndicator`, `WebView`, and more.
35+
- **Device APIs:** Cross-platform modules for `Camera`, `Location`, `FileSystem`, `Notifications`, `Clipboard`, `Share`, `Linking`, `Permissions`, `AppState`, `NetInfo`, `SecureStore`, `Battery`, `Haptics` / `Vibration`, and `Biometrics` — plus reactive `use_app_state` and `use_net_info` hooks.
3436
- **Hooks and function components:** Manage state with `use_state`, side effects with `use_effect`, and navigation with `use_navigation`, all through one consistent pattern.
3537
- **Typed `style` prop:** Pass all visual and layout properties through a single `style` dict, fully described by the `pn.Style` `TypedDict` and the ergonomic `pn.style(...)` helper for IDE autocomplete and static checking. Compose reusable styles with `StyleSheet`.
3638
- **Cross-platform flexbox engine:** A pure-Python, Yoga-style layout engine computes frames once and applies them to native views, so `flex`, `padding`, `aspect_ratio`, and `position: "absolute"` produce the same geometry on Android and iOS.

docs/api/component-properties.md

Lines changed: 189 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,50 @@ Container that respects safe area insets (notch, status bar).
142142
## ScrollView
143143

144144
```python
145-
pn.ScrollView(child, style={"background_color": "#FFF"})
145+
pn.ScrollView(
146+
child,
147+
scroll_axis="vertical", # or "horizontal"
148+
on_scroll=lambda x, y: ..., # content offset as the user scrolls
149+
shows_scroll_indicator=True,
150+
paging_enabled=False, # snap to viewport-sized pages (carousel)
151+
bounces=True, # iOS rubber-band overscroll
152+
keyboard_dismiss_mode=None, # "none" | "on_drag" | "interactive"
153+
content_container_style={"padding": 16},
154+
refresh_control=pn.RefreshControl(refreshing=loading, on_refresh=reload),
155+
style={"background_color": "#FFF"},
156+
)
146157
```
147158

159+
- `scroll_axis``"vertical"` (default) or `"horizontal"`
160+
- `on_scroll` — callback `(x, y) -> None` with the content offset
161+
- `shows_scroll_indicator` — show/hide the scroll bar
162+
- `paging_enabled` — snap scrolling to multiples of the viewport size
163+
- `bounces` — enable the iOS overscroll bounce
164+
- `content_container_style` — style for the inner content wrapper (padding,
165+
alignment, spacing), distinct from `style` (the scroll frame)
166+
- `keyboard_dismiss_mode``"none"`, `"on_drag"`, or `"interactive"`
167+
- `refresh_control` — pull-to-refresh spec (see [`RefreshControl`](#refreshcontrol))
168+
148169
## TextInput
149170

150171
```python
151172
pn.TextInput(value="", placeholder="Enter text", on_change=handler, secure=False,
173+
editable=True, clear_button=True, on_focus=f, on_blur=g,
174+
selection_color="#007AFF", text_content_type="password",
152175
style={"font_size": 16, "color": "#000", "background_color": "#FFF"})
153176
```
154177

155178
- `on_change` — callback `(str) -> None` receiving new text
179+
- `on_submit` — callback `(str) -> None` on Return / Done
180+
- `editable` — when `False`, the field is read-only
181+
- `clear_button` — show an inline clear ("x") button while editing
182+
- `on_focus` / `on_blur` — callbacks `() -> None` on focus changes
183+
- `selection_color` — cursor / selection highlight color
184+
- `text_content_type` — autofill hint (`"username"`, `"password"`,
185+
`"one_time_code"`, …)
186+
- Other kwargs: `secure`, `multiline`, `keyboard_type`, `auto_capitalize`,
187+
`auto_correct`, `auto_focus`, `return_key_type`, `max_length`,
188+
`placeholder_color`
156189

157190
## Image
158191

@@ -182,23 +215,46 @@ pn.Slider(value=0.5, min_value=0.0, max_value=1.0, on_change=handler)
182215
## ProgressBar
183216

184217
```python
185-
pn.ProgressBar(value=0.5, style={"background_color": "#EEE"})
218+
pn.ProgressBar(value=0.5, color="#007AFF", track_color="#EEE", indeterminate=False)
186219
```
187220

188221
- `value` — 0.0 to 1.0
222+
- `color` — color of the filled portion
223+
- `track_color` — color of the unfilled track
224+
- `indeterminate` — animate continuously and ignore `value`
189225

190226
## ActivityIndicator
191227

192228
```python
193-
pn.ActivityIndicator(animating=True)
229+
pn.ActivityIndicator(animating=True, color="#007AFF", size="large")
194230
```
195231

232+
- `animating` — hide the spinner when `False`
233+
- `color` — spinner color
234+
- `size``"small"` (default) or `"large"`
235+
196236
## WebView
197237

198238
```python
199-
pn.WebView(url="https://example.com")
239+
pn.WebView(
240+
url="https://example.com",
241+
html=None, # render inline HTML instead of a URL
242+
on_load=lambda url: ..., # page finished loading
243+
on_message=lambda msg: ..., # window.pythonnative.postMessage(...)
244+
on_navigation_state_change=lambda url: ...,
245+
inject_javascript="document.body.style.background='#fff'",
246+
scroll_enabled=True,
247+
)
200248
```
201249

250+
- `url` — page to load (ignored when `html` is given)
251+
- `html` — inline HTML markup
252+
- `on_load` — callback `(url) -> None` when a page finishes loading
253+
- `on_message` — callback `(str) -> None` for messages posted from page JS
254+
- `on_navigation_state_change` — callback `(url) -> None` on navigation
255+
- `inject_javascript` — JS evaluated after each page load
256+
- `scroll_enabled` — allow scrolling within the web content
257+
202258
## Spacer
203259

204260
```python
@@ -219,12 +275,123 @@ Wraps any child element with tap/long-press handling.
219275
## Modal
220276

221277
```python
222-
pn.Modal(*children, visible=show_modal, on_dismiss=handler, title="Confirm",
278+
pn.Modal(*children, visible=show_modal, on_dismiss=handler, on_show=shown,
279+
title="Confirm", animation_type="slide", transparent=False,
280+
presentation_style="page_sheet", dismiss_on_backdrop=True,
223281
style={"background_color": "#FFF"})
224282
```
225283

226284
Overlay dialog shown when `visible=True`.
227285

286+
- `on_dismiss` — callback `() -> None` when dismissed via gesture
287+
- `on_show` — callback `() -> None` once the modal finishes presenting
288+
- `animation_type``"slide"` (default), `"fade"`, `"none"`
289+
- `transparent` — dim the underlying view instead of fully covering it
290+
- `presentation_style``"page_sheet"` (default), `"form_sheet"`,
291+
`"full_screen"`, `"overlay"`
292+
- `dismiss_on_backdrop` — tapping the dimmed backdrop dismisses the modal
293+
294+
## TouchableOpacity
295+
296+
```python
297+
pn.TouchableOpacity(child, on_press=handler, on_long_press=handler,
298+
active_opacity=0.2, disabled=False)
299+
```
300+
301+
Tappable wrapper that dips to `active_opacity` while pressed (a thin alias
302+
over [`Pressable`](#pressable)). `disabled=True` ignores presses and dims the
303+
content.
304+
305+
## ImageBackground
306+
307+
```python
308+
pn.ImageBackground(child, source="bg.png", scale_type="cover",
309+
style={"width": 320, "height": 180, "padding": 16})
310+
```
311+
312+
Renders `child` content layered over a background image. Composed from an
313+
absolutely-filled `Image` plus a content `View`.
314+
315+
- `source` — image resource name or URL
316+
- `scale_type``"cover"` (default), `"contain"`, `"stretch"`, `"center"`
317+
318+
## Checkbox
319+
320+
```python
321+
pn.Checkbox(value=accepted, on_change=set_accepted, label="Accept terms",
322+
disabled=False, color="#007AFF")
323+
```
324+
325+
- `value` — checked state (`bool`)
326+
- `on_change` — callback `(bool) -> None`
327+
- `label` — optional inline label (also tappable)
328+
- `disabled` — grey out and ignore input
329+
- `color` — tint for the checked box
330+
331+
| Platform | Native view |
332+
|----------|--------------------------|
333+
| Android | `android.widget.CheckBox` |
334+
| iOS | checkmark `UIButton` |
335+
336+
## SegmentedControl
337+
338+
```python
339+
pn.SegmentedControl(segments=["Day", "Week", "Month"], selected_index=0,
340+
on_change=handler, tint_color="#007AFF")
341+
```
342+
343+
- `segments` — list of segment label strings
344+
- `selected_index` — index of the selected segment
345+
- `on_change` — callback `(int) -> None` with the new index
346+
- `tint_color` — accent color for the selected segment
347+
348+
| Platform | Native view |
349+
|----------|--------------------------|
350+
| Android | toggle row (`LinearLayout` of buttons) |
351+
| iOS | `UISegmentedControl` |
352+
353+
## DatePicker
354+
355+
```python
356+
pn.DatePicker(value="2026-05-31", mode="date", on_change=handler,
357+
minimum="2026-01-01", maximum="2026-12-31")
358+
```
359+
360+
ISO-8601 string values: `"YYYY-MM-DD"` (date), `"HH:MM"` (time),
361+
`"YYYY-MM-DDTHH:MM"` (datetime).
362+
363+
- `value` — current selection (ISO-8601 string)
364+
- `mode``"date"` (default), `"time"`, `"datetime"`
365+
- `on_change` — callback `(str) -> None` with the new ISO-8601 string
366+
- `minimum` / `maximum` — selectable bounds
367+
368+
| Platform | Native view |
369+
|----------|--------------------------|
370+
| Android | `DatePickerDialog` / `TimePickerDialog` |
371+
| iOS | `UIDatePicker` |
372+
373+
## Picker
374+
375+
```python
376+
pn.Picker(value=selected, items=[{"value": "a", "label": "Apple"}],
377+
on_change=handler, placeholder="Select…")
378+
```
379+
380+
Native dropdown / select. `items` is a list of `{"value": Any, "label": str}`.
381+
382+
## RefreshControl
383+
384+
```python
385+
pn.ScrollView(child, refresh_control=pn.RefreshControl(
386+
refreshing=loading, on_refresh=reload, tint_color="#007AFF"))
387+
```
388+
389+
Pull-to-refresh spec (a plain dict) passed to a `ScrollView` or `FlatList`.
390+
391+
- `refreshing` — drive the spinner from state
392+
- `on_refresh` — callback `() -> None` when pulled past threshold
393+
- `tint_color` — spinner color
394+
228395
## TabBar
229396

230397
```python
@@ -253,10 +420,26 @@ Native tab bar — typically created automatically by `Tab.Navigator`.
253420

254421
```python
255422
pn.FlatList(data=items, render_item=render_fn, key_extractor=key_fn,
256-
separator_height=1, style={"background_color": "#FFF"})
423+
item_height=44, separator_height=1,
424+
horizontal=False, num_columns=1,
425+
list_header=pn.Text("Header"), list_footer=pn.Text("Footer"),
426+
list_empty=pn.Text("Nothing here"),
427+
on_end_reached=load_more, on_end_reached_threshold=0.5,
428+
refresh_control=pn.RefreshControl(refreshing=loading, on_refresh=reload),
429+
content_container_style={"padding": 8},
430+
style={"background_color": "#FFF"})
257431
```
258432

259433
- `data` — list of items
260434
- `render_item``(item, index) -> Element` function
261435
- `key_extractor``(item, index) -> str` for stable keys
436+
- `item_height` — fixed row height; enables native virtualization
262437
- `separator_height` — spacing between items
438+
- `horizontal` — lay rows out left-to-right (eager backend)
439+
- `num_columns` — render as a grid of N columns (eager backend)
440+
- `list_header` / `list_footer` — elements rendered once above / below rows
441+
- `list_empty` — element rendered when `data` is empty
442+
- `on_end_reached` — callback `() -> None` near the end (virtualized)
443+
- `on_end_reached_threshold` — fraction-of-viewport trigger distance
444+
- `content_container_style` — style for the inner content wrapper
445+
- `refresh_control` — pull-to-refresh spec (see [`RefreshControl`](#refreshcontrol))

docs/api/native_modules.md

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
# Native modules
22

33
Cross-platform wrappers around device APIs that are not part of the
4-
view tree: camera and photo gallery, GPS, app-scoped file I/O, and
5-
local notifications. Each module is implemented twice (once per
4+
view tree: camera, GPS, file I/O, notifications, clipboard, share
5+
sheet, deep links, permissions, connectivity, secure storage, battery,
6+
haptics, and biometrics. Each module is implemented twice (once per
67
platform) and dispatches at runtime based on the `IS_ANDROID` and
7-
`IS_IOS` flags from `pythonnative.utils`.
8+
`IS_IOS` flags from `pythonnative.utils`, with a safe desktop fallback.
89

9-
Apart from `FileSystem`, every public method is a coroutine: ``await
10-
Camera.take_photo()``, ``await Location.get_current()``, and so on.
11-
For the call-site patterns and the runtime they're scheduled on, see
12-
the [Async + data guide](../guides/async.md).
10+
Both synchronous and coroutine APIs exist (chosen to match the
11+
platform call). For the call-site patterns, the reactive
12+
`use_app_state` / `use_net_info` hooks, and the runtime coroutines are
13+
scheduled on, see the [Native modules guide](../guides/native-modules.md)
14+
and the [Async + data guide](../guides/async.md).
1315

1416
## Camera
1517

@@ -47,6 +49,96 @@ the [Async + data guide](../guides/async.md).
4749
members_order: source
4850
filters: ["!^_"]
4951

52+
## Clipboard
53+
54+
::: pythonnative.native_modules.clipboard
55+
options:
56+
show_root_heading: false
57+
show_root_toc_entry: false
58+
members_order: source
59+
filters: ["!^_"]
60+
61+
## Share
62+
63+
::: pythonnative.native_modules.share
64+
options:
65+
show_root_heading: false
66+
show_root_toc_entry: false
67+
members_order: source
68+
filters: ["!^_"]
69+
70+
## Linking
71+
72+
::: pythonnative.native_modules.linking
73+
options:
74+
show_root_heading: false
75+
show_root_toc_entry: false
76+
members_order: source
77+
filters: ["!^_"]
78+
79+
## Permissions
80+
81+
::: pythonnative.native_modules.permissions
82+
options:
83+
show_root_heading: false
84+
show_root_toc_entry: false
85+
members_order: source
86+
filters: ["!^_"]
87+
88+
## App state
89+
90+
::: pythonnative.native_modules.app_state
91+
options:
92+
show_root_heading: false
93+
show_root_toc_entry: false
94+
members_order: source
95+
filters: ["!^_"]
96+
97+
## Network connectivity
98+
99+
::: pythonnative.native_modules.net_info
100+
options:
101+
show_root_heading: false
102+
show_root_toc_entry: false
103+
members_order: source
104+
filters: ["!^_"]
105+
106+
## Secure storage
107+
108+
::: pythonnative.native_modules.secure_store
109+
options:
110+
show_root_heading: false
111+
show_root_toc_entry: false
112+
members_order: source
113+
filters: ["!^_"]
114+
115+
## Battery
116+
117+
::: pythonnative.native_modules.battery
118+
options:
119+
show_root_heading: false
120+
show_root_toc_entry: false
121+
members_order: source
122+
filters: ["!^_"]
123+
124+
## Haptics & vibration
125+
126+
::: pythonnative.native_modules.haptics
127+
options:
128+
show_root_heading: false
129+
show_root_toc_entry: false
130+
members_order: source
131+
filters: ["!^_"]
132+
133+
## Biometrics
134+
135+
::: pythonnative.native_modules.biometrics
136+
options:
137+
show_root_heading: false
138+
show_root_toc_entry: false
139+
members_order: source
140+
filters: ["!^_"]
141+
50142
## Next steps
51143

52144
- See guidance and permission setup in [Native modules guide](../guides/native-modules.md).

0 commit comments

Comments
 (0)